diff options
author | bubulle <bubulle@alioth.debian.org> | 2009-07-06 18:17:09 +0000 |
---|---|---|
committer | bubulle <bubulle@alioth.debian.org> | 2009-07-06 18:17:09 +0000 |
commit | 1e4514a1edfdd1dde65a7567a7d7328de40e3493 (patch) | |
tree | 889204356442a0e13d8b3b2deaa1e0b310cea940 /source4/scripting/python | |
parent | 956e238a6ed95a84c85c14ac898ffee166c35287 (diff) | |
download | samba-1e4514a1edfdd1dde65a7567a7d7328de40e3493.tar.gz |
merge upstream 3.4.0
git-svn-id: svn://svn.debian.org/svn/pkg-samba/trunk/samba@2936 fc4039ab-9d04-0410-8cac-899223bdd6b0
Diffstat (limited to 'source4/scripting/python')
34 files changed, 6170 insertions, 0 deletions
diff --git a/source4/scripting/python/STATUS b/source4/scripting/python/STATUS new file mode 100644 index 0000000000..ee67b8bc7a --- /dev/null +++ b/source4/scripting/python/STATUS @@ -0,0 +1,14 @@ +dsdb/samdb/ldb_modules/tests/samba3sam.py: Fix remaining failing tests +lib/ldb/tests/python/ldap.py: Fix remaining 3 FIXME's +command-line vampire +provisioning: combine some of the python dictionaries +finish scripting/bin/smbstatus.py + +not important before making Python the default: +- hierarchy (rename samr -> dcerpc.samr, misc -> samba.misc, etc) +- scripting/python/samba/upgrade.py +- install python modules into system +- SWAT +- __ndr_pack__/__ndr_unpack__ members for the NDR struct bindings +- generate docstrings in DCE/RPC bindings +- eliminate some variables from the python interface because they can be induced diff --git a/source4/scripting/python/config.mk b/source4/scripting/python/config.mk new file mode 100644 index 0000000000..ba624ee163 --- /dev/null +++ b/source4/scripting/python/config.mk @@ -0,0 +1,35 @@ +[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 + +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)/%,%,$(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 new file mode 100644 index 0000000000..3671076a59 --- /dev/null +++ b/source4/scripting/python/examples/netbios.py @@ -0,0 +1,28 @@ +#!/usr/bin/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/>. +# + +from samba.netbios import Node + +n = Node() +(reply_from, names, addresses) = n.query_name("GANIEDA", "192.168.4.0", + timeout=4) + +print "Received reply from %s:" % (reply_from, ) +print "Names: %r" % (names, ) +print "Addresses: %r" % (addresses, ) diff --git a/source4/scripting/python/examples/samr.py b/source4/scripting/python/examples/samr.py new file mode 100755 index 0000000000..b3ea117b40 --- /dev/null +++ b/source4/scripting/python/examples/samr.py @@ -0,0 +1,117 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Unix SMB/CIFS implementation. +# Copyright © Jelmer Vernooij <jelmer@samba.org> 2008 +# +# Based on samr.js © Andrew Tridgell <tridge@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 sys + +sys.path.insert(0, "bin/python") + +from samba.dcerpc import samr, security, lsa + +def display_lsa_string(str): + return str.string + +def FillUserInfo(samr, dom_handle, users, level): + """fill a user array with user information from samrQueryUserInfo""" + for i in range(len(users)): + user_handle = samr.OpenUser(handle, security.SEC_FLAG_MAXIMUM_ALLOWED, users[i].idx) + info = samr.QueryUserInfo(user_handle, level) + info.name = users[i].name + info.idx = users[i].idx + users[i] = info + samr.Close(user_handle) + +def toArray((handle, array, num_entries)): + ret = [] + for x in range(num_entries): + ret.append((array.entries[x].idx, array.entries[x].name)) + return ret + + +def test_Connect(samr): + """test the samr_Connect interface""" + print "Testing samr_Connect" + return samr.Connect2(None, security.SEC_FLAG_MAXIMUM_ALLOWED) + +def test_LookupDomain(samr, handle, domain): + """test the samr_LookupDomain interface""" + print "Testing samr_LookupDomain" + return samr.LookupDomain(handle, domain) + +def test_OpenDomain(samr, handle, sid): + """test the samr_OpenDomain interface""" + print "Testing samr_OpenDomain" + return samr.OpenDomain(handle, security.SEC_FLAG_MAXIMUM_ALLOWED, sid) + +def test_EnumDomainUsers(samr, dom_handle): + """test the samr_EnumDomainUsers interface""" + print "Testing samr_EnumDomainUsers" + users = toArray(samr.EnumDomainUsers(dom_handle, 0, 0, -1)) + print "Found %d users" % len(users) + for idx, user in users: + print "\t%s\t(%d)" % (user, idx) + +def test_EnumDomainGroups(samr, dom_handle): + """test the samr_EnumDomainGroups interface""" + print "Testing samr_EnumDomainGroups" + groups = toArray(samr.EnumDomainGroups(dom_handle, 0, 0)) + print "Found %d groups" % len(groups) + for idx, group in groups: + print "\t%s\t(%d)" % (group, idx) + +def test_domain_ops(samr, dom_handle): + """test domain specific ops""" + test_EnumDomainUsers(samr, dom_handle) + test_EnumDomainGroups(samr, dom_handle) + +def test_EnumDomains(samr, handle): + """test the samr_EnumDomains interface""" + print "Testing samr_EnumDomains" + + domains = toArray(samr.EnumDomains(handle, 0, -1)) + print "Found %d domains" % len(domains) + for idx, domain in domains: + print "\t%s (%d)" % (display_lsa_string(domain), idx) + for idx, domain in domains: + print "Testing domain %s" % display_lsa_string(domain) + sid = samr.LookupDomain(handle, domain) + dom_handle = test_OpenDomain(samr, handle, sid) + test_domain_ops(samr, dom_handle) + samr.Close(dom_handle) + +if len(sys.argv) != 2: + print "Usage: samr.js <BINDING>" + sys.exit(1) + +binding = sys.argv[1] + +print "Connecting to %s" % binding +try: + samr = samr.samr(binding) +except Exception, e: + print "Failed to connect to %s: %s" % (binding, e.message) + sys.exit(1) + +handle = test_Connect(samr) +test_EnumDomains(samr, handle) +samr.Close(handle) + +print "All OK" diff --git a/source4/scripting/python/examples/winreg.py b/source4/scripting/python/examples/winreg.py new file mode 100755 index 0000000000..80b48ecfd7 --- /dev/null +++ b/source4/scripting/python/examples/winreg.py @@ -0,0 +1,87 @@ +#!/usr/bin/python +# +# tool to manipulate a remote registry +# Copyright Andrew Tridgell 2005 +# Copyright Jelmer Vernooij 2007 +# Released under the GNU GPL v3 or later +# + +import sys + +# Find right directory when running from source tree +sys.path.insert(0, "bin/python") + +from samba.dcerpc import winreg +import optparse +import samba.getopt as options + +parser = optparse.OptionParser("%s <BINDING> [path]" % sys.argv[0]) +sambaopts = options.SambaOptions(parser) +parser.add_option_group(sambaopts) +parser.add_option("--createkey", type="string", metavar="KEYNAME", + help="create a key") + +opts, args = parser.parse_args() + +if len(args) < 1: + parser.print_usage() + sys.exit(-1) + +binding = args[0] + +print "Connecting to " + binding +conn = winreg.winreg(binding, sambaopts.get_loadparm()) + +def list_values(key): + (num_values, max_valnamelen, max_valbufsize) = conn.QueryInfoKey(key, winreg.String())[4:8] + for i in range(num_values): + name = winreg.StringBuf() + name.size = max_valnamelen + (name, type, data, _, data_len) = conn.EnumValue(key, i, name, 0, "", max_valbufsize, 0) + print "\ttype=%-30s size=%4d '%s'" % type, len, name + if type in (winreg.REG_SZ, winreg.REG_EXPAND_SZ): + print "\t\t'%s'" % data +# if (v.type == reg.REG_MULTI_SZ) { +# for (j in v.value) { +# printf("\t\t'%s'\n", v.value[j]) +# } +# } +# if (v.type == reg.REG_DWORD || v.type == reg.REG_DWORD_BIG_ENDIAN) { +# printf("\t\t0x%08x (%d)\n", v.value, v.value) +# } +# if (v.type == reg.REG_QWORD) { +# printf("\t\t0x%llx (%lld)\n", v.value, v.value) +# } + +def list_path(key, path): + count = 0 + (num_subkeys, max_subkeylen, max_subkeysize) = conn.QueryInfoKey(key, winreg.String())[1:4] + for i in range(num_subkeys): + name = winreg.StringBuf() + name.size = max_subkeysize + keyclass = winreg.StringBuf() + keyclass.size = max_subkeysize + (name, _, _) = conn.EnumKey(key, i, name, keyclass=keyclass, last_changed_time=None)[0] + subkey = conn.OpenKey(key, name, 0, winreg.KEY_QUERY_VALUE | winreg.KEY_ENUMERATE_SUB_KEYS) + count += list_path(subkey, "%s\\%s" % (path, name)) + list_values(subkey) + return count + +if len(args) > 1: + root = args[1] +else: + root = "HKLM" + +if opts.createkey: + reg.create_key("HKLM\\SOFTWARE", opt.createkey) +else: + print "Listing registry tree '%s'" % root + try: + root_key = getattr(conn, "Open%s" % root)(None, winreg.KEY_QUERY_VALUE | winreg.KEY_ENUMERATE_SUB_KEYS) + except AttributeError: + print "Unknown root key name %s" % root + sys.exit(1) + count = list_path(root_key, root) + if count == 0: + print "No entries found" + sys.exit(1) diff --git a/source4/scripting/python/modules.c b/source4/scripting/python/modules.c new file mode 100644 index 0000000000..e53f4cfaf2 --- /dev/null +++ b/source4/scripting/python/modules.c @@ -0,0 +1,70 @@ +/* + Unix SMB/CIFS implementation. + Samba utility functions + 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/>. +*/ + +#include "includes.h" +#include "scripting/python/modules.h" +#include <Python.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 struct _inittab py_modules[] = { STATIC_LIBPYTHON_MODULES }; + +void py_load_samba_modules(void) +{ + int i; + for (i = 0; i < ARRAY_SIZE(py_modules); i++) { + PyImport_ExtendInittab(&py_modules[i]); + } +} + +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); +} diff --git a/source4/scripting/python/modules.h b/source4/scripting/python/modules.h new file mode 100644 index 0000000000..6b242ee257 --- /dev/null +++ b/source4/scripting/python/modules.h @@ -0,0 +1,28 @@ +/* + Unix SMB/CIFS implementation. + Samba utility functions + 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/>. +*/ + +#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) + +#endif /* __SAMBA_PYTHON_MODULES_H__ */ diff --git a/source4/scripting/python/pyglue.c b/source4/scripting/python/pyglue.c new file mode 100644 index 0000000000..a2c4790611 --- /dev/null +++ b/source4/scripting/python/pyglue.c @@ -0,0 +1,274 @@ +/* + 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/>. +*/ + +#include "includes.h" +#include "ldb.h" +#include "ldb_errors.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" + +#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 .. */ +extern struct cli_credentials *cli_credentials_from_py_object(PyObject *py_obj); + +#define PyErr_LDB_OR_RAISE(py_ldb, ldb) \ + if (!PyLdb_Check(py_ldb)) { \ + /*PyErr_SetString(PyExc_TypeError, "Ldb connection object required"); \ + return NULL; \ */ \ + } \ + ldb = PyLdb_AsLdbContext(py_ldb); + + +static PyObject *py_generate_random_str(PyObject *self, PyObject *args) +{ + int len; + PyObject *ret; + char *retstr; + if (!PyArg_ParseTuple(args, "i", &len)) + return NULL; + + retstr = generate_random_str(NULL, len); + ret = PyString_FromString(retstr); + talloc_free(retstr); + 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_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) +{ + 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_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_ldb_register_samba_handlers(PyObject *self, PyObject *args) +{ + PyObject *py_ldb; + struct ldb_context *ldb; + int ret; + + if (!PyArg_ParseTuple(args, "O", &py_ldb)) + return NULL; + + PyErr_LDB_OR_RAISE(py_ldb, ldb); + ret = ldb_register_samba_handlers(ldb); + + PyErr_LDB_ERROR_IS_ERR_RAISE(ret, ldb); + Py_RETURN_NONE; +} + +static PyObject *py_dsdb_set_ntds_invocation_id(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)) + return NULL; + + PyErr_LDB_OR_RAISE(py_ldb, ldb); + GUID_from_string(PyString_AsString(py_guid), &guid); + + ret = samdb_set_ntds_invocation_id(ldb, &guid); + if (!ret) { + PyErr_SetString(PyExc_RuntimeError, "set_ntds_invocation_id failed"); + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject *py_dsdb_set_global_schema(PyObject *self, PyObject *args) +{ + PyObject *py_ldb; + struct ldb_context *ldb; + int ret; + if (!PyArg_ParseTuple(args, "O", &py_ldb)) + return NULL; + + PyErr_LDB_OR_RAISE(py_ldb, ldb); + + ret = dsdb_set_global_schema(ldb); + PyErr_LDB_ERROR_IS_ERR_RAISE(ret, ldb); + + Py_RETURN_NONE; +} + +static PyObject *py_dsdb_attach_schema_from_ldif_file(PyObject *self, PyObject *args) +{ + 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_attach_schema_from_ldif_file(ldb, pf, df); + PyErr_WERROR_IS_ERR_RAISE(result); + + Py_RETURN_NONE; +} + +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." }, + { "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." }, + { "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." }, + { "dsdb_set_ntds_invocation_id", (PyCFunction)py_dsdb_set_ntds_invocation_id, METH_VARARGS, + NULL }, + { "dsdb_set_global_schema", (PyCFunction)py_dsdb_set_global_schema, METH_VARARGS, + NULL }, + { "dsdb_attach_schema_from_ldif_file", (PyCFunction)py_dsdb_attach_schema_from_ldif_file, METH_VARARGS, + NULL }, + { NULL } +}; + +void initglue(void) +{ + PyObject *m; + + 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)); +} + diff --git a/source4/scripting/python/samba/__init__.py b/source4/scripting/python/samba/__init__.py new file mode 100644 index 0000000000..a49e6e1ead --- /dev/null +++ b/source4/scripting/python/samba/__init__.py @@ -0,0 +1,242 @@ +#!/usr/bin/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/>. +# + +"""Samba 4.""" + +__docformat__ = "restructuredText" + +import os + +def _in_source_tree(): + """Check whether the script is being run from the source dir. """ + return os.path.exists("%s/../../../samba4-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 credentials +import glue + +class Ldb(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 + functions see samdb.py. + """ + def __init__(self, url=None, session_info=None, credentials=None, + modules_dir=None, lp=None): + """Open a Samba Ldb file. + + :param url: Optional LDB URL to open + :param session_info: Optional session information + :param credentials: Optional credentials, defaults to anonymous. + :param modules_dir: Modules directory, if not the default. + :param lp: Loadparm object, 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 + can be passed through (required by some modules). + """ + super(Ldb, self).__init__() + + 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) + + if credentials is not None: + self.set_credentials(credentials) + + if session_info is not None: + self.set_session_info(session_info) + + glue.ldb_register_samba_handlers(self) + + if lp is not None: + self.set_loadparm(lp) + + def msg(l,text): + print text + #self.set_debug(msg) + + if url is not None: + self.connect(url) + + 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, + 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. + :param scope: Search scope (defaults to base). + :return: Value of attribute as a string or None if it wasn't found. + """ + res = self.search(basedn, scope, expression, [attribute]) + if len(res) != 1 or res[0][attribute] is None: + return None + values = set(res[0][attribute]) + assert len(values) == 1 + return self.schema_format_value(attribute, values.pop()) + + def erase(self): + """Erase this ldb, removing all records.""" + # delete the specials + for attr in ["@INDEXLIST", "@ATTRIBUTES", "@SUBCLASSES", "@MODULES", + "@OPTIONS", "@PARTITION", "@KLUDGEACL"]: + try: + self.delete(attr) + except ldb.LdbError, (LDB_ERR_NO_SUCH_OBJECT, _): + # Ignore missing dn errors + pass + + basedn = "" + # and the rest + for msg in self.search(basedn, ldb.SCOPE_SUBTREE, + "(&(|(objectclass=*)(distinguishedName=*))(!(distinguishedName=@BASEINFO)))", + ["distinguishedName"]): + 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)))", ["distinguishedName"]) + assert len(res) == 0 + + def erase_partitions(self): + """Erase an ldb, removing all records.""" + 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"]: + previous_remaining = 1 + current_remaining = 0 + + k = 0 + while ++k < 10 and (previous_remaining != current_remaining): + # and the rest + try: + res2 = self.search(basedn, ldb.SCOPE_SUBTREE, "(|(objectclass=*)(distinguishedName=*))", ["distinguishedName"]) + except ldb.LdbError, (LDB_ERR_NO_SUCH_OBJECT, _): + # Ignore missing dn errors + return + + previous_remaining = current_remaining + current_remaining = len(res2) + for msg in res2: + try: + self.delete(msg.dn) + # Ignore no such object errors + except ldb.LdbError, (LDB_ERR_NO_SUCH_OBJECT, _): + pass + # Ignore not allowed on non leaf errors + except ldb.LdbError, (LDB_ERR_NOT_ALLOWED_ON_NON_LEAF, _): + pass + + def load_ldif_file_add(self, ldif_path): + """Load a LDIF file. + + :param ldif_path: Path to LDIF file. + """ + self.add_ldif(open(ldif_path, 'r').read()) + + def add_ldif(self, ldif): + """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) + + def modify_ldif(self, ldif): + """Modify database based on a LDIF string. + + :param ldif: LDIF text. + """ + for changetype, msg in self.parse_ldif(ldif): + self.modify(msg) + + +def substitute_var(text, values): + """substitute strings of the form ${NAME} in str, replacing + with substitutions from subobj. + + :param text: Text in which to subsitute. + :param values: Dictionary with keys and values. + """ + + for (name, value) in values.items(): + assert isinstance(name, str), "%r is not a string" % name + assert isinstance(value, str), "Value %r for %s is not a string" % (value, name) + text = text.replace("${%s}" % name, value) + + return text + + +def check_all_substituted(text): + """Make sure 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]) + + +def valid_netbios_name(name): + """Check whether a name is valid as a NetBIOS name. """ + # FIXME: There are probably more constraints here. + # crh has a paragraph on this in his book (1.4.1.1) + if len(name) > 15: + return False + return True + +version = glue.version diff --git a/source4/scripting/python/samba/getopt.py b/source4/scripting/python/samba/getopt.py new file mode 100644 index 0000000000..c12245f6c5 --- /dev/null +++ b/source4/scripting/python/samba/getopt.py @@ -0,0 +1,116 @@ +#!/usr/bin/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, AUTO_USE_KERBEROS, DONT_USE_KERBEROS, MUST_USE_KERBEROS +from hostconfig import Hostconfig + +__docformat__ = "restructuredText" + +class SambaOptions(optparse.OptionGroup): + """General Samba-related command line options.""" + def __init__(self, parser): + optparse.OptionGroup.__init__(self, parser, "Samba Common Options") + self.add_option("-s", "--configfile", action="callback", + type=str, metavar="FILE", help="Configuration file", + callback=self._load_configfile) + self._configfile = None + + def get_loadparm_path(self): + """Return the path to the smb.conf file specified on the command line. """ + return self._configfile + + def _load_configfile(self, option, opt_str, arg, parser): + self._configfile = arg + + def 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) + elif os.getenv("SMB_CONF_PATH") is not None: + lp.load(os.getenv("SMB_CONF_PATH")) + else: + lp.load_default() + return lp + + def get_hostconfig(self): + return Hostconfig(self.get_loadparm()) + + +class VersionOptions(optparse.OptionGroup): + """Command line option for printing Samba version.""" + def __init__(self, parser): + optparse.OptionGroup.__init__(self, parser, "Version Options") + + +class CredentialsOptions(optparse.OptionGroup): + """Command line options for specifying credentials.""" + def __init__(self, parser): + self.no_pass = False + 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", + action="callback", type=str, + help="Username", callback=self._parse_username) + 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", + action="callback", type=str, + help="Use Kerberos", callback=self._set_kerberos) + self.creds = Credentials() + + def _parse_username(self, option, opt_str, arg, parser): + self.creds.parse_string(arg) + + def _parse_workgroup(self, option, opt_str, arg, parser): + self.creds.set_domain(arg) + + def _set_password(self, option, opt_str, arg, parser): + self.creds.set_password(arg) + + def _set_kerberos(self, option, opt_str, arg, parser): + if bool(arg) or arg.lower() == "yes": + self.creds.set_kerberos_state(MUST_USE_KERBEROS) + else: + self.creds.set_kerberos_state(DONT_USE_KERBEROS) + + def _set_simple_bind_dn(self, option, opt_str, arg, parser): + self.creds.set_bind_dn(arg) + + def get_credentials(self, lp): + """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: + self.creds.set_cmdline_callbacks() + return self.creds diff --git a/source4/scripting/python/samba/hostconfig.py b/source4/scripting/python/samba/hostconfig.py new file mode 100644 index 0000000000..313e3420b0 --- /dev/null +++ b/source4/scripting/python/samba/hostconfig.py @@ -0,0 +1,33 @@ +#!/usr/bin/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/>. +# + +from samdb import SamDB + +class Hostconfig(object): + """Aggregate object that contains all information about the configuration + of a Samba host.""" + + def __init__(self, lp): + self.lp = lp + + def get_samdb(self, session_info, credentials): + return SamDB(url=self.lp.get("sam database"), + session_info=session_info, credentials=credentials, + lp=self.lp) + diff --git a/source4/scripting/python/samba/idmap.py b/source4/scripting/python/samba/idmap.py new file mode 100644 index 0000000000..f8eeb18925 --- /dev/null +++ b/source4/scripting/python/samba/idmap.py @@ -0,0 +1,82 @@ +#!/usr/bin/python + +# Unix SMB/CIFS implementation. +# Copyright (C) 2008 Kai Blin <kai@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/>. +# + +"""Convenience functions for using the idmap database.""" + +__docformat__ = "restructuredText" + +import samba +import glue +import ldb + +class IDmapDB(samba.Ldb): + """The IDmap database.""" + + # Mappings for ID_TYPE_UID, ID_TYPE_GID and ID_TYPE_BOTH + TYPE_UID = 1 + TYPE_GID = 2 + TYPE_BOTH = 3 + + def __init__(self, url=None, session_info=None, credentials=None, + modules_dir=None, lp=None): + """Open the IDmap Database. + + :param url: URL of the database. + """ + self.lp = lp + + super(IDmapDB, self).__init__(session_info=session_info, credentials=credentials, + modules_dir=modules_dir, lp=lp) + if url: + self.connect(url) + else: + self.connect(lp.get("idmap database")) + + def connect(self, url): + super(IDmapDB, self).connect(self.lp.private_path(url)) + + def setup_name_mapping(self, sid, type, unixid): + """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. + """ + type_string = "" + if type == self.TYPE_UID: + type_string = "ID_TYPE_UID" + elif type == self.TYPE_GID: + type_string = "ID_TYPE_GID" + elif type == self.TYPE_BOTH: + type_string = "ID_TYPE_BOTH" + else: + return + + mod = """ +dn: CN=%s +xidNumber: %s +objectSid: %s +objectClass: sidMap +type: %s +cn: %s + +""" % (sid, unixid, sid, type_string, sid) + self.add(self.parse_ldif(mod).next()[1]) + + diff --git a/source4/scripting/python/samba/ndr.py b/source4/scripting/python/samba/ndr.py new file mode 100644 index 0000000000..e718ff3422 --- /dev/null +++ b/source4/scripting/python/samba/ndr.py @@ -0,0 +1,28 @@ +#!/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/>. +# + +def ndr_pack(object): + return object.__ndr_pack__() + + +def ndr_unpack(cls, data): + object = cls() + object.__ndr_unpack__(data) + return object diff --git a/source4/scripting/python/samba/provision.py b/source4/scripting/python/samba/provision.py new file mode 100644 index 0000000000..d96857661e --- /dev/null +++ b/source4/scripting/python/samba/provision.py @@ -0,0 +1,1682 @@ +# +# 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 +# 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 +from auth import system_session +from samba import Ldb, substitute_var, valid_netbios_name, check_all_substituted +from samba.samdb import SamDB +from samba.idmap import IDmapDB +from samba.dcerpc import security +import urllib +from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError, \ + timestring, CHANGETYPE_MODIFY, CHANGETYPE_NONE + +__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.") + + +DEFAULTSITE = "Default-First-Site-Name" + +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.olmmron = None + self.olmmrserveridsconf = None + self.olmmrsyncreplconf = None + self.olcdir = None + self.olslaptest = None + self.olcseedldif = 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 + + +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") + ldb = Ldb(lp.get("sam database"), session_info=session_info, + credentials=credentials, lp=lp) + if len(ldb.search("(cn=Administrator)")) != 1: + raise "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.keytab = "secrets.keytab" + 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.templates = os.path.join(paths.private_dir, "templates.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.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.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): + """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 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, 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.""" + # add some foreign sids if they are not present already + samdb.add_stock_foreign_sids() + + 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, + ldap_backend_type=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 + + try: + samdb = SamDB(samdb_path, session_info=session_info, + credentials=credentials, lp=lp) + # Wipes the database + samdb.erase() + except LdbError: + os.unlink(samdb_path) + samdb = SamDB(samdb_path, session_info=session_info, + credentials=credentials, lp=lp) + # Wipes the database + samdb.erase() + + + #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 = ["rootdse", + "paged_results", + "ranged_results", + "anr", + "server_sort", + "asq", + "extended_dn_store", + "extended_dn_in", + "rdn_name", + "objectclass", + "samldb", + "kludge_acl", + "password_hash", + "operational"] + tdb_modules_list = [ + "subtree_rename", + "subtree_delete", + "linked_attributes", + "extended_dn_out_ldb"] + modules_list2 = ["show_deleted", + "partition"] + + domaindn_ldb = "users.ldb" + if ldap_backend is not None: + domaindn_ldb = ldap_backend + configdn_ldb = "configuration.ldb" + if ldap_backend is not None: + configdn_ldb = ldap_backend + schemadn_ldb = "schema.ldb" + if ldap_backend is not None: + schema_ldb = ldap_backend + schemadn_ldb = ldap_backend + + if 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_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 ldap_backend is not None: + raise "LDAP Backend specified, but LDAP Backend Type not specified" + elif serverrole == "domain controller": + backend_modules = ["repl_meta_data"] + 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: + 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), + }) + + except: + samdb.transaction_cancel() + raise + + samdb.transaction_commit() + + samdb = SamDB(samdb_path, session_info=session_info, + credentials=credentials, lp=lp) + + samdb.transaction_start() + try: + message("Setting up sam.ldb attributes") + samdb.load_ldif_file_add(setup_path("provision_init.ldif")) + + message("Setting up sam.ldb rootDSE") + setup_samdb_rootdse(samdb, setup_path, names) + + if erase: + message("Erasing data from partitions") + samdb.erase_partitions() + + except: + samdb.transaction_cancel() + raise + + samdb.transaction_commit() + + return samdb + + +def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain, + netbiosname, domainsid, keytab_path, samdb_url, + dns_keytab_path, dnspass, machinepass): + """Add DC-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_dc.ldif"), { + "MACHINEPASS_B64": b64encode(machinepass), + "DOMAIN": domain, + "REALM": realm, + "DNSDOMAIN": dnsdomain, + "DOMAINSID": str(domainsid), + "SECRETS_KEYTAB": keytab_path, + "NETBIOSNAME": netbiosname, + "SAM_LDB": samdb_url, + "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.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_templatesdb(path, setup_path, session_info, credentials, lp): + """Setup the templates database. + + :param path: Path to the database. + :param setup_path: Function for obtaining the path to setup files. + :param session_info: Session info + :param credentials: Credentials + :param lp: Loadparm context + """ + templates_ldb = SamDB(path, session_info=session_info, + credentials=credentials, lp=lp) + # Wipes the database + try: + templates_ldb.erase() + # This should be 'except LdbError', but on a re-provision the assert in ldb.erase fires, and we need to catch that too + except: + os.unlink(path) + + templates_ldb.load_ldif_file_add(setup_path("provision_templates_init.ldif")) + + templates_ldb = SamDB(path, session_info=session_info, + credentials=credentials, lp=lp) + + templates_ldb.load_ldif_file_add(setup_path("provision_templates.ldif")) + + +def setup_registry(path, setup_path, session_info, credentials, 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, + credentials=credentials, 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, credentials, 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, + credentials=credentials, 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): + """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}) + setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), { + "POLICYGUID": policyguid, + "DNSDOMAIN": names.dnsdomain, + "DOMAINSID": str(domainsid), + "DOMAINDN": names.domaindn}) + + +def setup_samdb(path, setup_path, session_info, credentials, lp, + names, message, + domainsid, aci, domainguid, policyguid, + fill, adminpass, krbtgtpass, + machinepass, invocationid, dnspass, + serverrole, ldap_backend=None, + ldap_backend_type=None): + """Setup a complete SAM Database. + + :note: This will wipe the main SAM database file! + """ + + erase = (fill != FILL_DRS) + + # 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, + ldap_backend_type=ldap_backend_type, erase=erase) + + samdb = SamDB(path, session_info=session_info, + credentials=credentials, lp=lp) + if fill == FILL_DRS: + return samdb + + message("Pre-loading the Samba 4 and AD schema") + samdb.set_domain_sid(str(domainsid)) + if serverrole == "domain controller": + samdb.set_invocation_id(invocationid) + + load_schema(setup_path, samdb, names.schemadn, names.netbiosname, + names.configdn, names.sitename, names.serverdn, + names.hostname) + + samdb.transaction_start() + + try: + message("Adding DomainDN: %s (permitted to fail)" % names.domaindn) + if serverrole == "domain controller": + domain_oc = "domainDNS" + else: + domain_oc = "samba4LocalDomain" + + setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), { + "DOMAINDN": names.domaindn, + "ACI": aci, + "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"), { + "LDAPTIME": timestring(int(time.time())), + "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, + }) + + message("Adding configuration container (permitted to fail)") + setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), { + "CONFIGDN": names.configdn, + "ACI": aci, + }) + message("Modifying configuration container") + setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), { + "CONFIGDN": names.configdn, + "SCHEMADN": names.schemadn, + }) + + message("Adding schema container (permitted to fail)") + setup_add_ldif(samdb, setup_path("provision_schema_basedn.ldif"), { + "SCHEMADN": names.schemadn, + "ACI": aci, + }) + message("Modifying schema container") + + prefixmap = open(setup_path("prefixMap.txt"), 'r').read() + + setup_modify_ldif(samdb, + setup_path("provision_schema_basedn_modify.ldif"), { + "SCHEMADN": names.schemadn, + "NETBIOSNAME": names.netbiosname, + "DEFAULTSITE": names.sitename, + "CONFIGDN": names.configdn, + "SERVERDN": names.serverdn, + "PREFIXMAP_B64": b64encode(prefixmap) + }) + + message("Setting up sam.ldb Samba4 schema") + setup_add_ldif(samdb, setup_path("schema_samba4.ldif"), + {"SCHEMADN": names.schemadn }) + message("Setting up sam.ldb AD schema") + setup_add_ldif(samdb, setup_path("schema.ldif"), + {"SCHEMADN": names.schemadn}) + 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 + }) + + message("Setting up display specifiers") + setup_add_ldif(samdb, setup_path("display_specifiers.ldif"), + {"CONFIGDN": names.configdn}) + + message("Adding users container (permitted to fail)") + 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 (permitted to fail)") + 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"), { + "DOMAINDN": names.domaindn, + "NETBIOSNAME": names.netbiosname, + "DEFAULTSITE": names.sitename, + "CONFIGDN": names.configdn, + "SERVERDN": names.serverdn + }) + + 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, + setup_path=setup_path) + + 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, krbtgtpass=None, domainguid=None, + policyguid=None, invocationid=None, machinepass=None, + dnspass=None, root=None, nobody=None, nogroup=None, users=None, + wheel=None, backup=None, aci=None, serverrole=None, + ldap_backend=None, ldap_backend_type=None, sitename=None): + """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() + + if policyguid is None: + policyguid = str(uuid.uuid4()) + 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) + 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 aci is None: + aci = "# no aci for local ldb" + + 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="") + + if ldap_backend is not None: + if ldap_backend == "ldapi": + # provision-backend will set this path suggested slapd command line / fedorads.inf + ldap_backend = "ldapi://%s" % urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="") + + # 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=credentials, lp=lp) + + message("Setting up the registry") + setup_registry(paths.hklm, setup_path, session_info, + credentials=credentials, lp=lp) + + message("Setting up templates db") + setup_templatesdb(paths.templates, setup_path, session_info=session_info, + credentials=credentials, lp=lp) + + message("Setting up idmap db") + idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info, + credentials=credentials, lp=lp) + + samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info, + credentials=credentials, lp=lp, names=names, + message=message, + domainsid=domainsid, + aci=aci, domainguid=domainguid, policyguid=policyguid, + fill=samdb_fill, + adminpass=adminpass, krbtgtpass=krbtgtpass, + invocationid=invocationid, + machinepass=machinepass, dnspass=dnspass, + serverrole=serverrole, ldap_backend=ldap_backend, + ldap_backend_type=ldap_backend_type) + + if lp.get("server role") == "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) + + 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("") + os.makedirs(os.path.join(policy_path, "Machine"), 0755) + os.makedirs(os.path.join(policy_path, "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": + secrets_ldb = Ldb(paths.secrets, session_info=session_info, + credentials=credentials, lp=lp) + secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=names.realm, + netbiosname=names.netbiosname, domainsid=domainsid, + keytab_path=paths.keytab, samdb_url=paths.samdb, + dns_keytab_path=paths.dns_keytab, dnspass=dnspass, + machinepass=machinepass, dnsdomain=names.dnsdomain) + + samdb = SamDB(paths.samdb, session_info=session_info, + credentials=credentials, lp=lp) + + domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID") + assert isinstance(domainguid, str) + hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID", + expression="(&(objectClass=computer)(cn=%s))" % names.hostname, + scope=SCOPE_SUBTREE) + assert isinstance(hostguid, 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, hostguid=hostguid) + + 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) + + 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) + + 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, invocationid=None, machinepass=None, + dnspass=None, root=None, nobody=None, nogroup=None, users=None, + wheel=None, backup=None, aci=None, serverrole=None, + ldap_backend=None, ldap_backend_type=None, sitename=None): + + def message(text): + """print a message if quiet is not set.""" + print text + + 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}) + + + +def provision_backend(setup_dir=None, message=None, + smbconf=None, targetdir=None, realm=None, + rootdn=None, domaindn=None, schemadn=None, configdn=None, + domain=None, hostname=None, adminpass=None, root=None, serverrole=None, + ldap_backend_type=None, ldap_backend_port=None, + ol_mmr_urls=None,ol_olc=None,ol_slaptest=None): + + def setup_path(file): + return os.path.join(setup_dir, file) + + if hostname is None: + hostname = socket.gethostname().split(".")[0].lower() + + if root is None: + root = findnss(pwd.getpwnam, ["root"])[0] + + if adminpass is None: + adminpass = glue.generate_random_str(12) + + 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() + assert smbconf is not None + + # 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) + + # openldap-online-configuration: validation of olc and slaptest + if ol_olc == "yes" and ol_slaptest is None: + sys.exit("Warning: OpenLDAP-Online-Configuration cant be setup without path to slaptest-Binary!") + + if ol_olc == "yes" and ol_slaptest is not None: + ol_slaptest = ol_slaptest + "/slaptest" + if not os.path.exists(ol_slaptest): + message (ol_slaptest) + sys.exit("Warning: Given Path to slaptest-Binary does not exist!") + ### + + + + lp = param.LoadParm() + lp.load(smbconf) + + if serverrole is None: + serverrole = lp.get("server role") + + names = guess_names(lp=lp, hostname=hostname, domain=domain, + dnsdomain=realm, serverrole=serverrole, + rootdn=rootdn, domaindn=domaindn, configdn=configdn, + schemadn=schemadn) + + paths = provision_paths_from_lp(lp, names.dnsdomain) + + if not os.path.isdir(paths.ldapdir): + os.makedirs(paths.ldapdir, 0700) + schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb") + try: + os.unlink(schemadb_path) + except OSError: + pass + + schemadb = Ldb(schemadb_path, lp=lp) + + prefixmap = open(setup_path("prefixMap.txt"), 'r').read() + + setup_add_ldif(schemadb, setup_path("provision_schema_basedn.ldif"), + {"SCHEMADN": names.schemadn, + "ACI": "#", + }) + setup_modify_ldif(schemadb, + setup_path("provision_schema_basedn_modify.ldif"), \ + {"SCHEMADN": names.schemadn, + "NETBIOSNAME": names.netbiosname, + "DEFAULTSITE": DEFAULTSITE, + "CONFIGDN": names.configdn, + "SERVERDN": names.serverdn, + "PREFIXMAP_B64": b64encode(prefixmap) + }) + + setup_add_ldif(schemadb, setup_path("schema_samba4.ldif"), + {"SCHEMADN": names.schemadn }) + setup_add_ldif(schemadb, setup_path("schema.ldif"), + {"SCHEMADN": names.schemadn}) + + if ldap_backend_type == "fedora-ds": + if ldap_backend_port is not None: + serverport = "ServerPort=%d" % ldap_backend_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": adminpass, + "SERVERPORT": serverport}) + + setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions, + {"CONFIGDN": names.configdn, + "SCHEMADN": names.schemadn, + }) + + mapping = "schema-map-fedora-ds-1.0" + backend_schema = "99_ad.ldif" + + slapdcommand="Initialise Fedora DS with: setup-ds.pl --file=%s" % paths.fedoradsinf + + ldapuser = "--simple-bind-dn=" + names.ldapmanagerdn + + elif ldap_backend_type == "openldap": + attrs = ["linkID", "lDAPDisplayName"] + res = schemadb.search(expression="(&(linkID=*)(!(linkID:1.2.840.113556.1.4.803:=1))(objectclass=attributeSchema)(attributeSyntax=2.5.5.1))", base=names.schemadn, scope=SCOPE_SUBTREE, attrs=attrs) + + memberof_config = "# Generated from schema in %s\n" % schemadb_path + refint_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 = schemadb.searchone(basedn=names.schemadn, + expression=expression, + attribute="lDAPDisplayName", + scope=SCOPE_SUBTREE) + if target is not None: + refint_attributes = refint_attributes + " " + target + " " + res[i]["lDAPDisplayName"][0] + + memberof_config += read_and_sub_file(setup_path("memberof.conf"), + { "MEMBER_ATTR" : str(res[i]["lDAPDisplayName"][0]), + "MEMBEROF_ATTR" : str(target) }) + + refint_config = read_and_sub_file(setup_path("refint.conf"), + { "LINK_ATTRS" : refint_attributes}) + +# 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 = adminpass + + 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 }) + # olc = yes? + olc_config_pass = "" + olc_config_acl = "" + olc_syncrepl_config = "" + olc_mmr_config = "" + if ol_olc == "yes": + olc_config_pass += read_and_sub_file(setup_path("olc_pass.conf"), + { "OLC_PW": adminpass }) + olc_config_acl += read_and_sub_file(setup_path("olc_acl.conf"),{}) + + # if olc = yes + mmr = yes, generate cn=config-replication directives + # and olc_seed.lif for the other mmr-servers + if ol_olc == "yes" and ol_mmr_urls is not None: + serverid=0 + olc_serverids_config = "" + olc_syncrepl_config = "" + olc_syncrepl_seed_config = "" + olc_mmr_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": adminpass}) + + 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": adminpass, + "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_CONFIG_PASS": olc_config_pass, + "OLC_SYNCREPL_CONFIG": olc_syncrepl_config, + "OLC_CONFIG_ACL": olc_config_acl, + "OLC_MMR_CONFIG": olc_mmr_config, + "REFINT_CONFIG": refint_config}) + setup_file(setup_path("modules.conf"), paths.modulesconf, + {"REALM": names.realm}) + + 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(adminpass), + "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" + + ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="") + if ldap_backend_port is not None: + server_port_string = " -h ldap://0.0.0.0:%d" % ldap_backend_port + else: + server_port_string = "" + + if ol_olc != "yes" and ol_mmr_urls is None: + slapdcommand="Start slapd with: slapd -f " + paths.ldapdir + "/slapd.conf -h " + ldapi_uri + server_port_string + + if ol_olc == "yes" and ol_mmr_urls is None: + slapdcommand="Start slapd with: slapd -F " + paths.olcdir + " -h \"" + ldapi_uri + " ldap://<FQHN>:<PORT>\"" + + if ol_olc != "yes" and ol_mmr_urls is not None: + slapdcommand="Start slapd with: slapd -f " + paths.ldapdir + "/slapd.conf -h \"" + ldapi_uri + " ldap://<FQHN>:<PORT>\"" + + if ol_olc == "yes" and ol_mmr_urls is not None: + slapdcommand="Start slapd with: slapd -F " + paths.olcdir + " -h \"" + ldapi_uri + " ldap://<FQHN>:<PORT>\"" + + + ldapuser = "--username=samba-admin" + + + schema_command = "bin/ad2oLschema --option=convert:target=" + ldap_backend_type + " -I " + setup_path(mapping) + " -H tdb://" + schemadb_path + " -O " + os.path.join(paths.ldapdir, backend_schema) + + os.system(schema_command) + + message("Your %s Backend for Samba4 is now configured, and is ready to be started" % ldap_backend_type) + message("Server Role: %s" % serverrole) + message("Hostname: %s" % names.hostname) + message("DNS Domain: %s" % names.dnsdomain) + message("Base DN: %s" % names.domaindn) + + if ldap_backend_type == "openldap": + message("LDAP admin user: samba-admin") + else: + message("LDAP admin DN: %s" % names.ldapmanagerdn) + + message("LDAP admin password: %s" % adminpass) + message(slapdcommand) + if ol_olc == "yes" or ol_mmr_urls is not None: + message("Attention to slapd-Port: <PORT> must be different than 389!") + assert isinstance(ldap_backend_type, str) + assert isinstance(ldapuser, str) + assert isinstance(adminpass, str) + assert isinstance(names.dnsdomain, str) + assert isinstance(names.domain, str) + assert isinstance(serverrole, str) + args = ["--ldap-backend=ldapi", + "--ldap-backend-type=" + ldap_backend_type, + "--password=" + adminpass, + ldapuser, + "--realm=" + names.dnsdomain, + "--domain=" + names.domain, + "--server-role='" + serverrole + "'"] + message("Run provision with: " + " ".join(args)) + + + # if --ol-olc=yes, generate online-configuration in ../private/ldap/slapd.d + if ol_olc == "yes": + if not os.path.isdir(paths.olcdir): + os.makedirs(paths.olcdir, 0770) + paths.olslaptest = str(ol_slaptest) + olc_command = paths.olslaptest + " -f" + paths.slapdconf + " -F" + paths.olcdir + " >/dev/null 2>&1" + os.system(olc_command) + os.remove(paths.slapdconf) + # use line below for debugging during olc-conversion with slaptest, instead of olc_command above + #olc_command = paths.olslaptest + " -f" + paths.slapdconf + " -F" + paths.olcdir" + + +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, hostguid): + """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 hostguid: GUID of the host. + """ + 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, + "HOSTGUID": hostguid, + "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, + }) + + +def load_schema(setup_path, samdb, schemadn, netbiosname, configdn, sitename, + serverdn, servername): + """Load schema for the SamDB. + + :param samdb: Load a schema into a SamDB. + :param setup_path: Setup path function. + :param schemadn: DN of the schema + :param netbiosname: NetBIOS name of the host. + :param configdn: DN of the configuration + :param serverdn: DN of the server + :param servername: Host name of the server + """ + schema_data = open(setup_path("schema.ldif"), 'r').read() + schema_data += open(setup_path("schema_samba4.ldif"), 'r').read() + schema_data = substitute_var(schema_data, {"SCHEMADN": schemadn}) + check_all_substituted(schema_data) + prefixmap = open(setup_path("prefixMap.txt"), 'r').read() + prefixmap = b64encode(prefixmap) + + head_data = open(setup_path("provision_schema_basedn_modify.ldif"), 'r').read() + head_data = substitute_var(head_data, { + "SCHEMADN": schemadn, + "NETBIOSNAME": netbiosname, + "CONFIGDN": configdn, + "DEFAULTSITE": sitename, + "PREFIXMAP_B64": prefixmap, + "SERVERDN": serverdn, + "SERVERNAME": servername, + }) + check_all_substituted(head_data) + samdb.attach_schema_from_ldif(head_data, schema_data) + diff --git a/source4/scripting/python/samba/samba3.py b/source4/scripting/python/samba/samba3.py new file mode 100644 index 0000000000..c8ddbc8864 --- /dev/null +++ b/source4/scripting/python/samba/samba3.py @@ -0,0 +1,792 @@ +#!/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/>. +# + +"""Support for reading Samba 3 data files.""" + +__docformat__ = "restructuredText" + +REGISTRY_VALUE_PREFIX = "SAMBA_REGVAL" +REGISTRY_DB_VERSION = 1 + +import os +import struct +import tdb + + +def fetch_uint32(tdb, key): + try: + data = tdb[key] + except KeyError: + return None + assert len(data) == 4 + return struct.unpack("<L", data)[0] + + +def fetch_int32(tdb, key): + try: + data = tdb[key] + except KeyError: + return None + assert len(data) == 4 + return struct.unpack("<l", data)[0] + + +class TdbDatabase(object): + """Simple Samba 3 TDB database reader.""" + def __init__(self, file): + """Open a file. + + :param file: Path of the file to open. + """ + self.tdb = tdb.Tdb(file, flags=os.O_RDONLY) + self._check_version() + + def _check_version(self): + pass + + def close(self): + """Close resources associated with this object.""" + self.tdb.close() + + +class Registry(TdbDatabase): + """Simple read-only support for reading the Samba3 registry. + + :note: This object uses the same syntax for registry key paths as + Samba 3. This particular format uses forward slashes for key path + separators and abbreviations for the predefined key names. + e.g.: HKLM/Software/Bar. + """ + def __len__(self): + """Return the number of keys.""" + return len(self.keys()) + + def keys(self): + """Return list with all the keys.""" + return [k.rstrip("\x00") for k in self.tdb.iterkeys() if not k.startswith(REGISTRY_VALUE_PREFIX)] + + def subkeys(self, key): + """Retrieve the subkeys for the specified key. + + :param key: Key path. + :return: list with key names + """ + data = self.tdb.get("%s\x00" % key) + if data is None: + return [] + (num, ) = struct.unpack("<L", data[0:4]) + keys = data[4:].split("\0") + assert keys[-1] == "" + keys.pop() + assert len(keys) == num + return keys + + def values(self, key): + """Return a dictionary with the values set for a specific key. + + :param key: Key to retrieve values for. + :return: Dictionary with value names as key, tuple with type and + data as value.""" + data = self.tdb.get("%s/%s\x00" % (REGISTRY_VALUE_PREFIX, key)) + if data is None: + return {} + ret = {} + (num, ) = struct.unpack("<L", data[0:4]) + data = data[4:] + for i in range(num): + # Value name + (name, data) = data.split("\0", 1) + + (type, ) = struct.unpack("<L", data[0:4]) + data = data[4:] + (value_len, ) = struct.unpack("<L", data[0:4]) + data = data[4:] + + ret[name] = (type, data[:value_len]) + data = data[value_len:] + + return ret + + +class PolicyDatabase(TdbDatabase): + """Samba 3 Account Policy database reader.""" + def __init__(self, file): + """Open a policy database + + :param file: Path to the file to open. + """ + super(PolicyDatabase, self).__init__(file) + self.min_password_length = fetch_uint32(self.tdb, "min password length\x00") + self.password_history = fetch_uint32(self.tdb, "password history\x00") + self.user_must_logon_to_change_password = fetch_uint32(self.tdb, "user must logon to change pasword\x00") + self.maximum_password_age = fetch_uint32(self.tdb, "maximum password age\x00") + self.minimum_password_age = fetch_uint32(self.tdb, "minimum password age\x00") + self.lockout_duration = fetch_uint32(self.tdb, "lockout duration\x00") + self.reset_count_minutes = fetch_uint32(self.tdb, "reset count minutes\x00") + self.bad_lockout_minutes = fetch_uint32(self.tdb, "bad lockout minutes\x00") + self.disconnect_time = fetch_int32(self.tdb, "disconnect time\x00") + self.refuse_machine_password_change = fetch_uint32(self.tdb, "refuse machine password change\x00") + + # FIXME: Read privileges as well + + +GROUPDB_DATABASE_VERSION_V1 = 1 # native byte format. +GROUPDB_DATABASE_VERSION_V2 = 2 # le format. + +GROUP_PREFIX = "UNIXGROUP/" + +# Alias memberships are stored reverse, as memberships. The performance +# critical operation is to determine the aliases a SID is member of, not +# listing alias members. So we store a list of alias SIDs a SID is member of +# hanging of the member as key. +MEMBEROF_PREFIX = "MEMBEROF/" + +class GroupMappingDatabase(TdbDatabase): + """Samba 3 group mapping database reader.""" + def _check_version(self): + assert fetch_int32(self.tdb, "INFO/version\x00") in (GROUPDB_DATABASE_VERSION_V1, GROUPDB_DATABASE_VERSION_V2) + + def groupsids(self): + """Retrieve the SIDs for the groups in this database. + + :return: List with sids as strings. + """ + for k in self.tdb.iterkeys(): + if k.startswith(GROUP_PREFIX): + yield k[len(GROUP_PREFIX):].rstrip("\0") + + def get_group(self, sid): + """Retrieve the group mapping information for a particular group. + + :param sid: SID of the group + :return: None if the group can not be found, otherwise + a tuple with gid, sid_name_use, the NT name and comment. + """ + data = self.tdb.get("%s%s\0" % (GROUP_PREFIX, sid)) + if data is None: + return data + (gid, sid_name_use) = struct.unpack("<lL", data[0:8]) + (nt_name, comment, _) = data[8:].split("\0") + return (gid, sid_name_use, nt_name, comment) + + def aliases(self): + """Retrieve the aliases in this database.""" + for k in self.tdb.iterkeys(): + if k.startswith(MEMBEROF_PREFIX): + yield k[len(MEMBEROF_PREFIX):].rstrip("\0") + + +# High water mark keys +IDMAP_HWM_GROUP = "GROUP HWM\0" +IDMAP_HWM_USER = "USER HWM\0" + +IDMAP_GROUP_PREFIX = "GID " +IDMAP_USER_PREFIX = "UID " + +# idmap version determines auto-conversion +IDMAP_VERSION_V2 = 2 + +class IdmapDatabase(TdbDatabase): + """Samba 3 ID map database reader.""" + def _check_version(self): + assert fetch_int32(self.tdb, "IDMAP_VERSION\0") == IDMAP_VERSION_V2 + + def uids(self): + """Retrieve a list of all uids in this database.""" + for k in self.tdb.iterkeys(): + if k.startswith(IDMAP_USER_PREFIX): + yield int(k[len(IDMAP_USER_PREFIX):].rstrip("\0")) + + def gids(self): + """Retrieve a list of all gids in this database.""" + for k in self.tdb.iterkeys(): + if k.startswith(IDMAP_GROUP_PREFIX): + yield int(k[len(IDMAP_GROUP_PREFIX):].rstrip("\0")) + + def get_user_sid(self, uid): + """Retrieve the SID associated with a particular uid. + + :param uid: UID to retrieve SID for. + :return: A SID or None if no mapping was found. + """ + data = self.tdb.get("%s%d\0" % (IDMAP_USER_PREFIX, uid)) + if data is None: + return data + return data.rstrip("\0") + + def get_group_sid(self, gid): + data = self.tdb.get("%s%d\0" % (IDMAP_GROUP_PREFIX, gid)) + if data is None: + return data + return data.rstrip("\0") + + def get_user_hwm(self): + """Obtain the user high-water mark.""" + return fetch_uint32(self.tdb, IDMAP_HWM_USER) + + def get_group_hwm(self): + """Obtain the group high-water mark.""" + return fetch_uint32(self.tdb, IDMAP_HWM_GROUP) + + +class SecretsDatabase(TdbDatabase): + """Samba 3 Secrets database reader.""" + def get_auth_password(self): + return self.tdb.get("SECRETS/AUTH_PASSWORD") + + def get_auth_domain(self): + return self.tdb.get("SECRETS/AUTH_DOMAIN") + + def get_auth_user(self): + return self.tdb.get("SECRETS/AUTH_USER") + + def get_domain_guid(self, host): + return self.tdb.get("SECRETS/DOMGUID/%s" % host) + + def ldap_dns(self): + for k in self.tdb.iterkeys(): + if k.startswith("SECRETS/LDAP_BIND_PW/"): + yield k[len("SECRETS/LDAP_BIND_PW/"):].rstrip("\0") + + def domains(self): + """Iterate over domains in this database. + + :return: Iterator over the names of domains in this database. + """ + for k in self.tdb.iterkeys(): + if k.startswith("SECRETS/SID/"): + yield k[len("SECRETS/SID/"):].rstrip("\0") + + def get_ldap_bind_pw(self, host): + return self.tdb.get("SECRETS/LDAP_BIND_PW/%s" % host) + + def get_afs_keyfile(self, host): + return self.tdb.get("SECRETS/AFS_KEYFILE/%s" % host) + + def get_machine_sec_channel_type(self, host): + return fetch_uint32(self.tdb, "SECRETS/MACHINE_SEC_CHANNEL_TYPE/%s" % host) + + def get_machine_last_change_time(self, host): + return fetch_uint32(self.tdb, "SECRETS/MACHINE_LAST_CHANGE_TIME/%s" % host) + + def get_machine_password(self, host): + return self.tdb.get("SECRETS/MACHINE_PASSWORD/%s" % host) + + def get_machine_acc(self, host): + return self.tdb.get("SECRETS/$MACHINE.ACC/%s" % host) + + def get_domtrust_acc(self, host): + return self.tdb.get("SECRETS/$DOMTRUST.ACC/%s" % host) + + def trusted_domains(self): + for k in self.tdb.iterkeys(): + if k.startswith("SECRETS/$DOMTRUST.ACC/"): + yield k[len("SECRETS/$DOMTRUST.ACC/"):].rstrip("\0") + + def get_random_seed(self): + return self.tdb.get("INFO/random_seed") + + def get_sid(self, host): + return self.tdb.get("SECRETS/SID/%s" % host.upper()) + + +SHARE_DATABASE_VERSION_V1 = 1 +SHARE_DATABASE_VERSION_V2 = 2 + +class ShareInfoDatabase(TdbDatabase): + """Samba 3 Share Info database reader.""" + def _check_version(self): + assert fetch_int32(self.tdb, "INFO/version\0") in (SHARE_DATABASE_VERSION_V1, SHARE_DATABASE_VERSION_V2) + + def get_secdesc(self, name): + """Obtain the security descriptor on a particular share. + + :param name: Name of the share + """ + secdesc = self.tdb.get("SECDESC/%s" % name) + # FIXME: Run ndr_pull_security_descriptor + return secdesc + + +class Shares(object): + """Container for share objects.""" + def __init__(self, lp, shareinfo): + self.lp = lp + self.shareinfo = shareinfo + + def __len__(self): + """Number of shares.""" + return len(self.lp) - 1 + + def __iter__(self): + """Iterate over the share names.""" + return self.lp.__iter__() + + +ACB_DISABLED = 0x00000001 +ACB_HOMDIRREQ = 0x00000002 +ACB_PWNOTREQ = 0x00000004 +ACB_TEMPDUP = 0x00000008 +ACB_NORMAL = 0x00000010 +ACB_MNS = 0x00000020 +ACB_DOMTRUST = 0x00000040 +ACB_WSTRUST = 0x00000080 +ACB_SVRTRUST = 0x00000100 +ACB_PWNOEXP = 0x00000200 +ACB_AUTOLOCK = 0x00000400 +ACB_ENC_TXT_PWD_ALLOWED = 0x00000800 +ACB_SMARTCARD_REQUIRED = 0x00001000 +ACB_TRUSTED_FOR_DELEGATION = 0x00002000 +ACB_NOT_DELEGATED = 0x00004000 +ACB_USE_DES_KEY_ONLY = 0x00008000 +ACB_DONT_REQUIRE_PREAUTH = 0x00010000 +ACB_PW_EXPIRED = 0x00020000 +ACB_NO_AUTH_DATA_REQD = 0x00080000 + +acb_info_mapping = { + 'N': ACB_PWNOTREQ, # 'N'o password. + 'D': ACB_DISABLED, # 'D'isabled. + 'H': ACB_HOMDIRREQ, # 'H'omedir required. + 'T': ACB_TEMPDUP, # 'T'emp account. + 'U': ACB_NORMAL, # 'U'ser account (normal). + 'M': ACB_MNS, # 'M'NS logon user account. What is this ? + 'W': ACB_WSTRUST, # 'W'orkstation account. + 'S': ACB_SVRTRUST, # 'S'erver account. + 'L': ACB_AUTOLOCK, # 'L'ocked account. + 'X': ACB_PWNOEXP, # No 'X'piry on password + 'I': ACB_DOMTRUST, # 'I'nterdomain trust account. + ' ': 0 + } + +def decode_acb(text): + """Decode a ACB field. + + :param text: ACB text + :return: integer with flags set. + """ + assert not "[" in text and not "]" in text + ret = 0 + for x in text: + ret |= acb_info_mapping[x] + return ret + + +class SAMUser(object): + """Samba 3 SAM User. + + :note: Unknown or unset fields are set to None. + """ + def __init__(self, name, uid=None, lm_password=None, nt_password=None, acct_ctrl=None, + last_change_time=None, nt_username=None, fullname=None, logon_time=None, logoff_time=None, + acct_desc=None, group_rid=None, bad_password_count=None, logon_count=None, + domain=None, dir_drive=None, munged_dial=None, homedir=None, logon_script=None, + profile_path=None, workstations=None, kickoff_time=None, bad_password_time=None, + pass_last_set_time=None, pass_can_change_time=None, pass_must_change_time=None, + user_rid=None, unknown_6=None, nt_password_history=None, + unknown_str=None, hours=None, logon_divs=None): + self.username = name + self.uid = uid + self.lm_password = lm_password + self.nt_password = nt_password + self.acct_ctrl = acct_ctrl + self.pass_last_set_time = last_change_time + self.nt_username = nt_username + self.fullname = fullname + self.logon_time = logon_time + self.logoff_time = logoff_time + self.acct_desc = acct_desc + self.group_rid = group_rid + self.bad_password_count = bad_password_count + self.logon_count = logon_count + self.domain = domain + self.dir_drive = dir_drive + self.munged_dial = munged_dial + self.homedir = homedir + self.logon_script = logon_script + self.profile_path = profile_path + self.workstations = workstations + self.kickoff_time = kickoff_time + self.bad_password_time = bad_password_time + self.pass_can_change_time = pass_can_change_time + self.pass_must_change_time = pass_must_change_time + self.user_rid = user_rid + self.unknown_6 = unknown_6 + self.nt_password_history = nt_password_history + self.unknown_str = unknown_str + self.hours = hours + self.logon_divs = logon_divs + + def __eq__(self, other): + if not isinstance(other, SAMUser): + return False + return self.__dict__ == other.__dict__ + + +class SmbpasswdFile(object): + """Samba 3 smbpasswd file reader.""" + def __init__(self, file): + self.users = {} + f = open(file, 'r') + for l in f.readlines(): + if len(l) == 0 or l[0] == "#": + continue # Skip comments and blank lines + parts = l.split(":") + username = parts[0] + uid = int(parts[1]) + acct_ctrl = 0 + last_change_time = None + if parts[2] == "NO PASSWORD": + acct_ctrl |= ACB_PWNOTREQ + lm_password = None + elif parts[2][0] in ("*", "X"): + # No password set + lm_password = None + else: + lm_password = parts[2] + + if parts[3][0] in ("*", "X"): + # No password set + nt_password = None + else: + nt_password = parts[3] + + if parts[4][0] == '[': + assert "]" in parts[4] + acct_ctrl |= decode_acb(parts[4][1:-1]) + if parts[5].startswith("LCT-"): + last_change_time = int(parts[5][len("LCT-"):], 16) + else: # old style file + if username[-1] == "$": + acct_ctrl &= ~ACB_NORMAL + acct_ctrl |= ACB_WSTRUST + + self.users[username] = SAMUser(username, uid, lm_password, nt_password, acct_ctrl, last_change_time) + + f.close() + + def __len__(self): + return len(self.users) + + def __getitem__(self, name): + return self.users[name] + + def __iter__(self): + return iter(self.users) + + def close(self): # For consistency + pass + + +TDBSAM_FORMAT_STRING_V0 = "ddddddBBBBBBBBBBBBddBBwdwdBwwd" +TDBSAM_FORMAT_STRING_V1 = "dddddddBBBBBBBBBBBBddBBwdwdBwwd" +TDBSAM_FORMAT_STRING_V2 = "dddddddBBBBBBBBBBBBddBBBwwdBwwd" +TDBSAM_USER_PREFIX = "USER_" + + +class LdapSam(object): + """Samba 3 LDAP passdb backend reader.""" + def __init__(self, url): + self.ldap_url = ldap_url + + +class TdbSam(TdbDatabase): + """Samba 3 TDB passdb backend reader.""" + def _check_version(self): + self.version = fetch_uint32(self.tdb, "INFO/version\0") or 0 + assert self.version in (0, 1, 2) + + def usernames(self): + """Iterate over the usernames in this Tdb database.""" + for k in self.tdb.iterkeys(): + if k.startswith(TDBSAM_USER_PREFIX): + yield k[len(TDBSAM_USER_PREFIX):].rstrip("\0") + + __iter__ = usernames + + def __getitem__(self, name): + data = self.tdb["%s%s\0" % (TDBSAM_USER_PREFIX, name)] + user = SAMUser(name) + + def unpack_string(data): + (length, ) = struct.unpack("<L", data[:4]) + data = data[4:] + if length == 0: + return (None, data) + return (data[:length].rstrip("\0"), data[length:]) + + def unpack_int32(data): + (value, ) = struct.unpack("<l", data[:4]) + return (value, data[4:]) + + def unpack_uint32(data): + (value, ) = struct.unpack("<L", data[:4]) + return (value, data[4:]) + + def unpack_uint16(data): + (value, ) = struct.unpack("<H", data[:2]) + return (value, data[2:]) + + (logon_time, data) = unpack_int32(data) + (logoff_time, data) = unpack_int32(data) + (kickoff_time, data) = unpack_int32(data) + + if self.version > 0: + (bad_password_time, data) = unpack_int32(data) + if bad_password_time != 0: + user.bad_password_time = bad_password_time + (pass_last_set_time, data) = unpack_int32(data) + (pass_can_change_time, data) = unpack_int32(data) + (pass_must_change_time, data) = unpack_int32(data) + + if logon_time != 0: + user.logon_time = logon_time + user.logoff_time = logoff_time + user.kickoff_time = kickoff_time + if pass_last_set_time != 0: + user.pass_last_set_time = pass_last_set_time + user.pass_can_change_time = pass_can_change_time + + (user.username, data) = unpack_string(data) + (user.domain, data) = unpack_string(data) + (user.nt_username, data) = unpack_string(data) + (user.fullname, data) = unpack_string(data) + (user.homedir, data) = unpack_string(data) + (user.dir_drive, data) = unpack_string(data) + (user.logon_script, data) = unpack_string(data) + (user.profile_path, data) = unpack_string(data) + (user.acct_desc, data) = unpack_string(data) + (user.workstations, data) = unpack_string(data) + (user.unknown_str, data) = unpack_string(data) + (user.munged_dial, data) = unpack_string(data) + + (user.user_rid, data) = unpack_int32(data) + (user.group_rid, data) = unpack_int32(data) + + (user.lm_password, data) = unpack_string(data) + (user.nt_password, data) = unpack_string(data) + + if self.version > 1: + (user.nt_password_history, data) = unpack_string(data) + + (user.acct_ctrl, data) = unpack_uint16(data) + (_, data) = unpack_uint32(data) # remove_me field + (user.logon_divs, data) = unpack_uint16(data) + (hours, data) = unpack_string(data) + user.hours = [] + for entry in hours: + for i in range(8): + user.hours.append(ord(entry) & (2 ** i) == (2 ** i)) + (user.bad_password_count, data) = unpack_uint16(data) + (user.logon_count, data) = unpack_uint16(data) + (user.unknown_6, data) = unpack_uint32(data) + assert len(data) == 0 + return user + + +def shellsplit(text): + """Very simple shell-like line splitting. + + :param text: Text to split. + :return: List with parts of the line as strings. + """ + ret = list() + inquotes = False + current = "" + for c in text: + if c == "\"": + inquotes = not inquotes + elif c in ("\t", "\n", " ") and not inquotes: + ret.append(current) + current = "" + else: + current += c + if current != "": + ret.append(current) + return ret + + +class WinsDatabase(object): + """Samba 3 WINS database reader.""" + def __init__(self, file): + self.entries = {} + f = open(file, 'r') + assert f.readline().rstrip("\n") == "VERSION 1 0" + for l in f.readlines(): + if l[0] == "#": # skip comments + continue + entries = shellsplit(l.rstrip("\n")) + name = entries[0] + ttl = int(entries[1]) + i = 2 + ips = [] + while "." in entries[i]: + ips.append(entries[i]) + i+=1 + nb_flags = int(entries[i][:-1], 16) + assert not name in self.entries, "Name %s exists twice" % name + self.entries[name] = (ttl, ips, nb_flags) + f.close() + + def __getitem__(self, name): + return self.entries[name] + + def __len__(self): + return len(self.entries) + + def __iter__(self): + return iter(self.entries) + + def items(self): + """Return the entries in this WINS database.""" + return self.entries.items() + + def close(self): # for consistency + pass + + +class ParamFile(object): + """Simple smb.conf-compatible file parser + + Does not use a parameter table, unlike the "normal". + """ + + def __init__(self, sections=None): + self._sections = sections or {} + + def _sanitize_name(self, name): + return name.strip().lower().replace(" ","") + + def __repr__(self): + return "ParamFile(%r)" % self._sections + + def read(self, filename): + """Read a file. + + :param filename: Path to the file + """ + section = None + for i, l in enumerate(open(filename, 'r').xreadlines()): + l = l.strip() + if not l: + continue + if l[0] == "[" and l[-1] == "]": + section = self._sanitize_name(l[1:-1]) + self._sections.setdefault(section, {}) + elif "=" in l: + (k, v) = l.split("=", 1) + self._sections[section][self._sanitize_name(k)] = v + else: + raise Error("Unable to parser line %d: %r" % (i+1,l)) + + def get(self, param, section=None): + """Return the value of a parameter. + + :param param: Parameter name + :param section: Section name, defaults to "global" + :return: parameter value as string if found, None otherwise. + """ + if section is None: + section = "global" + section = self._sanitize_name(section) + if not section in self._sections: + return None + param = self._sanitize_name(param) + if not param in self._sections[section]: + return None + return self._sections[section][param].strip() + + def __getitem__(self, section): + return self._sections[section] + + def get_section(self, section): + return self._sections.get(section) + + def add_section(self, section): + self._sections[self._sanitize_name(section)] = {} + + def set_string(self, name, value): + self._sections["global"][name] = value + + def get_string(self, name): + return self._sections["global"].get(name) + + +class Samba3(object): + """Samba 3 configuration and state data reader.""" + def __init__(self, libdir, smbconfpath): + """Open the configuration and data for a Samba 3 installation. + + :param libdir: Library directory + :param smbconfpath: Path to the smb.conf file. + """ + self.smbconfpath = smbconfpath + self.libdir = libdir + self.lp = ParamFile() + self.lp.read(self.smbconfpath) + + def libdir_path(self, path): + if path[0] == "/" or path[0] == ".": + return path + return os.path.join(self.libdir, path) + + def get_conf(self): + return self.lp + + def get_sam_db(self): + lp = self.get_conf() + backends = (lp.get("passdb backend") or "").split(" ") + if ":" in backends[0]: + (name, location) = backends[0].split(":", 2) + else: + name = backends[0] + location = None + if name == "smbpasswd": + return SmbpasswdFile(self.libdir_path(location or "smbpasswd")) + elif name == "tdbsam": + return TdbSam(self.libdir_path(location or "passdb.tdb")) + elif name == "ldapsam": + if location is not None: + return LdapSam("ldap:%s" % location) + return LdapSam(lp.get("ldap server")) + else: + raise NotImplementedError("unsupported passdb backend %s" % backends[0]) + + def get_policy_db(self): + return PolicyDatabase(self.libdir_path("account_policy.tdb")) + + def get_registry(self): + return Registry(self.libdir_path("registry.tdb")) + + def get_secrets_db(self): + return SecretsDatabase(self.libdir_path("secrets.tdb")) + + def get_shareinfo_db(self): + return ShareInfoDatabase(self.libdir_path("share_info.tdb")) + + def get_idmap_db(self): + return IdmapDatabase(self.libdir_path("winbindd_idmap.tdb")) + + def get_wins_db(self): + return WinsDatabase(self.libdir_path("wins.dat")) + + def get_shares(self): + return Shares(self.get_conf(), self.get_shareinfo_db()) + + def get_groupmapping_db(self): + return GroupMappingDatabase(self.libdir_path("group_mapping.tdb")) diff --git a/source4/scripting/python/samba/samdb.py b/source4/scripting/python/samba/samdb.py new file mode 100644 index 0000000000..b92a91e2ef --- /dev/null +++ b/source4/scripting/python/samba/samdb.py @@ -0,0 +1,245 @@ +#!/usr/bin/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/>. +# + +"""Convenience functions for using the SAM.""" + +import samba +import glue +import ldb +from samba.idmap import IDmapDB +import pwd +import time +import base64 + +__docformat__ = "restructuredText" + +class SamDB(samba.Ldb): + """The SAM database.""" + + def __init__(self, url=None, session_info=None, credentials=None, + modules_dir=None, lp=None): + """Open the Sam Database. + + :param url: URL of the database. + """ + self.lp = lp + super(SamDB, self).__init__(session_info=session_info, credentials=credentials, + modules_dir=modules_dir, lp=lp) + glue.dsdb_set_global_schema(self) + if url: + self.connect(url) + else: + self.connect(lp.get("sam database")) + + def connect(self, url): + super(SamDB, self).connect(self.lp.private_path(url)) + + def add_foreign(self, domaindn, sid, desc): + """Add a foreign security principle.""" + add = """ +dn: CN=%s,CN=ForeignSecurityPrincipals,%s +objectClass: top +objectClass: foreignSecurityPrincipal +description: %s +""" % (sid, domaindn, desc) + # deliberately ignore errors from this, as the records may + # already exist + for msg in self.parse_ldif(add): + self.add(msg[1]) + + def add_stock_foreign_sids(self): + domaindn = self.domain_dn() + self.add_foreign(domaindn, "S-1-5-7", "Anonymous") + self.add_foreign(domaindn, "S-1-1-0", "World") + self.add_foreign(domaindn, "S-1-5-2", "Network") + self.add_foreign(domaindn, "S-1-5-18", "System") + self.add_foreign(domaindn, "S-1-5-11", "Authenticated Users") + + def enable_account(self, user_dn): + """Enable an account. + + :param user_dn: Dn of the account to enable. + """ + res = self.search(user_dn, ldb.SCOPE_BASE, None, ["userAccountControl"]) + assert len(res) == 1 + userAccountControl = res[0]["userAccountControl"][0] + userAccountControl = int(userAccountControl) + if (userAccountControl & 0x2): + userAccountControl = userAccountControl & ~0x2 # remove disabled bit + if (userAccountControl & 0x20): + userAccountControl = userAccountControl & ~0x20 # remove 'no password required' bit + + mod = """ +dn: %s +changetype: modify +replace: userAccountControl +userAccountControl: %u +""" % (user_dn, userAccountControl) + self.modify_ldif(mod) + + def domain_dn(self): + # find the DNs for the domain and the domain users group + res = self.search("", 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 newuser(self, username, unixname, password): + """add a new user record. + + :param username: Name of the new user. + :param unixname: Name of the unix user to map to. + :param password: Password for the new user + """ + # connect to the sam + self.transaction_start() + try: + domain_dn = self.domain_dn() + assert(domain_dn is not None) + user_dn = "CN=%s,CN=Users,%s" % (username, domain_dn) + + # + # the new user record. note the reliance on the samdb module to + # fill in a sid, guid etc + # + # now the real work + self.add({"dn": user_dn, + "sAMAccountName": username, + "userPassword": password, + "objectClass": "user"}) + + 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 + + # modify the userAccountControl to remove the disabled bit + self.enable_account(user_dn) + except: + self.transaction_cancel() + raise + self.transaction_commit() + + def setpassword(self, filter, password): + """Set a password on a user record + + :param filter: LDAP filter to find the user (eg samccountname=name) + :param password: Password for the user + """ + # connect to the sam + self.transaction_start() + try: + # find the DNs for the domain + res = self.search("", scope=ldb.SCOPE_BASE, + expression="(defaultNamingContext=*)", + attrs=["defaultNamingContext"]) + assert(len(res) == 1 and res[0]["defaultNamingContext"] is not None) + domain_dn = res[0]["defaultNamingContext"][0] + assert(domain_dn is not None) + + res = self.search(domain_dn, scope=ldb.SCOPE_SUBTREE, + expression=filter, + attrs=[]) + assert(len(res) == 1) + user_dn = res[0].dn + + setpw = """ +dn: %s +changetype: modify +replace: userPassword +userPassword:: %s +""" % (user_dn, base64.b64encode(password)) + + self.modify_ldif(setpw) + + # modify the userAccountControl to remove the disabled bit + self.enable_account(user_dn) + except: + self.transaction_cancel() + raise + self.transaction_commit() + + def set_domain_sid(self, sid): + """Change the domain SID used by this SamDB. + + :param sid: The new domain sid to use. + """ + glue.samdb_set_domain_sid(self, sid) + + def attach_schema_from_ldif(self, pf, df): + glue.dsdb_attach_schema_from_ldif_file(self, pf, df) + + 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 setexpiry(self, user, expiry_seconds, noexpiry): + """Set the password expiry for a user + + :param expiry_seconds: expiry time from now in seconds + :param noexpiry: if set, then don't expire password + """ + self.transaction_start() + try: + res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE, + expression=("(samAccountName=%s)" % user), + attrs=["userAccountControl", "accountExpires"]) + assert len(res) == 1 + userAccountControl = int(res[0]["userAccountControl"][0]) + accountExpires = int(res[0]["accountExpires"][0]) + if noexpiry: + userAccountControl = userAccountControl | 0x10000 + accountExpires = 0 + else: + userAccountControl = userAccountControl & ~0x10000 + accountExpires = glue.unix2nttime(expiry_seconds + int(time.time())) + + mod = """ +dn: %s +changetype: modify +replace: userAccountControl +userAccountControl: %u +replace: accountExpires +accountExpires: %u +""" % (res[0].dn, userAccountControl, accountExpires) + # now change the database + self.modify_ldif(mod) + except: + self.transaction_cancel() + raise + self.transaction_commit(); diff --git a/source4/scripting/python/samba/tests/__init__.py b/source4/scripting/python/samba/tests/__init__.py new file mode 100644 index 0000000000..b342b93c49 --- /dev/null +++ b/source4/scripting/python/samba/tests/__init__.py @@ -0,0 +1,98 @@ +#!/usr/bin/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 os +import ldb +import samba +import tempfile +import unittest + +class LdbTestCase(unittest.TestCase): + """Trivial test case for running tests against a LDB.""" + def setUp(self): + self.filename = os.tempnam() + self.ldb = samba.Ldb(self.filename) + + def set_modules(self, modules=[]): + """Change the modules for this Ldb.""" + m = ldb.Message() + m.dn = ldb.Dn(self.ldb, "@MODULES") + m["@LIST"] = ",".join(modules) + self.ldb.add(m) + self.ldb = samba.Ldb(self.filename) + + +class TestCaseInTempDir(unittest.TestCase): + def setUp(self): + super(TestCaseInTempDir, self).setUp() + self.tempdir = tempfile.mkdtemp() + + def tearDown(self): + super(TestCaseInTempDir, self).tearDown() + self.assertEquals([], os.listdir(self.tempdir)) + 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 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) + + +cmdline_loadparm = None +cmdline_credentials = None + +class RpcInterfaceTestCase(unittest.TestCase): + def get_loadparm(self): + assert cmdline_loadparm is not None + return cmdline_loadparm + + def get_credentials(self): + return cmdline_credentials diff --git a/source4/scripting/python/samba/tests/dcerpc/__init__.py b/source4/scripting/python/samba/tests/dcerpc/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/source4/scripting/python/samba/tests/dcerpc/__init__.py diff --git a/source4/scripting/python/samba/tests/dcerpc/bare.py b/source4/scripting/python/samba/tests/dcerpc/bare.py new file mode 100644 index 0000000000..cd939b8098 --- /dev/null +++ b/source4/scripting/python/samba/tests/dcerpc/bare.py @@ -0,0 +1,48 @@ +#!/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/>. +# + +from samba.dcerpc import ClientConnection +from unittest import TestCase +from samba.tests import cmdline_loadparm + + +class BareTestCase(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) + 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) + y = ClientConnection("ncalrpc:localhost", + ("60a15ec5-4de8-11d7-a637-005056a20182", 1), + basis_connection=x, lp_ctx=cmdline_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) + y = ClientConnection("ncalrpc:localhost", + ("60a15ec5-4de8-11d7-a637-005056a20182", 1), + basis_connection=x, lp_ctx=cmdline_loadparm) + self.assertEquals("\x01\x00\x00\x00", y.request(0, chr(0) * 4)) diff --git a/source4/scripting/python/samba/tests/dcerpc/registry.py b/source4/scripting/python/samba/tests/dcerpc/registry.py new file mode 100644 index 0000000000..526b2340cc --- /dev/null +++ b/source4/scripting/python/samba/tests/dcerpc/registry.py @@ -0,0 +1,50 @@ +#!/usr/bin/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/>. +# + +from samba.dcerpc import winreg +import unittest +from samba.tests import RpcInterfaceTestCase + + +class WinregTests(RpcInterfaceTestCase): + def setUp(self): + self.conn = winreg.winreg("ncalrpc:", self.get_loadparm(), + self.get_credentials()) + + def get_hklm(self): + return self.conn.OpenHKLM(None, + winreg.KEY_QUERY_VALUE | winreg.KEY_ENUMERATE_SUB_KEYS) + + def test_hklm(self): + handle = self.conn.OpenHKLM(None, + winreg.KEY_QUERY_VALUE | winreg.KEY_ENUMERATE_SUB_KEYS) + self.conn.CloseKey(handle) + + def test_getversion(self): + handle = self.get_hklm() + version = self.conn.GetVersion(handle) + self.assertEquals(int, version.__class__) + self.conn.CloseKey(handle) + + def test_getkeyinfo(self): + handle = self.conn.OpenHKLM(None, + winreg.KEY_QUERY_VALUE | winreg.KEY_ENUMERATE_SUB_KEYS) + x = self.conn.QueryInfoKey(handle, winreg.String()) + self.assertEquals(9, len(x)) # should return a 9-tuple + self.conn.CloseKey(handle) diff --git a/source4/scripting/python/samba/tests/dcerpc/rpcecho.py b/source4/scripting/python/samba/tests/dcerpc/rpcecho.py new file mode 100644 index 0000000000..62268005c2 --- /dev/null +++ b/source4/scripting/python/samba/tests/dcerpc/rpcecho.py @@ -0,0 +1,69 @@ +#!/usr/bin/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/>. +# + +from samba.dcerpc import echo +from samba.ndr import ndr_pack, ndr_unpack +import unittest +from samba.tests import RpcInterfaceTestCase + + +class RpcEchoTests(RpcInterfaceTestCase): + def setUp(self): + self.conn = echo.rpcecho("ncalrpc:", self.get_loadparm()) + + def test_two_contexts(self): + self.conn2 = echo.rpcecho("ncalrpc:", self.get_loadparm(), basis_connection=self.conn) + self.assertEquals(3, self.conn2.AddOne(2)) + + def test_abstract_syntax(self): + self.assertEquals(("60a15ec5-4de8-11d7-a637-005056a20182", 1), + self.conn.abstract_syntax) + + def test_addone(self): + self.assertEquals(2, self.conn.AddOne(1)) + + def test_echodata(self): + self.assertEquals([1,2,3], self.conn.EchoData([1, 2, 3])) + + def test_call(self): + self.assertEquals(u"foobar", self.conn.TestCall(u"foobar")) + + def test_surrounding(self): + surrounding_struct = echo.Surrounding() + surrounding_struct.x = 4 + surrounding_struct.surrounding = [1,2,3,4] + y = self.conn.TestSurrounding(surrounding_struct) + self.assertEquals(4 * [0], y.surrounding) + + def test_manual_request(self): + self.assertEquals("\x01\x00\x00\x00", self.conn.request(0, chr(0) * 4)) + + def test_server_name(self): + self.assertEquals(None, self.conn.server_name) + + +class NdrEchoTests(unittest.TestCase): + def test_info1_push(self): + x = echo.info1() + x.v = 42 + self.assertEquals("\x2a", ndr_pack(x)) + + def test_info1_pull(self): + x = ndr_unpack(echo.info1, "\x42") + self.assertEquals(x.v, 66) diff --git a/source4/scripting/python/samba/tests/dcerpc/sam.py b/source4/scripting/python/samba/tests/dcerpc/sam.py new file mode 100644 index 0000000000..50e00a3f9e --- /dev/null +++ b/source4/scripting/python/samba/tests/dcerpc/sam.py @@ -0,0 +1,46 @@ +#!/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/>. +# + +from samba.dcerpc import samr, security +from samba.tests import RpcInterfaceTestCase + +# FIXME: Pidl should be doing this for us +def toArray((handle, array, num_entries)): + ret = [] + for x in range(num_entries): + ret.append((array.entries[x].idx, array.entries[x].name)) + return ret + + +class SamrTests(RpcInterfaceTestCase): + def setUp(self): + self.conn = samr.samr("ncalrpc:", self.get_loadparm()) + + def test_connect5(self): + (level, info, handle) = self.conn.Connect5(None, 0, 1, samr.ConnectInfo1()) + + def test_connect2(self): + handle = self.conn.Connect2(None, security.SEC_FLAG_MAXIMUM_ALLOWED) + + def test_EnumDomains(self): + handle = self.conn.Connect2(None, security.SEC_FLAG_MAXIMUM_ALLOWED) + domains = toArray(self.conn.EnumDomains(handle, 0, -1)) + self.conn.Close(handle) + diff --git a/source4/scripting/python/samba/tests/dcerpc/unix.py b/source4/scripting/python/samba/tests/dcerpc/unix.py new file mode 100644 index 0000000000..aa47b71b16 --- /dev/null +++ b/source4/scripting/python/samba/tests/dcerpc/unix.py @@ -0,0 +1,37 @@ +#!/usr/bin/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/>. +# + +from samba.dcerpc import unixinfo +from samba.tests import RpcInterfaceTestCase + +class UnixinfoTests(RpcInterfaceTestCase): + def setUp(self): + self.conn = unixinfo.unixinfo("ncalrpc:", self.get_loadparm()) + + def test_getpwuid(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_gidtosid(self): + self.conn.GidToSid(1000) + + def test_uidtosid(self): + self.conn.UidToSid(1000) diff --git a/source4/scripting/python/samba/tests/provision.py b/source4/scripting/python/samba/tests/provision.py new file mode 100644 index 0000000000..fdac9d4ea2 --- /dev/null +++ b/source4/scripting/python/samba/tests/provision.py @@ -0,0 +1,124 @@ +#!/usr/bin/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/>. +# + +import os +from samba.provision import setup_secretsdb, secretsdb_become_dc, findnss +import samba.tests +from ldb import Dn +from samba import param +import unittest + +lp = samba.tests.cmdline_loadparm + +setup_dir = "setup" +def setup_path(file): + return os.path.join(setup_dir, file) + + +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) + try: + self.assertEquals("LSA Secrets", + ldb.searchone(basedn="CN=LSA Secrets", attribute="CN")) + finally: + del ldb + os.unlink(path) + + def test_become_dc(self): + path = os.path.join(self.tempdir, "secrets.ldb") + secrets_ldb = setup_secretsdb(path, setup_path, None, None, lp=lp) + try: + secretsdb_become_dc(secrets_ldb, setup_path, domain="EXAMPLE", + realm="example", netbiosname="myhost", + domainsid="S-5-22", keytab_path="keytab.path", + samdb_url="ldap://url/", + dns_keytab_path="dns.keytab", dnspass="bla", + machinepass="machinepass", dnsdomain="example.com") + self.assertEquals(1, + len(secrets_ldb.search("samAccountName=krbtgt,flatname=EXAMPLE,CN=Principals"))) + self.assertEquals("keytab.path", + secrets_ldb.searchone(basedn="flatname=EXAMPLE,CN=primary domains", + expression="(privateKeytab=*)", + attribute="privateKeytab")) + self.assertEquals("S-5-22", + secrets_ldb.searchone(basedn="flatname=EXAMPLE,CN=primary domains", + expression="objectSid=*", attribute="objectSid")) + + finally: + del secrets_ldb + os.unlink(path) + + +class FindNssTests(unittest.TestCase): + """Test findnss() function.""" + def test_nothing(self): + def x(y): + raise KeyError + self.assertRaises(KeyError, findnss, x, []) + + def test_first(self): + self.assertEquals("bla", findnss(lambda x: "bla", ["bla"])) + + def test_skip_first(self): + def x(y): + if y != "bla": + raise KeyError + return "ha" + self.assertEquals("ha", findnss(x, ["bloe", "bla"])) + + +class Disabled(object): + def test_setup_templatesdb(self): + raise NotImplementedError(self.test_setup_templatesdb) + + def test_setup_registry(self): + raise NotImplementedError(self.test_setup_registry) + + def test_setup_samdb_rootdse(self): + raise NotImplementedError(self.test_setup_samdb_rootdse) + + def test_setup_samdb_partitions(self): + raise NotImplementedError(self.test_setup_samdb_partitions) + + def test_create_phpldapadmin_config(self): + raise NotImplementedError(self.test_create_phpldapadmin_config) + + def test_provision_dns(self): + raise NotImplementedError(self.test_provision_dns) + + def test_provision_ldapbase(self): + raise NotImplementedError(self.test_provision_ldapbase) + + def test_provision_guess(self): + raise NotImplementedError(self.test_provision_guess) + + def test_join_domain(self): + raise NotImplementedError(self.test_join_domain) + + 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/samba3.py b/source4/scripting/python/samba/tests/samba3.py new file mode 100644 index 0000000000..71e08bdd7f --- /dev/null +++ b/source4/scripting/python/samba/tests/samba3.py @@ -0,0 +1,232 @@ +#!/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/>. +# + +import unittest +from samba.samba3 import GroupMappingDatabase, Registry, PolicyDatabase, SecretsDatabase, TdbSam +from samba.samba3 import WinsDatabase, SmbpasswdFile, ACB_NORMAL, IdmapDatabase, SAMUser, ParamFile +import os + +DATADIR=os.path.join(os.path.dirname(__file__), "../../../../../testdata/samba3") +print "Samba 3 data dir: %s" % DATADIR + +class RegistryTestCase(unittest.TestCase): + def setUp(self): + self.registry = Registry(os.path.join(DATADIR, "registry.tdb")) + + def tearDown(self): + self.registry.close() + + def test_length(self): + self.assertEquals(28, len(self.registry)) + + def test_keys(self): + self.assertTrue("HKLM" in self.registry.keys()) + + def test_subkeys(self): + self.assertEquals(["SOFTWARE", "SYSTEM"], self.registry.subkeys("HKLM")) + + def test_values(self): + self.assertEquals({'DisplayName': (1L, 'E\x00v\x00e\x00n\x00t\x00 \x00L\x00o\x00g\x00\x00\x00'), + 'ErrorControl': (4L, '\x01\x00\x00\x00')}, + self.registry.values("HKLM/SYSTEM/CURRENTCONTROLSET/SERVICES/EVENTLOG")) + + +class PolicyTestCase(unittest.TestCase): + def setUp(self): + self.policy = PolicyDatabase(os.path.join(DATADIR, "account_policy.tdb")) + + def test_policy(self): + self.assertEquals(self.policy.min_password_length, 5) + self.assertEquals(self.policy.minimum_password_age, 0) + self.assertEquals(self.policy.maximum_password_age, 999999999) + self.assertEquals(self.policy.refuse_machine_password_change, 0) + self.assertEquals(self.policy.reset_count_minutes, 0) + self.assertEquals(self.policy.disconnect_time, -1) + self.assertEquals(self.policy.user_must_logon_to_change_password, None) + self.assertEquals(self.policy.password_history, 0) + self.assertEquals(self.policy.lockout_duration, 0) + self.assertEquals(self.policy.bad_lockout_minutes, None) + + +class GroupsTestCase(unittest.TestCase): + def setUp(self): + self.groupdb = GroupMappingDatabase(os.path.join(DATADIR, "group_mapping.tdb")) + + def tearDown(self): + self.groupdb.close() + + def test_group_length(self): + self.assertEquals(13, len(list(self.groupdb.groupsids()))) + + def test_get_group(self): + self.assertEquals((-1, 5L, 'Administrators', ''), self.groupdb.get_group("S-1-5-32-544")) + + def test_groupsids(self): + sids = list(self.groupdb.groupsids()) + self.assertTrue("S-1-5-32-544" in sids) + + def test_alias_length(self): + self.assertEquals(0, len(list(self.groupdb.aliases()))) + + +class SecretsDbTestCase(unittest.TestCase): + def setUp(self): + self.secretsdb = SecretsDatabase(os.path.join(DATADIR, "secrets.tdb")) + + def tearDown(self): + self.secretsdb.close() + + def test_get_sid(self): + self.assertTrue(self.secretsdb.get_sid("BEDWYR") is not None) + + +class TdbSamTestCase(unittest.TestCase): + def setUp(self): + self.samdb = TdbSam(os.path.join(DATADIR, "passdb.tdb")) + + def tearDown(self): + self.samdb.close() + + def test_usernames(self): + self.assertEquals(3, len(list(self.samdb.usernames()))) + + def test_getuser(self): + user = SAMUser("root") + user.logoff_time = 2147483647 + user.kickoff_time = 2147483647 + user.pass_can_change_time = 1125418267 + user.username = "root" + user.uid = None + user.lm_password = 'U)\x02\x03\x1b\xed\xe9\xef\xaa\xd3\xb45\xb5\x14\x04\xee' + user.nt_password = '\x87\x8d\x80\x14`l\xda)gzD\xef\xa15?\xc7' + user.acct_ctrl = 16 + user.pass_last_set_time = 1125418267 + user.fullname = "root" + user.nt_username = "" + user.logoff_time = 2147483647 + user.acct_desc = "" + user.group_rid = 1001 + user.logon_count = 0 + user.bad_password_count = 0 + user.domain = "BEDWYR" + user.munged_dial = "" + user.workstations = "" + user.user_rid = 1000 + user.kickoff_time = 2147483647 + user.logoff_time = 2147483647 + user.unknown_6 = 1260L + user.logon_divs = 0 + user.hours = [True for i in range(168)] + other = self.samdb["root"] + for name in other.__dict__: + if other.__dict__[name] != user.__dict__[name]: + print "%s: %r != %r" % (name, other.__dict__[name], user.__dict__[name]) + self.assertEquals(user, other) + + +class WinsDatabaseTestCase(unittest.TestCase): + def setUp(self): + self.winsdb = WinsDatabase(os.path.join(DATADIR, "wins.dat")) + + def test_length(self): + self.assertEquals(22, len(self.winsdb)) + + def test_first_entry(self): + self.assertEqual((1124185120, ["192.168.1.5"], 0x64), self.winsdb["ADMINISTRATOR#03"]) + + def tearDown(self): + self.winsdb.close() + + +class SmbpasswdTestCase(unittest.TestCase): + def setUp(self): + self.samdb = SmbpasswdFile(os.path.join(DATADIR, "smbpasswd")) + + def test_length(self): + self.assertEquals(3, len(self.samdb)) + + def test_get_user(self): + user = SAMUser("rootpw") + user.lm_password = "552902031BEDE9EFAAD3B435B51404EE" + user.nt_password = "878D8014606CDA29677A44EFA1353FC7" + user.acct_ctrl = ACB_NORMAL + user.pass_last_set_time = int(1125418267) + user.uid = 0 + self.assertEquals(user, self.samdb["rootpw"]) + + def tearDown(self): + self.samdb.close() + + +class IdmapDbTestCase(unittest.TestCase): + def setUp(self): + self.idmapdb = IdmapDatabase(os.path.join(DATADIR, "winbindd_idmap.tdb")) + + def test_user_hwm(self): + self.assertEquals(10000, self.idmapdb.get_user_hwm()) + + def test_group_hwm(self): + self.assertEquals(10002, self.idmapdb.get_group_hwm()) + + def test_uids(self): + self.assertEquals(1, len(list(self.idmapdb.uids()))) + + def test_gids(self): + self.assertEquals(3, len(list(self.idmapdb.gids()))) + + def test_get_user_sid(self): + self.assertEquals("S-1-5-21-58189338-3053988021-627566699-501", self.idmapdb.get_user_sid(65534)) + + def test_get_group_sid(self): + self.assertEquals("S-1-5-21-2447931902-1787058256-3961074038-3007", self.idmapdb.get_group_sid(10001)) + + def tearDown(self): + self.idmapdb.close() + + +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(unittest.TestCase): + def test_init(self): + file = ParamFile() + self.assertTrue(file is not None) + + def test_add_section(self): + file = ParamFile() + file.add_section("global") + self.assertTrue(file["global"] is not None) + + def test_set_param_string(self): + file = ParamFile() + file.add_section("global") + file.set_string("data", "bar") + self.assertEquals("bar", file.get_string("data")) + + def test_get_section(self): + file = ParamFile() + self.assertEquals(None, file.get_section("unknown")) + self.assertRaises(KeyError, lambda: file["unknown"]) diff --git a/source4/scripting/python/samba/tests/samdb.py b/source4/scripting/python/samba/tests/samdb.py new file mode 100644 index 0000000000..d0b95cf542 --- /dev/null +++ b/source4/scripting/python/samba/tests/samdb.py @@ -0,0 +1,97 @@ +#!/usr/bin/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 +import os +from samba.provision import setup_samdb, guess_names, setup_templatesdb, make_smbconf, find_setup_dir +from samba.samdb import SamDB +from samba.tests import TestCaseInTempDir +from samba.dcerpc import security +from unittest import TestCase +import uuid +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()) + domaindn = "DC=COM,DC=EXAMPLE" + self.domaindn = domaindn + configdn = "CN=Configuration," + domaindn + schemadn = "CN=Schema," + configdn + domainguid = str(uuid.uuid4()) + policyguid = str(uuid.uuid4()) + creds = Credentials() + creds.set_anonymous() + domainsid = security.random_sid() + hostguid = str(uuid.uuid4()) + path = os.path.join(self.tempdir, "samdb.ldb") + session_info = system_session() + + hostname="foo" + domain="EXAMPLE" + dnsdomain="example.com" + serverrole="domain controller" + + smbconf = os.path.join(self.tempdir, "smb.conf") + make_smbconf(smbconf, self.setup_path, hostname, domain, dnsdomain, + serverrole, self.tempdir) + + self.lp = param.LoadParm() + self.lp.load(smbconf) + + names = guess_names(lp=self.lp, hostname=hostname, + domain=domain, dnsdomain=dnsdomain, + serverrole=serverrole, + domaindn=self.domaindn, configdn=configdn, + schemadn=schemadn) + setup_templatesdb(os.path.join(self.tempdir, "templates.ldb"), + self.setup_path, session_info=session_info, + credentials=creds, lp=self.lp) + self.samdb = setup_samdb(path, self.setup_path, session_info, creds, + self.lp, names, + lambda x: None, domainsid, + "# no aci", domainguid, + policyguid, False, "secret", + "secret", "secret", invocationid, + "secret", "domain controller") + + def tearDown(self): + for f in ['templates.ldb', 'schema.ldb', 'configuration.ldb', + 'users.ldb', 'samdb.ldb', 'smb.conf']: + os.remove(os.path.join(self.tempdir, f)) + super(SamDBTestCase, self).tearDown() + + +class SamDBTests(SamDBTestCase): + """Tests for the SamDB implementation.""" + + def test_add_foreign(self): + self.samdb.add_foreign(self.domaindn, "S-1-5-7", "Somedescription") + diff --git a/source4/scripting/python/samba/tests/upgrade.py b/source4/scripting/python/samba/tests/upgrade.py new file mode 100644 index 0000000000..17ebfa7bf5 --- /dev/null +++ b/source4/scripting/python/samba/tests/upgrade.py @@ -0,0 +1,37 @@ +#!/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/>. +# + +from samba import Ldb +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) + } + import_wins(self.ldb, winsdb) + + self.assertEquals(['name=FOO,type=0x20'], + [str(m.dn) for m in self.ldb.search(expression="(objectClass=winsRecord)")]) + + def test_version(self): + import_wins(self.ldb, {}) + self.assertEquals("VERSION", + str(self.ldb.search(expression="(objectClass=winsMaxVersion)")[0]["cn"])) diff --git a/source4/scripting/python/samba/torture/pytorture b/source4/scripting/python/samba/torture/pytorture new file mode 100755 index 0000000000..e0123447e8 --- /dev/null +++ b/source4/scripting/python/samba/torture/pytorture @@ -0,0 +1,51 @@ +#!/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 new file mode 100644 index 0000000000..a75385e079 --- /dev/null +++ b/source4/scripting/python/samba/torture/spoolss.py @@ -0,0 +1,437 @@ +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 new file mode 100755 index 0000000000..15c6dc1a76 --- /dev/null +++ b/source4/scripting/python/samba/torture/torture_samr.py @@ -0,0 +1,221 @@ +#!/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 new file mode 100755 index 0000000000..7f97caf6cb --- /dev/null +++ b/source4/scripting/python/samba/torture/torture_tdb.py @@ -0,0 +1,90 @@ +#!/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 new file mode 100755 index 0000000000..eb60b9847e --- /dev/null +++ b/source4/scripting/python/samba/torture/winreg.py @@ -0,0 +1,165 @@ +#!/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 new file mode 100644 index 0000000000..0c83604e82 --- /dev/null +++ b/source4/scripting/python/samba/upgrade.py @@ -0,0 +1,437 @@ +#!/usr/bin/python +# +# backend code for upgrading from Samba3 +# Copyright Jelmer Vernooij 2005-2007 +# Released under the GNU GPL v3 or later +# + +"""Support code for upgrading from Samba 3 to Samba 4.""" + +__docformat__ = "restructuredText" + +from provision import findnss, provision, FILL_DRS +import grp +import ldb +import time +import pwd +import uuid +import registry +from samba import Ldb +from samba.samdb import SamDB + +def import_sam_policy(samldb, samba3_policy, domaindn): + """Import a Samba 3 policy database.""" + samldb.modify_ldif(""" +dn: %s +changetype: modify +replace: minPwdLength +minPwdLength: %d +pwdHistoryLength: %d +minPwdAge: %d +maxPwdAge: %d +lockoutDuration: %d +samba3ResetCountMinutes: %d +samba3UserMustLogonToChangePassword: %d +samba3BadLockoutMinutes: %d +samba3DisconnectTime: %d + +""" % (dn, policy.min_password_length, + policy.password_history, policy.minimum_password_age, + policy.maximum_password_age, policy.lockout_duration, + policy.reset_count_minutes, policy.user_must_logon_to_change_password, + policy.bad_lockout_minutes, policy.disconnect_time)) + + +def import_sam_account(samldb,acc,domaindn,domainsid): + """Import a Samba 3 SAM account. + + :param samldb: Samba 4 SAM Database handle + :param acc: Samba 3 account + :param domaindn: Domain DN + :param domainsid: Domain SID.""" + if acc.nt_username is None or acc.nt_username == "": + acc.nt_username = acc.username + + if acc.fullname is None: + try: + acc.fullname = pwd.getpwnam(acc.username)[4].split(",")[0] + except KeyError: + pass + + if acc.fullname is None: + acc.fullname = acc.username + + assert acc.fullname is not None + assert acc.nt_username is not None + + samldb.add({ + "dn": "cn=%s,%s" % (acc.fullname, domaindn), + "objectClass": ["top", "user"], + "lastLogon": str(acc.logon_time), + "lastLogoff": str(acc.logoff_time), + "unixName": acc.username, + "sAMAccountName": acc.nt_username, + "cn": acc.nt_username, + "description": acc.acct_desc, + "primaryGroupID": str(acc.group_rid), + "badPwdcount": str(acc.bad_password_count), + "logonCount": str(acc.logon_count), + "samba3Domain": acc.domain, + "samba3DirDrive": acc.dir_drive, + "samba3MungedDial": acc.munged_dial, + "samba3Homedir": acc.homedir, + "samba3LogonScript": acc.logon_script, + "samba3ProfilePath": acc.profile_path, + "samba3Workstations": acc.workstations, + "samba3KickOffTime": str(acc.kickoff_time), + "samba3BadPwdTime": str(acc.bad_password_time), + "samba3PassLastSetTime": str(acc.pass_last_set_time), + "samba3PassCanChangeTime": str(acc.pass_can_change_time), + "samba3PassMustChangeTime": str(acc.pass_must_change_time), + "objectSid": "%s-%d" % (domainsid, acc.user_rid), + "lmPwdHash:": acc.lm_password, + "ntPwdHash:": acc.nt_password, + }) + + +def import_sam_group(samldb, sid, gid, sid_name_use, nt_name, comment, domaindn): + """Upgrade a SAM group. + + :param samldb: SAM database. + :param gid: Group GID + :param sid_name_use: SID name use + :param nt_name: NT Group Name + :param comment: NT Group Comment + :param domaindn: Domain DN + """ + + if sid_name_use == 5: # Well-known group + return None + + if nt_name in ("Domain Guests", "Domain Users", "Domain Admins"): + return None + + if gid == -1: + gr = grp.getgrnam(nt_name) + else: + gr = grp.getgrgid(gid) + + if gr is None: + unixname = "UNKNOWN" + else: + 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, + "objectSid": sid, + "unixName": unixname, + "samba3SidNameUse": str(sid_name_use) + }) + + +def import_idmap(samdb,samba3_idmap,domaindn): + """Import idmap data. + + :param samdb: SamDB handle. + :param samba3_idmap: Samba 3 IDMAP database to import from + :param domaindn: Domain DN. + """ + samdb.add({ + "dn": domaindn, + "userHwm": str(samba3_idmap.get_user_hwm()), + "groupHwm": str(samba3_idmap.get_group_hwm())}) + + for uid in samba3_idmap.uids(): + samdb.add({"dn": "SID=%s,%s" % (samba3_idmap.get_user_sid(uid), domaindn), + "SID": samba3_idmap.get_user_sid(uid), + "type": "user", + "unixID": str(uid)}) + + for gid in samba3_idmap.uids(): + samdb.add({"dn": "SID=%s,%s" % (samba3_idmap.get_group_sid(gid), domaindn), + "SID": samba3_idmap.get_group_sid(gid), + "type": "group", + "unixID": str(gid)}) + + +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 + """ + version_id = 0 + + for (name, (ttl, ips, nb_flags)) in samba3_winsdb.items(): + version_id+=1 + + type = int(name.split("#", 1)[1], 16) + + if type == 0x1C: + rType = 0x2 + elif type & 0x80: + if len(ips) > 1: + rType = 0x2 + else: + rType = 0x1 + else: + if len(ips) > 1: + rType = 0x3 + else: + rType = 0x0 + + if ttl > time.time(): + rState = 0x0 # active + else: + rState = 0x1 # released + + nType = ((nb_flags & 0x60)>>5) + + samba4_winsdb.add({"dn": "name=%s,type=0x%s" % tuple(name.split("#")), + "type": name.split("#")[1], + "name": name.split("#")[0], + "objectClass": "winsRecord", + "recordType": str(rType), + "recordState": str(rState), + "nodeType": str(nType), + "expireTime": ldb.timestring(ttl), + "isStatic": "0", + "versionID": str(version_id), + "address": ips}) + + samba4_winsdb.add({"dn": "cn=VERSION", + "cn": "VERSION", + "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. + + :param samdb: SAM Database. + :param ldapurl: Samba 3 LDAP URL + """ + samdb.modify_ldif(""" +dn: @MODULES +changetype: modify +replace: @LIST +@LIST: samldb,operational,objectguid,rdn_name,samba3sam +""") + + samdb.add({"dn": "@MAP=samba3sam", "@MAP_URL": ldapurl}) + + +smbconf_keep = [ + "dos charset", + "unix charset", + "display charset", + "comment", + "path", + "directory", + "workgroup", + "realm", + "netbios name", + "netbios aliases", + "netbios scope", + "server string", + "interfaces", + "bind interfaces only", + "security", + "auth methods", + "encrypt passwords", + "null passwords", + "obey pam restrictions", + "password server", + "smb passwd file", + "private dir", + "passwd chat", + "password level", + "lanman auth", + "ntlm auth", + "client NTLMv2 auth", + "client lanman auth", + "client plaintext auth", + "read only", + "hosts allow", + "hosts deny", + "log level", + "debuglevel", + "log file", + "smb ports", + "large readwrite", + "max protocol", + "min protocol", + "unicode", + "read raw", + "write raw", + "disable netbios", + "nt status support", + "announce version", + "announce as", + "max mux", + "max xmit", + "name resolve order", + "max wins ttl", + "min wins ttl", + "time server", + "unix extensions", + "use spnego", + "server signing", + "client signing", + "max connections", + "paranoid server security", + "socket options", + "strict sync", + "max print jobs", + "printable", + "print ok", + "printer name", + "printer", + "map system", + "map hidden", + "map archive", + "preferred master", + "prefered master", + "local master", + "browseable", + "browsable", + "wins server", + "wins support", + "csc policy", + "strict locking", + "preload", + "auto services", + "lock dir", + "lock directory", + "pid directory", + "socket address", + "copy", + "include", + "available", + "volume", + "fstype", + "panic action", + "msdfs root", + "host msdfs", + "winbind separator"] + +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 + kept in the new configuration as "samba3:<name>" + """ + data = oldconf.data() + newconf = param_init() + + for s in data: + for p in data[s]: + keep = False + for k in smbconf_keep: + if smbconf_keep[k] == p: + keep = True + break + + if keep: + newconf.set(s, p, oldconf.get(s, p)) + elif mark: + newconf.set(s, "samba3:"+p, oldconf.get(s,p)) + + return newconf + +SAMBA3_PREDEF_NAMES = { + 'HKLM': registry.HKEY_LOCAL_MACHINE, +} + +def import_registry(samba4_registry, samba3_regdb): + """Import a Samba 3 registry database into the Samba 4 registry. + + :param samba4_registry: Samba 4 registry handle. + :param samba3_regdb: Samba 3 registry database handle. + """ + def ensure_key_exists(keypath): + (predef_name, keypath) = keypath.split("/", 1) + predef_id = SAMBA3_PREDEF_NAMES[predef_name] + keypath = keypath.replace("/", "\\") + return samba4_registry.create_key(predef_id, keypath) + + for key in samba3_regdb.keys(): + key_handle = ensure_key_exists(key) + for subkey in samba3_regdb.subkeys(key): + ensure_key_exists(subkey) + for (value_name, (value_type, value_data)) in samba3_regdb.values(key).items(): + key_handle.set_value(value_name, value_type, value_data) + + diff --git a/source4/scripting/python/uuidmodule.c b/source4/scripting/python/uuidmodule.c new file mode 100644 index 0000000000..98ef9adaa9 --- /dev/null +++ b/source4/scripting/python/uuidmodule.c @@ -0,0 +1,58 @@ +/* + Unix SMB/CIFS implementation. + Samba utility functions + 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/>. +*/ + +#include "includes.h" +#include <Python.h> +#include "librpc/ndr/libndr.h" + +static PyObject *uuid_random(PyObject *self, PyObject *args) +{ + struct GUID guid; + PyObject *pyobj; + char *str; + + if (!PyArg_ParseTuple(args, "")) + return NULL; + + guid = GUID_random(); + + str = GUID_string(NULL, &guid); + if (str == NULL) { + PyErr_SetString(PyExc_TypeError, "can't convert uuid to string"); + return NULL; + } + + pyobj = PyString_FromString(str); + + talloc_free(str); + + return pyobj; +} + +static PyMethodDef methods[] = { + { "uuid4", (PyCFunction)uuid_random, METH_VARARGS, NULL}, + { NULL, NULL } +}; + +void inituuid(void) +{ + PyObject *mod = Py_InitModule3("uuid", methods, "UUID helper routines"); + if (mod == NULL) + return; +} |