--- /dev/null
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * test SMB2 and NFS interop
+ *
+ * Copyright (C) Guenther Deschner 2023
+ *
+ * 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 "libcli/smb2/smb2.h"
+#include "libcli/smb2/smb2_calls.h"
+#include "torture/torture.h"
+#include "torture/util.h"
+#include "torture/smbtorture.h"
+#include "torture/smb2/proto.h"
+#include "libcli/security/security.h"
+#include "librpc/gen_ndr/ndr_security.h"
+#include "librpc/gen_ndr/ndr_ioctl.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "lib/cmdline/cmdline.h"
+#include "libcli/security/security.h"
+#include "libcli/resolve/resolve.h"
+#include "lib/socket/socket.h"
+#include "lib/param/param.h"
+#include "lib/events/events.h"
+#include "../smb2/oplock_break_handler.h"
+#include "../smb2/lease_break_handler.h"
+#include "torture/smb2/block.h"
+
+#ifdef HAVE_NFSC_LIBNFS_H
+#include <nfsc/libnfs.h>
+#endif
+#ifdef HAVE_NFSC_LIBNFS_ZDR_H
+#include <nfsc/libnfs-zdr.h>
+#endif
+#ifdef HAVE_LINUX_NFS_H
+#include <linux/nfs.h>
+#endif
+
+#define BASEDIR "smbtorture-nfs"
+
+struct test_nfs_context {
+ struct nfs_context *nfs;
+ const char *host;
+ struct {
+ struct nfs_context *nfs;
+ const char *nfs_host;
+ const char *nfs_path;
+ struct AUTH *nfs_auth;
+ int nfs_version;
+ } n;
+ struct {
+ struct smb2_tree *tree;
+ const char *smb_host;
+ const char *smb_share;
+ } s;
+};
+
+#define torture_assert_nfs_equal(torture_ctx,got,expected,cmt)\
+ do { int __got = (got), __expected = (expected); \
+ if (__got != __expected) { \
+ torture_result(torture_ctx, TORTURE_FAIL, \
+ __location__": result was %d, errno %d (%s), expected %d: %s (NFS error: %s): %s", \
+ __got, errno, strerror(errno), \
+ __expected, strerror(__expected), \
+ nfs_get_error(t->nfs), cmt); \
+ return false; \
+ } \
+ } while(0)
+
+#define torture_assert_nfs_ok(torture_ctx,got,cmt)\
+ torture_assert_nfs_equal(torture_ctx,got,0,cmt);
+
+static bool torture_nfs_teardown_common(struct torture_context *tctx,
+ struct test_nfs_context *t)
+{
+ torture_assert_nfs_ok(tctx,
+ nfs_umount(t->nfs),
+ talloc_asprintf(tctx, "Failed to unmount nfs path: //%s/%s",
+ t->n.nfs_host, t->n.nfs_path));
+
+ nfs_destroy_context(t->nfs);
+
+ return true;
+}
+
+static bool torture_nfs_teardown(struct torture_context *tctx,
+ void *data)
+{
+ struct test_nfs_context *t = talloc_get_type(data, struct test_nfs_context);
+ bool ret;
+
+ ret = torture_nfs_teardown_common(tctx, t);
+ talloc_free(t);
+
+ return ret;
+}
+
+static bool torture_nfs_setup_common(struct torture_context *tctx,
+ struct test_nfs_context *t)
+{
+ t->n.nfs_host = torture_setting_string(tctx, "nfs_host",
+ torture_setting_string(tctx, "host", NULL));
+ t->n.nfs_path = torture_setting_string(tctx, "nfs_path", NULL);
+ t->n.nfs_version = torture_setting_int(tctx, "nfs_version", 4);
+
+ torture_assert(tctx,
+ t->n.nfs_path,
+ "Please define NFS path via \"--option=torture:nfs_path=/my/path\"");
+
+ t->nfs = t->n.nfs = nfs_init_context();
+ torture_assert(tctx, t->n.nfs, "failed to init NFS context");
+
+ nfs_set_debug(t->nfs, torture_setting_int(tctx, "nfs_debug", 0));
+
+ torture_assert_nfs_ok(tctx,
+ nfs_set_version(t->nfs, t->n.nfs_version),
+ "failed to set NFS version");
+
+#if 0
+ auth = libnfs_authunix_create_default();
+ auth = libnfs_authnone_create();
+#endif
+ t->n.nfs_auth = libnfs_authunix_create("localhorst", /* host */
+ torture_setting_int(tctx, "nfs_auth_uid", 0),
+ torture_setting_int(tctx, "nfs_auth_gid", 0),
+ 0, /* num_groups */
+ NULL /* groups */);
+ torture_assert(tctx, t->n.nfs_auth, "failed to create auth");
+
+ nfs_set_auth(t->n.nfs, t->n.nfs_auth);
+
+ torture_comment(tctx,
+ "Establishing connection to %s using NFSv%d\n",
+ t->n.nfs_host, t->n.nfs_version);
+
+ torture_assert_nfs_ok(tctx,
+ nfs_mount(t->nfs, t->n.nfs_host, t->n.nfs_path),
+ talloc_asprintf(tctx, "Failed to mount NFS path: //%s/%s",
+ t->n.nfs_host, t->n.nfs_path));
+
+ nfs_rmdir(t->nfs, BASEDIR);
+
+ return true;
+}
+
+static bool torture_nfs_setup(struct torture_context *tctx,
+ void **data)
+{
+ struct test_nfs_context *t;
+
+ *data = t = talloc_zero(tctx, struct test_nfs_context);
+
+ return torture_nfs_setup_common(tctx, t);
+}
+
+static bool torture_nfs_smb_teardown_common(struct torture_context *tctx,
+ struct test_nfs_context *t)
+{
+ talloc_free(t->s.tree);
+
+ return true;
+}
+
+static bool torture_nfs_smb_teardown(struct torture_context *tctx,
+ void *data)
+{
+ struct test_nfs_context *t = talloc_get_type(data, struct test_nfs_context);
+ bool ret;
+
+ torture_assert_goto(tctx,
+ torture_nfs_teardown_common(tctx, t),
+ ret, done,
+ "NFS teardown failed");
+
+ torture_assert_goto(tctx,
+ torture_nfs_smb_teardown_common(tctx, t),
+ ret, done,
+ "SMB teardown failed");
+ done:
+ talloc_free(t);
+
+ return ret;
+}
+
+static bool torture_nfs_smb_setup_common(struct torture_context *tctx,
+ struct test_nfs_context *t)
+{
+ t->s.smb_host = torture_setting_string(tctx, "host", NULL);
+ t->s.smb_share = torture_setting_string(tctx, "share", NULL);
+
+ torture_comment(tctx,
+ "Establishing connection to %s using SMB2\n",
+ t->s.smb_host);
+
+ torture_assert(tctx,
+ torture_smb2_connection(tctx, &t->s.tree),
+ talloc_asprintf(tctx, "Failed to mount SMB share: //%s/%s",
+ t->s.smb_host, t->s.smb_share));
+
+ return true;
+}
+
+static bool torture_nfs_smb_setup(struct torture_context *tctx,
+ void **data)
+{
+ struct test_nfs_context *t;
+ bool ok;
+
+ *data = t = talloc_zero(tctx, struct test_nfs_context);
+
+ ok = torture_nfs_setup_common(tctx, t);
+ if (!ok) {
+ return false;
+ }
+ ok = torture_nfs_smb_setup_common(tctx, t);
+ if (!ok) {
+ return false;
+ }
+
+ return true;
+}
+
+/****************************************************************
+ very simple NFS test:
+ * mkdir(dir)
+ * open(file)
+ * close(file)
+ * unlink(file)
+ * rmdir(dir)
+****************************************************************/
+
+static bool test_nfs_simple(struct torture_context *tctx,
+ void *private_data)
+{
+ struct test_nfs_context *t =
+ talloc_get_type_abort(private_data, struct test_nfs_context);
+
+ bool ret = true;
+ struct nfsfh *nfsfh = NULL;
+ const char *fname = BASEDIR"/testfile";
+
+ torture_comment(tctx, "Testing [%s/%s] using NFSv%d\n",
+ t->n.nfs_host, t->n.nfs_path, t->n.nfs_version);
+
+ torture_assert_nfs_ok(tctx,
+ nfs_mkdir(t->nfs, BASEDIR),
+ "Failed to create directory");
+
+ torture_assert_nfs_ok(tctx,
+ nfs_open(t->nfs, fname, O_CREAT | O_RDWR, &nfsfh),
+ "Failed to open file");
+
+ torture_assert_nfs_ok(tctx,
+ nfs_close(t->nfs, nfsfh),
+ "Failed to close file");
+
+ torture_assert_nfs_ok(tctx,
+ nfs_unlink(t->nfs, fname),
+ "Failed to unlink file");
+
+ torture_assert_nfs_ok(tctx,
+ nfs_rmdir(t->nfs, BASEDIR),
+ "Failed to remove directory");
+
+ return ret;
+}
+
+/****************************************************************
+ very simple NFS test with error checks on repeats:
+ * mkdir(dir)
+ * mkdir(dir) -> -NFSERR_EXIST
+ * open(file)
+ * close(file)
+ * close(file) -> crash in libnfs currently
+ * unlink(file)
+ * unlink(file) -> -NFSERR_NOENT
+ * rmdir(dir)
+ * rmdir(dir) -> -NFSERR_NOENT
+****************************************************************/
+
+static bool test_nfs_simple_errors(struct torture_context *tctx,
+ void *private_data)
+{
+ struct test_nfs_context *t =
+ talloc_get_type_abort(private_data, struct test_nfs_context);
+
+ bool ret = true;
+ struct nfsfh *nfsfh = NULL;
+ const char *fname = BASEDIR"/testfile";
+
+ torture_comment(tctx, "Testing [%s/%s] using NFSv%d\n",
+ t->n.nfs_host, t->n.nfs_path, t->n.nfs_version);
+
+ torture_assert_nfs_ok(tctx,
+ nfs_mkdir(t->nfs, BASEDIR),
+ "Failed to create directory");
+
+ torture_assert_nfs_equal(tctx,
+ nfs_mkdir(t->nfs, BASEDIR),
+ -NFSERR_EXIST,
+ "Failed to create directory");
+
+ torture_assert_nfs_ok(tctx,
+ nfs_open(t->nfs, fname, O_CREAT | O_RDWR, &nfsfh),
+ "Failed to open file");
+
+ torture_assert_nfs_ok(tctx,
+ nfs_close(t->nfs, nfsfh),
+ "Failed to close file");
+#if 0
+ /* crash bug in libnfs */
+
+ ZERO_STRUCT(nfsfh);
+
+ torture_assert_nfs_equal(tctx,
+ nfs_close(t->nfs, nfsfh),
+ -1,
+ "Failed to close file");
+#endif
+ torture_assert_nfs_ok(tctx,
+ nfs_unlink(t->nfs, fname),
+ "Failed to unlink file");
+
+ torture_assert_nfs_equal(tctx,
+ nfs_unlink(t->nfs, fname),
+ -NFSERR_NOENT,
+ "Failed to unlink file");
+
+ torture_assert_nfs_ok(tctx,
+ nfs_rmdir(t->nfs, BASEDIR),
+ "Failed to remove directory");
+
+ torture_assert_nfs_equal(tctx,
+ nfs_rmdir(t->nfs, BASEDIR),
+ -NFSERR_NOENT,
+ "Failed to remove directory");
+
+ return ret;
+}
+
+/****************************************************************
+ very simple SMB test:
+ * mkdir(dir)
+ * open(file)
+ * close(file)
+ * unlink(file)
+ * rmdir(dir)
+****************************************************************/
+
+static bool test_nfs_simple_smb(struct torture_context *tctx,
+ void *private_data)
+{
+ struct test_nfs_context *t =
+ talloc_get_type_abort(private_data, struct test_nfs_context);
+
+ bool ret = true;
+ struct smb2_handle smbfh;
+ const char *fname = BASEDIR"\\testfile";
+
+ torture_comment(tctx, "Testing [%s/%s] using SMB2\n",
+ t->s.smb_host, t->s.smb_share);
+
+ torture_assert_ntstatus_ok(tctx,
+ smb2_util_mkdir(t->s.tree, BASEDIR),
+ "Failed to create directory");
+
+ torture_assert_ntstatus_ok(tctx,
+ torture_smb2_testfile(t->s.tree, fname, &smbfh),
+ "Failed to open file");
+
+ torture_assert_ntstatus_ok(tctx,
+ smb2_util_close(t->s.tree, smbfh),
+ "Failed to close file");
+
+ torture_assert_ntstatus_ok(tctx,
+ smb2_util_unlink(t->s.tree, fname),
+ "Failed to unlink file");
+
+ torture_assert_ntstatus_ok(tctx,
+ smb2_util_rmdir(t->s.tree, BASEDIR),
+ "Failed to remove directory");
+
+ return ret;
+}
+
+/****************************************************************
+ very simple mixed NFS/SMB test:
+ * NFS: mkdir(dir)
+ * SMB: open(file)
+ * SMB: close(file)
+ * SMB: unlink(file)
+ * NFS: rmdir(dir)
+****************************************************************/
+
+static bool test_nfs_mix_smb(struct torture_context *tctx,
+ void *private_data)
+{
+ struct test_nfs_context *t =
+ talloc_get_type_abort(private_data, struct test_nfs_context);
+
+ bool ret = true;
+ struct smb2_handle smbfh;
+
+ torture_comment(tctx, "Testing [%s/%s] using NFSv%d and SMB2\n",
+ t->s.smb_host, t->s.smb_share, t->n.nfs_version);
+
+ torture_assert_nfs_ok(tctx,
+ nfs_mkdir(t->nfs, BASEDIR),
+ "Failed to create directory");
+
+ torture_assert_ntstatus_ok_goto(tctx,
+ torture_smb2_testfile(t->s.tree, BASEDIR"\\testfile", &smbfh),
+ ret, done,
+ "Failed to open file");
+
+ torture_assert_ntstatus_ok(tctx,
+ smb2_util_close(t->s.tree, smbfh),
+ "Failed to close file");
+
+ torture_assert_ntstatus_ok(tctx,
+ smb2_util_unlink(t->s.tree, BASEDIR"\\testfile"),
+ "Failed to unlink file");
+ done:
+ torture_assert_nfs_ok(tctx,
+ nfs_rmdir(t->nfs, BASEDIR),
+ "Failed to remove directory");
+
+ return ret;
+}
+
+/****************************************************************
+ very simple mixed NFS/SMB test with I/O:
+ * NFS: mkdir(dir)
+ * SMB: open(file)
+ * SMB: write(file)
+ * SMB: close(file)
+ * NFS: open(file)
+ * NFS: read(file)
+ * NFS: close(file)
+ * SMB: unlink(file)
+ * NFS: rmdir(dir)
+****************************************************************/
+
+static bool test_nfs_mix_smb_with_io(struct torture_context *tctx,
+ void *private_data)
+{
+ struct test_nfs_context *t =
+ talloc_get_type_abort(private_data, struct test_nfs_context);
+
+ bool ret = true;
+ struct smb2_handle smbfh;
+ struct nfsfh *nfsfh;
+ DATA_BLOB blob_in, blob_out;
+ const char *str = "Hello other protocol!";
+ const char *fname = BASEDIR"/testfile";
+
+ torture_comment(tctx, "Testing [%s/%s] using NFSv%d and SMB2\n",
+ t->s.smb_host, t->s.smb_share, t->n.nfs_version);
+
+ torture_assert_nfs_ok(tctx,
+ nfs_mkdir(t->nfs, BASEDIR),
+ "Failed to create directory");
+
+ torture_assert_ntstatus_ok_goto(tctx,
+ torture_smb2_testfile(t->s.tree, BASEDIR"\\testfile", &smbfh),
+ ret, done,
+ "Failed to open file");
+
+ blob_in = data_blob_string_const(str);
+
+ torture_assert_ntstatus_ok(tctx,
+ smb2_util_write(t->s.tree, smbfh, blob_in.data, 0, blob_in.length),
+ "Failed to write file");
+
+ torture_assert_ntstatus_ok(tctx,
+ smb2_util_close(t->s.tree, smbfh),
+ "Failed to close file");
+
+ torture_assert_nfs_ok(tctx,
+ nfs_open(t->nfs, fname, O_RDONLY, &nfsfh),
+ "Failed to open file");
+
+ blob_out = data_blob_talloc(tctx, NULL, 0xff);
+
+ blob_out.length = nfs_read(t->nfs, nfsfh, 0xff, blob_out.data);
+
+ torture_assert_nfs_ok(tctx,
+ (blob_out.length < 0),
+ "Failed to read file");
+
+ dump_data(0, blob_in.data, blob_in.length);
+ dump_data(0, blob_out.data, blob_out.length);
+
+ torture_assert_data_blob_equal(tctx, blob_in, blob_out,
+ "Failed to compare written and read content");
+
+ torture_assert_nfs_ok(tctx,
+ nfs_close(t->nfs, nfsfh),
+ "Failed to close file");
+
+ torture_assert_ntstatus_ok(tctx,
+ smb2_util_unlink(t->s.tree, BASEDIR"\\testfile"),
+ "Failed to unlink file");
+ done:
+ torture_assert_nfs_ok(tctx,
+ nfs_rmdir(t->nfs, BASEDIR),
+ "Failed to remove directory");
+
+ return ret;
+}
+
+/****************************************************************
+ very simple mixed NFS/SMB test with I/O:
+ * NFS: mkdir(dir)
+ * SMB: open(file)
+ * SMB: write(file)
+ * NFS: unlink(file)
+ * NFS: unlink(file) -> fails
+ * NFS: open(file) -> fails
+ * SMB: read(file) -> succeeds?
+ * SMB: close(file) -> succeeds?
+ * SMB: unlink(file) -> fails
+ * NFS: rmdir(dir)
+****************************************************************/
+
+static bool test_nfs_mix_smb_with_io_and_unlink(struct torture_context *tctx,
+ void *private_data)
+{
+ struct test_nfs_context *t =
+ talloc_get_type_abort(private_data, struct test_nfs_context);
+
+ bool ret = true;
+ struct smb2_handle smbfh;
+ struct nfsfh *nfsfh;
+ DATA_BLOB blob_in;
+ const char *str = "Hello other protocol!";
+ const char *fname = BASEDIR"/testfile";
+ struct smb2_read rd;
+
+ torture_comment(tctx, "Testing [%s/%s] using NFSv%d and SMB2\n",
+ t->s.smb_host, t->s.smb_share, t->n.nfs_version);
+
+ torture_assert_nfs_ok(tctx,
+ nfs_mkdir(t->nfs, BASEDIR),
+ "Failed to create directory");
+
+ torture_assert_ntstatus_ok_goto(tctx,
+ torture_smb2_testfile(t->s.tree, BASEDIR"\\testfile", &smbfh),
+ ret, done,
+ "Failed to open file");
+
+ blob_in = data_blob_string_const(str);
+
+ torture_assert_ntstatus_ok(tctx,
+ smb2_util_write(t->s.tree, smbfh, blob_in.data, 0, blob_in.length),
+ "Failed to write file");
+
+ torture_assert_nfs_ok(tctx,
+ nfs_unlink(t->nfs, fname),
+ "Failed to unlink file");
+
+ torture_assert_nfs_equal(tctx,
+ nfs_unlink(t->nfs, fname),
+ -NFSERR_NOENT,
+ "Failed to unlink file");
+
+ torture_assert_nfs_equal(tctx,
+ nfs_open(t->nfs, fname, O_RDONLY, &nfsfh),
+ -NFSERR_NOENT,
+ "Failed to open file");
+
+ ZERO_STRUCT(rd);
+ rd.in.file.handle = smbfh;
+ rd.in.length = 0xff;
+
+ torture_assert_ntstatus_ok(tctx,
+ smb2_read(t->s.tree, tctx, &rd),
+ "failed to read file");
+
+ dump_data(0, rd.out.data.data, rd.out.data.length);
+
+ torture_assert_data_blob_equal(tctx, blob_in, rd.out.data,
+ "Failed to compare written and read content");
+
+ torture_assert_ntstatus_ok(tctx,
+ smb2_util_close(t->s.tree, smbfh),
+ "Failed to close file");
+
+ torture_assert_ntstatus_equal(tctx,
+ smb2_util_unlink(t->s.tree, BASEDIR"\\testfile"),
+ NT_STATUS_OBJECT_NAME_NOT_FOUND,
+ "Failed to unlink file");
+ done:
+ torture_assert_nfs_ok(tctx,
+ nfs_rmdir(t->nfs, BASEDIR),
+ "Failed to remove directory");
+
+ return ret;
+}
+
+NTSTATUS torture_nfs_init(TALLOC_CTX *ctx);
+NTSTATUS torture_nfs_init(TALLOC_CTX *ctx)
+{
+ struct torture_suite *suite = torture_suite_create(ctx, "nfs");
+ struct torture_tcase *tcase;
+
+ tcase = torture_suite_add_tcase(suite, "sync_nfs");
+
+ torture_tcase_set_fixture(tcase,
+ torture_nfs_setup,
+ torture_nfs_teardown);
+
+ torture_tcase_add_simple_test(tcase,
+ "simple",
+ test_nfs_simple);
+ torture_tcase_add_simple_test(tcase,
+ "simple_errors",
+ test_nfs_simple_errors);
+
+ tcase = torture_suite_add_tcase(suite, "sync_nfs_smb");
+
+ torture_tcase_set_fixture(tcase,
+ torture_nfs_smb_setup,
+ torture_nfs_smb_teardown);
+
+ torture_tcase_add_simple_test(tcase,
+ "simple_smb",
+ test_nfs_simple_smb);
+ torture_tcase_add_simple_test(tcase,
+ "mix_smb",
+ test_nfs_mix_smb);
+ torture_tcase_add_simple_test(tcase,
+ "mix_smb_with_io",
+ test_nfs_mix_smb_with_io);
+ torture_tcase_add_simple_test(tcase,
+ "mix_smb_with_io_and_unlink",
+ test_nfs_mix_smb_with_io_and_unlink);
+#if 0
+ tcase = torture_suite_add_tcase(suite, "async");
+
+ torture_tcase_set_fixture(tcase,
+ torture_nfs_setup,
+ torture_nfs_teardown);
+
+ torture_tcase_add_simple_test(tcase,
+ "simple_async",
+ test_nfs_simple_async);
+#endif
+ suite->description = talloc_strdup(suite, "NFS tests");
+
+ torture_register_suite(ctx, suite);
+
+ return NT_STATUS_OK;
+}