From 0dfadbdeb59ffecd5e4dcb9934956606d3a5ae43 Mon Sep 17 00:00:00 2001 From: Joe Guo Date: Wed, 13 Jun 2018 10:39:57 +1200 Subject: [PATCH 1/9] pysmb: add py_smb_unlink Add unlink api to delete a file with a smb connection. Signed-off-by: Joe Guo --- source4/libcli/pysmb.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/source4/libcli/pysmb.c b/source4/libcli/pysmb.c index 5bb3807ab76..a53e30bcf91 100644 --- a/source4/libcli/pysmb.c +++ b/source4/libcli/pysmb.c @@ -258,6 +258,27 @@ static PyObject *py_smb_rmdir(PyObject *self, PyObject *args) Py_RETURN_NONE; } + +/* + * Remove a file + */ +static PyObject *py_smb_unlink(PyObject *self, PyObject *args) +{ + NTSTATUS status; + const char *filename; + struct smb_private_data *spdata; + + if (!PyArg_ParseTuple(args, "s:unlink", &filename)) { + return NULL; + } + + spdata = pytalloc_get_ptr(self); + status = smbcli_unlink(spdata->tree, filename); + PyErr_NTSTATUS_IS_ERR_RAISE(status); + + Py_RETURN_NONE; +} + /* * Remove a directory and all its contents */ @@ -551,6 +572,9 @@ FILE_ATTRIBUTE_ARCHIVE\n\n \ { "rmdir", py_smb_rmdir, METH_VARARGS, "rmdir(path) -> None\n\n \ Delete a directory." }, + { "unlink", py_smb_unlink, METH_VARARGS, + "unlink(path) -> None\n\n \ + Delete a file." }, { "deltree", py_smb_deltree, METH_VARARGS, "deltree(path) -> None\n\n \ Delete a directory and all its contents." }, From 2ac3e0a40101f188555ae059a28cd00704d1c11f Mon Sep 17 00:00:00 2001 From: Joe Guo Date: Fri, 1 Jun 2018 13:40:42 +1200 Subject: [PATCH 2/9] pysmbd: add py_smbd_mkdir Add mkdir for smbd API. Signed-off-by: Joe Guo --- source3/smbd/pysmbd.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/source3/smbd/pysmbd.c b/source3/smbd/pysmbd.c index daaf95cb665..9ed1cabc253 100644 --- a/source3/smbd/pysmbd.c +++ b/source3/smbd/pysmbd.c @@ -711,6 +711,48 @@ static PyObject *py_smbd_get_sys_acl(PyObject *self, PyObject *args, PyObject *k return py_acl; } +static PyObject *py_smbd_mkdir(PyObject *self, PyObject *args, PyObject *kwargs) +{ + const char * const kwnames[] = { "fname", "service", NULL }; + char *fname, *service = NULL; + TALLOC_CTX *tmp_ctx = talloc_stackframe(); + struct connection_struct *conn = NULL; + struct smb_filename *smb_fname = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|z", discard_const_p(char *, kwnames), + &fname, &service)) { + TALLOC_FREE(tmp_ctx); + return NULL; + } + + conn = get_conn(tmp_ctx, service); + if (!conn) { + TALLOC_FREE(tmp_ctx); + return NULL; + } + + smb_fname = synthetic_smb_fname(talloc_tos(), + fname, + NULL, + NULL, + lp_posix_pathnames()? SMB_FILENAME_POSIX_PATH : 0); + + if (smb_fname == NULL) { + TALLOC_FREE(tmp_ctx); + return NULL; + } + + + if (SMB_VFS_MKDIR(conn, smb_fname, 00755) == -1) { + printf("mkdir error=%d (%s)\n", errno, strerror(errno)); + TALLOC_FREE(tmp_ctx); + return NULL; + } + + TALLOC_FREE(tmp_ctx); + Py_RETURN_NONE; +} + static PyMethodDef py_smbd_methods[] = { { "have_posix_acls", (PyCFunction)py_smbd_have_posix_acls, METH_NOARGS, @@ -736,6 +778,9 @@ static PyMethodDef py_smbd_methods[] = { { "unlink", (PyCFunction)py_smbd_unlink, METH_VARARGS|METH_KEYWORDS, NULL }, + { "mkdir", + (PyCFunction)py_smbd_mkdir, METH_VARARGS|METH_KEYWORDS, + NULL }, { NULL } }; From a29744d5bb409bb09951e0bd50323b4802963b6a Mon Sep 17 00:00:00 2001 From: Joe Guo Date: Fri, 1 Jun 2018 13:45:25 +1200 Subject: [PATCH 3/9] pysmbd: extract init_files_struct function Extract initialization code from set_nt_acl_conn for reuse. Signed-off-by: Joe Guo --- source3/smbd/pysmbd.c | 80 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 33 deletions(-) diff --git a/source3/smbd/pysmbd.c b/source3/smbd/pysmbd.c index 9ed1cabc253..af724329fd1 100644 --- a/source3/smbd/pysmbd.c +++ b/source3/smbd/pysmbd.c @@ -37,6 +37,13 @@ extern const struct generic_mapping file_generic_mapping; #undef DBGC_CLASS #define DBGC_CLASS DBGC_ACLS +#ifdef O_DIRECTORY +#define DIRECTORY_FLAGS O_RDONLY|O_DIRECTORY +#else +/* POSIX allows us to open a directory with O_RDONLY. */ +#define DIRECTORY_FLAGS O_RDONLY +#endif + static int conn_free_wrapper(connection_struct *conn) { conn_free(conn); @@ -108,16 +115,17 @@ static int set_sys_acl_conn(const char *fname, return ret; } -static NTSTATUS set_nt_acl_conn(const char *fname, - uint32_t security_info_sent, const struct security_descriptor *sd, - connection_struct *conn) + +static NTSTATUS init_files_struct(const char *fname, + struct connection_struct *conn, + int flags, + struct files_struct **_fsp) { - TALLOC_CTX *frame = talloc_stackframe(); - NTSTATUS status = NT_STATUS_OK; - files_struct *fsp; + TALLOC_CTX *frame = talloc_tos(); struct smb_filename *smb_fname = NULL; - int flags, ret; + int ret; mode_t saved_umask; + struct files_struct *fsp; fsp = talloc_zero(frame, struct files_struct); if (fsp == NULL) { @@ -139,39 +147,23 @@ static NTSTATUS set_nt_acl_conn(const char *fname, fname, lp_posix_pathnames()); if (smb_fname == NULL) { - TALLOC_FREE(frame); umask(saved_umask); return NT_STATUS_NO_MEMORY; } fsp->fsp_name = smb_fname; - -#ifdef O_DIRECTORY - flags = O_RDONLY|O_DIRECTORY; -#else - /* POSIX allows us to open a directory with O_RDONLY. */ - flags = O_RDONLY; -#endif - - fsp->fh->fd = SMB_VFS_OPEN(conn, smb_fname, fsp, O_RDWR, 00400); - if (fsp->fh->fd == -1 && errno == EISDIR) { - fsp->fh->fd = SMB_VFS_OPEN(conn, smb_fname, fsp, flags, 00400); - } + fsp->fh->fd = SMB_VFS_OPEN(conn, smb_fname, fsp, flags, 00644); if (fsp->fh->fd == -1) { - printf("open: error=%d (%s)\n", errno, strerror(errno)); - TALLOC_FREE(frame); umask(saved_umask); - return NT_STATUS_UNSUCCESSFUL; + return NT_STATUS_INVALID_PARAMETER; } ret = SMB_VFS_FSTAT(fsp, &smb_fname->st); if (ret == -1) { /* If we have an fd, this stat should succeed. */ - DEBUG(0,("Error doing fstat on open file %s " - "(%s)\n", - smb_fname_str_dbg(smb_fname), - strerror(errno) )); - TALLOC_FREE(frame); + DEBUG(0,("Error doing fstat on open file %s (%s)\n", + smb_fname_str_dbg(smb_fname), + strerror(errno) )); umask(saved_umask); return map_nt_error_from_unix(errno); } @@ -187,17 +179,39 @@ static NTSTATUS set_nt_acl_conn(const char *fname, fsp->sent_oplock_break = NO_BREAK_SENT; fsp->is_directory = S_ISDIR(smb_fname->st.st_ex_mode); - status = SMB_VFS_FSET_NT_ACL( fsp, security_info_sent, sd); + *_fsp = fsp; + + return NT_STATUS_OK; +} + +static NTSTATUS set_nt_acl_conn(const char *fname, + uint32_t security_info_sent, const struct security_descriptor *sd, + connection_struct *conn) +{ + struct files_struct *fsp = NULL; + NTSTATUS status = NT_STATUS_OK; + + /* first, try to open it as a file with flag O_RDWR */ + status = init_files_struct(fname, conn, O_RDWR, &fsp); + if (!NT_STATUS_IS_OK(status) && errno == EISDIR) { + /* if fail, try to open as dir */ + status = init_files_struct(fname, conn, DIRECTORY_FLAGS, &fsp); + } + + if (!NT_STATUS_IS_OK(status)) { + printf("open: error=%d (%s)\n", errno, strerror(errno)); + SMB_VFS_CLOSE(fsp); + conn_free(conn); + return status; + } + + status = SMB_VFS_FSET_NT_ACL(fsp, security_info_sent, sd); if (!NT_STATUS_IS_OK(status)) { DEBUG(0,("set_nt_acl_no_snum: fset_nt_acl returned %s.\n", nt_errstr(status))); } SMB_VFS_CLOSE(fsp); - conn_free(conn); - TALLOC_FREE(frame); - - umask(saved_umask); return status; } From a88f7a43fe9df4d5c62e03dcd3c665ee2fb963bf Mon Sep 17 00:00:00 2001 From: Joe Guo Date: Fri, 1 Jun 2018 13:48:31 +1200 Subject: [PATCH 4/9] pysmbd: add py_smbd_create_file Add create_file function to smbd API. Signed-off-by: Joe Guo --- source3/smbd/pysmbd.c | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/source3/smbd/pysmbd.c b/source3/smbd/pysmbd.c index af724329fd1..d1631571a0d 100644 --- a/source3/smbd/pysmbd.c +++ b/source3/smbd/pysmbd.c @@ -767,6 +767,41 @@ static PyObject *py_smbd_mkdir(PyObject *self, PyObject *args, PyObject *kwargs) Py_RETURN_NONE; } + +/* + Create an empty file + */ +static PyObject *py_smbd_create_file(PyObject *self, PyObject *args, PyObject *kwargs) +{ + const char * const kwnames[] = { "fname", "service", NULL }; + char *fname, *service = NULL; + TALLOC_CTX *tmp_ctx = talloc_stackframe(); + struct connection_struct *conn = NULL; + struct files_struct *fsp = NULL; + NTSTATUS status; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|z", discard_const_p(char *, kwnames), + &fname, &service)) { + TALLOC_FREE(tmp_ctx); + return NULL; + } + + conn = get_conn(tmp_ctx, service); + if (!conn) { + TALLOC_FREE(tmp_ctx); + return NULL; + } + + status = init_files_struct(fname, conn, O_CREAT|O_EXCL|O_RDWR, &fsp); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("init_files_struct failed: %s\n", nt_errstr(status))); + } + + TALLOC_FREE(tmp_ctx); + Py_RETURN_NONE; +} + + static PyMethodDef py_smbd_methods[] = { { "have_posix_acls", (PyCFunction)py_smbd_have_posix_acls, METH_NOARGS, @@ -795,6 +830,9 @@ static PyMethodDef py_smbd_methods[] = { { "mkdir", (PyCFunction)py_smbd_mkdir, METH_VARARGS|METH_KEYWORDS, NULL }, + { "create_file", + (PyCFunction)py_smbd_create_file, METH_VARARGS|METH_KEYWORDS, + NULL }, { NULL } }; From 0d67923d5ec289597361f285a3cf56406e2adb91 Mon Sep 17 00:00:00 2001 From: Joe Guo Date: Fri, 1 Jun 2018 13:50:05 +1200 Subject: [PATCH 5/9] ntacls: add function to backup and restore NTACLs 1. backup a share online from a smb connection with ntacls using pysmb API. 2. backup a share offline from service path with ntacls using pysmbd API. 3. restore from tarfile with pysmdb API. Signed-off-by: Joe Guo --- python/samba/ntacls.py | 187 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) diff --git a/python/samba/ntacls.py b/python/samba/ntacls.py index 6bb55b416e9..981c643e523 100644 --- a/python/samba/ntacls.py +++ b/python/samba/ntacls.py @@ -21,10 +21,17 @@ import os +import tarfile +import tempfile +import shutil + import samba.xattr_native, samba.xattr_tdb, samba.posix_eadb +from samba.samba3 import param as s3param from samba.dcerpc import security, xattr, idmap from samba.ndr import ndr_pack, ndr_unpack from samba.samba3 import smbd +from samba import smb + class XattrBackendError(Exception): """A generic xattr backend error.""" @@ -247,3 +254,183 @@ def dsacl2fsacl(dssddl, sid, as_sddl=True): return fdescr return fdescr.as_sddl(sid) + + +def backup_online(smb_connection, dest_tarfile_path, domain_sid_str): + """ + Backup all files and dirs with ntacl for the serive behind smb_connection. + + 1. Create a temp dir as container dir + 2. Backup all files with dir structure into container dir + 3. Generate file.NTACL files for each file and dir in contianer dir + 4. Create a tar file from container dir(without top level folder) + 5. Delete contianer dir + """ + def _get_ntacl_sddl_str(path): + """Get ntacl for path from smb connection""" + fs_sd = smb_connection.get_acl( + path, + security.SECINFO_OWNER | security.SECINFO_GROUP | + security.SECINFO_DACL, + security.SEC_FLAG_MAXIMUM_ALLOWED) + domain_sid = security.dom_sid(domain_sid_str) + return fs_sd.as_sddl(domain_sid) + + remotedir = '' # root dir + + localdir = tempfile.mkdtemp() + + r_dirs = [remotedir] + l_dirs = [localdir] + + while r_dirs: + r_dir = r_dirs.pop() + l_dir = l_dirs.pop() + + entries = smb_connection.list( + r_dir, attribs=smb.FILE_ATTRIBUTE_ALL_MASK) + for e in entries: + r_name = r_dir + '\\' + e['name'] + l_name = os.path.join(l_dir, e['name']) + + if e['attrib'] & smb.FILE_ATTRIBUTE_DIRECTORY: + r_dirs.append(r_name) + l_dirs.append(l_name) + os.mkdir(l_name) + else: + data = smb_connection.loadfile(r_name) + with open(l_name, 'w') as f: + f.write(data) + + # get ntacl for this entry and save alongside + ntacl_sddl_str = _get_ntacl_sddl_str(r_name) + with open(l_name + '.NTACL', 'w') as f: + f.write(ntacl_sddl_str) + + with tarfile.open(name=dest_tarfile_path, mode='w:gz') as tar: + for name in os.listdir(localdir): + path = os.path.join(localdir, name) + tar.add(path, arcname=name) + + shutil.rmtree(localdir) + + +def backup_offline(src_service_path, dest_tarfile_path, samdb, configfile): + """Backup files and ntacls to a tarfile for a service""" + s3conf = s3param.get_context() + s3conf.load(configfile) + # ensure we are using the right samba_dsdb passdb backend, no matter what + s3conf.set("passdb backend", "samba_dsdb:%s" % samdb.url) + + service = src_service_path.rstrip('/').rsplit('/', 1)[-1] + tempdir = tempfile.mkdtemp() + + def _get_ntacl_file(src, dst): + """Get ntacl from src and write to a .NTACL file beside dst""" + ntacl_obj = smbd.get_nt_acl( + src, + security.SECINFO_OWNER | security.SECINFO_GROUP | + security.SECINFO_DACL | security.SECINFO_SACL, + service=service) + domain_sid = security.dom_sid(samdb.get_domain_sid()) + ntacl_sddl = ntacl_obj.as_sddl(domain_sid) + ntacl_path = dst + '.NTACL' + with open(ntacl_path, 'w') as f: + f.write(ntacl_sddl) + + for dirpath, dirnames, filenames in os.walk(src_service_path): + # each dir only cares about its direct children + rel_dirpath = os.path.relpath(dirpath, start=src_service_path) + dst_dirpath = os.path.join(tempdir, rel_dirpath) + + # create sub dirs and NTACL file + for dirname in dirnames: + src = os.path.join(dirpath, dirname) + dst = os.path.join(dst_dirpath, dirname) + # mkdir with metadata + smbd.mkdir(dst, service) + _get_ntacl_file(src, dst) + + # create files and NTACL file, then copy data + for filename in filenames: + src = os.path.join(dirpath, filename) + dst = os.path.join(dst_dirpath, filename) + # create an empty file with metadata + smbd.create_file(dst, service) + _get_ntacl_file(src, dst) + + # now put data in + with open(src, 'rb') as src_file: + data = src_file.read() + with open(dst, 'wb') as dst_file: + dst_file.write(data) + + # add all files in tempdir to tarfile without a top folder + with tarfile.open(name=dest_tarfile_path, mode='w:gz') as tar: + for name in os.listdir(tempdir): + path = os.path.join(tempdir, name) + tar.add(path, arcname=name) + + shutil.rmtree(tempdir) + + +def backup_restore(src_tarfile_path, dst_service_path, samdb, configfile): + """Restore files and ntacls from a tarfile to a service""" + + s3conf = s3param.get_context() + s3conf.load(configfile) + # ensure we are using the right samba_dsdb passdb backend, no matter what + s3conf.set("passdb backend", "samba_dsdb:%s" % samdb.url) + + service = dst_service_path.rstrip('/').rsplit('/', 1)[-1] + tempdir = tempfile.mkdtemp() # src files + + with tarfile.open(src_tarfile_path) as f: + f.extractall(path=tempdir) + # e.g.: /tmp/tmpRNystY/{dir1,dir1.NTACL,...file1,file1.NTACL} + + def _set_ntacl_file(src, dst): + """Get .NTALC file beside src and set it to dst""" + ntacl_path = src + '.NTACL' + with open(ntacl_path, 'r') as f: + ntacl_sddl = f.read() + + domain_sid = security.dom_sid(samdb.get_domain_sid()) + sd = security.descriptor.from_sddl(ntacl_sddl, domain_sid) + + smbd.set_nt_acl( + dst, + security.SECINFO_OWNER | security.SECINFO_GROUP | + security.SECINFO_DACL | security.SECINFO_SACL, + sd, service) + + for dirpath, dirnames, filenames in os.walk(tempdir): + rel_dirpath = os.path.relpath(dirpath, start=tempdir) + dst_dirpath = os.path.normpath( + os.path.join(dst_service_path, rel_dirpath)) + + for dirname in dirnames: + if not dirname.endswith('.NTACL'): + src = os.path.join(dirpath, dirname) + dst = os.path.join(dst_dirpath, dirname) + if not os.path.isdir(dst): + # dst must be absolute path for smbd API + smbd.mkdir(dst, service) + _set_ntacl_file(src, dst) + + for filename in filenames: + if not filename.endswith('.NTACL'): + src = os.path.join(dirpath, filename) + dst = os.path.join(dst_dirpath, filename) + if not os.path.isfile(dst): + # dst must be absolute path for smbd API + smbd.create_file(dst, service) + _set_ntacl_file(src, dst) + + # now put data in + with open(src, 'rb') as src_file: + data = src_file.read() + with open(dst, 'wb') as dst_file: + dst_file.write(data) + + shutil.rmtree(tempdir) From bfb05afc89c1c7c72719af9055e7c34934ff165c Mon Sep 17 00:00:00 2001 From: Joe Guo Date: Wed, 13 Jun 2018 10:49:01 +1200 Subject: [PATCH 6/9] selftest: move ntacls test to source4 Then we can use the `planoldpythontestsuite` function to pass more args like credentials. Signed-off-by: Joe Guo --- selftest/tests.py | 1 - source4/selftest/tests.py | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/selftest/tests.py b/selftest/tests.py index f354bb57ef5..798addf1d1b 100644 --- a/selftest/tests.py +++ b/selftest/tests.py @@ -147,7 +147,6 @@ '$PREFIX_ABS/provision', configuration]) planpythontestsuite("none", "samba.tests.upgradeprovision", py3_compatible=True) planpythontestsuite("none", "samba.tests.xattr", py3_compatible=True) -planpythontestsuite("none", "samba.tests.ntacls", py3_compatible=True) planpythontestsuite("none", "samba.tests.policy", py3_compatible=True) planpythontestsuite("none", "samba.tests.kcc.graph", py3_compatible=True) planpythontestsuite("none", "samba.tests.kcc.graph_utils", py3_compatible=True) diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py index 8b1fb7b280a..d5b68bcf1de 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -641,6 +641,10 @@ def planoldpythontestsuite(env, module, name=None, extra_path=[], environ={}, ex "ad_dc_ntvfs:local", "samba.tests.dcerpc.registry", extra_args=['-U"$USERNAME%$PASSWORD"'], py3_compatible=True) +planoldpythontestsuite( + "ad_dc_ntvfs:local", "samba.tests.ntacls", + extra_args=['-U"$USERNAME%$PASSWORD"'], py3_compatible=True) + planoldpythontestsuite("ad_dc_ntvfs", "samba.tests.dcerpc.dnsserver", extra_args=['-U"$USERNAME%$PASSWORD"']) planoldpythontestsuite("ad_dc", "samba.tests.dcerpc.dnsserver", extra_args=['-U"$USERNAME%$PASSWORD"']) planoldpythontestsuite("chgdcpass", "samba.tests.dcerpc.raw_protocol", extra_args=['-U"$USERNAME%$PASSWORD"']) From 328c41a7bc9aafbf618b7193c9b3ec63c8e37114 Mon Sep 17 00:00:00 2001 From: Joe Guo Date: Fri, 1 Jun 2018 14:23:54 +1200 Subject: [PATCH 7/9] tests/ntacls: use global vars to make code DRY Move acl and dommain_sid to global vars so we don't repeat them in every test. Signed-off-by: Joe Guo --- python/samba/tests/ntacls.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/python/samba/tests/ntacls.py b/python/samba/tests/ntacls.py index 8cd09fbcc54..b01e0b1aeb8 100644 --- a/python/samba/tests/ntacls.py +++ b/python/samba/tests/ntacls.py @@ -24,53 +24,51 @@ from samba.tests import TestCaseInTempDir, SkipTest import os +NTACL_SDDL = "O:S-1-5-21-2212615479-2695158682-2101375467-512G:S-1-5-21-2212615479-2695158682-2101375467-513D:(A;OICI;0x001f01ff;;;S-1-5-21-2212615479-2695158682-2101375467-512)" +DOMAIN_SID = "S-1-5-21-2212615479-2695158682-2101375467" + + class NtaclsTests(TestCaseInTempDir): def test_setntacl(self): lp = LoadParm() - acl = "O:S-1-5-21-2212615479-2695158682-2101375467-512G:S-1-5-21-2212615479-2695158682-2101375467-513D:(A;OICI;0x001f01ff;;;S-1-5-21-2212615479-2695158682-2101375467-512)" open(self.tempf, 'w').write("empty") lp.set("posix:eadb",os.path.join(self.tempdir,"eadbtest.tdb")) - setntacl(lp, self.tempf, acl, "S-1-5-21-2212615479-2695158682-2101375467") + setntacl(lp, self.tempf, NTACL_SDDL, DOMAIN_SID) os.unlink(os.path.join(self.tempdir,"eadbtest.tdb")) def test_setntacl_getntacl(self): lp = LoadParm() - acl = "O:S-1-5-21-2212615479-2695158682-2101375467-512G:S-1-5-21-2212615479-2695158682-2101375467-513D:(A;OICI;0x001f01ff;;;S-1-5-21-2212615479-2695158682-2101375467-512)" open(self.tempf, 'w').write("empty") lp.set("posix:eadb",os.path.join(self.tempdir,"eadbtest.tdb")) - setntacl(lp,self.tempf,acl,"S-1-5-21-2212615479-2695158682-2101375467") + setntacl(lp, self.tempf, NTACL_SDDL, DOMAIN_SID) facl = getntacl(lp,self.tempf) anysid = security.dom_sid(security.SID_NT_SELF) - self.assertEquals(facl.as_sddl(anysid),acl) + self.assertEquals(facl.as_sddl(anysid), NTACL_SDDL) os.unlink(os.path.join(self.tempdir,"eadbtest.tdb")) def test_setntacl_getntacl_param(self): lp = LoadParm() - acl = "O:S-1-5-21-2212615479-2695158682-2101375467-512G:S-1-5-21-2212615479-2695158682-2101375467-513D:(A;OICI;0x001f01ff;;;S-1-5-21-2212615479-2695158682-2101375467-512)" open(self.tempf, 'w').write("empty") - setntacl(lp,self.tempf,acl,"S-1-5-21-2212615479-2695158682-2101375467","tdb",os.path.join(self.tempdir,"eadbtest.tdb")) + setntacl(lp, self.tempf, NTACL_SDDL, DOMAIN_SID,"tdb", os.path.join(self.tempdir,"eadbtest.tdb")) facl=getntacl(lp,self.tempf,"tdb",os.path.join(self.tempdir,"eadbtest.tdb")) domsid=security.dom_sid(security.SID_NT_SELF) - self.assertEquals(facl.as_sddl(domsid),acl) + self.assertEquals(facl.as_sddl(domsid), NTACL_SDDL) os.unlink(os.path.join(self.tempdir,"eadbtest.tdb")) def test_setntacl_invalidbackend(self): lp = LoadParm() - acl = "O:S-1-5-21-2212615479-2695158682-2101375467-512G:S-1-5-21-2212615479-2695158682-2101375467-513D:(A;OICI;0x001f01ff;;;S-1-5-21-2212615479-2695158682-2101375467-512)" open(self.tempf, 'w').write("empty") - self.assertRaises(XattrBackendError, setntacl, lp, self.tempf, acl, "S-1-5-21-2212615479-2695158682-2101375467","ttdb", os.path.join(self.tempdir,"eadbtest.tdb")) + self.assertRaises(XattrBackendError, setntacl, lp, self.tempf, NTACL_SDDL, DOMAIN_SID, "ttdb", os.path.join(self.tempdir,"eadbtest.tdb")) def test_setntacl_forcenative(self): if os.getuid() == 0: raise SkipTest("Running test as root, test skipped") lp = LoadParm() - acl = "O:S-1-5-21-2212615479-2695158682-2101375467-512G:S-1-5-21-2212615479-2695158682-2101375467-513D:(A;OICI;0x001f01ff;;;S-1-5-21-2212615479-2695158682-2101375467-512)" open(self.tempf, 'w').write("empty") lp.set("posix:eadb", os.path.join(self.tempdir,"eadbtest.tdb")) - self.assertRaises(Exception, setntacl, lp, self.tempf ,acl, - "S-1-5-21-2212615479-2695158682-2101375467","native") - + self.assertRaises(Exception, setntacl, lp, self.tempf, NTACL_SDDL, + DOMAIN_SID, "native") def setUp(self): super(NtaclsTests, self).setUp() From 8c4f48bfc4ff4101e5434d69d654e1c9be755b8a Mon Sep 17 00:00:00 2001 From: Joe Guo Date: Fri, 1 Jun 2018 14:28:43 +1200 Subject: [PATCH 8/9] tests/ntacls: fix pep8 warnings Signed-off-by: Joe Guo --- python/samba/tests/ntacls.py | 49 ++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/python/samba/tests/ntacls.py b/python/samba/tests/ntacls.py index b01e0b1aeb8..b345b283a76 100644 --- a/python/samba/tests/ntacls.py +++ b/python/samba/tests/ntacls.py @@ -18,11 +18,12 @@ """Tests for samba.ntacls.""" +import os + from samba.ntacls import setntacl, getntacl, XattrBackendError from samba.param import LoadParm from samba.dcerpc import security from samba.tests import TestCaseInTempDir, SkipTest -import os NTACL_SDDL = "O:S-1-5-21-2212615479-2695158682-2101375467-512G:S-1-5-21-2212615479-2695158682-2101375467-513D:(A;OICI;0x001f01ff;;;S-1-5-21-2212615479-2695158682-2101375467-512)" DOMAIN_SID = "S-1-5-21-2212615479-2695158682-2101375467" @@ -30,51 +31,55 @@ class NtaclsTests(TestCaseInTempDir): + def setUp(self): + super(NtaclsTests, self).setUp() + self.tempf = os.path.join(self.tempdir, "test") + open(self.tempf, 'w').write("empty") + + def tearDown(self): + os.unlink(self.tempf) + super(NtaclsTests, self).tearDown() + def test_setntacl(self): lp = LoadParm() open(self.tempf, 'w').write("empty") - lp.set("posix:eadb",os.path.join(self.tempdir,"eadbtest.tdb")) + lp.set("posix:eadb", os.path.join(self.tempdir, "eadbtest.tdb")) setntacl(lp, self.tempf, NTACL_SDDL, DOMAIN_SID) - os.unlink(os.path.join(self.tempdir,"eadbtest.tdb")) + os.unlink(os.path.join(self.tempdir, "eadbtest.tdb")) def test_setntacl_getntacl(self): lp = LoadParm() open(self.tempf, 'w').write("empty") - lp.set("posix:eadb",os.path.join(self.tempdir,"eadbtest.tdb")) + lp.set("posix:eadb", os.path.join(self.tempdir, "eadbtest.tdb")) setntacl(lp, self.tempf, NTACL_SDDL, DOMAIN_SID) - facl = getntacl(lp,self.tempf) + facl = getntacl(lp, self.tempf) anysid = security.dom_sid(security.SID_NT_SELF) self.assertEquals(facl.as_sddl(anysid), NTACL_SDDL) - os.unlink(os.path.join(self.tempdir,"eadbtest.tdb")) + os.unlink(os.path.join(self.tempdir, "eadbtest.tdb")) def test_setntacl_getntacl_param(self): lp = LoadParm() open(self.tempf, 'w').write("empty") - setntacl(lp, self.tempf, NTACL_SDDL, DOMAIN_SID,"tdb", os.path.join(self.tempdir,"eadbtest.tdb")) - facl=getntacl(lp,self.tempf,"tdb",os.path.join(self.tempdir,"eadbtest.tdb")) - domsid=security.dom_sid(security.SID_NT_SELF) + setntacl(lp, self.tempf, NTACL_SDDL, DOMAIN_SID, "tdb", + os.path.join(self.tempdir, "eadbtest.tdb")) + facl = getntacl(lp, self.tempf, "tdb", os.path.join( + self.tempdir, "eadbtest.tdb")) + domsid = security.dom_sid(security.SID_NT_SELF) self.assertEquals(facl.as_sddl(domsid), NTACL_SDDL) - os.unlink(os.path.join(self.tempdir,"eadbtest.tdb")) + os.unlink(os.path.join(self.tempdir, "eadbtest.tdb")) def test_setntacl_invalidbackend(self): lp = LoadParm() open(self.tempf, 'w').write("empty") - self.assertRaises(XattrBackendError, setntacl, lp, self.tempf, NTACL_SDDL, DOMAIN_SID, "ttdb", os.path.join(self.tempdir,"eadbtest.tdb")) + self.assertRaises(XattrBackendError, setntacl, lp, self.tempf, + NTACL_SDDL, DOMAIN_SID, "ttdb", + os.path.join(self.tempdir, "eadbtest.tdb")) def test_setntacl_forcenative(self): if os.getuid() == 0: raise SkipTest("Running test as root, test skipped") lp = LoadParm() open(self.tempf, 'w').write("empty") - lp.set("posix:eadb", os.path.join(self.tempdir,"eadbtest.tdb")) + lp.set("posix:eadb", os.path.join(self.tempdir, "eadbtest.tdb")) self.assertRaises(Exception, setntacl, lp, self.tempf, NTACL_SDDL, - DOMAIN_SID, "native") - - def setUp(self): - super(NtaclsTests, self).setUp() - self.tempf = os.path.join(self.tempdir, "test") - open(self.tempf, 'w').write("empty") - - def tearDown(self): - os.unlink(self.tempf) - super(NtaclsTests, self).tearDown() + DOMAIN_SID, "native") From cc20fda023f499703164b6f1b63fb9162bc1d21e Mon Sep 17 00:00:00 2001 From: Joe Guo Date: Wed, 13 Jun 2018 15:44:26 +1200 Subject: [PATCH 9/9] tests/ntacls: add tests for ntacls backup and restore Backup a share/service with both online and offline, restore and check. Signed-off-by: Joe Guo --- python/samba/ntacls.py | 6 +- python/samba/tests/ntacls.py | 152 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 154 insertions(+), 4 deletions(-) diff --git a/python/samba/ntacls.py b/python/samba/ntacls.py index 981c643e523..1992eb3fadf 100644 --- a/python/samba/ntacls.py +++ b/python/samba/ntacls.py @@ -299,13 +299,13 @@ def _get_ntacl_sddl_str(path): os.mkdir(l_name) else: data = smb_connection.loadfile(r_name) - with open(l_name, 'w') as f: + with open(l_name, 'wb') as f: f.write(data) # get ntacl for this entry and save alongside ntacl_sddl_str = _get_ntacl_sddl_str(r_name) - with open(l_name + '.NTACL', 'w') as f: - f.write(ntacl_sddl_str) + with open(l_name + '.NTACL', 'wb') as f: + f.write(ntacl_sddl_str.encode('utf-8')) # encode for python3 with tarfile.open(name=dest_tarfile_path, mode='w:gz') as tar: for name in os.listdir(localdir): diff --git a/python/samba/tests/ntacls.py b/python/samba/tests/ntacls.py index b345b283a76..14a8d9f382a 100644 --- a/python/samba/tests/ntacls.py +++ b/python/samba/tests/ntacls.py @@ -20,10 +20,16 @@ import os -from samba.ntacls import setntacl, getntacl, XattrBackendError +from samba.ntacls import ( + setntacl, getntacl, XattrBackendError, + backup_online, backup_offline, backup_restore +) +from samba.auth import system_session from samba.param import LoadParm from samba.dcerpc import security from samba.tests import TestCaseInTempDir, SkipTest +from samba import smb +from samba import samdb NTACL_SDDL = "O:S-1-5-21-2212615479-2695158682-2101375467-512G:S-1-5-21-2212615479-2695158682-2101375467-513D:(A;OICI;0x001f01ff;;;S-1-5-21-2212615479-2695158682-2101375467-512)" DOMAIN_SID = "S-1-5-21-2212615479-2695158682-2101375467" @@ -83,3 +89,147 @@ def test_setntacl_forcenative(self): lp.set("posix:eadb", os.path.join(self.tempdir, "eadbtest.tdb")) self.assertRaises(Exception, setntacl, lp, self.tempf, NTACL_SDDL, DOMAIN_SID, "native") + + +class NtaclsBackupRestoreTests(TestCaseInTempDir): + """ + Tests for NTACLs backup and restore. + """ + + def setUp(self): + super(NtaclsBackupRestoreTests, self).setUp() + + self.server = os.environ["SERVER"] # localdc + self.service = 'test1' # service/share to test + # root path for service + self.service_root = os.path.join( + os.environ["LOCAL_PATH"], self.service) + self.conf = os.environ['SMB_CONF_PATH'] + + self.creds = self.insta_creds(template=self.get_credentials()) + self.lp = self.get_loadparm() + + self.samdb_conn = samdb.SamDB( + url='ldap://' + self.server, session_info=system_session(), + credentials=self.creds, lp=self.lp) + + self.smb_conn = smb.SMB( + self.server, self.service, lp=self.lp, creds=self.creds) + + self.tarfile_path = '/tmp/ntacls-backup.tar.gz' + + # an example file tree + self.tree = { + 'file0.txt': 'test file0', + 'dir1': { + 'file1.txt': 'test file1', + 'dir2': {} # an empty dir in dir + }, + } + + self._delete_tarfile() + self._delete_tree() + + self._create_tree(self.tree) + self._check_tree() + + def tearDown(self): + self._delete_tarfile() + self._delete_tree() + super(NtaclsBackupRestoreTests, self).tearDown() + + def _delete_tarfile(self): + try: + os.remove(self.tarfile_path) + except OSError: + pass + + def _check_tarfile(self): + self.assertTrue(os.path.isfile(self.tarfile_path)) + + def _smb_join(self, root, name): + return root + '\\' + name if root else name + + def _create_tree(self, tree, path=''): + """Create files as defined in tree""" + for name, content in tree.items(): + if isinstance(content, str): # a file + self.smb_conn.savefile( + self._smb_join(path, name), + content.encode('utf-8')) # encode for python3 + elif isinstance(content, dict): # a dir + newpath = self._smb_join(path, name) + if not self.smb_conn.chkpath(newpath): + self.smb_conn.mkdir(newpath) + self._create_tree(content, path=newpath) + + def _get_tree(self, path=''): + """ + Get the tree structure via connection. + + self.smb_conn.list example: + + [ + { + 'attrib': 16, + 'mtime': 1528848309, + 'name': 'dir1', + 'short_name': 'dir1', + 'size': 0L + }, { + 'attrib': 32, + 'mtime': 1528848309, + 'name': 'file0.txt', + 'short_name': 'file0.txt', + 'size': 10L + } + ] + """ + tree = {} + items = self.smb_conn.list(path, attribs=smb.FILE_ATTRIBUTE_ALL_MASK) + for item in items: + name = item['name'] + if item['attrib'] == smb.FILE_ATTRIBUTE_DIRECTORY: + tree[name] = self._get_tree(path=self._smb_join(path, name)) + else: + tree[name] = self.smb_conn.loadfile( + self._smb_join(path, name)).decode('utf-8') + # decode for python3 + return tree + + def _delete_tree(self): + items = self.smb_conn.list('', attribs=smb.FILE_ATTRIBUTE_ALL_MASK) + for item in items: + name = item['name'] + if item['attrib'] == smb.FILE_ATTRIBUTE_DIRECTORY: + self.smb_conn.deltree(name) + else: + self.smb_conn.unlink(name) + + def _check_tree(self): + self.assertDictEqual(self.tree, self._get_tree()) + + def test_backup_online(self): + """ + Backup service online, delete files, restore and check. + """ + backup_online(self.smb_conn, self.tarfile_path, os.environ['DOMSID']) + self._check_tarfile() + + self._delete_tree() + backup_restore(self.tarfile_path, self.service_root, + self.samdb_conn, self.conf) + self._check_tree() + + def test_backup_offline(self): + """ + Backup service offline, delete files, restore and check. + """ + backup_offline( + self.service_root, self.tarfile_path, self.samdb_conn, self.conf) + self._check_tarfile() + + self._delete_tree() + backup_restore( + self.tarfile_path, self.service_root, self.samdb_conn, self.conf) + self._check_tree()