1
0
mirror of https://github.com/samba-team/samba.git synced 2025-01-06 13:18:07 +03:00
samba-mirror/source4/torture/smb2/block.c
Stefan Metzmacher 3d7f81979c s4:torture/smb2: move smb2_transport blocking to the generic block.[ch]
We may want to use this in other places too, not only multichannel.c

BUG: https://bugzilla.samba.org/show_bug.cgi?id=11897

Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Günther Deschner <gd@samba.org>
2020-07-08 15:54:39 +00:00

447 lines
11 KiB
C

/*
* Unix SMB/CIFS implementation.
*
* block SMB2 transports using iptables
*
* Copyright (C) Guenther Deschner, 2017
*
* 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 "torture/torture.h"
#include "torture/smb2/proto.h"
#include "system/network.h"
#include "lib/util/util_net.h"
#include "torture/smb2/block.h"
#include "libcli/smb/smbXcli_base.h"
#include "lib/util/tevent_ntstatus.h"
#include "oplock_break_handler.h"
#include "lease_break_handler.h"
/*
* OUTPUT
* |
* -----> SMBTORTURE_OUTPUT
* |
* -----> SMBTORTURE_transportname1
* -----> SMBTORTURE_transportname2
*/
static bool run_cmd(const char *cmd)
{
int ret;
DEBUG(10, ("%s will call '%s'\n", __location__, cmd));
ret = system(cmd);
if (ret) {
DEBUG(1, ("%s failed to execute system call: %s: %d\n",
__location__, cmd, ret));
return false;
}
return true;
}
static const char *iptables_command(struct torture_context *tctx)
{
return torture_setting_string(tctx, "iptables_command",
"/usr/sbin/iptables");
}
char *escape_shell_string(const char *src);
/*
* iptables v1.6.1: chain name `SMBTORTURE_INPUT_tree1->session->transport'
* too long (must be under 29 chars)
*
* maybe truncate chainname ?
*/
static const char *samba_chain_name(struct torture_context *tctx,
const char *name,
const char *prefix)
{
const char *s;
char *sm;
s = talloc_asprintf(tctx, "%s_%s", prefix, name);
if (s == NULL) {
return NULL;
}
sm = escape_shell_string(s);
if (sm == NULL) {
return NULL;
}
s = talloc_strdup(tctx, sm);
free(sm);
return s;
}
static bool iptables_setup_chain(struct torture_context *tctx,
const char *parent_chain,
const char *chain,
bool unblock)
{
const char *ipt = iptables_command(tctx);
const char *cmd;
if (unblock) {
cmd = talloc_asprintf(tctx,
"%s -L %s > /dev/null 2>&1 && "
"("
"%s -F %s;"
"%s -D %s -j %s > /dev/null 2>&1 || true;"
"%s -X %s;"
");"
"%s -L %s > /dev/null 2>&1 || true;",
ipt, chain,
ipt, chain,
ipt, parent_chain, chain,
ipt, chain,
ipt, chain);
} else {
cmd = talloc_asprintf(tctx,
"%s -L %s > /dev/null 2>&1 || "
"("
"%s -N %s && "
"%s -I %s -j %s;"
");"
"%s -F %s;",
ipt, chain,
ipt, chain,
ipt, parent_chain, chain,
ipt, chain);
}
if (cmd == NULL) {
return false;
}
if (!run_cmd(cmd)) {
return false;
}
return true;
}
uint16_t torture_get_local_port_from_transport(struct smb2_transport *t)
{
const struct sockaddr_storage *local_ss;
local_ss = smbXcli_conn_local_sockaddr(t->conn);
return get_sockaddr_port(local_ss);
}
static bool torture_block_tcp_output_port_internal(
struct torture_context *tctx,
const char *name,
uint16_t port,
bool unblock)
{
const char *ipt = iptables_command(tctx);
const char *chain_out = NULL;
char *cmd_out = NULL;
chain_out = samba_chain_name(tctx, name, "SMBTORTURE");
if (chain_out == NULL) {
return false;
}
torture_comment(tctx, "%sblocking %s dport %d\n",
unblock ? "un" : "", name, port);
if (!unblock) {
bool ok;
iptables_setup_chain(tctx,
"SMBTORTURE_OUTPUT",
chain_out,
true);
ok = iptables_setup_chain(tctx,
"SMBTORTURE_OUTPUT",
chain_out,
false);
if (!ok) {
return false;
}
}
cmd_out = talloc_asprintf(tctx,
"%s %s %s -p tcp --sport %d -j DROP",
ipt, unblock ? "-D" : "-I", chain_out, port);
if (cmd_out == NULL) {
return false;
}
if (!run_cmd(cmd_out)) {
return false;
}
if (unblock) {
bool ok;
ok = iptables_setup_chain(tctx,
"SMBTORTURE_OUTPUT",
chain_out,
true);
if (!ok) {
return false;
}
}
return true;
}
bool torture_block_tcp_output_port(struct torture_context *tctx,
const char *name,
uint16_t port)
{
return torture_block_tcp_output_port_internal(tctx, name, port, false);
}
bool torture_unblock_tcp_output_port(struct torture_context *tctx,
const char *name,
uint16_t port)
{
return torture_block_tcp_output_port_internal(tctx, name, port, true);
}
bool torture_block_tcp_output_setup(struct torture_context *tctx)
{
return iptables_setup_chain(tctx, "OUTPUT", "SMBTORTURE_OUTPUT", false);
}
bool torture_unblock_tcp_output_cleanup(struct torture_context *tctx)
{
return iptables_setup_chain(tctx, "OUTPUT", "SMBTORTURE_OUTPUT", true);
}
/*
* Use iptables to block channels
*/
static bool test_block_smb2_transport_iptables(struct torture_context *tctx,
struct smb2_transport *transport,
const char *name)
{
uint16_t local_port;
bool ret;
local_port = torture_get_local_port_from_transport(transport);
torture_comment(tctx, "transport[%s] uses tcp port: %d\n", name, local_port);
ret = torture_block_tcp_output_port(tctx, name, local_port);
torture_assert(tctx, ret, "we could not block tcp transport");
return ret;
}
static bool test_unblock_smb2_transport_iptables(struct torture_context *tctx,
struct smb2_transport *transport,
const char *name)
{
uint16_t local_port;
bool ret;
local_port = torture_get_local_port_from_transport(transport);
torture_comment(tctx, "transport[%s] uses tcp port: %d\n", name, local_port);
ret = torture_unblock_tcp_output_port(tctx, name, local_port);
torture_assert(tctx, ret, "we could not block tcp transport");
return ret;
}
static bool torture_blocked_lease_handler(struct smb2_transport *transport,
const struct smb2_lease_break *lb,
void *private_data)
{
struct smb2_transport *transport_copy =
talloc_get_type_abort(private_data,
struct smb2_transport);
bool lease_skip_ack = lease_break_info.lease_skip_ack;
bool ok;
lease_break_info.lease_skip_ack = true;
ok = transport_copy->lease.handler(transport,
lb,
transport_copy->lease.private_data);
lease_break_info.lease_skip_ack = lease_skip_ack;
if (!ok) {
return false;
}
if (lease_break_info.lease_skip_ack) {
return true;
}
if (lb->break_flags & SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED) {
lease_break_info.failures++;
}
return true;
}
static bool torture_blocked_oplock_handler(struct smb2_transport *transport,
const struct smb2_handle *handle,
uint8_t level,
void *private_data)
{
struct smb2_transport *transport_copy =
talloc_get_type_abort(private_data,
struct smb2_transport);
bool oplock_skip_ack = break_info.oplock_skip_ack;
bool ok;
break_info.oplock_skip_ack = true;
ok = transport_copy->oplock.handler(transport,
handle,
level,
transport_copy->oplock.private_data);
break_info.oplock_skip_ack = oplock_skip_ack;
if (!ok) {
return false;
}
if (break_info.oplock_skip_ack) {
return true;
}
break_info.failures++;
break_info.failure_status = NT_STATUS_CONNECTION_DISCONNECTED;
return true;
}
static bool test_block_smb2_transport_fsctl_smbtorture(struct torture_context *tctx,
struct smb2_transport *transport,
const char *name)
{
struct smb2_transport *transport_copy = NULL;
DATA_BLOB in_input_buffer = data_blob_null;
DATA_BLOB in_output_buffer = data_blob_null;
DATA_BLOB out_input_buffer = data_blob_null;
DATA_BLOB out_output_buffer = data_blob_null;
struct tevent_req *req = NULL;
uint16_t local_port;
NTSTATUS status;
bool ok;
transport_copy = talloc_zero(transport, struct smb2_transport);
torture_assert(tctx, transport_copy, "talloc transport_copy");
transport_copy->lease = transport->lease;
transport_copy->oplock = transport->oplock;
local_port = torture_get_local_port_from_transport(transport);
torture_comment(tctx, "transport[%s] uses tcp port: %d\n", name, local_port);
req = smb2cli_ioctl_send(tctx,
tctx->ev,
transport->conn,
1000, /* timeout_msec */
NULL, /* session */
NULL, /* tcon */
UINT64_MAX, /* in_fid_persistent */
UINT64_MAX, /* in_fid_volatile */
FSCTL_SMBTORTURE_FORCE_UNACKED_TIMEOUT,
0, /* in_max_input_length */
&in_input_buffer,
0, /* in_max_output_length */
&in_output_buffer,
SMB2_IOCTL_FLAG_IS_FSCTL);
torture_assert(tctx, req != NULL, "smb2cli_ioctl_send() failed");
ok = tevent_req_poll_ntstatus(req, tctx->ev, &status);
if (ok) {
status = NT_STATUS_OK;
}
torture_assert_ntstatus_ok(tctx, status, "tevent_req_poll_ntstatus() failed");
status = smb2cli_ioctl_recv(req, tctx,
&out_input_buffer,
&out_output_buffer);
torture_assert_ntstatus_ok(tctx, status,
"FSCTL_SMBTORTURE_FORCE_UNACKED_TIMEOUT failed\n\n"
"On a Samba server 'smbd:FSCTL_SMBTORTURE = yes' is needed!\n\n"
"Otherwise you may need to use iptables like this:\n"
"--option='torture:use_iptables=yes'\n"
"And maybe something like this in addition:\n"
"--option='torture:iptables_command=sudo /sbin/iptables'\n\n");
TALLOC_FREE(req);
if (transport->lease.handler != NULL) {
transport->lease.handler = torture_blocked_lease_handler;
transport->lease.private_data = transport_copy;
}
if (transport->oplock.handler != NULL) {
transport->oplock.handler = torture_blocked_oplock_handler;
transport->oplock.private_data = transport_copy;
}
return true;
}
bool _test_block_smb2_transport(struct torture_context *tctx,
struct smb2_transport *transport,
const char *name)
{
bool use_iptables = torture_setting_bool(tctx,
"use_iptables", false);
if (use_iptables) {
return test_block_smb2_transport_iptables(tctx, transport, name);
} else {
return test_block_smb2_transport_fsctl_smbtorture(tctx, transport, name);
}
}
bool _test_unblock_smb2_transport(struct torture_context *tctx,
struct smb2_transport *transport,
const char *name)
{
bool use_iptables = torture_setting_bool(tctx,
"use_iptables", false);
if (use_iptables) {
return test_unblock_smb2_transport_iptables(tctx, transport, name);
} else {
return true;
}
}
bool test_setup_blocked_transports(struct torture_context *tctx)
{
bool use_iptables = torture_setting_bool(tctx,
"use_iptables", false);
if (use_iptables) {
return torture_block_tcp_output_setup(tctx);
}
return true;
}
void test_cleanup_blocked_transports(struct torture_context *tctx)
{
bool use_iptables = torture_setting_bool(tctx,
"use_iptables", false);
if (use_iptables) {
torture_unblock_tcp_output_cleanup(tctx);
}
}