diff options
Diffstat (limited to 'source4/torture/smb2/block.c')
-rw-r--r-- | source4/torture/smb2/block.c | 446 |
1 files changed, 446 insertions, 0 deletions
diff --git a/source4/torture/smb2/block.c b/source4/torture/smb2/block.c new file mode 100644 index 0000000..b9982b0 --- /dev/null +++ b/source4/torture/smb2/block.c @@ -0,0 +1,446 @@ +/* + * 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); + } +} |