From f0ac40d43a8942ddf12395ffc88f6f606958ddee Mon Sep 17 00:00:00 2001 From: Noel Power Date: Thu, 14 Dec 2017 11:32:23 +0000 Subject: [PATCH 1/2] s4/libcli: python3 port for smb module Signed-off-by: Noel Power --- source4/libcli/pysmb.c | 45 +++++++++++++++++++++++++++++--------------- source4/libcli/wscript_build | 14 ++++++++------ 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/source4/libcli/pysmb.c b/source4/libcli/pysmb.c index 10ab54a9338..a370ab2a98e 100644 --- a/source4/libcli/pysmb.c +++ b/source4/libcli/pysmb.c @@ -18,6 +18,7 @@ */ #include +#include "python/py3compat.h" #include #include #include "includes.h" @@ -115,7 +116,7 @@ static PyObject * py_smb_loadfile(PyObject *self, PyObject *args) status = smb_composite_loadfile(spdata->tree, pytalloc_get_mem_ctx(self), &io); PyErr_NTSTATUS_IS_ERR_RAISE(status); - return Py_BuildValue("s#", io.out.data, io.out.size); + return Py_BuildValue(PYARG_BYTES_LEN, io.out.data, io.out.size); } /* @@ -125,17 +126,18 @@ static PyObject * py_smb_savefile(PyObject *self, PyObject *args) { struct smb_composite_savefile io; const char *filename; - char *data; + char *data = NULL; + Py_ssize_t size = 0; NTSTATUS status; struct smb_private_data *spdata; - if (!PyArg_ParseTuple(args, "ss:savefile", &filename, &data)) { + if (!PyArg_ParseTuple(args, "s"PYARG_BYTES_LEN":savefile", &filename, &data, &size )) { return NULL; } io.in.fname = filename; io.in.data = (unsigned char *)data; - io.in.size = strlen(data); + io.in.size = size; spdata = pytalloc_get_ptr(self); status = smb_composite_savefile(spdata->tree, &io); @@ -157,11 +159,11 @@ static void py_smb_list_callback(struct clilist_file_info *f, const char *mask, dict = PyDict_New(); if(dict) { - PyDict_SetItemString(dict, "name", PyString_FromString(f->name)); + PyDict_SetItemString(dict, "name", PyStr_FromString(f->name)); /* Windows does not always return short_name */ if (f->short_name) { - PyDict_SetItemString(dict, "short_name", PyString_FromString(f->short_name)); + PyDict_SetItemString(dict, "short_name", PyStr_FromString(f->short_name)); } else { PyDict_SetItemString(dict, "short_name", Py_None); } @@ -524,11 +526,13 @@ static PyObject *py_close_file(PyObject *self, PyObject *args, PyObject *kwargs) static PyMethodDef py_smb_methods[] = { { "loadfile", py_smb_loadfile, METH_VARARGS, - "loadfile(path) -> file contents as a string\n\n \ - Read contents of a file." }, + "loadfile(path) -> file contents as a " + PY_DESC_PY3_BYTES + "\n\n Read contents of a file." }, { "savefile", py_smb_savefile, METH_VARARGS, - "savefile(path, str) -> None\n\n \ - Write string str to file." }, + "savefile(path, str) -> None\n\n Write " + PY_DESC_PY3_BYTES + " str to file." }, { "list", (PyCFunction)py_smb_list, METH_VARARGS|METH_KEYWORDS, "list(path) -> directory contents as a dictionary\n\n \ List contents of a directory. The keys are, \n \ @@ -651,17 +655,27 @@ static PyTypeObject PySMB = { }; -void initsmb(void) +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + .m_name = "smb", + .m_doc = "SMB File I/O support", + .m_size = -1, + .m_methods = NULL, +}; + +void initsmb(void); + +MODULE_INIT_FUNC(smb) { - PyObject *m; + PyObject *m = NULL; if (pytalloc_BaseObject_PyType_Ready(&PySMB) < 0) { - return; + return m; } - m = Py_InitModule3("smb", NULL, "SMB File I/O support"); + m = PyModule_Create(&moduledef); if (m == NULL) { - return; + return m; } Py_INCREF(&PySMB); @@ -685,4 +699,5 @@ void initsmb(void) ADD_FLAGS(FILE_ATTRIBUTE_NONINDEXED); ADD_FLAGS(FILE_ATTRIBUTE_ENCRYPTED); ADD_FLAGS(FILE_ATTRIBUTE_ALL_MASK); + return m; } diff --git a/source4/libcli/wscript_build b/source4/libcli/wscript_build index 38a8f4e0718..a8863292ad7 100644 --- a/source4/libcli/wscript_build +++ b/source4/libcli/wscript_build @@ -31,12 +31,14 @@ bld.SAMBA_SUBSYSTEM('LIBCLI_SMB_COMPOSITE', private_headers='smb_composite/smb_composite.h', ) -bld.SAMBA_PYTHON('pysmb', - source='pysmb.c', - deps='LIBCLI_SMB_COMPOSITE LIBCLI_SMB2 tevent-util pyparam_util pytalloc-util', - public_deps='cli_composite samba-credentials gensec LIBCLI_RESOLVE tevent param_options', - realname='samba/smb.so' - ) + +for env in bld.gen_python_environments(): + bld.SAMBA_PYTHON('pysmb', + source='pysmb.c', + deps='LIBCLI_SMB_COMPOSITE LIBCLI_SMB2 tevent-util pyparam_util pytalloc-util', + public_deps='cli_composite samba-credentials gensec LIBCLI_RESOLVE tevent param_options', + realname='samba/smb.so' + ) bld.SAMBA_SUBSYSTEM('LIBCLI_DGRAM', source='dgram/dgramsocket.c dgram/mailslot.c dgram/netlogon.c dgram/browse.c', From d16816d932f5205c1750b4e4f9752d40d10abf96 Mon Sep 17 00:00:00 2001 From: David Mulder Date: Fri, 9 Feb 2018 08:42:18 -0700 Subject: [PATCH 2/2] python: create test for pysmb module Signed-off-by: David Mulder --- python/samba/tests/smb.py | 89 +++++++++++++++++++++++++++++++++++++++++++++++ source4/selftest/tests.py | 1 + 2 files changed, 90 insertions(+) create mode 100644 python/samba/tests/smb.py diff --git a/python/samba/tests/smb.py b/python/samba/tests/smb.py new file mode 100644 index 00000000000..4df83233357 --- /dev/null +++ b/python/samba/tests/smb.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +# Unix SMB/CIFS implementation. Tests for smb manipulation +# Copyright (C) David Mulder 2018 +# +# 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 . + +import samba, os, random, sys +from samba import smb + +PY3 = sys.version_info[0] == 3 +addom = 'addom.samba.example.com/' +test_contents = 'abcd'*256 +utf_contents = u'Süßigkeiten Äpfel '*128 +test_literal_bytes_embed_nulls = b'\xff\xfe\x14\x61\x00\x00\x62\x63\x64'*256 +binary_contents = b'\xff\xfe' +binary_contents = binary_contents + "Hello cruel world of python3".encode('utf8')*128 +test_dir = os.path.join(addom, 'testing_%d' % random.randint(0,0xFFFF)) +test_file = os.path.join(test_dir, 'testing').replace('/', '\\') + +class SMBTests(samba.tests.TestCase): + def setUp(self): + super(SMBTests, self).setUp() + self.server = os.environ["SERVER"] + creds = self.insta_creds(template=self.get_credentials()) + self.conn = smb.SMB(self.server, + "sysvol", + lp=self.get_loadparm(), + creds=creds) + self.conn.mkdir(test_dir) + + def tearDown(self): + super(SMBTests, self).tearDown() + try: + self.conn.deltree(test_dir) + except: + pass + + def test_list(self): + ls = [f['name'] for f in self.conn.list(addom)] + self.assertIn('scripts', ls, + msg='"scripts" directory not found in sysvol') + self.assertIn('Policies',ls, + msg='"Policies" directory not found in sysvol') + + def test_save_load_text(self): + + self.conn.savefile(test_file, test_contents.encode('utf8')) + + contents = self.conn.loadfile(test_file) + self.assertEquals(contents.decode('utf8'), test_contents, + msg='contents of test file did not match what was written') + + # with python2 this will save/load str type (with embedded nulls) + # with python3 this will save/load bytes type + def test_save_load_string_bytes(self): + self.conn.savefile(test_file, test_literal_bytes_embed_nulls) + + contents = self.conn.loadfile(test_file) + self.assertEquals(contents, test_literal_bytes_embed_nulls, + msg='contents of test file did not match what was written') + + # python3 only this will save/load unicode + def test_save_load_utfcontents(self): + if PY3: + self.conn.savefile(test_file, utf_contents.encode('utf8')) + + contents = self.conn.loadfile(test_file) + self.assertEquals(contents.decode('utf8'), utf_contents, + msg='contents of test file did not match what was written') + + # with python2 this will save/load str type + # with python3 this will save/load bytes type + def test_save_binary_contents(self): + self.conn.savefile(test_file, binary_contents); + + contents = self.conn.loadfile(test_file) + self.assertEquals(contents, binary_contents, + msg='contents of test file did not match what was written') diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py index 700decd8b9d..941647a21e1 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -620,6 +620,7 @@ def planoldpythontestsuite(env, module, name=None, extra_path=[], environ={}, ex planoldpythontestsuite("nt4_dc", "samba.tests.netbios", extra_args=['-U"$USERNAME%$PASSWORD"'], py3_compatible=True) planoldpythontestsuite("ad_dc:local", "samba.tests.gpo", extra_args=['-U"$USERNAME%$PASSWORD"'], py3_compatible=True) planoldpythontestsuite("ad_dc:local", "samba.tests.dckeytab", extra_args=['-U"$USERNAME%$PASSWORD"'], py3_compatible=True) +planoldpythontestsuite("ad_dc:local", "samba.tests.smb", extra_args=['-U"$USERNAME%$PASSWORD"'], py3_compatible=True) planoldpythontestsuite("ad_dc_ntvfs:local", "samba.tests.dcerpc.registry", extra_args=['-U"$USERNAME%$PASSWORD"']) planoldpythontestsuite("ad_dc_ntvfs", "samba.tests.dcerpc.dnsserver", extra_args=['-U"$USERNAME%$PASSWORD"'])