summaryrefslogtreecommitdiff
path: root/source4/scripting/python
diff options
context:
space:
mode:
authorbubulle <bubulle@alioth.debian.org>2012-01-26 19:58:37 +0000
committerbubulle <bubulle@alioth.debian.org>2012-01-26 19:58:37 +0000
commitcb25bc5ca98dff7a896f596f9f1586a4739ad8ec (patch)
tree31bd310956a0c533e3e46cb88aec6e00b5eedf53 /source4/scripting/python
parent5f021ee1efe415ba8fe4281d0622204a68074ea8 (diff)
downloadsamba-cb25bc5ca98dff7a896f596f9f1586a4739ad8ec.tar.gz
Load samba-3.6.2 into branches/samba/upstream.upstream/3.6.2
git-svn-id: svn://svn.debian.org/svn/pkg-samba/branches/samba/upstream@3992 fc4039ab-9d04-0410-8cac-899223bdd6b0
Diffstat (limited to 'source4/scripting/python')
-rw-r--r--source4/scripting/python/config.mk35
-rw-r--r--source4/scripting/python/examples/netbios.py2
-rwxr-xr-xsource4/scripting/python/examples/samr.py2
-rwxr-xr-xsource4/scripting/python/examples/winreg.py2
-rw-r--r--source4/scripting/python/modules.c77
-rw-r--r--source4/scripting/python/modules.h5
-rw-r--r--source4/scripting/python/pyglue.c540
-rw-r--r--source4/scripting/python/samba/__init__.py375
-rw-r--r--source4/scripting/python/samba/drs_utils.py173
-rw-r--r--source4/scripting/python/samba/getopt.py160
-rw-r--r--source4/scripting/python/samba/hostconfig.py66
-rw-r--r--source4/scripting/python/samba/idmap.py29
-rw-r--r--source4/scripting/python/samba/join.py582
-rw-r--r--source4/scripting/python/samba/ms_display_specifiers.py2
-rw-r--r--source4/scripting/python/samba/ms_schema.py35
-rw-r--r--source4/scripting/python/samba/ndr.py31
-rw-r--r--source4/scripting/python/samba/netcmd/__init__.py215
-rw-r--r--source4/scripting/python/samba/netcmd/common.py24
-rw-r--r--source4/scripting/python/samba/netcmd/domainlevel.py247
-rw-r--r--source4/scripting/python/samba/netcmd/drs.py477
-rw-r--r--source4/scripting/python/samba/netcmd/dsacl.py182
-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.py197
-rw-r--r--source4/scripting/python/samba/netcmd/gpo.py245
-rw-r--r--source4/scripting/python/samba/netcmd/group.py189
-rw-r--r--source4/scripting/python/samba/netcmd/join.py78
-rwxr-xr-xsource4/scripting/python/samba/netcmd/ldapcmp.py949
-rw-r--r--source4/scripting/python/samba/netcmd/machinepw.py53
-rw-r--r--source4/scripting/python/samba/netcmd/netacl.py33
-rw-r--r--source4/scripting/python/samba/netcmd/newuser.py97
-rw-r--r--source4/scripting/python/samba/netcmd/ntacl.py119
-rw-r--r--source4/scripting/python/samba/netcmd/pwsettings.py197
-rw-r--r--source4/scripting/python/samba/netcmd/rodc.py115
-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/spn.py217
-rw-r--r--source4/scripting/python/samba/netcmd/time.py47
-rw-r--r--source4/scripting/python/samba/netcmd/user.py76
-rw-r--r--source4/scripting/python/samba/netcmd/vampire.py56
-rw-r--r--source4/scripting/python/samba/ntacls.py171
-rw-r--r--source4/scripting/python/samba/provision.py2067
-rw-r--r--source4/scripting/python/samba/provision/__init__.py1929
-rw-r--r--source4/scripting/python/samba/provision/backend.py772
-rw-r--r--source4/scripting/python/samba/samba3.py14
-rw-r--r--source4/scripting/python/samba/samdb.py678
-rw-r--r--source4/scripting/python/samba/schema.py204
-rw-r--r--source4/scripting/python/samba/sd_utils.py79
-rw-r--r--source4/scripting/python/samba/shares.py61
-rw-r--r--source4/scripting/python/samba/tests/__init__.py213
-rw-r--r--source4/scripting/python/samba/tests/auth.py33
-rw-r--r--source4/scripting/python/samba/tests/blackbox/__init__.py1
-rwxr-xr-xsource4/scripting/python/samba/tests/blackbox/ndrdump.py36
-rw-r--r--source4/scripting/python/samba/tests/blackbox/samba_tool_drs.py100
-rw-r--r--source4/scripting/python/samba/tests/core.py65
-rw-r--r--source4/scripting/python/samba/tests/credentials.py100
-rw-r--r--source4/scripting/python/samba/tests/dcerpc/__init__.py9
-rw-r--r--source4/scripting/python/samba/tests/dcerpc/bare.py23
-rw-r--r--source4/scripting/python/samba/tests/dcerpc/misc.py14
-rw-r--r--source4/scripting/python/samba/tests/dcerpc/registry.py5
-rwxr-xr-xsource4/scripting/python/samba/tests/dcerpc/rpc_talloc.py70
-rw-r--r--source4/scripting/python/samba/tests/dcerpc/rpcecho.py12
-rw-r--r--source4/scripting/python/samba/tests/dcerpc/sam.py6
-rw-r--r--source4/scripting/python/samba/tests/dcerpc/testrpc.py130
-rw-r--r--source4/scripting/python/samba/tests/dcerpc/unix.py19
-rw-r--r--source4/scripting/python/samba/tests/dsdb.py135
-rw-r--r--source4/scripting/python/samba/tests/gensec.py90
-rw-r--r--source4/scripting/python/samba/tests/hostconfig.py (renamed from source4/scripting/python/samba/tests/shares.py)12
-rw-r--r--source4/scripting/python/samba/tests/messaging.py60
-rw-r--r--source4/scripting/python/samba/tests/netcmd.py35
-rw-r--r--source4/scripting/python/samba/tests/ntacls.py101
-rw-r--r--source4/scripting/python/samba/tests/param.py59
-rw-r--r--source4/scripting/python/samba/tests/provision.py45
-rw-r--r--source4/scripting/python/samba/tests/registry.py62
-rw-r--r--source4/scripting/python/samba/tests/samba3.py72
-rw-r--r--source4/scripting/python/samba/tests/samba3sam.py1096
-rw-r--r--source4/scripting/python/samba/tests/samdb.py76
-rw-r--r--source4/scripting/python/samba/tests/security.py145
-rw-r--r--source4/scripting/python/samba/tests/upgrade.py6
-rw-r--r--source4/scripting/python/samba/tests/upgradeprovision.py139
-rw-r--r--source4/scripting/python/samba/tests/upgradeprovisionneeddc.py180
-rw-r--r--source4/scripting/python/samba/tests/xattr.py105
-rwxr-xr-xsource4/scripting/python/samba/torture/pytorture51
-rw-r--r--source4/scripting/python/samba/torture/spoolss.py437
-rwxr-xr-xsource4/scripting/python/samba/torture/torture_samr.py221
-rwxr-xr-xsource4/scripting/python/samba/torture/torture_tdb.py90
-rwxr-xr-xsource4/scripting/python/samba/torture/winreg.py165
-rw-r--r--source4/scripting/python/samba/upgrade.py188
-rwxr-xr-xsource4/scripting/python/samba/upgradehelpers.py953
-rw-r--r--source4/scripting/python/samba/web_server/__init__.py52
-rw-r--r--source4/scripting/python/samba_external/README4
-rw-r--r--source4/scripting/python/uuidmodule.c2
-rw-r--r--source4/scripting/python/wscript_build39
93 files changed, 13389 insertions, 4234 deletions
diff --git a/source4/scripting/python/config.mk b/source4/scripting/python/config.mk
deleted file mode 100644
index a5e3f25d59..0000000000
--- a/source4/scripting/python/config.mk
+++ /dev/null
@@ -1,35 +0,0 @@
-[SUBSYSTEM::LIBPYTHON]
-PUBLIC_DEPENDENCIES = EXT_LIB_PYTHON
-PRIVATE_DEPENDENCIES = PYTALLOC
-INIT_FUNCTION_SENTINEL = { NULL, NULL }
-
-LIBPYTHON_OBJ_FILES = $(addprefix $(pyscriptsrcdir)/, modules.o)
-
-[SUBSYSTEM::PYTALLOC]
-PUBLIC_DEPENDENCIES = EXT_LIB_PYTHON LIBTALLOC
-
-PYTALLOC_OBJ_FILES = ../lib/talloc/pytalloc.o
-
-[PYTHON::python_uuid]
-PRIVATE_DEPENDENCIES = LIBNDR
-
-python_uuid_OBJ_FILES = $(pyscriptsrcdir)/uuidmodule.o
-
-[PYTHON::python_glue]
-LIBRARY_REALNAME = samba/glue.$(SHLIBEXT)
-PRIVATE_DEPENDENCIES = LIBNDR LIBLDB SAMDB CREDENTIALS pyldb python_dcerpc_misc python_dcerpc_security pyauth pyldb_util pyparam_util
-
-python_glue_OBJ_FILES = $(pyscriptsrcdir)/pyglue.o
-
-$(python_glue_OBJ_FILES): CFLAGS+=-I$(ldbsrcdir)
-
-_PY_FILES = $(shell find $(pyscriptsrcdir)/samba ../lib/subunit/python -name "*.py")
-
-$(eval $(foreach pyfile, $(_PY_FILES),$(call python_py_module_template,$(patsubst $(pyscriptsrcdir)/%,%,$(subst ../lib/subunit/python,,$(pyfile))),$(pyfile))))
-
-EPYDOC_OPTIONS = --no-private --url http://www.samba.org/ --no-sourcecode
-
-epydoc:: pythonmods
- PYTHONPATH=$(pythonbuilddir):../lib/subunit/python epydoc $(EPYDOC_OPTIONS) samba tdb ldb subunit
-
-install:: installpython
diff --git a/source4/scripting/python/examples/netbios.py b/source4/scripting/python/examples/netbios.py
index 3671076a59..a29b09f051 100644
--- a/source4/scripting/python/examples/netbios.py
+++ b/source4/scripting/python/examples/netbios.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2008
diff --git a/source4/scripting/python/examples/samr.py b/source4/scripting/python/examples/samr.py
index c0e3167a97..9b8e31e434 100755
--- a/source4/scripting/python/examples/samr.py
+++ b/source4/scripting/python/examples/samr.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Unix SMB/CIFS implementation.
diff --git a/source4/scripting/python/examples/winreg.py b/source4/scripting/python/examples/winreg.py
index 80b48ecfd7..484a69b842 100755
--- a/source4/scripting/python/examples/winreg.py
+++ b/source4/scripting/python/examples/winreg.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
#
# tool to manipulate a remote registry
# Copyright Andrew Tridgell 2005
diff --git a/source4/scripting/python/modules.c b/source4/scripting/python/modules.c
index e53f4cfaf2..78cdbc0d87 100644
--- a/source4/scripting/python/modules.c
+++ b/source4/scripting/python/modules.c
@@ -17,54 +17,47 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <Python.h>
#include "includes.h"
#include "scripting/python/modules.h"
-#include <Python.h>
+#include "dynconfig/dynconfig.h"
-extern void init_ldb(void);
-extern void init_security(void);
-extern void init_registry(void);
-extern void init_param(void);
-extern void init_misc(void);
-extern void init_ldb(void);
-extern void init_auth(void);
-extern void init_credentials(void);
-extern void init_tdb(void);
-extern void init_dcerpc(void);
-extern void init_events(void);
-extern void inituuid(void);
-extern void init_net(void);
-extern void initecho(void);
-extern void initdfs(void);
-extern void initdrsuapi(void);
-extern void initwinreg(void);
-extern void initepmapper(void);
-extern void initinitshutdown(void);
-extern void initmgmt(void);
-extern void initnet(void);
-extern void initatsvc(void);
-extern void initsamr(void);
-extern void initlsa(void);
-extern void initsvcctl(void);
-extern void initwkssvc(void);
-extern void initunixinfo(void);
-extern void init_libcli_nbt(void);
-extern void init_libcli_smb(void);
+static bool PySys_PathPrepend(PyObject *list, const char *path)
+{
+ PyObject *py_path = PyString_FromString(path);
+ if (py_path == NULL)
+ return false;
-static struct _inittab py_modules[] = { STATIC_LIBPYTHON_MODULES };
+ return (PyList_Insert(list, 0, py_path) == 0);
+}
-void py_load_samba_modules(void)
+bool py_update_path(void)
{
- int i;
- for (i = 0; i < ARRAY_SIZE(py_modules); i++) {
- PyImport_ExtendInittab(&py_modules[i]);
+ PyObject *mod_sys, *py_path;
+
+ mod_sys = PyImport_ImportModule("sys");
+ if (mod_sys == NULL) {
+ return false;
}
-}
-void py_update_path(const char *bindir)
-{
- char *newpath;
- asprintf(&newpath, "%s/python:%s/../scripting/python:%s", bindir, bindir, Py_GetPath());
- PySys_SetPath(newpath);
- free(newpath);
+ py_path = PyObject_GetAttrString(mod_sys, "path");
+ if (py_path == NULL) {
+ return false;
+ }
+
+ if (!PyList_Check(py_path)) {
+ return false;
+ }
+
+ if (!PySys_PathPrepend(py_path, dyn_PYTHONDIR)) {
+ return false;
+ }
+
+ if (strcmp(dyn_PYTHONARCHDIR, dyn_PYTHONDIR) != 0) {
+ if (!PySys_PathPrepend(py_path, dyn_PYTHONARCHDIR)) {
+ return false;
+ }
+ }
+
+ return true;
}
diff --git a/source4/scripting/python/modules.h b/source4/scripting/python/modules.h
index 6b242ee257..e7e97aa1bf 100644
--- a/source4/scripting/python/modules.h
+++ b/source4/scripting/python/modules.h
@@ -20,9 +20,6 @@
#ifndef __SAMBA_PYTHON_MODULES_H__
#define __SAMBA_PYTHON_MODULES_H__
-void py_load_samba_modules(void);
-void py_update_path(const char *bindir);
-
-#define py_iconv_convenience(mem_ctx) smb_iconv_convenience_init(mem_ctx, "ASCII", PyUnicode_GetDefaultEncoding(), true)
+bool py_update_path(void);
#endif /* __SAMBA_PYTHON_MODULES_H__ */
diff --git a/source4/scripting/python/pyglue.c b/source4/scripting/python/pyglue.c
index 753f2df464..f89785f971 100644
--- a/source4/scripting/python/pyglue.c
+++ b/source4/scripting/python/pyglue.c
@@ -17,55 +17,13 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <Python.h>
#include "includes.h"
-#include "ldb.h"
-#include "ldb_errors.h"
-#include "ldb_wrap.h"
-#include "param/param.h"
-#include "auth/credentials/credentials.h"
-#include "dsdb/samdb/samdb.h"
-#include "lib/ldb-samba/ldif_handlers.h"
-#include "librpc/ndr/libndr.h"
#include "version.h"
-#include <Python.h>
-#include "lib/ldb/pyldb.h"
-#include "libcli/util/pyerrors.h"
-#include "libcli/security/security.h"
-#include "auth/pyauth.h"
#include "param/pyparam.h"
-#include "auth/credentials/pycredentials.h"
-
-#ifndef Py_RETURN_NONE
-#define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None
-#endif
-
-/* FIXME: These should be in a header file somewhere, once we finish moving
- * away from SWIG .. */
-#define PyErr_LDB_OR_RAISE(py_ldb, ldb) \
-/* if (!PyLdb_Check(py_ldb)) { \
- PyErr_SetString(py_ldb_get_exception(), "Ldb connection object required"); \
- return NULL; \
- } */\
- ldb = PyLdb_AsLdbContext(py_ldb);
-
-static void PyErr_SetLdbError(PyObject *error, int ret, struct ldb_context *ldb_ctx)
-{
- if (ret == LDB_ERR_PYTHON_EXCEPTION)
- return; /* Python exception should already be set, just keep that */
-
- PyErr_SetObject(error,
- Py_BuildValue(discard_const_p(char, "(i,s)"), ret,
- ldb_ctx == NULL?ldb_strerror(ret):ldb_errstring(ldb_ctx)));
-}
+#include "lib/socket/netif.h"
-static PyObject *py_ldb_get_exception(void)
-{
- PyObject *mod = PyImport_ImportModule("ldb");
- if (mod == NULL)
- return NULL;
-
- return PyObject_GetAttrString(mod, "LdbError");
-}
+void init_glue(void);
static PyObject *py_generate_random_str(PyObject *self, PyObject *args)
{
@@ -81,473 +39,181 @@ static PyObject *py_generate_random_str(PyObject *self, PyObject *args)
return ret;
}
-static PyObject *py_unix2nttime(PyObject *self, PyObject *args)
-{
- time_t t;
- NTTIME nt;
- if (!PyArg_ParseTuple(args, "I", &t))
- return NULL;
-
- unix_to_nt_time(&nt, t);
-
- return PyInt_FromLong((uint64_t)nt);
-}
-
-static PyObject *py_set_debug_level(PyObject *self, PyObject *args)
-{
- unsigned level;
- if (!PyArg_ParseTuple(args, "I", &level))
- return NULL;
- (DEBUGLEVEL) = level;
- Py_RETURN_NONE;
-}
-
-static PyObject *py_ldb_set_credentials(PyObject *self, PyObject *args)
-{
- PyObject *py_creds, *py_ldb;
- struct cli_credentials *creds;
- struct ldb_context *ldb;
- if (!PyArg_ParseTuple(args, "OO", &py_ldb, &py_creds))
- return NULL;
-
- PyErr_LDB_OR_RAISE(py_ldb, ldb);
-
- creds = cli_credentials_from_py_object(py_creds);
- if (creds == NULL) {
- PyErr_SetString(PyExc_TypeError, "Expected credentials object");
- return NULL;
- }
-
- ldb_set_opaque(ldb, "credentials", creds);
-
- Py_RETURN_NONE;
-}
-
-static PyObject *py_ldb_set_loadparm(PyObject *self, PyObject *args)
+static PyObject *py_generate_random_password(PyObject *self, PyObject *args)
{
- PyObject *py_lp_ctx, *py_ldb;
- struct loadparm_context *lp_ctx;
- struct ldb_context *ldb;
- if (!PyArg_ParseTuple(args, "OO", &py_ldb, &py_lp_ctx))
- return NULL;
-
- PyErr_LDB_OR_RAISE(py_ldb, ldb);
-
- lp_ctx = lp_from_py_object(py_lp_ctx);
- if (lp_ctx == NULL) {
- PyErr_SetString(PyExc_TypeError, "Expected loadparm object");
- return NULL;
- }
-
- ldb_set_opaque(ldb, "loadparm", lp_ctx);
-
- Py_RETURN_NONE;
-}
-
-
-static PyObject *py_ldb_set_session_info(PyObject *self, PyObject *args)
-{
- PyObject *py_session_info, *py_ldb;
- struct auth_session_info *info;
- struct ldb_context *ldb;
- if (!PyArg_ParseTuple(args, "OO", &py_ldb, &py_session_info))
- return NULL;
-
- PyErr_LDB_OR_RAISE(py_ldb, ldb);
- /*if (!PyAuthSession_Check(py_session_info)) {
- PyErr_SetString(PyExc_TypeError, "Expected session info object");
- return NULL;
- }*/
-
- info = PyAuthSession_AsSession(py_session_info);
-
- ldb_set_opaque(ldb, "sessionInfo", info);
-
- Py_RETURN_NONE;
-}
-
-static PyObject *py_ldb_set_utf8_casefold(PyObject *self, PyObject *args)
-{
- PyObject *py_ldb;
- struct ldb_context *ldb;
-
- if (!PyArg_ParseTuple(args, "O", &py_ldb))
- return NULL;
-
- PyErr_LDB_OR_RAISE(py_ldb, ldb);
-
- ldb_set_utf8_fns(ldb, NULL, wrap_casefold);
-
- Py_RETURN_NONE;
-}
-
-static PyObject *py_samdb_set_domain_sid(PyLdbObject *self, PyObject *args)
-{
- PyObject *py_ldb, *py_sid;
- struct ldb_context *ldb;
- struct dom_sid *sid;
- bool ret;
-
- if (!PyArg_ParseTuple(args, "OO", &py_ldb, &py_sid))
- return NULL;
-
- PyErr_LDB_OR_RAISE(py_ldb, ldb);
-
- sid = dom_sid_parse_talloc(NULL, PyString_AsString(py_sid));
-
- ret = samdb_set_domain_sid(ldb, sid);
- if (!ret) {
- PyErr_SetString(PyExc_RuntimeError, "set_domain_sid failed");
- return NULL;
- }
- Py_RETURN_NONE;
-}
-
-static PyObject *py_samdb_get_domain_sid(PyLdbObject *self, PyObject *args)
-{
- PyObject *py_ldb;
- struct ldb_context *ldb;
- const struct dom_sid *sid;
+ int min, max;
PyObject *ret;
char *retstr;
-
- if (!PyArg_ParseTuple(args, "O", &py_ldb))
+ if (!PyArg_ParseTuple(args, "ii", &min, &max))
return NULL;
-
- PyErr_LDB_OR_RAISE(py_ldb, ldb);
- sid = samdb_domain_sid(ldb);
- if (!sid) {
- PyErr_SetString(PyExc_RuntimeError, "samdb_domain_sid failed");
+ retstr = generate_random_password(NULL, min, max);
+ if (retstr == NULL) {
return NULL;
- }
- retstr = dom_sid_string(NULL, sid);
+ }
ret = PyString_FromString(retstr);
talloc_free(retstr);
return ret;
}
-static PyObject *py_ldb_register_samba_handlers(PyObject *self, PyObject *args)
+static PyObject *py_unix2nttime(PyObject *self, PyObject *args)
{
- PyObject *py_ldb;
- struct ldb_context *ldb;
- int ret;
-
- if (!PyArg_ParseTuple(args, "O", &py_ldb))
+ time_t t;
+ NTTIME nt;
+ if (!PyArg_ParseTuple(args, "I", &t))
return NULL;
- PyErr_LDB_OR_RAISE(py_ldb, ldb);
- ret = ldb_register_samba_handlers(ldb);
+ unix_to_nt_time(&nt, t);
- PyErr_LDB_ERROR_IS_ERR_RAISE(py_ldb_get_exception(), ret, ldb);
- Py_RETURN_NONE;
+ return PyLong_FromLongLong((uint64_t)nt);
}
-static PyObject *py_dsdb_set_ntds_invocation_id(PyObject *self, PyObject *args)
+static PyObject *py_nttime2unix(PyObject *self, PyObject *args)
{
- PyObject *py_ldb, *py_guid;
- bool ret;
- struct GUID guid;
- struct ldb_context *ldb;
- if (!PyArg_ParseTuple(args, "OO", &py_ldb, &py_guid))
+ time_t t;
+ NTTIME nt;
+ if (!PyArg_ParseTuple(args, "K", &nt))
return NULL;
- PyErr_LDB_OR_RAISE(py_ldb, ldb);
- GUID_from_string(PyString_AsString(py_guid), &guid);
+ t = nt_time_to_unix(nt);
- ret = samdb_set_ntds_invocation_id(ldb, &guid);
- if (!ret) {
- PyErr_SetString(PyExc_RuntimeError, "set_ntds_invocation_id failed");
- return NULL;
- }
- Py_RETURN_NONE;
+ return PyInt_FromLong((uint64_t)t);
}
-static PyObject *py_dsdb_set_opaque_integer(PyObject *self, PyObject *args)
+static PyObject *py_nttime2string(PyObject *self, PyObject *args)
{
- PyObject *py_ldb;
- int value;
- int *old_val, *new_val;
- char *py_opaque_name, *opaque_name_talloc;
- struct ldb_context *ldb;
+ PyObject *ret;
+ NTTIME nt;
TALLOC_CTX *tmp_ctx;
-
- if (!PyArg_ParseTuple(args, "Osi", &py_ldb, &py_opaque_name, &value))
+ const char *string;
+ if (!PyArg_ParseTuple(args, "K", &nt))
return NULL;
- PyErr_LDB_OR_RAISE(py_ldb, ldb);
-
- /* see if we have a cached copy */
- old_val = (int *)ldb_get_opaque(ldb,
- py_opaque_name);
-
- if (old_val) {
- *old_val = value;
- Py_RETURN_NONE;
- }
-
- tmp_ctx = talloc_new(ldb);
+ tmp_ctx = talloc_new(NULL);
if (tmp_ctx == NULL) {
- goto failed;
- }
-
- new_val = talloc(tmp_ctx, int);
- if (!new_val) {
- goto failed;
- }
-
- opaque_name_talloc = talloc_strdup(tmp_ctx, py_opaque_name);
- if (!opaque_name_talloc) {
- goto failed;
+ PyErr_NoMemory();
+ return NULL;
}
-
- *new_val = value;
- /* cache the domain_sid in the ldb */
- if (ldb_set_opaque(ldb, opaque_name_talloc, new_val) != LDB_SUCCESS) {
- goto failed;
- }
+ string = nt_time_string(tmp_ctx, nt);
+ ret = PyString_FromString(string);
- talloc_steal(ldb, new_val);
- talloc_steal(ldb, opaque_name_talloc);
talloc_free(tmp_ctx);
- Py_RETURN_NONE;
-
-failed:
- talloc_free(tmp_ctx);
- PyErr_SetString(PyExc_RuntimeError, "Failed to set opaque integer into the ldb!\n");
- return NULL;
+ return ret;
}
-static PyObject *py_dsdb_set_global_schema(PyObject *self, PyObject *args)
+static PyObject *py_set_debug_level(PyObject *self, PyObject *args)
{
- PyObject *py_ldb;
- struct ldb_context *ldb;
- int ret;
- if (!PyArg_ParseTuple(args, "O", &py_ldb))
+ unsigned level;
+ if (!PyArg_ParseTuple(args, "I", &level))
return NULL;
-
- PyErr_LDB_OR_RAISE(py_ldb, ldb);
-
- ret = dsdb_set_global_schema(ldb);
- PyErr_LDB_ERROR_IS_ERR_RAISE(py_ldb_get_exception(), ret, ldb);
-
+ (DEBUGLEVEL) = level;
Py_RETURN_NONE;
}
-static PyObject *py_dsdb_set_schema_from_ldif(PyObject *self, PyObject *args)
+static PyObject *py_get_debug_level(PyObject *self)
{
- WERROR result;
- char *pf, *df;
- PyObject *py_ldb;
- struct ldb_context *ldb;
-
- if (!PyArg_ParseTuple(args, "Oss", &py_ldb, &pf, &df))
- return NULL;
-
- PyErr_LDB_OR_RAISE(py_ldb, ldb);
-
- result = dsdb_set_schema_from_ldif(ldb, pf, df);
- PyErr_WERROR_IS_ERR_RAISE(result);
-
- Py_RETURN_NONE;
+ return PyInt_FromLong(DEBUGLEVEL);
}
-static PyObject *py_dsdb_convert_schema_to_openldap(PyObject *self, PyObject *args)
-{
- char *target_str, *mapping;
- PyObject *py_ldb;
- struct ldb_context *ldb;
- PyObject *ret;
- char *retstr;
-
- if (!PyArg_ParseTuple(args, "Oss", &py_ldb, &target_str, &mapping))
- return NULL;
-
- PyErr_LDB_OR_RAISE(py_ldb, ldb);
-
- retstr = dsdb_convert_schema_to_openldap(ldb, target_str, mapping);
- if (!retstr) {
- PyErr_SetString(PyExc_RuntimeError, "dsdb_convert_schema_to_openldap failed");
- return NULL;
- }
- ret = PyString_FromString(retstr);
- talloc_free(retstr);
- return ret;
-}
+/*
+ return the list of interface IPs we have configured
+ takes an loadparm context, returns a list of IPs in string form
-static PyObject *py_dsdb_write_prefixes_from_schema_to_ldb(PyObject *self, PyObject *args)
+ Does not return addresses on 127.0.0.0/8
+ */
+static PyObject *py_interface_ips(PyObject *self, PyObject *args)
{
- PyObject *py_ldb;
- struct ldb_context *ldb;
- WERROR result;
- struct dsdb_schema *schema;
+ PyObject *pylist;
+ int count;
+ TALLOC_CTX *tmp_ctx;
+ PyObject *py_lp_ctx;
+ struct loadparm_context *lp_ctx;
+ struct interface *ifaces;
+ int i, ifcount;
+ int all_interfaces;
- if (!PyArg_ParseTuple(args, "O", &py_ldb))
+ if (!PyArg_ParseTuple(args, "Oi", &py_lp_ctx, &all_interfaces))
return NULL;
- PyErr_LDB_OR_RAISE(py_ldb, ldb);
-
- schema = dsdb_get_schema(ldb);
- if (!schema) {
- PyErr_SetString(PyExc_RuntimeError, "Failed to set find a schema on ldb!\n");
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ PyErr_NoMemory();
return NULL;
}
- result = dsdb_write_prefixes_from_schema_to_ldb(NULL, ldb, schema);
- PyErr_WERROR_IS_ERR_RAISE(result);
-
- Py_RETURN_NONE;
-}
-
-static PyObject *py_dsdb_set_schema_from_ldb(PyObject *self, PyObject *args)
-{
- PyObject *py_ldb;
- struct ldb_context *ldb;
- PyObject *py_from_ldb;
- struct ldb_context *from_ldb;
- struct dsdb_schema *schema;
- int ret;
- if (!PyArg_ParseTuple(args, "OO", &py_ldb, &py_from_ldb))
- return NULL;
-
- PyErr_LDB_OR_RAISE(py_ldb, ldb);
-
- PyErr_LDB_OR_RAISE(py_from_ldb, from_ldb);
-
- schema = dsdb_get_schema(from_ldb);
- if (!schema) {
- PyErr_SetString(PyExc_RuntimeError, "Failed to set find a schema on 'from' ldb!\n");
+ lp_ctx = lpcfg_from_py_object(tmp_ctx, py_lp_ctx);
+ if (lp_ctx == NULL) {
+ talloc_free(tmp_ctx);
return NULL;
}
- ret = dsdb_reference_schema(ldb, schema, true);
- PyErr_LDB_ERROR_IS_ERR_RAISE(py_ldb_get_exception(), ret, ldb);
+ load_interfaces(tmp_ctx, lpcfg_interfaces(lp_ctx), &ifaces);
- Py_RETURN_NONE;
-}
+ count = iface_count(ifaces);
-static PyObject *py_dom_sid_to_rid(PyLdbObject *self, PyObject *args)
-{
- PyObject *py_sid;
- struct dom_sid *sid;
- uint32_t rid;
- NTSTATUS status;
-
- if(!PyArg_ParseTuple(args, "O", &py_sid))
- return NULL;
-
- sid = dom_sid_parse_talloc(NULL, PyString_AsString(py_sid));
-
- status = dom_sid_split_rid(NULL, sid, NULL, &rid);
- if (!NT_STATUS_IS_OK(status)) {
- PyErr_SetString(PyExc_RuntimeError, "dom_sid_split_rid failed");
- return NULL;
+ /* 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"))) {
+ ifcount++;
+ }
}
- return PyInt_FromLong(rid);
+ 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"))) {
+ PyList_SetItem(pylist, ifcount, PyString_FromString(ip));
+ ifcount++;
+ }
+ }
+ talloc_free(tmp_ctx);
+ return pylist;
}
static PyMethodDef py_misc_methods[] = {
{ "generate_random_str", (PyCFunction)py_generate_random_str, METH_VARARGS,
- "random_password(len) -> string\n"
- "Generate random password with specified length." },
+ "generate_random_str(len) -> string\n"
+ "Generate random string with specified length." },
+ { "generate_random_password", (PyCFunction)py_generate_random_password,
+ METH_VARARGS, "generate_random_password(min, max) -> string\n"
+ "Generate random password with a length >= min and <= max." },
{ "unix2nttime", (PyCFunction)py_unix2nttime, METH_VARARGS,
"unix2nttime(timestamp) -> nttime" },
- { "ldb_set_credentials", (PyCFunction)py_ldb_set_credentials, METH_VARARGS,
- "ldb_set_credentials(ldb, credentials) -> None\n"
- "Set credentials to use when connecting." },
- { "ldb_set_session_info", (PyCFunction)py_ldb_set_session_info, METH_VARARGS,
- "ldb_set_session_info(ldb, session_info)\n"
- "Set session info to use when connecting." },
- { "ldb_set_loadparm", (PyCFunction)py_ldb_set_loadparm, METH_VARARGS,
- "ldb_set_loadparm(ldb, session_info)\n"
- "Set loadparm context to use when connecting." },
- { "samdb_set_domain_sid", (PyCFunction)py_samdb_set_domain_sid, METH_VARARGS,
- "samdb_set_domain_sid(samdb, sid)\n"
- "Set SID of domain to use." },
- { "samdb_get_domain_sid", (PyCFunction)py_samdb_get_domain_sid, METH_VARARGS,
- "samdb_get_domain_sid(samdb)\n"
- "Get SID of domain in use." },
- { "ldb_register_samba_handlers", (PyCFunction)py_ldb_register_samba_handlers, METH_VARARGS,
- "ldb_register_samba_handlers(ldb)\n"
- "Register Samba-specific LDB modules and schemas." },
- { "ldb_set_utf8_casefold", (PyCFunction)py_ldb_set_utf8_casefold, METH_VARARGS,
- "ldb_set_utf8_casefold(ldb)\n"
- "Set the right Samba casefolding function for UTF8 charset." },
- { "dsdb_set_ntds_invocation_id", (PyCFunction)py_dsdb_set_ntds_invocation_id, METH_VARARGS,
- NULL },
- { "dsdb_set_opaque_integer", (PyCFunction)py_dsdb_set_opaque_integer, METH_VARARGS,
- NULL },
- { "dsdb_set_global_schema", (PyCFunction)py_dsdb_set_global_schema, METH_VARARGS,
- NULL },
- { "dsdb_set_schema_from_ldif", (PyCFunction)py_dsdb_set_schema_from_ldif, METH_VARARGS,
- NULL },
- { "dsdb_write_prefixes_from_schema_to_ldb", (PyCFunction)py_dsdb_write_prefixes_from_schema_to_ldb, METH_VARARGS,
- NULL },
- { "dsdb_set_schema_from_ldb", (PyCFunction)py_dsdb_set_schema_from_ldb, METH_VARARGS,
- NULL },
- { "dsdb_convert_schema_to_openldap", (PyCFunction)py_dsdb_convert_schema_to_openldap, METH_VARARGS,
- NULL },
- { "dom_sid_to_rid", (PyCFunction)py_dom_sid_to_rid, METH_VARARGS,
- NULL },
+ { "nttime2unix", (PyCFunction)py_nttime2unix, METH_VARARGS,
+ "nttime2unix(nttime) -> timestamp" },
+ { "nttime2string", (PyCFunction)py_nttime2string, METH_VARARGS,
+ "nttime2string(nttime) -> string" },
{ "set_debug_level", (PyCFunction)py_set_debug_level, METH_VARARGS,
"set debug level" },
+ { "get_debug_level", (PyCFunction)py_get_debug_level, METH_NOARGS,
+ "get debug level" },
+ { "interface_ips", (PyCFunction)py_interface_ips, METH_VARARGS,
+ "get interface IP address list"},
{ NULL }
};
-void initglue(void)
+void init_glue(void)
{
PyObject *m;
- m = Py_InitModule3("glue", py_misc_methods,
+ debug_setup_talloc_log();
+
+ m = Py_InitModule3("_glue", py_misc_methods,
"Python bindings for miscellaneous Samba functions.");
if (m == NULL)
return;
- PyModule_AddObject(m, "version", PyString_FromString(SAMBA_VERSION_STRING));
-
- /* "userAccountControl" flags */
- PyModule_AddObject(m, "UF_NORMAL_ACCOUNT", PyInt_FromLong(UF_NORMAL_ACCOUNT));
- PyModule_AddObject(m, "UF_TEMP_DUPLICATE_ACCOUNT", PyInt_FromLong(UF_TEMP_DUPLICATE_ACCOUNT));
- PyModule_AddObject(m, "UF_SERVER_TRUST_ACCOUNT", PyInt_FromLong(UF_SERVER_TRUST_ACCOUNT));
- PyModule_AddObject(m, "UF_WORKSTATION_TRUST_ACCOUNT", PyInt_FromLong(UF_WORKSTATION_TRUST_ACCOUNT));
- PyModule_AddObject(m, "UF_INTERDOMAIN_TRUST_ACCOUNT", PyInt_FromLong(UF_INTERDOMAIN_TRUST_ACCOUNT));
- PyModule_AddObject(m, "UF_PASSWD_NOTREQD", PyInt_FromLong(UF_PASSWD_NOTREQD));
- PyModule_AddObject(m, "UF_ACCOUNTDISABLE", PyInt_FromLong(UF_ACCOUNTDISABLE));
-
- /* "groupType" flags */
- PyModule_AddObject(m, "GTYPE_SECURITY_BUILTIN_LOCAL_GROUP", PyInt_FromLong(GTYPE_SECURITY_BUILTIN_LOCAL_GROUP));
- PyModule_AddObject(m, "GTYPE_SECURITY_GLOBAL_GROUP", PyInt_FromLong(GTYPE_SECURITY_GLOBAL_GROUP));
- PyModule_AddObject(m, "GTYPE_SECURITY_DOMAIN_LOCAL_GROUP", PyInt_FromLong(GTYPE_SECURITY_DOMAIN_LOCAL_GROUP));
- PyModule_AddObject(m, "GTYPE_SECURITY_UNIVERSAL_GROUP", PyInt_FromLong(GTYPE_SECURITY_UNIVERSAL_GROUP));
- PyModule_AddObject(m, "GTYPE_DISTRIBUTION_GLOBAL_GROUP", PyInt_FromLong(GTYPE_DISTRIBUTION_GLOBAL_GROUP));
- PyModule_AddObject(m, "GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP", PyInt_FromLong(GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP));
- PyModule_AddObject(m, "GTYPE_DISTRIBUTION_UNIVERSAL_GROUP", PyInt_FromLong(GTYPE_DISTRIBUTION_UNIVERSAL_GROUP));
-
- /* "sAMAccountType" flags */
- PyModule_AddObject(m, "ATYPE_NORMAL_ACCOUNT", PyInt_FromLong(ATYPE_NORMAL_ACCOUNT));
- PyModule_AddObject(m, "ATYPE_WORKSTATION_TRUST", PyInt_FromLong(ATYPE_WORKSTATION_TRUST));
- PyModule_AddObject(m, "ATYPE_INTERDOMAIN_TRUST", PyInt_FromLong(ATYPE_INTERDOMAIN_TRUST));
- PyModule_AddObject(m, "ATYPE_SECURITY_GLOBAL_GROUP", PyInt_FromLong(ATYPE_SECURITY_GLOBAL_GROUP));
- PyModule_AddObject(m, "ATYPE_SECURITY_LOCAL_GROUP", PyInt_FromLong(ATYPE_SECURITY_LOCAL_GROUP));
- PyModule_AddObject(m, "ATYPE_SECURITY_UNIVERSAL_GROUP", PyInt_FromLong(ATYPE_SECURITY_UNIVERSAL_GROUP));
- PyModule_AddObject(m, "ATYPE_DISTRIBUTION_GLOBAL_GROUP", PyInt_FromLong(ATYPE_DISTRIBUTION_GLOBAL_GROUP));
- PyModule_AddObject(m, "ATYPE_DISTRIBUTION_LOCAL_GROUP", PyInt_FromLong(ATYPE_DISTRIBUTION_LOCAL_GROUP));
- PyModule_AddObject(m, "ATYPE_DISTRIBUTION_UNIVERSAL_GROUP", PyInt_FromLong(ATYPE_DISTRIBUTION_UNIVERSAL_GROUP));
-
- /* "domainFunctionality", "forestFunctionality" flags in the rootDSE */
- PyModule_AddObject(m, "DS_DOMAIN_FUNCTION_2000", PyInt_FromLong(DS_DOMAIN_FUNCTION_2000));
- PyModule_AddObject(m, "DS_DOMAIN_FUNCTION_2003_MIXED", PyInt_FromLong(DS_DOMAIN_FUNCTION_2003_MIXED));
- PyModule_AddObject(m, "DS_DOMAIN_FUNCTION_2003", PyInt_FromLong(DS_DOMAIN_FUNCTION_2003));
- PyModule_AddObject(m, "DS_DOMAIN_FUNCTION_2008", PyInt_FromLong(DS_DOMAIN_FUNCTION_2008));
- PyModule_AddObject(m, "DS_DOMAIN_FUNCTION_2008_R2", PyInt_FromLong(DS_DOMAIN_FUNCTION_2008_R2));
-
- /* "domainControllerFunctionality" flags in the rootDSE */
- PyModule_AddObject(m, "DS_DC_FUNCTION_2000", PyInt_FromLong(DS_DC_FUNCTION_2000));
- PyModule_AddObject(m, "DS_DC_FUNCTION_2003", PyInt_FromLong(DS_DC_FUNCTION_2003));
- PyModule_AddObject(m, "DS_DC_FUNCTION_2008", PyInt_FromLong(DS_DC_FUNCTION_2008));
- PyModule_AddObject(m, "DS_DC_FUNCTION_2008_R2", PyInt_FromLong(DS_DC_FUNCTION_2008_R2));
+ 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 82df4960cf..2a54f47d2b 100644
--- a/source4/scripting/python/samba/__init__.py
+++ b/source4/scripting/python/samba/__init__.py
@@ -1,21 +1,21 @@
-#!/usr/bin/python
+#!/usr/bin/env python
# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
-#
+#
# 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/>.
#
@@ -25,33 +25,39 @@
__docformat__ = "restructuredText"
import os
+import sys
+
+def source_tree_topdir():
+ '''return the top level directory (the one containing the source4 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'''
+ try:
+ topdir = source_tree_topdir()
+ except RuntimeError:
+ return False
+ return True
-def _in_source_tree():
- """Check whether the script is being run from the source dir. """
- return os.path.exists("%s/../../../selftest/skip" % os.path.dirname(__file__))
-
-
-# When running, in-tree, make sure bin/python is in the PYTHONPATH
-if _in_source_tree():
- import sys
- srcdir = "%s/../../.." % os.path.dirname(__file__)
- sys.path.append("%s/bin/python" % srcdir)
- default_ldb_modules_dir = "%s/bin/modules/ldb" % srcdir
-else:
- default_ldb_modules_dir = None
import ldb
-import glue
+from samba._ldb import Ldb as _Ldb
-class Ldb(ldb.Ldb):
- """Simple Samba-specific LDB subclass that takes care
+class Ldb(_Ldb):
+ """Simple Samba-specific LDB subclass that takes care
of setting up the modules dir, credentials pointers, etc.
-
- Please note that this is intended to be for all Samba LDB files,
- not necessarily the Sam database. For Sam-specific helper
+
+ Please note that this is intended to be for all Samba LDB files,
+ not necessarily the Sam database. For Sam-specific helper
functions see samdb.py.
"""
+
def __init__(self, url=None, lp=None, modules_dir=None, session_info=None,
credentials=None, flags=0, options=None):
"""Opens a Samba Ldb file.
@@ -65,14 +71,12 @@ class Ldb(ldb.Ldb):
:param options: Additional options (optional)
This is different from a regular Ldb file in that the Samba-specific
- modules-dir is used by default and that credentials and session_info
+ modules-dir is used by default and that credentials and session_info
can be passed through (required by some modules).
"""
if modules_dir is not None:
self.set_modules_dir(modules_dir)
- elif default_ldb_modules_dir is not None:
- self.set_modules_dir(default_ldb_modules_dir)
elif lp is not None:
self.set_modules_dir(os.path.join(lp.get("modules dir"), "ldb"))
@@ -88,37 +92,30 @@ class Ldb(ldb.Ldb):
# This must be done before we load the schema, as these handlers for
# objectSid and objectGUID etc must take precedence over the 'binary
# attribute' declaration in the schema
- glue.ldb_register_samba_handlers(self)
+ self.register_samba_handlers()
# TODO set debug
- def msg(l,text):
+ def msg(l, text):
print text
#self.set_debug(msg)
- glue.ldb_set_utf8_casefold(self)
+ self.set_utf8_casefold()
# 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:
- flags |= FLG_NOSYNC
+ if nosync_p is not None and nosync_p == True:
+ flags |= ldb.FLG_NOSYNC
+
+ self.set_create_perms(0600)
if url is not None:
self.connect(url, flags, options)
- def set_credentials(self, credentials):
- glue.ldb_set_credentials(self, credentials)
-
- def set_session_info(self, session_info):
- glue.ldb_set_session_info(self, session_info)
-
- def set_loadparm(self, lp_ctx):
- glue.ldb_set_loadparm(self, lp_ctx)
-
- def searchone(self, attribute, basedn=None, expression=None,
+ def searchone(self, attribute, basedn=None, expression=None,
scope=ldb.SCOPE_BASE):
"""Search for one attribute as a string.
-
+
:param basedn: BaseDN for the search.
:param attribute: Name of the attribute
:param expression: Optional search expression.
@@ -133,25 +130,36 @@ class Ldb(ldb.Ldb):
return self.schema_format_value(attribute, values.pop())
def erase_users_computers(self, dn):
- """Erases user and computer objects from our AD. This is needed since the 'samldb' module denies the deletion of primary groups. Therefore all groups shouldn't be primary somewhere anymore."""
+ """Erases user and computer objects from our AD.
+
+ This is needed since the 'samldb' module denies the deletion of primary
+ groups. Therefore all groups shouldn't be primary somewhere anymore.
+ """
try:
res = self.search(base=dn, scope=ldb.SCOPE_SUBTREE, attrs=[],
expression="(|(objectclass=user)(objectclass=computer))")
- except ldb.LdbError, (ldb.ERR_NO_SUCH_OBJECT, _):
- # Ignore no such object errors
- return
- pass
+ except ldb.LdbError, (errno, _):
+ if errno == ldb.ERR_NO_SUCH_OBJECT:
+ # Ignore no such object errors
+ return
+ else:
+ raise
try:
for msg in res:
- self.delete(msg.dn)
- except ldb.LdbError, (ldb.ERR_NO_SUCH_OBJECT, _):
- # Ignore no such object errors
- return
+ self.delete(msg.dn, ["relax:0"])
+ except ldb.LdbError, (errno, _):
+ if errno != ldb.ERR_NO_SUCH_OBJECT:
+ # Ignore no such object errors
+ raise
def erase_except_schema_controlled(self):
- """Erase this ldb, removing all records, except those that are controlled by Samba4's schema."""
+ """Erase this ldb.
+
+ :note: Removes all records, except those that are controlled by
+ Samba4's schema.
+ """
basedn = ""
@@ -159,73 +167,42 @@ class Ldb(ldb.Ldb):
self.erase_users_computers(basedn)
# 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"]):
+ for msg in self.search(basedn, ldb.SCOPE_SUBTREE,
+ "(&(|(objectclass=*)(distinguishedName=*))(!(distinguishedName=@BASEINFO)))",
+ [], controls=["show_deleted:0", "show_recycled:0"]):
try:
- self.delete(msg.dn)
- except ldb.LdbError, (ldb.ERR_NO_SUCH_OBJECT, _):
- # Ignore no such object errors
- pass
-
- res = self.search(basedn, ldb.SCOPE_SUBTREE,
- "(&(|(objectclass=*)(distinguishedName=*))(!(distinguishedName=@BASEINFO)))",
- [], controls=["show_deleted:0"])
+ self.delete(msg.dn, ["relax:0"])
+ except ldb.LdbError, (errno, _):
+ if errno != ldb.ERR_NO_SUCH_OBJECT:
+ # Ignore no such object errors
+ raise
+
+ res = self.search(basedn, ldb.SCOPE_SUBTREE,
+ "(&(|(objectclass=*)(distinguishedName=*))(!(distinguishedName=@BASEINFO)))", [], controls=["show_deleted:0", "show_recycled:0"])
assert len(res) == 0
# delete the specials
- for attr in ["@SUBCLASSES", "@MODULES",
+ for attr in ["@SUBCLASSES", "@MODULES",
"@OPTIONS", "@PARTITION", "@KLUDGEACL"]:
try:
- self.delete(attr)
- except ldb.LdbError, (ldb.ERR_NO_SUCH_OBJECT, _):
- # Ignore missing dn errors
- pass
+ self.delete(attr, ["relax:0"])
+ except ldb.LdbError, (errno, _):
+ if errno != ldb.ERR_NO_SUCH_OBJECT:
+ # Ignore missing dn errors
+ raise
def erase(self):
"""Erase this ldb, removing all records."""
-
self.erase_except_schema_controlled()
# delete the specials
for attr in ["@INDEXLIST", "@ATTRIBUTES"]:
try:
- self.delete(attr)
- except ldb.LdbError, (ldb.ERR_NO_SUCH_OBJECT, _):
- # Ignore missing dn errors
- pass
-
- def erase_partitions(self):
- """Erase an ldb, removing all records."""
-
- def erase_recursive(self, dn):
- try:
- res = self.search(base=dn, scope=ldb.SCOPE_ONELEVEL, attrs=[],
- controls=["show_deleted:0"])
- except ldb.LdbError, (ldb.ERR_NO_SUCH_OBJECT, _):
- # Ignore no such object errors
- return
- pass
-
- for msg in res:
- erase_recursive(self, msg.dn)
-
- try:
- self.delete(dn)
- except ldb.LdbError, (ldb.ERR_NO_SUCH_OBJECT, _):
- # Ignore no such object errors
- pass
-
- res = self.search("", ldb.SCOPE_BASE, "(objectClass=*)",
- ["namingContexts"])
- assert len(res) == 1
- if not "namingContexts" in res[0]:
- return
- for basedn in res[0]["namingContexts"]:
- # Try to delete user/computer accounts to allow deletion of groups
- self.erase_users_computers(basedn)
- # Try and erase from the bottom-up in the tree
- erase_recursive(self, basedn)
+ self.delete(attr, ["relax:0"])
+ except ldb.LdbError, (errno, _):
+ if errno != ldb.ERR_NO_SUCH_OBJECT:
+ # Ignore missing dn errors
+ raise
def load_ldif_file_add(self, ldif_path):
"""Load a LDIF file.
@@ -234,68 +211,31 @@ class Ldb(ldb.Ldb):
"""
self.add_ldif(open(ldif_path, 'r').read())
- def add_ldif(self, ldif):
+ def add_ldif(self, ldif, controls=None):
"""Add data based on a LDIF string.
:param ldif: LDIF text.
"""
for changetype, msg in self.parse_ldif(ldif):
assert changetype == ldb.CHANGETYPE_NONE
- self.add(msg)
+ self.add(msg, controls)
- def modify_ldif(self, ldif):
+ def modify_ldif(self, ldif, controls=None):
"""Modify database based on a LDIF string.
:param ldif: LDIF text.
"""
for changetype, msg in self.parse_ldif(ldif):
- self.modify(msg)
-
- def set_domain_sid(self, sid):
- """Change the domain SID used by this LDB.
-
- :param sid: The new domain sid to use.
- """
- glue.samdb_set_domain_sid(self, sid)
-
- def domain_sid(self):
- """Read the domain SID used by this LDB.
-
- """
- glue.samdb_get_domain_sid(self)
-
- def set_schema_from_ldif(self, pf, df):
- glue.dsdb_set_schema_from_ldif(self, pf, df)
-
- def set_schema_from_ldb(self, ldb):
- glue.dsdb_set_schema_from_ldb(self, ldb)
-
- def write_prefixes_from_schema(self):
- glue.dsdb_write_prefixes_from_schema_to_ldb(self)
-
- def convert_schema_to_openldap(self, target, mapping):
- return glue.dsdb_convert_schema_to_openldap(self, target, mapping)
-
- def set_invocation_id(self, invocation_id):
- """Set the invocation id for this SamDB handle.
-
- :param invocation_id: GUID of the invocation id.
- """
- glue.dsdb_set_ntds_invocation_id(self, invocation_id)
-
- def set_opaque_integer(self, name, value):
- """Set an integer as an opaque (a flag or other value) value on the database
-
- :param name: The name for the opaque value
- :param value: The integer value
- """
- glue.dsdb_set_opaque_integer(self, name, value)
+ if changetype == ldb.CHANGETYPE_ADD:
+ self.add(msg, controls)
+ else:
+ self.modify(msg, controls)
def substitute_var(text, values):
- """substitute strings of the form ${NAME} in str, replacing
- with substitutions from subobj.
-
+ """Substitute strings of the form ${NAME} in str, replacing
+ with substitutions from values.
+
:param text: Text in which to subsitute.
:param values: Dictionary with keys and values.
"""
@@ -309,18 +249,51 @@ def substitute_var(text, values):
def check_all_substituted(text):
- """Make sure that all substitution variables in a string have been replaced.
+ """Check that all substitution variables in a string have been replaced.
+
If not, raise an exception.
-
+
:param text: The text to search for substitution variables
"""
if not "${" in text:
return
-
+
var_start = text.find("${")
var_end = text.find("}", var_start)
-
- raise Exception("Not all variables substituted: %s" % text[var_start:var_end+1])
+
+ raise Exception("Not all variables substituted: %s" %
+ text[var_start:var_end+1])
+
+
+def read_and_sub_file(file_name, subst_vars):
+ """Read a file and sub in variables found in it
+
+ :param file_name: File to be read (typically from setup directory)
+ param subst_vars: Optional variables to subsitute in the file.
+ """
+ data = open(file_name, 'r').read()
+ if subst_vars is not None:
+ data = substitute_var(data, subst_vars)
+ check_all_substituted(data)
+ return data
+
+
+def setup_file(template, fname, subst_vars=None):
+ """Setup a file in the private dir.
+
+ :param template: Path of the template file.
+ :param fname: Path of the file to create.
+ :param subst_vars: Substitution variables.
+ """
+ if os.path.exists(fname):
+ os.unlink(fname)
+
+ data = read_and_sub_file(template, subst_vars)
+ f = open(fname, 'w')
+ try:
+ f.write(data)
+ finally:
+ f.close()
def valid_netbios_name(name):
@@ -334,56 +307,44 @@ def valid_netbios_name(name):
return True
-def dom_sid_to_rid(sid_str):
- """Converts a domain SID to the relative RID.
+def import_bundled_package(modulename, location):
+ """Import the bundled version of a package.
- :param sid_str: The domain SID formatted as string
+ :note: This should only be called if the system version of the package
+ is not adequate.
+
+ :param modulename: Module name to import
+ :param location: Location to add to sys.path (can be relative to
+ ${srcdir}/lib)
"""
+ if in_source_tree():
+ sys.path.insert(0, os.path.join(source_tree_topdir(), "lib", location))
+ sys.modules[modulename] = __import__(modulename)
+ else:
+ sys.modules[modulename] = __import__(
+ "samba.external.%s" % modulename, fromlist=["samba.external"])
+
- return glue.dom_sid_to_rid(sid_str)
-
-
-version = glue.version
-
-# "userAccountControl" flags
-UF_NORMAL_ACCOUNT = glue.UF_NORMAL_ACCOUNT
-UF_TEMP_DUPLICATE_ACCOUNT = glue.UF_TEMP_DUPLICATE_ACCOUNT
-UF_SERVER_TRUST_ACCOUNT = glue.UF_SERVER_TRUST_ACCOUNT
-UF_WORKSTATION_TRUST_ACCOUNT = glue.UF_WORKSTATION_TRUST_ACCOUNT
-UF_INTERDOMAIN_TRUST_ACCOUNT = glue.UF_INTERDOMAIN_TRUST_ACCOUNT
-UF_PASSWD_NOTREQD = glue.UF_PASSWD_NOTREQD
-UF_ACCOUNTDISABLE = glue.UF_ACCOUNTDISABLE
-
-# "groupType" flags
-GTYPE_SECURITY_BUILTIN_LOCAL_GROUP = glue.GTYPE_SECURITY_BUILTIN_LOCAL_GROUP
-GTYPE_SECURITY_GLOBAL_GROUP = glue.GTYPE_SECURITY_GLOBAL_GROUP
-GTYPE_SECURITY_DOMAIN_LOCAL_GROUP = glue.GTYPE_SECURITY_DOMAIN_LOCAL_GROUP
-GTYPE_SECURITY_UNIVERSAL_GROUP = glue.GTYPE_SECURITY_UNIVERSAL_GROUP
-GTYPE_DISTRIBUTION_GLOBAL_GROUP = glue.GTYPE_DISTRIBUTION_GLOBAL_GROUP
-GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP = glue.GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP
-GTYPE_DISTRIBUTION_UNIVERSAL_GROUP = glue.GTYPE_DISTRIBUTION_UNIVERSAL_GROUP
-
-# "sAMAccountType" flags
-ATYPE_NORMAL_ACCOUNT = glue.ATYPE_NORMAL_ACCOUNT
-ATYPE_WORKSTATION_TRUST = glue.ATYPE_WORKSTATION_TRUST
-ATYPE_INTERDOMAIN_TRUST = glue.ATYPE_INTERDOMAIN_TRUST
-ATYPE_SECURITY_GLOBAL_GROUP = glue.ATYPE_SECURITY_GLOBAL_GROUP
-ATYPE_SECURITY_LOCAL_GROUP = glue.ATYPE_SECURITY_LOCAL_GROUP
-ATYPE_SECURITY_UNIVERSAL_GROUP = glue.ATYPE_SECURITY_UNIVERSAL_GROUP
-ATYPE_DISTRIBUTION_GLOBAL_GROUP = glue.ATYPE_DISTRIBUTION_GLOBAL_GROUP
-ATYPE_DISTRIBUTION_LOCAL_GROUP = glue.ATYPE_DISTRIBUTION_LOCAL_GROUP
-ATYPE_DISTRIBUTION_UNIVERSAL_GROUP = glue.ATYPE_DISTRIBUTION_UNIVERSAL_GROUP
-
-# "domainFunctionality", "forestFunctionality" flags in the rootDSE */
-DS_DOMAIN_FUNCTION_2000 = glue.DS_DOMAIN_FUNCTION_2000
-DS_DOMAIN_FUNCTION_2003_MIXED = glue.DS_DOMAIN_FUNCTION_2003_MIXED
-DS_DOMAIN_FUNCTION_2003 = glue.DS_DOMAIN_FUNCTION_2003
-DS_DOMAIN_FUNCTION_2008 = glue.DS_DOMAIN_FUNCTION_2008
-DS_DOMAIN_FUNCTION_2008_R2 = glue.DS_DOMAIN_FUNCTION_2008_R2
-
-# "domainControllerFunctionality" flags in the rootDSE */
-DS_DC_FUNCTION_2000 = glue.DS_DC_FUNCTION_2000
-DS_DC_FUNCTION_2003 = glue.DS_DC_FUNCTION_2003
-DS_DC_FUNCTION_2008 = glue.DS_DC_FUNCTION_2008
-DS_DC_FUNCTION_2008_R2 = glue.DS_DC_FUNCTION_2008_R2
+def ensure_external_module(modulename, location):
+ """Add a location to sys.path if an external dependency can't be found.
+ :param modulename: Module name to import
+ :param location: Location to add to sys.path (can be relative to
+ ${srcdir}/lib)
+ """
+ try:
+ __import__(modulename)
+ except ImportError:
+ import_bundled_package(modulename, location)
+
+
+from samba import _glue
+version = _glue.version
+interface_ips = _glue.interface_ips
+set_debug_level = _glue.set_debug_level
+get_debug_level = _glue.get_debug_level
+unix2nttime = _glue.unix2nttime
+nttime2string = _glue.nttime2string
+nttime2unix = _glue.nttime2unix
+unix2nttime = _glue.unix2nttime
+generate_random_password = _glue.generate_random_password
diff --git a/source4/scripting/python/samba/drs_utils.py b/source4/scripting/python/samba/drs_utils.py
new file mode 100644
index 0000000000..77f415ed17
--- /dev/null
+++ b/source4/scripting/python/samba/drs_utils.py
@@ -0,0 +1,173 @@
+#!/usr/bin/env python
+#
+# DRS utility code
+#
+# Copyright Andrew Tridgell 2010
+# Copyright Andrew Bartlett 2010
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+from samba.dcerpc import drsuapi, misc
+from samba.net import Net
+import samba, ldb
+
+
+def drs_DsBind(drs):
+ '''make a DsBind call, returning the binding handle'''
+ bind_info = drsuapi.DsBindInfoCtr()
+ bind_info.length = 28
+ bind_info.info = drsuapi.DsBindInfo28()
+ bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_BASE
+ bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_ASYNC_REPLICATION
+ bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_REMOVEAPI
+ bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_MOVEREQ_V2
+ bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHG_COMPRESS
+ bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V1
+ bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_RESTORE_USN_OPTIMIZATION
+ bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_KCC_EXECUTE
+ bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_ADDENTRY_V2
+ bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_LINKED_VALUE_REPLICATION
+ bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V2
+ bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_INSTANCE_TYPE_NOT_REQ_ON_MOD
+ bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_CRYPTO_BIND
+ bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GET_REPL_INFO
+ bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_STRONG_ENCRYPTION
+ bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V01
+ bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_TRANSITIVE_MEMBERSHIP
+ bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_ADD_SID_HISTORY
+ bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_POST_BETA3
+ bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GET_MEMBERSHIPS2
+ bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V6
+ bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_NONDOMAIN_NCS
+ bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V8
+ bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V5
+ bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V6
+ bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_ADDENTRYREPLY_V3
+ bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V7
+ bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_VERIFY_OBJECT
+ (info, handle) = drs.DsBind(misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID), bind_info)
+
+ return (handle, info.info.supported_extensions)
+
+
+class drs_Replicate:
+ '''DRS replication calls'''
+
+ def __init__(self, binding_string, lp, creds, samdb):
+ self.drs = drsuapi.drsuapi(binding_string, lp, creds)
+ (self.drs_handle, self.supported_extensions) = drs_DsBind(self.drs)
+ self.net = Net(creds=creds, lp=lp)
+ self.samdb = samdb
+ self.replication_state = self.net.replicate_init(self.samdb, lp, self.drs)
+
+ def drs_get_rodc_partial_attribute_set(self):
+ '''get a list of attributes for RODC replication'''
+ partial_attribute_set = drsuapi.DsPartialAttributeSet()
+ partial_attribute_set.version = 1
+
+ attids = []
+
+ # the exact list of attids we send is quite critical. Note that
+ # we do ask for the secret attributes, but set SPECIAL_SECRET_PROCESSING
+ # to zero them out
+ schema_dn = self.samdb.get_schema_basedn()
+ res = self.samdb.search(base=schema_dn, scope=ldb.SCOPE_SUBTREE,
+ expression="objectClass=attributeSchema",
+ attrs=["lDAPDisplayName", "systemFlags",
+ "searchFlags"])
+
+ for r in res:
+ ldap_display_name = r["lDAPDisplayName"][0]
+ if "systemFlags" in r:
+ system_flags = r["systemFlags"][0]
+ if (int(system_flags) & (samba.dsdb.DS_FLAG_ATTR_NOT_REPLICATED |
+ samba.dsdb.DS_FLAG_ATTR_IS_CONSTRUCTED)):
+ continue
+ if "searchFlags" in r:
+ search_flags = r["searchFlags"][0]
+ if (int(search_flags) & samba.dsdb.SEARCH_FLAG_RODC_ATTRIBUTE):
+ continue
+ attid = self.samdb.get_attid_from_lDAPDisplayName(ldap_display_name)
+ attids.append(int(attid))
+
+ # the attids do need to be sorted, or windows doesn't return
+ # all the attributes we need
+ attids.sort()
+ partial_attribute_set.attids = attids
+ partial_attribute_set.num_attids = len(attids)
+ return partial_attribute_set
+
+ def replicate(self, dn, source_dsa_invocation_id, destination_dsa_guid,
+ schema=False, exop=drsuapi.DRSUAPI_EXOP_NONE, rodc=False,
+ replica_flags=None):
+ '''replicate a single DN'''
+
+ # setup for a GetNCChanges call
+ req8 = drsuapi.DsGetNCChangesRequest8()
+
+ req8.destination_dsa_guid = destination_dsa_guid
+ req8.source_dsa_invocation_id = source_dsa_invocation_id
+ req8.naming_context = drsuapi.DsReplicaObjectIdentifier()
+ req8.naming_context.dn = dn
+ req8.highwatermark = drsuapi.DsReplicaHighWaterMark()
+ req8.highwatermark.tmp_highest_usn = 0
+ req8.highwatermark.reserved_usn = 0
+ req8.highwatermark.highest_usn = 0
+ req8.uptodateness_vector = None
+ if replica_flags is not None:
+ req8.replica_flags = replica_flags
+ elif exop == drsuapi.DRSUAPI_EXOP_REPL_SECRET:
+ req8.replica_flags = 0
+ else:
+ req8.replica_flags = (drsuapi.DRSUAPI_DRS_INIT_SYNC |
+ drsuapi.DRSUAPI_DRS_PER_SYNC |
+ drsuapi.DRSUAPI_DRS_GET_ANC |
+ drsuapi.DRSUAPI_DRS_NEVER_SYNCED)
+ if rodc:
+ 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
+
+ if not schema and rodc:
+ req8.partial_attribute_set = self.drs_get_rodc_partial_attribute_set()
+
+ if self.supported_extensions & drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V8:
+ req_level = 8
+ req = req8
+ else:
+ req_level = 5
+ req5 = drsuapi.DsGetNCChangesRequest5()
+ for a in dir(req5):
+ if a[0] != '_':
+ 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:
+ raise RuntimeError("DsGetNCChanges: NULL first_object with object_count=%u" % (ctr.object_count))
+ self.net.replicate_chunk(self.replication_state, level, ctr, schema=schema)
+ 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 8b756b2d6f..671142b552 100644
--- a/source4/scripting/python/samba/getopt.py
+++ b/source4/scripting/python/samba/getopt.py
@@ -1,38 +1,56 @@
-#!/usr/bin/python
+#!/usr/bin/env python
# Samba-specific bits for optparse
# 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 parsing Samba-related command-line options."""
-import optparse
-from credentials import Credentials, DONT_USE_KERBEROS, MUST_USE_KERBEROS
-from hostconfig import Hostconfig
-
__docformat__ = "restructuredText"
+import optparse, os
+from samba.credentials import (
+ Credentials,
+ DONT_USE_KERBEROS,
+ MUST_USE_KERBEROS,
+ )
+from samba.hostconfig import Hostconfig
+import sys
+
+
class SambaOptions(optparse.OptionGroup):
"""General Samba-related command line options."""
+
def __init__(self, parser):
+ from samba.param import LoadParm
optparse.OptionGroup.__init__(self, parser, "Samba Common Options")
self.add_option("-s", "--configfile", action="callback",
- type=str, metavar="FILE", help="Configuration file",
+ type=str, metavar="FILE", help="Configuration file",
callback=self._load_configfile)
+ self.add_option("-d", "--debuglevel", action="callback",
+ 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",
+ 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()
def get_loadparm_path(self):
"""Return the path to the smb.conf file specified on the command line. """
@@ -41,17 +59,28 @@ class SambaOptions(optparse.OptionGroup):
def _load_configfile(self, option, opt_str, arg, parser):
self._configfile = arg
+ def _set_debuglevel(self, option, opt_str, arg, parser):
+ self._lp.set('debug level', str(arg))
+
+ def _set_realm(self, option, opt_str, arg, parser):
+ self._lp.set('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)
+ a = arg.split('=')
+ self._lp.set(a[0], a[1])
+
def get_loadparm(self):
"""Return a loadparm object with data specified on the command line. """
- import os, param
- lp = param.LoadParm()
if self._configfile is not None:
- lp.load(self._configfile)
+ self._lp.load(self._configfile)
elif os.getenv("SMB_CONF_PATH") is not None:
- lp.load(os.getenv("SMB_CONF_PATH"))
+ self._lp.load(os.getenv("SMB_CONF_PATH"))
else:
- lp.load_default()
- return lp
+ self._lp.load_default()
+ return self._lp
def get_hostconfig(self):
return Hostconfig(self.get_loadparm())
@@ -61,29 +90,41 @@ 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",
+ callback=self._display_version,
+ help="Display version number")
+
+ def _display_version(self, option, opt_str, arg, parser):
+ import samba
+ print samba.version
+ sys.exit(0)
class CredentialsOptions(optparse.OptionGroup):
"""Command line options for specifying credentials."""
def __init__(self, parser):
- self.no_pass = False
+ self.no_pass = True
+ self.ipaddress = None
optparse.OptionGroup.__init__(self, parser, "Credentials Options")
self.add_option("--simple-bind-dn", metavar="DN", action="callback",
callback=self._set_simple_bind_dn, type=str,
help="DN to use for a simple bind")
self.add_option("--password", metavar="PASSWORD", action="callback",
help="Password", type=str, callback=self._set_password)
- self.add_option("-U", "--username", metavar="USERNAME",
+ self.add_option("-U", "--username", metavar="USERNAME",
action="callback", type=str,
help="Username", callback=self._parse_username)
- self.add_option("-W", "--workgroup", metavar="WORKGROUP",
+ self.add_option("-W", "--workgroup", metavar="WORKGROUP",
action="callback", type=str,
help="Workgroup", callback=self._parse_workgroup)
self.add_option("-N", "--no-pass", action="store_true",
help="Don't ask for a password")
- self.add_option("-k", "--kerberos", metavar="KERBEROS",
+ self.add_option("-k", "--kerberos", metavar="KERBEROS",
action="callback", type=str,
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)
self.creds = Credentials()
def _parse_username(self, option, opt_str, arg, parser):
@@ -94,23 +135,96 @@ class CredentialsOptions(optparse.OptionGroup):
def _set_password(self, option, opt_str, arg, parser):
self.creds.set_password(arg)
+ self.no_pass = False
+
+ def _set_ipaddress(self, option, opt_str, arg, parser):
+ self.ipaddress = arg
def _set_kerberos(self, option, opt_str, arg, parser):
- if bool(arg) or arg.lower() == "yes":
+ if arg.lower() in ["yes", 'true', '1']:
self.creds.set_kerberos_state(MUST_USE_KERBEROS)
- else:
+ elif arg.lower() in ["no", 'false', '0']:
self.creds.set_kerberos_state(DONT_USE_KERBEROS)
+ else:
+ raise optparse.BadOptionErr("invalid kerberos option: %s" % arg)
def _set_simple_bind_dn(self, option, opt_str, arg, parser):
self.creds.set_bind_dn(arg)
- def get_credentials(self, lp):
+ def get_credentials(self, lp, fallback_machine=False):
"""Obtain the credentials set on the command-line.
:param lp: Loadparm object to use.
:return: Credentials object
"""
self.creds.guess(lp)
- if not self.no_pass:
+ if self.no_pass:
self.creds.set_cmdline_callbacks()
+
+ # possibly fallback to using the machine account, if we have
+ # access to the secrets db
+ if fallback_machine and not self.creds.authentication_requested():
+ try:
+ self.creds.set_machine_account(lp)
+ except Exception:
+ pass
+
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
+ self.add_option("--simple-bind-dn2", metavar="DN2", action="callback",
+ 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)
+ self.add_option("--username2", metavar="USERNAME2",
+ action="callback", type=str,
+ 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)
+ 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",
+ action="callback", type=str,
+ help="Use Kerberos", callback=self._set_kerberos2)
+ self.creds2 = Credentials()
+
+ def _parse_username2(self, option, opt_str, arg, parser):
+ self.creds2.parse_string(arg)
+
+ def _parse_workgroup2(self, option, opt_str, arg, parser):
+ self.creds2.set_domain(arg)
+
+ def _set_password2(self, option, opt_str, arg, parser):
+ self.creds2.set_password(arg)
+ 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)
+
+ def _set_simple_bind_dn2(self, option, opt_str, arg, parser):
+ self.creds2.set_bind_dn(arg)
+
+ def get_credentials2(self, lp, guess=True):
+ """Obtain the credentials set on the command-line.
+
+ :param lp: Loadparm object to use.
+ :param guess: Try guess Credentials from environment
+ :return: Credentials object
+ """
+ if guess:
+ self.creds2.guess(lp)
+ elif not self.creds2.get_username():
+ self.creds2.set_anonymous()
+
+ if self.no_pass2:
+ self.creds2.set_cmdline_callbacks()
+ return self.creds2
diff --git a/source4/scripting/python/samba/hostconfig.py b/source4/scripting/python/samba/hostconfig.py
index 313e3420b0..3e6dc6b1dd 100644
--- a/source4/scripting/python/samba/hostconfig.py
+++ b/source4/scripting/python/samba/hostconfig.py
@@ -1,33 +1,83 @@
-#!/usr/bin/python
+#!/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/>.
#
+"""Local host configuration."""
+
from samdb import SamDB
class Hostconfig(object):
- """Aggregate object that contains all information about the configuration
+ """Aggregate object that contains all information about the configuration
of a Samba host."""
- def __init__(self, lp):
+ def __init__(self, lp):
self.lp = lp
+ def get_shares(self):
+ return SharesContainer(self.lp)
+
def get_samdb(self, session_info, credentials):
- return SamDB(url=self.lp.get("sam database"),
- session_info=session_info, credentials=credentials,
+ """Access the SamDB host.
+
+ :param session_info: Session info to use
+ :param credentials: Credentials to access the SamDB with
+ """
+ return SamDB(url=self.lp.get("sam database"),
+ session_info=session_info, credentials=credentials,
lp=self.lp)
+
+# TODO: Rather than accessing Loadparm directly here, we should really
+# have bindings to the param/shares.c and use those.
+
+
+class SharesContainer(object):
+ """A shares container."""
+
+ def __init__(self, lp):
+ self._lp = lp
+
+ def __getitem__(self, name):
+ if name == "global":
+ # [global] is not a share
+ raise KeyError
+ return Share(self._lp[name])
+
+ def __len__(self):
+ if "global" in self._lp.services():
+ return len(self._lp)-1
+ return len(self._lp)
+
+ def keys(self):
+ return [name for name in self._lp.services() if name != "global"]
+
+ def __iter__(self):
+ return iter(self.keys())
+
+
+class Share(object):
+ """A file share."""
+
+ def __init__(self, service):
+ self._service = service
+
+ def __getitem__(self, name):
+ return self._service[name]
+
+ def __setitem__(self, name, value):
+ self._service[name] = value
diff --git a/source4/scripting/python/samba/idmap.py b/source4/scripting/python/samba/idmap.py
index ad209f42de..93fca46edd 100644
--- a/source4/scripting/python/samba/idmap.py
+++ b/source4/scripting/python/samba/idmap.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
# Unix SMB/CIFS implementation.
# Copyright (C) 2008 Kai Blin <kai@samba.org>
@@ -22,6 +22,7 @@
__docformat__ = "restructuredText"
+import ldb
import samba
class IDmapDB(samba.Ldb):
@@ -50,12 +51,34 @@ class IDmapDB(samba.Ldb):
super(IDmapDB, self).connect(url=self.lp.private_path(url), flags=flags,
options=options)
- def setup_name_mapping(self, sid, type, unixid):
+
+ 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="",
+ scope=ldb.SCOPE_SUBTREE)
+ id = res[0].get("xidNumber")
+ flag = ldb.FLAG_MOD_REPLACE
+ if id is None:
+ id = res[0].get("lowerBound")
+ flag = ldb.FLAG_MOD_ADD
+ newid = int(str(id)) + 1
+ msg = ldb.Message()
+ msg.dn = ldb.Dn(self, "CN=CONFIG")
+ msg["xidNumber"] = ldb.MessageElement(str(newid), flag, "xidNumber")
+ self.modify(msg)
+ return id
+
+ def setup_name_mapping(self, sid, type, unixid=None):
"""Setup a mapping between a sam name and a unix name.
:param sid: SID of the NT-side of the mapping.
- :param unixname: Unix name to map to.
+ :param unixname: Unix id to map to, if none supplied the next one will be selected
"""
+ if unixid is None:
+ unixid = self.increment_xid()
type_string = ""
if type == self.TYPE_UID:
type_string = "ID_TYPE_UID"
diff --git a/source4/scripting/python/samba/join.py b/source4/scripting/python/samba/join.py
new file mode 100644
index 0000000000..401f262154
--- /dev/null
+++ b/source4/scripting/python/samba/join.py
@@ -0,0 +1,582 @@
+#!/usr/bin/env python
+#
+# python join code
+# Copyright Andrew Tridgell 2010
+# Copyright Andrew Bartlett 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/>.
+#
+
+"""Joining a domain."""
+
+from samba.auth import system_session
+from samba.samdb import SamDB
+from samba import gensec, Ldb, drs_utils
+import ldb, samba, sys, os, uuid
+from samba.ndr import ndr_pack
+from samba.dcerpc import security, drsuapi, misc, nbt
+from samba.credentials import Credentials, DONT_USE_KERBEROS
+from samba.provision import secretsdb_self_join, provision, FILL_DRS
+from samba.schema import Schema
+from samba.net import Net
+import logging
+import talloc
+
+# this makes debugging easier
+talloc.enable_null_tracking()
+
+
+class dc_join(object):
+ '''perform a DC join'''
+
+ def __init__(ctx, server=None, creds=None, lp=None, site=None,
+ netbios_name=None, targetdir=None, domain=None):
+ ctx.creds = creds
+ ctx.lp = lp
+ ctx.site = site
+ ctx.netbios_name = netbios_name
+ ctx.targetdir = targetdir
+
+ ctx.creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
+ ctx.net = Net(creds=ctx.creds, lp=ctx.lp)
+
+ if server is not None:
+ ctx.server = server
+ else:
+ print("Finding a writeable DC for domain '%s'" % domain)
+ ctx.server = ctx.find_dc(domain)
+ print("Found DC %s" % ctx.server)
+
+ ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
+ session_info=system_session(),
+ credentials=ctx.creds, lp=ctx.lp)
+
+ ctx.myname = netbios_name
+ ctx.samname = "%s$" % ctx.myname
+ ctx.base_dn = str(ctx.samdb.get_default_basedn())
+ ctx.root_dn = str(ctx.samdb.get_root_basedn())
+ ctx.schema_dn = str(ctx.samdb.get_schema_basedn())
+ ctx.config_dn = str(ctx.samdb.get_config_basedn())
+ ctx.domsid = ctx.samdb.get_domain_sid()
+ ctx.domain_name = ctx.get_domain_name()
+
+ lp.set("workgroup", ctx.domain_name)
+ print("workgroup is %s" % ctx.domain_name)
+
+ ctx.dc_ntds_dn = ctx.get_dsServiceName()
+ ctx.dc_dnsHostName = ctx.get_dnsHostName()
+ ctx.behavior_version = ctx.get_behavior_version()
+
+ 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)
+ ctx.ntds_dn = "CN=NTDS Settings,%s" % ctx.server_dn
+ topology_base = "CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,CN=System,%s" % ctx.base_dn
+ if ctx.dn_exists(topology_base):
+ ctx.topology_dn = "CN=%s,%s" % (ctx.myname, topology_base)
+ 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)
+
+ print("realm is %s" % ctx.realm)
+
+ ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
+
+ ctx.acct_dn = "CN=%s,OU=Domain Controllers,%s" % (ctx.myname, ctx.base_dn)
+
+ ctx.tmp_samdb = None
+
+ ctx.SPNs = [ "HOST/%s" % ctx.myname,
+ "HOST/%s" % ctx.dnshostname,
+ "GC/%s/%s" % (ctx.dnshostname, ctx.dnsdomain) ]
+
+ # these elements are optional
+ ctx.never_reveal_sid = None
+ ctx.reveal_sid = None
+ ctx.connection_dn = None
+ ctx.RODC = False
+ ctx.krbtgt_dn = None
+ ctx.drsuapi = None
+ ctx.managedby = None
+
+
+ def del_noerror(ctx, dn, recursive=False):
+ if recursive:
+ try:
+ res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
+ except Exception:
+ return
+ for r in res:
+ ctx.del_noerror(r.dn, recursive=True)
+ try:
+ ctx.samdb.delete(dn)
+ print "Deleted %s" % dn
+ except Exception:
+ pass
+
+ def cleanup_old_join(ctx):
+ '''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)
+ if ctx.connection_dn is not None:
+ ctx.del_noerror(ctx.connection_dn)
+ if ctx.krbtgt_dn is not None:
+ ctx.del_noerror(ctx.krbtgt_dn)
+ ctx.del_noerror(ctx.ntds_dn)
+ ctx.del_noerror(ctx.server_dn, recursive=True)
+ if ctx.topology_dn:
+ ctx.del_noerror(ctx.topology_dn)
+ if res:
+ ctx.new_krbtgt_dn = res[0]["msDS-Krbtgtlink"][0]
+ ctx.del_noerror(ctx.new_krbtgt_dn)
+ except Exception:
+ pass
+
+ def find_dc(ctx, 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)
+ 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 != "":
+ ctx.site = ctx.cldap_ret.client_site
+ 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]:
+ return int(res[0]["msDS-Behavior-Version"][0])
+ else:
+ return samba.dsdb.DS_DOMAIN_FUNCTION_2000
+
+ def get_dnsHostName(ctx):
+ res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["dnsHostName"])
+ return res[0]["dnsHostName"][0]
+
+ def get_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_default_basedn())
+ return res[0]["nETBIOSName"][0]
+
+ def get_mysid(ctx):
+ '''get the SID of the connected user. Only works with w2k8 and later,
+ so only used for RODC join'''
+ res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["tokenGroups"])
+ binsid = res[0]["tokenGroups"][0]
+ return ctx.samdb.schema_format_value("objectSID", binsid)
+
+ def dn_exists(ctx, dn):
+ '''check if a DN exists'''
+ try:
+ res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[])
+ except ldb.LdbError, (enum, estr):
+ if enum == ldb.ERR_NO_SUCH_OBJECT:
+ return False
+ raise
+ return True
+
+ def add_krbtgt_account(ctx):
+ '''RODCs need a special krbtgt account'''
+ print "Adding %s" % ctx.krbtgt_dn
+ rec = {
+ "dn" : ctx.krbtgt_dn,
+ "objectclass" : "user",
+ "useraccountcontrol" : str(samba.dsdb.UF_NORMAL_ACCOUNT |
+ samba.dsdb.UF_ACCOUNTDISABLE),
+ "showinadvancedviewonly" : "TRUE",
+ "description" : "krbtgt for %s" % ctx.samname}
+ ctx.samdb.add(rec, ["rodc_join:1:1"])
+
+ # now we need to search for the samAccountName attribute on the krbtgt DN,
+ # as this will have been magically set to the krbtgt number
+ res = ctx.samdb.search(base=ctx.krbtgt_dn, scope=ldb.SCOPE_BASE, attrs=["samAccountName"])
+ ctx.krbtgt_name = res[0]["samAccountName"][0]
+
+ print "Got krbtgt_name=%s" % ctx.krbtgt_name
+
+ m = ldb.Message()
+ m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
+ m["msDS-krbTgtLink"] = ldb.MessageElement(ctx.krbtgt_dn,
+ ldb.FLAG_MOD_REPLACE, "msDS-krbTgtLink")
+ ctx.samdb.modify(m)
+
+ ctx.new_krbtgt_dn = "CN=%s,CN=Users,%s" % (ctx.krbtgt_name, ctx.base_dn)
+ print "Renaming %s to %s" % (ctx.krbtgt_dn, ctx.new_krbtgt_dn)
+ ctx.samdb.rename(ctx.krbtgt_dn, ctx.new_krbtgt_dn)
+
+ 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)
+ ctx.drsuapi = drsuapi.drsuapi(binding_string, ctx.lp, ctx.creds)
+ (ctx.drsuapi_handle, ctx.bind_supported_extensions) = drs_utils.drs_DsBind(ctx.drsuapi)
+
+ def create_tmp_samdb(ctx):
+ '''create a temporary samdb object for schema queries'''
+ ctx.tmp_schema = Schema(security.dom_sid(ctx.domsid),
+ schemadn=ctx.schema_dn)
+ ctx.tmp_samdb = SamDB(session_info=system_session(), url=None, auto_connect=False,
+ credentials=ctx.creds, lp=ctx.lp, global_schema=False,
+ am_rodc=False)
+ ctx.tmp_samdb.set_schema(ctx.tmp_schema)
+
+ def build_DsReplicaAttribute(ctx, attrname, attrvalue):
+ '''build a DsReplicaAttributeCtr object'''
+ r = drsuapi.DsReplicaAttribute()
+ r.attid = ctx.tmp_samdb.get_attid_from_lDAPDisplayName(attrname)
+ r.value_ctr = 1
+
+
+ def DsAddEntry(ctx, rec):
+ '''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
+
+ req2 = drsuapi.DsAddEntryRequest2()
+ req2.first_object = first_object
+
+ (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 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,
+ "objectclass" : "nTDSDSA",
+ "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
+ "dMDLocation" : ctx.schema_dn}
+
+ if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
+ rec["msDS-Behavior-Version"] = str(ctx.behavior_version)
+
+ 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["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 ]
+ if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
+ rec["msDS-HasMasterNCs"] = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
+ rec["options"] = "1"
+ rec["invocationId"] = ndr_pack(misc.GUID(str(uuid.uuid4())))
+ 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]))
+
+ if ctx.connection_dn is not None:
+ print "Adding %s" % ctx.connection_dn
+ rec = {
+ "dn" : ctx.connection_dn,
+ "objectclass" : "nTDSConnection",
+ "enabledconnection" : "TRUE",
+ "options" : "65",
+ "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)
+
+ 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)
+
+ 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])
+
+ 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_provision(ctx):
+ '''provision the local SAM'''
+
+ print "Calling bare provision"
+
+ logger = logging.getLogger("provision")
+ 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)
+ print "Provision OK for domain DN %s" % presult.domaindn
+ ctx.local_samdb = presult.samdb
+ ctx.lp = presult.lp
+ ctx.paths = presult.paths
+
+
+ def join_replicate(ctx):
+ '''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.RODC:
+ repl_creds = Credentials()
+ repl_creds.guess(ctx.lp)
+ repl_creds.set_kerberos_state(DONT_USE_KERBEROS)
+ repl_creds.set_username(ctx.samname)
+ repl_creds.set_password(ctx.acct_pass)
+ else:
+ repl_creds = ctx.creds
+
+ binding_options = "seal"
+ if ctx.lp.get("debug level") >= 5:
+ binding_options += ",print"
+ repl = drs_utils.drs_Replicate(
+ "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
+ ctx.lp, repl_creds, ctx.local_samdb)
+
+ repl.replicate(ctx.schema_dn, source_dsa_invocation_id,
+ destination_dsa_guid, schema=True, rodc=ctx.RODC,
+ replica_flags=ctx.replica_flags)
+ 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 ctx.RODC:
+ repl.replicate(ctx.acct_dn, source_dsa_invocation_id,
+ destination_dsa_guid,
+ exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
+ repl.replicate(ctx.new_krbtgt_dn, source_dsa_invocation_id,
+ destination_dsa_guid,
+ exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
+
+ print "Committing SAM database"
+ except:
+ ctx.local_samdb.transaction_cancel()
+ raise
+ else:
+ ctx.local_samdb.transaction_commit()
+
+
+ def join_finalise(ctx):
+ '''finalise the join, mark us synchronised and setup secrets db'''
+
+ print "Setting isSynchronized"
+ m = ldb.Message()
+ m.dn = ldb.Dn(ctx.samdb, '@ROOTDSE')
+ m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isSynchronized")
+ ctx.samdb.modify(m)
+
+ secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
+
+ print "Setting up secrets database"
+ secretsdb_self_join(secrets_ldb, domain=ctx.domain_name,
+ realm=ctx.realm,
+ dnsdomain=ctx.dnsdomain,
+ netbiosname=ctx.myname,
+ domainsid=security.dom_sid(ctx.domsid),
+ machinepass=ctx.acct_pass,
+ secure_channel_type=ctx.secure_channel_type,
+ key_version_number=ctx.key_version_number)
+
+ def do_join(ctx):
+ ctx.cleanup_old_join()
+ try:
+ ctx.join_add_objects()
+ ctx.join_provision()
+ ctx.join_replicate()
+ ctx.join_finalise()
+ except Exception:
+ 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"""
+
+ ctx = dc_join(server, creds, lp, site, netbios_name, targetdir, domain)
+
+ 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.reveal_sid = "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_ALLOW)
+
+ mysid = ctx.get_mysid()
+ admin_dn = "<SID=%s>" % mysid
+ ctx.managedby = admin_dn
+
+ ctx.userAccountControl = (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT |
+ samba.dsdb.UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION |
+ samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT)
+
+ ctx.SPNs.extend([ "RestrictedKrbHost/%s" % ctx.myname,
+ "RestrictedKrbHost/%s" % ctx.dnshostname ])
+
+ ctx.connection_dn = "CN=RODC Connection (FRS),%s" % ctx.ntds_dn
+ ctx.secure_channel_type = misc.SEC_CHAN_RODC
+ ctx.RODC = True
+ ctx.replica_flags = (drsuapi.DRSUAPI_DRS_INIT_SYNC |
+ drsuapi.DRSUAPI_DRS_PER_SYNC |
+ drsuapi.DRSUAPI_DRS_GET_ANC |
+ drsuapi.DRSUAPI_DRS_NEVER_SYNCED |
+ drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
+ drsuapi.DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP)
+ 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)
+
+ 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.do_join()
+ print "Joined domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid)
diff --git a/source4/scripting/python/samba/ms_display_specifiers.py b/source4/scripting/python/samba/ms_display_specifiers.py
index 2a54e4ae0e..fd92b20e66 100644
--- a/source4/scripting/python/samba/ms_display_specifiers.py
+++ b/source4/scripting/python/samba/ms_display_specifiers.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/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 a0abc337ce..64bb28a967 100644
--- a/source4/scripting/python/samba/ms_schema.py
+++ b/source4/scripting/python/samba/ms_schema.py
@@ -1,12 +1,14 @@
-#!/usr/bin/env python
#
# create schema.ldif (as a string) from WSPP documentation
#
# based on minschema.py and minschema_wspp
#
+"""Generate LDIF from WSPP documentation."""
+
import re
import base64
+import uuid
bitFields = {}
@@ -33,19 +35,19 @@ bitFields["searchflags"] = {
# ADTS: 2.2.10
bitFields["systemflags"] = {
- 'FLAG_ATTR_NOT_REPLICATED': 31, 'FLAG_CR_NTDS_NC': 31, # NR
- 'FLAG_ATTR_REQ_PARTIAL_SET_MEMBER': 30, 'FLAG_CR_NTDS_DOMAIN': 30, # PS
- 'FLAG_ATTR_IS_CONSTRUCTED': 29, 'FLAG_CR_NTDS_NOT_GC_REPLICATED': 29, # CS
- 'FLAG_ATTR_IS_OPERATIONAL': 28, # OP
- 'FLAG_SCHEMA_BASE_OBJECT': 27, # BS
- 'FLAG_ATTR_IS_RDN': 26, # RD
- 'FLAG_DISALLOW_MOVE_ON_DELETE': 6, # DE
- 'FLAG_DOMAIN_DISALLOW_MOVE': 5, # DM
- 'FLAG_DOMAIN_DISALLOW_RENAME': 4, # DR
- 'FLAG_CONFIG_ALLOW_LIMITED_MOVE': 3, # AL
- 'FLAG_CONFIG_ALLOW_MOVE': 2, # AM
- 'FLAG_CONFIG_ALLOW_RENAME': 1, # AR
- 'FLAG_DISALLOW_DELETE': 0 # DD
+ 'FLAG_ATTR_NOT_REPLICATED': 31, 'FLAG_CR_NTDS_NC': 31, # NR
+ 'FLAG_ATTR_REQ_PARTIAL_SET_MEMBER': 30, 'FLAG_CR_NTDS_DOMAIN': 30, # PS
+ 'FLAG_ATTR_IS_CONSTRUCTED': 29, 'FLAG_CR_NTDS_NOT_GC_REPLICATED': 29, # CS
+ 'FLAG_ATTR_IS_OPERATIONAL': 28, # OP
+ 'FLAG_SCHEMA_BASE_OBJECT': 27, # BS
+ 'FLAG_ATTR_IS_RDN': 26, # RD
+ 'FLAG_DISALLOW_MOVE_ON_DELETE': 6, # DE
+ 'FLAG_DOMAIN_DISALLOW_MOVE': 5, # DM
+ 'FLAG_DOMAIN_DISALLOW_RENAME': 4, # DR
+ 'FLAG_CONFIG_ALLOW_LIMITED_MOVE': 3, # AL
+ 'FLAG_CONFIG_ALLOW_MOVE': 2, # AM
+ 'FLAG_CONFIG_ALLOW_RENAME': 1, # AR
+ 'FLAG_DISALLOW_DELETE': 0 # DD
}
# ADTS: 2.2.11
@@ -227,6 +229,9 @@ def __transform_entry(entry, objectClass):
entry.insert(0, ["dn", "CN=%s,${SCHEMADN}" % cn])
entry.insert(1, ["objectClass", ["top", objectClass]])
entry.insert(2, ["cn", cn])
+ 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()
@@ -272,5 +277,3 @@ if __name__ == '__main__':
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 e718ff3422..112668523f 100644
--- a/source4/scripting/python/samba/ndr.py
+++ b/source4/scripting/python/samba/ndr.py
@@ -1,28 +1,49 @@
-#!/usr/bin/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/>.
#
+
+"""Network Data Representation (NDR) marshalling and unmarshalling."""
+
+
def ndr_pack(object):
- return object.__ndr_pack__()
+ """Pack a NDR object.
+
+ :param object: Object to pack
+ :return: String object with marshalled object.
+ """
+ ndr_pack = getattr(object, "__ndr_pack__", None)
+ if ndr_pack is None:
+ raise TypeError("%r is not a NDR object" % object)
+ return ndr_pack()
def ndr_unpack(cls, data):
+ """NDR unpack an object.
+
+ :param cls: Class of the object to unpack
+ :param data: Buffer to unpack
+ :return: Unpacked object
+ """
object = cls()
object.__ndr_unpack__(data)
return object
+
+
+def ndr_print(object):
+ return object.__ndr_print__()
diff --git a/source4/scripting/python/samba/netcmd/__init__.py b/source4/scripting/python/samba/netcmd/__init__.py
new file mode 100644
index 0000000000..aa74f657b2
--- /dev/null
+++ b/source4/scripting/python/samba/netcmd/__init__.py
@@ -0,0 +1,215 @@
+#!/usr/bin/env python
+
+# Unix SMB/CIFS implementation.
+# 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/>.
+#
+
+import optparse, samba
+from samba import getopt as options
+from ldb import LdbError
+import sys, traceback
+
+
+class Option(optparse.Option):
+ pass
+
+
+class Command(object):
+ """A net command."""
+
+ def _get_description(self):
+ return self.__doc__.splitlines()[0].rstrip("\n")
+
+ def _get_name(self):
+ name = self.__class__.__name__
+ if name.startswith("cmd_"):
+ return name[4:]
+ return name
+
+ name = property(_get_name)
+
+ def usage(self, *args):
+ parser, _ = self._create_parser()
+ parser.print_usage()
+
+ description = property(_get_description)
+
+ def _get_synopsis(self):
+ ret = self.name
+ if self.takes_args:
+ ret += " " + " ".join([x.upper() for x in self.takes_args])
+ return ret
+
+ def show_command_error(self, e):
+ '''display a command error'''
+ if isinstance(e, CommandError):
+ (etype, evalue, etraceback) = e.exception_info
+ inner_exception = e.inner_exception
+ message = e.message
+ force_traceback = False
+ else:
+ (etype, evalue, etraceback) = sys.exc_info()
+ inner_exception = e
+ message = "uncaught exception"
+ force_traceback = True
+
+ if isinstance(inner_exception, LdbError):
+ (ldb_ecode, ldb_emsg) = inner_exception
+ print >>sys.stderr, "ERROR(ldb): %s - %s" % (message, ldb_emsg)
+ elif isinstance(inner_exception, AssertionError):
+ print >>sys.stderr, "ERROR(assert): %s" % message
+ force_traceback = True
+ elif isinstance(inner_exception, RuntimeError):
+ print >>sys.stderr, "ERROR(runtime): %s - %s" % (message, evalue)
+ elif type(inner_exception) is Exception:
+ print >>sys.stderr, "ERROR(exception): %s - %s" % (message, evalue)
+ force_traceback = True
+ elif inner_exception is None:
+ print >>sys.stderr, "ERROR: %s" % (message)
+ else:
+ print >>sys.stderr, "ERROR(%s): %s - %s" % (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"
+ parser.add_options(self.takes_options)
+ optiongroups = {}
+ for name, optiongroup in self.takes_optiongroups.iteritems():
+ optiongroups[name] = optiongroup(parser)
+ parser.add_option_group(optiongroups[name])
+ return parser, optiongroups
+
+ def message(self, text):
+ print text
+
+ def _run(self, *argv):
+ parser, optiongroups = self._create_parser()
+ 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)
+ min_args = 0
+ max_args = 0
+ 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)
+ return -1
+ try:
+ return self.run(*args, **kwargs)
+ except Exception, e:
+ self.show_command_error(e)
+ return -1
+
+ def run(self):
+ """Run the command. This should be overriden by all subclasses."""
+ raise NotImplementedError(self.run)
+
+
+class SuperCommand(Command):
+ """A command with subcommands."""
+
+ 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)
+
+
+class CommandError(Exception):
+ '''an exception class for netcmd 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
new file mode 100644
index 0000000000..bd72c8f361
--- /dev/null
+++ b/source4/scripting/python/samba/netcmd/common.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python
+#
+# common functions for samba-tool python commands
+#
+# Copyright Andrew Tridgell 2010
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+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()
diff --git a/source4/scripting/python/samba/netcmd/domainlevel.py b/source4/scripting/python/samba/netcmd/domainlevel.py
new file mode 100644
index 0000000000..3d50ccea78
--- /dev/null
+++ b/source4/scripting/python/samba/netcmd/domainlevel.py
@@ -0,0 +1,247 @@
+#!/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
new file mode 100644
index 0000000000..7dea9de856
--- /dev/null
+++ b/source4/scripting/python/samba/netcmd/drs.py
@@ -0,0 +1,477 @@
+#!/usr/bin/env python
+#
+# implement samba_tool drs commands
+#
+# Copyright Andrew Tridgell 2010
+#
+# based on C implementation by Kamen Mazdrashki <kamen.mazdrashki@postpath.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import samba.getopt as options
+import ldb
+
+from samba.auth import system_session
+from samba.netcmd import (
+ Command,
+ CommandError,
+ Option,
+ SuperCommand,
+ )
+from samba.samdb import SamDB
+from samba import drs_utils, nttime2string, dsdb
+from samba.dcerpc import drsuapi, misc
+import common
+
+def drsuapi_connect(ctx):
+ '''make a DRSUAPI connection to the server'''
+ 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)
+ except Exception, e:
+ raise CommandError("DRS connection to %s failed" % ctx.server, e)
+
+
+def samdb_connect(ctx):
+ '''make a ldap connection to the server'''
+ try:
+ ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
+ session_info=system_session(),
+ credentials=ctx.creds, lp=ctx.lp)
+ except Exception, e:
+ raise CommandError("LDAP connection to %s failed" % ctx.server, e)
+
+
+def drs_errmsg(werr):
+ '''return "was successful" or an error string'''
+ (ecode, estring) = werr
+ if ecode == 0:
+ return "was successful"
+ return "failed, result %u (%s)" % (ecode, estring)
+
+
+def attr_default(msg, attrname, default):
+ '''get an attribute from a ldap msg with a default'''
+ if attrname in msg:
+ return msg[attrname][0]
+ return default
+
+
+def drs_parse_ntds_dn(ntds_dn):
+ '''parse a NTDS DN returning a site and server'''
+ a = ntds_dn.split(',')
+ if a[0] != "CN=NTDS Settings" or a[2] != "CN=Servers" or a[4] != 'CN=Sites':
+ raise RuntimeError("bad NTDS DN %s" % ntds_dn)
+ server = a[1].split('=')[1]
+ site = a[3].split('=')[1]
+ return (site, server)
+
+
+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"""
+
+ synopsis = "%prog drs showrepl <DC>"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_args = ["DC?"]
+
+ def print_neighbour(self, n):
+ '''print one set of neighbour information'''
+ self.message("%s" % n.naming_context_dn)
+ try:
+ (site, server) = drs_parse_ntds_dn(n.source_dsa_obj_dn)
+ self.message("\t%s\%s via RPC" % (site, server))
+ except RuntimeError:
+ self.message("\tNTDS DN: %s" % n.source_dsa_obj_dn)
+ self.message("\t\tDSA object GUID: %s" % n.source_dsa_obj_guid)
+ self.message("\t\tLast attempt @ %s %s" % (nttime2string(n.last_attempt),
+ drs_errmsg(n.result_last_attempt)))
+ self.message("\t\t%u consecutive failure(s)." % n.consecutive_sync_failures)
+ self.message("\t\tLast success @ %s" % nttime2string(n.last_success))
+ self.message("")
+
+ def drsuapi_ReplicaInfo(ctx, info_type):
+ '''call a DsReplicaInfo'''
+
+ req1 = drsuapi.DsReplicaGetInfoRequest1()
+ req1.info_type = info_type
+ try:
+ (info_type, info) = ctx.drsuapi.DsReplicaGetInfo(ctx.drsuapi_handle, 1, req1)
+ except Exception, e:
+ raise CommandError("DsReplicaGetInfo of type %u failed" % info_type, e)
+ return (info_type, info)
+
+
+ def run(self, DC=None, sambaopts=None,
+ credopts=None, versionopts=None, server=None):
+
+ self.lp = sambaopts.get_loadparm()
+ if DC is None:
+ DC = common.netcmd_dnsname(self.lp)
+ self.server = DC
+ self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
+
+ drsuapi_connect(self)
+ samdb_connect(self)
+
+ # show domain information
+ ntds_dn = get_dsServiceName(self.samdb)
+ server_dns = self.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["dnsHostName"])[0]['dnsHostName'][0]
+
+ (site, server) = drs_parse_ntds_dn(ntds_dn)
+ try:
+ ntds = self.samdb.search(base=ntds_dn, scope=ldb.SCOPE_BASE, attrs=['options', 'objectGUID', 'invocationId'])
+ except Exception, e:
+ raise CommandError("Failed to search NTDS DN %s" % ntds_dn)
+ conn = self.samdb.search(base=ntds_dn, expression="(objectClass=nTDSConnection)")
+
+ self.message("%s\\%s" % (site, server))
+ self.message("DSA Options: 0x%08x" % int(attr_default(ntds[0], "options", 0)))
+ self.message("DSA object GUID: %s" % self.samdb.schema_format_value("objectGUID", ntds[0]["objectGUID"][0]))
+ self.message("DSA invocationId: %s\n" % self.samdb.schema_format_value("objectGUID", ntds[0]["invocationId"][0]))
+
+ self.message("==== INBOUND NEIGHBORS ====\n")
+ (info_type, info) = self.drsuapi_ReplicaInfo(drsuapi.DRSUAPI_DS_REPLICA_INFO_NEIGHBORS)
+ for n in info.array:
+ self.print_neighbour(n)
+
+
+ self.message("==== OUTBOUND NEIGHBORS ====\n")
+ (info_type, info) = self.drsuapi_ReplicaInfo(drsuapi.DRSUAPI_DS_REPLICA_INFO_REPSTO)
+ for n in info.array:
+ self.print_neighbour(n)
+
+ reasons = ['NTDSCONN_KCC_GC_TOPOLOGY',
+ 'NTDSCONN_KCC_RING_TOPOLOGY',
+ 'NTDSCONN_KCC_MINIMIZE_HOPS_TOPOLOGY',
+ 'NTDSCONN_KCC_STALE_SERVERS_TOPOLOGY',
+ 'NTDSCONN_KCC_OSCILLATING_CONNECTION_TOPOLOGY',
+ 'NTDSCONN_KCC_INTERSITE_GC_TOPOLOGY',
+ 'NTDSCONN_KCC_INTERSITE_TOPOLOGY',
+ 'NTDSCONN_KCC_SERVER_FAILOVER_TOPOLOGY',
+ 'NTDSCONN_KCC_SITE_FAILOVER_TOPOLOGY',
+ 'NTDSCONN_KCC_REDUNDANT_SERVER_TOPOLOGY']
+
+ self.message("==== KCC CONNECTION OBJECTS ====\n")
+ for c in conn:
+ 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 DN name : %s" % c['fromServer'][0])
+ self.message("\t\tTransportType: RPC")
+ self.message("\t\toptions: 0x%08X" % int(attr_default(c, 'options', 0)))
+ if not 'mS-DS-ReplicatesNCReason' in c:
+ self.message("Warning: No NC replicated for Connection!")
+ continue
+ for r in c['mS-DS-ReplicatesNCReason']:
+ a = str(r).split(':')
+ self.message("\t\tReplicatesNC: %s" % a[3])
+ self.message("\t\tReason: 0x%08x" % int(a[2]))
+ for s in reasons:
+ if getattr(dsdb, s, 0) & int(a[2]):
+ self.message("\t\t\t%s" % s)
+
+
+class cmd_drs_kcc(Command):
+ """trigger knowledge consistency center run"""
+
+ synopsis = "%prog drs kcc <DC>"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_args = ["DC?"]
+
+ def run(self, DC=None, sambaopts=None,
+ credopts=None, versionopts=None, server=None):
+
+ self.lp = sambaopts.get_loadparm()
+ if DC is None:
+ DC = common.netcmd_dnsname(self.lp)
+ self.server = DC
+
+ self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
+
+ drsuapi_connect(self)
+
+ req1 = drsuapi.DsExecuteKCC1()
+ try:
+ self.drsuapi.DsExecuteKCC(self.drsuapi_handle, 1, req1)
+ except Exception, e:
+ raise CommandError("DsExecuteKCC failed", e)
+ self.message("Consistency check on %s successful." % DC)
+
+
+
+class cmd_drs_replicate(Command):
+ """replicate a naming context between two DCs"""
+
+ synopsis = "%prog drs replicate <DEST_DC> <SOURCE_DC> <NC>"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_args = ["DEST_DC", "SOURCE_DC", "NC"]
+
+ takes_options = [
+ Option("--add-ref", help="use ADD_REF to add to repsTo on source", action="store_true"),
+ Option("--sync-forced", help="use SYNC_FORCED to force inbound replication", action="store_true"),
+ ]
+
+ def run(self, DEST_DC, SOURCE_DC, NC, add_ref=False, sync_forced=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)
+
+ 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),
+ attrs=[])
+ if len(msg) == 0:
+ raise CommandError("Failed to find source DC %s" % SOURCE_DC)
+ server_dn = msg[0]['dn']
+
+ msg = self.samdb.search(base=server_dn, scope=ldb.SCOPE_ONELEVEL,
+ expression="(|(objectCategory=nTDSDSA)(objectCategory=nTDSDSARO))",
+ attrs=['objectGUID', 'options'])
+ if len(msg) == 0:
+ raise CommandError("Failed to find source NTDS DN %s" % SOURCE_DC)
+ source_dsa_guid = msg[0]['objectGUID'][0]
+ 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
+ if add_ref:
+ req1.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)
+
+ try:
+ self.drsuapi.DsReplicaSync(self.drsuapi_handle, 1, req1)
+ except Exception, estr:
+ raise CommandError("DsReplicaSync failed", estr)
+ self.message("Replicate from %s to %s was successful." % (SOURCE_DC, DEST_DC))
+
+
+
+class cmd_drs_bind(Command):
+ """show DRS capabilities of a server"""
+
+ synopsis = "%prog drs bind <DC>"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_args = ["DC?"]
+
+ def run(self, DC=None, sambaopts=None,
+ credopts=None, versionopts=None, server=None):
+
+ self.lp = sambaopts.get_loadparm()
+ if DC is None:
+ DC = common.netcmd_dnsname(self.lp)
+ self.server = DC
+ self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
+
+ drsuapi_connect(self)
+ samdb_connect(self)
+
+ bind_info = drsuapi.DsBindInfoCtr()
+ bind_info.length = 28
+ bind_info.info = drsuapi.DsBindInfo28()
+ (info, handle) = self.drsuapi.DsBind(misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID), bind_info)
+
+ optmap = [
+ ("DRSUAPI_SUPPORTED_EXTENSION_BASE" , "DRS_EXT_BASE"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_ASYNC_REPLICATION" , "DRS_EXT_ASYNCREPL"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_REMOVEAPI" , "DRS_EXT_REMOVEAPI"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_MOVEREQ_V2" , "DRS_EXT_MOVEREQ_V2"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_GETCHG_COMPRESS" , "DRS_EXT_GETCHG_DEFLATE"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V1" , "DRS_EXT_DCINFO_V1"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_RESTORE_USN_OPTIMIZATION" , "DRS_EXT_RESTORE_USN_OPTIMIZATION"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_ADDENTRY" , "DRS_EXT_ADDENTRY"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_KCC_EXECUTE" , "DRS_EXT_KCC_EXECUTE"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_ADDENTRY_V2" , "DRS_EXT_ADDENTRY_V2"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_LINKED_VALUE_REPLICATION" , "DRS_EXT_LINKED_VALUE_REPLICATION"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V2" , "DRS_EXT_DCINFO_V2"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_INSTANCE_TYPE_NOT_REQ_ON_MOD","DRS_EXT_INSTANCE_TYPE_NOT_REQ_ON_MOD"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_CRYPTO_BIND" , "DRS_EXT_CRYPTO_BIND"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_GET_REPL_INFO" , "DRS_EXT_GET_REPL_INFO"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_STRONG_ENCRYPTION" , "DRS_EXT_STRONG_ENCRYPTION"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V01" , "DRS_EXT_DCINFO_VFFFFFFFF"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_TRANSITIVE_MEMBERSHIP" , "DRS_EXT_TRANSITIVE_MEMBERSHIP"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_ADD_SID_HISTORY" , "DRS_EXT_ADD_SID_HISTORY"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_POST_BETA3" , "DRS_EXT_POST_BETA3"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V5" , "DRS_EXT_GETCHGREQ_V5"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_GET_MEMBERSHIPS2" , "DRS_EXT_GETMEMBERSHIPS2"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V6" , "DRS_EXT_GETCHGREQ_V6"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_NONDOMAIN_NCS" , "DRS_EXT_NONDOMAIN_NCS"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V8" , "DRS_EXT_GETCHGREQ_V8"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V5" , "DRS_EXT_GETCHGREPLY_V5"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V6" , "DRS_EXT_GETCHGREPLY_V6"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_ADDENTRYREPLY_V3" , "DRS_EXT_WHISTLER_BETA3"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V7" , "DRS_EXT_WHISTLER_BETA3"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_VERIFY_OBJECT" , "DRS_EXT_WHISTLER_BETA3"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_XPRESS_COMPRESS" , "DRS_EXT_W2K3_DEFLATE"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V10" , "DRS_EXT_GETCHGREQ_V10"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_RESERVED_PART2" , "DRS_EXT_RESERVED_FOR_WIN2K_OR_DOTNET_PART2"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_RESERVED_PART3" , "DRS_EXT_RESERVED_FOR_WIN2K_OR_DOTNET_PART3")
+ ]
+
+ optmap_ext = [
+ ("DRSUAPI_SUPPORTED_EXTENSION_ADAM", "DRS_EXT_ADAM"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_LH_BETA2", "DRS_EXT_LH_BETA2"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_RECYCLE_BIN", "DRS_EXT_RECYCLE_BIN")]
+
+ self.message("Bind to %s succeeded." % DC)
+ self.message("Extensions supported:")
+ for (opt, str) in optmap:
+ optval = getattr(drsuapi, opt, 0)
+ if info.info.supported_extensions & optval:
+ yesno = "Yes"
+ else:
+ yesno = "No "
+ self.message(" %-60s: %s (%s)" % (opt, yesno, str))
+
+ if isinstance(info.info, drsuapi.DsBindInfo48):
+ self.message("\nExtended Extensions supported:")
+ for (opt, str) in optmap_ext:
+ optval = getattr(drsuapi, opt, 0)
+ if info.info.supported_extensions_ext & optval:
+ yesno = "Yes"
+ else:
+ yesno = "No "
+ self.message(" %-60s: %s (%s)" % (opt, yesno, str))
+
+ self.message("\nSite GUID: %s" % info.info.site_guid)
+ self.message("Repl epoch: %u" % info.info.repl_epoch)
+ if isinstance(info.info, drsuapi.DsBindInfo48):
+ self.message("Forest GUID: %s" % info.info.config_dn_guid)
+
+
+
+class cmd_drs_options(Command):
+ """query or change 'options' for NTDS Settings object of a domain controller"""
+
+ synopsis = ("%prog drs options <DC>"
+ " [--dsa-option={+|-}IS_GC | {+|-}DISABLE_INBOUND_REPL"
+ " |{+|-}DISABLE_OUTBOUND_REPL | {+|-}DISABLE_NTDSCONN_XLATE]")
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_args = ["DC"]
+
+ takes_options = [
+ Option("--dsa-option", help="DSA option to enable/disable", type="str"),
+ ]
+
+ option_map = {"IS_GC": 0x00000001,
+ "DISABLE_INBOUND_REPL": 0x00000002,
+ "DISABLE_OUTBOUND_REPL": 0x00000004,
+ "DISABLE_NTDSCONN_XLATE": 0x00000008}
+
+ def run(self, DC, dsa_option=None,
+ sambaopts=None, credopts=None, versionopts=None):
+
+ self.lp = sambaopts.get_loadparm()
+ if DC is None:
+ DC = common.netcmd_dnsname(self.lp)
+ self.server = DC
+ self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
+
+ samdb_connect(self)
+
+ ntds_dn = get_dsServiceName(self.samdb)
+ res = self.samdb.search(base=ntds_dn, scope=ldb.SCOPE_BASE, attrs=["options"])
+ dsa_opts = int(res[0]["options"][0])
+
+ # print out current DSA options
+ cur_opts = [x for x in self.option_map if self.option_map[x] & dsa_opts]
+ self.message("Current DSA options: " + ", ".join(cur_opts))
+
+ # modify options
+ if dsa_option:
+ if dsa_option[:1] not in ("+", "-"):
+ raise CommandError("Unknown option %s" % dsa_option)
+ flag = dsa_option[1:]
+ if flag not in self.option_map.keys():
+ raise CommandError("Unknown option %s" % dsa_option)
+ if dsa_option[:1] == "+":
+ dsa_opts |= self.option_map[flag]
+ else:
+ dsa_opts &= ~self.option_map[flag]
+ #save new options
+ m = ldb.Message()
+ m.dn = ldb.Dn(self.samdb, ntds_dn)
+ m["options"]= ldb.MessageElement(str(dsa_opts), ldb.FLAG_MOD_REPLACE, "options")
+ self.samdb.modify(m)
+ # print out new DSA options
+ cur_opts = [x for x in self.option_map if self.option_map[x] & dsa_opts]
+ self.message("New DSA options: " + ", ".join(cur_opts))
+
+
+class cmd_drs(SuperCommand):
+ """DRS commands"""
+
+ subcommands = {}
+ subcommands["bind"] = cmd_drs_bind()
+ subcommands["kcc"] = cmd_drs_kcc()
+ subcommands["replicate"] = cmd_drs_replicate()
+ subcommands["showrepl"] = cmd_drs_showrepl()
+ subcommands["options"] = cmd_drs_options()
diff --git a/source4/scripting/python/samba/netcmd/dsacl.py b/source4/scripting/python/samba/netcmd/dsacl.py
new file mode 100644
index 0000000000..58a3552687
--- /dev/null
+++ b/source4/scripting/python/samba/netcmd/dsacl.py
@@ -0,0 +1,182 @@
+#!/usr/bin/env python
+#
+# Manipulate ACLs on directory objects
+#
+# Copyright (C) Nadezhda Ivanova <nivanova@samba.org> 2010
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import samba.getopt as options
+from samba.dcerpc import security
+from samba.samdb import SamDB
+from samba.ndr import ndr_unpack, ndr_pack
+from samba.dcerpc.security import (
+ GUID_DRS_ALLOCATE_RIDS, GUID_DRS_CHANGE_DOMAIN_MASTER,
+ GUID_DRS_CHANGE_INFR_MASTER, GUID_DRS_CHANGE_PDC,
+ GUID_DRS_CHANGE_RID_MASTER, GUID_DRS_CHANGE_SCHEMA_MASTER,
+ GUID_DRS_GET_CHANGES, GUID_DRS_GET_ALL_CHANGES,
+ GUID_DRS_GET_FILTERED_ATTRIBUTES, GUID_DRS_MANAGE_TOPOLOGY,
+ GUID_DRS_MONITOR_TOPOLOGY, GUID_DRS_REPL_SYNCRONIZE,
+ GUID_DRS_RO_REPL_SECRET_SYNC)
+
+
+import ldb
+from ldb import SCOPE_BASE
+import re
+
+from samba.auth import system_session
+from samba.netcmd import (
+ Command,
+ CommandError,
+ SuperCommand,
+ Option,
+ )
+
+class cmd_ds_acl_set(Command):
+ """Modify access list on a directory object"""
+
+ synopsis = "set --objectdn=objectdn --car=control right --action=[deny|allow] --trusteedn=trustee-dn"
+ car_help = """ The access control right to allow or deny """
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_options = [
+ Option("--host", help="LDB URL for database or target server",
+ type=str),
+ Option("--car", type="choice", choices=["change-rid",
+ "change-pdc",
+ "change-infrastructure",
+ "change-schema",
+ "change-naming",
+ "allocate_rids",
+ "get-changes",
+ "get-changes-all",
+ "get-changes-filtered",
+ "topology-manage",
+ "topology-monitor",
+ "repl-sync",
+ "ro-repl-secret-sync"],
+ help=car_help),
+ Option("--action", type="choice", choices=["allow", "deny"],
+ help="""Deny or allow access"""),
+ Option("--objectdn", help="DN of the object whose SD to modify",
+ type="string"),
+ Option("--trusteedn", help="DN of the entity that gets access",
+ type="string"),
+ Option("--sddl", help="An ACE or group of ACEs to be added on the object",
+ type="string"),
+ ]
+
+ def find_trustee_sid(self, samdb, trusteedn):
+ res = samdb.search(base=trusteedn, expression="(objectClass=*)",
+ scope=SCOPE_BASE)
+ assert(len(res) == 1)
+ return ndr_unpack( security.dom_sid,res[0]["objectSid"][0])
+
+ def modify_descriptor(self, samdb, object_dn, desc, controls=None):
+ assert(isinstance(desc, security.descriptor))
+ m = ldb.Message()
+ m.dn = ldb.Dn(samdb, object_dn)
+ m["nTSecurityDescriptor"]= ldb.MessageElement(
+ (ndr_pack(desc)), ldb.FLAG_MOD_REPLACE,
+ "nTSecurityDescriptor")
+ samdb.modify(m)
+
+ def read_descriptor(self, samdb, object_dn):
+ res = samdb.search(base=object_dn, scope=SCOPE_BASE,
+ attrs=["nTSecurityDescriptor"])
+ # we should theoretically always have an SD
+ assert(len(res) == 1)
+ desc = res[0]["nTSecurityDescriptor"][0]
+ return ndr_unpack(security.descriptor, desc)
+
+ def get_domain_sid(self, samdb):
+ res = samdb.search(base=samdb.domain_dn(),
+ expression="(objectClass=*)", scope=SCOPE_BASE)
+ return ndr_unpack( security.dom_sid,res[0]["objectSid"][0])
+
+ def add_ace(self, samdb, object_dn, new_ace):
+ """Add new ace explicitly."""
+ desc = self.read_descriptor(samdb, object_dn)
+ desc_sddl = desc.as_sddl(self.get_domain_sid(samdb))
+ #TODO add bindings for descriptor manipulation and get rid of this
+ desc_aces = re.findall("\(.*?\)", desc_sddl)
+ for ace in desc_aces:
+ if ("ID" in ace):
+ desc_sddl = desc_sddl.replace(ace, "")
+ if new_ace in desc_sddl:
+ return
+ if desc_sddl.find("(") >= 0:
+ desc_sddl = desc_sddl[:desc_sddl.index("(")] + new_ace + desc_sddl[desc_sddl.index("("):]
+ else:
+ desc_sddl = desc_sddl + new_ace
+ desc = security.descriptor.from_sddl(desc_sddl, self.get_domain_sid(samdb))
+ self.modify_descriptor(samdb, object_dn, desc)
+
+ def print_new_acl(self, samdb, object_dn):
+ desc = self.read_descriptor(samdb, object_dn)
+ desc_sddl = desc.as_sddl(self.get_domain_sid(samdb))
+ print "new descriptor for %s:" % object_dn
+ print desc_sddl
+
+ def run(self, car, action, objectdn, trusteedn, sddl,
+ host=None, credopts=None, sambaopts=None, versionopts=None):
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+
+ if sddl is None and (car is None or action is None
+ or objectdn is None or trusteedn is None):
+ return self.usage()
+
+ samdb = SamDB(url=host, session_info=system_session(),
+ credentials=creds, lp=lp)
+ cars = {'change-rid' : GUID_DRS_CHANGE_RID_MASTER,
+ 'change-pdc' : GUID_DRS_CHANGE_PDC,
+ 'change-infrastructure' : GUID_DRS_CHANGE_INFR_MASTER,
+ 'change-schema' : GUID_DRS_CHANGE_SCHEMA_MASTER,
+ 'change-naming' : GUID_DRS_CHANGE_DOMAIN_MASTER,
+ 'allocate_rids' : GUID_DRS_ALLOCATE_RIDS,
+ 'get-changes' : GUID_DRS_GET_CHANGES,
+ 'get-changes-all' : GUID_DRS_GET_ALL_CHANGES,
+ 'get-changes-filtered' : GUID_DRS_GET_FILTERED_ATTRIBUTES,
+ 'topology-manage' : GUID_DRS_MANAGE_TOPOLOGY,
+ 'topology-monitor' : GUID_DRS_MONITOR_TOPOLOGY,
+ 'repl-sync' : GUID_DRS_REPL_SYNCRONIZE,
+ 'ro-repl-secret-sync' : GUID_DRS_RO_REPL_SECRET_SYNC,
+ }
+ sid = self.find_trustee_sid(samdb, trusteedn)
+ if sddl:
+ new_ace = sddl
+ elif action == "allow":
+ new_ace = "(OA;;CR;%s;;%s)" % (cars[car], str(sid))
+ elif action == "deny":
+ new_ace = "(OD;;CR;%s;;%s)" % (cars[car], str(sid))
+ else:
+ raise CommandError("Wrong argument '%s'!" % action)
+
+ self.print_new_acl(samdb, objectdn)
+ self.add_ace(samdb, objectdn, new_ace)
+ self.print_new_acl(samdb, objectdn)
+
+
+class cmd_ds_acl(SuperCommand):
+ """DS ACLs manipulation"""
+
+ subcommands = {}
+ subcommands["set"] = cmd_ds_acl_set()
diff --git a/source4/scripting/python/samba/netcmd/enableaccount.py b/source4/scripting/python/samba/netcmd/enableaccount.py
new file mode 100644
index 0000000000..3ceddb3fd9
--- /dev/null
+++ b/source4/scripting/python/samba/netcmd/enableaccount.py
@@ -0,0 +1,60 @@
+#!/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
new file mode 100644
index 0000000000..649a2621b1
--- /dev/null
+++ b/source4/scripting/python/samba/netcmd/export.py
@@ -0,0 +1,56 @@
+#!/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
new file mode 100644
index 0000000000..0c8d17c74f
--- /dev/null
+++ b/source4/scripting/python/samba/netcmd/fsmo.py
@@ -0,0 +1,197 @@
+#!/usr/bin/env python
+#
+# Changes a FSMO role owner
+#
+# Copyright Nadezhda Ivanova 2009
+# Copyright Jelmer Vernooij 2009
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import samba.getopt as options
+import ldb
+from ldb import LdbError
+
+from samba.auth import system_session
+from samba.netcmd import (
+ Command,
+ CommandError,
+ Option,
+ )
+from samba.samdb import SamDB
+
+class cmd_fsmo(Command):
+ """Makes the targer DC transfer or seize a fsmo role [server connection needed]"""
+
+ synopsis = "(show | transfer <options> | seize <options>)"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_options = [
+ Option("--host", help="LDB URL for database or target server", type=str),
+ Option("--force", help="Force seizing of the role without attempting to transfer first.", action="store_true"),
+ Option("--role", type="choice", choices=["rid", "pdc", "infrastructure","schema","naming","all"],
+ help="""The FSMO role to seize or transfer.\n
+rid=RidAllocationMasterRole\n
+schema=SchemaMasterRole\n
+pdc=PdcEmulationMasterRole\n
+naming=DomainNamingMasterRole\n
+infrastructure=InfrastructureMasterRole\n
+all=all of the above"""),
+ ]
+
+ takes_args = ["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)
+
+ def seize_role(self, role, samdb, force):
+ res = samdb.search("",
+ scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
+ assert len(res) == 1
+ serviceName = res[0]["dsServiceName"][0]
+ domain_dn = samdb.domain_dn()
+ m = ldb.Message()
+ if role == "rid":
+ m.dn = ldb.Dn(samdb, self.rid_dn)
+ elif role == "pdc":
+ m.dn = ldb.Dn(samdb, domain_dn)
+ elif role == "naming":
+ m.dn = ldb.Dn(samdb, self.naming_dn)
+ elif role == "infrastructure":
+ m.dn = ldb.Dn(samdb, self.infrastructure_dn)
+ elif role == "schema":
+ m.dn = ldb.Dn(samdb, self.schema_dn)
+ else:
+ raise CommandError("Invalid FSMO role.")
+ #first try to transfer to avoid problem if the owner is still active
+ if force is None:
+ self.message("Attempting transfer...")
+ try:
+ self.transfer_role(role, samdb)
+ except LdbError, (num, _):
+ #transfer failed, use the big axe...
+ self.message("Transfer unsuccessfull, 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")
+ samdb.modify(m)
+
+ def run(self, subcommand, force=None, host=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(),
+ 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.rid_dn = "CN=RID Manager$,CN=System," + domain_dn
+
+ res = samdb.search(self.infrastructure_dn,
+ scope=ldb.SCOPE_BASE, attrs=["fSMORoleOwner"])
+ assert len(res) == 1
+ self.infrastructureMaster = res[0]["fSMORoleOwner"][0]
+
+ res = samdb.search(domain_dn,
+ scope=ldb.SCOPE_BASE, attrs=["fSMORoleOwner"])
+ assert len(res) == 1
+ self.pdcEmulator = res[0]["fSMORoleOwner"][0]
+
+ res = samdb.search(self.naming_dn,
+ scope=ldb.SCOPE_BASE, attrs=["fSMORoleOwner"])
+ assert len(res) == 1
+ self.namingMaster = res[0]["fSMORoleOwner"][0]
+
+ res = samdb.search(self.schema_dn,
+ scope=ldb.SCOPE_BASE, attrs=["fSMORoleOwner"])
+ assert len(res) == 1
+ self.schemaMaster = res[0]["fSMORoleOwner"][0]
+
+ res = samdb.search(self.rid_dn,
+ scope=ldb.SCOPE_BASE, attrs=["fSMORoleOwner"])
+ assert len(res) == 1
+ self.ridMaster = res[0]["fSMORoleOwner"][0]
+
+ 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)
+ else:
+ raise CommandError("Wrong argument '%s'!" % subcommand)
diff --git a/source4/scripting/python/samba/netcmd/gpo.py b/source4/scripting/python/samba/netcmd/gpo.py
new file mode 100644
index 0000000000..19007b361c
--- /dev/null
+++ b/source4/scripting/python/samba/netcmd/gpo.py
@@ -0,0 +1,245 @@
+#!/usr/bin/env python
+#
+# implement samba_tool gpo commands
+#
+# Copyright Andrew Tridgell 2010
+#
+# based on C implementation by Guenther Deschner and Wilco Baan Hofman
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import samba.getopt as options
+import ldb
+
+from samba.auth import system_session
+from samba.netcmd import (
+ Command,
+ CommandError,
+ Option,
+ SuperCommand,
+ )
+from samba.samdb import SamDB
+from samba import drs_utils, nttime2string, dsdb, dcerpc
+from samba.dcerpc import misc
+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
+
+def samdb_connect(ctx):
+ '''make a ldap connection to the server'''
+ try:
+ ctx.samdb = SamDB(url=ctx.url,
+ session_info=system_session(),
+ credentials=ctx.creds, lp=ctx.lp)
+ except Exception, e:
+ raise CommandError("LDAP connection to %s failed " % ctx.url, e)
+
+
+def attr_default(msg, attrname, default):
+ '''get an attribute from a ldap msg with a default'''
+ if attrname in msg:
+ return msg[attrname][0]
+ return default
+
+
+def 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 parse_gplink(gplink):
+ '''parse a gPLink into an array of dn and options'''
+ ret = []
+ a = gplink.split(']')
+ for g in a:
+ if not g:
+ continue
+ d = g.split(';')
+ if len(d) != 2 or not d[0].startswith("[LDAP://"):
+ raise RuntimeError("Badly formed gPLink '%s'" % g)
+ ret.append({ 'dn' : d[0][8:], 'options' : int(d[1])})
+ return ret
+
+
+class cmd_listall(Command):
+ """list all GPOs"""
+
+ synopsis = "%prog gpo listall"
+
+ 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)
+ ]
+
+ def run(self, H=None, sambaopts=None,
+ credopts=None, versionopts=None, server=None):
+
+ self.url = H
+ self.lp = sambaopts.get_loadparm()
+
+ self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
+
+ samdb_connect(self)
+
+ policies_dn = self.samdb.get_default_basedn()
+ policies_dn.add_child(ldb.Dn(self.samdb, "CN=Policies,CN=System"))
+
+ gpo_flags = [
+ ("GPO_FLAG_USER_DISABLE", dsdb.GPO_FLAG_USER_DISABLE ),
+ ( "GPO_FLAG_MACHINE_DISABLE", dsdb.GPO_FLAG_MACHINE_DISABLE ) ]
+
+ 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("")
+
+
+class cmd_list(Command):
+ """list GPOs for a user"""
+
+ synopsis = "%prog gpo list <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)
+ ]
+
+ def run(self, username, H=None, sambaopts=None,
+ credopts=None, versionopts=None, server=None):
+
+ self.url = H
+ self.lp = sambaopts.get_loadparm()
+
+ self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
+
+ 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)
+
+ # 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)
+
+ session_info_flags = ( AUTH_SESSION_INFO_DEFAULT_GROUPS |
+ AUTH_SESSION_INFO_AUTHENTICATED )
+
+ # When connecting to a remote server, don't look up the local privilege DB
+ if self.url is not None and self.url.startswith('ldap'):
+ session_info_flags |= AUTH_SESSION_INFO_SIMPLE_PRIVILEGES
+
+ session = samba.auth.user_session(self.samdb, lp_ctx=self.lp, dn=user_dn,
+ session_info_flags=session_info_flags)
+
+ token = session.security_token
+
+ gpos = []
+
+ inherit = True
+ dn = ldb.Dn(self.samdb, str(user_dn)).parent()
+ while True:
+ msg = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=['gPLink', 'gPOptions'])[0]
+ if 'gPLink' in msg:
+ glist = parse_gplink(msg['gPLink'][0])
+ for g in glist:
+ if not inherit and not (g['options'] & dsdb.GPLINK_OPT_ENFORCE):
+ continue
+ if g['options'] & dsdb.GPLINK_OPT_DISABLE:
+ continue
+
+ try:
+ gmsg = self.samdb.search(base=g['dn'], scope=ldb.SCOPE_BASE,
+ attrs=['flags', 'ntSecurityDescriptor'])
+ except Exception:
+ print "Failed to fetch gpo object %s" % 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)
+ except RuntimeError:
+ print "Failed access check on %s" % msg.dn
+ continue
+
+ # check the flags on the GPO
+ flags = int(attr_default(gmsg[0], 'flags', 0))
+ if is_computer and (flags & dsdb.GPO_FLAG_MACHINE_DISABLE):
+ continue
+ if not is_computer and (flags & dsdb.GPO_FLAG_USER_DISABLE):
+ continue
+ gpos.append(g)
+
+ # check if this blocks inheritance
+ gpoptions = int(attr_default(msg, 'gPOptions', 0))
+ if gpoptions & dsdb.GPO_BLOCK_INHERITANCE:
+ inherit = False
+
+ if dn == self.samdb.get_default_basedn():
+ break
+ dn = dn.parent()
+
+ print("GPO's for user %s" % username)
+ for g in gpos:
+ print("\t%s" % g['dn'])
+
+
+class cmd_gpo(SuperCommand):
+ """GPO commands"""
+
+ subcommands = {}
+ subcommands["listall"] = cmd_listall()
+ subcommands["list"] = cmd_list()
diff --git a/source4/scripting/python/samba/netcmd/group.py b/source4/scripting/python/samba/netcmd/group.py
new file mode 100644
index 0000000000..620a7be866
--- /dev/null
+++ b/source4/scripting/python/samba/netcmd/group.py
@@ -0,0 +1,189 @@
+#!/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, SuperCommand, CommandError, Option
+import ldb
+
+from getpass import getpass
+from samba.auth import system_session
+from samba.samdb import SamDB
+from samba.dsdb import (
+ GTYPE_SECURITY_DOMAIN_LOCAL_GROUP,
+ GTYPE_SECURITY_GLOBAL_GROUP,
+ GTYPE_SECURITY_UNIVERSAL_GROUP,
+ GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP,
+ GTYPE_DISTRIBUTION_GLOBAL_GROUP,
+ GTYPE_DISTRIBUTION_UNIVERSAL_GROUP,
+)
+
+security_group = dict({"Domain": GTYPE_SECURITY_DOMAIN_LOCAL_GROUP, "Global": GTYPE_SECURITY_GLOBAL_GROUP, "Universal": GTYPE_SECURITY_UNIVERSAL_GROUP})
+distribution_group = dict({"Domain": GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP, "Global": GTYPE_DISTRIBUTION_GLOBAL_GROUP, "Universal": GTYPE_DISTRIBUTION_UNIVERSAL_GROUP})
+
+
+class cmd_group_add(Command):
+ """Creates a new group"""
+
+ synopsis = "%prog group add [options] <groupname>"
+
+ 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("--groupou",
+ help="Alternative location (without domainDN counterpart) to default CN=Users in which new user object will be created",
+ type=str),
+ Option("--group-scope", type="choice", choices=["Domain", "Global", "Universal"],
+ help="Group scope (Domain | Global | Universal)"),
+ Option("--group-type", type="choice", choices=["Security", "Distribution"],
+ help="Group type (Security | Distribution)"),
+ Option("--description", help="Group's description", type=str),
+ Option("--mail-address", help="Group's email address", type=str),
+ Option("--notes", help="Groups's notes", type=str),
+ ]
+
+ takes_args = ["groupname"]
+
+ def run(self, groupname, credopts=None, sambaopts=None,
+ versionopts=None, H=None, groupou=None, group_scope=None,
+ group_type=None, description=None, mail_address=None, notes=None):
+
+ if (group_type or "Security") == "Security":
+ gtype = security_group.get(group_scope, GTYPE_SECURITY_GLOBAL_GROUP)
+ else:
+ gtype = distribution_group.get(group_scope, GTYPE_DISTRIBUTION_GLOBAL_GROUP)
+
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp, fallback_machine=True)
+
+ try:
+ samdb = SamDB(url=H, session_info=system_session(),
+ credentials=creds, lp=lp)
+ samdb.newgroup(groupname, groupou=groupou, grouptype = gtype,
+ description=description, mailaddress=mail_address, notes=notes)
+ except Exception, e:
+ raise CommandError('Failed to create group "%s"' % groupname, e)
+
+
+class cmd_group_delete(Command):
+ """Delete a group"""
+
+ synopsis = "%prog group delete <groupname>"
+
+ 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),
+ ]
+
+ takes_args = ["groupname"]
+
+ def run(self, groupname, credopts=None, sambaopts=None, versionopts=None, H=None):
+
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp, fallback_machine=True)
+
+ try:
+ samdb = SamDB(url=H, session_info=system_session(),
+ credentials=creds, lp=lp)
+ samdb.deletegroup(groupname)
+ except Exception, e:
+ raise CommandError('Failed to remove group "%s"' % groupname, e)
+
+
+class cmd_group_add_members(Command):
+ """Add (comma-separated list of) group members"""
+
+ synopsis = "%prog group addmembers <groupname> <listofmembers>"
+
+ 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),
+ ]
+
+ takes_args = ["groupname", "listofmembers"]
+
+ def run(self, groupname, listofmembers, credopts=None, sambaopts=None,
+ versionopts=None, H=None):
+
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp, fallback_machine=True)
+
+ try:
+ samdb = SamDB(url=H, session_info=system_session(),
+ credentials=creds, lp=lp)
+ samdb.add_remove_group_members(groupname, listofmembers, add_members_operation=True)
+ except Exception, e:
+ raise CommandError('Failed to add members "%s" to group "%s"' % (listofmembers, groupname), e)
+
+
+class cmd_group_remove_members(Command):
+ """Remove (comma-separated list of) group members"""
+
+ synopsis = "%prog group removemembers <groupname> <listofmembers>"
+
+ 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),
+ ]
+
+ takes_args = ["groupname", "listofmembers"]
+
+ def run(self, groupname, listofmembers, credopts=None, sambaopts=None,
+ versionopts=None, H=None):
+
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp, fallback_machine=True)
+
+ try:
+ samdb = SamDB(url=H, session_info=system_session(),
+ credentials=creds, lp=lp)
+ samdb.add_remove_group_members(groupname, listofmembers, add_members_operation=False)
+ except Exception, e:
+ raise CommandError('Failed to remove members "%s" from group "%s"' % (listofmembers, groupname), e)
+
+
+class cmd_group(SuperCommand):
+ """Group management"""
+
+ subcommands = {}
+ subcommands["add"] = cmd_group_add()
+ subcommands["delete"] = cmd_group_delete()
+ subcommands["addmembers"] = cmd_group_add_members()
+ subcommands["removemembers"] = cmd_group_remove_members()
diff --git a/source4/scripting/python/samba/netcmd/join.py b/source4/scripting/python/samba/netcmd/join.py
new file mode 100644
index 0000000000..507253ab81
--- /dev/null
+++ b/source4/scripting/python/samba/netcmd/join.py
@@ -0,0 +1,78 @@
+#!/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
new file mode 100755
index 0000000000..160aa31258
--- /dev/null
+++ b/source4/scripting/python/samba/netcmd/ldapcmp.py
@@ -0,0 +1,949 @@
+#!/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
+# one of the three pratitions DOMAIN, CONFIGURATION or SCHEMA. Users
+# that have to be provided sheould be able to read objects in any of the
+# above partitions.
+
+# Copyright (C) Zahari Zahariev <zahari.zahariev@postpath.com> 2009, 2010
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import os
+import re
+import sys
+
+import samba
+import samba.getopt as options
+from samba import Ldb
+from samba.ndr import ndr_pack, 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
+summary = {}
+
+class LDAPBase(object):
+
+ def __init__(self, host, creds, lp,
+ two=False, quiet=False, descriptor=False, sort_aces=False, verbose=False,
+ view="section", base="", scope="SUB"):
+ ldb_options = []
+ samdb_url = host
+ if not "://" in host:
+ if os.path.isfile(host):
+ samdb_url = "tdb://%s" % host
+ else:
+ samdb_url = "ldap://%s" % host
+ # use 'paged_search' module when connecting remotely
+ if samdb_url.lower().startswith("ldap://"):
+ ldb_options = ["modules:paged_searches"]
+ self.ldb = Ldb(url=samdb_url,
+ credentials=creds,
+ lp=lp,
+ options=ldb_options)
+ self.search_base = base
+ self.search_scope = scope
+ self.two_domains = two
+ self.quiet = quiet
+ self.descriptor = descriptor
+ self.sort_aces = sort_aces
+ self.view = view
+ self.verbose = verbose
+ self.host = host
+ self.base_dn = self.find_basedn()
+ self.domain_netbios = self.find_netbios()
+ self.server_names = self.find_servers()
+ self.domain_name = re.sub("[Dd][Cc]=", "", self.base_dn).replace(",", ".")
+ self.domain_sid = self.find_domain_sid()
+ self.get_guid_map()
+ self.get_sid_map()
+ #
+ # Log some domain controller specific place-holers that are being used
+ # when compare content of two DCs. Uncomment for DEBUG purposes.
+ if self.two_domains and not self.quiet:
+ 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
+
+ def find_domain_sid(self):
+ res = self.ldb.search(base=self.base_dn, expression="(objectClass=*)", scope=SCOPE_BASE)
+ return ndr_unpack(security.dom_sid,res[0]["objectSid"][0])
+
+ def find_servers(self):
+ """
+ """
+ res = self.ldb.search(base="OU=Domain Controllers,%s" % self.base_dn, \
+ scope=SCOPE_SUBTREE, expression="(objectClass=computer)", attrs=["cn"])
+ assert len(res) > 0
+ srv = []
+ for x in res:
+ srv.append(x["cn"][0])
+ return srv
+
+ def find_netbios(self):
+ res = self.ldb.search(base="CN=Partitions,CN=Configuration,%s" % self.base_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:
+ res = self.ldb.search(base=object_dn, scope=SCOPE_BASE)
+ except LdbError, (enum, estr):
+ if enum == ERR_NO_SUCH_OBJECT:
+ return False
+ raise
+ return len(res) == 1
+
+ def delete_force(self, object_dn):
+ try:
+ self.ldb.delete(object_dn)
+ except Ldb.LdbError, e:
+ assert "No such object" in str(e)
+
+ def get_attribute_name(self, key):
+ """ Returns the real attribute name
+ It resolved ranged results e.g. member;range=0-1499
+ """
+
+ r = re.compile("^([^;]+);range=(\d+)-(\d+|\*)$")
+
+ m = r.match(key)
+ if m is None:
+ return key
+
+ return m.group(1)
+
+ def get_attribute_values(self, object_dn, key, vals):
+ """ Returns list with all attribute values
+ It resolved ranged results e.g. member;range=0-1499
+ """
+
+ r = re.compile("^([^;]+);range=(\d+)-(\d+|\*)$")
+
+ m = r.match(key)
+ if m is None:
+ # no range, just return the values
+ return vals
+
+ attr = m.group(1)
+ hi = int(m.group(3))
+
+ # get additional values in a loop
+ # until we get a response with '*' at the end
+ while True:
+
+ n = "%s;range=%d-*" % (attr, hi + 1)
+ res = self.ldb.search(base=object_dn, scope=SCOPE_BASE, attrs=[n])
+ assert len(res) == 1
+ res = dict(res[0])
+ del res["dn"]
+
+ fm = None
+ fvals = None
+
+ for key in res.keys():
+ m = r.match(key)
+
+ if m is None:
+ continue
+
+ if m.group(1) != attr:
+ continue
+
+ fm = m
+ fvals = list(res[key])
+ break
+
+ if fm is None:
+ break
+
+ vals.extend(fvals)
+ if fm.group(3) == "*":
+ # if we got "*" we're done
+ break
+
+ assert int(fm.group(2)) == hi + 1
+ hi = int(fm.group(3))
+
+ return vals
+
+ def get_attributes(self, object_dn):
+ """ Returns dict with all default visible attributes
+ """
+ res = self.ldb.search(base=object_dn, scope=SCOPE_BASE, attrs=["*"])
+ assert len(res) == 1
+ res = dict(res[0])
+ # 'Dn' element is not iterable and we have it as 'distinguishedName'
+ del res["dn"]
+ for key in res.keys():
+ vals = list(res[key])
+ del res[key]
+ name = self.get_attribute_name(key)
+ res[name] = self.get_attribute_values(object_dn, key, vals)
+
+ return res
+
+ def get_descriptor_sddl(self, object_dn):
+ res = self.ldb.search(base=object_dn, scope=SCOPE_BASE, attrs=["nTSecurityDescriptor"])
+ desc = res[0]["nTSecurityDescriptor"][0]
+ desc = ndr_unpack(security.descriptor, desc)
+ return desc.as_sddl(self.domain_sid)
+
+ def guid_as_string(self, guid_blob):
+ """ Translate binary representation of schemaIDGUID to standard string representation.
+ @gid_blob: binary schemaIDGUID
+ """
+ blob = "%s" % guid_blob
+ stops = [4, 2, 2, 2, 6]
+ index = 0
+ res = ""
+ x = 0
+ while x < len(stops):
+ tmp = ""
+ y = 0
+ while y < stops[x]:
+ c = hex(ord(blob[index])).replace("0x", "")
+ c = [None, "0" + c, c][len(c)]
+ if 2 * index < len(blob):
+ tmp = c + tmp
+ else:
+ tmp += c
+ index += 1
+ y += 1
+ res += tmp + " "
+ x += 1
+ assert index == len(blob)
+ return res.strip().replace(" ", "-")
+
+ def get_guid_map(self):
+ """ Build dictionary that maps GUID to 'name' attribute found in Schema or Extended-Rights.
+ """
+ self.guid_map = {}
+ res = self.ldb.search(base="cn=schema,cn=configuration,%s" % self.base_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"])
+ for item in res:
+ self.guid_map[str(item["rightsGuid"]).lower()] = item["name"][0]
+
+ def get_sid_map(self):
+ """ Build dictionary that maps GUID to 'name' attribute found in Schema or Extended-Rights.
+ """
+ self.sid_map = {}
+ res = self.ldb.search(base="%s" % self.base_dn, \
+ expression="(objectSid=*)", scope=SCOPE_SUBTREE, attrs=["objectSid", "sAMAccountName"])
+ for item in res:
+ try:
+ self.sid_map["%s" % ndr_unpack(security.dom_sid, item["objectSid"][0])] = item["sAMAccountName"][0]
+ except KeyError:
+ pass
+
+class Descriptor(object):
+ def __init__(self, connection, dn):
+ self.con = connection
+ self.dn = dn
+ self.sddl = self.con.get_descriptor_sddl(self.dn)
+ self.dacl_list = self.extract_dacl()
+ if self.con.sort_aces:
+ self.dacl_list.sort()
+
+ def extract_dacl(self):
+ """ Extracts the DACL as a list of ACE string (with the brakets).
+ """
+ try:
+ if "S:" in self.sddl:
+ res = re.search("D:(.*?)(\(.*?\))S:", self.sddl).group(2)
+ else:
+ res = re.search("D:(.*?)(\(.*\))", self.sddl).group(2)
+ except AttributeError:
+ return []
+ return re.findall("(\(.*?\))", res)
+
+ def fix_guid(self, ace):
+ res = "%s" % ace
+ guids = re.findall("[a-z0-9]+?-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+", res)
+ # If there are not GUIDs to replace return the same ACE
+ if len(guids) == 0:
+ return res
+ for guid in guids:
+ try:
+ name = self.con.guid_map[guid.lower()]
+ res = res.replace(guid, name)
+ except KeyError:
+ # Do not bother if the GUID is not found in
+ # cn=Schema or cn=Extended-Rights
+ pass
+ return res
+
+ def fix_sid(self, ace):
+ res = "%s" % ace
+ sids = re.findall("S-[-0-9]+", res)
+ # If there are not SIDs to replace return the same ACE
+ if len(sids) == 0:
+ return res
+ for sid in sids:
+ try:
+ name = self.con.sid_map[sid]
+ res = res.replace(sid, name)
+ except KeyError:
+ # Do not bother if the SID is not found in baseDN
+ pass
+ return res
+
+ def fixit(self, ace):
+ """ Combine all replacement methods in one
+ """
+ res = "%s" % ace
+ res = self.fix_guid(res)
+ res = self.fix_sid(res)
+ return res
+
+ def diff_1(self, other):
+ res = ""
+ if len(self.dacl_list) != len(other.dacl_list):
+ res += 4*" " + "Difference in ACE count:\n"
+ res += 8*" " + "=> %s\n" % len(self.dacl_list)
+ res += 8*" " + "=> %s\n" % len(other.dacl_list)
+ #
+ i = 0
+ flag = True
+ while True:
+ self_ace = None
+ other_ace = None
+ try:
+ self_ace = "%s" % self.dacl_list[i]
+ except IndexError:
+ self_ace = ""
+ #
+ try:
+ other_ace = "%s" % other.dacl_list[i]
+ except IndexError:
+ other_ace = ""
+ if len(self_ace) + len(other_ace) == 0:
+ break
+ self_ace_fixed = "%s" % self.fixit(self_ace)
+ other_ace_fixed = "%s" % other.fixit(other_ace)
+ if self_ace_fixed != other_ace_fixed:
+ res += "%60s * %s\n" % ( self_ace_fixed, other_ace_fixed )
+ flag = False
+ else:
+ res += "%60s | %s\n" % ( self_ace_fixed, other_ace_fixed )
+ i += 1
+ return (flag, res)
+
+ def diff_2(self, other):
+ res = ""
+ if len(self.dacl_list) != len(other.dacl_list):
+ res += 4*" " + "Difference in ACE count:\n"
+ res += 8*" " + "=> %s\n" % len(self.dacl_list)
+ res += 8*" " + "=> %s\n" % len(other.dacl_list)
+ #
+ common_aces = []
+ self_aces = []
+ other_aces = []
+ self_dacl_list_fixed = []
+ other_dacl_list_fixed = []
+ [self_dacl_list_fixed.append( self.fixit(ace) ) for ace in self.dacl_list]
+ [other_dacl_list_fixed.append( other.fixit(ace) ) for ace in other.dacl_list]
+ for ace in self_dacl_list_fixed:
+ try:
+ other_dacl_list_fixed.index(ace)
+ except ValueError:
+ self_aces.append(ace)
+ else:
+ common_aces.append(ace)
+ self_aces = sorted(self_aces)
+ if len(self_aces) > 0:
+ res += 4*" " + "ACEs found only in %s:\n" % self.con.host
+ for ace in self_aces:
+ res += 8*" " + ace + "\n"
+ #
+ for ace in other_dacl_list_fixed:
+ try:
+ self_dacl_list_fixed.index(ace)
+ except ValueError:
+ other_aces.append(ace)
+ else:
+ common_aces.append(ace)
+ other_aces = sorted(other_aces)
+ if len(other_aces) > 0:
+ res += 4*" " + "ACEs found only in %s:\n" % other.con.host
+ for ace in other_aces:
+ res += 8*" " + ace + "\n"
+ #
+ common_aces = sorted(list(set(common_aces)))
+ if self.con.verbose:
+ res += 4*" " + "ACEs found in both:\n"
+ for ace in common_aces:
+ res += 8*" " + ace + "\n"
+ return (self_aces == [] and other_aces == [], res)
+
+class LDAPObject(object):
+ def __init__(self, connection, dn, summary):
+ self.con = connection
+ self.two_domains = self.con.two_domains
+ self.quiet = self.con.quiet
+ self.verbose = self.con.verbose
+ self.summary = summary
+ self.dn = dn.replace("${DOMAIN_DN}", self.con.base_dn)
+ self.dn = self.dn.replace("CN=${DOMAIN_NETBIOS}", "CN=%s" % self.con.domain_netbios)
+ for x in self.con.server_names:
+ self.dn = self.dn.replace("CN=${SERVER_NAME}", "CN=%s" % x)
+ self.attributes = self.con.get_attributes(self.dn)
+ # Attributes that are considered always to be different e.g based on timestamp etc.
+ #
+ # One domain - two domain controllers
+ self.ignore_attributes = [
+ # Default Naming Context
+ "lastLogon", "lastLogoff", "badPwdCount", "logonCount", "badPasswordTime", "modifiedCount",
+ "operatingSystemVersion","oEMInformation",
+ # Configuration Naming Context
+ "repsFrom", "dSCorePropagationData", "msExchServer1HighestUSN",
+ "replUpToDateVector", "repsTo", "whenChanged", "uSNChanged", "uSNCreated",
+ # Schema Naming Context
+ "prefixMap",]
+ self.dn_attributes = []
+ self.domain_attributes = []
+ self.servername_attributes = []
+ self.netbios_attributes = []
+ self.other_attributes = []
+ # Two domains - two domain controllers
+
+ if self.two_domains:
+ self.ignore_attributes += [
+ "objectCategory", "objectGUID", "objectSid", "whenCreated", "pwdLastSet", "uSNCreated", "creationTime",
+ "modifiedCount", "priorSetTime", "rIDManagerReference", "gPLink", "ipsecNFAReference",
+ "fRSPrimaryMember", "fSMORoleOwner", "masteredBy", "ipsecOwnersReference", "wellKnownObjects",
+ "badPwdCount", "ipsecISAKMPReference", "ipsecFilterReference", "msDs-masteredBy", "lastSetTime",
+ "ipsecNegotiationPolicyReference", "subRefs", "gPCFileSysPath", "accountExpires", "invocationId",
+ # After Exchange preps
+ "targetAddress", "msExchMailboxGuid", "siteFolderGUID"]
+ #
+ # Attributes that contain the unique DN tail part e.g. 'DC=samba,DC=org'
+ self.dn_attributes = [
+ "distinguishedName", "defaultObjectCategory", "member", "memberOf", "siteList", "nCName",
+ "homeMDB", "homeMTA", "interSiteTopologyGenerator", "serverReference",
+ "msDS-HasInstantiatedNCs", "hasMasterNCs", "msDS-hasMasterNCs", "msDS-HasDomainNCs", "dMDLocation",
+ "msDS-IsDomainFor", "rIDSetReferences", "serverReferenceBL",
+ # After Exchange preps
+ "msExchHomeRoutingGroup", "msExchResponsibleMTAServer", "siteFolderServer", "msExchRoutingMasterDN",
+ "msExchRoutingGroupMembersBL", "homeMDBBL", "msExchHomePublicMDB", "msExchOwningServer", "templateRoots",
+ "addressBookRoots", "msExchPolicyRoots", "globalAddressList", "msExchOwningPFTree",
+ "msExchResponsibleMTAServerBL", "msExchOwningPFTreeBL",]
+ self.dn_attributes = [x.upper() for x in self.dn_attributes]
+ #
+ # Attributes that contain the Domain name e.g. 'samba.org'
+ self.domain_attributes = [
+ "proxyAddresses", "mail", "userPrincipalName", "msExchSmtpFullyQualifiedDomainName",
+ "dnsHostName", "networkAddress", "dnsRoot", "servicePrincipalName",]
+ self.domain_attributes = [x.upper() for x in self.domain_attributes]
+ #
+ # May contain DOMAIN_NETBIOS and SERVER_NAME
+ self.servername_attributes = [ "distinguishedName", "name", "CN", "sAMAccountName", "dNSHostName",
+ "servicePrincipalName", "rIDSetReferences", "serverReference", "serverReferenceBL",
+ "msDS-IsDomainFor", "interSiteTopologyGenerator",]
+ self.servername_attributes = [x.upper() for x in self.servername_attributes]
+ #
+ self.netbios_attributes = [ "servicePrincipalName", "CN", "distinguishedName", "nETBIOSName", "name",]
+ self.netbios_attributes = [x.upper() for x in self.netbios_attributes]
+ #
+ self.other_attributes = [ "name", "DC",]
+ self.other_attributes = [x.upper() for x in self.other_attributes]
+ #
+ self.ignore_attributes = [x.upper() for x in self.ignore_attributes]
+
+ def log(self, msg):
+ """
+ Log on the screen if there is no --quiet oprion set
+ """
+ if not self.quiet:
+ print msg
+
+ def fix_dn(self, s):
+ res = "%s" % s
+ if not self.two_domains:
+ return res
+ if res.upper().endswith(self.con.base_dn.upper()):
+ res = res[:len(res)-len(self.con.base_dn)] + "${DOMAIN_DN}"
+ return res
+
+ def fix_domain_name(self, s):
+ res = "%s" % s
+ if not self.two_domains:
+ return res
+ res = res.replace(self.con.domain_name.lower(), self.con.domain_name.upper())
+ res = res.replace(self.con.domain_name.upper(), "${DOMAIN_NAME}")
+ return res
+
+ def fix_domain_netbios(self, s):
+ res = "%s" % s
+ if not self.two_domains:
+ return res
+ res = res.replace(self.con.domain_netbios.lower(), self.con.domain_netbios.upper())
+ res = res.replace(self.con.domain_netbios.upper(), "${DOMAIN_NETBIOS}")
+ return res
+
+ def fix_server_name(self, s):
+ res = "%s" % s
+ if not self.two_domains or len(self.con.server_names) > 1:
+ return res
+ for x in self.con.server_names:
+ res = res.upper().replace(x, "${SERVER_NAME}")
+ return res
+
+ def __eq__(self, other):
+ if self.con.descriptor:
+ return self.cmp_desc(other)
+ return self.cmp_attrs(other)
+
+ def cmp_desc(self, other):
+ d1 = Descriptor(self.con, self.dn)
+ d2 = Descriptor(other.con, other.dn)
+ if self.con.view == "section":
+ res = d1.diff_2(d2)
+ elif self.con.view == "collision":
+ res = d1.diff_1(d2)
+ else:
+ raise Exception("Unknown --view option value.")
+ #
+ self.screen_output = res[1][:-1]
+ other.screen_output = res[1][:-1]
+ #
+ return res[0]
+
+ def cmp_attrs(self, other):
+ res = ""
+ self.unique_attrs = []
+ self.df_value_attrs = []
+ other.unique_attrs = []
+ if self.attributes.keys() != other.attributes.keys():
+ #
+ title = 4*" " + "Attributes found only in %s:" % self.con.host
+ for x in self.attributes.keys():
+ if not x in other.attributes.keys() and \
+ not x.upper() in [q.upper() for q in other.ignore_attributes]:
+ if title:
+ res += title + "\n"
+ title = None
+ res += 8*" " + x + "\n"
+ self.unique_attrs.append(x)
+ #
+ title = 4*" " + "Attributes found only in %s:" % other.con.host
+ for x in other.attributes.keys():
+ if not x in self.attributes.keys() and \
+ not x.upper() in [q.upper() for q in self.ignore_attributes]:
+ if title:
+ res += title + "\n"
+ title = None
+ res += 8*" " + x + "\n"
+ other.unique_attrs.append(x)
+ #
+ missing_attrs = [x.upper() for x in self.unique_attrs]
+ missing_attrs += [x.upper() for x in other.unique_attrs]
+ title = 4*" " + "Difference in attribute values:"
+ for x in self.attributes.keys():
+ if x.upper() in self.ignore_attributes or x.upper() in missing_attrs:
+ continue
+ if isinstance(self.attributes[x], list) and isinstance(other.attributes[x], list):
+ self.attributes[x] = sorted(self.attributes[x])
+ other.attributes[x] = sorted(other.attributes[x])
+ if self.attributes[x] != other.attributes[x]:
+ p = None
+ q = None
+ m = None
+ n = None
+ # First check if the difference can be fixed but shunting the first part
+ # of the DomainHostName e.g. 'mysamba4.test.local' => 'mysamba4'
+ if x.upper() in self.other_attributes:
+ p = [self.con.domain_name.split(".")[0] == j for j in self.attributes[x]]
+ q = [other.con.domain_name.split(".")[0] == j for j in other.attributes[x]]
+ if p == q:
+ continue
+ # Attribute values that are list that contain DN based values that may differ
+ elif x.upper() in self.dn_attributes:
+ m = p
+ n = q
+ if not p and not q:
+ m = self.attributes[x]
+ n = other.attributes[x]
+ p = [self.fix_dn(j) for j in m]
+ q = [other.fix_dn(j) for j in n]
+ if p == q:
+ continue
+ # Attributes that contain the Domain name in them
+ if x.upper() in self.domain_attributes:
+ m = p
+ n = q
+ if not p and not q:
+ m = self.attributes[x]
+ n = other.attributes[x]
+ p = [self.fix_domain_name(j) for j in m]
+ q = [other.fix_domain_name(j) for j in n]
+ if p == q:
+ continue
+ #
+ if x.upper() in self.servername_attributes:
+ # Attributes with SERVER_NAME
+ m = p
+ n = q
+ if not p and not q:
+ m = self.attributes[x]
+ n = other.attributes[x]
+ p = [self.fix_server_name(j) for j in m]
+ q = [other.fix_server_name(j) for j in n]
+ if p == q:
+ continue
+ #
+ if x.upper() in self.netbios_attributes:
+ # Attributes with NETBIOS Domain name
+ m = p
+ n = q
+ if not p and not q:
+ m = self.attributes[x]
+ n = other.attributes[x]
+ p = [self.fix_domain_netbios(j) for j in m]
+ q = [other.fix_domain_netbios(j) for j in n]
+ if p == q:
+ continue
+ #
+ if title:
+ res += title + "\n"
+ title = None
+ if p and q:
+ res += 8*" " + x + " => \n%s\n%s" % (p, q) + "\n"
+ else:
+ res += 8*" " + x + " => \n%s\n%s" % (self.attributes[x], other.attributes[x]) + "\n"
+ self.df_value_attrs.append(x)
+ #
+ if self.unique_attrs + other.unique_attrs != []:
+ assert self.unique_attrs != other.unique_attrs
+ self.summary["unique_attrs"] += self.unique_attrs
+ self.summary["df_value_attrs"] += self.df_value_attrs
+ other.summary["unique_attrs"] += other.unique_attrs
+ other.summary["df_value_attrs"] += self.df_value_attrs # they are the same
+ #
+ self.screen_output = res[:-1]
+ other.screen_output = res[:-1]
+ #
+ return res == ""
+
+
+class LDAPBundel(object):
+ def __init__(self, connection, context, dn_list=None):
+ self.con = connection
+ self.two_domains = self.con.two_domains
+ self.quiet = self.con.quiet
+ self.verbose = self.con.verbose
+ self.search_base = self.con.search_base
+ self.search_scope = self.con.search_scope
+ self.summary = {}
+ self.summary["unique_attrs"] = []
+ self.summary["df_value_attrs"] = []
+ self.summary["known_ignored_dn"] = []
+ self.summary["abnormal_ignored_dn"] = []
+ if dn_list:
+ self.dn_list = dn_list
+ elif context.upper() in ["DOMAIN", "CONFIGURATION", "SCHEMA"]:
+ self.context = context.upper()
+ self.dn_list = self.get_dn_list(context)
+ else:
+ raise Exception("Unknown initialization data for LDAPBundel().")
+ counter = 0
+ while counter < len(self.dn_list) and self.two_domains:
+ # Use alias reference
+ tmp = self.dn_list[counter]
+ tmp = tmp[:len(tmp)-len(self.con.base_dn)] + "${DOMAIN_DN}"
+ tmp = tmp.replace("CN=%s" % self.con.domain_netbios, "CN=${DOMAIN_NETBIOS}")
+ if len(self.con.server_names) == 1:
+ for x in self.con.server_names:
+ tmp = tmp.replace("CN=%s" % x, "CN=${SERVER_NAME}")
+ self.dn_list[counter] = tmp
+ counter += 1
+ self.dn_list = list(set(self.dn_list))
+ self.dn_list = sorted(self.dn_list)
+ self.size = len(self.dn_list)
+
+ def log(self, msg):
+ """
+ Log on the screen if there is no --quiet oprion set
+ """
+ if not self.quiet:
+ print msg
+
+ def update_size(self):
+ self.size = len(self.dn_list)
+ self.dn_list = sorted(self.dn_list)
+
+ def __eq__(self, other):
+ res = True
+ if self.size != other.size:
+ self.log( "\n* DN lists have different size: %s != %s" % (self.size, other.size) )
+ res = False
+ #
+ # This is the case where we want to explicitly compare two objects with different DNs.
+ # It does not matter if they are in the same DC, in two DC in one domain or in two
+ # different domains.
+ if self.search_scope != SCOPE_BASE:
+ title= "\n* DNs found only in %s:" % self.con.host
+ for x in self.dn_list:
+ if not x.upper() in [q.upper() for q in other.dn_list]:
+ if title:
+ self.log( title )
+ title = None
+ res = False
+ self.log( 4*" " + x )
+ self.dn_list[self.dn_list.index(x)] = ""
+ self.dn_list = [x for x in self.dn_list if x]
+ #
+ title= "\n* DNs found only in %s:" % other.con.host
+ for x in other.dn_list:
+ if not x.upper() in [q.upper() for q in self.dn_list]:
+ if title:
+ self.log( title )
+ title = None
+ res = False
+ self.log( 4*" " + x )
+ other.dn_list[other.dn_list.index(x)] = ""
+ other.dn_list = [x for x in other.dn_list if x]
+ #
+ self.update_size()
+ other.update_size()
+ assert self.size == other.size
+ assert sorted([x.upper() for x in self.dn_list]) == sorted([x.upper() for x in other.dn_list])
+ self.log( "\n* Objects to be compared: %s" % self.size )
+
+ index = 0
+ while index < self.size:
+ skip = False
+ try:
+ object1 = LDAPObject(connection=self.con,
+ dn=self.dn_list[index],
+ summary=self.summary)
+ except LdbError, (enum, estr):
+ if enum == ERR_NO_SUCH_OBJECT:
+ self.log( "\n!!! Object not found: %s" % self.dn_list[index] )
+ skip = True
+ raise
+ try:
+ object2 = LDAPObject(connection=other.con,
+ dn=other.dn_list[index],
+ summary=other.summary)
+ except LdbError, (enum, estr):
+ if enum == ERR_NO_SUCH_OBJECT:
+ self.log( "\n!!! Object not found: %s" % other.dn_list[index] )
+ skip = True
+ raise
+ if skip:
+ index += 1
+ continue
+ if object1 == object2:
+ if self.con.verbose:
+ self.log( "\nComparing:" )
+ self.log( "'%s' [%s]" % (object1.dn, object1.con.host) )
+ self.log( "'%s' [%s]" % (object2.dn, object2.con.host) )
+ self.log( 4*" " + "OK" )
+ else:
+ self.log( "\nComparing:" )
+ self.log( "'%s' [%s]" % (object1.dn, object1.con.host) )
+ self.log( "'%s' [%s]" % (object2.dn, object2.con.host) )
+ self.log( object1.screen_output )
+ self.log( 4*" " + "FAILED" )
+ res = False
+ self.summary = object1.summary
+ other.summary = object2.summary
+ index += 1
+ #
+ return res
+
+ def get_dn_list(self, context):
+ """ Query LDAP server about the DNs of certain naming self.con.ext Domain (or Default), Configuration, Schema.
+ Parse all DNs and filter those that are 'strange' or abnormal.
+ """
+ if context.upper() == "DOMAIN":
+ search_base = "%s" % self.con.base_dn
+ elif context.upper() == "CONFIGURATION":
+ search_base = "CN=Configuration,%s" % self.con.base_dn
+ elif context.upper() == "SCHEMA":
+ search_base = "CN=Schema,CN=Configuration,%s" % self.con.base_dn
+
+ dn_list = []
+ if not self.search_base:
+ self.search_base = search_base
+ self.search_scope = self.search_scope.upper()
+ if self.search_scope == "SUB":
+ self.search_scope = SCOPE_SUBTREE
+ elif self.search_scope == "BASE":
+ self.search_scope = SCOPE_BASE
+ elif self.search_scope == "ONE":
+ self.search_scope = SCOPE_ONELEVEL
+ else:
+ raise StandardError("Wrong 'scope' given. Choose from: SUB, ONE, BASE")
+ 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"])
+ for x in res:
+ dn_list.append(x["dn"].get_linearized())
+ #
+ global summary
+ #
+ return dn_list
+
+ def print_summary(self):
+ self.summary["unique_attrs"] = list(set(self.summary["unique_attrs"]))
+ self.summary["df_value_attrs"] = list(set(self.summary["df_value_attrs"]))
+ #
+ if self.summary["unique_attrs"]:
+ self.log( "\nAttributes found only in %s:" % self.con.host )
+ self.log( "".join([str("\n" + 4*" " + x) for x in self.summary["unique_attrs"]]) )
+ #
+ if self.summary["df_value_attrs"]:
+ self.log( "\nAttributes with different values:" )
+ self.log( "".join([str("\n" + 4*" " + x) for x in self.summary["df_value_attrs"]]) )
+ self.summary["df_value_attrs"] = []
+
+class cmd_ldapcmp(Command):
+ """compare two ldap databases"""
+ synopsis = "ldapcmp URL1 URL2 <domain|configuration|schema> [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptionsDouble,
+ }
+
+ takes_args = ["URL1", "URL2", "context1?", "context2?", "context3?"]
+
+ takes_options = [
+ Option("-w", "--two", dest="two", action="store_true", default=False,
+ help="Hosts are in two different domains"),
+ Option("-q", "--quiet", dest="quiet", action="store_true", default=False,
+ help="Do not print anything but relay on just exit code"),
+ Option("-v", "--verbose", dest="verbose", action="store_true", default=False,
+ help="Print all DN pairs that have been compared"),
+ Option("--sd", dest="descriptor", action="store_true", default=False,
+ help="Compare nTSecurityDescriptor attibutes only"),
+ Option("--sort-aces", dest="sort_aces", action="store_true", default=False,
+ help="Sort ACEs before comparison of nTSecurityDescriptor attribute"),
+ Option("--view", dest="view", default="section",
+ help="Display mode for nTSecurityDescriptor results. Possible values: section or collision."),
+ Option("--base", dest="base", default="",
+ help="Pass search base that will build DN list for the first DC."),
+ Option("--base2", dest="base2", default="",
+ help="Pass search base that will build DN list for the second DC. Used when --two or when compare two different DNs."),
+ Option("--scope", dest="scope", default="SUB",
+ help="Pass search scope that builds DN list. Options: SUB, ONE, BASE"),
+ ]
+
+ 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):
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp, fallback_machine=True)
+ 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():
+ raise CommandError("You must supply at least one username/password pair")
+
+ # make a list of contexts to compare in
+ contexts = []
+ if context1 is None:
+ if base and base2:
+ # If search bases are specified context is defaulted to
+ # DOMAIN so the given search bases can be verified.
+ contexts = ["DOMAIN"]
+ else:
+ # if no argument given, we compare all contexts
+ contexts = ["DOMAIN", "CONFIGURATION", "SCHEMA"]
+ else:
+ for c in [context1, context2, context3]:
+ if c is None:
+ continue
+ if not c.upper() in ["DOMAIN", "CONFIGURATION", "SCHEMA"]:
+ raise CommandError("Incorrect argument: %s" % c)
+ contexts.append(c.upper())
+
+ if verbose and quiet:
+ raise CommandError("You cannot set --verbose and --quiet together")
+ if (not base and base2) or (base and not base2):
+ raise CommandError("You need to specify both --base and --base2 at the same time")
+ if descriptor and view.upper() not in ["SECTION", "COLLISION"]:
+ raise CommandError("Invalid --view value. Choose from: section or collision")
+ if not scope.upper() in ["SUB", "ONE", "BASE"]:
+ raise CommandError("Invalid --scope value. Choose from: SUB, ONE, BASE")
+
+ con1 = LDAPBase(URL1, creds, lp,
+ two=two, quiet=quiet, descriptor=descriptor, sort_aces=sort_aces,
+ verbose=verbose,view=view, base=base, scope=scope)
+ 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)
+ assert len(con2.base_dn) > 0
+
+ status = 0
+ for context in contexts:
+ if not quiet:
+ print "\n* Comparing [%s] context..." % context
+
+ b1 = LDAPBundel(con1, context=context)
+ b2 = LDAPBundel(con2, context=context)
+
+ if b1 == b2:
+ if not quiet:
+ print "\n* Result for [%s]: SUCCESS" % context
+ else:
+ if not quiet:
+ print "\n* Result for [%s]: FAILURE" % 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 "---------"
+ b1.print_summary()
+ b2.print_summary()
+ # mark exit status as FAILURE if a least one comparison failed
+ status = -1
+ if status != 0:
+ raise CommandError("Compare failed: %d" % status)
diff --git a/source4/scripting/python/samba/netcmd/machinepw.py b/source4/scripting/python/samba/netcmd/machinepw.py
new file mode 100644
index 0000000000..d822b22794
--- /dev/null
+++ b/source4/scripting/python/samba/netcmd/machinepw.py
@@ -0,0 +1,53 @@
+#!/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/netacl.py b/source4/scripting/python/samba/netcmd/netacl.py
new file mode 100644
index 0000000000..3f68ee756c
--- /dev/null
+++ b/source4/scripting/python/samba/netcmd/netacl.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+#
+# Manipulate ACLs
+#
+# Copyright (C) Matthieu Patou <mat@matws.net> 2010
+# Copyright (C) Nadezhda Ivanova <nivanova@samba.org> 2010
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+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/netcmd/newuser.py b/source4/scripting/python/samba/netcmd/newuser.py
new file mode 100644
index 0000000000..3581340577
--- /dev/null
+++ b/source4/scripting/python/samba/netcmd/newuser.py
@@ -0,0 +1,97 @@
+#!/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
new file mode 100644
index 0000000000..49f8fbc77f
--- /dev/null
+++ b/source4/scripting/python/samba/netcmd/ntacl.py
@@ -0,0 +1,119 @@
+#!/usr/bin/env python
+#
+# Manipulate file NT ACLs
+#
+# Copyright Matthieu Patou 2010 <mat@matws.net>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+from samba.credentials import DONT_USE_KERBEROS
+import samba.getopt as options
+from samba.dcerpc import security
+from samba.ntacls import setntacl, getntacl
+from samba import Ldb
+from samba.ndr import ndr_unpack
+
+from ldb import SCOPE_BASE
+import os
+
+from samba.auth import system_session
+from samba.netcmd import (
+ Command,
+ CommandError,
+ SuperCommand,
+ Option,
+ )
+
+class cmd_acl_set(Command):
+ """Set ACLs on a file"""
+ synopsis = "%prog set <acl> <file> [--xattr-backend=native|tdb] [--eadb-file=file] [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_options = [
+ Option("--quiet", help="Be quiet", action="store_true"),
+ Option("--xattr-backend", type="choice", help="xattr backend type (native fs or tdb)",
+ choices=["native","tdb"]),
+ Option("--eadb-file", help="Name of the tdb file where attributes are stored", type="string"),
+ ]
+
+ takes_args = ["acl","file"]
+
+ def run(self, acl, file, quiet=False,xattr_backend=None,eadb_file=None,
+ credopts=None, sambaopts=None, versionopts=None):
+ 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)
+ 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 read domain SID from configuration files")
+
+
+class cmd_acl_get(Command):
+ """Set ACLs on a file"""
+ synopsis = "%prog get <file> [--as-sddl] [--xattr-backend=native|tdb] [--eadb-file=file] [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_options = [
+ Option("--as-sddl", help="Output ACL in the SDDL format", action="store_true"),
+ Option("--xattr-backend", type="choice", help="xattr backend type (native fs or tdb)",
+ choices=["native","tdb"]),
+ Option("--eadb-file", help="Name of the tdb file where attributes are stored", type="string"),
+ ]
+
+ takes_args = ["file"]
+
+ def run(self, file, 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)
+ if as_sddl:
+ anysid = security.dom_sid(security.SID_NT_SELF)
+ print acl.info.as_sddl(anysid)
+ else:
+ acl.dump()
+
+
+class cmd_nt_acl(SuperCommand):
+ """NT ACLs manipulation"""
+
+ subcommands = {}
+ subcommands["set"] = cmd_acl_set()
+ subcommands["get"] = cmd_acl_get()
+
diff --git a/source4/scripting/python/samba/netcmd/pwsettings.py b/source4/scripting/python/samba/netcmd/pwsettings.py
new file mode 100644
index 0000000000..3f1a8e189d
--- /dev/null
+++ b/source4/scripting/python/samba/netcmd/pwsettings.py
@@ -0,0 +1,197 @@
+#!/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
new file mode 100644
index 0000000000..f4daac853d
--- /dev/null
+++ b/source4/scripting/python/samba/netcmd/rodc.py
@@ -0,0 +1,115 @@
+#!/usr/bin/env python
+#
+# rodc related commands
+#
+# Copyright Andrew Tridgell 2010
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+from samba.netcmd import Command, CommandError, Option, SuperCommand
+import samba.getopt as options
+from samba.samdb import SamDB
+from samba.auth import system_session
+import ldb
+from samba.dcerpc import misc, drsuapi
+from samba.credentials import Credentials
+from samba.drs_utils import drs_Replicate
+
+class cmd_rodc_preload(Command):
+ """Preload one account for an RODC"""
+
+ synopsis = "%prog rodc preload <SID|DN|accountname>"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_options = [
+ Option("--server", help="DC to use", type=str),
+ ]
+
+ takes_args = ["account"]
+
+ def get_dn(self, samdb, account):
+ '''work out what DN they meant'''
+
+ # we accept the account in SID, accountname or DN form
+ if account[0:2] == 'S-':
+ res = samdb.search(base="<SID=%s>" % account,
+ expression="objectclass=user",
+ scope=ldb.SCOPE_BASE, attrs=[])
+ elif account.find('=') >= 0:
+ res = samdb.search(base=account,
+ expression="objectclass=user",
+ scope=ldb.SCOPE_BASE, attrs=[])
+ else:
+ res = samdb.search(expression="(&(samAccountName=%s)(objectclass=user))" % 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):
+
+ if server is None:
+ raise Exception("You must supply a server")
+
+ lp = sambaopts.get_loadparm()
+
+ creds = credopts.get_credentials(lp, fallback_machine=True)
+
+ # connect to the remote and local SAMs
+ samdb = SamDB(url="ldap://%s" % server,
+ session_info=system_session(),
+ credentials=creds, lp=lp)
+
+ local_samdb = SamDB(url=None, session_info=system_session(),
+ credentials=creds, lp=lp)
+
+ # work out the source and destination GUIDs
+ dc_ntds_dn = self.get_dsServiceName(samdb)
+ 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
+
+ destination_dsa_guid = misc.GUID(local_samdb.get_ntds_GUID())
+
+ local_samdb.transaction_start()
+ repl = drs_Replicate("ncacn_ip_tcp:%s[seal,print]" % server, lp, creds, local_samdb)
+ try:
+ repl.replicate(dn, source_dsa_invocation_id, destination_dsa_guid,
+ exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
+ except Exception, e:
+ raise CommandError("Error replicating DN %s" % dn, e)
+ local_samdb.transaction_commit()
+
+
+
+class cmd_rodc(SuperCommand):
+ """RODC commands"""
+
+ subcommands = {}
+ subcommands["preload"] = cmd_rodc_preload()
diff --git a/source4/scripting/python/samba/netcmd/setexpiry.py b/source4/scripting/python/samba/netcmd/setexpiry.py
new file mode 100644
index 0000000000..bd8ea166fa
--- /dev/null
+++ b/source4/scripting/python/samba/netcmd/setexpiry.py
@@ -0,0 +1,67 @@
+#!/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
new file mode 100644
index 0000000000..b32b651822
--- /dev/null
+++ b/source4/scripting/python/samba/netcmd/setpassword.py
@@ -0,0 +1,80 @@
+#!/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/spn.py b/source4/scripting/python/samba/netcmd/spn.py
new file mode 100644
index 0000000000..4cfa21fa03
--- /dev/null
+++ b/source4/scripting/python/samba/netcmd/spn.py
@@ -0,0 +1,217 @@
+#!/usr/bin/env python
+#
+# spn management
+#
+# Copyright Matthieu Patou mat@samba.org 2010
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import samba.getopt as options
+import ldb
+import re
+from samba import provision
+from samba.samdb import SamDB
+from samba.auth import system_session
+from samba.netcmd import (
+ Command,
+ CommandError,
+ SuperCommand,
+ 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>"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_args = ["user"]
+
+ def run(self, user, credopts=None, sambaopts=None, versionopts=None):
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+ paths = provision.provision_paths_from_lp(lp, lp.get("realm"))
+ sam = SamDB(paths.samdb, session_info=system_session(),
+ credentials=creds, lp=lp)
+ # TODO once I understand how, use the domain info to naildown
+ # to the correct domain
+ (cleaneduser, realm, domain) = _get_user_realm_domain(user)
+ print cleaneduser
+ res = sam.search(expression="samaccountname=%s" % 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)
+ for e in spns:
+ print "\t %s" % (str(e))
+
+ else:
+ print "User %s has no servicePrincipalName" % str(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>"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+ takes_options = [
+ Option("--force", help="Force the addition of the spn"\
+ " even it exists already", action="store_true"),
+ ]
+ takes_args = ["name", "user"]
+
+ def run(self, name, user, force=False, credopts=None, sambaopts=None, versionopts=None):
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+ paths = provision.provision_paths_from_lp(lp, lp.get("realm"))
+ sam = SamDB(paths.samdb, session_info=system_session(),
+ credentials=creds, lp=lp)
+ res = sam.search(expression="servicePrincipalName=%s" % 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"])
+ if len(res) >0:
+ res[0].dn
+ msg = ldb.Message()
+ spns = res[0].get("servicePrincipalName")
+ tab = []
+ found = False
+ flag = ldb.FLAG_MOD_ADD
+ if spns != None:
+ for e in spns:
+ if str(e) == name:
+ found = True
+ tab.append(str(e))
+ flag = ldb.FLAG_MOD_REPLACE
+ tab.append(name)
+ msg.dn = res[0].dn
+ msg["servicePrincipalName"] = ldb.MessageElement(tab, flag,
+ "servicePrincipalName")
+ if not found:
+ sam.modify(msg)
+ else:
+ raise CommandError("Service principal %s already"
+ " affected to %s" % (name, user))
+ else:
+ raise CommandError("User %s not found" % user)
+
+
+class cmd_spn_delete(Command):
+ """Delete a spn."""
+ synopsis = "%prog spn delete <name> [user]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_args = ["name", "user?"]
+
+ def run(self, name, user=None, credopts=None, sambaopts=None, versionopts=None):
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+ paths = provision.provision_paths_from_lp(lp, lp.get("realm"))
+ sam = SamDB(paths.samdb, session_info=system_session(),
+ credentials=creds, lp=lp)
+ res = sam.search(expression="servicePrincipalName=%s" % name,
+ scope=ldb.SCOPE_SUBTREE,
+ attrs=["servicePrincipalName", "samAccountName"])
+ if len(res) >0:
+ result = None
+ if user is not None:
+ (cleaneduser, realm, domain) = _get_user_realm_domain(user)
+ for elem in res:
+ if str(elem["samAccountName"]).lower() == cleaneduser:
+ result = elem
+ if result is None:
+ raise CommandError("Unable to find user %s with"
+ " spn %s" % (user, name))
+ else:
+ if len(res) != 1:
+ listUser = ""
+ for r in res:
+ listUser = "%s\n%s" % (listUser, str(r.dn))
+ raise CommandError("More than one user has the spn %s "\
+ "and no specific user was specified, list of users"\
+ " with this spn:%s" % (name, listUser))
+ else:
+ result=res[0]
+
+
+ msg = ldb.Message()
+ spns = result.get("servicePrincipalName")
+ tab = []
+ if spns != None:
+ for e in spns:
+ if str(e) != name:
+ tab.append(str(e))
+ flag = ldb.FLAG_MOD_REPLACE
+ msg.dn = result.dn
+ msg["servicePrincipalName"] = ldb.MessageElement(tab, flag,
+ "servicePrincipalName")
+ sam.modify(msg)
+ else:
+ raise CommandError("Service principal %s not affected" % name)
+
+class cmd_spn(SuperCommand):
+ """SPN management [server connection needed]"""
+
+ subcommands = {}
+ subcommands["add"] = cmd_spn_add()
+ subcommands["list"] = cmd_spn_list()
+ subcommands["delete"] = cmd_spn_delete()
+
diff --git a/source4/scripting/python/samba/netcmd/time.py b/source4/scripting/python/samba/netcmd/time.py
new file mode 100644
index 0000000000..e13d3df96c
--- /dev/null
+++ b/source4/scripting/python/samba/netcmd/time.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+#
+# time
+#
+# Copyright Jelmer Vernooij 2010 <jelmer@samba.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import samba.getopt as options
+import common
+from samba.net import Net
+
+from samba.netcmd import (
+ Command,
+ )
+
+class cmd_time(Command):
+ """Retrieve the time on a remote server [server connection needed]"""
+ synopsis = "%prog time <server-name>"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_args = ["server_name?"]
+
+ def run(self, server_name=None, credopts=None, sambaopts=None, versionopts=None):
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp, fallback_machine=True)
+ net = Net(creds, lp, server=credopts.ipaddress)
+ if server_name is None:
+ server_name = common.netcmd_dnsname(lp)
+ print net.time(server_name)
diff --git a/source4/scripting/python/samba/netcmd/user.py b/source4/scripting/python/samba/netcmd/user.py
new file mode 100644
index 0000000000..bbc972bcc7
--- /dev/null
+++ b/source4/scripting/python/samba/netcmd/user.py
@@ -0,0 +1,76 @@
+#!/usr/bin/env python
+#
+# user management
+#
+# 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_user_add(Command):
+ """Create a new user."""
+ synopsis = "%prog user add <name> [<password>]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_args = ["name", "password?"]
+
+ 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)
+
+
+class cmd_user_delete(Command):
+ """Delete a user."""
+ synopsis = "%prog user delete <name>"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_args = ["name"]
+
+ def run(self, name, 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)
+ net.delete_user(name)
+
+
+class cmd_user(SuperCommand):
+ """User management [server connection needed]"""
+
+ subcommands = {}
+ subcommands["add"] = cmd_user_add()
+ subcommands["delete"] = cmd_user_delete()
+
diff --git a/source4/scripting/python/samba/netcmd/vampire.py b/source4/scripting/python/samba/netcmd/vampire.py
new file mode 100644
index 0000000000..509aa8aacd
--- /dev/null
+++ b/source4/scripting/python/samba/netcmd/vampire.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python
+#
+# Vampire
+#
+# Copyright Jelmer Vernooij 2010 <jelmer@samba.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import samba.getopt as options
+
+from samba.net import Net
+
+from samba.netcmd import (
+ Command,
+ Option,
+ SuperCommand,
+ CommandError
+ )
+
+class cmd_vampire(Command):
+ """Join and synchronise a remote AD domain to the local server [server connection needed]"""
+ synopsis = "%prog vampire [options] <domain>"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_options = [
+ Option("--target-dir", help="Target directory.", type=str),
+ Option("--force", help="force run", action='store_true', default=False),
+ ]
+
+ takes_args = ["domain"]
+
+ def run(self, domain, target_dir=None, credopts=None, sambaopts=None, versionopts=None, force=False):
+ if not force:
+ raise CommandError("samba-tool vampire is deprecated, please use samba-tool join. Use --force to override")
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+ net = Net(creds, lp, server=credopts.ipaddress)
+ (domain_name, domain_sid) = net.vampire(domain=domain, target_dir=target_dir)
+ self.outf.write("Vampired domain %s (%s)\n" % (domain_name, domain_sid))
diff --git a/source4/scripting/python/samba/ntacls.py b/source4/scripting/python/samba/ntacls.py
new file mode 100644
index 0000000000..78365e98b8
--- /dev/null
+++ b/source4/scripting/python/samba/ntacls.py
@@ -0,0 +1,171 @@
+#!/usr/bin/env python
+
+# Unix SMB/CIFS implementation.
+# Copyright (C) Matthieu Patou <mat@matws.net> 2009-2010
+#
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+"""NT Acls."""
+
+
+import os
+import samba.xattr_native, samba.xattr_tdb
+from samba.dcerpc import security, xattr
+from samba.ndr import ndr_pack, ndr_unpack
+
+class XattrBackendError(Exception):
+ """A generic xattr backend error."""
+
+
+def checkset_backend(lp, backend, eadbfile):
+ '''return the path to the eadb, or None'''
+ if backend is None:
+ return lp.get("posix:eadb")
+ elif backend == "native":
+ return None
+ elif backend == "tdb":
+ if eadbfile is not None:
+ return eadbfile
+ else:
+ return os.path.abspath(os.path.join(lp.get("private dir"), "eadb.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
+ attribute = samba.xattr_native.wrap_getxattr(file,
+ xattr.XATTR_NTACL_NAME)
+ 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))
+ else:
+ samba.xattr_native.wrap_setxattr(file, xattr.XATTR_NTACL_NAME,
+ ndr_pack(ntacl))
+
+
+def ldapmask2filemask(ldm):
+ """Takes the access mask of a DS ACE and transform them in a File ACE mask.
+ """
+ RIGHT_DS_CREATE_CHILD = 0x00000001
+ RIGHT_DS_DELETE_CHILD = 0x00000002
+ RIGHT_DS_LIST_CONTENTS = 0x00000004
+ ACTRL_DS_SELF = 0x00000008
+ RIGHT_DS_READ_PROPERTY = 0x00000010
+ RIGHT_DS_WRITE_PROPERTY = 0x00000020
+ RIGHT_DS_DELETE_TREE = 0x00000040
+ RIGHT_DS_LIST_OBJECT = 0x00000080
+ RIGHT_DS_CONTROL_ACCESS = 0x00000100
+ FILE_READ_DATA = 0x0001
+ FILE_LIST_DIRECTORY = 0x0001
+ FILE_WRITE_DATA = 0x0002
+ FILE_ADD_FILE = 0x0002
+ FILE_APPEND_DATA = 0x0004
+ FILE_ADD_SUBDIRECTORY = 0x0004
+ FILE_CREATE_PIPE_INSTANCE = 0x0004
+ FILE_READ_EA = 0x0008
+ FILE_WRITE_EA = 0x0010
+ FILE_EXECUTE = 0x0020
+ FILE_TRAVERSE = 0x0020
+ FILE_DELETE_CHILD = 0x0040
+ FILE_READ_ATTRIBUTES = 0x0080
+ FILE_WRITE_ATTRIBUTES = 0x0100
+ DELETE = 0x00010000
+ READ_CONTROL = 0x00020000
+ WRITE_DAC = 0x00040000
+ WRITE_OWNER = 0x00080000
+ SYNCHRONIZE = 0x00100000
+ STANDARD_RIGHTS_ALL = 0x001F0000
+
+ 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 |\
+ 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 |\
+ FILE_ADD_SUBDIRECTORY)
+
+ if ldm & RIGHT_DS_CREATE_CHILD:
+ filemask = filemask | (FILE_ADD_SUBDIRECTORY | FILE_ADD_FILE)
+
+ if ldm & RIGHT_DS_DELETE_CHILD:
+ filemask = filemask | FILE_DELETE_CHILD
+
+ return filemask
+
+
+def dsacl2fsacl(dssddl, domsid):
+ """
+
+ 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]
+ if not ace.type & security.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT and str(ace.trustee) != security.SID_BUILTIN_PREW2K:
+ # if fdescr.type & security.SEC_DESC_DACL_AUTO_INHERITED:
+ ace.flags = ace.flags | security.SEC_ACE_FLAG_OBJECT_INHERIT | security.SEC_ACE_FLAG_CONTAINER_INHERIT
+ if str(ace.trustee) == security.SID_CREATOR_OWNER:
+ # For Creator/Owner the IO flag is set as this ACE has only a sense for child objects
+ ace.flags = ace.flags | security.SEC_ACE_FLAG_INHERIT_ONLY
+ ace.access_mask = ldapmask2filemask(ace.access_mask)
+ fdescr.dacl_add(ace)
+
+ return fdescr.as_sddl(sid)
diff --git a/source4/scripting/python/samba/provision.py b/source4/scripting/python/samba/provision.py
deleted file mode 100644
index 64491c2b18..0000000000
--- a/source4/scripting/python/samba/provision.py
+++ /dev/null
@@ -1,2067 +0,0 @@
-#
-# Unix SMB/CIFS implementation.
-# backend code for provisioning a Samba4 server
-
-# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
-# 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."""
-
-from base64 import b64encode
-import os
-import sys
-import pwd
-import grp
-import time
-import uuid, glue
-import socket
-import param
-import registry
-import samba
-import subprocess
-import ldb
-
-import shutil
-from credentials import Credentials, DONT_USE_KERBEROS
-from auth import system_session, admin_session
-from samba import version, Ldb, substitute_var, valid_netbios_name
-from samba import check_all_substituted
-from samba import DS_DOMAIN_FUNCTION_2000, DS_DOMAIN_FUNCTION_2008, DS_DC_FUNCTION_2008, DS_DC_FUNCTION_2008_R2
-from samba.samdb import SamDB
-from samba.idmap import IDmapDB
-from samba.dcerpc import security
-from samba.ndr import ndr_pack
-import urllib
-from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError, timestring
-from ms_schema import read_ms_schema
-from ms_display_specifiers import read_ms_ldif
-from signal import SIGTERM
-from dcerpc.misc import SEC_CHAN_BDC, SEC_CHAN_WKSTA
-
-__docformat__ = "restructuredText"
-
-def find_setup_dir():
- """Find the setup directory used by provision."""
- dirname = os.path.dirname(__file__)
- if "/site-packages/" in dirname:
- prefix = "/".join(dirname[:dirname.index("/site-packages/")].split("/")[:-2])
- for suffix in ["share/setup", "share/samba/setup", "setup"]:
- ret = os.path.join(prefix, suffix)
- if os.path.isdir(ret):
- return ret
- # In source tree
- ret = os.path.join(dirname, "../../../setup")
- if os.path.isdir(ret):
- return ret
- raise Exception("Unable to find setup directory.")
-
-def get_schema_descriptor(domain_sid):
- sddl = "O:SAG:SAD:(A;CI;RPLCLORC;;;AU)(A;CI;RPWPCRCCLCLORCWOWDSW;;;SA)" \
- "(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
- "(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)" \
- "S:(AU;SA;WPCCDCWOWDSDDTSW;;;WD)" \
- "(AU;CISA;WP;;;WD)(AU;SA;CR;;;BA)" \
- "(AU;SA;CR;;;DU)(OU;SA;CR;e12b56b6-0a95-11d1-adbb-00c04fd8d5cd;;WD)" \
- "(OU;SA;CR;45ec5156-db7e-47bb-b53f-dbeb2d03c40f;;WD)"
- sec = security.descriptor.from_sddl(sddl, domain_sid)
- return b64encode(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;;S-1-5-21-3191434175-1265308384-3577286990-498)" \
- "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 b64encode(ndr_pack(sec))
-
-
-DEFAULTSITE = "Default-First-Site-Name"
-
-# Exception classes
-
-class ProvisioningError(Exception):
- """A generic provision error."""
-
-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 ProvisionPaths(object):
- def __init__(self):
- self.shareconf = None
- self.hklm = None
- self.hkcu = None
- self.hkcr = None
- self.hku = None
- self.hkpd = None
- self.hkpt = None
- self.samdb = None
- self.idmapdb = None
- self.secrets = None
- self.keytab = None
- self.dns_keytab = None
- self.dns = None
- self.winsdb = None
- self.private_dir = None
- self.ldapdir = None
- self.slapdconf = None
- self.modulesconf = None
- self.memberofconf = None
- self.fedoradsinf = None
- self.fedoradspartitions = None
- self.fedoradssasl = None
- self.olmmron = None
- self.olmmrserveridsconf = None
- self.olmmrsyncreplconf = None
- self.olcdir = None
- self.olslapd = None
- self.olcseedldif = None
-
-
-class ProvisionNames(object):
- def __init__(self):
- self.rootdn = None
- self.domaindn = None
- self.configdn = None
- self.schemadn = None
- self.sambadn = None
- self.ldapmanagerdn = None
- self.dnsdomain = None
- self.realm = None
- self.netbiosname = None
- self.domain = None
- self.hostname = None
- self.sitename = None
- self.smbconf = None
-
-
-class ProvisionResult(object):
- def __init__(self):
- self.paths = None
- self.domaindn = None
- self.lp = None
- self.samdb = None
-
-class Schema(object):
- def __init__(self, setup_path, domain_sid, schemadn=None,
- serverdn=None, sambadn=None, ldap_backend_type=None):
- """Load schema for the SamDB from the AD schema files and samba4_schema.ldif
-
- :param samdb: Load a schema into a SamDB.
- :param setup_path: Setup path function.
- :param schemadn: DN of the schema
- :param serverdn: DN of the server
-
- Returns the schema data loaded, to avoid double-parsing when then needing to add it to the db
- """
-
- self.ldb = Ldb()
- self.schema_data = read_ms_schema(setup_path('ad-schema/MS-AD_Schema_2K8_Attributes.txt'),
- setup_path('ad-schema/MS-AD_Schema_2K8_Classes.txt'))
- self.schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
- self.schema_data = substitute_var(self.schema_data, {"SCHEMADN": schemadn})
- check_all_substituted(self.schema_data)
-
- self.schema_dn_modify = read_and_sub_file(setup_path("provision_schema_basedn_modify.ldif"),
- {"SCHEMADN": schemadn,
- "SERVERDN": serverdn,
- })
-
- descr = get_schema_descriptor(domain_sid)
- self.schema_dn_add = read_and_sub_file(setup_path("provision_schema_basedn.ldif"),
- {"SCHEMADN": schemadn,
- "DESCRIPTOR": descr
- })
-
- prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
- prefixmap = b64encode(prefixmap)
-
- # We don't actually add this ldif, just parse it
- prefixmap_ldif = "dn: cn=schema\nprefixMap:: %s\n\n" % prefixmap
- self.ldb.set_schema_from_ldif(prefixmap_ldif, self.schema_data)
-
-
-# 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",
- scope=SCOPE_SUBTREE)
- if target is not None:
- attributes[str(res[i]["lDAPDisplayName"])]=str(target)
-
- return attributes
-
-def get_dnsyntax_attributes(schemadn,schemaldb):
- attrs = ["linkID", "lDAPDisplayName"]
- res = schemaldb.search(expression="(&(!(linkID=*))(objectclass=attributeSchema)(attributeSyntax=2.5.5.1))", base=schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
- attributes = []
- for i in range (0, len(res)):
- attributes.append(str(res[i]["lDAPDisplayName"]))
-
- return attributes
-
-
-def check_install(lp, session_info, credentials):
- """Check whether the current install seems ok.
-
- :param lp: Loadparm context
- :param session_info: Session information
- :param credentials: Credentials
- """
- if lp.get("realm") == "":
- raise Exception("Realm empty")
- ldb = Ldb(lp.get("sam database"), session_info=session_info,
- credentials=credentials, lp=lp)
- if len(ldb.search("(cn=Administrator)")) != 1:
- raise ProvisioningError("No administrator account found")
-
-
-def findnss(nssfn, names):
- """Find a user or group from a list of possibilities.
-
- :param nssfn: NSS Function to try (should raise KeyError if not found)
- :param names: Names to check.
- :return: Value return by first names list.
- """
- for name in names:
- try:
- return nssfn(name)
- except KeyError:
- pass
- raise KeyError("Unable to find user/group %r" % names)
-
-
-findnss_uid = lambda names: findnss(pwd.getpwnam, names)[2]
-findnss_gid = lambda names: findnss(grp.getgrnam, names)[2]
-
-
-def read_and_sub_file(file, subst_vars):
- """Read a file and sub in variables found in it
-
- :param file: File to be read (typically from setup directory)
- param subst_vars: Optional variables to subsitute in the file.
- """
- data = open(file, 'r').read()
- if subst_vars is not None:
- data = substitute_var(data, subst_vars)
- check_all_substituted(data)
- return data
-
-
-def setup_add_ldif(ldb, ldif_path, subst_vars=None):
- """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.
- """
- assert isinstance(ldif_path, str)
-
- data = read_and_sub_file(ldif_path, subst_vars)
- ldb.add_ldif(data)
-
-
-def setup_modify_ldif(ldb, ldif_path, subst_vars=None):
- """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)
-
-
-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
- ldb.transaction_commit()
-
-
-def setup_file(template, fname, subst_vars):
- """Setup a file in the private dir.
-
- :param template: Path of the template file.
- :param fname: Path of the file to create.
- :param subst_vars: Substitution variables.
- """
- f = fname
-
- if os.path.exists(f):
- os.unlink(f)
-
- data = read_and_sub_file(template, subst_vars)
- open(f, 'w').write(data)
-
-
-def provision_paths_from_lp(lp, dnsdomain):
- """Set the default paths for provisioning.
-
- :param lp: Loadparm context.
- :param dnsdomain: DNS Domain name
- """
- paths = ProvisionPaths()
- paths.private_dir = lp.get("private dir")
- paths.dns_keytab = "dns.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.dns = os.path.join(paths.private_dir, dnsdomain + ".zone")
- paths.namedconf = os.path.join(paths.private_dir, "named.conf")
- paths.namedtxt = os.path.join(paths.private_dir, "named.txt")
- 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.ldapdir = os.path.join(paths.private_dir,
- "ldap")
- paths.slapdconf = os.path.join(paths.ldapdir,
- "slapd.conf")
- paths.slapdpid = os.path.join(paths.ldapdir,
- "slapd.pid")
- paths.modulesconf = os.path.join(paths.ldapdir,
- "modules.conf")
- paths.memberofconf = os.path.join(paths.ldapdir,
- "memberof.conf")
- paths.fedoradsinf = os.path.join(paths.ldapdir,
- "fedorads.inf")
- paths.fedoradspartitions = os.path.join(paths.ldapdir,
- "fedorads-partitions.ldif")
- paths.fedoradssasl = os.path.join(paths.ldapdir,
- "fedorads-sasl.ldif")
- paths.fedoradssamba = os.path.join(paths.ldapdir,
- "fedorads-samba.ldif")
- paths.olmmrserveridsconf = os.path.join(paths.ldapdir,
- "mmr_serverids.conf")
- paths.olmmrsyncreplconf = os.path.join(paths.ldapdir,
- "mmr_syncrepl.conf")
- paths.olcdir = os.path.join(paths.ldapdir,
- "slapd.d")
- paths.olcseedldif = os.path.join(paths.ldapdir,
- "olc_seed.ldif")
- paths.hklm = "hklm.ldb"
- paths.hkcr = "hkcr.ldb"
- paths.hkcu = "hkcu.ldb"
- paths.hku = "hku.ldb"
- paths.hkpd = "hkpd.ldb"
- paths.hkpt = "hkpt.ldb"
-
- paths.sysvol = lp.get("path", "sysvol")
-
- paths.netlogon = lp.get("path", "netlogon")
-
- paths.smbconf = lp.configfile
-
- return paths
-
-
-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, sambadn=None):
- """Guess configuration settings to use."""
-
- if hostname is None:
- hostname = socket.gethostname().split(".")[0].lower()
-
- netbiosname = hostname.upper()
- if not valid_netbios_name(netbiosname):
- raise InvalidNetbiosName(netbiosname)
-
- hostname = hostname.lower()
-
- if dnsdomain is None:
- dnsdomain = lp.get("realm")
-
- if serverrole is None:
- serverrole = lp.get("server role")
-
- assert dnsdomain is not None
- realm = dnsdomain.upper()
-
- if lp.get("realm").upper() != realm:
- raise Exception("realm '%s' in %s must match chosen realm '%s'" %
- (lp.get("realm"), lp.configfile, realm))
-
- dnsdomain = dnsdomain.lower()
-
- if serverrole == "domain controller":
- if domain is None:
- domain = lp.get("workgroup")
- if domaindn is None:
- domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
- if lp.get("workgroup").upper() != domain.upper():
- raise Exception("workgroup '%s' in smb.conf must match chosen domain '%s'",
- lp.get("workgroup"), domain)
- else:
- domain = netbiosname
- if domaindn is None:
- domaindn = "CN=" + netbiosname
-
- assert domain is not None
- domain = domain.upper()
- if not valid_netbios_name(domain):
- raise InvalidNetbiosName(domain)
-
- if netbiosname.upper() == realm.upper():
- raise Exception("realm %s must not be equal to netbios domain name %s", realm, netbiosname)
-
- if hostname.upper() == realm.upper():
- raise Exception("realm %s must not be equal to hostname %s", realm, hostname)
-
- if domain.upper() == realm.upper():
- raise Exception("realm %s must not be equal to domain name %s", realm, domain)
-
- if rootdn is None:
- rootdn = domaindn
-
- if configdn is None:
- configdn = "CN=Configuration," + rootdn
- if schemadn is None:
- schemadn = "CN=Schema," + configdn
- if sambadn is None:
- sambadn = "CN=Samba"
-
- if sitename is None:
- sitename=DEFAULTSITE
-
- names = ProvisionNames()
- names.rootdn = rootdn
- names.domaindn = domaindn
- names.configdn = configdn
- names.schemadn = schemadn
- names.sambadn = sambadn
- names.ldapmanagerdn = "CN=Manager," + rootdn
- names.dnsdomain = dnsdomain
- names.domain = domain
- names.realm = realm
- names.netbiosname = netbiosname
- names.hostname = hostname
- names.sitename = sitename
- names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (netbiosname, sitename, configdn)
-
- return names
-
-
-def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
- targetdir):
- """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].lower()
-
- 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"
-
- assert domain is not None
- assert realm is not None
-
- default_lp = param.LoadParm()
- #Load non-existant file
- if os.path.exists(smbconf):
- default_lp.load(smbconf)
-
- 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)
-
- default_lp.set("lock dir", os.path.abspath(targetdir))
- else:
- privatedir_line = ""
- lockdir_line = ""
-
- sysvol = os.path.join(default_lp.get("lock dir"), "sysvol")
- netlogon = os.path.join(sysvol, realm.lower(), "scripts")
-
- setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix),
- smbconf, {
- "HOSTNAME": hostname,
- "DOMAIN": domain,
- "REALM": realm,
- "SERVERROLE": serverrole,
- "NETLOGONPATH": netlogon,
- "SYSVOLPATH": sysvol,
- "PRIVATEDIR_LINE": privatedir_line,
- "LOCKDIR_LINE": lockdir_line
- })
-
-
-def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
- users_gid, wheel_gid):
- """setup reasonable name mappings for sam names to unix names.
-
- :param samdb: SamDB object.
- :param idmap: IDmap db object.
- :param sid: The domain sid.
- :param domaindn: The domain DN.
- :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."""
-
- 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)
-
-def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info,
- credentials, names,
- serverrole, ldap_backend=None,
- erase=False):
- """Setup the partitions for the SAM database.
-
- Alternatively, provision() may call this, and then populate the database.
-
- :note: This will wipe the Sam Database!
-
- :note: This function always removes the local SAM LDB file. The erase
- parameter controls whether to erase the existing data, which
- may not be stored locally but in LDAP.
- """
- assert session_info is not None
-
- # We use options=["modules:"] to stop the modules loading - we
- # just want to wipe and re-initialise the database, not start it up
-
- try:
- samdb = Ldb(url=samdb_path, session_info=session_info,
- credentials=credentials, lp=lp, options=["modules:"])
- # Wipes the database
- samdb.erase_except_schema_controlled()
- except LdbError:
- os.unlink(samdb_path)
- samdb = Ldb(url=samdb_path, session_info=session_info,
- credentials=credentials, lp=lp, options=["modules:"])
- # Wipes the database
- samdb.erase_except_schema_controlled()
-
-
- #Add modules to the list to activate them by default
- #beware often order is important
- #
- # Some Known ordering constraints:
- # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
- # - objectclass must be before password_hash, because password_hash checks
- # that the objectclass is of type person (filled in by objectclass
- # module when expanding the objectclass list)
- # - partition must be last
- # - each partition has its own module list then
- modules_list = ["resolve_oids",
- "rootdse",
- "acl",
- "paged_results",
- "ranged_results",
- "anr",
- "server_sort",
- "asq",
- "extended_dn_store",
- "extended_dn_in",
- "rdn_name",
- "objectclass",
- "descriptor",
- "samldb",
- "password_hash",
- "operational",
- "kludge_acl"]
- tdb_modules_list = [
- "subtree_rename",
- "subtree_delete",
- "linked_attributes",
- "extended_dn_out_ldb"]
- modules_list2 = ["show_deleted",
- "partition"]
-
- domaindn_ldb = "users.ldb"
- configdn_ldb = "configuration.ldb"
- schemadn_ldb = "schema.ldb"
- if ldap_backend is not None:
- domaindn_ldb = ldap_backend.ldapi_uri
- configdn_ldb = ldap_backend.ldapi_uri
- schemadn_ldb = ldap_backend.ldapi_uri
-
- if ldap_backend.ldap_backend_type == "fedora-ds":
- backend_modules = ["nsuniqueid", "paged_searches"]
- # We can handle linked attributes here, as we don't have directory-side subtree operations
- tdb_modules_list = ["linked_attributes", "extended_dn_out_dereference"]
- elif ldap_backend.ldap_backend_type == "openldap":
- backend_modules = ["entryuuid", "paged_searches"]
- # OpenLDAP handles subtree renames, so we don't want to do any of these things
- tdb_modules_list = ["extended_dn_out_dereference"]
-
- elif serverrole == "domain controller":
- tdb_modules_list.insert(0, "repl_meta_data")
- backend_modules = []
- else:
- backend_modules = ["objectguid"]
-
- if tdb_modules_list is None:
- tdb_modules_list_as_string = ""
- else:
- tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
-
- samdb.transaction_start()
- try:
- message("Setting up sam.ldb partitions and settings")
- setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
- "SCHEMADN": names.schemadn,
- "SCHEMADN_LDB": schemadn_ldb,
- "SCHEMADN_MOD2": ",objectguid",
- "CONFIGDN": names.configdn,
- "CONFIGDN_LDB": configdn_ldb,
- "DOMAINDN": names.domaindn,
- "DOMAINDN_LDB": domaindn_ldb,
- "SCHEMADN_MOD": "schema_fsmo,instancetype",
- "CONFIGDN_MOD": "naming_fsmo,instancetype",
- "DOMAINDN_MOD": "pdc_fsmo,instancetype",
- "MODULES_LIST": ",".join(modules_list),
- "TDB_MODULES_LIST": tdb_modules_list_as_string,
- "MODULES_LIST2": ",".join(modules_list2),
- "BACKEND_MOD": ",".join(backend_modules),
- })
-
- samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
-
- message("Setting up sam.ldb rootDSE")
- setup_samdb_rootdse(samdb, setup_path, names)
-
- except:
- samdb.transaction_cancel()
- raise
-
- samdb.transaction_commit()
-
-def secretsdb_self_join(secretsdb, domain,
- netbiosname, domainsid, machinepass,
- realm=None, dnsdomain=None,
- keytab_path=None,
- key_version_number=1,
- secure_channel_type=SEC_CHAN_WKSTA):
- """Add domain join-specific bits to a secrets database.
-
- :param secretsdb: Ldb Handle to the secrets database
- :param machinepass: Machine password
- """
- attrs=["whenChanged",
- "secret",
- "priorSecret",
- "priorChanged",
- "krb5Keytab",
- "privateKeytab"]
-
-
- msg = ldb.Message(ldb.Dn(secretsdb, "flatname=%s,cn=Primary Domains" % domain));
- msg["secureChannelType"] = str(secure_channel_type)
- msg["flatname"] = [domain]
- msg["objectClass"] = ["top", "primaryDomain"]
- if realm is not None:
- if dnsdomain is None:
- dnsdomain = realm.lower()
- msg["objectClass"] = ["top", "primaryDomain", "kerberosSecret"]
- msg["realm"] = realm
- msg["saltPrincipal"] = "host/%s.%s@%s" % (netbiosname.lower(), dnsdomain.lower(), realm.upper())
- msg["msDS-KeyVersionNumber"] = [str(key_version_number)]
- msg["privateKeytab"] = ["secrets.keytab"];
-
-
- msg["secret"] = [machinepass]
- msg["samAccountName"] = ["%s$" % netbiosname]
- msg["secureChannelType"] = [str(secure_channel_type)]
- msg["objectSid"] = [ndr_pack(domainsid)]
-
- res = secretsdb.search(base="cn=Primary Domains",
- attrs=attrs,
- expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain))" % (domain, realm, str(domainsid))),
- scope=SCOPE_ONELEVEL)
-
- for del_msg in res:
- if del_msg.dn is not msg.dn:
- secretsdb.delete(del_msg.dn)
-
- res = secretsdb.search(base=msg.dn, attrs=attrs, scope=SCOPE_BASE)
-
- if len(res) == 1:
- msg["priorSecret"] = res[0]["secret"]
- msg["priorWhenChanged"] = res[0]["whenChanged"]
-
- if res["privateKeytab"] is not None:
- msg["privateKeytab"] = res[0]["privateKeytab"]
-
- if res["krb5Keytab"] is not None:
- msg["krb5Keytab"] = res[0]["krb5Keytab"]
-
- for el in msg:
- el.set_flags(ldb.FLAG_MOD_REPLACE)
- secretsdb.modify(msg)
- else:
- secretsdb.add(msg)
-
-
-def secretsdb_setup_dns(secretsdb, setup_path, realm, dnsdomain,
- dns_keytab_path, dnspass):
- """Add DNS specific bits to a secrets database.
-
- :param secretsdb: Ldb Handle to the secrets database
- :param setup_path: Setup path function
- :param machinepass: Machine password
- """
- setup_ldb(secretsdb, setup_path("secrets_dns.ldif"), {
- "REALM": realm,
- "DNSDOMAIN": dnsdomain,
- "DNS_KEYTAB": dns_keytab_path,
- "DNSPASS_B64": b64encode(dnspass),
- })
-
-
-def setup_secretsdb(path, setup_path, session_info, credentials, lp):
- """Setup the secrets database.
-
- :param path: Path to the secrets database.
- :param setup_path: Get the path to a setup file.
- :param session_info: Session info.
- :param credentials: Credentials
- :param lp: Loadparm context
- :return: LDB handle for the created secrets database
- """
- if os.path.exists(path):
- os.unlink(path)
- secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
- lp=lp)
- secrets_ldb.erase()
- secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
- secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
- lp=lp)
- secrets_ldb.transaction_start()
- secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
-
- if credentials is not None and credentials.authentication_requested():
- if credentials.get_bind_dn() is not None:
- setup_add_ldif(secrets_ldb, setup_path("secrets_simple_ldap.ldif"), {
- "LDAPMANAGERDN": credentials.get_bind_dn(),
- "LDAPMANAGERPASS_B64": b64encode(credentials.get_password())
- })
- else:
- setup_add_ldif(secrets_ldb, setup_path("secrets_sasl_ldap.ldif"), {
- "LDAPADMINUSER": credentials.get_username(),
- "LDAPADMINREALM": credentials.get_realm(),
- "LDAPADMINPASS_B64": b64encode(credentials.get_password())
- })
-
- return secrets_ldb
-
-def setup_registry(path, setup_path, session_info, lp):
- """Setup the registry.
-
- :param path: Path to the registry database
- :param setup_path: Function that returns the path to a setup.
- :param session_info: Session information
- :param credentials: Credentials
- :param lp: Loadparm context
- """
- reg = registry.Registry()
- hive = registry.open_ldb(path, session_info=session_info,
- lp_ctx=lp)
- reg.mount_hive(hive, registry.HKEY_LOCAL_MACHINE)
- provision_reg = setup_path("provision.reg")
- assert os.path.exists(provision_reg)
- reg.diff_apply(provision_reg)
-
-
-def setup_idmapdb(path, setup_path, session_info, lp):
- """Setup the idmap database.
-
- :param path: path to the idmap database
- :param setup_path: Function that returns a path to a setup file
- :param session_info: Session information
- :param credentials: Credentials
- :param lp: Loadparm context
- """
- if os.path.exists(path):
- os.unlink(path)
-
- idmap_ldb = IDmapDB(path, session_info=session_info,
- lp=lp)
-
- idmap_ldb.erase()
- idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
- return idmap_ldb
-
-
-def setup_samdb_rootdse(samdb, setup_path, names):
- """Setup the SamDB rootdse.
-
- :param samdb: Sam Database handle
- :param setup_path: Obtain setup path
- """
- setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
- "SCHEMADN": names.schemadn,
- "NETBIOSNAME": names.netbiosname,
- "DNSDOMAIN": names.dnsdomain,
- "REALM": names.realm,
- "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
- "DOMAINDN": names.domaindn,
- "ROOTDN": names.rootdn,
- "CONFIGDN": names.configdn,
- "SERVERDN": names.serverdn,
- })
-
-
-def setup_self_join(samdb, names,
- machinepass, dnspass,
- domainsid, invocationid, setup_path,
- policyguid, policyguid_dc, domainControllerFunctionality):
- """Join a host to its own domain."""
- assert isinstance(invocationid, str)
- setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
- "CONFIGDN": names.configdn,
- "SCHEMADN": names.schemadn,
- "DOMAINDN": names.domaindn,
- "SERVERDN": names.serverdn,
- "INVOCATIONID": invocationid,
- "NETBIOSNAME": names.netbiosname,
- "DEFAULTSITE": names.sitename,
- "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
- "MACHINEPASS_B64": b64encode(machinepass),
- "DNSPASS_B64": b64encode(dnspass),
- "REALM": names.realm,
- "DOMAIN": names.domain,
- "DNSDOMAIN": names.dnsdomain,
- "SAMBA_VERSION_STRING": version,
- "DOMAIN_CONTROLLER_FUNCTIONALITY": str(domainControllerFunctionality)})
-
- setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
- "POLICYGUID": policyguid,
- "POLICYGUID_DC": policyguid_dc,
- "DNSDOMAIN": names.dnsdomain,
- "DOMAINSID": str(domainsid),
- "DOMAINDN": names.domaindn})
-
- # add the NTDSGUID based SPNs
- ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
- names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
- expression="", scope=SCOPE_BASE)
- assert isinstance(names.ntdsguid, str)
-
- # Setup fSMORoleOwner entries to point at the newly created DC entry
- setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
- "DOMAIN": names.domain,
- "DNSDOMAIN": names.dnsdomain,
- "DOMAINDN": names.domaindn,
- "CONFIGDN": names.configdn,
- "SCHEMADN": names.schemadn,
- "DEFAULTSITE": names.sitename,
- "SERVERDN": names.serverdn,
- "NETBIOSNAME": names.netbiosname,
- "NTDSGUID": names.ntdsguid
- })
-
-
-def setup_samdb(path, setup_path, session_info, credentials, lp,
- names, message,
- domainsid, domainguid, policyguid, policyguid_dc,
- fill, adminpass, krbtgtpass,
- machinepass, invocationid, dnspass,
- serverrole, schema=None, ldap_backend=None):
- """Setup a complete SAM Database.
-
- :note: This will wipe the main SAM database file!
- """
-
- # Do NOT change these default values without discussion with the team and reslease manager.
- domainFunctionality = DS_DOMAIN_FUNCTION_2008
- forestFunctionality = DS_DOMAIN_FUNCTION_2008
- domainControllerFunctionality = DS_DC_FUNCTION_2008
-
- # Also wipes the database
- setup_samdb_partitions(path, setup_path, message=message, lp=lp,
- credentials=credentials, session_info=session_info,
- names=names,
- ldap_backend=ldap_backend, serverrole=serverrole)
-
- if (schema == None):
- schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn,
- sambadn=names.sambadn, ldap_backend_type=ldap_backend.ldap_backend_type)
-
- # Load the database, but importantly, use Ldb not SamDB as we don't want to load the global schema
- samdb = Ldb(session_info=session_info,
- credentials=credentials, lp=lp)
-
- message("Pre-loading the Samba 4 and AD schema")
-
- # Load the schema from the one we computed earlier
- samdb.set_schema_from_ldb(schema.ldb)
-
- # And now we can connect to the DB - the schema won't be loaded from the DB
- samdb.connect(path)
-
- # Load @OPTIONS
- samdb.load_ldif_file_add(setup_path("provision_options.ldif"))
-
- if fill == FILL_DRS:
- return samdb
-
- samdb.transaction_start()
- try:
- message("Erasing data from partitions")
- # Load the schema (again). This time it will force a reindex,
- # and will therefore make the erase_partitions() below
- # computationally sane
- samdb.set_schema_from_ldb(schema.ldb)
- samdb.erase_partitions()
-
- # Set the domain functionality levels onto the database.
- # Various module (the password_hash module in particular) need
- # to know what level of AD we are emulating.
-
- # These will be fixed into the database via the database
- # modifictions below, but we need them set from the start.
- samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
- samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
- samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
-
- samdb.set_domain_sid(str(domainsid))
- if serverrole == "domain controller":
- samdb.set_invocation_id(invocationid)
-
- message("Adding DomainDN: %s" % names.domaindn)
- if serverrole == "domain controller":
- domain_oc = "domainDNS"
- else:
- domain_oc = "samba4LocalDomain"
-
-#impersonate domain admin
- admin_session_info = admin_session(lp, str(domainsid))
- samdb.set_session_info(admin_session_info)
-
- setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
- "DOMAINDN": names.domaindn,
- "DOMAIN_OC": domain_oc
- })
-
- message("Modifying DomainDN: " + names.domaindn + "")
- if domainguid is not None:
- domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
- else:
- domainguid_mod = ""
-
- setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
- "CREATTIME": str(int(time.time()) * 1e7), # seconds -> ticks
- "DOMAINSID": str(domainsid),
- "SCHEMADN": names.schemadn,
- "NETBIOSNAME": names.netbiosname,
- "DEFAULTSITE": names.sitename,
- "CONFIGDN": names.configdn,
- "SERVERDN": names.serverdn,
- "POLICYGUID": policyguid,
- "DOMAINDN": names.domaindn,
- "DOMAINGUID_MOD": domainguid_mod,
- "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
- "SAMBA_VERSION_STRING": version
- })
-
- message("Adding configuration container")
- descr = get_config_descriptor(domainsid);
- setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
- "CONFIGDN": names.configdn,
- "DESCRIPTOR": descr,
- })
- message("Modifying configuration container")
- setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
- "CONFIGDN": names.configdn,
- "SCHEMADN": names.schemadn,
- })
-
- # The LDIF here was created when the Schema object was constructed
- message("Setting up sam.ldb schema")
- samdb.add_ldif(schema.schema_dn_add)
- samdb.modify_ldif(schema.schema_dn_modify)
- samdb.write_prefixes_from_schema()
- samdb.add_ldif(schema.schema_data)
- setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
- {"SCHEMADN": names.schemadn})
-
- message("Setting up sam.ldb configuration data")
- 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_FUNCTIONALALITY": str(forestFunctionality)
- })
-
- message("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)
-
- message("Adding users container")
- setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
- "DOMAINDN": names.domaindn})
- message("Modifying users container")
- setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
- "DOMAINDN": names.domaindn})
- message("Adding computers container")
- setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
- "DOMAINDN": names.domaindn})
- message("Modifying computers container")
- setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
- "DOMAINDN": names.domaindn})
- message("Setting up sam.ldb data")
- setup_add_ldif(samdb, setup_path("provision.ldif"), {
- "CREATTIME": str(int(time.time()) * 1e7), # seconds -> ticks
- "DOMAINDN": names.domaindn,
- "NETBIOSNAME": names.netbiosname,
- "DEFAULTSITE": names.sitename,
- "CONFIGDN": names.configdn,
- "SERVERDN": names.serverdn,
- "POLICYGUID_DC": policyguid_dc
- })
-
- if fill == FILL_FULL:
- message("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),
- "KRBTGTPASS_B64": b64encode(krbtgtpass),
- })
-
- if serverrole == "domain controller":
- message("Setting up self join")
- setup_self_join(samdb, names=names, invocationid=invocationid,
- dnspass=dnspass,
- machinepass=machinepass,
- domainsid=domainsid, policyguid=policyguid,
- policyguid_dc=policyguid_dc,
- setup_path=setup_path,
- domainControllerFunctionality=domainControllerFunctionality)
-
- ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
- names.ntdsguid = samdb.searchone(basedn=ntds_dn,
- attribute="objectGUID", expression="", scope=SCOPE_BASE)
- assert isinstance(names.ntdsguid, str)
-
- except:
- samdb.transaction_cancel()
- raise
-
- samdb.transaction_commit()
- return samdb
-
-
-FILL_FULL = "FULL"
-FILL_NT4SYNC = "NT4SYNC"
-FILL_DRS = "DRS"
-
-
-def provision(setup_dir, message, 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, adminpass=None, ldapadminpass=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, aci=None, serverrole=None,
- ldap_backend_extra_port=None, ldap_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):
- """Provision samba4
-
- :note: caution, this wipes all existing data!
- """
-
- def setup_path(file):
- return os.path.join(setup_dir, file)
-
- if domainsid is None:
- domainsid = security.random_sid()
- else:
- domainsid = security.dom_sid(domainsid)
-
-
- # create/adapt the group policy GUIDs
- if policyguid is None:
- policyguid = str(uuid.uuid4())
- policyguid = policyguid.upper()
- if policyguid_dc is None:
- policyguid_dc = str(uuid.uuid4())
- policyguid_dc = policyguid_dc.upper()
-
- if adminpass is None:
- adminpass = glue.generate_random_str(12)
- if krbtgtpass is None:
- krbtgtpass = glue.generate_random_str(12)
- if machinepass is None:
- machinepass = glue.generate_random_str(12)
- if dnspass is None:
- dnspass = glue.generate_random_str(12)
- if ldapadminpass is None:
- #Make a new, random password between Samba and it's LDAP server
- ldapadminpass=glue.generate_random_str(12)
-
-
- root_uid = findnss_uid([root or "root"])
- nobody_uid = findnss_uid([nobody or "nobody"])
- users_gid = findnss_gid([users or "users"])
- if wheel is None:
- wheel_gid = findnss_gid(["wheel", "adm"])
- else:
- wheel_gid = findnss_gid([wheel])
-
- if targetdir is not None:
- if (not os.path.exists(os.path.join(targetdir, "etc"))):
- os.makedirs(os.path.join(targetdir, "etc"))
- smbconf = os.path.join(targetdir, "etc", "smb.conf")
- elif smbconf is None:
- smbconf = param.default_path()
-
- # only install a new smb.conf if there isn't one there already
- if not os.path.exists(smbconf):
- make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
- targetdir)
-
- lp = param.LoadParm()
- lp.load(smbconf)
-
- names = guess_names(lp=lp, hostname=hostname, domain=domain,
- dnsdomain=realm, serverrole=serverrole, sitename=sitename,
- rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
- serverdn=serverdn)
-
- paths = provision_paths_from_lp(lp, names.dnsdomain)
-
- if hostip is None:
- try:
- hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
- except socket.gaierror, (socket.EAI_NODATA, msg):
- hostip = None
-
- if hostip6 is None:
- try:
- hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
- except socket.gaierror, (socket.EAI_NODATA, msg):
- hostip6 = None
-
- if serverrole is None:
- serverrole = lp.get("server role")
-
- assert serverrole in ("domain controller", "member server", "standalone")
- if invocationid is None and serverrole == "domain controller":
- invocationid = str(uuid.uuid4())
-
- if not os.path.exists(paths.private_dir):
- os.mkdir(paths.private_dir)
-
- ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
-
- schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn,
- sambadn=names.sambadn, ldap_backend_type=ldap_backend_type)
-
- secrets_credentials = credentials
- provision_backend = None
- if ldap_backend_type:
- # We only support an LDAP backend over ldapi://
-
- provision_backend = ProvisionBackend(paths=paths, setup_path=setup_path,
- lp=lp, credentials=credentials,
- names=names,
- message=message, hostname=hostname,
- root=root, schema=schema,
- ldap_backend_type=ldap_backend_type,
- ldapadminpass=ldapadminpass,
- ldap_backend_extra_port=ldap_backend_extra_port,
- ol_mmr_urls=ol_mmr_urls,
- slapd_path=slapd_path,
- setup_ds_path=setup_ds_path,
- ldap_dryrun_mode=ldap_dryrun_mode)
-
- # Now use the backend credentials to access the databases
- credentials = provision_backend.credentials
- secrets_credentials = provision_backend.adminCredentials
- ldapi_url = provision_backend.ldapi_uri
-
- # only install a new shares config db if there is none
- if not os.path.exists(paths.shareconf):
- message("Setting up share.ldb")
- share_ldb = Ldb(paths.shareconf, session_info=session_info,
- credentials=credentials, lp=lp)
- share_ldb.load_ldif_file_add(setup_path("share.ldif"))
-
-
- message("Setting up secrets.ldb")
- secrets_ldb = setup_secretsdb(paths.secrets, setup_path,
- session_info=session_info,
- credentials=secrets_credentials, lp=lp)
-
- message("Setting up the registry")
- setup_registry(paths.hklm, setup_path, session_info,
- lp=lp)
-
- message("Setting up idmap db")
- idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
- lp=lp)
-
- message("Setting up SAM db")
- samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info,
- credentials=credentials, lp=lp, names=names,
- message=message,
- 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,
- serverrole=serverrole, ldap_backend=provision_backend)
-
- if serverrole == "domain controller":
- if paths.netlogon is None:
- message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
- message("Please either remove %s or see the template at %s" %
- ( paths.smbconf, setup_path("provision.smb.conf.dc")))
- assert(paths.netlogon is not None)
-
- if paths.sysvol is None:
- message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
- message("Please either remove %s or see the template at %s" %
- (paths.smbconf, setup_path("provision.smb.conf.dc")))
- assert(paths.sysvol is not None)
-
- # Set up group policies (domain policy and domain controller policy)
-
- policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
- "{" + policyguid + "}")
- os.makedirs(policy_path, 0755)
- open(os.path.join(policy_path, "GPT.INI"), 'w').write(
- "[General]\r\nVersion=65543")
- os.makedirs(os.path.join(policy_path, "MACHINE"), 0755)
- os.makedirs(os.path.join(policy_path, "USER"), 0755)
-
- policy_path_dc = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
- "{" + policyguid_dc + "}")
- os.makedirs(policy_path_dc, 0755)
- open(os.path.join(policy_path_dc, "GPT.INI"), 'w').write(
- "[General]\r\nVersion=2")
- os.makedirs(os.path.join(policy_path_dc, "MACHINE"), 0755)
- os.makedirs(os.path.join(policy_path_dc, "USER"), 0755)
-
- 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)
-
- message("Setting up sam.ldb rootDSE marking as synchronized")
- setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
-
- # Only make a zone file on the first DC, it should be replicated with DNS replication
- if serverrole == "domain controller":
- secretsdb_self_join(secrets_ldb, domain=domain,
- realm=names.realm,
- dnsdomain=names.dnsdomain,
- netbiosname=names.netbiosname,
- domainsid=domainsid,
- machinepass=machinepass,
- secure_channel_type=SEC_CHAN_BDC)
-
- secretsdb_setup_dns(secrets_ldb, setup_path,
- 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)
-
- create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
- domaindn=names.domaindn, hostip=hostip,
- hostip6=hostip6, hostname=names.hostname,
- dnspass=dnspass, realm=names.realm,
- domainguid=domainguid, ntdsguid=names.ntdsguid)
-
- create_named_conf(paths.namedconf, setup_path, realm=names.realm,
- dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
-
- create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
- dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
- keytab_name=paths.dns_keytab)
- message("See %s for an example configuration include file for BIND" % paths.namedconf)
- message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
-
- create_krb5_conf(paths.krb5conf, setup_path,
- dnsdomain=names.dnsdomain, hostname=names.hostname,
- realm=names.realm)
- message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
-
- #Now commit the secrets.ldb to disk
- secrets_ldb.transaction_commit()
-
- if provision_backend is not None:
- if ldap_backend_type == "fedora-ds":
- ldapi_db = Ldb(provision_backend.ldapi_uri, lp=lp, credentials=credentials)
-
- # delete default SASL mappings
- res = ldapi_db.search(expression="(!(cn=samba-admin mapping))", base="cn=mapping,cn=sasl,cn=config", scope=SCOPE_ONELEVEL, attrs=["dn"])
-
- # configure in-directory access control on Fedora DS via the aci attribute (over a direct ldapi:// socket)
- for i in range (0, len(res)):
- dn = str(res[i]["dn"])
- ldapi_db.delete(dn)
-
- aci = """(targetattr = "*") (version 3.0;acl "full access to all by samba-admin";allow (all)(userdn = "ldap:///CN=samba-admin,%s");)""" % names.sambadn
-
- m = ldb.Message()
- m["aci"] = ldb.MessageElement([aci], ldb.FLAG_MOD_REPLACE, "aci")
-
- m.dn = ldb.Dn(1, names.domaindn)
- ldapi_db.modify(m)
-
- m.dn = ldb.Dn(1, names.configdn)
- ldapi_db.modify(m)
-
- m.dn = ldb.Dn(1, names.schemadn)
- ldapi_db.modify(m)
-
- # if an LDAP backend is in use, terminate slapd after final provision and check its proper termination
- if provision_backend.slapd.poll() is None:
- #Kill the slapd
- if hasattr(provision_backend.slapd, "terminate"):
- provision_backend.slapd.terminate()
- else:
- # Older python versions don't have .terminate()
- import signal
- os.kill(provision_backend.slapd.pid, signal.SIGTERM)
-
- #and now wait for it to die
- provision_backend.slapd.communicate()
-
- # now display slapd_command_file.txt to show how slapd must be started next time
- message("Use later the following commandline to start slapd, then Samba:")
- slapd_command = "\'" + "\' \'".join(provision_backend.slapd_command) + "\'"
- message(slapd_command)
- message("This slapd-Commandline is also stored under: " + paths.ldapdir + "/ldap_backend_startup.sh")
-
- setup_file(setup_path("ldap_backend_startup.sh"), paths.ldapdir + "/ldap_backend_startup.sh", {
- "SLAPD_COMMAND" : slapd_command})
-
-
- create_phpldapadmin_config(paths.phpldapadminconfig, setup_path,
- ldapi_url)
-
- message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
-
- message("Once the above files are installed, your Samba4 server will be ready to use")
- message("Server Role: %s" % serverrole)
- message("Hostname: %s" % names.hostname)
- message("NetBIOS Domain: %s" % names.domain)
- message("DNS Domain: %s" % names.dnsdomain)
- message("DOMAIN SID: %s" % str(domainsid))
- if samdb_fill == FILL_FULL:
- message("Admin password: %s" % adminpass)
- if provision_backend:
- if provision_backend.credentials.get_bind_dn() is not None:
- message("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
- else:
- message("LDAP Admin User: %s" % provision_backend.credentials.get_username())
-
- message("LDAP Admin Password: %s" % provision_backend.credentials.get_password())
-
- result = ProvisionResult()
- result.domaindn = domaindn
- result.paths = paths
- result.lp = lp
- result.samdb = samdb
- return result
-
-
-
-def provision_become_dc(setup_dir=None,
- smbconf=None, targetdir=None, realm=None,
- rootdn=None, domaindn=None, schemadn=None,
- configdn=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):
-
- def message(text):
- """print a message if quiet is not set."""
- print text
-
- glue.set_debug_level(debuglevel)
-
- return provision(setup_dir, message, system_session(), 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)
-
-
-def setup_db_config(setup_path, dbdir):
- """Setup a Berkeley database.
-
- :param setup_path: Setup path function.
- :param dbdir: Database directory."""
- if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
- os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
- if not os.path.isdir(os.path.join(dbdir, "tmp")):
- os.makedirs(os.path.join(dbdir, "tmp"), 0700)
-
- setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
- {"LDAPDBDIR": dbdir})
-
-class ProvisionBackend(object):
- def __init__(self, paths=None, setup_path=None, lp=None, credentials=None,
- names=None, message=None,
- hostname=None, root=None,
- schema=None, ldapadminpass=None,
- ldap_backend_type=None, ldap_backend_extra_port=None,
- ol_mmr_urls=None,
- setup_ds_path=None, slapd_path=None,
- nosync=False, ldap_dryrun_mode=False):
- """Provision an LDAP backend for samba4
-
- This works for OpenLDAP and Fedora DS
- """
-
- self.ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.ldapdir, "ldapi"), safe="")
-
- if not os.path.isdir(paths.ldapdir):
- os.makedirs(paths.ldapdir, 0700)
-
- if ldap_backend_type == "existing":
- #Check to see that this 'existing' LDAP backend in fact exists
- ldapi_db = Ldb(self.ldapi_uri, credentials=credentials)
- search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
- expression="(objectClass=OpenLDAProotDSE)")
-
- # If we have got here, then we must have a valid connection to the LDAP server, with valid credentials supplied
- # This caused them to be set into the long-term database later in the script.
- self.credentials = credentials
- self.ldap_backend_type = "openldap" #For now, assume existing backends at least emulate OpenLDAP
- return
-
- # we will shortly start slapd with ldapi for final provisioning. first check with ldapsearch -> rootDSE via self.ldapi_uri
- # if another instance of slapd is already running
- try:
- ldapi_db = Ldb(self.ldapi_uri)
- search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
- expression="(objectClass=OpenLDAProotDSE)");
- try:
- f = open(paths.slapdpid, "r")
- p = f.read()
- f.close()
- message("Check for slapd Process with PID: " + str(p) + " and terminate it manually.")
- except:
- pass
-
- raise ProvisioningError("Warning: Another slapd Instance seems already running on this host, listening to " + self.ldapi_uri + ". Please shut it down before you continue. ")
-
- except LdbError, e:
- pass
-
- # Try to print helpful messages when the user has not specified the path to slapd
- if slapd_path is None:
- raise ProvisioningError("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!")
- if not os.path.exists(slapd_path):
- message (slapd_path)
- raise ProvisioningError("Warning: Given Path to slapd does not exist!")
-
- schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
- try:
- os.unlink(schemadb_path)
- except OSError:
- pass
-
-
- # Put the LDIF of the schema into a database so we can search on
- # it to generate schema-dependent configurations in Fedora DS and
- # OpenLDAP
- os.path.join(paths.ldapdir, "schema-tmp.ldb")
- schema.ldb.connect(schemadb_path)
- schema.ldb.transaction_start()
-
- # These bits of LDIF are supplied when the Schema object is created
- schema.ldb.add_ldif(schema.schema_dn_add)
- schema.ldb.modify_ldif(schema.schema_dn_modify)
- schema.ldb.add_ldif(schema.schema_data)
- schema.ldb.transaction_commit()
-
- self.credentials = Credentials()
- self.credentials.guess(lp)
- #Kerberos to an ldapi:// backend makes no sense
- self.credentials.set_kerberos_state(DONT_USE_KERBEROS)
-
- self.adminCredentials = Credentials()
- self.adminCredentials.guess(lp)
- #Kerberos to an ldapi:// backend makes no sense
- self.adminCredentials.set_kerberos_state(DONT_USE_KERBEROS)
-
- self.ldap_backend_type = ldap_backend_type
-
- if ldap_backend_type == "fedora-ds":
- provision_fds_backend(self, paths=paths, setup_path=setup_path,
- names=names, message=message,
- hostname=hostname,
- ldapadminpass=ldapadminpass, root=root,
- schema=schema,
- ldap_backend_extra_port=ldap_backend_extra_port,
- setup_ds_path=setup_ds_path,
- slapd_path=slapd_path,
- nosync=nosync,
- ldap_dryrun_mode=ldap_dryrun_mode)
-
- elif ldap_backend_type == "openldap":
- provision_openldap_backend(self, paths=paths, setup_path=setup_path,
- names=names, message=message,
- hostname=hostname,
- ldapadminpass=ldapadminpass, root=root,
- schema=schema,
- ldap_backend_extra_port=ldap_backend_extra_port,
- ol_mmr_urls=ol_mmr_urls,
- slapd_path=slapd_path,
- nosync=nosync,
- ldap_dryrun_mode=ldap_dryrun_mode)
- else:
- raise ProvisioningError("Unknown LDAP backend type selected")
-
- self.credentials.set_password(ldapadminpass)
- self.adminCredentials.set_username("samba-admin")
- self.adminCredentials.set_password(ldapadminpass)
-
- # Now start the slapd, so we can provision onto it. We keep the
- # subprocess context around, to kill this off at the successful
- # end of the script
- self.slapd = subprocess.Popen(self.slapd_provision_command, close_fds=True, shell=False)
-
- while self.slapd.poll() is None:
- # Wait until the socket appears
- try:
- ldapi_db = Ldb(self.ldapi_uri, lp=lp, credentials=self.credentials)
- search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
- expression="(objectClass=OpenLDAProotDSE)")
- # If we have got here, then we must have a valid connection to the LDAP server!
- return
- except LdbError, e:
- time.sleep(1)
- pass
-
- raise ProvisioningError("slapd died before we could make a connection to it")
-
-
-def provision_openldap_backend(result, paths=None, setup_path=None, names=None,
- message=None,
- hostname=None, ldapadminpass=None, root=None,
- schema=None,
- ldap_backend_extra_port=None,
- ol_mmr_urls=None,
- slapd_path=None, nosync=False,
- ldap_dryrun_mode=False):
-
- #Allow the test scripts to turn off fsync() for OpenLDAP as for TDB and LDB
- nosync_config = ""
- if nosync:
- nosync_config = "dbnosync"
-
- lnkattr = get_linked_attributes(names.schemadn,schema.ldb)
- refint_attributes = ""
- memberof_config = "# Generated from Samba4 schema\n"
- for att in lnkattr.keys():
- if lnkattr[att] is not None:
- refint_attributes = refint_attributes + " " + att
-
- memberof_config += read_and_sub_file(setup_path("memberof.conf"),
- { "MEMBER_ATTR" : att ,
- "MEMBEROF_ATTR" : lnkattr[att] })
-
- refint_config = read_and_sub_file(setup_path("refint.conf"),
- { "LINK_ATTRS" : refint_attributes})
-
- attrs = ["linkID", "lDAPDisplayName"]
- res = schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
- index_config = ""
- for i in range (0, len(res)):
- index_attr = res[i]["lDAPDisplayName"][0]
- if index_attr == "objectGUID":
- index_attr = "entryUUID"
-
- index_config += "index " + index_attr + " eq\n"
-
-# generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
- mmr_on_config = ""
- mmr_replicator_acl = ""
- mmr_serverids_config = ""
- mmr_syncrepl_schema_config = ""
- mmr_syncrepl_config_config = ""
- mmr_syncrepl_user_config = ""
-
-
- if ol_mmr_urls is not None:
- # For now, make these equal
- mmr_pass = ldapadminpass
-
- url_list=filter(None,ol_mmr_urls.split(' '))
- if (len(url_list) == 1):
- url_list=filter(None,ol_mmr_urls.split(','))
-
-
- mmr_on_config = "MirrorMode On"
- mmr_replicator_acl = " by dn=cn=replicator,cn=samba read"
- serverid=0
- for url in url_list:
- serverid=serverid+1
- mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"),
- { "SERVERID" : str(serverid),
- "LDAPSERVER" : url })
- rid=serverid*10
- rid=rid+1
- mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
- { "RID" : str(rid),
- "MMRDN": names.schemadn,
- "LDAPSERVER" : url,
- "MMR_PASSWORD": mmr_pass})
-
- rid=rid+1
- mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
- { "RID" : str(rid),
- "MMRDN": names.configdn,
- "LDAPSERVER" : url,
- "MMR_PASSWORD": mmr_pass})
-
- rid=rid+1
- mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
- { "RID" : str(rid),
- "MMRDN": names.domaindn,
- "LDAPSERVER" : url,
- "MMR_PASSWORD": mmr_pass })
- # OpenLDAP cn=config initialisation
- olc_syncrepl_config = ""
- olc_mmr_config = ""
- # if mmr = yes, generate cn=config-replication directives
- # and olc_seed.lif for the other mmr-servers
- if ol_mmr_urls is not None:
- serverid=0
- olc_serverids_config = ""
- olc_syncrepl_seed_config = ""
- olc_mmr_config += read_and_sub_file(setup_path("olc_mmr.conf"),{})
- rid=1000
- for url in url_list:
- serverid=serverid+1
- olc_serverids_config += read_and_sub_file(setup_path("olc_serverid.conf"),
- { "SERVERID" : str(serverid),
- "LDAPSERVER" : url })
-
- rid=rid+1
- olc_syncrepl_config += read_and_sub_file(setup_path("olc_syncrepl.conf"),
- { "RID" : str(rid),
- "LDAPSERVER" : url,
- "MMR_PASSWORD": mmr_pass})
-
- olc_syncrepl_seed_config += read_and_sub_file(setup_path("olc_syncrepl_seed.conf"),
- { "RID" : str(rid),
- "LDAPSERVER" : url})
-
- setup_file(setup_path("olc_seed.ldif"), paths.olcseedldif,
- {"OLC_SERVER_ID_CONF": olc_serverids_config,
- "OLC_PW": ldapadminpass,
- "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
- # end olc
-
- setup_file(setup_path("slapd.conf"), paths.slapdconf,
- {"DNSDOMAIN": names.dnsdomain,
- "LDAPDIR": paths.ldapdir,
- "DOMAINDN": names.domaindn,
- "CONFIGDN": names.configdn,
- "SCHEMADN": names.schemadn,
- "MEMBEROF_CONFIG": memberof_config,
- "MIRRORMODE": mmr_on_config,
- "REPLICATOR_ACL": mmr_replicator_acl,
- "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
- "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
- "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
- "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
- "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
- "OLC_MMR_CONFIG": olc_mmr_config,
- "REFINT_CONFIG": refint_config,
- "INDEX_CONFIG": index_config,
- "NOSYNC": nosync_config})
-
- setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "user"))
- setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "config"))
- setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "schema"))
-
- if not os.path.exists(os.path.join(paths.ldapdir, "db", "samba", "cn=samba")):
- os.makedirs(os.path.join(paths.ldapdir, "db", "samba", "cn=samba"), 0700)
-
- setup_file(setup_path("cn=samba.ldif"),
- os.path.join(paths.ldapdir, "db", "samba", "cn=samba.ldif"),
- { "UUID": str(uuid.uuid4()),
- "LDAPTIME": timestring(int(time.time()))} )
- setup_file(setup_path("cn=samba-admin.ldif"),
- os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=samba-admin.ldif"),
- {"LDAPADMINPASS_B64": b64encode(ldapadminpass),
- "UUID": str(uuid.uuid4()),
- "LDAPTIME": timestring(int(time.time()))} )
-
- if ol_mmr_urls is not None:
- setup_file(setup_path("cn=replicator.ldif"),
- os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=replicator.ldif"),
- {"MMR_PASSWORD_B64": b64encode(mmr_pass),
- "UUID": str(uuid.uuid4()),
- "LDAPTIME": timestring(int(time.time()))} )
-
-
- mapping = "schema-map-openldap-2.3"
- backend_schema = "backend-schema.schema"
-
- backend_schema_data = schema.ldb.convert_schema_to_openldap("openldap", open(setup_path(mapping), 'r').read())
- assert backend_schema_data is not None
- open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
-
- # now we generate the needed strings to start slapd automatically,
- # first ldapi_uri...
- if ldap_backend_extra_port is not None:
- # When we use MMR, we can't use 0.0.0.0 as it uses the name
- # specified there as part of it's clue as to it's own name,
- # and not to replicate to itself
- if ol_mmr_urls is None:
- server_port_string = "ldap://0.0.0.0:%d" % ldap_backend_extra_port
- else:
- server_port_string = "ldap://" + names.hostname + "." + names.dnsdomain +":%d" % ldap_backend_extra_port
- else:
- server_port_string = ""
-
- # Prepare the 'result' information - the commands to return in particular
- result.slapd_provision_command = [slapd_path]
-
- result.slapd_provision_command.append("-F" + paths.olcdir)
-
- result.slapd_provision_command.append("-h")
-
- # copy this command so we have two version, one with -d0 and only ldapi, and one with all the listen commands
- result.slapd_command = list(result.slapd_provision_command)
-
- result.slapd_provision_command.append(result.ldapi_uri)
- result.slapd_provision_command.append("-d0")
-
- uris = result.ldapi_uri
- if server_port_string is not "":
- uris = uris + " " + server_port_string
-
- result.slapd_command.append(uris)
-
- # Set the username - done here because Fedora DS still uses the admin DN and simple bind
- result.credentials.set_username("samba-admin")
-
- # If we were just looking for crashes up to this point, it's a
- # good time to exit before we realise we don't have OpenLDAP on
- # this system
- if ldap_dryrun_mode:
- sys.exit(0)
-
- # Finally, convert the configuration into cn=config style!
- if not os.path.isdir(paths.olcdir):
- os.makedirs(paths.olcdir, 0770)
-
- retcode = subprocess.call([slapd_path, "-Ttest", "-f", paths.slapdconf, "-F", paths.olcdir], close_fds=True, shell=False)
-
-# We can't do this, as OpenLDAP is strange. It gives an error
-# output to the above, but does the conversion sucessfully...
-#
-# if retcode != 0:
-# raise ProvisioningError("conversion from slapd.conf to cn=config failed")
-
- if not os.path.exists(os.path.join(paths.olcdir, "cn=config.ldif")):
- raise ProvisioningError("conversion from slapd.conf to cn=config failed")
-
- # Don't confuse the admin by leaving the slapd.conf around
- os.remove(paths.slapdconf)
-
-
-def provision_fds_backend(result, paths=None, setup_path=None, names=None,
- message=None,
- hostname=None, ldapadminpass=None, root=None,
- schema=None,
- ldap_backend_extra_port=None,
- setup_ds_path=None,
- slapd_path=None,
- nosync=False,
- ldap_dryrun_mode=False):
-
- if ldap_backend_extra_port is not None:
- serverport = "ServerPort=%d" % ldap_backend_extra_port
- else:
- serverport = ""
-
- setup_file(setup_path("fedorads.inf"), paths.fedoradsinf,
- {"ROOT": root,
- "HOSTNAME": hostname,
- "DNSDOMAIN": names.dnsdomain,
- "LDAPDIR": paths.ldapdir,
- "DOMAINDN": names.domaindn,
- "LDAPMANAGERDN": names.ldapmanagerdn,
- "LDAPMANAGERPASS": ldapadminpass,
- "SERVERPORT": serverport})
-
- setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions,
- {"CONFIGDN": names.configdn,
- "SCHEMADN": names.schemadn,
- "SAMBADN": names.sambadn,
- })
-
- setup_file(setup_path("fedorads-sasl.ldif"), paths.fedoradssasl,
- {"SAMBADN": names.sambadn,
- })
-
- setup_file(setup_path("fedorads-samba.ldif"), paths.fedoradssamba,
- {"SAMBADN": names.sambadn,
- "LDAPADMINPASS": ldapadminpass
- })
-
- mapping = "schema-map-fedora-ds-1.0"
- backend_schema = "99_ad.ldif"
-
- # Build a schema file in Fedora DS format
- backend_schema_data = schema.ldb.convert_schema_to_openldap("fedora-ds", open(setup_path(mapping), 'r').read())
- assert backend_schema_data is not None
- open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
-
- result.credentials.set_bind_dn(names.ldapmanagerdn)
-
- # Destory the target directory, or else setup-ds.pl will complain
- fedora_ds_dir = os.path.join(paths.ldapdir, "slapd-samba4")
- shutil.rmtree(fedora_ds_dir, True)
-
- result.slapd_provision_command = [slapd_path, "-D", fedora_ds_dir, "-i", paths.slapdpid];
- #In the 'provision' command line, stay in the foreground so we can easily kill it
- result.slapd_provision_command.append("-d0")
-
- #the command for the final run is the normal script
- result.slapd_command = [os.path.join(paths.ldapdir, "slapd-samba4", "start-slapd")]
-
- # If we were just looking for crashes up to this point, it's a
- # good time to exit before we realise we don't have Fedora DS on
- if ldap_dryrun_mode:
- sys.exit(0)
-
- # Try to print helpful messages when the user has not specified the path to the setup-ds tool
- if setup_ds_path is None:
- raise ProvisioningError("Warning: Fedora DS LDAP-Backend must be setup with path to setup-ds, e.g. --setup-ds-path=\"/usr/sbin/setup-ds.pl\"!")
- if not os.path.exists(setup_ds_path):
- message (setup_ds_path)
- raise ProvisioningError("Warning: Given Path to slapd does not exist!")
-
- # Run the Fedora DS setup utility
- retcode = subprocess.call([setup_ds_path, "--silent", "--file", paths.fedoradsinf], close_fds=True, shell=False)
- if retcode != 0:
- raise ProvisioningError("setup-ds failed")
-
- # Load samba-admin
- retcode = subprocess.call([
- os.path.join(paths.ldapdir, "slapd-samba4", "ldif2db"), "-s", names.sambadn, "-i", paths.fedoradssamba],
- close_fds=True, shell=False)
- if retcode != 0:
- raise("ldib2db failed")
-
-def create_phpldapadmin_config(path, setup_path, ldapi_uri):
- """Create a PHP LDAP admin configuration file.
-
- :param path: Path to write the configuration to.
- :param setup_path: Function to generate setup paths.
- """
- setup_file(setup_path("phpldapadmin-config.php"), path,
- {"S4_LDAPI_URI": ldapi_uri})
-
-
-def create_zone_file(path, setup_path, dnsdomain, domaindn,
- hostip, hostip6, hostname, dnspass, realm, domainguid,
- ntdsguid):
- """Write out a DNS zone file, from the info in the current database.
-
- :param path: Path of the new zone file.
- :param setup_path: Setup path function.
- :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 dnspass: Password for DNS
- :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
- else:
- hostip6_base_line = ""
- hostip6_host_line = ""
-
- if hostip is not None:
- hostip_base_line = " IN A " + hostip
- hostip_host_line = hostname + " IN A " + hostip
- else:
- hostip_base_line = ""
- hostip_host_line = ""
-
- setup_file(setup_path("provision.zone"), path, {
- "DNSPASS_B64": b64encode(dnspass),
- "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,
- })
-
-
-def create_named_conf(path, setup_path, realm, dnsdomain,
- private_dir):
- """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 setup_path: Setup path function.
- :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"), path, {
- "DNSDOMAIN": dnsdomain,
- "REALM": realm,
- "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
- "PRIVATE_DIR": private_dir
- })
-
-def create_named_txt(path, setup_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 setup_path: Setup path function.
- :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, setup_path, dnsdomain, hostname, realm):
- """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 setup_path: Setup path function.
- :param dnsdomain: DNS Domain name
- :param hostname: Local hostname
- :param realm: Realm name
- """
-
- setup_file(setup_path("krb5.conf"), path, {
- "DNSDOMAIN": dnsdomain,
- "HOSTNAME": hostname,
- "REALM": realm,
- })
-
-
diff --git a/source4/scripting/python/samba/provision/__init__.py b/source4/scripting/python/samba/provision/__init__.py
new file mode 100644
index 0000000000..55774c225b
--- /dev/null
+++ b/source4/scripting/python/samba/provision/__init__.py
@@ -0,0 +1,1929 @@
+
+# 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
+#
+# 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"
+
+from base64 import b64encode
+import os
+import re
+import pwd
+import grp
+import logging
+import time
+import uuid
+import socket
+import urllib
+import shutil
+
+import ldb
+
+from samba.auth import system_session, admin_session
+import samba
+from samba import (
+ Ldb,
+ check_all_substituted,
+ in_source_tree,
+ source_tree_topdir,
+ read_and_sub_file,
+ setup_file,
+ substitute_var,
+ valid_netbios_name,
+ version,
+ )
+from samba.dcerpc import security
+from samba.dcerpc.misc import (
+ SEC_CHAN_BDC,
+ SEC_CHAN_WKSTA,
+ )
+from samba.dsdb import (
+ DS_DOMAIN_FUNCTION_2003,
+ DS_DOMAIN_FUNCTION_2008_R2,
+ ENC_ALL_TYPES,
+ )
+from samba.idmap import IDmapDB
+from samba.ms_display_specifiers import read_ms_ldif
+from samba.ntacls import setntacl, dsacl2fsacl
+from samba.ndr import ndr_pack, ndr_unpack
+from samba.provision.backend import (
+ ExistingBackend,
+ FDSBackend,
+ LDBBackend,
+ OpenLDAPBackend,
+ )
+import samba.param
+import samba.registry
+from samba.schema import Schema
+from samba.samdb import SamDB
+
+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):
+ self.shareconf = None
+ self.hklm = None
+ self.hkcu = None
+ self.hkcr = None
+ self.hku = None
+ self.hkpd = None
+ self.hkpt = None
+ self.samdb = None
+ self.idmapdb = None
+ self.secrets = None
+ self.keytab = None
+ self.dns_keytab = None
+ self.dns = None
+ self.winsdb = None
+ self.private_dir = None
+
+
+class ProvisionNames(object):
+
+ def __init__(self):
+ self.rootdn = None
+ self.domaindn = None
+ self.configdn = None
+ self.schemadn = None
+ self.ldapmanagerdn = None
+ self.dnsdomain = None
+ self.realm = None
+ self.netbiosname = None
+ self.domain = None
+ self.hostname = None
+ self.sitename = None
+ self.smbconf = None
+
+
+def update_provision_usn(samdb, low, high, replace=False):
+ """Update the field provisionUSN in sam.ldb
+
+ This field is used to track range of USN modified by provision and
+ upgradeprovision.
+ This value is used afterward by next provision to figure out if
+ the field have been modified since last provision.
+
+ :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 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"])
+ for e in entry[0][LAST_PROVISION_USN_ATTRIBUTE]:
+ tab.append(str(e))
+
+ tab.append("%s-%s" % (low, high))
+ 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)
+ samdb.modify(delta)
+
+
+def set_provision_usn(samdb, low, high):
+ """Set the field provisionUSN in sam.ldb
+ This field is used to track range of USN modified by provision and
+ upgradeprovision.
+ This value is used afterward by next provision to figure out if
+ the field have been modified since last provision.
+
+ :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"""
+ tab = []
+ tab.append("%s-%s" % (low, high))
+ delta = ldb.Message()
+ delta.dn = ldb.Dn(samdb, "@PROVISION")
+ delta[LAST_PROVISION_USN_ATTRIBUTE] = ldb.MessageElement(tab,
+ ldb.FLAG_MOD_ADD, LAST_PROVISION_USN_ATTRIBUTE)
+ samdb.add(delta)
+
+
+def get_max_usn(samdb,basedn):
+ """ This function return the biggest USN present in the provision
+
+ :param samdb: A LDB object pointing to the sam.ldb
+ :param basedn: A string containing the base DN of the provision
+ (ie. DC=foo, DC=bar)
+ :return: The biggest USN in the provision"""
+
+ res = samdb.search(expression="objectClass=*",base=basedn,
+ scope=ldb.SCOPE_SUBTREE,attrs=["uSNChanged"],
+ controls=["search_options:1:2",
+ "server_sort:1:1:uSNChanged",
+ "paged_results:1:1"])
+ return res[0]["uSNChanged"]
+
+
+def get_last_provision_usn(sam):
+ """Get the lastest USN 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
+ """
+ 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
+ p = re.compile(r'-')
+ 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
+ return range
+ else:
+ return None
+
+
+class ProvisionResult(object):
+
+ def __init__(self):
+ self.paths = None
+ self.domaindn = None
+ self.lp = None
+ self.samdb = None
+
+
+def check_install(lp, session_info, credentials):
+ """Check whether the current install seems ok.
+
+ :param lp: Loadparm context
+ :param session_info: Session information
+ :param credentials: Credentials
+ """
+ if lp.get("realm") == "":
+ raise Exception("Realm empty")
+ samdb = Ldb(lp.get("sam database"), session_info=session_info,
+ credentials=credentials, lp=lp)
+ if len(samdb.search("(cn=Administrator)")) != 1:
+ raise ProvisioningError("No administrator account found")
+
+
+def findnss(nssfn, names):
+ """Find a user or group from a list of possibilities.
+
+ :param nssfn: NSS Function to try (should raise KeyError if not found)
+ :param names: Names to check.
+ :return: Value return by first names list.
+ """
+ for name in names:
+ try:
+ return nssfn(name)
+ except KeyError:
+ pass
+ raise KeyError("Unable to find user/group in %r" % names)
+
+
+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.
+
+ :param lp: Loadparm context.
+ :param dnsdomain: DNS Domain name
+ """
+ paths = ProvisionPaths()
+ paths.private_dir = lp.get("private dir")
+
+ # This is stored without path prefix for the "privateKeytab" attribute in
+ # "secrets_dns.ldif".
+ paths.dns_keytab = "dns.keytab"
+ 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.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")
+ paths.spn_update_list = os.path.join(paths.private_dir, "spn_update_list")
+ paths.namedconf = os.path.join(paths.private_dir, "named.conf")
+ paths.namedconf_update = os.path.join(paths.private_dir, "named.conf.update")
+ paths.namedtxt = os.path.join(paths.private_dir, "named.txt")
+ 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"
+ paths.hku = "hku.ldb"
+ paths.hkpd = "hkpd.ldb"
+ paths.hkpt = "hkpt.ldb"
+ paths.sysvol = lp.get("path", "sysvol")
+ paths.netlogon = lp.get("path", "netlogon")
+ paths.smbconf = lp.configfile
+ return paths
+
+
+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):
+ """Guess configuration settings to use."""
+
+ if hostname is None:
+ hostname = socket.gethostname().split(".")[0]
+
+ 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 = netbiosname.upper()
+ if not valid_netbios_name(netbiosname):
+ raise InvalidNetbiosName(netbiosname)
+
+ if dnsdomain is None:
+ dnsdomain = lp.get("realm")
+ if dnsdomain is None or dnsdomain == "":
+ raise ProvisioningError("guess_names: 'realm' not specified in supplied %s!", lp.configfile)
+
+ dnsdomain = dnsdomain.lower()
+
+ if serverrole is None:
+ serverrole = lp.get("server role")
+ if serverrole is None:
+ raise ProvisioningError("guess_names: 'server role' not specified in supplied %s!" % lp.configfile)
+
+ serverrole = serverrole.lower()
+
+ realm = dnsdomain.upper()
+
+ if lp.get("realm") == "":
+ raise ProvisioningError("guess_names: 'realm =' was not specified in supplied %s. Please remove the smb.conf file and let provision generate it" % lp.configfile)
+
+ if lp.get("realm").upper() != realm:
+ 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))
+
+ if serverrole == "domain controller":
+ if domain is None:
+ # This will, for better or worse, default to 'WORKGROUP'
+ domain = lp.get("workgroup")
+ domain = domain.upper()
+
+ if lp.get("workgroup").upper() != domain:
+ 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=")
+
+ if domain == netbiosname:
+ raise ProvisioningError("guess_names: Domain '%s' must not be equal to short host name '%s'!" % (domain, netbiosname))
+ else:
+ domain = netbiosname
+ if domaindn is None:
+ domaindn = "DC=" + netbiosname
+
+ if not valid_netbios_name(domain):
+ raise InvalidNetbiosName(domain)
+
+ if hostname.upper() == realm:
+ raise ProvisioningError("guess_names: Realm '%s' must not be equal to hostname '%s'!" % (realm, hostname))
+ if netbiosname.upper() == realm:
+ raise ProvisioningError("guess_names: Realm '%s' must not be equal to netbios hostname '%s'!" % (realm, netbiosname))
+ if domain == realm:
+ raise ProvisioningError("guess_names: Realm '%s' must not be equal to short domain name '%s'!" % (realm, domain))
+
+ if rootdn is None:
+ rootdn = domaindn
+
+ if configdn is None:
+ configdn = "CN=Configuration," + rootdn
+ if schemadn is None:
+ schemadn = "CN=Schema," + configdn
+
+ if sitename is None:
+ sitename=DEFAULTSITE
+
+ names = ProvisionNames()
+ names.rootdn = rootdn
+ names.domaindn = domaindn
+ names.configdn = configdn
+ names.schemadn = schemadn
+ names.ldapmanagerdn = "CN=Manager," + rootdn
+ names.dnsdomain = dnsdomain
+ names.domain = domain
+ names.realm = realm
+ names.netbiosname = netbiosname
+ names.hostname = hostname
+ names.sitename = sitename
+ names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (
+ netbiosname, sitename, configdn)
+
+ return names
+
+
+def make_smbconf(smbconf, hostname, domain, realm, serverrole,
+ targetdir, sid_generator="internal", eadb=False, lp=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"
+
+ if sid_generator is None:
+ sid_generator = "internal"
+
+ assert domain is not None
+ domain = domain.upper()
+
+ assert realm is not None
+ realm = realm.upper()
+
+ if lp is None:
+ lp = samba.param.LoadParm()
+ #Load non-existant 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 targetdir is not None:
+ privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
+ lockdir_line = "lock dir = " + os.path.abspath(targetdir)
+
+ lp.set("lock dir", os.path.abspath(targetdir))
+ else:
+ privatedir_line = ""
+ lockdir_line = ""
+
+ if sid_generator == "internal":
+ sid_generator_line = ""
+ 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
+ })
+
+ # reload the smb.conf
+ lp.load(smbconf)
+
+ # and dump it without any values that are the default
+ # 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()
+
+
+
+def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
+ users_gid, wheel_gid):
+ """setup reasonable name mappings for sam names to unix names.
+
+ :param samdb: SamDB object.
+ :param idmap: IDmap db object.
+ :param sid: The domain sid.
+ :param domaindn: The domain DN.
+ :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.
+ """
+ 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)
+
+
+def setup_samdb_partitions(samdb_path, logger, lp, session_info,
+ provision_backend, names, schema, serverrole,
+ erase=False):
+ """Setup the partitions for the SAM database.
+
+ Alternatively, provision() may call this, and then populate the database.
+
+ :note: This will wipe the Sam Database!
+
+ :note: This function always removes the local SAM LDB file. The erase
+ parameter controls whether to erase the existing data, which
+ may not be stored locally but in LDAP.
+
+ """
+ assert session_info is not None
+
+ # We use options=["modules:"] to stop the modules loading - we
+ # just want to wipe and re-initialise the database, not start it up
+
+ try:
+ os.unlink(samdb_path)
+ except OSError:
+ pass
+
+ samdb = Ldb(url=samdb_path, session_info=session_info,
+ lp=lp, options=["modules:"])
+
+ ldap_backend_line = "# No LDAP backend"
+ if provision_backend.type is not "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,
+ })
+
+
+ setup_add_ldif(samdb, setup_path("provision_init.ldif"), {
+ "BACKEND_TYPE": provision_backend.type,
+ "SERVER_ROLE": serverrole
+ })
+
+ logger.info("Setting up sam.ldb rootDSE")
+ setup_samdb_rootdse(samdb, names)
+ except Exception:
+ samdb.transaction_cancel()
+ raise
+ else:
+ samdb.transaction_commit()
+
+
+def secretsdb_self_join(secretsdb, domain,
+ netbiosname, machinepass, domainsid=None,
+ realm=None, dnsdomain=None,
+ keytab_path=None,
+ key_version_number=1,
+ secure_channel_type=SEC_CHAN_WKSTA):
+ """Add domain join-specific bits to a secrets database.
+
+ :param secretsdb: Ldb Handle to the secrets database
+ :param machinepass: Machine password
+ """
+ attrs = ["whenChanged",
+ "secret",
+ "priorSecret",
+ "priorChanged",
+ "krb5Keytab",
+ "privateKeytab"]
+
+ if realm is not None:
+ if dnsdomain is None:
+ dnsdomain = realm.lower()
+ dnsname = '%s.%s' % (netbiosname.lower(), dnsdomain.lower())
+ else:
+ dnsname = None
+ shortname = netbiosname.lower()
+
+ # We don't need to set msg["flatname"] here, because rdn_name will handle
+ # it, and it causes problems for modifies anyway
+ msg = ldb.Message(ldb.Dn(secretsdb, "flatname=%s,cn=Primary Domains" % domain))
+ msg["secureChannelType"] = [str(secure_channel_type)]
+ msg["objectClass"] = ["top", "primaryDomain"]
+ if dnsname is not None:
+ msg["objectClass"] = ["top", "primaryDomain", "kerberosSecret"]
+ msg["realm"] = [realm]
+ msg["saltPrincipal"] = ["host/%s@%s" % (dnsname, realm.upper())]
+ msg["msDS-KeyVersionNumber"] = [str(key_version_number)]
+ msg["privateKeytab"] = ["secrets.keytab"]
+
+ msg["secret"] = [machinepass]
+ msg["samAccountName"] = ["%s$" % netbiosname]
+ msg["secureChannelType"] = [str(secure_channel_type)]
+ if domainsid is not None:
+ msg["objectSid"] = [ndr_pack(domainsid)]
+
+ # This complex expression tries to ensure that we don't have more
+ # than one record for this SID, realm or netbios domain at a time,
+ # 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))),
+ scope=ldb.SCOPE_ONELEVEL)
+
+ for del_msg in res:
+ secretsdb.delete(del_msg.dn)
+
+ res = secretsdb.search(base=msg.dn, attrs=attrs, scope=ldb.SCOPE_BASE)
+
+ if len(res) == 1:
+ msg["priorSecret"] = [res[0]["secret"][0]]
+ msg["priorWhenChanged"] = [res[0]["whenChanged"][0]]
+
+ try:
+ msg["privateKeytab"] = [res[0]["privateKeytab"][0]]
+ except KeyError:
+ pass
+
+ try:
+ msg["krb5Keytab"] = [res[0]["krb5Keytab"][0]]
+ except KeyError:
+ pass
+
+ for el in msg:
+ if el != 'dn':
+ msg[el].set_flags(ldb.FLAG_MOD_REPLACE)
+ secretsdb.modify(msg)
+ secretsdb.rename(res[0].dn, msg.dn)
+ else:
+ spn = [ 'HOST/%s' % shortname ]
+ if secure_channel_type == SEC_CHAN_BDC and dnsname is not None:
+ # we are a domain controller then we add servicePrincipalName
+ # entries for the keytab code to update.
+ spn.extend([ 'HOST/%s' % dnsname ])
+ msg["servicePrincipalName"] = spn
+
+ 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.
+
+ :note: This function does not handle exceptions and transaction on purpose,
+ it's up to the caller to do this job.
+
+ :param path: Path to the secrets database.
+ :param session_info: Session info.
+ :param credentials: Credentials
+ :param lp: Loadparm context
+ :return: LDB handle for the created secrets database
+ """
+ if os.path.exists(paths.secrets):
+ os.unlink(paths.secrets)
+
+ keytab_path = os.path.join(paths.private_dir, paths.keytab)
+ if os.path.exists(keytab_path):
+ os.unlink(keytab_path)
+
+ dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab)
+ if os.path.exists(dns_keytab_path):
+ os.unlink(dns_keytab_path)
+
+ path = paths.secrets
+
+ 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.transaction_start()
+ try:
+ secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
+
+ if (backend_credentials is not None and
+ backend_credentials.authentication_requested()):
+ if backend_credentials.get_bind_dn() is not None:
+ setup_add_ldif(secrets_ldb,
+ setup_path("secrets_simple_ldap.ldif"), {
+ "LDAPMANAGERDN": backend_credentials.get_bind_dn(),
+ "LDAPMANAGERPASS_B64": b64encode(backend_credentials.get_password())
+ })
+ else:
+ setup_add_ldif(secrets_ldb,
+ setup_path("secrets_sasl_ldap.ldif"), {
+ "LDAPADMINUSER": backend_credentials.get_username(),
+ "LDAPADMINREALM": backend_credentials.get_realm(),
+ "LDAPADMINPASS_B64": b64encode(backend_credentials.get_password())
+ })
+
+ return secrets_ldb
+ except Exception:
+ secrets_ldb.transaction_cancel()
+ raise
+
+
+def setup_privileges(path, session_info, lp):
+ """Setup the privileges database.
+
+ :param path: Path to the privileges database.
+ :param session_info: Session info.
+ :param credentials: Credentials
+ :param lp: Loadparm context
+ :return: LDB handle for the created secrets database
+ """
+ if os.path.exists(path):
+ os.unlink(path)
+ privilege_ldb = Ldb(path, session_info=session_info, lp=lp)
+ privilege_ldb.erase()
+ privilege_ldb.load_ldif_file_add(setup_path("provision_privilege.ldif"))
+
+
+def setup_registry(path, session_info, lp):
+ """Setup the registry.
+
+ :param path: Path to the registry database
+ :param session_info: Session information
+ :param credentials: Credentials
+ :param lp: Loadparm context
+ """
+ reg = samba.registry.Registry()
+ hive = samba.registry.open_ldb(path, session_info=session_info, lp_ctx=lp)
+ reg.mount_hive(hive, samba.registry.HKEY_LOCAL_MACHINE)
+ provision_reg = setup_path("provision.reg")
+ assert os.path.exists(provision_reg)
+ reg.diff_apply(provision_reg)
+
+
+def setup_idmapdb(path, session_info, lp):
+ """Setup the idmap database.
+
+ :param path: path to the idmap database
+ :param session_info: Session information
+ :param credentials: Credentials
+ :param lp: Loadparm context
+ """
+ if os.path.exists(path):
+ os.unlink(path)
+
+ idmap_ldb = IDmapDB(path, session_info=session_info, lp=lp)
+ idmap_ldb.erase()
+ idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
+ return idmap_ldb
+
+
+def setup_samdb_rootdse(samdb, names):
+ """Setup the SamDB rootdse.
+
+ :param samdb: Sam Database handle
+ """
+ setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
+ "SCHEMADN": names.schemadn,
+ "DOMAINDN": names.domaindn,
+ "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):
+ """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 = ""
+ setup_add_ldif(samdb, setup_path("provision_self_join.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(next_rid),
+ "SAMBA_VERSION_STRING": version,
+ "NTDSGUID": ntdsguid_line,
+ "DOMAIN_CONTROLLER_FUNCTIONALITY": str(
+ domainControllerFunctionality)})
+
+ setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
+ "POLICYGUID": policyguid,
+ "POLICYGUID_DC": policyguid_dc,
+ "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)
+
+ # Setup fSMORoleOwner entries to point at the newly created DC entry
+ 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"), {
+ "DNSDOMAIN": names.dnsdomain,
+ "DOMAINDN": names.domaindn,
+ "DNSPASS_B64": b64encode(dnspass.encode('utf-16-le')),
+ "HOSTNAME" : names.hostname,
+ "DNSNAME" : '%s.%s' % (
+ names.netbiosname.lower(), names.dnsdomain.lower())
+ })
+
+
+def getpolicypath(sysvolpath, dnsdomain, guid):
+ """Return the physical path of policy given its guid.
+
+ :param sysvolpath: Path to the sysvol folder
+ :param dnsdomain: DNS name of the AD domain
+ :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)
+ return policy_path
+
+
+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")
+ p = os.path.join(policy_path, "MACHINE")
+ if not os.path.exists(p):
+ os.makedirs(p, 0775)
+ p = os.path.join(policy_path, "USER")
+ if not os.path.exists(p):
+ os.makedirs(p, 0775)
+
+
+def create_default_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc):
+ """Create the default GPO for a domain
+
+ :param sysvolpath: Physical path for the sysvol folder
+ :param dnsdomain: DNS domain name of the AD domain
+ :param policyguid: GUID of the default domain policy
+ :param policyguid_dc: GUID of the default domain controler policy
+ """
+ policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid)
+ create_gpo_struct(policy_path)
+
+ policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid_dc)
+ create_gpo_struct(policy_path)
+
+
+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):
+ """Setup a complete SAM Database.
+
+ :note: This will wipe the main SAM database file!
+ """
+
+ # 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.
+ if next_rid < 1000 or next_rid > 1000000000:
+ error = "You want to run SAMBA 4 with a next_rid of %u, " % (next_rid)
+ error += "the valid range is %u-%u. The default is %u." % (
+ 1000, 1000000000, 1000)
+ raise ProvisioningError(error)
+
+ # ATTENTION: Do NOT change these default values without discussion with the
+ # team and/or release manager. They have a big impact on the whole program!
+ domainControllerFunctionality = DS_DOMAIN_FUNCTION_2008_R2
+
+ if dom_for_fun_level is None:
+ dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
+
+ if dom_for_fun_level > domainControllerFunctionality:
+ raise ProvisioningError("You want to run SAMBA 4 on a domain and forest function level which itself is higher than its actual DC function level (2008_R2). This won't work!")
+
+ 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.
+ # Various module (the password_hash module in particular) need
+ # to know what level of AD we are emulating.
+
+ # These will be fixed into the database via the database
+ # modifictions below, but we need them set from the start.
+ samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
+ samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
+ samdb.set_opaque_integer("domainControllerFunctionality",
+ domainControllerFunctionality)
+
+ samdb.set_domain_sid(str(domainsid))
+ samdb.set_invocation_id(invocationid)
+
+ logger.info("Adding DomainDN: %s" % names.domaindn)
+
+ # impersonate domain admin
+ admin_session_info = admin_session(lp, str(domainsid))
+ samdb.set_session_info(admin_session_info)
+ if domainguid is not None:
+ domainguid_line = "objectGUID: %s\n-" % domainguid
+ else:
+ domainguid_line = ""
+
+ descr = b64encode(get_domain_descriptor(domainsid))
+ setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
+ "DOMAINDN": names.domaindn,
+ "DOMAINSID": str(domainsid),
+ "DESCRIPTOR": descr,
+ "DOMAINGUID": domainguid_line
+ })
+
+ setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
+ "DOMAINDN": names.domaindn,
+ "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks
+ "NEXTRID": str(next_rid),
+ "DEFAULTSITE": names.sitename,
+ "CONFIGDN": names.configdn,
+ "POLICYGUID": policyguid,
+ "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
+ "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:
+ 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)
+
+ logger.info("Adding users container")
+ setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
+ "DOMAINDN": names.domaindn})
+ logger.info("Modifying users container")
+ setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
+ "DOMAINDN": names.domaindn})
+ logger.info("Adding computers container")
+ setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
+ "DOMAINDN": names.domaindn})
+ 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")
+ setup_add_ldif(samdb, setup_path("provision.ldif"), {
+ "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks
+ "DOMAINDN": names.domaindn,
+ "NETBIOSNAME": names.netbiosname,
+ "DEFAULTSITE": names.sitename,
+ "CONFIGDN": names.configdn,
+ "SERVERDN": names.serverdn,
+ "RIDAVAILABLESTART": str(next_rid + 600),
+ "POLICYGUID_DC": policyguid_dc
+ })
+
+ setup_modify_ldif(samdb,
+ setup_path("provision_basedn_references.ldif"), {
+ "DOMAINDN": names.domaindn})
+
+ setup_modify_ldif(samdb,
+ setup_path("provision_configuration_references.ldif"), {
+ "CONFIGDN": names.configdn,
+ "SCHEMADN": names.schemadn})
+ if fill == FILL_FULL:
+ 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,
+ dnspass=dnspass,
+ machinepass=machinepass,
+ domainsid=domainsid,
+ next_rid=next_rid,
+ policyguid=policyguid,
+ policyguid_dc=policyguid_dc,
+ domainControllerFunctionality=domainControllerFunctionality,
+ ntdsguid=ntdsguid)
+
+ 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)
+ except Exception:
+ samdb.transaction_cancel()
+ raise
+ else:
+ samdb.transaction_commit()
+ return samdb
+
+
+FILL_FULL = "FULL"
+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)
+ for root, dirs, files in os.walk(path, topdown=False):
+ for name in files:
+ setntacl(lp, os.path.join(root, name), acl, domsid)
+ for name in dirs:
+ setntacl(lp, os.path.join(root, name), acl, domsid)
+
+
+def set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp):
+ """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
+ """
+
+ # Set ACL for GPO root folder
+ root_policy_path = os.path.join(sysvol, dnsdomain, "Policies")
+ setntacl(lp, root_policy_path, POLICIES_ACL, str(domainsid))
+
+ 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"]))
+ set_dir_acl(policy_path, dsacl2fsacl(acl, str(domainsid)), lp,
+ str(domainsid))
+
+
+def setsysvolacl(samdb, netlogon, sysvol, gid, 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 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=...)
+ """
+
+ try:
+ 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))
+ for root, dirs, files in os.walk(sysvol, topdown=False):
+ for name in files:
+ if canchown:
+ os.chown(os.path.join(root, name), -1, gid)
+ setntacl(lp, os.path.join(root, name), SYSVOL_ACL, str(domainsid))
+ for name in dirs:
+ if canchown:
+ os.chown(os.path.join(root, name), -1, gid)
+ setntacl(lp, os.path.join(root, name), SYSVOL_ACL, str(domainsid))
+
+ # Set acls on Policy folder and policies folders
+ set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp)
+
+
+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
+
+ :note: caution, this wipes all existing data!
+ """
+
+ if domainsid is None:
+ domainsid = security.random_sid()
+ else:
+ domainsid = security.dom_sid(domainsid)
+
+ # create/adapt the group policy GUIDs
+ # Default GUID for default policy are described at
+ # "How Core Group Policy Works"
+ # http://technet.microsoft.com/en-us/library/cc784268%28WS.10%29.aspx
+ if policyguid is None:
+ policyguid = DEFAULT_POLICY_GUID
+ policyguid = policyguid.upper()
+ if policyguid_dc is None:
+ policyguid_dc = DEFAULT_DC_POLICY_GUID
+ policyguid_dc = policyguid_dc.upper()
+
+ if adminpass is None:
+ adminpass = samba.generate_random_password(12, 32)
+ 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)
+ if ldapadminpass is None:
+ # Make a new, random password between Samba and it's LDAP server
+ 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"
+
+ 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])
+ try:
+ bind_gid = findnss_gid(["bind", "named"])
+ except KeyError:
+ bind_gid = None
+
+ if targetdir is not None:
+ smbconf = os.path.join(targetdir, "etc", "smb.conf")
+ elif smbconf is None:
+ smbconf = samba.param.default_path()
+ if not os.path.exists(os.path.dirname(smbconf)):
+ os.makedirs(os.path.dirname(smbconf))
+
+ # 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()
+ if data is None or data == "":
+ make_smbconf(smbconf, hostname, domain, realm,
+ serverrole, targetdir, sid_generator, useeadb,
+ lp=lp)
+ else:
+ make_smbconf(smbconf, hostname, domain, realm, serverrole,
+ targetdir, sid_generator, useeadb, lp=lp)
+
+ if lp is None:
+ lp = samba.param.LoadParm()
+ lp.load(smbconf)
+ names = guess_names(lp=lp, hostname=hostname, domain=domain,
+ dnsdomain=realm, serverrole=serverrole, domaindn=domaindn,
+ configdn=configdn, schemadn=schemadn, serverdn=serverdn,
+ sitename=sitename)
+ paths = provision_paths_from_lp(lp, names.dnsdomain)
+
+ paths.bind_gid = bind_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:
+ hostip = hostips[0]
+ if len(hostips) > 1:
+ logger.warning("More than one IPv4 address found. Using %s.",
+ hostip)
+
+ 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"))
+
+ ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
+
+ schema = Schema(domainsid, invocationid=invocationid,
+ schemadn=names.schemadn)
+
+ if backend_type == "ldb":
+ provision_backend = LDBBackend(backend_type, paths=paths,
+ lp=lp, credentials=credentials,
+ names=names, logger=logger)
+ elif backend_type == "existing":
+ provision_backend = ExistingBackend(backend_type, paths=paths,
+ lp=lp, credentials=credentials,
+ names=names, logger=logger,
+ ldap_backend_forced_uri=ldap_backend_forced_uri)
+ 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)
+ 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)
+ else:
+ raise ValueError("Unknown LDAP backend type selected")
+
+ provision_backend.init()
+ provision_backend.start()
+
+ # 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.load_ldif_file_add(setup_path("share.ldif"))
+
+ logger.info("Setting up secrets.ldb")
+ secrets_ldb = setup_secretsdb(paths,
+ session_info=session_info,
+ backend_credentials=provision_backend.secrets_credentials, lp=lp)
+
+ try:
+ logger.info("Setting up the registry")
+ 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)
+
+ 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":
+ 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
+
+ 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
+
+ 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)
+
+ 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)
+
+ create_krb5_conf(paths.krb5conf,
+ dnsdomain=names.dnsdomain, hostname=names.hostname,
+ realm=names.realm)
+ logger.info("A Kerberos configuration suitable for Samba 4 has been "
+ "generated at %s", paths.krb5conf)
+
+ if serverrole == "domain controller":
+ create_dns_update_list(lp, logger, paths)
+
+ provision_backend.post_setup()
+ provision_backend.shutdown()
+
+ create_phpldapadmin_config(paths.phpldapadminconfig,
+ ldapi_url)
+ except Exception:
+ secrets_ldb.transaction_cancel()
+ raise
+
+ # Now commit the secrets.ldb to disk
+ secrets_ldb.transaction_commit()
+
+ # the commit creates the dns.keytab, now chown it
+ dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab)
+ if os.path.isfile(dns_keytab_path) and paths.bind_gid is not None:
+ try:
+ os.chmod(dns_keytab_path, 0640)
+ os.chown(dns_keytab_path, -1, paths.bind_gid)
+ except OSError:
+ if not os.environ.has_key('SAMBA_SELFTEST'):
+ 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.domaindn = domaindn
+ result.paths = paths
+ result.lp = lp
+ result.samdb = samdb
+ return result
+
+
+def provision_become_dc(smbconf=None, targetdir=None,
+ realm=None, rootdn=None, domaindn=None, schemadn=None, configdn=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):
+
+ logger = logging.getLogger("provision")
+ samba.set_debug_level(debuglevel)
+
+ res = provision(logger, system_session(), 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)
+ 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).
+
+ :param path: Path of the new named.conf file.
+ :param dnsdomain: DNS Domain name
+ :param hostname: Local hostname
+ :param realm: Realm name
+ """
+ setup_file(setup_path("krb5.conf"), path, {
+ "DNSDOMAIN": dnsdomain,
+ "HOSTNAME": hostname,
+ "REALM": realm,
+ })
+
+
+class ProvisioningError(Exception):
+ """A generic provision error."""
+
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return "ProvisioningError: " + self.value
+
+
+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)
diff --git a/source4/scripting/python/samba/provision/backend.py b/source4/scripting/python/samba/provision/backend.py
new file mode 100644
index 0000000000..f9dbba85f6
--- /dev/null
+++ b/source4/scripting/python/samba/provision/backend.py
@@ -0,0 +1,772 @@
+#
+# Unix SMB/CIFS implementation.
+# backend code for provisioning a Samba4 server
+
+# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
+# 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 (LDB and LDAP backends)."""
+
+from base64 import b64encode
+import errno
+import ldb
+import os
+import sys
+import uuid
+import time
+import shutil
+import subprocess
+import urllib
+
+from ldb import SCOPE_BASE, SCOPE_ONELEVEL, LdbError, timestring
+
+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):
+ self.ldapi_uri = uri
+ super(SlapdAlreadyRunning, self).__init__("Another slapd Instance "
+ "seems already running on this host, listening to %s." %
+ self.ldapi_uri)
+
+
+class ProvisionBackend(object):
+ def __init__(self, backend_type, paths=None, lp=None,
+ credentials=None, names=None, logger=None):
+ """Provision a backend for samba4"""
+ self.paths = paths
+ self.lp = lp
+ self.credentials = credentials
+ self.names = names
+ self.logger = logger
+
+ self.type = backend_type
+
+ # Set a default - the code for "existing" below replaces this
+ self.ldap_backend_type = backend_type
+
+ def init(self):
+ """Initialize the backend."""
+ raise NotImplementedError(self.init)
+
+ def start(self):
+ """Start the backend."""
+ raise NotImplementedError(self.start)
+
+ def shutdown(self):
+ """Shutdown the backend."""
+ raise NotImplementedError(self.shutdown)
+
+ def post_setup(self):
+ """Post setup."""
+ raise NotImplementedError(self.post_setup)
+
+
+class LDBBackend(ProvisionBackend):
+
+ def init(self):
+ self.credentials = None
+ self.secrets_credentials = None
+
+ # Wipe the old sam.ldb databases away
+ shutil.rmtree(self.paths.samdb + ".d", True)
+
+ def start(self):
+ pass
+
+ def shutdown(self):
+ pass
+
+ def post_setup(self):
+ pass
+
+
+class ExistingBackend(ProvisionBackend):
+
+ def __init__(self, backend_type, paths=None, lp=None,
+ credentials=None, names=None, logger=None, ldapi_uri=None):
+
+ super(ExistingBackend, self).__init__(backend_type=backend_type,
+ paths=paths, lp=lp,
+ credentials=credentials, names=names, logger=logger,
+ ldap_backend_forced_uri=ldapi_uri)
+
+ def init(self):
+ # Check to see that this 'existing' LDAP backend in fact exists
+ ldapi_db = Ldb(self.ldapi_uri, credentials=self.credentials)
+ ldapi_db.search(base="", scope=SCOPE_BASE,
+ expression="(objectClass=OpenLDAProotDSE)")
+
+ # If we have got here, then we must have a valid connection to the LDAP
+ # server, with valid credentials supplied This caused them to be set
+ # into the long-term database later in the script.
+ self.secrets_credentials = self.credentials
+
+ # For now, assume existing backends at least emulate OpenLDAP
+ self.ldap_backend_type = "openldap"
+
+
+class LDAPBackend(ProvisionBackend):
+
+ 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_backend_forced_uri=None, ldap_dryrun_mode=False):
+
+ super(LDAPBackend, self).__init__(backend_type=backend_type,
+ paths=paths, lp=lp,
+ credentials=credentials, names=names, logger=logger)
+
+ self.domainsid = domainsid
+ self.schema = schema
+ self.hostname = hostname
+
+ self.ldapdir = os.path.join(paths.private_dir, "ldap")
+ self.ldapadminpass = ldapadminpass
+
+ self.slapd_path = slapd_path
+ self.slapd_command = None
+ self.slapd_command_escaped = None
+ self.slapd_pid = os.path.join(self.ldapdir, "slapd.pid")
+
+ self.ldap_backend_extra_port = ldap_backend_extra_port
+ self.ldap_dryrun_mode = ldap_dryrun_mode
+
+ if ldap_backend_forced_uri is not None:
+ self.ldap_uri = ldap_backend_forced_uri
+ else:
+ self.ldap_uri = "ldapi://%s" % urllib.quote(
+ os.path.join(self.ldapdir, "ldapi"), safe="")
+
+ if not os.path.exists(self.ldapdir):
+ os.mkdir(self.ldapdir)
+
+ def init(self):
+ from samba.provision import ProvisioningError
+ # we will shortly start slapd with ldapi for final provisioning. first
+ # check with ldapsearch -> rootDSE via self.ldap_uri if another
+ # instance of slapd is already running
+ try:
+ ldapi_db = Ldb(self.ldap_uri)
+ ldapi_db.search(base="", scope=SCOPE_BASE,
+ expression="(objectClass=OpenLDAProotDSE)")
+ try:
+ f = open(self.slapd_pid, "r")
+ except IOError, err:
+ 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)
+ raise SlapdAlreadyRunning(self.ldap_uri)
+ except LdbError:
+ # XXX: We should never be catching all Ldb errors
+ pass
+
+ # Try to print helpful messages when the user has not specified the
+ # path to slapd
+ if self.slapd_path is None:
+ raise ProvisioningError("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!")
+ if not os.path.exists(self.slapd_path):
+ self.logger.warning("Path (%s) to slapd does not exist!",
+ self.slapd_path)
+
+ if not os.path.isdir(self.ldapdir):
+ os.makedirs(self.ldapdir, 0700)
+
+ # Put the LDIF of the schema into a database so we can search on
+ # it to generate schema-dependent configurations in Fedora DS and
+ # OpenLDAP
+ schemadb_path = os.path.join(self.ldapdir, "schema-tmp.ldb")
+ try:
+ os.unlink(schemadb_path)
+ except OSError:
+ pass
+
+ self.schema.write_to_tmp_ldb(schemadb_path)
+
+ self.credentials = Credentials()
+ self.credentials.guess(self.lp)
+ # Kerberos to an ldapi:// backend makes no sense
+ self.credentials.set_kerberos_state(DONT_USE_KERBEROS)
+ self.credentials.set_password(self.ldapadminpass)
+
+ self.secrets_credentials = Credentials()
+ self.secrets_credentials.guess(self.lp)
+ # Kerberos to an ldapi:// backend makes no sense
+ self.secrets_credentials.set_kerberos_state(DONT_USE_KERBEROS)
+ self.secrets_credentials.set_username("samba-admin")
+ self.secrets_credentials.set_password(self.ldapadminpass)
+
+ self.provision()
+
+ def provision(self):
+ pass
+
+ def start(self):
+ from samba.provision import ProvisioningError
+ self.slapd_command_escaped = "\'" + "\' \'".join(self.slapd_command) + "\'"
+ f = open(os.path.join(self.ldapdir, "ldap_backend_startup.sh"), 'w')
+ try:
+ f.write("#!/bin/sh\n" + self.slapd_command_escaped + "\n")
+ finally:
+ f.close()
+
+ # Now start the slapd, so we can provision onto it. We keep the
+ # subprocess context around, to kill this off at the successful
+ # end of the script
+ self.slapd = subprocess.Popen(self.slapd_provision_command,
+ close_fds=True, shell=False)
+
+ count = 0
+ while self.slapd.poll() is None:
+ # Wait until the socket appears
+ try:
+ ldapi_db = Ldb(self.ldap_uri, lp=self.lp, credentials=self.credentials)
+ ldapi_db.search(base="", scope=SCOPE_BASE,
+ expression="(objectClass=OpenLDAProotDSE)")
+ # If we have got here, then we must have a valid connection to
+ # the LDAP server!
+ return
+ except LdbError:
+ time.sleep(1)
+ count = count + 1
+
+ if count > 15:
+ self.logger.error("Could not connect to slapd started with: %s" % "\'" + "\' \'".join(self.slapd_provision_command) + "\'")
+ raise ProvisioningError("slapd never accepted a connection within 15 seconds of starting")
+
+ self.logger.error("Could not start slapd with: %s" % "\'" + "\' \'".join(self.slapd_provision_command) + "\'")
+ raise ProvisioningError("slapd died before we could make a connection to it")
+
+ def shutdown(self):
+ # if an LDAP backend is in use, terminate slapd after final provision
+ # and check its proper termination
+ if self.slapd.poll() is None:
+ # Kill the slapd
+ if getattr(self.slapd, "terminate", None) is not None:
+ self.slapd.terminate()
+ else:
+ # Older python versions don't have .terminate()
+ import signal
+ os.kill(self.slapd.pid, signal.SIGTERM)
+
+ # and now wait for it to die
+ self.slapd.communicate()
+
+ def post_setup(self):
+ pass
+
+
+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,
+ 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,
+ 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_backend_forced_uri=ldap_backend_forced_uri,
+ ldap_dryrun_mode=ldap_dryrun_mode)
+
+ self.ol_mmr_urls = ol_mmr_urls
+ self.nosync = nosync
+
+ self.slapdconf = os.path.join(self.ldapdir, "slapd.conf")
+ self.modulesconf = os.path.join(self.ldapdir, "modules.conf")
+ self.memberofconf = os.path.join(self.ldapdir, "memberof.conf")
+ self.olmmrserveridsconf = os.path.join(self.ldapdir, "mmr_serverids.conf")
+ self.olmmrsyncreplconf = os.path.join(self.ldapdir, "mmr_syncrepl.conf")
+ self.olcdir = os.path.join(self.ldapdir, "slapd.d")
+ self.olcseedldif = os.path.join(self.ldapdir, "olc_seed.ldif")
+
+ self.schema = Schema(self.domainsid,
+ schemadn=self.names.schemadn, files=[
+ setup_path("schema_samba4.ldif")])
+
+ def setup_db_config(self, dbdir):
+ """Setup a Berkeley database.
+
+ :param dbdir: Database directory.
+ """
+ from samba.provision import setup_path
+ if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
+ os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
+ if not os.path.isdir(os.path.join(dbdir, "tmp")):
+ os.makedirs(os.path.join(dbdir, "tmp"), 0700)
+
+ setup_file(setup_path("DB_CONFIG"),
+ os.path.join(dbdir, "DB_CONFIG"), {"LDAPDBDIR": dbdir})
+
+ def provision(self):
+ from samba.provision import ProvisioningError, setup_path
+ # Wipe the directories so we can start
+ shutil.rmtree(os.path.join(self.ldapdir, "db"), True)
+
+ # Allow the test scripts to turn off fsync() for OpenLDAP as for TDB
+ # and LDB
+ nosync_config = ""
+ if self.nosync:
+ nosync_config = "dbnosync"
+
+ lnkattr = self.schema.linked_attributes()
+ refint_attributes = ""
+ memberof_config = "# Generated from Samba4 schema\n"
+ for att in lnkattr.keys():
+ if lnkattr[att] is not None:
+ refint_attributes = refint_attributes + " " + att
+
+ memberof_config += read_and_sub_file(
+ setup_path("memberof.conf"), {
+ "MEMBER_ATTR": att,
+ "MEMBEROF_ATTR" : lnkattr[att] })
+
+ refint_config = read_and_sub_file(setup_path("refint.conf"),
+ { "LINK_ATTRS" : refint_attributes})
+
+ attrs = ["linkID", "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)
+ index_config = ""
+ for i in range (0, len(res)):
+ index_attr = res[i]["lDAPDisplayName"][0]
+ if index_attr == "objectGUID":
+ index_attr = "entryUUID"
+
+ index_config += "index " + index_attr + " eq\n"
+
+ # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
+ mmr_on_config = ""
+ mmr_replicator_acl = ""
+ mmr_serverids_config = ""
+ mmr_syncrepl_schema_config = ""
+ mmr_syncrepl_config_config = ""
+ mmr_syncrepl_user_config = ""
+
+ if self.ol_mmr_urls is not None:
+ # For now, make these equal
+ mmr_pass = self.ldapadminpass
+
+ url_list = filter(None,self.ol_mmr_urls.split(','))
+ for url in url_list:
+ self.logger.info("Using LDAP-URL: "+url)
+ if len(url_list) == 1:
+ raise ProvisioningError("At least 2 LDAP-URLs needed for MMR!")
+
+ mmr_on_config = "MirrorMode On"
+ mmr_replicator_acl = " by dn=cn=replicator,cn=samba read"
+ serverid = 0
+ for url in url_list:
+ serverid = serverid + 1
+ mmr_serverids_config += read_and_sub_file(
+ setup_path("mmr_serverids.conf"), {
+ "SERVERID": str(serverid),
+ "LDAPSERVER": url })
+ rid = serverid * 10
+ rid = rid + 1
+ mmr_syncrepl_schema_config += read_and_sub_file(
+ setup_path("mmr_syncrepl.conf"), {
+ "RID" : str(rid),
+ "MMRDN": self.names.schemadn,
+ "LDAPSERVER" : url,
+ "MMR_PASSWORD": mmr_pass})
+
+ rid = rid + 1
+ mmr_syncrepl_config_config += read_and_sub_file(
+ setup_path("mmr_syncrepl.conf"), {
+ "RID" : str(rid),
+ "MMRDN": self.names.configdn,
+ "LDAPSERVER" : url,
+ "MMR_PASSWORD": mmr_pass})
+
+ rid = rid + 1
+ mmr_syncrepl_user_config += read_and_sub_file(
+ setup_path("mmr_syncrepl.conf"), {
+ "RID" : str(rid),
+ "MMRDN": self.names.domaindn,
+ "LDAPSERVER" : url,
+ "MMR_PASSWORD": mmr_pass })
+ # OpenLDAP cn=config initialisation
+ olc_syncrepl_config = ""
+ olc_mmr_config = ""
+ # if mmr = yes, generate cn=config-replication directives
+ # and olc_seed.lif for the other mmr-servers
+ if self.ol_mmr_urls is not None:
+ serverid = 0
+ olc_serverids_config = ""
+ olc_syncrepl_seed_config = ""
+ olc_mmr_config += read_and_sub_file(
+ setup_path("olc_mmr.conf"), {})
+ rid = 500
+ for url in url_list:
+ serverid = serverid + 1
+ olc_serverids_config += read_and_sub_file(
+ setup_path("olc_serverid.conf"), {
+ "SERVERID" : str(serverid), "LDAPSERVER" : url })
+
+ rid = rid + 1
+ olc_syncrepl_config += read_and_sub_file(
+ setup_path("olc_syncrepl.conf"), {
+ "RID" : str(rid), "LDAPSERVER" : url,
+ "MMR_PASSWORD": mmr_pass})
+
+ olc_syncrepl_seed_config += read_and_sub_file(
+ setup_path("olc_syncrepl_seed.conf"), {
+ "RID" : str(rid), "LDAPSERVER" : url})
+
+ setup_file(setup_path("olc_seed.ldif"), self.olcseedldif,
+ {"OLC_SERVER_ID_CONF": olc_serverids_config,
+ "OLC_PW": self.ldapadminpass,
+ "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
+ # end olc
+
+ setup_file(setup_path("slapd.conf"), self.slapdconf,
+ {"DNSDOMAIN": self.names.dnsdomain,
+ "LDAPDIR": self.ldapdir,
+ "DOMAINDN": self.names.domaindn,
+ "CONFIGDN": self.names.configdn,
+ "SCHEMADN": self.names.schemadn,
+ "MEMBEROF_CONFIG": memberof_config,
+ "MIRRORMODE": mmr_on_config,
+ "REPLICATOR_ACL": mmr_replicator_acl,
+ "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
+ "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
+ "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
+ "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
+ "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
+ "OLC_MMR_CONFIG": olc_mmr_config,
+ "REFINT_CONFIG": refint_config,
+ "INDEX_CONFIG": index_config,
+ "NOSYNC": nosync_config})
+
+ self.setup_db_config(os.path.join(self.ldapdir, "db", "user"))
+ self.setup_db_config(os.path.join(self.ldapdir, "db", "config"))
+ self.setup_db_config(os.path.join(self.ldapdir, "db", "schema"))
+
+ if not os.path.exists(os.path.join(self.ldapdir, "db", "samba", "cn=samba")):
+ os.makedirs(os.path.join(self.ldapdir, "db", "samba", "cn=samba"), 0700)
+
+ setup_file(setup_path("cn=samba.ldif"),
+ os.path.join(self.ldapdir, "db", "samba", "cn=samba.ldif"),
+ { "UUID": str(uuid.uuid4()),
+ "LDAPTIME": timestring(int(time.time()))} )
+ setup_file(setup_path("cn=samba-admin.ldif"),
+ os.path.join(self.ldapdir, "db", "samba", "cn=samba", "cn=samba-admin.ldif"),
+ {"LDAPADMINPASS_B64": b64encode(self.ldapadminpass),
+ "UUID": str(uuid.uuid4()),
+ "LDAPTIME": timestring(int(time.time()))} )
+
+ if self.ol_mmr_urls is not None:
+ setup_file(setup_path("cn=replicator.ldif"),
+ os.path.join(self.ldapdir, "db", "samba", "cn=samba", "cn=replicator.ldif"),
+ {"MMR_PASSWORD_B64": b64encode(mmr_pass),
+ "UUID": str(uuid.uuid4()),
+ "LDAPTIME": timestring(int(time.time()))} )
+
+ mapping = "schema-map-openldap-2.3"
+ backend_schema = "backend-schema.schema"
+
+ f = open(setup_path(mapping), 'r')
+ backend_schema_data = self.schema.convert_to_openldap(
+ "openldap", f.read())
+ assert backend_schema_data is not None
+ f = open(os.path.join(self.ldapdir, backend_schema), 'w')
+ try:
+ f.write(backend_schema_data)
+ finally:
+ f.close()
+
+ # now we generate the needed strings to start slapd automatically,
+ if self.ldap_backend_extra_port is not None:
+ # When we use MMR, we can't use 0.0.0.0 as it uses the name
+ # specified there as part of it's clue as to it's own name,
+ # and not to replicate to itself
+ if self.ol_mmr_urls is None:
+ server_port_string = "ldap://0.0.0.0:%d" % self.ldap_backend_extra_port
+ else:
+ server_port_string = "ldap://%s.%s:%d" (self.names.hostname,
+ self.names.dnsdomain, self.ldap_backend_extra_port)
+ else:
+ server_port_string = ""
+
+ # Prepare the 'result' information - the commands to return in
+ # particular
+ self.slapd_provision_command = [self.slapd_path, "-F" + self.olcdir,
+ "-h"]
+
+ # copy this command so we have two version, one with -d0 and only
+ # ldapi (or the forced ldap_uri), and one with all the listen commands
+ self.slapd_command = list(self.slapd_provision_command)
+
+ self.slapd_provision_command.extend([self.ldap_uri, "-d0"])
+
+ uris = self.ldap_uri
+ if server_port_string is not "":
+ uris = uris + " " + server_port_string
+
+ self.slapd_command.append(uris)
+
+ # Set the username - done here because Fedora DS still uses the admin
+ # DN and simple bind
+ self.credentials.set_username("samba-admin")
+
+ # Wipe the old sam.ldb databases away
+ shutil.rmtree(self.olcdir, True)
+ os.makedirs(self.olcdir, 0770)
+
+ # If we were just looking for crashes up to this point, it's a
+ # good time to exit before we realise we don't have OpenLDAP on
+ # this system
+ if self.ldap_dryrun_mode:
+ sys.exit(0)
+
+ slapd_cmd = [self.slapd_path, "-Ttest", "-n", "0", "-f",
+ self.slapdconf, "-F", self.olcdir]
+ retcode = subprocess.call(slapd_cmd, close_fds=True, shell=False)
+
+ if retcode != 0:
+ self.logger.error("conversion from slapd.conf to cn=config failed slapd started with: %s" % "\'" + "\' \'".join(slapd_cmd) + "\'")
+ raise ProvisioningError("conversion from slapd.conf to cn=config failed")
+
+ if not os.path.exists(os.path.join(self.olcdir, "cn=config.ldif")):
+ raise ProvisioningError("conversion from slapd.conf to cn=config failed")
+
+ # Don't confuse the admin by leaving the slapd.conf around
+ os.remove(self.slapdconf)
+
+
+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,
+ setup_ds_path=None):
+
+ from samba.provision import setup_path
+
+ super(FDSBackend, self).__init__(backend_type=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_backend_forced_uri=ldap_backend_forced_uri,
+ ldap_dryrun_mode=ldap_dryrun_mode)
+
+ self.root = root
+ self.setup_ds_path = setup_ds_path
+ self.ldap_instance = self.names.netbiosname.lower()
+
+ self.sambadn = "CN=Samba"
+
+ self.fedoradsinf = os.path.join(self.ldapdir, "fedorads.inf")
+ self.partitions_ldif = os.path.join(self.ldapdir,
+ "fedorads-partitions.ldif")
+ self.sasl_ldif = os.path.join(self.ldapdir, "fedorads-sasl.ldif")
+ self.dna_ldif = os.path.join(self.ldapdir, "fedorads-dna.ldif")
+ self.pam_ldif = os.path.join(self.ldapdir, "fedorads-pam.ldif")
+ self.refint_ldif = os.path.join(self.ldapdir, "fedorads-refint.ldif")
+ self.linked_attrs_ldif = os.path.join(self.ldapdir,
+ "fedorads-linked-attributes.ldif")
+ self.index_ldif = os.path.join(self.ldapdir, "fedorads-index.ldif")
+ self.samba_ldif = os.path.join(self.ldapdir, "fedorads-samba.ldif")
+
+ self.samba3_schema = setup_path(
+ "../../examples/LDAP/samba.schema")
+ self.samba3_ldif = os.path.join(self.ldapdir, "samba3.ldif")
+
+ self.retcode = subprocess.call(["bin/oLschema2ldif",
+ "-I", self.samba3_schema,
+ "-O", self.samba3_ldif,
+ "-b", self.names.domaindn],
+ close_fds=True, shell=False)
+
+ if self.retcode != 0:
+ raise Exception("Unable to convert Samba 3 schema.")
+
+ self.schema = Schema(
+ self.domainsid,
+ schemadn=self.names.schemadn,
+ files=[setup_path("schema_samba4.ldif"), self.samba3_ldif],
+ additional_prefixmap=["1000:1.3.6.1.4.1.7165.2.1",
+ "1001:1.3.6.1.4.1.7165.2.2"])
+
+ def provision(self):
+ from samba.provision import ProvisioningError, setup_path
+ if self.ldap_backend_extra_port is not None:
+ serverport = "ServerPort=%d" % self.ldap_backend_extra_port
+ else:
+ serverport = ""
+
+ setup_file(setup_path("fedorads.inf"), self.fedoradsinf,
+ {"ROOT": self.root,
+ "HOSTNAME": self.hostname,
+ "DNSDOMAIN": self.names.dnsdomain,
+ "LDAPDIR": self.ldapdir,
+ "DOMAINDN": self.names.domaindn,
+ "LDAP_INSTANCE": self.ldap_instance,
+ "LDAPMANAGERDN": self.names.ldapmanagerdn,
+ "LDAPMANAGERPASS": self.ldapadminpass,
+ "SERVERPORT": serverport})
+
+ setup_file(setup_path("fedorads-partitions.ldif"),
+ self.partitions_ldif,
+ {"CONFIGDN": self.names.configdn,
+ "SCHEMADN": self.names.schemadn,
+ "SAMBADN": self.sambadn,
+ })
+
+ setup_file(setup_path("fedorads-sasl.ldif"), self.sasl_ldif,
+ {"SAMBADN": self.sambadn,
+ })
+
+ setup_file(setup_path("fedorads-dna.ldif"), self.dna_ldif,
+ {"DOMAINDN": self.names.domaindn,
+ "SAMBADN": self.sambadn,
+ "DOMAINSID": str(self.domainsid),
+ })
+
+ setup_file(setup_path("fedorads-pam.ldif"), self.pam_ldif)
+
+ lnkattr = self.schema.linked_attributes()
+
+ refint_config = open(setup_path("fedorads-refint-delete.ldif"), 'r').read()
+ memberof_config = ""
+ index_config = ""
+ argnum = 3
+
+ for attr in lnkattr.keys():
+ if lnkattr[attr] is not None:
+ refint_config += read_and_sub_file(
+ setup_path("fedorads-refint-add.ldif"),
+ { "ARG_NUMBER" : str(argnum),
+ "LINK_ATTR" : attr })
+ memberof_config += read_and_sub_file(
+ setup_path("fedorads-linked-attributes.ldif"),
+ { "MEMBER_ATTR" : attr,
+ "MEMBEROF_ATTR" : lnkattr[attr] })
+ index_config += read_and_sub_file(
+ 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)
+
+ 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)
+
+ for i in range (0, len(res)):
+ attr = res[i]["lDAPDisplayName"][0]
+
+ if attr == "objectGUID":
+ attr = "nsUniqueId"
+
+ index_config += read_and_sub_file(
+ setup_path("fedorads-index.ldif"), { "ATTR" : attr })
+
+ open(self.index_ldif, 'w').write(index_config)
+
+ setup_file(setup_path("fedorads-samba.ldif"), self.samba_ldif, {
+ "SAMBADN": self.sambadn,
+ "LDAPADMINPASS": self.ldapadminpass
+ })
+
+ mapping = "schema-map-fedora-ds-1.0"
+ 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())
+ assert backend_schema_data is not None
+ f = open(os.path.join(self.ldapdir, backend_schema), 'w')
+ try:
+ f.write(backend_schema_data)
+ finally:
+ f.close()
+
+ self.credentials.set_bind_dn(self.names.ldapmanagerdn)
+
+ # Destory the target directory, or else setup-ds.pl will complain
+ fedora_ds_dir = os.path.join(self.ldapdir,
+ "slapd-" + self.ldap_instance)
+ shutil.rmtree(fedora_ds_dir, True)
+
+ self.slapd_provision_command = [self.slapd_path, "-D", fedora_ds_dir,
+ "-i", self.slapd_pid]
+ # In the 'provision' command line, stay in the foreground so we can
+ # easily kill it
+ self.slapd_provision_command.append("-d0")
+
+ #the command for the final run is the normal script
+ self.slapd_command = [os.path.join(self.ldapdir,
+ "slapd-" + self.ldap_instance, "start-slapd")]
+
+ # If we were just looking for crashes up to this point, it's a
+ # good time to exit before we realise we don't have Fedora DS on
+ if self.ldap_dryrun_mode:
+ sys.exit(0)
+
+ # Try to print helpful messages when the user has not specified the
+ # path to the setup-ds tool
+ if self.setup_ds_path is None:
+ raise ProvisioningError("Fedora DS LDAP-Backend must be setup with path to setup-ds, e.g. --setup-ds-path=\"/usr/sbin/setup-ds.pl\"!")
+ if not os.path.exists(self.setup_ds_path):
+ self.logger.warning("Path (%s) to slapd does not exist!",
+ self.setup_ds_path)
+
+ # Run the Fedora DS setup utility
+ retcode = subprocess.call([self.setup_ds_path, "--silent", "--file",
+ self.fedoradsinf], close_fds=True, shell=False)
+ if retcode != 0:
+ raise ProvisioningError("setup-ds failed")
+
+ # Load samba-admin
+ retcode = subprocess.call([
+ os.path.join(self.ldapdir, "slapd-" + self.ldap_instance, "ldif2db"), "-s", self.sambadn, "-i", self.samba_ldif],
+ close_fds=True, shell=False)
+ if retcode != 0:
+ raise ProvisioningError("ldif2db failed")
+
+ def post_setup(self):
+ ldapi_db = Ldb(self.ldap_uri, credentials=self.credentials)
+
+ # configure in-directory access control on Fedora DS via the aci
+ # attribute (over a direct ldapi:// socket)
+ aci = """(targetattr = "*") (version 3.0;acl "full access to all by samba-admin";allow (all)(userdn = "ldap:///CN=samba-admin,%s");)""" % self.sambadn
+
+ m = ldb.Message()
+ m["aci"] = ldb.MessageElement([aci], ldb.FLAG_MOD_REPLACE, "aci")
+
+ for dnstring in (self.names.domaindn, self.names.configdn,
+ self.names.schemadn):
+ m.dn = ldb.Dn(ldapi_db, dnstring)
+ ldapi_db.modify(m)
diff --git a/source4/scripting/python/samba/samba3.py b/source4/scripting/python/samba/samba3.py
index 179efa2700..2c323bd0b4 100644
--- a/source4/scripting/python/samba/samba3.py
+++ b/source4/scripting/python/samba/samba3.py
@@ -1,18 +1,16 @@
-#!/usr/bin/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/>.
#
@@ -67,7 +65,7 @@ class TdbDatabase(object):
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.
@@ -683,7 +681,7 @@ class ParamFile(object):
section = None
for i, l in enumerate(open(filename, 'r').xreadlines()):
l = l.strip()
- if not l:
+ if not l or l[0] == '#' or l[0] == ';':
continue
if l[0] == "[" and l[-1] == "]":
section = self._sanitize_name(l[1:-1])
@@ -769,7 +767,7 @@ class Samba3(object):
def get_policy_db(self):
return PolicyDatabase(self.libdir_path("account_policy.tdb"))
-
+
def get_registry(self):
return Registry(self.libdir_path("registry.tdb"))
diff --git a/source4/scripting/python/samba/samdb.py b/source4/scripting/python/samba/samdb.py
index 39cf1d6c40..99f141e664 100644
--- a/source4/scripting/python/samba/samdb.py
+++ b/source4/scripting/python/samba/samdb.py
@@ -1,22 +1,22 @@
-#!/usr/bin/python
+#!/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-2010
# Copyright (C) Matthias Dieter Wallnoefer 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/>.
#
@@ -24,62 +24,71 @@
"""Convenience functions for using the SAM."""
import samba
-import glue
import ldb
-from samba.idmap import IDmapDB
-import pwd
import time
import base64
+from samba import dsdb
+from samba.ndr import ndr_unpack, ndr_pack
+from samba.dcerpc import drsblobs, misc
__docformat__ = "restructuredText"
+
class SamDB(samba.Ldb):
"""The SAM database."""
- def __init__(self, url=None, lp=None, modules_dir=None, session_info=None,
- credentials=None, flags=0, options=None):
- """Opens the SAM Database
- For parameter meanings see the super class (samba.Ldb)
- """
+ hash_oid_name = {}
+ def __init__(self, url=None, lp=None, modules_dir=None, session_info=None,
+ credentials=None, flags=0, options=None, global_schema=True,
+ auto_connect=True, am_rodc=None):
self.lp = lp
- if url is None:
- url = lp.get("sam database")
+ if not auto_connect:
+ url = None
+ elif url is None and lp is not None:
+ url = lp.get("sam database")
super(SamDB, self).__init__(url=url, lp=lp, modules_dir=modules_dir,
- session_info=session_info, credentials=credentials, flags=flags,
- options=options)
+ session_info=session_info, credentials=credentials, flags=flags,
+ options=options)
+
+ if global_schema:
+ dsdb._dsdb_set_global_schema(self)
- glue.dsdb_set_global_schema(self)
+ if am_rodc is not None:
+ dsdb._dsdb_set_am_rodc(self, am_rodc)
def connect(self, url=None, flags=0, options=None):
- super(SamDB, self).connect(url=self.lp.private_path(url), flags=flags,
+ if self.lp is not None:
+ url = self.lp.private_path(url)
+
+ super(SamDB, self).connect(url=url, flags=flags,
options=options)
+ def am_rodc(self):
+ return dsdb._am_rodc(self)
+
def domain_dn(self):
- # find the DNs for the domain
- res = self.search(base="",
- scope=ldb.SCOPE_BASE,
- expression="(defaultNamingContext=*)",
- attrs=["defaultNamingContext"])
- assert(len(res) == 1 and res[0]["defaultNamingContext"] is not None)
- return res[0]["defaultNamingContext"][0]
-
- def enable_account(self, filter):
+ return str(self.get_default_basedn())
+
+ def enable_account(self, search_filter):
"""Enables an account
-
- :param filter: LDAP filter to find the user (eg samccountname=name)
+
+ :param search_filter: LDAP filter to find the user (eg
+ samccountname=name)
"""
res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
- expression=filter, attrs=["userAccountControl"])
+ expression=search_filter, attrs=["userAccountControl"])
assert(len(res) == 1)
user_dn = res[0].dn
userAccountControl = int(res[0]["userAccountControl"][0])
- if (userAccountControl & 0x2):
- userAccountControl = userAccountControl & ~0x2 # remove disabled bit
- if (userAccountControl & 0x20):
- userAccountControl = userAccountControl & ~0x20 # remove 'no password required' bit
+ if userAccountControl & 0x2:
+ # remove disabled bit
+ userAccountControl = userAccountControl & ~0x2
+ if userAccountControl & 0x20:
+ # remove 'no password required' bit
+ userAccountControl = userAccountControl & ~0x20
mod = """
dn: %s
@@ -88,14 +97,15 @@ replace: userAccountControl
userAccountControl: %u
""" % (user_dn, userAccountControl)
self.modify_ldif(mod)
-
- def force_password_change_at_next_login(self, filter):
+
+ def force_password_change_at_next_login(self, search_filter):
"""Forces a password change at next login
-
- :param filter: LDAP filter to find the user (eg samccountname=name)
+
+ :param search_filter: LDAP filter to find the user (eg
+ samccountname=name)
"""
res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
- expression=filter, attrs=[])
+ expression=search_filter, attrs=[])
assert(len(res) == 1)
user_dn = res[0].dn
@@ -107,102 +117,297 @@ pwdLastSet: 0
""" % (user_dn)
self.modify_ldif(mod)
- def newuser(self, username, unixname, password, force_password_change_at_next_login_req=False):
- """Adds a new user
+ def newgroup(self, groupname, groupou=None, grouptype=None,
+ description=None, mailaddress=None, notes=None, sd=None):
+ """Adds a new group with additional parameters
+
+ :param groupname: Name of the new group
+ :param grouptype: Type of the new group
+ :param description: Description of the new group
+ :param mailaddress: Email address of the new group
+ :param notes: Notes of the new group
+ :param sd: security descriptor of the object
+ """
+
+ group_dn = "CN=%s,%s,%s" % (groupname, (groupou or "CN=Users"), self.domain_dn())
+
+ # The new user record. Note the reliance on the SAMLDB module which
+ # fills in the default informations
+ ldbmessage = {"dn": group_dn,
+ "sAMAccountName": groupname,
+ "objectClass": "group"}
+
+ if grouptype is not None:
+ ldbmessage["groupType"] = "%d" % grouptype
+
+ if description is not None:
+ ldbmessage["description"] = description
+
+ if mailaddress is not None:
+ ldbmessage["mail"] = mailaddress
+
+ if notes is not None:
+ ldbmessage["info"] = notes
+
+ if sd is not None:
+ ldbmessage["nTSecurityDescriptor"] = ndr_pack(sd)
+
+ self.add(ldbmessage)
+
+ def deletegroup(self, groupname):
+ """Deletes a group
+
+ :param groupname: Name of the target group
+ """
+
+ groupfilter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (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,
+ expression=groupfilter, attrs=[])
+ if len(targetgroup) == 0:
+ raise Exception('Unable to find group "%s"' % groupname)
+ assert(len(targetgroup) == 1)
+ self.delete(targetgroup[0].dn)
+ except Exception:
+ self.transaction_cancel()
+ raise
+ else:
+ self.transaction_commit()
+
+ def add_remove_group_members(self, groupname, listofmembers,
+ 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 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(',')
+
+ self.transaction_start()
+ try:
+ targetgroup = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
+ expression=groupfilter, attrs=['member'])
+ if len(targetgroup) == 0:
+ raise Exception('Unable to find group "%s"' % groupname)
+ assert(len(targetgroup) == 1)
+
+ modified = False
+
+ addtargettogroup = """
+dn: %s
+changetype: modify
+""" % (str(targetgroup[0].dn))
+
+ for member in groupmembers:
+ targetmember = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
+ expression="(|(sAMAccountName=%s)(CN=%s))" % (member, member), attrs=[])
+
+ if len(targetmember) != 1:
+ continue
+
+ if add_members_operation is True and (targetgroup[0].get('member') is None or str(targetmember[0].dn) not in targetgroup[0]['member']):
+ modified = True
+ addtargettogroup += """add: member
+member: %s
+""" % (str(targetmember[0].dn))
+
+ elif add_members_operation is False and (targetgroup[0].get('member') is not None and str(targetmember[0].dn) in targetgroup[0]['member']):
+ modified = True
+ addtargettogroup += """delete: member
+member: %s
+""" % (str(targetmember[0].dn))
+
+ if modified is True:
+ self.modify_ldif(addtargettogroup)
+
+ except Exception:
+ self.transaction_cancel()
+ raise
+ else:
+ self.transaction_commit()
+
+ def newuser(self, username, password,
+ force_password_change_at_next_login_req=False,
+ useusernameascn=False, userou=None, surname=None, givenname=None,
+ initials=None, profilepath=None, scriptpath=None, homedrive=None,
+ homedirectory=None, jobtitle=None, department=None, company=None,
+ description=None, mailaddress=None, internetaddress=None,
+ telephonenumber=None, physicaldeliveryoffice=None, sd=None,
+ setpassword=True):
+ """Adds a new user with additional parameters
- Note: This call adds also the ID mapping for winbind; therefore it works
- *only* on SAMBA 4.
-
:param username: Name of the new user
- :param unixname: Name of the unix user to map to
:param password: Password for the new user
:param force_password_change_at_next_login_req: Force password change
+ :param useusernameascn: Use username as cn rather that firstname +
+ initials + lastname
+ :param userou: Object container (without domainDN postfix) for new user
+ :param surname: Surname of the new user
+ :param givenname: First name of the new user
+ :param initials: Initials of the new user
+ :param profilepath: Profile path of the new user
+ :param scriptpath: Logon script path of the new user
+ :param homedrive: Home drive of the new user
+ :param homedirectory: Home directory of the new user
+ :param jobtitle: Job title of the new user
+ :param department: Department of the new user
+ :param company: Company of the new user
+ :param description: of the new user
+ :param mailaddress: Email address of the new user
+ :param internetaddress: Home page of the new user
+ :param telephonenumber: Phone number of the new user
+ :param physicaldeliveryoffice: Office location of the new user
+ :param sd: security descriptor of the object
+ :param setpassword: optionally disable password reset
"""
+
+ displayname = ""
+ if givenname is not None:
+ displayname += givenname
+
+ if initials is not None:
+ displayname += ' %s.' % initials
+
+ if surname is not None:
+ displayname += ' %s' % surname
+
+ cn = username
+ if useusernameascn is None and displayname is not "":
+ cn = displayname
+
+ user_dn = "CN=%s,%s,%s" % (cn, (userou or "CN=Users"), self.domain_dn())
+
+ dnsdomain = ldb.Dn(self, self.domain_dn()).canonical_str().replace("/", "")
+ user_principal_name = "%s@%s" % (username, dnsdomain)
+ # The new user record. Note the reliance on the SAMLDB module which
+ # fills in the default informations
+ ldbmessage = {"dn": user_dn,
+ "sAMAccountName": username,
+ "userPrincipalName": user_principal_name,
+ "objectClass": "user"}
+
+ if surname is not None:
+ ldbmessage["sn"] = surname
+
+ if givenname is not None:
+ ldbmessage["givenName"] = givenname
+
+ if displayname is not "":
+ ldbmessage["displayName"] = displayname
+ ldbmessage["name"] = displayname
+
+ if initials is not None:
+ ldbmessage["initials"] = '%s.' % initials
+
+ if profilepath is not None:
+ ldbmessage["profilePath"] = profilepath
+
+ if scriptpath is not None:
+ ldbmessage["scriptPath"] = scriptpath
+
+ if homedrive is not None:
+ ldbmessage["homeDrive"] = homedrive
+
+ if homedirectory is not None:
+ ldbmessage["homeDirectory"] = homedirectory
+
+ if jobtitle is not None:
+ ldbmessage["title"] = jobtitle
+
+ if department is not None:
+ ldbmessage["department"] = department
+
+ if company is not None:
+ ldbmessage["company"] = company
+
+ if description is not None:
+ ldbmessage["description"] = description
+
+ if mailaddress is not None:
+ ldbmessage["mail"] = mailaddress
+
+ if internetaddress is not None:
+ ldbmessage["wWWHomePage"] = internetaddress
+
+ if telephonenumber is not None:
+ ldbmessage["telephoneNumber"] = telephonenumber
+
+ if physicaldeliveryoffice is not None:
+ ldbmessage["physicalDeliveryOfficeName"] = physicaldeliveryoffice
+
+ if sd is not None:
+ ldbmessage["nTSecurityDescriptor"] = ndr_pack(sd)
+
self.transaction_start()
try:
- user_dn = "CN=%s,CN=Users,%s" % (username, self.domain_dn())
-
- # The new user record. Note the reliance on the SAMLDB module which
- # fills in the default informations
- self.add({"dn": user_dn,
- "sAMAccountName": username,
- "objectClass": "user"})
+ self.add(ldbmessage)
# Sets the password for it
- self.setpassword("(dn=" + user_dn + ")", password,
- force_password_change_at_next_login_req)
-
- # Gets the user SID (for the account mapping setup)
- res = self.search(user_dn, scope=ldb.SCOPE_BASE,
- expression="objectclass=*",
- attrs=["objectSid"])
- assert len(res) == 1
- user_sid = self.schema_format_value("objectSid", res[0]["objectSid"][0])
-
- try:
- idmap = IDmapDB(lp=self.lp)
-
- user = pwd.getpwnam(unixname)
-
- # setup ID mapping for this UID
- idmap.setup_name_mapping(user_sid, idmap.TYPE_UID, user[2])
-
- except KeyError:
- pass
- except:
+ if setpassword:
+ self.setpassword("(samAccountName=%s)" % username, password,
+ force_password_change_at_next_login_req)
+ except Exception:
self.transaction_cancel()
raise
- self.transaction_commit()
+ else:
+ self.transaction_commit()
- def setpassword(self, filter, password, force_password_change_at_next_login_req=False):
+ def setpassword(self, search_filter, password,
+ force_change_at_next_login=False, username=None):
"""Sets the password for a user
-
- Note: This call uses the "userPassword" attribute to set the password.
- This works correctly on SAMBA 4 and on Windows DCs with
- "2003 Native" or higer domain function level.
- :param filter: LDAP filter to find the user (eg samccountname=name)
+ :param search_filter: LDAP filter to find the user (eg
+ samccountname=name)
:param password: Password for the user
- :param force_password_change_at_next_login_req: Force password change
+ :param force_change_at_next_login: Force password change
"""
self.transaction_start()
try:
res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
- expression=filter, attrs=[])
- assert(len(res) == 1)
+ expression=search_filter, attrs=[])
+ if len(res) == 0:
+ raise Exception('Unable to find user "%s"' % (username or search_filter))
+ if len(res) > 1:
+ raise Exception('Matched %u multiple users with filter "%s"' % (len(res), search_filter))
user_dn = res[0].dn
-
setpw = """
dn: %s
changetype: modify
-replace: userPassword
-userPassword:: %s
-""" % (user_dn, base64.b64encode(password))
+replace: unicodePwd
+unicodePwd:: %s
+""" % (user_dn, base64.b64encode(("\"" + password + "\"").encode('utf-16-le')))
self.modify_ldif(setpw)
- if force_password_change_at_next_login_req:
+ if force_change_at_next_login:
self.force_password_change_at_next_login(
"(dn=" + str(user_dn) + ")")
# modify the userAccountControl to remove the disabled bit
- self.enable_account(filter)
- except:
+ self.enable_account(search_filter)
+ except Exception:
self.transaction_cancel()
raise
- self.transaction_commit()
+ else:
+ self.transaction_commit()
- def setexpiry(self, filter, expiry_seconds, no_expiry_req=False):
+ def setexpiry(self, search_filter, expiry_seconds, no_expiry_req=False):
"""Sets the account expiry for a user
-
- :param filter: LDAP filter to find the user (eg samccountname=name)
+
+ :param search_filter: LDAP filter to find the user (eg
+ samaccountname=name)
:param expiry_seconds: expiry time from now in seconds
:param no_expiry_req: if set, then don't expire password
"""
self.transaction_start()
try:
res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
- expression=filter,
+ expression=search_filter,
attrs=["userAccountControl", "accountExpires"])
assert(len(res) == 1)
user_dn = res[0].dn
@@ -214,7 +419,7 @@ userPassword:: %s
accountExpires = 0
else:
userAccountControl = userAccountControl & ~0x10000
- accountExpires = glue.unix2nttime(expiry_seconds + int(time.time()))
+ accountExpires = samba.unix2nttime(expiry_seconds + int(time.time()))
setexp = """
dn: %s
@@ -226,8 +431,283 @@ accountExpires: %u
""" % (user_dn, userAccountControl, accountExpires)
self.modify_ldif(setexp)
- except:
+ except Exception:
self.transaction_cancel()
raise
- self.transaction_commit();
+ else:
+ self.transaction_commit()
+
+ def set_domain_sid(self, sid):
+ """Change the domain SID used by this LDB.
+
+ :param sid: The new domain sid to use.
+ """
+ dsdb._samdb_set_domain_sid(self, sid)
+
+ def get_domain_sid(self):
+ """Read the domain SID used by this LDB. """
+ return dsdb._samdb_get_domain_sid(self)
+
+ domain_sid = property(get_domain_sid, set_domain_sid,
+ "SID for the domain")
+
+ def set_invocation_id(self, invocation_id):
+ """Set the invocation id for this SamDB handle.
+
+ :param invocation_id: GUID of the invocation id.
+ """
+ dsdb._dsdb_set_ntds_invocation_id(self, invocation_id)
+
+ def get_invocation_id(self):
+ """Get the invocation_id id"""
+ return dsdb._samdb_ntds_invocation_id(self)
+
+ invocation_id = property(get_invocation_id, set_invocation_id,
+ "Invocation ID GUID")
+
+ def get_oid_from_attid(self, attid):
+ return dsdb._dsdb_get_oid_from_attid(self, attid)
+
+ def get_attid_from_lDAPDisplayName(self, ldap_display_name,
+ is_schema_nc=False):
+ return dsdb._dsdb_get_attid_from_lDAPDisplayName(self,
+ ldap_display_name, is_schema_nc)
+
+ def set_ntds_settings_dn(self, ntds_settings_dn):
+ """Set the NTDS Settings DN, as would be returned on the dsServiceName
+ rootDSE attribute.
+
+ This allows the DN to be set before the database fully exists
+
+ :param ntds_settings_dn: The new DN to use
+ """
+ dsdb._samdb_set_ntds_settings_dn(self, ntds_settings_dn)
+
+ def get_ntds_GUID(self):
+ """Get the NTDS objectGUID"""
+ return dsdb._samdb_ntds_objectGUID(self)
+
+ def server_site_name(self):
+ """Get the server site name"""
+ return dsdb._samdb_server_site_name(self)
+
+ 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_from_ldb(self, ldb_conn):
+ dsdb._dsdb_set_schema_from_ldb(self, ldb_conn)
+
+ def dsdb_DsReplicaAttribute(self, ldb, ldap_display_name, ldif_elements):
+ return dsdb._dsdb_DsReplicaAttribute(ldb, ldap_display_name, ldif_elements)
+
+ def get_attribute_from_attid(self, attid):
+ """ Get from an attid the associated attribute
+
+ :param attid: The attribute id for searched attribute
+ :return: The name of the attribute associated with this id
+ """
+ if len(self.hash_oid_name.keys()) == 0:
+ self._populate_oid_attid()
+ if self.hash_oid_name.has_key(self.get_oid_from_attid(attid)):
+ return self.hash_oid_name[self.get_oid_from_attid(attid)]
+ else:
+ return None
+
+ def _populate_oid_attid(self):
+ """Populate the hash hash_oid_name.
+
+ This hash contains the oid of the attribute as a key and
+ its display name as a value
+ """
+ self.hash_oid_name = {}
+ res = self.search(expression="objectClass=attributeSchema",
+ controls=["search_options:1:2"],
+ attrs=["attributeID",
+ "lDAPDisplayName"])
+ if len(res) > 0:
+ for e in res:
+ strDisplay = str(e.get("lDAPDisplayName"))
+ self.hash_oid_name[str(e.get("attributeID"))] = strDisplay
+
+ def get_attribute_replmetadata_version(self, dn, att):
+ """Get the version field trom the replPropertyMetaData for
+ the given field
+
+ :param dn: The on which we want to get the version
+ :param att: The name of the attribute
+ :return: The value of the version field in the replPropertyMetaData
+ for the given attribute. None if the attribute is not replicated
+ """
+
+ res = self.search(expression="dn=%s" % dn,
+ scope=ldb.SCOPE_SUBTREE,
+ controls=["search_options:1:2"],
+ attrs=["replPropertyMetaData"])
+ if len(res) == 0:
+ return None
+
+ repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
+ str(res[0]["replPropertyMetaData"]))
+ ctr = repl.ctr
+ if len(self.hash_oid_name.keys()) == 0:
+ self._populate_oid_attid()
+ for o in ctr.array:
+ # Search for Description
+ att_oid = self.get_oid_from_attid(o.attid)
+ if self.hash_oid_name.has_key(att_oid) and\
+ att.lower() == self.hash_oid_name[att_oid].lower():
+ return o.version
+ return None
+
+ def set_attribute_replmetadata_version(self, dn, att, value,
+ addifnotexist=False):
+ res = self.search(expression="dn=%s" % dn,
+ scope=ldb.SCOPE_SUBTREE,
+ controls=["search_options:1:2"],
+ attrs=["replPropertyMetaData"])
+ if len(res) == 0:
+ return None
+
+ repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
+ str(res[0]["replPropertyMetaData"]))
+ ctr = repl.ctr
+ now = samba.unix2nttime(int(time.time()))
+ found = False
+ if len(self.hash_oid_name.keys()) == 0:
+ self._populate_oid_attid()
+ for o in ctr.array:
+ # Search for Description
+ att_oid = self.get_oid_from_attid(o.attid)
+ if self.hash_oid_name.has_key(att_oid) and\
+ att.lower() == self.hash_oid_name[att_oid].lower():
+ found = True
+ seq = self.sequence_number(ldb.SEQ_NEXT)
+ o.version = value
+ o.originating_change_time = now
+ o.originating_invocation_id = misc.GUID(self.get_invocation_id())
+ o.originating_usn = seq
+ o.local_usn = seq
+
+ if not found and addifnotexist and len(ctr.array) >0:
+ o2 = drsblobs.replPropertyMetaData1()
+ o2.attid = 589914
+ att_oid = self.get_oid_from_attid(o2.attid)
+ seq = self.sequence_number(ldb.SEQ_NEXT)
+ o2.version = value
+ o2.originating_change_time = now
+ o2.originating_invocation_id = misc.GUID(self.get_invocation_id())
+ o2.originating_usn = seq
+ o2.local_usn = seq
+ found = True
+ tab = ctr.array
+ tab.append(o2)
+ ctr.count = ctr.count + 1
+ ctr.array = tab
+
+ if found :
+ replBlob = ndr_pack(repl)
+ msg = ldb.Message()
+ msg.dn = res[0].dn
+ msg["replPropertyMetaData"] = ldb.MessageElement(replBlob,
+ ldb.FLAG_MOD_REPLACE,
+ "replPropertyMetaData")
+ self.modify(msg, ["local_oid:1.3.6.1.4.1.7165.4.3.14:0"])
+
+ def write_prefixes_from_schema(self):
+ dsdb._dsdb_write_prefixes_from_schema_to_ldb(self)
+
+ def get_partitions_dn(self):
+ return dsdb._dsdb_get_partitions_dn(self)
+
+ def set_minPwdAge(self, value):
+ m = ldb.Message()
+ m.dn = ldb.Dn(self, self.domain_dn())
+ m["minPwdAge"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "minPwdAge")
+ self.modify(m)
+
+ def get_minPwdAge(self):
+ res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["minPwdAge"])
+ if len(res) == 0:
+ return None
+ elif not "minPwdAge" in res[0]:
+ return None
+ else:
+ return res[0]["minPwdAge"][0]
+
+ def set_minPwdLength(self, value):
+ m = ldb.Message()
+ m.dn = ldb.Dn(self, self.domain_dn())
+ m["minPwdLength"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "minPwdLength")
+ self.modify(m)
+
+ def get_minPwdLength(self):
+ res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["minPwdLength"])
+ if len(res) == 0:
+ return None
+ elif not "minPwdLength" in res[0]:
+ return None
+ else:
+ return res[0]["minPwdLength"][0]
+
+ def set_pwdProperties(self, value):
+ m = ldb.Message()
+ m.dn = ldb.Dn(self, self.domain_dn())
+ m["pwdProperties"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "pwdProperties")
+ self.modify(m)
+
+ def get_pwdProperties(self):
+ res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["pwdProperties"])
+ if len(res) == 0:
+ return None
+ elif not "pwdProperties" in res[0]:
+ return None
+ else:
+ return res[0]["pwdProperties"][0]
+
+ def set_dsheuristics(self, dsheuristics):
+ m = ldb.Message()
+ m.dn = ldb.Dn(self, "CN=Directory Service,CN=Windows NT,CN=Services,%s"
+ % self.get_config_basedn().get_linearized())
+ if dsheuristics is not None:
+ m["dSHeuristics"] = ldb.MessageElement(dsheuristics,
+ ldb.FLAG_MOD_REPLACE, "dSHeuristics")
+ else:
+ m["dSHeuristics"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE,
+ "dSHeuristics")
+ self.modify(m)
+
+ def get_dsheuristics(self):
+ res = self.search("CN=Directory Service,CN=Windows NT,CN=Services,%s"
+ % self.get_config_basedn().get_linearized(),
+ scope=ldb.SCOPE_BASE, attrs=["dSHeuristics"])
+ if len(res) == 0:
+ dsheuristics = None
+ elif "dSHeuristics" in res[0]:
+ dsheuristics = res[0]["dSHeuristics"][0]
+ else:
+ dsheuristics = None
+
+ return dsheuristics
+
+ def create_ou(self, ou_dn, description=None, name=None, sd=None):
+ """Creates an organizationalUnit object
+ :param ou_dn: dn of the new object
+ :param description: description attribute
+ :param name: name atttribute
+ :param sd: security descriptor of the object, can be
+ an SDDL string or security.descriptor type
+ """
+ m = {"dn": ou_dn,
+ "objectClass": "organizationalUnit"}
+
+ if description:
+ m["description"] = description
+ if name:
+ m["name"] = name
+ if sd:
+ m["nTSecurityDescriptor"] = ndr_pack(sd)
+ self.add(m)
diff --git a/source4/scripting/python/samba/schema.py b/source4/scripting/python/samba/schema.py
new file mode 100644
index 0000000000..8bac26e24f
--- /dev/null
+++ b/source4/scripting/python/samba/schema.py
@@ -0,0 +1,204 @@
+#
+# Unix SMB/CIFS implementation.
+# backend code for provisioning a Samba4 server
+#
+# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
+# Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008-2009
+# Copyright (C) Oliver Liebel <oliver@itc.li> 2008-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/>.
+#
+
+"""Functions for setting up a Samba Schema."""
+
+from base64 import b64encode
+from samba import read_and_sub_file, substitute_var, check_all_substituted
+from samba.dcerpc import security
+from samba.ms_schema import read_ms_schema
+from samba.ndr import ndr_pack
+from samba.samdb import SamDB
+from samba import dsdb
+from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL
+import os
+
+def get_schema_descriptor(domain_sid):
+ sddl = "O:SAG:SAD:AI(OA;;CR;e12b56b6-0a95-11d1-adbb-00c04fd8d5cd;;SA)" \
+ "(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;CI;RPLCLORC;;;AU)" \
+ "(A;CI;RPWPCRCCLCLORCWOWDSW;;;SA)" \
+ "(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
+ "(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)" \
+ "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;ER)" \
+ "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ER)" \
+ "S:(AU;SA;WPCCDCWOWDSDDTSW;;;WD)" \
+ "(AU;CISA;WP;;;WD)" \
+ "(AU;SA;CR;;;BA)" \
+ "(AU;SA;CR;;;DU)" \
+ "(OU;SA;CR;e12b56b6-0a95-11d1-adbb-00c04fd8d5cd;;WD)" \
+ "(OU;SA;CR;45ec5156-db7e-47bb-b53f-dbeb2d03c40f;;WD)"
+ sec = security.descriptor.from_sddl(sddl, domain_sid)
+ return ndr_pack(sec)
+
+
+class Schema(object):
+
+ def __init__(self, domain_sid, invocationid=None, schemadn=None,
+ files=None, override_prefixmap=None, additional_prefixmap=None):
+ from samba.provision import setup_path
+
+ """Load schema for the SamDB from the AD schema files and
+ samba4_schema.ldif
+
+ :param samdb: Load a schema into a SamDB.
+ :param schemadn: DN of the schema
+
+ Returns the schema data loaded, to avoid double-parsing when then
+ needing to add it to the db
+ """
+
+ self.schemadn = schemadn
+ # We need to have the am_rodc=False just to keep some warnings quiet -
+ # this isn't a real SAM, so it's meaningless.
+ self.ldb = SamDB(global_schema=False, am_rodc=False)
+ if invocationid is not None:
+ self.ldb.set_invocation_id(invocationid)
+
+ self.schema_data = read_ms_schema(
+ setup_path('ad-schema/MS-AD_Schema_2K8_R2_Attributes.txt'),
+ setup_path('ad-schema/MS-AD_Schema_2K8_R2_Classes.txt'))
+
+ if files is not None:
+ for file in files:
+ self.schema_data += open(file, 'r').read()
+
+ self.schema_data = substitute_var(self.schema_data,
+ {"SCHEMADN": schemadn})
+ check_all_substituted(self.schema_data)
+
+ self.schema_dn_modify = read_and_sub_file(
+ setup_path("provision_schema_basedn_modify.ldif"),
+ {"SCHEMADN": schemadn})
+
+ descr = b64encode(get_schema_descriptor(domain_sid))
+ self.schema_dn_add = read_and_sub_file(
+ setup_path("provision_schema_basedn.ldif"),
+ {"SCHEMADN": schemadn, "DESCRIPTOR": descr})
+
+ if override_prefixmap is not None:
+ self.prefixmap_data = override_prefixmap
+ else:
+ self.prefixmap_data = open(setup_path("prefixMap.txt"), 'r').read()
+
+ if additional_prefixmap is not None:
+ for map in additional_prefixmap:
+ self.prefixmap_data += "%s\n" % map
+
+ 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)
+
+ def set_from_ldif(self, pf, df):
+ dsdb._dsdb_set_schema_from_ldif(self.ldb, pf, df)
+
+ def write_to_tmp_ldb(self, schemadb_path):
+ self.ldb.connect(url=schemadb_path)
+ self.ldb.transaction_start()
+ try:
+ self.ldb.add_ldif("""dn: @ATTRIBUTES
+linkID: INTEGER
+
+dn: @INDEXLIST
+@IDXATTR: linkID
+@IDXATTR: attributeSyntax
+""")
+ # These bits of LDIF are supplied when the Schema object is created
+ 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:
+ self.ldb.transaction_cancel()
+ raise
+ else:
+ self.ldb.transaction_commit()
+
+ # Return a hash with the forward attribute as a key and the back as the
+ # value
+ def linked_attributes(self):
+ return get_linked_attributes(self.schemadn, self.ldb)
+
+ def dnsyntax_attributes(self):
+ return get_dnsyntax_attributes(self.schemadn, self.ldb)
+
+ def convert_to_openldap(self, target, mapping):
+ 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
+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",
+ scope=SCOPE_SUBTREE)
+ if target is not None:
+ attributes[str(res[i]["lDAPDisplayName"])]=str(target)
+
+ return attributes
+
+
+def get_dnsyntax_attributes(schemadn,schemaldb):
+ res = schemaldb.search(
+ expression="(&(!(linkID=*))(objectclass=attributeSchema)(attributeSyntax=2.5.5.1))",
+ base=schemadn, scope=SCOPE_ONELEVEL,
+ attrs=["linkID", "lDAPDisplayName"])
+ attributes = []
+ for i in range (0, len(res)):
+ attributes.append(str(res[i]["lDAPDisplayName"]))
+ return attributes
+
+
+def ldb_with_schema(schemadn="cn=schema,cn=configuration,dc=example,dc=com",
+ domainsid=None,
+ override_prefixmap=None):
+ """Load schema for the SamDB from the AD schema files and samba4_schema.ldif
+
+ :param schemadn: DN of the schema
+ :param serverdn: DN of the server
+
+ Returns the schema data loaded as an object, with .ldb being a
+ new ldb with the schema loaded. This allows certain tests to
+ operate without a remote or local schema.
+ """
+
+ if domainsid is None:
+ domainsid = security.random_sid()
+ else:
+ domainsid = security.dom_sid(domainsid)
+ return Schema(domainsid, schemadn=schemadn,
+ override_prefixmap=override_prefixmap)
diff --git a/source4/scripting/python/samba/sd_utils.py b/source4/scripting/python/samba/sd_utils.py
new file mode 100644
index 0000000000..4694f4cc76
--- /dev/null
+++ b/source4/scripting/python/samba/sd_utils.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+#
+# Utility methods for security descriptor manipulation
+#
+# Copyright Nadezhda Ivanova 2010 <nivanova@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
+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'''
+
+ 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
+ or security.descriptor object
+ """
+ m = Message()
+ m.dn = Dn(self.ldb, object_dn)
+ assert(isinstance(sd, str) or isinstance(sd, security.descriptor))
+ if isinstance(sd, str):
+ tmp_desc = security.descriptor.from_sddl(sd, self.domain_sid)
+ elif isinstance(sd, security.descriptor):
+ tmp_desc = sd
+
+ m["nTSecurityDescriptor"] = MessageElement(ndr_pack(tmp_desc),
+ FLAG_MOD_REPLACE,
+ "nTSecurityDescriptor")
+ self.ldb.modify(m, controls)
+
+ def read_sd_on_dn(self, object_dn, controls=None):
+ res = self.ldb.search(object_dn, SCOPE_BASE, None,
+ ["nTSecurityDescriptor"], controls=controls)
+ desc = res[0]["nTSecurityDescriptor"][0]
+ return ndr_unpack(security.descriptor, desc)
+
+ def get_object_sid(self, object_dn):
+ res = self.ldb.search(object_dn)
+ 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
+ """
+ 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("("):]
+ 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
+ """
+ desc = self.read_sd_on_dn(object_dn, controls=controls)
+ return desc.as_sddl(self.domain_sid)
diff --git a/source4/scripting/python/samba/shares.py b/source4/scripting/python/samba/shares.py
deleted file mode 100644
index 89a312e855..0000000000
--- a/source4/scripting/python/samba/shares.py
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/usr/bin/python
-
-# Unix SMB/CIFS implementation.
-# 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/>.
-#
-
-"""Share management."""
-
-
-# TODO: Rather than accessing Loadparm directly here, we should really
-# have bindings to the param/shares.c and use those.
-
-
-class SharesContainer(object):
- """A shares container."""
-
- def __init__(self, lp):
- self._lp = lp
-
- def __getitem__(self, name):
- if name == "global":
- # [global] is not a share
- raise KeyError
- return Share(self._lp[name])
-
- def __len__(self):
- if "global" in self._lp:
- return len(self._lp)-1
- return len(self._lp)
-
- def keys(self):
- return [name for name in self._lp.services() if name != "global"]
-
- def __iter__(self):
- return iter(self.keys())
-
-
-class Share(object):
- """A file share."""
-
- def __init__(self, service):
- self._service = service
-
- def __getitem__(self, name):
- return self._service[name]
-
- def __setitem__(self, name, value):
- self._service[name] = value
diff --git a/source4/scripting/python/samba/tests/__init__.py b/source4/scripting/python/samba/tests/__init__.py
index ae7a707e35..58e4130998 100644
--- a/source4/scripting/python/samba/tests/__init__.py
+++ b/source4/scripting/python/samba/tests/__init__.py
@@ -1,18 +1,18 @@
-#!/usr/bin/python
+#!/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-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/>.
#
@@ -22,13 +22,43 @@
import os
import ldb
import samba
+import samba.auth
+from samba import param
+from samba.samdb import SamDB
+import subprocess
import tempfile
-import unittest
-class LdbTestCase(unittest.TestCase):
+# Other modules import these two classes from here, for convenience:
+from testtools.testcase import (
+ TestCase as TesttoolsTestCase,
+ TestSkipped,
+ )
+
+
+class TestCase(TesttoolsTestCase):
+ """A Samba test case."""
+
+ def setUp(self):
+ super(TestCase, self).setUp()
+ test_debug_level = os.getenv("TEST_DEBUG_LEVEL")
+ if test_debug_level is not None:
+ test_debug_level = int(test_debug_level)
+ self._old_debug_level = samba.get_debug_level()
+ samba.set_debug_level(test_debug_level)
+ self.addCleanup(samba.set_debug_level, test_debug_level)
+
+ def get_loadparm(self):
+ return env_loadparm()
+
+ def get_credentials(self):
+ return cmdline_credentials
+
+class LdbTestCase(TesttoolsTestCase):
"""Trivial test case for running tests against a LDB."""
+
def setUp(self):
+ super(LdbTestCase, self).setUp()
self.filename = os.tempnam()
self.ldb = samba.Ldb(self.filename)
@@ -41,7 +71,7 @@ class LdbTestCase(unittest.TestCase):
self.ldb = samba.Ldb(self.filename)
-class TestCaseInTempDir(unittest.TestCase):
+class TestCaseInTempDir(TestCase):
def setUp(self):
super(TestCaseInTempDir, self).setUp()
@@ -53,57 +83,33 @@ class TestCaseInTempDir(unittest.TestCase):
os.rmdir(self.tempdir)
-class SubstituteVarTestCase(unittest.TestCase):
-
- def test_empty(self):
- self.assertEquals("", samba.substitute_var("", {}))
-
- def test_nothing(self):
- self.assertEquals("foo bar", samba.substitute_var("foo bar", {"bar": "bla"}))
-
- def test_replace(self):
- self.assertEquals("foo bla", samba.substitute_var("foo ${bar}", {"bar": "bla"}))
+def env_loadparm():
+ lp = param.LoadParm()
+ try:
+ lp.load(os.environ["SMB_CONF_PATH"])
+ except KeyError:
+ raise Exception("SMB_CONF_PATH not set")
+ return lp
- def test_broken(self):
- self.assertEquals("foo ${bdkjfhsdkfh sdkfh ",
- samba.substitute_var("foo ${bdkjfhsdkfh sdkfh ", {"bar": "bla"}))
- def test_unknown_var(self):
- self.assertEquals("foo ${bla} gsff",
- samba.substitute_var("foo ${bla} gsff", {"bar": "bla"}))
-
- def test_check_all_substituted(self):
- samba.check_all_substituted("nothing to see here")
- self.assertRaises(Exception, samba.check_all_substituted, "Not subsituted: ${FOOBAR}")
+def env_get_var_value(var_name):
+ """Returns value for variable in os.environ
+ Function throws AssertionError if variable is defined.
+ Unit-test based python tests require certain input params
+ to be set in environment, otherwise they can't be run
+ """
+ assert var_name in os.environ.keys(), "Please supply %s in environment" % var_name
+ return os.environ[var_name]
-class LdbExtensionTests(TestCaseInTempDir):
- def test_searchone(self):
- path = self.tempdir + "/searchone.ldb"
- l = samba.Ldb(path)
- try:
- l.add({"dn": "foo=dc", "bar": "bla"})
- self.assertEquals("bla", l.searchone(basedn=ldb.Dn(l, "foo=dc"), attribute="bar"))
- finally:
- del l
- os.unlink(path)
-
-
-cmdline_loadparm = None
cmdline_credentials = None
-class RpcInterfaceTestCase(unittest.TestCase):
+class RpcInterfaceTestCase(TestCase):
+ """DCE/RPC Test case."""
- def get_loadparm(self):
- assert cmdline_loadparm is not None
- return cmdline_loadparm
-
- def get_credentials(self):
- return cmdline_credentials
-
-class ValidNetbiosNameTests(unittest.TestCase):
+class ValidNetbiosNameTests(TestCase):
def test_valid(self):
self.assertTrue(samba.valid_netbios_name("FOO"))
@@ -113,3 +119,110 @@ class ValidNetbiosNameTests(unittest.TestCase):
def test_invalid_characters(self):
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)"""
+ def __init__(self, returncode, cmd, stdout, stderr):
+ super(BlackboxProcessError, self).__init__(returncode, 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)
+
+class BlackboxTestCase(TestCase):
+ """Base test case for blackbox tests."""
+
+ def _make_cmdline(self, line):
+ bindir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../bin"))
+ parts = line.split(" ")
+ if os.path.exists(os.path.join(bindir, parts[0])):
+ parts[0] = os.path.join(bindir, parts[0])
+ line = " ".join(parts)
+ return line
+
+ def check_run(self, line):
+ line = self._make_cmdline(line)
+ subprocess.check_call(line, shell=True)
+
+ def check_output(self, line):
+ line = self._make_cmdline(line)
+ p = subprocess.Popen(line, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, close_fds=True)
+ retcode = p.wait()
+ if retcode:
+ raise BlackboxProcessError(retcode, line, p.stdout.read(), p.stderr.read())
+ return p.stdout.read()
+
+def connect_samdb(samdb_url, lp=None, session_info=None, credentials=None,
+ flags=0, ldb_options=None, ldap_only=False):
+ """Create SamDB instance and connects to samdb_url database.
+
+ :param samdb_url: Url for database to connect to.
+ :param lp: Optional loadparm object
+ :param session_info: Optional session information
+ :param credentials: Optional credentials, defaults to anonymous.
+ :param flags: Optional LDB flags
+ :param ldap_only: If set, only remote LDAP connection will be created.
+
+ Added value for tests is that we have a shorthand function
+ to make proper URL for ldb.connect() while using default
+ parameters for connection based on test environment
+ """
+ samdb_url = samdb_url.lower()
+ if not "://" in samdb_url:
+ if not ldap_only and os.path.isfile(samdb_url):
+ samdb_url = "tdb://%s" % samdb_url
+ else:
+ samdb_url = "ldap://%s" % samdb_url
+ # use 'paged_search' module when connecting remotely
+ if samdb_url.startswith("ldap://"):
+ ldb_options = ["modules:paged_searches"]
+ elif ldap_only:
+ raise AssertionError("Trying to connect to %s while remote "
+ "connection is required" % samdb_url)
+
+ # set defaults for test environment
+ if lp is None:
+ lp = env_loadparm()
+ if session_info is None:
+ session_info = samba.auth.system_session(lp)
+ if credentials is None:
+ credentials = cmdline_credentials
+
+ return SamDB(url=samdb_url,
+ lp=lp,
+ session_info=session_info,
+ credentials=credentials,
+ flags=flags,
+ options=ldb_options)
+
+
+def connect_samdb_ex(samdb_url, lp=None, session_info=None, credentials=None,
+ flags=0, ldb_options=None, ldap_only=False):
+ """Connects to samdb_url database
+
+ :param samdb_url: Url for database to connect to.
+ :param lp: Optional loadparm object
+ :param session_info: Optional session information
+ :param credentials: Optional credentials, defaults to anonymous.
+ :param flags: Optional LDB flags
+ :param ldap_only: If set, only remote LDAP connection will be created.
+ :return: (sam_db_connection, rootDse_record) tuple
+ """
+ sam_db = connect_samdb(samdb_url, lp, session_info, credentials,
+ flags, ldb_options, ldap_only)
+ # fetch RootDse
+ res = sam_db.search(base="", expression="", scope=ldb.SCOPE_BASE,
+ attrs=["*"])
+ return (sam_db, res[0])
+
+
+def delete_force(samdb, dn):
+ try:
+ samdb.delete(dn)
+ except ldb.LdbError, (num, _):
+ assert(num == ldb.ERR_NO_SUCH_OBJECT)
diff --git a/source4/scripting/python/samba/tests/auth.py b/source4/scripting/python/samba/tests/auth.py
new file mode 100644
index 0000000000..6ecfc2047f
--- /dev/null
+++ b/source4/scripting/python/samba/tests/auth.py
@@ -0,0 +1,33 @@
+#!/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.
+
+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 import auth
+import samba.tests
+
+class AuthTests(samba.tests.TestCase):
+
+ def test_system_session(self):
+ auth.system_session()
+
diff --git a/source4/scripting/python/samba/tests/blackbox/__init__.py b/source4/scripting/python/samba/tests/blackbox/__init__.py
new file mode 100644
index 0000000000..8569cb55bd
--- /dev/null
+++ b/source4/scripting/python/samba/tests/blackbox/__init__.py
@@ -0,0 +1 @@
+"""Blackbox tests. """
diff --git a/source4/scripting/python/samba/tests/blackbox/ndrdump.py b/source4/scripting/python/samba/tests/blackbox/ndrdump.py
new file mode 100755
index 0000000000..c07e32a24f
--- /dev/null
+++ b/source4/scripting/python/samba/tests/blackbox/ndrdump.py
@@ -0,0 +1,36 @@
+#!/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
+
+"""Blackbox tests for ndrdump."""
+
+import os
+from samba.tests import BlackboxTestCase
+
+for p in [ "../../../../../source4/librpc/tests", "../../../../../librpc/tests"]:
+ data_path_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), p))
+ print data_path_dir
+ if os.path.exists(data_path_dir):
+ break
+
+
+class NdrDumpTests(BlackboxTestCase):
+ """Blackbox tests for ndrdump."""
+
+ def data_path(self, name):
+ return os.path.join(data_path_dir, name)
+
+ def test_ndrdump_with_in(self):
+ self.check_run("ndrdump samr samr_CreateUser in %s" % (self.data_path("samr-CreateUser-in.dat")))
+
+ def test_ndrdump_with_out(self):
+ self.check_run("ndrdump samr samr_CreateUser out %s" % (self.data_path("samr-CreateUser-out.dat")))
+
+ def test_ndrdump_context_file(self):
+ self.check_run("ndrdump --context-file %s samr samr_CreateUser out %s" % (self.data_path("samr-CreateUser-in.dat"), self.data_path("samr-CreateUser-out.dat")))
+
+ def test_ndrdump_with_validate(self):
+ self.check_run("ndrdump --validate samr samr_CreateUser in %s" % (self.data_path("samr-CreateUser-in.dat")))
diff --git a/source4/scripting/python/samba/tests/blackbox/samba_tool_drs.py b/source4/scripting/python/samba/tests/blackbox/samba_tool_drs.py
new file mode 100644
index 0000000000..51274cc9bd
--- /dev/null
+++ b/source4/scripting/python/samba/tests/blackbox/samba_tool_drs.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python
+
+# Blackbox tests for "samba-tool drs" command
+# Copyright (C) Kamen Mazdrashki <kamenim@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/>.
+#
+
+"""Blackbox tests for samba-tool drs."""
+
+import os
+import samba.tests
+
+
+class SambaToolDrsTests(samba.tests.BlackboxTestCase):
+ """Blackbox test case for samba-tool drs."""
+
+ def setUp(self):
+ super(SambaToolDrsTests, self).setUp()
+
+ self.dc1 = samba.tests.env_get_var_value("DC1")
+ self.dc2 = samba.tests.env_get_var_value("DC2")
+
+ creds = self.get_credentials()
+ self.cmdline_creds = "-U%s/%s%%%s" % (creds.get_domain(),
+ creds.get_username(), creds.get_password())
+
+ def _get_rootDSE(self, dc):
+ samdb = samba.tests.connect_samdb(dc, lp=self.get_loadparm(),
+ credentials=self.get_credentials(),
+ ldap_only=True)
+ return samdb.search(base="", scope=samba.tests.ldb.SCOPE_BASE)[0]
+
+ def test_samba_tool_bind(self):
+ """Tests 'samba-tool drs bind' command
+ Output should be like:
+ Extensions supported:
+ <list-of-supported-extensions>
+ Site GUID: <GUID>
+ Repl epoch: 0"""
+ out = self.check_output("samba-tool drs bind %s %s" % (self.dc1,
+ self.cmdline_creds))
+ self.assertTrue("Site GUID:" in out)
+ self.assertTrue("Repl epoch:" in out)
+
+ def test_samba_tool_kcc(self):
+ """Tests 'samba-tool drs kcc' command
+ Output should be like 'Consistency check on <DC> successful.'"""
+ out = self.check_output("samba-tool drs kcc %s %s" % (self.dc1,
+ self.cmdline_creds))
+ self.assertTrue("Consistency check on" in out)
+ self.assertTrue("successful" in out)
+
+ def test_samba_tool_showrepl(self):
+ """Tests 'samba-tool drs showrepl' command
+ Output should be like:
+ <site-name>/<domain-name>
+ DSA Options: <hex-options>
+ DSA object GUID: <DSA-object-GUID>
+ DSA invocationId: <DSA-invocationId>
+ <Inbound-connections-list>
+ <Outbound-connections-list>
+ <KCC-objects>
+ ...
+ TODO: Perhaps we should check at least for
+ DSA's objectGUDI and invocationId"""
+ out = self.check_output("samba-tool drs showrepl %s %s" % (self.dc1,
+ self.cmdline_creds))
+ self.assertTrue("DSA Options:" in out)
+ self.assertTrue("DSA object GUID:" in out)
+ self.assertTrue("DSA invocationId:" in out)
+
+ def test_samba_tool_options(self):
+ """Tests 'samba-tool drs options' command
+ Output should be like 'Current DSA options: IS_GC <OTHER_FLAGS>'"""
+ out = self.check_output("samba-tool drs options %s %s" % (self.dc1,
+ self.cmdline_creds))
+ self.assertTrue("Current DSA options:" in out)
+
+ def test_samba_tool_replicate(self):
+ """Tests 'samba-tool drs replicate' command
+ Output should be like 'Replicate from <DC-SRC> to <DC-DEST> was successful.'"""
+ nc_name = self._get_rootDSE(self.dc1)["defaultNamingContext"]
+ out = self.check_output("samba-tool drs replicate %s %s %s %s" % (self.dc1,
+ self.dc2,
+ nc_name,
+ self.cmdline_creds))
+ self.assertTrue("Replicate from" in out)
+ self.assertTrue("was successful" in out)
diff --git a/source4/scripting/python/samba/tests/core.py b/source4/scripting/python/samba/tests/core.py
new file mode 100644
index 0000000000..1c3d7dbe44
--- /dev/null
+++ b/source4/scripting/python/samba/tests/core.py
@@ -0,0 +1,65 @@
+#!/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/>.
+#
+
+"""Samba Python tests."""
+
+import ldb
+import os
+import samba
+from samba.tests import TestCase, TestCaseInTempDir
+
+class SubstituteVarTestCase(TestCase):
+
+ def test_empty(self):
+ self.assertEquals("", samba.substitute_var("", {}))
+
+ def test_nothing(self):
+ self.assertEquals("foo bar",
+ samba.substitute_var("foo bar", {"bar": "bla"}))
+
+ def test_replace(self):
+ self.assertEquals("foo bla",
+ samba.substitute_var("foo ${bar}", {"bar": "bla"}))
+
+ def test_broken(self):
+ self.assertEquals("foo ${bdkjfhsdkfh sdkfh ",
+ samba.substitute_var("foo ${bdkjfhsdkfh sdkfh ", {"bar": "bla"}))
+
+ def test_unknown_var(self):
+ self.assertEquals("foo ${bla} gsff",
+ samba.substitute_var("foo ${bla} gsff", {"bar": "bla"}))
+
+ def test_check_all_substituted(self):
+ samba.check_all_substituted("nothing to see here")
+ self.assertRaises(Exception, samba.check_all_substituted,
+ "Not subsituted: ${FOOBAR}")
+
+
+class LdbExtensionTests(TestCaseInTempDir):
+
+ def test_searchone(self):
+ path = self.tempdir + "/searchone.ldb"
+ l = samba.Ldb(path)
+ try:
+ l.add({"dn": "foo=dc", "bar": "bla"})
+ self.assertEquals("bla",
+ l.searchone(basedn=ldb.Dn(l, "foo=dc"), attribute="bar"))
+ finally:
+ del l
+ os.unlink(path)
diff --git a/source4/scripting/python/samba/tests/credentials.py b/source4/scripting/python/samba/tests/credentials.py
new file mode 100644
index 0000000000..5ed61c6b21
--- /dev/null
+++ b/source4/scripting/python/samba/tests/credentials.py
@@ -0,0 +1,100 @@
+#!/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.
+
+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 import credentials
+import samba.tests
+
+class CredentialsTests(samba.tests.TestCase):
+
+ def setUp(self):
+ super(CredentialsTests, self).setUp()
+ self.creds = credentials.Credentials()
+
+ def test_set_username(self):
+ self.creds.set_username("somebody")
+ self.assertEquals("somebody", self.creds.get_username())
+
+ def test_set_password(self):
+ self.creds.set_password("S3CreT")
+ self.assertEquals("S3CreT", self.creds.get_password())
+
+ def test_set_domain(self):
+ self.creds.set_domain("ABMAS")
+ self.assertEquals("ABMAS", self.creds.get_domain())
+
+ def test_set_realm(self):
+ self.creds.set_realm("myrealm")
+ self.assertEquals("MYREALM", self.creds.get_realm())
+
+ def test_parse_string_anon(self):
+ self.creds.parse_string("%")
+ self.assertEquals("", self.creds.get_username())
+ self.assertEquals(None, self.creds.get_password())
+
+ def test_parse_string_user_pw_domain(self):
+ self.creds.parse_string("dom\\someone%secr")
+ self.assertEquals("someone", self.creds.get_username())
+ self.assertEquals("secr", self.creds.get_password())
+ self.assertEquals("DOM", self.creds.get_domain())
+
+ def test_bind_dn(self):
+ self.assertEquals(None, self.creds.get_bind_dn())
+ self.creds.set_bind_dn("dc=foo,cn=bar")
+ self.assertEquals("dc=foo,cn=bar", self.creds.get_bind_dn())
+
+ def test_is_anon(self):
+ self.creds.set_username("")
+ self.assertTrue(self.creds.is_anonymous())
+ self.creds.set_username("somebody")
+ self.assertFalse(self.creds.is_anonymous())
+ self.creds.set_anonymous()
+ self.assertTrue(self.creds.is_anonymous())
+
+ def test_workstation(self):
+ # FIXME: This is uninitialised, it should be None
+ #self.assertEquals(None, self.creds.get_workstation())
+ self.creds.set_workstation("myworksta")
+ self.assertEquals("myworksta", self.creds.get_workstation())
+
+ def test_get_nt_hash(self):
+ self.creds.set_password("geheim")
+ self.assertEquals('\xc2\xae\x1f\xe6\xe6H\x84cRE>\x81o*\xeb\x93',
+ self.creds.get_nt_hash())
+
+ def test_guess(self):
+ # Just check the method is there and doesn't raise an exception
+ self.creds.guess()
+
+ def test_set_cmdline_callbacks(self):
+ self.creds.set_cmdline_callbacks()
+
+ def test_authentication_requested(self):
+ self.creds.set_username("")
+ self.assertFalse(self.creds.authentication_requested())
+ self.creds.set_username("somebody")
+ self.assertTrue(self.creds.authentication_requested())
+
+ def test_wrong_password(self):
+ self.assertFalse(self.creds.wrong_password())
diff --git a/source4/scripting/python/samba/tests/dcerpc/__init__.py b/source4/scripting/python/samba/tests/dcerpc/__init__.py
index c64c9dc9f6..0f9625570a 100644
--- a/source4/scripting/python/samba/tests/dcerpc/__init__.py
+++ b/source4/scripting/python/samba/tests/dcerpc/__init__.py
@@ -1,14 +1,14 @@
-#!/usr/bin/python
+#!/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
@@ -16,5 +16,6 @@
#
# 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 DCE/RPC Python bindings."""
diff --git a/source4/scripting/python/samba/tests/dcerpc/bare.py b/source4/scripting/python/samba/tests/dcerpc/bare.py
index 6cadad89f1..22163697f8 100644
--- a/source4/scripting/python/samba/tests/dcerpc/bare.py
+++ b/source4/scripting/python/samba/tests/dcerpc/bare.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Unix SMB/CIFS implementation.
@@ -18,32 +18,35 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
-from samba.dcerpc import ClientConnection
-from unittest import TestCase
-from samba.tests import cmdline_loadparm
+"""Tests for samba.dcerpc.bare."""
+from samba.dcerpc import ClientConnection
+import samba.tests
-class BareTestCase(TestCase):
+class BareTestCase(samba.tests.TestCase):
def test_bare(self):
# Connect to the echo pipe
x = ClientConnection("ncalrpc:localhost[DEFAULT]",
- ("60a15ec5-4de8-11d7-a637-005056a20182", 1), lp_ctx=cmdline_loadparm)
+ ("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]",
- ("12345778-1234-abcd-ef00-0123456789ac", 1), lp_ctx=cmdline_loadparm)
+ ("12345778-1234-abcd-ef00-0123456789ac", 1),
+ lp_ctx=samba.tests.env_loadparm())
y = ClientConnection("ncalrpc:localhost",
("60a15ec5-4de8-11d7-a637-005056a20182", 1),
- basis_connection=x, lp_ctx=cmdline_loadparm)
+ 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), lp_ctx=cmdline_loadparm)
+ ("60a15ec5-4de8-11d7-a637-005056a20182", 1),
+ lp_ctx=samba.tests.env_loadparm())
y = ClientConnection("ncalrpc:localhost",
("60a15ec5-4de8-11d7-a637-005056a20182", 1),
- basis_connection=x, lp_ctx=cmdline_loadparm)
+ 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/misc.py b/source4/scripting/python/samba/tests/dcerpc/misc.py
index ab76efc536..37a647a842 100644
--- a/source4/scripting/python/samba/tests/dcerpc/misc.py
+++ b/source4/scripting/python/samba/tests/dcerpc/misc.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007
@@ -17,13 +17,15 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
-import unittest
+"""Tests for samba.dcerpc.misc."""
+
from samba.dcerpc import misc
+import samba.tests
text1 = "76f53846-a7c2-476a-ae2c-20e2b80d7b34"
text2 = "344edffa-330a-4b39-b96e-2c34da52e8b1"
-class GUIDTests(unittest.TestCase):
+class GUIDTests(samba.tests.TestCase):
def test_str(self):
guid = misc.GUID(text1)
@@ -43,9 +45,9 @@ class GUIDTests(unittest.TestCase):
guid2 = misc.GUID(text1)
self.assertEquals(0, cmp(guid1, guid2))
self.assertEquals(guid1, guid2)
-
-
-class PolicyHandleTests(unittest.TestCase):
+
+
+class PolicyHandleTests(samba.tests.TestCase):
def test_init(self):
x = misc.policy_handle(text1, 1)
diff --git a/source4/scripting/python/samba/tests/dcerpc/registry.py b/source4/scripting/python/samba/tests/dcerpc/registry.py
index f1cd83790d..f0716d5ffb 100644
--- a/source4/scripting/python/samba/tests/dcerpc/registry.py
+++ b/source4/scripting/python/samba/tests/dcerpc/registry.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2008
@@ -17,6 +17,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
+"""Tests for samba.dcerpc.registry."""
+
from samba.dcerpc import winreg
from samba.tests import RpcInterfaceTestCase
@@ -24,6 +26,7 @@ from samba.tests import RpcInterfaceTestCase
class WinregTests(RpcInterfaceTestCase):
def setUp(self):
+ super(WinregTests, self).setUp()
self.conn = winreg.winreg("ncalrpc:", self.get_loadparm(),
self.get_credentials())
diff --git a/source4/scripting/python/samba/tests/dcerpc/rpc_talloc.py b/source4/scripting/python/samba/tests/dcerpc/rpc_talloc.py
new file mode 100755
index 0000000000..fe0f93ef9b
--- /dev/null
+++ b/source4/scripting/python/samba/tests/dcerpc/rpc_talloc.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+# test generated python code from pidl
+# Andrew Tridgell August 2010
+#
+# to run this test, use one of these:
+#
+# python -m testtools.run samba.tests.dcerpc.rpc_talloc
+#
+# or if you have trial installed (from twisted), use
+#
+# trial samba.tests.dcerpc.rpc_talloc
+
+"""Tests for the talloc handling in the generated Python DCE/RPC bindings."""
+
+import sys
+
+sys.path.insert(0, "bin/python")
+
+import samba
+import samba.tests
+from samba.dcerpc import drsuapi
+import talloc
+
+talloc.enable_null_tracking()
+
+class TallocTests(samba.tests.TestCase):
+ '''test talloc behaviour of pidl generated python code'''
+
+ def check_blocks(self, object, num_expected):
+ '''check that the number of allocated blocks is correct'''
+ nblocks = talloc.total_blocks(object)
+ if object is None:
+ nblocks -= self.initial_blocks
+ self.assertEquals(nblocks, num_expected)
+
+ def get_rodc_partial_attribute_set(self):
+ '''get a list of attributes for RODC replication'''
+ partial_attribute_set = drsuapi.DsPartialAttributeSet()
+
+ # we expect one block for the object, and one for the structure
+ self.check_blocks(partial_attribute_set, 2)
+
+ attids = [ 1, 2, 3]
+ partial_attribute_set.version = 1
+ partial_attribute_set.attids = attids
+ partial_attribute_set.num_attids = len(attids)
+
+ # we expect one block object, a structure, an ARRAY, and a
+ # reference to the array
+ self.check_blocks(partial_attribute_set, 3)
+
+ return partial_attribute_set
+
+ def pas_test(self):
+ pas = self.get_rodc_partial_attribute_set()
+ self.check_blocks(pas, 3)
+ req8 = drsuapi.DsGetNCChangesRequest8()
+ self.check_blocks(req8, 2)
+ self.check_blocks(None, 5)
+ req8.partial_attribute_set = pas
+ if req8.partial_attribute_set.attids[1] != 2:
+ raise Exception("Wrong value in attids[2]")
+ # we now get an additional reference
+ self.check_blocks(None, 6)
+
+ def test_run(self):
+ self.initial_blocks = talloc.total_blocks(None)
+ self.check_blocks(None, 0)
+ self.pas_test()
+ self.check_blocks(None, 0)
diff --git a/source4/scripting/python/samba/tests/dcerpc/rpcecho.py b/source4/scripting/python/samba/tests/dcerpc/rpcecho.py
index 72eb87d750..ba7ad9688b 100644
--- a/source4/scripting/python/samba/tests/dcerpc/rpcecho.py
+++ b/source4/scripting/python/samba/tests/dcerpc/rpcecho.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2008
@@ -17,15 +17,17 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
+"""Tests for samba.dceprc.rpcecho."""
+
from samba.dcerpc import echo
from samba.ndr import ndr_pack, ndr_unpack
-import unittest
-from samba.tests import RpcInterfaceTestCase
+from samba.tests import RpcInterfaceTestCase, TestCase
class RpcEchoTests(RpcInterfaceTestCase):
def setUp(self):
+ super(RpcEchoTests, self).setUp()
self.conn = echo.rpcecho("ncalrpc:", self.get_loadparm())
def test_two_contexts(self):
@@ -33,7 +35,7 @@ class RpcEchoTests(RpcInterfaceTestCase):
self.assertEquals(3, self.conn2.AddOne(2))
def test_abstract_syntax(self):
- self.assertEquals(("60a15ec5-4de8-11d7-a637-005056a20182", 1),
+ self.assertEquals(("60a15ec5-4de8-11d7-a637-005056a20182", 1),
self.conn.abstract_syntax)
def test_addone(self):
@@ -59,7 +61,7 @@ class RpcEchoTests(RpcInterfaceTestCase):
self.assertEquals(None, self.conn.server_name)
-class NdrEchoTests(unittest.TestCase):
+class NdrEchoTests(TestCase):
def test_info1_push(self):
x = echo.info1()
diff --git a/source4/scripting/python/samba/tests/dcerpc/sam.py b/source4/scripting/python/samba/tests/dcerpc/sam.py
index 9532333a00..38b381d3aa 100644
--- a/source4/scripting/python/samba/tests/dcerpc/sam.py
+++ b/source4/scripting/python/samba/tests/dcerpc/sam.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Unix SMB/CIFS implementation.
@@ -18,6 +18,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
+"""Tests for samba.dcerpc.sam."""
+
from samba.dcerpc import samr, security
from samba.tests import RpcInterfaceTestCase
@@ -32,6 +34,7 @@ def toArray((handle, array, num_entries)):
class SamrTests(RpcInterfaceTestCase):
def setUp(self):
+ super(SamrTests, self).setUp()
self.conn = samr.samr("ncalrpc:", self.get_loadparm())
def test_connect5(self):
@@ -39,6 +42,7 @@ class SamrTests(RpcInterfaceTestCase):
def test_connect2(self):
handle = self.conn.Connect2(None, security.SEC_FLAG_MAXIMUM_ALLOWED)
+ self.assertTrue(handle is not None)
def test_EnumDomains(self):
handle = self.conn.Connect2(None, security.SEC_FLAG_MAXIMUM_ALLOWED)
diff --git a/source4/scripting/python/samba/tests/dcerpc/testrpc.py b/source4/scripting/python/samba/tests/dcerpc/testrpc.py
new file mode 100644
index 0000000000..2f3a6a0aef
--- /dev/null
+++ b/source4/scripting/python/samba/tests/dcerpc/testrpc.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python
+#
+# test generated python code from pidl
+# Andrew Tridgell August 2010
+#
+import sys
+
+sys.path.insert(0, "bin/python")
+
+import samba
+import samba.tests
+from samba.dcerpc import drsuapi
+import talloc
+
+talloc.enable_null_tracking()
+
+class RpcTests(object):
+ '''test type behaviour of pidl generated python RPC code'''
+
+ def check_blocks(self, object, num_expected):
+ '''check that the number of allocated blocks is correct'''
+ nblocks = talloc.total_blocks(object)
+ if object is None:
+ nblocks -= self.initial_blocks
+ leaked_blocks = (nblocks - num_expected)
+ if leaked_blocks != 0:
+ print "Leaked %d blocks" % leaked_blocks
+
+ def check_type(self, interface, typename, type):
+ print "Checking type %s" % typename
+ v = type()
+ for n in dir(v):
+ if n[0] == '_':
+ continue
+ try:
+ value = getattr(v, n)
+ except TypeError, errstr:
+ if str(errstr) == "unknown union level":
+ print "ERROR: Unknown union level in %s.%s" % (typename, n)
+ self.errcount += 1
+ continue
+ print str(errstr)[1:21]
+ if str(errstr)[0:21] == "Can not convert C Type":
+ print "ERROR: Unknown C type for %s.%s" % (typename, n)
+ self.errcount += 1
+ continue
+ else:
+ print "ERROR: Failed to instantiate %s.%s" % (typename, n)
+ self.errcount += 1
+ continue
+ except:
+ print "ERROR: Failed to instantiate %s.%s" % (typename, n)
+ self.errcount += 1
+ continue
+
+ # now try setting the value back
+ try:
+ print "Setting %s.%s" % (typename, n)
+ setattr(v, n, value)
+ except Exception, e:
+ if isinstance(e, AttributeError) and str(e).endswith("is read-only"):
+ # readonly, ignore
+ continue
+ else:
+ print "ERROR: Failed to set %s.%s: %r: %s" % (typename, n, e.__class__, e)
+ self.errcount += 1
+ continue
+
+ # and try a comparison
+ try:
+ if value != getattr(v, n):
+ print "ERROR: Comparison failed for %s.%s: %r != %r" % (typename, n, value, getattr(v, n))
+ continue
+ except Exception, e:
+ print "ERROR: compare exception for %s.%s: %r: %s" % (typename, n, e.__class__, e)
+ continue
+
+ def check_interface(self, interface, iname):
+ errcount = self.errcount
+ for n in dir(interface):
+ if n[0] == '_' or n == iname:
+ # skip the special ones
+ continue
+ value = getattr(interface, n)
+ if isinstance(value, str):
+ #print "%s=\"%s\"" % (n, value)
+ pass
+ elif isinstance(value, int) or isinstance(value, long):
+ #print "%s=%d" % (n, value)
+ pass
+ elif isinstance(value, type):
+ try:
+ initial_blocks = talloc.total_blocks(None)
+ self.check_type(interface, n, value)
+ self.check_blocks(None, initial_blocks)
+ except Exception, e:
+ print "ERROR: Failed to check_type %s.%s: %r: %s" % (iname, n, e.__class__, e)
+ self.errcount += 1
+ elif callable(value):
+ pass # Method
+ else:
+ print "UNKNOWN: %s=%s" % (n, value)
+ if self.errcount - errcount != 0:
+ print "Found %d errors in %s" % (self.errcount - errcount, iname)
+
+ def check_all_interfaces(self):
+ for iname in dir(samba.dcerpc):
+ if iname[0] == '_':
+ continue
+ if iname == 'ClientConnection' or iname == 'base':
+ continue
+ print "Checking interface %s" % iname
+ iface = getattr(samba.dcerpc, iname)
+ initial_blocks = talloc.total_blocks(None)
+ self.check_interface(iface, iname)
+ self.check_blocks(None, initial_blocks)
+
+ def run(self):
+ self.initial_blocks = talloc.total_blocks(None)
+ self.errcount = 0
+ self.check_all_interfaces()
+ return self.errcount
+
+tests = RpcTests()
+errcount = tests.run()
+if errcount == 0:
+ sys.exit(0)
+else:
+ print "%d failures" % errcount
+ sys.exit(1)
diff --git a/source4/scripting/python/samba/tests/dcerpc/unix.py b/source4/scripting/python/samba/tests/dcerpc/unix.py
index 62169ad12c..16bf37e749 100644
--- a/source4/scripting/python/samba/tests/dcerpc/unix.py
+++ b/source4/scripting/python/samba/tests/dcerpc/unix.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2008
@@ -17,22 +17,35 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
+"""Tests for samba.dcerpc.unixinfo."""
+
+
from samba.dcerpc import unixinfo
from samba.tests import RpcInterfaceTestCase
class UnixinfoTests(RpcInterfaceTestCase):
def setUp(self):
+ super(UnixinfoTests, self).setUp()
self.conn = unixinfo.unixinfo("ncalrpc:", self.get_loadparm())
- def test_getpwuid(self):
+ def test_getpwuid_int(self):
infos = self.conn.GetPWUid(range(512))
self.assertEquals(512, len(infos))
self.assertEquals("/bin/false", infos[0].shell)
self.assertTrue(isinstance(infos[0].homedir, unicode))
+ def test_getpwuid(self):
+ infos = self.conn.GetPWUid(map(long, range(512)))
+ self.assertEquals(512, len(infos))
+ self.assertEquals("/bin/false", infos[0].shell)
+ self.assertTrue(isinstance(infos[0].homedir, unicode))
+
def test_gidtosid(self):
- self.conn.GidToSid(1000)
+ self.conn.GidToSid(1000L)
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/dsdb.py b/source4/scripting/python/samba/tests/dsdb.py
new file mode 100644
index 0000000000..d4331f3ef5
--- /dev/null
+++ b/source4/scripting/python/samba/tests/dsdb.py
@@ -0,0 +1,135 @@
+#!/usr/bin/env python
+
+# Unix SMB/CIFS implementation. Tests for dsdb
+# Copyright (C) Matthieu Patou <mat@matws.net> 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 samba.dsdb."""
+
+from samba.credentials import Credentials
+from samba.samdb import SamDB
+from samba.auth import system_session
+from testtools.testcase import TestCase
+from samba.ndr import ndr_unpack, ndr_pack
+from samba.dcerpc import drsblobs
+import ldb
+import os
+import samba
+
+
+class DsdbTests(TestCase):
+
+
+ def setUp(self):
+ super(DsdbTests, self).setUp()
+ self.lp = samba.param.LoadParm()
+ self.lp.load(os.path.join(os.path.join(self.baseprovpath(), "etc"), "smb.conf"))
+ self.creds = Credentials()
+ self.creds.guess(self.lp)
+ self.session = system_session()
+ 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")
+
+ def test_error_replpropertymetadata(self):
+ res = self.samdb.search(expression="cn=Administrator",
+ scope=ldb.SCOPE_SUBTREE,
+ attrs=["replPropertyMetaData"])
+ repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
+ str(res[0]["replPropertyMetaData"]))
+ ctr = repl.ctr
+ for o in ctr.array:
+ # Search for Description
+ if o.attid == 13:
+ old_version = o.version
+ o.version = o.version + 1
+ replBlob = ndr_pack(repl)
+ msg = ldb.Message()
+ msg.dn = res[0].dn
+ msg["replPropertyMetaData"] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, "replPropertyMetaData")
+ self.assertRaises(ldb.LdbError, self.samdb.modify, msg, ["local_oid:1.3.6.1.4.1.7165.4.3.14:0"])
+
+ def test_twoatt_replpropertymetadata(self):
+ res = self.samdb.search(expression="cn=Administrator",
+ scope=ldb.SCOPE_SUBTREE,
+ attrs=["replPropertyMetaData", "uSNChanged"])
+ repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
+ str(res[0]["replPropertyMetaData"]))
+ ctr = repl.ctr
+ for o in ctr.array:
+ # Search for Description
+ if o.attid == 13:
+ old_version = o.version
+ o.version = o.version + 1
+ o.local_usn = long(str(res[0]["uSNChanged"])) + 1
+ replBlob = ndr_pack(repl)
+ msg = ldb.Message()
+ msg.dn = res[0].dn
+ msg["replPropertyMetaData"] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, "replPropertyMetaData")
+ msg["description"] = ldb.MessageElement("new val", ldb.FLAG_MOD_REPLACE, "description")
+ self.assertRaises(ldb.LdbError, self.samdb.modify, msg, ["local_oid:1.3.6.1.4.1.7165.4.3.14:0"])
+
+ def test_set_replpropertymetadata(self):
+ res = self.samdb.search(expression="cn=Administrator",
+ scope=ldb.SCOPE_SUBTREE,
+ attrs=["replPropertyMetaData", "uSNChanged"])
+ repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
+ str(res[0]["replPropertyMetaData"]))
+ ctr = repl.ctr
+ for o in ctr.array:
+ # Search for Description
+ if o.attid == 13:
+ old_version = o.version
+ o.version = o.version + 1
+ o.local_usn = long(str(res[0]["uSNChanged"])) + 1
+ o.originating_usn = long(str(res[0]["uSNChanged"])) + 1
+ replBlob = ndr_pack(repl)
+ msg = ldb.Message()
+ msg.dn = res[0].dn
+ msg["replPropertyMetaData"] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, "replPropertyMetaData")
+ self.samdb.modify(msg, ["local_oid:1.3.6.1.4.1.7165.4.3.14:0"])
+
+ def test_ok_get_attribute_from_attid(self):
+ self.assertEquals(self.samdb.get_attribute_from_attid(13), "description")
+
+ def test_ko_get_attribute_from_attid(self):
+ self.assertEquals(self.samdb.get_attribute_from_attid(11979), None)
+
+ def test_get_attribute_replmetadata_version(self):
+ res = self.samdb.search(expression="cn=Administrator",
+ scope=ldb.SCOPE_SUBTREE,
+ attrs=["dn"])
+ self.assertEquals(len(res), 1)
+ dn = str(res[0].dn)
+ self.assertEqual(self.samdb.get_attribute_replmetadata_version(dn, "unicodePwd"), 1)
+
+ def test_set_attribute_replmetadata_version(self):
+ res = self.samdb.search(expression="cn=Administrator",
+ scope=ldb.SCOPE_SUBTREE,
+ attrs=["dn"])
+ self.assertEquals(len(res), 1)
+ dn = str(res[0].dn)
+ version = self.samdb.get_attribute_replmetadata_version(dn, "description")
+ self.samdb.set_attribute_replmetadata_version(dn, "description", version + 2)
+ self.assertEqual(self.samdb.get_attribute_replmetadata_version(dn, "description"), version + 2)
diff --git a/source4/scripting/python/samba/tests/gensec.py b/source4/scripting/python/samba/tests/gensec.py
new file mode 100644
index 0000000000..ddca0df980
--- /dev/null
+++ b/source4/scripting/python/samba/tests/gensec.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+
+# Unix SMB/CIFS implementation.
+# 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/>.
+#
+
+"""Tests for GENSEC.
+
+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
+import samba.tests
+
+class GensecTests(samba.tests.TestCase):
+
+ def setUp(self):
+ super(GensecTests, self).setUp()
+ self.settings = {}
+ self.settings["lp_ctx"] = self.lp_ctx = samba.tests.env_loadparm()
+ self.settings["target_hostname"] = self.lp_ctx.get("netbios name")
+ """This is just for the API tests"""
+ self.gensec = gensec.Security.start_client(self.settings)
+
+ def test_start_mech_by_unknown_name(self):
+ self.assertRaises(RuntimeError, self.gensec.start_mech_by_name, "foo")
+
+ def test_start_mech_by_name(self):
+ self.gensec.start_mech_by_name("spnego")
+
+ def test_info_uninitialized(self):
+ self.assertRaises(RuntimeError, self.gensec.session_info)
+
+ def test_update(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_SEAL)
+ self.gensec_client.start_mech_by_sasl_name("GSSAPI")
+
+ self.gensec_server = gensec.Security.start_server(self.settings)
+ 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_SEAL)
+ self.gensec_server.start_mech_by_sasl_name("GSSAPI")
+
+ client_finished = False
+ server_finished = False
+ server_to_client = ""
+
+ """Run the actual call loop"""
+ while client_finished == False and server_finished == False:
+ if not client_finished:
+ print "running client gensec_update"
+ (client_finished, client_to_server) = self.gensec_client.update(server_to_client)
+ if not server_finished:
+ print "running server gensec_update"
+ (server_finished, server_to_client) = self.gensec_server.update(client_to_server)
+ 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)
+
diff --git a/source4/scripting/python/samba/tests/shares.py b/source4/scripting/python/samba/tests/hostconfig.py
index 9130c36780..78ca6202b2 100644
--- a/source4/scripting/python/samba/tests/shares.py
+++ b/source4/scripting/python/samba/tests/hostconfig.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
# Unix SMB/CIFS implementation. Tests for shares
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2009
@@ -16,8 +16,11 @@
# 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.shares import SharesContainer
-from unittest import TestCase
+
+"""Tests for samba.hostconfig."""
+
+from samba.hostconfig import SharesContainer
+from samba.tests import TestCase
class MockService(object):
@@ -37,9 +40,6 @@ class MockLoadParm(object):
def __getitem__(self, name):
return MockService(self.data[name])
- def __contains__(self, name):
- return name in self.data
-
def __len__(self):
return len(self.data)
diff --git a/source4/scripting/python/samba/tests/messaging.py b/source4/scripting/python/samba/tests/messaging.py
new file mode 100644
index 0000000000..d2a0b73775
--- /dev/null
+++ b/source4/scripting/python/samba/tests/messaging.py
@@ -0,0 +1,60 @@
+#!/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.messaging."""
+
+from samba.messaging import Messaging
+from samba.tests import TestCase
+
+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():
+ pass
+ msg_type = x.register(callback)
+ x.deregister(callback, msg_type)
+
+ def test_assign_server_id(self):
+ x = self.get_context()
+ self.assertTrue(isinstance(x.server_id, tuple))
+ self.assertEquals(3, len(x.server_id))
+
+ def test_ping_speed(self):
+ server_ctx = self.get_context((0, 1))
+ def ping_callback(src, data):
+ server_ctx.send(src, data)
+ def exit_callback():
+ print "received exit"
+ msg_ping = server_ctx.register(ping_callback)
+ msg_exit = server_ctx.register(exit_callback)
+
+ def pong_callback():
+ print "received pong"
+ client_ctx = self.get_context((0, 2))
+ msg_pong = client_ctx.register(pong_callback)
+
+ client_ctx.send((0, 1), msg_ping, "testing")
+ client_ctx.send((0, 1), msg_ping, "")
+
diff --git a/source4/scripting/python/samba/tests/netcmd.py b/source4/scripting/python/samba/tests/netcmd.py
new file mode 100644
index 0000000000..787bcd5a72
--- /dev/null
+++ b/source4/scripting/python/samba/tests/netcmd.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+
+# Unix SMB/CIFS implementation.
+# 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/>.
+#
+
+"""Tests for samba.netcmd."""
+
+from samba.netcmd import Command
+import samba.tests
+
+class CommandTests(samba.tests.TestCase):
+
+ 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)
diff --git a/source4/scripting/python/samba/tests/ntacls.py b/source4/scripting/python/samba/tests/ntacls.py
new file mode 100644
index 0000000000..2d8d6b9d67
--- /dev/null
+++ b/source4/scripting/python/samba/tests/ntacls.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python
+
+# Unix SMB/CIFS implementation. Tests for ntacls manipulation
+# Copyright (C) Matthieu Patou <mat@matws.net> 2009-2010
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+"""Tests for samba.ntacls."""
+
+from samba.ntacls import setntacl, getntacl, XattrBackendError
+from samba.dcerpc import xattr, security
+from samba.param import LoadParm
+from samba.tests import TestCase, TestSkipped
+import random
+import os
+
+class NtaclsTests(TestCase):
+
+ 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)
+
+ 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)
+ anysid = security.dom_sid(security.SID_NT_SELF)
+ self.assertEquals(facl.info.as_sddl(anysid),acl)
+ os.unlink(tempf)
+
+ 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"))
+ domsid=security.dom_sid(security.SID_NT_SELF)
+ self.assertEquals(facl.info.as_sddl(domsid),acl)
+ os.unlink(tempf)
+
+ 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"))
+
+ 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,
+ "S-1-5-21-2212615479-2695158682-2101375467","native")
+ os.unlink(tempf)
diff --git a/source4/scripting/python/samba/tests/param.py b/source4/scripting/python/samba/tests/param.py
new file mode 100644
index 0000000000..7848e1c23b
--- /dev/null
+++ b/source4/scripting/python/samba/tests/param.py
@@ -0,0 +1,59 @@
+#!/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.param."""
+
+from samba import param
+import samba.tests
+
+class LoadParmTestCase(samba.tests.TestCase):
+
+ def test_init(self):
+ file = param.LoadParm()
+ self.assertTrue(file is not None)
+
+ def test_length(self):
+ file = param.LoadParm()
+ self.assertEquals(0, len(file))
+
+ def test_set_workgroup(self):
+ file = param.LoadParm()
+ file.set("workgroup", "bla")
+ self.assertEquals("BLA", file.get("workgroup"))
+
+ def test_is_mydomain(self):
+ file = param.LoadParm()
+ file.set("workgroup", "bla")
+ self.assertTrue(file.is_mydomain("BLA"))
+ self.assertFalse(file.is_mydomain("FOOBAR"))
+
+ def test_is_myname(self):
+ file = param.LoadParm()
+ file.set("netbios name", "bla")
+ self.assertTrue(file.is_myname("BLA"))
+ self.assertFalse(file.is_myname("FOOBAR"))
+
+ def test_load_default(self):
+ file = param.LoadParm()
+ file.load_default()
+
+ def test_section_nonexistant(self):
+ samba_lp = param.LoadParm()
+ samba_lp.load_default()
+ self.assertRaises(KeyError, samba_lp.__getitem__, "nonexistant")
diff --git a/source4/scripting/python/samba/tests/provision.py b/source4/scripting/python/samba/tests/provision.py
index f34073504c..39a01606bb 100644
--- a/source4/scripting/python/samba/tests/provision.py
+++ b/source4/scripting/python/samba/tests/provision.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
@@ -17,26 +17,44 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
+"""Tests for samba.provision."""
+
import os
-from samba.provision import setup_secretsdb, findnss
+from samba.provision import setup_secretsdb, findnss, ProvisionPaths
import samba.tests
-from ldb import Dn
-from samba import param
-import unittest
+from samba.tests import env_loadparm, TestCase
-lp = samba.tests.cmdline_loadparm
+def create_dummy_secretsdb(path, lp=None):
+ """Create a dummy secrets database for use in tests.
-setup_dir = "setup"
-def setup_path(file):
- return os.path.join(setup_dir, file)
+ :param path: Path to store the secrets db
+ :param lp: Optional loadparm context. A simple one will
+ be generated if not specified.
+ """
+ if lp is None:
+ lp = env_loadparm()
+ paths = ProvisionPaths()
+ paths.secrets = path
+ paths.private_dir = os.path.dirname(path)
+ paths.keytab = "no.keytab"
+ paths.dns_keytab = "no.dns.keytab"
+ secrets_ldb = setup_secretsdb(paths, None, None, lp=lp)
+ secrets_ldb.transaction_commit()
+ return secrets_ldb
class ProvisionTestCase(samba.tests.TestCaseInTempDir):
"""Some simple tests for individual functions in the provisioning code.
"""
+
def test_setup_secretsdb(self):
path = os.path.join(self.tempdir, "secrets.ldb")
- ldb = setup_secretsdb(path, setup_path, None, None, lp=lp)
+ paths = ProvisionPaths()
+ paths.secrets = path
+ paths.private_dir = os.path.dirname(path)
+ paths.keytab = "no.keytab"
+ paths.dns_keytab = "no.dns.keytab"
+ ldb = setup_secretsdb(paths, None, None, lp=env_loadparm())
try:
self.assertEquals("LSA Secrets",
ldb.searchone(basedn="CN=LSA Secrets", attribute="CN"))
@@ -45,8 +63,9 @@ class ProvisionTestCase(samba.tests.TestCaseInTempDir):
os.unlink(path)
-class FindNssTests(unittest.TestCase):
+class FindNssTests(TestCase):
"""Test findnss() function."""
+
def test_nothing(self):
def x(y):
raise KeyError
@@ -64,6 +83,7 @@ class FindNssTests(unittest.TestCase):
class Disabled(object):
+
def test_setup_templatesdb(self):
raise NotImplementedError(self.test_setup_templatesdb)
@@ -94,7 +114,4 @@ class Disabled(object):
def test_vampire(self):
raise NotImplementedError(self.test_vampire)
- def test_erase_partitions(self):
- raise NotImplementedError(self.test_erase_partitions)
-
diff --git a/source4/scripting/python/samba/tests/registry.py b/source4/scripting/python/samba/tests/registry.py
new file mode 100644
index 0000000000..97926850d2
--- /dev/null
+++ b/source4/scripting/python/samba/tests/registry.py
@@ -0,0 +1,62 @@
+#!/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.registry."""
+
+import os
+from samba import registry
+import samba.tests
+
+class HelperTests(samba.tests.TestCase):
+
+ def test_predef_to_name(self):
+ self.assertEquals("HKEY_LOCAL_MACHINE",
+ registry.get_predef_name(0x80000002))
+
+ def test_str_regtype(self):
+ self.assertEquals("REG_DWORD", registry.str_regtype(4))
+
+
+
+class HiveTests(samba.tests.TestCaseInTempDir):
+
+ def setUp(self):
+ super(HiveTests, self).setUp()
+ self.hive_path = os.path.join(self.tempdir, "ldb_new.ldb")
+ self.hive = registry.open_ldb(self.hive_path)
+
+ def tearDown(self):
+ del self.hive
+ os.unlink(self.hive_path)
+ super(HiveTests, self).tearDown()
+
+ def test_ldb_new(self):
+ self.assertTrue(self.hive is not None)
+
+ #def test_flush(self):
+ # self.hive.flush()
+
+ #def test_del_value(self):
+ # self.hive.del_value("FOO")
+
+
+class RegistryTests(samba.tests.TestCase):
+
+ def test_new(self):
+ self.registry = registry.Registry()
diff --git a/source4/scripting/python/samba/tests/samba3.py b/source4/scripting/python/samba/tests/samba3.py
index 71e08bdd7f..3a4b851c75 100644
--- a/source4/scripting/python/samba/tests/samba3.py
+++ b/source4/scripting/python/samba/tests/samba3.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007
@@ -17,20 +17,30 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
-import unittest
-from samba.samba3 import GroupMappingDatabase, Registry, PolicyDatabase, SecretsDatabase, TdbSam
-from samba.samba3 import WinsDatabase, SmbpasswdFile, ACB_NORMAL, IdmapDatabase, SAMUser, ParamFile
+"""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
import os
-DATADIR=os.path.join(os.path.dirname(__file__), "../../../../../testdata/samba3")
-print "Samba 3 data dir: %s" % DATADIR
+for p in [ "../../../../../testdata/samba3", "../../../../testdata/samba3" ]:
+ DATADIR = os.path.join(os.path.dirname(__file__), p)
+ if os.path.exists(DATADIR):
+ break
+
+
+class RegistryTestCase(TestCase):
-class RegistryTestCase(unittest.TestCase):
def setUp(self):
+ super(RegistryTestCase, self).setUp()
self.registry = Registry(os.path.join(DATADIR, "registry.tdb"))
def tearDown(self):
self.registry.close()
+ super(RegistryTestCase, self).tearDown()
def test_length(self):
self.assertEquals(28, len(self.registry))
@@ -47,8 +57,10 @@ class RegistryTestCase(unittest.TestCase):
self.registry.values("HKLM/SYSTEM/CURRENTCONTROLSET/SERVICES/EVENTLOG"))
-class PolicyTestCase(unittest.TestCase):
+class PolicyTestCase(TestCase):
+
def setUp(self):
+ super(PolicyTestCase, self).setUp()
self.policy = PolicyDatabase(os.path.join(DATADIR, "account_policy.tdb"))
def test_policy(self):
@@ -64,12 +76,15 @@ class PolicyTestCase(unittest.TestCase):
self.assertEquals(self.policy.bad_lockout_minutes, None)
-class GroupsTestCase(unittest.TestCase):
+class GroupsTestCase(TestCase):
+
def setUp(self):
+ super(GroupsTestCase, self).setUp()
self.groupdb = GroupMappingDatabase(os.path.join(DATADIR, "group_mapping.tdb"))
def tearDown(self):
self.groupdb.close()
+ super(GroupsTestCase, self).tearDown()
def test_group_length(self):
self.assertEquals(13, len(list(self.groupdb.groupsids())))
@@ -85,23 +100,29 @@ class GroupsTestCase(unittest.TestCase):
self.assertEquals(0, len(list(self.groupdb.aliases())))
-class SecretsDbTestCase(unittest.TestCase):
+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(unittest.TestCase):
+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())))
@@ -140,8 +161,10 @@ class TdbSamTestCase(unittest.TestCase):
self.assertEquals(user, other)
-class WinsDatabaseTestCase(unittest.TestCase):
+class WinsDatabaseTestCase(TestCase):
+
def setUp(self):
+ super(WinsDatabaseTestCase, self).setUp()
self.winsdb = WinsDatabase(os.path.join(DATADIR, "wins.dat"))
def test_length(self):
@@ -152,10 +175,13 @@ class WinsDatabaseTestCase(unittest.TestCase):
def tearDown(self):
self.winsdb.close()
+ super(WinsDatabaseTestCase, self).tearDown()
-class SmbpasswdTestCase(unittest.TestCase):
+class SmbpasswdTestCase(TestCase):
+
def setUp(self):
+ super(SmbpasswdTestCase, self).setUp()
self.samdb = SmbpasswdFile(os.path.join(DATADIR, "smbpasswd"))
def test_length(self):
@@ -172,11 +198,15 @@ class SmbpasswdTestCase(unittest.TestCase):
def tearDown(self):
self.samdb.close()
+ super(SmbpasswdTestCase, self).tearDown()
+
+class IdmapDbTestCase(TestCase):
-class IdmapDbTestCase(unittest.TestCase):
def setUp(self):
- self.idmapdb = IdmapDatabase(os.path.join(DATADIR, "winbindd_idmap.tdb"))
+ super(IdmapDbTestCase, self).setUp()
+ self.idmapdb = IdmapDatabase(os.path.join(DATADIR,
+ "winbindd_idmap.tdb"))
def test_user_hwm(self):
self.assertEquals(10000, self.idmapdb.get_user_hwm())
@@ -198,19 +228,11 @@ class IdmapDbTestCase(unittest.TestCase):
def tearDown(self):
self.idmapdb.close()
+ super(IdmapDbTestCase, self).tearDown()
-class ShareInfoTestCase(unittest.TestCase):
- def setUp(self):
- self.shareinfodb = ShareInfoDatabase(os.path.join(DATADIR, "share_info.tdb"))
-
- # FIXME: needs proper data so it can be tested
-
- def tearDown(self):
- self.shareinfodb.close()
-
+class ParamTestCase(TestCase):
-class ParamTestCase(unittest.TestCase):
def test_init(self):
file = ParamFile()
self.assertTrue(file is not None)
diff --git a/source4/scripting/python/samba/tests/samba3sam.py b/source4/scripting/python/samba/tests/samba3sam.py
new file mode 100644
index 0000000000..a34f0f620c
--- /dev/null
+++ b/source4/scripting/python/samba/tests/samba3sam.py
@@ -0,0 +1,1096 @@
+#!/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/>.
+#
+
+"""Tests for the samba3sam LDB module, which maps Samba3 LDAP to AD LDAP."""
+
+import os
+import ldb
+from ldb import SCOPE_DEFAULT, SCOPE_BASE
+from samba import Ldb, substitute_var
+from samba.tests import TestCaseInTempDir, env_loadparm
+import samba.dcerpc.security
+import samba.ndr
+from samba.auth import system_session
+
+
+def read_datafile(filename):
+ paths = [ "../../../../../testdata/samba3",
+ "../../../../testdata/samba3" ]
+ for p in paths:
+ datadir = os.path.join(os.path.dirname(__file__), p)
+ if os.path.exists(datadir):
+ break
+ return open(os.path.join(datadir, filename), 'r').read()
+
+def ldb_debug(l, text):
+ print text
+
+
+class MapBaseTestCase(TestCaseInTempDir):
+ """Base test case for mapping tests."""
+
+ def setup_modules(self, ldb, s3, s4):
+ ldb.add({"dn": "@MAP=samba3sam",
+ "@FROM": s4.basedn,
+ "@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"})
+
+ ldb.add({"dn": "@PARTITION",
+ "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()
+
+ def make_dn(basedn, rdn):
+ return "%s,sambaDomainName=TESTS,%s" % (rdn, basedn)
+
+ def make_s4dn(basedn, rdn):
+ return "%s,%s" % (rdn, basedn)
+
+ self.ldbfile = os.path.join(self.tempdir, "test.ldb")
+ self.ldburl = "tdb://" + self.ldbfile
+
+ tempdir = self.tempdir
+
+ class Target:
+ """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.basedn = basedn
+ self.basedn_casefold = ldb.Dn(self.db, basedn).get_casefold()
+ self.substvars = {"BASEDN": self.basedn}
+ self.file = os.path.join(tempdir, "%s.ldb" % self.basedn_casefold)
+ self.url = "tdb://" + self.file
+ self._dn = dn
+
+ def dn(self, rdn):
+ return self._dn(self.basedn, rdn)
+
+ def connect(self):
+ return self.db.connect(self.url)
+
+ def setup_data(self, path):
+ self.add_ldif(read_datafile(path))
+
+ def subst(self, text):
+ return substitute_var(text, self.substvars)
+
+ def add_ldif(self, ldif):
+ self.db.add_ldif(self.subst(ldif))
+
+ def modify_ldif(self, ldif):
+ self.db.modify_ldif(self.subst(ldif))
+
+ self.samba4 = Target("dc=vernstok,dc=nl", make_s4dn, self.lp)
+ self.samba3 = Target("cn=Samba3Sam", make_dn, self.lp)
+
+ self.samba3.connect()
+ self.samba4.connect()
+
+ def tearDown(self):
+ os.unlink(self.ldbfile)
+ os.unlink(self.samba3.file)
+ os.unlink(self.samba4.file)
+ super(MapBaseTestCase, self).tearDown()
+
+ def assertSidEquals(self, text, ndr_sid):
+ sid_obj1 = samba.ndr.ndr_unpack(samba.dcerpc.security.dom_sid,
+ str(ndr_sid[0]))
+ sid_obj2 = samba.dcerpc.security.dom_sid(text)
+ self.assertEquals(sid_obj1, sid_obj2)
+
+
+class Samba3SamTestCase(MapBaseTestCase):
+
+ def setUp(self):
+ super(Samba3SamTestCase, self).setUp()
+ ldb = Ldb(self.ldburl, lp=self.lp, session_info=system_session())
+ 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())
+
+ def test_search_non_mapped(self):
+ """Looking up by non-mapped attribute"""
+ msg = self.ldb.search(expression="(cn=Administrator)")
+ self.assertEquals(len(msg), 1)
+ self.assertEquals(msg[0]["cn"], "Administrator")
+
+ def test_search_non_mapped(self):
+ """Looking up by mapped attribute"""
+ msg = self.ldb.search(expression="(name=Backup Operators)")
+ self.assertEquals(len(msg), 1)
+ self.assertEquals(str(msg[0]["name"]), "Backup Operators")
+
+ def test_old_name_of_renamed(self):
+ """Looking up by old name of renamed attribute"""
+ msg = self.ldb.search(expression="(displayName=Backup Operators)")
+ self.assertEquals(len(msg), 0)
+
+ def test_mapped_containing_sid(self):
+ """Looking up mapped entry containing SID"""
+ msg = self.ldb.search(expression="(cn=Replicator)")
+ self.assertEquals(len(msg), 1)
+ self.assertEquals(str(msg[0].dn),
+ "cn=Replicator,ou=Groups,dc=vernstok,dc=nl")
+ self.assertTrue("objectSid" in msg[0])
+ self.assertSidEquals("S-1-5-21-4231626423-2410014848-2360679739-552",
+ msg[0]["objectSid"])
+ oc = set(msg[0]["objectClass"])
+ self.assertEquals(oc, set(["group"]))
+
+ 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",
+ "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",
+ "showInAdvancedViewOnly": "TRUE"}
+ )
+
+ # Checking for existence of record (local)
+ # 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,
+ attrs=['foo','blah','cn','showInAdvancedViewOnly'])
+ self.assertEquals(len(msg), 1)
+ self.assertEquals(str(msg[0]["showInAdvancedViewOnly"]), "TRUE")
+ self.assertEquals(str(msg[0]["foo"]), "bar")
+ self.assertEquals(str(msg[0]["blah"]), "Blie")
+
+ # Adding record that will be mapped
+ self.ldb.add({"dn": "cn=Niemand,cn=Users,dc=vernstok,dc=nl",
+ "objectClass": "user",
+ "unixName": "bin",
+ "sambaUnicodePwd": "geheim",
+ "cn": "Niemand"})
+
+ # Checking for existence of record (remote)
+ 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))",
+ attrs=['unixName','cn','dn', 'sambaUnicodePwd'])
+ self.assertEquals(len(msg), 1) # TODO: should check with more records
+ self.assertEquals(str(msg[0]["cn"]), "Niemand")
+ self.assertEquals(str(msg[0]["unixName"]), "bin")
+ self.assertEquals(str(msg[0]["sambaUnicodePwd"]), "geheim")
+
+ # Checking for existence of record (local || remote)
+ 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
+ self.assertEquals(str(msg[0]["cn"]), "Niemand")
+ self.assertEquals(str(msg[0]["unixName"]), "bin")
+ self.assertEquals(str(msg[0]["sambaUnicodePwd"]), "geheim")
+
+ # 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"]),
+ "S-1-5-21-4231626423-2410014848-2360679739-2001")
+ self.assertEquals(str(msg[0]["displayName"]), "Niemand")
+
+ # Adding attribute...
+ self.ldb.modify_ldif("""
+dn: cn=Niemand,cn=Users,dc=vernstok,dc=nl
+changetype: modify
+add: description
+description: Blah
+""")
+
+ # Checking whether changes are still there...
+ msg = self.ldb.search(expression="(cn=Niemand)")
+ self.assertTrue(len(msg) >= 1)
+ self.assertEquals(str(msg[0]["cn"]), "Niemand")
+ self.assertEquals(str(msg[0]["description"]), "Blah")
+
+ # Modifying attribute...
+ self.ldb.modify_ldif("""
+dn: cn=Niemand,cn=Users,dc=vernstok,dc=nl
+changetype: modify
+replace: description
+description: Blie
+""")
+
+ # Checking whether changes are still there...
+ msg = self.ldb.search(expression="(cn=Niemand)")
+ self.assertTrue(len(msg) >= 1)
+ self.assertEquals(str(msg[0]["description"]), "Blie")
+
+ # Deleting attribute...
+ self.ldb.modify_ldif("""
+dn: cn=Niemand,cn=Users,dc=vernstok,dc=nl
+changetype: modify
+delete: description
+""")
+
+ # Checking whether changes are no longer there...
+ msg = self.ldb.search(expression="(cn=Niemand)")
+ self.assertTrue(len(msg) >= 1)
+ self.assertTrue(not "description" in msg[0])
+
+ # Renaming record...
+ 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),
+ "cn=Niemand2,cn=Users,dc=vernstok,dc=nl")
+
+ # Deleting record...
+ self.ldb.delete("cn=Niemand2,cn=Users,dc=vernstok,dc=nl")
+
+ # Checking whether record is gone...
+ msg = self.ldb.search(expression="(cn=Niemand2)")
+ self.assertEquals(len(msg), 0)
+
+
+class MapTestCase(MapBaseTestCase):
+
+ def setUp(self):
+ super(MapTestCase, self).setUp()
+ ldb = Ldb(self.ldburl, lp=self.lp, session_info=system_session())
+ 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())
+
+ def test_map_search(self):
+ """Running search tests on mapped data."""
+ self.samba3.db.add({
+ "dn": "sambaDomainName=TESTS," + self.samba3.basedn,
+ "objectclass": ["sambaDomain", "top"],
+ "sambaSID": "S-1-5-21-4231626423-2410014848-2360679739",
+ "sambaNextRid": "2000",
+ "sambaDomainName": "TESTS"
+ })
+
+ # Add a set of split records
+ self.ldb.add_ldif("""
+dn: """+ self.samba4.dn("cn=Domain Users") + """
+objectClass: group
+cn: Domain Users
+objectSid: S-1-5-21-4231626423-2410014848-2360679739-513
+""")
+
+ # Add a set of split records
+ self.ldb.add_ldif("""
+dn: """+ self.samba4.dn("cn=X") + """
+objectClass: user
+cn: X
+codePage: x
+revision: x
+dnsHostName: x
+nextRid: y
+lastLogon: x
+description: x
+objectSid: S-1-5-21-4231626423-2410014848-2360679739-552
+""")
+
+ self.ldb.add({
+ "dn": self.samba4.dn("cn=Y"),
+ "objectClass": "top",
+ "cn": "Y",
+ "codePage": "x",
+ "revision": "x",
+ "dnsHostName": "y",
+ "nextRid": "y",
+ "lastLogon": "y",
+ "description": "x"})
+
+ self.ldb.add({
+ "dn": self.samba4.dn("cn=Z"),
+ "objectClass": "top",
+ "cn": "Z",
+ "codePage": "x",
+ "revision": "y",
+ "dnsHostName": "z",
+ "nextRid": "y",
+ "lastLogon": "z",
+ "description": "y"})
+
+ # Add a set of remote records
+
+ self.samba3.db.add({
+ "dn": self.samba3.dn("cn=A"),
+ "objectClass": "posixAccount",
+ "cn": "A",
+ "sambaNextRid": "x",
+ "sambaBadPasswordCount": "x",
+ "sambaLogonTime": "x",
+ "description": "x",
+ "sambaSID": "S-1-5-21-4231626423-2410014848-2360679739-552",
+ "sambaPrimaryGroupSID": "S-1-5-21-4231626423-2410014848-2360679739-512"})
+
+ self.samba3.db.add({
+ "dn": self.samba3.dn("cn=B"),
+ "objectClass": "top",
+ "cn": "B",
+ "sambaNextRid": "x",
+ "sambaBadPasswordCount": "x",
+ "sambaLogonTime": "y",
+ "description": "x"})
+
+ self.samba3.db.add({
+ "dn": self.samba3.dn("cn=C"),
+ "objectClass": "top",
+ "cn": "C",
+ "sambaNextRid": "x",
+ "sambaBadPasswordCount": "y",
+ "sambaLogonTime": "z",
+ "description": "y"})
+
+ # Testing search by DN
+
+ # Search remote record by local DN
+ dn = self.samba4.dn("cn=A")
+ res = self.ldb.search(dn, scope=SCOPE_BASE,
+ attrs=["dnsHostName", "lastLogon"])
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertEquals(str(res[0]["lastLogon"]), "x")
+
+ # Search remote record by remote DN
+ dn = self.samba3.dn("cn=A")
+ 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)
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertTrue(not "lastLogon" in res[0])
+ self.assertEquals(str(res[0]["sambaLogonTime"]), "x")
+
+ # Search split record by local DN
+ dn = self.samba4.dn("cn=X")
+ res = self.ldb.search(dn, scope=SCOPE_BASE,
+ attrs=["dnsHostName", "lastLogon"])
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertEquals(str(res[0]["dnsHostName"]), "x")
+ self.assertEquals(str(res[0]["lastLogon"]), "x")
+
+ # Search split record by remote DN
+ dn = self.samba3.dn("cn=X")
+ 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)
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertTrue(not "lastLogon" in res[0])
+ self.assertEquals(str(res[0]["sambaLogonTime"]), "x")
+
+ # Testing search by attribute
+
+ # Search by ignored attribute
+ 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")
+
+ # Search by kept attribute
+ 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")
+ 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]["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"))
+ 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.assertTrue(not "dnsHostName" in res[1])
+ self.assertEquals(str(res[1]["lastLogon"]), "x")
+
+ # Search by converted attribute
+ # TODO:
+ # Using the SID directly in the parse tree leads to conversion
+ # errors, letting the search fail with no results.
+ #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])
+ 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])
+
+ # 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)",
+ attrs=["dnsHostName", "lastLogon", "primaryGroupID"])
+ self.assertEquals(len(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.assertEquals(str(res[0]["primaryGroupID"]), "512")
+
+ # Note that Xs "objectSid" seems to be fine in the previous search for
+ # "objectSid"...
+ #res = ldb.search(expression="(primaryGroupID=*)", NULL, ldb. SCOPE_DEFAULT, attrs)
+ #print len(res) + " results found"
+ #for i in range(len(res)):
+ # for (obj in res[i]) {
+ # print obj + ": " + res[i][obj]
+ # }
+ # print "---"
+ #
+
+ # Search by remote name of renamed attribute */
+ res = self.ldb.search(expression="(sambaBadPasswordCount=*)",
+ attrs=["dnsHostName", "lastLogon"])
+ self.assertEquals(len(res), 0)
+
+ # Search by objectClass
+ 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")
+ 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]["lastLogon"]), "x")
+ self.assertEquals(str(res[1]["objectClass"][0]), "user")
+
+ # Prove that the objectClass is actually used for the search
+ 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"))
+ 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[2]["lastLogon"]), "x")
+ self.assertEquals(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))",
+ 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")
+
+ # Search by conjunction of remote attributes
+ res = self.ldb.search(expression="(&(lastLogon=x)(description=x))",
+ attrs=["dnsHostName", "lastLogon"])
+ self.assertEquals(len(res), 2)
+ 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")
+
+ # Search by conjunction of local and remote attribute w/o match
+ attrs = ["dnsHostName", "lastLogon"]
+ 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))",
+ attrs=attrs)
+ self.assertEquals(len(res), 0)
+
+ # Search by disjunction of local attributes
+ 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")
+
+ # Search by disjunction of remote attributes
+ 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"))
+ 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[1].dn), self.samba4.dn("cn=B"))
+ self.assertFalse("dnsHostName" in res[1])
+ self.assertEquals(str(res[1]["lastLogon"]), "y")
+ 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")
+
+ # Search by disjunction of local and remote attribute w/o match
+ 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))",
+ attrs=["dnsHostName", "lastLogon"])
+ self.assertEquals(len(res), 6)
+ 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=A"))
+ 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[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]["lastLogon"]), "z")
+
+ # Search by negated remote attribute
+ 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")
+ 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]["lastLogon"]), "z")
+
+ # Search by negated conjunction of local attributes
+ 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"))
+ 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.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[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]["lastLogon"]), "z")
+
+ # Search by negated conjunction of remote attributes
+ 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")
+ self.assertEquals(str(res[0]["lastLogon"]), "y")
+ 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=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[3]["lastLogon"]), "z")
+
+ # Search by negated conjunction of local and remote attribute
+ 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"))
+ 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.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[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]["lastLogon"]), "z")
+
+ # Search by negated disjunction of local attributes
+ res = self.ldb.search(expression="(!(|(revision=x)(dnsHostName=x)))",
+ attrs=["dnsHostName", "lastLogon"])
+ 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=A"))
+ 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[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]["lastLogon"]), "z")
+
+ # Search by negated disjunction of remote attributes
+ 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])
+ 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)))",
+ attrs=["dnsHostName", "lastLogon"])
+ self.assertEquals(len(res), 5)
+ 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]["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]["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"))
+ 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.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[4]["lastLogon"]), "z")
+
+ # Clean up
+ dns = [self.samba4.dn("cn=%s" % n) for n in ["A","B","C","X","Y","Z"]]
+ for dn in dns:
+ self.ldb.delete(dn)
+
+ def test_map_modify_local(self):
+ """Modification of local records."""
+ # Add local record
+ dn = "cn=test,dc=idealx,dc=org"
+ self.ldb.add({"dn": dn,
+ "cn": "test",
+ "foo": "bar",
+ "revision": "1",
+ "description": "test"})
+ # Check it's there
+ attrs = ["foo", "revision", "description"]
+ res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertEquals(str(res[0]["foo"]), "bar")
+ 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)",
+ 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)",
+ scope=SCOPE_DEFAULT, attrs=attrs)
+ self.assertEquals(len(res), 0)
+
+ # Modify local record
+ ldif = """
+dn: """ + dn + """
+replace: foo
+foo: baz
+replace: description
+description: foo
+"""
+ self.ldb.modify_ldif(ldif)
+ # Check in local db
+ res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertEquals(str(res[0]["foo"]), "baz")
+ self.assertEquals(str(res[0]["revision"]), "1")
+ self.assertEquals(str(res[0]["description"]), "foo")
+
+ # Rename local record
+ dn2 = "cn=toast,dc=idealx,dc=org"
+ self.ldb.rename(dn, dn2)
+ # Check in local db
+ res = self.ldb.search(dn2, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn2)
+ self.assertEquals(str(res[0]["foo"]), "baz")
+ self.assertEquals(str(res[0]["revision"]), "1")
+ self.assertEquals(str(res[0]["description"]), "foo")
+
+ # Delete local record
+ self.ldb.delete(dn2)
+ # Check it's gone
+ res = self.ldb.search(dn2, scope=SCOPE_BASE)
+ self.assertEquals(len(res), 0)
+
+ def test_map_modify_remote_remote(self):
+ """Modification of remote data of remote records"""
+ # Add remote record
+ dn = self.samba4.dn("cn=test")
+ dn2 = self.samba3.dn("cn=test")
+ 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,
+ attrs=["description", "sambaBadPasswordCount", "sambaNextRid"])
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn2)
+ self.assertEquals(str(res[0]["description"]), "foo")
+ self.assertEquals(str(res[0]["sambaBadPasswordCount"]), "3")
+ self.assertEquals(str(res[0]["sambaNextRid"]), "1001")
+ # Check in mapped db
+ attrs = ["description", "badPwdCount", "nextRid"]
+ res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs, expression="")
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertEquals(str(res[0]["description"]), "foo")
+ self.assertEquals(str(res[0]["badPwdCount"]), "3")
+ self.assertEquals(str(res[0]["nextRid"]), "1001")
+ # Check in local db
+ res = self.samba4.db.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 0)
+
+ # Modify remote data of remote record
+ ldif = """
+dn: """ + dn + """
+replace: description
+description: test
+replace: badPwdCount
+badPwdCount: 4
+"""
+ self.ldb.modify_ldif(ldif)
+ # Check in mapped db
+ 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
+ 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)
+ self.assertEquals(str(res[0]["description"]), "test")
+ self.assertEquals(str(res[0]["sambaBadPasswordCount"]), "4")
+ self.assertEquals(str(res[0]["sambaNextRid"]), "1001")
+
+ # Rename remote record
+ dn2 = self.samba4.dn("cn=toast")
+ self.ldb.rename(dn, dn2)
+ # Check in mapped db
+ dn = dn2
+ 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
+ dn2 = self.samba3.dn("cn=toast")
+ 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)
+ self.assertEquals(str(res[0]["description"]), "test")
+ self.assertEquals(str(res[0]["sambaBadPasswordCount"]), "4")
+ self.assertEquals(str(res[0]["sambaNextRid"]), "1001")
+
+ # Delete remote record
+ self.ldb.delete(dn)
+ # Check in mapped db that it's removed
+ res = self.ldb.search(dn, scope=SCOPE_BASE)
+ self.assertEquals(len(res), 0)
+ # Check in remote db
+ res = self.samba3.db.search(dn2, scope=SCOPE_BASE)
+ self.assertEquals(len(res), 0)
+
+ def test_map_modify_remote_local(self):
+ """Modification of local data of remote records"""
+ # Add remote record (same as before)
+ dn = self.samba4.dn("cn=test")
+ dn2 = self.samba3.dn("cn=test")
+ self.samba3.db.add({"dn": dn2,
+ "cn": "test",
+ "description": "foo",
+ "sambaBadPasswordCount": "3",
+ "sambaNextRid": "1001"})
+
+ # Modify local data of remote record
+ ldif = """
+dn: """ + dn + """
+add: revision
+revision: 1
+replace: description
+description: test
+
+"""
+ self.ldb.modify_ldif(ldif)
+ # Check in mapped db
+ attrs = ["revision", "description"]
+ res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertEquals(str(res[0]["description"]), "test")
+ self.assertEquals(str(res[0]["revision"]), "1")
+ # Check in remote db
+ res = self.samba3.db.search(dn2, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn2)
+ self.assertEquals(str(res[0]["description"]), "test")
+ self.assertTrue(not "revision" in res[0])
+ # Check in local db
+ res = self.samba4.db.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertTrue(not "description" in res[0])
+ self.assertEquals(str(res[0]["revision"]), "1")
+
+ # Delete (newly) split record
+ self.ldb.delete(dn)
+
+ def test_map_modify_split(self):
+ """Testing modification of split records"""
+ # Add split record
+ dn = self.samba4.dn("cn=test")
+ dn2 = self.samba3.dn("cn=test")
+ self.ldb.add({
+ "dn": dn,
+ "cn": "test",
+ "description": "foo",
+ "badPwdCount": "3",
+ "nextRid": "1001",
+ "revision": "1"})
+ # Check it's there
+ attrs = ["description", "badPwdCount", "nextRid", "revision"]
+ res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertEquals(str(res[0]["description"]), "foo")
+ self.assertEquals(str(res[0]["badPwdCount"]), "3")
+ self.assertEquals(str(res[0]["nextRid"]), "1001")
+ self.assertEquals(str(res[0]["revision"]), "1")
+ # Check in local db
+ res = self.samba4.db.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertTrue(not "description" in res[0])
+ self.assertTrue(not "badPwdCount" in res[0])
+ self.assertTrue(not "nextRid" in res[0])
+ self.assertEquals(str(res[0]["revision"]), "1")
+ # Check in remote db
+ attrs = ["description", "sambaBadPasswordCount", "sambaNextRid",
+ "revision"]
+ res = self.samba3.db.search(dn2, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn2)
+ self.assertEquals(str(res[0]["description"]), "foo")
+ self.assertEquals(str(res[0]["sambaBadPasswordCount"]), "3")
+ self.assertEquals(str(res[0]["sambaNextRid"]), "1001")
+ self.assertTrue(not "revision" in res[0])
+
+ # Modify of split record
+ ldif = """
+dn: """ + dn + """
+replace: description
+description: test
+replace: badPwdCount
+badPwdCount: 4
+replace: revision
+revision: 2
+"""
+ self.ldb.modify_ldif(ldif)
+ # Check in mapped db
+ attrs = ["description", "badPwdCount", "nextRid", "revision"]
+ res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ 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")
+ self.assertEquals(str(res[0]["revision"]), "2")
+ # Check in local db
+ res = self.samba4.db.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertTrue(not "description" in res[0])
+ self.assertTrue(not "badPwdCount" in res[0])
+ self.assertTrue(not "nextRid" in res[0])
+ self.assertEquals(str(res[0]["revision"]), "2")
+ # Check in remote db
+ attrs = ["description", "sambaBadPasswordCount", "sambaNextRid",
+ "revision"]
+ res = self.samba3.db.search(dn2, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn2)
+ self.assertEquals(str(res[0]["description"]), "test")
+ self.assertEquals(str(res[0]["sambaBadPasswordCount"]), "4")
+ self.assertEquals(str(res[0]["sambaNextRid"]), "1001")
+ self.assertTrue(not "revision" in res[0])
+
+ # Rename split record
+ dn2 = self.samba4.dn("cn=toast")
+ self.ldb.rename(dn, dn2)
+ # Check in mapped db
+ dn = dn2
+ attrs = ["description", "badPwdCount", "nextRid", "revision"]
+ res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ 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")
+ self.assertEquals(str(res[0]["revision"]), "2")
+ # Check in local db
+ res = self.samba4.db.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertTrue(not "description" in res[0])
+ self.assertTrue(not "badPwdCount" in res[0])
+ self.assertTrue(not "nextRid" in res[0])
+ 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",
+ "revision"])
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn2)
+ self.assertEquals(str(res[0]["description"]), "test")
+ self.assertEquals(str(res[0]["sambaBadPasswordCount"]), "4")
+ self.assertEquals(str(res[0]["sambaNextRid"]), "1001")
+ self.assertTrue(not "revision" in res[0])
+
+ # Delete split record
+ self.ldb.delete(dn)
+ # Check in mapped db
+ res = self.ldb.search(dn, scope=SCOPE_BASE)
+ self.assertEquals(len(res), 0)
+ # Check in local db
+ res = self.samba4.db.search(dn, scope=SCOPE_BASE)
+ self.assertEquals(len(res), 0)
+ # Check in remote db
+ res = self.samba3.db.search(dn2, scope=SCOPE_BASE)
+ self.assertEquals(len(res), 0)
diff --git a/source4/scripting/python/samba/tests/samdb.py b/source4/scripting/python/samba/tests/samdb.py
index 8c7bb0ae98..3df72b0840 100644
--- a/source4/scripting/python/samba/tests/samdb.py
+++ b/source4/scripting/python/samba/tests/samdb.py
@@ -1,43 +1,46 @@
-#!/usr/bin/python
+#!/usr/bin/env python
# Unix SMB/CIFS implementation. Tests for SamDB
# 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/>.
#
-from samba.auth import system_session
-from samba.credentials import Credentials
+
+"""Tests for samba.samdb."""
+
+import logging
import os
-from samba.provision import setup_samdb, guess_names, setup_templatesdb, make_smbconf, find_setup_dir
-from samba.samdb import SamDB
+import uuid
+
+from samba.auth import system_session
+from samba.provision import (setup_samdb, guess_names, make_smbconf,
+ provision_paths_from_lp)
+from samba.provision import DEFAULT_POLICY_GUID, DEFAULT_DC_POLICY_GUID
+from samba.provision.backend import ProvisionBackend
from samba.tests import TestCaseInTempDir
from samba.dcerpc import security
-from unittest import TestCase
-import uuid
+from samba.schema import Schema
from samba import param
class SamDBTestCase(TestCaseInTempDir):
"""Base-class for tests with a Sam Database.
-
+
This is used by the Samba SamDB-tests, but e.g. also by the OpenChange
provisioning tests (which need a Sam).
"""
- def setup_path(self, relpath):
- return os.path.join(find_setup_dir(), relpath)
-
def setUp(self):
super(SamDBTestCase, self).setUp()
invocationid = str(uuid.uuid4())
@@ -46,11 +49,8 @@ class SamDBTestCase(TestCaseInTempDir):
configdn = "CN=Configuration," + domaindn
schemadn = "CN=Schema," + configdn
domainguid = str(uuid.uuid4())
- policyguid = str(uuid.uuid4())
- creds = Credentials()
- creds.set_anonymous()
+ policyguid = DEFAULT_POLICY_GUID
domainsid = security.random_sid()
- hostguid = str(uuid.uuid4())
path = os.path.join(self.tempdir, "samdb.ldb")
session_info = system_session()
@@ -58,9 +58,10 @@ class SamDBTestCase(TestCaseInTempDir):
domain="EXAMPLE"
dnsdomain="example.com"
serverrole="domain controller"
+ policyguid_dc = DEFAULT_DC_POLICY_GUID
smbconf = os.path.join(self.tempdir, "smb.conf")
- make_smbconf(smbconf, self.setup_path, hostname, domain, dnsdomain,
+ make_smbconf(smbconf, hostname, domain, dnsdomain,
serverrole, self.tempdir)
self.lp = param.LoadParm()
@@ -71,26 +72,27 @@ class SamDBTestCase(TestCaseInTempDir):
serverrole=serverrole,
domaindn=self.domaindn, configdn=configdn,
schemadn=schemadn)
- setup_templatesdb(os.path.join(self.tempdir, "templates.ldb"),
- self.setup_path, session_info=session_info, lp=self.lp)
- self.samdb = setup_samdb(path, self.setup_path, session_info, creds,
- self.lp, names,
- lambda x: None, domainsid,
- domainguid,
- policyguid, False, "secret",
- "secret", "secret", invocationid,
- "secret", "domain controller")
+
+ paths = provision_paths_from_lp(self.lp, names.dnsdomain)
+
+ logger = logging.getLogger("provision")
+
+ provision_backend = ProvisionBackend("ldb", paths=paths,
+ lp=self.lp, credentials=None,
+ names=names, logger=logger)
+
+ schema = Schema(domainsid, invocationid=invocationid,
+ schemadn=names.schemadn, serverdn=names.serverdn,
+ am_rodc=False)
+
+ self.samdb = setup_samdb(path, session_info,
+ provision_backend, self.lp, names, logger,
+ domainsid, domainguid, policyguid, policyguid_dc, False,
+ "secret", "secret", "secret", invocationid, "secret",
+ None, "domain controller", schema=schema)
def tearDown(self):
- for f in ['templates.ldb', 'schema.ldb', 'configuration.ldb',
+ for f in ['schema.ldb', 'configuration.ldb',
'users.ldb', 'samdb.ldb', 'smb.conf']:
os.remove(os.path.join(self.tempdir, f))
super(SamDBTestCase, self).tearDown()
-
-
-# disable this test till andrew works it out ...
-class SamDBTests(SamDBTestCase):
- """Tests for the SamDB implementation."""
-
- print "samdb add_foreign disabled for now"
-# def test_add_foreign(self):
diff --git a/source4/scripting/python/samba/tests/security.py b/source4/scripting/python/samba/tests/security.py
new file mode 100644
index 0000000000..59e3113068
--- /dev/null
+++ b/source4/scripting/python/samba/tests/security.py
@@ -0,0 +1,145 @@
+#!/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.dcerpc.security."""
+
+import samba.tests
+from samba.dcerpc import security
+
+class SecurityTokenTests(samba.tests.TestCase):
+
+ def setUp(self):
+ super(SecurityTokenTests, self).setUp()
+ self.token = security.token()
+
+ def test_is_system(self):
+ self.assertFalse(self.token.is_system())
+
+ def test_is_anonymous(self):
+ self.assertFalse(self.token.is_anonymous())
+
+ def test_has_builtin_administrators(self):
+ self.assertFalse(self.token.has_builtin_administrators())
+
+ def test_has_nt_authenticated_users(self):
+ self.assertFalse(self.token.has_nt_authenticated_users())
+
+ def test_has_priv(self):
+ self.assertFalse(self.token.has_privilege(security.SEC_PRIV_SHUTDOWN))
+
+ def test_set_priv(self):
+ self.assertFalse(self.token.has_privilege(security.SEC_PRIV_SHUTDOWN))
+ self.assertFalse(self.token.set_privilege(security.SEC_PRIV_SHUTDOWN))
+ self.assertTrue(self.token.has_privilege(security.SEC_PRIV_SHUTDOWN))
+
+
+class SecurityDescriptorTests(samba.tests.TestCase):
+
+ def setUp(self):
+ super(SecurityDescriptorTests, self).setUp()
+ self.descriptor = security.descriptor()
+
+ def test_from_sddl(self):
+ desc = security.descriptor.from_sddl("O:AOG:DAD:(A;;RPWPCCDCLCSWRCWDWOGA;;;S-1-0-0)", security.dom_sid("S-2-0-0"))
+ self.assertEquals(desc.group_sid, security.dom_sid('S-2-0-0-512'))
+ self.assertEquals(desc.owner_sid, security.dom_sid('S-1-5-32-548'))
+ self.assertEquals(desc.revision, 1)
+ self.assertEquals(desc.sacl, None)
+ self.assertEquals(desc.type, 0x8004)
+
+ def test_from_sddl_invalidsddl(self):
+ self.assertRaises(TypeError,security.descriptor.from_sddl, "foo",security.dom_sid("S-2-0-0"))
+
+ def test_from_sddl_invalidtype1(self):
+ self.assertRaises(TypeError, security.descriptor.from_sddl, security.dom_sid('S-2-0-0-512'),security.dom_sid("S-2-0-0"))
+
+ def test_from_sddl_invalidtype2(self):
+ sddl = "O:AOG:DAD:(A;;RPWPCCDCLCSWRCWDWOGA;;;S-1-0-0)"
+ self.assertRaises(TypeError, security.descriptor.from_sddl, sddl,
+ "S-2-0-0")
+
+ def test_as_sddl(self):
+ text = "O:AOG:DAD:(A;;RPWPCCDCLCSWRCWDWOGA;;;S-1-0-0)"
+ dom = security.dom_sid("S-2-0-0")
+ desc1 = security.descriptor.from_sddl(text, dom)
+ desc2 = security.descriptor.from_sddl(desc1.as_sddl(dom), dom)
+ self.assertEquals(desc1.group_sid, desc2.group_sid)
+ self.assertEquals(desc1.owner_sid, desc2.owner_sid)
+ self.assertEquals(desc1.sacl, desc2.sacl)
+ self.assertEquals(desc1.type, desc2.type)
+
+ def test_as_sddl_invalid(self):
+ text = "O:AOG:DAD:(A;;RPWPCCDCLCSWRCWDWOGA;;;S-1-0-0)"
+ dom = security.dom_sid("S-2-0-0")
+ desc1 = security.descriptor.from_sddl(text, dom)
+ self.assertRaises(TypeError, desc1.as_sddl,text)
+
+
+ def test_as_sddl_no_domainsid(self):
+ dom = security.dom_sid("S-2-0-0")
+ text = "O:AOG:DAD:(A;;RPWPCCDCLCSWRCWDWOGA;;;S-1-0-0)"
+ desc1 = security.descriptor.from_sddl(text, dom)
+ desc2 = security.descriptor.from_sddl(desc1.as_sddl(), dom)
+ self.assertEquals(desc1.group_sid, desc2.group_sid)
+ self.assertEquals(desc1.owner_sid, desc2.owner_sid)
+ self.assertEquals(desc1.sacl, desc2.sacl)
+ self.assertEquals(desc1.type, desc2.type)
+
+ def test_domsid_nodomsid_as_sddl(self):
+ dom = security.dom_sid("S-2-0-0")
+ text = "O:AOG:DAD:(A;;RPWPCCDCLCSWRCWDWOGA;;;S-1-0-0)"
+ desc1 = security.descriptor.from_sddl(text, dom)
+ self.assertNotEqual(desc1.as_sddl(), desc1.as_sddl(dom))
+
+ def test_split(self):
+ dom = security.dom_sid("S-2-0-7")
+ self.assertEquals((security.dom_sid("S-2-0"), 7), dom.split())
+
+
+class DomSidTests(samba.tests.TestCase):
+
+ def test_parse_sid(self):
+ sid = security.dom_sid("S-1-5-21")
+ self.assertEquals("S-1-5-21", str(sid))
+
+ def test_sid_equal(self):
+ sid1 = security.dom_sid("S-1-5-21")
+ sid2 = security.dom_sid("S-1-5-21")
+ self.assertEquals(sid1, sid1)
+ self.assertEquals(sid1, sid2)
+
+ def test_random(self):
+ sid = security.random_sid()
+ self.assertTrue(str(sid).startswith("S-1-5-21-"))
+
+ def test_repr(self):
+ sid = security.random_sid()
+ self.assertTrue(repr(sid).startswith("dom_sid('S-1-5-21-"))
+
+
+class PrivilegeTests(samba.tests.TestCase):
+
+ def test_privilege_name(self):
+ self.assertEquals("SeShutdownPrivilege",
+ security.privilege_name(security.SEC_PRIV_SHUTDOWN))
+
+ def test_privilege_id(self):
+ self.assertEquals(security.SEC_PRIV_SHUTDOWN,
+ security.privilege_id("SeShutdownPrivilege"))
+
diff --git a/source4/scripting/python/samba/tests/upgrade.py b/source4/scripting/python/samba/tests/upgrade.py
index 17ebfa7bf5..16ccbd567e 100644
--- a/source4/scripting/python/samba/tests/upgrade.py
+++ b/source4/scripting/python/samba/tests/upgrade.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007
@@ -17,11 +17,13 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
-from samba import Ldb
+"""Tests for samba.upgrade."""
+
from samba.upgrade import import_wins
from samba.tests import LdbTestCase
class WinsUpgradeTests(LdbTestCase):
+
def test_upgrade(self):
winsdb = {
"FOO#20": (200, ["127.0.0.1", "127.0.0.2"], 0x60)
diff --git a/source4/scripting/python/samba/tests/upgradeprovision.py b/source4/scripting/python/samba/tests/upgradeprovision.py
new file mode 100644
index 0000000000..b81ee8a8ab
--- /dev/null
+++ b/source4/scripting/python/samba/tests/upgradeprovision.py
@@ -0,0 +1,139 @@
+#!/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/>.
+#
+
+"""Tests for samba.upgradeprovision."""
+
+import os
+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
+import samba.tests
+
+def dummymessage(a=None, b=None):
+ pass
+
+
+class UpgradeProvisionTestCase(TestCaseInTempDir):
+ """Some simple tests for individual functions in the provisioning code.
+ """
+ def test_usn_in_range(self):
+ range = [5, 25, 35, 55]
+
+ vals = [3, 26, 56]
+
+ for v in vals:
+ self.assertFalse(usn_in_range(v, range))
+
+ vals = [5, 20, 25, 35, 36]
+
+ for v in vals:
+ self.assertTrue(usn_in_range(v, range))
+
+ def test_dn_sort(self):
+ # higher level comes after lower even if lexicographicaly closer
+ # ie dc=tata,dc=toto (2 levels), comes after dc=toto
+ # even if dc=toto is lexicographicaly after dc=tata, dc=toto
+ self.assertEquals(dn_sort("dc=tata,dc=toto", "dc=toto"), 1)
+ self.assertEquals(dn_sort("dc=zata", "dc=tata"), 1)
+ self.assertEquals(dn_sort("dc=toto,dc=tata",
+ "cn=foo,dc=toto,dc=tata"), -1)
+ self.assertEquals(dn_sort("cn=bar, dc=toto,dc=tata",
+ "cn=foo, dc=toto,dc=tata"), -1)
+
+ def test_get_diff_sddl(self):
+ sddl = "O:SAG:DUD:AI(A;CIID;RPWPCRCCLCLORCWOWDSW;;;SA)\
+(A;CIID;RP LCLORC;;;AU)(A;CIID;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)S:AI(AU;CIIDSA;WP;;;WD)"
+ sddl1 = "O:SAG:DUD:AI(A;CIID;RPWPCRCCLCLORCWOWDSW;;;SA)\
+(A;CIID;RP LCLORC;;;AU)(A;CIID;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)S:AI(AU;CIIDSA;WP;;;WD)"
+ sddl2 = "O:BAG:DUD:AI(A;CIID;RPWPCRCCLCLORCWOWDSW;;;SA)\
+(A;CIID;RP LCLORC;;;AU)(A;CIID;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)S:AI(AU;CIIDSA;WP;;;WD)"
+ sddl3 = "O:SAG:BAD:AI(A;CIID;RPWPCRCCLCLORCWOWDSW;;;SA)\
+(A;CIID;RP LCLORC;;;AU)(A;CIID;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)S:AI(AU;CIIDSA;WP;;;WD)"
+ sddl4 = "O:SAG:DUD:AI(A;CIID;RPWPCRCCLCLORCWOWDSW;;;BA)\
+(A;CIID;RP LCLORC;;;AU)(A;CIID;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)S:AI(AU;CIIDSA;WP;;;WD)"
+ sddl5 = "O:SAG:DUD:AI(A;CIID;RPWPCRCCLCLORCWOWDSW;;;SA)\
+(A;CIID;RP LCLORC;;;AU)(A;CIID;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)"
+
+ self.assertEquals(get_diff_sddls(sddl, sddl1), "")
+ txt = get_diff_sddls(sddl, sddl2)
+ self.assertEquals(txt, "\tOwner mismatch: SA (in ref) BA(in current)\n")
+ txt = get_diff_sddls(sddl, sddl3)
+ self.assertEquals(txt, "\tGroup mismatch: DU (in ref) BA(in current)\n")
+ txt = get_diff_sddls(sddl, sddl4)
+ txtmsg = "\tPart dacl is different between reference and current here\
+ is the detail:\n\t\t(A;CIID;RPWPCRCCLCLORCWOWDSW;;;BA) ACE is not present in\
+ the reference\n\t\t(A;CIID;RPWPCRCCLCLORCWOWDSW;;;SA) ACE is not present in\
+ the current\n"
+ self.assertEquals(txt, txtmsg)
+ txt = get_diff_sddls(sddl, sddl5)
+ self.assertEquals(txt, "\tCurrent ACL hasn't a sacl part\n")
+
+ def test_construct_existor_expr(self):
+ res = construct_existor_expr([])
+ self.assertEquals(res, "")
+
+ res = construct_existor_expr(["foo"])
+ self.assertEquals(res, "(|(foo=*))")
+
+ res = construct_existor_expr(["foo", "bar"])
+ self.assertEquals(res, "(|(foo=*)(bar=*))")
+
+
+class UpdateSecretsTests(samba.tests.TestCaseInTempDir):
+
+ def setUp(self):
+ super(UpdateSecretsTests, self).setUp()
+ self.referencedb = create_dummy_secretsdb(
+ os.path.join(self.tempdir, "ref.ldb"))
+
+ def _getEmptyDb(self):
+ return Ldb(os.path.join(self.tempdir, "secrets.ldb"))
+
+ def _getCurrentFormatDb(self):
+ return create_dummy_secretsdb(
+ os.path.join(self.tempdir, "secrets.ldb"))
+
+ def test_trivial(self):
+ # Test that updating an already up-to-date secretsdb works fine
+ self.secretsdb = self._getCurrentFormatDb()
+ self.assertEquals(None,
+ update_secrets(self.referencedb, self.secretsdb, dummymessage))
+
+ 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)
+ self.assertEquals(newmodules.msgs, refmodules.msgs)
+
+ def tearDown(self):
+ for name in ["ref.ldb", "secrets.ldb"]:
+ path = os.path.join(self.tempdir, name)
+ if os.path.exists(path):
+ os.unlink(path)
+ super(UpdateSecretsTests, self).tearDown()
+
+
diff --git a/source4/scripting/python/samba/tests/upgradeprovisionneeddc.py b/source4/scripting/python/samba/tests/upgradeprovisionneeddc.py
new file mode 100644
index 0000000000..3a9c78e0dc
--- /dev/null
+++ b/source4/scripting/python/samba/tests/upgradeprovisionneeddc.py
@@ -0,0 +1,180 @@
+#!/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/>.
+#
+
+"""Tests for samba.upgradeprovision that need a DC."""
+
+import os
+import re
+import shutil
+
+from samba import param
+from samba.credentials import Credentials
+from samba.auth import system_session
+from samba.provision import getpolicypath
+from samba.upgradehelpers import (get_paths, get_ldbs,
+ find_provision_key_parameters, identic_rename,
+ updateOEMInfo, getOEMInfo, update_gpo,
+ delta_update_basesamdb,
+ update_dns_account_password,
+ search_constructed_attrs_stored,
+ increment_calculated_keyversion_number)
+from samba.tests import env_loadparm, TestCaseInTempDir
+from samba.tests.provision import create_dummy_secretsdb
+import ldb
+
+
+def dummymessage(a=None, b=None):
+ pass
+
+smb_conf_path = "%s/%s/%s" % (os.environ["SELFTEST_PREFIX"], "dc", "etc/smb.conf")
+
+class UpgradeProvisionBasicLdbHelpersTestCase(TestCaseInTempDir):
+ """Some simple tests for individual functions in the provisioning code.
+ """
+
+ def test_get_ldbs(self):
+ paths = get_paths(param, None, smb_conf_path)
+ creds = Credentials()
+ lp = env_loadparm()
+ creds.guess(lp)
+ get_ldbs(paths, creds, system_session(), lp)
+
+ def test_find_key_param(self):
+ paths = get_paths(param, None, smb_conf_path)
+ creds = Credentials()
+ lp = env_loadparm()
+ creds.guess(lp)
+ rootdn = "dc=samba,dc=example,dc=com"
+ ldbs = get_ldbs(paths, creds, system_session(), lp)
+ names = find_provision_key_parameters(ldbs.sam, ldbs.secrets, ldbs.idmap,
+ paths, smb_conf_path, lp)
+ self.assertEquals(names.realm, "SAMBA.EXAMPLE.COM")
+ self.assertEquals(str(names.rootdn).lower(), rootdn.lower())
+ self.assertNotEquals(names.policyid_dc, None)
+ self.assertNotEquals(names.ntdsguid, "")
+
+
+class UpgradeProvisionWithLdbTestCase(TestCaseInTempDir):
+
+ def _getEmptyDbName(self):
+ return os.path.join(self.tempdir, "sam.ldb")
+
+ def setUp(self):
+ super(UpgradeProvisionWithLdbTestCase, self).setUp()
+ paths = get_paths(param, None, smb_conf_path)
+ self.creds = Credentials()
+ self.lp = env_loadparm()
+ self.creds.guess(self.lp)
+ self.paths = paths
+ self.ldbs = get_ldbs(paths, self.creds, system_session(), self.lp)
+ self.names = find_provision_key_parameters(self.ldbs.sam,
+ self.ldbs.secrets, self.ldbs.idmap, paths, smb_conf_path,
+ self.lp)
+ self.referencedb = create_dummy_secretsdb(
+ os.path.join(self.tempdir, "ref.ldb"))
+
+ def test_search_constructed_attrs_stored(self):
+ hashAtt = search_constructed_attrs_stored(self.ldbs.sam,
+ self.names.rootdn,
+ ["msds-KeyVersionNumber"])
+ self.assertFalse(hashAtt.has_key("msds-KeyVersionNumber"))
+
+ def test_increment_calculated_keyversion_number(self):
+ dn = "CN=Administrator,CN=Users,%s" % self.names.rootdn
+ # We conctruct a simple hash for the user administrator
+ hash = {}
+ # And we want the version to be 140
+ hash[dn.lower()] = 140
+
+ increment_calculated_keyversion_number(self.ldbs.sam,
+ self.names.rootdn,
+ hash)
+ self.assertEqual(self.ldbs.sam.get_attribute_replmetadata_version(dn,
+ "unicodePwd"),
+ 140)
+ # This function should not decrement the version
+ hash[dn.lower()] = 130
+
+ increment_calculated_keyversion_number(self.ldbs.sam,
+ self.names.rootdn,
+ hash)
+ self.assertEqual(self.ldbs.sam.get_attribute_replmetadata_version(dn,
+ "unicodePwd"),
+ 140)
+
+ def test_identic_rename(self):
+ rootdn = "DC=samba,DC=example,DC=com"
+
+ guestDN = ldb.Dn(self.ldbs.sam, "CN=Guest,CN=Users,%s" % rootdn)
+ identic_rename(self.ldbs.sam, guestDN)
+ res = self.ldbs.sam.search(expression="(name=Guest)", base=rootdn,
+ scope=ldb.SCOPE_SUBTREE, attrs=["dn"])
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0]["dn"]), "CN=Guest,CN=Users,%s" % rootdn)
+
+ def test_delta_update_basesamdb(self):
+ dummysampath = self._getEmptyDbName()
+ delta_update_basesamdb(self.paths.samdb, dummysampath,
+ self.creds, system_session(), self.lp,
+ dummymessage)
+
+ def test_update_gpo_simple(self):
+ dir = getpolicypath(self.paths.sysvol, self.names.dnsdomain,
+ self.names.policyid)
+ shutil.rmtree(dir)
+ self.assertFalse(os.path.isdir(dir))
+ update_gpo(self.paths, self.ldbs.sam, self.names, self.lp, dummymessage)
+ self.assertTrue(os.path.isdir(dir))
+
+ def test_update_gpo_acl(self):
+ path = os.path.join(self.tempdir, "testupdategpo")
+ save = self.paths.sysvol
+ self.paths.sysvol = path
+ os.mkdir(path)
+ os.mkdir(os.path.join(path, self.names.dnsdomain))
+ os.mkdir(os.path.join(os.path.join(path, self.names.dnsdomain),
+ "Policies"))
+ update_gpo(self.paths, self.ldbs.sam, self.names, self.lp, dummymessage)
+ shutil.rmtree(path)
+ self.paths.sysvol = save
+
+ def test_getOEMInfo(self):
+ realm = self.lp.get("realm")
+ basedn = "DC=%s" % realm.replace(".", ", DC=")
+ oem = getOEMInfo(self.ldbs.sam, basedn)
+ self.assertNotEquals(oem, "")
+
+ def test_update_dns_account(self):
+ update_dns_account_password(self.ldbs.sam, self.ldbs.secrets, self.names)
+
+ def test_updateOEMInfo(self):
+ realm = self.lp.get("realm")
+ basedn = "DC=%s" % realm.replace(".", ", DC=")
+ oem = getOEMInfo(self.ldbs.sam, basedn)
+ updateOEMInfo(self.ldbs.sam, basedn)
+ oem2 = getOEMInfo(self.ldbs.sam, basedn)
+ self.assertNotEquals(str(oem), str(oem2))
+ self.assertTrue(re.match(".*upgrade to.*", str(oem2)))
+
+ def tearDown(self):
+ for name in ["ref.ldb", "secrets.ldb", "sam.ldb"]:
+ path = os.path.join(self.tempdir, name)
+ if os.path.exists(path):
+ os.unlink(path)
+ super(UpgradeProvisionWithLdbTestCase, self).tearDown()
diff --git a/source4/scripting/python/samba/tests/xattr.py b/source4/scripting/python/samba/tests/xattr.py
new file mode 100644
index 0000000000..f978ee5b2a
--- /dev/null
+++ b/source4/scripting/python/samba/tests/xattr.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python
+
+# Unix SMB/CIFS implementation. Tests for xattr manipulation
+# Copyright (C) Matthieu Patou <mat@matws.net> 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/>.
+#
+
+"""Tests for samba.xattr_native and samba.xattr_tdb."""
+
+import samba.xattr_native, samba.xattr_tdb
+from samba.dcerpc import xattr
+from samba.ndr import ndr_pack
+from samba.tests import TestCase, TestSkipped
+import random
+import os
+
+class XattrTests(TestCase):
+
+ def _tmpfilename(self):
+ random.seed()
+ path = os.environ['SELFTEST_PREFIX']
+ return os.path.join(path, "pytests"+str(int(100000*random.random())))
+
+ def _eadbpath(self):
+ return os.path.join(os.environ['SELFTEST_PREFIX'], "eadb.tdb")
+
+ def test_set_xattr_native(self):
+ if not samba.xattr_native.is_xattr_supported():
+ raise TestSkipped()
+ ntacl = xattr.NTACL()
+ ntacl.version = 1
+ tempf = self._tmpfilename()
+ open(tempf, 'w').write("empty")
+ try:
+ 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")
+ os.unlink(tempf)
+
+ def test_set_and_get_native(self):
+ if not samba.xattr_native.is_xattr_supported():
+ raise TestSkipped()
+ tempf = self._tmpfilename()
+ reftxt = "this is a test"
+ open(tempf, 'w').write("empty")
+ try:
+ samba.xattr_native.wrap_setxattr(tempf, "user.unittests", reftxt)
+ text = samba.xattr_native.wrap_getxattr(tempf, "user.unittests")
+ self.assertEquals(text, reftxt)
+ except IOError:
+ raise TestSkipped("the filesystem where the tests are runned do not support XATTR")
+ os.unlink(tempf)
+
+ def test_set_xattr_tdb(self):
+ tempf = self._tmpfilename()
+ eadb_path = self._eadbpath()
+ ntacl = xattr.NTACL()
+ ntacl.version = 1
+ open(tempf, 'w').write("empty")
+ try:
+ samba.xattr_tdb.wrap_setxattr(eadb_path,
+ tempf, "user.unittests", ndr_pack(ntacl))
+ finally:
+ os.unlink(tempf)
+ os.unlink(eadb_path)
+
+ def test_set_tdb_not_open(self):
+ tempf = self._tmpfilename()
+ ntacl = xattr.NTACL()
+ ntacl.version = 1
+ open(tempf, 'w').write("empty")
+ try:
+ self.assertRaises(IOError, samba.xattr_tdb.wrap_setxattr,
+ os.path.join("nonexistent", "eadb.tdb"), tempf,
+ "user.unittests", ndr_pack(ntacl))
+ finally:
+ os.unlink(tempf)
+
+ def test_set_and_get_tdb(self):
+ tempf = self._tmpfilename()
+ eadb_path = self._eadbpath()
+ reftxt = "this is a test"
+ open(tempf, 'w').write("empty")
+ try:
+ samba.xattr_tdb.wrap_setxattr(eadb_path, tempf, "user.unittests",
+ reftxt)
+ text = samba.xattr_tdb.wrap_getxattr(eadb_path, tempf,
+ "user.unittests")
+ self.assertEquals(text, reftxt)
+ finally:
+ os.unlink(tempf)
+ os.unlink(eadb_path)
diff --git a/source4/scripting/python/samba/torture/pytorture b/source4/scripting/python/samba/torture/pytorture
deleted file mode 100755
index e0123447e8..0000000000
--- a/source4/scripting/python/samba/torture/pytorture
+++ /dev/null
@@ -1,51 +0,0 @@
-#!/usr/bin/python
-
-import sys
-from optparse import OptionParser
-
-# Parse command line
-
-parser = OptionParser()
-
-parser.add_option("-b", "--binding", action="store", type="string",
- dest="binding")
-
-parser.add_option("-d", "--domain", action="store", type="string",
- dest="domain")
-
-parser.add_option("-u", "--username", action="store", type="string",
- dest="username")
-
-parser.add_option("-p", "--password", action="store", type="string",
- dest="password")
-
-(options, args) = parser.parse_args()
-
-if not options.binding:
- parser.error('You must supply a binding string')
-
-if not options.username or not options.password or not options.domain:
- parser.error('You must supply a domain, username and password')
-
-binding = options.binding
-domain = options.domain
-username = options.username
-password = options.password
-
-if len(args) == 0:
- parser.error('You must supply the name of a module to test')
-
-# Import and test
-
-for test in args:
-
- try:
- module = __import__('torture_%s' % test)
- except ImportError:
- print 'No such module "%s"' % test
- sys.exit(1)
-
- if not hasattr(module, 'runtests'):
- print 'Module "%s" does not have a runtests function' % test
-
- module.runtests(binding, (domain, username, password))
diff --git a/source4/scripting/python/samba/torture/spoolss.py b/source4/scripting/python/samba/torture/spoolss.py
deleted file mode 100644
index a75385e079..0000000000
--- a/source4/scripting/python/samba/torture/spoolss.py
+++ /dev/null
@@ -1,437 +0,0 @@
-import sys, string
-import dcerpc
-
-
-def ResizeBufferCall(fn, pipe, r):
-
- r['buffer'] = None
- r['buf_size'] = 0
-
- result = fn(pipe, r)
-
- if result['result'] == dcerpc.WERR_INSUFFICIENT_BUFFER or \
- result['result'] == dcerpc.WERR_MORE_DATA:
- r['buffer'] = result['buf_size'] * '\x00'
- r['buf_size'] = result['buf_size']
-
- result = fn(pipe, r)
-
- return result
-
-
-def test_OpenPrinterEx(pipe, printer):
-
- print 'spoolss_OpenPrinterEx(%s)' % printer
-
- printername = '\\\\%s' % dcerpc.dcerpc_server_name(pipe)
-
- if printer is not None:
- printername = printername + '\\%s' % printer
-
- r = {}
- r['printername'] = printername
- r['datatype'] = None
- r['devmode_ctr'] = {}
- r['devmode_ctr']['size'] = 0
- r['devmode_ctr']['devmode'] = None
- r['access_mask'] = 0x02000000
- r['level'] = 1
- r['userlevel'] = {}
- r['userlevel']['level1'] = {}
- r['userlevel']['level1']['size'] = 0
- r['userlevel']['level1']['client'] = None
- r['userlevel']['level1']['user'] = None
- r['userlevel']['level1']['build'] = 1381
- r['userlevel']['level1']['major'] = 2
- r['userlevel']['level1']['minor'] = 0
- r['userlevel']['level1']['processor'] = 0
-
- result = dcerpc.spoolss_OpenPrinterEx(pipe, r)
-
- return result['handle']
-
-
-def test_ClosePrinter(pipe, handle):
-
- r = {}
- r['handle'] = handle
-
- dcerpc.spoolss_ClosePrinter(pipe, r)
-
-
-def test_GetPrinter(pipe, handle):
-
- r = {}
- r['handle'] = handle
-
- for level in [0, 1, 2, 3, 4, 5, 6, 7]:
-
- print 'spoolss_GetPrinter(level = %d)' % level
-
- r['level'] = level
- r['buffer'] = None
- r['buf_size'] = 0
-
- result = ResizeBufferCall(dcerpc.spoolss_GetPrinter, pipe, r)
-
-
-def test_EnumForms(pipe, handle):
-
- print 'spoolss_EnumForms()'
-
- r = {}
- r['handle'] = handle
- r['level'] = 1
- r['buffer'] = None
- r['buf_size'] = 0
-
- result = ResizeBufferCall(dcerpc.spoolss_EnumForms, pipe, r)
-
- forms = dcerpc.unmarshall_spoolss_FormInfo_array(
- result['buffer'], r['level'], result['count'])
-
- for form in forms:
-
- r = {}
- r['handle'] = handle
- r['formname'] = form['info1']['formname']
- r['level'] = 1
-
- result = ResizeBufferCall(dcerpc.spoolss_GetForm, pipe, r)
-
-
-def test_EnumPorts(pipe, handle):
-
- print 'spoolss_EnumPorts()'
-
- for level in [1, 2]:
-
- r = {}
- r['handle'] = handle
- r['servername'] = None
- r['level'] = level
-
- result = ResizeBufferCall(dcerpc.spoolss_EnumPorts, pipe, r)
-
- ports = dcerpc.unmarshall_spoolss_PortInfo_array(
- result['buffer'], r['level'], result['count'])
-
- if level == 1:
- port_names = map(lambda x: x['info1']['port_name'], ports)
-
-
-def test_DeleteForm(pipe, handle, formname):
-
- r = {}
- r['handle'] = handle
- r['formname'] = formname
-
- dcerpc.spoolss_DeleteForm(pipe, r)
-
-
-def test_GetForm(pipe, handle, formname):
-
- r = {}
- r['handle'] = handle
- r['formname'] = formname
- r['level'] = 1
-
- result = ResizeBufferCall(dcerpc.spoolss_GetForm, pipe, r)
-
- return result['info']['info1']
-
-
-def test_SetForm(pipe, handle, form):
-
- print 'spoolss_SetForm()'
-
- r = {}
- r['handle'] = handle
- r['level'] = 1
- r['formname'] = form['info1']['formname']
- r['info'] = form
-
- dcerpc.spoolss_SetForm(pipe, r)
-
- newform = test_GetForm(pipe, handle, r['formname'])
-
- if form['info1'] != newform:
- print 'SetForm: mismatch: %s != %s' % \
- (r['info']['info1'], f)
- sys.exit(1)
-
-
-def test_AddForm(pipe, handle):
-
- print 'spoolss_AddForm()'
-
- formname = '__testform__'
-
- r = {}
- r['handle'] = handle
- r['level'] = 1
- r['info'] = {}
- r['info']['info1'] = {}
- r['info']['info1']['formname'] = formname
- r['info']['info1']['flags'] = 0x0002
- r['info']['info1']['width'] = 100
- r['info']['info1']['length'] = 100
- r['info']['info1']['left'] = 0
- r['info']['info1']['top'] = 1000
- r['info']['info1']['right'] = 2000
- r['info']['info1']['bottom'] = 3000
-
- try:
- result = dcerpc.spoolss_AddForm(pipe, r)
- except dcerpc.WERROR, arg:
- if arg[0] == dcerpc.WERR_ALREADY_EXISTS:
- test_DeleteForm(pipe, handle, formname)
- result = dcerpc.spoolss_AddForm(pipe, r)
-
- f = test_GetForm(pipe, handle, formname)
-
- if r['info']['info1'] != f:
- print 'AddForm: mismatch: %s != %s' % \
- (r['info']['info1'], f)
- sys.exit(1)
-
- r['formname'] = formname
-
- test_SetForm(pipe, handle, r['info'])
-
- test_DeleteForm(pipe, handle, formname)
-
-
-def test_EnumJobs(pipe, handle):
-
- print 'spoolss_EnumJobs()'
-
- r = {}
- r['handle'] = handle
- r['firstjob'] = 0
- r['numjobs'] = 0xffffffff
- r['level'] = 1
-
- result = ResizeBufferCall(dcerpc.spoolss_EnumJobs, pipe, r)
-
- if result['buffer'] is None:
- return
-
- jobs = dcerpc.unmarshall_spoolss_JobInfo_array(
- result['buffer'], r['level'], result['count'])
-
- for job in jobs:
-
- s = {}
- s['handle'] = handle
- s['job_id'] = job['info1']['job_id']
- s['level'] = 1
-
- result = ResizeBufferCall(dcerpc.spoolss_GetJob, pipe, s)
-
- if result['info'] != job:
- print 'EnumJobs: mismatch: %s != %s' % (result['info'], job)
- sys.exit(1)
-
-
- # TODO: AddJob, DeleteJob, ScheduleJob
-
-
-def test_EnumPrinterData(pipe, handle):
-
- print 'test_EnumPrinterData()'
-
- enum_index = 0
-
- while 1:
-
- r = {}
- r['handle'] = handle
- r['enum_index'] = enum_index
-
- r['value_offered'] = 0
- r['data_size'] = 0
-
- result = dcerpc.spoolss_EnumPrinterData(pipe, r)
-
- r['value_offered'] = result['value_needed']
- r['data_size'] = result['data_size']
-
- result = dcerpc.spoolss_EnumPrinterData(pipe, r)
-
- if result['result'] == dcerpc.WERR_NO_MORE_ITEMS:
- break
-
- s = {}
- s['handle'] = handle
- s['value_name'] = result['value_name']
-
- result2 = ResizeBufferCall(dcerpc.spoolss_GetPrinterData, pipe, s)
-
- if result['buffer'][:result2['buf_size']] != result2['buffer']:
- print 'EnumPrinterData/GetPrinterData mismatch'
- sys.exit(1)
-
- enum_index += 1
-
-
-def test_SetPrinterDataEx(pipe, handle):
-
- valuename = '__printerdataextest__'
- data = '12345'
-
- r = {}
- r['handle'] = handle
- r['key_name'] = 'DsSpooler'
- r['value_name'] = valuename
- r['type'] = 3
- r['buffer'] = data
- r['buf_size'] = len(data)
-
- result = dcerpc.spoolss_SetPrinterDataEx(pipe, r)
-
-
-def test_EnumPrinterDataEx(pipe, handle):
-
- r = {}
- r['handle'] = handle
- r['key_name'] = 'DsSpooler'
- r['buf_size'] = 0
-
- result = dcerpc.spoolss_EnumPrinterDataEx(pipe, r)
-
- if result['result'] == dcerpc.WERR_MORE_DATA:
- r['buf_size'] = result['buf_size']
-
- result = dcerpc.spoolss_EnumPrinterDataEx(pipe, r)
-
- # TODO: test spoolss_GetPrinterDataEx()
-
-
-def test_SetPrinterData(pipe, handle):
-
- print 'testing spoolss_SetPrinterData()'
-
- valuename = '__printerdatatest__'
- data = '12345'
-
- r = {}
- r['handle'] = handle
- r['value_name'] = valuename
- r['type'] = 3 # REG_BINARY
- r['buffer'] = data
- r['real_len'] = 5
-
- dcerpc.spoolss_SetPrinterData(pipe, r)
-
- s = {}
- s['handle'] = handle
- s['value_name'] = valuename
-
- result = ResizeBufferCall(dcerpc.spoolss_GetPrinterData, pipe, r)
-
- if result['buffer'] != data:
- print 'SetPrinterData: mismatch'
- sys.exit(1)
-
- dcerpc.spoolss_DeletePrinterData(pipe, r)
-
-
-def test_EnumPrinters(pipe):
-
- print 'testing spoolss_EnumPrinters()'
-
- printer_names = None
-
- r = {}
- r['flags'] = 0x02
- r['server'] = None
-
- for level in [0, 1, 2, 4, 5]:
-
- print 'test_EnumPrinters(level = %d)' % level
-
- r['level'] = level
-
- result = ResizeBufferCall(dcerpc.spoolss_EnumPrinters, pipe, r)
-
- printers = dcerpc.unmarshall_spoolss_PrinterInfo_array(
- result['buffer'], r['level'], result['count'])
-
- if level == 2:
- for p in printers:
-
- # A nice check is for the specversion in the
- # devicemode. This has always been observed to be
- # 1025.
-
- if p['info2']['devmode']['specversion'] != 1025:
- print 'test_EnumPrinters: specversion != 1025'
- sys.exit(1)
-
- r['level'] = 1
- result = ResizeBufferCall(dcerpc.spoolss_EnumPrinters, pipe, r)
-
- for printer in dcerpc.unmarshall_spoolss_PrinterInfo_array(
- result['buffer'], r['level'], result['count']):
-
- if string.find(printer['info1']['name'], '\\\\') == 0:
- print 'Skipping remote printer %s' % printer['info1']['name']
- continue
-
- printername = string.split(printer['info1']['name'], ',')[0]
-
- handle = test_OpenPrinterEx(pipe, printername)
-
- test_GetPrinter(pipe, handle)
- test_EnumPorts(pipe, handle)
- test_EnumForms(pipe, handle)
- test_AddForm(pipe, handle)
- test_EnumJobs(pipe, handle)
- test_EnumPrinterData(pipe, handle)
- test_EnumPrinterDataEx(pipe, handle)
- test_SetPrinterData(pipe, handle)
-# test_SetPrinterDataEx(pipe, handle)
- test_ClosePrinter(pipe, handle)
-
-
-def test_EnumPrinterDrivers(pipe):
-
- print 'test spoolss_EnumPrinterDrivers()'
-
- for level in [1, 2, 3]:
-
- r = {}
- r['server'] = None
- r['environment'] = None
- r['level'] = level
-
- result = ResizeBufferCall(dcerpc.spoolss_EnumPrinterDrivers, pipe, r)
-
- drivers = dcerpc.unmarshall_spoolss_DriverInfo_array(
- result['buffer'], r['level'], result['count'])
-
- if level == 1:
- driver_names = map(lambda x: x['info1']['driver_name'], drivers)
-
-
-def test_PrintServer(pipe):
-
- handle = test_OpenPrinterEx(pipe, None)
-
- # EnumForms and AddForm tests return WERR_BADFID here (??)
-
- test_ClosePrinter(pipe, handle)
-
-
-def runtests(binding, domain, username, password):
-
- print 'Testing SPOOLSS pipe'
-
- pipe = dcerpc.pipe_connect(binding,
- dcerpc.DCERPC_SPOOLSS_UUID, dcerpc.DCERPC_SPOOLSS_VERSION,
- domain, username, password)
-
- test_EnumPrinters(pipe)
- test_EnumPrinterDrivers(pipe)
- test_PrintServer(pipe)
diff --git a/source4/scripting/python/samba/torture/torture_samr.py b/source4/scripting/python/samba/torture/torture_samr.py
deleted file mode 100755
index 15c6dc1a76..0000000000
--- a/source4/scripting/python/samba/torture/torture_samr.py
+++ /dev/null
@@ -1,221 +0,0 @@
-#!/usr/bin/python
-
-import sys
-import dcerpc, samr
-
-def test_Connect(pipe):
-
- handle = samr.Connect(pipe)
- handle = samr.Connect2(pipe)
- handle = samr.Connect3(pipe)
- handle = samr.Connect4(pipe)
-
- # WIN2K3 only?
-
- try:
- handle = samr.Connect5(pipe)
- except dcerpc.NTSTATUS, arg:
- if arg[0] != 0xc00000d2L: # NT_STATUS_NET_WRITE_FAULT
- raise
-
- return handle
-
-def test_UserHandle(user_handle):
-
- # QuerySecurity()/SetSecurity()
-
- user_handle.SetSecurity(user_handle.QuerySecurity())
-
- # GetUserPwInfo()
-
- user_handle.GetUserPwInfo()
-
- # GetUserInfo()
-
- for level in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 20,
- 21, 23, 24, 25, 26]:
-
- try:
- user_handle.QueryUserInfo(level)
- user_handle.QueryUserInfo2(level)
- except dcerpc.NTSTATUS, arg:
- if arg[0] != 0xc0000003L: # NT_STATUS_INVALID_INFO_CLASS
- raise
-
- # GetGroupsForUser()
-
- user_handle.GetGroupsForUser()
-
- # TestPrivateFunctionsUser()
-
- try:
- user_handle.TestPrivateFunctionsUser()
- except dcerpc.NTSTATUS, arg:
- if arg[0] != 0xC0000002L:
- raise
-
-def test_GroupHandle(group_handle):
-
- # QuerySecurity()/SetSecurity()
-
- group_handle.SetSecurity(group_handle.QuerySecurity())
-
- # QueryGroupInfo()
-
- for level in [1, 2, 3, 4, 5]:
- info = group_handle.QueryGroupInfo(level)
-
- # TODO: SetGroupinfo()
-
- # QueryGroupMember()
-
- group_handle.QueryGroupMember()
-
-def test_AliasHandle(alias_handle):
-
- # QuerySecurity()/SetSecurity()
-
- alias_handle.SetSecurity(alias_handle.QuerySecurity())
-
- print alias_handle.GetMembersInAlias()
-
-def test_DomainHandle(name, sid, domain_handle):
-
- print 'testing %s (%s)' % (name, sid)
-
- # QuerySecurity()/SetSecurity()
-
- domain_handle.SetSecurity(domain_handle.QuerySecurity())
-
- # LookupNames(), none mapped
-
- try:
- domain_handle.LookupNames(['xxNONAMExx'])
- except dcerpc.NTSTATUS, arg:
- if arg[0] != 0xc0000073L:
- raise dcerpc.NTSTATUS(arg)
-
- # LookupNames(), some mapped
-
- if name != 'Builtin':
- domain_handle.LookupNames(['Administrator', 'xxNONAMExx'])
-
- # QueryDomainInfo()/SetDomainInfo()
-
- levels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13]
- set_ok = [1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0]
-
- for i in range(len(levels)):
-
- info = domain_handle.QueryDomainInfo(level = levels[i])
-
- try:
- domain_handle.SetDomainInfo(levels[i], info)
- except dcerpc.NTSTATUS, arg:
- if not (arg[0] == 0xc0000003L and not set_ok[i]):
- raise
-
- # QueryDomainInfo2()
-
- levels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13]
-
- for i in range(len(levels)):
- domain_handle.QueryDomainInfo2(level = levels[i])
-
- # EnumDomainUsers
-
- print 'testing users'
-
- users = domain_handle.EnumDomainUsers()
- rids = domain_handle.LookupNames(users)
-
- for i in range(len(users)):
- test_UserHandle(domain_handle.OpenUser(rids[0][i]))
-
- # QueryDisplayInfo
-
- for i in [1, 2, 3, 4, 5]:
- domain_handle.QueryDisplayInfo(level = i)
- domain_handle.QueryDisplayInfo2(level = i)
- domain_handle.QueryDisplayInfo3(level = i)
-
- # EnumDomainGroups
-
- print 'testing groups'
-
- groups = domain_handle.EnumDomainGroups()
- rids = domain_handle.LookupNames(groups)
-
- for i in range(len(groups)):
- test_GroupHandle(domain_handle.OpenGroup(rids[0][i]))
-
- # EnumDomainAliases
-
- print 'testing aliases'
-
- aliases = domain_handle.EnumDomainAliases()
- rids = domain_handle.LookupNames(aliases)
-
- for i in range(len(aliases)):
- test_AliasHandle(domain_handle.OpenAlias(rids[0][i]))
-
- # CreateUser
- # CreateUser2
- # CreateDomAlias
- # RidToSid
- # RemoveMemberFromForeignDomain
- # CreateDomainGroup
- # GetAliasMembership
-
- # GetBootKeyInformation()
-
- try:
- domain_handle.GetBootKeyInformation()
- except dcerpc.NTSTATUS, arg:
- pass
-
- # TestPrivateFunctionsDomain()
-
- try:
- domain_handle.TestPrivateFunctionsDomain()
- except dcerpc.NTSTATUS, arg:
- if arg[0] != 0xC0000002L:
- raise
-
-def test_ConnectHandle(connect_handle):
-
- print 'testing connect handle'
-
- # QuerySecurity/SetSecurity
-
- connect_handle.SetSecurity(connect_handle.QuerySecurity())
-
- # Lookup bogus domain
-
- try:
- connect_handle.LookupDomain('xxNODOMAINxx')
- except dcerpc.NTSTATUS, arg:
- if arg[0] != 0xC00000DFL: # NT_STATUS_NO_SUCH_DOMAIN
- raise
-
- # Test all domains
-
- for domain_name in connect_handle.EnumDomains():
-
- connect_handle.GetDomPwInfo(domain_name)
- sid = connect_handle.LookupDomain(domain_name)
- domain_handle = connect_handle.OpenDomain(sid)
-
- test_DomainHandle(domain_name, sid, domain_handle)
-
- # TODO: Test Shutdown() function
-
-def runtests(binding, creds):
-
- print 'Testing SAMR pipe'
-
- pipe = dcerpc.pipe_connect(binding,
- dcerpc.DCERPC_SAMR_UUID, int(dcerpc.DCERPC_SAMR_VERSION), creds)
-
- handle = test_Connect(pipe)
- test_ConnectHandle(handle)
diff --git a/source4/scripting/python/samba/torture/torture_tdb.py b/source4/scripting/python/samba/torture/torture_tdb.py
deleted file mode 100755
index 7f97caf6cb..0000000000
--- a/source4/scripting/python/samba/torture/torture_tdb.py
+++ /dev/null
@@ -1,90 +0,0 @@
-#!/usr/bin/python
-
-import sys, os
-import Tdb
-
-def fail(msg):
- print 'FAILED:', msg
- sys.exit(1)
-
-tdb_file = '/tmp/torture_tdb.tdb'
-
-# Create temporary tdb file
-
-t = Tdb.Tdb(tdb_file, flags = Tdb.CLEAR_IF_FIRST)
-
-# Check non-existent key throws KeyError exception
-
-try:
- t['__none__']
-except KeyError:
- pass
-else:
- fail('non-existent key did not throw KeyError')
-
-# Check storing key
-
-t['bar'] = '1234'
-if t['bar'] != '1234':
- fail('store key failed')
-
-# Check key exists
-
-if not t.has_key('bar'):
- fail('has_key() failed for existing key')
-
-if t.has_key('__none__'):
- fail('has_key() succeeded for non-existent key')
-
-# Delete key
-
-try:
- del(t['__none__'])
-except KeyError:
- pass
-else:
- fail('delete of non-existent key did not throw KeyError')
-
-del t['bar']
-if t.has_key('bar'):
- fail('delete of existing key did not delete key')
-
-# Clear all keys
-
-t.clear()
-if len(t) != 0:
- fail('clear failed to remove all keys')
-
-# Other dict functions
-
-t['a'] = '1'
-t['ab'] = '12'
-t['abc'] = '123'
-
-if len(t) != 3:
- fail('len method produced wrong value')
-
-keys = t.keys()
-values = t.values()
-items = t.items()
-
-if set(keys) != set(['a', 'ab', 'abc']):
- fail('keys method produced wrong values')
-
-if set(values) != set(['1', '12', '123']):
- fail('values method produced wrong values')
-
-if set(items) != set([('a', '1'), ('ab', '12'), ('abc', '123')]):
- fail('values method produced wrong values')
-
-t.close()
-
-# Re-open read-only
-
-t = Tdb.Tdb(tdb_file, open_flags = os.O_RDONLY)
-t.keys()
-t.close()
-
-# Clean up
-
-os.unlink(tdb_file)
diff --git a/source4/scripting/python/samba/torture/winreg.py b/source4/scripting/python/samba/torture/winreg.py
deleted file mode 100755
index eb60b9847e..0000000000
--- a/source4/scripting/python/samba/torture/winreg.py
+++ /dev/null
@@ -1,165 +0,0 @@
-#!/usr/bin/python
-
-import sys, dcerpc
-
-def test_OpenHKLM(pipe):
-
- r = {}
- r['unknown'] = {}
- r['unknown']['unknown0'] = 0x9038
- r['unknown']['unknown1'] = 0x0000
- r['access_required'] = 0x02000000
-
- result = dcerpc.winreg_OpenHKLM(pipe, r)
-
- return result['handle']
-
-def test_QueryInfoKey(pipe, handle):
-
- r = {}
- r['handle'] = handle
- r['class'] = {}
- r['class']['name'] = None
-
- return dcerpc.winreg_QueryInfoKey(pipe, r)
-
-def test_CloseKey(pipe, handle):
-
- r = {}
- r['handle'] = handle
-
- dcerpc.winreg_CloseKey(pipe, r)
-
-def test_FlushKey(pipe, handle):
-
- r = {}
- r['handle'] = handle
-
- dcerpc.winreg_FlushKey(pipe, r)
-
-def test_GetVersion(pipe, handle):
-
- r = {}
- r['handle'] = handle
-
- dcerpc.winreg_GetVersion(pipe, r)
-
-def test_GetKeySecurity(pipe, handle):
-
- r = {}
- r['handle'] = handle
- r['unknown'] = 4
- r['size'] = None
- r['data'] = {}
- r['data']['max_len'] = 0
- r['data']['data'] = ''
-
- result = dcerpc.winreg_GetKeySecurity(pipe, r)
-
- print result
-
- if result['result'] == dcerpc.WERR_INSUFFICIENT_BUFFER:
- r['size'] = {}
- r['size']['max_len'] = result['data']['max_len']
- r['size']['offset'] = 0
- r['size']['len'] = result['data']['max_len']
-
- result = dcerpc.winreg_GetKeySecurity(pipe, r)
-
- print result
-
- sys.exit(1)
-
-def test_Key(pipe, handle, name, depth = 0):
-
- # Don't descend too far. Registries can be very deep.
-
- if depth > 2:
- return
-
- try:
- keyinfo = test_QueryInfoKey(pipe, handle)
- except dcerpc.WERROR, arg:
- if arg[0] == dcerpc.WERR_ACCESS_DENIED:
- return
-
- test_GetVersion(pipe, handle)
-
- test_FlushKey(pipe, handle)
-
- test_GetKeySecurity(pipe, handle)
-
- # Enumerate values in this key
-
- r = {}
- r['handle'] = handle
- r['name_in'] = {}
- r['name_in']['len'] = 0
- r['name_in']['max_len'] = (keyinfo['max_valnamelen'] + 1) * 2
- r['name_in']['buffer'] = {}
- r['name_in']['buffer']['max_len'] = keyinfo['max_valnamelen'] + 1
- r['name_in']['buffer']['offset'] = 0
- r['name_in']['buffer']['len'] = 0
- r['type'] = 0
- r['value_in'] = {}
- r['value_in']['max_len'] = keyinfo['max_valbufsize']
- r['value_in']['offset'] = 0
- r['value_in']['len'] = 0
- r['value_len1'] = keyinfo['max_valbufsize']
- r['value_len2'] = 0
-
- for i in range(0, keyinfo['num_values']):
-
- r['enum_index'] = i
-
- dcerpc.winreg_EnumValue(pipe, r)
-
- # Recursively test subkeys of this key
-
- r = {}
- r['handle'] = handle
- r['key_name_len'] = 0
- r['unknown'] = 0x0414
- r['in_name'] = {}
- r['in_name']['unknown'] = 0x20a
- r['in_name']['key_name'] = {}
- r['in_name']['key_name']['name'] = None
- r['class'] = {}
- r['class']['name'] = None
- r['last_changed_time'] = {}
- r['last_changed_time']['low'] = 0
- r['last_changed_time']['high'] = 0
-
- for i in range(0, keyinfo['num_subkeys']):
-
- r['enum_index'] = i
-
- subkey = dcerpc.winreg_EnumKey(pipe, r)
-
- s = {}
- s['handle'] = handle
- s['keyname'] = {}
- s['keyname']['name'] = subkey['out_name']['name']
- s['unknown'] = 0
- s['access_mask'] = 0x02000000
-
- result = dcerpc.winreg_OpenKey(pipe, s)
-
- test_Key(pipe, result['handle'], name + '/' + s['keyname']['name'],
- depth + 1)
-
- test_CloseKey(pipe, result['handle'])
-
- # Enumerate values
-
-def runtests(binding, domain, username, password):
-
- print 'Testing WINREG pipe'
-
- pipe = dcerpc.pipe_connect(binding,
- dcerpc.DCERPC_WINREG_UUID, dcerpc.DCERPC_WINREG_VERSION,
- domain, username, password)
-
- handle = test_OpenHKLM(pipe)
-
- test_Key(pipe, handle, 'HKLM')
diff --git a/source4/scripting/python/samba/upgrade.py b/source4/scripting/python/samba/upgrade.py
index 81945525e6..5bcc4294ba 100644
--- a/source4/scripting/python/samba/upgrade.py
+++ b/source4/scripting/python/samba/upgrade.py
@@ -1,22 +1,32 @@
-#!/usr/bin/python
+# backend code for upgrading from Samba3
+# Copyright Jelmer Vernooij 2005-2007
#
-# backend code for upgrading from Samba3
-# Copyright Jelmer Vernooij 2005-2007
-# Released under the GNU GPL v3 or later
+# 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 code for upgrading from Samba 3 to Samba 4."""
__docformat__ = "restructuredText"
-from provision import provision, FILL_DRS
import grp
import ldb
import time
import pwd
-import registry
-from samba import Ldb
+
+from samba import Ldb, registry
from samba.param import LoadParm
+from samba.provision import provision
def import_sam_policy(samldb, policy, dn):
"""Import a Samba 3 policy database."""
@@ -34,7 +44,7 @@ samba3UserMustLogonToChangePassword: %d
samba3BadLockoutMinutes: %d
samba3DisconnectTime: %d
-""" % (dn, policy.min_password_length,
+""" % (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,
@@ -43,7 +53,7 @@ samba3DisconnectTime: %d
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
@@ -59,7 +69,7 @@ def import_sam_account(samldb,acc,domaindn,domainsid):
if acc.fullname is None:
acc.fullname = acc.username
-
+
assert acc.fullname is not None
assert acc.nt_username is not None
@@ -78,8 +88,8 @@ def import_sam_account(samldb,acc,domaindn,domainsid):
"samba3Domain": acc.domain,
"samba3DirDrive": acc.dir_drive,
"samba3MungedDial": acc.munged_dial,
- "samba3Homedir": acc.homedir,
- "samba3LogonScript": acc.logon_script,
+ "samba3Homedir": acc.homedir,
+ "samba3LogonScript": acc.logon_script,
"samba3ProfilePath": acc.profile_path,
"samba3Workstations": acc.workstations,
"samba3KickOffTime": str(acc.kickoff_time),
@@ -95,7 +105,7 @@ def import_sam_account(samldb,acc,domaindn,domainsid):
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
@@ -109,7 +119,7 @@ def import_sam_group(samldb, sid, gid, sid_name_use, nt_name, comment, domaindn)
if nt_name in ("Domain Guests", "Domain Users", "Domain Admins"):
return None
-
+
if gid == -1:
gr = grp.getgrnam(nt_name)
else:
@@ -121,12 +131,12 @@ def import_sam_group(samldb, sid, gid, sid_name_use, nt_name, comment, domaindn)
unixname = gr.gr_name
assert unixname is not None
-
+
samldb.add({
"dn": "cn=%s,%s" % (nt_name, domaindn),
"objectClass": ["top", "group"],
"description": comment,
- "cn": nt_name,
+ "cn": nt_name,
"objectSid": sid,
"unixName": unixname,
"samba3SidNameUse": str(sid_name_use)
@@ -160,7 +170,7 @@ def import_idmap(samdb,samba3_idmap,domaindn):
def import_wins(samba4_winsdb, samba3_winsdb):
"""Import settings from a Samba3 WINS database.
-
+
:param samba4_winsdb: WINS database to import to
:param samba3_winsdb: WINS database to import from
"""
@@ -208,73 +218,6 @@ def import_wins(samba4_winsdb, samba3_winsdb):
"objectClass": "winsMaxVersion",
"maxVersion": str(version_id)})
-def upgrade_provision(samba3, setup_dir, message, credentials, session_info, smbconf, targetdir):
- oldconf = samba3.get_conf()
-
- if oldconf.get("domain logons") == "True":
- serverrole = "domain controller"
- else:
- if oldconf.get("security") == "user":
- serverrole = "standalone"
- else:
- serverrole = "member server"
-
- domainname = oldconf.get("workgroup")
- if domainname:
- domainname = str(domainname)
- realm = oldconf.get("realm")
- netbiosname = oldconf.get("netbios name")
-
- secrets_db = samba3.get_secrets_db()
-
- if domainname is None:
- domainname = secrets_db.domains()[0]
- message("No domain specified in smb.conf file, assuming '%s'" % domainname)
-
- if realm is None:
- realm = domainname.lower()
- message("No realm specified in smb.conf file, assuming '%s'\n" % realm)
-
- domainguid = secrets_db.get_domain_guid(domainname)
- domainsid = secrets_db.get_sid(domainname)
- if domainsid is None:
- message("Can't find domain secrets for '%s'; using random SID\n" % domainname)
-
- if netbiosname is not None:
- machinepass = secrets_db.get_machine_password(netbiosname)
- else:
- machinepass = None
-
- result = provision(setup_dir=setup_dir, message=message,
- samdb_fill=FILL_DRS, smbconf=smbconf, session_info=session_info,
- credentials=credentials, realm=realm,
- domain=domainname, domainsid=domainsid, domainguid=domainguid,
- machinepass=machinepass, serverrole=serverrole, targetdir=targetdir)
-
- import_wins(Ldb(result.paths.winsdb), samba3.get_wins_db())
-
- # FIXME: import_registry(registry.Registry(), samba3.get_registry())
-
- # FIXME: import_idmap(samdb,samba3.get_idmap_db(),domaindn)
-
- 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)
-
- # FIXME: Aliases
-
- passdb = samba3.get_sam_db()
- for name in passdb:
- user = passdb[name]
- #FIXME: import_sam_account(result.samdb, user, domaindn, domainsid)
-
- if hasattr(passdb, 'ldap_url'):
- message("Enabling Samba3 LDAP mappings for SAM database")
-
- enable_samba3sam(result.samdb, passdb.ldap_url)
-
-
def enable_samba3sam(samdb, ldapurl):
"""Enable Samba 3 LDAP URL database.
@@ -292,7 +235,7 @@ replace: @LIST
smbconf_keep = [
- "dos charset",
+ "dos charset",
"unix charset",
"display charset",
"comment",
@@ -389,7 +332,7 @@ def upgrade_smbconf(oldconf,mark):
"""Remove configuration variables not present in Samba4
:param oldconf: Old configuration structure
- :param mark: Whether removed configuration variables should be
+ :param mark: Whether removed configuration variables should be
kept in the new configuration as "samba3:<name>"
"""
data = oldconf.data()
@@ -434,3 +377,76 @@ def import_registry(samba4_registry, samba3_regdb):
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"
+ else:
+ if oldconf.get("security") == "user":
+ serverrole = "standalone"
+ else:
+ serverrole = "member server"
+
+ domainname = oldconf.get("workgroup")
+ realm = oldconf.get("realm")
+ netbiosname = oldconf.get("netbios name")
+
+ secrets_db = samba3.get_secrets_db()
+
+ if domainname is None:
+ domainname = secrets_db.domains()[0]
+ logger.warning("No domain 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
+ 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)
+
+ if netbiosname is not None:
+ machinepass = secrets_db.get_machine_password(netbiosname)
+ else:
+ 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)
+
+ import_wins(Ldb(result.paths.winsdb), samba3.get_wins_db())
+
+ # FIXME: import_registry(registry.Registry(), samba3.get_registry())
+
+ # FIXME: import_idmap(samdb,samba3.get_idmap_db(),domaindn)
+
+ 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)
+
+ # FIXME: Aliases
+
+ passdb = samba3.get_sam_db()
+ for name in passdb:
+ user = passdb[name]
+ #FIXME: import_sam_account(result.samdb, user, domaindn, domainsid)
+
+ if hasattr(passdb, 'ldap_url'):
+ logger.info("Enabling Samba3 LDAP mappings for SAM database")
+
+ enable_samba3sam(result.samdb, passdb.ldap_url)
+
diff --git a/source4/scripting/python/samba/upgradehelpers.py b/source4/scripting/python/samba/upgradehelpers.py
new file mode 100755
index 0000000000..3a7dfb3997
--- /dev/null
+++ b/source4/scripting/python/samba/upgradehelpers.py
@@ -0,0 +1,953 @@
+#!/usr/bin/env python
+#
+# Helpers for provision stuff
+# Copyright (C) Matthieu Patou <mat@matws.net> 2009-2010
+#
+# Based on provision a Samba4 server by
+# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
+# Copyright (C) Andrew Bartlett <abartlet@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/>.
+
+"""Helers 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,
+ 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.misc import SEC_CHAN_BDC
+from samba.ndr import ndr_unpack
+from samba.samdb import SamDB
+
+# All the ldb related to registry are commented because the path for them is
+# relative in the provisionPath object
+# And so opening them create a file in the current directory which is not what
+# we want
+# I still keep them commented because I plan soon to make more cleaner
+ERROR = -1
+SIMPLE = 0x00
+CHANGE = 0x01
+CHANGESD = 0x02
+GUESS = 0x04
+PROVISION = 0x08
+CHANGEALL = 0xff
+
+hashAttrNotCopied = set(["dn", "whenCreated", "whenChanged", "objectGUID",
+ "uSNCreated", "replPropertyMetaData", "uSNChanged", "parentGUID",
+ "objectCategory", "distinguishedName", "nTMixedDomain",
+ "showInAdvancedViewOnly", "instanceType", "msDS-Behavior-Version",
+ "nextRid", "cn", "versionNumber", "lmPwdHistory", "pwdLastSet",
+ "ntPwdHistory", "unicodePwd","dBCSPwd", "supplementalCredentials",
+ "gPCUserExtensionNames", "gPCMachineExtensionNames","maxPwdAge", "secret",
+ "possibleInferiors", "privilege", "sAMAccountType"])
+
+
+class ProvisionLDB(object):
+
+ def __init__(self):
+ self.sam = None
+ self.secrets = None
+ self.idmap = None
+ self.privilege = None
+ self.hkcr = None
+ self.hkcu = None
+ self.hku = None
+ self.hklm = None
+
+ def startTransactions(self):
+ self.sam.transaction_start()
+ self.secrets.transaction_start()
+ self.idmap.transaction_start()
+ self.privilege.transaction_start()
+# TO BE DONE
+# self.hkcr.transaction_start()
+# self.hkcu.transaction_start()
+# self.hku.transaction_start()
+# self.hklm.transaction_start()
+
+ 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
+
+ return ok
+# TO BE DONE
+# self.hkcr.transaction_cancel()
+# self.hkcu.transaction_cancel()
+# self.hku.transaction_cancel()
+# self.hklm.transaction_cancel()
+
+ def groupedCommit(self):
+ try:
+ self.sam.transaction_prepare_commit()
+ self.secrets.transaction_prepare_commit()
+ self.idmap.transaction_prepare_commit()
+ self.privilege.transaction_prepare_commit()
+ except Exception:
+ return self.groupedRollback()
+# TO BE DONE
+# self.hkcr.transaction_prepare_commit()
+# self.hkcu.transaction_prepare_commit()
+# 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()
+ except Exception:
+ return self.groupedRollback()
+
+# TO BE DONE
+# self.hkcr.transaction_commit()
+# self.hkcu.transaction_commit()
+# self.hku.transaction_commit()
+# self.hklm.transaction_commit()
+ return True
+
+def get_ldbs(paths, creds, session, lp):
+ """Return LDB object mapped on most important databases
+
+ :param paths: An object holding the different importants paths for provision object
+ :param creds: Credential used for openning LDB files
+ :param session: Session to use for openning LDB files
+ :param lp: A loadparam object
+ :return: A ProvisionLDB object that contains LDB object for the different LDB files of the provision"""
+
+ ldbs = ProvisionLDB()
+
+ ldbs.sam = SamDB(paths.samdb, session_info=session, credentials=creds, lp=lp, options=["modules:samba_dsdb"])
+ ldbs.secrets = Ldb(paths.secrets, session_info=session, credentials=creds, lp=lp)
+ ldbs.idmap = Ldb(paths.idmapdb, session_info=session, credentials=creds, lp=lp)
+ ldbs.privilege = Ldb(paths.privilege, session_info=session, credentials=creds, lp=lp)
+# ldbs.hkcr = Ldb(paths.hkcr, session_info=session, credentials=creds, lp=lp)
+# ldbs.hkcu = Ldb(paths.hkcu, session_info=session, credentials=creds, lp=lp)
+# ldbs.hku = Ldb(paths.hku, session_info=session, credentials=creds, lp=lp)
+# ldbs.hklm = Ldb(paths.hklm, session_info=session, credentials=creds, lp=lp)
+
+ return ldbs
+
+
+def usn_in_range(usn, range):
+ """Check if the usn is in one of the range provided.
+ To do so, the value is checked to be between the lower bound and
+ higher bound of a range
+
+ :param usn: A integer value corresponding to the usn that we want to update
+ :param range: A list of integer representing ranges, lower bounds are in
+ the even indices, higher in odd indices
+ :return: True if the usn is in one of the range, False otherwise
+ """
+
+ idx = 0
+ cont = True
+ ok = False
+ while cont:
+ if idx == len(range):
+ cont = False
+ continue
+ if usn < int(range[idx]):
+ if idx %2 == 1:
+ ok = True
+ cont = False
+ if usn == int(range[idx]):
+ cont = False
+ ok = True
+ idx = idx + 1
+ return ok
+
+
+def get_paths(param, targetdir=None, smbconf=None):
+ """Get paths to important provision objects (smb.conf, ldb files, ...)
+
+ :param param: Param object
+ :param targetdir: Directory where the provision is (or will be) stored
+ :param smbconf: Path to the smb.conf file
+ :return: A list with the path of important provision objects"""
+ if targetdir is not None:
+ etcdir = os.path.join(targetdir, "etc")
+ if not os.path.exists(etcdir):
+ os.makedirs(etcdir)
+ smbconf = os.path.join(etcdir, "smb.conf")
+ if smbconf is None:
+ smbconf = param.default_path()
+
+ if not os.path.exists(smbconf):
+ raise ProvisioningError("Unable to find smb.conf")
+
+ lp = param.LoadParm()
+ lp.load(smbconf)
+ paths = provision_paths_from_lp(lp, lp.get("realm"))
+ return paths
+
+def update_policyids(names, samdb):
+ """Update policy ids that could have changed after sam update
+
+ :param names: List of key provision parameters
+ :param samdb: An Ldb object conntected with the sam DB
+ """
+ # policy guid
+ res = samdb.search(expression="(displayName=Default Domain Policy)",
+ base="CN=Policies,CN=System," + str(names.rootdn),
+ scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
+ names.policyid = str(res[0]["cn"]).replace("{","").replace("}","")
+ # dc policy guid
+ res2 = samdb.search(expression="(displayName=Default Domain Controllers"
+ " Policy)",
+ base="CN=Policies,CN=System," + str(names.rootdn),
+ scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
+ if len(res2) == 1:
+ names.policyid_dc = str(res2[0]["cn"]).replace("{","").replace("}","")
+ else:
+ 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.
+
+ This provision will be the reference for knowing what has changed in the
+ since the latest upgrade in the current provision
+
+ :param names: List of provision parameters
+ :param creds: Credentials for the authentification
+ :param session: Session object
+ :param smbconf: Path to the smb.conf file
+ :param provdir: Directory where the provision will be stored
+ :param logger: A Logger
+ """
+ if os.path.isdir(provdir):
+ shutil.rmtree(provdir)
+ os.mkdir(provdir)
+ logger.info("Provision stored in %s", provdir)
+ 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,
+ policyguid=names.policyid, policyguid_dc=names.policyid_dc,
+ 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,
+ 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)
+
+
+def dn_sort(x, y):
+ """Sorts two DNs in the lexicographical order it and put higher level DN
+ before.
+
+ So given the dns cn=bar,cn=foo and cn=foo the later will be return as
+ smaller
+
+ :param x: First object to compare
+ :param y: Second object to compare
+ """
+ p = re.compile(r'(?<!\\), ?')
+ tab1 = p.split(str(x))
+ tab2 = p.split(str(y))
+ minimum = min(len(tab1), len(tab2))
+ len1 = len(tab1)-1
+ len2 = len(tab2)-1
+ # Note: python range go up to upper limit but do not include it
+ for i in range(0, minimum):
+ ret = cmp(tab1[len1-i], tab2[len2-i])
+ if ret != 0:
+ return ret
+ else:
+ if i == minimum-1:
+ assert len1!=len2,"PB PB PB" + " ".join(tab1)+" / " + " ".join(tab2)
+ if len1 > len2:
+ return 1
+ else:
+ return -1
+ return ret
+
+
+def identic_rename(ldbobj, dn):
+ """Perform a back and forth rename to trigger renaming on attribute that
+ can't be directly modified.
+
+ :param lbdobj: An Ldb Object
+ :param dn: DN of the object to manipulate
+ """
+ (before, after) = str(dn).split('=', 1)
+ # we need to use relax to avoid the subtree_rename constraints
+ ldbobj.rename(dn, ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), ["relax:0"])
+ ldbobj.rename(ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), dn, ["relax:0"])
+
+
+def chunck_acl(acl):
+ """Return separate ACE of an ACL
+
+ :param acl: A string representing the ACL
+ :return: A hash with different parts
+ """
+
+ p = re.compile(r'(\w+)?(\(.*?\))')
+ tab = p.findall(acl)
+
+ hash = {}
+ hash["aces"] = []
+ for e in tab:
+ if len(e[0]) > 0:
+ hash["flags"] = e[0]
+ hash["aces"].append(e[1])
+
+ return hash
+
+
+def chunck_sddl(sddl):
+ """ Return separate parts of the SDDL (owner, group, ...)
+
+ :param sddl: An string containing the SDDL to chunk
+ :return: A hash with the different chunk
+ """
+
+ p = re.compile(r'([OGDS]:)(.*?)(?=(?:[GDS]:|$))')
+ tab = p.findall(sddl)
+
+ hash = {}
+ for e in tab:
+ if e[0] == "O:":
+ hash["owner"] = e[1]
+ if e[0] == "G:":
+ hash["group"] = e[1]
+ if e[0] == "D:":
+ hash["dacl"] = e[1]
+ if e[0] == "S:":
+ hash["sacl"] = e[1]
+
+ return hash
+
+
+def get_diff_sddls(refsddl, cursddl):
+ """Get the difference between 2 sddl
+
+ This function split the textual representation of ACL into smaller
+ chunck in order to not to report a simple permutation as a difference
+
+ :param refsddl: First sddl to compare
+ :param cursddl: Second sddl to compare
+ :return: A string that explain difference between sddls
+ """
+
+ txt = ""
+ hash_new = chunck_sddl(cursddl)
+ hash_ref = chunck_sddl(refsddl)
+
+ if hash_new["owner"] != hash_ref["owner"]:
+ txt = "\tOwner mismatch: %s (in ref) %s" \
+ "(in current)\n" % (hash_ref["owner"], hash_new["owner"])
+
+ if hash_new["group"] != hash_ref["group"]:
+ txt = "%s\tGroup mismatch: %s (in ref) %s" \
+ "(in current)\n" % (txt, hash_ref["group"], hash_new["group"])
+
+ for part in ["dacl", "sacl"]:
+ if hash_new.has_key(part) and hash_ref.has_key(part):
+
+ # both are present, check if they contain the same ACE
+ h_new = set()
+ h_ref = set()
+ c_new = chunck_acl(hash_new[part])
+ c_ref = chunck_acl(hash_ref[part])
+
+ for elem in c_new["aces"]:
+ h_new.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)
+ h_ref.remove(k)
+
+ if len(h_new) + 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:
+ txt = "%s\t\t%s ACE is not present in the" \
+ " reference\n" % (txt, item)
+
+ for item in h_ref:
+ 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):
+ 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):
+ txt = "%s\tCurrent ACL hasn't a %s part\n" % (txt, part)
+
+ return txt
+
+
+def update_secrets(newsecrets_ldb, secrets_ldb, messagefunc):
+ """Update secrets.ldb
+
+ :param newsecrets_ldb: An LDB object that is connected to the secrets.ldb
+ of the reference provision
+ :param secrets_ldb: An LDB object that is connected to the secrets.ldb
+ 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)
+ assert reference, "Reference modules list can not be empty"
+ if len(current) == 0:
+ # No modules present
+ delta = secrets_ldb.msg_diff(ldb.Message(), reference[0])
+ delta.dn = reference[0].dn
+ secrets_ldb.add(reference[0])
+ else:
+ delta = secrets_ldb.msg_diff(current[0], reference[0])
+ delta.dn = current[0].dn
+ secrets_ldb.modify(delta)
+
+ reference = newsecrets_ldb.search(expression="objectClass=top", base="",
+ scope=SCOPE_SUBTREE, attrs=["dn"])
+ current = secrets_ldb.search(expression="objectClass=top", base="",
+ scope=SCOPE_SUBTREE, attrs=["dn"])
+ hash_new = {}
+ hash = {}
+ listMissing = []
+ listPresent = []
+
+ empty = ldb.Message()
+ for i in range(0, len(reference)):
+ hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
+
+ # Create a hash for speeding the search of existing object in the
+ # current provision
+ for i in range(0, len(current)):
+ hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
+
+ for k in hash_new.keys():
+ if not hash.has_key(k):
+ listMissing.append(hash_new[k])
+ else:
+ listPresent.append(hash_new[k])
+
+ for entry in listMissing:
+ reference = newsecrets_ldb.search(expression="dn=%s" % entry,
+ base="", scope=SCOPE_SUBTREE)
+ current = secrets_ldb.search(expression="dn=%s" % entry,
+ base="", scope=SCOPE_SUBTREE)
+ delta = secrets_ldb.msg_diff(empty, reference[0])
+ for att in hashAttrNotCopied:
+ delta.remove(att)
+ messagefunc(CHANGE, "Entry %s is missing from secrets.ldb" %
+ reference[0].dn)
+ for att in delta:
+ messagefunc(CHANGE, " Adding attribute %s" % att)
+ delta.dn = reference[0].dn
+ secrets_ldb.add(delta)
+
+ for entry in listPresent:
+ reference = newsecrets_ldb.search(expression="dn=%s" % entry,
+ base="", scope=SCOPE_SUBTREE)
+ current = secrets_ldb.search(expression="dn=%s" % entry, base="",
+ scope=SCOPE_SUBTREE)
+ delta = secrets_ldb.msg_diff(current[0], reference[0])
+ for att in hashAttrNotCopied:
+ delta.remove(att)
+ for att in delta:
+ if att == "name":
+ messagefunc(CHANGE, "Found attribute name on %s,"
+ " must rename the DN" % (current[0].dn))
+ identic_rename(secrets_ldb, reference[0].dn)
+ else:
+ delta.remove(att)
+
+ for entry in listPresent:
+ reference = newsecrets_ldb.search(expression="dn=%s" % entry, base="",
+ scope=SCOPE_SUBTREE)
+ current = secrets_ldb.search(expression="dn=%s" % entry, base="",
+ scope=SCOPE_SUBTREE)
+ delta = secrets_ldb.msg_diff(current[0], reference[0])
+ for att in hashAttrNotCopied:
+ delta.remove(att)
+ for att in delta:
+ if att == "msDS-KeyVersionNumber":
+ delta.remove(att)
+ if att != "dn":
+ messagefunc(CHANGE,
+ "Adding/Changing attribute %s to %s" %
+ (att, current[0].dn))
+
+ delta.dn = current[0].dn
+ secrets_ldb.modify(delta)
+
+ res2 = secrets_ldb.search(expression="(samaccountname=dns)",
+ scope=SCOPE_SUBTREE, attrs=["dn"])
+
+ if (len(res2) == 1):
+ messagefunc(SIMPLE, "Remove old dns account")
+ secrets_ldb.delete(res2[0]["dn"])
+
+
+def getOEMInfo(samdb, rootdn):
+ """Return OEM Information on the top level Samba4 use to store version
+ info in this field
+
+ :param samdb: An LDB object connect to sam.ldb
+ :param rootdn: Root DN of the domain
+ :return: The content of the field oEMInformation (if any)
+ """
+ res = samdb.search(expression="(objectClass=*)", base=str(rootdn),
+ scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
+ if len(res) > 0:
+ info = res[0]["oEMInformation"]
+ return info
+ else:
+ return ""
+
+
+def updateOEMInfo(samdb, rootdn):
+ """Update the OEMinfo field to add information about upgrade
+
+ :param samdb: an LDB object connected to the sam DB
+ :param rootdn: The string representation of the root DN of
+ the provision (ie. DC=...,DC=...)
+ """
+ res = samdb.search(expression="(objectClass=*)", base=rootdn,
+ scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
+ if len(res) > 0:
+ info = res[0]["oEMInformation"]
+ info = "%s, upgrade to %s" % (info, version)
+ delta = ldb.Message()
+ delta.dn = ldb.Dn(samdb, str(res[0]["dn"]))
+ delta["oEMInformation"] = ldb.MessageElement(info, ldb.FLAG_MOD_REPLACE,
+ "oEMInformation" )
+ samdb.modify(delta)
+
+def update_gpo(paths, samdb, names, lp, message, force=0):
+ """Create missing GPO file object if needed
+
+ Set ACL correctly also.
+ Check ACLs for sysvol/netlogon dirs also
+ """
+ resetacls = False
+ try:
+ ntacls.checkset_backend(lp, None, None)
+ eadbname = lp.get("posix:eadb")
+ if eadbname is not None and eadbname != "":
+ try:
+ attribute = samba.xattr_tdb.wrap_getxattr(eadbname,
+ paths.sysvol, xattr.XATTR_NTACL_NAME)
+ except Exception:
+ attribute = samba.xattr_native.wrap_getxattr(paths.sysvol,
+ xattr.XATTR_NTACL_NAME)
+ else:
+ attribute = samba.xattr_native.wrap_getxattr(paths.sysvol,
+ xattr.XATTR_NTACL_NAME)
+ except Exception:
+ resetacls = True
+
+ if force:
+ resetacls = True
+
+ dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid)
+ if not os.path.isdir(dir):
+ create_gpo_struct(dir)
+
+ if names.policyid_dc is None:
+ raise ProvisioningError("Policy ID for Domain controller is missing")
+ dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid_dc)
+ if not os.path.isdir(dir):
+ create_gpo_struct(dir)
+ # 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")
+
+ if resetacls:
+ try:
+ setsysvolacl(samdb, paths.netlogon, paths.sysvol, names.wheel_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")
+
+def increment_calculated_keyversion_number(samdb, rootdn, hashDns):
+ """For a given hash associating dn and a number, this function will
+ update the replPropertyMetaData of each dn in the hash, so that the
+ calculated value of the msDs-KeyVersionNumber is equal or superior to the
+ one associated to the given dn.
+
+ :param samdb: An SamDB object pointing to the sam
+ :param rootdn: The base DN where we want to start
+ :param hashDns: A hash with dn as key and number representing the
+ minimum value of msDs-KeyVersionNumber that we want to
+ have
+ """
+ entry = samdb.search(expression='(objectClass=user)',
+ base=ldb.Dn(samdb,str(rootdn)),
+ scope=SCOPE_SUBTREE, attrs=["msDs-KeyVersionNumber"],
+ controls=["search_options:1:2"])
+ done = 0
+ hashDone = {}
+ if len(entry) == 0:
+ raise ProvisioningError("Unable to find msDs-KeyVersionNumber")
+ else:
+ for e in entry:
+ if hashDns.has_key(str(e.dn).lower()):
+ val = e.get("msDs-KeyVersionNumber")
+ if not val:
+ val = "0"
+ version = int(str(hashDns[str(e.dn).lower()]))
+ if int(str(val)) < version:
+ done = done + 1
+ samdb.set_attribute_replmetadata_version(str(e.dn),
+ "unicodePwd",
+ version, True)
+def delta_update_basesamdb(refsampath, sampath, creds, session, lp, message):
+ """Update the provision container db: sam.ldb
+ This function is aimed for alpha9 and newer;
+
+ :param refsampath: Path to the samdb in the reference provision
+ :param sampath: Path to the samdb in the upgraded provision
+ :param creds: Credential used for openning LDB files
+ :param session: Session to use for openning LDB files
+ :param lp: A loadparam object
+ :return: A msg_diff object with the difference between the @ATTRIBUTES
+ of the current provision and the reference provision
+ """
+
+ message(SIMPLE,
+ "Update base samdb by searching difference with reference one")
+ refsam = Ldb(refsampath, session_info=session, credentials=creds,
+ lp=lp, options=["modules:"])
+ sam = Ldb(sampath, session_info=session, credentials=creds, lp=lp,
+ options=["modules:"])
+
+ empty = ldb.Message()
+ deltaattr = None
+ reference = refsam.search(expression="")
+
+ for refentry in reference:
+ entry = sam.search(expression="dn=%s" % refentry["dn"],
+ scope=SCOPE_SUBTREE)
+ if not len(entry):
+ delta = sam.msg_diff(empty, refentry)
+ message(CHANGE, "Adding %s to sam db" % str(refentry.dn))
+ if str(refentry.dn) == "@PROVISION" and\
+ delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
+ delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
+ delta.dn = refentry.dn
+ sam.add(delta)
+ else:
+ delta = sam.msg_diff(entry[0], refentry)
+ if str(refentry.dn) == "@ATTRIBUTES":
+ deltaattr = sam.msg_diff(refentry, entry[0])
+ if str(refentry.dn) == "@PROVISION" and\
+ delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
+ delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
+ if len(delta.items()) > 1:
+ delta.dn = refentry.dn
+ sam.modify(delta)
+
+ return deltaattr
+
+
+def construct_existor_expr(attrs):
+ """Construct a exists or LDAP search expression.
+
+ :param attrs: List of attribute on which we want to create the search
+ expression.
+ :return: A string representing the expression, if attrs is empty an
+ empty string is returned
+ """
+ expr = ""
+ if len(attrs) > 0:
+ expr = "(|"
+ for att in attrs:
+ expr = "%s(%s=*)"%(expr,att)
+ expr = "%s)"%expr
+ return expr
+
+def update_machine_account_password(samdb, secrets_ldb, names):
+ """Update (change) the password of the current DC both in the SAM db and in
+ secret one
+
+ :param samdb: An LDB object related to the sam.ldb file of a given provision
+ :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
+ provision
+ :param names: List of key provision parameters"""
+
+ expression = "samAccountName=%s$" % names.netbiosname
+ secrets_msg = secrets_ldb.search(expression=expression,
+ attrs=["secureChannelType"])
+ if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
+ res = samdb.search(expression=expression, attrs=[])
+ assert(len(res) == 1)
+
+ msg = ldb.Message(res[0].dn)
+ machinepass = samba.generate_random_password(128, 255)
+ mputf16 = machinepass.encode('utf-16-le')
+ msg["clearTextPassword"] = ldb.MessageElement(mputf16,
+ ldb.FLAG_MOD_REPLACE,
+ "clearTextPassword")
+ samdb.modify(msg)
+
+ res = samdb.search(expression=("samAccountName=%s$" % names.netbiosname),
+ attrs=["msDs-keyVersionNumber"])
+ assert(len(res) == 1)
+ kvno = int(str(res[0]["msDs-keyVersionNumber"]))
+ secChanType = int(secrets_msg[0]["secureChannelType"][0])
+
+ secretsdb_self_join(secrets_ldb, domain=names.domain,
+ realm=names.realm,
+ domainsid=names.domainsid,
+ dnsdomain=names.dnsdomain,
+ netbiosname=names.netbiosname,
+ machinepass=machinepass,
+ key_version_number=kvno,
+ secure_channel_type=secChanType)
+ else:
+ raise ProvisioningError("Unable to find a Secure Channel"
+ "of type SEC_CHAN_BDC")
+
+def update_dns_account_password(samdb, secrets_ldb, names):
+ """Update (change) the password of the dns both in the SAM db and in
+ secret one
+
+ :param samdb: An LDB object related to the sam.ldb file of a given provision
+ :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
+ provision
+ :param names: List of key provision parameters"""
+
+ expression = "samAccountName=dns-%s" % names.netbiosname
+ secrets_msg = secrets_ldb.search(expression=expression)
+ if len(secrets_msg) == 1:
+ res = samdb.search(expression=expression, attrs=[])
+ assert(len(res) == 1)
+
+ msg = ldb.Message(res[0].dn)
+ machinepass = samba.generate_random_password(128, 255)
+ mputf16 = machinepass.encode('utf-16-le')
+ msg["clearTextPassword"] = ldb.MessageElement(mputf16,
+ ldb.FLAG_MOD_REPLACE,
+ "clearTextPassword")
+
+ samdb.modify(msg)
+
+ res = samdb.search(expression=expression,
+ attrs=["msDs-keyVersionNumber"])
+ assert(len(res) == 1)
+ kvno = str(res[0]["msDs-keyVersionNumber"])
+
+ msg = ldb.Message(secrets_msg[0].dn)
+ msg["secret"] = ldb.MessageElement(machinepass,
+ ldb.FLAG_MOD_REPLACE,
+ "secret")
+ msg["msDS-KeyVersionNumber"] = ldb.MessageElement(kvno,
+ ldb.FLAG_MOD_REPLACE,
+ "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
+ still stored in the db.
+
+ :param samdb: An LDB object pointing to the sam
+ :param rootdn: The base DN where the search should start
+ :param attrs: A list of attributes to be searched
+ :return: A hash with attributes as key and an array of
+ array. Each array contains the dn and the associated
+ values for this attribute as they are stored in the
+ sam."""
+
+ hashAtt = {}
+ expr = construct_existor_expr(attrs)
+ if expr == "":
+ return hashAtt
+ entry = samdb.search(expression=expr, base=ldb.Dn(samdb, str(rootdn)),
+ scope=SCOPE_SUBTREE, attrs=attrs,
+ controls=["search_options:1:2","bypassoperational:0"])
+ if len(entry) == 0:
+ # Nothing anymore
+ return hashAtt
+
+ for ent in entry:
+ for att in attrs:
+ if ent.get(att):
+ if hashAtt.has_key(att):
+ hashAtt[att][str(ent.dn).lower()] = str(ent[att])
+ else:
+ hashAtt[att] = {}
+ hashAtt[att][str(ent.dn).lower()] = str(ent[att])
+
+ return hashAtt
+
+def int64range2str(value):
+ """Display the int64 range stored in value as xxx-yyy
+
+ :param value: The int64 range
+ :return: A string of the representation of the range
+ """
+
+ lvalue = long(value)
+ str = "%d-%d" % (lvalue&0xFFFFFFFF, lvalue>>32)
+ return str
diff --git a/source4/scripting/python/samba/web_server/__init__.py b/source4/scripting/python/samba/web_server/__init__.py
new file mode 100644
index 0000000000..da528f42d1
--- /dev/null
+++ b/source4/scripting/python/samba/web_server/__init__.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Unix SMB/CIFS implementation.
+# Copyright © Jelmer Vernooij <jelmer@samba.org> 2008
+#
+# Implementation of SWAT that uses WSGI
+#
+# 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/>.
+#
+
+
+
+def render_placeholder(environ, start_response):
+ status = '200 OK'
+ response_headers = [('Content-type','text/html')]
+ start_response(status, response_headers)
+
+ yield "<!doctype html>\n"
+ yield "<html>\n"
+ yield " <title>The Samba web service</title>\n"
+ yield "</html>\n"
+
+ yield "<body>\n"
+ yield "<p>Welcome to this Samba web server.</p>\n"
+ yield "<p>This page is a simple placeholder. You probably want to install "
+ yield "SWAT. More information can be found "
+ yield "<a href='http://wiki.samba.org/index.php/SWAT'>on the wiki</a>.</p>"
+ yield "</p>\n"
+ yield "</body>\n"
+ yield "</html>\n"
+
+
+__call__ = render_placeholder
+
+
+if __name__ == '__main__':
+ from wsgiref import simple_server
+ httpd = simple_server.make_server('localhost', 8090, __call__)
+ print "Serving HTTP on port 8090..."
+ httpd.serve_forever()
diff --git a/source4/scripting/python/samba_external/README b/source4/scripting/python/samba_external/README
new file mode 100644
index 0000000000..d6a4dec7b1
--- /dev/null
+++ b/source4/scripting/python/samba_external/README
@@ -0,0 +1,4 @@
+This directory is for external python libraries that may not be
+installed on the local system. We always should try to use the
+system version of the library if possible, then use the Samba
+supplied copy if the system copy is unavailable
diff --git a/source4/scripting/python/uuidmodule.c b/source4/scripting/python/uuidmodule.c
index 98ef9adaa9..3bfe0162ca 100644
--- a/source4/scripting/python/uuidmodule.c
+++ b/source4/scripting/python/uuidmodule.c
@@ -17,8 +17,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#include "includes.h"
#include <Python.h>
+#include "includes.h"
#include "librpc/ndr/libndr.h"
static PyObject *uuid_random(PyObject *self, PyObject *args)
diff --git a/source4/scripting/python/wscript_build b/source4/scripting/python/wscript_build
new file mode 100644
index 0000000000..540f3b7bb7
--- /dev/null
+++ b/source4/scripting/python/wscript_build
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+
+bld.SAMBA_LIBRARY('samba_python',
+ source=[],
+ deps='LIBPYTHON pytalloc-util pyrpc_util',
+ grouping_library=True,
+ private_library=True,
+ pyext=True)
+
+bld.SAMBA_SUBSYSTEM('LIBPYTHON',
+ source='modules.c',
+ public_deps='',
+ init_function_sentinal='{NULL,NULL}',
+ deps='talloc',
+ pyext=True,
+ )
+
+
+bld.SAMBA_PYTHON('python_uuid',
+ source='uuidmodule.c',
+ deps='ndr',
+ realname='uuid.so',
+ enabled = float(bld.env.PYTHON_VERSION) <= 2.4
+ )
+
+
+bld.SAMBA_PYTHON('python_glue',
+ source='pyglue.c',
+ deps='pyparam_util samba-util netif pytalloc-util',
+ realname='samba/_glue.so'
+ )
+
+
+# install out various python scripts for use by make test
+bld.SAMBA_SCRIPT('samba_python',
+ pattern='samba/**/*.py',
+ installdir='python')
+
+bld.INSTALL_WILDCARD('${PYTHONARCHDIR}', 'samba/**/*.py', flat=False)