summaryrefslogtreecommitdiffstats
path: root/source3/smbd
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
commit8daa83a594a2e98f39d764422bfbdbc62c9efd44 (patch)
tree4099e8021376c7d8c05bdf8503093d80e9c7bad0 /source3/smbd
parentInitial commit. (diff)
downloadsamba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.tar.xz
samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.zip
Adding upstream version 2:4.20.0+dfsg.upstream/2%4.20.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'source3/smbd')
-rw-r--r--source3/smbd/avahi_register.c300
-rw-r--r--source3/smbd/blocking.c763
-rw-r--r--source3/smbd/close.c1742
-rw-r--r--source3/smbd/conn.c267
-rw-r--r--source3/smbd/conn_idle.c277
-rw-r--r--source3/smbd/conn_msg.c148
-rw-r--r--source3/smbd/connection.c97
-rw-r--r--source3/smbd/dfree.c296
-rw-r--r--source3/smbd/dir.c1504
-rw-r--r--source3/smbd/dir.h89
-rw-r--r--source3/smbd/dmapi.c357
-rw-r--r--source3/smbd/dnsregister.c200
-rw-r--r--source3/smbd/dosmode.c1305
-rw-r--r--source3/smbd/durable.c938
-rw-r--r--source3/smbd/error.c175
-rw-r--r--source3/smbd/fake_file.c213
-rw-r--r--source3/smbd/fd_handle.c147
-rw-r--r--source3/smbd/fd_handle.h49
-rw-r--r--source3/smbd/file_access.c247
-rw-r--r--source3/smbd/fileio.c318
-rw-r--r--source3/smbd/filename.c1271
-rw-r--r--source3/smbd/files.c2651
-rw-r--r--source3/smbd/globals.c123
-rw-r--r--source3/smbd/globals.h912
-rw-r--r--source3/smbd/mangle.c153
-rw-r--r--source3/smbd/mangle_hash.c1132
-rw-r--r--source3/smbd/mangle_hash2.c875
-rw-r--r--source3/smbd/msdfs.c1774
-rw-r--r--source3/smbd/notify.c964
-rw-r--r--source3/smbd/notify_fam.c307
-rw-r--r--source3/smbd/notify_inotify.c457
-rw-r--r--source3/smbd/notify_msg.c247
-rw-r--r--source3/smbd/notifyd/fcn_wait.c270
-rw-r--r--source3/smbd/notifyd/fcn_wait.h38
-rw-r--r--source3/smbd/notifyd/notifyd.c1428
-rw-r--r--source3/smbd/notifyd/notifyd.h145
-rw-r--r--source3/smbd/notifyd/notifyd_db.c165
-rw-r--r--source3/smbd/notifyd/notifyd_db.h27
-rw-r--r--source3/smbd/notifyd/notifyd_entry.c42
-rw-r--r--source3/smbd/notifyd/notifyd_private.h49
-rw-r--r--source3/smbd/notifyd/notifydd.c98
-rw-r--r--source3/smbd/notifyd/test_notifyd.c347
-rw-r--r--source3/smbd/notifyd/tests.c118
-rw-r--r--source3/smbd/notifyd/wscript_build44
-rw-r--r--source3/smbd/ntquotas.c265
-rw-r--r--source3/smbd/open.c6676
-rw-r--r--source3/smbd/oplock_linux.c243
-rw-r--r--source3/smbd/password.c91
-rw-r--r--source3/smbd/posix_acls.c4862
-rw-r--r--source3/smbd/proto.h1224
-rw-r--r--source3/smbd/pysmbd.c1305
-rw-r--r--source3/smbd/quotas.c497
-rw-r--r--source3/smbd/scavenger.c731
-rw-r--r--source3/smbd/scavenger.h31
-rw-r--r--source3/smbd/seal.c313
-rw-r--r--source3/smbd/sec_ctx.c513
-rw-r--r--source3/smbd/server.c2136
-rw-r--r--source3/smbd/server_exit.c262
-rw-r--r--source3/smbd/server_reload.c176
-rw-r--r--source3/smbd/session.c218
-rw-r--r--source3/smbd/share_access.c291
-rw-r--r--source3/smbd/smb1_aio.c409
-rw-r--r--source3/smbd/smb1_aio.h29
-rw-r--r--source3/smbd/smb1_ipc.c949
-rw-r--r--source3/smbd/smb1_ipc.h33
-rw-r--r--source3/smbd/smb1_lanman.c5920
-rw-r--r--source3/smbd/smb1_lanman.h28
-rw-r--r--source3/smbd/smb1_message.c334
-rw-r--r--source3/smbd/smb1_message.h23
-rw-r--r--source3/smbd/smb1_negprot.c706
-rw-r--r--source3/smbd/smb1_negprot.h21
-rw-r--r--source3/smbd/smb1_nttrans.c2718
-rw-r--r--source3/smbd/smb1_nttrans.h25
-rw-r--r--source3/smbd/smb1_oplock.c72
-rw-r--r--source3/smbd/smb1_oplock.h26
-rw-r--r--source3/smbd/smb1_pipes.c447
-rw-r--r--source3/smbd/smb1_pipes.h26
-rw-r--r--source3/smbd/smb1_process.c2718
-rw-r--r--source3/smbd/smb1_process.h72
-rw-r--r--source3/smbd/smb1_reply.c7178
-rw-r--r--source3/smbd/smb1_reply.h80
-rw-r--r--source3/smbd/smb1_service.c241
-rw-r--r--source3/smbd/smb1_service.h24
-rw-r--r--source3/smbd/smb1_sesssetup.c1118
-rw-r--r--source3/smbd/smb1_sesssetup.h25
-rw-r--r--source3/smbd/smb1_signing.c290
-rw-r--r--source3/smbd/smb1_signing.h37
-rw-r--r--source3/smbd/smb1_trans2.c5703
-rw-r--r--source3/smbd/smb1_trans2.h27
-rw-r--r--source3/smbd/smb1_utils.c261
-rw-r--r--source3/smbd/smb1_utils.h46
-rw-r--r--source3/smbd/smb2_aio.c608
-rw-r--r--source3/smbd/smb2_break.c495
-rw-r--r--source3/smbd/smb2_close.c427
-rw-r--r--source3/smbd/smb2_create.c2113
-rw-r--r--source3/smbd/smb2_flush.c257
-rw-r--r--source3/smbd/smb2_getinfo.c694
-rw-r--r--source3/smbd/smb2_glue.c118
-rw-r--r--source3/smbd/smb2_ioctl.c505
-rw-r--r--source3/smbd/smb2_ioctl_dfs.c157
-rw-r--r--source3/smbd/smb2_ioctl_filesys.c830
-rw-r--r--source3/smbd/smb2_ioctl_named_pipe.c188
-rw-r--r--source3/smbd/smb2_ioctl_network_fs.c839
-rw-r--r--source3/smbd/smb2_ioctl_private.h60
-rw-r--r--source3/smbd/smb2_ioctl_smbtorture.c230
-rw-r--r--source3/smbd/smb2_ipc.c40
-rw-r--r--source3/smbd/smb2_keepalive.c50
-rw-r--r--source3/smbd/smb2_lock.c782
-rw-r--r--source3/smbd/smb2_negprot.c1229
-rw-r--r--source3/smbd/smb2_notify.c394
-rw-r--r--source3/smbd/smb2_nttrans.c911
-rw-r--r--source3/smbd/smb2_oplock.c1430
-rw-r--r--source3/smbd/smb2_pipes.c150
-rw-r--r--source3/smbd/smb2_posix.c73
-rw-r--r--source3/smbd/smb2_process.c2073
-rw-r--r--source3/smbd/smb2_query_directory.c1076
-rw-r--r--source3/smbd/smb2_read.c685
-rw-r--r--source3/smbd/smb2_reply.c2195
-rw-r--r--source3/smbd/smb2_server.c5235
-rw-r--r--source3/smbd/smb2_service.c951
-rw-r--r--source3/smbd/smb2_sesssetup.c1373
-rw-r--r--source3/smbd/smb2_setinfo.c628
-rw-r--r--source3/smbd/smb2_signing.c52
-rw-r--r--source3/smbd/smb2_tcon.c765
-rw-r--r--source3/smbd/smb2_trans2.c5243
-rw-r--r--source3/smbd/smb2_write.c457
-rw-r--r--source3/smbd/smbXsrv_client.c1458
-rw-r--r--source3/smbd/smbXsrv_open.c1526
-rw-r--r--source3/smbd/smbXsrv_open.h75
-rw-r--r--source3/smbd/smbXsrv_session.c2527
-rw-r--r--source3/smbd/smbXsrv_tcon.c1272
-rw-r--r--source3/smbd/smbXsrv_version.c265
-rw-r--r--source3/smbd/smbd.h102
-rw-r--r--source3/smbd/smbd_cleanupd.c182
-rw-r--r--source3/smbd/smbd_cleanupd.h33
-rw-r--r--source3/smbd/srvstr.c78
-rw-r--r--source3/smbd/statvfs.c182
-rw-r--r--source3/smbd/uid.c752
-rw-r--r--source3/smbd/utmp.c604
-rw-r--r--source3/smbd/vfs.c2661
140 files changed, 117519 insertions, 0 deletions
diff --git a/source3/smbd/avahi_register.c b/source3/smbd/avahi_register.c
new file mode 100644
index 0000000..883c862
--- /dev/null
+++ b/source3/smbd/avahi_register.c
@@ -0,0 +1,300 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Register _smb._tcp with avahi
+ *
+ * Copyright (C) Volker Lendecke 2009
+ *
+ * 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 "smbd/smbd.h"
+
+#include <avahi-client/client.h>
+#include <avahi-client/publish.h>
+#include <avahi-common/error.h>
+#include <avahi-common/malloc.h>
+#include <avahi-common/strlst.h>
+
+struct avahi_state_struct {
+ struct AvahiPoll *poll;
+ AvahiClient *client;
+ AvahiEntryGroup *entry_group;
+ uint16_t port;
+};
+
+static void *avahi_allocator_ctx = NULL;
+
+static void * avahi_allocator_malloc(size_t size)
+{
+ return talloc_size(avahi_allocator_ctx, size);
+}
+
+static void avahi_allocator_free(void *p)
+{
+ TALLOC_FREE(p);
+}
+
+static void * avahi_allocator_realloc(void *p, size_t size)
+{
+ return talloc_realloc_size(avahi_allocator_ctx, p, size);
+}
+
+static void * avahi_allocator_calloc(size_t count, size_t size)
+{
+ void *p = talloc_array_size(avahi_allocator_ctx, size, count);
+ if (p) {
+ memset(p, 0, size * count);
+ }
+ return p;
+}
+
+static const struct AvahiAllocator avahi_talloc_allocator = {
+ &avahi_allocator_malloc,
+ &avahi_allocator_free,
+ &avahi_allocator_realloc,
+ &avahi_allocator_calloc
+};
+
+static void avahi_entry_group_callback(AvahiEntryGroup *g,
+ AvahiEntryGroupState status,
+ void *userdata)
+{
+ struct avahi_state_struct *state = talloc_get_type_abort(
+ userdata, struct avahi_state_struct);
+ int error;
+
+ switch (status) {
+ case AVAHI_ENTRY_GROUP_ESTABLISHED:
+ DBG_DEBUG("AVAHI_ENTRY_GROUP_ESTABLISHED\n");
+ break;
+ case AVAHI_ENTRY_GROUP_FAILURE:
+ error = avahi_client_errno(state->client);
+
+ DBG_DEBUG("AVAHI_ENTRY_GROUP_FAILURE: %s\n",
+ avahi_strerror(error));
+ break;
+ case AVAHI_ENTRY_GROUP_COLLISION:
+ DBG_DEBUG("AVAHI_ENTRY_GROUP_COLLISION\n");
+ break;
+ case AVAHI_ENTRY_GROUP_UNCOMMITED:
+ DBG_DEBUG("AVAHI_ENTRY_GROUP_UNCOMMITED\n");
+ break;
+ case AVAHI_ENTRY_GROUP_REGISTERING:
+ DBG_DEBUG("AVAHI_ENTRY_GROUP_REGISTERING\n");
+ break;
+ }
+}
+
+static void avahi_client_callback(AvahiClient *c, AvahiClientState status,
+ void *userdata)
+{
+ struct avahi_state_struct *state = talloc_get_type_abort(
+ userdata, struct avahi_state_struct);
+ int error;
+
+ switch (status) {
+ case AVAHI_CLIENT_S_RUNNING: {
+ int snum;
+ int num_services = lp_numservices();
+ size_t dk = 0;
+ AvahiStringList *adisk = NULL;
+ AvahiStringList *adisk2 = NULL;
+ AvahiStringList *dinfo = NULL;
+ const char *hostname = NULL;
+ enum mdns_name_values mdns_name = lp_mdns_name();
+ const char *model = NULL;
+
+ DBG_DEBUG("AVAHI_CLIENT_S_RUNNING\n");
+
+ switch (mdns_name) {
+ case MDNS_NAME_MDNS:
+ hostname = avahi_client_get_host_name(c);
+ break;
+ case MDNS_NAME_NETBIOS:
+ hostname = lp_netbios_name();
+ break;
+ default:
+ DBG_ERR("Unhandled mdns_name %d\n", mdns_name);
+ return;
+ }
+
+ state->entry_group = avahi_entry_group_new(
+ c, avahi_entry_group_callback, state);
+ if (state->entry_group == NULL) {
+ error = avahi_client_errno(c);
+ DBG_DEBUG("avahi_entry_group_new failed: %s\n",
+ avahi_strerror(error));
+ break;
+ }
+
+ error = avahi_entry_group_add_service(
+ state->entry_group, AVAHI_IF_UNSPEC,
+ AVAHI_PROTO_UNSPEC, 0, hostname,
+ "_smb._tcp", NULL, NULL, state->port, NULL);
+ if (error != AVAHI_OK) {
+ DBG_DEBUG("avahi_entry_group_add_service failed: %s\n",
+ avahi_strerror(error));
+ avahi_entry_group_free(state->entry_group);
+ state->entry_group = NULL;
+ break;
+ }
+
+ for (snum = 0; snum < num_services; snum++) {
+ if (lp_snum_ok(snum) &&
+ lp_parm_bool(snum, "fruit", "time machine", false))
+ {
+ adisk2 = avahi_string_list_add_printf(
+ adisk, "dk%zu=adVN=%s,adVF=0x82",
+ dk++, lp_const_servicename(snum));
+ if (adisk2 == NULL) {
+ DBG_DEBUG("avahi_string_list_add_printf"
+ "failed: returned NULL\n");
+ avahi_string_list_free(adisk);
+ avahi_entry_group_free(state->entry_group);
+ state->entry_group = NULL;
+ break;
+ }
+ adisk = adisk2;
+ adisk2 = NULL;
+ }
+ }
+ if (dk > 0) {
+ adisk2 = avahi_string_list_add(adisk, "sys=adVF=0x100");
+ if (adisk2 == NULL) {
+ DBG_DEBUG("avahi_string_list_add failed: "
+ "returned NULL\n");
+ avahi_string_list_free(adisk);
+ avahi_entry_group_free(state->entry_group);
+ state->entry_group = NULL;
+ break;
+ }
+ adisk = adisk2;
+ adisk2 = NULL;
+
+ error = avahi_entry_group_add_service_strlst(
+ state->entry_group, AVAHI_IF_UNSPEC,
+ AVAHI_PROTO_UNSPEC, 0, hostname,
+ "_adisk._tcp", NULL, NULL, 0, adisk);
+ avahi_string_list_free(adisk);
+ adisk = NULL;
+ if (error != AVAHI_OK) {
+ DBG_DEBUG("avahi_entry_group_add_service_strlst "
+ "failed: %s\n", avahi_strerror(error));
+ avahi_entry_group_free(state->entry_group);
+ state->entry_group = NULL;
+ break;
+ }
+ }
+
+ model = lp_parm_const_string(-1, "fruit", "model", "MacSamba");
+
+ dinfo = avahi_string_list_add_printf(NULL, "model=%s", model);
+ if (dinfo == NULL) {
+ DBG_DEBUG("avahi_string_list_add_printf"
+ "failed: returned NULL\n");
+ avahi_entry_group_free(state->entry_group);
+ state->entry_group = NULL;
+ break;
+ }
+
+ error = avahi_entry_group_add_service_strlst(
+ state->entry_group, AVAHI_IF_UNSPEC,
+ AVAHI_PROTO_UNSPEC, 0, hostname,
+ "_device-info._tcp", NULL, NULL, 0,
+ dinfo);
+ avahi_string_list_free(dinfo);
+ if (error != AVAHI_OK) {
+ DBG_DEBUG("avahi_entry_group_add_service failed: %s\n",
+ avahi_strerror(error));
+ avahi_entry_group_free(state->entry_group);
+ state->entry_group = NULL;
+ break;
+ }
+
+ error = avahi_entry_group_commit(state->entry_group);
+ if (error != AVAHI_OK) {
+ DBG_DEBUG("avahi_entry_group_commit failed: %s\n",
+ avahi_strerror(error));
+ avahi_entry_group_free(state->entry_group);
+ state->entry_group = NULL;
+ break;
+ }
+ break;
+ }
+ case AVAHI_CLIENT_FAILURE:
+ error = avahi_client_errno(c);
+
+ DBG_DEBUG("AVAHI_CLIENT_FAILURE: %s\n", avahi_strerror(error));
+
+ if (error != AVAHI_ERR_DISCONNECTED) {
+ break;
+ }
+ avahi_client_free(c);
+ state->client = avahi_client_new(state->poll, AVAHI_CLIENT_NO_FAIL,
+ avahi_client_callback, state,
+ &error);
+ if (state->client == NULL) {
+ DBG_DEBUG("avahi_client_new failed: %s\n",
+ avahi_strerror(error));
+ break;
+ }
+ break;
+ case AVAHI_CLIENT_S_COLLISION:
+ DBG_DEBUG("AVAHI_CLIENT_S_COLLISION\n");
+ break;
+ case AVAHI_CLIENT_S_REGISTERING:
+ DBG_DEBUG("AVAHI_CLIENT_S_REGISTERING\n");
+ break;
+ case AVAHI_CLIENT_CONNECTING:
+ DBG_DEBUG("AVAHI_CLIENT_CONNECTING\n");
+ break;
+ }
+}
+
+void *avahi_start_register(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ uint16_t port)
+{
+ struct avahi_state_struct *state;
+ int error;
+
+ avahi_allocator_ctx = talloc_new(mem_ctx);
+ if (avahi_allocator_ctx == NULL) {
+ return NULL;
+ }
+ avahi_set_allocator(&avahi_talloc_allocator);
+
+ state = talloc(mem_ctx, struct avahi_state_struct);
+ if (state == NULL) {
+ return state;
+ }
+ state->port = port;
+ state->poll = tevent_avahi_poll(state, ev);
+ if (state->poll == NULL) {
+ goto fail;
+ }
+ state->client = avahi_client_new(state->poll, AVAHI_CLIENT_NO_FAIL,
+ avahi_client_callback, state,
+ &error);
+ if (state->client == NULL) {
+ DBG_DEBUG("avahi_client_new failed: %s\n",
+ avahi_strerror(error));
+ goto fail;
+ }
+ return state;
+
+ fail:
+ TALLOC_FREE(state);
+ return NULL;
+}
diff --git a/source3/smbd/blocking.c b/source3/smbd/blocking.c
new file mode 100644
index 0000000..8b41288
--- /dev/null
+++ b/source3/smbd/blocking.c
@@ -0,0 +1,763 @@
+/*
+ Unix SMB/CIFS implementation.
+ Blocking Locking functions
+ Copyright (C) Jeremy Allison 1998-2003
+
+ 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 "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "messages.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "lib/dbwrap/dbwrap_watch.h"
+#include "librpc/gen_ndr/ndr_open_files.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_LOCKING
+
+NTSTATUS smbd_do_locks_try(
+ struct files_struct *fsp,
+ uint16_t num_locks,
+ struct smbd_lock_element *locks,
+ uint16_t *blocker_idx,
+ struct server_id *blocking_pid,
+ uint64_t *blocking_smblctx)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ uint16_t i;
+
+ for (i=0; i<num_locks; i++) {
+ struct smbd_lock_element *e = &locks[i];
+
+ status = do_lock(
+ fsp,
+ locks, /* req_mem_ctx */
+ &e->req_guid,
+ e->smblctx,
+ e->count,
+ e->offset,
+ e->brltype,
+ e->lock_flav,
+ blocking_pid,
+ blocking_smblctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ break;
+ }
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_OK;
+ }
+
+ *blocker_idx = i;
+
+ /*
+ * Undo the locks we successfully got
+ */
+ for (i = i-1; i != UINT16_MAX; i--) {
+ struct smbd_lock_element *e = &locks[i];
+ do_unlock(fsp,
+ e->smblctx,
+ e->count,
+ e->offset,
+ e->lock_flav);
+ }
+
+ return status;
+}
+
+static bool smbd_smb1_fsp_add_blocked_lock_req(
+ struct files_struct *fsp, struct tevent_req *req)
+{
+ size_t num_reqs = talloc_array_length(fsp->blocked_smb1_lock_reqs);
+ struct tevent_req **tmp = NULL;
+
+ tmp = talloc_realloc(
+ fsp,
+ fsp->blocked_smb1_lock_reqs,
+ struct tevent_req *,
+ num_reqs+1);
+ if (tmp == NULL) {
+ return false;
+ }
+ fsp->blocked_smb1_lock_reqs = tmp;
+ fsp->blocked_smb1_lock_reqs[num_reqs] = req;
+ return true;
+}
+
+struct smbd_smb1_do_locks_state {
+ struct tevent_context *ev;
+ struct smb_request *smbreq;
+ struct files_struct *fsp;
+ uint32_t timeout;
+ uint32_t polling_msecs;
+ uint32_t retry_msecs;
+ struct timeval endtime;
+ bool large_offset; /* required for correct cancel */
+ uint16_t num_locks;
+ struct smbd_lock_element *locks;
+ uint16_t blocker;
+ NTSTATUS deny_status;
+};
+
+static void smbd_smb1_do_locks_try(struct tevent_req *req);
+static void smbd_smb1_do_locks_retry(struct tevent_req *subreq);
+static void smbd_smb1_blocked_locks_cleanup(
+ struct tevent_req *req, enum tevent_req_state req_state);
+static NTSTATUS smbd_smb1_do_locks_check(
+ struct files_struct *fsp,
+ uint16_t num_locks,
+ struct smbd_lock_element *locks,
+ uint16_t *blocker_idx,
+ struct server_id *blocking_pid,
+ uint64_t *blocking_smblctx);
+
+static void smbd_smb1_do_locks_setup_timeout(
+ struct smbd_smb1_do_locks_state *state,
+ const struct smbd_lock_element *blocker)
+{
+ struct files_struct *fsp = state->fsp;
+
+ if (!timeval_is_zero(&state->endtime)) {
+ /*
+ * already done
+ */
+ return;
+ }
+
+ if ((state->timeout != 0) && (state->timeout != UINT32_MAX)) {
+ /*
+ * Windows internal resolution for blocking locks
+ * seems to be about 200ms... Don't wait for less than
+ * that. JRA.
+ */
+ state->timeout = MAX(state->timeout, lp_lock_spin_time());
+ }
+
+ if (state->timeout != 0) {
+ goto set_endtime;
+ }
+
+ if (blocker == NULL) {
+ goto set_endtime;
+ }
+
+ if ((blocker->offset >= 0xEF000000) &&
+ ((blocker->offset >> 63) == 0)) {
+ /*
+ * This must be an optimization of an ancient
+ * application bug...
+ */
+ state->timeout = lp_lock_spin_time();
+ }
+
+ if (fsp->fsp_flags.lock_failure_seen &&
+ (blocker->offset == fsp->lock_failure_offset)) {
+ /*
+ * Delay repeated lock attempts on the same
+ * lock. Maybe a more advanced version of the
+ * above check?
+ */
+ DBG_DEBUG("Delaying lock request due to previous "
+ "failure\n");
+ state->timeout = lp_lock_spin_time();
+ }
+
+set_endtime:
+ /*
+ * Note state->timeout might still 0,
+ * but that's ok, as we don't want to retry
+ * in that case.
+ */
+ state->endtime = timeval_add(&state->smbreq->request_time,
+ state->timeout / 1000,
+ (state->timeout % 1000) * 1000);
+}
+
+static void smbd_smb1_do_locks_update_retry_msecs(
+ struct smbd_smb1_do_locks_state *state)
+{
+ /*
+ * The default lp_lock_spin_time() is 200ms,
+ * we just use half of it to trigger the first retry.
+ *
+ * v_min is in the range of 0.001 to 10 secs
+ * (0.1 secs by default)
+ *
+ * v_max is in the range of 0.01 to 100 secs
+ * (1.0 secs by default)
+ *
+ * The typical steps are:
+ * 0.1, 0.2, 0.3, 0.4, ... 1.0
+ */
+ uint32_t v_min = MAX(2, MIN(20000, lp_lock_spin_time()))/2;
+ uint32_t v_max = 10 * v_min;
+
+ if (state->retry_msecs >= v_max) {
+ state->retry_msecs = v_max;
+ return;
+ }
+
+ state->retry_msecs += v_min;
+}
+
+static void smbd_smb1_do_locks_update_polling_msecs(
+ struct smbd_smb1_do_locks_state *state)
+{
+ /*
+ * The default lp_lock_spin_time() is 200ms.
+ *
+ * v_min is in the range of 0.002 to 20 secs
+ * (0.2 secs by default)
+ *
+ * v_max is in the range of 0.02 to 200 secs
+ * (2.0 secs by default)
+ *
+ * The typical steps are:
+ * 0.2, 0.4, 0.6, 0.8, ... 2.0
+ */
+ uint32_t v_min = MAX(2, MIN(20000, lp_lock_spin_time()));
+ uint32_t v_max = 10 * v_min;
+
+ if (state->polling_msecs >= v_max) {
+ state->polling_msecs = v_max;
+ return;
+ }
+
+ state->polling_msecs += v_min;
+}
+
+struct tevent_req *smbd_smb1_do_locks_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smb_request **smbreq, /* talloc_move()d into our state */
+ struct files_struct *fsp,
+ uint32_t lock_timeout,
+ bool large_offset,
+ uint16_t num_locks,
+ struct smbd_lock_element *locks)
+{
+ struct tevent_req *req = NULL;
+ struct smbd_smb1_do_locks_state *state = NULL;
+ bool ok;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct smbd_smb1_do_locks_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->smbreq = talloc_move(state, smbreq);
+ state->fsp = fsp;
+ state->timeout = lock_timeout;
+ state->large_offset = large_offset;
+ state->num_locks = num_locks;
+ state->locks = locks;
+ state->deny_status = NT_STATUS_LOCK_NOT_GRANTED;
+
+ DBG_DEBUG("state=%p, state->smbreq=%p\n", state, state->smbreq);
+
+ if (num_locks == 0 || locks == NULL) {
+ DBG_DEBUG("no locks\n");
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ if (state->locks[0].lock_flav == POSIX_LOCK) {
+ /*
+ * SMB1 posix locks always use
+ * NT_STATUS_FILE_LOCK_CONFLICT.
+ */
+ state->deny_status = NT_STATUS_FILE_LOCK_CONFLICT;
+ }
+
+ smbd_smb1_do_locks_try(req);
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ ok = smbd_smb1_fsp_add_blocked_lock_req(fsp, req);
+ if (!ok) {
+ tevent_req_oom(req);
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_cleanup_fn(req, smbd_smb1_blocked_locks_cleanup);
+ return req;
+}
+
+static void smbd_smb1_blocked_locks_cleanup(
+ struct tevent_req *req, enum tevent_req_state req_state)
+{
+ struct smbd_smb1_do_locks_state *state = tevent_req_data(
+ req, struct smbd_smb1_do_locks_state);
+ struct files_struct *fsp = state->fsp;
+ struct tevent_req **blocked = fsp->blocked_smb1_lock_reqs;
+ size_t num_blocked = talloc_array_length(blocked);
+ size_t i;
+
+ DBG_DEBUG("req=%p, state=%p, req_state=%d\n",
+ req,
+ state,
+ (int)req_state);
+
+ if (req_state == TEVENT_REQ_RECEIVED) {
+ DBG_DEBUG("already received\n");
+ return;
+ }
+
+ for (i=0; i<num_blocked; i++) {
+ if (blocked[i] == req) {
+ break;
+ }
+ }
+ SMB_ASSERT(i<num_blocked);
+
+ ARRAY_DEL_ELEMENT(blocked, i, num_blocked);
+
+ fsp->blocked_smb1_lock_reqs = talloc_realloc(
+ fsp, blocked, struct tevent_req *, num_blocked-1);
+}
+
+static NTSTATUS smbd_smb1_do_locks_check_blocked(
+ uint16_t num_blocked,
+ struct smbd_lock_element *blocked,
+ uint16_t num_locks,
+ struct smbd_lock_element *locks,
+ uint16_t *blocker_idx,
+ uint64_t *blocking_smblctx)
+{
+ uint16_t li;
+
+ for (li=0; li < num_locks; li++) {
+ struct smbd_lock_element *l = &locks[li];
+ uint16_t bi;
+ bool valid;
+
+ valid = byte_range_valid(l->offset, l->count);
+ if (!valid) {
+ return NT_STATUS_INVALID_LOCK_RANGE;
+ }
+
+ for (bi = 0; bi < num_blocked; bi++) {
+ struct smbd_lock_element *b = &blocked[li];
+ bool overlap;
+
+ /* Read locks never conflict. */
+ if (l->brltype == READ_LOCK && b->brltype == READ_LOCK) {
+ continue;
+ }
+
+ overlap = byte_range_overlap(l->offset,
+ l->count,
+ b->offset,
+ b->count);
+ if (!overlap) {
+ continue;
+ }
+
+ *blocker_idx = li;
+ *blocking_smblctx = b->smblctx;
+ return NT_STATUS_LOCK_NOT_GRANTED;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbd_smb1_do_locks_check(
+ struct files_struct *fsp,
+ uint16_t num_locks,
+ struct smbd_lock_element *locks,
+ uint16_t *blocker_idx,
+ struct server_id *blocking_pid,
+ uint64_t *blocking_smblctx)
+{
+ struct tevent_req **blocked = fsp->blocked_smb1_lock_reqs;
+ size_t num_blocked = talloc_array_length(blocked);
+ NTSTATUS status;
+ size_t bi;
+
+ /*
+ * We check the pending/blocked requests
+ * from the oldest to the youngest request.
+ *
+ * Note due to the retry logic the current request
+ * might already be in the list.
+ */
+
+ for (bi = 0; bi < num_blocked; bi++) {
+ struct smbd_smb1_do_locks_state *blocked_state =
+ tevent_req_data(blocked[bi],
+ struct smbd_smb1_do_locks_state);
+
+ if (blocked_state->locks == locks) {
+ SMB_ASSERT(blocked_state->num_locks == num_locks);
+
+ /*
+ * We found ourself...
+ */
+ break;
+ }
+
+ status = smbd_smb1_do_locks_check_blocked(
+ blocked_state->num_locks,
+ blocked_state->locks,
+ num_locks,
+ locks,
+ blocker_idx,
+ blocking_smblctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ *blocking_pid = messaging_server_id(
+ fsp->conn->sconn->msg_ctx);
+ return status;
+ }
+ }
+
+ status = smbd_do_locks_try(
+ fsp,
+ num_locks,
+ locks,
+ blocker_idx,
+ blocking_pid,
+ blocking_smblctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static void smbd_smb1_do_locks_try(struct tevent_req *req)
+{
+ struct smbd_smb1_do_locks_state *state = tevent_req_data(
+ req, struct smbd_smb1_do_locks_state);
+ struct files_struct *fsp = state->fsp;
+ struct share_mode_lock *lck;
+ struct timeval endtime = { 0 };
+ struct server_id blocking_pid = { 0 };
+ uint64_t blocking_smblctx = 0;
+ struct tevent_req *subreq = NULL;
+ NTSTATUS status;
+ bool ok;
+ bool expired;
+
+ lck = get_existing_share_mode_lock(state, fsp->file_id);
+ if (tevent_req_nomem(lck, req)) {
+ DBG_DEBUG("Could not get share mode lock\n");
+ return;
+ }
+
+ status = smbd_smb1_do_locks_check(
+ fsp,
+ state->num_locks,
+ state->locks,
+ &state->blocker,
+ &blocking_pid,
+ &blocking_smblctx);
+ if (NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
+ /*
+ * We got NT_STATUS_RETRY,
+ * we reset polling_msecs so that
+ * that the retries based on LOCK_NOT_GRANTED
+ * will later start with small intervals again.
+ */
+ state->polling_msecs = 0;
+
+ /*
+ * The backend wasn't able to decide yet.
+ * We need to wait even for non-blocking
+ * locks.
+ *
+ * The backend uses blocking_smblctx == UINT64_MAX
+ * to indicate that we should use retry timers.
+ *
+ * It uses blocking_smblctx == 0 to indicate
+ * it will use share_mode_wakeup_waiters()
+ * to wake us. Note that unrelated changes in
+ * locking.tdb may cause retries.
+ */
+
+ if (blocking_smblctx != UINT64_MAX) {
+ SMB_ASSERT(blocking_smblctx == 0);
+ goto setup_retry;
+ }
+
+ smbd_smb1_do_locks_update_retry_msecs(state);
+
+ DBG_DEBUG("Waiting for a backend decision. "
+ "Retry in %"PRIu32" msecs\n",
+ state->retry_msecs);
+
+ /*
+ * We completely ignore state->endtime here
+ * we we'll wait for a backend decision forever.
+ * If the backend is smart enough to implement
+ * some NT_STATUS_RETRY logic, it has to
+ * switch to any other status after in order
+ * to avoid waiting forever.
+ */
+ endtime = timeval_current_ofs_msec(state->retry_msecs);
+ goto setup_retry;
+ }
+ if (!ERROR_WAS_LOCK_DENIED(status)) {
+ goto done;
+ }
+ /*
+ * We got LOCK_NOT_GRANTED, make sure
+ * a following STATUS_RETRY will start
+ * with short intervals again.
+ */
+ state->retry_msecs = 0;
+
+ smbd_smb1_do_locks_setup_timeout(state, &state->locks[state->blocker]);
+ DBG_DEBUG("timeout=%"PRIu32", blocking_smblctx=%"PRIu64"\n",
+ state->timeout,
+ blocking_smblctx);
+
+ /*
+ * The client specified timeout expired
+ * avoid further retries.
+ *
+ * Otherwise keep waiting either waiting
+ * for changes in locking.tdb or the polling
+ * mode timers waiting for posix locks.
+ *
+ * If the endtime is not expired yet,
+ * it means we'll retry after a timeout.
+ * In that case we'll have to return
+ * NT_STATUS_FILE_LOCK_CONFLICT
+ * instead of NT_STATUS_LOCK_NOT_GRANTED.
+ */
+ expired = timeval_expired(&state->endtime);
+ if (expired) {
+ status = state->deny_status;
+ goto done;
+ }
+ state->deny_status = NT_STATUS_FILE_LOCK_CONFLICT;
+
+ endtime = state->endtime;
+
+ if (blocking_smblctx == UINT64_MAX) {
+ struct timeval tmp;
+
+ smbd_smb1_do_locks_update_polling_msecs(state);
+
+ DBG_DEBUG("Blocked on a posix lock. Retry in %"PRIu32" msecs\n",
+ state->polling_msecs);
+
+ tmp = timeval_current_ofs_msec(state->polling_msecs);
+ endtime = timeval_min(&endtime, &tmp);
+ }
+
+setup_retry:
+ subreq = share_mode_watch_send(
+ state, state->ev, lck, blocking_pid);
+ if (tevent_req_nomem(subreq, req)) {
+ goto done;
+ }
+ TALLOC_FREE(lck);
+ tevent_req_set_callback(subreq, smbd_smb1_do_locks_retry, req);
+
+ if (timeval_is_zero(&endtime)) {
+ return;
+ }
+
+ ok = tevent_req_set_endtime(subreq, state->ev, endtime);
+ if (!ok) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ return;
+done:
+ TALLOC_FREE(lck);
+ smbd_smb1_brl_finish_by_req(req, status);
+}
+
+static void smbd_smb1_do_locks_retry(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smbd_smb1_do_locks_state *state = tevent_req_data(
+ req, struct smbd_smb1_do_locks_state);
+ NTSTATUS status;
+ bool ok;
+
+ /*
+ * Make sure we run as the user again
+ */
+ ok = change_to_user_and_service_by_fsp(state->fsp);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+
+ status = share_mode_watch_recv(subreq, NULL, NULL);
+ TALLOC_FREE(subreq);
+
+ DBG_DEBUG("share_mode_watch_recv returned %s\n",
+ nt_errstr(status));
+
+ /*
+ * We ignore any errors here, it's most likely
+ * we just get NT_STATUS_OK or NT_STATUS_IO_TIMEOUT.
+ *
+ * In any case we can just give it a retry.
+ */
+
+ smbd_smb1_do_locks_try(req);
+}
+
+NTSTATUS smbd_smb1_do_locks_recv(struct tevent_req *req)
+{
+ struct smbd_smb1_do_locks_state *state = tevent_req_data(
+ req, struct smbd_smb1_do_locks_state);
+ NTSTATUS status = NT_STATUS_OK;
+ bool err;
+
+ err = tevent_req_is_nterror(req, &status);
+
+ DBG_DEBUG("err=%d, status=%s\n", (int)err, nt_errstr(status));
+
+ if (tevent_req_is_nterror(req, &status)) {
+ struct files_struct *fsp = state->fsp;
+ struct smbd_lock_element *blocker =
+ &state->locks[state->blocker];
+
+ DBG_DEBUG("Setting lock_failure_offset=%"PRIu64"\n",
+ blocker->offset);
+
+ fsp->fsp_flags.lock_failure_seen = true;
+ fsp->lock_failure_offset = blocker->offset;
+ return status;
+ }
+
+ tevent_req_received(req);
+
+ return NT_STATUS_OK;
+}
+
+bool smbd_smb1_do_locks_extract_smbreq(
+ struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct smb_request **psmbreq)
+{
+ struct smbd_smb1_do_locks_state *state = tevent_req_data(
+ req, struct smbd_smb1_do_locks_state);
+
+ DBG_DEBUG("req=%p, state=%p, state->smbreq=%p\n",
+ req,
+ state,
+ state->smbreq);
+
+ if (state->smbreq == NULL) {
+ return false;
+ }
+ *psmbreq = talloc_move(mem_ctx, &state->smbreq);
+ return true;
+}
+
+void smbd_smb1_brl_finish_by_req(struct tevent_req *req, NTSTATUS status)
+{
+ DBG_DEBUG("req=%p, status=%s\n", req, nt_errstr(status));
+
+ if (NT_STATUS_IS_OK(status)) {
+ tevent_req_done(req);
+ } else {
+ tevent_req_nterror(req, status);
+ }
+}
+
+bool smbd_smb1_brl_finish_by_lock(
+ struct files_struct *fsp,
+ bool large_offset,
+ struct smbd_lock_element lock,
+ NTSTATUS finish_status)
+{
+ struct tevent_req **blocked = fsp->blocked_smb1_lock_reqs;
+ size_t num_blocked = talloc_array_length(blocked);
+ size_t i;
+
+ DBG_DEBUG("num_blocked=%zu\n", num_blocked);
+
+ for (i=0; i<num_blocked; i++) {
+ struct tevent_req *req = blocked[i];
+ struct smbd_smb1_do_locks_state *state = tevent_req_data(
+ req, struct smbd_smb1_do_locks_state);
+ uint16_t j;
+
+ DBG_DEBUG("i=%zu, req=%p\n", i, req);
+
+ if (state->large_offset != large_offset) {
+ continue;
+ }
+
+ for (j=0; j<state->num_locks; j++) {
+ struct smbd_lock_element *l = &state->locks[j];
+
+ if ((lock.smblctx == l->smblctx) &&
+ (lock.offset == l->offset) &&
+ (lock.count == l->count)) {
+ smbd_smb1_brl_finish_by_req(
+ req, finish_status);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static struct files_struct *smbd_smb1_brl_finish_by_mid_fn(
+ struct files_struct *fsp, void *private_data)
+{
+ struct tevent_req **blocked = fsp->blocked_smb1_lock_reqs;
+ size_t num_blocked = talloc_array_length(blocked);
+ uint64_t mid = *((uint64_t *)private_data);
+ size_t i;
+
+ DBG_DEBUG("fsp=%p, num_blocked=%zu\n", fsp, num_blocked);
+
+ for (i=0; i<num_blocked; i++) {
+ struct tevent_req *req = blocked[i];
+ struct smbd_smb1_do_locks_state *state = tevent_req_data(
+ req, struct smbd_smb1_do_locks_state);
+ struct smb_request *smbreq = state->smbreq;
+
+ if (smbreq->mid == mid) {
+ tevent_req_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
+ return fsp;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * This walks the list of fsps, we store the blocked reqs attached to
+ * them. It can be expensive, but this is legacy SMB1 and trying to
+ * remember looking at traces I don't really see many of those calls.
+ */
+
+bool smbd_smb1_brl_finish_by_mid(
+ struct smbd_server_connection *sconn, uint64_t mid)
+{
+ struct files_struct *found = files_forall(
+ sconn, smbd_smb1_brl_finish_by_mid_fn, &mid);
+ return (found != NULL);
+}
diff --git a/source3/smbd/close.c b/source3/smbd/close.c
new file mode 100644
index 0000000..bbca474
--- /dev/null
+++ b/source3/smbd/close.c
@@ -0,0 +1,1742 @@
+/*
+ Unix SMB/CIFS implementation.
+ file closing
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 1992-2007.
+ Copyright (C) Volker Lendecke 2005
+
+ 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 "system/filesys.h"
+#include "lib/util/server_id.h"
+#include "printing.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "smbd/smbXsrv_open.h"
+#include "smbd/scavenger.h"
+#include "fake_file.h"
+#include "transfer_file.h"
+#include "auth.h"
+#include "messages.h"
+#include "../librpc/gen_ndr/open_files.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "source3/smbd/dir.h"
+
+/****************************************************************************
+ Run a file if it is a magic script.
+****************************************************************************/
+
+static NTSTATUS check_magic(struct files_struct *fsp)
+{
+ int ret;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *magic_output = NULL;
+ SMB_STRUCT_STAT st;
+ int tmp_fd, outfd;
+ TALLOC_CTX *ctx = NULL;
+ const char *p;
+ struct connection_struct *conn = fsp->conn;
+ char *fname = NULL;
+ NTSTATUS status;
+
+ if (!*lp_magic_script(talloc_tos(), lp_sub, SNUM(conn))) {
+ return NT_STATUS_OK;
+ }
+
+ DEBUG(5,("checking magic for %s\n", fsp_str_dbg(fsp)));
+
+ ctx = talloc_stackframe();
+
+ fname = fsp->fsp_name->base_name;
+
+ if (!(p = strrchr_m(fname,'/'))) {
+ p = fname;
+ } else {
+ p++;
+ }
+
+ if (!strequal(lp_magic_script(talloc_tos(), lp_sub, SNUM(conn)),p)) {
+ status = NT_STATUS_OK;
+ goto out;
+ }
+
+ if (*lp_magic_output(talloc_tos(), lp_sub, SNUM(conn))) {
+ magic_output = lp_magic_output(talloc_tos(), lp_sub, SNUM(conn));
+ } else {
+ magic_output = talloc_asprintf(ctx,
+ "%s.out",
+ fname);
+ }
+ if (!magic_output) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ /* Ensure we don't depend on user's PATH. */
+ p = talloc_asprintf(ctx, "./%s", fname);
+ if (!p) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ if (chmod(fname, 0755) == -1) {
+ status = map_nt_error_from_unix(errno);
+ goto out;
+ }
+ ret = smbrun(p, &tmp_fd, NULL);
+ DEBUG(3,("Invoking magic command %s gave %d\n",
+ p,ret));
+
+ unlink(fname);
+ if (ret != 0 || tmp_fd == -1) {
+ if (tmp_fd != -1) {
+ close(tmp_fd);
+ }
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto out;
+ }
+ outfd = open(magic_output, O_CREAT|O_EXCL|O_RDWR, 0600);
+ if (outfd == -1) {
+ int err = errno;
+ close(tmp_fd);
+ status = map_nt_error_from_unix(err);
+ goto out;
+ }
+
+ if (sys_fstat(tmp_fd, &st, false) == -1) {
+ int err = errno;
+ close(tmp_fd);
+ close(outfd);
+ status = map_nt_error_from_unix(err);
+ goto out;
+ }
+
+ if (transfer_file(tmp_fd,outfd,(off_t)st.st_ex_size) == (off_t)-1) {
+ int err = errno;
+ close(tmp_fd);
+ close(outfd);
+ status = map_nt_error_from_unix(err);
+ goto out;
+ }
+ close(tmp_fd);
+ if (close(outfd) == -1) {
+ status = map_nt_error_from_unix(errno);
+ goto out;
+ }
+
+ status = NT_STATUS_OK;
+
+ out:
+ TALLOC_FREE(ctx);
+ return status;
+}
+
+/****************************************************************************
+ Delete all streams
+****************************************************************************/
+
+NTSTATUS delete_all_streams(connection_struct *conn,
+ const struct smb_filename *smb_fname)
+{
+ struct stream_struct *stream_info = NULL;
+ unsigned int i;
+ unsigned int num_streams = 0;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ status = vfs_fstreaminfo(smb_fname->fsp, talloc_tos(),
+ &num_streams, &stream_info);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)) {
+ DEBUG(10, ("no streams around\n"));
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("vfs_fstreaminfo failed: %s\n",
+ nt_errstr(status)));
+ goto fail;
+ }
+
+ DEBUG(10, ("delete_all_streams found %d streams\n",
+ num_streams));
+
+ if (num_streams == 0) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ for (i=0; i<num_streams; i++) {
+ int res;
+ struct smb_filename *smb_fname_stream;
+
+ if (strequal(stream_info[i].name, "::$DATA")) {
+ continue;
+ }
+
+ status = synthetic_pathref(talloc_tos(),
+ conn->cwd_fsp,
+ smb_fname->base_name,
+ stream_info[i].name,
+ NULL,
+ smb_fname->twrp,
+ (smb_fname->flags &
+ ~SMB_FILENAME_POSIX_PATH),
+ &smb_fname_stream);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("talloc_aprintf failed\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ res = SMB_VFS_UNLINKAT(conn,
+ conn->cwd_fsp,
+ smb_fname_stream,
+ 0);
+
+ if (res == -1) {
+ status = map_nt_error_from_unix(errno);
+ DEBUG(10, ("Could not delete stream %s: %s\n",
+ smb_fname_str_dbg(smb_fname_stream),
+ strerror(errno)));
+ TALLOC_FREE(smb_fname_stream);
+ break;
+ }
+ TALLOC_FREE(smb_fname_stream);
+ }
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct has_other_nonposix_opens_state {
+ files_struct *fsp;
+ bool found_another;
+};
+
+static bool has_other_nonposix_opens_fn(
+ struct share_mode_entry *e,
+ bool *modified,
+ void *private_data)
+{
+ struct has_other_nonposix_opens_state *state = private_data;
+ struct files_struct *fsp = state->fsp;
+
+ if (e->name_hash != fsp->name_hash) {
+ return false;
+ }
+ if (e->flags & SHARE_MODE_FLAG_POSIX_OPEN) {
+ return false;
+ }
+ if (e->share_file_id == fh_get_gen_id(fsp->fh)) {
+ struct server_id self = messaging_server_id(
+ fsp->conn->sconn->msg_ctx);
+ if (server_id_equal(&self, &e->pid)) {
+ return false;
+ }
+ }
+ if (share_entry_stale_pid(e)) {
+ return false;
+ }
+
+ state->found_another = true;
+ return true;
+}
+
+bool has_other_nonposix_opens(struct share_mode_lock *lck,
+ struct files_struct *fsp)
+{
+ struct has_other_nonposix_opens_state state = { .fsp = fsp };
+ bool ok;
+
+ ok = share_mode_forall_entries(
+ lck, has_other_nonposix_opens_fn, &state);
+ if (!ok) {
+ return false;
+ }
+ return state.found_another;
+}
+
+struct close_share_mode_lock_state {
+ struct share_mode_entry_prepare_state prepare_state;
+ const char *object_type;
+ struct files_struct *fsp;
+ enum file_close_type close_type;
+ bool delete_object;
+ bool got_tokens;
+ const struct security_unix_token *del_token;
+ const struct security_token *del_nt_token;
+ bool reset_delete_on_close;
+ share_mode_entry_prepare_unlock_fn_t cleanup_fn;
+};
+
+static void close_share_mode_lock_prepare(struct share_mode_lock *lck,
+ bool *keep_locked,
+ void *private_data)
+{
+ struct close_share_mode_lock_state *state =
+ (struct close_share_mode_lock_state *)private_data;
+ struct files_struct *fsp = state->fsp;
+ bool normal_close;
+ bool ok;
+
+ /*
+ * By default drop the g_lock again if we leave the
+ * tdb chainlock.
+ */
+ *keep_locked = false;
+
+ if (fsp->oplock_type != NO_OPLOCK) {
+ ok = remove_share_oplock(lck, fsp);
+ if (!ok) {
+ struct file_id_buf buf;
+
+ DBG_ERR("failed to remove share oplock for "
+ "%s %s, %s, %s\n",
+ state->object_type,
+ fsp_str_dbg(fsp), fsp_fnum_dbg(fsp),
+ file_id_str_buf(fsp->file_id, &buf));
+ }
+ }
+
+ if (fsp->fsp_flags.write_time_forced) {
+ NTTIME mtime = share_mode_changed_write_time(lck);
+ struct timespec ts = nt_time_to_full_timespec(mtime);
+
+ DBG_DEBUG("write time forced for %s %s\n",
+ state->object_type, fsp_str_dbg(fsp));
+ set_close_write_time(fsp, ts);
+ } else if (fsp->fsp_flags.update_write_time_on_close) {
+ /* Someone had a pending write. */
+ if (is_omit_timespec(&fsp->close_write_time)) {
+ DBG_DEBUG("update to current time for %s %s\n",
+ state->object_type, fsp_str_dbg(fsp));
+ /* Update to current time due to "normal" write. */
+ set_close_write_time(fsp, timespec_current());
+ } else {
+ DBG_DEBUG("write time pending for %s %s\n",
+ state->object_type, fsp_str_dbg(fsp));
+ /* Update to time set on close call. */
+ set_close_write_time(fsp, fsp->close_write_time);
+ }
+ }
+
+ if (fsp->fsp_flags.initial_delete_on_close &&
+ !is_delete_on_close_set(lck, fsp->name_hash)) {
+ /* Initial delete on close was set and no one else
+ * wrote a real delete on close. */
+
+ fsp->fsp_flags.delete_on_close = true;
+ set_delete_on_close_lck(fsp, lck,
+ fsp->conn->session_info->security_token,
+ fsp->conn->session_info->unix_token);
+ }
+
+ state->delete_object = is_delete_on_close_set(lck, fsp->name_hash) &&
+ !has_other_nonposix_opens(lck, fsp);
+
+ /*
+ * NT can set delete_on_close of the last open
+ * reference to a file.
+ */
+
+ normal_close = (state->close_type == NORMAL_CLOSE || state->close_type == SHUTDOWN_CLOSE);
+ if (!normal_close) {
+ /*
+ * Never try to delete the file/directory for ERROR_CLOSE
+ */
+ state->delete_object = false;
+ }
+
+ if (!state->delete_object) {
+ ok = del_share_mode(lck, fsp);
+ if (!ok) {
+ DBG_ERR("Could not delete share entry for %s %s\n",
+ state->object_type, fsp_str_dbg(fsp));
+ }
+ return;
+ }
+
+ /*
+ * We're going to remove the file/directory
+ * so keep the g_lock after the tdb chainlock
+ * is left, so we hold the share_mode_lock
+ * also during the deletion
+ */
+ *keep_locked = true;
+
+ state->got_tokens = get_delete_on_close_token(lck, fsp->name_hash,
+ &state->del_nt_token, &state->del_token);
+ if (state->close_type != ERROR_CLOSE) {
+ SMB_ASSERT(state->got_tokens);
+ }
+}
+
+static void close_share_mode_lock_cleanup(struct share_mode_lock *lck,
+ void *private_data)
+{
+ struct close_share_mode_lock_state *state =
+ (struct close_share_mode_lock_state *)private_data;
+ struct files_struct *fsp = state->fsp;
+ bool ok;
+
+ if (state->reset_delete_on_close) {
+ reset_delete_on_close_lck(fsp, lck);
+ }
+
+ ok = del_share_mode(lck, fsp);
+ if (!ok) {
+ DBG_ERR("Could not delete share entry for %s %s\n",
+ state->object_type, fsp_str_dbg(fsp));
+ }
+}
+
+/****************************************************************************
+ Deal with removing a share mode on last close.
+****************************************************************************/
+
+static NTSTATUS close_remove_share_mode(files_struct *fsp,
+ enum file_close_type close_type)
+{
+ connection_struct *conn = fsp->conn;
+ struct close_share_mode_lock_state lck_state = {};
+ bool changed_user = false;
+ NTSTATUS status = NT_STATUS_OK;
+ NTSTATUS tmp_status;
+ NTSTATUS ulstatus;
+ struct file_id id;
+ struct smb_filename *parent_fname = NULL;
+ struct smb_filename *base_fname = NULL;
+ int ret;
+
+ /* Ensure any pending write time updates are done. */
+ if (fsp->update_write_time_event) {
+ fsp_flush_write_time_update(fsp);
+ }
+
+ /*
+ * Lock the share entries, and determine if we should delete
+ * on close. If so delete whilst the lock is still in effect.
+ * This prevents race conditions with the file being created. JRA.
+ */
+
+ lck_state = (struct close_share_mode_lock_state) {
+ .fsp = fsp,
+ .object_type = "file",
+ .close_type = close_type,
+ };
+
+ status = share_mode_entry_prepare_lock_del(&lck_state.prepare_state,
+ fsp->file_id,
+ close_share_mode_lock_prepare,
+ &lck_state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("share_mode_entry_prepare_lock_del() failed for %s - %s\n",
+ fsp_str_dbg(fsp), nt_errstr(status));
+ return status;
+ }
+
+ /* Remove the oplock before potentially deleting the file. */
+ if (fsp->oplock_type != NO_OPLOCK) {
+ release_file_oplock(fsp);
+ }
+
+ /*
+ * NT can set delete_on_close of the last open
+ * reference to a file.
+ */
+
+ if (!lck_state.delete_object) {
+ status = NT_STATUS_OK;
+ goto done;
+ }
+
+ /*
+ * Ok, we have to delete the file
+ */
+ lck_state.cleanup_fn = close_share_mode_lock_cleanup;
+
+ DEBUG(5,("close_remove_share_mode: file %s. Delete on close was set "
+ "- deleting file.\n", fsp_str_dbg(fsp)));
+
+ /*
+ * Don't try to update the write time when we delete the file
+ */
+ fsp->fsp_flags.update_write_time_on_close = false;
+
+ if (lck_state.got_tokens &&
+ !unix_token_equal(lck_state.del_token, get_current_utok(conn)))
+ {
+ /* Become the user who requested the delete. */
+
+ DEBUG(5,("close_remove_share_mode: file %s. "
+ "Change user to uid %u\n",
+ fsp_str_dbg(fsp),
+ (unsigned int)lck_state.del_token->uid));
+
+ if (!push_sec_ctx()) {
+ smb_panic("close_remove_share_mode: file %s. failed to push "
+ "sec_ctx.\n");
+ }
+
+ set_sec_ctx(lck_state.del_token->uid,
+ lck_state.del_token->gid,
+ lck_state.del_token->ngroups,
+ lck_state.del_token->groups,
+ lck_state.del_nt_token);
+
+ changed_user = true;
+ }
+
+ /* We can only delete the file if the name we have is still valid and
+ hasn't been renamed. */
+
+ tmp_status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(tmp_status)) {
+ DEBUG(5,("close_remove_share_mode: file %s. Delete on close "
+ "was set and stat failed with error %s\n",
+ fsp_str_dbg(fsp), nt_errstr(tmp_status)));
+ /*
+ * Don't save the errno here, we ignore this error
+ */
+ goto done;
+ }
+
+ id = vfs_file_id_from_sbuf(conn, &fsp->fsp_name->st);
+
+ if (!file_id_equal(&fsp->file_id, &id)) {
+ struct file_id_buf ftmp1, ftmp2;
+ DEBUG(5,("close_remove_share_mode: file %s. Delete on close "
+ "was set and dev and/or inode does not match\n",
+ fsp_str_dbg(fsp)));
+ DEBUG(5,("close_remove_share_mode: file %s. stored file_id %s, "
+ "stat file_id %s\n",
+ fsp_str_dbg(fsp),
+ file_id_str_buf(fsp->file_id, &ftmp1),
+ file_id_str_buf(id, &ftmp2)));
+ /*
+ * Don't save the errno here, we ignore this error
+ */
+ goto done;
+ }
+
+ if ((conn->fs_capabilities & FILE_NAMED_STREAMS)
+ && !fsp_is_alternate_stream(fsp)) {
+
+ status = delete_all_streams(conn, fsp->fsp_name);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("delete_all_streams failed: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+ }
+
+ if (fsp->fsp_flags.kernel_share_modes_taken) {
+ /*
+ * A file system sharemode could block the unlink;
+ * remove filesystem sharemodes first.
+ */
+ ret = SMB_VFS_FILESYSTEM_SHAREMODE(fsp, 0, 0);
+ if (ret == -1) {
+ DBG_INFO("Removing file system sharemode for %s "
+ "failed: %s\n",
+ fsp_str_dbg(fsp), strerror(errno));
+ }
+
+ fsp->fsp_flags.kernel_share_modes_taken = false;
+ }
+
+ status = parent_pathref(talloc_tos(),
+ conn->cwd_fsp,
+ fsp->fsp_name,
+ &parent_fname,
+ &base_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ ret = SMB_VFS_UNLINKAT(conn,
+ parent_fname->fsp,
+ base_fname,
+ 0);
+ TALLOC_FREE(parent_fname);
+ base_fname = NULL;
+ if (ret != 0) {
+ /*
+ * This call can potentially fail as another smbd may
+ * have had the file open with delete on close set and
+ * deleted it when its last reference to this file
+ * went away. Hence we log this but not at debug level
+ * zero.
+ */
+
+ DEBUG(5,("close_remove_share_mode: file %s. Delete on close "
+ "was set and unlink failed with error %s\n",
+ fsp_str_dbg(fsp), strerror(errno)));
+
+ status = map_nt_error_from_unix(errno);
+ }
+
+ /* As we now have POSIX opens which can unlink
+ * with other open files we may have taken
+ * this code path with more than one share mode
+ * entry - ensure we only delete once by resetting
+ * the delete on close flag. JRA.
+ */
+
+ fsp->fsp_flags.delete_on_close = false;
+ fsp->fsp_flags.fstat_before_close = false;
+ lck_state.reset_delete_on_close = true;
+
+ done:
+
+ if (changed_user) {
+ /* unbecome user. */
+ pop_sec_ctx();
+ }
+
+ if (fsp->fsp_flags.kernel_share_modes_taken) {
+ /* remove filesystem sharemodes */
+ ret = SMB_VFS_FILESYSTEM_SHAREMODE(fsp, 0, 0);
+ if (ret == -1) {
+ DBG_INFO("Removing file system sharemode for "
+ "%s failed: %s\n",
+ fsp_str_dbg(fsp), strerror(errno));
+ }
+ }
+
+ ulstatus = share_mode_entry_prepare_unlock(&lck_state.prepare_state,
+ lck_state.cleanup_fn,
+ &lck_state);
+ if (!NT_STATUS_IS_OK(ulstatus)) {
+ DBG_ERR("share_mode_entry_prepare_unlock() failed for %s - %s\n",
+ fsp_str_dbg(fsp), nt_errstr(ulstatus));
+ smb_panic("share_mode_entry_prepare_unlock() failed!");
+ }
+
+ if (lck_state.delete_object) {
+ /*
+ * Do the notification after we released the share
+ * mode lock. Inside notify_fname we take out another
+ * tdb lock. With ctdb also accessing our databases,
+ * this can lead to deadlocks. Putting this notify
+ * after the TALLOC_FREE(lck) above we avoid locking
+ * two records simultaneously. Notifies are async and
+ * informational only, so calling the notify_fname
+ * without holding the share mode lock should not do
+ * any harm.
+ */
+ notify_fname(conn, NOTIFY_ACTION_REMOVED,
+ FILE_NOTIFY_CHANGE_FILE_NAME,
+ fsp->fsp_name->base_name);
+ }
+
+ return status;
+}
+
+void set_close_write_time(struct files_struct *fsp, struct timespec ts)
+{
+ DEBUG(6,("close_write_time: %s" , time_to_asc(convert_timespec_to_time_t(ts))));
+
+ if (is_omit_timespec(&ts)) {
+ return;
+ }
+ fsp->fsp_flags.write_time_forced = false;
+ fsp->fsp_flags.update_write_time_on_close = true;
+ fsp->close_write_time = ts;
+}
+
+static void update_write_time_on_close_share_mode_fn(struct share_mode_lock *lck,
+ void *private_data)
+{
+ struct files_struct *fsp =
+ talloc_get_type_abort(private_data,
+ struct files_struct);
+ NTTIME share_mtime = share_mode_changed_write_time(lck);
+
+ /*
+ * On close if we're changing the real file time we
+ * must update it in the open file db too.
+ */
+ share_mode_set_old_write_time(lck, fsp->close_write_time);
+
+ /*
+ * Close write times overwrite sticky write times
+ * so we must replace any sticky write time here.
+ */
+ if (!null_nttime(share_mtime)) {
+ share_mode_set_changed_write_time(lck, fsp->close_write_time);
+ }
+}
+
+static NTSTATUS update_write_time_on_close(struct files_struct *fsp)
+{
+ struct smb_file_time ft;
+ NTSTATUS status;
+
+ init_smb_file_time(&ft);
+
+ if (!(fsp->fsp_flags.update_write_time_on_close)) {
+ return NT_STATUS_OK;
+ }
+
+ if (is_omit_timespec(&fsp->close_write_time)) {
+ fsp->close_write_time = timespec_current();
+ }
+
+ /* Ensure we have a valid stat struct for the source. */
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!VALID_STAT(fsp->fsp_name->st)) {
+ /* if it doesn't seem to be a real file */
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * We're being called after close_remove_share_mode() inside
+ * close_normal_file() so it's quite normal to not have an
+ * existing share. So just ignore the result of
+ * share_mode_do_locked_vfs_denied()...
+ */
+ share_mode_do_locked_vfs_denied(fsp->file_id,
+ update_write_time_on_close_share_mode_fn,
+ fsp);
+
+ ft.mtime = fsp->close_write_time;
+ /* As this is a close based update, we are not directly changing the
+ file attributes from a client call, but indirectly from a write. */
+ status = smb_set_file_time(fsp->conn, fsp, fsp->fsp_name, &ft, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10,("update_write_time_on_close: smb_set_file_time "
+ "on file %s returned %s\n",
+ fsp_str_dbg(fsp),
+ nt_errstr(status)));
+ return status;
+ }
+
+ return status;
+}
+
+static NTSTATUS ntstatus_keeperror(NTSTATUS s1, NTSTATUS s2)
+{
+ if (!NT_STATUS_IS_OK(s1)) {
+ return s1;
+ }
+ return s2;
+}
+
+static void assert_no_pending_aio(struct files_struct *fsp,
+ enum file_close_type close_type)
+{
+ struct smbXsrv_client *client = global_smbXsrv_client;
+ size_t num_connections_alive;
+ unsigned num_requests = fsp->num_aio_requests;
+
+ if (num_requests == 0) {
+ return;
+ }
+
+ num_connections_alive = smbXsrv_client_valid_connections(client);
+
+ if (close_type == SHUTDOWN_CLOSE && num_connections_alive == 0) {
+ /*
+ * fsp->aio_requests and the contents (fsp->aio_requests[x])
+ * are both independently owned by fsp and are not in a
+ * talloc hierarchy. This allows the fsp->aio_requests array to
+ * be reallocated independently of the array contents so it can
+ * grow on demand.
+ *
+ * This means we must ensure order of deallocation
+ * on a SHUTDOWN_CLOSE by deallocating the fsp->aio_requests[x]
+ * contents first, as their destructors access the
+ * fsp->aio_request array. If we don't deallocate them
+ * first, when fsp is deallocated fsp->aio_requests
+ * could have been deallocated *before* its contents
+ * fsp->aio_requests[x], causing a crash.
+ */
+ while (fsp->num_aio_requests != 0) {
+ /*
+ * NB. We *MUST* use
+ * talloc_free(fsp->aio_requests[0]),
+ * and *NOT* TALLOC_FREE() here, as
+ * TALLOC_FREE(fsp->aio_requests[0])
+ * will overwrite any new contents of
+ * fsp->aio_requests[0] that were
+ * copied into it via the destructor
+ * aio_del_req_from_fsp().
+ *
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=14515
+ */
+ talloc_free(fsp->aio_requests[0]);
+ }
+ return;
+ }
+
+ DBG_ERR("fsp->num_aio_requests=%u\n", num_requests);
+ smb_panic("can not close with outstanding aio requests");
+ return;
+}
+
+/****************************************************************************
+ Close a file.
+
+ close_type can be NORMAL_CLOSE=0,SHUTDOWN_CLOSE,ERROR_CLOSE.
+ printing and magic scripts are only run on normal close.
+ delete on close is done on normal and shutdown close.
+****************************************************************************/
+
+static NTSTATUS close_normal_file(struct smb_request *req, files_struct *fsp,
+ enum file_close_type close_type)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ NTSTATUS tmp;
+ connection_struct *conn = fsp->conn;
+ bool is_durable = false;
+
+ SMB_ASSERT(fsp->fsp_flags.is_fsa);
+
+ assert_no_pending_aio(fsp, close_type);
+
+ while (talloc_array_length(fsp->blocked_smb1_lock_reqs) != 0) {
+ smbd_smb1_brl_finish_by_req(
+ fsp->blocked_smb1_lock_reqs[0],
+ NT_STATUS_RANGE_NOT_LOCKED);
+ }
+
+ /*
+ * If we're flushing on a close we can get a write
+ * error here, we must remember this.
+ */
+
+ if (NT_STATUS_IS_OK(status) && fsp->op != NULL) {
+ is_durable = fsp->op->global->durable;
+ }
+
+ if (close_type != SHUTDOWN_CLOSE) {
+ is_durable = false;
+ }
+
+ if (is_durable) {
+ DATA_BLOB new_cookie = data_blob_null;
+
+ tmp = SMB_VFS_DURABLE_DISCONNECT(fsp,
+ fsp->op->global->backend_cookie,
+ fsp->op,
+ &new_cookie);
+ if (NT_STATUS_IS_OK(tmp)) {
+ struct timeval tv;
+ NTTIME now;
+
+ if (req != NULL) {
+ tv = req->request_time;
+ } else {
+ tv = timeval_current();
+ }
+ now = timeval_to_nttime(&tv);
+
+ data_blob_free(&fsp->op->global->backend_cookie);
+ fsp->op->global->backend_cookie = new_cookie;
+
+ fsp->op->compat = NULL;
+ tmp = smbXsrv_open_close(fsp->op, now);
+ if (!NT_STATUS_IS_OK(tmp)) {
+ DEBUG(1, ("Failed to update smbXsrv_open "
+ "record when disconnecting durable "
+ "handle for file %s: %s - "
+ "proceeding with normal close\n",
+ fsp_str_dbg(fsp), nt_errstr(tmp)));
+ }
+ scavenger_schedule_disconnected(fsp);
+ } else {
+ DEBUG(1, ("Failed to disconnect durable handle for "
+ "file %s: %s - proceeding with normal "
+ "close\n", fsp_str_dbg(fsp), nt_errstr(tmp)));
+ }
+ if (!NT_STATUS_IS_OK(tmp)) {
+ is_durable = false;
+ }
+ }
+
+ if (is_durable) {
+ /*
+ * This is the case where we successfully disconnected
+ * a durable handle and closed the underlying file.
+ * In all other cases, we proceed with a genuine close.
+ */
+ DEBUG(10, ("%s disconnected durable handle for file %s\n",
+ conn->session_info->unix_info->unix_name,
+ fsp_str_dbg(fsp)));
+ return NT_STATUS_OK;
+ }
+
+ if (fsp->op != NULL) {
+ /*
+ * Make sure the handle is not marked as durable anymore
+ */
+ fsp->op->global->durable = false;
+ }
+
+ /* If this is an old DOS or FCB open and we have multiple opens on
+ the same handle we only have one share mode. Ensure we only remove
+ the share mode on the last close. */
+
+ if (fh_get_refcount(fsp->fh) == 1) {
+ /* Should we return on error here... ? */
+ tmp = close_remove_share_mode(fsp, close_type);
+ status = ntstatus_keeperror(status, tmp);
+ }
+
+ locking_close_file(fsp, close_type);
+
+ /*
+ * Ensure pending modtime is set before closing underlying fd.
+ */
+
+ tmp = update_write_time_on_close(fsp);
+ if (NT_STATUS_EQUAL(tmp, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ /*
+ * Someone renamed the file or a parent directory containing
+ * this file. We can't do anything about this, eat the error.
+ */
+ tmp = NT_STATUS_OK;
+ }
+ status = ntstatus_keeperror(status, tmp);
+
+ tmp = fd_close(fsp);
+ status = ntstatus_keeperror(status, tmp);
+
+ /* check for magic scripts */
+ if (close_type == NORMAL_CLOSE) {
+ tmp = check_magic(fsp);
+ status = ntstatus_keeperror(status, tmp);
+ }
+
+ DEBUG(2,("%s closed file %s (numopen=%d) %s\n",
+ conn->session_info->unix_info->unix_name, fsp_str_dbg(fsp),
+ conn->num_files_open - 1,
+ nt_errstr(status) ));
+
+ return status;
+}
+/****************************************************************************
+ Function used by reply_rmdir to delete an entire directory
+ tree recursively. Return True on ok, False on fail.
+****************************************************************************/
+
+NTSTATUS recursive_rmdir(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct smb_filename *smb_dname)
+{
+ const char *dname = NULL;
+ char *talloced = NULL;
+ struct smb_Dir *dir_hnd = NULL;
+ struct files_struct *dirfsp = NULL;
+ int retval;
+ NTSTATUS status = NT_STATUS_OK;
+
+ SMB_ASSERT(!is_ntfs_stream_smb_fname(smb_dname));
+
+ status = OpenDir(talloc_tos(),
+ conn,
+ smb_dname,
+ NULL,
+ 0,
+ &dir_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ dirfsp = dir_hnd_fetch_fsp(dir_hnd);
+
+ while ((dname = ReadDirName(dir_hnd, &talloced))) {
+ struct smb_filename *atname = NULL;
+ struct smb_filename *smb_dname_full = NULL;
+ char *fullname = NULL;
+ bool do_break = true;
+ int unlink_flags = 0;
+
+ if (ISDOT(dname) || ISDOTDOT(dname)) {
+ TALLOC_FREE(talloced);
+ continue;
+ }
+
+ /* Construct the full name. */
+ fullname = talloc_asprintf(ctx,
+ "%s/%s",
+ smb_dname->base_name,
+ dname);
+ if (!fullname) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err_break;
+ }
+
+ smb_dname_full = synthetic_smb_fname(talloc_tos(),
+ fullname,
+ NULL,
+ NULL,
+ smb_dname->twrp,
+ smb_dname->flags);
+ if (smb_dname_full == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err_break;
+ }
+
+ if (SMB_VFS_LSTAT(conn, smb_dname_full) != 0) {
+ status = map_nt_error_from_unix(errno);
+ goto err_break;
+ }
+
+ if (smb_dname_full->st.st_ex_mode & S_IFDIR) {
+ status = recursive_rmdir(ctx, conn, smb_dname_full);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_break;
+ }
+ unlink_flags = AT_REMOVEDIR;
+ }
+
+ status = synthetic_pathref(talloc_tos(),
+ dirfsp,
+ dname,
+ NULL,
+ &smb_dname_full->st,
+ smb_dname_full->twrp,
+ smb_dname_full->flags,
+ &atname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_break;
+ }
+
+ if (!is_visible_fsp(atname->fsp)) {
+ TALLOC_FREE(smb_dname_full);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(atname);
+ continue;
+ }
+
+ retval = SMB_VFS_UNLINKAT(conn,
+ dirfsp,
+ atname,
+ unlink_flags);
+ if (retval != 0) {
+ status = map_nt_error_from_unix(errno);
+ goto err_break;
+ }
+
+ /* Successful iteration. */
+ do_break = false;
+
+ err_break:
+ TALLOC_FREE(smb_dname_full);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(atname);
+ if (do_break) {
+ break;
+ }
+ }
+ TALLOC_FREE(dir_hnd);
+ return status;
+}
+
+/****************************************************************************
+ The internals of the rmdir code - called elsewhere.
+****************************************************************************/
+
+static NTSTATUS rmdir_internals(TALLOC_CTX *ctx, struct files_struct *fsp)
+{
+ struct connection_struct *conn = fsp->conn;
+ struct smb_filename *smb_dname = fsp->fsp_name;
+ struct smb_filename *parent_fname = NULL;
+ struct smb_filename *at_fname = NULL;
+ const char *dname = NULL;
+ char *talloced = NULL;
+ struct smb_Dir *dir_hnd = NULL;
+ struct files_struct *dirfsp = NULL;
+ int unlink_flags = 0;
+ NTSTATUS status;
+ int ret;
+
+ SMB_ASSERT(!is_ntfs_stream_smb_fname(smb_dname));
+
+ status = parent_pathref(talloc_tos(),
+ conn->cwd_fsp,
+ fsp->fsp_name,
+ &parent_fname,
+ &at_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * Todo: use SMB_VFS_STATX() once it's available.
+ */
+
+ /* Might be a symlink. */
+ ret = SMB_VFS_LSTAT(conn, smb_dname);
+ if (ret != 0) {
+ TALLOC_FREE(parent_fname);
+ return map_nt_error_from_unix(errno);
+ }
+
+ if (S_ISLNK(smb_dname->st.st_ex_mode)) {
+ /* Is what it points to a directory ? */
+ ret = SMB_VFS_STAT(conn, smb_dname);
+ if (ret != 0) {
+ TALLOC_FREE(parent_fname);
+ return map_nt_error_from_unix(errno);
+ }
+ if (!(S_ISDIR(smb_dname->st.st_ex_mode))) {
+ TALLOC_FREE(parent_fname);
+ return NT_STATUS_NOT_A_DIRECTORY;
+ }
+ } else {
+ unlink_flags = AT_REMOVEDIR;
+ }
+
+ ret = SMB_VFS_UNLINKAT(conn,
+ parent_fname->fsp,
+ at_fname,
+ unlink_flags);
+ if (ret == 0) {
+ TALLOC_FREE(parent_fname);
+ notify_fname(conn, NOTIFY_ACTION_REMOVED,
+ FILE_NOTIFY_CHANGE_DIR_NAME,
+ smb_dname->base_name);
+ return NT_STATUS_OK;
+ }
+
+ if (!((errno == ENOTEMPTY) || (errno == EEXIST))) {
+ DEBUG(3,("rmdir_internals: couldn't remove directory %s : "
+ "%s\n", smb_fname_str_dbg(smb_dname),
+ strerror(errno)));
+ TALLOC_FREE(parent_fname);
+ return map_nt_error_from_unix(errno);
+ }
+
+ /*
+ * Here we know the initial directory unlink failed with
+ * ENOTEMPTY or EEXIST so we know there are objects within.
+ * If we don't have permission to delete files non
+ * visible to the client just fail the directory delete.
+ */
+
+ if (!lp_delete_veto_files(SNUM(conn))) {
+ status = NT_STATUS_DIRECTORY_NOT_EMPTY;
+ goto err;
+ }
+
+ /*
+ * Check to see if the only thing in this directory are
+ * files non-visible to the client. If not, fail the delete.
+ */
+
+ status = OpenDir(talloc_tos(),
+ conn,
+ smb_dname,
+ NULL,
+ 0,
+ &dir_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * Note, we deliberately squash the error here
+ * to avoid leaking information about what we
+ * can't delete.
+ */
+ status = NT_STATUS_DIRECTORY_NOT_EMPTY;
+ goto err;
+ }
+
+ dirfsp = dir_hnd_fetch_fsp(dir_hnd);
+
+ while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) {
+ struct smb_filename *smb_dname_full = NULL;
+ struct smb_filename *direntry_fname = NULL;
+ char *fullname = NULL;
+ int retval;
+
+ if (ISDOT(dname) || ISDOTDOT(dname)) {
+ TALLOC_FREE(talloced);
+ continue;
+ }
+ if (IS_VETO_PATH(conn, dname)) {
+ TALLOC_FREE(talloced);
+ continue;
+ }
+
+ fullname = talloc_asprintf(talloc_tos(),
+ "%s/%s",
+ smb_dname->base_name,
+ dname);
+
+ if (fullname == NULL) {
+ TALLOC_FREE(talloced);
+ status = NT_STATUS_NO_MEMORY;
+ goto err;
+ }
+
+ smb_dname_full = synthetic_smb_fname(talloc_tos(),
+ fullname,
+ NULL,
+ NULL,
+ smb_dname->twrp,
+ smb_dname->flags);
+ if (smb_dname_full == NULL) {
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ status = NT_STATUS_NO_MEMORY;
+ goto err;
+ }
+
+ retval = SMB_VFS_LSTAT(conn, smb_dname_full);
+ if (retval != 0) {
+ status = map_nt_error_from_unix(errno);
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ goto err;
+ }
+
+ if (S_ISLNK(smb_dname_full->st.st_ex_mode)) {
+ /* Could it be an msdfs link ? */
+ if (lp_host_msdfs() &&
+ lp_msdfs_root(SNUM(conn))) {
+ struct smb_filename *smb_atname;
+ smb_atname = synthetic_smb_fname(talloc_tos(),
+ dname,
+ NULL,
+ &smb_dname_full->st,
+ fsp->fsp_name->twrp,
+ fsp->fsp_name->flags);
+ if (smb_atname == NULL) {
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ status = NT_STATUS_NO_MEMORY;
+ goto err;
+ }
+ if (is_msdfs_link(fsp, smb_atname)) {
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ TALLOC_FREE(smb_atname);
+ DBG_DEBUG("got msdfs link name %s "
+ "- can't delete directory %s\n",
+ dname,
+ fsp_str_dbg(fsp));
+ status = NT_STATUS_DIRECTORY_NOT_EMPTY;
+ goto err;
+ }
+ TALLOC_FREE(smb_atname);
+ }
+
+ /* Not a DFS link - could it be a dangling symlink ? */
+ retval = SMB_VFS_STAT(conn, smb_dname_full);
+ if (retval == -1 && (errno == ENOENT || errno == ELOOP)) {
+ /*
+ * Dangling symlink.
+ * Allow delete as "delete veto files = yes"
+ */
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ continue;
+ }
+
+ DBG_DEBUG("got symlink name %s - "
+ "can't delete directory %s\n",
+ dname,
+ fsp_str_dbg(fsp));
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ status = NT_STATUS_DIRECTORY_NOT_EMPTY;
+ goto err;
+ }
+
+ /* Not a symlink, get a pathref. */
+ status = synthetic_pathref(talloc_tos(),
+ dirfsp,
+ dname,
+ NULL,
+ &smb_dname_full->st,
+ smb_dname->twrp,
+ smb_dname->flags,
+ &direntry_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ goto err;
+ }
+
+ if (!is_visible_fsp(direntry_fname->fsp)) {
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ TALLOC_FREE(direntry_fname);
+ continue;
+ }
+
+ /*
+ * We found a client visible name.
+ * We cannot delete this directory.
+ */
+ DBG_DEBUG("got name %s - "
+ "can't delete directory %s\n",
+ dname,
+ fsp_str_dbg(fsp));
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ TALLOC_FREE(direntry_fname);
+ status = NT_STATUS_DIRECTORY_NOT_EMPTY;
+ goto err;
+ }
+
+ /* Do a recursive delete. */
+ RewindDir(dir_hnd);
+
+ while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) {
+ struct smb_filename *direntry_fname = NULL;
+ struct smb_filename *smb_dname_full = NULL;
+ char *fullname = NULL;
+ bool do_break = true;
+ int retval;
+
+ if (ISDOT(dname) || ISDOTDOT(dname)) {
+ TALLOC_FREE(talloced);
+ continue;
+ }
+
+ fullname = talloc_asprintf(ctx,
+ "%s/%s",
+ smb_dname->base_name,
+ dname);
+
+ if (fullname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err_break;
+ }
+
+ smb_dname_full = synthetic_smb_fname(talloc_tos(),
+ fullname,
+ NULL,
+ NULL,
+ smb_dname->twrp,
+ smb_dname->flags);
+ if (smb_dname_full == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err_break;
+ }
+
+ /*
+ * Todo: use SMB_VFS_STATX() once that's available.
+ */
+
+ retval = SMB_VFS_LSTAT(conn, smb_dname_full);
+ if (retval != 0) {
+ status = map_nt_error_from_unix(errno);
+ goto err_break;
+ }
+
+ /*
+ * We are only dealing with VETO'ed objects
+ * here. If it's a symlink, just delete the
+ * link without caring what it is pointing
+ * to.
+ */
+ if (S_ISLNK(smb_dname_full->st.st_ex_mode)) {
+ direntry_fname = synthetic_smb_fname(talloc_tos(),
+ dname,
+ NULL,
+ &smb_dname_full->st,
+ smb_dname->twrp,
+ smb_dname->flags);
+ if (direntry_fname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err_break;
+ }
+ } else {
+ status = synthetic_pathref(talloc_tos(),
+ dirfsp,
+ dname,
+ NULL,
+ &smb_dname_full->st,
+ smb_dname->twrp,
+ smb_dname->flags,
+ &direntry_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_break;
+ }
+
+ if (!is_visible_fsp(direntry_fname->fsp)) {
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(direntry_fname);
+ continue;
+ }
+ }
+
+ unlink_flags = 0;
+
+ if (smb_dname_full->st.st_ex_mode & S_IFDIR) {
+ status = recursive_rmdir(ctx, conn, smb_dname_full);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_break;
+ }
+ unlink_flags = AT_REMOVEDIR;
+ }
+
+ retval = SMB_VFS_UNLINKAT(conn,
+ dirfsp,
+ direntry_fname,
+ unlink_flags);
+ if (retval != 0) {
+ status = map_nt_error_from_unix(errno);
+ goto err_break;
+ }
+
+ /* Successful iteration. */
+ do_break = false;
+
+ err_break:
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(direntry_fname);
+ if (do_break) {
+ break;
+ }
+ }
+
+ /* If we get here, we know NT_STATUS_IS_OK(status) */
+ SMB_ASSERT(NT_STATUS_IS_OK(status));
+
+ /* Retry the rmdir */
+ ret = SMB_VFS_UNLINKAT(conn,
+ parent_fname->fsp,
+ at_fname,
+ AT_REMOVEDIR);
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ }
+
+ err:
+
+ TALLOC_FREE(dir_hnd);
+ TALLOC_FREE(parent_fname);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_NOTICE("couldn't remove directory %s : "
+ "%s\n", smb_fname_str_dbg(smb_dname),
+ nt_errstr(status));
+ return status;
+ }
+
+ notify_fname(conn, NOTIFY_ACTION_REMOVED,
+ FILE_NOTIFY_CHANGE_DIR_NAME,
+ smb_dname->base_name);
+
+ return status;
+}
+
+/****************************************************************************
+ Close a directory opened by an NT SMB call.
+****************************************************************************/
+
+static NTSTATUS close_directory(struct smb_request *req, files_struct *fsp,
+ enum file_close_type close_type)
+{
+ connection_struct *conn = fsp->conn;
+ struct close_share_mode_lock_state lck_state = {};
+ bool changed_user = false;
+ NTSTATUS status = NT_STATUS_OK;
+ NTSTATUS status1 = NT_STATUS_OK;
+ NTSTATUS notify_status;
+ NTSTATUS ulstatus;
+
+ SMB_ASSERT(fsp->fsp_flags.is_fsa);
+
+ if (fsp->conn->sconn->using_smb2) {
+ notify_status = NT_STATUS_NOTIFY_CLEANUP;
+ } else {
+ notify_status = NT_STATUS_OK;
+ }
+
+ assert_no_pending_aio(fsp, close_type);
+
+ /*
+ * NT can set delete_on_close of the last open
+ * reference to a directory also.
+ */
+
+ lck_state = (struct close_share_mode_lock_state) {
+ .fsp = fsp,
+ .object_type = "directory",
+ .close_type = close_type,
+ };
+
+ status = share_mode_entry_prepare_lock_del(&lck_state.prepare_state,
+ fsp->file_id,
+ close_share_mode_lock_prepare,
+ &lck_state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("share_mode_entry_prepare_lock_del() failed for %s - %s\n",
+ fsp_str_dbg(fsp), nt_errstr(status));
+ return status;
+ }
+
+ /*
+ * We don't have directory leases yet, so assert it in order
+ * to skip release_file_oplock().
+ */
+ SMB_ASSERT(fsp->oplock_type == NO_OPLOCK);
+
+ /*
+ * NT can set delete_on_close of the last open
+ * reference to a file.
+ */
+
+ if (!lck_state.delete_object) {
+ status = NT_STATUS_OK;
+ goto done;
+ }
+
+ /*
+ * Ok, we have to delete the directory
+ */
+ lck_state.cleanup_fn = close_share_mode_lock_cleanup;
+
+ if (lck_state.got_tokens &&
+ !unix_token_equal(lck_state.del_token, get_current_utok(conn)))
+ {
+ /* Become the user who requested the delete. */
+
+ DBG_INFO("dir %s. Change user to uid %u\n",
+ fsp_str_dbg(fsp),
+ (unsigned int)lck_state.del_token->uid);
+
+ if (!push_sec_ctx()) {
+ smb_panic("close_directory: failed to push sec_ctx.\n");
+ }
+
+ set_sec_ctx(lck_state.del_token->uid,
+ lck_state.del_token->gid,
+ lck_state.del_token->ngroups,
+ lck_state.del_token->groups,
+ lck_state.del_nt_token);
+
+ changed_user = true;
+ }
+
+ if ((fsp->conn->fs_capabilities & FILE_NAMED_STREAMS)
+ && !is_ntfs_stream_smb_fname(fsp->fsp_name)) {
+
+ status = delete_all_streams(fsp->conn, fsp->fsp_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("delete_all_streams failed: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+ }
+
+ status = rmdir_internals(talloc_tos(), fsp);
+
+ DEBUG(5,("close_directory: %s. Delete on close was set - "
+ "deleting directory returned %s.\n",
+ fsp_str_dbg(fsp), nt_errstr(status)));
+
+ /*
+ * Ensure we remove any change notify requests that would
+ * now fail as the directory has been deleted.
+ */
+
+ if (NT_STATUS_IS_OK(status)) {
+ notify_status = NT_STATUS_DELETE_PENDING;
+ }
+
+done:
+ if (changed_user) {
+ /* unbecome user. */
+ pop_sec_ctx();
+ }
+
+ ulstatus = share_mode_entry_prepare_unlock(&lck_state.prepare_state,
+ lck_state.cleanup_fn,
+ &lck_state);
+ if (!NT_STATUS_IS_OK(ulstatus)) {
+ DBG_ERR("share_mode_entry_prepare_unlock() failed for %s - %s\n",
+ fsp_str_dbg(fsp), nt_errstr(ulstatus));
+ smb_panic("share_mode_entry_prepare_unlock() failed!");
+ }
+
+ remove_pending_change_notify_requests_by_fid(fsp, notify_status);
+
+ status1 = fd_close(fsp);
+
+ if (!NT_STATUS_IS_OK(status1)) {
+ DEBUG(0, ("Could not close dir! fname=%s, fd=%d, err=%d=%s\n",
+ fsp_str_dbg(fsp), fsp_get_pathref_fd(fsp), errno,
+ strerror(errno)));
+ }
+
+ if (NT_STATUS_IS_OK(status) && !NT_STATUS_IS_OK(status1)) {
+ status = status1;
+ }
+ return status;
+}
+
+/****************************************************************************
+ Rundown all SMB-related dependencies of a files struct
+****************************************************************************/
+
+NTSTATUS close_file_smb(struct smb_request *req,
+ struct files_struct *fsp,
+ enum file_close_type close_type)
+{
+ NTSTATUS status;
+
+ /*
+ * This fsp can never be an internal dirfsp. They must
+ * be explicitly closed by TALLOC_FREE of the dir handle.
+ */
+ SMB_ASSERT(!fsp->fsp_flags.is_dirfsp);
+
+ /*
+ * Never call directly on a base fsp
+ */
+ SMB_ASSERT(fsp->stream_fsp == NULL);
+
+ if (fsp->fake_file_handle != NULL) {
+ /*
+ * Named pipes are opened as fake files and
+ * can have pending aio requests. Ensure
+ * we clear out all pending aio on force
+ * shutdown of named pipes also.
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=15423
+ */
+ assert_no_pending_aio(fsp, close_type);
+ status = close_fake_file(req, fsp);
+ } else if (fsp->print_file != NULL) {
+ /* FIXME: return spool errors */
+ print_spool_end(fsp, close_type);
+ fd_close(fsp);
+ status = NT_STATUS_OK;
+ } else if (!fsp->fsp_flags.is_fsa) {
+ if (close_type == NORMAL_CLOSE) {
+ DBG_ERR("unexpected NORMAL_CLOSE for [%s] "
+ "is_fsa[%u] is_pathref[%u] is_directory[%u]\n",
+ fsp_str_dbg(fsp),
+ fsp->fsp_flags.is_fsa,
+ fsp->fsp_flags.is_pathref,
+ fsp->fsp_flags.is_directory);
+ }
+ SMB_ASSERT(close_type != NORMAL_CLOSE);
+ fd_close(fsp);
+ status = NT_STATUS_OK;
+ } else if (fsp->fsp_flags.is_directory) {
+ status = close_directory(req, fsp, close_type);
+ } else {
+ status = close_normal_file(req, fsp, close_type);
+ }
+
+ if (fsp_is_alternate_stream(fsp)) {
+ /*
+ * fsp was a stream, its base_fsp can't be a stream
+ * as well
+ */
+ SMB_ASSERT(!fsp_is_alternate_stream(fsp->base_fsp));
+
+ /*
+ * There's a 1:1 relationship between fsp and a base_fsp
+ */
+ SMB_ASSERT(fsp->base_fsp->stream_fsp == fsp);
+
+ /*
+ * Make base_fsp look standalone now
+ */
+ fsp->base_fsp->stream_fsp = NULL;
+
+ close_file_free(req, &fsp->base_fsp, close_type);
+ }
+
+ fsp_unbind_smb(req, fsp);
+
+ return status;
+}
+
+NTSTATUS close_file_free(struct smb_request *req,
+ struct files_struct **_fsp,
+ enum file_close_type close_type)
+{
+ struct files_struct *fsp = *_fsp;
+ NTSTATUS status;
+
+ status = close_file_smb(req, fsp, close_type);
+
+ file_free(req, fsp);
+ *_fsp = NULL;
+
+ return status;
+}
+
+/****************************************************************************
+ Deal with an (authorized) message to close a file given the share mode
+ entry.
+****************************************************************************/
+
+void msg_close_file(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ files_struct *fsp = NULL;
+ struct file_id id;
+ struct share_mode_entry e;
+ struct smbd_server_connection *sconn =
+ talloc_get_type_abort(private_data,
+ struct smbd_server_connection);
+
+ message_to_share_mode_entry(&id, &e, (char *)data->data);
+
+ if(DEBUGLVL(10)) {
+ char *sm_str = share_mode_str(NULL, 0, &id, &e);
+ if (!sm_str) {
+ smb_panic("talloc failed");
+ }
+ DEBUG(10,("msg_close_file: got request to close share mode "
+ "entry %s\n", sm_str));
+ TALLOC_FREE(sm_str);
+ }
+
+ fsp = file_find_dif(sconn, id, e.share_file_id);
+ if (!fsp) {
+ DEBUG(10,("msg_close_file: failed to find file.\n"));
+ return;
+ }
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+}
diff --git a/source3/smbd/conn.c b/source3/smbd/conn.c
new file mode 100644
index 0000000..27a4d27
--- /dev/null
+++ b/source3/smbd/conn.c
@@ -0,0 +1,267 @@
+/*
+ Unix SMB/CIFS implementation.
+ Manage connections_struct structures
+ Copyright (C) Andrew Tridgell 1998
+ Copyright (C) Alexander Bokovoy 2002
+ Copyright (C) Jeremy Allison 2010
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "lib/util/bitmap.h"
+
+static void conn_free_internal(connection_struct *conn);
+
+/****************************************************************************
+ * Remove a conn struct from conn->sconn->connections
+ * if not already done.
+****************************************************************************/
+
+static int conn_struct_destructor(connection_struct *conn)
+{
+ if (conn->sconn != NULL) {
+ DLIST_REMOVE(conn->sconn->connections, conn);
+ SMB_ASSERT(conn->sconn->num_connections > 0);
+ conn->sconn->num_connections--;
+ conn->sconn = NULL;
+ }
+ conn_free_internal(conn);
+ return 0;
+}
+
+/****************************************************************************
+ Return the number of open connections.
+****************************************************************************/
+
+int conn_num_open(struct smbd_server_connection *sconn)
+{
+ return sconn->num_connections;
+}
+
+/****************************************************************************
+ Check if a snum is in use.
+****************************************************************************/
+
+bool conn_snum_used(struct smbd_server_connection *sconn,
+ int snum)
+{
+ struct connection_struct *conn;
+
+ for (conn=sconn->connections; conn; conn=conn->next) {
+ if (conn->params->service == snum) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/****************************************************************************
+ Find first available connection slot, starting from a random position.
+ The randomisation stops problems with the server dying and clients
+ thinking the server is still available.
+****************************************************************************/
+
+connection_struct *conn_new(struct smbd_server_connection *sconn)
+{
+ connection_struct *conn = NULL;
+
+ conn = talloc_zero(NULL, connection_struct);
+ if (conn == NULL) {
+ DBG_ERR("talloc_zero failed\n");
+ return NULL;
+ }
+ conn->params = talloc(conn, struct share_params);
+ if (conn->params == NULL) {
+ DBG_ERR("talloc_zero failed\n");
+ TALLOC_FREE(conn);
+ return NULL;
+ }
+ conn->vuid_cache = talloc_zero(conn, struct vuid_cache);
+ if (conn->vuid_cache == NULL) {
+ DBG_ERR("talloc_zero failed\n");
+ TALLOC_FREE(conn);
+ return NULL;
+ }
+ conn->connectpath = talloc_strdup(conn, "");
+ if (conn->connectpath == NULL) {
+ DBG_ERR("talloc_zero failed\n");
+ TALLOC_FREE(conn);
+ return NULL;
+ }
+ conn->cwd_fsp = talloc_zero(conn, struct files_struct);
+ if (conn->cwd_fsp == NULL) {
+ DBG_ERR("talloc_zero failed\n");
+ TALLOC_FREE(conn);
+ return NULL;
+ }
+ conn->cwd_fsp->fsp_name = synthetic_smb_fname(conn->cwd_fsp,
+ ".",
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (conn->cwd_fsp->fsp_name == NULL) {
+ TALLOC_FREE(conn);
+ return NULL;
+ }
+ conn->cwd_fsp->fh = fd_handle_create(conn->cwd_fsp);
+ if (conn->cwd_fsp->fh == NULL) {
+ DBG_ERR("talloc_zero failed\n");
+ TALLOC_FREE(conn);
+ return NULL;
+ }
+ conn->sconn = sconn;
+ conn->force_group_gid = (gid_t)-1;
+ fsp_set_fd(conn->cwd_fsp, -1);
+ conn->cwd_fsp->fnum = FNUM_FIELD_INVALID;
+ conn->cwd_fsp->conn = conn;
+
+ DLIST_ADD(sconn->connections, conn);
+ sconn->num_connections++;
+
+ /*
+ * Catches the case where someone forgets to call
+ * conn_free().
+ */
+ talloc_set_destructor(conn, conn_struct_destructor);
+ return conn;
+}
+
+/****************************************************************************
+ Clear a vuid out of the connection's vuid cache
+****************************************************************************/
+
+static void conn_clear_vuid_cache(connection_struct *conn, uint64_t vuid)
+{
+ int i;
+
+ for (i=0; i<VUID_CACHE_SIZE; i++) {
+ struct vuid_cache_entry *ent;
+
+ ent = &conn->vuid_cache->array[i];
+
+ if (ent->vuid == vuid) {
+ ent->vuid = UID_FIELD_INVALID;
+ /*
+ * We need to keep conn->session_info around
+ * if it's equal to ent->session_info as a SMBulogoff
+ * is often followed by a SMBtdis (with an invalid
+ * vuid). The debug code (or regular code in
+ * vfs_full_audit) wants to refer to the
+ * conn->session_info pointer to print debug
+ * statements. Theoretically this is a bug,
+ * as once the vuid is gone the session_info
+ * on the conn struct isn't valid any more,
+ * but there's enough code that assumes
+ * conn->session_info is never null that
+ * it's easier to hold onto the old pointer
+ * until we get a new sessionsetupX.
+ * As everything is hung off the
+ * conn pointer as a talloc context we're not
+ * leaking memory here. See bug #6315. JRA.
+ */
+ if (conn->session_info == ent->session_info) {
+ ent->session_info = NULL;
+ } else {
+ TALLOC_FREE(ent->session_info);
+ }
+ ent->read_only = False;
+ ent->share_access = 0;
+ }
+ }
+}
+
+/****************************************************************************
+ Clear a vuid out of the validity cache, and as the 'owner' of a connection.
+
+ Called from invalidate_vuid()
+****************************************************************************/
+
+void conn_clear_vuid_caches(struct smbd_server_connection *sconn, uint64_t vuid)
+{
+ connection_struct *conn;
+
+ for (conn=sconn->connections; conn;conn=conn->next) {
+ if (conn->vuid == vuid) {
+ conn->vuid = UID_FIELD_INVALID;
+ }
+ conn_clear_vuid_cache(conn, vuid);
+ }
+}
+
+/****************************************************************************
+ Free a conn structure - internal part.
+****************************************************************************/
+
+static void conn_free_internal(connection_struct *conn)
+{
+ vfs_handle_struct *handle = NULL, *thandle = NULL;
+ struct trans_state *state = NULL;
+
+ /* Free vfs_connection_struct */
+ handle = conn->vfs_handles;
+ while(handle) {
+ thandle = handle->next;
+ DLIST_REMOVE(conn->vfs_handles, handle);
+ if (handle->free_data)
+ handle->free_data(&handle->data);
+ handle = thandle;
+ }
+
+ /* Free any pending transactions stored on this conn. */
+ for (state = conn->pending_trans; state; state = state->next) {
+ /* state->setup is a talloc child of state. */
+ SAFE_FREE(state->param);
+ SAFE_FREE(state->data);
+ }
+
+ free_namearray(conn->veto_list);
+ free_namearray(conn->hide_list);
+ free_namearray(conn->veto_oplock_list);
+ free_namearray(conn->aio_write_behind_list);
+
+ ZERO_STRUCTP(conn);
+}
+
+/****************************************************************************
+ Free a conn structure.
+****************************************************************************/
+
+void conn_free(connection_struct *conn)
+{
+ TALLOC_FREE(conn);
+}
+
+/*
+ * Correctly initialize a share with case options.
+ */
+void conn_setup_case_options(connection_struct *conn)
+{
+ int snum = conn->params->service;
+
+ if (lp_case_sensitive(snum) == Auto) {
+ /* We will be setting this per packet. Set to be case
+ * insensitive for now. */
+ conn->case_sensitive = false;
+ } else {
+ conn->case_sensitive = (bool)lp_case_sensitive(snum);
+ }
+
+ conn->case_preserve = lp_preserve_case(snum);
+ conn->short_case_preserve = lp_short_preserve_case(snum);
+}
diff --git a/source3/smbd/conn_idle.c b/source3/smbd/conn_idle.c
new file mode 100644
index 0000000..870b2b7
--- /dev/null
+++ b/source3/smbd/conn_idle.c
@@ -0,0 +1,277 @@
+/*
+ Unix SMB/CIFS implementation.
+ Manage connections_struct structures
+ Copyright (C) Andrew Tridgell 1998
+ Copyright (C) Alexander Bokovoy 2002
+ Copyright (C) Jeremy Allison 2010
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "rpc_server/rpc_pipes.h"
+#include "lib/util/tevent_ntstatus.h"
+
+/****************************************************************************
+ Update last used timestamps.
+****************************************************************************/
+
+static void conn_lastused_update(struct smbd_server_connection *sconn,time_t t)
+{
+ struct connection_struct *conn;
+
+ for (conn=sconn->connections; conn; conn=conn->next) {
+ /* Update if connection wasn't idle. */
+ if (conn->lastused != conn->lastused_count) {
+ conn->lastused = t;
+ conn->lastused_count = t;
+ }
+ }
+}
+
+/****************************************************************************
+ Idle inactive connections.
+****************************************************************************/
+
+bool conn_idle_all(struct smbd_server_connection *sconn, time_t t)
+{
+ int deadtime = lp_deadtime()*60;
+ struct connection_struct *conn;
+
+ conn_lastused_update(sconn, t);
+
+ if (deadtime <= 0) {
+ return false;
+ }
+
+ for (conn=sconn->connections;conn;conn=conn->next) {
+ time_t age = t - conn->lastused;
+
+ if (conn->num_files_open > 0 || age < deadtime) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/****************************************************************************
+ Forcibly unmount a share - async
+ All instances of the parameter 'sharename' share are unmounted.
+ The special sharename '*' forces unmount of all shares.
+****************************************************************************/
+
+static struct tevent_req *conn_force_tdis_send(connection_struct *conn);
+static void conn_force_tdis_done(struct tevent_req *req);
+
+void conn_force_tdis(
+ struct smbd_server_connection *sconn,
+ bool (*check_fn)(struct connection_struct *conn,
+ void *private_data),
+ void *private_data)
+{
+ connection_struct *conn;
+
+ /* SMB1 and SMB 2*/
+ for (conn = sconn->connections; conn; conn = conn->next) {
+ struct smbXsrv_tcon *tcon;
+ bool do_close = false;
+ struct tevent_req *req;
+
+ if (conn->tcon == NULL) {
+ continue;
+ }
+ tcon = conn->tcon;
+
+ if (!NT_STATUS_IS_OK(tcon->status)) {
+ /* In the process of already being disconnected. */
+ continue;
+ }
+
+ do_close = check_fn(conn, private_data);
+ if (!do_close) {
+ continue;
+ }
+
+ req = conn_force_tdis_send(conn);
+ if (req == NULL) {
+ DBG_WARNING("talloc_fail forcing async close of "
+ "share '%s'\n",
+ tcon->global->share_name);
+ continue;
+ }
+
+ DBG_WARNING("Forcing close of "
+ "share '%s' (wire_id=0x%08x)\n",
+ tcon->global->share_name,
+ tcon->global->tcon_wire_id);
+
+ tevent_req_set_callback(req, conn_force_tdis_done, conn);
+ }
+}
+
+struct conn_force_tdis_state {
+ struct tevent_queue *wait_queue;
+};
+
+static void conn_force_tdis_wait_done(struct tevent_req *subreq);
+
+static struct tevent_req *conn_force_tdis_send(connection_struct *conn)
+{
+ struct tevent_req *req;
+ struct conn_force_tdis_state *state;
+ struct tevent_req *subreq;
+ files_struct *fsp;
+
+ /* Create this off the NULL context. We must clean up on return. */
+ req = tevent_req_create(NULL, &state,
+ struct conn_force_tdis_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->wait_queue = tevent_queue_create(state,
+ "conn_force_tdis_wait_queue");
+ if (tevent_req_nomem(state->wait_queue, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+
+ /*
+ * Make sure that no new request will be able to use this tcon.
+ * This ensures that once all outstanding fsp->aio_requests
+ * on this tcon are done, we are safe to close it.
+ */
+ conn->tcon->status = NT_STATUS_NETWORK_NAME_DELETED;
+
+ for (fsp = conn->sconn->files; fsp; fsp = fsp->next) {
+ if (fsp->conn != conn) {
+ continue;
+ }
+ /*
+ * Flag the file as close in progress.
+ * This will prevent any more IO being
+ * done on it. Not strictly needed, but
+ * doesn't hurt to flag it as closing.
+ */
+ fsp->fsp_flags.closing = true;
+
+ if (fsp->num_aio_requests > 0) {
+ /*
+ * Now wait until all aio requests on this fsp are
+ * finished.
+ *
+ * We don't set a callback, as we just want to block the
+ * wait queue and the talloc_free() of fsp->aio_request
+ * will remove the item from the wait queue.
+ */
+ subreq = tevent_queue_wait_send(fsp->aio_requests,
+ conn->sconn->ev_ctx,
+ state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+ }
+ }
+ /*
+ * Now we add our own waiter to the end of the queue,
+ * this way we get notified when all pending requests are finished
+ * and reply to the outstanding SMB1 request.
+ */
+ subreq = tevent_queue_wait_send(state,
+ conn->sconn->ev_ctx,
+ state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+
+ tevent_req_set_callback(subreq, conn_force_tdis_wait_done, req);
+ return req;
+}
+
+static void conn_force_tdis_wait_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+
+ tevent_queue_wait_recv(subreq);
+ TALLOC_FREE(subreq);
+ tevent_req_done(req);
+}
+
+static NTSTATUS conn_force_tdis_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static void conn_force_tdis_done(struct tevent_req *req)
+{
+ connection_struct *conn = tevent_req_callback_data(
+ req, connection_struct);
+ NTSTATUS status;
+ uint64_t vuid = UID_FIELD_INVALID;
+ struct smbXsrv_tcon *tcon = conn->tcon;
+ struct smbd_server_connection *sconn = conn->sconn;
+
+ status = conn_force_tdis_recv(req);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("conn_force_tdis_recv of share '%s' "
+ "(wire_id=0x%08x) failed: %s\n",
+ tcon->global->share_name,
+ tcon->global->tcon_wire_id,
+ nt_errstr(status));
+ return;
+ }
+
+ if (conn->sconn->using_smb2) {
+ vuid = conn->vuid;
+ }
+
+ DBG_WARNING("Closing "
+ "share '%s' (wire_id=0x%08x)\n",
+ tcon->global->share_name,
+ tcon->global->tcon_wire_id);
+
+ conn = NULL;
+ status = smbXsrv_tcon_disconnect(tcon, vuid);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smbXsrv_tcon_disconnect() of share '%s' "
+ "(wire_id=0x%08x) failed: %s\n",
+ tcon->global->share_name,
+ tcon->global->tcon_wire_id,
+ nt_errstr(status));
+ return;
+ }
+
+ TALLOC_FREE(tcon);
+
+ /*
+ * As we've been awoken, we may have changed
+ * uid in the meantime. Ensure we're still root.
+ */
+ change_to_root_user();
+ /*
+ * Use 'false' in the last parameter (test) to force
+ * a full reload of services. Prevents
+ * reload_services caching the fact it's
+ * been called multiple times in a row.
+ * See BUG: https://bugzilla.samba.org/show_bug.cgi?id=14604
+ * for details.
+ */
+ reload_services(sconn, conn_snum_used, false);
+}
diff --git a/source3/smbd/conn_msg.c b/source3/smbd/conn_msg.c
new file mode 100644
index 0000000..9435d38
--- /dev/null
+++ b/source3/smbd/conn_msg.c
@@ -0,0 +1,148 @@
+/*
+ Unix SMB/CIFS implementation.
+ Manage connections_struct structures
+ Copyright (C) Andrew Tridgell 1998
+ Copyright (C) Alexander Bokovoy 2002
+ Copyright (C) Jeremy Allison 2010
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+
+/****************************************************************************
+ Receive a smbcontrol message to forcibly unmount a share.
+ The message contains just a share name and all instances of that
+ share are unmounted.
+ The special sharename '*' forces unmount of all shares.
+****************************************************************************/
+
+struct force_tdis_state {
+ const char *sharename;
+};
+
+static bool force_tdis_check(
+ struct connection_struct *conn,
+ void *private_data)
+{
+ struct force_tdis_state *state = private_data;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *servicename = NULL;
+ bool do_close;
+
+ if (strcmp(state->sharename, "*") == 0) {
+ DBG_WARNING("Forcing close of all shares\n");
+ return true;
+ }
+
+ servicename = lp_servicename(talloc_tos(), lp_sub, SNUM(conn));
+ do_close = strequal(servicename, state->sharename);
+
+ TALLOC_FREE(servicename);
+
+ return do_close;
+}
+
+void msg_force_tdis(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct force_tdis_state state = {
+ .sharename = (const char *)data->data,
+ };
+ struct smbd_server_connection *sconn =
+ talloc_get_type_abort(private_data,
+ struct smbd_server_connection);
+
+ if ((data->length == 0) || (data->data[data->length-1] != 0)) {
+ DBG_WARNING("Ignoring invalid sharename\n");
+ return;
+ }
+
+ conn_force_tdis(sconn, force_tdis_check, &state);
+}
+
+static bool force_tdis_denied_check(
+ struct connection_struct *conn,
+ void *private_data)
+{
+ bool do_close;
+ uint32_t share_access;
+ bool read_only;
+ NTSTATUS status;
+
+ do_close = force_tdis_check(conn, private_data);
+ if (!do_close) {
+ return false;
+ }
+
+ status = check_user_share_access(
+ conn,
+ conn->session_info,
+ &share_access,
+ &read_only);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("check_user_share_access returned %s\n",
+ nt_errstr(status));
+ return true; /* close the share */
+ }
+
+ if (conn->share_access != share_access) {
+ DBG_DEBUG("share_access changed from %"PRIx32" to %"PRIx32"\n",
+ conn->share_access, share_access);
+ return true; /* close the share */
+ }
+
+ if (conn->read_only != read_only) {
+ DBG_DEBUG("read_only changed from %s to %s\n",
+ conn->read_only ? "true" : "false",
+ read_only ? "true" : "false");
+ return true; /* close the share */
+ }
+
+ /*
+ * all still ok, keep the connection open
+ */
+ return false;
+}
+
+void msg_force_tdis_denied(
+ struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct force_tdis_state state = {
+ .sharename = (const char *)data->data,
+ };
+ struct smbd_server_connection *sconn =
+ talloc_get_type_abort(private_data,
+ struct smbd_server_connection);
+
+ if ((data->length == 0) || (data->data[data->length-1] != 0)) {
+ DBG_WARNING("Ignoring invalid sharename\n");
+ return;
+ }
+
+ change_to_root_user();
+ reload_services(sconn, conn_snum_used, false);
+
+ conn_force_tdis(sconn, force_tdis_denied_check, &state);
+}
diff --git a/source3/smbd/connection.c b/source3/smbd/connection.c
new file mode 100644
index 0000000..8dea205
--- /dev/null
+++ b/source3/smbd/connection.c
@@ -0,0 +1,97 @@
+/*
+ Unix SMB/CIFS implementation.
+ connection claim routines
+ Copyright (C) Andrew Tridgell 1998
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "dbwrap/dbwrap.h"
+#include "auth.h"
+#include "../lib/tsocket/tsocket.h"
+#include "messages.h"
+
+struct count_stat {
+ int curr_connections;
+ const char *name;
+ bool verify;
+};
+
+/****************************************************************************
+ Count the entries belonging to a service in the connection db.
+****************************************************************************/
+
+static int count_fn(struct smbXsrv_tcon_global0 *tcon,
+ void *udp)
+{
+ struct count_stat *cs = (struct count_stat *)udp;
+
+ if (cs->verify && !process_exists(tcon->server_id)) {
+ return 0;
+ }
+
+ if (strequal(tcon->share_name, cs->name)) {
+ cs->curr_connections++;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ Claim an entry in the connections database.
+****************************************************************************/
+
+int count_current_connections(const char *sharename, bool verify)
+{
+ struct count_stat cs;
+ NTSTATUS status;
+
+ cs.curr_connections = 0;
+ cs.name = sharename;
+ cs.verify = verify;
+
+ /*
+ * This has a race condition, but locking the chain before hand is worse
+ * as it leads to deadlock.
+ */
+
+ status = smbXsrv_tcon_global_traverse(count_fn, &cs);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("count_current_connections: traverse of "
+ "smbXsrv_tcon_global.tdb failed - %s\n",
+ nt_errstr(status)));
+ return 0;
+ }
+
+ return cs.curr_connections;
+}
+
+bool connections_snum_used(struct smbd_server_connection *unused, int snum)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int active;
+
+ active = count_current_connections(lp_servicename(talloc_tos(), lp_sub, snum),
+ true);
+ if (active > 0) {
+ return true;
+ }
+
+ return false;
+}
diff --git a/source3/smbd/dfree.c b/source3/smbd/dfree.c
new file mode 100644
index 0000000..89dc112
--- /dev/null
+++ b/source3/smbd/dfree.c
@@ -0,0 +1,296 @@
+/*
+ Unix SMB/CIFS implementation.
+ functions to calculate the free disk space
+ Copyright (C) Andrew Tridgell 1998
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "lib/util_file.h"
+#include "lib/util/memcache.h"
+
+/****************************************************************************
+ Normalise for DOS usage.
+****************************************************************************/
+
+static void disk_norm(uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
+{
+ /* check if the disk is beyond the max disk size */
+ uint64_t maxdisksize = lp_max_disk_size();
+ if (maxdisksize) {
+ /* convert to blocks - and don't overflow */
+ maxdisksize = ((maxdisksize*1024)/(*bsize))*1024;
+ if (*dsize > maxdisksize) {
+ *dsize = maxdisksize;
+ }
+ if (*dfree > maxdisksize) {
+ *dfree = maxdisksize - 1;
+ }
+ /* the -1 should stop applications getting div by 0
+ errors */
+ }
+}
+
+
+
+/****************************************************************************
+ Return number of 1K blocks available on a path and total number.
+****************************************************************************/
+
+static uint64_t sys_disk_free(connection_struct *conn,
+ struct smb_filename *fname,
+ uint64_t *bsize,
+ uint64_t *dfree,
+ uint64_t *dsize)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ uint64_t dfree_retval;
+ uint64_t dfree_q = 0;
+ uint64_t bsize_q = 0;
+ uint64_t dsize_q = 0;
+ const char *dfree_command;
+ static bool dfree_broken = false;
+ char *path = fname->base_name;
+
+ (*dfree) = (*dsize) = 0;
+ (*bsize) = 512;
+
+ /*
+ * If external disk calculation specified, use it.
+ */
+
+ dfree_command = lp_dfree_command(talloc_tos(), lp_sub, SNUM(conn));
+ if (dfree_command && *dfree_command) {
+ const char *p;
+ char **lines = NULL;
+ char **argl = NULL;
+
+ argl = str_list_make_empty(talloc_tos());
+ str_list_add_printf(&argl, "%s", dfree_command);
+ str_list_add_printf(&argl, "%s", path);
+ if (argl == NULL) {
+ return (uint64_t)-1;
+ }
+
+ DBG_NOTICE("Running command '%s %s'\n",
+ dfree_command,
+ path);
+
+ lines = file_lines_ploadv(talloc_tos(), argl, NULL);
+
+ TALLOC_FREE(argl);
+
+ if (lines != NULL) {
+ char *line = lines[0];
+
+ DEBUG (3, ("Read input from dfree, \"%s\"\n", line));
+
+ *dsize = STR_TO_SMB_BIG_UINT(line, &p);
+ while (p && *p && isspace(*p))
+ p++;
+ if (p && *p)
+ *dfree = STR_TO_SMB_BIG_UINT(p, &p);
+ while (p && *p && isspace(*p))
+ p++;
+ if (p && *p)
+ *bsize = STR_TO_SMB_BIG_UINT(p, NULL);
+ else
+ *bsize = 1024;
+ TALLOC_FREE(lines);
+ DEBUG (3, ("Parsed output of dfree, dsize=%u, dfree=%u, bsize=%u\n",
+ (unsigned int)*dsize, (unsigned int)*dfree, (unsigned int)*bsize));
+
+ if (!*dsize)
+ *dsize = 2048;
+ if (!*dfree)
+ *dfree = 1024;
+
+ goto dfree_done;
+ }
+ DBG_ERR("file_lines_load() failed for "
+ "command '%s %s'. Error was : %s\n",
+ dfree_command, path, strerror(errno));
+ }
+
+ if (SMB_VFS_DISK_FREE(conn, fname, bsize, dfree, dsize) ==
+ (uint64_t)-1) {
+ DBG_ERR("VFS disk_free failed. Error was : %s\n",
+ strerror(errno));
+ return (uint64_t)-1;
+ }
+
+ if (disk_quotas(conn, fname, &bsize_q, &dfree_q, &dsize_q)) {
+ uint64_t min_bsize = MIN(*bsize, bsize_q);
+
+ (*dfree) = (*dfree) * (*bsize) / min_bsize;
+ (*dsize) = (*dsize) * (*bsize) / min_bsize;
+ dfree_q = dfree_q * bsize_q / min_bsize;
+ dsize_q = dsize_q * bsize_q / min_bsize;
+
+ (*bsize) = min_bsize;
+ (*dfree) = MIN(*dfree,dfree_q);
+ (*dsize) = MIN(*dsize,dsize_q);
+ }
+
+ /* FIXME : Any reason for this assumption ? */
+ if (*bsize < 256) {
+ DEBUG(5,("disk_free:Warning: bsize == %d < 256 . Changing to assumed correct bsize = 512\n",(int)*bsize));
+ *bsize = 512;
+ }
+
+ if ((*dsize)<1) {
+ if (!dfree_broken) {
+ DEBUG(0,("WARNING: dfree is broken on this system\n"));
+ dfree_broken=true;
+ }
+ *dsize = 20*1024*1024/(*bsize);
+ *dfree = MAX(1,*dfree);
+ }
+
+dfree_done:
+ disk_norm(bsize, dfree, dsize);
+
+ if ((*bsize) < 1024) {
+ dfree_retval = (*dfree)/(1024/(*bsize));
+ } else {
+ dfree_retval = ((*bsize)/1024)*(*dfree);
+ }
+
+ return(dfree_retval);
+}
+
+/****************************************************************************
+ Potentially returned cached dfree info.
+
+ Depending on the file system layout and file system features, the free space
+ information can be different for different sub directories underneath a SMB
+ share. Store the cache information in memcache using the query path as the
+ key to accommodate this.
+****************************************************************************/
+
+struct dfree_cached_info {
+ time_t last_dfree_time;
+ uint64_t dfree_ret;
+ uint64_t bsize;
+ uint64_t dfree;
+ uint64_t dsize;
+};
+
+uint64_t get_dfree_info(connection_struct *conn, struct smb_filename *fname,
+ uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
+{
+ int dfree_cache_time = lp_dfree_cache_time(SNUM(conn));
+ struct dfree_cached_info *dfc = NULL;
+ struct dfree_cached_info dfc_new = { 0 };
+ uint64_t dfree_ret;
+ char tmpbuf[PATH_MAX];
+ char *full_path = NULL;
+ char *to_free = NULL;
+ char *key_path = NULL;
+ size_t len;
+ DATA_BLOB key, value;
+ bool found;
+
+ if (!dfree_cache_time) {
+ return sys_disk_free(conn, fname, bsize, dfree, dsize);
+ }
+
+ len = full_path_tos(conn->connectpath,
+ fname->base_name,
+ tmpbuf,
+ sizeof(tmpbuf),
+ &full_path,
+ &to_free);
+ if (len == -1) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ if (VALID_STAT(fname->st) && S_ISREG(fname->st.st_ex_mode)) {
+ /*
+ * In case of a file use the parent directory to reduce number
+ * of cache entries.
+ */
+ bool ok;
+
+ ok = parent_dirname(talloc_tos(),
+ full_path,
+ &key_path,
+ NULL);
+ TALLOC_FREE(to_free); /* We're done with full_path */
+
+ if (!ok) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ /*
+ * key_path is always a talloced object.
+ */
+ to_free = key_path;
+ } else {
+ /*
+ * key_path might not be a talloced object; rely on
+ * to_free set from full_path_tos.
+ */
+ key_path = full_path;
+ }
+
+ key = data_blob_const(key_path, strlen(key_path));
+ found = memcache_lookup(smbd_memcache(),
+ DFREE_CACHE,
+ key,
+ &value);
+ dfc = found ? (struct dfree_cached_info *)value.data : NULL;
+
+ if (dfc && (conn->lastused - dfc->last_dfree_time < dfree_cache_time)) {
+ DBG_DEBUG("Returning dfree cache entry for %s\n", key_path);
+ *bsize = dfc->bsize;
+ *dfree = dfc->dfree;
+ *dsize = dfc->dsize;
+ dfree_ret = dfc->dfree_ret;
+ goto out;
+ }
+
+ dfree_ret = sys_disk_free(conn, fname, bsize, dfree, dsize);
+
+ if (dfree_ret == (uint64_t)-1) {
+ /* Don't cache bad data. */
+ goto out;
+ }
+
+ DBG_DEBUG("Creating dfree cache entry for %s\n", key_path);
+ dfc_new.bsize = *bsize;
+ dfc_new.dfree = *dfree;
+ dfc_new.dsize = *dsize;
+ dfc_new.dfree_ret = dfree_ret;
+ dfc_new.last_dfree_time = conn->lastused;
+ memcache_add(smbd_memcache(),
+ DFREE_CACHE,
+ key,
+ data_blob_const(&dfc_new, sizeof(dfc_new)));
+
+out:
+ TALLOC_FREE(to_free);
+ return dfree_ret;
+}
+
+void flush_dfree_cache(void)
+{
+ memcache_flush(smbd_memcache(), DFREE_CACHE);
+}
diff --git a/source3/smbd/dir.c b/source3/smbd/dir.c
new file mode 100644
index 0000000..49c37cb
--- /dev/null
+++ b/source3/smbd/dir.c
@@ -0,0 +1,1504 @@
+/*
+ Unix SMB/CIFS implementation.
+ Directory handling routines
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 2007
+
+ 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 "system/filesys.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "libcli/security/security.h"
+#include "lib/util/bitmap.h"
+#include "../lib/util/memcache.h"
+#include "../librpc/gen_ndr/open_files.h"
+#include "lib/util/string_wrappers.h"
+#include "libcli/smb/reparse.h"
+#include "source3/smbd/dir.h"
+
+/*
+ This module implements directory related functions for Samba.
+*/
+
+/* "Special" directory offsets. */
+#define END_OF_DIRECTORY_OFFSET ((long)-1)
+#define START_OF_DIRECTORY_OFFSET ((long)0)
+#define DOT_DOT_DIRECTORY_OFFSET ((long)0x80000000)
+
+/* Make directory handle internals available. */
+
+struct smb_Dir {
+ connection_struct *conn;
+ DIR *dir;
+ struct smb_filename *dir_smb_fname;
+ unsigned int file_number;
+ bool case_sensitive;
+ files_struct *fsp; /* Back pointer to containing fsp, only
+ set from OpenDir_fsp(). */
+};
+
+struct dptr_struct {
+ struct dptr_struct *next, *prev;
+ int dnum;
+ struct smb_Dir *dir_hnd;
+ char *wcard;
+ uint32_t attr;
+ bool has_wild; /* Set to true if the wcard entry has MS wildcard characters in it. */
+ bool did_stat; /* Optimisation for non-wcard searches. */
+ bool priv; /* Directory handle opened with privilege. */
+ uint32_t counter;
+
+ char *last_name_sent; /* for name-based trans2 resume */
+
+ struct {
+ char *fname;
+ struct smb_filename *smb_fname;
+ uint32_t mode;
+ } overflow;
+};
+
+static NTSTATUS OpenDir_fsp(
+ TALLOC_CTX *mem_ctx,
+ connection_struct *conn,
+ files_struct *fsp,
+ const char *mask,
+ uint32_t attr,
+ struct smb_Dir **_dir_hnd);
+
+static int smb_Dir_destructor(struct smb_Dir *dir_hnd);
+
+#define INVALID_DPTR_KEY (-3)
+
+/****************************************************************************
+ Initialise the dir bitmap.
+****************************************************************************/
+
+bool init_dptrs(struct smbd_server_connection *sconn)
+{
+ if (sconn->searches.dptr_bmap) {
+ return true;
+ }
+
+ sconn->searches.dptr_bmap = bitmap_talloc(
+ sconn, MAX_DIRECTORY_HANDLES);
+
+ if (sconn->searches.dptr_bmap == NULL) {
+ return false;
+ }
+
+ return true;
+}
+
+/****************************************************************************
+ Get the struct dptr_struct for a dir index.
+****************************************************************************/
+
+static struct dptr_struct *dptr_get(struct smbd_server_connection *sconn,
+ int key)
+{
+ struct dptr_struct *dptr;
+
+ for (dptr = sconn->searches.dirptrs; dptr != NULL; dptr = dptr->next) {
+ if(dptr->dnum != key) {
+ continue;
+ }
+ DLIST_PROMOTE(sconn->searches.dirptrs, dptr);
+ return dptr;
+ }
+ return(NULL);
+}
+
+/****************************************************************************
+ Get the dir path for a dir index.
+****************************************************************************/
+
+const char *dptr_path(struct smbd_server_connection *sconn, int key)
+{
+ struct dptr_struct *dptr = dptr_get(sconn, key);
+ if (dptr)
+ return(dptr->dir_hnd->dir_smb_fname->base_name);
+ return(NULL);
+}
+
+/****************************************************************************
+ Get the dir wcard for a dir index.
+****************************************************************************/
+
+const char *dptr_wcard(struct smbd_server_connection *sconn, int key)
+{
+ struct dptr_struct *dptr = dptr_get(sconn, key);
+ if (dptr)
+ return(dptr->wcard);
+ return(NULL);
+}
+
+/****************************************************************************
+ Get the dir attrib for a dir index.
+****************************************************************************/
+
+uint16_t dptr_attr(struct smbd_server_connection *sconn, int key)
+{
+ struct dptr_struct *dptr = dptr_get(sconn, key);
+ if (dptr)
+ return(dptr->attr);
+ return(0);
+}
+
+/****************************************************************************
+ Close all dptrs for a cnum.
+****************************************************************************/
+
+void dptr_closecnum(connection_struct *conn)
+{
+ struct dptr_struct *dptr, *next;
+ struct smbd_server_connection *sconn = conn->sconn;
+
+ if (sconn == NULL) {
+ return;
+ }
+
+ for(dptr = sconn->searches.dirptrs; dptr; dptr = next) {
+ next = dptr->next;
+ if (dptr->dir_hnd->conn == conn) {
+ /*
+ * Need to make a copy, "dptr" will be gone
+ * after close_file_free() returns
+ */
+ struct files_struct *fsp = dptr->dir_hnd->fsp;
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ }
+ }
+}
+
+/****************************************************************************
+ Create a new dir ptr. If the flag old_handle is true then we must allocate
+ from the bitmap range 0 - 255 as old SMBsearch directory handles are only
+ one byte long. If old_handle is false we allocate from the range
+ 256 - MAX_DIRECTORY_HANDLES. We bias the number we return by 1 to ensure
+ a directory handle is never zero.
+ wcard must not be zero.
+****************************************************************************/
+
+NTSTATUS dptr_create(connection_struct *conn,
+ struct smb_request *req,
+ files_struct *fsp,
+ bool old_handle,
+ const char *wcard,
+ uint32_t attr,
+ struct dptr_struct **dptr_ret)
+{
+ struct smbd_server_connection *sconn = conn->sconn;
+ struct dptr_struct *dptr = NULL;
+ struct smb_Dir *dir_hnd = NULL;
+ NTSTATUS status;
+
+ DBG_INFO("dir=%s\n", fsp_str_dbg(fsp));
+
+ if (sconn == NULL) {
+ DEBUG(0,("dptr_create: called with fake connection_struct\n"));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (!wcard) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = check_any_access_fsp(fsp, SEC_DIR_LIST);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_INFO("dptr_create: directory %s "
+ "not open for LIST access\n",
+ fsp_str_dbg(fsp));
+ return status;
+ }
+ status = OpenDir_fsp(NULL, conn, fsp, wcard, attr, &dir_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ dptr = talloc_zero(NULL, struct dptr_struct);
+ if(!dptr) {
+ DEBUG(0,("talloc fail in dptr_create.\n"));
+ TALLOC_FREE(dir_hnd);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ dptr->dir_hnd = dir_hnd;
+ dptr->wcard = talloc_strdup(dptr, wcard);
+ if (!dptr->wcard) {
+ TALLOC_FREE(dptr);
+ TALLOC_FREE(dir_hnd);
+ return NT_STATUS_NO_MEMORY;
+ }
+ if ((req != NULL && req->posix_pathnames) || ISDOT(wcard)) {
+ dptr->has_wild = True;
+ } else {
+ dptr->has_wild = ms_has_wild(dptr->wcard);
+ }
+
+ dptr->attr = attr;
+
+ if (sconn->using_smb2) {
+ goto done;
+ }
+
+ if(old_handle) {
+
+ /*
+ * This is an old-style SMBsearch request. Ensure the
+ * value we return will fit in the range 1-255.
+ */
+
+ dptr->dnum = bitmap_find(sconn->searches.dptr_bmap, 0);
+
+ if(dptr->dnum == -1 || dptr->dnum > 254) {
+ DBG_ERR("returned %d: Error - all old "
+ "dirptrs in use ?\n",
+ dptr->dnum);
+ TALLOC_FREE(dptr);
+ TALLOC_FREE(dir_hnd);
+ return NT_STATUS_TOO_MANY_OPENED_FILES;
+ }
+ } else {
+
+ /*
+ * This is a new-style trans2 request. Allocate from
+ * a range that will return 256 - MAX_DIRECTORY_HANDLES.
+ */
+
+ dptr->dnum = bitmap_find(sconn->searches.dptr_bmap, 255);
+
+ if(dptr->dnum == -1 || dptr->dnum < 255) {
+ DBG_ERR("returned %d: Error - all new "
+ "dirptrs in use ?\n",
+ dptr->dnum);
+ TALLOC_FREE(dptr);
+ TALLOC_FREE(dir_hnd);
+ return NT_STATUS_TOO_MANY_OPENED_FILES;
+ }
+ }
+
+ bitmap_set(sconn->searches.dptr_bmap, dptr->dnum);
+
+ dptr->dnum += 1; /* Always bias the dnum by one - no zero dnums allowed. */
+
+ DLIST_ADD(sconn->searches.dirptrs, dptr);
+
+done:
+ DBG_INFO("creating new dirptr [%d] for path [%s]\n",
+ dptr->dnum, fsp_str_dbg(fsp));
+
+ *dptr_ret = dptr;
+
+ return NT_STATUS_OK;
+}
+
+
+/****************************************************************************
+ Wrapper functions to access the lower level directory handles.
+****************************************************************************/
+
+void dptr_CloseDir(files_struct *fsp)
+{
+ struct smbd_server_connection *sconn = NULL;
+
+ if (fsp->dptr == NULL) {
+ return;
+ }
+ sconn = fsp->conn->sconn;
+
+ /*
+ * The destructor for the struct smb_Dir (fsp->dptr->dir_hnd)
+ * now handles all resource deallocation.
+ */
+
+ DBG_INFO("closing dptr key %d\n", fsp->dptr->dnum);
+
+ if (sconn != NULL && !sconn->using_smb2) {
+ DLIST_REMOVE(sconn->searches.dirptrs, fsp->dptr);
+
+ /*
+ * Free the dnum in the bitmap. Remember the dnum value is
+ * always biased by one with respect to the bitmap.
+ */
+
+ if (!bitmap_query(sconn->searches.dptr_bmap,
+ fsp->dptr->dnum - 1))
+ {
+ DBG_ERR("closing dnum = %d and bitmap not set !\n",
+ fsp->dptr->dnum);
+ }
+
+ bitmap_clear(sconn->searches.dptr_bmap, fsp->dptr->dnum - 1);
+ }
+
+ TALLOC_FREE(fsp->dptr->dir_hnd);
+ TALLOC_FREE(fsp->dptr);
+}
+
+void dptr_RewindDir(struct dptr_struct *dptr)
+{
+ RewindDir(dptr->dir_hnd);
+ dptr->did_stat = false;
+ TALLOC_FREE(dptr->overflow.fname);
+ TALLOC_FREE(dptr->overflow.smb_fname);
+}
+
+unsigned int dptr_FileNumber(struct dptr_struct *dptr)
+{
+ return dptr->dir_hnd->file_number;
+}
+
+bool dptr_has_wild(struct dptr_struct *dptr)
+{
+ return dptr->has_wild;
+}
+
+int dptr_dnum(struct dptr_struct *dptr)
+{
+ return dptr->dnum;
+}
+
+bool dptr_get_priv(struct dptr_struct *dptr)
+{
+ return dptr->priv;
+}
+
+void dptr_set_priv(struct dptr_struct *dptr)
+{
+ dptr->priv = true;
+}
+
+bool dptr_case_sensitive(struct dptr_struct *dptr)
+{
+ return dptr->dir_hnd->case_sensitive;
+}
+
+/****************************************************************************
+ Return the next visible file name, skipping veto'd and invisible files.
+****************************************************************************/
+
+char *dptr_ReadDirName(TALLOC_CTX *ctx, struct dptr_struct *dptr)
+{
+ struct stat_ex st = {
+ .st_ex_nlink = 0,
+ };
+ struct smb_Dir *dir_hnd = dptr->dir_hnd;
+ struct files_struct *dir_fsp = dir_hnd->fsp;
+ struct smb_filename *dir_name = dir_fsp->fsp_name;
+ struct smb_filename smb_fname_base;
+ bool retry_scanning = false;
+ int ret;
+ int flags = 0;
+
+ if (dptr->has_wild) {
+ const char *name_temp = NULL;
+ char *talloced = NULL;
+ name_temp = ReadDirName(dir_hnd, &talloced);
+ if (name_temp == NULL) {
+ return NULL;
+ }
+ if (talloced != NULL) {
+ return talloc_move(ctx, &talloced);
+ }
+ return talloc_strdup(ctx, name_temp);
+ }
+
+ if (dptr->did_stat) {
+ /*
+ * No wildcard, this is not a real directory traverse
+ * but a "stat" call behind a query_directory. We've
+ * been here, nothing else to look at.
+ */
+ return NULL;
+ }
+ dptr->did_stat = true;
+
+ /* Create an smb_filename with stream_name == NULL. */
+ smb_fname_base = (struct smb_filename){
+ .base_name = dptr->wcard,
+ .flags = dir_name->flags,
+ .twrp = dir_name->twrp,
+ };
+
+ if (dir_name->flags & SMB_FILENAME_POSIX_PATH) {
+ flags |= AT_SYMLINK_NOFOLLOW;
+ }
+
+ ret = SMB_VFS_FSTATAT(
+ dir_hnd->conn, dir_fsp, &smb_fname_base, &st, flags);
+ if (ret == 0) {
+ return talloc_strdup(ctx, dptr->wcard);
+ }
+
+ /*
+ * If we get any other error than ENOENT or ENOTDIR
+ * then the file exists, we just can't stat it.
+ */
+ if (errno != ENOENT && errno != ENOTDIR) {
+ return talloc_strdup(ctx, dptr->wcard);
+ }
+
+ /*
+ * A scan will find the long version of a mangled name as
+ * wildcard.
+ */
+ retry_scanning |= mangle_is_mangled(dptr->wcard,
+ dir_hnd->conn->params);
+
+ /*
+ * Also retry scanning if the client requested case
+ * insensitive semantics and the file system does not provide
+ * it.
+ */
+ retry_scanning |= (!dir_hnd->case_sensitive &&
+ (dir_hnd->conn->fs_capabilities &
+ FILE_CASE_SENSITIVE_SEARCH));
+
+ if (retry_scanning) {
+ char *found_name = NULL;
+ NTSTATUS status;
+
+ status = get_real_filename_at(dir_fsp,
+ dptr->wcard,
+ ctx,
+ &found_name);
+ if (NT_STATUS_IS_OK(status)) {
+ return found_name;
+ }
+ }
+
+ return NULL;
+}
+
+struct files_struct *dir_hnd_fetch_fsp(struct smb_Dir *dir_hnd)
+{
+ return dir_hnd->fsp;
+}
+
+/****************************************************************************
+ Fetch the fsp associated with the dptr_num.
+****************************************************************************/
+
+files_struct *dptr_fetch_lanman2_fsp(struct smbd_server_connection *sconn,
+ int dptr_num)
+{
+ struct dptr_struct *dptr = dptr_get(sconn, dptr_num);
+ if (dptr == NULL) {
+ return NULL;
+ }
+ DBG_NOTICE("fetching dirptr %d for path %s\n",
+ dptr_num,
+ dptr->dir_hnd->dir_smb_fname->base_name);
+ return dptr->dir_hnd->fsp;
+}
+
+bool smbd_dirptr_get_entry(TALLOC_CTX *ctx,
+ struct dptr_struct *dirptr,
+ const char *mask,
+ uint32_t dirtype,
+ bool dont_descend,
+ bool ask_sharemode,
+ bool get_dosmode_in,
+ bool (*match_fn)(TALLOC_CTX *ctx,
+ void *private_data,
+ const char *dname,
+ const char *mask,
+ char **_fname),
+ void *private_data,
+ char **_fname,
+ struct smb_filename **_smb_fname,
+ uint32_t *_mode)
+{
+ struct smb_Dir *dir_hnd = dirptr->dir_hnd;
+ connection_struct *conn = dir_hnd->conn;
+ struct smb_filename *dir_fname = dir_hnd->dir_smb_fname;
+ bool posix = (dir_fname->flags & SMB_FILENAME_POSIX_PATH);
+ const bool toplevel = ISDOT(dir_fname->base_name);
+ NTSTATUS status;
+
+ *_smb_fname = NULL;
+ *_mode = 0;
+
+ if (dirptr->overflow.smb_fname != NULL) {
+ *_fname = talloc_move(ctx, &dirptr->overflow.fname);
+ *_smb_fname = talloc_move(ctx, &dirptr->overflow.smb_fname);
+ *_mode = dirptr->overflow.mode;
+ return true;
+ }
+
+ if (dont_descend && (dptr_FileNumber(dirptr) >= 2)) {
+ /*
+ * . and .. were returned first, we're done showing
+ * the directory as empty.
+ */
+ return false;
+ }
+
+ while (true) {
+ char *dname = NULL;
+ char *fname = NULL;
+ struct smb_filename *smb_fname = NULL;
+ uint32_t mode = 0;
+ bool get_dosmode = get_dosmode_in;
+ bool toplevel_dotdot;
+ bool visible;
+ bool ok;
+
+ dname = dptr_ReadDirName(ctx, dirptr);
+
+ DBG_DEBUG("dir [%s] dirptr [%p] offset [%u] => "
+ "dname [%s]\n",
+ smb_fname_str_dbg(dir_fname),
+ dirptr,
+ dir_hnd->file_number,
+ dname ? dname : "(finished)");
+
+ if (dname == NULL) {
+ return false;
+ }
+
+ if (IS_VETO_PATH(conn, dname)) {
+ TALLOC_FREE(dname);
+ continue;
+ }
+
+ /*
+ * fname may get mangled, dname is never mangled.
+ * Whenever we're accessing the filesystem we use
+ * pathreal which is composed from dname.
+ */
+
+ ok = match_fn(ctx, private_data, dname, mask, &fname);
+ if (!ok) {
+ TALLOC_FREE(dname);
+ continue;
+ }
+
+ toplevel_dotdot = toplevel && ISDOTDOT(dname);
+
+ smb_fname = synthetic_smb_fname(talloc_tos(),
+ toplevel_dotdot ? "." : dname,
+ NULL,
+ NULL,
+ dir_fname->twrp,
+ dir_fname->flags);
+ if (smb_fname == NULL) {
+ TALLOC_FREE(dname);
+ return false;
+ }
+
+ /*
+ * UCF_POSIX_PATHNAMES to avoid the readdir fallback
+ * if we get raced between readdir and unlink.
+ */
+ status = openat_pathref_fsp_lcomp(dir_hnd->fsp,
+ smb_fname,
+ UCF_POSIX_PATHNAMES);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Could not open %s: %s\n",
+ dname,
+ nt_errstr(status));
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(fname);
+ TALLOC_FREE(dname);
+ continue;
+ }
+
+ visible = is_visible_fsp(smb_fname->fsp);
+ if (!visible) {
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(fname);
+ TALLOC_FREE(dname);
+ continue;
+ }
+
+ if (!S_ISLNK(smb_fname->st.st_ex_mode)) {
+ goto done;
+ }
+
+ if (lp_host_msdfs() && lp_msdfs_root(SNUM(conn)) &&
+ is_msdfs_link(dir_hnd->fsp, smb_fname))
+ {
+ DBG_INFO("Masquerading msdfs link %s as a directory\n",
+ smb_fname->base_name);
+
+ smb_fname->st.st_ex_mode = (smb_fname->st.st_ex_mode &
+ ~S_IFMT) |
+ S_IFDIR;
+
+ mode = dos_mode_msdfs(conn, dname, &smb_fname->st);
+ get_dosmode = false;
+ ask_sharemode = false;
+ goto done;
+ }
+
+ if (posix) {
+ /*
+ * Posix always wants to see symlinks.
+ */
+ ask_sharemode = false;
+ goto done;
+ }
+
+ if (!lp_follow_symlinks(SNUM(conn))) {
+ /*
+ * Hide symlinks not followed
+ */
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(fname);
+ TALLOC_FREE(dname);
+ continue;
+ }
+
+ /*
+ * We have to find out if it's a dangling
+ * symlink. Use the fat logic behind
+ * openat_pathref_fsp().
+ */
+
+ {
+ struct files_struct *fsp = smb_fname->fsp;
+ smb_fname_fsp_unlink(smb_fname);
+ fd_close(fsp);
+ file_free(NULL, fsp);
+ }
+
+ status = openat_pathref_fsp(dir_hnd->fsp, smb_fname);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * Dangling symlink. Hide.
+ */
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(fname);
+ TALLOC_FREE(dname);
+ continue;
+ }
+
+done:
+ if (get_dosmode) {
+ mode = fdos_mode(smb_fname->fsp);
+ smb_fname->st = smb_fname->fsp->fsp_name->st;
+ }
+
+ if (!dir_check_ftype(mode, dirtype)) {
+ DBG_INFO("[%s] attribs 0x%" PRIx32 " didn't match "
+ "0x%" PRIx32 "\n",
+ fname,
+ mode,
+ dirtype);
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(dname);
+ TALLOC_FREE(fname);
+ continue;
+ }
+
+ if (ask_sharemode && !S_ISDIR(smb_fname->st.st_ex_mode)) {
+ struct timespec write_time_ts;
+ struct file_id fileid;
+
+ fileid = vfs_file_id_from_sbuf(conn,
+ &smb_fname->st);
+ get_file_infos(fileid, 0, NULL, &write_time_ts);
+ if (!is_omit_timespec(&write_time_ts)) {
+ update_stat_ex_mtime(&smb_fname->st,
+ write_time_ts);
+ }
+ }
+
+ if (toplevel_dotdot) {
+ /*
+ * Ensure posix fileid and sids are hidden
+ */
+ smb_fname->st.st_ex_ino = 0;
+ smb_fname->st.st_ex_dev = 0;
+ smb_fname->st.st_ex_uid = -1;
+ smb_fname->st.st_ex_gid = -1;
+ }
+
+ DBG_NOTICE("mask=[%s] found %s fname=%s (%s)\n",
+ mask,
+ smb_fname_str_dbg(smb_fname),
+ dname,
+ fname);
+
+ TALLOC_FREE(dname);
+
+ *_smb_fname = talloc_move(ctx, &smb_fname);
+ *_fname = fname;
+ *_mode = mode;
+
+ return true;
+ }
+
+ return false;
+}
+
+void smbd_dirptr_push_overflow(struct dptr_struct *dirptr,
+ char **_fname,
+ struct smb_filename **_smb_fname,
+ uint32_t mode)
+{
+ SMB_ASSERT(dirptr->overflow.fname == NULL);
+ SMB_ASSERT(dirptr->overflow.smb_fname == NULL);
+
+ dirptr->overflow.fname = talloc_move(dirptr, _fname);
+ dirptr->overflow.smb_fname = talloc_move(dirptr, _smb_fname);
+ dirptr->overflow.mode = mode;
+}
+
+void smbd_dirptr_set_last_name_sent(struct dptr_struct *dirptr,
+ char **_fname)
+{
+ TALLOC_FREE(dirptr->last_name_sent);
+ dirptr->last_name_sent = talloc_move(dirptr, _fname);
+}
+
+char *smbd_dirptr_get_last_name_sent(struct dptr_struct *dirptr)
+{
+ return dirptr->last_name_sent;
+}
+
+/*******************************************************************
+ Check to see if a user can read an fsp . This is only approximate,
+ it is used as part of the "hide unreadable" option. Don't
+ use it for anything security sensitive.
+********************************************************************/
+
+static bool user_can_read_fsp(struct files_struct *fsp)
+{
+ NTSTATUS status;
+ uint32_t rejected_share_access = 0;
+ uint32_t rejected_mask = 0;
+ struct security_descriptor *sd = NULL;
+ uint32_t access_mask = FILE_READ_DATA|
+ FILE_READ_EA|
+ FILE_READ_ATTRIBUTES|
+ SEC_STD_READ_CONTROL;
+
+ /*
+ * Never hide files from the root user.
+ * We use (uid_t)0 here not sec_initial_uid()
+ * as make test uses a single user context.
+ */
+
+ if (get_current_uid(fsp->conn) == (uid_t)0) {
+ return true;
+ }
+
+ /*
+ * We can't directly use smbd_check_access_rights_fsp()
+ * here, as this implicitly grants FILE_READ_ATTRIBUTES
+ * which the Windows access-based-enumeration code
+ * explicitly checks for on the file security descriptor.
+ * See bug:
+ *
+ * https://bugzilla.samba.org/show_bug.cgi?id=10252
+ *
+ * and the smb2.acl2.ACCESSBASED test for details.
+ */
+
+ rejected_share_access = access_mask & ~(fsp->conn->share_access);
+ if (rejected_share_access) {
+ DBG_DEBUG("rejected share access 0x%x "
+ "on %s (0x%x)\n",
+ (unsigned int)access_mask,
+ fsp_str_dbg(fsp),
+ (unsigned int)rejected_share_access);
+ return false;
+ }
+
+ status = SMB_VFS_FGET_NT_ACL(metadata_fsp(fsp),
+ (SECINFO_OWNER |
+ SECINFO_GROUP |
+ SECINFO_DACL),
+ talloc_tos(),
+ &sd);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Could not get acl "
+ "on %s: %s\n",
+ fsp_str_dbg(fsp),
+ nt_errstr(status));
+ return false;
+ }
+
+ status = se_file_access_check(sd,
+ get_current_nttok(fsp->conn),
+ false,
+ access_mask,
+ &rejected_mask);
+
+ TALLOC_FREE(sd);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ DBG_DEBUG("rejected bits 0x%x read access for %s\n",
+ (unsigned int)rejected_mask,
+ fsp_str_dbg(fsp));
+ return false;
+ }
+ return true;
+}
+
+/*******************************************************************
+ Check to see if a user can write to an fsp.
+ Always return true for directories.
+ This is only approximate,
+ it is used as part of the "hide unwriteable" option. Don't
+ use it for anything security sensitive.
+********************************************************************/
+
+static bool user_can_write_fsp(struct files_struct *fsp)
+{
+ /*
+ * Never hide files from the root user.
+ * We use (uid_t)0 here not sec_initial_uid()
+ * as make test uses a single user context.
+ */
+
+ if (get_current_uid(fsp->conn) == (uid_t)0) {
+ return true;
+ }
+
+ if (fsp->fsp_flags.is_directory) {
+ return true;
+ }
+
+ return can_write_to_fsp(fsp);
+}
+
+/*******************************************************************
+ Is a file a "special" type ?
+********************************************************************/
+
+static bool file_is_special(connection_struct *conn,
+ const struct smb_filename *smb_fname)
+{
+ /*
+ * Never hide files from the root user.
+ * We use (uid_t)0 here not sec_initial_uid()
+ * as make test uses a single user context.
+ */
+
+ if (get_current_uid(conn) == (uid_t)0) {
+ return False;
+ }
+
+ SMB_ASSERT(VALID_STAT(smb_fname->st));
+
+ if (S_ISREG(smb_fname->st.st_ex_mode) ||
+ S_ISDIR(smb_fname->st.st_ex_mode) ||
+ S_ISLNK(smb_fname->st.st_ex_mode))
+ return False;
+
+ return True;
+}
+
+/*******************************************************************
+ Should the file be seen by the client?
+********************************************************************/
+
+bool is_visible_fsp(struct files_struct *fsp)
+{
+ bool hide_unreadable = false;
+ bool hide_unwriteable = false;
+ bool hide_special = false;
+ int hide_new_files_timeout = 0;
+ const char *last_component = NULL;
+
+ /*
+ * If the file does not exist, there's no point checking
+ * the configuration options. We succeed, on the basis that the
+ * checks *might* have passed if the file was present.
+ */
+ if (fsp == NULL) {
+ return true;
+ }
+
+ hide_unreadable = lp_hide_unreadable(SNUM(fsp->conn));
+ hide_unwriteable = lp_hide_unwriteable_files(SNUM(fsp->conn));
+ hide_special = lp_hide_special_files(SNUM(fsp->conn));
+ hide_new_files_timeout = lp_hide_new_files_timeout(SNUM(fsp->conn));
+
+ if (!hide_unreadable &&
+ !hide_unwriteable &&
+ !hide_special &&
+ (hide_new_files_timeout == 0))
+ {
+ return true;
+ }
+
+ fsp = metadata_fsp(fsp);
+
+ /* Get the last component of the base name. */
+ last_component = strrchr_m(fsp->fsp_name->base_name, '/');
+ if (!last_component) {
+ last_component = fsp->fsp_name->base_name;
+ } else {
+ last_component++; /* Go past '/' */
+ }
+
+ if (ISDOT(last_component) || ISDOTDOT(last_component)) {
+ return true; /* . and .. are always visible. */
+ }
+
+ if (fsp_get_pathref_fd(fsp) == -1) {
+ /*
+ * Symlink in POSIX mode or MS-DFS.
+ * We've checked veto files so the
+ * only thing we can check is the
+ * hide_new_files_timeout.
+ */
+ if ((hide_new_files_timeout != 0) &&
+ !S_ISDIR(fsp->fsp_name->st.st_ex_mode)) {
+ double age = timespec_elapsed(
+ &fsp->fsp_name->st.st_ex_mtime);
+
+ if (age < (double)hide_new_files_timeout) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /* Honour _hide unreadable_ option */
+ if (hide_unreadable && !user_can_read_fsp(fsp)) {
+ DBG_DEBUG("file %s is unreadable.\n", fsp_str_dbg(fsp));
+ return false;
+ }
+
+ /* Honour _hide unwriteable_ option */
+ if (hide_unwriteable && !user_can_write_fsp(fsp)) {
+ DBG_DEBUG("file %s is unwritable.\n", fsp_str_dbg(fsp));
+ return false;
+ }
+
+ /* Honour _hide_special_ option */
+ if (hide_special && file_is_special(fsp->conn, fsp->fsp_name)) {
+ DBG_DEBUG("file %s is special.\n", fsp_str_dbg(fsp));
+ return false;
+ }
+
+ if ((hide_new_files_timeout != 0) &&
+ !S_ISDIR(fsp->fsp_name->st.st_ex_mode)) {
+ double age = timespec_elapsed(&fsp->fsp_name->st.st_ex_mtime);
+
+ if (age < (double)hide_new_files_timeout) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static int smb_Dir_destructor(struct smb_Dir *dir_hnd)
+{
+ files_struct *fsp = dir_hnd->fsp;
+
+ SMB_VFS_CLOSEDIR(dir_hnd->conn, dir_hnd->dir);
+ fsp_set_fd(fsp, -1);
+ if (fsp->dptr != NULL) {
+ SMB_ASSERT(fsp->dptr->dir_hnd == dir_hnd);
+ fsp->dptr->dir_hnd = NULL;
+ }
+ dir_hnd->fsp = NULL;
+ return 0;
+}
+
+/*******************************************************************
+ Open a directory.
+********************************************************************/
+
+static int smb_Dir_OpenDir_destructor(struct smb_Dir *dir_hnd)
+{
+ files_struct *fsp = dir_hnd->fsp;
+
+ smb_Dir_destructor(dir_hnd);
+ file_free(NULL, fsp);
+ return 0;
+}
+
+NTSTATUS OpenDir(TALLOC_CTX *mem_ctx,
+ connection_struct *conn,
+ const struct smb_filename *smb_dname,
+ const char *mask,
+ uint32_t attr,
+ struct smb_Dir **_dir_hnd)
+{
+ struct files_struct *fsp = NULL;
+ struct smb_Dir *dir_hnd = NULL;
+ NTSTATUS status;
+
+ status = open_internal_dirfsp(conn,
+ smb_dname,
+ O_RDONLY,
+ &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = OpenDir_fsp(mem_ctx, conn, fsp, mask, attr, &dir_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * This overwrites the destructor set by OpenDir_fsp() but
+ * smb_Dir_OpenDir_destructor() calls the OpenDir_fsp()
+ * destructor.
+ */
+ talloc_set_destructor(dir_hnd, smb_Dir_OpenDir_destructor);
+
+ *_dir_hnd = dir_hnd;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS OpenDir_from_pathref(TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ const char *mask,
+ uint32_t attr,
+ struct smb_Dir **_dir_hnd)
+{
+ struct files_struct *fsp = NULL;
+ struct smb_Dir *dir_hnd = NULL;
+ NTSTATUS status;
+
+ status = openat_internal_dir_from_pathref(dirfsp, O_RDONLY, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = OpenDir_fsp(mem_ctx, fsp->conn, fsp, mask, attr, &dir_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * This overwrites the destructor set by OpenDir_fsp() but
+ * smb_Dir_OpenDir_destructor() calls the OpenDir_fsp()
+ * destructor.
+ */
+ talloc_set_destructor(dir_hnd, smb_Dir_OpenDir_destructor);
+
+ *_dir_hnd = dir_hnd;
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ Open a directory from an fsp.
+********************************************************************/
+
+static NTSTATUS OpenDir_fsp(
+ TALLOC_CTX *mem_ctx,
+ connection_struct *conn,
+ files_struct *fsp,
+ const char *mask,
+ uint32_t attr,
+ struct smb_Dir **_dir_hnd)
+{
+ struct smb_Dir *dir_hnd = talloc_zero(mem_ctx, struct smb_Dir);
+ NTSTATUS status;
+
+ if (!dir_hnd) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!fsp->fsp_flags.is_directory) {
+ status = NT_STATUS_INVALID_HANDLE;
+ goto fail;
+ }
+
+ if (fsp_get_io_fd(fsp) == -1) {
+ status = NT_STATUS_INVALID_HANDLE;
+ goto fail;
+ }
+
+ dir_hnd->conn = conn;
+
+ dir_hnd->dir_smb_fname = cp_smb_filename(dir_hnd, fsp->fsp_name);
+ if (!dir_hnd->dir_smb_fname) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ dir_hnd->dir = SMB_VFS_FDOPENDIR(fsp, mask, attr);
+ if (dir_hnd->dir == NULL) {
+ status = map_nt_error_from_unix(errno);
+ goto fail;
+ }
+ dir_hnd->fsp = fsp;
+ if (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) {
+ dir_hnd->case_sensitive = true;
+ } else {
+ dir_hnd->case_sensitive = conn->case_sensitive;
+ }
+
+ talloc_set_destructor(dir_hnd, smb_Dir_destructor);
+
+ *_dir_hnd = dir_hnd;
+ return NT_STATUS_OK;
+
+ fail:
+ TALLOC_FREE(dir_hnd);
+ return status;
+}
+
+
+/*******************************************************************
+ Read from a directory.
+ Return directory entry, current offset, and optional stat information.
+ Don't check for veto or invisible files.
+********************************************************************/
+
+const char *ReadDirName(struct smb_Dir *dir_hnd, char **ptalloced)
+{
+ const char *n;
+ char *talloced = NULL;
+ connection_struct *conn = dir_hnd->conn;
+
+ if (dir_hnd->file_number < 2) {
+ if (dir_hnd->file_number == 0) {
+ n = ".";
+ } else {
+ n = "..";
+ }
+ dir_hnd->file_number++;
+ *ptalloced = NULL;
+ return n;
+ }
+
+ while ((n = vfs_readdirname(conn,
+ dir_hnd->fsp,
+ dir_hnd->dir,
+ &talloced))) {
+ /* Ignore . and .. - we've already returned them. */
+ if (ISDOT(n) || ISDOTDOT(n)) {
+ TALLOC_FREE(talloced);
+ continue;
+ }
+ *ptalloced = talloced;
+ dir_hnd->file_number++;
+ return n;
+ }
+ *ptalloced = NULL;
+ return NULL;
+}
+
+/*******************************************************************
+ Rewind to the start.
+********************************************************************/
+
+void RewindDir(struct smb_Dir *dir_hnd)
+{
+ SMB_VFS_REWINDDIR(dir_hnd->conn, dir_hnd->dir);
+ dir_hnd->file_number = 0;
+}
+
+struct files_below_forall_state {
+ char *dirpath;
+ ssize_t dirpath_len;
+ int (*fn)(struct file_id fid, const struct share_mode_data *data,
+ void *private_data);
+ void *private_data;
+};
+
+static int files_below_forall_fn(struct file_id fid,
+ const struct share_mode_data *data,
+ void *private_data)
+{
+ struct files_below_forall_state *state = private_data;
+ char tmpbuf[PATH_MAX];
+ char *fullpath, *to_free;
+ ssize_t len;
+
+ len = full_path_tos(data->servicepath, data->base_name,
+ tmpbuf, sizeof(tmpbuf),
+ &fullpath, &to_free);
+ if (len == -1) {
+ return 0;
+ }
+ if (state->dirpath_len >= len) {
+ /*
+ * Filter files above dirpath
+ */
+ goto out;
+ }
+ if (fullpath[state->dirpath_len] != '/') {
+ /*
+ * Filter file that don't have a path separator at the end of
+ * dirpath's length
+ */
+ goto out;
+ }
+
+ if (memcmp(state->dirpath, fullpath, state->dirpath_len) != 0) {
+ /*
+ * Not a parent
+ */
+ goto out;
+ }
+
+ TALLOC_FREE(to_free);
+ return state->fn(fid, data, state->private_data);
+
+out:
+ TALLOC_FREE(to_free);
+ return 0;
+}
+
+static int files_below_forall(connection_struct *conn,
+ const struct smb_filename *dir_name,
+ int (*fn)(struct file_id fid,
+ const struct share_mode_data *data,
+ void *private_data),
+ void *private_data)
+{
+ struct files_below_forall_state state = {
+ .fn = fn,
+ .private_data = private_data,
+ };
+ int ret;
+ char tmpbuf[PATH_MAX];
+ char *to_free;
+
+ state.dirpath_len = full_path_tos(conn->connectpath,
+ dir_name->base_name,
+ tmpbuf, sizeof(tmpbuf),
+ &state.dirpath, &to_free);
+ if (state.dirpath_len == -1) {
+ return -1;
+ }
+
+ ret = share_mode_forall(files_below_forall_fn, &state);
+ TALLOC_FREE(to_free);
+ return ret;
+}
+
+struct have_file_open_below_state {
+ bool found_one;
+};
+
+static int have_file_open_below_fn(struct file_id fid,
+ const struct share_mode_data *data,
+ void *private_data)
+{
+ struct have_file_open_below_state *state = private_data;
+ state->found_one = true;
+ return 1;
+}
+
+bool have_file_open_below(connection_struct *conn,
+ const struct smb_filename *name)
+{
+ struct have_file_open_below_state state = {
+ .found_one = false,
+ };
+ int ret;
+
+ if (!VALID_STAT(name->st)) {
+ return false;
+ }
+ if (!S_ISDIR(name->st.st_ex_mode)) {
+ return false;
+ }
+
+ ret = files_below_forall(conn, name, have_file_open_below_fn, &state);
+ if (ret == -1) {
+ return false;
+ }
+
+ return state.found_one;
+}
+
+/*****************************************************************
+ Is this directory empty ?
+*****************************************************************/
+
+NTSTATUS can_delete_directory_fsp(files_struct *fsp)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ const char *dname = NULL;
+ char *talloced = NULL;
+ struct connection_struct *conn = fsp->conn;
+ struct smb_Dir *dir_hnd = NULL;
+
+ status = OpenDir(
+ talloc_tos(), conn, fsp->fsp_name, NULL, 0, &dir_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ while ((dname = ReadDirName(dir_hnd, &talloced))) {
+ struct smb_filename *smb_dname_full = NULL;
+ struct smb_filename *direntry_fname = NULL;
+ char *fullname = NULL;
+ int ret;
+
+ if (ISDOT(dname) || (ISDOTDOT(dname))) {
+ TALLOC_FREE(talloced);
+ continue;
+ }
+ if (IS_VETO_PATH(conn, dname)) {
+ TALLOC_FREE(talloced);
+ continue;
+ }
+
+ fullname = talloc_asprintf(talloc_tos(),
+ "%s/%s",
+ fsp->fsp_name->base_name,
+ dname);
+ if (fullname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ break;
+ }
+
+ smb_dname_full = synthetic_smb_fname(talloc_tos(),
+ fullname,
+ NULL,
+ NULL,
+ fsp->fsp_name->twrp,
+ fsp->fsp_name->flags);
+ if (smb_dname_full == NULL) {
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ status = NT_STATUS_NO_MEMORY;
+ break;
+ }
+
+ ret = SMB_VFS_LSTAT(conn, smb_dname_full);
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ break;
+ }
+
+ if (S_ISLNK(smb_dname_full->st.st_ex_mode)) {
+ /* Could it be an msdfs link ? */
+ if (lp_host_msdfs() &&
+ lp_msdfs_root(SNUM(conn))) {
+ struct smb_filename *smb_dname;
+ smb_dname = synthetic_smb_fname(talloc_tos(),
+ dname,
+ NULL,
+ &smb_dname_full->st,
+ fsp->fsp_name->twrp,
+ fsp->fsp_name->flags);
+ if (smb_dname == NULL) {
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ status = NT_STATUS_NO_MEMORY;
+ break;
+ }
+ if (is_msdfs_link(fsp, smb_dname)) {
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ TALLOC_FREE(smb_dname);
+ DBG_DEBUG("got msdfs link name %s "
+ "- can't delete directory %s\n",
+ dname,
+ fsp_str_dbg(fsp));
+ status = NT_STATUS_DIRECTORY_NOT_EMPTY;
+ break;
+ }
+ TALLOC_FREE(smb_dname);
+ }
+ /* Not a DFS link - could it be a dangling symlink ? */
+ ret = SMB_VFS_STAT(conn, smb_dname_full);
+ if (ret == -1 && (errno == ENOENT || errno == ELOOP)) {
+ /*
+ * Dangling symlink.
+ * Allow if "delete veto files = yes"
+ */
+ if (lp_delete_veto_files(SNUM(conn))) {
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ continue;
+ }
+ }
+ DBG_DEBUG("got symlink name %s - "
+ "can't delete directory %s\n",
+ dname,
+ fsp_str_dbg(fsp));
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ status = NT_STATUS_DIRECTORY_NOT_EMPTY;
+ break;
+ }
+
+ /* Not a symlink, get a pathref. */
+ status = synthetic_pathref(talloc_tos(),
+ fsp,
+ dname,
+ NULL,
+ &smb_dname_full->st,
+ fsp->fsp_name->twrp,
+ fsp->fsp_name->flags,
+ &direntry_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ status = map_nt_error_from_unix(errno);
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ break;
+ }
+
+ if (!is_visible_fsp(direntry_fname->fsp)) {
+ /*
+ * Hidden file.
+ * Allow if "delete veto files = yes"
+ */
+ if (lp_delete_veto_files(SNUM(conn))) {
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ TALLOC_FREE(direntry_fname);
+ continue;
+ }
+ }
+
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ TALLOC_FREE(direntry_fname);
+
+ DBG_DEBUG("got name %s - can't delete\n", dname);
+ status = NT_STATUS_DIRECTORY_NOT_EMPTY;
+ break;
+ }
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(dir_hnd);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!(fsp->posix_flags & FSP_POSIX_FLAGS_RENAME) &&
+ lp_strict_rename(SNUM(conn)) &&
+ have_file_open_below(fsp->conn, fsp->fsp_name))
+ {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/dir.h b/source3/smbd/dir.h
new file mode 100644
index 0000000..d520d13
--- /dev/null
+++ b/source3/smbd/dir.h
@@ -0,0 +1,89 @@
+/*
+ * Unix SMB/Netbios implementation.
+ *
+ * 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/>.
+ */
+
+#ifndef _SOURCE3_SMBD_DIR_H_
+#define _SOURCE3_SMBD_DIR_H_
+
+#include "includes.h"
+
+struct smb_Dir;
+struct dptr_struct;
+
+NTSTATUS can_delete_directory_fsp(files_struct *fsp);
+struct files_struct *dir_hnd_fetch_fsp(struct smb_Dir *dir_hnd);
+uint16_t dptr_attr(struct smbd_server_connection *sconn, int key);
+bool dptr_case_sensitive(struct dptr_struct *dptr);
+void dptr_closecnum(connection_struct *conn);
+void dptr_CloseDir(files_struct *fsp);
+NTSTATUS dptr_create(connection_struct *conn,
+ struct smb_request *req,
+ files_struct *fsp,
+ bool old_handle,
+ const char *wcard,
+ uint32_t attr,
+ struct dptr_struct **dptr_ret);
+int dptr_dnum(struct dptr_struct *dptr);
+files_struct *dptr_fetch_lanman2_fsp(struct smbd_server_connection *sconn,
+ int dptr_num);
+unsigned int dptr_FileNumber(struct dptr_struct *dptr);
+bool dptr_get_priv(struct dptr_struct *dptr);
+bool dptr_has_wild(struct dptr_struct *dptr);
+const char *dptr_path(struct smbd_server_connection *sconn, int key);
+char *dptr_ReadDirName(TALLOC_CTX *ctx, struct dptr_struct *dptr);
+void dptr_RewindDir(struct dptr_struct *dptr);
+void dptr_set_priv(struct dptr_struct *dptr);
+const char *dptr_wcard(struct smbd_server_connection *sconn, int key);
+bool have_file_open_below(connection_struct *conn,
+ const struct smb_filename *name);
+bool init_dptrs(struct smbd_server_connection *sconn);
+bool is_visible_fsp(files_struct *fsp);
+NTSTATUS OpenDir(TALLOC_CTX *mem_ctx,
+ connection_struct *conn,
+ const struct smb_filename *smb_dname,
+ const char *mask,
+ uint32_t attr,
+ struct smb_Dir **_dir_hnd);
+NTSTATUS OpenDir_from_pathref(TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ const char *mask,
+ uint32_t attr,
+ struct smb_Dir **_dir_hnd);
+const char *ReadDirName(struct smb_Dir *dir_hnd, char **talloced);
+void RewindDir(struct smb_Dir *dir_hnd);
+bool smbd_dirptr_get_entry(TALLOC_CTX *ctx,
+ struct dptr_struct *dirptr,
+ const char *mask,
+ uint32_t dirtype,
+ bool dont_descend,
+ bool ask_sharemode,
+ bool get_dosmode,
+ bool (*match_fn)(TALLOC_CTX *ctx,
+ void *private_data,
+ const char *dname,
+ const char *mask,
+ char **_fname),
+ void *private_data,
+ char **_fname,
+ struct smb_filename **_smb_fname,
+ uint32_t *_mode);
+char *smbd_dirptr_get_last_name_sent(struct dptr_struct *dirptr);
+void smbd_dirptr_push_overflow(struct dptr_struct *dirptr,
+ char **_fname,
+ struct smb_filename **_smb_fname,
+ uint32_t mode);
+void smbd_dirptr_set_last_name_sent(struct dptr_struct *dirptr, char **_fname);
+#endif
diff --git a/source3/smbd/dmapi.c b/source3/smbd/dmapi.c
new file mode 100644
index 0000000..1943fe9
--- /dev/null
+++ b/source3/smbd/dmapi.c
@@ -0,0 +1,357 @@
+/*
+ Unix SMB/CIFS implementation.
+ DMAPI Support routines
+
+ Copyright (C) James Peach 2006
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_DMAPI
+
+#ifndef USE_DMAPI
+
+uint32_t dmapi_file_flags(const char * const path) { return 0; }
+bool dmapi_have_session(void) { return False; }
+const void * dmapi_get_current_session(void) { return NULL; }
+
+#else /* USE_DMAPI */
+
+#ifdef HAVE_XFS_DMAPI_H
+#include <xfs/dmapi.h>
+#elif defined(HAVE_SYS_DMI_H)
+#include <sys/dmi.h>
+#elif defined(HAVE_SYS_JFSDMAPI_H)
+#include <sys/jfsdmapi.h>
+#elif defined(HAVE_SYS_DMAPI_H)
+#include <sys/dmapi.h>
+#elif defined(HAVE_DMAPI_H)
+#include <dmapi.h>
+#endif
+
+#define DMAPI_SESSION_NAME "samba"
+#define DMAPI_TRACE 10
+
+struct smbd_dmapi_context {
+ dm_sessid_t session;
+ unsigned session_num;
+};
+
+/*
+ Initialise DMAPI session. The session is persistent kernel state,
+ so it might already exist, in which case we merely want to
+ reconnect to it. This function should be called as root.
+*/
+static int dmapi_init_session(struct smbd_dmapi_context *ctx)
+{
+ char buf[DM_SESSION_INFO_LEN];
+ size_t buflen;
+ uint nsessions = 5;
+ dm_sessid_t *sessions = NULL;
+ char *version;
+ char *session_name;
+ TALLOC_CTX *tmp_ctx = talloc_new(NULL);
+
+ int i, err;
+
+ if (ctx->session_num == 0) {
+ session_name = talloc_strdup(tmp_ctx, DMAPI_SESSION_NAME);
+ } else {
+ session_name = talloc_asprintf(tmp_ctx, "%s%u", DMAPI_SESSION_NAME,
+ ctx->session_num);
+ }
+
+ if (session_name == NULL) {
+ DEBUG(0,("Out of memory in dmapi_init_session\n"));
+ talloc_free(tmp_ctx);
+ return -1;
+ }
+
+
+ if (dm_init_service(&version) < 0) {
+ DEBUG(0, ("dm_init_service failed - disabling DMAPI\n"));
+ talloc_free(tmp_ctx);
+ return -1;
+ }
+
+ ZERO_STRUCT(buf);
+
+ /* Fetch kernel DMAPI sessions until we get any of them */
+ do {
+ dm_sessid_t *new_sessions;
+ nsessions *= 2;
+ new_sessions = talloc_realloc(tmp_ctx, sessions,
+ dm_sessid_t, nsessions);
+ if (new_sessions == NULL) {
+ talloc_free(tmp_ctx);
+ return -1;
+ }
+
+ sessions = new_sessions;
+ err = dm_getall_sessions(nsessions, sessions, &nsessions);
+ } while (err == -1 && errno == E2BIG);
+
+ if (err == -1) {
+ DEBUGADD(DMAPI_TRACE,
+ ("failed to retrieve DMAPI sessions: %s\n",
+ strerror(errno)));
+ talloc_free(tmp_ctx);
+ return -1;
+ }
+
+ /* Look through existing kernel DMAPI sessions to find out ours */
+ for (i = 0; i < nsessions; ++i) {
+ err = dm_query_session(sessions[i], sizeof(buf), buf, &buflen);
+ buf[sizeof(buf) - 1] = '\0';
+ if (err == 0 && strcmp(session_name, buf) == 0) {
+ ctx->session = sessions[i];
+ DEBUGADD(DMAPI_TRACE,
+ ("attached to existing DMAPI session "
+ "named '%s'\n", buf));
+ break;
+ }
+ }
+
+ /* No session already defined. */
+ if (ctx->session == DM_NO_SESSION) {
+ err = dm_create_session(DM_NO_SESSION,
+ session_name,
+ &ctx->session);
+ if (err < 0) {
+ DEBUGADD(DMAPI_TRACE,
+ ("failed to create new DMAPI session: %s\n",
+ strerror(errno)));
+ ctx->session = DM_NO_SESSION;
+ talloc_free(tmp_ctx);
+ return -1;
+ }
+
+ DEBUG(0, ("created new DMAPI session named '%s' for %s\n",
+ session_name, version));
+ }
+
+ if (ctx->session != DM_NO_SESSION) {
+ set_effective_capability(DMAPI_ACCESS_CAPABILITY);
+ }
+
+ /*
+ Note that we never end the DMAPI session. It gets re-used if possiblie.
+ DMAPI session is a kernel resource that is usually lives until server reboot
+ and doesn't get destroed when an application finishes.
+
+ However, we free list of references to DMAPI sessions we've got from the kernel
+ as it is not needed anymore once we have found (or created) our session.
+ */
+
+ talloc_free(tmp_ctx);
+ return 0;
+}
+
+/*
+ Return a pointer to our DMAPI session, if available.
+ This assumes that you have called dmapi_have_session() first.
+*/
+const void *dmapi_get_current_session(void)
+{
+ if (!dmapi_ctx) {
+ return NULL;
+ }
+
+ if (dmapi_ctx->session == DM_NO_SESSION) {
+ return NULL;
+ }
+
+ return (void *)&dmapi_ctx->session;
+}
+
+/*
+ dmapi_have_session() must be the first DMAPI call you make in Samba. It will
+ initialize DMAPI, if available, and tell you if you can get a DMAPI session.
+ This should be called in the client-specific child process.
+*/
+
+bool dmapi_have_session(void)
+{
+ if (!dmapi_ctx) {
+ dmapi_ctx = talloc(NULL, struct smbd_dmapi_context);
+ if (!dmapi_ctx) {
+ exit_server("unable to allocate smbd_dmapi_context");
+ }
+ dmapi_ctx->session = DM_NO_SESSION;
+ dmapi_ctx->session_num = 0;
+
+ become_root();
+ dmapi_init_session(dmapi_ctx);
+ unbecome_root();
+
+ }
+
+ return dmapi_ctx->session != DM_NO_SESSION;
+}
+
+/*
+ only call this when you get back an EINVAL error indicating that the
+ session you are using is invalid. This destroys the existing session
+ and creates a new one.
+ */
+bool dmapi_new_session(void)
+{
+ if (dmapi_have_session()) {
+ /* try to destroy the old one - this may not succeed */
+ dm_destroy_session(dmapi_ctx->session);
+ }
+ dmapi_ctx->session = DM_NO_SESSION;
+ become_root();
+ dmapi_ctx->session_num++;
+ dmapi_init_session(dmapi_ctx);
+ unbecome_root();
+ return dmapi_ctx->session != DM_NO_SESSION;
+}
+
+/*
+ only call this when exiting from master smbd process. DMAPI sessions
+ are long-lived kernel resources we ought to share across smbd processes.
+ However, we must free them when all smbd processes are finished to
+ allow other subsystems clean up properly. Not freeing DMAPI session
+ blocks certain HSM implementations from proper shutdown.
+*/
+bool dmapi_destroy_session(void)
+{
+ if (!dmapi_ctx) {
+ return true;
+ }
+ if (dmapi_ctx->session != DM_NO_SESSION) {
+ become_root();
+ if (0 == dm_destroy_session(dmapi_ctx->session)) {
+ dmapi_ctx->session_num--;
+ dmapi_ctx->session = DM_NO_SESSION;
+ } else {
+ DEBUG(0,("Couldn't destroy DMAPI session: %s\n",
+ strerror(errno)));
+ }
+ unbecome_root();
+ }
+ return dmapi_ctx->session == DM_NO_SESSION;
+}
+
+
+/*
+ This is default implementation of dmapi_file_flags() that is
+ called from VFS is_offline() call to know whether file is offline.
+ For GPFS-specific version see modules/vfs_tsmsm.c. It might be
+ that approach on querying existence of a specific attribute that
+ is used in vfs_tsmsm.c will work with other DMAPI-based HSM
+ implementations as well.
+*/
+uint32_t dmapi_file_flags(const char * const path)
+{
+ int err;
+ dm_eventset_t events = {0};
+ uint nevents;
+
+ dm_sessid_t dmapi_session;
+ dm_sessid_t *dmapi_session_ptr;
+ const void *_dmapi_session_ptr;
+ void *dm_handle = NULL;
+ size_t dm_handle_len = 0;
+
+ uint32_t flags = 0;
+
+ _dmapi_session_ptr = dmapi_get_current_session();
+ if (_dmapi_session_ptr == NULL) {
+ return 0;
+ }
+
+ dmapi_session_ptr = discard_const_p(dm_sessid_t, _dmapi_session_ptr);
+ dmapi_session = *dmapi_session_ptr;
+ if (dmapi_session == DM_NO_SESSION) {
+ return 0;
+ }
+
+ /* AIX has DMAPI but no POSIX capabilities support. In this case,
+ * we need to be root to do DMAPI manipulations.
+ */
+#ifndef HAVE_POSIX_CAPABILITIES
+ become_root();
+#endif
+
+ err = dm_path_to_handle(discard_const_p(char, path),
+ &dm_handle, &dm_handle_len);
+ if (err < 0) {
+ DEBUG(DMAPI_TRACE, ("dm_path_to_handle(%s): %s\n",
+ path, strerror(errno)));
+
+ if (errno != EPERM) {
+ goto done;
+ }
+
+ /* Linux capabilities are broken in that changing our
+ * user ID will clobber out effective capabilities irrespective
+ * of whether we have set PR_SET_KEEPCAPS. Fortunately, the
+ * capabilities are not removed from our permitted set, so we
+ * can re-acquire them if necessary.
+ */
+
+ set_effective_capability(DMAPI_ACCESS_CAPABILITY);
+
+ err = dm_path_to_handle(discard_const_p(char, path),
+ &dm_handle, &dm_handle_len);
+ if (err < 0) {
+ DEBUG(DMAPI_TRACE,
+ ("retrying dm_path_to_handle(%s): %s\n",
+ path, strerror(errno)));
+ goto done;
+ }
+ }
+
+ err = dm_get_eventlist(dmapi_session, dm_handle, dm_handle_len,
+ DM_NO_TOKEN, DM_EVENT_MAX, &events, &nevents);
+ if (err < 0) {
+ DEBUG(DMAPI_TRACE, ("dm_get_eventlist(%s): %s\n",
+ path, strerror(errno)));
+ dm_handle_free(dm_handle, dm_handle_len);
+ goto done;
+ }
+
+ /* We figure that the only reason a DMAPI application would be
+ * interested in trapping read events is that part of the file is
+ * offline.
+ */
+ DEBUG(DMAPI_TRACE, ("DMAPI event list for %s\n", path));
+ if (DMEV_ISSET(DM_EVENT_READ, events)) {
+ flags = FILE_ATTRIBUTE_OFFLINE;
+ }
+
+ dm_handle_free(dm_handle, dm_handle_len);
+
+ if (flags & FILE_ATTRIBUTE_OFFLINE) {
+ DEBUG(DMAPI_TRACE, ("%s is OFFLINE\n", path));
+ }
+
+done:
+
+#ifndef HAVE_POSIX_CAPABILITIES
+ unbecome_root();
+#endif
+
+ return flags;
+}
+
+
+#endif /* USE_DMAPI */
diff --git a/source3/smbd/dnsregister.c b/source3/smbd/dnsregister.c
new file mode 100644
index 0000000..df18900
--- /dev/null
+++ b/source3/smbd/dnsregister.c
@@ -0,0 +1,200 @@
+/*
+ Unix SMB/CIFS implementation.
+ DNS-SD registration
+ Copyright (C) Rishi Srivatsavai 2007
+
+ 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 "smbd/smbd.h"
+
+/* Uses DNS service discovery (libdns_sd) to
+ * register the SMB service. SMB service is registered
+ * on ".local" domain via Multicast DNS & any
+ * other unicast DNS domains available.
+ *
+ * Users use the smbclient -B (Browse) option to
+ * browse for advertised SMB services.
+ */
+
+#define DNS_REG_RETRY_INTERVAL (5*60) /* in seconds */
+
+#ifdef WITH_DNSSD_SUPPORT
+
+#include <dns_sd.h>
+
+struct dns_reg_state {
+ struct tevent_context *event_ctx;
+ uint16_t port;
+ DNSServiceRef srv_ref;
+ struct tevent_timer *te;
+ int fd;
+ struct tevent_fd *fde;
+};
+
+static int dns_reg_state_destructor(struct dns_reg_state *dns_state)
+{
+ if (dns_state->srv_ref != NULL) {
+ /* Close connection to the mDNS daemon */
+ DNSServiceRefDeallocate(dns_state->srv_ref);
+ dns_state->srv_ref = NULL;
+ }
+
+ /* Clear event handler */
+ TALLOC_FREE(dns_state->te);
+ TALLOC_FREE(dns_state->fde);
+ dns_state->fd = -1;
+
+ return 0;
+}
+
+static void dns_register_smbd_retry(struct tevent_context *ctx,
+ struct tevent_timer *te,
+ struct timeval now,
+ void *private_data);
+static void dns_register_smbd_fde_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data);
+
+static bool dns_register_smbd_schedule(struct dns_reg_state *dns_state,
+ struct timeval tval)
+{
+ dns_reg_state_destructor(dns_state);
+
+ dns_state->te = tevent_add_timer(dns_state->event_ctx,
+ dns_state,
+ tval,
+ dns_register_smbd_retry,
+ dns_state);
+ if (!dns_state->te) {
+ return false;
+ }
+
+ return true;
+}
+
+static void dns_register_smbd_retry(struct tevent_context *ctx,
+ struct tevent_timer *te,
+ struct timeval now,
+ void *private_data)
+{
+ struct dns_reg_state *dns_state = talloc_get_type_abort(private_data,
+ struct dns_reg_state);
+ DNSServiceErrorType err;
+
+ dns_reg_state_destructor(dns_state);
+
+ DEBUG(6, ("registering _smb._tcp service on port %d\n",
+ dns_state->port));
+
+ /* Register service with DNS. Connects with the mDNS
+ * daemon running on the local system to perform DNS
+ * service registration.
+ */
+ err = DNSServiceRegister(&dns_state->srv_ref, 0 /* flags */,
+ kDNSServiceInterfaceIndexAny,
+ NULL /* service name */,
+ "_smb._tcp" /* service type */,
+ NULL /* domain */,
+ "" /* SRV target host name */,
+ htons(dns_state->port),
+ 0 /* TXT record len */,
+ NULL /* TXT record data */,
+ NULL /* callback func */,
+ NULL /* callback context */);
+
+ if (err != kDNSServiceErr_NoError) {
+ /* Failed to register service. Schedule a re-try attempt.
+ */
+ DEBUG(3, ("unable to register with mDNS (err %d)\n", err));
+ goto retry;
+ }
+
+ dns_state->fd = DNSServiceRefSockFD(dns_state->srv_ref);
+ if (dns_state->fd == -1) {
+ goto retry;
+ }
+
+ dns_state->fde = tevent_add_fd(dns_state->event_ctx,
+ dns_state,
+ dns_state->fd,
+ TEVENT_FD_READ,
+ dns_register_smbd_fde_handler,
+ dns_state);
+ if (!dns_state->fde) {
+ goto retry;
+ }
+
+ return;
+ retry:
+ dns_register_smbd_schedule(dns_state,
+ timeval_current_ofs(DNS_REG_RETRY_INTERVAL, 0));
+}
+
+/* Processes reply from mDNS daemon. Returns true if a reply was received */
+static void dns_register_smbd_fde_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct dns_reg_state *dns_state = talloc_get_type_abort(private_data,
+ struct dns_reg_state);
+ DNSServiceErrorType err;
+
+ err = DNSServiceProcessResult(dns_state->srv_ref);
+ if (err != kDNSServiceErr_NoError) {
+ DEBUG(3, ("failed to process mDNS result (err %d), re-trying\n",
+ err));
+ goto retry;
+ }
+
+ talloc_free(dns_state);
+ return;
+
+ retry:
+ dns_register_smbd_schedule(dns_state,
+ timeval_current_ofs(DNS_REG_RETRY_INTERVAL, 0));
+}
+
+bool smbd_setup_mdns_registration(struct tevent_context *ev,
+ TALLOC_CTX *mem_ctx,
+ uint16_t port)
+{
+ struct dns_reg_state *dns_state;
+
+ dns_state = talloc_zero(mem_ctx, struct dns_reg_state);
+ if (dns_state == NULL) {
+ return false;
+ }
+ dns_state->event_ctx = ev;
+ dns_state->port = port;
+ dns_state->fd = -1;
+
+ talloc_set_destructor(dns_state, dns_reg_state_destructor);
+
+ return dns_register_smbd_schedule(dns_state, timeval_zero());
+}
+
+#else /* WITH_DNSSD_SUPPORT */
+
+bool smbd_setup_mdns_registration(struct tevent_context *ev,
+ TALLOC_CTX *mem_ctx,
+ uint16_t port)
+{
+ return true;
+}
+
+#endif /* WITH_DNSSD_SUPPORT */
diff --git a/source3/smbd/dosmode.c b/source3/smbd/dosmode.c
new file mode 100644
index 0000000..b4b6955
--- /dev/null
+++ b/source3/smbd/dosmode.c
@@ -0,0 +1,1305 @@
+/*
+ Unix SMB/CIFS implementation.
+ dos mode handling functions
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) James Peach 2006
+
+ 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 "globals.h"
+#include "system/filesys.h"
+#include "librpc/gen_ndr/ndr_xattr.h"
+#include "librpc/gen_ndr/ioctl.h"
+#include "../libcli/security/security.h"
+#include "smbd/smbd.h"
+#include "lib/param/loadparm.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "lib/util/string_wrappers.h"
+#include "fake_file.h"
+
+static void dos_mode_debug_print(const char *func, uint32_t mode)
+{
+ fstring modestr;
+
+ if (DEBUGLEVEL < DBGLVL_INFO) {
+ return;
+ }
+
+ modestr[0] = '\0';
+
+ if (mode & FILE_ATTRIBUTE_HIDDEN) {
+ fstrcat(modestr, "h");
+ }
+ if (mode & FILE_ATTRIBUTE_READONLY) {
+ fstrcat(modestr, "r");
+ }
+ if (mode & FILE_ATTRIBUTE_SYSTEM) {
+ fstrcat(modestr, "s");
+ }
+ if (mode & FILE_ATTRIBUTE_DIRECTORY) {
+ fstrcat(modestr, "d");
+ }
+ if (mode & FILE_ATTRIBUTE_ARCHIVE) {
+ fstrcat(modestr, "a");
+ }
+ if (mode & FILE_ATTRIBUTE_SPARSE) {
+ fstrcat(modestr, "[sparse]");
+ }
+ if (mode & FILE_ATTRIBUTE_OFFLINE) {
+ fstrcat(modestr, "[offline]");
+ }
+ if (mode & FILE_ATTRIBUTE_COMPRESSED) {
+ fstrcat(modestr, "[compressed]");
+ }
+
+ DBG_INFO("%s returning (0x%x): \"%s\"\n", func, (unsigned)mode,
+ modestr);
+}
+
+static uint32_t filter_mode_by_protocol(uint32_t mode)
+{
+ if (get_Protocol() <= PROTOCOL_LANMAN2) {
+ DEBUG(10,("filter_mode_by_protocol: "
+ "filtering result 0x%x to 0x%x\n",
+ (unsigned int)mode,
+ (unsigned int)(mode & 0x3f) ));
+ mode &= 0x3f;
+ }
+ return mode;
+}
+
+/****************************************************************************
+ Change a dos mode to a unix mode.
+ Base permission for files:
+ if creating file and inheriting (i.e. parent_dir != NULL)
+ apply read/write bits from parent directory.
+ else
+ everybody gets read bit set
+ dos readonly is represented in unix by removing everyone's write bit
+ dos archive is represented in unix by the user's execute bit
+ dos system is represented in unix by the group's execute bit
+ dos hidden is represented in unix by the other's execute bit
+ if !inheriting {
+ Then apply create mask,
+ then add force bits.
+ }
+ Base permission for directories:
+ dos directory is represented in unix by unix's dir bit and the exec bit
+ if !inheriting {
+ Then apply create mask,
+ then add force bits.
+ }
+****************************************************************************/
+
+mode_t unix_mode(connection_struct *conn, int dosmode,
+ const struct smb_filename *smb_fname,
+ struct files_struct *parent_dirfsp)
+{
+ mode_t result = (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH);
+ mode_t dir_mode = 0; /* Mode of the inherit_from directory if
+ * inheriting. */
+
+ if ((dosmode & FILE_ATTRIBUTE_READONLY) &&
+ !lp_store_dos_attributes(SNUM(conn))) {
+ result &= ~(S_IWUSR | S_IWGRP | S_IWOTH);
+ }
+
+ if ((parent_dirfsp != NULL) && lp_inherit_permissions(SNUM(conn))) {
+ struct stat_ex sbuf = { .st_ex_nlink = 0, };
+ int ret;
+
+ DBG_DEBUG("[%s] inheriting from [%s]\n",
+ smb_fname_str_dbg(smb_fname),
+ smb_fname_str_dbg(parent_dirfsp->fsp_name));
+
+ ret = SMB_VFS_FSTAT(parent_dirfsp, &sbuf);
+ if (ret != 0) {
+ DBG_ERR("fstat failed [%s]: %s\n",
+ smb_fname_str_dbg(parent_dirfsp->fsp_name),
+ strerror(errno));
+ return(0); /* *** shouldn't happen! *** */
+ }
+
+ /* Save for later - but explicitly remove setuid bit for safety. */
+ dir_mode = sbuf.st_ex_mode & ~S_ISUID;
+ DEBUG(2,("unix_mode(%s) inherit mode %o\n",
+ smb_fname_str_dbg(smb_fname), (int)dir_mode));
+ /* Clear "result" */
+ result = 0;
+ }
+
+ if (dosmode & FILE_ATTRIBUTE_DIRECTORY) {
+ /* We never make directories read only for the owner as under DOS a user
+ can always create a file in a read-only directory. */
+ result |= (S_IFDIR | S_IWUSR);
+
+ if (dir_mode) {
+ /* Inherit mode of parent directory. */
+ result |= dir_mode;
+ } else {
+ /* Provisionally add all 'x' bits */
+ result |= (S_IXUSR | S_IXGRP | S_IXOTH);
+
+ /* Apply directory mask */
+ result &= lp_directory_mask(SNUM(conn));
+ /* Add in force bits */
+ result |= lp_force_directory_mode(SNUM(conn));
+ }
+ } else {
+ if ((dosmode & FILE_ATTRIBUTE_ARCHIVE) &&
+ lp_map_archive(SNUM(conn))) {
+ result |= S_IXUSR;
+ }
+
+ if ((dosmode & FILE_ATTRIBUTE_SYSTEM) &&
+ lp_map_system(SNUM(conn))) {
+ result |= S_IXGRP;
+ }
+
+ if ((dosmode & FILE_ATTRIBUTE_HIDDEN) &&
+ lp_map_hidden(SNUM(conn))) {
+ result |= S_IXOTH;
+ }
+
+ if (dir_mode) {
+ /* Inherit 666 component of parent directory mode */
+ result |= dir_mode & (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH);
+ } else {
+ /* Apply mode mask */
+ result &= lp_create_mask(SNUM(conn));
+ /* Add in force bits */
+ result |= lp_force_create_mode(SNUM(conn));
+ }
+ }
+
+ DBG_INFO("unix_mode(%s) returning 0%o\n",
+ smb_fname_str_dbg(smb_fname), (int)result);
+
+ return(result);
+}
+
+/****************************************************************************
+ Change a unix mode to a dos mode.
+****************************************************************************/
+
+static uint32_t dos_mode_from_sbuf(connection_struct *conn,
+ const struct stat_ex *st,
+ struct files_struct *fsp)
+{
+ int result = 0;
+ enum mapreadonly_options ro_opts =
+ (enum mapreadonly_options)lp_map_readonly(SNUM(conn));
+
+#if defined(UF_IMMUTABLE) && defined(SF_IMMUTABLE)
+ /* if we can find out if a file is immutable we should report it r/o */
+ if (st->st_ex_flags & (UF_IMMUTABLE | SF_IMMUTABLE)) {
+ result |= FILE_ATTRIBUTE_READONLY;
+ }
+#endif
+ if (ro_opts == MAP_READONLY_YES) {
+ /* Original Samba method - map inverse of user "w" bit. */
+ if ((st->st_ex_mode & S_IWUSR) == 0) {
+ result |= FILE_ATTRIBUTE_READONLY;
+ }
+ } else if (ro_opts == MAP_READONLY_PERMISSIONS) {
+ /* smb_fname->fsp can be NULL for an MS-DFS link. */
+ /* Check actual permissions for read-only. */
+ if ((fsp != NULL) && !can_write_to_fsp(fsp)) {
+ result |= FILE_ATTRIBUTE_READONLY;
+ }
+ } /* Else never set the readonly bit. */
+
+ if (MAP_ARCHIVE(conn) && ((st->st_ex_mode & S_IXUSR) != 0)) {
+ result |= FILE_ATTRIBUTE_ARCHIVE;
+ }
+
+ if (MAP_SYSTEM(conn) && ((st->st_ex_mode & S_IXGRP) != 0)) {
+ result |= FILE_ATTRIBUTE_SYSTEM;
+ }
+
+ if (MAP_HIDDEN(conn) && ((st->st_ex_mode & S_IXOTH) != 0)) {
+ result |= FILE_ATTRIBUTE_HIDDEN;
+ }
+
+ if (S_ISDIR(st->st_ex_mode)) {
+ result = FILE_ATTRIBUTE_DIRECTORY |
+ (result & FILE_ATTRIBUTE_READONLY);
+ }
+
+ dos_mode_debug_print(__func__, result);
+
+ return result;
+}
+
+/****************************************************************************
+ Get DOS attributes from an EA.
+ This can also pull the create time into the stat struct inside smb_fname.
+****************************************************************************/
+
+NTSTATUS parse_dos_attribute_blob(struct smb_filename *smb_fname,
+ DATA_BLOB blob,
+ uint32_t *pattr)
+{
+ struct xattr_DOSATTRIB dosattrib;
+ enum ndr_err_code ndr_err;
+ uint32_t dosattr;
+
+ ndr_err = ndr_pull_struct_blob(&blob, talloc_tos(), &dosattrib,
+ (ndr_pull_flags_fn_t)ndr_pull_xattr_DOSATTRIB);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_WARNING("bad ndr decode "
+ "from EA on file %s: Error = %s\n",
+ smb_fname_str_dbg(smb_fname),
+ ndr_errstr(ndr_err));
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ DBG_DEBUG("%s attr = %s\n",
+ smb_fname_str_dbg(smb_fname), dosattrib.attrib_hex);
+
+ switch (dosattrib.version) {
+ case 0xFFFF:
+ dosattr = dosattrib.info.compatinfoFFFF.attrib;
+ break;
+ case 1:
+ dosattr = dosattrib.info.info1.attrib;
+ if (!null_nttime(dosattrib.info.info1.create_time)) {
+ struct timespec create_time =
+ nt_time_to_unix_timespec(
+ dosattrib.info.info1.create_time);
+
+ update_stat_ex_create_time(&smb_fname->st,
+ create_time);
+
+ DBG_DEBUG("file %s case 1 set btime %s",
+ smb_fname_str_dbg(smb_fname),
+ time_to_asc(convert_timespec_to_time_t(
+ create_time)));
+ }
+ break;
+ case 2:
+ dosattr = dosattrib.info.oldinfo2.attrib;
+ /* Don't know what flags to check for this case. */
+ break;
+ case 3:
+ dosattr = dosattrib.info.info3.attrib;
+ if ((dosattrib.info.info3.valid_flags & XATTR_DOSINFO_CREATE_TIME) &&
+ !null_nttime(dosattrib.info.info3.create_time)) {
+ struct timespec create_time =
+ nt_time_to_full_timespec(
+ dosattrib.info.info3.create_time);
+
+ update_stat_ex_create_time(&smb_fname->st,
+ create_time);
+
+ DBG_DEBUG("file %s case 3 set btime %s",
+ smb_fname_str_dbg(smb_fname),
+ time_to_asc(convert_timespec_to_time_t(
+ create_time)));
+ }
+ break;
+ case 4:
+ case 5:
+ {
+ uint32_t info_valid_flags;
+ NTTIME info_create_time;
+
+ if (dosattrib.version == 4) {
+ info_valid_flags = dosattrib.info.info4.valid_flags;
+ info_create_time = dosattrib.info.info4.create_time;
+ dosattr = dosattrib.info.info4.attrib;
+ } else {
+ info_valid_flags = dosattrib.info.info5.valid_flags;
+ info_create_time = dosattrib.info.info5.create_time;
+ dosattr = dosattrib.info.info5.attrib;
+ }
+
+ if ((info_valid_flags & XATTR_DOSINFO_CREATE_TIME) &&
+ !null_nttime(info_create_time))
+ {
+ struct timespec creat_time;
+
+ creat_time = nt_time_to_full_timespec(info_create_time);
+ update_stat_ex_create_time(&smb_fname->st, creat_time);
+
+ DBG_DEBUG("file [%s] creation time [%s]\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_time_string(talloc_tos(), info_create_time));
+ }
+
+ break;
+ }
+ default:
+ DBG_WARNING("Badly formed DOSATTRIB on file %s - %s\n",
+ smb_fname_str_dbg(smb_fname), blob.data);
+ /* Should this be INTERNAL_ERROR? */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (S_ISDIR(smb_fname->st.st_ex_mode)) {
+ dosattr |= FILE_ATTRIBUTE_DIRECTORY;
+ }
+
+ /* FILE_ATTRIBUTE_SPARSE is valid on get but not on set. */
+ *pattr |= (uint32_t)(dosattr & (SAMBA_ATTRIBUTES_MASK|FILE_ATTRIBUTE_SPARSE));
+
+ dos_mode_debug_print(__func__, *pattr);
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS fget_ea_dos_attribute(struct files_struct *fsp,
+ uint32_t *pattr)
+{
+ DATA_BLOB blob;
+ ssize_t sizeret;
+ fstring attrstr;
+ NTSTATUS status;
+
+ if (!lp_store_dos_attributes(SNUM(fsp->conn))) {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ /* Don't reset pattr to zero as we may already have filename-based attributes we
+ need to preserve. */
+
+ sizeret = SMB_VFS_FGETXATTR(fsp,
+ SAMBA_XATTR_DOS_ATTRIB,
+ attrstr,
+ sizeof(attrstr));
+ if (sizeret == -1 && ( errno == EPERM || errno == EACCES )) {
+ /* we may also retrieve dos attribs for unreadable files, this
+ is why we'll retry as root. We don't use root in the first
+ run because in cases like NFS, root might have even less
+ rights than the real user
+ */
+ become_root();
+ sizeret = SMB_VFS_FGETXATTR(fsp,
+ SAMBA_XATTR_DOS_ATTRIB,
+ attrstr,
+ sizeof(attrstr));
+ unbecome_root();
+ }
+ if (sizeret == -1) {
+ DBG_INFO("Cannot get attribute "
+ "from EA on file %s: Error = %s\n",
+ fsp_str_dbg(fsp), strerror(errno));
+ return map_nt_error_from_unix(errno);
+ }
+
+ blob.data = (uint8_t *)attrstr;
+ blob.length = sizeret;
+
+ status = parse_dos_attribute_blob(fsp->fsp_name, blob, pattr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Set DOS attributes in an EA.
+ Also sets the create time.
+****************************************************************************/
+
+NTSTATUS set_ea_dos_attribute(connection_struct *conn,
+ struct smb_filename *smb_fname,
+ uint32_t dosmode)
+{
+ struct xattr_DOSATTRIB dosattrib = { .version = 0, };
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob = { .data = NULL, };
+ struct timespec btime;
+ int ret;
+
+ if (!lp_store_dos_attributes(SNUM(conn))) {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ if (smb_fname->fsp == NULL) {
+ /* symlink */
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ /*
+ * Don't store FILE_ATTRIBUTE_OFFLINE, it's dealt with in
+ * vfs_default via DMAPI if that is enabled.
+ */
+ dosmode &= ~FILE_ATTRIBUTE_OFFLINE;
+
+ dosattrib.version = 5;
+ dosattrib.info.info5.valid_flags = XATTR_DOSINFO_ATTRIB |
+ XATTR_DOSINFO_CREATE_TIME;
+ dosattrib.info.info5.attrib = dosmode;
+ dosattrib.info.info5.create_time = full_timespec_to_nt_time(
+ &smb_fname->st.st_ex_btime);
+
+ DEBUG(10,("set_ea_dos_attributes: set attribute 0x%x, btime = %s on file %s\n",
+ (unsigned int)dosmode,
+ time_to_asc(convert_timespec_to_time_t(smb_fname->st.st_ex_btime)),
+ smb_fname_str_dbg(smb_fname) ));
+
+ ndr_err = ndr_push_struct_blob(
+ &blob, talloc_tos(), &dosattrib,
+ (ndr_push_flags_fn_t)ndr_push_xattr_DOSATTRIB);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(5, ("create_acl_blob: ndr_push_xattr_DOSATTRIB failed: %s\n",
+ ndr_errstr(ndr_err)));
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ if (blob.data == NULL || blob.length == 0) {
+ /* Should this be INTERNAL_ERROR? */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ret = SMB_VFS_FSETXATTR(smb_fname->fsp,
+ SAMBA_XATTR_DOS_ATTRIB,
+ blob.data, blob.length, 0);
+ if (ret != 0) {
+ NTSTATUS status = NT_STATUS_OK;
+ bool set_dosmode_ok = false;
+
+ if ((errno != EPERM) && (errno != EACCES)) {
+ DBG_INFO("Cannot set "
+ "attribute EA on file %s: Error = %s\n",
+ smb_fname_str_dbg(smb_fname), strerror(errno));
+ return map_nt_error_from_unix(errno);
+ }
+
+ /* We want DOS semantics, ie allow non owner with write permission to change the
+ bits on a file. Just like file_ntimes below.
+ */
+
+ /* Check if we have write access. */
+ if (!CAN_WRITE(conn)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ status = smbd_check_access_rights_fsp(conn->cwd_fsp,
+ smb_fname->fsp,
+ false,
+ FILE_WRITE_ATTRIBUTES);
+ if (NT_STATUS_IS_OK(status)) {
+ set_dosmode_ok = true;
+ }
+
+ if (!set_dosmode_ok && lp_dos_filemode(SNUM(conn))) {
+ set_dosmode_ok = can_write_to_fsp(smb_fname->fsp);
+ }
+
+ if (!set_dosmode_ok) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ become_root();
+ ret = SMB_VFS_FSETXATTR(smb_fname->fsp,
+ SAMBA_XATTR_DOS_ATTRIB,
+ blob.data, blob.length, 0);
+ if (ret == 0) {
+ status = NT_STATUS_OK;
+ }
+ unbecome_root();
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ /*
+ * We correctly stored the create time.
+ * We *always* set XATTR_DOSINFO_CREATE_TIME,
+ * so now it can no longer be considered
+ * calculated. Make sure to use the value rounded
+ * to NTTIME granularity we've stored in the xattr.
+ */
+ btime = nt_time_to_full_timespec(dosattrib.info.info5.create_time);
+ update_stat_ex_create_time(&smb_fname->st, btime);
+
+ DEBUG(10,("set_ea_dos_attribute: set EA 0x%x on file %s\n",
+ (unsigned int)dosmode,
+ smb_fname_str_dbg(smb_fname)));
+ return NT_STATUS_OK;
+}
+
+static uint32_t
+dos_mode_from_name(connection_struct *conn, const char *name, uint32_t dosmode)
+{
+ const char *p = NULL;
+ uint32_t result = dosmode;
+
+ if (!(result & FILE_ATTRIBUTE_HIDDEN) &&
+ lp_hide_dot_files(SNUM(conn)))
+ {
+ p = strrchr_m(name, '/');
+ if (p) {
+ p++;
+ } else {
+ p = name;
+ }
+
+ /* Only . and .. are not hidden. */
+ if ((p[0] == '.') && !(ISDOT(p) || ISDOTDOT(p))) {
+ result |= FILE_ATTRIBUTE_HIDDEN;
+ }
+ }
+
+ if (!(result & FILE_ATTRIBUTE_HIDDEN) && IS_HIDDEN_PATH(conn, name)) {
+ result |= FILE_ATTRIBUTE_HIDDEN;
+ }
+
+ return result;
+}
+
+/****************************************************************************
+ Change a unix mode to a dos mode for an ms dfs link.
+****************************************************************************/
+
+uint32_t dos_mode_msdfs(connection_struct *conn,
+ const char *name,
+ const struct stat_ex *st)
+{
+ uint32_t result = 0;
+
+ DEBUG(8, ("dos_mode_msdfs: %s\n", name));
+
+ if (!VALID_STAT(*st)) {
+ return 0;
+ }
+
+ result = dos_mode_from_name(conn, name, result);
+ result |= dos_mode_from_sbuf(conn, st, NULL);
+
+ if (result == 0) {
+ result = FILE_ATTRIBUTE_NORMAL;
+ }
+
+ result = filter_mode_by_protocol(result);
+
+ /*
+ * Add in that it is a reparse point
+ */
+ result |= FILE_ATTRIBUTE_REPARSE_POINT;
+
+ dos_mode_debug_print(__func__, result);
+
+ return(result);
+}
+
+/*
+ * check whether a file or directory is flagged as compressed.
+ */
+static NTSTATUS dos_mode_check_compressed(struct files_struct *fsp,
+ bool *is_compressed)
+{
+ NTSTATUS status;
+ uint16_t compression_fmt;
+
+ status = SMB_VFS_FGET_COMPRESSION(
+ fsp->conn, talloc_tos(), fsp, &compression_fmt);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (compression_fmt == COMPRESSION_FORMAT_LZNT1) {
+ *is_compressed = true;
+ } else {
+ *is_compressed = false;
+ }
+ return NT_STATUS_OK;
+}
+
+static uint32_t dos_mode_post(uint32_t dosmode,
+ struct files_struct *fsp,
+ const char *func)
+{
+ struct smb_filename *smb_fname = NULL;
+ NTSTATUS status;
+
+ if (fsp != NULL) {
+ smb_fname = fsp->fsp_name;
+ }
+ SMB_ASSERT(smb_fname != NULL);
+
+ /*
+ * According to MS-FSA a stream name does not have
+ * separate DOS attribute metadata, so we must return
+ * the DOS attribute from the base filename. With one caveat,
+ * a non-default stream name can never be a directory.
+ *
+ * As this is common to all streams data stores, we handle
+ * it here instead of inside all stream VFS modules.
+ *
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=13380
+ */
+
+ if (is_named_stream(smb_fname)) {
+ /* is_ntfs_stream_smb_fname() returns false for a POSIX path. */
+ dosmode &= ~(FILE_ATTRIBUTE_DIRECTORY);
+ }
+
+ if (fsp->conn->fs_capabilities & FILE_FILE_COMPRESSION) {
+ bool compressed = false;
+
+ status = dos_mode_check_compressed(fsp, &compressed);
+ if (NT_STATUS_IS_OK(status) && compressed) {
+ dosmode |= FILE_ATTRIBUTE_COMPRESSED;
+ }
+ }
+
+ dosmode |= dos_mode_from_name(fsp->conn, smb_fname->base_name, dosmode);
+
+ if (S_ISDIR(smb_fname->st.st_ex_mode)) {
+ dosmode |= FILE_ATTRIBUTE_DIRECTORY;
+ } else if (dosmode == 0) {
+ dosmode = FILE_ATTRIBUTE_NORMAL;
+ }
+
+ dosmode = filter_mode_by_protocol(dosmode);
+
+ dos_mode_debug_print(func, dosmode);
+ return dosmode;
+}
+
+/****************************************************************************
+ Change a unix mode to a dos mode.
+ May also read the create timespec into the stat struct in smb_fname
+ if "store dos attributes" is true.
+****************************************************************************/
+
+uint32_t fdos_mode(struct files_struct *fsp)
+{
+ uint32_t result = 0;
+ NTSTATUS status = NT_STATUS_OK;
+
+ DBG_DEBUG("%s\n", fsp_str_dbg(fsp));
+
+ if (fsp->fake_file_handle != NULL) {
+ return dosmode_from_fake_filehandle(fsp->fake_file_handle);
+ }
+
+ if (!VALID_STAT(fsp->fsp_name->st)) {
+ return 0;
+ }
+
+ if (S_ISLNK(fsp->fsp_name->st.st_ex_mode)) {
+ return FILE_ATTRIBUTE_NORMAL;
+ }
+
+ if (fsp->fsp_name->st.cached_dos_attributes != FILE_ATTRIBUTE_INVALID) {
+ return fsp->fsp_name->st.cached_dos_attributes;
+ }
+
+ /* Get the DOS attributes via the VFS if we can */
+ status = SMB_VFS_FGET_DOS_ATTRIBUTES(fsp->conn,
+ metadata_fsp(fsp),
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * Only fall back to using UNIX modes if we get NOT_IMPLEMENTED.
+ */
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)) {
+ result |= dos_mode_from_sbuf(fsp->conn,
+ &fsp->fsp_name->st,
+ fsp);
+ }
+ }
+
+ fsp->fsp_name->st.cached_dos_attributes = dos_mode_post(result, fsp, __func__);
+ return fsp->fsp_name->st.cached_dos_attributes;
+}
+
+struct dos_mode_at_state {
+ files_struct *dir_fsp;
+ struct smb_filename *smb_fname;
+ uint32_t dosmode;
+};
+
+static void dos_mode_at_vfs_get_dosmode_done(struct tevent_req *subreq);
+
+struct tevent_req *dos_mode_at_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ files_struct *dir_fsp,
+ struct smb_filename *smb_fname)
+{
+ struct tevent_req *req = NULL;
+ struct dos_mode_at_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+
+ DBG_DEBUG("%s\n", smb_fname_str_dbg(smb_fname));
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct dos_mode_at_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ *state = (struct dos_mode_at_state) {
+ .dir_fsp = dir_fsp,
+ .smb_fname = smb_fname,
+ };
+
+ if (!VALID_STAT(smb_fname->st)) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ if (smb_fname->fsp == NULL) {
+ if (ISDOTDOT(smb_fname->base_name)) {
+ /*
+ * smb_fname->fsp is explicitly closed
+ * for ".." to prevent meta-data leakage.
+ */
+ state->dosmode = FILE_ATTRIBUTE_DIRECTORY;
+ } else {
+ /*
+ * This is a symlink in POSIX context.
+ * FIXME ? Should we move to returning
+ * FILE_ATTRIBUTE_REPARSE_POINT here ?
+ */
+ state->dosmode = FILE_ATTRIBUTE_NORMAL;
+ }
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = SMB_VFS_GET_DOS_ATTRIBUTES_SEND(state,
+ ev,
+ dir_fsp,
+ smb_fname);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, dos_mode_at_vfs_get_dosmode_done, req);
+
+ return req;
+}
+
+static void dos_mode_at_vfs_get_dosmode_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct dos_mode_at_state *state =
+ tevent_req_data(req,
+ struct dos_mode_at_state);
+ struct vfs_aio_state aio_state;
+ NTSTATUS status;
+ bool ok;
+
+ /*
+ * Make sure we run as the user again
+ */
+ ok = change_to_user_and_service_by_fsp(state->dir_fsp);
+ SMB_ASSERT(ok);
+
+ status = SMB_VFS_GET_DOS_ATTRIBUTES_RECV(subreq,
+ &aio_state,
+ &state->dosmode);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * Both the sync dos_mode() as well as the async
+ * dos_mode_at_[send|recv] have no real error return, the only
+ * unhandled error is when the stat info in smb_fname is not
+ * valid (cf the checks in dos_mode() and dos_mode_at_send().
+ *
+ * If SMB_VFS_GET_DOS_ATTRIBUTES[_SEND|_RECV] fails we must call
+ * dos_mode_post() which also does the mapping of a last resort
+ * from S_IFMT(st_mode).
+ *
+ * Only if we get NT_STATUS_NOT_IMPLEMENTED or
+ * NT_STATUS_NOT_SUPPORTED from a stacked VFS module we must
+ * fallback to sync processing.
+ */
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED))
+ {
+ /*
+ * state->dosmode should still be 0, but reset
+ * it to be sure.
+ */
+ state->dosmode = 0;
+ status = NT_STATUS_OK;
+ }
+ }
+ if (NT_STATUS_IS_OK(status)) {
+ state->dosmode = dos_mode_post(state->dosmode,
+ state->smb_fname->fsp,
+ __func__);
+ tevent_req_done(req);
+ return;
+ }
+
+ /*
+ * Fall back to sync dos_mode() if we got NOT_IMPLEMENTED.
+ */
+
+ state->dosmode = fdos_mode(state->smb_fname->fsp);
+ tevent_req_done(req);
+ return;
+}
+
+NTSTATUS dos_mode_at_recv(struct tevent_req *req, uint32_t *dosmode)
+{
+ struct dos_mode_at_state *state =
+ tevent_req_data(req,
+ struct dos_mode_at_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *dosmode = state->dosmode;
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ chmod a file - but preserve some bits.
+ If "store dos attributes" is also set it will store the create time
+ from the stat struct in smb_fname (in NTTIME format) in the EA
+ attribute also.
+********************************************************************/
+
+int file_set_dosmode(connection_struct *conn,
+ struct smb_filename *smb_fname,
+ uint32_t dosmode,
+ struct smb_filename *parent_dir,
+ bool newfile)
+{
+ int mask=0;
+ mode_t tmp;
+ mode_t unixmode;
+ int ret = -1;
+ NTSTATUS status;
+
+ if (!CAN_WRITE(conn)) {
+ errno = EROFS;
+ return -1;
+ }
+
+ if (S_ISLNK(smb_fname->st.st_ex_mode)) {
+ /* A symlink in POSIX context, ignore */
+ return 0;
+ }
+
+ if ((S_ISDIR(smb_fname->st.st_ex_mode)) &&
+ (dosmode & FILE_ATTRIBUTE_TEMPORARY))
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ dosmode &= SAMBA_ATTRIBUTES_MASK;
+
+ DEBUG(10,("file_set_dosmode: setting dos mode 0x%x on file %s\n",
+ dosmode, smb_fname_str_dbg(smb_fname)));
+
+ if (smb_fname->fsp == NULL) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ if (smb_fname->fsp->posix_flags & FSP_POSIX_FLAGS_OPEN &&
+ !lp_store_dos_attributes(SNUM(conn)))
+ {
+ return 0;
+ }
+
+ unixmode = smb_fname->st.st_ex_mode;
+
+ get_acl_group_bits(conn, smb_fname->fsp, &smb_fname->st.st_ex_mode);
+
+ if (S_ISDIR(smb_fname->st.st_ex_mode))
+ dosmode |= FILE_ATTRIBUTE_DIRECTORY;
+ else
+ dosmode &= ~FILE_ATTRIBUTE_DIRECTORY;
+
+ /* Store the DOS attributes in an EA by preference. */
+ status = SMB_VFS_FSET_DOS_ATTRIBUTES(conn,
+ metadata_fsp(smb_fname->fsp),
+ dosmode);
+ if (NT_STATUS_IS_OK(status)) {
+ smb_fname->st.cached_dos_attributes = dosmode;
+ ret = 0;
+ goto done;
+ }
+
+ /*
+ * Only fall back to using UNIX modes if
+ * we get NOT_IMPLEMENTED.
+ */
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)) {
+ errno = map_errno_from_nt_status(status);
+ return -1;
+ }
+
+ /* Fall back to UNIX modes. */
+ unixmode = unix_mode(
+ conn,
+ dosmode,
+ smb_fname,
+ parent_dir != NULL ? parent_dir->fsp : NULL);
+
+ /* preserve the file type bits */
+ mask |= S_IFMT;
+
+ /* preserve the s bits */
+ mask |= (S_ISUID | S_ISGID);
+
+ /* preserve the t bit */
+#ifdef S_ISVTX
+ mask |= S_ISVTX;
+#endif
+
+ /* possibly preserve the x bits */
+ if (!MAP_ARCHIVE(conn))
+ mask |= S_IXUSR;
+ if (!MAP_SYSTEM(conn))
+ mask |= S_IXGRP;
+ if (!MAP_HIDDEN(conn))
+ mask |= S_IXOTH;
+
+ unixmode |= (smb_fname->st.st_ex_mode & mask);
+
+ /* if we previously had any r bits set then leave them alone */
+ if ((tmp = smb_fname->st.st_ex_mode & (S_IRUSR|S_IRGRP|S_IROTH))) {
+ unixmode &= ~(S_IRUSR|S_IRGRP|S_IROTH);
+ unixmode |= tmp;
+ }
+
+ /* if we previously had any w bits set then leave them alone
+ whilst adding in the new w bits, if the new mode is not rdonly */
+ if (!(dosmode & FILE_ATTRIBUTE_READONLY)) {
+ unixmode |= (smb_fname->st.st_ex_mode & (S_IWUSR|S_IWGRP|S_IWOTH));
+ }
+
+ /*
+ * From the chmod 2 man page:
+ *
+ * "If the calling process is not privileged, and the group of the file
+ * does not match the effective group ID of the process or one of its
+ * supplementary group IDs, the S_ISGID bit will be turned off, but
+ * this will not cause an error to be returned."
+ *
+ * Simply refuse to do the chmod in this case.
+ */
+
+ if (S_ISDIR(smb_fname->st.st_ex_mode) &&
+ (unixmode & S_ISGID) &&
+ geteuid() != sec_initial_uid() &&
+ !current_user_in_group(conn, smb_fname->st.st_ex_gid))
+ {
+ DEBUG(3,("file_set_dosmode: setgid bit cannot be "
+ "set for directory %s\n",
+ smb_fname_str_dbg(smb_fname)));
+ errno = EPERM;
+ return -1;
+ }
+
+ ret = SMB_VFS_FCHMOD(smb_fname->fsp, unixmode);
+ if (ret == 0) {
+ goto done;
+ }
+
+ if((errno != EPERM) && (errno != EACCES))
+ return -1;
+
+ if(!lp_dos_filemode(SNUM(conn)))
+ return -1;
+
+ /* We want DOS semantics, ie allow non owner with write permission to change the
+ bits on a file. Just like file_ntimes below.
+ */
+
+ if (!can_write_to_fsp(smb_fname->fsp))
+ {
+ errno = EACCES;
+ return -1;
+ }
+
+ become_root();
+ ret = SMB_VFS_FCHMOD(smb_fname->fsp, unixmode);
+ unbecome_root();
+
+done:
+ if (!newfile) {
+ notify_fname(conn, NOTIFY_ACTION_MODIFIED,
+ FILE_NOTIFY_CHANGE_ATTRIBUTES,
+ smb_fname->base_name);
+ }
+ if (ret == 0) {
+ smb_fname->st.st_ex_mode = unixmode;
+ }
+
+ return( ret );
+}
+
+
+NTSTATUS file_set_sparse(connection_struct *conn,
+ files_struct *fsp,
+ bool sparse)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ uint32_t old_dosmode;
+ uint32_t new_dosmode;
+ NTSTATUS status;
+
+ if (!CAN_WRITE(conn)) {
+ DEBUG(9,("file_set_sparse: fname[%s] set[%u] "
+ "on readonly share[%s]\n",
+ smb_fname_str_dbg(fsp->fsp_name),
+ sparse,
+ lp_servicename(talloc_tos(), lp_sub, SNUM(conn))));
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+
+ /*
+ * Windows Server 2008 & 2012 permit FSCTL_SET_SPARSE if any of the
+ * following access flags are granted.
+ */
+ status = check_any_access_fsp(fsp,
+ FILE_WRITE_DATA
+ | FILE_WRITE_ATTRIBUTES
+ | SEC_FILE_APPEND_DATA);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("fname[%s] set[%u] "
+ "access_mask[0x%08X] - access denied\n",
+ smb_fname_str_dbg(fsp->fsp_name),
+ sparse,
+ fsp->access_mask);
+ return status;
+ }
+
+ if (fsp->fsp_flags.is_directory) {
+ DEBUG(9, ("invalid attempt to %s sparse flag on dir %s\n",
+ (sparse ? "set" : "clear"),
+ smb_fname_str_dbg(fsp->fsp_name)));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (IS_IPC(conn) || IS_PRINT(conn)) {
+ DEBUG(9, ("attempt to %s sparse flag over invalid conn\n",
+ (sparse ? "set" : "clear")));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (fsp_is_alternate_stream(fsp)) {
+ /*
+ * MS-FSA 2.1.1.5 IsSparse
+ *
+ * This is a per stream attribute, but our backends don't
+ * support it a consistent way, therefore just pretend
+ * success and ignore the request.
+ */
+ DBG_DEBUG("Ignoring request to set FILE_ATTRIBUTE_SPARSE on "
+ "[%s]\n", fsp_str_dbg(fsp));
+ return NT_STATUS_OK;
+ }
+
+ DEBUG(10,("file_set_sparse: setting sparse bit %u on file %s\n",
+ sparse, smb_fname_str_dbg(fsp->fsp_name)));
+
+ if (!lp_store_dos_attributes(SNUM(conn))) {
+ return NT_STATUS_INVALID_DEVICE_REQUEST;
+ }
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ old_dosmode = fdos_mode(fsp);
+
+ if (sparse && !(old_dosmode & FILE_ATTRIBUTE_SPARSE)) {
+ new_dosmode = old_dosmode | FILE_ATTRIBUTE_SPARSE;
+ } else if (!sparse && (old_dosmode & FILE_ATTRIBUTE_SPARSE)) {
+ new_dosmode = old_dosmode & ~FILE_ATTRIBUTE_SPARSE;
+ } else {
+ return NT_STATUS_OK;
+ }
+
+ /* Store the DOS attributes in an EA. */
+ status = SMB_VFS_FSET_DOS_ATTRIBUTES(conn, fsp, new_dosmode);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ notify_fname(conn, NOTIFY_ACTION_MODIFIED,
+ FILE_NOTIFY_CHANGE_ATTRIBUTES,
+ fsp->fsp_name->base_name);
+
+ fsp->fsp_name->st.cached_dos_attributes = new_dosmode;
+ fsp->fsp_flags.is_sparse = sparse;
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ Wrapper around the VFS ntimes that possibly allows DOS semantics rather
+ than POSIX.
+*******************************************************************/
+
+int file_ntimes(connection_struct *conn,
+ files_struct *fsp,
+ struct smb_file_time *ft)
+{
+ int ret = -1;
+
+ errno = 0;
+
+ DBG_INFO("actime: %s",
+ time_to_asc(convert_timespec_to_time_t(ft->atime)));
+ DBG_INFO("modtime: %s",
+ time_to_asc(convert_timespec_to_time_t(ft->mtime)));
+ DBG_INFO("ctime: %s",
+ time_to_asc(convert_timespec_to_time_t(ft->ctime)));
+ DBG_INFO("createtime: %s",
+ time_to_asc(convert_timespec_to_time_t(ft->create_time)));
+
+ /* Don't update the time on read-only shares */
+ /* We need this as set_filetime (which can be called on
+ close and other paths) can end up calling this function
+ without the NEED_WRITE protection. Found by :
+ Leo Weppelman <leo@wau.mis.ah.nl>
+ */
+
+ if (!CAN_WRITE(conn)) {
+ return 0;
+ }
+
+ if (SMB_VFS_FNTIMES(fsp, ft) == 0) {
+ return 0;
+ }
+
+ if((errno != EPERM) && (errno != EACCES)) {
+ return -1;
+ }
+
+ if(!lp_dos_filetimes(SNUM(conn))) {
+ return -1;
+ }
+
+ /* We have permission (given by the Samba admin) to
+ break POSIX semantics and allow a user to change
+ the time on a file they don't own but can write to
+ (as DOS does).
+ */
+
+ /* Check if we have write access. */
+ if (can_write_to_fsp(fsp)) {
+ /* We are allowed to become root and change the filetime. */
+ become_root();
+ ret = SMB_VFS_FNTIMES(fsp, ft);
+ unbecome_root();
+ }
+
+ return ret;
+}
+
+/******************************************************************
+ Force a "sticky" write time on a pathname. This will always be
+ returned on all future write time queries and set on close.
+******************************************************************/
+
+bool set_sticky_write_time_path(struct file_id fileid, struct timespec mtime)
+{
+ if (is_omit_timespec(&mtime)) {
+ return true;
+ }
+
+ if (!set_sticky_write_time(fileid, mtime)) {
+ return false;
+ }
+
+ return true;
+}
+
+/******************************************************************
+ Force a "sticky" write time on an fsp. This will always be
+ returned on all future write time queries and set on close.
+******************************************************************/
+
+bool set_sticky_write_time_fsp(struct files_struct *fsp, struct timespec mtime)
+{
+ if (is_omit_timespec(&mtime)) {
+ return true;
+ }
+
+ fsp->fsp_flags.write_time_forced = true;
+ TALLOC_FREE(fsp->update_write_time_event);
+
+ return set_sticky_write_time_path(fsp->file_id, mtime);
+}
+
+/******************************************************************
+ Set a create time EA.
+******************************************************************/
+
+NTSTATUS set_create_timespec_ea(struct files_struct *fsp,
+ struct timespec create_time)
+{
+ uint32_t dosmode;
+ int ret;
+
+ if (!lp_store_dos_attributes(SNUM(fsp->conn))) {
+ return NT_STATUS_OK;
+ }
+
+ dosmode = fdos_mode(fsp);
+
+ fsp->fsp_name->st.st_ex_btime = create_time;
+ ret = file_set_dosmode(fsp->conn, fsp->fsp_name, dosmode, NULL, false);
+ if (ret == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ DBG_DEBUG("wrote create time EA for file %s\n",
+ smb_fname_str_dbg(fsp->fsp_name));
+
+ return NT_STATUS_OK;
+}
+
+/******************************************************************
+ Return a create time.
+******************************************************************/
+
+struct timespec get_create_timespec(connection_struct *conn,
+ struct files_struct *fsp,
+ const struct smb_filename *smb_fname)
+{
+ if (fsp != NULL) {
+ struct files_struct *meta_fsp = metadata_fsp(fsp);
+ return meta_fsp->fsp_name->st.st_ex_btime;
+ }
+ return smb_fname->st.st_ex_btime;
+}
+
+/******************************************************************
+ Return a change time (may look at EA in future).
+******************************************************************/
+
+struct timespec get_change_timespec(connection_struct *conn,
+ struct files_struct *fsp,
+ const struct smb_filename *smb_fname)
+{
+ return smb_fname->st.st_ex_mtime;
+}
diff --git a/source3/smbd/durable.c b/source3/smbd/durable.c
new file mode 100644
index 0000000..5f55ff6
--- /dev/null
+++ b/source3/smbd/durable.c
@@ -0,0 +1,938 @@
+/*
+ Unix SMB/CIFS implementation.
+ Durable Handle default VFS implementation
+
+ Copyright (C) Stefan Metzmacher 2012
+ Copyright (C) Michael Adam 2012
+ Copyright (C) Volker Lendecke 2012
+
+ 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 "system/filesys.h"
+#include "lib/util/server_id.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "libcli/security/security.h"
+#include "messages.h"
+#include "librpc/gen_ndr/ndr_open_files.h"
+#include "serverid.h"
+#include "fake_file.h"
+#include "locking/leases_db.h"
+
+NTSTATUS vfs_default_durable_cookie(struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *cookie_blob)
+{
+ struct connection_struct *conn = fsp->conn;
+ enum ndr_err_code ndr_err;
+ struct vfs_default_durable_cookie cookie;
+
+ if (!lp_durable_handles(SNUM(conn))) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if (lp_kernel_share_modes(SNUM(conn))) {
+ /*
+ * We do not support durable handles
+ * if file system sharemodes are used
+ */
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if (lp_kernel_oplocks(SNUM(conn))) {
+ /*
+ * We do not support durable handles
+ * if kernel oplocks are used
+ */
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if ((fsp->current_lock_count > 0) &&
+ lp_posix_locking(fsp->conn->params))
+ {
+ /*
+ * We do not support durable handles
+ * if the handle has posix locks.
+ */
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if (fsp->fsp_flags.is_directory) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if (fsp_is_alternate_stream(fsp)) {
+ /*
+ * We do not support durable handles
+ * on streams for now.
+ */
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if (is_fake_file(fsp->fsp_name)) {
+ /*
+ * We do not support durable handles
+ * on fake files.
+ */
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ ZERO_STRUCT(cookie);
+ cookie.allow_reconnect = false;
+ cookie.id = fsp->file_id;
+ cookie.servicepath = conn->connectpath;
+ cookie.base_name = fsp->fsp_name->base_name;
+ cookie.initial_allocation_size = fsp->initial_allocation_size;
+ cookie.position_information = fh_get_position_information(fsp->fh);
+ cookie.update_write_time_triggered =
+ fsp->fsp_flags.update_write_time_triggered;
+ cookie.update_write_time_on_close =
+ fsp->fsp_flags.update_write_time_on_close;
+ cookie.write_time_forced = fsp->fsp_flags.write_time_forced;
+ cookie.close_write_time = full_timespec_to_nt_time(
+ &fsp->close_write_time);
+
+ cookie.stat_info.st_ex_dev = fsp->fsp_name->st.st_ex_dev;
+ cookie.stat_info.st_ex_ino = fsp->fsp_name->st.st_ex_ino;
+ cookie.stat_info.st_ex_mode = fsp->fsp_name->st.st_ex_mode;
+ cookie.stat_info.st_ex_nlink = fsp->fsp_name->st.st_ex_nlink;
+ cookie.stat_info.st_ex_uid = fsp->fsp_name->st.st_ex_uid;
+ cookie.stat_info.st_ex_gid = fsp->fsp_name->st.st_ex_gid;
+ cookie.stat_info.st_ex_rdev = fsp->fsp_name->st.st_ex_rdev;
+ cookie.stat_info.st_ex_size = fsp->fsp_name->st.st_ex_size;
+ cookie.stat_info.st_ex_atime = fsp->fsp_name->st.st_ex_atime;
+ cookie.stat_info.st_ex_mtime = fsp->fsp_name->st.st_ex_mtime;
+ cookie.stat_info.st_ex_ctime = fsp->fsp_name->st.st_ex_ctime;
+ cookie.stat_info.st_ex_btime = fsp->fsp_name->st.st_ex_btime;
+ cookie.stat_info.st_ex_iflags = fsp->fsp_name->st.st_ex_iflags;
+ cookie.stat_info.st_ex_blksize = fsp->fsp_name->st.st_ex_blksize;
+ cookie.stat_info.st_ex_blocks = fsp->fsp_name->st.st_ex_blocks;
+ cookie.stat_info.st_ex_flags = fsp->fsp_name->st.st_ex_flags;
+
+ ndr_err = ndr_push_struct_blob(cookie_blob, mem_ctx, &cookie,
+ (ndr_push_flags_fn_t)ndr_push_vfs_default_durable_cookie);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS vfs_default_durable_disconnect(struct files_struct *fsp,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *new_cookie)
+{
+ struct connection_struct *conn = fsp->conn;
+ NTSTATUS status;
+ enum ndr_err_code ndr_err;
+ struct vfs_default_durable_cookie cookie;
+ DATA_BLOB new_cookie_blob = data_blob_null;
+ struct share_mode_lock *lck;
+ bool ok;
+
+ *new_cookie = data_blob_null;
+
+ ZERO_STRUCT(cookie);
+
+ ndr_err = ndr_pull_struct_blob(&old_cookie, talloc_tos(), &cookie,
+ (ndr_pull_flags_fn_t)ndr_pull_vfs_default_durable_cookie);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ return status;
+ }
+
+ if (strcmp(cookie.magic, VFS_DEFAULT_DURABLE_COOKIE_MAGIC) != 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (cookie.version != VFS_DEFAULT_DURABLE_COOKIE_VERSION) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!file_id_equal(&fsp->file_id, &cookie.id)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if ((fsp_lease_type(fsp) & SMB2_LEASE_HANDLE) == 0) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ /*
+ * For now let it be simple and do not keep
+ * delete on close files durable open
+ */
+ if (fsp->fsp_flags.initial_delete_on_close) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+ if (fsp->fsp_flags.delete_on_close) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if (!VALID_STAT(fsp->fsp_name->st)) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if (!S_ISREG(fsp->fsp_name->st.st_ex_mode)) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ /* Ensure any pending write time updates are done. */
+ if (fsp->update_write_time_event) {
+ fsp_flush_write_time_update(fsp);
+ }
+
+ /*
+ * The above checks are done in mark_share_mode_disconnected() too
+ * but we want to avoid getting the lock if possible
+ */
+ lck = get_existing_share_mode_lock(talloc_tos(), fsp->file_id);
+ if (lck != NULL) {
+ struct smb_file_time ft;
+
+ init_smb_file_time(&ft);
+
+ if (fsp->fsp_flags.write_time_forced) {
+ NTTIME mtime = share_mode_changed_write_time(lck);
+ ft.mtime = nt_time_to_full_timespec(mtime);
+ } else if (fsp->fsp_flags.update_write_time_on_close) {
+ if (is_omit_timespec(&fsp->close_write_time)) {
+ ft.mtime = timespec_current();
+ } else {
+ ft.mtime = fsp->close_write_time;
+ }
+ }
+
+ if (!is_omit_timespec(&ft.mtime)) {
+ round_timespec(conn->ts_res, &ft.mtime);
+ file_ntimes(conn, fsp, &ft);
+ }
+
+ ok = mark_share_mode_disconnected(lck, fsp);
+ if (!ok) {
+ TALLOC_FREE(lck);
+ }
+ }
+ if (lck != NULL) {
+ ok = brl_mark_disconnected(fsp);
+ if (!ok) {
+ TALLOC_FREE(lck);
+ }
+ }
+ if (lck == NULL) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+ TALLOC_FREE(lck);
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ ZERO_STRUCT(cookie);
+ cookie.allow_reconnect = true;
+ cookie.id = fsp->file_id;
+ cookie.servicepath = conn->connectpath;
+ cookie.base_name = fsp_str_dbg(fsp);
+ cookie.initial_allocation_size = fsp->initial_allocation_size;
+ cookie.position_information = fh_get_position_information(fsp->fh);
+ cookie.update_write_time_triggered =
+ fsp->fsp_flags.update_write_time_triggered;
+ cookie.update_write_time_on_close =
+ fsp->fsp_flags.update_write_time_on_close;
+ cookie.write_time_forced = fsp->fsp_flags.write_time_forced;
+ cookie.close_write_time = full_timespec_to_nt_time(
+ &fsp->close_write_time);
+
+ cookie.stat_info.st_ex_dev = fsp->fsp_name->st.st_ex_dev;
+ cookie.stat_info.st_ex_ino = fsp->fsp_name->st.st_ex_ino;
+ cookie.stat_info.st_ex_mode = fsp->fsp_name->st.st_ex_mode;
+ cookie.stat_info.st_ex_nlink = fsp->fsp_name->st.st_ex_nlink;
+ cookie.stat_info.st_ex_uid = fsp->fsp_name->st.st_ex_uid;
+ cookie.stat_info.st_ex_gid = fsp->fsp_name->st.st_ex_gid;
+ cookie.stat_info.st_ex_rdev = fsp->fsp_name->st.st_ex_rdev;
+ cookie.stat_info.st_ex_size = fsp->fsp_name->st.st_ex_size;
+ cookie.stat_info.st_ex_atime = fsp->fsp_name->st.st_ex_atime;
+ cookie.stat_info.st_ex_mtime = fsp->fsp_name->st.st_ex_mtime;
+ cookie.stat_info.st_ex_ctime = fsp->fsp_name->st.st_ex_ctime;
+ cookie.stat_info.st_ex_btime = fsp->fsp_name->st.st_ex_btime;
+ cookie.stat_info.st_ex_iflags = fsp->fsp_name->st.st_ex_iflags;
+ cookie.stat_info.st_ex_blksize = fsp->fsp_name->st.st_ex_blksize;
+ cookie.stat_info.st_ex_blocks = fsp->fsp_name->st.st_ex_blocks;
+ cookie.stat_info.st_ex_flags = fsp->fsp_name->st.st_ex_flags;
+
+ ndr_err = ndr_push_struct_blob(&new_cookie_blob, mem_ctx, &cookie,
+ (ndr_push_flags_fn_t)ndr_push_vfs_default_durable_cookie);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ return status;
+ }
+
+ status = fd_close(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ data_blob_free(&new_cookie_blob);
+ return status;
+ }
+
+ *new_cookie = new_cookie_blob;
+ return NT_STATUS_OK;
+}
+
+
+/**
+ * Check whether a cookie-stored struct info is the same
+ * as a given SMB_STRUCT_STAT, as coming with the fsp.
+ */
+static bool vfs_default_durable_reconnect_check_stat(
+ struct vfs_default_durable_stat *cookie_st,
+ SMB_STRUCT_STAT *fsp_st,
+ const char *name)
+{
+ int ret;
+
+ if (cookie_st->st_ex_mode != fsp_st->st_ex_mode) {
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%llu != stat:%llu, "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_mode",
+ (unsigned long long)cookie_st->st_ex_mode,
+ (unsigned long long)fsp_st->st_ex_mode));
+ return false;
+ }
+
+ if (cookie_st->st_ex_nlink != fsp_st->st_ex_nlink) {
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%llu != stat:%llu, "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_nlink",
+ (unsigned long long)cookie_st->st_ex_nlink,
+ (unsigned long long)fsp_st->st_ex_nlink));
+ return false;
+ }
+
+ if (cookie_st->st_ex_uid != fsp_st->st_ex_uid) {
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%llu != stat:%llu, "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_uid",
+ (unsigned long long)cookie_st->st_ex_uid,
+ (unsigned long long)fsp_st->st_ex_uid));
+ return false;
+ }
+
+ if (cookie_st->st_ex_gid != fsp_st->st_ex_gid) {
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%llu != stat:%llu, "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_gid",
+ (unsigned long long)cookie_st->st_ex_gid,
+ (unsigned long long)fsp_st->st_ex_gid));
+ return false;
+ }
+
+ if (cookie_st->st_ex_rdev != fsp_st->st_ex_rdev) {
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%llu != stat:%llu, "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_rdev",
+ (unsigned long long)cookie_st->st_ex_rdev,
+ (unsigned long long)fsp_st->st_ex_rdev));
+ return false;
+ }
+
+ if (cookie_st->st_ex_size != fsp_st->st_ex_size) {
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%llu != stat:%llu, "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_size",
+ (unsigned long long)cookie_st->st_ex_size,
+ (unsigned long long)fsp_st->st_ex_size));
+ return false;
+ }
+
+ ret = timespec_compare(&cookie_st->st_ex_atime,
+ &fsp_st->st_ex_atime);
+ if (ret != 0) {
+ struct timeval tc, ts;
+ tc = convert_timespec_to_timeval(cookie_st->st_ex_atime);
+ ts = convert_timespec_to_timeval(fsp_st->st_ex_atime);
+
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:'%s' != stat:'%s', "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_atime",
+ timeval_string(talloc_tos(), &tc, true),
+ timeval_string(talloc_tos(), &ts, true)));
+ return false;
+ }
+
+ ret = timespec_compare(&cookie_st->st_ex_mtime,
+ &fsp_st->st_ex_mtime);
+ if (ret != 0) {
+ struct timeval tc, ts;
+ tc = convert_timespec_to_timeval(cookie_st->st_ex_mtime);
+ ts = convert_timespec_to_timeval(fsp_st->st_ex_mtime);
+
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:'%s' != stat:'%s', "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_mtime",
+ timeval_string(talloc_tos(), &tc, true),
+ timeval_string(talloc_tos(), &ts, true)));
+ return false;
+ }
+
+ ret = timespec_compare(&cookie_st->st_ex_ctime,
+ &fsp_st->st_ex_ctime);
+ if (ret != 0) {
+ struct timeval tc, ts;
+ tc = convert_timespec_to_timeval(cookie_st->st_ex_ctime);
+ ts = convert_timespec_to_timeval(fsp_st->st_ex_ctime);
+
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:'%s' != stat:'%s', "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_ctime",
+ timeval_string(talloc_tos(), &tc, true),
+ timeval_string(talloc_tos(), &ts, true)));
+ return false;
+ }
+
+ ret = timespec_compare(&cookie_st->st_ex_btime,
+ &fsp_st->st_ex_btime);
+ if (ret != 0) {
+ struct timeval tc, ts;
+ tc = convert_timespec_to_timeval(cookie_st->st_ex_btime);
+ ts = convert_timespec_to_timeval(fsp_st->st_ex_btime);
+
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:'%s' != stat:'%s', "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_btime",
+ timeval_string(talloc_tos(), &tc, true),
+ timeval_string(talloc_tos(), &ts, true)));
+ return false;
+ }
+
+ if (cookie_st->st_ex_iflags != fsp_st->st_ex_iflags) {
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%llu != stat:%llu, "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_calculated_birthtime",
+ (unsigned long long)cookie_st->st_ex_iflags,
+ (unsigned long long)fsp_st->st_ex_iflags));
+ return false;
+ }
+
+ if (cookie_st->st_ex_blksize != fsp_st->st_ex_blksize) {
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%llu != stat:%llu, "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_blksize",
+ (unsigned long long)cookie_st->st_ex_blksize,
+ (unsigned long long)fsp_st->st_ex_blksize));
+ return false;
+ }
+
+ if (cookie_st->st_ex_blocks != fsp_st->st_ex_blocks) {
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%llu != stat:%llu, "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_blocks",
+ (unsigned long long)cookie_st->st_ex_blocks,
+ (unsigned long long)fsp_st->st_ex_blocks));
+ return false;
+ }
+
+ if (cookie_st->st_ex_flags != fsp_st->st_ex_flags) {
+ DBG_WARNING(" (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%"PRIu32" != stat:%"PRIu32", "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_flags",
+ cookie_st->st_ex_flags,
+ fsp_st->st_ex_flags);
+ return false;
+ }
+
+ return true;
+}
+
+static bool durable_reconnect_fn(
+ struct share_mode_entry *e,
+ bool *modified,
+ void *private_data)
+{
+ struct share_mode_entry *dst_e = private_data;
+
+ if (dst_e->pid.pid != 0) {
+ DBG_INFO("Found more than one entry, invalidating previous\n");
+ dst_e->pid.pid = 0;
+ return true; /* end the loop through share mode entries */
+ }
+ *dst_e = *e;
+ return false; /* Look at potential other entries */
+}
+
+NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
+ struct smb_request *smb1req,
+ struct smbXsrv_open *op,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ files_struct **result,
+ DATA_BLOB *new_cookie)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct share_mode_lock *lck;
+ struct share_mode_entry e;
+ struct files_struct *fsp = NULL;
+ NTSTATUS status;
+ bool ok;
+ int ret;
+ struct vfs_open_how how = { .flags = 0, };
+ struct file_id file_id;
+ struct smb_filename *smb_fname = NULL;
+ enum ndr_err_code ndr_err;
+ struct vfs_default_durable_cookie cookie;
+ DATA_BLOB new_cookie_blob = data_blob_null;
+
+ *result = NULL;
+ *new_cookie = data_blob_null;
+
+ if (!lp_durable_handles(SNUM(conn))) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ /*
+ * the checks for kernel oplocks
+ * and similar things are done
+ * in the vfs_default_durable_cookie()
+ * call below.
+ */
+
+ ndr_err = ndr_pull_struct_blob_all(
+ &old_cookie,
+ talloc_tos(),
+ &cookie,
+ (ndr_pull_flags_fn_t)ndr_pull_vfs_default_durable_cookie);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ return status;
+ }
+
+ if (strcmp(cookie.magic, VFS_DEFAULT_DURABLE_COOKIE_MAGIC) != 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (cookie.version != VFS_DEFAULT_DURABLE_COOKIE_VERSION) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!cookie.allow_reconnect) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (strcmp(cookie.servicepath, conn->connectpath) != 0) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ /* Create an smb_filename with stream_name == NULL. */
+ smb_fname = synthetic_smb_fname(talloc_tos(),
+ cookie.base_name,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = SMB_VFS_LSTAT(conn, smb_fname);
+ if (ret == -1) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(1, ("Unable to lstat stream: %s => %s\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status)));
+ return status;
+ }
+
+ if (!S_ISREG(smb_fname->st.st_ex_mode)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ file_id = vfs_file_id_from_sbuf(conn, &smb_fname->st);
+ if (!file_id_equal(&cookie.id, &file_id)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ /*
+ * 1. check entry in locking.tdb
+ */
+
+ lck = get_existing_share_mode_lock(mem_ctx, file_id);
+ if (lck == NULL) {
+ DEBUG(5, ("vfs_default_durable_reconnect: share-mode lock "
+ "not obtained from db\n"));
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ e = (struct share_mode_entry) { .pid.pid = 0 };
+
+ ok = share_mode_forall_entries(lck, durable_reconnect_fn, &e);
+ if (!ok) {
+ DBG_WARNING("share_mode_forall_entries failed\n");
+ TALLOC_FREE(lck);
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ if (e.pid.pid == 0) {
+ DBG_WARNING("Did not find a unique valid share mode entry\n");
+ TALLOC_FREE(lck);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (!server_id_is_disconnected(&e.pid)) {
+ DEBUG(5, ("vfs_default_durable_reconnect: denying durable "
+ "reconnect for handle that was not marked "
+ "disconnected (e.g. smbd or cluster node died)\n"));
+ TALLOC_FREE(lck);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (e.share_file_id != op->global->open_persistent_id) {
+ DBG_INFO("denying durable "
+ "share_file_id changed %"PRIu64" != %"PRIu64" "
+ "(e.g. another client had opened the file)\n",
+ e.share_file_id,
+ op->global->open_persistent_id);
+ TALLOC_FREE(lck);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if ((e.access_mask & (FILE_WRITE_DATA|FILE_APPEND_DATA)) &&
+ !CAN_WRITE(conn))
+ {
+ DEBUG(5, ("vfs_default_durable_reconnect: denying durable "
+ "share[%s] is not writeable anymore\n",
+ lp_servicename(talloc_tos(), lp_sub, SNUM(conn))));
+ TALLOC_FREE(lck);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ /*
+ * 2. proceed with opening file
+ */
+
+ status = fsp_new(conn, conn, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("vfs_default_durable_reconnect: failed to create "
+ "new fsp: %s\n", nt_errstr(status)));
+ TALLOC_FREE(lck);
+ return status;
+ }
+
+ fh_set_private_options(fsp->fh, e.private_options);
+ fsp->file_id = file_id;
+ fsp->file_pid = smb1req->smbpid;
+ fsp->vuid = smb1req->vuid;
+ fsp->open_time = e.time;
+ fsp->access_mask = e.access_mask;
+ fsp->fsp_flags.can_read = ((fsp->access_mask & FILE_READ_DATA) != 0);
+ fsp->fsp_flags.can_write = ((fsp->access_mask & (FILE_WRITE_DATA|FILE_APPEND_DATA)) != 0);
+ fsp->fnum = op->local_id;
+ fsp_set_gen_id(fsp);
+
+ /*
+ * TODO:
+ * Do we need to store the modified flag in the DB?
+ */
+ fsp->fsp_flags.modified = false;
+ /*
+ * no durables for directories
+ */
+ fsp->fsp_flags.is_directory = false;
+ /*
+ * For normal files, can_lock == !is_directory
+ */
+ fsp->fsp_flags.can_lock = true;
+ /*
+ * We do not support aio write behind for smb2
+ */
+ fsp->fsp_flags.aio_write_behind = false;
+ fsp->oplock_type = e.op_type;
+
+ if (fsp->oplock_type == LEASE_OPLOCK) {
+ uint32_t current_state;
+ uint16_t lease_version, epoch;
+
+ /*
+ * Ensure the existing client guid matches the
+ * stored one in the share_mode_entry.
+ */
+ if (!GUID_equal(fsp_client_guid(fsp),
+ &e.client_guid)) {
+ TALLOC_FREE(lck);
+ file_free(smb1req, fsp);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ status = leases_db_get(
+ &e.client_guid,
+ &e.lease_key,
+ &file_id,
+ &current_state, /* current_state */
+ NULL, /* breaking */
+ NULL, /* breaking_to_requested */
+ NULL, /* breaking_to_required */
+ &lease_version, /* lease_version */
+ &epoch); /* epoch */
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(lck);
+ file_free(smb1req, fsp);
+ return status;
+ }
+
+ fsp->lease = find_fsp_lease(
+ fsp,
+ &e.lease_key,
+ current_state,
+ lease_version,
+ epoch);
+ if (fsp->lease == NULL) {
+ TALLOC_FREE(lck);
+ file_free(smb1req, fsp);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ fsp->initial_allocation_size = cookie.initial_allocation_size;
+ fh_set_position_information(fsp->fh, cookie.position_information);
+ fsp->fsp_flags.update_write_time_triggered =
+ cookie.update_write_time_triggered;
+ fsp->fsp_flags.update_write_time_on_close =
+ cookie.update_write_time_on_close;
+ fsp->fsp_flags.write_time_forced = cookie.write_time_forced;
+ fsp->close_write_time = nt_time_to_full_timespec(
+ cookie.close_write_time);
+
+ status = fsp_set_smb_fname(fsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(lck);
+ file_free(smb1req, fsp);
+ DEBUG(0, ("vfs_default_durable_reconnect: "
+ "fsp_set_smb_fname failed: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ op->compat = fsp;
+ fsp->op = op;
+
+ ok = reset_share_mode_entry(
+ lck,
+ e.pid,
+ e.share_file_id,
+ messaging_server_id(conn->sconn->msg_ctx),
+ smb1req->mid,
+ fh_get_gen_id(fsp->fh));
+ if (!ok) {
+ DBG_DEBUG("Could not set new share_mode_entry values\n");
+ TALLOC_FREE(lck);
+ op->compat = NULL;
+ fsp->op = NULL;
+ file_free(smb1req, fsp);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ ok = brl_reconnect_disconnected(fsp);
+ if (!ok) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ DEBUG(1, ("vfs_default_durable_reconnect: "
+ "failed to reopen brlocks: %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(lck);
+ op->compat = NULL;
+ fsp->op = NULL;
+ file_free(smb1req, fsp);
+ return status;
+ }
+
+ /*
+ * TODO: properly calculate open flags
+ */
+ if (fsp->fsp_flags.can_write && fsp->fsp_flags.can_read) {
+ how.flags = O_RDWR;
+ } else if (fsp->fsp_flags.can_write) {
+ how.flags = O_WRONLY;
+ } else if (fsp->fsp_flags.can_read) {
+ how.flags = O_RDONLY;
+ }
+
+ status = fd_openat(conn->cwd_fsp, fsp->fsp_name, fsp, &how);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(lck);
+ DEBUG(1, ("vfs_default_durable_reconnect: failed to open "
+ "file: %s\n", nt_errstr(status)));
+ op->compat = NULL;
+ fsp->op = NULL;
+ file_free(smb1req, fsp);
+ return status;
+ }
+
+ /*
+ * We now check the stat info stored in the cookie against
+ * the current stat data from the file we just opened.
+ * If any detail differs, we deny the durable reconnect,
+ * because in that case it is very likely that someone
+ * opened the file while the handle was disconnected,
+ * which has to be interpreted as an oplock break.
+ */
+
+ ret = SMB_VFS_FSTAT(fsp, &fsp->fsp_name->st);
+ if (ret == -1) {
+ NTSTATUS close_status;
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(1, ("Unable to fstat stream: %s => %s\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status)));
+ close_status = fd_close(fsp);
+ if (!NT_STATUS_IS_OK(close_status)) {
+ DBG_ERR("fd_close failed (%s) - leaking file "
+ "descriptor\n", nt_errstr(close_status));
+ }
+ TALLOC_FREE(lck);
+ op->compat = NULL;
+ fsp->op = NULL;
+ file_free(smb1req, fsp);
+ return status;
+ }
+
+ if (!S_ISREG(fsp->fsp_name->st.st_ex_mode)) {
+ NTSTATUS close_status = fd_close(fsp);
+ if (!NT_STATUS_IS_OK(close_status)) {
+ DBG_ERR("fd_close failed (%s) - leaking file "
+ "descriptor\n", nt_errstr(close_status));
+ }
+ TALLOC_FREE(lck);
+ op->compat = NULL;
+ fsp->op = NULL;
+ file_free(smb1req, fsp);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ file_id = vfs_file_id_from_sbuf(conn, &fsp->fsp_name->st);
+ if (!file_id_equal(&cookie.id, &file_id)) {
+ NTSTATUS close_status = fd_close(fsp);
+ if (!NT_STATUS_IS_OK(close_status)) {
+ DBG_ERR("fd_close failed (%s) - leaking file "
+ "descriptor\n", nt_errstr(close_status));
+ }
+ TALLOC_FREE(lck);
+ op->compat = NULL;
+ fsp->op = NULL;
+ file_free(smb1req, fsp);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ (void)fdos_mode(fsp);
+
+ ok = vfs_default_durable_reconnect_check_stat(&cookie.stat_info,
+ &fsp->fsp_name->st,
+ fsp_str_dbg(fsp));
+ if (!ok) {
+ NTSTATUS close_status = fd_close(fsp);
+ if (!NT_STATUS_IS_OK(close_status)) {
+ DBG_ERR("fd_close failed (%s) - leaking file "
+ "descriptor\n", nt_errstr(close_status));
+ }
+ TALLOC_FREE(lck);
+ op->compat = NULL;
+ fsp->op = NULL;
+ file_free(smb1req, fsp);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ status = set_file_oplock(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ NTSTATUS close_status = fd_close(fsp);
+ if (!NT_STATUS_IS_OK(close_status)) {
+ DBG_ERR("fd_close failed (%s) - leaking file "
+ "descriptor\n", nt_errstr(close_status));
+ }
+ TALLOC_FREE(lck);
+ op->compat = NULL;
+ fsp->op = NULL;
+ file_free(smb1req, fsp);
+ return status;
+ }
+
+ status = vfs_default_durable_cookie(fsp, mem_ctx, &new_cookie_blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(lck);
+ DEBUG(1, ("vfs_default_durable_reconnect: "
+ "vfs_default_durable_cookie - %s\n",
+ nt_errstr(status)));
+ op->compat = NULL;
+ fsp->op = NULL;
+ file_free(smb1req, fsp);
+ return status;
+ }
+
+ smb1req->chain_fsp = fsp;
+ smb1req->smb2req->compat_chain_fsp = fsp;
+
+ DEBUG(10, ("vfs_default_durable_reconnect: opened file '%s'\n",
+ fsp_str_dbg(fsp)));
+
+ TALLOC_FREE(lck);
+
+ fsp->fsp_flags.is_fsa = true;
+
+ *result = fsp;
+ *new_cookie = new_cookie_blob;
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/error.c b/source3/smbd/error.c
new file mode 100644
index 0000000..fdea191
--- /dev/null
+++ b/source3/smbd/error.c
@@ -0,0 +1,175 @@
+/*
+ Unix SMB/CIFS implementation.
+ error packet handling
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+
+bool use_nt_status(void)
+{
+ return lp_nt_status_support() && (global_client_caps & CAP_STATUS32);
+}
+
+/****************************************************************************
+ Create an error packet. Normally called using the ERROR() macro.
+
+ Setting eclass and ecode to zero and status to a valid NT error will
+ reply with an NT error if the client supports CAP_STATUS32, otherwise
+ it maps to and returns a DOS error if the client doesn't support CAP_STATUS32.
+ This is the normal mode of calling this function via reply_nterror(req, status).
+
+ Setting eclass and ecode to non-zero and status to NT_STATUS_OK (0) will map
+ from a DOS error to an NT error and reply with an NT error if the client
+ supports CAP_STATUS32, otherwise it replies with the given DOS error.
+ This mode is currently not used in the server.
+
+ Setting both eclass, ecode and status to non-zero values allows a non-default
+ mapping from NT error codes to DOS error codes, and will return one or the
+ other depending on the client supporting CAP_STATUS32 or not. This is the
+ path taken by calling reply_botherror(req, eclass, ecode, status);
+
+ Setting status to NT_STATUS_DOS(eclass, ecode) forces DOS errors even if the
+ client supports CAP_STATUS32. This is the path taken to force a DOS error
+ reply by calling reply_force_doserror(req, eclass, ecode).
+
+ Setting status only and eclass to -1 forces NT errors even if the client
+ doesn't support CAP_STATUS32. This mode is currently never used in the
+ server.
+****************************************************************************/
+
+void error_packet_set(char *outbuf, uint8_t eclass, uint32_t ecode, NTSTATUS ntstatus, int line, const char *file)
+{
+ bool force_nt_status = False;
+ bool force_dos_status = False;
+
+ if (eclass == (uint8_t)-1) {
+ force_nt_status = True;
+ } else if (NT_STATUS_IS_DOS(ntstatus)) {
+ force_dos_status = True;
+ }
+
+ if (force_nt_status || (!force_dos_status && lp_nt_status_support() && (global_client_caps & CAP_STATUS32))) {
+ /* We're returning an NT error. */
+ if (NT_STATUS_V(ntstatus) == 0 && eclass) {
+ ntstatus = dos_to_ntstatus(eclass, ecode);
+ }
+ SIVAL(outbuf,smb_rcls,NT_STATUS_V(ntstatus));
+ SSVAL(outbuf,smb_flg2, SVAL(outbuf,smb_flg2)|FLAGS2_32_BIT_ERROR_CODES);
+ /* This must not start with the word 'error', as this
+ * is reserved in the subunit stream protocol, causing
+ * false errors to show up when debugging is turned
+ * on */
+ DEBUG(3,("NT error packet at %s(%d) cmd=%d (%s) %s\n",
+ file, line,
+ (int)CVAL(outbuf,smb_com),
+#if defined(WITH_SMB1SERVER)
+ smb_fn_name(CVAL(outbuf,smb_com)),
+#else
+ "",
+#endif
+ nt_errstr(ntstatus)));
+ } else {
+ /* We're returning a DOS error only,
+ * nt_status_to_dos() pulls DOS error codes out of the
+ * NTSTATUS */
+ if (NT_STATUS_IS_DOS(ntstatus) || (eclass == 0 && NT_STATUS_V(ntstatus))) {
+ ntstatus_to_dos(ntstatus, &eclass, &ecode);
+ }
+
+ SSVAL(outbuf,smb_flg2, SVAL(outbuf,smb_flg2)&~FLAGS2_32_BIT_ERROR_CODES);
+ SSVAL(outbuf,smb_rcls,eclass);
+ SSVAL(outbuf,smb_err,ecode);
+
+ /* This must not start with the word 'error', as this
+ * is reserved in the subunit stream protocol, causing
+ * false errors to show up when debugging is turned
+ * on */
+ DEBUG(3,("DOS error packet at %s(%d) cmd=%d (%s) eclass=%d ecode=%d\n",
+ file, line,
+ (int)CVAL(outbuf,smb_com),
+#if defined(WITH_SMB1SERVER)
+ smb_fn_name(CVAL(outbuf,smb_com)),
+#else
+ "",
+#endif
+ eclass,
+ ecode));
+ }
+}
+
+size_t error_packet(char *outbuf, uint8_t eclass, uint32_t ecode, NTSTATUS ntstatus, int line, const char *file)
+{
+ size_t outsize = srv_smb1_set_message(outbuf,0,0,True);
+ error_packet_set(outbuf, eclass, ecode, ntstatus, line, file);
+ return outsize;
+}
+
+void reply_nt_error(struct smb_request *req, NTSTATUS ntstatus,
+ int line, const char *file)
+{
+ TALLOC_FREE(req->outbuf);
+ reply_smb1_outbuf(req, 0, 0);
+ error_packet_set((char *)req->outbuf, 0, 0, ntstatus, line, file);
+}
+
+/****************************************************************************
+ Forces a DOS error on the wire.
+****************************************************************************/
+
+void reply_force_dos_error(struct smb_request *req, uint8_t eclass, uint32_t ecode,
+ int line, const char *file)
+{
+ TALLOC_FREE(req->outbuf);
+ reply_smb1_outbuf(req, 0, 0);
+ error_packet_set((char *)req->outbuf,
+ eclass, ecode,
+ NT_STATUS_DOS(eclass, ecode),
+ line,
+ file);
+}
+
+void reply_both_error(struct smb_request *req, uint8_t eclass, uint32_t ecode,
+ NTSTATUS status, int line, const char *file)
+{
+ TALLOC_FREE(req->outbuf);
+ reply_smb1_outbuf(req, 0, 0);
+ error_packet_set((char *)req->outbuf, eclass, ecode, status,
+ line, file);
+}
+
+void reply_openerror(struct smb_request *req, NTSTATUS status)
+{
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) {
+ /*
+ * We hit an existing file, and if we're returning DOS
+ * error codes OBJECT_NAME_COLLISION would map to
+ * ERRDOS/183, we need to return ERRDOS/80, see bug
+ * 4852.
+ */
+ reply_botherror(req, NT_STATUS_OBJECT_NAME_COLLISION,
+ ERRDOS, ERRfilexists);
+ } else if (NT_STATUS_EQUAL(status, NT_STATUS_TOO_MANY_OPENED_FILES)) {
+ /* EMFILE always seems to be returned as a DOS error.
+ * See bug 6837. NOTE this forces a DOS error on the wire
+ * even though it's calling reply_nterror(). */
+ reply_force_doserror(req, ERRDOS, ERRnofids);
+ } else {
+ reply_nterror(req, status);
+ }
+}
diff --git a/source3/smbd/fake_file.c b/source3/smbd/fake_file.c
new file mode 100644
index 0000000..32f2595
--- /dev/null
+++ b/source3/smbd/fake_file.c
@@ -0,0 +1,213 @@
+/*
+ Unix SMB/CIFS implementation.
+ FAKE FILE support, for faking up special files windows want access to
+ Copyright (C) Stefan (metze) Metzmacher 2003
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "fake_file.h"
+#include "auth.h"
+
+struct fake_file_type {
+ const char *name;
+ enum FAKE_FILE_TYPE type;
+ void *(*init_pd)(TALLOC_CTX *mem_ctx);
+};
+
+static const struct fake_file_type fake_files[] = {
+#ifdef WITH_QUOTAS
+ {FAKE_FILE_NAME_QUOTA_UNIX, FAKE_FILE_TYPE_QUOTA, init_quota_handle},
+#endif /* WITH_QUOTAS */
+ {NULL, FAKE_FILE_TYPE_NONE, NULL}
+};
+
+/****************************************************************************
+ Create a fake file handle
+****************************************************************************/
+
+static struct fake_file_handle *init_fake_file_handle(enum FAKE_FILE_TYPE type)
+{
+ struct fake_file_handle *fh = NULL;
+ int i;
+
+ for (i=0; fake_files[i].name!=NULL; i++) {
+ if (fake_files[i].type==type) {
+ break;
+ }
+ }
+
+ if (fake_files[i].name == NULL) {
+ return NULL;
+ }
+
+ DEBUG(5,("init_fake_file_handle: for [%s]\n",fake_files[i].name));
+
+ fh = talloc(NULL, struct fake_file_handle);
+ if (fh == NULL) {
+ DEBUG(0,("TALLOC_ZERO() failed.\n"));
+ return NULL;
+ }
+
+ fh->type = type;
+
+ if (fake_files[i].init_pd) {
+ fh->private_data = fake_files[i].init_pd(fh);
+ }
+ return fh;
+}
+
+/****************************************************************************
+ Does this name match a fake filename ?
+****************************************************************************/
+
+enum FAKE_FILE_TYPE is_fake_file_path(const char *path)
+{
+ int i;
+
+ if (!path) {
+ return FAKE_FILE_TYPE_NONE;
+ }
+
+ for (i=0;fake_files[i].name!=NULL;i++) {
+ if (strncmp(path,fake_files[i].name,strlen(fake_files[i].name))==0) {
+ DEBUG(5,("is_fake_file: [%s] is a fake file\n",path));
+ return fake_files[i].type;
+ }
+ }
+
+ return FAKE_FILE_TYPE_NONE;
+}
+
+enum FAKE_FILE_TYPE is_fake_file(const struct smb_filename *smb_fname)
+{
+ char *fname = NULL;
+ NTSTATUS status;
+ enum FAKE_FILE_TYPE ret;
+
+ if (!smb_fname) {
+ return FAKE_FILE_TYPE_NONE;
+ }
+
+ status = get_full_smb_filename(talloc_tos(), smb_fname, &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return FAKE_FILE_TYPE_NONE;
+ }
+
+ ret = is_fake_file_path(fname);
+
+ TALLOC_FREE(fname);
+
+ return ret;
+}
+
+uint32_t dosmode_from_fake_filehandle(const struct fake_file_handle *ffh)
+{
+ if (ffh->type != FAKE_FILE_TYPE_QUOTA) {
+ DBG_ERR("Unexpected fake_file_handle: %d\n", ffh->type);
+ log_stack_trace();
+ return FILE_ATTRIBUTE_NORMAL;
+ }
+
+ /* This is what Windows 2016 returns */
+ return FILE_ATTRIBUTE_HIDDEN
+ | FILE_ATTRIBUTE_SYSTEM
+ | FILE_ATTRIBUTE_DIRECTORY
+ | FILE_ATTRIBUTE_ARCHIVE;
+}
+
+/****************************************************************************
+ Open a fake quota file with a share mode.
+****************************************************************************/
+
+NTSTATUS open_fake_file(struct smb_request *req, connection_struct *conn,
+ uint64_t current_vuid,
+ enum FAKE_FILE_TYPE fake_file_type,
+ const struct smb_filename *smb_fname,
+ uint32_t access_mask,
+ files_struct **result)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ files_struct *fsp = NULL;
+ NTSTATUS status;
+
+ /* access check */
+ if (geteuid() != sec_initial_uid()) {
+ DBG_NOTICE("access_denied to service[%s] file[%s] user[%s]\n",
+ lp_servicename(talloc_tos(), lp_sub, SNUM(conn)),
+ smb_fname_str_dbg(smb_fname),
+ conn->session_info->unix_info->unix_name);
+ return NT_STATUS_ACCESS_DENIED;
+
+ }
+
+ status = file_new(req, conn, &fsp);
+ if(!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DBG_INFO("fname = %s, %s, access_mask = 0x%"PRIx32"\n",
+ smb_fname_str_dbg(smb_fname),
+ fsp_fnum_dbg(fsp),
+ access_mask);
+
+ fsp->conn = conn;
+ fsp_set_fd(fsp, -1);
+ fsp->vuid = current_vuid;
+ fh_set_pos(fsp->fh, -1);
+ fsp->fsp_flags.can_lock = false; /* Should this be true ? - No, JRA */
+ fsp->access_mask = access_mask;
+ status = fsp_set_smb_fname(fsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ file_free(req, fsp);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ fsp->fake_file_handle = init_fake_file_handle(fake_file_type);
+
+ if (fsp->fake_file_handle==NULL) {
+ file_free(req, fsp);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = smbd_calculate_access_mask_fsp(conn->cwd_fsp,
+ fsp,
+ false,
+ access_mask,
+ &access_mask);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("smbd_calculate_access_mask_fsp "
+ "on service[%s] file[%s] returned %s\n",
+ lp_servicename(talloc_tos(), lp_sub, SNUM(conn)),
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status));
+ file_free(req, fsp);
+ return status;
+ }
+
+ *result = fsp;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS close_fake_file(struct smb_request *req, files_struct *fsp)
+{
+ /*
+ * Nothing to do, fake files don't hold any resources
+ */
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/fd_handle.c b/source3/smbd/fd_handle.c
new file mode 100644
index 0000000..eb7fa55
--- /dev/null
+++ b/source3/smbd/fd_handle.c
@@ -0,0 +1,147 @@
+/*
+ Unix SMB/CIFS implementation.
+ fd_handle structure handling
+ Copyright (C) Ralph Boehme 2020
+
+ 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 "fd_handle.h"
+
+struct fd_handle {
+ size_t ref_count;
+ int fd;
+ uint64_t position_information;
+ off_t pos;
+ /*
+ * NT Create options, but we only look at
+ * NTCREATEX_FLAG_DENY_DOS and
+ * NTCREATEX_FLAG_DENY_FCB.
+ */
+ uint32_t private_options;
+ uint64_t gen_id;
+};
+
+static int fd_handle_destructor(struct fd_handle *fh)
+{
+ SMB_ASSERT((fh->fd == -1) || (fh->fd == AT_FDCWD));
+ return 0;
+}
+
+struct fd_handle *fd_handle_create(TALLOC_CTX *mem_ctx)
+{
+ struct fd_handle *fh = NULL;
+
+ fh = talloc_zero(mem_ctx, struct fd_handle);
+ if (fh == NULL) {
+ return NULL;
+ }
+ fh->fd = -1;
+
+ talloc_set_destructor(fh, fd_handle_destructor);
+
+ return fh;
+}
+
+size_t fh_get_refcount(struct fd_handle *fh)
+{
+ return fh->ref_count;
+}
+
+void fh_set_refcount(struct fd_handle *fh, size_t ref_count)
+{
+ fh->ref_count = ref_count;
+}
+
+uint64_t fh_get_position_information(struct fd_handle *fh)
+{
+ return fh->position_information;
+}
+
+void fh_set_position_information(struct fd_handle *fh, uint64_t posinfo)
+{
+ fh->position_information = posinfo;
+}
+
+off_t fh_get_pos(struct fd_handle *fh)
+{
+ return fh->pos;
+}
+
+void fh_set_pos(struct fd_handle *fh, off_t pos)
+{
+ fh->pos = pos;
+}
+
+uint32_t fh_get_private_options(struct fd_handle *fh)
+{
+ return fh->private_options;
+}
+
+void fh_set_private_options(struct fd_handle *fh, uint32_t private_options)
+{
+ fh->private_options = private_options;
+}
+
+uint64_t fh_get_gen_id(struct fd_handle *fh)
+{
+ return fh->gen_id;
+}
+
+void fh_set_gen_id(struct fd_handle *fh, uint64_t gen_id)
+{
+ fh->gen_id = gen_id;
+}
+
+/****************************************************************************
+ Helper functions for working with fsp->fh->fd
+****************************************************************************/
+
+int fsp_get_io_fd(const struct files_struct *fsp)
+{
+ if (fsp->fsp_flags.is_pathref) {
+ DBG_ERR("fsp [%s] is a path referencing fsp\n",
+ fsp_str_dbg(fsp));
+#ifdef DEVELOPER
+ smb_panic("fsp is a pathref");
+#endif
+ return -1;
+ }
+
+ return fsp->fh->fd;
+}
+
+int fsp_get_pathref_fd(const struct files_struct *fsp)
+{
+ return fsp->fh->fd;
+}
+
+void fsp_set_fd(struct files_struct *fsp, int fd)
+{
+ /*
+ * Deliberately allow setting an fd if the existing fd is the
+ * same. This happens if a VFS module assigns the fd to
+ * fsp->fh->fd in its openat VFS function. The canonical place
+ * where the assignment is done is in fd_open(), but some VFS
+ * modules do it anyway.
+ */
+
+ SMB_ASSERT(fsp->fh->fd == -1 ||
+ fsp->fh->fd == fd ||
+ fd == -1 ||
+ fd == AT_FDCWD);
+
+ fsp->fh->fd = fd;
+}
diff --git a/source3/smbd/fd_handle.h b/source3/smbd/fd_handle.h
new file mode 100644
index 0000000..dc0e5e4
--- /dev/null
+++ b/source3/smbd/fd_handle.h
@@ -0,0 +1,49 @@
+/*
+ Unix SMB/CIFS implementation.
+ Files handle structure handling
+ Copyright (C) Ralph Boehme 2020
+
+ 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/>.
+*/
+
+#ifndef FD_HANDLE_H
+#define FD_HANDLE_H
+
+#include "replace.h"
+#include <talloc.h>
+
+struct fd_handle;
+
+struct fd_handle *fd_handle_create(TALLOC_CTX *mem_ctx);
+
+size_t fh_get_refcount(struct fd_handle *fh);
+void fh_set_refcount(struct fd_handle *fh, size_t ref_count);
+
+uint64_t fh_get_position_information(struct fd_handle *fh);
+void fh_set_position_information(struct fd_handle *fh, uint64_t posinfo);
+
+off_t fh_get_pos(struct fd_handle *fh);
+void fh_set_pos(struct fd_handle *fh, off_t pos);
+
+uint32_t fh_get_private_options(struct fd_handle *fh);
+void fh_set_private_options(struct fd_handle *fh, uint32_t private_options);
+
+uint64_t fh_get_gen_id(struct fd_handle *fh);
+void fh_set_gen_id(struct fd_handle *fh, uint64_t gen_id);
+
+int fsp_get_io_fd(const struct files_struct *fsp);
+int fsp_get_pathref_fd(const struct files_struct *fsp);
+void fsp_set_fd(struct files_struct *fsp, int fd);
+
+#endif
diff --git a/source3/smbd/file_access.c b/source3/smbd/file_access.c
new file mode 100644
index 0000000..9928eb9
--- /dev/null
+++ b/source3/smbd/file_access.c
@@ -0,0 +1,247 @@
+/*
+ Unix SMB/CIFS implementation.
+ Check access to files based on security descriptors.
+ Copyright (C) Jeremy Allison 2005-2006.
+ Copyright (C) Michael Adam 2007.
+
+ 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 "system/filesys.h"
+#include "../libcli/security/security.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "smbd/smbd.h"
+#include "source3/smbd/dir.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_ACLS
+
+/****************************************************************************
+ Actually emulate the in-kernel access checking for delete access. We need
+ this to successfully return ACCESS_DENIED on a file open for delete access.
+****************************************************************************/
+
+bool can_delete_file_in_directory(connection_struct *conn,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname)
+{
+ struct smb_filename *smb_fname_parent = NULL;
+ bool ret;
+ NTSTATUS status;
+
+ if (!CAN_WRITE(conn)) {
+ return False;
+ }
+
+ if (!lp_acl_check_permissions(SNUM(conn))) {
+ /* This option means don't check. */
+ return true;
+ }
+
+ if (get_current_uid(conn) == (uid_t)0) {
+ /* I'm sorry sir, I didn't know you were root... */
+ return true;
+ }
+
+ if (dirfsp != conn->cwd_fsp) {
+ smb_fname_parent = dirfsp->fsp_name;
+ } else {
+ struct smb_filename *atname = NULL;
+ /*
+ * Get a pathref on the parent.
+ */
+ status = parent_pathref(talloc_tos(),
+ conn->cwd_fsp,
+ smb_fname,
+ &smb_fname_parent,
+ &atname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+ }
+
+ SMB_ASSERT(VALID_STAT(smb_fname_parent->st));
+
+ /* fast paths first */
+
+ if (!S_ISDIR(smb_fname_parent->st.st_ex_mode)) {
+ ret = false;
+ goto out;
+ }
+
+#ifdef S_ISVTX
+ /* sticky bit means delete only by owner of file or by root or
+ * by owner of directory. */
+ if (smb_fname_parent->st.st_ex_mode & S_ISVTX) {
+ if (!VALID_STAT(smb_fname->st)) {
+ /* If the file doesn't already exist then
+ * yes we'll be able to delete it. */
+ ret = true;
+ goto out;
+ }
+
+ /*
+ * Patch from SATOH Fumiyasu <fumiyas@miraclelinux.com>
+ * for bug #3348. Don't assume owning sticky bit
+ * directory means write access allowed.
+ * Fail to delete if we're not the owner of the file,
+ * or the owner of the directory as we have no possible
+ * chance of deleting. Otherwise, go on and check the ACL.
+ */
+ if ((get_current_uid(conn) !=
+ smb_fname_parent->st.st_ex_uid) &&
+ (get_current_uid(conn) != smb_fname->st.st_ex_uid)) {
+ DEBUG(10,("can_delete_file_in_directory: not "
+ "owner of file %s or directory %s\n",
+ smb_fname_str_dbg(smb_fname),
+ smb_fname_str_dbg(smb_fname_parent)));
+ ret = false;
+ goto out;
+ }
+ }
+#endif
+
+ /* now for ACL checks */
+
+ /*
+ * There's two ways to get the permission to delete a file: First by
+ * having the DELETE bit on the file itself and second if that does
+ * not help, by the DELETE_CHILD bit on the containing directory.
+ *
+ * Here we only check the directory permissions, we will
+ * check the file DELETE permission separately.
+ */
+
+ ret = NT_STATUS_IS_OK(smbd_check_access_rights_fsp(
+ conn->cwd_fsp,
+ smb_fname_parent->fsp,
+ false,
+ FILE_DELETE_CHILD));
+ out:
+ if (smb_fname_parent != dirfsp->fsp_name) {
+ TALLOC_FREE(smb_fname_parent);
+ }
+ return ret;
+}
+
+/****************************************************************************
+ Userspace check for write access to fsp.
+****************************************************************************/
+
+bool can_write_to_fsp(struct files_struct *fsp)
+{
+ return NT_STATUS_IS_OK(smbd_check_access_rights_fsp(
+ fsp->conn->cwd_fsp,
+ fsp,
+ false,
+ FILE_WRITE_DATA));
+}
+
+/****************************************************************************
+ Check for an existing default Windows ACL on a directory fsp.
+****************************************************************************/
+
+bool directory_has_default_acl_fsp(struct files_struct *fsp)
+{
+ struct security_descriptor *secdesc = NULL;
+ unsigned int i;
+ NTSTATUS status;
+
+ status = SMB_VFS_FGET_NT_ACL(metadata_fsp(fsp),
+ SECINFO_DACL,
+ talloc_tos(),
+ &secdesc);
+
+ if (!NT_STATUS_IS_OK(status) ||
+ secdesc == NULL ||
+ secdesc->dacl == NULL)
+ {
+ TALLOC_FREE(secdesc);
+ return false;
+ }
+
+ for (i = 0; i < secdesc->dacl->num_aces; i++) {
+ struct security_ace *psa = &secdesc->dacl->aces[i];
+
+ if (psa->flags & (SEC_ACE_FLAG_OBJECT_INHERIT|
+ SEC_ACE_FLAG_CONTAINER_INHERIT))
+ {
+ TALLOC_FREE(secdesc);
+ return true;
+ }
+ }
+ TALLOC_FREE(secdesc);
+ return false;
+}
+
+/****************************************************************************
+ Check if setting delete on close is allowed on this fsp.
+****************************************************************************/
+
+NTSTATUS can_set_delete_on_close(files_struct *fsp, uint32_t dosmode)
+{
+ NTSTATUS status;
+ /*
+ * Only allow delete on close for writable files.
+ */
+
+ if ((dosmode & FILE_ATTRIBUTE_READONLY) &&
+ !lp_delete_readonly(SNUM(fsp->conn))) {
+ DEBUG(10,("can_set_delete_on_close: file %s delete on close "
+ "flag set but file attribute is readonly.\n",
+ fsp_str_dbg(fsp)));
+ return NT_STATUS_CANNOT_DELETE;
+ }
+
+ /*
+ * Only allow delete on close for writable shares.
+ */
+
+ if (!CAN_WRITE(fsp->conn)) {
+ DEBUG(10,("can_set_delete_on_close: file %s delete on "
+ "close flag set but write access denied on share.\n",
+ fsp_str_dbg(fsp)));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /*
+ * Only allow delete on close for files/directories opened with delete
+ * intent.
+ */
+
+ status = check_any_access_fsp(fsp, DELETE_ACCESS);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("file %s delete on "
+ "close flag set but delete access denied.\n",
+ fsp_str_dbg(fsp));
+ return status;
+ }
+
+ /* Don't allow delete on close for non-empty directories. */
+ if (fsp->fsp_flags.is_directory) {
+ SMB_ASSERT(!fsp_is_alternate_stream(fsp));
+
+ /* Or the root of a share. */
+ if (ISDOT(fsp->fsp_name->base_name)) {
+ DEBUG(10,("can_set_delete_on_close: can't set delete on "
+ "close for the root of a share.\n"));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ return can_delete_directory_fsp(fsp);
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/fileio.c b/source3/smbd/fileio.c
new file mode 100644
index 0000000..ed62159
--- /dev/null
+++ b/source3/smbd/fileio.c
@@ -0,0 +1,318 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 1.9.
+ read/write to a files_struct
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 2000-2002. - write cache.
+
+ 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 "printing.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "smbprofile.h"
+
+/****************************************************************************
+ Read from a file.
+****************************************************************************/
+
+ssize_t read_file(files_struct *fsp,char *data,off_t pos,size_t n)
+{
+ off_t new_pos;
+ ssize_t ret = 0;
+ bool ok;
+
+ /* you can't read from print files */
+ if (fsp->print_file) {
+ errno = EBADF;
+ return -1;
+ }
+
+ ok = vfs_valid_pread_range(pos, n);
+ if (!ok) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ fh_set_pos(fsp->fh, pos);
+
+ if (n > 0) {
+ ret = SMB_VFS_PREAD(fsp,data,n,pos);
+
+ if (ret == -1) {
+ return -1;
+ }
+ }
+
+ DEBUG(10,("read_file (%s): pos = %.0f, size = %lu, returned %lu\n",
+ fsp_str_dbg(fsp), (double)pos, (unsigned long)n, (long)ret));
+
+ new_pos = fh_get_pos(fsp->fh) + ret;
+ fh_set_pos(fsp->fh, new_pos);
+ fh_set_position_information(fsp->fh, new_pos);
+
+ return(ret);
+}
+
+/****************************************************************************
+ *Really* write to a file.
+****************************************************************************/
+
+static ssize_t real_write_file(struct smb_request *req,
+ files_struct *fsp,
+ const char *data,
+ off_t pos,
+ size_t n)
+{
+ ssize_t ret;
+ bool ok;
+
+ ok = vfs_valid_pwrite_range(pos, n);
+ if (!ok) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (n == 0) {
+ return 0;
+ }
+
+ fh_set_pos(fsp->fh, pos);
+ if (pos &&
+ lp_strict_allocate(SNUM(fsp->conn)) &&
+ !fsp->fsp_flags.is_sparse)
+ {
+ if (vfs_fill_sparse(fsp, pos) == -1) {
+ return -1;
+ }
+ }
+ ret = vfs_pwrite_data(req, fsp, data, n, pos);
+
+ DEBUG(10,("real_write_file (%s): pos = %.0f, size = %lu, returned %ld\n",
+ fsp_str_dbg(fsp), (double)pos, (unsigned long)n, (long)ret));
+
+ if (ret != -1) {
+ off_t new_pos = fh_get_pos(fsp->fh) + ret;
+ fh_set_pos(fsp->fh, new_pos);
+
+/* Yes - this is correct - writes don't update this. JRA. */
+/* Found by Samba4 tests. */
+#if 0
+ fsp->position_information = fsp->pos;
+#endif
+ }
+
+ return ret;
+}
+
+void fsp_flush_write_time_update(struct files_struct *fsp)
+{
+ /*
+ * Note this won't expect any impersonation!
+ * So don't call any SMB_VFS operations here!
+ */
+
+ DEBUG(5, ("Update write time on %s\n", fsp_str_dbg(fsp)));
+
+ trigger_write_time_update_immediate(fsp);
+}
+
+static void update_write_time_handler(struct tevent_context *ctx,
+ struct tevent_timer *te,
+ struct timeval now,
+ void *private_data)
+{
+ files_struct *fsp = (files_struct *)private_data;
+ fsp_flush_write_time_update(fsp);
+}
+
+/*********************************************************
+ Schedule a write time update for WRITE_TIME_UPDATE_USEC_DELAY
+ in the future.
+*********************************************************/
+
+void trigger_write_time_update(struct files_struct *fsp)
+{
+ int delay;
+
+ if (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) {
+ /* Don't use delayed writes on POSIX files. */
+ return;
+ }
+
+ if (fsp->fsp_flags.write_time_forced) {
+ /* No point - "sticky" write times
+ * in effect.
+ */
+ return;
+ }
+
+ /* We need to remember someone did a write
+ * and update to current time on close. */
+
+ fsp->fsp_flags.update_write_time_on_close = true;
+
+ if (fsp->fsp_flags.update_write_time_triggered) {
+ /*
+ * We only update the write time after 2 seconds
+ * on the first normal write. After that
+ * no other writes affect this until close.
+ */
+ return;
+ }
+ fsp->fsp_flags.update_write_time_triggered = true;
+
+ delay = lp_parm_int(SNUM(fsp->conn),
+ "smbd", "writetimeupdatedelay",
+ WRITE_TIME_UPDATE_USEC_DELAY);
+
+ DEBUG(5, ("Update write time %d usec later on %s\n",
+ delay, fsp_str_dbg(fsp)));
+
+ /* trigger the update 2 seconds later */
+ fsp->update_write_time_event =
+ tevent_add_timer(fsp->conn->sconn->ev_ctx, NULL,
+ timeval_current_ofs_usec(delay),
+ update_write_time_handler, fsp);
+}
+
+void trigger_write_time_update_immediate(struct files_struct *fsp)
+{
+ struct smb_file_time ft;
+
+ init_smb_file_time(&ft);
+
+ if (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) {
+ /* Don't use delayed writes on POSIX files. */
+ return;
+ }
+
+ if (fsp->fsp_flags.write_time_forced) {
+ /*
+ * No point - "sticky" write times
+ * in effect.
+ */
+ return;
+ }
+
+ TALLOC_FREE(fsp->update_write_time_event);
+ DEBUG(5, ("Update write time immediate on %s\n",
+ fsp_str_dbg(fsp)));
+
+ /* After an immediate update, reset the trigger. */
+ fsp->fsp_flags.update_write_time_triggered = true;
+ fsp->fsp_flags.update_write_time_on_close = false;
+
+ ft.mtime = timespec_current();
+
+ /* Update the time in the open file db. */
+ (void)set_write_time(fsp->file_id, ft.mtime);
+
+ /* Now set on disk - takes care of notify. */
+ (void)smb_set_file_time(fsp->conn, fsp, fsp->fsp_name, &ft, false);
+}
+
+void mark_file_modified(files_struct *fsp)
+{
+ int dosmode;
+
+ trigger_write_time_update(fsp);
+
+ if (fsp->fsp_flags.modified) {
+ return;
+ }
+
+ fsp->fsp_flags.modified = true;
+
+ if (!(lp_store_dos_attributes(SNUM(fsp->conn)) ||
+ MAP_ARCHIVE(fsp->conn))) {
+ return;
+ }
+
+ dosmode = fdos_mode(fsp);
+ if (dosmode & FILE_ATTRIBUTE_ARCHIVE) {
+ return;
+ }
+ file_set_dosmode(fsp->conn, fsp->fsp_name,
+ dosmode | FILE_ATTRIBUTE_ARCHIVE, NULL, false);
+}
+
+/****************************************************************************
+ Write to a file.
+****************************************************************************/
+
+ssize_t write_file(struct smb_request *req,
+ files_struct *fsp,
+ const char *data,
+ off_t pos,
+ size_t n)
+{
+ ssize_t total_written = 0;
+
+ if (fsp->print_file) {
+ uint32_t t;
+ int ret;
+
+ ret = print_spool_write(fsp, data, n, pos, &t);
+ if (ret) {
+ errno = ret;
+ return -1;
+ }
+ return t;
+ }
+
+ if (!fsp->fsp_flags.can_write) {
+ errno = EPERM;
+ return -1;
+ }
+
+ mark_file_modified(fsp);
+
+ /*
+ * If this file is level II oplocked then we need
+ * to grab the shared memory lock and inform all
+ * other files with a level II lock that they need
+ * to flush their read caches. We keep the lock over
+ * the shared memory area whilst doing this.
+ */
+
+ /* This should actually be improved to span the write. */
+ contend_level2_oplocks_begin(fsp, LEVEL2_CONTEND_WRITE);
+ contend_level2_oplocks_end(fsp, LEVEL2_CONTEND_WRITE);
+
+ total_written = real_write_file(req, fsp, data, pos, n);
+ return total_written;
+}
+
+/*******************************************************************
+sync a file
+********************************************************************/
+
+NTSTATUS sync_file(connection_struct *conn, files_struct *fsp, bool write_through)
+{
+ if (fsp_get_io_fd(fsp) == -1)
+ return NT_STATUS_INVALID_HANDLE;
+
+ if (lp_strict_sync(SNUM(conn)) &&
+ (lp_sync_always(SNUM(conn)) || write_through)) {
+ int ret;
+ ret = smb_vfs_fsync_sync(fsp);
+ if (ret == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+ }
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/filename.c b/source3/smbd/filename.c
new file mode 100644
index 0000000..f80f67c
--- /dev/null
+++ b/source3/smbd/filename.c
@@ -0,0 +1,1271 @@
+/*
+ Unix SMB/CIFS implementation.
+ filename handling routines
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 1999-2007
+ Copyright (C) Ying Chen 2000
+ Copyright (C) Volker Lendecke 2007
+
+ 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/>.
+*/
+
+/*
+ * New hash table stat cache code added by Ying Chen.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "fake_file.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "libcli/smb/reparse.h"
+#include "source3/smbd/dir.h"
+
+uint32_t ucf_flags_from_smb_request(struct smb_request *req)
+{
+ uint32_t ucf_flags = 0;
+
+ if (req == NULL) {
+ return 0;
+ }
+
+ if (req->posix_pathnames) {
+ ucf_flags |= UCF_POSIX_PATHNAMES;
+
+ if (!req->sconn->using_smb2) {
+ ucf_flags |= UCF_LCOMP_LNK_OK;
+ }
+ }
+ if (req->flags2 & FLAGS2_DFS_PATHNAMES) {
+ ucf_flags |= UCF_DFS_PATHNAME;
+ }
+ if (req->flags2 & FLAGS2_REPARSE_PATH) {
+ ucf_flags |= UCF_GMT_PATHNAME;
+ }
+
+ return ucf_flags;
+}
+
+uint32_t filename_create_ucf_flags(struct smb_request *req, uint32_t create_disposition)
+{
+ uint32_t ucf_flags = 0;
+
+ ucf_flags |= ucf_flags_from_smb_request(req);
+
+ switch (create_disposition) {
+ case FILE_OPEN:
+ case FILE_OVERWRITE:
+ break;
+ case FILE_SUPERSEDE:
+ case FILE_CREATE:
+ case FILE_OPEN_IF:
+ case FILE_OVERWRITE_IF:
+ ucf_flags |= UCF_PREP_CREATEFILE;
+ break;
+ }
+
+ return ucf_flags;
+}
+
+/****************************************************************************
+ Mangle the 2nd name and check if it is then equal to the first name.
+****************************************************************************/
+
+static bool mangled_equal(const char *name1,
+ const char *name2,
+ const struct share_params *p)
+{
+ char mname[13];
+
+ if (!name_to_8_3(name2, mname, False, p)) {
+ return False;
+ }
+ return strequal(name1, mname);
+}
+
+/*
+ * Strip a valid @GMT-token from any incoming filename path,
+ * adding any NTTIME encoded in the pathname into the
+ * twrp field of the passed in smb_fname.
+ *
+ * Valid @GMT-tokens look like @GMT-YYYY-MM-DD-HH-MM-SS
+ * at the *start* of a pathname component.
+ *
+ * If twrp is passed in then smb_fname->twrp is set to that
+ * value, and the @GMT-token part of the filename is removed
+ * and does not change the stored smb_fname->twrp.
+ *
+ */
+
+NTSTATUS canonicalize_snapshot_path(struct smb_filename *smb_fname,
+ uint32_t ucf_flags,
+ NTTIME twrp)
+{
+ bool found;
+
+ if (twrp != 0) {
+ smb_fname->twrp = twrp;
+ }
+
+ if (!(ucf_flags & UCF_GMT_PATHNAME)) {
+ return NT_STATUS_OK;
+ }
+
+ found = extract_snapshot_token(smb_fname->base_name, &twrp);
+ if (!found) {
+ return NT_STATUS_OK;
+ }
+
+ if (smb_fname->twrp == 0) {
+ smb_fname->twrp = twrp;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static bool strnorm(char *s, int case_default)
+{
+ if (case_default == CASE_UPPER)
+ return strupper_m(s);
+ else
+ return strlower_m(s);
+}
+
+/*
+ * Utility function to normalize case on an incoming client filename
+ * if required on this connection struct.
+ * Performs an in-place case conversion guaranteed to stay the same size.
+ */
+
+static NTSTATUS normalize_filename_case(connection_struct *conn,
+ char *filename,
+ uint32_t ucf_flags)
+{
+ bool ok;
+
+ if (ucf_flags & UCF_POSIX_PATHNAMES) {
+ /*
+ * POSIX never normalizes filename case.
+ */
+ return NT_STATUS_OK;
+ }
+ if (!conn->case_sensitive) {
+ return NT_STATUS_OK;
+ }
+ if (conn->case_preserve) {
+ return NT_STATUS_OK;
+ }
+ if (conn->short_case_preserve) {
+ return NT_STATUS_OK;
+ }
+ ok = strnorm(filename, lp_default_case(SNUM(conn)));
+ if (!ok) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Check if two filenames are equal.
+ This needs to be careful about whether we are case sensitive.
+****************************************************************************/
+
+static bool fname_equal(const char *name1, const char *name2,
+ bool case_sensitive)
+{
+ /* Normal filename handling */
+ if (case_sensitive) {
+ return(strcmp(name1,name2) == 0);
+ }
+
+ return(strequal(name1,name2));
+}
+
+static bool sname_equal(const char *name1, const char *name2,
+ bool case_sensitive)
+{
+ bool match;
+ const char *s1 = NULL;
+ const char *s2 = NULL;
+ size_t n1;
+ size_t n2;
+ const char *e1 = NULL;
+ const char *e2 = NULL;
+ char *c1 = NULL;
+ char *c2 = NULL;
+
+ match = fname_equal(name1, name2, case_sensitive);
+ if (match) {
+ return true;
+ }
+
+ if (name1[0] != ':') {
+ return false;
+ }
+ if (name2[0] != ':') {
+ return false;
+ }
+ s1 = &name1[1];
+ e1 = strchr(s1, ':');
+ if (e1 == NULL) {
+ n1 = strlen(s1);
+ } else {
+ n1 = PTR_DIFF(e1, s1);
+ }
+ s2 = &name2[1];
+ e2 = strchr(s2, ':');
+ if (e2 == NULL) {
+ n2 = strlen(s2);
+ } else {
+ n2 = PTR_DIFF(e2, s2);
+ }
+
+ /* Normal filename handling */
+ if (case_sensitive) {
+ return (strncmp(s1, s2, n1) == 0);
+ }
+
+ /*
+ * We can't use strnequal() here
+ * as it takes the number of codepoints
+ * and not the number of bytes.
+ *
+ * So we make a copy before calling
+ * strequal().
+ *
+ * Note that we TALLOC_FREE() in reverse order
+ * in order to avoid memory fragmentation.
+ */
+
+ c1 = talloc_strndup(talloc_tos(), s1, n1);
+ c2 = talloc_strndup(talloc_tos(), s2, n2);
+ if (c1 == NULL || c2 == NULL) {
+ TALLOC_FREE(c2);
+ TALLOC_FREE(c1);
+ return (strncmp(s1, s2, n1) == 0);
+ }
+
+ match = strequal(c1, c2);
+ TALLOC_FREE(c2);
+ TALLOC_FREE(c1);
+ return match;
+}
+
+/****************************************************************************
+ Scan a directory to find a filename, matching without case sensitivity.
+ If the name looks like a mangled name then try via the mangling functions
+****************************************************************************/
+
+NTSTATUS get_real_filename_full_scan_at(struct files_struct *dirfsp,
+ const char *name,
+ bool mangled,
+ TALLOC_CTX *mem_ctx,
+ char **found_name)
+{
+ struct connection_struct *conn = dirfsp->conn;
+ struct smb_Dir *cur_dir = NULL;
+ const char *dname = NULL;
+ char *talloced = NULL;
+ char *unmangled_name = NULL;
+ NTSTATUS status;
+
+ /* If we have a case-sensitive filesystem, it doesn't do us any
+ * good to search for a name. If a case variation of the name was
+ * there, then the original stat(2) would have found it.
+ */
+ if (!mangled && !(conn->fs_capabilities & FILE_CASE_SENSITIVE_SEARCH)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ /*
+ * The incoming name can be mangled, and if we de-mangle it
+ * here it will not compare correctly against the filename (name2)
+ * read from the directory and then mangled by the name_to_8_3()
+ * call. We need to mangle both names or neither.
+ * (JRA).
+ *
+ * Fix for bug found by Dina Fine. If in case sensitive mode then
+ * the mangle cache is no good (3 letter extension could be wrong
+ * case - so don't demangle in this case - leave as mangled and
+ * allow the mangling of the directory entry read (which is done
+ * case insensitively) to match instead. This will lead to more
+ * false positive matches but we fail completely without it. JRA.
+ */
+
+ if (mangled && !conn->case_sensitive) {
+ mangled = !mangle_lookup_name_from_8_3(talloc_tos(), name,
+ &unmangled_name,
+ conn->params);
+ if (!mangled) {
+ /* Name is now unmangled. */
+ name = unmangled_name;
+ }
+ }
+
+ /* open the directory */
+ status = OpenDir_from_pathref(talloc_tos(), dirfsp, NULL, 0, &cur_dir);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_NOTICE("scan dir didn't open dir [%s]: %s\n",
+ fsp_str_dbg(dirfsp),
+ nt_errstr(status));
+ TALLOC_FREE(unmangled_name);
+ return status;
+ }
+
+ /* now scan for matching names */
+ while ((dname = ReadDirName(cur_dir, &talloced))) {
+
+ /* Is it dot or dot dot. */
+ if (ISDOT(dname) || ISDOTDOT(dname)) {
+ TALLOC_FREE(talloced);
+ continue;
+ }
+
+ /*
+ * At this point dname is the unmangled name.
+ * name is either mangled or not, depending on the state
+ * of the "mangled" variable. JRA.
+ */
+
+ /*
+ * Check mangled name against mangled name, or unmangled name
+ * against unmangled name.
+ */
+
+ if ((mangled && mangled_equal(name,dname,conn->params)) ||
+ fname_equal(name, dname, conn->case_sensitive)) {
+ /* we've found the file, change it's name and return */
+ *found_name = talloc_strdup(mem_ctx, dname);
+ TALLOC_FREE(unmangled_name);
+ TALLOC_FREE(cur_dir);
+ if (!*found_name) {
+ TALLOC_FREE(talloced);
+ return NT_STATUS_NO_MEMORY;
+ }
+ TALLOC_FREE(talloced);
+ return NT_STATUS_OK;
+ }
+ TALLOC_FREE(talloced);
+ }
+
+ TALLOC_FREE(unmangled_name);
+ TALLOC_FREE(cur_dir);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+}
+
+/****************************************************************************
+ Wrapper around the vfs get_real_filename and the full directory scan
+ fallback.
+****************************************************************************/
+
+NTSTATUS get_real_filename_at(struct files_struct *dirfsp,
+ const char *name,
+ TALLOC_CTX *mem_ctx,
+ char **found_name)
+{
+ struct connection_struct *conn = dirfsp->conn;
+ NTSTATUS status;
+ bool mangled;
+
+ mangled = mangle_is_mangled(name, conn->params);
+
+ if (mangled) {
+ status = get_real_filename_full_scan_at(
+ dirfsp, name, mangled, mem_ctx, found_name);
+ return status;
+ }
+
+ /* Try the vfs first to take advantage of case-insensitive stat. */
+ status = SMB_VFS_GET_REAL_FILENAME_AT(
+ dirfsp->conn, dirfsp, name, mem_ctx, found_name);
+
+ /*
+ * If the case-insensitive stat was successful, or returned an error
+ * other than EOPNOTSUPP then there is no need to fall back on the
+ * full directory scan.
+ */
+ if (NT_STATUS_IS_OK(status) ||
+ !NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
+ return status;
+ }
+
+ status = get_real_filename_full_scan_at(
+ dirfsp, name, mangled, mem_ctx, found_name);
+ return status;
+}
+
+/*
+ * Lightweight function to just get last component
+ * for rename / enumerate directory calls.
+ */
+
+char *get_original_lcomp(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ const char *filename_in,
+ uint32_t ucf_flags)
+{
+ char *last_slash = NULL;
+ char *orig_lcomp;
+ NTSTATUS status;
+
+ last_slash = strrchr(filename_in, '/');
+ if (last_slash != NULL) {
+ orig_lcomp = talloc_strdup(ctx, last_slash+1);
+ } else {
+ orig_lcomp = talloc_strdup(ctx, filename_in);
+ }
+ if (orig_lcomp == NULL) {
+ return NULL;
+ }
+ status = normalize_filename_case(conn, orig_lcomp, ucf_flags);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(orig_lcomp);
+ return NULL;
+ }
+ return orig_lcomp;
+}
+
+/*
+ * Get the correct capitalized stream name hanging off
+ * base_fsp. Equivalent of get_real_filename(), but for streams.
+ */
+static NTSTATUS get_real_stream_name(
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *base_fsp,
+ const char *stream_name,
+ char **_found)
+{
+ unsigned int i, num_streams = 0;
+ struct stream_struct *streams = NULL;
+ NTSTATUS status;
+
+ status = vfs_fstreaminfo(
+ base_fsp, talloc_tos(), &num_streams, &streams);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ for (i=0; i<num_streams; i++) {
+ bool equal = sname_equal(stream_name, streams[i].name, false);
+
+ DBG_DEBUG("comparing [%s] and [%s]: %sequal\n",
+ stream_name,
+ streams[i].name,
+ equal ? "" : "not ");
+
+ if (equal) {
+ *_found = talloc_move(mem_ctx, &streams[i].name);
+ TALLOC_FREE(streams);
+ return NT_STATUS_OK;
+ }
+ }
+
+ TALLOC_FREE(streams);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+}
+
+static bool filename_split_lcomp(
+ TALLOC_CTX *mem_ctx,
+ const char *name_in,
+ bool posix,
+ char **_dirname,
+ const char **_fname_rel,
+ const char **_streamname)
+{
+ const char *lcomp = NULL;
+ const char *fname_rel = NULL;
+ const char *streamname = NULL;
+ char *dirname = NULL;
+
+ if (name_in[0] == '\0') {
+ fname_rel = ".";
+ dirname = talloc_strdup(mem_ctx, "");
+ if (dirname == NULL) {
+ return false;
+ }
+ goto done;
+ }
+
+ lcomp = strrchr_m(name_in, '/');
+ if (lcomp != NULL) {
+ fname_rel = lcomp+1;
+ dirname = talloc_strndup(mem_ctx, name_in, lcomp - name_in);
+ if (dirname == NULL) {
+ return false;
+ }
+ goto find_stream;
+ }
+
+ /*
+ * No slash, dir is empty
+ */
+ dirname = talloc_strdup(mem_ctx, "");
+ if (dirname == NULL) {
+ return false;
+ }
+
+ if (!posix && (name_in[0] == ':')) {
+ /*
+ * Special case for stream on root directory
+ */
+ fname_rel = ".";
+ streamname = name_in;
+ goto done;
+ }
+
+ fname_rel = name_in;
+
+find_stream:
+ if (!posix) {
+ streamname = strchr_m(fname_rel, ':');
+
+ if (streamname != NULL) {
+ fname_rel = talloc_strndup(
+ mem_ctx,
+ fname_rel,
+ streamname - fname_rel);
+ if (fname_rel == NULL) {
+ TALLOC_FREE(dirname);
+ return false;
+ }
+ }
+ }
+
+done:
+ *_dirname = dirname;
+ *_fname_rel = fname_rel;
+ *_streamname = streamname;
+ return true;
+}
+
+/*
+ * Create the correct capitalization of a file name to be created.
+ */
+static NTSTATUS filename_convert_normalize_new(
+ TALLOC_CTX *mem_ctx,
+ struct connection_struct *conn,
+ char *name_in,
+ char **_normalized)
+{
+ char *name = name_in;
+
+ *_normalized = NULL;
+
+ if (!conn->case_preserve ||
+ (mangle_is_8_3(name, false,
+ conn->params) &&
+ !conn->short_case_preserve)) {
+
+ char *normalized = talloc_strdup(mem_ctx, name);
+ if (normalized == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ strnorm(normalized, lp_default_case(SNUM(conn)));
+ name = normalized;
+ }
+
+ if (mangle_is_mangled(name, conn->params)) {
+ bool found;
+ char *unmangled = NULL;
+
+ found = mangle_lookup_name_from_8_3(
+ mem_ctx, name, &unmangled, conn->params);
+ if (found) {
+ name = unmangled;
+ }
+ }
+
+ if (name != name_in) {
+ *_normalized = name;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static const char *previous_slash(const char *name_in, const char *slash)
+{
+ const char *prev = NULL;
+
+ SMB_ASSERT((name_in <= slash) && (slash[0] == '/'));
+
+ prev = strchr_m(name_in, '/');
+
+ if (prev == slash) {
+ /* No previous slash */
+ return NULL;
+ }
+
+ while (true) {
+ const char *next = strchr_m(prev + 1, '/');
+
+ if (next == slash) {
+ return prev;
+ }
+ prev = next;
+ }
+
+ return NULL; /* unreachable */
+}
+
+static char *symlink_target_path(
+ TALLOC_CTX *mem_ctx,
+ const char *name_in,
+ const char *substitute,
+ size_t unparsed)
+{
+ size_t name_in_len = strlen(name_in);
+ const char *p_unparsed = NULL;
+ const char *parent = NULL;
+ char *ret;
+
+ SMB_ASSERT(unparsed <= name_in_len);
+
+ p_unparsed = name_in + (name_in_len - unparsed);
+
+ if (substitute[0] == '/') {
+ ret = talloc_asprintf(mem_ctx, "%s%s", substitute, p_unparsed);
+ return ret;
+ }
+
+ if (unparsed == 0) {
+ parent = strrchr_m(name_in, '/');
+ } else {
+ parent = previous_slash(name_in, p_unparsed);
+ }
+
+ if (parent == NULL) {
+ ret = talloc_asprintf(mem_ctx, "%s%s", substitute, p_unparsed);
+ } else {
+ ret = talloc_asprintf(mem_ctx,
+ "%.*s/%s%s",
+ (int)(parent - name_in),
+ name_in,
+ substitute,
+ p_unparsed);
+ }
+
+ return ret;
+}
+
+NTSTATUS safe_symlink_target_path(TALLOC_CTX *mem_ctx,
+ const char *connectpath,
+ const char *dir,
+ const char *target,
+ size_t unparsed,
+ char **_relative)
+{
+ char *abs_target = NULL;
+ char *abs_target_canon = NULL;
+ const char *relative = NULL;
+ bool in_share;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ DBG_DEBUG("connectpath [%s] target [%s] unparsed [%zu]\n",
+ connectpath, target, unparsed);
+
+ if (target[0] == '/') {
+ abs_target = talloc_strdup(mem_ctx, target);
+ } else if (dir == NULL) {
+ abs_target = talloc_asprintf(mem_ctx,
+ "%s/%s",
+ connectpath,
+ target);
+ } else if (dir[0] == '/') {
+ abs_target = talloc_asprintf(mem_ctx,
+ "%s/%s",
+ dir,
+ target);
+ } else {
+ abs_target = talloc_asprintf(mem_ctx,
+ "%s/%s/%s",
+ connectpath,
+ dir,
+ target);
+ }
+ if (abs_target == NULL) {
+ goto fail;
+ }
+
+ abs_target_canon = canonicalize_absolute_path(abs_target, abs_target);
+ if (abs_target_canon == NULL) {
+ goto fail;
+ }
+
+ DBG_DEBUG("abs_target_canon=%s\n", abs_target_canon);
+
+ in_share = subdir_of(
+ connectpath, strlen(connectpath), abs_target_canon, &relative);
+ if (!in_share) {
+ DBG_DEBUG("wide link to %s\n", abs_target_canon);
+ status = (unparsed != 0) ? NT_STATUS_OBJECT_PATH_NOT_FOUND
+ : NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ goto fail;
+ }
+
+ *_relative = talloc_strdup(mem_ctx, relative);
+ if (*_relative == NULL) {
+ goto fail;
+ }
+
+ status = NT_STATUS_OK;
+fail:
+ TALLOC_FREE(abs_target);
+ return status;
+}
+
+/*
+ * Split up name_in as sent by the client into a directory pathref fsp
+ * and a relative smb_filename.
+ */
+static NTSTATUS filename_convert_dirfsp_nosymlink(
+ TALLOC_CTX *mem_ctx,
+ connection_struct *conn,
+ const char *name_in,
+ uint32_t ucf_flags,
+ NTTIME twrp,
+ struct files_struct **_dirfsp,
+ struct smb_filename **_smb_fname,
+ struct open_symlink_err **_symlink_err)
+{
+ struct smb_filename *smb_dirname = NULL;
+ struct smb_filename *smb_fname_rel = NULL;
+ struct smb_filename *smb_fname = NULL;
+ struct open_symlink_err *symlink_err = NULL;
+ const bool posix = (ucf_flags & UCF_POSIX_PATHNAMES);
+ char *dirname = NULL;
+ const char *fname_rel = NULL;
+ const char *streamname = NULL;
+ char *saved_streamname = NULL;
+ struct files_struct *base_fsp = NULL;
+ bool ok;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+
+ SMB_ASSERT(!(ucf_flags & UCF_DFS_PATHNAME));
+
+ if (is_fake_file_path(name_in)) {
+ smb_fname = synthetic_smb_fname_split(mem_ctx, name_in, posix);
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ smb_fname->st = (SMB_STRUCT_STAT){
+ .st_ex_nlink = 1,
+ .st_ex_mode = S_IFREG | 0644,
+ };
+ smb_fname->st.st_ex_btime =
+ (struct timespec){0, SAMBA_UTIME_OMIT};
+ smb_fname->st.st_ex_atime =
+ (struct timespec){0, SAMBA_UTIME_OMIT};
+ smb_fname->st.st_ex_mtime =
+ (struct timespec){0, SAMBA_UTIME_OMIT};
+ smb_fname->st.st_ex_ctime =
+ (struct timespec){0, SAMBA_UTIME_OMIT};
+
+ *_dirfsp = conn->cwd_fsp;
+ *_smb_fname = smb_fname;
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * Catch an invalid path of "." before we
+ * call filename_split_lcomp(). We need to
+ * do this as filename_split_lcomp() will
+ * use "." for the missing relative component
+ * when an empty name_in path is sent by
+ * the client.
+ */
+ if (ISDOT(name_in)) {
+ status = NT_STATUS_OBJECT_NAME_INVALID;
+ goto fail;
+ }
+
+ ok = filename_split_lcomp(
+ talloc_tos(),
+ name_in,
+ posix,
+ &dirname,
+ &fname_rel,
+ &streamname);
+ if (!ok) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if ((streamname != NULL) &&
+ ((conn->fs_capabilities & FILE_NAMED_STREAMS) == 0)) {
+ status = NT_STATUS_OBJECT_NAME_INVALID;
+ goto fail;
+ }
+
+ if (!posix) {
+ bool name_has_wild = ms_has_wild(dirname);
+ name_has_wild |= ms_has_wild(fname_rel);
+ if (name_has_wild) {
+ status = NT_STATUS_OBJECT_NAME_INVALID;
+ goto fail;
+ }
+ }
+
+ if (dirname[0] == '\0') {
+ status = synthetic_pathref(
+ mem_ctx,
+ conn->cwd_fsp,
+ ".",
+ NULL,
+ NULL,
+ 0,
+ posix ? SMB_FILENAME_POSIX_PATH : 0,
+ &smb_dirname);
+ } else {
+ status = normalize_filename_case(conn, dirname, ucf_flags);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("normalize_filename_case %s failed: %s\n",
+ dirname,
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = openat_pathref_fsp_nosymlink(mem_ctx,
+ conn,
+ conn->cwd_fsp,
+ dirname,
+ twrp,
+ posix,
+ &smb_dirname,
+ &symlink_err);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) {
+ size_t name_in_len, dirname_len;
+
+ name_in_len = strlen(name_in);
+ dirname_len = strlen(dirname);
+
+ SMB_ASSERT(name_in_len >= dirname_len);
+
+ symlink_err->unparsed += (name_in_len - dirname_len);
+ *_symlink_err = symlink_err;
+
+ goto fail;
+ }
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("opening directory %s failed: %s\n",
+ dirname,
+ nt_errstr(status));
+ TALLOC_FREE(dirname);
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ /*
+ * Except ACCESS_DENIED, everything else leads
+ * to PATH_NOT_FOUND.
+ */
+ status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ }
+
+ goto fail;
+ }
+
+ if (!VALID_STAT_OF_DIR(smb_dirname->st)) {
+ status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ goto fail;
+ }
+ smb_dirname->fsp->fsp_flags.is_directory = true;
+
+ /*
+ * Only look at bad last component values
+ * once we know we have a valid directory. That
+ * way we won't confuse error messages from
+ * opening the directory path with error
+ * messages from a bad last component.
+ */
+
+ /* Relative filename can't be empty */
+ if (fname_rel[0] == '\0') {
+ status = NT_STATUS_OBJECT_NAME_INVALID;
+ goto fail;
+ }
+
+ /* Relative filename can't be ".." */
+ if (ISDOTDOT(fname_rel)) {
+ status = NT_STATUS_OBJECT_NAME_INVALID;
+ goto fail;
+ }
+ /* Relative name can only be dot if directory is empty. */
+ if (ISDOT(fname_rel) && dirname[0] != '\0') {
+ status = NT_STATUS_OBJECT_NAME_INVALID;
+ goto fail;
+ }
+
+ TALLOC_FREE(dirname);
+
+ smb_fname_rel = synthetic_smb_fname(
+ mem_ctx,
+ fname_rel,
+ streamname,
+ NULL,
+ twrp,
+ posix ? SMB_FILENAME_POSIX_PATH : 0);
+ if (smb_fname_rel == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if ((conn->fs_capabilities & FILE_NAMED_STREAMS) &&
+ is_named_stream(smb_fname_rel)) {
+ /*
+ * Find the base_fsp first without the stream.
+ */
+ saved_streamname = smb_fname_rel->stream_name;
+ smb_fname_rel->stream_name = NULL;
+ }
+
+ status = normalize_filename_case(
+ conn, smb_fname_rel->base_name, ucf_flags);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("normalize_filename_case %s failed: %s\n",
+ smb_fname_rel->base_name,
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = openat_pathref_fsp_lcomp(smb_dirname->fsp,
+ smb_fname_rel,
+ ucf_flags);
+
+ if (NT_STATUS_IS_OK(status) && S_ISLNK(smb_fname_rel->st.st_ex_mode)) {
+
+ /*
+ * Upper layers might need the link target. Here we
+ * still have the relname around, get the symlink err.
+ */
+ status = create_open_symlink_err(mem_ctx,
+ smb_dirname->fsp,
+ smb_fname_rel,
+ &symlink_err);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Could not read symlink for %s: %s\n",
+ smb_fname_str_dbg(
+ smb_fname_rel->fsp->fsp_name),
+ nt_errstr(status));
+ goto fail;
+ }
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND) &&
+ !VALID_STAT(smb_fname_rel->st)) {
+
+ char *normalized = NULL;
+
+ /*
+ * Creating a new file
+ */
+
+ status = filename_convert_normalize_new(
+ smb_fname_rel,
+ conn,
+ smb_fname_rel->base_name,
+ &normalized);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("filename_convert_normalize_new failed: "
+ "%s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ if (normalized != NULL) {
+ smb_fname_rel->base_name = normalized;
+ }
+
+ smb_fname_rel->stream_name = saved_streamname;
+
+ smb_fname = full_path_from_dirfsp_atname(
+ mem_ctx, smb_dirname->fsp, smb_fname_rel);
+ if (smb_fname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ goto done;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_OPEN_RESTRICTION)) {
+ /* A vetoed file, pretend it's not there */
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ if (saved_streamname == NULL) {
+ /* smb_fname must be allocated off mem_ctx. */
+ smb_fname = cp_smb_filename(mem_ctx,
+ smb_fname_rel->fsp->fsp_name);
+ if (smb_fname == NULL) {
+ goto fail;
+ }
+ status = move_smb_fname_fsp_link(smb_fname, smb_fname_rel);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ goto done;
+ }
+
+ base_fsp = smb_fname_rel->fsp;
+ smb_fname_fsp_unlink(smb_fname_rel);
+ SET_STAT_INVALID(smb_fname_rel->st);
+
+ smb_fname_rel->stream_name = saved_streamname;
+
+ status = open_stream_pathref_fsp(&base_fsp, smb_fname_rel);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND) &&
+ !conn->case_sensitive) {
+ char *found = NULL;
+
+ status = get_real_stream_name(
+ smb_fname_rel,
+ base_fsp,
+ smb_fname_rel->stream_name,
+ &found);
+
+ if (NT_STATUS_IS_OK(status)) {
+ smb_fname_rel->stream_name = found;
+ found = NULL;
+ status = open_stream_pathref_fsp(
+ &base_fsp, smb_fname_rel);
+ }
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ /* smb_fname must be allocated off mem_ctx. */
+ smb_fname = cp_smb_filename(mem_ctx,
+ smb_fname_rel->fsp->fsp_name);
+ if (smb_fname == NULL) {
+ goto fail;
+ }
+ status = move_smb_fname_fsp_link(smb_fname, smb_fname_rel);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ goto done;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ /*
+ * Creating a new stream
+ *
+ * We should save the already-open base fsp for
+ * create_file_unixpath() somehow.
+ */
+ smb_fname = full_path_from_dirfsp_atname(
+ mem_ctx, smb_dirname->fsp, smb_fname_rel);
+ if (smb_fname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ /*
+ * When open_stream_pathref_fsp() returns
+ * NT_STATUS_OBJECT_NAME_NOT_FOUND, smb_fname_rel->fsp
+ * has been set to NULL, so we must free base_fsp separately
+ * to prevent fd-leaks when opening a stream that doesn't
+ * exist.
+ */
+ fd_close(base_fsp);
+ file_free(NULL, base_fsp);
+ base_fsp = NULL;
+ goto done;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+done:
+ *_dirfsp = smb_dirname->fsp;
+ *_smb_fname = smb_fname;
+ *_symlink_err = symlink_err;
+
+ smb_fname_fsp_unlink(smb_fname_rel);
+ TALLOC_FREE(smb_fname_rel);
+ return NT_STATUS_OK;
+
+fail:
+ /*
+ * If open_stream_pathref_fsp() returns an error, smb_fname_rel->fsp
+ * has been set to NULL, so we must free base_fsp separately
+ * to prevent fd-leaks when opening a stream that doesn't
+ * exist.
+ */
+ if (base_fsp != NULL) {
+ fd_close(base_fsp);
+ file_free(NULL, base_fsp);
+ base_fsp = NULL;
+ }
+ TALLOC_FREE(dirname);
+ TALLOC_FREE(smb_dirname);
+ TALLOC_FREE(smb_fname_rel);
+ return status;
+}
+
+NTSTATUS filename_convert_dirfsp(
+ TALLOC_CTX *mem_ctx,
+ connection_struct *conn,
+ const char *name_in,
+ uint32_t ucf_flags,
+ NTTIME twrp,
+ struct files_struct **_dirfsp,
+ struct smb_filename **_smb_fname)
+{
+ struct open_symlink_err *symlink_err = NULL;
+ NTSTATUS status;
+ char *target = NULL;
+ char *safe_target = NULL;
+ size_t symlink_redirects = 0;
+
+next:
+ if (symlink_redirects > 40) {
+ return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ }
+
+ status = filename_convert_dirfsp_nosymlink(mem_ctx,
+ conn,
+ name_in,
+ ucf_flags,
+ twrp,
+ _dirfsp,
+ _smb_fname,
+ &symlink_err);
+
+ if (NT_STATUS_IS_OK(status) && S_ISLNK((*_smb_fname)->st.st_ex_mode)) {
+ /*
+ * lcomp is a symlink
+ */
+ if (ucf_flags & UCF_LCOMP_LNK_OK) {
+ TALLOC_FREE(symlink_err);
+ return NT_STATUS_OK;
+ }
+ close_file_free(NULL, _dirfsp, ERROR_CLOSE);
+ status = NT_STATUS_STOPPED_ON_SYMLINK;
+ }
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) {
+ return status;
+ }
+
+ /*
+ * If we're on an MSDFS share, see if this is
+ * an MSDFS link.
+ */
+ if (lp_host_msdfs() && lp_msdfs_root(SNUM(conn)) &&
+ strnequal(symlink_err->reparse->substitute_name, "msdfs:", 6))
+ {
+ TALLOC_FREE(*_smb_fname);
+ TALLOC_FREE(symlink_err);
+ return NT_STATUS_PATH_NOT_COVERED;
+ }
+
+ if (!lp_follow_symlinks(SNUM(conn))) {
+ status = (symlink_err->unparsed == 0)
+ ? NT_STATUS_OBJECT_NAME_NOT_FOUND
+ : NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ TALLOC_FREE(symlink_err);
+ return status;
+ }
+
+ /*
+ * Right now, SMB2 and SMB1 always traverse symlinks
+ * within the share. SMB1+POSIX traverses non-terminal
+ * symlinks within the share.
+ *
+ * When we add SMB2+POSIX we need to return
+ * a NT_STATUS_STOPPED_ON_SYMLINK error here, using the
+ * symlink target data read below if SMB2+POSIX has
+ * UCF_POSIX_PATHNAMES set to cause the client to
+ * resolve all symlinks locally.
+ */
+
+ target = symlink_target_path(mem_ctx,
+ name_in,
+ symlink_err->reparse->substitute_name,
+ symlink_err->unparsed);
+ if (target == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = safe_symlink_target_path(mem_ctx,
+ conn->connectpath,
+ NULL,
+ target,
+ symlink_err->unparsed,
+ &safe_target);
+ TALLOC_FREE(symlink_err);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ name_in = safe_target;
+
+ symlink_redirects += 1;
+
+ goto next;
+}
+
+char *full_path_from_dirfsp_at_basename(TALLOC_CTX *mem_ctx,
+ const struct files_struct *dirfsp,
+ const char *at_base_name)
+{
+ char *path = NULL;
+
+ if (dirfsp == dirfsp->conn->cwd_fsp ||
+ ISDOT(dirfsp->fsp_name->base_name) || at_base_name[0] == '/') {
+ path = talloc_strdup(mem_ctx, at_base_name);
+ } else {
+ path = talloc_asprintf(mem_ctx,
+ "%s/%s",
+ dirfsp->fsp_name->base_name,
+ at_base_name);
+ }
+
+ return path;
+}
+
+/*
+ * Build the full path from a dirfsp and dirfsp relative name
+ */
+struct smb_filename *
+full_path_from_dirfsp_atname(TALLOC_CTX *mem_ctx,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *atname)
+{
+ struct smb_filename *fname = NULL;
+ char *path = NULL;
+
+ path = full_path_from_dirfsp_at_basename(mem_ctx,
+ dirfsp,
+ atname->base_name);
+ if (path == NULL) {
+ return NULL;
+ }
+
+ fname = synthetic_smb_fname(mem_ctx,
+ path,
+ atname->stream_name,
+ &atname->st,
+ atname->twrp,
+ atname->flags);
+ TALLOC_FREE(path);
+ if (fname == NULL) {
+ return NULL;
+ }
+
+ return fname;
+}
diff --git a/source3/smbd/files.c b/source3/smbd/files.c
new file mode 100644
index 0000000..6aad76a
--- /dev/null
+++ b/source3/smbd/files.c
@@ -0,0 +1,2651 @@
+/*
+ Unix SMB/CIFS implementation.
+ Files[] structure handling
+ Copyright (C) Andrew Tridgell 1998
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "smbd/smbXsrv_open.h"
+#include "libcli/security/security.h"
+#include "util_tdb.h"
+#include "lib/util/bitmap.h"
+#include "lib/util/strv.h"
+#include "lib/util/memcache.h"
+#include "libcli/smb/reparse.h"
+
+#define FILE_HANDLE_OFFSET 0x1000
+
+static NTSTATUS fsp_attach_smb_fname(struct files_struct *fsp,
+ struct smb_filename **_smb_fname);
+
+/**
+ * create new fsp to be used for file_new or a durable handle reconnect
+ */
+NTSTATUS fsp_new(struct connection_struct *conn, TALLOC_CTX *mem_ctx,
+ files_struct **result)
+{
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+ files_struct *fsp = NULL;
+ struct smbd_server_connection *sconn = conn->sconn;
+
+ fsp = talloc_zero(mem_ctx, struct files_struct);
+ if (fsp == NULL) {
+ goto fail;
+ }
+
+ /*
+ * This can't be a child of fsp because the file_handle can be ref'd
+ * when doing a dos/fcb open, which will then share the file_handle
+ * across multiple fsps.
+ */
+ fsp->fh = fd_handle_create(mem_ctx);
+ if (fsp->fh == NULL) {
+ goto fail;
+ }
+
+ fsp->fsp_flags.use_ofd_locks = !lp_smbd_force_process_locks(SNUM(conn));
+#ifndef HAVE_OFD_LOCKS
+ fsp->fsp_flags.use_ofd_locks = false;
+#endif
+
+ fh_set_refcount(fsp->fh, 1);
+ fsp_set_fd(fsp, -1);
+
+ fsp->fnum = FNUM_FIELD_INVALID;
+ fsp->conn = conn;
+ fsp->close_write_time = make_omit_timespec();
+
+ DLIST_ADD(sconn->files, fsp);
+ sconn->num_files += 1;
+
+ conn->num_files_open++;
+
+ DBG_INFO("allocated files structure (%u used)\n",
+ (unsigned int)sconn->num_files);
+
+ *result = fsp;
+ return NT_STATUS_OK;
+
+fail:
+ if (fsp != NULL) {
+ TALLOC_FREE(fsp->fh);
+ }
+ TALLOC_FREE(fsp);
+
+ return status;
+}
+
+void fsp_set_gen_id(files_struct *fsp)
+{
+ static uint64_t gen_id = 1;
+
+ /*
+ * A billion of 64-bit increments per second gives us
+ * more than 500 years of runtime without wrap.
+ */
+ gen_id++;
+ fh_set_gen_id(fsp->fh, gen_id);
+}
+
+/****************************************************************************
+ Find first available file slot.
+****************************************************************************/
+
+NTSTATUS fsp_bind_smb(struct files_struct *fsp, struct smb_request *req)
+{
+ struct smbXsrv_open *op = NULL;
+ NTTIME now;
+ NTSTATUS status;
+
+ if (req == NULL) {
+ DBG_DEBUG("INTERNAL_OPEN_ONLY, skipping smbXsrv_open\n");
+ return NT_STATUS_OK;
+ }
+
+ now = timeval_to_nttime(&fsp->open_time);
+
+ status = smbXsrv_open_create(req->xconn,
+ fsp->conn->session_info,
+ now,
+ &op);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ fsp->op = op;
+ op->compat = fsp;
+ fsp->fnum = op->local_id;
+
+ fsp->mid = req->mid;
+ req->chain_fsp = fsp;
+
+ DBG_DEBUG("fsp [%s] mid [%" PRIu64"]\n",
+ fsp_str_dbg(fsp), fsp->mid);
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS file_new(struct smb_request *req, connection_struct *conn,
+ files_struct **result)
+{
+ struct smbd_server_connection *sconn = conn->sconn;
+ files_struct *fsp;
+ NTSTATUS status;
+
+ status = fsp_new(conn, conn, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ GetTimeOfDay(&fsp->open_time);
+
+ status = fsp_bind_smb(fsp, req);
+ if (!NT_STATUS_IS_OK(status)) {
+ file_free(NULL, fsp);
+ return status;
+ }
+
+ fsp_set_gen_id(fsp);
+
+ /*
+ * Create an smb_filename with "" for the base_name. There are very
+ * few NULL checks, so make sure it's initialized with something. to
+ * be safe until an audit can be done.
+ */
+ fsp->fsp_name = synthetic_smb_fname(fsp,
+ "",
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (fsp->fsp_name == NULL) {
+ file_free(NULL, fsp);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DBG_INFO("new file %s\n", fsp_fnum_dbg(fsp));
+
+ /* A new fsp invalidates the positive and
+ negative fsp_fi_cache as the new fsp is pushed
+ at the start of the list and we search from
+ a cache hit to the *end* of the list. */
+
+ ZERO_STRUCT(sconn->fsp_fi_cache);
+
+ *result = fsp;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS create_internal_fsp(connection_struct *conn,
+ const struct smb_filename *smb_fname,
+ struct files_struct **_fsp)
+{
+ struct files_struct *fsp = NULL;
+ NTSTATUS status;
+
+ status = file_new(NULL, conn, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = fsp_set_smb_fname(fsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ file_free(NULL, fsp);
+ return status;
+ }
+
+ *_fsp = fsp;
+ return NT_STATUS_OK;
+}
+
+/*
+ * Create an internal fsp for an *existing* directory.
+ *
+ * This should only be used by callers in the VFS that need to control the
+ * opening of the directory. Otherwise use open_internal_dirfsp().
+ */
+NTSTATUS create_internal_dirfsp(connection_struct *conn,
+ const struct smb_filename *smb_dname,
+ struct files_struct **_fsp)
+{
+ struct files_struct *fsp = NULL;
+ NTSTATUS status;
+
+ status = create_internal_fsp(conn, smb_dname, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ fsp->access_mask = FILE_LIST_DIRECTORY;
+ fsp->fsp_flags.is_directory = true;
+ fsp->fsp_flags.is_dirfsp = true;
+
+ *_fsp = fsp;
+ return NT_STATUS_OK;
+}
+
+/*
+ * Open an internal fsp for an *existing* directory.
+ */
+NTSTATUS open_internal_dirfsp(connection_struct *conn,
+ const struct smb_filename *smb_dname,
+ int _open_flags,
+ struct files_struct **_fsp)
+{
+ struct vfs_open_how how = { .flags = _open_flags, };
+ struct files_struct *fsp = NULL;
+ NTSTATUS status;
+
+ status = create_internal_dirfsp(conn, smb_dname, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+#ifdef O_DIRECTORY
+ how.flags |= O_DIRECTORY;
+#endif
+ status = fd_openat(conn->cwd_fsp, fsp->fsp_name, fsp, &how);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_INFO("Could not open fd for %s (%s)\n",
+ smb_fname_str_dbg(smb_dname),
+ nt_errstr(status));
+ file_free(NULL, fsp);
+ return status;
+ }
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ file_free(NULL, fsp);
+ return status;
+ }
+
+ if (!S_ISDIR(fsp->fsp_name->st.st_ex_mode)) {
+ DBG_ERR("%s is not a directory!\n",
+ smb_fname_str_dbg(smb_dname));
+ file_free(NULL, fsp);
+ return NT_STATUS_NOT_A_DIRECTORY;
+ }
+
+ fsp->file_id = vfs_file_id_from_sbuf(conn, &fsp->fsp_name->st);
+
+ *_fsp = fsp;
+ return NT_STATUS_OK;
+}
+
+/*
+ * Convert a pathref dirfsp into a real fsp. No need to do any cwd
+ * tricks, we just open ".".
+ */
+NTSTATUS openat_internal_dir_from_pathref(
+ struct files_struct *dirfsp,
+ int _open_flags,
+ struct files_struct **_fsp)
+{
+ struct connection_struct *conn = dirfsp->conn;
+ struct smb_filename *smb_dname = dirfsp->fsp_name;
+ struct files_struct *fsp = NULL;
+ char dot[] = ".";
+ struct smb_filename smb_dot = {
+ .base_name = dot,
+ .flags = smb_dname->flags,
+ .twrp = smb_dname->twrp,
+ };
+ struct vfs_open_how how = { .flags = _open_flags, };
+ NTSTATUS status;
+
+ status = create_internal_dirfsp(conn, smb_dname, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * Pointless for opening ".", but you never know...
+ */
+ how.flags |= O_NOFOLLOW;
+
+ status = fd_openat(dirfsp, &smb_dot, fsp, &how);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_INFO("fd_openat(\"%s\", \".\") failed: %s\n",
+ fsp_str_dbg(dirfsp),
+ nt_errstr(status));
+ file_free(NULL, fsp);
+ return status;
+ }
+
+ fsp->fsp_name->st = smb_dname->st;
+ fsp->file_id = vfs_file_id_from_sbuf(conn, &fsp->fsp_name->st);
+ *_fsp = fsp;
+ return NT_STATUS_OK;
+}
+
+/*
+ * The "link" in the name doesn't imply link in the filesystem
+ * sense. It's a object that "links" together an fsp and an smb_fname
+ * and the link allocated as talloc child of an fsp.
+ *
+ * The link is created for fsps that openat_pathref_fsp() returns in
+ * smb_fname->fsp. When this fsp is freed by file_free() by some caller
+ * somewhere, the destructor fsp_smb_fname_link_destructor() on the link object
+ * will use the link to reset the reference in smb_fname->fsp that is about to
+ * go away.
+ *
+ * This prevents smb_fname_internal_fsp_destructor() from seeing dangling fsp
+ * pointers.
+ */
+
+struct fsp_smb_fname_link {
+ struct fsp_smb_fname_link **smb_fname_link;
+ struct files_struct **smb_fname_fsp;
+};
+
+static int fsp_smb_fname_link_destructor(struct fsp_smb_fname_link *link)
+{
+ if (link->smb_fname_link == NULL) {
+ return 0;
+ }
+
+ *link->smb_fname_link = NULL;
+ *link->smb_fname_fsp = NULL;
+ return 0;
+}
+
+static NTSTATUS fsp_smb_fname_link(struct files_struct *fsp,
+ struct fsp_smb_fname_link **smb_fname_link,
+ struct files_struct **smb_fname_fsp)
+{
+ struct fsp_smb_fname_link *link = NULL;
+
+ SMB_ASSERT(*smb_fname_link == NULL);
+ SMB_ASSERT(*smb_fname_fsp == NULL);
+
+ link = talloc_zero(fsp, struct fsp_smb_fname_link);
+ if (link == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ link->smb_fname_link = smb_fname_link;
+ link->smb_fname_fsp = smb_fname_fsp;
+ *smb_fname_link = link;
+ *smb_fname_fsp = fsp;
+
+ talloc_set_destructor(link, fsp_smb_fname_link_destructor);
+ return NT_STATUS_OK;
+}
+
+/*
+ * Free a link, carefully avoiding to trigger the link destructor
+ */
+static void destroy_fsp_smb_fname_link(struct fsp_smb_fname_link **_link)
+{
+ struct fsp_smb_fname_link *link = *_link;
+
+ if (link == NULL) {
+ return;
+ }
+ talloc_set_destructor(link, NULL);
+ TALLOC_FREE(link);
+ *_link = NULL;
+}
+
+/*
+ * Talloc destructor set on an smb_fname set by openat_pathref_fsp() used to
+ * close the embedded smb_fname->fsp.
+ */
+static int smb_fname_fsp_destructor(struct smb_filename *smb_fname)
+{
+ struct files_struct *fsp = smb_fname->fsp;
+ struct files_struct *base_fsp = NULL;
+ NTSTATUS status;
+ int saved_errno = errno;
+
+ destroy_fsp_smb_fname_link(&smb_fname->fsp_link);
+
+ if (fsp == NULL) {
+ errno = saved_errno;
+ return 0;
+ }
+
+ if (fsp_is_alternate_stream(fsp)) {
+ base_fsp = fsp->base_fsp;
+ }
+
+ status = fd_close(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Closing fd for fsp [%s] failed: %s. "
+ "Please check your filesystem!!!\n",
+ fsp_str_dbg(fsp), nt_errstr(status));
+ }
+ file_free(NULL, fsp);
+ smb_fname->fsp = NULL;
+
+ if (base_fsp != NULL) {
+ base_fsp->stream_fsp = NULL;
+ status = fd_close(base_fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Closing fd for base_fsp [%s] failed: %s. "
+ "Please check your filesystem!!!\n",
+ fsp_str_dbg(base_fsp), nt_errstr(status));
+ }
+ file_free(NULL, base_fsp);
+ }
+
+ errno = saved_errno;
+ return 0;
+}
+
+static NTSTATUS openat_pathref_fullname(
+ struct connection_struct *conn,
+ const struct files_struct *dirfsp,
+ struct files_struct *basefsp,
+ struct smb_filename **full_fname,
+ struct smb_filename *smb_fname,
+ const struct vfs_open_how *how)
+{
+ struct files_struct *fsp = NULL;
+ bool have_dirfsp = (dirfsp != NULL);
+ bool have_basefsp = (basefsp != NULL);
+ NTSTATUS status;
+
+ DBG_DEBUG("smb_fname [%s]\n", smb_fname_str_dbg(smb_fname));
+
+ SMB_ASSERT(smb_fname->fsp == NULL);
+ SMB_ASSERT(have_dirfsp != have_basefsp);
+
+ status = fsp_new(conn, conn, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ GetTimeOfDay(&fsp->open_time);
+ fsp_set_gen_id(fsp);
+ ZERO_STRUCT(conn->sconn->fsp_fi_cache);
+
+ fsp->fsp_flags.is_pathref = true;
+
+ status = fsp_attach_smb_fname(fsp, full_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ fsp_set_base_fsp(fsp, basefsp);
+
+ status = fd_openat(dirfsp, smb_fname, fsp, how);
+ if (!NT_STATUS_IS_OK(status)) {
+
+ smb_fname->st = fsp->fsp_name->st;
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_NOT_FOUND) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK))
+ {
+ /*
+ * streams_xattr return NT_STATUS_NOT_FOUND for
+ * opens of not yet existing streams.
+ *
+ * ELOOP maps to NT_STATUS_OBJECT_PATH_NOT_FOUND
+ * and this will result from a open request from
+ * a POSIX client on a symlink.
+ *
+ * NT_STATUS_OBJECT_NAME_NOT_FOUND is the simple
+ * ENOENT case.
+ *
+ * NT_STATUS_STOPPED_ON_SYMLINK is returned when trying
+ * to open a symlink, our callers are not interested in
+ * this.
+ */
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ goto fail;
+ }
+
+ /*
+ * fd_openat() has done an FSTAT on the handle
+ * so update the smb_fname stat info with "truth".
+ * from the handle.
+ */
+ smb_fname->st = fsp->fsp_name->st;
+
+ fsp->fsp_flags.is_directory = S_ISDIR(fsp->fsp_name->st.st_ex_mode);
+
+ fsp->file_id = vfs_file_id_from_sbuf(conn, &fsp->fsp_name->st);
+
+ status = fsp_smb_fname_link(fsp,
+ &smb_fname->fsp_link,
+ &smb_fname->fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ DBG_DEBUG("fsp [%s]: OK\n", fsp_str_dbg(fsp));
+
+ talloc_set_destructor(smb_fname, smb_fname_fsp_destructor);
+ return NT_STATUS_OK;
+
+fail:
+ DBG_DEBUG("Opening pathref for [%s] failed: %s\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status));
+
+ fsp_set_base_fsp(fsp, NULL);
+ fd_close(fsp);
+ file_free(NULL, fsp);
+ return status;
+}
+
+/*
+ * Open an internal O_PATH based fsp for smb_fname. If O_PATH is not
+ * available, open O_RDONLY as root. Both is done in fd_open() ->
+ * non_widelink_open(), triggered by setting fsp->fsp_flags.is_pathref to
+ * true.
+ */
+NTSTATUS openat_pathref_fsp(const struct files_struct *dirfsp,
+ struct smb_filename *smb_fname)
+{
+ connection_struct *conn = dirfsp->conn;
+ struct smb_filename *full_fname = NULL;
+ struct smb_filename *base_fname = NULL;
+ struct vfs_open_how how = { .flags = O_RDONLY|O_NONBLOCK, };
+ NTSTATUS status;
+
+ DBG_DEBUG("smb_fname [%s]\n", smb_fname_str_dbg(smb_fname));
+
+ if (smb_fname->fsp != NULL) {
+ /* We already have one for this name. */
+ DBG_DEBUG("smb_fname [%s] already has a pathref fsp.\n",
+ smb_fname_str_dbg(smb_fname));
+ return NT_STATUS_OK;
+ }
+
+ if (is_named_stream(smb_fname) &&
+ ((conn->fs_capabilities & FILE_NAMED_STREAMS) == 0)) {
+ DBG_DEBUG("stream open [%s] on non-stream share\n",
+ smb_fname_str_dbg(smb_fname));
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+
+ if (!is_named_stream(smb_fname)) {
+ /*
+ * openat_pathref_fullname() will make "full_fname" a
+ * talloc child of the smb_fname->fsp. Don't use
+ * talloc_tos() to allocate it to avoid making the
+ * talloc stackframe pool long-lived.
+ */
+ full_fname = full_path_from_dirfsp_atname(
+ conn,
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ status = openat_pathref_fullname(
+ conn, dirfsp, NULL, &full_fname, smb_fname, &how);
+ TALLOC_FREE(full_fname);
+ return status;
+ }
+
+ /*
+ * stream open
+ */
+ base_fname = cp_smb_filename_nostream(conn, smb_fname);
+ if (base_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ full_fname = full_path_from_dirfsp_atname(
+ conn, /* no talloc_tos(), see comment above */
+ dirfsp,
+ base_fname);
+ if (full_fname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ status = openat_pathref_fullname(
+ conn, dirfsp, NULL, &full_fname, base_fname, &how);
+ TALLOC_FREE(full_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("openat_pathref_fullname() failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = open_stream_pathref_fsp(&base_fname->fsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("open_stream_pathref_fsp failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ smb_fname_fsp_unlink(base_fname);
+fail:
+ TALLOC_FREE(base_fname);
+ return status;
+}
+
+/*
+ * Open a stream given an already opened base_fsp. Avoid
+ * non_widelink_open: This is only valid for the case where we have a
+ * valid non-cwd_fsp dirfsp that we can pass to SMB_VFS_OPENAT()
+ */
+NTSTATUS open_stream_pathref_fsp(
+ struct files_struct **_base_fsp,
+ struct smb_filename *smb_fname)
+{
+ struct files_struct *base_fsp = *_base_fsp;
+ connection_struct *conn = base_fsp->conn;
+ struct smb_filename *base_fname = base_fsp->fsp_name;
+ struct smb_filename *full_fname = NULL;
+ struct vfs_open_how how = { .flags = O_RDONLY|O_NONBLOCK, };
+ NTSTATUS status;
+
+ SMB_ASSERT(smb_fname->fsp == NULL);
+ SMB_ASSERT(is_named_stream(smb_fname));
+
+ full_fname = synthetic_smb_fname(
+ conn, /* no talloc_tos(), this will be long-lived */
+ base_fname->base_name,
+ smb_fname->stream_name,
+ &smb_fname->st,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (full_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = openat_pathref_fullname(
+ conn, NULL, base_fsp, &full_fname, smb_fname, &how);
+ TALLOC_FREE(full_fname);
+ return status;
+}
+
+static char *path_to_strv(TALLOC_CTX *mem_ctx, const char *path)
+{
+ char *result = talloc_strdup(mem_ctx, path);
+
+ if (result == NULL) {
+ return NULL;
+ }
+ string_replace(result, '/', '\0');
+ return result;
+}
+
+NTSTATUS readlink_talloc(
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_relname,
+ char **_substitute)
+{
+ struct smb_filename null_fname = {
+ .base_name = discard_const_p(char, ""),
+ };
+ char buf[PATH_MAX];
+ ssize_t ret;
+ char *substitute;
+ NTSTATUS status;
+
+ if (smb_relname == NULL) {
+ /*
+ * We have a Linux O_PATH handle in dirfsp and want to
+ * read its value, essentially a freadlink
+ */
+ smb_relname = &null_fname;
+ }
+
+ ret = SMB_VFS_READLINKAT(
+ dirfsp->conn, dirfsp, smb_relname, buf, sizeof(buf));
+ if (ret < 0) {
+ status = map_nt_error_from_unix(errno);
+ DBG_DEBUG("SMB_VFS_READLINKAT() failed: %s\n",
+ strerror(errno));
+ return status;
+ }
+
+ if ((size_t)ret == sizeof(buf)) {
+ /*
+ * Do we need symlink targets longer than PATH_MAX?
+ */
+ DBG_DEBUG("Got full %zu bytes from readlink, too long\n",
+ sizeof(buf));
+ return NT_STATUS_BUFFER_OVERFLOW;
+ }
+
+ substitute = talloc_strndup(mem_ctx, buf, ret);
+ if (substitute == NULL) {
+ DBG_DEBUG("talloc_strndup() failed\n");
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *_substitute = substitute;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS read_symlink_reparse(
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_relname,
+ struct symlink_reparse_struct **_symlink)
+{
+ struct symlink_reparse_struct *symlink = NULL;
+ NTSTATUS status;
+
+ symlink = talloc_zero(mem_ctx, struct symlink_reparse_struct);
+ if (symlink == NULL) {
+ goto nomem;
+ }
+
+ status = readlink_talloc(
+ symlink, dirfsp, smb_relname, &symlink->substitute_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("readlink_talloc failed: %s\n", nt_errstr(status));
+ goto fail;
+ }
+
+ if (symlink->substitute_name[0] == '/') {
+ char *subdir_path = NULL;
+ char *abs_target_canon = NULL;
+ const char *relative = NULL;
+ bool in_share;
+
+ subdir_path = talloc_asprintf(talloc_tos(),
+ "%s/%s",
+ dirfsp->conn->connectpath,
+ dirfsp->fsp_name->base_name);
+ if (subdir_path == NULL) {
+ goto nomem;
+ }
+
+ abs_target_canon =
+ canonicalize_absolute_path(talloc_tos(),
+ symlink->substitute_name);
+ if (abs_target_canon == NULL) {
+ goto nomem;
+ }
+
+ in_share = subdir_of(subdir_path,
+ strlen(subdir_path),
+ abs_target_canon,
+ &relative);
+ if (in_share) {
+ TALLOC_FREE(symlink->substitute_name);
+ symlink->substitute_name =
+ talloc_strdup(symlink, relative);
+ if (symlink->substitute_name == NULL) {
+ goto nomem;
+ }
+ }
+ }
+
+ if (!IS_DIRECTORY_SEP(symlink->substitute_name[0])) {
+ symlink->flags |= SYMLINK_FLAG_RELATIVE;
+ }
+
+ *_symlink = symlink;
+ return NT_STATUS_OK;
+nomem:
+ status = NT_STATUS_NO_MEMORY;
+fail:
+ TALLOC_FREE(symlink);
+ return status;
+}
+
+static bool full_path_extend(char **dir, const char *atname)
+{
+ talloc_asprintf_addbuf(dir,
+ "%s%s",
+ (*dir)[0] == '\0' ? "" : "/",
+ atname);
+ return (*dir) != NULL;
+}
+
+NTSTATUS create_open_symlink_err(TALLOC_CTX *mem_ctx,
+ files_struct *dirfsp,
+ struct smb_filename *smb_relname,
+ struct open_symlink_err **_err)
+{
+ struct open_symlink_err *err = NULL;
+ NTSTATUS status;
+
+ err = talloc_zero(mem_ctx, struct open_symlink_err);
+ if (err == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = read_symlink_reparse(err, dirfsp, smb_relname, &err->reparse);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(err);
+ return status;
+ }
+
+ *_err = err;
+ return NT_STATUS_OK;
+}
+
+/*
+ * Create the memcache-key for GETREALFILENAME_CACHE: This supplements
+ * the stat cache for the last component to be looked up. Cache
+ * contents is the correctly capitalized translation of the parameter
+ * "name" as it exists on disk. This is indexed by inode of the dirfsp
+ * and name, and contrary to stat_cahce_lookup() it does not
+ * vfs_stat() the last component. This will be taken care of by an
+ * attempt to do a openat_pathref_fsp().
+ */
+static bool get_real_filename_cache_key(TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ const char *name,
+ DATA_BLOB *_key)
+{
+ struct file_id fid = vfs_file_id_from_sbuf(dirfsp->conn,
+ &dirfsp->fsp_name->st);
+ char *upper = NULL;
+ uint8_t *key = NULL;
+ size_t namelen, keylen;
+
+ upper = talloc_strdup_upper(mem_ctx, name);
+ if (upper == NULL) {
+ return false;
+ }
+ namelen = talloc_get_size(upper);
+
+ keylen = namelen + sizeof(fid);
+ if (keylen < sizeof(fid)) {
+ TALLOC_FREE(upper);
+ return false;
+ }
+
+ key = talloc_size(mem_ctx, keylen);
+ if (key == NULL) {
+ TALLOC_FREE(upper);
+ return false;
+ }
+
+ memcpy(key, &fid, sizeof(fid));
+ memcpy(key + sizeof(fid), upper, namelen);
+ TALLOC_FREE(upper);
+
+ *_key = (DATA_BLOB){
+ .data = key,
+ .length = keylen,
+ };
+ return true;
+}
+
+static int smb_vfs_openat_ci(TALLOC_CTX *mem_ctx,
+ bool case_sensitive,
+ struct connection_struct *conn,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname_rel,
+ files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ char *orig_base_name = smb_fname_rel->base_name;
+ DATA_BLOB cache_key = {
+ .data = NULL,
+ };
+ DATA_BLOB cache_value = {
+ .data = NULL,
+ };
+ NTSTATUS status;
+ int fd;
+ bool ok;
+
+ fd = SMB_VFS_OPENAT(conn, dirfsp, smb_fname_rel, fsp, how);
+ if ((fd >= 0) || case_sensitive) {
+ return fd;
+ }
+ if (errno != ENOENT) {
+ return -1;
+ }
+
+ if (!lp_stat_cache()) {
+ goto lookup;
+ }
+
+ ok = get_real_filename_cache_key(mem_ctx,
+ dirfsp,
+ orig_base_name,
+ &cache_key);
+ if (!ok) {
+ /*
+ * probably ENOMEM, just bail
+ */
+ errno = ENOMEM;
+ return -1;
+ }
+
+ DO_PROFILE_INC(statcache_lookups);
+
+ ok = memcache_lookup(NULL,
+ GETREALFILENAME_CACHE,
+ cache_key,
+ &cache_value);
+ if (!ok) {
+ DO_PROFILE_INC(statcache_misses);
+ goto lookup;
+ }
+ DO_PROFILE_INC(statcache_hits);
+
+ smb_fname_rel->base_name = talloc_strndup(mem_ctx,
+ (char *)cache_value.data,
+ cache_value.length);
+ if (smb_fname_rel->base_name == NULL) {
+ TALLOC_FREE(cache_key.data);
+ smb_fname_rel->base_name = orig_base_name;
+ errno = ENOMEM;
+ return -1;
+ }
+
+ if (IS_VETO_PATH(dirfsp->conn, smb_fname_rel->base_name)) {
+ DBG_DEBUG("veto files rejecting last component %s\n",
+ smb_fname_str_dbg(smb_fname_rel));
+ TALLOC_FREE(cache_key.data);
+ smb_fname_rel->base_name = orig_base_name;
+ errno = EPERM;
+ return -1;
+ }
+
+ fd = SMB_VFS_OPENAT(conn, dirfsp, smb_fname_rel, fsp, how);
+ if (fd >= 0) {
+ TALLOC_FREE(cache_key.data);
+ return fd;
+ }
+
+ memcache_delete(NULL, GETREALFILENAME_CACHE, cache_key);
+
+ /*
+ * For the "new filename" case we need to preserve the
+ * capitalization the client sent us, see
+ * https://bugzilla.samba.org/show_bug.cgi?id=15481
+ */
+ TALLOC_FREE(smb_fname_rel->base_name);
+ smb_fname_rel->base_name = orig_base_name;
+
+lookup:
+
+ status = get_real_filename_at(dirfsp,
+ orig_base_name,
+ mem_ctx,
+ &smb_fname_rel->base_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("get_real_filename_at() failed: %s\n",
+ nt_errstr(status));
+ errno = ENOENT;
+ return -1;
+ }
+
+ if (IS_VETO_PATH(conn, smb_fname_rel->base_name)) {
+ DBG_DEBUG("found veto files path component "
+ "%s => %s\n",
+ orig_base_name,
+ smb_fname_rel->base_name);
+ TALLOC_FREE(smb_fname_rel->base_name);
+ smb_fname_rel->base_name = orig_base_name;
+ errno = ENOENT;
+ return -1;
+ }
+
+ fd = SMB_VFS_OPENAT(conn, dirfsp, smb_fname_rel, fsp, how);
+
+ if ((fd >= 0) && (cache_key.data != NULL)) {
+ DATA_BLOB value = {
+ .data = (uint8_t *)smb_fname_rel->base_name,
+ .length = strlen(smb_fname_rel->base_name) + 1,
+ };
+
+ memcache_add(NULL, GETREALFILENAME_CACHE, cache_key, value);
+ TALLOC_FREE(cache_key.data);
+ }
+
+ return fd;
+}
+
+NTSTATUS openat_pathref_fsp_nosymlink(TALLOC_CTX *mem_ctx,
+ struct connection_struct *conn,
+ struct files_struct *in_dirfsp,
+ const char *path_in,
+ NTTIME twrp,
+ bool posix,
+ struct smb_filename **_smb_fname,
+ struct open_symlink_err **_symlink_err)
+{
+ struct files_struct *dirfsp = in_dirfsp;
+ struct smb_filename full_fname = {
+ .base_name = NULL,
+ .twrp = twrp,
+ .flags = posix ? SMB_FILENAME_POSIX_PATH : 0,
+ };
+ struct smb_filename rel_fname = {
+ .base_name = NULL,
+ .twrp = twrp,
+ .flags = full_fname.flags,
+ };
+ struct smb_filename *result = NULL;
+ struct open_symlink_err *symlink_err = NULL;
+ struct files_struct *fsp = NULL;
+ char *path = NULL, *next = NULL;
+ bool ok, is_toplevel;
+ int fd;
+ NTSTATUS status;
+ struct vfs_open_how how = {
+ .flags = O_NOFOLLOW | O_NONBLOCK,
+ .mode = 0,
+ };
+
+ DBG_DEBUG("path_in=%s\n", path_in);
+
+ status = fsp_new(conn, conn, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("fsp_new() failed: %s\n", nt_errstr(status));
+ goto fail;
+ }
+
+ GetTimeOfDay(&fsp->open_time);
+ fsp_set_gen_id(fsp);
+ ZERO_STRUCT(conn->sconn->fsp_fi_cache);
+
+ fsp->fsp_name = &full_fname;
+
+#ifdef O_PATH
+ /*
+ * Add O_PATH manually, doing this by setting
+ * fsp->fsp_flags.is_pathref will make us become_root() in the
+ * non-O_PATH case, which would cause a security problem.
+ */
+ how.flags |= O_PATH;
+#else
+#ifdef O_SEARCH
+ /*
+ * O_SEARCH just checks for the "x" bit. We are traversing
+ * directories, so we don't need the implicit O_RDONLY ("r"
+ * permissions) but only the "x"-permissions requested by
+ * O_SEARCH. We need either O_PATH or O_SEARCH to correctly
+ * function, without either we will incorrectly require also
+ * the "r" bit when traversing the directory hierarchy.
+ */
+ how.flags |= O_SEARCH;
+#endif
+#endif
+
+ is_toplevel = (dirfsp == dirfsp->conn->cwd_fsp);
+ is_toplevel |= ISDOT(dirfsp->fsp_name->base_name);
+
+ full_fname.base_name =
+ talloc_strdup(talloc_tos(),
+ is_toplevel ? "" : dirfsp->fsp_name->base_name);
+ if (full_fname.base_name == NULL) {
+ DBG_DEBUG("talloc_strdup() failed\n");
+ goto nomem;
+ }
+
+ /*
+ * First split the path into individual components.
+ */
+ path = path_to_strv(talloc_tos(), path_in);
+ if (path == NULL) {
+ DBG_DEBUG("path_to_strv() failed\n");
+ goto nomem;
+ }
+
+ /*
+ * First we loop over all components
+ * in order to verify, there's no '.' or '..'
+ */
+ rel_fname.base_name = path;
+ while (rel_fname.base_name != NULL) {
+
+ next = strv_next(path, rel_fname.base_name);
+
+ /*
+ * Path sanitizing further up has cleaned or rejected
+ * empty path components. Assert this here.
+ */
+ SMB_ASSERT(rel_fname.base_name[0] != '\0');
+
+ if (ISDOT(rel_fname.base_name) ||
+ ISDOTDOT(rel_fname.base_name)) {
+ DBG_DEBUG("%s contains a dot\n", path_in);
+ status = NT_STATUS_OBJECT_NAME_INVALID;
+ goto fail;
+ }
+
+ /* Check veto files. */
+ if (IS_VETO_PATH(conn, rel_fname.base_name)) {
+ DBG_DEBUG("%s contains veto files path component %s\n",
+ path_in, rel_fname.base_name);
+ status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ goto fail;
+ }
+
+ rel_fname.base_name = next;
+ }
+
+ if (conn->open_how_resolve & VFS_OPEN_HOW_RESOLVE_NO_SYMLINKS) {
+
+ /*
+ * Try a direct openat2 with RESOLVE_NO_SYMLINKS to
+ * avoid the openat/close loop further down.
+ */
+
+ rel_fname.base_name = discard_const_p(char, path_in);
+ how.resolve = VFS_OPEN_HOW_RESOLVE_NO_SYMLINKS;
+
+ fd = SMB_VFS_OPENAT(conn, dirfsp, &rel_fname, fsp, &how);
+ if (fd >= 0) {
+ fsp_set_fd(fsp, fd);
+ ok = full_path_extend(&full_fname.base_name,
+ rel_fname.base_name);
+ if (!ok) {
+ goto nomem;
+ }
+ goto done;
+ }
+
+ status = map_nt_error_from_unix(errno);
+ DBG_DEBUG("SMB_VFS_OPENAT(%s, %s, RESOLVE_NO_SYMLINKS) "
+ "returned %d %s => %s\n",
+ smb_fname_str_dbg(dirfsp->fsp_name), path_in,
+ errno, strerror(errno), nt_errstr(status));
+ SMB_ASSERT(fd == -1);
+ switch (errno) {
+ case ENOSYS:
+ /*
+ * We got ENOSYS, so fallback to the old code
+ * if the kernel doesn't support openat2() yet.
+ */
+ break;
+
+ case ELOOP:
+ case ENOTDIR:
+ /*
+ * For ELOOP we also fallback in order to
+ * return the correct information with
+ * NT_STATUS_STOPPED_ON_SYMLINK.
+ *
+ * O_NOFOLLOW|O_DIRECTORY results in
+ * ENOTDIR instead of ELOOP for the final
+ * component.
+ */
+ break;
+
+ case ENOENT:
+ /*
+ * If we got ENOENT, the filesystem could
+ * be case sensitive. For now we only do
+ * the get_real_filename_at() dance in
+ * the fallback loop below.
+ */
+ break;
+
+ default:
+ goto fail;
+ }
+
+ /*
+ * Just fallback to the openat loop
+ */
+ how.resolve = 0;
+ }
+
+ /*
+ * Now we loop over all components
+ * opening each one and using it
+ * as dirfd for the next one.
+ *
+ * It means we can detect symlinks
+ * within the path.
+ */
+ rel_fname.base_name = path;
+next:
+ next = strv_next(path, rel_fname.base_name);
+
+ fd = smb_vfs_openat_ci(talloc_tos(),
+ posix || conn->case_sensitive,
+ conn,
+ dirfsp,
+ &rel_fname,
+ fsp,
+ &how);
+
+#ifndef O_PATH
+ if ((fd == -1) && (errno == ELOOP)) {
+ int ret;
+
+ /*
+ * openat() hit a symlink. With O_PATH we open the
+ * symlink and get ENOTDIR in the next round, see
+ * below.
+ */
+
+ status = create_open_symlink_err(mem_ctx,
+ dirfsp,
+ &rel_fname,
+ &symlink_err);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("create_open_symlink_err failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ if (next != NULL) {
+ size_t parsed = next - path;
+ size_t len = talloc_get_size(path);
+ symlink_err->unparsed = len - parsed;
+ }
+
+ /*
+ * We know rel_fname is a symlink, now fill in the
+ * rest of the metadata for our callers.
+ */
+
+ ret = SMB_VFS_FSTATAT(conn,
+ dirfsp,
+ &rel_fname,
+ &symlink_err->st,
+ AT_SYMLINK_NOFOLLOW);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_DEBUG("SMB_VFS_FSTATAT(%s/%s) failed: %s\n",
+ fsp_str_dbg(dirfsp),
+ rel_fname.base_name,
+ strerror(errno));
+ TALLOC_FREE(symlink_err);
+ goto fail;
+ }
+
+ if (!S_ISLNK(symlink_err->st.st_ex_mode)) {
+ /*
+ * Hit a race: readlink_talloc() worked before
+ * the fstatat(), but rel_fname changed to
+ * something that's not a symlink.
+ */
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ TALLOC_FREE(symlink_err);
+ goto fail;
+ }
+
+ status = NT_STATUS_STOPPED_ON_SYMLINK;
+ goto fail;
+ }
+#endif
+
+ if ((fd == -1) && (errno == ENOTDIR)) {
+ size_t parsed, len;
+
+ /*
+ * dirfsp does not point at a directory, try a
+ * freadlink.
+ */
+
+ status = create_open_symlink_err(mem_ctx,
+ dirfsp,
+ NULL,
+ &symlink_err);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("create_open_symlink_err failed: %s\n",
+ nt_errstr(status));
+ status = NT_STATUS_NOT_A_DIRECTORY;
+ goto fail;
+ }
+
+ parsed = rel_fname.base_name - path;
+ len = talloc_get_size(path);
+ symlink_err->unparsed = len - parsed;
+
+ symlink_err->st = dirfsp->fsp_name->st;
+
+ status = NT_STATUS_STOPPED_ON_SYMLINK;
+ goto fail;
+ }
+
+ if (fd == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_DEBUG("SMB_VFS_OPENAT() failed: %s\n",
+ strerror(errno));
+ goto fail;
+ }
+ fsp_set_fd(fsp, fd);
+
+ ok = full_path_extend(&full_fname.base_name, rel_fname.base_name);
+ if (!ok) {
+ goto nomem;
+ }
+
+ if (next != NULL) {
+ struct files_struct *tmp = NULL;
+
+ if (dirfsp != in_dirfsp) {
+ fd_close(dirfsp);
+ }
+
+ tmp = dirfsp;
+ dirfsp = fsp;
+
+ if (tmp == in_dirfsp) {
+ status = fsp_new(conn, conn, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("fsp_new() failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ fsp->fsp_name = &full_fname;
+ } else {
+ fsp = tmp;
+ }
+
+ rel_fname.base_name = next;
+
+ goto next;
+ }
+
+ if (dirfsp != in_dirfsp) {
+ SMB_ASSERT(fsp_get_pathref_fd(dirfsp) != -1);
+ fd_close(dirfsp);
+ dirfsp->fsp_name = NULL;
+ file_free(NULL, dirfsp);
+ dirfsp = NULL;
+ }
+
+done:
+ fsp->fsp_flags.is_pathref = true;
+ fsp->fsp_name = NULL;
+
+ status = fsp_set_smb_fname(fsp, &full_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("fsp_set_smb_fname() failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("vfs_stat_fsp(%s) failed: %s\n",
+ fsp_str_dbg(fsp),
+ nt_errstr(status));
+ goto fail;
+ }
+
+ if (S_ISLNK(fsp->fsp_name->st.st_ex_mode)) {
+ /*
+ * Last component was a symlink we opened with O_PATH, fail it
+ * here.
+ */
+ status = create_open_symlink_err(mem_ctx,
+ fsp,
+ NULL,
+ &symlink_err);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ symlink_err->st = fsp->fsp_name->st;
+
+ status = NT_STATUS_STOPPED_ON_SYMLINK;
+ goto fail;
+ }
+
+ /*
+ * We must correctly set fsp->file_id as code inside
+ * open.c will use this to check if delete_on_close
+ * has been set on the dirfsp.
+ */
+ fsp->file_id = vfs_file_id_from_sbuf(conn, &fsp->fsp_name->st);
+
+ result = cp_smb_filename(mem_ctx, fsp->fsp_name);
+ if (result == NULL) {
+ DBG_DEBUG("cp_smb_filename() failed\n");
+ goto nomem;
+ }
+
+ status = fsp_smb_fname_link(fsp,
+ &result->fsp_link,
+ &result->fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ talloc_set_destructor(result, smb_fname_fsp_destructor);
+
+ *_smb_fname = result;
+
+ DBG_DEBUG("returning %s\n", smb_fname_str_dbg(result));
+
+ return NT_STATUS_OK;
+
+nomem:
+ status = NT_STATUS_NO_MEMORY;
+fail:
+ if (fsp != NULL) {
+ if (fsp_get_pathref_fd(fsp) != -1) {
+ fd_close(fsp);
+ }
+ file_free(NULL, fsp);
+ fsp = NULL;
+ }
+
+ if ((dirfsp != NULL) && (dirfsp != in_dirfsp)) {
+ SMB_ASSERT(fsp_get_pathref_fd(dirfsp) != -1);
+ fd_close(dirfsp);
+ dirfsp->fsp_name = NULL;
+ file_free(NULL, dirfsp);
+ dirfsp = NULL;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) {
+ *_symlink_err = symlink_err;
+ }
+
+ TALLOC_FREE(path);
+ return status;
+}
+
+/*
+ * Open smb_fname_rel->fsp as a pathref fsp with a case insensitive
+ * fallback using GETREALFILENAME_CACHE and get_real_filename_at() if
+ * the first attempt based on the filename sent by the client gives
+ * ENOENT.
+ */
+NTSTATUS openat_pathref_fsp_lcomp(struct files_struct *dirfsp,
+ struct smb_filename *smb_fname_rel,
+ uint32_t ucf_flags)
+{
+ struct connection_struct *conn = dirfsp->conn;
+ const char *orig_rel_base_name = smb_fname_rel->base_name;
+ struct files_struct *fsp = NULL;
+ struct smb_filename *full_fname = NULL;
+ struct vfs_open_how how = {
+ .flags = O_RDONLY | O_NONBLOCK | O_NOFOLLOW,
+ };
+ NTSTATUS status;
+ int ret, fd;
+
+ /*
+ * Make sure we don't need of the all the magic in
+ * openat_pathref_fsp() with regards non_widelink_open etc.
+ */
+
+ SMB_ASSERT((smb_fname_rel->fsp == NULL) &&
+ (dirfsp != dirfsp->conn->cwd_fsp) &&
+ (strchr_m(smb_fname_rel->base_name, '/') == NULL) &&
+ !is_named_stream(smb_fname_rel));
+
+ SET_STAT_INVALID(smb_fname_rel->st);
+
+ /* Check veto files - only looks at last component. */
+ if (IS_VETO_PATH(dirfsp->conn, smb_fname_rel->base_name)) {
+ DBG_DEBUG("veto files rejecting last component %s\n",
+ smb_fname_str_dbg(smb_fname_rel));
+ return NT_STATUS_NETWORK_OPEN_RESTRICTION;
+ }
+
+ status = fsp_new(conn, conn, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("fsp_new() failed: %s\n", nt_errstr(status));
+ return status;
+ }
+
+ GetTimeOfDay(&fsp->open_time);
+ fsp_set_gen_id(fsp);
+ ZERO_STRUCT(conn->sconn->fsp_fi_cache);
+
+ fsp->fsp_flags.is_pathref = true;
+
+ full_fname = full_path_from_dirfsp_atname(conn, dirfsp, smb_fname_rel);
+ if (full_fname == NULL) {
+ DBG_DEBUG("full_path_from_dirfsp_atname(%s/%s) failed\n",
+ dirfsp->fsp_name->base_name,
+ smb_fname_rel->base_name);
+ file_free(NULL, fsp);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = fsp_attach_smb_fname(fsp, &full_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("fsp_attach_smb_fname(fsp, %s) failed: %s\n",
+ smb_fname_str_dbg(full_fname),
+ nt_errstr(status));
+ file_free(NULL, fsp);
+ return status;
+ }
+
+ fd = smb_vfs_openat_ci(smb_fname_rel,
+ (ucf_flags & UCF_POSIX_PATHNAMES) ||
+ conn->case_sensitive,
+ conn,
+ dirfsp,
+ smb_fname_rel,
+ fsp,
+ &how);
+
+ if ((fd == -1) && (errno == ENOENT)) {
+ status = map_nt_error_from_unix(errno);
+ DBG_DEBUG("smb_vfs_openat(%s/%s) failed: %s\n",
+ dirfsp->fsp_name->base_name,
+ smb_fname_rel->base_name,
+ strerror(errno));
+ file_free(NULL, fsp);
+ return status;
+ }
+
+ if (smb_fname_rel->base_name != orig_rel_base_name) {
+ struct smb_filename new_fullname = *smb_fname_rel;
+
+ DBG_DEBUG("rel->base_name changed from %s to %s\n",
+ orig_rel_base_name,
+ smb_fname_rel->base_name);
+
+ new_fullname.base_name = full_path_from_dirfsp_at_basename(
+ talloc_tos(), dirfsp, new_fullname.base_name);
+ if (new_fullname.base_name == NULL) {
+ fd_close(fsp);
+ file_free(NULL, fsp);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = fsp_set_smb_fname(fsp, &new_fullname);
+ if (!NT_STATUS_IS_OK(status)) {
+ fd_close(fsp);
+ file_free(NULL, fsp);
+ return status;
+ }
+ }
+
+ fsp_set_fd(fsp, fd);
+
+ if (fd >= 0) {
+ ret = SMB_VFS_FSTAT(fsp, &fsp->fsp_name->st);
+ } else {
+ ret = SMB_VFS_FSTATAT(fsp->conn,
+ dirfsp,
+ smb_fname_rel,
+ &fsp->fsp_name->st,
+ AT_SYMLINK_NOFOLLOW);
+ }
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_DEBUG("SMB_VFS_%sSTAT(%s/%s) failed: %s\n",
+ (fd >= 0) ? "F" : "",
+ dirfsp->fsp_name->base_name,
+ smb_fname_rel->base_name,
+ strerror(errno));
+ fd_close(fsp);
+ file_free(NULL, fsp);
+ return status;
+ }
+
+ fsp->fsp_flags.is_directory = S_ISDIR(fsp->fsp_name->st.st_ex_mode);
+ fsp->file_id = vfs_file_id_from_sbuf(conn, &fsp->fsp_name->st);
+
+ smb_fname_rel->st = fsp->fsp_name->st;
+
+ status = fsp_smb_fname_link(fsp,
+ &smb_fname_rel->fsp_link,
+ &smb_fname_rel->fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("fsp_smb_fname_link() failed: %s\n",
+ nt_errstr(status));
+ fd_close(fsp);
+ file_free(NULL, fsp);
+ return status;
+ }
+
+ DBG_DEBUG("fsp [%s]: OK, fd=%d\n", fsp_str_dbg(fsp), fd);
+
+ talloc_set_destructor(smb_fname_rel, smb_fname_fsp_destructor);
+ return NT_STATUS_OK;
+}
+
+void smb_fname_fsp_unlink(struct smb_filename *smb_fname)
+{
+ talloc_set_destructor(smb_fname, NULL);
+ smb_fname->fsp = NULL;
+ destroy_fsp_smb_fname_link(&smb_fname->fsp_link);
+}
+
+/*
+ * Move any existing embedded fsp refs from the src name to the
+ * destination. It's safe to call this on src smb_fname's that have no embedded
+ * pathref fsp.
+ */
+NTSTATUS move_smb_fname_fsp_link(struct smb_filename *smb_fname_dst,
+ struct smb_filename *smb_fname_src)
+{
+ NTSTATUS status;
+
+ /*
+ * The target should always not be linked yet!
+ */
+ SMB_ASSERT(smb_fname_dst->fsp == NULL);
+ SMB_ASSERT(smb_fname_dst->fsp_link == NULL);
+
+ if (smb_fname_src->fsp == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ status = fsp_smb_fname_link(smb_fname_src->fsp,
+ &smb_fname_dst->fsp_link,
+ &smb_fname_dst->fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ talloc_set_destructor(smb_fname_dst, smb_fname_fsp_destructor);
+
+ smb_fname_fsp_unlink(smb_fname_src);
+
+ return NT_STATUS_OK;
+}
+
+static int fsp_ref_no_close_destructor(struct smb_filename *smb_fname)
+{
+ destroy_fsp_smb_fname_link(&smb_fname->fsp_link);
+ return 0;
+}
+
+NTSTATUS reference_smb_fname_fsp_link(struct smb_filename *smb_fname_dst,
+ const struct smb_filename *smb_fname_src)
+{
+ NTSTATUS status;
+
+ /*
+ * The target should always not be linked yet!
+ */
+ SMB_ASSERT(smb_fname_dst->fsp == NULL);
+ SMB_ASSERT(smb_fname_dst->fsp_link == NULL);
+
+ if (smb_fname_src->fsp == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ status = fsp_smb_fname_link(smb_fname_src->fsp,
+ &smb_fname_dst->fsp_link,
+ &smb_fname_dst->fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ talloc_set_destructor(smb_fname_dst, fsp_ref_no_close_destructor);
+
+ return NT_STATUS_OK;
+}
+
+/**
+ * Create an smb_fname and open smb_fname->fsp pathref
+ **/
+NTSTATUS synthetic_pathref(TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ const char *base_name,
+ const char *stream_name,
+ const SMB_STRUCT_STAT *psbuf,
+ NTTIME twrp,
+ uint32_t flags,
+ struct smb_filename **_smb_fname)
+{
+ struct smb_filename *smb_fname = NULL;
+ NTSTATUS status;
+
+ smb_fname = synthetic_smb_fname(mem_ctx,
+ base_name,
+ stream_name,
+ psbuf,
+ twrp,
+ flags);
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = openat_pathref_fsp(dirfsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_NOTICE("opening [%s] failed\n",
+ smb_fname_str_dbg(smb_fname));
+ TALLOC_FREE(smb_fname);
+ return status;
+ }
+
+ *_smb_fname = smb_fname;
+ return NT_STATUS_OK;
+}
+
+/**
+ * Turn a path into a parent pathref and atname
+ *
+ * This returns the parent pathref in _parent and the name relative to it. If
+ * smb_fname was a pathref (ie smb_fname->fsp != NULL), then _atname will be a
+ * pathref as well, ie _atname->fsp will point at the same fsp as
+ * smb_fname->fsp.
+ **/
+NTSTATUS parent_pathref(TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ struct smb_filename **_parent,
+ struct smb_filename **_atname)
+{
+ struct smb_filename *parent = NULL;
+ struct smb_filename *atname = NULL;
+ NTSTATUS status;
+
+ status = SMB_VFS_PARENT_PATHNAME(dirfsp->conn,
+ mem_ctx,
+ smb_fname,
+ &parent,
+ &atname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * We know that the parent name must
+ * exist, and the name has been canonicalized
+ * even if this was a POSIX pathname.
+ * Ensure that we follow symlinks for
+ * the parent. See the torture test
+ * POSIX-SYMLINK-PARENT for details.
+ */
+ parent->flags &= ~SMB_FILENAME_POSIX_PATH;
+
+ status = openat_pathref_fsp(dirfsp, parent);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(parent);
+ return status;
+ }
+
+ status = reference_smb_fname_fsp_link(atname, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(parent);
+ return status;
+ }
+
+ *_parent = parent;
+ *_atname = atname;
+ return NT_STATUS_OK;
+}
+
+static bool close_file_in_loop(struct files_struct *fsp,
+ enum file_close_type close_type)
+{
+ if (fsp_is_alternate_stream(fsp)) {
+ /*
+ * This is a stream, it can't be a base
+ */
+ SMB_ASSERT(fsp->stream_fsp == NULL);
+ SMB_ASSERT(fsp->base_fsp->stream_fsp == fsp);
+
+ /*
+ * Remove the base<->stream link so that
+ * close_file_free() does not close fsp->base_fsp as
+ * well. This would destroy walking the linked list of
+ * fsps.
+ */
+ fsp->base_fsp->stream_fsp = NULL;
+ fsp->base_fsp = NULL;
+
+ close_file_free(NULL, &fsp, close_type);
+ return NULL;
+ }
+
+ if (fsp->stream_fsp != NULL) {
+ /*
+ * This is the base of a stream.
+ */
+ SMB_ASSERT(fsp->stream_fsp->base_fsp == fsp);
+
+ /*
+ * Remove the base<->stream link. This will make fsp
+ * look like a normal fsp for the next round.
+ */
+ fsp->stream_fsp->base_fsp = NULL;
+ fsp->stream_fsp = NULL;
+
+ /*
+ * Have us called back a second time. In the second
+ * round, "fsp" now looks like a normal fsp.
+ */
+ return false;
+ }
+
+ close_file_free(NULL, &fsp, close_type);
+ return true;
+}
+
+/****************************************************************************
+ Close all open files for a connection.
+****************************************************************************/
+
+struct file_close_conn_state {
+ struct connection_struct *conn;
+ enum file_close_type close_type;
+ bool fsp_left_behind;
+};
+
+static struct files_struct *file_close_conn_fn(
+ struct files_struct *fsp,
+ void *private_data)
+{
+ struct file_close_conn_state *state = private_data;
+ bool did_close;
+
+ if (fsp->conn != state->conn) {
+ return NULL;
+ }
+
+ if (fsp->op != NULL && fsp->op->global->durable) {
+ /*
+ * A tree disconnect closes a durable handle
+ */
+ fsp->op->global->durable = false;
+ }
+
+ did_close = close_file_in_loop(fsp, state->close_type);
+ if (!did_close) {
+ state->fsp_left_behind = true;
+ }
+
+ return NULL;
+}
+
+void file_close_conn(connection_struct *conn, enum file_close_type close_type)
+{
+ struct file_close_conn_state state = { .conn = conn,
+ .close_type = close_type };
+
+ files_forall(conn->sconn, file_close_conn_fn, &state);
+
+ if (state.fsp_left_behind) {
+ state.fsp_left_behind = false;
+ files_forall(conn->sconn, file_close_conn_fn, &state);
+ SMB_ASSERT(!state.fsp_left_behind);
+ }
+}
+
+/****************************************************************************
+ Initialise file structures.
+****************************************************************************/
+
+static int files_max_open_fds;
+
+bool file_init_global(void)
+{
+ int request_max = lp_max_open_files();
+ int real_lim;
+ int real_max;
+
+ if (files_max_open_fds != 0) {
+ return true;
+ }
+
+ /*
+ * Set the max_open files to be the requested
+ * max plus a fudgefactor to allow for the extra
+ * fd's we need such as log files etc...
+ */
+ real_lim = set_maxfiles(request_max + MAX_OPEN_FUDGEFACTOR);
+
+ real_max = real_lim - MAX_OPEN_FUDGEFACTOR;
+
+ if (real_max + FILE_HANDLE_OFFSET + MAX_OPEN_PIPES > 65536) {
+ real_max = 65536 - FILE_HANDLE_OFFSET - MAX_OPEN_PIPES;
+ }
+
+ if (real_max != request_max) {
+ DEBUG(1, ("file_init_global: Information only: requested %d "
+ "open files, %d are available.\n",
+ request_max, real_max));
+ }
+
+ SMB_ASSERT(real_max > 100);
+
+ files_max_open_fds = real_max;
+ return true;
+}
+
+bool file_init(struct smbd_server_connection *sconn)
+{
+ bool ok;
+
+ ok = file_init_global();
+ if (!ok) {
+ return false;
+ }
+
+ sconn->real_max_open_files = files_max_open_fds;
+
+ return true;
+}
+
+/****************************************************************************
+ Close files open by a specified vuid.
+****************************************************************************/
+
+struct file_close_user_state {
+ uint64_t vuid;
+ bool fsp_left_behind;
+};
+
+static struct files_struct *file_close_user_fn(
+ struct files_struct *fsp,
+ void *private_data)
+{
+ struct file_close_user_state *state = private_data;
+ bool did_close;
+
+ if (fsp->vuid != state->vuid) {
+ return NULL;
+ }
+
+ did_close = close_file_in_loop(fsp, SHUTDOWN_CLOSE);
+ if (!did_close) {
+ state->fsp_left_behind = true;
+ }
+
+ return NULL;
+}
+
+void file_close_user(struct smbd_server_connection *sconn, uint64_t vuid)
+{
+ struct file_close_user_state state = { .vuid = vuid };
+
+ files_forall(sconn, file_close_user_fn, &state);
+
+ if (state.fsp_left_behind) {
+ state.fsp_left_behind = false;
+ files_forall(sconn, file_close_user_fn, &state);
+ SMB_ASSERT(!state.fsp_left_behind);
+ }
+}
+
+/*
+ * Walk the files table until "fn" returns non-NULL
+ */
+
+struct files_struct *files_forall(
+ struct smbd_server_connection *sconn,
+ struct files_struct *(*fn)(struct files_struct *fsp,
+ void *private_data),
+ void *private_data)
+{
+ struct files_struct *fsp, *next;
+
+ for (fsp = sconn->files; fsp; fsp = next) {
+ struct files_struct *ret;
+ next = fsp->next;
+ ret = fn(fsp, private_data);
+ if (ret != NULL) {
+ return ret;
+ }
+ }
+ return NULL;
+}
+
+/****************************************************************************
+ Find a fsp given a file descriptor.
+****************************************************************************/
+
+files_struct *file_find_fd(struct smbd_server_connection *sconn, int fd)
+{
+ int count=0;
+ files_struct *fsp;
+
+ for (fsp=sconn->files; fsp; fsp=fsp->next,count++) {
+ if (fsp_get_pathref_fd(fsp) == fd) {
+ if (count > 10) {
+ DLIST_PROMOTE(sconn->files, fsp);
+ }
+ return fsp;
+ }
+ }
+
+ return NULL;
+}
+
+/****************************************************************************
+ Find a fsp given a device, inode and file_id.
+****************************************************************************/
+
+files_struct *file_find_dif(struct smbd_server_connection *sconn,
+ struct file_id id, unsigned long gen_id)
+{
+ int count=0;
+ files_struct *fsp;
+
+ if (gen_id == 0) {
+ return NULL;
+ }
+
+ for (fsp = sconn->files; fsp; fsp = fsp->next,count++) {
+ /*
+ * We can have a fsp->fh->fd == -1 here as it could be a stat
+ * open.
+ */
+ if (!file_id_equal(&fsp->file_id, &id)) {
+ continue;
+ }
+ if (!fsp->fsp_flags.is_fsa) {
+ continue;
+ }
+ if (fh_get_gen_id(fsp->fh) != gen_id) {
+ continue;
+ }
+ if (count > 10) {
+ DLIST_PROMOTE(sconn->files, fsp);
+ }
+ return fsp;
+ }
+
+ return NULL;
+}
+
+/****************************************************************************
+ Find the first fsp given a device and inode.
+ We use a singleton cache here to speed up searching from getfilepathinfo
+ calls.
+****************************************************************************/
+
+files_struct *file_find_di_first(struct smbd_server_connection *sconn,
+ struct file_id id,
+ bool need_fsa)
+{
+ files_struct *fsp;
+
+ if (file_id_equal(&sconn->fsp_fi_cache.id, &id)) {
+ /* Positive or negative cache hit. */
+ return sconn->fsp_fi_cache.fsp;
+ }
+
+ sconn->fsp_fi_cache.id = id;
+
+ for (fsp=sconn->files;fsp;fsp=fsp->next) {
+ if (need_fsa && !fsp->fsp_flags.is_fsa) {
+ continue;
+ }
+ if (file_id_equal(&fsp->file_id, &id)) {
+ /* Setup positive cache. */
+ sconn->fsp_fi_cache.fsp = fsp;
+ return fsp;
+ }
+ }
+
+ /* Setup negative cache. */
+ sconn->fsp_fi_cache.fsp = NULL;
+ return NULL;
+}
+
+/****************************************************************************
+ Find the next fsp having the same device and inode.
+****************************************************************************/
+
+files_struct *file_find_di_next(files_struct *start_fsp,
+ bool need_fsa)
+{
+ files_struct *fsp;
+
+ for (fsp = start_fsp->next;fsp;fsp=fsp->next) {
+ if (need_fsa && !fsp->fsp_flags.is_fsa) {
+ continue;
+ }
+ if (file_id_equal(&fsp->file_id, &start_fsp->file_id)) {
+ return fsp;
+ }
+ }
+
+ return NULL;
+}
+
+struct files_struct *file_find_one_fsp_from_lease_key(
+ struct smbd_server_connection *sconn,
+ const struct smb2_lease_key *lease_key)
+{
+ struct files_struct *fsp;
+
+ for (fsp = sconn->files; fsp; fsp=fsp->next) {
+ if ((fsp->lease != NULL) &&
+ (fsp->lease->lease.lease_key.data[0] ==
+ lease_key->data[0]) &&
+ (fsp->lease->lease.lease_key.data[1] ==
+ lease_key->data[1])) {
+ return fsp;
+ }
+ }
+ return NULL;
+}
+
+/****************************************************************************
+ Find any fsp open with a pathname below that of an already open path.
+****************************************************************************/
+
+bool file_find_subpath(files_struct *dir_fsp)
+{
+ files_struct *fsp;
+ size_t dlen;
+ char *d_fullname = NULL;
+
+ d_fullname = talloc_asprintf(talloc_tos(), "%s/%s",
+ dir_fsp->conn->connectpath,
+ dir_fsp->fsp_name->base_name);
+
+ if (!d_fullname) {
+ return false;
+ }
+
+ dlen = strlen(d_fullname);
+
+ for (fsp=dir_fsp->conn->sconn->files; fsp; fsp=fsp->next) {
+ char *d1_fullname;
+
+ if (fsp == dir_fsp) {
+ continue;
+ }
+
+ d1_fullname = talloc_asprintf(talloc_tos(),
+ "%s/%s",
+ fsp->conn->connectpath,
+ fsp->fsp_name->base_name);
+
+ /*
+ * If the open file has a path that is a longer
+ * component, then it's a subpath.
+ */
+ if (strnequal(d_fullname, d1_fullname, dlen) &&
+ (d1_fullname[dlen] == '/')) {
+ TALLOC_FREE(d1_fullname);
+ TALLOC_FREE(d_fullname);
+ return true;
+ }
+ TALLOC_FREE(d1_fullname);
+ }
+
+ TALLOC_FREE(d_fullname);
+ return false;
+}
+
+/****************************************************************************
+ Free up a fsp.
+****************************************************************************/
+
+static void fsp_free(files_struct *fsp)
+{
+ struct smbd_server_connection *sconn = fsp->conn->sconn;
+
+ if (fsp == sconn->fsp_fi_cache.fsp) {
+ ZERO_STRUCT(sconn->fsp_fi_cache);
+ }
+
+ DLIST_REMOVE(sconn->files, fsp);
+ SMB_ASSERT(sconn->num_files > 0);
+ sconn->num_files--;
+
+ TALLOC_FREE(fsp->fake_file_handle);
+
+ if (fh_get_refcount(fsp->fh) == 1) {
+ TALLOC_FREE(fsp->fh);
+ } else {
+ size_t new_refcount = fh_get_refcount(fsp->fh) - 1;
+ fh_set_refcount(fsp->fh, new_refcount);
+ }
+
+ if (fsp->lease != NULL) {
+ if (fsp->lease->ref_count == 1) {
+ TALLOC_FREE(fsp->lease);
+ } else {
+ fsp->lease->ref_count--;
+ }
+ }
+
+ fsp->conn->num_files_open--;
+
+ if (fsp->fsp_name != NULL &&
+ fsp->fsp_name->fsp_link != NULL)
+ {
+ /*
+ * Free fsp_link of fsp->fsp_name. To do this in the correct
+ * talloc destructor order we have to do it here. The
+ * talloc_free() of the link should set the fsp pointer to NULL.
+ */
+ TALLOC_FREE(fsp->fsp_name->fsp_link);
+ SMB_ASSERT(fsp->fsp_name->fsp == NULL);
+ }
+
+ /* this is paranoia, just in case someone tries to reuse the
+ information */
+ ZERO_STRUCTP(fsp);
+
+ /* fsp->fsp_name is a talloc child and is free'd automatically. */
+ TALLOC_FREE(fsp);
+}
+
+/*
+ * Rundown of all smb-related sub-structures of an fsp
+ */
+void fsp_unbind_smb(struct smb_request *req, files_struct *fsp)
+{
+ if (fsp == fsp->conn->cwd_fsp) {
+ return;
+ }
+
+ if (fsp->notify) {
+ size_t len = fsp_fullbasepath(fsp, NULL, 0);
+ char fullpath[len+1];
+
+ fsp_fullbasepath(fsp, fullpath, sizeof(fullpath));
+
+ /*
+ * Avoid /. at the end of the path name. notify can't
+ * deal with it.
+ */
+ if (len > 1 && fullpath[len-1] == '.' &&
+ fullpath[len-2] == '/') {
+ fullpath[len-2] = '\0';
+ }
+
+ notify_remove(fsp->conn->sconn->notify_ctx, fsp, fullpath);
+ TALLOC_FREE(fsp->notify);
+ }
+
+ /* Ensure this event will never fire. */
+ TALLOC_FREE(fsp->update_write_time_event);
+
+ if (fsp->op != NULL) {
+ fsp->op->compat = NULL;
+ }
+ TALLOC_FREE(fsp->op);
+
+ if ((req != NULL) && (fsp == req->chain_fsp)) {
+ req->chain_fsp = NULL;
+ }
+
+ /*
+ * Clear all possible chained fsp
+ * pointers in the SMB2 request queue.
+ */
+ remove_smb2_chained_fsp(fsp);
+}
+
+void file_free(struct smb_request *req, files_struct *fsp)
+{
+ struct smbd_server_connection *sconn = fsp->conn->sconn;
+ uint64_t fnum = fsp->fnum;
+
+ fsp_unbind_smb(req, fsp);
+
+ /* Drop all remaining extensions. */
+ vfs_remove_all_fsp_extensions(fsp);
+
+ fsp_free(fsp);
+
+ DBG_INFO("freed files structure %"PRIu64" (%zu used)\n",
+ fnum,
+ sconn->num_files);
+}
+
+/****************************************************************************
+ Get an fsp from a packet given a 16 bit fnum.
+****************************************************************************/
+
+files_struct *file_fsp(struct smb_request *req, uint16_t fid)
+{
+ struct smbXsrv_open *op;
+ NTSTATUS status;
+ NTTIME now = 0;
+ files_struct *fsp;
+
+ if (req == NULL) {
+ /*
+ * We should never get here. req==NULL could in theory
+ * only happen from internal opens with a non-zero
+ * root_dir_fid. Internal opens just don't do that, at
+ * least they are not supposed to do so. And if they
+ * start to do so, they better fake up a smb_request
+ * from which we get the right smbd_server_conn. While
+ * this should never happen, let's return NULL here.
+ */
+ return NULL;
+ }
+
+ if (req->chain_fsp != NULL) {
+ if (req->chain_fsp->fsp_flags.closing) {
+ return NULL;
+ }
+ return req->chain_fsp;
+ }
+
+ if (req->xconn == NULL) {
+ return NULL;
+ }
+
+ now = timeval_to_nttime(&req->request_time);
+
+ status = smb1srv_open_lookup(req->xconn,
+ fid, now, &op);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NULL;
+ }
+
+ fsp = op->compat;
+ if (fsp == NULL) {
+ return NULL;
+ }
+
+ if (fsp->fsp_flags.closing) {
+ return NULL;
+ }
+
+ req->chain_fsp = fsp;
+ fsp->fsp_name->st.cached_dos_attributes = FILE_ATTRIBUTE_INVALID;
+ return fsp;
+}
+
+struct files_struct *file_fsp_get(struct smbd_smb2_request *smb2req,
+ uint64_t persistent_id,
+ uint64_t volatile_id)
+{
+ struct smbXsrv_open *op;
+ NTSTATUS status;
+ NTTIME now = 0;
+ struct files_struct *fsp;
+
+ now = timeval_to_nttime(&smb2req->request_time);
+
+ status = smb2srv_open_lookup(smb2req->xconn,
+ persistent_id, volatile_id,
+ now, &op);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NULL;
+ }
+
+ fsp = op->compat;
+ if (fsp == NULL) {
+ return NULL;
+ }
+
+ if (smb2req->tcon == NULL) {
+ return NULL;
+ }
+
+ if (smb2req->tcon->compat != fsp->conn) {
+ return NULL;
+ }
+
+ if (smb2req->session == NULL) {
+ return NULL;
+ }
+
+ if (smb2req->session->global->session_wire_id != fsp->vuid) {
+ return NULL;
+ }
+
+ if (fsp->fsp_flags.closing) {
+ return NULL;
+ }
+
+ fsp->fsp_name->st.cached_dos_attributes = FILE_ATTRIBUTE_INVALID;
+
+ return fsp;
+}
+
+struct files_struct *file_fsp_smb2(struct smbd_smb2_request *smb2req,
+ uint64_t persistent_id,
+ uint64_t volatile_id)
+{
+ struct files_struct *fsp;
+
+ if (smb2req->compat_chain_fsp != NULL) {
+ if (smb2req->compat_chain_fsp->fsp_flags.closing) {
+ return NULL;
+ }
+ smb2req->compat_chain_fsp->fsp_name->st.cached_dos_attributes =
+ FILE_ATTRIBUTE_INVALID;
+ return smb2req->compat_chain_fsp;
+ }
+
+ fsp = file_fsp_get(smb2req, persistent_id, volatile_id);
+ if (fsp == NULL) {
+ return NULL;
+ }
+
+ smb2req->compat_chain_fsp = fsp;
+ return fsp;
+}
+
+/****************************************************************************
+ Duplicate the file handle part for a DOS or FCB open.
+****************************************************************************/
+
+NTSTATUS dup_file_fsp(
+ files_struct *from,
+ uint32_t access_mask,
+ files_struct *to)
+{
+ size_t new_refcount;
+
+ /* this can never happen for print files */
+ SMB_ASSERT(from->print_file == NULL);
+
+ TALLOC_FREE(to->fh);
+
+ to->fh = from->fh;
+ new_refcount = fh_get_refcount(to->fh) + 1;
+ fh_set_refcount(to->fh, new_refcount);
+
+ to->file_id = from->file_id;
+ to->initial_allocation_size = from->initial_allocation_size;
+ to->file_pid = from->file_pid;
+ to->vuid = from->vuid;
+ to->open_time = from->open_time;
+ to->access_mask = access_mask;
+ to->oplock_type = from->oplock_type;
+ to->fsp_flags.can_lock = from->fsp_flags.can_lock;
+ to->fsp_flags.can_read = ((access_mask & FILE_READ_DATA) != 0);
+ to->fsp_flags.can_write =
+ CAN_WRITE(from->conn) &&
+ ((access_mask & (FILE_WRITE_DATA | FILE_APPEND_DATA)) != 0);
+ if (from->fsp_name->twrp != 0) {
+ to->fsp_flags.can_write = false;
+ }
+ to->fsp_flags.modified = from->fsp_flags.modified;
+ to->fsp_flags.is_directory = from->fsp_flags.is_directory;
+ to->fsp_flags.aio_write_behind = from->fsp_flags.aio_write_behind;
+ to->fsp_flags.is_fsa = from->fsp_flags.is_fsa;
+ to->fsp_flags.is_pathref = from->fsp_flags.is_pathref;
+ to->fsp_flags.have_proc_fds = from->fsp_flags.have_proc_fds;
+ to->fsp_flags.is_dirfsp = from->fsp_flags.is_dirfsp;
+
+ return fsp_set_smb_fname(to, from->fsp_name);
+}
+
+/**
+ * Return a jenkins hash of a pathname on a connection.
+ */
+
+NTSTATUS file_name_hash(connection_struct *conn,
+ const char *name, uint32_t *p_name_hash)
+{
+ char tmpbuf[PATH_MAX];
+ char *fullpath, *to_free;
+ ssize_t len;
+ TDB_DATA key;
+
+ /* Set the hash of the full pathname. */
+
+ if (name[0] == '/') {
+ strlcpy(tmpbuf, name, sizeof(tmpbuf));
+ fullpath = tmpbuf;
+ len = strlen(fullpath);
+ to_free = NULL;
+ } else {
+ len = full_path_tos(conn->connectpath,
+ name,
+ tmpbuf,
+ sizeof(tmpbuf),
+ &fullpath,
+ &to_free);
+ }
+ if (len == -1) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ key = (TDB_DATA) { .dptr = (uint8_t *)fullpath, .dsize = len+1 };
+ *p_name_hash = tdb_jenkins_hash(&key);
+
+ DEBUG(10,("file_name_hash: %s hash 0x%x\n",
+ fullpath,
+ (unsigned int)*p_name_hash ));
+
+ TALLOC_FREE(to_free);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fsp_attach_smb_fname(struct files_struct *fsp,
+ struct smb_filename **_smb_fname)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct smb_filename *smb_fname_new = talloc_move(fsp, _smb_fname);
+ const char *name_str = NULL;
+ uint32_t name_hash = 0;
+ NTSTATUS status;
+
+ name_str = smb_fname_str_dbg(smb_fname_new);
+ if (name_str == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = file_name_hash(fsp->conn,
+ name_str,
+ &name_hash);
+ TALLOC_FREE(frame);
+ name_str = NULL;
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = fsp_smb_fname_link(fsp,
+ &smb_fname_new->fsp_link,
+ &smb_fname_new->fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ fsp->name_hash = name_hash;
+ fsp->fsp_name = smb_fname_new;
+ fsp->fsp_name->st.cached_dos_attributes = FILE_ATTRIBUTE_INVALID;
+ *_smb_fname = NULL;
+ return NT_STATUS_OK;
+}
+
+/**
+ * The only way that the fsp->fsp_name field should ever be set.
+ */
+NTSTATUS fsp_set_smb_fname(struct files_struct *fsp,
+ const struct smb_filename *smb_fname_in)
+{
+ struct smb_filename *smb_fname_old = fsp->fsp_name;
+ struct smb_filename *smb_fname_new = NULL;
+ NTSTATUS status;
+
+ smb_fname_new = cp_smb_filename(fsp, smb_fname_in);
+ if (smb_fname_new == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = fsp_attach_smb_fname(fsp, &smb_fname_new);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(smb_fname_new);
+ return status;
+ }
+
+ if (smb_fname_old != NULL) {
+ smb_fname_fsp_unlink(smb_fname_old);
+ TALLOC_FREE(smb_fname_old);
+ }
+
+ return NT_STATUS_OK;
+}
+
+size_t fsp_fullbasepath(struct files_struct *fsp, char *buf, size_t buflen)
+{
+ int len = 0;
+
+ if ((buf == NULL) || (buflen == 0)) {
+ return strlen(fsp->conn->connectpath) + 1 +
+ strlen(fsp->fsp_name->base_name);
+ }
+
+ len = snprintf(buf, buflen, "%s/%s", fsp->conn->connectpath,
+ fsp->fsp_name->base_name);
+ SMB_ASSERT(len>0);
+
+ return len;
+}
+
+void fsp_set_base_fsp(struct files_struct *fsp, struct files_struct *base_fsp)
+{
+ SMB_ASSERT(fsp->stream_fsp == NULL);
+ if (base_fsp != NULL) {
+ SMB_ASSERT(base_fsp->base_fsp == NULL);
+ SMB_ASSERT(base_fsp->stream_fsp == NULL);
+ }
+
+ if (fsp->base_fsp != NULL) {
+ SMB_ASSERT(fsp->base_fsp->stream_fsp == fsp);
+ fsp->base_fsp->stream_fsp = NULL;
+ }
+
+ fsp->base_fsp = base_fsp;
+ if (fsp->base_fsp != NULL) {
+ fsp->base_fsp->stream_fsp = fsp;
+ }
+}
+
+bool fsp_is_alternate_stream(const struct files_struct *fsp)
+{
+ return (fsp->base_fsp != NULL);
+}
+
+struct files_struct *metadata_fsp(struct files_struct *fsp)
+{
+ if (fsp_is_alternate_stream(fsp)) {
+ return fsp->base_fsp;
+ }
+ return fsp;
+}
+
+static bool fsp_generic_ask_sharemode(struct files_struct *fsp)
+{
+ if (fsp == NULL) {
+ return false;
+ }
+
+ if (fsp->posix_flags & FSP_POSIX_FLAGS_PATHNAMES) {
+ /* Always use filesystem for UNIX mtime query. */
+ return false;
+ }
+
+ return true;
+}
+
+bool fsp_search_ask_sharemode(struct files_struct *fsp)
+{
+ if (!fsp_generic_ask_sharemode(fsp)) {
+ return false;
+ }
+
+ return lp_smbd_search_ask_sharemode(SNUM(fsp->conn));
+}
+
+bool fsp_getinfo_ask_sharemode(struct files_struct *fsp)
+{
+ if (!fsp_generic_ask_sharemode(fsp)) {
+ return false;
+ }
+
+ return lp_smbd_getinfo_ask_sharemode(SNUM(fsp->conn));
+}
diff --git a/source3/smbd/globals.c b/source3/smbd/globals.c
new file mode 100644
index 0000000..9989a73
--- /dev/null
+++ b/source3/smbd/globals.c
@@ -0,0 +1,123 @@
+/*
+ Unix SMB/Netbios implementation.
+ smbd globals
+ Copyright (C) Stefan Metzmacher 2009
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../lib/util/memcache.h"
+#include "messages.h"
+#include <tdb.h>
+
+#ifdef USE_DMAPI
+struct smbd_dmapi_context *dmapi_ctx = NULL;
+#endif
+
+const struct mangle_fns *mangle_fns = NULL;
+
+unsigned char *chartest = NULL;
+TDB_CONTEXT *tdb_mangled_cache = NULL;
+
+/*
+ this determines how many characters are used from the original filename
+ in the 8.3 mangled name. A larger value leads to a weaker hash and more collisions.
+ The largest possible value is 6.
+*/
+unsigned mangle_prefix = 0;
+
+bool logged_ioctl_message = false;
+
+time_t last_smb_conf_reload_time = 0;
+pid_t background_lpq_updater_pid = -1;
+
+/****************************************************************************
+ structure to hold a linked list of queued messages.
+ for processing.
+****************************************************************************/
+uint32_t common_flags2 = FLAGS2_LONG_PATH_COMPONENTS|FLAGS2_32_BIT_ERROR_CODES|FLAGS2_EXTENDED_ATTRIBUTES;
+
+struct smb_trans_enc_state *partial_srv_trans_enc_ctx = NULL;
+struct smb_trans_enc_state *srv_trans_enc_ctx = NULL;
+
+/* A stack of security contexts. We include the current context as being
+ the first one, so there is room for another MAX_SEC_CTX_DEPTH more. */
+struct sec_ctx sec_ctx_stack[MAX_SEC_CTX_DEPTH + 1];
+int sec_ctx_stack_ndx = 0;
+bool become_uid_done = false;
+bool become_gid_done = false;
+
+uint32_t global_client_caps = 0;
+
+uint16_t fnf_handle = 257;
+
+/* A stack of current_user connection contexts. */
+struct conn_ctx conn_ctx_stack[MAX_SEC_CTX_DEPTH];
+int conn_ctx_stack_ndx = 0;
+
+struct vfs_init_function_entry *backends = NULL;
+char *sparse_buf = NULL;
+char *LastDir = NULL;
+
+struct smbd_parent_context *am_parent = NULL;
+struct memcache *smbd_memcache_ctx = NULL;
+bool exit_firsttime = true;
+
+struct smbXsrv_client *global_smbXsrv_client = NULL;
+
+struct memcache *smbd_memcache(void)
+{
+ if (!smbd_memcache_ctx) {
+ /*
+ * Note we MUST use the NULL context here, not the
+ * autofree context, to avoid side effects in forked
+ * children exiting.
+ */
+ smbd_memcache_ctx = memcache_init(NULL,
+ lp_max_stat_cache_size()*1024);
+ }
+ if (!smbd_memcache_ctx) {
+ smb_panic("Could not init smbd memcache");
+ }
+
+ return smbd_memcache_ctx;
+}
+
+void smbd_init_globals(void)
+{
+ ZERO_STRUCT(conn_ctx_stack);
+
+ ZERO_STRUCT(sec_ctx_stack);
+}
+
+struct GUID smbd_request_guid(struct smb_request *smb1req, uint16_t idx)
+{
+ struct GUID v = {
+ .time_low = (uint32_t)smb1req->mid,
+ .time_hi_and_version = idx,
+ };
+
+ if (smb1req->smb2req != NULL) {
+ v.time_mid = (uint16_t)smb1req->smb2req->current_idx;
+ } else {
+ v.time_mid = (uint16_t)(uintptr_t)smb1req->vwv;
+ }
+
+ SBVAL((uint8_t *)&v, 8, smb1req->xconn->channel_id);
+
+ return v;
+}
diff --git a/source3/smbd/globals.h b/source3/smbd/globals.h
new file mode 100644
index 0000000..4928d1f
--- /dev/null
+++ b/source3/smbd/globals.h
@@ -0,0 +1,912 @@
+/*
+ Unix SMB/Netbios implementation.
+ smbd globals
+ Copyright (C) Stefan Metzmacher 2009
+ Copyright (C) Jeremy Allison 2010
+
+ 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/>.
+*/
+
+#ifndef _SOURCE3_SMBD_GLOBALS_H_
+#define _SOURCE3_SMBD_GLOBALS_H_
+
+#include "system/select.h"
+#include "librpc/gen_ndr/smbXsrv.h"
+#include "smbprofile.h"
+
+#ifdef USE_DMAPI
+struct smbd_dmapi_context;
+extern struct smbd_dmapi_context *dmapi_ctx;
+#endif
+
+/* A singleton cache to speed up searching by dev/inode. */
+struct fsp_singleton_cache {
+ files_struct *fsp;
+ struct file_id id;
+};
+
+extern const struct mangle_fns *mangle_fns;
+
+extern unsigned char *chartest;
+struct tdb_context;
+extern struct tdb_context *tdb_mangled_cache;
+
+/*
+ this determines how many characters are used from the original filename
+ in the 8.3 mangled name. A larger value leads to a weaker hash and more collisions.
+ The largest possible value is 6.
+*/
+extern unsigned mangle_prefix;
+
+struct msg_state;
+
+extern bool logged_ioctl_message;
+
+extern int trans_num;
+
+extern time_t last_smb_conf_reload_time;
+extern time_t last_printer_reload_time;
+extern pid_t background_lpq_updater_pid;
+
+/****************************************************************************
+ structure to hold a linked list of queued messages.
+ for processing.
+****************************************************************************/
+extern uint32_t common_flags2;
+
+extern struct smb_trans_enc_state *partial_srv_trans_enc_ctx;
+extern struct smb_trans_enc_state *srv_trans_enc_ctx;
+
+struct sec_ctx {
+ struct security_unix_token ut;
+ struct security_token *token;
+};
+/* A stack of security contexts. We include the current context as being
+ the first one, so there is room for another MAX_SEC_CTX_DEPTH more. */
+extern struct sec_ctx sec_ctx_stack[MAX_SEC_CTX_DEPTH + 1];
+extern int sec_ctx_stack_ndx;
+extern bool become_uid_done;
+extern bool become_gid_done;
+
+extern uint32_t global_client_caps;
+
+extern uint16_t fnf_handle;
+
+struct conn_ctx {
+ connection_struct *conn;
+ uint64_t vuid;
+ userdom_struct user_info;
+};
+/* A stack of current_user connection contexts. */
+extern struct conn_ctx conn_ctx_stack[MAX_SEC_CTX_DEPTH];
+extern int conn_ctx_stack_ndx;
+
+struct vfs_init_function_entry;
+extern struct vfs_init_function_entry *backends;
+extern char *sparse_buf;
+extern char *LastDir;
+
+struct smbd_parent_context;
+extern struct smbd_parent_context *am_parent;
+extern struct memcache *smbd_memcache_ctx;
+extern bool exit_firsttime;
+
+struct tstream_context;
+struct smbd_smb2_request;
+
+DATA_BLOB negprot_spnego(TALLOC_CTX *ctx, struct smbXsrv_connection *xconn);
+
+void smbd_lock_socket(struct smbXsrv_connection *xconn);
+void smbd_unlock_socket(struct smbXsrv_connection *xconn);
+
+struct GUID smbd_request_guid(struct smb_request *smb1req, uint16_t idx);
+
+NTSTATUS smbd_do_unlocking(struct smb_request *req,
+ files_struct *fsp,
+ uint16_t num_ulocks,
+ struct smbd_lock_element *ulocks);
+
+NTSTATUS smbd_do_qfilepathinfo(connection_struct *conn,
+ TALLOC_CTX *mem_ctx,
+ struct smb_request *req,
+ uint16_t info_level,
+ files_struct *fsp,
+ struct smb_filename *smb_fname,
+ bool delete_pending,
+ struct timespec write_time_ts,
+ struct ea_list *ea_list,
+ uint16_t flags2,
+ unsigned int max_data_bytes,
+ size_t *fixed_portion,
+ char **ppdata,
+ unsigned int *pdata_size);
+
+NTSTATUS smbd_do_setfsinfo(connection_struct *conn,
+ struct smb_request *req,
+ TALLOC_CTX *mem_ctx,
+ uint16_t info_level,
+ files_struct *fsp,
+ const DATA_BLOB *pdata);
+
+NTSTATUS smbd_do_setfilepathinfo(connection_struct *conn,
+ struct smb_request *req,
+ TALLOC_CTX *mem_ctx,
+ uint16_t info_level,
+ files_struct *fsp,
+ struct smb_filename *smb_fname,
+ char **ppdata, int total_data,
+ int *ret_data_size);
+
+NTSTATUS smbd_do_qfsinfo(struct smbXsrv_connection *xconn,
+ connection_struct *conn,
+ TALLOC_CTX *mem_ctx,
+ uint16_t info_level,
+ uint16_t flags2,
+ unsigned int max_data_bytes,
+ size_t *fixed_portion,
+ struct files_struct *fsp,
+ struct smb_filename *smb_fname,
+ char **ppdata,
+ int *ret_data_len);
+
+NTSTATUS smbd_dirptr_lanman2_entry(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct dptr_struct *dirptr,
+ uint16_t flags2,
+ const char *path_mask,
+ uint32_t dirtype,
+ int info_level,
+ int requires_resume_key,
+ bool dont_descend,
+ bool ask_sharemode,
+ bool get_dosmode,
+ uint8_t align,
+ bool do_pad,
+ char **ppdata,
+ char *base_data,
+ char *end_data,
+ int space_remaining,
+ struct smb_filename **smb_fname,
+ int *_last_entry_off,
+ struct ea_list *name_list,
+ struct file_id *file_id);
+
+NTSTATUS smbd_calculate_access_mask_fsp(struct files_struct *dirsfp,
+ struct files_struct *fsp,
+ bool use_privs,
+ uint32_t access_mask,
+ uint32_t *access_mask_out);
+
+void smbd_notify_cancel_by_smbreq(const struct smb_request *smbreq);
+
+void smbXsrv_connection_disconnect_transport(struct smbXsrv_connection *xconn,
+ NTSTATUS status);
+size_t smbXsrv_client_valid_connections(struct smbXsrv_client *client);
+void smbd_server_connection_terminate_ex(struct smbXsrv_connection *xconn,
+ const char *reason,
+ const char *location);
+#define smbd_server_connection_terminate(xconn, reason) \
+ smbd_server_connection_terminate_ex(xconn, reason, __location__)
+
+void smbd_server_disconnect_client_ex(struct smbXsrv_client *client,
+ const char *reason,
+ const char *location);
+#define smbd_server_disconnect_client(__client, __reason) \
+ smbd_server_disconnect_client_ex(__client, __reason, __location__)
+
+const char *smb2_opcode_name(uint16_t opcode);
+bool smbd_is_smb2_header(const uint8_t *inbuf, size_t size);
+bool smbd_smb2_is_compound(const struct smbd_smb2_request *req);
+bool smbd_smb2_is_last_in_compound(const struct smbd_smb2_request *req);
+
+NTSTATUS smbd_add_connection(struct smbXsrv_client *client, int sock_fd,
+ NTTIME now, struct smbXsrv_connection **_xconn);
+
+NTSTATUS reply_smb2002(struct smb_request *req, uint16_t choice);
+NTSTATUS reply_smb20ff(struct smb_request *req, uint16_t choice);
+NTSTATUS smbd_smb2_process_negprot(struct smbXsrv_connection *xconn,
+ uint64_t expected_seq_low,
+ const uint8_t *inpdu, size_t size);
+NTSTATUS smb2_multi_protocol_reply_negprot(struct smb_request *req);
+
+DATA_BLOB smbd_smb2_generate_outbody(struct smbd_smb2_request *req, size_t size);
+
+bool smbXsrv_server_multi_channel_enabled(void);
+
+NTSTATUS smbd_smb2_request_error_ex(struct smbd_smb2_request *req,
+ NTSTATUS status,
+ uint8_t error_context_count,
+ DATA_BLOB *info,
+ const char *location);
+#define smbd_smb2_request_error(req, status) \
+ smbd_smb2_request_error_ex(req, status, 0, NULL, __location__)
+NTSTATUS smbd_smb2_request_done_ex(struct smbd_smb2_request *req,
+ NTSTATUS status,
+ DATA_BLOB body, DATA_BLOB *dyn,
+ const char *location);
+#define smbd_smb2_request_done(req, body, dyn) \
+ smbd_smb2_request_done_ex(req, NT_STATUS_OK, body, dyn, __location__)
+
+NTSTATUS smbd_smb2_send_oplock_break(struct smbXsrv_client *client,
+ struct smbXsrv_open *op,
+ uint8_t oplock_level);
+NTSTATUS smbd_smb2_send_lease_break(struct smbXsrv_client *client,
+ uint16_t new_epoch,
+ uint32_t lease_flags,
+ struct smb2_lease_key *lease_key,
+ uint32_t current_lease_state,
+ uint32_t new_lease_state);
+
+NTSTATUS smbd_smb2_request_pending_queue(struct smbd_smb2_request *req,
+ struct tevent_req *subreq,
+ uint32_t defer_time);
+
+struct smb_request *smbd_smb2_fake_smb_request(struct smbd_smb2_request *req,
+ struct files_struct *fsp);
+size_t smbd_smb2_unread_bytes(struct smbd_smb2_request *req);
+void remove_smb2_chained_fsp(files_struct *fsp);
+
+NTSTATUS smbd_smb2_request_verify_creditcharge(struct smbd_smb2_request *req,
+ uint32_t data_length);
+
+NTSTATUS smbd_smb2_request_verify_sizes(struct smbd_smb2_request *req,
+ size_t expected_body_size);
+
+void smb2_request_set_async_internal(struct smbd_smb2_request *req,
+ bool async_internal);
+
+enum protocol_types smbd_smb2_protocol_dialect_match(const uint8_t *indyn,
+ const int dialect_count,
+ uint16_t *dialect);
+NTSTATUS smbd_smb2_request_process_negprot(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_sesssetup(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_logoff(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_tcon(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_tdis(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_create(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_close(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_flush(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_read(struct smbd_smb2_request *req);
+NTSTATUS smb2_read_complete(struct tevent_req *req, ssize_t nread, int err);
+NTSTATUS smbd_smb2_request_process_write(struct smbd_smb2_request *req);
+NTSTATUS smb2_write_complete(struct tevent_req *req, ssize_t nwritten, int err);
+NTSTATUS smb2_write_complete_nosync(struct tevent_req *req, ssize_t nwritten,
+ int err);
+NTSTATUS smbd_smb2_request_process_lock(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_ioctl(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_keepalive(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_query_directory(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_notify(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_getinfo(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_setinfo(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_break(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req);
+void smbd_smb2_request_dispatch_immediate(struct tevent_context *ctx,
+ struct tevent_immediate *im,
+ void *private_data);
+
+struct deferred_open_record;
+
+/* SMB1 -> SMB2 glue. */
+void send_break_message_smb2(files_struct *fsp,
+ uint32_t break_from,
+ uint32_t break_to);
+/* From smbd/smb2_create.c */
+int map_smb2_oplock_levels_to_samba(uint8_t in_oplock_level);
+bool get_deferred_open_message_state_smb2(struct smbd_smb2_request *smb2req,
+ struct timeval *p_request_time,
+ struct deferred_open_record **open_rec);
+bool open_was_deferred_smb2(
+ struct smbXsrv_connection *xconn, uint64_t mid);
+void remove_deferred_open_message_smb2(
+ struct smbXsrv_connection *xconn, uint64_t mid);
+bool schedule_deferred_open_message_smb2(
+ struct smbXsrv_connection *xconn, uint64_t mid);
+bool push_deferred_open_message_smb2(struct smbd_smb2_request *smb2req,
+ struct timeval request_time,
+ struct timeval timeout,
+ struct file_id id,
+ struct deferred_open_record *open_rec);
+
+struct smbXsrv_client;
+
+struct smbXsrv_preauth {
+ uint8_t sha512_value[64];
+};
+
+struct smbXsrv_connection {
+ struct smbXsrv_connection *prev, *next;
+
+ struct smbXsrv_client *client;
+
+ NTTIME connect_time;
+ uint64_t channel_id;
+ const struct tsocket_address *local_address;
+ const struct tsocket_address *remote_address;
+ const char *remote_hostname;
+ bool has_cluster_movable_ip;
+
+ enum protocol_types protocol;
+
+ struct {
+ NTSTATUS status;
+ bool terminating;
+ struct tevent_queue *shutdown_wait_queue;
+ int sock;
+ struct tevent_fd *fde;
+
+ struct {
+ bool got_session;
+ } nbt;
+ } transport;
+
+ struct {
+ bool force_unacked_timeout;
+ uint64_t unacked_bytes;
+ uint32_t rto_usecs;
+ struct tevent_req *checker_subreq;
+ struct smbd_smb2_send_queue *queue;
+ } ack;
+
+#if defined(WITH_SMB1SERVER)
+ struct {
+ struct {
+ /*
+ * fd for the fcntl lock and process shared
+ * robust mutex to coordinate access to the
+ * client socket. When the system supports
+ * process shared robust mutexes, those are
+ * used. If not, then the fcntl lock will be
+ * used.
+ */
+ int socket_lock_fd;
+#ifdef HAVE_ROBUST_MUTEXES
+ pthread_mutex_t *socket_mutex;
+#endif
+
+ /*
+ * fd for the trusted pipe from
+ * echo handler child
+ */
+ int trusted_fd;
+
+ /*
+ * fde for the trusted_fd
+ */
+ struct tevent_fd *trusted_fde;
+
+ /*
+ * Reference count for the fcntl lock to
+ * allow recursive locks.
+ */
+ int ref_count;
+ } echo_handler;
+
+ struct {
+ bool encrypted_passwords;
+ bool spnego;
+ struct auth4_context *auth_context;
+ bool done;
+ /*
+ * Size of the data we can receive. Set by us.
+ * Can be modified by the max xmit parameter.
+ */
+ int max_recv;
+ } negprot;
+
+ struct {
+ bool done_sesssetup;
+ /*
+ * Size of data we can send to client. Set
+ * by the client for all protocols above CORE.
+ * Set by us for CORE protocol.
+ */
+ int max_send;
+ } sessions;
+ struct smb1_signing_state *signing_state;
+
+ struct {
+ uint16_t client_major;
+ uint16_t client_minor;
+ uint32_t client_cap_low;
+ uint32_t client_cap_high;
+ } unix_info;
+
+ struct msg_state *msg_state;
+ } smb1;
+#endif
+ struct {
+ struct smbd_smb2_request_read_state {
+ struct smbd_smb2_request *req;
+ struct {
+ uint8_t nbt[NBT_HDR_SIZE];
+ } hdr;
+ struct iovec _vector[1];
+ struct iovec *vector;
+ int count;
+ struct msghdr msg;
+ bool doing_receivefile;
+ size_t min_recv_size;
+ size_t pktfull;
+ size_t pktlen;
+ uint8_t *pktbuf;
+ } request_read_state;
+ struct smbd_smb2_send_queue *send_queue;
+ size_t send_queue_len;
+
+ struct {
+ /*
+ * seq_low is the lowest sequence number
+ * we will accept.
+ */
+ uint64_t seq_low;
+ /*
+ * seq_range is the range of credits we have
+ * granted from the sequence windows starting
+ * at seq_low.
+ *
+ * This gets incremented when new credits are
+ * granted and gets decremented when the
+ * lowest sequence number is consumed
+ * (when seq_low gets incremented).
+ */
+ uint16_t seq_range;
+ /*
+ * The number of credits we have currently granted
+ * to the client.
+ *
+ * This gets incremented when new credits are
+ * granted and gets decremented when any credit
+ * is consumed.
+ *
+ * Note: the decrementing is different compared
+ * to seq_range.
+ */
+ uint16_t granted;
+ /*
+ * The maximum number of credits we will ever
+ * grant to the client.
+ *
+ * Typically we will only grant 1/16th of
+ * max_credits.
+ *
+ * This is the "server max credits" parameter.
+ */
+ uint16_t max;
+ /*
+ * a bitmap of size max_credits
+ */
+ struct bitmap *bitmap;
+ bool multicredit;
+ } credits;
+
+ bool allow_2ff;
+ struct {
+ uint32_t capabilities;
+ struct GUID guid;
+ bool guid_verified;
+ uint16_t security_mode;
+ uint16_t num_dialects;
+ uint16_t *dialects;
+ } client;
+ struct {
+ uint32_t capabilities;
+ struct GUID guid;
+ uint16_t security_mode;
+ uint16_t dialect;
+ uint32_t max_trans;
+ uint32_t max_read;
+ uint32_t max_write;
+ uint16_t sign_algo;
+ uint16_t cipher;
+ bool posix_extensions_negotiated;
+ } server;
+
+ struct smbXsrv_preauth preauth;
+
+ struct smbd_smb2_request *requests;
+
+ struct {
+ uint8_t read_body_padding;
+ } smbtorture;
+
+ bool signing_mandatory;
+ } smb2;
+};
+
+const char *smbXsrv_connection_dbg(const struct smbXsrv_connection *xconn);
+
+NTSTATUS smbXsrv_version_global_init(const struct server_id *server_id);
+uint32_t smbXsrv_version_global_current(void);
+
+struct smbXsrv_client_table;
+NTSTATUS smbXsrv_client_global_init(void);
+NTSTATUS smbXsrv_client_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ NTTIME now,
+ struct smbXsrv_client **_client);
+NTSTATUS smbXsrv_client_remove(struct smbXsrv_client *client);
+struct tevent_req *smb2srv_client_mc_negprot_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req);
+NTSTATUS smb2srv_client_mc_negprot_recv(struct tevent_req *req);
+
+NTSTATUS smbXsrv_connection_init_tables(struct smbXsrv_connection *conn,
+ enum protocol_types protocol);
+
+NTSTATUS smbXsrv_session_global_init(struct messaging_context *msg_ctx);
+NTSTATUS smbXsrv_session_create(struct smbXsrv_connection *conn,
+ NTTIME now,
+ struct smbXsrv_session **_session);
+NTSTATUS smbXsrv_session_add_channel(struct smbXsrv_session *session,
+ struct smbXsrv_connection *conn,
+ NTTIME now,
+ struct smbXsrv_channel_global0 **_c);
+NTSTATUS smbXsrv_session_remove_channel(struct smbXsrv_session *session,
+ struct smbXsrv_connection *xconn);
+NTSTATUS smbXsrv_session_disconnect_xconn(struct smbXsrv_connection *xconn);
+NTSTATUS smbXsrv_session_update(struct smbXsrv_session *session);
+struct smbXsrv_channel_global0;
+NTSTATUS smbXsrv_session_find_channel(const struct smbXsrv_session *session,
+ const struct smbXsrv_connection *conn,
+ struct smbXsrv_channel_global0 **_c);
+NTSTATUS smbXsrv_session_find_auth(const struct smbXsrv_session *session,
+ const struct smbXsrv_connection *conn,
+ NTTIME now,
+ struct smbXsrv_session_auth0 **_a);
+NTSTATUS smbXsrv_session_create_auth(struct smbXsrv_session *session,
+ struct smbXsrv_connection *conn,
+ NTTIME now,
+ uint8_t in_flags,
+ uint8_t in_security_mode,
+ struct smbXsrv_session_auth0 **_a);
+struct tevent_req *smb2srv_session_shutdown_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbXsrv_session *session,
+ struct smbd_smb2_request *current_req);
+NTSTATUS smb2srv_session_shutdown_recv(struct tevent_req *req);
+NTSTATUS smbXsrv_session_logoff(struct smbXsrv_session *session);
+NTSTATUS smbXsrv_session_logoff_all(struct smbXsrv_client *client);
+NTSTATUS smb1srv_session_table_init(struct smbXsrv_connection *conn);
+NTSTATUS smb1srv_session_lookup(struct smbXsrv_connection *conn,
+ uint16_t vuid, NTTIME now,
+ struct smbXsrv_session **session);
+NTSTATUS smbXsrv_session_info_lookup(struct smbXsrv_client *client,
+ uint64_t session_wire_id,
+ struct auth_session_info **si);
+NTSTATUS smb2srv_session_table_init(struct smbXsrv_connection *conn);
+NTSTATUS smb2srv_session_lookup_conn(struct smbXsrv_connection *conn,
+ uint64_t session_id, NTTIME now,
+ struct smbXsrv_session **session);
+NTSTATUS smb2srv_session_lookup_client(struct smbXsrv_client *client,
+ uint64_t session_id, NTTIME now,
+ struct smbXsrv_session **session);
+NTSTATUS smb2srv_session_lookup_global(struct smbXsrv_client *client,
+ uint64_t session_wire_id,
+ TALLOC_CTX *mem_ctx,
+ struct smbXsrv_session **session);
+NTSTATUS get_valid_smbXsrv_session(struct smbXsrv_client *client,
+ uint64_t session_wire_id,
+ struct smbXsrv_session **session);
+NTSTATUS smbXsrv_session_local_traverse(
+ struct smbXsrv_client *client,
+ int (*caller_cb)(struct smbXsrv_session *session,
+ void *caller_data),
+ void *caller_data);
+struct smbXsrv_session_global0;
+NTSTATUS smbXsrv_session_global_traverse(
+ int (*fn)(struct smbXsrv_session_global0 *, void *),
+ void *private_data);
+struct tevent_req *smb2srv_session_close_previous_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbXsrv_connection *conn,
+ struct auth_session_info *session_info,
+ uint64_t previous_session_id,
+ uint64_t current_session_id);
+NTSTATUS smb2srv_session_close_previous_recv(struct tevent_req *req);
+
+NTSTATUS smbXsrv_tcon_global_init(void);
+NTSTATUS smbXsrv_tcon_update(struct smbXsrv_tcon *tcon);
+NTSTATUS smbXsrv_tcon_disconnect(struct smbXsrv_tcon *tcon, uint64_t vuid);
+NTSTATUS smb1srv_tcon_table_init(struct smbXsrv_connection *conn);
+NTSTATUS smb1srv_tcon_create(struct smbXsrv_connection *conn,
+ uint32_t session_global_id,
+ const char *share_name,
+ NTTIME now,
+ struct smbXsrv_tcon **_tcon);
+NTSTATUS smb1srv_tcon_lookup(struct smbXsrv_connection *conn,
+ uint16_t tree_id, NTTIME now,
+ struct smbXsrv_tcon **tcon);
+NTSTATUS smb1srv_tcon_disconnect_all(struct smbXsrv_client *client);
+NTSTATUS smb2srv_tcon_table_init(struct smbXsrv_session *session);
+NTSTATUS smb2srv_tcon_create(struct smbXsrv_session *session,
+ uint32_t session_global_id,
+ uint8_t encryption_flags,
+ const char *share_name,
+ NTTIME now,
+ struct smbXsrv_tcon **_tcon);
+NTSTATUS smb2srv_tcon_lookup(struct smbXsrv_session *session,
+ uint32_t tree_id, NTTIME now,
+ struct smbXsrv_tcon **tcon);
+NTSTATUS smb2srv_tcon_disconnect_all(struct smbXsrv_session *session);
+struct smbXsrv_tcon_global0;
+NTSTATUS smbXsrv_tcon_global_traverse(
+ int (*fn)(struct smbXsrv_tcon_global0 *, void *),
+ void *private_data);
+
+
+bool smbXsrv_is_encrypted(uint8_t encryption_flags);
+bool smbXsrv_is_partially_encrypted(uint8_t encryption_flags);
+bool smbXsrv_set_crypto_flag(uint8_t *flags, uint8_t flag);
+bool smbXsrv_is_signed(uint8_t signing_flags);
+bool smbXsrv_is_partially_signed(uint8_t signing_flags);
+
+struct smbd_smb2_send_queue {
+ struct smbd_smb2_send_queue *prev, *next;
+
+ DATA_BLOB *sendfile_header;
+ uint32_t sendfile_body_size;
+ NTSTATUS *sendfile_status;
+
+ struct msghdr msg;
+ struct iovec *vector;
+ int count;
+
+ struct {
+ struct tevent_req *req;
+ struct timeval timeout;
+ uint64_t required_acked_bytes;
+ } ack;
+
+ TALLOC_CTX *mem_ctx;
+};
+
+struct smbd_smb2_request {
+ struct smbd_smb2_request *prev, *next;
+
+ struct smbd_server_connection *sconn;
+ struct smbXsrv_connection *xconn;
+
+ struct smbd_smb2_send_queue queue_entry;
+
+ /* the session the request operates on, maybe NULL */
+ struct smbXsrv_session *session;
+ uint64_t last_session_id;
+
+ /* the tcon the request operates on, maybe NULL */
+ struct smbXsrv_tcon *tcon;
+ uint32_t last_tid;
+
+ int current_idx;
+ bool do_signing;
+ /* Was the request encrypted? */
+ bool was_encrypted;
+ /* Should we encrypt? */
+ bool do_encryption;
+ struct tevent_timer *async_te;
+ bool compound_related;
+ NTSTATUS compound_create_err;
+
+ /*
+ * Give the implementation of an SMB2 req a way to tell the SMB2 request
+ * processing engine that the internal request is going async, while
+ * preserving synchronous SMB2 behaviour.
+ */
+ bool async_internal;
+
+ /*
+ * the encryption key for the whole
+ * compound chain
+ */
+ struct smb2_signing_key *first_enc_key;
+ /*
+ * the signing key for the last
+ * request/response of a compound chain
+ */
+ struct smb2_signing_key *last_sign_key;
+ struct smbXsrv_preauth *preauth;
+
+ struct timeval request_time;
+
+ SMBPROFILE_IOBYTES_ASYNC_STATE(profile);
+
+ /* fake smb1 request. */
+ struct smb_request *smb1req;
+ struct files_struct *compat_chain_fsp;
+
+ /*
+ * Keep track of whether the outstanding request counters
+ * had been updated in dispatch, so that they need to be
+ * adapted again in reply.
+ */
+ bool request_counters_updated;
+ uint64_t channel_generation;
+
+ /*
+ * The sub request for async backend calls.
+ * This is used for SMB2 Cancel.
+ */
+ struct tevent_req *subreq;
+
+#define SMBD_SMB2_TF_IOV_OFS 0
+#define SMBD_SMB2_HDR_IOV_OFS 1
+#define SMBD_SMB2_BODY_IOV_OFS 2
+#define SMBD_SMB2_DYN_IOV_OFS 3
+
+#define SMBD_SMB2_NUM_IOV_PER_REQ 4
+
+#define SMBD_SMB2_IOV_IDX_OFS(req,dir,idx,ofs) \
+ (&req->dir.vector[(idx)+(ofs)])
+
+#define SMBD_SMB2_IDX_TF_IOV(req,dir,idx) \
+ SMBD_SMB2_IOV_IDX_OFS(req,dir,idx,SMBD_SMB2_TF_IOV_OFS)
+#define SMBD_SMB2_IDX_HDR_IOV(req,dir,idx) \
+ SMBD_SMB2_IOV_IDX_OFS(req,dir,idx,SMBD_SMB2_HDR_IOV_OFS)
+#define SMBD_SMB2_IDX_BODY_IOV(req,dir,idx) \
+ SMBD_SMB2_IOV_IDX_OFS(req,dir,idx,SMBD_SMB2_BODY_IOV_OFS)
+#define SMBD_SMB2_IDX_DYN_IOV(req,dir,idx) \
+ SMBD_SMB2_IOV_IDX_OFS(req,dir,idx,SMBD_SMB2_DYN_IOV_OFS)
+
+#define SMBD_SMB2_IN_TF_IOV(req) SMBD_SMB2_IDX_TF_IOV(req,in,req->current_idx)
+#define SMBD_SMB2_IN_TF_PTR(req) (uint8_t *)(SMBD_SMB2_IN_TF_IOV(req)->iov_base)
+#define SMBD_SMB2_IN_HDR_IOV(req) SMBD_SMB2_IDX_HDR_IOV(req,in,req->current_idx)
+#define SMBD_SMB2_IN_HDR_PTR(req) (uint8_t *)(SMBD_SMB2_IN_HDR_IOV(req)->iov_base)
+#define SMBD_SMB2_IN_BODY_IOV(req) SMBD_SMB2_IDX_BODY_IOV(req,in,req->current_idx)
+#define SMBD_SMB2_IN_BODY_PTR(req) (uint8_t *)(SMBD_SMB2_IN_BODY_IOV(req)->iov_base)
+#define SMBD_SMB2_IN_BODY_LEN(req) (SMBD_SMB2_IN_BODY_IOV(req)->iov_len)
+#define SMBD_SMB2_IN_DYN_IOV(req) SMBD_SMB2_IDX_DYN_IOV(req,in,req->current_idx)
+#define SMBD_SMB2_IN_DYN_PTR(req) (uint8_t *)(SMBD_SMB2_IN_DYN_IOV(req)->iov_base)
+#define SMBD_SMB2_IN_DYN_LEN(req) (SMBD_SMB2_IN_DYN_IOV(req)->iov_len)
+
+#define SMBD_SMB2_OUT_TF_IOV(req) SMBD_SMB2_IDX_TF_IOV(req,out,req->current_idx)
+#define SMBD_SMB2_OUT_TF_PTR(req) (uint8_t *)(SMBD_SMB2_OUT_TF_IOV(req)->iov_base)
+#define SMBD_SMB2_OUT_HDR_IOV(req) SMBD_SMB2_IDX_HDR_IOV(req,out,req->current_idx)
+#define SMBD_SMB2_OUT_HDR_PTR(req) (uint8_t *)(SMBD_SMB2_OUT_HDR_IOV(req)->iov_base)
+#define SMBD_SMB2_OUT_BODY_IOV(req) SMBD_SMB2_IDX_BODY_IOV(req,out,req->current_idx)
+#define SMBD_SMB2_OUT_BODY_PTR(req) (uint8_t *)(SMBD_SMB2_OUT_BODY_IOV(req)->iov_base)
+#define SMBD_SMB2_OUT_BODY_LEN(req) (SMBD_SMB2_OUT_BODY_IOV(req)->iov_len)
+#define SMBD_SMB2_OUT_DYN_IOV(req) SMBD_SMB2_IDX_DYN_IOV(req,out,req->current_idx)
+#define SMBD_SMB2_OUT_DYN_PTR(req) (uint8_t *)(SMBD_SMB2_OUT_DYN_IOV(req)->iov_base)
+#define SMBD_SMB2_OUT_DYN_LEN(req) (SMBD_SMB2_OUT_DYN_IOV(req)->iov_len)
+
+#define SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN (SMB2_HDR_BODY + 0x30)
+
+ struct {
+ /*
+ * vector[0] TRANSPORT HEADER (empty)
+ * .
+ * vector[1] SMB2_TRANSFORM (optional)
+ * vector[2] SMB2
+ * vector[3] fixed body
+ * vector[4] dynamic body
+ * .
+ * .
+ * .
+ * vector[5] SMB2_TRANSFORM (optional)
+ * vector[6] SMB2
+ * vector[7] fixed body
+ * vector[8] dynamic body
+ * .
+ * .
+ * .
+ */
+ struct iovec *vector;
+ int vector_count;
+ struct iovec _vector[1 + SMBD_SMB2_NUM_IOV_PER_REQ];
+ } in;
+ struct {
+ /* the NBT header is not allocated */
+ uint8_t nbt_hdr[4];
+ /*
+ * vector[0] TRANSPORT HEADER
+ * .
+ * vector[1] SMB2_TRANSFORM (optional)
+ * vector[2] SMB2
+ * vector[3] fixed body
+ * vector[4] dynamic body
+ * .
+ * .
+ * .
+ * vector[5] SMB2_TRANSFORM (empty)
+ * vector[6] SMB2
+ * vector[7] fixed body
+ * vector[8] dynamic body
+ * .
+ * .
+ * .
+ */
+ struct iovec *vector;
+ int vector_count;
+ struct iovec _vector[1 + SMBD_SMB2_NUM_IOV_PER_REQ];
+#define OUTVEC_ALLOC_SIZE (SMB2_HDR_BODY + 9)
+ uint8_t _hdr[OUTVEC_ALLOC_SIZE];
+ uint8_t _body[0x58];
+ } out;
+};
+
+struct smbd_server_connection;
+
+struct pending_message_list;
+struct pending_auth_data;
+
+struct pthreadpool_tevent;
+struct dcesrv_context;
+
+struct smbd_server_connection {
+ const struct tsocket_address *local_address;
+ const struct tsocket_address *remote_address;
+ const char *remote_hostname;
+ struct tevent_context *ev_ctx;
+ struct messaging_context *msg_ctx;
+ struct dcesrv_context *dce_ctx;
+ struct notify_context *notify_ctx;
+ bool using_smb2;
+ int trans_num;
+
+ size_t num_users;
+
+ size_t num_connections;
+ struct connection_struct *connections;
+
+ size_t num_files;
+ struct files_struct *files;
+
+ int real_max_open_files;
+ struct fsp_singleton_cache fsp_fi_cache;
+
+ struct pending_message_list *deferred_open_queue;
+
+
+ /* open directory handles. */
+ struct {
+ struct bitmap *dptr_bmap;
+ struct dptr_struct *dirptrs;
+ } searches;
+
+ uint64_t num_requests;
+
+ /* Current number of oplocks we have outstanding. */
+ struct {
+ int32_t exclusive_open;
+ int32_t level_II_open;
+ struct kernel_oplocks *kernel_ops;
+ } oplocks;
+
+ struct notify_mid_map *notify_mid_maps;
+
+ struct pthreadpool_tevent *pool;
+
+ struct smbXsrv_client *client;
+};
+
+extern struct smbXsrv_client *global_smbXsrv_client;
+
+void smbd_init_globals(void);
+
+/****************************************************************************
+ The buffer we keep around whilst an aio request is in process.
+*****************************************************************************/
+
+struct aio_extra {
+ files_struct *fsp;
+ struct smb_request *smbreq;
+ DATA_BLOB outbuf;
+ struct lock_struct lock;
+ size_t nbyte;
+ off_t offset;
+ bool write_through;
+};
+
+#endif /* _SOURCE3_SMBD_GLOBALS_H_ */
diff --git a/source3/smbd/mangle.c b/source3/smbd/mangle.c
new file mode 100644
index 0000000..dbb187a
--- /dev/null
+++ b/source3/smbd/mangle.c
@@ -0,0 +1,153 @@
+/*
+ Unix SMB/CIFS implementation.
+ Name mangling interface
+ Copyright (C) Andrew Tridgell 2002
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "mangle.h"
+
+/* this allows us to add more mangling backends */
+static const struct {
+ const char *name;
+ const struct mangle_fns *(*init_fn)(void);
+} mangle_backends[] = {
+ { "hash", mangle_hash_init },
+ { "hash2", mangle_hash2_init },
+ { "posix", posix_mangle_init },
+ /*{ "tdb", mangle_tdb_init }, */
+ { NULL, NULL }
+};
+
+/*
+ initialise the mangling subsystem
+*/
+static void mangle_init(void)
+{
+ int i;
+ const char *method;
+
+ if (mangle_fns)
+ return;
+
+ method = lp_mangling_method();
+
+ /* find the first mangling method that manages to initialise and
+ matches the "mangling method" parameter */
+ for (i=0; mangle_backends[i].name && !mangle_fns; i++) {
+ if (!method || !*method || strcmp(method, mangle_backends[i].name) == 0) {
+ mangle_fns = mangle_backends[i].init_fn();
+ }
+ }
+
+ if (!mangle_fns) {
+ DEBUG(0,("Failed to initialise mangling system '%s'\n", method));
+ exit_server("mangling init failed");
+ }
+}
+
+
+/*
+ reset the cache. This is called when smb.conf has been reloaded
+*/
+void mangle_reset_cache(void)
+{
+ mangle_init();
+ mangle_fns->reset();
+}
+
+void mangle_change_to_posix(void)
+{
+ mangle_fns = NULL;
+ lp_set_mangling_method("posix");
+ mangle_reset_cache();
+}
+
+/*
+ see if a filename has come out of our mangling code
+*/
+bool mangle_is_mangled(const char *s, const struct share_params *p)
+{
+ return mangle_fns->is_mangled(s, p);
+}
+
+/*
+ see if a filename matches the rules of a 8.3 filename
+*/
+bool mangle_is_8_3(const char *fname, bool check_case,
+ const struct share_params *p)
+{
+ return mangle_fns->is_8_3(fname, check_case, False, p);
+}
+
+bool mangle_is_8_3_wildcards(const char *fname, bool check_case,
+ const struct share_params *p)
+{
+ return mangle_fns->is_8_3(fname, check_case, True, p);
+}
+
+bool mangle_must_mangle(const char *fname,
+ const struct share_params *p)
+{
+ if (lp_mangled_names(p) == MANGLED_NAMES_NO) {
+ return False;
+ }
+ return mangle_fns->must_mangle(fname, p);
+}
+
+/*
+ try to reverse map a 8.3 name to the original filename. This doesn't have to
+ always succeed, as the directory handling code in smbd will scan the directory
+ looking for a matching name if it doesn't. It should succeed most of the time
+ or there will be a huge performance penalty
+*/
+bool mangle_lookup_name_from_8_3(TALLOC_CTX *ctx,
+ const char *in,
+ char **out, /* talloced on the given context. */
+ const struct share_params *p)
+{
+ return mangle_fns->lookup_name_from_8_3(ctx, in, out, p);
+}
+
+/*
+ mangle a long filename to a 8.3 name.
+ Return True if we did mangle the name (ie. out is filled in).
+ False on error.
+ JRA.
+ */
+
+bool name_to_8_3(const char *in,
+ char out[13],
+ bool cache83,
+ const struct share_params *p)
+{
+ memset(out,'\0',13);
+
+ /* name mangling can be disabled for speed, in which case
+ we just truncate the string */
+ if (lp_mangled_names(p) == MANGLED_NAMES_NO) {
+ strlcpy(out, in, 13);
+ return True;
+ }
+
+ return mangle_fns->name_to_8_3(in,
+ out,
+ cache83,
+ lp_default_case(p->service),
+ p);
+}
diff --git a/source3/smbd/mangle_hash.c b/source3/smbd/mangle_hash.c
new file mode 100644
index 0000000..6a3cb33
--- /dev/null
+++ b/source3/smbd/mangle_hash.c
@@ -0,0 +1,1132 @@
+/*
+ Unix SMB/CIFS implementation.
+ Name mangling
+ Copyright (C) Andrew Tridgell 1992-2002
+ Copyright (C) Simo Sorce 2001
+ Copyright (C) Andrew Bartlett 2002
+ Copyright (C) Jeremy Allison 2007
+
+ 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 "system/filesys.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "mangle.h"
+#include "util_tdb.h"
+#include "lib/param/loadparm.h"
+
+/* -------------------------------------------------------------------------- **
+ * Other stuff...
+ *
+ * magic_char - This is the magic char used for mangling. It's
+ * global. There is a call to lp_mangling_char() in server.c
+ * that is used to override the initial value.
+ *
+ * MANGLE_BASE - This is the number of characters we use for name mangling.
+ *
+ * basechars - The set characters used for name mangling. This
+ * is static (scope is this file only).
+ *
+ * mangle() - Macro used to select a character from basechars (i.e.,
+ * mangle(n) will return the nth digit, modulo MANGLE_BASE).
+ *
+ * chartest - array 0..255. The index range is the set of all possible
+ * values of a byte. For each byte value, the content is a
+ * two nibble pair. See BASECHAR_MASK below.
+ *
+ * ct_initialized - False until the chartest array has been initialized via
+ * a call to init_chartest().
+ *
+ * BASECHAR_MASK - Masks the upper nibble of a one-byte value.
+ *
+ * isbasecahr() - Given a character, check the chartest array to see
+ * if that character is in the basechars set. This is
+ * faster than using strchr_m().
+ *
+ */
+
+static const char basechars[43]="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_-!@#$%";
+#define MANGLE_BASE (sizeof(basechars)/sizeof(char)-1)
+
+#define mangle(V) ((char)(basechars[(V) % MANGLE_BASE]))
+#define BASECHAR_MASK 0xf0
+#define isbasechar(C) ( (chartest[ ((C) & 0xff) ]) & BASECHAR_MASK )
+
+/* -------------------------------------------------------------------- */
+
+
+/*******************************************************************
+ Determine if a character is valid in a 8.3 name.
+********************************************************************/
+
+static const uint32_t valid_table[] = {
+ 0x00000000,0x2fff7bfa,0xefffffff,0xefffffff,0x00000001,0x0fffffee,
+ 0xffffffff,0xffffffff,0x00000000,0x00000000,0x00000000,0x01000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0xfffe0000,0xfffe03fb,
+ 0x000003ff,0x00000000,0xffff0002,0xffffffff,0x0002ffff,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x33210000,0x080d0063,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00400008,0x00000802,0x00000000,0x03ff03ff,0x000f0000,0x00000000,
+ 0x00140000,0x00000000,0xe402098d,0x20305fa1,0x00040000,0x00000cc3,
+ 0x000000cc,0x80000020,0x00000000,0x00000000,0x00040000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x000fffff,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x3999900f,0x99999939,0x00000804,0x00000000,
+ 0x00000000,0x300c0003,0x0000c8c0,0x00008000,0x00000060,0x00000000,
+ 0x00000005,0x0000a400,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0xa03fffef,0x00000000,0xfffffffe,0xffffffff,0x781fffff,0xfffffffe,
+ 0xffffffff,0x787fffff,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x02060000,
+ 0x00000000,0x00000000,0x00000000,0x000001f0,0x00000000,0x00000000,
+ 0x01102008,0x084008cc,0x00822600,0x78000000,0x7000c000,0x00000002,
+ 0x00002010,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x43f36f8b,0x9b462542,0xe3e0e82c,0x400a0004,0xdb365f65,0x04497977,
+ 0xe3f0ecd7,0x18c5603a,0x3403e60b,0x37518000,0x7eebe0c8,0x98698200,
+ 0x2d56ad48,0x8060e803,0xad93661c,0xc568c03a,0xc656aa60,0x02403f7e,
+ 0x146183cd,0x21751020,0x07122021,0x40bc3000,0x4562a624,0x0a3060a8,
+ 0x85740217,0x9c840402,0x14157ffb,0x11e27f34,0x22efb665,0x60ff1f75,
+ 0x38403a70,0x676336c3,0x20b24dd9,0x0fc946b0,0x4850bc98,0xa03f8638,
+ 0x98162388,0x5232be49,0xeba422ab,0xc72c00dd,0x26e1a1e7,0x8f0a841b,
+ 0x559e27eb,0x89bfc241,0x85480014,0x084d6361,0xaad07f0c,0x05cfff3e,
+ 0xa803ff1a,0x7b407a41,0x80024745,0x38eb0500,0x1005dc51,0x710c9b34,
+ 0x01000397,0xa4046366,0x005180d0,0x430ac000,0x30c89071,0x58000008,
+ 0xf7000ed9,0x00415f80,0x941000b0,0x62800018,0x09d00240,0x01568200,
+ 0x08015004,0x05101d10,0x001084c1,0x10504025,0x4d8a410f,0xa60d4009,
+ 0x914cab19,0x098121c0,0x0203c485,0x80000672,0x00080b04,0x0009141d,
+ 0x905c49c9,0x16900009,0x22200c65,0x24338412,0x47960c03,0x42250a04,
+ 0xd0880028,0x4f0c4900,0xd3aa14a2,0x3e87d830,0x1f618e04,0x41867ea4,
+ 0x2dbbc390,0x211857ad,0x2a48241e,0x4e041138,0x161b0a40,0x88400d60,
+ 0x9502020a,0x10608221,0x04000243,0x80001444,0x0c040000,0x70000000,
+ 0x00c11a06,0x0c00024a,0x00401a00,0x40451404,0xbdf30029,0x052b0a78,
+ 0xbfa0bba9,0x8379407c,0xe91d12fd,0xc5695bf6,0x444aeff6,0xff022115,
+ 0x402bed63,0x0242d033,0x00131000,0x5dca1b42,0x020000a0,0x2c61a703,
+ 0x8ff24880,0x00000284,0x100d5804,0x0048b200,0x20011894,0x37805004,
+ 0x684d3200,0x68be49ea,0x2e42184c,0x21c9a820,0x80b050b9,0xff7c001e,
+ 0x14e0849a,0x01e028c1,0xac49870e,0xdddb130f,0x89fbbe1a,0x51b2a2e2,
+ 0x32ca5522,0x928b3ec6,0x438f1dbf,0x32986703,0x73c03028,0xa9230811,
+ 0x3a65c000,0x04028fe3,0xa6252c4e,0x00a1bf3d,0x8cd43e3a,0x317c06c9,
+ 0xd52a00e0,0x0edf018b,0x8c22e34b,0xf0911183,0xa7287d94,0x40fbc9ac,
+ 0x07534484,0x44445a90,0x00013fc8,0xf5d40048,0xec5f7701,0x891dc442,
+ 0x49286b83,0xd2424109,0x59fe061d,0x3a221840,0x3b9fb7e4,0xc0eaf003,
+ 0x82021386,0xe4008980,0x10a1b200,0x0cc44b80,0x8944d309,0x48341faf,
+ 0x0c458259,0x0470420a,0x10c8a040,0x44503140,0x01004004,0x05408281,
+ 0x642c0108,0x1a056a30,0x051460a6,0x645690cf,0x31000021,0xcbf09c18,
+ 0x63e2e120,0x01b5104c,0x9a83538c,0x3281b8b2,0x0a84987a,0x0c0233e7,
+ 0xd038d6cd,0x9872e1b1,0xe2848a1e,0x0459c3f4,0x23c2439a,0xd3144845,
+ 0x36400292,0xffbd0241,0xe8f0eb09,0xa5d27dc0,0xd24bc242,0xd0afa47f,
+ 0x34a11aa0,0x0bd88247,0x651bc453,0xc83ad294,0x40c8001e,0x33140e06,
+ 0xb21f615f,0xc0d00088,0xa898a02a,0x166ba1c5,0x85b4af50,0x0604c08b,
+ 0x1e04f933,0xa251056e,0x76380400,0x73b8ed07,0x19324406,0xc8164081,
+ 0x63097c8a,0xaa042984,0xca9c1c24,0x27614e0e,0x830009d0,0xc10c0846,
+ 0x10816011,0x0908540d,0xcc0a000e,0x0c000514,0xa0440430,0x6784008b,
+ 0x8a195288,0x8b18865e,0x41602e59,0x9cbe8c10,0x895c6861,0x00089800,
+ 0x089a8100,0xc1900018,0xf4a14007,0x640d8505,0x0e4d314e,0xff0a4806,
+ 0x2ea81632,0x000b852e,0xca841810,0x696c0e20,0x16000032,0x0390d658,
+ 0x1a6851a0,0x11249000,0x432698e1,0x1fae5d52,0xae280fa0,0x5700fafb,
+ 0x99406408,0xc044c880,0xb1419005,0xa4c48424,0x603a1a34,0xc1949000,
+ 0x003a8246,0xc106180d,0x99100022,0x1511e050,0x00824157,0x022a041a,
+ 0x8930004f,0x446ad813,0xed228aa2,0x400511c0,0x01021000,0x31018808,
+ 0x02044620,0x0f08f800,0xa2008900,0x22020000,0x16108210,0x10400042,
+ 0x126052c0,0x200052f4,0x82308510,0x42021100,0x80b5430a,0xda2070e1,
+ 0x08012040,0xfc653500,0xab0419c1,0x62140286,0x00440087,0x42469085,
+ 0x0a85405c,0x33803207,0xb8c00400,0xc0d0ce30,0x0080c030,0x0da50508,
+ 0x00400a90,0x280c0200,0x40446705,0x41226429,0x000002e8,0x847c4664,
+ 0xde200002,0x4049861d,0xc0000a08,0x20010084,0x10108400,0x01c742cd,
+ 0xd52a703a,0x1d8f9968,0x3e12be50,0x81d9aef5,0x2412cec4,0x732e0828,
+ 0x4b3424ac,0xd41d020c,0x80002a02,0x08110097,0x114411c4,0x7d451786,
+ 0x5e4949dd,0x87914040,0xd8c4254c,0x491444ba,0xc8001b92,0x15800271,
+ 0x0c0000c1,0xc200096a,0x40024800,0xba493021,0x1c802080,0x1008e2ac,
+ 0x00341004,0x841400e3,0x20004020,0x14149810,0x04aa70c2,0x54208688,
+ 0x04130c62,0x20109180,0x02064082,0x54011c40,0xe4e90383,0x84802125,
+ 0x2810e433,0xe60944c0,0x81260a03,0x080112da,0x97906901,0xf8864001,
+ 0x0081e24d,0xa6510a0e,0x81ec011a,0x8441c600,0xb62eadb8,0x8741acef,
+ 0x4b028d54,0x02681161,0x2057bb60,0x043350a0,0xf7b4a8c0,0x01122402,
+ 0x20009ad3,0x00c82271,0x809e2081,0xe1800c8a,0x8151b009,0x40281031,
+ 0x89a52a0e,0x620e69b6,0xd1444425,0x4d548085,0x1fb12c75,0x862dd807,
+ 0x5841d97c,0x226e414e,0x9e088200,0xedb7f80d,0x75668c80,0x08149313,
+ 0xc8040e32,0x6ea6484e,0x66742c4a,0xba0126c0,0x185dd70c,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x05400000,0x813370a0,0x03a54f81,
+ 0x641055ec,0x2344c31a,0x00341462,0x1a090a43,0x13a5187b,0xa8480102,
+ 0xc5440440,0xe2dd8106,0x2d481af0,0x0416b626,0x6e405058,0x31128032,
+ 0x0c0007e4,0x420a8208,0x803b4840,0x87134860,0x3428850d,0xe5290319,
+ 0x870a2345,0x5c1825a9,0xd9c577a6,0x03e85e00,0xa7000081,0x41c6cd54,
+ 0xa2042800,0x2b0ab860,0xda9e0020,0x0e1a08ea,0x11c0427e,0x03768908,
+ 0x01058621,0x98a80004,0xc44846a0,0x20220d05,0x914854a2,0x28d78a01,
+ 0x00087898,0x31221605,0x08804340,0x06a2fa4e,0x92110814,0x9b142002,
+ 0x16432e52,0x90105000,0x85ba0041,0x20203042,0x07a84f0b,0x40802f08,
+ 0x1a930591,0x0601df50,0x3021a202,0x4e800630,0x04c80cc4,0x8001a004,
+ 0xd4316000,0x0a020880,0x00281c00,0x00418e18,0xca106ad0,0x4b00f210,
+ 0x1506274d,0x88900220,0x82a85a00,0x81504549,0x80002004,0x2c088804,
+ 0x000508d1,0x4ac48001,0x0062e0a0,0x0a42008e,0x6a8c3055,0xe0a5090e,
+ 0x42c42906,0x80b34814,0xb330803e,0x733c0102,0x700d1494,0x09400c20,
+ 0xc040301a,0xc094a451,0x05c88dca,0xa40c96c2,0x34040001,0x011000c8,
+ 0xa9cd550d,0x1cda2428,0x48370142,0x120f7a4d,0x452a32b4,0xd20531fb,
+ 0xdc44b894,0x45ca68d7,0x2ed15097,0x42081943,0x9d48d202,0xa0979840,
+ 0x064d5409,0x00000000,0x00000000,0x00000000,0x00000000,0x84800000,
+ 0x04215542,0x17001c06,0x61107624,0xb9ddff87,0x5c0a659f,0x3c11245d,
+ 0x005dadb0,0x00000000,0x00000000,0x00db28d0,0x02000422,0x44080108,
+ 0xac409804,0x90288d0a,0xe0018700,0x00310400,0x82211794,0x10540019,
+ 0x021a2cb2,0x40039c02,0x8804bd60,0x7900080c,0xba3c1628,0xcb088640,
+ 0x90807274,0x0000001e,0xd8000000,0x9c87e188,0x04124034,0x2791ae64,
+ 0xe6fbe86b,0x5366408f,0x537feea6,0xb5e4e3ab,0x0002869f,0x01228548,
+ 0x48004402,0x20a02116,0x02240004,0x00052080,0x01547e00,0x01ac162c,
+ 0x10852a84,0x05308c14,0xfdc3fbc3,0x906060fa,0x40336440,0x96901200,
+ 0x4e834b31,0x418200d4,0x1d6a0129,0x02802080,0x02ad8000,0x9f0c2691,
+ 0x67018044,0x0c24d96f,0x18d02910,0x50215001,0x04d01000,0x02017090,
+ 0x61c30148,0x01000132,0x07190088,0x05620802,0x4c0e0132,0xf0a10405,
+ 0x00000002,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00800000,0x035e8e8d,0x5a0421bd,0x11703488,0x00000026,
+ 0x10000000,0x8804c502,0xf801b815,0x25ed147c,0x3bb0ed60,0x1bd78589,
+ 0x1a627af3,0x0ac50d0c,0x524ae5d1,0x6b0d0490,0x5266a35c,0x16122b57,
+ 0x1101a872,0x00182949,0x10080948,0x886c6000,0x058f916e,0x39903012,
+ 0x49b0f840,0x001b88a0,0x00000000,0x00428500,0x98000058,0x7014ea04,
+ 0x611d1628,0x60005193,0x00a71a24,0x00000000,0x43c00000,0x10187120,
+ 0xa9270172,0x89066004,0x020cc022,0x40810900,0x8ca0602d,0x00000e34,
+ 0x00000000,0x11012100,0xd31a8011,0x0892ec4c,0x85000040,0x1806c7ac,
+ 0x0512e03e,0x00348000,0x80cec008,0x0a126d01,0x08568641,0x0027011e,
+ 0x083d3751,0x4e05e032,0x048401c0,0x01400081,0x00000000,0x00000000,
+ 0x00000000,0x00591aa0,0x882443c8,0xc8001d48,0x72030152,0x04059813,
+ 0x04008280,0x0d148a10,0x02088056,0x2704a040,0x4e000000,0x00000000,
+ 0x00000000,0xa3200000,0xa0ae1902,0xdf002660,0x7b17f010,0x3ad08121,
+ 0x00284180,0x48001003,0x8014cc00,0x00c414cf,0x30202000,0x00000001,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0xffffffff,0xffffffff,
+ 0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,
+ 0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,
+ 0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,
+ 0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,
+ 0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,
+ 0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,
+ 0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,
+ 0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,
+ 0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,
+ 0xffffffff,0xffffffff,0x00ffffff,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x000f0000,
+ 0x00000000,0x00000200,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x10000000,0x00000000,0xffffc000,0x00003fff,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0xfffffffe,0xffffffff,0x7fffffff,0xfffffffe,0xffffffff,0x00000000,
+ 0x00000000,0x0000003f
+};
+
+#if 0
+/*
+ * The following program regenerates the good old valid.dat. Try it
+ * yourself :-)
+ */
+int main(void)
+{
+ int i;
+ for (i=0; i<65536; i++) {
+ char c = (valid_table[i/32] & (1<<(i%32))) ? 1 : 0;
+ write(1, &c, 1);
+ }
+}
+#endif
+
+static bool isvalid83_w(smb_ucs2_t c)
+{
+ uint16_t idx = SVAL(&c, 0);
+ return (valid_table[idx/32] & (1 << (idx%32))) != 0;
+}
+
+static NTSTATUS has_valid_83_chars(const smb_ucs2_t *s, bool allow_wildcards)
+{
+ if (!*s) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!allow_wildcards && ms_has_wild_w(s)) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ while (*s) {
+ if(!isvalid83_w(*s)) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ s++;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS has_illegal_chars(const smb_ucs2_t *s, bool allow_wildcards)
+{
+ if (!allow_wildcards && ms_has_wild_w(s)) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ while (*s) {
+ if (*s <= 0x1f) {
+ /* Control characters. */
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ switch(*s) {
+ case UCS2_CHAR('\\'):
+ case UCS2_CHAR('/'):
+ case UCS2_CHAR('|'):
+ case UCS2_CHAR(':'):
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ s++;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ Duplicate string.
+********************************************************************/
+
+static smb_ucs2_t *strdup_w(const smb_ucs2_t *src)
+{
+ smb_ucs2_t *dest;
+ size_t len = strlen_w(src);
+ dest = SMB_MALLOC_ARRAY(smb_ucs2_t, len + 1);
+ if (!dest) {
+ DEBUG(0,("strdup_w: out of memory!\n"));
+ return NULL;
+ }
+
+ memcpy(dest, src, len * sizeof(smb_ucs2_t));
+ dest[len] = 0;
+ return dest;
+}
+
+/* return False if something fail and
+ * return 2 alloced unicode strings that contain prefix and extension
+ */
+
+static NTSTATUS mangle_get_prefix(const smb_ucs2_t *ucs2_string, smb_ucs2_t **prefix,
+ smb_ucs2_t **extension, bool allow_wildcards)
+{
+ size_t ext_len;
+ smb_ucs2_t *p;
+
+ *extension = 0;
+ *prefix = strdup_w(ucs2_string);
+ if (!*prefix) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ if ((p = strrchr_w(*prefix, UCS2_CHAR('.')))) {
+ ext_len = strlen_w(p+1);
+ if ((ext_len > 0) && (ext_len < 4) && (p != *prefix) &&
+ (NT_STATUS_IS_OK(has_valid_83_chars(p+1,allow_wildcards)))) /* check extension */ {
+ *p = 0;
+ *extension = strdup_w(p+1);
+ if (!*extension) {
+ SAFE_FREE(*prefix);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+/* ************************************************************************** **
+ * Return NT_STATUS_UNSUCCESSFUL if a name is a special msdos reserved name.
+ * or contains illegal characters.
+ *
+ * Input: fname - String containing the name to be tested.
+ *
+ * Output: NT_STATUS_UNSUCCESSFUL, if the condition above is true.
+ *
+ * Notes: This is a static function called by is_8_3(), below.
+ *
+ * ************************************************************************** **
+ */
+
+static NTSTATUS is_valid_name(const smb_ucs2_t *fname, bool allow_wildcards, bool only_8_3)
+{
+ smb_ucs2_t *str, *p;
+ size_t num_ucs2_chars;
+ NTSTATUS ret = NT_STATUS_OK;
+
+ if (!fname || !*fname)
+ return NT_STATUS_INVALID_PARAMETER;
+
+ /* . and .. are valid names. */
+ if (strcmp_wa(fname, ".")==0 || strcmp_wa(fname, "..")==0)
+ return NT_STATUS_OK;
+
+ if (only_8_3) {
+ ret = has_valid_83_chars(fname, allow_wildcards);
+ if (!NT_STATUS_IS_OK(ret))
+ return ret;
+ }
+
+ ret = has_illegal_chars(fname, allow_wildcards);
+ if (!NT_STATUS_IS_OK(ret))
+ return ret;
+
+ /* Name can't end in '.' or ' ' */
+ num_ucs2_chars = strlen_w(fname);
+ if (fname[num_ucs2_chars-1] == UCS2_CHAR('.') || fname[num_ucs2_chars-1] == UCS2_CHAR(' ')) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ str = strdup_w(fname);
+
+ /* Truncate copy after the first dot. */
+ p = strchr_w(str, UCS2_CHAR('.'));
+ if (p) {
+ *p = 0;
+ }
+
+ strupper_w(str);
+ p = &str[1];
+
+ switch(str[0])
+ {
+ case UCS2_CHAR('A'):
+ if(strcmp_wa(p, "UX") == 0)
+ ret = NT_STATUS_UNSUCCESSFUL;
+ break;
+ case UCS2_CHAR('C'):
+ if((strcmp_wa(p, "LOCK$") == 0)
+ || (strcmp_wa(p, "ON") == 0)
+ || (strcmp_wa(p, "OM1") == 0)
+ || (strcmp_wa(p, "OM2") == 0)
+ || (strcmp_wa(p, "OM3") == 0)
+ || (strcmp_wa(p, "OM4") == 0)
+ )
+ ret = NT_STATUS_UNSUCCESSFUL;
+ break;
+ case UCS2_CHAR('L'):
+ if((strcmp_wa(p, "PT1") == 0)
+ || (strcmp_wa(p, "PT2") == 0)
+ || (strcmp_wa(p, "PT3") == 0)
+ )
+ ret = NT_STATUS_UNSUCCESSFUL;
+ break;
+ case UCS2_CHAR('N'):
+ if(strcmp_wa(p, "UL") == 0)
+ ret = NT_STATUS_UNSUCCESSFUL;
+ break;
+ case UCS2_CHAR('P'):
+ if(strcmp_wa(p, "RN") == 0)
+ ret = NT_STATUS_UNSUCCESSFUL;
+ break;
+ default:
+ break;
+ }
+
+ SAFE_FREE(str);
+ return ret;
+}
+
+static NTSTATUS is_8_3_w(const smb_ucs2_t *fname, bool allow_wildcards)
+{
+ smb_ucs2_t *pref = 0, *ext = 0;
+ size_t plen;
+ NTSTATUS ret = NT_STATUS_UNSUCCESSFUL;
+
+ if (!fname || !*fname)
+ return NT_STATUS_INVALID_PARAMETER;
+
+ if (strlen_w(fname) > 12)
+ return NT_STATUS_UNSUCCESSFUL;
+
+ if (strcmp_wa(fname, ".") == 0 || strcmp_wa(fname, "..") == 0)
+ return NT_STATUS_OK;
+
+ /* Name cannot start with '.' */
+ if (*fname == UCS2_CHAR('.'))
+ return NT_STATUS_UNSUCCESSFUL;
+
+ if (!NT_STATUS_IS_OK(is_valid_name(fname, allow_wildcards, True)))
+ goto done;
+
+ if (!NT_STATUS_IS_OK(mangle_get_prefix(fname, &pref, &ext, allow_wildcards)))
+ goto done;
+ plen = strlen_w(pref);
+
+ if (strchr_wa(pref, '.'))
+ goto done;
+ if (plen < 1 || plen > 8)
+ goto done;
+ if (ext && (strlen_w(ext) > 3))
+ goto done;
+
+ ret = NT_STATUS_OK;
+
+done:
+ SAFE_FREE(pref);
+ SAFE_FREE(ext);
+ return ret;
+}
+
+static bool is_8_3(const char *fname, bool check_case, bool allow_wildcards,
+ const struct share_params *p)
+{
+ const char *f;
+ smb_ucs2_t *ucs2name;
+ NTSTATUS ret = NT_STATUS_UNSUCCESSFUL;
+ size_t size;
+
+ if (!fname || !*fname)
+ return False;
+ if ((f = strrchr(fname, '/')) == NULL)
+ f = fname;
+ else
+ f++;
+
+ if (strlen(f) > 12)
+ return False;
+
+ if (!push_ucs2_talloc(NULL, &ucs2name, f, &size)) {
+ DEBUG(0,("is_8_3: internal error push_ucs2_talloc() failed!\n"));
+ goto done;
+ }
+
+ ret = is_8_3_w(ucs2name, allow_wildcards);
+
+done:
+ TALLOC_FREE(ucs2name);
+
+ if (!NT_STATUS_IS_OK(ret)) {
+ return False;
+ }
+
+ return True;
+}
+
+/* -------------------------------------------------------------------------- **
+ * Functions...
+ */
+
+/* ************************************************************************** **
+ * Initialize the static character test array.
+ *
+ * Input: none
+ *
+ * Output: none
+ *
+ * Notes: This function changes (loads) the contents of the <chartest>
+ * array. The scope of <chartest> is this file.
+ *
+ * ************************************************************************** **
+ */
+
+static void init_chartest( void )
+{
+ const unsigned char *s;
+
+ chartest = SMB_MALLOC_ARRAY(unsigned char, 256);
+
+ SMB_ASSERT(chartest != NULL);
+ memset(chartest, '\0', 256);
+
+ for( s = (const unsigned char *)basechars; *s; s++ ) {
+ chartest[*s] |= BASECHAR_MASK;
+ }
+}
+
+/* ************************************************************************** **
+ * Return True if the name *could be* a mangled name.
+ *
+ * Input: s - A path name - in UNIX pathname format.
+ *
+ * Output: True if the name matches the pattern described below in the
+ * notes, else False.
+ *
+ * Notes: The input name is *not* tested for 8.3 compliance. This must be
+ * done separately. This function returns true if the name contains
+ * a magic character followed by exactly two characters from the
+ * basechars list (above), which in turn are followed either by the
+ * nul (end of string) byte or a dot (extension) or by a '/' (end of
+ * a directory name).
+ *
+ * ************************************************************************** **
+ */
+
+static bool is_mangled(const char *s, const struct share_params *p)
+{
+ char *magic;
+ char magic_char;
+
+ magic_char = lp_mangling_char(p);
+
+ if (chartest == NULL) {
+ init_chartest();
+ }
+
+ magic = strchr_m( s, magic_char );
+ while( magic && magic[1] && magic[2] ) { /* 3 chars, 1st is magic. */
+ if( ('.' == magic[3] || '/' == magic[3] || !(magic[3])) /* Ends with '.' or nul or '/' ? */
+ && isbasechar( toupper_m(magic[1]) ) /* is 2nd char basechar? */
+ && isbasechar( toupper_m(magic[2]) ) ) /* is 3rd char basechar? */
+ return( True ); /* If all above, then true, */
+ magic = strchr_m( magic+1, magic_char ); /* else seek next magic. */
+ }
+ return( False );
+}
+
+/***************************************************************************
+ Initializes or clears the mangled cache.
+***************************************************************************/
+
+static void mangle_reset( void )
+{
+ /* We could close and re-open the tdb here... should we ? The old code did
+ the equivalent... JRA. */
+}
+
+/***************************************************************************
+ Add a mangled name into the cache.
+ If the extension of the raw name maps directly to the
+ extension of the mangled name, then we'll store both names
+ *without* extensions. That way, we can provide consistent
+ reverse mangling for all names that match. The test here is
+ a bit more careful than the one done in earlier versions of
+ mangle.c:
+
+ - the extension must exist on the raw name,
+ - it must be all lower case
+ - it must match the mangled extension (to prove that no
+ mangling occurred).
+ crh 07-Apr-1998
+**************************************************************************/
+
+static void cache_mangled_name( const char mangled_name[13],
+ const char *raw_name )
+{
+ TDB_DATA data_val;
+ char mangled_name_key[13];
+ char *s1 = NULL;
+ char *s2 = NULL;
+
+ /* If the cache isn't initialized, give up. */
+ if( !tdb_mangled_cache )
+ return;
+
+ /* Init the string lengths. */
+ strlcpy(mangled_name_key, mangled_name, sizeof(mangled_name_key));
+
+ /* See if the extensions are unmangled. If so, store the entry
+ * without the extension, thus creating a "group" reverse map.
+ */
+ s1 = strrchr( mangled_name_key, '.' );
+ if( s1 && (s2 = strrchr( raw_name, '.' )) ) {
+ size_t i = 1;
+ while( s1[i] && (tolower_m( s1[i] ) == s2[i]) )
+ i++;
+ if( !s1[i] && !s2[i] ) {
+ /* Truncate at the '.' */
+ *s1 = '\0';
+ /*
+ * DANGER WILL ROBINSON - this
+ * is changing a const string via
+ * an aliased pointer ! Remember to
+ * put it back once we've used it.
+ * JRA
+ */
+ *s2 = '\0';
+ }
+ }
+
+ /* Allocate a new cache entry. If the allocation fails, just return. */
+ data_val = string_term_tdb_data(raw_name);
+ if (tdb_store_bystring(tdb_mangled_cache, mangled_name_key, data_val, TDB_REPLACE) != 0) {
+ DEBUG(0,("cache_mangled_name: Error storing entry %s -> %s\n", mangled_name_key, raw_name));
+ } else {
+ DEBUG(5,("cache_mangled_name: Stored entry %s -> %s\n", mangled_name_key, raw_name));
+ }
+ /* Restore the change we made to the const string. */
+ if (s2) {
+ *s2 = '.';
+ }
+}
+
+/* ************************************************************************** **
+ * Check for a name on the mangled name stack
+ *
+ * Input: s - Input *and* output string buffer.
+ * maxlen - space in i/o string buffer.
+ * Output: True if the name was found in the cache, else False.
+ *
+ * Notes: If a reverse map is found, the function will overwrite the string
+ * space indicated by the input pointer <s>. This is frightening.
+ * It should be rewritten to return NULL if the long name was not
+ * found, and a pointer to the long name if it was found.
+ *
+ * ************************************************************************** **
+ */
+
+static bool lookup_name_from_8_3(TALLOC_CTX *ctx,
+ const char *in,
+ char **out, /* talloced on the given context. */
+ const struct share_params *p)
+{
+ TDB_DATA data_val;
+ char *saved_ext = NULL;
+ char *s = talloc_strdup(ctx, in);
+
+ /* If the cache isn't initialized, give up. */
+ if(!s || !tdb_mangled_cache ) {
+ TALLOC_FREE(s);
+ return False;
+ }
+
+ data_val = tdb_fetch_bystring(tdb_mangled_cache, s);
+
+ /* If we didn't find the name *with* the extension, try without. */
+ if(data_val.dptr == NULL || data_val.dsize == 0) {
+ char *ext_start = strrchr( s, '.' );
+ if( ext_start ) {
+ if((saved_ext = talloc_strdup(ctx,ext_start)) == NULL) {
+ TALLOC_FREE(s);
+ return False;
+ }
+
+ *ext_start = '\0';
+ data_val = tdb_fetch_bystring(tdb_mangled_cache, s);
+ /*
+ * At this point s is the name without the
+ * extension. We re-add the extension if saved_ext
+ * is not null, before freeing saved_ext.
+ */
+ }
+ }
+
+ /* Okay, if we haven't found it we're done. */
+ if(data_val.dptr == NULL || data_val.dsize == 0) {
+ TALLOC_FREE(saved_ext);
+ TALLOC_FREE(s);
+ return False;
+ }
+
+ /* If we *did* find it, we need to talloc it on the given ctx. */
+ if (saved_ext) {
+ *out = talloc_asprintf(ctx, "%s%s",
+ (char *)data_val.dptr,
+ saved_ext);
+ } else {
+ *out = talloc_strdup(ctx, (char *)data_val.dptr);
+ }
+
+ TALLOC_FREE(s);
+ TALLOC_FREE(saved_ext);
+ SAFE_FREE(data_val.dptr);
+
+ return *out ? True : False;
+}
+
+/**
+ Check if a string is in "normal" case.
+**/
+
+static bool strisnormal(const char *s, int case_default)
+{
+ if (case_default == CASE_UPPER)
+ return(!strhaslower(s));
+
+ return(!strhasupper(s));
+}
+
+
+/*****************************************************************************
+ Do the actual mangling to 8.3 format.
+*****************************************************************************/
+
+static bool to_8_3(char magic_char, const char *in, char out[13], int default_case)
+{
+ int csum;
+ char *p;
+ char extension[4];
+ char base[9];
+ int baselen = 0;
+ int extlen = 0;
+ char *s = SMB_STRDUP(in);
+
+ extension[0] = 0;
+ base[0] = 0;
+
+ if (!s) {
+ return False;
+ }
+
+ p = strrchr(s,'.');
+ if( p && (strlen(p+1) < (size_t)4) ) {
+ bool all_normal = ( strisnormal(p+1, default_case) ); /* XXXXXXXXX */
+
+ if( all_normal && p[1] != 0 ) {
+ *p = 0;
+ csum = str_checksum( s );
+ *p = '.';
+ } else
+ csum = str_checksum(s);
+ } else
+ csum = str_checksum(s);
+
+ if (!strupper_m( s )) {
+ SAFE_FREE(s);
+ return false;
+ }
+
+ if( p ) {
+ if( p == s )
+ strlcpy( extension, "___", 4);
+ else {
+ *p++ = 0;
+ while( *p && extlen < 3 ) {
+ if ( *p != '.') {
+ extension[extlen++] = p[0];
+ }
+ p++;
+ }
+ extension[extlen] = 0;
+ }
+ }
+
+ p = s;
+
+ while( *p && baselen < 5 ) {
+ if (isbasechar(*p)) {
+ base[baselen++] = p[0];
+ }
+ p++;
+ }
+ base[baselen] = 0;
+
+ csum = csum % (MANGLE_BASE*MANGLE_BASE);
+
+ memcpy(out, base, baselen);
+ out[baselen] = magic_char;
+ out[baselen+1] = mangle( csum/MANGLE_BASE );
+ out[baselen+2] = mangle( csum );
+
+ if( *extension ) {
+ out[baselen+3] = '.';
+ strlcpy(&out[baselen+4], extension, 4);
+ }
+
+ SAFE_FREE(s);
+ return True;
+}
+
+static bool must_mangle(const char *name,
+ const struct share_params *p)
+{
+ smb_ucs2_t *name_ucs2 = NULL;
+ NTSTATUS status;
+ size_t converted_size;
+
+ if (!push_ucs2_talloc(NULL, &name_ucs2, name, &converted_size)) {
+ DEBUG(0, ("push_ucs2_talloc failed!\n"));
+ return False;
+ }
+ status = is_valid_name(name_ucs2, False, False);
+ TALLOC_FREE(name_ucs2);
+ /* We return true if we *must* mangle, so if it's
+ * a valid name (status == OK) then we must return
+ * false. Bug #6939. */
+ return !NT_STATUS_IS_OK(status);
+}
+
+/*****************************************************************************
+ * Convert a filename to DOS format. Return True if successful.
+ * Input: in Incoming name.
+ *
+ * out 8.3 DOS name.
+ *
+ * cache83 - If False, the mangled name cache will not be updated.
+ * This is usually used to prevent that we overwrite
+ * a conflicting cache entry prematurely, i.e. before
+ * we know whether the client is really interested in the
+ * current name. (See PR#13758). UKD.
+ *
+ * ****************************************************************************
+ */
+
+static bool hash_name_to_8_3(const char *in,
+ char out[13],
+ bool cache83,
+ int default_case,
+ const struct share_params *p)
+{
+ smb_ucs2_t *in_ucs2 = NULL;
+ size_t converted_size;
+ char magic_char;
+
+ magic_char = lp_mangling_char(p);
+
+ DEBUG(5,("hash_name_to_8_3( %s, cache83 = %s)\n", in,
+ cache83 ? "True" : "False"));
+
+ if (!push_ucs2_talloc(NULL, &in_ucs2, in, &converted_size)) {
+ DEBUG(0, ("push_ucs2_talloc failed!\n"));
+ return False;
+ }
+
+ /* If it's already 8.3, just copy. */
+ if (NT_STATUS_IS_OK(is_valid_name(in_ucs2, False, False)) &&
+ NT_STATUS_IS_OK(is_8_3_w(in_ucs2, False))) {
+ TALLOC_FREE(in_ucs2);
+ strlcpy(out, in, 13);
+ return True;
+ }
+
+ TALLOC_FREE(in_ucs2);
+ if (!to_8_3(magic_char, in, out, default_case)) {
+ return False;
+ }
+
+ cache_mangled_name(out, in);
+
+ DEBUG(5,("hash_name_to_8_3(%s) ==> [%s]\n", in, out));
+ return True;
+}
+
+/*
+ the following provides the abstraction layer to make it easier
+ to drop in an alternative mangling implementation
+*/
+static const struct mangle_fns mangle_hash_fns = {
+ mangle_reset,
+ is_mangled,
+ must_mangle,
+ is_8_3,
+ lookup_name_from_8_3,
+ hash_name_to_8_3
+};
+
+/***************************************************************
+ Compute a hash value based on a string key value.
+ The function returns the bucket index number for the hashed key.
+ JRA. Use a djb-algorithm hash for speed.
+***************************************************************/
+
+static unsigned int fast_string_hash(TDB_DATA *key)
+{
+ unsigned int n = 0;
+ const char *p;
+ for (p = (const char *)key->dptr; *p != '\0'; p++) {
+ n = ((n << 5) + n) ^ (unsigned int)(*p);
+ }
+ return n;
+}
+
+/* return the methods for this mangling implementation */
+const struct mangle_fns *mangle_hash_init(void)
+{
+ mangle_reset();
+
+ if (chartest == NULL) {
+ init_chartest();
+ }
+
+ /* Create the in-memory tdb using our custom hash function. */
+ tdb_mangled_cache = tdb_open_ex("mangled_cache", 1031, TDB_INTERNAL,
+ (O_RDWR|O_CREAT), 0644, NULL, fast_string_hash);
+
+ return &mangle_hash_fns;
+}
diff --git a/source3/smbd/mangle_hash2.c b/source3/smbd/mangle_hash2.c
new file mode 100644
index 0000000..0075d3b
--- /dev/null
+++ b/source3/smbd/mangle_hash2.c
@@ -0,0 +1,875 @@
+/*
+ Unix SMB/CIFS implementation.
+ new hash based name mangling implementation
+ Copyright (C) Andrew Tridgell 2002
+ Copyright (C) Simo Sorce 2002
+
+ 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/>.
+*/
+
+/*
+ this mangling scheme uses the following format
+
+ Annnn~n.AAA
+
+ where nnnnn is a base 36 hash, and A represents characters from the original string
+
+ The hash is taken of the leading part of the long filename, in uppercase
+
+ for simplicity, we only allow ascii characters in 8.3 names
+ */
+
+ /* hash algorithm changed to FNV1 by idra@samba.org (Simo Sorce).
+ * see http://www.isthe.com/chongo/tech/comp/fnv/index.html for a
+ * discussion on Fowler / Noll / Vo (FNV) Hash by one of it's authors
+ */
+
+/*
+ ===============================================================================
+ NOTE NOTE NOTE!!!
+
+ This file deliberately uses non-multibyte string functions in many places. This
+ is *not* a mistake. This code is multi-byte safe, but it gets this property
+ through some very subtle knowledge of the way multi-byte strings are encoded
+ and the fact that this mangling algorithm only supports ascii characters in
+ 8.3 names.
+
+ please don't convert this file to use the *_m() functions!!
+ ===============================================================================
+*/
+
+/*
+ * ============================================================================
+ * Whenever you change anything in the FLAG_ or other fields,
+ * re-initialize the tables char_flags and base_reverse by running the
+ * init_tables() routine once and dump its results. To do this, a
+ * single smbd run with
+ *
+ * #define DYNAMIC_MANGLE_TABLES 1
+ *
+ * and debug level 10 should be sufficient.
+ * ============================================================================
+ */
+
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../lib/util/memcache.h"
+#include "mangle.h"
+
+#if 1
+#define M_DEBUG(level, x) DEBUG(level, x)
+#else
+#define M_DEBUG(level, x)
+#endif
+
+/* these flags are used to mark characters in as having particular
+ properties */
+#define FLAG_BASECHAR 1
+#define FLAG_ASCII 2
+#define FLAG_ILLEGAL 4
+#define FLAG_WILDCARD 8
+
+/* the "possible" flags are used as a fast way to find possible DOS
+ reserved filenames */
+#define FLAG_POSSIBLE1 16
+#define FLAG_POSSIBLE2 32
+#define FLAG_POSSIBLE3 64
+#define FLAG_POSSIBLE4 128
+
+#define FNV1_PRIME 0x01000193
+/*the following number is a fnv1 of the string: idra@samba.org 2002 */
+#define FNV1_INIT 0xa6b93095
+
+#define FLAG_CHECK(c, flag) (char_flags[(unsigned char)(c)] & (flag))
+
+/* these are the characters we use in the 8.3 hash. Must be 36 chars long */
+static const char basechars[36] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+#define base_forward(v) basechars[v]
+
+/* the list of reserved dos names - all of these are illegal */
+static const char * const reserved_names[] =
+{ "AUX", "CON",
+ "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
+ "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
+ "NUL", "PRN", NULL };
+
+#define DYNAMIC_MANGLE_TABLES 0
+
+#if DYNAMIC_MANGLE_TABLES
+
+/* these tables are used to provide fast tests for characters */
+static unsigned char char_flags[256];
+static unsigned char base_reverse[256];
+
+/* initialise the flags table
+
+ we allow only a very restricted set of characters as 'ascii' in this
+ mangling backend. This isn't a significant problem as modern clients
+ use the 'long' filenames anyway, and those don't have these
+ restrictions.
+*/
+static void init_tables(void)
+{
+ int i;
+
+ memset(char_flags, 0, sizeof(char_flags));
+
+ for (i=1;i<128;i++) {
+ if (i <= 0x1f) {
+ /* Control characters. */
+ char_flags[i] |= FLAG_ILLEGAL;
+ }
+
+ if ((i >= '0' && i <= '9') ||
+ (i >= 'a' && i <= 'z') ||
+ (i >= 'A' && i <= 'Z')) {
+ char_flags[i] |= (FLAG_ASCII | FLAG_BASECHAR);
+ }
+ if (strchr("_-$~", i)) {
+ char_flags[i] |= FLAG_ASCII;
+ }
+
+ if (strchr("*\\/?<>|\":", i)) {
+ char_flags[i] |= FLAG_ILLEGAL;
+ }
+
+ if (strchr("*?\"<>", i)) {
+ char_flags[i] |= FLAG_WILDCARD;
+ }
+ }
+
+ memset(base_reverse, 0, sizeof(base_reverse));
+ for (i=0;i<36;i++) {
+ base_reverse[(unsigned char)base_forward(i)] = i;
+ }
+
+ /* fill in the reserved names flags. These are used as a very
+ fast filter for finding possible DOS reserved filenames */
+ for (i=0; reserved_names[i]; i++) {
+ unsigned char c1, c2, c3, c4;
+
+ c1 = (unsigned char)reserved_names[i][0];
+ c2 = (unsigned char)reserved_names[i][1];
+ c3 = (unsigned char)reserved_names[i][2];
+ c4 = (unsigned char)reserved_names[i][3];
+
+ char_flags[c1] |= FLAG_POSSIBLE1;
+ char_flags[c2] |= FLAG_POSSIBLE2;
+ char_flags[c3] |= FLAG_POSSIBLE3;
+ char_flags[c4] |= FLAG_POSSIBLE4;
+ char_flags[tolower_m(c1)] |= FLAG_POSSIBLE1;
+ char_flags[tolower_m(c2)] |= FLAG_POSSIBLE2;
+ char_flags[tolower_m(c3)] |= FLAG_POSSIBLE3;
+ char_flags[tolower_m(c4)] |= FLAG_POSSIBLE4;
+
+ char_flags[(unsigned char)'.'] |= FLAG_POSSIBLE4;
+ }
+
+#if 0
+ DEBUG(10, ("char_flags\n"));
+ dump_data(10, char_flags, sizeof(char_flags));
+
+ DEBUG(10, ("base_reverse\n"));
+ dump_data(10, base_reverse, sizeof(base_reverse));
+#endif
+}
+
+#else /* DYNAMIC_MANGLE_TABLES */
+
+/*
+ * These tables were initialized by a single run of the above
+ * init_tables() routine, dumping the tables and a simple emacs macro.
+ *
+ * Technically we could leave out the 0's at the end of the array
+ * initializers, but I'll leave it in: less surprise.
+ */
+
+static const uint8_t char_flags[256] = {
+ 0x80, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x00, 0x00, 0x0C, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x0C, 0x00, 0x00, 0x02, 0x80, 0x04,
+ 0x03, 0x83, 0x83, 0x83, 0x83, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x04, 0x00, 0x0C, 0x00, 0x0C, 0x0C,
+ 0x00, 0x13, 0x03, 0x53, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x83, 0x53, 0x43, 0x53, 0x23,
+ 0x33, 0x03, 0x23, 0x03, 0x43, 0x23, 0x03, 0x03,
+ 0x43, 0x03, 0x03, 0x00, 0x04, 0x00, 0x00, 0x02,
+ 0x00, 0x13, 0x03, 0x53, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x83, 0x53, 0x43, 0x53, 0x23,
+ 0x33, 0x03, 0x23, 0x03, 0x43, 0x23, 0x03, 0x03,
+ 0x43, 0x03, 0x03, 0x00, 0x04, 0x00, 0x02, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+static const uint8_t base_reverse[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10,
+ 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
+ 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20,
+ 0x21, 0x22, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+#endif /* DYNAMIC_MANGLE_TABLES */
+
+/*
+ hash a string of the specified length. The string does not need to be
+ null terminated
+
+ this hash needs to be fast with a low collision rate (what hash doesn't?)
+*/
+static unsigned int mangle_hash(const char *key, unsigned int length)
+{
+ unsigned int value;
+ unsigned int i;
+ fstring str;
+
+ /* we have to uppercase here to ensure that the mangled name
+ doesn't depend on the case of the long name. Note that this
+ is the only place where we need to use a multi-byte string
+ function */
+ length = MIN(length,sizeof(fstring)-1);
+ strncpy(str, key, length);
+ str[length] = 0;
+ (void)strupper_m(str);
+
+ /* the length of a multi-byte string can change after a strupper_m */
+ length = strlen(str);
+
+ /* Set the initial value from the key size. */
+ for (value = FNV1_INIT, i=0; i < length; i++) {
+ value *= (unsigned int)FNV1_PRIME;
+ value ^= (unsigned int)(str[i]);
+ }
+
+ /* note that we force it to a 31 bit hash, to keep within the limits
+ of the 36^6 mangle space */
+ return value & ~0x80000000;
+}
+
+/*
+ insert an entry into the prefix cache. The string might not be null
+ terminated */
+static void cache_insert(const char *prefix, int length, unsigned int hash)
+{
+ char *str = SMB_STRNDUP(prefix, length);
+
+ if (str == NULL) {
+ return;
+ }
+
+ memcache_add(smbd_memcache(), MANGLE_HASH2_CACHE,
+ data_blob_const(&hash, sizeof(hash)),
+ data_blob_const(str, length+1));
+ SAFE_FREE(str);
+}
+
+/*
+ lookup an entry in the prefix cache. Return NULL if not found.
+*/
+static char *cache_lookup(TALLOC_CTX *mem_ctx, unsigned int hash)
+{
+ DATA_BLOB value;
+
+ if (!memcache_lookup(smbd_memcache(), MANGLE_HASH2_CACHE,
+ data_blob_const(&hash, sizeof(hash)), &value)) {
+ return NULL;
+ }
+
+ SMB_ASSERT((value.length > 0)
+ && (value.data[value.length-1] == '\0'));
+
+ return talloc_strdup(mem_ctx, (char *)value.data);
+}
+
+
+/*
+ determine if a string is possibly in a mangled format, ignoring
+ case
+
+ In this algorithm, mangled names use only pure ascii characters (no
+ multi-byte) so we can avoid doing a UCS2 conversion
+ */
+static bool is_mangled_component(const char *name, size_t len)
+{
+ unsigned int i;
+
+ M_DEBUG(10,("is_mangled_component %s (len %lu) ?\n", name, (unsigned long)len));
+
+ /* check the length */
+ if (len > 12 || len < 8)
+ return False;
+
+ /* the best distinguishing characteristic is the ~ */
+ if (name[6] != '~')
+ return False;
+
+ /* check extension */
+ if (len > 8) {
+ if (name[8] != '.')
+ return False;
+ for (i=9; name[i] && i < len; i++) {
+ if (! FLAG_CHECK(name[i], FLAG_ASCII)) {
+ return False;
+ }
+ }
+ }
+
+ /* check lead characters */
+ for (i=0;i<mangle_prefix;i++) {
+ if (! FLAG_CHECK(name[i], FLAG_ASCII)) {
+ return False;
+ }
+ }
+
+ /* check rest of hash */
+ if (! FLAG_CHECK(name[7], FLAG_BASECHAR)) {
+ return False;
+ }
+ for (i=mangle_prefix;i<6;i++) {
+ if (! FLAG_CHECK(name[i], FLAG_BASECHAR)) {
+ return False;
+ }
+ }
+
+ M_DEBUG(10,("is_mangled_component %s (len %lu) -> yes\n", name, (unsigned long)len));
+
+ return True;
+}
+
+
+
+/*
+ determine if a string is possibly in a mangled format, ignoring
+ case
+
+ In this algorithm, mangled names use only pure ascii characters (no
+ multi-byte) so we can avoid doing a UCS2 conversion
+
+ NOTE! This interface must be able to handle a path with unix
+ directory separators. It should return true if any component is
+ mangled
+ */
+static bool is_mangled(const char *name, const struct share_params *parm)
+{
+ const char *p;
+ const char *s;
+
+ M_DEBUG(10,("is_mangled %s ?\n", name));
+
+ for (s=name; (p=strchr(s, '/')); s=p+1) {
+ if (is_mangled_component(s, PTR_DIFF(p, s))) {
+ return True;
+ }
+ }
+
+ /* and the last part ... */
+ return is_mangled_component(s,strlen(s));
+}
+
+
+/*
+ see if a filename is an allowable 8.3 name to return to the client.
+ Note this is not testing if this is a valid Samba mangled name, so
+ the rules are different for is_mangled.
+
+ we are only going to allow ascii characters in 8.3 names, as this
+ simplifies things greatly (it means that we know the string won't
+ get larger when converted from UNIX to DOS formats)
+*/
+
+static const char force_shortname_chars[] = " +,[];=";
+
+static bool is_8_3(const char *name, bool check_case, bool allow_wildcards, const struct share_params *p)
+{
+ int len, i;
+ char *dot_p;
+
+ /* as a special case, the names '.' and '..' are allowable 8.3 names */
+ if (ISDOT(name) || (ISDOTDOT(name))) {
+ return true;
+ }
+
+ /* the simplest test is on the overall length of the
+ filename. Note that we deliberately use the ascii string
+ length (not the multi-byte one) as it is faster, and gives us
+ the result we need in this case. Using strlen_m would not
+ only be slower, it would be incorrect */
+ len = strlen(name);
+ if (len > 12)
+ return False;
+
+ /* find the '.'. Note that once again we use the non-multibyte
+ function */
+ dot_p = strchr(name, '.');
+
+ if (!dot_p) {
+ /* if the name doesn't contain a '.' then its length
+ must be less than 8 */
+ if (len > 8) {
+ return False;
+ }
+ } else {
+ int prefix_len, suffix_len;
+
+ /* if it does contain a dot then the prefix must be <=
+ 8 and the suffix <= 3 in length */
+ prefix_len = PTR_DIFF(dot_p, name);
+ suffix_len = len - (prefix_len+1);
+
+ if (prefix_len > 8 || suffix_len > 3 || suffix_len == 0) {
+ return False;
+ }
+
+ /* a 8.3 name cannot contain more than 1 '.' */
+ if (strchr(dot_p+1, '.')) {
+ return False;
+ }
+ }
+
+ /* the length are all OK. Now check to see if the characters themselves are OK */
+ for (i=0; name[i]; i++) {
+ if (FLAG_CHECK(name[i], FLAG_ILLEGAL)) {
+ return false;
+ }
+ /* note that we may allow wildcard petterns! */
+ if (!allow_wildcards && FLAG_CHECK(name[i], FLAG_WILDCARD)) {
+ return false;
+ }
+ if (((unsigned char)name[i]) > 0x7e) {
+ return false;
+ }
+ if (strchr(force_shortname_chars, name[i])) {
+ return false;
+ }
+ }
+
+ /* it is a good 8.3 name */
+ return True;
+}
+
+
+/*
+ reset the mangling cache on a smb.conf reload. This only really makes sense for
+ mangling backends that have parameters in smb.conf, and as this backend doesn't
+ this is a NULL operation
+*/
+static void mangle_reset(void)
+{
+ /* noop */
+}
+
+
+/*
+ try to find a 8.3 name in the cache, and if found then
+ replace the string with the original long name.
+*/
+static bool lookup_name_from_8_3(TALLOC_CTX *ctx,
+ const char *name,
+ char **pp_out, /* talloced on the given context. */
+ const struct share_params *p)
+{
+ unsigned int hash, multiplier;
+ unsigned int i;
+ char *prefix;
+ char extension[4];
+
+ *pp_out = NULL;
+
+ /* make sure that this is a mangled name from this cache */
+ if (!is_mangled(name, p)) {
+ M_DEBUG(10,("lookup_name_from_8_3: %s -> not mangled\n", name));
+ return False;
+ }
+
+ /* we need to extract the hash from the 8.3 name */
+ hash = base_reverse[(unsigned char)name[7]];
+ for (multiplier=36, i=5;i>=mangle_prefix;i--) {
+ unsigned int v = base_reverse[(unsigned char)name[i]];
+ hash += multiplier * v;
+ multiplier *= 36;
+ }
+
+ /* now look in the prefix cache for that hash */
+ prefix = cache_lookup(ctx, hash);
+ if (!prefix) {
+ M_DEBUG(10,("lookup_name_from_8_3: %s -> %08X -> not found\n",
+ name, hash));
+ return False;
+ }
+
+ /* we found it - construct the full name */
+ if (name[8] == '.') {
+ strncpy(extension, name+9, 3);
+ extension[3] = 0;
+ } else {
+ extension[0] = 0;
+ }
+
+ if (extension[0]) {
+ M_DEBUG(10,("lookup_name_from_8_3: %s -> %s.%s\n",
+ name, prefix, extension));
+ *pp_out = talloc_asprintf(ctx, "%s.%s", prefix, extension);
+ } else {
+ M_DEBUG(10,("lookup_name_from_8_3: %s -> %s\n", name, prefix));
+ *pp_out = talloc_strdup(ctx, prefix);
+ }
+
+ TALLOC_FREE(prefix);
+
+ if (!*pp_out) {
+ M_DEBUG(0,("talloc_fail\n"));
+ return False;
+ }
+
+ return True;
+}
+
+/*
+ look for a DOS reserved name
+*/
+static bool is_reserved_name(const char *name)
+{
+ if (FLAG_CHECK(name[0], FLAG_POSSIBLE1) &&
+ FLAG_CHECK(name[1], FLAG_POSSIBLE2) &&
+ FLAG_CHECK(name[2], FLAG_POSSIBLE3) &&
+ FLAG_CHECK(name[3], FLAG_POSSIBLE4)) {
+ /* a likely match, scan the lot */
+ int i;
+ for (i=0; reserved_names[i]; i++) {
+ int len = strlen(reserved_names[i]);
+ /* note that we match on COM1 as well as COM1.foo */
+ if (strnequal(name, reserved_names[i], len) &&
+ (name[len] == '.' || name[len] == 0)) {
+ return True;
+ }
+ }
+ }
+
+ return False;
+}
+
+/*
+ See if a filename is a legal long filename.
+ A filename ending in a '.' is not legal unless it's "." or "..". JRA.
+ A filename ending in ' ' is not legal either. See bug id #2769.
+*/
+
+static bool is_legal_name(const char *name)
+{
+ const char *dot_pos = NULL;
+ bool alldots = True;
+ size_t numdots = 0;
+
+ while (*name) {
+ if (((unsigned int)name[0]) > 128 && (name[1] != 0)) {
+ /* Possible start of mb character. */
+ size_t size = 0;
+ (void)next_codepoint(name, &size);
+ /*
+ * Note that we're only looking for multibyte
+ * encoding here. No encoding with a length > 1
+ * contains invalid characters.
+ */
+ if (size > 1) {
+ /* Was a mb string. */
+ name += size;
+ continue;
+ }
+ }
+
+ if (FLAG_CHECK(name[0], FLAG_ILLEGAL)) {
+ return False;
+ }
+ if (name[0] == '.') {
+ dot_pos = name;
+ numdots++;
+ } else {
+ alldots = False;
+ }
+ if ((name[0] == ' ') && (name[1] == '\0')) {
+ /* Can't end in ' ' */
+ return False;
+ }
+ name++;
+ }
+
+ if (dot_pos) {
+ if (alldots && (numdots == 1 || numdots == 2))
+ return True; /* . or .. is a valid name */
+
+ /* A valid long name cannot end in '.' */
+ if (dot_pos[1] == '\0')
+ return False;
+ }
+ return True;
+}
+
+static bool must_mangle(const char *name,
+ const struct share_params *p)
+{
+ if (is_reserved_name(name)) {
+ return True;
+ }
+ return !is_legal_name(name);
+}
+
+/*
+ the main forward mapping function, which converts a long filename to
+ a 8.3 name
+
+ if cache83 is not set then we don't cache the result
+
+*/
+static bool hash2_name_to_8_3(const char *name,
+ char new_name[13],
+ bool cache83,
+ int default_case,
+ const struct share_params *p)
+{
+ char *dot_p;
+ char lead_chars[7];
+ char extension[4];
+ unsigned int extension_length, i;
+ unsigned int prefix_len;
+ unsigned int hash, v;
+
+ /* reserved names are handled specially */
+ if (!is_reserved_name(name)) {
+ /* if the name is already a valid 8.3 name then we don't need to
+ * change anything */
+ if (is_legal_name(name) && is_8_3(name, False, False, p)) {
+ strlcpy(new_name, name, 13);
+ return True;
+ }
+ }
+
+ /* find the '.' if any */
+ dot_p = strrchr(name, '.');
+
+ if (dot_p) {
+ /* if the extension contains any illegal characters or
+ is too long or zero length then we treat it as part
+ of the prefix */
+ for (i=0; i<4 && dot_p[i+1]; i++) {
+ if (! FLAG_CHECK(dot_p[i+1], FLAG_ASCII)) {
+ dot_p = NULL;
+ break;
+ }
+ }
+ if (i == 0 || i == 4) {
+ dot_p = NULL;
+ }
+ }
+
+ /* the leading characters in the mangled name is taken from
+ the first characters of the name, if they are ascii otherwise
+ '_' is used
+ */
+ for (i=0;i<mangle_prefix && name[i];i++) {
+ lead_chars[i] = name[i];
+ if (! FLAG_CHECK(lead_chars[i], FLAG_ASCII)) {
+ lead_chars[i] = '_';
+ }
+ lead_chars[i] = toupper_m(lead_chars[i]);
+ }
+ for (;i<mangle_prefix;i++) {
+ lead_chars[i] = '_';
+ }
+
+ /* the prefix is anything up to the first dot */
+ if (dot_p) {
+ prefix_len = PTR_DIFF(dot_p, name);
+ } else {
+ prefix_len = strlen(name);
+ }
+
+ /* the extension of the mangled name is taken from the first 3
+ ascii chars after the dot */
+ extension_length = 0;
+ if (dot_p) {
+ for (i=1; extension_length < 3 && dot_p[i]; i++) {
+ char c = dot_p[i];
+ if (FLAG_CHECK(c, FLAG_ASCII)) {
+ extension[extension_length++] =
+ toupper_m(c);
+ }
+ }
+ }
+
+ /* find the hash for this prefix */
+ v = hash = mangle_hash(name, prefix_len);
+
+ /* now form the mangled name. */
+ for (i=0;i<mangle_prefix;i++) {
+ new_name[i] = lead_chars[i];
+ }
+ new_name[7] = base_forward(v % 36);
+ new_name[6] = '~';
+ for (i=5; i>=mangle_prefix; i--) {
+ v = v / 36;
+ new_name[i] = base_forward(v % 36);
+ }
+
+ /* add the extension */
+ if (extension_length) {
+ new_name[8] = '.';
+ memcpy(&new_name[9], extension, extension_length);
+ new_name[9+extension_length] = 0;
+ } else {
+ new_name[8] = 0;
+ }
+
+ if (cache83) {
+ /* put it in the cache */
+ cache_insert(name, prefix_len, hash);
+ }
+
+ M_DEBUG(10,("hash2_name_to_8_3: %s -> %08X -> %s (cache=%d)\n",
+ name, hash, new_name, cache83));
+
+ return True;
+}
+
+/*
+ the following provides the abstraction layer to make it easier
+ to drop in an alternative mangling implementation */
+static const struct mangle_fns mangle_hash2_fns = {
+ mangle_reset,
+ is_mangled,
+ must_mangle,
+ is_8_3,
+ lookup_name_from_8_3,
+ hash2_name_to_8_3
+};
+
+/* return the methods for this mangling implementation */
+const struct mangle_fns *mangle_hash2_init(void)
+{
+ /* the mangle prefix can only be in the mange 1 to 6 */
+ mangle_prefix = lp_mangle_prefix();
+ if (mangle_prefix > 6) {
+ mangle_prefix = 6;
+ }
+ if (mangle_prefix < 1) {
+ mangle_prefix = 1;
+ }
+
+#if DYNAMIC_MANGLE_TABLES
+ init_tables();
+#endif
+ mangle_reset();
+
+ return &mangle_hash2_fns;
+}
+
+static void posix_mangle_reset(void)
+{;}
+
+static bool posix_is_mangled(const char *s, const struct share_params *p)
+{
+ return False;
+}
+
+static bool posix_must_mangle(const char *s, const struct share_params *p)
+{
+ return False;
+}
+
+static bool posix_is_8_3(const char *fname,
+ bool check_case,
+ bool allow_wildcards,
+ const struct share_params *p)
+{
+ return False;
+}
+
+static bool posix_lookup_name_from_8_3(TALLOC_CTX *ctx,
+ const char *in,
+ char **out, /* talloced on the given context. */
+ const struct share_params *p)
+{
+ return False;
+}
+
+static bool posix_name_to_8_3(const char *in,
+ char out[13],
+ bool cache83,
+ int default_case,
+ const struct share_params *p)
+{
+ memset(out, '\0', 13);
+ return True;
+}
+
+/* POSIX paths backend - no mangle. */
+static const struct mangle_fns posix_mangle_fns = {
+ posix_mangle_reset,
+ posix_is_mangled,
+ posix_must_mangle,
+ posix_is_8_3,
+ posix_lookup_name_from_8_3,
+ posix_name_to_8_3
+};
+
+const struct mangle_fns *posix_mangle_init(void)
+{
+ return &posix_mangle_fns;
+}
diff --git a/source3/smbd/msdfs.c b/source3/smbd/msdfs.c
new file mode 100644
index 0000000..4a322d1
--- /dev/null
+++ b/source3/smbd/msdfs.c
@@ -0,0 +1,1774 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 3.0
+ MSDFS services for Samba
+ Copyright (C) Shirish Kalele 2000
+ Copyright (C) Jeremy Allison 2007
+ Copyright (C) Robin McCorkell 2015
+
+ 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/>.
+
+*/
+
+#define DBGC_CLASS DBGC_MSDFS
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "msdfs.h"
+#include "auth.h"
+#include "../auth/auth_util.h"
+#include "lib/param/loadparm.h"
+#include "libcli/security/security.h"
+#include "librpc/gen_ndr/ndr_dfsblobs.h"
+#include "lib/tsocket/tsocket.h"
+#include "lib/global_contexts.h"
+#include "source3/lib/substitute.h"
+#include "source3/smbd/dir.h"
+
+/**********************************************************************
+ Parse a DFS pathname of the form(s)
+
+ \hostname\service - self referral
+ \hostname\service\remainingpath - Windows referral path
+
+ FIXME! Should we also parse:
+ \hostname\service/remainingpath - POSIX referral path
+ as currently nothing uses this ?
+
+ into the dfs_path components. Strict form.
+
+ Checks DFS path starts with separator.
+ Checks hostname is ours.
+ Ensures servicename (share) is sent, and
+ if so, terminates the name or is followed by
+ \pathname.
+
+ If returned, remainingpath is untouched. Caller must call
+ check_path_syntax() on it.
+
+ Called by all non-fileserver processing (DFS RPC, FSCTL_DFS_GET_REFERRALS)
+ etc. Errors out on any inconsistency in the path.
+**********************************************************************/
+
+static NTSTATUS parse_dfs_path_strict(TALLOC_CTX *ctx,
+ const char *pathname,
+ char **_hostname,
+ char **_servicename,
+ char **_remaining_path)
+{
+ char *pathname_local = NULL;
+ char *p = NULL;
+ const char *hostname = NULL;
+ const char *servicename = NULL;
+ const char *reqpath = NULL;
+ bool my_hostname = false;
+ NTSTATUS status;
+
+ DBG_DEBUG("path = |%s|\n", pathname);
+
+ pathname_local = talloc_strdup(talloc_tos(), pathname);
+ if (pathname_local == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ /*
+ * parse_dfs_path_strict() is called from
+ * get_referred_path() and create_junction()
+ * which use Windows DFS paths of \server\share.
+ */
+
+ /*
+ * Strict DFS paths *must* start with the
+ * path separator '\\'.
+ */
+
+ if (pathname_local[0] != '\\') {
+ DBG_ERR("path %s doesn't start with \\\n",
+ pathname_local);
+ status = NT_STATUS_NOT_FOUND;
+ goto out;
+ }
+
+ /* Now tokenize. */
+ /* Parse out hostname. */
+ p = strchr(pathname_local + 1, '\\');
+ if (p == NULL) {
+ DBG_ERR("can't parse hostname from path %s\n",
+ pathname_local);
+ status = NT_STATUS_NOT_FOUND;
+ goto out;
+ }
+ *p = '\0';
+ hostname = &pathname_local[1];
+
+ DBG_DEBUG("hostname: %s\n", hostname);
+
+ /* Is this really our hostname ? */
+ my_hostname = is_myname_or_ipaddr(hostname);
+ if (!my_hostname) {
+ DBG_ERR("Hostname %s is not ours.\n",
+ hostname);
+ status = NT_STATUS_NOT_FOUND;
+ goto out;
+ }
+
+ servicename = p + 1;
+
+ /*
+ * Find the end of servicename by looking for
+ * a directory separator character. The character
+ * should be '\\' for a Windows path.
+ * If there is no separator, then this is a self-referral
+ * of "\server\share".
+ */
+
+ p = strchr(servicename, '\\');
+ if (p != NULL) {
+ *p = '\0';
+ }
+
+ DBG_DEBUG("servicename: %s\n", servicename);
+
+ if (p == NULL) {
+ /* Client sent self referral "\server\share". */
+ reqpath = "";
+ } else {
+ /* Step past the '\0' we just replaced '\\' with. */
+ reqpath = p + 1;
+ }
+
+ DBG_DEBUG("rest of the path: %s\n", reqpath);
+
+ if (_hostname != NULL) {
+ *_hostname = talloc_strdup(ctx, hostname);
+ if (*_hostname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+ if (_servicename != NULL) {
+ *_servicename = talloc_strdup(ctx, servicename);
+ if (*_servicename == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+ if (_remaining_path != NULL) {
+ *_remaining_path = talloc_strdup(ctx, reqpath);
+ if (*_remaining_path == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+
+ status = NT_STATUS_OK;
+out:
+ TALLOC_FREE(pathname_local);
+ return status;
+}
+
+/********************************************************
+ Fake up a connection struct for the VFS layer, for use in
+ applications (such as the python bindings), that do not want the
+ global working directory changed under them.
+
+ SMB_VFS_CONNECT requires root privileges.
+*********************************************************/
+
+static NTSTATUS create_conn_struct_as_root(TALLOC_CTX *ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg,
+ connection_struct **pconn,
+ int snum,
+ const char *path,
+ const struct auth_session_info *session_info)
+{
+ connection_struct *conn;
+ char *connpath;
+ const char *vfs_user;
+ struct smbd_server_connection *sconn;
+ const char *servicename = lp_const_servicename(snum);
+ bool ok;
+
+ sconn = talloc_zero(ctx, struct smbd_server_connection);
+ if (sconn == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ sconn->ev_ctx = ev;
+ sconn->msg_ctx = msg;
+
+ conn = conn_new(sconn);
+ if (conn == NULL) {
+ TALLOC_FREE(sconn);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Now we have conn, we need to make sconn a child of conn,
+ * for a proper talloc tree */
+ talloc_steal(conn, sconn);
+
+ if (snum == -1 && servicename == NULL) {
+ servicename = "Unknown Service (snum == -1)";
+ }
+
+ connpath = talloc_strdup(conn, path);
+ if (!connpath) {
+ TALLOC_FREE(conn);
+ return NT_STATUS_NO_MEMORY;
+ }
+ connpath = talloc_string_sub(conn,
+ connpath,
+ "%S",
+ servicename);
+ if (!connpath) {
+ TALLOC_FREE(conn);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* needed for smbd_vfs_init() */
+
+ conn->params->service = snum;
+ conn->cnum = TID_FIELD_INVALID;
+
+ SMB_ASSERT(session_info != NULL);
+
+ conn->session_info = copy_session_info(conn, session_info);
+ if (conn->session_info == NULL) {
+ DBG_ERR("copy_serverinfo failed\n");
+ TALLOC_FREE(conn);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* unix_info could be NULL in session_info */
+ if (conn->session_info->unix_info != NULL) {
+ vfs_user = conn->session_info->unix_info->unix_name;
+ } else {
+ vfs_user = get_current_username();
+ }
+
+ conn_setup_case_options(conn);
+
+ set_conn_connectpath(conn, connpath);
+
+ /*
+ * New code to check if there's a share security descriptor
+ * added from NT server manager. This is done after the
+ * smb.conf checks are done as we need a uid and token. JRA.
+ *
+ */
+ share_access_check(conn->session_info->security_token,
+ servicename,
+ MAXIMUM_ALLOWED_ACCESS,
+ &conn->share_access);
+
+ if ((conn->share_access & FILE_WRITE_DATA) == 0) {
+ if ((conn->share_access & FILE_READ_DATA) == 0) {
+ /* No access, read or write. */
+ DBG_WARNING("connection to %s "
+ "denied due to security "
+ "descriptor.\n",
+ servicename);
+ conn_free(conn);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ conn->read_only = true;
+ }
+
+ if (!smbd_vfs_init(conn)) {
+ NTSTATUS status = map_nt_error_from_unix(errno);
+ DEBUG(0,("create_conn_struct: smbd_vfs_init failed.\n"));
+ conn_free(conn);
+ return status;
+ }
+
+ /* this must be the first filesystem operation that we do */
+ if (SMB_VFS_CONNECT(conn, servicename, vfs_user) < 0) {
+ DEBUG(0,("VFS connect failed!\n"));
+ conn_free(conn);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ ok = canonicalize_connect_path(conn);
+ if (!ok) {
+ DBG_ERR("Failed to canonicalize sharepath\n");
+ conn_free(conn);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ conn->fs_capabilities = SMB_VFS_FS_CAPABILITIES(conn, &conn->ts_res);
+ conn->tcon_done = true;
+ *pconn = talloc_move(ctx, &conn);
+
+ return NT_STATUS_OK;
+}
+
+static int conn_struct_tos_destructor(struct conn_struct_tos *c)
+{
+ if (c->oldcwd_fname != NULL) {
+ vfs_ChDir(c->conn, c->oldcwd_fname);
+ TALLOC_FREE(c->oldcwd_fname);
+ }
+ SMB_VFS_DISCONNECT(c->conn);
+ conn_free(c->conn);
+ return 0;
+}
+
+/********************************************************
+ Fake up a connection struct for the VFS layer, for use in
+ applications (such as the python bindings), that do not want the
+ global working directory changed under them.
+
+ SMB_VFS_CONNECT requires root privileges.
+ This temporary uses become_root() and unbecome_root().
+
+ But further impersonation has to be cone by the caller.
+*********************************************************/
+NTSTATUS create_conn_struct_tos(struct messaging_context *msg,
+ int snum,
+ const char *path,
+ const struct auth_session_info *session_info,
+ struct conn_struct_tos **_c)
+{
+ struct conn_struct_tos *c = NULL;
+ struct tevent_context *ev = NULL;
+ NTSTATUS status;
+
+ *_c = NULL;
+
+ c = talloc_zero(talloc_tos(), struct conn_struct_tos);
+ if (c == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ev = samba_tevent_context_init(c);
+ if (ev == NULL) {
+ TALLOC_FREE(c);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ become_root();
+ status = create_conn_struct_as_root(c,
+ ev,
+ msg,
+ &c->conn,
+ snum,
+ path,
+ session_info);
+ unbecome_root();
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(c);
+ return status;
+ }
+
+ talloc_set_destructor(c, conn_struct_tos_destructor);
+
+ *_c = c;
+ return NT_STATUS_OK;
+}
+
+/********************************************************
+ Fake up a connection struct for the VFS layer.
+ Note: this performs a vfs connect and CHANGES CWD !!!! JRA.
+
+ See also the comment for create_conn_struct_tos() above!
+
+ The CWD change is reverted by the destructor of
+ conn_struct_tos when the current talloc_tos() is destroyed.
+*********************************************************/
+NTSTATUS create_conn_struct_tos_cwd(struct messaging_context *msg,
+ int snum,
+ const char *path,
+ const struct auth_session_info *session_info,
+ struct conn_struct_tos **_c)
+{
+ struct conn_struct_tos *c = NULL;
+ struct smb_filename smb_fname_connectpath = {0};
+ NTSTATUS status;
+
+ *_c = NULL;
+
+ status = create_conn_struct_tos(msg,
+ snum,
+ path,
+ session_info,
+ &c);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * Windows seems to insist on doing trans2getdfsreferral() calls on
+ * the IPC$ share as the anonymous user. If we try to chdir as that
+ * user we will fail.... WTF ? JRA.
+ */
+
+ c->oldcwd_fname = vfs_GetWd(c, c->conn);
+ if (c->oldcwd_fname == NULL) {
+ status = map_nt_error_from_unix(errno);
+ DEBUG(3, ("vfs_GetWd failed: %s\n", strerror(errno)));
+ TALLOC_FREE(c);
+ return status;
+ }
+
+ smb_fname_connectpath = (struct smb_filename) {
+ .base_name = c->conn->connectpath
+ };
+
+ if (vfs_ChDir(c->conn, &smb_fname_connectpath) != 0) {
+ status = map_nt_error_from_unix(errno);
+ DBG_NOTICE("Can't ChDir to new conn path %s. "
+ "Error was %s\n",
+ c->conn->connectpath, strerror(errno));
+ TALLOC_FREE(c->oldcwd_fname);
+ TALLOC_FREE(c);
+ return status;
+ }
+
+ *_c = c;
+ return NT_STATUS_OK;
+}
+
+/********************************************************
+ Fake up a connection struct for the VFS layer.
+ This takes an TALLOC_CTX and tevent_context from the
+ caller and the resulting connection_struct is stable
+ across the lifetime of mem_ctx and ev.
+
+ Note: this performs a vfs connect and changes cwd.
+
+ See also the comment for create_conn_struct_tos() above!
+*********************************************************/
+
+NTSTATUS create_conn_struct_cwd(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg,
+ const struct auth_session_info *session_info,
+ int snum,
+ const char *path,
+ struct connection_struct **c)
+{
+ NTSTATUS status;
+
+ become_root();
+ status = create_conn_struct_as_root(mem_ctx,
+ ev,
+ msg,
+ c,
+ snum,
+ path,
+ session_info);
+ unbecome_root();
+ return status;
+}
+
+static void shuffle_strlist(char **list, int count)
+{
+ int i;
+ uint32_t r;
+ char *tmp;
+
+ for (i = count; i > 1; i--) {
+ r = generate_random() % i;
+
+ tmp = list[i-1];
+ list[i-1] = list[r];
+ list[r] = tmp;
+ }
+}
+
+/**********************************************************************
+ Parse the contents of a symlink to verify if it is an msdfs referral
+ A valid referral is of the form:
+
+ msdfs:server1\share1,server2\share2
+ msdfs:server1\share1\pathname,server2\share2\pathname
+ msdfs:server1/share1,server2/share2
+ msdfs:server1/share1/pathname,server2/share2/pathname.
+
+ Note that the alternate paths returned here must be of the canonicalized
+ form:
+
+ \server\share or
+ \server\share\path\to\file,
+
+ even in posix path mode. This is because we have no knowledge if the
+ server we're referring to understands posix paths.
+ **********************************************************************/
+
+bool parse_msdfs_symlink(TALLOC_CTX *ctx,
+ bool shuffle_referrals,
+ const char *target,
+ struct referral **ppreflist,
+ size_t *prefcount)
+{
+ char *temp = NULL;
+ char *prot;
+ char **alt_path = NULL;
+ size_t count = 0, i;
+ struct referral *reflist = NULL;
+ char *saveptr;
+
+ temp = talloc_strdup(ctx, target);
+ if (!temp) {
+ return false;
+ }
+ prot = strtok_r(temp, ":", &saveptr);
+ if (!prot) {
+ DEBUG(0,("parse_msdfs_symlink: invalid path !\n"));
+ TALLOC_FREE(temp);
+ return false;
+ }
+
+ alt_path = talloc_array(ctx, char *, MAX_REFERRAL_COUNT);
+ if (!alt_path) {
+ TALLOC_FREE(temp);
+ return false;
+ }
+
+ /* parse out the alternate paths */
+ while((count<MAX_REFERRAL_COUNT) &&
+ ((alt_path[count] = strtok_r(NULL, ",", &saveptr)) != NULL)) {
+ count++;
+ }
+
+ /* shuffle alternate paths */
+ if (shuffle_referrals) {
+ shuffle_strlist(alt_path, count);
+ }
+
+ DBG_DEBUG("count=%zu\n", count);
+
+ if (count) {
+ reflist = talloc_zero_array(ctx,
+ struct referral, count);
+ if(reflist == NULL) {
+ TALLOC_FREE(temp);
+ TALLOC_FREE(alt_path);
+ return false;
+ }
+ } else {
+ reflist = NULL;
+ }
+
+ for(i=0;i<count;i++) {
+ char *p;
+
+ /* Canonicalize link target.
+ * Replace all /'s in the path by a \ */
+ string_replace(alt_path[i], '/', '\\');
+
+ /* Remove leading '\\'s */
+ p = alt_path[i];
+ while (*p && (*p == '\\')) {
+ p++;
+ }
+
+ reflist[i].alternate_path = talloc_asprintf(reflist,
+ "\\%s",
+ p);
+ if (!reflist[i].alternate_path) {
+ TALLOC_FREE(temp);
+ TALLOC_FREE(alt_path);
+ TALLOC_FREE(reflist);
+ return false;
+ }
+
+ reflist[i].proximity = 0;
+ reflist[i].ttl = REFERRAL_TTL;
+ DBG_DEBUG("Created alt path: %s\n",
+ reflist[i].alternate_path);
+ }
+
+ if (ppreflist != NULL) {
+ *ppreflist = reflist;
+ } else {
+ TALLOC_FREE(reflist);
+ }
+ if (prefcount != NULL) {
+ *prefcount = count;
+ }
+ TALLOC_FREE(temp);
+ TALLOC_FREE(alt_path);
+ return true;
+}
+
+/**********************************************************************
+ Returns true if the unix path is a valid msdfs symlink.
+**********************************************************************/
+
+bool is_msdfs_link(struct files_struct *dirfsp,
+ struct smb_filename *atname)
+{
+ NTSTATUS status = SMB_VFS_READ_DFS_PATHAT(dirfsp->conn,
+ talloc_tos(),
+ dirfsp,
+ atname,
+ NULL,
+ NULL);
+ return (NT_STATUS_IS_OK(status));
+}
+
+/*****************************************************************
+ Used by other functions to decide if a dfs path is remote,
+ and to get the list of referred locations for that remote path.
+
+ consumedcntp: how much of the dfs path is being redirected. the client
+ should try the remaining path on the redirected server.
+*****************************************************************/
+
+static NTSTATUS dfs_path_lookup(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ const char *dfspath, /* Incoming complete dfs path */
+ const char *reqpath, /* Parsed out remaining path. */
+ uint32_t ucf_flags,
+ size_t *consumedcntp,
+ struct referral **ppreflist,
+ size_t *preferral_count)
+{
+ NTSTATUS status;
+ struct smb_filename *parent_smb_fname = NULL;
+ struct smb_filename *smb_fname_rel = NULL;
+ NTTIME twrp = 0;
+ char *local_pathname = NULL;
+ char *last_component = NULL;
+ char *atname = NULL;
+ size_t removed_components = 0;
+ bool posix = (ucf_flags & UCF_POSIX_PATHNAMES);
+ char *p = NULL;
+ char *canon_dfspath = NULL;
+
+ DBG_DEBUG("Conn path = %s reqpath = %s\n", conn->connectpath, reqpath);
+
+ local_pathname = talloc_strdup(ctx, reqpath);
+ if (local_pathname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ /* We know reqpath isn't a DFS path. */
+ ucf_flags &= ~UCF_DFS_PATHNAME;
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(local_pathname, &twrp);
+ ucf_flags &= ~UCF_GMT_PATHNAME;
+ }
+
+ /*
+ * We should have been given a DFS path to resolve.
+ * This should return NT_STATUS_PATH_NOT_COVERED.
+ *
+ * Do a pathname walk, stripping off components
+ * until we get NT_STATUS_OK instead of
+ * NT_STATUS_PATH_NOT_COVERED.
+ *
+ * Fail on any other error.
+ */
+
+ for (;;) {
+ TALLOC_CTX *frame = NULL;
+ struct files_struct *dirfsp = NULL;
+ struct smb_filename *smb_fname_walk = NULL;
+
+ TALLOC_FREE(parent_smb_fname);
+
+ /*
+ * Use a local stackframe as filename_convert_dirfsp()
+ * opens handles on the last two components in the path.
+ * Allow these to be freed as we step back through
+ * the local_pathname.
+ */
+ frame = talloc_stackframe();
+ status = filename_convert_dirfsp(frame,
+ conn,
+ local_pathname,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_fname_walk);
+ /* If we got a name, save it. */
+ if (smb_fname_walk != NULL) {
+ parent_smb_fname = talloc_move(ctx, &smb_fname_walk);
+ }
+ TALLOC_FREE(frame);
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_PATH_NOT_COVERED)) {
+ /*
+ * For any other status than NT_STATUS_PATH_NOT_COVERED
+ * (including NT_STATUS_OK) we exit the walk.
+ * If it's an error we catch it outside the loop.
+ */
+ break;
+ }
+
+ /* Step back one component and save it off as last_component. */
+ TALLOC_FREE(last_component);
+ p = strrchr(local_pathname, '/');
+ if (p == NULL) {
+ /*
+ * We removed all components.
+ * Go around once more to make
+ * sure we can open the root '\0'.
+ */
+ last_component = talloc_strdup(ctx, local_pathname);
+ *local_pathname = '\0';
+ } else {
+ last_component = talloc_strdup(ctx, p+1);
+ *p = '\0';
+ }
+ if (last_component == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ /* Integer wrap check. */
+ if (removed_components + 1 < removed_components) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+ removed_components++;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dfspath = %s. reqpath = %s. Error %s.\n",
+ dfspath,
+ reqpath,
+ nt_errstr(status));
+ goto out;
+ }
+
+ if (parent_smb_fname->fsp == NULL) {
+ /* Unable to open parent. */
+ DBG_DEBUG("dfspath = %s. reqpath = %s. "
+ "Unable to open parent directory (%s).\n",
+ dfspath,
+ reqpath,
+ smb_fname_str_dbg(parent_smb_fname));
+ status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ goto out;
+ }
+
+ if (removed_components == 0) {
+ /*
+ * We never got NT_STATUS_PATH_NOT_COVERED.
+ * There was no DFS redirect.
+ */
+ DBG_DEBUG("dfspath = %s. reqpath = %s. "
+ "No removed components.\n",
+ dfspath,
+ reqpath);
+ status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ goto out;
+ }
+
+ /*
+ * One of the removed_components was the MSDFS link
+ * at the end. We need to count this in the resolved
+ * path below, so remove one from removed_components.
+ */
+ removed_components--;
+
+ /*
+ * Now parent_smb_fname->fsp is the parent directory dirfsp,
+ * last_component is the untranslated MS-DFS link name.
+ * Search for it in the parent directory to get the real
+ * filename on disk.
+ */
+ status = get_real_filename_at(parent_smb_fname->fsp,
+ last_component,
+ ctx,
+ &atname);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dfspath = %s. reqpath = %s "
+ "get_real_filename_at(%s, %s) error (%s)\n",
+ dfspath,
+ reqpath,
+ smb_fname_str_dbg(parent_smb_fname),
+ last_component,
+ nt_errstr(status));
+ goto out;
+ }
+
+ smb_fname_rel = synthetic_smb_fname(ctx,
+ atname,
+ NULL,
+ NULL,
+ twrp,
+ posix ? SMB_FILENAME_POSIX_PATH : 0);
+ if (smb_fname_rel == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ /* Get the referral to return. */
+ status = SMB_VFS_READ_DFS_PATHAT(conn,
+ ctx,
+ parent_smb_fname->fsp,
+ smb_fname_rel,
+ ppreflist,
+ preferral_count);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dfspath = %s. reqpath = %s. "
+ "SMB_VFS_READ_DFS_PATHAT(%s, %s) error (%s)\n",
+ dfspath,
+ reqpath,
+ smb_fname_str_dbg(parent_smb_fname),
+ smb_fname_str_dbg(smb_fname_rel),
+ nt_errstr(status));
+ goto out;
+ }
+
+ /*
+ * Now we must work out how much of the
+ * given pathname we consumed.
+ */
+ canon_dfspath = talloc_strdup(ctx, dfspath);
+ if (!canon_dfspath) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ /* Canonicalize the raw dfspath. */
+ string_replace(canon_dfspath, '\\', '/');
+
+ /*
+ * reqpath comes out of parse_dfs_path(), so it has
+ * no trailing backslash. Make sure that canon_dfspath hasn't either.
+ */
+ trim_char(canon_dfspath, 0, '/');
+
+ DBG_DEBUG("Unconsumed path: %s\n", canon_dfspath);
+
+ while (removed_components > 0) {
+ p = strrchr(canon_dfspath, '/');
+ if (p != NULL) {
+ *p = '\0';
+ }
+ removed_components--;
+ if (p == NULL && removed_components != 0) {
+ DBG_ERR("Component mismatch. path = %s, "
+ "%zu components left\n",
+ canon_dfspath,
+ removed_components);
+ status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ goto out;
+ }
+ }
+ *consumedcntp = strlen(canon_dfspath);
+ DBG_DEBUG("Path consumed: %s (%zu)\n", canon_dfspath, *consumedcntp);
+ status = NT_STATUS_OK;
+
+ out:
+
+ TALLOC_FREE(parent_smb_fname);
+ TALLOC_FREE(local_pathname);
+ TALLOC_FREE(last_component);
+ TALLOC_FREE(atname);
+ TALLOC_FREE(smb_fname_rel);
+ TALLOC_FREE(canon_dfspath);
+ return status;
+}
+
+/**********************************************************************
+ Return a self referral.
+**********************************************************************/
+
+static NTSTATUS self_ref(TALLOC_CTX *ctx,
+ const char *dfs_path,
+ struct junction_map *jucn,
+ size_t *consumedcntp,
+ bool *self_referralp)
+{
+ struct referral *ref;
+
+ *self_referralp = True;
+
+ jucn->referral_count = 1;
+ if((ref = talloc_zero(ctx, struct referral)) == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ref->alternate_path = talloc_strdup(ctx, dfs_path);
+ if (!ref->alternate_path) {
+ TALLOC_FREE(ref);
+ return NT_STATUS_NO_MEMORY;
+ }
+ ref->proximity = 0;
+ ref->ttl = REFERRAL_TTL;
+ jucn->referral_list = ref;
+ *consumedcntp = strlen(dfs_path);
+ return NT_STATUS_OK;
+}
+
+/**********************************************************************
+ Gets valid referrals for a dfs path and fills up the
+ junction_map structure.
+**********************************************************************/
+
+NTSTATUS get_referred_path(TALLOC_CTX *ctx,
+ struct auth_session_info *session_info,
+ const char *dfs_path,
+ const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ struct junction_map *jucn,
+ size_t *consumedcntp,
+ bool *self_referralp)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct conn_struct_tos *c = NULL;
+ struct connection_struct *conn = NULL;
+ char *servicename = NULL;
+ char *reqpath = NULL;
+ int snum;
+ NTSTATUS status = NT_STATUS_NOT_FOUND;
+
+ *self_referralp = False;
+
+ status = parse_dfs_path_strict(
+ frame,
+ dfs_path,
+ NULL, /* hostname */
+ &servicename,
+ &reqpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* Path referrals are always non-POSIX. */
+ status = check_path_syntax(reqpath, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ jucn->service_name = talloc_strdup(ctx, servicename);
+ jucn->volume_name = talloc_strdup(ctx, reqpath);
+ if (!jucn->service_name || !jucn->volume_name) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Verify the share is a dfs root */
+ snum = lp_servicenumber(jucn->service_name);
+ if(snum < 0) {
+ char *service_name = NULL;
+ if ((snum = find_service(ctx, jucn->service_name, &service_name)) < 0) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NOT_FOUND;
+ }
+ if (!service_name) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ TALLOC_FREE(jucn->service_name);
+ jucn->service_name = talloc_strdup(ctx, service_name);
+ if (!jucn->service_name) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ if (!lp_msdfs_root(snum) && (*lp_msdfs_proxy(talloc_tos(), lp_sub, snum) == '\0')) {
+ DEBUG(3,("get_referred_path: |%s| in dfs path %s is not "
+ "a dfs root.\n",
+ servicename, dfs_path));
+ TALLOC_FREE(frame);
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ /*
+ * Self referrals are tested with a anonymous IPC connection and
+ * a GET_DFS_REFERRAL call to \\server\share. (which means
+ * dp.reqpath[0] points to an empty string). create_conn_struct cd's
+ * into the directory and will fail if it cannot (as the anonymous
+ * user). Cope with this.
+ */
+
+ if (reqpath[0] == '\0') {
+ char *tmp;
+ struct referral *ref;
+ size_t refcount;
+
+ if (*lp_msdfs_proxy(talloc_tos(), lp_sub, snum) == '\0') {
+ TALLOC_FREE(frame);
+ return self_ref(ctx,
+ dfs_path,
+ jucn,
+ consumedcntp,
+ self_referralp);
+ }
+
+ /*
+ * It's an msdfs proxy share. Redirect to
+ * the configured target share.
+ */
+
+ tmp = talloc_asprintf(frame, "msdfs:%s",
+ lp_msdfs_proxy(frame, lp_sub, snum));
+ if (tmp == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!parse_msdfs_symlink(ctx,
+ lp_msdfs_shuffle_referrals(snum),
+ tmp,
+ &ref,
+ &refcount)) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ jucn->referral_count = refcount;
+ jucn->referral_list = ref;
+ *consumedcntp = strlen(dfs_path);
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ status = create_conn_struct_tos_cwd(global_messaging_context(),
+ snum,
+ lp_path(frame, lp_sub, snum),
+ session_info,
+ &c);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+ conn = c->conn;
+
+ /*
+ * TODO
+ *
+ * The remote and local address should be passed down to
+ * create_conn_struct_cwd.
+ */
+ if (conn->sconn->remote_address == NULL) {
+ conn->sconn->remote_address =
+ tsocket_address_copy(remote_address, conn->sconn);
+ if (conn->sconn->remote_address == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ if (conn->sconn->local_address == NULL) {
+ conn->sconn->local_address =
+ tsocket_address_copy(local_address, conn->sconn);
+ if (conn->sconn->local_address == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ status = dfs_path_lookup(ctx,
+ conn,
+ dfs_path,
+ reqpath,
+ 0, /* ucf_flags */
+ consumedcntp,
+ &jucn->referral_list,
+ &jucn->referral_count);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_NOTICE("No valid referrals for path %s (%s)\n",
+ dfs_path,
+ nt_errstr(status));
+ }
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/******************************************************************
+ Set up the DFS referral for the dfs pathname. This call returns
+ the amount of the path covered by this server, and where the
+ client should be redirected to. This is the meat of the
+ TRANS2_GET_DFS_REFERRAL call.
+******************************************************************/
+
+int setup_dfs_referral(connection_struct *orig_conn,
+ const char *dfs_path,
+ int max_referral_level,
+ char **ppdata, NTSTATUS *pstatus)
+{
+ char *pdata = *ppdata;
+ int reply_size = 0;
+ struct dfs_GetDFSReferral *r;
+ DATA_BLOB blob = data_blob_null;
+ NTSTATUS status;
+ enum ndr_err_code ndr_err;
+
+ r = talloc_zero(talloc_tos(), struct dfs_GetDFSReferral);
+ if (r == NULL) {
+ *pstatus = NT_STATUS_NO_MEMORY;
+ return -1;
+ }
+
+ r->in.req.max_referral_level = max_referral_level;
+ r->in.req.servername = talloc_strdup(r, dfs_path);
+ if (r->in.req.servername == NULL) {
+ talloc_free(r);
+ *pstatus = NT_STATUS_NO_MEMORY;
+ return -1;
+ }
+
+ status = SMB_VFS_GET_DFS_REFERRALS(orig_conn, r);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(r);
+ *pstatus = status;
+ return -1;
+ }
+
+ ndr_err = ndr_push_struct_blob(&blob, r,
+ r->out.resp,
+ (ndr_push_flags_fn_t)ndr_push_dfs_referral_resp);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ TALLOC_FREE(r);
+ *pstatus = NT_STATUS_INVALID_PARAMETER;
+ return -1;
+ }
+
+ pdata = (char *)SMB_REALLOC(pdata, blob.length);
+ if(pdata == NULL) {
+ TALLOC_FREE(r);
+ DEBUG(0,("referral setup:"
+ "malloc failed for Realloc!\n"));
+ return -1;
+ }
+ *ppdata = pdata;
+ reply_size = blob.length;
+ memcpy(pdata, blob.data, blob.length);
+ TALLOC_FREE(r);
+
+ *pstatus = NT_STATUS_OK;
+ return reply_size;
+}
+
+/**********************************************************************
+ The following functions are called by the NETDFS RPC pipe functions
+ **********************************************************************/
+
+/*********************************************************************
+ Creates a junction structure from a DFS pathname
+**********************************************************************/
+
+bool create_junction(TALLOC_CTX *ctx,
+ const char *dfs_path,
+ struct junction_map *jucn)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int snum;
+ char *servicename = NULL;
+ char *reqpath = NULL;
+ NTSTATUS status;
+
+ status = parse_dfs_path_strict(
+ ctx,
+ dfs_path,
+ NULL,
+ &servicename,
+ &reqpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ return False;
+ }
+
+ /* Check for a non-DFS share */
+ snum = lp_servicenumber(servicename);
+
+ if(snum < 0 || !lp_msdfs_root(snum)) {
+ DEBUG(4,("create_junction: %s is not an msdfs root.\n",
+ servicename));
+ return False;
+ }
+
+ /* Junction create paths are always non-POSIX. */
+ status = check_path_syntax(reqpath, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ jucn->service_name = talloc_strdup(ctx, servicename);
+ jucn->volume_name = talloc_strdup(ctx, reqpath);
+ jucn->comment = lp_comment(ctx, lp_sub, snum);
+
+ if (!jucn->service_name || !jucn->volume_name || ! jucn->comment) {
+ return False;
+ }
+ return True;
+}
+
+/**********************************************************************
+ Forms a valid Unix pathname from the junction
+ **********************************************************************/
+
+static bool junction_to_local_path_tos(const struct junction_map *jucn,
+ struct auth_session_info *session_info,
+ char **pp_path_out,
+ connection_struct **conn_out)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct conn_struct_tos *c = NULL;
+ int snum;
+ char *path_out = NULL;
+ NTSTATUS status;
+
+ snum = lp_servicenumber(jucn->service_name);
+ if(snum < 0) {
+ return False;
+ }
+ status = create_conn_struct_tos_cwd(global_messaging_context(),
+ snum,
+ lp_path(talloc_tos(), lp_sub, snum),
+ session_info,
+ &c);
+ if (!NT_STATUS_IS_OK(status)) {
+ return False;
+ }
+
+ path_out = talloc_asprintf(c,
+ "%s/%s",
+ lp_path(talloc_tos(), lp_sub, snum),
+ jucn->volume_name);
+ if (path_out == NULL) {
+ TALLOC_FREE(c);
+ return False;
+ }
+ *pp_path_out = path_out;
+ *conn_out = c->conn;
+ return True;
+}
+
+/*
+ * Create a msdfs string in Samba format we can store
+ * in a filesystem object (currently a symlink).
+ */
+
+char *msdfs_link_string(TALLOC_CTX *ctx,
+ const struct referral *reflist,
+ size_t referral_count)
+{
+ char *refpath = NULL;
+ bool insert_comma = false;
+ char *msdfs_link = NULL;
+ size_t i;
+
+ /* Form the msdfs_link contents */
+ msdfs_link = talloc_strdup(ctx, "msdfs:");
+ if (msdfs_link == NULL) {
+ goto err;
+ }
+
+ for( i= 0; i < referral_count; i++) {
+ refpath = talloc_strdup(ctx, reflist[i].alternate_path);
+
+ if (refpath == NULL) {
+ goto err;
+ }
+
+ /* Alternate paths always use Windows separators. */
+ trim_char(refpath, '\\', '\\');
+ if (*refpath == '\0') {
+ if (i == 0) {
+ insert_comma = false;
+ }
+ continue;
+ }
+ if (i > 0 && insert_comma) {
+ msdfs_link = talloc_asprintf_append_buffer(msdfs_link,
+ ",%s",
+ refpath);
+ } else {
+ msdfs_link = talloc_asprintf_append_buffer(msdfs_link,
+ "%s",
+ refpath);
+ }
+
+ if (msdfs_link == NULL) {
+ goto err;
+ }
+
+ if (!insert_comma) {
+ insert_comma = true;
+ }
+
+ TALLOC_FREE(refpath);
+ }
+
+ return msdfs_link;
+
+ err:
+
+ TALLOC_FREE(refpath);
+ TALLOC_FREE(msdfs_link);
+ return NULL;
+}
+
+bool create_msdfs_link(const struct junction_map *jucn,
+ struct auth_session_info *session_info)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ char *path = NULL;
+ connection_struct *conn;
+ struct smb_filename *smb_fname = NULL;
+ struct smb_filename *parent_fname = NULL;
+ struct smb_filename *at_fname = NULL;
+ bool ok;
+ NTSTATUS status;
+ bool ret = false;
+
+ ok = junction_to_local_path_tos(jucn, session_info, &path, &conn);
+ if (!ok) {
+ goto out;
+ }
+
+ if (!CAN_WRITE(conn)) {
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int snum = lp_servicenumber(jucn->service_name);
+
+ DBG_WARNING("Can't create DFS entry on read-only share %s\n",
+ lp_servicename(frame, lp_sub, snum));
+ goto out;
+ }
+
+ smb_fname = synthetic_smb_fname(frame,
+ path,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname == NULL) {
+ goto out;
+ }
+
+ status = parent_pathref(frame,
+ conn->cwd_fsp,
+ smb_fname,
+ &parent_fname,
+ &at_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = SMB_VFS_CREATE_DFS_PATHAT(conn,
+ parent_fname->fsp,
+ at_fname,
+ jucn->referral_list,
+ jucn->referral_count);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) {
+ int retval = SMB_VFS_UNLINKAT(conn,
+ parent_fname->fsp,
+ at_fname,
+ 0);
+ if (retval != 0) {
+ goto out;
+ }
+ }
+ status = SMB_VFS_CREATE_DFS_PATHAT(conn,
+ parent_fname->fsp,
+ at_fname,
+ jucn->referral_list,
+ jucn->referral_count);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("SMB_VFS_CREATE_DFS_PATHAT failed "
+ "%s - Error: %s\n",
+ path,
+ nt_errstr(status));
+ goto out;
+ }
+ }
+
+ ret = true;
+
+out:
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+bool remove_msdfs_link(const struct junction_map *jucn,
+ struct auth_session_info *session_info)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ char *path = NULL;
+ connection_struct *conn;
+ bool ret = False;
+ struct smb_filename *smb_fname;
+ struct smb_filename *parent_fname = NULL;
+ struct smb_filename *at_fname = NULL;
+ NTSTATUS status;
+ bool ok;
+ int retval;
+
+ ok = junction_to_local_path_tos(jucn, session_info, &path, &conn);
+ if (!ok) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ if (!CAN_WRITE(conn)) {
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int snum = lp_servicenumber(jucn->service_name);
+
+ DBG_WARNING("Can't remove DFS entry on read-only share %s\n",
+ lp_servicename(frame, lp_sub, snum));
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ smb_fname = synthetic_smb_fname(frame,
+ path,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname == NULL) {
+ TALLOC_FREE(frame);
+ errno = ENOMEM;
+ return false;
+ }
+
+ status = parent_pathref(frame,
+ conn->cwd_fsp,
+ smb_fname,
+ &parent_fname,
+ &at_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ retval = SMB_VFS_UNLINKAT(conn,
+ parent_fname->fsp,
+ at_fname,
+ 0);
+ if (retval == 0) {
+ ret = True;
+ }
+
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+/*********************************************************************
+ Return the number of DFS links at the root of this share.
+*********************************************************************/
+
+static size_t count_dfs_links(TALLOC_CTX *ctx,
+ struct auth_session_info *session_info,
+ int snum)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ size_t cnt = 0;
+ const char *dname = NULL;
+ char *talloced = NULL;
+ const char *connect_path = lp_path(frame, lp_sub, snum);
+ const char *msdfs_proxy = lp_msdfs_proxy(frame, lp_sub, snum);
+ struct conn_struct_tos *c = NULL;
+ connection_struct *conn = NULL;
+ NTSTATUS status;
+ struct smb_filename *smb_fname = NULL;
+ struct smb_Dir *dir_hnd = NULL;
+
+ if(*connect_path == '\0') {
+ TALLOC_FREE(frame);
+ return 0;
+ }
+
+ /*
+ * Fake up a connection struct for the VFS layer.
+ */
+
+ status = create_conn_struct_tos_cwd(global_messaging_context(),
+ snum,
+ connect_path,
+ session_info,
+ &c);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("create_conn_struct failed: %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return 0;
+ }
+ conn = c->conn;
+
+ /* Count a link for the msdfs root - convention */
+ cnt = 1;
+
+ /* No more links if this is an msdfs proxy. */
+ if (*msdfs_proxy != '\0') {
+ goto out;
+ }
+
+ smb_fname = synthetic_smb_fname(frame,
+ ".",
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname == NULL) {
+ goto out;
+ }
+
+ /* Now enumerate all dfs links */
+ status = OpenDir(frame,
+ conn,
+ smb_fname,
+ NULL,
+ 0,
+ &dir_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ goto out;
+ }
+
+ while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) {
+ struct smb_filename *smb_dname =
+ synthetic_smb_fname(frame,
+ dname,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_dname == NULL) {
+ goto out;
+ }
+ if (is_msdfs_link(dir_hnd_fetch_fsp(dir_hnd), smb_dname)) {
+ if (cnt + 1 < cnt) {
+ cnt = 0;
+ goto out;
+ }
+ cnt++;
+ }
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(smb_dname);
+ }
+
+out:
+ TALLOC_FREE(frame);
+ return cnt;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static int form_junctions(TALLOC_CTX *ctx,
+ struct auth_session_info *session_info,
+ int snum,
+ struct junction_map *jucn,
+ size_t jn_remain)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ size_t cnt = 0;
+ const char *dname = NULL;
+ char *talloced = NULL;
+ const char *connect_path = lp_path(frame, lp_sub, snum);
+ char *service_name = lp_servicename(frame, lp_sub, snum);
+ const char *msdfs_proxy = lp_msdfs_proxy(frame, lp_sub, snum);
+ struct conn_struct_tos *c = NULL;
+ connection_struct *conn = NULL;
+ struct referral *ref = NULL;
+ struct smb_filename *smb_fname = NULL;
+ struct smb_Dir *dir_hnd = NULL;
+ NTSTATUS status;
+
+ if (jn_remain == 0) {
+ TALLOC_FREE(frame);
+ return 0;
+ }
+
+ if(*connect_path == '\0') {
+ TALLOC_FREE(frame);
+ return 0;
+ }
+
+ /*
+ * Fake up a connection struct for the VFS layer.
+ */
+
+ status = create_conn_struct_tos_cwd(global_messaging_context(),
+ snum,
+ connect_path,
+ session_info,
+ &c);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("create_conn_struct failed: %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return 0;
+ }
+ conn = c->conn;
+
+ /* form a junction for the msdfs root - convention
+ DO NOT REMOVE THIS: NT clients will not work with us
+ if this is not present
+ */
+ jucn[cnt].service_name = talloc_strdup(ctx,service_name);
+ jucn[cnt].volume_name = talloc_strdup(ctx, "");
+ if (!jucn[cnt].service_name || !jucn[cnt].volume_name) {
+ goto out;
+ }
+ jucn[cnt].comment = "";
+ jucn[cnt].referral_count = 1;
+
+ ref = jucn[cnt].referral_list = talloc_zero(ctx, struct referral);
+ if (jucn[cnt].referral_list == NULL) {
+ goto out;
+ }
+
+ ref->proximity = 0;
+ ref->ttl = REFERRAL_TTL;
+ if (*msdfs_proxy != '\0') {
+ ref->alternate_path = talloc_strdup(ctx,
+ msdfs_proxy);
+ } else {
+ ref->alternate_path = talloc_asprintf(ctx,
+ "\\\\%s\\%s",
+ get_local_machine_name(),
+ service_name);
+ }
+
+ if (!ref->alternate_path) {
+ goto out;
+ }
+ cnt++;
+
+ /* Don't enumerate if we're an msdfs proxy. */
+ if (*msdfs_proxy != '\0') {
+ goto out;
+ }
+
+ smb_fname = synthetic_smb_fname(frame,
+ ".",
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname == NULL) {
+ goto out;
+ }
+
+ /* Now enumerate all dfs links */
+ status = OpenDir(frame,
+ conn,
+ smb_fname,
+ NULL,
+ 0,
+ &dir_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ goto out;
+ }
+
+ while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) {
+ struct smb_filename *smb_dname = NULL;
+
+ if (cnt >= jn_remain) {
+ DEBUG(2, ("form_junctions: ran out of MSDFS "
+ "junction slots\n"));
+ TALLOC_FREE(talloced);
+ goto out;
+ }
+ smb_dname = synthetic_smb_fname(talloc_tos(),
+ dname,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_dname == NULL) {
+ TALLOC_FREE(talloced);
+ goto out;
+ }
+
+ status = SMB_VFS_READ_DFS_PATHAT(conn,
+ ctx,
+ conn->cwd_fsp,
+ smb_dname,
+ &jucn[cnt].referral_list,
+ &jucn[cnt].referral_count);
+
+ if (NT_STATUS_IS_OK(status)) {
+ jucn[cnt].service_name = talloc_strdup(ctx,
+ service_name);
+ jucn[cnt].volume_name = talloc_strdup(ctx, dname);
+ if (!jucn[cnt].service_name || !jucn[cnt].volume_name) {
+ TALLOC_FREE(talloced);
+ goto out;
+ }
+ jucn[cnt].comment = "";
+ cnt++;
+ }
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(smb_dname);
+ }
+
+out:
+ TALLOC_FREE(frame);
+ return cnt;
+}
+
+struct junction_map *enum_msdfs_links(TALLOC_CTX *ctx,
+ struct auth_session_info *session_info,
+ size_t *p_num_jn)
+{
+ struct junction_map *jn = NULL;
+ int i=0;
+ size_t jn_count = 0;
+ int sharecount = 0;
+
+ *p_num_jn = 0;
+ if(!lp_host_msdfs()) {
+ return NULL;
+ }
+
+ /* Ensure all the usershares are loaded. */
+ become_root();
+ load_registry_shares();
+ sharecount = load_usershare_shares(NULL, connections_snum_used);
+ unbecome_root();
+
+ for(i=0;i < sharecount;i++) {
+ if(lp_msdfs_root(i)) {
+ jn_count += count_dfs_links(ctx, session_info, i);
+ }
+ }
+ if (jn_count == 0) {
+ return NULL;
+ }
+ jn = talloc_array(ctx, struct junction_map, jn_count);
+ if (!jn) {
+ return NULL;
+ }
+ for(i=0; i < sharecount; i++) {
+ if (*p_num_jn >= jn_count) {
+ break;
+ }
+ if(lp_msdfs_root(i)) {
+ *p_num_jn += form_junctions(ctx,
+ session_info,
+ i,
+ &jn[*p_num_jn],
+ jn_count - *p_num_jn);
+ }
+ }
+ return jn;
+}
diff --git a/source3/smbd/notify.c b/source3/smbd/notify.c
new file mode 100644
index 0000000..156fbf7
--- /dev/null
+++ b/source3/smbd/notify.c
@@ -0,0 +1,964 @@
+/*
+ Unix SMB/CIFS implementation.
+ change notify handling
+ Copyright (C) Andrew Tridgell 2000
+ Copyright (C) Jeremy Allison 1994-1998
+ Copyright (C) Volker Lendecke 2007
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../librpc/gen_ndr/ndr_notify.h"
+#include "librpc/gen_ndr/ndr_file_id.h"
+#include "libcli/security/privileges.h"
+#include "libcli/security/security.h"
+
+struct notify_change_event {
+ struct timespec when;
+ uint32_t action;
+ const char *name;
+};
+
+struct notify_change_buf {
+ /*
+ * Filters for reinitializing after notifyd has been restarted
+ */
+ uint32_t filter;
+ uint32_t subdir_filter;
+
+ /*
+ * If no requests are pending, changes are queued here. Simple array,
+ * we only append.
+ */
+
+ uint32_t max_buffer_size;
+
+ /*
+ * num_changes == -1 means that we have got a catch-all change, when
+ * asked we just return NT_STATUS_OK without specific changes.
+ */
+ int num_changes;
+ struct notify_change_event *changes;
+
+ /*
+ * If no changes are around requests are queued here. Using a linked
+ * list, because we have to append at the end and delete from the top.
+ */
+ struct notify_change_request *requests;
+};
+
+struct notify_change_request {
+ struct notify_change_request *prev, *next;
+ struct files_struct *fsp; /* backpointer for cancel by mid */
+ struct smb_request *req;
+ uint32_t filter;
+ uint32_t max_param;
+ void (*reply_fn)(struct smb_request *req,
+ NTSTATUS error_code,
+ uint8_t *buf, size_t len);
+ struct notify_mid_map *mid_map;
+ void *backend_data;
+};
+
+static void notify_fsp(files_struct *fsp, struct timespec when,
+ uint32_t action, const char *name);
+
+bool change_notify_fsp_has_changes(struct files_struct *fsp)
+{
+ if (fsp == NULL) {
+ return false;
+ }
+
+ if (fsp->notify == NULL) {
+ return false;
+ }
+
+ if (fsp->notify->num_changes == 0) {
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * For NTCancel, we need to find the notify_change_request indexed by
+ * mid. Separate list here.
+ */
+
+struct notify_mid_map {
+ struct notify_mid_map *prev, *next;
+ struct notify_change_request *req;
+ uint64_t mid;
+};
+
+static bool notify_change_record_identical(struct notify_change_event *c1,
+ struct notify_change_event *c2)
+{
+ /* Note this is deliberately case sensitive. */
+ if (c1->action == c2->action &&
+ strcmp(c1->name, c2->name) == 0) {
+ return True;
+ }
+ return False;
+}
+
+static int compare_notify_change_events(const void *p1, const void *p2)
+{
+ const struct notify_change_event *e1 = p1;
+ const struct notify_change_event *e2 = p2;
+
+ return timespec_compare(&e1->when, &e2->when);
+}
+
+static bool notify_marshall_changes(int num_changes,
+ uint32_t max_offset,
+ struct notify_change_event *changes,
+ DATA_BLOB *final_blob)
+{
+ int i;
+
+ if (num_changes == -1) {
+ return false;
+ }
+
+ /*
+ * Sort the notifies by timestamp when the event happened to avoid
+ * coalescing and thus dropping events.
+ */
+
+ qsort(changes, num_changes,
+ sizeof(*changes), compare_notify_change_events);
+
+ for (i=0; i<num_changes; i++) {
+ enum ndr_err_code ndr_err;
+ struct notify_change_event *c;
+ struct FILE_NOTIFY_INFORMATION m;
+ DATA_BLOB blob;
+ uint16_t pad = 0;
+
+ /* Coalesce any identical records. */
+ while (i+1 < num_changes &&
+ notify_change_record_identical(&changes[i],
+ &changes[i+1])) {
+ i++;
+ }
+
+ c = &changes[i];
+
+ m.FileName1 = c->name;
+ m.FileNameLength = strlen_m(c->name)*2;
+ m.Action = c->action;
+
+ m._pad = data_blob_null;
+
+ /*
+ * Offset to next entry, only if there is one
+ */
+
+ if (i == (num_changes-1)) {
+ m.NextEntryOffset = 0;
+ } else {
+ if ((m.FileNameLength % 4) == 2) {
+ m._pad = data_blob_const(&pad, 2);
+ }
+ m.NextEntryOffset =
+ ndr_size_FILE_NOTIFY_INFORMATION(&m, 0);
+ }
+
+ ndr_err = ndr_push_struct_blob(&blob, talloc_tos(), &m,
+ (ndr_push_flags_fn_t)ndr_push_FILE_NOTIFY_INFORMATION);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return false;
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(FILE_NOTIFY_INFORMATION, &m);
+ }
+
+ if (!data_blob_append(talloc_tos(), final_blob,
+ blob.data, blob.length)) {
+ data_blob_free(&blob);
+ return false;
+ }
+
+ data_blob_free(&blob);
+
+ if (final_blob->length > max_offset) {
+ /* Too much data for client. */
+ DEBUG(10, ("Client only wanted %d bytes, trying to "
+ "marshall %d bytes\n", (int)max_offset,
+ (int)final_blob->length));
+ return False;
+ }
+ }
+
+ return True;
+}
+
+/****************************************************************************
+ Setup the common parts of the return packet and send it.
+*****************************************************************************/
+
+void change_notify_reply(struct smb_request *req,
+ NTSTATUS error_code,
+ uint32_t max_param,
+ struct notify_change_buf *notify_buf,
+ void (*reply_fn)(struct smb_request *req,
+ NTSTATUS error_code,
+ uint8_t *buf, size_t len))
+{
+ DATA_BLOB blob = data_blob_null;
+
+ if (!NT_STATUS_IS_OK(error_code)) {
+ reply_fn(req, error_code, NULL, 0);
+ return;
+ }
+
+ if (notify_buf == NULL) {
+ reply_fn(req, NT_STATUS_OK, NULL, 0);
+ return;
+ }
+
+ max_param = MIN(max_param, notify_buf->max_buffer_size);
+
+ if (!notify_marshall_changes(notify_buf->num_changes, max_param,
+ notify_buf->changes, &blob)) {
+ /*
+ * We exceed what the client is willing to accept. Send
+ * nothing.
+ */
+ data_blob_free(&blob);
+ }
+
+ reply_fn(req, NT_STATUS_OK, blob.data, blob.length);
+
+ data_blob_free(&blob);
+
+ TALLOC_FREE(notify_buf->changes);
+ notify_buf->num_changes = 0;
+}
+
+struct notify_fsp_state {
+ struct files_struct *notified_fsp;
+ struct timespec when;
+ const struct notify_event *e;
+};
+
+static struct files_struct *notify_fsp_cb(struct files_struct *fsp,
+ void *private_data)
+{
+ struct notify_fsp_state *state = private_data;
+
+ if (fsp == state->notified_fsp) {
+ DBG_DEBUG("notify_callback called for %s\n", fsp_str_dbg(fsp));
+ notify_fsp(fsp, state->when, state->e->action, state->e->path);
+ return fsp;
+ }
+
+ return NULL;
+}
+
+void notify_callback(struct smbd_server_connection *sconn,
+ void *private_data, struct timespec when,
+ const struct notify_event *e)
+{
+ struct notify_fsp_state state = {
+ .notified_fsp = private_data, .when = when, .e = e
+ };
+ files_forall(sconn, notify_fsp_cb, &state);
+}
+
+NTSTATUS change_notify_create(struct files_struct *fsp,
+ uint32_t max_buffer_size,
+ uint32_t filter,
+ bool recursive)
+{
+ size_t len = fsp_fullbasepath(fsp, NULL, 0);
+ char fullpath[len+1];
+ NTSTATUS status = NT_STATUS_NOT_IMPLEMENTED;
+
+ /*
+ * Setting a changenotify needs READ/LIST access
+ * on the directory handle.
+ */
+ status = check_any_access_fsp(fsp, SEC_DIR_LIST);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (fsp->notify != NULL) {
+ DEBUG(1, ("change_notify_create: fsp->notify != NULL, "
+ "fname = %s\n", fsp->fsp_name->base_name));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!(fsp->notify = talloc_zero(NULL, struct notify_change_buf))) {
+ DEBUG(0, ("talloc failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ fsp->notify->filter = filter;
+ fsp->notify->subdir_filter = recursive ? filter : 0;
+ fsp->notify->max_buffer_size = max_buffer_size;
+
+ fsp_fullbasepath(fsp, fullpath, sizeof(fullpath));
+
+ /*
+ * Avoid /. at the end of the path name. notify can't deal with it.
+ */
+ if (len > 1 && fullpath[len-1] == '.' && fullpath[len-2] == '/') {
+ fullpath[len-2] = '\0';
+ }
+
+ if ((fsp->notify->filter != 0) ||
+ (fsp->notify->subdir_filter != 0)) {
+ status = notify_add(fsp->conn->sconn->notify_ctx,
+ fullpath, fsp->notify->filter,
+ fsp->notify->subdir_filter, fsp);
+ }
+
+ return status;
+}
+
+NTSTATUS change_notify_add_request(struct smb_request *req,
+ uint32_t max_param,
+ uint32_t filter, bool recursive,
+ struct files_struct *fsp,
+ void (*reply_fn)(struct smb_request *req,
+ NTSTATUS error_code,
+ uint8_t *buf, size_t len))
+{
+ struct notify_change_request *request = NULL;
+ struct notify_mid_map *map = NULL;
+ struct smbd_server_connection *sconn = req->sconn;
+
+ DEBUG(10, ("change_notify_add_request: Adding request for %s: "
+ "max_param = %d\n", fsp_str_dbg(fsp), (int)max_param));
+
+ if (!(request = talloc(NULL, struct notify_change_request))
+ || !(map = talloc(request, struct notify_mid_map))) {
+ TALLOC_FREE(request);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ request->mid_map = map;
+ map->req = request;
+
+ request->req = talloc_move(request, &req);
+ request->max_param = max_param;
+ request->filter = filter;
+ request->fsp = fsp;
+ request->reply_fn = reply_fn;
+ request->backend_data = NULL;
+
+ DLIST_ADD_END(fsp->notify->requests, request);
+
+ map->mid = request->req->mid;
+ DLIST_ADD(sconn->notify_mid_maps, map);
+
+ return NT_STATUS_OK;
+}
+
+static void change_notify_remove_request(struct smbd_server_connection *sconn,
+ struct notify_change_request *remove_req)
+{
+ files_struct *fsp;
+ struct notify_change_request *req;
+
+ /*
+ * Paranoia checks, the fsp referenced must must have the request in
+ * its list of pending requests
+ */
+
+ fsp = remove_req->fsp;
+ SMB_ASSERT(fsp->notify != NULL);
+
+ for (req = fsp->notify->requests; req; req = req->next) {
+ if (req == remove_req) {
+ break;
+ }
+ }
+
+ if (req == NULL) {
+ smb_panic("notify_req not found in fsp's requests");
+ }
+
+ DLIST_REMOVE(fsp->notify->requests, req);
+ DLIST_REMOVE(sconn->notify_mid_maps, req->mid_map);
+ TALLOC_FREE(req);
+}
+
+static void smbd_notify_cancel_by_map(struct notify_mid_map *map)
+{
+ struct smb_request *smbreq = map->req->req;
+ struct smbd_server_connection *sconn = smbreq->sconn;
+ struct smbd_smb2_request *smb2req = smbreq->smb2req;
+ NTSTATUS notify_status = NT_STATUS_CANCELLED;
+
+ if (smb2req != NULL) {
+ NTSTATUS sstatus;
+
+ if (smb2req->session == NULL) {
+ sstatus = NT_STATUS_USER_SESSION_DELETED;
+ } else {
+ sstatus = smb2req->session->status;
+ }
+
+ if (NT_STATUS_EQUAL(sstatus, NT_STATUS_NETWORK_SESSION_EXPIRED)) {
+ sstatus = NT_STATUS_OK;
+ }
+
+ if (!NT_STATUS_IS_OK(sstatus)) {
+ notify_status = NT_STATUS_NOTIFY_CLEANUP;
+ } else if (smb2req->tcon == NULL) {
+ notify_status = NT_STATUS_NOTIFY_CLEANUP;
+ } else if (!NT_STATUS_IS_OK(smb2req->tcon->status)) {
+ notify_status = NT_STATUS_NOTIFY_CLEANUP;
+ }
+ }
+
+ change_notify_reply(smbreq, notify_status,
+ 0, NULL, map->req->reply_fn);
+ change_notify_remove_request(sconn, map->req);
+}
+
+/****************************************************************************
+ Delete entries by mid from the change notify pending queue. Always send reply.
+*****************************************************************************/
+
+bool remove_pending_change_notify_requests_by_mid(
+ struct smbd_server_connection *sconn, uint64_t mid)
+{
+ struct notify_mid_map *map;
+
+ for (map = sconn->notify_mid_maps; map; map = map->next) {
+ if (map->mid == mid) {
+ break;
+ }
+ }
+
+ if (map == NULL) {
+ return false;
+ }
+
+ smbd_notify_cancel_by_map(map);
+ return true;
+}
+
+void smbd_notify_cancel_by_smbreq(const struct smb_request *smbreq)
+{
+ struct smbd_server_connection *sconn = smbreq->sconn;
+ struct notify_mid_map *map;
+
+ for (map = sconn->notify_mid_maps; map; map = map->next) {
+ if (map->req->req == smbreq) {
+ break;
+ }
+ }
+
+ if (map == NULL) {
+ return;
+ }
+
+ smbd_notify_cancel_by_map(map);
+}
+
+static struct files_struct *smbd_notify_cancel_deleted_fn(
+ struct files_struct *fsp, void *private_data)
+{
+ struct file_id *fid = talloc_get_type_abort(
+ private_data, struct file_id);
+
+ if (file_id_equal(&fsp->file_id, fid)) {
+ remove_pending_change_notify_requests_by_fid(
+ fsp, NT_STATUS_DELETE_PENDING);
+ }
+ return NULL;
+}
+
+void smbd_notify_cancel_deleted(struct messaging_context *msg,
+ void *private_data, uint32_t msg_type,
+ struct server_id server_id, DATA_BLOB *data)
+{
+ struct smbd_server_connection *sconn = talloc_get_type_abort(
+ private_data, struct smbd_server_connection);
+ struct file_id *fid;
+ enum ndr_err_code ndr_err;
+
+ fid = talloc(talloc_tos(), struct file_id);
+ if (fid == NULL) {
+ DEBUG(1, ("talloc failed\n"));
+ return;
+ }
+
+ ndr_err = ndr_pull_struct_blob_all(
+ data, fid, fid, (ndr_pull_flags_fn_t)ndr_pull_file_id);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(10, ("%s: ndr_pull_file_id failed: %s\n", __func__,
+ ndr_errstr(ndr_err)));
+ goto done;
+ }
+
+ files_forall(sconn, smbd_notify_cancel_deleted_fn, fid);
+
+done:
+ TALLOC_FREE(fid);
+}
+
+static struct files_struct *smbd_notifyd_reregister(struct files_struct *fsp,
+ void *private_data)
+{
+ DBG_DEBUG("reregister %s\n", fsp->fsp_name->base_name);
+
+ if ((fsp->conn->sconn->notify_ctx != NULL) &&
+ (fsp->notify != NULL) &&
+ ((fsp->notify->filter != 0) ||
+ (fsp->notify->subdir_filter != 0))) {
+ size_t len = fsp_fullbasepath(fsp, NULL, 0);
+ char fullpath[len+1];
+
+ NTSTATUS status;
+
+ fsp_fullbasepath(fsp, fullpath, sizeof(fullpath));
+ if (len > 1 && fullpath[len-1] == '.' &&
+ fullpath[len-2] == '/') {
+ fullpath[len-2] = '\0';
+ }
+
+ status = notify_add(fsp->conn->sconn->notify_ctx,
+ fullpath, fsp->notify->filter,
+ fsp->notify->subdir_filter, fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("notify_add failed: %s\n",
+ nt_errstr(status));
+ }
+ }
+ return NULL;
+}
+
+void smbd_notifyd_restarted(struct messaging_context *msg,
+ void *private_data, uint32_t msg_type,
+ struct server_id server_id, DATA_BLOB *data)
+{
+ struct smbd_server_connection *sconn = talloc_get_type_abort(
+ private_data, struct smbd_server_connection);
+
+ TALLOC_FREE(sconn->notify_ctx);
+
+ sconn->notify_ctx = notify_init(sconn, sconn->msg_ctx,
+ sconn, notify_callback);
+ if (sconn->notify_ctx == NULL) {
+ DBG_DEBUG("notify_init failed\n");
+ return;
+ }
+
+ files_forall(sconn, smbd_notifyd_reregister, sconn->notify_ctx);
+}
+
+/****************************************************************************
+ Delete entries by fnum from the change notify pending queue.
+*****************************************************************************/
+
+void remove_pending_change_notify_requests_by_fid(files_struct *fsp,
+ NTSTATUS status)
+{
+ if (fsp->notify == NULL) {
+ return;
+ }
+
+ while (fsp->notify->requests != NULL) {
+ change_notify_reply(fsp->notify->requests->req,
+ status, 0, NULL,
+ fsp->notify->requests->reply_fn);
+ change_notify_remove_request(fsp->conn->sconn,
+ fsp->notify->requests);
+ }
+}
+
+void notify_fname(connection_struct *conn, uint32_t action, uint32_t filter,
+ const char *path)
+{
+ struct notify_context *notify_ctx = conn->sconn->notify_ctx;
+
+ if (path[0] == '.' && path[1] == '/') {
+ path += 2;
+ }
+
+ notify_trigger(notify_ctx, action, filter, conn->connectpath, path);
+}
+
+static bool user_can_stat_name_under_fsp(files_struct *fsp, const char *name)
+{
+ uint32_t rights;
+ struct smb_filename *fname = NULL;
+ char *filepath = NULL;
+ NTSTATUS status;
+ char *p = NULL;
+
+ /*
+ * Assume we get filepath (relative to the share)
+ * like this:
+ *
+ * 'dir1/dir2/dir3/file'
+ *
+ * We start with LIST and TRAVERSE on the
+ * direct parent ('dir1/dir2/dir3')
+ *
+ * Then we switch to just TRAVERSE for
+ * the rest: 'dir1/dir2', 'dir1', '.'
+ *
+ * For a file in the share root, we'll have
+ * 'file'
+ * and would just check '.' with LIST and TRAVERSE.
+ *
+ * It's important to always check '.' as the last step,
+ * which means we check the permissions of the share root
+ * directory.
+ */
+
+ if (ISDOT(fsp->fsp_name->base_name)) {
+ filepath = talloc_strdup(talloc_tos(), name);
+ } else {
+ filepath = talloc_asprintf(talloc_tos(),
+ "%s/%s",
+ fsp->fsp_name->base_name,
+ name);
+ }
+ if (filepath == NULL) {
+ DBG_ERR("Memory allocation failed\n");
+ return false;
+ }
+
+ rights = SEC_DIR_LIST|SEC_DIR_TRAVERSE;
+ p = strrchr_m(filepath, '/');
+ /*
+ * Check each path component, excluding the share root.
+ *
+ * We could check all components including root using
+ * a do { .. } while() loop, but IMHO the logic is clearer
+ * having the share root check separately afterwards.
+ */
+ while (p != NULL) {
+ *p = '\0';
+ status = synthetic_pathref(talloc_tos(),
+ fsp->conn->cwd_fsp,
+ filepath,
+ NULL,
+ NULL,
+ 0,
+ 0,
+ &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("synthetic_pathref failed for %s, error %s\n",
+ filepath,
+ nt_errstr(status));
+ TALLOC_FREE(fname);
+ TALLOC_FREE(filepath);
+ return false;
+ }
+
+ status = smbd_check_access_rights_fsp(fsp->conn->cwd_fsp,
+ fname->fsp,
+ false,
+ rights);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Access rights for %s/%s: %s\n",
+ fsp->conn->connectpath,
+ filepath,
+ nt_errstr(status));
+ TALLOC_FREE(fname);
+ TALLOC_FREE(filepath);
+ return false;
+ }
+
+ TALLOC_FREE(fname);
+ rights = SEC_DIR_TRAVERSE;
+ p = strrchr_m(filepath, '/');
+ }
+
+ TALLOC_FREE(filepath);
+
+ /* Finally check share root. */
+ filepath = talloc_strdup(talloc_tos(), ".");
+ if (filepath == NULL) {
+ DBG_ERR("Memory allocation failed\n");
+ return false;
+ }
+ status = synthetic_pathref(talloc_tos(),
+ fsp->conn->cwd_fsp,
+ filepath,
+ NULL,
+ NULL,
+ 0,
+ 0,
+ &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("synthetic_pathref failed for %s, error %s\n",
+ filepath,
+ nt_errstr(status));
+ TALLOC_FREE(fname);
+ TALLOC_FREE(filepath);
+ return false;
+ }
+ status = smbd_check_access_rights_fsp(fsp->conn->cwd_fsp,
+ fname->fsp,
+ false,
+ rights);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("TRAVERSE access rights for %s failed with %s\n",
+ fsp->conn->connectpath,
+ nt_errstr(status));
+ TALLOC_FREE(fname);
+ TALLOC_FREE(filepath);
+ return false;
+ }
+ TALLOC_FREE(fname);
+ TALLOC_FREE(filepath);
+ return true;
+}
+
+static void notify_fsp(files_struct *fsp, struct timespec when,
+ uint32_t action, const char *name)
+{
+ struct notify_change_event *change, *changes;
+ char *tmp;
+
+ if (fsp->notify == NULL) {
+ /*
+ * Nobody is waiting, don't queue
+ */
+ return;
+ }
+
+ if (lp_honor_change_notify_privilege(SNUM(fsp->conn))) {
+ bool has_sec_change_notify_privilege;
+ bool expose = false;
+
+ has_sec_change_notify_privilege = security_token_has_privilege(
+ fsp->conn->session_info->security_token,
+ SEC_PRIV_CHANGE_NOTIFY);
+
+ if (has_sec_change_notify_privilege) {
+ expose = true;
+ } else {
+ bool ok;
+
+ ok = become_user_without_service_by_fsp(fsp);
+ if (ok) {
+ expose = user_can_stat_name_under_fsp(fsp, name);
+ unbecome_user_without_service();
+ }
+ }
+ DBG_DEBUG("has_sec_change_notify_privilege=%s "
+ "expose=%s for %s notify %s\n",
+ has_sec_change_notify_privilege ? "true" : "false",
+ expose ? "true" : "false",
+ fsp->fsp_name->base_name, name);
+ if (!expose) {
+ return;
+ }
+ }
+
+ /*
+ * Someone has triggered a notify previously, queue the change for
+ * later.
+ */
+
+ if ((fsp->notify->num_changes > 1000) || (name == NULL)) {
+ /*
+ * The real number depends on the client buf, just provide a
+ * guard against a DoS here. If name == NULL the CN backend is
+ * alerting us to a problem. Possibly dropped events. Clear
+ * queued changes and send the catch-all response to the client
+ * if a request is pending.
+ */
+ TALLOC_FREE(fsp->notify->changes);
+ fsp->notify->num_changes = -1;
+ if (fsp->notify->requests != NULL) {
+ change_notify_reply(fsp->notify->requests->req,
+ NT_STATUS_OK,
+ fsp->notify->requests->max_param,
+ fsp->notify,
+ fsp->notify->requests->reply_fn);
+ change_notify_remove_request(fsp->conn->sconn,
+ fsp->notify->requests);
+ }
+ return;
+ }
+
+ /* If we've exceeded the server side queue or received a NULL name
+ * from the underlying CN implementation, don't queue up any more
+ * requests until we can send a catch-all response to the client */
+ if (fsp->notify->num_changes == -1) {
+ return;
+ }
+
+ if (!(changes = talloc_realloc(
+ fsp->notify, fsp->notify->changes,
+ struct notify_change_event,
+ fsp->notify->num_changes+1))) {
+ DEBUG(0, ("talloc_realloc failed\n"));
+ return;
+ }
+
+ fsp->notify->changes = changes;
+
+ change = &(fsp->notify->changes[fsp->notify->num_changes]);
+
+ if (!(tmp = talloc_strdup(changes, name))) {
+ DEBUG(0, ("talloc_strdup failed\n"));
+ return;
+ }
+
+ string_replace(tmp, '/', '\\');
+ change->name = tmp;
+
+ change->when = when;
+ change->action = action;
+ fsp->notify->num_changes += 1;
+
+ if (fsp->notify->requests == NULL) {
+ /*
+ * Nobody is waiting, so don't send anything. The ot
+ */
+ return;
+ }
+
+ if (action == NOTIFY_ACTION_OLD_NAME) {
+ /*
+ * We have to send the two rename events in one reply. So hold
+ * the first part back.
+ */
+ return;
+ }
+
+ /*
+ * Someone is waiting for the change, trigger the reply immediately.
+ *
+ * TODO: do we have to walk the lists of requests pending?
+ */
+
+ change_notify_reply(fsp->notify->requests->req,
+ NT_STATUS_OK,
+ fsp->notify->requests->max_param,
+ fsp->notify,
+ fsp->notify->requests->reply_fn);
+
+ change_notify_remove_request(fsp->conn->sconn, fsp->notify->requests);
+}
+
+char *notify_filter_string(TALLOC_CTX *mem_ctx, uint32_t filter)
+{
+ char *result = NULL;
+
+ result = talloc_strdup(mem_ctx, "");
+ if (result == NULL) {
+ return NULL;
+ }
+
+ if (filter & FILE_NOTIFY_CHANGE_FILE_NAME) {
+ result = talloc_asprintf_append(result, "FILE_NAME|");
+ if (result == NULL) {
+ return NULL;
+ }
+ }
+ if (filter & FILE_NOTIFY_CHANGE_DIR_NAME) {
+ result = talloc_asprintf_append(result, "DIR_NAME|");
+ if (result == NULL) {
+ return NULL;
+ }
+ }
+ if (filter & FILE_NOTIFY_CHANGE_ATTRIBUTES) {
+ result = talloc_asprintf_append(result, "ATTRIBUTES|");
+ if (result == NULL) {
+ return NULL;
+ }
+ }
+ if (filter & FILE_NOTIFY_CHANGE_SIZE) {
+ result = talloc_asprintf_append(result, "SIZE|");
+ if (result == NULL) {
+ return NULL;
+ }
+ }
+ if (filter & FILE_NOTIFY_CHANGE_LAST_WRITE) {
+ result = talloc_asprintf_append(result, "LAST_WRITE|");
+ if (result == NULL) {
+ return NULL;
+ }
+ }
+ if (filter & FILE_NOTIFY_CHANGE_LAST_ACCESS) {
+ result = talloc_asprintf_append(result, "LAST_ACCESS|");
+ if (result == NULL) {
+ return NULL;
+ }
+ }
+ if (filter & FILE_NOTIFY_CHANGE_CREATION) {
+ result = talloc_asprintf_append(result, "CREATION|");
+ if (result == NULL) {
+ return NULL;
+ }
+ }
+ if (filter & FILE_NOTIFY_CHANGE_EA) {
+ result = talloc_asprintf_append(result, "EA|");
+ if (result == NULL) {
+ return NULL;
+ }
+ }
+ if (filter & FILE_NOTIFY_CHANGE_SECURITY) {
+ result = talloc_asprintf_append(result, "SECURITY|");
+ if (result == NULL) {
+ return NULL;
+ }
+ }
+ if (filter & FILE_NOTIFY_CHANGE_STREAM_NAME) {
+ result = talloc_asprintf_append(result, "STREAM_NAME|");
+ if (result == NULL) {
+ return NULL;
+ }
+ }
+ if (filter & FILE_NOTIFY_CHANGE_STREAM_SIZE) {
+ result = talloc_asprintf_append(result, "STREAM_SIZE|");
+ if (result == NULL) {
+ return NULL;
+ }
+ }
+ if (filter & FILE_NOTIFY_CHANGE_STREAM_WRITE) {
+ result = talloc_asprintf_append(result, "STREAM_WRITE|");
+ if (result == NULL) {
+ return NULL;
+ }
+ }
+
+ if (*result == '\0') return result;
+
+ result[strlen(result)-1] = '\0';
+ return result;
+}
+
+struct sys_notify_context *sys_notify_context_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev)
+{
+ struct sys_notify_context *ctx;
+
+ if (!(ctx = talloc(mem_ctx, struct sys_notify_context))) {
+ DEBUG(0, ("talloc failed\n"));
+ return NULL;
+ }
+
+ ctx->ev = ev;
+ ctx->private_data = NULL;
+ return ctx;
+}
diff --git a/source3/smbd/notify_fam.c b/source3/smbd/notify_fam.c
new file mode 100644
index 0000000..2cf1e35
--- /dev/null
+++ b/source3/smbd/notify_fam.c
@@ -0,0 +1,307 @@
+/*
+ * FAM file notification support.
+ *
+ * Copyright (c) James Peach 2005
+ * Copyright (c) Volker Lendecke 2007
+ *
+ * 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 "smbd/smbd.h"
+#include "librpc/gen_ndr/notify.h"
+
+#include <fam.h>
+
+#if !defined(HAVE_FAM_H_FAMCODES_TYPEDEF)
+/* Gamin provides this typedef which means we can't use 'enum FAMCodes' as per
+ * every other FAM implementation. Phooey.
+ */
+typedef enum FAMCodes FAMCodes;
+#endif
+
+/* NOTE: There are multiple versions of FAM floating around the net, each with
+ * slight differences from the original SGI FAM implementation. In this file,
+ * we rely only on the SGI features and do not assume any extensions. For
+ * example, we do not look at FAMErrno, because it is not set by the original
+ * implementation.
+ *
+ * Random FAM links:
+ * http://oss.sgi.com/projects/fam/
+ * http://savannah.nongnu.org/projects/fam/
+ * http://sourceforge.net/projects/bsdfam/
+ */
+
+/* ------------------------------------------------------------------------- */
+
+struct fam_watch_context {
+ struct fam_watch_context *prev, *next;
+ FAMConnection *fam_connection;
+ struct FAMRequest fr;
+ struct sys_notify_context *sys_ctx;
+ void (*callback)(struct sys_notify_context *ctx,
+ void *private_data,
+ struct notify_event *ev,
+ uint32_t filter);
+ void *private_data;
+ uint32_t mask; /* the inotify mask */
+ uint32_t filter; /* the windows completion filter */
+ const char *path;
+};
+
+
+/*
+ * We want one FAM connection per smbd, not one per tcon.
+ */
+static FAMConnection fam_connection;
+static bool fam_connection_initialized = False;
+
+static struct fam_watch_context *fam_notify_list;
+static void fam_handler(struct tevent_context *event_ctx,
+ struct tevent_fd *fd_event,
+ uint16_t flags,
+ void *private_data);
+
+static NTSTATUS fam_open_connection(FAMConnection *fam_conn,
+ struct tevent_context *event_ctx)
+{
+ int res;
+ char *name;
+
+ ZERO_STRUCTP(fam_conn);
+ FAMCONNECTION_GETFD(fam_conn) = -1;
+
+
+#ifdef HAVE_FAMNOEXISTS
+ /* We should honor outside setting of the GAM_CLIENT_ID. */
+ setenv("GAM_CLIENT_ID","SAMBA",0);
+#endif
+
+ if (asprintf(&name, "smbd (%lu)", (unsigned long)getpid()) == -1) {
+ DEBUG(0, ("No memory\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ res = FAMOpen2(fam_conn, name);
+
+#ifdef HAVE_FAMNOEXISTS
+ /*
+ * This reduces the chatter between GAMIN and samba making the pair
+ * much more reliable.
+ */
+ FAMNoExists(fam_conn);
+#endif
+
+ SAFE_FREE(name);
+
+ if (res < 0) {
+ DEBUG(10, ("FAM file change notifications not available: %s\n",
+ FamErrlist[-res]));
+ /*
+ * No idea how to get NT_STATUS from a FAM result
+ */
+ FAMCONNECTION_GETFD(fam_conn) = -1;
+ return NT_STATUS_UNEXPECTED_IO_ERROR;
+ }
+
+ if (tevent_add_fd(event_ctx, event_ctx,
+ FAMCONNECTION_GETFD(fam_conn),
+ TEVENT_FD_READ, fam_handler,
+ (void *)fam_conn) == NULL) {
+ DEBUG(0, ("event_add_fd failed\n"));
+ FAMClose(fam_conn);
+ FAMCONNECTION_GETFD(fam_conn) = -1;
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static void fam_reopen(FAMConnection *fam_conn,
+ struct tevent_context *event_ctx,
+ struct fam_watch_context *notify_list)
+{
+ struct fam_watch_context *ctx;
+
+ DEBUG(5, ("Re-opening FAM connection\n"));
+
+ FAMClose(fam_conn);
+
+ if (!NT_STATUS_IS_OK(fam_open_connection(fam_conn, event_ctx))) {
+ DEBUG(5, ("Re-opening fam connection failed\n"));
+ return;
+ }
+
+ for (ctx = notify_list; ctx; ctx = ctx->next) {
+ FAMMonitorDirectory(fam_conn, ctx->path, &ctx->fr, NULL);
+ }
+}
+
+static void fam_handler(struct tevent_context *event_ctx,
+ struct tevent_fd *fd_event,
+ uint16_t flags,
+ void *private_data)
+{
+ FAMConnection *fam_conn = (FAMConnection *)private_data;
+ FAMEvent fam_event;
+ struct fam_watch_context *ctx;
+ struct notify_event ne;
+
+ if (FAMPending(fam_conn) == 0) {
+ DEBUG(10, ("fam_handler called but nothing pending\n"));
+ return;
+ }
+
+ if (FAMNextEvent(fam_conn, &fam_event) != 1) {
+ DEBUG(5, ("FAMNextEvent returned an error\n"));
+ TALLOC_FREE(fd_event);
+ fam_reopen(fam_conn, event_ctx, fam_notify_list);
+ return;
+ }
+
+ DEBUG(10, ("Got FAMCode %d for %s\n", fam_event.code,
+ fam_event.filename));
+
+ switch (fam_event.code) {
+ case FAMChanged:
+ ne.action = NOTIFY_ACTION_MODIFIED;
+ break;
+ case FAMCreated:
+ ne.action = NOTIFY_ACTION_ADDED;
+ break;
+ case FAMDeleted:
+ ne.action = NOTIFY_ACTION_REMOVED;
+ break;
+ default:
+ DEBUG(10, ("Ignoring code FAMCode %d for file %s\n",
+ (int)fam_event.code, fam_event.filename));
+ return;
+ }
+
+ for (ctx = fam_notify_list; ctx; ctx = ctx->next) {
+ if (memcmp(&fam_event.fr, &ctx->fr, sizeof(FAMRequest)) == 0) {
+ break;
+ }
+ }
+
+ if (ctx == NULL) {
+ DEBUG(5, ("Discarding event for file %s\n",
+ fam_event.filename));
+ return;
+ }
+
+ if ((ne.path = strrchr_m(fam_event.filename, '\\')) == NULL) {
+ ne.path = fam_event.filename;
+ }
+ ne.dir = ctx->path;
+
+ ctx->callback(ctx->sys_ctx, ctx->private_data, &ne, UINT32_MAX);
+}
+
+static int fam_watch_context_destructor(struct fam_watch_context *ctx)
+{
+ if (FAMCONNECTION_GETFD(ctx->fam_connection) != -1) {
+ FAMCancelMonitor(&fam_connection, &ctx->fr);
+ }
+ DLIST_REMOVE(fam_notify_list, ctx);
+ return 0;
+}
+
+/*
+ add a watch. The watch is removed when the caller calls
+ talloc_free() on *handle
+*/
+int fam_watch(TALLOC_CTX *mem_ctx,
+ struct sys_notify_context *ctx,
+ const char *path,
+ uint32_t *filter,
+ uint32_t *subdir_filter,
+ void (*callback)(struct sys_notify_context *ctx,
+ void *private_data,
+ struct notify_event *ev,
+ uint32_t filter),
+ void *private_data,
+ void *handle_p)
+{
+ const uint32_t fam_mask = (FILE_NOTIFY_CHANGE_FILE_NAME|
+ FILE_NOTIFY_CHANGE_DIR_NAME);
+ struct fam_watch_context *watch;
+ void **handle = (void **)handle_p;
+
+ *handle = NULL;
+
+ if ((*filter & fam_mask) == 0) {
+ DEBUG(10, ("filter = %u, ignoring in FAM\n", *filter));
+ return 0;
+ }
+
+ if (!fam_connection_initialized) {
+ if (!NT_STATUS_IS_OK(fam_open_connection(&fam_connection,
+ ctx->ev))) {
+ /*
+ * Just let smbd do all the work itself
+ */
+ return 0;
+ }
+ fam_connection_initialized = True;
+ }
+
+ if (!(watch = talloc(mem_ctx, struct fam_watch_context))) {
+ return ENOMEM;
+ }
+
+ watch->fam_connection = &fam_connection;
+
+ watch->callback = callback;
+ watch->private_data = private_data;
+ watch->sys_ctx = ctx;
+
+ watch->path = talloc_strdup(watch, path);
+ if (watch->path == NULL) {
+ DEBUG(0, ("talloc_asprintf failed\n"));
+ TALLOC_FREE(watch);
+ return ENOMEM;
+ }
+
+ /*
+ * The FAM module in this early state will only take care of
+ * FAMCreated and FAMDeleted events, Leave the rest to notifyd
+ */
+
+ watch->filter = fam_mask;
+ *filter &= ~fam_mask;
+
+ DLIST_ADD(fam_notify_list, watch);
+ talloc_set_destructor(watch, fam_watch_context_destructor);
+
+ /*
+ * Only directories monitored so far
+ */
+
+ if (FAMCONNECTION_GETFD(watch->fam_connection) != -1) {
+ FAMMonitorDirectory(watch->fam_connection, watch->path,
+ &watch->fr, NULL);
+ }
+ else {
+ /*
+ * If the re-open is successful, this will establish the
+ * FAMMonitor from the list
+ */
+ fam_reopen(watch->fam_connection, ctx->ev, fam_notify_list);
+ }
+
+ *handle = watch;
+
+ return 0;
+}
diff --git a/source3/smbd/notify_inotify.c b/source3/smbd/notify_inotify.c
new file mode 100644
index 0000000..81fafc2
--- /dev/null
+++ b/source3/smbd/notify_inotify.c
@@ -0,0 +1,457 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Andrew Tridgell 2006
+
+ 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/>.
+*/
+
+/*
+ notify implementation using inotify
+*/
+
+#include "includes.h"
+#include "../librpc/gen_ndr/notify.h"
+#include "smbd/smbd.h"
+#include "lib/util/sys_rw_data.h"
+
+#include <sys/inotify.h>
+
+/* glibc < 2.5 headers don't have these defines */
+#ifndef IN_ONLYDIR
+#define IN_ONLYDIR 0x01000000
+#endif
+#ifndef IN_MASK_ADD
+#define IN_MASK_ADD 0x20000000
+#endif
+
+struct inotify_private {
+ struct sys_notify_context *ctx;
+ int fd;
+ struct inotify_watch_context *watches;
+};
+
+struct inotify_watch_context {
+ struct inotify_watch_context *next, *prev;
+ struct inotify_private *in;
+ int wd;
+ void (*callback)(struct sys_notify_context *ctx,
+ void *private_data,
+ struct notify_event *ev,
+ uint32_t filter);
+ void *private_data;
+ uint32_t mask; /* the inotify mask */
+ uint32_t filter; /* the windows completion filter */
+ const char *path;
+};
+
+
+/*
+ map from a change notify mask to a inotify mask. Remove any bits
+ which we can handle
+*/
+static const struct {
+ uint32_t notify_mask;
+ uint32_t inotify_mask;
+} inotify_mapping[] = {
+ {FILE_NOTIFY_CHANGE_FILE_NAME, IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO},
+ {FILE_NOTIFY_CHANGE_DIR_NAME, IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO},
+ {FILE_NOTIFY_CHANGE_ATTRIBUTES, IN_ATTRIB|IN_MOVED_TO|IN_MOVED_FROM|IN_MODIFY},
+ {FILE_NOTIFY_CHANGE_LAST_WRITE, IN_ATTRIB},
+ {FILE_NOTIFY_CHANGE_LAST_ACCESS, IN_ATTRIB},
+ {FILE_NOTIFY_CHANGE_EA, IN_ATTRIB},
+ {FILE_NOTIFY_CHANGE_SECURITY, IN_ATTRIB}
+};
+
+static uint32_t inotify_map(uint32_t *filter)
+{
+ size_t i;
+ uint32_t out=0;
+ for (i=0;i<ARRAY_SIZE(inotify_mapping);i++) {
+ if (inotify_mapping[i].notify_mask & *filter) {
+ out |= inotify_mapping[i].inotify_mask;
+ *filter &= ~inotify_mapping[i].notify_mask;
+ }
+ }
+ return out;
+}
+
+/*
+ * Map inotify mask back to filter. This returns all filters that
+ * could have created the inotify watch.
+ */
+static uint32_t inotify_map_mask_to_filter(uint32_t mask)
+{
+ size_t i;
+ uint32_t filter = 0;
+
+ for (i = 0; i < ARRAY_SIZE(inotify_mapping); i++) {
+ if (inotify_mapping[i].inotify_mask & mask) {
+ filter |= inotify_mapping[i].notify_mask;
+ }
+ }
+
+ if (mask & IN_ISDIR) {
+ filter &= ~FILE_NOTIFY_CHANGE_FILE_NAME;
+ } else {
+ filter &= ~FILE_NOTIFY_CHANGE_DIR_NAME;
+ }
+
+ return filter;
+}
+
+/*
+ destroy the inotify private context
+*/
+static int inotify_destructor(struct inotify_private *in)
+{
+ close(in->fd);
+ return 0;
+}
+
+
+/*
+ see if a particular event from inotify really does match a requested
+ notify event in SMB
+*/
+static bool filter_match(struct inotify_watch_context *w,
+ struct inotify_event *e)
+{
+ bool ok;
+
+ DEBUG(10, ("filter_match: e->mask=%x, w->mask=%x, w->filter=%x\n",
+ e->mask, w->mask, w->filter));
+
+ if ((e->mask & w->mask) == 0) {
+ /* this happens because inotify_add_watch() coalesces watches on the same
+ path, oring their masks together */
+ return False;
+ }
+
+ /* SMB separates the filters for files and directories */
+ if (e->mask & IN_ISDIR) {
+ ok = ((w->filter & FILE_NOTIFY_CHANGE_DIR_NAME) != 0);
+ return ok;
+ }
+
+ if ((e->mask & IN_ATTRIB) &&
+ (w->filter & (FILE_NOTIFY_CHANGE_ATTRIBUTES|
+ FILE_NOTIFY_CHANGE_LAST_WRITE|
+ FILE_NOTIFY_CHANGE_LAST_ACCESS|
+ FILE_NOTIFY_CHANGE_EA|
+ FILE_NOTIFY_CHANGE_SECURITY))) {
+ return True;
+ }
+ if ((e->mask & IN_MODIFY) &&
+ (w->filter & FILE_NOTIFY_CHANGE_ATTRIBUTES)) {
+ return True;
+ }
+
+ ok = ((w->filter & FILE_NOTIFY_CHANGE_FILE_NAME) != 0);
+ return ok;
+}
+
+
+
+/*
+ dispatch one inotify event
+
+ the cookies are used to correctly handle renames
+*/
+static void inotify_dispatch(struct inotify_private *in,
+ struct inotify_event *e,
+ int prev_wd,
+ uint32_t prev_cookie,
+ struct inotify_event *e2)
+{
+ struct inotify_watch_context *w, *next;
+ struct notify_event ne;
+ uint32_t filter;
+
+ DEBUG(10, ("inotify_dispatch called with mask=%x, name=[%s]\n",
+ e->mask, e->len ? e->name : ""));
+
+ /* ignore extraneous events, such as unmount and IN_IGNORED events */
+ if ((e->mask & (IN_ATTRIB|IN_MODIFY|IN_CREATE|IN_DELETE|
+ IN_MOVED_FROM|IN_MOVED_TO)) == 0) {
+ return;
+ }
+
+ /* map the inotify mask to a action. This gets complicated for
+ renames */
+ if (e->mask & IN_CREATE) {
+ ne.action = NOTIFY_ACTION_ADDED;
+ } else if (e->mask & IN_DELETE) {
+ ne.action = NOTIFY_ACTION_REMOVED;
+ } else if (e->mask & IN_MOVED_FROM) {
+ if (e2 != NULL && e2->cookie == e->cookie &&
+ e2->wd == e->wd) {
+ ne.action = NOTIFY_ACTION_OLD_NAME;
+ } else {
+ ne.action = NOTIFY_ACTION_REMOVED;
+ }
+ } else if (e->mask & IN_MOVED_TO) {
+ if ((e->cookie == prev_cookie) && (e->wd == prev_wd)) {
+ ne.action = NOTIFY_ACTION_NEW_NAME;
+ } else {
+ ne.action = NOTIFY_ACTION_ADDED;
+ }
+ } else {
+ ne.action = NOTIFY_ACTION_MODIFIED;
+ }
+ ne.path = e->name;
+
+ filter = inotify_map_mask_to_filter(e->mask);
+
+ DBG_DEBUG("ne.action = %d, ne.path = %s, filter = %d\n",
+ ne.action, ne.path, filter);
+
+ /* find any watches that have this watch descriptor */
+ for (w=in->watches;w;w=next) {
+ next = w->next;
+ if (w->wd == e->wd && filter_match(w, e)) {
+ ne.dir = w->path;
+ w->callback(in->ctx, w->private_data, &ne, filter);
+ }
+ }
+
+ if ((ne.action == NOTIFY_ACTION_NEW_NAME) &&
+ ((e->mask & IN_ISDIR) == 0)) {
+
+ /*
+ * SMB expects a file rename to generate three events, two for
+ * the rename and the other for a modify of the
+ * destination. Strange!
+ */
+
+ ne.action = NOTIFY_ACTION_MODIFIED;
+ e->mask = IN_ATTRIB;
+
+ for (w=in->watches;w;w=next) {
+ next = w->next;
+ if (w->wd == e->wd && filter_match(w, e) &&
+ !(w->filter & FILE_NOTIFY_CHANGE_CREATION)) {
+ ne.dir = w->path;
+ w->callback(in->ctx, w->private_data, &ne,
+ filter);
+ }
+ }
+ }
+}
+
+/*
+ called when the kernel has some events for us
+*/
+static void inotify_handler(struct tevent_context *ev, struct tevent_fd *fde,
+ uint16_t flags, void *private_data)
+{
+ struct inotify_private *in = talloc_get_type(private_data,
+ struct inotify_private);
+ int bufsize = 0;
+ struct inotify_event *e0, *e;
+ uint32_t prev_cookie=0;
+ int prev_wd = -1;
+ ssize_t ret;
+
+ /*
+ we must use FIONREAD as we cannot predict the length of the
+ filenames, and thus can't know how much to allocate
+ otherwise
+ */
+ if (ioctl(in->fd, FIONREAD, &bufsize) != 0 ||
+ bufsize == 0) {
+ DEBUG(0,("No data on inotify fd?!\n"));
+ TALLOC_FREE(fde);
+ return;
+ }
+
+ e0 = e = (struct inotify_event *)TALLOC_SIZE(in, bufsize + 1);
+ if (e == NULL) return;
+ ((uint8_t *)e)[bufsize] = '\0';
+
+ ret = read_data(in->fd, e0, bufsize);
+ if (ret != bufsize) {
+ DEBUG(0, ("Failed to read all inotify data - %s\n",
+ strerror(errno)));
+ talloc_free(e0);
+ /* the inotify fd will now be out of sync,
+ * can't keep reading data off it */
+ TALLOC_FREE(fde);
+ return;
+ }
+
+ /* we can get more than one event in the buffer */
+ while (e && (bufsize >= sizeof(*e))) {
+ struct inotify_event *e2 = NULL;
+ bufsize -= e->len + sizeof(*e);
+ if (bufsize >= sizeof(*e)) {
+ e2 = (struct inotify_event *)(e->len + sizeof(*e) + (char *)e);
+ }
+ inotify_dispatch(in, e, prev_wd, prev_cookie, e2);
+ prev_wd = e->wd;
+ prev_cookie = e->cookie;
+ e = e2;
+ }
+
+ talloc_free(e0);
+}
+
+/*
+ setup the inotify handle - called the first time a watch is added on
+ this context
+*/
+static int inotify_setup(struct sys_notify_context *ctx)
+{
+ struct inotify_private *in;
+ struct tevent_fd *fde;
+
+ in = talloc(ctx, struct inotify_private);
+ if (in == NULL) {
+ return ENOMEM;
+ }
+
+ in->fd = inotify_init();
+ if (in->fd == -1) {
+ int ret = errno;
+ DEBUG(0, ("Failed to init inotify - %s\n", strerror(ret)));
+ talloc_free(in);
+ return ret;
+ }
+ in->ctx = ctx;
+ in->watches = NULL;
+
+ ctx->private_data = in;
+ talloc_set_destructor(in, inotify_destructor);
+
+ /* add a event waiting for the inotify fd to be readable */
+ fde = tevent_add_fd(ctx->ev, in, in->fd, TEVENT_FD_READ,
+ inotify_handler, in);
+ if (fde == NULL) {
+ ctx->private_data = NULL;
+ TALLOC_FREE(in);
+ return ENOMEM;
+ }
+ return 0;
+}
+
+/*
+ destroy a watch
+*/
+static int watch_destructor(struct inotify_watch_context *w)
+{
+ struct inotify_private *in = w->in;
+ int wd = w->wd;
+ DLIST_REMOVE(w->in->watches, w);
+
+ for (w=in->watches;w;w=w->next) {
+ if (w->wd == wd) {
+ /*
+ * Another inotify_watch_context listens on this path,
+ * leave the kernel level watch in place
+ */
+ return 0;
+ }
+ }
+
+ DEBUG(10, ("Deleting inotify watch %d\n", wd));
+ if (inotify_rm_watch(in->fd, wd) == -1) {
+ DEBUG(1, ("inotify_rm_watch returned %s\n", strerror(errno)));
+ }
+ return 0;
+}
+
+
+/*
+ add a watch. The watch is removed when the caller calls
+ talloc_free() on *handle
+*/
+int inotify_watch(TALLOC_CTX *mem_ctx,
+ struct sys_notify_context *ctx,
+ const char *path,
+ uint32_t *filter,
+ uint32_t *subdir_filter,
+ void (*callback)(struct sys_notify_context *ctx,
+ void *private_data,
+ struct notify_event *ev,
+ uint32_t filter),
+ void *private_data,
+ void *handle_p)
+{
+ struct inotify_private *in;
+ uint32_t mask;
+ struct inotify_watch_context *w;
+ uint32_t orig_filter = *filter;
+ void **handle = (void **)handle_p;
+
+ /* maybe setup the inotify fd */
+ if (ctx->private_data == NULL) {
+ int ret;
+ ret = inotify_setup(ctx);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ in = talloc_get_type(ctx->private_data, struct inotify_private);
+
+ mask = inotify_map(filter);
+ if (mask == 0) {
+ /* this filter can't be handled by inotify */
+ return EINVAL;
+ }
+
+ /* using IN_MASK_ADD allows us to cope with inotify() returning the same
+ watch descriptor for multiple watches on the same path */
+ mask |= (IN_MASK_ADD | IN_ONLYDIR);
+
+ w = talloc(mem_ctx, struct inotify_watch_context);
+ if (w == NULL) {
+ *filter = orig_filter;
+ return ENOMEM;
+ }
+
+ w->in = in;
+ w->callback = callback;
+ w->private_data = private_data;
+ w->mask = mask;
+ w->filter = orig_filter;
+ w->path = talloc_strdup(w, path);
+ if (w->path == NULL) {
+ *filter = orig_filter;
+ TALLOC_FREE(w);
+ return ENOMEM;
+ }
+
+ /* get a new watch descriptor for this path */
+ w->wd = inotify_add_watch(in->fd, path, mask);
+ if (w->wd == -1) {
+ int err = errno;
+ *filter = orig_filter;
+ TALLOC_FREE(w);
+ DEBUG(1, ("inotify_add_watch returned %s\n", strerror(err)));
+ return err;
+ }
+
+ DEBUG(10, ("inotify_add_watch for %s mask %x returned wd %d\n",
+ path, mask, w->wd));
+
+ (*handle) = w;
+
+ DLIST_ADD(in->watches, w);
+
+ /* the caller frees the handle to stop watching */
+ talloc_set_destructor(w, watch_destructor);
+
+ return 0;
+}
diff --git a/source3/smbd/notify_msg.c b/source3/smbd/notify_msg.c
new file mode 100644
index 0000000..0ea992c
--- /dev/null
+++ b/source3/smbd/notify_msg.c
@@ -0,0 +1,247 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Copyright (C) Volker Lendecke 2014
+ *
+ * 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 "librpc/gen_ndr/notify.h"
+#include "librpc/gen_ndr/messaging.h"
+#include "lib/dbwrap/dbwrap.h"
+#include "lib/dbwrap/dbwrap_rbt.h"
+#include "lib/util/server_id.h"
+#include "messages.h"
+#include "proto.h"
+#include "globals.h"
+#include "tdb.h"
+#include "util_tdb.h"
+#include "lib/util/server_id_db.h"
+#include "smbd/notifyd/notifyd.h"
+
+struct notify_context {
+ struct server_id notifyd;
+ struct messaging_context *msg_ctx;
+
+ struct smbd_server_connection *sconn;
+ void (*callback)(struct smbd_server_connection *sconn,
+ void *private_data, struct timespec when,
+ const struct notify_event *ctx);
+};
+
+static void notify_handler(struct messaging_context *msg, void *private_data,
+ uint32_t msg_type, struct server_id src,
+ DATA_BLOB *data);
+static int notify_context_destructor(struct notify_context *ctx);
+
+struct notify_context *notify_init(
+ TALLOC_CTX *mem_ctx, struct messaging_context *msg,
+ struct smbd_server_connection *sconn,
+ void (*callback)(struct smbd_server_connection *sconn,
+ void *, struct timespec,
+ const struct notify_event *))
+{
+ struct server_id_db *names_db;
+ struct notify_context *ctx;
+ NTSTATUS status;
+
+ ctx = talloc(mem_ctx, struct notify_context);
+ if (ctx == NULL) {
+ return NULL;
+ }
+ ctx->msg_ctx = msg;
+
+ ctx->sconn = sconn;
+ ctx->callback = callback;
+
+ names_db = messaging_names_db(msg);
+ if (!server_id_db_lookup_one(names_db, "notify-daemon",
+ &ctx->notifyd)) {
+ DBG_WARNING("No notify daemon around\n");
+ TALLOC_FREE(ctx);
+ return NULL;
+ }
+
+ {
+ struct server_id_buf tmp;
+ DBG_DEBUG("notifyd=%s\n",
+ server_id_str_buf(ctx->notifyd, &tmp));
+ }
+
+ if (callback != NULL) {
+ status = messaging_register(msg, ctx, MSG_PVFS_NOTIFY,
+ notify_handler);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("messaging_register failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(ctx);
+ return NULL;
+ }
+ }
+
+ talloc_set_destructor(ctx, notify_context_destructor);
+
+ return ctx;
+}
+
+static int notify_context_destructor(struct notify_context *ctx)
+{
+ if (ctx->callback != NULL) {
+ messaging_deregister(ctx->msg_ctx, MSG_PVFS_NOTIFY, ctx);
+ }
+
+ return 0;
+}
+
+static void notify_handler(struct messaging_context *msg, void *private_data,
+ uint32_t msg_type, struct server_id src,
+ DATA_BLOB *data)
+{
+ struct notify_context *ctx = talloc_get_type_abort(
+ private_data, struct notify_context);
+ struct notify_event_msg *event_msg;
+ struct notify_event event;
+
+ if (data->length < offsetof(struct notify_event_msg, path) + 1) {
+ DBG_WARNING("message too short: %zu\n", data->length);
+ return;
+ }
+ if (data->data[data->length-1] != 0) {
+ DBG_WARNING("path not 0-terminated\n");
+ return;
+ }
+
+ event_msg = (struct notify_event_msg *)data->data;
+
+ event.action = event_msg->action;
+ event.path = event_msg->path;
+ event.private_data = event_msg->private_data;
+
+ DBG_DEBUG("Got notify_event action=%"PRIu32", private_data=%p, "
+ "path=%s\n",
+ event.action,
+ event.private_data,
+ event.path);
+
+ ctx->callback(ctx->sconn, event.private_data, event_msg->when, &event);
+}
+
+NTSTATUS notify_add(struct notify_context *ctx,
+ const char *path, uint32_t filter, uint32_t subdir_filter,
+ void *private_data)
+{
+ struct notify_rec_change_msg msg = {};
+ struct iovec iov[2];
+ size_t pathlen;
+ NTSTATUS status;
+
+ if (ctx == NULL) {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ DBG_DEBUG("path=[%s], filter=%"PRIu32", subdir_filter=%"PRIu32", "
+ "private_data=%p\n",
+ path,
+ filter,
+ subdir_filter,
+ private_data);
+
+ pathlen = strlen(path)+1;
+
+ clock_gettime_mono(&msg.instance.creation_time);
+ msg.instance.filter = filter;
+ msg.instance.subdir_filter = subdir_filter;
+ msg.instance.private_data = private_data;
+
+ iov[0].iov_base = &msg;
+ iov[0].iov_len = offsetof(struct notify_rec_change_msg, path);
+ iov[1].iov_base = discard_const_p(char, path);
+ iov[1].iov_len = pathlen;
+
+ status = messaging_send_iov(
+ ctx->msg_ctx, ctx->notifyd, MSG_SMB_NOTIFY_REC_CHANGE,
+ iov, ARRAY_SIZE(iov), NULL, 0);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("messaging_send_iov returned %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS notify_remove(struct notify_context *ctx, void *private_data,
+ char *path)
+{
+ struct notify_rec_change_msg msg = {};
+ struct iovec iov[2];
+ NTSTATUS status;
+
+ /* see if change notify is enabled at all */
+ if (ctx == NULL) {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ msg.instance.private_data = private_data;
+
+ iov[0].iov_base = &msg;
+ iov[0].iov_len = offsetof(struct notify_rec_change_msg, path);
+ iov[1].iov_base = path;
+ iov[1].iov_len = strlen(path)+1;
+
+ status = messaging_send_iov(
+ ctx->msg_ctx, ctx->notifyd, MSG_SMB_NOTIFY_REC_CHANGE,
+ iov, ARRAY_SIZE(iov), NULL, 0);
+
+ return status;
+}
+
+void notify_trigger(struct notify_context *ctx,
+ uint32_t action, uint32_t filter,
+ const char *dir, const char *name)
+{
+ struct notify_trigger_msg msg;
+ struct iovec iov[4];
+ char slash = '/';
+
+ DBG_DEBUG("notify_trigger called action=0x%"PRIx32", "
+ "filter=0x%"PRIx32", dir=%s, name=%s\n",
+ action,
+ filter,
+ dir,
+ name);
+
+ if (ctx == NULL) {
+ return;
+ }
+
+ msg.when = timespec_current();
+ msg.action = action;
+ msg.filter = filter;
+
+ iov[0].iov_base = &msg;
+ iov[0].iov_len = offsetof(struct notify_trigger_msg, path);
+ iov[1].iov_base = discard_const_p(char, dir);
+ iov[1].iov_len = strlen(dir);
+ iov[2].iov_base = &slash;
+ iov[2].iov_len = 1;
+ iov[3].iov_base = discard_const_p(char, name);
+ iov[3].iov_len = strlen(name)+1;
+
+ messaging_send_iov(
+ ctx->msg_ctx, ctx->notifyd, MSG_SMB_NOTIFY_TRIGGER,
+ iov, ARRAY_SIZE(iov), NULL, 0);
+}
diff --git a/source3/smbd/notifyd/fcn_wait.c b/source3/smbd/notifyd/fcn_wait.c
new file mode 100644
index 0000000..e32240d
--- /dev/null
+++ b/source3/smbd/notifyd/fcn_wait.c
@@ -0,0 +1,270 @@
+/*
+ * 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 "replace.h"
+#include "fcn_wait.h"
+#include "notifyd.h"
+#include "lib/util/tevent_ntstatus.h"
+
+struct fcn_event {
+ struct fcn_event *prev, *next;
+ struct notify_event_msg msg;
+};
+
+struct fcn_wait_state {
+ struct tevent_context *ev;
+ struct messaging_context *msg_ctx;
+ struct server_id notifyd;
+ const char *path;
+
+ struct tevent_req *recv_subreq;
+
+ struct fcn_event *events;
+};
+
+static bool fcn_wait_cancel(struct tevent_req *req);
+static void fcn_wait_cleanup(
+ struct tevent_req *req, enum tevent_req_state req_state);
+static bool fcn_wait_filter(struct messaging_rec *rec, void *private_data);
+static void fcn_wait_done(struct tevent_req *subreq);
+
+struct tevent_req *fcn_wait_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ struct server_id notifyd,
+ const char *path,
+ uint32_t filter,
+ uint32_t subdir_filter)
+{
+ struct tevent_req *req = NULL;
+ struct fcn_wait_state *state = NULL;
+ struct notify_rec_change_msg msg = {
+ .instance.filter = filter,
+ .instance.subdir_filter = subdir_filter,
+ };
+ struct iovec iov[2];
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state, struct fcn_wait_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->msg_ctx = msg_ctx;
+ state->notifyd = notifyd;
+ state->path = path;
+
+ state->recv_subreq = messaging_filtered_read_send(
+ state, ev, msg_ctx, fcn_wait_filter, req);
+ if (tevent_req_nomem(state->recv_subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(state->recv_subreq, fcn_wait_done, req);
+ tevent_req_set_cleanup_fn(req, fcn_wait_cleanup);
+
+ clock_gettime_mono(&msg.instance.creation_time);
+ msg.instance.private_data = state;
+
+ iov[0].iov_base = &msg;
+ iov[0].iov_len = offsetof(struct notify_rec_change_msg, path);
+ iov[1].iov_base = discard_const_p(char, path);
+ iov[1].iov_len = strlen(path)+1;
+
+ status = messaging_send_iov(
+ msg_ctx, /* msg_ctx */
+ notifyd, /* dst */
+ MSG_SMB_NOTIFY_REC_CHANGE, /* mst_type */
+ iov, /* iov */
+ ARRAY_SIZE(iov), /* iovlen */
+ NULL, /* fds */
+ 0); /* num_fds */
+ if (tevent_req_nterror(req, status)) {
+ DBG_DEBUG("messaging_send_iov failed: %s\n",
+ nt_errstr(status));
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_cancel_fn(req, fcn_wait_cancel);
+
+ return req;
+}
+
+static bool fcn_wait_cancel(struct tevent_req *req)
+{
+ struct fcn_wait_state *state = tevent_req_data(
+ req, struct fcn_wait_state);
+ struct notify_rec_change_msg msg = {
+ .instance.filter = 0, /* filter==0 is a delete msg */
+ .instance.subdir_filter = 0,
+ };
+ struct iovec iov[2];
+ NTSTATUS status;
+
+ clock_gettime_mono(&msg.instance.creation_time);
+ msg.instance.private_data = state;
+
+ iov[0].iov_base = &msg;
+ iov[0].iov_len = offsetof(struct notify_rec_change_msg, path);
+ iov[1].iov_base = discard_const_p(char, state->path);
+ iov[1].iov_len = strlen(state->path)+1;
+
+ status = messaging_send_iov(
+ state->msg_ctx, /* msg_ctx */
+ state->notifyd, /* dst */
+ MSG_SMB_NOTIFY_REC_CHANGE, /* mst_type */
+ iov, /* iov */
+ ARRAY_SIZE(iov), /* iovlen */
+ NULL, /* fds */
+ 0); /* num_fds */
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("messaging_send_iov failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ fcn_wait_cleanup(req, 0); /* fcn_wait_cleanup ignores req_state */
+ tevent_req_defer_callback(req, state->ev);
+ tevent_req_nterror(req, NT_STATUS_CANCELLED);
+
+ return true;
+}
+
+static void fcn_wait_cleanup(
+ struct tevent_req *req, enum tevent_req_state req_state)
+{
+ struct fcn_wait_state *state = tevent_req_data(
+ req, struct fcn_wait_state);
+ TALLOC_FREE(state->recv_subreq);
+}
+
+static bool fcn_wait_filter(struct messaging_rec *rec, void *private_data)
+{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
+ struct fcn_wait_state *state = tevent_req_data(
+ req, struct fcn_wait_state);
+ struct notify_event_msg msg = { .action = 0 };
+ struct fcn_event *evt = NULL;
+
+ if (rec->msg_type != MSG_PVFS_NOTIFY) {
+ DBG_DEBUG("Ignoring msg %"PRIu32"\n", rec->msg_type);
+ return false;
+ }
+
+ /*
+ * We need at least the trailing '\0' for the path
+ */
+ if (rec->buf.length < (offsetof(struct notify_event_msg, path) + 1)) {
+ DBG_DEBUG("Ignoring short (%zu) msg\n", rec->buf.length);
+ return false;
+ }
+ if (rec->buf.data[rec->buf.length-1] != '\0') {
+ DBG_DEBUG("Expected 0-terminated path\n");
+ return false;
+ }
+
+ memcpy(&msg, rec->buf.data, sizeof(msg));
+
+ if (msg.private_data != state) {
+ DBG_DEBUG("Got private_data=%p, expected %p\n",
+ msg.private_data,
+ state);
+ return false;
+ }
+
+ evt = talloc_memdup(state, rec->buf.data, rec->buf.length);
+ if (evt == NULL) {
+ DBG_DEBUG("talloc_memdup failed\n");
+ return false;
+ }
+ talloc_set_name_const(evt, "struct fcn_event");
+
+ /*
+ * TODO: Sort by timestamp
+ */
+
+ DLIST_ADD_END(state->events, evt);
+
+ tevent_req_defer_callback(req, state->ev);
+ tevent_req_notify_callback(req);
+
+ return false;
+}
+
+static void fcn_wait_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ int ret;
+
+ ret = messaging_filtered_read_recv(subreq, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ DBG_DEBUG("messaging_filtered_read failed: %s\n",
+ strerror(ret));
+ tevent_req_nterror(req, map_nt_error_from_unix(ret));
+ return;
+ }
+
+ /*
+ * We should never have gotten here, all work is done from the
+ * filter function.
+ */
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+}
+
+NTSTATUS fcn_wait_recv(
+ struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct timespec *when,
+ uint32_t *action,
+ char **path)
+{
+ struct fcn_wait_state *state = tevent_req_data(
+ req, struct fcn_wait_state);
+ struct fcn_event *evt = NULL;
+ NTSTATUS status;
+
+ if (!tevent_req_is_in_progress(req) &&
+ tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ evt = state->events;
+ if (evt == NULL) {
+ return NT_STATUS_RETRY;
+ }
+
+ if (path != NULL) {
+ *path = talloc_strdup(mem_ctx, evt->msg.path);
+ if ((*path) == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ if (when != NULL) {
+ *when = evt->msg.when;
+ }
+ if (action != NULL) {
+ *action = evt->msg.action;
+ }
+
+ DLIST_REMOVE(state->events, evt);
+
+ if (state->events != NULL) {
+ tevent_req_defer_callback(req, state->ev);
+ tevent_req_notify_callback(req);
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/notifyd/fcn_wait.h b/source3/smbd/notifyd/fcn_wait.h
new file mode 100644
index 0000000..daadee4
--- /dev/null
+++ b/source3/smbd/notifyd/fcn_wait.h
@@ -0,0 +1,38 @@
+/*
+ * 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/>.
+ */
+
+#ifndef __NOTIFYD_FCN_WAIT_H__
+#define __NOTIFYD_FCN_WAIT_H__
+
+#include "replace.h"
+#include "messages.h"
+#include "librpc/gen_ndr/server_id.h"
+
+struct tevent_req *fcn_wait_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ struct server_id notifyd,
+ const char *path,
+ uint32_t filter,
+ uint32_t subdir_filter);
+NTSTATUS fcn_wait_recv(
+ struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct timespec *when,
+ uint32_t *action,
+ char **path);
+
+#endif
diff --git a/source3/smbd/notifyd/notifyd.c b/source3/smbd/notifyd/notifyd.c
new file mode 100644
index 0000000..ca303bd
--- /dev/null
+++ b/source3/smbd/notifyd/notifyd.c
@@ -0,0 +1,1428 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Copyright (C) Volker Lendecke 2014
+ *
+ * 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 "replace.h"
+#include <tevent.h>
+#include "notifyd_private.h"
+#include "lib/util/server_id.h"
+#include "lib/util/data_blob.h"
+#include "librpc/gen_ndr/notify.h"
+#include "librpc/gen_ndr/messaging.h"
+#include "librpc/gen_ndr/server_id.h"
+#include "lib/dbwrap/dbwrap.h"
+#include "lib/dbwrap/dbwrap_rbt.h"
+#include "messages.h"
+#include "tdb.h"
+#include "util_tdb.h"
+#include "notifyd.h"
+#include "lib/util/server_id_db.h"
+#include "lib/util/tevent_unix.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "ctdbd_conn.h"
+#include "ctdb_srvids.h"
+#include "server_id_db_util.h"
+#include "lib/util/iov_buf.h"
+#include "messages_util.h"
+
+#ifdef CLUSTER_SUPPORT
+#include "ctdb_protocol.h"
+#endif
+
+struct notifyd_peer;
+
+/*
+ * All of notifyd's state
+ */
+
+struct notifyd_state {
+ struct tevent_context *ev;
+ struct messaging_context *msg_ctx;
+ struct ctdbd_connection *ctdbd_conn;
+
+ /*
+ * Database of everything clients show interest in. Indexed by
+ * absolute path. The database keys are not 0-terminated
+ * to allow the critical operation, notifyd_trigger, to walk
+ * the structure from the top without adding intermediate 0s.
+ * The database records contain an array of
+ *
+ * struct notifyd_instance
+ *
+ * to be maintained and parsed by notifyd_parse_entry()
+ */
+ struct db_context *entries;
+
+ /*
+ * In the cluster case, this is the place where we store a log
+ * of all MSG_SMB_NOTIFY_REC_CHANGE messages. We just 1:1
+ * forward them to our peer notifyd's in the cluster once a
+ * second or when the log grows too large.
+ */
+
+ struct messaging_reclog *log;
+
+ /*
+ * Array of companion notifyd's in a cluster. Every notifyd
+ * broadcasts its messaging_reclog to every other notifyd in
+ * the cluster. This is done by making ctdb send a message to
+ * srvid CTDB_SRVID_SAMBA_NOTIFY_PROXY with destination node
+ * number CTDB_BROADCAST_CONNECTED. Everybody in the cluster who
+ * had called register_with_ctdbd this srvid will receive the
+ * broadcasts.
+ *
+ * Database replication happens via these broadcasts. Also,
+ * they serve as liveness indication. If a notifyd receives a
+ * broadcast from an unknown peer, it will create one for this
+ * srvid. Also when we don't hear anything from a peer for a
+ * while, we will discard it.
+ */
+
+ struct notifyd_peer **peers;
+ size_t num_peers;
+
+ sys_notify_watch_fn sys_notify_watch;
+ struct sys_notify_context *sys_notify_ctx;
+};
+
+struct notifyd_peer {
+ struct notifyd_state *state;
+ struct server_id pid;
+ uint64_t rec_index;
+ struct db_context *db;
+ time_t last_broadcast;
+};
+
+static void notifyd_rec_change(struct messaging_context *msg_ctx,
+ void *private_data, uint32_t msg_type,
+ struct server_id src, DATA_BLOB *data);
+static void notifyd_trigger(struct messaging_context *msg_ctx,
+ void *private_data, uint32_t msg_type,
+ struct server_id src, DATA_BLOB *data);
+static void notifyd_get_db(struct messaging_context *msg_ctx,
+ void *private_data, uint32_t msg_type,
+ struct server_id src, DATA_BLOB *data);
+
+#ifdef CLUSTER_SUPPORT
+static void notifyd_got_db(struct messaging_context *msg_ctx,
+ void *private_data, uint32_t msg_type,
+ struct server_id src, DATA_BLOB *data);
+static void notifyd_broadcast_reclog(struct ctdbd_connection *ctdbd_conn,
+ struct server_id src,
+ struct messaging_reclog *log);
+#endif
+static void notifyd_sys_callback(struct sys_notify_context *ctx,
+ void *private_data, struct notify_event *ev,
+ uint32_t filter);
+
+#ifdef CLUSTER_SUPPORT
+static struct tevent_req *notifyd_broadcast_reclog_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct ctdbd_connection *ctdbd_conn, struct server_id src,
+ struct messaging_reclog *log);
+static int notifyd_broadcast_reclog_recv(struct tevent_req *req);
+
+static struct tevent_req *notifyd_clean_peers_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct notifyd_state *notifyd);
+static int notifyd_clean_peers_recv(struct tevent_req *req);
+#endif
+
+static int sys_notify_watch_dummy(
+ TALLOC_CTX *mem_ctx,
+ struct sys_notify_context *ctx,
+ const char *path,
+ uint32_t *filter,
+ uint32_t *subdir_filter,
+ void (*callback)(struct sys_notify_context *ctx,
+ void *private_data,
+ struct notify_event *ev,
+ uint32_t filter),
+ void *private_data,
+ void *handle_p)
+{
+ void **handle = handle_p;
+ *handle = NULL;
+ return 0;
+}
+
+#ifdef CLUSTER_SUPPORT
+static void notifyd_broadcast_reclog_finished(struct tevent_req *subreq);
+static void notifyd_clean_peers_finished(struct tevent_req *subreq);
+static int notifyd_snoop_broadcast(struct tevent_context *ev,
+ uint32_t src_vnn, uint32_t dst_vnn,
+ uint64_t dst_srvid,
+ const uint8_t *msg, size_t msglen,
+ void *private_data);
+#endif
+
+struct tevent_req *notifyd_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ struct ctdbd_connection *ctdbd_conn,
+ sys_notify_watch_fn sys_notify_watch,
+ struct sys_notify_context *sys_notify_ctx)
+{
+ struct tevent_req *req;
+#ifdef CLUSTER_SUPPORT
+ struct tevent_req *subreq;
+#endif
+ struct notifyd_state *state;
+ struct server_id_db *names_db;
+ NTSTATUS status;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct notifyd_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->msg_ctx = msg_ctx;
+ state->ctdbd_conn = ctdbd_conn;
+
+ if (sys_notify_watch == NULL) {
+ sys_notify_watch = sys_notify_watch_dummy;
+ }
+
+ state->sys_notify_watch = sys_notify_watch;
+ state->sys_notify_ctx = sys_notify_ctx;
+
+ state->entries = db_open_rbt(state);
+ if (tevent_req_nomem(state->entries, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ status = messaging_register(msg_ctx, state, MSG_SMB_NOTIFY_REC_CHANGE,
+ notifyd_rec_change);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ status = messaging_register(msg_ctx, state, MSG_SMB_NOTIFY_TRIGGER,
+ notifyd_trigger);
+ if (tevent_req_nterror(req, status)) {
+ goto deregister_rec_change;
+ }
+
+ status = messaging_register(msg_ctx, state, MSG_SMB_NOTIFY_GET_DB,
+ notifyd_get_db);
+ if (tevent_req_nterror(req, status)) {
+ goto deregister_trigger;
+ }
+
+ names_db = messaging_names_db(msg_ctx);
+
+ ret = server_id_db_set_exclusive(names_db, "notify-daemon");
+ if (ret != 0) {
+ DBG_DEBUG("server_id_db_add failed: %s\n",
+ strerror(ret));
+ tevent_req_error(req, ret);
+ goto deregister_get_db;
+ }
+
+ if (ctdbd_conn == NULL) {
+ /*
+ * No cluster around, skip the database replication
+ * engine
+ */
+ return req;
+ }
+
+#ifdef CLUSTER_SUPPORT
+ status = messaging_register(msg_ctx, state, MSG_SMB_NOTIFY_DB,
+ notifyd_got_db);
+ if (tevent_req_nterror(req, status)) {
+ goto deregister_get_db;
+ }
+
+ state->log = talloc_zero(state, struct messaging_reclog);
+ if (tevent_req_nomem(state->log, req)) {
+ goto deregister_db;
+ }
+
+ subreq = notifyd_broadcast_reclog_send(
+ state->log, ev, ctdbd_conn,
+ messaging_server_id(msg_ctx),
+ state->log);
+ if (tevent_req_nomem(subreq, req)) {
+ goto deregister_db;
+ }
+ tevent_req_set_callback(subreq,
+ notifyd_broadcast_reclog_finished,
+ req);
+
+ subreq = notifyd_clean_peers_send(state, ev, state);
+ if (tevent_req_nomem(subreq, req)) {
+ goto deregister_db;
+ }
+ tevent_req_set_callback(subreq, notifyd_clean_peers_finished,
+ req);
+
+ ret = register_with_ctdbd(ctdbd_conn,
+ CTDB_SRVID_SAMBA_NOTIFY_PROXY,
+ notifyd_snoop_broadcast, state);
+ if (ret != 0) {
+ tevent_req_error(req, ret);
+ goto deregister_db;
+ }
+#endif
+
+ return req;
+
+#ifdef CLUSTER_SUPPORT
+deregister_db:
+ messaging_deregister(msg_ctx, MSG_SMB_NOTIFY_DB, state);
+#endif
+deregister_get_db:
+ messaging_deregister(msg_ctx, MSG_SMB_NOTIFY_GET_DB, state);
+deregister_trigger:
+ messaging_deregister(msg_ctx, MSG_SMB_NOTIFY_TRIGGER, state);
+deregister_rec_change:
+ messaging_deregister(msg_ctx, MSG_SMB_NOTIFY_REC_CHANGE, state);
+ return tevent_req_post(req, ev);
+}
+
+#ifdef CLUSTER_SUPPORT
+
+static void notifyd_broadcast_reclog_finished(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ int ret;
+
+ ret = notifyd_broadcast_reclog_recv(subreq);
+ TALLOC_FREE(subreq);
+ tevent_req_error(req, ret);
+}
+
+static void notifyd_clean_peers_finished(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ int ret;
+
+ ret = notifyd_clean_peers_recv(subreq);
+ TALLOC_FREE(subreq);
+ tevent_req_error(req, ret);
+}
+
+#endif
+
+int notifyd_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_unix(req);
+}
+
+static bool notifyd_apply_rec_change(
+ const struct server_id *client,
+ const char *path, size_t pathlen,
+ const struct notify_instance *chg,
+ struct db_context *entries,
+ sys_notify_watch_fn sys_notify_watch,
+ struct sys_notify_context *sys_notify_ctx,
+ struct messaging_context *msg_ctx)
+{
+ struct db_record *rec = NULL;
+ struct notifyd_instance *instances = NULL;
+ size_t num_instances;
+ size_t i;
+ struct notifyd_instance *instance = NULL;
+ TDB_DATA value;
+ NTSTATUS status;
+ bool ok = false;
+
+ if (pathlen == 0) {
+ DBG_WARNING("pathlen==0\n");
+ return false;
+ }
+ if (path[pathlen-1] != '\0') {
+ DBG_WARNING("path not 0-terminated\n");
+ return false;
+ }
+
+ DBG_DEBUG("path=%s, filter=%"PRIu32", subdir_filter=%"PRIu32", "
+ "private_data=%p\n",
+ path,
+ chg->filter,
+ chg->subdir_filter,
+ chg->private_data);
+
+ rec = dbwrap_fetch_locked(
+ entries, entries,
+ make_tdb_data((const uint8_t *)path, pathlen-1));
+
+ if (rec == NULL) {
+ DBG_WARNING("dbwrap_fetch_locked failed\n");
+ goto fail;
+ }
+
+ num_instances = 0;
+ value = dbwrap_record_get_value(rec);
+
+ if (value.dsize != 0) {
+ if (!notifyd_parse_entry(value.dptr, value.dsize, NULL,
+ &num_instances)) {
+ goto fail;
+ }
+ }
+
+ /*
+ * Overallocate by one instance to avoid a realloc when adding
+ */
+ instances = talloc_array(rec, struct notifyd_instance,
+ num_instances + 1);
+ if (instances == NULL) {
+ DBG_WARNING("talloc failed\n");
+ goto fail;
+ }
+
+ if (value.dsize != 0) {
+ memcpy(instances, value.dptr, value.dsize);
+ }
+
+ for (i=0; i<num_instances; i++) {
+ instance = &instances[i];
+
+ if (server_id_equal(&instance->client, client) &&
+ (instance->instance.private_data == chg->private_data)) {
+ break;
+ }
+ }
+
+ if (i < num_instances) {
+ instance->instance = *chg;
+ } else {
+ /*
+ * We've overallocated for one instance
+ */
+ instance = &instances[num_instances];
+
+ *instance = (struct notifyd_instance) {
+ .client = *client,
+ .instance = *chg,
+ .internal_filter = chg->filter,
+ .internal_subdir_filter = chg->subdir_filter
+ };
+
+ num_instances += 1;
+ }
+
+ if ((instance->instance.filter != 0) ||
+ (instance->instance.subdir_filter != 0)) {
+ int ret;
+
+ TALLOC_FREE(instance->sys_watch);
+
+ ret = sys_notify_watch(entries, sys_notify_ctx, path,
+ &instance->internal_filter,
+ &instance->internal_subdir_filter,
+ notifyd_sys_callback, msg_ctx,
+ &instance->sys_watch);
+ if (ret != 0) {
+ DBG_WARNING("sys_notify_watch for [%s] returned %s\n",
+ path, strerror(errno));
+ }
+ }
+
+ if ((instance->instance.filter == 0) &&
+ (instance->instance.subdir_filter == 0)) {
+ /* This is a delete request */
+ TALLOC_FREE(instance->sys_watch);
+ *instance = instances[num_instances-1];
+ num_instances -= 1;
+ }
+
+ DBG_DEBUG("%s has %zu instances\n", path, num_instances);
+
+ if (num_instances == 0) {
+ status = dbwrap_record_delete(rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("dbwrap_record_delete returned %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ } else {
+ value = make_tdb_data(
+ (uint8_t *)instances,
+ sizeof(struct notifyd_instance) * num_instances);
+
+ status = dbwrap_record_store(rec, value, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("dbwrap_record_store returned %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ }
+
+ ok = true;
+fail:
+ TALLOC_FREE(rec);
+ return ok;
+}
+
+static void notifyd_sys_callback(struct sys_notify_context *ctx,
+ void *private_data, struct notify_event *ev,
+ uint32_t filter)
+{
+ struct messaging_context *msg_ctx = talloc_get_type_abort(
+ private_data, struct messaging_context);
+ struct notify_trigger_msg msg;
+ struct iovec iov[4];
+ char slash = '/';
+
+ msg = (struct notify_trigger_msg) {
+ .when = timespec_current(),
+ .action = ev->action,
+ .filter = filter,
+ };
+
+ iov[0].iov_base = &msg;
+ iov[0].iov_len = offsetof(struct notify_trigger_msg, path);
+ iov[1].iov_base = discard_const_p(char, ev->dir);
+ iov[1].iov_len = strlen(ev->dir);
+ iov[2].iov_base = &slash;
+ iov[2].iov_len = 1;
+ iov[3].iov_base = discard_const_p(char, ev->path);
+ iov[3].iov_len = strlen(ev->path)+1;
+
+ messaging_send_iov(
+ msg_ctx, messaging_server_id(msg_ctx),
+ MSG_SMB_NOTIFY_TRIGGER, iov, ARRAY_SIZE(iov), NULL, 0);
+}
+
+static bool notifyd_parse_rec_change(uint8_t *buf, size_t bufsize,
+ struct notify_rec_change_msg **pmsg,
+ size_t *pathlen)
+{
+ struct notify_rec_change_msg *msg;
+
+ if (bufsize < offsetof(struct notify_rec_change_msg, path) + 1) {
+ DBG_WARNING("message too short, ignoring: %zu\n", bufsize);
+ return false;
+ }
+
+ *pmsg = msg = (struct notify_rec_change_msg *)buf;
+ *pathlen = bufsize - offsetof(struct notify_rec_change_msg, path);
+
+ DBG_DEBUG("Got rec_change_msg filter=%"PRIu32", "
+ "subdir_filter=%"PRIu32", private_data=%p, path=%.*s\n",
+ msg->instance.filter,
+ msg->instance.subdir_filter,
+ msg->instance.private_data,
+ (int)(*pathlen),
+ msg->path);
+
+ return true;
+}
+
+static void notifyd_rec_change(struct messaging_context *msg_ctx,
+ void *private_data, uint32_t msg_type,
+ struct server_id src, DATA_BLOB *data)
+{
+ struct notifyd_state *state = talloc_get_type_abort(
+ private_data, struct notifyd_state);
+ struct server_id_buf idbuf;
+ struct notify_rec_change_msg *msg;
+ size_t pathlen;
+ bool ok;
+ struct notify_instance instance;
+
+ DBG_DEBUG("Got %zu bytes from %s\n", data->length,
+ server_id_str_buf(src, &idbuf));
+
+ ok = notifyd_parse_rec_change(data->data, data->length,
+ &msg, &pathlen);
+ if (!ok) {
+ return;
+ }
+
+ memcpy(&instance, &msg->instance, sizeof(instance)); /* avoid SIGBUS */
+
+ ok = notifyd_apply_rec_change(
+ &src, msg->path, pathlen, &instance,
+ state->entries, state->sys_notify_watch, state->sys_notify_ctx,
+ state->msg_ctx);
+ if (!ok) {
+ DBG_DEBUG("notifyd_apply_rec_change failed, ignoring\n");
+ return;
+ }
+
+ if ((state->log == NULL) || (state->ctdbd_conn == NULL)) {
+ return;
+ }
+
+#ifdef CLUSTER_SUPPORT
+ {
+
+ struct messaging_rec **tmp;
+ struct messaging_reclog *log;
+ struct iovec iov = { .iov_base = data->data, .iov_len = data->length };
+
+ log = state->log;
+
+ tmp = talloc_realloc(log, log->recs, struct messaging_rec *,
+ log->num_recs+1);
+ if (tmp == NULL) {
+ DBG_WARNING("talloc_realloc failed, ignoring\n");
+ return;
+ }
+ log->recs = tmp;
+
+ log->recs[log->num_recs] = messaging_rec_create(
+ log->recs, src, messaging_server_id(msg_ctx),
+ msg_type, &iov, 1, NULL, 0);
+
+ if (log->recs[log->num_recs] == NULL) {
+ DBG_WARNING("messaging_rec_create failed, ignoring\n");
+ return;
+ }
+
+ log->num_recs += 1;
+
+ if (log->num_recs >= 100) {
+ /*
+ * Don't let the log grow too large
+ */
+ notifyd_broadcast_reclog(state->ctdbd_conn,
+ messaging_server_id(msg_ctx), log);
+ }
+
+ }
+#endif
+}
+
+struct notifyd_trigger_state {
+ struct messaging_context *msg_ctx;
+ struct notify_trigger_msg *msg;
+ bool recursive;
+ bool covered_by_sys_notify;
+};
+
+static void notifyd_trigger_parser(TDB_DATA key, TDB_DATA data,
+ void *private_data);
+
+static void notifyd_trigger(struct messaging_context *msg_ctx,
+ void *private_data, uint32_t msg_type,
+ struct server_id src, DATA_BLOB *data)
+{
+ struct notifyd_state *state = talloc_get_type_abort(
+ private_data, struct notifyd_state);
+ struct server_id my_id = messaging_server_id(msg_ctx);
+ struct notifyd_trigger_state tstate;
+ const char *path;
+ const char *p, *next_p;
+
+ if (data->length < offsetof(struct notify_trigger_msg, path) + 1) {
+ DBG_WARNING("message too short, ignoring: %zu\n",
+ data->length);
+ return;
+ }
+ if (data->data[data->length-1] != 0) {
+ DBG_WARNING("path not 0-terminated, ignoring\n");;
+ return;
+ }
+
+ tstate.msg_ctx = msg_ctx;
+
+ tstate.covered_by_sys_notify = (src.vnn == my_id.vnn);
+ tstate.covered_by_sys_notify &= !server_id_equal(&src, &my_id);
+
+ tstate.msg = (struct notify_trigger_msg *)data->data;
+ path = tstate.msg->path;
+
+ DBG_DEBUG("Got trigger_msg action=%"PRIu32", filter=%"PRIu32", "
+ "path=%s\n",
+ tstate.msg->action,
+ tstate.msg->filter,
+ path);
+
+ if (path[0] != '/') {
+ DBG_WARNING("path %s does not start with /, ignoring\n",
+ path);
+ return;
+ }
+
+ for (p = strchr(path+1, '/'); p != NULL; p = next_p) {
+ ptrdiff_t path_len = p - path;
+ TDB_DATA key;
+ uint32_t i;
+
+ next_p = strchr(p+1, '/');
+ tstate.recursive = (next_p != NULL);
+
+ DBG_DEBUG("Trying path %.*s\n", (int)path_len, path);
+
+ key = (TDB_DATA) { .dptr = discard_const_p(uint8_t, path),
+ .dsize = path_len };
+
+ dbwrap_parse_record(state->entries, key,
+ notifyd_trigger_parser, &tstate);
+
+ if (state->peers == NULL) {
+ continue;
+ }
+
+ if (src.vnn != my_id.vnn) {
+ continue;
+ }
+
+ for (i=0; i<state->num_peers; i++) {
+ if (state->peers[i]->db == NULL) {
+ /*
+ * Inactive peer, did not get a db yet
+ */
+ continue;
+ }
+ dbwrap_parse_record(state->peers[i]->db, key,
+ notifyd_trigger_parser, &tstate);
+ }
+ }
+}
+
+static void notifyd_send_delete(struct messaging_context *msg_ctx,
+ TDB_DATA key,
+ struct notifyd_instance *instance);
+
+static void notifyd_trigger_parser(TDB_DATA key, TDB_DATA data,
+ void *private_data)
+
+{
+ struct notifyd_trigger_state *tstate = private_data;
+ struct notify_event_msg msg = { .action = tstate->msg->action,
+ .when = tstate->msg->when };
+ struct iovec iov[2];
+ size_t path_len = key.dsize;
+ struct notifyd_instance *instances = NULL;
+ size_t num_instances = 0;
+ size_t i;
+
+ if (!notifyd_parse_entry(data.dptr, data.dsize, &instances,
+ &num_instances)) {
+ DBG_DEBUG("Could not parse notifyd_entry\n");
+ return;
+ }
+
+ DBG_DEBUG("Found %zu instances for %.*s\n",
+ num_instances,
+ (int)key.dsize,
+ (char *)key.dptr);
+
+ iov[0].iov_base = &msg;
+ iov[0].iov_len = offsetof(struct notify_event_msg, path);
+ iov[1].iov_base = tstate->msg->path + path_len + 1;
+ iov[1].iov_len = strlen((char *)(iov[1].iov_base)) + 1;
+
+ for (i=0; i<num_instances; i++) {
+ struct notifyd_instance *instance = &instances[i];
+ struct server_id_buf idbuf;
+ uint32_t i_filter;
+ NTSTATUS status;
+
+ if (tstate->covered_by_sys_notify) {
+ if (tstate->recursive) {
+ i_filter = instance->internal_subdir_filter;
+ } else {
+ i_filter = instance->internal_filter;
+ }
+ } else {
+ if (tstate->recursive) {
+ i_filter = instance->instance.subdir_filter;
+ } else {
+ i_filter = instance->instance.filter;
+ }
+ }
+
+ if ((i_filter & tstate->msg->filter) == 0) {
+ continue;
+ }
+
+ msg.private_data = instance->instance.private_data;
+
+ status = messaging_send_iov(
+ tstate->msg_ctx, instance->client,
+ MSG_PVFS_NOTIFY, iov, ARRAY_SIZE(iov), NULL, 0);
+
+ DBG_DEBUG("messaging_send_iov to %s returned %s\n",
+ server_id_str_buf(instance->client, &idbuf),
+ nt_errstr(status));
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND) &&
+ procid_is_local(&instance->client)) {
+ /*
+ * That process has died
+ */
+ notifyd_send_delete(tstate->msg_ctx, key, instance);
+ continue;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("messaging_send_iov returned %s\n",
+ nt_errstr(status));
+ }
+ }
+}
+
+/*
+ * Send a delete request to ourselves to properly discard a notify
+ * record for an smbd that has died.
+ */
+
+static void notifyd_send_delete(struct messaging_context *msg_ctx,
+ TDB_DATA key,
+ struct notifyd_instance *instance)
+{
+ struct notify_rec_change_msg msg = {
+ .instance.private_data = instance->instance.private_data
+ };
+ uint8_t nul = 0;
+ struct iovec iov[3];
+ int ret;
+
+ /*
+ * Send a rec_change to ourselves to delete a dead entry
+ */
+
+ iov[0] = (struct iovec) {
+ .iov_base = &msg,
+ .iov_len = offsetof(struct notify_rec_change_msg, path) };
+ iov[1] = (struct iovec) { .iov_base = key.dptr, .iov_len = key.dsize };
+ iov[2] = (struct iovec) { .iov_base = &nul, .iov_len = sizeof(nul) };
+
+ ret = messaging_send_iov_from(
+ msg_ctx, instance->client, messaging_server_id(msg_ctx),
+ MSG_SMB_NOTIFY_REC_CHANGE, iov, ARRAY_SIZE(iov), NULL, 0);
+
+ if (ret != 0) {
+ DBG_WARNING("messaging_send_iov_from returned %s\n",
+ strerror(ret));
+ }
+}
+
+static void notifyd_get_db(struct messaging_context *msg_ctx,
+ void *private_data, uint32_t msg_type,
+ struct server_id src, DATA_BLOB *data)
+{
+ struct notifyd_state *state = talloc_get_type_abort(
+ private_data, struct notifyd_state);
+ struct server_id_buf id1, id2;
+ NTSTATUS status;
+ uint64_t rec_index = UINT64_MAX;
+ uint8_t index_buf[sizeof(uint64_t)];
+ size_t dbsize;
+ uint8_t *buf;
+ struct iovec iov[2];
+
+ dbsize = dbwrap_marshall(state->entries, NULL, 0);
+
+ buf = talloc_array(talloc_tos(), uint8_t, dbsize);
+ if (buf == NULL) {
+ DBG_WARNING("talloc_array(%zu) failed\n", dbsize);
+ return;
+ }
+
+ dbsize = dbwrap_marshall(state->entries, buf, dbsize);
+
+ if (dbsize != talloc_get_size(buf)) {
+ DBG_DEBUG("dbsize changed: %zu->%zu\n",
+ talloc_get_size(buf),
+ dbsize);
+ TALLOC_FREE(buf);
+ return;
+ }
+
+ if (state->log != NULL) {
+ rec_index = state->log->rec_index;
+ }
+ SBVAL(index_buf, 0, rec_index);
+
+ iov[0] = (struct iovec) { .iov_base = index_buf,
+ .iov_len = sizeof(index_buf) };
+ iov[1] = (struct iovec) { .iov_base = buf,
+ .iov_len = dbsize };
+
+ DBG_DEBUG("Sending %zu bytes to %s->%s\n",
+ iov_buflen(iov, ARRAY_SIZE(iov)),
+ server_id_str_buf(messaging_server_id(msg_ctx), &id1),
+ server_id_str_buf(src, &id2));
+
+ status = messaging_send_iov(msg_ctx, src, MSG_SMB_NOTIFY_DB,
+ iov, ARRAY_SIZE(iov), NULL, 0);
+ TALLOC_FREE(buf);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("messaging_send_iov failed: %s\n",
+ nt_errstr(status));
+ }
+}
+
+#ifdef CLUSTER_SUPPORT
+
+static int notifyd_add_proxy_syswatches(struct db_record *rec,
+ void *private_data);
+
+static void notifyd_got_db(struct messaging_context *msg_ctx,
+ void *private_data, uint32_t msg_type,
+ struct server_id src, DATA_BLOB *data)
+{
+ struct notifyd_state *state = talloc_get_type_abort(
+ private_data, struct notifyd_state);
+ struct notifyd_peer *p = NULL;
+ struct server_id_buf idbuf;
+ NTSTATUS status;
+ int count;
+ size_t i;
+
+ for (i=0; i<state->num_peers; i++) {
+ if (server_id_equal(&src, &state->peers[i]->pid)) {
+ p = state->peers[i];
+ break;
+ }
+ }
+
+ if (p == NULL) {
+ DBG_DEBUG("Did not find peer for db from %s\n",
+ server_id_str_buf(src, &idbuf));
+ return;
+ }
+
+ if (data->length < 8) {
+ DBG_DEBUG("Got short db length %zu from %s\n", data->length,
+ server_id_str_buf(src, &idbuf));
+ TALLOC_FREE(p);
+ return;
+ }
+
+ p->rec_index = BVAL(data->data, 0);
+
+ p->db = db_open_rbt(p);
+ if (p->db == NULL) {
+ DBG_DEBUG("db_open_rbt failed\n");
+ TALLOC_FREE(p);
+ return;
+ }
+
+ status = dbwrap_unmarshall(p->db, data->data + 8,
+ data->length - 8);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dbwrap_unmarshall returned %s for db %s\n",
+ nt_errstr(status),
+ server_id_str_buf(src, &idbuf));
+ TALLOC_FREE(p);
+ return;
+ }
+
+ dbwrap_traverse_read(p->db, notifyd_add_proxy_syswatches, state,
+ &count);
+
+ DBG_DEBUG("Database from %s contained %d records\n",
+ server_id_str_buf(src, &idbuf),
+ count);
+}
+
+static void notifyd_broadcast_reclog(struct ctdbd_connection *ctdbd_conn,
+ struct server_id src,
+ struct messaging_reclog *log)
+{
+ enum ndr_err_code ndr_err;
+ uint8_t msghdr[MESSAGE_HDR_LENGTH];
+ DATA_BLOB blob;
+ struct iovec iov[2];
+ int ret;
+
+ if (log == NULL) {
+ return;
+ }
+
+ DBG_DEBUG("rec_index=%"PRIu64", num_recs=%"PRIu32"\n",
+ log->rec_index,
+ log->num_recs);
+
+ message_hdr_put(msghdr, MSG_SMB_NOTIFY_REC_CHANGES, src,
+ (struct server_id) {0 });
+ iov[0] = (struct iovec) { .iov_base = msghdr,
+ .iov_len = sizeof(msghdr) };
+
+ ndr_err = ndr_push_struct_blob(
+ &blob, log, log,
+ (ndr_push_flags_fn_t)ndr_push_messaging_reclog);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_WARNING("ndr_push_messaging_recs failed: %s\n",
+ ndr_errstr(ndr_err));
+ goto done;
+ }
+ iov[1] = (struct iovec) { .iov_base = blob.data,
+ .iov_len = blob.length };
+
+ ret = ctdbd_messaging_send_iov(
+ ctdbd_conn, CTDB_BROADCAST_CONNECTED,
+ CTDB_SRVID_SAMBA_NOTIFY_PROXY, iov, ARRAY_SIZE(iov));
+ TALLOC_FREE(blob.data);
+ if (ret != 0) {
+ DBG_WARNING("ctdbd_messaging_send failed: %s\n",
+ strerror(ret));
+ goto done;
+ }
+
+ log->rec_index += 1;
+
+done:
+ log->num_recs = 0;
+ TALLOC_FREE(log->recs);
+}
+
+struct notifyd_broadcast_reclog_state {
+ struct tevent_context *ev;
+ struct ctdbd_connection *ctdbd_conn;
+ struct server_id src;
+ struct messaging_reclog *log;
+};
+
+static void notifyd_broadcast_reclog_next(struct tevent_req *subreq);
+
+static struct tevent_req *notifyd_broadcast_reclog_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct ctdbd_connection *ctdbd_conn, struct server_id src,
+ struct messaging_reclog *log)
+{
+ struct tevent_req *req, *subreq;
+ struct notifyd_broadcast_reclog_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct notifyd_broadcast_reclog_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->ctdbd_conn = ctdbd_conn;
+ state->src = src;
+ state->log = log;
+
+ subreq = tevent_wakeup_send(state, state->ev,
+ timeval_current_ofs_msec(1000));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, notifyd_broadcast_reclog_next, req);
+ return req;
+}
+
+static void notifyd_broadcast_reclog_next(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct notifyd_broadcast_reclog_state *state = tevent_req_data(
+ req, struct notifyd_broadcast_reclog_state);
+ bool ok;
+
+ ok = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ tevent_req_oom(req);
+ return;
+ }
+
+ notifyd_broadcast_reclog(state->ctdbd_conn, state->src, state->log);
+
+ subreq = tevent_wakeup_send(state, state->ev,
+ timeval_current_ofs_msec(1000));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, notifyd_broadcast_reclog_next, req);
+}
+
+static int notifyd_broadcast_reclog_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_unix(req);
+}
+
+struct notifyd_clean_peers_state {
+ struct tevent_context *ev;
+ struct notifyd_state *notifyd;
+};
+
+static void notifyd_clean_peers_next(struct tevent_req *subreq);
+
+static struct tevent_req *notifyd_clean_peers_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct notifyd_state *notifyd)
+{
+ struct tevent_req *req, *subreq;
+ struct notifyd_clean_peers_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct notifyd_clean_peers_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->notifyd = notifyd;
+
+ subreq = tevent_wakeup_send(state, state->ev,
+ timeval_current_ofs_msec(30000));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, notifyd_clean_peers_next, req);
+ return req;
+}
+
+static void notifyd_clean_peers_next(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct notifyd_clean_peers_state *state = tevent_req_data(
+ req, struct notifyd_clean_peers_state);
+ struct notifyd_state *notifyd = state->notifyd;
+ size_t i;
+ bool ok;
+ time_t now = time(NULL);
+
+ ok = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ tevent_req_oom(req);
+ return;
+ }
+
+ i = 0;
+ while (i < notifyd->num_peers) {
+ struct notifyd_peer *p = notifyd->peers[i];
+
+ if ((now - p->last_broadcast) > 60) {
+ struct server_id_buf idbuf;
+
+ /*
+ * Haven't heard for more than 60 seconds. Call this
+ * peer dead
+ */
+
+ DBG_DEBUG("peer %s died\n",
+ server_id_str_buf(p->pid, &idbuf));
+ /*
+ * This implicitly decrements notifyd->num_peers
+ */
+ TALLOC_FREE(p);
+ } else {
+ i += 1;
+ }
+ }
+
+ subreq = tevent_wakeup_send(state, state->ev,
+ timeval_current_ofs_msec(30000));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, notifyd_clean_peers_next, req);
+}
+
+static int notifyd_clean_peers_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_unix(req);
+}
+
+static int notifyd_add_proxy_syswatches(struct db_record *rec,
+ void *private_data)
+{
+ struct notifyd_state *state = talloc_get_type_abort(
+ private_data, struct notifyd_state);
+ struct db_context *db = dbwrap_record_get_db(rec);
+ TDB_DATA key = dbwrap_record_get_key(rec);
+ TDB_DATA value = dbwrap_record_get_value(rec);
+ struct notifyd_instance *instances = NULL;
+ size_t num_instances = 0;
+ size_t i;
+ char path[key.dsize+1];
+ bool ok;
+
+ memcpy(path, key.dptr, key.dsize);
+ path[key.dsize] = '\0';
+
+ ok = notifyd_parse_entry(value.dptr, value.dsize, &instances,
+ &num_instances);
+ if (!ok) {
+ DBG_WARNING("Could not parse notifyd entry for %s\n", path);
+ return 0;
+ }
+
+ for (i=0; i<num_instances; i++) {
+ struct notifyd_instance *instance = &instances[i];
+ uint32_t filter = instance->instance.filter;
+ uint32_t subdir_filter = instance->instance.subdir_filter;
+ int ret;
+
+ /*
+ * This is a remote database. Pointers that we were
+ * given don't make sense locally. Initialize to NULL
+ * in case sys_notify_watch fails.
+ */
+ instances[i].sys_watch = NULL;
+
+ ret = state->sys_notify_watch(
+ db, state->sys_notify_ctx, path,
+ &filter, &subdir_filter,
+ notifyd_sys_callback, state->msg_ctx,
+ &instance->sys_watch);
+ if (ret != 0) {
+ DBG_WARNING("inotify_watch returned %s\n",
+ strerror(errno));
+ }
+ }
+
+ return 0;
+}
+
+static int notifyd_db_del_syswatches(struct db_record *rec, void *private_data)
+{
+ TDB_DATA key = dbwrap_record_get_key(rec);
+ TDB_DATA value = dbwrap_record_get_value(rec);
+ struct notifyd_instance *instances = NULL;
+ size_t num_instances = 0;
+ size_t i;
+ bool ok;
+
+ ok = notifyd_parse_entry(value.dptr, value.dsize, &instances,
+ &num_instances);
+ if (!ok) {
+ DBG_WARNING("Could not parse notifyd entry for %.*s\n",
+ (int)key.dsize, (char *)key.dptr);
+ return 0;
+ }
+ for (i=0; i<num_instances; i++) {
+ TALLOC_FREE(instances[i].sys_watch);
+ }
+ return 0;
+}
+
+static int notifyd_peer_destructor(struct notifyd_peer *p)
+{
+ struct notifyd_state *state = p->state;
+ size_t i;
+
+ if (p->db != NULL) {
+ dbwrap_traverse_read(p->db, notifyd_db_del_syswatches,
+ NULL, NULL);
+ }
+
+ for (i = 0; i<state->num_peers; i++) {
+ if (p == state->peers[i]) {
+ state->peers[i] = state->peers[state->num_peers-1];
+ state->num_peers -= 1;
+ break;
+ }
+ }
+ return 0;
+}
+
+static struct notifyd_peer *notifyd_peer_new(
+ struct notifyd_state *state, struct server_id pid)
+{
+ struct notifyd_peer *p, **tmp;
+
+ tmp = talloc_realloc(state, state->peers, struct notifyd_peer *,
+ state->num_peers+1);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ state->peers = tmp;
+
+ p = talloc_zero(state->peers, struct notifyd_peer);
+ if (p == NULL) {
+ return NULL;
+ }
+ p->state = state;
+ p->pid = pid;
+
+ state->peers[state->num_peers] = p;
+ state->num_peers += 1;
+
+ talloc_set_destructor(p, notifyd_peer_destructor);
+
+ return p;
+}
+
+static void notifyd_apply_reclog(struct notifyd_peer *peer,
+ const uint8_t *msg, size_t msglen)
+{
+ struct notifyd_state *state = peer->state;
+ DATA_BLOB blob = { .data = discard_const_p(uint8_t, msg),
+ .length = msglen };
+ struct server_id_buf idbuf;
+ struct messaging_reclog *log;
+ enum ndr_err_code ndr_err;
+ uint32_t i;
+
+ if (peer->db == NULL) {
+ /*
+ * No db yet
+ */
+ return;
+ }
+
+ log = talloc(peer, struct messaging_reclog);
+ if (log == NULL) {
+ DBG_DEBUG("talloc failed\n");
+ return;
+ }
+
+ ndr_err = ndr_pull_struct_blob_all(
+ &blob, log, log,
+ (ndr_pull_flags_fn_t)ndr_pull_messaging_reclog);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_DEBUG("ndr_pull_messaging_reclog failed: %s\n",
+ ndr_errstr(ndr_err));
+ goto fail;
+ }
+
+ DBG_DEBUG("Got %"PRIu32" recs index %"PRIu64" from %s\n",
+ log->num_recs,
+ log->rec_index,
+ server_id_str_buf(peer->pid, &idbuf));
+
+ if (log->rec_index != peer->rec_index) {
+ DBG_INFO("Got rec index %"PRIu64" from %s, "
+ "expected %"PRIu64"\n",
+ log->rec_index,
+ server_id_str_buf(peer->pid, &idbuf),
+ peer->rec_index);
+ goto fail;
+ }
+
+ for (i=0; i<log->num_recs; i++) {
+ struct messaging_rec *r = log->recs[i];
+ struct notify_rec_change_msg *chg;
+ size_t pathlen;
+ bool ok;
+ struct notify_instance instance;
+
+ ok = notifyd_parse_rec_change(r->buf.data, r->buf.length,
+ &chg, &pathlen);
+ if (!ok) {
+ DBG_INFO("notifyd_parse_rec_change failed\n");
+ goto fail;
+ }
+
+ /* avoid SIGBUS */
+ memcpy(&instance, &chg->instance, sizeof(instance));
+
+ ok = notifyd_apply_rec_change(&r->src, chg->path, pathlen,
+ &instance, peer->db,
+ state->sys_notify_watch,
+ state->sys_notify_ctx,
+ state->msg_ctx);
+ if (!ok) {
+ DBG_INFO("notifyd_apply_rec_change failed\n");
+ goto fail;
+ }
+ }
+
+ peer->rec_index += 1;
+ peer->last_broadcast = time(NULL);
+
+ TALLOC_FREE(log);
+ return;
+
+fail:
+ DBG_DEBUG("Dropping peer %s\n",
+ server_id_str_buf(peer->pid, &idbuf));
+ TALLOC_FREE(peer);
+}
+
+/*
+ * Receive messaging_reclog (log of MSG_SMB_NOTIFY_REC_CHANGE
+ * messages) broadcasts by other notifyds. Several cases:
+ *
+ * We don't know the source. This creates a new peer. Creating a peer
+ * involves asking the peer for its full database. We assume ordered
+ * messages, so the new database will arrive before the next broadcast
+ * will.
+ *
+ * We know the source and the log index matches. We will apply the log
+ * locally to our peer's db as if we had received it from a local
+ * client.
+ *
+ * We know the source but the log index does not match. This means we
+ * lost a message. We just drop the whole peer and wait for the next
+ * broadcast, which will then trigger a fresh database pull.
+ */
+
+static int notifyd_snoop_broadcast(struct tevent_context *ev,
+ uint32_t src_vnn, uint32_t dst_vnn,
+ uint64_t dst_srvid,
+ const uint8_t *msg, size_t msglen,
+ void *private_data)
+{
+ struct notifyd_state *state = talloc_get_type_abort(
+ private_data, struct notifyd_state);
+ struct server_id my_id = messaging_server_id(state->msg_ctx);
+ struct notifyd_peer *p;
+ uint32_t i;
+ uint32_t msg_type;
+ struct server_id src, dst;
+ struct server_id_buf idbuf;
+ NTSTATUS status;
+
+ if (msglen < MESSAGE_HDR_LENGTH) {
+ DBG_DEBUG("Got short broadcast\n");
+ return 0;
+ }
+ message_hdr_get(&msg_type, &src, &dst, msg);
+
+ if (msg_type != MSG_SMB_NOTIFY_REC_CHANGES) {
+ DBG_DEBUG("Got message %"PRIu32", ignoring\n", msg_type);
+ return 0;
+ }
+ if (server_id_equal(&src, &my_id)) {
+ DBG_DEBUG("Ignoring my own broadcast\n");
+ return 0;
+ }
+
+ DBG_DEBUG("Got MSG_SMB_NOTIFY_REC_CHANGES from %s\n",
+ server_id_str_buf(src, &idbuf));
+
+ for (i=0; i<state->num_peers; i++) {
+ if (server_id_equal(&state->peers[i]->pid, &src)) {
+
+ DBG_DEBUG("Applying changes to peer %"PRIu32"\n", i);
+
+ notifyd_apply_reclog(state->peers[i],
+ msg + MESSAGE_HDR_LENGTH,
+ msglen - MESSAGE_HDR_LENGTH);
+ return 0;
+ }
+ }
+
+ DBG_DEBUG("Creating new peer for %s\n",
+ server_id_str_buf(src, &idbuf));
+
+ p = notifyd_peer_new(state, src);
+ if (p == NULL) {
+ DBG_DEBUG("notifyd_peer_new failed\n");
+ return 0;
+ }
+
+ status = messaging_send_buf(state->msg_ctx, src, MSG_SMB_NOTIFY_GET_DB,
+ NULL, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("messaging_send_buf failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(p);
+ return 0;
+ }
+
+ return 0;
+}
+#endif
diff --git a/source3/smbd/notifyd/notifyd.h b/source3/smbd/notifyd/notifyd.h
new file mode 100644
index 0000000..5ef8c4c
--- /dev/null
+++ b/source3/smbd/notifyd/notifyd.h
@@ -0,0 +1,145 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Copyright (C) Volker Lendecke 2014
+ *
+ * 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/>.
+ */
+
+#ifndef __NOTIFYD_NOTIFYD_H__
+#define __NOTIFYD_NOTIFYD_H__
+
+#include "includes.h"
+#include "librpc/gen_ndr/notify.h"
+#include "librpc/gen_ndr/messaging.h"
+#include "lib/dbwrap/dbwrap.h"
+#include "lib/dbwrap/dbwrap_rbt.h"
+#include "messages.h"
+#include "tdb.h"
+#include "util_tdb.h"
+
+/*
+ * Filechangenotify based on asynchronous messages
+ *
+ * smbds talk to local notify daemons to inform them about paths they are
+ * interested in. They also tell local notify daemons about changes they have
+ * done to the file system. There's two message types from smbd to
+ * notifyd. The first is used to inform notifyd about changes in notify
+ * interest. These are only sent from smbd to notifyd if the SMB client issues
+ * FileChangeNotify requests.
+ */
+
+/*
+ * The notifyd implementation is designed to cope with multiple daemons taking
+ * care of just a subset of smbds. The goal is to minimize the traffic between
+ * the notify daemons. The idea behind this is a samba/ctdb cluster, but it
+ * could also be used to spread the load of notifyd instances on a single
+ * node, should this become a bottleneck. The following diagram illustrates
+ * the setup. The numbers in the boxes are node:process ids.
+ *
+ * +-----------+ +-----------+
+ * |notifyd 0:5|------------------|notifyd 1:6|
+ * +-----------+ +-----------+
+ * / | \ / \
+ * / | \ / \
+ * +--------+ | +--------+ +--------+ +--------+
+ * |smbd 0:1| | |smbd 0:4| |smbd 1:7| |smbd 1:2|
+ * +--------+ | +--------+ +--------+ +--------+
+ * |
+ * +---------+
+ * |smbd 0:20|
+ * +---------+
+ *
+ * Suppose 0:1 and 0:4 are interested in changes for /foo and 0:20 creates the
+ * file /foo/bar, if everything fully connected, 0:20 would have to send two
+ * local messages, one to 0:1 and one to 0:4. With the notifyd design, 0:20
+ * only has to send one message, it lets notifyd 0:5 do the hard work to
+ * multicast the change to 0:1 and 0:4.
+ *
+ * Now lets assume 1:7 on the other node creates /foo/baz. It tells its
+ * notifyd 1:6 about this change. All 1:6 will know about is that its peer
+ * notifyd 0:5 is interested in the change. Thus it forwards the event to 0:5,
+ * which sees it as if it came from just another local event creator. 0:5 will
+ * multicast the change to 0:1 and 0:4. To prevent notify loops, the message
+ * from 1:6 to 0:5 will carry a "proxied" flag, so that 0:5 will only forward
+ * the event to local clients.
+ */
+
+/*
+ * Data that notifyd maintains per smbd notify instance
+ */
+struct notify_instance {
+ struct timespec creation_time;
+ uint32_t filter;
+ uint32_t subdir_filter;
+ void *private_data;
+};
+
+/* MSG_SMB_NOTIFY_REC_CHANGE payload */
+struct notify_rec_change_msg {
+ struct notify_instance instance;
+ char path[];
+};
+
+/*
+ * The second message from smbd to notifyd is sent whenever an smbd makes a
+ * file system change. It tells notifyd to inform all interested parties about
+ * that change. This is the message that needs to be really fast in smbd
+ * because it is called a lot.
+ */
+
+/* MSG_SMB_NOTIFY_TRIGGER payload */
+struct notify_trigger_msg {
+ struct timespec when;
+ uint32_t action;
+ uint32_t filter;
+ char path[];
+};
+
+/*
+ * In response to a MSG_SMB_NOTIFY_TRIGGER message notifyd walks its database
+ * and sends out the following message to all interested clients
+ */
+
+/* MSG_PVFS_NOTIFY payload */
+struct notify_event_msg {
+ struct timespec when;
+ void *private_data;
+ uint32_t action;
+ char path[];
+};
+
+struct sys_notify_context;
+struct ctdbd_connection;
+
+typedef int (*sys_notify_watch_fn)(TALLOC_CTX *mem_ctx,
+ struct sys_notify_context *ctx,
+ const char *path,
+ uint32_t *filter,
+ uint32_t *subdir_filter,
+ void (*callback)(struct sys_notify_context *ctx,
+ void *private_data,
+ struct notify_event *ev,
+ uint32_t filter),
+ void *private_data,
+ void *handle_p);
+
+struct tevent_req *notifyd_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ struct ctdbd_connection *ctdbd_conn,
+ sys_notify_watch_fn sys_notify_watch,
+ struct sys_notify_context *sys_notify_ctx);
+int notifyd_recv(struct tevent_req *req);
+
+#endif
diff --git a/source3/smbd/notifyd/notifyd_db.c b/source3/smbd/notifyd/notifyd_db.c
new file mode 100644
index 0000000..1822861
--- /dev/null
+++ b/source3/smbd/notifyd/notifyd_db.c
@@ -0,0 +1,165 @@
+/*
+ * 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 "replace.h"
+#include "lib/util/debug.h"
+#include "lib/util/server_id_db.h"
+#include "notifyd_private.h"
+#include "notifyd_db.h"
+
+struct notifyd_parse_db_state {
+ bool (*fn)(const char *path,
+ struct server_id server,
+ const struct notify_instance *instance,
+ void *private_data);
+ void *private_data;
+};
+
+static bool notifyd_parse_db_parser(TDB_DATA key, TDB_DATA value,
+ void *private_data)
+{
+ struct notifyd_parse_db_state *state = private_data;
+ char path[key.dsize+1];
+ struct notifyd_instance *instances = NULL;
+ size_t num_instances = 0;
+ size_t i;
+ bool ok;
+
+ memcpy(path, key.dptr, key.dsize);
+ path[key.dsize] = 0;
+
+ ok = notifyd_parse_entry(value.dptr, value.dsize, &instances,
+ &num_instances);
+ if (!ok) {
+ DBG_DEBUG("Could not parse entry for path %s\n", path);
+ return true;
+ }
+
+ for (i=0; i<num_instances; i++) {
+ ok = state->fn(path, instances[i].client,
+ &instances[i].instance,
+ state->private_data);
+ if (!ok) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static NTSTATUS notifyd_parse_db(
+ const uint8_t *buf,
+ size_t buflen,
+ uint64_t *log_index,
+ bool (*fn)(const char *path,
+ struct server_id server,
+ const struct notify_instance *instance,
+ void *private_data),
+ void *private_data)
+{
+ struct notifyd_parse_db_state state = {
+ .fn = fn, .private_data = private_data
+ };
+ NTSTATUS status;
+
+ if (buflen < 8) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+ *log_index = BVAL(buf, 0);
+
+ buf += 8;
+ buflen -= 8;
+
+ status = dbwrap_parse_marshall_buf(
+ buf, buflen, notifyd_parse_db_parser, &state);
+ return status;
+}
+
+NTSTATUS notify_walk(struct messaging_context *msg_ctx,
+ bool (*fn)(const char *path, struct server_id server,
+ const struct notify_instance *instance,
+ void *private_data),
+ void *private_data)
+{
+ struct server_id_db *names_db = NULL;
+ struct server_id notifyd = { .pid = 0, };
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ struct messaging_rec *rec = NULL;
+ uint64_t log_idx;
+ NTSTATUS status;
+ int ret;
+ bool ok;
+
+ names_db = messaging_names_db(msg_ctx);
+ ok = server_id_db_lookup_one(names_db, "notify-daemon", &notifyd);
+ if (!ok) {
+ DBG_WARNING("No notify daemon around\n");
+ return NT_STATUS_SERVER_UNAVAILABLE;
+ }
+
+ ev = samba_tevent_context_init(msg_ctx);
+ if (ev == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ req = messaging_read_send(ev, ev, msg_ctx, MSG_SMB_NOTIFY_DB);
+ if (req == NULL) {
+ TALLOC_FREE(ev);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ok = tevent_req_set_endtime(req, ev, timeval_current_ofs(10, 0));
+ if (!ok) {
+ TALLOC_FREE(ev);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = messaging_send_buf(
+ msg_ctx, notifyd, MSG_SMB_NOTIFY_GET_DB, NULL, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("messaging_send_buf failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(ev);
+ return status;
+ }
+
+ ok = tevent_req_poll(req, ev);
+ if (!ok) {
+ DBG_DEBUG("tevent_req_poll failed\n");
+ TALLOC_FREE(ev);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ ret = messaging_read_recv(req, ev, &rec);
+ if (ret != 0) {
+ DBG_DEBUG("messaging_read_recv failed: %s\n",
+ strerror(ret));
+ TALLOC_FREE(ev);
+ return map_nt_error_from_unix(ret);
+ }
+
+ status = notifyd_parse_db(
+ rec->buf.data, rec->buf.length, &log_idx, fn, private_data);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("notifyd_parse_db failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(ev);
+ return status;
+ }
+
+ TALLOC_FREE(ev);
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/notifyd/notifyd_db.h b/source3/smbd/notifyd/notifyd_db.h
new file mode 100644
index 0000000..bd9e226
--- /dev/null
+++ b/source3/smbd/notifyd/notifyd_db.h
@@ -0,0 +1,27 @@
+/*
+ * 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/>.
+ */
+
+#ifndef __NOTIFYD_NOTIFYD_DB_H__
+#define __NOTIFYD_NOTIFYD_DB_H__
+#include "replace.h"
+#include "notifyd.h"
+
+NTSTATUS notify_walk(struct messaging_context *msg_ctx,
+ bool (*fn)(const char *path, struct server_id server,
+ const struct notify_instance *instance,
+ void *private_data),
+ void *private_data);
+
+#endif
diff --git a/source3/smbd/notifyd/notifyd_entry.c b/source3/smbd/notifyd/notifyd_entry.c
new file mode 100644
index 0000000..539010d
--- /dev/null
+++ b/source3/smbd/notifyd/notifyd_entry.c
@@ -0,0 +1,42 @@
+/*
+ * 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 "replace.h"
+#include "lib/util/debug.h"
+#include "notifyd_private.h"
+
+/*
+ * Parse an entry in the notifyd_context->entries database
+ */
+
+bool notifyd_parse_entry(
+ uint8_t *buf,
+ size_t buflen,
+ struct notifyd_instance **instances,
+ size_t *num_instances)
+{
+ if ((buflen % sizeof(struct notifyd_instance)) != 0) {
+ DBG_WARNING("invalid buffer size: %zu\n", buflen);
+ return false;
+ }
+
+ if (instances != NULL) {
+ *instances = (struct notifyd_instance *)buf;
+ }
+ if (num_instances != NULL) {
+ *num_instances = buflen / sizeof(struct notifyd_instance);
+ }
+ return true;
+}
diff --git a/source3/smbd/notifyd/notifyd_private.h b/source3/smbd/notifyd/notifyd_private.h
new file mode 100644
index 0000000..36c08f4
--- /dev/null
+++ b/source3/smbd/notifyd/notifyd_private.h
@@ -0,0 +1,49 @@
+/*
+ * 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/>.
+ */
+
+#ifndef __NOTIFYD_PRIVATE_H__
+#define __NOTIFYD_PRIVATE_H__
+
+#include "replace.h"
+#include "lib/util/server_id.h"
+#include "notifyd.h"
+
+/*
+ * notifyd's representation of a notify instance
+ */
+struct notifyd_instance {
+ struct server_id client;
+ struct notify_instance instance;
+
+ void *sys_watch; /* inotify/fam/etc handle */
+
+ /*
+ * Filters after sys_watch took responsibility of some bits
+ */
+ uint32_t internal_filter;
+ uint32_t internal_subdir_filter;
+};
+
+/*
+ * Parse an entry in the notifyd_context->entries database
+ */
+
+bool notifyd_parse_entry(
+ uint8_t *buf,
+ size_t buflen,
+ struct notifyd_instance **instances,
+ size_t *num_instances);
+
+#endif
diff --git a/source3/smbd/notifyd/notifydd.c b/source3/smbd/notifyd/notifydd.c
new file mode 100644
index 0000000..b27a4ab
--- /dev/null
+++ b/source3/smbd/notifyd/notifydd.c
@@ -0,0 +1,98 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Copyright (C) Volker Lendecke 2014
+ *
+ * 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 "replace.h"
+#include "notifyd.h"
+#include "lib/messages_ctdb.h"
+#include <tevent.h>
+#include "lib/util/tevent_unix.h"
+#include "lib/param/param.h"
+
+int main(int argc, const char *argv[])
+{
+ TALLOC_CTX *frame;
+ struct loadparm_context *lp_ctx = NULL;
+ struct tevent_context *ev;
+ struct messaging_context *msg;
+ struct tevent_req *req;
+ int err, ret;
+ bool ok;
+
+ talloc_enable_leak_report_full();
+
+ frame = talloc_stackframe();
+
+ lp_ctx = loadparm_init_s3(frame, loadparm_s3_helpers());
+ if (lp_ctx == NULL) {
+ fprintf(stderr,
+ "Failed to initialise the global parameter structure.\n");
+ return 1;
+ }
+
+ setup_logging("notifyd", DEBUG_DEFAULT_STDOUT);
+ lpcfg_set_cmdline(lp_ctx, "log level", "10");
+
+ ok = lp_load_initial_only(get_dyn_CONFIGFILE());
+ if (!ok) {
+ fprintf(stderr, "Can't load %s - run testparm to debug it\n",
+ get_dyn_CONFIGFILE());
+ return 1;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ fprintf(stderr, "samba_tevent_context_init failed\n");
+ return 1;
+ }
+
+ msg = messaging_init(ev, ev);
+ if (msg == NULL) {
+ fprintf(stderr, "messaging_init failed\n");
+ return 1;
+ }
+
+ if (!lp_load_global(get_dyn_CONFIGFILE())) {
+ fprintf(stderr, "Can't load %s - run testparm to debug it\n",
+ get_dyn_CONFIGFILE());
+ return 1;
+ }
+
+ req = notifyd_send(ev, ev, msg, messaging_ctdb_connection(),
+ NULL, NULL);
+ if (req == NULL) {
+ fprintf(stderr, "notifyd_send failed\n");
+ return 1;
+ }
+
+ ok = tevent_req_poll_unix(req, ev, &err);
+ if (!ok) {
+ fprintf(stderr, "tevent_req_poll_unix failed: %s\n",
+ strerror(err));
+ return 1;
+ }
+
+ ret = notifyd_recv(req);
+
+ printf("notifyd_recv returned %d (%s)\n", ret,
+ ret ? strerror(ret) : "ok");
+
+ TALLOC_FREE(frame);
+
+ return 0;
+}
diff --git a/source3/smbd/notifyd/test_notifyd.c b/source3/smbd/notifyd/test_notifyd.c
new file mode 100644
index 0000000..431da9b
--- /dev/null
+++ b/source3/smbd/notifyd/test_notifyd.c
@@ -0,0 +1,347 @@
+/*
+ * 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 "replace.h"
+#include "fcn_wait.h"
+#include "notifyd.h"
+#include "notifyd_db.h"
+#include "messages.h"
+#include "lib/util/server_id.h"
+#include "lib/util/server_id_db.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "lib/torture/torture.h"
+#include "torture/local/proto.h"
+#include "lib/param/loadparm.h"
+#include "source3/param/loadparm.h"
+#include "source4/torture/smbtorture.h"
+
+struct fcn_test_state {
+ struct tevent_req *fcn_req;
+ bool got_trigger;
+};
+
+static void fcn_test_done(struct tevent_req *subreq);
+
+static struct tevent_req *fcn_test_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ struct server_id notifyd,
+ const char *fcn_path,
+ uint32_t fcn_filter,
+ uint32_t fcn_subdir_filter,
+ const char *trigger_path,
+ uint32_t trigger_action,
+ uint32_t trigger_filter)
+{
+ struct tevent_req *req = NULL;
+ struct fcn_test_state *state = NULL;
+ struct notify_trigger_msg msg;
+ struct iovec iov[2];
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state, struct fcn_test_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->fcn_req = fcn_wait_send(
+ state,
+ ev,
+ msg_ctx,
+ notifyd,
+ fcn_path,
+ fcn_filter,
+ fcn_subdir_filter);
+ if (tevent_req_nomem(state->fcn_req, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(state->fcn_req, fcn_test_done, req);
+
+ msg = (struct notify_trigger_msg) {
+ .when = timespec_current(),
+ .action = trigger_action,
+ .filter = trigger_filter,
+ };
+ iov[0] = (struct iovec) {
+ .iov_base = &msg,
+ .iov_len = offsetof(struct notify_trigger_msg, path),
+ };
+ iov[1] = (struct iovec) {
+ .iov_base = discard_const_p(char, trigger_path),
+ .iov_len = strlen(trigger_path)+1,
+ };
+
+ status = messaging_send_iov(
+ msg_ctx,
+ notifyd,
+ MSG_SMB_NOTIFY_TRIGGER,
+ iov,
+ ARRAY_SIZE(iov),
+ NULL,
+ 0);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void fcn_test_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fcn_test_state *state = tevent_req_data(
+ req, struct fcn_test_state);
+ NTSTATUS status;
+ bool ok;
+
+ SMB_ASSERT(subreq == state->fcn_req);
+
+ status = fcn_wait_recv(subreq, NULL, NULL, NULL, NULL);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_CANCELLED)) {
+ TALLOC_FREE(subreq);
+ state->fcn_req = NULL;
+ tevent_req_done(req);
+ return;
+ }
+
+ if (tevent_req_nterror(req, status)) {
+ TALLOC_FREE(subreq);
+ state->fcn_req = NULL;
+ return;
+ }
+
+ state->got_trigger = true;
+
+ ok = tevent_req_cancel(subreq);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+}
+
+static NTSTATUS fcn_test_recv(struct tevent_req *req, bool *got_trigger)
+{
+ struct fcn_test_state *state = tevent_req_data(
+ req, struct fcn_test_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ if (got_trigger != NULL) {
+ *got_trigger = state->got_trigger;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fcn_test(
+ struct messaging_context *msg_ctx,
+ struct server_id notifyd,
+ const char *fcn_path,
+ uint32_t fcn_filter,
+ uint32_t fcn_subdir_filter,
+ const char *trigger_path,
+ uint32_t trigger_action,
+ uint32_t trigger_filter,
+ bool *got_trigger)
+{
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(msg_ctx);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = fcn_test_send(
+ ev,
+ ev,
+ msg_ctx,
+ notifyd,
+ fcn_path,
+ fcn_filter,
+ fcn_subdir_filter,
+ trigger_path,
+ trigger_action,
+ trigger_filter);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = fcn_test_recv(req, got_trigger);
+fail:
+ TALLOC_FREE(ev);
+ return status;
+}
+
+static bool test_notifyd_trigger1(struct torture_context *tctx)
+{
+ struct messaging_context *msg_ctx = NULL;
+ struct server_id_db *names = NULL;
+ struct server_id notifyd;
+ NTSTATUS status;
+ bool got_trigger = false;
+ bool ok;
+
+ /*
+ * Basic filechangenotify test: Wait for /home, trigger on
+ * /home/foo, check an event was received
+ */
+
+ lp_load_global(tctx->lp_ctx->szConfigFile);
+
+ msg_ctx = messaging_init(tctx, tctx->ev);
+ torture_assert_not_null(tctx, msg_ctx, "messaging_init");
+
+ names = messaging_names_db(msg_ctx);
+ ok = server_id_db_lookup_one(names, "notify-daemon", &notifyd);
+ torture_assert(tctx, ok, "server_id_db_lookup_one");
+
+ status = fcn_test(
+ msg_ctx,
+ notifyd,
+ "/home",
+ UINT32_MAX,
+ UINT32_MAX,
+ "/home/foo",
+ UINT32_MAX,
+ UINT32_MAX,
+ &got_trigger);
+ torture_assert_ntstatus_ok(tctx, status, "fcn_test");
+ torture_assert(tctx, got_trigger, "got_trigger");
+
+ return true;
+}
+
+struct notifyd_have_state {
+ struct server_id self;
+ bool found;
+};
+
+static bool notifyd_have_fn(
+ const char *path,
+ struct server_id server,
+ const struct notify_instance *instance,
+ void *private_data)
+{
+ struct notifyd_have_state *state = private_data;
+ state->found |= server_id_equal(&server, &state->self);
+ return true;
+}
+
+static bool notifyd_have_self(struct messaging_context *msg_ctx)
+{
+ struct notifyd_have_state state = {
+ .self = messaging_server_id(msg_ctx),
+ };
+ NTSTATUS status;
+
+ status = notify_walk(msg_ctx, notifyd_have_fn, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+ return state.found;
+}
+
+static bool test_notifyd_dbtest1(struct torture_context *tctx)
+{
+ struct tevent_context *ev = tctx->ev;
+ struct messaging_context *msg_ctx = NULL;
+ struct tevent_req *req = NULL;
+ struct server_id_db *names = NULL;
+ struct server_id notifyd;
+ NTSTATUS status;
+ bool ok;
+
+ /*
+ * Make sure fcn_wait_send adds us to the notifyd internal
+ * database and that cancelling the fcn request removes us
+ * again.
+ */
+
+ lp_load_global(tctx->lp_ctx->szConfigFile);
+
+ msg_ctx = messaging_init(tctx, ev);
+ torture_assert_not_null(tctx, msg_ctx, "messaging_init");
+
+ names = messaging_names_db(msg_ctx);
+ ok = server_id_db_lookup_one(names, "notify-daemon", &notifyd);
+ torture_assert(tctx, ok, "server_id_db_lookup_one");
+
+ req = fcn_wait_send(
+ msg_ctx, ev, msg_ctx, notifyd, "/x", UINT32_MAX, UINT32_MAX);
+ torture_assert_not_null(tctx, req, "fcn_wait_send");
+
+ ok = notifyd_have_self(msg_ctx);
+ torture_assert(tctx, ok, "notifyd_have_self");
+
+ ok = tevent_req_cancel(req);
+ torture_assert(tctx, ok, "tevent_req_cancel");
+
+ ok = tevent_req_poll(req, ev);
+ torture_assert(tctx, ok, "tevent_req_poll");
+
+ status = fcn_wait_recv(req, NULL, NULL, NULL, NULL);
+ torture_assert_ntstatus_equal(
+ tctx, status, NT_STATUS_CANCELLED, "fcn_wait_recv");
+ TALLOC_FREE(req);
+
+ ok = notifyd_have_self(msg_ctx);
+ torture_assert(tctx, !ok, "tevent_req_poll");
+ TALLOC_FREE(msg_ctx);
+
+ return true;
+}
+
+NTSTATUS torture_notifyd_init(TALLOC_CTX *mem_ctx);
+NTSTATUS torture_notifyd_init(TALLOC_CTX *mem_ctx)
+{
+ struct torture_suite *suite = NULL;
+ struct torture_tcase *tcase = NULL;
+ bool ok;
+
+ suite = torture_suite_create(mem_ctx, "notifyd");
+ if (suite == NULL) {
+ goto fail;
+ }
+
+ tcase = torture_suite_add_simple_test(
+ suite, "trigger1", test_notifyd_trigger1);
+ if (tcase == NULL) {
+ goto fail;
+ }
+
+ tcase = torture_suite_add_simple_test(
+ suite, "dbtest1", test_notifyd_dbtest1);
+ if (tcase == NULL) {
+ goto fail;
+ }
+ suite->description = "notifyd unit tests";
+
+ ok = torture_register_suite(mem_ctx, suite);
+ if (!ok) {
+ goto fail;
+ }
+ return NT_STATUS_OK;
+fail:
+ TALLOC_FREE(suite);
+ return NT_STATUS_NO_MEMORY;
+}
diff --git a/source3/smbd/notifyd/tests.c b/source3/smbd/notifyd/tests.c
new file mode 100644
index 0000000..6bcce6a
--- /dev/null
+++ b/source3/smbd/notifyd/tests.c
@@ -0,0 +1,118 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Copyright (C) Volker Lendecke 2014
+ *
+ * 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 "replace.h"
+#include "notifyd.h"
+#include "messages.h"
+#include "lib/util/server_id_db.h"
+
+int main(int argc, const char *argv[])
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct messaging_context *msg_ctx;
+ struct server_id_db *names;
+ struct server_id notifyd;
+ struct tevent_req *req;
+ unsigned i;
+ bool ok;
+
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s <smb.conf-file>\n", argv[0]);
+ exit(1);
+ }
+
+ setup_logging(argv[0], DEBUG_STDOUT);
+ lp_load_global(argv[1]);
+
+ ev = tevent_context_init(NULL);
+ if (ev == NULL) {
+ fprintf(stderr, "tevent_context_init failed\n");
+ exit(1);
+ }
+
+ msg_ctx = messaging_init(ev, ev);
+ if (msg_ctx == NULL) {
+ fprintf(stderr, "messaging_init failed\n");
+ exit(1);
+ }
+
+ names = messaging_names_db(msg_ctx);
+
+ ok = server_id_db_lookup_one(names, "notify-daemon", &notifyd);
+ if (!ok) {
+ fprintf(stderr, "no notifyd\n");
+ exit(1);
+ }
+
+ for (i=0; i<50000; i++) {
+ struct notify_rec_change_msg msg = {
+ .instance.filter = UINT32_MAX,
+ .instance.subdir_filter = UINT32_MAX
+ };
+ char path[64];
+ size_t len;
+ struct iovec iov[2];
+ NTSTATUS status;
+
+ len = snprintf(path, sizeof(path), "/tmp%u", i);
+
+ iov[0].iov_base = &msg;
+ iov[0].iov_len = offsetof(struct notify_rec_change_msg, path);
+ iov[1].iov_base = path;
+ iov[1].iov_len = len+1;
+
+ status = messaging_send_iov(
+ msg_ctx, notifyd, MSG_SMB_NOTIFY_REC_CHANGE,
+ iov, ARRAY_SIZE(iov), NULL, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "messaging_send_iov returned %s\n",
+ nt_errstr(status));
+ exit(1);
+ }
+
+ msg.instance.filter = 0;
+ msg.instance.subdir_filter = 0;
+
+ status = messaging_send_iov(
+ msg_ctx, notifyd, MSG_SMB_NOTIFY_REC_CHANGE,
+ iov, ARRAY_SIZE(iov), NULL, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "messaging_send_iov returned %s\n",
+ nt_errstr(status));
+ exit(1);
+ }
+ }
+
+ req = messaging_read_send(ev, ev, msg_ctx, MSG_PONG);
+ if (req == NULL) {
+ fprintf(stderr, "messaging_read_send failed\n");
+ exit(1);
+ }
+ messaging_send_buf(msg_ctx, notifyd, MSG_PING, NULL, 0);
+
+ ok = tevent_req_poll(req, ev);
+ if (!ok) {
+ fprintf(stderr, "tevent_req_poll failed\n");
+ exit(1);
+ }
+
+ TALLOC_FREE(frame);
+ return 0;
+}
diff --git a/source3/smbd/notifyd/wscript_build b/source3/smbd/notifyd/wscript_build
new file mode 100644
index 0000000..6880a31
--- /dev/null
+++ b/source3/smbd/notifyd/wscript_build
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+bld.SAMBA3_SUBSYSTEM('fcn_wait',
+ source='fcn_wait.c',
+ deps='samba3core')
+
+bld.SAMBA3_SUBSYSTEM('notifyd_db',
+ source='notifyd_entry.c notifyd_db.c',
+ deps='samba-debug dbwrap errors3')
+
+bld.SAMBA3_SUBSYSTEM('notifyd',
+ source='notifyd.c',
+ deps='''
+ util_tdb
+ TDB_LIB
+ messages_util
+ notifyd_db
+ ''')
+
+bld.SAMBA3_BINARY('notifyd-tests',
+ source='tests.c',
+ install=False,
+ deps='''
+ smbconf
+ ''')
+
+bld.SAMBA3_BINARY('notifydd',
+ source='notifydd.c',
+ install=False,
+ deps='''notifyd
+ smbconf
+ ''')
+
+TORTURE_NOTIFYD_SOURCE='test_notifyd.c'
+TORTURE_NOTIFYD_DEPS='fcn_wait notifyd_db'
+
+bld.SAMBA_MODULE('TORTURE_NOTIFYD',
+ source=TORTURE_NOTIFYD_SOURCE,
+ subsystem='smbtorture',
+ init_function='torture_notifyd_init',
+ deps=TORTURE_NOTIFYD_DEPS,
+ internal_module=True,
+ enabled=bld.PYTHON_BUILD_IS_ENABLED()
+ )
diff --git a/source3/smbd/ntquotas.c b/source3/smbd/ntquotas.c
new file mode 100644
index 0000000..5705a4f
--- /dev/null
+++ b/source3/smbd/ntquotas.c
@@ -0,0 +1,265 @@
+/*
+ Unix SMB/CIFS implementation.
+ NT QUOTA support
+ Copyright (C) Stefan (metze) Metzmacher 2003
+
+ 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 "smbd/smbd.h"
+#include "../lib/util/util_pw.h"
+#include "system/passwd.h"
+#include "passdb/lookup_sid.h"
+#include "libsmb/libsmb.h"
+#include "libcli/security/dom_sid.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_QUOTA
+
+static uint64_t limit_nt2unix(uint64_t in, uint64_t bsize)
+{
+ uint64_t ret = (uint64_t)0;
+
+ ret = (uint64_t)(in/bsize);
+ if (in>0 && ret==0) {
+ /* we have to make sure that a overflow didn't set NO_LIMIT */
+ ret = (uint64_t)1;
+ }
+
+ if (in == SMB_NTQUOTAS_NO_LIMIT)
+ ret = SMB_QUOTAS_NO_LIMIT;
+ else if (in == SMB_NTQUOTAS_NO_SPACE)
+ ret = SMB_QUOTAS_NO_SPACE;
+ else if (in == SMB_NTQUOTAS_NO_ENTRY)
+ ret = SMB_QUOTAS_NO_LIMIT;
+
+ return ret;
+}
+
+static uint64_t limit_unix2nt(uint64_t in, uint64_t bsize)
+{
+ uint64_t ret = (uint64_t)0;
+
+ ret = (uint64_t)(in*bsize);
+
+ return ret;
+}
+
+NTSTATUS vfs_get_ntquota(files_struct *fsp, enum SMB_QUOTA_TYPE qtype,
+ struct dom_sid *psid, SMB_NTQUOTA_STRUCT *qt)
+{
+ int ret;
+ SMB_DISK_QUOTA D;
+ unid_t id;
+ struct smb_filename *smb_fname_cwd = NULL;
+ int saved_errno = 0;
+
+ ZERO_STRUCT(D);
+
+ if (!fsp || !fsp->conn || !qt) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ ZERO_STRUCT(*qt);
+
+ id.uid = -1;
+
+ if (psid && !sid_to_uid(psid, &id.uid)) {
+ struct dom_sid_buf buf;
+ DEBUG(0,("sid_to_uid: failed, SID[%s]\n",
+ dom_sid_str_buf(psid, &buf)));
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ smb_fname_cwd = synthetic_smb_fname(talloc_tos(),
+ ".",
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname_cwd == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = SMB_VFS_GET_QUOTA(fsp->conn, smb_fname_cwd, qtype, id, &D);
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+ TALLOC_FREE(smb_fname_cwd);
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+
+ if (psid)
+ qt->sid = *psid;
+
+ if (ret!=0) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ qt->usedspace = (uint64_t)D.curblocks*D.bsize;
+ qt->softlim = limit_unix2nt(D.softlimit, D.bsize);
+ qt->hardlim = limit_unix2nt(D.hardlimit, D.bsize);
+ qt->qflags = D.qflags;
+
+ return NT_STATUS_OK;
+}
+
+int vfs_set_ntquota(files_struct *fsp, enum SMB_QUOTA_TYPE qtype, struct dom_sid *psid, SMB_NTQUOTA_STRUCT *qt)
+{
+ int ret;
+ SMB_DISK_QUOTA D;
+ unid_t id;
+ ZERO_STRUCT(D);
+
+ if (!fsp||!fsp->conn||!qt)
+ return (-1);
+
+ id.uid = -1;
+
+ D.bsize = (uint64_t)QUOTABLOCK_SIZE;
+
+ D.softlimit = limit_nt2unix(qt->softlim,D.bsize);
+ D.hardlimit = limit_nt2unix(qt->hardlim,D.bsize);
+ D.qflags = qt->qflags;
+
+ if (psid && !sid_to_uid(psid, &id.uid)) {
+ struct dom_sid_buf buf;
+ DEBUG(0,("sid_to_uid: failed, SID[%s]\n",
+ dom_sid_str_buf(psid, &buf)));
+ }
+
+ ret = SMB_VFS_SET_QUOTA(fsp->conn, qtype, id, &D);
+
+ return ret;
+}
+
+static bool already_in_quota_list(SMB_NTQUOTA_LIST *qt_list, uid_t uid)
+{
+ SMB_NTQUOTA_LIST *tmp_list = NULL;
+
+ if (!qt_list)
+ return False;
+
+ for (tmp_list=qt_list;tmp_list!=NULL;tmp_list=tmp_list->next) {
+ if (tmp_list->uid == uid) {
+ return True;
+ }
+ }
+
+ return False;
+}
+
+int vfs_get_user_ntquota_list(files_struct *fsp, SMB_NTQUOTA_LIST **qt_list)
+{
+ struct passwd *usr;
+ TALLOC_CTX *mem_ctx = NULL;
+
+ if (!fsp||!fsp->conn||!qt_list)
+ return (-1);
+
+ *qt_list = NULL;
+
+ if ((mem_ctx=talloc_init("SMB_USER_QUOTA_LIST"))==NULL) {
+ DEBUG(0,("talloc_init() failed\n"));
+ return (-1);
+ }
+
+ setpwent();
+ while ((usr = getpwent()) != NULL) {
+ SMB_NTQUOTA_STRUCT tmp_qt;
+ SMB_NTQUOTA_LIST *tmp_list_ent;
+ struct dom_sid sid;
+ struct dom_sid_buf buf;
+ NTSTATUS status;
+
+ ZERO_STRUCT(tmp_qt);
+
+ if (already_in_quota_list((*qt_list),usr->pw_uid)) {
+ DEBUG(5,("record for uid[%ld] already in the list\n",(long)usr->pw_uid));
+ continue;
+ }
+
+ uid_to_sid(&sid, usr->pw_uid);
+
+ status =
+ vfs_get_ntquota(fsp, SMB_USER_QUOTA_TYPE, &sid, &tmp_qt);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("failed getting quota for uid[%ld] - %s\n",
+ (long)usr->pw_uid, nt_errstr(status)));
+ continue;
+ }
+ if (tmp_qt.softlim == 0 && tmp_qt.hardlim == 0) {
+ DEBUG(5,("no quota entry for sid[%s] path[%s]\n",
+ dom_sid_str_buf(&sid, &buf),
+ fsp->conn->connectpath));
+ continue;
+ }
+
+ DEBUG(15,("quota entry for id[%s] path[%s]\n",
+ dom_sid_str_buf(&sid, &buf),
+ fsp->conn->connectpath));
+
+ if ((tmp_list_ent=talloc_zero(mem_ctx,SMB_NTQUOTA_LIST))==NULL) {
+ DEBUG(0,("TALLOC_ZERO() failed\n"));
+ *qt_list = NULL;
+ talloc_destroy(mem_ctx);
+ return (-1);
+ }
+
+ if ((tmp_list_ent->quotas=talloc_zero(mem_ctx,SMB_NTQUOTA_STRUCT))==NULL) {
+ DEBUG(0,("TALLOC_ZERO() failed\n"));
+ *qt_list = NULL;
+ talloc_destroy(mem_ctx);
+ return (-1);
+ }
+
+ tmp_list_ent->uid = usr->pw_uid;
+ memcpy(tmp_list_ent->quotas,&tmp_qt,sizeof(tmp_qt));
+ tmp_list_ent->mem_ctx = mem_ctx;
+
+ DLIST_ADD((*qt_list),tmp_list_ent);
+
+ }
+ endpwent();
+
+ if (*qt_list == NULL) {
+ TALLOC_FREE(mem_ctx);
+ }
+ return 0;
+}
+
+static int quota_handle_destructor(SMB_NTQUOTA_HANDLE *handle)
+{
+ free_ntquota_list(&handle->quota_list);
+ return 0;
+}
+
+void *init_quota_handle(TALLOC_CTX *mem_ctx)
+{
+ SMB_NTQUOTA_HANDLE *qt_handle;
+
+ if (!mem_ctx)
+ return NULL;
+
+ qt_handle = talloc_zero(mem_ctx,SMB_NTQUOTA_HANDLE);
+ if (qt_handle==NULL) {
+ DEBUG(0,("TALLOC_ZERO() failed\n"));
+ return NULL;
+ }
+
+ talloc_set_destructor(qt_handle, quota_handle_destructor);
+ return (void *)qt_handle;
+}
diff --git a/source3/smbd/open.c b/source3/smbd/open.c
new file mode 100644
index 0000000..95034b1
--- /dev/null
+++ b/source3/smbd/open.c
@@ -0,0 +1,6676 @@
+/*
+ Unix SMB/CIFS implementation.
+ file opening and share modes
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 2001-2004
+ Copyright (C) Volker Lendecke 2005
+ Copyright (C) Ralph Boehme 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 "system/filesys.h"
+#include "lib/util/server_id.h"
+#include "printing.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "fake_file.h"
+#include "../libcli/security/security.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "../librpc/gen_ndr/ndr_open_files.h"
+#include "../librpc/gen_ndr/idmap.h"
+#include "../librpc/gen_ndr/ioctl.h"
+#include "passdb/lookup_sid.h"
+#include "auth.h"
+#include "serverid.h"
+#include "messages.h"
+#include "source3/lib/dbwrap/dbwrap_watch.h"
+#include "locking/leases_db.h"
+#include "librpc/gen_ndr/ndr_leases_db.h"
+#include "lib/util/time_basic.h"
+#include "source3/smbd/dir.h"
+
+extern const struct generic_mapping file_generic_mapping;
+
+struct deferred_open_record {
+ struct smbXsrv_connection *xconn;
+ uint64_t mid;
+
+ bool async_open;
+
+ /*
+ * Timer for async opens, needed because they don't use a watch on
+ * a locking.tdb record. This is currently only used for real async
+ * opens and just terminates smbd if the async open times out.
+ */
+ struct tevent_timer *te;
+
+ /*
+ * For the samba kernel oplock case we use both a timeout and
+ * a watch on locking.tdb. This way in case it's smbd holding
+ * the kernel oplock we get directly notified for the retry
+ * once the kernel oplock is properly broken. Store the req
+ * here so that it can be timely discarded once the timer
+ * above fires.
+ */
+ struct tevent_req *watch_req;
+};
+
+/****************************************************************************
+ If the requester wanted DELETE_ACCESS and was rejected because
+ the file ACL didn't include DELETE_ACCESS, see if the parent ACL
+ overrides this.
+****************************************************************************/
+
+static bool parent_override_delete(connection_struct *conn,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ uint32_t access_mask,
+ uint32_t rejected_mask)
+{
+ if ((access_mask & DELETE_ACCESS) &&
+ (rejected_mask & DELETE_ACCESS) &&
+ can_delete_file_in_directory(conn,
+ dirfsp,
+ smb_fname))
+ {
+ return true;
+ }
+ return false;
+}
+
+/****************************************************************************
+ Check if we have open rights.
+****************************************************************************/
+
+static NTSTATUS smbd_check_access_rights_fname(
+ struct connection_struct *conn,
+ const struct smb_filename *smb_fname,
+ bool use_privs,
+ uint32_t access_mask,
+ uint32_t do_not_check_mask)
+{
+ uint32_t rejected_share_access;
+ uint32_t effective_access;
+
+ rejected_share_access = access_mask & ~(conn->share_access);
+
+ if (rejected_share_access) {
+ DBG_DEBUG("rejected share access 0x%"PRIx32" on "
+ "%s (0x%"PRIx32")\n",
+ access_mask,
+ smb_fname_str_dbg(smb_fname),
+ rejected_share_access);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ effective_access = access_mask & ~do_not_check_mask;
+ if (effective_access == 0) {
+ DBG_DEBUG("do_not_check_mask override on %s. Granting 0x%x for free.\n",
+ smb_fname_str_dbg(smb_fname),
+ (unsigned int)access_mask);
+ return NT_STATUS_OK;
+ }
+
+ if (!use_privs && get_current_uid(conn) == (uid_t)0) {
+ /* I'm sorry sir, I didn't know you were root... */
+ DBG_DEBUG("root override on %s. Granting 0x%x\n",
+ smb_fname_str_dbg(smb_fname),
+ (unsigned int)access_mask);
+ return NT_STATUS_OK;
+ }
+
+ if ((access_mask & DELETE_ACCESS) &&
+ !lp_acl_check_permissions(SNUM(conn)))
+ {
+ DBG_DEBUG("Not checking ACL on DELETE_ACCESS on file %s. "
+ "Granting 0x%"PRIx32"\n",
+ smb_fname_str_dbg(smb_fname),
+ access_mask);
+ return NT_STATUS_OK;
+ }
+
+ if (access_mask == DELETE_ACCESS &&
+ VALID_STAT(smb_fname->st) &&
+ S_ISLNK(smb_fname->st.st_ex_mode))
+ {
+ /* We can always delete a symlink. */
+ DBG_DEBUG("Not checking ACL on DELETE_ACCESS on symlink %s.\n",
+ smb_fname_str_dbg(smb_fname));
+ return NT_STATUS_OK;
+ }
+
+ return NT_STATUS_MORE_PROCESSING_REQUIRED;
+}
+
+static NTSTATUS smbd_check_access_rights_sd(
+ struct connection_struct *conn,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ struct security_descriptor *sd,
+ bool use_privs,
+ uint32_t access_mask,
+ uint32_t do_not_check_mask)
+{
+ uint32_t rejected_mask = access_mask;
+ NTSTATUS status;
+
+ if (sd == NULL) {
+ goto access_denied;
+ }
+
+ status = se_file_access_check(sd,
+ get_current_nttok(conn),
+ use_privs,
+ (access_mask & ~do_not_check_mask),
+ &rejected_mask);
+
+ DBG_DEBUG("File [%s] requesting [0x%"PRIx32"] "
+ "returning [0x%"PRIx32"] (%s)\n",
+ smb_fname_str_dbg(smb_fname),
+ access_mask,
+ rejected_mask,
+ nt_errstr(status));
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (DEBUGLEVEL >= 10) {
+ DBG_DEBUG("acl for %s is:\n",
+ smb_fname_str_dbg(smb_fname));
+ NDR_PRINT_DEBUG(security_descriptor, sd);
+ }
+ }
+
+ TALLOC_FREE(sd);
+
+ if (NT_STATUS_IS_OK(status) ||
+ !NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED))
+ {
+ return status;
+ }
+
+ /* Here we know status == NT_STATUS_ACCESS_DENIED. */
+
+access_denied:
+
+ if ((access_mask & FILE_WRITE_ATTRIBUTES) &&
+ (rejected_mask & FILE_WRITE_ATTRIBUTES) &&
+ !lp_store_dos_attributes(SNUM(conn)) &&
+ (lp_map_readonly(SNUM(conn)) ||
+ lp_map_archive(SNUM(conn)) ||
+ lp_map_hidden(SNUM(conn)) ||
+ lp_map_system(SNUM(conn))))
+ {
+ rejected_mask &= ~FILE_WRITE_ATTRIBUTES;
+
+ DBG_DEBUG("overrode FILE_WRITE_ATTRIBUTES on file %s\n",
+ smb_fname_str_dbg(smb_fname));
+ }
+
+ if (parent_override_delete(conn,
+ dirfsp,
+ smb_fname,
+ access_mask,
+ rejected_mask))
+ {
+ /*
+ * Were we trying to do an open for delete and didn't get DELETE
+ * access. Check if the directory allows DELETE_CHILD.
+ * See here:
+ * http://blogs.msdn.com/oldnewthing/archive/2004/06/04/148426.aspx
+ * for details.
+ */
+
+ rejected_mask &= ~DELETE_ACCESS;
+
+ DBG_DEBUG("Overrode DELETE_ACCESS on file %s\n",
+ smb_fname_str_dbg(smb_fname));
+ }
+
+ if (rejected_mask != 0) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smbd_check_access_rights_fsp(struct files_struct *dirfsp,
+ struct files_struct *fsp,
+ bool use_privs,
+ uint32_t access_mask)
+{
+ struct security_descriptor *sd = NULL;
+ uint32_t do_not_check_mask = 0;
+ NTSTATUS status;
+
+ /* Cope with fake/printer fsp's. */
+ if (fsp->fake_file_handle != NULL || fsp->print_file != NULL) {
+ if ((fsp->access_mask & access_mask) != access_mask) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ return NT_STATUS_OK;
+ }
+
+ if (fsp_get_pathref_fd(fsp) == -1) {
+ /*
+ * This is a POSIX open on a symlink. For the pathname
+ * version of this function we used to return the st_mode
+ * bits turned into an NT ACL. For a symlink the mode bits
+ * are always rwxrwxrwx which means the pathname version always
+ * returned NT_STATUS_OK for a symlink. For the handle reference
+ * to a symlink use the handle access bits.
+ */
+ if ((fsp->access_mask & access_mask) != access_mask) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * If we can access the path to this file, by
+ * default we have FILE_READ_ATTRIBUTES from the
+ * containing directory. See the section:
+ * "Algorithm to Check Access to an Existing File"
+ * in MS-FSA.pdf.
+ *
+ * se_file_access_check() also takes care of
+ * owner WRITE_DAC and READ_CONTROL.
+ */
+ do_not_check_mask = FILE_READ_ATTRIBUTES;
+
+ /*
+ * Samba 3.6 and earlier granted execute access even
+ * if the ACL did not contain execute rights.
+ * Samba 4.0 is more correct and checks it.
+ * The compatibility mode allows one to skip this check
+ * to smoothen upgrades.
+ */
+ if (lp_acl_allow_execute_always(SNUM(fsp->conn))) {
+ do_not_check_mask |= FILE_EXECUTE;
+ }
+
+ status = smbd_check_access_rights_fname(fsp->conn,
+ fsp->fsp_name,
+ use_privs,
+ access_mask,
+ do_not_check_mask);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ return status;
+ }
+
+ status = SMB_VFS_FGET_NT_ACL(metadata_fsp(fsp),
+ (SECINFO_OWNER |
+ SECINFO_GROUP |
+ SECINFO_DACL),
+ talloc_tos(),
+ &sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Could not get acl on %s: %s\n",
+ fsp_str_dbg(fsp),
+ nt_errstr(status));
+ return status;
+ }
+
+ return smbd_check_access_rights_sd(fsp->conn,
+ dirfsp,
+ fsp->fsp_name,
+ sd,
+ use_privs,
+ access_mask,
+ do_not_check_mask);
+}
+
+/*
+ * Given an fsp that represents a parent directory,
+ * check if the requested access can be granted.
+ */
+NTSTATUS check_parent_access_fsp(struct files_struct *fsp,
+ uint32_t access_mask)
+{
+ NTSTATUS status;
+ struct security_descriptor *parent_sd = NULL;
+ uint32_t access_granted = 0;
+ struct share_mode_lock *lck = NULL;
+ uint32_t name_hash;
+ bool delete_on_close_set;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (get_current_uid(fsp->conn) == (uid_t)0) {
+ /* I'm sorry sir, I didn't know you were root... */
+ DBG_DEBUG("root override on %s. Granting 0x%x\n",
+ fsp_str_dbg(fsp),
+ (unsigned int)access_mask);
+ status = NT_STATUS_OK;
+ goto out;
+ }
+
+ status = SMB_VFS_FGET_NT_ACL(fsp,
+ SECINFO_DACL,
+ frame,
+ &parent_sd);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_INFO("SMB_VFS_FGET_NT_ACL failed for "
+ "%s with error %s\n",
+ fsp_str_dbg(fsp),
+ nt_errstr(status));
+ goto out;
+ }
+
+ /*
+ * If we can access the path to this file, by
+ * default we have FILE_READ_ATTRIBUTES from the
+ * containing directory. See the section:
+ * "Algorithm to Check Access to an Existing File"
+ * in MS-FSA.pdf.
+ *
+ * se_file_access_check() also takes care of
+ * owner WRITE_DAC and READ_CONTROL.
+ */
+ status = se_file_access_check(parent_sd,
+ get_current_nttok(fsp->conn),
+ false,
+ (access_mask & ~FILE_READ_ATTRIBUTES),
+ &access_granted);
+ if(!NT_STATUS_IS_OK(status)) {
+ DBG_INFO("access check "
+ "on directory %s for mask 0x%x returned (0x%x) %s\n",
+ fsp_str_dbg(fsp),
+ access_mask,
+ access_granted,
+ nt_errstr(status));
+ goto out;
+ }
+
+ if (!(access_mask & (SEC_DIR_ADD_FILE | SEC_DIR_ADD_SUBDIR))) {
+ status = NT_STATUS_OK;
+ goto out;
+ }
+ if (!lp_check_parent_directory_delete_on_close(SNUM(fsp->conn))) {
+ status = NT_STATUS_OK;
+ goto out;
+ }
+
+ /* Check if the directory has delete-on-close set */
+ status = file_name_hash(fsp->conn,
+ fsp->fsp_name->base_name,
+ &name_hash);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ /*
+ * Don't take a lock here. We just need a snapshot
+ * of the current state of delete on close and this is
+ * called in a codepath where we may already have a lock
+ * (and we explicitly can't hold 2 locks at the same time
+ * as that may deadlock).
+ */
+ lck = fetch_share_mode_unlocked(frame, fsp->file_id);
+ if (lck == NULL) {
+ status = NT_STATUS_OK;
+ goto out;
+ }
+
+ delete_on_close_set = is_delete_on_close_set(lck, name_hash);
+ if (delete_on_close_set) {
+ status = NT_STATUS_DELETE_PENDING;
+ goto out;
+ }
+
+ status = NT_STATUS_OK;
+
+out:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Ensure when opening a base file for a stream open that we have permissions
+ to do so given the access mask on the base file.
+****************************************************************************/
+
+static NTSTATUS check_base_file_access(struct files_struct *fsp,
+ uint32_t access_mask)
+{
+ NTSTATUS status;
+
+ status = smbd_calculate_access_mask_fsp(fsp->conn->cwd_fsp,
+ fsp,
+ false,
+ access_mask,
+ &access_mask);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("smbd_calculate_access_mask "
+ "on file %s returned %s\n",
+ fsp_str_dbg(fsp),
+ nt_errstr(status)));
+ return status;
+ }
+
+ if (access_mask & (FILE_WRITE_DATA|FILE_APPEND_DATA)) {
+ uint32_t dosattrs;
+ if (!CAN_WRITE(fsp->conn)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ dosattrs = fdos_mode(fsp);
+ if (dosattrs & FILE_ATTRIBUTE_READONLY) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+
+ return smbd_check_access_rights_fsp(fsp->conn->cwd_fsp,
+ fsp,
+ false,
+ access_mask);
+}
+
+static NTSTATUS chdir_below_conn(
+ TALLOC_CTX *mem_ctx,
+ connection_struct *conn,
+ const char *connectpath,
+ size_t connectpath_len,
+ struct smb_filename *dir_fname,
+ struct smb_filename **_oldwd_fname)
+{
+ struct smb_filename *oldwd_fname = NULL;
+ struct smb_filename *smb_fname_dot = NULL;
+ struct smb_filename *real_fname = NULL;
+ const char *relative = NULL;
+ NTSTATUS status;
+ int ret;
+ bool ok;
+
+ if (!ISDOT(dir_fname->base_name)) {
+
+ oldwd_fname = vfs_GetWd(talloc_tos(), conn);
+ if (oldwd_fname == NULL) {
+ status = map_nt_error_from_unix(errno);
+ goto out;
+ }
+
+ /* Pin parent directory in place. */
+ ret = vfs_ChDir(conn, dir_fname);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_DEBUG("chdir to %s failed: %s\n",
+ dir_fname->base_name,
+ strerror(errno));
+ goto out;
+ }
+ }
+
+ smb_fname_dot = synthetic_smb_fname(
+ talloc_tos(),
+ ".",
+ NULL,
+ NULL,
+ dir_fname->twrp,
+ dir_fname->flags);
+ if (smb_fname_dot == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ real_fname = SMB_VFS_REALPATH(conn, talloc_tos(), smb_fname_dot);
+ if (real_fname == NULL) {
+ status = map_nt_error_from_unix(errno);
+ DBG_DEBUG("realpath in %s failed: %s\n",
+ dir_fname->base_name,
+ strerror(errno));
+ goto out;
+ }
+ TALLOC_FREE(smb_fname_dot);
+
+ ok = subdir_of(connectpath,
+ connectpath_len,
+ real_fname->base_name,
+ &relative);
+ if (ok) {
+ TALLOC_FREE(real_fname);
+ *_oldwd_fname = oldwd_fname;
+ return NT_STATUS_OK;
+ }
+
+ DBG_NOTICE("Bad access attempt: %s is a symlink "
+ "outside the share path\n"
+ "conn_rootdir =%s\n"
+ "resolved_name=%s\n",
+ dir_fname->base_name,
+ connectpath,
+ real_fname->base_name);
+ TALLOC_FREE(real_fname);
+
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+
+out:
+ if (oldwd_fname != NULL) {
+ ret = vfs_ChDir(conn, oldwd_fname);
+ SMB_ASSERT(ret == 0);
+ TALLOC_FREE(oldwd_fname);
+ }
+
+ return status;
+}
+
+/*
+ * Get the symlink target of dirfsp/symlink_name, making sure the
+ * target is below connection_path.
+ */
+
+static NTSTATUS symlink_target_below_conn(
+ TALLOC_CTX *mem_ctx,
+ const char *connection_path,
+ struct files_struct *fsp,
+ struct files_struct *dirfsp,
+ struct smb_filename *symlink_name,
+ char **_target)
+{
+ char *target = NULL;
+ char *absolute = NULL;
+ NTSTATUS status;
+
+ if (fsp_get_pathref_fd(fsp) != -1) {
+ /*
+ * fsp is an O_PATH open, Linux does a "freadlink"
+ * with an empty name argument to readlinkat
+ */
+ status = readlink_talloc(talloc_tos(), fsp, NULL, &target);
+ } else {
+ status = readlink_talloc(
+ talloc_tos(), dirfsp, symlink_name, &target);
+ }
+
+ status = safe_symlink_target_path(talloc_tos(),
+ connection_path,
+ dirfsp->fsp_name->base_name,
+ target,
+ 0,
+ &absolute);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("safe_symlink_target_path() failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ if (absolute[0] == '\0') {
+ /*
+ * special case symlink to share root: "." is our
+ * share root filename
+ */
+ TALLOC_FREE(absolute);
+ absolute = talloc_strdup(talloc_tos(), ".");
+ if (absolute == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ *_target = absolute;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Non-widelink open.
+****************************************************************************/
+
+static NTSTATUS non_widelink_open(const struct files_struct *dirfsp,
+ files_struct *fsp,
+ struct smb_filename *smb_fname,
+ const struct vfs_open_how *_how)
+{
+ struct connection_struct *conn = fsp->conn;
+ const char *connpath = SMB_VFS_CONNECTPATH(conn, dirfsp, smb_fname);
+ size_t connpath_len;
+ NTSTATUS status = NT_STATUS_OK;
+ int fd = -1;
+ char *orig_smb_fname_base = smb_fname->base_name;
+ struct smb_filename *orig_fsp_name = fsp->fsp_name;
+ struct smb_filename *smb_fname_rel = NULL;
+ struct smb_filename *oldwd_fname = NULL;
+ struct smb_filename *parent_dir_fname = NULL;
+ struct vfs_open_how how = *_how;
+ char *target = NULL;
+ size_t link_depth = 0;
+ int ret;
+
+ SMB_ASSERT(!fsp_is_alternate_stream(fsp));
+
+ if (connpath == NULL) {
+ /*
+ * This can happen with shadow_copy2 if the snapshot
+ * path is not found
+ */
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ connpath_len = strlen(connpath);
+
+again:
+ if (smb_fname->base_name[0] == '/') {
+ int cmp = strcmp(connpath, smb_fname->base_name);
+ if (cmp == 0) {
+ smb_fname->base_name = talloc_strdup(smb_fname, "");
+ if (smb_fname->base_name == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+ }
+
+ if (dirfsp == conn->cwd_fsp) {
+
+ status = SMB_VFS_PARENT_PATHNAME(fsp->conn,
+ talloc_tos(),
+ smb_fname,
+ &parent_dir_fname,
+ &smb_fname_rel);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = chdir_below_conn(
+ talloc_tos(),
+ conn,
+ connpath,
+ connpath_len,
+ parent_dir_fname,
+ &oldwd_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ /* Setup fsp->fsp_name to be relative to cwd */
+ fsp->fsp_name = smb_fname_rel;
+ } else {
+ /*
+ * fsp->fsp_name is unchanged as it is already correctly
+ * relative to conn->cwd.
+ */
+ smb_fname_rel = smb_fname;
+ }
+
+ {
+ /*
+ * Assert nobody can step in with a symlink on the
+ * path, there is no path anymore and we'll use
+ * O_NOFOLLOW to open.
+ */
+ char *slash = strchr_m(smb_fname_rel->base_name, '/');
+ SMB_ASSERT(slash == NULL);
+ }
+
+ how.flags |= O_NOFOLLOW;
+
+ fd = SMB_VFS_OPENAT(conn,
+ dirfsp,
+ smb_fname_rel,
+ fsp,
+ &how);
+ fsp_set_fd(fsp, fd); /* This preserves errno */
+
+ if (fd == -1) {
+ status = map_nt_error_from_unix(errno);
+
+ if (errno == ENOENT) {
+ goto out;
+ }
+
+ /*
+ * ENOENT makes it worthless retrying with a
+ * stat, we know for sure the file does not
+ * exist. For everything else we want to know
+ * what's there.
+ */
+ ret = SMB_VFS_FSTATAT(
+ fsp->conn,
+ dirfsp,
+ smb_fname_rel,
+ &fsp->fsp_name->st,
+ AT_SYMLINK_NOFOLLOW);
+
+ if (ret == -1) {
+ /*
+ * Keep the original error. Otherwise we would
+ * mask for example EROFS for open(O_CREAT),
+ * turning it into ENOENT.
+ */
+ goto out;
+ }
+ } else {
+ ret = SMB_VFS_FSTAT(fsp, &fsp->fsp_name->st);
+ }
+
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_DEBUG("fstat[at](%s) failed: %s\n",
+ smb_fname_str_dbg(smb_fname),
+ strerror(errno));
+ goto out;
+ }
+
+ fsp->fsp_flags.is_directory = S_ISDIR(fsp->fsp_name->st.st_ex_mode);
+ orig_fsp_name->st = fsp->fsp_name->st;
+
+ if (!S_ISLNK(fsp->fsp_name->st.st_ex_mode)) {
+ goto out;
+ }
+
+ /*
+ * Found a symlink to follow in user space
+ */
+
+ if (fsp->fsp_name->flags & SMB_FILENAME_POSIX_PATH) {
+ /* Never follow symlinks on posix open. */
+ status = NT_STATUS_STOPPED_ON_SYMLINK;
+ goto out;
+ }
+ if (!lp_follow_symlinks(SNUM(conn))) {
+ /* Explicitly no symlinks. */
+ status = NT_STATUS_STOPPED_ON_SYMLINK;
+ goto out;
+ }
+
+ link_depth += 1;
+ if (link_depth >= 40) {
+ status = NT_STATUS_STOPPED_ON_SYMLINK;
+ goto out;
+ }
+
+ fsp->fsp_name = orig_fsp_name;
+
+ status = symlink_target_below_conn(
+ talloc_tos(),
+ connpath,
+ fsp,
+ discard_const_p(files_struct, dirfsp),
+ smb_fname_rel,
+ &target);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("symlink_target_below_conn() failed: %s\n",
+ nt_errstr(status));
+ goto out;
+ }
+
+ /*
+ * Close what openat(O_PATH) potentially left behind
+ */
+ fd_close(fsp);
+
+ if (smb_fname->base_name != orig_smb_fname_base) {
+ TALLOC_FREE(smb_fname->base_name);
+ }
+ smb_fname->base_name = target;
+
+ if (oldwd_fname != NULL) {
+ ret = vfs_ChDir(conn, oldwd_fname);
+ if (ret == -1) {
+ smb_panic("unable to get back to old directory\n");
+ }
+ TALLOC_FREE(oldwd_fname);
+ }
+
+ /*
+ * And do it all again... As smb_fname is not relative to the passed in
+ * dirfsp anymore, we pass conn->cwd_fsp as dirfsp to
+ * non_widelink_open() to trigger the chdir(parentdir) logic.
+ */
+ dirfsp = conn->cwd_fsp;
+
+ goto again;
+
+ out:
+ fsp->fsp_name = orig_fsp_name;
+ smb_fname->base_name = orig_smb_fname_base;
+
+ TALLOC_FREE(parent_dir_fname);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ fd_close(fsp);
+ }
+
+ if (oldwd_fname != NULL) {
+ ret = vfs_ChDir(conn, oldwd_fname);
+ if (ret == -1) {
+ smb_panic("unable to get back to old directory\n");
+ }
+ TALLOC_FREE(oldwd_fname);
+ }
+ return status;
+}
+
+/****************************************************************************
+ fd support routines - attempt to do a dos_open.
+****************************************************************************/
+
+NTSTATUS fd_openat(const struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ files_struct *fsp,
+ const struct vfs_open_how *_how)
+{
+ struct vfs_open_how how = *_how;
+ struct connection_struct *conn = fsp->conn;
+ NTSTATUS status = NT_STATUS_OK;
+ bool fsp_is_stream = fsp_is_alternate_stream(fsp);
+ bool smb_fname_is_stream = is_named_stream(smb_fname);
+
+ SMB_ASSERT(fsp_is_stream == smb_fname_is_stream);
+
+ /*
+ * Never follow symlinks on a POSIX client. The
+ * client should be doing this.
+ */
+
+ if ((fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) || !lp_follow_symlinks(SNUM(conn))) {
+ how.flags |= O_NOFOLLOW;
+ }
+
+ if (fsp_is_stream) {
+ int fd;
+
+ fd = SMB_VFS_OPENAT(
+ conn,
+ NULL, /* stream open is relative to fsp->base_fsp */
+ smb_fname,
+ fsp,
+ &how);
+ if (fd == -1) {
+ status = map_nt_error_from_unix(errno);
+ }
+ fsp_set_fd(fsp, fd);
+
+ if (fd != -1) {
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("vfs_stat_fsp failed: %s\n",
+ nt_errstr(status));
+ fd_close(fsp);
+ }
+ }
+
+ return status;
+ }
+
+ /*
+ * Only follow symlinks within a share
+ * definition.
+ */
+ status = non_widelink_open(dirfsp, fsp, smb_fname, &how);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_TOO_MANY_OPENED_FILES)) {
+ static time_t last_warned = 0L;
+
+ if (time((time_t *) NULL) > last_warned) {
+ DEBUG(0,("Too many open files, unable "
+ "to open more! smbd's max "
+ "open files = %d\n",
+ lp_max_open_files()));
+ last_warned = time((time_t *) NULL);
+ }
+ }
+
+ DBG_DEBUG("name %s, flags = 0%o mode = 0%o, fd = %d. %s\n",
+ smb_fname_str_dbg(smb_fname),
+ how.flags,
+ (int)how.mode,
+ fsp_get_pathref_fd(fsp),
+ nt_errstr(status));
+ return status;
+ }
+
+ DBG_DEBUG("name %s, flags = 0%o mode = 0%o, fd = %d\n",
+ smb_fname_str_dbg(smb_fname),
+ how.flags,
+ (int)how.mode,
+ fsp_get_pathref_fd(fsp));
+
+ return status;
+}
+
+/****************************************************************************
+ Close the file associated with a fsp.
+****************************************************************************/
+
+NTSTATUS fd_close(files_struct *fsp)
+{
+ NTSTATUS stat_status = NT_STATUS_OK;
+ int ret;
+
+ if (fsp == fsp->conn->cwd_fsp) {
+ return NT_STATUS_OK;
+ }
+
+ if (fsp->fsp_flags.fstat_before_close) {
+ /*
+ * capture status, if failure
+ * continue close processing
+ * and return status
+ */
+ stat_status = vfs_stat_fsp(fsp);
+ }
+
+ if (fsp->dptr) {
+ dptr_CloseDir(fsp);
+ }
+ if (fsp_get_pathref_fd(fsp) == -1) {
+ /*
+ * Either a directory where the dptr_CloseDir() already closed
+ * the fd or a stat open.
+ */
+ return NT_STATUS_OK;
+ }
+ if (fh_get_refcount(fsp->fh) > 1) {
+ return NT_STATUS_OK; /* Shared handle. Only close last reference. */
+ }
+
+ ret = SMB_VFS_CLOSE(fsp);
+ fsp_set_fd(fsp, -1);
+ if (ret == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+ return stat_status;
+}
+
+/****************************************************************************
+ Change the ownership of a file to that of the parent directory.
+ Do this by fd if possible.
+****************************************************************************/
+
+static void change_file_owner_to_parent_fsp(struct files_struct *parent_fsp,
+ struct files_struct *fsp)
+{
+ int ret;
+
+ if (parent_fsp->fsp_name->st.st_ex_uid == fsp->fsp_name->st.st_ex_uid) {
+ /* Already this uid - no need to change. */
+ DBG_DEBUG("file %s is already owned by uid %u\n",
+ fsp_str_dbg(fsp),
+ (unsigned int)fsp->fsp_name->st.st_ex_uid);
+ return;
+ }
+
+ become_root();
+ ret = SMB_VFS_FCHOWN(fsp,
+ parent_fsp->fsp_name->st.st_ex_uid,
+ (gid_t)-1);
+ unbecome_root();
+ if (ret == -1) {
+ DBG_ERR("failed to fchown "
+ "file %s to parent directory uid %u. Error "
+ "was %s\n",
+ fsp_str_dbg(fsp),
+ (unsigned int)parent_fsp->fsp_name->st.st_ex_uid,
+ strerror(errno));
+ } else {
+ DBG_DEBUG("changed new file %s to "
+ "parent directory uid %u.\n",
+ fsp_str_dbg(fsp),
+ (unsigned int)parent_fsp->fsp_name->st.st_ex_uid);
+ /* Ensure the uid entry is updated. */
+ fsp->fsp_name->st.st_ex_uid =
+ parent_fsp->fsp_name->st.st_ex_uid;
+ }
+}
+
+static NTSTATUS change_dir_owner_to_parent_fsp(struct files_struct *parent_fsp,
+ struct files_struct *fsp)
+{
+ NTSTATUS status;
+ int ret;
+
+ if (parent_fsp->fsp_name->st.st_ex_uid == fsp->fsp_name->st.st_ex_uid) {
+ /* Already this uid - no need to change. */
+ DBG_DEBUG("directory %s is already owned by uid %u\n",
+ fsp_str_dbg(fsp),
+ (unsigned int)fsp->fsp_name->st.st_ex_uid);
+ return NT_STATUS_OK;
+ }
+
+ become_root();
+ ret = SMB_VFS_FCHOWN(fsp,
+ parent_fsp->fsp_name->st.st_ex_uid,
+ (gid_t)-1);
+ unbecome_root();
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_ERR("failed to chown "
+ "directory %s to parent directory uid %u. "
+ "Error was %s\n",
+ fsp_str_dbg(fsp),
+ (unsigned int)parent_fsp->fsp_name->st.st_ex_uid,
+ nt_errstr(status));
+ return status;
+ }
+
+ DBG_DEBUG("changed ownership of new "
+ "directory %s to parent directory uid %u.\n",
+ fsp_str_dbg(fsp),
+ (unsigned int)parent_fsp->fsp_name->st.st_ex_uid);
+
+ /* Ensure the uid entry is updated. */
+ fsp->fsp_name->st.st_ex_uid = parent_fsp->fsp_name->st.st_ex_uid;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Open a file - returning a guaranteed ATOMIC indication of if the
+ file was created or not.
+****************************************************************************/
+
+static NTSTATUS fd_open_atomic(struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ files_struct *fsp,
+ const struct vfs_open_how *_how,
+ bool *file_created)
+{
+ struct vfs_open_how how = *_how;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ NTSTATUS retry_status;
+ bool file_existed = VALID_STAT(smb_fname->st);
+
+ if (!(how.flags & O_CREAT)) {
+ /*
+ * We're not creating the file, just pass through.
+ */
+ status = fd_openat(dirfsp, smb_fname, fsp, &how);
+ *file_created = false;
+ return status;
+ }
+
+ if (how.flags & O_EXCL) {
+ /*
+ * Fail if already exists, just pass through.
+ */
+ status = fd_openat(dirfsp, smb_fname, fsp, &how);
+
+ /*
+ * Here we've opened with O_CREAT|O_EXCL. If that went
+ * NT_STATUS_OK, we *know* we created this file.
+ */
+ *file_created = NT_STATUS_IS_OK(status);
+
+ return status;
+ }
+
+ /*
+ * Now it gets tricky. We have O_CREAT, but not O_EXCL.
+ * To know absolutely if we created the file or not,
+ * we can never call O_CREAT without O_EXCL. So if
+ * we think the file existed, try without O_CREAT|O_EXCL.
+ * If we think the file didn't exist, try with
+ * O_CREAT|O_EXCL.
+ *
+ * The big problem here is dangling symlinks. Opening
+ * without O_NOFOLLOW means both bad symlink
+ * and missing path return -1, ENOENT from open(). As POSIX
+ * is pathname based it's not possible to tell
+ * the difference between these two cases in a
+ * non-racy way, so change to try only two attempts before
+ * giving up.
+ *
+ * We don't have this problem for the O_NOFOLLOW
+ * case as it just returns NT_STATUS_OBJECT_PATH_NOT_FOUND
+ * mapped from the ELOOP POSIX error.
+ */
+
+ if (file_existed) {
+ how.flags = _how->flags & ~(O_CREAT);
+ retry_status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ } else {
+ how.flags = _how->flags | O_EXCL;
+ retry_status = NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+
+ status = fd_openat(dirfsp, smb_fname, fsp, &how);
+ if (NT_STATUS_IS_OK(status)) {
+ *file_created = !file_existed;
+ return NT_STATUS_OK;
+ }
+ if (NT_STATUS_EQUAL(status, retry_status)) {
+
+ file_existed = !file_existed;
+
+ DBG_DEBUG("File %s %s. Retry.\n",
+ fsp_str_dbg(fsp),
+ file_existed ? "existed" : "did not exist");
+
+ if (file_existed) {
+ how.flags = _how->flags & ~(O_CREAT);
+ } else {
+ how.flags = _how->flags | O_EXCL;
+ }
+
+ status = fd_openat(dirfsp, smb_fname, fsp, &how);
+ }
+
+ *file_created = (NT_STATUS_IS_OK(status) && !file_existed);
+ return status;
+}
+
+static NTSTATUS reopen_from_fsp(struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ struct files_struct *fsp,
+ const struct vfs_open_how *how,
+ bool *p_file_created)
+{
+ NTSTATUS status;
+ int old_fd;
+
+ if (fsp->fsp_flags.have_proc_fds &&
+ ((old_fd = fsp_get_pathref_fd(fsp)) != -1)) {
+
+ struct sys_proc_fd_path_buf buf;
+ struct smb_filename proc_fname = (struct smb_filename){
+ .base_name = sys_proc_fd_path(old_fd, &buf),
+ };
+ mode_t mode = fsp->fsp_name->st.st_ex_mode;
+ int new_fd;
+
+ SMB_ASSERT(fsp->fsp_flags.is_pathref);
+
+ if (S_ISLNK(mode)) {
+ return NT_STATUS_STOPPED_ON_SYMLINK;
+ }
+ if (!(S_ISREG(mode) || S_ISDIR(mode))) {
+ return NT_STATUS_IO_REPARSE_TAG_NOT_HANDLED;
+ }
+
+ fsp->fsp_flags.is_pathref = false;
+
+ new_fd = SMB_VFS_OPENAT(fsp->conn,
+ fsp->conn->cwd_fsp,
+ &proc_fname,
+ fsp,
+ how);
+ if (new_fd == -1) {
+ status = map_nt_error_from_unix(errno);
+ fd_close(fsp);
+ return status;
+ }
+
+ status = fd_close(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ fsp_set_fd(fsp, new_fd);
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * Close the existing pathref fd and set the fsp flag
+ * is_pathref to false so we get a "normal" fd this time.
+ */
+ status = fd_close(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ fsp->fsp_flags.is_pathref = false;
+
+ status = fd_open_atomic(dirfsp, smb_fname, fsp, how, p_file_created);
+ return status;
+}
+
+/****************************************************************************
+ Open a file.
+****************************************************************************/
+
+static NTSTATUS open_file(
+ struct smb_request *req,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname_atname,
+ files_struct *fsp,
+ const struct vfs_open_how *_how,
+ uint32_t access_mask, /* client requested access mask. */
+ uint32_t open_access_mask, /* what we're actually using in the open. */
+ uint32_t private_flags,
+ bool *p_file_created)
+{
+ connection_struct *conn = fsp->conn;
+ struct smb_filename *smb_fname = fsp->fsp_name;
+ struct vfs_open_how how = *_how;
+ NTSTATUS status = NT_STATUS_OK;
+ bool file_existed = VALID_STAT(fsp->fsp_name->st);
+ const uint32_t need_fd_mask =
+ FILE_READ_DATA |
+ FILE_WRITE_DATA |
+ FILE_APPEND_DATA |
+ FILE_EXECUTE |
+ SEC_FLAG_SYSTEM_SECURITY;
+ bool creating = !file_existed && (how.flags & O_CREAT);
+ bool open_fd = false;
+ bool posix_open = (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN);
+
+ /*
+ * Catch early an attempt to open an existing
+ * directory as a file.
+ */
+ if (file_existed && S_ISDIR(fsp->fsp_name->st.st_ex_mode)) {
+ return NT_STATUS_FILE_IS_A_DIRECTORY;
+ }
+
+ /*
+ * This little piece of insanity is inspired by the
+ * fact that an NT client can open a file for O_RDONLY,
+ * but set the create disposition to FILE_EXISTS_TRUNCATE.
+ * If the client *can* write to the file, then it expects to
+ * truncate the file, even though it is opening for readonly.
+ * Quicken uses this stupid trick in backup file creation...
+ * Thanks *greatly* to "David W. Chapman Jr." <dwcjr@inethouston.net>
+ * for helping track this one down. It didn't bite us in 2.0.x
+ * as we always opened files read-write in that release. JRA.
+ */
+
+ if (((how.flags & O_ACCMODE) == O_RDONLY) && (how.flags & O_TRUNC)) {
+ DBG_DEBUG("truncate requested on read-only open for file %s\n",
+ smb_fname_str_dbg(smb_fname));
+ how.flags = (how.flags & ~O_ACCMODE) | O_RDWR;
+ }
+
+ /* Check permissions */
+
+ /*
+ * This code was changed after seeing a client open request
+ * containing the open mode of (DENY_WRITE/read-only) with
+ * the 'create if not exist' bit set. The previous code
+ * would fail to open the file read only on a read-only share
+ * as it was checking the flags parameter directly against O_RDONLY,
+ * this was failing as the flags parameter was set to O_RDONLY|O_CREAT.
+ * JRA.
+ */
+
+ if (!CAN_WRITE(conn)) {
+ /* It's a read-only share - fail if we wanted to write. */
+ if ((how.flags & O_ACCMODE) != O_RDONLY ||
+ (how.flags & O_TRUNC) || (how.flags & O_APPEND)) {
+ DEBUG(3,("Permission denied opening %s\n",
+ smb_fname_str_dbg(smb_fname)));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ /*
+ * We don't want to write - but we must make sure that
+ * O_CREAT doesn't create the file if we have write
+ * access into the directory.
+ */
+ how.flags &= ~(O_CREAT | O_EXCL);
+ }
+
+ if ((open_access_mask & need_fd_mask) || creating ||
+ (how.flags & O_TRUNC)) {
+ open_fd = true;
+ }
+
+ if (open_fd) {
+ int ret;
+
+#if defined(O_NONBLOCK) && defined(S_ISFIFO)
+ /*
+ * We would block on opening a FIFO with no one else on the
+ * other end. Do what we used to do and add O_NONBLOCK to the
+ * open flags. JRA.
+ */
+
+ if (file_existed && S_ISFIFO(smb_fname->st.st_ex_mode)) {
+ how.flags |= O_NONBLOCK;
+ }
+#endif
+
+ if (!posix_open) {
+ const char *wild = smb_fname->base_name;
+ /*
+ * Don't open files with Microsoft wildcard characters.
+ */
+ if (fsp_is_alternate_stream(fsp)) {
+ /*
+ * wildcard characters are allowed in stream
+ * names only test the basefilename
+ */
+ wild = fsp->base_fsp->fsp_name->base_name;
+ }
+
+ if (ms_has_wild(wild)) {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ }
+
+ /* Can we access this file ? */
+ if (!fsp_is_alternate_stream(fsp)) {
+ /* Only do this check on non-stream open. */
+ if (file_existed) {
+ status = smbd_check_access_rights_fsp(
+ dirfsp,
+ fsp,
+ false,
+ open_access_mask);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("smbd_check_access_rights_fsp"
+ " on file %s returned %s\n",
+ fsp_str_dbg(fsp),
+ nt_errstr(status));
+ }
+
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status,
+ NT_STATUS_OBJECT_NAME_NOT_FOUND))
+ {
+ return status;
+ }
+
+ if (NT_STATUS_EQUAL(status,
+ NT_STATUS_OBJECT_NAME_NOT_FOUND))
+ {
+ DEBUG(10, ("open_file: "
+ "file %s vanished since we "
+ "checked for existence.\n",
+ smb_fname_str_dbg(smb_fname)));
+ file_existed = false;
+ SET_STAT_INVALID(fsp->fsp_name->st);
+ }
+ }
+
+ if (!file_existed) {
+ if (!(how.flags & O_CREAT)) {
+ /* File didn't exist and no O_CREAT. */
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ status = check_parent_access_fsp(
+ dirfsp,
+ SEC_DIR_ADD_FILE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("check_parent_access_fsp on "
+ "directory %s for file %s "
+ "returned %s\n",
+ smb_fname_str_dbg(
+ dirfsp->fsp_name),
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status));
+ return status;
+ }
+ }
+ }
+
+ /*
+ * Actually do the open - if O_TRUNC is needed handle it
+ * below under the share mode lock.
+ */
+ how.flags &= ~O_TRUNC;
+ status = reopen_from_fsp(dirfsp,
+ smb_fname_atname,
+ fsp,
+ &how,
+ p_file_created);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) {
+ /*
+ * Non-O_PATH reopen that hit a race
+ * condition: Someone has put a symlink where
+ * we used to have a file. Can't happen with
+ * O_PATH and reopening from /proc/self/fd/ or
+ * equivalent.
+ */
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_NOTICE("Error opening file %s (%s) (in_flags=%d) "
+ "(flags=%d)\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status),
+ _how->flags,
+ how.flags);
+ return status;
+ }
+
+ if (how.flags & O_NONBLOCK) {
+ /*
+ * GPFS can return ETIMEDOUT for pread on
+ * nonblocking file descriptors when files
+ * migrated to tape need to be recalled. I
+ * could imagine this happens elsewhere
+ * too. With blocking file descriptors this
+ * does not happen.
+ */
+ ret = vfs_set_blocking(fsp, true);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_WARNING("Could not set fd to blocking: "
+ "%s\n", strerror(errno));
+ fd_close(fsp);
+ return status;
+ }
+ }
+
+ if (*p_file_created) {
+ /* We created this file. */
+
+ bool need_re_stat = false;
+ /* Do all inheritance work after we've
+ done a successful fstat call and filled
+ in the stat struct in fsp->fsp_name. */
+
+ /* Inherit the ACL if required */
+ if (lp_inherit_permissions(SNUM(conn))) {
+ inherit_access_posix_acl(conn,
+ dirfsp,
+ smb_fname,
+ how.mode);
+ need_re_stat = true;
+ }
+
+ /* Change the owner if required. */
+ if (lp_inherit_owner(SNUM(conn)) != INHERIT_OWNER_NO) {
+ change_file_owner_to_parent_fsp(dirfsp, fsp);
+ need_re_stat = true;
+ }
+
+ if (need_re_stat) {
+ status = vfs_stat_fsp(fsp);
+ /*
+ * If we have an fd, this stat should succeed.
+ */
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Error doing fstat on open "
+ "file %s (%s)\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status));
+ fd_close(fsp);
+ return status;
+ }
+ }
+
+ notify_fname(conn, NOTIFY_ACTION_ADDED,
+ FILE_NOTIFY_CHANGE_FILE_NAME,
+ smb_fname->base_name);
+ }
+ } else {
+ if (!file_existed) {
+ /* File must exist for a stat open. */
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (S_ISLNK(smb_fname->st.st_ex_mode) &&
+ !posix_open)
+ {
+ /*
+ * Don't allow stat opens on symlinks directly unless
+ * it's a POSIX open. Match the return code from
+ * openat_pathref_fsp().
+ */
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (!fsp->fsp_flags.is_pathref) {
+ /*
+ * There is only one legit case where end up here:
+ * openat_pathref_fsp() failed to open a symlink, so the
+ * fsp was created by fsp_new() which doesn't set
+ * is_pathref. Other than that, we should always have a
+ * pathref fsp at this point. The subsequent checks
+ * assert this.
+ */
+ if (!(smb_fname->flags & SMB_FILENAME_POSIX_PATH)) {
+ DBG_ERR("[%s] is not a POSIX pathname\n",
+ smb_fname_str_dbg(smb_fname));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ if (!S_ISLNK(smb_fname->st.st_ex_mode)) {
+ DBG_ERR("[%s] is not a symlink\n",
+ smb_fname_str_dbg(smb_fname));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ if (fsp_get_pathref_fd(fsp) != -1) {
+ DBG_ERR("fd for [%s] is not -1: fd [%d]\n",
+ smb_fname_str_dbg(smb_fname),
+ fsp_get_pathref_fd(fsp));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ /*
+ * Access to streams is checked by checking the basefile and
+ * that has already been checked by check_base_file_access()
+ * in create_file_unixpath().
+ */
+ if (!fsp_is_alternate_stream(fsp)) {
+ status = smbd_check_access_rights_fsp(dirfsp,
+ fsp,
+ false,
+ open_access_mask);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND) &&
+ posix_open &&
+ S_ISLNK(smb_fname->st.st_ex_mode)) {
+ /* This is a POSIX stat open for delete
+ * or rename on a symlink that points
+ * nowhere. Allow. */
+ DEBUG(10,("open_file: allowing POSIX "
+ "open on bad symlink %s\n",
+ smb_fname_str_dbg(smb_fname)));
+ status = NT_STATUS_OK;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("smbd_check_access_rights_fsp on file "
+ "%s returned %s\n",
+ fsp_str_dbg(fsp),
+ nt_errstr(status));
+ return status;
+ }
+ }
+ }
+
+ fsp->file_id = vfs_file_id_from_sbuf(conn, &smb_fname->st);
+ fsp->vuid = req ? req->vuid : UID_FIELD_INVALID;
+ fsp->file_pid = req ? req->smbpid : 0;
+ fsp->fsp_flags.can_lock = true;
+ fsp->fsp_flags.can_read = ((access_mask & FILE_READ_DATA) != 0);
+ fsp->fsp_flags.can_write =
+ CAN_WRITE(conn) &&
+ ((access_mask & (FILE_WRITE_DATA | FILE_APPEND_DATA)) != 0);
+ if (fsp->fsp_name->twrp != 0) {
+ fsp->fsp_flags.can_write = false;
+ }
+ fsp->print_file = NULL;
+ fsp->fsp_flags.modified = false;
+ fsp->sent_oplock_break = NO_BREAK_SENT;
+ fsp->fsp_flags.is_directory = false;
+ if (is_in_path(smb_fname->base_name,
+ conn->aio_write_behind_list,
+ posix_open ? true : conn->case_sensitive)) {
+ fsp->fsp_flags.aio_write_behind = true;
+ }
+
+ DEBUG(2,("%s opened file %s read=%s write=%s (numopen=%d)\n",
+ conn->session_info->unix_info->unix_name,
+ smb_fname_str_dbg(smb_fname),
+ BOOLSTR(fsp->fsp_flags.can_read),
+ BOOLSTR(fsp->fsp_flags.can_write),
+ conn->num_files_open));
+
+ return NT_STATUS_OK;
+}
+
+static bool mask_conflict(
+ uint32_t new_access,
+ uint32_t existing_access,
+ uint32_t access_mask,
+ uint32_t new_sharemode,
+ uint32_t existing_sharemode,
+ uint32_t sharemode_mask)
+{
+ bool want_access = (new_access & access_mask);
+ bool allow_existing = (existing_sharemode & sharemode_mask);
+ bool have_access = (existing_access & access_mask);
+ bool allow_new = (new_sharemode & sharemode_mask);
+
+ if (want_access && !allow_existing) {
+ DBG_DEBUG("Access request 0x%"PRIx32"/0x%"PRIx32" conflicts "
+ "with existing sharemode 0x%"PRIx32"/0x%"PRIx32"\n",
+ new_access,
+ access_mask,
+ existing_sharemode,
+ sharemode_mask);
+ return true;
+ }
+ if (have_access && !allow_new) {
+ DBG_DEBUG("Sharemode request 0x%"PRIx32"/0x%"PRIx32" conflicts "
+ "with existing access 0x%"PRIx32"/0x%"PRIx32"\n",
+ new_sharemode,
+ sharemode_mask,
+ existing_access,
+ access_mask);
+ return true;
+ }
+ return false;
+}
+
+/****************************************************************************
+ Check if we can open a file with a share mode.
+ Returns True if conflict, False if not.
+****************************************************************************/
+
+static const uint32_t conflicting_access =
+ FILE_WRITE_DATA|
+ FILE_APPEND_DATA|
+ FILE_READ_DATA|
+ FILE_EXECUTE|
+ DELETE_ACCESS;
+
+static bool share_conflict(uint32_t e_access_mask,
+ uint32_t e_share_access,
+ uint32_t access_mask,
+ uint32_t share_access)
+{
+ bool conflict;
+
+ DBG_DEBUG("existing access_mask = 0x%"PRIx32", "
+ "existing share access = 0x%"PRIx32", "
+ "access_mask = 0x%"PRIx32", "
+ "share_access = 0x%"PRIx32"\n",
+ e_access_mask,
+ e_share_access,
+ access_mask,
+ share_access);
+
+ if ((e_access_mask & conflicting_access) == 0) {
+ DBG_DEBUG("No conflict due to "
+ "existing access_mask = 0x%"PRIx32"\n",
+ e_access_mask);
+ return false;
+ }
+ if ((access_mask & conflicting_access) == 0) {
+ DBG_DEBUG("No conflict due to access_mask = 0x%"PRIx32"\n",
+ access_mask);
+ return false;
+ }
+
+ conflict = mask_conflict(
+ access_mask, e_access_mask, FILE_WRITE_DATA | FILE_APPEND_DATA,
+ share_access, e_share_access, FILE_SHARE_WRITE);
+ conflict |= mask_conflict(
+ access_mask, e_access_mask, FILE_READ_DATA | FILE_EXECUTE,
+ share_access, e_share_access, FILE_SHARE_READ);
+ conflict |= mask_conflict(
+ access_mask, e_access_mask, DELETE_ACCESS,
+ share_access, e_share_access, FILE_SHARE_DELETE);
+
+ DBG_DEBUG("conflict=%s\n", conflict ? "true" : "false");
+ return conflict;
+}
+
+#if defined(DEVELOPER)
+
+struct validate_my_share_entries_state {
+ struct smbd_server_connection *sconn;
+ struct file_id fid;
+ struct server_id self;
+};
+
+static bool validate_my_share_entries_fn(
+ struct share_mode_entry *e,
+ bool *modified,
+ void *private_data)
+{
+ struct validate_my_share_entries_state *state = private_data;
+ files_struct *fsp;
+
+ if (!server_id_equal(&state->self, &e->pid)) {
+ return false;
+ }
+
+ if (e->op_mid == 0) {
+ /* INTERNAL_OPEN_ONLY */
+ return false;
+ }
+
+ fsp = file_find_dif(state->sconn, state->fid, e->share_file_id);
+ if (!fsp) {
+ DBG_ERR("PANIC : %s\n",
+ share_mode_str(talloc_tos(), 0, &state->fid, e));
+ smb_panic("validate_my_share_entries: Cannot match a "
+ "share entry with an open file\n");
+ }
+
+ if (((uint16_t)fsp->oplock_type) != e->op_type) {
+ goto panic;
+ }
+
+ return false;
+
+ panic:
+ {
+ char *str;
+ DBG_ERR("validate_my_share_entries: PANIC : %s\n",
+ share_mode_str(talloc_tos(), 0, &state->fid, e));
+ str = talloc_asprintf(talloc_tos(),
+ "validate_my_share_entries: "
+ "file %s, oplock_type = 0x%x, op_type = 0x%x\n",
+ fsp->fsp_name->base_name,
+ (unsigned int)fsp->oplock_type,
+ (unsigned int)e->op_type);
+ smb_panic(str);
+ }
+
+ return false;
+}
+#endif
+
+/**
+ * Allowed access mask for stat opens relevant to oplocks
+ **/
+bool is_oplock_stat_open(uint32_t access_mask)
+{
+ const uint32_t stat_open_bits =
+ (SYNCHRONIZE_ACCESS|
+ FILE_READ_ATTRIBUTES|
+ FILE_WRITE_ATTRIBUTES);
+
+ return (((access_mask & stat_open_bits) != 0) &&
+ ((access_mask & ~stat_open_bits) == 0));
+}
+
+/**
+ * Allowed access mask for stat opens relevant to leases
+ **/
+bool is_lease_stat_open(uint32_t access_mask)
+{
+ const uint32_t stat_open_bits =
+ (SYNCHRONIZE_ACCESS|
+ FILE_READ_ATTRIBUTES|
+ FILE_WRITE_ATTRIBUTES|
+ READ_CONTROL_ACCESS);
+
+ return (((access_mask & stat_open_bits) != 0) &&
+ ((access_mask & ~stat_open_bits) == 0));
+}
+
+struct has_delete_on_close_state {
+ bool ret;
+};
+
+static bool has_delete_on_close_fn(
+ struct share_mode_entry *e,
+ bool *modified,
+ void *private_data)
+{
+ struct has_delete_on_close_state *state = private_data;
+ state->ret = !share_entry_stale_pid(e);
+ return state->ret;
+}
+
+static bool has_delete_on_close(struct share_mode_lock *lck,
+ uint32_t name_hash)
+{
+ struct has_delete_on_close_state state = { .ret = false };
+ bool ok;
+
+ if (!is_delete_on_close_set(lck, name_hash)) {
+ return false;
+ }
+
+ ok= share_mode_forall_entries(lck, has_delete_on_close_fn, &state);
+ if (!ok) {
+ DBG_DEBUG("share_mode_forall_entries failed\n");
+ return false;
+ }
+ return state.ret;
+}
+
+static void share_mode_flags_restrict(
+ struct share_mode_lock *lck,
+ uint32_t access_mask,
+ uint32_t share_mode,
+ uint32_t lease_type)
+{
+ uint32_t existing_access_mask, existing_share_mode;
+ uint32_t existing_lease_type;
+
+ share_mode_flags_get(
+ lck,
+ &existing_access_mask,
+ &existing_share_mode,
+ &existing_lease_type);
+
+ existing_access_mask |= access_mask;
+ if (access_mask & conflicting_access) {
+ existing_share_mode &= share_mode;
+ }
+ existing_lease_type |= lease_type;
+
+ share_mode_flags_set(
+ lck,
+ existing_access_mask,
+ existing_share_mode,
+ existing_lease_type,
+ NULL);
+}
+
+/****************************************************************************
+ Deal with share modes
+ Invariant: Share mode must be locked on entry and exit.
+ Returns -1 on error, or number of share modes on success (may be zero).
+****************************************************************************/
+
+struct open_mode_check_state {
+ struct file_id fid;
+ uint32_t access_mask;
+ uint32_t share_access;
+ uint32_t lease_type;
+};
+
+static bool open_mode_check_fn(
+ struct share_mode_entry *e,
+ bool *modified,
+ void *private_data)
+{
+ struct open_mode_check_state *state = private_data;
+ bool disconnected, stale;
+ uint32_t access_mask, share_access, lease_type;
+
+ disconnected = server_id_is_disconnected(&e->pid);
+ if (disconnected) {
+ return false;
+ }
+
+ access_mask = state->access_mask | e->access_mask;
+ share_access = state->share_access;
+ if (e->access_mask & conflicting_access) {
+ share_access &= e->share_access;
+ }
+ lease_type = state->lease_type | get_lease_type(e, state->fid);
+
+ if ((access_mask == state->access_mask) &&
+ (share_access == state->share_access) &&
+ (lease_type == state->lease_type)) {
+ return false;
+ }
+
+ stale = share_entry_stale_pid(e);
+ if (stale) {
+ return false;
+ }
+
+ state->access_mask = access_mask;
+ state->share_access = share_access;
+ state->lease_type = lease_type;
+
+ return false;
+}
+
+static NTSTATUS open_mode_check(connection_struct *conn,
+ struct file_id fid,
+ struct share_mode_lock *lck,
+ uint32_t access_mask,
+ uint32_t share_access)
+{
+ struct open_mode_check_state state;
+ bool ok, conflict;
+ bool modified = false;
+
+ if (is_oplock_stat_open(access_mask)) {
+ /* Stat open that doesn't trigger oplock breaks or share mode
+ * checks... ! JRA. */
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * Check if the share modes will give us access.
+ */
+
+#if defined(DEVELOPER)
+ {
+ struct validate_my_share_entries_state validate_state = {
+ .sconn = conn->sconn,
+ .fid = fid,
+ .self = messaging_server_id(conn->sconn->msg_ctx),
+ };
+ ok = share_mode_forall_entries(
+ lck, validate_my_share_entries_fn, &validate_state);
+ SMB_ASSERT(ok);
+ }
+#endif
+
+ share_mode_flags_get(
+ lck, &state.access_mask, &state.share_access, NULL);
+
+ conflict = share_conflict(
+ state.access_mask,
+ state.share_access,
+ access_mask,
+ share_access);
+ if (!conflict) {
+ DBG_DEBUG("No conflict due to share_mode_flags access\n");
+ return NT_STATUS_OK;
+ }
+
+ state = (struct open_mode_check_state) {
+ .fid = fid,
+ .share_access = (FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE),
+ };
+
+ /*
+ * Walk the share mode array to recalculate d->flags
+ */
+
+ ok = share_mode_forall_entries(lck, open_mode_check_fn, &state);
+ if (!ok) {
+ DBG_DEBUG("share_mode_forall_entries failed\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ share_mode_flags_set(
+ lck,
+ state.access_mask,
+ state.share_access,
+ state.lease_type,
+ &modified);
+ if (!modified) {
+ /*
+ * We only end up here if we had a sharing violation
+ * from d->flags and have recalculated it.
+ */
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+
+ conflict = share_conflict(
+ state.access_mask,
+ state.share_access,
+ access_mask,
+ share_access);
+ if (!conflict) {
+ DBG_DEBUG("No conflict due to share_mode_flags access\n");
+ return NT_STATUS_OK;
+ }
+
+ return NT_STATUS_SHARING_VIOLATION;
+}
+
+/*
+ * Send a break message to the oplock holder and delay the open for
+ * our client.
+ */
+
+NTSTATUS send_break_message(struct messaging_context *msg_ctx,
+ const struct file_id *id,
+ const struct share_mode_entry *exclusive,
+ uint16_t break_to)
+{
+ struct oplock_break_message msg = {
+ .id = *id,
+ .share_file_id = exclusive->share_file_id,
+ .break_to = break_to,
+ };
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob;
+ NTSTATUS status;
+
+ if (DEBUGLVL(10)) {
+ struct server_id_buf buf;
+ DBG_DEBUG("Sending break message to %s\n",
+ server_id_str_buf(exclusive->pid, &buf));
+ NDR_PRINT_DEBUG(oplock_break_message, &msg);
+ }
+
+ ndr_err = ndr_push_struct_blob(
+ &blob,
+ talloc_tos(),
+ &msg,
+ (ndr_push_flags_fn_t)ndr_push_oplock_break_message);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_WARNING("ndr_push_oplock_break_message failed: %s\n",
+ ndr_errstr(ndr_err));
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ status = messaging_send(
+ msg_ctx, exclusive->pid, MSG_SMB_BREAK_REQUEST, &blob);
+ TALLOC_FREE(blob.data);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("Could not send oplock break message: %s\n",
+ nt_errstr(status)));
+ }
+
+ return status;
+}
+
+struct validate_oplock_types_state {
+ bool valid;
+ bool batch;
+ bool ex_or_batch;
+ bool level2;
+ bool no_oplock;
+ uint32_t num_non_stat_opens;
+};
+
+static bool validate_oplock_types_fn(
+ struct share_mode_entry *e,
+ bool *modified,
+ void *private_data)
+{
+ struct validate_oplock_types_state *state = private_data;
+
+ if (e->op_mid == 0) {
+ /* INTERNAL_OPEN_ONLY */
+ return false;
+ }
+
+ if (e->op_type == NO_OPLOCK && is_oplock_stat_open(e->access_mask)) {
+ /*
+ * We ignore stat opens in the table - they always
+ * have NO_OPLOCK and never get or cause breaks. JRA.
+ */
+ return false;
+ }
+
+ state->num_non_stat_opens += 1;
+
+ if (BATCH_OPLOCK_TYPE(e->op_type)) {
+ /* batch - can only be one. */
+ if (share_entry_stale_pid(e)) {
+ DBG_DEBUG("Found stale batch oplock\n");
+ return false;
+ }
+ if (state->ex_or_batch ||
+ state->batch ||
+ state->level2 ||
+ state->no_oplock) {
+ DBG_ERR("Bad batch oplock entry\n");
+ state->valid = false;
+ return true;
+ }
+ state->batch = true;
+ }
+
+ if (EXCLUSIVE_OPLOCK_TYPE(e->op_type)) {
+ if (share_entry_stale_pid(e)) {
+ DBG_DEBUG("Found stale duplicate oplock\n");
+ return false;
+ }
+ /* Exclusive or batch - can only be one. */
+ if (state->ex_or_batch ||
+ state->level2 ||
+ state->no_oplock) {
+ DBG_ERR("Bad exclusive or batch oplock entry\n");
+ state->valid = false;
+ return true;
+ }
+ state->ex_or_batch = true;
+ }
+
+ if (LEVEL_II_OPLOCK_TYPE(e->op_type)) {
+ if (state->batch || state->ex_or_batch) {
+ if (share_entry_stale_pid(e)) {
+ DBG_DEBUG("Found stale LevelII oplock\n");
+ return false;
+ }
+ DBG_DEBUG("Bad levelII oplock entry\n");
+ state->valid = false;
+ return true;
+ }
+ state->level2 = true;
+ }
+
+ if (e->op_type == NO_OPLOCK) {
+ if (state->batch || state->ex_or_batch) {
+ if (share_entry_stale_pid(e)) {
+ DBG_DEBUG("Found stale NO_OPLOCK entry\n");
+ return false;
+ }
+ DBG_ERR("Bad no oplock entry\n");
+ state->valid = false;
+ return true;
+ }
+ state->no_oplock = true;
+ }
+
+ return false;
+}
+
+/*
+ * Do internal consistency checks on the share mode for a file.
+ */
+
+static bool validate_oplock_types(struct share_mode_lock *lck)
+{
+ struct validate_oplock_types_state state = { .valid = true };
+ static bool skip_validation;
+ bool validate;
+ bool ok;
+
+ if (skip_validation) {
+ return true;
+ }
+
+ validate = lp_parm_bool(-1, "smbd", "validate_oplock_types", false);
+ if (!validate) {
+ DBG_DEBUG("smbd:validate_oplock_types not set to yes\n");
+ skip_validation = true;
+ return true;
+ }
+
+ ok = share_mode_forall_entries(lck, validate_oplock_types_fn, &state);
+ if (!ok) {
+ DBG_DEBUG("share_mode_forall_entries failed\n");
+ return false;
+ }
+ if (!state.valid) {
+ DBG_DEBUG("Got invalid oplock configuration\n");
+ return false;
+ }
+
+ if ((state.batch || state.ex_or_batch) &&
+ (state.num_non_stat_opens != 1)) {
+ DBG_WARNING("got batch (%d) or ex (%d) non-exclusively "
+ "(%"PRIu32")\n",
+ (int)state.batch,
+ (int)state.ex_or_batch,
+ state.num_non_stat_opens);
+ return false;
+ }
+
+ return true;
+}
+
+static bool is_same_lease(const files_struct *fsp,
+ const struct share_mode_entry *e,
+ const struct smb2_lease *lease)
+{
+ if (e->op_type != LEASE_OPLOCK) {
+ return false;
+ }
+ if (lease == NULL) {
+ return false;
+ }
+
+ return smb2_lease_equal(fsp_client_guid(fsp),
+ &lease->lease_key,
+ &e->client_guid,
+ &e->lease_key);
+}
+
+static bool file_has_brlocks(files_struct *fsp)
+{
+ struct byte_range_lock *br_lck;
+
+ br_lck = brl_get_locks_readonly(fsp);
+ if (!br_lck)
+ return false;
+
+ return (brl_num_locks(br_lck) > 0);
+}
+
+struct fsp_lease *find_fsp_lease(struct files_struct *new_fsp,
+ const struct smb2_lease_key *key,
+ uint32_t current_state,
+ uint16_t lease_version,
+ uint16_t lease_epoch)
+{
+ struct files_struct *fsp;
+
+ /*
+ * TODO: Measure how expensive this loop is with thousands of open
+ * handles...
+ */
+
+ for (fsp = file_find_di_first(new_fsp->conn->sconn, new_fsp->file_id, true);
+ fsp != NULL;
+ fsp = file_find_di_next(fsp, true)) {
+
+ if (fsp == new_fsp) {
+ continue;
+ }
+ if (fsp->oplock_type != LEASE_OPLOCK) {
+ continue;
+ }
+ if (smb2_lease_key_equal(&fsp->lease->lease.lease_key, key)) {
+ fsp->lease->ref_count += 1;
+ return fsp->lease;
+ }
+ }
+
+ /* Not found - must be leased in another smbd. */
+ new_fsp->lease = talloc_zero(new_fsp->conn->sconn, struct fsp_lease);
+ if (new_fsp->lease == NULL) {
+ return NULL;
+ }
+ new_fsp->lease->ref_count = 1;
+ new_fsp->lease->sconn = new_fsp->conn->sconn;
+ new_fsp->lease->lease.lease_key = *key;
+ new_fsp->lease->lease.lease_state = current_state;
+ /*
+ * We internally treat all leases as V2 and update
+ * the epoch, but when sending breaks it matters if
+ * the requesting lease was v1 or v2.
+ */
+ new_fsp->lease->lease.lease_version = lease_version;
+ new_fsp->lease->lease.lease_epoch = lease_epoch;
+ return new_fsp->lease;
+}
+
+static NTSTATUS try_lease_upgrade(struct files_struct *fsp,
+ struct share_mode_lock *lck,
+ const struct GUID *client_guid,
+ const struct smb2_lease *lease,
+ uint32_t granted)
+{
+ bool do_upgrade;
+ uint32_t current_state, breaking_to_requested, breaking_to_required;
+ bool breaking;
+ uint16_t lease_version, epoch;
+ uint32_t existing, requested;
+ NTSTATUS status;
+
+ status = leases_db_get(
+ client_guid,
+ &lease->lease_key,
+ &fsp->file_id,
+ &current_state,
+ &breaking,
+ &breaking_to_requested,
+ &breaking_to_required,
+ &lease_version,
+ &epoch);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ fsp->lease = find_fsp_lease(
+ fsp,
+ &lease->lease_key,
+ current_state,
+ lease_version,
+ epoch);
+ if (fsp->lease == NULL) {
+ DEBUG(1, ("Did not find existing lease for file %s\n",
+ fsp_str_dbg(fsp)));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * Upgrade only if the requested lease is a strict upgrade.
+ */
+ existing = current_state;
+ requested = lease->lease_state;
+
+ /*
+ * Tricky: This test makes sure that "requested" is a
+ * strict bitwise superset of "existing".
+ */
+ do_upgrade = ((existing & requested) == existing);
+
+ /*
+ * Upgrade only if there's a change.
+ */
+ do_upgrade &= (granted != existing);
+
+ /*
+ * Upgrade only if other leases don't prevent what was asked
+ * for.
+ */
+ do_upgrade &= (granted == requested);
+
+ /*
+ * only upgrade if we are not in breaking state
+ */
+ do_upgrade &= !breaking;
+
+ DEBUG(10, ("existing=%"PRIu32", requested=%"PRIu32", "
+ "granted=%"PRIu32", do_upgrade=%d\n",
+ existing, requested, granted, (int)do_upgrade));
+
+ if (do_upgrade) {
+ NTSTATUS set_status;
+
+ current_state = granted;
+ epoch += 1;
+
+ set_status = leases_db_set(
+ client_guid,
+ &lease->lease_key,
+ current_state,
+ breaking,
+ breaking_to_requested,
+ breaking_to_required,
+ lease_version,
+ epoch);
+
+ if (!NT_STATUS_IS_OK(set_status)) {
+ DBG_DEBUG("leases_db_set failed: %s\n",
+ nt_errstr(set_status));
+ return set_status;
+ }
+ }
+
+ fsp_lease_update(fsp);
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS grant_new_fsp_lease(struct files_struct *fsp,
+ struct share_mode_lock *lck,
+ const struct GUID *client_guid,
+ const struct smb2_lease *lease,
+ uint32_t granted)
+{
+ NTSTATUS status;
+
+ fsp->lease = talloc_zero(fsp->conn->sconn, struct fsp_lease);
+ if (fsp->lease == NULL) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+ fsp->lease->ref_count = 1;
+ fsp->lease->sconn = fsp->conn->sconn;
+ fsp->lease->lease.lease_version = lease->lease_version;
+ fsp->lease->lease.lease_key = lease->lease_key;
+ fsp->lease->lease.lease_state = granted;
+ fsp->lease->lease.lease_epoch = lease->lease_epoch + 1;
+
+ status = leases_db_add(client_guid,
+ &lease->lease_key,
+ &fsp->file_id,
+ fsp->lease->lease.lease_state,
+ fsp->lease->lease.lease_version,
+ fsp->lease->lease.lease_epoch,
+ fsp->conn->connectpath,
+ fsp->fsp_name->base_name,
+ fsp->fsp_name->stream_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("%s: leases_db_add failed: %s\n", __func__,
+ nt_errstr(status)));
+ TALLOC_FREE(fsp->lease);
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ /*
+ * We used to set lck->data->modified=true here without
+ * actually modifying lck->data, triggering a needless
+ * writeback of lck->data.
+ *
+ * Apart from that writeback, setting modified=true has the
+ * effect of triggering all waiters for this file to
+ * retry. This only makes sense if any blocking condition
+ * (i.e. waiting for a lease to be downgraded or removed) is
+ * gone. This routine here only adds a lease, so it will never
+ * free up resources that blocked waiters can now claim. So
+ * that second effect also does not matter in this
+ * routine. Thus setting lck->data->modified=true does not
+ * need to be done here.
+ */
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS grant_fsp_lease(struct files_struct *fsp,
+ struct share_mode_lock *lck,
+ const struct smb2_lease *lease,
+ uint32_t granted)
+{
+ const struct GUID *client_guid = fsp_client_guid(fsp);
+ NTSTATUS status;
+
+ status = try_lease_upgrade(fsp, lck, client_guid, lease, granted);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ status = grant_new_fsp_lease(
+ fsp, lck, client_guid, lease, granted);
+ }
+
+ return status;
+}
+
+static int map_lease_type_to_oplock(uint32_t lease_type)
+{
+ int result = NO_OPLOCK;
+
+ switch (lease_type) {
+ case SMB2_LEASE_READ|SMB2_LEASE_WRITE|SMB2_LEASE_HANDLE:
+ result = BATCH_OPLOCK|EXCLUSIVE_OPLOCK;
+ break;
+ case SMB2_LEASE_READ|SMB2_LEASE_WRITE:
+ result = EXCLUSIVE_OPLOCK;
+ break;
+ case SMB2_LEASE_READ|SMB2_LEASE_HANDLE:
+ case SMB2_LEASE_READ:
+ result = LEVEL_II_OPLOCK;
+ break;
+ }
+
+ return result;
+}
+
+struct delay_for_oplock_state {
+ struct files_struct *fsp;
+ const struct smb2_lease *lease;
+ bool will_overwrite;
+ uint32_t delay_mask;
+ bool first_open_attempt;
+ bool got_handle_lease;
+ bool got_oplock;
+ bool have_other_lease;
+ uint32_t total_lease_types;
+ bool delay;
+};
+
+static bool delay_for_oplock_fn(
+ struct share_mode_entry *e,
+ bool *modified,
+ void *private_data)
+{
+ struct delay_for_oplock_state *state = private_data;
+ struct files_struct *fsp = state->fsp;
+ const struct smb2_lease *lease = state->lease;
+ bool e_is_lease = (e->op_type == LEASE_OPLOCK);
+ uint32_t e_lease_type = SMB2_LEASE_NONE;
+ uint32_t break_to;
+ bool lease_is_breaking = false;
+
+ if (e_is_lease) {
+ NTSTATUS status;
+
+ if (lease != NULL) {
+ bool our_lease = is_same_lease(fsp, e, lease);
+ if (our_lease) {
+ DBG_DEBUG("Ignoring our own lease\n");
+ return false;
+ }
+ }
+
+ status = leases_db_get(
+ &e->client_guid,
+ &e->lease_key,
+ &fsp->file_id,
+ &e_lease_type, /* current_state */
+ &lease_is_breaking,
+ NULL, /* breaking_to_requested */
+ NULL, /* breaking_to_required */
+ NULL, /* lease_version */
+ NULL); /* epoch */
+
+ /*
+ * leases_db_get() can return NT_STATUS_NOT_FOUND
+ * if the share_mode_entry e is stale and the
+ * lease record was already removed. In this case return
+ * false so the traverse continues.
+ */
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND) &&
+ share_entry_stale_pid(e))
+ {
+ struct GUID_txt_buf guid_strbuf;
+ struct file_id_buf file_id_strbuf;
+ DBG_DEBUG("leases_db_get for client_guid [%s] "
+ "lease_key [%"PRIu64"/%"PRIu64"] "
+ "file_id [%s] failed for stale "
+ "share_mode_entry\n",
+ GUID_buf_string(&e->client_guid, &guid_strbuf),
+ e->lease_key.data[0],
+ e->lease_key.data[1],
+ file_id_str_buf(fsp->file_id, &file_id_strbuf));
+ return false;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ struct GUID_txt_buf guid_strbuf;
+ struct file_id_buf file_id_strbuf;
+ DBG_ERR("leases_db_get for client_guid [%s] "
+ "lease_key [%"PRIu64"/%"PRIu64"] "
+ "file_id [%s] failed: %s\n",
+ GUID_buf_string(&e->client_guid, &guid_strbuf),
+ e->lease_key.data[0],
+ e->lease_key.data[1],
+ file_id_str_buf(fsp->file_id, &file_id_strbuf),
+ nt_errstr(status));
+ smb_panic("leases_db_get() failed");
+ }
+ } else {
+ e_lease_type = get_lease_type(e, fsp->file_id);
+ }
+
+ if (((e_lease_type & ~state->total_lease_types) != 0) &&
+ !share_entry_stale_pid(e))
+ {
+ state->total_lease_types |= e_lease_type;
+ }
+
+ if (!state->got_handle_lease &&
+ ((e_lease_type & SMB2_LEASE_HANDLE) != 0) &&
+ !share_entry_stale_pid(e)) {
+ state->got_handle_lease = true;
+ }
+
+ if (!state->got_oplock &&
+ (e->op_type != LEASE_OPLOCK) &&
+ !share_entry_stale_pid(e)) {
+ state->got_oplock = true;
+ }
+
+ if (!state->have_other_lease &&
+ !is_same_lease(fsp, e, lease) &&
+ !share_entry_stale_pid(e)) {
+ state->have_other_lease = true;
+ }
+
+ if (e_is_lease && is_lease_stat_open(fsp->access_mask)) {
+ return false;
+ }
+
+ break_to = e_lease_type & ~state->delay_mask;
+
+ if (state->will_overwrite) {
+ break_to &= ~(SMB2_LEASE_HANDLE|SMB2_LEASE_READ);
+ }
+
+ DBG_DEBUG("e_lease_type %u, will_overwrite: %u\n",
+ (unsigned)e_lease_type,
+ (unsigned)state->will_overwrite);
+
+ if ((e_lease_type & ~break_to) == 0) {
+ if (lease_is_breaking) {
+ state->delay = true;
+ }
+ return false;
+ }
+
+ if (share_entry_stale_pid(e)) {
+ return false;
+ }
+
+ if (state->will_overwrite) {
+ /*
+ * If we break anyway break to NONE directly.
+ * Otherwise vfs_set_filelen() will trigger the
+ * break.
+ */
+ break_to &= ~(SMB2_LEASE_READ|SMB2_LEASE_WRITE);
+ }
+
+ if (!e_is_lease) {
+ /*
+ * Oplocks only support breaking to R or NONE.
+ */
+ break_to &= ~(SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE);
+ }
+
+ DBG_DEBUG("breaking from %d to %d\n",
+ (int)e_lease_type,
+ (int)break_to);
+ send_break_message(
+ fsp->conn->sconn->msg_ctx, &fsp->file_id, e, break_to);
+ if (e_lease_type & state->delay_mask) {
+ state->delay = true;
+ }
+ if (lease_is_breaking && !state->first_open_attempt) {
+ state->delay = true;
+ }
+
+ return false;
+};
+
+static NTSTATUS delay_for_oplock(files_struct *fsp,
+ int oplock_request,
+ const struct smb2_lease *lease,
+ struct share_mode_lock *lck,
+ bool have_sharing_violation,
+ uint32_t create_disposition,
+ bool first_open_attempt,
+ int *poplock_type,
+ uint32_t *pgranted)
+{
+ struct delay_for_oplock_state state = {
+ .fsp = fsp,
+ .lease = lease,
+ .first_open_attempt = first_open_attempt,
+ };
+ uint32_t requested;
+ uint32_t granted;
+ int oplock_type;
+ bool ok;
+
+ *poplock_type = NO_OPLOCK;
+ *pgranted = 0;
+
+ if (fsp->fsp_flags.is_directory) {
+ /*
+ * No directory leases yet
+ */
+ SMB_ASSERT(oplock_request == NO_OPLOCK);
+ if (have_sharing_violation) {
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+ return NT_STATUS_OK;
+ }
+
+ if (oplock_request == LEASE_OPLOCK) {
+ if (lease == NULL) {
+ /*
+ * The SMB2 layer should have checked this
+ */
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ requested = lease->lease_state;
+ } else {
+ requested = map_oplock_to_lease_type(
+ oplock_request & ~SAMBA_PRIVATE_OPLOCK_MASK);
+ }
+
+ share_mode_flags_get(lck, NULL, NULL, &state.total_lease_types);
+
+ if (is_oplock_stat_open(fsp->access_mask)) {
+ goto grant;
+ }
+
+ state.delay_mask = have_sharing_violation ?
+ SMB2_LEASE_HANDLE : SMB2_LEASE_WRITE;
+
+ switch (create_disposition) {
+ case FILE_SUPERSEDE:
+ case FILE_OVERWRITE:
+ case FILE_OVERWRITE_IF:
+ state.will_overwrite = true;
+ break;
+ default:
+ state.will_overwrite = false;
+ break;
+ }
+
+ state.total_lease_types = SMB2_LEASE_NONE;
+ ok = share_mode_forall_entries(lck, delay_for_oplock_fn, &state);
+ if (!ok) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (state.delay) {
+ return NT_STATUS_RETRY;
+ }
+
+grant:
+ if (have_sharing_violation) {
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+
+ granted = requested;
+
+ if (oplock_request == LEASE_OPLOCK) {
+ if (lp_kernel_oplocks(SNUM(fsp->conn))) {
+ DEBUG(10, ("No lease granted because kernel oplocks are enabled\n"));
+ granted = SMB2_LEASE_NONE;
+ }
+ if ((granted & (SMB2_LEASE_READ|SMB2_LEASE_WRITE)) == 0) {
+ DEBUG(10, ("No read or write lease requested\n"));
+ granted = SMB2_LEASE_NONE;
+ }
+ if (granted == SMB2_LEASE_WRITE) {
+ DEBUG(10, ("pure write lease requested\n"));
+ granted = SMB2_LEASE_NONE;
+ }
+ if (granted == (SMB2_LEASE_WRITE|SMB2_LEASE_HANDLE)) {
+ DEBUG(10, ("write and handle lease requested\n"));
+ granted = SMB2_LEASE_NONE;
+ }
+ }
+
+ if (lp_locking(fsp->conn->params) && file_has_brlocks(fsp)) {
+ DBG_DEBUG("file %s has byte range locks\n",
+ fsp_str_dbg(fsp));
+ granted &= ~SMB2_LEASE_READ;
+ }
+
+ if (state.have_other_lease) {
+ /*
+ * Can grant only one writer
+ */
+ granted &= ~SMB2_LEASE_WRITE;
+ }
+
+ if ((granted & SMB2_LEASE_READ) && !(granted & SMB2_LEASE_WRITE)) {
+ bool allow_level2 =
+ (global_client_caps & CAP_LEVEL_II_OPLOCKS) &&
+ lp_level2_oplocks(SNUM(fsp->conn));
+
+ if (!allow_level2) {
+ granted = SMB2_LEASE_NONE;
+ }
+ }
+
+ if (oplock_request == LEASE_OPLOCK) {
+ if (state.got_oplock) {
+ granted &= ~SMB2_LEASE_HANDLE;
+ }
+
+ oplock_type = LEASE_OPLOCK;
+ } else {
+ if (state.got_handle_lease) {
+ granted = SMB2_LEASE_NONE;
+ }
+
+ /*
+ * Reflect possible downgrades from:
+ * - map_lease_type_to_oplock() => "RH" to just LEVEL_II
+ */
+ oplock_type = map_lease_type_to_oplock(granted);
+ granted = map_oplock_to_lease_type(oplock_type);
+ }
+
+ state.total_lease_types |= granted;
+
+ {
+ uint32_t acc, sh, ls;
+ share_mode_flags_get(lck, &acc, &sh, &ls);
+ ls = state.total_lease_types;
+ share_mode_flags_set(lck, acc, sh, ls, NULL);
+ }
+
+ DBG_DEBUG("oplock type 0x%x granted (%s%s%s)(0x%x), on file %s, "
+ "requested 0x%x (%s%s%s)(0x%x) => total (%s%s%s)(0x%x)\n",
+ fsp->oplock_type,
+ granted & SMB2_LEASE_READ ? "R":"",
+ granted & SMB2_LEASE_WRITE ? "W":"",
+ granted & SMB2_LEASE_HANDLE ? "H":"",
+ granted,
+ fsp_str_dbg(fsp),
+ oplock_request,
+ requested & SMB2_LEASE_READ ? "R":"",
+ requested & SMB2_LEASE_WRITE ? "W":"",
+ requested & SMB2_LEASE_HANDLE ? "H":"",
+ requested,
+ state.total_lease_types & SMB2_LEASE_READ ? "R":"",
+ state.total_lease_types & SMB2_LEASE_WRITE ? "W":"",
+ state.total_lease_types & SMB2_LEASE_HANDLE ? "H":"",
+ state.total_lease_types);
+
+ *poplock_type = oplock_type;
+ *pgranted = granted;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS handle_share_mode_lease(
+ files_struct *fsp,
+ struct share_mode_lock *lck,
+ uint32_t create_disposition,
+ uint32_t access_mask,
+ uint32_t share_access,
+ int oplock_request,
+ const struct smb2_lease *lease,
+ bool first_open_attempt,
+ int *poplock_type,
+ uint32_t *pgranted)
+{
+ bool sharing_violation = false;
+ NTSTATUS status;
+
+ *poplock_type = NO_OPLOCK;
+ *pgranted = 0;
+
+ status = open_mode_check(
+ fsp->conn, fsp->file_id, lck, access_mask, share_access);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ sharing_violation = true;
+ status = NT_STATUS_OK; /* handled later */
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (oplock_request == INTERNAL_OPEN_ONLY) {
+ if (sharing_violation) {
+ DBG_DEBUG("Sharing violation for internal open\n");
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+
+ /*
+ * Internal opens never do oplocks or leases. We don't
+ * need to go through delay_for_oplock().
+ */
+ return NT_STATUS_OK;
+ }
+
+ status = delay_for_oplock(
+ fsp,
+ oplock_request,
+ lease,
+ lck,
+ sharing_violation,
+ create_disposition,
+ first_open_attempt,
+ poplock_type,
+ pgranted);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static bool request_timed_out(struct smb_request *req, struct timeval timeout)
+{
+ struct timeval now, end_time;
+ GetTimeOfDay(&now);
+ end_time = timeval_sum(&req->request_time, &timeout);
+ return (timeval_compare(&end_time, &now) < 0);
+}
+
+struct defer_open_state {
+ struct smbXsrv_connection *xconn;
+ uint64_t mid;
+};
+
+static void defer_open_done(struct tevent_req *req);
+
+/**
+ * Defer an open and watch a locking.tdb record
+ *
+ * This defers an open that gets rescheduled once the locking.tdb record watch
+ * is triggered by a change to the record.
+ *
+ * It is used to defer opens that triggered an oplock break and for the SMB1
+ * sharing violation delay.
+ **/
+static void defer_open(struct share_mode_lock *lck,
+ struct timeval timeout,
+ struct smb_request *req,
+ struct file_id id)
+{
+ struct deferred_open_record *open_rec = NULL;
+ struct timeval abs_timeout;
+ struct defer_open_state *watch_state;
+ struct tevent_req *watch_req;
+ struct timeval_buf tvbuf1, tvbuf2;
+ struct file_id_buf fbuf;
+ bool ok;
+
+ abs_timeout = timeval_sum(&req->request_time, &timeout);
+
+ DBG_DEBUG("request time [%s] timeout [%s] mid [%" PRIu64 "] "
+ "file_id [%s]\n",
+ timeval_str_buf(&req->request_time, false, true, &tvbuf1),
+ timeval_str_buf(&abs_timeout, false, true, &tvbuf2),
+ req->mid,
+ file_id_str_buf(id, &fbuf));
+
+ open_rec = talloc_zero(NULL, struct deferred_open_record);
+ if (open_rec == NULL) {
+ TALLOC_FREE(lck);
+ exit_server("talloc failed");
+ }
+
+ watch_state = talloc(open_rec, struct defer_open_state);
+ if (watch_state == NULL) {
+ exit_server("talloc failed");
+ }
+ watch_state->xconn = req->xconn;
+ watch_state->mid = req->mid;
+
+ DBG_DEBUG("deferring mid %" PRIu64 "\n", req->mid);
+
+ watch_req = share_mode_watch_send(
+ watch_state,
+ req->sconn->ev_ctx,
+ lck,
+ (struct server_id){0});
+ if (watch_req == NULL) {
+ exit_server("Could not watch share mode record");
+ }
+ tevent_req_set_callback(watch_req, defer_open_done, watch_state);
+
+ ok = tevent_req_set_endtime(watch_req, req->sconn->ev_ctx, abs_timeout);
+ if (!ok) {
+ exit_server("tevent_req_set_endtime failed");
+ }
+
+ ok = push_deferred_open_message_smb(req, timeout, id, open_rec);
+ if (!ok) {
+ TALLOC_FREE(lck);
+ exit_server("push_deferred_open_message_smb failed");
+ }
+}
+
+static void defer_open_done(struct tevent_req *req)
+{
+ struct defer_open_state *state = tevent_req_callback_data(
+ req, struct defer_open_state);
+ NTSTATUS status;
+ bool ret;
+
+ status = share_mode_watch_recv(req, NULL, NULL);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("dbwrap_watched_watch_recv returned %s\n",
+ nt_errstr(status)));
+ /*
+ * Even if it failed, retry anyway. TODO: We need a way to
+ * tell a re-scheduled open about that error.
+ */
+ }
+
+ DEBUG(10, ("scheduling mid %llu\n", (unsigned long long)state->mid));
+
+ ret = schedule_deferred_open_message_smb(state->xconn, state->mid);
+ SMB_ASSERT(ret);
+ TALLOC_FREE(state);
+}
+
+/**
+ * Actually attempt the kernel oplock polling open.
+ */
+
+static void poll_open_fn(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data)
+{
+ struct deferred_open_record *open_rec = talloc_get_type_abort(
+ private_data, struct deferred_open_record);
+ bool ok;
+
+ TALLOC_FREE(open_rec->watch_req);
+
+ ok = schedule_deferred_open_message_smb(
+ open_rec->xconn, open_rec->mid);
+ if (!ok) {
+ exit_server("schedule_deferred_open_message_smb failed");
+ }
+ DBG_DEBUG("timer fired. Retrying open !\n");
+}
+
+static void poll_open_done(struct tevent_req *subreq);
+
+struct poll_open_setup_watcher_state {
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev_ctx;
+ struct tevent_req *watch_req;
+};
+
+static void poll_open_setup_watcher_fn(struct share_mode_lock *lck,
+ void *private_data)
+{
+ struct poll_open_setup_watcher_state *state =
+ (struct poll_open_setup_watcher_state *)private_data;
+
+ if (!validate_oplock_types(lck)) {
+ smb_panic("validate_oplock_types failed");
+ }
+
+ state->watch_req = share_mode_watch_send(
+ state->mem_ctx,
+ state->ev_ctx,
+ lck,
+ (struct server_id) {0});
+ if (state->watch_req == NULL) {
+ DBG_WARNING("share_mode_watch_send failed\n");
+ return;
+ }
+}
+
+/**
+ * Reschedule an open for 1 second from now, if not timed out.
+ **/
+static bool setup_poll_open(
+ struct smb_request *req,
+ const struct file_id *id,
+ struct timeval max_timeout,
+ struct timeval interval)
+{
+ static struct file_id zero_id = {};
+ bool ok;
+ struct deferred_open_record *open_rec = NULL;
+ struct timeval endtime, next_interval;
+ struct file_id_buf ftmp;
+
+ if (request_timed_out(req, max_timeout)) {
+ return false;
+ }
+
+ open_rec = talloc_zero(NULL, struct deferred_open_record);
+ if (open_rec == NULL) {
+ DBG_WARNING("talloc failed\n");
+ return false;
+ }
+ open_rec->xconn = req->xconn;
+ open_rec->mid = req->mid;
+
+ /*
+ * Make sure open_rec->te does not come later than the
+ * request's maximum endtime.
+ */
+
+ endtime = timeval_sum(&req->request_time, &max_timeout);
+ next_interval = timeval_current_ofs(interval.tv_sec, interval.tv_usec);
+ next_interval = timeval_min(&endtime, &next_interval);
+
+ open_rec->te = tevent_add_timer(
+ req->sconn->ev_ctx,
+ open_rec,
+ next_interval,
+ poll_open_fn,
+ open_rec);
+ if (open_rec->te == NULL) {
+ DBG_WARNING("tevent_add_timer failed\n");
+ TALLOC_FREE(open_rec);
+ return false;
+ }
+
+ if (id != NULL) {
+ struct poll_open_setup_watcher_state wstate = {
+ .mem_ctx = open_rec,
+ .ev_ctx = req->sconn->ev_ctx,
+ };
+ NTSTATUS status;
+
+ status = share_mode_do_locked_vfs_denied(*id,
+ poll_open_setup_watcher_fn,
+ &wstate);
+ if (NT_STATUS_IS_OK(status)) {
+ if (wstate.watch_req == NULL) {
+ DBG_WARNING("share_mode_watch_send failed\n");
+ TALLOC_FREE(open_rec);
+ return false;
+ }
+ open_rec->watch_req = wstate.watch_req;
+ tevent_req_set_callback(open_rec->watch_req,
+ poll_open_done,
+ open_rec);
+ } else if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ DBG_WARNING("share_mode_do_locked_vfs_denied failed - %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(open_rec);
+ return false;
+ }
+ } else {
+ id = &zero_id;
+ }
+
+ ok = push_deferred_open_message_smb(req, max_timeout, *id, open_rec);
+ if (!ok) {
+ DBG_WARNING("push_deferred_open_message_smb failed\n");
+ TALLOC_FREE(open_rec);
+ return false;
+ }
+
+ DBG_DEBUG("poll request time [%s] mid [%" PRIu64 "] file_id [%s]\n",
+ timeval_string(talloc_tos(), &req->request_time, false),
+ req->mid,
+ file_id_str_buf(*id, &ftmp));
+
+ return true;
+}
+
+static void poll_open_done(struct tevent_req *subreq)
+{
+ struct deferred_open_record *open_rec = tevent_req_callback_data(
+ subreq, struct deferred_open_record);
+ NTSTATUS status;
+ bool ok;
+
+ status = share_mode_watch_recv(subreq, NULL, NULL);
+ TALLOC_FREE(subreq);
+ open_rec->watch_req = NULL;
+ TALLOC_FREE(open_rec->te);
+
+ DBG_DEBUG("dbwrap_watched_watch_recv returned %s\n",
+ nt_errstr(status));
+
+ ok = schedule_deferred_open_message_smb(
+ open_rec->xconn, open_rec->mid);
+ if (!ok) {
+ exit_server("schedule_deferred_open_message_smb failed");
+ }
+}
+
+bool defer_smb1_sharing_violation(struct smb_request *req)
+{
+ bool ok;
+ int timeout_usecs;
+
+ if (!lp_defer_sharing_violations()) {
+ return false;
+ }
+
+ /*
+ * Try every 200msec up to (by default) one second. To be
+ * precise, according to behaviour note <247> in [MS-CIFS],
+ * the server tries 5 times. But up to one second should be
+ * close enough.
+ */
+
+ timeout_usecs = lp_parm_int(
+ SNUM(req->conn),
+ "smbd",
+ "sharedelay",
+ SHARING_VIOLATION_USEC_WAIT);
+
+ ok = setup_poll_open(
+ req,
+ NULL,
+ (struct timeval) { .tv_usec = timeout_usecs },
+ (struct timeval) { .tv_usec = 200000 });
+ return ok;
+}
+
+/****************************************************************************
+ On overwrite open ensure that the attributes match.
+****************************************************************************/
+
+static bool open_match_attributes(connection_struct *conn,
+ uint32_t old_dos_attr,
+ uint32_t new_dos_attr,
+ mode_t new_unx_mode,
+ mode_t *returned_unx_mode)
+{
+ uint32_t noarch_old_dos_attr, noarch_new_dos_attr;
+
+ noarch_old_dos_attr = (old_dos_attr & ~FILE_ATTRIBUTE_ARCHIVE);
+ noarch_new_dos_attr = (new_dos_attr & ~FILE_ATTRIBUTE_ARCHIVE);
+
+ if((noarch_old_dos_attr == 0 && noarch_new_dos_attr != 0) ||
+ (noarch_old_dos_attr != 0 && ((noarch_old_dos_attr & noarch_new_dos_attr) == noarch_old_dos_attr))) {
+ *returned_unx_mode = new_unx_mode;
+ } else {
+ *returned_unx_mode = (mode_t)0;
+ }
+
+ DEBUG(10,("open_match_attributes: old_dos_attr = 0x%x, "
+ "new_dos_attr = 0x%x "
+ "returned_unx_mode = 0%o\n",
+ (unsigned int)old_dos_attr,
+ (unsigned int)new_dos_attr,
+ (unsigned int)*returned_unx_mode ));
+
+ /* If we're mapping SYSTEM and HIDDEN ensure they match. */
+ if (lp_map_system(SNUM(conn)) || lp_store_dos_attributes(SNUM(conn))) {
+ if ((old_dos_attr & FILE_ATTRIBUTE_SYSTEM) &&
+ !(new_dos_attr & FILE_ATTRIBUTE_SYSTEM)) {
+ return False;
+ }
+ }
+ if (lp_map_hidden(SNUM(conn)) || lp_store_dos_attributes(SNUM(conn))) {
+ if ((old_dos_attr & FILE_ATTRIBUTE_HIDDEN) &&
+ !(new_dos_attr & FILE_ATTRIBUTE_HIDDEN)) {
+ return False;
+ }
+ }
+ return True;
+}
+
+static void schedule_defer_open(struct share_mode_lock *lck,
+ struct file_id id,
+ struct smb_request *req)
+{
+ /* This is a relative time, added to the absolute
+ request_time value to get the absolute timeout time.
+ Note that if this is the second or greater time we enter
+ this codepath for this particular request mid then
+ request_time is left as the absolute time of the *first*
+ time this request mid was processed. This is what allows
+ the request to eventually time out. */
+
+ struct timeval timeout;
+
+ /* Normally the smbd we asked should respond within
+ * OPLOCK_BREAK_TIMEOUT seconds regardless of whether
+ * the client did, give twice the timeout as a safety
+ * measure here in case the other smbd is stuck
+ * somewhere else. */
+
+ timeout = timeval_set(OPLOCK_BREAK_TIMEOUT*2, 0);
+
+ if (request_timed_out(req, timeout)) {
+ return;
+ }
+
+ defer_open(lck, timeout, req, id);
+}
+
+/****************************************************************************
+ Reschedule an open call that went asynchronous.
+****************************************************************************/
+
+static void schedule_async_open_timer(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data)
+{
+ exit_server("async open timeout");
+}
+
+static void schedule_async_open(struct smb_request *req)
+{
+ struct deferred_open_record *open_rec = NULL;
+ struct timeval timeout = timeval_set(20, 0);
+ bool ok;
+
+ if (request_timed_out(req, timeout)) {
+ return;
+ }
+
+ open_rec = talloc_zero(NULL, struct deferred_open_record);
+ if (open_rec == NULL) {
+ exit_server("deferred_open_record_create failed");
+ }
+ open_rec->async_open = true;
+
+ ok = push_deferred_open_message_smb(
+ req, timeout, (struct file_id){0}, open_rec);
+ if (!ok) {
+ exit_server("push_deferred_open_message_smb failed");
+ }
+
+ open_rec->te = tevent_add_timer(req->sconn->ev_ctx,
+ req,
+ timeval_current_ofs(20, 0),
+ schedule_async_open_timer,
+ open_rec);
+ if (open_rec->te == NULL) {
+ exit_server("tevent_add_timer failed");
+ }
+}
+
+static NTSTATUS check_and_store_share_mode(
+ struct files_struct *fsp,
+ struct smb_request *req,
+ struct share_mode_lock *lck,
+ uint32_t create_disposition,
+ uint32_t access_mask,
+ uint32_t share_access,
+ int oplock_request,
+ const struct smb2_lease *lease,
+ bool first_open_attempt)
+{
+ NTSTATUS status;
+ int oplock_type = NO_OPLOCK;
+ uint32_t granted_lease = 0;
+ const struct smb2_lease_key *lease_key = NULL;
+ bool delete_on_close;
+ bool ok;
+
+ /* Get the types we need to examine. */
+ if (!validate_oplock_types(lck)) {
+ smb_panic("validate_oplock_types failed");
+ }
+
+ delete_on_close = has_delete_on_close(lck, fsp->name_hash);
+ if (delete_on_close) {
+ return NT_STATUS_DELETE_PENDING;
+ }
+
+ status = handle_share_mode_lease(fsp,
+ lck,
+ create_disposition,
+ access_mask,
+ share_access,
+ oplock_request,
+ lease,
+ first_open_attempt,
+ &oplock_type,
+ &granted_lease);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
+ schedule_defer_open(lck, fsp->file_id, req);
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (oplock_type == LEASE_OPLOCK) {
+ lease_key = &lease->lease_key;
+ }
+
+ share_mode_flags_restrict(lck, access_mask, share_access, 0);
+
+ ok = set_share_mode(lck,
+ fsp,
+ get_current_uid(fsp->conn),
+ req ? req->mid : 0,
+ oplock_type,
+ lease_key,
+ share_access,
+ access_mask);
+ if (!ok) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (oplock_type == LEASE_OPLOCK) {
+ status = grant_fsp_lease(fsp, lck, lease, granted_lease);
+ if (!NT_STATUS_IS_OK(status)) {
+ del_share_mode(lck, fsp);
+ return status;
+ }
+
+ DBG_DEBUG("lease_state=%d\n", fsp->lease->lease.lease_state);
+ }
+
+ fsp->oplock_type = oplock_type;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Work out what access_mask to use from what the client sent us.
+****************************************************************************/
+
+static NTSTATUS smbd_calculate_maximum_allowed_access_fsp(
+ struct files_struct *dirfsp,
+ struct files_struct *fsp,
+ bool use_privs,
+ uint32_t *p_access_mask)
+{
+ struct security_descriptor *sd = NULL;
+ uint32_t access_granted = 0;
+ uint32_t dosattrs;
+ NTSTATUS status;
+
+ /* Cope with symlinks */
+ if (fsp == NULL || fsp_get_pathref_fd(fsp) == -1) {
+ *p_access_mask = FILE_GENERIC_ALL;
+ return NT_STATUS_OK;
+ }
+
+ /* Cope with fake/printer fsp's. */
+ if (fsp->fake_file_handle != NULL || fsp->print_file != NULL) {
+ *p_access_mask = FILE_GENERIC_ALL;
+ return NT_STATUS_OK;
+ }
+
+ if (!use_privs && (get_current_uid(fsp->conn) == (uid_t)0)) {
+ *p_access_mask |= FILE_GENERIC_ALL;
+ return NT_STATUS_OK;
+ }
+
+ status = SMB_VFS_FGET_NT_ACL(metadata_fsp(fsp),
+ (SECINFO_OWNER |
+ SECINFO_GROUP |
+ SECINFO_DACL),
+ talloc_tos(),
+ &sd);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ /*
+ * File did not exist
+ */
+ *p_access_mask = FILE_GENERIC_ALL;
+ return NT_STATUS_OK;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Could not get acl on file %s: %s\n",
+ fsp_str_dbg(fsp),
+ nt_errstr(status));
+ return status;
+ }
+
+ /*
+ * If we can access the path to this file, by
+ * default we have FILE_READ_ATTRIBUTES from the
+ * containing directory. See the section:
+ * "Algorithm to Check Access to an Existing File"
+ * in MS-FSA.pdf.
+ *
+ * se_file_access_check()
+ * also takes care of owner WRITE_DAC and READ_CONTROL.
+ */
+ status = se_file_access_check(sd,
+ get_current_nttok(fsp->conn),
+ use_privs,
+ (*p_access_mask & ~FILE_READ_ATTRIBUTES),
+ &access_granted);
+
+ TALLOC_FREE(sd);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Status %s on file %s: "
+ "when calculating maximum access\n",
+ nt_errstr(status),
+ fsp_str_dbg(fsp));
+ return status;
+ }
+
+ *p_access_mask = (access_granted | FILE_READ_ATTRIBUTES);
+
+ if (!(access_granted & DELETE_ACCESS)) {
+ if (can_delete_file_in_directory(fsp->conn,
+ dirfsp,
+ fsp->fsp_name)) {
+ *p_access_mask |= DELETE_ACCESS;
+ }
+ }
+
+ dosattrs = fdos_mode(fsp);
+ if ((dosattrs & FILE_ATTRIBUTE_READONLY) || !CAN_WRITE(fsp->conn)) {
+ *p_access_mask &= ~(FILE_GENERIC_WRITE | DELETE_ACCESS);
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smbd_calculate_access_mask_fsp(struct files_struct *dirfsp,
+ struct files_struct *fsp,
+ bool use_privs,
+ uint32_t access_mask,
+ uint32_t *access_mask_out)
+{
+ NTSTATUS status;
+ uint32_t orig_access_mask = access_mask;
+ uint32_t rejected_share_access;
+
+ if (access_mask & SEC_MASK_INVALID) {
+ DBG_DEBUG("access_mask [%8x] contains invalid bits\n",
+ access_mask);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /*
+ * Convert GENERIC bits to specific bits.
+ */
+
+ se_map_generic(&access_mask, &file_generic_mapping);
+
+ /* Calculate MAXIMUM_ALLOWED_ACCESS if requested. */
+ if (access_mask & MAXIMUM_ALLOWED_ACCESS) {
+
+ status = smbd_calculate_maximum_allowed_access_fsp(
+ dirfsp,
+ fsp,
+ use_privs,
+ &access_mask);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ access_mask &= fsp->conn->share_access;
+ }
+
+ rejected_share_access = access_mask & ~(fsp->conn->share_access);
+
+ if (rejected_share_access) {
+ DBG_INFO("Access denied on file %s: "
+ "rejected by share access mask[0x%08X] "
+ "orig[0x%08X] mapped[0x%08X] reject[0x%08X]\n",
+ fsp_str_dbg(fsp),
+ fsp->conn->share_access,
+ orig_access_mask, access_mask,
+ rejected_share_access);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ *access_mask_out = access_mask;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Remove the deferred open entry under lock.
+****************************************************************************/
+
+/****************************************************************************
+ Return true if this is a state pointer to an asynchronous create.
+****************************************************************************/
+
+bool is_deferred_open_async(const struct deferred_open_record *rec)
+{
+ return rec->async_open;
+}
+
+static bool clear_ads(uint32_t create_disposition)
+{
+ bool ret = false;
+
+ switch (create_disposition) {
+ case FILE_SUPERSEDE:
+ case FILE_OVERWRITE_IF:
+ case FILE_OVERWRITE:
+ ret = true;
+ break;
+ default:
+ break;
+ }
+ return ret;
+}
+
+static int disposition_to_open_flags(uint32_t create_disposition)
+{
+ int ret = 0;
+
+ /*
+ * Currently we're using FILE_SUPERSEDE as the same as
+ * FILE_OVERWRITE_IF but they really are
+ * different. FILE_SUPERSEDE deletes an existing file
+ * (requiring delete access) then recreates it.
+ */
+
+ switch (create_disposition) {
+ case FILE_SUPERSEDE:
+ case FILE_OVERWRITE_IF:
+ /*
+ * If file exists replace/overwrite. If file doesn't
+ * exist create.
+ */
+ ret = O_CREAT|O_TRUNC;
+ break;
+
+ case FILE_OPEN:
+ /*
+ * If file exists open. If file doesn't exist error.
+ */
+ ret = 0;
+ break;
+
+ case FILE_OVERWRITE:
+ /*
+ * If file exists overwrite. If file doesn't exist
+ * error.
+ */
+ ret = O_TRUNC;
+ break;
+
+ case FILE_CREATE:
+ /*
+ * If file exists error. If file doesn't exist create.
+ */
+ ret = O_CREAT|O_EXCL;
+ break;
+
+ case FILE_OPEN_IF:
+ /*
+ * If file exists open. If file doesn't exist create.
+ */
+ ret = O_CREAT;
+ break;
+ }
+ return ret;
+}
+
+static int calculate_open_access_flags(uint32_t access_mask,
+ uint32_t private_flags,
+ NTTIME twrp)
+{
+ bool need_write, need_read;
+
+ /*
+ * Note that we ignore the append flag as append does not
+ * mean the same thing under DOS and Unix.
+ */
+
+ if (twrp != 0) {
+ /*
+ * Pave over the user requested mode and force O_RDONLY for the
+ * file handle. Windows allows opening a VSS file with O_RDWR,
+ * even though actual writes on the handle will fail.
+ */
+ return O_RDONLY;
+ }
+
+ need_write = (access_mask & (FILE_WRITE_DATA | FILE_APPEND_DATA));
+ if (!need_write) {
+ return O_RDONLY;
+ }
+
+ /* DENY_DOS opens are always underlying read-write on the
+ file handle, no matter what the requested access mask
+ says. */
+
+ need_read =
+ ((private_flags & NTCREATEX_FLAG_DENY_DOS) ||
+ access_mask & (FILE_READ_ATTRIBUTES|FILE_READ_DATA|
+ FILE_READ_EA|FILE_EXECUTE));
+
+ if (!need_read) {
+ return O_WRONLY;
+ }
+ return O_RDWR;
+}
+
+struct open_ntcreate_lock_state {
+ struct share_mode_entry_prepare_state prepare_state;
+ struct files_struct *fsp;
+ const char *object_type;
+ struct smb_request *req;
+ uint32_t create_disposition;
+ uint32_t access_mask;
+ uint32_t share_access;
+ int oplock_request;
+ const struct smb2_lease *lease;
+ bool first_open_attempt;
+ bool keep_locked;
+ NTSTATUS status;
+ struct timespec write_time;
+ share_mode_entry_prepare_unlock_fn_t cleanup_fn;
+};
+
+static void open_ntcreate_lock_add_entry(struct share_mode_lock *lck,
+ bool *keep_locked,
+ void *private_data)
+{
+ struct open_ntcreate_lock_state *state =
+ (struct open_ntcreate_lock_state *)private_data;
+
+ /*
+ * By default drop the g_lock again if we leave the
+ * tdb chainlock.
+ */
+ *keep_locked = false;
+
+ state->status = check_and_store_share_mode(state->fsp,
+ state->req,
+ lck,
+ state->create_disposition,
+ state->access_mask,
+ state->share_access,
+ state->oplock_request,
+ state->lease,
+ state->first_open_attempt);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ return;
+ }
+
+ state->write_time = get_share_mode_write_time(lck);
+
+ /*
+ * keep the g_lock while existing the tdb chainlock,
+ * we we're asked to, which mean we'll keep
+ * the share_mode_lock during object creation,
+ * or setting delete on close.
+ */
+ *keep_locked = state->keep_locked;
+}
+
+static void open_ntcreate_lock_cleanup_oplock(struct share_mode_lock *lck,
+ void *private_data)
+{
+ struct open_ntcreate_lock_state *state =
+ (struct open_ntcreate_lock_state *)private_data;
+ bool ok;
+
+ ok = remove_share_oplock(lck, state->fsp);
+ if (!ok) {
+ DBG_ERR("Could not remove oplock for %s %s\n",
+ state->object_type, fsp_str_dbg(state->fsp));
+ }
+}
+
+static void open_ntcreate_lock_cleanup_entry(struct share_mode_lock *lck,
+ void *private_data)
+{
+ struct open_ntcreate_lock_state *state =
+ (struct open_ntcreate_lock_state *)private_data;
+ bool ok;
+
+ ok = del_share_mode(lck, state->fsp);
+ if (!ok) {
+ DBG_ERR("Could not delete share entry for %s %s\n",
+ state->object_type, fsp_str_dbg(state->fsp));
+ }
+}
+
+static void possibly_set_archive(struct connection_struct *conn,
+ struct files_struct *fsp,
+ struct smb_filename *smb_fname,
+ struct smb_filename *parent_dir_fname,
+ int info,
+ uint32_t dosattrs,
+ mode_t *unx_mode)
+{
+ bool set_archive = false;
+ int ret;
+
+ if (info == FILE_WAS_OPENED) {
+ return;
+ }
+
+ /* Overwritten files should be initially set as archive */
+ if ((info == FILE_WAS_OVERWRITTEN && lp_map_archive(SNUM(conn)))) {
+ set_archive = true;
+ } else if (lp_store_dos_attributes(SNUM(conn))) {
+ set_archive = true;
+ }
+ if (!set_archive) {
+ return;
+ }
+
+ ret = file_set_dosmode(conn,
+ smb_fname,
+ dosattrs | FILE_ATTRIBUTE_ARCHIVE,
+ parent_dir_fname,
+ true);
+ if (ret != 0) {
+ return;
+ }
+ *unx_mode = smb_fname->st.st_ex_mode;
+}
+
+/****************************************************************************
+ Open a file with a share mode. Passed in an already created files_struct *.
+****************************************************************************/
+
+static NTSTATUS open_file_ntcreate(connection_struct *conn,
+ struct smb_request *req,
+ uint32_t access_mask, /* access bits (FILE_READ_DATA etc.) */
+ uint32_t share_access, /* share constants (FILE_SHARE_READ etc) */
+ uint32_t create_disposition, /* FILE_OPEN_IF etc. */
+ uint32_t create_options, /* options such as delete on close. */
+ uint32_t new_dos_attributes, /* attributes used for new file. */
+ int oplock_request, /* internal Samba oplock codes. */
+ const struct smb2_lease *lease,
+ /* Information (FILE_EXISTS etc.) */
+ uint32_t private_flags, /* Samba specific flags. */
+ struct smb_filename *parent_dir_fname, /* parent. */
+ struct smb_filename *smb_fname_atname, /* atname relative to parent. */
+ int *pinfo,
+ files_struct *fsp)
+{
+ struct smb_filename *smb_fname = fsp->fsp_name;
+ int flags=0;
+ bool file_existed = VALID_STAT(smb_fname->st);
+ bool def_acl = False;
+ bool posix_open = False;
+ bool new_file_created = False;
+ bool first_open_attempt = true;
+ bool is_twrp = (smb_fname_atname->twrp != 0);
+ NTSTATUS fsp_open = NT_STATUS_ACCESS_DENIED;
+ mode_t new_unx_mode = (mode_t)0;
+ mode_t unx_mode = (mode_t)0;
+ int info;
+ uint32_t existing_dos_attributes = 0;
+ struct open_ntcreate_lock_state lck_state = {};
+ bool keep_locked = false;
+ uint32_t open_access_mask = access_mask;
+ NTSTATUS status;
+ SMB_STRUCT_STAT saved_stat = smb_fname->st;
+ struct timespec old_write_time;
+ bool setup_poll = false;
+ NTSTATUS ulstatus;
+
+ if (conn->printer) {
+ /*
+ * Printers are handled completely differently.
+ * Most of the passed parameters are ignored.
+ */
+
+ if (pinfo) {
+ *pinfo = FILE_WAS_CREATED;
+ }
+
+ DBG_DEBUG("printer open fname=%s\n",
+ smb_fname_str_dbg(smb_fname));
+
+ if (!req) {
+ DBG_ERR("printer open without an SMB request!\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ return print_spool_open(fsp, smb_fname->base_name,
+ req->vuid);
+ }
+
+ if (new_dos_attributes & FILE_FLAG_POSIX_SEMANTICS) {
+ posix_open = True;
+ unx_mode = (mode_t)(new_dos_attributes & ~FILE_FLAG_POSIX_SEMANTICS);
+ new_dos_attributes = 0;
+ } else {
+ /* Windows allows a new file to be created and
+ silently removes a FILE_ATTRIBUTE_DIRECTORY
+ sent by the client. Do the same. */
+
+ new_dos_attributes &= ~FILE_ATTRIBUTE_DIRECTORY;
+
+ /* We add FILE_ATTRIBUTE_ARCHIVE to this as this mode is only used if the file is
+ * created new. */
+ unx_mode = unix_mode(
+ conn,
+ new_dos_attributes | FILE_ATTRIBUTE_ARCHIVE,
+ smb_fname,
+ parent_dir_fname->fsp);
+ }
+
+ DEBUG(10, ("open_file_ntcreate: fname=%s, dos_attrs=0x%x "
+ "access_mask=0x%x share_access=0x%x "
+ "create_disposition = 0x%x create_options=0x%x "
+ "unix mode=0%o oplock_request=%d private_flags = 0x%x\n",
+ smb_fname_str_dbg(smb_fname), new_dos_attributes,
+ access_mask, share_access, create_disposition,
+ create_options, (unsigned int)unx_mode, oplock_request,
+ (unsigned int)private_flags));
+
+ if (req == NULL) {
+ /* Ensure req == NULL means INTERNAL_OPEN_ONLY */
+ SMB_ASSERT(oplock_request == INTERNAL_OPEN_ONLY);
+ } else {
+ /* And req != NULL means no INTERNAL_OPEN_ONLY */
+ SMB_ASSERT(((oplock_request & INTERNAL_OPEN_ONLY) == 0));
+ }
+
+ /*
+ * Only non-internal opens can be deferred at all
+ */
+
+ if (req) {
+ struct deferred_open_record *open_rec;
+ if (get_deferred_open_message_state(req, NULL, &open_rec)) {
+
+ /* If it was an async create retry, the file
+ didn't exist. */
+
+ if (is_deferred_open_async(open_rec)) {
+ SET_STAT_INVALID(smb_fname->st);
+ file_existed = false;
+ }
+
+ /* Ensure we don't reprocess this message. */
+ remove_deferred_open_message_smb(req->xconn, req->mid);
+
+ first_open_attempt = false;
+ }
+ }
+
+ if (!posix_open) {
+ new_dos_attributes &= SAMBA_ATTRIBUTES_MASK;
+ if (file_existed) {
+ /*
+ * Only use stored DOS attributes for checks
+ * against requested attributes (below via
+ * open_match_attributes()), cf bug #11992
+ * for details. -slow
+ */
+ uint32_t attr = 0;
+
+ status = SMB_VFS_FGET_DOS_ATTRIBUTES(
+ conn,
+ metadata_fsp(smb_fname->fsp),
+ &attr);
+ if (NT_STATUS_IS_OK(status)) {
+ existing_dos_attributes = attr;
+ }
+ }
+ }
+
+ /* ignore any oplock requests if oplocks are disabled */
+ if (!lp_oplocks(SNUM(conn)) ||
+ IS_VETO_OPLOCK_PATH(conn, smb_fname->base_name)) {
+ /* Mask off everything except the private Samba bits. */
+ oplock_request &= SAMBA_PRIVATE_OPLOCK_MASK;
+ }
+
+ /* this is for OS/2 long file names - say we don't support them */
+ if (req != NULL && !req->posix_pathnames &&
+ strstr(smb_fname->base_name,".+,;=[].")) {
+ /* OS/2 Workplace shell fix may be main code stream in a later
+ * release. */
+ DEBUG(5,("open_file_ntcreate: OS/2 long filenames are not "
+ "supported.\n"));
+ if (use_nt_status()) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ return NT_STATUS_DOS(ERRDOS, ERRcannotopen);
+ }
+
+ switch( create_disposition ) {
+ case FILE_OPEN:
+ /* If file exists open. If file doesn't exist error. */
+ if (!file_existed) {
+ DEBUG(5,("open_file_ntcreate: FILE_OPEN "
+ "requested for file %s and file "
+ "doesn't exist.\n",
+ smb_fname_str_dbg(smb_fname)));
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ break;
+
+ case FILE_OVERWRITE:
+ /* If file exists overwrite. If file doesn't exist
+ * error. */
+ if (!file_existed) {
+ DEBUG(5,("open_file_ntcreate: FILE_OVERWRITE "
+ "requested for file %s and file "
+ "doesn't exist.\n",
+ smb_fname_str_dbg(smb_fname) ));
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ if (is_twrp) {
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+ break;
+
+ case FILE_CREATE:
+ /* If file exists error. If file doesn't exist
+ * create. */
+ if (file_existed) {
+ DEBUG(5,("open_file_ntcreate: FILE_CREATE "
+ "requested for file %s and file "
+ "already exists.\n",
+ smb_fname_str_dbg(smb_fname)));
+ if (S_ISDIR(smb_fname->st.st_ex_mode)) {
+ return NT_STATUS_FILE_IS_A_DIRECTORY;
+ }
+ return NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+ if (is_twrp) {
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+ break;
+
+ case FILE_SUPERSEDE:
+ case FILE_OVERWRITE_IF:
+ if (is_twrp) {
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+ break;
+ case FILE_OPEN_IF:
+ if (is_twrp) {
+ if (!file_existed) {
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+ create_disposition = FILE_OPEN;
+ }
+ break;
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ flags = disposition_to_open_flags(create_disposition);
+
+ /* We only care about matching attributes on file exists and
+ * overwrite. */
+
+ if (!posix_open && file_existed &&
+ ((create_disposition == FILE_OVERWRITE) ||
+ (create_disposition == FILE_OVERWRITE_IF))) {
+ if (!open_match_attributes(conn, existing_dos_attributes,
+ new_dos_attributes,
+ unx_mode, &new_unx_mode)) {
+ DEBUG(5,("open_file_ntcreate: attributes mismatch "
+ "for file %s (%x %x) (0%o, 0%o)\n",
+ smb_fname_str_dbg(smb_fname),
+ existing_dos_attributes,
+ new_dos_attributes,
+ (unsigned int)smb_fname->st.st_ex_mode,
+ (unsigned int)unx_mode ));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+
+ status = smbd_calculate_access_mask_fsp(parent_dir_fname->fsp,
+ smb_fname->fsp,
+ false,
+ access_mask,
+ &access_mask);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("smbd_calculate_access_mask_fsp "
+ "on file %s returned %s\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status));
+ return status;
+ }
+
+ open_access_mask = access_mask;
+
+ if (flags & O_TRUNC) {
+ open_access_mask |= FILE_WRITE_DATA; /* This will cause oplock breaks. */
+ }
+
+ if (file_existed) {
+ /*
+ * stat opens on existing files don't get oplocks.
+ * They can get leases.
+ *
+ * Note that we check for stat open on the *open_access_mask*,
+ * i.e. the access mask we actually used to do the open,
+ * not the one the client asked for (which is in
+ * fsp->access_mask). This is due to the fact that
+ * FILE_OVERWRITE and FILE_OVERWRITE_IF add in O_TRUNC,
+ * which adds FILE_WRITE_DATA to open_access_mask.
+ */
+ if (is_oplock_stat_open(open_access_mask) && lease == NULL) {
+ oplock_request = NO_OPLOCK;
+ }
+ }
+
+ DEBUG(10, ("open_file_ntcreate: fname=%s, after mapping "
+ "access_mask=0x%x\n", smb_fname_str_dbg(smb_fname),
+ access_mask));
+
+ /*
+ * Note that we ignore the append flag as append does not
+ * mean the same thing under DOS and Unix.
+ */
+
+ flags |= calculate_open_access_flags(access_mask,
+ private_flags,
+ smb_fname->twrp);
+
+ /*
+ * Currently we only look at FILE_WRITE_THROUGH for create options.
+ */
+
+#if defined(O_SYNC)
+ if ((create_options & FILE_WRITE_THROUGH) && lp_strict_sync(SNUM(conn))) {
+ flags |= O_SYNC;
+ }
+#endif /* O_SYNC */
+
+ if (posix_open && (access_mask & FILE_APPEND_DATA)) {
+ flags |= O_APPEND;
+ }
+
+ if (!posix_open && !CAN_WRITE(conn)) {
+ /*
+ * We should really return a permission denied error if either
+ * O_CREAT or O_TRUNC are set, but for compatibility with
+ * older versions of Samba we just AND them out.
+ */
+ flags &= ~(O_CREAT | O_TRUNC);
+ }
+
+ /*
+ * With kernel oplocks the open breaking an oplock
+ * blocks until the oplock holder has given up the
+ * oplock or closed the file. We prevent this by always
+ * trying to open the file with O_NONBLOCK (see "man
+ * fcntl" on Linux).
+ *
+ * If a process that doesn't use the smbd open files
+ * database or communication methods holds a kernel
+ * oplock we must periodically poll for available open
+ * using O_NONBLOCK.
+ */
+ flags |= O_NONBLOCK;
+
+ /*
+ * Ensure we can't write on a read-only share or file.
+ */
+
+ if (((flags & O_ACCMODE) != O_RDONLY) && file_existed &&
+ (!CAN_WRITE(conn) ||
+ (existing_dos_attributes & FILE_ATTRIBUTE_READONLY))) {
+ DEBUG(5,("open_file_ntcreate: write access requested for "
+ "file %s on read only %s\n",
+ smb_fname_str_dbg(smb_fname),
+ !CAN_WRITE(conn) ? "share" : "file" ));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (VALID_STAT(smb_fname->st)) {
+ /*
+ * Only try and create a file id before open
+ * for an existing file. For a file being created
+ * this won't do anything useful until the file
+ * exists and has a valid stat struct.
+ */
+ fsp->file_id = vfs_file_id_from_sbuf(conn, &smb_fname->st);
+ }
+ fh_set_private_options(fsp->fh, private_flags);
+ fsp->access_mask = open_access_mask; /* We change this to the
+ * requested access_mask after
+ * the open is done. */
+ if (posix_open) {
+ fsp->posix_flags |= FSP_POSIX_FLAGS_ALL;
+ }
+
+ if ((create_options & FILE_DELETE_ON_CLOSE) && (flags & O_CREAT) &&
+ !file_existed) {
+ /* Delete on close semantics for new files. */
+ status = can_set_delete_on_close(fsp,
+ new_dos_attributes);
+ if (!NT_STATUS_IS_OK(status)) {
+ fd_close(fsp);
+ return status;
+ }
+ }
+
+ /*
+ * Ensure we pay attention to default ACLs on directories if required.
+ */
+
+ if ((flags & O_CREAT) && lp_inherit_acls(SNUM(conn)) &&
+ (def_acl = directory_has_default_acl_fsp(parent_dir_fname->fsp))) {
+ unx_mode = (0777 & lp_create_mask(SNUM(conn)));
+ }
+
+ DEBUG(4,
+ ("calling open_file with flags=0x%X mode=0%o, "
+ "access_mask = 0x%x, open_access_mask = 0x%x\n",
+ (unsigned int)flags,
+ (unsigned int)unx_mode,
+ (unsigned int)access_mask,
+ (unsigned int)open_access_mask));
+
+ {
+ struct vfs_open_how how = {
+ .flags = flags,
+ .mode = unx_mode,
+ };
+
+ if (create_options & FILE_OPEN_FOR_BACKUP_INTENT) {
+ how.resolve |= VFS_OPEN_HOW_WITH_BACKUP_INTENT;
+ }
+
+ fsp_open = open_file(req,
+ parent_dir_fname->fsp,
+ smb_fname_atname,
+ fsp,
+ &how,
+ access_mask,
+ open_access_mask,
+ private_flags,
+ &new_file_created);
+ }
+ if (NT_STATUS_EQUAL(fsp_open, NT_STATUS_NETWORK_BUSY)) {
+ if (file_existed && S_ISFIFO(fsp->fsp_name->st.st_ex_mode)) {
+ DEBUG(10, ("FIFO busy\n"));
+ return NT_STATUS_NETWORK_BUSY;
+ }
+ if (req == NULL) {
+ DEBUG(10, ("Internal open busy\n"));
+ return NT_STATUS_NETWORK_BUSY;
+ }
+ /*
+ * This handles the kernel oplock case:
+ *
+ * the file has an active kernel oplock and the open() returned
+ * EWOULDBLOCK/EAGAIN which maps to NETWORK_BUSY.
+ *
+ * "Samba locking.tdb oplocks" are handled below after acquiring
+ * the sharemode lock with get_share_mode_lock().
+ */
+ setup_poll = true;
+ }
+
+ if (NT_STATUS_EQUAL(fsp_open, NT_STATUS_RETRY)) {
+ /*
+ * EINTR from the open(2) syscall. Just setup a retry
+ * in a bit. We can't use the sys_write() tight retry
+ * loop here, as we might have to actually deal with
+ * lease-break signals to avoid a deadlock.
+ */
+ setup_poll = true;
+ }
+
+ if (setup_poll) {
+ /*
+ * Retry once a second. If there's a share_mode_lock
+ * around, also wait for it in case it was smbd
+ * holding that kernel oplock that can quickly tell us
+ * the oplock got removed.
+ */
+
+ setup_poll_open(
+ req,
+ &fsp->file_id,
+ timeval_set(OPLOCK_BREAK_TIMEOUT*2, 0),
+ timeval_set(1, 0));
+
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+
+ if (!NT_STATUS_IS_OK(fsp_open)) {
+ bool wait_for_aio = NT_STATUS_EQUAL(
+ fsp_open, NT_STATUS_MORE_PROCESSING_REQUIRED);
+ if (wait_for_aio) {
+ schedule_async_open(req);
+ }
+ return fsp_open;
+ }
+
+ if (new_file_created) {
+ /*
+ * As we atomically create using O_CREAT|O_EXCL,
+ * then if new_file_created is true, then
+ * file_existed *MUST* have been false (even
+ * if the file was previously detected as being
+ * there).
+ */
+ file_existed = false;
+ }
+
+ if (file_existed && !check_same_dev_ino(&saved_stat, &smb_fname->st)) {
+ /*
+ * The file did exist, but some other (local or NFS)
+ * process either renamed/unlinked and re-created the
+ * file with different dev/ino after we walked the path,
+ * but before we did the open. We could retry the
+ * open but it's a rare enough case it's easier to
+ * just fail the open to prevent creating any problems
+ * in the open file db having the wrong dev/ino key.
+ */
+ fd_close(fsp);
+ DBG_WARNING("file %s - dev/ino mismatch. "
+ "Old (dev=%ju, ino=%ju). "
+ "New (dev=%ju, ino=%ju). Failing open "
+ "with NT_STATUS_ACCESS_DENIED.\n",
+ smb_fname_str_dbg(smb_fname),
+ (uintmax_t)saved_stat.st_ex_dev,
+ (uintmax_t)saved_stat.st_ex_ino,
+ (uintmax_t)smb_fname->st.st_ex_dev,
+ (uintmax_t)smb_fname->st.st_ex_ino);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ old_write_time = smb_fname->st.st_ex_mtime;
+
+ /*
+ * Deal with the race condition where two smbd's detect the
+ * file doesn't exist and do the create at the same time. One
+ * of them will win and set a share mode, the other (ie. this
+ * one) should check if the requested share mode for this
+ * create is allowed.
+ */
+
+ /*
+ * Now the file exists and fsp is successfully opened,
+ * fsp->dev and fsp->inode are valid and should replace the
+ * dev=0,inode=0 from a non existent file. Spotted by
+ * Nadav Danieli <nadavd@exanet.com>. JRA.
+ */
+
+ if (new_file_created) {
+ info = FILE_WAS_CREATED;
+ } else {
+ if (flags & O_TRUNC) {
+ info = FILE_WAS_OVERWRITTEN;
+ } else {
+ info = FILE_WAS_OPENED;
+ }
+ }
+
+ /*
+ * If we created a new file, overwrite an existing one
+ * or going to delete it later, we should keep
+ * the share_mode_lock (g_lock) until we call
+ * share_mode_entry_prepare_unlock()
+ */
+ if (info != FILE_WAS_OPENED) {
+ keep_locked = true;
+ } else if (create_options & FILE_DELETE_ON_CLOSE) {
+ keep_locked = true;
+ }
+
+ lck_state = (struct open_ntcreate_lock_state) {
+ .fsp = fsp,
+ .object_type = "file",
+ .req = req,
+ .create_disposition = create_disposition,
+ .access_mask = access_mask,
+ .share_access = share_access,
+ .oplock_request = oplock_request,
+ .lease = lease,
+ .first_open_attempt = first_open_attempt,
+ .keep_locked = keep_locked,
+ };
+
+ status = share_mode_entry_prepare_lock_add(&lck_state.prepare_state,
+ fsp->file_id,
+ conn->connectpath,
+ smb_fname,
+ &old_write_time,
+ open_ntcreate_lock_add_entry,
+ &lck_state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("share_mode_entry_prepare_lock_add() failed for %s - %s\n",
+ smb_fname_str_dbg(smb_fname), nt_errstr(status));
+ fd_close(fsp);
+ return status;
+ }
+
+ status = lck_state.status;
+ if (!NT_STATUS_IS_OK(status)) {
+ fd_close(fsp);
+ return status;
+ }
+
+ /*
+ * From here we need to use 'goto unlock;' instead of return !!!
+ */
+
+ if (fsp->oplock_type != NO_OPLOCK && fsp->oplock_type != LEASE_OPLOCK) {
+ /*
+ * Now ask for kernel oplocks
+ * and cleanup on failure.
+ */
+ status = set_file_oplock(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * Could not get the kernel oplock
+ */
+ lck_state.cleanup_fn =
+ open_ntcreate_lock_cleanup_oplock;
+ fsp->oplock_type = NO_OPLOCK;
+ }
+ }
+
+ /* Should we atomically (to the client at least) truncate ? */
+ if ((!new_file_created) && (flags & O_TRUNC) &&
+ (S_ISREG(fsp->fsp_name->st.st_ex_mode))) {
+ int ret;
+
+ ret = SMB_VFS_FTRUNCATE(fsp, 0);
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ lck_state.cleanup_fn =
+ open_ntcreate_lock_cleanup_entry;
+ goto unlock;
+ }
+ notify_fname(fsp->conn, NOTIFY_ACTION_MODIFIED,
+ FILE_NOTIFY_CHANGE_SIZE
+ | FILE_NOTIFY_CHANGE_ATTRIBUTES,
+ fsp->fsp_name->base_name);
+ }
+
+ /*
+ * We have the share entry *locked*.....
+ */
+
+ /* Delete streams if create_disposition requires it */
+ if (!new_file_created &&
+ clear_ads(create_disposition) &&
+ !fsp_is_alternate_stream(fsp)) {
+ status = delete_all_streams(conn, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ lck_state.cleanup_fn =
+ open_ntcreate_lock_cleanup_entry;
+ goto unlock;
+ }
+ }
+
+ if (!fsp->fsp_flags.is_pathref &&
+ fsp_get_io_fd(fsp) != -1 &&
+ lp_kernel_share_modes(SNUM(conn)))
+ {
+ int ret;
+ /*
+ * Beware: streams implementing VFS modules may
+ * implement streams in a way that fsp will have the
+ * basefile open in the fsp fd, so lacking a distinct
+ * fd for the stream the file-system sharemode will
+ * apply on the basefile which is wrong. The actual
+ * check is deferred to the VFS module implementing
+ * the file-system sharemode call.
+ */
+ ret = SMB_VFS_FILESYSTEM_SHAREMODE(fsp,
+ share_access,
+ access_mask);
+ if (ret == -1){
+ status = NT_STATUS_SHARING_VIOLATION;
+ lck_state.cleanup_fn =
+ open_ntcreate_lock_cleanup_entry;
+ goto unlock;
+ }
+
+ fsp->fsp_flags.kernel_share_modes_taken = true;
+ }
+
+ /*
+ * At this point onwards, we can guarantee that the share entry
+ * is locked, whether we created the file or not, and that the
+ * deny mode is compatible with all current opens.
+ */
+
+ /*
+ * According to Samba4, SEC_FILE_READ_ATTRIBUTE is always granted,
+ * but we don't have to store this - just ignore it on access check.
+ */
+ if (conn->sconn->using_smb2) {
+ /*
+ * SMB2 doesn't return it (according to Microsoft tests).
+ * Test Case: TestSuite_ScenarioNo009GrantedAccessTestS0
+ * File created with access = 0x7 (Read, Write, Delete)
+ * Query Info on file returns 0x87 (Read, Write, Delete, Read Attributes)
+ */
+ fsp->access_mask = access_mask;
+ } else {
+ /* But SMB1 does. */
+ fsp->access_mask = access_mask | FILE_READ_ATTRIBUTES;
+ }
+
+ if (pinfo) {
+ *pinfo = info;
+ }
+
+ /* Handle strange delete on close create semantics. */
+ if (create_options & FILE_DELETE_ON_CLOSE) {
+ if (!new_file_created) {
+ status = can_set_delete_on_close(fsp,
+ existing_dos_attributes);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Remember to delete the mode we just added. */
+ lck_state.cleanup_fn =
+ open_ntcreate_lock_cleanup_entry;
+ goto unlock;
+ }
+ }
+ /* Note that here we set the *initial* delete on close flag,
+ not the regular one. The magic gets handled in close. */
+ fsp->fsp_flags.initial_delete_on_close = true;
+ }
+
+ possibly_set_archive(conn,
+ fsp,
+ smb_fname,
+ parent_dir_fname,
+ info,
+ new_dos_attributes,
+ &smb_fname->st.st_ex_mode);
+
+ /* Determine sparse flag. */
+ if (posix_open) {
+ /* POSIX opens are sparse by default. */
+ fsp->fsp_flags.is_sparse = true;
+ } else {
+ fsp->fsp_flags.is_sparse =
+ (existing_dos_attributes & FILE_ATTRIBUTE_SPARSE);
+ }
+
+ /*
+ * Take care of inherited ACLs on created files - if default ACL not
+ * selected.
+ */
+
+ if (!posix_open && new_file_created && !def_acl) {
+ if (unx_mode != smb_fname->st.st_ex_mode) {
+ int ret = SMB_VFS_FCHMOD(fsp, unx_mode);
+ if (ret == -1) {
+ DBG_INFO("failed to reset "
+ "attributes of file %s to 0%o\n",
+ smb_fname_str_dbg(smb_fname),
+ (unsigned int)unx_mode);
+ }
+ }
+
+ } else if (new_unx_mode) {
+ /*
+ * We only get here in the case of:
+ *
+ * a). Not a POSIX open.
+ * b). File already existed.
+ * c). File was overwritten.
+ * d). Requested DOS attributes didn't match
+ * the DOS attributes on the existing file.
+ *
+ * In that case new_unx_mode has been set
+ * equal to the calculated mode (including
+ * possible inheritance of the mode from the
+ * containing directory).
+ *
+ * Note this mode was calculated with the
+ * DOS attribute FILE_ATTRIBUTE_ARCHIVE added,
+ * so the mode change here is suitable for
+ * an overwritten file.
+ */
+
+ if (new_unx_mode != smb_fname->st.st_ex_mode) {
+ int ret = SMB_VFS_FCHMOD(fsp, new_unx_mode);
+ if (ret == -1) {
+ DBG_INFO("failed to reset "
+ "attributes of file %s to 0%o\n",
+ smb_fname_str_dbg(smb_fname),
+ (unsigned int)new_unx_mode);
+ }
+ }
+ }
+
+ /*
+ * Deal with other opens having a modified write time.
+ */
+ if (fsp_getinfo_ask_sharemode(fsp) &&
+ !is_omit_timespec(&lck_state.write_time))
+ {
+ update_stat_ex_mtime(&fsp->fsp_name->st, lck_state.write_time);
+ }
+
+ status = NT_STATUS_OK;
+
+unlock:
+ ulstatus = share_mode_entry_prepare_unlock(&lck_state.prepare_state,
+ lck_state.cleanup_fn,
+ &lck_state);
+ if (!NT_STATUS_IS_OK(ulstatus)) {
+ DBG_ERR("share_mode_entry_prepare_unlock() failed for %s - %s\n",
+ smb_fname_str_dbg(smb_fname), nt_errstr(ulstatus));
+ smb_panic("share_mode_entry_prepare_unlock() failed!");
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ fd_close(fsp);
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS mkdir_internal(connection_struct *conn,
+ struct smb_filename *parent_dir_fname, /* parent. */
+ struct smb_filename *smb_fname_atname, /* atname relative to parent. */
+ struct smb_filename *smb_dname, /* full pathname from root of share. */
+ uint32_t file_attributes,
+ struct files_struct *fsp)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ mode_t mode;
+ NTSTATUS status;
+ bool posix_open = false;
+ bool need_re_stat = false;
+ uint32_t access_mask = SEC_DIR_ADD_SUBDIR;
+ struct vfs_open_how how = { .flags = O_RDONLY|O_DIRECTORY, };
+ int ret;
+
+ if (!CAN_WRITE(conn) || (access_mask & ~(conn->share_access))) {
+ DEBUG(5,("mkdir_internal: failing share access "
+ "%s\n", lp_servicename(talloc_tos(), lp_sub, SNUM(conn))));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (file_attributes & FILE_FLAG_POSIX_SEMANTICS) {
+ posix_open = true;
+ mode = (mode_t)(file_attributes & ~FILE_FLAG_POSIX_SEMANTICS);
+ } else {
+ mode = unix_mode(conn,
+ FILE_ATTRIBUTE_DIRECTORY,
+ smb_dname,
+ parent_dir_fname->fsp);
+ }
+
+ status = check_parent_access_fsp(parent_dir_fname->fsp, access_mask);
+ if(!NT_STATUS_IS_OK(status)) {
+ DBG_INFO("check_parent_access_fsp "
+ "on directory %s for path %s returned %s\n",
+ smb_fname_str_dbg(parent_dir_fname),
+ smb_dname->base_name,
+ nt_errstr(status));
+ return status;
+ }
+
+ if (lp_inherit_acls(SNUM(conn))) {
+ if (directory_has_default_acl_fsp(parent_dir_fname->fsp)) {
+ mode = (0777 & lp_directory_mask(SNUM(conn)));
+ }
+ }
+
+ ret = SMB_VFS_MKDIRAT(conn,
+ parent_dir_fname->fsp,
+ smb_fname_atname,
+ mode);
+ if (ret != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ /*
+ * Make this a pathref fsp for now. open_directory() will reopen as a
+ * full fsp.
+ */
+ fsp->fsp_flags.is_pathref = true;
+
+ status = fd_openat(parent_dir_fname->fsp, smb_fname_atname, fsp, &how);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* Ensure we're checking for a symlink here.... */
+ /* We don't want to get caught by a symlink racer. */
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("Could not stat directory '%s' just created: %s\n",
+ smb_fname_str_dbg(smb_dname), nt_errstr(status)));
+ return status;
+ }
+
+ if (!S_ISDIR(smb_dname->st.st_ex_mode)) {
+ DEBUG(0, ("Directory '%s' just created is not a directory !\n",
+ smb_fname_str_dbg(smb_dname)));
+ return NT_STATUS_NOT_A_DIRECTORY;
+ }
+
+ if (lp_store_dos_attributes(SNUM(conn))) {
+ file_set_dosmode(conn,
+ smb_dname,
+ file_attributes | FILE_ATTRIBUTE_DIRECTORY,
+ parent_dir_fname,
+ true);
+ }
+
+ if (lp_inherit_permissions(SNUM(conn))) {
+ inherit_access_posix_acl(conn, parent_dir_fname->fsp,
+ smb_dname, mode);
+ need_re_stat = true;
+ }
+
+ if (!posix_open) {
+ /*
+ * Check if high bits should have been set,
+ * then (if bits are missing): add them.
+ * Consider bits automagically set by UNIX, i.e. SGID bit from parent
+ * dir.
+ */
+ if ((mode & ~(S_IRWXU|S_IRWXG|S_IRWXO)) &&
+ (mode & ~smb_dname->st.st_ex_mode)) {
+ SMB_VFS_FCHMOD(fsp,
+ (smb_dname->st.st_ex_mode |
+ (mode & ~smb_dname->st.st_ex_mode)));
+ need_re_stat = true;
+ }
+ }
+
+ /* Change the owner if required. */
+ if (lp_inherit_owner(SNUM(conn)) != INHERIT_OWNER_NO) {
+ change_dir_owner_to_parent_fsp(parent_dir_fname->fsp,
+ fsp);
+ need_re_stat = true;
+ }
+
+ if (need_re_stat) {
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("Could not stat directory '%s' just created: %s\n",
+ smb_fname_str_dbg(smb_dname), nt_errstr(status)));
+ return status;
+ }
+ }
+
+ notify_fname(conn, NOTIFY_ACTION_ADDED, FILE_NOTIFY_CHANGE_DIR_NAME,
+ smb_dname->base_name);
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Open a directory from an NT SMB call.
+****************************************************************************/
+
+static NTSTATUS open_directory(connection_struct *conn,
+ struct smb_request *req,
+ uint32_t access_mask,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ uint32_t file_attributes,
+ struct smb_filename *parent_dir_fname,
+ struct smb_filename *smb_fname_atname,
+ int *pinfo,
+ struct files_struct *fsp)
+{
+ struct smb_filename *smb_dname = fsp->fsp_name;
+ bool dir_existed = VALID_STAT(smb_dname->st);
+ struct open_ntcreate_lock_state lck_state = {};
+ bool keep_locked = false;
+ NTSTATUS status;
+ struct timespec mtimespec;
+ int info = 0;
+ uint32_t need_fd_access;
+ NTSTATUS ulstatus;
+
+ if (is_ntfs_stream_smb_fname(smb_dname)) {
+ DEBUG(2, ("open_directory: %s is a stream name!\n",
+ smb_fname_str_dbg(smb_dname)));
+ return NT_STATUS_NOT_A_DIRECTORY;
+ }
+
+ if (!(file_attributes & FILE_FLAG_POSIX_SEMANTICS)) {
+ /* Ensure we have a directory attribute. */
+ file_attributes |= FILE_ATTRIBUTE_DIRECTORY;
+ }
+
+ DBG_INFO("opening directory %s, access_mask = 0x%"PRIx32", "
+ "share_access = 0x%"PRIx32" create_options = 0x%"PRIx32", "
+ "create_disposition = 0x%"PRIx32", "
+ "file_attributes = 0x%"PRIx32"\n",
+ smb_fname_str_dbg(smb_dname),
+ access_mask,
+ share_access,
+ create_options,
+ create_disposition,
+ file_attributes);
+
+ status = smbd_calculate_access_mask_fsp(parent_dir_fname->fsp,
+ smb_dname->fsp,
+ false,
+ access_mask,
+ &access_mask);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("smbd_calculate_access_mask_fsp "
+ "on file %s returned %s\n",
+ smb_fname_str_dbg(smb_dname),
+ nt_errstr(status));
+ return status;
+ }
+
+ if ((access_mask & SEC_FLAG_SYSTEM_SECURITY) &&
+ !security_token_has_privilege(get_current_nttok(conn),
+ SEC_PRIV_SECURITY)) {
+ DEBUG(10, ("open_directory: open on %s "
+ "failed - SEC_FLAG_SYSTEM_SECURITY denied.\n",
+ smb_fname_str_dbg(smb_dname)));
+ return NT_STATUS_PRIVILEGE_NOT_HELD;
+ }
+
+ switch( create_disposition ) {
+ case FILE_OPEN:
+
+ if (!dir_existed) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ info = FILE_WAS_OPENED;
+ break;
+
+ case FILE_CREATE:
+
+ /* If directory exists error. If directory doesn't
+ * exist create. */
+
+ if (dir_existed) {
+ status = NT_STATUS_OBJECT_NAME_COLLISION;
+ DEBUG(2, ("open_directory: unable to create "
+ "%s. Error was %s\n",
+ smb_fname_str_dbg(smb_dname),
+ nt_errstr(status)));
+ return status;
+ }
+
+ if (smb_fname_atname->twrp != 0) {
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+
+ status = mkdir_internal(conn,
+ parent_dir_fname,
+ smb_fname_atname,
+ smb_dname,
+ file_attributes,
+ fsp);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("open_directory: unable to create "
+ "%s. Error was %s\n",
+ smb_fname_str_dbg(smb_dname),
+ nt_errstr(status)));
+ return status;
+ }
+
+ info = FILE_WAS_CREATED;
+ break;
+
+ case FILE_OPEN_IF:
+ /*
+ * If directory exists open. If directory doesn't
+ * exist create.
+ */
+
+ if (dir_existed) {
+ status = NT_STATUS_OK;
+ info = FILE_WAS_OPENED;
+ } else {
+ if (smb_fname_atname->twrp != 0) {
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+ status = mkdir_internal(conn,
+ parent_dir_fname,
+ smb_fname_atname,
+ smb_dname,
+ file_attributes,
+ fsp);
+
+ if (NT_STATUS_IS_OK(status)) {
+ info = FILE_WAS_CREATED;
+ } else {
+ int ret;
+ /* Cope with create race. */
+ if (!NT_STATUS_EQUAL(status,
+ NT_STATUS_OBJECT_NAME_COLLISION)) {
+ DEBUG(2, ("open_directory: unable to create "
+ "%s. Error was %s\n",
+ smb_fname_str_dbg(smb_dname),
+ nt_errstr(status)));
+ return status;
+ }
+
+ /*
+ * If mkdir_internal() returned
+ * NT_STATUS_OBJECT_NAME_COLLISION
+ * we still must lstat the path.
+ */
+ ret = SMB_VFS_FSTATAT(
+ conn,
+ parent_dir_fname->fsp,
+ smb_fname_atname,
+ &smb_dname->st,
+ AT_SYMLINK_NOFOLLOW);
+ if (ret == -1) {
+ DEBUG(2, ("Could not stat "
+ "directory '%s' just "
+ "opened: %s\n",
+ smb_fname_str_dbg(
+ smb_dname),
+ strerror(errno)));
+ return map_nt_error_from_unix(
+ errno);
+ }
+
+ info = FILE_WAS_OPENED;
+ }
+ }
+
+ break;
+
+ case FILE_SUPERSEDE:
+ case FILE_OVERWRITE:
+ case FILE_OVERWRITE_IF:
+ default:
+ DEBUG(5,("open_directory: invalid create_disposition "
+ "0x%x for directory %s\n",
+ (unsigned int)create_disposition,
+ smb_fname_str_dbg(smb_dname)));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if(!S_ISDIR(smb_dname->st.st_ex_mode)) {
+ DEBUG(5,("open_directory: %s is not a directory !\n",
+ smb_fname_str_dbg(smb_dname)));
+ return NT_STATUS_NOT_A_DIRECTORY;
+ }
+
+ /*
+ * Setup the files_struct for it.
+ */
+
+ fsp->file_id = vfs_file_id_from_sbuf(conn, &smb_dname->st);
+ fsp->vuid = req ? req->vuid : UID_FIELD_INVALID;
+ fsp->file_pid = req ? req->smbpid : 0;
+ fsp->fsp_flags.can_lock = false;
+ fsp->fsp_flags.can_read = false;
+ fsp->fsp_flags.can_write = false;
+
+ fh_set_private_options(fsp->fh, 0);
+ /*
+ * According to Samba4, SEC_FILE_READ_ATTRIBUTE is always granted,
+ */
+ fsp->access_mask = access_mask | FILE_READ_ATTRIBUTES;
+ fsp->print_file = NULL;
+ fsp->fsp_flags.modified = false;
+ fsp->oplock_type = NO_OPLOCK;
+ fsp->sent_oplock_break = NO_BREAK_SENT;
+ fsp->fsp_flags.is_directory = true;
+ if (file_attributes & FILE_FLAG_POSIX_SEMANTICS) {
+ fsp->posix_flags |= FSP_POSIX_FLAGS_ALL;
+ }
+
+ /* Don't store old timestamps for directory
+ handles in the internal database. We don't
+ update them in there if new objects
+ are created in the directory. Currently
+ we only update timestamps on file writes.
+ See bug #9870.
+ */
+ mtimespec = make_omit_timespec();
+
+ /*
+ * Obviously for FILE_LIST_DIRECTORY we need to reopen to get an fd
+ * usable for reading a directory. SMB2_FLUSH may be called on
+ * directories opened with FILE_ADD_FILE and FILE_ADD_SUBDIRECTORY so
+ * for those we need to reopen as well.
+ */
+ need_fd_access =
+ FILE_LIST_DIRECTORY |
+ FILE_ADD_FILE |
+ FILE_ADD_SUBDIRECTORY;
+
+ if (access_mask & need_fd_access) {
+ struct vfs_open_how how = {
+ .flags = O_RDONLY | O_DIRECTORY,
+ };
+ bool file_created;
+
+ status = reopen_from_fsp(parent_dir_fname->fsp,
+ smb_fname_atname,
+ fsp,
+ &how,
+ &file_created);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_INFO("Could not open fd for [%s]: %s\n",
+ smb_fname_str_dbg(smb_dname),
+ nt_errstr(status));
+ return status;
+ }
+ }
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ fd_close(fsp);
+ return status;
+ }
+
+ if(!S_ISDIR(fsp->fsp_name->st.st_ex_mode)) {
+ DEBUG(5,("open_directory: %s is not a directory !\n",
+ smb_fname_str_dbg(smb_dname)));
+ fd_close(fsp);
+ return NT_STATUS_NOT_A_DIRECTORY;
+ }
+
+ /* Ensure there was no race condition. We need to check
+ * dev/inode but not permissions, as these can change
+ * legitimately */
+ if (!check_same_dev_ino(&smb_dname->st, &fsp->fsp_name->st)) {
+ DEBUG(5,("open_directory: stat struct differs for "
+ "directory %s.\n",
+ smb_fname_str_dbg(smb_dname)));
+ fd_close(fsp);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (info == FILE_WAS_OPENED) {
+ status = smbd_check_access_rights_fsp(parent_dir_fname->fsp,
+ fsp,
+ false,
+ access_mask);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("smbd_check_access_rights_fsp on "
+ "file %s failed with %s\n",
+ fsp_str_dbg(fsp),
+ nt_errstr(status));
+ fd_close(fsp);
+ return status;
+ }
+ }
+
+ /*
+ * If we created a new directory or going to delete it later,
+ * we should keep * the share_mode_lock (g_lock) until we call
+ * share_mode_entry_prepare_unlock()
+ */
+ if (info != FILE_WAS_OPENED) {
+ keep_locked = true;
+ } else if (create_options & FILE_DELETE_ON_CLOSE) {
+ keep_locked = true;
+ }
+
+ lck_state = (struct open_ntcreate_lock_state) {
+ .fsp = fsp,
+ .object_type = "directory",
+ .req = req,
+ .create_disposition = create_disposition,
+ .access_mask = access_mask,
+ .share_access = share_access,
+ .oplock_request = NO_OPLOCK,
+ .lease = NULL,
+ .first_open_attempt = true,
+ .keep_locked = keep_locked,
+ };
+
+ status = share_mode_entry_prepare_lock_add(&lck_state.prepare_state,
+ fsp->file_id,
+ conn->connectpath,
+ smb_dname,
+ &mtimespec,
+ open_ntcreate_lock_add_entry,
+ &lck_state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("share_mode_entry_prepare_lock_add() failed for %s - %s\n",
+ smb_fname_str_dbg(smb_dname), nt_errstr(status));
+ fd_close(fsp);
+ return status;
+ }
+
+ status = lck_state.status;
+ if (!NT_STATUS_IS_OK(status)) {
+ fd_close(fsp);
+ return status;
+ }
+
+ /*
+ * From here we need to use 'goto unlock;' instead of return !!!
+ */
+
+ /* For directories the delete on close bit at open time seems
+ always to be honored on close... See test 19 in Samba4 BASE-DELETE. */
+ if (create_options & FILE_DELETE_ON_CLOSE) {
+ status = can_set_delete_on_close(fsp, 0);
+ if (!NT_STATUS_IS_OK(status) && !NT_STATUS_EQUAL(status, NT_STATUS_DIRECTORY_NOT_EMPTY)) {
+ lck_state.cleanup_fn =
+ open_ntcreate_lock_cleanup_entry;
+ goto unlock;
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ /* Note that here we set the *initial* delete on close flag,
+ not the regular one. The magic gets handled in close. */
+ fsp->fsp_flags.initial_delete_on_close = true;
+ }
+ }
+
+ /*
+ * Deal with other opens having a modified write time.
+ */
+ if (!is_omit_timespec(&lck_state.write_time)) {
+ update_stat_ex_mtime(&fsp->fsp_name->st, lck_state.write_time);
+ }
+
+ if (pinfo) {
+ *pinfo = info;
+ }
+
+ status = NT_STATUS_OK;
+
+unlock:
+ ulstatus = share_mode_entry_prepare_unlock(&lck_state.prepare_state,
+ lck_state.cleanup_fn,
+ &lck_state);
+ if (!NT_STATUS_IS_OK(ulstatus)) {
+ DBG_ERR("share_mode_entry_prepare_unlock() failed for %s - %s\n",
+ smb_fname_str_dbg(smb_dname), nt_errstr(ulstatus));
+ smb_panic("share_mode_entry_prepare_unlock() failed!");
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ fd_close(fsp);
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS create_directory(connection_struct *conn,
+ struct smb_request *req,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_dname)
+{
+ NTSTATUS status;
+ files_struct *fsp;
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_dname, /* fname */
+ FILE_READ_ATTRIBUTES, /* access_mask */
+ FILE_SHARE_NONE, /* share_access */
+ FILE_CREATE, /* create_disposition*/
+ FILE_DIRECTORY_FILE, /* create_options */
+ FILE_ATTRIBUTE_DIRECTORY, /* file_attributes */
+ 0, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (NT_STATUS_IS_OK(status)) {
+ close_file_free(req, &fsp, NORMAL_CLOSE);
+ }
+
+ return status;
+}
+
+/****************************************************************************
+ Receive notification that one of our open files has been renamed by another
+ smbd process.
+****************************************************************************/
+
+void msg_file_was_renamed(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id src,
+ DATA_BLOB *data)
+{
+ struct file_rename_message *msg = NULL;
+ enum ndr_err_code ndr_err;
+ files_struct *fsp;
+ struct smb_filename *smb_fname = NULL;
+ struct smbd_server_connection *sconn =
+ talloc_get_type_abort(private_data,
+ struct smbd_server_connection);
+
+ msg = talloc(talloc_tos(), struct file_rename_message);
+ if (msg == NULL) {
+ DBG_WARNING("talloc failed\n");
+ return;
+ }
+
+ ndr_err = ndr_pull_struct_blob_all(
+ data,
+ msg,
+ msg,
+ (ndr_pull_flags_fn_t)ndr_pull_file_rename_message);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_DEBUG("ndr_pull_oplock_break_message failed: %s\n",
+ ndr_errstr(ndr_err));
+ goto out;
+ }
+ if (DEBUGLEVEL >= 10) {
+ struct server_id_buf buf;
+ DBG_DEBUG("Got rename message from %s\n",
+ server_id_str_buf(src, &buf));
+ NDR_PRINT_DEBUG(file_rename_message, msg);
+ }
+
+ /* stream_name must always be NULL if there is no stream. */
+ if ((msg->stream_name != NULL) && (msg->stream_name[0] == '\0')) {
+ msg->stream_name = NULL;
+ }
+
+ smb_fname = synthetic_smb_fname(msg,
+ msg->base_name,
+ msg->stream_name,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname == NULL) {
+ DBG_DEBUG("synthetic_smb_fname failed\n");
+ goto out;
+ }
+
+ fsp = file_find_dif(sconn, msg->id, msg->share_file_id);
+ if (fsp == NULL) {
+ DBG_DEBUG("fsp not found\n");
+ goto out;
+ }
+
+ if (strcmp(fsp->conn->connectpath, msg->servicepath) == 0) {
+ SMB_STRUCT_STAT fsp_orig_sbuf;
+ NTSTATUS status;
+ DBG_DEBUG("renaming file %s from %s -> %s\n",
+ fsp_fnum_dbg(fsp),
+ fsp_str_dbg(fsp),
+ smb_fname_str_dbg(smb_fname));
+
+ /*
+ * The incoming smb_fname here has an
+ * invalid stat struct from synthetic_smb_fname()
+ * above.
+ * Preserve the existing stat from the
+ * open fsp after fsp_set_smb_fname()
+ * overwrites with the invalid stat.
+ *
+ * (We could just copy this into
+ * smb_fname->st, but keep this code
+ * identical to the fix in rename_open_files()
+ * for clarity.
+ *
+ * We will do an fstat before returning
+ * any of this metadata to the client anyway.
+ */
+ fsp_orig_sbuf = fsp->fsp_name->st;
+ status = fsp_set_smb_fname(fsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("fsp_set_smb_fname failed: %s\n",
+ nt_errstr(status));
+ }
+ fsp->fsp_name->st = fsp_orig_sbuf;
+ } else {
+ /* TODO. JRA. */
+ /*
+ * Now we have the complete path we can work out if
+ * this is actually within this share and adjust
+ * newname accordingly.
+ */
+ DBG_DEBUG("share mismatch (sharepath %s not sharepath %s) "
+ "%s from %s -> %s\n",
+ fsp->conn->connectpath,
+ msg->servicepath,
+ fsp_fnum_dbg(fsp),
+ fsp_str_dbg(fsp),
+ smb_fname_str_dbg(smb_fname));
+ }
+ out:
+ TALLOC_FREE(msg);
+}
+
+/*
+ * If a main file is opened for delete, all streams need to be checked for
+ * !FILE_SHARE_DELETE. Do this by opening with DELETE_ACCESS.
+ * If that works, delete them all by setting the delete on close and close.
+ */
+
+static NTSTATUS open_streams_for_delete(connection_struct *conn,
+ const struct smb_filename *smb_fname)
+{
+ struct stream_struct *stream_info = NULL;
+ files_struct **streams = NULL;
+ int j;
+ unsigned int i, num_streams = 0;
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct smb_filename *pathref = NULL;
+ NTSTATUS status;
+
+ if (smb_fname->fsp == NULL) {
+ struct smb_filename *tmp = NULL;
+ status = synthetic_pathref(frame,
+ conn->cwd_fsp,
+ smb_fname->base_name,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags,
+ &tmp);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)
+ || NT_STATUS_EQUAL(status,
+ NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ DBG_DEBUG("no streams around\n");
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+ DBG_DEBUG("synthetic_pathref failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ pathref = tmp;
+ } else {
+ pathref = smb_fname;
+ }
+ status = vfs_fstreaminfo(pathref->fsp, talloc_tos(),
+ &num_streams, &stream_info);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)
+ || NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ DEBUG(10, ("no streams around\n"));
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("vfs_fstreaminfo failed: %s\n",
+ nt_errstr(status)));
+ goto fail;
+ }
+
+ DEBUG(10, ("open_streams_for_delete found %d streams\n",
+ num_streams));
+
+ if (num_streams == 0) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ streams = talloc_array(talloc_tos(), files_struct *, num_streams);
+ if (streams == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ for (i=0; i<num_streams; i++) {
+ struct smb_filename *smb_fname_cp;
+
+ if (strequal(stream_info[i].name, "::$DATA")) {
+ streams[i] = NULL;
+ continue;
+ }
+
+ smb_fname_cp = synthetic_smb_fname(talloc_tos(),
+ smb_fname->base_name,
+ stream_info[i].name,
+ NULL,
+ smb_fname->twrp,
+ (smb_fname->flags &
+ ~SMB_FILENAME_POSIX_PATH));
+ if (smb_fname_cp == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ status = openat_pathref_fsp(conn->cwd_fsp, smb_fname_cp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Unable to open stream [%s]: %s\n",
+ smb_fname_str_dbg(smb_fname_cp),
+ nt_errstr(status));
+ TALLOC_FREE(smb_fname_cp);
+ break;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ NULL, /* req */
+ NULL, /* dirfsp */
+ smb_fname_cp, /* fname */
+ DELETE_ACCESS, /* access_mask */
+ (FILE_SHARE_READ | /* share_access */
+ FILE_SHARE_WRITE | FILE_SHARE_DELETE),
+ FILE_OPEN, /* create_disposition*/
+ 0, /* create_options */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes */
+ 0, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &streams[i], /* result */
+ NULL, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("Could not open stream %s: %s\n",
+ smb_fname_str_dbg(smb_fname_cp),
+ nt_errstr(status)));
+
+ TALLOC_FREE(smb_fname_cp);
+ break;
+ }
+ TALLOC_FREE(smb_fname_cp);
+ }
+
+ /*
+ * don't touch the variable "status" beyond this point :-)
+ */
+
+ for (j = i-1 ; j >= 0; j--) {
+ if (streams[j] == NULL) {
+ continue;
+ }
+
+ DEBUG(10, ("Closing stream # %d, %s\n", j,
+ fsp_str_dbg(streams[j])));
+ close_file_free(NULL, &streams[j], NORMAL_CLOSE);
+ }
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/*********************************************************************
+ Create a default ACL by inheriting from the parent. If no inheritance
+ from the parent available, don't set anything. This will leave the actual
+ permissions the new file or directory already got from the filesystem
+ as the NT ACL when read.
+*********************************************************************/
+
+static NTSTATUS inherit_new_acl(files_struct *dirfsp, files_struct *fsp)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct security_descriptor *parent_desc = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+ struct security_descriptor *psd = NULL;
+ const struct dom_sid *owner_sid = NULL;
+ const struct dom_sid *group_sid = NULL;
+ uint32_t security_info_sent = (SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL);
+ struct security_token *token = fsp->conn->session_info->security_token;
+ bool inherit_owner =
+ (lp_inherit_owner(SNUM(fsp->conn)) == INHERIT_OWNER_WINDOWS_AND_UNIX);
+ bool inheritable_components = false;
+ bool try_builtin_administrators = false;
+ const struct dom_sid *BA_U_sid = NULL;
+ const struct dom_sid *BA_G_sid = NULL;
+ bool try_system = false;
+ const struct dom_sid *SY_U_sid = NULL;
+ const struct dom_sid *SY_G_sid = NULL;
+ size_t size = 0;
+ bool ok;
+
+ status = SMB_VFS_FGET_NT_ACL(dirfsp,
+ (SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL),
+ frame,
+ &parent_desc);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ inheritable_components = sd_has_inheritable_components(parent_desc,
+ fsp->fsp_flags.is_directory);
+
+ if (!inheritable_components && !inherit_owner) {
+ TALLOC_FREE(frame);
+ /* Nothing to inherit and not setting owner. */
+ return NT_STATUS_OK;
+ }
+
+ /* Create an inherited descriptor from the parent. */
+
+ if (DEBUGLEVEL >= 10) {
+ DEBUG(10,("inherit_new_acl: parent acl for %s is:\n",
+ fsp_str_dbg(fsp) ));
+ NDR_PRINT_DEBUG(security_descriptor, parent_desc);
+ }
+
+ /* Inherit from parent descriptor if "inherit owner" set. */
+ if (inherit_owner) {
+ owner_sid = parent_desc->owner_sid;
+ group_sid = parent_desc->group_sid;
+ }
+
+ if (owner_sid == NULL) {
+ if (security_token_has_builtin_administrators(token)) {
+ try_builtin_administrators = true;
+ } else if (security_token_is_system(token)) {
+ try_builtin_administrators = true;
+ try_system = true;
+ }
+ }
+
+ if (group_sid == NULL &&
+ token->num_sids == PRIMARY_GROUP_SID_INDEX)
+ {
+ if (security_token_is_system(token)) {
+ try_builtin_administrators = true;
+ try_system = true;
+ }
+ }
+
+ if (try_builtin_administrators) {
+ struct unixid ids = { .id = 0 };
+
+ ok = sids_to_unixids(&global_sid_Builtin_Administrators, 1, &ids);
+ if (ok) {
+ switch (ids.type) {
+ case ID_TYPE_BOTH:
+ BA_U_sid = &global_sid_Builtin_Administrators;
+ BA_G_sid = &global_sid_Builtin_Administrators;
+ break;
+ case ID_TYPE_UID:
+ BA_U_sid = &global_sid_Builtin_Administrators;
+ break;
+ case ID_TYPE_GID:
+ BA_G_sid = &global_sid_Builtin_Administrators;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if (try_system) {
+ struct unixid ids = { .id = 0 };
+
+ ok = sids_to_unixids(&global_sid_System, 1, &ids);
+ if (ok) {
+ switch (ids.type) {
+ case ID_TYPE_BOTH:
+ SY_U_sid = &global_sid_System;
+ SY_G_sid = &global_sid_System;
+ break;
+ case ID_TYPE_UID:
+ SY_U_sid = &global_sid_System;
+ break;
+ case ID_TYPE_GID:
+ SY_G_sid = &global_sid_System;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if (owner_sid == NULL) {
+ owner_sid = BA_U_sid;
+ }
+
+ if (owner_sid == NULL) {
+ owner_sid = SY_U_sid;
+ }
+
+ if (group_sid == NULL) {
+ group_sid = SY_G_sid;
+ }
+
+ if (try_system && group_sid == NULL) {
+ group_sid = BA_G_sid;
+ }
+
+ if (owner_sid == NULL) {
+ owner_sid = &token->sids[PRIMARY_USER_SID_INDEX];
+ }
+ if (group_sid == NULL) {
+ if (token->num_sids == PRIMARY_GROUP_SID_INDEX) {
+ group_sid = &token->sids[PRIMARY_USER_SID_INDEX];
+ } else {
+ group_sid = &token->sids[PRIMARY_GROUP_SID_INDEX];
+ }
+ }
+
+ status = se_create_child_secdesc(frame,
+ &psd,
+ &size,
+ parent_desc,
+ owner_sid,
+ group_sid,
+ fsp->fsp_flags.is_directory);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* If inheritable_components == false,
+ se_create_child_secdesc()
+ creates a security descriptor with a NULL dacl
+ entry, but with SEC_DESC_DACL_PRESENT. We need
+ to remove that flag. */
+
+ if (!inheritable_components) {
+ security_info_sent &= ~SECINFO_DACL;
+ psd->type &= ~SEC_DESC_DACL_PRESENT;
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ DEBUG(10,("inherit_new_acl: child acl for %s is:\n",
+ fsp_str_dbg(fsp) ));
+ NDR_PRINT_DEBUG(security_descriptor, psd);
+ }
+
+ if (inherit_owner) {
+ /* We need to be root to force this. */
+ become_root();
+ }
+ status = SMB_VFS_FSET_NT_ACL(metadata_fsp(fsp),
+ security_info_sent,
+ psd);
+ if (inherit_owner) {
+ unbecome_root();
+ }
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/*
+ * If we already have a lease, it must match the new file id. [MS-SMB2]
+ * 3.3.5.9.8 speaks about INVALID_PARAMETER if an already used lease key is
+ * used for a different file name.
+ */
+
+struct lease_match_state {
+ /* Input parameters. */
+ TALLOC_CTX *mem_ctx;
+ const char *servicepath;
+ const struct smb_filename *fname;
+ bool file_existed;
+ struct file_id id;
+ /* Return parameters. */
+ uint32_t num_file_ids;
+ struct file_id *ids;
+ NTSTATUS match_status;
+};
+
+/*************************************************************
+ File doesn't exist but this lease key+guid is already in use.
+
+ This is only allowable in the dynamic share case where the
+ service path must be different.
+
+ There is a small race condition here in the multi-connection
+ case where a client sends two create calls on different connections,
+ where the file doesn't exist and one smbd creates the leases_db
+ entry first, but this will get fixed by the multichannel cleanup
+ when all identical client_guids get handled by a single smbd.
+**************************************************************/
+
+static void lease_match_parser_new_file(
+ uint32_t num_files,
+ const struct leases_db_file *files,
+ struct lease_match_state *state)
+{
+ uint32_t i;
+
+ for (i = 0; i < num_files; i++) {
+ const struct leases_db_file *f = &files[i];
+ if (strequal(state->servicepath, f->servicepath)) {
+ state->match_status = NT_STATUS_INVALID_PARAMETER;
+ return;
+ }
+ }
+
+ /* Dynamic share case. Break leases on all other files. */
+ state->match_status = leases_db_copy_file_ids(state->mem_ctx,
+ num_files,
+ files,
+ &state->ids);
+ if (!NT_STATUS_IS_OK(state->match_status)) {
+ return;
+ }
+
+ state->num_file_ids = num_files;
+ state->match_status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ return;
+}
+
+static void lease_match_parser(
+ uint32_t num_files,
+ const struct leases_db_file *files,
+ void *private_data)
+{
+ struct lease_match_state *state =
+ (struct lease_match_state *)private_data;
+ uint32_t i;
+
+ if (!state->file_existed) {
+ /*
+ * Deal with name mismatch or
+ * possible dynamic share case separately
+ * to make code clearer.
+ */
+ lease_match_parser_new_file(num_files,
+ files,
+ state);
+ return;
+ }
+
+ /* File existed. */
+ state->match_status = NT_STATUS_OK;
+
+ for (i = 0; i < num_files; i++) {
+ const struct leases_db_file *f = &files[i];
+
+ /* Everything should be the same. */
+ if (!file_id_equal(&state->id, &f->id)) {
+ /*
+ * The client asked for a lease on a
+ * file that doesn't match the file_id
+ * in the database.
+ *
+ * Maybe this is a dynamic share, i.e.
+ * a share where the servicepath is
+ * different for different users (e.g.
+ * the [HOMES] share.
+ *
+ * If the servicepath is different, but the requested
+ * file name + stream name is the same then this is
+ * a dynamic share, the client is using the same share
+ * name and doesn't know that the underlying servicepath
+ * is different. It was expecting a lease on the
+ * same file. Return NT_STATUS_OPLOCK_NOT_GRANTED
+ * to break leases
+ *
+ * Otherwise the client has messed up, or is
+ * testing our error codes, so return
+ * NT_STATUS_INVALID_PARAMETER.
+ */
+ if (!strequal(f->servicepath, state->servicepath) &&
+ strequal(f->base_name, state->fname->base_name) &&
+ strequal(f->stream_name, state->fname->stream_name))
+ {
+ /*
+ * Name is the same but servicepath is
+ * different, dynamic share. Break leases.
+ */
+ state->match_status =
+ NT_STATUS_OPLOCK_NOT_GRANTED;
+ } else {
+ state->match_status =
+ NT_STATUS_INVALID_PARAMETER;
+ }
+ break;
+ }
+ if (!strequal(f->servicepath, state->servicepath)) {
+ state->match_status = NT_STATUS_INVALID_PARAMETER;
+ break;
+ }
+ if (!strequal(f->base_name, state->fname->base_name)) {
+ state->match_status = NT_STATUS_INVALID_PARAMETER;
+ break;
+ }
+ if (!strequal(f->stream_name, state->fname->stream_name)) {
+ state->match_status = NT_STATUS_INVALID_PARAMETER;
+ break;
+ }
+ }
+
+ if (NT_STATUS_IS_OK(state->match_status)) {
+ /*
+ * Common case - just opening another handle on a
+ * file on a non-dynamic share.
+ */
+ return;
+ }
+
+ if (NT_STATUS_EQUAL(state->match_status, NT_STATUS_INVALID_PARAMETER)) {
+ /* Mismatched path. Error back to client. */
+ return;
+ }
+
+ /*
+ * File id mismatch. Dynamic share case NT_STATUS_OPLOCK_NOT_GRANTED.
+ * Don't allow leases.
+ */
+
+ state->match_status = leases_db_copy_file_ids(state->mem_ctx,
+ num_files,
+ files,
+ &state->ids);
+ if (!NT_STATUS_IS_OK(state->match_status)) {
+ return;
+ }
+
+ state->num_file_ids = num_files;
+ state->match_status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ return;
+}
+
+struct lease_match_break_state {
+ struct messaging_context *msg_ctx;
+ const struct smb2_lease_key *lease_key;
+ struct file_id id;
+
+ bool found_lease;
+ uint16_t version;
+ uint16_t epoch;
+};
+
+static bool lease_match_break_fn(
+ struct share_mode_entry *e,
+ void *private_data)
+{
+ struct lease_match_break_state *state = private_data;
+ bool stale, equal;
+ uint32_t e_lease_type = SMB2_LEASE_NONE;
+ NTSTATUS status;
+
+ stale = share_entry_stale_pid(e);
+ if (stale) {
+ return false;
+ }
+
+ equal = smb2_lease_key_equal(&e->lease_key, state->lease_key);
+ if (!equal) {
+ return false;
+ }
+
+ status = leases_db_get(
+ &e->client_guid,
+ &e->lease_key,
+ &state->id,
+ &e_lease_type, /* current_state */
+ NULL, /* breaking */
+ NULL, /* breaking_to_requested */
+ NULL, /* breaking_to_required */
+ &state->version, /* lease_version */
+ &state->epoch); /* epoch */
+ if (NT_STATUS_IS_OK(status)) {
+ state->found_lease = true;
+ } else {
+ DBG_WARNING("Could not find version/epoch: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ if (e_lease_type == SMB2_LEASE_NONE) {
+ return false;
+ }
+ send_break_message(state->msg_ctx, &state->id, e, SMB2_LEASE_NONE);
+
+ /*
+ * Windows 7 and 8 lease clients are broken in that they will
+ * not respond to lease break requests whilst waiting for an
+ * outstanding open request on that lease handle on the same
+ * TCP connection, due to holding an internal inode lock.
+ *
+ * This means we can't reschedule ourselves here, but must
+ * return from the create.
+ *
+ * Work around:
+ *
+ * Send the breaks and then return SMB2_LEASE_NONE in the
+ * lease handle to cause them to acknowledge the lease
+ * break. Consultation with Microsoft engineering confirmed
+ * this approach is safe.
+ */
+
+ return false;
+}
+
+static void lease_match_fid_fn(struct share_mode_lock *lck,
+ void *private_data)
+{
+ bool ok;
+
+ ok = share_mode_forall_leases(lck, lease_match_break_fn, private_data);
+ if (!ok) {
+ DBG_DEBUG("share_mode_forall_leases failed\n");
+ }
+}
+
+static NTSTATUS lease_match(connection_struct *conn,
+ struct smb_request *req,
+ const struct smb2_lease_key *lease_key,
+ const char *servicepath,
+ const struct smb_filename *fname,
+ uint16_t *p_version,
+ uint16_t *p_epoch)
+{
+ struct smbd_server_connection *sconn = req->sconn;
+ TALLOC_CTX *tos = talloc_tos();
+ struct lease_match_state state = {
+ .mem_ctx = tos,
+ .servicepath = servicepath,
+ .fname = fname,
+ .match_status = NT_STATUS_OK
+ };
+ uint32_t i;
+ NTSTATUS status;
+
+ state.file_existed = VALID_STAT(fname->st);
+ if (state.file_existed) {
+ state.id = vfs_file_id_from_sbuf(conn, &fname->st);
+ }
+
+ status = leases_db_parse(&sconn->client->global->client_guid,
+ lease_key, lease_match_parser, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * Not found or error means okay: We can make the lease pass
+ */
+ return NT_STATUS_OK;
+ }
+ if (!NT_STATUS_EQUAL(state.match_status, NT_STATUS_OPLOCK_NOT_GRANTED)) {
+ /*
+ * Anything but NT_STATUS_OPLOCK_NOT_GRANTED, let the caller
+ * deal with it.
+ */
+ return state.match_status;
+ }
+
+ /* We have to break all existing leases. */
+ for (i = 0; i < state.num_file_ids; i++) {
+ struct lease_match_break_state break_state = {
+ .msg_ctx = conn->sconn->msg_ctx,
+ .lease_key = lease_key,
+ };
+
+ if (file_id_equal(&state.ids[i], &state.id)) {
+ /* Don't need to break our own file. */
+ continue;
+ }
+
+ break_state.id = state.ids[i];
+
+ status = share_mode_do_locked_vfs_denied(break_state.id,
+ lease_match_fid_fn,
+ &break_state);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Race condition - file already closed. */
+ continue;
+ }
+
+ if (break_state.found_lease) {
+ *p_version = break_state.version;
+ *p_epoch = break_state.epoch;
+ }
+ }
+ /*
+ * Ensure we don't grant anything more so we
+ * never upgrade.
+ */
+ return NT_STATUS_OPLOCK_NOT_GRANTED;
+}
+
+/*
+ * Wrapper around open_file_ntcreate and open_directory
+ */
+
+static NTSTATUS create_file_unixpath(connection_struct *conn,
+ struct smb_request *req,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ uint32_t access_mask,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ uint32_t file_attributes,
+ uint32_t oplock_request,
+ const struct smb2_lease *lease,
+ uint64_t allocation_size,
+ uint32_t private_flags,
+ struct security_descriptor *sd,
+ struct ea_list *ea_list,
+
+ files_struct **result,
+ int *pinfo)
+{
+ struct smb2_lease none_lease;
+ int info = FILE_WAS_OPENED;
+ files_struct *base_fsp = NULL;
+ files_struct *fsp = NULL;
+ bool free_fsp_on_error = false;
+ NTSTATUS status;
+ int ret;
+ struct smb_filename *parent_dir_fname = NULL;
+ struct smb_filename *smb_fname_atname = NULL;
+
+ DBG_DEBUG("access_mask = 0x%"PRIx32" "
+ "file_attributes = 0x%"PRIx32" "
+ "share_access = 0x%"PRIx32" "
+ "create_disposition = 0x%"PRIx32" "
+ "create_options = 0x%"PRIx32" "
+ "oplock_request = 0x%"PRIx32" "
+ "private_flags = 0x%"PRIx32" "
+ "ea_list = %p, "
+ "sd = %p, "
+ "fname = %s\n",
+ access_mask,
+ file_attributes,
+ share_access,
+ create_disposition,
+ create_options,
+ oplock_request,
+ private_flags,
+ ea_list,
+ sd,
+ smb_fname_str_dbg(smb_fname));
+
+ if (create_options & FILE_OPEN_BY_FILE_ID) {
+ status = NT_STATUS_NOT_SUPPORTED;
+ goto fail;
+ }
+
+ if (create_options & NTCREATEX_OPTIONS_INVALID_PARAM_MASK) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ if (!(create_options & FILE_OPEN_REPARSE_POINT) &&
+ (smb_fname->fsp != NULL) && /* new files don't have an fsp */
+ VALID_STAT(smb_fname->fsp->fsp_name->st))
+ {
+ mode_t type = (smb_fname->fsp->fsp_name->st.st_ex_mode &
+ S_IFMT);
+
+ switch (type) {
+ case S_IFREG:
+ FALL_THROUGH;
+ case S_IFDIR:
+ break;
+ case S_IFLNK:
+ /*
+ * We should never get this far with a symlink
+ * "as such". Report as not existing.
+ */
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ goto fail;
+ default:
+ status = NT_STATUS_IO_REPARSE_TAG_NOT_HANDLED;
+ goto fail;
+ }
+ }
+
+ if (req == NULL) {
+ oplock_request |= INTERNAL_OPEN_ONLY;
+ }
+
+ if (lease != NULL) {
+ uint16_t epoch = lease->lease_epoch;
+ uint16_t version = lease->lease_version;
+
+ if (req == NULL) {
+ DBG_WARNING("Got lease on internal open\n");
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto fail;
+ }
+
+ status = lease_match(conn,
+ req,
+ &lease->lease_key,
+ conn->connectpath,
+ smb_fname,
+ &version,
+ &epoch);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) {
+ /* Dynamic share file. No leases and update epoch... */
+ none_lease = *lease;
+ none_lease.lease_state = SMB2_LEASE_NONE;
+ none_lease.lease_epoch = epoch;
+ none_lease.lease_version = version;
+ lease = &none_lease;
+ } else if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ if ((conn->fs_capabilities & FILE_NAMED_STREAMS)
+ && (access_mask & DELETE_ACCESS)
+ && !is_named_stream(smb_fname)) {
+ /*
+ * We can't open a file with DELETE access if any of the
+ * streams is open without FILE_SHARE_DELETE
+ */
+ status = open_streams_for_delete(conn, smb_fname);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ if (access_mask & SEC_FLAG_SYSTEM_SECURITY) {
+ bool ok;
+
+ ok = security_token_has_privilege(get_current_nttok(conn),
+ SEC_PRIV_SECURITY);
+ if (!ok) {
+ DBG_DEBUG("open on %s failed - "
+ "SEC_FLAG_SYSTEM_SECURITY denied.\n",
+ smb_fname_str_dbg(smb_fname));
+ status = NT_STATUS_PRIVILEGE_NOT_HELD;
+ goto fail;
+ }
+
+ if (conn->sconn->using_smb2 &&
+ (access_mask == SEC_FLAG_SYSTEM_SECURITY))
+ {
+ /*
+ * No other bits set. Windows SMB2 refuses this.
+ * See smbtorture3 SMB2-SACL test.
+ *
+ * Note this is an SMB2-only behavior,
+ * smbtorture3 SMB1-SYSTEM-SECURITY already tests
+ * that SMB1 allows this.
+ */
+ status = NT_STATUS_ACCESS_DENIED;
+ goto fail;
+ }
+ }
+
+ /*
+ * Files or directories can't be opened DELETE_ON_CLOSE without
+ * delete access.
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=13358
+ */
+ if ((create_options & FILE_DELETE_ON_CLOSE) &&
+ ((access_mask & DELETE_ACCESS) == 0)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ if ((conn->fs_capabilities & FILE_NAMED_STREAMS)
+ && is_named_stream(smb_fname))
+ {
+ uint32_t base_create_disposition;
+ struct smb_filename *smb_fname_base = NULL;
+ uint32_t base_privflags;
+
+ if (create_options & FILE_DIRECTORY_FILE) {
+ DBG_DEBUG("Can't open a stream as directory\n");
+ status = NT_STATUS_NOT_A_DIRECTORY;
+ goto fail;
+ }
+
+ switch (create_disposition) {
+ case FILE_OPEN:
+ base_create_disposition = FILE_OPEN;
+ break;
+ default:
+ base_create_disposition = FILE_OPEN_IF;
+ break;
+ }
+
+ smb_fname_base = cp_smb_filename_nostream(
+ talloc_tos(), smb_fname);
+
+ if (smb_fname_base == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ /*
+ * We may be creating the basefile as part of creating the
+ * stream, so it's legal if the basefile doesn't exist at this
+ * point, the create_file_unixpath() below will create it. But
+ * if the basefile exists we want a handle so we can fstat() it.
+ */
+
+ ret = vfs_stat(conn, smb_fname_base);
+ if (ret == -1 && errno != ENOENT) {
+ status = map_nt_error_from_unix(errno);
+ TALLOC_FREE(smb_fname_base);
+ goto fail;
+ }
+ if (ret == 0) {
+ status = openat_pathref_fsp(conn->cwd_fsp,
+ smb_fname_base);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("open_smb_fname_fsp [%s] failed: %s\n",
+ smb_fname_str_dbg(smb_fname_base),
+ nt_errstr(status));
+ TALLOC_FREE(smb_fname_base);
+ goto fail;
+ }
+
+ /*
+ * https://bugzilla.samba.org/show_bug.cgi?id=10229
+ * We need to check if the requested access mask
+ * could be used to open the underlying file (if
+ * it existed), as we're passing in zero for the
+ * access mask to the base filename.
+ */
+ status = check_base_file_access(smb_fname_base->fsp,
+ access_mask);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("Permission check "
+ "for base %s failed: "
+ "%s\n", smb_fname->base_name,
+ nt_errstr(status)));
+ TALLOC_FREE(smb_fname_base);
+ goto fail;
+ }
+ }
+
+ base_privflags = NTCREATEX_FLAG_STREAM_BASEOPEN;
+
+ /* Open the base file. */
+ status = create_file_unixpath(conn,
+ NULL,
+ dirfsp,
+ smb_fname_base,
+ 0,
+ FILE_SHARE_READ
+ | FILE_SHARE_WRITE
+ | FILE_SHARE_DELETE,
+ base_create_disposition,
+ 0,
+ 0,
+ 0,
+ NULL,
+ 0,
+ base_privflags,
+ NULL,
+ NULL,
+ &base_fsp,
+ NULL);
+ TALLOC_FREE(smb_fname_base);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("create_file_unixpath for base %s failed: "
+ "%s\n", smb_fname->base_name,
+ nt_errstr(status)));
+ goto fail;
+ }
+ }
+
+ if (smb_fname->fsp != NULL) {
+
+ fsp = smb_fname->fsp;
+
+ /*
+ * We're about to use smb_fname->fsp for the fresh open.
+ *
+ * Every fsp passed in via smb_fname->fsp already
+ * holds a fsp->fsp_name. If it is already this
+ * fsp->fsp_name that we got passed in as our input
+ * argument smb_fname, these two are assumed to have
+ * the same lifetime: Every fsp hangs of "conn", and
+ * fsp->fsp_name is its talloc child.
+ */
+
+ if (smb_fname != smb_fname->fsp->fsp_name) {
+ /*
+ * "smb_fname" is temporary in this case, but
+ * the destructor of smb_fname would also tear
+ * down the fsp we're about to use. Unlink
+ * them from each other.
+ */
+ smb_fname_fsp_unlink(smb_fname);
+
+ /*
+ * "fsp" is ours now
+ */
+ free_fsp_on_error = true;
+ }
+
+ status = fsp_bind_smb(fsp, req);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ if (fsp_is_alternate_stream(fsp)) {
+ struct files_struct *tmp_base_fsp = fsp->base_fsp;
+
+ fsp_set_base_fsp(fsp, NULL);
+
+ fd_close(tmp_base_fsp);
+ file_free(NULL, tmp_base_fsp);
+ }
+ } else {
+ /*
+ * No fsp passed in that we can use, create one
+ */
+ status = file_new(req, conn, &fsp);
+ if(!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ free_fsp_on_error = true;
+
+ status = fsp_set_smb_fname(fsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ SMB_ASSERT(fsp->fsp_name->fsp != NULL);
+ SMB_ASSERT(fsp->fsp_name->fsp == fsp);
+
+ if (base_fsp) {
+ /*
+ * We're opening the stream element of a
+ * base_fsp we already opened. Set up the
+ * base_fsp pointer.
+ */
+ fsp_set_base_fsp(fsp, base_fsp);
+ }
+
+ if (dirfsp != NULL) {
+ status = SMB_VFS_PARENT_PATHNAME(
+ conn,
+ talloc_tos(),
+ smb_fname,
+ &parent_dir_fname,
+ &smb_fname_atname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ } else {
+ /*
+ * Get a pathref on the parent. We can re-use this for
+ * multiple calls to check parent ACLs etc. to avoid
+ * pathname calls.
+ */
+ status = parent_pathref(talloc_tos(),
+ conn->cwd_fsp,
+ smb_fname,
+ &parent_dir_fname,
+ &smb_fname_atname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ dirfsp = parent_dir_fname->fsp;
+ status = fsp_set_smb_fname(dirfsp, parent_dir_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ /*
+ * If it's a request for a directory open, deal with it separately.
+ */
+
+ if (create_options & FILE_DIRECTORY_FILE) {
+
+ if (create_options & FILE_NON_DIRECTORY_FILE) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ /* Can't open a temp directory. IFS kit test. */
+ if (!(file_attributes & FILE_FLAG_POSIX_SEMANTICS) &&
+ (file_attributes & FILE_ATTRIBUTE_TEMPORARY)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ /*
+ * We will get a create directory here if the Win32
+ * app specified a security descriptor in the
+ * CreateDirectory() call.
+ */
+
+ oplock_request = 0;
+ status = open_directory(conn,
+ req,
+ access_mask,
+ share_access,
+ create_disposition,
+ create_options,
+ file_attributes,
+ dirfsp->fsp_name,
+ smb_fname_atname,
+ &info,
+ fsp);
+ } else {
+
+ /*
+ * Ordinary file case.
+ */
+
+ if (allocation_size) {
+ fsp->initial_allocation_size = smb_roundup(fsp->conn,
+ allocation_size);
+ }
+
+ status = open_file_ntcreate(conn,
+ req,
+ access_mask,
+ share_access,
+ create_disposition,
+ create_options,
+ file_attributes,
+ oplock_request,
+ lease,
+ private_flags,
+ dirfsp->fsp_name,
+ smb_fname_atname,
+ &info,
+ fsp);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) {
+
+ /* A stream open never opens a directory */
+
+ if (base_fsp) {
+ status = NT_STATUS_FILE_IS_A_DIRECTORY;
+ goto fail;
+ }
+
+ /*
+ * Fail the open if it was explicitly a non-directory
+ * file.
+ */
+
+ if (create_options & FILE_NON_DIRECTORY_FILE) {
+ status = NT_STATUS_FILE_IS_A_DIRECTORY;
+ goto fail;
+ }
+
+ oplock_request = 0;
+ status = open_directory(conn,
+ req,
+ access_mask,
+ share_access,
+ create_disposition,
+ create_options,
+ file_attributes,
+ dirfsp->fsp_name,
+ smb_fname_atname,
+ &info,
+ fsp);
+ }
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ fsp->fsp_flags.is_fsa = true;
+
+ if ((ea_list != NULL) &&
+ ((info == FILE_WAS_CREATED) || (info == FILE_WAS_OVERWRITTEN))) {
+ status = set_ea(conn, fsp, ea_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ if (!fsp->fsp_flags.is_directory &&
+ S_ISDIR(fsp->fsp_name->st.st_ex_mode))
+ {
+ status = NT_STATUS_ACCESS_DENIED;
+ goto fail;
+ }
+
+ /* Save the requested allocation size. */
+ if ((info == FILE_WAS_CREATED) || (info == FILE_WAS_OVERWRITTEN)) {
+ if ((allocation_size > (uint64_t)fsp->fsp_name->st.st_ex_size)
+ && !(fsp->fsp_flags.is_directory))
+ {
+ fsp->initial_allocation_size = smb_roundup(
+ fsp->conn, allocation_size);
+ if (vfs_allocate_file_space(
+ fsp, fsp->initial_allocation_size) == -1) {
+ status = NT_STATUS_DISK_FULL;
+ goto fail;
+ }
+ } else {
+ fsp->initial_allocation_size = smb_roundup(
+ fsp->conn, (uint64_t)fsp->fsp_name->st.st_ex_size);
+ }
+ } else {
+ fsp->initial_allocation_size = 0;
+ }
+
+ if ((info == FILE_WAS_CREATED) &&
+ lp_nt_acl_support(SNUM(conn)) &&
+ !fsp_is_alternate_stream(fsp)) {
+ if (sd != NULL) {
+ /*
+ * According to the MS documentation, the only time the security
+ * descriptor is applied to the opened file is iff we *created* the
+ * file; an existing file stays the same.
+ *
+ * Also, it seems (from observation) that you can open the file with
+ * any access mask but you can still write the sd. We need to override
+ * the granted access before we call set_sd
+ * Patch for bug #2242 from Tom Lackemann <cessnatomny@yahoo.com>.
+ */
+
+ uint32_t sec_info_sent;
+ uint32_t saved_access_mask = fsp->access_mask;
+
+ sec_info_sent = get_sec_info(sd);
+
+ fsp->access_mask = FILE_GENERIC_ALL;
+
+ if (sec_info_sent & (SECINFO_OWNER|
+ SECINFO_GROUP|
+ SECINFO_DACL|
+ SECINFO_SACL)) {
+ status = set_sd(fsp, sd, sec_info_sent);
+ }
+
+ fsp->access_mask = saved_access_mask;
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ } else if (lp_inherit_acls(SNUM(conn))) {
+ /* Inherit from parent. Errors here are not fatal. */
+ status = inherit_new_acl(dirfsp, fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10,("inherit_new_acl: failed for %s with %s\n",
+ fsp_str_dbg(fsp),
+ nt_errstr(status) ));
+ }
+ }
+ }
+
+ if ((conn->fs_capabilities & FILE_FILE_COMPRESSION)
+ && (create_options & FILE_NO_COMPRESSION)
+ && (info == FILE_WAS_CREATED)) {
+ status = SMB_VFS_SET_COMPRESSION(conn, fsp, fsp,
+ COMPRESSION_FORMAT_NONE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("failed to disable compression: %s\n",
+ nt_errstr(status)));
+ }
+ }
+
+ DEBUG(10, ("create_file_unixpath: info=%d\n", info));
+
+ *result = fsp;
+ if (pinfo != NULL) {
+ *pinfo = info;
+ }
+
+ smb_fname->st = fsp->fsp_name->st;
+
+ TALLOC_FREE(parent_dir_fname);
+
+ return NT_STATUS_OK;
+
+ fail:
+ DEBUG(10, ("create_file_unixpath: %s\n", nt_errstr(status)));
+
+ if (fsp != NULL) {
+ /*
+ * The close_file below will close
+ * fsp->base_fsp.
+ */
+ base_fsp = NULL;
+ close_file_smb(req, fsp, ERROR_CLOSE);
+ if (free_fsp_on_error) {
+ file_free(req, fsp);
+ fsp = NULL;
+ }
+ }
+ if (base_fsp != NULL) {
+ close_file_free(req, &base_fsp, ERROR_CLOSE);
+ }
+
+ TALLOC_FREE(parent_dir_fname);
+
+ return status;
+}
+
+NTSTATUS create_file_default(connection_struct *conn,
+ struct smb_request *req,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ uint32_t access_mask,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ uint32_t file_attributes,
+ uint32_t oplock_request,
+ const struct smb2_lease *lease,
+ uint64_t allocation_size,
+ uint32_t private_flags,
+ struct security_descriptor *sd,
+ struct ea_list *ea_list,
+ files_struct **result,
+ int *pinfo,
+ const struct smb2_create_blobs *in_context_blobs,
+ struct smb2_create_blobs *out_context_blobs)
+{
+ int info = FILE_WAS_OPENED;
+ files_struct *fsp = NULL;
+ NTSTATUS status;
+ bool stream_name = false;
+ struct smb2_create_blob *posx = NULL;
+
+ DBG_DEBUG("access_mask = 0x%" PRIu32
+ " file_attributes = 0x%" PRIu32
+ " share_access = 0x%" PRIu32
+ " create_disposition = 0x%" PRIu32
+ " create_options = 0x%" PRIu32
+ " oplock_request = 0x%" PRIu32
+ " private_flags = 0x%" PRIu32
+ " ea_list = %p, sd = %p, fname = %s\n",
+ access_mask,
+ file_attributes,
+ share_access,
+ create_disposition,
+ create_options,
+ oplock_request,
+ private_flags,
+ ea_list,
+ sd,
+ smb_fname_str_dbg(smb_fname));
+
+ if (req != NULL) {
+ /*
+ * Remember the absolute time of the original request
+ * with this mid. We'll use it later to see if this
+ * has timed out.
+ */
+ get_deferred_open_message_state(req, &req->request_time, NULL);
+ }
+
+ /*
+ * Check to see if this is a mac fork of some kind.
+ */
+
+ stream_name = is_ntfs_stream_smb_fname(smb_fname);
+ if (stream_name) {
+ enum FAKE_FILE_TYPE fake_file_type;
+
+ fake_file_type = is_fake_file(smb_fname);
+
+ if (req != NULL && fake_file_type != FAKE_FILE_TYPE_NONE) {
+
+ /*
+ * Here we go! support for changing the disk quotas
+ * --metze
+ *
+ * We need to fake up to open this MAGIC QUOTA file
+ * and return a valid FID.
+ *
+ * w2k close this file directly after opening xp
+ * also tries a QUERY_FILE_INFO on the file and then
+ * close it
+ */
+ status = open_fake_file(req, conn, req->vuid,
+ fake_file_type, smb_fname,
+ access_mask, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ ZERO_STRUCT(smb_fname->st);
+ goto done;
+ }
+
+ if (!(conn->fs_capabilities & FILE_NAMED_STREAMS)) {
+ status = NT_STATUS_OBJECT_NAME_INVALID;
+ goto fail;
+ }
+ }
+
+ if (is_ntfs_default_stream_smb_fname(smb_fname)) {
+ int ret;
+ /* We have to handle this error here. */
+ if (create_options & FILE_DIRECTORY_FILE) {
+ status = NT_STATUS_NOT_A_DIRECTORY;
+ goto fail;
+ }
+ ret = vfs_stat(conn, smb_fname);
+ if (ret == 0 && VALID_STAT_OF_DIR(smb_fname->st)) {
+ status = NT_STATUS_FILE_IS_A_DIRECTORY;
+ goto fail;
+ }
+ }
+
+ posx = smb2_create_blob_find(
+ in_context_blobs, SMB2_CREATE_TAG_POSIX);
+ if (posx != NULL) {
+ uint32_t wire_mode_bits = 0;
+ mode_t mode_bits = 0;
+ SMB_STRUCT_STAT sbuf = { 0 };
+ enum perm_type ptype =
+ (create_options & FILE_DIRECTORY_FILE) ?
+ PERM_NEW_DIR : PERM_NEW_FILE;
+
+ if (posx->data.length != 4) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ wire_mode_bits = IVAL(posx->data.data, 0);
+ status = unix_perms_from_wire(
+ conn, &sbuf, wire_mode_bits, ptype, &mode_bits);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ /*
+ * Remove type info from mode, leaving only the
+ * permissions and setuid/gid bits.
+ */
+ mode_bits &= ~S_IFMT;
+
+ file_attributes = (FILE_FLAG_POSIX_SEMANTICS | mode_bits);
+ }
+
+ status = create_file_unixpath(conn,
+ req,
+ dirfsp,
+ smb_fname,
+ access_mask,
+ share_access,
+ create_disposition,
+ create_options,
+ file_attributes,
+ oplock_request,
+ lease,
+ allocation_size,
+ private_flags,
+ sd,
+ ea_list,
+ &fsp,
+ &info);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ done:
+ DEBUG(10, ("create_file: info=%d\n", info));
+
+ *result = fsp;
+ if (pinfo != NULL) {
+ *pinfo = info;
+ }
+ return NT_STATUS_OK;
+
+ fail:
+ DEBUG(10, ("create_file: %s\n", nt_errstr(status)));
+
+ if (fsp != NULL) {
+ close_file_free(req, &fsp, ERROR_CLOSE);
+ }
+ return status;
+}
diff --git a/source3/smbd/oplock_linux.c b/source3/smbd/oplock_linux.c
new file mode 100644
index 0000000..3ed2001
--- /dev/null
+++ b/source3/smbd/oplock_linux.c
@@ -0,0 +1,243 @@
+/*
+ Unix SMB/CIFS implementation.
+ kernel oplock processing for Linux
+ Copyright (C) Andrew Tridgell 2000
+
+ 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/>.
+*/
+
+#define DBGC_CLASS DBGC_LOCKING
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+
+#ifdef HAVE_KERNEL_OPLOCKS_LINUX
+
+#ifndef RT_SIGNAL_LEASE
+#define RT_SIGNAL_LEASE (SIGRTMIN+1)
+#endif
+
+/*
+ * Call to set the kernel lease signal handler
+ */
+int linux_set_lease_sighandler(int fd)
+{
+ if (fcntl(fd, F_SETSIG, RT_SIGNAL_LEASE) == -1) {
+ DBG_NOTICE("Failed to set signal handler for kernel lease\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ Call SETLEASE. If we get EACCES then we try setting up the right capability and
+ try again.
+ Use the SMB_VFS_LINUX_SETLEASE instead of this call directly.
+****************************************************************************/
+
+int linux_setlease(int fd, int leasetype)
+{
+ int ret;
+ int saved_errno = 0;
+
+ /*
+ * Ensure the lease owner is root to allow
+ * correct delivery of lease-break signals.
+ */
+
+ become_root();
+
+ /* First set the signal handler. */
+ if (linux_set_lease_sighandler(fd) == -1) {
+ saved_errno = errno;
+ ret = -1;
+ goto out;
+ }
+ ret = fcntl(fd, F_SETLEASE, leasetype);
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+
+ out:
+
+ unbecome_root();
+
+ if (ret == -1) {
+ errno = saved_errno;
+ }
+ return ret;
+}
+
+/****************************************************************************
+ * Deal with the Linux kernel <--> smbd
+ * oplock break protocol.
+****************************************************************************/
+
+static void linux_oplock_signal_handler(struct tevent_context *ev_ctx,
+ struct tevent_signal *se,
+ int signum, int count,
+ void *_info, void *private_data)
+{
+ struct kernel_oplocks *ctx =
+ talloc_get_type_abort(private_data,
+ struct kernel_oplocks);
+ struct smbd_server_connection *sconn =
+ talloc_get_type_abort(ctx->private_data,
+ struct smbd_server_connection);
+ siginfo_t *info = (siginfo_t *)_info;
+ int fd = info->si_fd;
+ files_struct *fsp;
+
+ fsp = file_find_fd(sconn, fd);
+ if (fsp == NULL) {
+ DBG_ERR("linux_oplock_signal_handler: failed to find fsp for file fd=%d (file was closed ?)\n", fd );
+ return;
+ }
+ break_kernel_oplock(sconn->msg_ctx, fsp);
+}
+
+/****************************************************************************
+ Attempt to set an kernel oplock on a file.
+****************************************************************************/
+
+static bool linux_set_kernel_oplock(struct kernel_oplocks *ctx,
+ files_struct *fsp, int oplock_type)
+{
+ struct file_id_buf idbuf;
+
+ if ( SMB_VFS_LINUX_SETLEASE(fsp, F_WRLCK) == -1) {
+ DBG_NOTICE("Refused oplock on file %s, "
+ "fd = %d, file_id = %s. (%s)\n",
+ fsp_str_dbg(fsp),
+ fsp_get_io_fd(fsp),
+ file_id_str_buf(fsp->file_id, &idbuf),
+ strerror(errno));
+ return False;
+ }
+
+ DBG_NOTICE("got kernel oplock on file %s, "
+ "file_id = %s gen_id = %"PRIu64"\n",
+ fsp_str_dbg(fsp),
+ file_id_str_buf(fsp->file_id, &idbuf),
+ fh_get_gen_id(fsp->fh));
+
+ return True;
+}
+
+/****************************************************************************
+ Release a kernel oplock on a file.
+****************************************************************************/
+
+static void linux_release_kernel_oplock(struct kernel_oplocks *ctx,
+ files_struct *fsp, int oplock_type)
+{
+ struct file_id_buf idbuf;
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ /*
+ * Check and print out the current kernel
+ * oplock state of this file.
+ */
+ int state = fcntl(fsp_get_io_fd(fsp), F_GETLEASE, 0);
+ dbgtext("linux_release_kernel_oplock: file %s, file_id = %s "
+ "gen_id = %"PRIu64" has kernel oplock state "
+ "of %x.\n",
+ fsp_str_dbg(fsp),
+ file_id_str_buf(fsp->file_id, &idbuf),
+ fh_get_gen_id(fsp->fh),
+ state);
+ }
+
+ /*
+ * Remove the kernel oplock on this file.
+ */
+ if ( SMB_VFS_LINUX_SETLEASE(fsp, F_UNLCK) == -1) {
+ if (DEBUGLVL(DBGLVL_ERR)) {
+ dbgtext("linux_release_kernel_oplock: Error when "
+ "removing kernel oplock on file " );
+ dbgtext("%s, file_id = %s, gen_id = %"PRIu64". "
+ "Error was %s\n",
+ fsp_str_dbg(fsp),
+ file_id_str_buf(fsp->file_id, &idbuf),
+ fh_get_gen_id(fsp->fh),
+ strerror(errno));
+ }
+ }
+}
+
+/****************************************************************************
+ See if the kernel supports oplocks.
+****************************************************************************/
+
+static bool linux_oplocks_available(void)
+{
+ int fd, ret;
+ fd = open("/dev/null", O_RDONLY);
+ if (fd == -1)
+ return False; /* uggh! */
+ ret = fcntl(fd, F_GETLEASE, 0);
+ close(fd);
+ return ret == F_UNLCK;
+}
+
+/****************************************************************************
+ Setup kernel oplocks.
+****************************************************************************/
+
+static const struct kernel_oplocks_ops linux_koplocks = {
+ .set_oplock = linux_set_kernel_oplock,
+ .release_oplock = linux_release_kernel_oplock,
+};
+
+struct kernel_oplocks *linux_init_kernel_oplocks(struct smbd_server_connection *sconn)
+{
+ struct kernel_oplocks *ctx;
+ struct tevent_signal *se;
+
+ if (!linux_oplocks_available()) {
+ DBG_NOTICE("Linux kernel oplocks not available\n");
+ return NULL;
+ }
+
+ ctx = talloc_zero(sconn, struct kernel_oplocks);
+ if (!ctx) {
+ DBG_ERR("Linux Kernel oplocks talloc_Zero failed\n");
+ return NULL;
+ }
+
+ ctx->ops = &linux_koplocks;
+ ctx->private_data = sconn;
+
+ se = tevent_add_signal(sconn->ev_ctx,
+ ctx,
+ RT_SIGNAL_LEASE, SA_SIGINFO,
+ linux_oplock_signal_handler,
+ ctx);
+ if (!se) {
+ DBG_ERR("Failed to setup RT_SIGNAL_LEASE handler\n");
+ TALLOC_FREE(ctx);
+ return NULL;
+ }
+
+ DBG_NOTICE("Linux kernel oplocks enabled\n");
+
+ return ctx;
+}
+#else
+ void oplock_linux_dummy(void);
+
+ void oplock_linux_dummy(void) {}
+#endif /* HAVE_KERNEL_OPLOCKS_LINUX */
diff --git a/source3/smbd/password.c b/source3/smbd/password.c
new file mode 100644
index 0000000..9709a51
--- /dev/null
+++ b/source3/smbd/password.c
@@ -0,0 +1,91 @@
+/*
+ Unix SMB/CIFS implementation.
+ Password and authentication handling
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 2007.
+
+ 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 "system/passwd.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../librpc/gen_ndr/netlogon.h"
+#include "auth.h"
+#include "../libcli/security/security.h"
+
+/****************************************************************************
+ Invalidate a uid.
+****************************************************************************/
+
+void invalidate_vuid(struct smbd_server_connection *sconn, uint64_t vuid)
+{
+ struct smbXsrv_session *session = NULL;
+ NTSTATUS status;
+
+ status = get_valid_smbXsrv_session(sconn->client, vuid, &session);
+ if (!NT_STATUS_IS_OK(status)) {
+ return;
+ }
+
+ session_yield(session);
+
+ SMB_ASSERT(sconn->num_users > 0);
+ sconn->num_users--;
+
+ /* clear the vuid from the 'cache' on each connection, and
+ from the vuid 'owner' of connections */
+ conn_clear_vuid_caches(sconn, vuid);
+}
+
+int register_homes_share(const char *username)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int result;
+ struct passwd *pwd;
+
+ result = lp_servicenumber(username);
+ if (result != -1) {
+ DEBUG(3, ("Using static (or previously created) service for "
+ "user '%s'; path = '%s'\n", username,
+ lp_path(talloc_tos(), lp_sub, result)));
+ return result;
+ }
+
+ pwd = Get_Pwnam_alloc(talloc_tos(), username);
+
+ if ((pwd == NULL) || (pwd->pw_dir[0] == '\0')) {
+ DEBUG(3, ("No home directory defined for user '%s'\n",
+ username));
+ TALLOC_FREE(pwd);
+ return -1;
+ }
+
+ if (strequal(pwd->pw_dir, "/")) {
+ DBG_NOTICE("Invalid home directory defined for user '%s'\n",
+ username);
+ TALLOC_FREE(pwd);
+ return -1;
+ }
+
+ DEBUG(3, ("Adding homes service for user '%s' using home directory: "
+ "'%s'\n", username, pwd->pw_dir));
+
+ result = add_home_service(username, username, pwd->pw_dir);
+
+ TALLOC_FREE(pwd);
+ return result;
+}
diff --git a/source3/smbd/posix_acls.c b/source3/smbd/posix_acls.c
new file mode 100644
index 0000000..d275bdb
--- /dev/null
+++ b/source3/smbd/posix_acls.c
@@ -0,0 +1,4862 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB NT Security Descriptor / Unix permission conversion.
+ Copyright (C) Jeremy Allison 1994-2009.
+ Copyright (C) Andreas Gruenbacher 2002.
+ Copyright (C) Simo Sorce <idra@samba.org> 2009.
+
+ 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 "smbd/smbd.h"
+#include "system/filesys.h"
+#include "../libcli/security/security.h"
+#include "trans2.h"
+#include "passdb/lookup_sid.h"
+#include "auth.h"
+#include "../librpc/gen_ndr/idmap.h"
+#include "../librpc/gen_ndr/ndr_smb_acl.h"
+#include "lib/param/loadparm.h"
+
+extern const struct generic_mapping file_generic_mapping;
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_ACLS
+
+/****************************************************************************
+ Data structures representing the internal ACE format.
+****************************************************************************/
+
+enum ace_owner {UID_ACE, GID_ACE, WORLD_ACE};
+enum ace_attribute {ALLOW_ACE, DENY_ACE}; /* Used for incoming NT ACLS. */
+
+typedef struct canon_ace {
+ struct canon_ace *next, *prev;
+ SMB_ACL_TAG_T type;
+ mode_t perms; /* Only use S_I(R|W|X)USR mode bits here. */
+ struct dom_sid trustee;
+ enum ace_owner owner_type;
+ enum ace_attribute attr;
+ struct unixid unix_ug;
+ uint8_t ace_flags; /* From windows ACE entry. */
+} canon_ace;
+
+#define ALL_ACE_PERMS (S_IRUSR|S_IWUSR|S_IXUSR)
+
+/*
+ * EA format of user.SAMBA_PAI (Samba_Posix_Acl_Interitance)
+ * attribute on disk - version 1.
+ * All values are little endian.
+ *
+ * | 1 | 1 | 2 | 2 | ....
+ * +------+------+-------------+---------------------+-------------+--------------------+
+ * | vers | flag | num_entries | num_default_entries | ..entries.. | default_entries... |
+ * +------+------+-------------+---------------------+-------------+--------------------+
+ *
+ * Entry format is :
+ *
+ * | 1 | 4 |
+ * +------+-------------------+
+ * | value| uid/gid or world |
+ * | type | value |
+ * +------+-------------------+
+ *
+ * Version 2 format. Stores extra Windows metadata about an ACL.
+ *
+ * | 1 | 2 | 2 | 2 | ....
+ * +------+----------+-------------+---------------------+-------------+--------------------+
+ * | vers | ace | num_entries | num_default_entries | ..entries.. | default_entries... |
+ * | 2 | type | | | | |
+ * +------+----------+-------------+---------------------+-------------+--------------------+
+ *
+ * Entry format is :
+ *
+ * | 1 | 1 | 4 |
+ * +------+------+-------------------+
+ * | ace | value| uid/gid or world |
+ * | flag | type | value |
+ * +------+-------------------+------+
+ *
+ */
+
+#define PAI_VERSION_OFFSET 0
+
+#define PAI_V1_FLAG_OFFSET 1
+#define PAI_V1_NUM_ENTRIES_OFFSET 2
+#define PAI_V1_NUM_DEFAULT_ENTRIES_OFFSET 4
+#define PAI_V1_ENTRIES_BASE 6
+#define PAI_V1_ACL_FLAG_PROTECTED 0x1
+#define PAI_V1_ENTRY_LENGTH 5
+
+#define PAI_V1_VERSION 1
+
+#define PAI_V2_TYPE_OFFSET 1
+#define PAI_V2_NUM_ENTRIES_OFFSET 3
+#define PAI_V2_NUM_DEFAULT_ENTRIES_OFFSET 5
+#define PAI_V2_ENTRIES_BASE 7
+#define PAI_V2_ENTRY_LENGTH 6
+
+#define PAI_V2_VERSION 2
+
+/*
+ * In memory format of user.SAMBA_PAI attribute.
+ */
+
+struct pai_entry {
+ struct pai_entry *next, *prev;
+ uint8_t ace_flags;
+ enum ace_owner owner_type;
+ struct unixid unix_ug;
+};
+
+struct pai_val {
+ uint16_t sd_type;
+ unsigned int num_entries;
+ struct pai_entry *entry_list;
+ unsigned int num_def_entries;
+ struct pai_entry *def_entry_list;
+};
+
+/************************************************************************
+ Return a uint32_t of the pai_entry principal.
+************************************************************************/
+
+static uint32_t get_pai_entry_val(struct pai_entry *paie)
+{
+ switch (paie->owner_type) {
+ case UID_ACE:
+ DEBUG(10,("get_pai_entry_val: uid = %u\n", (unsigned int)paie->unix_ug.id ));
+ return (uint32_t)paie->unix_ug.id;
+ case GID_ACE:
+ DEBUG(10,("get_pai_entry_val: gid = %u\n", (unsigned int)paie->unix_ug.id ));
+ return (uint32_t)paie->unix_ug.id;
+ case WORLD_ACE:
+ default:
+ DEBUG(10,("get_pai_entry_val: world ace\n"));
+ return (uint32_t)-1;
+ }
+}
+
+/************************************************************************
+ Return a uint32_t of the entry principal.
+************************************************************************/
+
+static uint32_t get_entry_val(canon_ace *ace_entry)
+{
+ switch (ace_entry->owner_type) {
+ case UID_ACE:
+ DEBUG(10,("get_entry_val: uid = %u\n", (unsigned int)ace_entry->unix_ug.id ));
+ return (uint32_t)ace_entry->unix_ug.id;
+ case GID_ACE:
+ DEBUG(10,("get_entry_val: gid = %u\n", (unsigned int)ace_entry->unix_ug.id ));
+ return (uint32_t)ace_entry->unix_ug.id;
+ case WORLD_ACE:
+ default:
+ DEBUG(10,("get_entry_val: world ace\n"));
+ return (uint32_t)-1;
+ }
+}
+
+/************************************************************************
+ Create the on-disk format (always v2 now). Caller must free.
+************************************************************************/
+
+static char *create_pai_buf_v2(canon_ace *file_ace_list,
+ canon_ace *dir_ace_list,
+ uint16_t sd_type,
+ size_t *store_size)
+{
+ char *pai_buf = NULL;
+ canon_ace *ace_list = NULL;
+ char *entry_offset = NULL;
+ unsigned int num_entries = 0;
+ unsigned int num_def_entries = 0;
+ unsigned int i;
+
+ for (ace_list = file_ace_list; ace_list; ace_list = ace_list->next) {
+ num_entries++;
+ }
+
+ for (ace_list = dir_ace_list; ace_list; ace_list = ace_list->next) {
+ num_def_entries++;
+ }
+
+ DEBUG(10,("create_pai_buf_v2: num_entries = %u, num_def_entries = %u\n", num_entries, num_def_entries ));
+
+ *store_size = PAI_V2_ENTRIES_BASE +
+ ((num_entries + num_def_entries)*PAI_V2_ENTRY_LENGTH);
+
+ pai_buf = talloc_array(talloc_tos(), char, *store_size);
+ if (!pai_buf) {
+ return NULL;
+ }
+
+ /* Set up the header. */
+ memset(pai_buf, '\0', PAI_V2_ENTRIES_BASE);
+ SCVAL(pai_buf,PAI_VERSION_OFFSET,PAI_V2_VERSION);
+ SSVAL(pai_buf,PAI_V2_TYPE_OFFSET, sd_type);
+ SSVAL(pai_buf,PAI_V2_NUM_ENTRIES_OFFSET,num_entries);
+ SSVAL(pai_buf,PAI_V2_NUM_DEFAULT_ENTRIES_OFFSET,num_def_entries);
+
+ DEBUG(10,("create_pai_buf_v2: sd_type = 0x%x\n",
+ (unsigned int)sd_type ));
+
+ entry_offset = pai_buf + PAI_V2_ENTRIES_BASE;
+
+ i = 0;
+ for (ace_list = file_ace_list; ace_list; ace_list = ace_list->next) {
+ uint8_t type_val = (uint8_t)ace_list->owner_type;
+ uint32_t entry_val = get_entry_val(ace_list);
+
+ SCVAL(entry_offset,0,ace_list->ace_flags);
+ SCVAL(entry_offset,1,type_val);
+ SIVAL(entry_offset,2,entry_val);
+ DEBUG(10,("create_pai_buf_v2: entry %u [0x%x] [0x%x] [0x%x]\n",
+ i,
+ (unsigned int)ace_list->ace_flags,
+ (unsigned int)type_val,
+ (unsigned int)entry_val ));
+ i++;
+ entry_offset += PAI_V2_ENTRY_LENGTH;
+ }
+
+ for (ace_list = dir_ace_list; ace_list; ace_list = ace_list->next) {
+ uint8_t type_val = (uint8_t)ace_list->owner_type;
+ uint32_t entry_val = get_entry_val(ace_list);
+
+ SCVAL(entry_offset,0,ace_list->ace_flags);
+ SCVAL(entry_offset,1,type_val);
+ SIVAL(entry_offset,2,entry_val);
+ DEBUG(10,("create_pai_buf_v2: entry %u [0x%x] [0x%x] [0x%x]\n",
+ i,
+ (unsigned int)ace_list->ace_flags,
+ (unsigned int)type_val,
+ (unsigned int)entry_val ));
+ i++;
+ entry_offset += PAI_V2_ENTRY_LENGTH;
+ }
+
+ return pai_buf;
+}
+
+/************************************************************************
+ Store the user.SAMBA_PAI attribute on disk.
+************************************************************************/
+
+static void store_inheritance_attributes(files_struct *fsp,
+ canon_ace *file_ace_list,
+ canon_ace *dir_ace_list,
+ uint16_t sd_type)
+{
+ int ret;
+ size_t store_size;
+ char *pai_buf;
+
+ if (!lp_map_acl_inherit(SNUM(fsp->conn))) {
+ return;
+ }
+
+ pai_buf = create_pai_buf_v2(file_ace_list, dir_ace_list,
+ sd_type, &store_size);
+
+ ret = SMB_VFS_FSETXATTR(fsp, SAMBA_POSIX_INHERITANCE_EA_NAME,
+ pai_buf, store_size, 0);
+
+ TALLOC_FREE(pai_buf);
+
+ DEBUG(10,("store_inheritance_attribute: type 0x%x for file %s\n",
+ (unsigned int)sd_type,
+ fsp_str_dbg(fsp)));
+
+ if (ret == -1 && !no_acl_syscall_error(errno)) {
+ DEBUG(1,("store_inheritance_attribute: Error %s\n", strerror(errno) ));
+ }
+}
+
+/************************************************************************
+ Delete the in memory inheritance info.
+************************************************************************/
+
+static void free_inherited_info(struct pai_val *pal)
+{
+ if (pal) {
+ struct pai_entry *paie, *paie_next;
+ for (paie = pal->entry_list; paie; paie = paie_next) {
+ paie_next = paie->next;
+ TALLOC_FREE(paie);
+ }
+ for (paie = pal->def_entry_list; paie; paie = paie_next) {
+ paie_next = paie->next;
+ TALLOC_FREE(paie);
+ }
+ TALLOC_FREE(pal);
+ }
+}
+
+/************************************************************************
+ Get any stored ACE flags.
+************************************************************************/
+
+static uint16_t get_pai_flags(struct pai_val *pal, canon_ace *ace_entry, bool default_ace)
+{
+ struct pai_entry *paie;
+
+ if (!pal) {
+ return 0;
+ }
+
+ /* If the entry exists it is inherited. */
+ for (paie = (default_ace ? pal->def_entry_list : pal->entry_list); paie; paie = paie->next) {
+ if (ace_entry->owner_type == paie->owner_type &&
+ get_entry_val(ace_entry) == get_pai_entry_val(paie))
+ return paie->ace_flags;
+ }
+ return 0;
+}
+
+/************************************************************************
+ Ensure an attribute just read is valid - v1.
+************************************************************************/
+
+static bool check_pai_ok_v1(const char *pai_buf, size_t pai_buf_data_size)
+{
+ uint16_t num_entries;
+ uint16_t num_def_entries;
+
+ if (pai_buf_data_size < PAI_V1_ENTRIES_BASE) {
+ /* Corrupted - too small. */
+ return false;
+ }
+
+ if (CVAL(pai_buf,PAI_VERSION_OFFSET) != PAI_V1_VERSION) {
+ return false;
+ }
+
+ num_entries = SVAL(pai_buf,PAI_V1_NUM_ENTRIES_OFFSET);
+ num_def_entries = SVAL(pai_buf,PAI_V1_NUM_DEFAULT_ENTRIES_OFFSET);
+
+ /* Check the entry lists match. */
+ /* Each entry is 5 bytes (type plus 4 bytes of uid or gid). */
+
+ if (((num_entries + num_def_entries)*PAI_V1_ENTRY_LENGTH) +
+ PAI_V1_ENTRIES_BASE != pai_buf_data_size) {
+ return false;
+ }
+
+ return true;
+}
+
+/************************************************************************
+ Ensure an attribute just read is valid - v2.
+************************************************************************/
+
+static bool check_pai_ok_v2(const char *pai_buf, size_t pai_buf_data_size)
+{
+ uint16_t num_entries;
+ uint16_t num_def_entries;
+
+ if (pai_buf_data_size < PAI_V2_ENTRIES_BASE) {
+ /* Corrupted - too small. */
+ return false;
+ }
+
+ if (CVAL(pai_buf,PAI_VERSION_OFFSET) != PAI_V2_VERSION) {
+ return false;
+ }
+
+ num_entries = SVAL(pai_buf,PAI_V2_NUM_ENTRIES_OFFSET);
+ num_def_entries = SVAL(pai_buf,PAI_V2_NUM_DEFAULT_ENTRIES_OFFSET);
+
+ /* Check the entry lists match. */
+ /* Each entry is 6 bytes (flags + type + 4 bytes of uid or gid). */
+
+ if (((num_entries + num_def_entries)*PAI_V2_ENTRY_LENGTH) +
+ PAI_V2_ENTRIES_BASE != pai_buf_data_size) {
+ return false;
+ }
+
+ return true;
+}
+
+/************************************************************************
+ Decode the owner.
+************************************************************************/
+
+static bool get_pai_owner_type(struct pai_entry *paie, const char *entry_offset)
+{
+ paie->owner_type = (enum ace_owner)CVAL(entry_offset,0);
+ switch( paie->owner_type) {
+ case UID_ACE:
+ paie->unix_ug.type = ID_TYPE_UID;
+ paie->unix_ug.id = (uid_t)IVAL(entry_offset,1);
+ DEBUG(10,("get_pai_owner_type: uid = %u\n",
+ (unsigned int)paie->unix_ug.id ));
+ break;
+ case GID_ACE:
+ paie->unix_ug.type = ID_TYPE_GID;
+ paie->unix_ug.id = (gid_t)IVAL(entry_offset,1);
+ DEBUG(10,("get_pai_owner_type: gid = %u\n",
+ (unsigned int)paie->unix_ug.id ));
+ break;
+ case WORLD_ACE:
+ paie->unix_ug.type = ID_TYPE_NOT_SPECIFIED;
+ paie->unix_ug.id = -1;
+ DEBUG(10,("get_pai_owner_type: world ace\n"));
+ break;
+ default:
+ DEBUG(10,("get_pai_owner_type: unknown type %u\n",
+ (unsigned int)paie->owner_type ));
+ return false;
+ }
+ return true;
+}
+
+/************************************************************************
+ Process v2 entries.
+************************************************************************/
+
+static const char *create_pai_v1_entries(struct pai_val *paiv,
+ const char *entry_offset,
+ bool def_entry)
+{
+ unsigned int i;
+
+ for (i = 0; i < paiv->num_entries; i++) {
+ struct pai_entry *paie = talloc(talloc_tos(), struct pai_entry);
+ if (!paie) {
+ return NULL;
+ }
+
+ paie->ace_flags = SEC_ACE_FLAG_INHERITED_ACE;
+ if (!get_pai_owner_type(paie, entry_offset)) {
+ TALLOC_FREE(paie);
+ return NULL;
+ }
+
+ if (!def_entry) {
+ DLIST_ADD(paiv->entry_list, paie);
+ } else {
+ DLIST_ADD(paiv->def_entry_list, paie);
+ }
+ entry_offset += PAI_V1_ENTRY_LENGTH;
+ }
+ return entry_offset;
+}
+
+/************************************************************************
+ Convert to in-memory format from version 1.
+************************************************************************/
+
+static struct pai_val *create_pai_val_v1(const char *buf, size_t size)
+{
+ const char *entry_offset;
+ struct pai_val *paiv = NULL;
+
+ if (!check_pai_ok_v1(buf, size)) {
+ return NULL;
+ }
+
+ paiv = talloc(talloc_tos(), struct pai_val);
+ if (!paiv) {
+ return NULL;
+ }
+
+ memset(paiv, '\0', sizeof(struct pai_val));
+
+ paiv->sd_type = (CVAL(buf,PAI_V1_FLAG_OFFSET) == PAI_V1_ACL_FLAG_PROTECTED) ?
+ SEC_DESC_DACL_PROTECTED : 0;
+
+ paiv->num_entries = SVAL(buf,PAI_V1_NUM_ENTRIES_OFFSET);
+ paiv->num_def_entries = SVAL(buf,PAI_V1_NUM_DEFAULT_ENTRIES_OFFSET);
+
+ entry_offset = buf + PAI_V1_ENTRIES_BASE;
+
+ DEBUG(10,("create_pai_val: num_entries = %u, num_def_entries = %u\n",
+ paiv->num_entries, paiv->num_def_entries ));
+
+ entry_offset = create_pai_v1_entries(paiv, entry_offset, false);
+ if (entry_offset == NULL) {
+ free_inherited_info(paiv);
+ return NULL;
+ }
+ entry_offset = create_pai_v1_entries(paiv, entry_offset, true);
+ if (entry_offset == NULL) {
+ free_inherited_info(paiv);
+ return NULL;
+ }
+
+ return paiv;
+}
+
+/************************************************************************
+ Process v2 entries.
+************************************************************************/
+
+static const char *create_pai_v2_entries(struct pai_val *paiv,
+ unsigned int num_entries,
+ const char *entry_offset,
+ bool def_entry)
+{
+ unsigned int i;
+
+ for (i = 0; i < num_entries; i++) {
+ struct pai_entry *paie = talloc(talloc_tos(), struct pai_entry);
+ if (!paie) {
+ return NULL;
+ }
+
+ paie->ace_flags = CVAL(entry_offset,0);
+
+ if (!get_pai_owner_type(paie, entry_offset+1)) {
+ TALLOC_FREE(paie);
+ return NULL;
+ }
+ if (!def_entry) {
+ DLIST_ADD(paiv->entry_list, paie);
+ } else {
+ DLIST_ADD(paiv->def_entry_list, paie);
+ }
+ entry_offset += PAI_V2_ENTRY_LENGTH;
+ }
+ return entry_offset;
+}
+
+/************************************************************************
+ Convert to in-memory format from version 2.
+************************************************************************/
+
+static struct pai_val *create_pai_val_v2(const char *buf, size_t size)
+{
+ const char *entry_offset;
+ struct pai_val *paiv = NULL;
+
+ if (!check_pai_ok_v2(buf, size)) {
+ return NULL;
+ }
+
+ paiv = talloc(talloc_tos(), struct pai_val);
+ if (!paiv) {
+ return NULL;
+ }
+
+ memset(paiv, '\0', sizeof(struct pai_val));
+
+ paiv->sd_type = SVAL(buf,PAI_V2_TYPE_OFFSET);
+
+ paiv->num_entries = SVAL(buf,PAI_V2_NUM_ENTRIES_OFFSET);
+ paiv->num_def_entries = SVAL(buf,PAI_V2_NUM_DEFAULT_ENTRIES_OFFSET);
+
+ entry_offset = buf + PAI_V2_ENTRIES_BASE;
+
+ DEBUG(10,("create_pai_val_v2: sd_type = 0x%x num_entries = %u, num_def_entries = %u\n",
+ (unsigned int)paiv->sd_type,
+ paiv->num_entries, paiv->num_def_entries ));
+
+ entry_offset = create_pai_v2_entries(paiv, paiv->num_entries,
+ entry_offset, false);
+ if (entry_offset == NULL) {
+ free_inherited_info(paiv);
+ return NULL;
+ }
+ entry_offset = create_pai_v2_entries(paiv, paiv->num_def_entries,
+ entry_offset, true);
+ if (entry_offset == NULL) {
+ free_inherited_info(paiv);
+ return NULL;
+ }
+
+ return paiv;
+}
+
+/************************************************************************
+ Convert to in-memory format - from either version 1 or 2.
+************************************************************************/
+
+static struct pai_val *create_pai_val(const char *buf, size_t size)
+{
+ if (size < 1) {
+ return NULL;
+ }
+ if (CVAL(buf,PAI_VERSION_OFFSET) == PAI_V1_VERSION) {
+ return create_pai_val_v1(buf, size);
+ } else if (CVAL(buf,PAI_VERSION_OFFSET) == PAI_V2_VERSION) {
+ return create_pai_val_v2(buf, size);
+ } else {
+ return NULL;
+ }
+}
+
+/************************************************************************
+ Load the user.SAMBA_PAI attribute.
+************************************************************************/
+
+static struct pai_val *fload_inherited_info(files_struct *fsp)
+{
+ char *pai_buf;
+ size_t pai_buf_size = 1024;
+ struct pai_val *paiv = NULL;
+ ssize_t ret;
+
+ if (!lp_map_acl_inherit(SNUM(fsp->conn))) {
+ return NULL;
+ }
+
+ if ((pai_buf = talloc_array(talloc_tos(), char, pai_buf_size)) == NULL) {
+ return NULL;
+ }
+
+ do {
+ ret = SMB_VFS_FGETXATTR(fsp, SAMBA_POSIX_INHERITANCE_EA_NAME,
+ pai_buf, pai_buf_size);
+ if (ret == -1) {
+ if (errno != ERANGE) {
+ break;
+ }
+ /* Buffer too small - enlarge it. */
+ pai_buf_size *= 2;
+ TALLOC_FREE(pai_buf);
+ if (pai_buf_size > 1024*1024) {
+ return NULL; /* Limit malloc to 1mb. */
+ }
+ if ((pai_buf = talloc_array(talloc_tos(), char, pai_buf_size)) == NULL)
+ return NULL;
+ }
+ } while (ret == -1);
+
+ DEBUG(10,("load_inherited_info: ret = %lu for file %s\n",
+ (unsigned long)ret, fsp_str_dbg(fsp)));
+
+ if (ret == -1) {
+ /* No attribute or not supported. */
+#if defined(ENOATTR)
+ if (errno != ENOATTR)
+ DEBUG(10,("load_inherited_info: Error %s\n", strerror(errno) ));
+#else
+ if (errno != ENOSYS)
+ DEBUG(10,("load_inherited_info: Error %s\n", strerror(errno) ));
+#endif
+ TALLOC_FREE(pai_buf);
+ return NULL;
+ }
+
+ paiv = create_pai_val(pai_buf, ret);
+
+ if (paiv) {
+ DEBUG(10,("load_inherited_info: ACL type is 0x%x for file %s\n",
+ (unsigned int)paiv->sd_type, fsp_str_dbg(fsp)));
+ }
+
+ TALLOC_FREE(pai_buf);
+ return paiv;
+}
+
+/****************************************************************************
+ Functions to manipulate the internal ACE format.
+****************************************************************************/
+
+/****************************************************************************
+ Count a linked list of canonical ACE entries.
+****************************************************************************/
+
+static size_t count_canon_ace_list( canon_ace *l_head )
+{
+ size_t count = 0;
+ canon_ace *ace;
+
+ for (ace = l_head; ace; ace = ace->next)
+ count++;
+
+ return count;
+}
+
+/****************************************************************************
+ Free a linked list of canonical ACE entries.
+****************************************************************************/
+
+static void free_canon_ace_list( canon_ace *l_head )
+{
+ canon_ace *list, *next;
+
+ for (list = l_head; list; list = next) {
+ next = list->next;
+ DLIST_REMOVE(l_head, list);
+ TALLOC_FREE(list);
+ }
+}
+
+/****************************************************************************
+ Function to duplicate a canon_ace entry.
+****************************************************************************/
+
+static canon_ace *dup_canon_ace( canon_ace *src_ace)
+{
+ canon_ace *dst_ace = talloc(talloc_tos(), canon_ace);
+
+ if (dst_ace == NULL)
+ return NULL;
+
+ *dst_ace = *src_ace;
+ dst_ace->prev = dst_ace->next = NULL;
+ return dst_ace;
+}
+
+/****************************************************************************
+ Print out a canon ace.
+****************************************************************************/
+
+static void print_canon_ace(canon_ace *pace, int num)
+{
+ struct dom_sid_buf buf;
+ dbgtext( "canon_ace index %d. Type = %s ", num, pace->attr == ALLOW_ACE ? "allow" : "deny" );
+ dbgtext( "SID = %s ", dom_sid_str_buf(&pace->trustee, &buf));
+ if (pace->owner_type == UID_ACE) {
+ dbgtext( "uid %u ", (unsigned int)pace->unix_ug.id);
+ } else if (pace->owner_type == GID_ACE) {
+ dbgtext( "gid %u ", (unsigned int)pace->unix_ug.id);
+ } else
+ dbgtext( "other ");
+ switch (pace->type) {
+ case SMB_ACL_USER:
+ dbgtext( "SMB_ACL_USER ");
+ break;
+ case SMB_ACL_USER_OBJ:
+ dbgtext( "SMB_ACL_USER_OBJ ");
+ break;
+ case SMB_ACL_GROUP:
+ dbgtext( "SMB_ACL_GROUP ");
+ break;
+ case SMB_ACL_GROUP_OBJ:
+ dbgtext( "SMB_ACL_GROUP_OBJ ");
+ break;
+ case SMB_ACL_OTHER:
+ dbgtext( "SMB_ACL_OTHER ");
+ break;
+ default:
+ dbgtext( "MASK " );
+ break;
+ }
+
+ dbgtext( "ace_flags = 0x%x ", (unsigned int)pace->ace_flags);
+ dbgtext( "perms ");
+ dbgtext( "%c", pace->perms & S_IRUSR ? 'r' : '-');
+ dbgtext( "%c", pace->perms & S_IWUSR ? 'w' : '-');
+ dbgtext( "%c\n", pace->perms & S_IXUSR ? 'x' : '-');
+}
+
+/****************************************************************************
+ Print out a canon ace list.
+****************************************************************************/
+
+static void print_canon_ace_list(const char *name, canon_ace *ace_list)
+{
+ int count = 0;
+
+ if( DEBUGLVL( 10 )) {
+ dbgtext( "print_canon_ace_list: %s\n", name );
+ for (;ace_list; ace_list = ace_list->next, count++)
+ print_canon_ace(ace_list, count );
+ }
+}
+
+/****************************************************************************
+ Map POSIX ACL perms to canon_ace permissions (a mode_t containing only S_(R|W|X)USR bits).
+****************************************************************************/
+
+static mode_t convert_permset_to_mode_t(SMB_ACL_PERMSET_T permset)
+{
+ mode_t ret = 0;
+
+ ret |= (sys_acl_get_perm(permset, SMB_ACL_READ) ? S_IRUSR : 0);
+ ret |= (sys_acl_get_perm(permset, SMB_ACL_WRITE) ? S_IWUSR : 0);
+ ret |= (sys_acl_get_perm(permset, SMB_ACL_EXECUTE) ? S_IXUSR : 0);
+
+ return ret;
+}
+
+/****************************************************************************
+ Map generic UNIX permissions to canon_ace permissions (a mode_t containing only S_(R|W|X)USR bits).
+****************************************************************************/
+
+mode_t unix_perms_to_acl_perms(mode_t mode, int r_mask, int w_mask, int x_mask)
+{
+ mode_t ret = 0;
+
+ if (mode & r_mask)
+ ret |= S_IRUSR;
+ if (mode & w_mask)
+ ret |= S_IWUSR;
+ if (mode & x_mask)
+ ret |= S_IXUSR;
+
+ return ret;
+}
+
+/****************************************************************************
+ Map canon_ace permissions (a mode_t containing only S_(R|W|X)USR bits) to
+ an SMB_ACL_PERMSET_T.
+****************************************************************************/
+
+int map_acl_perms_to_permset(mode_t mode, SMB_ACL_PERMSET_T *p_permset)
+{
+ if (sys_acl_clear_perms(*p_permset) == -1)
+ return -1;
+ if (mode & S_IRUSR) {
+ if (sys_acl_add_perm(*p_permset, SMB_ACL_READ) == -1)
+ return -1;
+ }
+ if (mode & S_IWUSR) {
+ if (sys_acl_add_perm(*p_permset, SMB_ACL_WRITE) == -1)
+ return -1;
+ }
+ if (mode & S_IXUSR) {
+ if (sys_acl_add_perm(*p_permset, SMB_ACL_EXECUTE) == -1)
+ return -1;
+ }
+ return 0;
+}
+
+/****************************************************************************
+ Function to create owner and group SIDs from a SMB_STRUCT_STAT.
+****************************************************************************/
+
+static void create_file_sids(const SMB_STRUCT_STAT *psbuf,
+ struct dom_sid *powner_sid,
+ struct dom_sid *pgroup_sid)
+{
+ uid_to_sid( powner_sid, psbuf->st_ex_uid );
+ gid_to_sid( pgroup_sid, psbuf->st_ex_gid );
+}
+
+/****************************************************************************
+ Merge aces with a common UID or GID - if both are allow or deny, OR the permissions together and
+ delete the second one. If the first is deny, mask the permissions off and delete the allow
+ if the permissions become zero, delete the deny if the permissions are non zero.
+****************************************************************************/
+
+static void merge_aces( canon_ace **pp_list_head, bool dir_acl)
+{
+ canon_ace *l_head = *pp_list_head;
+ canon_ace *curr_ace_outer;
+ canon_ace *curr_ace_outer_next;
+
+ /*
+ * First, merge allow entries with identical SIDs, and deny entries
+ * with identical SIDs.
+ */
+
+ for (curr_ace_outer = l_head; curr_ace_outer; curr_ace_outer = curr_ace_outer_next) {
+ canon_ace *curr_ace;
+ canon_ace *curr_ace_next;
+
+ curr_ace_outer_next = curr_ace_outer->next; /* Save the link in case we delete. */
+
+ for (curr_ace = curr_ace_outer->next; curr_ace; curr_ace = curr_ace_next) {
+ bool can_merge = false;
+
+ curr_ace_next = curr_ace->next; /* Save the link in case of delete. */
+
+ /* For file ACLs we can merge if the SIDs and ALLOW/DENY
+ * types are the same. For directory acls we must also
+ * ensure the POSIX ACL types are the same.
+ *
+ * For the IDMAP_BOTH case, we must not merge
+ * the UID and GID ACE values for same SID
+ */
+
+ if (!dir_acl) {
+ can_merge = (curr_ace->unix_ug.id == curr_ace_outer->unix_ug.id &&
+ curr_ace->owner_type == curr_ace_outer->owner_type &&
+ (curr_ace->attr == curr_ace_outer->attr));
+ } else {
+ can_merge = (curr_ace->unix_ug.id == curr_ace_outer->unix_ug.id &&
+ curr_ace->owner_type == curr_ace_outer->owner_type &&
+ (curr_ace->type == curr_ace_outer->type) &&
+ (curr_ace->attr == curr_ace_outer->attr));
+ }
+
+ if (can_merge) {
+ if( DEBUGLVL( 10 )) {
+ dbgtext("merge_aces: Merging ACE's\n");
+ print_canon_ace( curr_ace_outer, 0);
+ print_canon_ace( curr_ace, 0);
+ }
+
+ /* Merge two allow or two deny ACE's. */
+
+ /* Theoretically we shouldn't merge a dir ACE if
+ * one ACE has the CI flag set, and the other
+ * ACE has the OI flag set, but this is rare
+ * enough we can ignore it. */
+
+ curr_ace_outer->perms |= curr_ace->perms;
+ curr_ace_outer->ace_flags |= curr_ace->ace_flags;
+ DLIST_REMOVE(l_head, curr_ace);
+ TALLOC_FREE(curr_ace);
+ curr_ace_outer_next = curr_ace_outer->next; /* We may have deleted the link. */
+ }
+ }
+ }
+
+ /*
+ * Now go through and mask off allow permissions with deny permissions.
+ * We can delete either the allow or deny here as we know that each SID
+ * appears only once in the list.
+ */
+
+ for (curr_ace_outer = l_head; curr_ace_outer; curr_ace_outer = curr_ace_outer_next) {
+ canon_ace *curr_ace;
+ canon_ace *curr_ace_next;
+
+ curr_ace_outer_next = curr_ace_outer->next; /* Save the link in case we delete. */
+
+ for (curr_ace = curr_ace_outer->next; curr_ace; curr_ace = curr_ace_next) {
+
+ curr_ace_next = curr_ace->next; /* Save the link in case of delete. */
+
+ /*
+ * Subtract ACE's with different entries. Due to the ordering constraints
+ * we've put on the ACL, we know the deny must be the first one.
+ */
+
+ if (curr_ace->unix_ug.id == curr_ace_outer->unix_ug.id &&
+ (curr_ace->owner_type == curr_ace_outer->owner_type) &&
+ (curr_ace_outer->attr == DENY_ACE) && (curr_ace->attr == ALLOW_ACE)) {
+
+ if( DEBUGLVL( 10 )) {
+ dbgtext("merge_aces: Masking ACE's\n");
+ print_canon_ace( curr_ace_outer, 0);
+ print_canon_ace( curr_ace, 0);
+ }
+
+ curr_ace->perms &= ~curr_ace_outer->perms;
+
+ if (curr_ace->perms == 0) {
+
+ /*
+ * The deny overrides the allow. Remove the allow.
+ */
+
+ DLIST_REMOVE(l_head, curr_ace);
+ TALLOC_FREE(curr_ace);
+ curr_ace_outer_next = curr_ace_outer->next; /* We may have deleted the link. */
+
+ } else {
+
+ /*
+ * Even after removing permissions, there
+ * are still allow permissions - delete the deny.
+ * It is safe to delete the deny here,
+ * as we are guaranteed by the deny first
+ * ordering that all the deny entries for
+ * this SID have already been merged into one
+ * before we can get to an allow ace.
+ */
+
+ DLIST_REMOVE(l_head, curr_ace_outer);
+ TALLOC_FREE(curr_ace_outer);
+ break;
+ }
+ }
+
+ } /* end for curr_ace */
+ } /* end for curr_ace_outer */
+
+ /* We may have modified the list. */
+
+ *pp_list_head = l_head;
+}
+
+/****************************************************************************
+ Map canon_ace perms to permission bits NT.
+ The attr element is not used here - we only process deny entries on set,
+ not get. Deny entries are implicit on get with ace->perms = 0.
+****************************************************************************/
+
+uint32_t map_canon_ace_perms(int snum,
+ enum security_ace_type *pacl_type,
+ mode_t perms,
+ bool directory_ace)
+{
+ uint32_t nt_mask = 0;
+
+ *pacl_type = SEC_ACE_TYPE_ACCESS_ALLOWED;
+
+ if (lp_acl_map_full_control(snum) && ((perms & ALL_ACE_PERMS) == ALL_ACE_PERMS)) {
+ if (directory_ace) {
+ nt_mask = UNIX_DIRECTORY_ACCESS_RWX;
+ } else {
+ nt_mask = (UNIX_ACCESS_RWX & ~DELETE_ACCESS);
+ }
+ } else if ((perms & ALL_ACE_PERMS) == (mode_t)0) {
+ /*
+ * Windows NT refuses to display ACEs with no permissions in them (but
+ * they are perfectly legal with Windows 2000). If the ACE has empty
+ * permissions we cannot use 0, so we use the otherwise unused
+ * WRITE_OWNER permission, which we ignore when we set an ACL.
+ * We abstract this into a #define of UNIX_ACCESS_NONE to allow this
+ * to be changed in the future.
+ */
+
+ nt_mask = 0;
+ } else {
+ if (directory_ace) {
+ nt_mask |= ((perms & S_IRUSR) ? UNIX_DIRECTORY_ACCESS_R : 0 );
+ nt_mask |= ((perms & S_IWUSR) ? UNIX_DIRECTORY_ACCESS_W : 0 );
+ nt_mask |= ((perms & S_IXUSR) ? UNIX_DIRECTORY_ACCESS_X : 0 );
+ } else {
+ nt_mask |= ((perms & S_IRUSR) ? UNIX_ACCESS_R : 0 );
+ nt_mask |= ((perms & S_IWUSR) ? UNIX_ACCESS_W : 0 );
+ nt_mask |= ((perms & S_IXUSR) ? UNIX_ACCESS_X : 0 );
+ }
+ }
+
+ if ((perms & S_IWUSR) && lp_dos_filemode(snum)) {
+ nt_mask |= (SEC_STD_WRITE_DAC|SEC_STD_WRITE_OWNER|DELETE_ACCESS);
+ }
+
+ DEBUG(10,("map_canon_ace_perms: Mapped (UNIX) %x to (NT) %x\n",
+ (unsigned int)perms, (unsigned int)nt_mask ));
+
+ return nt_mask;
+}
+
+/****************************************************************************
+ Map NT perms to a UNIX mode_t.
+****************************************************************************/
+
+#define FILE_SPECIFIC_READ_BITS (FILE_READ_DATA|FILE_READ_EA)
+#define FILE_SPECIFIC_WRITE_BITS (FILE_WRITE_DATA|FILE_APPEND_DATA|FILE_WRITE_EA)
+#define FILE_SPECIFIC_EXECUTE_BITS (FILE_EXECUTE)
+
+static mode_t map_nt_perms( uint32_t *mask, int type)
+{
+ mode_t mode = 0;
+
+ switch(type) {
+ case S_IRUSR:
+ if((*mask) & GENERIC_ALL_ACCESS)
+ mode = S_IRUSR|S_IWUSR|S_IXUSR;
+ else {
+ mode |= ((*mask) & (GENERIC_READ_ACCESS|FILE_SPECIFIC_READ_BITS)) ? S_IRUSR : 0;
+ mode |= ((*mask) & (GENERIC_WRITE_ACCESS|FILE_SPECIFIC_WRITE_BITS)) ? S_IWUSR : 0;
+ mode |= ((*mask) & (GENERIC_EXECUTE_ACCESS|FILE_SPECIFIC_EXECUTE_BITS)) ? S_IXUSR : 0;
+ }
+ break;
+ case S_IRGRP:
+ if((*mask) & GENERIC_ALL_ACCESS)
+ mode = S_IRGRP|S_IWGRP|S_IXGRP;
+ else {
+ mode |= ((*mask) & (GENERIC_READ_ACCESS|FILE_SPECIFIC_READ_BITS)) ? S_IRGRP : 0;
+ mode |= ((*mask) & (GENERIC_WRITE_ACCESS|FILE_SPECIFIC_WRITE_BITS)) ? S_IWGRP : 0;
+ mode |= ((*mask) & (GENERIC_EXECUTE_ACCESS|FILE_SPECIFIC_EXECUTE_BITS)) ? S_IXGRP : 0;
+ }
+ break;
+ case S_IROTH:
+ if((*mask) & GENERIC_ALL_ACCESS)
+ mode = S_IROTH|S_IWOTH|S_IXOTH;
+ else {
+ mode |= ((*mask) & (GENERIC_READ_ACCESS|FILE_SPECIFIC_READ_BITS)) ? S_IROTH : 0;
+ mode |= ((*mask) & (GENERIC_WRITE_ACCESS|FILE_SPECIFIC_WRITE_BITS)) ? S_IWOTH : 0;
+ mode |= ((*mask) & (GENERIC_EXECUTE_ACCESS|FILE_SPECIFIC_EXECUTE_BITS)) ? S_IXOTH : 0;
+ }
+ break;
+ }
+
+ return mode;
+}
+
+/****************************************************************************
+ Unpack a struct security_descriptor into a UNIX owner and group.
+****************************************************************************/
+
+static NTSTATUS unpack_nt_owners(struct connection_struct *conn,
+ uid_t *puser, gid_t *pgrp,
+ uint32_t security_info_sent,
+ const struct security_descriptor *psd)
+{
+ *puser = (uid_t)-1;
+ *pgrp = (gid_t)-1;
+
+ if(security_info_sent == 0) {
+ DEBUG(0,("unpack_nt_owners: no security info sent !\n"));
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * Validate the owner and group SID's.
+ */
+
+ DEBUG(5,("unpack_nt_owners: validating owner_sids.\n"));
+
+ /*
+ * Don't immediately fail if the owner sid cannot be validated.
+ * This may be a group chown only set.
+ */
+
+ if (security_info_sent & SECINFO_OWNER) {
+ if (!sid_to_uid(psd->owner_sid, puser)) {
+ if (lp_force_unknown_acl_user(SNUM(conn))) {
+ /* this allows take ownership to work
+ * reasonably */
+ *puser = get_current_uid(conn);
+ } else {
+ struct dom_sid_buf buf;
+ DBG_NOTICE("unable to validate"
+ " owner sid for %s\n",
+ dom_sid_str_buf(psd->owner_sid,
+ &buf));
+ return NT_STATUS_INVALID_OWNER;
+ }
+ }
+ DEBUG(3,("unpack_nt_owners: owner sid mapped to uid %u\n",
+ (unsigned int)*puser ));
+ }
+
+ /*
+ * Don't immediately fail if the group sid cannot be validated.
+ * This may be an owner chown only set.
+ */
+
+ if (security_info_sent & SECINFO_GROUP) {
+ if (!sid_to_gid(psd->group_sid, pgrp)) {
+ if (lp_force_unknown_acl_user(SNUM(conn))) {
+ /* this allows take group ownership to work
+ * reasonably */
+ *pgrp = get_current_gid(conn);
+ } else {
+ DEBUG(3,("unpack_nt_owners: unable to validate"
+ " group sid.\n"));
+ return NT_STATUS_INVALID_OWNER;
+ }
+ }
+ DEBUG(3,("unpack_nt_owners: group sid mapped to gid %u\n",
+ (unsigned int)*pgrp));
+ }
+
+ DEBUG(5,("unpack_nt_owners: owner_sids validated.\n"));
+
+ return NT_STATUS_OK;
+}
+
+
+static void trim_ace_perms(canon_ace *pace)
+{
+ pace->perms = pace->perms & (S_IXUSR|S_IWUSR|S_IRUSR);
+}
+
+static void ensure_minimal_owner_ace_perms(const bool is_directory,
+ canon_ace *pace)
+{
+ pace->perms |= S_IRUSR;
+ if (is_directory) {
+ pace->perms |= (S_IWUSR|S_IXUSR);
+ }
+}
+
+/****************************************************************************
+ Check if a given uid/SID is in a group gid/SID. This is probably very
+ expensive and will need optimisation. A *lot* of optimisation :-). JRA.
+****************************************************************************/
+
+static bool uid_entry_in_group(connection_struct *conn, canon_ace *uid_ace, canon_ace *group_ace )
+{
+ bool is_sid = false;
+ bool has_sid = false;
+ struct security_token *security_token = NULL;
+
+ /* "Everyone" always matches every uid. */
+
+ if (dom_sid_equal(&group_ace->trustee, &global_sid_World))
+ return True;
+
+ security_token = conn->session_info->security_token;
+ /* security_token should not be NULL */
+ SMB_ASSERT(security_token);
+ is_sid = security_token_is_sid(security_token,
+ &uid_ace->trustee);
+ if (is_sid) {
+ has_sid = security_token_has_sid(security_token,
+ &group_ace->trustee);
+
+ if (has_sid) {
+ return true;
+ }
+ }
+
+ /*
+ * if it's the current user, we already have the unix token
+ * and don't need to do the complex user_in_group_sid() call
+ */
+ if (uid_ace->unix_ug.id == get_current_uid(conn)) {
+ const struct security_unix_token *curr_utok = NULL;
+ size_t i;
+
+ if (group_ace->unix_ug.id == get_current_gid(conn)) {
+ return True;
+ }
+
+ curr_utok = get_current_utok(conn);
+ for (i=0; i < curr_utok->ngroups; i++) {
+ if (group_ace->unix_ug.id == curr_utok->groups[i]) {
+ return True;
+ }
+ }
+ }
+
+ /*
+ * user_in_group_sid() uses create_token_from_sid()
+ * which creates an artificial NT token given just a username,
+ * so this is not reliable for users from foreign domains
+ * exported by winbindd!
+ */
+ return user_sid_in_group_sid(&uid_ace->trustee, &group_ace->trustee);
+}
+
+/****************************************************************************
+ A well formed POSIX file or default ACL has at least 3 entries, a
+ SMB_ACL_USER_OBJ, SMB_ACL_GROUP_OBJ, SMB_ACL_OTHER_OBJ.
+ In addition, the owner must always have at least read access.
+ When using this call on get_acl, the pst struct is valid and contains
+ the mode of the file.
+****************************************************************************/
+
+static bool ensure_canon_entry_valid_on_get(connection_struct *conn,
+ canon_ace **pp_ace,
+ const struct dom_sid *pfile_owner_sid,
+ const struct dom_sid *pfile_grp_sid,
+ const SMB_STRUCT_STAT *pst)
+{
+ canon_ace *pace;
+ bool got_user = false;
+ bool got_group = false;
+ bool got_other = false;
+
+ for (pace = *pp_ace; pace; pace = pace->next) {
+ if (pace->type == SMB_ACL_USER_OBJ) {
+ got_user = true;
+ } else if (pace->type == SMB_ACL_GROUP_OBJ) {
+ got_group = true;
+ } else if (pace->type == SMB_ACL_OTHER) {
+ got_other = true;
+ }
+ }
+
+ if (!got_user) {
+ if ((pace = talloc(talloc_tos(), canon_ace)) == NULL) {
+ DEBUG(0,("malloc fail.\n"));
+ return false;
+ }
+
+ ZERO_STRUCTP(pace);
+ pace->type = SMB_ACL_USER_OBJ;
+ pace->owner_type = UID_ACE;
+ pace->unix_ug.type = ID_TYPE_UID;
+ pace->unix_ug.id = pst->st_ex_uid;
+ pace->trustee = *pfile_owner_sid;
+ pace->attr = ALLOW_ACE;
+ pace->perms = unix_perms_to_acl_perms(pst->st_ex_mode, S_IRUSR, S_IWUSR, S_IXUSR);
+ DLIST_ADD(*pp_ace, pace);
+ }
+
+ if (!got_group) {
+ if ((pace = talloc(talloc_tos(), canon_ace)) == NULL) {
+ DEBUG(0,("malloc fail.\n"));
+ return false;
+ }
+
+ ZERO_STRUCTP(pace);
+ pace->type = SMB_ACL_GROUP_OBJ;
+ pace->owner_type = GID_ACE;
+ pace->unix_ug.type = ID_TYPE_GID;
+ pace->unix_ug.id = pst->st_ex_gid;
+ pace->trustee = *pfile_grp_sid;
+ pace->attr = ALLOW_ACE;
+ pace->perms = unix_perms_to_acl_perms(pst->st_ex_mode, S_IRGRP, S_IWGRP, S_IXGRP);
+ DLIST_ADD(*pp_ace, pace);
+ }
+
+ if (!got_other) {
+ if ((pace = talloc(talloc_tos(), canon_ace)) == NULL) {
+ DEBUG(0,("malloc fail.\n"));
+ return false;
+ }
+
+ ZERO_STRUCTP(pace);
+ pace->type = SMB_ACL_OTHER;
+ pace->owner_type = WORLD_ACE;
+ pace->unix_ug.type = ID_TYPE_NOT_SPECIFIED;
+ pace->unix_ug.id = -1;
+ pace->trustee = global_sid_World;
+ pace->attr = ALLOW_ACE;
+ pace->perms = unix_perms_to_acl_perms(pst->st_ex_mode, S_IROTH, S_IWOTH, S_IXOTH);
+ DLIST_ADD(*pp_ace, pace);
+ }
+
+ return true;
+}
+
+/****************************************************************************
+ A well formed POSIX file or default ACL has at least 3 entries, a
+ SMB_ACL_USER_OBJ, SMB_ACL_GROUP_OBJ, SMB_ACL_OTHER_OBJ.
+ In addition, the owner must always have at least read access.
+ When using this call on set_acl, the pst struct has
+ been modified to have a mode containing the default for this file or directory
+ type.
+****************************************************************************/
+
+static bool ensure_canon_entry_valid_on_set(connection_struct *conn,
+ canon_ace **pp_ace,
+ bool is_default_acl,
+ const struct share_params *params,
+ const bool is_directory,
+ const struct dom_sid *pfile_owner_sid,
+ const struct dom_sid *pfile_grp_sid,
+ const SMB_STRUCT_STAT *pst)
+{
+ canon_ace *pace;
+ canon_ace *pace_user = NULL;
+ canon_ace *pace_group = NULL;
+ canon_ace *pace_other = NULL;
+ bool got_duplicate_user = false;
+ bool got_duplicate_group = false;
+
+ for (pace = *pp_ace; pace; pace = pace->next) {
+ trim_ace_perms(pace);
+ if (pace->type == SMB_ACL_USER_OBJ) {
+ ensure_minimal_owner_ace_perms(is_directory, pace);
+ pace_user = pace;
+ } else if (pace->type == SMB_ACL_GROUP_OBJ) {
+ pace_group = pace;
+ } else if (pace->type == SMB_ACL_OTHER) {
+ pace_other = pace;
+ }
+ }
+
+ if (!pace_user) {
+ canon_ace *pace_iter;
+
+ if ((pace = talloc(talloc_tos(), canon_ace)) == NULL) {
+ DEBUG(0,("talloc fail.\n"));
+ return false;
+ }
+
+ ZERO_STRUCTP(pace);
+ pace->type = SMB_ACL_USER_OBJ;
+ pace->owner_type = UID_ACE;
+ pace->unix_ug.type = ID_TYPE_UID;
+ pace->unix_ug.id = pst->st_ex_uid;
+ pace->trustee = *pfile_owner_sid;
+ pace->attr = ALLOW_ACE;
+ /* Start with existing user permissions, principle of least
+ surprises for the user. */
+ pace->perms = unix_perms_to_acl_perms(pst->st_ex_mode, S_IRUSR, S_IWUSR, S_IXUSR);
+
+ /* See if the owning user is in any of the other groups in
+ the ACE, or if there's a matching user entry (by uid
+ or in the case of ID_TYPE_BOTH by SID).
+ If so, OR in the permissions from that entry. */
+
+
+ for (pace_iter = *pp_ace; pace_iter; pace_iter = pace_iter->next) {
+ if (pace_iter->type == SMB_ACL_USER &&
+ pace_iter->unix_ug.id == pace->unix_ug.id) {
+ pace->perms |= pace_iter->perms;
+ } else if (pace_iter->type == SMB_ACL_GROUP_OBJ || pace_iter->type == SMB_ACL_GROUP) {
+ if (dom_sid_equal(&pace->trustee, &pace_iter->trustee)) {
+ pace->perms |= pace_iter->perms;
+ } else if (uid_entry_in_group(conn, pace, pace_iter)) {
+ pace->perms |= pace_iter->perms;
+ }
+ }
+ }
+
+ if (pace->perms == 0) {
+ /* If we only got an "everyone" perm, just use that. */
+ if (pace_other)
+ pace->perms = pace_other->perms;
+ }
+
+ /*
+ * Ensure we have default parameters for the
+ * user (owner) even on default ACLs.
+ */
+ ensure_minimal_owner_ace_perms(is_directory, pace);
+
+ DLIST_ADD(*pp_ace, pace);
+ pace_user = pace;
+ }
+
+ if (!pace_group) {
+ if ((pace = talloc(talloc_tos(), canon_ace)) == NULL) {
+ DEBUG(0,("talloc fail.\n"));
+ return false;
+ }
+
+ ZERO_STRUCTP(pace);
+ pace->type = SMB_ACL_GROUP_OBJ;
+ pace->owner_type = GID_ACE;
+ pace->unix_ug.type = ID_TYPE_GID;
+ pace->unix_ug.id = pst->st_ex_gid;
+ pace->trustee = *pfile_grp_sid;
+ pace->attr = ALLOW_ACE;
+
+ /* If we only got an "everyone" perm, just use that. */
+ if (pace_other) {
+ pace->perms = pace_other->perms;
+ } else {
+ pace->perms = 0;
+ }
+
+ DLIST_ADD(*pp_ace, pace);
+ pace_group = pace;
+ }
+
+ if (!pace_other) {
+ if ((pace = talloc(talloc_tos(), canon_ace)) == NULL) {
+ DEBUG(0,("talloc fail.\n"));
+ return false;
+ }
+
+ ZERO_STRUCTP(pace);
+ pace->type = SMB_ACL_OTHER;
+ pace->owner_type = WORLD_ACE;
+ pace->unix_ug.type = ID_TYPE_NOT_SPECIFIED;
+ pace->unix_ug.id = -1;
+ pace->trustee = global_sid_World;
+ pace->attr = ALLOW_ACE;
+ pace->perms = 0;
+
+ DLIST_ADD(*pp_ace, pace);
+ pace_other = pace;
+ }
+
+ /* Ensure when setting a POSIX ACL, that the uid for a
+ SMB_ACL_USER_OBJ ACE (the owner ACE entry) has a duplicate
+ permission entry as an SMB_ACL_USER, and a gid for a
+ SMB_ACL_GROUP_OBJ ACE (the primary group ACE entry) also has
+ a duplicate permission entry as an SMB_ACL_GROUP. If not,
+ then if the ownership or group ownership of this file or
+ directory gets changed, the user or group can lose their
+ access. */
+
+ for (pace = *pp_ace; pace; pace = pace->next) {
+ if (pace->type == SMB_ACL_USER &&
+ pace->unix_ug.id == pace_user->unix_ug.id) {
+ /* Already got one. */
+ got_duplicate_user = true;
+ } else if (pace->type == SMB_ACL_GROUP &&
+ pace->unix_ug.id == pace_group->unix_ug.id) {
+ /* Already got one. */
+ got_duplicate_group = true;
+ } else if ((pace->type == SMB_ACL_GROUP)
+ && (dom_sid_equal(&pace->trustee, &pace_user->trustee))) {
+ /* If the SID owning the file appears
+ * in a group entry, then we have
+ * enough duplication, they will still
+ * have access */
+ got_duplicate_user = true;
+ }
+ }
+
+ /* If the SID is equal for the user and group that we need
+ to add the duplicate for, add only the group */
+ if (!got_duplicate_user && !got_duplicate_group
+ && dom_sid_equal(&pace_group->trustee,
+ &pace_user->trustee)) {
+ /* Add a duplicate SMB_ACL_GROUP entry, this
+ * will cover the owning SID as well, as it
+ * will always be mapped to both a uid and
+ * gid. */
+
+ if ((pace = talloc(talloc_tos(), canon_ace)) == NULL) {
+ DEBUG(0,("talloc fail.\n"));
+ return false;
+ }
+
+ ZERO_STRUCTP(pace);
+ pace->type = SMB_ACL_GROUP;;
+ pace->owner_type = GID_ACE;
+ pace->unix_ug.type = ID_TYPE_GID;
+ pace->unix_ug.id = pace_group->unix_ug.id;
+ pace->trustee = pace_group->trustee;
+ pace->attr = pace_group->attr;
+ pace->perms = pace_group->perms;
+
+ DLIST_ADD(*pp_ace, pace);
+
+ /* We're done here, make sure the
+ statements below are not executed. */
+ got_duplicate_user = true;
+ got_duplicate_group = true;
+ }
+
+ if (!got_duplicate_user) {
+ /* Add a duplicate SMB_ACL_USER entry. */
+ if ((pace = talloc(talloc_tos(), canon_ace)) == NULL) {
+ DEBUG(0,("talloc fail.\n"));
+ return false;
+ }
+
+ ZERO_STRUCTP(pace);
+ pace->type = SMB_ACL_USER;;
+ pace->owner_type = UID_ACE;
+ pace->unix_ug.type = ID_TYPE_UID;
+ pace->unix_ug.id = pace_user->unix_ug.id;
+ pace->trustee = pace_user->trustee;
+ pace->attr = pace_user->attr;
+ pace->perms = pace_user->perms;
+
+ DLIST_ADD(*pp_ace, pace);
+
+ got_duplicate_user = true;
+ }
+
+ if (!got_duplicate_group) {
+ /* Add a duplicate SMB_ACL_GROUP entry. */
+ if ((pace = talloc(talloc_tos(), canon_ace)) == NULL) {
+ DEBUG(0,("talloc fail.\n"));
+ return false;
+ }
+
+ ZERO_STRUCTP(pace);
+ pace->type = SMB_ACL_GROUP;;
+ pace->owner_type = GID_ACE;
+ pace->unix_ug.type = ID_TYPE_GID;
+ pace->unix_ug.id = pace_group->unix_ug.id;
+ pace->trustee = pace_group->trustee;
+ pace->attr = pace_group->attr;
+ pace->perms = pace_group->perms;
+
+ DLIST_ADD(*pp_ace, pace);
+
+ got_duplicate_group = true;
+ }
+
+ return true;
+}
+
+/****************************************************************************
+ Check if a POSIX ACL has the required SMB_ACL_USER_OBJ and SMB_ACL_GROUP_OBJ entries.
+ If it does not have them, check if there are any entries where the trustee is the
+ file owner or the owning group, and map these to SMB_ACL_USER_OBJ and SMB_ACL_GROUP_OBJ.
+ Note we must not do this to default directory ACLs.
+****************************************************************************/
+
+static void check_owning_objs(canon_ace *ace, struct dom_sid *pfile_owner_sid, struct dom_sid *pfile_grp_sid)
+{
+ bool got_user_obj, got_group_obj;
+ canon_ace *current_ace;
+ int i, entries;
+
+ entries = count_canon_ace_list(ace);
+ got_user_obj = False;
+ got_group_obj = False;
+
+ for (i=0, current_ace = ace; i < entries; i++, current_ace = current_ace->next) {
+ if (current_ace->type == SMB_ACL_USER_OBJ)
+ got_user_obj = True;
+ else if (current_ace->type == SMB_ACL_GROUP_OBJ)
+ got_group_obj = True;
+ }
+ if (got_user_obj && got_group_obj) {
+ DEBUG(10,("check_owning_objs: ACL had owning user/group entries.\n"));
+ return;
+ }
+
+ for (i=0, current_ace = ace; i < entries; i++, current_ace = current_ace->next) {
+ if (!got_user_obj && current_ace->owner_type == UID_ACE &&
+ dom_sid_equal(&current_ace->trustee, pfile_owner_sid)) {
+ current_ace->type = SMB_ACL_USER_OBJ;
+ got_user_obj = True;
+ }
+ if (!got_group_obj && current_ace->owner_type == GID_ACE &&
+ dom_sid_equal(&current_ace->trustee, pfile_grp_sid)) {
+ current_ace->type = SMB_ACL_GROUP_OBJ;
+ got_group_obj = True;
+ }
+ }
+ if (!got_user_obj)
+ DEBUG(10,("check_owning_objs: ACL is missing an owner entry.\n"));
+ if (!got_group_obj)
+ DEBUG(10,("check_owning_objs: ACL is missing an owning group entry.\n"));
+}
+
+static bool add_current_ace_to_acl(files_struct *fsp, struct security_ace *psa,
+ canon_ace **file_ace, canon_ace **dir_ace,
+ bool *got_file_allow, bool *got_dir_allow,
+ bool *all_aces_are_inherit_only,
+ canon_ace *current_ace)
+{
+
+ /*
+ * Map the given NT permissions into a UNIX mode_t containing only
+ * S_I(R|W|X)USR bits.
+ */
+
+ current_ace->perms |= map_nt_perms( &psa->access_mask, S_IRUSR);
+ current_ace->attr = (psa->type == SEC_ACE_TYPE_ACCESS_ALLOWED) ? ALLOW_ACE : DENY_ACE;
+
+ /* Store the ace_flag. */
+ current_ace->ace_flags = psa->flags;
+
+ /*
+ * Now add the created ace to either the file list, the directory
+ * list, or both. We *MUST* preserve the order here (hence we use
+ * DLIST_ADD_END) as NT ACLs are order dependent.
+ */
+
+ if (fsp->fsp_flags.is_directory) {
+
+ /*
+ * We can only add to the default POSIX ACE list if the ACE is
+ * designed to be inherited by both files and directories.
+ */
+
+ if ((psa->flags & (SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT)) ==
+ (SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT)) {
+
+ canon_ace *current_dir_ace = current_ace;
+ DLIST_ADD_END(*dir_ace, current_ace);
+
+ /*
+ * Note if this was an allow ace. We can't process
+ * any further deny ace's after this.
+ */
+
+ if (current_ace->attr == ALLOW_ACE)
+ *got_dir_allow = True;
+
+ if ((current_ace->attr == DENY_ACE) && *got_dir_allow) {
+ DEBUG(0,("add_current_ace_to_acl: "
+ "malformed ACL in "
+ "inheritable ACL! Deny entry "
+ "after Allow entry. Failing "
+ "to set on file %s.\n",
+ fsp_str_dbg(fsp)));
+ return False;
+ }
+
+ if( DEBUGLVL( 10 )) {
+ dbgtext("add_current_ace_to_acl: adding dir ACL:\n");
+ print_canon_ace( current_ace, 0);
+ }
+
+ /*
+ * If this is not an inherit only ACE we need to add a duplicate
+ * to the file acl.
+ */
+
+ if (!(psa->flags & SEC_ACE_FLAG_INHERIT_ONLY)) {
+ canon_ace *dup_ace = dup_canon_ace(current_ace);
+
+ if (!dup_ace) {
+ DEBUG(0,("add_current_ace_to_acl: malloc fail !\n"));
+ return False;
+ }
+
+ /*
+ * We must not free current_ace here as its
+ * pointer is now owned by the dir_ace list.
+ */
+ current_ace = dup_ace;
+ /* We've essentially split this ace into two,
+ * and added the ace with inheritance request
+ * bits to the directory ACL. Drop those bits for
+ * the ACE we're adding to the file list. */
+ current_ace->ace_flags &= ~(SEC_ACE_FLAG_OBJECT_INHERIT|
+ SEC_ACE_FLAG_CONTAINER_INHERIT|
+ SEC_ACE_FLAG_INHERIT_ONLY);
+ } else {
+ /*
+ * We must not free current_ace here as its
+ * pointer is now owned by the dir_ace list.
+ */
+ current_ace = NULL;
+ }
+
+ /*
+ * current_ace is now either owned by file_ace
+ * or is NULL. We can safely operate on current_dir_ace
+ * to treat mapping for default acl entries differently
+ * than access acl entries.
+ */
+
+ if (current_dir_ace->owner_type == UID_ACE) {
+ /*
+ * We already decided above this is a uid,
+ * for default acls ace's only CREATOR_OWNER
+ * maps to ACL_USER_OBJ. All other uid
+ * ace's are ACL_USER.
+ */
+ if (dom_sid_equal(&current_dir_ace->trustee,
+ &global_sid_Creator_Owner)) {
+ current_dir_ace->type = SMB_ACL_USER_OBJ;
+ } else {
+ current_dir_ace->type = SMB_ACL_USER;
+ }
+ }
+
+ if (current_dir_ace->owner_type == GID_ACE) {
+ /*
+ * We already decided above this is a gid,
+ * for default acls ace's only CREATOR_GROUP
+ * maps to ACL_GROUP_OBJ. All other uid
+ * ace's are ACL_GROUP.
+ */
+ if (dom_sid_equal(&current_dir_ace->trustee,
+ &global_sid_Creator_Group)) {
+ current_dir_ace->type = SMB_ACL_GROUP_OBJ;
+ } else {
+ current_dir_ace->type = SMB_ACL_GROUP;
+ }
+ }
+ }
+ }
+
+ /*
+ * Only add to the file ACL if not inherit only.
+ */
+
+ if (current_ace && !(psa->flags & SEC_ACE_FLAG_INHERIT_ONLY)) {
+ DLIST_ADD_END(*file_ace, current_ace);
+
+ /*
+ * Note if this was an allow ace. We can't process
+ * any further deny ace's after this.
+ */
+
+ if (current_ace->attr == ALLOW_ACE)
+ *got_file_allow = True;
+
+ if ((current_ace->attr == DENY_ACE) && *got_file_allow) {
+ DEBUG(0,("add_current_ace_to_acl: malformed "
+ "ACL in file ACL ! Deny entry after "
+ "Allow entry. Failing to set on file "
+ "%s.\n", fsp_str_dbg(fsp)));
+ return False;
+ }
+
+ if( DEBUGLVL( 10 )) {
+ dbgtext("add_current_ace_to_acl: adding file ACL:\n");
+ print_canon_ace( current_ace, 0);
+ }
+ *all_aces_are_inherit_only = False;
+ /*
+ * We must not free current_ace here as its
+ * pointer is now owned by the file_ace list.
+ */
+ current_ace = NULL;
+ }
+
+ /*
+ * Free if ACE was not added.
+ */
+
+ TALLOC_FREE(current_ace);
+ return true;
+}
+
+/****************************************************************************
+ Unpack a struct security_descriptor into two canonical ace lists.
+****************************************************************************/
+
+static bool create_canon_ace_lists(files_struct *fsp,
+ const SMB_STRUCT_STAT *pst,
+ struct dom_sid *pfile_owner_sid,
+ struct dom_sid *pfile_grp_sid,
+ canon_ace **ppfile_ace,
+ canon_ace **ppdir_ace,
+ const struct security_acl *dacl)
+{
+ bool all_aces_are_inherit_only = (fsp->fsp_flags.is_directory);
+ canon_ace *file_ace = NULL;
+ canon_ace *dir_ace = NULL;
+ canon_ace *current_ace = NULL;
+ bool got_dir_allow = False;
+ bool got_file_allow = False;
+ uint32_t i, j;
+
+ *ppfile_ace = NULL;
+ *ppdir_ace = NULL;
+
+ /*
+ * Convert the incoming ACL into a more regular form.
+ */
+
+ for(i = 0; i < dacl->num_aces; i++) {
+ struct security_ace *psa = &dacl->aces[i];
+
+ if((psa->type != SEC_ACE_TYPE_ACCESS_ALLOWED) && (psa->type != SEC_ACE_TYPE_ACCESS_DENIED)) {
+ DEBUG(3,("create_canon_ace_lists: unable to set anything but an ALLOW or DENY ACE.\n"));
+ return False;
+ }
+ }
+
+ /*
+ * Deal with the fact that NT 4.x re-writes the canonical format
+ * that we return for default ACLs. If a directory ACE is identical
+ * to a inherited directory ACE then NT changes the bits so that the
+ * first ACE is set to OI|IO and the second ACE for this SID is set
+ * to CI. We need to repair this. JRA.
+ */
+
+ for(i = 0; i < dacl->num_aces; i++) {
+ struct security_ace *psa1 = &dacl->aces[i];
+
+ for (j = i + 1; j < dacl->num_aces; j++) {
+ struct security_ace *psa2 = &dacl->aces[j];
+
+ if (psa1->access_mask != psa2->access_mask)
+ continue;
+
+ if (!dom_sid_equal(&psa1->trustee, &psa2->trustee))
+ continue;
+
+ /*
+ * Ok - permission bits and SIDs are equal.
+ * Check if flags were re-written.
+ */
+
+ if (psa1->flags & SEC_ACE_FLAG_INHERIT_ONLY) {
+
+ psa1->flags |= (psa2->flags & (SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_OBJECT_INHERIT));
+ psa2->flags &= ~(SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_OBJECT_INHERIT);
+
+ } else if (psa2->flags & SEC_ACE_FLAG_INHERIT_ONLY) {
+
+ psa2->flags |= (psa1->flags & (SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_OBJECT_INHERIT));
+ psa1->flags &= ~(SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_OBJECT_INHERIT);
+
+ }
+ }
+ }
+
+ for(i = 0; i < dacl->num_aces; i++) {
+ struct security_ace *psa = &dacl->aces[i];
+
+ /*
+ * Create a canon_ace entry representing this NT DACL ACE.
+ */
+
+ if ((current_ace = talloc(talloc_tos(), canon_ace)) == NULL) {
+ free_canon_ace_list(file_ace);
+ free_canon_ace_list(dir_ace);
+ DEBUG(0,("create_canon_ace_lists: malloc fail.\n"));
+ return False;
+ }
+
+ ZERO_STRUCTP(current_ace);
+
+ sid_copy(&current_ace->trustee, &psa->trustee);
+
+ /*
+ * Try and work out if the SID is a user or group
+ * as we need to flag these differently for POSIX.
+ * Note what kind of a POSIX ACL this should map to.
+ */
+
+ if( dom_sid_equal(&current_ace->trustee, &global_sid_World)) {
+ current_ace->owner_type = WORLD_ACE;
+ current_ace->unix_ug.type = ID_TYPE_NOT_SPECIFIED;
+ current_ace->unix_ug.id = -1;
+ current_ace->type = SMB_ACL_OTHER;
+ } else if (dom_sid_equal(&current_ace->trustee, &global_sid_Creator_Owner)) {
+ current_ace->owner_type = UID_ACE;
+ current_ace->unix_ug.type = ID_TYPE_UID;
+ current_ace->unix_ug.id = pst->st_ex_uid;
+ current_ace->type = SMB_ACL_USER_OBJ;
+
+ /*
+ * The Creator Owner entry only specifies inheritable permissions,
+ * never access permissions. WinNT doesn't always set the ACE to
+ * INHERIT_ONLY, though.
+ */
+
+ psa->flags |= SEC_ACE_FLAG_INHERIT_ONLY;
+
+ } else if (dom_sid_equal(&current_ace->trustee, &global_sid_Creator_Group)) {
+ current_ace->owner_type = GID_ACE;
+ current_ace->unix_ug.type = ID_TYPE_GID;
+ current_ace->unix_ug.id = pst->st_ex_gid;
+ current_ace->type = SMB_ACL_GROUP_OBJ;
+
+ /*
+ * The Creator Group entry only specifies inheritable permissions,
+ * never access permissions. WinNT doesn't always set the ACE to
+ * INHERIT_ONLY, though.
+ */
+ psa->flags |= SEC_ACE_FLAG_INHERIT_ONLY;
+
+ } else {
+ struct unixid unixid;
+
+ if (!sids_to_unixids(&current_ace->trustee, 1, &unixid)) {
+ struct dom_sid_buf buf;
+ free_canon_ace_list(file_ace);
+ free_canon_ace_list(dir_ace);
+ TALLOC_FREE(current_ace);
+ DBG_ERR("sids_to_unixids failed for %s "
+ "(allocation failure)\n",
+ dom_sid_str_buf(&current_ace->trustee,
+ &buf));
+ return false;
+ }
+
+ if (unixid.type == ID_TYPE_BOTH) {
+ /*
+ * We must add both a user and group
+ * entry POSIX_ACL.
+ * This is due to the fact that in POSIX
+ * user entries are more specific than
+ * groups.
+ */
+ current_ace->owner_type = UID_ACE;
+ current_ace->unix_ug.type = ID_TYPE_UID;
+ current_ace->unix_ug.id = unixid.id;
+ current_ace->type =
+ (unixid.id == pst->st_ex_uid) ?
+ SMB_ACL_USER_OBJ :
+ SMB_ACL_USER;
+
+ /* Add the user object to the posix ACL,
+ and proceed to the group mapping
+ below. This handles the talloc_free
+ of current_ace if not added for some
+ reason */
+ if (!add_current_ace_to_acl(fsp,
+ psa,
+ &file_ace,
+ &dir_ace,
+ &got_file_allow,
+ &got_dir_allow,
+ &all_aces_are_inherit_only,
+ current_ace)) {
+ free_canon_ace_list(file_ace);
+ free_canon_ace_list(dir_ace);
+ return false;
+ }
+
+ if ((current_ace = talloc(talloc_tos(),
+ canon_ace)) == NULL) {
+ free_canon_ace_list(file_ace);
+ free_canon_ace_list(dir_ace);
+ DEBUG(0,("create_canon_ace_lists: "
+ "malloc fail.\n"));
+ return False;
+ }
+
+ ZERO_STRUCTP(current_ace);
+
+ sid_copy(&current_ace->trustee, &psa->trustee);
+
+ current_ace->unix_ug.type = ID_TYPE_GID;
+ current_ace->unix_ug.id = unixid.id;
+ current_ace->owner_type = GID_ACE;
+ /* If it's the primary group, this is a
+ group_obj, not a group. */
+ if (current_ace->unix_ug.id == pst->st_ex_gid) {
+ current_ace->type = SMB_ACL_GROUP_OBJ;
+ } else {
+ current_ace->type = SMB_ACL_GROUP;
+ }
+
+ } else if (unixid.type == ID_TYPE_UID) {
+ current_ace->owner_type = UID_ACE;
+ current_ace->unix_ug.type = ID_TYPE_UID;
+ current_ace->unix_ug.id = unixid.id;
+ /* If it's the owning user, this is a user_obj,
+ not a user. */
+ if (current_ace->unix_ug.id == pst->st_ex_uid) {
+ current_ace->type = SMB_ACL_USER_OBJ;
+ } else {
+ current_ace->type = SMB_ACL_USER;
+ }
+ } else if (unixid.type == ID_TYPE_GID) {
+ current_ace->unix_ug.type = ID_TYPE_GID;
+ current_ace->unix_ug.id = unixid.id;
+ current_ace->owner_type = GID_ACE;
+ /* If it's the primary group, this is a
+ group_obj, not a group. */
+ if (current_ace->unix_ug.id == pst->st_ex_gid) {
+ current_ace->type = SMB_ACL_GROUP_OBJ;
+ } else {
+ current_ace->type = SMB_ACL_GROUP;
+ }
+ } else {
+ struct dom_sid_buf buf;
+ /*
+ * Silently ignore map failures in non-mappable SIDs (NT Authority, BUILTIN etc).
+ */
+
+ if (non_mappable_sid(&psa->trustee)) {
+ DBG_DEBUG("ignoring "
+ "non-mappable SID %s\n",
+ dom_sid_str_buf(
+ &psa->trustee,
+ &buf));
+ TALLOC_FREE(current_ace);
+ continue;
+ }
+
+ if (lp_force_unknown_acl_user(SNUM(fsp->conn))) {
+ DBG_DEBUG("ignoring unknown or "
+ "foreign SID %s\n",
+ dom_sid_str_buf(
+ &psa->trustee,
+ &buf));
+ TALLOC_FREE(current_ace);
+ continue;
+ }
+
+ free_canon_ace_list(file_ace);
+ free_canon_ace_list(dir_ace);
+ DBG_ERR("unable to map SID %s to uid or "
+ "gid.\n",
+ dom_sid_str_buf(&current_ace->trustee,
+ &buf));
+ TALLOC_FREE(current_ace);
+ return false;
+ }
+ }
+
+ /* handles the talloc_free of current_ace if not added for some reason */
+ if (!add_current_ace_to_acl(fsp, psa, &file_ace, &dir_ace,
+ &got_file_allow, &got_dir_allow,
+ &all_aces_are_inherit_only, current_ace)) {
+ free_canon_ace_list(file_ace);
+ free_canon_ace_list(dir_ace);
+ return false;
+ }
+ }
+
+ if (fsp->fsp_flags.is_directory && all_aces_are_inherit_only) {
+ /*
+ * Windows 2000 is doing one of these weird 'inherit acl'
+ * traverses to conserve NTFS ACL resources. Just pretend
+ * there was no DACL sent. JRA.
+ */
+
+ DEBUG(10,("create_canon_ace_lists: Win2k inherit acl traverse. Ignoring DACL.\n"));
+ free_canon_ace_list(file_ace);
+ free_canon_ace_list(dir_ace);
+ file_ace = NULL;
+ dir_ace = NULL;
+ } else {
+ /*
+ * Check if we have SMB_ACL_USER_OBJ and SMB_ACL_GROUP_OBJ entries in
+ * the file ACL. If we don't have them, check if any SMB_ACL_USER/SMB_ACL_GROUP
+ * entries can be converted to *_OBJ. Don't do this for the default
+ * ACL, we will create them separately for this if needed inside
+ * ensure_canon_entry_valid_on_set().
+ */
+ if (file_ace) {
+ check_owning_objs(file_ace, pfile_owner_sid, pfile_grp_sid);
+ }
+ }
+
+ *ppfile_ace = file_ace;
+ *ppdir_ace = dir_ace;
+
+ return True;
+}
+
+/****************************************************************************
+ ASCII art time again... JRA :-).
+
+ We have 4 cases to process when moving from an NT ACL to a POSIX ACL. Firstly,
+ we insist the ACL is in canonical form (ie. all DENY entries precede ALLOW
+ entries). Secondly, the merge code has ensured that all duplicate SID entries for
+ allow or deny have been merged, so the same SID can only appear once in the deny
+ list or once in the allow list.
+
+ We then process as follows :
+
+ ---------------------------------------------------------------------------
+ First pass - look for a Everyone DENY entry.
+
+ If it is deny all (rwx) trunate the list at this point.
+ Else, walk the list from this point and use the deny permissions of this
+ entry as a mask on all following allow entries. Finally, delete
+ the Everyone DENY entry (we have applied it to everything possible).
+
+ In addition, in this pass we remove any DENY entries that have
+ no permissions (ie. they are a DENY nothing).
+ ---------------------------------------------------------------------------
+ Second pass - only deal with deny user entries.
+
+ DENY user1 (perms XXX)
+
+ new_perms = 0
+ for all following allow group entries where user1 is in group
+ new_perms |= group_perms;
+
+ user1 entry perms = new_perms & ~ XXX;
+
+ Convert the deny entry to an allow entry with the new perms and
+ push to the end of the list. Note if the user was in no groups
+ this maps to a specific allow nothing entry for this user.
+
+ The common case from the NT ACL chooser (userX deny all) is
+ optimised so we don't do the group lookup - we just map to
+ an allow nothing entry.
+
+ What we're doing here is inferring the allow permissions the
+ person setting the ACE on user1 wanted by looking at the allow
+ permissions on the groups the user is currently in. This will
+ be a snapshot, depending on group membership but is the best
+ we can do and has the advantage of failing closed rather than
+ open.
+ ---------------------------------------------------------------------------
+ Third pass - only deal with deny group entries.
+
+ DENY group1 (perms XXX)
+
+ for all following allow user entries where user is in group1
+ user entry perms = user entry perms & ~ XXX;
+
+ If there is a group Everyone allow entry with permissions YYY,
+ convert the group1 entry to an allow entry and modify its
+ permissions to be :
+
+ new_perms = YYY & ~ XXX
+
+ and push to the end of the list.
+
+ If there is no group Everyone allow entry then convert the
+ group1 entry to a allow nothing entry and push to the end of the list.
+
+ Note that the common case from the NT ACL chooser (groupX deny all)
+ cannot be optimised here as we need to modify user entries who are
+ in the group to change them to a deny all also.
+
+ What we're doing here is modifying the allow permissions of
+ user entries (which are more specific in POSIX ACLs) to mask
+ out the explicit deny set on the group they are in. This will
+ be a snapshot depending on current group membership but is the
+ best we can do and has the advantage of failing closed rather
+ than open.
+ ---------------------------------------------------------------------------
+ Fourth pass - cope with cumulative permissions.
+
+ for all allow user entries, if there exists an allow group entry with
+ more permissive permissions, and the user is in that group, rewrite the
+ allow user permissions to contain both sets of permissions.
+
+ Currently the code for this is #ifdef'ed out as these semantics make
+ no sense to me. JRA.
+ ---------------------------------------------------------------------------
+
+ Note we *MUST* do the deny user pass first as this will convert deny user
+ entries into allow user entries which can then be processed by the deny
+ group pass.
+
+ The above algorithm took a *lot* of thinking about - hence this
+ explanation :-). JRA.
+****************************************************************************/
+
+/****************************************************************************
+ Process a canon_ace list entries. This is very complex code. We need
+ to go through and remove the "deny" permissions from any allow entry that matches
+ the id of this entry. We have already refused any NT ACL that wasn't in correct
+ order (DENY followed by ALLOW). If any allow entry ends up with zero permissions,
+ we just remove it (to fail safe). We have already removed any duplicate ace
+ entries. Treat an "Everyone" DENY_ACE as a special case - use it to mask all
+ allow entries.
+****************************************************************************/
+
+static void process_deny_list(connection_struct *conn, canon_ace **pp_ace_list )
+{
+ canon_ace *ace_list = *pp_ace_list;
+ canon_ace *curr_ace = NULL;
+ canon_ace *curr_ace_next = NULL;
+
+ /* Pass 1 above - look for an Everyone, deny entry. */
+
+ for (curr_ace = ace_list; curr_ace; curr_ace = curr_ace_next) {
+ canon_ace *allow_ace_p;
+
+ curr_ace_next = curr_ace->next; /* So we can't lose the link. */
+
+ if (curr_ace->attr != DENY_ACE)
+ continue;
+
+ if (curr_ace->perms == (mode_t)0) {
+
+ /* Deny nothing entry - delete. */
+
+ DLIST_REMOVE(ace_list, curr_ace);
+ continue;
+ }
+
+ if (!dom_sid_equal(&curr_ace->trustee, &global_sid_World))
+ continue;
+
+ /* JRATEST - assert. */
+ SMB_ASSERT(curr_ace->owner_type == WORLD_ACE);
+
+ if (curr_ace->perms == ALL_ACE_PERMS) {
+
+ /*
+ * Optimisation. This is a DENY_ALL to Everyone. Truncate the
+ * list at this point including this entry.
+ */
+
+ canon_ace *prev_entry = DLIST_PREV(curr_ace);
+
+ free_canon_ace_list( curr_ace );
+ if (prev_entry)
+ DLIST_REMOVE(ace_list, prev_entry);
+ else {
+ /* We deleted the entire list. */
+ ace_list = NULL;
+ }
+ break;
+ }
+
+ for (allow_ace_p = curr_ace->next; allow_ace_p; allow_ace_p = allow_ace_p->next) {
+
+ /*
+ * Only mask off allow entries.
+ */
+
+ if (allow_ace_p->attr != ALLOW_ACE)
+ continue;
+
+ allow_ace_p->perms &= ~curr_ace->perms;
+ }
+
+ /*
+ * Now it's been applied, remove it.
+ */
+
+ DLIST_REMOVE(ace_list, curr_ace);
+ }
+
+ /* Pass 2 above - deal with deny user entries. */
+
+ for (curr_ace = ace_list; curr_ace; curr_ace = curr_ace_next) {
+ mode_t new_perms = (mode_t)0;
+ canon_ace *allow_ace_p;
+
+ curr_ace_next = curr_ace->next; /* So we can't lose the link. */
+
+ if (curr_ace->attr != DENY_ACE)
+ continue;
+
+ if (curr_ace->owner_type != UID_ACE)
+ continue;
+
+ if (curr_ace->perms == ALL_ACE_PERMS) {
+
+ /*
+ * Optimisation - this is a deny everything to this user.
+ * Convert to an allow nothing and push to the end of the list.
+ */
+
+ curr_ace->attr = ALLOW_ACE;
+ curr_ace->perms = (mode_t)0;
+ DLIST_DEMOTE(ace_list, curr_ace);
+ continue;
+ }
+
+ for (allow_ace_p = curr_ace->next; allow_ace_p; allow_ace_p = allow_ace_p->next) {
+
+ if (allow_ace_p->attr != ALLOW_ACE)
+ continue;
+
+ /* We process GID_ACE and WORLD_ACE entries only. */
+
+ if (allow_ace_p->owner_type == UID_ACE)
+ continue;
+
+ if (uid_entry_in_group(conn, curr_ace, allow_ace_p))
+ new_perms |= allow_ace_p->perms;
+ }
+
+ /*
+ * Convert to a allow entry, modify the perms and push to the end
+ * of the list.
+ */
+
+ curr_ace->attr = ALLOW_ACE;
+ curr_ace->perms = (new_perms & ~curr_ace->perms);
+ DLIST_DEMOTE(ace_list, curr_ace);
+ }
+
+ /* Pass 3 above - deal with deny group entries. */
+
+ for (curr_ace = ace_list; curr_ace; curr_ace = curr_ace_next) {
+ canon_ace *allow_ace_p;
+ canon_ace *allow_everyone_p = NULL;
+
+ curr_ace_next = curr_ace->next; /* So we can't lose the link. */
+
+ if (curr_ace->attr != DENY_ACE)
+ continue;
+
+ if (curr_ace->owner_type != GID_ACE)
+ continue;
+
+ for (allow_ace_p = curr_ace->next; allow_ace_p; allow_ace_p = allow_ace_p->next) {
+
+ if (allow_ace_p->attr != ALLOW_ACE)
+ continue;
+
+ /* Store a pointer to the Everyone allow, if it exists. */
+ if (allow_ace_p->owner_type == WORLD_ACE)
+ allow_everyone_p = allow_ace_p;
+
+ /* We process UID_ACE entries only. */
+
+ if (allow_ace_p->owner_type != UID_ACE)
+ continue;
+
+ /* Mask off the deny group perms. */
+
+ if (uid_entry_in_group(conn, allow_ace_p, curr_ace))
+ allow_ace_p->perms &= ~curr_ace->perms;
+ }
+
+ /*
+ * Convert the deny to an allow with the correct perms and
+ * push to the end of the list.
+ */
+
+ curr_ace->attr = ALLOW_ACE;
+ if (allow_everyone_p)
+ curr_ace->perms = allow_everyone_p->perms & ~curr_ace->perms;
+ else
+ curr_ace->perms = (mode_t)0;
+ DLIST_DEMOTE(ace_list, curr_ace);
+ }
+
+ /* Doing this fourth pass allows Windows semantics to be layered
+ * on top of POSIX semantics. I'm not sure if this is desirable.
+ * For example, in W2K ACLs there is no way to say, "Group X no
+ * access, user Y full access" if user Y is a member of group X.
+ * This seems completely broken semantics to me.... JRA.
+ */
+
+#if 0
+ /* Pass 4 above - deal with allow entries. */
+
+ for (curr_ace = ace_list; curr_ace; curr_ace = curr_ace_next) {
+ canon_ace *allow_ace_p;
+
+ curr_ace_next = curr_ace->next; /* So we can't lose the link. */
+
+ if (curr_ace->attr != ALLOW_ACE)
+ continue;
+
+ if (curr_ace->owner_type != UID_ACE)
+ continue;
+
+ for (allow_ace_p = ace_list; allow_ace_p; allow_ace_p = allow_ace_p->next) {
+
+ if (allow_ace_p->attr != ALLOW_ACE)
+ continue;
+
+ /* We process GID_ACE entries only. */
+
+ if (allow_ace_p->owner_type != GID_ACE)
+ continue;
+
+ /* OR in the group perms. */
+
+ if (uid_entry_in_group(conn, curr_ace, allow_ace_p))
+ curr_ace->perms |= allow_ace_p->perms;
+ }
+ }
+#endif
+
+ *pp_ace_list = ace_list;
+}
+
+/****************************************************************************
+ Unpack a struct security_descriptor into two canonical ace lists. We don't depend on this
+ succeeding.
+****************************************************************************/
+
+static bool unpack_canon_ace(files_struct *fsp,
+ const SMB_STRUCT_STAT *pst,
+ struct dom_sid *pfile_owner_sid,
+ struct dom_sid *pfile_grp_sid,
+ canon_ace **ppfile_ace,
+ canon_ace **ppdir_ace,
+ uint32_t security_info_sent,
+ const struct security_descriptor *psd)
+{
+ canon_ace *file_ace = NULL;
+ canon_ace *dir_ace = NULL;
+ bool ok;
+
+ *ppfile_ace = NULL;
+ *ppdir_ace = NULL;
+
+ if(security_info_sent == 0) {
+ DEBUG(0,("unpack_canon_ace: no security info sent !\n"));
+ return False;
+ }
+
+ /*
+ * If no DACL then this is a chown only security descriptor.
+ */
+
+ if(!(security_info_sent & SECINFO_DACL) || !psd->dacl)
+ return True;
+
+ /*
+ * Now go through the DACL and create the canon_ace lists.
+ */
+
+ if (!create_canon_ace_lists(fsp, pst, pfile_owner_sid, pfile_grp_sid,
+ &file_ace, &dir_ace, psd->dacl)) {
+ return False;
+ }
+
+ if ((file_ace == NULL) && (dir_ace == NULL)) {
+ /* W2K traverse DACL set - ignore. */
+ return True;
+ }
+
+ /*
+ * Go through the canon_ace list and merge entries
+ * belonging to identical users of identical allow or deny type.
+ * We can do this as all deny entries come first, followed by
+ * all allow entries (we have mandated this before accepting this acl).
+ */
+
+ print_canon_ace_list( "file ace - before merge", file_ace);
+ merge_aces( &file_ace, false);
+
+ print_canon_ace_list( "dir ace - before merge", dir_ace);
+ merge_aces( &dir_ace, true);
+
+ /*
+ * NT ACLs are order dependent. Go through the acl lists and
+ * process DENY entries by masking the allow entries.
+ */
+
+ print_canon_ace_list( "file ace - before deny", file_ace);
+ process_deny_list(fsp->conn, &file_ace);
+
+ print_canon_ace_list( "dir ace - before deny", dir_ace);
+ process_deny_list(fsp->conn, &dir_ace);
+
+ /*
+ * A well formed POSIX file or default ACL has at least 3 entries, a
+ * SMB_ACL_USER_OBJ, SMB_ACL_GROUP_OBJ, SMB_ACL_OTHER_OBJ
+ * and optionally a mask entry. Ensure this is the case.
+ */
+
+ print_canon_ace_list( "file ace - before valid", file_ace);
+
+ ok = ensure_canon_entry_valid_on_set(
+ fsp->conn,
+ &file_ace,
+ false,
+ fsp->conn->params,
+ fsp->fsp_flags.is_directory,
+ pfile_owner_sid,
+ pfile_grp_sid,
+ pst);
+ if (!ok) {
+ free_canon_ace_list(file_ace);
+ free_canon_ace_list(dir_ace);
+ return False;
+ }
+
+ print_canon_ace_list( "dir ace - before valid", dir_ace);
+
+ if (dir_ace != NULL) {
+ ok = ensure_canon_entry_valid_on_set(
+ fsp->conn,
+ &dir_ace,
+ true,
+ fsp->conn->params,
+ fsp->fsp_flags.is_directory,
+ pfile_owner_sid,
+ pfile_grp_sid,
+ pst);
+ if (!ok) {
+ free_canon_ace_list(file_ace);
+ free_canon_ace_list(dir_ace);
+ return False;
+ }
+ }
+
+ print_canon_ace_list( "file ace - return", file_ace);
+ print_canon_ace_list( "dir ace - return", dir_ace);
+
+ *ppfile_ace = file_ace;
+ *ppdir_ace = dir_ace;
+ return True;
+
+}
+
+/******************************************************************************
+ When returning permissions, try and fit NT display
+ semantics if possible. Note the the canon_entries here must have been malloced.
+ The list format should be - first entry = owner, followed by group and other user
+ entries, last entry = other.
+
+ Note that this doesn't exactly match the NT semantics for an ACL. As POSIX entries
+ are not ordered, and match on the most specific entry rather than walking a list,
+ then a simple POSIX permission of rw-r--r-- should really map to 5 entries,
+
+ Entry 0: owner : deny all except read and write.
+ Entry 1: owner : allow read and write.
+ Entry 2: group : deny all except read.
+ Entry 3: group : allow read.
+ Entry 4: Everyone : allow read.
+
+ But NT cannot display this in their ACL editor !
+********************************************************************************/
+
+static void arrange_posix_perms(const char *filename, canon_ace **pp_list_head)
+{
+ canon_ace *l_head = *pp_list_head;
+ canon_ace *owner_ace = NULL;
+ canon_ace *other_ace = NULL;
+ canon_ace *ace = NULL;
+
+ for (ace = l_head; ace; ace = ace->next) {
+ if (ace->type == SMB_ACL_USER_OBJ)
+ owner_ace = ace;
+ else if (ace->type == SMB_ACL_OTHER) {
+ /* Last ace - this is "other" */
+ other_ace = ace;
+ }
+ }
+
+ if (!owner_ace || !other_ace) {
+ DEBUG(0,("arrange_posix_perms: Invalid POSIX permissions for file %s, missing owner or other.\n",
+ filename ));
+ return;
+ }
+
+ /*
+ * The POSIX algorithm applies to owner first, and other last,
+ * so ensure they are arranged in this order.
+ */
+
+ if (owner_ace) {
+ DLIST_PROMOTE(l_head, owner_ace);
+ }
+
+ if (other_ace) {
+ DLIST_DEMOTE(l_head, other_ace);
+ }
+
+ /* We have probably changed the head of the list. */
+
+ *pp_list_head = l_head;
+}
+
+/****************************************************************************
+ Create a linked list of canonical ACE entries.
+****************************************************************************/
+
+static canon_ace *canonicalise_acl(struct connection_struct *conn,
+ const char *fname, SMB_ACL_T posix_acl,
+ const SMB_STRUCT_STAT *psbuf,
+ const struct dom_sid *powner, const struct dom_sid *pgroup, struct pai_val *pal, SMB_ACL_TYPE_T the_acl_type)
+{
+ mode_t acl_mask = (S_IRUSR|S_IWUSR|S_IXUSR);
+ canon_ace *l_head = NULL;
+ canon_ace *ace = NULL;
+ canon_ace *next_ace = NULL;
+ int entry_id = SMB_ACL_FIRST_ENTRY;
+ bool is_default_acl = (the_acl_type == SMB_ACL_TYPE_DEFAULT);
+ SMB_ACL_ENTRY_T entry;
+ size_t ace_count;
+
+ while ( posix_acl && (sys_acl_get_entry(posix_acl, entry_id, &entry) == 1)) {
+ SMB_ACL_TAG_T tagtype;
+ SMB_ACL_PERMSET_T permset;
+ struct dom_sid sid;
+ struct unixid unix_ug;
+ enum ace_owner owner_type;
+
+ entry_id = SMB_ACL_NEXT_ENTRY;
+
+ if (sys_acl_get_tag_type(entry, &tagtype) == -1)
+ continue;
+
+ if (sys_acl_get_permset(entry, &permset) == -1)
+ continue;
+
+ /* Decide which SID to use based on the ACL type. */
+ switch(tagtype) {
+ case SMB_ACL_USER_OBJ:
+ /* Get the SID from the owner. */
+ sid_copy(&sid, powner);
+ unix_ug.type = ID_TYPE_UID;
+ unix_ug.id = psbuf->st_ex_uid;
+ owner_type = UID_ACE;
+ break;
+ case SMB_ACL_USER:
+ {
+ uid_t *puid = (uid_t *)sys_acl_get_qualifier(entry);
+ if (puid == NULL) {
+ DEBUG(0,("canonicalise_acl: Failed to get uid.\n"));
+ continue;
+ }
+ uid_to_sid( &sid, *puid);
+ unix_ug.type = ID_TYPE_UID;
+ unix_ug.id = *puid;
+ owner_type = UID_ACE;
+ break;
+ }
+ case SMB_ACL_GROUP_OBJ:
+ /* Get the SID from the owning group. */
+ sid_copy(&sid, pgroup);
+ unix_ug.type = ID_TYPE_GID;
+ unix_ug.id = psbuf->st_ex_gid;
+ owner_type = GID_ACE;
+ break;
+ case SMB_ACL_GROUP:
+ {
+ gid_t *pgid = (gid_t *)sys_acl_get_qualifier(entry);
+ if (pgid == NULL) {
+ DEBUG(0,("canonicalise_acl: Failed to get gid.\n"));
+ continue;
+ }
+ gid_to_sid( &sid, *pgid);
+ unix_ug.type = ID_TYPE_GID;
+ unix_ug.id = *pgid;
+ owner_type = GID_ACE;
+ break;
+ }
+ case SMB_ACL_MASK:
+ acl_mask = convert_permset_to_mode_t(permset);
+ continue; /* Don't count the mask as an entry. */
+ case SMB_ACL_OTHER:
+ /* Use the Everyone SID */
+ sid = global_sid_World;
+ unix_ug.type = ID_TYPE_NOT_SPECIFIED;
+ unix_ug.id = -1;
+ owner_type = WORLD_ACE;
+ break;
+ default:
+ DEBUG(0,("canonicalise_acl: Unknown tagtype %u\n", (unsigned int)tagtype));
+ continue;
+ }
+
+ /*
+ * Add this entry to the list.
+ */
+
+ if ((ace = talloc(talloc_tos(), canon_ace)) == NULL)
+ goto fail;
+
+ *ace = (canon_ace) {
+ .type = tagtype,
+ .perms = convert_permset_to_mode_t(permset),
+ .attr = ALLOW_ACE,
+ .trustee = sid,
+ .unix_ug = unix_ug,
+ .owner_type = owner_type
+ };
+ ace->ace_flags = get_pai_flags(pal, ace, is_default_acl);
+
+ DLIST_ADD(l_head, ace);
+ }
+
+ /*
+ * This next call will ensure we have at least a user/group/world set.
+ */
+
+ if (!ensure_canon_entry_valid_on_get(conn, &l_head,
+ powner, pgroup,
+ psbuf))
+ goto fail;
+
+ /*
+ * Now go through the list, masking the permissions with the
+ * acl_mask. Ensure all DENY Entries are at the start of the list.
+ */
+
+ DEBUG(10,("canonicalise_acl: %s ace entries before arrange :\n", is_default_acl ? "Default" : "Access"));
+
+ for ( ace_count = 0, ace = l_head; ace; ace = next_ace, ace_count++) {
+ next_ace = ace->next;
+
+ /* Masks are only applied to entries other than USER_OBJ and OTHER. */
+ if (ace->type != SMB_ACL_OTHER && ace->type != SMB_ACL_USER_OBJ)
+ ace->perms &= acl_mask;
+
+ if (ace->perms == 0) {
+ DLIST_PROMOTE(l_head, ace);
+ }
+
+ if( DEBUGLVL( 10 ) ) {
+ print_canon_ace(ace, ace_count);
+ }
+ }
+
+ arrange_posix_perms(fname,&l_head );
+
+ print_canon_ace_list( "canonicalise_acl: ace entries after arrange", l_head );
+
+ return l_head;
+
+ fail:
+
+ free_canon_ace_list(l_head);
+ return NULL;
+}
+
+/****************************************************************************
+ Check if the current user group list contains a given group.
+****************************************************************************/
+
+bool current_user_in_group(connection_struct *conn, gid_t gid)
+{
+ uint32_t i;
+ const struct security_unix_token *utok = get_current_utok(conn);
+
+ for (i = 0; i < utok->ngroups; i++) {
+ if (utok->groups[i] == gid) {
+ return True;
+ }
+ }
+
+ return False;
+}
+
+/****************************************************************************
+ Should we override a deny ? Check 'acl group control' and 'dos filemode'.
+****************************************************************************/
+
+static bool acl_group_override_fsp(files_struct *fsp)
+{
+ if ((errno != EPERM) && (errno != EACCES)) {
+ return false;
+ }
+
+ /* file primary group == user primary or supplementary group */
+ if (lp_acl_group_control(SNUM(fsp->conn)) &&
+ current_user_in_group(fsp->conn, fsp->fsp_name->st.st_ex_gid)) {
+ return true;
+ }
+
+ /* user has writeable permission */
+ if (lp_dos_filemode(SNUM(fsp->conn)) && can_write_to_fsp(fsp)) {
+ return true;
+ }
+
+ return false;
+}
+
+/****************************************************************************
+ Attempt to apply an ACL to a file or directory.
+****************************************************************************/
+
+static bool set_canon_ace_list(files_struct *fsp,
+ canon_ace *the_ace,
+ bool default_ace,
+ const SMB_STRUCT_STAT *psbuf,
+ bool *pacl_set_support)
+{
+ bool ret = False;
+ SMB_ACL_T the_acl = sys_acl_init(talloc_tos());
+ canon_ace *p_ace;
+ int i;
+ SMB_ACL_ENTRY_T mask_entry;
+ bool got_mask_entry = False;
+ SMB_ACL_PERMSET_T mask_permset;
+ SMB_ACL_TYPE_T the_acl_type = (default_ace ? SMB_ACL_TYPE_DEFAULT : SMB_ACL_TYPE_ACCESS);
+ bool needs_mask = False;
+ int sret;
+
+ /* Use the psbuf that was passed in. */
+ if (psbuf != &fsp->fsp_name->st) {
+ fsp->fsp_name->st = *psbuf;
+ }
+
+#if defined(POSIX_ACL_NEEDS_MASK)
+ /* HP-UX always wants to have a mask (called "class" there). */
+ needs_mask = True;
+#endif
+
+ if (the_acl == NULL) {
+ DEBUG(0, ("sys_acl_init failed to allocate an ACL\n"));
+ return false;
+ }
+
+ if( DEBUGLVL( 10 )) {
+ dbgtext("set_canon_ace_list: setting ACL:\n");
+ for (i = 0, p_ace = the_ace; p_ace; p_ace = p_ace->next, i++ ) {
+ print_canon_ace( p_ace, i);
+ }
+ }
+
+ for (i = 0, p_ace = the_ace; p_ace; p_ace = p_ace->next, i++ ) {
+ SMB_ACL_ENTRY_T the_entry;
+ SMB_ACL_PERMSET_T the_permset;
+
+ /*
+ * ACLs only "need" an ACL_MASK entry if there are any named user or
+ * named group entries. But if there is an ACL_MASK entry, it applies
+ * to ACL_USER, ACL_GROUP, and ACL_GROUP_OBJ entries. Set the mask
+ * so that it doesn't deny (i.e., mask off) any permissions.
+ */
+
+ if (p_ace->type == SMB_ACL_USER || p_ace->type == SMB_ACL_GROUP) {
+ needs_mask = True;
+ }
+
+ /*
+ * Get the entry for this ACE.
+ */
+
+ if (sys_acl_create_entry(&the_acl, &the_entry) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to create entry %d. (%s)\n",
+ i, strerror(errno) ));
+ goto fail;
+ }
+
+ if (p_ace->type == SMB_ACL_MASK) {
+ mask_entry = the_entry;
+ got_mask_entry = True;
+ }
+
+ /*
+ * Ok - we now know the ACL calls should be working, don't
+ * allow fallback to chmod.
+ */
+
+ *pacl_set_support = True;
+
+ /*
+ * Initialise the entry from the canon_ace.
+ */
+
+ /*
+ * First tell the entry what type of ACE this is.
+ */
+
+ if (sys_acl_set_tag_type(the_entry, p_ace->type) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to set tag type on entry %d. (%s)\n",
+ i, strerror(errno) ));
+ goto fail;
+ }
+
+ /*
+ * Only set the qualifier (user or group id) if the entry is a user
+ * or group id ACE.
+ */
+
+ if ((p_ace->type == SMB_ACL_USER) || (p_ace->type == SMB_ACL_GROUP)) {
+ if (sys_acl_set_qualifier(the_entry,(void *)&p_ace->unix_ug.id) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to set qualifier on entry %d. (%s)\n",
+ i, strerror(errno) ));
+ goto fail;
+ }
+ }
+
+ /*
+ * Convert the mode_t perms in the canon_ace to a POSIX permset.
+ */
+
+ if (sys_acl_get_permset(the_entry, &the_permset) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to get permset on entry %d. (%s)\n",
+ i, strerror(errno) ));
+ goto fail;
+ }
+
+ if (map_acl_perms_to_permset(p_ace->perms, &the_permset) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to create permset for mode (%u) on entry %d. (%s)\n",
+ (unsigned int)p_ace->perms, i, strerror(errno) ));
+ goto fail;
+ }
+
+ /*
+ * ..and apply them to the entry.
+ */
+
+ if (sys_acl_set_permset(the_entry, the_permset) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to add permset on entry %d. (%s)\n",
+ i, strerror(errno) ));
+ goto fail;
+ }
+
+ if( DEBUGLVL( 10 ))
+ print_canon_ace( p_ace, i);
+
+ }
+
+ if (needs_mask && !got_mask_entry) {
+ if (sys_acl_create_entry(&the_acl, &mask_entry) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to create mask entry. (%s)\n", strerror(errno) ));
+ goto fail;
+ }
+
+ if (sys_acl_set_tag_type(mask_entry, SMB_ACL_MASK) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to set tag type on mask entry. (%s)\n",strerror(errno) ));
+ goto fail;
+ }
+
+ if (sys_acl_get_permset(mask_entry, &mask_permset) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to get mask permset. (%s)\n", strerror(errno) ));
+ goto fail;
+ }
+
+ if (map_acl_perms_to_permset(S_IRUSR|S_IWUSR|S_IXUSR, &mask_permset) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to create mask permset. (%s)\n", strerror(errno) ));
+ goto fail;
+ }
+
+ if (sys_acl_set_permset(mask_entry, mask_permset) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to add mask permset. (%s)\n", strerror(errno) ));
+ goto fail;
+ }
+ }
+
+ /*
+ * Finally apply it to the file or directory.
+ */
+ sret = SMB_VFS_SYS_ACL_SET_FD(fsp, the_acl_type, the_acl);
+ if (sret == -1) {
+ /*
+ * Some systems allow all the above calls and only fail with no ACL support
+ * when attempting to apply the acl. HPUX with HFS is an example of this. JRA.
+ */
+ if (no_acl_syscall_error(errno)) {
+ *pacl_set_support = false;
+ }
+
+ if (acl_group_override_fsp(fsp)) {
+ DBG_DEBUG("acl group control on and current user in "
+ "file [%s] primary group.\n",
+ fsp_str_dbg(fsp));
+
+ become_root();
+ sret = SMB_VFS_SYS_ACL_SET_FD(fsp,
+ the_acl_type,
+ the_acl);
+ unbecome_root();
+ if (sret == 0) {
+ ret = true;
+ }
+ }
+
+ if (ret == false) {
+ DBG_WARNING("sys_acl_set_file on file [%s]: (%s)\n",
+ fsp_str_dbg(fsp), strerror(errno));
+ goto fail;
+ }
+ }
+
+ ret = True;
+
+ fail:
+
+ if (the_acl != NULL) {
+ TALLOC_FREE(the_acl);
+ }
+
+ return ret;
+}
+
+/****************************************************************************
+
+****************************************************************************/
+
+SMB_ACL_T free_empty_sys_acl(connection_struct *conn, SMB_ACL_T the_acl)
+{
+ SMB_ACL_ENTRY_T entry;
+
+ if (!the_acl)
+ return NULL;
+ if (sys_acl_get_entry(the_acl, SMB_ACL_FIRST_ENTRY, &entry) != 1) {
+ TALLOC_FREE(the_acl);
+ return NULL;
+ }
+ return the_acl;
+}
+
+/****************************************************************************
+ Convert a canon_ace to a generic 3 element permission - if possible.
+****************************************************************************/
+
+#define MAP_PERM(p,mask,result) (((p) & (mask)) ? (result) : 0 )
+
+static bool convert_canon_ace_to_posix_perms( files_struct *fsp, canon_ace *file_ace_list, mode_t *posix_perms)
+{
+ size_t ace_count = count_canon_ace_list(file_ace_list);
+ canon_ace *ace_p;
+ canon_ace *owner_ace = NULL;
+ canon_ace *group_ace = NULL;
+ canon_ace *other_ace = NULL;
+
+ if (ace_count > 5) {
+ DEBUG(3,("convert_canon_ace_to_posix_perms: Too many ACE "
+ "entries for file %s to convert to posix perms.\n",
+ fsp_str_dbg(fsp)));
+ return False;
+ }
+
+ for (ace_p = file_ace_list; ace_p; ace_p = ace_p->next) {
+ if (ace_p->owner_type == UID_ACE)
+ owner_ace = ace_p;
+ else if (ace_p->owner_type == GID_ACE)
+ group_ace = ace_p;
+ else if (ace_p->owner_type == WORLD_ACE)
+ other_ace = ace_p;
+ }
+
+ if (!owner_ace || !group_ace || !other_ace) {
+ DEBUG(3,("convert_canon_ace_to_posix_perms: Can't get "
+ "standard entries for file %s.\n", fsp_str_dbg(fsp)));
+ return False;
+ }
+
+ /*
+ * Ensure all ACE entries are owner, group or other.
+ * We can't set if there are any other SIDs.
+ */
+ for (ace_p = file_ace_list; ace_p; ace_p = ace_p->next) {
+ if (ace_p == owner_ace || ace_p == group_ace ||
+ ace_p == other_ace) {
+ continue;
+ }
+ if (ace_p->owner_type == UID_ACE) {
+ if (ace_p->unix_ug.id != owner_ace->unix_ug.id) {
+ DEBUG(3,("Invalid uid %u in ACE for file %s.\n",
+ (unsigned int)ace_p->unix_ug.id,
+ fsp_str_dbg(fsp)));
+ return false;
+ }
+ } else if (ace_p->owner_type == GID_ACE) {
+ if (ace_p->unix_ug.id != group_ace->unix_ug.id) {
+ DEBUG(3,("Invalid gid %u in ACE for file %s.\n",
+ (unsigned int)ace_p->unix_ug.id,
+ fsp_str_dbg(fsp)));
+ return false;
+ }
+ } else {
+ /*
+ * There should be no duplicate WORLD_ACE entries.
+ */
+
+ DEBUG(3,("Invalid type %u, uid %u in "
+ "ACE for file %s.\n",
+ (unsigned int)ace_p->owner_type,
+ (unsigned int)ace_p->unix_ug.id,
+ fsp_str_dbg(fsp)));
+ return false;
+ }
+ }
+
+ *posix_perms = (mode_t)0;
+
+ *posix_perms |= owner_ace->perms;
+ *posix_perms |= MAP_PERM(group_ace->perms, S_IRUSR, S_IRGRP);
+ *posix_perms |= MAP_PERM(group_ace->perms, S_IWUSR, S_IWGRP);
+ *posix_perms |= MAP_PERM(group_ace->perms, S_IXUSR, S_IXGRP);
+ *posix_perms |= MAP_PERM(other_ace->perms, S_IRUSR, S_IROTH);
+ *posix_perms |= MAP_PERM(other_ace->perms, S_IWUSR, S_IWOTH);
+ *posix_perms |= MAP_PERM(other_ace->perms, S_IXUSR, S_IXOTH);
+
+ /* The owner must have at least read access. */
+
+ *posix_perms |= S_IRUSR;
+ if (fsp->fsp_flags.is_directory)
+ *posix_perms |= (S_IWUSR|S_IXUSR);
+
+ DEBUG(10,("convert_canon_ace_to_posix_perms: converted u=%o,g=%o,w=%o "
+ "to perm=0%o for file %s.\n", (int)owner_ace->perms,
+ (int)group_ace->perms, (int)other_ace->perms,
+ (int)*posix_perms, fsp_str_dbg(fsp)));
+
+ return True;
+}
+
+/****************************************************************************
+ Incoming NT ACLs on a directory can be split into a default POSIX acl (CI|OI|IO) and
+ a normal POSIX acl. Win2k needs these split acls re-merging into one ACL
+ with CI|OI set so it is inherited and also applies to the directory.
+ Based on code from "Jim McDonough" <jmcd@us.ibm.com>.
+****************************************************************************/
+
+static size_t merge_default_aces( struct security_ace *nt_ace_list, size_t num_aces)
+{
+ size_t i, j;
+
+ for (i = 0; i < num_aces; i++) {
+ for (j = i+1; j < num_aces; j++) {
+ uint32_t i_flags_ni = (nt_ace_list[i].flags & ~SEC_ACE_FLAG_INHERITED_ACE);
+ uint32_t j_flags_ni = (nt_ace_list[j].flags & ~SEC_ACE_FLAG_INHERITED_ACE);
+ bool i_inh = (nt_ace_list[i].flags & SEC_ACE_FLAG_INHERITED_ACE) ? True : False;
+ bool j_inh = (nt_ace_list[j].flags & SEC_ACE_FLAG_INHERITED_ACE) ? True : False;
+
+ /* We know the lower number ACE's are file entries. */
+ if ((nt_ace_list[i].type == nt_ace_list[j].type) &&
+ (nt_ace_list[i].size == nt_ace_list[j].size) &&
+ (nt_ace_list[i].access_mask == nt_ace_list[j].access_mask) &&
+ dom_sid_equal(&nt_ace_list[i].trustee, &nt_ace_list[j].trustee) &&
+ (i_inh == j_inh) &&
+ (i_flags_ni == 0) &&
+ (j_flags_ni == (SEC_ACE_FLAG_OBJECT_INHERIT|
+ SEC_ACE_FLAG_CONTAINER_INHERIT|
+ SEC_ACE_FLAG_INHERIT_ONLY))) {
+ /*
+ * W2K wants to have access allowed zero access ACE's
+ * at the end of the list. If the mask is zero, merge
+ * the non-inherited ACE onto the inherited ACE.
+ */
+
+ if (nt_ace_list[i].access_mask == 0) {
+ nt_ace_list[j].flags = SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT|
+ (i_inh ? SEC_ACE_FLAG_INHERITED_ACE : 0);
+ ARRAY_DEL_ELEMENT(nt_ace_list, i, num_aces);
+
+ DEBUG(10,("merge_default_aces: Merging zero access ACE %u onto ACE %u.\n",
+ (unsigned int)i, (unsigned int)j ));
+
+ /*
+ * If we remove the i'th element, we
+ * should decrement i so that we don't
+ * skip over the succeeding element.
+ */
+ i--;
+ num_aces--;
+ break;
+ } else {
+ /*
+ * These are identical except for the flags.
+ * Merge the inherited ACE onto the non-inherited ACE.
+ */
+
+ nt_ace_list[i].flags = SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT|
+ (i_inh ? SEC_ACE_FLAG_INHERITED_ACE : 0);
+ ARRAY_DEL_ELEMENT(nt_ace_list, j, num_aces);
+
+ DEBUG(10,("merge_default_aces: Merging ACE %u onto ACE %u.\n",
+ (unsigned int)j, (unsigned int)i ));
+
+ /*
+ * If we remove the j'th element, we
+ * should decrement j and continue
+ * around the loop, so as not to skip
+ * subsequent elements.
+ */
+ j--;
+ num_aces--;
+ }
+ }
+ }
+ }
+
+ return num_aces;
+}
+
+
+/****************************************************************************
+ Reply to query a security descriptor from an fsp. If it succeeds it allocates
+ the space for the return elements and returns the size needed to return the
+ security descriptor. This should be the only external function needed for
+ the UNIX style get ACL.
+****************************************************************************/
+
+static NTSTATUS posix_get_nt_acl_common(struct connection_struct *conn,
+ const char *name,
+ const SMB_STRUCT_STAT *sbuf,
+ struct pai_val *pal,
+ SMB_ACL_T posix_acl,
+ SMB_ACL_T def_acl,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc)
+{
+ struct dom_sid owner_sid;
+ struct dom_sid group_sid;
+ size_t sd_size = 0;
+ struct security_acl *psa = NULL;
+ size_t num_acls = 0;
+ size_t num_def_acls = 0;
+ size_t num_aces = 0;
+ canon_ace *file_ace = NULL;
+ canon_ace *dir_ace = NULL;
+ struct security_ace *nt_ace_list = NULL;
+ struct security_descriptor *psd = NULL;
+
+ /*
+ * Get the owner, group and world SIDs.
+ */
+
+ create_file_sids(sbuf, &owner_sid, &group_sid);
+
+ if (security_info & SECINFO_DACL) {
+
+ /*
+ * In the optimum case Creator Owner and Creator Group would be used for
+ * the ACL_USER_OBJ and ACL_GROUP_OBJ entries, respectively, but this
+ * would lead to usability problems under Windows: The Creator entries
+ * are only available in browse lists of directories and not for files;
+ * additionally the identity of the owning group couldn't be determined.
+ * We therefore use those identities only for Default ACLs.
+ */
+
+ /* Create the canon_ace lists. */
+ file_ace = canonicalise_acl(conn, name, posix_acl, sbuf,
+ &owner_sid, &group_sid, pal,
+ SMB_ACL_TYPE_ACCESS);
+
+ /* We must have *some* ACLS. */
+
+ if (count_canon_ace_list(file_ace) == 0) {
+ DEBUG(0,("get_nt_acl : No ACLs on file (%s) !\n", name));
+ goto done;
+ }
+
+ if (S_ISDIR(sbuf->st_ex_mode) && def_acl) {
+ dir_ace = canonicalise_acl(conn, name, def_acl,
+ sbuf,
+ &global_sid_Creator_Owner,
+ &global_sid_Creator_Group,
+ pal, SMB_ACL_TYPE_DEFAULT);
+ }
+
+ /*
+ * Create the NT ACE list from the canonical ace lists.
+ */
+
+ {
+ canon_ace *ace;
+ enum security_ace_type nt_acl_type;
+
+ num_acls = count_canon_ace_list(file_ace);
+ num_def_acls = count_canon_ace_list(dir_ace);
+
+ nt_ace_list = talloc_zero_array(
+ talloc_tos(), struct security_ace,
+ num_acls + num_def_acls);
+
+ if (nt_ace_list == NULL) {
+ DEBUG(0,("get_nt_acl: Unable to malloc space for nt_ace_list.\n"));
+ goto done;
+ }
+
+ /*
+ * Create the NT ACE list from the canonical ace lists.
+ */
+
+ for (ace = file_ace; ace != NULL; ace = ace->next) {
+ uint32_t acc = map_canon_ace_perms(SNUM(conn),
+ &nt_acl_type,
+ ace->perms,
+ S_ISDIR(sbuf->st_ex_mode));
+ init_sec_ace(&nt_ace_list[num_aces++],
+ &ace->trustee,
+ nt_acl_type,
+ acc,
+ ace->ace_flags);
+ }
+
+ for (ace = dir_ace; ace != NULL; ace = ace->next) {
+ uint32_t acc = map_canon_ace_perms(SNUM(conn),
+ &nt_acl_type,
+ ace->perms,
+ S_ISDIR(sbuf->st_ex_mode));
+ init_sec_ace(&nt_ace_list[num_aces++],
+ &ace->trustee,
+ nt_acl_type,
+ acc,
+ ace->ace_flags |
+ SEC_ACE_FLAG_OBJECT_INHERIT|
+ SEC_ACE_FLAG_CONTAINER_INHERIT|
+ SEC_ACE_FLAG_INHERIT_ONLY);
+ }
+
+ /*
+ * Merge POSIX default ACLs and normal ACLs into one NT ACE.
+ * Win2K needs this to get the inheritance correct when replacing ACLs
+ * on a directory tree. Based on work by Jim @ IBM.
+ */
+
+ num_aces = merge_default_aces(nt_ace_list, num_aces);
+ }
+
+ if (num_aces) {
+ if((psa = make_sec_acl( talloc_tos(), NT4_ACL_REVISION, num_aces, nt_ace_list)) == NULL) {
+ DEBUG(0,("get_nt_acl: Unable to malloc space for acl.\n"));
+ goto done;
+ }
+ }
+ } /* security_info & SECINFO_DACL */
+
+ psd = make_standard_sec_desc(mem_ctx,
+ (security_info & SECINFO_OWNER) ? &owner_sid : NULL,
+ (security_info & SECINFO_GROUP) ? &group_sid : NULL,
+ psa,
+ &sd_size);
+
+ if(!psd) {
+ DEBUG(0,("get_nt_acl: Unable to malloc space for security descriptor.\n"));
+ sd_size = 0;
+ goto done;
+ }
+
+ /*
+ * Windows 2000: The DACL_PROTECTED flag in the security
+ * descriptor marks the ACL as non-inheriting, i.e., no
+ * ACEs from higher level directories propagate to this
+ * ACL. In the POSIX ACL model permissions are only
+ * inherited at file create time, so ACLs never contain
+ * any ACEs that are inherited dynamically. The DACL_PROTECTED
+ * flag doesn't seem to bother Windows NT.
+ * Always set this if map acl inherit is turned off.
+ */
+ if (pal == NULL || !lp_map_acl_inherit(SNUM(conn))) {
+ psd->type |= SEC_DESC_DACL_PROTECTED;
+ } else {
+ psd->type |= pal->sd_type;
+ }
+
+ if (psd->dacl) {
+ dacl_sort_into_canonical_order(psd->dacl->aces, (unsigned int)psd->dacl->num_aces);
+ }
+
+ *ppdesc = psd;
+
+ done:
+
+ if (posix_acl) {
+ TALLOC_FREE(posix_acl);
+ }
+ if (def_acl) {
+ TALLOC_FREE(def_acl);
+ }
+ free_canon_ace_list(file_ace);
+ free_canon_ace_list(dir_ace);
+ free_inherited_info(pal);
+ TALLOC_FREE(nt_ace_list);
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS posix_fget_nt_acl(struct files_struct *fsp, uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc)
+{
+ SMB_STRUCT_STAT sbuf;
+ SMB_ACL_T posix_acl = NULL;
+ SMB_ACL_T def_acl = NULL;
+ struct pai_val *pal;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ *ppdesc = NULL;
+
+ DEBUG(10,("posix_fget_nt_acl: called for file %s\n",
+ fsp_str_dbg(fsp)));
+
+ /* Get the stat struct for the owner info. */
+ if(SMB_VFS_FSTAT(fsp, &sbuf) != 0) {
+ TALLOC_FREE(frame);
+ return map_nt_error_from_unix(errno);
+ }
+
+ /* Get the ACL from the fd. */
+ posix_acl = SMB_VFS_SYS_ACL_GET_FD(fsp,
+ SMB_ACL_TYPE_ACCESS,
+ frame);
+
+ /* If it's a directory get the default POSIX ACL. */
+ if(fsp->fsp_flags.is_directory) {
+ def_acl = SMB_VFS_SYS_ACL_GET_FD(fsp,
+ SMB_ACL_TYPE_DEFAULT,
+ frame);
+ def_acl = free_empty_sys_acl(fsp->conn, def_acl);
+ }
+
+ pal = fload_inherited_info(fsp);
+
+ status = posix_get_nt_acl_common(fsp->conn, fsp->fsp_name->base_name,
+ &sbuf, pal, posix_acl, def_acl,
+ security_info, mem_ctx, ppdesc);
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Try to chown a file. We will be able to chown it under the following conditions.
+
+ 1) If we have root privileges, then it will just work.
+ 2) If we have SeRestorePrivilege we can change the user + group to any other user.
+ 3) If we have SeTakeOwnershipPrivilege we can change the user to the current user.
+ 4) If we have write permission to the file and dos_filemodes is set
+ then allow chown to the currently authenticated user.
+****************************************************************************/
+
+static NTSTATUS try_chown(files_struct *fsp, uid_t uid, gid_t gid)
+{
+ NTSTATUS status;
+ int ret;
+
+ if(!CAN_WRITE(fsp->conn)) {
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+
+ /* Case (1). */
+ ret = SMB_VFS_FCHOWN(fsp, uid, gid);
+ if (ret == 0) {
+ return NT_STATUS_OK;
+ }
+
+ /* Case (2) / (3) */
+ if (lp_enable_privileges()) {
+ bool has_take_ownership_priv = security_token_has_privilege(
+ get_current_nttok(fsp->conn),
+ SEC_PRIV_TAKE_OWNERSHIP);
+ bool has_restore_priv = security_token_has_privilege(
+ get_current_nttok(fsp->conn),
+ SEC_PRIV_RESTORE);
+
+ if (has_restore_priv) {
+ ; /* Case (2) */
+ } else if (has_take_ownership_priv) {
+ /* Case (3) */
+ if (uid == get_current_uid(fsp->conn)) {
+ gid = (gid_t)-1;
+ } else {
+ has_take_ownership_priv = false;
+ }
+ }
+
+ if (has_take_ownership_priv || has_restore_priv) {
+ status = NT_STATUS_OK;
+ become_root();
+ ret = SMB_VFS_FCHOWN(fsp, uid, gid);
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ }
+ unbecome_root();
+ return status;
+ }
+ }
+
+ /* Case (4). */
+ /* If "dos filemode" isn't set, we're done. */
+ if (!lp_dos_filemode(SNUM(fsp->conn))) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ /*
+ * If we have a writable handle, obviously we
+ * can write to the file.
+ */
+ if (!fsp->fsp_flags.can_write) {
+ /*
+ * If we don't have a writable handle, we
+ * need to read the ACL on the file to
+ * see if we can write to it.
+ */
+ if (!can_write_to_fsp(fsp)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+
+ /* only allow chown to the current user. This is more secure,
+ and also copes with the case where the SID in a take ownership ACL is
+ a local SID on the users workstation
+ */
+ if (uid != get_current_uid(fsp->conn)) {
+ return NT_STATUS_INVALID_OWNER;
+ }
+
+ status = NT_STATUS_OK;
+ become_root();
+ /* Keep the current file gid the same. */
+ ret = SMB_VFS_FCHOWN(fsp, uid, (gid_t)-1);
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ }
+ unbecome_root();
+
+ return status;
+}
+
+/*
+ * Check whether a chown is needed and if so, attempt the chown
+ * A returned error indicates that the chown failed.
+ * NT_STATUS_OK with did_chown == false indicates that the chown was skipped.
+ * NT_STATUS_OK with did_chown == true indicates that the chown succeeded
+ */
+NTSTATUS chown_if_needed(files_struct *fsp, uint32_t security_info_sent,
+ const struct security_descriptor *psd,
+ bool *did_chown)
+{
+ NTSTATUS status;
+ uid_t uid = (uid_t)-1;
+ gid_t gid = (gid_t)-1;
+
+ status = unpack_nt_owners(fsp->conn, &uid, &gid, security_info_sent, psd);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (((uid == (uid_t)-1) || (fsp->fsp_name->st.st_ex_uid == uid)) &&
+ ((gid == (gid_t)-1) || (fsp->fsp_name->st.st_ex_gid == gid))) {
+ /*
+ * Skip chown
+ */
+ *did_chown = false;
+ return NT_STATUS_OK;
+ }
+
+ DBG_NOTICE("chown %s. uid = %u, gid = %u.\n",
+ fsp_str_dbg(fsp), (unsigned int) uid, (unsigned int)gid);
+
+ status = try_chown(fsp, uid, gid);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_INFO("chown %s, %u, %u failed. Error = %s.\n",
+ fsp_str_dbg(fsp), (unsigned int) uid,
+ (unsigned int)gid, nt_errstr(status));
+ return status;
+ }
+
+ /*
+ * Recheck the current state of the file, which may have changed.
+ * (owner and suid/sgid bits, for instance)
+ */
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ *did_chown = true;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Reply to set a security descriptor on an fsp. security_info_sent is the
+ description of the following NT ACL.
+ This should be the only external function needed for the UNIX style set ACL.
+ We make a copy of psd_orig as internal functions modify the elements inside
+ it, even though it's a const pointer.
+****************************************************************************/
+
+NTSTATUS set_nt_acl(files_struct *fsp, uint32_t security_info_sent, const struct security_descriptor *psd_orig)
+{
+ connection_struct *conn = fsp->conn;
+ struct dom_sid file_owner_sid;
+ struct dom_sid file_grp_sid;
+ canon_ace *file_ace_list = NULL;
+ canon_ace *dir_ace_list = NULL;
+ bool acl_perms = False;
+ mode_t orig_mode = (mode_t)0;
+ NTSTATUS status;
+ bool set_acl_as_root = false;
+ bool acl_set_support = false;
+ bool ret = false;
+ struct security_descriptor *psd = NULL;
+
+ DEBUG(10,("set_nt_acl: called for file %s\n",
+ fsp_str_dbg(fsp)));
+
+ if (!CAN_WRITE(conn)) {
+ DEBUG(10,("set acl rejected on read-only share\n"));
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+
+ if (psd_orig == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * MS NFS mode, here's the deal: the client merely wants to
+ * modify the mode, but roundtripping get_acl/set/acl would
+ * add additional POSIX ACEs. So in case we get a request
+ * containing a MS NFS mode SID, we do nothing here.
+ */
+ if (security_descriptor_with_ms_nfs(psd_orig)) {
+ return NT_STATUS_OK;
+ }
+
+ psd = security_descriptor_copy(talloc_tos(), psd_orig);
+ if (psd == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * Get the current state of the file.
+ */
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* Save the original element we check against. */
+ orig_mode = fsp->fsp_name->st.st_ex_mode;
+
+ /*
+ * Unpack the user/group/world id's.
+ */
+
+ /* POSIX can't cope with missing owner/group. */
+ if ((security_info_sent & SECINFO_OWNER) && (psd->owner_sid == NULL)) {
+ security_info_sent &= ~SECINFO_OWNER;
+ }
+ if ((security_info_sent & SECINFO_GROUP) && (psd->group_sid == NULL)) {
+ security_info_sent &= ~SECINFO_GROUP;
+ }
+
+ /* If UNIX owner is inherited and Windows isn't, then
+ * setting the UNIX owner based on Windows owner conflicts
+ * with the inheritance rule
+ */
+ if (lp_inherit_owner(SNUM(conn)) == INHERIT_OWNER_UNIX_ONLY) {
+ security_info_sent &= ~SECINFO_OWNER;
+ }
+
+ /*
+ * Do we need to chown ? If so this must be done first as the incoming
+ * CREATOR_OWNER acl will be relative to the *new* owner, not the old.
+ * Noticed by Simo.
+ *
+ * If we successfully chowned, we know we must be able to set
+ * the acl, so do it as root (set_acl_as_root).
+ */
+ status = chown_if_needed(fsp, security_info_sent, psd, &set_acl_as_root);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ create_file_sids(&fsp->fsp_name->st, &file_owner_sid, &file_grp_sid);
+
+ if((security_info_sent & SECINFO_DACL) &&
+ (psd->type & SEC_DESC_DACL_PRESENT) &&
+ (psd->dacl == NULL)) {
+ struct security_ace ace[3];
+
+ /* We can't have NULL DACL in POSIX.
+ Use owner/group/Everyone -> full access. */
+
+ init_sec_ace(&ace[0],
+ &file_owner_sid,
+ SEC_ACE_TYPE_ACCESS_ALLOWED,
+ GENERIC_ALL_ACCESS,
+ 0);
+ init_sec_ace(&ace[1],
+ &file_grp_sid,
+ SEC_ACE_TYPE_ACCESS_ALLOWED,
+ GENERIC_ALL_ACCESS,
+ 0);
+ init_sec_ace(&ace[2],
+ &global_sid_World,
+ SEC_ACE_TYPE_ACCESS_ALLOWED,
+ GENERIC_ALL_ACCESS,
+ 0);
+ psd->dacl = make_sec_acl(talloc_tos(),
+ NT4_ACL_REVISION,
+ 3,
+ ace);
+ if (psd->dacl == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ security_acl_map_generic(psd->dacl, &file_generic_mapping);
+ }
+
+ acl_perms = unpack_canon_ace(fsp, &fsp->fsp_name->st, &file_owner_sid,
+ &file_grp_sid, &file_ace_list,
+ &dir_ace_list, security_info_sent, psd);
+
+ /* Ignore W2K traverse DACL set. */
+ if (!file_ace_list && !dir_ace_list) {
+ return NT_STATUS_OK;
+ }
+
+ if (!acl_perms) {
+ DEBUG(3,("set_nt_acl: cannot set permissions\n"));
+ free_canon_ace_list(file_ace_list);
+ free_canon_ace_list(dir_ace_list);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /*
+ * Only change security if we got a DACL.
+ */
+
+ if(!(security_info_sent & SECINFO_DACL) || (psd->dacl == NULL)) {
+ free_canon_ace_list(file_ace_list);
+ free_canon_ace_list(dir_ace_list);
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * Try using the POSIX ACL set first. Fall back to chmod if
+ * we have no ACL support on this filesystem.
+ */
+
+ if (acl_perms && file_ace_list) {
+ if (set_acl_as_root) {
+ become_root();
+ }
+ ret = set_canon_ace_list(fsp, file_ace_list, false,
+ &fsp->fsp_name->st, &acl_set_support);
+ if (set_acl_as_root) {
+ unbecome_root();
+ }
+ if (acl_set_support && ret == false) {
+ DEBUG(3,("set_nt_acl: failed to set file acl on file "
+ "%s (%s).\n", fsp_str_dbg(fsp),
+ strerror(errno)));
+ free_canon_ace_list(file_ace_list);
+ free_canon_ace_list(dir_ace_list);
+ return map_nt_error_from_unix(errno);
+ }
+ }
+
+ if (acl_perms && acl_set_support && fsp->fsp_flags.is_directory) {
+ if (dir_ace_list) {
+ if (set_acl_as_root) {
+ become_root();
+ }
+ ret = set_canon_ace_list(fsp, dir_ace_list, true,
+ &fsp->fsp_name->st,
+ &acl_set_support);
+ if (set_acl_as_root) {
+ unbecome_root();
+ }
+ if (ret == false) {
+ DEBUG(3,("set_nt_acl: failed to set default "
+ "acl on directory %s (%s).\n",
+ fsp_str_dbg(fsp), strerror(errno)));
+ free_canon_ace_list(file_ace_list);
+ free_canon_ace_list(dir_ace_list);
+ return map_nt_error_from_unix(errno);
+ }
+ } else {
+ int sret = -1;
+
+ /*
+ * No default ACL - delete one if it exists.
+ */
+
+ if (set_acl_as_root) {
+ become_root();
+ }
+ sret = SMB_VFS_SYS_ACL_DELETE_DEF_FD(fsp);
+ if (set_acl_as_root) {
+ unbecome_root();
+ }
+ if (sret == -1) {
+ if (acl_group_override_fsp(fsp)) {
+ DEBUG(5,("set_nt_acl: acl group "
+ "control on and current user "
+ "in file %s primary group. "
+ "Override delete_def_acl\n",
+ fsp_str_dbg(fsp)));
+
+ become_root();
+ sret =
+ SMB_VFS_SYS_ACL_DELETE_DEF_FD(fsp);
+ unbecome_root();
+ }
+
+ if (sret == -1) {
+ DBG_NOTICE("sys_acl_delete_def_fd for "
+ "directory %s failed (%s)\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ free_canon_ace_list(file_ace_list);
+ free_canon_ace_list(dir_ace_list);
+ return map_nt_error_from_unix(errno);
+ }
+ }
+ }
+ }
+
+ if (acl_set_support) {
+ if (set_acl_as_root) {
+ become_root();
+ }
+ store_inheritance_attributes(fsp,
+ file_ace_list,
+ dir_ace_list,
+ psd->type);
+ if (set_acl_as_root) {
+ unbecome_root();
+ }
+ }
+
+ /*
+ * If we cannot set using POSIX ACLs we fall back to checking if we need to chmod.
+ */
+
+ if(!acl_set_support && acl_perms) {
+ mode_t posix_perms;
+
+ if (!convert_canon_ace_to_posix_perms( fsp, file_ace_list, &posix_perms)) {
+ free_canon_ace_list(file_ace_list);
+ free_canon_ace_list(dir_ace_list);
+ DEBUG(3,("set_nt_acl: failed to convert file acl to "
+ "posix permissions for file %s.\n",
+ fsp_str_dbg(fsp)));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (orig_mode != posix_perms) {
+ int sret = -1;
+
+ DEBUG(3,("set_nt_acl: chmod %s. perms = 0%o.\n",
+ fsp_str_dbg(fsp), (unsigned int)posix_perms));
+
+ if (set_acl_as_root) {
+ become_root();
+ }
+ sret = SMB_VFS_FCHMOD(fsp, posix_perms);
+ if (set_acl_as_root) {
+ unbecome_root();
+ }
+ if(sret == -1) {
+ if (acl_group_override_fsp(fsp)) {
+ DEBUG(5,("set_nt_acl: acl group "
+ "control on and current user "
+ "in file %s primary group. "
+ "Override chmod\n",
+ fsp_str_dbg(fsp)));
+
+ become_root();
+ sret = SMB_VFS_FCHMOD(fsp, posix_perms);
+ unbecome_root();
+ }
+
+ if (sret == -1) {
+ DEBUG(3,("set_nt_acl: chmod %s, 0%o "
+ "failed. Error = %s.\n",
+ fsp_str_dbg(fsp),
+ (unsigned int)posix_perms,
+ strerror(errno)));
+ free_canon_ace_list(file_ace_list);
+ free_canon_ace_list(dir_ace_list);
+ return map_nt_error_from_unix(errno);
+ }
+ }
+ }
+ }
+
+ free_canon_ace_list(file_ace_list);
+ free_canon_ace_list(dir_ace_list);
+
+ /* Ensure the stat struct in the fsp is correct. */
+ status = vfs_stat_fsp(fsp);
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Get the actual group bits stored on a file with an ACL. Has no effect if
+ the file has no ACL. Needed in dosmode code where the stat() will return
+ the mask bits, not the real group bits, for a file with an ACL.
+****************************************************************************/
+
+int get_acl_group_bits(connection_struct *conn,
+ struct files_struct *fsp,
+ mode_t *mode )
+{
+ int entry_id = SMB_ACL_FIRST_ENTRY;
+ SMB_ACL_ENTRY_T entry;
+ SMB_ACL_T posix_acl;
+ int result = -1;
+
+ posix_acl = SMB_VFS_SYS_ACL_GET_FD(metadata_fsp(fsp),
+ SMB_ACL_TYPE_ACCESS,
+ talloc_tos());
+ if (posix_acl == (SMB_ACL_T)NULL)
+ return -1;
+
+ while (sys_acl_get_entry(posix_acl, entry_id, &entry) == 1) {
+ SMB_ACL_TAG_T tagtype;
+ SMB_ACL_PERMSET_T permset;
+
+ entry_id = SMB_ACL_NEXT_ENTRY;
+
+ if (sys_acl_get_tag_type(entry, &tagtype) ==-1)
+ break;
+
+ if (tagtype == SMB_ACL_GROUP_OBJ) {
+ if (sys_acl_get_permset(entry, &permset) == -1) {
+ break;
+ } else {
+ *mode &= ~(S_IRGRP|S_IWGRP|S_IXGRP);
+ *mode |= (sys_acl_get_perm(permset, SMB_ACL_READ) ? S_IRGRP : 0);
+ *mode |= (sys_acl_get_perm(permset, SMB_ACL_WRITE) ? S_IWGRP : 0);
+ *mode |= (sys_acl_get_perm(permset, SMB_ACL_EXECUTE) ? S_IXGRP : 0);
+ result = 0;
+ break;
+ }
+ }
+ }
+ TALLOC_FREE(posix_acl);
+ return result;
+}
+
+/****************************************************************************
+ Do a chmod by setting the ACL USER_OBJ, GROUP_OBJ and OTHER bits in an ACL
+ and set the mask to rwx. Needed to preserve complex ACLs set by NT.
+****************************************************************************/
+
+static int chmod_acl_internals(SMB_ACL_T posix_acl, mode_t mode)
+{
+ int entry_id = SMB_ACL_FIRST_ENTRY;
+ SMB_ACL_ENTRY_T entry;
+ int num_entries = 0;
+
+ while ( sys_acl_get_entry(posix_acl, entry_id, &entry) == 1) {
+ SMB_ACL_TAG_T tagtype;
+ SMB_ACL_PERMSET_T permset;
+ mode_t perms;
+
+ entry_id = SMB_ACL_NEXT_ENTRY;
+
+ if (sys_acl_get_tag_type(entry, &tagtype) == -1)
+ return -1;
+
+ if (sys_acl_get_permset(entry, &permset) == -1)
+ return -1;
+
+ num_entries++;
+
+ switch(tagtype) {
+ case SMB_ACL_USER_OBJ:
+ perms = unix_perms_to_acl_perms(mode, S_IRUSR, S_IWUSR, S_IXUSR);
+ break;
+ case SMB_ACL_GROUP_OBJ:
+ perms = unix_perms_to_acl_perms(mode, S_IRGRP, S_IWGRP, S_IXGRP);
+ break;
+ case SMB_ACL_MASK:
+ /*
+ * FIXME: The ACL_MASK entry permissions should really be set to
+ * the union of the permissions of all ACL_USER,
+ * ACL_GROUP_OBJ, and ACL_GROUP entries. That's what
+ * acl_calc_mask() does, but Samba ACLs doesn't provide it.
+ */
+ perms = S_IRUSR|S_IWUSR|S_IXUSR;
+ break;
+ case SMB_ACL_OTHER:
+ perms = unix_perms_to_acl_perms(mode, S_IROTH, S_IWOTH, S_IXOTH);
+ break;
+ default:
+ continue;
+ }
+
+ if (map_acl_perms_to_permset(perms, &permset) == -1)
+ return -1;
+
+ if (sys_acl_set_permset(entry, permset) == -1)
+ return -1;
+ }
+
+ /*
+ * If this is a simple 3 element ACL or no elements then it's a standard
+ * UNIX permission set. Just use chmod...
+ */
+
+ if ((num_entries == 3) || (num_entries == 0))
+ return -1;
+
+ return 0;
+}
+
+/****************************************************************************
+ Get the access ACL of FROM, do a chmod by setting the ACL USER_OBJ,
+ GROUP_OBJ and OTHER bits in an ACL and set the mask to rwx. Set the
+ resulting ACL on TO. Note that name is in UNIX character set.
+****************************************************************************/
+
+static int copy_access_posix_acl(struct files_struct *from,
+ struct files_struct *to,
+ mode_t mode)
+{
+ SMB_ACL_T posix_acl = NULL;
+ int ret = -1;
+
+ posix_acl = SMB_VFS_SYS_ACL_GET_FD(
+ from, SMB_ACL_TYPE_ACCESS, talloc_tos());
+ if (posix_acl == NULL) {
+ return -1;
+ }
+
+ ret = chmod_acl_internals(posix_acl, mode);
+ if (ret == -1) {
+ goto done;
+ }
+
+ ret = SMB_VFS_SYS_ACL_SET_FD(to, SMB_ACL_TYPE_ACCESS, posix_acl);
+
+ done:
+
+ TALLOC_FREE(posix_acl);
+ return ret;
+}
+
+/****************************************************************************
+ Check for an existing default POSIX ACL on a directory.
+****************************************************************************/
+
+static bool directory_has_default_posix_acl(struct files_struct *dirfsp)
+{
+ SMB_ACL_T def_acl = SMB_VFS_SYS_ACL_GET_FD(
+ dirfsp, SMB_ACL_TYPE_DEFAULT, talloc_tos());
+ bool has_acl = False;
+ SMB_ACL_ENTRY_T entry;
+
+ if (def_acl != NULL && (sys_acl_get_entry(def_acl, SMB_ACL_FIRST_ENTRY, &entry) == 1)) {
+ has_acl = True;
+ }
+
+ if (def_acl) {
+ TALLOC_FREE(def_acl);
+ }
+ return has_acl;
+}
+
+/****************************************************************************
+ If the parent directory has no default ACL but it does have an Access ACL,
+ inherit this Access ACL to file name.
+****************************************************************************/
+
+int inherit_access_posix_acl(connection_struct *conn,
+ struct files_struct *inherit_from_dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode)
+{
+ int ret;
+
+ if (directory_has_default_posix_acl(inherit_from_dirfsp))
+ return 0;
+
+ ret = copy_access_posix_acl(
+ inherit_from_dirfsp, smb_fname->fsp, mode);
+ return ret;
+}
+
+/****************************************************************************
+ Map from wire type to permset.
+****************************************************************************/
+
+static bool unix_ex_wire_to_permset(connection_struct *conn, unsigned char wire_perm, SMB_ACL_PERMSET_T *p_permset)
+{
+ if (wire_perm & ~(SMB_POSIX_ACL_READ|SMB_POSIX_ACL_WRITE|SMB_POSIX_ACL_EXECUTE)) {
+ return False;
+ }
+
+ if (sys_acl_clear_perms(*p_permset) == -1) {
+ return False;
+ }
+
+ if (wire_perm & SMB_POSIX_ACL_READ) {
+ if (sys_acl_add_perm(*p_permset, SMB_ACL_READ) == -1) {
+ return False;
+ }
+ }
+ if (wire_perm & SMB_POSIX_ACL_WRITE) {
+ if (sys_acl_add_perm(*p_permset, SMB_ACL_WRITE) == -1) {
+ return False;
+ }
+ }
+ if (wire_perm & SMB_POSIX_ACL_EXECUTE) {
+ if (sys_acl_add_perm(*p_permset, SMB_ACL_EXECUTE) == -1) {
+ return False;
+ }
+ }
+ return True;
+}
+
+/****************************************************************************
+ Map from wire type to tagtype.
+****************************************************************************/
+
+static bool unix_ex_wire_to_tagtype(unsigned char wire_tt, SMB_ACL_TAG_T *p_tt)
+{
+ switch (wire_tt) {
+ case SMB_POSIX_ACL_USER_OBJ:
+ *p_tt = SMB_ACL_USER_OBJ;
+ break;
+ case SMB_POSIX_ACL_USER:
+ *p_tt = SMB_ACL_USER;
+ break;
+ case SMB_POSIX_ACL_GROUP_OBJ:
+ *p_tt = SMB_ACL_GROUP_OBJ;
+ break;
+ case SMB_POSIX_ACL_GROUP:
+ *p_tt = SMB_ACL_GROUP;
+ break;
+ case SMB_POSIX_ACL_MASK:
+ *p_tt = SMB_ACL_MASK;
+ break;
+ case SMB_POSIX_ACL_OTHER:
+ *p_tt = SMB_ACL_OTHER;
+ break;
+ default:
+ return False;
+ }
+ return True;
+}
+
+/****************************************************************************
+ Create a new POSIX acl from wire permissions.
+ FIXME ! How does the share mask/mode fit into this.... ?
+****************************************************************************/
+
+static SMB_ACL_T create_posix_acl_from_wire(connection_struct *conn,
+ uint16_t num_acls,
+ const char *pdata,
+ TALLOC_CTX *mem_ctx)
+{
+ unsigned int i;
+ SMB_ACL_T the_acl = sys_acl_init(mem_ctx);
+
+ if (the_acl == NULL) {
+ return NULL;
+ }
+
+ for (i = 0; i < num_acls; i++) {
+ SMB_ACL_ENTRY_T the_entry;
+ SMB_ACL_PERMSET_T the_permset;
+ SMB_ACL_TAG_T tag_type;
+
+ if (sys_acl_create_entry(&the_acl, &the_entry) == -1) {
+ DEBUG(0,("create_posix_acl_from_wire: Failed to create entry %u. (%s)\n",
+ i, strerror(errno) ));
+ goto fail;
+ }
+
+ if (!unix_ex_wire_to_tagtype(CVAL(pdata,(i*SMB_POSIX_ACL_ENTRY_SIZE)), &tag_type)) {
+ DEBUG(0,("create_posix_acl_from_wire: invalid wire tagtype %u on entry %u.\n",
+ CVAL(pdata,(i*SMB_POSIX_ACL_ENTRY_SIZE)), i ));
+ goto fail;
+ }
+
+ if (sys_acl_set_tag_type(the_entry, tag_type) == -1) {
+ DEBUG(0,("create_posix_acl_from_wire: Failed to set tagtype on entry %u. (%s)\n",
+ i, strerror(errno) ));
+ goto fail;
+ }
+
+ /* Get the permset pointer from the new ACL entry. */
+ if (sys_acl_get_permset(the_entry, &the_permset) == -1) {
+ DEBUG(0,("create_posix_acl_from_wire: Failed to get permset on entry %u. (%s)\n",
+ i, strerror(errno) ));
+ goto fail;
+ }
+
+ /* Map from wire to permissions. */
+ if (!unix_ex_wire_to_permset(conn, CVAL(pdata,(i*SMB_POSIX_ACL_ENTRY_SIZE)+1), &the_permset)) {
+ DEBUG(0,("create_posix_acl_from_wire: invalid permset %u on entry %u.\n",
+ CVAL(pdata,(i*SMB_POSIX_ACL_ENTRY_SIZE) + 1), i ));
+ goto fail;
+ }
+
+ /* Now apply to the new ACL entry. */
+ if (sys_acl_set_permset(the_entry, the_permset) == -1) {
+ DEBUG(0,("create_posix_acl_from_wire: Failed to add permset on entry %u. (%s)\n",
+ i, strerror(errno) ));
+ goto fail;
+ }
+
+ if (tag_type == SMB_ACL_USER) {
+ uint32_t uidval = IVAL(pdata,(i*SMB_POSIX_ACL_ENTRY_SIZE)+2);
+ uid_t uid = (uid_t)uidval;
+ if (sys_acl_set_qualifier(the_entry,(void *)&uid) == -1) {
+ DEBUG(0,("create_posix_acl_from_wire: Failed to set uid %u on entry %u. (%s)\n",
+ (unsigned int)uid, i, strerror(errno) ));
+ goto fail;
+ }
+ }
+
+ if (tag_type == SMB_ACL_GROUP) {
+ uint32_t gidval = IVAL(pdata,(i*SMB_POSIX_ACL_ENTRY_SIZE)+2);
+ gid_t gid = (uid_t)gidval;
+ if (sys_acl_set_qualifier(the_entry,(void *)&gid) == -1) {
+ DEBUG(0,("create_posix_acl_from_wire: Failed to set gid %u on entry %u. (%s)\n",
+ (unsigned int)gid, i, strerror(errno) ));
+ goto fail;
+ }
+ }
+ }
+
+ return the_acl;
+
+ fail:
+
+ if (the_acl != NULL) {
+ TALLOC_FREE(the_acl);
+ }
+ return NULL;
+}
+
+/****************************************************************************
+ Calls from UNIX extensions - Default POSIX ACL set.
+ If num_def_acls == 0 and not a directory just return. If it is a directory
+ and num_def_acls == 0 then remove the default acl. Else set the default acl
+ on the directory.
+****************************************************************************/
+
+NTSTATUS set_unix_posix_default_acl(connection_struct *conn,
+ files_struct *fsp,
+ uint16_t num_def_acls,
+ const char *pdata)
+{
+ SMB_ACL_T def_acl = NULL;
+ NTSTATUS status;
+ int ret;
+
+ if (!fsp->fsp_flags.is_directory) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (!num_def_acls) {
+ /* Remove the default ACL. */
+ ret = SMB_VFS_SYS_ACL_DELETE_DEF_FD(fsp);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("acl_delete_def_fd failed on "
+ "directory %s (%s)\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ return status;
+ }
+ return NT_STATUS_OK;
+ }
+
+ def_acl = create_posix_acl_from_wire(conn,
+ num_def_acls,
+ pdata,
+ talloc_tos());
+ if (def_acl == NULL) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ ret = SMB_VFS_SYS_ACL_SET_FD(fsp,
+ SMB_ACL_TYPE_DEFAULT,
+ def_acl);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("acl_set_file failed on directory %s (%s)\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ TALLOC_FREE(def_acl);
+ return status;
+ }
+
+ DBG_DEBUG("set default acl for file %s\n",
+ fsp_str_dbg(fsp));
+ TALLOC_FREE(def_acl);
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Remove an ACL from a file. As we don't have acl_delete_entry() available
+ we must read the current acl and copy all entries except MASK, USER and GROUP
+ to a new acl, then set that. This (at least on Linux) causes any ACL to be
+ removed.
+ FIXME ! How does the share mask/mode fit into this.... ?
+****************************************************************************/
+
+static NTSTATUS remove_posix_acl(connection_struct *conn,
+ files_struct *fsp)
+{
+ SMB_ACL_T file_acl = NULL;
+ int entry_id = SMB_ACL_FIRST_ENTRY;
+ SMB_ACL_ENTRY_T entry;
+ /* Create a new ACL with only 3 entries, u/g/w. */
+ SMB_ACL_T new_file_acl = NULL;
+ SMB_ACL_ENTRY_T user_ent = NULL;
+ SMB_ACL_ENTRY_T group_ent = NULL;
+ SMB_ACL_ENTRY_T other_ent = NULL;
+ NTSTATUS status;
+ int ret;
+
+ new_file_acl = sys_acl_init(talloc_tos());
+ if (new_file_acl == NULL) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("failed to init new ACL with 3 entries "
+ "for file %s %s.\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ goto done;
+ }
+
+ /* Now create the u/g/w entries. */
+ ret = sys_acl_create_entry(&new_file_acl, &user_ent);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("Failed to create user entry for file %s. (%s)\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ goto done;
+ }
+ ret = sys_acl_set_tag_type(user_ent, SMB_ACL_USER_OBJ);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("Failed to set user entry for file %s. (%s)\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ goto done;
+ }
+
+ ret = sys_acl_create_entry(&new_file_acl, &group_ent);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("Failed to create group entry for file %s. (%s)\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ goto done;
+ }
+ ret = sys_acl_set_tag_type(group_ent, SMB_ACL_GROUP_OBJ);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("Failed to set group entry for file %s. (%s)\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ goto done;
+ }
+
+ ret = sys_acl_create_entry(&new_file_acl, &other_ent);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("Failed to create other entry for file %s. (%s)\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ goto done;
+ }
+ ret = sys_acl_set_tag_type(other_ent, SMB_ACL_OTHER);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("Failed to set other entry for file %s. (%s)\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ goto done;
+ }
+
+ /* Get the current file ACL. */
+ file_acl = SMB_VFS_SYS_ACL_GET_FD(fsp,
+ SMB_ACL_TYPE_ACCESS,
+ talloc_tos());
+
+ if (file_acl == NULL) {
+ status = map_nt_error_from_unix(errno);
+ /* This is only returned if an error occurred. Even for a file with
+ no acl a u/g/w acl should be returned. */
+ DBG_INFO("failed to get ACL from file %s (%s).\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ goto done;
+ }
+
+ while ( sys_acl_get_entry(file_acl, entry_id, &entry) == 1) {
+ SMB_ACL_TAG_T tagtype;
+ SMB_ACL_PERMSET_T permset;
+
+ entry_id = SMB_ACL_NEXT_ENTRY;
+
+ ret = sys_acl_get_tag_type(entry, &tagtype);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("failed to get tagtype from ACL "
+ "on file %s (%s).\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ goto done;
+ }
+
+ ret = sys_acl_get_permset(entry, &permset);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("failed to get permset from ACL "
+ "on file %s (%s).\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ goto done;
+ }
+
+ if (tagtype == SMB_ACL_USER_OBJ) {
+ ret = sys_acl_set_permset(user_ent, permset);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("failed to set permset from ACL "
+ "on file %s (%s).\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ goto done;
+ }
+ } else if (tagtype == SMB_ACL_GROUP_OBJ) {
+ ret = sys_acl_set_permset(group_ent, permset);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("failed to set permset from ACL "
+ "on file %s (%s).\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ goto done;
+ }
+ } else if (tagtype == SMB_ACL_OTHER) {
+ ret = sys_acl_set_permset(other_ent, permset);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("failed to set permset from ACL "
+ "on file %s (%s).\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ goto done;
+ }
+ }
+ }
+
+ /* Set the new empty file ACL. */
+ ret = SMB_VFS_SYS_ACL_SET_FD(fsp, SMB_ACL_TYPE_ACCESS, new_file_acl);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("acl_set_file failed on %s (%s)\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ goto done;
+ }
+
+ status = NT_STATUS_OK;
+
+ done:
+
+ TALLOC_FREE(file_acl);
+ TALLOC_FREE(new_file_acl);
+ return status;
+}
+
+/****************************************************************************
+ Calls from UNIX extensions - POSIX ACL set.
+ If num_def_acls == 0 then read/modify/write acl after removing all entries
+ except SMB_ACL_USER_OBJ, SMB_ACL_GROUP_OBJ, SMB_ACL_OTHER.
+****************************************************************************/
+
+NTSTATUS set_unix_posix_acl(connection_struct *conn,
+ files_struct *fsp,
+ uint16_t num_acls,
+ const char *pdata)
+{
+ SMB_ACL_T file_acl = NULL;
+ int ret;
+ NTSTATUS status;
+
+ if (!num_acls) {
+ /* Remove the ACL from the file. */
+ return remove_posix_acl(conn, fsp);
+ }
+
+ file_acl = create_posix_acl_from_wire(conn,
+ num_acls,
+ pdata,
+ talloc_tos());
+ if (file_acl == NULL) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ ret = SMB_VFS_SYS_ACL_SET_FD(fsp, SMB_ACL_TYPE_ACCESS, file_acl);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("acl_set_file failed on %s (%s)\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ TALLOC_FREE(file_acl);
+ return status;
+ }
+
+ DBG_DEBUG("set acl for file %s\n",
+ fsp_str_dbg(fsp));
+
+ TALLOC_FREE(file_acl);
+ return NT_STATUS_OK;
+}
+
+int posix_sys_acl_blob_get_file(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname_in,
+ TALLOC_CTX *mem_ctx,
+ char **blob_description,
+ DATA_BLOB *blob)
+{
+ int ret;
+ TALLOC_CTX *frame = talloc_stackframe();
+ /* Initialise this to zero, in a portable way */
+ struct smb_acl_wrapper acl_wrapper = {
+ 0
+ };
+ struct smb_filename *smb_fname = cp_smb_filename_nostream(frame,
+ smb_fname_in);
+ if (smb_fname == NULL) {
+ TALLOC_FREE(frame);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ ret = smb_vfs_call_stat(handle, smb_fname);
+ if (ret == -1) {
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ acl_wrapper.owner = smb_fname->st.st_ex_uid;
+ acl_wrapper.group = smb_fname->st.st_ex_gid;
+ acl_wrapper.mode = smb_fname->st.st_ex_mode;
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_push_struct_blob(blob, mem_ctx,
+ &acl_wrapper,
+ (ndr_push_flags_fn_t)ndr_push_smb_acl_wrapper))) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ *blob_description = talloc_strdup(mem_ctx, "posix_acl");
+ if (!*blob_description) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ TALLOC_FREE(frame);
+ return 0;
+}
+
+int posix_sys_acl_blob_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ char **blob_description,
+ DATA_BLOB *blob)
+{
+ SMB_STRUCT_STAT sbuf;
+ TALLOC_CTX *frame;
+ struct smb_acl_wrapper acl_wrapper = { 0 };
+ int ret;
+
+ frame = talloc_stackframe();
+
+ acl_wrapper.access_acl = smb_vfs_call_sys_acl_get_fd(handle,
+ fsp,
+ SMB_ACL_TYPE_ACCESS,
+ frame);
+
+ if (fsp->fsp_flags.is_directory) {
+ acl_wrapper.default_acl = smb_vfs_call_sys_acl_get_fd(handle,
+ fsp,
+ SMB_ACL_TYPE_DEFAULT,
+ frame);
+ }
+
+ ret = smb_vfs_call_fstat(handle, fsp, &sbuf);
+ if (ret == -1) {
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ acl_wrapper.owner = sbuf.st_ex_uid;
+ acl_wrapper.group = sbuf.st_ex_gid;
+ acl_wrapper.mode = sbuf.st_ex_mode;
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_push_struct_blob(blob, mem_ctx,
+ &acl_wrapper,
+ (ndr_push_flags_fn_t)ndr_push_smb_acl_wrapper))) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ *blob_description = talloc_strdup(mem_ctx, "posix_acl");
+ if (!*blob_description) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ TALLOC_FREE(frame);
+ return 0;
+}
+
+static NTSTATUS make_default_acl_posix(TALLOC_CTX *ctx,
+ const char *name,
+ const SMB_STRUCT_STAT *psbuf,
+ struct security_descriptor **ppdesc)
+{
+ struct dom_sid owner_sid, group_sid;
+ size_t size = 0;
+ struct security_ace aces[4];
+ uint32_t access_mask = 0;
+ mode_t mode = psbuf->st_ex_mode;
+ struct security_acl *new_dacl = NULL;
+ int idx = 0;
+
+ DBG_DEBUG("file %s mode = 0%o\n",name, (int)mode);
+
+ uid_to_sid(&owner_sid, psbuf->st_ex_uid);
+ gid_to_sid(&group_sid, psbuf->st_ex_gid);
+
+ /*
+ We provide up to 4 ACEs
+ - Owner
+ - Group
+ - Everyone
+ - NT System
+ */
+
+ if (mode & S_IRUSR) {
+ if (mode & S_IWUSR) {
+ access_mask |= SEC_RIGHTS_FILE_ALL;
+ } else {
+ access_mask |= SEC_RIGHTS_FILE_READ | SEC_FILE_EXECUTE;
+ }
+ }
+ if (mode & S_IWUSR) {
+ access_mask |= SEC_RIGHTS_FILE_WRITE | SEC_STD_DELETE;
+ }
+
+ init_sec_ace(&aces[idx],
+ &owner_sid,
+ SEC_ACE_TYPE_ACCESS_ALLOWED,
+ access_mask,
+ 0);
+ idx++;
+
+ access_mask = 0;
+ if (mode & S_IRGRP) {
+ access_mask |= SEC_RIGHTS_FILE_READ | SEC_FILE_EXECUTE;
+ }
+ if (mode & S_IWGRP) {
+ /* note that delete is not granted - this matches posix behaviour */
+ access_mask |= SEC_RIGHTS_FILE_WRITE;
+ }
+ if (access_mask) {
+ init_sec_ace(&aces[idx],
+ &group_sid,
+ SEC_ACE_TYPE_ACCESS_ALLOWED,
+ access_mask,
+ 0);
+ idx++;
+ }
+
+ access_mask = 0;
+ if (mode & S_IROTH) {
+ access_mask |= SEC_RIGHTS_FILE_READ | SEC_FILE_EXECUTE;
+ }
+ if (mode & S_IWOTH) {
+ access_mask |= SEC_RIGHTS_FILE_WRITE;
+ }
+ if (access_mask) {
+ init_sec_ace(&aces[idx],
+ &global_sid_World,
+ SEC_ACE_TYPE_ACCESS_ALLOWED,
+ access_mask,
+ 0);
+ idx++;
+ }
+
+ init_sec_ace(&aces[idx],
+ &global_sid_System,
+ SEC_ACE_TYPE_ACCESS_ALLOWED,
+ SEC_RIGHTS_FILE_ALL,
+ 0);
+ idx++;
+
+ new_dacl = make_sec_acl(ctx,
+ NT4_ACL_REVISION,
+ idx,
+ aces);
+
+ if (!new_dacl) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *ppdesc = make_sec_desc(ctx,
+ SECURITY_DESCRIPTOR_REVISION_1,
+ SEC_DESC_SELF_RELATIVE|SEC_DESC_DACL_PRESENT,
+ &owner_sid,
+ &group_sid,
+ NULL,
+ new_dacl,
+ &size);
+ if (!*ppdesc) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS make_default_acl_windows(TALLOC_CTX *ctx,
+ const char *name,
+ const SMB_STRUCT_STAT *psbuf,
+ struct security_descriptor **ppdesc)
+{
+ struct dom_sid owner_sid, group_sid;
+ size_t size = 0;
+ struct security_ace aces[4];
+ uint32_t access_mask = 0;
+ mode_t mode = psbuf->st_ex_mode;
+ struct security_acl *new_dacl = NULL;
+ int idx = 0;
+
+ DBG_DEBUG("file [%s] mode [0%o]\n", name, (int)mode);
+
+ uid_to_sid(&owner_sid, psbuf->st_ex_uid);
+ gid_to_sid(&group_sid, psbuf->st_ex_gid);
+
+ /*
+ * We provide 2 ACEs:
+ * - Owner
+ * - NT System
+ */
+
+ if (mode & S_IRUSR) {
+ if (mode & S_IWUSR) {
+ access_mask |= SEC_RIGHTS_FILE_ALL;
+ } else {
+ access_mask |= SEC_RIGHTS_FILE_READ | SEC_FILE_EXECUTE;
+ }
+ }
+ if (mode & S_IWUSR) {
+ access_mask |= SEC_RIGHTS_FILE_WRITE | SEC_STD_DELETE;
+ }
+
+ init_sec_ace(&aces[idx],
+ &owner_sid,
+ SEC_ACE_TYPE_ACCESS_ALLOWED,
+ access_mask,
+ 0);
+ idx++;
+
+ init_sec_ace(&aces[idx],
+ &global_sid_System,
+ SEC_ACE_TYPE_ACCESS_ALLOWED,
+ SEC_RIGHTS_FILE_ALL,
+ 0);
+ idx++;
+
+ new_dacl = make_sec_acl(ctx,
+ NT4_ACL_REVISION,
+ idx,
+ aces);
+
+ if (!new_dacl) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *ppdesc = make_sec_desc(ctx,
+ SECURITY_DESCRIPTOR_REVISION_1,
+ SEC_DESC_SELF_RELATIVE|SEC_DESC_DACL_PRESENT,
+ &owner_sid,
+ &group_sid,
+ NULL,
+ new_dacl,
+ &size);
+ if (!*ppdesc) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS make_default_acl_everyone(TALLOC_CTX *ctx,
+ const char *name,
+ const SMB_STRUCT_STAT *psbuf,
+ struct security_descriptor **ppdesc)
+{
+ struct dom_sid owner_sid, group_sid;
+ size_t size = 0;
+ struct security_ace aces[1];
+ mode_t mode = psbuf->st_ex_mode;
+ struct security_acl *new_dacl = NULL;
+ int idx = 0;
+
+ DBG_DEBUG("file [%s] mode [0%o]\n", name, (int)mode);
+
+ uid_to_sid(&owner_sid, psbuf->st_ex_uid);
+ gid_to_sid(&group_sid, psbuf->st_ex_gid);
+
+ /*
+ * We provide one ACEs: full access for everyone
+ */
+
+ init_sec_ace(&aces[idx],
+ &global_sid_World,
+ SEC_ACE_TYPE_ACCESS_ALLOWED,
+ SEC_RIGHTS_FILE_ALL,
+ 0);
+ idx++;
+
+ new_dacl = make_sec_acl(ctx,
+ NT4_ACL_REVISION,
+ idx,
+ aces);
+
+ if (!new_dacl) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *ppdesc = make_sec_desc(ctx,
+ SECURITY_DESCRIPTOR_REVISION_1,
+ SEC_DESC_SELF_RELATIVE|SEC_DESC_DACL_PRESENT,
+ &owner_sid,
+ &group_sid,
+ NULL,
+ new_dacl,
+ &size);
+ if (!*ppdesc) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ return NT_STATUS_OK;
+}
+
+static const struct enum_list default_acl_style_list[] = {
+ {DEFAULT_ACL_POSIX, "posix"},
+ {DEFAULT_ACL_WINDOWS, "windows"},
+ {DEFAULT_ACL_EVERYONE, "everyone"},
+};
+
+const struct enum_list *get_default_acl_style_list(void)
+{
+ return default_acl_style_list;
+}
+
+NTSTATUS make_default_filesystem_acl(
+ TALLOC_CTX *ctx,
+ enum default_acl_style acl_style,
+ const char *name,
+ const SMB_STRUCT_STAT *psbuf,
+ struct security_descriptor **ppdesc)
+{
+ NTSTATUS status;
+
+ switch (acl_style) {
+ case DEFAULT_ACL_POSIX:
+ status = make_default_acl_posix(ctx, name, psbuf, ppdesc);
+ break;
+
+ case DEFAULT_ACL_WINDOWS:
+ status = make_default_acl_windows(ctx, name, psbuf, ppdesc);
+ break;
+
+ case DEFAULT_ACL_EVERYONE:
+ status = make_default_acl_everyone(ctx, name, psbuf, ppdesc);
+ break;
+
+ default:
+ DBG_ERR("unknown acl style %d\n", acl_style);
+ status = NT_STATUS_INTERNAL_ERROR;
+ break;
+ }
+
+ return status;
+}
diff --git a/source3/smbd/proto.h b/source3/smbd/proto.h
new file mode 100644
index 0000000..d18afe3
--- /dev/null
+++ b/source3/smbd/proto.h
@@ -0,0 +1,1224 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Main SMB server routines
+ *
+ * Copyright (C) Andrew Tridgell 1992-2002,2006
+ * Copyright (C) Jeremy Allison 1992-2010
+ * Copyright (C) Volker Lendecke 1993-2009
+ * Copyright (C) John H Terpstra 1995-1998
+ * Copyright (C) Luke Kenneth Casson Leighton 1996-1998
+ * Copyright (C) Paul Ashton 1997-1998
+ * Copyright (C) Tim Potter 1999-2000
+ * Copyright (C) T.D.Lee@durham.ac.uk 1999
+ * Copyright (C) Ying Chen 2000
+ * Copyright (C) Shirish Kalele 2000
+ * Copyright (C) Andrew Bartlett 2001-2003
+ * Copyright (C) Alexander Bokovoy 2002,2005
+ * Copyright (C) Simo Sorce 2001-2002,2009
+ * Copyright (C) Andreas Gruenbacher 2002
+ * Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002
+ * Copyright (C) Martin Pool 2002
+ * Copyright (C) Luke Howard 2003
+ * Copyright (C) Stefan (metze) Metzmacher 2003,2009
+ * Copyright (C) Steve French 2005
+ * Copyright (C) Gerald (Jerry) Carter 2006
+ * Copyright (C) James Peach 2006-2007
+ * Copyright (C) Jelmer Vernooij 2002-2003
+ * Copyright (C) Michael Adam 2007
+ * Copyright (C) Rishi Srivatsavai 2007
+ * Copyright (C) Tim Prouty 2009
+ * Copyright (C) Gregor Beck 2011
+ *
+ * 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/>.
+ */
+
+#ifndef _SMBD_PROTO_H_
+#define _SMBD_PROTO_H_
+
+struct smbXsrv_client;
+struct smbXsrv_connection;
+struct dcesrv_context;
+
+/* The following definitions come from smbd/smb2_signing.c */
+
+bool srv_init_signing(struct smbXsrv_connection *conn);
+
+/* The following definitions come from smbd/aio.c */
+
+struct aio_extra;
+bool aio_write_through_requested(struct aio_extra *aio_ex);
+NTSTATUS schedule_smb2_aio_read(connection_struct *conn,
+ struct smb_request *smbreq,
+ files_struct *fsp,
+ TALLOC_CTX *ctx,
+ DATA_BLOB *preadbuf,
+ off_t startpos,
+ size_t smb_maxcnt);
+NTSTATUS schedule_aio_smb2_write(connection_struct *conn,
+ struct smb_request *smbreq,
+ files_struct *fsp,
+ uint64_t in_offset,
+ DATA_BLOB in_data,
+ bool write_through);
+bool cancel_smb2_aio(struct smb_request *smbreq);
+bool aio_add_req_to_fsp(files_struct *fsp, struct tevent_req *req);
+struct aio_extra *create_aio_extra(TALLOC_CTX *mem_ctx,
+ files_struct *fsp,
+ size_t buflen);
+struct tevent_req *pwrite_fsync_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ const void *data,
+ size_t n, off_t offset,
+ bool write_through);
+ssize_t pwrite_fsync_recv(struct tevent_req *req, int *perr);
+
+/* The following definitions come from smbd/blocking.c */
+
+NTSTATUS smbd_do_locks_try(
+ struct files_struct *fsp,
+ uint16_t num_locks,
+ struct smbd_lock_element *locks,
+ uint16_t *blocker_idx,
+ struct server_id *blocking_pid,
+ uint64_t *blocking_smblctx);
+struct tevent_req *smbd_smb1_do_locks_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smb_request **smbreq, /* talloc_move()d into our state */
+ struct files_struct *fsp,
+ uint32_t lock_timeout,
+ bool large_offset,
+ uint16_t num_locks,
+ struct smbd_lock_element *locks);
+NTSTATUS smbd_smb1_do_locks_recv(struct tevent_req *req);
+bool smbd_smb1_do_locks_extract_smbreq(
+ struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct smb_request **psmbreq);
+void smbd_smb1_brl_finish_by_req(struct tevent_req *req, NTSTATUS status);
+bool smbd_smb1_brl_finish_by_lock(
+ struct files_struct *fsp,
+ bool large_offset,
+ struct smbd_lock_element lock,
+ NTSTATUS finish_status);
+bool smbd_smb1_brl_finish_by_mid(
+ struct smbd_server_connection *sconn, uint64_t mid);
+
+/* The following definitions come from smbd/close.c */
+
+void set_close_write_time(struct files_struct *fsp, struct timespec ts);
+NTSTATUS close_file_smb(struct smb_request *req,
+ struct files_struct *fsp,
+ enum file_close_type close_type);
+NTSTATUS close_file_free(struct smb_request *req,
+ struct files_struct **_fsp,
+ enum file_close_type close_type);
+void msg_close_file(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+NTSTATUS delete_all_streams(connection_struct *conn,
+ const struct smb_filename *smb_fname);
+NTSTATUS recursive_rmdir(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct smb_filename *smb_dname);
+bool has_other_nonposix_opens(struct share_mode_lock *lck,
+ struct files_struct *fsp);
+
+/* The following definitions come from smbd/conn.c */
+
+int conn_num_open(struct smbd_server_connection *sconn);
+bool conn_snum_used(struct smbd_server_connection *sconn, int snum);
+connection_struct *conn_new(struct smbd_server_connection *sconn);
+bool conn_idle_all(struct smbd_server_connection *sconn, time_t t);
+void conn_clear_vuid_caches(struct smbd_server_connection *sconn, uint64_t vuid);
+void conn_free(connection_struct *conn);
+void conn_setup_case_options(connection_struct *conn);
+void conn_force_tdis(
+ struct smbd_server_connection *sconn,
+ bool (*check_fn)(struct connection_struct *conn,
+ void *private_data),
+ void *private_data);
+void msg_force_tdis(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+void msg_force_tdis_denied(
+ struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+
+/* The following definitions come from smbd/connection.c */
+
+int count_current_connections(const char *sharename, bool verify);
+bool connections_snum_used(struct smbd_server_connection *unused, int snum);
+
+/* The following definitions come from smbd/dfree.c */
+
+uint64_t get_dfree_info(connection_struct *conn, struct smb_filename *fname,
+ uint64_t *bsize, uint64_t *dfree, uint64_t *dsize);
+void flush_dfree_cache(void);
+
+/* The following definitions come from smbd/dmapi.c */
+
+const void *dmapi_get_current_session(void);
+bool dmapi_have_session(void);
+bool dmapi_new_session(void);
+bool dmapi_destroy_session(void);
+uint32_t dmapi_file_flags(const char * const path);
+
+/* The following definitions come from smbd/dnsregister.c */
+
+bool smbd_setup_mdns_registration(struct tevent_context *ev,
+ TALLOC_CTX *mem_ctx,
+ uint16_t port);
+
+/* The following definitions come from smbd/dosmode.c */
+
+mode_t unix_mode(connection_struct *conn, int dosmode,
+ const struct smb_filename *smb_fname,
+ struct files_struct *parent_dirfsp);
+uint32_t dos_mode_msdfs(connection_struct *conn,
+ const char *name,
+ const struct stat_ex *st);
+uint32_t fdos_mode(struct files_struct *fsp);
+struct tevent_req *dos_mode_at_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ files_struct *dir_fsp,
+ struct smb_filename *smb_fname);
+NTSTATUS dos_mode_at_recv(struct tevent_req *req, uint32_t *dosmode);
+int file_set_dosmode(connection_struct *conn,
+ struct smb_filename *smb_fname,
+ uint32_t dosmode,
+ struct smb_filename *parent_dir,
+ bool newfile);
+NTSTATUS file_set_sparse(connection_struct *conn,
+ struct files_struct *fsp,
+ bool sparse);
+int file_ntimes(connection_struct *conn,
+ files_struct *fsp,
+ struct smb_file_time *ft);
+bool set_sticky_write_time_path(struct file_id fileid, struct timespec mtime);
+bool set_sticky_write_time_fsp(struct files_struct *fsp,
+ struct timespec mtime);
+
+NTSTATUS fget_ea_dos_attribute(struct files_struct *fsp,
+ uint32_t *pattr);
+NTSTATUS set_ea_dos_attribute(connection_struct *conn,
+ struct smb_filename *smb_fname,
+ uint32_t dosmode);
+
+NTSTATUS set_create_timespec_ea(struct files_struct *fsp,
+ struct timespec create_time);
+
+struct timespec get_create_timespec(connection_struct *conn,
+ struct files_struct *fsp,
+ const struct smb_filename *smb_fname);
+
+struct timespec get_change_timespec(connection_struct *conn,
+ struct files_struct *fsp,
+ const struct smb_filename *smb_fname);
+
+NTSTATUS parse_dos_attribute_blob(struct smb_filename *smb_fname,
+ DATA_BLOB blob,
+ uint32_t *pattr);
+
+/* The following definitions come from smbd/error.c */
+
+bool use_nt_status(void);
+void error_packet_set(char *outbuf, uint8_t eclass, uint32_t ecode, NTSTATUS ntstatus, int line, const char *file);
+size_t error_packet(char *outbuf,
+ uint8_t eclass,
+ uint32_t ecode,
+ NTSTATUS ntstatus,
+ int line,
+ const char *file);
+void reply_nt_error(struct smb_request *req, NTSTATUS ntstatus,
+ int line, const char *file);
+void reply_force_dos_error(struct smb_request *req, uint8_t eclass, uint32_t ecode,
+ int line, const char *file);
+void reply_both_error(struct smb_request *req, uint8_t eclass, uint32_t ecode,
+ NTSTATUS status, int line, const char *file);
+void reply_openerror(struct smb_request *req, NTSTATUS status);
+
+/* The following definitions come from smbd/file_access.c */
+
+bool can_delete_file_in_directory(connection_struct *conn,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname);
+bool can_write_to_fsp(struct files_struct *fsp);
+bool directory_has_default_acl_fsp(struct files_struct *fsp);
+NTSTATUS can_set_delete_on_close(files_struct *fsp, uint32_t dosmode);
+
+/* The following definitions come from smbd/fileio.c */
+
+ssize_t read_file(files_struct *fsp,char *data,off_t pos,size_t n);
+void fsp_flush_write_time_update(struct files_struct *fsp);
+void trigger_write_time_update(struct files_struct *fsp);
+void trigger_write_time_update_immediate(struct files_struct *fsp);
+void mark_file_modified(files_struct *fsp);
+ssize_t write_file(struct smb_request *req,
+ files_struct *fsp,
+ const char *data,
+ off_t pos,
+ size_t n);
+NTSTATUS sync_file(connection_struct *conn, files_struct *fsp, bool write_through);
+
+/* The following definitions come from smbd/filename.c */
+
+uint32_t ucf_flags_from_smb_request(struct smb_request *req);
+uint32_t filename_create_ucf_flags(struct smb_request *req, uint32_t create_disposition);
+NTSTATUS canonicalize_snapshot_path(struct smb_filename *smb_fname,
+ uint32_t ucf_flags,
+ NTTIME twrp);
+NTSTATUS get_real_filename_full_scan_at(struct files_struct *dirfsp,
+ const char *name,
+ bool mangled,
+ TALLOC_CTX *mem_ctx,
+ char **found_name);
+char *get_original_lcomp(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ const char *filename_in,
+ uint32_t ucf_flags);
+NTSTATUS get_real_filename_at(struct files_struct *dirfsp,
+ const char *name,
+ TALLOC_CTX *mem_ctx,
+ char **found_name);
+
+/* The following definitions come from smbd/files.c */
+
+NTSTATUS fsp_new(struct connection_struct *conn, TALLOC_CTX *mem_ctx,
+ files_struct **result);
+void fsp_set_gen_id(files_struct *fsp);
+NTSTATUS file_new(struct smb_request *req, connection_struct *conn,
+ files_struct **result);
+NTSTATUS fsp_bind_smb(struct files_struct *fsp, struct smb_request *req);
+void file_close_conn(connection_struct *conn, enum file_close_type close_type);
+bool file_init_global(void);
+bool file_init(struct smbd_server_connection *sconn);
+void file_close_user(struct smbd_server_connection *sconn, uint64_t vuid);
+struct files_struct *files_forall(
+ struct smbd_server_connection *sconn,
+ struct files_struct *(*fn)(struct files_struct *fsp,
+ void *private_data),
+ void *private_data);
+files_struct *file_find_fd(struct smbd_server_connection *sconn, int fd);
+files_struct *file_find_dif(struct smbd_server_connection *sconn,
+ struct file_id id, unsigned long gen_id);
+files_struct *file_find_di_first(struct smbd_server_connection *sconn,
+ struct file_id id,
+ bool need_fsa);
+files_struct *file_find_di_next(files_struct *start_fsp,
+ bool need_fsa);
+struct files_struct *file_find_one_fsp_from_lease_key(
+ struct smbd_server_connection *sconn,
+ const struct smb2_lease_key *lease_key);
+bool file_find_subpath(files_struct *dir_fsp);
+void fsp_unbind_smb(struct smb_request *req, files_struct *fsp);
+void file_free(struct smb_request *req, files_struct *fsp);
+files_struct *file_fsp(struct smb_request *req, uint16_t fid);
+struct files_struct *file_fsp_get(struct smbd_smb2_request *smb2req,
+ uint64_t persistent_id,
+ uint64_t volatile_id);
+struct files_struct *file_fsp_smb2(struct smbd_smb2_request *smb2req,
+ uint64_t persistent_id,
+ uint64_t volatile_id);
+NTSTATUS dup_file_fsp(
+ files_struct *from,
+ uint32_t access_mask,
+ files_struct *to);
+NTSTATUS file_name_hash(connection_struct *conn,
+ const char *name, uint32_t *p_name_hash);
+NTSTATUS fsp_set_smb_fname(struct files_struct *fsp,
+ const struct smb_filename *smb_fname_in);
+size_t fsp_fullbasepath(struct files_struct *fsp, char *buf, size_t buflen);
+void fsp_set_base_fsp(struct files_struct *fsp, struct files_struct *base_fsp);
+bool fsp_is_alternate_stream(const struct files_struct *fsp);
+struct files_struct *metadata_fsp(struct files_struct *fsp);
+bool fsp_search_ask_sharemode(struct files_struct *fsp);
+bool fsp_getinfo_ask_sharemode(struct files_struct *fsp);
+
+NTSTATUS create_internal_fsp(connection_struct *conn,
+ const struct smb_filename *smb_fname,
+ struct files_struct **_fsp);
+NTSTATUS create_internal_dirfsp(connection_struct *conn,
+ const struct smb_filename *smb_dname,
+ struct files_struct **_fsp);
+
+NTSTATUS open_internal_dirfsp(connection_struct *conn,
+ const struct smb_filename *smb_dname,
+ int open_flags,
+ struct files_struct **_fsp);
+NTSTATUS openat_internal_dir_from_pathref(
+ struct files_struct *dirfsp,
+ int open_flags,
+ struct files_struct **_fsp);
+
+NTSTATUS openat_pathref_fsp(const struct files_struct *dirfsp,
+ struct smb_filename *smb_fname);
+NTSTATUS open_stream_pathref_fsp(
+ struct files_struct **_base_fsp,
+ struct smb_filename *smb_fname);
+
+struct symlink_reparse_struct;
+
+struct open_symlink_err {
+ struct stat_ex st;
+ size_t unparsed;
+ struct symlink_reparse_struct *reparse;
+};
+
+NTSTATUS create_open_symlink_err(TALLOC_CTX *mem_ctx,
+ files_struct *dirfsp,
+ struct smb_filename *smb_relname,
+ struct open_symlink_err **_err);
+
+NTSTATUS openat_pathref_fsp_nosymlink(TALLOC_CTX *mem_ctx,
+ struct connection_struct *conn,
+ struct files_struct *dirfsp,
+ const char *path_in,
+ NTTIME twrp,
+ bool posix,
+ struct smb_filename **_smb_fname,
+ struct open_symlink_err **_symlink_err);
+NTSTATUS openat_pathref_fsp_lcomp(struct files_struct *dirfsp,
+ struct smb_filename *smb_fname_rel,
+ uint32_t ucf_flags);
+NTSTATUS readlink_talloc(
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_relname,
+ char **_substitute);
+
+struct symlink_reparse_struct;
+
+NTSTATUS read_symlink_reparse(
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_relname,
+ struct symlink_reparse_struct **_symlink);
+
+void smb_fname_fsp_unlink(struct smb_filename *smb_fname);
+
+NTSTATUS move_smb_fname_fsp_link(struct smb_filename *smb_fname_dst,
+ struct smb_filename *smb_fname_src);
+
+NTSTATUS reference_smb_fname_fsp_link(struct smb_filename *smb_fname_dst,
+ const struct smb_filename *smb_fname_src);
+
+NTSTATUS synthetic_pathref(TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ const char *base_name,
+ const char *stream_name,
+ const SMB_STRUCT_STAT *psbuf,
+ NTTIME twrp,
+ uint32_t flags,
+ struct smb_filename **_smb_fname);
+
+NTSTATUS parent_pathref(TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ struct smb_filename **_parent,
+ struct smb_filename **_atname);
+
+/* The following definitions come from smbd/smb2_ipc.c */
+
+NTSTATUS nt_status_np_pipe(NTSTATUS status);
+
+/* The following definitions come from smbd/mangle.c */
+
+void mangle_reset_cache(void);
+void mangle_change_to_posix(void);
+bool mangle_is_mangled(const char *s, const struct share_params *p);
+bool mangle_is_8_3(const char *fname, bool check_case,
+ const struct share_params *p);
+bool mangle_is_8_3_wildcards(const char *fname, bool check_case,
+ const struct share_params *p);
+bool mangle_must_mangle(const char *fname,
+ const struct share_params *p);
+bool mangle_lookup_name_from_8_3(TALLOC_CTX *ctx,
+ const char *in,
+ char **out, /* talloced on the given context. */
+ const struct share_params *p);
+bool name_to_8_3(const char *in,
+ char out[13],
+ bool cache83,
+ const struct share_params *p);
+
+/* The following definitions come from smbd/mangle_hash.c */
+
+const struct mangle_fns *mangle_hash_init(void);
+
+/* The following definitions come from smbd/mangle_hash2.c */
+
+const struct mangle_fns *mangle_hash2_init(void);
+const struct mangle_fns *posix_mangle_init(void);
+
+/* The following definitions come from smbd/msdfs.c */
+
+bool parse_msdfs_symlink(TALLOC_CTX *ctx,
+ bool shuffle_referrals,
+ const char *target,
+ struct referral **preflist,
+ size_t *refcount);
+bool is_msdfs_link(struct files_struct *dirfsp,
+ struct smb_filename *smb_fname);
+struct junction_map;
+NTSTATUS get_referred_path(TALLOC_CTX *ctx,
+ struct auth_session_info *session_info,
+ const char *dfs_path,
+ const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ struct junction_map *jucn,
+ size_t *consumedcntp,
+ bool *self_referralp);
+int setup_dfs_referral(connection_struct *orig_conn,
+ const char *dfs_path,
+ int max_referral_level,
+ char **ppdata, NTSTATUS *pstatus);
+bool create_junction(TALLOC_CTX *ctx,
+ const char *dfs_path,
+ struct junction_map *jucn);
+struct referral;
+char *msdfs_link_string(TALLOC_CTX *ctx,
+ const struct referral *reflist,
+ size_t referral_count);
+bool create_msdfs_link(const struct junction_map *jucn,
+ struct auth_session_info *session_info);
+bool remove_msdfs_link(const struct junction_map *jucn,
+ struct auth_session_info *session_info);
+
+struct junction_map *enum_msdfs_links(TALLOC_CTX *ctx,
+ struct auth_session_info *session_info,
+ size_t *p_num_jn);
+struct connection_struct;
+struct smb_filename;
+
+NTSTATUS create_conn_struct_cwd(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg,
+ const struct auth_session_info *session_info,
+ int snum,
+ const char *path,
+ struct connection_struct **c);
+struct conn_struct_tos {
+ struct connection_struct *conn;
+ struct smb_filename *oldcwd_fname;
+};
+NTSTATUS create_conn_struct_tos(struct messaging_context *msg,
+ int snum,
+ const char *path,
+ const struct auth_session_info *session_info,
+ struct conn_struct_tos **_c);
+NTSTATUS create_conn_struct_tos_cwd(struct messaging_context *msg,
+ int snum,
+ const char *path,
+ const struct auth_session_info *session_info,
+ struct conn_struct_tos **_c);
+
+/* The following definitions come from smbd/notify.c */
+
+bool change_notify_fsp_has_changes(struct files_struct *fsp);
+void change_notify_reply(struct smb_request *req,
+ NTSTATUS error_code,
+ uint32_t max_param,
+ struct notify_change_buf *notify_buf,
+ void (*reply_fn)(struct smb_request *req,
+ NTSTATUS error_code,
+ uint8_t *buf, size_t len));
+void notify_callback(struct smbd_server_connection *sconn,
+ void *private_data, struct timespec when,
+ const struct notify_event *e);
+NTSTATUS change_notify_create(struct files_struct *fsp,
+ uint32_t max_buffer_size,
+ uint32_t filter,
+ bool recursive);
+NTSTATUS change_notify_add_request(struct smb_request *req,
+ uint32_t max_param,
+ uint32_t filter, bool recursive,
+ struct files_struct *fsp,
+ void (*reply_fn)(struct smb_request *req,
+ NTSTATUS error_code,
+ uint8_t *buf, size_t len));
+void smbd_notify_cancel_deleted(struct messaging_context *msg,
+ void *private_data, uint32_t msg_type,
+ struct server_id server_id, DATA_BLOB *data);
+void smbd_notifyd_restarted(struct messaging_context *msg,
+ void *private_data, uint32_t msg_type,
+ struct server_id server_id, DATA_BLOB *data);
+bool remove_pending_change_notify_requests_by_mid(
+ struct smbd_server_connection *sconn, uint64_t mid);
+void remove_pending_change_notify_requests_by_fid(files_struct *fsp,
+ NTSTATUS status);
+void notify_fname(connection_struct *conn, uint32_t action, uint32_t filter,
+ const char *path);
+char *notify_filter_string(TALLOC_CTX *mem_ctx, uint32_t filter);
+struct sys_notify_context *sys_notify_context_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev);
+
+/* The following definitions come from smbd/notify_inotify.c */
+
+int inotify_watch(TALLOC_CTX *mem_ctx,
+ struct sys_notify_context *ctx,
+ const char *path,
+ uint32_t *filter,
+ uint32_t *subdir_filter,
+ void (*callback)(struct sys_notify_context *ctx,
+ void *private_data,
+ struct notify_event *ev,
+ uint32_t filter),
+ void *private_data,
+ void *handle_p);
+
+int fam_watch(TALLOC_CTX *mem_ctx,
+ struct sys_notify_context *ctx,
+ const char *path,
+ uint32_t *filter,
+ uint32_t *subdir_filter,
+ void (*callback)(struct sys_notify_context *ctx,
+ void *private_data,
+ struct notify_event *ev,
+ uint32_t filter),
+ void *private_data,
+ void *handle_p);
+
+
+/* The following definitions come from smbd/notify_internal.c */
+
+struct notify_context *notify_init(
+ TALLOC_CTX *mem_ctx, struct messaging_context *msg,
+ struct smbd_server_connection *sconn,
+ void (*callback)(struct smbd_server_connection *sconn,
+ void *, struct timespec,
+ const struct notify_event *));
+NTSTATUS notify_add(struct notify_context *ctx,
+ const char *path, uint32_t filter, uint32_t subdir_filter,
+ void *private_data);
+NTSTATUS notify_remove(struct notify_context *ctx, void *private_data,
+ char *path);
+void notify_trigger(struct notify_context *notify,
+ uint32_t action, uint32_t filter,
+ const char *dir, const char *path);
+
+/* The following definitions come from smbd/ntquotas.c */
+
+NTSTATUS vfs_get_ntquota(files_struct *fsp, enum SMB_QUOTA_TYPE qtype,
+ struct dom_sid *psid, SMB_NTQUOTA_STRUCT *qt);
+int vfs_set_ntquota(files_struct *fsp, enum SMB_QUOTA_TYPE qtype, struct dom_sid *psid, SMB_NTQUOTA_STRUCT *qt);
+int vfs_get_user_ntquota_list(files_struct *fsp, SMB_NTQUOTA_LIST **qt_list);
+void *init_quota_handle(TALLOC_CTX *mem_ctx);
+
+/* The following definitions come from smbd/smb2_nttrans.c */
+
+NTSTATUS set_sd(files_struct *fsp, struct security_descriptor *psd,
+ uint32_t security_info_sent);
+NTSTATUS set_sd_blob(files_struct *fsp, uint8_t *data, uint32_t sd_len,
+ uint32_t security_info_sent);
+NTSTATUS copy_internals(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct smb_request *req,
+ struct files_struct *src_dirfsp,
+ struct smb_filename *smb_fname_src,
+ struct files_struct *dst_dirfsp,
+ struct smb_filename *smb_fname_dst,
+ uint32_t attrs);
+NTSTATUS smbd_do_query_security_desc(connection_struct *conn,
+ TALLOC_CTX *mem_ctx,
+ files_struct *fsp,
+ uint32_t security_info_wanted,
+ uint32_t max_data_count,
+ uint8_t **ppmarshalled_sd,
+ size_t *psd_size);
+#ifdef HAVE_SYS_QUOTAS
+
+struct smb2_query_quota_info;
+
+NTSTATUS smbd_do_query_getinfo_quota(TALLOC_CTX *mem_ctx,
+ files_struct *fsp,
+ bool restart_scan,
+ bool return_single,
+ uint32_t sid_list_length,
+ DATA_BLOB *sidbuffer,
+ uint32_t max_data_count,
+ uint8_t **p_data,
+ uint32_t *p_data_size);
+#endif
+
+/* The following definitions come from smbd/open.c */
+
+NTSTATUS smbd_check_access_rights_fsp(struct files_struct *dirfsp,
+ struct files_struct *fsp,
+ bool use_privs,
+ uint32_t access_mask);
+NTSTATUS check_parent_access_fsp(struct files_struct *fsp,
+ uint32_t access_mask);
+NTSTATUS fd_openat(const struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ files_struct *fsp,
+ const struct vfs_open_how *how);
+NTSTATUS fd_close(files_struct *fsp);
+bool is_oplock_stat_open(uint32_t access_mask);
+bool is_lease_stat_open(uint32_t access_mask);
+NTSTATUS send_break_message(struct messaging_context *msg_ctx,
+ const struct file_id *id,
+ const struct share_mode_entry *exclusive,
+ uint16_t break_to);
+struct deferred_open_record;
+bool is_deferred_open_async(const struct deferred_open_record *rec);
+bool defer_smb1_sharing_violation(struct smb_request *req);
+NTSTATUS create_directory(connection_struct *conn,
+ struct smb_request *req,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_dname);
+void msg_file_was_renamed(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+struct fsp_lease *find_fsp_lease(struct files_struct *new_fsp,
+ const struct smb2_lease_key *key,
+ uint32_t current_state,
+ uint16_t lease_version,
+ uint16_t lease_epoch);
+NTSTATUS create_file_default(connection_struct *conn,
+ struct smb_request *req,
+ struct files_struct *dirfsp,
+ struct smb_filename * smb_fname,
+ uint32_t access_mask,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ uint32_t file_attributes,
+ uint32_t oplock_request,
+ const struct smb2_lease *lease,
+ uint64_t allocation_size,
+ uint32_t private_flags,
+ struct security_descriptor *sd,
+ struct ea_list *ea_list,
+ files_struct **result,
+ int *pinfo,
+ const struct smb2_create_blobs *in_context_blobs,
+ struct smb2_create_blobs *out_context_blobs);
+
+/* The following definitions come from smbd/oplock.c */
+
+uint32_t get_lease_type(struct share_mode_entry *e, struct file_id id);
+
+void break_kernel_oplock(struct messaging_context *msg_ctx, files_struct *fsp);
+NTSTATUS set_file_oplock(files_struct *fsp);
+void release_file_oplock(files_struct *fsp);
+bool remove_oplock(files_struct *fsp);
+bool downgrade_oplock(files_struct *fsp);
+bool fsp_lease_update(struct files_struct *fsp);
+NTSTATUS downgrade_lease(struct smbXsrv_client *client,
+ uint32_t num_file_ids,
+ const struct file_id *ids,
+ const struct smb2_lease_key *key,
+ uint32_t lease_state);
+void contend_level2_oplocks_begin(files_struct *fsp,
+ enum level2_contention_type type);
+void contend_level2_oplocks_end(files_struct *fsp,
+ enum level2_contention_type type);
+void smbd_contend_level2_oplocks_begin(files_struct *fsp,
+ enum level2_contention_type type);
+void smbd_contend_level2_oplocks_end(files_struct *fsp,
+ enum level2_contention_type type);
+void share_mode_entry_to_message(char *msg, const struct file_id *id,
+ const struct share_mode_entry *e);
+void message_to_share_mode_entry(struct file_id *id,
+ struct share_mode_entry *e,
+ const char *msg);
+bool init_oplocks(struct smbd_server_connection *sconn);
+void init_kernel_oplocks(struct smbd_server_connection *sconn);
+
+/* The following definitions come from smbd/oplock_linux.c */
+
+int linux_set_lease_sighandler(int fd);
+int linux_setlease(int fd, int leasetype);
+struct kernel_oplocks *linux_init_kernel_oplocks(struct smbd_server_connection *sconn);
+
+/* The following definitions come from smbd/password.c */
+
+void invalidate_vuid(struct smbd_server_connection *sconn, uint64_t vuid);
+int register_homes_share(const char *username);
+
+/* The following definitions come from smbd/pipes.c */
+
+NTSTATUS open_np_file(struct smb_request *smb_req, const char *name,
+ struct files_struct **pfsp);
+
+/* The following definitions come from smbd/posix_acls.c */
+
+mode_t unix_perms_to_acl_perms(mode_t mode, int r_mask, int w_mask, int x_mask);
+int map_acl_perms_to_permset(mode_t mode, SMB_ACL_PERMSET_T *p_permset);
+uint32_t map_canon_ace_perms(int snum,
+ enum security_ace_type *pacl_type,
+ mode_t perms,
+ bool directory_ace);
+bool current_user_in_group(connection_struct *conn, gid_t gid);
+SMB_ACL_T free_empty_sys_acl(connection_struct *conn, SMB_ACL_T the_acl);
+NTSTATUS posix_fget_nt_acl(struct files_struct *fsp, uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc);
+NTSTATUS chown_if_needed(files_struct *fsp, uint32_t security_info_sent,
+ const struct security_descriptor *psd,
+ bool *did_chown);
+NTSTATUS set_nt_acl(files_struct *fsp, uint32_t security_info_sent, const struct security_descriptor *psd);
+int get_acl_group_bits(connection_struct *conn,
+ struct files_struct *fsp,
+ mode_t *mode);
+int inherit_access_posix_acl(connection_struct *conn,
+ struct files_struct *inherit_from_dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode);
+NTSTATUS set_unix_posix_default_acl(connection_struct *conn,
+ files_struct *fsp,
+ uint16_t num_def_acls, const char *pdata);
+NTSTATUS set_unix_posix_acl(connection_struct *conn, files_struct *fsp,
+ uint16_t num_acls,
+ const char *pdata);
+int posix_sys_acl_blob_get_file(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ TALLOC_CTX *mem_ctx,
+ char **blob_description,
+ DATA_BLOB *blob);
+int posix_sys_acl_blob_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ char **blob_description,
+ DATA_BLOB *blob);
+
+enum default_acl_style {DEFAULT_ACL_POSIX, DEFAULT_ACL_WINDOWS, DEFAULT_ACL_EVERYONE};
+
+const struct enum_list *get_default_acl_style_list(void);
+
+NTSTATUS make_default_filesystem_acl(
+ TALLOC_CTX *ctx,
+ enum default_acl_style acl_style,
+ const char *name,
+ const SMB_STRUCT_STAT *psbuf,
+ struct security_descriptor **ppdesc);
+
+/* The following definitions come from smbd/smb2_process.c */
+
+#if !defined(WITH_SMB1SERVER)
+bool smb1_srv_send(struct smbXsrv_connection *xconn,
+ char *buffer,
+ bool do_signing,
+ uint32_t seqnum,
+ bool do_encrypt);
+#endif
+size_t srv_smb1_set_message(char *buf,
+ size_t num_words,
+ size_t num_bytes,
+ bool zero);
+NTSTATUS read_packet_remainder(int fd, char *buffer,
+ unsigned int timeout, ssize_t len);
+NTSTATUS receive_smb_talloc(TALLOC_CTX *mem_ctx,
+ struct smbXsrv_connection *xconn,
+ int sock,
+ char **buffer, unsigned int timeout,
+ size_t *p_unread, bool *p_encrypted,
+ size_t *p_len,
+ uint32_t *seqnum,
+ bool trusted_channel);
+void remove_deferred_open_message_smb(struct smbXsrv_connection *xconn,
+ uint64_t mid);
+bool schedule_deferred_open_message_smb(struct smbXsrv_connection *xconn,
+ uint64_t mid);
+bool open_was_deferred(struct smbXsrv_connection *xconn, uint64_t mid);
+bool get_deferred_open_message_state(struct smb_request *smbreq,
+ struct timeval *p_request_time,
+ struct deferred_open_record **open_rec);
+bool push_deferred_open_message_smb(struct smb_request *req,
+ struct timeval timeout,
+ struct file_id id,
+ struct deferred_open_record *open_rec);
+bool create_smb1_outbuf(TALLOC_CTX *mem_ctx, struct smb_request *req,
+ const uint8_t *inbuf, char **outbuf,
+ uint8_t num_words, uint32_t num_bytes);
+void construct_smb1_reply_common_req(struct smb_request *req, char *outbuf);
+void reply_smb1_outbuf(struct smb_request *req, uint8_t num_words, uint32_t num_bytes);
+void process_smb(struct smbXsrv_connection *xconn,
+ uint8_t *inbuf,
+ size_t nread,
+ size_t unread_bytes,
+ uint32_t seqnum,
+ bool encrypted);
+void smbd_process(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ int sock_fd,
+ bool interactive);
+bool valid_smb1_header(const uint8_t *inbuf);
+bool init_smb1_request(struct smb_request *req,
+ struct smbd_server_connection *sconn,
+ struct smbXsrv_connection *xconn,
+ const uint8_t *inbuf,
+ size_t unread_bytes, bool encrypted,
+ uint32_t seqnum);
+
+/* The following definitions come from smbd/quotas.c */
+
+bool disk_quotas(connection_struct *conn, struct smb_filename *fname,
+ uint64_t *bsize, uint64_t *dfree, uint64_t *dsize);
+
+/* The following definitions come from smbd/smb2_reply.c */
+
+NTSTATUS check_path_syntax(char *path, bool posix);
+NTSTATUS smb1_strip_dfs_path(TALLOC_CTX *mem_ctx,
+ uint32_t *ucf_flags,
+ char **in_path);
+NTSTATUS smb2_strip_dfs_path(const char *in_path, const char **out_path);
+size_t srvstr_get_path(TALLOC_CTX *ctx,
+ const char *inbuf,
+ uint16_t smb_flags2,
+ char **pp_dest,
+ const char *src,
+ size_t src_len,
+ int flags,
+ NTSTATUS *err);
+size_t srvstr_get_path_posix(TALLOC_CTX *ctx,
+ const char *inbuf,
+ uint16_t smb_flags2,
+ char **pp_dest,
+ const char *src,
+ size_t src_len,
+ int flags,
+ NTSTATUS *err);
+size_t srvstr_get_path_req(TALLOC_CTX *mem_ctx, struct smb_request *req,
+ char **pp_dest, const char *src, int flags,
+ NTSTATUS *err);
+size_t srvstr_pull_req_talloc(TALLOC_CTX *ctx, struct smb_request *req,
+ char **dest, const uint8_t *src, int flags);
+bool check_fsp_open(connection_struct *conn, struct smb_request *req,
+ files_struct *fsp);
+bool check_fsp(connection_struct *conn, struct smb_request *req,
+ files_struct *fsp);
+bool check_fsp_ntquota_handle(connection_struct *conn, struct smb_request *req,
+ files_struct *fsp);
+void reply_special(struct smbXsrv_connection *xconn, char *inbuf, size_t inbuf_size);
+NTSTATUS unlink_internals(connection_struct *conn,
+ struct smb_request *req,
+ uint32_t dirtype,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname);
+ssize_t fake_sendfile(struct smbXsrv_connection *xconn, files_struct *fsp,
+ off_t startpos, size_t nread);
+ssize_t sendfile_short_send(struct smbXsrv_connection *xconn,
+ files_struct *fsp,
+ ssize_t nread,
+ size_t headersize,
+ size_t smb_maxcnt);
+NTSTATUS rename_internals_fsp(connection_struct *conn,
+ files_struct *fsp,
+ struct smb_filename *smb_fname_dst_in,
+ const char *dst_original_lcomp,
+ uint32_t attrs,
+ bool replace_if_exists);
+NTSTATUS rename_internals(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct smb_request *req,
+ struct files_struct *src_dirfsp,
+ struct smb_filename *smb_fname_src,
+ struct smb_filename *smb_fname_dst,
+ const char *dst_original_lcomp,
+ uint32_t attrs,
+ bool replace_if_exists,
+ uint32_t access_mask);
+NTSTATUS copy_file(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct smb_filename *smb_fname_src,
+ struct smb_filename *smb_fname_dst,
+ uint32_t new_create_disposition);
+uint64_t get_lock_offset(const uint8_t *data, int data_offset,
+ bool large_file_format);
+
+/* The following definitions come from smbd/seal.c */
+
+bool is_encrypted_packet(const uint8_t *inbuf);
+void srv_free_enc_buffer(struct smbXsrv_connection *xconn, char *buf);
+NTSTATUS srv_decrypt_buffer(struct smbXsrv_connection *xconn, char *buf);
+NTSTATUS srv_encrypt_buffer(struct smbXsrv_connection *xconn, char *buf,
+ char **buf_out);
+NTSTATUS srv_request_encryption_setup(connection_struct *conn,
+ unsigned char **ppdata,
+ size_t *p_data_size,
+ unsigned char **pparam,
+ size_t *p_param_size);
+NTSTATUS srv_encryption_start(connection_struct *conn);
+void server_encryption_shutdown(struct smbXsrv_connection *xconn);
+
+/* The following definitions come from smbd/sec_ctx.c */
+
+bool unix_token_equal(const struct security_unix_token *t1, const struct security_unix_token *t2);
+bool push_sec_ctx(void);
+void set_sec_ctx(uid_t uid, gid_t gid, int ngroups, gid_t *groups, const struct security_token *token);
+void set_root_sec_ctx(void);
+bool pop_sec_ctx(void);
+void init_sec_ctx(void);
+const struct security_token *sec_ctx_active_token(void);
+
+/* The following definitions come from smbd/server.c */
+
+struct memcache *smbd_memcache(void);
+bool snum_is_shared_printer(int snum);
+void delete_and_reload_printers(void);
+bool reload_services(struct smbd_server_connection *sconn,
+ bool (*snumused) (struct smbd_server_connection *, int),
+ bool test);
+
+/* The following definitions come from smbd/server_exit.c */
+
+void smbd_exit_server(const char *reason) _NORETURN_;
+void smbd_exit_server_cleanly(const char *const reason) _NORETURN_;
+
+/* The following definitions come from smbd/smb2_service.c */
+
+bool set_conn_connectpath(connection_struct *conn, const char *connectpath);
+bool canonicalize_connect_path(connection_struct *conn);
+NTSTATUS set_conn_force_user_group(connection_struct *conn, int snum);
+bool chdir_current_service(connection_struct *conn);
+void load_registry_shares(void);
+int add_home_service(const char *service, const char *username, const char *homedir);
+int find_service(TALLOC_CTX *ctx, const char *service, char **p_service_out);
+connection_struct *make_connection_smb2(struct smbd_smb2_request *req,
+ struct smbXsrv_tcon *tcon,
+ int snum,
+ const char *pdev,
+ NTSTATUS *pstatus);
+NTSTATUS make_connection_snum(struct smbXsrv_connection *xconn,
+ connection_struct *conn,
+ int snum,
+ struct smbXsrv_session *session,
+ const char *pdev);
+void close_cnum(connection_struct *conn,
+ uint64_t vuid,
+ enum file_close_type close_type);
+
+/* The following definitions come from smbd/session.c */
+struct sessionid;
+struct smbXsrv_session;
+bool session_init(void);
+bool session_claim(struct smbXsrv_session *session);
+void session_yield(struct smbXsrv_session *session);
+int list_sessions(TALLOC_CTX *mem_ctx, struct sessionid **session_list);
+int find_sessions(TALLOC_CTX *mem_ctx, const char *username,
+ const char *machine, struct sessionid **session_list);
+
+/* The following definitions come from smbd/share_access.c */
+
+bool token_contains_name_in_list(const char *username,
+ const char *domain,
+ const char *sharename,
+ const struct security_token *token,
+ const char **list);
+bool user_ok_token(const char *username, const char *domain,
+ const struct security_token *token, int snum);
+bool is_share_read_only_for_token(const char *username,
+ const char *domain,
+ const struct security_token *token,
+ connection_struct *conn);
+
+/* The following definitions come from smbd/srvstr.c */
+
+NTSTATUS srvstr_push_fn(const char *base_ptr, uint16_t smb_flags2, void *dest,
+ const char *src, int dest_len, int flags, size_t *ret_len);
+
+/* The following definitions come from smbd/statvfs.c */
+
+int sys_statvfs(const char *path, struct vfs_statvfs_struct *statbuf);
+
+/* The following definitions come from smbd/trans2.c */
+
+char *store_file_unix_basic(connection_struct *conn,
+ char *pdata,
+ files_struct *fsp,
+ const SMB_STRUCT_STAT *psbuf);
+char *store_file_unix_basic_info2(connection_struct *conn,
+ char *pdata,
+ files_struct *fsp,
+ const SMB_STRUCT_STAT *psbuf);
+NTSTATUS smb_set_file_disposition_info(connection_struct *conn,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ struct smb_filename *smb_fname);
+NTSTATUS refuse_symlink_fsp(const struct files_struct *fsp);
+NTSTATUS check_any_access_fsp(struct files_struct *fsp,
+ uint32_t access_requested);
+uint64_t smb_roundup(connection_struct *conn, uint64_t val);
+bool samba_private_attr_name(const char *unix_ea_name);
+NTSTATUS get_ea_value_fsp(TALLOC_CTX *mem_ctx,
+ files_struct *fsp,
+ const char *ea_name,
+ struct ea_struct *pea);
+NTSTATUS get_ea_names_from_fsp(TALLOC_CTX *mem_ctx,
+ files_struct *fsp,
+ char ***pnames,
+ size_t *pnum_names);
+NTSTATUS set_ea(connection_struct *conn, files_struct *fsp,
+ struct ea_list *ea_list);
+unsigned char *create_volume_objectid(connection_struct *conn, unsigned char objid[16]);
+NTSTATUS hardlink_internals(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct smb_request *req,
+ bool overwrite_if_exists,
+ const struct smb_filename *smb_fname_old,
+ struct smb_filename *smb_fname_new);
+NTSTATUS smb_set_file_time(connection_struct *conn,
+ files_struct *fsp,
+ struct smb_filename *smb_fname,
+ struct smb_file_time *ft,
+ bool setting_write_time);
+NTSTATUS smb_set_file_size(connection_struct *conn,
+ struct smb_request *req,
+ files_struct *fsp,
+ struct smb_filename *smb_fname,
+ const SMB_STRUCT_STAT *psbuf,
+ off_t size,
+ bool fail_after_createfile);
+
+bool map_info2_flags_to_sbuf(const SMB_STRUCT_STAT *psbuf,
+ const uint32_t smb_fflags,
+ const uint32_t smb_fmask,
+ int *stat_fflags);
+
+enum perm_type {
+ PERM_NEW_FILE,
+ PERM_NEW_DIR,
+ PERM_EXISTING_FILE,
+ PERM_EXISTING_DIR
+};
+
+NTSTATUS unix_perms_from_wire(connection_struct *conn,
+ const SMB_STRUCT_STAT *psbuf,
+ uint32_t perms,
+ enum perm_type ptype,
+ mode_t *ret_perms);
+struct ea_list *read_ea_list(TALLOC_CTX *ctx, const char *pdata,
+ size_t data_size);
+unsigned int estimate_ea_size(files_struct *fsp);
+NTSTATUS smb_set_fsquota(connection_struct *conn,
+ struct smb_request *req,
+ files_struct *fsp,
+ const DATA_BLOB *qdata);
+
+/* The following definitions come from smbd/uid.c */
+
+bool change_to_guest(void);
+NTSTATUS check_user_share_access(connection_struct *conn,
+ const struct auth_session_info *session_info,
+ uint32_t *p_share_access,
+ bool *p_readonly_share);
+bool change_to_user_and_service(connection_struct *conn, uint64_t vuid);
+bool change_to_user_and_service_by_fsp(struct files_struct *fsp);
+bool smbd_change_to_root_user(void);
+bool smbd_become_authenticated_pipe_user(struct auth_session_info *session_info);
+bool smbd_unbecome_authenticated_pipe_user(void);
+void become_root(void);
+void unbecome_root(void);
+void smbd_become_root(void);
+void smbd_unbecome_root(void);
+bool become_user_without_service(connection_struct *conn, uint64_t vuid);
+bool become_user_without_service_by_fsp(struct files_struct *fsp);
+bool become_user_without_service_by_session(connection_struct *conn,
+ const struct auth_session_info *session_info);
+bool unbecome_user_without_service(void);
+uid_t get_current_uid(connection_struct *conn);
+gid_t get_current_gid(connection_struct *conn);
+const struct security_unix_token *get_current_utok(connection_struct *conn);
+const struct security_token *get_current_nttok(connection_struct *conn);
+
+/* The following definitions come from smbd/utmp.c */
+
+void sys_utmp_claim(const char *username, const char *hostname,
+ const char *id_str, int id_num);
+void sys_utmp_yield(const char *username, const char *hostname,
+ const char *id_str, int id_num);
+
+/* The following definitions come from smbd/vfs.c */
+
+bool vfs_init_custom(connection_struct *conn, const char *vfs_object);
+bool smbd_vfs_init(connection_struct *conn);
+NTSTATUS vfs_file_exist(connection_struct *conn, struct smb_filename *smb_fname);
+bool vfs_valid_pread_range(off_t offset, size_t length);
+bool vfs_valid_pwrite_range(off_t offset, size_t length);
+ssize_t vfs_pwrite_data(struct smb_request *req,
+ files_struct *fsp,
+ const char *buffer,
+ size_t N,
+ off_t offset);
+int vfs_allocate_file_space(files_struct *fsp, uint64_t len);
+int vfs_set_filelen(files_struct *fsp, off_t len);
+int vfs_slow_fallocate(files_struct *fsp, off_t offset, off_t len);
+int vfs_fill_sparse(files_struct *fsp, off_t len);
+int vfs_set_blocking(files_struct *fsp, bool set);
+off_t vfs_transfer_file(files_struct *in, files_struct *out, off_t n);
+const char *vfs_readdirname(connection_struct *conn,
+ struct files_struct *dirfsp,
+ DIR *d,
+ char **talloced);
+int vfs_ChDir(connection_struct *conn,
+ const struct smb_filename *smb_fname);
+struct smb_filename *vfs_GetWd(TALLOC_CTX *ctx, connection_struct *conn);
+int vfs_stat(struct connection_struct *conn,
+ struct smb_filename *smb_fname);
+int vfs_stat_smb_basename(struct connection_struct *conn,
+ const struct smb_filename *smb_fname_in,
+ SMB_STRUCT_STAT *psbuf);
+NTSTATUS vfs_stat_fsp(files_struct *fsp);
+NTSTATUS vfs_fstreaminfo(struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *num_streams,
+ struct stream_struct **streams);
+void init_smb_file_time(struct smb_file_time *ft);
+int vfs_fake_fd(void);
+int vfs_fake_fd_close(int fd);
+
+/* The following definitions come from smbd/avahi_register.c */
+
+void *avahi_start_register(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ uint16_t port);
+
+/* The following definitions come from smbd/smb2_create.c */
+
+NTSTATUS vfs_default_durable_cookie(struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *cookie_blob);
+NTSTATUS vfs_default_durable_disconnect(struct files_struct *fsp,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *new_cookie);
+NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
+ struct smb_request *smb1req,
+ struct smbXsrv_open *op,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ files_struct **result,
+ DATA_BLOB *new_cookie);
+
+struct smb3_file_posix_information;
+void smb3_file_posix_information_init(
+ connection_struct *conn,
+ const struct stat_ex *st,
+ uint32_t reparse_tag,
+ uint32_t dos_attributes,
+ struct smb3_file_posix_information *dst);
+
+#endif /* _SMBD_PROTO_H_ */
diff --git a/source3/smbd/pysmbd.c b/source3/smbd/pysmbd.c
new file mode 100644
index 0000000..e1f889d
--- /dev/null
+++ b/source3/smbd/pysmbd.c
@@ -0,0 +1,1305 @@
+/*
+ Unix SMB/CIFS implementation.
+ Set NT and POSIX ACLs and other VFS operations from Python
+
+ Copyrigyt (C) Andrew Bartlett 2012
+ Copyright (C) Jeremy Allison 1994-2009.
+ Copyright (C) Andreas Gruenbacher 2002.
+ Copyright (C) Simo Sorce <idra@samba.org> 2009.
+ Copyright (C) Simo Sorce 2002
+ Copyright (C) Eric Lorimer 2002
+
+ 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 "lib/replace/system/python.h"
+#include "includes.h"
+#include "python/py3compat.h"
+#include "python/modules.h"
+#include "smbd/smbd.h"
+#include "libcli/util/pyerrors.h"
+#include "librpc/rpc/pyrpc_util.h"
+#include <pytalloc.h>
+#include "system/filesys.h"
+#include "passdb.h"
+#include "secrets.h"
+#include "auth.h"
+
+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 connection_struct *get_conn_tos(
+ const char *service,
+ const struct auth_session_info *session_info)
+{
+ struct conn_struct_tos *c = NULL;
+ int snum = -1;
+ NTSTATUS status;
+ char *cwd = NULL;
+ struct smb_filename cwd_fname = {0};
+ int ret;
+
+ if (!posix_locking_init(false)) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ if (service) {
+ snum = lp_servicenumber(service);
+ if (snum == -1) {
+ PyErr_SetString(PyExc_RuntimeError, "unknown service");
+ return NULL;
+ }
+ }
+
+ /*
+ * Make sure that session unix info is filled,
+ * which is required by vfs operations.
+ */
+ if (session_info->unix_info == NULL) {
+ PyErr_SetString(PyExc_RuntimeError,
+ "Session unix info not initialized");
+ return NULL;
+ }
+ if (session_info->unix_info->unix_name == NULL) {
+ PyErr_SetString(PyExc_RuntimeError,
+ "Session unix info not available");
+ return NULL;
+ }
+
+ status = create_conn_struct_tos(NULL,
+ snum,
+ "/",
+ session_info,
+ &c);
+ PyErr_NTSTATUS_IS_ERR_RAISE(status);
+
+ /* Ignore read-only and share restrictions */
+ c->conn->read_only = false;
+ c->conn->share_access = SEC_RIGHTS_FILE_ALL;
+
+ /* Provided by libreplace if not present. Always mallocs. */
+ cwd = get_current_dir_name();
+ if (cwd == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ cwd_fname.base_name = cwd;
+ /*
+ * We need to call vfs_ChDir() to initialize
+ * conn->cwd_fsp correctly. Change directory
+ * to current directory (so no change for process).
+ */
+ ret = vfs_ChDir(c->conn, &cwd_fname);
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ SAFE_FREE(cwd);
+ PyErr_NTSTATUS_IS_ERR_RAISE(status);
+ }
+
+ SAFE_FREE(cwd);
+
+ return c->conn;
+}
+
+static int set_sys_acl_conn(const char *fname,
+ SMB_ACL_TYPE_T acltype,
+ SMB_ACL_T theacl, connection_struct *conn)
+{
+ int ret;
+ struct smb_filename *smb_fname = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ smb_fname = synthetic_smb_fname_split(frame,
+ fname,
+ lp_posix_pathnames());
+ if (smb_fname == NULL) {
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ status = openat_pathref_fsp(conn->cwd_fsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ errno = map_errno_from_nt_status(status);
+ return -1;
+ }
+
+ ret = SMB_VFS_SYS_ACL_SET_FD(smb_fname->fsp, acltype, theacl);
+
+ status = fd_close(smb_fname->fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ errno = map_errno_from_nt_status(status);
+ return -1;
+ }
+
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+
+static NTSTATUS init_files_struct(TALLOC_CTX *mem_ctx,
+ const char *fname,
+ struct connection_struct *conn,
+ int flags,
+ struct files_struct **_fsp)
+{
+ struct vfs_open_how how = { .flags = flags, .mode = 0644 };
+ struct smb_filename *smb_fname = NULL;
+ int fd;
+ mode_t saved_umask;
+ struct files_struct *fsp;
+ struct files_struct *fspcwd = NULL;
+ NTSTATUS status;
+
+ fsp = talloc_zero(mem_ctx, struct files_struct);
+ if (fsp == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ fsp->fh = fd_handle_create(fsp);
+ if (fsp->fh == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ fsp->conn = conn;
+
+ smb_fname = synthetic_smb_fname_split(fsp,
+ fname,
+ lp_posix_pathnames());
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ fsp->fsp_name = smb_fname;
+
+ status = vfs_at_fspcwd(fsp, conn, &fspcwd);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * we want total control over the permissions on created files,
+ * so set our umask to 0 (this matters if flags contains O_CREAT)
+ */
+ saved_umask = umask(0);
+
+ fd = SMB_VFS_OPENAT(conn,
+ fspcwd,
+ smb_fname,
+ fsp,
+ &how);
+
+ umask(saved_umask);
+
+ if (fd == -1) {
+ int err = errno;
+ if (err == ENOENT) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ fsp_set_fd(fsp, fd);
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* 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),
+ nt_errstr(status) ));
+ return status;
+ }
+
+ fsp->file_id = vfs_file_id_from_sbuf(conn, &smb_fname->st);
+ fsp->vuid = UID_FIELD_INVALID;
+ fsp->file_pid = 0;
+ fsp->fsp_flags.can_lock = true;
+ fsp->fsp_flags.can_read = true;
+ fsp->fsp_flags.can_write = true;
+ fsp->print_file = NULL;
+ fsp->fsp_flags.modified = false;
+ fsp->sent_oplock_break = NO_BREAK_SENT;
+ fsp->fsp_flags.is_directory = S_ISDIR(smb_fname->st.st_ex_mode);
+
+ *_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)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ 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(frame,
+ fname,
+ conn,
+ O_RDWR,
+ &fsp);
+ if (!NT_STATUS_IS_OK(status) && errno == EISDIR) {
+ /* if fail, try to open as dir */
+ status = init_files_struct(frame,
+ fname,
+ conn,
+ DIRECTORY_FLAGS,
+ &fsp);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("init_files_struct failed: %s\n",
+ nt_errstr(status));
+ if (fsp != NULL) {
+ fd_close(fsp);
+ }
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ status = SMB_VFS_FSET_NT_ACL(metadata_fsp(fsp), security_info_sent, sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("set_nt_acl_conn: fset_nt_acl returned %s.\n", nt_errstr(status)));
+ }
+
+ fd_close(fsp);
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static NTSTATUS get_nt_acl_conn(TALLOC_CTX *mem_ctx,
+ const char *fname,
+ connection_struct *conn,
+ uint32_t security_info_wanted,
+ struct security_descriptor **sd)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct smb_filename *smb_fname = NULL;
+
+ smb_fname = synthetic_smb_fname_split(frame,
+ fname,
+ lp_posix_pathnames());
+
+ if (smb_fname == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = openat_pathref_fsp(conn->cwd_fsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ status = SMB_VFS_FGET_NT_ACL(metadata_fsp(smb_fname->fsp),
+ security_info_wanted,
+ mem_ctx,
+ sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("fget_nt_acl_at returned %s.\n",
+ nt_errstr(status));
+ }
+
+ status = fd_close(smb_fname->fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ TALLOC_FREE(frame);
+
+ return status;
+}
+
+static int set_acl_entry_perms(SMB_ACL_ENTRY_T entry, mode_t perm_mask)
+{
+ SMB_ACL_PERMSET_T perms = NULL;
+
+ if (sys_acl_get_permset(entry, &perms) != 0) {
+ return -1;
+ }
+
+ if (sys_acl_clear_perms(perms) != 0) {
+ return -1;
+ }
+
+ if ((perm_mask & SMB_ACL_READ) != 0 &&
+ sys_acl_add_perm(perms, SMB_ACL_READ) != 0) {
+ return -1;
+ }
+
+ if ((perm_mask & SMB_ACL_WRITE) != 0 &&
+ sys_acl_add_perm(perms, SMB_ACL_WRITE) != 0) {
+ return -1;
+ }
+
+ if ((perm_mask & SMB_ACL_EXECUTE) != 0 &&
+ sys_acl_add_perm(perms, SMB_ACL_EXECUTE) != 0) {
+ return -1;
+ }
+
+ if (sys_acl_set_permset(entry, perms) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static SMB_ACL_T make_simple_acl(TALLOC_CTX *mem_ctx,
+ gid_t gid,
+ mode_t chmod_mode)
+{
+ mode_t mode = SMB_ACL_READ|SMB_ACL_WRITE|SMB_ACL_EXECUTE;
+
+ mode_t mode_user = (chmod_mode & 0700) >> 6;
+ mode_t mode_group = (chmod_mode & 070) >> 3;
+ mode_t mode_other = chmod_mode & 07;
+ SMB_ACL_ENTRY_T entry;
+ SMB_ACL_T acl = sys_acl_init(mem_ctx);
+
+ if (!acl) {
+ return NULL;
+ }
+
+ if (sys_acl_create_entry(&acl, &entry) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ if (sys_acl_set_tag_type(entry, SMB_ACL_USER_OBJ) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ if (set_acl_entry_perms(entry, mode_user) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ if (sys_acl_create_entry(&acl, &entry) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ if (sys_acl_set_tag_type(entry, SMB_ACL_GROUP_OBJ) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ if (set_acl_entry_perms(entry, mode_group) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ if (sys_acl_create_entry(&acl, &entry) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ if (sys_acl_set_tag_type(entry, SMB_ACL_OTHER) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ if (set_acl_entry_perms(entry, mode_other) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ if (gid != -1) {
+ if (sys_acl_create_entry(&acl, &entry) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ if (sys_acl_set_tag_type(entry, SMB_ACL_GROUP) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ if (sys_acl_set_qualifier(entry, &gid) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ if (set_acl_entry_perms(entry, mode_group) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+ }
+
+ if (sys_acl_create_entry(&acl, &entry) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ if (sys_acl_set_tag_type(entry, SMB_ACL_MASK) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ if (set_acl_entry_perms(entry, mode) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ return acl;
+}
+
+/*
+ set a simple ACL on a file, as a test
+ */
+static PyObject *py_smbd_set_simple_acl(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ const char * const kwnames[] = {
+ "fname",
+ "mode",
+ "session_info",
+ "gid",
+ "service",
+ NULL
+ };
+ char *fname, *service = NULL;
+ PyObject *py_session = Py_None;
+ struct auth_session_info *session_info = NULL;
+ int ret;
+ int mode, gid = -1;
+ SMB_ACL_T acl;
+ TALLOC_CTX *frame;
+ connection_struct *conn;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "siO|iz",
+ discard_const_p(char *, kwnames),
+ &fname,
+ &mode,
+ &py_session,
+ &gid,
+ &service))
+ return NULL;
+
+ if (!py_check_dcerpc_type(py_session,
+ "samba.dcerpc.auth",
+ "session_info")) {
+ return NULL;
+ }
+ session_info = pytalloc_get_type(py_session,
+ struct auth_session_info);
+ if (session_info == NULL) {
+ PyErr_Format(PyExc_TypeError,
+ "Expected auth_session_info for session_info argument got %s",
+ pytalloc_get_name(py_session));
+ return NULL;
+ }
+
+ frame = talloc_stackframe();
+
+ acl = make_simple_acl(frame, gid, mode);
+ if (acl == NULL) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ conn = get_conn_tos(service, session_info);
+ if (!conn) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ ret = set_sys_acl_conn(fname, SMB_ACL_TYPE_ACCESS, acl, conn);
+
+ if (ret != 0) {
+ TALLOC_FREE(frame);
+ errno = ret;
+ return PyErr_SetFromErrno(PyExc_OSError);
+ }
+
+ TALLOC_FREE(frame);
+
+ Py_RETURN_NONE;
+}
+
+/*
+ chown a file
+ */
+static PyObject *py_smbd_chown(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ const char * const kwnames[] = {
+ "fname",
+ "uid",
+ "gid",
+ "session_info",
+ "service",
+ NULL
+ };
+ connection_struct *conn;
+ int ret;
+ NTSTATUS status;
+ char *fname, *service = NULL;
+ PyObject *py_session = Py_None;
+ struct auth_session_info *session_info = NULL;
+ int uid, gid;
+ TALLOC_CTX *frame;
+ struct files_struct *fsp = NULL;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "siiO|z",
+ discard_const_p(char *, kwnames),
+ &fname,
+ &uid,
+ &gid,
+ &py_session,
+ &service))
+ return NULL;
+
+ if (!py_check_dcerpc_type(py_session,
+ "samba.dcerpc.auth",
+ "session_info")) {
+ return NULL;
+ }
+ session_info = pytalloc_get_type(py_session,
+ struct auth_session_info);
+ if (session_info == NULL) {
+ PyErr_Format(PyExc_TypeError,
+ "Expected auth_session_info for session_info argument got %s",
+ pytalloc_get_name(py_session));
+ return NULL;
+ }
+
+ frame = talloc_stackframe();
+
+ conn = get_conn_tos(service, session_info);
+ if (!conn) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ /* first, try to open it as a file with flag O_RDWR */
+ status = init_files_struct(frame,
+ fname,
+ conn,
+ O_RDWR,
+ &fsp);
+ if (!NT_STATUS_IS_OK(status) && errno == EISDIR) {
+ /* if fail, try to open as dir */
+ status = init_files_struct(frame,
+ fname,
+ conn,
+ DIRECTORY_FLAGS,
+ &fsp);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("init_files_struct failed: %s\n",
+ nt_errstr(status));
+ if (fsp != NULL) {
+ fd_close(fsp);
+ }
+ TALLOC_FREE(frame);
+ /*
+ * The following macro raises a python
+ * error then returns NULL.
+ */
+ PyErr_NTSTATUS_IS_ERR_RAISE(status);
+ }
+
+ ret = SMB_VFS_FCHOWN(fsp, uid, gid);
+ if (ret != 0) {
+ int saved_errno = errno;
+ fd_close(fsp);
+ TALLOC_FREE(frame);
+ errno = saved_errno;
+ return PyErr_SetFromErrno(PyExc_OSError);
+ }
+
+ fd_close(fsp);
+ TALLOC_FREE(frame);
+
+ Py_RETURN_NONE;
+}
+
+/*
+ unlink a file
+ */
+static PyObject *py_smbd_unlink(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ const char * const kwnames[] = {
+ "fname",
+ "session_info",
+ "service",
+ NULL
+ };
+ connection_struct *conn;
+ int ret;
+ struct smb_filename *smb_fname = NULL;
+ struct smb_filename *parent_fname = NULL;
+ struct smb_filename *at_fname = NULL;
+ PyObject *py_session = Py_None;
+ struct auth_session_info *session_info = NULL;
+ char *fname, *service = NULL;
+ TALLOC_CTX *frame;
+ NTSTATUS status;
+
+ frame = talloc_stackframe();
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|z",
+ discard_const_p(char *, kwnames),
+ &fname,
+ &py_session ,
+ &service)) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ if (!py_check_dcerpc_type(py_session,
+ "samba.dcerpc.auth",
+ "session_info")) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+ session_info = pytalloc_get_type(py_session,
+ struct auth_session_info);
+ if (session_info == NULL) {
+ PyErr_Format(PyExc_TypeError,
+ "Expected auth_session_info for session_info argument got %s",
+ pytalloc_get_name(py_session));
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ conn = get_conn_tos(service, session_info);
+ if (!conn) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ smb_fname = synthetic_smb_fname_split(frame,
+ fname,
+ lp_posix_pathnames());
+ if (smb_fname == NULL) {
+ TALLOC_FREE(frame);
+ return PyErr_NoMemory();
+ }
+
+ status = parent_pathref(frame,
+ conn->cwd_fsp,
+ smb_fname,
+ &parent_fname,
+ &at_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return PyErr_NoMemory();
+ }
+
+ ret = SMB_VFS_UNLINKAT(conn,
+ parent_fname->fsp,
+ at_fname,
+ 0);
+ if (ret != 0) {
+ TALLOC_FREE(frame);
+ errno = ret;
+ return PyErr_SetFromErrno(PyExc_OSError);
+ }
+
+ TALLOC_FREE(frame);
+
+ Py_RETURN_NONE;
+}
+
+/*
+ check if we have ACL support
+ */
+static PyObject *py_smbd_have_posix_acls(PyObject *self,
+ PyObject *Py_UNUSED(ignored))
+{
+#ifdef HAVE_POSIX_ACLS
+ return PyBool_FromLong(true);
+#else
+ return PyBool_FromLong(false);
+#endif
+}
+
+/*
+ set the NT ACL on a file
+ */
+static PyObject *py_smbd_set_nt_acl(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ const char * const kwnames[] = {
+ "fname",
+ "security_info_sent",
+ "sd",
+ "session_info",
+ "service",
+ NULL
+ };
+
+ NTSTATUS status;
+ char *fname, *service = NULL;
+ int security_info_sent;
+ PyObject *py_sd;
+ struct security_descriptor *sd;
+ PyObject *py_session = Py_None;
+ struct auth_session_info *session_info = NULL;
+ connection_struct *conn;
+ TALLOC_CTX *frame;
+
+ frame = talloc_stackframe();
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "siOO|z",
+ discard_const_p(char *, kwnames),
+ &fname,
+ &security_info_sent,
+ &py_sd,
+ &py_session,
+ &service)) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ if (!py_check_dcerpc_type(py_sd, "samba.dcerpc.security", "descriptor")) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ if (!py_check_dcerpc_type(py_session,
+ "samba.dcerpc.auth",
+ "session_info")) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+ session_info = pytalloc_get_type(py_session,
+ struct auth_session_info);
+ if (session_info == NULL) {
+ PyErr_Format(PyExc_TypeError,
+ "Expected auth_session_info for session_info argument got %s",
+ pytalloc_get_name(py_session));
+ return NULL;
+ }
+
+ conn = get_conn_tos(service, session_info);
+ if (!conn) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ sd = pytalloc_get_type(py_sd, struct security_descriptor);
+
+ status = set_nt_acl_conn(fname, security_info_sent, sd, conn);
+ TALLOC_FREE(frame);
+ if (NT_STATUS_IS_ERR(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ /*
+ * This will show up as a FileNotFoundError in python.
+ */
+ PyErr_SetFromErrnoWithFilename(PyExc_OSError, fname);
+ } else {
+ PyErr_SetNTSTATUS(status);
+ }
+ return NULL;
+ }
+
+ Py_RETURN_NONE;
+}
+
+/*
+ Return the NT ACL on a file
+ */
+static PyObject *py_smbd_get_nt_acl(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ const char * const kwnames[] = {
+ "fname",
+ "security_info_wanted",
+ "session_info",
+ "service",
+ NULL
+ };
+ char *fname, *service = NULL;
+ int security_info_wanted;
+ PyObject *py_sd;
+ struct security_descriptor *sd;
+ TALLOC_CTX *frame = talloc_stackframe();
+ PyObject *py_session = Py_None;
+ struct auth_session_info *session_info = NULL;
+ connection_struct *conn;
+ NTSTATUS status;
+ int ret = 1;
+
+ ret = PyArg_ParseTupleAndKeywords(args,
+ kwargs,
+ "siO|z",
+ discard_const_p(char *, kwnames),
+ &fname,
+ &security_info_wanted,
+ &py_session,
+ &service);
+ if (!ret) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ if (!py_check_dcerpc_type(py_session,
+ "samba.dcerpc.auth",
+ "session_info")) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+ session_info = pytalloc_get_type(py_session,
+ struct auth_session_info);
+ if (session_info == NULL) {
+ PyErr_Format(
+ PyExc_TypeError,
+ "Expected auth_session_info for "
+ "session_info argument got %s",
+ pytalloc_get_name(py_session));
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ conn = get_conn_tos(service, session_info);
+ if (!conn) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ status = get_nt_acl_conn(frame, fname, conn, security_info_wanted, &sd);
+ if (NT_STATUS_IS_ERR(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ /*
+ * This will show up as a FileNotFoundError in python,
+ * from which samba-tool can at least produce a short
+ * message containing the problematic filename.
+ */
+ PyErr_SetFromErrnoWithFilename(PyExc_OSError, fname);
+ } else {
+ PyErr_SetNTSTATUS(status);
+ }
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ py_sd = py_return_ndr_struct("samba.dcerpc.security", "descriptor", sd, sd);
+
+ TALLOC_FREE(frame);
+
+ return py_sd;
+}
+
+/*
+ set the posix (or similar) ACL on a file
+ */
+static PyObject *py_smbd_set_sys_acl(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ const char * const kwnames[] = {
+ "fname",
+ "acl_type",
+ "acl",
+ "session_info",
+ "service",
+ NULL
+ };
+ TALLOC_CTX *frame = talloc_stackframe();
+ int ret;
+ char *fname, *service = NULL;
+ PyObject *py_acl;
+ PyObject *py_session = Py_None;
+ struct auth_session_info *session_info = NULL;
+ struct smb_acl_t *acl;
+ int acl_type;
+ connection_struct *conn;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "siOO|z",
+ discard_const_p(char *, kwnames),
+ &fname,
+ &acl_type,
+ &py_acl,
+ &py_session,
+ &service)) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ if (!py_check_dcerpc_type(py_acl, "samba.dcerpc.smb_acl", "t")) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ if (!py_check_dcerpc_type(py_session,
+ "samba.dcerpc.auth",
+ "session_info")) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+ session_info = pytalloc_get_type(py_session,
+ struct auth_session_info);
+ if (session_info == NULL) {
+ PyErr_Format(PyExc_TypeError,
+ "Expected auth_session_info for session_info argument got %s",
+ pytalloc_get_name(py_session));
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ conn = get_conn_tos(service, session_info);
+ if (!conn) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ acl = pytalloc_get_type(py_acl, struct smb_acl_t);
+
+ ret = set_sys_acl_conn(fname, acl_type, acl, conn);
+ if (ret != 0) {
+ TALLOC_FREE(frame);
+ errno = ret;
+ return PyErr_SetFromErrno(PyExc_OSError);
+ }
+
+ TALLOC_FREE(frame);
+ Py_RETURN_NONE;
+}
+
+/*
+ Return the posix (or similar) ACL on a file
+ */
+static PyObject *py_smbd_get_sys_acl(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ const char * const kwnames[] = {
+ "fname",
+ "acl_type",
+ "session_info",
+ "service",
+ NULL
+ };
+ char *fname;
+ PyObject *py_acl;
+ PyObject *py_session = Py_None;
+ struct auth_session_info *session_info = NULL;
+ struct smb_acl_t *acl;
+ int acl_type;
+ TALLOC_CTX *frame = talloc_stackframe();
+ connection_struct *conn;
+ char *service = NULL;
+ struct smb_filename *smb_fname = NULL;
+ NTSTATUS status;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "siO|z",
+ discard_const_p(char *, kwnames),
+ &fname,
+ &acl_type,
+ &py_session,
+ &service)) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ if (!py_check_dcerpc_type(py_session,
+ "samba.dcerpc.auth",
+ "session_info")) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+ session_info = pytalloc_get_type(py_session,
+ struct auth_session_info);
+ if (session_info == NULL) {
+ PyErr_Format(PyExc_TypeError,
+ "Expected auth_session_info for session_info argument got %s",
+ pytalloc_get_name(py_session));
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ conn = get_conn_tos(service, session_info);
+ if (!conn) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ smb_fname = synthetic_smb_fname_split(frame,
+ fname,
+ lp_posix_pathnames());
+ if (smb_fname == NULL) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ status = openat_pathref_fsp(conn->cwd_fsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ PyErr_SetNTSTATUS(status);
+ return NULL;
+ }
+
+ acl = SMB_VFS_SYS_ACL_GET_FD(smb_fname->fsp, acl_type, frame);
+ if (!acl) {
+ TALLOC_FREE(frame);
+ return PyErr_SetFromErrno(PyExc_OSError);
+ }
+
+ status = fd_close(smb_fname->fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ PyErr_SetNTSTATUS(status);
+ return NULL;
+ }
+
+ py_acl = py_return_ndr_struct("samba.dcerpc.smb_acl", "t", acl, acl);
+
+ TALLOC_FREE(frame);
+
+ return py_acl;
+}
+
+static PyObject *py_smbd_mkdir(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ const char * const kwnames[] = {
+ "fname",
+ "session_info",
+ "service",
+ NULL
+ };
+ char *fname, *service = NULL;
+ PyObject *py_session = Py_None;
+ struct auth_session_info *session_info = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct connection_struct *conn = NULL;
+ struct smb_filename *smb_fname = NULL;
+ struct smb_filename *parent_fname = NULL;
+ struct smb_filename *base_name = NULL;
+ NTSTATUS status;
+ int ret;
+ mode_t saved_umask;
+
+ if (!PyArg_ParseTupleAndKeywords(args,
+ kwargs,
+ "sO|z",
+ discard_const_p(char *,
+ kwnames),
+ &fname,
+ &py_session,
+ &service)) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ if (!py_check_dcerpc_type(py_session,
+ "samba.dcerpc.auth",
+ "session_info")) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+ session_info = pytalloc_get_type(py_session,
+ struct auth_session_info);
+ if (session_info == NULL) {
+ PyErr_Format(PyExc_TypeError,
+ "Expected auth_session_info for session_info argument got %s",
+ pytalloc_get_name(py_session));
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ conn = get_conn_tos(service, session_info);
+ if (!conn) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ smb_fname = synthetic_smb_fname(talloc_tos(),
+ fname,
+ NULL,
+ NULL,
+ 0,
+ lp_posix_pathnames() ?
+ SMB_FILENAME_POSIX_PATH : 0);
+
+ if (smb_fname == NULL) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ status = parent_pathref(talloc_tos(),
+ conn->cwd_fsp,
+ smb_fname,
+ &parent_fname,
+ &base_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ /* we want total control over the permissions on created files,
+ so set our umask to 0 */
+ saved_umask = umask(0);
+
+ ret = SMB_VFS_MKDIRAT(conn,
+ parent_fname->fsp,
+ base_name,
+ 00755);
+
+ umask(saved_umask);
+
+ if (ret == -1) {
+ DBG_ERR("mkdirat error=%d (%s)\n", errno, strerror(errno));
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ TALLOC_FREE(frame);
+ Py_RETURN_NONE;
+}
+
+
+/*
+ Create an empty file
+ */
+static PyObject *py_smbd_create_file(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ const char * const kwnames[] = {
+ "fname",
+ "session_info",
+ "service",
+ NULL
+ };
+ char *fname, *service = NULL;
+ PyObject *py_session = Py_None;
+ struct auth_session_info *session_info = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct connection_struct *conn = NULL;
+ struct files_struct *fsp = NULL;
+ NTSTATUS status;
+
+ if (!PyArg_ParseTupleAndKeywords(args,
+ kwargs,
+ "sO|z",
+ discard_const_p(char *,
+ kwnames),
+ &fname,
+ &py_session,
+ &service)) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ if (!py_check_dcerpc_type(py_session,
+ "samba.dcerpc.auth",
+ "session_info")) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+ session_info = pytalloc_get_type(py_session,
+ struct auth_session_info);
+ if (session_info == NULL) {
+ PyErr_Format(PyExc_TypeError,
+ "Expected auth_session_info for session_info argument got %s",
+ pytalloc_get_name(py_session));
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ conn = get_conn_tos(service, session_info);
+ if (!conn) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ status = init_files_struct(frame,
+ fname,
+ conn,
+ O_CREAT|O_EXCL|O_RDWR,
+ &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("init_files_struct failed: %s\n",
+ nt_errstr(status));
+ } else if (fsp != NULL) {
+ fd_close(fsp);
+ }
+
+ TALLOC_FREE(frame);
+ PyErr_NTSTATUS_NOT_OK_RAISE(status);
+ Py_RETURN_NONE;
+}
+
+
+static PyMethodDef py_smbd_methods[] = {
+ { "have_posix_acls",
+ (PyCFunction)py_smbd_have_posix_acls, METH_NOARGS,
+ NULL },
+ { "set_simple_acl",
+ PY_DISCARD_FUNC_SIG(PyCFunction, py_smbd_set_simple_acl),
+ METH_VARARGS|METH_KEYWORDS,
+ NULL },
+ { "set_nt_acl",
+ PY_DISCARD_FUNC_SIG(PyCFunction, py_smbd_set_nt_acl),
+ METH_VARARGS|METH_KEYWORDS,
+ NULL },
+ { "get_nt_acl",
+ PY_DISCARD_FUNC_SIG(PyCFunction, py_smbd_get_nt_acl),
+ METH_VARARGS|METH_KEYWORDS,
+ NULL },
+ { "get_sys_acl",
+ PY_DISCARD_FUNC_SIG(PyCFunction, py_smbd_get_sys_acl),
+ METH_VARARGS|METH_KEYWORDS,
+ NULL },
+ { "set_sys_acl",
+ PY_DISCARD_FUNC_SIG(PyCFunction, py_smbd_set_sys_acl),
+ METH_VARARGS|METH_KEYWORDS,
+ NULL },
+ { "chown",
+ PY_DISCARD_FUNC_SIG(PyCFunction, py_smbd_chown),
+ METH_VARARGS|METH_KEYWORDS,
+ NULL },
+ { "unlink",
+ PY_DISCARD_FUNC_SIG(PyCFunction, py_smbd_unlink),
+ METH_VARARGS|METH_KEYWORDS,
+ NULL },
+ { "mkdir",
+ PY_DISCARD_FUNC_SIG(PyCFunction, py_smbd_mkdir),
+ METH_VARARGS|METH_KEYWORDS,
+ NULL },
+ { "create_file",
+ PY_DISCARD_FUNC_SIG(PyCFunction, py_smbd_create_file),
+ METH_VARARGS|METH_KEYWORDS,
+ NULL },
+ {0}
+};
+
+void initsmbd(void);
+
+static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "smbd",
+ .m_doc = "Python bindings for the smbd file server.",
+ .m_size = -1,
+ .m_methods = py_smbd_methods,
+};
+
+MODULE_INIT_FUNC(smbd)
+{
+ PyObject *m = NULL;
+
+ m = PyModule_Create(&moduledef);
+ return m;
+}
diff --git a/source3/smbd/quotas.c b/source3/smbd/quotas.c
new file mode 100644
index 0000000..40fb3ee
--- /dev/null
+++ b/source3/smbd/quotas.c
@@ -0,0 +1,497 @@
+/*
+ Unix SMB/CIFS implementation.
+ support for quotas
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ 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/>.
+*/
+
+
+/*
+ * This is one of the most system dependent parts of Samba, and its
+ * done a little differently. Each system has its own way of doing
+ * things :-(
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "system/filesys.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_QUOTA
+
+#ifndef HAVE_SYS_QUOTAS
+
+/* just a quick hack because sysquotas.h is included before linux/quota.h */
+#ifdef QUOTABLOCK_SIZE
+#undef QUOTABLOCK_SIZE
+#endif
+
+#ifdef WITH_QUOTAS
+
+#if defined(SUNOS5) /* Solaris */
+
+#include <fcntl.h>
+#include <sys/param.h>
+#include <sys/fs/ufs_quota.h>
+#include <sys/mnttab.h>
+#include <sys/mntent.h>
+
+/****************************************************************************
+ Allows querying of remote hosts for quotas on NFS mounted shares.
+ Supports normal NFS and AMD mounts.
+ Alan Romeril <a.romeril@ic.ac.uk> July 2K.
+****************************************************************************/
+
+#include <rpc/rpc.h>
+#include <rpc/types.h>
+#include <rpcsvc/rquota.h>
+#include <rpc/nettype.h>
+#include <rpc/xdr.h>
+
+static int my_xdr_getquota_rslt(XDR *xdrsp, struct getquota_rslt *gqr)
+{
+ int quotastat;
+
+ if (!xdr_int(xdrsp, &quotastat)) {
+ DEBUG(6,("nfs_quotas: Status bad or zero\n"));
+ return 0;
+ }
+ gqr->status = quotastat;
+
+ if (!xdr_int(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_bsize)) {
+ DEBUG(6,("nfs_quotas: Block size bad or zero\n"));
+ return 0;
+ }
+ if (!xdr_bool(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_active)) {
+ DEBUG(6,("nfs_quotas: Active bad or zero\n"));
+ return 0;
+ }
+ if (!xdr_int(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_bhardlimit)) {
+ DEBUG(6,("nfs_quotas: Hardlimit bad or zero\n"));
+ return 0;
+ }
+ if (!xdr_int(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_bsoftlimit)) {
+ DEBUG(6,("nfs_quotas: Softlimit bad or zero\n"));
+ return 0;
+ }
+ if (!xdr_int(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_curblocks)) {
+ DEBUG(6,("nfs_quotas: Currentblocks bad or zero\n"));
+ return 0;
+ }
+ return (1);
+}
+
+static int my_xdr_getquota_args(XDR *xdrsp, struct getquota_args *args)
+{
+ if (!xdr_string(xdrsp, &args->gqa_pathp, RQ_PATHLEN ))
+ return(0);
+ if (!xdr_int(xdrsp, &args->gqa_uid))
+ return(0);
+ return (1);
+}
+
+/* Restricted to SUNOS5 for the moment, I haven`t access to others to test. */
+static bool nfs_quotas(char *nfspath, uid_t euser_id, uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
+{
+ uid_t uid = euser_id;
+ struct dqblk D;
+ char *mnttype = nfspath;
+ CLIENT *clnt;
+ struct getquota_rslt gqr;
+ struct getquota_args args;
+ char *cutstr, *pathname, *host, *testpath;
+ int len;
+ static struct timeval timeout = {2,0};
+ enum clnt_stat clnt_stat;
+ bool ret = True;
+
+ *bsize = *dfree = *dsize = (uint64_t)0;
+
+ len=strcspn(mnttype, ":");
+ pathname=strstr(mnttype, ":");
+ cutstr = (char *) SMB_MALLOC(len+1);
+ if (!cutstr)
+ return False;
+
+ memset(cutstr, '\0', len+1);
+ host = strncat(cutstr,mnttype, sizeof(char) * len );
+ DEBUG(5,("nfs_quotas: looking for mount on \"%s\"\n", cutstr));
+ DEBUG(5,("nfs_quotas: of path \"%s\"\n", mnttype));
+ testpath=strchr_m(mnttype, ':');
+ args.gqa_pathp = testpath+1;
+ args.gqa_uid = uid;
+
+ DEBUG(5,("nfs_quotas: Asking for host \"%s\" rpcprog \"%i\" rpcvers \"%i\" network \"%s\"\n", host, RQUOTAPROG, RQUOTAVERS, "udp"));
+
+ if ((clnt = clnt_create(host, RQUOTAPROG, RQUOTAVERS, "udp")) == NULL) {
+ ret = False;
+ goto out;
+ }
+
+ clnt->cl_auth = authunix_create_default();
+ DEBUG(9,("nfs_quotas: auth_success\n"));
+
+ clnt_stat=clnt_call(clnt, RQUOTAPROC_GETQUOTA, my_xdr_getquota_args, (caddr_t)&args, my_xdr_getquota_rslt, (caddr_t)&gqr, timeout);
+
+ if (clnt_stat != RPC_SUCCESS) {
+ DEBUG(9,("nfs_quotas: clnt_call fail\n"));
+ ret = False;
+ goto out;
+ }
+
+ /*
+ * gqr.status returns 1 if quotas exist, 2 if there is
+ * no quota set, and 3 if no permission to get the quota.
+ * If 3, return something sensible.
+ */
+
+ switch (gqr.status) {
+ case 1:
+ DEBUG(9,("nfs_quotas: Good quota data\n"));
+ D.dqb_bsoftlimit = gqr.getquota_rslt_u.gqr_rquota.rq_bsoftlimit;
+ D.dqb_bhardlimit = gqr.getquota_rslt_u.gqr_rquota.rq_bhardlimit;
+ D.dqb_curblocks = gqr.getquota_rslt_u.gqr_rquota.rq_curblocks;
+ break;
+
+ case 2:
+ case 3:
+ D.dqb_bsoftlimit = 1;
+ D.dqb_curblocks = 1;
+ DEBUG(9,("nfs_quotas: Remote Quotas returned \"%i\" \n", gqr.status));
+ break;
+
+ default:
+ DEBUG(9, ("nfs_quotas: Unknown Remote Quota Status \"%i\"\n",
+ gqr.status));
+ ret = false;
+ goto out;
+ }
+
+ DEBUG(10,("nfs_quotas: Let`s look at D a bit closer... status \"%i\" bsize \"%i\" active? \"%i\" bhard \"%i\" bsoft \"%i\" curb \"%i\" \n",
+ gqr.status,
+ gqr.getquota_rslt_u.gqr_rquota.rq_bsize,
+ gqr.getquota_rslt_u.gqr_rquota.rq_active,
+ gqr.getquota_rslt_u.gqr_rquota.rq_bhardlimit,
+ gqr.getquota_rslt_u.gqr_rquota.rq_bsoftlimit,
+ gqr.getquota_rslt_u.gqr_rquota.rq_curblocks));
+
+ *bsize = gqr.getquota_rslt_u.gqr_rquota.rq_bsize;
+ *dsize = D.dqb_bsoftlimit;
+
+ if (D.dqb_curblocks > D.dqb_bsoftlimit) {
+ *dfree = 0;
+ *dsize = D.dqb_curblocks;
+ } else
+ *dfree = D.dqb_bsoftlimit - D.dqb_curblocks;
+
+ out:
+
+ if (clnt) {
+ if (clnt->cl_auth)
+ auth_destroy(clnt->cl_auth);
+ clnt_destroy(clnt);
+ }
+
+ DEBUG(5,("nfs_quotas: For path \"%s\" returning bsize %.0f, dfree %.0f, dsize %.0f\n",args.gqa_pathp,(double)*bsize,(double)*dfree,(double)*dsize));
+
+ SAFE_FREE(cutstr);
+ DEBUG(10,("nfs_quotas: End of nfs_quotas\n" ));
+ return ret;
+}
+
+/****************************************************************************
+try to get the disk space from disk quotas (SunOS & Solaris2 version)
+Quota code by Peter Urbanec (amiga@cse.unsw.edu.au).
+****************************************************************************/
+
+bool disk_quotas(connection_struct *conn, struct smb_filename *fname,
+ uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
+{
+ uid_t euser_id;
+ int ret;
+ struct dqblk D;
+ struct quotctl command;
+ int file;
+ struct mnttab mnt;
+ char *name = NULL;
+ FILE *fd;
+ SMB_STRUCT_STAT sbuf;
+ SMB_DEV_T devno;
+ bool found = false;
+ const char *path = fname->base_name;
+
+ euser_id = geteuid();
+
+ devno = fname->st.st_ex_dev;
+ DEBUG(5,("disk_quotas: looking for path \"%s\" devno=%x\n",
+ path, (unsigned int)devno));
+ if ((fd = fopen(MNTTAB, "r")) == NULL) {
+ return false;
+ }
+
+ while (getmntent(fd, &mnt) == 0) {
+ if (sys_stat(mnt.mnt_mountp, &sbuf, false) == -1) {
+ continue;
+ }
+
+ DEBUG(5,("disk_quotas: testing \"%s\" devno=%x\n",
+ mnt.mnt_mountp, (unsigned int)devno));
+
+ /* quotas are only on vxfs, UFS or NFS */
+ if ((sbuf.st_ex_dev == devno) && (
+ strcmp( mnt.mnt_fstype, MNTTYPE_UFS ) == 0 ||
+ strcmp( mnt.mnt_fstype, "nfs" ) == 0 ||
+ strcmp( mnt.mnt_fstype, "vxfs" ) == 0 )) {
+ found = true;
+ name = talloc_asprintf(talloc_tos(),
+ "%s/quotas",
+ mnt.mnt_mountp);
+ break;
+ }
+ }
+
+ fclose(fd);
+ if (!found) {
+ return false;
+ }
+
+ if (!name) {
+ return false;
+ }
+ become_root();
+
+ if (strcmp(mnt.mnt_fstype, "nfs") == 0) {
+ bool retval;
+ DEBUG(5,("disk_quotas: looking for mountpath (NFS) \"%s\"\n",
+ mnt.mnt_special));
+ retval = nfs_quotas(mnt.mnt_special,
+ euser_id, bsize, dfree, dsize);
+ unbecome_root();
+ return retval;
+ }
+
+ DEBUG(5,("disk_quotas: looking for quotas file \"%s\"\n", name));
+ if((file=open(name, O_RDONLY,0))<0) {
+ unbecome_root();
+ return false;
+ }
+ command.op = Q_GETQUOTA;
+ command.uid = euser_id;
+ command.addr = (caddr_t) &D;
+ ret = ioctl(file, Q_QUOTACTL, &command);
+ close(file);
+
+ unbecome_root();
+
+ if (ret < 0) {
+ DEBUG(5,("disk_quotas ioctl (Solaris) failed. Error = %s\n",
+ strerror(errno) ));
+
+ return false;
+ }
+
+ /* If softlimit is zero, set it equal to hardlimit.
+ */
+
+ if (D.dqb_bsoftlimit==0) {
+ D.dqb_bsoftlimit = D.dqb_bhardlimit;
+ }
+
+ /* Use softlimit to determine disk space. A user exceeding the quota
+ * is told that there's no space left. Writes might actually work for
+ * a bit if the hardlimit is set higher than softlimit. Effectively
+ * the disk becomes made of rubber latex and begins to expand to
+ * accommodate the user :-)
+ */
+
+ if (D.dqb_bsoftlimit==0)
+ return(False);
+ *bsize = DEV_BSIZE;
+ *dsize = D.dqb_bsoftlimit;
+
+ if (D.dqb_curblocks > D.dqb_bsoftlimit) {
+ *dfree = 0;
+ *dsize = D.dqb_curblocks;
+ } else {
+ *dfree = D.dqb_bsoftlimit - D.dqb_curblocks;
+ }
+
+ DEBUG(5,("disk_quotas for path \"%s\" returning "
+ "bsize %.0f, dfree %.0f, dsize %.0f\n",
+ path,(double)*bsize,(double)*dfree,(double)*dsize));
+
+ return true;
+}
+
+#endif /* Solaris */
+
+#else /* WITH_QUOTAS */
+
+bool disk_quotas(connection_struct *conn, struct smb_filename *fname,
+ uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
+{
+ (*bsize) = 512; /* This value should be ignored */
+
+ /* And just to be sure we set some values that hopefully */
+ /* will be larger that any possible real-world value */
+ (*dfree) = (uint64_t)-1;
+ (*dsize) = (uint64_t)-1;
+
+ /* As we have select not to use quotas, always fail */
+ return false;
+}
+#endif /* WITH_QUOTAS */
+
+#else /* HAVE_SYS_QUOTAS */
+/* wrapper to the new sys_quota interface
+ this file should be removed later
+ */
+bool disk_quotas(connection_struct *conn, struct smb_filename *fname,
+ uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
+{
+ int r;
+ SMB_DISK_QUOTA D;
+ unid_t id;
+
+ /*
+ * First of all, check whether user quota is
+ * enforced. If the call fails, assume it is
+ * not enforced.
+ */
+ ZERO_STRUCT(D);
+ id.uid = -1;
+ r = SMB_VFS_GET_QUOTA(conn, fname, SMB_USER_FS_QUOTA_TYPE,
+ id, &D);
+ if (r == -1 && errno != ENOSYS) {
+ goto try_group_quota;
+ }
+ if (r == 0 && (D.qflags & QUOTAS_DENY_DISK) == 0) {
+ goto try_group_quota;
+ }
+
+ ZERO_STRUCT(D);
+ id.uid = geteuid();
+
+ /* if new files created under this folder get this
+ * folder's UID, then available space is governed by
+ * the quota of the folder's UID, not the creating user.
+ */
+ if (lp_inherit_owner(SNUM(conn)) != INHERIT_OWNER_NO &&
+ id.uid != fname->st.st_ex_uid && id.uid != sec_initial_uid()) {
+ int save_errno;
+
+ id.uid = fname->st.st_ex_uid;
+ become_root();
+ r = SMB_VFS_GET_QUOTA(conn, fname,
+ SMB_USER_QUOTA_TYPE, id, &D);
+ save_errno = errno;
+ unbecome_root();
+ errno = save_errno;
+ } else {
+ r = SMB_VFS_GET_QUOTA(conn, fname,
+ SMB_USER_QUOTA_TYPE, id, &D);
+ }
+
+ if (r == -1) {
+ goto try_group_quota;
+ }
+
+ *bsize = D.bsize;
+ /* Use softlimit to determine disk space, except when it has been exceeded */
+ if (
+ (D.softlimit && D.curblocks >= D.softlimit) ||
+ (D.hardlimit && D.curblocks >= D.hardlimit) ||
+ (D.isoftlimit && D.curinodes >= D.isoftlimit) ||
+ (D.ihardlimit && D.curinodes>=D.ihardlimit)
+ ) {
+ *dfree = 0;
+ *dsize = D.curblocks;
+ } else if (D.softlimit==0 && D.hardlimit==0) {
+ goto try_group_quota;
+ } else {
+ if (D.softlimit == 0) {
+ D.softlimit = D.hardlimit;
+ }
+ *dfree = D.softlimit - D.curblocks;
+ *dsize = D.softlimit;
+ }
+
+ return True;
+
+try_group_quota:
+ /*
+ * First of all, check whether group quota is
+ * enforced. If the call fails, assume it is
+ * not enforced.
+ */
+ ZERO_STRUCT(D);
+ id.gid = -1;
+ r = SMB_VFS_GET_QUOTA(conn, fname, SMB_GROUP_FS_QUOTA_TYPE,
+ id, &D);
+ if (r == -1 && errno != ENOSYS) {
+ return false;
+ }
+ if (r == 0 && (D.qflags & QUOTAS_DENY_DISK) == 0) {
+ return false;
+ }
+
+ ZERO_STRUCT(D);
+
+ /*
+ * If new files created under this folder get this folder's
+ * GID, then available space is governed by the quota of the
+ * folder's GID, not the primary group of the creating user.
+ */
+ if (VALID_STAT(fname->st) &&
+ S_ISDIR(fname->st.st_ex_mode) &&
+ fname->st.st_ex_mode & S_ISGID) {
+ id.gid = fname->st.st_ex_gid;
+ become_root();
+ r = SMB_VFS_GET_QUOTA(conn, fname, SMB_GROUP_QUOTA_TYPE, id,
+ &D);
+ unbecome_root();
+ } else {
+ id.gid = getegid();
+ r = SMB_VFS_GET_QUOTA(conn, fname, SMB_GROUP_QUOTA_TYPE, id,
+ &D);
+ }
+
+ if (r == -1) {
+ return False;
+ }
+
+ *bsize = D.bsize;
+ /* Use softlimit to determine disk space, except when it has been exceeded */
+ if (
+ (D.softlimit && D.curblocks >= D.softlimit) ||
+ (D.hardlimit && D.curblocks >= D.hardlimit) ||
+ (D.isoftlimit && D.curinodes >= D.isoftlimit) ||
+ (D.ihardlimit && D.curinodes>=D.ihardlimit)
+ ) {
+ *dfree = 0;
+ *dsize = D.curblocks;
+ } else if (D.softlimit==0 && D.hardlimit==0) {
+ return False;
+ } else {
+ if (D.softlimit == 0) {
+ D.softlimit = D.hardlimit;
+ }
+ *dfree = D.softlimit - D.curblocks;
+ *dsize = D.softlimit;
+ }
+
+ return (True);
+}
+#endif /* HAVE_SYS_QUOTAS */
diff --git a/source3/smbd/scavenger.c b/source3/smbd/scavenger.c
new file mode 100644
index 0000000..40b2fe5
--- /dev/null
+++ b/source3/smbd/scavenger.c
@@ -0,0 +1,731 @@
+/*
+ Unix SMB/CIFS implementation.
+ smbd scavenger daemon
+
+ Copyright (C) Gregor Beck 2013
+
+ 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 "messages.h"
+#include "serverid.h"
+#include "smbd/globals.h"
+#include "smbd/smbXsrv_open.h"
+#include "smbd/scavenger.h"
+#include "locking/share_mode_lock.h"
+#include "locking/leases_db.h"
+#include "locking/proto.h"
+#include "librpc/gen_ndr/open_files.h"
+#include "lib/util/server_id.h"
+#include "lib/util/util_process.h"
+#include "lib/util/sys_rw_data.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SCAVENGER
+
+struct smbd_scavenger_state {
+ struct tevent_context *ev;
+ struct messaging_context *msg;
+ struct server_id parent_id;
+ struct server_id *scavenger_id;
+ bool am_scavenger;
+};
+
+static struct smbd_scavenger_state *smbd_scavenger_state = NULL;
+
+struct scavenger_message {
+ struct file_id file_id;
+ uint64_t open_persistent_id;
+ NTTIME until;
+};
+
+static int smbd_scavenger_main(struct smbd_scavenger_state *state)
+{
+ struct server_id_buf tmp1, tmp2;
+
+ DEBUG(10, ("scavenger: %s started, parent: %s\n",
+ server_id_str_buf(*state->scavenger_id, &tmp1),
+ server_id_str_buf(state->parent_id, &tmp2)));
+
+ while (true) {
+ TALLOC_CTX *frame = talloc_stackframe();
+ int ret;
+
+ ret = tevent_loop_once(state->ev);
+ if (ret != 0) {
+ DEBUG(2, ("tevent_loop_once failed: %s\n",
+ strerror(errno)));
+ TALLOC_FREE(frame);
+ return 1;
+ }
+
+ DEBUG(10, ("scavenger: %s event loop iteration\n",
+ server_id_str_buf(*state->scavenger_id, &tmp1)));
+ TALLOC_FREE(frame);
+ }
+
+ return 0;
+}
+
+static void smbd_scavenger_done(struct tevent_context *event_ctx, struct tevent_fd *fde,
+ uint16_t flags, void *private_data)
+{
+ struct smbd_scavenger_state *state = talloc_get_type_abort(
+ private_data, struct smbd_scavenger_state);
+ struct server_id_buf tmp;
+
+ DEBUG(2, ("scavenger: %s died\n",
+ server_id_str_buf(*state->scavenger_id, &tmp)));
+
+ TALLOC_FREE(state->scavenger_id);
+}
+
+static void smbd_scavenger_parent_dead(struct tevent_context *event_ctx,
+ struct tevent_fd *fde,
+ uint16_t flags, void *private_data)
+{
+ struct smbd_scavenger_state *state = talloc_get_type_abort(
+ private_data, struct smbd_scavenger_state);
+ struct server_id_buf tmp1, tmp2;
+
+ DEBUG(2, ("scavenger: %s parent %s died\n",
+ server_id_str_buf(*state->scavenger_id, &tmp1),
+ server_id_str_buf(state->parent_id, &tmp2)));
+
+ exit_server_cleanly("smbd_scavenger_parent_dead");
+}
+
+static void scavenger_sig_term_handler(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ exit_server_cleanly("termination signal");
+}
+
+static void scavenger_setup_sig_term_handler(struct tevent_context *ev_ctx)
+{
+ struct tevent_signal *se;
+
+ se = tevent_add_signal(ev_ctx,
+ ev_ctx,
+ SIGTERM, 0,
+ scavenger_sig_term_handler,
+ NULL);
+ if (se == NULL) {
+ exit_server("failed to setup SIGTERM handler");
+ }
+}
+
+static bool smbd_scavenger_running(struct smbd_scavenger_state *state)
+{
+ if (state->scavenger_id == NULL) {
+ return false;
+ }
+
+ return serverid_exists(state->scavenger_id);
+}
+
+static int smbd_scavenger_server_id_destructor(struct server_id *id)
+{
+ return 0;
+}
+
+static bool scavenger_say_hello(int fd, struct server_id self)
+{
+ ssize_t ret;
+ struct server_id_buf tmp;
+
+ ret = write_data(fd, &self, sizeof(self));
+ if (ret == -1) {
+ DEBUG(2, ("Failed to write to pipe: %s\n", strerror(errno)));
+ return false;
+ }
+ if (ret < sizeof(self)) {
+ DBG_WARNING("Could not write serverid\n");
+ return false;
+ }
+
+ DEBUG(4, ("scavenger_say_hello: self[%s]\n",
+ server_id_str_buf(self, &tmp)));
+ return true;
+}
+
+static bool scavenger_wait_hello(int fd, struct server_id *child)
+{
+ struct server_id_buf tmp;
+ ssize_t ret;
+
+ ret = read_data(fd, child, sizeof(struct server_id));
+ if (ret == -1) {
+ DEBUG(2, ("Failed to read from pipe: %s\n",
+ strerror(errno)));
+ return false;
+ }
+ if (ret < sizeof(struct server_id)) {
+ DBG_WARNING("Could not read serverid\n");
+ return false;
+ }
+
+ DEBUG(4, ("scavenger_say_hello: child[%s]\n",
+ server_id_str_buf(*child, &tmp)));
+ return true;
+}
+
+static bool smbd_scavenger_start(struct smbd_scavenger_state *state)
+{
+ struct server_id self = messaging_server_id(state->msg);
+ struct tevent_fd *fde = NULL;
+ int fds[2];
+ int ret;
+ bool ok;
+
+ SMB_ASSERT(server_id_equal(&state->parent_id, &self));
+
+ if (smbd_scavenger_running(state)) {
+ struct server_id_buf tmp;
+ DEBUG(10, ("scavenger %s already running\n",
+ server_id_str_buf(*state->scavenger_id,
+ &tmp)));
+ return true;
+ }
+
+ if (state->scavenger_id != NULL) {
+ struct server_id_buf tmp;
+ DEBUG(10, ("scavenger zombie %s, cleaning up\n",
+ server_id_str_buf(*state->scavenger_id,
+ &tmp)));
+ TALLOC_FREE(state->scavenger_id);
+ }
+
+ state->scavenger_id = talloc_zero(state, struct server_id);
+ if (state->scavenger_id == NULL) {
+ DEBUG(2, ("Out of memory\n"));
+ goto fail;
+ }
+ talloc_set_destructor(state->scavenger_id,
+ smbd_scavenger_server_id_destructor);
+
+ ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
+ if (ret == -1) {
+ DEBUG(2, ("socketpair failed: %s\n", strerror(errno)));
+ goto fail;
+ }
+
+ smb_set_close_on_exec(fds[0]);
+ smb_set_close_on_exec(fds[1]);
+
+ ret = fork();
+ if (ret == -1) {
+ int err = errno;
+ close(fds[0]);
+ close(fds[1]);
+ DEBUG(0, ("fork failed: %s\n", strerror(err)));
+ goto fail;
+ }
+
+ if (ret == 0) {
+ /* child */
+
+ NTSTATUS status;
+
+ close(fds[0]);
+
+ status = smbd_reinit_after_fork(state->msg, state->ev,
+ true);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("reinit_after_fork failed: %s\n",
+ nt_errstr(status)));
+ exit_server("reinit_after_fork failed");
+ return false;
+ }
+
+ process_set_title("smbd-scavenger", "scavenger");
+ reopen_logs();
+
+ state->am_scavenger = true;
+ *state->scavenger_id = messaging_server_id(state->msg);
+
+ scavenger_setup_sig_term_handler(state->ev);
+
+ ok = scavenger_say_hello(fds[1], *state->scavenger_id);
+ if (!ok) {
+ DEBUG(2, ("scavenger_say_hello failed\n"));
+ exit_server("scavenger_say_hello failed");
+ return false;
+ }
+
+ fde = tevent_add_fd(state->ev, state->scavenger_id,
+ fds[1], TEVENT_FD_READ,
+ smbd_scavenger_parent_dead, state);
+ if (fde == NULL) {
+ DEBUG(2, ("tevent_add_fd(smbd_scavenger_parent_dead) "
+ "failed\n"));
+ exit_server("tevent_add_fd(smbd_scavenger_parent_dead) "
+ "failed");
+ return false;
+ }
+ tevent_fd_set_auto_close(fde);
+
+ ret = smbd_scavenger_main(state);
+
+ DEBUG(10, ("scavenger ended: %d\n", ret));
+ exit_server_cleanly("scavenger ended");
+ return false;
+ }
+
+ /* parent */
+ close(fds[1]);
+
+ ok = scavenger_wait_hello(fds[0], state->scavenger_id);
+ if (!ok) {
+ close(fds[0]);
+ goto fail;
+ }
+
+ fde = tevent_add_fd(state->ev, state->scavenger_id,
+ fds[0], TEVENT_FD_READ,
+ smbd_scavenger_done, state);
+ if (fde == NULL) {
+ close(fds[0]);
+ goto fail;
+ }
+ tevent_fd_set_auto_close(fde);
+
+ return true;
+fail:
+ TALLOC_FREE(state->scavenger_id);
+ return false;
+}
+
+static void scavenger_add_timer(struct smbd_scavenger_state *state,
+ struct scavenger_message *msg);
+
+static void smbd_scavenger_msg(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id src,
+ DATA_BLOB *data)
+{
+ struct smbd_scavenger_state *state =
+ talloc_get_type_abort(private_data,
+ struct smbd_scavenger_state);
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct server_id self = messaging_server_id(msg_ctx);
+ struct scavenger_message *msg = NULL;
+ struct server_id_buf tmp1, tmp2;
+
+ DEBUG(10, ("smbd_scavenger_msg: %s got message from %s\n",
+ server_id_str_buf(self, &tmp1),
+ server_id_str_buf(src, &tmp2)));
+
+ if (server_id_equal(&state->parent_id, &self)) {
+ NTSTATUS status;
+
+ if (!smbd_scavenger_running(state) &&
+ !smbd_scavenger_start(state))
+ {
+ DEBUG(2, ("Failed to start scavenger\n"));
+ goto done;
+ }
+ DEBUG(10, ("forwarding message to scavenger\n"));
+
+ status = messaging_send(msg_ctx,
+ *state->scavenger_id, msg_type, data);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("forwarding message to scavenger failed: "
+ "%s\n", nt_errstr(status)));
+ goto done;
+ }
+ goto done;
+ }
+
+ if (!state->am_scavenger) {
+ DEBUG(10, ("im not the scavenger: ignore message\n"));
+ goto done;
+ }
+
+ if (!server_id_equal(&state->parent_id, &src)) {
+ DEBUG(10, ("scavenger: ignore spurious message\n"));
+ goto done;
+ }
+
+ DEBUG(10, ("scavenger: got a message\n"));
+ msg = (struct scavenger_message*)data->data;
+ scavenger_add_timer(state, msg);
+done:
+ talloc_free(frame);
+}
+
+bool smbd_scavenger_init(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg,
+ struct tevent_context *ev)
+{
+ struct smbd_scavenger_state *state;
+ NTSTATUS status;
+
+ if (smbd_scavenger_state) {
+ DEBUG(10, ("smbd_scavenger_init called again\n"));
+ return true;
+ }
+
+ state = talloc_zero(mem_ctx, struct smbd_scavenger_state);
+ if (state == NULL) {
+ DEBUG(2, ("Out of memory\n"));
+ return false;
+ }
+
+ state->msg = msg;
+ state->ev = ev;
+ state->parent_id = messaging_server_id(msg);
+
+ status = messaging_register(msg, state, MSG_SMB_SCAVENGER,
+ smbd_scavenger_msg);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("failed to register message handler: %s\n",
+ nt_errstr(status)));
+ goto fail;
+ }
+
+ smbd_scavenger_state = state;
+ return true;
+fail:
+ talloc_free(state);
+ return false;
+}
+
+void scavenger_schedule_disconnected(struct files_struct *fsp)
+{
+ NTSTATUS status;
+ struct server_id self = messaging_server_id(fsp->conn->sconn->msg_ctx);
+ struct timeval disconnect_time, until;
+ uint64_t timeout_usec;
+ struct scavenger_message msg;
+ DATA_BLOB msg_blob;
+ struct server_id_buf tmp;
+ struct file_id_buf idbuf;
+
+ if (fsp->op == NULL) {
+ return;
+ }
+ nttime_to_timeval(&disconnect_time, fsp->op->global->disconnect_time);
+ timeout_usec = UINT64_C(1000) * fsp->op->global->durable_timeout_msec;
+ until = timeval_add(&disconnect_time,
+ timeout_usec / 1000000,
+ timeout_usec % 1000000);
+
+ ZERO_STRUCT(msg);
+ msg.file_id = fsp->file_id;
+ msg.open_persistent_id = fsp->op->global->open_persistent_id;
+ msg.until = timeval_to_nttime(&until);
+
+ DEBUG(10, ("smbd: %s mark file %s as disconnected at %s with timeout "
+ "at %s in %fs\n",
+ server_id_str_buf(self, &tmp),
+ file_id_str_buf(fsp->file_id, &idbuf),
+ timeval_string(talloc_tos(), &disconnect_time, true),
+ timeval_string(talloc_tos(), &until, true),
+ fsp->op->global->durable_timeout_msec/1000.0));
+
+ SMB_ASSERT(server_id_is_disconnected(&fsp->op->global->server_id));
+ SMB_ASSERT(!server_id_equal(&self, &smbd_scavenger_state->parent_id));
+ SMB_ASSERT(!smbd_scavenger_state->am_scavenger);
+
+ msg_blob = data_blob_const(&msg, sizeof(msg));
+ DEBUG(10, ("send message to scavenger\n"));
+
+ status = messaging_send(smbd_scavenger_state->msg,
+ smbd_scavenger_state->parent_id,
+ MSG_SMB_SCAVENGER,
+ &msg_blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ struct server_id_buf tmp1, tmp2;
+ DEBUG(2, ("Failed to send message to parent smbd %s "
+ "from %s: %s\n",
+ server_id_str_buf(smbd_scavenger_state->parent_id,
+ &tmp1),
+ server_id_str_buf(self, &tmp2),
+ nt_errstr(status)));
+ }
+}
+
+struct scavenger_timer_context {
+ struct smbd_scavenger_state *state;
+ struct scavenger_message msg;
+};
+
+struct cleanup_disconnected_state {
+ struct file_id fid;
+ struct share_mode_lock *lck;
+ uint64_t open_persistent_id;
+ size_t num_disconnected;
+ bool found_connected;
+};
+
+static bool cleanup_disconnected_lease(struct share_mode_entry *e,
+ void *private_data)
+{
+ struct cleanup_disconnected_state *state = private_data;
+ NTSTATUS status;
+
+ status = leases_db_del(&e->client_guid, &e->lease_key, &state->fid);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("leases_db_del failed: %s\n",
+ nt_errstr(status));
+ }
+
+ return false;
+}
+
+static bool share_mode_find_connected_fn(
+ struct share_mode_entry *e,
+ bool *modified,
+ void *private_data)
+{
+ struct cleanup_disconnected_state *state = private_data;
+ bool disconnected;
+
+ disconnected = server_id_is_disconnected(&e->pid);
+ if (!disconnected) {
+ char *name = share_mode_filename(talloc_tos(), state->lck);
+ struct file_id_buf tmp1;
+ struct server_id_buf tmp2;
+ DBG_INFO("file (file-id='%s', servicepath='%s', name='%s') "
+ "is used by server %s ==> do not cleanup\n",
+ file_id_str_buf(state->fid, &tmp1),
+ share_mode_servicepath(state->lck),
+ name,
+ server_id_str_buf(e->pid, &tmp2));
+ TALLOC_FREE(name);
+ state->found_connected = true;
+ return true;
+ }
+
+ if (state->open_persistent_id != e->share_file_id) {
+ char *name = share_mode_filename(talloc_tos(), state->lck);
+ struct file_id_buf tmp;
+ DBG_INFO("entry for file "
+ "(file-id='%s', servicepath='%s', name='%s') "
+ "has share_file_id %"PRIu64" but expected "
+ "%"PRIu64"==> do not cleanup\n",
+ file_id_str_buf(state->fid, &tmp),
+ share_mode_servicepath(state->lck),
+ name,
+ e->share_file_id,
+ state->open_persistent_id);
+ TALLOC_FREE(name);
+ state->found_connected = true;
+ return true;
+ }
+
+ state->num_disconnected += 1;
+
+ return false;
+}
+
+static bool cleanup_disconnected_share_mode_entry_fn(
+ struct share_mode_entry *e,
+ bool *modified,
+ void *private_data)
+{
+ struct cleanup_disconnected_state *state = private_data;
+
+ bool disconnected;
+
+ disconnected = server_id_is_disconnected(&e->pid);
+ if (!disconnected) {
+ char *name = share_mode_filename(talloc_tos(), state->lck);
+ struct file_id_buf tmp1;
+ struct server_id_buf tmp2;
+ DBG_ERR("file (file-id='%s', servicepath='%s', name='%s') "
+ "is used by server %s ==> internal error\n",
+ file_id_str_buf(state->fid, &tmp1),
+ share_mode_servicepath(state->lck),
+ name,
+ server_id_str_buf(e->pid, &tmp2));
+ TALLOC_FREE(name);
+ smb_panic(__location__);
+ }
+
+ /*
+ * Setting e->stale = true is
+ * the indication to delete the entry.
+ */
+ e->stale = true;
+ return false;
+}
+
+static bool share_mode_cleanup_disconnected(
+ struct file_id fid, uint64_t open_persistent_id)
+{
+ struct cleanup_disconnected_state state = {
+ .fid = fid,
+ .open_persistent_id = open_persistent_id
+ };
+ bool ret = false;
+ TALLOC_CTX *frame = talloc_stackframe();
+ char *name = NULL;
+ struct file_id_buf idbuf;
+ bool ok;
+
+ state.lck = get_existing_share_mode_lock(frame, fid);
+ if (state.lck == NULL) {
+ DBG_INFO("Could not fetch share mode entry for %s\n",
+ file_id_str_buf(fid, &idbuf));
+ goto done;
+ }
+ name = share_mode_filename(frame, state.lck);
+
+ ok = share_mode_forall_entries(
+ state.lck, share_mode_find_connected_fn, &state);
+ if (!ok) {
+ DBG_DEBUG("share_mode_forall_entries failed\n");
+ goto done;
+ }
+ if (state.found_connected) {
+ DBG_DEBUG("Found connected entry\n");
+ goto done;
+ }
+
+ ok = share_mode_forall_leases(
+ state.lck, cleanup_disconnected_lease, &state);
+ if (!ok) {
+ DBG_DEBUG("failed to clean up leases associated "
+ "with file (file-id='%s', servicepath='%s', "
+ "name='%s') and open_persistent_id %"PRIu64" "
+ "==> do not cleanup\n",
+ file_id_str_buf(fid, &idbuf),
+ share_mode_servicepath(state.lck),
+ name,
+ open_persistent_id);
+ goto done;
+ }
+
+ ok = brl_cleanup_disconnected(fid, open_persistent_id);
+ if (!ok) {
+ DBG_DEBUG("failed to clean up byte range locks associated "
+ "with file (file-id='%s', servicepath='%s', "
+ "name='%s') and open_persistent_id %"PRIu64" "
+ "==> do not cleanup\n",
+ file_id_str_buf(fid, &idbuf),
+ share_mode_servicepath(state.lck),
+ name,
+ open_persistent_id);
+ goto done;
+ }
+
+ DBG_DEBUG("cleaning up %zu entries for file "
+ "(file-id='%s', servicepath='%s', name='%s') "
+ "from open_persistent_id %"PRIu64"\n",
+ state.num_disconnected,
+ file_id_str_buf(fid, &idbuf),
+ share_mode_servicepath(state.lck),
+ name,
+ open_persistent_id);
+
+ ok = share_mode_forall_entries(
+ state.lck, cleanup_disconnected_share_mode_entry_fn, &state);
+ if (!ok) {
+ DBG_DEBUG("failed to clean up %zu entries associated "
+ "with file (file-id='%s', servicepath='%s', "
+ "name='%s') and open_persistent_id %"PRIu64" "
+ "==> do not cleanup\n",
+ state.num_disconnected,
+ file_id_str_buf(fid, &idbuf),
+ share_mode_servicepath(state.lck),
+ name,
+ open_persistent_id);
+ goto done;
+ }
+
+ ret = true;
+done:
+ talloc_free(frame);
+ return ret;
+}
+
+static void scavenger_timer(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval t, void *data)
+{
+ struct scavenger_timer_context *ctx =
+ talloc_get_type_abort(data, struct scavenger_timer_context);
+ struct file_id_buf idbuf;
+ NTSTATUS status;
+ bool ok;
+
+ DBG_DEBUG("do cleanup for file %s at %s\n",
+ file_id_str_buf(ctx->msg.file_id, &idbuf),
+ timeval_string(talloc_tos(), &t, true));
+
+ ok = share_mode_cleanup_disconnected(ctx->msg.file_id,
+ ctx->msg.open_persistent_id);
+ if (!ok) {
+ DBG_WARNING("Failed to cleanup share modes and byte range "
+ "locks for file %s open %"PRIu64"\n",
+ file_id_str_buf(ctx->msg.file_id, &idbuf),
+ ctx->msg.open_persistent_id);
+ }
+
+ status = smbXsrv_open_cleanup(ctx->msg.open_persistent_id);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("Failed to cleanup open global for file %s open "
+ "%"PRIu64": %s\n",
+ file_id_str_buf(ctx->msg.file_id, &idbuf),
+ ctx->msg.open_persistent_id,
+ nt_errstr(status));
+ }
+}
+
+static void scavenger_add_timer(struct smbd_scavenger_state *state,
+ struct scavenger_message *msg)
+{
+ struct tevent_timer *te;
+ struct scavenger_timer_context *ctx;
+ struct timeval until;
+ struct file_id_buf idbuf;
+
+ nttime_to_timeval(&until, msg->until);
+
+ DBG_DEBUG("schedule file %s for cleanup at %s\n",
+ file_id_str_buf(msg->file_id, &idbuf),
+ timeval_string(talloc_tos(), &until, true));
+
+ ctx = talloc_zero(state, struct scavenger_timer_context);
+ if (ctx == NULL) {
+ DEBUG(2, ("Failed to talloc_zero(scavenger_timer_context)\n"));
+ return;
+ }
+
+ ctx->state = state;
+ ctx->msg = *msg;
+
+ te = tevent_add_timer(state->ev,
+ state,
+ until,
+ scavenger_timer,
+ ctx);
+ if (te == NULL) {
+ DEBUG(2, ("Failed to add scavenger_timer event\n"));
+ talloc_free(ctx);
+ return;
+ }
+
+ /* delete context after handler was running */
+ talloc_steal(te, ctx);
+}
diff --git a/source3/smbd/scavenger.h b/source3/smbd/scavenger.h
new file mode 100644
index 0000000..966c80d
--- /dev/null
+++ b/source3/smbd/scavenger.h
@@ -0,0 +1,31 @@
+/*
+ Unix SMB/CIFS implementation.
+ smbd scavenger daemon
+
+ Copyright (C) Gregor Beck 2013
+
+ 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/>.
+*/
+
+#ifndef _SCAVENGER_H_
+#define _SCAVENGER_H_
+
+
+bool smbd_scavenger_init(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg,
+ struct tevent_context *ev);
+
+void scavenger_schedule_disconnected(struct files_struct *fsp);
+
+#endif
diff --git a/source3/smbd/seal.c b/source3/smbd/seal.c
new file mode 100644
index 0000000..8a0dbeb
--- /dev/null
+++ b/source3/smbd/seal.c
@@ -0,0 +1,313 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB Transport encryption (sealing) code - server code.
+ Copyright (C) Jeremy Allison 2007.
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_seal.h"
+#include "auth.h"
+#include "libsmb/libsmb.h"
+#include "../lib/tsocket/tsocket.h"
+#include "auth/gensec/gensec.h"
+
+/******************************************************************************
+ Server side encryption.
+******************************************************************************/
+
+/******************************************************************************
+ Return global enc context - this must change if we ever do multiple contexts.
+******************************************************************************/
+
+static uint16_t srv_enc_ctx(const struct smb_trans_enc_state *es)
+{
+ return es->enc_ctx_num;
+}
+
+/******************************************************************************
+ Is this an incoming encrypted packet ?
+******************************************************************************/
+
+bool is_encrypted_packet(const uint8_t *inbuf)
+{
+ NTSTATUS status;
+ uint16_t enc_num;
+
+ /* Ignore non-session messages or non 0xFF'E' messages. */
+ if(CVAL(inbuf,0)
+ || (smb_len(inbuf) < 8)
+ || !(inbuf[4] == 0xFF && inbuf[5] == 'E')) {
+ return false;
+ }
+
+ status = get_enc_ctx_num(inbuf, &enc_num);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ /* Encrypted messages are 0xFF'E'<ctx> */
+ if (srv_trans_enc_ctx && enc_num == srv_enc_ctx(srv_trans_enc_ctx)) {
+ return true;
+ }
+ return false;
+}
+
+/******************************************************************************
+ Create an gensec_security and ensure pointer copy is correct.
+******************************************************************************/
+
+static NTSTATUS make_auth_gensec(const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ struct smb_trans_enc_state *es)
+{
+ NTSTATUS status;
+
+ status = auth_generic_prepare(es, remote_address,
+ local_address,
+ "SMB encryption",
+ &es->gensec_security);
+ if (!NT_STATUS_IS_OK(status)) {
+ return nt_status_squash(status);
+ }
+
+ gensec_want_feature(es->gensec_security, GENSEC_FEATURE_SEAL);
+
+ /*
+ * We could be accessing the secrets.tdb or krb5.keytab file here.
+ * ensure we have permissions to do so.
+ */
+ become_root();
+
+ status = gensec_start_mech_by_oid(es->gensec_security, GENSEC_OID_SPNEGO);
+
+ unbecome_root();
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return nt_status_squash(status);
+ }
+
+ return status;
+}
+
+/******************************************************************************
+ Create a server encryption context.
+******************************************************************************/
+
+static NTSTATUS make_srv_encryption_context(const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ struct smb_trans_enc_state **pp_es)
+{
+ NTSTATUS status;
+ struct smb_trans_enc_state *es;
+
+ *pp_es = NULL;
+
+ ZERO_STRUCTP(partial_srv_trans_enc_ctx);
+ es = talloc_zero(NULL, struct smb_trans_enc_state);
+ if (!es) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ status = make_auth_gensec(remote_address,
+ local_address,
+ es);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(es);
+ return status;
+ }
+ *pp_es = es;
+ return NT_STATUS_OK;
+}
+
+/******************************************************************************
+ Free an encryption-allocated buffer.
+******************************************************************************/
+
+void srv_free_enc_buffer(struct smbXsrv_connection *xconn, char *buf)
+{
+ /* We know this is an smb buffer, and we
+ * didn't malloc, only copy, for a keepalive,
+ * so ignore non-session messages. */
+
+ if(CVAL(buf,0)) {
+ return;
+ }
+
+ if (srv_trans_enc_ctx) {
+ common_free_enc_buffer(srv_trans_enc_ctx, buf);
+ }
+}
+
+/******************************************************************************
+ Decrypt an incoming buffer.
+******************************************************************************/
+
+NTSTATUS srv_decrypt_buffer(struct smbXsrv_connection *xconn, char *buf)
+{
+ /* Ignore non-session messages. */
+ if(CVAL(buf,0)) {
+ return NT_STATUS_OK;
+ }
+
+ if (srv_trans_enc_ctx) {
+ return common_decrypt_buffer(srv_trans_enc_ctx, buf);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/******************************************************************************
+ Encrypt an outgoing buffer. Return the encrypted pointer in buf_out.
+******************************************************************************/
+
+NTSTATUS srv_encrypt_buffer(struct smbXsrv_connection *xconn, char *buf,
+ char **buf_out)
+{
+ *buf_out = buf;
+
+ /* Ignore non-session messages. */
+ if(CVAL(buf,0)) {
+ return NT_STATUS_OK;
+ }
+
+ if (srv_trans_enc_ctx) {
+ return common_encrypt_buffer(srv_trans_enc_ctx, buf, buf_out);
+ }
+ /* Not encrypting. */
+ return NT_STATUS_OK;
+}
+
+/******************************************************************************
+ Do the SPNEGO encryption negotiation. Parameters are in/out.
+******************************************************************************/
+
+NTSTATUS srv_request_encryption_setup(connection_struct *conn,
+ unsigned char **ppdata,
+ size_t *p_data_size,
+ unsigned char **pparam,
+ size_t *p_param_size)
+{
+ NTSTATUS status;
+ DATA_BLOB blob = data_blob_const(*ppdata, *p_data_size);
+ DATA_BLOB response = data_blob_null;
+ struct smb_trans_enc_state *es;
+
+ SAFE_FREE(*pparam);
+ *p_param_size = 0;
+
+ if (!partial_srv_trans_enc_ctx) {
+ /* This is the initial step. */
+ status = make_srv_encryption_context(conn->sconn->remote_address,
+ conn->sconn->local_address,
+ &partial_srv_trans_enc_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ es = partial_srv_trans_enc_ctx;
+ if (!es || es->gensec_security == NULL) {
+ TALLOC_FREE(partial_srv_trans_enc_ctx);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* Second step. */
+ become_root();
+ status = gensec_update(es->gensec_security,
+ talloc_tos(),
+ blob, &response);
+ unbecome_root();
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED) &&
+ !NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(partial_srv_trans_enc_ctx);
+ return nt_status_squash(status);
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ /* Return the context we're using for this encryption state. */
+ if (!(*pparam = SMB_MALLOC_ARRAY(unsigned char, 2))) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ SSVAL(*pparam, 0, es->enc_ctx_num);
+ *p_param_size = 2;
+ }
+
+ /* Return the raw blob. */
+ SAFE_FREE(*ppdata);
+ *ppdata = (unsigned char *)smb_memdup(response.data, response.length);
+ if ((*ppdata) == NULL && response.length > 0)
+ return NT_STATUS_NO_MEMORY;
+ *p_data_size = response.length;
+ data_blob_free(&response);
+ return status;
+}
+
+/******************************************************************************
+ Negotiation was successful - turn on server-side encryption.
+******************************************************************************/
+
+static NTSTATUS check_enc_good(struct smb_trans_enc_state *es)
+{
+ if (!es) {
+ return NT_STATUS_LOGON_FAILURE;
+ }
+
+ if (!gensec_have_feature(es->gensec_security, GENSEC_FEATURE_SIGN)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!gensec_have_feature(es->gensec_security, GENSEC_FEATURE_SEAL)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ return NT_STATUS_OK;
+}
+
+/******************************************************************************
+ Negotiation was successful - turn on server-side encryption.
+******************************************************************************/
+
+NTSTATUS srv_encryption_start(connection_struct *conn)
+{
+ NTSTATUS status;
+
+ /* Check that we are really doing sign+seal. */
+ status = check_enc_good(partial_srv_trans_enc_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ /* Throw away the context we're using currently (if any). */
+ TALLOC_FREE(srv_trans_enc_ctx);
+
+ /* Steal the partial pointer. Deliberate shallow copy. */
+ srv_trans_enc_ctx = partial_srv_trans_enc_ctx;
+ srv_trans_enc_ctx->enc_on = true;
+
+ partial_srv_trans_enc_ctx = NULL;
+
+ DEBUG(1,("srv_encryption_start: context negotiated\n"));
+ return NT_STATUS_OK;
+}
+
+/******************************************************************************
+ Shutdown all server contexts.
+******************************************************************************/
+
+void server_encryption_shutdown(struct smbXsrv_connection *xconn)
+{
+ TALLOC_FREE(partial_srv_trans_enc_ctx);
+ TALLOC_FREE(srv_trans_enc_ctx);
+}
diff --git a/source3/smbd/sec_ctx.c b/source3/smbd/sec_ctx.c
new file mode 100644
index 0000000..dc06acd
--- /dev/null
+++ b/source3/smbd/sec_ctx.c
@@ -0,0 +1,513 @@
+/*
+ Unix SMB/CIFS implementation.
+ uid/user handling
+ Copyright (C) Tim Potter 2000
+
+ 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 "system/passwd.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "libcli/security/security_token.h"
+#include "auth.h"
+#include "smbprofile.h"
+#include "../lib/util/setid.h"
+
+extern struct current_user current_user;
+
+/****************************************************************************
+ Are two UNIX tokens equal ?
+****************************************************************************/
+
+bool unix_token_equal(const struct security_unix_token *t1, const struct security_unix_token *t2)
+{
+ if (t1->uid != t2->uid || t1->gid != t2->gid ||
+ t1->ngroups != t2->ngroups) {
+ return false;
+ }
+ if (memcmp(t1->groups, t2->groups,
+ t1->ngroups*sizeof(gid_t)) != 0) {
+ return false;
+ }
+ return true;
+}
+
+/****************************************************************************
+ Become the specified uid.
+****************************************************************************/
+
+static bool become_uid(uid_t uid)
+{
+ /* Check for dodgy uid values */
+
+ if (uid == (uid_t)-1 ||
+ ((sizeof(uid_t) == 2) && (uid == (uid_t)65535))) {
+ if (!become_uid_done) {
+ DEBUG(1,("WARNING: using uid %d is a security risk\n",
+ (int)uid));
+ become_uid_done = true;
+ }
+ }
+
+ /* Set effective user id */
+
+ set_effective_uid(uid);
+
+ return True;
+}
+
+/****************************************************************************
+ Become the specified gid.
+****************************************************************************/
+
+static bool become_gid(gid_t gid)
+{
+ /* Check for dodgy gid values */
+
+ if (gid == (gid_t)-1 || ((sizeof(gid_t) == 2) &&
+ (gid == (gid_t)65535))) {
+ if (!become_gid_done) {
+ DEBUG(1,("WARNING: using gid %d is a security risk\n",
+ (int)gid));
+ become_gid_done = true;
+ }
+ }
+
+ /* Set effective group id */
+
+ set_effective_gid(gid);
+ return True;
+}
+
+/****************************************************************************
+ Drop back to root privileges in order to change to another user.
+****************************************************************************/
+
+static void gain_root(void)
+{
+ if (non_root_mode()) {
+ return;
+ }
+
+ if (geteuid() != 0) {
+ set_effective_uid(0);
+
+ if (geteuid() != 0) {
+ DEBUG(0,
+ ("Warning: You appear to have a trapdoor "
+ "uid system\n"));
+ }
+ }
+
+ if (getegid() != 0) {
+ set_effective_gid(0);
+
+ if (getegid() != 0) {
+ DEBUG(0,
+ ("Warning: You appear to have a trapdoor "
+ "gid system\n"));
+ }
+ }
+}
+
+/****************************************************************************
+ Get the list of current groups.
+****************************************************************************/
+
+static int get_current_groups(gid_t gid, uint32_t *p_ngroups, gid_t **p_groups)
+{
+ int i;
+ int ngroups;
+ gid_t *groups = NULL;
+
+ (*p_ngroups) = 0;
+ (*p_groups) = NULL;
+
+ /* this looks a little strange, but is needed to cope with
+ systems that put the current egid in the group list
+ returned from getgroups() (tridge) */
+ save_re_gid();
+ set_effective_gid(gid);
+ samba_setgid(gid);
+
+ ngroups = sys_getgroups(0, NULL);
+ if (ngroups <= 0) {
+ goto fail;
+ }
+
+ if((groups = SMB_MALLOC_ARRAY(gid_t, ngroups+1)) == NULL) {
+ DEBUG(0,("setup_groups malloc fail !\n"));
+ goto fail;
+ }
+
+ if ((ngroups = sys_getgroups(ngroups,groups)) == -1) {
+ goto fail;
+ }
+
+ restore_re_gid();
+
+ (*p_ngroups) = ngroups;
+ (*p_groups) = groups;
+
+ DEBUG( 4, ( "get_current_groups: user is in %u groups: ", ngroups));
+ for (i = 0; i < ngroups; i++ ) {
+ DEBUG( 4, ( "%s%d", (i ? ", " : ""), (int)groups[i] ) );
+ }
+ DEBUG( 4, ( "\n" ) );
+
+ return ngroups;
+
+fail:
+ SAFE_FREE(groups);
+ restore_re_gid();
+ return -1;
+}
+
+/****************************************************************************
+ Create a new security context on the stack. It is the same as the old
+ one. User changes are done using the set_sec_ctx() function.
+****************************************************************************/
+
+bool push_sec_ctx(void)
+{
+ struct sec_ctx *ctx_p;
+
+ START_PROFILE(push_sec_ctx);
+
+ /* Check we don't overflow our stack */
+
+ if (sec_ctx_stack_ndx == MAX_SEC_CTX_DEPTH) {
+ DEBUG(0, ("Security context stack overflow!\n"));
+ smb_panic("Security context stack overflow!");
+ }
+
+ /* Store previous user context */
+
+ sec_ctx_stack_ndx++;
+
+ ctx_p = &sec_ctx_stack[sec_ctx_stack_ndx];
+
+ ctx_p->ut.uid = geteuid();
+ ctx_p->ut.gid = getegid();
+
+ DEBUG(4, ("push_sec_ctx(%u, %u) : sec_ctx_stack_ndx = %d\n",
+ (unsigned int)ctx_p->ut.uid, (unsigned int)ctx_p->ut.gid, sec_ctx_stack_ndx ));
+
+ ctx_p->token = security_token_duplicate(NULL,
+ sec_ctx_stack[sec_ctx_stack_ndx-1].token);
+
+ ctx_p->ut.ngroups = sys_getgroups(0, NULL);
+
+ if (ctx_p->ut.ngroups != 0) {
+ if (!(ctx_p->ut.groups = SMB_MALLOC_ARRAY(gid_t, ctx_p->ut.ngroups))) {
+ DEBUG(0, ("Out of memory in push_sec_ctx()\n"));
+ TALLOC_FREE(ctx_p->token);
+ return False;
+ }
+
+ sys_getgroups(ctx_p->ut.ngroups, ctx_p->ut.groups);
+ } else {
+ ctx_p->ut.groups = NULL;
+ }
+
+ END_PROFILE(push_sec_ctx);
+
+ return True;
+}
+
+#ifndef HAVE_DARWIN_INITGROUPS
+/****************************************************************************
+ Become the specified uid and gid.
+****************************************************************************/
+
+static bool become_id(uid_t uid, gid_t gid)
+{
+ return become_gid(gid) && become_uid(uid);
+}
+
+/****************************************************************************
+ Change UNIX security context. Calls panic if not successful so no return value.
+****************************************************************************/
+/* Normal credential switch path. */
+
+static void set_unix_security_ctx(uid_t uid, gid_t gid, int ngroups, gid_t *groups)
+{
+ /* Start context switch */
+ gain_root();
+#ifdef HAVE_SETGROUPS
+ if (sys_setgroups(gid, ngroups, groups) != 0 && !non_root_mode()) {
+ smb_panic("sys_setgroups failed");
+ }
+#endif
+ become_id(uid, gid);
+ /* end context switch */
+}
+
+#else /* HAVE_DARWIN_INITGROUPS */
+
+/* The Darwin groups implementation is a little unusual. The list of
+* groups in the kernel credential is not exhaustive, but more like
+* a cache. The full group list is held in userspace and checked
+* dynamically.
+*
+* This is an optional mechanism, and setgroups(2) opts out
+* of it. That is, if you call setgroups, then the list of groups you
+* set are the only groups that are ever checked. This is not what we
+* want. We want to opt in to the dynamic resolution mechanism, so we
+* need to specify the uid of the user whose group list (cache) we are
+* setting.
+*
+* The Darwin rules are:
+* 1. Thou shalt setegid, initgroups and seteuid IN THAT ORDER
+* 2. Thou shalt not pass more that NGROUPS_MAX to initgroups
+* 3. Thou shalt leave the first entry in the groups list well alone
+*/
+
+#include <sys/syscall.h>
+
+static void set_unix_security_ctx(uid_t uid, gid_t gid, int ngroups, gid_t *groups)
+{
+ int max = NGROUPS_MAX;
+
+ /* Start context switch */
+ gain_root();
+
+ become_gid(gid);
+
+
+ if (syscall(SYS_initgroups, (ngroups > max) ? max : ngroups,
+ groups, uid) == -1 && !non_root_mode()) {
+ DEBUG(0, ("WARNING: failed to set group list "
+ "(%d groups) for UID %d: %s\n",
+ ngroups, uid, strerror(errno)));
+ smb_panic("sys_setgroups failed");
+ }
+
+ become_uid(uid);
+ /* end context switch */
+}
+
+#endif /* HAVE_DARWIN_INITGROUPS */
+
+/****************************************************************************
+ Set the current security context to a given user.
+****************************************************************************/
+
+static void set_sec_ctx_internal(uid_t uid, gid_t gid,
+ int ngroups, gid_t *groups,
+ const struct security_token *token)
+{
+ struct sec_ctx *ctx_p = &sec_ctx_stack[sec_ctx_stack_ndx];
+
+ /* Set the security context */
+
+ DEBUG(4, ("setting sec ctx (%u, %u) - sec_ctx_stack_ndx = %d\n",
+ (unsigned int)uid, (unsigned int)gid, sec_ctx_stack_ndx));
+
+ security_token_debug(DBGC_CLASS, 5, token);
+ debug_unix_user_token(DBGC_CLASS, 5, uid, gid, ngroups, groups);
+
+ /* Change uid, gid and supplementary group list. */
+ set_unix_security_ctx(uid, gid, ngroups, groups);
+
+ ctx_p->ut.ngroups = ngroups;
+
+ SAFE_FREE(ctx_p->ut.groups);
+ if (token && (token == ctx_p->token)) {
+ smb_panic("DUPLICATE_TOKEN");
+ }
+
+ TALLOC_FREE(ctx_p->token);
+
+ if (ngroups) {
+ ctx_p->ut.groups = (gid_t *)smb_xmemdup(groups,
+ sizeof(gid_t) * ngroups);
+ } else {
+ ctx_p->ut.groups = NULL;
+ }
+
+ if (token) {
+ ctx_p->token = security_token_duplicate(NULL, token);
+ if (!ctx_p->token) {
+ smb_panic("security_token_duplicate failed");
+ }
+ } else {
+ ctx_p->token = NULL;
+ }
+
+ ctx_p->ut.uid = uid;
+ ctx_p->ut.gid = gid;
+
+ /* Update current_user stuff */
+
+ current_user.ut.uid = uid;
+ current_user.ut.gid = gid;
+ current_user.ut.ngroups = ngroups;
+ current_user.ut.groups = groups;
+ current_user.nt_user_token = ctx_p->token;
+
+ /*
+ * Delete any ChDir cache. We can't assume
+ * the new uid has access to current working
+ * directory.
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=14682
+ */
+ SAFE_FREE(LastDir);
+}
+
+void set_sec_ctx(uid_t uid, gid_t gid, int ngroups, gid_t *groups, const struct security_token *token)
+{
+ START_PROFILE(set_sec_ctx);
+ set_sec_ctx_internal(uid, gid, ngroups, groups, token);
+ END_PROFILE(set_sec_ctx);
+}
+
+/****************************************************************************
+ Become root context.
+****************************************************************************/
+
+void set_root_sec_ctx(void)
+{
+ /* May need to worry about supplementary groups at some stage */
+
+ START_PROFILE(set_root_sec_ctx);
+ set_sec_ctx_internal(0, 0, 0, NULL, NULL);
+ END_PROFILE(set_root_sec_ctx);
+}
+
+/****************************************************************************
+ Pop a security context from the stack.
+****************************************************************************/
+
+bool pop_sec_ctx(void)
+{
+ struct sec_ctx *ctx_p;
+ struct sec_ctx *prev_ctx_p;
+
+ START_PROFILE(pop_sec_ctx);
+
+ /* Check for stack underflow */
+
+ if (sec_ctx_stack_ndx == 0) {
+ DEBUG(0, ("Security context stack underflow!\n"));
+ smb_panic("Security context stack underflow!");
+ }
+
+ ctx_p = &sec_ctx_stack[sec_ctx_stack_ndx];
+
+ /* Clear previous user info */
+
+ ctx_p->ut.uid = (uid_t)-1;
+ ctx_p->ut.gid = (gid_t)-1;
+
+ SAFE_FREE(ctx_p->ut.groups);
+ ctx_p->ut.ngroups = 0;
+
+ TALLOC_FREE(ctx_p->token);
+
+ /* Pop back previous user */
+
+ sec_ctx_stack_ndx--;
+
+ prev_ctx_p = &sec_ctx_stack[sec_ctx_stack_ndx];
+
+ /* Change uid, gid and supplementary group list. */
+ set_unix_security_ctx(prev_ctx_p->ut.uid,
+ prev_ctx_p->ut.gid,
+ prev_ctx_p->ut.ngroups,
+ prev_ctx_p->ut.groups);
+
+ /* Update current_user stuff */
+
+ current_user.ut.uid = prev_ctx_p->ut.uid;
+ current_user.ut.gid = prev_ctx_p->ut.gid;
+ current_user.ut.ngroups = prev_ctx_p->ut.ngroups;
+ current_user.ut.groups = prev_ctx_p->ut.groups;
+ current_user.nt_user_token = prev_ctx_p->token;
+
+ END_PROFILE(pop_sec_ctx);
+
+ DEBUG(4, ("pop_sec_ctx (%u, %u) - sec_ctx_stack_ndx = %d\n",
+ (unsigned int)geteuid(), (unsigned int)getegid(), sec_ctx_stack_ndx));
+
+ return True;
+}
+
+/* Initialise the security context system */
+
+void init_sec_ctx(void)
+{
+ int i;
+ struct sec_ctx *ctx_p;
+
+ /* Initialise security context stack */
+
+ memset(sec_ctx_stack, 0, sizeof(struct sec_ctx) * MAX_SEC_CTX_DEPTH);
+
+ for (i = 0; i < MAX_SEC_CTX_DEPTH; i++) {
+ sec_ctx_stack[i].ut.uid = (uid_t)-1;
+ sec_ctx_stack[i].ut.gid = (gid_t)-1;
+ }
+
+ /* Initialise first level of stack. It is the current context */
+ ctx_p = &sec_ctx_stack[0];
+
+ ctx_p->ut.uid = geteuid();
+ ctx_p->ut.gid = getegid();
+
+ get_current_groups(ctx_p->ut.gid, &ctx_p->ut.ngroups, &ctx_p->ut.groups);
+
+ ctx_p->token = NULL; /* Maps to guest user. */
+
+ /* Initialise current_user global */
+
+ current_user.ut.uid = ctx_p->ut.uid;
+ current_user.ut.gid = ctx_p->ut.gid;
+ current_user.ut.ngroups = ctx_p->ut.ngroups;
+ current_user.ut.groups = ctx_p->ut.groups;
+
+ /* The conn and vuid are usually taken care of by other modules.
+ We initialise them here. */
+
+ current_user.conn = NULL;
+ current_user.vuid = UID_FIELD_INVALID;
+ current_user.nt_user_token = NULL;
+}
+
+/*************************************************************
+ Called when we're inside a become_root() temporary escalation
+ of privileges and the nt_user_token is NULL. Return the last
+ active token on the context stack. We know there is at least
+ one valid non-NULL token on the stack so panic if we underflow.
+*************************************************************/
+
+const struct security_token *sec_ctx_active_token(void)
+{
+ int stack_index = sec_ctx_stack_ndx;
+ struct sec_ctx *ctx_p = &sec_ctx_stack[stack_index];
+
+ while (ctx_p->token == NULL) {
+ stack_index--;
+ if (stack_index < 0) {
+ DEBUG(0, ("Security context active token "
+ "stack underflow!\n"));
+ smb_panic("Security context active token "
+ "stack underflow!");
+ }
+ ctx_p = &sec_ctx_stack[stack_index];
+ }
+ return ctx_p->token;
+}
diff --git a/source3/smbd/server.c b/source3/smbd/server.c
new file mode 100644
index 0000000..42abfa9
--- /dev/null
+++ b/source3/smbd/server.c
@@ -0,0 +1,2136 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main SMB server routines
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Martin Pool 2002
+ Copyright (C) Jelmer Vernooij 2002-2003
+ Copyright (C) Volker Lendecke 1993-2007
+ Copyright (C) Jeremy Allison 1993-2007
+
+ 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 "system/filesys.h"
+#include "lib/util/server_id.h"
+#include "lib/util/close_low_fd.h"
+#include "lib/cmdline/cmdline.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "smbd/smbXsrv_open.h"
+#include "registry/reg_init_full.h"
+#include "libcli/auth/schannel.h"
+#include "secrets.h"
+#include "../lib/util/memcache.h"
+#include "ctdbd_conn.h"
+#include "lib/util/util_process.h"
+#include "util_cluster.h"
+#include "printing/queue_process.h"
+#include "rpc_server/rpc_config.h"
+#include "passdb.h"
+#include "auth.h"
+#include "messages.h"
+#include "messages_ctdb.h"
+#include "smbprofile.h"
+#include "lib/id_cache.h"
+#include "lib/param/param.h"
+#include "lib/background.h"
+#include "../lib/util/pidfile.h"
+#include "lib/smbd_shim.h"
+#include "scavenger.h"
+#include "locking/leases_db.h"
+#include "smbd/notifyd/notifyd.h"
+#include "smbd/smbd_cleanupd.h"
+#include "lib/util/sys_rw.h"
+#include "cleanupdb.h"
+#include "g_lock.h"
+#include "lib/global_contexts.h"
+#include "source3/lib/substitute.h"
+
+#ifdef CLUSTER_SUPPORT
+#include "ctdb_protocol.h"
+#endif
+
+struct smbd_open_socket;
+struct smbd_child_pid;
+
+struct smbd_parent_context {
+ bool interactive;
+
+ struct tevent_context *ev_ctx;
+ struct messaging_context *msg_ctx;
+
+ /* the list of listening sockets */
+ struct smbd_open_socket *sockets;
+
+ /* the list of current child processes */
+ struct smbd_child_pid *children;
+ size_t num_children;
+
+ struct server_id cleanupd;
+ struct server_id notifyd;
+
+ struct tevent_timer *cleanup_te;
+};
+
+struct smbd_open_socket {
+ struct smbd_open_socket *prev, *next;
+ struct smbd_parent_context *parent;
+ int fd;
+ struct tevent_fd *fde;
+};
+
+struct smbd_child_pid {
+ struct smbd_child_pid *prev, *next;
+ pid_t pid;
+};
+
+/*******************************************************************
+ What to do when smb.conf is updated.
+ ********************************************************************/
+
+static NTSTATUS messaging_send_to_children(struct messaging_context *msg_ctx,
+ uint32_t msg_type, DATA_BLOB* data);
+
+static void smbd_parent_conf_updated(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ bool ok;
+
+ DEBUG(10,("smbd_parent_conf_updated: Got message saying smb.conf was "
+ "updated. Reloading.\n"));
+ change_to_root_user();
+ reload_services(NULL, NULL, false);
+
+ ok = reinit_guest_session_info(NULL);
+ if (!ok) {
+ DBG_ERR("Failed to reinit guest info\n");
+ }
+ messaging_send_to_children(msg, MSG_SMB_CONF_UPDATED, NULL);
+}
+
+/****************************************************************************
+ Send a SIGTERM to our process group.
+*****************************************************************************/
+
+static void killkids(void)
+{
+ if(am_parent) kill(0,SIGTERM);
+}
+
+static void msg_exit_server(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ DEBUG(3, ("got a SHUTDOWN message\n"));
+ exit_server_cleanly(NULL);
+}
+
+#ifdef DEVELOPER
+static void msg_inject_fault(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id src,
+ DATA_BLOB *data)
+{
+ int sig;
+ struct server_id_buf tmp;
+
+ if (data->length != sizeof(sig)) {
+ DEBUG(0, ("Process %s sent bogus signal injection request\n",
+ server_id_str_buf(src, &tmp)));
+ return;
+ }
+
+ sig = *(int *)data->data;
+ if (sig == -1) {
+ exit_server("internal error injected");
+ return;
+ }
+
+#ifdef HAVE_STRSIGNAL
+ DEBUG(0, ("Process %s requested injection of signal %d (%s)\n",
+ server_id_str_buf(src, &tmp), sig, strsignal(sig)));
+#else
+ DEBUG(0, ("Process %s requested injection of signal %d\n",
+ server_id_str_buf(src, &tmp), sig));
+#endif
+
+ kill(getpid(), sig);
+}
+#endif /* DEVELOPER */
+
+#if defined(DEVELOPER) || defined(ENABLE_SELFTEST)
+/*
+ * Sleep for the specified number of seconds.
+ */
+static void msg_sleep(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id src,
+ DATA_BLOB *data)
+{
+ unsigned int seconds;
+ struct server_id_buf tmp;
+
+ if (data->length != sizeof(seconds)) {
+ DBG_ERR("Process %s sent bogus sleep request\n",
+ server_id_str_buf(src, &tmp));
+ return;
+ }
+
+ seconds = *(unsigned int *)data->data;
+ DBG_ERR("Process %s request a sleep of %u seconds\n",
+ server_id_str_buf(src, &tmp),
+ seconds);
+ sleep(seconds);
+ DBG_ERR("Restarting after %u second sleep requested by process %s\n",
+ seconds,
+ server_id_str_buf(src, &tmp));
+}
+#endif /* DEVELOPER */
+
+static NTSTATUS messaging_send_to_children(struct messaging_context *msg_ctx,
+ uint32_t msg_type, DATA_BLOB* data)
+{
+ NTSTATUS status;
+ struct smbd_parent_context *parent = am_parent;
+ struct smbd_child_pid *child;
+
+ if (parent == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ for (child = parent->children; child != NULL; child = child->next) {
+ status = messaging_send(parent->msg_ctx,
+ pid_to_procid(child->pid),
+ msg_type, data);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("messaging_send(%d) failed: %s\n",
+ (int)child->pid, nt_errstr(status));
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+static void smb_parent_send_to_children(struct messaging_context *ctx,
+ void* data,
+ uint32_t msg_type,
+ struct server_id srv_id,
+ DATA_BLOB* msg_data)
+{
+ messaging_send_to_children(ctx, msg_type, msg_data);
+}
+
+/*
+ * Parent smbd process sets its own debug level first and then
+ * sends a message to all the smbd children to adjust their debug
+ * level to that of the parent.
+ */
+
+static void smbd_msg_debug(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ debug_message(msg_ctx, private_data, MSG_DEBUG, server_id, data);
+
+ messaging_send_to_children(msg_ctx, MSG_DEBUG, data);
+}
+
+static void smbd_parent_id_cache_kill(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB* data)
+{
+ const char *msg = (data && data->data)
+ ? (const char *)data->data : "<NULL>";
+ struct id_cache_ref id;
+
+ if (!id_cache_ref_parse(msg, &id)) {
+ DEBUG(0, ("Invalid ?ID: %s\n", msg));
+ return;
+ }
+
+ id_cache_delete_from_cache(&id);
+
+ messaging_send_to_children(msg_ctx, msg_type, data);
+}
+
+static void smbd_parent_id_cache_delete(struct messaging_context *ctx,
+ void* data,
+ uint32_t msg_type,
+ struct server_id srv_id,
+ DATA_BLOB* msg_data)
+{
+ id_cache_delete_message(ctx, data, msg_type, srv_id, msg_data);
+
+ messaging_send_to_children(ctx, msg_type, msg_data);
+}
+
+static void add_child_pid(struct smbd_parent_context *parent,
+ pid_t pid)
+{
+ struct smbd_child_pid *child;
+
+ child = talloc_zero(parent, struct smbd_child_pid);
+ if (child == NULL) {
+ DEBUG(0, ("Could not add child struct -- malloc failed\n"));
+ return;
+ }
+ child->pid = pid;
+ DLIST_ADD(parent->children, child);
+ parent->num_children += 1;
+}
+
+static void smb_tell_num_children(struct messaging_context *ctx, void *data,
+ uint32_t msg_type, struct server_id srv_id,
+ DATA_BLOB *msg_data)
+{
+ uint8_t buf[sizeof(uint32_t)];
+
+ if (am_parent) {
+ SIVAL(buf, 0, am_parent->num_children);
+ messaging_send_buf(ctx, srv_id, MSG_SMB_NUM_CHILDREN,
+ buf, sizeof(buf));
+ }
+}
+
+static void notifyd_stopped(struct tevent_req *req);
+
+static struct tevent_req *notifyd_req(struct messaging_context *msg_ctx,
+ struct tevent_context *ev)
+{
+ struct tevent_req *req;
+ sys_notify_watch_fn sys_notify_watch = NULL;
+ struct sys_notify_context *sys_notify_ctx = NULL;
+ struct ctdbd_connection *ctdbd_conn = NULL;
+
+ if (lp_kernel_change_notify()) {
+
+#ifdef HAVE_INOTIFY
+ if (lp_parm_bool(-1, "notify", "inotify", true)) {
+ sys_notify_watch = inotify_watch;
+ }
+#endif
+
+#ifdef HAVE_FAM
+ if (lp_parm_bool(-1, "notify", "fam",
+ (sys_notify_watch == NULL))) {
+ sys_notify_watch = fam_watch;
+ }
+#endif
+ }
+
+ if (sys_notify_watch != NULL) {
+ sys_notify_ctx = sys_notify_context_create(msg_ctx, ev);
+ if (sys_notify_ctx == NULL) {
+ return NULL;
+ }
+ }
+
+ if (lp_clustering()) {
+ ctdbd_conn = messaging_ctdb_connection();
+ }
+
+ req = notifyd_send(msg_ctx, ev, msg_ctx, ctdbd_conn,
+ sys_notify_watch, sys_notify_ctx);
+ if (req == NULL) {
+ TALLOC_FREE(sys_notify_ctx);
+ return NULL;
+ }
+ tevent_req_set_callback(req, notifyd_stopped, msg_ctx);
+
+ return req;
+}
+
+static void notifyd_stopped(struct tevent_req *req)
+{
+ int ret;
+
+ ret = notifyd_recv(req);
+ TALLOC_FREE(req);
+ DEBUG(1, ("notifyd stopped: %s\n", strerror(ret)));
+}
+
+static void notifyd_sig_hup_handler(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *pvt)
+{
+ DBG_NOTICE("notifyd: Reloading services after SIGHUP\n");
+ reload_services(NULL, NULL, false);
+ reopen_logs();
+}
+
+static bool smbd_notifyd_init(struct messaging_context *msg, bool interactive,
+ struct server_id *ppid)
+{
+ struct tevent_context *ev = messaging_tevent_context(msg);
+ struct tevent_req *req;
+ struct tevent_signal *se = NULL;
+ pid_t pid;
+ NTSTATUS status;
+ bool ok;
+
+ if (interactive) {
+ req = notifyd_req(msg, ev);
+ return (req != NULL);
+ }
+
+ pid = fork();
+ if (pid == -1) {
+ DEBUG(1, ("%s: fork failed: %s\n", __func__,
+ strerror(errno)));
+ return false;
+ }
+
+ if (pid != 0) {
+ if (am_parent != NULL) {
+ add_child_pid(am_parent, pid);
+ }
+ *ppid = pid_to_procid(pid);
+ return true;
+ }
+
+ status = smbd_reinit_after_fork(msg, ev, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("%s: reinit_after_fork failed: %s\n",
+ __func__, nt_errstr(status)));
+ exit(1);
+ }
+
+ process_set_title("smbd-notifyd", "notifyd");
+
+ reopen_logs();
+
+ /* Set up sighup handler for notifyd */
+ se = tevent_add_signal(ev,
+ ev,
+ SIGHUP, 0,
+ notifyd_sig_hup_handler,
+ NULL);
+ if (!se) {
+ DEBUG(0, ("failed to setup notifyd SIGHUP handler\n"));
+ exit(1);
+ }
+
+ req = notifyd_req(msg, ev);
+ if (req == NULL) {
+ exit(1);
+ }
+ tevent_req_set_callback(req, notifyd_stopped, msg);
+
+ /* Block those signals that we are not handling */
+ BlockSignals(True, SIGUSR1);
+
+ messaging_send(msg, pid_to_procid(getppid()), MSG_SMB_NOTIFY_STARTED,
+ NULL);
+
+ ok = tevent_req_poll(req, ev);
+ if (!ok) {
+ DBG_WARNING("tevent_req_poll returned %s\n", strerror(errno));
+ exit(1);
+ }
+ exit(0);
+}
+
+static void notifyd_init_trigger(struct tevent_req *req);
+
+struct notifyd_init_state {
+ bool ok;
+ struct tevent_context *ev;
+ struct messaging_context *msg;
+ struct server_id *ppid;
+};
+
+static struct tevent_req *notifyd_init_send(struct tevent_context *ev,
+ TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg,
+ struct server_id *ppid)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct notifyd_init_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct notifyd_init_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ *state = (struct notifyd_init_state) {
+ .msg = msg,
+ .ev = ev,
+ .ppid = ppid
+ };
+
+ subreq = tevent_wakeup_send(state, ev, tevent_timeval_current_ofs(1, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_set_callback(subreq, notifyd_init_trigger, req);
+ return req;
+}
+
+static void notifyd_init_trigger(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct notifyd_init_state *state = tevent_req_data(
+ req, struct notifyd_init_state);
+ bool ok;
+
+ DBG_NOTICE("Triggering notifyd startup\n");
+
+ ok = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ state->ok = smbd_notifyd_init(state->msg, false, state->ppid);
+ if (state->ok) {
+ DBG_WARNING("notifyd restarted\n");
+ tevent_req_done(req);
+ return;
+ }
+
+ DBG_NOTICE("notifyd startup failed, rescheduling\n");
+
+ subreq = tevent_wakeup_send(state, state->ev,
+ tevent_timeval_current_ofs(1, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ DBG_ERR("scheduling notifyd restart failed, giving up\n");
+ return;
+ }
+
+ tevent_req_set_callback(subreq, notifyd_init_trigger, req);
+ return;
+}
+
+static bool notifyd_init_recv(struct tevent_req *req)
+{
+ struct notifyd_init_state *state = tevent_req_data(
+ req, struct notifyd_init_state);
+
+ return state->ok;
+}
+
+static void notifyd_started(struct tevent_req *req)
+{
+ bool ok;
+
+ ok = notifyd_init_recv(req);
+ TALLOC_FREE(req);
+ if (!ok) {
+ DBG_ERR("Failed to restart notifyd, giving up\n");
+ return;
+ }
+}
+
+static void cleanupd_sig_hup_handler(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *pvt)
+{
+ DBG_NOTICE("cleanupd: Reloading services after SIGHUP\n");
+ reopen_logs();
+}
+
+static void cleanupd_stopped(struct tevent_req *req);
+
+static bool cleanupd_init(struct messaging_context *msg, bool interactive,
+ struct server_id *ppid)
+{
+ struct tevent_context *ev = messaging_tevent_context(msg);
+ struct server_id parent_id = messaging_server_id(msg);
+ struct tevent_signal *se = NULL;
+ struct tevent_req *req;
+ pid_t pid;
+ NTSTATUS status;
+ ssize_t rwret;
+ int ret;
+ bool ok;
+ char c;
+ int up_pipe[2];
+
+ if (interactive) {
+ req = smbd_cleanupd_send(msg, ev, msg, parent_id.pid);
+ *ppid = messaging_server_id(msg);
+ return (req != NULL);
+ }
+
+ ret = pipe(up_pipe);
+ if (ret == -1) {
+ DBG_WARNING("pipe failed: %s\n", strerror(errno));
+ return false;
+ }
+
+ pid = fork();
+ if (pid == -1) {
+ DBG_WARNING("fork failed: %s\n", strerror(errno));
+ close(up_pipe[0]);
+ close(up_pipe[1]);
+ return false;
+ }
+
+ if (pid != 0) {
+
+ close(up_pipe[1]);
+ rwret = sys_read(up_pipe[0], &c, 1);
+ close(up_pipe[0]);
+
+ if (rwret == -1) {
+ DBG_WARNING("sys_read failed: %s\n", strerror(errno));
+ return false;
+ }
+ if (rwret == 0) {
+ DBG_WARNING("cleanupd could not start\n");
+ return false;
+ }
+ if (c != 0) {
+ DBG_WARNING("cleanupd returned %d\n", (int)c);
+ return false;
+ }
+
+ DBG_DEBUG("Started cleanupd pid=%d\n", (int)pid);
+
+ if (am_parent != NULL) {
+ add_child_pid(am_parent, pid);
+ }
+
+ *ppid = pid_to_procid(pid);
+ return true;
+ }
+
+ close(up_pipe[0]);
+
+ status = smbd_reinit_after_fork(msg, ev, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("reinit_after_fork failed: %s\n",
+ nt_errstr(status));
+ c = 1;
+ sys_write(up_pipe[1], &c, 1);
+
+ exit(1);
+ }
+
+ process_set_title("smbd-cleanupd", "cleanupd");
+
+ se = tevent_add_signal(ev,
+ ev,
+ SIGHUP,
+ 0,
+ cleanupd_sig_hup_handler,
+ NULL);
+ if (se == NULL) {
+ DBG_ERR("Could not add SIGHUP handler\n");
+ exit(1);
+ }
+
+ req = smbd_cleanupd_send(msg, ev, msg, parent_id.pid);
+ if (req == NULL) {
+ DBG_WARNING("smbd_cleanupd_send failed\n");
+ c = 2;
+ sys_write(up_pipe[1], &c, 1);
+
+ exit(1);
+ }
+
+ tevent_req_set_callback(req, cleanupd_stopped, msg);
+
+ c = 0;
+ rwret = sys_write(up_pipe[1], &c, 1);
+ close(up_pipe[1]);
+
+ if (rwret == -1) {
+ DBG_WARNING("sys_write failed: %s\n", strerror(errno));
+ exit(1);
+ }
+ if (rwret != 1) {
+ DBG_WARNING("sys_write could not write result\n");
+ exit(1);
+ }
+
+ ok = tevent_req_poll(req, ev);
+ if (!ok) {
+ DBG_WARNING("tevent_req_poll returned %s\n", strerror(errno));
+ }
+ exit(0);
+}
+
+static void cleanupd_stopped(struct tevent_req *req)
+{
+ NTSTATUS status;
+
+ status = smbd_cleanupd_recv(req);
+ DBG_WARNING("cleanupd stopped: %s\n", nt_errstr(status));
+}
+
+static void cleanupd_init_trigger(struct tevent_req *req);
+
+struct cleanup_init_state {
+ bool ok;
+ struct tevent_context *ev;
+ struct messaging_context *msg;
+ struct server_id *ppid;
+};
+
+static struct tevent_req *cleanupd_init_send(struct tevent_context *ev,
+ TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg,
+ struct server_id *ppid)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct cleanup_init_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct cleanup_init_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ *state = (struct cleanup_init_state) {
+ .msg = msg,
+ .ev = ev,
+ .ppid = ppid
+ };
+
+ subreq = tevent_wakeup_send(state, ev, tevent_timeval_current_ofs(0, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_set_callback(subreq, cleanupd_init_trigger, req);
+ return req;
+}
+
+static void cleanupd_init_trigger(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cleanup_init_state *state = tevent_req_data(
+ req, struct cleanup_init_state);
+ bool ok;
+
+ DBG_NOTICE("Triggering cleanupd startup\n");
+
+ ok = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ state->ok = cleanupd_init(state->msg, false, state->ppid);
+ if (state->ok) {
+ DBG_WARNING("cleanupd restarted\n");
+ tevent_req_done(req);
+ return;
+ }
+
+ DBG_NOTICE("cleanupd startup failed, rescheduling\n");
+
+ subreq = tevent_wakeup_send(state, state->ev,
+ tevent_timeval_current_ofs(1, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ DBG_ERR("scheduling cleanupd restart failed, giving up\n");
+ return;
+ }
+
+ tevent_req_set_callback(subreq, cleanupd_init_trigger, req);
+ return;
+}
+
+static bool cleanupd_init_recv(struct tevent_req *req)
+{
+ struct cleanup_init_state *state = tevent_req_data(
+ req, struct cleanup_init_state);
+
+ return state->ok;
+}
+
+static void cleanupd_started(struct tevent_req *req)
+{
+ bool ok;
+ NTSTATUS status;
+ struct smbd_parent_context *parent = tevent_req_callback_data(
+ req, struct smbd_parent_context);
+
+ ok = cleanupd_init_recv(req);
+ TALLOC_FREE(req);
+ if (!ok) {
+ DBG_ERR("Failed to restart cleanupd, giving up\n");
+ return;
+ }
+
+ status = messaging_send(parent->msg_ctx,
+ parent->cleanupd,
+ MSG_SMB_NOTIFY_CLEANUP,
+ &data_blob_null);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("messaging_send returned %s\n",
+ nt_errstr(status));
+ }
+}
+
+static void remove_child_pid(struct smbd_parent_context *parent,
+ pid_t pid,
+ bool unclean_shutdown)
+{
+ struct smbd_child_pid *child;
+ NTSTATUS status;
+ bool ok;
+
+ for (child = parent->children; child != NULL; child = child->next) {
+ if (child->pid == pid) {
+ struct smbd_child_pid *tmp = child;
+ DLIST_REMOVE(parent->children, child);
+ TALLOC_FREE(tmp);
+ parent->num_children -= 1;
+ break;
+ }
+ }
+
+ if (child == NULL) {
+ /* not all forked child processes are added to the children list */
+ DEBUG(2, ("Could not find child %d -- ignoring\n", (int)pid));
+ return;
+ }
+
+ if (pid == procid_to_pid(&parent->cleanupd)) {
+ struct tevent_req *req;
+
+ server_id_set_disconnected(&parent->cleanupd);
+
+ DBG_WARNING("Restarting cleanupd\n");
+ req = cleanupd_init_send(messaging_tevent_context(parent->msg_ctx),
+ parent,
+ parent->msg_ctx,
+ &parent->cleanupd);
+ if (req == NULL) {
+ DBG_ERR("Failed to restart cleanupd\n");
+ return;
+ }
+ tevent_req_set_callback(req, cleanupd_started, parent);
+ return;
+ }
+
+ if (pid == procid_to_pid(&parent->notifyd)) {
+ struct tevent_req *req;
+ struct tevent_context *ev = messaging_tevent_context(
+ parent->msg_ctx);
+
+ server_id_set_disconnected(&parent->notifyd);
+
+ DBG_WARNING("Restarting notifyd\n");
+ req = notifyd_init_send(ev,
+ parent,
+ parent->msg_ctx,
+ &parent->notifyd);
+ if (req == NULL) {
+ DBG_ERR("Failed to restart notifyd\n");
+ return;
+ }
+ tevent_req_set_callback(req, notifyd_started, parent);
+ return;
+ }
+
+ ok = cleanupdb_store_child(pid, unclean_shutdown);
+ if (!ok) {
+ DBG_ERR("cleanupdb_store_child failed\n");
+ return;
+ }
+
+ if (!server_id_is_disconnected(&parent->cleanupd)) {
+ status = messaging_send(parent->msg_ctx,
+ parent->cleanupd,
+ MSG_SMB_NOTIFY_CLEANUP,
+ &data_blob_null);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("messaging_send returned %s\n",
+ nt_errstr(status));
+ }
+ }
+}
+
+/****************************************************************************
+ Have we reached the process limit ?
+****************************************************************************/
+
+static bool allowable_number_of_smbd_processes(struct smbd_parent_context *parent)
+{
+ int max_processes = lp_max_smbd_processes();
+
+ if (!max_processes)
+ return True;
+
+ return parent->num_children < max_processes;
+}
+
+static void smbd_sig_chld_handler(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ pid_t pid;
+ int status;
+ struct smbd_parent_context *parent =
+ talloc_get_type_abort(private_data,
+ struct smbd_parent_context);
+
+ while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
+ bool unclean_shutdown = False;
+
+ /* If the child terminated normally, assume
+ it was an unclean shutdown unless the
+ status is 0
+ */
+ if (WIFEXITED(status)) {
+ unclean_shutdown = WEXITSTATUS(status);
+ }
+ /* If the child terminated due to a signal
+ we always assume it was unclean.
+ */
+ if (WIFSIGNALED(status)) {
+ unclean_shutdown = True;
+ }
+ remove_child_pid(parent, pid, unclean_shutdown);
+ }
+}
+
+static void smbd_setup_sig_chld_handler(struct smbd_parent_context *parent)
+{
+ struct tevent_signal *se;
+
+ se = tevent_add_signal(parent->ev_ctx,
+ parent, /* mem_ctx */
+ SIGCHLD, 0,
+ smbd_sig_chld_handler,
+ parent);
+ if (!se) {
+ exit_server("failed to setup SIGCHLD handler");
+ }
+}
+
+static void smbd_open_socket_close_fn(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ int fd,
+ void *private_data)
+{
+ /* this might be the socket_wrapper swrap_close() */
+ close(fd);
+}
+
+static void smbd_accept_connection(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct smbd_open_socket *s = talloc_get_type_abort(private_data,
+ struct smbd_open_socket);
+ struct messaging_context *msg_ctx = s->parent->msg_ctx;
+ struct sockaddr_storage addr;
+ socklen_t in_addrlen = sizeof(addr);
+ int fd;
+ pid_t pid = 0;
+
+ fd = accept(s->fd, (struct sockaddr *)(void *)&addr,&in_addrlen);
+ if (fd == -1 && errno == EINTR)
+ return;
+
+ if (fd == -1) {
+ DEBUG(0,("accept: %s\n",
+ strerror(errno)));
+ return;
+ }
+ smb_set_close_on_exec(fd);
+
+ if (s->parent->interactive) {
+ reinit_after_fork(msg_ctx, ev, true);
+ smbd_process(ev, msg_ctx, fd, true);
+ exit_server_cleanly("end of interactive mode");
+ return;
+ }
+
+ if (!allowable_number_of_smbd_processes(s->parent)) {
+ close(fd);
+ return;
+ }
+
+ pid = fork();
+ if (pid == 0) {
+ char addrstr[INET6_ADDRSTRLEN];
+ NTSTATUS status = NT_STATUS_OK;
+
+ /*
+ * Can't use TALLOC_FREE here. Nulling out the argument to it
+ * would overwrite memory we've just freed.
+ */
+ talloc_free(s->parent);
+ s = NULL;
+
+ /* Stop zombies, the parent explicitly handles
+ * them, counting worker smbds. */
+ CatchChild();
+
+ status = smbd_reinit_after_fork(msg_ctx, ev, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,
+ NT_STATUS_TOO_MANY_OPENED_FILES)) {
+ DEBUG(0,("child process cannot initialize "
+ "because too many files are open\n"));
+ goto exit;
+ }
+ if (lp_clustering() &&
+ (NT_STATUS_EQUAL(
+ status, NT_STATUS_INTERNAL_DB_ERROR) ||
+ NT_STATUS_EQUAL(
+ status, NT_STATUS_CONNECTION_REFUSED))) {
+ DEBUG(1, ("child process cannot initialize "
+ "because connection to CTDB "
+ "has failed: %s\n",
+ nt_errstr(status)));
+ goto exit;
+ }
+
+ DEBUG(0,("reinit_after_fork() failed\n"));
+ smb_panic("reinit_after_fork() failed");
+ }
+
+ print_sockaddr(addrstr, sizeof(addrstr), &addr);
+ process_set_title("smbd[%s]", "client [%s]", addrstr);
+
+ smbd_process(ev, msg_ctx, fd, false);
+ exit:
+ exit_server_cleanly("end of child");
+ return;
+ }
+
+ if (pid < 0) {
+ DEBUG(0,("smbd_accept_connection: fork() failed: %s\n",
+ strerror(errno)));
+ }
+
+ /* The parent doesn't need this socket */
+ close(fd);
+
+ /* Sun May 6 18:56:14 2001 ackley@cs.unm.edu:
+ Clear the closed fd info out of server_fd --
+ and more importantly, out of client_fd in
+ util_sock.c, to avoid a possible
+ getpeername failure if we reopen the logs
+ and use %I in the filename.
+ */
+
+ if (pid != 0) {
+ add_child_pid(s->parent, pid);
+ }
+
+ /* Force parent to check log size after
+ * spawning child. Fix from
+ * klausr@ITAP.Physik.Uni-Stuttgart.De. The
+ * parent smbd will log to logserver.smb. It
+ * writes only two messages for each child
+ * started/finished. But each child writes,
+ * say, 50 messages also in logserver.smb,
+ * beginning with the debug_count of the
+ * parent, before the child opens its own log
+ * file logserver.client. In a worst case
+ * scenario the size of logserver.smb would be
+ * checked after about 50*50=2500 messages
+ * (ca. 100kb).
+ * */
+ force_check_log_size();
+}
+
+static bool smbd_open_one_socket(struct smbd_parent_context *parent,
+ struct tevent_context *ev_ctx,
+ const struct sockaddr_storage *ifss,
+ uint16_t port)
+{
+ struct smbd_open_socket *s;
+
+ s = talloc(parent, struct smbd_open_socket);
+ if (!s) {
+ return false;
+ }
+
+ s->parent = parent;
+
+ s->fd = open_socket_in(SOCK_STREAM, ifss, port, true);
+ if (s->fd < 0) {
+ int err = -(s->fd);
+ DBG_ERR("open_socket_in failed: %s\n", strerror(err));
+ TALLOC_FREE(s);
+ /*
+ * We ignore an error here, as we've done before
+ */
+ return true;
+ }
+
+ /* ready to listen */
+ set_socket_options(s->fd, "SO_KEEPALIVE");
+ set_socket_options(s->fd, lp_socket_options());
+
+ /* Set server socket to
+ * non-blocking for the accept. */
+ set_blocking(s->fd, False);
+
+ if (listen(s->fd, SMBD_LISTEN_BACKLOG) == -1) {
+ DEBUG(0,("smbd_open_one_socket: listen: "
+ "%s\n", strerror(errno)));
+ close(s->fd);
+ TALLOC_FREE(s);
+ return false;
+ }
+
+ s->fde = tevent_add_fd(ev_ctx,
+ s,
+ s->fd, TEVENT_FD_READ,
+ smbd_accept_connection,
+ s);
+ if (!s->fde) {
+ DEBUG(0,("smbd_open_one_socket: "
+ "tevent_add_fd: %s\n",
+ strerror(errno)));
+ close(s->fd);
+ TALLOC_FREE(s);
+ return false;
+ }
+ tevent_fd_set_close_fn(s->fde, smbd_open_socket_close_fn);
+
+ DLIST_ADD_END(parent->sockets, s);
+
+ return true;
+}
+
+/****************************************************************************
+ Open the socket communication.
+****************************************************************************/
+
+static bool open_sockets_smbd(struct smbd_parent_context *parent,
+ struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const char *smb_ports)
+{
+ int num_interfaces = iface_count();
+ int i,j;
+ const char **ports;
+ unsigned dns_port = 0;
+
+#ifdef HAVE_ATEXIT
+ atexit(killkids);
+#endif
+
+ /* Stop zombies */
+ smbd_setup_sig_chld_handler(parent);
+
+ ports = lp_smb_ports();
+
+ /* use a reasonable default set of ports - listing on 445 and 139 */
+ if (smb_ports) {
+ char **l;
+ l = str_list_make_v3(talloc_tos(), smb_ports, NULL);
+ ports = discard_const_p(const char *, l);
+ }
+
+ for (j = 0; ports && ports[j]; j++) {
+ unsigned port = atoi(ports[j]);
+
+ if (port == 0 || port > 0xffff) {
+ exit_server_cleanly("Invalid port in the config or on "
+ "the commandline specified!");
+ }
+ }
+
+ if (lp_interfaces() && lp_bind_interfaces_only()) {
+ /* We have been given an interfaces line, and been
+ told to only bind to those interfaces. Create a
+ socket per interface and bind to only these.
+ */
+
+ /* Now open a listen socket for each of the
+ interfaces. */
+ for(i = 0; i < num_interfaces; i++) {
+ const struct sockaddr_storage *ifss =
+ iface_n_sockaddr_storage(i);
+ if (ifss == NULL) {
+ DEBUG(0,("open_sockets_smbd: "
+ "interface %d has NULL IP address !\n",
+ i));
+ continue;
+ }
+
+ for (j = 0; ports && ports[j]; j++) {
+ unsigned port = atoi(ports[j]);
+
+ /* Keep the first port for mDNS service
+ * registration.
+ */
+ if (dns_port == 0) {
+ dns_port = port;
+ }
+
+ if (!smbd_open_one_socket(parent,
+ ev_ctx,
+ ifss,
+ port)) {
+ return false;
+ }
+ }
+ }
+ } else {
+ /* Just bind to 0.0.0.0 - accept connections
+ from anywhere. */
+
+ const char *sock_addr;
+ char *sock_tok;
+ const char *sock_ptr;
+
+#ifdef HAVE_IPV6
+ sock_addr = "::,0.0.0.0";
+#else
+ sock_addr = "0.0.0.0";
+#endif
+
+ for (sock_ptr=sock_addr;
+ next_token_talloc(talloc_tos(), &sock_ptr, &sock_tok, " \t,"); ) {
+ for (j = 0; ports && ports[j]; j++) {
+ struct sockaddr_storage ss;
+ unsigned port = atoi(ports[j]);
+
+ /* Keep the first port for mDNS service
+ * registration.
+ */
+ if (dns_port == 0) {
+ dns_port = port;
+ }
+
+ /* open an incoming socket */
+ if (!interpret_string_addr(&ss, sock_tok,
+ AI_NUMERICHOST|AI_PASSIVE)) {
+ continue;
+ }
+
+ /*
+ * If we fail to open any sockets
+ * in this loop the parent-sockets == NULL
+ * case below will prevent us from starting.
+ */
+
+ (void)smbd_open_one_socket(parent,
+ ev_ctx,
+ &ss,
+ port);
+ }
+ }
+ }
+
+ if (parent->sockets == NULL) {
+ DEBUG(0,("open_sockets_smbd: No "
+ "sockets available to bind to.\n"));
+ return false;
+ }
+
+ /* Listen to messages */
+
+ messaging_register(msg_ctx, NULL, MSG_SHUTDOWN, msg_exit_server);
+ messaging_register(msg_ctx, ev_ctx, MSG_SMB_CONF_UPDATED,
+ smbd_parent_conf_updated);
+ messaging_register(msg_ctx, NULL, MSG_DEBUG, smbd_msg_debug);
+ messaging_register(msg_ctx, NULL, MSG_SMB_FORCE_TDIS,
+ smb_parent_send_to_children);
+ messaging_register(msg_ctx, NULL, MSG_SMB_FORCE_TDIS_DENIED,
+ smb_parent_send_to_children);
+ messaging_register(msg_ctx, NULL, MSG_SMB_KILL_CLIENT_IP,
+ smb_parent_send_to_children);
+ messaging_register(msg_ctx, NULL, MSG_SMB_TELL_NUM_CHILDREN,
+ smb_tell_num_children);
+
+ messaging_register(msg_ctx, NULL,
+ ID_CACHE_DELETE, smbd_parent_id_cache_delete);
+ messaging_register(msg_ctx, NULL,
+ ID_CACHE_KILL, smbd_parent_id_cache_kill);
+ messaging_register(msg_ctx, NULL, MSG_SMB_NOTIFY_STARTED,
+ smb_parent_send_to_children);
+
+#ifdef DEVELOPER
+ messaging_register(msg_ctx, NULL, MSG_SMB_INJECT_FAULT,
+ msg_inject_fault);
+#endif
+
+#if defined(DEVELOPER) || defined(ENABLE_SELFTEST)
+ messaging_register(msg_ctx, NULL, MSG_SMB_SLEEP, msg_sleep);
+#endif
+
+ if (lp_multicast_dns_register() && (dns_port != 0)) {
+#ifdef WITH_DNSSD_SUPPORT
+ smbd_setup_mdns_registration(ev_ctx,
+ parent, dns_port);
+#endif
+#ifdef WITH_AVAHI_SUPPORT
+ void *avahi_conn;
+
+ avahi_conn = avahi_start_register(ev_ctx,
+ ev_ctx,
+ dns_port);
+ if (avahi_conn == NULL) {
+ DEBUG(10, ("avahi_start_register failed\n"));
+ }
+#endif
+ }
+
+ return true;
+}
+
+
+/*
+ handle stdin becoming readable when we are in --foreground mode
+ */
+static void smbd_stdin_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ char c;
+ if (read(0, &c, 1) != 1) {
+ /* we have reached EOF on stdin, which means the
+ parent has exited. Shutdown the server */
+ exit_server_cleanly("EOF on stdin");
+ }
+}
+
+struct smbd_parent_tevent_trace_state {
+ TALLOC_CTX *frame;
+};
+
+static void smbd_parent_tevent_trace_callback(enum tevent_trace_point point,
+ void *private_data)
+{
+ struct smbd_parent_tevent_trace_state *state =
+ (struct smbd_parent_tevent_trace_state *)private_data;
+
+ switch (point) {
+ case TEVENT_TRACE_BEFORE_WAIT:
+ break;
+ case TEVENT_TRACE_AFTER_WAIT:
+ break;
+ case TEVENT_TRACE_BEFORE_LOOP_ONCE:
+ TALLOC_FREE(state->frame);
+ state->frame = talloc_stackframe();
+ break;
+ case TEVENT_TRACE_AFTER_LOOP_ONCE:
+ TALLOC_FREE(state->frame);
+ break;
+ }
+
+ errno = 0;
+}
+
+static void smbd_parent_loop(struct tevent_context *ev_ctx,
+ struct smbd_parent_context *parent)
+{
+ struct smbd_parent_tevent_trace_state trace_state = {
+ .frame = NULL,
+ };
+ int ret = 0;
+
+ tevent_set_trace_callback(ev_ctx, smbd_parent_tevent_trace_callback,
+ &trace_state);
+
+ /* now accept incoming connections - forking a new process
+ for each incoming connection */
+ DEBUG(2,("waiting for connections\n"));
+
+ ret = tevent_loop_wait(ev_ctx);
+ if (ret != 0) {
+ DEBUG(0, ("tevent_loop_wait failed: %d, %s, exiting\n",
+ ret, strerror(errno)));
+ }
+
+ TALLOC_FREE(trace_state.frame);
+
+/* NOTREACHED return True; */
+}
+
+
+/****************************************************************************
+ Initialise connect, service and file structs.
+****************************************************************************/
+
+static bool init_structs(void )
+{
+ /*
+ * Set the machine NETBIOS name if not already
+ * set from the config file.
+ */
+
+ if (!secrets_init())
+ return False;
+
+ return True;
+}
+
+static void smbd_parent_sig_term_handler(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ exit_server_cleanly("termination signal");
+}
+
+static void smbd_parent_sig_hup_handler(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ change_to_root_user();
+ DEBUG(1,("parent: Reloading services after SIGHUP\n"));
+ reload_services(NULL, NULL, false);
+}
+
+struct smbd_claim_version_state {
+ TALLOC_CTX *mem_ctx;
+ char *version;
+};
+
+static void smbd_claim_version_parser(struct server_id exclusive,
+ size_t num_shared,
+ const struct server_id *shared,
+ const uint8_t *data,
+ size_t datalen,
+ void *private_data)
+{
+ struct smbd_claim_version_state *state = private_data;
+
+ if (datalen == 0) {
+ state->version = NULL;
+ return;
+ }
+ if (data[datalen-1] != '\0') {
+ DBG_WARNING("Invalid samba version\n");
+ dump_data(DBGLVL_WARNING, data, datalen);
+ state->version = NULL;
+ return;
+ }
+ state->version = talloc_strdup(state->mem_ctx, (const char *)data);
+}
+
+static NTSTATUS smbd_claim_version(struct messaging_context *msg,
+ const char *version)
+{
+ const char *name = "samba_version_string";
+ const TDB_DATA key = string_term_tdb_data(name);
+ struct smbd_claim_version_state state;
+ struct g_lock_ctx *ctx;
+ NTSTATUS status;
+
+ ctx = g_lock_ctx_init(msg, msg);
+ if (ctx == NULL) {
+ DBG_WARNING("g_lock_ctx_init failed\n");
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ status = g_lock_lock(ctx,
+ key,
+ G_LOCK_READ,
+ (struct timeval) { .tv_sec = 60 },
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("g_lock_lock(G_LOCK_READ) failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(ctx);
+ return status;
+ }
+
+ state = (struct smbd_claim_version_state) { .mem_ctx = ctx };
+
+ status = g_lock_dump(ctx, key, smbd_claim_version_parser, &state);
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ DBG_ERR("Could not read samba_version_string\n");
+ g_lock_unlock(ctx, key);
+ TALLOC_FREE(ctx);
+ return status;
+ }
+
+ if ((state.version != NULL) && (strcmp(version, state.version) == 0)) {
+ /*
+ * Leave the read lock for us around. Someone else already
+ * set the version correctly
+ */
+ TALLOC_FREE(ctx);
+ return NT_STATUS_OK;
+ }
+
+ status = g_lock_lock(ctx,
+ key,
+ G_LOCK_UPGRADE,
+ (struct timeval) { .tv_sec = 60 },
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("g_lock_lock(G_LOCK_WRITE) failed: %s\n",
+ nt_errstr(status));
+ DBG_ERR("smbd %s already running, refusing to start "
+ "version %s\n", state.version, version);
+ TALLOC_FREE(ctx);
+ return NT_STATUS_SXS_VERSION_CONFLICT;
+ }
+
+ status = g_lock_write_data(
+ ctx, key, (const uint8_t *)version, strlen(version)+1);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("g_lock_write_data failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(ctx);
+ return status;
+ }
+
+ status = g_lock_lock(ctx,
+ key,
+ G_LOCK_DOWNGRADE,
+ (struct timeval) { .tv_sec = 60 },
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("g_lock_lock(G_LOCK_READ) failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(ctx);
+ return status;
+ }
+
+ /*
+ * Leave "ctx" dangling so that g_lock.tdb keeps opened.
+ */
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ main program.
+****************************************************************************/
+
+/* Declare prototype for build_options() to avoid having to run it through
+ mkproto.h. Mixing $(builddir) and $(srcdir) source files in the current
+ prototype generation system is too complicated. */
+
+extern void build_options(bool screen);
+
+ int main(int argc,const char *argv[])
+{
+ /* shall I run as a daemon */
+ struct samba_cmdline_daemon_cfg *cmdline_daemon_cfg = NULL;
+ bool log_stdout = false;
+ char *ports = NULL;
+ char *profile_level = NULL;
+ int opt;
+ poptContext pc;
+ struct server_id main_server_id = {0};
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ {
+ .longName = "build-options",
+ .shortName = 'b',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'b',
+ .descrip = "Print build options" ,
+ },
+ {
+ .longName = "port",
+ .shortName = 'p',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &ports,
+ .val = 0,
+ .descrip = "Listen on the specified ports",
+ },
+ {
+ .longName = "profiling-level",
+ .shortName = 'P',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &profile_level,
+ .val = 0,
+ .descrip = "Set profiling level","PROFILE_LEVEL",
+ },
+ POPT_COMMON_SAMBA
+ POPT_COMMON_DAEMON
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+ struct smbd_parent_context *parent = NULL;
+ TALLOC_CTX *frame;
+ NTSTATUS status;
+ struct tevent_context *ev_ctx;
+ struct messaging_context *msg_ctx;
+ struct server_id server_id;
+ struct tevent_signal *se;
+ int profiling_level;
+ char *np_dir = NULL;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ static const struct smbd_shim smbd_shim_fns =
+ {
+ .change_to_root_user = smbd_change_to_root_user,
+ .become_authenticated_pipe_user = smbd_become_authenticated_pipe_user,
+ .unbecome_authenticated_pipe_user = smbd_unbecome_authenticated_pipe_user,
+
+ .contend_level2_oplocks_begin = smbd_contend_level2_oplocks_begin,
+ .contend_level2_oplocks_end = smbd_contend_level2_oplocks_end,
+
+ .become_root = smbd_become_root,
+ .unbecome_root = smbd_unbecome_root,
+
+ .exit_server = smbd_exit_server,
+ .exit_server_cleanly = smbd_exit_server_cleanly,
+ };
+ bool ok;
+
+ setproctitle_init(argc, discard_const(argv), environ);
+
+ /*
+ * Do this before any other talloc operation
+ */
+ talloc_enable_null_tracking();
+ frame = talloc_stackframe();
+
+ smb_init_locale();
+
+ set_smbd_shim(&smbd_shim_fns);
+
+ smbd_init_globals();
+
+ TimeInit();
+
+#ifdef HAVE_SET_AUTH_PARAMETERS
+ set_auth_parameters(argc,argv);
+#endif
+
+ ok = samba_cmdline_init(frame,
+ SAMBA_CMDLINE_CONFIG_SERVER,
+ true /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to setup cmdline parser!\n");
+ exit(ENOMEM);
+ }
+
+ cmdline_daemon_cfg = samba_cmdline_get_daemon_cfg();
+
+ pc = samba_popt_get_context(getprogname(),
+ argc,
+ argv,
+ long_options,
+ 0);
+ if (pc == NULL) {
+ DBG_ERR("Failed to get popt context!\n");
+ exit(ENOMEM);
+ }
+
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ case 'b':
+ build_options(true); /* Display output to screen as well as debug */
+ exit(0);
+ break;
+ default:
+ d_fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+ }
+ poptFreeContext(pc);
+
+ log_stdout = (debug_get_log_type() == DEBUG_STDOUT);
+
+ if (cmdline_daemon_cfg->interactive) {
+ log_stdout = True;
+ }
+
+#ifdef HAVE_SETLUID
+ /* needed for SecureWare on SCO */
+ setluid(0);
+#endif
+
+ set_remote_machine_name("smbd", False);
+
+ if (cmdline_daemon_cfg->interactive && (DEBUGLEVEL >= 9)) {
+ talloc_enable_leak_report();
+ }
+
+ if (log_stdout && cmdline_daemon_cfg->fork) {
+ DEBUG(0,("ERROR: Can't log to stdout (-S) unless daemon is in foreground (-F) or interactive (-i)\n"));
+ exit(1);
+ }
+
+ /*
+ * We want to die early if we can't open /dev/urandom
+ */
+ generate_random_buffer(NULL, 0);
+
+ /* get initial effective uid and gid */
+ sec_init();
+
+ /* make absolutely sure we run as root - to handle cases where people
+ are crazy enough to have it setuid */
+ gain_root_privilege();
+ gain_root_group_privilege();
+
+ dump_core_setup("smbd", lp_logfile(talloc_tos(), lp_sub));
+
+ /* we are never interested in SIGPIPE */
+ BlockSignals(True,SIGPIPE);
+
+#if defined(SIGFPE)
+ /* we are never interested in SIGFPE */
+ BlockSignals(True,SIGFPE);
+#endif
+
+#if defined(SIGUSR2)
+ /* We are no longer interested in USR2 */
+ BlockSignals(True,SIGUSR2);
+#endif
+
+ /*
+ * POSIX demands that signals are inherited. If the invoking
+ * process has these signals masked, we will have problems, as
+ * we won't receive them.
+ */
+ BlockSignals(False, SIGHUP);
+ BlockSignals(False, SIGUSR1);
+ BlockSignals(False, SIGTERM);
+
+ /* Ensure we leave no zombies until we
+ * correctly set up child handling below. */
+
+ CatchChild();
+
+ /* we want total control over the permissions on created files,
+ so set our umask to 0 */
+ umask(0);
+
+ reopen_logs();
+
+ DBG_STARTUP_NOTICE("smbd version %s started.\n%s\n",
+ samba_version_string(),
+ samba_copyright_string());
+
+ DEBUG(2,("uid=%d gid=%d euid=%d egid=%d\n",
+ (int)getuid(),(int)getgid(),(int)geteuid(),(int)getegid()));
+
+ /* Output the build options to the debug log */
+ build_options(False);
+
+ if (sizeof(uint16_t) < 2 || sizeof(uint32_t) < 4) {
+ DEBUG(0,("ERROR: Samba is not configured correctly for the word size on your machine\n"));
+ exit(1);
+ }
+
+ /*
+ * This calls unshare(CLONE_FS); on linux
+ * in order to check if the running kernel/container
+ * environment supports it.
+ */
+ per_thread_cwd_check();
+
+ if (!cluster_probe_ok()) {
+ exit(1);
+ }
+
+ /* Init the security context and global current_user */
+ init_sec_ctx();
+
+ /*
+ * Initialize the event context. The event context needs to be
+ * initialized before the messaging context, cause the messaging
+ * context holds an event context.
+ */
+ ev_ctx = global_event_context();
+ if (ev_ctx == NULL) {
+ exit(1);
+ }
+
+ /*
+ * Init the messaging context
+ * FIXME: This should only call messaging_init()
+ */
+ msg_ctx = global_messaging_context();
+ if (msg_ctx == NULL) {
+ exit(1);
+ }
+
+ /*
+ * Reloading of the printers will not work here as we don't have a
+ * server info and rpc services set up. It will be called later.
+ */
+ if (!reload_services(NULL, NULL, false)) {
+ exit(1);
+ }
+
+ if (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC) {
+ if (!lp_parm_bool(-1, "server role check", "inhibit", false)) {
+ DBG_ERR("server role = 'active directory domain controller' not compatible with running smbd standalone. \n");
+ DEBUGADD(0, ("You should start 'samba' instead, and it will control starting smbd if required\n"));
+ exit(1);
+ }
+ /* Main 'samba' daemon will notify */
+ daemon_sd_notifications(false);
+ }
+
+ /* ...NOTE... Log files are working from this point! */
+
+ DEBUG(3,("loaded services\n"));
+
+ init_structs();
+
+ if (!profile_setup(msg_ctx, False)) {
+ DEBUG(0,("ERROR: failed to setup profiling\n"));
+ return -1;
+ }
+
+ if (profile_level != NULL) {
+ profiling_level = atoi(profile_level);
+ } else {
+ profiling_level = lp_smbd_profiling_level();
+ }
+ main_server_id = messaging_server_id(msg_ctx);
+ set_profile_level(profiling_level, &main_server_id);
+
+ if (!cmdline_daemon_cfg->daemon && !is_a_socket(0)) {
+ if (!cmdline_daemon_cfg->interactive) {
+ DEBUG(3, ("Standard input is not a socket, "
+ "assuming -D option\n"));
+ }
+
+ /*
+ * Setting "daemon" here prevents us from eventually calling
+ * the open_sockets_inetd()
+ */
+
+ cmdline_daemon_cfg->daemon = true;
+ }
+
+ if (cmdline_daemon_cfg->daemon && !cmdline_daemon_cfg->interactive) {
+ DEBUG(3, ("Becoming a daemon.\n"));
+ become_daemon(cmdline_daemon_cfg->fork,
+ cmdline_daemon_cfg->no_process_group,
+ log_stdout);
+ } else {
+ daemon_status("smbd", "Starting process ...");
+ }
+
+#ifdef HAVE_SETPGID
+ /*
+ * If we're interactive we want to set our own process group for
+ * signal management.
+ */
+ if (cmdline_daemon_cfg->interactive &&
+ !cmdline_daemon_cfg->no_process_group)
+ {
+ setpgid( (pid_t)0, (pid_t)0);
+ }
+#endif
+
+ if (!directory_exist(lp_lock_directory()))
+ mkdir(lp_lock_directory(), 0755);
+
+ if (!directory_exist(lp_pid_directory()))
+ mkdir(lp_pid_directory(), 0755);
+
+ if (cmdline_daemon_cfg->daemon)
+ pidfile_create(lp_pid_directory(), "smbd");
+
+ status = reinit_after_fork(msg_ctx, ev_ctx, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_daemon("reinit_after_fork() failed", map_errno_from_nt_status(status));
+ }
+
+ if (!cmdline_daemon_cfg->interactive) {
+ /*
+ * Do not initialize the parent-child-pipe before becoming a
+ * daemon: this is used to detect a died parent in the child
+ * process.
+ */
+ status = init_before_fork();
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_daemon(nt_errstr(status), map_errno_from_nt_status(status));
+ }
+ }
+
+ parent = talloc_zero(ev_ctx, struct smbd_parent_context);
+ if (!parent) {
+ exit_server("talloc(struct smbd_parent_context) failed");
+ }
+ parent->interactive = cmdline_daemon_cfg->interactive;
+ parent->ev_ctx = ev_ctx;
+ parent->msg_ctx = msg_ctx;
+ am_parent = parent;
+
+ se = tevent_add_signal(parent->ev_ctx,
+ parent,
+ SIGTERM, 0,
+ smbd_parent_sig_term_handler,
+ parent);
+ if (!se) {
+ exit_server("failed to setup SIGTERM handler");
+ }
+ se = tevent_add_signal(parent->ev_ctx,
+ parent,
+ SIGHUP, 0,
+ smbd_parent_sig_hup_handler,
+ parent);
+ if (!se) {
+ exit_server("failed to setup SIGHUP handler");
+ }
+
+ /* Setup all the TDB's - including CLEAR_IF_FIRST tdb's. */
+
+ if (smbd_memcache() == NULL) {
+ exit_daemon("no memcache available", EACCES);
+ }
+
+ memcache_set_global(smbd_memcache());
+
+ /* Initialise the password backed before the global_sam_sid
+ to ensure that we fetch from ldap before we make a domain sid up */
+
+ if(!initialize_password_db(false, ev_ctx))
+ exit(1);
+
+ if (!secrets_init()) {
+ exit_daemon("smbd can not open secrets.tdb", EACCES);
+ }
+
+ if (lp_server_role() == ROLE_DOMAIN_BDC || lp_server_role() == ROLE_DOMAIN_PDC || lp_server_role() == ROLE_IPA_DC) {
+ struct loadparm_context *lp_ctx = loadparm_init_s3(NULL, loadparm_s3_helpers());
+ if (!open_schannel_session_store(NULL, lp_ctx)) {
+ exit_daemon("ERROR: Samba cannot open schannel store for secured NETLOGON operations.", EACCES);
+ }
+ TALLOC_FREE(lp_ctx);
+ }
+
+ if(!get_global_sam_sid()) {
+ exit_daemon("Samba cannot create a SAM SID", EACCES);
+ }
+
+ server_id = messaging_server_id(msg_ctx);
+ status = smbXsrv_version_global_init(&server_id);
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_daemon("Samba cannot init server context", EACCES);
+ }
+
+ status = smbXsrv_client_global_init();
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_daemon("Samba cannot init clients context", EACCES);
+ }
+
+ status = smbXsrv_session_global_init(msg_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_daemon("Samba cannot init session context", EACCES);
+ }
+
+ status = smbXsrv_tcon_global_init();
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_daemon("Samba cannot init tcon context", EACCES);
+ }
+
+ if (!locking_init())
+ exit_daemon("Samba cannot init locking", EACCES);
+
+ if (!leases_db_init(false)) {
+ exit_daemon("Samba cannot init leases", EACCES);
+ }
+
+ if (!smbd_notifyd_init(
+ msg_ctx,
+ cmdline_daemon_cfg->interactive,
+ &parent->notifyd)) {
+ exit_daemon("Samba cannot init notification", EACCES);
+ }
+
+ if (!cleanupd_init(
+ msg_ctx,
+ cmdline_daemon_cfg->interactive,
+ &parent->cleanupd)) {
+ exit_daemon("Samba cannot init the cleanupd", EACCES);
+ }
+
+ if (!messaging_parent_dgm_cleanup_init(msg_ctx)) {
+ exit(1);
+ }
+
+ if (!smbd_scavenger_init(NULL, msg_ctx, ev_ctx)) {
+ exit_daemon("Samba cannot init scavenging", EACCES);
+ }
+
+ if (!W_ERROR_IS_OK(registry_init_full()))
+ exit_daemon("Samba cannot init registry", EACCES);
+
+ /* Open the share_info.tdb here, so we don't have to open
+ after the fork on every single connection. This is a small
+ performance improvement and reduces the total number of system
+ fds used. */
+ status = share_info_db_init();
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_daemon("ERROR: failed to load share info db.", EACCES);
+ }
+
+ status = init_system_session_info(NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("ERROR: failed to setup system user info: %s.\n",
+ nt_errstr(status)));
+ return -1;
+ }
+
+ if (!init_guest_session_info(NULL)) {
+ DEBUG(0,("ERROR: failed to setup guest info.\n"));
+ return -1;
+ }
+
+ if (!file_init_global()) {
+ DEBUG(0, ("ERROR: file_init_global() failed\n"));
+ return -1;
+ }
+ status = smbXsrv_open_global_init();
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_daemon("Samba cannot init global open", map_errno_from_nt_status(status));
+ }
+
+ if (lp_clustering() && !lp_allow_unsafe_cluster_upgrade()) {
+ status = smbd_claim_version(msg_ctx, samba_version_string());
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Could not claim version: %s\n",
+ nt_errstr(status));
+ return -1;
+ }
+ }
+
+ /* This MUST be done before start_epmd() because otherwise
+ * start_epmd() forks and races against dcesrv_ep_setup() to
+ * call directory_create_or_exist() */
+ if (!directory_create_or_exist(lp_ncalrpc_dir(), 0755)) {
+ DEBUG(0, ("Failed to create pipe directory %s - %s\n",
+ lp_ncalrpc_dir(), strerror(errno)));
+ return -1;
+ }
+
+ np_dir = talloc_asprintf(talloc_tos(), "%s/np", lp_ncalrpc_dir());
+ if (!np_dir) {
+ DEBUG(0, ("%s: Out of memory\n", __location__));
+ return -1;
+ }
+
+ if (!directory_create_or_exist_strict(np_dir, geteuid(), 0700)) {
+ DEBUG(0, ("Failed to create pipe directory %s - %s\n",
+ np_dir, strerror(errno)));
+ return -1;
+ }
+
+ if (!cmdline_daemon_cfg->interactive) {
+ daemon_ready("smbd");
+ }
+
+ if (!cmdline_daemon_cfg->daemon) {
+ int ret, sock;
+
+ /* inetd mode */
+ TALLOC_FREE(frame);
+
+ /* Started from inetd. fd 0 is the socket. */
+ /* We will abort gracefully when the client or remote system
+ goes away */
+ sock = dup(0);
+
+ /* close stdin, stdout (if not logging to it), but not stderr */
+ ret = close_low_fd(0);
+ if (ret != 0) {
+ DBG_ERR("close_low_fd(0) failed: %s\n", strerror(ret));
+ return 1;
+ }
+ if (!debug_get_output_is_stdout()) {
+ ret = close_low_fd(1);
+ if (ret != 0) {
+ DBG_ERR("close_low_fd(1) failed: %s\n",
+ strerror(ret));
+ return 1;
+ }
+ }
+
+#ifdef HAVE_ATEXIT
+ atexit(killkids);
+#endif
+
+ /* Stop zombies */
+ smbd_setup_sig_chld_handler(parent);
+
+ smbd_process(ev_ctx, msg_ctx, sock, true);
+
+ exit_server_cleanly(NULL);
+ return(0);
+ }
+
+ if (!open_sockets_smbd(parent, ev_ctx, msg_ctx, ports))
+ exit_server("open_sockets_smbd() failed");
+
+ TALLOC_FREE(frame);
+ /* make sure we always have a valid stackframe */
+ frame = talloc_stackframe();
+
+ if (!cmdline_daemon_cfg->fork) {
+ /* if we are running in the foreground then look for
+ EOF on stdin, and exit if it happens. This allows
+ us to die if the parent process dies
+ Only do this on a pipe or socket, no other device.
+ */
+ struct stat st;
+ if (fstat(0, &st) != 0) {
+ return 1;
+ }
+ if (S_ISFIFO(st.st_mode) || S_ISSOCK(st.st_mode)) {
+ tevent_add_fd(ev_ctx,
+ parent,
+ 0,
+ TEVENT_FD_READ,
+ smbd_stdin_handler,
+ NULL);
+ }
+ }
+
+ smbd_parent_loop(ev_ctx, parent);
+
+ exit_server_cleanly(NULL);
+ TALLOC_FREE(frame);
+ return(0);
+}
diff --git a/source3/smbd/server_exit.c b/source3/smbd/server_exit.c
new file mode 100644
index 0000000..5e0c386
--- /dev/null
+++ b/source3/smbd/server_exit.c
@@ -0,0 +1,262 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main SMB server routines
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Martin Pool 2002
+ Copyright (C) Jelmer Vernooij 2002-2003
+ Copyright (C) Volker Lendecke 1993-2007
+ Copyright (C) Jeremy Allison 1993-2007
+ Copyright (C) Andrew Bartlett 2010
+
+ 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 "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "ntdomain.h"
+#include "librpc/rpc/dcesrv_core.h"
+#include "printing/notify.h"
+#include "printing.h"
+#include "serverid.h"
+#include "messages.h"
+#include "passdb.h"
+#include "../lib/util/pidfile.h"
+#include "smbprofile.h"
+#include "libcli/auth/netlogon_creds_cli.h"
+#include "lib/gencache.h"
+#include "rpc_server/rpc_config.h"
+#include "lib/global_contexts.h"
+
+static struct files_struct *log_writeable_file_fn(
+ struct files_struct *fsp, void *private_data)
+{
+ bool *found = (bool *)private_data;
+ char *path;
+
+ if (!fsp->fsp_flags.can_write) {
+ return NULL;
+ }
+ if (!(*found)) {
+ DEBUG(0, ("Writable files open at exit:\n"));
+ *found = true;
+ }
+
+ path = talloc_asprintf(talloc_tos(), "%s/%s", fsp->conn->connectpath,
+ smb_fname_str_dbg(fsp->fsp_name));
+ if (path == NULL) {
+ DEBUGADD(0, ("<NOMEM>\n"));
+ }
+
+ DEBUGADD(0, ("%s\n", path));
+
+ TALLOC_FREE(path);
+ return NULL;
+}
+
+/****************************************************************************
+ Exit the server.
+****************************************************************************/
+
+/* Reasons for shutting down a server process. */
+enum server_exit_reason { SERVER_EXIT_NORMAL, SERVER_EXIT_ABNORMAL };
+
+static void exit_server_common(enum server_exit_reason how,
+ const char *reason) _NORETURN_;
+
+static void exit_server_common(enum server_exit_reason how,
+ const char *reason)
+{
+ struct smbXsrv_client *client = global_smbXsrv_client;
+ struct smbXsrv_connection *xconn = NULL;
+ struct smbd_server_connection *sconn = NULL;
+ NTSTATUS disconnect_status;
+
+ if (!exit_firsttime) {
+ exit(0);
+ }
+ exit_firsttime = false;
+
+ switch (how) {
+ case SERVER_EXIT_NORMAL:
+ disconnect_status = NT_STATUS_LOCAL_DISCONNECT;
+ break;
+ case SERVER_EXIT_ABNORMAL:
+ default:
+ disconnect_status = NT_STATUS_INTERNAL_ERROR;
+ break;
+ }
+
+ if (client != NULL) {
+ NTSTATUS status;
+
+ sconn = client->sconn;
+ xconn = client->connections;
+
+ /*
+ * Make sure we stop handling new multichannel
+ * connections early!
+ *
+ * From here, we're not able to handle them.
+ */
+ status = smbXsrv_client_remove(client);
+ if (!NT_STATUS_IS_OK(status)) {
+ D_ERR("Server exit (%s)\n",
+ (reason ? reason : "normal exit"));
+ DBG_ERR("smbXsrv_client_remove() failed (%s)\n",
+ nt_errstr(status));
+ }
+ }
+
+ change_to_root_user();
+
+
+ /*
+ * Here we typically have just one connection
+ */
+ for (; xconn != NULL; xconn = xconn->next) {
+ /*
+ * This is typically the disconnect for the only
+ * (or with multi-channel last) connection of the client.
+ *
+ * smbXsrv_connection_disconnect_transport() might be called already,
+ * but calling it again is a no-op.
+ */
+ smbXsrv_connection_disconnect_transport(xconn, disconnect_status);
+ }
+
+ change_to_root_user();
+
+ if (sconn != NULL) {
+ if (lp_log_writeable_files_on_exit()) {
+ bool found = false;
+ files_forall(sconn, log_writeable_file_fn, &found);
+ }
+ }
+
+ change_to_root_user();
+
+ if (client != NULL) {
+ NTSTATUS status;
+
+ /*
+ * Note: this is a no-op for smb2 as
+ * conn->tcon_table is empty
+ */
+ status = smb1srv_tcon_disconnect_all(client);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Server exit (%s)\n",
+ (reason ? reason : "normal exit")));
+ DEBUG(0, ("exit_server_common: "
+ "smb1srv_tcon_disconnect_all() failed (%s) - "
+ "triggering cleanup\n", nt_errstr(status)));
+ }
+
+ status = smbXsrv_session_logoff_all(client);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Server exit (%s)\n",
+ (reason ? reason : "normal exit")));
+ DEBUG(0, ("exit_server_common: "
+ "smbXsrv_session_logoff_all() failed (%s) - "
+ "triggering cleanup\n", nt_errstr(status)));
+ }
+ }
+
+ change_to_root_user();
+
+ if (client != NULL) {
+ struct smbXsrv_connection *xconn_next = NULL;
+
+ for (xconn = client->connections;
+ xconn != NULL;
+ xconn = xconn_next) {
+ xconn_next = xconn->next;
+ DLIST_REMOVE(client->connections, xconn);
+ TALLOC_FREE(xconn);
+ }
+ }
+
+ change_to_root_user();
+
+#ifdef USE_DMAPI
+ /* Destroy Samba DMAPI session only if we are master smbd process */
+ if (am_parent) {
+ if (!dmapi_destroy_session()) {
+ DEBUG(0,("Unable to close Samba DMAPI session\n"));
+ }
+ }
+#endif
+
+
+ /*
+ * we need to force the order of freeing the following,
+ * because smbd_msg_ctx is not a talloc child of smbd_server_conn.
+ */
+ if (client != NULL) {
+ TALLOC_FREE(client->sconn);
+ }
+ sconn = NULL;
+ xconn = NULL;
+ client = NULL;
+ netlogon_creds_cli_close_global_db();
+ TALLOC_FREE(global_smbXsrv_client);
+ smbprofile_dump();
+ global_messaging_context_free();
+ global_event_context_free();
+ TALLOC_FREE(smbd_memcache_ctx);
+
+ locking_end();
+
+ if (how != SERVER_EXIT_NORMAL) {
+
+ smb_panic(reason);
+
+ /* Notreached. */
+ exit(1);
+ } else {
+ DEBUG(3,("Server exit (%s)\n",
+ (reason ? reason : "normal exit")));
+ if (am_parent) {
+ pidfile_unlink(lp_pid_directory(), "smbd");
+ }
+ }
+
+ exit(0);
+}
+
+void smbd_exit_server(const char *const explanation)
+{
+ exit_server_common(SERVER_EXIT_ABNORMAL, explanation);
+}
+
+void smbd_exit_server_cleanly(const char *const explanation)
+{
+ exit_server_common(SERVER_EXIT_NORMAL, explanation);
+}
+
+/*
+ * reinit_after_fork() wrapper that should be called when forking from
+ * smbd.
+ */
+NTSTATUS smbd_reinit_after_fork(struct messaging_context *msg_ctx,
+ struct tevent_context *ev_ctx,
+ bool parent_longlived)
+{
+ NTSTATUS ret;
+ am_parent = NULL;
+ ret = reinit_after_fork(msg_ctx, ev_ctx, parent_longlived);
+ initialize_password_db(true, ev_ctx);
+ return ret;
+}
diff --git a/source3/smbd/server_reload.c b/source3/smbd/server_reload.c
new file mode 100644
index 0000000..d3322d1
--- /dev/null
+++ b/source3/smbd/server_reload.c
@@ -0,0 +1,176 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main SMB server routines
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Martin Pool 2002
+ Copyright (C) Jelmer Vernooij 2002-2003
+ Copyright (C) Volker Lendecke 1993-2007
+ Copyright (C) Jeremy Allison 1993-2007
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "nt_printing.h"
+#include "printing/pcap.h"
+#include "printing/printer_list.h"
+#include "printing/load.h"
+#include "auth.h"
+#include "messages.h"
+#include "lib/param/loadparm.h"
+
+/*
+ * The persistent pcap cache is populated by the background print process. Per
+ * client smbds should only reload their printer share inventories if this
+ * information has changed. Use reload_last_pcap_time to detect this.
+ */
+static time_t reload_last_pcap_time = 0;
+
+bool snum_is_shared_printer(int snum)
+{
+ return (lp_browseable(snum) && lp_snum_ok(snum) && lp_printable(snum));
+}
+
+/**
+ * @brief Purge stale printer shares and reload from pre-populated pcap cache.
+ *
+ * This function should normally only be called as a callback on a successful
+ * pcap_cache_reload(), or on client enumeration.
+ */
+void delete_and_reload_printers(void)
+{
+ int n_services;
+ int pnum;
+ int snum;
+ const char *pname;
+ bool ok;
+ time_t pcap_last_update;
+ TALLOC_CTX *frame = NULL;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ if (!lp_load_printers()) {
+ DBG_DEBUG("skipping printer reload: disabled\n");
+ return;
+ }
+
+ frame = talloc_stackframe();
+ ok = pcap_cache_loaded(&pcap_last_update);
+ if (!ok) {
+ DEBUG(1, ("pcap cache not loaded\n"));
+ talloc_free(frame);
+ return;
+ }
+
+ if (reload_last_pcap_time == pcap_last_update) {
+ DEBUG(5, ("skipping printer reload, already up to date.\n"));
+ talloc_free(frame);
+ return;
+ }
+ reload_last_pcap_time = pcap_last_update;
+
+ /* Get pcap printers updated */
+ load_printers();
+
+ n_services = lp_numservices();
+ pnum = lp_servicenumber(PRINTERS_NAME);
+
+ DEBUG(10, ("reloading printer services from pcap cache\n"));
+
+ /*
+ * Add default config for printers added to smb.conf file and remove
+ * stale printers
+ */
+ for (snum = 0; snum < n_services; snum++) {
+ /* avoid removing PRINTERS_NAME */
+ if (snum == pnum) {
+ continue;
+ }
+
+ /* skip no-printer services */
+ if (!snum_is_shared_printer(snum)) {
+ continue;
+ }
+
+ pname = lp_printername(frame, lp_sub, snum);
+
+ /* check printer, but avoid removing non-autoloaded printers */
+ if (lp_autoloaded(snum) &&
+ !printer_list_printername_exists(pname)) {
+ lp_killservice(snum);
+ }
+ }
+
+ /* Make sure deleted printers are gone */
+ load_printers();
+
+ talloc_free(frame);
+}
+
+/****************************************************************************
+ Reload the services file.
+**************************************************************************/
+
+bool reload_services(struct smbd_server_connection *sconn,
+ bool (*snumused) (struct smbd_server_connection *, int),
+ bool test)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct smbXsrv_connection *xconn = NULL;
+ bool ret;
+
+ if (lp_loaded()) {
+ char *fname = lp_next_configfile(talloc_tos(), lp_sub);
+ if (file_exist(fname) &&
+ !strcsequal(fname, get_dyn_CONFIGFILE())) {
+ set_dyn_CONFIGFILE(fname);
+ test = False;
+ }
+ TALLOC_FREE(fname);
+ }
+
+ reopen_logs();
+
+ if (test && !lp_file_list_changed())
+ return(True);
+
+ lp_killunused(sconn, snumused);
+
+ ret = lp_load_with_shares(get_dyn_CONFIGFILE());
+
+ /* perhaps the config filename is now set */
+ if (!test) {
+ reload_services(sconn, snumused, true);
+ }
+
+ reopen_logs();
+
+ load_interfaces();
+
+ if (sconn != NULL && sconn->client != NULL) {
+ xconn = sconn->client->connections;
+ }
+ for (;xconn != NULL; xconn = xconn->next) {
+ set_socket_options(xconn->transport.sock, "SO_KEEPALIVE");
+ set_socket_options(xconn->transport.sock, lp_socket_options());
+ }
+
+ mangle_reset_cache();
+ flush_dfree_cache();
+
+ return(ret);
+}
diff --git a/source3/smbd/session.c b/source3/smbd/session.c
new file mode 100644
index 0000000..abc7991
--- /dev/null
+++ b/source3/smbd/session.c
@@ -0,0 +1,218 @@
+/*
+ Unix SMB/CIFS implementation.
+ session handling for utmp and PAM
+
+ Copyright (C) tridge@samba.org 2001
+ Copyright (C) abartlet@samba.org 2001
+ Copyright (C) Gerald (Jerry) Carter 2006
+
+ 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/>.
+*/
+
+/* a "session" is claimed when we do a SessionSetupX operation
+ and is yielded when the corresponding vuid is destroyed.
+
+ sessions are used to populate utmp and PAM session structures
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "dbwrap/dbwrap.h"
+#include "session.h"
+#include "auth.h"
+#include "../lib/tsocket/tsocket.h"
+#include "../libcli/security/security.h"
+#include "messages.h"
+
+/********************************************************************
+ called when a session is created
+********************************************************************/
+
+bool session_claim(struct smbXsrv_session *session)
+{
+ struct auth_session_info *session_info =
+ session->global->auth_session_info;
+ const char *username;
+ const char *hostname;
+ unsigned int id_num;
+ fstring id_str;
+
+ /* don't register sessions for the guest user - its just too
+ expensive to go through pam session code for browsing etc */
+ if (security_session_user_level(session_info, NULL) < SECURITY_USER) {
+ return true;
+ }
+
+ id_num = session->global->session_global_id;
+
+ snprintf(id_str, sizeof(id_str), "smb/%u", id_num);
+
+ /* Make clear that we require the optional unix_token in the source3 code */
+ SMB_ASSERT(session_info->unix_token);
+
+ username = session_info->unix_info->unix_name;
+ hostname = session->global->channels[0].remote_name;
+
+ if (!smb_pam_claim_session(username, id_str, hostname)) {
+ DEBUG(1,("pam_session rejected the session for %s [%s]\n",
+ username, id_str));
+ return false;
+ }
+
+ if (lp_utmp()) {
+ sys_utmp_claim(username, hostname, id_str, id_num);
+ }
+
+ return true;
+}
+
+/********************************************************************
+ called when a session is destroyed
+********************************************************************/
+
+void session_yield(struct smbXsrv_session *session)
+{
+ struct auth_session_info *session_info =
+ session->global->auth_session_info;
+ const char *username;
+ const char *hostname;
+ unsigned int id_num;
+ fstring id_str = "";
+
+ id_num = session->global->session_global_id;
+
+ snprintf(id_str, sizeof(id_str), "smb/%u", id_num);
+
+ /* Make clear that we require the optional unix_token in the source3 code */
+ SMB_ASSERT(session_info->unix_token);
+
+ username = session_info->unix_info->unix_name;
+ hostname = session->global->channels[0].remote_name;
+
+ if (lp_utmp()) {
+ sys_utmp_yield(username, hostname, id_str, id_num);
+ }
+
+ smb_pam_close_session(username, id_str, hostname);
+}
+
+/********************************************************************
+********************************************************************/
+
+struct session_list {
+ TALLOC_CTX *mem_ctx;
+ int count;
+ const char *filter_user;
+ const char *filter_machine;
+ struct sessionid *sessions;
+};
+
+static int gather_sessioninfo(const char *key, struct sessionid *session,
+ void *private_data)
+{
+ struct session_list *sesslist = (struct session_list *)private_data;
+
+ /* filter the session if required */
+
+ if (sesslist->filter_user &&
+ (sesslist->filter_user[0] != '\0') &&
+ !strequal(session->username, sesslist->filter_user)) {
+ return 0;
+ }
+
+ if (sesslist->filter_machine &&
+ (sesslist->filter_machine[0] != '\0') &&
+ !strequal(session->remote_machine,
+ sesslist->filter_machine)) {
+ return 0;
+ }
+
+ if (!process_exists(session->pid)) {
+ return 0;
+ }
+
+ sesslist->sessions = talloc_realloc(
+ sesslist->mem_ctx, sesslist->sessions, struct sessionid,
+ sesslist->count+1);
+
+ if (!sesslist->sessions) {
+ sesslist->count = 0;
+ return -1;
+ }
+
+ memcpy(&sesslist->sessions[sesslist->count], session,
+ sizeof(struct sessionid));
+
+ sesslist->count++;
+
+ DEBUG(7, ("gather_sessioninfo session from %s@%s\n",
+ session->username, session->remote_machine));
+
+ return 0;
+}
+
+/********************************************************************
+********************************************************************/
+
+int list_sessions(TALLOC_CTX *mem_ctx, struct sessionid **session_list)
+{
+ struct session_list sesslist;
+ NTSTATUS status;
+
+ sesslist.mem_ctx = mem_ctx;
+ sesslist.count = 0;
+ sesslist.filter_user = NULL;
+ sesslist.filter_machine = NULL;
+ sesslist.sessions = NULL;
+
+ status = sessionid_traverse_read(gather_sessioninfo, (void *) &sesslist);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Session traverse failed: %s\n", nt_errstr(status));
+ TALLOC_FREE(sesslist.sessions);
+ *session_list = NULL;
+ return 0;
+ }
+
+ *session_list = sesslist.sessions;
+ return sesslist.count;
+}
+
+/********************************************************************
+find the sessions that match the given username and machine
+********************************************************************/
+
+int find_sessions(TALLOC_CTX *mem_ctx, const char *username,
+ const char *machine, struct sessionid **session_list)
+{
+ struct session_list sesslist;
+ NTSTATUS status;
+
+ sesslist.mem_ctx = mem_ctx;
+ sesslist.count = 0;
+ sesslist.filter_user = username;
+ sesslist.filter_machine = machine;
+ sesslist.sessions = NULL;
+
+ status = sessionid_traverse_read(gather_sessioninfo, (void *)&sesslist);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("Session traverse failed: %s\n", nt_errstr(status)));
+ TALLOC_FREE(sesslist.sessions);
+ *session_list = NULL;
+ return 0;
+ }
+
+ *session_list = sesslist.sessions;
+ return sesslist.count;
+}
diff --git a/source3/smbd/share_access.c b/source3/smbd/share_access.c
new file mode 100644
index 0000000..4592814
--- /dev/null
+++ b/source3/smbd/share_access.c
@@ -0,0 +1,291 @@
+/*
+ Unix SMB/CIFS implementation.
+ Check access based on valid users, read list and friends
+ Copyright (C) Volker Lendecke 2005
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/security/security.h"
+#include "passdb/lookup_sid.h"
+#include "auth.h"
+#include "source3/lib/substitute.h"
+
+/*
+ * No prefix means direct username
+ * @name means netgroup first, then unix group
+ * &name means netgroup
+ * +name means unix group
+ * + and & may be combined
+ */
+
+static bool do_group_checks(const char **name, const char **pattern)
+{
+ if ((*name)[0] == '@') {
+ *pattern = "&+";
+ *name += 1;
+ return True;
+ }
+
+ if (((*name)[0] == '+') && ((*name)[1] == '&')) {
+ *pattern = "+&";
+ *name += 2;
+ return True;
+ }
+
+ if ((*name)[0] == '+') {
+ *pattern = "+";
+ *name += 1;
+ return True;
+ }
+
+ if (((*name)[0] == '&') && ((*name)[1] == '+')) {
+ *pattern = "&+";
+ *name += 2;
+ return True;
+ }
+
+ if ((*name)[0] == '&') {
+ *pattern = "&";
+ *name += 1;
+ return True;
+ }
+
+ return False;
+}
+
+static bool token_contains_name(TALLOC_CTX *mem_ctx,
+ const char *username,
+ const char *domain,
+ const char *sharename,
+ const struct security_token *token,
+ const char *name)
+{
+ const char *prefix;
+ struct dom_sid sid;
+ enum lsa_SidType type;
+
+ if (username != NULL) {
+ size_t domain_len = domain != NULL ? strlen(domain) : 0;
+
+ /* Check if username starts with domain name */
+ if (domain_len > 0) {
+ const char *sep = lp_winbind_separator();
+ int cmp = strncasecmp_m(username, domain, domain_len);
+ if (cmp == 0 && sep[0] == username[domain_len]) {
+ /* Move after the winbind separator */
+ domain_len += 1;
+ } else {
+ domain_len = 0;
+ }
+ }
+ name = talloc_sub_basic(mem_ctx,
+ username + domain_len,
+ domain,
+ name);
+ }
+ if (sharename != NULL) {
+ name = talloc_string_sub(mem_ctx, name, "%S", sharename);
+ }
+
+ if (name == NULL) {
+ /* This is too security sensitive, better panic than return a
+ * result that might be interpreted in a wrong way. */
+ smb_panic("substitutions failed");
+ }
+
+ if ( string_to_sid( &sid, name ) ) {
+ DEBUG(5,("token_contains_name: Checking for SID [%s] in token\n", name));
+ return nt_token_check_sid( &sid, token );
+ }
+
+ if (!do_group_checks(&name, &prefix)) {
+ if (!lookup_name_smbconf(mem_ctx, name, LOOKUP_NAME_ALL,
+ NULL, NULL, &sid, &type)) {
+ DEBUG(5, ("lookup_name %s failed\n", name));
+ return False;
+ }
+ if (type != SID_NAME_USER) {
+ DEBUG(5, ("%s is a %s, expected a user\n",
+ name, sid_type_lookup(type)));
+ return False;
+ }
+ return nt_token_check_sid(&sid, token);
+ }
+
+ for (/* initialized above */ ; *prefix != '\0'; prefix++) {
+ if (*prefix == '+') {
+ if (!lookup_name_smbconf(mem_ctx, name,
+ LOOKUP_NAME_ALL|LOOKUP_NAME_GROUP,
+ NULL, NULL, &sid, &type)) {
+ DEBUG(5, ("lookup_name %s failed\n", name));
+ return False;
+ }
+ if ((type != SID_NAME_DOM_GRP) &&
+ (type != SID_NAME_ALIAS) &&
+ (type != SID_NAME_WKN_GRP)) {
+ DEBUG(5, ("%s is a %s, expected a group\n",
+ name, sid_type_lookup(type)));
+ return False;
+ }
+ if (nt_token_check_sid(&sid, token)) {
+ return True;
+ }
+ continue;
+ }
+ if (*prefix == '&') {
+ if (username) {
+ if (user_in_netgroup(mem_ctx, username, name)) {
+ return True;
+ }
+ }
+ continue;
+ }
+ smb_panic("got invalid prefix from do_groups_check");
+ }
+ return False;
+}
+
+/*
+ * Check whether a user is contained in the list provided.
+ *
+ * Please note that the user name and share names passed in here mainly for
+ * the substitution routines that expand the parameter values, the decision
+ * whether a user is in the list is done after a lookup_name on the expanded
+ * parameter value, solely based on comparing the SIDs in token.
+ *
+ * The other use is the netgroup check when using @group or &group.
+ */
+
+bool token_contains_name_in_list(const char *username,
+ const char *domain,
+ const char *sharename,
+ const struct security_token *token,
+ const char **list)
+{
+ if (list == NULL) {
+ return False;
+ }
+ while (*list != NULL) {
+ TALLOC_CTX *frame = talloc_stackframe();
+ bool ret;
+
+ ret = token_contains_name(frame, username, domain, sharename,
+ token, *list);
+ TALLOC_FREE(frame);
+ if (ret) {
+ return true;
+ }
+ list += 1;
+ }
+ return False;
+}
+
+/*
+ * Check whether the user described by "token" has access to share snum.
+ *
+ * This looks at "invalid users" and "valid users".
+ *
+ * Please note that the user name and share names passed in here mainly for
+ * the substitution routines that expand the parameter values, the decision
+ * whether a user is in the list is done after a lookup_name on the expanded
+ * parameter value, solely based on comparing the SIDs in token.
+ *
+ * The other use is the netgroup check when using @group or &group.
+ */
+
+bool user_ok_token(const char *username, const char *domain,
+ const struct security_token *token, int snum)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ if (lp_invalid_users(snum) != NULL) {
+ if (token_contains_name_in_list(username, domain,
+ lp_servicename(talloc_tos(), lp_sub, snum),
+ token,
+ lp_invalid_users(snum))) {
+ DEBUG(10, ("User %s in 'invalid users'\n", username));
+ return False;
+ }
+ }
+
+ if (lp_valid_users(snum) != NULL) {
+ if (!token_contains_name_in_list(username, domain,
+ lp_servicename(talloc_tos(), lp_sub, snum),
+ token,
+ lp_valid_users(snum))) {
+ DEBUG(10, ("User %s not in 'valid users'\n",
+ username));
+ return False;
+ }
+ }
+
+ DEBUG(10, ("user_ok_token: share %s is ok for unix user %s\n",
+ lp_servicename(talloc_tos(), lp_sub, snum), username));
+
+ return True;
+}
+
+/*
+ * Check whether the user described by "token" is restricted to read-only
+ * access on share snum.
+ *
+ * This looks at "read list", "write list" and "read only".
+ *
+ * Please note that the user name and share names passed in here mainly for
+ * the substitution routines that expand the parameter values, the decision
+ * whether a user is in the list is done after a lookup_name on the expanded
+ * parameter value, solely based on comparing the SIDs in token.
+ *
+ * The other use is the netgroup check when using @group or &group.
+ */
+
+bool is_share_read_only_for_token(const char *username,
+ const char *domain,
+ const struct security_token *token,
+ connection_struct *conn)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int snum = SNUM(conn);
+ bool result = conn->read_only;
+
+ if (lp_read_list(snum) != NULL) {
+ if (token_contains_name_in_list(username, domain,
+ lp_servicename(talloc_tos(), lp_sub, snum),
+ token,
+ lp_read_list(snum))) {
+ result = True;
+ }
+ }
+
+ if (lp_write_list(snum) != NULL) {
+ if (token_contains_name_in_list(username, domain,
+ lp_servicename(talloc_tos(), lp_sub, snum),
+ token,
+ lp_write_list(snum))) {
+ result = False;
+ }
+ }
+
+ DEBUG(10,("is_share_read_only_for_user: share %s is %s for unix user "
+ "%s\n", lp_servicename(talloc_tos(), lp_sub, snum),
+ result ? "read-only" : "read-write", username));
+
+ return result;
+}
diff --git a/source3/smbd/smb1_aio.c b/source3/smbd/smb1_aio.c
new file mode 100644
index 0000000..d54a372
--- /dev/null
+++ b/source3/smbd/smb1_aio.c
@@ -0,0 +1,409 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 3.0
+ async_io read handling using POSIX async io.
+ Copyright (C) Jeremy Allison 2005.
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "../lib/util/tevent_unix.h"
+
+static void aio_pread_smb1_done(struct tevent_req *req);
+
+/****************************************************************************
+ Set up an aio request from a SMBreadX call.
+*****************************************************************************/
+
+NTSTATUS schedule_aio_read_and_X(connection_struct *conn,
+ struct smb_request *smbreq,
+ files_struct *fsp, off_t startpos,
+ size_t smb_maxcnt)
+{
+ struct aio_extra *aio_ex;
+ size_t bufsize;
+ size_t min_aio_read_size = lp_aio_read_size(SNUM(conn));
+ struct tevent_req *req;
+ bool ok;
+
+ ok = vfs_valid_pread_range(startpos, smb_maxcnt);
+ if (!ok) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (fsp_is_alternate_stream(fsp)) {
+ DEBUG(10, ("AIO on streams not yet supported\n"));
+ return NT_STATUS_RETRY;
+ }
+
+ if ((!min_aio_read_size || (smb_maxcnt < min_aio_read_size))
+ && !SMB_VFS_AIO_FORCE(fsp)) {
+ /* Too small a read for aio request. */
+ DEBUG(10,("schedule_aio_read_and_X: read size (%u) too small "
+ "for minimum aio_read of %u\n",
+ (unsigned int)smb_maxcnt,
+ (unsigned int)min_aio_read_size ));
+ return NT_STATUS_RETRY;
+ }
+
+ /* Only do this on non-chained and non-chaining reads */
+ if (req_is_in_chain(smbreq)) {
+ return NT_STATUS_RETRY;
+ }
+
+ /* The following is safe from integer wrap as we've already checked
+ smb_maxcnt is 128k or less. Wct is 12 for read replies */
+
+ bufsize = smb_size + 12 * 2 + smb_maxcnt + 1 /* padding byte */;
+
+ if ((aio_ex = create_aio_extra(NULL, fsp, bufsize)) == NULL) {
+ DEBUG(10,("schedule_aio_read_and_X: malloc fail.\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ construct_smb1_reply_common_req(smbreq, (char *)aio_ex->outbuf.data);
+ srv_smb1_set_message((char *)aio_ex->outbuf.data, 12, 0, True);
+ SCVAL(aio_ex->outbuf.data,smb_vwv0,0xFF); /* Never a chained reply. */
+ SCVAL(smb_buf(aio_ex->outbuf.data), 0, 0); /* padding byte */
+
+ init_strict_lock_struct(fsp,
+ (uint64_t)smbreq->smbpid,
+ (uint64_t)startpos,
+ (uint64_t)smb_maxcnt,
+ READ_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &aio_ex->lock);
+
+ /* Take the lock until the AIO completes. */
+ if (!SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, &aio_ex->lock)) {
+ TALLOC_FREE(aio_ex);
+ return NT_STATUS_FILE_LOCK_CONFLICT;
+ }
+
+ aio_ex->nbyte = smb_maxcnt;
+ aio_ex->offset = startpos;
+
+ req = SMB_VFS_PREAD_SEND(aio_ex, fsp->conn->sconn->ev_ctx,
+ fsp,
+ smb_buf(aio_ex->outbuf.data) + 1 /* pad */,
+ smb_maxcnt, startpos);
+ if (req == NULL) {
+ DEBUG(0,("schedule_aio_read_and_X: aio_read failed. "
+ "Error %s\n", strerror(errno) ));
+ TALLOC_FREE(aio_ex);
+ return NT_STATUS_RETRY;
+ }
+ tevent_req_set_callback(req, aio_pread_smb1_done, aio_ex);
+
+ if (!aio_add_req_to_fsp(fsp, req)) {
+ DEBUG(1, ("Could not add req to fsp\n"));
+ TALLOC_FREE(aio_ex);
+ return NT_STATUS_RETRY;
+ }
+
+ aio_ex->smbreq = talloc_move(aio_ex, &smbreq);
+
+ DEBUG(10,("schedule_aio_read_and_X: scheduled aio_read for file %s, "
+ "offset %.0f, len = %u (mid = %u)\n",
+ fsp_str_dbg(fsp), (double)startpos, (unsigned int)smb_maxcnt,
+ (unsigned int)aio_ex->smbreq->mid ));
+
+ return NT_STATUS_OK;
+}
+
+static void aio_pread_smb1_done(struct tevent_req *req)
+{
+ struct aio_extra *aio_ex = tevent_req_callback_data(
+ req, struct aio_extra);
+ files_struct *fsp = aio_ex->fsp;
+ size_t outsize;
+ char *outbuf = (char *)aio_ex->outbuf.data;
+ ssize_t nread;
+ struct vfs_aio_state vfs_aio_state;
+
+ nread = SMB_VFS_PREAD_RECV(req, &vfs_aio_state);
+ TALLOC_FREE(req);
+
+ DEBUG(10, ("pread_recv returned %d, err = %s\n", (int)nread,
+ (nread == -1) ? strerror(vfs_aio_state.error) : "no error"));
+
+ if (fsp == NULL) {
+ DEBUG( 3, ("aio_pread_smb1_done: file closed whilst "
+ "aio outstanding (mid[%llu]).\n",
+ (unsigned long long)aio_ex->smbreq->mid));
+ TALLOC_FREE(aio_ex);
+ return;
+ }
+
+ if (nread < 0) {
+ DEBUG( 3, ("handle_aio_read_complete: file %s nread == %d. "
+ "Error = %s\n", fsp_str_dbg(fsp), (int)nread,
+ strerror(vfs_aio_state.error)));
+
+ ERROR_NT(map_nt_error_from_unix(vfs_aio_state.error));
+ outsize = srv_smb1_set_message(outbuf,0,0,true);
+ } else {
+ outsize = setup_readX_header(outbuf, nread);
+
+ fh_set_pos(aio_ex->fsp->fh, aio_ex->offset + nread);
+ fh_set_position_information(aio_ex->fsp->fh,
+ fh_get_pos(aio_ex->fsp->fh));
+
+ DEBUG( 3, ("handle_aio_read_complete file %s max=%d "
+ "nread=%d\n", fsp_str_dbg(fsp),
+ (int)aio_ex->nbyte, (int)nread ) );
+
+ }
+
+ if (outsize <= 4) {
+ DBG_INFO("Invalid outsize (%zu)\n", outsize);
+ TALLOC_FREE(aio_ex);
+ return;
+ }
+ outsize -= 4;
+ _smb_setlen_large(outbuf, outsize);
+
+ show_msg(outbuf);
+ if (!smb1_srv_send(aio_ex->smbreq->xconn,
+ outbuf,
+ true,
+ aio_ex->smbreq->seqnum + 1,
+ IS_CONN_ENCRYPTED(fsp->conn))) {
+ exit_server_cleanly("handle_aio_read_complete: smb1_srv_send "
+ "failed.");
+ }
+
+ DEBUG(10, ("handle_aio_read_complete: scheduled aio_read completed "
+ "for file %s, offset %.0f, len = %u\n",
+ fsp_str_dbg(fsp), (double)aio_ex->offset,
+ (unsigned int)nread));
+
+ TALLOC_FREE(aio_ex);
+}
+
+static void aio_pwrite_smb1_done(struct tevent_req *req);
+
+/****************************************************************************
+ Set up an aio request from a SMBwriteX call.
+*****************************************************************************/
+
+NTSTATUS schedule_aio_write_and_X(connection_struct *conn,
+ struct smb_request *smbreq,
+ files_struct *fsp, const char *data,
+ off_t startpos,
+ size_t numtowrite)
+{
+ struct aio_extra *aio_ex;
+ size_t bufsize;
+ size_t min_aio_write_size = lp_aio_write_size(SNUM(conn));
+ struct tevent_req *req;
+
+ if (fsp_is_alternate_stream(fsp)) {
+ DEBUG(10, ("AIO on streams not yet supported\n"));
+ return NT_STATUS_RETRY;
+ }
+
+ if ((!min_aio_write_size || (numtowrite < min_aio_write_size))
+ && !SMB_VFS_AIO_FORCE(fsp)) {
+ /* Too small a write for aio request. */
+ DEBUG(10,("schedule_aio_write_and_X: write size (%u) too "
+ "small for minimum aio_write of %u\n",
+ (unsigned int)numtowrite,
+ (unsigned int)min_aio_write_size ));
+ return NT_STATUS_RETRY;
+ }
+
+ /* Only do this on non-chained and non-chaining writes */
+ if (req_is_in_chain(smbreq)) {
+ return NT_STATUS_RETRY;
+ }
+
+ bufsize = smb_size + 6*2;
+
+ if (!(aio_ex = create_aio_extra(NULL, fsp, bufsize))) {
+ DEBUG(0,("schedule_aio_write_and_X: malloc fail.\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ aio_ex->write_through = BITSETW(smbreq->vwv+7,0);
+
+ construct_smb1_reply_common_req(smbreq, (char *)aio_ex->outbuf.data);
+ srv_smb1_set_message((char *)aio_ex->outbuf.data, 6, 0, True);
+ SCVAL(aio_ex->outbuf.data,smb_vwv0,0xFF); /* Never a chained reply. */
+
+ init_strict_lock_struct(fsp,
+ (uint64_t)smbreq->smbpid,
+ (uint64_t)startpos,
+ (uint64_t)numtowrite,
+ WRITE_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &aio_ex->lock);
+
+ /* Take the lock until the AIO completes. */
+ if (!SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, &aio_ex->lock)) {
+ TALLOC_FREE(aio_ex);
+ return NT_STATUS_FILE_LOCK_CONFLICT;
+ }
+
+ aio_ex->nbyte = numtowrite;
+ aio_ex->offset = startpos;
+
+ req = pwrite_fsync_send(aio_ex, fsp->conn->sconn->ev_ctx, fsp,
+ data, numtowrite, startpos,
+ aio_ex->write_through);
+ if (req == NULL) {
+ DEBUG(3,("schedule_aio_wrote_and_X: aio_write failed. "
+ "Error %s\n", strerror(errno) ));
+ TALLOC_FREE(aio_ex);
+ return NT_STATUS_RETRY;
+ }
+ tevent_req_set_callback(req, aio_pwrite_smb1_done, aio_ex);
+
+ if (!aio_add_req_to_fsp(fsp, req)) {
+ DEBUG(1, ("Could not add req to fsp\n"));
+ TALLOC_FREE(aio_ex);
+ return NT_STATUS_RETRY;
+ }
+
+ aio_ex->smbreq = talloc_move(aio_ex, &smbreq);
+
+ /* This should actually be improved to span the write. */
+ contend_level2_oplocks_begin(fsp, LEVEL2_CONTEND_WRITE);
+ contend_level2_oplocks_end(fsp, LEVEL2_CONTEND_WRITE);
+
+ if (!aio_ex->write_through && !lp_sync_always(SNUM(fsp->conn))
+ && fsp->fsp_flags.aio_write_behind)
+ {
+ /* Lie to the client and immediately claim we finished the
+ * write. */
+ SSVAL(aio_ex->outbuf.data,smb_vwv2,numtowrite);
+ SSVAL(aio_ex->outbuf.data,smb_vwv4,(numtowrite>>16)&1);
+ show_msg((char *)aio_ex->outbuf.data);
+ if (!smb1_srv_send(aio_ex->smbreq->xconn,
+ (char *)aio_ex->outbuf.data,
+ true,
+ aio_ex->smbreq->seqnum + 1,
+ IS_CONN_ENCRYPTED(fsp->conn))) {
+ exit_server_cleanly("schedule_aio_write_and_X: "
+ "smb1_srv_send failed.");
+ }
+ DEBUG(10,("schedule_aio_write_and_X: scheduled aio_write "
+ "behind for file %s\n", fsp_str_dbg(fsp)));
+ }
+
+ DEBUG(10,("schedule_aio_write_and_X: scheduled aio_write for file "
+ "%s, offset %.0f, len = %u (mid = %u)\n",
+ fsp_str_dbg(fsp), (double)startpos, (unsigned int)numtowrite,
+ (unsigned int)aio_ex->smbreq->mid));
+
+ return NT_STATUS_OK;
+}
+
+static void aio_pwrite_smb1_done(struct tevent_req *req)
+{
+ struct aio_extra *aio_ex = tevent_req_callback_data(
+ req, struct aio_extra);
+ files_struct *fsp = aio_ex->fsp;
+ char *outbuf = (char *)aio_ex->outbuf.data;
+ ssize_t numtowrite = aio_ex->nbyte;
+ ssize_t nwritten;
+ int err;
+
+ nwritten = pwrite_fsync_recv(req, &err);
+ TALLOC_FREE(req);
+
+ DEBUG(10, ("pwrite_recv returned %d, err = %s\n", (int)nwritten,
+ (nwritten == -1) ? strerror(err) : "no error"));
+
+ if (fsp == NULL) {
+ DEBUG( 3, ("aio_pwrite_smb1_done: file closed whilst "
+ "aio outstanding (mid[%llu]).\n",
+ (unsigned long long)aio_ex->smbreq->mid));
+ TALLOC_FREE(aio_ex);
+ return;
+ }
+
+ mark_file_modified(fsp);
+
+ if (fsp->fsp_flags.aio_write_behind) {
+
+ if (nwritten != numtowrite) {
+ if (nwritten == -1) {
+ DEBUG(5,("handle_aio_write_complete: "
+ "aio_write_behind failed ! File %s "
+ "is corrupt ! Error %s\n",
+ fsp_str_dbg(fsp), strerror(err)));
+ } else {
+ DEBUG(0,("handle_aio_write_complete: "
+ "aio_write_behind failed ! File %s "
+ "is corrupt ! Wanted %u bytes but "
+ "only wrote %d\n", fsp_str_dbg(fsp),
+ (unsigned int)numtowrite,
+ (int)nwritten ));
+ }
+ } else {
+ DEBUG(10,("handle_aio_write_complete: "
+ "aio_write_behind completed for file %s\n",
+ fsp_str_dbg(fsp)));
+ }
+ /* TODO: should no return success in case of an error !!! */
+ TALLOC_FREE(aio_ex);
+ return;
+ }
+
+ /* We don't need outsize or set_message here as we've already set the
+ fixed size length when we set up the aio call. */
+
+ if (nwritten == -1) {
+ DEBUG(3, ("handle_aio_write: file %s wanted %u bytes. "
+ "nwritten == %d. Error = %s\n",
+ fsp_str_dbg(fsp), (unsigned int)numtowrite,
+ (int)nwritten, strerror(err)));
+
+ ERROR_NT(map_nt_error_from_unix(err));
+ srv_smb1_set_message(outbuf,0,0,true);
+ } else {
+ SSVAL(outbuf,smb_vwv2,nwritten);
+ SSVAL(outbuf,smb_vwv4,(nwritten>>16)&1);
+ if (nwritten < (ssize_t)numtowrite) {
+ SCVAL(outbuf,smb_rcls,ERRHRD);
+ SSVAL(outbuf,smb_err,ERRdiskfull);
+ }
+
+ DEBUG(3,("handle_aio_write: %s, num=%d wrote=%d\n",
+ fsp_fnum_dbg(fsp), (int)numtowrite, (int)nwritten));
+
+ fh_set_pos(aio_ex->fsp->fh, aio_ex->offset + nwritten);
+ }
+
+ show_msg(outbuf);
+ if (!smb1_srv_send(aio_ex->smbreq->xconn,
+ outbuf,
+ true,
+ aio_ex->smbreq->seqnum + 1,
+ IS_CONN_ENCRYPTED(fsp->conn))) {
+ exit_server_cleanly("handle_aio_write_complete: "
+ "smb1_srv_send failed.");
+ }
+
+ DEBUG(10, ("handle_aio_write_complete: scheduled aio_write completed "
+ "for file %s, offset %.0f, requested %u, written = %u\n",
+ fsp_str_dbg(fsp), (double)aio_ex->offset,
+ (unsigned int)numtowrite, (unsigned int)nwritten));
+
+ TALLOC_FREE(aio_ex);
+}
diff --git a/source3/smbd/smb1_aio.h b/source3/smbd/smb1_aio.h
new file mode 100644
index 0000000..d13bbb9
--- /dev/null
+++ b/source3/smbd/smb1_aio.h
@@ -0,0 +1,29 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 3.0
+ async_io read handling using POSIX async io.
+ Copyright (C) Jeremy Allison 2005.
+
+ 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/>.
+*/
+
+NTSTATUS schedule_aio_read_and_X(connection_struct *conn,
+ struct smb_request *req,
+ files_struct *fsp, off_t startpos,
+ size_t smb_maxcnt);
+NTSTATUS schedule_aio_write_and_X(connection_struct *conn,
+ struct smb_request *req,
+ files_struct *fsp, const char *data,
+ off_t startpos,
+ size_t numtowrite);
diff --git a/source3/smbd/smb1_ipc.c b/source3/smbd/smb1_ipc.c
new file mode 100644
index 0000000..716b67b
--- /dev/null
+++ b/source3/smbd/smb1_ipc.c
@@ -0,0 +1,949 @@
+/*
+ Unix SMB/CIFS implementation.
+ Inter-process communication and named pipe handling
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ SMB Version handling
+ Copyright (C) John H Terpstra 1995-1998
+
+ 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/>.
+ */
+/*
+ This file handles the named pipe and mailslot calls
+ in the SMBtrans protocol
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "smbprofile.h"
+#include "rpc_server/srv_pipe_hnd.h"
+#include "source3/lib/substitute.h"
+
+#define NERR_notsupported 50
+
+static void api_no_reply(connection_struct *conn, struct smb_request *req);
+
+/*******************************************************************
+ copies parameters and data, as needed, into the smb buffer
+
+ *both* the data and params sections should be aligned. this
+ is fudged in the rpc pipes by
+ at present, only the data section is. this may be a possible
+ cause of some of the ipc problems being experienced. lkcl26dec97
+
+ ******************************************************************/
+
+static void copy_trans_params_and_data(char *outbuf, int align,
+ char *rparam, int param_offset, int param_len,
+ char *rdata, int data_offset, int data_len)
+{
+ char *copy_into = smb_buf(outbuf);
+
+ if(param_len < 0)
+ param_len = 0;
+
+ if(data_len < 0)
+ data_len = 0;
+
+ DEBUG(5,("copy_trans_params_and_data: params[%d..%d] data[%d..%d] (align %d)\n",
+ param_offset, param_offset + param_len,
+ data_offset , data_offset + data_len,
+ align));
+
+ *copy_into = '\0';
+
+ copy_into += 1;
+
+ if (param_len)
+ memcpy(copy_into, &rparam[param_offset], param_len);
+
+ copy_into += param_len;
+ if (align) {
+ memset(copy_into, '\0', align);
+ }
+
+ copy_into += align;
+
+ if (data_len )
+ memcpy(copy_into, &rdata[data_offset], data_len);
+}
+
+/****************************************************************************
+ Send a trans reply.
+ ****************************************************************************/
+
+void send_trans_reply(connection_struct *conn,
+ struct smb_request *req,
+ char *rparam, int rparam_len,
+ char *rdata, int rdata_len,
+ bool buffer_too_large)
+{
+ int this_ldata,this_lparam;
+ int tot_data_sent = 0;
+ int tot_param_sent = 0;
+ int align;
+
+ int ldata = rdata ? rdata_len : 0;
+ int lparam = rparam ? rparam_len : 0;
+ struct smbXsrv_connection *xconn = req->xconn;
+ int max_send = xconn->smb1.sessions.max_send;
+ /* HACK: make sure we send at least 128 byte in one go */
+ int hdr_overhead = SMB_BUFFER_SIZE_MIN - 128;
+
+ if (buffer_too_large)
+ DEBUG(5,("send_trans_reply: buffer %d too large\n", ldata ));
+
+ this_lparam = MIN(lparam,max_send - hdr_overhead);
+ this_ldata = MIN(ldata,max_send - (hdr_overhead+this_lparam));
+
+ align = ((this_lparam)%4);
+
+ reply_smb1_outbuf(req, 10, 1+align+this_ldata+this_lparam);
+
+ /*
+ * We might have SMBtranss in req which was transferred to the outbuf,
+ * fix that.
+ */
+ SCVAL(req->outbuf, smb_com, SMBtrans);
+
+ copy_trans_params_and_data((char *)req->outbuf, align,
+ rparam, tot_param_sent, this_lparam,
+ rdata, tot_data_sent, this_ldata);
+
+ SSVAL(req->outbuf,smb_vwv0,lparam);
+ SSVAL(req->outbuf,smb_vwv1,ldata);
+ SSVAL(req->outbuf,smb_vwv3,this_lparam);
+ SSVAL(req->outbuf,smb_vwv4,
+ smb_offset(smb_buf(req->outbuf)+1, req->outbuf));
+ SSVAL(req->outbuf,smb_vwv5,0);
+ SSVAL(req->outbuf,smb_vwv6,this_ldata);
+ SSVAL(req->outbuf,smb_vwv7,
+ smb_offset(smb_buf(req->outbuf)+1+this_lparam+align,
+ req->outbuf));
+ SSVAL(req->outbuf,smb_vwv8,0);
+ SSVAL(req->outbuf,smb_vwv9,0);
+
+ if (buffer_too_large) {
+ error_packet_set((char *)req->outbuf, ERRDOS, ERRmoredata,
+ STATUS_BUFFER_OVERFLOW, __LINE__, __FILE__);
+ }
+
+ show_msg((char *)req->outbuf);
+ if (!smb1_srv_send(xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(conn))) {
+ exit_server_cleanly("send_trans_reply: smb1_srv_send failed.");
+ }
+
+ TALLOC_FREE(req->outbuf);
+
+ tot_data_sent = this_ldata;
+ tot_param_sent = this_lparam;
+
+ while (tot_data_sent < ldata || tot_param_sent < lparam)
+ {
+ this_lparam = MIN(lparam-tot_param_sent,
+ max_send - hdr_overhead);
+ this_ldata = MIN(ldata -tot_data_sent,
+ max_send - (hdr_overhead+this_lparam));
+
+ if(this_lparam < 0)
+ this_lparam = 0;
+
+ if(this_ldata < 0)
+ this_ldata = 0;
+
+ align = (this_lparam%4);
+
+ reply_smb1_outbuf(req, 10, 1+align+this_ldata+this_lparam);
+
+ /*
+ * We might have SMBtranss in req which was transferred to the
+ * outbuf, fix that.
+ */
+ SCVAL(req->outbuf, smb_com, SMBtrans);
+
+ copy_trans_params_and_data((char *)req->outbuf, align,
+ rparam, tot_param_sent, this_lparam,
+ rdata, tot_data_sent, this_ldata);
+
+ SSVAL(req->outbuf,smb_vwv0,lparam);
+ SSVAL(req->outbuf,smb_vwv1,ldata);
+
+ SSVAL(req->outbuf,smb_vwv3,this_lparam);
+ SSVAL(req->outbuf,smb_vwv4,
+ smb_offset(smb_buf(req->outbuf)+1,req->outbuf));
+ SSVAL(req->outbuf,smb_vwv5,tot_param_sent);
+ SSVAL(req->outbuf,smb_vwv6,this_ldata);
+ SSVAL(req->outbuf,smb_vwv7,
+ smb_offset(smb_buf(req->outbuf)+1+this_lparam+align,
+ req->outbuf));
+ SSVAL(req->outbuf,smb_vwv8,tot_data_sent);
+ SSVAL(req->outbuf,smb_vwv9,0);
+
+ if (buffer_too_large) {
+ error_packet_set((char *)req->outbuf,
+ ERRDOS, ERRmoredata,
+ STATUS_BUFFER_OVERFLOW,
+ __LINE__, __FILE__);
+ }
+
+ show_msg((char *)req->outbuf);
+ if (!smb1_srv_send(xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(conn))) {
+ exit_server_cleanly("send_trans_reply: smb1_srv_send "
+ "failed.");
+ }
+
+ tot_data_sent += this_ldata;
+ tot_param_sent += this_lparam;
+ TALLOC_FREE(req->outbuf);
+ }
+}
+
+/****************************************************************************
+ Start the first part of an RPC reply which began with an SMBtrans request.
+****************************************************************************/
+
+struct dcerpc_cmd_state {
+ struct fake_file_handle *handle;
+ uint8_t *data;
+ size_t num_data;
+ size_t max_read;
+};
+
+static void api_dcerpc_cmd_write_done(struct tevent_req *subreq);
+static void api_dcerpc_cmd_read_done(struct tevent_req *subreq);
+
+static void api_dcerpc_cmd(connection_struct *conn, struct smb_request *req,
+ files_struct *fsp, uint8_t *data, size_t length,
+ size_t max_read)
+{
+ struct tevent_req *subreq;
+ struct dcerpc_cmd_state *state;
+ bool busy;
+
+ if (!fsp_is_np(fsp)) {
+ api_no_reply(conn, req);
+ return;
+ }
+
+ /*
+ * Trans requests are only allowed
+ * if no other Trans or Read is active
+ */
+ busy = np_read_in_progress(fsp->fake_file_handle);
+ if (busy) {
+ reply_nterror(req, NT_STATUS_PIPE_BUSY);
+ return;
+ }
+
+ state = talloc(req, struct dcerpc_cmd_state);
+ if (state == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ req->async_priv = state;
+
+ state->handle = fsp->fake_file_handle;
+
+ /*
+ * This memdup severely sucks. But doing it properly essentially means
+ * to rewrite lanman.c, something which I don't really want to do now.
+ */
+ state->data = (uint8_t *)talloc_memdup(state, data, length);
+ if (state->data == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ state->num_data = length;
+ state->max_read = max_read;
+
+ subreq = np_write_send(state, req->sconn->ev_ctx, state->handle,
+ state->data, length);
+ if (subreq == NULL) {
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ tevent_req_set_callback(subreq, api_dcerpc_cmd_write_done,
+ talloc_move(conn, &req));
+}
+
+static void api_dcerpc_cmd_write_done(struct tevent_req *subreq)
+{
+ struct smb_request *req = tevent_req_callback_data(
+ subreq, struct smb_request);
+ struct dcerpc_cmd_state *state = talloc_get_type_abort(
+ req->async_priv, struct dcerpc_cmd_state);
+ NTSTATUS status;
+ ssize_t nwritten = -1;
+
+ status = np_write_recv(subreq, &nwritten);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ NTSTATUS old = status;
+ status = nt_status_np_pipe(old);
+
+ DEBUG(10, ("Could not write to pipe: %s%s%s\n",
+ nt_errstr(old),
+ NT_STATUS_EQUAL(old, status)?"":" => ",
+ NT_STATUS_EQUAL(old, status)?"":nt_errstr(status)));
+ reply_nterror(req, status);
+ goto send;
+ }
+ if (nwritten != state->num_data) {
+ status = NT_STATUS_PIPE_NOT_AVAILABLE;
+ DEBUG(10, ("Could not write to pipe: (%d/%d) => %s\n",
+ (int)state->num_data,
+ (int)nwritten, nt_errstr(status)));
+ reply_nterror(req, status);
+ goto send;
+ }
+
+ state->data = talloc_realloc(state, state->data, uint8_t,
+ state->max_read);
+ if (state->data == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto send;
+ }
+
+ subreq = np_read_send(state, req->sconn->ev_ctx,
+ state->handle, state->data, state->max_read);
+ if (subreq == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto send;
+ }
+ tevent_req_set_callback(subreq, api_dcerpc_cmd_read_done, req);
+ return;
+
+ send:
+ if (!smb1_srv_send(req->xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(req->conn) || req->encrypted)) {
+ exit_server_cleanly("api_dcerpc_cmd_write_done: "
+ "smb1_srv_send failed.");
+ }
+ TALLOC_FREE(req);
+}
+
+static void api_dcerpc_cmd_read_done(struct tevent_req *subreq)
+{
+ struct smb_request *req = tevent_req_callback_data(
+ subreq, struct smb_request);
+ struct dcerpc_cmd_state *state = talloc_get_type_abort(
+ req->async_priv, struct dcerpc_cmd_state);
+ NTSTATUS status;
+ ssize_t nread;
+ bool is_data_outstanding;
+
+ status = np_read_recv(subreq, &nread, &is_data_outstanding);
+ TALLOC_FREE(subreq);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ NTSTATUS old = status;
+ status = nt_status_np_pipe(old);
+
+ DEBUG(10, ("Could not read from to pipe: %s%s%s\n",
+ nt_errstr(old),
+ NT_STATUS_EQUAL(old, status)?"":" => ",
+ NT_STATUS_EQUAL(old, status)?"":nt_errstr(status)));
+ reply_nterror(req, status);
+
+ if (!smb1_srv_send(req->xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(req->conn) ||
+ req->encrypted)) {
+ exit_server_cleanly("api_dcerpc_cmd_read_done: "
+ "smb1_srv_send failed.");
+ }
+ TALLOC_FREE(req);
+ return;
+ }
+
+ send_trans_reply(req->conn, req, NULL, 0, (char *)state->data, nread,
+ is_data_outstanding);
+ TALLOC_FREE(req);
+}
+
+/****************************************************************************
+ WaitNamedPipeHandleState
+****************************************************************************/
+
+static void api_WNPHS(connection_struct *conn, struct smb_request *req,
+ struct files_struct *fsp, char *param, int param_len)
+{
+ if (!param || param_len < 2) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ DEBUG(4,("WaitNamedPipeHandleState priority %x\n",
+ (int)SVAL(param,0)));
+
+ send_trans_reply(conn, req, NULL, 0, NULL, 0, False);
+}
+
+
+/****************************************************************************
+ SetNamedPipeHandleState
+****************************************************************************/
+
+static void api_SNPHS(connection_struct *conn, struct smb_request *req,
+ struct files_struct *fsp, char *param, int param_len)
+{
+ if (!param || param_len < 2) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ DEBUG(4,("SetNamedPipeHandleState to code %x\n", (int)SVAL(param,0)));
+
+ send_trans_reply(conn, req, NULL, 0, NULL, 0, False);
+}
+
+
+/****************************************************************************
+ When no reply is generated, indicate unsupported.
+ ****************************************************************************/
+
+static void api_no_reply(connection_struct *conn, struct smb_request *req)
+{
+ char rparam[4];
+
+ /* unsupported */
+ SSVAL(rparam,0,NERR_notsupported);
+ SSVAL(rparam,2,0); /* converter word */
+
+ DEBUG(3,("Unsupported API fd command\n"));
+
+ /* now send the reply */
+ send_trans_reply(conn, req, rparam, 4, NULL, 0, False);
+
+ return;
+}
+
+/****************************************************************************
+ Handle remote api calls delivered to a named pipe already opened.
+ ****************************************************************************/
+
+static void api_fd_reply(connection_struct *conn, uint64_t vuid,
+ struct smb_request *req,
+ uint16_t *setup, uint8_t *data, char *params,
+ int suwcnt, int tdscnt, int tpscnt,
+ int mdrcnt, int mprcnt)
+{
+ struct files_struct *fsp;
+ int pnum;
+ int subcommand;
+
+ DEBUG(5,("api_fd_reply\n"));
+
+ /* First find out the name of this file. */
+ if (suwcnt != 2) {
+ DEBUG(0,("Unexpected named pipe transaction.\n"));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ /* Get the file handle and hence the file name. */
+ /*
+ * NB. The setup array has already been transformed
+ * via SVAL and so is in host byte order.
+ */
+ pnum = ((int)setup[1]) & 0xFFFF;
+ subcommand = ((int)setup[0]) & 0xFFFF;
+
+ fsp = file_fsp(req, pnum);
+
+ if (!fsp_is_np(fsp)) {
+ if (subcommand == TRANSACT_WAITNAMEDPIPEHANDLESTATE) {
+ /* Win9x does this call with a unicode pipe name, not a pnum. */
+ /* Just return success for now... */
+ DEBUG(3,("Got TRANSACT_WAITNAMEDPIPEHANDLESTATE on text pipe name\n"));
+ send_trans_reply(conn, req, NULL, 0, NULL, 0, False);
+ return;
+ }
+
+ DEBUG(1,("api_fd_reply: INVALID PIPE HANDLE: %x\n", pnum));
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ if (vuid != fsp->vuid) {
+ DEBUG(1, ("Got pipe request (pnum %x) using invalid VUID %llu, "
+ "expected %llu\n", pnum, (unsigned long long)vuid,
+ (unsigned long long)fsp->vuid));
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ DEBUG(3,("Got API command 0x%x on pipe \"%s\" (pnum %x)\n",
+ subcommand, fsp_str_dbg(fsp), pnum));
+
+ DEBUG(10, ("api_fd_reply: p:%p max_trans_reply: %d\n", fsp, mdrcnt));
+
+ switch (subcommand) {
+ case TRANSACT_DCERPCCMD: {
+ /* dce/rpc command */
+ api_dcerpc_cmd(conn, req, fsp, (uint8_t *)data, tdscnt,
+ mdrcnt);
+ break;
+ }
+ case TRANSACT_WAITNAMEDPIPEHANDLESTATE:
+ /* Wait Named Pipe Handle state */
+ api_WNPHS(conn, req, fsp, params, tpscnt);
+ break;
+ case TRANSACT_SETNAMEDPIPEHANDLESTATE:
+ /* Set Named Pipe Handle state */
+ api_SNPHS(conn, req, fsp, params, tpscnt);
+ break;
+ default:
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+}
+
+/****************************************************************************
+ Handle named pipe commands.
+****************************************************************************/
+
+static void named_pipe(connection_struct *conn, uint64_t vuid,
+ struct smb_request *req,
+ const char *name, uint16_t *setup,
+ char *data, char *params,
+ int suwcnt, int tdscnt,int tpscnt,
+ int msrcnt, int mdrcnt, int mprcnt)
+{
+ DEBUG(3,("named pipe command on <%s> name\n", name));
+
+ if (strequal(name,"LANMAN")) {
+ api_reply(conn, vuid, req,
+ data, params,
+ tdscnt, tpscnt,
+ mdrcnt, mprcnt);
+ return;
+ }
+
+ if (strequal(name,"WKSSVC") ||
+ strequal(name,"SRVSVC") ||
+ strequal(name,"WINREG") ||
+ strequal(name,"SAMR") ||
+ strequal(name,"LSARPC")) {
+
+ DEBUG(4,("named pipe command from Win95 (wow!)\n"));
+
+ api_fd_reply(conn, vuid, req,
+ setup, (uint8_t *)data, params,
+ suwcnt, tdscnt, tpscnt,
+ mdrcnt, mprcnt);
+ return;
+ }
+
+ if (strlen(name) < 1) {
+ api_fd_reply(conn, vuid, req,
+ setup, (uint8_t *)data,
+ params, suwcnt, tdscnt,
+ tpscnt, mdrcnt, mprcnt);
+ return;
+ }
+
+ if (setup)
+ DEBUG(3,("unknown named pipe: setup 0x%X setup1=%d\n",
+ (int)setup[0],(int)setup[1]));
+
+ reply_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return;
+}
+
+static void handle_trans(connection_struct *conn, struct smb_request *req,
+ struct trans_state *state)
+{
+ char *local_machine_name;
+ int name_offset = 0;
+
+ DEBUG(3,("trans <%s> data=%u params=%u setup=%u\n",
+ state->name,(unsigned int)state->total_data,(unsigned int)state->total_param,
+ (unsigned int)state->setup_count));
+
+ /*
+ * WinCE weirdness....
+ */
+
+ local_machine_name = talloc_asprintf(state, "\\%s\\",
+ get_local_machine_name());
+
+ if (local_machine_name == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ if (strnequal(state->name, local_machine_name,
+ strlen(local_machine_name))) {
+ name_offset = strlen(local_machine_name)-1;
+ }
+
+ if (!strnequal(&state->name[name_offset], "\\PIPE",
+ strlen("\\PIPE"))) {
+ reply_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return;
+ }
+
+ name_offset += strlen("\\PIPE");
+
+ /* Win9x weirdness. When talking to a unicode server Win9x
+ only sends \PIPE instead of \PIPE\ */
+
+ if (state->name[name_offset] == '\\')
+ name_offset++;
+
+ DEBUG(5,("calling named_pipe\n"));
+ named_pipe(conn, state->vuid, req,
+ state->name+name_offset,
+ state->setup,state->data,
+ state->param,
+ state->setup_count,state->total_data,
+ state->total_param,
+ state->max_setup_return,
+ state->max_data_return,
+ state->max_param_return);
+
+ if (state->close_on_completion) {
+ struct smbXsrv_tcon *tcon;
+ NTSTATUS status;
+
+ tcon = conn->tcon;
+ req->conn = NULL;
+ conn = NULL;
+
+ /*
+ * TODO: cancel all outstanding requests on the tcon
+ */
+ status = smbXsrv_tcon_disconnect(tcon, state->vuid);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("handle_trans: "
+ "smbXsrv_tcon_disconnect() failed: %s\n",
+ nt_errstr(status)));
+ /*
+ * If we hit this case, there is something completely
+ * wrong, so we better disconnect the transport connection.
+ */
+ exit_server(__location__ ": smbXsrv_tcon_disconnect failed");
+ return;
+ }
+
+ TALLOC_FREE(tcon);
+ }
+
+ return;
+}
+
+/****************************************************************************
+ Reply to a SMBtrans.
+ ****************************************************************************/
+
+void reply_trans(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ unsigned int dsoff;
+ unsigned int dscnt;
+ unsigned int psoff;
+ unsigned int pscnt;
+ struct trans_state *state;
+ NTSTATUS result;
+
+ START_PROFILE(SMBtrans);
+
+ if (req->wct < 14) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtrans);
+ return;
+ }
+
+ dsoff = SVAL(req->vwv+12, 0);
+ dscnt = SVAL(req->vwv+11, 0);
+ psoff = SVAL(req->vwv+10, 0);
+ pscnt = SVAL(req->vwv+9, 0);
+
+ result = allow_new_trans(conn->pending_trans, req->mid);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(2, ("Got invalid trans request: %s\n",
+ nt_errstr(result)));
+ reply_nterror(req, result);
+ END_PROFILE(SMBtrans);
+ return;
+ }
+
+ if ((state = talloc_zero(conn, struct trans_state)) == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtrans);
+ return;
+ }
+
+ state->cmd = SMBtrans;
+
+ state->mid = req->mid;
+ state->vuid = req->vuid;
+ state->setup_count = CVAL(req->vwv+13, 0);
+ state->setup = NULL;
+ state->total_param = SVAL(req->vwv+0, 0);
+ state->param = NULL;
+ state->total_data = SVAL(req->vwv+1, 0);
+ state->data = NULL;
+ state->max_param_return = SVAL(req->vwv+2, 0);
+ state->max_data_return = SVAL(req->vwv+3, 0);
+ state->max_setup_return = CVAL(req->vwv+4, 0);
+ state->close_on_completion = BITSETW(req->vwv+5, 0);
+ state->one_way = BITSETW(req->vwv+5, 1);
+
+ srvstr_pull_req_talloc(state, req, &state->name, req->buf,
+ STR_TERMINATE);
+
+ if ((dscnt > state->total_data) || (pscnt > state->total_param) ||
+ !state->name)
+ goto bad_param;
+
+ if (state->total_data) {
+
+ if (smb_buffer_oob(state->total_data, 0, dscnt)
+ || smb_buffer_oob(smb_len(req->inbuf), dsoff, dscnt)) {
+ goto bad_param;
+ }
+
+ /* Can't use talloc here, the core routines do realloc on the
+ * params and data. Out of paranoia, 100 bytes too many. */
+ state->data = (char *)SMB_MALLOC(state->total_data+100);
+ if (state->data == NULL) {
+ DEBUG(0,("reply_trans: data malloc fail for %u "
+ "bytes !\n", (unsigned int)state->total_data));
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtrans);
+ return;
+ }
+ /* null-terminate the slack space */
+ memset(&state->data[state->total_data], 0, 100);
+
+ memcpy(state->data,smb_base(req->inbuf)+dsoff,dscnt);
+ }
+
+ if (state->total_param) {
+
+ if (smb_buffer_oob(state->total_param, 0, pscnt)
+ || smb_buffer_oob(smb_len(req->inbuf), psoff, pscnt)) {
+ goto bad_param;
+ }
+
+ /* Can't use talloc here, the core routines do realloc on the
+ * params and data. Out of paranoia, 100 bytes too many */
+ state->param = (char *)SMB_MALLOC(state->total_param+100);
+ if (state->param == NULL) {
+ DEBUG(0,("reply_trans: param malloc fail for %u "
+ "bytes !\n", (unsigned int)state->total_param));
+ SAFE_FREE(state->data);
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtrans);
+ return;
+ }
+ /* null-terminate the slack space */
+ memset(&state->param[state->total_param], 0, 100);
+
+ memcpy(state->param,smb_base(req->inbuf)+psoff,pscnt);
+ }
+
+ state->received_data = dscnt;
+ state->received_param = pscnt;
+
+ if (state->setup_count) {
+ unsigned int i;
+
+ /*
+ * No overflow possible here, state->setup_count is an
+ * unsigned int, being filled by a single byte from
+ * CVAL(req->vwv+13, 0) above. The cast in the comparison
+ * below is not necessary, it's here to clarify things. The
+ * validity of req->vwv and req->wct has been checked in
+ * init_smb1_request already.
+ */
+ if (state->setup_count + 14 > (unsigned int)req->wct) {
+ goto bad_param;
+ }
+
+ if((state->setup = talloc_array(
+ state, uint16_t, state->setup_count)) == NULL) {
+ DEBUG(0,("reply_trans: setup malloc fail for %u "
+ "bytes !\n", (unsigned int)
+ (state->setup_count * sizeof(uint16_t))));
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtrans);
+ return;
+ }
+
+ for (i=0;i<state->setup_count;i++) {
+ state->setup[i] = SVAL(req->vwv + 14 + i, 0);
+ }
+ }
+
+ state->received_param = pscnt;
+
+ if ((state->received_param != state->total_param) ||
+ (state->received_data != state->total_data)) {
+ DLIST_ADD(conn->pending_trans, state);
+
+ /* We need to send an interim response then receive the rest
+ of the parameter/data bytes */
+ reply_smb1_outbuf(req, 0, 0);
+ show_msg((char *)req->outbuf);
+ END_PROFILE(SMBtrans);
+ return;
+ }
+
+ talloc_steal(talloc_tos(), state);
+
+ handle_trans(conn, req, state);
+
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+
+ END_PROFILE(SMBtrans);
+ return;
+
+ bad_param:
+
+ DEBUG(0,("reply_trans: invalid trans parameters\n"));
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ END_PROFILE(SMBtrans);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+}
+
+/****************************************************************************
+ Reply to a secondary SMBtrans.
+ ****************************************************************************/
+
+void reply_transs(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ unsigned int pcnt,poff,dcnt,doff,pdisp,ddisp;
+ struct trans_state *state;
+
+ START_PROFILE(SMBtranss);
+
+ show_msg((const char *)req->inbuf);
+
+ if (req->wct < 8) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtranss);
+ return;
+ }
+
+ for (state = conn->pending_trans; state != NULL;
+ state = state->next) {
+ if (state->mid == req->mid) {
+ break;
+ }
+ }
+
+ if ((state == NULL) || (state->cmd != SMBtrans)) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtranss);
+ return;
+ }
+
+ /* Revise total_params and total_data in case they have changed
+ * downwards */
+
+ if (SVAL(req->vwv+0, 0) < state->total_param)
+ state->total_param = SVAL(req->vwv+0, 0);
+ if (SVAL(req->vwv+1, 0) < state->total_data)
+ state->total_data = SVAL(req->vwv+1, 0);
+
+ pcnt = SVAL(req->vwv+2, 0);
+ poff = SVAL(req->vwv+3, 0);
+ pdisp = SVAL(req->vwv+4, 0);
+
+ dcnt = SVAL(req->vwv+5, 0);
+ doff = SVAL(req->vwv+6, 0);
+ ddisp = SVAL(req->vwv+7, 0);
+
+ state->received_param += pcnt;
+ state->received_data += dcnt;
+
+ if ((state->received_data > state->total_data) ||
+ (state->received_param > state->total_param))
+ goto bad_param;
+
+ if (pcnt) {
+ if (smb_buffer_oob(state->total_param, pdisp, pcnt)
+ || smb_buffer_oob(smb_len(req->inbuf), poff, pcnt)) {
+ goto bad_param;
+ }
+ memcpy(state->param+pdisp,smb_base(req->inbuf)+poff,pcnt);
+ }
+
+ if (dcnt) {
+ if (smb_buffer_oob(state->total_data, ddisp, dcnt)
+ || smb_buffer_oob(smb_len(req->inbuf), doff, dcnt)) {
+ goto bad_param;
+ }
+ memcpy(state->data+ddisp, smb_base(req->inbuf)+doff,dcnt);
+ }
+
+ if ((state->received_param < state->total_param) ||
+ (state->received_data < state->total_data)) {
+ END_PROFILE(SMBtranss);
+ return;
+ }
+
+ talloc_steal(talloc_tos(), state);
+
+ handle_trans(conn, req, state);
+
+ DLIST_REMOVE(conn->pending_trans, state);
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+
+ END_PROFILE(SMBtranss);
+ return;
+
+ bad_param:
+
+ DEBUG(0,("reply_transs: invalid trans parameters\n"));
+ DLIST_REMOVE(conn->pending_trans, state);
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtranss);
+ return;
+}
diff --git a/source3/smbd/smb1_ipc.h b/source3/smbd/smb1_ipc.h
new file mode 100644
index 0000000..5866f21
--- /dev/null
+++ b/source3/smbd/smb1_ipc.h
@@ -0,0 +1,33 @@
+/*
+ Unix SMB/CIFS implementation.
+ Inter-process communication and named pipe handling
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ SMB Version handling
+ Copyright (C) John H Terpstra 1995-1998
+
+ 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/>.
+ */
+/*
+ This file handles the named pipe and mailslot calls
+ in the SMBtrans protocol
+ */
+
+void send_trans_reply(connection_struct *conn,
+ struct smb_request *req,
+ char *rparam, int rparam_len,
+ char *rdata, int rdata_len,
+ bool buffer_too_large);
+void reply_trans(struct smb_request *req);
+void reply_transs(struct smb_request *req);
diff --git a/source3/smbd/smb1_lanman.c b/source3/smbd/smb1_lanman.c
new file mode 100644
index 0000000..9f703eb
--- /dev/null
+++ b/source3/smbd/smb1_lanman.c
@@ -0,0 +1,5920 @@
+/*
+ Unix SMB/CIFS implementation.
+ Inter-process communication and named pipe handling
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 2007.
+
+ SMB Version handling
+ Copyright (C) John H Terpstra 1995-1998
+
+ 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/>.
+ */
+/*
+ This file handles the named pipe and mailslot calls
+ in the SMBtrans protocol
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "rpc_client/rpc_client.h"
+#include "../librpc/gen_ndr/ndr_samr_c.h"
+#include "../librpc/gen_ndr/ndr_spoolss_c.h"
+#include "rpc_client/cli_spoolss.h"
+#include "rpc_client/init_spoolss.h"
+#include "../librpc/gen_ndr/ndr_srvsvc_c.h"
+#include "../librpc/gen_ndr/rap.h"
+#include "../lib/util/binsearch.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "rpc_client/init_lsa.h"
+#include "../libcli/security/security.h"
+#include "printing.h"
+#include "passdb/machine_sid.h"
+#include "auth.h"
+#include "rpc_server/rpc_ncacn_np.h"
+#include "lib/util/string_wrappers.h"
+#include "source3/printing/rap_jobid.h"
+#include "source3/lib/substitute.h"
+
+#ifdef CHECK_TYPES
+#undef CHECK_TYPES
+#endif
+#define CHECK_TYPES 0
+
+#define NERR_Success 0
+#define NERR_badpass 86
+#define NERR_notsupported 50
+
+#define NERR_BASE (2100)
+#define NERR_BufTooSmall (NERR_BASE+23)
+#define NERR_JobNotFound (NERR_BASE+51)
+#define NERR_DestNotFound (NERR_BASE+52)
+
+#define ACCESS_READ 0x01
+#define ACCESS_WRITE 0x02
+#define ACCESS_CREATE 0x04
+
+#define SHPWLEN 8 /* share password length */
+
+/* Limit size of ipc replies */
+
+static char *smb_realloc_limit(void *ptr, size_t size)
+{
+ char *val;
+
+ size = MAX((size),4*1024);
+ val = (char *)SMB_REALLOC(ptr,size);
+ if (val) {
+ memset(val,'\0',size);
+ }
+ return val;
+}
+
+static bool api_Unsupported(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt, int mprcnt,
+ char **rdata, char **rparam,
+ int *rdata_len, int *rparam_len);
+
+static bool api_TooSmall(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid, char *param, char *data,
+ int mdrcnt, int mprcnt,
+ char **rdata, char **rparam,
+ int *rdata_len, int *rparam_len);
+
+
+static int CopyExpanded(connection_struct *conn,
+ int snum, char **dst, char *src, int *p_space_remaining)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *buf = NULL;
+ int l;
+
+ if (!src || !dst || !p_space_remaining || !(*dst) ||
+ *p_space_remaining <= 0) {
+ return 0;
+ }
+
+ buf = talloc_strdup(ctx, src);
+ if (!buf) {
+ *p_space_remaining = 0;
+ return 0;
+ }
+ buf = talloc_string_sub(ctx, buf,"%S", lp_servicename(ctx, lp_sub, snum));
+ if (!buf) {
+ *p_space_remaining = 0;
+ return 0;
+ }
+ buf = talloc_sub_full(ctx,
+ lp_servicename(ctx, lp_sub, SNUM(conn)),
+ conn->session_info->unix_info->unix_name,
+ conn->connectpath,
+ conn->session_info->unix_token->gid,
+ conn->session_info->unix_info->sanitized_username,
+ conn->session_info->info->domain_name,
+ buf);
+ if (!buf) {
+ *p_space_remaining = 0;
+ return 0;
+ }
+ l = push_ascii(*dst,buf,*p_space_remaining, STR_TERMINATE);
+ if (l == 0) {
+ return 0;
+ }
+ (*dst) += l;
+ (*p_space_remaining) -= l;
+ return l;
+}
+
+static int CopyAndAdvance(char **dst, char *src, int *n)
+{
+ int l;
+ if (!src || !dst || !n || !(*dst)) {
+ return 0;
+ }
+ l = push_ascii(*dst,src,*n, STR_TERMINATE);
+ if (l == 0) {
+ return 0;
+ }
+ (*dst) += l;
+ (*n) -= l;
+ return l;
+}
+
+static int StrlenExpanded(connection_struct *conn, int snum, char *s)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *buf = NULL;
+ if (!s) {
+ return 0;
+ }
+ buf = talloc_strdup(ctx,s);
+ if (!buf) {
+ return 0;
+ }
+ buf = talloc_string_sub(ctx,buf,"%S",lp_servicename(ctx, lp_sub, snum));
+ if (!buf) {
+ return 0;
+ }
+ buf = talloc_sub_full(ctx,
+ lp_servicename(ctx, lp_sub, SNUM(conn)),
+ conn->session_info->unix_info->unix_name,
+ conn->connectpath,
+ conn->session_info->unix_token->gid,
+ conn->session_info->unix_info->sanitized_username,
+ conn->session_info->info->domain_name,
+ buf);
+ if (!buf) {
+ return 0;
+ }
+ return strlen(buf) + 1;
+}
+
+/****************************************************************
+ Return an SVAL at a pointer, or failval if beyond the end.
+****************************************************************/
+
+static int get_safe_SVAL(
+ const char *buf_base,
+ size_t buf_len,
+ char *ptr,
+ size_t off,
+ int failval)
+{
+ /*
+ * Note we use off+1 here, not off+2 as SVAL accesses ptr[0]
+ * and ptr[1], NOT ptr[2].
+ */
+ if (!is_offset_safe(buf_base, buf_len, ptr, off+1)) {
+ return failval;
+ }
+ return SVAL(ptr,off);
+}
+
+/****************************************************************
+ Return an IVAL at a pointer, or failval if beyond the end.
+****************************************************************/
+
+static int get_safe_IVAL(
+ const char *buf_base,
+ size_t buf_len,
+ char *ptr,
+ size_t off,
+ int failval)
+{
+ /*
+ * Note we use off+3 here, not off+4 as IVAL accesses
+ * ptr[0] ptr[1] ptr[2] ptr[3] NOT ptr[4].
+ */
+ if (!is_offset_safe(buf_base, buf_len, ptr, off+3)) {
+ return failval;
+ }
+ return IVAL(ptr,off);
+}
+
+/****************************************************************
+ Return a safe pointer into a buffer, or NULL.
+****************************************************************/
+
+static char *get_safe_ptr(
+ const char *buf_base,
+ size_t buf_len,
+ char *ptr,
+ size_t off)
+{
+ return is_offset_safe(buf_base, buf_len, ptr, off) ?
+ ptr + off : NULL;
+}
+
+/*******************************************************************
+ Check a API string for validity when we only need to check the prefix.
+******************************************************************/
+
+static bool prefix_ok(const char *str, const char *prefix)
+{
+ return(strncmp(str,prefix,strlen(prefix)) == 0);
+}
+
+struct pack_desc {
+ const char *format; /* formatstring for structure */
+ const char *subformat; /* subformat for structure */
+ char *base; /* baseaddress of buffer */
+ int buflen; /* remaining size for fixed part; on init: length of base */
+ int subcount; /* count of substructures */
+ char *structbuf; /* pointer into buffer for remaining fixed part */
+ int stringlen; /* remaining size for variable part */
+ char *stringbuf; /* pointer into buffer for remaining variable part */
+ int neededlen; /* total needed size */
+ int usedlen; /* total used size (usedlen <= neededlen and usedlen <= buflen) */
+ const char *curpos; /* current position; pointer into format or subformat */
+ int errcode;
+};
+
+static int get_counter(const char **p)
+{
+ int i, n;
+ if (!p || !(*p)) {
+ return 1;
+ }
+ if (!isdigit((int)**p)) {
+ return 1;
+ }
+ for (n = 0;;) {
+ i = **p;
+ if (isdigit(i)) {
+ n = 10 * n + (i - '0');
+ } else {
+ return n;
+ }
+ (*p)++;
+ }
+}
+
+static int getlen(const char *p)
+{
+ int n = 0;
+ if (!p) {
+ return 0;
+ }
+
+ while (*p) {
+ switch( *p++ ) {
+ case 'W': /* word (2 byte) */
+ n += 2;
+ break;
+ case 'K': /* status word? (2 byte) */
+ n += 2;
+ break;
+ case 'N': /* count of substructures (word) at end */
+ n += 2;
+ break;
+ case 'D': /* double word (4 byte) */
+ case 'z': /* offset to zero terminated string (4 byte) */
+ case 'l': /* offset to user data (4 byte) */
+ n += 4;
+ break;
+ case 'b': /* offset to data (with counter) (4 byte) */
+ n += 4;
+ get_counter(&p);
+ break;
+ case 'B': /* byte (with optional counter) */
+ n += get_counter(&p);
+ break;
+ }
+ }
+ return n;
+}
+
+static bool init_package(struct pack_desc *p, int count, int subcount)
+{
+ int n = p->buflen;
+ int i;
+
+ if (!p->format || !p->base) {
+ return False;
+ }
+
+ i = count * getlen(p->format);
+ if (p->subformat) {
+ i += subcount * getlen(p->subformat);
+ }
+ p->structbuf = p->base;
+ p->neededlen = 0;
+ p->usedlen = 0;
+ p->subcount = 0;
+ p->curpos = p->format;
+ if (i > n) {
+ p->neededlen = i;
+ i = n = 0;
+#if 0
+ /*
+ * This is the old error code we used. Apparently
+ * WinNT/2k systems return ERRbuftoosmall (2123) and
+ * OS/2 needs this. I'm leaving this here so we can revert
+ * if needed. JRA.
+ */
+ p->errcode = ERRmoredata;
+#else
+ p->errcode = ERRbuftoosmall;
+#endif
+ } else {
+ p->errcode = NERR_Success;
+ }
+ p->buflen = i;
+ n -= i;
+ p->stringbuf = p->base + i;
+ p->stringlen = n;
+ return (p->errcode == NERR_Success);
+}
+
+static int package(struct pack_desc *p, ...)
+{
+ va_list args;
+ int needed=0, stringneeded;
+ const char *str=NULL;
+ int is_string=0, stringused;
+ int32_t temp;
+
+ va_start(args,p);
+
+ if (!*p->curpos) {
+ if (!p->subcount) {
+ p->curpos = p->format;
+ } else {
+ p->curpos = p->subformat;
+ p->subcount--;
+ }
+ }
+#if CHECK_TYPES
+ str = va_arg(args,char*);
+ SMB_ASSERT(strncmp(str,p->curpos,strlen(str)) == 0);
+#endif
+ stringneeded = -1;
+
+ if (!p->curpos) {
+ va_end(args);
+ return 0;
+ }
+
+ switch( *p->curpos++ ) {
+ case 'W': /* word (2 byte) */
+ needed = 2;
+ temp = va_arg(args,int);
+ if (p->buflen >= needed) {
+ SSVAL(p->structbuf,0,temp);
+ }
+ break;
+ case 'K': /* status word? (2 byte) */
+ needed = 2;
+ temp = va_arg(args,int);
+ if (p->buflen >= needed) {
+ SSVAL(p->structbuf,0,temp);
+ }
+ break;
+ case 'N': /* count of substructures (word) at end */
+ needed = 2;
+ p->subcount = va_arg(args,int);
+ if (p->buflen >= needed) {
+ SSVAL(p->structbuf,0,p->subcount);
+ }
+ break;
+ case 'D': /* double word (4 byte) */
+ needed = 4;
+ temp = va_arg(args,int);
+ if (p->buflen >= needed) {
+ SIVAL(p->structbuf,0,temp);
+ }
+ break;
+ case 'B': /* byte (with optional counter) */
+ needed = get_counter(&p->curpos);
+ {
+ char *s = va_arg(args,char*);
+ if (p->buflen >= needed) {
+ strlcpy(p->structbuf,s?s:"",needed);
+ }
+ }
+ break;
+ case 'z': /* offset to zero terminated string (4 byte) */
+ str = va_arg(args,char*);
+ stringneeded = (str ? strlen(str)+1 : 0);
+ is_string = 1;
+ break;
+ case 'l': /* offset to user data (4 byte) */
+ str = va_arg(args,char*);
+ stringneeded = va_arg(args,int);
+ is_string = 0;
+ break;
+ case 'b': /* offset to data (with counter) (4 byte) */
+ str = va_arg(args,char*);
+ stringneeded = get_counter(&p->curpos);
+ is_string = 0;
+ break;
+ }
+
+ va_end(args);
+ if (stringneeded >= 0) {
+ needed = 4;
+ if (p->buflen >= needed) {
+ stringused = stringneeded;
+ if (stringused > p->stringlen) {
+ stringused = (is_string ? p->stringlen : 0);
+ if (p->errcode == NERR_Success) {
+ p->errcode = ERRmoredata;
+ }
+ }
+ if (!stringused) {
+ SIVAL(p->structbuf,0,0);
+ } else {
+ SIVAL(p->structbuf,0,PTR_DIFF(p->stringbuf,p->base));
+ memcpy(p->stringbuf,str?str:"",stringused);
+ if (is_string) {
+ p->stringbuf[stringused-1] = '\0';
+ }
+ p->stringbuf += stringused;
+ p->stringlen -= stringused;
+ p->usedlen += stringused;
+ }
+ }
+ p->neededlen += stringneeded;
+ }
+
+ p->neededlen += needed;
+ if (p->buflen >= needed) {
+ p->structbuf += needed;
+ p->buflen -= needed;
+ p->usedlen += needed;
+ } else {
+ if (p->errcode == NERR_Success) {
+ p->errcode = ERRmoredata;
+ }
+ }
+ return 1;
+}
+
+#if CHECK_TYPES
+#define PACK(desc,t,v) package(desc,t,v,0,0,0,0)
+#define PACKl(desc,t,v,l) package(desc,t,v,l,0,0,0,0)
+#else
+#define PACK(desc,t,v) package(desc,v)
+#define PACKl(desc,t,v,l) package(desc,v,l)
+#endif
+
+static void PACKI(struct pack_desc* desc, const char *t,int v)
+{
+ PACK(desc,t,v);
+}
+
+static void PACKS(struct pack_desc* desc,const char *t,const char *v)
+{
+ PACK(desc,t,v);
+}
+
+/****************************************************************************
+ Get a print queue.
+****************************************************************************/
+
+static void PackDriverData(struct pack_desc* desc)
+{
+ char drivdata[4+4+32];
+ SIVAL(drivdata,0,sizeof drivdata); /* cb */
+ SIVAL(drivdata,4,1000); /* lVersion */
+ memset(drivdata+8,0,32); /* szDeviceName */
+ push_ascii(drivdata+8,"NULL",32, STR_TERMINATE);
+ PACKl(desc,"l",drivdata,sizeof drivdata); /* pDriverData */
+}
+
+static int check_printq_info(struct pack_desc* desc,
+ unsigned int uLevel, char *id1, char *id2)
+{
+ desc->subformat = NULL;
+ switch( uLevel ) {
+ case 0:
+ desc->format = "B13";
+ break;
+ case 1:
+ desc->format = "B13BWWWzzzzzWW";
+ break;
+ case 2:
+ desc->format = "B13BWWWzzzzzWN";
+ desc->subformat = "WB21BB16B10zWWzDDz";
+ break;
+ case 3:
+ desc->format = "zWWWWzzzzWWzzl";
+ break;
+ case 4:
+ desc->format = "zWWWWzzzzWNzzl";
+ desc->subformat = "WWzWWDDzz";
+ break;
+ case 5:
+ desc->format = "z";
+ break;
+ case 51:
+ desc->format = "K";
+ break;
+ case 52:
+ desc->format = "WzzzzzzzzN";
+ desc->subformat = "z";
+ break;
+ default:
+ DEBUG(0,("check_printq_info: invalid level %d\n",
+ uLevel ));
+ return False;
+ }
+ if (id1 == NULL || strcmp(desc->format,id1) != 0) {
+ DEBUG(0,("check_printq_info: invalid format %s\n",
+ id1 ? id1 : "<NULL>" ));
+ return False;
+ }
+ if (desc->subformat && (id2 == NULL || strcmp(desc->subformat,id2) != 0)) {
+ DEBUG(0,("check_printq_info: invalid subformat %s\n",
+ id2 ? id2 : "<NULL>" ));
+ return False;
+ }
+ return True;
+}
+
+
+#define RAP_JOB_STATUS_QUEUED 0
+#define RAP_JOB_STATUS_PAUSED 1
+#define RAP_JOB_STATUS_SPOOLING 2
+#define RAP_JOB_STATUS_PRINTING 3
+#define RAP_JOB_STATUS_PRINTED 4
+
+#define RAP_QUEUE_STATUS_PAUSED 1
+#define RAP_QUEUE_STATUS_ERROR 2
+
+/* turn a print job status into a on the wire status
+*/
+static int printj_spoolss_status(int v)
+{
+ if (v == JOB_STATUS_QUEUED)
+ return RAP_JOB_STATUS_QUEUED;
+ if (v & JOB_STATUS_PAUSED)
+ return RAP_JOB_STATUS_PAUSED;
+ if (v & JOB_STATUS_SPOOLING)
+ return RAP_JOB_STATUS_SPOOLING;
+ if (v & JOB_STATUS_PRINTING)
+ return RAP_JOB_STATUS_PRINTING;
+ return 0;
+}
+
+/* turn a print queue status into a on the wire status
+*/
+static int printq_spoolss_status(int v)
+{
+ if (v == PRINTER_STATUS_OK)
+ return 0;
+ if (v & PRINTER_STATUS_PAUSED)
+ return RAP_QUEUE_STATUS_PAUSED;
+ return RAP_QUEUE_STATUS_ERROR;
+}
+
+static void fill_spoolss_printjob_info(int uLevel,
+ struct pack_desc *desc,
+ struct spoolss_JobInfo2 *info2,
+ int n)
+{
+ time_t t = spoolss_Time_to_time_t(&info2->submitted);
+
+ /* the client expects localtime */
+ t -= get_time_zone(t);
+
+ PACKI(desc,"W",pjobid_to_rap(info2->printer_name, info2->job_id)); /* uJobId */
+ if (uLevel == 1) {
+ PACKS(desc,"B21", info2->user_name); /* szUserName */
+ PACKS(desc,"B",""); /* pad */
+ PACKS(desc,"B16",""); /* szNotifyName */
+ PACKS(desc,"B10","PM_Q_RAW"); /* szDataType */
+ PACKS(desc,"z",""); /* pszParms */
+ PACKI(desc,"W",n+1); /* uPosition */
+ PACKI(desc,"W", printj_spoolss_status(info2->status)); /* fsStatus */
+ PACKS(desc,"z",""); /* pszStatus */
+ PACKI(desc,"D", t); /* ulSubmitted */
+ PACKI(desc,"D", info2->size); /* ulSize */
+ PACKS(desc,"z", info2->document_name); /* pszComment */
+ }
+ if (uLevel == 2 || uLevel == 3 || uLevel == 4) {
+ PACKI(desc,"W", info2->priority); /* uPriority */
+ PACKS(desc,"z", info2->user_name); /* pszUserName */
+ PACKI(desc,"W",n+1); /* uPosition */
+ PACKI(desc,"W", printj_spoolss_status(info2->status)); /* fsStatus */
+ PACKI(desc,"D",t); /* ulSubmitted */
+ PACKI(desc,"D", info2->size); /* ulSize */
+ PACKS(desc,"z","Samba"); /* pszComment */
+ PACKS(desc,"z", info2->document_name); /* pszDocument */
+ if (uLevel == 3) {
+ PACKS(desc,"z",""); /* pszNotifyName */
+ PACKS(desc,"z","PM_Q_RAW"); /* pszDataType */
+ PACKS(desc,"z",""); /* pszParms */
+ PACKS(desc,"z",""); /* pszStatus */
+ PACKS(desc,"z", info2->printer_name); /* pszQueue */
+ PACKS(desc,"z","lpd"); /* pszQProcName */
+ PACKS(desc,"z",""); /* pszQProcParms */
+ PACKS(desc,"z","NULL"); /* pszDriverName */
+ PackDriverData(desc); /* pDriverData */
+ PACKS(desc,"z",""); /* pszPrinterName */
+ } else if (uLevel == 4) { /* OS2 */
+ PACKS(desc,"z",""); /* pszSpoolFileName */
+ PACKS(desc,"z",""); /* pszPortName */
+ PACKS(desc,"z",""); /* pszStatus */
+ PACKI(desc,"D",0); /* ulPagesSpooled */
+ PACKI(desc,"D",0); /* ulPagesSent */
+ PACKI(desc,"D",0); /* ulPagesPrinted */
+ PACKI(desc,"D",0); /* ulTimePrinted */
+ PACKI(desc,"D",0); /* ulExtendJobStatus */
+ PACKI(desc,"D",0); /* ulStartPage */
+ PACKI(desc,"D",0); /* ulEndPage */
+ }
+ }
+}
+
+/********************************************************************
+ Respond to the DosPrintQInfo command with a level of 52
+ This is used to get printer driver information for Win9x clients
+ ********************************************************************/
+static void fill_printq_info_52(struct spoolss_DriverInfo3 *driver,
+ struct pack_desc* desc, int count,
+ const char *printer_name)
+{
+ int i;
+ fstring location;
+ trim_string(discard_const_p(char, driver->driver_path), "\\print$\\WIN40\\0\\", 0);
+ trim_string(discard_const_p(char, driver->data_file), "\\print$\\WIN40\\0\\", 0);
+ trim_string(discard_const_p(char, driver->help_file), "\\print$\\WIN40\\0\\", 0);
+
+ PACKI(desc, "W", 0x0400); /* don't know */
+ PACKS(desc, "z", driver->driver_name); /* long printer name */
+ PACKS(desc, "z", driver->driver_path); /* Driverfile Name */
+ PACKS(desc, "z", driver->data_file); /* Datafile name */
+ PACKS(desc, "z", driver->monitor_name); /* language monitor */
+
+ fstrcpy(location, "\\\\%L\\print$\\WIN40\\0");
+ standard_sub_basic( "", "", location, sizeof(location)-1 );
+ PACKS(desc,"z", location); /* share to retrieve files */
+
+ PACKS(desc,"z", driver->default_datatype); /* default data type */
+ PACKS(desc,"z", driver->help_file); /* helpfile name */
+ PACKS(desc,"z", driver->driver_path); /* driver name */
+
+ DEBUG(3,("Printer Driver Name: %s:\n",driver->driver_name));
+ DEBUG(3,("Driver: %s:\n",driver->driver_path));
+ DEBUG(3,("Data File: %s:\n",driver->data_file));
+ DEBUG(3,("Language Monitor: %s:\n",driver->monitor_name));
+ DEBUG(3,("Driver Location: %s:\n",location));
+ DEBUG(3,("Data Type: %s:\n",driver->default_datatype));
+ DEBUG(3,("Help File: %s:\n",driver->help_file));
+ PACKI(desc,"N",count); /* number of files to copy */
+
+ for ( i=0; i<count && driver->dependent_files && *driver->dependent_files[i]; i++)
+ {
+ trim_string(discard_const_p(char, driver->dependent_files[i]), "\\print$\\WIN40\\0\\", 0);
+ PACKS(desc,"z",driver->dependent_files[i]); /* driver files to copy */
+ DEBUG(3,("Dependent File: %s:\n", driver->dependent_files[i]));
+ }
+
+ /* sanity check */
+ if ( i != count )
+ DEBUG(3,("fill_printq_info_52: file count specified by client [%d] != number of dependent files [%i]\n",
+ count, i));
+
+ DEBUG(3,("fill_printq_info on <%s> gave %d entries\n", printer_name, i));
+
+ desc->errcode=NERR_Success;
+
+}
+
+static const char *strip_unc(const char *unc)
+{
+ char *p;
+
+ if (unc == NULL) {
+ return NULL;
+ }
+
+ if ((p = strrchr(unc, '\\')) != NULL) {
+ return p+1;
+ }
+
+ return unc;
+}
+
+static void fill_printq_info(int uLevel,
+ struct pack_desc* desc,
+ int count,
+ union spoolss_JobInfo *job_info,
+ struct spoolss_DriverInfo3 *driver_info,
+ struct spoolss_PrinterInfo2 *printer_info)
+{
+ switch (uLevel) {
+ case 0:
+ case 1:
+ case 2:
+ PACKS(desc,"B13", strip_unc(printer_info->printername));
+ break;
+ case 3:
+ case 4:
+ case 5:
+ PACKS(desc,"z", strip_unc(printer_info->printername));
+ break;
+ case 51:
+ PACKI(desc,"K", printq_spoolss_status(printer_info->status));
+ break;
+ }
+
+ if (uLevel == 1 || uLevel == 2) {
+ PACKS(desc,"B",""); /* alignment */
+ PACKI(desc,"W",5); /* priority */
+ PACKI(desc,"W",0); /* start time */
+ PACKI(desc,"W",0); /* until time */
+ PACKS(desc,"z",""); /* pSepFile */
+ PACKS(desc,"z","lpd"); /* pPrProc */
+ PACKS(desc,"z", strip_unc(printer_info->printername)); /* pDestinations */
+ PACKS(desc,"z",""); /* pParms */
+ if (printer_info->printername == NULL) {
+ PACKS(desc,"z","UNKNOWN PRINTER");
+ PACKI(desc,"W",LPSTAT_ERROR);
+ } else {
+ PACKS(desc,"z", printer_info->comment);
+ PACKI(desc,"W", printq_spoolss_status(printer_info->status)); /* status */
+ }
+ PACKI(desc,(uLevel == 1 ? "W" : "N"),count);
+ }
+
+ if (uLevel == 3 || uLevel == 4) {
+ PACKI(desc,"W",5); /* uPriority */
+ PACKI(desc,"W",0); /* uStarttime */
+ PACKI(desc,"W",0); /* uUntiltime */
+ PACKI(desc,"W",5); /* pad1 */
+ PACKS(desc,"z",""); /* pszSepFile */
+ PACKS(desc,"z","WinPrint"); /* pszPrProc */
+ PACKS(desc,"z",NULL); /* pszParms */
+ PACKS(desc,"z",NULL); /* pszComment - don't ask.... JRA */
+ /* "don't ask" that it's done this way to fix corrupted
+ Win9X/ME printer comments. */
+ PACKI(desc,"W", printq_spoolss_status(printer_info->status)); /* fsStatus */
+ PACKI(desc,(uLevel == 3 ? "W" : "N"),count); /* cJobs */
+ PACKS(desc,"z", strip_unc(printer_info->printername)); /* pszPrinters */
+ PACKS(desc,"z", printer_info->drivername); /* pszDriverName */
+ PackDriverData(desc); /* pDriverData */
+ }
+
+ if (uLevel == 2 || uLevel == 4) {
+ int i;
+ for (i = 0; i < count; i++) {
+ fill_spoolss_printjob_info(uLevel == 2 ? 1 : 2, desc, &job_info[i].info2, i);
+ }
+ }
+
+ if (uLevel==52)
+ fill_printq_info_52(driver_info, desc, count, printer_info->printername);
+}
+
+/* This function returns the number of files for a given driver */
+static int get_printerdrivernumber(const struct spoolss_DriverInfo3 *driver)
+{
+ int result = 0;
+
+ /* count the number of files */
+ while (driver->dependent_files && *driver->dependent_files[result])
+ result++;
+
+ return result;
+}
+
+static bool api_DosPrintQGetInfo(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ char *QueueName = p;
+ unsigned int uLevel;
+ uint32_t count = 0;
+ char *str3;
+ struct pack_desc desc;
+ char* tmpdata=NULL;
+
+ WERROR werr = WERR_OK;
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ NTSTATUS status;
+ struct rpc_pipe_client *cli = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+ struct policy_handle handle;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ union spoolss_DriverInfo driver_info;
+ union spoolss_JobInfo *job_info = NULL;
+ union spoolss_PrinterInfo printer_info;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+ memset((char *)&desc,'\0',sizeof(desc));
+
+ p = skip_string(param,tpscnt,p);
+ if (!p) {
+ return False;
+ }
+ uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ str3 = get_safe_str_ptr(param,tpscnt,p,4);
+ /* str3 may be null here and is checked in check_printq_info(). */
+
+ /* remove any trailing username */
+ if ((p = strchr_m(QueueName,'%')))
+ *p = 0;
+
+ DEBUG(3,("api_DosPrintQGetInfo uLevel=%d name=%s\n",uLevel,QueueName));
+
+ /* check it's a supported variant */
+ if (!prefix_ok(str1,"zWrLh"))
+ return False;
+ if (!check_printq_info(&desc,uLevel,str2,str3)) {
+ /*
+ * Patch from Scott Moomaw <scott@bridgewater.edu>
+ * to return the 'invalid info level' error if an
+ * unknown level was requested.
+ */
+ *rdata_len = 0;
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,ERRunknownlevel);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,0);
+ return(True);
+ }
+
+ ZERO_STRUCT(handle);
+
+ if (QueueName == NULL || (strlen(QueueName) < 1)) {
+ desc.errcode = W_ERROR_V(WERR_INVALID_PARAMETER);
+ goto out;
+ }
+
+ status = rpc_pipe_open_interface(mem_ctx,
+ &ndr_table_spoolss,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("api_DosPrintQGetInfo: could not connect to spoolss: %s\n",
+ nt_errstr(status)));
+ desc.errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ b = cli->binding_handle;
+
+ ZERO_STRUCT(devmode_ctr);
+
+ status = dcerpc_spoolss_OpenPrinter(b, mem_ctx,
+ QueueName,
+ "RAW",
+ devmode_ctr,
+ PRINTER_ACCESS_USE,
+ &handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ desc.errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ desc.errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ werr = rpccli_spoolss_getprinter(cli, mem_ctx,
+ &handle,
+ 2,
+ 0,
+ &printer_info);
+ if (!W_ERROR_IS_OK(werr)) {
+ desc.errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ if (uLevel==52) {
+ uint32_t server_major_version;
+ uint32_t server_minor_version;
+
+ werr = rpccli_spoolss_getprinterdriver2(cli, mem_ctx,
+ &handle,
+ "Windows 4.0",
+ 3, /* level */
+ 0,
+ 0, /* version */
+ 0,
+ &driver_info,
+ &server_major_version,
+ &server_minor_version);
+ if (!W_ERROR_IS_OK(werr)) {
+ desc.errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ count = get_printerdrivernumber(&driver_info.info3);
+ DEBUG(3,("api_DosPrintQGetInfo: Driver files count: %d\n",count));
+ } else {
+ uint32_t num_jobs;
+ werr = rpccli_spoolss_enumjobs(cli, mem_ctx,
+ &handle,
+ 0, /* firstjob */
+ 0xff, /* numjobs */
+ 2, /* level */
+ 0, /* offered */
+ &num_jobs,
+ &job_info);
+ if (!W_ERROR_IS_OK(werr)) {
+ desc.errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ count = num_jobs;
+ }
+
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+ } else {
+ /*
+ * Don't return data but need to get correct length
+ * init_package will return wrong size if buflen=0
+ */
+ desc.buflen = getlen(desc.format);
+ desc.base = tmpdata = (char *) SMB_MALLOC (desc.buflen);
+ }
+
+ if (init_package(&desc,1,count)) {
+ desc.subcount = count;
+ fill_printq_info(uLevel,&desc,count, job_info, &driver_info.info3, &printer_info.info2);
+ }
+
+ *rdata_len = desc.usedlen;
+
+ /*
+ * We must set the return code to ERRbuftoosmall
+ * in order to support lanman style printing with Win NT/2k
+ * clients --jerry
+ */
+ if (!mdrcnt && lp_disable_spoolss())
+ desc.errcode = ERRbuftoosmall;
+
+ out:
+ if (b && is_valid_policy_hnd(&handle)) {
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &handle, &werr);
+ }
+
+ *rdata_len = desc.usedlen;
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ SAFE_FREE(tmpdata);
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,desc.neededlen);
+
+ DEBUG(4,("printqgetinfo: errorcode %d\n",desc.errcode));
+
+ SAFE_FREE(tmpdata);
+
+ return(True);
+}
+
+/****************************************************************************
+ View list of all print jobs on all queues.
+****************************************************************************/
+
+static bool api_DosPrintQEnum(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt, int mprcnt,
+ char **rdata, char** rparam,
+ int *rdata_len, int *rparam_len)
+{
+ char *param_format = get_safe_str_ptr(param,tpscnt,param,2);
+ char *output_format1 = skip_string(param,tpscnt,param_format);
+ char *p = skip_string(param,tpscnt,output_format1);
+ unsigned int uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ char *output_format2 = get_safe_str_ptr(param,tpscnt,p,4);
+ int i;
+ struct pack_desc desc;
+ int *subcntarr = NULL;
+ int queuecnt = 0, subcnt = 0, succnt = 0;
+
+ WERROR werr = WERR_OK;
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ NTSTATUS status;
+ struct rpc_pipe_client *cli = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ uint32_t num_printers;
+ union spoolss_PrinterInfo *printer_info;
+ union spoolss_DriverInfo *driver_info;
+ union spoolss_JobInfo **job_info;
+
+ if (!param_format || !output_format1 || !p) {
+ return False;
+ }
+
+ memset((char *)&desc,'\0',sizeof(desc));
+
+ DEBUG(3,("DosPrintQEnum uLevel=%d\n",uLevel));
+
+ if (!prefix_ok(param_format,"WrLeh")) {
+ return False;
+ }
+ if (!check_printq_info(&desc,uLevel,output_format1,output_format2)) {
+ /*
+ * Patch from Scott Moomaw <scott@bridgewater.edu>
+ * to return the 'invalid info level' error if an
+ * unknown level was requested.
+ */
+ *rdata_len = 0;
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,ERRunknownlevel);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,0);
+ return(True);
+ }
+
+ status = rpc_pipe_open_interface(mem_ctx,
+ &ndr_table_spoolss,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("api_DosPrintQEnum: could not connect to spoolss: %s\n",
+ nt_errstr(status)));
+ desc.errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ b = cli->binding_handle;
+
+ werr = rpccli_spoolss_enumprinters(cli, mem_ctx,
+ PRINTER_ENUM_LOCAL,
+ cli->srv_name_slash,
+ 2,
+ 0,
+ &num_printers,
+ &printer_info);
+ if (!W_ERROR_IS_OK(werr)) {
+ desc.errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ queuecnt = num_printers;
+
+ job_info = talloc_array(mem_ctx, union spoolss_JobInfo *, num_printers);
+ if (job_info == NULL) {
+ goto err;
+ }
+
+ driver_info = talloc_array(mem_ctx, union spoolss_DriverInfo, num_printers);
+ if (driver_info == NULL) {
+ goto err;
+ }
+
+ if((subcntarr = SMB_MALLOC_ARRAY(int,queuecnt)) == NULL) {
+ DEBUG(0,("api_DosPrintQEnum: malloc fail !\n"));
+ goto err;
+ }
+
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ goto err;
+ }
+ }
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+
+ subcnt = 0;
+ for (i = 0; i < num_printers; i++) {
+
+ uint32_t num_jobs;
+ struct policy_handle handle;
+ const char *printername;
+
+ printername = talloc_strdup(mem_ctx, printer_info[i].info2.printername);
+ if (printername == NULL) {
+ goto err;
+ }
+
+ ZERO_STRUCT(handle);
+ ZERO_STRUCT(devmode_ctr);
+
+ status = dcerpc_spoolss_OpenPrinter(b, mem_ctx,
+ printername,
+ "RAW",
+ devmode_ctr,
+ PRINTER_ACCESS_USE,
+ &handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ desc.errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ desc.errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ werr = rpccli_spoolss_enumjobs(cli, mem_ctx,
+ &handle,
+ 0, /* firstjob */
+ 0xff, /* numjobs */
+ 2, /* level */
+ 0, /* offered */
+ &num_jobs,
+ &job_info[i]);
+ if (!W_ERROR_IS_OK(werr)) {
+ desc.errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ if (uLevel==52) {
+ uint32_t server_major_version;
+ uint32_t server_minor_version;
+
+ werr = rpccli_spoolss_getprinterdriver2(cli, mem_ctx,
+ &handle,
+ "Windows 4.0",
+ 3, /* level */
+ 0,
+ 0, /* version */
+ 0,
+ &driver_info[i],
+ &server_major_version,
+ &server_minor_version);
+ if (!W_ERROR_IS_OK(werr)) {
+ desc.errcode = W_ERROR_V(werr);
+ goto out;
+ }
+ }
+
+ subcntarr[i] = num_jobs;
+ subcnt += subcntarr[i];
+
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &handle, &werr);
+ }
+
+ if (init_package(&desc,queuecnt,subcnt)) {
+ for (i = 0; i < num_printers; i++) {
+ fill_printq_info(uLevel,&desc,subcntarr[i], job_info[i], &driver_info[i].info3, &printer_info[i].info2);
+ if (desc.errcode == NERR_Success) {
+ succnt = i;
+ }
+ }
+ }
+
+ out:
+ SAFE_FREE(subcntarr);
+ *rdata_len = desc.usedlen;
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ goto err;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,succnt);
+ SSVAL(*rparam,6,queuecnt);
+
+ return True;
+
+ err:
+
+ SAFE_FREE(subcntarr);
+
+ return False;
+}
+
+/****************************************************************************
+ Get info level for a server list query.
+****************************************************************************/
+
+static bool check_session_info(int uLevel, char* id)
+{
+ switch( uLevel ) {
+ case 0:
+ if (strcmp(id,"B16") != 0) {
+ return False;
+ }
+ break;
+ case 1:
+ if (strcmp(id,"B16BBDz") != 0) {
+ return False;
+ }
+ break;
+ default:
+ return False;
+ }
+ return True;
+}
+
+struct srv_info_struct {
+ fstring name;
+ uint32_t type;
+ fstring comment;
+ fstring domain;
+ bool server_added;
+};
+
+/*******************************************************************
+ Get server info lists from the files saved by nmbd. Return the
+ number of entries.
+******************************************************************/
+
+static int get_session_info(uint32_t servertype,
+ struct srv_info_struct **servers,
+ const char *domain)
+{
+ int count=0;
+ int alloced=0;
+ char **lines;
+ bool local_list_only;
+ int i;
+ char *slist_cache_path = cache_path(talloc_tos(), SERVER_LIST);
+ if (slist_cache_path == NULL) {
+ return 0;
+ }
+
+ lines = file_lines_load(slist_cache_path, NULL, 0, NULL);
+ if (!lines) {
+ DEBUG(4, ("Can't open %s - %s\n",
+ slist_cache_path, strerror(errno)));
+ TALLOC_FREE(slist_cache_path);
+ return 0;
+ }
+ TALLOC_FREE(slist_cache_path);
+
+ /* request for everything is code for request all servers */
+ if (servertype == SV_TYPE_ALL) {
+ servertype &= ~(SV_TYPE_DOMAIN_ENUM|SV_TYPE_LOCAL_LIST_ONLY);
+ }
+
+ local_list_only = (servertype & SV_TYPE_LOCAL_LIST_ONLY);
+
+ DEBUG(4,("Servertype search: %8x\n",servertype));
+
+ for (i=0;lines[i];i++) {
+ fstring stype;
+ struct srv_info_struct *s;
+ const char *ptr = lines[i];
+ bool ok = True;
+ TALLOC_CTX *frame = NULL;
+ char *p;
+
+ if (!*ptr) {
+ continue;
+ }
+
+ if (count == alloced) {
+ alloced += 10;
+ *servers = SMB_REALLOC_ARRAY(*servers,struct srv_info_struct, alloced);
+ if (!*servers) {
+ DEBUG(0,("get_session_info: failed to enlarge servers info struct!\n"));
+ TALLOC_FREE(lines);
+ return 0;
+ }
+ memset((char *)((*servers)+count),'\0',sizeof(**servers)*(alloced-count));
+ }
+ s = &(*servers)[count];
+
+ frame = talloc_stackframe();
+ s->name[0] = '\0';
+ if (!next_token_talloc(frame,&ptr,&p, NULL)) {
+ TALLOC_FREE(frame);
+ continue;
+ }
+ fstrcpy(s->name, p);
+
+ stype[0] = '\0';
+ if (!next_token_talloc(frame,&ptr, &p, NULL)) {
+ TALLOC_FREE(frame);
+ continue;
+ }
+ fstrcpy(stype, p);
+
+ s->comment[0] = '\0';
+ if (!next_token_talloc(frame,&ptr, &p, NULL)) {
+ TALLOC_FREE(frame);
+ continue;
+ }
+ fstrcpy(s->comment, p);
+ string_truncate(s->comment, MAX_SERVER_STRING_LENGTH);
+
+ s->domain[0] = '\0';
+ if (!next_token_talloc(frame,&ptr,&p, NULL)) {
+ /* this allows us to cope with an old nmbd */
+ fstrcpy(s->domain,lp_workgroup());
+ } else {
+ fstrcpy(s->domain, p);
+ }
+ TALLOC_FREE(frame);
+
+ if (sscanf(stype,"%X",&s->type) != 1) {
+ DEBUG(4,("r:host file "));
+ ok = False;
+ }
+
+ /* Filter the servers/domains we return based on what was asked for. */
+
+ /* Check to see if we are being asked for a local list only. */
+ if(local_list_only && ((s->type & SV_TYPE_LOCAL_LIST_ONLY) == 0)) {
+ DEBUG(4,("r: local list only"));
+ ok = False;
+ }
+
+ /* doesn't match up: don't want it */
+ if (!(servertype & s->type)) {
+ DEBUG(4,("r:serv type "));
+ ok = False;
+ }
+
+ if ((servertype & SV_TYPE_DOMAIN_ENUM) !=
+ (s->type & SV_TYPE_DOMAIN_ENUM)) {
+ DEBUG(4,("s: dom mismatch "));
+ ok = False;
+ }
+
+ if (!strequal(domain, s->domain) && !(servertype & SV_TYPE_DOMAIN_ENUM)) {
+ ok = False;
+ }
+
+ /* We should never return a server type with a SV_TYPE_LOCAL_LIST_ONLY set. */
+ s->type &= ~SV_TYPE_LOCAL_LIST_ONLY;
+
+ if (ok) {
+ DEBUG(4,("**SV** %20s %8x %25s %15s\n",
+ s->name, s->type, s->comment, s->domain));
+ s->server_added = True;
+ count++;
+ } else {
+ DEBUG(4,("%20s %8x %25s %15s\n",
+ s->name, s->type, s->comment, s->domain));
+ }
+ }
+
+ TALLOC_FREE(lines);
+ return count;
+}
+
+/*******************************************************************
+ Fill in a server info structure.
+******************************************************************/
+
+static int fill_srv_info(struct srv_info_struct *service,
+ int uLevel, char **buf, int *buflen,
+ char **stringbuf, int *stringspace, char *baseaddr)
+{
+ int struct_len;
+ char* p;
+ char* p2;
+ int l2;
+ int len;
+
+ switch (uLevel) {
+ case 0:
+ struct_len = 16;
+ break;
+ case 1:
+ struct_len = 26;
+ break;
+ default:
+ return -1;
+ }
+
+ if (!buf) {
+ len = 0;
+ switch (uLevel) {
+ case 1:
+ len = strlen(service->comment)+1;
+ break;
+ }
+
+ *buflen = struct_len;
+ *stringspace = len;
+ return struct_len + len;
+ }
+
+ len = struct_len;
+ p = *buf;
+ if (*buflen < struct_len) {
+ return -1;
+ }
+ if (stringbuf) {
+ p2 = *stringbuf;
+ l2 = *stringspace;
+ } else {
+ p2 = p + struct_len;
+ l2 = *buflen - struct_len;
+ }
+ if (!baseaddr) {
+ baseaddr = p;
+ }
+
+ switch (uLevel) {
+ case 0:
+ push_ascii(p,service->name, MAX_NETBIOSNAME_LEN, STR_TERMINATE);
+ break;
+
+ case 1:
+ push_ascii(p,service->name,MAX_NETBIOSNAME_LEN, STR_TERMINATE);
+ SIVAL(p,18,service->type);
+ SIVAL(p,22,PTR_DIFF(p2,baseaddr));
+ len += CopyAndAdvance(&p2,service->comment,&l2);
+ break;
+ }
+
+ if (stringbuf) {
+ *buf = p + struct_len;
+ *buflen -= struct_len;
+ *stringbuf = p2;
+ *stringspace = l2;
+ } else {
+ *buf = p2;
+ *buflen -= len;
+ }
+ return len;
+}
+
+
+static int srv_comp(struct srv_info_struct *s1,struct srv_info_struct *s2)
+{
+ return strcasecmp_m(s1->name,s2->name);
+}
+
+/****************************************************************************
+ View list of servers available (or possibly domains). The info is
+ extracted from lists saved by nmbd on the local host.
+****************************************************************************/
+
+static bool api_RNetServerEnum2(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt, int mprcnt, char **rdata,
+ char **rparam, int *rdata_len, int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param, tpscnt, param, 2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel = get_safe_SVAL(param, tpscnt, p, 0, -1);
+ int buf_len = get_safe_SVAL(param,tpscnt, p, 2, 0);
+ uint32_t servertype = get_safe_IVAL(param,tpscnt,p,4, 0);
+ char *p2;
+ int data_len, fixed_len, string_len;
+ int f_len = 0, s_len = 0;
+ struct srv_info_struct *servers=NULL;
+ int counted=0,total=0;
+ int i,missed;
+ fstring domain;
+ bool domain_request;
+ bool local_request;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ /* If someone sets all the bits they don't really mean to set
+ DOMAIN_ENUM and LOCAL_LIST_ONLY, they just want all the
+ known servers. */
+
+ if (servertype == SV_TYPE_ALL) {
+ servertype &= ~(SV_TYPE_DOMAIN_ENUM|SV_TYPE_LOCAL_LIST_ONLY);
+ }
+
+ /* If someone sets SV_TYPE_LOCAL_LIST_ONLY but hasn't set
+ any other bit (they may just set this bit on its own) they
+ want all the locally seen servers. However this bit can be
+ set on its own so set the requested servers to be
+ ALL - DOMAIN_ENUM. */
+
+ if ((servertype & SV_TYPE_LOCAL_LIST_ONLY) && !(servertype & SV_TYPE_DOMAIN_ENUM)) {
+ servertype = SV_TYPE_ALL & ~(SV_TYPE_DOMAIN_ENUM);
+ }
+
+ domain_request = ((servertype & SV_TYPE_DOMAIN_ENUM) != 0);
+ local_request = ((servertype & SV_TYPE_LOCAL_LIST_ONLY) != 0);
+
+ p += 8;
+
+ if (!prefix_ok(str1,"WrLehD")) {
+ return False;
+ }
+ if (!check_session_info(uLevel,str2)) {
+ return False;
+ }
+
+ DEBUG(4, ("server request level: %s %8x ", str2, servertype));
+ DEBUG(4, ("domains_req:%s ", BOOLSTR(domain_request)));
+ DEBUG(4, ("local_only:%s\n", BOOLSTR(local_request)));
+
+ if (strcmp(str1, "WrLehDz") == 0) {
+ if (skip_string(param,tpscnt,p) == NULL) {
+ return False;
+ }
+ pull_ascii_fstring(domain, p);
+ } else {
+ fstrcpy(domain, lp_workgroup());
+ }
+
+ DEBUG(4, ("domain [%s]\n", domain));
+
+ if (lp_browse_list()) {
+ total = get_session_info(servertype,&servers,domain);
+ }
+
+ data_len = fixed_len = string_len = 0;
+ missed = 0;
+
+ TYPESAFE_QSORT(servers, total, srv_comp);
+
+ {
+ char *lastname=NULL;
+
+ for (i=0;i<total;i++) {
+ struct srv_info_struct *s = &servers[i];
+
+ if (lastname && strequal(lastname,s->name)) {
+ continue;
+ }
+ lastname = s->name;
+ data_len += fill_srv_info(s,uLevel,0,&f_len,0,&s_len,0);
+ DEBUG(4,("fill_srv_info[%d] %20s %8x %25s %15s\n",
+ i, s->name, s->type, s->comment, s->domain));
+
+ if (data_len < buf_len) {
+ counted++;
+ fixed_len += f_len;
+ string_len += s_len;
+ } else {
+ missed++;
+ }
+ }
+ }
+
+ *rdata_len = fixed_len + string_len;
+ *rdata = smb_realloc_limit(*rdata,*rdata_len);
+ if (!*rdata) {
+ return False;
+ }
+
+ p2 = (*rdata) + fixed_len; /* auxiliary data (strings) will go here */
+ p = *rdata;
+ f_len = fixed_len;
+ s_len = string_len;
+
+ {
+ char *lastname=NULL;
+ int count2 = counted;
+
+ for (i = 0; i < total && count2;i++) {
+ struct srv_info_struct *s = &servers[i];
+
+ if (lastname && strequal(lastname,s->name)) {
+ continue;
+ }
+ lastname = s->name;
+ fill_srv_info(s,uLevel,&p,&f_len,&p2,&s_len,*rdata);
+ DEBUG(4,("fill_srv_info[%d] %20s %8x %25s %15s\n",
+ i, s->name, s->type, s->comment, s->domain));
+ count2--;
+ }
+ }
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVAL(*rparam,0,(missed == 0 ? NERR_Success : ERRmoredata));
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,counted);
+ SSVAL(*rparam,6,counted+missed);
+
+ SAFE_FREE(servers);
+
+ DEBUG(3,("NetServerEnum2 domain = %s uLevel=%d counted=%d total=%d\n",
+ domain,uLevel,counted,counted+missed));
+
+ return True;
+}
+
+static int srv_name_match(const char *n1, const char *n2)
+{
+ /*
+ * [MS-RAP] footnote <88> for Section 3.2.5.15 says:
+ *
+ * In Windows, FirstNameToReturn need not be an exact match:
+ * the server will return a list of servers that exist on
+ * the network greater than or equal to the FirstNameToReturn.
+ */
+ int ret = strcasecmp_m(n1, n2);
+
+ if (ret <= 0) {
+ return 0;
+ }
+
+ return ret;
+}
+
+static bool api_RNetServerEnum3(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt, int mprcnt, char **rdata,
+ char **rparam, int *rdata_len, int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param, tpscnt, param, 2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel = get_safe_SVAL(param, tpscnt, p, 0, -1);
+ int buf_len = get_safe_SVAL(param,tpscnt, p, 2, 0);
+ uint32_t servertype = get_safe_IVAL(param,tpscnt,p,4, 0);
+ char *p2;
+ int data_len, fixed_len, string_len;
+ int f_len = 0, s_len = 0;
+ struct srv_info_struct *servers=NULL;
+ int counted=0,first=0,total=0;
+ int i,missed;
+ fstring domain;
+ fstring first_name;
+ bool domain_request;
+ bool local_request;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ /* If someone sets all the bits they don't really mean to set
+ DOMAIN_ENUM and LOCAL_LIST_ONLY, they just want all the
+ known servers. */
+
+ if (servertype == SV_TYPE_ALL) {
+ servertype &= ~(SV_TYPE_DOMAIN_ENUM|SV_TYPE_LOCAL_LIST_ONLY);
+ }
+
+ /* If someone sets SV_TYPE_LOCAL_LIST_ONLY but hasn't set
+ any other bit (they may just set this bit on its own) they
+ want all the locally seen servers. However this bit can be
+ set on its own so set the requested servers to be
+ ALL - DOMAIN_ENUM. */
+
+ if ((servertype & SV_TYPE_LOCAL_LIST_ONLY) && !(servertype & SV_TYPE_DOMAIN_ENUM)) {
+ servertype = SV_TYPE_ALL & ~(SV_TYPE_DOMAIN_ENUM);
+ }
+
+ domain_request = ((servertype & SV_TYPE_DOMAIN_ENUM) != 0);
+ local_request = ((servertype & SV_TYPE_LOCAL_LIST_ONLY) != 0);
+
+ p += 8;
+
+ if (strcmp(str1, "WrLehDzz") != 0) {
+ return false;
+ }
+ if (!check_session_info(uLevel,str2)) {
+ return False;
+ }
+
+ DEBUG(4, ("server request level: %s %8x ", str2, servertype));
+ DEBUG(4, ("domains_req:%s ", BOOLSTR(domain_request)));
+ DEBUG(4, ("local_only:%s\n", BOOLSTR(local_request)));
+
+ if (skip_string(param,tpscnt,p) == NULL) {
+ return False;
+ }
+ pull_ascii_fstring(domain, p);
+ if (domain[0] == '\0') {
+ fstrcpy(domain, lp_workgroup());
+ }
+ p = skip_string(param,tpscnt,p);
+ if (skip_string(param,tpscnt,p) == NULL) {
+ return False;
+ }
+ pull_ascii_fstring(first_name, p);
+
+ DEBUG(4, ("domain: '%s' first_name: '%s'\n",
+ domain, first_name));
+
+ if (lp_browse_list()) {
+ total = get_session_info(servertype,&servers,domain);
+ }
+
+ data_len = fixed_len = string_len = 0;
+ missed = 0;
+
+ TYPESAFE_QSORT(servers, total, srv_comp);
+
+ if (first_name[0] != '\0') {
+ struct srv_info_struct *first_server = NULL;
+
+ BINARY_ARRAY_SEARCH(servers, total, name, first_name,
+ srv_name_match, first_server);
+ if (first_server) {
+ first = PTR_DIFF(first_server, servers) / sizeof(*servers);
+ /*
+ * The binary search may not find the exact match
+ * so we need to search backward to find the first match
+ *
+ * This implements the strange matching windows
+ * implements. (see the comment in srv_name_match().
+ */
+ for (;first > 0;) {
+ int ret;
+ ret = strcasecmp_m(first_name,
+ servers[first-1].name);
+ if (ret > 0) {
+ break;
+ }
+ first--;
+ }
+ } else {
+ /* we should return no entries */
+ first = total;
+ }
+ }
+
+ {
+ char *lastname=NULL;
+
+ for (i=first;i<total;i++) {
+ struct srv_info_struct *s = &servers[i];
+
+ if (lastname && strequal(lastname,s->name)) {
+ continue;
+ }
+ lastname = s->name;
+ data_len += fill_srv_info(s,uLevel,0,&f_len,0,&s_len,0);
+ DEBUG(4,("fill_srv_info[%d] %20s %8x %25s %15s\n",
+ i, s->name, s->type, s->comment, s->domain));
+
+ if (data_len < buf_len) {
+ counted++;
+ fixed_len += f_len;
+ string_len += s_len;
+ } else {
+ missed++;
+ }
+ }
+ }
+
+ *rdata_len = fixed_len + string_len;
+ *rdata = smb_realloc_limit(*rdata,*rdata_len);
+ if (!*rdata) {
+ return False;
+ }
+
+ p2 = (*rdata) + fixed_len; /* auxiliary data (strings) will go here */
+ p = *rdata;
+ f_len = fixed_len;
+ s_len = string_len;
+
+ {
+ char *lastname=NULL;
+ int count2 = counted;
+
+ for (i = first; i < total && count2;i++) {
+ struct srv_info_struct *s = &servers[i];
+
+ if (lastname && strequal(lastname,s->name)) {
+ continue;
+ }
+ lastname = s->name;
+ fill_srv_info(s,uLevel,&p,&f_len,&p2,&s_len,*rdata);
+ DEBUG(4,("fill_srv_info[%d] %20s %8x %25s %15s\n",
+ i, s->name, s->type, s->comment, s->domain));
+ count2--;
+ }
+ }
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVAL(*rparam,0,(missed == 0 ? NERR_Success : ERRmoredata));
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,counted);
+ SSVAL(*rparam,6,counted+missed);
+
+ DEBUG(3,("NetServerEnum3 domain = %s uLevel=%d first=%d[%s => %s] counted=%d total=%d\n",
+ domain,uLevel,first,first_name,
+ first < total ? servers[first].name : "",
+ counted,counted+missed));
+
+ SAFE_FREE(servers);
+
+ return True;
+}
+
+/****************************************************************************
+ command 0x34 - suspected of being a "Lookup Names" stub api
+ ****************************************************************************/
+
+static bool api_RNetGroupGetUsers(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt, int mprcnt, char **rdata,
+ char **rparam, int *rdata_len, int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ int buf_len = get_safe_SVAL(param,tpscnt,p,2,0);
+ int counted=0;
+ int missed=0;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ DEBUG(5,("RNetGroupGetUsers: %s %s %s %d %d\n",
+ str1, str2, p, uLevel, buf_len));
+
+ if (!prefix_ok(str1,"zWrLeh")) {
+ return False;
+ }
+
+ *rdata_len = 0;
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ SSVAL(*rparam,0,0x08AC); /* informational warning message */
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,counted);
+ SSVAL(*rparam,6,counted+missed);
+
+ return True;
+}
+
+/****************************************************************************
+ get info about a share
+ ****************************************************************************/
+
+static bool check_share_info(int uLevel, char* id)
+{
+ switch( uLevel ) {
+ case 0:
+ if (strcmp(id,"B13") != 0) {
+ return False;
+ }
+ break;
+ case 1:
+ /* Level-2 descriptor is allowed (and ignored) */
+ if (strcmp(id,"B13BWz") != 0 &&
+ strcmp(id,"B13BWzWWWzB9B") != 0) {
+ return False;
+ }
+ break;
+ case 2:
+ if (strcmp(id,"B13BWzWWWzB9B") != 0) {
+ return False;
+ }
+ break;
+ case 91:
+ if (strcmp(id,"B13BWzWWWzB9BB9BWzWWzWW") != 0) {
+ return False;
+ }
+ break;
+ default:
+ return False;
+ }
+ return True;
+}
+
+static int fill_share_info(connection_struct *conn, int snum, int uLevel,
+ char** buf, int* buflen,
+ char** stringbuf, int* stringspace, char* baseaddr)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int struct_len;
+ char* p;
+ char* p2;
+ int l2;
+ int len;
+
+ switch( uLevel ) {
+ case 0:
+ struct_len = 13;
+ break;
+ case 1:
+ struct_len = 20;
+ break;
+ case 2:
+ struct_len = 40;
+ break;
+ case 91:
+ struct_len = 68;
+ break;
+ default:
+ return -1;
+ }
+
+ if (!buf) {
+ len = 0;
+
+ if (uLevel > 0) {
+ len += StrlenExpanded(conn,snum,lp_comment(talloc_tos(), lp_sub, snum));
+ }
+ if (uLevel > 1) {
+ len += strlen(lp_path(talloc_tos(), lp_sub, snum)) + 1;
+ }
+ if (buflen) {
+ *buflen = struct_len;
+ }
+ if (stringspace) {
+ *stringspace = len;
+ }
+ return struct_len + len;
+ }
+
+ len = struct_len;
+ p = *buf;
+ if ((*buflen) < struct_len) {
+ return -1;
+ }
+
+ if (stringbuf) {
+ p2 = *stringbuf;
+ l2 = *stringspace;
+ } else {
+ p2 = p + struct_len;
+ l2 = (*buflen) - struct_len;
+ }
+
+ if (!baseaddr) {
+ baseaddr = p;
+ }
+
+ push_ascii(p,lp_servicename(talloc_tos(), lp_sub, snum),13, STR_TERMINATE);
+
+ if (uLevel > 0) {
+ int type;
+
+ SCVAL(p,13,0);
+ type = STYPE_DISKTREE;
+ if (lp_printable(snum)) {
+ type = STYPE_PRINTQ;
+ }
+ if (strequal("IPC",lp_fstype(snum))) {
+ type = STYPE_IPC;
+ }
+ SSVAL(p,14,type); /* device type */
+ SIVAL(p,16,PTR_DIFF(p2,baseaddr));
+ len += CopyExpanded(conn,snum,&p2,lp_comment(talloc_tos(), lp_sub, snum),&l2);
+ }
+
+ if (uLevel > 1) {
+ SSVAL(p,20,ACCESS_READ|ACCESS_WRITE|ACCESS_CREATE); /* permissions */
+ SSVALS(p,22,-1); /* max uses */
+ SSVAL(p,24,1); /* current uses */
+ SIVAL(p,26,PTR_DIFF(p2,baseaddr)); /* local pathname */
+ len += CopyAndAdvance(&p2,lp_path(talloc_tos(),lp_sub, snum),&l2);
+ memset(p+30,0,SHPWLEN+2); /* passwd (reserved), pad field */
+ }
+
+ if (uLevel > 2) {
+ memset(p+40,0,SHPWLEN+2);
+ SSVAL(p,50,0);
+ SIVAL(p,52,0);
+ SSVAL(p,56,0);
+ SSVAL(p,58,0);
+ SIVAL(p,60,0);
+ SSVAL(p,64,0);
+ SSVAL(p,66,0);
+ }
+
+ if (stringbuf) {
+ (*buf) = p + struct_len;
+ (*buflen) -= struct_len;
+ (*stringbuf) = p2;
+ (*stringspace) = l2;
+ } else {
+ (*buf) = p2;
+ (*buflen) -= len;
+ }
+
+ return len;
+}
+
+static bool api_RNetShareGetInfo(struct smbd_server_connection *sconn,
+ connection_struct *conn,uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *netname_in = skip_string(param,tpscnt,str2);
+ char *netname = NULL;
+ char *p = skip_string(param,tpscnt,netname);
+ int uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ int snum;
+
+ if (!str1 || !str2 || !netname_in || !p) {
+ return False;
+ }
+
+ snum = find_service(talloc_tos(), netname_in, &netname);
+ if (snum < 0 || !netname) {
+ return False;
+ }
+
+ /* check it's a supported variant */
+ if (!prefix_ok(str1,"zWrLh")) {
+ return False;
+ }
+ if (!check_share_info(uLevel,str2)) {
+ return False;
+ }
+
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ p = *rdata;
+ *rdata_len = fill_share_info(conn,snum,uLevel,&p,&mdrcnt,0,0,0);
+ if (*rdata_len < 0) {
+ return False;
+ }
+
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVAL(*rparam,0,NERR_Success);
+ SSVAL(*rparam,2,0); /* converter word */
+ SSVAL(*rparam,4,*rdata_len);
+
+ return True;
+}
+
+/****************************************************************************
+ View the list of available shares.
+
+ This function is the server side of the NetShareEnum() RAP call.
+ It fills the return buffer with share names and share comments.
+ Note that the return buffer normally (in all known cases) allows only
+ twelve byte strings for share names (plus one for a nul terminator).
+ Share names longer than 12 bytes must be skipped.
+ ****************************************************************************/
+
+static bool api_RNetShareEnum(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,
+ int mprcnt,
+ char **rdata,
+ char **rparam,
+ int *rdata_len,
+ int *rparam_len )
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ int buf_len = get_safe_SVAL(param,tpscnt,p,2,0);
+ char *p2;
+ int count = 0;
+ int total=0,counted=0;
+ bool missed = False;
+ int i;
+ int data_len, fixed_len, string_len;
+ int f_len = 0, s_len = 0;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ if (!prefix_ok(str1,"WrLeh")) {
+ return False;
+ }
+ if (!check_share_info(uLevel,str2)) {
+ return False;
+ }
+
+ /* Ensure all the usershares are loaded. */
+ become_root();
+ delete_and_reload_printers();
+ load_registry_shares();
+ count = load_usershare_shares(NULL, connections_snum_used);
+ unbecome_root();
+
+ data_len = fixed_len = string_len = 0;
+ for (i=0;i<count;i++) {
+ fstring servicename_dos;
+ if (!(lp_browseable(i) && lp_snum_ok(i))) {
+ continue;
+ }
+ push_ascii_fstring(servicename_dos, lp_servicename(talloc_tos(), lp_sub, i));
+ /* Maximum name length = 13. */
+ if( lp_browseable( i ) && lp_snum_ok( i ) && (strlen(servicename_dos) < 13)) {
+ total++;
+ data_len += fill_share_info(conn,i,uLevel,0,&f_len,0,&s_len,0);
+ if (data_len < buf_len) {
+ counted++;
+ fixed_len += f_len;
+ string_len += s_len;
+ } else {
+ missed = True;
+ }
+ }
+ }
+
+ *rdata_len = fixed_len + string_len;
+ *rdata = smb_realloc_limit(*rdata,*rdata_len);
+ if (!*rdata) {
+ return False;
+ }
+
+ p2 = (*rdata) + fixed_len; /* auxiliary data (strings) will go here */
+ p = *rdata;
+ f_len = fixed_len;
+ s_len = string_len;
+
+ for( i = 0; i < count; i++ ) {
+ fstring servicename_dos;
+ if (!(lp_browseable(i) && lp_snum_ok(i))) {
+ continue;
+ }
+
+ push_ascii_fstring(servicename_dos,
+ lp_servicename(talloc_tos(), lp_sub, i));
+ if (lp_browseable(i) && lp_snum_ok(i) && (strlen(servicename_dos) < 13)) {
+ if (fill_share_info( conn,i,uLevel,&p,&f_len,&p2,&s_len,*rdata ) < 0) {
+ break;
+ }
+ }
+ }
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVAL(*rparam,0,missed ? ERRmoredata : NERR_Success);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,counted);
+ SSVAL(*rparam,6,total);
+
+ DEBUG(3,("RNetShareEnum gave %d entries of %d (%d %d %d %d)\n",
+ counted,total,uLevel,
+ buf_len,*rdata_len,mdrcnt));
+
+ return True;
+}
+
+/****************************************************************************
+ Add a share
+ ****************************************************************************/
+
+static bool api_RNetShareAdd(struct smbd_server_connection *sconn,
+ connection_struct *conn,uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ fstring sharename;
+ fstring comment;
+ char *pathname = NULL;
+ unsigned int offset;
+ int res = ERRunsup;
+ size_t converted_size;
+
+ WERROR werr = WERR_OK;
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ NTSTATUS status;
+ struct rpc_pipe_client *cli = NULL;
+ union srvsvc_NetShareInfo info;
+ struct srvsvc_NetShareInfo2 info2;
+ struct dcerpc_binding_handle *b;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ /* check it's a supported variant */
+ if (!prefix_ok(str1,RAP_WShareAdd_REQ)) {
+ return False;
+ }
+ if (!check_share_info(uLevel,str2)) {
+ return False;
+ }
+ if (uLevel != 2) {
+ return False;
+ }
+
+ /* Do we have a string ? */
+ if (skip_string(data,mdrcnt,data) == NULL) {
+ return False;
+ }
+ pull_ascii_fstring(sharename,data);
+
+ if (mdrcnt < 28) {
+ return False;
+ }
+
+ /* only support disk share adds */
+ if (SVAL(data,14)!=STYPE_DISKTREE) {
+ return False;
+ }
+
+ offset = IVAL(data, 16);
+ if (offset >= mdrcnt) {
+ res = ERRinvalidparam;
+ goto out;
+ }
+
+ /* Do we have a string ? */
+ if (skip_string(data,mdrcnt,data+offset) == NULL) {
+ return False;
+ }
+ pull_ascii_fstring(comment, offset? (data+offset) : "");
+
+ offset = IVAL(data, 26);
+
+ if (offset >= mdrcnt) {
+ res = ERRinvalidparam;
+ goto out;
+ }
+
+ /* Do we have a string ? */
+ if (skip_string(data,mdrcnt,data+offset) == NULL) {
+ return False;
+ }
+
+ if (!pull_ascii_talloc(talloc_tos(), &pathname,
+ offset ? (data+offset) : "", &converted_size))
+ {
+ DEBUG(0,("api_RNetShareAdd: pull_ascii_talloc failed: %s\n",
+ strerror(errno)));
+ }
+
+ if (!pathname) {
+ return false;
+ }
+
+ status = rpc_pipe_open_interface(mem_ctx, &ndr_table_srvsvc,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("api_RNetShareAdd: could not connect to srvsvc: %s\n",
+ nt_errstr(status)));
+ res = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+
+ b = cli->binding_handle;
+
+ info2.name = sharename;
+ info2.type = STYPE_DISKTREE;
+ info2.comment = comment;
+ info2.permissions = 0;
+ info2.max_users = 0;
+ info2.current_users = 0;
+ info2.path = pathname;
+ info2.password = NULL;
+
+ info.info2 = &info2;
+
+ status = dcerpc_srvsvc_NetShareAdd(b, mem_ctx,
+ cli->srv_name_slash,
+ 2,
+ &info,
+ NULL,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ res = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ res = W_ERROR_V(werr);
+ goto out;
+ }
+
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVAL(*rparam,0,NERR_Success);
+ SSVAL(*rparam,2,0); /* converter word */
+ SSVAL(*rparam,4,*rdata_len);
+ *rdata_len = 0;
+
+ return True;
+
+ out:
+
+ *rparam_len = 4;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ *rdata_len = 0;
+ SSVAL(*rparam,0,res);
+ SSVAL(*rparam,2,0);
+ return True;
+}
+
+/****************************************************************************
+ view list of groups available
+ ****************************************************************************/
+
+static bool api_RNetGroupEnum(struct smbd_server_connection *sconn,
+ connection_struct *conn,uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ int i;
+ int errflags=0;
+ int resume_context, cli_buf_size;
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+
+ uint32_t num_groups;
+ uint32_t resume_handle;
+ struct rpc_pipe_client *samr_pipe = NULL;
+ struct policy_handle samr_handle, domain_handle;
+ NTSTATUS status, result;
+ struct dcerpc_binding_handle *b;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ if (strcmp(str1,"WrLeh") != 0) {
+ return False;
+ }
+
+ /* parameters
+ * W-> resume context (number of users to skip)
+ * r -> return parameter pointer to receive buffer
+ * L -> length of receive buffer
+ * e -> return parameter number of entries
+ * h -> return parameter total number of users
+ */
+
+ if (strcmp("B21",str2) != 0) {
+ return False;
+ }
+
+ status = rpc_pipe_open_interface(
+ talloc_tos(), &ndr_table_samr,
+ conn->session_info, conn->sconn->remote_address,
+ conn->sconn->local_address, conn->sconn->msg_ctx, &samr_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("api_RNetUserEnum: Could not connect to samr: %s\n",
+ nt_errstr(status)));
+ return false;
+ }
+
+ b = samr_pipe->binding_handle;
+
+ status = dcerpc_samr_Connect2(b, talloc_tos(), lp_netbios_name(),
+ SAMR_ACCESS_LOOKUP_DOMAIN, &samr_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_Connect2 failed: %s\n",
+ nt_errstr(status)));
+ return false;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_Connect2 failed: %s\n",
+ nt_errstr(result)));
+ return false;
+ }
+
+ status = dcerpc_samr_OpenDomain(b, talloc_tos(), &samr_handle,
+ SAMR_DOMAIN_ACCESS_ENUM_ACCOUNTS,
+ get_global_sam_sid(), &domain_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_OpenDomain failed: %s\n",
+ nt_errstr(status)));
+ dcerpc_samr_Close(b, talloc_tos(), &samr_handle, &result);
+ return false;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_OpenDomain failed: %s\n",
+ nt_errstr(result)));
+ dcerpc_samr_Close(b, talloc_tos(), &samr_handle, &result);
+ return false;
+ }
+
+ resume_context = get_safe_SVAL(param,tpscnt,p,0,-1);
+ cli_buf_size= get_safe_SVAL(param,tpscnt,p,2,0);
+ DEBUG(10,("api_RNetGroupEnum:resume context: %d, client buffer size: "
+ "%d\n", resume_context, cli_buf_size));
+
+ *rdata_len = cli_buf_size;
+ *rdata = smb_realloc_limit(*rdata,*rdata_len);
+ if (!*rdata) {
+ return False;
+ }
+
+ p = *rdata;
+
+ errflags = NERR_Success;
+ num_groups = 0;
+ resume_handle = 0;
+
+ while (true) {
+ struct samr_SamArray *sam_entries;
+ uint32_t num_entries;
+
+ status = dcerpc_samr_EnumDomainGroups(b, talloc_tos(),
+ &domain_handle,
+ &resume_handle,
+ &sam_entries, 1,
+ &num_entries,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("dcerpc_samr_EnumDomainGroups returned "
+ "%s\n", nt_errstr(status)));
+ break;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ DEBUG(10, ("dcerpc_samr_EnumDomainGroups returned "
+ "%s\n", nt_errstr(result)));
+ break;
+ }
+
+ if (num_entries == 0) {
+ DEBUG(10, ("dcerpc_samr_EnumDomainGroups returned "
+ "no entries -- done\n"));
+ break;
+ }
+
+ for(i=0; i<num_entries; i++) {
+ const char *name;
+
+ name = sam_entries->entries[i].name.string;
+
+ if( ((PTR_DIFF(p,*rdata)+21) > *rdata_len) ) {
+ /* set overflow error */
+ DEBUG(3,("overflow on entry %d group %s\n", i,
+ name));
+ errflags=234;
+ break;
+ }
+
+ /* truncate the name at 21 chars. */
+ memset(p, 0, 21);
+ strlcpy(p, name, 21);
+ DEBUG(10,("adding entry %d group %s\n", i, p));
+ p += 21;
+ p += 5; /* Both NT4 and W2k3SP1 do padding here. No
+ * idea why... */
+ num_groups += 1;
+ }
+
+ if (errflags != NERR_Success) {
+ break;
+ }
+
+ TALLOC_FREE(sam_entries);
+ }
+
+ dcerpc_samr_Close(b, talloc_tos(), &domain_handle, &result);
+ dcerpc_samr_Close(b, talloc_tos(), &samr_handle, &result);
+
+ *rdata_len = PTR_DIFF(p,*rdata);
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVAL(*rparam, 0, errflags);
+ SSVAL(*rparam, 2, 0); /* converter word */
+ SSVAL(*rparam, 4, num_groups); /* is this right?? */
+ SSVAL(*rparam, 6, resume_context+num_groups); /* is this right?? */
+
+ return(True);
+}
+
+/*******************************************************************
+ Get groups that a user is a member of.
+******************************************************************/
+
+static bool api_NetUserGetGroups(struct smbd_server_connection *sconn,
+ connection_struct *conn,uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *UserName = skip_string(param,tpscnt,str2);
+ char *p = skip_string(param,tpscnt,UserName);
+ int uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ const char *level_string;
+ int count=0;
+ bool ret = False;
+ uint32_t i;
+ char *endp = NULL;
+
+ struct rpc_pipe_client *samr_pipe = NULL;
+ struct policy_handle samr_handle, domain_handle, user_handle;
+ struct lsa_String name;
+ struct lsa_Strings names;
+ struct samr_Ids type, rid;
+ struct samr_RidWithAttributeArray *rids;
+ NTSTATUS status, result;
+ struct dcerpc_binding_handle *b;
+
+ if (!str1 || !str2 || !UserName || !p) {
+ return False;
+ }
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ /* check it's a supported variant */
+
+ if ( strcmp(str1,"zWrLeh") != 0 )
+ return False;
+
+ switch( uLevel ) {
+ case 0:
+ level_string = "B21";
+ break;
+ default:
+ return False;
+ }
+
+ if (strcmp(level_string,str2) != 0)
+ return False;
+
+ *rdata_len = mdrcnt + 1024;
+ *rdata = smb_realloc_limit(*rdata,*rdata_len);
+ if (!*rdata) {
+ return False;
+ }
+
+ SSVAL(*rparam,0,NERR_Success);
+ SSVAL(*rparam,2,0); /* converter word */
+
+ p = *rdata;
+ endp = *rdata + *rdata_len;
+
+ status = rpc_pipe_open_interface(
+ talloc_tos(), &ndr_table_samr,
+ conn->session_info, conn->sconn->remote_address,
+ conn->sconn->local_address, conn->sconn->msg_ctx, &samr_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("api_RNetUserEnum: Could not connect to samr: %s\n",
+ nt_errstr(status)));
+ return false;
+ }
+
+ b = samr_pipe->binding_handle;
+
+ status = dcerpc_samr_Connect2(b, talloc_tos(), lp_netbios_name(),
+ SAMR_ACCESS_LOOKUP_DOMAIN, &samr_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_Connect2 failed: %s\n",
+ nt_errstr(status)));
+ return false;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_Connect2 failed: %s\n",
+ nt_errstr(result)));
+ return false;
+ }
+
+ status = dcerpc_samr_OpenDomain(b, talloc_tos(), &samr_handle,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ get_global_sam_sid(), &domain_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_OpenDomain failed: %s\n",
+ nt_errstr(status)));
+ goto close_sam;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_OpenDomain failed: %s\n",
+ nt_errstr(result)));
+ goto close_sam;
+ }
+
+ name.string = UserName;
+
+ status = dcerpc_samr_LookupNames(b, talloc_tos(),
+ &domain_handle, 1, &name,
+ &rid, &type,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_LookupNames failed: %s\n",
+ nt_errstr(status)));
+ goto close_domain;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_LookupNames failed: %s\n",
+ nt_errstr(result)));
+ goto close_domain;
+ }
+ if (rid.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto close_domain;
+ }
+ if (type.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto close_domain;
+ }
+
+ if (type.ids[0] != SID_NAME_USER) {
+ DEBUG(10, ("%s is a %s, not a user\n", UserName,
+ sid_type_lookup(type.ids[0])));
+ goto close_domain;
+ }
+
+ status = dcerpc_samr_OpenUser(b, talloc_tos(),
+ &domain_handle,
+ SAMR_USER_ACCESS_GET_GROUPS,
+ rid.ids[0], &user_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_LookupNames failed: %s\n",
+ nt_errstr(status)));
+ goto close_domain;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_LookupNames failed: %s\n",
+ nt_errstr(result)));
+ goto close_domain;
+ }
+
+ status = dcerpc_samr_GetGroupsForUser(b, talloc_tos(),
+ &user_handle, &rids,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_LookupNames failed: %s\n",
+ nt_errstr(status)));
+ goto close_user;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_LookupNames failed: %s\n",
+ nt_errstr(result)));
+ goto close_user;
+ }
+
+ for (i=0; i<rids->count; i++) {
+
+ status = dcerpc_samr_LookupRids(b, talloc_tos(),
+ &domain_handle,
+ 1, &rids->rids[i].rid,
+ &names, &type,
+ &result);
+ if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(result) && (names.count == 1)) {
+ strlcpy(p, names.names[0].string, PTR_DIFF(endp,p));
+ p += 21;
+ count++;
+ }
+ }
+
+ *rdata_len = PTR_DIFF(p,*rdata);
+
+ SSVAL(*rparam,4,count); /* is this right?? */
+ SSVAL(*rparam,6,count); /* is this right?? */
+
+ ret = True;
+
+ close_user:
+ dcerpc_samr_Close(b, talloc_tos(), &user_handle, &result);
+ close_domain:
+ dcerpc_samr_Close(b, talloc_tos(), &domain_handle, &result);
+ close_sam:
+ dcerpc_samr_Close(b, talloc_tos(), &samr_handle, &result);
+
+ return ret;
+}
+
+/*******************************************************************
+ Get all users.
+******************************************************************/
+
+static bool api_RNetUserEnum(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ int count_sent=0;
+ int num_users=0;
+ int errflags=0;
+ int i, resume_context, cli_buf_size;
+ uint32_t resume_handle;
+
+ struct rpc_pipe_client *samr_pipe = NULL;
+ struct policy_handle samr_handle, domain_handle;
+ NTSTATUS status, result;
+
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ char *endp = NULL;
+
+ struct dcerpc_binding_handle *b;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ if (strcmp(str1,"WrLeh") != 0)
+ return False;
+ /* parameters
+ * W-> resume context (number of users to skip)
+ * r -> return parameter pointer to receive buffer
+ * L -> length of receive buffer
+ * e -> return parameter number of entries
+ * h -> return parameter total number of users
+ */
+
+ resume_context = get_safe_SVAL(param,tpscnt,p,0,-1);
+ cli_buf_size= get_safe_SVAL(param,tpscnt,p,2,0);
+ DEBUG(10,("api_RNetUserEnum:resume context: %d, client buffer size: %d\n",
+ resume_context, cli_buf_size));
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ /* check it's a supported variant */
+ if (strcmp("B21",str2) != 0)
+ return False;
+
+ *rdata_len = cli_buf_size;
+ *rdata = smb_realloc_limit(*rdata,*rdata_len);
+ if (!*rdata) {
+ return False;
+ }
+
+ p = *rdata;
+ endp = *rdata + *rdata_len;
+
+ status = rpc_pipe_open_interface(
+ talloc_tos(), &ndr_table_samr,
+ conn->session_info, conn->sconn->remote_address,
+ conn->sconn->local_address, conn->sconn->msg_ctx, &samr_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("api_RNetUserEnum: Could not connect to samr: %s\n",
+ nt_errstr(status)));
+ return false;
+ }
+
+ b = samr_pipe->binding_handle;
+
+ status = dcerpc_samr_Connect2(b, talloc_tos(), lp_netbios_name(),
+ SAMR_ACCESS_LOOKUP_DOMAIN, &samr_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_Connect2 failed: %s\n",
+ nt_errstr(status)));
+ return false;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_Connect2 failed: %s\n",
+ nt_errstr(result)));
+ return false;
+ }
+
+ status = dcerpc_samr_OpenDomain(b, talloc_tos(), &samr_handle,
+ SAMR_DOMAIN_ACCESS_ENUM_ACCOUNTS,
+ get_global_sam_sid(), &domain_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_OpenDomain failed: %s\n",
+ nt_errstr(status)));
+ dcerpc_samr_Close(b, talloc_tos(), &samr_handle, &result);
+ return false;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_OpenDomain failed: %s\n",
+ nt_errstr(result)));
+ dcerpc_samr_Close(b, talloc_tos(), &samr_handle, &result);
+ return false;
+ }
+
+ errflags=NERR_Success;
+
+ resume_handle = 0;
+
+ while (true) {
+ struct samr_SamArray *sam_entries;
+ uint32_t num_entries;
+
+ status = dcerpc_samr_EnumDomainUsers(b, talloc_tos(),
+ &domain_handle,
+ &resume_handle,
+ 0, &sam_entries, 1,
+ &num_entries,
+ &result);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("dcerpc_samr_EnumDomainUsers returned "
+ "%s\n", nt_errstr(status)));
+ break;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10, ("dcerpc_samr_EnumDomainUsers returned "
+ "%s\n", nt_errstr(result)));
+ break;
+ }
+
+ if (num_entries == 0) {
+ DEBUG(10, ("dcerpc_samr_EnumDomainUsers returned "
+ "no entries -- done\n"));
+ break;
+ }
+
+ for (i=0; i<num_entries; i++) {
+ const char *name;
+
+ name = sam_entries->entries[i].name.string;
+
+ if(((PTR_DIFF(p,*rdata)+21)<=*rdata_len)
+ &&(strlen(name)<=21)) {
+ strlcpy(p,name,PTR_DIFF(endp,p));
+ DEBUG(10,("api_RNetUserEnum:adding entry %d "
+ "username %s\n",count_sent,p));
+ p += 21;
+ count_sent++;
+ } else {
+ /* set overflow error */
+ DEBUG(10,("api_RNetUserEnum:overflow on entry %d "
+ "username %s\n",count_sent,name));
+ errflags=234;
+ break;
+ }
+ }
+
+ if (errflags != NERR_Success) {
+ break;
+ }
+
+ TALLOC_FREE(sam_entries);
+ }
+
+ dcerpc_samr_Close(b, talloc_tos(), &domain_handle, &result);
+ dcerpc_samr_Close(b, talloc_tos(), &samr_handle, &result);
+
+ *rdata_len = PTR_DIFF(p,*rdata);
+
+ SSVAL(*rparam,0,errflags);
+ SSVAL(*rparam,2,0); /* converter word */
+ SSVAL(*rparam,4,count_sent); /* is this right?? */
+ SSVAL(*rparam,6,num_users); /* is this right?? */
+
+ return True;
+}
+
+/****************************************************************************
+ Get the time of day info.
+****************************************************************************/
+
+static bool api_NetRemoteTOD(struct smbd_server_connection *sconn,
+ connection_struct *conn,uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ struct tm *t;
+ time_t unixdate = time(NULL);
+ char *p;
+
+ *rparam_len = 4;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ *rdata_len = 21;
+ *rdata = smb_realloc_limit(*rdata,*rdata_len);
+ if (!*rdata) {
+ return False;
+ }
+
+ SSVAL(*rparam,0,NERR_Success);
+ SSVAL(*rparam,2,0); /* converter word */
+
+ p = *rdata;
+
+ srv_put_dos_date3(p,0,unixdate); /* this is the time that is looked at
+ by NT in a "net time" operation,
+ it seems to ignore the one below */
+
+ /* the client expects to get localtime, not GMT, in this bit
+ (I think, this needs testing) */
+ t = localtime(&unixdate);
+ if (!t) {
+ return False;
+ }
+
+ SIVAL(p,4,0); /* msecs ? */
+ SCVAL(p,8,t->tm_hour);
+ SCVAL(p,9,t->tm_min);
+ SCVAL(p,10,t->tm_sec);
+ SCVAL(p,11,0); /* hundredths of seconds */
+ SSVALS(p,12,get_time_zone(unixdate)/60); /* timezone in minutes from GMT */
+ SSVAL(p,14,10000); /* timer interval in 0.0001 of sec */
+ SCVAL(p,16,t->tm_mday);
+ SCVAL(p,17,t->tm_mon + 1);
+ SSVAL(p,18,1900+t->tm_year);
+ SCVAL(p,20,t->tm_wday);
+
+ return True;
+}
+
+/****************************************************************************
+ Set the user password (SamOEM version - gets plaintext).
+****************************************************************************/
+
+static bool api_SamOEMChangePassword(struct smbd_server_connection *sconn,
+ connection_struct *conn,uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ fstring user;
+ char *p = get_safe_str_ptr(param,tpscnt,param,2);
+
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ NTSTATUS status, result;
+ struct rpc_pipe_client *cli = NULL;
+ struct lsa_AsciiString server, account;
+ struct samr_CryptPassword password;
+ struct samr_Password hash;
+ int errcode = NERR_badpass;
+ int bufsize;
+ struct dcerpc_binding_handle *b;
+
+ *rparam_len = 4;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ if (!p) {
+ return False;
+ }
+ *rdata_len = 0;
+
+ SSVAL(*rparam,0,NERR_badpass);
+
+ /*
+ * Check the parameter definition is correct.
+ */
+
+ /* Do we have a string ? */
+ if (skip_string(param,tpscnt,p) == 0) {
+ return False;
+ }
+ if(!strequal(p, "zsT")) {
+ DEBUG(0,("api_SamOEMChangePassword: Invalid parameter string %s\n", p));
+ return False;
+ }
+ p = skip_string(param, tpscnt, p);
+ if (!p) {
+ return False;
+ }
+
+ /* Do we have a string ? */
+ if (skip_string(param,tpscnt,p) == 0) {
+ return False;
+ }
+ if(!strequal(p, "B516B16")) {
+ DEBUG(0,("api_SamOEMChangePassword: Invalid data parameter string %s\n", p));
+ return False;
+ }
+ p = skip_string(param,tpscnt,p);
+ if (!p) {
+ return False;
+ }
+ /* Do we have a string ? */
+ if (skip_string(param,tpscnt,p) == 0) {
+ return False;
+ }
+ p += pull_ascii_fstring(user,p);
+
+ DEBUG(3,("api_SamOEMChangePassword: Change password for <%s>\n",user));
+
+ if (tdscnt != 532) {
+ errcode = W_ERROR_V(WERR_INVALID_PARAMETER);
+ goto out;
+ }
+
+ bufsize = get_safe_SVAL(param,tpscnt,p,0,-1);
+ if (bufsize != 532) {
+ errcode = W_ERROR_V(WERR_INVALID_PARAMETER);
+ goto out;
+ }
+
+ memcpy(password.data, data, 516);
+ memcpy(hash.hash, data+516, 16);
+
+ status = rpc_pipe_open_interface(mem_ctx, &ndr_table_samr,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("api_SamOEMChangePassword: could not connect to samr: %s\n",
+ nt_errstr(status)));
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+
+ b = cli->binding_handle;
+
+ init_lsa_AsciiString(&server, lp_netbios_name());
+ init_lsa_AsciiString(&account, user);
+
+ status = dcerpc_samr_OemChangePasswordUser2(b, mem_ctx,
+ &server,
+ &account,
+ &password,
+ &hash,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(result));
+ goto out;
+ }
+
+ errcode = NERR_Success;
+ out:
+ SSVAL(*rparam,0,errcode);
+ SSVAL(*rparam,2,0); /* converter word */
+
+ return(True);
+}
+
+/****************************************************************************
+ delete a print job
+ Form: <W> <>
+ ****************************************************************************/
+
+static bool api_RDosPrintJobDel(struct smbd_server_connection *sconn,
+ connection_struct *conn,uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ int function = get_safe_SVAL(param,tpscnt,param,0,0);
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ uint32_t jobid;
+ fstring sharename;
+ int errcode;
+ WERROR werr = WERR_OK;
+
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ NTSTATUS status;
+ struct rpc_pipe_client *cli = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+ struct policy_handle handle;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ enum spoolss_JobControl command;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+ /*
+ * We use 1 here not 2 as we're checking
+ * the last byte we want to access is safe.
+ */
+ if (!is_offset_safe(param,tpscnt,p,1)) {
+ return False;
+ }
+ if(!rap_to_pjobid(SVAL(p,0), sharename, &jobid))
+ return False;
+
+ /* check it's a supported variant */
+ if (!(strcsequal(str1,"W") && strcsequal(str2,"")))
+ return(False);
+
+ *rparam_len = 4;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ *rdata_len = 0;
+
+ ZERO_STRUCT(handle);
+
+ status = rpc_pipe_open_interface(mem_ctx,
+ &ndr_table_spoolss,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("api_RDosPrintJobDel: could not connect to spoolss: %s\n",
+ nt_errstr(status)));
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ b = cli->binding_handle;
+
+ ZERO_STRUCT(devmode_ctr);
+
+ status = dcerpc_spoolss_OpenPrinter(b, mem_ctx,
+ sharename,
+ "RAW",
+ devmode_ctr,
+ JOB_ACCESS_ADMINISTER,
+ &handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ /* FIXME: formerly NERR_JobNotFound was returned if job did not exist
+ * and NERR_DestNotFound if share did not exist */
+
+ errcode = NERR_Success;
+
+ switch (function) {
+ case 81: /* delete */
+ command = SPOOLSS_JOB_CONTROL_DELETE;
+ break;
+ case 82: /* pause */
+ command = SPOOLSS_JOB_CONTROL_PAUSE;
+ break;
+ case 83: /* resume */
+ command = SPOOLSS_JOB_CONTROL_RESUME;
+ break;
+ default:
+ errcode = NERR_notsupported;
+ goto out;
+ }
+
+ status = dcerpc_spoolss_SetJob(b, mem_ctx,
+ &handle,
+ jobid,
+ NULL, /* unique ptr ctr */
+ command,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ out:
+ if (b && is_valid_policy_hnd(&handle)) {
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &handle, &werr);
+ }
+
+ SSVAL(*rparam,0,errcode);
+ SSVAL(*rparam,2,0); /* converter word */
+
+ return(True);
+}
+
+/****************************************************************************
+ Purge a print queue - or pause or resume it.
+ ****************************************************************************/
+
+static bool api_WPrintQueueCtrl(struct smbd_server_connection *sconn,
+ connection_struct *conn,uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ int function = get_safe_SVAL(param,tpscnt,param,0,0);
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *QueueName = skip_string(param,tpscnt,str2);
+ int errcode = NERR_notsupported;
+ WERROR werr = WERR_OK;
+ NTSTATUS status;
+
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ struct rpc_pipe_client *cli = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+ struct policy_handle handle;
+ struct spoolss_SetPrinterInfoCtr info_ctr;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ struct sec_desc_buf secdesc_ctr;
+ enum spoolss_PrinterControl command = SPOOLSS_PRINTER_CONTROL_UNPAUSE;
+
+ if (!str1 || !str2 || !QueueName) {
+ return False;
+ }
+
+ /* check it's a supported variant */
+ if (!(strcsequal(str1,"z") && strcsequal(str2,"")))
+ return(False);
+
+ *rparam_len = 4;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ *rdata_len = 0;
+
+ if (skip_string(param,tpscnt,QueueName) == NULL) {
+ return False;
+ }
+
+ ZERO_STRUCT(handle);
+
+ status = rpc_pipe_open_interface(mem_ctx,
+ &ndr_table_spoolss,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("api_WPrintQueueCtrl: could not connect to spoolss: %s\n",
+ nt_errstr(status)));
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ b = cli->binding_handle;
+
+ ZERO_STRUCT(devmode_ctr);
+
+ status = dcerpc_spoolss_OpenPrinter(b, mem_ctx,
+ QueueName,
+ NULL,
+ devmode_ctr,
+ PRINTER_ACCESS_ADMINISTER,
+ &handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ switch (function) {
+ case 74: /* Pause queue */
+ command = SPOOLSS_PRINTER_CONTROL_PAUSE;
+ break;
+ case 75: /* Resume queue */
+ command = SPOOLSS_PRINTER_CONTROL_RESUME;
+ break;
+ case 103: /* Purge */
+ command = SPOOLSS_PRINTER_CONTROL_PURGE;
+ break;
+ default:
+ werr = WERR_NOT_SUPPORTED;
+ break;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ ZERO_STRUCT(info_ctr);
+ ZERO_STRUCT(secdesc_ctr);
+
+ status = dcerpc_spoolss_SetPrinter(b, mem_ctx,
+ &handle,
+ &info_ctr,
+ &devmode_ctr,
+ &secdesc_ctr,
+ command,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ errcode = W_ERROR_V(werr);
+
+ out:
+
+ if (b && is_valid_policy_hnd(&handle)) {
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &handle, &werr);
+ }
+
+ SSVAL(*rparam,0,errcode);
+ SSVAL(*rparam,2,0); /* converter word */
+
+ return(True);
+}
+
+/****************************************************************************
+ set the property of a print job (undocumented?)
+ ? function = 0xb -> set name of print job
+ ? function = 0x6 -> move print job up/down
+ Form: <WWsTP> <WWzWWDDzzzzzzzzzzlz>
+ or <WWsTP> <WB21BB16B10zWWzDDz>
+****************************************************************************/
+
+static int check_printjob_info(struct pack_desc* desc,
+ int uLevel, char* id)
+{
+ desc->subformat = NULL;
+ switch( uLevel ) {
+ case 0: desc->format = "W"; break;
+ case 1: desc->format = "WB21BB16B10zWWzDDz"; break;
+ case 2: desc->format = "WWzWWDDzz"; break;
+ case 3: desc->format = "WWzWWDDzzzzzzzzzzlz"; break;
+ case 4: desc->format = "WWzWWDDzzzzzDDDDDDD"; break;
+ default:
+ DEBUG(0,("check_printjob_info: invalid level %d\n",
+ uLevel ));
+ return False;
+ }
+ if (id == NULL || strcmp(desc->format,id) != 0) {
+ DEBUG(0,("check_printjob_info: invalid format %s\n",
+ id ? id : "<NULL>" ));
+ return False;
+ }
+ return True;
+}
+
+static bool api_PrintJobInfo(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ struct pack_desc desc;
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ uint32_t jobid;
+ fstring sharename;
+ int uLevel = get_safe_SVAL(param,tpscnt,p,2,-1);
+ int function = get_safe_SVAL(param,tpscnt,p,4,-1);
+ int errcode;
+
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ WERROR werr;
+ NTSTATUS status;
+ struct rpc_pipe_client *cli = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+ struct policy_handle handle;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ struct spoolss_JobInfoContainer ctr;
+ union spoolss_JobInfo info;
+ struct spoolss_SetJobInfo1 info1;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+ /*
+ * We use 1 here not 2 as we're checking
+ * the last byte we want to access is safe.
+ */
+ if (!is_offset_safe(param,tpscnt,p,1)) {
+ return False;
+ }
+ if(!rap_to_pjobid(SVAL(p,0), sharename, &jobid))
+ return False;
+ *rparam_len = 4;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ *rdata_len = 0;
+
+ /* check it's a supported variant */
+ if ((strcmp(str1,"WWsTP")) ||
+ (!check_printjob_info(&desc,uLevel,str2)))
+ return(False);
+
+ errcode = NERR_notsupported;
+
+ switch (function) {
+ case 0xb:
+ /* change print job name, data gives the name */
+ break;
+ default:
+ goto out;
+ }
+
+ ZERO_STRUCT(handle);
+
+ status = rpc_pipe_open_interface(mem_ctx,
+ &ndr_table_spoolss,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("api_PrintJobInfo: could not connect to spoolss: %s\n",
+ nt_errstr(status)));
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ b = cli->binding_handle;
+
+ ZERO_STRUCT(devmode_ctr);
+
+ status = dcerpc_spoolss_OpenPrinter(b, mem_ctx,
+ sharename,
+ "RAW",
+ devmode_ctr,
+ PRINTER_ACCESS_USE,
+ &handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ werr = rpccli_spoolss_getjob(cli, mem_ctx,
+ &handle,
+ jobid,
+ 1, /* level */
+ 0, /* offered */
+ &info);
+ if (!W_ERROR_IS_OK(werr)) {
+ errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ ZERO_STRUCT(ctr);
+
+ info1.job_id = info.info1.job_id;
+ info1.printer_name = info.info1.printer_name;
+ info1.user_name = info.info1.user_name;
+ info1.document_name = data;
+ info1.data_type = info.info1.data_type;
+ info1.text_status = info.info1.text_status;
+ info1.status = info.info1.status;
+ info1.priority = info.info1.priority;
+ info1.position = info.info1.position;
+ info1.total_pages = info.info1.total_pages;
+ info1.pages_printed = info.info1.pages_printed;
+ info1.submitted = info.info1.submitted;
+
+ ctr.level = 1;
+ ctr.info.info1 = &info1;
+
+ status = dcerpc_spoolss_SetJob(b, mem_ctx,
+ &handle,
+ jobid,
+ &ctr,
+ 0,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ errcode = NERR_Success;
+ out:
+
+ if (b && is_valid_policy_hnd(&handle)) {
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &handle, &werr);
+ }
+
+ SSVALS(*rparam,0,errcode);
+ SSVAL(*rparam,2,0); /* converter word */
+
+ return(True);
+}
+
+
+/****************************************************************************
+ Get info about the server.
+****************************************************************************/
+
+static bool api_RNetServerGetInfo(struct smbd_server_connection *sconn,
+ connection_struct *conn,uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ char *p2;
+ int struct_len;
+
+ NTSTATUS status;
+ WERROR werr;
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ struct rpc_pipe_client *cli = NULL;
+ union srvsvc_NetSrvInfo info;
+ int errcode;
+ struct dcerpc_binding_handle *b;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ DEBUG(4,("NetServerGetInfo level %d\n",uLevel));
+
+ /* check it's a supported variant */
+ if (!prefix_ok(str1,"WrLh")) {
+ return False;
+ }
+
+ switch( uLevel ) {
+ case 0:
+ if (strcmp(str2,"B16") != 0) {
+ return False;
+ }
+ struct_len = 16;
+ break;
+ case 1:
+ if (strcmp(str2,"B16BBDz") != 0) {
+ return False;
+ }
+ struct_len = 26;
+ break;
+ case 2:
+ if (strcmp(str2,"B16BBDzDDDWWzWWWWWWWBB21zWWWWWWWWWWWWWWWWWWWWWWz")!= 0) {
+ return False;
+ }
+ struct_len = 134;
+ break;
+ case 3:
+ if (strcmp(str2,"B16BBDzDDDWWzWWWWWWWBB21zWWWWWWWWWWWWWWWWWWWWWWzDWz") != 0) {
+ return False;
+ }
+ struct_len = 144;
+ break;
+ case 20:
+ if (strcmp(str2,"DN") != 0) {
+ return False;
+ }
+ struct_len = 6;
+ break;
+ case 50:
+ if (strcmp(str2,"B16BBDzWWzzz") != 0) {
+ return False;
+ }
+ struct_len = 42;
+ break;
+ default:
+ return False;
+ }
+
+ *rdata_len = mdrcnt;
+ *rdata = smb_realloc_limit(*rdata,*rdata_len);
+ if (!*rdata) {
+ return False;
+ }
+
+ p = *rdata;
+ p2 = p + struct_len;
+
+ status = rpc_pipe_open_interface(mem_ctx, &ndr_table_srvsvc,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("api_RNetServerGetInfo: could not connect to srvsvc: %s\n",
+ nt_errstr(status)));
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+
+ b = cli->binding_handle;
+
+ status = dcerpc_srvsvc_NetSrvGetInfo(b, mem_ctx,
+ NULL,
+ 101,
+ &info,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ if (info.info101 == NULL) {
+ errcode = W_ERROR_V(WERR_INVALID_PARAMETER);
+ goto out;
+ }
+
+ if (uLevel != 20) {
+ size_t len = 0;
+ status = srvstr_push(NULL, 0, p, info.info101->server_name, 16,
+ STR_ASCII|STR_UPPER|STR_TERMINATE, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ }
+ p += 16;
+ if (uLevel > 0) {
+ SCVAL(p,0,info.info101->version_major);
+ SCVAL(p,1,info.info101->version_minor);
+ SIVAL(p,2,info.info101->server_type);
+
+ if (mdrcnt == struct_len) {
+ SIVAL(p,6,0);
+ } else {
+ SIVAL(p,6,PTR_DIFF(p2,*rdata));
+ if (mdrcnt - struct_len <= 0) {
+ return false;
+ }
+ push_ascii(p2,
+ info.info101->comment,
+ MIN(mdrcnt - struct_len,
+ MAX_SERVER_STRING_LENGTH),
+ STR_TERMINATE);
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ }
+ }
+
+ if (uLevel > 1) {
+ return False; /* not yet implemented */
+ }
+
+ errcode = NERR_Success;
+
+ out:
+
+ *rdata_len = PTR_DIFF(p2,*rdata);
+
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVAL(*rparam,0,errcode);
+ SSVAL(*rparam,2,0); /* converter word */
+ SSVAL(*rparam,4,*rdata_len);
+
+ return True;
+}
+
+/****************************************************************************
+ Get info about the server.
+****************************************************************************/
+
+static bool api_NetWkstaGetInfo(struct smbd_server_connection *sconn,
+ connection_struct *conn,uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ char *p2;
+ char *endp;
+ int level = get_safe_SVAL(param,tpscnt,p,0,-1);
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ DEBUG(4,("NetWkstaGetInfo level %d\n",level));
+
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ /* check it's a supported variant */
+ if (!(level==10 && strcsequal(str1,"WrLh") && strcsequal(str2,"zzzBBzz"))) {
+ return False;
+ }
+
+ *rdata_len = mdrcnt + 1024;
+ *rdata = smb_realloc_limit(*rdata,*rdata_len);
+ if (!*rdata) {
+ return False;
+ }
+
+ SSVAL(*rparam,0,NERR_Success);
+ SSVAL(*rparam,2,0); /* converter word */
+
+ p = *rdata;
+ endp = *rdata + *rdata_len;
+
+ p2 = get_safe_ptr(*rdata,*rdata_len,p,22);
+ if (!p2) {
+ return False;
+ }
+
+ SIVAL(p,0,PTR_DIFF(p2,*rdata)); /* host name */
+ strlcpy(p2,get_local_machine_name(),PTR_DIFF(endp,p2));
+ if (!strupper_m(p2)) {
+ return false;
+ }
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ p += 4;
+
+ SIVAL(p,0,PTR_DIFF(p2,*rdata));
+ strlcpy(p2,conn->session_info->unix_info->sanitized_username,PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ p += 4;
+
+ SIVAL(p,0,PTR_DIFF(p2,*rdata)); /* login domain */
+ strlcpy(p2,lp_workgroup(),PTR_DIFF(endp,p2));
+ if (!strupper_m(p2)) {
+ return false;
+ }
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ p += 4;
+
+ SCVAL(p,0,SAMBA_MAJOR_NBT_ANNOUNCE_VERSION); /* system version - e.g 4 in 4.1 */
+ SCVAL(p,1,SAMBA_MINOR_NBT_ANNOUNCE_VERSION); /* system version - e.g .1 in 4.1 */
+ p += 2;
+
+ SIVAL(p,0,PTR_DIFF(p2,*rdata));
+ strlcpy(p2,lp_workgroup(),PTR_DIFF(endp,p2)); /* don't know. login domain?? */
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ p += 4;
+
+ SIVAL(p,0,PTR_DIFF(p2,*rdata)); /* don't know */
+ strlcpy(p2,"",PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ p += 4;
+
+ *rdata_len = PTR_DIFF(p2,*rdata);
+
+ SSVAL(*rparam,4,*rdata_len);
+
+ return True;
+}
+
+/****************************************************************************
+ get info about a user
+
+ struct user_info_11 {
+ char usri11_name[21]; 0-20
+ char usri11_pad; 21
+ char *usri11_comment; 22-25
+ char *usri11_usr_comment; 26-29
+ unsigned short usri11_priv; 30-31
+ unsigned long usri11_auth_flags; 32-35
+ long usri11_password_age; 36-39
+ char *usri11_homedir; 40-43
+ char *usri11_parms; 44-47
+ long usri11_last_logon; 48-51
+ long usri11_last_logoff; 52-55
+ unsigned short usri11_bad_pw_count; 56-57
+ unsigned short usri11_num_logons; 58-59
+ char *usri11_logon_server; 60-63
+ unsigned short usri11_country_code; 64-65
+ char *usri11_workstations; 66-69
+ unsigned long usri11_max_storage; 70-73
+ unsigned short usri11_units_per_week; 74-75
+ unsigned char *usri11_logon_hours; 76-79
+ unsigned short usri11_code_page; 80-81
+ };
+
+where:
+
+ usri11_name specifies the user name for which information is retrieved
+
+ usri11_pad aligns the next data structure element to a word boundary
+
+ usri11_comment is a null terminated ASCII comment
+
+ usri11_user_comment is a null terminated ASCII comment about the user
+
+ usri11_priv specifies the level of the privilege assigned to the user.
+ The possible values are:
+
+Name Value Description
+USER_PRIV_GUEST 0 Guest privilege
+USER_PRIV_USER 1 User privilege
+USER_PRV_ADMIN 2 Administrator privilege
+
+ usri11_auth_flags specifies the account operator privileges. The
+ possible values are:
+
+Name Value Description
+AF_OP_PRINT 0 Print operator
+
+
+Leach, Naik [Page 28]
+
+
+
+INTERNET-DRAFT CIFS Remote Admin Protocol January 10, 1997
+
+
+AF_OP_COMM 1 Communications operator
+AF_OP_SERVER 2 Server operator
+AF_OP_ACCOUNTS 3 Accounts operator
+
+
+ usri11_password_age specifies how many seconds have elapsed since the
+ password was last changed.
+
+ usri11_home_dir points to a null terminated ASCII string that contains
+ the path name of the user's home directory.
+
+ usri11_parms points to a null terminated ASCII string that is set
+ aside for use by applications.
+
+ usri11_last_logon specifies the time when the user last logged on.
+ This value is stored as the number of seconds elapsed since
+ 00:00:00, January 1, 1970.
+
+ usri11_last_logoff specifies the time when the user last logged off.
+ This value is stored as the number of seconds elapsed since
+ 00:00:00, January 1, 1970. A value of 0 means the last logoff
+ time is unknown.
+
+ usri11_bad_pw_count specifies the number of incorrect passwords
+ entered since the last successful logon.
+
+ usri11_log1_num_logons specifies the number of times this user has
+ logged on. A value of -1 means the number of logons is unknown.
+
+ usri11_logon_server points to a null terminated ASCII string that
+ contains the name of the server to which logon requests are sent.
+ A null string indicates logon requests should be sent to the
+ domain controller.
+
+ usri11_country_code specifies the country code for the user's language
+ of choice.
+
+ usri11_workstations points to a null terminated ASCII string that
+ contains the names of workstations the user may log on from.
+ There may be up to 8 workstations, with the names separated by
+ commas. A null strings indicates there are no restrictions.
+
+ usri11_max_storage specifies the maximum amount of disk space the user
+ can occupy. A value of 0xffffffff indicates there are no
+ restrictions.
+
+ usri11_units_per_week specifies the equal number of time units into
+ which a week is divided. This value must be equal to 168.
+
+ usri11_logon_hours points to a 21 byte (168 bits) string that
+ specifies the time during which the user can log on. Each bit
+ represents one unique hour in a week. The first bit (bit 0, word
+ 0) is Sunday, 0:00 to 0:59, the second bit (bit 1, word 0) is
+
+
+
+Leach, Naik [Page 29]
+
+
+
+INTERNET-DRAFT CIFS Remote Admin Protocol January 10, 1997
+
+
+ Sunday, 1:00 to 1:59 and so on. A null pointer indicates there
+ are no restrictions.
+
+ usri11_code_page specifies the code page for the user's language of
+ choice
+
+All of the pointers in this data structure need to be treated
+specially. The pointer is a 32 bit pointer. The higher 16 bits need
+to be ignored. The converter word returned in the parameters section
+needs to be subtracted from the lower 16 bits to calculate an offset
+into the return buffer where this ASCII string resides.
+
+There is no auxiliary data in the response.
+
+ ****************************************************************************/
+
+#define usri11_name 0
+#define usri11_pad 21
+#define usri11_comment 22
+#define usri11_usr_comment 26
+#define usri11_full_name 30
+#define usri11_priv 34
+#define usri11_auth_flags 36
+#define usri11_password_age 40
+#define usri11_homedir 44
+#define usri11_parms 48
+#define usri11_last_logon 52
+#define usri11_last_logoff 56
+#define usri11_bad_pw_count 60
+#define usri11_num_logons 62
+#define usri11_logon_server 64
+#define usri11_country_code 68
+#define usri11_workstations 70
+#define usri11_max_storage 74
+#define usri11_units_per_week 78
+#define usri11_logon_hours 80
+#define usri11_code_page 84
+#define usri11_end 86
+
+static bool api_RNetUserGetInfo(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *UserName = skip_string(param,tpscnt,str2);
+ char *p = skip_string(param,tpscnt,UserName);
+ int uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ char *p2;
+ char *endp;
+ const char *level_string;
+
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ NTSTATUS status, result;
+ struct rpc_pipe_client *cli = NULL;
+ struct policy_handle connect_handle, domain_handle, user_handle;
+ struct lsa_String domain_name;
+ struct dom_sid2 *domain_sid;
+ struct lsa_String names;
+ struct samr_Ids rids;
+ struct samr_Ids types;
+ int errcode = W_ERROR_V(WERR_NERR_USERNOTFOUND);
+ uint32_t rid;
+ union samr_UserInfo *info;
+ struct dcerpc_binding_handle *b = NULL;
+
+ if (!str1 || !str2 || !UserName || !p) {
+ return False;
+ }
+
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ DEBUG(4,("RNetUserGetInfo level=%d\n", uLevel));
+
+ /* check it's a supported variant */
+ if (strcmp(str1,"zWrLh") != 0) {
+ return False;
+ }
+ switch( uLevel ) {
+ case 0: level_string = "B21"; break;
+ case 1: level_string = "B21BB16DWzzWz"; break;
+ case 2: level_string = "B21BB16DWzzWzDzzzzDDDDWb21WWzWW"; break;
+ case 10: level_string = "B21Bzzz"; break;
+ case 11: level_string = "B21BzzzWDDzzDDWWzWzDWb21W"; break;
+ default: return False;
+ }
+
+ if (strcmp(level_string,str2) != 0) {
+ return False;
+ }
+
+ *rdata_len = mdrcnt + 1024;
+ *rdata = smb_realloc_limit(*rdata,*rdata_len);
+ if (!*rdata) {
+ return False;
+ }
+
+ p = *rdata;
+ endp = *rdata + *rdata_len;
+ p2 = get_safe_ptr(*rdata,*rdata_len,p,usri11_end);
+ if (!p2) {
+ return False;
+ }
+
+ ZERO_STRUCT(connect_handle);
+ ZERO_STRUCT(domain_handle);
+ ZERO_STRUCT(user_handle);
+
+ status = rpc_pipe_open_interface(mem_ctx, &ndr_table_samr,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("api_RNetUserGetInfo: could not connect to samr: %s\n",
+ nt_errstr(status)));
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+
+ b = cli->binding_handle;
+
+ status = dcerpc_samr_Connect2(b, mem_ctx,
+ lp_netbios_name(),
+ SAMR_ACCESS_CONNECT_TO_SERVER |
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ &connect_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(result));
+ goto out;
+ }
+
+ init_lsa_String(&domain_name, get_global_sam_name());
+
+ status = dcerpc_samr_LookupDomain(b, mem_ctx,
+ &connect_handle,
+ &domain_name,
+ &domain_sid,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(result));
+ goto out;
+ }
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_handle,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ domain_sid,
+ &domain_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(result));
+ goto out;
+ }
+
+ init_lsa_String(&names, UserName);
+
+ status = dcerpc_samr_LookupNames(b, mem_ctx,
+ &domain_handle,
+ 1,
+ &names,
+ &rids,
+ &types,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(result));
+ goto out;
+ }
+
+ if (rids.count != 1) {
+ errcode = W_ERROR_V(WERR_NO_SUCH_USER);
+ goto out;
+ }
+ if (rids.count != types.count) {
+ errcode = W_ERROR_V(WERR_INVALID_PARAMETER);
+ goto out;
+ }
+ if (types.ids[0] != SID_NAME_USER) {
+ errcode = W_ERROR_V(WERR_INVALID_PARAMETER);
+ goto out;
+ }
+
+ rid = rids.ids[0];
+
+ status = dcerpc_samr_OpenUser(b, mem_ctx,
+ &domain_handle,
+ SAMR_USER_ACCESS_GET_LOCALE |
+ SAMR_USER_ACCESS_GET_LOGONINFO |
+ SAMR_USER_ACCESS_GET_ATTRIBUTES |
+ SAMR_USER_ACCESS_GET_GROUPS |
+ SAMR_USER_ACCESS_GET_GROUP_MEMBERSHIP |
+ SEC_STD_READ_CONTROL,
+ rid,
+ &user_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(result));
+ goto out;
+ }
+
+ status = dcerpc_samr_QueryUserInfo2(b, mem_ctx,
+ &user_handle,
+ UserAllInformation,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(result));
+ goto out;
+ }
+
+ memset(p,0,21);
+ fstrcpy(p+usri11_name,UserName); /* 21 bytes - user name */
+
+ if (uLevel > 0) {
+ SCVAL(p,usri11_pad,0); /* padding - 1 byte */
+ *p2 = 0;
+ }
+
+ if (uLevel >= 10) {
+ SIVAL(p,usri11_comment,PTR_DIFF(p2,p)); /* comment */
+ strlcpy(p2,"Comment",PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+
+ SIVAL(p,usri11_usr_comment,PTR_DIFF(p2,p)); /* user_comment */
+ strlcpy(p2,"UserComment",PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+
+ /* EEK! the cifsrap.txt doesn't have this in!!!! */
+ SIVAL(p,usri11_full_name,PTR_DIFF(p2,p)); /* full name */
+ strlcpy(p2,info->info21.full_name.string,PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ }
+
+ if (uLevel == 11) {
+ const char *homedir = info->info21.home_directory.string;
+ /* modelled after NTAS 3.51 reply */
+ SSVAL(p,usri11_priv,
+ (get_current_uid(conn) == sec_initial_uid())?
+ USER_PRIV_ADMIN:USER_PRIV_USER);
+ SIVAL(p,usri11_auth_flags,AF_OP_PRINT); /* auth flags */
+ SIVALS(p,usri11_password_age,-1); /* password age */
+ SIVAL(p,usri11_homedir,PTR_DIFF(p2,p)); /* home dir */
+ strlcpy(p2, homedir, PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ SIVAL(p,usri11_parms,PTR_DIFF(p2,p)); /* parms */
+ strlcpy(p2,"",PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ SIVAL(p,usri11_last_logon,0); /* last logon */
+ SIVAL(p,usri11_last_logoff,0); /* last logoff */
+ SSVALS(p,usri11_bad_pw_count,-1); /* bad pw counts */
+ SSVALS(p,usri11_num_logons,-1); /* num logons */
+ SIVAL(p,usri11_logon_server,PTR_DIFF(p2,p)); /* logon server */
+ strlcpy(p2,"\\\\*",PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ SSVAL(p,usri11_country_code,0); /* country code */
+
+ SIVAL(p,usri11_workstations,PTR_DIFF(p2,p)); /* workstations */
+ strlcpy(p2,"",PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+
+ SIVALS(p,usri11_max_storage,-1); /* max storage */
+ SSVAL(p,usri11_units_per_week,168); /* units per week */
+ SIVAL(p,usri11_logon_hours,PTR_DIFF(p2,p)); /* logon hours */
+
+ /* a simple way to get logon hours at all times. */
+ memset(p2,0xff,21);
+ SCVAL(p2,21,0); /* fix zero termination */
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+
+ SSVAL(p,usri11_code_page,0); /* code page */
+ }
+
+ if (uLevel == 1 || uLevel == 2) {
+ memset(p+22,' ',16); /* password */
+ SIVALS(p,38,-1); /* password age */
+ SSVAL(p,42,
+ (get_current_uid(conn) == sec_initial_uid())?
+ USER_PRIV_ADMIN:USER_PRIV_USER);
+ SIVAL(p,44,PTR_DIFF(p2,*rdata)); /* home dir */
+ strlcpy(p2, info->info21.home_directory.string,
+ PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ SIVAL(p,48,PTR_DIFF(p2,*rdata)); /* comment */
+ *p2++ = 0;
+ SSVAL(p,52,0); /* flags */
+ SIVAL(p,54,PTR_DIFF(p2,*rdata)); /* script_path */
+ strlcpy(p2, info->info21.logon_script.string,
+ PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ if (uLevel == 2) {
+ SIVAL(p,58,0); /* auth_flags */
+ SIVAL(p,62,PTR_DIFF(p2,*rdata)); /* full_name */
+ strlcpy(p2,info->info21.full_name.string,PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ SIVAL(p,66,0); /* urs_comment */
+ SIVAL(p,70,PTR_DIFF(p2,*rdata)); /* parms */
+ strlcpy(p2,"",PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ SIVAL(p,74,0); /* workstations */
+ SIVAL(p,78,0); /* last_logon */
+ SIVAL(p,82,0); /* last_logoff */
+ SIVALS(p,86,-1); /* acct_expires */
+ SIVALS(p,90,-1); /* max_storage */
+ SSVAL(p,94,168); /* units_per_week */
+ SIVAL(p,96,PTR_DIFF(p2,*rdata)); /* logon_hours */
+ memset(p2,-1,21);
+ p2 += 21;
+ SSVALS(p,100,-1); /* bad_pw_count */
+ SSVALS(p,102,-1); /* num_logons */
+ SIVAL(p,104,PTR_DIFF(p2,*rdata)); /* logon_server */
+ {
+ TALLOC_CTX *ctx = talloc_tos();
+ int space_rem = *rdata_len - (p2 - *rdata);
+ char *tmp;
+
+ if (space_rem <= 0) {
+ return false;
+ }
+ tmp = talloc_strdup(ctx, "\\\\%L");
+ if (!tmp) {
+ return false;
+ }
+ tmp = talloc_sub_basic(ctx,
+ "",
+ "",
+ tmp);
+ if (!tmp) {
+ return false;
+ }
+
+ push_ascii(p2,
+ tmp,
+ space_rem,
+ STR_TERMINATE);
+ }
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ SSVAL(p,108,49); /* country_code */
+ SSVAL(p,110,860); /* code page */
+ }
+ }
+
+ errcode = NERR_Success;
+
+ out:
+ *rdata_len = PTR_DIFF(p2,*rdata);
+
+ if (b && is_valid_policy_hnd(&user_handle)) {
+ dcerpc_samr_Close(b, mem_ctx, &user_handle, &result);
+ }
+ if (b && is_valid_policy_hnd(&domain_handle)) {
+ dcerpc_samr_Close(b, mem_ctx, &domain_handle, &result);
+ }
+ if (b && is_valid_policy_hnd(&connect_handle)) {
+ dcerpc_samr_Close(b, mem_ctx, &connect_handle, &result);
+ }
+
+ SSVAL(*rparam,0,errcode);
+ SSVAL(*rparam,2,0); /* converter word */
+ SSVAL(*rparam,4,*rdata_len); /* is this right?? */
+
+ return(True);
+}
+
+static bool api_WWkstaUserLogon(struct smbd_server_connection *sconn,
+ connection_struct *conn,uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel;
+ struct pack_desc desc;
+ char* name;
+ struct auth_session_info *si = NULL;
+ NTSTATUS status;
+
+ status = smbXsrv_session_info_lookup(conn->sconn->client,
+ vuid,
+ &si);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ DBG_INFO("Username of UID %ju is %s\n",
+ (uintmax_t)si->unix_token->uid,
+ si->unix_info->unix_name);
+
+ uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ name = get_safe_str_ptr(param,tpscnt,p,2);
+ if (!name) {
+ return False;
+ }
+
+ memset((char *)&desc,'\0',sizeof(desc));
+
+ DEBUG(3,("WWkstaUserLogon uLevel=%d name=%s\n",uLevel,name));
+
+ /* check it's a supported variant */
+ if (strcmp(str1,"OOWb54WrLh") != 0) {
+ return False;
+ }
+ if (uLevel != 1 || strcmp(str2,"WB21BWDWWDDDDDDDzzzD") != 0) {
+ return False;
+ }
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ }
+
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+ desc.subformat = NULL;
+ desc.format = str2;
+
+ if (init_package(&desc,1,0)) {
+ PACKI(&desc,"W",0); /* code */
+ PACKS(&desc,"B21",name); /* eff. name */
+ PACKS(&desc,"B",""); /* pad */
+ PACKI(&desc,"W",
+ (get_current_uid(conn) == sec_initial_uid())?
+ USER_PRIV_ADMIN:USER_PRIV_USER);
+ PACKI(&desc,"D",0); /* auth flags XXX */
+ PACKI(&desc,"W",0); /* num logons */
+ PACKI(&desc,"W",0); /* bad pw count */
+ PACKI(&desc,"D",0); /* last logon */
+ PACKI(&desc,"D",-1); /* last logoff */
+ PACKI(&desc,"D",-1); /* logoff time */
+ PACKI(&desc,"D",-1); /* kickoff time */
+ PACKI(&desc,"D",0); /* password age */
+ PACKI(&desc,"D",0); /* password can change */
+ PACKI(&desc,"D",-1); /* password must change */
+
+ {
+ fstring mypath;
+ fstrcpy(mypath,"\\\\");
+ fstrcat(mypath,get_local_machine_name());
+ if (!strupper_m(mypath)) {
+ return false;
+ }
+ PACKS(&desc,"z",mypath); /* computer */
+ }
+
+ PACKS(&desc,"z",lp_workgroup());/* domain */
+ PACKS(&desc,"z", si->info->logon_script); /* script path */
+ PACKI(&desc,"D",0x00000000); /* reserved */
+ }
+
+ *rdata_len = desc.usedlen;
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,desc.neededlen);
+
+ DEBUG(4,("WWkstaUserLogon: errorcode %d\n",desc.errcode));
+
+ return True;
+}
+
+/****************************************************************************
+ api_WAccessGetUserPerms
+****************************************************************************/
+
+static bool api_WAccessGetUserPerms(struct smbd_server_connection *sconn,
+ connection_struct *conn,uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *user = skip_string(param,tpscnt,str2);
+ char *resource = skip_string(param,tpscnt,user);
+
+ if (!str1 || !str2 || !user || !resource) {
+ return False;
+ }
+
+ if (skip_string(param,tpscnt,resource) == NULL) {
+ return False;
+ }
+ DEBUG(3,("WAccessGetUserPerms user=%s resource=%s\n",user,resource));
+
+ /* check it's a supported variant */
+ if (strcmp(str1,"zzh") != 0) {
+ return False;
+ }
+ if (strcmp(str2,"") != 0) {
+ return False;
+ }
+
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,0); /* errorcode */
+ SSVAL(*rparam,2,0); /* converter word */
+ SSVAL(*rparam,4,0x7f); /* permission flags */
+
+ return True;
+}
+
+/****************************************************************************
+ api_WPrintJobEnumerate
+ ****************************************************************************/
+
+static bool api_WPrintJobGetInfo(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel;
+ fstring sharename;
+ uint32_t jobid;
+ struct pack_desc desc;
+ char *tmpdata=NULL;
+
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ WERROR werr;
+ NTSTATUS status;
+ struct rpc_pipe_client *cli = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+ struct policy_handle handle;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ union spoolss_JobInfo info;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ uLevel = get_safe_SVAL(param,tpscnt,p,2,-1);
+
+ memset((char *)&desc,'\0',sizeof(desc));
+ memset((char *)&status,'\0',sizeof(status));
+
+ DEBUG(3,("WPrintJobGetInfo uLevel=%d uJobId=0x%X\n",uLevel,SVAL(p,0)));
+
+ /* check it's a supported variant */
+ if (strcmp(str1,"WWrLh") != 0) {
+ return False;
+ }
+ if (!check_printjob_info(&desc,uLevel,str2)) {
+ return False;
+ }
+
+ if(!rap_to_pjobid(SVAL(p,0), sharename, &jobid)) {
+ return False;
+ }
+
+ ZERO_STRUCT(handle);
+
+ status = rpc_pipe_open_interface(mem_ctx,
+ &ndr_table_spoolss,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("api_WPrintJobGetInfo: could not connect to spoolss: %s\n",
+ nt_errstr(status)));
+ desc.errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ b = cli->binding_handle;
+
+ ZERO_STRUCT(devmode_ctr);
+
+ status = dcerpc_spoolss_OpenPrinter(b, mem_ctx,
+ sharename,
+ "RAW",
+ devmode_ctr,
+ PRINTER_ACCESS_USE,
+ &handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ desc.errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ desc.errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ werr = rpccli_spoolss_getjob(cli, mem_ctx,
+ &handle,
+ jobid,
+ 2, /* level */
+ 0, /* offered */
+ &info);
+ if (!W_ERROR_IS_OK(werr)) {
+ desc.errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+ } else {
+ /*
+ * Don't return data but need to get correct length
+ * init_package will return wrong size if buflen=0
+ */
+ desc.buflen = getlen(desc.format);
+ desc.base = tmpdata = (char *)SMB_MALLOC( desc.buflen );
+ }
+
+ if (init_package(&desc,1,0)) {
+ fill_spoolss_printjob_info(uLevel, &desc, &info.info2, info.info2.position);
+ *rdata_len = desc.usedlen;
+ } else {
+ desc.errcode = NERR_JobNotFound;
+ *rdata_len = 0;
+ }
+ out:
+ if (b && is_valid_policy_hnd(&handle)) {
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &handle, &werr);
+ }
+
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,desc.neededlen);
+
+ SAFE_FREE(tmpdata);
+
+ DEBUG(4,("WPrintJobGetInfo: errorcode %d\n",desc.errcode));
+
+ return True;
+}
+
+static bool api_WPrintJobEnumerate(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ char *name = p;
+ int uLevel;
+ int i, succnt=0;
+ struct pack_desc desc;
+
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ WERROR werr;
+ NTSTATUS status;
+ struct rpc_pipe_client *cli = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+ struct policy_handle handle;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ uint32_t count = 0;
+ union spoolss_JobInfo *info;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ memset((char *)&desc,'\0',sizeof(desc));
+
+ p = skip_string(param,tpscnt,p);
+ if (!p) {
+ return False;
+ }
+ uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+
+ DEBUG(3,("WPrintJobEnumerate uLevel=%d name=%s\n",uLevel,name));
+
+ /* check it's a supported variant */
+ if (strcmp(str1,"zWrLeh") != 0) {
+ return False;
+ }
+
+ if (uLevel > 2) {
+ return False; /* defined only for uLevel 0,1,2 */
+ }
+
+ if (!check_printjob_info(&desc,uLevel,str2)) {
+ return False;
+ }
+
+ ZERO_STRUCT(handle);
+
+ status = rpc_pipe_open_interface(mem_ctx,
+ &ndr_table_spoolss,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("api_WPrintJobEnumerate: could not connect to spoolss: %s\n",
+ nt_errstr(status)));
+ desc.errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ b = cli->binding_handle;
+
+ ZERO_STRUCT(devmode_ctr);
+
+ status = dcerpc_spoolss_OpenPrinter(b, mem_ctx,
+ name,
+ NULL,
+ devmode_ctr,
+ PRINTER_ACCESS_USE,
+ &handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ desc.errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ desc.errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ werr = rpccli_spoolss_enumjobs(cli, mem_ctx,
+ &handle,
+ 0, /* firstjob */
+ 0xff, /* numjobs */
+ 2, /* level */
+ 0, /* offered */
+ &count,
+ &info);
+ if (!W_ERROR_IS_OK(werr)) {
+ desc.errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ }
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+
+ if (init_package(&desc,count,0)) {
+ succnt = 0;
+ for (i = 0; i < count; i++) {
+ fill_spoolss_printjob_info(uLevel, &desc, &info[i].info2, i);
+ if (desc.errcode == NERR_Success) {
+ succnt = i+1;
+ }
+ }
+ }
+ out:
+ if (b && is_valid_policy_hnd(&handle)) {
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &handle, &werr);
+ }
+
+ *rdata_len = desc.usedlen;
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,succnt);
+ SSVAL(*rparam,6,count);
+
+ DEBUG(4,("WPrintJobEnumerate: errorcode %d\n",desc.errcode));
+
+ return True;
+}
+
+static int check_printdest_info(struct pack_desc* desc,
+ int uLevel, char* id)
+{
+ desc->subformat = NULL;
+ switch( uLevel ) {
+ case 0:
+ desc->format = "B9";
+ break;
+ case 1:
+ desc->format = "B9B21WWzW";
+ break;
+ case 2:
+ desc->format = "z";
+ break;
+ case 3:
+ desc->format = "zzzWWzzzWW";
+ break;
+ default:
+ DEBUG(0,("check_printdest_info: invalid level %d\n",
+ uLevel));
+ return False;
+ }
+ if (id == NULL || strcmp(desc->format,id) != 0) {
+ DEBUG(0,("check_printdest_info: invalid string %s\n",
+ id ? id : "<NULL>" ));
+ return False;
+ }
+ return True;
+}
+
+static void fill_printdest_info(struct spoolss_PrinterInfo2 *info2, int uLevel,
+ struct pack_desc* desc)
+{
+ char buf[100];
+
+ strncpy(buf, info2->printername, sizeof(buf)-1);
+ buf[sizeof(buf)-1] = 0;
+ (void)strupper_m(buf);
+
+ if (uLevel <= 1) {
+ PACKS(desc,"B9",buf); /* szName */
+ if (uLevel == 1) {
+ PACKS(desc,"B21",""); /* szUserName */
+ PACKI(desc,"W",0); /* uJobId */
+ PACKI(desc,"W",0); /* fsStatus */
+ PACKS(desc,"z",""); /* pszStatus */
+ PACKI(desc,"W",0); /* time */
+ }
+ }
+
+ if (uLevel == 2 || uLevel == 3) {
+ PACKS(desc,"z",buf); /* pszPrinterName */
+ if (uLevel == 3) {
+ PACKS(desc,"z",""); /* pszUserName */
+ PACKS(desc,"z",""); /* pszLogAddr */
+ PACKI(desc,"W",0); /* uJobId */
+ PACKI(desc,"W",0); /* fsStatus */
+ PACKS(desc,"z",""); /* pszStatus */
+ PACKS(desc,"z",""); /* pszComment */
+ PACKS(desc,"z","NULL"); /* pszDrivers */
+ PACKI(desc,"W",0); /* time */
+ PACKI(desc,"W",0); /* pad1 */
+ }
+ }
+}
+
+static bool api_WPrintDestGetInfo(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ char* PrinterName = p;
+ int uLevel;
+ struct pack_desc desc;
+ char *tmpdata=NULL;
+
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ WERROR werr;
+ NTSTATUS status;
+ struct rpc_pipe_client *cli = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+ struct policy_handle handle;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ union spoolss_PrinterInfo info;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ memset((char *)&desc,'\0',sizeof(desc));
+
+ p = skip_string(param,tpscnt,p);
+ if (!p) {
+ return False;
+ }
+ uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+
+ DEBUG(3,("WPrintDestGetInfo uLevel=%d PrinterName=%s\n",uLevel,PrinterName));
+
+ /* check it's a supported variant */
+ if (strcmp(str1,"zWrLh") != 0) {
+ return False;
+ }
+ if (!check_printdest_info(&desc,uLevel,str2)) {
+ return False;
+ }
+
+ ZERO_STRUCT(handle);
+
+ status = rpc_pipe_open_interface(mem_ctx,
+ &ndr_table_spoolss,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("api_WPrintDestGetInfo: could not connect to spoolss: %s\n",
+ nt_errstr(status)));
+ desc.errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ b = cli->binding_handle;
+
+ ZERO_STRUCT(devmode_ctr);
+
+ status = dcerpc_spoolss_OpenPrinter(b, mem_ctx,
+ PrinterName,
+ NULL,
+ devmode_ctr,
+ PRINTER_ACCESS_USE,
+ &handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ *rdata_len = 0;
+ desc.errcode = NERR_DestNotFound;
+ desc.neededlen = 0;
+ goto out;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ *rdata_len = 0;
+ desc.errcode = NERR_DestNotFound;
+ desc.neededlen = 0;
+ goto out;
+ }
+
+ werr = rpccli_spoolss_getprinter(cli, mem_ctx,
+ &handle,
+ 2,
+ 0,
+ &info);
+ if (!W_ERROR_IS_OK(werr)) {
+ *rdata_len = 0;
+ desc.errcode = NERR_DestNotFound;
+ desc.neededlen = 0;
+ goto out;
+ }
+
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+ } else {
+ /*
+ * Don't return data but need to get correct length
+ * init_package will return wrong size if buflen=0
+ */
+ desc.buflen = getlen(desc.format);
+ desc.base = tmpdata = (char *)SMB_MALLOC( desc.buflen );
+ }
+ if (init_package(&desc,1,0)) {
+ fill_printdest_info(&info.info2, uLevel,&desc);
+ }
+
+ out:
+ if (b && is_valid_policy_hnd(&handle)) {
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &handle, &werr);
+ }
+
+ *rdata_len = desc.usedlen;
+
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,desc.neededlen);
+
+ DEBUG(4,("WPrintDestGetInfo: errorcode %d\n",desc.errcode));
+ SAFE_FREE(tmpdata);
+
+ return True;
+}
+
+static bool api_WPrintDestEnum(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel;
+ int queuecnt;
+ int i, n, succnt=0;
+ struct pack_desc desc;
+
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ WERROR werr;
+ NTSTATUS status;
+ struct rpc_pipe_client *cli = NULL;
+ union spoolss_PrinterInfo *info;
+ uint32_t count;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ memset((char *)&desc,'\0',sizeof(desc));
+
+ uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+
+ DEBUG(3,("WPrintDestEnum uLevel=%d\n",uLevel));
+
+ /* check it's a supported variant */
+ if (strcmp(str1,"WrLeh") != 0) {
+ return False;
+ }
+ if (!check_printdest_info(&desc,uLevel,str2)) {
+ return False;
+ }
+
+ queuecnt = 0;
+
+ status = rpc_pipe_open_interface(mem_ctx,
+ &ndr_table_spoolss,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("api_WPrintDestEnum: could not connect to spoolss: %s\n",
+ nt_errstr(status)));
+ desc.errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+
+ werr = rpccli_spoolss_enumprinters(cli, mem_ctx,
+ PRINTER_ENUM_LOCAL,
+ cli->srv_name_slash,
+ 2,
+ 0,
+ &count,
+ &info);
+ if (!W_ERROR_IS_OK(werr)) {
+ desc.errcode = W_ERROR_V(werr);
+ *rdata_len = 0;
+ desc.errcode = NERR_DestNotFound;
+ desc.neededlen = 0;
+ goto out;
+ }
+
+ queuecnt = count;
+
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ }
+
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+ if (init_package(&desc,queuecnt,0)) {
+ succnt = 0;
+ n = 0;
+ for (i = 0; i < count; i++) {
+ fill_printdest_info(&info[i].info2, uLevel,&desc);
+ n++;
+ if (desc.errcode == NERR_Success) {
+ succnt = n;
+ }
+ }
+ }
+ out:
+ *rdata_len = desc.usedlen;
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,succnt);
+ SSVAL(*rparam,6,queuecnt);
+
+ DEBUG(4,("WPrintDestEnumerate: errorcode %d\n",desc.errcode));
+
+ return True;
+}
+
+static bool api_WPrintDriverEnum(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel;
+ int succnt;
+ struct pack_desc desc;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ memset((char *)&desc,'\0',sizeof(desc));
+
+ uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+
+ DEBUG(3,("WPrintDriverEnum uLevel=%d\n",uLevel));
+
+ /* check it's a supported variant */
+ if (strcmp(str1,"WrLeh") != 0) {
+ return False;
+ }
+ if (uLevel != 0 || strcmp(str2,"B41") != 0) {
+ return False;
+ }
+
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ }
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+ if (init_package(&desc,1,0)) {
+ PACKS(&desc,"B41","NULL");
+ }
+
+ succnt = (desc.errcode == NERR_Success ? 1 : 0);
+
+ *rdata_len = desc.usedlen;
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,succnt);
+ SSVAL(*rparam,6,1);
+
+ DEBUG(4,("WPrintDriverEnum: errorcode %d\n",desc.errcode));
+
+ return True;
+}
+
+static bool api_WPrintQProcEnum(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel;
+ int succnt;
+ struct pack_desc desc;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+ memset((char *)&desc,'\0',sizeof(desc));
+
+ uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+
+ DEBUG(3,("WPrintQProcEnum uLevel=%d\n",uLevel));
+
+ /* check it's a supported variant */
+ if (strcmp(str1,"WrLeh") != 0) {
+ return False;
+ }
+ if (uLevel != 0 || strcmp(str2,"B13") != 0) {
+ return False;
+ }
+
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ }
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+ desc.format = str2;
+ if (init_package(&desc,1,0)) {
+ PACKS(&desc,"B13","lpd");
+ }
+
+ succnt = (desc.errcode == NERR_Success ? 1 : 0);
+
+ *rdata_len = desc.usedlen;
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,succnt);
+ SSVAL(*rparam,6,1);
+
+ DEBUG(4,("WPrintQProcEnum: errorcode %d\n",desc.errcode));
+
+ return True;
+}
+
+static bool api_WPrintPortEnum(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel;
+ int succnt;
+ struct pack_desc desc;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ memset((char *)&desc,'\0',sizeof(desc));
+
+ uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+
+ DEBUG(3,("WPrintPortEnum uLevel=%d\n",uLevel));
+
+ /* check it's a supported variant */
+ if (strcmp(str1,"WrLeh") != 0) {
+ return False;
+ }
+ if (uLevel != 0 || strcmp(str2,"B9") != 0) {
+ return False;
+ }
+
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ }
+ memset((char *)&desc,'\0',sizeof(desc));
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+ desc.format = str2;
+ if (init_package(&desc,1,0)) {
+ PACKS(&desc,"B13","lp0");
+ }
+
+ succnt = (desc.errcode == NERR_Success ? 1 : 0);
+
+ *rdata_len = desc.usedlen;
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,succnt);
+ SSVAL(*rparam,6,1);
+
+ DEBUG(4,("WPrintPortEnum: errorcode %d\n",desc.errcode));
+
+ return True;
+}
+
+/****************************************************************************
+ List open sessions
+ ****************************************************************************/
+
+static bool api_RNetSessionEnum(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel;
+ struct pack_desc desc;
+ int i;
+
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ WERROR werr;
+ NTSTATUS status;
+ struct rpc_pipe_client *cli = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+ struct srvsvc_NetSessInfoCtr info_ctr;
+ uint32_t totalentries, resume_handle = 0;
+ uint32_t count = 0;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ ZERO_STRUCT(desc);
+
+ uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+
+ DEBUG(3,("RNetSessionEnum uLevel=%d\n",uLevel));
+ DEBUG(7,("RNetSessionEnum req string=%s\n",str1));
+ DEBUG(7,("RNetSessionEnum ret string=%s\n",str2));
+
+ /* check it's a supported variant */
+ if (strcmp(str1,RAP_NetSessionEnum_REQ) != 0) {
+ return False;
+ }
+ if (uLevel != 2 || strcmp(str2,RAP_SESSION_INFO_L2) != 0) {
+ return False;
+ }
+
+ status = rpc_pipe_open_interface(mem_ctx,
+ &ndr_table_srvsvc,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("RNetSessionEnum: could not connect to srvsvc: %s\n",
+ nt_errstr(status)));
+ desc.errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ b = cli->binding_handle;
+
+ info_ctr.level = 1;
+ info_ctr.ctr.ctr1 = talloc_zero(talloc_tos(), struct srvsvc_NetSessCtr1);
+ if (info_ctr.ctr.ctr1 == NULL) {
+ desc.errcode = W_ERROR_V(WERR_NOT_ENOUGH_MEMORY);
+ goto out;
+ }
+
+ status = dcerpc_srvsvc_NetSessEnum(b, mem_ctx,
+ cli->srv_name_slash,
+ NULL, /* client */
+ NULL, /* user */
+ &info_ctr,
+ (uint32_t)-1, /* max_buffer */
+ &totalentries,
+ &resume_handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("RNetSessionEnum: dcerpc_srvsvc_NetSessEnum failed: %s\n",
+ nt_errstr(status)));
+ desc.errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(0,("RNetSessionEnum: dcerpc_srvsvc_NetSessEnum failed: %s\n",
+ win_errstr(werr)));
+ desc.errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ count = info_ctr.ctr.ctr1->count;
+
+ out:
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ }
+
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+ desc.format = str2;
+ if (!init_package(&desc, count,0)) {
+ return False;
+ }
+
+ for(i=0; i < count; i++) {
+ PACKS(&desc, "z", info_ctr.ctr.ctr1->array[i].client);
+ PACKS(&desc, "z", info_ctr.ctr.ctr1->array[i].user);
+ PACKI(&desc, "W", 1); /* num conns */
+ PACKI(&desc, "W", info_ctr.ctr.ctr1->array[i].num_open);
+ PACKI(&desc, "W", 1); /* num users */
+ PACKI(&desc, "D", 0); /* session time */
+ PACKI(&desc, "D", 0); /* idle time */
+ PACKI(&desc, "D", 0); /* flags */
+ PACKS(&desc, "z", "Unknown Client"); /* client type string */
+ }
+
+ *rdata_len = desc.usedlen;
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0); /* converter */
+ SSVAL(*rparam,4, count); /* count */
+
+ DEBUG(4,("RNetSessionEnum: errorcode %d\n",desc.errcode));
+
+ return True;
+}
+
+
+/****************************************************************************
+ The buffer was too small.
+ ****************************************************************************/
+
+static bool api_TooSmall(struct smbd_server_connection *sconn,
+ connection_struct *conn,uint64_t vuid, char *param, char *data,
+ int mdrcnt, int mprcnt,
+ char **rdata, char **rparam,
+ int *rdata_len, int *rparam_len)
+{
+ *rparam_len = MIN(*rparam_len,mprcnt);
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ *rdata_len = 0;
+
+ SSVAL(*rparam,0,NERR_BufTooSmall);
+
+ DEBUG(3,("Supplied buffer too small in API command\n"));
+
+ return True;
+}
+
+/****************************************************************************
+ The request is not supported.
+ ****************************************************************************/
+
+static bool api_Unsupported(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt, int mprcnt,
+ char **rdata, char **rparam,
+ int *rdata_len, int *rparam_len)
+{
+ *rparam_len = 4;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ *rdata_len = 0;
+
+ SSVAL(*rparam,0,NERR_notsupported);
+ SSVAL(*rparam,2,0); /* converter word */
+
+ DEBUG(3,("Unsupported API command\n"));
+
+ return True;
+}
+
+static const struct {
+ const char *name;
+ int id;
+ bool (*fn)(struct smbd_server_connection *sconn,
+ connection_struct *, uint64_t,
+ char *, int,
+ char *, int,
+ int,int,char **,char **,int *,int *);
+ bool auth_user; /* Deny anonymous access? */
+} api_commands[] = {
+ {
+ .name = "RNetShareEnum",
+ .id = RAP_WshareEnum,
+ .fn = api_RNetShareEnum,
+ .auth_user = true,
+ },
+ {
+ .name = "RNetShareGetInfo",
+ .id = RAP_WshareGetInfo,
+ .fn = api_RNetShareGetInfo
+ },
+ {
+ .name = "RNetShareAdd",
+ .id = RAP_WshareAdd,
+ .fn = api_RNetShareAdd
+ },
+ {
+ .name = "RNetSessionEnum",
+ .id = RAP_WsessionEnum,
+ .fn = api_RNetSessionEnum,
+ .auth_user = true,
+ },
+ {
+ .name = "RNetServerGetInfo",
+ .id = RAP_WserverGetInfo,
+ .fn = api_RNetServerGetInfo
+ },
+ {
+ .name = "RNetGroupEnum",
+ .id = RAP_WGroupEnum,
+ .fn = api_RNetGroupEnum, True
+ },
+ {
+ .name = "RNetGroupGetUsers",
+ .id = RAP_WGroupGetUsers,
+ .fn = api_RNetGroupGetUsers,
+ .auth_user = true},
+ {
+ .name = "RNetUserEnum",
+ .id = RAP_WUserEnum,
+ .fn = api_RNetUserEnum,
+ .auth_user = true,
+ },
+ {
+ .name = "RNetUserGetInfo",
+ .id = RAP_WUserGetInfo,
+ .fn = api_RNetUserGetInfo
+ },
+ {
+ .name = "NetUserGetGroups",
+ .id = RAP_WUserGetGroups,
+ .fn = api_NetUserGetGroups
+ },
+ {
+ .name = "NetWkstaGetInfo",
+ .id = RAP_WWkstaGetInfo,
+ .fn = api_NetWkstaGetInfo
+ },
+ {
+ .name = "DosPrintQEnum",
+ .id = RAP_WPrintQEnum,
+ .fn = api_DosPrintQEnum,
+ .auth_user = true,
+ },
+ {
+ .name = "DosPrintQGetInfo",
+ .id = RAP_WPrintQGetInfo,
+ .fn = api_DosPrintQGetInfo
+ },
+ {
+ .name = "WPrintQueuePause",
+ .id = RAP_WPrintQPause,
+ .fn = api_WPrintQueueCtrl
+ },
+ {
+ .name = "WPrintQueueResume",
+ .id = RAP_WPrintQContinue,
+ .fn = api_WPrintQueueCtrl
+ },
+ {
+ .name = "WPrintJobEnumerate",
+ .id = RAP_WPrintJobEnum,
+ .fn = api_WPrintJobEnumerate
+ },
+ {
+ .name = "WPrintJobGetInfo",
+ .id = RAP_WPrintJobGetInfo,
+ .fn = api_WPrintJobGetInfo
+ },
+ {
+ .name = "RDosPrintJobDel",
+ .id = RAP_WPrintJobDel,
+ .fn = api_RDosPrintJobDel
+ },
+ {
+ .name = "RDosPrintJobPause",
+ .id = RAP_WPrintJobPause,
+ .fn = api_RDosPrintJobDel
+ },
+ {
+ .name = "RDosPrintJobResume",
+ .id = RAP_WPrintJobContinue,
+ .fn = api_RDosPrintJobDel
+ },
+ {
+ .name = "WPrintDestEnum",
+ .id = RAP_WPrintDestEnum,
+ .fn = api_WPrintDestEnum
+ },
+ {
+ .name = "WPrintDestGetInfo",
+ .id = RAP_WPrintDestGetInfo,
+ .fn = api_WPrintDestGetInfo
+ },
+ {
+ .name = "NetRemoteTOD",
+ .id = RAP_NetRemoteTOD,
+ .fn = api_NetRemoteTOD
+ },
+ {
+ .name = "WPrintQueuePurge",
+ .id = RAP_WPrintQPurge,
+ .fn = api_WPrintQueueCtrl
+ },
+ {
+ .name = "NetServerEnum2",
+ .id = RAP_NetServerEnum2,
+ .fn = api_RNetServerEnum2
+ }, /* anon OK */
+ {
+ .name = "NetServerEnum3",
+ .id = RAP_NetServerEnum3,
+ .fn = api_RNetServerEnum3
+ }, /* anon OK */
+ {
+ .name = "WAccessGetUserPerms",
+ .id = RAP_WAccessGetUserPerms,
+ .fn = api_WAccessGetUserPerms
+ },
+ {
+ .name = "WWkstaUserLogon",
+ .id = RAP_WWkstaUserLogon,
+ .fn = api_WWkstaUserLogon
+ },
+ {
+ .name = "PrintJobInfo",
+ .id = RAP_WPrintJobSetInfo,
+ .fn = api_PrintJobInfo
+ },
+ {
+ .name = "WPrintDriverEnum",
+ .id = RAP_WPrintDriverEnum,
+ .fn = api_WPrintDriverEnum
+ },
+ {
+ .name = "WPrintQProcEnum",
+ .id = RAP_WPrintQProcessorEnum,
+ .fn = api_WPrintQProcEnum
+ },
+ {
+ .name = "WPrintPortEnum",
+ .id = RAP_WPrintPortEnum,
+ .fn = api_WPrintPortEnum
+ },
+ {
+ .name = "SamOEMChangePassword",
+ .id = RAP_SamOEMChgPasswordUser2_P,
+ .fn = api_SamOEMChangePassword
+ }, /* anon OK */
+ {
+ .name = NULL,
+ .id = -1,
+ .fn = api_Unsupported}
+ /*
+ * The following RAP calls are not implemented by Samba:
+ * RAP_WFileEnum2 - anon not OK
+ */
+};
+
+
+/****************************************************************************
+ Handle remote api calls.
+****************************************************************************/
+
+void api_reply(connection_struct *conn, uint64_t vuid,
+ struct smb_request *req,
+ char *data, char *params,
+ int tdscnt, int tpscnt,
+ int mdrcnt, int mprcnt)
+{
+ int api_command;
+ char *rdata = NULL;
+ char *rparam = NULL;
+ const char *name1 = NULL;
+ const char *name2 = NULL;
+ int rdata_len = 0;
+ int rparam_len = 0;
+ bool reply=False;
+ int i;
+
+ if (!params) {
+ DEBUG(0,("ERROR: NULL params in api_reply()\n"));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if (tpscnt < 2) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ api_command = SVAL(params,0);
+ /* Is there a string at position params+2 ? */
+ if (skip_string(params,tpscnt,params+2)) {
+ name1 = params + 2;
+ } else {
+ name1 = "";
+ }
+ name2 = skip_string(params,tpscnt,params+2);
+ if (!name2) {
+ name2 = "";
+ }
+
+ DEBUG(3,("Got API command %d of form <%s> <%s> (tdscnt=%d,tpscnt=%d,mdrcnt=%d,mprcnt=%d)\n",
+ api_command,
+ name1,
+ name2,
+ tdscnt,tpscnt,mdrcnt,mprcnt));
+
+ for (i=0;api_commands[i].name;i++) {
+ if (api_commands[i].id == api_command && api_commands[i].fn) {
+ DEBUG(3,("Doing %s\n",api_commands[i].name));
+ break;
+ }
+ }
+
+ /* Check whether this api call can be done anonymously */
+
+ if (api_commands[i].auth_user && lp_restrict_anonymous()) {
+ struct auth_session_info *si = NULL;
+ NTSTATUS status;
+
+ status = smbXsrv_session_info_lookup(conn->sconn->client,
+ vuid,
+ &si);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+
+ if (security_session_user_level(si, NULL) < SECURITY_USER) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+ }
+
+ rdata = (char *)SMB_MALLOC(1024);
+ if (rdata) {
+ memset(rdata,'\0',1024);
+ }
+
+ rparam = (char *)SMB_MALLOC(1024);
+ if (rparam) {
+ memset(rparam,'\0',1024);
+ }
+
+ if(!rdata || !rparam) {
+ DEBUG(0,("api_reply: malloc fail !\n"));
+ SAFE_FREE(rdata);
+ SAFE_FREE(rparam);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ reply = api_commands[i].fn(req->sconn, conn,
+ vuid,
+ params,tpscnt, /* params + length */
+ data,tdscnt, /* data + length */
+ mdrcnt,mprcnt,
+ &rdata,&rparam,&rdata_len,&rparam_len);
+
+
+ if (rdata_len > mdrcnt || rparam_len > mprcnt) {
+ reply = api_TooSmall(req->sconn,conn,vuid,params,data,
+ mdrcnt,mprcnt,
+ &rdata,&rparam,&rdata_len,&rparam_len);
+ }
+
+ /* if we get False back then it's actually unsupported */
+ if (!reply) {
+ reply = api_Unsupported(req->sconn,conn,vuid,params,tpscnt,
+ data,
+ tdscnt,mdrcnt,mprcnt,
+ &rdata,&rparam,&rdata_len,&rparam_len);
+ }
+
+ /* If api_Unsupported returns false we can't return anything. */
+ if (reply) {
+ send_trans_reply(conn, req, rparam, rparam_len,
+ rdata, rdata_len, False);
+ }
+
+ SAFE_FREE(rdata);
+ SAFE_FREE(rparam);
+ return;
+}
diff --git a/source3/smbd/smb1_lanman.h b/source3/smbd/smb1_lanman.h
new file mode 100644
index 0000000..9e7ecb4
--- /dev/null
+++ b/source3/smbd/smb1_lanman.h
@@ -0,0 +1,28 @@
+/*
+ Unix SMB/CIFS implementation.
+ Inter-process communication and named pipe handling
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 2007.
+
+ SMB Version handling
+ Copyright (C) John H Terpstra 1995-1998
+
+ 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/>.
+ */
+
+void api_reply(connection_struct *conn, uint64_t vuid,
+ struct smb_request *req,
+ char *data, char *params,
+ int tdscnt, int tpscnt,
+ int mdrcnt, int mprcnt);
diff --git a/source3/smbd/smb1_message.c b/source3/smbd/smb1_message.c
new file mode 100644
index 0000000..ca7201e
--- /dev/null
+++ b/source3/smbd/smb1_message.c
@@ -0,0 +1,334 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB messaging
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ 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/>.
+*/
+/*
+ This file handles the messaging system calls for winpopup style
+ messages
+*/
+
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "smbprofile.h"
+#include "source3/lib/substitute.h"
+
+struct msg_state {
+ char *from;
+ char *to;
+ char *msg;
+};
+
+/****************************************************************************
+ Deliver the message.
+****************************************************************************/
+
+static void msg_deliver(struct msg_state *state)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *name = NULL;
+ int i;
+ int fd;
+ char *msg;
+ size_t len;
+ ssize_t sz;
+ fstring alpha_buf;
+ char *s;
+ mode_t mask;
+
+ if (! (*lp_message_command(frame, lp_sub))) {
+ DEBUG(1,("no messaging command specified\n"));
+ goto done;
+ }
+
+ /* put it in a temporary file */
+ name = talloc_asprintf(talloc_tos(), "%s/msg.XXXXXX", tmpdir());
+ if (!name) {
+ goto done;
+ }
+ mask = umask(S_IRWXO | S_IRWXG);
+ fd = mkstemp(name);
+ umask(mask);
+
+ if (fd == -1) {
+ DEBUG(1, ("can't open message file %s: %s\n", name,
+ strerror(errno)));
+ goto done;
+ }
+
+ /*
+ * Incoming message is in DOS codepage format. Convert to UNIX.
+ */
+
+ if (!convert_string_talloc(talloc_tos(), CH_DOS, CH_UNIX, state->msg,
+ talloc_get_size(state->msg), (void *)&msg,
+ &len)) {
+ DEBUG(3, ("Conversion failed, delivering message in DOS "
+ "codepage format\n"));
+ msg = state->msg;
+ }
+
+ for (i = 0; i < len; i++) {
+ if ((msg[i] == '\r') &&
+ (i < (len-1)) && (msg[i+1] == '\n')) {
+ continue;
+ }
+ sz = write(fd, &msg[i], 1);
+ if ( sz != 1 ) {
+ DEBUG(0, ("Write error to fd %d: %ld(%s)\n", fd,
+ (long)sz, strerror(errno)));
+ }
+ }
+
+ close(fd);
+
+ /* run the command */
+ s = lp_message_command(frame, lp_sub);
+ if (s == NULL) {
+ goto done;
+ }
+
+ alpha_strcpy(alpha_buf, state->from, NULL, sizeof(alpha_buf));
+
+ s = talloc_string_sub(talloc_tos(), s, "%f", alpha_buf);
+ if (s == NULL) {
+ goto done;
+ }
+
+ alpha_strcpy(alpha_buf, state->to, NULL, sizeof(alpha_buf));
+
+ s = talloc_string_sub(talloc_tos(), s, "%t", alpha_buf);
+ if (s == NULL) {
+ goto done;
+ }
+
+ s = talloc_sub_basic(talloc_tos(), get_current_username(),
+ get_current_user_info_domain(), s);
+ if (s == NULL) {
+ goto done;
+ }
+
+ s = talloc_string_sub(talloc_tos(), s, "%s", name);
+ if (s == NULL) {
+ goto done;
+ }
+ smbrun(s, NULL, NULL);
+
+ done:
+ TALLOC_FREE(frame);
+ return;
+}
+
+/****************************************************************************
+ Reply to a sends.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_sends(struct smb_request *req)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct msg_state *state;
+ int len;
+ const uint8_t *msg;
+ const uint8_t *p;
+
+ START_PROFILE(SMBsends);
+
+ if (!(*lp_message_command(talloc_tos(), lp_sub))) {
+ reply_nterror(req, NT_STATUS_REQUEST_NOT_ACCEPTED);
+ END_PROFILE(SMBsends);
+ return;
+ }
+
+ state = talloc_zero(talloc_tos(), struct msg_state);
+
+ p = req->buf + 1;
+ p += srvstr_pull_req_talloc(
+ state, req, &state->from, p, STR_ASCII|STR_TERMINATE) + 1;
+ p += srvstr_pull_req_talloc(
+ state, req, &state->to, p, STR_ASCII|STR_TERMINATE) + 1;
+
+ msg = p;
+
+ len = SVAL(msg,0);
+ len = MIN(len, smbreq_bufrem(req, msg+2));
+
+ state->msg = talloc_array(state, char, len);
+
+ if (state->msg == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBsends);
+ return;
+ }
+
+ memcpy(state->msg, msg+2, len);
+
+ msg_deliver(state);
+
+ reply_smb1_outbuf(req, 0, 0);
+
+ END_PROFILE(SMBsends);
+ return;
+}
+
+/****************************************************************************
+ Reply to a sendstrt.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_sendstrt(struct smb_request *req)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct smbXsrv_connection *xconn = req->xconn;
+ const uint8_t *p;
+
+ START_PROFILE(SMBsendstrt);
+
+ if (!(*lp_message_command(talloc_tos(), lp_sub))) {
+ reply_nterror(req, NT_STATUS_REQUEST_NOT_ACCEPTED);
+ END_PROFILE(SMBsendstrt);
+ return;
+ }
+
+ TALLOC_FREE(xconn->smb1.msg_state);
+
+ xconn->smb1.msg_state = talloc_zero(xconn, struct msg_state);
+
+ if (xconn->smb1.msg_state == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBsendstrt);
+ return;
+ }
+
+ p = req->buf+1;
+ p += srvstr_pull_req_talloc(
+ xconn->smb1.msg_state, req,
+ &xconn->smb1.msg_state->from, p,
+ STR_ASCII|STR_TERMINATE) + 1;
+ p += srvstr_pull_req_talloc(
+ xconn->smb1.msg_state, req,
+ &xconn->smb1.msg_state->to, p,
+ STR_ASCII|STR_TERMINATE) + 1;
+
+ DEBUG(3, ("SMBsendstrt (from %s to %s)\n",
+ xconn->smb1.msg_state->from,
+ xconn->smb1.msg_state->to));
+
+ reply_smb1_outbuf(req, 0, 0);
+
+ END_PROFILE(SMBsendstrt);
+ return;
+}
+
+/****************************************************************************
+ Reply to a sendtxt.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_sendtxt(struct smb_request *req)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct smbXsrv_connection *xconn = req->xconn;
+ int len;
+ const char *msg;
+ char *tmp;
+ size_t old_len;
+
+ START_PROFILE(SMBsendtxt);
+
+ if (! (*lp_message_command(talloc_tos(), lp_sub))) {
+ reply_nterror(req, NT_STATUS_REQUEST_NOT_ACCEPTED);
+ END_PROFILE(SMBsendtxt);
+ return;
+ }
+
+ if ((xconn->smb1.msg_state == NULL) || (req->buflen < 3)) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBsendtxt);
+ return;
+ }
+
+ msg = (const char *)req->buf + 1;
+
+ old_len = talloc_get_size(xconn->smb1.msg_state->msg);
+
+ len = MIN(SVAL(msg, 0), smbreq_bufrem(req, msg+2));
+
+ tmp = talloc_realloc(xconn->smb1.msg_state,
+ xconn->smb1.msg_state->msg,
+ char, old_len + len);
+
+ if (tmp == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBsendtxt);
+ return;
+ }
+
+ xconn->smb1.msg_state->msg = tmp;
+
+ memcpy(&xconn->smb1.msg_state->msg[old_len], msg+2, len);
+
+ DEBUG( 3, ( "SMBsendtxt\n" ) );
+
+ reply_smb1_outbuf(req, 0, 0);
+
+ END_PROFILE(SMBsendtxt);
+ return;
+}
+
+/****************************************************************************
+ Reply to a sendend.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_sendend(struct smb_request *req)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct smbXsrv_connection *xconn = req->xconn;
+ START_PROFILE(SMBsendend);
+
+ if (! (*lp_message_command(talloc_tos(), lp_sub))) {
+ reply_nterror(req, NT_STATUS_REQUEST_NOT_ACCEPTED);
+ END_PROFILE(SMBsendend);
+ return;
+ }
+
+ if (xconn->smb1.msg_state == NULL) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBsendend);
+ return;
+ }
+
+ DEBUG(3,("SMBsendend\n"));
+
+ msg_deliver(xconn->smb1.msg_state);
+
+ TALLOC_FREE(xconn->smb1.msg_state);
+
+ reply_smb1_outbuf(req, 0, 0);
+
+ END_PROFILE(SMBsendend);
+ return;
+}
diff --git a/source3/smbd/smb1_message.h b/source3/smbd/smb1_message.h
new file mode 100644
index 0000000..f54d7ae
--- /dev/null
+++ b/source3/smbd/smb1_message.h
@@ -0,0 +1,23 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB messaging
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ 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/>.
+*/
+
+void reply_sends(struct smb_request *req);
+void reply_sendstrt(struct smb_request *req);
+void reply_sendtxt(struct smb_request *req);
+void reply_sendend(struct smb_request *req);
diff --git a/source3/smbd/smb1_negprot.c b/source3/smbd/smb1_negprot.c
new file mode 100644
index 0000000..7171c66
--- /dev/null
+++ b/source3/smbd/smb1_negprot.c
@@ -0,0 +1,706 @@
+/*
+ Unix SMB/CIFS implementation.
+ negprot reply code
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Volker Lendecke 2007
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "serverid.h"
+#include "auth.h"
+#include "messages.h"
+#include "smbprofile.h"
+#include "auth/gensec/gensec.h"
+#include "../libcli/smb/smb_signing.h"
+#include "lib/util/string_wrappers.h"
+#include "source3/lib/substitute.h"
+
+/*
+ * MS-CIFS, 2.2.4.52.2 SMB_COM_NEGOTIATE Response:
+ * If the server does not support any of the listed dialects, it MUST return a
+ * DialectIndex of 0XFFFF
+ */
+#define NO_PROTOCOL_CHOSEN 0xffff
+
+static void get_challenge(struct smbXsrv_connection *xconn, uint8_t buff[8])
+{
+ NTSTATUS nt_status;
+
+ /* We might be called more than once, multiple negprots are
+ * permitted */
+ if (xconn->smb1.negprot.auth_context) {
+ DEBUG(3, ("get challenge: is this a secondary negprot? "
+ "sconn->negprot.auth_context is non-NULL!\n"));
+ TALLOC_FREE(xconn->smb1.negprot.auth_context);
+ }
+
+ DEBUG(10, ("get challenge: creating negprot_global_auth_context\n"));
+ nt_status = make_auth4_context(
+ xconn, &xconn->smb1.negprot.auth_context);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("make_auth_context_subsystem returned %s\n",
+ nt_errstr(nt_status)));
+ smb_panic("cannot make_negprot_global_auth_context!");
+ }
+ DEBUG(10, ("get challenge: getting challenge\n"));
+ xconn->smb1.negprot.auth_context->get_ntlm_challenge(
+ xconn->smb1.negprot.auth_context, buff);
+}
+
+/****************************************************************************
+ Reply for the lanman 1.0 protocol.
+****************************************************************************/
+
+static NTSTATUS reply_lanman1(struct smb_request *req, uint16_t choice)
+{
+ int secword=0;
+ time_t t = time(NULL);
+ struct smbXsrv_connection *xconn = req->xconn;
+ uint16_t raw;
+ NTSTATUS status;
+
+ if (lp_async_smb_echo_handler()) {
+ raw = 0;
+ } else {
+ raw = (lp_read_raw()?1:0) | (lp_write_raw()?2:0);
+ }
+
+ xconn->smb1.negprot.encrypted_passwords = lp_encrypt_passwords();
+
+ secword |= NEGOTIATE_SECURITY_USER_LEVEL;
+ if (xconn->smb1.negprot.encrypted_passwords) {
+ secword |= NEGOTIATE_SECURITY_CHALLENGE_RESPONSE;
+ }
+
+ reply_smb1_outbuf(req, 13, xconn->smb1.negprot.encrypted_passwords?8:0);
+
+ SSVAL(req->outbuf,smb_vwv0,choice);
+ SSVAL(req->outbuf,smb_vwv1,secword);
+ /* Create a token value and add it to the outgoing packet. */
+ if (xconn->smb1.negprot.encrypted_passwords) {
+ get_challenge(xconn, (uint8_t *)smb_buf(req->outbuf));
+ SSVAL(req->outbuf,smb_vwv11, 8);
+ }
+
+ status = smbXsrv_connection_init_tables(xconn, PROTOCOL_LANMAN1);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return status;
+ }
+
+ /* Reply, SMBlockread, SMBwritelock supported. */
+ SCVAL(req->outbuf,smb_flg, FLAG_REPLY|FLAG_SUPPORT_LOCKREAD);
+ SSVAL(req->outbuf,smb_vwv2, xconn->smb1.negprot.max_recv);
+ SSVAL(req->outbuf,smb_vwv3, lp_max_mux()); /* maxmux */
+ SSVAL(req->outbuf,smb_vwv4, 1);
+ SSVAL(req->outbuf,smb_vwv5, raw); /* tell redirector we support
+ readbraw writebraw (possibly) */
+ SIVAL(req->outbuf,smb_vwv6, getpid());
+ SSVAL(req->outbuf,smb_vwv10, set_server_zone_offset(t)/60);
+
+ srv_put_dos_date((char *)req->outbuf,smb_vwv8,t);
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Reply for the lanman 2.0 protocol.
+****************************************************************************/
+
+static NTSTATUS reply_lanman2(struct smb_request *req, uint16_t choice)
+{
+ int secword=0;
+ time_t t = time(NULL);
+ struct smbXsrv_connection *xconn = req->xconn;
+ uint16_t raw;
+ NTSTATUS status;
+
+ if (lp_async_smb_echo_handler()) {
+ raw = 0;
+ } else {
+ raw = (lp_read_raw()?1:0) | (lp_write_raw()?2:0);
+ }
+
+ xconn->smb1.negprot.encrypted_passwords = lp_encrypt_passwords();
+
+ secword |= NEGOTIATE_SECURITY_USER_LEVEL;
+ if (xconn->smb1.negprot.encrypted_passwords) {
+ secword |= NEGOTIATE_SECURITY_CHALLENGE_RESPONSE;
+ }
+
+ reply_smb1_outbuf(req, 13, xconn->smb1.negprot.encrypted_passwords?8:0);
+
+ SSVAL(req->outbuf,smb_vwv0, choice);
+ SSVAL(req->outbuf,smb_vwv1, secword);
+ SIVAL(req->outbuf,smb_vwv6, getpid());
+
+ /* Create a token value and add it to the outgoing packet. */
+ if (xconn->smb1.negprot.encrypted_passwords) {
+ get_challenge(xconn, (uint8_t *)smb_buf(req->outbuf));
+ SSVAL(req->outbuf,smb_vwv11, 8);
+ }
+
+ status = smbXsrv_connection_init_tables(xconn, PROTOCOL_LANMAN2);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return status;
+ }
+
+ /* Reply, SMBlockread, SMBwritelock supported. */
+ SCVAL(req->outbuf,smb_flg,FLAG_REPLY|FLAG_SUPPORT_LOCKREAD);
+ SSVAL(req->outbuf,smb_vwv2,xconn->smb1.negprot.max_recv);
+ SSVAL(req->outbuf,smb_vwv3,lp_max_mux());
+ SSVAL(req->outbuf,smb_vwv4,1);
+ SSVAL(req->outbuf,smb_vwv5,raw); /* readbraw and/or writebraw */
+ SSVAL(req->outbuf,smb_vwv10, set_server_zone_offset(t)/60);
+ srv_put_dos_date((char *)req->outbuf,smb_vwv8,t);
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Reply for the nt protocol.
+****************************************************************************/
+
+static NTSTATUS reply_nt1(struct smb_request *req, uint16_t choice)
+{
+ /* dual names + lock_and_read + nt SMBs + remote API calls */
+ int capabilities = CAP_NT_FIND|CAP_LOCK_AND_READ|
+ CAP_LEVEL_II_OPLOCKS;
+
+ int secword=0;
+ bool negotiate_spnego = False;
+ struct timespec ts;
+ ssize_t ret;
+ struct smbXsrv_connection *xconn = req->xconn;
+ bool signing_desired = false;
+ bool signing_required = false;
+ NTSTATUS status;
+
+ xconn->smb1.negprot.encrypted_passwords = lp_encrypt_passwords();
+
+ /* Check the flags field to see if this is Vista.
+ WinXP sets it and Vista does not. But we have to
+ distinguish from NT which doesn't set it either. */
+
+ if ( (req->flags2 & FLAGS2_EXTENDED_SECURITY) &&
+ ((req->flags2 & FLAGS2_SMB_SECURITY_SIGNATURES_REQUIRED) == 0) )
+ {
+ if ((get_remote_arch() != RA_SAMBA) &&
+ (get_remote_arch() != RA_CIFSFS)) {
+ set_remote_arch( RA_VISTA );
+ }
+ }
+
+ reply_smb1_outbuf(req,17,0);
+
+ /* do spnego in user level security if the client
+ supports it and we can do encrypted passwords */
+
+ if (xconn->smb1.negprot.encrypted_passwords &&
+ (req->flags2 & FLAGS2_EXTENDED_SECURITY)) {
+ negotiate_spnego = True;
+ capabilities |= CAP_EXTENDED_SECURITY;
+ add_to_common_flags2(FLAGS2_EXTENDED_SECURITY);
+ /* Ensure FLAGS2_EXTENDED_SECURITY gets set in this reply
+ (already partially constructed. */
+ SSVAL(req->outbuf, smb_flg2,
+ req->flags2 | FLAGS2_EXTENDED_SECURITY);
+ }
+
+ capabilities |= CAP_NT_SMBS|CAP_RPC_REMOTE_APIS;
+
+ if (lp_unicode()) {
+ capabilities |= CAP_UNICODE;
+ }
+
+ if (lp_smb1_unix_extensions()) {
+ capabilities |= CAP_UNIX;
+ }
+
+ if (lp_large_readwrite())
+ capabilities |= CAP_LARGE_READX|CAP_LARGE_WRITEX|CAP_W2K_SMBS;
+
+ capabilities |= CAP_LARGE_FILES;
+
+ if (!lp_async_smb_echo_handler() && lp_read_raw() && lp_write_raw())
+ capabilities |= CAP_RAW_MODE;
+
+ if (lp_nt_status_support())
+ capabilities |= CAP_STATUS32;
+
+ if (lp_host_msdfs())
+ capabilities |= CAP_DFS;
+
+ secword |= NEGOTIATE_SECURITY_USER_LEVEL;
+ if (xconn->smb1.negprot.encrypted_passwords) {
+ secword |= NEGOTIATE_SECURITY_CHALLENGE_RESPONSE;
+ }
+
+ signing_desired = smb1_signing_is_desired(xconn->smb1.signing_state);
+ signing_required = smb1_signing_is_mandatory(xconn->smb1.signing_state);
+
+ if (signing_desired) {
+ secword |= NEGOTIATE_SECURITY_SIGNATURES_ENABLED;
+ /* No raw mode with smb signing. */
+ capabilities &= ~CAP_RAW_MODE;
+ if (signing_required) {
+ secword |=NEGOTIATE_SECURITY_SIGNATURES_REQUIRED;
+ }
+ }
+
+ SSVAL(req->outbuf,smb_vwv0,choice);
+ SCVAL(req->outbuf,smb_vwv1,secword);
+
+ status = smbXsrv_connection_init_tables(xconn, PROTOCOL_NT1);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return status;
+ }
+
+ SSVAL(req->outbuf,smb_vwv1+1, lp_max_mux()); /* maxmpx */
+ SSVAL(req->outbuf,smb_vwv2+1, 1); /* num vcs */
+ SIVAL(req->outbuf,smb_vwv3+1,
+ xconn->smb1.negprot.max_recv); /* max buffer. LOTS! */
+ SIVAL(req->outbuf,smb_vwv5+1, 0x10000); /* raw size. full 64k */
+ SIVAL(req->outbuf,smb_vwv7+1, getpid()); /* session key */
+ SIVAL(req->outbuf,smb_vwv9+1, capabilities); /* capabilities */
+ clock_gettime(CLOCK_REALTIME,&ts);
+ put_long_date_full_timespec(TIMESTAMP_SET_NT_OR_BETTER,(char *)req->outbuf+smb_vwv11+1,&ts);
+ SSVALS(req->outbuf,smb_vwv15+1,set_server_zone_offset(ts.tv_sec)/60);
+
+ if (!negotiate_spnego) {
+ /* Create a token value and add it to the outgoing packet. */
+ if (xconn->smb1.negprot.encrypted_passwords) {
+ uint8_t chal[8];
+ /* note that we do not send a challenge at all if
+ we are using plaintext */
+ get_challenge(xconn, chal);
+ ret = message_push_blob(
+ &req->outbuf, data_blob_const(chal, sizeof(chal)));
+ if (ret == -1) {
+ DEBUG(0, ("Could not push challenge\n"));
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return NT_STATUS_NO_MEMORY;
+ }
+ SCVAL(req->outbuf, smb_vwv16+1, ret);
+ }
+ ret = message_push_string(&req->outbuf, lp_workgroup(),
+ STR_UNICODE|STR_TERMINATE
+ |STR_NOALIGN);
+ if (ret == -1) {
+ DEBUG(0, ("Could not push workgroup string\n"));
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return NT_STATUS_NO_MEMORY;
+ }
+ ret = message_push_string(&req->outbuf, lp_netbios_name(),
+ STR_UNICODE|STR_TERMINATE
+ |STR_NOALIGN);
+ if (ret == -1) {
+ DEBUG(0, ("Could not push netbios name string\n"));
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return NT_STATUS_NO_MEMORY;
+ }
+ DEBUG(3,("not using SPNEGO\n"));
+ } else {
+ DATA_BLOB spnego_blob = negprot_spnego(req, xconn);
+
+ if (spnego_blob.data == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = message_push_blob(&req->outbuf, spnego_blob);
+ if (ret == -1) {
+ DEBUG(0, ("Could not push spnego blob\n"));
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return NT_STATUS_NO_MEMORY;
+ }
+ data_blob_free(&spnego_blob);
+
+ SCVAL(req->outbuf,smb_vwv16+1, 0);
+ DEBUG(3,("using SPNEGO\n"));
+ }
+
+ return NT_STATUS_OK;
+}
+
+/* these are the protocol lists used for auto architecture detection:
+
+WinNT 3.51:
+protocol [PC NETWORK PROGRAM 1.0]
+protocol [XENIX CORE]
+protocol [MICROSOFT NETWORKS 1.03]
+protocol [LANMAN1.0]
+protocol [Windows for Workgroups 3.1a]
+protocol [LM1.2X002]
+protocol [LANMAN2.1]
+protocol [NT LM 0.12]
+
+Win95:
+protocol [PC NETWORK PROGRAM 1.0]
+protocol [XENIX CORE]
+protocol [MICROSOFT NETWORKS 1.03]
+protocol [LANMAN1.0]
+protocol [Windows for Workgroups 3.1a]
+protocol [LM1.2X002]
+protocol [LANMAN2.1]
+protocol [NT LM 0.12]
+
+Win2K:
+protocol [PC NETWORK PROGRAM 1.0]
+protocol [LANMAN1.0]
+protocol [Windows for Workgroups 3.1a]
+protocol [LM1.2X002]
+protocol [LANMAN2.1]
+protocol [NT LM 0.12]
+
+Vista:
+protocol [PC NETWORK PROGRAM 1.0]
+protocol [LANMAN1.0]
+protocol [Windows for Workgroups 3.1a]
+protocol [LM1.2X002]
+protocol [LANMAN2.1]
+protocol [NT LM 0.12]
+protocol [SMB 2.001]
+
+OS/2:
+protocol [PC NETWORK PROGRAM 1.0]
+protocol [XENIX CORE]
+protocol [LANMAN1.0]
+protocol [LM1.2X002]
+protocol [LANMAN2.1]
+
+OSX:
+protocol [NT LM 0.12]
+protocol [SMB 2.002]
+protocol [SMB 2.???]
+*/
+
+/*
+ * Modified to recognize the architecture of the remote machine better.
+ *
+ * This appears to be the matrix of which protocol is used by which
+ * product.
+ Protocol WfWg Win95 WinNT Win2K OS/2 Vista OSX
+ PC NETWORK PROGRAM 1.0 1 1 1 1 1 1
+ XENIX CORE 2 2
+ MICROSOFT NETWORKS 3.0 2 2
+ DOS LM1.2X002 3 3
+ MICROSOFT NETWORKS 1.03 3
+ DOS LANMAN2.1 4 4
+ LANMAN1.0 4 2 3 2
+ Windows for Workgroups 3.1a 5 5 5 3 3
+ LM1.2X002 6 4 4 4
+ LANMAN2.1 7 5 5 5
+ NT LM 0.12 6 8 6 6 6 1
+ SMB 2.001 7
+ SMB 2.002 2
+ SMB 2.??? 3
+ *
+ * tim@fsg.com 09/29/95
+ * Win2K added by matty 17/7/99
+ */
+
+#define PROT_PC_NETWORK_PROGRAM_1_0 0x0001
+#define PROT_XENIX_CORE 0x0002
+#define PROT_MICROSOFT_NETWORKS_3_0 0x0004
+#define PROT_DOS_LM1_2X002 0x0008
+#define PROT_MICROSOFT_NETWORKS_1_03 0x0010
+#define PROT_DOS_LANMAN2_1 0x0020
+#define PROT_LANMAN1_0 0x0040
+#define PROT_WFWG 0x0080
+#define PROT_LM1_2X002 0x0100
+#define PROT_LANMAN2_1 0x0200
+#define PROT_NT_LM_0_12 0x0400
+#define PROT_SMB_2_001 0x0800
+#define PROT_SMB_2_002 0x1000
+#define PROT_SMB_2_FF 0x2000
+#define PROT_SAMBA 0x4000
+#define PROT_POSIX_2 0x8000
+
+#define ARCH_WFWG ( PROT_PC_NETWORK_PROGRAM_1_0 | PROT_MICROSOFT_NETWORKS_3_0 | \
+ PROT_DOS_LM1_2X002 | PROT_DOS_LANMAN2_1 | PROT_WFWG )
+#define ARCH_WIN95 ( ARCH_WFWG | PROT_NT_LM_0_12 )
+#define ARCH_WINNT ( PROT_PC_NETWORK_PROGRAM_1_0 | PROT_XENIX_CORE | \
+ PROT_MICROSOFT_NETWORKS_1_03 | PROT_LANMAN1_0 | PROT_WFWG | \
+ PROT_LM1_2X002 | PROT_LANMAN2_1 | PROT_NT_LM_0_12 )
+#define ARCH_WIN2K ( ARCH_WINNT & ~(PROT_XENIX_CORE | PROT_MICROSOFT_NETWORKS_1_03) )
+#define ARCH_OS2 ( ARCH_WINNT & ~(PROT_MICROSOFT_NETWORKS_1_03 | PROT_WFWG) )
+#define ARCH_VISTA ( ARCH_WIN2K | PROT_SMB_2_001 )
+#define ARCH_SAMBA ( PROT_SAMBA )
+#define ARCH_CIFSFS ( PROT_POSIX_2 )
+#define ARCH_OSX ( PROT_NT_LM_0_12 | PROT_SMB_2_002 | PROT_SMB_2_FF )
+
+/* List of supported protocols, most desired first */
+static const struct {
+ const char *proto_name;
+ const char *short_name;
+ NTSTATUS (*proto_reply_fn)(struct smb_request *req, uint16_t choice);
+ int protocol_level;
+} supported_protocols[] = {
+ {"SMB 2.???", "SMB2_FF", reply_smb20ff, PROTOCOL_SMB2_10},
+ {"SMB 2.002", "SMB2_02", reply_smb2002, PROTOCOL_SMB2_02},
+ {"NT LANMAN 1.0", "NT1", reply_nt1, PROTOCOL_NT1},
+ {"NT LM 0.12", "NT1", reply_nt1, PROTOCOL_NT1},
+ {"POSIX 2", "NT1", reply_nt1, PROTOCOL_NT1},
+ {"LANMAN2.1", "LANMAN2", reply_lanman2, PROTOCOL_LANMAN2},
+ {"LM1.2X002", "LANMAN2", reply_lanman2, PROTOCOL_LANMAN2},
+ {"Samba", "LANMAN2", reply_lanman2, PROTOCOL_LANMAN2},
+ {"DOS LM1.2X002", "LANMAN2", reply_lanman2, PROTOCOL_LANMAN2},
+ {"LANMAN1.0", "LANMAN1", reply_lanman1, PROTOCOL_LANMAN1},
+ {"MICROSOFT NETWORKS 3.0", "LANMAN1", reply_lanman1, PROTOCOL_LANMAN1},
+ {NULL,NULL,NULL,0},
+};
+
+/****************************************************************************
+ Reply to a negprot.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_negprot(struct smb_request *req)
+{
+ size_t choice = 0;
+ int chosen_level = -1;
+ bool choice_set = false;
+ int protocol;
+ const char *p;
+ int protocols = 0;
+ int num_cliprotos;
+ char **cliprotos;
+ size_t i;
+ size_t converted_size;
+ struct smbXsrv_connection *xconn = req->xconn;
+ struct smbd_server_connection *sconn = req->sconn;
+ bool signing_required = true;
+ int max_proto;
+ int min_proto;
+ NTSTATUS status;
+
+ START_PROFILE(SMBnegprot);
+
+ if (xconn->smb1.negprot.done) {
+ END_PROFILE(SMBnegprot);
+ exit_server_cleanly("multiple negprot's are not permitted");
+ }
+
+ if (req->buflen == 0) {
+ DEBUG(0, ("negprot got no protocols\n"));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBnegprot);
+ return;
+ }
+
+ if (req->buf[req->buflen-1] != '\0') {
+ DEBUG(0, ("negprot protocols not 0-terminated\n"));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBnegprot);
+ return;
+ }
+
+ p = (const char *)req->buf + 1;
+
+ num_cliprotos = 0;
+ cliprotos = NULL;
+
+ while (smbreq_bufrem(req, p) > 0) {
+
+ char **tmp;
+
+ tmp = talloc_realloc(talloc_tos(), cliprotos, char *,
+ num_cliprotos+1);
+ if (tmp == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ TALLOC_FREE(cliprotos);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBnegprot);
+ return;
+ }
+
+ cliprotos = tmp;
+
+ if (!pull_ascii_talloc(cliprotos, &cliprotos[num_cliprotos], p,
+ &converted_size)) {
+ DEBUG(0, ("pull_ascii_talloc failed\n"));
+ TALLOC_FREE(cliprotos);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBnegprot);
+ return;
+ }
+
+ DEBUG(3, ("Requested protocol [%s]\n",
+ cliprotos[num_cliprotos]));
+
+ num_cliprotos += 1;
+ p += strlen(p) + 2;
+ }
+
+ for (i=0; i<num_cliprotos; i++) {
+ if (strcsequal(cliprotos[i], "Windows for Workgroups 3.1a")) {
+ protocols |= PROT_WFWG;
+ } else if (strcsequal(cliprotos[i], "DOS LM1.2X002")) {
+ protocols |= PROT_DOS_LM1_2X002;
+ } else if (strcsequal(cliprotos[i], "DOS LANMAN2.1")) {
+ protocols |= PROT_DOS_LANMAN2_1;
+ } else if (strcsequal(cliprotos[i], "LANMAN1.0")) {
+ protocols |= PROT_LANMAN1_0;
+ } else if (strcsequal(cliprotos[i], "NT LM 0.12")) {
+ protocols |= PROT_NT_LM_0_12;
+ } else if (strcsequal(cliprotos[i], "SMB 2.001")) {
+ protocols |= PROT_SMB_2_001;
+ } else if (strcsequal(cliprotos[i], "SMB 2.002")) {
+ protocols |= PROT_SMB_2_002;
+ } else if (strcsequal(cliprotos[i], "SMB 2.???")) {
+ protocols |= PROT_SMB_2_FF;
+ } else if (strcsequal(cliprotos[i], "LANMAN2.1")) {
+ protocols |= PROT_LANMAN2_1;
+ } else if (strcsequal(cliprotos[i], "LM1.2X002")) {
+ protocols |= PROT_LM1_2X002;
+ } else if (strcsequal(cliprotos[i], "MICROSOFT NETWORKS 1.03")) {
+ protocols |= PROT_MICROSOFT_NETWORKS_1_03;
+ } else if (strcsequal(cliprotos[i], "MICROSOFT NETWORKS 3.0")) {
+ protocols |= PROT_MICROSOFT_NETWORKS_3_0;
+ } else if (strcsequal(cliprotos[i], "PC NETWORK PROGRAM 1.0")) {
+ protocols |= PROT_PC_NETWORK_PROGRAM_1_0;
+ } else if (strcsequal(cliprotos[i], "XENIX CORE")) {
+ protocols |= PROT_XENIX_CORE;
+ } else if (strcsequal(cliprotos[i], "Samba")) {
+ protocols = PROT_SAMBA;
+ break;
+ } else if (strcsequal(cliprotos[i], "POSIX 2")) {
+ protocols = PROT_POSIX_2;
+ break;
+ }
+ }
+
+ switch ( protocols ) {
+ /* Old CIFSFS can send one arch only, NT LM 0.12. */
+ case PROT_NT_LM_0_12:
+ case ARCH_CIFSFS:
+ set_remote_arch(RA_CIFSFS);
+ break;
+ case ARCH_SAMBA:
+ set_remote_arch(RA_SAMBA);
+ break;
+ case ARCH_WFWG:
+ set_remote_arch(RA_WFWG);
+ break;
+ case ARCH_WIN95:
+ set_remote_arch(RA_WIN95);
+ break;
+ case ARCH_WINNT:
+ set_remote_arch(RA_WINNT);
+ break;
+ case ARCH_WIN2K:
+ set_remote_arch(RA_WIN2K);
+ break;
+ case ARCH_VISTA:
+ set_remote_arch(RA_VISTA);
+ break;
+ case ARCH_OS2:
+ set_remote_arch(RA_OS2);
+ break;
+ case ARCH_OSX:
+ set_remote_arch(RA_OSX);
+ break;
+ default:
+ set_remote_arch(RA_UNKNOWN);
+ break;
+ }
+
+ /* possibly reload - change of architecture */
+ reload_services(sconn, conn_snum_used, true);
+
+ /*
+ * Anything higher than PROTOCOL_SMB2_10 still
+ * needs to go via "SMB 2.???", which is marked
+ * as PROTOCOL_SMB2_10.
+ *
+ * The real negotiation happens via reply_smb20ff()
+ * using SMB2 Negotiation.
+ */
+ max_proto = lp_server_max_protocol();
+ if (max_proto > PROTOCOL_SMB2_10) {
+ max_proto = PROTOCOL_SMB2_10;
+ }
+ min_proto = lp_server_min_protocol();
+ if (min_proto > PROTOCOL_SMB2_10) {
+ min_proto = PROTOCOL_SMB2_10;
+ }
+
+ /* Check for protocols, most desirable first */
+ for (protocol = 0; supported_protocols[protocol].proto_name; protocol++) {
+ i = 0;
+ if ((supported_protocols[protocol].protocol_level <= max_proto) &&
+ (supported_protocols[protocol].protocol_level >= min_proto))
+ while (i < num_cliprotos) {
+ if (strequal(cliprotos[i],supported_protocols[protocol].proto_name)) {
+ choice = i;
+ chosen_level = supported_protocols[protocol].protocol_level;
+ choice_set = true;
+ }
+ i++;
+ }
+ if (choice_set) {
+ break;
+ }
+ }
+
+ if (!choice_set) {
+ bool ok;
+
+ DBG_NOTICE("No protocol supported !\n");
+ reply_smb1_outbuf(req, 1, 0);
+ SSVAL(req->outbuf, smb_vwv0, NO_PROTOCOL_CHOSEN);
+
+ ok = smb1_srv_send(xconn, (char *)req->outbuf, false, 0, false);
+ if (!ok) {
+ DBG_NOTICE("smb1_srv_send failed\n");
+ }
+ exit_server_cleanly("no protocol supported\n");
+ }
+
+ set_remote_proto(supported_protocols[protocol].short_name);
+ reload_services(sconn, conn_snum_used, true);
+ status = supported_protocols[protocol].proto_reply_fn(req, choice);
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_server_cleanly("negprot function failed\n");
+ }
+
+ DEBUG(3,("Selected protocol %s\n",supported_protocols[protocol].proto_name));
+
+ DBG_INFO("negprot index=%zu\n", choice);
+
+ xconn->smb1.negprot.done = true;
+
+ /* We always have xconn->smb1.signing_state also for >= SMB2_02 */
+ signing_required = smb1_signing_is_mandatory(xconn->smb1.signing_state);
+ if (signing_required && (chosen_level < PROTOCOL_NT1)) {
+ exit_server_cleanly("SMB signing is required and "
+ "client negotiated a downlevel protocol");
+ }
+
+ TALLOC_FREE(cliprotos);
+
+ if (lp_async_smb_echo_handler() && (chosen_level < PROTOCOL_SMB2_02) &&
+ !fork_echo_handler(xconn)) {
+ exit_server("Failed to fork echo handler");
+ }
+
+ END_PROFILE(SMBnegprot);
+ return;
+}
diff --git a/source3/smbd/smb1_negprot.h b/source3/smbd/smb1_negprot.h
new file mode 100644
index 0000000..2b23a04
--- /dev/null
+++ b/source3/smbd/smb1_negprot.h
@@ -0,0 +1,21 @@
+/*
+ Unix SMB/CIFS implementation.
+ negprot reply code
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Volker Lendecke 2007
+
+ 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/>.
+*/
+
+void reply_negprot(struct smb_request *req);
diff --git a/source3/smbd/smb1_nttrans.c b/source3/smbd/smb1_nttrans.c
new file mode 100644
index 0000000..104d3c0
--- /dev/null
+++ b/source3/smbd/smb1_nttrans.c
@@ -0,0 +1,2718 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB NT transaction handling
+ Copyright (C) Jeremy Allison 1994-2007
+ Copyright (C) Stefan (metze) Metzmacher 2003
+
+ 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 "system/filesys.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "fake_file.h"
+#include "../libcli/security/security.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "passdb/lookup_sid.h"
+#include "auth.h"
+#include "smbprofile.h"
+#include "libsmb/libsmb.h"
+#include "lib/util_ea.h"
+#include "librpc/gen_ndr/ndr_quota.h"
+#include "librpc/gen_ndr/ndr_security.h"
+
+static char *nttrans_realloc(char **ptr, size_t size)
+{
+ if (ptr==NULL) {
+ smb_panic("nttrans_realloc() called with NULL ptr");
+ }
+
+ *ptr = (char *)SMB_REALLOC(*ptr, size);
+ if(*ptr == NULL) {
+ return NULL;
+ }
+ memset(*ptr,'\0',size);
+ return *ptr;
+}
+
+/****************************************************************************
+ Send the required number of replies back.
+ We assume all fields other than the data fields are
+ set correctly for the type of call.
+ HACK ! Always assumes smb_setup field is zero.
+****************************************************************************/
+
+static void send_nt_replies(connection_struct *conn,
+ struct smb_request *req, NTSTATUS nt_error,
+ char *params, int paramsize,
+ char *pdata, int datasize)
+{
+ int data_to_send = datasize;
+ int params_to_send = paramsize;
+ int useable_space;
+ char *pp = params;
+ char *pd = pdata;
+ int params_sent_thistime, data_sent_thistime, total_sent_thistime;
+ int alignment_offset = 1;
+ int data_alignment_offset = 0;
+ struct smbXsrv_connection *xconn = req->xconn;
+ int max_send = xconn->smb1.sessions.max_send;
+
+ /*
+ * If there genuinely are no parameters or data to send just send
+ * the empty packet.
+ */
+
+ if(params_to_send == 0 && data_to_send == 0) {
+ reply_smb1_outbuf(req, 18, 0);
+ if (NT_STATUS_V(nt_error)) {
+ error_packet_set((char *)req->outbuf,
+ 0, 0, nt_error,
+ __LINE__,__FILE__);
+ }
+ show_msg((char *)req->outbuf);
+ if (!smb1_srv_send(xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(conn))) {
+ exit_server_cleanly("send_nt_replies: smb1_srv_send failed.");
+ }
+ TALLOC_FREE(req->outbuf);
+ return;
+ }
+
+ /*
+ * When sending params and data ensure that both are nicely aligned.
+ * Only do this alignment when there is also data to send - else
+ * can cause NT redirector problems.
+ */
+
+ if (((params_to_send % 4) != 0) && (data_to_send != 0)) {
+ data_alignment_offset = 4 - (params_to_send % 4);
+ }
+
+ /*
+ * Space is bufsize minus Netbios over TCP header minus SMB header.
+ * The alignment_offset is to align the param bytes on a four byte
+ * boundary (2 bytes for data len, one byte pad).
+ * NT needs this to work correctly.
+ */
+
+ useable_space = max_send - (smb_size
+ + 2 * 18 /* wct */
+ + alignment_offset
+ + data_alignment_offset);
+
+ if (useable_space < 0) {
+ char *msg = talloc_asprintf(
+ talloc_tos(),
+ "send_nt_replies failed sanity useable_space = %d!!!",
+ useable_space);
+ DEBUG(0, ("%s\n", msg));
+ exit_server_cleanly(msg);
+ }
+
+ while (params_to_send || data_to_send) {
+
+ /*
+ * Calculate whether we will totally or partially fill this packet.
+ */
+
+ total_sent_thistime = params_to_send + data_to_send;
+
+ /*
+ * We can never send more than useable_space.
+ */
+
+ total_sent_thistime = MIN(total_sent_thistime, useable_space);
+
+ reply_smb1_outbuf(req, 18,
+ total_sent_thistime + alignment_offset
+ + data_alignment_offset);
+
+ /*
+ * Set total params and data to be sent.
+ */
+
+ SIVAL(req->outbuf,smb_ntr_TotalParameterCount,paramsize);
+ SIVAL(req->outbuf,smb_ntr_TotalDataCount,datasize);
+
+ /*
+ * Calculate how many parameters and data we can fit into
+ * this packet. Parameters get precedence.
+ */
+
+ params_sent_thistime = MIN(params_to_send,useable_space);
+ data_sent_thistime = useable_space - params_sent_thistime;
+ data_sent_thistime = MIN(data_sent_thistime,data_to_send);
+
+ SIVAL(req->outbuf, smb_ntr_ParameterCount,
+ params_sent_thistime);
+
+ if(params_sent_thistime == 0) {
+ SIVAL(req->outbuf,smb_ntr_ParameterOffset,0);
+ SIVAL(req->outbuf,smb_ntr_ParameterDisplacement,0);
+ } else {
+ /*
+ * smb_ntr_ParameterOffset is the offset from the start of the SMB header to the
+ * parameter bytes, however the first 4 bytes of outbuf are
+ * the Netbios over TCP header. Thus use smb_base() to subtract
+ * them from the calculation.
+ */
+
+ SIVAL(req->outbuf,smb_ntr_ParameterOffset,
+ ((smb_buf(req->outbuf)+alignment_offset)
+ - smb_base(req->outbuf)));
+ /*
+ * Absolute displacement of param bytes sent in this packet.
+ */
+
+ SIVAL(req->outbuf, smb_ntr_ParameterDisplacement,
+ pp - params);
+ }
+
+ /*
+ * Deal with the data portion.
+ */
+
+ SIVAL(req->outbuf, smb_ntr_DataCount, data_sent_thistime);
+
+ if(data_sent_thistime == 0) {
+ SIVAL(req->outbuf,smb_ntr_DataOffset,0);
+ SIVAL(req->outbuf,smb_ntr_DataDisplacement, 0);
+ } else {
+ /*
+ * The offset of the data bytes is the offset of the
+ * parameter bytes plus the number of parameters being sent this time.
+ */
+
+ SIVAL(req->outbuf, smb_ntr_DataOffset,
+ ((smb_buf(req->outbuf)+alignment_offset) -
+ smb_base(req->outbuf))
+ + params_sent_thistime + data_alignment_offset);
+ SIVAL(req->outbuf,smb_ntr_DataDisplacement, pd - pdata);
+ }
+
+ /*
+ * Copy the param bytes into the packet.
+ */
+
+ if(params_sent_thistime) {
+ if (alignment_offset != 0) {
+ memset(smb_buf(req->outbuf), 0,
+ alignment_offset);
+ }
+ memcpy((smb_buf(req->outbuf)+alignment_offset), pp,
+ params_sent_thistime);
+ }
+
+ /*
+ * Copy in the data bytes
+ */
+
+ if(data_sent_thistime) {
+ if (data_alignment_offset != 0) {
+ memset((smb_buf(req->outbuf)+alignment_offset+
+ params_sent_thistime), 0,
+ data_alignment_offset);
+ }
+ memcpy(smb_buf(req->outbuf)+alignment_offset
+ +params_sent_thistime+data_alignment_offset,
+ pd,data_sent_thistime);
+ }
+
+ DEBUG(9,("nt_rep: params_sent_thistime = %d, data_sent_thistime = %d, useable_space = %d\n",
+ params_sent_thistime, data_sent_thistime, useable_space));
+ DEBUG(9,("nt_rep: params_to_send = %d, data_to_send = %d, paramsize = %d, datasize = %d\n",
+ params_to_send, data_to_send, paramsize, datasize));
+
+ if (NT_STATUS_V(nt_error)) {
+ error_packet_set((char *)req->outbuf,
+ 0, 0, nt_error,
+ __LINE__,__FILE__);
+ }
+
+ /* Send the packet */
+ show_msg((char *)req->outbuf);
+ if (!smb1_srv_send(xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(conn))) {
+ exit_server_cleanly("send_nt_replies: smb1_srv_send failed.");
+ }
+
+ TALLOC_FREE(req->outbuf);
+
+ pp += params_sent_thistime;
+ pd += data_sent_thistime;
+
+ params_to_send -= params_sent_thistime;
+ data_to_send -= data_sent_thistime;
+
+ /*
+ * Sanity check
+ */
+
+ if(params_to_send < 0 || data_to_send < 0) {
+ DEBUG(0,("send_nt_replies failed sanity check pts = %d, dts = %d\n!!!",
+ params_to_send, data_to_send));
+ exit_server_cleanly("send_nt_replies: internal error");
+ }
+ }
+}
+
+/****************************************************************************
+ Reply to an NT create and X call on a pipe
+****************************************************************************/
+
+static void nt_open_pipe(char *fname, connection_struct *conn,
+ struct smb_request *req, uint16_t *ppnum)
+{
+ files_struct *fsp;
+ NTSTATUS status;
+
+ DEBUG(4,("nt_open_pipe: Opening pipe %s.\n", fname));
+
+ /* Strip \\ off the name if present. */
+ while (fname[0] == '\\') {
+ fname++;
+ }
+
+ status = open_np_file(req, fname, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ reply_botherror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND,
+ ERRDOS, ERRbadpipe);
+ return;
+ }
+ reply_nterror(req, status);
+ return;
+ }
+
+ *ppnum = fsp->fnum;
+ return;
+}
+
+/****************************************************************************
+ Reply to an NT create and X call for pipes.
+****************************************************************************/
+
+static void do_ntcreate_pipe_open(connection_struct *conn,
+ struct smb_request *req)
+{
+ char *fname = NULL;
+ uint16_t pnum = FNUM_FIELD_INVALID;
+ char *p = NULL;
+ uint32_t flags = IVAL(req->vwv+3, 1);
+ TALLOC_CTX *ctx = talloc_tos();
+
+ srvstr_pull_req_talloc(ctx, req, &fname, req->buf, STR_TERMINATE);
+
+ if (!fname) {
+ reply_botherror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND,
+ ERRDOS, ERRbadpipe);
+ return;
+ }
+ nt_open_pipe(fname, conn, req, &pnum);
+
+ if (req->outbuf) {
+ /* error reply */
+ return;
+ }
+
+ /*
+ * Deal with pipe return.
+ */
+
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ /* This is very strange. We
+ * return 50 words, but only set
+ * the wcnt to 42 ? It's definitely
+ * what happens on the wire....
+ */
+ reply_smb1_outbuf(req, 50, 0);
+ SCVAL(req->outbuf,smb_wct,42);
+ } else {
+ reply_smb1_outbuf(req, 34, 0);
+ }
+
+ SSVAL(req->outbuf, smb_vwv0, 0xff); /* andx chain ends */
+ SSVAL(req->outbuf, smb_vwv1, 0); /* no andx offset */
+
+ p = (char *)req->outbuf + smb_vwv2;
+ p++;
+ SSVAL(p,0,pnum);
+ p += 2;
+ SIVAL(p,0,FILE_WAS_OPENED);
+ p += 4;
+ p += 32;
+ SIVAL(p,0,FILE_ATTRIBUTE_NORMAL); /* File Attributes. */
+ p += 20;
+ /* File type. */
+ SSVAL(p,0,FILE_TYPE_MESSAGE_MODE_PIPE);
+ /* Device state. */
+ SSVAL(p,2, 0x5FF); /* ? */
+ p += 4;
+
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ p += 25;
+ SIVAL(p,0,FILE_GENERIC_ALL);
+ /*
+ * For pipes W2K3 seems to return
+ * 0x12019B next.
+ * This is ((FILE_GENERIC_READ|FILE_GENERIC_WRITE) & ~FILE_APPEND_DATA)
+ */
+ SIVAL(p,4,(FILE_GENERIC_READ|FILE_GENERIC_WRITE)&~FILE_APPEND_DATA);
+ }
+
+ DEBUG(5,("do_ntcreate_pipe_open: open pipe = %s\n", fname));
+}
+
+struct case_semantics_state {
+ connection_struct *conn;
+ bool case_sensitive;
+ bool case_preserve;
+ bool short_case_preserve;
+};
+
+/****************************************************************************
+ Restore case semantics.
+****************************************************************************/
+
+static int restore_case_semantics(struct case_semantics_state *state)
+{
+ state->conn->case_sensitive = state->case_sensitive;
+ state->conn->case_preserve = state->case_preserve;
+ state->conn->short_case_preserve = state->short_case_preserve;
+ return 0;
+}
+
+/****************************************************************************
+ Save case semantics.
+****************************************************************************/
+
+static struct case_semantics_state *set_posix_case_semantics(TALLOC_CTX *mem_ctx,
+ connection_struct *conn)
+{
+ struct case_semantics_state *result;
+
+ if (!(result = talloc(mem_ctx, struct case_semantics_state))) {
+ return NULL;
+ }
+
+ result->conn = conn;
+ result->case_sensitive = conn->case_sensitive;
+ result->case_preserve = conn->case_preserve;
+ result->short_case_preserve = conn->short_case_preserve;
+
+ /* Set to POSIX. */
+ conn->case_sensitive = True;
+ conn->case_preserve = True;
+ conn->short_case_preserve = True;
+
+ talloc_set_destructor(result, restore_case_semantics);
+
+ return result;
+}
+
+/*
+ * Calculate the full path name given a relative fid.
+ */
+static NTSTATUS get_relative_fid_filename(connection_struct *conn,
+ struct smb_request *req,
+ uint16_t root_dir_fid,
+ char *path,
+ char **path_out)
+{
+ struct files_struct *dir_fsp = NULL;
+ char *new_path = NULL;
+
+ if (root_dir_fid == 0 || path == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ dir_fsp = file_fsp(req, root_dir_fid);
+ if (dir_fsp == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (fsp_is_alternate_stream(dir_fsp)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (!dir_fsp->fsp_flags.is_directory) {
+ /*
+ * Check to see if this is a mac fork of some kind.
+ */
+ if (conn->fs_capabilities & FILE_NAMED_STREAMS) {
+ char *stream = NULL;
+
+ stream = strchr_m(path, ':');
+ if (stream != NULL) {
+ return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ }
+ }
+
+ /*
+ * We need to handle the case when we get a relative open
+ * relative to a file and the pathname is blank - this is a
+ * reopen! (hint from demyn plantenberg)
+ */
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (ISDOT(dir_fsp->fsp_name->base_name)) {
+ /*
+ * We're at the toplevel dir, the final file name
+ * must not contain ./, as this is filtered out
+ * normally by srvstr_get_path and unix_convert
+ * explicitly rejects paths containing ./.
+ */
+ new_path = talloc_strdup(talloc_tos(), path);
+ } else {
+ /*
+ * Copy in the base directory name.
+ */
+
+ new_path = talloc_asprintf(talloc_tos(),
+ "%s/%s",
+ dir_fsp->fsp_name->base_name,
+ path);
+ }
+ if (new_path == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *path_out = new_path;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Reply to an NT create and X call.
+****************************************************************************/
+
+void reply_ntcreate_and_X(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ struct files_struct *dirfsp = NULL;
+ struct smb_filename *smb_fname = NULL;
+ char *fname = NULL;
+ uint32_t flags;
+ uint32_t access_mask;
+ uint32_t file_attributes;
+ uint32_t share_access;
+ uint32_t create_disposition;
+ uint32_t create_options;
+ uint16_t root_dir_fid;
+ uint64_t allocation_size;
+ /* Breakout the oplock request bits so we can set the
+ reply bits separately. */
+ uint32_t fattr=0;
+ off_t file_len = 0;
+ int info = 0;
+ files_struct *fsp = NULL;
+ char *p = NULL;
+ struct timespec create_timespec;
+ struct timespec c_timespec;
+ struct timespec a_timespec;
+ struct timespec m_timespec;
+ NTSTATUS status;
+ int oplock_request;
+ uint8_t oplock_granted = NO_OPLOCK_RETURN;
+ struct case_semantics_state *case_state = NULL;
+ uint32_t ucf_flags;
+ NTTIME twrp = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBntcreateX);
+
+ if (req->wct < 24) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ flags = IVAL(req->vwv+3, 1);
+ access_mask = IVAL(req->vwv+7, 1);
+ file_attributes = IVAL(req->vwv+13, 1);
+ share_access = IVAL(req->vwv+15, 1);
+ create_disposition = IVAL(req->vwv+17, 1);
+ create_options = IVAL(req->vwv+19, 1);
+ root_dir_fid = (uint16_t)IVAL(req->vwv+5, 1);
+
+ allocation_size = BVAL(req->vwv+9, 1);
+
+ srvstr_get_path_req(ctx, req, &fname, (const char *)req->buf,
+ STR_TERMINATE, &status);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ DEBUG(10,("reply_ntcreate_and_X: flags = 0x%x, access_mask = 0x%x "
+ "file_attributes = 0x%x, share_access = 0x%x, "
+ "create_disposition = 0x%x create_options = 0x%x "
+ "root_dir_fid = 0x%x, fname = %s\n",
+ (unsigned int)flags,
+ (unsigned int)access_mask,
+ (unsigned int)file_attributes,
+ (unsigned int)share_access,
+ (unsigned int)create_disposition,
+ (unsigned int)create_options,
+ (unsigned int)root_dir_fid,
+ fname));
+
+ /*
+ * we need to remove ignored bits when they come directly from the client
+ * because we reuse some of them for internal stuff
+ */
+ create_options &= ~NTCREATEX_OPTIONS_MUST_IGNORE_MASK;
+
+ /*
+ * If it's an IPC, use the pipe handler.
+ */
+
+ if (IS_IPC(conn)) {
+ if (lp_nt_pipe_support()) {
+ do_ntcreate_pipe_open(conn, req);
+ goto out;
+ }
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ goto out;
+ }
+
+ oplock_request = (flags & REQUEST_OPLOCK) ? EXCLUSIVE_OPLOCK : 0;
+ if (oplock_request) {
+ oplock_request |= (flags & REQUEST_BATCH_OPLOCK)
+ ? BATCH_OPLOCK : 0;
+ }
+
+ if (file_attributes & FILE_FLAG_POSIX_SEMANTICS) {
+ case_state = set_posix_case_semantics(ctx, conn);
+ if (!case_state) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ }
+
+ if (root_dir_fid != 0) {
+ char *new_fname = NULL;
+
+ status = get_relative_fid_filename(conn,
+ req,
+ root_dir_fid,
+ fname,
+ &new_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ fname = new_fname;
+ }
+
+ ucf_flags = filename_create_ucf_flags(req, create_disposition);
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(fname, &twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = filename_convert_dirfsp(
+ ctx, conn, fname, ucf_flags, twrp, &dirfsp, &smb_fname);
+
+ TALLOC_FREE(case_state);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req,
+ NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ /*
+ * Bug #6898 - clients using Windows opens should
+ * never be able to set this attribute into the
+ * VFS.
+ */
+ file_attributes &= ~FILE_FLAG_POSIX_SEMANTICS;
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_fname, /* fname */
+ access_mask, /* access_mask */
+ share_access, /* share_access */
+ create_disposition, /* create_disposition*/
+ create_options, /* create_options */
+ file_attributes, /* file_attributes */
+ oplock_request, /* oplock_request */
+ NULL, /* lease */
+ allocation_size, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ &info, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->xconn, req->mid)) {
+ /* We have re-scheduled this call, no error. */
+ goto out;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ bool ok = defer_smb1_sharing_violation(req);
+ if (ok) {
+ goto out;
+ }
+ }
+ reply_openerror(req, status);
+ goto out;
+ }
+
+ /* Ensure we're pointing at the correct stat struct. */
+ smb_fname = fsp->fsp_name;
+
+ /*
+ * If the caller set the extended oplock request bit
+ * and we granted one (by whatever means) - set the
+ * correct bit for extended oplock reply.
+ */
+
+ if (oplock_request &&
+ (lp_fake_oplocks(SNUM(conn))
+ || EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type))) {
+
+ /*
+ * Exclusive oplock granted
+ */
+
+ if (flags & REQUEST_BATCH_OPLOCK) {
+ oplock_granted = BATCH_OPLOCK_RETURN;
+ } else {
+ oplock_granted = EXCLUSIVE_OPLOCK_RETURN;
+ }
+ } else if (fsp->oplock_type == LEVEL_II_OPLOCK) {
+ oplock_granted = LEVEL_II_OPLOCK_RETURN;
+ } else {
+ oplock_granted = NO_OPLOCK_RETURN;
+ }
+
+ file_len = smb_fname->st.st_ex_size;
+
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ /* This is very strange. We
+ * return 50 words, but only set
+ * the wcnt to 42 ? It's definitely
+ * what happens on the wire....
+ */
+ reply_smb1_outbuf(req, 50, 0);
+ SCVAL(req->outbuf,smb_wct,42);
+ } else {
+ reply_smb1_outbuf(req, 34, 0);
+ }
+
+ SSVAL(req->outbuf, smb_vwv0, 0xff); /* andx chain ends */
+ SSVAL(req->outbuf, smb_vwv1, 0); /* no andx offset */
+
+ p = (char *)req->outbuf + smb_vwv2;
+
+ SCVAL(p, 0, oplock_granted);
+
+ p++;
+ SSVAL(p,0,fsp->fnum);
+ p += 2;
+ if ((create_disposition == FILE_SUPERSEDE)
+ && (info == FILE_WAS_OVERWRITTEN)) {
+ SIVAL(p,0,FILE_WAS_SUPERSEDED);
+ } else {
+ SIVAL(p,0,info);
+ }
+ p += 4;
+
+ fattr = fdos_mode(fsp);
+ if (fattr == 0) {
+ fattr = FILE_ATTRIBUTE_NORMAL;
+ }
+
+ /* Create time. */
+ create_timespec = get_create_timespec(conn, fsp, smb_fname);
+ a_timespec = smb_fname->st.st_ex_atime;
+ m_timespec = smb_fname->st.st_ex_mtime;
+ c_timespec = get_change_timespec(conn, fsp, smb_fname);
+
+ if (lp_dos_filetime_resolution(SNUM(conn))) {
+ dos_filetime_timespec(&create_timespec);
+ dos_filetime_timespec(&a_timespec);
+ dos_filetime_timespec(&m_timespec);
+ dos_filetime_timespec(&c_timespec);
+ }
+
+ put_long_date_full_timespec(conn->ts_res, p, &create_timespec); /* create time. */
+ p += 8;
+ put_long_date_full_timespec(conn->ts_res, p, &a_timespec); /* access time */
+ p += 8;
+ put_long_date_full_timespec(conn->ts_res, p, &m_timespec); /* write time */
+ p += 8;
+ put_long_date_full_timespec(conn->ts_res, p, &c_timespec); /* change time */
+ p += 8;
+ SIVAL(p,0,fattr); /* File Attributes. */
+ p += 4;
+ SOFF_T(p, 0, SMB_VFS_GET_ALLOC_SIZE(conn,fsp,&smb_fname->st));
+ p += 8;
+ SOFF_T(p,0,file_len);
+ p += 8;
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ uint16_t file_status = (NO_EAS|NO_SUBSTREAMS|NO_REPARSETAG);
+ unsigned int num_streams = 0;
+ struct stream_struct *streams = NULL;
+
+ if (lp_ea_support(SNUM(conn))) {
+ size_t num_names = 0;
+ /* Do we have any EA's ? */
+ status = get_ea_names_from_fsp(
+ ctx, smb_fname->fsp, NULL, &num_names);
+ if (NT_STATUS_IS_OK(status) && num_names) {
+ file_status &= ~NO_EAS;
+ }
+ }
+
+ status = vfs_fstreaminfo(smb_fname->fsp, ctx,
+ &num_streams, &streams);
+ /* There is always one stream, ::$DATA. */
+ if (NT_STATUS_IS_OK(status) && num_streams > 1) {
+ file_status &= ~NO_SUBSTREAMS;
+ }
+ TALLOC_FREE(streams);
+ SSVAL(p,2,file_status);
+ }
+ p += 4;
+ SCVAL(p,0,fsp->fsp_flags.is_directory ? 1 : 0);
+
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ uint32_t perms = 0;
+ p += 25;
+ if (fsp->fsp_flags.is_directory ||
+ fsp->fsp_flags.can_write ||
+ can_write_to_fsp(fsp))
+ {
+ perms = FILE_GENERIC_ALL;
+ } else {
+ perms = FILE_GENERIC_READ|FILE_EXECUTE;
+ }
+ SIVAL(p,0,perms);
+ }
+
+ DEBUG(5,("reply_ntcreate_and_X: %s, open name = %s\n",
+ fsp_fnum_dbg(fsp), smb_fname_str_dbg(smb_fname)));
+
+ out:
+ END_PROFILE(SMBntcreateX);
+ return;
+}
+
+/****************************************************************************
+ Reply to a NT_TRANSACT_CREATE call to open a pipe.
+****************************************************************************/
+
+static void do_nt_transact_create_pipe(connection_struct *conn,
+ struct smb_request *req,
+ uint16_t **ppsetup, uint32_t setup_count,
+ char **ppparams, uint32_t parameter_count,
+ char **ppdata, uint32_t data_count)
+{
+ char *fname = NULL;
+ char *params = *ppparams;
+ uint16_t pnum = FNUM_FIELD_INVALID;
+ char *p = NULL;
+ NTSTATUS status;
+ size_t param_len;
+ uint32_t flags;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ /*
+ * Ensure minimum number of parameters sent.
+ */
+
+ if(parameter_count < 54) {
+ DEBUG(0,("do_nt_transact_create_pipe - insufficient parameters (%u)\n", (unsigned int)parameter_count));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ flags = IVAL(params,0);
+
+ if (req->posix_pathnames) {
+ srvstr_get_path_posix(ctx,
+ params,
+ req->flags2,
+ &fname,
+ params+53,
+ parameter_count-53,
+ STR_TERMINATE,
+ &status);
+ } else {
+ srvstr_get_path(ctx,
+ params,
+ req->flags2,
+ &fname,
+ params+53,
+ parameter_count-53,
+ STR_TERMINATE,
+ &status);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ nt_open_pipe(fname, conn, req, &pnum);
+
+ if (req->outbuf) {
+ /* Error return */
+ return;
+ }
+
+ /* Realloc the size of parameters and data we will return */
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ /* Extended response is 32 more byyes. */
+ param_len = 101;
+ } else {
+ param_len = 69;
+ }
+ params = nttrans_realloc(ppparams, param_len);
+ if(params == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ p = params;
+ SCVAL(p,0,NO_OPLOCK_RETURN);
+
+ p += 2;
+ SSVAL(p,0,pnum);
+ p += 2;
+ SIVAL(p,0,FILE_WAS_OPENED);
+ p += 8;
+
+ p += 32;
+ SIVAL(p,0,FILE_ATTRIBUTE_NORMAL); /* File Attributes. */
+ p += 20;
+ /* File type. */
+ SSVAL(p,0,FILE_TYPE_MESSAGE_MODE_PIPE);
+ /* Device state. */
+ SSVAL(p,2, 0x5FF); /* ? */
+ p += 4;
+
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ p += 25;
+ SIVAL(p,0,FILE_GENERIC_ALL);
+ /*
+ * For pipes W2K3 seems to return
+ * 0x12019B next.
+ * This is ((FILE_GENERIC_READ|FILE_GENERIC_WRITE) & ~FILE_APPEND_DATA)
+ */
+ SIVAL(p,4,(FILE_GENERIC_READ|FILE_GENERIC_WRITE)&~FILE_APPEND_DATA);
+ }
+
+ DEBUG(5,("do_nt_transact_create_pipe: open name = %s\n", fname));
+
+ /* Send the required number of replies */
+ send_nt_replies(conn, req, NT_STATUS_OK, params, param_len, *ppdata, 0);
+
+ return;
+}
+
+/****************************************************************************
+ Reply to a NT_TRANSACT_CREATE call (needs to process SD's).
+****************************************************************************/
+
+static void call_nt_transact_create(connection_struct *conn,
+ struct smb_request *req,
+ uint16_t **ppsetup, uint32_t setup_count,
+ char **ppparams, uint32_t parameter_count,
+ char **ppdata, uint32_t data_count,
+ uint32_t max_data_count)
+{
+ struct smb_filename *smb_fname = NULL;
+ char *fname = NULL;
+ char *params = *ppparams;
+ char *data = *ppdata;
+ /* Breakout the oplock request bits so we can set the reply bits separately. */
+ uint32_t fattr=0;
+ off_t file_len = 0;
+ int info = 0;
+ struct files_struct *dirfsp = NULL;
+ files_struct *fsp = NULL;
+ char *p = NULL;
+ uint32_t flags;
+ uint32_t access_mask;
+ uint32_t file_attributes;
+ uint32_t share_access;
+ uint32_t create_disposition;
+ uint32_t create_options;
+ uint32_t sd_len;
+ struct security_descriptor *sd = NULL;
+ uint32_t ea_len;
+ uint16_t root_dir_fid;
+ struct timespec create_timespec;
+ struct timespec c_timespec;
+ struct timespec a_timespec;
+ struct timespec m_timespec;
+ struct ea_list *ea_list = NULL;
+ NTSTATUS status;
+ size_t param_len;
+ uint64_t allocation_size;
+ int oplock_request;
+ uint8_t oplock_granted;
+ struct case_semantics_state *case_state = NULL;
+ uint32_t ucf_flags;
+ NTTIME twrp = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ DEBUG(5,("call_nt_transact_create\n"));
+
+ /*
+ * If it's an IPC, use the pipe handler.
+ */
+
+ if (IS_IPC(conn)) {
+ if (lp_nt_pipe_support()) {
+ do_nt_transact_create_pipe(
+ conn, req,
+ ppsetup, setup_count,
+ ppparams, parameter_count,
+ ppdata, data_count);
+ goto out;
+ }
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ goto out;
+ }
+
+ /*
+ * Ensure minimum number of parameters sent.
+ */
+
+ if(parameter_count < 54) {
+ DEBUG(0,("call_nt_transact_create - insufficient parameters (%u)\n", (unsigned int)parameter_count));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ flags = IVAL(params,0);
+ access_mask = IVAL(params,8);
+ file_attributes = IVAL(params,20);
+ share_access = IVAL(params,24);
+ create_disposition = IVAL(params,28);
+ create_options = IVAL(params,32);
+ sd_len = IVAL(params,36);
+ ea_len = IVAL(params,40);
+ root_dir_fid = (uint16_t)IVAL(params,4);
+ allocation_size = BVAL(params,12);
+
+ /*
+ * we need to remove ignored bits when they come directly from the client
+ * because we reuse some of them for internal stuff
+ */
+ create_options &= ~NTCREATEX_OPTIONS_MUST_IGNORE_MASK;
+
+ if (req->posix_pathnames) {
+ srvstr_get_path_posix(ctx,
+ params,
+ req->flags2,
+ &fname,
+ params+53,
+ parameter_count-53,
+ STR_TERMINATE,
+ &status);
+ } else {
+ srvstr_get_path(ctx,
+ params,
+ req->flags2,
+ &fname,
+ params+53,
+ parameter_count-53,
+ STR_TERMINATE,
+ &status);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (file_attributes & FILE_FLAG_POSIX_SEMANTICS) {
+ case_state = set_posix_case_semantics(ctx, conn);
+ if (!case_state) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ }
+
+ if (root_dir_fid != 0) {
+ char *new_fname = NULL;
+
+ status = get_relative_fid_filename(conn,
+ req,
+ root_dir_fid,
+ fname,
+ &new_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ fname = new_fname;
+ }
+
+ ucf_flags = filename_create_ucf_flags(req, create_disposition);
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(fname, &twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ fname,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_fname);
+
+ TALLOC_FREE(case_state);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req,
+ NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ /* Ensure the data_len is correct for the sd and ea values given. */
+ if ((ea_len + sd_len > data_count)
+ || (ea_len > data_count) || (sd_len > data_count)
+ || (ea_len + sd_len < ea_len) || (ea_len + sd_len < sd_len)) {
+ DEBUG(10, ("call_nt_transact_create - ea_len = %u, sd_len = "
+ "%u, data_count = %u\n", (unsigned int)ea_len,
+ (unsigned int)sd_len, (unsigned int)data_count));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ if (sd_len) {
+ DEBUG(10, ("call_nt_transact_create - sd_len = %d\n",
+ sd_len));
+
+ status = unmarshall_sec_desc(ctx, (uint8_t *)data, sd_len,
+ &sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("call_nt_transact_create: "
+ "unmarshall_sec_desc failed: %s\n",
+ nt_errstr(status)));
+ reply_nterror(req, status);
+ goto out;
+ }
+ }
+
+ if (ea_len) {
+ if (!lp_ea_support(SNUM(conn))) {
+ DEBUG(10, ("call_nt_transact_create - ea_len = %u but "
+ "EA's not supported.\n",
+ (unsigned int)ea_len));
+ reply_nterror(req, NT_STATUS_EAS_NOT_SUPPORTED);
+ goto out;
+ }
+
+ if (ea_len < 10) {
+ DEBUG(10,("call_nt_transact_create - ea_len = %u - "
+ "too small (should be more than 10)\n",
+ (unsigned int)ea_len ));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ /* We have already checked that ea_len <= data_count here. */
+ ea_list = read_nttrans_ea_list(talloc_tos(), data + sd_len,
+ ea_len);
+ if (ea_list == NULL) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ if (!req->posix_pathnames &&
+ ea_list_has_invalid_name(ea_list)) {
+ /* Realloc the size of parameters and data we will return */
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ /* Extended response is 32 more bytes. */
+ param_len = 101;
+ } else {
+ param_len = 69;
+ }
+ params = nttrans_realloc(ppparams, param_len);
+ if(params == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+
+ memset(params, '\0', param_len);
+ send_nt_replies(conn, req, STATUS_INVALID_EA_NAME,
+ params, param_len, NULL, 0);
+ goto out;
+ }
+ }
+
+ oplock_request = (flags & REQUEST_OPLOCK) ? EXCLUSIVE_OPLOCK : 0;
+ if (oplock_request) {
+ oplock_request |= (flags & REQUEST_BATCH_OPLOCK)
+ ? BATCH_OPLOCK : 0;
+ }
+
+ /*
+ * Bug #6898 - clients using Windows opens should
+ * never be able to set this attribute into the
+ * VFS.
+ */
+ file_attributes &= ~FILE_FLAG_POSIX_SEMANTICS;
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_fname, /* fname */
+ access_mask, /* access_mask */
+ share_access, /* share_access */
+ create_disposition, /* create_disposition*/
+ create_options, /* create_options */
+ file_attributes, /* file_attributes */
+ oplock_request, /* oplock_request */
+ NULL, /* lease */
+ allocation_size, /* allocation_size */
+ 0, /* private_flags */
+ sd, /* sd */
+ ea_list, /* ea_list */
+ &fsp, /* result */
+ &info, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if(!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->xconn, req->mid)) {
+ /* We have re-scheduled this call, no error. */
+ return;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ bool ok = defer_smb1_sharing_violation(req);
+ if (ok) {
+ return;
+ }
+ }
+ reply_openerror(req, status);
+ goto out;
+ }
+
+ /* Ensure we're pointing at the correct stat struct. */
+ TALLOC_FREE(smb_fname);
+ smb_fname = fsp->fsp_name;
+
+ /*
+ * If the caller set the extended oplock request bit
+ * and we granted one (by whatever means) - set the
+ * correct bit for extended oplock reply.
+ */
+
+ if (oplock_request &&
+ (lp_fake_oplocks(SNUM(conn))
+ || EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type))) {
+
+ /*
+ * Exclusive oplock granted
+ */
+
+ if (flags & REQUEST_BATCH_OPLOCK) {
+ oplock_granted = BATCH_OPLOCK_RETURN;
+ } else {
+ oplock_granted = EXCLUSIVE_OPLOCK_RETURN;
+ }
+ } else if (fsp->oplock_type == LEVEL_II_OPLOCK) {
+ oplock_granted = LEVEL_II_OPLOCK_RETURN;
+ } else {
+ oplock_granted = NO_OPLOCK_RETURN;
+ }
+
+ file_len = smb_fname->st.st_ex_size;
+
+ /* Realloc the size of parameters and data we will return */
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ /* Extended response is 32 more byyes. */
+ param_len = 101;
+ } else {
+ param_len = 69;
+ }
+ params = nttrans_realloc(ppparams, param_len);
+ if(params == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+
+ p = params;
+ SCVAL(p, 0, oplock_granted);
+
+ p += 2;
+ SSVAL(p,0,fsp->fnum);
+ p += 2;
+ if ((create_disposition == FILE_SUPERSEDE)
+ && (info == FILE_WAS_OVERWRITTEN)) {
+ SIVAL(p,0,FILE_WAS_SUPERSEDED);
+ } else {
+ SIVAL(p,0,info);
+ }
+ p += 8;
+
+ fattr = fdos_mode(fsp);
+ if (fattr == 0) {
+ fattr = FILE_ATTRIBUTE_NORMAL;
+ }
+
+ /* Create time. */
+ create_timespec = get_create_timespec(conn, fsp, smb_fname);
+ a_timespec = smb_fname->st.st_ex_atime;
+ m_timespec = smb_fname->st.st_ex_mtime;
+ c_timespec = get_change_timespec(conn, fsp, smb_fname);
+
+ if (lp_dos_filetime_resolution(SNUM(conn))) {
+ dos_filetime_timespec(&create_timespec);
+ dos_filetime_timespec(&a_timespec);
+ dos_filetime_timespec(&m_timespec);
+ dos_filetime_timespec(&c_timespec);
+ }
+
+ put_long_date_full_timespec(conn->ts_res, p, &create_timespec); /* create time. */
+ p += 8;
+ put_long_date_full_timespec(conn->ts_res, p, &a_timespec); /* access time */
+ p += 8;
+ put_long_date_full_timespec(conn->ts_res, p, &m_timespec); /* write time */
+ p += 8;
+ put_long_date_full_timespec(conn->ts_res, p, &c_timespec); /* change time */
+ p += 8;
+ SIVAL(p,0,fattr); /* File Attributes. */
+ p += 4;
+ SOFF_T(p, 0, SMB_VFS_GET_ALLOC_SIZE(conn, fsp, &smb_fname->st));
+ p += 8;
+ SOFF_T(p,0,file_len);
+ p += 8;
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ uint16_t file_status = (NO_EAS|NO_SUBSTREAMS|NO_REPARSETAG);
+ unsigned int num_streams = 0;
+ struct stream_struct *streams = NULL;
+
+ if (lp_ea_support(SNUM(conn))) {
+ size_t num_names = 0;
+ /* Do we have any EA's ? */
+ status = get_ea_names_from_fsp(
+ ctx, smb_fname->fsp, NULL, &num_names);
+ if (NT_STATUS_IS_OK(status) && num_names) {
+ file_status &= ~NO_EAS;
+ }
+ }
+
+ status = vfs_fstreaminfo(smb_fname->fsp, ctx,
+ &num_streams, &streams);
+ /* There is always one stream, ::$DATA. */
+ if (NT_STATUS_IS_OK(status) && num_streams > 1) {
+ file_status &= ~NO_SUBSTREAMS;
+ }
+ TALLOC_FREE(streams);
+ SSVAL(p,2,file_status);
+ }
+ p += 4;
+ SCVAL(p,0,fsp->fsp_flags.is_directory ? 1 : 0);
+
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ uint32_t perms = 0;
+ p += 25;
+ if (fsp->fsp_flags.is_directory ||
+ fsp->fsp_flags.can_write ||
+ can_write_to_fsp(fsp))
+ {
+ perms = FILE_GENERIC_ALL;
+ } else {
+ perms = FILE_GENERIC_READ|FILE_EXECUTE;
+ }
+ SIVAL(p,0,perms);
+ }
+
+ DEBUG(5,("call_nt_transact_create: open name = %s\n",
+ smb_fname_str_dbg(smb_fname)));
+
+ /* Send the required number of replies */
+ send_nt_replies(conn, req, NT_STATUS_OK, params, param_len, *ppdata, 0);
+ out:
+ return;
+}
+
+/****************************************************************************
+ Reply to a NT CANCEL request.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_ntcancel(struct smb_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ struct smbd_server_connection *sconn = req->sconn;
+ bool found;
+
+ /*
+ * Go through and cancel any pending change notifies.
+ */
+
+ START_PROFILE(SMBntcancel);
+ smb1_srv_cancel_sign_response(xconn);
+ found = remove_pending_change_notify_requests_by_mid(sconn, req->mid);
+ if (!found) {
+ smbd_smb1_brl_finish_by_mid(sconn, req->mid);
+ }
+
+ DEBUG(3,("reply_ntcancel: cancel called on mid = %llu.\n",
+ (unsigned long long)req->mid));
+
+ END_PROFILE(SMBntcancel);
+ return;
+}
+
+/****************************************************************************
+ Reply to a NT rename request.
+****************************************************************************/
+
+void reply_ntrename(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ struct files_struct *src_dirfsp = NULL;
+ struct smb_filename *smb_fname_old = NULL;
+ struct files_struct *dst_dirfsp = NULL;
+ struct smb_filename *smb_fname_new = NULL;
+ char *oldname = NULL;
+ char *newname = NULL;
+ const char *dst_original_lcomp = NULL;
+ const char *p;
+ NTSTATUS status;
+ uint32_t attrs;
+ uint32_t ucf_flags_src = ucf_flags_from_smb_request(req);
+ NTTIME src_twrp = 0;
+ uint32_t ucf_flags_dst = ucf_flags_from_smb_request(req);
+ NTTIME dst_twrp = 0;
+ uint16_t rename_type;
+ TALLOC_CTX *ctx = talloc_tos();
+ bool stream_rename = false;
+
+ START_PROFILE(SMBntrename);
+
+ if (req->wct < 4) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ attrs = SVAL(req->vwv+0, 0);
+ rename_type = SVAL(req->vwv+1, 0);
+
+ p = (const char *)req->buf + 1;
+ p += srvstr_get_path_req(ctx, req, &oldname, p, STR_TERMINATE,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (!req->posix_pathnames && ms_has_wild(oldname)) {
+ reply_nterror(req, NT_STATUS_OBJECT_PATH_SYNTAX_BAD);
+ goto out;
+ }
+
+ p++;
+ p += srvstr_get_path_req(ctx, req, &newname, p, STR_TERMINATE,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (!req->posix_pathnames && ms_has_wild(newname)) {
+ reply_nterror(req, NT_STATUS_OBJECT_PATH_SYNTAX_BAD);
+ goto out;
+ }
+
+ if (!req->posix_pathnames) {
+ /* The newname must begin with a ':' if the
+ oldname contains a ':'. */
+ if (strchr_m(oldname, ':')) {
+ if (newname[0] != ':') {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+ stream_rename = true;
+ }
+ }
+
+ if (ucf_flags_src & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(oldname, &src_twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags_src, &oldname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ oldname,
+ ucf_flags_src,
+ src_twrp,
+ &src_dirfsp,
+ &smb_fname_old);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,
+ NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req,
+ NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (stream_rename) {
+ /*
+ * No point in calling filename_convert()
+ * on a raw stream name. It can never find
+ * the file anyway. Use the same logic as
+ * SMB2_FILE_RENAME_INFORMATION_INTERNAL
+ * and generate smb_fname_new directly.
+ */
+ smb_fname_new = synthetic_smb_fname(talloc_tos(),
+ smb_fname_old->base_name,
+ newname,
+ NULL,
+ smb_fname_old->twrp,
+ smb_fname_old->flags);
+ if (smb_fname_new == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ } else {
+ if (ucf_flags_dst & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(newname,
+ &dst_twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags_dst, &newname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ newname,
+ ucf_flags_dst,
+ dst_twrp,
+ &dst_dirfsp,
+ &smb_fname_new);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,
+ NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req,
+ NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+ }
+
+ /* Get the last component of the destination for rename_internals(). */
+ dst_original_lcomp = get_original_lcomp(ctx,
+ conn,
+ newname,
+ ucf_flags_dst);
+ if (dst_original_lcomp == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+
+
+ DEBUG(3,("reply_ntrename: %s -> %s\n",
+ smb_fname_str_dbg(smb_fname_old),
+ smb_fname_str_dbg(smb_fname_new)));
+
+ switch(rename_type) {
+ case RENAME_FLAG_RENAME:
+ status = rename_internals(ctx,
+ conn,
+ req,
+ src_dirfsp,
+ smb_fname_old,
+ smb_fname_new,
+ dst_original_lcomp,
+ attrs,
+ false,
+ DELETE_ACCESS);
+ break;
+ case RENAME_FLAG_HARD_LINK:
+ status = hardlink_internals(ctx,
+ conn,
+ req,
+ false,
+ smb_fname_old,
+ smb_fname_new);
+ break;
+ case RENAME_FLAG_COPY:
+ status = copy_internals(ctx,
+ conn,
+ req,
+ src_dirfsp,
+ smb_fname_old,
+ dst_dirfsp,
+ smb_fname_new,
+ attrs);
+ break;
+ case RENAME_FLAG_MOVE_CLUSTER_INFORMATION:
+ status = NT_STATUS_INVALID_PARAMETER;
+ break;
+ default:
+ status = NT_STATUS_ACCESS_DENIED; /* Default error. */
+ break;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->xconn, req->mid)) {
+ /* We have re-scheduled this call. */
+ goto out;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ bool ok = defer_smb1_sharing_violation(req);
+ if (ok) {
+ goto out;
+ }
+ }
+
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ reply_smb1_outbuf(req, 0, 0);
+ out:
+ END_PROFILE(SMBntrename);
+ return;
+}
+
+/****************************************************************************
+ Reply to a notify change - queue the request and
+ don't allow a directory to be opened.
+****************************************************************************/
+
+static void smbd_smb1_notify_reply(struct smb_request *req,
+ NTSTATUS error_code,
+ uint8_t *buf, size_t len)
+{
+ send_nt_replies(req->conn, req, error_code, (char *)buf, len, NULL, 0);
+}
+
+static void call_nt_transact_notify_change(connection_struct *conn,
+ struct smb_request *req,
+ uint16_t **ppsetup,
+ uint32_t setup_count,
+ char **ppparams,
+ uint32_t parameter_count,
+ char **ppdata, uint32_t data_count,
+ uint32_t max_data_count,
+ uint32_t max_param_count)
+{
+ uint16_t *setup = *ppsetup;
+ files_struct *fsp;
+ uint32_t filter;
+ NTSTATUS status;
+ bool recursive;
+
+ if(setup_count < 6) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(setup,4));
+ filter = IVAL(setup, 0);
+ recursive = (SVAL(setup, 6) != 0) ? True : False;
+
+ DEBUG(3,("call_nt_transact_notify_change\n"));
+
+ if(!fsp) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ {
+ char *filter_string;
+
+ if (!(filter_string = notify_filter_string(NULL, filter))) {
+ reply_nterror(req,NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ DEBUG(3,("call_nt_transact_notify_change: notify change "
+ "called on %s, filter = %s, recursive = %d\n",
+ fsp_str_dbg(fsp), filter_string, recursive));
+
+ TALLOC_FREE(filter_string);
+ }
+
+ if((!fsp->fsp_flags.is_directory) || (conn != fsp->conn)) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if (fsp->notify == NULL) {
+
+ status = change_notify_create(fsp,
+ max_param_count,
+ filter,
+ recursive);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("change_notify_create returned %s\n",
+ nt_errstr(status)));
+ reply_nterror(req, status);
+ return;
+ }
+ }
+
+ if (change_notify_fsp_has_changes(fsp)) {
+
+ /*
+ * We've got changes pending, respond immediately
+ */
+
+ /*
+ * TODO: write a torture test to check the filtering behaviour
+ * here.
+ */
+
+ change_notify_reply(req,
+ NT_STATUS_OK,
+ max_param_count,
+ fsp->notify,
+ smbd_smb1_notify_reply);
+
+ /*
+ * change_notify_reply() above has independently sent its
+ * results
+ */
+ return;
+ }
+
+ /*
+ * No changes pending, queue the request
+ */
+
+ status = change_notify_add_request(req,
+ max_param_count,
+ filter,
+ recursive, fsp,
+ smbd_smb1_notify_reply);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ }
+ return;
+}
+
+/****************************************************************************
+ Reply to an NT transact rename command.
+****************************************************************************/
+
+static void call_nt_transact_rename(connection_struct *conn,
+ struct smb_request *req,
+ uint16_t **ppsetup, uint32_t setup_count,
+ char **ppparams, uint32_t parameter_count,
+ char **ppdata, uint32_t data_count,
+ uint32_t max_data_count)
+{
+ char *params = *ppparams;
+ char *new_name = NULL;
+ files_struct *fsp = NULL;
+ NTSTATUS status;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ if(parameter_count < 5) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(params, 0));
+ if (!check_fsp(conn, req, fsp)) {
+ return;
+ }
+ if (req->posix_pathnames) {
+ srvstr_get_path_posix(ctx,
+ params,
+ req->flags2,
+ &new_name,
+ params+4,
+ parameter_count - 4,
+ STR_TERMINATE,
+ &status);
+ } else {
+ srvstr_get_path(ctx,
+ params,
+ req->flags2,
+ &new_name,
+ params+4,
+ parameter_count - 4,
+ STR_TERMINATE,
+ &status);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ /*
+ * W2K3 ignores this request as the RAW-RENAME test
+ * demonstrates, so we do.
+ */
+ send_nt_replies(conn, req, NT_STATUS_OK, NULL, 0, NULL, 0);
+
+ DEBUG(3,("nt transact rename from = %s, to = %s ignored!\n",
+ fsp_str_dbg(fsp), new_name));
+
+ return;
+}
+
+/****************************************************************************
+ SMB1 reply to query a security descriptor.
+****************************************************************************/
+
+static void call_nt_transact_query_security_desc(connection_struct *conn,
+ struct smb_request *req,
+ uint16_t **ppsetup,
+ uint32_t setup_count,
+ char **ppparams,
+ uint32_t parameter_count,
+ char **ppdata,
+ uint32_t data_count,
+ uint32_t max_data_count)
+{
+ char *params = *ppparams;
+ char *data = *ppdata;
+ size_t sd_size = 0;
+ uint32_t security_info_wanted;
+ files_struct *fsp = NULL;
+ NTSTATUS status;
+ uint8_t *marshalled_sd = NULL;
+
+ if(parameter_count < 8) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(params,0));
+ if(!fsp) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ security_info_wanted = IVAL(params,4);
+
+ DEBUG(3,("call_nt_transact_query_security_desc: file = %s, "
+ "info_wanted = 0x%x\n", fsp_str_dbg(fsp),
+ (unsigned int)security_info_wanted));
+
+ params = nttrans_realloc(ppparams, 4);
+ if(params == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ /*
+ * Get the permissions to return.
+ */
+
+ status = smbd_do_query_security_desc(conn,
+ talloc_tos(),
+ fsp,
+ security_info_wanted &
+ SMB_SUPPORTED_SECINFO_FLAGS,
+ max_data_count,
+ &marshalled_sd,
+ &sd_size);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_BUFFER_TOO_SMALL)) {
+ SIVAL(params,0,(uint32_t)sd_size);
+ send_nt_replies(conn, req, NT_STATUS_BUFFER_TOO_SMALL,
+ params, 4, NULL, 0);
+ return;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ SMB_ASSERT(sd_size > 0);
+
+ SIVAL(params,0,(uint32_t)sd_size);
+
+ if (max_data_count < sd_size) {
+ send_nt_replies(conn, req, NT_STATUS_BUFFER_TOO_SMALL,
+ params, 4, NULL, 0);
+ return;
+ }
+
+ /*
+ * Allocate the data we will return.
+ */
+
+ data = nttrans_realloc(ppdata, sd_size);
+ if(data == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ memcpy(data, marshalled_sd, sd_size);
+
+ send_nt_replies(conn, req, NT_STATUS_OK, params, 4, data, (int)sd_size);
+
+ return;
+}
+
+/****************************************************************************
+ Reply to set a security descriptor. Map to UNIX perms or POSIX ACLs.
+****************************************************************************/
+
+static void call_nt_transact_set_security_desc(connection_struct *conn,
+ struct smb_request *req,
+ uint16_t **ppsetup,
+ uint32_t setup_count,
+ char **ppparams,
+ uint32_t parameter_count,
+ char **ppdata,
+ uint32_t data_count,
+ uint32_t max_data_count)
+{
+ char *params= *ppparams;
+ char *data = *ppdata;
+ files_struct *fsp = NULL;
+ uint32_t security_info_sent = 0;
+ NTSTATUS status;
+
+ if(parameter_count < 8) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if((fsp = file_fsp(req, SVAL(params,0))) == NULL) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ if (!CAN_WRITE(fsp->conn)) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+
+ if(!lp_nt_acl_support(SNUM(conn))) {
+ goto done;
+ }
+
+ security_info_sent = IVAL(params,4);
+
+ DEBUG(3,("call_nt_transact_set_security_desc: file = %s, sent 0x%x\n",
+ fsp_str_dbg(fsp), (unsigned int)security_info_sent));
+
+ if (data_count == 0) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ status = set_sd_blob(fsp, (uint8_t *)data, data_count,
+ security_info_sent & SMB_SUPPORTED_SECINFO_FLAGS);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ done:
+ send_nt_replies(conn, req, NT_STATUS_OK, NULL, 0, NULL, 0);
+ return;
+}
+
+/****************************************************************************
+ Reply to NT IOCTL
+****************************************************************************/
+
+static void call_nt_transact_ioctl(connection_struct *conn,
+ struct smb_request *req,
+ uint16_t **ppsetup, uint32_t setup_count,
+ char **ppparams, uint32_t parameter_count,
+ char **ppdata, uint32_t data_count,
+ uint32_t max_data_count)
+{
+ NTSTATUS status;
+ uint32_t function;
+ uint16_t fidnum;
+ files_struct *fsp;
+ uint8_t isFSctl;
+ uint8_t compfilter;
+ char *out_data = NULL;
+ uint32_t out_data_len = 0;
+ char *pdata = *ppdata;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ if (setup_count != 8) {
+ DEBUG(3,("call_nt_transact_ioctl: invalid setup count %d\n", setup_count));
+ reply_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return;
+ }
+
+ function = IVAL(*ppsetup, 0);
+ fidnum = SVAL(*ppsetup, 4);
+ isFSctl = CVAL(*ppsetup, 6);
+ compfilter = CVAL(*ppsetup, 7);
+
+ DEBUG(10, ("call_nt_transact_ioctl: function[0x%08X] FID[0x%04X] isFSctl[0x%02X] compfilter[0x%02X]\n",
+ function, fidnum, isFSctl, compfilter));
+
+ fsp=file_fsp(req, fidnum);
+
+ /*
+ * We don't really implement IOCTLs, especially on files.
+ */
+ if (!isFSctl) {
+ DEBUG(10, ("isFSctl: 0x%02X indicates IOCTL, not FSCTL!\n",
+ isFSctl));
+ reply_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return;
+ }
+
+ /* Has to be for an open file! */
+ if (!check_fsp_open(conn, req, fsp)) {
+ return;
+ }
+
+ /*
+ * out_data might be allocated by the VFS module, but talloc should be
+ * used, and should be cleaned up when the request ends.
+ */
+ status = SMB_VFS_FSCTL(fsp,
+ ctx,
+ function,
+ req->flags2,
+ (uint8_t *)pdata,
+ data_count,
+ (uint8_t **)&out_data,
+ max_data_count,
+ &out_data_len);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ } else {
+ send_nt_replies(conn, req, NT_STATUS_OK, NULL, 0, out_data, out_data_len);
+ }
+}
+
+
+#ifdef HAVE_SYS_QUOTAS
+/****************************************************************************
+ Reply to get user quota
+****************************************************************************/
+
+static void call_nt_transact_get_user_quota(connection_struct *conn,
+ struct smb_request *req,
+ uint16_t **ppsetup,
+ uint32_t setup_count,
+ char **ppparams,
+ uint32_t parameter_count,
+ char **ppdata,
+ uint32_t data_count,
+ uint32_t max_data_count)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ NTSTATUS nt_status = NT_STATUS_OK;
+ char *params = *ppparams;
+ char *pdata = *ppdata;
+ int data_len = 0;
+ int param_len = 0;
+ files_struct *fsp = NULL;
+ DATA_BLOB blob = data_blob_null;
+ struct nttrans_query_quota_params info = {0};
+ enum ndr_err_code err;
+ TALLOC_CTX *tmp_ctx = NULL;
+ uint32_t resp_len = 0;
+ uint8_t *resp_data = 0;
+
+ tmp_ctx = talloc_init("ntquota_list");
+ if (!tmp_ctx) {
+ nt_status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+
+ /* access check */
+ if (get_current_uid(conn) != sec_initial_uid()) {
+ DEBUG(1,("get_user_quota: access_denied service [%s] user "
+ "[%s]\n", lp_servicename(talloc_tos(), lp_sub, SNUM(conn)),
+ conn->session_info->unix_info->unix_name));
+ nt_status = NT_STATUS_ACCESS_DENIED;
+ goto error;
+ }
+
+ blob.data = (uint8_t*)params;
+ blob.length = parameter_count;
+
+ err = ndr_pull_struct_blob(&blob, tmp_ctx, &info,
+ (ndr_pull_flags_fn_t)ndr_pull_nttrans_query_quota_params);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+ DEBUG(0,("TRANSACT_GET_USER_QUOTA: failed to pull "
+ "query_quota_params.\n"));
+ nt_status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ }
+ DBG_DEBUG("info.return_single_entry = %u, info.restart_scan = %u, "
+ "info.sid_list_length = %u, info.start_sid_length = %u, "
+ "info.start_sid_offset = %u\n",
+ (unsigned int)info.return_single_entry,
+ (unsigned int)info.restart_scan,
+ (unsigned int)info.sid_list_length,
+ (unsigned int)info.start_sid_length,
+ (unsigned int)info.start_sid_offset);
+
+ /* set blob to point at data for further parsing */
+ blob.data = (uint8_t*)pdata;
+ blob.length = data_count;
+ /*
+ * Although MS-SMB ref is ambiguous here, a microsoft client will
+ * only ever send a start sid (as part of a list) with
+ * sid_list_length & start_sid_offset both set to the actual list
+ * length. Note: Only a single result is returned in this case
+ * In the case where either start_sid_offset or start_sid_length
+ * are set alone or if both set (but have different values) then
+ * it seems windows will return a number of entries from the start
+ * of the list of users with quotas set. This behaviour is undocumented
+ * and windows clients do not send messages of that type. As such we
+ * currently will reject these requests.
+ */
+ if (info.start_sid_length
+ || (info.sid_list_length != info.start_sid_offset)) {
+ DBG_ERR("TRANSACT_GET_USER_QUOTA: unsupported single or "
+ "compound sid format\n");
+ nt_status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ }
+
+ /* maybe we can check the quota_fnum */
+ fsp = file_fsp(req, info.fid);
+ if (!check_fsp_ntquota_handle(conn, req, fsp)) {
+ DEBUG(3,("TRANSACT_GET_USER_QUOTA: no valid QUOTA HANDLE\n"));
+ nt_status = NT_STATUS_INVALID_HANDLE;
+ goto error;
+ }
+ nt_status = smbd_do_query_getinfo_quota(tmp_ctx,
+ fsp,
+ info.restart_scan,
+ info.return_single_entry,
+ info.sid_list_length,
+ &blob,
+ max_data_count,
+ &resp_data,
+ &resp_len);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_MORE_ENTRIES)) {
+ goto error;
+ }
+ nt_status = NT_STATUS_OK;
+ }
+
+ param_len = 4;
+ params = nttrans_realloc(ppparams, param_len);
+ if(params == NULL) {
+ nt_status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+
+ data_len = resp_len;
+ SIVAL(params, 0, data_len);
+ pdata = nttrans_realloc(ppdata, data_len);
+ memcpy(pdata, resp_data, data_len);
+
+ TALLOC_FREE(tmp_ctx);
+ send_nt_replies(conn, req, nt_status, params, param_len,
+ pdata, data_len);
+ return;
+error:
+ TALLOC_FREE(tmp_ctx);
+ reply_nterror(req, nt_status);
+}
+
+/****************************************************************************
+ Reply to set user quota
+****************************************************************************/
+
+static void call_nt_transact_set_user_quota(connection_struct *conn,
+ struct smb_request *req,
+ uint16_t **ppsetup,
+ uint32_t setup_count,
+ char **ppparams,
+ uint32_t parameter_count,
+ char **ppdata,
+ uint32_t data_count,
+ uint32_t max_data_count)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *params = *ppparams;
+ char *pdata = *ppdata;
+ int data_len=0,param_len=0;
+ SMB_NTQUOTA_STRUCT qt;
+ struct file_quota_information info = {0};
+ enum ndr_err_code err;
+ struct dom_sid sid;
+ DATA_BLOB inblob;
+ files_struct *fsp = NULL;
+ TALLOC_CTX *ctx = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+ ZERO_STRUCT(qt);
+
+ /* access check */
+ if (get_current_uid(conn) != sec_initial_uid()) {
+ DEBUG(1,("set_user_quota: access_denied service [%s] user "
+ "[%s]\n", lp_servicename(talloc_tos(), lp_sub, SNUM(conn)),
+ conn->session_info->unix_info->unix_name));
+ status = NT_STATUS_ACCESS_DENIED;
+ goto error;
+ }
+
+ /*
+ * Ensure minimum number of parameters sent.
+ */
+
+ if (parameter_count < 2) {
+ DEBUG(0,("TRANSACT_SET_USER_QUOTA: requires %d >= 2 bytes parameters\n",parameter_count));
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ }
+
+ /* maybe we can check the quota_fnum */
+ fsp = file_fsp(req, SVAL(params,0));
+ if (!check_fsp_ntquota_handle(conn, req, fsp)) {
+ DEBUG(3,("TRANSACT_GET_USER_QUOTA: no valid QUOTA HANDLE\n"));
+ status = NT_STATUS_INVALID_HANDLE;
+ goto error;
+ }
+
+ ctx = talloc_init("set_user_quota");
+ if (!ctx) {
+ status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+ inblob.data = (uint8_t*)pdata;
+ inblob.length = data_count;
+
+ err = ndr_pull_struct_blob(
+ &inblob,
+ ctx,
+ &info,
+ (ndr_pull_flags_fn_t)ndr_pull_file_quota_information);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+ DEBUG(0,("TRANSACT_SET_USER_QUOTA: failed to pull "
+ "file_quota_information\n"));
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ }
+ qt.usedspace = info.quota_used;
+
+ qt.softlim = info.quota_threshold;
+
+ qt.hardlim = info.quota_limit;
+
+ sid = info.sid;
+
+ if (vfs_set_ntquota(fsp, SMB_USER_QUOTA_TYPE, &sid, &qt)!=0) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto error;
+ }
+
+ send_nt_replies(conn, req, NT_STATUS_OK, params, param_len,
+ pdata, data_len);
+ TALLOC_FREE(ctx);
+ return;
+error:
+ TALLOC_FREE(ctx);
+ reply_nterror(req, status);
+}
+#endif /* HAVE_SYS_QUOTAS */
+
+static void handle_nttrans(connection_struct *conn,
+ struct trans_state *state,
+ struct smb_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+
+ if (xconn->protocol >= PROTOCOL_NT1) {
+ req->flags2 |= 0x40; /* IS_LONG_NAME */
+ SSVAL(discard_const_p(uint8_t, req->inbuf),smb_flg2,req->flags2);
+ }
+
+
+ /* Now we must call the relevant NT_TRANS function */
+ switch(state->call) {
+ case NT_TRANSACT_CREATE:
+ {
+ START_PROFILE(NT_transact_create);
+ call_nt_transact_create(
+ conn, req,
+ &state->setup, state->setup_count,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(NT_transact_create);
+ break;
+ }
+
+ case NT_TRANSACT_IOCTL:
+ {
+ START_PROFILE(NT_transact_ioctl);
+ call_nt_transact_ioctl(
+ conn, req,
+ &state->setup, state->setup_count,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(NT_transact_ioctl);
+ break;
+ }
+
+ case NT_TRANSACT_SET_SECURITY_DESC:
+ {
+ START_PROFILE(NT_transact_set_security_desc);
+ call_nt_transact_set_security_desc(
+ conn, req,
+ &state->setup, state->setup_count,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(NT_transact_set_security_desc);
+ break;
+ }
+
+ case NT_TRANSACT_NOTIFY_CHANGE:
+ {
+ START_PROFILE(NT_transact_notify_change);
+ call_nt_transact_notify_change(
+ conn, req,
+ &state->setup, state->setup_count,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return,
+ state->max_param_return);
+ END_PROFILE(NT_transact_notify_change);
+ break;
+ }
+
+ case NT_TRANSACT_RENAME:
+ {
+ START_PROFILE(NT_transact_rename);
+ call_nt_transact_rename(
+ conn, req,
+ &state->setup, state->setup_count,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(NT_transact_rename);
+ break;
+ }
+
+ case NT_TRANSACT_QUERY_SECURITY_DESC:
+ {
+ START_PROFILE(NT_transact_query_security_desc);
+ call_nt_transact_query_security_desc(
+ conn, req,
+ &state->setup, state->setup_count,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(NT_transact_query_security_desc);
+ break;
+ }
+
+#ifdef HAVE_SYS_QUOTAS
+ case NT_TRANSACT_GET_USER_QUOTA:
+ {
+ START_PROFILE(NT_transact_get_user_quota);
+ call_nt_transact_get_user_quota(
+ conn, req,
+ &state->setup, state->setup_count,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(NT_transact_get_user_quota);
+ break;
+ }
+
+ case NT_TRANSACT_SET_USER_QUOTA:
+ {
+ START_PROFILE(NT_transact_set_user_quota);
+ call_nt_transact_set_user_quota(
+ conn, req,
+ &state->setup, state->setup_count,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(NT_transact_set_user_quota);
+ break;
+ }
+#endif /* HAVE_SYS_QUOTAS */
+
+ default:
+ /* Error in request */
+ DEBUG(0,("handle_nttrans: Unknown request %d in "
+ "nttrans call\n", state->call));
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+ return;
+}
+
+/****************************************************************************
+ Reply to a SMBNTtrans.
+****************************************************************************/
+
+void reply_nttrans(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ uint32_t pscnt;
+ uint32_t psoff;
+ uint32_t dscnt;
+ uint32_t dsoff;
+ uint16_t function_code;
+ NTSTATUS result;
+ struct trans_state *state;
+
+ START_PROFILE(SMBnttrans);
+
+ if (req->wct < 19) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBnttrans);
+ return;
+ }
+
+ pscnt = IVAL(req->vwv+9, 1);
+ psoff = IVAL(req->vwv+11, 1);
+ dscnt = IVAL(req->vwv+13, 1);
+ dsoff = IVAL(req->vwv+15, 1);
+ function_code = SVAL(req->vwv+18, 0);
+
+ if (IS_IPC(conn) && (function_code != NT_TRANSACT_CREATE)) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ END_PROFILE(SMBnttrans);
+ return;
+ }
+
+ result = allow_new_trans(conn->pending_trans, req->mid);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(2, ("Got invalid nttrans request: %s\n", nt_errstr(result)));
+ reply_nterror(req, result);
+ END_PROFILE(SMBnttrans);
+ return;
+ }
+
+ if ((state = talloc(conn, struct trans_state)) == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBnttrans);
+ return;
+ }
+
+ state->cmd = SMBnttrans;
+
+ state->mid = req->mid;
+ state->vuid = req->vuid;
+ state->total_data = IVAL(req->vwv+3, 1);
+ state->data = NULL;
+ state->total_param = IVAL(req->vwv+1, 1);
+ state->param = NULL;
+ state->max_data_return = IVAL(req->vwv+7, 1);
+ state->max_param_return = IVAL(req->vwv+5, 1);
+
+ /* setup count is in *words* */
+ state->setup_count = 2*CVAL(req->vwv+17, 1);
+ state->setup = NULL;
+ state->call = function_code;
+
+ DEBUG(10, ("num_setup=%u, "
+ "param_total=%u, this_param=%u, max_param=%u, "
+ "data_total=%u, this_data=%u, max_data=%u, "
+ "param_offset=%u, data_offset=%u\n",
+ (unsigned)state->setup_count,
+ (unsigned)state->total_param, (unsigned)pscnt,
+ (unsigned)state->max_param_return,
+ (unsigned)state->total_data, (unsigned)dscnt,
+ (unsigned)state->max_data_return,
+ (unsigned)psoff, (unsigned)dsoff));
+
+ /*
+ * All nttrans messages we handle have smb_wct == 19 +
+ * state->setup_count. Ensure this is so as a sanity check.
+ */
+
+ if(req->wct != 19 + (state->setup_count/2)) {
+ DEBUG(2,("Invalid smb_wct %d in nttrans call (should be %d)\n",
+ req->wct, 19 + (state->setup_count/2)));
+ goto bad_param;
+ }
+
+ /* Don't allow more than 128mb for each value. */
+ if ((state->total_data > (1024*1024*128)) ||
+ (state->total_param > (1024*1024*128))) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBnttrans);
+ return;
+ }
+
+ if ((dscnt > state->total_data) || (pscnt > state->total_param))
+ goto bad_param;
+
+ if (state->total_data) {
+
+ if (smb_buffer_oob(state->total_data, 0, dscnt)
+ || smb_buffer_oob(smb_len(req->inbuf), dsoff, dscnt)) {
+ goto bad_param;
+ }
+
+ /* Can't use talloc here, the core routines do realloc on the
+ * params and data. */
+ if ((state->data = (char *)SMB_MALLOC(state->total_data)) == NULL) {
+ DEBUG(0,("reply_nttrans: data malloc fail for %u "
+ "bytes !\n", (unsigned int)state->total_data));
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBnttrans);
+ return;
+ }
+
+ memcpy(state->data,smb_base(req->inbuf)+dsoff,dscnt);
+ }
+
+ if (state->total_param) {
+
+ if (smb_buffer_oob(state->total_param, 0, pscnt)
+ || smb_buffer_oob(smb_len(req->inbuf), psoff, pscnt)) {
+ goto bad_param;
+ }
+
+ /* Can't use talloc here, the core routines do realloc on the
+ * params and data. */
+ if ((state->param = (char *)SMB_MALLOC(state->total_param)) == NULL) {
+ DEBUG(0,("reply_nttrans: param malloc fail for %u "
+ "bytes !\n", (unsigned int)state->total_param));
+ SAFE_FREE(state->data);
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBnttrans);
+ return;
+ }
+
+ memcpy(state->param,smb_base(req->inbuf)+psoff,pscnt);
+ }
+
+ state->received_data = dscnt;
+ state->received_param = pscnt;
+
+ if(state->setup_count > 0) {
+ DEBUG(10,("reply_nttrans: state->setup_count = %d\n",
+ state->setup_count));
+
+ /*
+ * No overflow possible here, state->setup_count is an
+ * unsigned int, being filled by a single byte from
+ * CVAL(req->vwv+13, 0) above. The cast in the comparison
+ * below is not necessary, it's here to clarify things. The
+ * validity of req->vwv and req->wct has been checked in
+ * init_smb1_request already.
+ */
+ if ((state->setup_count/2) + 19 > (unsigned int)req->wct) {
+ goto bad_param;
+ }
+
+ state->setup = (uint16_t *)TALLOC(state, state->setup_count);
+ if (state->setup == NULL) {
+ DEBUG(0,("reply_nttrans : Out of memory\n"));
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBnttrans);
+ return;
+ }
+
+ memcpy(state->setup, req->vwv+19, state->setup_count);
+ dump_data(10, (uint8_t *)state->setup, state->setup_count);
+ }
+
+ if ((state->received_data == state->total_data) &&
+ (state->received_param == state->total_param)) {
+ handle_nttrans(conn, state, req);
+ SAFE_FREE(state->param);
+ SAFE_FREE(state->data);
+ TALLOC_FREE(state);
+ END_PROFILE(SMBnttrans);
+ return;
+ }
+
+ DLIST_ADD(conn->pending_trans, state);
+
+ /* We need to send an interim response then receive the rest
+ of the parameter/data bytes */
+ reply_smb1_outbuf(req, 0, 0);
+ show_msg((char *)req->outbuf);
+ END_PROFILE(SMBnttrans);
+ return;
+
+ bad_param:
+
+ DEBUG(0,("reply_nttrans: invalid trans parameters\n"));
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBnttrans);
+ return;
+}
+
+/****************************************************************************
+ Reply to a SMBnttranss
+ ****************************************************************************/
+
+void reply_nttranss(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ uint32_t pcnt,poff,dcnt,doff,pdisp,ddisp;
+ struct trans_state *state;
+
+ START_PROFILE(SMBnttranss);
+
+ show_msg((const char *)req->inbuf);
+
+ /* Windows clients expect all replies to
+ an NT transact secondary (SMBnttranss 0xA1)
+ to have a command code of NT transact
+ (SMBnttrans 0xA0). See bug #8989 for details. */
+ req->cmd = SMBnttrans;
+
+ if (req->wct < 18) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBnttranss);
+ return;
+ }
+
+ for (state = conn->pending_trans; state != NULL;
+ state = state->next) {
+ if (state->mid == req->mid) {
+ break;
+ }
+ }
+
+ if ((state == NULL) || (state->cmd != SMBnttrans)) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBnttranss);
+ return;
+ }
+
+ /* Revise state->total_param and state->total_data in case they have
+ changed downwards */
+ if (IVAL(req->vwv+1, 1) < state->total_param) {
+ state->total_param = IVAL(req->vwv+1, 1);
+ }
+ if (IVAL(req->vwv+3, 1) < state->total_data) {
+ state->total_data = IVAL(req->vwv+3, 1);
+ }
+
+ pcnt = IVAL(req->vwv+5, 1);
+ poff = IVAL(req->vwv+7, 1);
+ pdisp = IVAL(req->vwv+9, 1);
+
+ dcnt = IVAL(req->vwv+11, 1);
+ doff = IVAL(req->vwv+13, 1);
+ ddisp = IVAL(req->vwv+15, 1);
+
+ state->received_param += pcnt;
+ state->received_data += dcnt;
+
+ if ((state->received_data > state->total_data) ||
+ (state->received_param > state->total_param))
+ goto bad_param;
+
+ if (pcnt) {
+ if (smb_buffer_oob(state->total_param, pdisp, pcnt)
+ || smb_buffer_oob(smb_len(req->inbuf), poff, pcnt)) {
+ goto bad_param;
+ }
+ memcpy(state->param+pdisp, smb_base(req->inbuf)+poff,pcnt);
+ }
+
+ if (dcnt) {
+ if (smb_buffer_oob(state->total_data, ddisp, dcnt)
+ || smb_buffer_oob(smb_len(req->inbuf), doff, dcnt)) {
+ goto bad_param;
+ }
+ memcpy(state->data+ddisp, smb_base(req->inbuf)+doff,dcnt);
+ }
+
+ if ((state->received_param < state->total_param) ||
+ (state->received_data < state->total_data)) {
+ END_PROFILE(SMBnttranss);
+ return;
+ }
+
+ handle_nttrans(conn, state, req);
+
+ DLIST_REMOVE(conn->pending_trans, state);
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ END_PROFILE(SMBnttranss);
+ return;
+
+ bad_param:
+
+ DEBUG(0,("reply_nttranss: invalid trans parameters\n"));
+ DLIST_REMOVE(conn->pending_trans, state);
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBnttranss);
+ return;
+}
diff --git a/source3/smbd/smb1_nttrans.h b/source3/smbd/smb1_nttrans.h
new file mode 100644
index 0000000..cbd6d1f
--- /dev/null
+++ b/source3/smbd/smb1_nttrans.h
@@ -0,0 +1,25 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB NT transaction handling
+ Copyright (C) Jeremy Allison 1994-2007
+ Copyright (C) Stefan (metze) Metzmacher 2003
+
+ 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/>.
+*/
+
+void reply_ntcreate_and_X(struct smb_request *req);
+void reply_ntcancel(struct smb_request *req);
+void reply_ntrename(struct smb_request *req);
+void reply_nttrans(struct smb_request *req);
+void reply_nttranss(struct smb_request *req);
diff --git a/source3/smbd/smb1_oplock.c b/source3/smbd/smb1_oplock.c
new file mode 100644
index 0000000..5b8a09b
--- /dev/null
+++ b/source3/smbd/smb1_oplock.c
@@ -0,0 +1,72 @@
+/*
+ Unix SMB/CIFS implementation.
+ oplock processing
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 1998 - 2001
+ Copyright (C) Volker Lendecke 2005
+
+ 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 "lib/util/server_id.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "messages.h"
+#include "locking/leases_db.h"
+#include "../librpc/gen_ndr/ndr_open_files.h"
+
+/****************************************************************************
+ Set up an oplock break message.
+****************************************************************************/
+
+void new_break_message_smb1(files_struct *fsp, int cmd,
+ char result[SMB1_BREAK_MESSAGE_LENGTH])
+{
+ memset(result,'\0',smb_size);
+ srv_smb1_set_message(result,8,0,true);
+ SCVAL(result,smb_com,SMBlockingX);
+ SSVAL(result,smb_tid,fsp->conn->cnum);
+ SSVAL(result,smb_pid,0xFFFF);
+ SSVAL(result,smb_uid,0);
+ SSVAL(result,smb_mid,0xFFFF);
+ SCVAL(result,smb_vwv0,0xFF);
+ SSVAL(result,smb_vwv2,fsp->fnum);
+ SCVAL(result,smb_vwv3,LOCKING_ANDX_OPLOCK_RELEASE);
+ SCVAL(result,smb_vwv3+1,cmd);
+}
+
+void send_break_message_smb1(files_struct *fsp, int level)
+{
+ struct smbXsrv_connection *xconn = NULL;
+ char break_msg[SMB1_BREAK_MESSAGE_LENGTH];
+
+ /*
+ * For SMB1 we only have one connection
+ */
+ xconn = fsp->conn->sconn->client->connections;
+
+ new_break_message_smb1(fsp, level, break_msg);
+
+ show_msg(break_msg);
+ if (!smb1_srv_send(xconn,
+ break_msg,
+ false,
+ 0,
+ IS_CONN_ENCRYPTED(fsp->conn))) {
+ exit_server_cleanly("send_break_message_smb1: "
+ "smb1_srv_send failed.");
+ }
+}
diff --git a/source3/smbd/smb1_oplock.h b/source3/smbd/smb1_oplock.h
new file mode 100644
index 0000000..a9b3c47
--- /dev/null
+++ b/source3/smbd/smb1_oplock.h
@@ -0,0 +1,26 @@
+/*
+ Unix SMB/CIFS implementation.
+ oplock processing
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 1998 - 2001
+ Copyright (C) Volker Lendecke 2005
+
+ 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/>.
+*/
+
+#define SMB1_BREAK_MESSAGE_LENGTH (smb_size + 8*2)
+
+void new_break_message_smb1(files_struct *fsp, int cmd,
+ char result[SMB1_BREAK_MESSAGE_LENGTH]);
+void send_break_message_smb1(files_struct *fsp, int level);
diff --git a/source3/smbd/smb1_pipes.c b/source3/smbd/smb1_pipes.c
new file mode 100644
index 0000000..ea2fae5
--- /dev/null
+++ b/source3/smbd/smb1_pipes.c
@@ -0,0 +1,447 @@
+/*
+ Unix SMB/CIFS implementation.
+ Pipe SMB reply routines
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1996-1998
+ Copyright (C) Paul Ashton 1997-1998.
+ Copyright (C) Jeremy Allison 2005.
+
+ 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/>.
+*/
+/*
+ This file handles reply_ calls on named pipes that the server
+ makes to handle specific protocols
+*/
+
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "libcli/security/security.h"
+#include "rpc_server/srv_pipe_hnd.h"
+#include "auth/auth_util.h"
+#include "librpc/rpc/dcerpc_helper.h"
+
+/****************************************************************************
+ Reply to an open and X on a named pipe.
+ This code is basically stolen from reply_open_and_X with some
+ wrinkles to handle pipes.
+****************************************************************************/
+
+void reply_open_pipe_and_X(connection_struct *conn, struct smb_request *req)
+{
+ const char *fname = NULL;
+ char *pipe_name = NULL;
+ files_struct *fsp;
+ TALLOC_CTX *ctx = talloc_tos();
+ NTSTATUS status;
+
+ /* XXXX we need to handle passed times, sattr and flags */
+ srvstr_pull_req_talloc(ctx, req, &pipe_name, req->buf, STR_TERMINATE);
+ if (!pipe_name) {
+ reply_botherror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND,
+ ERRDOS, ERRbadpipe);
+ return;
+ }
+
+ /* If the name doesn't start \PIPE\ then this is directed */
+ /* at a mailslot or something we really, really don't understand, */
+ /* not just something we really don't understand. */
+
+#define PIPE "PIPE\\"
+#define PIPELEN strlen(PIPE)
+
+ fname = pipe_name;
+ while (fname[0] == '\\') {
+ fname++;
+ }
+ if (!strnequal(fname, PIPE, PIPELEN)) {
+ reply_nterror(req, NT_STATUS_OBJECT_PATH_SYNTAX_BAD);
+ return;
+ }
+ fname += PIPELEN;
+ while (fname[0] == '\\') {
+ fname++;
+ }
+
+ DEBUG(4,("Opening pipe %s => %s.\n", pipe_name, fname));
+
+ status = open_np_file(req, fname, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ reply_botherror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND,
+ ERRDOS, ERRbadpipe);
+ return;
+ }
+ reply_nterror(req, status);
+ return;
+ }
+
+ /* Prepare the reply */
+ reply_smb1_outbuf(req, 15, 0);
+
+ SSVAL(req->outbuf, smb_vwv0, 0xff); /* andx chain ends */
+ SSVAL(req->outbuf, smb_vwv1, 0); /* no andx offset */
+
+ /* Mark the opened file as an existing named pipe in message mode. */
+ SSVAL(req->outbuf,smb_vwv9,2);
+ SSVAL(req->outbuf,smb_vwv10,0xc700);
+
+ SSVAL(req->outbuf, smb_vwv2, fsp->fnum);
+ SSVAL(req->outbuf, smb_vwv3, 0); /* fmode */
+ srv_put_dos_date3((char *)req->outbuf, smb_vwv4, 0); /* mtime */
+ SIVAL(req->outbuf, smb_vwv6, 0); /* size */
+ SSVAL(req->outbuf, smb_vwv8, 0); /* rmode */
+ SSVAL(req->outbuf, smb_vwv11, 0x0001);
+}
+
+/****************************************************************************
+ Reply to a write and X.
+
+ This code is basically stolen from reply_write_and_X with some
+ wrinkles to handle pipes.
+****************************************************************************/
+
+struct pipe_write_andx_state {
+ bool pipe_start_message_raw;
+ size_t numtowrite;
+};
+
+static void pipe_write_andx_done(struct tevent_req *subreq);
+
+void reply_pipe_write_and_X(struct smb_request *req)
+{
+ files_struct *fsp = file_fsp(req, SVAL(req->vwv+2, 0));
+ int smb_doff = SVAL(req->vwv+11, 0);
+ const uint8_t *data;
+ struct pipe_write_andx_state *state;
+ struct tevent_req *subreq;
+
+ if (!fsp_is_np(fsp)) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ if (fsp->vuid != req->vuid) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ state = talloc(req, struct pipe_write_andx_state);
+ if (state == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ req->async_priv = state;
+
+ state->numtowrite = SVAL(req->vwv+10, 0);
+ state->pipe_start_message_raw =
+ ((SVAL(req->vwv+7, 0) & (PIPE_START_MESSAGE|PIPE_RAW_MODE))
+ == (PIPE_START_MESSAGE|PIPE_RAW_MODE));
+
+ DEBUG(6, ("reply_pipe_write_and_X: %s, name: %s len: %d\n",
+ fsp_fnum_dbg(fsp), fsp_str_dbg(fsp), (int)state->numtowrite));
+
+ data = (const uint8_t *)smb_base(req->inbuf) + smb_doff;
+
+ if (state->pipe_start_message_raw) {
+ /*
+ * For the start of a message in named pipe byte mode,
+ * the first two bytes are a length-of-pdu field. Ignore
+ * them (we don't trust the client). JRA.
+ */
+ if (state->numtowrite < 2) {
+ DEBUG(0,("reply_pipe_write_and_X: start of message "
+ "set and not enough data sent.(%u)\n",
+ (unsigned int)state->numtowrite ));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ data += 2;
+ state->numtowrite -= 2;
+ }
+
+ subreq = np_write_send(state, req->sconn->ev_ctx,
+ fsp->fake_file_handle, data, state->numtowrite);
+ if (subreq == NULL) {
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ tevent_req_set_callback(subreq, pipe_write_andx_done,
+ talloc_move(req->conn, &req));
+}
+
+static void pipe_write_andx_done(struct tevent_req *subreq)
+{
+ struct smb_request *req = tevent_req_callback_data(
+ subreq, struct smb_request);
+ struct pipe_write_andx_state *state = talloc_get_type_abort(
+ req->async_priv, struct pipe_write_andx_state);
+ NTSTATUS status;
+ ssize_t nwritten = -1;
+
+ status = np_write_recv(subreq, &nwritten);
+ TALLOC_FREE(subreq);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto done;
+ }
+
+ /* Looks bogus to me now. Is this error message correct ? JRA. */
+ if (nwritten != state->numtowrite) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ goto done;
+ }
+
+ reply_smb1_outbuf(req, 6, 0);
+
+ SSVAL(req->outbuf, smb_vwv0, 0xff); /* andx chain ends */
+ SSVAL(req->outbuf, smb_vwv1, 0); /* no andx offset */
+
+ nwritten = (state->pipe_start_message_raw ? nwritten + 2 : nwritten);
+ SSVAL(req->outbuf,smb_vwv2,nwritten);
+
+ DEBUG(3,("writeX-IPC nwritten=%d\n", (int)nwritten));
+
+ done:
+ /*
+ * We must free here as the ownership of req was
+ * moved to the connection struct in reply_pipe_write_and_X().
+ */
+ smb_request_done(req);
+}
+
+/****************************************************************************
+ Reply to a read and X.
+ This code is basically stolen from reply_read_and_X with some
+ wrinkles to handle pipes.
+****************************************************************************/
+
+struct pipe_read_andx_state {
+ uint8_t *outbuf;
+ int smb_mincnt;
+ int smb_maxcnt;
+};
+
+static void pipe_read_andx_done(struct tevent_req *subreq);
+
+void reply_pipe_read_and_X(struct smb_request *req)
+{
+ files_struct *fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+ uint8_t *data;
+ struct pipe_read_andx_state *state;
+ struct tevent_req *subreq;
+
+ /* we don't use the offset given to use for pipe reads. This
+ is deliberate, instead we always return the next lump of
+ data on the pipe */
+#if 0
+ uint32_t smb_offs = IVAL(req->vwv+3, 0);
+#endif
+
+ if (!fsp_is_np(fsp)) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ if (fsp->vuid != req->vuid) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ state = talloc(req, struct pipe_read_andx_state);
+ if (state == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ req->async_priv = state;
+
+ state->smb_maxcnt = SVAL(req->vwv+5, 0);
+ state->smb_mincnt = SVAL(req->vwv+6, 0);
+
+ reply_smb1_outbuf(req, 12, state->smb_maxcnt + 1 /* padding byte */);
+ SSVAL(req->outbuf, smb_vwv0, 0xff); /* andx chain ends */
+ SSVAL(req->outbuf, smb_vwv1, 0); /* no andx offset */
+ SCVAL(smb_buf(req->outbuf), 0, 0); /* padding byte */
+
+ data = (uint8_t *)smb_buf(req->outbuf) + 1 /* padding byte */;
+
+ /*
+ * We have to tell the upper layers that we're async.
+ */
+ state->outbuf = req->outbuf;
+ req->outbuf = NULL;
+
+ subreq = np_read_send(state, req->sconn->ev_ctx,
+ fsp->fake_file_handle, data,
+ state->smb_maxcnt);
+ if (subreq == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ tevent_req_set_callback(subreq, pipe_read_andx_done,
+ talloc_move(req->conn, &req));
+}
+
+static void pipe_read_andx_done(struct tevent_req *subreq)
+{
+ struct smb_request *req = tevent_req_callback_data(
+ subreq, struct smb_request);
+ struct pipe_read_andx_state *state = talloc_get_type_abort(
+ req->async_priv, struct pipe_read_andx_state);
+ NTSTATUS status;
+ ssize_t nread;
+ bool is_data_outstanding;
+
+ status = np_read_recv(subreq, &nread, &is_data_outstanding);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ NTSTATUS old = status;
+ status = nt_status_np_pipe(old);
+ reply_nterror(req, status);
+ goto done;
+ }
+
+ req->outbuf = state->outbuf;
+ state->outbuf = NULL;
+
+ srv_smb1_set_message((char *)req->outbuf, 12, nread + 1 /* padding byte */,
+ false);
+
+#if 0
+ /*
+ * we should return STATUS_BUFFER_OVERFLOW if there's
+ * out standing data.
+ *
+ * But we can't enable it yet, as it has bad interactions
+ * with fixup_chain_error_packet() in chain_reply().
+ */
+ if (is_data_outstanding) {
+ error_packet_set((char *)req->outbuf, ERRDOS, ERRmoredata,
+ STATUS_BUFFER_OVERFLOW, __LINE__, __FILE__);
+ }
+#endif
+
+ SSVAL(req->outbuf,smb_vwv5,nread);
+ SSVAL(req->outbuf,smb_vwv6,
+ (smb_wct - 4) /* offset from smb header to wct */
+ + 1 /* the wct field */
+ + 12 * sizeof(uint16_t) /* vwv */
+ + 2 /* the buflen field */
+ + 1); /* padding byte */
+
+ DEBUG(3,("readX-IPC min=%d max=%d nread=%d\n",
+ state->smb_mincnt, state->smb_maxcnt, (int)nread));
+
+ done:
+ /*
+ * We must free here as the ownership of req was
+ * moved to the connection struct in reply_pipe_read_and_X().
+ */
+ smb_request_done(req);
+}
+
+/****************************************************************************
+ Reply to a write on a pipe.
+****************************************************************************/
+
+struct pipe_write_state {
+ size_t numtowrite;
+};
+
+static void pipe_write_done(struct tevent_req *subreq);
+
+void reply_pipe_write(struct smb_request *req)
+{
+ files_struct *fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+ const uint8_t *data;
+ struct pipe_write_state *state;
+ struct tevent_req *subreq;
+
+ if (!fsp_is_np(fsp)) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ if (fsp->vuid != req->vuid) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ state = talloc(req, struct pipe_write_state);
+ if (state == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ req->async_priv = state;
+
+ state->numtowrite = SVAL(req->vwv+1, 0);
+
+ data = req->buf + 3;
+
+ DEBUG(6, ("reply_pipe_write: %s, name: %s len: %d\n", fsp_fnum_dbg(fsp),
+ fsp_str_dbg(fsp), (int)state->numtowrite));
+
+ subreq = np_write_send(state, req->sconn->ev_ctx,
+ fsp->fake_file_handle, data, state->numtowrite);
+ if (subreq == NULL) {
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ tevent_req_set_callback(subreq, pipe_write_done,
+ talloc_move(req->conn, &req));
+}
+
+static void pipe_write_done(struct tevent_req *subreq)
+{
+ struct smb_request *req = tevent_req_callback_data(
+ subreq, struct smb_request);
+ struct pipe_write_state *state = talloc_get_type_abort(
+ req->async_priv, struct pipe_write_state);
+ NTSTATUS status;
+ ssize_t nwritten = -1;
+
+ status = np_write_recv(subreq, &nwritten);
+ TALLOC_FREE(subreq);
+ if (nwritten < 0) {
+ reply_nterror(req, status);
+ goto send;
+ }
+
+ /* Looks bogus to me now. Needs to be removed ? JRA. */
+ if ((nwritten == 0 && state->numtowrite != 0)) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ goto send;
+ }
+
+ reply_smb1_outbuf(req, 1, 0);
+
+ SSVAL(req->outbuf,smb_vwv0,nwritten);
+
+ DEBUG(3,("write-IPC nwritten=%d\n", (int)nwritten));
+
+ send:
+ if (!smb1_srv_send(req->xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(req->conn) || req->encrypted)) {
+ exit_server_cleanly("construct_reply: smb1_srv_send failed.");
+ }
+ TALLOC_FREE(req);
+}
diff --git a/source3/smbd/smb1_pipes.h b/source3/smbd/smb1_pipes.h
new file mode 100644
index 0000000..20e8a99
--- /dev/null
+++ b/source3/smbd/smb1_pipes.h
@@ -0,0 +1,26 @@
+/*
+ Unix SMB/CIFS implementation.
+ Pipe SMB reply routines
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1996-1998
+ Copyright (C) Paul Ashton 1997-1998.
+ Copyright (C) Jeremy Allison 2005.
+
+ 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/>.
+*/
+
+void reply_open_pipe_and_X(connection_struct *conn, struct smb_request *req);
+void reply_pipe_write_and_X(struct smb_request *req);
+void reply_pipe_read_and_X(struct smb_request *req);
+void reply_pipe_write(struct smb_request *req);
diff --git a/source3/smbd/smb1_process.c b/source3/smbd/smb1_process.c
new file mode 100644
index 0000000..e4f6cdf
--- /dev/null
+++ b/source3/smbd/smb1_process.c
@@ -0,0 +1,2718 @@
+/*
+ Unix SMB/CIFS implementation.
+ process incoming packets - main loop
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Volker Lendecke 2005-2007
+
+ 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 "../lib/tsocket/tsocket.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "smbd/smbXsrv_open.h"
+#include "librpc/gen_ndr/netlogon.h"
+#include "../lib/async_req/async_sock.h"
+#include "ctdbd_conn.h"
+#include "../lib/util/select.h"
+#include "printing/queue_process.h"
+#include "system/select.h"
+#include "passdb.h"
+#include "auth.h"
+#include "messages.h"
+#include "lib/messages_ctdb.h"
+#include "smbprofile.h"
+#include "rpc_server/spoolss/srv_spoolss_nt.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "../libcli/security/dom_sid.h"
+#include "../libcli/security/security_token.h"
+#include "lib/id_cache.h"
+#include "lib/util/sys_rw_data.h"
+#include "system/threads.h"
+#include "lib/pthreadpool/pthreadpool_tevent.h"
+#include "util_event.h"
+#include "libcli/smb/smbXcli_base.h"
+#include "lib/util/time_basic.h"
+#include "source3/lib/substitute.h"
+#include "lib/util/util_process.h"
+
+/* Internal message queue for deferred opens. */
+struct pending_message_list {
+ struct pending_message_list *next, *prev;
+ struct timeval request_time; /* When was this first issued? */
+ struct smbd_server_connection *sconn;
+ struct smbXsrv_connection *xconn;
+ struct tevent_timer *te;
+ uint32_t seqnum;
+ bool encrypted;
+ bool processed;
+ DATA_BLOB buf;
+ struct deferred_open_record *open_rec;
+};
+
+static bool smb_splice_chain(uint8_t **poutbuf, const uint8_t *andx_buf);
+
+void smbd_echo_init(struct smbXsrv_connection *xconn)
+{
+ xconn->smb1.echo_handler.trusted_fd = -1;
+ xconn->smb1.echo_handler.socket_lock_fd = -1;
+#ifdef HAVE_ROBUST_MUTEXES
+ xconn->smb1.echo_handler.socket_mutex = NULL;
+#endif
+}
+
+static bool smbd_echo_active(struct smbXsrv_connection *xconn)
+{
+ if (xconn->smb1.echo_handler.socket_lock_fd != -1) {
+ return true;
+ }
+
+#ifdef HAVE_ROBUST_MUTEXES
+ if (xconn->smb1.echo_handler.socket_mutex != NULL) {
+ return true;
+ }
+#endif
+
+ return false;
+}
+
+static bool smbd_lock_socket_internal(struct smbXsrv_connection *xconn)
+{
+ if (!smbd_echo_active(xconn)) {
+ return true;
+ }
+
+ xconn->smb1.echo_handler.ref_count++;
+
+ if (xconn->smb1.echo_handler.ref_count > 1) {
+ return true;
+ }
+
+ DEBUG(10,("pid[%d] wait for socket lock\n", (int)getpid()));
+
+#ifdef HAVE_ROBUST_MUTEXES
+ if (xconn->smb1.echo_handler.socket_mutex != NULL) {
+ int ret = EINTR;
+
+ while (ret == EINTR) {
+ ret = pthread_mutex_lock(
+ xconn->smb1.echo_handler.socket_mutex);
+ if (ret == 0) {
+ break;
+ }
+ }
+ if (ret != 0) {
+ DEBUG(1, ("pthread_mutex_lock failed: %s\n",
+ strerror(ret)));
+ return false;
+ }
+ }
+#endif
+
+ if (xconn->smb1.echo_handler.socket_lock_fd != -1) {
+ bool ok;
+
+ do {
+ ok = fcntl_lock(
+ xconn->smb1.echo_handler.socket_lock_fd,
+ F_SETLKW, 0, 0, F_WRLCK);
+ } while (!ok && (errno == EINTR));
+
+ if (!ok) {
+ DEBUG(1, ("fcntl_lock failed: %s\n", strerror(errno)));
+ return false;
+ }
+ }
+
+ DEBUG(10,("pid[%d] got socket lock\n", (int)getpid()));
+
+ return true;
+}
+
+void smbd_lock_socket(struct smbXsrv_connection *xconn)
+{
+ if (!smbd_lock_socket_internal(xconn)) {
+ exit_server_cleanly("failed to lock socket");
+ }
+}
+
+static bool smbd_unlock_socket_internal(struct smbXsrv_connection *xconn)
+{
+ if (!smbd_echo_active(xconn)) {
+ return true;
+ }
+
+ xconn->smb1.echo_handler.ref_count--;
+
+ if (xconn->smb1.echo_handler.ref_count > 0) {
+ return true;
+ }
+
+#ifdef HAVE_ROBUST_MUTEXES
+ if (xconn->smb1.echo_handler.socket_mutex != NULL) {
+ int ret;
+ ret = pthread_mutex_unlock(
+ xconn->smb1.echo_handler.socket_mutex);
+ if (ret != 0) {
+ DEBUG(1, ("pthread_mutex_unlock failed: %s\n",
+ strerror(ret)));
+ return false;
+ }
+ }
+#endif
+
+ if (xconn->smb1.echo_handler.socket_lock_fd != -1) {
+ bool ok;
+
+ do {
+ ok = fcntl_lock(
+ xconn->smb1.echo_handler.socket_lock_fd,
+ F_SETLKW, 0, 0, F_UNLCK);
+ } while (!ok && (errno == EINTR));
+
+ if (!ok) {
+ DEBUG(1, ("fcntl_lock failed: %s\n", strerror(errno)));
+ return false;
+ }
+ }
+
+ DEBUG(10,("pid[%d] unlocked socket\n", (int)getpid()));
+
+ return true;
+}
+
+void smbd_unlock_socket(struct smbXsrv_connection *xconn)
+{
+ if (!smbd_unlock_socket_internal(xconn)) {
+ exit_server_cleanly("failed to unlock socket");
+ }
+}
+
+/* Accessor function for smb_read_error for smbd functions. */
+
+/****************************************************************************
+ Send an smb to a fd.
+****************************************************************************/
+
+bool smb1_srv_send(struct smbXsrv_connection *xconn,
+ char *buffer,
+ bool do_signing,
+ uint32_t seqnum,
+ bool do_encrypt)
+{
+ size_t len = 0;
+ ssize_t ret;
+ char *buf_out = buffer;
+
+ if (!NT_STATUS_IS_OK(xconn->transport.status)) {
+ /*
+ * we're not supposed to do any io
+ */
+ return true;
+ }
+
+ smbd_lock_socket(xconn);
+
+ if (do_signing) {
+ NTSTATUS status;
+
+ /* Sign the outgoing packet if required. */
+ status = smb1_srv_calculate_sign_mac(xconn, buf_out, seqnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Failed to calculate signing mac: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+ }
+
+ if (do_encrypt) {
+ NTSTATUS status = srv_encrypt_buffer(xconn, buffer, &buf_out);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("send_smb: SMB encryption failed "
+ "on outgoing packet! Error %s\n",
+ nt_errstr(status) ));
+ ret = -1;
+ goto out;
+ }
+ }
+
+ len = smb_len_large(buf_out) + 4;
+
+ ret = write_data(xconn->transport.sock, buf_out, len);
+ if (ret <= 0) {
+ int saved_errno = errno;
+ /*
+ * Try and give an error message saying what
+ * client failed.
+ */
+ DEBUG(1,("pid[%d] Error writing %d bytes to client %s. %d. (%s)\n",
+ (int)getpid(), (int)len,
+ smbXsrv_connection_dbg(xconn),
+ (int)ret, strerror(saved_errno)));
+ errno = saved_errno;
+
+ srv_free_enc_buffer(xconn, buf_out);
+ goto out;
+ }
+
+ srv_free_enc_buffer(xconn, buf_out);
+out:
+ smbd_unlock_socket(xconn);
+ return (ret > 0);
+}
+
+/* Socket functions for smbd packet processing. */
+
+static bool valid_packet_size(size_t len)
+{
+ /*
+ * A WRITEX with CAP_LARGE_WRITEX can be 64k worth of data plus 65 bytes
+ * of header. Don't print the error if this fits.... JRA.
+ */
+
+ if (len > (LARGE_WRITEX_BUFFER_SIZE + LARGE_WRITEX_HDR_SIZE)) {
+ DEBUG(0,("Invalid packet length! (%lu bytes).\n",
+ (unsigned long)len));
+ return false;
+ }
+ return true;
+}
+
+/****************************************************************************
+ Attempt a zerocopy writeX read. We know here that len > smb_size-4
+****************************************************************************/
+
+/*
+ * Unfortunately, earlier versions of smbclient/libsmbclient
+ * don't send this "standard" writeX header. I've fixed this
+ * for 3.2 but we'll use the old method with earlier versions.
+ * Windows and CIFSFS at least use this standard size. Not
+ * sure about MacOSX.
+ */
+
+#define STANDARD_WRITE_AND_X_HEADER_SIZE (smb_size - 4 + /* basic header */ \
+ (2*14) + /* word count (including bcc) */ \
+ 1 /* pad byte */)
+
+static NTSTATUS receive_smb_raw_talloc_partial_read(TALLOC_CTX *mem_ctx,
+ const char lenbuf[4],
+ struct smbXsrv_connection *xconn,
+ int sock,
+ char **buffer,
+ unsigned int timeout,
+ size_t *p_unread,
+ size_t *len_ret)
+{
+ /* Size of a WRITEX call (+4 byte len). */
+ char writeX_header[4 + STANDARD_WRITE_AND_X_HEADER_SIZE];
+ ssize_t len = smb_len_large(lenbuf); /* Could be a UNIX large writeX. */
+ ssize_t toread;
+ NTSTATUS status;
+
+ memcpy(writeX_header, lenbuf, 4);
+
+ status = read_fd_with_timeout(
+ sock, writeX_header + 4,
+ STANDARD_WRITE_AND_X_HEADER_SIZE,
+ STANDARD_WRITE_AND_X_HEADER_SIZE,
+ timeout, NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("read_fd_with_timeout failed for client %s read "
+ "error = %s.\n",
+ smbXsrv_connection_dbg(xconn),
+ nt_errstr(status)));
+ return status;
+ }
+
+ /*
+ * Ok - now try and see if this is a possible
+ * valid writeX call.
+ */
+
+ if (is_valid_writeX_buffer(xconn, (uint8_t *)writeX_header)) {
+ /*
+ * If the data offset is beyond what
+ * we've read, drain the extra bytes.
+ */
+ uint16_t doff = SVAL(writeX_header,smb_vwv11);
+ ssize_t newlen;
+
+ if (doff > STANDARD_WRITE_AND_X_HEADER_SIZE) {
+ size_t drain = doff - STANDARD_WRITE_AND_X_HEADER_SIZE;
+ if (drain_socket(sock, drain) != drain) {
+ smb_panic("receive_smb_raw_talloc_partial_read:"
+ " failed to drain pending bytes");
+ }
+ } else {
+ doff = STANDARD_WRITE_AND_X_HEADER_SIZE;
+ }
+
+ /* Spoof down the length and null out the bcc. */
+ set_message_bcc(writeX_header, 0);
+ newlen = smb_len(writeX_header);
+
+ /* Copy the header we've written. */
+
+ *buffer = (char *)talloc_memdup(mem_ctx,
+ writeX_header,
+ sizeof(writeX_header));
+
+ if (*buffer == NULL) {
+ DEBUG(0, ("Could not allocate inbuf of length %d\n",
+ (int)sizeof(writeX_header)));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Work out the remaining bytes. */
+ *p_unread = len - STANDARD_WRITE_AND_X_HEADER_SIZE;
+ *len_ret = newlen + 4;
+ return NT_STATUS_OK;
+ }
+
+ if (!valid_packet_size(len)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * Not a valid writeX call. Just do the standard
+ * talloc and return.
+ */
+
+ *buffer = talloc_array(mem_ctx, char, len+4);
+
+ if (*buffer == NULL) {
+ DEBUG(0, ("Could not allocate inbuf of length %d\n",
+ (int)len+4));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Copy in what we already read. */
+ memcpy(*buffer,
+ writeX_header,
+ 4 + STANDARD_WRITE_AND_X_HEADER_SIZE);
+ toread = len - STANDARD_WRITE_AND_X_HEADER_SIZE;
+
+ if(toread > 0) {
+ status = read_packet_remainder(
+ sock,
+ (*buffer) + 4 + STANDARD_WRITE_AND_X_HEADER_SIZE,
+ timeout, toread);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("receive_smb_raw_talloc_partial_read: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+ }
+
+ *len_ret = len + 4;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS receive_smb_raw_talloc(TALLOC_CTX *mem_ctx,
+ struct smbXsrv_connection *xconn,
+ int sock,
+ char **buffer, unsigned int timeout,
+ size_t *p_unread, size_t *plen)
+{
+ char lenbuf[4];
+ size_t len;
+ int min_recv_size = lp_min_receive_file_size();
+ NTSTATUS status;
+
+ *p_unread = 0;
+
+ status = read_smb_length_return_keepalive(sock, lenbuf, timeout,
+ &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (CVAL(lenbuf,0) == 0 && min_recv_size &&
+ (smb_len_large(lenbuf) > /* Could be a UNIX large writeX. */
+ (min_recv_size + STANDARD_WRITE_AND_X_HEADER_SIZE)) &&
+ !smb1_srv_is_signing_active(xconn) &&
+ xconn->smb1.echo_handler.trusted_fde == NULL) {
+
+ return receive_smb_raw_talloc_partial_read(
+ mem_ctx, lenbuf, xconn, sock, buffer, timeout,
+ p_unread, plen);
+ }
+
+ if (!valid_packet_size(len)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * The +4 here can't wrap, we've checked the length above already.
+ */
+
+ *buffer = talloc_array(mem_ctx, char, len+4);
+
+ if (*buffer == NULL) {
+ DEBUG(0, ("Could not allocate inbuf of length %d\n",
+ (int)len+4));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ memcpy(*buffer, lenbuf, sizeof(lenbuf));
+
+ status = read_packet_remainder(sock, (*buffer)+4, timeout, len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ *plen = len + 4;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smb1_receive_talloc(TALLOC_CTX *mem_ctx,
+ struct smbXsrv_connection *xconn,
+ int sock,
+ char **buffer, unsigned int timeout,
+ size_t *p_unread, bool *p_encrypted,
+ size_t *p_len,
+ uint32_t *seqnum,
+ bool trusted_channel)
+{
+ size_t len = 0;
+ NTSTATUS status;
+
+ *p_encrypted = false;
+
+ status = receive_smb_raw_talloc(mem_ctx, xconn, sock, buffer, timeout,
+ p_unread, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(NT_STATUS_EQUAL(status, NT_STATUS_END_OF_FILE)?5:1,
+ ("receive_smb_raw_talloc failed for client %s "
+ "read error = %s.\n",
+ smbXsrv_connection_dbg(xconn),
+ nt_errstr(status)) );
+ return status;
+ }
+
+ if (is_encrypted_packet((uint8_t *)*buffer)) {
+ status = srv_decrypt_buffer(xconn, *buffer);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("receive_smb_talloc: SMB decryption failed on "
+ "incoming packet! Error %s\n",
+ nt_errstr(status) ));
+ return status;
+ }
+ *p_encrypted = true;
+ }
+
+ /* Check the incoming SMB signature. */
+ if (!smb1_srv_check_sign_mac(xconn, *buffer, seqnum, trusted_channel)) {
+ DEBUG(0, ("receive_smb: SMB Signature verification failed on "
+ "incoming packet!\n"));
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ *p_len = len;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Function to push a message onto the tail of a linked list of smb messages ready
+ for processing.
+****************************************************************************/
+
+static bool push_queued_message(struct smb_request *req,
+ struct timeval request_time,
+ struct timeval end_time,
+ struct deferred_open_record *open_rec)
+{
+ int msg_len = smb_len(req->inbuf) + 4;
+ struct pending_message_list *msg;
+
+ msg = talloc_zero(NULL, struct pending_message_list);
+
+ if(msg == NULL) {
+ DEBUG(0,("push_message: malloc fail (1)\n"));
+ return False;
+ }
+ msg->sconn = req->sconn;
+ msg->xconn = req->xconn;
+
+ msg->buf = data_blob_talloc(msg, req->inbuf, msg_len);
+ if(msg->buf.data == NULL) {
+ DEBUG(0,("push_message: malloc fail (2)\n"));
+ TALLOC_FREE(msg);
+ return False;
+ }
+
+ msg->request_time = request_time;
+ msg->seqnum = req->seqnum;
+ msg->encrypted = req->encrypted;
+ msg->processed = false;
+
+ if (open_rec) {
+ msg->open_rec = talloc_move(msg, &open_rec);
+ }
+
+#if 0
+ msg->te = tevent_add_timer(msg->sconn->ev_ctx,
+ msg,
+ end_time,
+ smbd_deferred_open_timer,
+ msg);
+ if (!msg->te) {
+ DEBUG(0,("push_message: event_add_timed failed\n"));
+ TALLOC_FREE(msg);
+ return false;
+ }
+#endif
+
+ DLIST_ADD_END(req->sconn->deferred_open_queue, msg);
+
+ DEBUG(10,("push_message: pushed message length %u on "
+ "deferred_open_queue\n", (unsigned int)msg_len));
+
+ return True;
+}
+
+/****************************************************************************
+ Function to push a deferred open smb message onto a linked list of local smb
+ messages ready for processing.
+****************************************************************************/
+
+bool push_deferred_open_message_smb1(struct smb_request *req,
+ struct timeval timeout,
+ struct file_id id,
+ struct deferred_open_record *open_rec)
+{
+ struct timeval_buf tvbuf;
+ struct timeval end_time;
+
+ if (req->unread_bytes) {
+ DEBUG(0,("push_deferred_open_message_smb: logic error ! "
+ "unread_bytes = %u\n",
+ (unsigned int)req->unread_bytes ));
+ smb_panic("push_deferred_open_message_smb: "
+ "logic error unread_bytes != 0" );
+ }
+
+ end_time = timeval_sum(&req->request_time, &timeout);
+
+ DBG_DEBUG("pushing message len %u mid %"PRIu64" timeout time [%s]\n",
+ (unsigned int) smb_len(req->inbuf)+4,
+ req->mid,
+ timeval_str_buf(&end_time, false, true, &tvbuf));
+
+ return push_queued_message(req, req->request_time, end_time, open_rec);
+}
+
+/*
+ * Only allow 5 outstanding trans requests. We're allocating memory, so
+ * prevent a DoS.
+ */
+
+NTSTATUS allow_new_trans(struct trans_state *list, uint64_t mid)
+{
+ int count = 0;
+ for (; list != NULL; list = list->next) {
+
+ if (list->mid == mid) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ count += 1;
+ }
+ if (count > 5) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+These flags determine some of the permissions required to do an operation
+
+Note that I don't set NEED_WRITE on some write operations because they
+are used by some brain-dead clients when printing, and I don't want to
+force write permissions on print services.
+*/
+#define AS_USER (1<<0)
+#define NEED_WRITE (1<<1) /* Must be paired with AS_USER */
+#define TIME_INIT (1<<2)
+#define CAN_IPC (1<<3) /* Must be paired with AS_USER */
+#define AS_GUEST (1<<5) /* Must *NOT* be paired with AS_USER */
+#define DO_CHDIR (1<<6)
+
+/*
+ define a list of possible SMB messages and their corresponding
+ functions. Any message that has a NULL function is unimplemented -
+ please feel free to contribute implementations!
+*/
+static const struct smb_message_struct {
+ const char *name;
+ void (*fn)(struct smb_request *req);
+ int flags;
+} smb_messages[256] = {
+
+/* 0x00 */ { "SMBmkdir",reply_mkdir,AS_USER | NEED_WRITE},
+/* 0x01 */ { "SMBrmdir",reply_rmdir,AS_USER | NEED_WRITE},
+/* 0x02 */ { "SMBopen",reply_open,AS_USER },
+/* 0x03 */ { "SMBcreate",reply_mknew,AS_USER},
+/* 0x04 */ { "SMBclose",reply_close,AS_USER | CAN_IPC },
+/* 0x05 */ { "SMBflush",reply_flush,AS_USER},
+/* 0x06 */ { "SMBunlink",reply_unlink,AS_USER | NEED_WRITE },
+/* 0x07 */ { "SMBmv",reply_mv,AS_USER | NEED_WRITE },
+/* 0x08 */ { "SMBgetatr",reply_getatr,AS_USER},
+/* 0x09 */ { "SMBsetatr",reply_setatr,AS_USER | NEED_WRITE},
+/* 0x0a */ { "SMBread",reply_read,AS_USER},
+/* 0x0b */ { "SMBwrite",reply_write,AS_USER | CAN_IPC },
+/* 0x0c */ { "SMBlock",reply_lock,AS_USER},
+/* 0x0d */ { "SMBunlock",reply_unlock,AS_USER},
+/* 0x0e */ { "SMBctemp",reply_ctemp,AS_USER },
+/* 0x0f */ { "SMBmknew",reply_mknew,AS_USER},
+/* 0x10 */ { "SMBcheckpath",reply_checkpath,AS_USER},
+/* 0x11 */ { "SMBexit",reply_exit,DO_CHDIR},
+/* 0x12 */ { "SMBlseek",reply_lseek,AS_USER},
+/* 0x13 */ { "SMBlockread",reply_lockread,AS_USER},
+/* 0x14 */ { "SMBwriteunlock",reply_writeunlock,AS_USER},
+/* 0x15 */ { NULL, NULL, 0 },
+/* 0x16 */ { NULL, NULL, 0 },
+/* 0x17 */ { NULL, NULL, 0 },
+/* 0x18 */ { NULL, NULL, 0 },
+/* 0x19 */ { NULL, NULL, 0 },
+/* 0x1a */ { "SMBreadbraw",reply_readbraw,AS_USER},
+/* 0x1b */ { "SMBreadBmpx",reply_readbmpx,AS_USER},
+/* 0x1c */ { "SMBreadBs",reply_readbs,AS_USER },
+/* 0x1d */ { "SMBwritebraw",reply_writebraw,AS_USER},
+/* 0x1e */ { "SMBwriteBmpx",reply_writebmpx,AS_USER},
+/* 0x1f */ { "SMBwriteBs",reply_writebs,AS_USER},
+/* 0x20 */ { "SMBwritec", NULL,0},
+/* 0x21 */ { NULL, NULL, 0 },
+/* 0x22 */ { "SMBsetattrE",reply_setattrE,AS_USER | NEED_WRITE },
+/* 0x23 */ { "SMBgetattrE",reply_getattrE,AS_USER },
+/* 0x24 */ { "SMBlockingX",reply_lockingX,AS_USER },
+/* 0x25 */ { "SMBtrans",reply_trans,AS_USER | CAN_IPC },
+/* 0x26 */ { "SMBtranss",reply_transs,AS_USER | CAN_IPC},
+/* 0x27 */ { "SMBioctl",reply_ioctl,0},
+/* 0x28 */ { "SMBioctls", NULL,AS_USER},
+/* 0x29 */ { "SMBcopy",reply_copy,AS_USER | NEED_WRITE },
+/* 0x2a */ { "SMBmove", NULL,AS_USER | NEED_WRITE },
+/* 0x2b */ { "SMBecho",reply_echo,0},
+/* 0x2c */ { "SMBwriteclose",reply_writeclose,AS_USER},
+/* 0x2d */ { "SMBopenX",reply_open_and_X,AS_USER | CAN_IPC },
+/* 0x2e */ { "SMBreadX",reply_read_and_X,AS_USER | CAN_IPC },
+/* 0x2f */ { "SMBwriteX",reply_write_and_X,AS_USER | CAN_IPC },
+/* 0x30 */ { NULL, NULL, 0 },
+/* 0x31 */ { NULL, NULL, 0 },
+/* 0x32 */ { "SMBtrans2",reply_trans2, AS_USER | CAN_IPC },
+/* 0x33 */ { "SMBtranss2",reply_transs2, AS_USER | CAN_IPC },
+/* 0x34 */ { "SMBfindclose",reply_findclose,AS_USER},
+/* 0x35 */ { "SMBfindnclose",reply_findnclose,AS_USER},
+/* 0x36 */ { NULL, NULL, 0 },
+/* 0x37 */ { NULL, NULL, 0 },
+/* 0x38 */ { NULL, NULL, 0 },
+/* 0x39 */ { NULL, NULL, 0 },
+/* 0x3a */ { NULL, NULL, 0 },
+/* 0x3b */ { NULL, NULL, 0 },
+/* 0x3c */ { NULL, NULL, 0 },
+/* 0x3d */ { NULL, NULL, 0 },
+/* 0x3e */ { NULL, NULL, 0 },
+/* 0x3f */ { NULL, NULL, 0 },
+/* 0x40 */ { NULL, NULL, 0 },
+/* 0x41 */ { NULL, NULL, 0 },
+/* 0x42 */ { NULL, NULL, 0 },
+/* 0x43 */ { NULL, NULL, 0 },
+/* 0x44 */ { NULL, NULL, 0 },
+/* 0x45 */ { NULL, NULL, 0 },
+/* 0x46 */ { NULL, NULL, 0 },
+/* 0x47 */ { NULL, NULL, 0 },
+/* 0x48 */ { NULL, NULL, 0 },
+/* 0x49 */ { NULL, NULL, 0 },
+/* 0x4a */ { NULL, NULL, 0 },
+/* 0x4b */ { NULL, NULL, 0 },
+/* 0x4c */ { NULL, NULL, 0 },
+/* 0x4d */ { NULL, NULL, 0 },
+/* 0x4e */ { NULL, NULL, 0 },
+/* 0x4f */ { NULL, NULL, 0 },
+/* 0x50 */ { NULL, NULL, 0 },
+/* 0x51 */ { NULL, NULL, 0 },
+/* 0x52 */ { NULL, NULL, 0 },
+/* 0x53 */ { NULL, NULL, 0 },
+/* 0x54 */ { NULL, NULL, 0 },
+/* 0x55 */ { NULL, NULL, 0 },
+/* 0x56 */ { NULL, NULL, 0 },
+/* 0x57 */ { NULL, NULL, 0 },
+/* 0x58 */ { NULL, NULL, 0 },
+/* 0x59 */ { NULL, NULL, 0 },
+/* 0x5a */ { NULL, NULL, 0 },
+/* 0x5b */ { NULL, NULL, 0 },
+/* 0x5c */ { NULL, NULL, 0 },
+/* 0x5d */ { NULL, NULL, 0 },
+/* 0x5e */ { NULL, NULL, 0 },
+/* 0x5f */ { NULL, NULL, 0 },
+/* 0x60 */ { NULL, NULL, 0 },
+/* 0x61 */ { NULL, NULL, 0 },
+/* 0x62 */ { NULL, NULL, 0 },
+/* 0x63 */ { NULL, NULL, 0 },
+/* 0x64 */ { NULL, NULL, 0 },
+/* 0x65 */ { NULL, NULL, 0 },
+/* 0x66 */ { NULL, NULL, 0 },
+/* 0x67 */ { NULL, NULL, 0 },
+/* 0x68 */ { NULL, NULL, 0 },
+/* 0x69 */ { NULL, NULL, 0 },
+/* 0x6a */ { NULL, NULL, 0 },
+/* 0x6b */ { NULL, NULL, 0 },
+/* 0x6c */ { NULL, NULL, 0 },
+/* 0x6d */ { NULL, NULL, 0 },
+/* 0x6e */ { NULL, NULL, 0 },
+/* 0x6f */ { NULL, NULL, 0 },
+/* 0x70 */ { "SMBtcon",reply_tcon,0},
+/* 0x71 */ { "SMBtdis",reply_tdis,DO_CHDIR},
+/* 0x72 */ { "SMBnegprot",reply_negprot,0},
+/* 0x73 */ { "SMBsesssetupX",reply_sesssetup_and_X,0},
+/* 0x74 */ { "SMBulogoffX",reply_ulogoffX, 0}, /* ulogoff doesn't give a valid TID */
+/* 0x75 */ { "SMBtconX",reply_tcon_and_X,0},
+/* 0x76 */ { NULL, NULL, 0 },
+/* 0x77 */ { NULL, NULL, 0 },
+/* 0x78 */ { NULL, NULL, 0 },
+/* 0x79 */ { NULL, NULL, 0 },
+/* 0x7a */ { NULL, NULL, 0 },
+/* 0x7b */ { NULL, NULL, 0 },
+/* 0x7c */ { NULL, NULL, 0 },
+/* 0x7d */ { NULL, NULL, 0 },
+/* 0x7e */ { NULL, NULL, 0 },
+/* 0x7f */ { NULL, NULL, 0 },
+/* 0x80 */ { "SMBdskattr",reply_dskattr,AS_USER},
+/* 0x81 */ { "SMBsearch",reply_search,AS_USER},
+/* 0x82 */ { "SMBffirst",reply_search,AS_USER},
+/* 0x83 */ { "SMBfunique",reply_search,AS_USER},
+/* 0x84 */ { "SMBfclose",reply_fclose,AS_USER},
+/* 0x85 */ { NULL, NULL, 0 },
+/* 0x86 */ { NULL, NULL, 0 },
+/* 0x87 */ { NULL, NULL, 0 },
+/* 0x88 */ { NULL, NULL, 0 },
+/* 0x89 */ { NULL, NULL, 0 },
+/* 0x8a */ { NULL, NULL, 0 },
+/* 0x8b */ { NULL, NULL, 0 },
+/* 0x8c */ { NULL, NULL, 0 },
+/* 0x8d */ { NULL, NULL, 0 },
+/* 0x8e */ { NULL, NULL, 0 },
+/* 0x8f */ { NULL, NULL, 0 },
+/* 0x90 */ { NULL, NULL, 0 },
+/* 0x91 */ { NULL, NULL, 0 },
+/* 0x92 */ { NULL, NULL, 0 },
+/* 0x93 */ { NULL, NULL, 0 },
+/* 0x94 */ { NULL, NULL, 0 },
+/* 0x95 */ { NULL, NULL, 0 },
+/* 0x96 */ { NULL, NULL, 0 },
+/* 0x97 */ { NULL, NULL, 0 },
+/* 0x98 */ { NULL, NULL, 0 },
+/* 0x99 */ { NULL, NULL, 0 },
+/* 0x9a */ { NULL, NULL, 0 },
+/* 0x9b */ { NULL, NULL, 0 },
+/* 0x9c */ { NULL, NULL, 0 },
+/* 0x9d */ { NULL, NULL, 0 },
+/* 0x9e */ { NULL, NULL, 0 },
+/* 0x9f */ { NULL, NULL, 0 },
+/* 0xa0 */ { "SMBnttrans",reply_nttrans, AS_USER | CAN_IPC },
+/* 0xa1 */ { "SMBnttranss",reply_nttranss, AS_USER | CAN_IPC },
+/* 0xa2 */ { "SMBntcreateX",reply_ntcreate_and_X, AS_USER | CAN_IPC },
+/* 0xa3 */ { NULL, NULL, 0 },
+/* 0xa4 */ { "SMBntcancel",reply_ntcancel, 0 },
+/* 0xa5 */ { "SMBntrename",reply_ntrename, AS_USER | NEED_WRITE },
+/* 0xa6 */ { NULL, NULL, 0 },
+/* 0xa7 */ { NULL, NULL, 0 },
+/* 0xa8 */ { NULL, NULL, 0 },
+/* 0xa9 */ { NULL, NULL, 0 },
+/* 0xaa */ { NULL, NULL, 0 },
+/* 0xab */ { NULL, NULL, 0 },
+/* 0xac */ { NULL, NULL, 0 },
+/* 0xad */ { NULL, NULL, 0 },
+/* 0xae */ { NULL, NULL, 0 },
+/* 0xaf */ { NULL, NULL, 0 },
+/* 0xb0 */ { NULL, NULL, 0 },
+/* 0xb1 */ { NULL, NULL, 0 },
+/* 0xb2 */ { NULL, NULL, 0 },
+/* 0xb3 */ { NULL, NULL, 0 },
+/* 0xb4 */ { NULL, NULL, 0 },
+/* 0xb5 */ { NULL, NULL, 0 },
+/* 0xb6 */ { NULL, NULL, 0 },
+/* 0xb7 */ { NULL, NULL, 0 },
+/* 0xb8 */ { NULL, NULL, 0 },
+/* 0xb9 */ { NULL, NULL, 0 },
+/* 0xba */ { NULL, NULL, 0 },
+/* 0xbb */ { NULL, NULL, 0 },
+/* 0xbc */ { NULL, NULL, 0 },
+/* 0xbd */ { NULL, NULL, 0 },
+/* 0xbe */ { NULL, NULL, 0 },
+/* 0xbf */ { NULL, NULL, 0 },
+/* 0xc0 */ { "SMBsplopen",reply_printopen,AS_USER},
+/* 0xc1 */ { "SMBsplwr",reply_printwrite,AS_USER},
+/* 0xc2 */ { "SMBsplclose",reply_printclose,AS_USER},
+/* 0xc3 */ { "SMBsplretq",reply_printqueue,AS_USER},
+/* 0xc4 */ { NULL, NULL, 0 },
+/* 0xc5 */ { NULL, NULL, 0 },
+/* 0xc6 */ { NULL, NULL, 0 },
+/* 0xc7 */ { NULL, NULL, 0 },
+/* 0xc8 */ { NULL, NULL, 0 },
+/* 0xc9 */ { NULL, NULL, 0 },
+/* 0xca */ { NULL, NULL, 0 },
+/* 0xcb */ { NULL, NULL, 0 },
+/* 0xcc */ { NULL, NULL, 0 },
+/* 0xcd */ { NULL, NULL, 0 },
+/* 0xce */ { NULL, NULL, 0 },
+/* 0xcf */ { NULL, NULL, 0 },
+/* 0xd0 */ { "SMBsends",reply_sends,AS_GUEST},
+/* 0xd1 */ { "SMBsendb", NULL,AS_GUEST},
+/* 0xd2 */ { "SMBfwdname", NULL,AS_GUEST},
+/* 0xd3 */ { "SMBcancelf", NULL,AS_GUEST},
+/* 0xd4 */ { "SMBgetmac", NULL,AS_GUEST},
+/* 0xd5 */ { "SMBsendstrt",reply_sendstrt,AS_GUEST},
+/* 0xd6 */ { "SMBsendend",reply_sendend,AS_GUEST},
+/* 0xd7 */ { "SMBsendtxt",reply_sendtxt,AS_GUEST},
+/* 0xd8 */ { NULL, NULL, 0 },
+/* 0xd9 */ { NULL, NULL, 0 },
+/* 0xda */ { NULL, NULL, 0 },
+/* 0xdb */ { NULL, NULL, 0 },
+/* 0xdc */ { NULL, NULL, 0 },
+/* 0xdd */ { NULL, NULL, 0 },
+/* 0xde */ { NULL, NULL, 0 },
+/* 0xdf */ { NULL, NULL, 0 },
+/* 0xe0 */ { NULL, NULL, 0 },
+/* 0xe1 */ { NULL, NULL, 0 },
+/* 0xe2 */ { NULL, NULL, 0 },
+/* 0xe3 */ { NULL, NULL, 0 },
+/* 0xe4 */ { NULL, NULL, 0 },
+/* 0xe5 */ { NULL, NULL, 0 },
+/* 0xe6 */ { NULL, NULL, 0 },
+/* 0xe7 */ { NULL, NULL, 0 },
+/* 0xe8 */ { NULL, NULL, 0 },
+/* 0xe9 */ { NULL, NULL, 0 },
+/* 0xea */ { NULL, NULL, 0 },
+/* 0xeb */ { NULL, NULL, 0 },
+/* 0xec */ { NULL, NULL, 0 },
+/* 0xed */ { NULL, NULL, 0 },
+/* 0xee */ { NULL, NULL, 0 },
+/* 0xef */ { NULL, NULL, 0 },
+/* 0xf0 */ { NULL, NULL, 0 },
+/* 0xf1 */ { NULL, NULL, 0 },
+/* 0xf2 */ { NULL, NULL, 0 },
+/* 0xf3 */ { NULL, NULL, 0 },
+/* 0xf4 */ { NULL, NULL, 0 },
+/* 0xf5 */ { NULL, NULL, 0 },
+/* 0xf6 */ { NULL, NULL, 0 },
+/* 0xf7 */ { NULL, NULL, 0 },
+/* 0xf8 */ { NULL, NULL, 0 },
+/* 0xf9 */ { NULL, NULL, 0 },
+/* 0xfa */ { NULL, NULL, 0 },
+/* 0xfb */ { NULL, NULL, 0 },
+/* 0xfc */ { NULL, NULL, 0 },
+/* 0xfd */ { NULL, NULL, 0 },
+/* 0xfe */ { NULL, NULL, 0 },
+/* 0xff */ { NULL, NULL, 0 }
+
+};
+
+
+/*******************************************************************
+ Dump a packet to a file.
+********************************************************************/
+
+static void smb_dump(const char *name, int type, const char *data)
+{
+ size_t len;
+ int fd, i;
+ char *fname = NULL;
+ if (DEBUGLEVEL < 50) {
+ return;
+ }
+
+ len = smb_len_tcp(data)+4;
+ for (i=1;i<100;i++) {
+ fname = talloc_asprintf(talloc_tos(),
+ "/tmp/%s.%d.%s",
+ name,
+ i,
+ type ? "req" : "resp");
+ if (fname == NULL) {
+ return;
+ }
+ fd = open(fname, O_WRONLY|O_CREAT|O_EXCL, 0644);
+ if (fd != -1 || errno != EEXIST) break;
+ TALLOC_FREE(fname);
+ }
+ if (fd != -1) {
+ ssize_t ret = write(fd, data, len);
+ if (ret != len)
+ DEBUG(0,("smb_dump: problem: write returned %d\n", (int)ret ));
+ close(fd);
+ DEBUG(0,("created %s len %lu\n", fname, (unsigned long)len));
+ }
+ TALLOC_FREE(fname);
+}
+
+static void smb1srv_update_crypto_flags(struct smbXsrv_session *session,
+ struct smb_request *req,
+ uint8_t type,
+ bool *update_session_globalp,
+ bool *update_tcon_globalp)
+{
+ connection_struct *conn = req->conn;
+ struct smbXsrv_tcon *tcon = conn ? conn->tcon : NULL;
+ uint8_t encrypt_flag = SMBXSRV_PROCESSED_UNENCRYPTED_PACKET;
+ uint8_t sign_flag = SMBXSRV_PROCESSED_UNSIGNED_PACKET;
+ bool update_session = false;
+ bool update_tcon = false;
+
+ if (req->encrypted) {
+ encrypt_flag = SMBXSRV_PROCESSED_ENCRYPTED_PACKET;
+ }
+
+ if (smb1_srv_is_signing_active(req->xconn)) {
+ sign_flag = SMBXSRV_PROCESSED_SIGNED_PACKET;
+ } else if ((type == SMBecho) || (type == SMBsesssetupX)) {
+ /*
+ * echo can be unsigned. Session setup except final
+ * session setup response too
+ */
+ sign_flag &= ~SMBXSRV_PROCESSED_UNSIGNED_PACKET;
+ }
+
+ update_session |= smbXsrv_set_crypto_flag(
+ &session->global->encryption_flags, encrypt_flag);
+ update_session |= smbXsrv_set_crypto_flag(
+ &session->global->signing_flags, sign_flag);
+
+ if (tcon) {
+ update_tcon |= smbXsrv_set_crypto_flag(
+ &tcon->global->encryption_flags, encrypt_flag);
+ update_tcon |= smbXsrv_set_crypto_flag(
+ &tcon->global->signing_flags, sign_flag);
+ }
+
+ if (update_session) {
+ session->global->channels[0].encryption_cipher = SMB_ENCRYPTION_GSSAPI;
+ }
+
+ *update_session_globalp = update_session;
+ *update_tcon_globalp = update_tcon;
+ return;
+}
+
+static void set_current_case_sensitive(connection_struct *conn, uint16_t flags)
+{
+ int snum;
+ enum remote_arch_types ra_type;
+
+ SMB_ASSERT(conn != NULL);
+ SMB_ASSERT(!conn->sconn->using_smb2);
+
+ snum = SNUM(conn);
+
+ /*
+ * Obey the client case sensitivity requests - only for clients that
+ * support it. */
+ switch (lp_case_sensitive(snum)) {
+ case Auto:
+ /*
+ * We need this ugliness due to DOS/Win9x clients that lie
+ * about case insensitivity. */
+ ra_type = get_remote_arch();
+ if ((ra_type != RA_SAMBA) && (ra_type != RA_CIFSFS)) {
+ /*
+ * Client can't support per-packet case sensitive
+ * pathnames. */
+ conn->case_sensitive = false;
+ } else {
+ conn->case_sensitive =
+ !(flags & FLAG_CASELESS_PATHNAMES);
+ }
+ break;
+ case True:
+ conn->case_sensitive = true;
+ break;
+ default:
+ conn->case_sensitive = false;
+ break;
+ }
+}
+
+/****************************************************************************
+ Prepare everything for calling the actual request function, and potentially
+ call the request function via the "new" interface.
+
+ Return False if the "legacy" function needs to be called, everything is
+ prepared.
+
+ Return True if we're done.
+
+ I know this API sucks, but it is the one with the least code change I could
+ find.
+****************************************************************************/
+
+static connection_struct *switch_message(uint8_t type, struct smb_request *req)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int flags;
+ uint64_t session_tag;
+ connection_struct *conn = NULL;
+ struct smbXsrv_connection *xconn = req->xconn;
+ NTTIME now = timeval_to_nttime(&req->request_time);
+ struct smbXsrv_session *session = NULL;
+ NTSTATUS status;
+
+ errno = 0;
+
+ if (!xconn->smb1.negprot.done) {
+ switch (type) {
+ /*
+ * Without a negprot the request must
+ * either be a negprot, or one of the
+ * evil old SMB mailslot messaging types.
+ */
+ case SMBnegprot:
+ case SMBsendstrt:
+ case SMBsendend:
+ case SMBsendtxt:
+ break;
+ default:
+ exit_server_cleanly("The first request "
+ "should be a negprot");
+ }
+ }
+
+ if (smb_messages[type].fn == NULL) {
+ DEBUG(0,("Unknown message type %d!\n",type));
+ smb_dump("Unknown", 1, (const char *)req->inbuf);
+ reply_unknown_new(req, type);
+ return NULL;
+ }
+
+ flags = smb_messages[type].flags;
+
+ /* In share mode security we must ignore the vuid. */
+ session_tag = req->vuid;
+ conn = req->conn;
+
+ DEBUG(3,("switch message %s (pid %d) conn 0x%lx\n", smb_fn_name(type),
+ (int)getpid(), (unsigned long)conn));
+
+ smb_dump(smb_fn_name(type), 1, (const char *)req->inbuf);
+
+ /* Ensure this value is replaced in the incoming packet. */
+ SSVAL(discard_const_p(uint8_t, req->inbuf),smb_uid,session_tag);
+
+ /*
+ * Ensure the correct username is in current_user_info. This is a
+ * really ugly bugfix for problems with multiple session_setup_and_X's
+ * being done and allowing %U and %G substitutions to work correctly.
+ * There is a reason this code is done here, don't move it unless you
+ * know what you're doing... :-).
+ * JRA.
+ */
+
+ /*
+ * lookup an existing session
+ *
+ * Note: for now we only check for NT_STATUS_NETWORK_SESSION_EXPIRED
+ * here, the main check is still in change_to_user()
+ */
+ status = smb1srv_session_lookup(xconn,
+ session_tag,
+ now,
+ &session);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_SESSION_EXPIRED)) {
+ switch (type) {
+ case SMBsesssetupX:
+ status = NT_STATUS_OK;
+ break;
+ default:
+ DEBUG(1,("Error: session %llu is expired, mid=%llu.\n",
+ (unsigned long long)session_tag,
+ (unsigned long long)req->mid));
+ reply_nterror(req, NT_STATUS_NETWORK_SESSION_EXPIRED);
+ return conn;
+ }
+ }
+
+ if (session != NULL &&
+ session->global->auth_session_info != NULL &&
+ !(flags & AS_USER))
+ {
+ /*
+ * change_to_user() implies set_current_user_info()
+ * and chdir_connect_service().
+ *
+ * So we only call set_current_user_info if
+ * we don't have AS_USER specified.
+ */
+ set_current_user_info(
+ session->global->auth_session_info->unix_info->sanitized_username,
+ session->global->auth_session_info->unix_info->unix_name,
+ session->global->auth_session_info->info->domain_name);
+ }
+
+ /* Does this call need to be run as the connected user? */
+ if (flags & AS_USER) {
+
+ /* Does this call need a valid tree connection? */
+ if (!conn) {
+ /*
+ * Amazingly, the error code depends on the command
+ * (from Samba4).
+ */
+ if (type == SMBntcreateX) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ } else {
+ reply_nterror(req, NT_STATUS_NETWORK_NAME_DELETED);
+ }
+ return NULL;
+ }
+
+ set_current_case_sensitive(conn, SVAL(req->inbuf,smb_flg));
+
+ /*
+ * change_to_user() implies set_current_user_info()
+ * and chdir_connect_service().
+ */
+ if (!change_to_user_and_service(conn,session_tag)) {
+ DEBUG(0, ("Error: Could not change to user. Removing "
+ "deferred open, mid=%llu.\n",
+ (unsigned long long)req->mid));
+ reply_force_doserror(req, ERRSRV, ERRbaduid);
+ return conn;
+ }
+
+ /* All NEED_WRITE and CAN_IPC flags must also have AS_USER. */
+
+ /* Does it need write permission? */
+ if ((flags & NEED_WRITE) && !CAN_WRITE(conn)) {
+ reply_nterror(req, NT_STATUS_MEDIA_WRITE_PROTECTED);
+ return conn;
+ }
+
+ /* IPC services are limited */
+ if (IS_IPC(conn) && !(flags & CAN_IPC)) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return conn;
+ }
+ } else if (flags & AS_GUEST) {
+ /*
+ * Does this protocol need to be run as guest? (Only archane
+ * messenger service requests have this...)
+ */
+ if (!change_to_guest()) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return conn;
+ }
+ } else {
+ /* This call needs to be run as root */
+ change_to_root_user();
+ }
+
+ /* load service specific parameters */
+ if (conn) {
+ if (req->encrypted) {
+ conn->encrypted_tid = true;
+ /* encrypted required from now on. */
+ conn->encrypt_level = SMB_SIGNING_REQUIRED;
+ } else if (ENCRYPTION_REQUIRED(conn)) {
+ if (req->cmd != SMBtrans2 && req->cmd != SMBtranss2) {
+ DEBUG(1,("service[%s] requires encryption"
+ "%s ACCESS_DENIED. mid=%llu\n",
+ lp_servicename(talloc_tos(), lp_sub, SNUM(conn)),
+ smb_fn_name(type),
+ (unsigned long long)req->mid));
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return conn;
+ }
+ }
+
+ if (flags & DO_CHDIR) {
+ bool ok;
+
+ ok = chdir_current_service(conn);
+ if (!ok) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return conn;
+ }
+ }
+ conn->num_smb_operations++;
+ }
+
+ /*
+ * Update encryption and signing state tracking flags that are
+ * used by smbstatus to display signing and encryption status.
+ */
+ if (session != NULL) {
+ bool update_session_global = false;
+ bool update_tcon_global = false;
+
+ req->session = session;
+
+ smb1srv_update_crypto_flags(session, req, type,
+ &update_session_global,
+ &update_tcon_global);
+
+ if (update_session_global) {
+ status = smbXsrv_session_update(session);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, NT_STATUS_UNSUCCESSFUL);
+ return conn;
+ }
+ }
+
+ if (update_tcon_global) {
+ status = smbXsrv_tcon_update(req->conn->tcon);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, NT_STATUS_UNSUCCESSFUL);
+ return conn;
+ }
+ }
+ }
+
+ smb_messages[type].fn(req);
+ return req->conn;
+}
+
+/****************************************************************************
+ Construct a reply to the incoming packet.
+****************************************************************************/
+
+void construct_reply(struct smbXsrv_connection *xconn,
+ char *inbuf,
+ int size,
+ size_t unread_bytes,
+ uint32_t seqnum,
+ bool encrypted)
+{
+ struct smbd_server_connection *sconn = xconn->client->sconn;
+ struct smb_request *req;
+
+ if (!(req = talloc(talloc_tos(), struct smb_request))) {
+ smb_panic("could not allocate smb_request");
+ }
+
+ if (!init_smb1_request(req, sconn, xconn, (uint8_t *)inbuf, unread_bytes,
+ encrypted, seqnum)) {
+ exit_server_cleanly("Invalid SMB request");
+ }
+
+ req->inbuf = (uint8_t *)talloc_move(req, &inbuf);
+
+ req->conn = switch_message(req->cmd, req);
+
+ if (req->outbuf == NULL) {
+ /*
+ * Request has suspended itself, will come
+ * back here.
+ */
+ return;
+ }
+ if (CVAL(req->outbuf,0) == 0) {
+ show_msg((char *)req->outbuf);
+ }
+ smb_request_done(req);
+}
+
+static void construct_reply_chain(struct smbXsrv_connection *xconn,
+ char *inbuf,
+ int size,
+ uint32_t seqnum,
+ bool encrypted)
+{
+ struct smb_request **reqs = NULL;
+ struct smb_request *req;
+ unsigned num_reqs;
+ bool ok;
+
+ ok = smb1_parse_chain(xconn, (uint8_t *)inbuf, xconn, encrypted,
+ seqnum, &reqs, &num_reqs);
+ if (!ok) {
+ char errbuf[smb_size];
+ error_packet(errbuf, 0, 0, NT_STATUS_INVALID_PARAMETER,
+ __LINE__, __FILE__);
+ if (!smb1_srv_send(xconn, errbuf, true, seqnum, encrypted)) {
+ exit_server_cleanly("construct_reply_chain: "
+ "smb1_srv_send failed.");
+ }
+ return;
+ }
+
+ req = reqs[0];
+ req->inbuf = (uint8_t *)talloc_move(reqs, &inbuf);
+
+ req->conn = switch_message(req->cmd, req);
+
+ if (req->outbuf == NULL) {
+ /*
+ * Request has suspended itself, will come
+ * back here.
+ */
+ return;
+ }
+ smb_request_done(req);
+}
+
+/*
+ * To be called from an async SMB handler that is potentially chained
+ * when it is finished for shipping.
+ */
+
+void smb_request_done(struct smb_request *req)
+{
+ struct smb_request **reqs = NULL;
+ struct smb_request *first_req;
+ size_t i, num_reqs, next_index;
+ NTSTATUS status;
+
+ if (req->chain == NULL) {
+ first_req = req;
+ goto shipit;
+ }
+
+ reqs = req->chain;
+ num_reqs = talloc_array_length(reqs);
+
+ for (i=0; i<num_reqs; i++) {
+ if (reqs[i] == req) {
+ break;
+ }
+ }
+ if (i == num_reqs) {
+ /*
+ * Invalid chain, should not happen
+ */
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto error;
+ }
+ next_index = i+1;
+
+ while ((next_index < num_reqs) && (IVAL(req->outbuf, smb_rcls) == 0)) {
+ struct smb_request *next = reqs[next_index];
+ struct smbXsrv_tcon *tcon;
+ NTTIME now = timeval_to_nttime(&req->request_time);
+
+ next->vuid = SVAL(req->outbuf, smb_uid);
+ next->tid = SVAL(req->outbuf, smb_tid);
+ status = smb1srv_tcon_lookup(req->xconn, next->tid,
+ now, &tcon);
+
+ if (NT_STATUS_IS_OK(status)) {
+ next->conn = tcon->compat;
+ } else {
+ next->conn = NULL;
+ }
+ next->chain_fsp = req->chain_fsp;
+ next->inbuf = req->inbuf;
+
+ req = next;
+ req->conn = switch_message(req->cmd, req);
+
+ if (req->outbuf == NULL) {
+ /*
+ * Request has suspended itself, will come
+ * back here.
+ */
+ return;
+ }
+ next_index += 1;
+ }
+
+ first_req = reqs[0];
+
+ for (i=1; i<next_index; i++) {
+ bool ok;
+
+ ok = smb_splice_chain(&first_req->outbuf, reqs[i]->outbuf);
+ if (!ok) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto error;
+ }
+ }
+
+ SSVAL(first_req->outbuf, smb_uid, SVAL(req->outbuf, smb_uid));
+ SSVAL(first_req->outbuf, smb_tid, SVAL(req->outbuf, smb_tid));
+
+ /*
+ * This scary statement intends to set the
+ * FLAGS2_32_BIT_ERROR_CODES flg2 field in first_req->outbuf
+ * to the value last_req->outbuf carries
+ */
+ SSVAL(first_req->outbuf, smb_flg2,
+ (SVAL(first_req->outbuf, smb_flg2) & ~FLAGS2_32_BIT_ERROR_CODES)
+ |(SVAL(req->outbuf, smb_flg2) & FLAGS2_32_BIT_ERROR_CODES));
+
+ /*
+ * Transfer the error codes from the subrequest to the main one
+ */
+ SSVAL(first_req->outbuf, smb_rcls, SVAL(req->outbuf, smb_rcls));
+ SSVAL(first_req->outbuf, smb_err, SVAL(req->outbuf, smb_err));
+
+ _smb_setlen_large(
+ first_req->outbuf, talloc_get_size(first_req->outbuf) - 4);
+
+shipit:
+ if (!smb1_srv_send(first_req->xconn,
+ (char *)first_req->outbuf,
+ true,
+ first_req->seqnum + 1,
+ IS_CONN_ENCRYPTED(req->conn) ||
+ first_req->encrypted)) {
+ exit_server_cleanly("construct_reply_chain: smb1_srv_send "
+ "failed.");
+ }
+ TALLOC_FREE(req); /* non-chained case */
+ TALLOC_FREE(reqs); /* chained case */
+ return;
+
+error:
+ {
+ char errbuf[smb_size];
+ error_packet(errbuf, 0, 0, status, __LINE__, __FILE__);
+ if (!smb1_srv_send(req->xconn,
+ errbuf,
+ true,
+ req->seqnum + 1,
+ req->encrypted)) {
+ exit_server_cleanly("construct_reply_chain: "
+ "smb1_srv_send failed.");
+ }
+ }
+ TALLOC_FREE(req); /* non-chained case */
+ TALLOC_FREE(reqs); /* chained case */
+}
+
+/****************************************************************************
+ Process an smb from the client
+****************************************************************************/
+
+void process_smb1(struct smbXsrv_connection *xconn,
+ uint8_t *inbuf,
+ size_t nread,
+ size_t unread_bytes,
+ uint32_t seqnum,
+ bool encrypted)
+{
+ struct smbd_server_connection *sconn = xconn->client->sconn;
+
+ /* Make sure this is an SMB packet. smb_size contains NetBIOS header
+ * so subtract 4 from it. */
+ if ((nread < (smb_size - 4)) || !valid_smb1_header(inbuf)) {
+ DEBUG(2,("Non-SMB packet of length %d. Terminating server\n",
+ smb_len(inbuf)));
+
+ /* special magic for immediate exit */
+ if ((nread == 9) &&
+ (IVAL(inbuf, 4) == SMB_SUICIDE_PACKET) &&
+ lp_parm_bool(-1, "smbd", "suicide mode", false)) {
+ uint8_t exitcode = CVAL(inbuf, 8);
+ DBG_WARNING("SUICIDE: Exiting immediately with code %d\n",
+ (int)exitcode);
+ exit(exitcode);
+ }
+
+ exit_server_cleanly("Non-SMB packet");
+ }
+
+ show_msg((char *)inbuf);
+
+ if ((unread_bytes == 0) && smb1_is_chain(inbuf)) {
+ construct_reply_chain(xconn,
+ (char *)inbuf,
+ nread,
+ seqnum,
+ encrypted);
+ } else {
+ construct_reply(xconn,
+ (char *)inbuf,
+ nread,
+ unread_bytes,
+ seqnum,
+ encrypted);
+ }
+
+ sconn->trans_num++;
+}
+
+/****************************************************************************
+ Return a string containing the function name of a SMB command.
+****************************************************************************/
+
+const char *smb_fn_name(int type)
+{
+ const char *unknown_name = "SMBunknown";
+
+ if (smb_messages[type].name == NULL)
+ return(unknown_name);
+
+ return(smb_messages[type].name);
+}
+
+/****************************************************************************
+ Helper functions for contruct_reply.
+****************************************************************************/
+
+void add_to_common_flags2(uint32_t v)
+{
+ common_flags2 |= v;
+}
+
+void remove_from_common_flags2(uint32_t v)
+{
+ common_flags2 &= ~v;
+}
+
+/**
+ * @brief Find the smb_cmd offset of the last command pushed
+ * @param[in] buf The buffer we're building up
+ * @retval Where can we put our next andx cmd?
+ *
+ * While chaining requests, the "next" request we're looking at needs to put
+ * its SMB_Command before the data the previous request already built up added
+ * to the chain. Find the offset to the place where we have to put our cmd.
+ */
+
+static bool find_andx_cmd_ofs(uint8_t *buf, size_t *pofs)
+{
+ uint8_t cmd;
+ size_t ofs;
+
+ cmd = CVAL(buf, smb_com);
+
+ if (!smb1cli_is_andx_req(cmd)) {
+ return false;
+ }
+
+ ofs = smb_vwv0;
+
+ while (CVAL(buf, ofs) != 0xff) {
+
+ if (!smb1cli_is_andx_req(CVAL(buf, ofs))) {
+ return false;
+ }
+
+ /*
+ * ofs is from start of smb header, so add the 4 length
+ * bytes. The next cmd is right after the wct field.
+ */
+ ofs = SVAL(buf, ofs+2) + 4 + 1;
+
+ if (ofs+4 >= talloc_get_size(buf)) {
+ return false;
+ }
+ }
+
+ *pofs = ofs;
+ return true;
+}
+
+/**
+ * @brief Do the smb chaining at a buffer level
+ * @param[in] poutbuf Pointer to the talloc'ed buffer to be modified
+ * @param[in] andx_buf Buffer to be appended
+ */
+
+static bool smb_splice_chain(uint8_t **poutbuf, const uint8_t *andx_buf)
+{
+ uint8_t smb_command = CVAL(andx_buf, smb_com);
+ uint8_t wct = CVAL(andx_buf, smb_wct);
+ const uint16_t *vwv = (const uint16_t *)(andx_buf + smb_vwv);
+ uint32_t num_bytes = smb_buflen(andx_buf);
+ const uint8_t *bytes = (const uint8_t *)smb_buf_const(andx_buf);
+
+ uint8_t *outbuf;
+ size_t old_size, new_size;
+ size_t ofs;
+ size_t chain_padding = 0;
+ size_t andx_cmd_ofs;
+
+
+ old_size = talloc_get_size(*poutbuf);
+
+ if ((old_size % 4) != 0) {
+ /*
+ * Align the wct field of subsequent requests to a 4-byte
+ * boundary
+ */
+ chain_padding = 4 - (old_size % 4);
+ }
+
+ /*
+ * After the old request comes the new wct field (1 byte), the vwv's
+ * and the num_bytes field.
+ */
+
+ new_size = old_size + chain_padding + 1 + wct * sizeof(uint16_t) + 2;
+ new_size += num_bytes;
+
+ if ((smb_command != SMBwriteX) && (new_size > 0xffff)) {
+ DEBUG(1, ("smb_splice_chain: %u bytes won't fit\n",
+ (unsigned)new_size));
+ return false;
+ }
+
+ outbuf = talloc_realloc(NULL, *poutbuf, uint8_t, new_size);
+ if (outbuf == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ return false;
+ }
+ *poutbuf = outbuf;
+
+ if (!find_andx_cmd_ofs(outbuf, &andx_cmd_ofs)) {
+ DEBUG(1, ("invalid command chain\n"));
+ *poutbuf = talloc_realloc(NULL, *poutbuf, uint8_t, old_size);
+ return false;
+ }
+
+ if (chain_padding != 0) {
+ memset(outbuf + old_size, 0, chain_padding);
+ old_size += chain_padding;
+ }
+
+ SCVAL(outbuf, andx_cmd_ofs, smb_command);
+ SSVAL(outbuf, andx_cmd_ofs + 2, old_size - 4);
+
+ ofs = old_size;
+
+ /*
+ * Push the chained request:
+ *
+ * wct field
+ */
+
+ SCVAL(outbuf, ofs, wct);
+ ofs += 1;
+
+ /*
+ * vwv array
+ */
+
+ memcpy(outbuf + ofs, vwv, sizeof(uint16_t) * wct);
+
+ /*
+ * HACK ALERT
+ *
+ * Read&X has an offset into its data buffer at
+ * vwv[6]. reply_read_andx has no idea anymore that it's
+ * running from within a chain, so we have to fix up the
+ * offset here.
+ *
+ * Although it looks disgusting at this place, I want to keep
+ * it here. The alternative would be to push knowledge about
+ * the andx chain down into read&x again.
+ */
+
+ if (smb_command == SMBreadX) {
+ uint8_t *bytes_addr;
+
+ if (wct < 7) {
+ /*
+ * Invalid read&x response
+ */
+ return false;
+ }
+
+ bytes_addr = outbuf + ofs /* vwv start */
+ + sizeof(uint16_t) * wct /* vwv array */
+ + sizeof(uint16_t) /* bcc */
+ + 1; /* padding byte */
+
+ SSVAL(outbuf + ofs, 6 * sizeof(uint16_t),
+ bytes_addr - outbuf - 4);
+ }
+
+ ofs += sizeof(uint16_t) * wct;
+
+ /*
+ * bcc (byte count)
+ */
+
+ SSVAL(outbuf, ofs, num_bytes);
+ ofs += sizeof(uint16_t);
+
+ /*
+ * The bytes field
+ */
+
+ memcpy(outbuf + ofs, bytes, num_bytes);
+
+ return true;
+}
+
+bool smb1_is_chain(const uint8_t *buf)
+{
+ uint8_t cmd, wct, andx_cmd;
+
+ cmd = CVAL(buf, smb_com);
+ if (!smb1cli_is_andx_req(cmd)) {
+ return false;
+ }
+ wct = CVAL(buf, smb_wct);
+ if (wct < 2) {
+ return false;
+ }
+ andx_cmd = CVAL(buf, smb_vwv);
+ return (andx_cmd != 0xFF);
+}
+
+bool smb1_walk_chain(const uint8_t *buf,
+ bool (*fn)(uint8_t cmd,
+ uint8_t wct, const uint16_t *vwv,
+ uint16_t num_bytes, const uint8_t *bytes,
+ void *private_data),
+ void *private_data)
+{
+ size_t smblen = smb_len(buf);
+ const char *smb_buf = smb_base(buf);
+ uint8_t cmd, chain_cmd;
+ uint8_t wct;
+ const uint16_t *vwv;
+ uint16_t num_bytes;
+ const uint8_t *bytes;
+
+ cmd = CVAL(buf, smb_com);
+ wct = CVAL(buf, smb_wct);
+ vwv = (const uint16_t *)(buf + smb_vwv);
+ num_bytes = smb_buflen(buf);
+ bytes = (const uint8_t *)smb_buf_const(buf);
+
+ if (!fn(cmd, wct, vwv, num_bytes, bytes, private_data)) {
+ return false;
+ }
+
+ if (!smb1cli_is_andx_req(cmd)) {
+ return true;
+ }
+ if (wct < 2) {
+ return false;
+ }
+
+ chain_cmd = CVAL(vwv, 0);
+
+ while (chain_cmd != 0xff) {
+ uint32_t chain_offset; /* uint32_t to avoid overflow */
+ size_t length_needed;
+ ptrdiff_t vwv_offset;
+
+ chain_offset = SVAL(vwv+1, 0);
+
+ /*
+ * Check if the client tries to fool us. The chain
+ * offset needs to point beyond the current request in
+ * the chain, it needs to strictly grow. Otherwise we
+ * might be tricked into an endless loop always
+ * processing the same request over and over again. We
+ * used to assume that vwv and the byte buffer array
+ * in a chain are always attached, but OS/2 the
+ * Write&X/Read&X chain puts the Read&X vwv array
+ * right behind the Write&X vwv chain. The Write&X bcc
+ * array is put behind the Read&X vwv array. So now we
+ * check whether the chain offset points strictly
+ * behind the previous vwv array. req->buf points
+ * right after the vwv array of the previous
+ * request. See
+ * https://bugzilla.samba.org/show_bug.cgi?id=8360 for
+ * more information.
+ */
+
+ vwv_offset = ((const char *)vwv - smb_buf);
+ if (chain_offset <= vwv_offset) {
+ return false;
+ }
+
+ /*
+ * Next check: Make sure the chain offset does not
+ * point beyond the overall smb request length.
+ */
+
+ length_needed = chain_offset+1; /* wct */
+ if (length_needed > smblen) {
+ return false;
+ }
+
+ /*
+ * Now comes the pointer magic. Goal here is to set up
+ * vwv and buf correctly again. The chain offset (the
+ * former vwv[1]) points at the new wct field.
+ */
+
+ wct = CVAL(smb_buf, chain_offset);
+
+ if (smb1cli_is_andx_req(chain_cmd) && (wct < 2)) {
+ return false;
+ }
+
+ /*
+ * Next consistency check: Make the new vwv array fits
+ * in the overall smb request.
+ */
+
+ length_needed += (wct+1)*sizeof(uint16_t); /* vwv+buflen */
+ if (length_needed > smblen) {
+ return false;
+ }
+ vwv = (const uint16_t *)(smb_buf + chain_offset + 1);
+
+ /*
+ * Now grab the new byte buffer....
+ */
+
+ num_bytes = SVAL(vwv+wct, 0);
+
+ /*
+ * .. and check that it fits.
+ */
+
+ length_needed += num_bytes;
+ if (length_needed > smblen) {
+ return false;
+ }
+ bytes = (const uint8_t *)(vwv+wct+1);
+
+ if (!fn(chain_cmd, wct, vwv, num_bytes, bytes, private_data)) {
+ return false;
+ }
+
+ if (!smb1cli_is_andx_req(chain_cmd)) {
+ return true;
+ }
+ chain_cmd = CVAL(vwv, 0);
+ }
+ return true;
+}
+
+static bool smb1_chain_length_cb(uint8_t cmd,
+ uint8_t wct, const uint16_t *vwv,
+ uint16_t num_bytes, const uint8_t *bytes,
+ void *private_data)
+{
+ unsigned *count = (unsigned *)private_data;
+ *count += 1;
+ return true;
+}
+
+unsigned smb1_chain_length(const uint8_t *buf)
+{
+ unsigned count = 0;
+
+ if (!smb1_walk_chain(buf, smb1_chain_length_cb, &count)) {
+ return 0;
+ }
+ return count;
+}
+
+struct smb1_parse_chain_state {
+ TALLOC_CTX *mem_ctx;
+ const uint8_t *buf;
+ struct smbd_server_connection *sconn;
+ struct smbXsrv_connection *xconn;
+ bool encrypted;
+ uint32_t seqnum;
+
+ struct smb_request **reqs;
+ unsigned num_reqs;
+};
+
+static bool smb1_parse_chain_cb(uint8_t cmd,
+ uint8_t wct, const uint16_t *vwv,
+ uint16_t num_bytes, const uint8_t *bytes,
+ void *private_data)
+{
+ struct smb1_parse_chain_state *state =
+ (struct smb1_parse_chain_state *)private_data;
+ struct smb_request **reqs;
+ struct smb_request *req;
+ bool ok;
+
+ reqs = talloc_realloc(state->mem_ctx, state->reqs,
+ struct smb_request *, state->num_reqs+1);
+ if (reqs == NULL) {
+ return false;
+ }
+ state->reqs = reqs;
+
+ req = talloc(reqs, struct smb_request);
+ if (req == NULL) {
+ return false;
+ }
+
+ ok = init_smb1_request(req, state->sconn, state->xconn, state->buf, 0,
+ state->encrypted, state->seqnum);
+ if (!ok) {
+ return false;
+ }
+ req->cmd = cmd;
+ req->wct = wct;
+ req->vwv = vwv;
+ req->buflen = num_bytes;
+ req->buf = bytes;
+
+ reqs[state->num_reqs] = req;
+ state->num_reqs += 1;
+ return true;
+}
+
+bool smb1_parse_chain(TALLOC_CTX *mem_ctx, const uint8_t *buf,
+ struct smbXsrv_connection *xconn,
+ bool encrypted, uint32_t seqnum,
+ struct smb_request ***reqs, unsigned *num_reqs)
+{
+ struct smbd_server_connection *sconn = NULL;
+ struct smb1_parse_chain_state state;
+ unsigned i;
+
+ if (xconn != NULL) {
+ sconn = xconn->client->sconn;
+ }
+
+ state.mem_ctx = mem_ctx;
+ state.buf = buf;
+ state.sconn = sconn;
+ state.xconn = xconn;
+ state.encrypted = encrypted;
+ state.seqnum = seqnum;
+ state.reqs = NULL;
+ state.num_reqs = 0;
+
+ if (!smb1_walk_chain(buf, smb1_parse_chain_cb, &state)) {
+ TALLOC_FREE(state.reqs);
+ return false;
+ }
+ for (i=0; i<state.num_reqs; i++) {
+ state.reqs[i]->chain = state.reqs;
+ }
+ *reqs = state.reqs;
+ *num_reqs = state.num_reqs;
+ return true;
+}
+
+static bool fd_is_readable(int fd)
+{
+ int ret, revents;
+
+ ret = poll_one_fd(fd, POLLIN|POLLHUP, 0, &revents);
+
+ return ((ret > 0) && ((revents & (POLLIN|POLLHUP|POLLERR)) != 0));
+
+}
+
+static void smbd_server_connection_write_handler(
+ struct smbXsrv_connection *xconn)
+{
+ /* TODO: make write nonblocking */
+}
+
+void smbd_smb1_server_connection_read_handler(struct smbXsrv_connection *xconn,
+ int fd)
+{
+ uint8_t *inbuf = NULL;
+ size_t inbuf_len = 0;
+ size_t unread_bytes = 0;
+ bool encrypted = false;
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ NTSTATUS status;
+ uint32_t seqnum;
+
+ bool async_echo = lp_async_smb_echo_handler();
+ bool from_client = false;
+
+ if (async_echo) {
+ if (fd_is_readable(xconn->smb1.echo_handler.trusted_fd)) {
+ /*
+ * This is the super-ugly hack to prefer the packets
+ * forwarded by the echo handler over the ones by the
+ * client directly
+ */
+ fd = xconn->smb1.echo_handler.trusted_fd;
+ }
+ }
+
+ from_client = (xconn->transport.sock == fd);
+
+ if (async_echo && from_client) {
+ smbd_lock_socket(xconn);
+
+ if (!fd_is_readable(fd)) {
+ DEBUG(10,("the echo listener was faster\n"));
+ smbd_unlock_socket(xconn);
+ return;
+ }
+ }
+
+ /* TODO: make this completely nonblocking */
+ status = receive_smb_talloc(mem_ctx, xconn, fd,
+ (char **)(void *)&inbuf,
+ 0, /* timeout */
+ &unread_bytes,
+ &encrypted,
+ &inbuf_len, &seqnum,
+ !from_client /* trusted channel */);
+
+ if (async_echo && from_client) {
+ smbd_unlock_socket(xconn);
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
+ goto process;
+ }
+ if (NT_STATUS_IS_ERR(status)) {
+ exit_server_cleanly("failed to receive smb request");
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return;
+ }
+
+process:
+ process_smb(xconn, inbuf, inbuf_len, unread_bytes, seqnum, encrypted);
+}
+
+static void smbd_server_echo_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct smbXsrv_connection *xconn =
+ talloc_get_type_abort(private_data,
+ struct smbXsrv_connection);
+
+ if (!NT_STATUS_IS_OK(xconn->transport.status)) {
+ /*
+ * we're not supposed to do any io
+ */
+ TEVENT_FD_NOT_READABLE(xconn->smb1.echo_handler.trusted_fde);
+ TEVENT_FD_NOT_WRITEABLE(xconn->smb1.echo_handler.trusted_fde);
+ return;
+ }
+
+ if (flags & TEVENT_FD_WRITE) {
+ smbd_server_connection_write_handler(xconn);
+ return;
+ }
+ if (flags & TEVENT_FD_READ) {
+ smbd_smb1_server_connection_read_handler(
+ xconn, xconn->smb1.echo_handler.trusted_fd);
+ return;
+ }
+}
+
+/*
+ * Send keepalive packets to our client
+ */
+bool keepalive_fn(const struct timeval *now, void *private_data)
+{
+ struct smbd_server_connection *sconn = talloc_get_type_abort(
+ private_data, struct smbd_server_connection);
+ struct smbXsrv_connection *xconn = NULL;
+ bool ret;
+
+ if (sconn->using_smb2) {
+ /* Don't do keepalives on an SMB2 connection. */
+ return false;
+ }
+
+ /*
+ * With SMB1 we only have 1 connection
+ */
+ xconn = sconn->client->connections;
+ smbd_lock_socket(xconn);
+ ret = send_keepalive(xconn->transport.sock);
+ smbd_unlock_socket(xconn);
+
+ if (!ret) {
+ int saved_errno = errno;
+ /*
+ * Try and give an error message saying what
+ * client failed.
+ */
+ DEBUG(0, ("send_keepalive failed for client %s. "
+ "Error %s - exiting\n",
+ smbXsrv_connection_dbg(xconn),
+ strerror(saved_errno)));
+ errno = saved_errno;
+ return False;
+ }
+ return True;
+}
+
+/*
+ * Read an smb packet in the echo handler child, giving the parent
+ * smbd one second to react once the socket becomes readable.
+ */
+
+struct smbd_echo_read_state {
+ struct tevent_context *ev;
+ struct smbXsrv_connection *xconn;
+
+ char *buf;
+ size_t buflen;
+ uint32_t seqnum;
+};
+
+static void smbd_echo_read_readable(struct tevent_req *subreq);
+static void smbd_echo_read_waited(struct tevent_req *subreq);
+
+static struct tevent_req *smbd_echo_read_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct smbXsrv_connection *xconn)
+{
+ struct tevent_req *req, *subreq;
+ struct smbd_echo_read_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_echo_read_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->xconn = xconn;
+
+ subreq = wait_for_read_send(state, ev, xconn->transport.sock, false);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smbd_echo_read_readable, req);
+ return req;
+}
+
+static void smbd_echo_read_readable(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smbd_echo_read_state *state = tevent_req_data(
+ req, struct smbd_echo_read_state);
+ bool ok;
+ int err;
+
+ ok = wait_for_read_recv(subreq, &err);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ tevent_req_nterror(req, map_nt_error_from_unix(err));
+ return;
+ }
+
+ /*
+ * Give the parent smbd one second to step in
+ */
+
+ subreq = tevent_wakeup_send(
+ state, state->ev, timeval_current_ofs(1, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, smbd_echo_read_waited, req);
+}
+
+static void smbd_echo_read_waited(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smbd_echo_read_state *state = tevent_req_data(
+ req, struct smbd_echo_read_state);
+ struct smbXsrv_connection *xconn = state->xconn;
+ bool ok;
+ NTSTATUS status;
+ size_t unread = 0;
+ bool encrypted;
+
+ ok = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ ok = smbd_lock_socket_internal(xconn);
+ if (!ok) {
+ tevent_req_nterror(req, map_nt_error_from_unix(errno));
+ DEBUG(0, ("%s: failed to lock socket\n", __location__));
+ return;
+ }
+
+ if (!fd_is_readable(xconn->transport.sock)) {
+ DEBUG(10,("echo_handler[%d] the parent smbd was faster\n",
+ (int)getpid()));
+
+ ok = smbd_unlock_socket_internal(xconn);
+ if (!ok) {
+ tevent_req_nterror(req, map_nt_error_from_unix(errno));
+ DEBUG(1, ("%s: failed to unlock socket\n",
+ __location__));
+ return;
+ }
+
+ subreq = wait_for_read_send(state, state->ev,
+ xconn->transport.sock, false);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, smbd_echo_read_readable, req);
+ return;
+ }
+
+ status = receive_smb_talloc(state, xconn,
+ xconn->transport.sock,
+ &state->buf,
+ 0 /* timeout */,
+ &unread,
+ &encrypted,
+ &state->buflen,
+ &state->seqnum,
+ false /* trusted_channel*/);
+
+ if (tevent_req_nterror(req, status)) {
+ tevent_req_nterror(req, status);
+ DEBUG(1, ("echo_handler[%d]: receive_smb_raw_talloc failed: %s\n",
+ (int)getpid(), nt_errstr(status)));
+ return;
+ }
+
+ ok = smbd_unlock_socket_internal(xconn);
+ if (!ok) {
+ tevent_req_nterror(req, map_nt_error_from_unix(errno));
+ DEBUG(1, ("%s: failed to unlock socket\n", __location__));
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS smbd_echo_read_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ char **pbuf, size_t *pbuflen, uint32_t *pseqnum)
+{
+ struct smbd_echo_read_state *state = tevent_req_data(
+ req, struct smbd_echo_read_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *pbuf = talloc_move(mem_ctx, &state->buf);
+ *pbuflen = state->buflen;
+ *pseqnum = state->seqnum;
+ return NT_STATUS_OK;
+}
+
+struct smbd_echo_state {
+ struct tevent_context *ev;
+ struct iovec *pending;
+ struct smbd_server_connection *sconn;
+ struct smbXsrv_connection *xconn;
+ int parent_pipe;
+
+ struct tevent_fd *parent_fde;
+
+ struct tevent_req *write_req;
+};
+
+static void smbd_echo_writer_done(struct tevent_req *req);
+
+static void smbd_echo_activate_writer(struct smbd_echo_state *state)
+{
+ int num_pending;
+
+ if (state->write_req != NULL) {
+ return;
+ }
+
+ num_pending = talloc_array_length(state->pending);
+ if (num_pending == 0) {
+ return;
+ }
+
+ state->write_req = writev_send(state, state->ev, NULL,
+ state->parent_pipe, false,
+ state->pending, num_pending);
+ if (state->write_req == NULL) {
+ DEBUG(1, ("writev_send failed\n"));
+ exit(1);
+ }
+
+ talloc_steal(state->write_req, state->pending);
+ state->pending = NULL;
+
+ tevent_req_set_callback(state->write_req, smbd_echo_writer_done,
+ state);
+}
+
+static void smbd_echo_writer_done(struct tevent_req *req)
+{
+ struct smbd_echo_state *state = tevent_req_callback_data(
+ req, struct smbd_echo_state);
+ ssize_t written;
+ int err;
+
+ written = writev_recv(req, &err);
+ TALLOC_FREE(req);
+ state->write_req = NULL;
+ if (written == -1) {
+ DEBUG(1, ("writev to parent failed: %s\n", strerror(err)));
+ exit(1);
+ }
+ DEBUG(10,("echo_handler[%d]: forwarded pdu to main\n", (int)getpid()));
+ smbd_echo_activate_writer(state);
+}
+
+static bool smbd_echo_reply(struct smbd_echo_state *state,
+ uint8_t *inbuf, size_t inbuf_len,
+ uint32_t seqnum)
+{
+ struct smb_request req;
+ uint16_t num_replies;
+ char *outbuf;
+ bool ok;
+
+ if ((inbuf_len == 4) && (CVAL(inbuf, 0) == NBSSkeepalive)) {
+ DEBUG(10, ("Got netbios keepalive\n"));
+ /*
+ * Just swallow it
+ */
+ return true;
+ }
+
+ if (inbuf_len < smb_size) {
+ DEBUG(10, ("Got short packet: %d bytes\n", (int)inbuf_len));
+ return false;
+ }
+ if (!valid_smb1_header(inbuf)) {
+ DEBUG(10, ("Got invalid SMB header\n"));
+ return false;
+ }
+
+ if (!init_smb1_request(&req, state->sconn, state->xconn, inbuf, 0, false,
+ seqnum)) {
+ return false;
+ }
+ req.inbuf = inbuf;
+
+ DEBUG(10, ("smbecho handler got cmd %d (%s)\n", (int)req.cmd,
+ smb_fn_name(req.cmd)));
+
+ if (req.cmd != SMBecho) {
+ return false;
+ }
+ if (req.wct < 1) {
+ return false;
+ }
+
+ num_replies = SVAL(req.vwv+0, 0);
+ if (num_replies != 1) {
+ /* Not a Windows "Hey, you're still there?" request */
+ return false;
+ }
+
+ if (!create_smb1_outbuf(talloc_tos(), &req, req.inbuf, &outbuf,
+ 1, req.buflen)) {
+ DEBUG(10, ("create_smb1_outbuf failed\n"));
+ return false;
+ }
+ req.outbuf = (uint8_t *)outbuf;
+
+ SSVAL(req.outbuf, smb_vwv0, num_replies);
+
+ if (req.buflen > 0) {
+ memcpy(smb_buf(req.outbuf), req.buf, req.buflen);
+ }
+
+ ok = smb1_srv_send(req.xconn, (char *)outbuf, true, seqnum + 1, false);
+ TALLOC_FREE(outbuf);
+ if (!ok) {
+ exit(1);
+ }
+
+ return true;
+}
+
+static void smbd_echo_exit(struct tevent_context *ev,
+ struct tevent_fd *fde, uint16_t flags,
+ void *private_data)
+{
+ DEBUG(2, ("smbd_echo_exit: lost connection to parent\n"));
+ exit(0);
+}
+
+static void smbd_echo_got_packet(struct tevent_req *req);
+
+static void smbd_echo_loop(struct smbXsrv_connection *xconn,
+ int parent_pipe)
+{
+ struct smbd_echo_state *state;
+ struct tevent_req *read_req;
+
+ state = talloc_zero(xconn, struct smbd_echo_state);
+ if (state == NULL) {
+ DEBUG(1, ("talloc failed\n"));
+ return;
+ }
+ state->xconn = xconn;
+ state->parent_pipe = parent_pipe;
+ state->ev = samba_tevent_context_init(state);
+ if (state->ev == NULL) {
+ DEBUG(1, ("samba_tevent_context_init failed\n"));
+ TALLOC_FREE(state);
+ return;
+ }
+ state->parent_fde = tevent_add_fd(state->ev, state, parent_pipe,
+ TEVENT_FD_READ, smbd_echo_exit,
+ state);
+ if (state->parent_fde == NULL) {
+ DEBUG(1, ("tevent_add_fd failed\n"));
+ TALLOC_FREE(state);
+ return;
+ }
+
+ read_req = smbd_echo_read_send(state, state->ev, xconn);
+ if (read_req == NULL) {
+ DEBUG(1, ("smbd_echo_read_send failed\n"));
+ TALLOC_FREE(state);
+ return;
+ }
+ tevent_req_set_callback(read_req, smbd_echo_got_packet, state);
+
+ while (true) {
+ if (tevent_loop_once(state->ev) == -1) {
+ DEBUG(1, ("tevent_loop_once failed: %s\n",
+ strerror(errno)));
+ break;
+ }
+ }
+ TALLOC_FREE(state);
+}
+
+static void smbd_echo_got_packet(struct tevent_req *req)
+{
+ struct smbd_echo_state *state = tevent_req_callback_data(
+ req, struct smbd_echo_state);
+ NTSTATUS status;
+ char *buf = NULL;
+ size_t buflen = 0;
+ uint32_t seqnum = 0;
+ bool reply;
+
+ status = smbd_echo_read_recv(req, state, &buf, &buflen, &seqnum);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("smbd_echo_read_recv returned %s\n",
+ nt_errstr(status)));
+ exit(1);
+ }
+
+ reply = smbd_echo_reply(state, (uint8_t *)buf, buflen, seqnum);
+ if (!reply) {
+ size_t num_pending;
+ struct iovec *tmp;
+ struct iovec *iov;
+
+ num_pending = talloc_array_length(state->pending);
+ tmp = talloc_realloc(state, state->pending, struct iovec,
+ num_pending+1);
+ if (tmp == NULL) {
+ DEBUG(1, ("talloc_realloc failed\n"));
+ exit(1);
+ }
+ state->pending = tmp;
+
+ if (buflen >= smb_size) {
+ /*
+ * place the seqnum in the packet so that the main process
+ * can reply with signing
+ */
+ SIVAL(buf, smb_ss_field, seqnum);
+ SIVAL(buf, smb_ss_field+4, NT_STATUS_V(NT_STATUS_OK));
+ }
+
+ iov = &state->pending[num_pending];
+ iov->iov_base = talloc_move(state->pending, &buf);
+ iov->iov_len = buflen;
+
+ DEBUG(10,("echo_handler[%d]: forward to main\n",
+ (int)getpid()));
+ smbd_echo_activate_writer(state);
+ }
+
+ req = smbd_echo_read_send(state, state->ev, state->xconn);
+ if (req == NULL) {
+ DEBUG(1, ("smbd_echo_read_send failed\n"));
+ exit(1);
+ }
+ tevent_req_set_callback(req, smbd_echo_got_packet, state);
+}
+
+
+/*
+ * Handle SMBecho requests in a forked child process
+ */
+bool fork_echo_handler(struct smbXsrv_connection *xconn)
+{
+ int listener_pipe[2];
+ int res;
+ pid_t child;
+ bool use_mutex = false;
+
+ res = pipe(listener_pipe);
+ if (res == -1) {
+ DEBUG(1, ("pipe() failed: %s\n", strerror(errno)));
+ return false;
+ }
+
+#ifdef HAVE_ROBUST_MUTEXES
+ use_mutex = tdb_runtime_check_for_robust_mutexes();
+
+ if (use_mutex) {
+ pthread_mutexattr_t a;
+
+ xconn->smb1.echo_handler.socket_mutex =
+ anonymous_shared_allocate(sizeof(pthread_mutex_t));
+ if (xconn->smb1.echo_handler.socket_mutex == NULL) {
+ DEBUG(1, ("Could not create mutex shared memory: %s\n",
+ strerror(errno)));
+ goto fail;
+ }
+
+ res = pthread_mutexattr_init(&a);
+ if (res != 0) {
+ DEBUG(1, ("pthread_mutexattr_init failed: %s\n",
+ strerror(res)));
+ goto fail;
+ }
+ res = pthread_mutexattr_settype(&a, PTHREAD_MUTEX_ERRORCHECK);
+ if (res != 0) {
+ DEBUG(1, ("pthread_mutexattr_settype failed: %s\n",
+ strerror(res)));
+ pthread_mutexattr_destroy(&a);
+ goto fail;
+ }
+ res = pthread_mutexattr_setpshared(&a, PTHREAD_PROCESS_SHARED);
+ if (res != 0) {
+ DEBUG(1, ("pthread_mutexattr_setpshared failed: %s\n",
+ strerror(res)));
+ pthread_mutexattr_destroy(&a);
+ goto fail;
+ }
+ res = pthread_mutexattr_setrobust(&a, PTHREAD_MUTEX_ROBUST);
+ if (res != 0) {
+ DEBUG(1, ("pthread_mutexattr_setrobust failed: "
+ "%s\n", strerror(res)));
+ pthread_mutexattr_destroy(&a);
+ goto fail;
+ }
+ res = pthread_mutex_init(xconn->smb1.echo_handler.socket_mutex,
+ &a);
+ pthread_mutexattr_destroy(&a);
+ if (res != 0) {
+ DEBUG(1, ("pthread_mutex_init failed: %s\n",
+ strerror(res)));
+ goto fail;
+ }
+ }
+#endif
+
+ if (!use_mutex) {
+ xconn->smb1.echo_handler.socket_lock_fd =
+ create_unlink_tmp(lp_lock_directory());
+ if (xconn->smb1.echo_handler.socket_lock_fd == -1) {
+ DEBUG(1, ("Could not create lock fd: %s\n",
+ strerror(errno)));
+ goto fail;
+ }
+ }
+
+ child = fork();
+ if (child == 0) {
+ NTSTATUS status;
+
+ close(listener_pipe[0]);
+ set_blocking(listener_pipe[1], false);
+
+ status = smbd_reinit_after_fork(xconn->client->msg_ctx,
+ xconn->client->raw_ev_ctx,
+ true);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("reinit_after_fork failed: %s\n",
+ nt_errstr(status)));
+ exit(1);
+ }
+ process_set_title("smbd-echo", "echo handler");
+ initialize_password_db(true, xconn->client->raw_ev_ctx);
+ smbd_echo_loop(xconn, listener_pipe[1]);
+ exit(0);
+ }
+ close(listener_pipe[1]);
+ listener_pipe[1] = -1;
+ xconn->smb1.echo_handler.trusted_fd = listener_pipe[0];
+
+ DEBUG(10,("fork_echo_handler: main[%d] echo_child[%d]\n", (int)getpid(), (int)child));
+
+ /*
+ * Without smb signing this is the same as the normal smbd
+ * listener. This needs to change once signing comes in.
+ */
+ xconn->smb1.echo_handler.trusted_fde = tevent_add_fd(
+ xconn->client->raw_ev_ctx,
+ xconn,
+ xconn->smb1.echo_handler.trusted_fd,
+ TEVENT_FD_READ,
+ smbd_server_echo_handler,
+ xconn);
+ if (xconn->smb1.echo_handler.trusted_fde == NULL) {
+ DEBUG(1, ("event_add_fd failed\n"));
+ goto fail;
+ }
+
+ return true;
+
+fail:
+ if (listener_pipe[0] != -1) {
+ close(listener_pipe[0]);
+ }
+ if (listener_pipe[1] != -1) {
+ close(listener_pipe[1]);
+ }
+ if (xconn->smb1.echo_handler.socket_lock_fd != -1) {
+ close(xconn->smb1.echo_handler.socket_lock_fd);
+ }
+#ifdef HAVE_ROBUST_MUTEXES
+ if (xconn->smb1.echo_handler.socket_mutex != NULL) {
+ pthread_mutex_destroy(xconn->smb1.echo_handler.socket_mutex);
+ anonymous_shared_free(xconn->smb1.echo_handler.socket_mutex);
+ }
+#endif
+ smbd_echo_init(xconn);
+
+ return false;
+}
+
+bool req_is_in_chain(const struct smb_request *req)
+{
+ if (req->vwv != (const uint16_t *)(req->inbuf+smb_vwv)) {
+ /*
+ * We're right now handling a subsequent request, so we must
+ * be in a chain
+ */
+ return true;
+ }
+
+ if (!smb1cli_is_andx_req(req->cmd)) {
+ return false;
+ }
+
+ if (req->wct < 2) {
+ /*
+ * Okay, an illegal request, but definitely not chained :-)
+ */
+ return false;
+ }
+
+ return (CVAL(req->vwv+0, 0) != 0xFF);
+}
diff --git a/source3/smbd/smb1_process.h b/source3/smbd/smb1_process.h
new file mode 100644
index 0000000..1e36248
--- /dev/null
+++ b/source3/smbd/smb1_process.h
@@ -0,0 +1,72 @@
+/*
+ Unix SMB/CIFS implementation.
+ process incoming packets - main loop
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Volker Lendecke 2005-2007
+
+ 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/>.
+*/
+
+bool smb1_srv_send(struct smbXsrv_connection *xconn,
+ char *buffer,
+ bool do_signing,
+ uint32_t seqnum,
+ bool do_encrypt);
+NTSTATUS allow_new_trans(struct trans_state *list, uint64_t mid);
+void smb_request_done(struct smb_request *req);
+const char *smb_fn_name(int type);
+void add_to_common_flags2(uint32_t v);
+void remove_from_common_flags2(uint32_t v);
+bool smb1_is_chain(const uint8_t *buf);
+bool smb1_walk_chain(const uint8_t *buf,
+ bool (*fn)(uint8_t cmd,
+ uint8_t wct, const uint16_t *vwv,
+ uint16_t num_bytes, const uint8_t *bytes,
+ void *private_data),
+ void *private_data);
+unsigned smb1_chain_length(const uint8_t *buf);
+bool smb1_parse_chain(TALLOC_CTX *mem_ctx, const uint8_t *buf,
+ struct smbXsrv_connection *xconn,
+ bool encrypted, uint32_t seqnum,
+ struct smb_request ***reqs, unsigned *num_reqs);
+bool req_is_in_chain(const struct smb_request *req);
+bool fork_echo_handler(struct smbXsrv_connection *xconn);
+NTSTATUS smb1_receive_talloc(TALLOC_CTX *mem_ctx,
+ struct smbXsrv_connection *xconn,
+ int sock,
+ char **buffer, unsigned int timeout,
+ size_t *p_unread, bool *p_encrypted,
+ size_t *p_len,
+ uint32_t *seqnum,
+ bool trusted_channel);
+bool push_deferred_open_message_smb1(struct smb_request *req,
+ struct timeval timeout,
+ struct file_id id,
+ struct deferred_open_record *open_rec);
+void process_smb1(struct smbXsrv_connection *xconn,
+ uint8_t *inbuf,
+ size_t nread,
+ size_t unread_bytes,
+ uint32_t seqnum,
+ bool encrypted);
+void smbd_echo_init(struct smbXsrv_connection *xconn);
+void construct_reply(struct smbXsrv_connection *xconn,
+ char *inbuf,
+ int size,
+ size_t unread_bytes,
+ uint32_t seqnum,
+ bool encrypted);
+void smbd_smb1_server_connection_read_handler(struct smbXsrv_connection *xconn,
+ int fd);
+bool keepalive_fn(const struct timeval *now, void *private_data);
diff --git a/source3/smbd/smb1_reply.c b/source3/smbd/smb1_reply.c
new file mode 100644
index 0000000..b2f317a
--- /dev/null
+++ b/source3/smbd/smb1_reply.c
@@ -0,0 +1,7178 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main SMB reply routines
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Andrew Bartlett 2001
+ Copyright (C) Jeremy Allison 1992-2007.
+ Copyright (C) Volker Lendecke 2007
+
+ 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/>.
+*/
+/*
+ This file handles most of the reply_ calls that the server
+ makes to handle specific protocols
+*/
+
+#include "includes.h"
+#include "libsmb/namequery.h"
+#include "system/filesys.h"
+#include "printing.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "smbd/smbXsrv_open.h"
+#include "fake_file.h"
+#include "rpc_client/rpc_client.h"
+#include "../librpc/gen_ndr/ndr_spoolss_c.h"
+#include "rpc_client/cli_spoolss.h"
+#include "rpc_client/init_spoolss.h"
+#include "rpc_server/rpc_ncacn_np.h"
+#include "libcli/security/security.h"
+#include "libsmb/nmblib.h"
+#include "auth.h"
+#include "smbprofile.h"
+#include "../lib/tsocket/tsocket.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "libcli/smb/smb_signing.h"
+#include "lib/util/sys_rw_data.h"
+#include "librpc/gen_ndr/open_files.h"
+#include "libcli/smb/smb2_posix.h"
+#include "lib/util/string_wrappers.h"
+#include "source3/printing/rap_jobid.h"
+#include "source3/lib/substitute.h"
+#include "source3/smbd/dir.h"
+
+/****************************************************************************
+ Check if we have a correct fsp pointing to a file. Basic check for open fsp.
+****************************************************************************/
+
+bool check_fsp_open(connection_struct *conn, struct smb_request *req,
+ files_struct *fsp)
+{
+ if ((fsp == NULL) || (conn == NULL)) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return false;
+ }
+ if ((conn != fsp->conn) || (req->vuid != fsp->vuid)) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return false;
+ }
+ return true;
+}
+
+/****************************************************************************
+ SMB1 version of smb2_strip_dfs_path()
+ Differs from SMB2 in that all Windows path separator '\' characters
+ have already been converted to '/' by check_path_syntax().
+****************************************************************************/
+
+NTSTATUS smb1_strip_dfs_path(TALLOC_CTX *mem_ctx,
+ uint32_t *_ucf_flags,
+ char **in_path)
+{
+ uint32_t ucf_flags = *_ucf_flags;
+ char *path = *in_path;
+ char *return_path = NULL;
+
+ if (!(ucf_flags & UCF_DFS_PATHNAME)) {
+ return NT_STATUS_OK;
+ }
+
+ /* Strip any leading '/' characters - MacOSX client behavior. */
+ while (*path == '/') {
+ path++;
+ }
+
+ /* We should now be pointing at the server name. Go past it. */
+ for (;;) {
+ if (*path == '\0') {
+ /* End of complete path. Exit OK. */
+ goto done;
+ }
+ if (*path == '/') {
+ /* End of server name. Go past and break. */
+ path++;
+ break;
+ }
+ path++; /* Continue looking for end of server name or string. */
+ }
+
+ /* We should now be pointing at the share name. Go past it. */
+ for (;;) {
+ if (*path == '\0') {
+ /* End of complete path. Exit OK. */
+ goto done;
+ }
+ if (*path == '/') {
+ /* End of share name. Go past and break. */
+ path++;
+ break;
+ }
+ if (*path == ':') {
+ /* Only invalid character in sharename. */
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ path++; /* Continue looking for end of share name or string. */
+ }
+
+ done:
+ /* path now points at the start of the real filename (if any). */
+ /* Duplicate it first. */
+ return_path = talloc_strdup(mem_ctx, path);
+ if (return_path == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Now we can free the original (path points to part of this). */
+ TALLOC_FREE(*in_path);
+
+ *in_path = return_path;
+ ucf_flags &= ~UCF_DFS_PATHNAME;
+ *_ucf_flags = ucf_flags;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Check if we have a correct fsp pointing to a file.
+****************************************************************************/
+
+bool check_fsp(connection_struct *conn, struct smb_request *req,
+ files_struct *fsp)
+{
+ if (!check_fsp_open(conn, req, fsp)) {
+ return false;
+ }
+ if (fsp->fsp_flags.is_directory) {
+ reply_nterror(req, NT_STATUS_INVALID_DEVICE_REQUEST);
+ return false;
+ }
+ if (fsp_get_pathref_fd(fsp) == -1) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return false;
+ }
+ fsp->num_smb_operations++;
+ return true;
+}
+
+/****************************************************************************
+ Reply to a tcon.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_tcon(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ const char *service;
+ char *service_buf = NULL;
+ char *password = NULL;
+ char *dev = NULL;
+ int pwlen=0;
+ NTSTATUS nt_status;
+ const uint8_t *p;
+ const char *p2;
+ TALLOC_CTX *ctx = talloc_tos();
+ struct smbXsrv_connection *xconn = req->xconn;
+ NTTIME now = timeval_to_nttime(&req->request_time);
+
+ START_PROFILE(SMBtcon);
+
+ if (req->buflen < 4) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtcon);
+ return;
+ }
+
+ p = req->buf + 1;
+ p += srvstr_pull_req_talloc(ctx, req, &service_buf, p, STR_TERMINATE);
+ p += 1;
+ pwlen = srvstr_pull_req_talloc(ctx, req, &password, p, STR_TERMINATE);
+ p += pwlen+1;
+ p += srvstr_pull_req_talloc(ctx, req, &dev, p, STR_TERMINATE);
+ p += 1;
+
+ if (service_buf == NULL || password == NULL || dev == NULL) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtcon);
+ return;
+ }
+ p2 = strrchr_m(service_buf,'\\');
+ if (p2) {
+ service = p2+1;
+ } else {
+ service = service_buf;
+ }
+
+ conn = make_connection(req, now, service, dev,
+ req->vuid,&nt_status);
+ req->conn = conn;
+
+ if (!conn) {
+ reply_nterror(req, nt_status);
+ END_PROFILE(SMBtcon);
+ return;
+ }
+
+ reply_smb1_outbuf(req, 2, 0);
+ SSVAL(req->outbuf,smb_vwv0,xconn->smb1.negprot.max_recv);
+ SSVAL(req->outbuf,smb_vwv1,conn->cnum);
+ SSVAL(req->outbuf,smb_tid,conn->cnum);
+
+ DEBUG(3,("tcon service=%s cnum=%d\n",
+ service, conn->cnum));
+
+ END_PROFILE(SMBtcon);
+ return;
+}
+
+/****************************************************************************
+ Reply to a tcon and X.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_tcon_and_X(struct smb_request *req)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ connection_struct *conn = req->conn;
+ const char *service = NULL;
+ TALLOC_CTX *ctx = talloc_tos();
+ /* what the client thinks the device is */
+ char *client_devicetype = NULL;
+ /* what the server tells the client the share represents */
+ const char *server_devicetype;
+ NTSTATUS nt_status;
+ int passlen;
+ char *path = NULL;
+ const uint8_t *p;
+ const char *q;
+ uint16_t tcon_flags;
+ struct smbXsrv_session *session = NULL;
+ NTTIME now = timeval_to_nttime(&req->request_time);
+ bool session_key_updated = false;
+ uint16_t optional_support = 0;
+ struct smbXsrv_connection *xconn = req->xconn;
+
+ START_PROFILE(SMBtconX);
+
+ if (req->wct < 4) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+
+ passlen = SVAL(req->vwv+3, 0);
+ tcon_flags = SVAL(req->vwv+2, 0);
+
+ /* we might have to close an old one */
+ if ((tcon_flags & TCONX_FLAG_DISCONNECT_TID) && conn) {
+ struct smbXsrv_tcon *tcon;
+ NTSTATUS status;
+
+ tcon = conn->tcon;
+ req->conn = NULL;
+ conn = NULL;
+
+ /*
+ * TODO: cancel all outstanding requests on the tcon
+ */
+ status = smbXsrv_tcon_disconnect(tcon, req->vuid);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("reply_tcon_and_X: "
+ "smbXsrv_tcon_disconnect() failed: %s\n",
+ nt_errstr(status)));
+ /*
+ * If we hit this case, there is something completely
+ * wrong, so we better disconnect the transport connection.
+ */
+ END_PROFILE(SMBtconX);
+ exit_server(__location__ ": smbXsrv_tcon_disconnect failed");
+ return;
+ }
+
+ TALLOC_FREE(tcon);
+ /*
+ * This tree id is gone. Make sure we can't re-use it
+ * by accident.
+ */
+ req->tid = 0;
+ }
+
+ if ((passlen > MAX_PASS_LEN) || (passlen >= req->buflen)) {
+ reply_force_doserror(req, ERRDOS, ERRbuftoosmall);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+
+ if (xconn->smb1.negprot.encrypted_passwords) {
+ p = req->buf + passlen;
+ } else {
+ p = req->buf + passlen + 1;
+ }
+
+ p += srvstr_pull_req_talloc(ctx, req, &path, p, STR_TERMINATE);
+
+ if (path == NULL) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+
+ /*
+ * the service name can be either: \\server\share
+ * or share directly like on the DELL PowerVault 705
+ */
+ if (*path=='\\') {
+ q = strchr_m(path+2,'\\');
+ if (!q) {
+ reply_nterror(req, NT_STATUS_BAD_NETWORK_NAME);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+ service = q+1;
+ } else {
+ service = path;
+ }
+
+ p += srvstr_pull_talloc(ctx, req->inbuf, req->flags2,
+ &client_devicetype, p,
+ MIN(6, smbreq_bufrem(req, p)), STR_ASCII);
+
+ if (client_devicetype == NULL) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+
+ DEBUG(4,("Client requested device type [%s] for share [%s]\n", client_devicetype, service));
+
+ nt_status = smb1srv_session_lookup(xconn,
+ req->vuid, now, &session);
+ if (NT_STATUS_EQUAL(nt_status, NT_STATUS_USER_SESSION_DELETED)) {
+ reply_force_doserror(req, ERRSRV, ERRbaduid);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+ if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NETWORK_SESSION_EXPIRED)) {
+ reply_nterror(req, nt_status);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+
+ if (session->global->auth_session_info == NULL) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+
+ /*
+ * If there is no application key defined yet
+ * we create one.
+ *
+ * This means we setup the application key on the
+ * first tcon that happens via the given session.
+ *
+ * Once the application key is defined, it does not
+ * change any more.
+ */
+ if (session->global->application_key_blob.length == 0 &&
+ smb2_signing_key_valid(session->global->signing_key))
+ {
+ struct smbXsrv_session *x = session;
+ struct auth_session_info *session_info =
+ session->global->auth_session_info;
+ uint8_t session_key[16];
+
+ ZERO_STRUCT(session_key);
+ memcpy(session_key, x->global->signing_key->blob.data,
+ MIN(x->global->signing_key->blob.length, sizeof(session_key)));
+
+ /*
+ * The application key is truncated/padded to 16 bytes
+ */
+ x->global->application_key_blob = data_blob_talloc(x->global,
+ session_key,
+ sizeof(session_key));
+ ZERO_STRUCT(session_key);
+ if (x->global->application_key_blob.data == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+ talloc_keep_secret(x->global->application_key_blob.data);
+
+ if (tcon_flags & TCONX_FLAG_EXTENDED_SIGNATURES) {
+ NTSTATUS status;
+
+ status = smb1_key_derivation(x->global->application_key_blob.data,
+ x->global->application_key_blob.length,
+ x->global->application_key_blob.data);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smb1_key_derivation failed: %s\n",
+ nt_errstr(status));
+ END_PROFILE(SMBtconX);
+ return;
+ }
+ optional_support |= SMB_EXTENDED_SIGNATURES;
+ }
+
+ /*
+ * Place the application key into the session_info
+ */
+ data_blob_clear_free(&session_info->session_key);
+ session_info->session_key = data_blob_dup_talloc(session_info,
+ x->global->application_key_blob);
+ if (session_info->session_key.data == NULL) {
+ data_blob_clear_free(&x->global->application_key_blob);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+ talloc_keep_secret(session_info->session_key.data);
+ session_key_updated = true;
+ }
+
+ conn = make_connection(req, now, service, client_devicetype,
+ req->vuid, &nt_status);
+ req->conn =conn;
+
+ if (!conn) {
+ if (session_key_updated) {
+ struct smbXsrv_session *x = session;
+ struct auth_session_info *session_info =
+ session->global->auth_session_info;
+ data_blob_clear_free(&x->global->application_key_blob);
+ data_blob_clear_free(&session_info->session_key);
+ }
+ reply_nterror(req, nt_status);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+
+ if ( IS_IPC(conn) )
+ server_devicetype = "IPC";
+ else if ( IS_PRINT(conn) )
+ server_devicetype = "LPT1:";
+ else
+ server_devicetype = "A:";
+
+ if (xconn->protocol < PROTOCOL_NT1) {
+ reply_smb1_outbuf(req, 2, 0);
+ if (message_push_string(&req->outbuf, server_devicetype,
+ STR_TERMINATE|STR_ASCII) == -1) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+ } else {
+ /* NT sets the fstype of IPC$ to the null string */
+ const char *fstype = IS_IPC(conn) ? "" : lp_fstype(SNUM(conn));
+
+ if (tcon_flags & TCONX_FLAG_EXTENDED_RESPONSE) {
+ /* Return permissions. */
+ uint32_t perm1 = 0;
+ uint32_t perm2 = 0;
+
+ reply_smb1_outbuf(req, 7, 0);
+
+ if (IS_IPC(conn)) {
+ perm1 = FILE_ALL_ACCESS;
+ perm2 = FILE_ALL_ACCESS;
+ } else {
+ perm1 = conn->share_access;
+ }
+
+ SIVAL(req->outbuf, smb_vwv3, perm1);
+ SIVAL(req->outbuf, smb_vwv5, perm2);
+ } else {
+ reply_smb1_outbuf(req, 3, 0);
+ }
+
+ if ((message_push_string(&req->outbuf, server_devicetype,
+ STR_TERMINATE|STR_ASCII) == -1)
+ || (message_push_string(&req->outbuf, fstype,
+ STR_TERMINATE) == -1)) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+
+ /* what does setting this bit do? It is set by NT4 and
+ may affect the ability to autorun mounted cdroms */
+ optional_support |= SMB_SUPPORT_SEARCH_BITS;
+ optional_support |=
+ (lp_csc_policy(SNUM(conn)) << SMB_CSC_POLICY_SHIFT);
+
+ if (lp_msdfs_root(SNUM(conn)) && lp_host_msdfs()) {
+ DEBUG(2,("Serving %s as a Dfs root\n",
+ lp_servicename(ctx, lp_sub, SNUM(conn)) ));
+ optional_support |= SMB_SHARE_IN_DFS;
+ }
+
+ SSVAL(req->outbuf, smb_vwv2, optional_support);
+ }
+
+ SSVAL(req->outbuf, smb_vwv0, 0xff); /* andx chain ends */
+ SSVAL(req->outbuf, smb_vwv1, 0); /* no andx offset */
+
+ DEBUG(3,("tconX service=%s \n",
+ service));
+
+ /* set the incoming and outgoing tid to the just created one */
+ SSVAL(discard_const_p(uint8_t, req->inbuf),smb_tid,conn->cnum);
+ SSVAL(req->outbuf,smb_tid,conn->cnum);
+
+ END_PROFILE(SMBtconX);
+
+ req->tid = conn->cnum;
+}
+
+/****************************************************************************
+ Reply to an unknown type.
+****************************************************************************/
+
+void reply_unknown_new(struct smb_request *req, uint8_t type)
+{
+ DEBUG(0, ("unknown command type (%s): type=%d (0x%X)\n",
+ smb_fn_name(type), type, type));
+ reply_force_doserror(req, ERRSRV, ERRunknownsmb);
+ return;
+}
+
+/****************************************************************************
+ Reply to an ioctl.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_ioctl(struct smb_request *req)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ connection_struct *conn = req->conn;
+ uint16_t device;
+ uint16_t function;
+ uint32_t ioctl_code;
+ int replysize;
+ char *p;
+
+ START_PROFILE(SMBioctl);
+
+ if (req->wct < 3) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBioctl);
+ return;
+ }
+
+ device = SVAL(req->vwv+1, 0);
+ function = SVAL(req->vwv+2, 0);
+ ioctl_code = (device << 16) + function;
+
+ DEBUG(4, ("Received IOCTL (code 0x%x)\n", ioctl_code));
+
+ switch (ioctl_code) {
+ case IOCTL_QUERY_JOB_INFO:
+ replysize = 32;
+ break;
+ default:
+ reply_force_doserror(req, ERRSRV, ERRnosupport);
+ END_PROFILE(SMBioctl);
+ return;
+ }
+
+ reply_smb1_outbuf(req, 8, replysize+1);
+ SSVAL(req->outbuf,smb_vwv1,replysize); /* Total data bytes returned */
+ SSVAL(req->outbuf,smb_vwv5,replysize); /* Data bytes this buffer */
+ SSVAL(req->outbuf,smb_vwv6,52); /* Offset to data */
+ p = smb_buf(req->outbuf);
+ memset(p, '\0', replysize+1); /* valgrind-safe. */
+ p += 1; /* Allow for alignment */
+
+ switch (ioctl_code) {
+ case IOCTL_QUERY_JOB_INFO:
+ {
+ NTSTATUS status;
+ size_t len = 0;
+ files_struct *fsp = file_fsp(
+ req, SVAL(req->vwv+0, 0));
+ if (!fsp) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ END_PROFILE(SMBioctl);
+ return;
+ }
+ /* Job number */
+ SSVAL(p, 0, print_spool_rap_jobid(fsp->print_file));
+
+ status = srvstr_push((char *)req->outbuf, req->flags2, p+2,
+ lp_netbios_name(), 15,
+ STR_TERMINATE|STR_ASCII, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBioctl);
+ return;
+ }
+ if (conn) {
+ status = srvstr_push((char *)req->outbuf, req->flags2,
+ p+18,
+ lp_servicename(talloc_tos(),
+ lp_sub,
+ SNUM(conn)),
+ 13, STR_TERMINATE|STR_ASCII, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBioctl);
+ return;
+ }
+ } else {
+ memset(p+18, 0, 13);
+ }
+ break;
+ }
+ }
+
+ END_PROFILE(SMBioctl);
+ return;
+}
+
+/****************************************************************************
+ Strange checkpath NTSTATUS mapping.
+****************************************************************************/
+
+static NTSTATUS map_checkpath_error(uint16_t flags2, NTSTATUS status)
+{
+ /* Strange DOS error code semantics only for checkpath... */
+ if (!(flags2 & FLAGS2_32_BIT_ERROR_CODES)) {
+ if (NT_STATUS_EQUAL(NT_STATUS_OBJECT_NAME_INVALID,status)) {
+ /* We need to map to ERRbadpath */
+ return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ }
+ }
+ return status;
+}
+
+/****************************************************************************
+ Reply to a checkpath.
+****************************************************************************/
+
+void reply_checkpath(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ struct smb_filename *smb_fname = NULL;
+ char *name = NULL;
+ NTSTATUS status;
+ struct files_struct *dirfsp = NULL;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+ NTTIME twrp = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBcheckpath);
+
+ srvstr_get_path_req(ctx, req, &name, (const char *)req->buf + 1,
+ STR_TERMINATE, &status);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ status = map_checkpath_error(req->flags2, status);
+ reply_nterror(req, status);
+ END_PROFILE(SMBcheckpath);
+ return;
+ }
+
+ DEBUG(3,("reply_checkpath %s mode=%d\n", name, (int)SVAL(req->vwv+0, 0)));
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(name, &twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &name);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ name,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ END_PROFILE(SMBcheckpath);
+ return;
+ }
+ goto path_err;
+ }
+
+ if (!VALID_STAT(smb_fname->st) &&
+ (SMB_VFS_STAT(conn, smb_fname) != 0)) {
+ DEBUG(3,("reply_checkpath: stat of %s failed (%s)\n",
+ smb_fname_str_dbg(smb_fname), strerror(errno)));
+ status = map_nt_error_from_unix(errno);
+ goto path_err;
+ }
+
+ if (!S_ISDIR(smb_fname->st.st_ex_mode)) {
+ reply_botherror(req, NT_STATUS_NOT_A_DIRECTORY,
+ ERRDOS, ERRbadpath);
+ goto out;
+ }
+
+ reply_smb1_outbuf(req, 0, 0);
+
+ path_err:
+ /* We special case this - as when a Windows machine
+ is parsing a path is steps through the components
+ one at a time - if a component fails it expects
+ ERRbadpath, not ERRbadfile.
+ */
+ status = map_checkpath_error(req->flags2, status);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ /*
+ * Windows returns different error codes if
+ * the parent directory is valid but not the
+ * last component - it returns NT_STATUS_OBJECT_NAME_NOT_FOUND
+ * for that case and NT_STATUS_OBJECT_PATH_NOT_FOUND
+ * if the path is invalid.
+ */
+ reply_botherror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND,
+ ERRDOS, ERRbadpath);
+ goto out;
+ }
+
+ reply_nterror(req, status);
+
+ out:
+ TALLOC_FREE(smb_fname);
+ END_PROFILE(SMBcheckpath);
+ return;
+}
+
+/****************************************************************************
+ Reply to a getatr.
+****************************************************************************/
+
+void reply_getatr(struct smb_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ connection_struct *conn = req->conn;
+ struct smb_filename *smb_fname = NULL;
+ char *fname = NULL;
+ int mode=0;
+ off_t size=0;
+ time_t mtime=0;
+ const char *p;
+ NTSTATUS status;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBgetatr);
+
+ p = (const char *)req->buf + 1;
+ p += srvstr_get_path_req(ctx, req, &fname, p, STR_TERMINATE, &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ /*
+ * dos sometimes asks for a stat of "" - it returns a "hidden
+ * directory" under WfWg - weird!
+ */
+ if (*fname == '\0') {
+ mode = FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_DIRECTORY;
+ if (!CAN_WRITE(conn)) {
+ mode |= FILE_ATTRIBUTE_READONLY;
+ }
+ size = 0;
+ mtime = 0;
+ } else {
+ struct files_struct *dirfsp = NULL;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+ NTTIME twrp = 0;
+ bool ask_sharemode;
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(fname, &twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ fname,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+ if (!VALID_STAT(smb_fname->st) &&
+ (SMB_VFS_STAT(conn, smb_fname) != 0)) {
+ DEBUG(3,("reply_getatr: stat of %s failed (%s)\n",
+ smb_fname_str_dbg(smb_fname),
+ strerror(errno)));
+ reply_nterror(req, map_nt_error_from_unix(errno));
+ goto out;
+ }
+
+ mode = fdos_mode(smb_fname->fsp);
+ size = smb_fname->st.st_ex_size;
+
+ ask_sharemode = fsp_search_ask_sharemode(smb_fname->fsp);
+ if (ask_sharemode) {
+ struct timespec write_time_ts;
+ struct file_id fileid;
+
+ ZERO_STRUCT(write_time_ts);
+ fileid = vfs_file_id_from_sbuf(conn, &smb_fname->st);
+ get_file_infos(fileid, 0, NULL, &write_time_ts);
+ if (!is_omit_timespec(&write_time_ts)) {
+ update_stat_ex_mtime(&smb_fname->st, write_time_ts);
+ }
+ }
+
+ mtime = convert_timespec_to_time_t(smb_fname->st.st_ex_mtime);
+ if (mode & FILE_ATTRIBUTE_DIRECTORY) {
+ size = 0;
+ }
+ }
+
+ reply_smb1_outbuf(req, 10, 0);
+
+ SSVAL(req->outbuf,smb_vwv0,mode);
+ if(lp_dos_filetime_resolution(SNUM(conn)) ) {
+ srv_put_dos_date3((char *)req->outbuf,smb_vwv1,mtime & ~1);
+ } else {
+ srv_put_dos_date3((char *)req->outbuf,smb_vwv1,mtime);
+ }
+ SIVAL(req->outbuf,smb_vwv3,(uint32_t)size);
+
+ if (xconn->protocol >= PROTOCOL_NT1) {
+ SSVAL(req->outbuf, smb_flg2,
+ SVAL(req->outbuf, smb_flg2) | FLAGS2_IS_LONG_NAME);
+ }
+
+ DEBUG(3,("reply_getatr: name=%s mode=%d size=%u\n",
+ smb_fname_str_dbg(smb_fname), mode, (unsigned int)size));
+
+ out:
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(fname);
+ END_PROFILE(SMBgetatr);
+ return;
+}
+
+/****************************************************************************
+ Reply to a setatr.
+****************************************************************************/
+
+void reply_setatr(struct smb_request *req)
+{
+ struct smb_file_time ft;
+ connection_struct *conn = req->conn;
+ struct smb_filename *smb_fname = NULL;
+ struct files_struct *dirfsp = NULL;
+ char *fname = NULL;
+ int mode;
+ time_t mtime;
+ const char *p;
+ NTSTATUS status;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+ NTTIME twrp = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBsetatr);
+ init_smb_file_time(&ft);
+
+ if (req->wct < 2) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ p = (const char *)req->buf + 1;
+ p += srvstr_get_path_req(ctx, req, &fname, p, STR_TERMINATE, &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(fname, &twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ fname,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (ISDOT(smb_fname->base_name)) {
+ /*
+ * Not sure here is the right place to catch this
+ * condition. Might be moved to somewhere else later -- vl
+ */
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ goto out;
+ }
+
+ if (smb_fname->fsp == NULL) {
+ /* Can't set access rights on a symlink. */
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ goto out;
+ }
+
+ mode = SVAL(req->vwv+0, 0);
+ mtime = srv_make_unix_date3(req->vwv+1);
+
+ if (mode != FILE_ATTRIBUTE_NORMAL) {
+ if (VALID_STAT_OF_DIR(smb_fname->st))
+ mode |= FILE_ATTRIBUTE_DIRECTORY;
+ else
+ mode &= ~FILE_ATTRIBUTE_DIRECTORY;
+
+ status = smbd_check_access_rights_fsp(conn->cwd_fsp,
+ smb_fname->fsp,
+ false,
+ FILE_WRITE_ATTRIBUTES);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (file_set_dosmode(conn, smb_fname, mode, NULL,
+ false) != 0) {
+ reply_nterror(req, map_nt_error_from_unix(errno));
+ goto out;
+ }
+ }
+
+ ft.mtime = time_t_to_full_timespec(mtime);
+
+ status = smb_set_file_time(conn, smb_fname->fsp, smb_fname, &ft, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ reply_smb1_outbuf(req, 0, 0);
+
+ DEBUG(3, ("setatr name=%s mode=%d\n", smb_fname_str_dbg(smb_fname),
+ mode));
+ out:
+ TALLOC_FREE(smb_fname);
+ END_PROFILE(SMBsetatr);
+ return;
+}
+
+/****************************************************************************
+ Reply to a dskattr.
+****************************************************************************/
+
+void reply_dskattr(struct smb_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ connection_struct *conn = req->conn;
+ uint64_t ret;
+ uint64_t dfree,dsize,bsize;
+ struct smb_filename smb_fname;
+ START_PROFILE(SMBdskattr);
+
+ ZERO_STRUCT(smb_fname);
+ smb_fname.base_name = discard_const_p(char, ".");
+
+ if (SMB_VFS_STAT(conn, &smb_fname) != 0) {
+ reply_nterror(req, map_nt_error_from_unix(errno));
+ DBG_WARNING("stat of . failed (%s)\n", strerror(errno));
+ END_PROFILE(SMBdskattr);
+ return;
+ }
+
+ ret = get_dfree_info(conn, &smb_fname, &bsize, &dfree, &dsize);
+ if (ret == (uint64_t)-1) {
+ reply_nterror(req, map_nt_error_from_unix(errno));
+ END_PROFILE(SMBdskattr);
+ return;
+ }
+
+ /*
+ * Force max to fit in 16 bit fields.
+ */
+ while (dfree > WORDMAX || dsize > WORDMAX || bsize < 512) {
+ dfree /= 2;
+ dsize /= 2;
+ bsize *= 2;
+ if (bsize > (WORDMAX*512)) {
+ bsize = (WORDMAX*512);
+ if (dsize > WORDMAX)
+ dsize = WORDMAX;
+ if (dfree > WORDMAX)
+ dfree = WORDMAX;
+ break;
+ }
+ }
+
+ reply_smb1_outbuf(req, 5, 0);
+
+ if (xconn->protocol <= PROTOCOL_LANMAN2) {
+ double total_space, free_space;
+ /* we need to scale this to a number that DOS6 can handle. We
+ use floating point so we can handle large drives on systems
+ that don't have 64 bit integers
+
+ we end up displaying a maximum of 2G to DOS systems
+ */
+ total_space = dsize * (double)bsize;
+ free_space = dfree * (double)bsize;
+
+ dsize = (uint64_t)((total_space+63*512) / (64*512));
+ dfree = (uint64_t)((free_space+63*512) / (64*512));
+
+ if (dsize > 0xFFFF) dsize = 0xFFFF;
+ if (dfree > 0xFFFF) dfree = 0xFFFF;
+
+ SSVAL(req->outbuf,smb_vwv0,dsize);
+ SSVAL(req->outbuf,smb_vwv1,64); /* this must be 64 for dos systems */
+ SSVAL(req->outbuf,smb_vwv2,512); /* and this must be 512 */
+ SSVAL(req->outbuf,smb_vwv3,dfree);
+ } else {
+ SSVAL(req->outbuf,smb_vwv0,dsize);
+ SSVAL(req->outbuf,smb_vwv1,bsize/512);
+ SSVAL(req->outbuf,smb_vwv2,512);
+ SSVAL(req->outbuf,smb_vwv3,dfree);
+ }
+
+ DEBUG(3,("dskattr dfree=%d\n", (unsigned int)dfree));
+
+ END_PROFILE(SMBdskattr);
+ return;
+}
+
+/****************************************************************************
+ Make a dir struct.
+****************************************************************************/
+
+static void make_dir_struct(TALLOC_CTX *ctx,
+ char *buf,
+ const char *mask,
+ const char *fname,
+ off_t size,
+ uint32_t mode,
+ time_t date,
+ bool uc)
+{
+ char *p;
+
+ if ((mode & FILE_ATTRIBUTE_DIRECTORY) != 0) {
+ size = 0;
+ }
+
+ memset(buf+1,' ',11);
+ if ((p = strchr_m(mask, '.')) != NULL) {
+ char name[p - mask + 1];
+ strlcpy(name, mask, sizeof(name));
+ push_ascii(buf + 1, name, 8, 0);
+ push_ascii(buf+9,p+1,3, 0);
+ } else {
+ push_ascii(buf + 1, mask, 11, 0);
+ }
+
+ memset(buf+21,'\0',DIR_STRUCT_SIZE-21);
+ SCVAL(buf,21,mode);
+ srv_put_dos_date(buf,22,date);
+ SSVAL(buf,26,size & 0xFFFF);
+ SSVAL(buf,28,(size >> 16)&0xFFFF);
+ /* We only uppercase if FLAGS2_LONG_PATH_COMPONENTS is zero in the input buf.
+ Strange, but verified on W2K3. Needed for OS/2. JRA. */
+ push_ascii(buf+30,fname,12, uc ? STR_UPPER : 0);
+ DEBUG(8,("put name [%s] from [%s] into dir struct\n",buf+30, fname));
+}
+
+/*******************************************************************
+ A wrapper that handles case sensitivity and the special handling
+ of the ".." name.
+*******************************************************************/
+
+static bool mask_match_search(const char *string,
+ const char *pattern,
+ bool is_case_sensitive)
+{
+ if (ISDOTDOT(string)) {
+ string = ".";
+ }
+ if (ISDOT(pattern)) {
+ return False;
+ }
+
+ return ms_fnmatch(pattern, string, True, is_case_sensitive) == 0;
+}
+
+static bool mangle_mask_match(connection_struct *conn,
+ const char *filename,
+ const char *mask)
+{
+ char mname[13];
+
+ if (!name_to_8_3(filename, mname, False, conn->params)) {
+ return False;
+ }
+ return mask_match_search(mname, mask, False);
+}
+
+/****************************************************************************
+ Get an 8.3 directory entry.
+****************************************************************************/
+
+static bool smbd_dirptr_8_3_match_fn(TALLOC_CTX *ctx,
+ void *private_data,
+ const char *dname,
+ const char *mask,
+ char **_fname)
+{
+ connection_struct *conn = (connection_struct *)private_data;
+
+ if ((strcmp(mask, "*.*") == 0) ||
+ mask_match_search(dname, mask, false) ||
+ mangle_mask_match(conn, dname, mask)) {
+ char mname[13];
+ const char *fname;
+ /*
+ * Ensure we can push the original name as UCS2. If
+ * not, then just don't return this name.
+ */
+ NTSTATUS status;
+ size_t ret_len = 0;
+ size_t len = (strlen(dname) + 2) * 4; /* Allow enough space. */
+ uint8_t *tmp = talloc_array(talloc_tos(), uint8_t, len);
+
+ status = srvstr_push(NULL,
+ FLAGS2_UNICODE_STRINGS,
+ tmp,
+ dname,
+ len,
+ STR_TERMINATE,
+ &ret_len);
+
+ TALLOC_FREE(tmp);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ if (!mangle_is_8_3(dname, false, conn->params)) {
+ bool ok =
+ name_to_8_3(dname, mname, false, conn->params);
+ if (!ok) {
+ return false;
+ }
+ fname = mname;
+ } else {
+ fname = dname;
+ }
+
+ *_fname = talloc_strdup(ctx, fname);
+ if (*_fname == NULL) {
+ return false;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+static bool get_dir_entry(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct dptr_struct *dirptr,
+ const char *mask,
+ uint32_t dirtype,
+ char **_fname,
+ off_t *_size,
+ uint32_t *_mode,
+ struct timespec *_date,
+ bool check_descend,
+ bool ask_sharemode)
+{
+ char *fname = NULL;
+ struct smb_filename *smb_fname = NULL;
+ uint32_t mode = 0;
+ bool ok;
+
+again:
+ ok = smbd_dirptr_get_entry(ctx,
+ dirptr,
+ mask,
+ dirtype,
+ check_descend,
+ ask_sharemode,
+ true,
+ smbd_dirptr_8_3_match_fn,
+ conn,
+ &fname,
+ &smb_fname,
+ &mode);
+ if (!ok) {
+ return false;
+ }
+ if (mode & FILE_ATTRIBUTE_REPARSE_POINT) {
+ /* hide reparse points from ancient clients */
+ TALLOC_FREE(fname);
+ TALLOC_FREE(smb_fname);
+ goto again;
+ }
+
+ *_fname = talloc_move(ctx, &fname);
+ *_size = smb_fname->st.st_ex_size;
+ *_mode = mode;
+ *_date = smb_fname->st.st_ex_mtime;
+ TALLOC_FREE(smb_fname);
+ return true;
+}
+
+/****************************************************************************
+ Reply to a search.
+ Can be called from SMBsearch, SMBffirst or SMBfunique.
+****************************************************************************/
+
+void reply_search(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ char *path = NULL;
+ char *mask = NULL;
+ char *directory = NULL;
+ struct smb_filename *smb_fname = NULL;
+ char *fname = NULL;
+ off_t size;
+ uint32_t mode;
+ struct timespec date;
+ uint32_t dirtype;
+ unsigned int numentries = 0;
+ unsigned int maxentries = 0;
+ bool finished = False;
+ const char *p;
+ int status_len;
+ char status[21];
+ int dptr_num= -1;
+ bool check_descend = False;
+ bool expect_close = False;
+ NTSTATUS nt_status;
+ bool mask_contains_wcard = False;
+ bool allow_long_path_components = (req->flags2 & FLAGS2_LONG_PATH_COMPONENTS) ? True : False;
+ TALLOC_CTX *ctx = talloc_tos();
+ struct smbXsrv_connection *xconn = req->xconn;
+ struct smbd_server_connection *sconn = req->sconn;
+ files_struct *fsp = NULL;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ START_PROFILE(SMBsearch);
+
+ if (req->wct < 2) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ if (req->posix_pathnames) {
+ reply_unknown_new(req, req->cmd);
+ goto out;
+ }
+
+ /* If we were called as SMBffirst then we must expect close. */
+ if(req->cmd == SMBffirst) {
+ expect_close = True;
+ }
+
+ reply_smb1_outbuf(req, 1, 3);
+ maxentries = SVAL(req->vwv+0, 0);
+ dirtype = SVAL(req->vwv+1, 0);
+ p = (const char *)req->buf + 1;
+ p += srvstr_get_path_req(ctx, req, &path, p, STR_TERMINATE,
+ &nt_status);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ reply_nterror(req, nt_status);
+ goto out;
+ }
+
+ if (smbreq_bufrem(req, p) < 3) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ p++;
+ status_len = SVAL(p, 0);
+ p += 2;
+
+ /* dirtype &= ~FILE_ATTRIBUTE_DIRECTORY; */
+
+ if (status_len == 0) {
+ const char *dirpath;
+ struct files_struct *dirfsp = NULL;
+ struct smb_filename *smb_dname = NULL;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+
+ nt_status = smb1_strip_dfs_path(ctx, &ucf_flags, &path);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ reply_nterror(req, nt_status);
+ goto out;
+ }
+
+ nt_status = filename_convert_smb1_search_path(ctx,
+ conn,
+ path,
+ ucf_flags,
+ &dirfsp,
+ &smb_dname,
+ &mask);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ if (NT_STATUS_EQUAL(nt_status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, nt_status);
+ goto out;
+ }
+
+ memset((char *)status,'\0',21);
+ SCVAL(status,0,(dirtype & 0x1F));
+
+ /*
+ * Open an fsp on this directory for the dptr.
+ */
+ nt_status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_dname, /* dname */
+ FILE_LIST_DIRECTORY, /* access_mask */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE, /* share_access */
+ FILE_OPEN, /* create_disposition*/
+ FILE_DIRECTORY_FILE, /* create_options */
+ FILE_ATTRIBUTE_DIRECTORY,/* file_attributes */
+ NO_OPLOCK, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* pinfo */
+ NULL, /* in_context */
+ NULL);/* out_context */
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("failed to open directory %s\n",
+ smb_fname_str_dbg(smb_dname));
+ reply_nterror(req, nt_status);
+ goto out;
+ }
+
+ nt_status = dptr_create(conn,
+ NULL, /* req */
+ fsp, /* fsp */
+ True,
+ mask,
+ dirtype,
+ &fsp->dptr);
+
+ TALLOC_FREE(smb_dname);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ /*
+ * Use NULL here for the first parameter (req)
+ * as this is not a client visible handle so
+ * can't be part of an SMB1 chain.
+ */
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ reply_nterror(req, nt_status);
+ goto out;
+ }
+
+ dptr_num = dptr_dnum(fsp->dptr);
+ dirpath = dptr_path(sconn, dptr_num);
+ directory = talloc_strdup(ctx, dirpath);
+ if (!directory) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+
+ } else {
+ int status_dirtype;
+ const char *dirpath;
+ unsigned int dptr_filenum;
+ uint32_t resume_key_index;
+
+ if (smbreq_bufrem(req, p) < 21) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ memcpy(status,p,21);
+ status_dirtype = CVAL(status,0) & 0x1F;
+ if (status_dirtype != (dirtype & 0x1F)) {
+ dirtype = status_dirtype;
+ }
+
+ dptr_num = CVAL(status, 12);
+ fsp = dptr_fetch_lanman2_fsp(sconn, dptr_num);
+ if (fsp == NULL) {
+ goto SearchEmpty;
+ }
+
+ resume_key_index = PULL_LE_U32(status, 13);
+ dptr_filenum = dptr_FileNumber(fsp->dptr);
+
+ if (resume_key_index > dptr_filenum) {
+ /*
+ * Haven't seen this resume key yet. Just stop
+ * the search.
+ */
+ goto SearchEmpty;
+ }
+
+ if (resume_key_index < dptr_filenum) {
+ /*
+ * The resume key was not the last one we
+ * sent, rewind and skip to what the client
+ * sent.
+ */
+ dptr_RewindDir(fsp->dptr);
+
+ dptr_filenum = dptr_FileNumber(fsp->dptr);
+ SMB_ASSERT(dptr_filenum == 0);
+
+ while (dptr_filenum < resume_key_index) {
+ bool ok = get_dir_entry(
+ ctx,
+ conn,
+ fsp->dptr,
+ dptr_wcard(sconn, dptr_num),
+ dirtype,
+ &fname,
+ &size,
+ &mode,
+ &date,
+ check_descend,
+ false);
+ TALLOC_FREE(fname);
+ if (!ok) {
+ goto SearchEmpty;
+ }
+
+ dptr_filenum = dptr_FileNumber(fsp->dptr);
+ }
+ }
+
+ dirpath = dptr_path(sconn, dptr_num);
+ directory = talloc_strdup(ctx, dirpath);
+ if (!directory) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+
+ mask = talloc_strdup(ctx, dptr_wcard(sconn, dptr_num));
+ if (!mask) {
+ goto SearchEmpty;
+ }
+ dirtype = dptr_attr(sconn, dptr_num);
+ }
+
+ mask_contains_wcard = dptr_has_wild(fsp->dptr);
+
+ DEBUG(4,("dptr_num is %d\n",dptr_num));
+
+ if ((dirtype&0x1F) == FILE_ATTRIBUTE_VOLUME) {
+ char buf[DIR_STRUCT_SIZE];
+ memcpy(buf,status,21);
+ make_dir_struct(ctx,
+ buf,
+ "???????????",
+ volume_label(ctx, SNUM(conn)),
+ 0,
+ FILE_ATTRIBUTE_VOLUME,
+ 0,
+ !allow_long_path_components);
+ SCVAL(buf, 12, dptr_num);
+ numentries = 1;
+ if (message_push_blob(&req->outbuf,
+ data_blob_const(buf, sizeof(buf)))
+ == -1) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ } else {
+ unsigned int i;
+ size_t hdr_size = ((uint8_t *)smb_buf(req->outbuf) + 3 - req->outbuf);
+ size_t available_space = xconn->smb1.sessions.max_send - hdr_size;
+ bool ask_sharemode;
+
+ maxentries = MIN(maxentries, available_space/DIR_STRUCT_SIZE);
+
+ DEBUG(8,("dirpath=<%s> dontdescend=<%s>\n",
+ directory,lp_dont_descend(ctx, lp_sub, SNUM(conn))));
+ if (in_list(directory, lp_dont_descend(ctx, lp_sub, SNUM(conn)),True)) {
+ check_descend = True;
+ }
+
+ ask_sharemode = fsp_search_ask_sharemode(fsp);
+
+ for (i=numentries;(i<maxentries) && !finished;i++) {
+ finished = !get_dir_entry(ctx,
+ conn,
+ fsp->dptr,
+ mask,
+ dirtype,
+ &fname,
+ &size,
+ &mode,
+ &date,
+ check_descend,
+ ask_sharemode);
+ if (!finished) {
+ char buf[DIR_STRUCT_SIZE];
+ memcpy(buf,status,21);
+ make_dir_struct(
+ ctx,
+ buf,
+ mask,
+ fname,
+ size,
+ mode,
+ convert_timespec_to_time_t(date),
+ !allow_long_path_components);
+ SCVAL(buf, 12, dptr_num);
+ PUSH_LE_U32(buf,
+ 13,
+ dptr_FileNumber(fsp->dptr));
+ if (message_push_blob(&req->outbuf,
+ data_blob_const(buf, sizeof(buf)))
+ == -1) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ numentries++;
+ }
+ TALLOC_FREE(fname);
+ }
+ }
+
+ SearchEmpty:
+
+ /* If we were called as SMBffirst with smb_search_id == NULL
+ and no entries were found then return error and close fsp->dptr
+ (X/Open spec) */
+
+ if (numentries == 0) {
+ dptr_num = -1;
+ if (fsp != NULL) {
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ }
+ } else if(expect_close && status_len == 0) {
+ /* Close the dptr - we know it's gone */
+ dptr_num = -1;
+ if (fsp != NULL) {
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ }
+ }
+
+ /* If we were called as SMBfunique, then we can close the fsp->dptr now ! */
+ if(dptr_num >= 0 && req->cmd == SMBfunique) {
+ dptr_num = -1;
+ /* fsp may have been closed above. */
+ if (fsp != NULL) {
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ }
+ }
+
+ if ((numentries == 0) && !mask_contains_wcard) {
+ reply_botherror(req, STATUS_NO_MORE_FILES, ERRDOS, ERRnofiles);
+ goto out;
+ }
+
+ SSVAL(req->outbuf,smb_vwv0,numentries);
+ SSVAL(req->outbuf,smb_vwv1,3 + numentries * DIR_STRUCT_SIZE);
+ SCVAL(smb_buf(req->outbuf),0,5);
+ SSVAL(smb_buf(req->outbuf),1,numentries*DIR_STRUCT_SIZE);
+
+ /* The replies here are never long name. */
+ SSVAL(req->outbuf, smb_flg2,
+ SVAL(req->outbuf, smb_flg2) & (~FLAGS2_IS_LONG_NAME));
+ if (!allow_long_path_components) {
+ SSVAL(req->outbuf, smb_flg2,
+ SVAL(req->outbuf, smb_flg2)
+ & (~FLAGS2_LONG_PATH_COMPONENTS));
+ }
+
+ /* This SMB *always* returns ASCII names. Remove the unicode bit in flags2. */
+ SSVAL(req->outbuf, smb_flg2,
+ (SVAL(req->outbuf, smb_flg2) & (~FLAGS2_UNICODE_STRINGS)));
+
+ DEBUG(4,("%s mask=%s path=%s dtype=%d nument=%u of %u\n",
+ smb_fn_name(req->cmd),
+ mask,
+ directory,
+ dirtype,
+ numentries,
+ maxentries ));
+ out:
+ TALLOC_FREE(directory);
+ TALLOC_FREE(mask);
+ TALLOC_FREE(smb_fname);
+ END_PROFILE(SMBsearch);
+ return;
+}
+
+/****************************************************************************
+ Reply to a fclose (stop directory search).
+****************************************************************************/
+
+void reply_fclose(struct smb_request *req)
+{
+ int status_len;
+ int dptr_num= -2;
+ const char *p;
+ char *path = NULL;
+ NTSTATUS err;
+ TALLOC_CTX *ctx = talloc_tos();
+ struct smbd_server_connection *sconn = req->sconn;
+ files_struct *fsp = NULL;
+
+ START_PROFILE(SMBfclose);
+
+ if (req->posix_pathnames) {
+ reply_unknown_new(req, req->cmd);
+ END_PROFILE(SMBfclose);
+ return;
+ }
+
+ p = (const char *)req->buf + 1;
+ p += srvstr_get_path_req(ctx, req, &path, p, STR_TERMINATE,
+ &err);
+ if (!NT_STATUS_IS_OK(err)) {
+ reply_nterror(req, err);
+ END_PROFILE(SMBfclose);
+ return;
+ }
+
+ if (smbreq_bufrem(req, p) < 3) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBfclose);
+ return;
+ }
+
+ p++;
+ status_len = SVAL(p,0);
+ p += 2;
+
+ if (status_len == 0) {
+ reply_force_doserror(req, ERRSRV, ERRsrverror);
+ END_PROFILE(SMBfclose);
+ return;
+ }
+
+ if (smbreq_bufrem(req, p) < 21) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBfclose);
+ return;
+ }
+
+ dptr_num = CVAL(p, 12);
+
+ fsp = dptr_fetch_lanman2_fsp(sconn, dptr_num);
+ if(fsp != NULL) {
+ /* Close the file - we know it's gone */
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ dptr_num = -1;
+ }
+
+ reply_smb1_outbuf(req, 1, 0);
+ SSVAL(req->outbuf,smb_vwv0,0);
+
+ DEBUG(3,("search close\n"));
+
+ END_PROFILE(SMBfclose);
+ return;
+}
+
+/****************************************************************************
+ Reply to an open.
+****************************************************************************/
+
+void reply_open(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ struct smb_filename *smb_fname = NULL;
+ char *fname = NULL;
+ uint32_t fattr=0;
+ off_t size = 0;
+ time_t mtime=0;
+ int info;
+ struct files_struct *dirfsp = NULL;
+ files_struct *fsp;
+ int oplock_request;
+ int deny_mode;
+ uint32_t dos_attr;
+ uint32_t access_mask;
+ uint32_t share_mode;
+ uint32_t create_disposition;
+ uint32_t create_options = 0;
+ uint32_t private_flags = 0;
+ NTSTATUS status;
+ uint32_t ucf_flags;
+ NTTIME twrp = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBopen);
+
+ if (req->wct < 2) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ oplock_request = CORE_OPLOCK_REQUEST(req->inbuf);
+ deny_mode = SVAL(req->vwv+0, 0);
+ dos_attr = SVAL(req->vwv+1, 0);
+
+ srvstr_get_path_req(ctx, req, &fname, (const char *)req->buf+1,
+ STR_TERMINATE, &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (!map_open_params_to_ntcreate(fname, deny_mode,
+ OPENX_FILE_EXISTS_OPEN, &access_mask,
+ &share_mode, &create_disposition,
+ &create_options, &private_flags)) {
+ reply_force_doserror(req, ERRDOS, ERRbadaccess);
+ goto out;
+ }
+
+ ucf_flags = filename_create_ucf_flags(req, create_disposition);
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(fname, &twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ fname,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req,
+ NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_fname, /* fname */
+ access_mask, /* access_mask */
+ share_mode, /* share_access */
+ create_disposition, /* create_disposition*/
+ create_options, /* create_options */
+ dos_attr, /* file_attributes */
+ oplock_request, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ private_flags,
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ &info, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->xconn, req->mid)) {
+ /* We have re-scheduled this call. */
+ goto out;
+ }
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ reply_openerror(req, status);
+ goto out;
+ }
+
+ fsp = fcb_or_dos_open(
+ req,
+ smb_fname,
+ access_mask,
+ create_options,
+ private_flags);
+ if (fsp == NULL) {
+ bool ok = defer_smb1_sharing_violation(req);
+ if (ok) {
+ goto out;
+ }
+ reply_openerror(req, status);
+ goto out;
+ }
+ }
+
+ /* Ensure we're pointing at the correct stat struct. */
+ TALLOC_FREE(smb_fname);
+ smb_fname = fsp->fsp_name;
+
+ size = smb_fname->st.st_ex_size;
+ fattr = fdos_mode(fsp);
+
+ mtime = convert_timespec_to_time_t(smb_fname->st.st_ex_mtime);
+
+ if (fattr & FILE_ATTRIBUTE_DIRECTORY) {
+ DEBUG(3,("attempt to open a directory %s\n",
+ fsp_str_dbg(fsp)));
+ close_file_free(req, &fsp, ERROR_CLOSE);
+ reply_botherror(req, NT_STATUS_ACCESS_DENIED,
+ ERRDOS, ERRnoaccess);
+ goto out;
+ }
+
+ reply_smb1_outbuf(req, 7, 0);
+ SSVAL(req->outbuf,smb_vwv0,fsp->fnum);
+ SSVAL(req->outbuf,smb_vwv1,fattr);
+ if(lp_dos_filetime_resolution(SNUM(conn)) ) {
+ srv_put_dos_date3((char *)req->outbuf,smb_vwv2,mtime & ~1);
+ } else {
+ srv_put_dos_date3((char *)req->outbuf,smb_vwv2,mtime);
+ }
+ SIVAL(req->outbuf,smb_vwv4,(uint32_t)size);
+ SSVAL(req->outbuf,smb_vwv6,deny_mode);
+
+ if (oplock_request && lp_fake_oplocks(SNUM(conn))) {
+ SCVAL(req->outbuf,smb_flg,
+ CVAL(req->outbuf,smb_flg)|CORE_OPLOCK_GRANTED);
+ }
+
+ if(EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
+ SCVAL(req->outbuf,smb_flg,
+ CVAL(req->outbuf,smb_flg)|CORE_OPLOCK_GRANTED);
+ }
+ out:
+ END_PROFILE(SMBopen);
+ return;
+}
+
+/****************************************************************************
+ Reply to an open and X.
+****************************************************************************/
+
+void reply_open_and_X(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ struct smb_filename *smb_fname = NULL;
+ char *fname = NULL;
+ uint16_t open_flags;
+ int deny_mode;
+ uint32_t smb_attr;
+ /* Breakout the oplock request bits so we can set the
+ reply bits separately. */
+ int ex_oplock_request;
+ int core_oplock_request;
+ int oplock_request;
+#if 0
+ int smb_sattr = SVAL(req->vwv+4, 0);
+ uint32_t smb_time = make_unix_date3(req->vwv+6);
+#endif
+ int smb_ofun;
+ uint32_t fattr=0;
+ int mtime=0;
+ int smb_action = 0;
+ struct files_struct *dirfsp = NULL;
+ files_struct *fsp;
+ NTSTATUS status;
+ uint64_t allocation_size;
+ ssize_t retval = -1;
+ uint32_t access_mask;
+ uint32_t share_mode;
+ uint32_t create_disposition;
+ uint32_t create_options = 0;
+ uint32_t private_flags = 0;
+ uint32_t ucf_flags;
+ NTTIME twrp = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBopenX);
+
+ if (req->wct < 15) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ open_flags = SVAL(req->vwv+2, 0);
+ deny_mode = SVAL(req->vwv+3, 0);
+ smb_attr = SVAL(req->vwv+5, 0);
+ ex_oplock_request = EXTENDED_OPLOCK_REQUEST(req->inbuf);
+ core_oplock_request = CORE_OPLOCK_REQUEST(req->inbuf);
+ oplock_request = ex_oplock_request | core_oplock_request;
+ smb_ofun = SVAL(req->vwv+8, 0);
+ allocation_size = (uint64_t)IVAL(req->vwv+9, 0);
+
+ /* If it's an IPC, pass off the pipe handler. */
+ if (IS_IPC(conn)) {
+ if (lp_nt_pipe_support()) {
+ reply_open_pipe_and_X(conn, req);
+ } else {
+ reply_nterror(req, NT_STATUS_NETWORK_ACCESS_DENIED);
+ }
+ goto out;
+ }
+
+ /* XXXX we need to handle passed times, sattr and flags */
+ srvstr_get_path_req(ctx, req, &fname, (const char *)req->buf,
+ STR_TERMINATE, &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (!map_open_params_to_ntcreate(fname, deny_mode,
+ smb_ofun,
+ &access_mask, &share_mode,
+ &create_disposition,
+ &create_options,
+ &private_flags)) {
+ reply_force_doserror(req, ERRDOS, ERRbadaccess);
+ goto out;
+ }
+
+ ucf_flags = filename_create_ucf_flags(req, create_disposition);
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(fname, &twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ fname,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req,
+ NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_fname, /* fname */
+ access_mask, /* access_mask */
+ share_mode, /* share_access */
+ create_disposition, /* create_disposition*/
+ create_options, /* create_options */
+ smb_attr, /* file_attributes */
+ oplock_request, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ private_flags,
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ &smb_action, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->xconn, req->mid)) {
+ /* We have re-scheduled this call. */
+ goto out;
+ }
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ reply_openerror(req, status);
+ goto out;
+ }
+
+ fsp = fcb_or_dos_open(
+ req,
+ smb_fname,
+ access_mask,
+ create_options,
+ private_flags);
+ if (fsp == NULL) {
+ bool ok = defer_smb1_sharing_violation(req);
+ if (ok) {
+ goto out;
+ }
+ reply_openerror(req, status);
+ goto out;
+ }
+
+
+ smb_action = FILE_WAS_OPENED;
+ }
+
+ /* Setting the "size" field in vwv9 and vwv10 causes the file to be set to this size,
+ if the file is truncated or created. */
+ if (((smb_action == FILE_WAS_CREATED) || (smb_action == FILE_WAS_OVERWRITTEN)) && allocation_size) {
+ fsp->initial_allocation_size = smb_roundup(fsp->conn, allocation_size);
+ if (vfs_allocate_file_space(fsp, fsp->initial_allocation_size) == -1) {
+ close_file_free(req, &fsp, ERROR_CLOSE);
+ reply_nterror(req, NT_STATUS_DISK_FULL);
+ goto out;
+ }
+ retval = vfs_set_filelen(fsp, (off_t)allocation_size);
+ if (retval < 0) {
+ close_file_free(req, &fsp, ERROR_CLOSE);
+ reply_nterror(req, NT_STATUS_DISK_FULL);
+ goto out;
+ }
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ close_file_free(req, &fsp, ERROR_CLOSE);
+ reply_nterror(req, status);
+ goto out;
+ }
+ }
+
+ fattr = fdos_mode(fsp);
+ if (fattr & FILE_ATTRIBUTE_DIRECTORY) {
+ close_file_free(req, &fsp, ERROR_CLOSE);
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ goto out;
+ }
+ mtime = convert_timespec_to_time_t(fsp->fsp_name->st.st_ex_mtime);
+
+ /* If the caller set the extended oplock request bit
+ and we granted one (by whatever means) - set the
+ correct bit for extended oplock reply.
+ */
+
+ if (ex_oplock_request && lp_fake_oplocks(SNUM(conn))) {
+ smb_action |= EXTENDED_OPLOCK_GRANTED;
+ }
+
+ if(ex_oplock_request && EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
+ smb_action |= EXTENDED_OPLOCK_GRANTED;
+ }
+
+ /* If the caller set the core oplock request bit
+ and we granted one (by whatever means) - set the
+ correct bit for core oplock reply.
+ */
+
+ if (open_flags & EXTENDED_RESPONSE_REQUIRED) {
+ reply_smb1_outbuf(req, 19, 0);
+ } else {
+ reply_smb1_outbuf(req, 15, 0);
+ }
+
+ SSVAL(req->outbuf, smb_vwv0, 0xff); /* andx chain ends */
+ SSVAL(req->outbuf, smb_vwv1, 0); /* no andx offset */
+
+ if (core_oplock_request && lp_fake_oplocks(SNUM(conn))) {
+ SCVAL(req->outbuf, smb_flg,
+ CVAL(req->outbuf,smb_flg)|CORE_OPLOCK_GRANTED);
+ }
+
+ if(core_oplock_request && EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
+ SCVAL(req->outbuf, smb_flg,
+ CVAL(req->outbuf,smb_flg)|CORE_OPLOCK_GRANTED);
+ }
+
+ SSVAL(req->outbuf,smb_vwv2,fsp->fnum);
+ SSVAL(req->outbuf,smb_vwv3,fattr);
+ if(lp_dos_filetime_resolution(SNUM(conn)) ) {
+ srv_put_dos_date3((char *)req->outbuf,smb_vwv4,mtime & ~1);
+ } else {
+ srv_put_dos_date3((char *)req->outbuf,smb_vwv4,mtime);
+ }
+ SIVAL(req->outbuf,smb_vwv6,(uint32_t)fsp->fsp_name->st.st_ex_size);
+ SSVAL(req->outbuf,smb_vwv8,GET_OPENX_MODE(deny_mode));
+ SSVAL(req->outbuf,smb_vwv11,smb_action);
+
+ if (open_flags & EXTENDED_RESPONSE_REQUIRED) {
+ SIVAL(req->outbuf, smb_vwv15, SEC_STD_ALL);
+ }
+
+ out:
+ TALLOC_FREE(smb_fname);
+ END_PROFILE(SMBopenX);
+ return;
+}
+
+/****************************************************************************
+ Reply to a SMBulogoffX.
+****************************************************************************/
+
+static struct tevent_req *reply_ulogoffX_send(struct smb_request *smb1req,
+ struct smbXsrv_session *session);
+static void reply_ulogoffX_done(struct tevent_req *req);
+
+void reply_ulogoffX(struct smb_request *smb1req)
+{
+ struct timeval now = timeval_current();
+ struct smbXsrv_session *session = NULL;
+ struct tevent_req *req;
+ NTSTATUS status;
+
+ /*
+ * Don't setup the profile charge here, take
+ * it in reply_ulogoffX_done(). Not strictly correct
+ * but better than the other SMB1 async
+ * code that double-charges at the moment.
+ */
+
+ status = smb1srv_session_lookup(smb1req->xconn,
+ smb1req->vuid,
+ timeval_to_nttime(&now),
+ &session);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Not going async, profile here. */
+ START_PROFILE(SMBulogoffX);
+ DBG_WARNING("ulogoff, vuser id %llu does not map to user.\n",
+ (unsigned long long)smb1req->vuid);
+
+ smb1req->vuid = UID_FIELD_INVALID;
+ reply_force_doserror(smb1req, ERRSRV, ERRbaduid);
+ END_PROFILE(SMBulogoffX);
+ return;
+ }
+
+ req = reply_ulogoffX_send(smb1req, session);
+ if (req == NULL) {
+ /* Not going async, profile here. */
+ START_PROFILE(SMBulogoffX);
+ reply_force_doserror(smb1req, ERRDOS, ERRnomem);
+ END_PROFILE(SMBulogoffX);
+ return;
+ }
+
+ /* We're async. This will complete later. */
+ tevent_req_set_callback(req, reply_ulogoffX_done, smb1req);
+ return;
+}
+
+struct reply_ulogoffX_state {
+ struct tevent_queue *wait_queue;
+ struct smbXsrv_session *session;
+};
+
+static void reply_ulogoffX_wait_done(struct tevent_req *subreq);
+
+/****************************************************************************
+ Async SMB1 ulogoffX.
+ Note, on failure here we deallocate and return NULL to allow the caller to
+ SMB1 return an error of ERRnomem immediately.
+****************************************************************************/
+
+static struct tevent_req *reply_ulogoffX_send(struct smb_request *smb1req,
+ struct smbXsrv_session *session)
+{
+ struct tevent_req *req;
+ struct reply_ulogoffX_state *state;
+ struct tevent_req *subreq;
+ files_struct *fsp;
+ struct smbd_server_connection *sconn = session->client->sconn;
+ uint64_t vuid = session->global->session_wire_id;
+
+ req = tevent_req_create(smb1req, &state,
+ struct reply_ulogoffX_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->wait_queue = tevent_queue_create(state,
+ "reply_ulogoffX_wait_queue");
+ if (tevent_req_nomem(state->wait_queue, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+ state->session = session;
+
+ /*
+ * Make sure that no new request will be able to use this session.
+ * This ensures that once all outstanding fsp->aio_requests
+ * on this session are done, we are safe to close it.
+ */
+ session->status = NT_STATUS_USER_SESSION_DELETED;
+
+ for (fsp = sconn->files; fsp; fsp = fsp->next) {
+ if (fsp->vuid != vuid) {
+ continue;
+ }
+ /*
+ * Flag the file as close in progress.
+ * This will prevent any more IO being
+ * done on it.
+ */
+ fsp->fsp_flags.closing = true;
+
+ if (fsp->num_aio_requests > 0) {
+ /*
+ * Now wait until all aio requests on this fsp are
+ * finished.
+ *
+ * We don't set a callback, as we just want to block the
+ * wait queue and the talloc_free() of fsp->aio_request
+ * will remove the item from the wait queue.
+ */
+ subreq = tevent_queue_wait_send(fsp->aio_requests,
+ sconn->ev_ctx,
+ state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+ }
+ }
+
+ /*
+ * Now we add our own waiter to the end of the queue,
+ * this way we get notified when all pending requests are finished
+ * and reply to the outstanding SMB1 request.
+ */
+ subreq = tevent_queue_wait_send(state,
+ sconn->ev_ctx,
+ state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+
+ /*
+ * We're really going async - move the SMB1 request from
+ * a talloc stackframe above us to the sconn talloc-context.
+ * We need this to stick around until the wait_done
+ * callback is invoked.
+ */
+ smb1req = talloc_move(sconn, &smb1req);
+
+ tevent_req_set_callback(subreq, reply_ulogoffX_wait_done, req);
+
+ return req;
+}
+
+static void reply_ulogoffX_wait_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+
+ tevent_queue_wait_recv(subreq);
+ TALLOC_FREE(subreq);
+ tevent_req_done(req);
+}
+
+static NTSTATUS reply_ulogoffX_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static void reply_ulogoffX_done(struct tevent_req *req)
+{
+ struct smb_request *smb1req = tevent_req_callback_data(
+ req, struct smb_request);
+ struct reply_ulogoffX_state *state = tevent_req_data(req,
+ struct reply_ulogoffX_state);
+ struct smbXsrv_session *session = state->session;
+ NTSTATUS status;
+
+ /*
+ * Take the profile charge here. Not strictly
+ * correct but better than the other SMB1 async
+ * code that double-charges at the moment.
+ */
+ START_PROFILE(SMBulogoffX);
+
+ status = reply_ulogoffX_recv(req);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(smb1req);
+ END_PROFILE(SMBulogoffX);
+ exit_server(__location__ ": reply_ulogoffX_recv failed");
+ return;
+ }
+
+ status = smbXsrv_session_logoff(session);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(smb1req);
+ END_PROFILE(SMBulogoffX);
+ exit_server(__location__ ": smbXsrv_session_logoff failed");
+ return;
+ }
+
+ TALLOC_FREE(session);
+
+ reply_smb1_outbuf(smb1req, 2, 0);
+ SSVAL(smb1req->outbuf, smb_vwv0, 0xff); /* andx chain ends */
+ SSVAL(smb1req->outbuf, smb_vwv1, 0); /* no andx offset */
+
+ DBG_NOTICE("ulogoffX vuid=%llu\n",
+ (unsigned long long)smb1req->vuid);
+
+ smb1req->vuid = UID_FIELD_INVALID;
+ /*
+ * The following call is needed to push the
+ * reply data back out the socket after async
+ * return. Plus it frees smb1req.
+ */
+ smb_request_done(smb1req);
+ END_PROFILE(SMBulogoffX);
+}
+
+/****************************************************************************
+ Reply to a mknew or a create.
+****************************************************************************/
+
+void reply_mknew(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ struct smb_filename *smb_fname = NULL;
+ char *fname = NULL;
+ uint32_t fattr = 0;
+ struct smb_file_time ft;
+ struct files_struct *dirfsp = NULL;
+ files_struct *fsp;
+ int oplock_request = 0;
+ NTSTATUS status;
+ uint32_t access_mask = FILE_GENERIC_READ | FILE_GENERIC_WRITE;
+ uint32_t share_mode = FILE_SHARE_READ|FILE_SHARE_WRITE;
+ uint32_t create_disposition;
+ uint32_t create_options = 0;
+ uint32_t ucf_flags;
+ NTTIME twrp = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBcreate);
+ init_smb_file_time(&ft);
+
+ if (req->wct < 3) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ fattr = SVAL(req->vwv+0, 0);
+ oplock_request = CORE_OPLOCK_REQUEST(req->inbuf);
+
+ if (req->cmd == SMBmknew) {
+ /* We should fail if file exists. */
+ create_disposition = FILE_CREATE;
+ } else {
+ /* Create if file doesn't exist, truncate if it does. */
+ create_disposition = FILE_OVERWRITE_IF;
+ }
+
+ /* mtime. */
+ ft.mtime = time_t_to_full_timespec(srv_make_unix_date3(req->vwv+1));
+
+ srvstr_get_path_req(ctx, req, &fname, (const char *)req->buf + 1,
+ STR_TERMINATE, &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ ucf_flags = filename_create_ucf_flags(req, create_disposition);
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(fname, &twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ fname,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req,
+ NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (fattr & FILE_ATTRIBUTE_VOLUME) {
+ DEBUG(0,("Attempt to create file (%s) with volid set - "
+ "please report this\n",
+ smb_fname_str_dbg(smb_fname)));
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_fname, /* fname */
+ access_mask, /* access_mask */
+ share_mode, /* share_access */
+ create_disposition, /* create_disposition*/
+ create_options, /* create_options */
+ fattr, /* file_attributes */
+ oplock_request, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->xconn, req->mid)) {
+ /* We have re-scheduled this call. */
+ goto out;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ bool ok = defer_smb1_sharing_violation(req);
+ if (ok) {
+ goto out;
+ }
+ }
+ reply_openerror(req, status);
+ goto out;
+ }
+
+ ft.atime = smb_fname->st.st_ex_atime; /* atime. */
+ status = smb_set_file_time(conn, fsp, smb_fname, &ft, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ END_PROFILE(SMBcreate);
+ goto out;
+ }
+
+ reply_smb1_outbuf(req, 1, 0);
+ SSVAL(req->outbuf,smb_vwv0,fsp->fnum);
+
+ if (oplock_request && lp_fake_oplocks(SNUM(conn))) {
+ SCVAL(req->outbuf,smb_flg,
+ CVAL(req->outbuf,smb_flg)|CORE_OPLOCK_GRANTED);
+ }
+
+ if(EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
+ SCVAL(req->outbuf,smb_flg,
+ CVAL(req->outbuf,smb_flg)|CORE_OPLOCK_GRANTED);
+ }
+
+ DEBUG(2, ("reply_mknew: file %s\n", smb_fname_str_dbg(smb_fname)));
+ DEBUG(3, ("reply_mknew %s fd=%d dmode=0x%x\n",
+ smb_fname_str_dbg(smb_fname), fsp_get_io_fd(fsp),
+ (unsigned int)fattr));
+
+ out:
+ TALLOC_FREE(smb_fname);
+ END_PROFILE(SMBcreate);
+ return;
+}
+
+/****************************************************************************
+ Reply to a create temporary file.
+****************************************************************************/
+
+void reply_ctemp(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ struct smb_filename *smb_fname = NULL;
+ char *wire_name = NULL;
+ char *fname = NULL;
+ uint32_t fattr;
+ struct files_struct *dirfsp = NULL;
+ files_struct *fsp;
+ int oplock_request;
+ char *s;
+ NTSTATUS status;
+ int i;
+ uint32_t ucf_flags;
+ NTTIME twrp = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBctemp);
+
+ if (req->wct < 3) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ fattr = SVAL(req->vwv+0, 0);
+ oplock_request = CORE_OPLOCK_REQUEST(req->inbuf);
+
+ srvstr_get_path_req(ctx, req, &wire_name, (const char *)req->buf+1,
+ STR_TERMINATE, &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ for (i = 0; i < 10; i++) {
+ if (*wire_name) {
+ fname = talloc_asprintf(ctx,
+ "%s/TMP%s",
+ wire_name,
+ generate_random_str_list(ctx, 5, "0123456789"));
+ } else {
+ fname = talloc_asprintf(ctx,
+ "TMP%s",
+ generate_random_str_list(ctx, 5, "0123456789"));
+ }
+
+ if (!fname) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+
+ ucf_flags = filename_create_ucf_flags(req, FILE_CREATE);
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(fname, &twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ fname,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ /* Create the file. */
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_fname, /* fname */
+ FILE_GENERIC_READ | FILE_GENERIC_WRITE, /* access_mask */
+ FILE_SHARE_READ | FILE_SHARE_WRITE, /* share_access */
+ FILE_CREATE, /* create_disposition*/
+ 0, /* create_options */
+ fattr, /* file_attributes */
+ oplock_request, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) {
+ TALLOC_FREE(fname);
+ TALLOC_FREE(dirfsp);
+ TALLOC_FREE(smb_fname);
+ continue;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->xconn, req->mid)) {
+ /* We have re-scheduled this call. */
+ goto out;
+ }
+ if (NT_STATUS_EQUAL(
+ status, NT_STATUS_SHARING_VIOLATION)) {
+ bool ok = defer_smb1_sharing_violation(req);
+ if (ok) {
+ goto out;
+ }
+ }
+ reply_openerror(req, status);
+ goto out;
+ }
+
+ break;
+ }
+
+ if (i == 10) {
+ /* Collision after 10 times... */
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ reply_smb1_outbuf(req, 1, 0);
+ SSVAL(req->outbuf,smb_vwv0,fsp->fnum);
+
+ /* the returned filename is relative to the directory */
+ s = strrchr_m(fsp->fsp_name->base_name, '/');
+ if (!s) {
+ s = fsp->fsp_name->base_name;
+ } else {
+ s++;
+ }
+
+#if 0
+ /* Tested vs W2K3 - this doesn't seem to be here - null terminated filename is the only
+ thing in the byte section. JRA */
+ SSVALS(p, 0, -1); /* what is this? not in spec */
+#endif
+ if (message_push_string(&req->outbuf, s, STR_ASCII|STR_TERMINATE)
+ == -1) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+
+ if (oplock_request && lp_fake_oplocks(SNUM(conn))) {
+ SCVAL(req->outbuf, smb_flg,
+ CVAL(req->outbuf,smb_flg)|CORE_OPLOCK_GRANTED);
+ }
+
+ if (EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
+ SCVAL(req->outbuf, smb_flg,
+ CVAL(req->outbuf,smb_flg)|CORE_OPLOCK_GRANTED);
+ }
+
+ DEBUG(2, ("reply_ctemp: created temp file %s\n", fsp_str_dbg(fsp)));
+ DEBUG(3, ("reply_ctemp %s fd=%d umode=0%o\n", fsp_str_dbg(fsp),
+ fsp_get_io_fd(fsp), (unsigned int)smb_fname->st.st_ex_mode));
+ out:
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(wire_name);
+ END_PROFILE(SMBctemp);
+ return;
+}
+
+/****************************************************************************
+ Reply to a unlink
+****************************************************************************/
+
+void reply_unlink(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ char *name = NULL;
+ struct files_struct *dirfsp = NULL;
+ struct smb_filename *smb_fname = NULL;
+ uint32_t dirtype;
+ NTSTATUS status;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+ NTTIME twrp = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBunlink);
+
+ if (req->wct < 1) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ dirtype = SVAL(req->vwv+0, 0);
+
+ srvstr_get_path_req(ctx, req, &name, (const char *)req->buf + 1,
+ STR_TERMINATE, &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(name, &twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &name);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ name,
+ ucf_flags | UCF_LCOMP_LNK_OK,
+ twrp,
+ &dirfsp,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ DEBUG(3,("reply_unlink : %s\n", smb_fname_str_dbg(smb_fname)));
+
+ status = unlink_internals(conn, req, dirtype, dirfsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->xconn, req->mid)) {
+ /* We have re-scheduled this call. */
+ goto out;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ bool ok = defer_smb1_sharing_violation(req);
+ if (ok) {
+ goto out;
+ }
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ reply_smb1_outbuf(req, 0, 0);
+ out:
+ TALLOC_FREE(smb_fname);
+ END_PROFILE(SMBunlink);
+ return;
+}
+
+/****************************************************************************
+ Fail for readbraw.
+****************************************************************************/
+
+static void fail_readraw(void)
+{
+ const char *errstr = talloc_asprintf(talloc_tos(),
+ "FAIL ! reply_readbraw: socket write fail (%s)",
+ strerror(errno));
+ if (!errstr) {
+ errstr = "";
+ }
+ exit_server_cleanly(errstr);
+}
+
+/****************************************************************************
+ Return a readbraw error (4 bytes of zero).
+****************************************************************************/
+
+static void reply_readbraw_error(struct smbXsrv_connection *xconn)
+{
+ char header[4];
+
+ SIVAL(header,0,0);
+
+ smbd_lock_socket(xconn);
+ if (write_data(xconn->transport.sock,header,4) != 4) {
+ int saved_errno = errno;
+ /*
+ * Try and give an error message saying what
+ * client failed.
+ */
+ DEBUG(0, ("write_data failed for client %s. "
+ "Error %s\n",
+ smbXsrv_connection_dbg(xconn),
+ strerror(saved_errno)));
+ errno = saved_errno;
+
+ fail_readraw();
+ }
+ smbd_unlock_socket(xconn);
+}
+
+/*******************************************************************
+ Ensure we don't use sendfile if server smb signing is active.
+********************************************************************/
+
+static bool lp_use_sendfile(struct smbXsrv_connection *xconn,
+ int snum,
+ struct smb1_signing_state *signing_state)
+{
+ bool sign_active = false;
+
+ /* Using sendfile blows the brains out of any DOS or Win9x TCP stack... JRA. */
+ if (xconn->protocol < PROTOCOL_NT1) {
+ return false;
+ }
+ if (signing_state) {
+ sign_active = smb1_signing_is_active(signing_state);
+ }
+ return (lp__use_sendfile(snum) &&
+ (get_remote_arch() != RA_WIN95) &&
+ !sign_active);
+}
+/****************************************************************************
+ Use sendfile in readbraw.
+****************************************************************************/
+
+static void send_file_readbraw(connection_struct *conn,
+ struct smb_request *req,
+ files_struct *fsp,
+ off_t startpos,
+ size_t nread,
+ ssize_t mincount)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ char *outbuf = NULL;
+ ssize_t ret=0;
+
+ /*
+ * We can only use sendfile on a non-chained packet
+ * but we can use on a non-oplocked file. tridge proved this
+ * on a train in Germany :-). JRA.
+ * reply_readbraw has already checked the length.
+ */
+
+ if ( !req_is_in_chain(req) &&
+ (nread > 0) &&
+ !fsp_is_alternate_stream(fsp) &&
+ lp_use_sendfile(xconn, SNUM(conn), xconn->smb1.signing_state) ) {
+ ssize_t sendfile_read = -1;
+ char header[4];
+ DATA_BLOB header_blob;
+
+ _smb_setlen(header,nread);
+ header_blob = data_blob_const(header, 4);
+
+ sendfile_read = SMB_VFS_SENDFILE(xconn->transport.sock, fsp,
+ &header_blob, startpos,
+ nread);
+ if (sendfile_read == -1) {
+ /* Returning ENOSYS means no data at all was sent.
+ * Do this as a normal read. */
+ if (errno == ENOSYS) {
+ goto normal_readbraw;
+ }
+
+ /*
+ * Special hack for broken Linux with no working sendfile. If we
+ * return EINTR we sent the header but not the rest of the data.
+ * Fake this up by doing read/write calls.
+ */
+ if (errno == EINTR) {
+ /* Ensure we don't do this again. */
+ set_use_sendfile(SNUM(conn), False);
+ DEBUG(0,("send_file_readbraw: sendfile not available. Faking..\n"));
+
+ if (fake_sendfile(xconn, fsp, startpos, nread) == -1) {
+ DEBUG(0,("send_file_readbraw: "
+ "fake_sendfile failed for "
+ "file %s (%s).\n",
+ fsp_str_dbg(fsp),
+ strerror(errno)));
+ exit_server_cleanly("send_file_readbraw fake_sendfile failed");
+ }
+ return;
+ }
+
+ DEBUG(0,("send_file_readbraw: sendfile failed for "
+ "file %s (%s). Terminating\n",
+ fsp_str_dbg(fsp), strerror(errno)));
+ exit_server_cleanly("send_file_readbraw sendfile failed");
+ } else if (sendfile_read == 0) {
+ /*
+ * Some sendfile implementations return 0 to indicate
+ * that there was a short read, but nothing was
+ * actually written to the socket. In this case,
+ * fallback to the normal read path so the header gets
+ * the correct byte count.
+ */
+ DEBUG(3, ("send_file_readbraw: sendfile sent zero "
+ "bytes falling back to the normal read: "
+ "%s\n", fsp_str_dbg(fsp)));
+ goto normal_readbraw;
+ }
+
+ /* Deal with possible short send. */
+ if (sendfile_read != 4+nread) {
+ ret = sendfile_short_send(xconn, fsp,
+ sendfile_read, 4, nread);
+ if (ret == -1) {
+ fail_readraw();
+ }
+ }
+ return;
+ }
+
+normal_readbraw:
+
+ outbuf = talloc_array(NULL, char, nread+4);
+ if (!outbuf) {
+ DEBUG(0,("send_file_readbraw: talloc_array failed for size %u.\n",
+ (unsigned)(nread+4)));
+ reply_readbraw_error(xconn);
+ return;
+ }
+
+ if (nread > 0) {
+ ret = read_file(fsp,outbuf+4,startpos,nread);
+#if 0 /* mincount appears to be ignored in a W2K server. JRA. */
+ if (ret < mincount)
+ ret = 0;
+#else
+ if (ret < nread)
+ ret = 0;
+#endif
+ }
+
+ _smb_setlen(outbuf,ret);
+ if (write_data(xconn->transport.sock, outbuf, 4+ret) != 4+ret) {
+ int saved_errno = errno;
+ /*
+ * Try and give an error message saying what
+ * client failed.
+ */
+ DEBUG(0, ("write_data failed for client %s. Error %s\n",
+ smbXsrv_connection_dbg(xconn),
+ strerror(saved_errno)));
+ errno = saved_errno;
+
+ fail_readraw();
+ }
+
+ TALLOC_FREE(outbuf);
+}
+
+/****************************************************************************
+ Reply to a readbraw (core+ protocol).
+****************************************************************************/
+
+void reply_readbraw(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ struct smbXsrv_connection *xconn = req->xconn;
+ ssize_t maxcount,mincount;
+ size_t nread = 0;
+ off_t startpos;
+ files_struct *fsp;
+ struct lock_struct lock;
+ off_t size = 0;
+ NTSTATUS status;
+
+ START_PROFILE(SMBreadbraw);
+
+ if (smb1_srv_is_signing_active(xconn) || req->encrypted) {
+ exit_server_cleanly("reply_readbraw: SMB signing/sealing is active - "
+ "raw reads/writes are disallowed.");
+ }
+
+ if (req->wct < 8) {
+ reply_readbraw_error(xconn);
+ END_PROFILE(SMBreadbraw);
+ return;
+ }
+
+ if (xconn->smb1.echo_handler.trusted_fde) {
+ DEBUG(2,("SMBreadbraw rejected with NOT_SUPPORTED because of "
+ "'async smb echo handler = yes'\n"));
+ reply_readbraw_error(xconn);
+ END_PROFILE(SMBreadbraw);
+ return;
+ }
+
+ /*
+ * Special check if an oplock break has been issued
+ * and the readraw request croses on the wire, we must
+ * return a zero length response here.
+ */
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+
+ /*
+ * We have to do a check_fsp by hand here, as
+ * we must always return 4 zero bytes on error,
+ * not a NTSTATUS.
+ */
+
+ if (fsp == NULL ||
+ conn == NULL ||
+ conn != fsp->conn ||
+ req->vuid != fsp->vuid ||
+ fsp->fsp_flags.is_directory ||
+ fsp_get_io_fd(fsp) == -1)
+ {
+ /*
+ * fsp could be NULL here so use the value from the packet. JRA.
+ */
+ DEBUG(3,("reply_readbraw: fnum %d not valid "
+ "- cache prime?\n",
+ (int)SVAL(req->vwv+0, 0)));
+ reply_readbraw_error(xconn);
+ END_PROFILE(SMBreadbraw);
+ return;
+ }
+
+ /* Do a "by hand" version of CHECK_READ. */
+ if (!(fsp->fsp_flags.can_read ||
+ ((req->flags2 & FLAGS2_READ_PERMIT_EXECUTE) &&
+ (fsp->access_mask & FILE_EXECUTE)))) {
+ DEBUG(3,("reply_readbraw: fnum %d not readable.\n",
+ (int)SVAL(req->vwv+0, 0)));
+ reply_readbraw_error(xconn);
+ END_PROFILE(SMBreadbraw);
+ return;
+ }
+
+ startpos = IVAL_TO_SMB_OFF_T(req->vwv+1, 0);
+ if(req->wct == 10) {
+ /*
+ * This is a large offset (64 bit) read.
+ */
+
+ startpos |= (((off_t)IVAL(req->vwv+8, 0)) << 32);
+
+ if(startpos < 0) {
+ DEBUG(0,("reply_readbraw: negative 64 bit "
+ "readraw offset (%.0f) !\n",
+ (double)startpos ));
+ reply_readbraw_error(xconn);
+ END_PROFILE(SMBreadbraw);
+ return;
+ }
+ }
+
+ maxcount = (SVAL(req->vwv+3, 0) & 0xFFFF);
+ mincount = (SVAL(req->vwv+4, 0) & 0xFFFF);
+
+ /* ensure we don't overrun the packet size */
+ maxcount = MIN(65535,maxcount);
+
+ init_strict_lock_struct(fsp,
+ (uint64_t)req->smbpid,
+ (uint64_t)startpos,
+ (uint64_t)maxcount,
+ READ_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &lock);
+
+ if (!SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, &lock)) {
+ reply_readbraw_error(xconn);
+ END_PROFILE(SMBreadbraw);
+ return;
+ }
+
+ status = vfs_stat_fsp(fsp);
+ if (NT_STATUS_IS_OK(status)) {
+ size = fsp->fsp_name->st.st_ex_size;
+ }
+
+ if (startpos >= size) {
+ nread = 0;
+ } else {
+ nread = MIN(maxcount,(size - startpos));
+ }
+
+#if 0 /* mincount appears to be ignored in a W2K server. JRA. */
+ if (nread < mincount)
+ nread = 0;
+#endif
+
+ DEBUG( 3, ( "reply_readbraw: %s start=%.0f max=%lu "
+ "min=%lu nread=%lu\n",
+ fsp_fnum_dbg(fsp), (double)startpos,
+ (unsigned long)maxcount,
+ (unsigned long)mincount,
+ (unsigned long)nread ) );
+
+ send_file_readbraw(conn, req, fsp, startpos, nread, mincount);
+
+ DEBUG(5,("reply_readbraw finished\n"));
+
+ END_PROFILE(SMBreadbraw);
+ return;
+}
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_LOCKING
+
+/****************************************************************************
+ Reply to a lockread (core+ protocol).
+****************************************************************************/
+
+static void reply_lockread_locked(struct tevent_req *subreq);
+
+void reply_lockread(struct smb_request *req)
+{
+ struct tevent_req *subreq = NULL;
+ connection_struct *conn = req->conn;
+ files_struct *fsp;
+ struct smbd_lock_element *lck = NULL;
+
+ START_PROFILE(SMBlockread);
+
+ if (req->wct < 5) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBlockread);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBlockread);
+ return;
+ }
+
+ if (!CHECK_READ(fsp,req)) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ END_PROFILE(SMBlockread);
+ return;
+ }
+
+ lck = talloc(req, struct smbd_lock_element);
+ if (lck == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBlockread);
+ return;
+ }
+
+ /*
+ * NB. Discovered by Menny Hamburger at Mainsoft. This is a core+
+ * protocol request that predates the read/write lock concept.
+ * Thus instead of asking for a read lock here we need to ask
+ * for a write lock. JRA.
+ * Note that the requested lock size is unaffected by max_send.
+ */
+
+ *lck = (struct smbd_lock_element) {
+ .req_guid = smbd_request_guid(req, 0),
+ .smblctx = req->smbpid,
+ .brltype = WRITE_LOCK,
+ .lock_flav = WINDOWS_LOCK,
+ .count = SVAL(req->vwv+1, 0),
+ .offset = IVAL_TO_SMB_OFF_T(req->vwv+2, 0),
+ };
+
+ subreq = smbd_smb1_do_locks_send(
+ fsp,
+ req->sconn->ev_ctx,
+ &req,
+ fsp,
+ 0,
+ false, /* large_offset */
+ 1,
+ lck);
+ if (subreq == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBlockread);
+ return;
+ }
+ tevent_req_set_callback(subreq, reply_lockread_locked, NULL);
+ END_PROFILE(SMBlockread);
+}
+
+static void reply_lockread_locked(struct tevent_req *subreq)
+{
+ struct smb_request *req = NULL;
+ ssize_t nread = -1;
+ char *data = NULL;
+ NTSTATUS status;
+ bool ok;
+ off_t startpos;
+ size_t numtoread, maxtoread;
+ struct files_struct *fsp = NULL;
+ char *p = NULL;
+
+ START_PROFILE(SMBlockread);
+
+ ok = smbd_smb1_do_locks_extract_smbreq(subreq, talloc_tos(), &req);
+ SMB_ASSERT(ok);
+
+ status = smbd_smb1_do_locks_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto send;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+ if (fsp == NULL) {
+ reply_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ goto send;
+ }
+
+ numtoread = SVAL(req->vwv+1, 0);
+ startpos = IVAL_TO_SMB_OFF_T(req->vwv+2, 0);
+
+ /*
+ * However the requested READ size IS affected by max_send. Insanity.... JRA.
+ */
+ maxtoread = req->xconn->smb1.sessions.max_send - (MIN_SMB_SIZE + 5*2 + 3);
+
+ if (numtoread > maxtoread) {
+ DBG_WARNING("requested read size (%zu) is greater than "
+ "maximum allowed (%zu/%d). "
+ "Returning short read of maximum allowed for "
+ "compatibility with Windows 2000.\n",
+ numtoread,
+ maxtoread,
+ req->xconn->smb1.sessions.max_send);
+ numtoread = maxtoread;
+ }
+
+ reply_smb1_outbuf(req, 5, numtoread + 3);
+
+ data = smb_buf(req->outbuf) + 3;
+
+ nread = read_file(fsp,data,startpos,numtoread);
+
+ if (nread < 0) {
+ reply_nterror(req, map_nt_error_from_unix(errno));
+ goto send;
+ }
+
+ srv_smb1_set_message((char *)req->outbuf, 5, nread+3, False);
+
+ SSVAL(req->outbuf,smb_vwv0,nread);
+ SSVAL(req->outbuf,smb_vwv5,nread+3);
+ p = smb_buf(req->outbuf);
+ SCVAL(p,0,0); /* pad byte. */
+ SSVAL(p,1,nread);
+
+ DEBUG(3,("lockread %s num=%d nread=%d\n",
+ fsp_fnum_dbg(fsp), (int)numtoread, (int)nread));
+
+send:
+ ok = smb1_srv_send(req->xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(req->conn));
+ if (!ok) {
+ exit_server_cleanly("reply_lock_done: smb1_srv_send failed.");
+ }
+ TALLOC_FREE(req);
+ END_PROFILE(SMBlockread);
+ return;
+}
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_ALL
+
+/****************************************************************************
+ Reply to a read.
+****************************************************************************/
+
+void reply_read(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ size_t numtoread;
+ size_t maxtoread;
+ ssize_t nread = 0;
+ char *data;
+ off_t startpos;
+ files_struct *fsp;
+ struct lock_struct lock;
+ struct smbXsrv_connection *xconn = req->xconn;
+
+ START_PROFILE(SMBread);
+
+ if (req->wct < 3) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBread);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBread);
+ return;
+ }
+
+ if (!CHECK_READ(fsp,req)) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ END_PROFILE(SMBread);
+ return;
+ }
+
+ numtoread = SVAL(req->vwv+1, 0);
+ startpos = IVAL_TO_SMB_OFF_T(req->vwv+2, 0);
+
+ /*
+ * The requested read size cannot be greater than max_send. JRA.
+ */
+ maxtoread = xconn->smb1.sessions.max_send - (MIN_SMB_SIZE + 5*2 + 3);
+
+ if (numtoread > maxtoread) {
+ DEBUG(0,("reply_read: requested read size (%u) is greater than maximum allowed (%u/%u). \
+Returning short read of maximum allowed for compatibility with Windows 2000.\n",
+ (unsigned int)numtoread, (unsigned int)maxtoread,
+ (unsigned int)xconn->smb1.sessions.max_send));
+ numtoread = maxtoread;
+ }
+
+ reply_smb1_outbuf(req, 5, numtoread+3);
+
+ data = smb_buf(req->outbuf) + 3;
+
+ init_strict_lock_struct(fsp,
+ (uint64_t)req->smbpid,
+ (uint64_t)startpos,
+ (uint64_t)numtoread,
+ READ_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &lock);
+
+ if (!SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, &lock)) {
+ reply_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
+ END_PROFILE(SMBread);
+ return;
+ }
+
+ if (numtoread > 0)
+ nread = read_file(fsp,data,startpos,numtoread);
+
+ if (nread < 0) {
+ reply_nterror(req, map_nt_error_from_unix(errno));
+ goto out;
+ }
+
+ srv_smb1_set_message((char *)req->outbuf, 5, nread+3, False);
+
+ SSVAL(req->outbuf,smb_vwv0,nread);
+ SSVAL(req->outbuf,smb_vwv5,nread+3);
+ SCVAL(smb_buf(req->outbuf),0,1);
+ SSVAL(smb_buf(req->outbuf),1,nread);
+
+ DEBUG(3, ("read %s num=%d nread=%d\n",
+ fsp_fnum_dbg(fsp), (int)numtoread, (int)nread));
+
+out:
+ END_PROFILE(SMBread);
+ return;
+}
+
+/****************************************************************************
+ Setup readX header.
+****************************************************************************/
+
+size_t setup_readX_header(char *outbuf, size_t smb_maxcnt)
+{
+ size_t outsize;
+
+ outsize = srv_smb1_set_message(outbuf,12,smb_maxcnt + 1 /* padding byte */,
+ False);
+
+ memset(outbuf+smb_vwv0,'\0',24); /* valgrind init. */
+
+ SCVAL(outbuf,smb_vwv0,0xFF);
+ SSVAL(outbuf,smb_vwv2,0xFFFF); /* Remaining - must be -1. */
+ SSVAL(outbuf,smb_vwv5,smb_maxcnt);
+ SSVAL(outbuf,smb_vwv6,
+ (smb_wct - 4) /* offset from smb header to wct */
+ + 1 /* the wct field */
+ + 12 * sizeof(uint16_t) /* vwv */
+ + 2 /* the buflen field */
+ + 1); /* padding byte */
+ SSVAL(outbuf,smb_vwv7,(smb_maxcnt >> 16));
+ SCVAL(smb_buf(outbuf), 0, 0); /* padding byte */
+ /* Reset the outgoing length, set_message truncates at 0x1FFFF. */
+ _smb_setlen_large(outbuf,
+ smb_size + 12*2 + smb_maxcnt - 4 + 1 /* pad */);
+ return outsize;
+}
+
+/****************************************************************************
+ Reply to a read and X - possibly using sendfile.
+****************************************************************************/
+
+static void send_file_readX(connection_struct *conn, struct smb_request *req,
+ files_struct *fsp, off_t startpos,
+ size_t smb_maxcnt)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ ssize_t nread = -1;
+ struct lock_struct lock;
+ int saved_errno = 0;
+ NTSTATUS status;
+
+ init_strict_lock_struct(fsp,
+ (uint64_t)req->smbpid,
+ (uint64_t)startpos,
+ (uint64_t)smb_maxcnt,
+ READ_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &lock);
+
+ if (!SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, &lock)) {
+ reply_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
+ return;
+ }
+
+ /*
+ * We can only use sendfile on a non-chained packet
+ * but we can use on a non-oplocked file. tridge proved this
+ * on a train in Germany :-). JRA.
+ */
+
+ if (!req_is_in_chain(req) &&
+ !req->encrypted &&
+ !fsp_is_alternate_stream(fsp) &&
+ lp_use_sendfile(xconn, SNUM(conn), xconn->smb1.signing_state) ) {
+ uint8_t headerbuf[smb_size + 12 * 2 + 1 /* padding byte */];
+ DATA_BLOB header;
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (!S_ISREG(fsp->fsp_name->st.st_ex_mode) ||
+ (startpos > fsp->fsp_name->st.st_ex_size) ||
+ (smb_maxcnt > (fsp->fsp_name->st.st_ex_size - startpos))) {
+ /*
+ * We already know that we would do a short read, so don't
+ * try the sendfile() path.
+ */
+ goto nosendfile_read;
+ }
+
+ /*
+ * Set up the packet header before send. We
+ * assume here the sendfile will work (get the
+ * correct amount of data).
+ */
+
+ header = data_blob_const(headerbuf, sizeof(headerbuf));
+
+ construct_smb1_reply_common_req(req, (char *)headerbuf);
+ setup_readX_header((char *)headerbuf, smb_maxcnt);
+
+ nread = SMB_VFS_SENDFILE(xconn->transport.sock, fsp, &header,
+ startpos, smb_maxcnt);
+ if (nread == -1) {
+ saved_errno = errno;
+
+ /* Returning ENOSYS means no data at all was sent.
+ Do this as a normal read. */
+ if (errno == ENOSYS) {
+ goto normal_read;
+ }
+
+ /*
+ * Special hack for broken Linux with no working sendfile. If we
+ * return EINTR we sent the header but not the rest of the data.
+ * Fake this up by doing read/write calls.
+ */
+
+ if (errno == EINTR) {
+ /* Ensure we don't do this again. */
+ set_use_sendfile(SNUM(conn), False);
+ DEBUG(0,("send_file_readX: sendfile not available. Faking..\n"));
+ nread = fake_sendfile(xconn, fsp, startpos,
+ smb_maxcnt);
+ if (nread == -1) {
+ saved_errno = errno;
+ DEBUG(0,("send_file_readX: "
+ "fake_sendfile failed for "
+ "file %s (%s) for client %s. "
+ "Terminating\n",
+ fsp_str_dbg(fsp),
+ smbXsrv_connection_dbg(xconn),
+ strerror(saved_errno)));
+ errno = saved_errno;
+ exit_server_cleanly("send_file_readX: fake_sendfile failed");
+ }
+ DEBUG(3, ("send_file_readX: fake_sendfile %s max=%d nread=%d\n",
+ fsp_fnum_dbg(fsp), (int)smb_maxcnt, (int)nread));
+ /* No outbuf here means successful sendfile. */
+ goto out;
+ }
+
+ DEBUG(0,("send_file_readX: sendfile failed for file "
+ "%s (%s). Terminating\n", fsp_str_dbg(fsp),
+ strerror(errno)));
+ exit_server_cleanly("send_file_readX sendfile failed");
+ } else if (nread == 0) {
+ /*
+ * Some sendfile implementations return 0 to indicate
+ * that there was a short read, but nothing was
+ * actually written to the socket. In this case,
+ * fallback to the normal read path so the header gets
+ * the correct byte count.
+ */
+ DEBUG(3, ("send_file_readX: sendfile sent zero bytes "
+ "falling back to the normal read: %s\n",
+ fsp_str_dbg(fsp)));
+ goto normal_read;
+ }
+
+ DEBUG(3, ("send_file_readX: sendfile %s max=%d nread=%d\n",
+ fsp_fnum_dbg(fsp), (int)smb_maxcnt, (int)nread));
+
+ /* Deal with possible short send. */
+ if (nread != smb_maxcnt + sizeof(headerbuf)) {
+ ssize_t ret;
+
+ ret = sendfile_short_send(xconn, fsp, nread,
+ sizeof(headerbuf), smb_maxcnt);
+ if (ret == -1) {
+ const char *r;
+ r = "send_file_readX: sendfile_short_send failed";
+ DEBUG(0,("%s for file %s (%s).\n",
+ r, fsp_str_dbg(fsp), strerror(errno)));
+ exit_server_cleanly(r);
+ }
+ }
+ /* No outbuf here means successful sendfile. */
+ goto out;
+ }
+
+normal_read:
+
+ if ((smb_maxcnt & 0xFF0000) > 0x10000) {
+ uint8_t headerbuf[smb_size + 2*12 + 1 /* padding byte */];
+ ssize_t ret;
+
+ if (!S_ISREG(fsp->fsp_name->st.st_ex_mode) ||
+ (startpos > fsp->fsp_name->st.st_ex_size) ||
+ (smb_maxcnt > (fsp->fsp_name->st.st_ex_size - startpos))) {
+ /*
+ * We already know that we would do a short
+ * read, so don't try the sendfile() path.
+ */
+ goto nosendfile_read;
+ }
+
+ construct_smb1_reply_common_req(req, (char *)headerbuf);
+ setup_readX_header((char *)headerbuf, smb_maxcnt);
+
+ /* Send out the header. */
+ ret = write_data(xconn->transport.sock, (char *)headerbuf,
+ sizeof(headerbuf));
+ if (ret != sizeof(headerbuf)) {
+ saved_errno = errno;
+ /*
+ * Try and give an error message saying what
+ * client failed.
+ */
+ DEBUG(0,("send_file_readX: write_data failed for file "
+ "%s (%s) for client %s. Terminating\n",
+ fsp_str_dbg(fsp),
+ smbXsrv_connection_dbg(xconn),
+ strerror(saved_errno)));
+ errno = saved_errno;
+ exit_server_cleanly("send_file_readX sendfile failed");
+ }
+ nread = fake_sendfile(xconn, fsp, startpos, smb_maxcnt);
+ if (nread == -1) {
+ saved_errno = errno;
+ DEBUG(0,("send_file_readX: fake_sendfile failed for file "
+ "%s (%s) for client %s. Terminating\n",
+ fsp_str_dbg(fsp),
+ smbXsrv_connection_dbg(xconn),
+ strerror(saved_errno)));
+ errno = saved_errno;
+ exit_server_cleanly("send_file_readX: fake_sendfile failed");
+ }
+ goto out;
+ }
+
+nosendfile_read:
+
+ reply_smb1_outbuf(req, 12, smb_maxcnt + 1 /* padding byte */);
+ SSVAL(req->outbuf, smb_vwv0, 0xff); /* andx chain ends */
+ SSVAL(req->outbuf, smb_vwv1, 0); /* no andx offset */
+
+ nread = read_file(fsp, smb_buf(req->outbuf) + 1 /* padding byte */,
+ startpos, smb_maxcnt);
+ saved_errno = errno;
+
+ if (nread < 0) {
+ reply_nterror(req, map_nt_error_from_unix(saved_errno));
+ return;
+ }
+
+ setup_readX_header((char *)req->outbuf, nread);
+
+ DEBUG(3, ("send_file_readX %s max=%d nread=%d\n",
+ fsp_fnum_dbg(fsp), (int)smb_maxcnt, (int)nread));
+ return;
+
+out:
+ TALLOC_FREE(req->outbuf);
+ return;
+}
+
+/****************************************************************************
+ Work out how much space we have for a read return.
+****************************************************************************/
+
+static size_t calc_max_read_pdu(const struct smb_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+
+ if (xconn->protocol < PROTOCOL_NT1) {
+ return xconn->smb1.sessions.max_send;
+ }
+
+ if (!lp_large_readwrite()) {
+ return xconn->smb1.sessions.max_send;
+ }
+
+ if (req_is_in_chain(req)) {
+ return xconn->smb1.sessions.max_send;
+ }
+
+ if (req->encrypted) {
+ /*
+ * Don't take encrypted traffic up to the
+ * limit. There are padding considerations
+ * that make that tricky.
+ */
+ return xconn->smb1.sessions.max_send;
+ }
+
+ if (smb1_srv_is_signing_active(xconn)) {
+ return 0x1FFFF;
+ }
+
+ if (!lp_smb1_unix_extensions()) {
+ return 0x1FFFF;
+ }
+
+ /*
+ * We can do ultra-large POSIX reads.
+ */
+ return 0xFFFFFF;
+}
+
+/****************************************************************************
+ Calculate how big a read can be. Copes with all clients. It's always
+ safe to return a short read - Windows does this.
+****************************************************************************/
+
+static size_t calc_read_size(const struct smb_request *req,
+ size_t upper_size,
+ size_t lower_size)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ size_t max_pdu = calc_max_read_pdu(req);
+ size_t total_size = 0;
+ size_t hdr_len = MIN_SMB_SIZE + VWV(12);
+ size_t max_len = max_pdu - hdr_len - 1 /* padding byte */;
+
+ /*
+ * Windows explicitly ignores upper size of 0xFFFF.
+ * See [MS-SMB].pdf <26> Section 2.2.4.2.1:
+ * We must do the same as these will never fit even in
+ * an extended size NetBIOS packet.
+ */
+ if (upper_size == 0xFFFF) {
+ upper_size = 0;
+ }
+
+ if (xconn->protocol < PROTOCOL_NT1) {
+ upper_size = 0;
+ }
+
+ total_size = ((upper_size<<16) | lower_size);
+
+ /*
+ * LARGE_READX test shows it's always safe to return
+ * a short read. Windows does so.
+ */
+ return MIN(total_size, max_len);
+}
+
+/****************************************************************************
+ Reply to a read and X.
+****************************************************************************/
+
+void reply_read_and_X(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ files_struct *fsp;
+ off_t startpos;
+ size_t smb_maxcnt;
+ size_t upper_size;
+ bool big_readX = False;
+#if 0
+ size_t smb_mincnt = SVAL(req->vwv+6, 0);
+#endif
+
+ START_PROFILE(SMBreadX);
+
+ if ((req->wct != 10) && (req->wct != 12)) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+2, 0));
+ startpos = IVAL_TO_SMB_OFF_T(req->vwv+3, 0);
+ smb_maxcnt = SVAL(req->vwv+5, 0);
+
+ /* If it's an IPC, pass off the pipe handler. */
+ if (IS_IPC(conn)) {
+ reply_pipe_read_and_X(req);
+ END_PROFILE(SMBreadX);
+ return;
+ }
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBreadX);
+ return;
+ }
+
+ if (!CHECK_READ(fsp,req)) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ END_PROFILE(SMBreadX);
+ return;
+ }
+
+ upper_size = SVAL(req->vwv+7, 0);
+ smb_maxcnt = calc_read_size(req, upper_size, smb_maxcnt);
+ if (smb_maxcnt > (0x1FFFF - (MIN_SMB_SIZE + VWV(12)))) {
+ /*
+ * This is a heuristic to avoid keeping large
+ * outgoing buffers around over long-lived aio
+ * requests.
+ */
+ big_readX = True;
+ }
+
+ if (req->wct == 12) {
+ /*
+ * This is a large offset (64 bit) read.
+ */
+ startpos |= (((off_t)IVAL(req->vwv+10, 0)) << 32);
+
+ }
+
+ if (!big_readX) {
+ NTSTATUS status = schedule_aio_read_and_X(conn,
+ req,
+ fsp,
+ startpos,
+ smb_maxcnt);
+ if (NT_STATUS_IS_OK(status)) {
+ /* Read scheduled - we're done. */
+ goto out;
+ }
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
+ /* Real error - report to client. */
+ END_PROFILE(SMBreadX);
+ reply_nterror(req, status);
+ return;
+ }
+ /* NT_STATUS_RETRY - fall back to sync read. */
+ }
+
+ smbd_lock_socket(req->xconn);
+ send_file_readX(conn, req, fsp, startpos, smb_maxcnt);
+ smbd_unlock_socket(req->xconn);
+
+ out:
+ END_PROFILE(SMBreadX);
+ return;
+}
+
+/****************************************************************************
+ Error replies to writebraw must have smb_wct == 1. Fix this up.
+****************************************************************************/
+
+void error_to_writebrawerr(struct smb_request *req)
+{
+ uint8_t *old_outbuf = req->outbuf;
+
+ reply_smb1_outbuf(req, 1, 0);
+
+ memcpy(req->outbuf, old_outbuf, smb_size);
+ TALLOC_FREE(old_outbuf);
+}
+
+/****************************************************************************
+ Read 4 bytes of a smb packet and return the smb length of the packet.
+ Store the result in the buffer. This version of the function will
+ never return a session keepalive (length of zero).
+ Timeout is in milliseconds.
+****************************************************************************/
+
+static NTSTATUS read_smb_length(int fd, char *inbuf, unsigned int timeout,
+ size_t *len)
+{
+ uint8_t msgtype = NBSSkeepalive;
+
+ while (msgtype == NBSSkeepalive) {
+ NTSTATUS status;
+
+ status = read_smb_length_return_keepalive(fd, inbuf, timeout,
+ len);
+ if (!NT_STATUS_IS_OK(status)) {
+ char addr[INET6_ADDRSTRLEN];
+ /* Try and give an error message
+ * saying what client failed. */
+ DEBUG(0, ("read_smb_length_return_keepalive failed for "
+ "client %s read error = %s.\n",
+ get_peer_addr(fd,addr,sizeof(addr)),
+ nt_errstr(status)));
+ return status;
+ }
+
+ msgtype = CVAL(inbuf, 0);
+ }
+
+ DEBUG(10,("read_smb_length: got smb length of %lu\n",
+ (unsigned long)len));
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Reply to a writebraw (core+ or LANMAN1.0 protocol).
+****************************************************************************/
+
+void reply_writebraw(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ struct smbXsrv_connection *xconn = req->xconn;
+ char *buf = NULL;
+ ssize_t nwritten=0;
+ ssize_t total_written=0;
+ size_t numtowrite=0;
+ size_t tcount;
+ off_t startpos;
+ const char *data=NULL;
+ bool write_through;
+ files_struct *fsp;
+ struct lock_struct lock;
+ NTSTATUS status;
+
+ START_PROFILE(SMBwritebraw);
+
+ /*
+ * If we ever reply with an error, it must have the SMB command
+ * type of SMBwritec, not SMBwriteBraw, as this tells the client
+ * we're finished.
+ */
+ SCVAL(discard_const_p(uint8_t, req->inbuf),smb_com,SMBwritec);
+
+ if (smb1_srv_is_signing_active(xconn)) {
+ END_PROFILE(SMBwritebraw);
+ exit_server_cleanly("reply_writebraw: SMB signing is active - "
+ "raw reads/writes are disallowed.");
+ }
+
+ if (req->wct < 12) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ error_to_writebrawerr(req);
+ END_PROFILE(SMBwritebraw);
+ return;
+ }
+
+ if (xconn->smb1.echo_handler.trusted_fde) {
+ DEBUG(2,("SMBwritebraw rejected with NOT_SUPPORTED because of "
+ "'async smb echo handler = yes'\n"));
+ reply_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ error_to_writebrawerr(req);
+ END_PROFILE(SMBwritebraw);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+ if (!check_fsp(conn, req, fsp)) {
+ error_to_writebrawerr(req);
+ END_PROFILE(SMBwritebraw);
+ return;
+ }
+
+ status = check_any_access_fsp(fsp, FILE_WRITE_DATA|FILE_APPEND_DATA);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ error_to_writebrawerr(req);
+ END_PROFILE(SMBwritebraw);
+ return;
+ }
+
+ tcount = IVAL(req->vwv+1, 0);
+ startpos = IVAL_TO_SMB_OFF_T(req->vwv+3, 0);
+ write_through = BITSETW(req->vwv+7,0);
+
+ /* We have to deal with slightly different formats depending
+ on whether we are using the core+ or lanman1.0 protocol */
+
+ if(xconn->protocol <= PROTOCOL_COREPLUS) {
+ numtowrite = SVAL(smb_buf_const(req->inbuf),-2);
+ data = smb_buf_const(req->inbuf);
+ } else {
+ numtowrite = SVAL(req->vwv+10, 0);
+ data = smb_base(req->inbuf) + SVAL(req->vwv+11, 0);
+ }
+
+ /* Ensure we don't write bytes past the end of this packet. */
+ /*
+ * This already protects us against CVE-2017-12163.
+ */
+ if (data + numtowrite > smb_base(req->inbuf) + smb_len(req->inbuf)) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ error_to_writebrawerr(req);
+ END_PROFILE(SMBwritebraw);
+ return;
+ }
+
+ if (!fsp->print_file) {
+ init_strict_lock_struct(fsp,
+ (uint64_t)req->smbpid,
+ (uint64_t)startpos,
+ (uint64_t)tcount,
+ WRITE_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &lock);
+
+ if (!SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, &lock)) {
+ reply_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
+ error_to_writebrawerr(req);
+ END_PROFILE(SMBwritebraw);
+ return;
+ }
+ }
+
+ if (numtowrite>0) {
+ nwritten = write_file(req,fsp,data,startpos,numtowrite);
+ }
+
+ DEBUG(3, ("reply_writebraw: initial write %s start=%.0f num=%d "
+ "wrote=%d sync=%d\n",
+ fsp_fnum_dbg(fsp), (double)startpos, (int)numtowrite,
+ (int)nwritten, (int)write_through));
+
+ if (nwritten < (ssize_t)numtowrite) {
+ reply_nterror(req, NT_STATUS_DISK_FULL);
+ error_to_writebrawerr(req);
+ goto out;
+ }
+
+ total_written = nwritten;
+
+ /* Allocate a buffer of 64k + length. */
+ buf = talloc_array(NULL, char, 65540);
+ if (!buf) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ error_to_writebrawerr(req);
+ goto out;
+ }
+
+ /* Return a SMBwritebraw message to the redirector to tell
+ * it to send more bytes */
+
+ memcpy(buf, req->inbuf, smb_size);
+ srv_smb1_set_message(buf,xconn->protocol>PROTOCOL_COREPLUS?1:0,0,True);
+ SCVAL(buf,smb_com,SMBwritebraw);
+ SSVALS(buf,smb_vwv0,0xFFFF);
+ show_msg(buf);
+ if (!smb1_srv_send(req->xconn,
+ buf,
+ false,
+ 0, /* no signing */
+ IS_CONN_ENCRYPTED(conn))) {
+ exit_server_cleanly("reply_writebraw: smb1_srv_send "
+ "failed.");
+ }
+
+ /* Now read the raw data into the buffer and write it */
+ status = read_smb_length(xconn->transport.sock, buf, SMB_SECONDARY_WAIT,
+ &numtowrite);
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_server_cleanly("secondary writebraw failed");
+ }
+
+ /* Set up outbuf to return the correct size */
+ reply_smb1_outbuf(req, 1, 0);
+
+ if (numtowrite != 0) {
+
+ if (numtowrite > 0xFFFF) {
+ DEBUG(0,("reply_writebraw: Oversize secondary write "
+ "raw requested (%u). Terminating\n",
+ (unsigned int)numtowrite ));
+ exit_server_cleanly("secondary writebraw failed");
+ }
+
+ if (tcount > nwritten+numtowrite) {
+ DEBUG(3,("reply_writebraw: Client overestimated the "
+ "write %d %d %d\n",
+ (int)tcount,(int)nwritten,(int)numtowrite));
+ }
+
+ status = read_data_ntstatus(xconn->transport.sock, buf+4,
+ numtowrite);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Try and give an error message
+ * saying what client failed. */
+ DEBUG(0, ("reply_writebraw: Oversize secondary write "
+ "raw read failed (%s) for client %s. "
+ "Terminating\n", nt_errstr(status),
+ smbXsrv_connection_dbg(xconn)));
+ exit_server_cleanly("secondary writebraw failed");
+ }
+
+ /*
+ * We are not vulnerable to CVE-2017-12163
+ * here as we are guaranteed to have numtowrite
+ * bytes available - we just read from the client.
+ */
+ nwritten = write_file(req,fsp,buf+4,startpos+nwritten,numtowrite);
+ if (nwritten == -1) {
+ TALLOC_FREE(buf);
+ reply_nterror(req, map_nt_error_from_unix(errno));
+ error_to_writebrawerr(req);
+ goto out;
+ }
+
+ if (nwritten < (ssize_t)numtowrite) {
+ SCVAL(req->outbuf,smb_rcls,ERRHRD);
+ SSVAL(req->outbuf,smb_err,ERRdiskfull);
+ }
+
+ if (nwritten > 0) {
+ total_written += nwritten;
+ }
+ }
+
+ TALLOC_FREE(buf);
+ SSVAL(req->outbuf,smb_vwv0,total_written);
+
+ status = sync_file(conn, fsp, write_through);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("reply_writebraw: sync_file for %s returned %s\n",
+ fsp_str_dbg(fsp), nt_errstr(status)));
+ reply_nterror(req, status);
+ error_to_writebrawerr(req);
+ goto out;
+ }
+
+ DEBUG(3,("reply_writebraw: secondary write %s start=%.0f num=%d "
+ "wrote=%d\n",
+ fsp_fnum_dbg(fsp), (double)startpos, (int)numtowrite,
+ (int)total_written));
+
+ /* We won't return a status if write through is not selected - this
+ * follows what WfWg does */
+ END_PROFILE(SMBwritebraw);
+
+ if (!write_through && total_written==tcount) {
+
+#if RABBIT_PELLET_FIX
+ /*
+ * Fix for "rabbit pellet" mode, trigger an early TCP ack by
+ * sending a NBSSkeepalive. Thanks to DaveCB at Sun for this.
+ * JRA.
+ */
+ if (!send_keepalive(xconn->transport.sock)) {
+ exit_server_cleanly("reply_writebraw: send of "
+ "keepalive failed");
+ }
+#endif
+ TALLOC_FREE(req->outbuf);
+ }
+ return;
+
+out:
+ END_PROFILE(SMBwritebraw);
+ return;
+}
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_LOCKING
+
+/****************************************************************************
+ Reply to a writeunlock (core+).
+****************************************************************************/
+
+void reply_writeunlock(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ ssize_t nwritten = -1;
+ size_t numtowrite;
+ size_t remaining;
+ off_t startpos;
+ const char *data;
+ NTSTATUS status = NT_STATUS_OK;
+ files_struct *fsp;
+ struct lock_struct lock;
+ int saved_errno = 0;
+
+ START_PROFILE(SMBwriteunlock);
+
+ if (req->wct < 5) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBwriteunlock);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBwriteunlock);
+ return;
+ }
+
+ status = check_any_access_fsp(fsp, FILE_WRITE_DATA|FILE_APPEND_DATA);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBwriteunlock);
+ return;
+ }
+
+ numtowrite = SVAL(req->vwv+1, 0);
+ startpos = IVAL_TO_SMB_OFF_T(req->vwv+2, 0);
+ data = (const char *)req->buf + 3;
+
+ /*
+ * Ensure client isn't asking us to write more than
+ * they sent. CVE-2017-12163.
+ */
+ remaining = smbreq_bufrem(req, data);
+ if (numtowrite > remaining) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBwriteunlock);
+ return;
+ }
+
+ if (!fsp->print_file && numtowrite > 0) {
+ init_strict_lock_struct(fsp,
+ (uint64_t)req->smbpid,
+ (uint64_t)startpos,
+ (uint64_t)numtowrite,
+ WRITE_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &lock);
+
+ if (!SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, &lock)) {
+ reply_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
+ END_PROFILE(SMBwriteunlock);
+ return;
+ }
+ }
+
+ /* The special X/Open SMB protocol handling of
+ zero length writes is *NOT* done for
+ this call */
+ if(numtowrite == 0) {
+ nwritten = 0;
+ } else {
+ nwritten = write_file(req,fsp,data,startpos,numtowrite);
+ saved_errno = errno;
+ }
+
+ status = sync_file(conn, fsp, False /* write through */);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("reply_writeunlock: sync_file for %s returned %s\n",
+ fsp_str_dbg(fsp), nt_errstr(status)));
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if(nwritten < 0) {
+ reply_nterror(req, map_nt_error_from_unix(saved_errno));
+ goto out;
+ }
+
+ if((nwritten < numtowrite) && (numtowrite != 0)) {
+ reply_nterror(req, NT_STATUS_DISK_FULL);
+ goto out;
+ }
+
+ if (numtowrite && !fsp->print_file) {
+ struct smbd_lock_element l = {
+ .req_guid = smbd_request_guid(req, 0),
+ .smblctx = req->smbpid,
+ .brltype = UNLOCK_LOCK,
+ .lock_flav = WINDOWS_LOCK,
+ .offset = startpos,
+ .count = numtowrite,
+ };
+ status = smbd_do_unlocking(req, fsp, 1, &l);
+ if (NT_STATUS_V(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ }
+
+ reply_smb1_outbuf(req, 1, 0);
+
+ SSVAL(req->outbuf,smb_vwv0,nwritten);
+
+ DEBUG(3, ("writeunlock %s num=%d wrote=%d\n",
+ fsp_fnum_dbg(fsp), (int)numtowrite, (int)nwritten));
+
+out:
+ END_PROFILE(SMBwriteunlock);
+ return;
+}
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_ALL
+
+/****************************************************************************
+ Reply to a write.
+****************************************************************************/
+
+void reply_write(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ size_t numtowrite;
+ size_t remaining;
+ ssize_t nwritten = -1;
+ off_t startpos;
+ const char *data;
+ files_struct *fsp;
+ struct lock_struct lock;
+ NTSTATUS status;
+ int saved_errno = 0;
+
+ START_PROFILE(SMBwrite);
+
+ if (req->wct < 5) {
+ END_PROFILE(SMBwrite);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ /* If it's an IPC, pass off the pipe handler. */
+ if (IS_IPC(conn)) {
+ reply_pipe_write(req);
+ END_PROFILE(SMBwrite);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBwrite);
+ return;
+ }
+
+ status = check_any_access_fsp(fsp, FILE_WRITE_DATA|FILE_APPEND_DATA);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBwrite);
+ return;
+ }
+
+ numtowrite = SVAL(req->vwv+1, 0);
+ startpos = IVAL_TO_SMB_OFF_T(req->vwv+2, 0);
+ data = (const char *)req->buf + 3;
+
+ /*
+ * Ensure client isn't asking us to write more than
+ * they sent. CVE-2017-12163.
+ */
+ remaining = smbreq_bufrem(req, data);
+ if (numtowrite > remaining) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBwrite);
+ return;
+ }
+
+ if (!fsp->print_file) {
+ init_strict_lock_struct(fsp,
+ (uint64_t)req->smbpid,
+ (uint64_t)startpos,
+ (uint64_t)numtowrite,
+ WRITE_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &lock);
+
+ if (!SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, &lock)) {
+ reply_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
+ END_PROFILE(SMBwrite);
+ return;
+ }
+ }
+
+ /*
+ * X/Open SMB protocol says that if smb_vwv1 is
+ * zero then the file size should be extended or
+ * truncated to the size given in smb_vwv[2-3].
+ */
+
+ if(numtowrite == 0) {
+ /*
+ * This is actually an allocate call, and set EOF. JRA.
+ */
+ nwritten = vfs_allocate_file_space(fsp, (off_t)startpos);
+ if (nwritten < 0) {
+ reply_nterror(req, NT_STATUS_DISK_FULL);
+ goto out;
+ }
+ nwritten = vfs_set_filelen(fsp, (off_t)startpos);
+ if (nwritten < 0) {
+ reply_nterror(req, NT_STATUS_DISK_FULL);
+ goto out;
+ }
+ trigger_write_time_update_immediate(fsp);
+ } else {
+ nwritten = write_file(req,fsp,data,startpos,numtowrite);
+ }
+
+ status = sync_file(conn, fsp, False);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("reply_write: sync_file for %s returned %s\n",
+ fsp_str_dbg(fsp), nt_errstr(status)));
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if(nwritten < 0) {
+ reply_nterror(req, map_nt_error_from_unix(saved_errno));
+ goto out;
+ }
+
+ if((nwritten == 0) && (numtowrite != 0)) {
+ reply_nterror(req, NT_STATUS_DISK_FULL);
+ goto out;
+ }
+
+ reply_smb1_outbuf(req, 1, 0);
+
+ SSVAL(req->outbuf,smb_vwv0,nwritten);
+
+ if (nwritten < (ssize_t)numtowrite) {
+ SCVAL(req->outbuf,smb_rcls,ERRHRD);
+ SSVAL(req->outbuf,smb_err,ERRdiskfull);
+ }
+
+ DEBUG(3, ("write %s num=%d wrote=%d\n", fsp_fnum_dbg(fsp), (int)numtowrite, (int)nwritten));
+
+out:
+ END_PROFILE(SMBwrite);
+ return;
+}
+
+/****************************************************************************
+ Ensure a buffer is a valid writeX for recvfile purposes.
+****************************************************************************/
+
+#define STANDARD_WRITE_AND_X_HEADER_SIZE (smb_size - 4 + /* basic header */ \
+ (2*14) + /* word count (including bcc) */ \
+ 1 /* pad byte */)
+
+bool is_valid_writeX_buffer(struct smbXsrv_connection *xconn,
+ const uint8_t *inbuf)
+{
+ size_t numtowrite;
+ unsigned int doff = 0;
+ size_t len = smb_len_large(inbuf);
+ uint16_t fnum;
+ struct smbXsrv_open *op = NULL;
+ struct files_struct *fsp = NULL;
+ NTSTATUS status;
+
+ if (is_encrypted_packet(inbuf)) {
+ /* Can't do this on encrypted
+ * connections. */
+ return false;
+ }
+
+ if (CVAL(inbuf,smb_com) != SMBwriteX) {
+ return false;
+ }
+
+ if (CVAL(inbuf,smb_vwv0) != 0xFF ||
+ CVAL(inbuf,smb_wct) != 14) {
+ DEBUG(10,("is_valid_writeX_buffer: chained or "
+ "invalid word length.\n"));
+ return false;
+ }
+
+ fnum = SVAL(inbuf, smb_vwv2);
+ status = smb1srv_open_lookup(xconn,
+ fnum,
+ 0, /* now */
+ &op);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10,("is_valid_writeX_buffer: bad fnum\n"));
+ return false;
+ }
+ fsp = op->compat;
+ if (fsp == NULL) {
+ DEBUG(10,("is_valid_writeX_buffer: bad fsp\n"));
+ return false;
+ }
+ if (fsp->conn == NULL) {
+ DEBUG(10,("is_valid_writeX_buffer: bad fsp->conn\n"));
+ return false;
+ }
+
+ if (IS_IPC(fsp->conn)) {
+ DEBUG(10,("is_valid_writeX_buffer: IPC$ tid\n"));
+ return false;
+ }
+ if (IS_PRINT(fsp->conn)) {
+ DEBUG(10,("is_valid_writeX_buffer: printing tid\n"));
+ return false;
+ }
+ if (fsp_is_alternate_stream(fsp)) {
+ DEBUG(10,("is_valid_writeX_buffer: stream fsp\n"));
+ return false;
+ }
+ doff = SVAL(inbuf,smb_vwv11);
+
+ numtowrite = SVAL(inbuf,smb_vwv10);
+
+ if (len > doff && len - doff > 0xFFFF) {
+ numtowrite |= (((size_t)SVAL(inbuf,smb_vwv9))<<16);
+ }
+
+ if (numtowrite == 0) {
+ DEBUG(10,("is_valid_writeX_buffer: zero write\n"));
+ return false;
+ }
+
+ /* Ensure the sizes match up. */
+ if (doff < STANDARD_WRITE_AND_X_HEADER_SIZE) {
+ /* no pad byte...old smbclient :-( */
+ DEBUG(10,("is_valid_writeX_buffer: small doff %u (min %u)\n",
+ (unsigned int)doff,
+ (unsigned int)STANDARD_WRITE_AND_X_HEADER_SIZE));
+ return false;
+ }
+
+ if (len - doff != numtowrite) {
+ DEBUG(10,("is_valid_writeX_buffer: doff mismatch "
+ "len = %u, doff = %u, numtowrite = %u\n",
+ (unsigned int)len,
+ (unsigned int)doff,
+ (unsigned int)numtowrite ));
+ return false;
+ }
+
+ DEBUG(10,("is_valid_writeX_buffer: true "
+ "len = %u, doff = %u, numtowrite = %u\n",
+ (unsigned int)len,
+ (unsigned int)doff,
+ (unsigned int)numtowrite ));
+
+ return true;
+}
+
+/****************************************************************************
+ Reply to a write and X.
+****************************************************************************/
+
+void reply_write_and_X(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ struct smbXsrv_connection *xconn = req->xconn;
+ files_struct *fsp;
+ struct lock_struct lock;
+ off_t startpos;
+ size_t numtowrite;
+ bool write_through;
+ ssize_t nwritten;
+ unsigned int smb_doff;
+ unsigned int smblen;
+ const char *data;
+ NTSTATUS status;
+ int saved_errno = 0;
+
+ START_PROFILE(SMBwriteX);
+
+ if ((req->wct != 12) && (req->wct != 14)) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ numtowrite = SVAL(req->vwv+10, 0);
+ smb_doff = SVAL(req->vwv+11, 0);
+ smblen = smb_len(req->inbuf);
+
+ if (req->unread_bytes > 0xFFFF ||
+ (smblen > smb_doff &&
+ smblen - smb_doff > 0xFFFF)) {
+ numtowrite |= (((size_t)SVAL(req->vwv+9, 0))<<16);
+ }
+
+ if (req->unread_bytes) {
+ /* Can't do a recvfile write on IPC$ */
+ if (IS_IPC(conn)) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+ if (numtowrite != req->unread_bytes) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+ } else {
+ /*
+ * This already protects us against CVE-2017-12163.
+ */
+ if (smb_doff > smblen || smb_doff + numtowrite < numtowrite ||
+ smb_doff + numtowrite > smblen) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+ }
+
+ /* If it's an IPC, pass off the pipe handler. */
+ if (IS_IPC(conn)) {
+ if (req->unread_bytes) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+ reply_pipe_write_and_X(req);
+ goto out;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+2, 0));
+ startpos = IVAL_TO_SMB_OFF_T(req->vwv+3, 0);
+ write_through = BITSETW(req->vwv+7,0);
+
+ if (!check_fsp(conn, req, fsp)) {
+ goto out;
+ }
+
+ status = check_any_access_fsp(fsp, FILE_WRITE_DATA|FILE_APPEND_DATA);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ data = smb_base(req->inbuf) + smb_doff;
+
+ if(req->wct == 14) {
+ /*
+ * This is a large offset (64 bit) write.
+ */
+ startpos |= (((off_t)IVAL(req->vwv+12, 0)) << 32);
+
+ }
+
+ /* X/Open SMB protocol says that, unlike SMBwrite
+ if the length is zero then NO truncation is
+ done, just a write of zero. To truncate a file,
+ use SMBwrite. */
+
+ if(numtowrite == 0) {
+ nwritten = 0;
+ } else {
+ if (req->unread_bytes == 0) {
+ status = schedule_aio_write_and_X(conn,
+ req,
+ fsp,
+ data,
+ startpos,
+ numtowrite);
+
+ if (NT_STATUS_IS_OK(status)) {
+ /* write scheduled - we're done. */
+ goto out;
+ }
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
+ /* Real error - report to client. */
+ reply_nterror(req, status);
+ goto out;
+ }
+ /* NT_STATUS_RETRY - fall through to sync write. */
+ }
+
+ init_strict_lock_struct(fsp,
+ (uint64_t)req->smbpid,
+ (uint64_t)startpos,
+ (uint64_t)numtowrite,
+ WRITE_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &lock);
+
+ if (!SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, &lock)) {
+ reply_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
+ goto out;
+ }
+
+ nwritten = write_file(req,fsp,data,startpos,numtowrite);
+ saved_errno = errno;
+ }
+
+ if(nwritten < 0) {
+ reply_nterror(req, map_nt_error_from_unix(saved_errno));
+ goto out;
+ }
+
+ if((nwritten == 0) && (numtowrite != 0)) {
+ reply_nterror(req, NT_STATUS_DISK_FULL);
+ goto out;
+ }
+
+ reply_smb1_outbuf(req, 6, 0);
+ SSVAL(req->outbuf, smb_vwv0, 0xff); /* andx chain ends */
+ SSVAL(req->outbuf, smb_vwv1, 0); /* no andx offset */
+ SSVAL(req->outbuf,smb_vwv2,nwritten);
+ SSVAL(req->outbuf,smb_vwv4,nwritten>>16);
+
+ DEBUG(3,("writeX %s num=%d wrote=%d\n",
+ fsp_fnum_dbg(fsp), (int)numtowrite, (int)nwritten));
+
+ status = sync_file(conn, fsp, write_through);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("reply_write_and_X: sync_file for %s returned %s\n",
+ fsp_str_dbg(fsp), nt_errstr(status)));
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ END_PROFILE(SMBwriteX);
+ return;
+
+out:
+ if (req->unread_bytes) {
+ /* writeX failed. drain socket. */
+ if (drain_socket(xconn->transport.sock, req->unread_bytes) !=
+ req->unread_bytes) {
+ smb_panic("failed to drain pending bytes");
+ }
+ req->unread_bytes = 0;
+ }
+
+ END_PROFILE(SMBwriteX);
+ return;
+}
+
+/****************************************************************************
+ Reply to a lseek.
+****************************************************************************/
+
+void reply_lseek(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ off_t startpos;
+ off_t res= -1;
+ int mode,umode;
+ files_struct *fsp;
+ NTSTATUS status;
+
+ START_PROFILE(SMBlseek);
+
+ if (req->wct < 4) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBlseek);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ return;
+ }
+
+ mode = SVAL(req->vwv+1, 0) & 3;
+ /* NB. This doesn't use IVAL_TO_SMB_OFF_T as startpos can be signed in this case. */
+ startpos = (off_t)IVALS(req->vwv+2, 0);
+
+ switch (mode) {
+ case 0:
+ umode = SEEK_SET;
+ res = startpos;
+ break;
+ case 1:
+ umode = SEEK_CUR;
+ res = fh_get_pos(fsp->fh) + startpos;
+ break;
+ case 2:
+ umode = SEEK_END;
+ break;
+ default:
+ umode = SEEK_SET;
+ res = startpos;
+ break;
+ }
+
+ if (umode == SEEK_END) {
+ if((res = SMB_VFS_LSEEK(fsp,startpos,umode)) == -1) {
+ if(errno == EINVAL) {
+ off_t current_pos = startpos;
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBlseek);
+ return;
+ }
+
+ current_pos += fsp->fsp_name->st.st_ex_size;
+ if(current_pos < 0)
+ res = SMB_VFS_LSEEK(fsp,0,SEEK_SET);
+ }
+ }
+
+ if(res == -1) {
+ reply_nterror(req, map_nt_error_from_unix(errno));
+ END_PROFILE(SMBlseek);
+ return;
+ }
+ }
+
+ fh_set_pos(fsp->fh, res);
+
+ reply_smb1_outbuf(req, 2, 0);
+ SIVAL(req->outbuf,smb_vwv0,res);
+
+ DEBUG(3,("lseek %s ofs=%.0f newpos = %.0f mode=%d\n",
+ fsp_fnum_dbg(fsp), (double)startpos, (double)res, mode));
+
+ END_PROFILE(SMBlseek);
+ return;
+}
+
+static struct files_struct *file_sync_one_fn(struct files_struct *fsp,
+ void *private_data)
+{
+ connection_struct *conn = talloc_get_type_abort(
+ private_data, connection_struct);
+
+ if (conn != fsp->conn) {
+ return NULL;
+ }
+ if (fsp_get_io_fd(fsp) == -1) {
+ return NULL;
+ }
+ sync_file(conn, fsp, True /* write through */);
+
+ if (fsp->fsp_flags.modified) {
+ trigger_write_time_update_immediate(fsp);
+ }
+
+ return NULL;
+}
+
+/****************************************************************************
+ Reply to a flush.
+****************************************************************************/
+
+void reply_flush(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ uint16_t fnum;
+ files_struct *fsp;
+
+ START_PROFILE(SMBflush);
+
+ if (req->wct < 1) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ fnum = SVAL(req->vwv+0, 0);
+ fsp = file_fsp(req, fnum);
+
+ if ((fnum != 0xFFFF) && !check_fsp(conn, req, fsp)) {
+ return;
+ }
+
+ if (!fsp) {
+ files_forall(req->sconn, file_sync_one_fn, conn);
+ } else {
+ NTSTATUS status = sync_file(conn, fsp, True);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("reply_flush: sync_file for %s returned %s\n",
+ fsp_str_dbg(fsp), nt_errstr(status)));
+ reply_nterror(req, status);
+ END_PROFILE(SMBflush);
+ return;
+ }
+ if (fsp->fsp_flags.modified) {
+ trigger_write_time_update_immediate(fsp);
+ }
+ }
+
+ reply_smb1_outbuf(req, 0, 0);
+
+ DEBUG(3,("flush\n"));
+ END_PROFILE(SMBflush);
+ return;
+}
+
+/****************************************************************************
+ Reply to a exit.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+static struct tevent_req *reply_exit_send(struct smb_request *smb1req);
+static void reply_exit_done(struct tevent_req *req);
+
+void reply_exit(struct smb_request *smb1req)
+{
+ struct tevent_req *req;
+
+ /*
+ * Don't setup the profile charge here, take
+ * it in reply_exit_done(). Not strictly correct
+ * but better than the other SMB1 async
+ * code that double-charges at the moment.
+ */
+ req = reply_exit_send(smb1req);
+ if (req == NULL) {
+ /* Not going async, profile here. */
+ START_PROFILE(SMBexit);
+ reply_force_doserror(smb1req, ERRDOS, ERRnomem);
+ END_PROFILE(SMBexit);
+ return;
+ }
+
+ /* We're async. This will complete later. */
+ tevent_req_set_callback(req, reply_exit_done, smb1req);
+ return;
+}
+
+struct reply_exit_state {
+ struct tevent_queue *wait_queue;
+};
+
+static void reply_exit_wait_done(struct tevent_req *subreq);
+
+/****************************************************************************
+ Async SMB1 exit.
+ Note, on failure here we deallocate and return NULL to allow the caller to
+ SMB1 return an error of ERRnomem immediately.
+****************************************************************************/
+
+static struct tevent_req *reply_exit_send(struct smb_request *smb1req)
+{
+ struct tevent_req *req;
+ struct reply_exit_state *state;
+ struct tevent_req *subreq;
+ files_struct *fsp;
+ struct smbd_server_connection *sconn = smb1req->sconn;
+
+ req = tevent_req_create(smb1req, &state,
+ struct reply_exit_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->wait_queue = tevent_queue_create(state,
+ "reply_exit_wait_queue");
+ if (tevent_req_nomem(state->wait_queue, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+
+ for (fsp = sconn->files; fsp; fsp = fsp->next) {
+ if (fsp->file_pid != smb1req->smbpid) {
+ continue;
+ }
+ if (fsp->vuid != smb1req->vuid) {
+ continue;
+ }
+ /*
+ * Flag the file as close in progress.
+ * This will prevent any more IO being
+ * done on it.
+ */
+ fsp->fsp_flags.closing = true;
+
+ if (fsp->num_aio_requests > 0) {
+ /*
+ * Now wait until all aio requests on this fsp are
+ * finished.
+ *
+ * We don't set a callback, as we just want to block the
+ * wait queue and the talloc_free() of fsp->aio_request
+ * will remove the item from the wait queue.
+ */
+ subreq = tevent_queue_wait_send(fsp->aio_requests,
+ sconn->ev_ctx,
+ state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+ }
+ }
+
+ /*
+ * Now we add our own waiter to the end of the queue,
+ * this way we get notified when all pending requests are finished
+ * and reply to the outstanding SMB1 request.
+ */
+ subreq = tevent_queue_wait_send(state,
+ sconn->ev_ctx,
+ state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+
+ /*
+ * We're really going async - move the SMB1 request from
+ * a talloc stackframe above us to the conn talloc-context.
+ * We need this to stick around until the wait_done
+ * callback is invoked.
+ */
+ smb1req = talloc_move(sconn, &smb1req);
+
+ tevent_req_set_callback(subreq, reply_exit_wait_done, req);
+
+ return req;
+}
+
+static void reply_exit_wait_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+
+ tevent_queue_wait_recv(subreq);
+ TALLOC_FREE(subreq);
+ tevent_req_done(req);
+}
+
+static NTSTATUS reply_exit_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static void reply_exit_done(struct tevent_req *req)
+{
+ struct smb_request *smb1req = tevent_req_callback_data(
+ req, struct smb_request);
+ struct smbd_server_connection *sconn = smb1req->sconn;
+ struct smbXsrv_connection *xconn = smb1req->xconn;
+ NTTIME now = timeval_to_nttime(&smb1req->request_time);
+ struct smbXsrv_session *session = NULL;
+ files_struct *fsp, *next;
+ NTSTATUS status;
+
+ /*
+ * Take the profile charge here. Not strictly
+ * correct but better than the other SMB1 async
+ * code that double-charges at the moment.
+ */
+ START_PROFILE(SMBexit);
+
+ status = reply_exit_recv(req);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(smb1req);
+ END_PROFILE(SMBexit);
+ exit_server(__location__ ": reply_exit_recv failed");
+ return;
+ }
+
+ /*
+ * Ensure the session is still valid.
+ */
+ status = smb1srv_session_lookup(xconn,
+ smb1req->vuid,
+ now,
+ &session);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_force_doserror(smb1req, ERRSRV, ERRinvnid);
+ smb_request_done(smb1req);
+ END_PROFILE(SMBexit);
+ return;
+ }
+
+ /*
+ * Ensure the vuid is still valid - no one
+ * called reply_ulogoffX() in the meantime.
+ * reply_exit() doesn't have AS_USER set, so
+ * use set_current_user_info() directly.
+ * This is the same logic as in switch_message().
+ */
+ if (session->global->auth_session_info != NULL) {
+ set_current_user_info(
+ session->global->auth_session_info->unix_info->sanitized_username,
+ session->global->auth_session_info->unix_info->unix_name,
+ session->global->auth_session_info->info->domain_name);
+ }
+
+ /* No more aio - do the actual closes. */
+ for (fsp = sconn->files; fsp; fsp = next) {
+ bool ok;
+ next = fsp->next;
+
+ if (fsp->file_pid != smb1req->smbpid) {
+ continue;
+ }
+ if (fsp->vuid != smb1req->vuid) {
+ continue;
+ }
+ if (!fsp->fsp_flags.closing) {
+ continue;
+ }
+
+ /*
+ * reply_exit() has the DO_CHDIR flag set.
+ */
+ ok = chdir_current_service(fsp->conn);
+ if (!ok) {
+ reply_force_doserror(smb1req, ERRSRV, ERRinvnid);
+ smb_request_done(smb1req);
+ END_PROFILE(SMBexit);
+ return;
+ }
+ close_file_free(NULL, &fsp, SHUTDOWN_CLOSE);
+ }
+
+ reply_smb1_outbuf(smb1req, 0, 0);
+ /*
+ * The following call is needed to push the
+ * reply data back out the socket after async
+ * return. Plus it frees smb1req.
+ */
+ smb_request_done(smb1req);
+ DBG_INFO("reply_exit complete\n");
+ END_PROFILE(SMBexit);
+ return;
+}
+
+static struct tevent_req *reply_close_send(struct smb_request *smb1req,
+ files_struct *fsp);
+static void reply_close_done(struct tevent_req *req);
+
+void reply_close(struct smb_request *smb1req)
+{
+ connection_struct *conn = smb1req->conn;
+ NTSTATUS status = NT_STATUS_OK;
+ files_struct *fsp = NULL;
+ START_PROFILE(SMBclose);
+
+ if (smb1req->wct < 3) {
+ reply_nterror(smb1req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBclose);
+ return;
+ }
+
+ fsp = file_fsp(smb1req, SVAL(smb1req->vwv+0, 0));
+
+ /*
+ * We can only use check_fsp if we know it's not a directory.
+ */
+
+ if (!check_fsp_open(conn, smb1req, fsp)) {
+ END_PROFILE(SMBclose);
+ return;
+ }
+
+ DBG_NOTICE("Close %s fd=%d %s (numopen=%d)\n",
+ fsp->fsp_flags.is_directory ?
+ "directory" : "file",
+ fsp_get_pathref_fd(fsp), fsp_fnum_dbg(fsp),
+ conn->num_files_open);
+
+ if (!fsp->fsp_flags.is_directory) {
+ time_t t;
+
+ /*
+ * Take care of any time sent in the close.
+ */
+
+ t = srv_make_unix_date3(smb1req->vwv+1);
+ set_close_write_time(fsp, time_t_to_full_timespec(t));
+ }
+
+ if (fsp->num_aio_requests != 0) {
+ struct tevent_req *req;
+
+ req = reply_close_send(smb1req, fsp);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ /* We're async. This will complete later. */
+ tevent_req_set_callback(req, reply_close_done, smb1req);
+ END_PROFILE(SMBclose);
+ return;
+ }
+
+ /*
+ * close_file_free() returns the unix errno if an error was detected on
+ * close - normally this is due to a disk full error. If not then it
+ * was probably an I/O error.
+ */
+
+ status = close_file_free(smb1req, &fsp, NORMAL_CLOSE);
+done:
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(smb1req, status);
+ END_PROFILE(SMBclose);
+ return;
+ }
+
+ reply_smb1_outbuf(smb1req, 0, 0);
+ END_PROFILE(SMBclose);
+ return;
+}
+
+struct reply_close_state {
+ files_struct *fsp;
+ struct tevent_queue *wait_queue;
+};
+
+static void reply_close_wait_done(struct tevent_req *subreq);
+
+/****************************************************************************
+ Async SMB1 close.
+ Note, on failure here we deallocate and return NULL to allow the caller to
+ SMB1 return an error of ERRnomem immediately.
+****************************************************************************/
+
+static struct tevent_req *reply_close_send(struct smb_request *smb1req,
+ files_struct *fsp)
+{
+ struct tevent_req *req;
+ struct reply_close_state *state;
+ struct tevent_req *subreq;
+ struct smbd_server_connection *sconn = smb1req->sconn;
+
+ req = tevent_req_create(smb1req, &state,
+ struct reply_close_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->wait_queue = tevent_queue_create(state,
+ "reply_close_wait_queue");
+ if (tevent_req_nomem(state->wait_queue, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+
+ /*
+ * Flag the file as close in progress.
+ * This will prevent any more IO being
+ * done on it.
+ */
+ fsp->fsp_flags.closing = true;
+
+ /*
+ * Now wait until all aio requests on this fsp are
+ * finished.
+ *
+ * We don't set a callback, as we just want to block the
+ * wait queue and the talloc_free() of fsp->aio_request
+ * will remove the item from the wait queue.
+ */
+ subreq = tevent_queue_wait_send(fsp->aio_requests,
+ sconn->ev_ctx,
+ state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+
+ /*
+ * Now we add our own waiter to the end of the queue,
+ * this way we get notified when all pending requests are finished
+ * and reply to the outstanding SMB1 request.
+ */
+ subreq = tevent_queue_wait_send(state,
+ sconn->ev_ctx,
+ state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+
+ /*
+ * We're really going async - move the SMB1 request from
+ * a talloc stackframe above us to the conn talloc-context.
+ * We need this to stick around until the wait_done
+ * callback is invoked.
+ */
+ smb1req = talloc_move(sconn, &smb1req);
+
+ tevent_req_set_callback(subreq, reply_close_wait_done, req);
+
+ return req;
+}
+
+static void reply_close_wait_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+
+ tevent_queue_wait_recv(subreq);
+ TALLOC_FREE(subreq);
+ tevent_req_done(req);
+}
+
+static NTSTATUS reply_close_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static void reply_close_done(struct tevent_req *req)
+{
+ struct smb_request *smb1req = tevent_req_callback_data(
+ req, struct smb_request);
+ struct reply_close_state *state = tevent_req_data(req,
+ struct reply_close_state);
+ NTSTATUS status;
+
+ status = reply_close_recv(req);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(smb1req);
+ exit_server(__location__ ": reply_close_recv failed");
+ return;
+ }
+
+ status = close_file_free(smb1req, &state->fsp, NORMAL_CLOSE);
+ if (NT_STATUS_IS_OK(status)) {
+ reply_smb1_outbuf(smb1req, 0, 0);
+ } else {
+ reply_nterror(smb1req, status);
+ }
+ /*
+ * The following call is needed to push the
+ * reply data back out the socket after async
+ * return. Plus it frees smb1req.
+ */
+ smb_request_done(smb1req);
+}
+
+/****************************************************************************
+ Reply to a writeclose (Core+ protocol).
+****************************************************************************/
+
+void reply_writeclose(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ size_t numtowrite;
+ size_t remaining;
+ ssize_t nwritten = -1;
+ NTSTATUS close_status = NT_STATUS_OK;
+ off_t startpos;
+ const char *data;
+ struct timespec mtime;
+ files_struct *fsp;
+ struct lock_struct lock;
+ NTSTATUS status;
+
+ START_PROFILE(SMBwriteclose);
+
+ if (req->wct < 6) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBwriteclose);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBwriteclose);
+ return;
+ }
+ status = check_any_access_fsp(fsp, FILE_WRITE_DATA|FILE_APPEND_DATA);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBwriteclose);
+ return;
+ }
+
+ numtowrite = SVAL(req->vwv+1, 0);
+ startpos = IVAL_TO_SMB_OFF_T(req->vwv+2, 0);
+ mtime = time_t_to_full_timespec(srv_make_unix_date3(req->vwv+4));
+ data = (const char *)req->buf + 1;
+
+ /*
+ * Ensure client isn't asking us to write more than
+ * they sent. CVE-2017-12163.
+ */
+ remaining = smbreq_bufrem(req, data);
+ if (numtowrite > remaining) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBwriteclose);
+ return;
+ }
+
+ if (fsp->print_file == NULL) {
+ init_strict_lock_struct(fsp,
+ (uint64_t)req->smbpid,
+ (uint64_t)startpos,
+ (uint64_t)numtowrite,
+ WRITE_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &lock);
+
+ if (!SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, &lock)) {
+ reply_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
+ END_PROFILE(SMBwriteclose);
+ return;
+ }
+ }
+
+ nwritten = write_file(req,fsp,data,startpos,numtowrite);
+
+ set_close_write_time(fsp, mtime);
+
+ /*
+ * More insanity. W2K only closes the file if writelen > 0.
+ * JRA.
+ */
+
+ DEBUG(3,("writeclose %s num=%d wrote=%d (numopen=%d)\n",
+ fsp_fnum_dbg(fsp), (int)numtowrite, (int)nwritten,
+ (numtowrite) ? conn->num_files_open - 1 : conn->num_files_open));
+
+ if (numtowrite) {
+ DEBUG(3,("reply_writeclose: zero length write doesn't close "
+ "file %s\n", fsp_str_dbg(fsp)));
+ close_status = close_file_free(req, &fsp, NORMAL_CLOSE);
+ }
+
+ if(((nwritten == 0) && (numtowrite != 0))||(nwritten < 0)) {
+ reply_nterror(req, NT_STATUS_DISK_FULL);
+ goto out;
+ }
+
+ if(!NT_STATUS_IS_OK(close_status)) {
+ reply_nterror(req, close_status);
+ goto out;
+ }
+
+ reply_smb1_outbuf(req, 1, 0);
+
+ SSVAL(req->outbuf,smb_vwv0,nwritten);
+
+out:
+
+ END_PROFILE(SMBwriteclose);
+ return;
+}
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_LOCKING
+
+/****************************************************************************
+ Reply to a lock.
+****************************************************************************/
+
+static void reply_lock_done(struct tevent_req *subreq);
+
+void reply_lock(struct smb_request *req)
+{
+ struct tevent_req *subreq = NULL;
+ connection_struct *conn = req->conn;
+ files_struct *fsp;
+ struct smbd_lock_element *lck = NULL;
+
+ START_PROFILE(SMBlock);
+
+ if (req->wct < 5) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBlock);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBlock);
+ return;
+ }
+
+ lck = talloc(req, struct smbd_lock_element);
+ if (lck == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBlock);
+ return;
+ }
+
+ *lck = (struct smbd_lock_element) {
+ .req_guid = smbd_request_guid(req, 0),
+ .smblctx = req->smbpid,
+ .brltype = WRITE_LOCK,
+ .lock_flav = WINDOWS_LOCK,
+ .count = IVAL(req->vwv+1, 0),
+ .offset = IVAL(req->vwv+3, 0),
+ };
+
+ DBG_NOTICE("lock fd=%d %s offset=%"PRIu64" count=%"PRIu64"\n",
+ fsp_get_io_fd(fsp),
+ fsp_fnum_dbg(fsp),
+ lck->offset,
+ lck->count);
+
+ subreq = smbd_smb1_do_locks_send(
+ fsp,
+ req->sconn->ev_ctx,
+ &req,
+ fsp,
+ 0,
+ false, /* large_offset */
+ 1,
+ lck);
+ if (subreq == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBlock);
+ return;
+ }
+ tevent_req_set_callback(subreq, reply_lock_done, NULL);
+ END_PROFILE(SMBlock);
+}
+
+static void reply_lock_done(struct tevent_req *subreq)
+{
+ struct smb_request *req = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ START_PROFILE(SMBlock);
+
+ ok = smbd_smb1_do_locks_extract_smbreq(subreq, talloc_tos(), &req);
+ SMB_ASSERT(ok);
+
+ status = smbd_smb1_do_locks_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ if (NT_STATUS_IS_OK(status)) {
+ reply_smb1_outbuf(req, 0, 0);
+ } else {
+ reply_nterror(req, status);
+ }
+
+ ok = smb1_srv_send(req->xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(req->conn));
+ if (!ok) {
+ exit_server_cleanly("reply_lock_done: smb1_srv_send failed.");
+ }
+ TALLOC_FREE(req);
+ END_PROFILE(SMBlock);
+}
+
+/****************************************************************************
+ Reply to a unlock.
+****************************************************************************/
+
+void reply_unlock(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ NTSTATUS status;
+ files_struct *fsp;
+ struct smbd_lock_element lck;
+
+ START_PROFILE(SMBunlock);
+
+ if (req->wct < 5) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBunlock);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBunlock);
+ return;
+ }
+
+ lck = (struct smbd_lock_element) {
+ .req_guid = smbd_request_guid(req, 0),
+ .smblctx = req->smbpid,
+ .brltype = UNLOCK_LOCK,
+ .lock_flav = WINDOWS_LOCK,
+ .offset = IVAL(req->vwv+3, 0),
+ .count = IVAL(req->vwv+1, 0),
+ };
+
+ status = smbd_do_unlocking(req, fsp, 1, &lck);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBunlock);
+ return;
+ }
+
+ DBG_NOTICE("unlock fd=%d %s offset=%"PRIu64" count=%"PRIu64"\n",
+ fsp_get_io_fd(fsp),
+ fsp_fnum_dbg(fsp),
+ lck.offset,
+ lck.count);
+
+ reply_smb1_outbuf(req, 0, 0);
+
+ END_PROFILE(SMBunlock);
+ return;
+}
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_ALL
+
+/****************************************************************************
+ Reply to a tdis.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+static struct tevent_req *reply_tdis_send(struct smb_request *smb1req);
+static void reply_tdis_done(struct tevent_req *req);
+
+void reply_tdis(struct smb_request *smb1req)
+{
+ connection_struct *conn = smb1req->conn;
+ struct tevent_req *req;
+
+ /*
+ * Don't setup the profile charge here, take
+ * it in reply_tdis_done(). Not strictly correct
+ * but better than the other SMB1 async
+ * code that double-charges at the moment.
+ */
+
+ if (conn == NULL) {
+ /* Not going async, profile here. */
+ START_PROFILE(SMBtdis);
+ DBG_INFO("Invalid connection in tdis\n");
+ reply_force_doserror(smb1req, ERRSRV, ERRinvnid);
+ END_PROFILE(SMBtdis);
+ return;
+ }
+
+ req = reply_tdis_send(smb1req);
+ if (req == NULL) {
+ /* Not going async, profile here. */
+ START_PROFILE(SMBtdis);
+ reply_force_doserror(smb1req, ERRDOS, ERRnomem);
+ END_PROFILE(SMBtdis);
+ return;
+ }
+ /* We're async. This will complete later. */
+ tevent_req_set_callback(req, reply_tdis_done, smb1req);
+ return;
+}
+
+struct reply_tdis_state {
+ struct tevent_queue *wait_queue;
+};
+
+static void reply_tdis_wait_done(struct tevent_req *subreq);
+
+/****************************************************************************
+ Async SMB1 tdis.
+ Note, on failure here we deallocate and return NULL to allow the caller to
+ SMB1 return an error of ERRnomem immediately.
+****************************************************************************/
+
+static struct tevent_req *reply_tdis_send(struct smb_request *smb1req)
+{
+ struct tevent_req *req;
+ struct reply_tdis_state *state;
+ struct tevent_req *subreq;
+ connection_struct *conn = smb1req->conn;
+ files_struct *fsp;
+
+ req = tevent_req_create(smb1req, &state,
+ struct reply_tdis_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->wait_queue = tevent_queue_create(state, "reply_tdis_wait_queue");
+ if (tevent_req_nomem(state->wait_queue, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+
+ /*
+ * Make sure that no new request will be able to use this tcon.
+ * This ensures that once all outstanding fsp->aio_requests
+ * on this tcon are done, we are safe to close it.
+ */
+ conn->tcon->status = NT_STATUS_NETWORK_NAME_DELETED;
+
+ for (fsp = conn->sconn->files; fsp; fsp = fsp->next) {
+ if (fsp->conn != conn) {
+ continue;
+ }
+ /*
+ * Flag the file as close in progress.
+ * This will prevent any more IO being
+ * done on it. Not strictly needed, but
+ * doesn't hurt to flag it as closing.
+ */
+ fsp->fsp_flags.closing = true;
+
+ if (fsp->num_aio_requests > 0) {
+ /*
+ * Now wait until all aio requests on this fsp are
+ * finished.
+ *
+ * We don't set a callback, as we just want to block the
+ * wait queue and the talloc_free() of fsp->aio_request
+ * will remove the item from the wait queue.
+ */
+ subreq = tevent_queue_wait_send(fsp->aio_requests,
+ conn->sconn->ev_ctx,
+ state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+ }
+ }
+
+ /*
+ * Now we add our own waiter to the end of the queue,
+ * this way we get notified when all pending requests are finished
+ * and reply to the outstanding SMB1 request.
+ */
+ subreq = tevent_queue_wait_send(state,
+ conn->sconn->ev_ctx,
+ state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+
+ /*
+ * We're really going async - move the SMB1 request from
+ * a talloc stackframe above us to the sconn talloc-context.
+ * We need this to stick around until the wait_done
+ * callback is invoked.
+ */
+ smb1req = talloc_move(smb1req->sconn, &smb1req);
+
+ tevent_req_set_callback(subreq, reply_tdis_wait_done, req);
+
+ return req;
+}
+
+static void reply_tdis_wait_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+
+ tevent_queue_wait_recv(subreq);
+ TALLOC_FREE(subreq);
+ tevent_req_done(req);
+}
+
+static NTSTATUS reply_tdis_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static void reply_tdis_done(struct tevent_req *req)
+{
+ struct smb_request *smb1req = tevent_req_callback_data(
+ req, struct smb_request);
+ NTSTATUS status;
+ struct smbXsrv_tcon *tcon = smb1req->conn->tcon;
+ bool ok;
+
+ /*
+ * Take the profile charge here. Not strictly
+ * correct but better than the other SMB1 async
+ * code that double-charges at the moment.
+ */
+ START_PROFILE(SMBtdis);
+
+ status = reply_tdis_recv(req);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(smb1req);
+ END_PROFILE(SMBtdis);
+ exit_server(__location__ ": reply_tdis_recv failed");
+ return;
+ }
+
+ /*
+ * As we've been awoken, we may have changed
+ * directory in the meantime.
+ * reply_tdis() has the DO_CHDIR flag set.
+ */
+ ok = chdir_current_service(smb1req->conn);
+ if (!ok) {
+ reply_force_doserror(smb1req, ERRSRV, ERRinvnid);
+ smb_request_done(smb1req);
+ END_PROFILE(SMBtdis);
+ }
+
+ status = smbXsrv_tcon_disconnect(tcon,
+ smb1req->vuid);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(smb1req);
+ END_PROFILE(SMBtdis);
+ exit_server(__location__ ": smbXsrv_tcon_disconnect failed");
+ return;
+ }
+
+ /* smbXsrv_tcon_disconnect frees smb1req->conn. */
+ smb1req->conn = NULL;
+
+ TALLOC_FREE(tcon);
+
+ reply_smb1_outbuf(smb1req, 0, 0);
+ /*
+ * The following call is needed to push the
+ * reply data back out the socket after async
+ * return. Plus it frees smb1req.
+ */
+ smb_request_done(smb1req);
+ END_PROFILE(SMBtdis);
+}
+
+/****************************************************************************
+ Reply to a echo.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_echo(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ int smb_reverb;
+ int seq_num;
+
+ START_PROFILE(SMBecho);
+
+ if (req->wct < 1) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBecho);
+ return;
+ }
+
+ smb_reverb = SVAL(req->vwv+0, 0);
+
+ reply_smb1_outbuf(req, 1, req->buflen);
+
+ /* copy any incoming data back out */
+ if (req->buflen > 0) {
+ memcpy(smb_buf(req->outbuf), req->buf, req->buflen);
+ }
+
+ if (smb_reverb > 100) {
+ DEBUG(0,("large reverb (%d)?? Setting to 100\n",smb_reverb));
+ smb_reverb = 100;
+ }
+
+ for (seq_num = 1 ; seq_num <= smb_reverb ; seq_num++) {
+
+ SSVAL(req->outbuf,smb_vwv0,seq_num);
+
+ show_msg((char *)req->outbuf);
+ if (!smb1_srv_send(req->xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(conn) || req->encrypted))
+ exit_server_cleanly("reply_echo: smb1_srv_send failed.");
+ }
+
+ DEBUG(3,("echo %d times\n", smb_reverb));
+
+ TALLOC_FREE(req->outbuf);
+
+ END_PROFILE(SMBecho);
+ return;
+}
+
+/****************************************************************************
+ Reply to a printopen.
+****************************************************************************/
+
+void reply_printopen(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ files_struct *fsp;
+ NTSTATUS status;
+
+ START_PROFILE(SMBsplopen);
+
+ if (req->wct < 2) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBsplopen);
+ return;
+ }
+
+ if (!CAN_PRINT(conn)) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ END_PROFILE(SMBsplopen);
+ return;
+ }
+
+ status = file_new(req, conn, &fsp);
+ if(!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBsplopen);
+ return;
+ }
+
+ /* Open for exclusive use, write only. */
+ status = print_spool_open(fsp, NULL, req->vuid);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ file_free(req, fsp);
+ reply_nterror(req, status);
+ END_PROFILE(SMBsplopen);
+ return;
+ }
+
+ reply_smb1_outbuf(req, 1, 0);
+ SSVAL(req->outbuf,smb_vwv0,fsp->fnum);
+
+ DEBUG(3,("openprint fd=%d %s\n",
+ fsp_get_io_fd(fsp), fsp_fnum_dbg(fsp)));
+
+ END_PROFILE(SMBsplopen);
+ return;
+}
+
+/****************************************************************************
+ Reply to a printclose.
+****************************************************************************/
+
+void reply_printclose(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ files_struct *fsp;
+ NTSTATUS status;
+
+ START_PROFILE(SMBsplclose);
+
+ if (req->wct < 1) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBsplclose);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBsplclose);
+ return;
+ }
+
+ if (!CAN_PRINT(conn)) {
+ reply_force_doserror(req, ERRSRV, ERRerror);
+ END_PROFILE(SMBsplclose);
+ return;
+ }
+
+ DEBUG(3,("printclose fd=%d %s\n",
+ fsp_get_io_fd(fsp), fsp_fnum_dbg(fsp)));
+
+ status = close_file_free(req, &fsp, NORMAL_CLOSE);
+
+ if(!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBsplclose);
+ return;
+ }
+
+ reply_smb1_outbuf(req, 0, 0);
+
+ END_PROFILE(SMBsplclose);
+ return;
+}
+
+/****************************************************************************
+ Reply to a printqueue.
+****************************************************************************/
+
+void reply_printqueue(struct smb_request *req)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ connection_struct *conn = req->conn;
+ int max_count;
+ int start_index;
+
+ START_PROFILE(SMBsplretq);
+
+ if (req->wct < 2) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBsplretq);
+ return;
+ }
+
+ max_count = SVAL(req->vwv+0, 0);
+ start_index = SVAL(req->vwv+1, 0);
+
+ /* we used to allow the client to get the cnum wrong, but that
+ is really quite gross and only worked when there was only
+ one printer - I think we should now only accept it if they
+ get it right (tridge) */
+ if (!CAN_PRINT(conn)) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ END_PROFILE(SMBsplretq);
+ return;
+ }
+
+ reply_smb1_outbuf(req, 2, 3);
+ SSVAL(req->outbuf,smb_vwv0,0);
+ SSVAL(req->outbuf,smb_vwv1,0);
+ SCVAL(smb_buf(req->outbuf),0,1);
+ SSVAL(smb_buf(req->outbuf),1,0);
+
+ DEBUG(3,("printqueue start_index=%d max_count=%d\n",
+ start_index, max_count));
+
+ {
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ NTSTATUS status;
+ WERROR werr;
+ const char *sharename = lp_servicename(mem_ctx, lp_sub, SNUM(conn));
+ struct rpc_pipe_client *cli = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+ struct policy_handle handle;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ union spoolss_JobInfo *info;
+ uint32_t count;
+ uint32_t num_to_get;
+ uint32_t first;
+ uint32_t i;
+
+ ZERO_STRUCT(handle);
+
+ status = rpc_pipe_open_interface(mem_ctx,
+ &ndr_table_spoolss,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("reply_printqueue: "
+ "could not connect to spoolss: %s\n",
+ nt_errstr(status)));
+ reply_nterror(req, status);
+ goto out;
+ }
+ b = cli->binding_handle;
+
+ ZERO_STRUCT(devmode_ctr);
+
+ status = dcerpc_spoolss_OpenPrinter(b, mem_ctx,
+ sharename,
+ NULL, devmode_ctr,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ reply_nterror(req, werror_to_ntstatus(werr));
+ goto out;
+ }
+
+ werr = rpccli_spoolss_enumjobs(cli, mem_ctx,
+ &handle,
+ 0, /* firstjob */
+ 0xff, /* numjobs */
+ 2, /* level */
+ 0, /* offered */
+ &count,
+ &info);
+ if (!W_ERROR_IS_OK(werr)) {
+ reply_nterror(req, werror_to_ntstatus(werr));
+ goto out;
+ }
+
+ if (max_count > 0) {
+ first = start_index;
+ } else {
+ first = start_index + max_count + 1;
+ }
+
+ if (first >= count) {
+ num_to_get = first;
+ } else {
+ num_to_get = first + MIN(ABS(max_count), count - first);
+ }
+
+ for (i = first; i < num_to_get; i++) {
+ char blob[28];
+ char *p = blob;
+ struct timespec qtime = {
+ .tv_sec = spoolss_Time_to_time_t(
+ &info[i].info2.submitted),
+ };
+ int qstatus;
+ size_t len = 0;
+ uint16_t qrapjobid = pjobid_to_rap(sharename,
+ info[i].info2.job_id);
+
+ if (info[i].info2.status == JOB_STATUS_PRINTING) {
+ qstatus = 2;
+ } else {
+ qstatus = 3;
+ }
+
+ srv_put_dos_date2_ts(p, 0, qtime);
+ SCVAL(p, 4, qstatus);
+ SSVAL(p, 5, qrapjobid);
+ SIVAL(p, 7, info[i].info2.size);
+ SCVAL(p, 11, 0);
+ status = srvstr_push(blob, req->flags2, p+12,
+ info[i].info2.notify_name, 16, STR_ASCII, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ if (message_push_blob(
+ &req->outbuf,
+ data_blob_const(
+ blob, sizeof(blob))) == -1) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ }
+
+ if (count > 0) {
+ SSVAL(req->outbuf,smb_vwv0,count);
+ SSVAL(req->outbuf,smb_vwv1,
+ (max_count>0?first+count:first-1));
+ SCVAL(smb_buf(req->outbuf),0,1);
+ SSVAL(smb_buf(req->outbuf),1,28*count);
+ }
+
+
+ DEBUG(3, ("%u entries returned in queue\n",
+ (unsigned)count));
+
+out:
+ if (b && is_valid_policy_hnd(&handle)) {
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &handle, &werr);
+ }
+
+ }
+
+ END_PROFILE(SMBsplretq);
+ return;
+}
+
+/****************************************************************************
+ Reply to a printwrite.
+****************************************************************************/
+
+void reply_printwrite(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ int numtowrite;
+ const char *data;
+ files_struct *fsp;
+ NTSTATUS status;
+
+ START_PROFILE(SMBsplwr);
+
+ if (req->wct < 1) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBsplwr);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBsplwr);
+ return;
+ }
+
+ if (!fsp->print_file) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ END_PROFILE(SMBsplwr);
+ return;
+ }
+
+ status = check_any_access_fsp(fsp, FILE_WRITE_DATA|FILE_APPEND_DATA);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBsplwr);
+ return;
+ }
+
+ numtowrite = SVAL(req->buf, 1);
+
+ /*
+ * This already protects us against CVE-2017-12163.
+ */
+ if (req->buflen < numtowrite + 3) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBsplwr);
+ return;
+ }
+
+ data = (const char *)req->buf + 3;
+
+ if (write_file(req,fsp,data,(off_t)-1,numtowrite) != numtowrite) {
+ reply_nterror(req, map_nt_error_from_unix(errno));
+ END_PROFILE(SMBsplwr);
+ return;
+ }
+
+ DEBUG(3, ("printwrite %s num=%d\n", fsp_fnum_dbg(fsp), numtowrite));
+
+ reply_smb1_outbuf(req, 0, 0);
+
+ END_PROFILE(SMBsplwr);
+ return;
+}
+
+/****************************************************************************
+ Reply to a mkdir.
+****************************************************************************/
+
+void reply_mkdir(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ struct files_struct *dirfsp = NULL;
+ struct smb_filename *smb_dname = NULL;
+ char *directory = NULL;
+ NTSTATUS status;
+ uint32_t ucf_flags;
+ NTTIME twrp = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBmkdir);
+
+ srvstr_get_path_req(ctx, req, &directory, (const char *)req->buf + 1,
+ STR_TERMINATE, &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ ucf_flags = filename_create_ucf_flags(req, FILE_CREATE);
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(directory, &twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &directory);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ directory,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_dname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = create_directory(conn, req, dirfsp, smb_dname);
+
+ DEBUG(5, ("create_directory returned %s\n", nt_errstr(status)));
+
+ if (!NT_STATUS_IS_OK(status)) {
+
+ if (!use_nt_status()
+ && NT_STATUS_EQUAL(status,
+ NT_STATUS_OBJECT_NAME_COLLISION)) {
+ /*
+ * Yes, in the DOS error code case we get a
+ * ERRDOS:ERRnoaccess here. See BASE-SAMBA3ERROR
+ * samba4 torture test.
+ */
+ status = NT_STATUS_DOS(ERRDOS, ERRnoaccess);
+ }
+
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ reply_smb1_outbuf(req, 0, 0);
+
+ DEBUG(3, ("mkdir %s\n", smb_dname->base_name));
+ out:
+ TALLOC_FREE(smb_dname);
+ END_PROFILE(SMBmkdir);
+ return;
+}
+
+/****************************************************************************
+ Reply to a rmdir.
+****************************************************************************/
+
+void reply_rmdir(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ struct smb_filename *smb_dname = NULL;
+ char *directory = NULL;
+ NTSTATUS status;
+ TALLOC_CTX *ctx = talloc_tos();
+ struct files_struct *dirfsp = NULL;
+ files_struct *fsp = NULL;
+ int info = 0;
+ NTTIME twrp = 0;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+
+ START_PROFILE(SMBrmdir);
+
+ srvstr_get_path_req(ctx, req, &directory, (const char *)req->buf + 1,
+ STR_TERMINATE, &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(directory, &twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &directory);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ directory,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_dname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_dname, /* fname */
+ DELETE_ACCESS, /* access_mask */
+ (FILE_SHARE_READ | FILE_SHARE_WRITE | /* share_access */
+ FILE_SHARE_DELETE),
+ FILE_OPEN, /* create_disposition*/
+ FILE_DIRECTORY_FILE |
+ FILE_OPEN_REPARSE_POINT, /* create_options */
+ FILE_ATTRIBUTE_DIRECTORY, /* file_attributes */
+ 0, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ &info, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->xconn, req->mid)) {
+ /* We have re-scheduled this call. */
+ goto out;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ bool ok = defer_smb1_sharing_violation(req);
+ if (ok) {
+ goto out;
+ }
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = can_set_delete_on_close(fsp, FILE_ATTRIBUTE_DIRECTORY);
+ if (!NT_STATUS_IS_OK(status)) {
+ close_file_free(req, &fsp, ERROR_CLOSE);
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (!set_delete_on_close(fsp, true,
+ conn->session_info->security_token,
+ conn->session_info->unix_token)) {
+ close_file_free(req, &fsp, ERROR_CLOSE);
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ goto out;
+ }
+
+ status = close_file_free(req, &fsp, NORMAL_CLOSE);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ } else {
+ reply_smb1_outbuf(req, 0, 0);
+ }
+
+ DEBUG(3, ("rmdir %s\n", smb_fname_str_dbg(smb_dname)));
+ out:
+ TALLOC_FREE(smb_dname);
+ END_PROFILE(SMBrmdir);
+ return;
+}
+
+/****************************************************************************
+ Reply to a mv.
+****************************************************************************/
+
+void reply_mv(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ char *name = NULL;
+ char *newname = NULL;
+ const char *p;
+ uint32_t attrs;
+ NTSTATUS status;
+ TALLOC_CTX *ctx = talloc_tos();
+ struct files_struct *src_dirfsp = NULL;
+ struct smb_filename *smb_fname_src = NULL;
+ struct files_struct *dst_dirfsp = NULL;
+ struct smb_filename *smb_fname_dst = NULL;
+ const char *dst_original_lcomp = NULL;
+ uint32_t src_ucf_flags = ucf_flags_from_smb_request(req);
+ NTTIME src_twrp = 0;
+ uint32_t dst_ucf_flags = ucf_flags_from_smb_request(req);
+ NTTIME dst_twrp = 0;
+ bool stream_rename = false;
+
+ START_PROFILE(SMBmv);
+
+ if (req->wct < 1) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ attrs = SVAL(req->vwv+0, 0);
+
+ p = (const char *)req->buf + 1;
+ p += srvstr_get_path_req(ctx, req, &name, p, STR_TERMINATE,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ p++;
+ p += srvstr_get_path_req(ctx, req, &newname, p, STR_TERMINATE,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (!req->posix_pathnames) {
+ /* The newname must begin with a ':' if the
+ name contains a ':'. */
+ if (strchr_m(name, ':')) {
+ if (newname[0] != ':') {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+ stream_rename = true;
+ }
+ }
+
+ if (src_ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(name, &src_twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &src_ucf_flags, &name);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ name,
+ src_ucf_flags,
+ src_twrp,
+ &src_dirfsp,
+ &smb_fname_src);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (dst_ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(newname, &dst_twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &dst_ucf_flags, &newname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ newname,
+ dst_ucf_flags,
+ dst_twrp,
+ &dst_dirfsp,
+ &smb_fname_dst);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ /* Get the last component of the destination for rename_internals(). */
+ dst_original_lcomp = get_original_lcomp(ctx,
+ conn,
+ newname,
+ dst_ucf_flags);
+ if (dst_original_lcomp == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+
+ if (stream_rename) {
+ /* smb_fname_dst->base_name must be the same as
+ smb_fname_src->base_name. */
+ TALLOC_FREE(smb_fname_dst->base_name);
+ smb_fname_dst->base_name = talloc_strdup(smb_fname_dst,
+ smb_fname_src->base_name);
+ if (!smb_fname_dst->base_name) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ }
+
+ DEBUG(3,("reply_mv : %s -> %s\n", smb_fname_str_dbg(smb_fname_src),
+ smb_fname_str_dbg(smb_fname_dst)));
+
+ status = rename_internals(ctx,
+ conn,
+ req,
+ src_dirfsp, /* src_dirfsp */
+ smb_fname_src,
+ smb_fname_dst,
+ dst_original_lcomp,
+ attrs,
+ false,
+ DELETE_ACCESS);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->xconn, req->mid)) {
+ /* We have re-scheduled this call. */
+ goto out;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ bool ok = defer_smb1_sharing_violation(req);
+ if (ok) {
+ goto out;
+ }
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ reply_smb1_outbuf(req, 0, 0);
+ out:
+ TALLOC_FREE(smb_fname_src);
+ TALLOC_FREE(smb_fname_dst);
+ END_PROFILE(SMBmv);
+ return;
+}
+
+/****************************************************************************
+ Reply to a file copy.
+
+ From MS-CIFS.
+
+ This command was introduced in the LAN Manager 1.0 dialect
+ It was rendered obsolete in the NT LAN Manager dialect.
+ This command was used to perform server-side file copies, but
+ is no longer used. Clients SHOULD
+ NOT send requests using this command code.
+ Servers receiving requests with this command code
+ SHOULD return STATUS_NOT_IMPLEMENTED (ERRDOS/ERRbadfunc).
+****************************************************************************/
+
+void reply_copy(struct smb_request *req)
+{
+ START_PROFILE(SMBcopy);
+ reply_nterror(req, NT_STATUS_NOT_IMPLEMENTED);
+ END_PROFILE(SMBcopy);
+ return;
+}
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_LOCKING
+
+/****************************************************************************
+ Get a lock pid, dealing with large count requests.
+****************************************************************************/
+
+uint64_t get_lock_pid(const uint8_t *data, int data_offset,
+ bool large_file_format)
+{
+ if(!large_file_format)
+ return (uint64_t)SVAL(data,SMB_LPID_OFFSET(data_offset));
+ else
+ return (uint64_t)SVAL(data,SMB_LARGE_LPID_OFFSET(data_offset));
+}
+
+/****************************************************************************
+ Get a lock count, dealing with large count requests.
+****************************************************************************/
+
+uint64_t get_lock_count(const uint8_t *data, int data_offset,
+ bool large_file_format)
+{
+ uint64_t count = 0;
+
+ if(!large_file_format) {
+ count = (uint64_t)IVAL(data,SMB_LKLEN_OFFSET(data_offset));
+ } else {
+ /*
+ * No BVAL, this is reversed!
+ */
+ count = (((uint64_t) IVAL(data,SMB_LARGE_LKLEN_OFFSET_HIGH(data_offset))) << 32) |
+ ((uint64_t) IVAL(data,SMB_LARGE_LKLEN_OFFSET_LOW(data_offset)));
+ }
+
+ return count;
+}
+
+/****************************************************************************
+ Reply to a lockingX request.
+****************************************************************************/
+
+static void reply_lockingx_done(struct tevent_req *subreq);
+
+void reply_lockingX(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ files_struct *fsp;
+ unsigned char locktype;
+ enum brl_type brltype;
+ unsigned char oplocklevel;
+ uint16_t num_ulocks;
+ uint16_t num_locks;
+ int32_t lock_timeout;
+ uint16_t i;
+ const uint8_t *data;
+ bool large_file_format;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ struct smbd_lock_element *locks = NULL;
+ struct tevent_req *subreq = NULL;
+
+ START_PROFILE(SMBlockingX);
+
+ if (req->wct < 8) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+2, 0));
+ locktype = CVAL(req->vwv+3, 0);
+ oplocklevel = CVAL(req->vwv+3, 1);
+ num_ulocks = SVAL(req->vwv+6, 0);
+ num_locks = SVAL(req->vwv+7, 0);
+ lock_timeout = IVAL(req->vwv+4, 0);
+ large_file_format = ((locktype & LOCKING_ANDX_LARGE_FILES) != 0);
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+
+ data = req->buf;
+
+ if (locktype & LOCKING_ANDX_CHANGE_LOCKTYPE) {
+ /* we don't support these - and CANCEL_LOCK makes w2k
+ and XP reboot so I don't really want to be
+ compatible! (tridge) */
+ reply_force_doserror(req, ERRDOS, ERRnoatomiclocks);
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+
+ /* Check if this is an oplock break on a file
+ we have granted an oplock on.
+ */
+ if (locktype & LOCKING_ANDX_OPLOCK_RELEASE) {
+ /* Client can insist on breaking to none. */
+ bool break_to_none = (oplocklevel == 0);
+ bool result;
+
+ DEBUG(5,("reply_lockingX: oplock break reply (%u) from client "
+ "for %s\n", (unsigned int)oplocklevel,
+ fsp_fnum_dbg(fsp)));
+
+ /*
+ * Make sure we have granted an exclusive or batch oplock on
+ * this file.
+ */
+
+ if (fsp->oplock_type == 0) {
+
+ /* The Samba4 nbench simulator doesn't understand
+ the difference between break to level2 and break
+ to none from level2 - it sends oplock break
+ replies in both cases. Don't keep logging an error
+ message here - just ignore it. JRA. */
+
+ DEBUG(5,("reply_lockingX: Error : oplock break from "
+ "client for %s (oplock=%d) and no "
+ "oplock granted on this file (%s).\n",
+ fsp_fnum_dbg(fsp), fsp->oplock_type,
+ fsp_str_dbg(fsp)));
+
+ /* if this is a pure oplock break request then don't
+ * send a reply */
+ if (num_locks == 0 && num_ulocks == 0) {
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+
+ END_PROFILE(SMBlockingX);
+ reply_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
+ return;
+ }
+
+ if ((fsp->sent_oplock_break == BREAK_TO_NONE_SENT) ||
+ (break_to_none)) {
+ result = remove_oplock(fsp);
+ } else {
+ result = downgrade_oplock(fsp);
+ }
+
+ if (!result) {
+ DEBUG(0, ("reply_lockingX: error in removing "
+ "oplock on file %s\n", fsp_str_dbg(fsp)));
+ /* Hmmm. Is this panic justified? */
+ smb_panic("internal tdb error");
+ }
+
+ /* if this is a pure oplock break request then don't send a
+ * reply */
+ if (num_locks == 0 && num_ulocks == 0) {
+ /* Sanity check - ensure a pure oplock break is not a
+ chained request. */
+ if (CVAL(req->vwv+0, 0) != 0xff) {
+ DEBUG(0,("reply_lockingX: Error : pure oplock "
+ "break is a chained %d request !\n",
+ (unsigned int)CVAL(req->vwv+0, 0)));
+ }
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+ }
+
+ if (req->buflen <
+ (num_ulocks + num_locks) * (large_file_format ? 20 : 10)) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+
+ if (num_ulocks != 0) {
+ struct smbd_lock_element *ulocks = NULL;
+ bool ok;
+
+ ulocks = talloc_array(
+ req, struct smbd_lock_element, num_ulocks);
+ if (ulocks == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+
+ /*
+ * Data now points at the beginning of the list of
+ * smb_unlkrng structs
+ */
+ for (i = 0; i < num_ulocks; i++) {
+ ulocks[i].req_guid = smbd_request_guid(req,
+ UINT16_MAX - i),
+ ulocks[i].smblctx = get_lock_pid(
+ data, i, large_file_format);
+ ulocks[i].count = get_lock_count(
+ data, i, large_file_format);
+ ulocks[i].offset = get_lock_offset(
+ data, i, large_file_format);
+ ulocks[i].brltype = UNLOCK_LOCK;
+ ulocks[i].lock_flav = WINDOWS_LOCK;
+ }
+
+ /*
+ * Unlock cancels pending locks
+ */
+
+ ok = smbd_smb1_brl_finish_by_lock(
+ fsp,
+ large_file_format,
+ ulocks[0],
+ NT_STATUS_OK);
+ if (ok) {
+ reply_smb1_outbuf(req, 2, 0);
+ SSVAL(req->outbuf, smb_vwv0, 0xff);
+ SSVAL(req->outbuf, smb_vwv1, 0);
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+
+ status = smbd_do_unlocking(
+ req, fsp, num_ulocks, ulocks);
+ TALLOC_FREE(ulocks);
+ if (!NT_STATUS_IS_OK(status)) {
+ END_PROFILE(SMBlockingX);
+ reply_nterror(req, status);
+ return;
+ }
+ }
+
+ /* Now do any requested locks */
+ data += ((large_file_format ? 20 : 10)*num_ulocks);
+
+ /* Data now points at the beginning of the list
+ of smb_lkrng structs */
+
+ if (locktype & LOCKING_ANDX_SHARED_LOCK) {
+ brltype = READ_LOCK;
+ } else {
+ brltype = WRITE_LOCK;
+ }
+
+ locks = talloc_array(req, struct smbd_lock_element, num_locks);
+ if (locks == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+
+ for (i = 0; i < num_locks; i++) {
+ locks[i].req_guid = smbd_request_guid(req, i),
+ locks[i].smblctx = get_lock_pid(data, i, large_file_format);
+ locks[i].count = get_lock_count(data, i, large_file_format);
+ locks[i].offset = get_lock_offset(data, i, large_file_format);
+ locks[i].brltype = brltype;
+ locks[i].lock_flav = WINDOWS_LOCK;
+ }
+
+ if (locktype & LOCKING_ANDX_CANCEL_LOCK) {
+
+ bool ok;
+
+ if (num_locks == 0) {
+ /* See smbtorture3 lock11 test */
+ reply_smb1_outbuf(req, 2, 0);
+ /* andx chain ends */
+ SSVAL(req->outbuf, smb_vwv0, 0xff);
+ SSVAL(req->outbuf, smb_vwv1, 0);
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+
+ ok = smbd_smb1_brl_finish_by_lock(
+ fsp,
+ large_file_format,
+ locks[0], /* Windows only cancels the first lock */
+ NT_STATUS_FILE_LOCK_CONFLICT);
+
+ if (!ok) {
+ reply_force_doserror(req, ERRDOS, ERRcancelviolation);
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+
+ reply_smb1_outbuf(req, 2, 0);
+ SSVAL(req->outbuf, smb_vwv0, 0xff);
+ SSVAL(req->outbuf, smb_vwv1, 0);
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+
+ subreq = smbd_smb1_do_locks_send(
+ fsp,
+ req->sconn->ev_ctx,
+ &req,
+ fsp,
+ lock_timeout,
+ large_file_format,
+ num_locks,
+ locks);
+ if (subreq == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+ tevent_req_set_callback(subreq, reply_lockingx_done, NULL);
+ END_PROFILE(SMBlockingX);
+}
+
+static void reply_lockingx_done(struct tevent_req *subreq)
+{
+ struct smb_request *req = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ START_PROFILE(SMBlockingX);
+
+ ok = smbd_smb1_do_locks_extract_smbreq(subreq, talloc_tos(), &req);
+ SMB_ASSERT(ok);
+
+ status = smbd_smb1_do_locks_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ DBG_DEBUG("smbd_smb1_do_locks_recv returned %s\n", nt_errstr(status));
+
+ if (NT_STATUS_IS_OK(status)) {
+ reply_smb1_outbuf(req, 2, 0);
+ SSVAL(req->outbuf, smb_vwv0, 0xff); /* andx chain ends */
+ SSVAL(req->outbuf, smb_vwv1, 0); /* no andx offset */
+ } else {
+ reply_nterror(req, status);
+ }
+
+ ok = smb1_srv_send(req->xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(req->conn));
+ if (!ok) {
+ exit_server_cleanly("reply_lock_done: smb1_srv_send failed.");
+ }
+ TALLOC_FREE(req);
+ END_PROFILE(SMBlockingX);
+}
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_ALL
+
+/****************************************************************************
+ Reply to a SMBreadbmpx (read block multiplex) request.
+ Always reply with an error, if someone has a platform really needs this,
+ please contact vl@samba.org
+****************************************************************************/
+
+void reply_readbmpx(struct smb_request *req)
+{
+ START_PROFILE(SMBreadBmpx);
+ reply_force_doserror(req, ERRSRV, ERRuseSTD);
+ END_PROFILE(SMBreadBmpx);
+ return;
+}
+
+/****************************************************************************
+ Reply to a SMBreadbs (read block multiplex secondary) request.
+ Always reply with an error, if someone has a platform really needs this,
+ please contact vl@samba.org
+****************************************************************************/
+
+void reply_readbs(struct smb_request *req)
+{
+ START_PROFILE(SMBreadBs);
+ reply_force_doserror(req, ERRSRV, ERRuseSTD);
+ END_PROFILE(SMBreadBs);
+ return;
+}
+
+/****************************************************************************
+ Reply to a SMBsetattrE.
+****************************************************************************/
+
+void reply_setattrE(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ struct smb_file_time ft;
+ files_struct *fsp;
+ NTSTATUS status;
+
+ START_PROFILE(SMBsetattrE);
+ init_smb_file_time(&ft);
+
+ if (req->wct < 7) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+
+ if(!fsp || (fsp->conn != conn)) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ goto out;
+ }
+
+ /*
+ * Convert the DOS times into unix times.
+ */
+
+ ft.atime = time_t_to_full_timespec(
+ srv_make_unix_date2(req->vwv+3));
+ ft.mtime = time_t_to_full_timespec(
+ srv_make_unix_date2(req->vwv+5));
+ ft.create_time = time_t_to_full_timespec(
+ srv_make_unix_date2(req->vwv+1));
+
+ reply_smb1_outbuf(req, 0, 0);
+
+ /*
+ * Patch from Ray Frush <frush@engr.colostate.edu>
+ * Sometimes times are sent as zero - ignore them.
+ */
+
+ /* Ensure we have a valid stat struct for the source. */
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = check_any_access_fsp(fsp, FILE_WRITE_ATTRIBUTES);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = smb_set_file_time(conn, fsp, fsp->fsp_name, &ft, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (fsp->fsp_flags.modified) {
+ trigger_write_time_update_immediate(fsp);
+ }
+
+ DEBUG( 3, ( "reply_setattrE %s actime=%u modtime=%u "
+ " createtime=%u\n",
+ fsp_fnum_dbg(fsp),
+ (unsigned int)ft.atime.tv_sec,
+ (unsigned int)ft.mtime.tv_sec,
+ (unsigned int)ft.create_time.tv_sec
+ ));
+ out:
+ END_PROFILE(SMBsetattrE);
+ return;
+}
+
+
+/* Back from the dead for OS/2..... JRA. */
+
+/****************************************************************************
+ Reply to a SMBwritebmpx (write block multiplex primary) request.
+ Always reply with an error, if someone has a platform really needs this,
+ please contact vl@samba.org
+****************************************************************************/
+
+void reply_writebmpx(struct smb_request *req)
+{
+ START_PROFILE(SMBwriteBmpx);
+ reply_force_doserror(req, ERRSRV, ERRuseSTD);
+ END_PROFILE(SMBwriteBmpx);
+ return;
+}
+
+/****************************************************************************
+ Reply to a SMBwritebs (write block multiplex secondary) request.
+ Always reply with an error, if someone has a platform really needs this,
+ please contact vl@samba.org
+****************************************************************************/
+
+void reply_writebs(struct smb_request *req)
+{
+ START_PROFILE(SMBwriteBs);
+ reply_force_doserror(req, ERRSRV, ERRuseSTD);
+ END_PROFILE(SMBwriteBs);
+ return;
+}
+
+/****************************************************************************
+ Reply to a SMBgetattrE.
+****************************************************************************/
+
+void reply_getattrE(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ int mode;
+ files_struct *fsp;
+ struct timespec create_ts;
+ NTSTATUS status;
+
+ START_PROFILE(SMBgetattrE);
+
+ if (req->wct < 1) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBgetattrE);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+
+ if(!fsp || (fsp->conn != conn)) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ END_PROFILE(SMBgetattrE);
+ return;
+ }
+
+ /* Do an fstat on this file */
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBgetattrE);
+ return;
+ }
+
+ mode = fdos_mode(fsp);
+
+ /*
+ * Convert the times into dos times. Set create
+ * date to be last modify date as UNIX doesn't save
+ * this.
+ */
+
+ reply_smb1_outbuf(req, 11, 0);
+
+ create_ts = get_create_timespec(conn, fsp, fsp->fsp_name);
+ srv_put_dos_date2_ts((char *)req->outbuf, smb_vwv0, create_ts);
+ srv_put_dos_date2_ts((char *)req->outbuf,
+ smb_vwv2,
+ fsp->fsp_name->st.st_ex_atime);
+ /* Should we check pending modtime here ? JRA */
+ srv_put_dos_date2_ts((char *)req->outbuf,
+ smb_vwv4,
+ fsp->fsp_name->st.st_ex_mtime);
+
+ if (mode & FILE_ATTRIBUTE_DIRECTORY) {
+ SIVAL(req->outbuf, smb_vwv6, 0);
+ SIVAL(req->outbuf, smb_vwv8, 0);
+ } else {
+ uint32_t allocation_size = SMB_VFS_GET_ALLOC_SIZE(conn,fsp, &fsp->fsp_name->st);
+ SIVAL(req->outbuf, smb_vwv6, (uint32_t)fsp->fsp_name->st.st_ex_size);
+ SIVAL(req->outbuf, smb_vwv8, allocation_size);
+ }
+ SSVAL(req->outbuf,smb_vwv10, mode);
+
+ DEBUG( 3, ( "reply_getattrE %s\n", fsp_fnum_dbg(fsp)));
+
+ END_PROFILE(SMBgetattrE);
+ return;
+}
+
+/****************************************************************************
+ Reply to a SMBfindclose (stop trans2 directory search).
+****************************************************************************/
+
+void reply_findclose(struct smb_request *req)
+{
+ int dptr_num;
+ struct smbd_server_connection *sconn = req->sconn;
+ files_struct *fsp = NULL;
+
+ START_PROFILE(SMBfindclose);
+
+ if (req->wct < 1) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBfindclose);
+ return;
+ }
+
+ dptr_num = SVALS(req->vwv+0, 0);
+
+ DEBUG(3,("reply_findclose, dptr_num = %d\n", dptr_num));
+
+ /*
+ * OS/2 seems to use -1 to indicate "close all directories"
+ * This has to mean on this specific connection struct.
+ */
+ if (dptr_num == -1) {
+ dptr_closecnum(req->conn);
+ } else {
+ fsp = dptr_fetch_lanman2_fsp(sconn, dptr_num);
+ dptr_num = -1;
+ if (fsp != NULL) {
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ }
+ }
+
+ reply_smb1_outbuf(req, 0, 0);
+
+ DEBUG(3,("SMBfindclose dptr_num = %d\n", dptr_num));
+
+ END_PROFILE(SMBfindclose);
+ return;
+}
+
+/****************************************************************************
+ Reply to a SMBfindnclose (stop FINDNOTIFYFIRST directory search).
+****************************************************************************/
+
+void reply_findnclose(struct smb_request *req)
+{
+ int dptr_num;
+
+ START_PROFILE(SMBfindnclose);
+
+ if (req->wct < 1) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBfindnclose);
+ return;
+ }
+
+ dptr_num = SVAL(req->vwv+0, 0);
+
+ DEBUG(3,("reply_findnclose, dptr_num = %d\n", dptr_num));
+
+ /* We never give out valid handles for a
+ findnotifyfirst - so any dptr_num is ok here.
+ Just ignore it. */
+
+ reply_smb1_outbuf(req, 0, 0);
+
+ DEBUG(3,("SMB_findnclose dptr_num = %d\n", dptr_num));
+
+ END_PROFILE(SMBfindnclose);
+ return;
+}
diff --git a/source3/smbd/smb1_reply.h b/source3/smbd/smb1_reply.h
new file mode 100644
index 0000000..5a12f0b
--- /dev/null
+++ b/source3/smbd/smb1_reply.h
@@ -0,0 +1,80 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main SMB reply routines
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Andrew Bartlett 2001
+ Copyright (C) Jeremy Allison 1992-2007.
+ Copyright (C) Volker Lendecke 2007
+
+ 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/>.
+*/
+
+void reply_tcon(struct smb_request *req);
+void reply_tcon_and_X(struct smb_request *req);
+void reply_unknown_new(struct smb_request *req, uint8_t type);
+void reply_ioctl(struct smb_request *req);
+void reply_checkpath(struct smb_request *req);
+void reply_getatr(struct smb_request *req);
+void reply_setatr(struct smb_request *req);
+void reply_dskattr(struct smb_request *req);
+void reply_search(struct smb_request *req);
+void reply_fclose(struct smb_request *req);
+void reply_open(struct smb_request *req);
+void reply_open_and_X(struct smb_request *req);
+void reply_ulogoffX(struct smb_request *req);
+void reply_mknew(struct smb_request *req);
+void reply_ctemp(struct smb_request *req);
+void reply_unlink(struct smb_request *req);
+void reply_readbraw(struct smb_request *req);
+void reply_lockread(struct smb_request *req);
+size_t setup_readX_header(char *outbuf, size_t smb_maxcnt);
+void reply_read(struct smb_request *req);
+void reply_read_and_X(struct smb_request *req);
+void error_to_writebrawerr(struct smb_request *req);
+void reply_writebraw(struct smb_request *req);
+void reply_writeunlock(struct smb_request *req);
+void reply_write(struct smb_request *req);
+bool is_valid_writeX_buffer(struct smbXsrv_connection *xconn,
+ const uint8_t *inbuf);
+void reply_write_and_X(struct smb_request *req);
+void reply_lseek(struct smb_request *req);
+void reply_flush(struct smb_request *req);
+void reply_exit(struct smb_request *req);
+void reply_close(struct smb_request *req);
+void reply_writeclose(struct smb_request *req);
+void reply_lock(struct smb_request *req);
+void reply_unlock(struct smb_request *req);
+void reply_tdis(struct smb_request *req);
+void reply_echo(struct smb_request *req);
+void reply_printopen(struct smb_request *req);
+void reply_printclose(struct smb_request *req);
+void reply_printqueue(struct smb_request *req);
+void reply_printwrite(struct smb_request *req);
+void reply_mkdir(struct smb_request *req);
+void reply_rmdir(struct smb_request *req);
+void reply_mv(struct smb_request *req);
+void reply_copy(struct smb_request *req);
+uint64_t get_lock_pid(const uint8_t *data, int data_offset,
+ bool large_file_format);
+uint64_t get_lock_count(const uint8_t *data, int data_offset,
+ bool large_file_format);
+void reply_lockingX(struct smb_request *req);
+void reply_readbmpx(struct smb_request *req);
+void reply_readbs(struct smb_request *req);
+void reply_setattrE(struct smb_request *req);
+void reply_writebmpx(struct smb_request *req);
+void reply_writebs(struct smb_request *req);
+void reply_getattrE(struct smb_request *req);
+void reply_findclose(struct smb_request *req);
+void reply_findnclose(struct smb_request *req);
diff --git a/source3/smbd/smb1_service.c b/source3/smbd/smb1_service.c
new file mode 100644
index 0000000..9954101
--- /dev/null
+++ b/source3/smbd/smb1_service.c
@@ -0,0 +1,241 @@
+/*
+ Unix SMB/CIFS implementation.
+ service (connection) opening and closing
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ 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 "system/filesys.h"
+#include "system/passwd.h" /* uid_wrapper */
+#include "../lib/tsocket/tsocket.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../librpc/gen_ndr/netlogon.h"
+#include "../libcli/security/security.h"
+#include "printing/pcap.h"
+#include "passdb/lookup_sid.h"
+#include "auth.h"
+#include "../auth/auth_util.h"
+#include "lib/param/loadparm.h"
+#include "messages.h"
+#include "lib/afs/afs_funcs.h"
+#include "lib/util_path.h"
+#include "lib/util/string_wrappers.h"
+#include "source3/lib/substitute.h"
+
+/****************************************************************************
+ Make a connection to a service from SMB1. Internal interface.
+****************************************************************************/
+
+static connection_struct *make_connection_smb1(struct smb_request *req,
+ NTTIME now,
+ int snum,
+ const char *pdev,
+ NTSTATUS *pstatus)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ uint32_t session_global_id;
+ char *share_name = NULL;
+ struct smbXsrv_tcon *tcon;
+ NTSTATUS status;
+ struct connection_struct *conn;
+
+ session_global_id = req->session->global->session_global_id;
+ share_name = lp_servicename(talloc_tos(), lp_sub, snum);
+ if (share_name == NULL) {
+ *pstatus = NT_STATUS_NO_MEMORY;
+ return NULL;
+ }
+
+ if ((lp_max_connections(snum) > 0)
+ && (count_current_connections(lp_const_servicename(snum), true) >=
+ lp_max_connections(snum))) {
+
+ DBG_WARNING("Max connections (%d) exceeded for [%s][%s]\n",
+ lp_max_connections(snum),
+ lp_const_servicename(snum), share_name);
+ TALLOC_FREE(share_name);
+ *pstatus = NT_STATUS_INSUFFICIENT_RESOURCES;
+ return NULL;
+ }
+
+ status = smb1srv_tcon_create(req->xconn,
+ session_global_id,
+ share_name,
+ now, &tcon);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("make_connection_smb1: Couldn't find free tcon for [%s] - %s\n",
+ share_name, nt_errstr(status)));
+ TALLOC_FREE(share_name);
+ *pstatus = status;
+ return NULL;
+ }
+ TALLOC_FREE(share_name);
+
+ conn = conn_new(req->sconn);
+ if (!conn) {
+ TALLOC_FREE(tcon);
+
+ DEBUG(0,("make_connection_smb1: Couldn't find free connection.\n"));
+ *pstatus = NT_STATUS_INSUFFICIENT_RESOURCES;
+ return NULL;
+ }
+
+ conn->cnum = tcon->global->tcon_wire_id;
+ conn->tcon = tcon;
+
+ *pstatus = make_connection_snum(req->xconn,
+ conn,
+ snum,
+ req->session,
+ pdev);
+ if (!NT_STATUS_IS_OK(*pstatus)) {
+ conn_free(conn);
+ TALLOC_FREE(tcon);
+ return NULL;
+ }
+
+ tcon->compat = talloc_move(tcon, &conn);
+ tcon->status = NT_STATUS_OK;
+
+ *pstatus = NT_STATUS_OK;
+
+ return tcon->compat;
+}
+
+/****************************************************************************
+ Make a connection to a service. External SMB1 interface.
+ *
+ * @param service
+****************************************************************************/
+
+connection_struct *make_connection(struct smb_request *req,
+ NTTIME now,
+ const char *service_in,
+ const char *pdev, uint64_t vuid,
+ NTSTATUS *status)
+{
+ struct smbd_server_connection *sconn = req->sconn;
+ struct smbXsrv_session *session = req->session;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ uid_t euid;
+ char *service = NULL;
+ fstring dev;
+ int snum = -1;
+
+ fstrcpy(dev, pdev);
+
+ /* This must ONLY BE CALLED AS ROOT. As it exits this function as
+ * root. */
+ if (!non_root_mode() && (euid = geteuid()) != 0) {
+ DEBUG(0,("make_connection: PANIC ERROR. Called as nonroot "
+ "(%u)\n", (unsigned int)euid ));
+ smb_panic("make_connection: PANIC ERROR. Called as nonroot\n");
+ }
+
+ if (conn_num_open(sconn) > 2047) {
+ *status = NT_STATUS_INSUFF_SERVER_RESOURCES;
+ return NULL;
+ }
+
+ if (session == NULL) {
+ DEBUG(1,("make_connection: refusing to connect with "
+ "no session setup\n"));
+ *status = NT_STATUS_ACCESS_DENIED;
+ return NULL;
+ }
+
+ /* Logic to try and connect to the correct [homes] share, preferably
+ without too many getpwnam() lookups. This is particularly nasty for
+ winbind usernames, where the share name isn't the same as unix
+ username.
+ */
+
+ if (strequal(service_in,HOMES_NAME)) {
+ if (session->homes_snum == -1) {
+ DEBUG(2, ("[homes] share not available for "
+ "this user because it was not found "
+ "or created at session setup "
+ "time\n"));
+ *status = NT_STATUS_BAD_NETWORK_NAME;
+ return NULL;
+ }
+ DEBUG(5, ("making a connection to [homes] service "
+ "created at session setup time\n"));
+ return make_connection_smb1(req, now,
+ session->homes_snum,
+ dev, status);
+ } else if ((session->homes_snum != -1)
+ && strequal(service_in,
+ lp_const_servicename(session->homes_snum))) {
+ DEBUG(5, ("making a connection to 'homes' service [%s] "
+ "created at session setup time\n", service_in));
+ return make_connection_smb1(req, now,
+ session->homes_snum,
+ dev, status);
+ }
+
+ service = talloc_strdup(talloc_tos(), service_in);
+ if (!service) {
+ *status = NT_STATUS_NO_MEMORY;
+ return NULL;
+ }
+
+ if (!strlower_m(service)) {
+ DEBUG(2, ("strlower_m %s failed\n", service));
+ *status = NT_STATUS_INVALID_PARAMETER;
+ return NULL;
+ }
+
+ snum = find_service(talloc_tos(), service, &service);
+ if (!service) {
+ *status = NT_STATUS_NO_MEMORY;
+ return NULL;
+ }
+
+ if (snum < 0) {
+ if (strequal(service,"IPC$") ||
+ (lp_enable_asu_support() && strequal(service,"ADMIN$"))) {
+ DEBUG(3,("refusing IPC connection to %s\n", service));
+ *status = NT_STATUS_ACCESS_DENIED;
+ return NULL;
+ }
+
+ DEBUG(3,("%s (%s) couldn't find service %s\n",
+ get_remote_machine_name(),
+ tsocket_address_string(
+ sconn->remote_address, talloc_tos()),
+ service));
+ *status = NT_STATUS_BAD_NETWORK_NAME;
+ return NULL;
+ }
+
+ /* Handle non-Dfs clients attempting connections to msdfs proxy */
+ if (lp_host_msdfs() && (*lp_msdfs_proxy(talloc_tos(), lp_sub, snum) != '\0')) {
+ DEBUG(3, ("refusing connection to dfs proxy share '%s' "
+ "(pointing to %s)\n",
+ service, lp_msdfs_proxy(talloc_tos(), lp_sub, snum)));
+ *status = NT_STATUS_BAD_NETWORK_NAME;
+ return NULL;
+ }
+
+ DEBUG(5, ("making a connection to 'normal' service %s\n", service));
+
+ return make_connection_smb1(req, now, snum,
+ dev, status);
+}
diff --git a/source3/smbd/smb1_service.h b/source3/smbd/smb1_service.h
new file mode 100644
index 0000000..cf6b967
--- /dev/null
+++ b/source3/smbd/smb1_service.h
@@ -0,0 +1,24 @@
+/*
+ Unix SMB/CIFS implementation.
+ service (connection) opening and closing
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ 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/>.
+*/
+
+connection_struct *make_connection(struct smb_request *req,
+ NTTIME now,
+ const char *service_in,
+ const char *pdev, uint64_t vuid,
+ NTSTATUS *status);
diff --git a/source3/smbd/smb1_sesssetup.c b/source3/smbd/smb1_sesssetup.c
new file mode 100644
index 0000000..6c668ff
--- /dev/null
+++ b/source3/smbd/smb1_sesssetup.c
@@ -0,0 +1,1118 @@
+/*
+ Unix SMB/CIFS implementation.
+ handle SMBsessionsetup
+ Copyright (C) Andrew Tridgell 1998-2001
+ Copyright (C) Andrew Bartlett 2001
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002
+ Copyright (C) Luke Howard 2003
+ Copyright (C) Volker Lendecke 2007
+ Copyright (C) Jeremy Allison 2007
+
+ 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 "../lib/tsocket/tsocket.h"
+#include "lib/util/server_id.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "auth.h"
+#include "messages.h"
+#include "smbprofile.h"
+#include "../libcli/security/security.h"
+#include "auth/gensec/gensec.h"
+#include "../libcli/smb/smb_signing.h"
+#include "lib/util/string_wrappers.h"
+#include "source3/lib/substitute.h"
+
+/****************************************************************************
+ Add the standard 'Samba' signature to the end of the session setup.
+****************************************************************************/
+
+static int push_signature(uint8_t **outbuf)
+{
+ char *lanman;
+ int result, tmp;
+ fstring native_os;
+
+ result = 0;
+
+ fstr_sprintf(native_os, "Windows %d.%d", SAMBA_MAJOR_NBT_ANNOUNCE_VERSION,
+ SAMBA_MINOR_NBT_ANNOUNCE_VERSION);
+
+ tmp = message_push_string(outbuf, native_os, STR_TERMINATE);
+
+ if (tmp == -1) return -1;
+ result += tmp;
+
+ if (asprintf(&lanman, "Samba %s", samba_version_string()) != -1) {
+ tmp = message_push_string(outbuf, lanman, STR_TERMINATE);
+ SAFE_FREE(lanman);
+ }
+ else {
+ tmp = message_push_string(outbuf, "Samba", STR_TERMINATE);
+ }
+
+ if (tmp == -1) return -1;
+ result += tmp;
+
+ tmp = message_push_string(outbuf, lp_workgroup(), STR_TERMINATE);
+
+ if (tmp == -1) return -1;
+ result += tmp;
+
+ return result;
+}
+
+/****************************************************************************
+ Reply to a session setup command.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+static void reply_sesssetup_and_X_spnego(struct smb_request *req)
+{
+ const uint8_t *p;
+ DATA_BLOB in_blob;
+ DATA_BLOB out_blob = data_blob_null;
+ size_t bufrem;
+ char *tmp = NULL;
+ const char *native_os;
+ const char *native_lanman;
+ const char *primary_domain;
+ uint16_t data_blob_len = SVAL(req->vwv+7, 0);
+ enum remote_arch_types ra_type = get_remote_arch();
+ uint64_t vuid = req->vuid;
+ NTSTATUS status = NT_STATUS_OK;
+ struct smbXsrv_connection *xconn = req->xconn;
+ struct smbd_server_connection *sconn = req->sconn;
+ uint16_t action = 0;
+ bool is_authenticated = false;
+ NTTIME now = timeval_to_nttime(&req->request_time);
+ struct smbXsrv_session *session = NULL;
+ uint16_t smb_bufsize = SVAL(req->vwv+2, 0);
+ uint32_t client_caps = IVAL(req->vwv+10, 0);
+ struct smbXsrv_session_auth0 *auth;
+
+ DEBUG(3,("Doing spnego session setup\n"));
+
+ if (!xconn->smb1.sessions.done_sesssetup) {
+ global_client_caps = client_caps;
+
+ if (!(global_client_caps & CAP_STATUS32)) {
+ remove_from_common_flags2(FLAGS2_32_BIT_ERROR_CODES);
+ }
+ }
+
+ p = req->buf;
+
+ if (data_blob_len == 0) {
+ /* an invalid request */
+ reply_nterror(req, nt_status_squash(NT_STATUS_LOGON_FAILURE));
+ return;
+ }
+
+ bufrem = smbreq_bufrem(req, p);
+ /* pull the spnego blob */
+ in_blob = data_blob_const(p, MIN(bufrem, data_blob_len));
+
+#if 0
+ file_save("negotiate.dat", in_blob.data, in_blob.length);
+#endif
+
+ p = req->buf + in_blob.length;
+
+ p += srvstr_pull_req_talloc(talloc_tos(), req, &tmp, p,
+ STR_TERMINATE);
+ native_os = tmp ? tmp : "";
+
+ p += srvstr_pull_req_talloc(talloc_tos(), req, &tmp, p,
+ STR_TERMINATE);
+ native_lanman = tmp ? tmp : "";
+
+ p += srvstr_pull_req_talloc(talloc_tos(), req, &tmp, p,
+ STR_TERMINATE);
+ primary_domain = tmp ? tmp : "";
+
+ DEBUG(3,("NativeOS=[%s] NativeLanMan=[%s] PrimaryDomain=[%s]\n",
+ native_os, native_lanman, primary_domain));
+
+ if ( ra_type == RA_WIN2K ) {
+ /* Vista sets neither the OS or lanman strings */
+
+ if ( !strlen(native_os) && !strlen(native_lanman) )
+ set_remote_arch(RA_VISTA);
+
+ /* Windows 2003 doesn't set the native lanman string,
+ but does set primary domain which is a bug I think */
+
+ if ( !strlen(native_lanman) ) {
+ ra_lanman_string( primary_domain );
+ } else {
+ ra_lanman_string( native_lanman );
+ }
+ } else if ( ra_type == RA_VISTA ) {
+ if ( strncmp(native_os, "Mac OS X", 8) == 0 ) {
+ set_remote_arch(RA_OSX);
+ }
+ }
+
+ if (vuid != 0) {
+ status = smb1srv_session_lookup(xconn,
+ vuid, now,
+ &session);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_USER_SESSION_DELETED)) {
+ reply_force_doserror(req, ERRSRV, ERRbaduid);
+ return;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_SESSION_EXPIRED)) {
+ status = NT_STATUS_OK;
+ }
+ if (NT_STATUS_IS_OK(status)) {
+ session->status = NT_STATUS_MORE_PROCESSING_REQUIRED;
+ status = NT_STATUS_MORE_PROCESSING_REQUIRED;
+ TALLOC_FREE(session->pending_auth);
+ }
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ reply_nterror(req, nt_status_squash(status));
+ return;
+ }
+ }
+
+ if (session == NULL) {
+ /* create a new session */
+ status = smbXsrv_session_create(xconn,
+ now, &session);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, nt_status_squash(status));
+ return;
+ }
+ }
+
+ status = smbXsrv_session_find_auth(session, xconn, now, &auth);
+ if (!NT_STATUS_IS_OK(status)) {
+ status = smbXsrv_session_create_auth(session, xconn, now,
+ 0, /* flags */
+ 0, /* security */
+ &auth);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, nt_status_squash(status));
+ return;
+ }
+ }
+
+ if (auth->gensec == NULL) {
+ status = auth_generic_prepare(session,
+ xconn->remote_address,
+ xconn->local_address,
+ "SMB",
+ &auth->gensec);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(session);
+ reply_nterror(req, nt_status_squash(status));
+ return;
+ }
+
+ gensec_want_feature(auth->gensec, GENSEC_FEATURE_SESSION_KEY);
+ gensec_want_feature(auth->gensec, GENSEC_FEATURE_UNIX_TOKEN);
+ gensec_want_feature(auth->gensec, GENSEC_FEATURE_SMB_TRANSPORT);
+
+ status = gensec_start_mech_by_oid(auth->gensec,
+ GENSEC_OID_SPNEGO);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Failed to start SPNEGO handler!\n"));
+ TALLOC_FREE(session);;
+ reply_nterror(req, nt_status_squash(status));
+ return;
+ }
+ }
+
+ become_root();
+ status = gensec_update(auth->gensec,
+ talloc_tos(),
+ in_blob, &out_blob);
+ unbecome_root();
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ TALLOC_FREE(session);
+ reply_nterror(req, nt_status_squash(status));
+ return;
+ }
+
+ if (NT_STATUS_IS_OK(status) && session->global->auth_session_info == NULL) {
+ struct auth_session_info *session_info = NULL;
+
+ status = gensec_session_info(auth->gensec,
+ session,
+ &session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1,("Failed to generate session_info "
+ "(user and group token) for session setup: %s\n",
+ nt_errstr(status)));
+ data_blob_free(&out_blob);
+ TALLOC_FREE(session);
+ reply_nterror(req, nt_status_squash(status));
+ return;
+ }
+
+ if (security_session_user_level(session_info, NULL) == SECURITY_GUEST) {
+ action |= SMB_SETUP_GUEST;
+ }
+
+ session->global->signing_algo = SMB2_SIGNING_MD5_SMB1;
+ session->global->encryption_cipher = 0;
+ session->global->channels[0].signing_algo =
+ session->global->signing_algo;
+ session->global->channels[0].encryption_cipher =
+ session->global->encryption_cipher;
+
+ if (session_info->session_key.length > 0) {
+ struct smbXsrv_session *x = session;
+
+ status = smb2_signing_key_sign_create(x->global,
+ x->global->signing_algo,
+ &session_info->session_key,
+ NULL, /* no derivation */
+ &x->global->signing_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ data_blob_free(&out_blob);
+ TALLOC_FREE(session);
+ reply_nterror(req, status);
+ return;
+ }
+ x->global->signing_key_blob = x->global->signing_key->blob;
+
+ /*
+ * clear the session key
+ * the first tcon will add setup the application key
+ */
+ data_blob_clear_free(&session_info->session_key);
+ }
+
+ sconn->num_users++;
+
+ if (security_session_user_level(session_info, NULL) >= SECURITY_USER) {
+ is_authenticated = true;
+ session->homes_snum =
+ register_homes_share(session_info->unix_info->unix_name);
+ }
+
+ if (smb1_srv_is_signing_negotiated(xconn) &&
+ is_authenticated &&
+ smb2_signing_key_valid(session->global->signing_key))
+ {
+ /*
+ * Try and turn on server signing on the first non-guest
+ * sessionsetup.
+ */
+ smb1_srv_set_signing(xconn,
+ session->global->signing_key->blob,
+ data_blob_null);
+ }
+
+ set_current_user_info(session_info->unix_info->sanitized_username,
+ session_info->unix_info->unix_name,
+ session_info->info->domain_name);
+
+ session->status = NT_STATUS_OK;
+ session->global->auth_session_info = talloc_move(session->global,
+ &session_info);
+ session->global->auth_session_info_seqnum += 1;
+ session->global->channels[0].auth_session_info_seqnum =
+ session->global->auth_session_info_seqnum;
+ session->global->auth_time = now;
+ if (client_caps & CAP_DYNAMIC_REAUTH) {
+ session->global->expiration_time =
+ gensec_expire_time(auth->gensec);
+ } else {
+ session->global->expiration_time =
+ GENSEC_EXPIRE_TIME_INFINITY;
+ }
+
+ if (!session_claim(session)) {
+ DEBUG(1, ("smb1: Failed to claim session for vuid=%llu\n",
+ (unsigned long long)session->global->session_wire_id));
+ data_blob_free(&out_blob);
+ TALLOC_FREE(session);
+ reply_nterror(req, NT_STATUS_LOGON_FAILURE);
+ return;
+ }
+
+ status = smbXsrv_session_update(session);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("smb1: Failed to update session for vuid=%llu - %s\n",
+ (unsigned long long)session->global->session_wire_id,
+ nt_errstr(status)));
+ data_blob_free(&out_blob);
+ TALLOC_FREE(session);
+ reply_nterror(req, NT_STATUS_LOGON_FAILURE);
+ return;
+ }
+
+ if (!xconn->smb1.sessions.done_sesssetup) {
+ if (smb_bufsize < SMB_BUFFER_SIZE_MIN) {
+ reply_force_doserror(req, ERRSRV, ERRerror);
+ return;
+ }
+ xconn->smb1.sessions.max_send = smb_bufsize;
+ xconn->smb1.sessions.done_sesssetup = true;
+ }
+
+ /* current_user_info is changed on new vuid */
+ reload_services(sconn, conn_snum_used, true);
+ } else if (NT_STATUS_IS_OK(status)) {
+ struct auth_session_info *session_info = NULL;
+
+ status = gensec_session_info(auth->gensec,
+ session,
+ &session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1,("Failed to generate session_info "
+ "(user and group token) for session setup: %s\n",
+ nt_errstr(status)));
+ data_blob_free(&out_blob);
+ TALLOC_FREE(session);
+ reply_nterror(req, nt_status_squash(status));
+ return;
+ }
+
+ if (security_session_user_level(session_info, NULL) == SECURITY_GUEST) {
+ action |= SMB_SETUP_GUEST;
+ }
+
+ /*
+ * Keep the application key
+ */
+ data_blob_clear_free(&session_info->session_key);
+ session_info->session_key =
+ session->global->auth_session_info->session_key;
+ talloc_steal(session_info, session_info->session_key.data);
+ TALLOC_FREE(session->global->auth_session_info);
+
+ if (security_session_user_level(session_info, NULL) >= SECURITY_USER) {
+ session->homes_snum =
+ register_homes_share(session_info->unix_info->unix_name);
+ }
+
+ set_current_user_info(session_info->unix_info->sanitized_username,
+ session_info->unix_info->unix_name,
+ session_info->info->domain_name);
+
+ session->status = NT_STATUS_OK;
+ session->global->auth_session_info = talloc_move(session->global,
+ &session_info);
+ session->global->auth_session_info_seqnum += 1;
+ session->global->channels[0].auth_session_info_seqnum =
+ session->global->auth_session_info_seqnum;
+ session->global->auth_time = now;
+ if (client_caps & CAP_DYNAMIC_REAUTH) {
+ session->global->expiration_time =
+ gensec_expire_time(auth->gensec);
+ } else {
+ session->global->expiration_time =
+ GENSEC_EXPIRE_TIME_INFINITY;
+ }
+
+ status = smbXsrv_session_update(session);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("smb1: Failed to update session for vuid=%llu - %s\n",
+ (unsigned long long)session->global->session_wire_id,
+ nt_errstr(status)));
+ data_blob_free(&out_blob);
+ TALLOC_FREE(session);
+ reply_nterror(req, NT_STATUS_LOGON_FAILURE);
+ return;
+ }
+
+ conn_clear_vuid_caches(sconn, session->global->session_wire_id);
+
+ /* current_user_info is changed on new vuid */
+ reload_services(sconn, conn_snum_used, true);
+ }
+
+ vuid = session->global->session_wire_id;
+
+ reply_smb1_outbuf(req, 4, 0);
+
+ SSVAL(req->outbuf, smb_uid, vuid);
+ SIVAL(req->outbuf, smb_rcls, NT_STATUS_V(status));
+ SSVAL(req->outbuf, smb_vwv0, 0xFF); /* no chaining possible */
+ SSVAL(req->outbuf, smb_vwv2, action);
+ SSVAL(req->outbuf, smb_vwv3, out_blob.length);
+
+ if (message_push_blob(&req->outbuf, out_blob) == -1) {
+ data_blob_free(&out_blob);
+ TALLOC_FREE(session);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ data_blob_free(&out_blob);
+
+ if (push_signature(&req->outbuf) == -1) {
+ TALLOC_FREE(session);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+}
+
+/****************************************************************************
+ On new VC == 0, shutdown *all* old connections and users.
+ It seems that only NT4.x does this. At W2K and above (XP etc.).
+ a new session setup with VC==0 is ignored.
+****************************************************************************/
+
+struct shutdown_state {
+ const char *ip;
+ size_t ip_length;
+ struct messaging_context *msg_ctx;
+};
+
+static int shutdown_other_smbds(struct smbXsrv_session_global0 *session,
+ void *private_data)
+{
+ struct shutdown_state *state = (struct shutdown_state *)private_data;
+ struct server_id self_pid = messaging_server_id(state->msg_ctx);
+ struct server_id pid = session->channels[0].server_id;
+ const char *addr = session->channels[0].remote_address;
+ const char *port_colon;
+ size_t addr_len;
+ struct server_id_buf tmp;
+
+ DEBUG(10, ("shutdown_other_smbds: %s, %s\n",
+ server_id_str_buf(pid, &tmp), addr));
+
+ if (!process_exists(pid)) {
+ DEBUG(10, ("process does not exist\n"));
+ return 0;
+ }
+
+ if (server_id_equal(&pid, &self_pid)) {
+ DEBUG(10, ("It's me\n"));
+ return 0;
+ }
+
+ port_colon = strrchr(addr, ':');
+ if (port_colon == NULL) {
+ DBG_DEBUG("addr %s in contains no port\n", addr);
+ return 0;
+ }
+ addr_len = port_colon - addr;
+
+ if ((addr_len != state->ip_length) ||
+ (strncmp(addr, state->ip, state->ip_length) != 0)) {
+ DEBUG(10, ("%s (%zu) does not match %s (%zu)\n",
+ state->ip, state->ip_length, addr, addr_len));
+ return 0;
+ }
+
+ DEBUG(1, ("shutdown_other_smbds: shutting down pid %u "
+ "(IP %s)\n", (unsigned int)procid_to_pid(&pid),
+ state->ip));
+
+ messaging_send(state->msg_ctx, pid, MSG_SHUTDOWN,
+ &data_blob_null);
+ return 0;
+}
+
+static void setup_new_vc_session(struct smbd_server_connection *sconn)
+{
+ DEBUG(2,("setup_new_vc_session: New VC == 0, if NT4.x "
+ "compatible we would close all old resources.\n"));
+
+ if (lp_reset_on_zero_vc()) {
+ char *addr;
+ const char *port_colon;
+ struct shutdown_state state;
+
+ addr = tsocket_address_string(
+ sconn->remote_address, talloc_tos());
+ if (addr == NULL) {
+ return;
+ }
+ state.ip = addr;
+
+ port_colon = strrchr(addr, ':');
+ if (port_colon == NULL) {
+ return;
+ }
+ state.ip_length = port_colon - addr;
+ state.msg_ctx = sconn->msg_ctx;
+ smbXsrv_session_global_traverse(shutdown_other_smbds, &state);
+ TALLOC_FREE(addr);
+ }
+}
+
+/****************************************************************************
+ Reply to a session setup command.
+****************************************************************************/
+
+struct reply_sesssetup_and_X_state {
+ struct smb_request *req;
+ struct auth4_context *auth_context;
+ struct auth_usersupplied_info *user_info;
+ const char *user;
+ const char *domain;
+ DATA_BLOB lm_resp;
+ DATA_BLOB nt_resp;
+ DATA_BLOB plaintext_password;
+};
+
+static int reply_sesssetup_and_X_state_destructor(
+ struct reply_sesssetup_and_X_state *state)
+{
+ data_blob_clear_free(&state->nt_resp);
+ data_blob_clear_free(&state->lm_resp);
+ data_blob_clear_free(&state->plaintext_password);
+ return 0;
+}
+
+void reply_sesssetup_and_X(struct smb_request *req)
+{
+ struct reply_sesssetup_and_X_state *state = NULL;
+ uint64_t sess_vuid;
+ uint16_t smb_bufsize;
+ char *tmp = NULL;
+ fstring sub_user; /* Sanitised username for substitution */
+ const char *native_os;
+ const char *native_lanman;
+ const char *primary_domain;
+ struct auth_session_info *session_info = NULL;
+ uint16_t smb_flag2 = req->flags2;
+ uint16_t action = 0;
+ bool is_authenticated = false;
+ NTTIME now = timeval_to_nttime(&req->request_time);
+ struct smbXsrv_session *session = NULL;
+ NTSTATUS nt_status;
+ struct smbXsrv_connection *xconn = req->xconn;
+ struct smbd_server_connection *sconn = req->sconn;
+ bool doencrypt = xconn->smb1.negprot.encrypted_passwords;
+ bool signing_allowed = false;
+ bool signing_mandatory = smb1_signing_is_mandatory(
+ xconn->smb1.signing_state);
+
+ START_PROFILE(SMBsesssetupX);
+
+ DEBUG(3,("wct=%d flg2=0x%x\n", req->wct, req->flags2));
+
+ state = talloc_zero(req, struct reply_sesssetup_and_X_state);
+ if (state == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+ state->req = req;
+ talloc_set_destructor(state, reply_sesssetup_and_X_state_destructor);
+
+ if (req->flags2 & FLAGS2_SMB_SECURITY_SIGNATURES) {
+ signing_allowed = true;
+ }
+ if (req->flags2 & FLAGS2_SMB_SECURITY_SIGNATURES_REQUIRED) {
+ signing_mandatory = true;
+ }
+
+ /*
+ * We can call smb1_srv_set_signing_negotiated() each time.
+ * It finds out when it needs to turn into a noop
+ * itself.
+ */
+ smb1_srv_set_signing_negotiated(xconn,
+ signing_allowed,
+ signing_mandatory);
+
+ /* a SPNEGO session setup has 12 command words, whereas a normal
+ NT1 session setup has 13. See the cifs spec. */
+ if (req->wct == 12 &&
+ (req->flags2 & FLAGS2_EXTENDED_SECURITY)) {
+
+ if (!xconn->smb1.negprot.spnego) {
+ DEBUG(0,("reply_sesssetup_and_X: Rejecting attempt "
+ "at SPNEGO session setup when it was not "
+ "negotiated.\n"));
+ reply_nterror(req, nt_status_squash(
+ NT_STATUS_LOGON_FAILURE));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+
+ if (SVAL(req->vwv+4, 0) == 0) {
+ setup_new_vc_session(req->sconn);
+ }
+
+ reply_sesssetup_and_X_spnego(req);
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+
+ smb_bufsize = SVAL(req->vwv+2, 0);
+
+ if (xconn->protocol < PROTOCOL_NT1) {
+ uint16_t passlen1 = SVAL(req->vwv+7, 0);
+
+ /* Never do NT status codes with protocols before NT1 as we
+ * don't get client caps. */
+ remove_from_common_flags2(FLAGS2_32_BIT_ERROR_CODES);
+
+ if ((passlen1 > MAX_PASS_LEN) || (passlen1 > req->buflen)) {
+ reply_nterror(req, nt_status_squash(
+ NT_STATUS_INVALID_PARAMETER));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+
+ if (doencrypt) {
+ state->lm_resp = data_blob_talloc(state,
+ req->buf,
+ passlen1);
+ } else {
+ state->plaintext_password = data_blob_talloc(state,
+ req->buf,
+ passlen1+1);
+ /* Ensure null termination */
+ state->plaintext_password.data[passlen1] = 0;
+ }
+
+ srvstr_pull_req_talloc(state, req, &tmp,
+ req->buf + passlen1, STR_TERMINATE);
+ state->user = tmp ? tmp : "";
+
+ state->domain = "";
+
+ } else {
+ uint16_t passlen1 = SVAL(req->vwv+7, 0);
+ uint16_t passlen2 = SVAL(req->vwv+8, 0);
+ enum remote_arch_types ra_type = get_remote_arch();
+ const uint8_t *p = req->buf;
+ const uint8_t *save_p = req->buf;
+ uint16_t byte_count;
+
+ if (!xconn->smb1.sessions.done_sesssetup) {
+ global_client_caps = IVAL(req->vwv+11, 0);
+
+ if (!(global_client_caps & CAP_STATUS32)) {
+ remove_from_common_flags2(
+ FLAGS2_32_BIT_ERROR_CODES);
+ }
+
+ /* client_caps is used as final determination if
+ * client is NT or Win95. This is needed to return
+ * the correct error codes in some circumstances.
+ */
+
+ if(ra_type == RA_WINNT || ra_type == RA_WIN2K ||
+ ra_type == RA_WIN95) {
+ if(!(global_client_caps & (CAP_NT_SMBS|
+ CAP_STATUS32))) {
+ set_remote_arch( RA_WIN95);
+ }
+ }
+ }
+
+ if (!doencrypt) {
+ /* both Win95 and WinNT stuff up the password
+ * lengths for non-encrypting systems. Uggh.
+
+ if passlen1==24 its a win95 system, and its setting
+ the password length incorrectly. Luckily it still
+ works with the default code because Win95 will null
+ terminate the password anyway
+
+ if passlen1>0 and passlen2>0 then maybe its a NT box
+ and its setting passlen2 to some random value which
+ really stuffs things up. we need to fix that one. */
+
+ if (passlen1 > 0 && passlen2 > 0 && passlen2 != 24 &&
+ passlen2 != 1) {
+ passlen2 = 0;
+ }
+ }
+
+ /* check for nasty tricks */
+ if (passlen1 > MAX_PASS_LEN
+ || passlen1 > smbreq_bufrem(req, p)) {
+ reply_nterror(req, nt_status_squash(
+ NT_STATUS_INVALID_PARAMETER));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+
+ if (passlen2 > MAX_PASS_LEN
+ || passlen2 > smbreq_bufrem(req, p+passlen1)) {
+ reply_nterror(req, nt_status_squash(
+ NT_STATUS_INVALID_PARAMETER));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+
+ /* Save the lanman2 password and the NT md4 password. */
+
+ if ((doencrypt) && (passlen1 != 0) && (passlen1 != 24)) {
+ doencrypt = False;
+ }
+
+ if (doencrypt) {
+ state->lm_resp = data_blob_talloc(state, p, passlen1);
+ state->nt_resp = data_blob_talloc(state, p+passlen1, passlen2);
+ } else {
+ char *pass = NULL;
+ bool unic= smb_flag2 & FLAGS2_UNICODE_STRINGS;
+
+ if (unic && (passlen2 == 0) && passlen1) {
+ /* Only a ascii plaintext password was sent. */
+ (void)srvstr_pull_talloc(state,
+ req->inbuf,
+ req->flags2,
+ &pass,
+ req->buf,
+ passlen1,
+ STR_TERMINATE|STR_ASCII);
+ } else {
+ (void)srvstr_pull_talloc(state,
+ req->inbuf,
+ req->flags2,
+ &pass,
+ req->buf,
+ unic ? passlen2 : passlen1,
+ STR_TERMINATE);
+ }
+ if (!pass) {
+ reply_nterror(req, nt_status_squash(
+ NT_STATUS_INVALID_PARAMETER));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+ state->plaintext_password = data_blob_talloc(state,
+ pass,
+ strlen(pass)+1);
+ }
+
+ p += passlen1 + passlen2;
+
+ p += srvstr_pull_req_talloc(state, req, &tmp, p,
+ STR_TERMINATE);
+ state->user = tmp ? tmp : "";
+
+ p += srvstr_pull_req_talloc(state, req, &tmp, p,
+ STR_TERMINATE);
+ state->domain = tmp ? tmp : "";
+
+ p += srvstr_pull_req_talloc(talloc_tos(), req, &tmp, p,
+ STR_TERMINATE);
+ native_os = tmp ? tmp : "";
+
+ p += srvstr_pull_req_talloc(talloc_tos(), req, &tmp, p,
+ STR_TERMINATE);
+ native_lanman = tmp ? tmp : "";
+
+ /* not documented or decoded by Ethereal but there is one more
+ * string in the extra bytes which is the same as the
+ * PrimaryDomain when using extended security. Windows NT 4
+ * and 2003 use this string to store the native lanman string.
+ * Windows 9x does not include a string here at all so we have
+ * to check if we have any extra bytes left */
+
+ byte_count = SVAL(req->vwv+13, 0);
+ if ( PTR_DIFF(p, save_p) < byte_count) {
+ p += srvstr_pull_req_talloc(talloc_tos(), req, &tmp, p,
+ STR_TERMINATE);
+ primary_domain = tmp ? tmp : "";
+ } else {
+ primary_domain = talloc_strdup(talloc_tos(), "null");
+ }
+
+ DEBUG(3,("Domain=[%s] NativeOS=[%s] NativeLanMan=[%s] "
+ "PrimaryDomain=[%s]\n",
+ state->domain, native_os, native_lanman, primary_domain));
+
+ if ( ra_type == RA_WIN2K ) {
+ if ( strlen(native_lanman) == 0 )
+ ra_lanman_string( primary_domain );
+ else
+ ra_lanman_string( native_lanman );
+ }
+
+ }
+
+ if (SVAL(req->vwv+4, 0) == 0) {
+ setup_new_vc_session(req->sconn);
+ }
+
+ DEBUG(3,("sesssetupX:name=[%s]\\[%s]@[%s]\n",
+ state->domain, state->user, get_remote_machine_name()));
+
+ if (*state->user) {
+ if (xconn->smb1.negprot.spnego) {
+
+ /* This has to be here, because this is a perfectly
+ * valid behaviour for guest logons :-( */
+
+ DEBUG(0,("reply_sesssetup_and_X: Rejecting attempt "
+ "at 'normal' session setup after "
+ "negotiating spnego.\n"));
+ reply_nterror(req, nt_status_squash(
+ NT_STATUS_LOGON_FAILURE));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+ fstrcpy(sub_user, state->user);
+ } else {
+ fstrcpy(sub_user, "");
+ }
+
+ if (!*state->user) {
+ DEBUG(3,("Got anonymous request\n"));
+
+ nt_status = make_auth4_context(state, &state->auth_context);
+ if (NT_STATUS_IS_OK(nt_status)) {
+ uint8_t chal[8];
+
+ state->auth_context->get_ntlm_challenge(
+ state->auth_context, chal);
+
+ if (!make_user_info_guest(state,
+ sconn->remote_address,
+ sconn->local_address,
+ "SMB", &state->user_info)) {
+ nt_status = NT_STATUS_NO_MEMORY;
+ }
+
+ if (NT_STATUS_IS_OK(nt_status)) {
+ state->user_info->auth_description = "guest";
+ }
+ }
+ } else if (doencrypt) {
+ state->auth_context = xconn->smb1.negprot.auth_context;
+ if (state->auth_context == NULL) {
+ DEBUG(0, ("reply_sesssetup_and_X: Attempted encrypted "
+ "session setup without negprot denied!\n"));
+ reply_nterror(req, nt_status_squash(
+ NT_STATUS_LOGON_FAILURE));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+ nt_status = make_user_info_for_reply_enc(state,
+ &state->user_info,
+ state->user,
+ state->domain,
+ sconn->remote_address,
+ sconn->local_address,
+ "SMB",
+ state->lm_resp,
+ state->nt_resp);
+
+ if (NT_STATUS_IS_OK(nt_status)) {
+ state->user_info->auth_description = "bare-NTLM";
+ }
+ } else {
+ nt_status = make_auth4_context(state, &state->auth_context);
+ if (NT_STATUS_IS_OK(nt_status)) {
+ uint8_t chal[8];
+
+ state->auth_context->get_ntlm_challenge(
+ state->auth_context, chal);
+
+ if (!make_user_info_for_reply(state,
+ &state->user_info,
+ state->user,
+ state->domain,
+ sconn->remote_address,
+ sconn->local_address,
+ "SMB",
+ chal,
+ state->plaintext_password)) {
+ nt_status = NT_STATUS_NO_MEMORY;
+ }
+
+ if (NT_STATUS_IS_OK(nt_status)) {
+ state->user_info->auth_description = "plaintext";
+ }
+ }
+ }
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ reply_nterror(req, nt_status_squash(nt_status));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+
+ nt_status = auth_check_password_session_info(state->auth_context,
+ req, state->user_info,
+ &session_info);
+ TALLOC_FREE(state->user_info);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ reply_nterror(req, nt_status_squash(nt_status));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+
+ /* it's ok - setup a reply */
+ reply_smb1_outbuf(req, 3, 0);
+ SSVAL(req->outbuf, smb_vwv0, 0xff); /* andx chain ends */
+ SSVAL(req->outbuf, smb_vwv1, 0); /* no andx offset */
+
+ if (xconn->protocol >= PROTOCOL_NT1) {
+ push_signature(&req->outbuf);
+ /* perhaps grab OS version here?? */
+ }
+
+ if (security_session_user_level(session_info, NULL) == SECURITY_GUEST) {
+ action |= SMB_SETUP_GUEST;
+ }
+
+ /* register the name and uid as being validated, so further connections
+ to a uid can get through without a password, on the same VC */
+
+ nt_status = smbXsrv_session_create(xconn,
+ now, &session);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ reply_nterror(req, nt_status_squash(nt_status));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+
+ session->global->signing_algo = SMB2_SIGNING_MD5_SMB1;
+ session->global->encryption_cipher = 0;
+ session->global->channels[0].signing_algo =
+ session->global->signing_algo;
+ session->global->channels[0].encryption_cipher =
+ session->global->encryption_cipher;
+
+ if (session_info->session_key.length > 0) {
+ struct smbXsrv_session *x = session;
+ uint8_t session_key[16];
+ NTSTATUS status;
+
+ status = smb2_signing_key_sign_create(x->global,
+ x->global->signing_algo,
+ &session_info->session_key,
+ NULL, /* no derivation */
+ &x->global->signing_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(session);
+ reply_nterror(req, status);
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+ x->global->signing_key_blob = x->global->signing_key->blob;
+
+ /*
+ * The application key is truncated/padded to 16 bytes
+ */
+ ZERO_STRUCT(session_key);
+ memcpy(session_key, session->global->signing_key_blob.data,
+ MIN(session->global->signing_key_blob.length,
+ sizeof(session_key)));
+ session->global->application_key_blob =
+ data_blob_talloc(session->global,
+ session_key,
+ sizeof(session_key));
+ ZERO_STRUCT(session_key);
+ if (session->global->application_key_blob.data == NULL) {
+ TALLOC_FREE(session);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+ talloc_keep_secret(session->global->application_key_blob.data);
+
+ /*
+ * Place the application key into the session_info
+ */
+ data_blob_clear_free(&session_info->session_key);
+ session_info->session_key = data_blob_dup_talloc(session_info,
+ session->global->application_key_blob);
+ if (session_info->session_key.data == NULL) {
+ TALLOC_FREE(session);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+ talloc_keep_secret(session_info->session_key.data);
+ }
+
+ sconn->num_users++;
+
+ if (security_session_user_level(session_info, NULL) >= SECURITY_USER) {
+ is_authenticated = true;
+ session->homes_snum =
+ register_homes_share(session_info->unix_info->unix_name);
+ }
+
+ if (smb1_srv_is_signing_negotiated(xconn) &&
+ is_authenticated &&
+ smb2_signing_key_valid(session->global->signing_key))
+ {
+ /*
+ * Try and turn on server signing on the first non-guest
+ * sessionsetup.
+ */
+ smb1_srv_set_signing(xconn,
+ session->global->signing_key->blob,
+ state->nt_resp.data ? state->nt_resp : state->lm_resp);
+ }
+
+ set_current_user_info(session_info->unix_info->sanitized_username,
+ session_info->unix_info->unix_name,
+ session_info->info->domain_name);
+
+ session->status = NT_STATUS_OK;
+ session->global->auth_session_info = talloc_move(session->global,
+ &session_info);
+ session->global->auth_session_info_seqnum += 1;
+ session->global->channels[0].auth_session_info_seqnum =
+ session->global->auth_session_info_seqnum;
+ session->global->auth_time = now;
+ session->global->expiration_time = GENSEC_EXPIRE_TIME_INFINITY;
+
+ nt_status = smbXsrv_session_update(session);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("smb1: Failed to update session for vuid=%llu - %s\n",
+ (unsigned long long)session->global->session_wire_id,
+ nt_errstr(nt_status)));
+ TALLOC_FREE(session);
+ reply_nterror(req, nt_status_squash(nt_status));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+
+ if (!session_claim(session)) {
+ DEBUG(1, ("smb1: Failed to claim session for vuid=%llu\n",
+ (unsigned long long)session->global->session_wire_id));
+ TALLOC_FREE(session);
+ reply_nterror(req, NT_STATUS_LOGON_FAILURE);
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+
+ /* current_user_info is changed on new vuid */
+ reload_services(sconn, conn_snum_used, true);
+
+ sess_vuid = session->global->session_wire_id;
+
+ SSVAL(req->outbuf,smb_vwv2,action);
+ SSVAL(req->outbuf,smb_uid,sess_vuid);
+ SSVAL(discard_const_p(char, req->inbuf),smb_uid,sess_vuid);
+ req->vuid = sess_vuid;
+
+ if (!xconn->smb1.sessions.done_sesssetup) {
+ if (smb_bufsize < SMB_BUFFER_SIZE_MIN) {
+ reply_force_doserror(req, ERRSRV, ERRerror);
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+ xconn->smb1.sessions.max_send = smb_bufsize;
+ xconn->smb1.sessions.done_sesssetup = true;
+ }
+
+ TALLOC_FREE(state);
+ END_PROFILE(SMBsesssetupX);
+}
diff --git a/source3/smbd/smb1_sesssetup.h b/source3/smbd/smb1_sesssetup.h
new file mode 100644
index 0000000..6f1324a
--- /dev/null
+++ b/source3/smbd/smb1_sesssetup.h
@@ -0,0 +1,25 @@
+/*
+ Unix SMB/CIFS implementation.
+ handle SMBsessionsetup
+ Copyright (C) Andrew Tridgell 1998-2001
+ Copyright (C) Andrew Bartlett 2001
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002
+ Copyright (C) Luke Howard 2003
+ Copyright (C) Volker Lendecke 2007
+ Copyright (C) Jeremy Allison 2007
+
+ 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/>.
+*/
+
+void reply_sesssetup_and_X(struct smb_request *req);
diff --git a/source3/smbd/smb1_signing.c b/source3/smbd/smb1_signing.c
new file mode 100644
index 0000000..aa3027d
--- /dev/null
+++ b/source3/smbd/smb1_signing.c
@@ -0,0 +1,290 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB Signing Code
+ Copyright (C) Jeremy Allison 2003.
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2002-2003
+ Copyright (C) Stefan Metzmacher 2009
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_signing.h"
+#include "lib/param/param.h"
+
+/***********************************************************
+ Called to validate an incoming packet from the client.
+************************************************************/
+
+bool smb1_srv_check_sign_mac(struct smbXsrv_connection *conn,
+ const char *inbuf, uint32_t *seqnum,
+ bool trusted_channel)
+{
+ const uint8_t *inhdr;
+ size_t len;
+
+ /* Check if it's a non-session message. */
+ if(CVAL(inbuf,0)) {
+ return true;
+ }
+
+ len = smb_len(inbuf);
+ inhdr = (const uint8_t *)inbuf + NBT_HDR_SIZE;
+
+ if (trusted_channel) {
+ NTSTATUS status;
+
+ if (len < (HDR_SS_FIELD + 8)) {
+ DBG_WARNING("Can't check signature "
+ "on short packet! smb_len = %u\n",
+ (unsigned)len);
+ return false;
+ }
+
+ status = NT_STATUS(IVAL(inhdr, HDR_SS_FIELD + 4));
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("trusted channel passed %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ *seqnum = IVAL(inhdr, HDR_SS_FIELD);
+ return true;
+ }
+
+ *seqnum = smb1_signing_next_seqnum(conn->smb1.signing_state, false);
+ return smb1_signing_check_pdu(conn->smb1.signing_state,
+ inhdr, len,
+ *seqnum);
+}
+
+/***********************************************************
+ Called to sign an outgoing packet to the client.
+************************************************************/
+
+NTSTATUS smb1_srv_calculate_sign_mac(struct smbXsrv_connection *conn,
+ char *outbuf, uint32_t seqnum)
+{
+ uint8_t *outhdr;
+ size_t len;
+
+ /* Check if it's a non-session message. */
+ if(CVAL(outbuf,0)) {
+ return NT_STATUS_OK;;
+ }
+
+ len = smb_len(outbuf);
+ outhdr = (uint8_t *)outbuf + NBT_HDR_SIZE;
+
+ return smb1_signing_sign_pdu(conn->smb1.signing_state,
+ outhdr,
+ len,
+ seqnum);
+}
+
+
+/***********************************************************
+ Called to indicate a oneway request
+************************************************************/
+void smb1_srv_cancel_sign_response(struct smbXsrv_connection *conn)
+{
+ smb1_signing_cancel_reply(conn->smb1.signing_state, true);
+}
+
+struct smbd_shm_signing {
+ size_t shm_size;
+ uint8_t *shm_pointer;
+
+ /* we know the signing engine will only allocate 2 chunks */
+ uint8_t *ptr1;
+ size_t len1;
+ uint8_t *ptr2;
+ size_t len2;
+};
+
+static int smbd_shm_signing_destructor(struct smbd_shm_signing *s)
+{
+ anonymous_shared_free(s->shm_pointer);
+ return 0;
+}
+
+static void *smbd_shm_signing_alloc(TALLOC_CTX *mem_ctx, size_t len)
+{
+ struct smbd_shm_signing *s = talloc_get_type_abort(mem_ctx,
+ struct smbd_shm_signing);
+
+ if (s->ptr1 == NULL) {
+ s->len1 = len;
+ if (len % 8) {
+ s->len1 += (8 - (len % 8));
+ }
+ if (s->len1 > s->shm_size) {
+ s->len1 = 0;
+ errno = ENOMEM;
+ return NULL;
+ }
+ s->ptr1 = s->shm_pointer;
+ return s->ptr1;
+ }
+
+ if (s->ptr2 == NULL) {
+ s->len2 = len;
+ if (s->len2 > (s->shm_size - s->len1)) {
+ s->len2 = 0;
+ errno = ENOMEM;
+ return NULL;
+ }
+ s->ptr2 = s->shm_pointer + s->len1;
+ return s->ptr2;
+ }
+
+ errno = ENOMEM;
+ return NULL;
+}
+
+static void smbd_shm_signing_free(TALLOC_CTX *mem_ctx, void *ptr)
+{
+ struct smbd_shm_signing *s = talloc_get_type_abort(mem_ctx,
+ struct smbd_shm_signing);
+
+ if (s->ptr2 == ptr) {
+ s->ptr2 = NULL;
+ s->len2 = 0;
+ }
+}
+
+/***********************************************************
+ Called by server negprot when signing has been negotiated.
+************************************************************/
+
+bool smb1_srv_init_signing(struct loadparm_context *lp_ctx,
+ struct smbXsrv_connection *conn)
+{
+ bool allowed = true;
+ bool desired;
+ bool mandatory = false;
+
+ /*
+ * if the client and server allow signing,
+ * we desire to use it.
+ *
+ * This matches Windows behavior and is needed
+ * because not every client that requires signing
+ * sends FLAGS2_SMB_SECURITY_SIGNATURES_REQUIRED.
+ *
+ * Note that we'll always allow signing if the client
+ * does send FLAGS2_SMB_SECURITY_SIGNATURES_REQUIRED.
+ */
+
+ desired = lpcfg_server_signing_allowed(lp_ctx, &mandatory);
+
+ if (lp_async_smb_echo_handler()) {
+ struct smbd_shm_signing *s;
+
+ /* setup the signing state in shared memory */
+ s = talloc_zero(conn, struct smbd_shm_signing);
+ if (s == NULL) {
+ return false;
+ }
+ s->shm_size = 4096;
+ s->shm_pointer =
+ (uint8_t *)anonymous_shared_allocate(s->shm_size);
+ if (s->shm_pointer == NULL) {
+ talloc_free(s);
+ return false;
+ }
+ talloc_set_destructor(s, smbd_shm_signing_destructor);
+ conn->smb1.signing_state = smb1_signing_init_ex(s,
+ allowed, desired, mandatory,
+ smbd_shm_signing_alloc,
+ smbd_shm_signing_free);
+ if (!conn->smb1.signing_state) {
+ return false;
+ }
+ return true;
+ }
+
+ conn->smb1.signing_state = smb1_signing_init(conn,
+ allowed, desired, mandatory);
+ if (!conn->smb1.signing_state) {
+ return false;
+ }
+
+ return true;
+}
+
+void smb1_srv_set_signing_negotiated(struct smbXsrv_connection *conn,
+ bool allowed, bool mandatory)
+{
+ smb1_signing_set_negotiated(conn->smb1.signing_state,
+ allowed, mandatory);
+}
+
+/***********************************************************
+ Returns whether signing is active. We can't use sendfile or raw
+ reads/writes if it is.
+************************************************************/
+
+bool smb1_srv_is_signing_active(struct smbXsrv_connection *conn)
+{
+ return smb1_signing_is_active(conn->smb1.signing_state);
+}
+
+
+/***********************************************************
+ Returns whether signing is negotiated. We can't use it unless it was
+ in the negprot.
+************************************************************/
+
+bool smb1_srv_is_signing_negotiated(struct smbXsrv_connection *conn)
+{
+ return smb1_signing_is_negotiated(conn->smb1.signing_state);
+}
+
+/***********************************************************
+ Turn on signing from this packet onwards.
+************************************************************/
+
+void smb1_srv_set_signing(struct smbXsrv_connection *conn,
+ const DATA_BLOB user_session_key,
+ const DATA_BLOB response)
+{
+ bool negotiated;
+ bool mandatory;
+
+ if (!user_session_key.length)
+ return;
+
+ negotiated = smb1_signing_is_negotiated(conn->smb1.signing_state);
+ mandatory = smb1_signing_is_mandatory(conn->smb1.signing_state);
+
+ if (!negotiated && !mandatory) {
+ DBG_INFO("signing negotiated = %u, "
+ "mandatory_signing = %u. Not allowing smb signing.\n",
+ negotiated, mandatory);
+ return;
+ }
+
+ if (!smb1_signing_activate(conn->smb1.signing_state,
+ user_session_key, response)) {
+ return;
+ }
+
+ DBG_NOTICE("turning on SMB signing: "
+ "signing negotiated = %u, mandatory_signing = %u.\n",
+ negotiated, mandatory);
+}
+
diff --git a/source3/smbd/smb1_signing.h b/source3/smbd/smb1_signing.h
new file mode 100644
index 0000000..26f6042
--- /dev/null
+++ b/source3/smbd/smb1_signing.h
@@ -0,0 +1,37 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB Signing Code
+ Copyright (C) Jeremy Allison 2003.
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2002-2003
+ Copyright (C) Stefan Metzmacher 2009
+
+ 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/>.
+*/
+
+struct smbXsrv_connection;
+
+bool smb1_srv_check_sign_mac(struct smbXsrv_connection *conn,
+ const char *inbuf, uint32_t *seqnum, bool trusted_channel);
+NTSTATUS smb1_srv_calculate_sign_mac(struct smbXsrv_connection *conn,
+ char *outbuf, uint32_t seqnum);
+void smb1_srv_cancel_sign_response(struct smbXsrv_connection *conn);
+void smb1_srv_set_signing_negotiated(struct smbXsrv_connection *conn,
+ bool allowed, bool mandatory);
+bool smb1_srv_is_signing_active(struct smbXsrv_connection *conn);
+bool smb1_srv_is_signing_negotiated(struct smbXsrv_connection *conn);
+void smb1_srv_set_signing(struct smbXsrv_connection *conn,
+ const DATA_BLOB user_session_key,
+ const DATA_BLOB response);
+bool smb1_srv_init_signing(struct loadparm_context *lp_ctx,
+ struct smbXsrv_connection *conn);
diff --git a/source3/smbd/smb1_trans2.c b/source3/smbd/smb1_trans2.c
new file mode 100644
index 0000000..16dff34
--- /dev/null
+++ b/source3/smbd/smb1_trans2.c
@@ -0,0 +1,5703 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB transaction2 handling
+ Copyright (C) Jeremy Allison 1994-2007
+ Copyright (C) Stefan (metze) Metzmacher 2003
+ Copyright (C) Volker Lendecke 2005-2007
+ Copyright (C) Steve French 2005
+ Copyright (C) James Peach 2006-2007
+
+ Extensively modified by Andrew Tridgell, 1995
+
+ 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 "ntioctl.h"
+#include "system/filesys.h"
+#include "lib/util/time_basic.h"
+#include "version.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "../librpc/gen_ndr/xattr.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "libcli/security/security.h"
+#include "trans2.h"
+#include "auth.h"
+#include "smbprofile.h"
+#include "rpc_server/srv_pipe_hnd.h"
+#include "printing.h"
+#include "lib/util_ea.h"
+#include "lib/readdir_attr.h"
+#include "messages.h"
+#include "libcli/smb/smb2_posix.h"
+#include "lib/util/string_wrappers.h"
+#include "source3/lib/substitute.h"
+#include "source3/lib/adouble.h"
+#include "source3/smbd/dir.h"
+
+#define DIR_ENTRY_SAFETY_MARGIN 4096
+
+/****************************************************************************
+ Send the required number of replies back.
+ We assume all fields other than the data fields are
+ set correctly for the type of call.
+ HACK ! Always assumes smb_setup field is zero.
+****************************************************************************/
+
+static void send_trans2_replies(connection_struct *conn,
+ struct smb_request *req,
+ NTSTATUS status,
+ const char *params,
+ int paramsize,
+ const char *pdata,
+ int datasize,
+ int max_data_bytes)
+{
+ /* As we are using a protocol > LANMAN1 then the max_send
+ variable must have been set in the sessetupX call.
+ This takes precedence over the max_xmit field in the
+ global struct. These different max_xmit variables should
+ be merged as this is now too confusing */
+
+ int data_to_send = datasize;
+ int params_to_send = paramsize;
+ int useable_space;
+ const char *pp = params;
+ const char *pd = pdata;
+ int params_sent_thistime, data_sent_thistime, total_sent_thistime;
+ int alignment_offset = 1; /* JRA. This used to be 3. Set to 1 to make netmon parse ok. */
+ int data_alignment_offset = 0;
+ bool overflow = False;
+ struct smbXsrv_connection *xconn = req->xconn;
+ int max_send = xconn->smb1.sessions.max_send;
+
+ /* Modify the data_to_send and datasize and set the error if
+ we're trying to send more than max_data_bytes. We still send
+ the part of the packet(s) that fit. Strange, but needed
+ for OS/2. */
+
+ if (max_data_bytes > 0 && datasize > max_data_bytes) {
+ DEBUG(5,("send_trans2_replies: max_data_bytes %d exceeded by data %d\n",
+ max_data_bytes, datasize ));
+ datasize = data_to_send = max_data_bytes;
+ overflow = True;
+ }
+
+ /* If there genuinely are no parameters or data to send just send the empty packet */
+
+ if(params_to_send == 0 && data_to_send == 0) {
+ reply_smb1_outbuf(req, 10, 0);
+ if (NT_STATUS_V(status)) {
+ uint8_t eclass;
+ uint32_t ecode;
+ ntstatus_to_dos(status, &eclass, &ecode);
+ error_packet_set((char *)req->outbuf,
+ eclass, ecode, status,
+ __LINE__,__FILE__);
+ }
+ show_msg((char *)req->outbuf);
+ if (!smb1_srv_send(xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(conn))) {
+ exit_server_cleanly("send_trans2_replies: smb1_srv_send failed.");
+ }
+ TALLOC_FREE(req->outbuf);
+ return;
+ }
+
+ /* When sending params and data ensure that both are nicely aligned */
+ /* Only do this alignment when there is also data to send - else
+ can cause NT redirector problems. */
+
+ if (((params_to_send % 4) != 0) && (data_to_send != 0))
+ data_alignment_offset = 4 - (params_to_send % 4);
+
+ /* Space is bufsize minus Netbios over TCP header minus SMB header */
+ /* The alignment_offset is to align the param bytes on an even byte
+ boundary. NT 4.0 Beta needs this to work correctly. */
+
+ useable_space = max_send - (smb_size
+ + 2 * 10 /* wct */
+ + alignment_offset
+ + data_alignment_offset);
+
+ if (useable_space < 0) {
+ DEBUG(0, ("send_trans2_replies failed sanity useable_space "
+ "= %d!!!\n", useable_space));
+ exit_server_cleanly("send_trans2_replies: Not enough space");
+ }
+
+ while (params_to_send || data_to_send) {
+ /* Calculate whether we will totally or partially fill this packet */
+
+ total_sent_thistime = params_to_send + data_to_send;
+
+ /* We can never send more than useable_space */
+ /*
+ * Note that 'useable_space' does not include the alignment offsets,
+ * but we must include the alignment offsets in the calculation of
+ * the length of the data we send over the wire, as the alignment offsets
+ * are sent here. Fix from Marc_Jacobsen@hp.com.
+ */
+
+ total_sent_thistime = MIN(total_sent_thistime, useable_space);
+
+ reply_smb1_outbuf(req, 10, total_sent_thistime + alignment_offset
+ + data_alignment_offset);
+
+ /* Set total params and data to be sent */
+ SSVAL(req->outbuf,smb_tprcnt,paramsize);
+ SSVAL(req->outbuf,smb_tdrcnt,datasize);
+
+ /* Calculate how many parameters and data we can fit into
+ * this packet. Parameters get precedence
+ */
+
+ params_sent_thistime = MIN(params_to_send,useable_space);
+ data_sent_thistime = useable_space - params_sent_thistime;
+ data_sent_thistime = MIN(data_sent_thistime,data_to_send);
+
+ SSVAL(req->outbuf,smb_prcnt, params_sent_thistime);
+
+ /* smb_proff is the offset from the start of the SMB header to the
+ parameter bytes, however the first 4 bytes of outbuf are
+ the Netbios over TCP header. Thus use smb_base() to subtract
+ them from the calculation */
+
+ SSVAL(req->outbuf,smb_proff,
+ ((smb_buf(req->outbuf)+alignment_offset)
+ - smb_base(req->outbuf)));
+
+ if(params_sent_thistime == 0)
+ SSVAL(req->outbuf,smb_prdisp,0);
+ else
+ /* Absolute displacement of param bytes sent in this packet */
+ SSVAL(req->outbuf,smb_prdisp,pp - params);
+
+ SSVAL(req->outbuf,smb_drcnt, data_sent_thistime);
+ if(data_sent_thistime == 0) {
+ SSVAL(req->outbuf,smb_droff,0);
+ SSVAL(req->outbuf,smb_drdisp, 0);
+ } else {
+ /* The offset of the data bytes is the offset of the
+ parameter bytes plus the number of parameters being sent this time */
+ SSVAL(req->outbuf, smb_droff,
+ ((smb_buf(req->outbuf)+alignment_offset)
+ - smb_base(req->outbuf))
+ + params_sent_thistime + data_alignment_offset);
+ SSVAL(req->outbuf,smb_drdisp, pd - pdata);
+ }
+
+ /* Initialize the padding for alignment */
+
+ if (alignment_offset != 0) {
+ memset(smb_buf(req->outbuf), 0, alignment_offset);
+ }
+
+ /* Copy the param bytes into the packet */
+
+ if(params_sent_thistime) {
+ memcpy((smb_buf(req->outbuf)+alignment_offset), pp,
+ params_sent_thistime);
+ }
+
+ /* Copy in the data bytes */
+ if(data_sent_thistime) {
+ if (data_alignment_offset != 0) {
+ memset((smb_buf(req->outbuf)+alignment_offset+
+ params_sent_thistime), 0,
+ data_alignment_offset);
+ }
+ memcpy(smb_buf(req->outbuf)+alignment_offset
+ +params_sent_thistime+data_alignment_offset,
+ pd,data_sent_thistime);
+ }
+
+ DEBUG(9,("t2_rep: params_sent_thistime = %d, data_sent_thistime = %d, useable_space = %d\n",
+ params_sent_thistime, data_sent_thistime, useable_space));
+ DEBUG(9,("t2_rep: params_to_send = %d, data_to_send = %d, paramsize = %d, datasize = %d\n",
+ params_to_send, data_to_send, paramsize, datasize));
+
+ if (overflow) {
+ error_packet_set((char *)req->outbuf,
+ ERRDOS,ERRbufferoverflow,
+ STATUS_BUFFER_OVERFLOW,
+ __LINE__,__FILE__);
+ } else if (NT_STATUS_V(status)) {
+ uint8_t eclass;
+ uint32_t ecode;
+ ntstatus_to_dos(status, &eclass, &ecode);
+ error_packet_set((char *)req->outbuf,
+ eclass, ecode, status,
+ __LINE__,__FILE__);
+ }
+
+ /* Send the packet */
+ show_msg((char *)req->outbuf);
+ if (!smb1_srv_send(xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(conn))) {
+ exit_server_cleanly("send_trans2_replies: smb1_srv_send failed.");
+ }
+
+ TALLOC_FREE(req->outbuf);
+
+ pp += params_sent_thistime;
+ pd += data_sent_thistime;
+
+ params_to_send -= params_sent_thistime;
+ data_to_send -= data_sent_thistime;
+
+ /* Sanity check */
+ if(params_to_send < 0 || data_to_send < 0) {
+ DEBUG(0,("send_trans2_replies failed sanity check pts = %d, dts = %d\n!!!",
+ params_to_send, data_to_send));
+ return;
+ }
+ }
+
+ return;
+}
+
+/****************************************************************************
+ Deal with SMB_SET_POSIX_LOCK.
+****************************************************************************/
+
+static void smb_set_posix_lock_done(struct tevent_req *subreq);
+
+static NTSTATUS smb_set_posix_lock(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp)
+{
+ struct tevent_req *subreq = NULL;
+ struct smbd_lock_element *lck = NULL;
+ uint64_t count;
+ uint64_t offset;
+ uint64_t smblctx;
+ bool blocking_lock = False;
+ enum brl_type lock_type;
+
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (!CAN_WRITE(conn)) {
+ return NT_STATUS_DOS(ERRSRV, ERRaccess);
+ }
+
+ if (fsp == NULL ||
+ fsp->fsp_flags.is_pathref ||
+ fsp_get_io_fd(fsp) == -1)
+ {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (total_data != POSIX_LOCK_DATA_SIZE) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ switch (SVAL(pdata, POSIX_LOCK_TYPE_OFFSET)) {
+ case POSIX_LOCK_TYPE_READ:
+ lock_type = READ_LOCK;
+ break;
+ case POSIX_LOCK_TYPE_WRITE:
+ /* Return the right POSIX-mappable error code for files opened read-only. */
+ if (!fsp->fsp_flags.can_write) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+ lock_type = WRITE_LOCK;
+ break;
+ case POSIX_LOCK_TYPE_UNLOCK:
+ lock_type = UNLOCK_LOCK;
+ break;
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ switch (SVAL(pdata, POSIX_LOCK_FLAGS_OFFSET)) {
+ case POSIX_LOCK_FLAG_NOWAIT:
+ blocking_lock = false;
+ break;
+ case POSIX_LOCK_FLAG_WAIT:
+ blocking_lock = true;
+ break;
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!lp_blocking_locks(SNUM(conn))) {
+ blocking_lock = False;
+ }
+
+ smblctx = (uint64_t)IVAL(pdata, POSIX_LOCK_PID_OFFSET);
+ offset = (((uint64_t) IVAL(pdata,(POSIX_LOCK_START_OFFSET+4))) << 32) |
+ ((uint64_t) IVAL(pdata,POSIX_LOCK_START_OFFSET));
+ count = (((uint64_t) IVAL(pdata,(POSIX_LOCK_LEN_OFFSET+4))) << 32) |
+ ((uint64_t) IVAL(pdata,POSIX_LOCK_LEN_OFFSET));
+
+ DBG_DEBUG("file %s, lock_type = %u, smblctx = %"PRIu64", "
+ "count = %"PRIu64", offset = %"PRIu64"\n",
+ fsp_str_dbg(fsp),
+ (unsigned int)lock_type,
+ smblctx,
+ count,
+ offset);
+
+ if (lock_type == UNLOCK_LOCK) {
+ struct smbd_lock_element l = {
+ .req_guid = smbd_request_guid(req, 0),
+ .smblctx = smblctx,
+ .brltype = UNLOCK_LOCK,
+ .lock_flav = POSIX_LOCK,
+ .offset = offset,
+ .count = count,
+ };
+ status = smbd_do_unlocking(req, fsp, 1, &l);
+ return status;
+ }
+
+ lck = talloc(req, struct smbd_lock_element);
+ if (lck == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *lck = (struct smbd_lock_element) {
+ .req_guid = smbd_request_guid(req, 0),
+ .smblctx = smblctx,
+ .brltype = lock_type,
+ .lock_flav = POSIX_LOCK,
+ .count = count,
+ .offset = offset,
+ };
+
+ subreq = smbd_smb1_do_locks_send(
+ fsp,
+ req->sconn->ev_ctx,
+ &req,
+ fsp,
+ blocking_lock ? UINT32_MAX : 0,
+ true, /* large_offset */
+ 1,
+ lck);
+ if (subreq == NULL) {
+ TALLOC_FREE(lck);
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_req_set_callback(subreq, smb_set_posix_lock_done, req);
+ return NT_STATUS_EVENT_PENDING;
+}
+
+static void smb_set_posix_lock_done(struct tevent_req *subreq)
+{
+ struct smb_request *req = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ ok = smbd_smb1_do_locks_extract_smbreq(subreq, talloc_tos(), &req);
+ SMB_ASSERT(ok);
+
+ status = smbd_smb1_do_locks_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ if (NT_STATUS_IS_OK(status)) {
+ char params[2] = {0};
+ /* Fake up max_data_bytes here - we know it fits. */
+ send_trans2_replies(
+ req->conn,
+ req,
+ NT_STATUS_OK,
+ params,
+ 2,
+ NULL,
+ 0,
+ 0xffff);
+ } else {
+ reply_nterror(req, status);
+ ok = smb1_srv_send(req->xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(req->conn));
+ if (!ok) {
+ exit_server_cleanly("smb_set_posix_lock_done: "
+ "smb1_srv_send failed.");
+ }
+ }
+
+ TALLOC_FREE(req);
+ return;
+}
+
+/****************************************************************************
+ Read a list of EA names from an incoming data buffer. Create an ea_list with them.
+****************************************************************************/
+
+static struct ea_list *read_ea_name_list(TALLOC_CTX *ctx, const char *pdata, size_t data_size)
+{
+ struct ea_list *ea_list_head = NULL;
+ size_t converted_size, offset = 0;
+
+ while (offset + 2 < data_size) {
+ struct ea_list *eal = talloc_zero(ctx, struct ea_list);
+ unsigned int namelen = CVAL(pdata,offset);
+
+ offset++; /* Go past the namelen byte. */
+
+ /* integer wrap paranioa. */
+ if ((offset + namelen < offset) || (offset + namelen < namelen) ||
+ (offset > data_size) || (namelen > data_size) ||
+ (offset + namelen >= data_size)) {
+ break;
+ }
+ /* Ensure the name is null terminated. */
+ if (pdata[offset + namelen] != '\0') {
+ return NULL;
+ }
+ if (!pull_ascii_talloc(ctx, &eal->ea.name, &pdata[offset],
+ &converted_size)) {
+ DEBUG(0,("read_ea_name_list: pull_ascii_talloc "
+ "failed: %s\n", strerror(errno)));
+ }
+ if (!eal->ea.name) {
+ return NULL;
+ }
+
+ offset += (namelen + 1); /* Go past the name + terminating zero. */
+ DLIST_ADD_END(ea_list_head, eal);
+ DEBUG(10,("read_ea_name_list: read ea name %s\n", eal->ea.name));
+ }
+
+ return ea_list_head;
+}
+
+/****************************************************************************
+ Reply to a TRANSACT2_OPEN.
+****************************************************************************/
+
+static void call_trans2open(connection_struct *conn,
+ struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ struct smb_filename *smb_fname = NULL;
+ char *params = *pparams;
+ char *pdata = *ppdata;
+ int deny_mode;
+ int32_t open_attr;
+ bool oplock_request;
+#if 0
+ bool return_additional_info;
+ int16 open_sattr;
+ time_t open_time;
+#endif
+ int open_ofun;
+ uint32_t open_size;
+ char *pname;
+ char *fname = NULL;
+ off_t size=0;
+ int fattr = 0;
+ SMB_INO_T inode = 0;
+ int smb_action = 0;
+ struct files_struct *dirfsp = NULL;
+ files_struct *fsp;
+ struct ea_list *ea_list = NULL;
+ uint16_t flags = 0;
+ NTSTATUS status;
+ uint32_t access_mask;
+ uint32_t share_mode;
+ uint32_t create_disposition;
+ uint32_t create_options = 0;
+ uint32_t private_flags = 0;
+ NTTIME twrp = 0;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+ TALLOC_CTX *ctx = talloc_tos();
+
+ /*
+ * Ensure we have enough parameters to perform the operation.
+ */
+
+ if (total_params < 29) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ flags = SVAL(params, 0);
+ deny_mode = SVAL(params, 2);
+ open_attr = SVAL(params,6);
+ oplock_request = (flags & REQUEST_OPLOCK) ? EXCLUSIVE_OPLOCK : 0;
+ if (oplock_request) {
+ oplock_request |= (flags & REQUEST_BATCH_OPLOCK) ? BATCH_OPLOCK : 0;
+ }
+
+#if 0
+ return_additional_info = BITSETW(params,0);
+ open_sattr = SVAL(params, 4);
+ open_time = make_unix_date3(params+8);
+#endif
+ open_ofun = SVAL(params,12);
+ open_size = IVAL(params,14);
+ pname = &params[28];
+
+ if (IS_IPC(conn)) {
+ reply_nterror(req, NT_STATUS_NETWORK_ACCESS_DENIED);
+ goto out;
+ }
+
+ if (req->posix_pathnames) {
+ srvstr_get_path_posix(ctx,
+ params,
+ req->flags2,
+ &fname,
+ pname,
+ total_params - 28,
+ STR_TERMINATE,
+ &status);
+ } else {
+ srvstr_get_path(ctx,
+ params,
+ req->flags2,
+ &fname,
+ pname,
+ total_params - 28,
+ STR_TERMINATE,
+ &status);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ DEBUG(3,("call_trans2open %s deny_mode=0x%x attr=%d ofun=0x%x size=%d\n",
+ fname, (unsigned int)deny_mode, (unsigned int)open_attr,
+ (unsigned int)open_ofun, open_size));
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(fname, &twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ fname,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req,
+ NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (open_ofun == 0) {
+ reply_nterror(req, NT_STATUS_OBJECT_NAME_COLLISION);
+ goto out;
+ }
+
+ if (!map_open_params_to_ntcreate(smb_fname->base_name, deny_mode,
+ open_ofun,
+ &access_mask, &share_mode,
+ &create_disposition,
+ &create_options,
+ &private_flags)) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ goto out;
+ }
+
+ /* Any data in this call is an EA list. */
+ if (total_data && (total_data != 4)) {
+ if (total_data < 10) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ if (IVAL(pdata,0) > total_data) {
+ DEBUG(10,("call_trans2open: bad total data size (%u) > %u\n",
+ IVAL(pdata,0), (unsigned int)total_data));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ ea_list = read_ea_list(talloc_tos(), pdata + 4,
+ total_data - 4);
+ if (!ea_list) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ if (!lp_ea_support(SNUM(conn))) {
+ reply_nterror(req, NT_STATUS_EAS_NOT_SUPPORTED);
+ goto out;
+ }
+
+ if (!req->posix_pathnames &&
+ ea_list_has_invalid_name(ea_list)) {
+ int param_len = 30;
+ *pparams = (char *)SMB_REALLOC(*pparams, param_len);
+ if(*pparams == NULL ) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ params = *pparams;
+ memset(params, '\0', param_len);
+ send_trans2_replies(conn, req, STATUS_INVALID_EA_NAME,
+ params, param_len, NULL, 0, max_data_bytes);
+ goto out;
+ }
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_fname, /* fname */
+ access_mask, /* access_mask */
+ share_mode, /* share_access */
+ create_disposition, /* create_disposition*/
+ create_options, /* create_options */
+ open_attr, /* file_attributes */
+ oplock_request, /* oplock_request */
+ NULL, /* lease */
+ open_size, /* allocation_size */
+ private_flags,
+ NULL, /* sd */
+ ea_list, /* ea_list */
+ &fsp, /* result */
+ &smb_action, /* psbuf */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->xconn, req->mid)) {
+ /* We have re-scheduled this call. */
+ goto out;
+ }
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ reply_openerror(req, status);
+ goto out;
+ }
+
+ fsp = fcb_or_dos_open(
+ req,
+ smb_fname,
+ access_mask,
+ create_options,
+ private_flags);
+ if (fsp == NULL) {
+ bool ok = defer_smb1_sharing_violation(req);
+ if (ok) {
+ goto out;
+ }
+ reply_openerror(req, status);
+ goto out;
+ }
+
+ smb_action = FILE_WAS_OPENED;
+ }
+
+ size = get_file_size_stat(&smb_fname->st);
+ fattr = fdos_mode(fsp);
+ inode = smb_fname->st.st_ex_ino;
+ if (fattr & FILE_ATTRIBUTE_DIRECTORY) {
+ close_file_free(req, &fsp, ERROR_CLOSE);
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ goto out;
+ }
+
+ /* Realloc the size of parameters and data we will return */
+ *pparams = (char *)SMB_REALLOC(*pparams, 30);
+ if(*pparams == NULL ) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ params = *pparams;
+
+ SSVAL(params,0,fsp->fnum);
+ SSVAL(params,2,fattr);
+ srv_put_dos_date2_ts(params, 4, smb_fname->st.st_ex_mtime);
+ SIVAL(params,8, (uint32_t)size);
+ SSVAL(params,12,deny_mode);
+ SSVAL(params,14,0); /* open_type - file or directory. */
+ SSVAL(params,16,0); /* open_state - only valid for IPC device. */
+
+ if (oplock_request && lp_fake_oplocks(SNUM(conn))) {
+ smb_action |= EXTENDED_OPLOCK_GRANTED;
+ }
+
+ SSVAL(params,18,smb_action);
+
+ /*
+ * WARNING - this may need to be changed if SMB_INO_T <> 4 bytes.
+ */
+ SIVAL(params,20,inode);
+ SSVAL(params,24,0); /* Padding. */
+ if (flags & 8) {
+ uint32_t ea_size = estimate_ea_size(smb_fname->fsp);
+ SIVAL(params, 26, ea_size);
+ } else {
+ SIVAL(params, 26, 0);
+ }
+
+ /* Send the required number of replies */
+ send_trans2_replies(conn, req, NT_STATUS_OK, params, 30, *ppdata, 0, max_data_bytes);
+ out:
+ TALLOC_FREE(smb_fname);
+}
+
+static NTSTATUS get_lanman2_dir_entry(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct dptr_struct *dirptr,
+ uint16_t flags2,
+ const char *path_mask,
+ uint32_t dirtype,
+ int info_level,
+ bool requires_resume_key,
+ bool dont_descend,
+ bool ask_sharemode,
+ char **ppdata,
+ char *base_data,
+ char *end_data,
+ int space_remaining,
+ int *last_entry_off,
+ struct ea_list *name_list)
+{
+ uint8_t align = 4;
+ const bool do_pad = true;
+
+ if (info_level >= 1 && info_level <= 3) {
+ /* No alignment on earlier info levels. */
+ align = 1;
+ }
+
+ return smbd_dirptr_lanman2_entry(ctx, conn, dirptr, flags2,
+ path_mask, dirtype, info_level,
+ requires_resume_key, dont_descend, ask_sharemode,
+ true, align, do_pad,
+ ppdata, base_data, end_data,
+ space_remaining,
+ NULL,
+ last_entry_off, name_list, NULL);
+}
+
+/****************************************************************************
+ Reply to a TRANS2_FINDFIRST.
+****************************************************************************/
+
+static void call_trans2findfirst(connection_struct *conn,
+ struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ /* We must be careful here that we don't return more than the
+ allowed number of data bytes. If this means returning fewer than
+ maxentries then so be it. We assume that the redirector has
+ enough room for the fixed number of parameter bytes it has
+ requested. */
+ struct smb_filename *smb_dname = NULL;
+ char *params = *pparams;
+ char *pdata = *ppdata;
+ char *data_end;
+ uint32_t dirtype;
+ int maxentries;
+ uint16_t findfirst_flags;
+ bool close_after_first;
+ bool close_if_end;
+ bool requires_resume_key;
+ int info_level;
+ char *directory = NULL;
+ char *mask = NULL;
+ char *p;
+ int last_entry_off=0;
+ int dptr_num = -1;
+ int numentries = 0;
+ int i;
+ bool finished = False;
+ bool dont_descend = False;
+ bool out_of_space = False;
+ int space_remaining;
+ struct ea_list *ea_list = NULL;
+ NTSTATUS ntstatus = NT_STATUS_OK;
+ bool ask_sharemode;
+ struct smbXsrv_connection *xconn = req->xconn;
+ struct smbd_server_connection *sconn = req->sconn;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+ bool backup_priv = false;
+ bool as_root = false;
+ files_struct *fsp = NULL;
+ struct files_struct *dirfsp = NULL;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ if (total_params < 13) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ dirtype = SVAL(params,0);
+ maxentries = SVAL(params,2);
+ findfirst_flags = SVAL(params,4);
+ close_after_first = (findfirst_flags & FLAG_TRANS2_FIND_CLOSE);
+ close_if_end = (findfirst_flags & FLAG_TRANS2_FIND_CLOSE_IF_END);
+ requires_resume_key = (findfirst_flags & FLAG_TRANS2_FIND_REQUIRE_RESUME);
+ backup_priv = ((findfirst_flags & FLAG_TRANS2_FIND_BACKUP_INTENT) &&
+ security_token_has_privilege(get_current_nttok(conn),
+ SEC_PRIV_BACKUP));
+
+ info_level = SVAL(params,6);
+
+ DBG_NOTICE("dirtype = %"PRIx32", maxentries = %d, "
+ "close_after_first=%d, close_if_end = %d "
+ "requires_resume_key = %d backup_priv = %d level = 0x%x, "
+ "max_data_bytes = %d\n",
+ dirtype,
+ maxentries,
+ close_after_first,
+ close_if_end,
+ requires_resume_key,
+ backup_priv,
+ info_level,
+ max_data_bytes);
+
+ if (!maxentries) {
+ /* W2K3 seems to treat zero as 1. */
+ maxentries = 1;
+ }
+
+ switch (info_level) {
+ case SMB_FIND_INFO_STANDARD:
+ case SMB_FIND_EA_SIZE:
+ case SMB_FIND_EA_LIST:
+ case SMB_FIND_FILE_DIRECTORY_INFO:
+ case SMB_FIND_FILE_FULL_DIRECTORY_INFO:
+ case SMB_FIND_FILE_NAMES_INFO:
+ case SMB_FIND_FILE_BOTH_DIRECTORY_INFO:
+ case SMB_FIND_ID_FULL_DIRECTORY_INFO:
+ case SMB_FIND_ID_BOTH_DIRECTORY_INFO:
+ break;
+ case SMB_FIND_FILE_UNIX:
+ case SMB_FIND_FILE_UNIX_INFO2:
+ if (!lp_smb1_unix_extensions()) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ goto out;
+ }
+ if (!req->posix_pathnames) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ goto out;
+ }
+ break;
+ default:
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ goto out;
+ }
+
+ if (req->posix_pathnames) {
+ srvstr_get_path_posix(talloc_tos(),
+ params,
+ req->flags2,
+ &directory,
+ params+12,
+ total_params - 12,
+ STR_TERMINATE,
+ &ntstatus);
+ } else {
+ srvstr_get_path(talloc_tos(),
+ params,
+ req->flags2,
+ &directory,
+ params+12,
+ total_params - 12,
+ STR_TERMINATE,
+ &ntstatus);
+ }
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ reply_nterror(req, ntstatus);
+ goto out;
+ }
+
+ if (backup_priv) {
+ become_root();
+ as_root = true;
+ }
+ ntstatus = smb1_strip_dfs_path(talloc_tos(), &ucf_flags, &directory);
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ reply_nterror(req, ntstatus);
+ goto out;
+ }
+
+ ntstatus = filename_convert_smb1_search_path(talloc_tos(),
+ conn,
+ directory,
+ ucf_flags,
+ &dirfsp,
+ &smb_dname,
+ &mask);
+
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ if (NT_STATUS_EQUAL(ntstatus,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, ntstatus);
+ goto out;
+ }
+
+ TALLOC_FREE(directory);
+ directory = smb_dname->base_name;
+
+ DEBUG(5,("dir=%s, mask = %s\n",directory, mask));
+
+ if (info_level == SMB_FIND_EA_LIST) {
+ uint32_t ea_size;
+
+ if (total_data < 4) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ ea_size = IVAL(pdata,0);
+ if (ea_size != total_data) {
+ DBG_NOTICE("Rejecting EA request with incorrect "
+ "total_data=%d (should be %" PRIu32 ")\n",
+ total_data,
+ ea_size);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ if (!lp_ea_support(SNUM(conn))) {
+ reply_nterror(req, NT_STATUS_EAS_NOT_SUPPORTED);
+ goto out;
+ }
+
+ /* Pull out the list of names. */
+ ea_list = read_ea_name_list(talloc_tos(), pdata + 4, ea_size - 4);
+ if (!ea_list) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+ }
+
+ if (max_data_bytes + DIR_ENTRY_SAFETY_MARGIN < max_data_bytes) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ *ppdata = (char *)SMB_REALLOC(
+ *ppdata, max_data_bytes + DIR_ENTRY_SAFETY_MARGIN);
+ if(*ppdata == NULL ) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ pdata = *ppdata;
+ data_end = pdata + max_data_bytes + DIR_ENTRY_SAFETY_MARGIN - 1;
+ /*
+ * squash valgrind "writev(vector[...]) points to uninitialised byte(s)"
+ * error.
+ */
+ memset(pdata + total_data, 0, ((max_data_bytes + DIR_ENTRY_SAFETY_MARGIN) - total_data));
+ /* Realloc the params space */
+ *pparams = (char *)SMB_REALLOC(*pparams, 10);
+ if (*pparams == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ params = *pparams;
+
+ /*
+ * Open an fsp on this directory for the dptr.
+ */
+ ntstatus = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_dname, /* dname */
+ FILE_LIST_DIRECTORY, /* access_mask */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE, /* share_access */
+ FILE_OPEN, /* create_disposition*/
+ FILE_DIRECTORY_FILE, /* create_options */
+ FILE_ATTRIBUTE_DIRECTORY,/* file_attributes */
+ NO_OPLOCK, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* pinfo */
+ NULL, /* in_context */
+ NULL);/* out_context */
+
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ DBG_ERR("failed to open directory %s\n",
+ smb_fname_str_dbg(smb_dname));
+ reply_nterror(req, ntstatus);
+ goto out;
+ }
+
+ /* Save the wildcard match and attribs we are using on this directory -
+ needed as lanman2 assumes these are being saved between calls */
+
+ ntstatus = dptr_create(conn,
+ req,
+ fsp, /* fsp */
+ False,
+ mask,
+ dirtype,
+ &fsp->dptr);
+
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ /*
+ * Use NULL here for the first parameter (req)
+ * as this is not a client visible handle so
+ * can't be part of an SMB1 chain.
+ */
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ reply_nterror(req, ntstatus);
+ goto out;
+ }
+
+ if (backup_priv) {
+ /* Remember this in case we have
+ to do a findnext. */
+ dptr_set_priv(fsp->dptr);
+ }
+
+ dptr_num = dptr_dnum(fsp->dptr);
+ DEBUG(4,("dptr_num is %d, wcard = %s, attr = %d\n", dptr_num, mask, dirtype));
+
+ /* We don't need to check for VOL here as this is returned by
+ a different TRANS2 call. */
+
+ DEBUG(8,("dirpath=<%s> dontdescend=<%s>\n",
+ directory,lp_dont_descend(talloc_tos(), lp_sub, SNUM(conn))));
+ if (in_list(directory,
+ lp_dont_descend(talloc_tos(), lp_sub, SNUM(conn)),
+ dptr_case_sensitive(fsp->dptr))) {
+ dont_descend = True;
+ }
+
+ p = pdata;
+ space_remaining = max_data_bytes;
+ out_of_space = False;
+
+ ask_sharemode = fsp_search_ask_sharemode(fsp);
+
+ for (i=0;(i<maxentries) && !finished && !out_of_space;i++) {
+
+ ntstatus = get_lanman2_dir_entry(talloc_tos(),
+ conn,
+ fsp->dptr,
+ req->flags2,
+ mask,
+ dirtype,
+ info_level,
+ requires_resume_key,
+ dont_descend,
+ ask_sharemode,
+ &p,
+ pdata,
+ data_end,
+ space_remaining,
+ &last_entry_off,
+ ea_list);
+ if (NT_STATUS_EQUAL(ntstatus, NT_STATUS_ILLEGAL_CHARACTER)) {
+ /*
+ * Bad character conversion on name. Ignore
+ * this entry.
+ */
+ continue;
+ }
+ if (NT_STATUS_EQUAL(ntstatus, STATUS_MORE_ENTRIES)) {
+ out_of_space = true;
+ } else {
+ finished = !NT_STATUS_IS_OK(ntstatus);
+ }
+
+ if (!finished && !out_of_space) {
+ numentries++;
+ }
+
+ /* Ensure space_remaining never goes -ve. */
+ if (PTR_DIFF(p,pdata) > max_data_bytes) {
+ space_remaining = 0;
+ out_of_space = true;
+ } else {
+ space_remaining = max_data_bytes - PTR_DIFF(p,pdata);
+ }
+ }
+
+ /* Check if we can close the dirptr */
+ if(close_after_first || (finished && close_if_end)) {
+ DEBUG(5,("call_trans2findfirst - (2) closing dptr_num %d\n", dptr_num));
+ dptr_num = -1;
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ }
+
+ /*
+ * If there are no matching entries we must return ERRDOS/ERRbadfile -
+ * from observation of NT. NB. This changes to ERRDOS,ERRnofiles if
+ * the protocol level is less than NT1. Tested with smbclient. JRA.
+ * This should fix the OS/2 client bug #2335.
+ */
+
+ if(numentries == 0) {
+ dptr_num = -1;
+ /*
+ * We may have already closed the file in the
+ * close_after_first or finished case above.
+ */
+ if (fsp != NULL) {
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ }
+ if (xconn->protocol < PROTOCOL_NT1) {
+ reply_force_doserror(req, ERRDOS, ERRnofiles);
+ goto out;
+ } else {
+ reply_botherror(req, NT_STATUS_NO_SUCH_FILE,
+ ERRDOS, ERRbadfile);
+ goto out;
+ }
+ }
+
+ /* At this point pdata points to numentries directory entries. */
+
+ /* Set up the return parameter block */
+ SSVAL(params,0,dptr_num);
+ SSVAL(params,2,numentries);
+ SSVAL(params,4,finished);
+ SSVAL(params,6,0); /* Never an EA error */
+ SSVAL(params,8,last_entry_off);
+
+ send_trans2_replies(conn, req, NT_STATUS_OK, params, 10, pdata, PTR_DIFF(p,pdata),
+ max_data_bytes);
+
+ if ((! *directory) && dptr_path(sconn, dptr_num)) {
+ directory = talloc_strdup(talloc_tos(),dptr_path(sconn, dptr_num));
+ if (!directory) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ }
+ }
+
+ DEBUG( 4, ( "%s mask=%s directory=%s dirtype=%d numentries=%d\n",
+ smb_fn_name(req->cmd),
+ mask, directory, dirtype, numentries ) );
+
+ /*
+ * Force a name mangle here to ensure that the
+ * mask as an 8.3 name is top of the mangled cache.
+ * The reasons for this are subtle. Don't remove
+ * this code unless you know what you are doing
+ * (see PR#13758). JRA.
+ */
+
+ if(!mangle_is_8_3_wildcards( mask, False, conn->params)) {
+ char mangled_name[13];
+ name_to_8_3(mask, mangled_name, True, conn->params);
+ }
+ out:
+
+ if (as_root) {
+ unbecome_root();
+ }
+
+ TALLOC_FREE(smb_dname);
+ return;
+}
+
+static bool smbd_dptr_name_equal(struct dptr_struct *dptr,
+ const char *name1,
+ const char *name2)
+{
+ bool equal;
+
+ if (dptr_case_sensitive(dptr)) {
+ equal = (strcmp(name1, name2) == 0);
+ } else {
+ equal = strequal(name1, name2);
+ }
+
+ return equal;
+}
+
+/****************************************************************************
+ Reply to a TRANS2_FINDNEXT.
+****************************************************************************/
+
+static void call_trans2findnext(connection_struct *conn,
+ struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ /* We must be careful here that we don't return more than the
+ allowed number of data bytes. If this means returning fewer than
+ maxentries then so be it. We assume that the redirector has
+ enough room for the fixed number of parameter bytes it has
+ requested. */
+ char *params = *pparams;
+ char *pdata = *ppdata;
+ char *data_end;
+ int dptr_num;
+ int maxentries;
+ uint16_t info_level;
+ uint32_t resume_key;
+ uint16_t findnext_flags;
+ bool close_after_request;
+ bool close_if_end;
+ bool requires_resume_key;
+ bool continue_bit;
+ char *resume_name = NULL;
+ const char *mask = NULL;
+ const char *directory = NULL;
+ char *p = NULL;
+ uint16_t dirtype;
+ int numentries = 0;
+ int i, last_entry_off=0;
+ bool finished = False;
+ bool dont_descend = False;
+ bool out_of_space = False;
+ int space_remaining;
+ struct ea_list *ea_list = NULL;
+ NTSTATUS ntstatus = NT_STATUS_OK;
+ bool ask_sharemode;
+ TALLOC_CTX *ctx = talloc_tos();
+ struct smbd_server_connection *sconn = req->sconn;
+ bool backup_priv = false;
+ bool as_root = false;
+ files_struct *fsp = NULL;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ if (total_params < 13) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ dptr_num = SVAL(params,0);
+ maxentries = SVAL(params,2);
+ info_level = SVAL(params,4);
+ resume_key = IVAL(params,6);
+ findnext_flags = SVAL(params,10);
+ close_after_request = (findnext_flags & FLAG_TRANS2_FIND_CLOSE);
+ close_if_end = (findnext_flags & FLAG_TRANS2_FIND_CLOSE_IF_END);
+ requires_resume_key = (findnext_flags & FLAG_TRANS2_FIND_REQUIRE_RESUME);
+ continue_bit = (findnext_flags & FLAG_TRANS2_FIND_CONTINUE);
+
+ if (!continue_bit) {
+ /* We only need resume_name if continue_bit is zero. */
+ if (req->posix_pathnames) {
+ srvstr_get_path_posix(ctx,
+ params,
+ req->flags2,
+ &resume_name,
+ params+12,
+ total_params - 12,
+ STR_TERMINATE,
+ &ntstatus);
+ } else {
+ srvstr_get_path(ctx,
+ params,
+ req->flags2,
+ &resume_name,
+ params+12,
+ total_params - 12,
+ STR_TERMINATE,
+ &ntstatus);
+ }
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ /* Win9x or OS/2 can send a resume name of ".." or ".". This will cause the parser to
+ complain (it thinks we're asking for the directory above the shared
+ path or an invalid name). Catch this as the resume name is only compared, never used in
+ a file access. JRA. */
+ srvstr_pull_talloc(ctx, params, req->flags2,
+ &resume_name, params+12,
+ total_params - 12,
+ STR_TERMINATE);
+
+ if (!resume_name || !(ISDOT(resume_name) || ISDOTDOT(resume_name))) {
+ reply_nterror(req, ntstatus);
+ return;
+ }
+ }
+ }
+
+ DBG_NOTICE("dirhandle = %d, max_data_bytes = %u, maxentries = %d, "
+ "close_after_request=%d, close_if_end = %d "
+ "requires_resume_key = %d resume_key = %d "
+ "resume name = %s continue=%d level = %d\n",
+ dptr_num,
+ max_data_bytes,
+ maxentries,
+ close_after_request,
+ close_if_end,
+ requires_resume_key,
+ resume_key,
+ resume_name ? resume_name : "(NULL)",
+ continue_bit,
+ info_level);
+
+ if (!maxentries) {
+ /* W2K3 seems to treat zero as 1. */
+ maxentries = 1;
+ }
+
+ switch (info_level) {
+ case SMB_FIND_INFO_STANDARD:
+ case SMB_FIND_EA_SIZE:
+ case SMB_FIND_EA_LIST:
+ case SMB_FIND_FILE_DIRECTORY_INFO:
+ case SMB_FIND_FILE_FULL_DIRECTORY_INFO:
+ case SMB_FIND_FILE_NAMES_INFO:
+ case SMB_FIND_FILE_BOTH_DIRECTORY_INFO:
+ case SMB_FIND_ID_FULL_DIRECTORY_INFO:
+ case SMB_FIND_ID_BOTH_DIRECTORY_INFO:
+ break;
+ case SMB_FIND_FILE_UNIX:
+ case SMB_FIND_FILE_UNIX_INFO2:
+ if (!lp_smb1_unix_extensions()) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+ if (!req->posix_pathnames) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+ break;
+ default:
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ if (info_level == SMB_FIND_EA_LIST) {
+ uint32_t ea_size;
+
+ if (total_data < 4) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ ea_size = IVAL(pdata,0);
+ if (ea_size != total_data) {
+ DBG_NOTICE("Rejecting EA request with incorrect "
+ "total_data=%d (should be %" PRIu32 ")\n",
+ total_data,
+ ea_size);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if (!lp_ea_support(SNUM(conn))) {
+ reply_nterror(req, NT_STATUS_EAS_NOT_SUPPORTED);
+ return;
+ }
+
+ /* Pull out the list of names. */
+ ea_list = read_ea_name_list(ctx, pdata + 4, ea_size - 4);
+ if (!ea_list) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ }
+
+ if (max_data_bytes + DIR_ENTRY_SAFETY_MARGIN < max_data_bytes) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ *ppdata = (char *)SMB_REALLOC(
+ *ppdata, max_data_bytes + DIR_ENTRY_SAFETY_MARGIN);
+ if(*ppdata == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ pdata = *ppdata;
+ data_end = pdata + max_data_bytes + DIR_ENTRY_SAFETY_MARGIN - 1;
+
+ /*
+ * squash valgrind "writev(vector[...]) points to uninitialised byte(s)"
+ * error.
+ */
+ memset(pdata + total_data, 0, (max_data_bytes + DIR_ENTRY_SAFETY_MARGIN) - total_data);
+ /* Realloc the params space */
+ *pparams = (char *)SMB_REALLOC(*pparams, 6*SIZEOFWORD);
+ if(*pparams == NULL ) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ params = *pparams;
+
+ /* Check that the dptr is valid */
+ fsp = dptr_fetch_lanman2_fsp(sconn, dptr_num);
+ if (fsp == NULL) {
+ reply_nterror(req, STATUS_NO_MORE_FILES);
+ return;
+ }
+
+ directory = dptr_path(sconn, dptr_num);
+
+ /* Get the wildcard mask from the dptr */
+ if((mask = dptr_wcard(sconn, dptr_num))== NULL) {
+ DEBUG(2,("dptr_num %d has no wildcard\n", dptr_num));
+ reply_nterror(req, STATUS_NO_MORE_FILES);
+ return;
+ }
+
+ /* Get the attr mask from the dptr */
+ dirtype = dptr_attr(sconn, dptr_num);
+
+ backup_priv = dptr_get_priv(fsp->dptr);
+
+ DEBUG(3,("dptr_num is %d, mask = %s, attr = %x, dirptr=(0x%lX) "
+ "backup_priv = %d\n",
+ dptr_num, mask, dirtype,
+ (long)fsp->dptr,
+ (int)backup_priv));
+
+ /* We don't need to check for VOL here as this is returned by
+ a different TRANS2 call. */
+
+ DEBUG(8,("dirpath=<%s> dontdescend=<%s>\n",
+ directory,lp_dont_descend(ctx, lp_sub, SNUM(conn))));
+ if (in_list(directory,lp_dont_descend(ctx, lp_sub, SNUM(conn)),
+ dptr_case_sensitive(fsp->dptr)))
+ dont_descend = True;
+
+ p = pdata;
+ space_remaining = max_data_bytes;
+ out_of_space = False;
+
+ if (backup_priv) {
+ become_root();
+ as_root = true;
+ }
+
+ /*
+ * Seek to the correct position. We no longer use the resume key but
+ * depend on the last file name instead.
+ */
+
+ if(!continue_bit && resume_name && *resume_name) {
+ bool posix_open = (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN);
+ char *last_name_sent = NULL;
+ bool sequential;
+
+ /*
+ * Remember, name_to_8_3 is called by
+ * get_lanman2_dir_entry(), so the resume name
+ * could be mangled. Ensure we check the unmangled name.
+ */
+
+ if (!posix_open &&
+ mangle_is_mangled(resume_name, conn->params)) {
+ char *new_resume_name = NULL;
+ mangle_lookup_name_from_8_3(ctx,
+ resume_name,
+ &new_resume_name,
+ conn->params);
+ if (new_resume_name) {
+ resume_name = new_resume_name;
+ }
+ }
+
+ /*
+ * Fix for NT redirector problem triggered by resume key indexes
+ * changing between directory scans. We now return a resume key of 0
+ * and instead look for the filename to continue from (also given
+ * to us by NT/95/smbfs/smbclient). If no other scans have been done between the
+ * findfirst/findnext (as is usual) then the directory pointer
+ * should already be at the correct place.
+ */
+
+ last_name_sent = smbd_dirptr_get_last_name_sent(fsp->dptr);
+ sequential = smbd_dptr_name_equal(fsp->dptr,
+ resume_name,
+ last_name_sent);
+ if (!sequential) {
+ char *name = NULL;
+ bool found = false;
+
+ dptr_RewindDir(fsp->dptr);
+
+ while ((name = dptr_ReadDirName(talloc_tos(),
+ fsp->dptr)) != NULL) {
+ found = smbd_dptr_name_equal(fsp->dptr,
+ resume_name,
+ name);
+ TALLOC_FREE(name);
+ if (found) {
+ break;
+ }
+ }
+
+ if (!found) {
+ /*
+ * We got a name that used to exist
+ * but does not anymore. Just start
+ * from the beginning. Shown by the
+ * "raw.search.os2 delete" smbtorture
+ * test.
+ */
+ dptr_RewindDir(fsp->dptr);
+ }
+ }
+ } /* end if resume_name && !continue_bit */
+
+ ask_sharemode = fsp_search_ask_sharemode(fsp);
+
+ for (i=0;(i<(int)maxentries) && !finished && !out_of_space ;i++) {
+
+ ntstatus = get_lanman2_dir_entry(ctx,
+ conn,
+ fsp->dptr,
+ req->flags2,
+ mask,
+ dirtype,
+ info_level,
+ requires_resume_key,
+ dont_descend,
+ ask_sharemode,
+ &p,
+ pdata,
+ data_end,
+ space_remaining,
+ &last_entry_off,
+ ea_list);
+ if (NT_STATUS_EQUAL(ntstatus, NT_STATUS_ILLEGAL_CHARACTER)) {
+ /*
+ * Bad character conversion on name. Ignore
+ * this entry.
+ */
+ continue;
+ }
+ if (NT_STATUS_EQUAL(ntstatus, STATUS_MORE_ENTRIES)) {
+ out_of_space = true;
+ } else {
+ finished = !NT_STATUS_IS_OK(ntstatus);
+ }
+
+ if (!finished && !out_of_space) {
+ numentries++;
+ }
+
+ space_remaining = max_data_bytes - PTR_DIFF(p,pdata);
+ }
+
+ DEBUG( 3, ( "%s mask=%s directory=%s dirtype=%d numentries=%d\n",
+ smb_fn_name(req->cmd),
+ mask, directory, dirtype, numentries ) );
+
+ /* Check if we can close the fsp->dptr */
+ if(close_after_request || (finished && close_if_end)) {
+ DBG_INFO("closing dptr_num = %d\n", dptr_num);
+ dptr_num = -1;
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ }
+
+ if (as_root) {
+ unbecome_root();
+ }
+
+ /* Set up the return parameter block */
+ SSVAL(params,0,numentries);
+ SSVAL(params,2,finished);
+ SSVAL(params,4,0); /* Never an EA error */
+ SSVAL(params,6,last_entry_off);
+
+ send_trans2_replies(conn, req, NT_STATUS_OK, params, 8, pdata, PTR_DIFF(p,pdata),
+ max_data_bytes);
+
+ return;
+}
+
+/****************************************************************************
+ Reply to a TRANS2_QFSINFO (query filesystem info).
+****************************************************************************/
+
+static void call_trans2qfsinfo(connection_struct *conn,
+ struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ char *params = *pparams;
+ uint16_t info_level;
+ int data_len = 0;
+ size_t fixed_portion;
+ NTSTATUS status;
+
+ if (total_params < 2) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ info_level = SVAL(params,0);
+
+ if (ENCRYPTION_REQUIRED(conn) && !req->encrypted) {
+ if (info_level != SMB_QUERY_CIFS_UNIX_INFO) {
+ DEBUG(0,("call_trans2qfsinfo: encryption required "
+ "and info level 0x%x sent.\n",
+ (unsigned int)info_level));
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+ }
+
+ DEBUG(3,("call_trans2qfsinfo: level = %d\n", info_level));
+
+ status = smbd_do_qfsinfo(req->xconn, conn, req,
+ info_level,
+ req->flags2,
+ max_data_bytes,
+ &fixed_portion,
+ NULL,
+ NULL,
+ ppdata, &data_len);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ send_trans2_replies(conn, req, NT_STATUS_OK, params, 0, *ppdata, data_len,
+ max_data_bytes);
+
+ DEBUG( 4, ( "%s info_level = %d\n",
+ smb_fn_name(req->cmd), info_level) );
+
+ return;
+}
+
+/****************************************************************************
+ Reply to a TRANS2_SETFSINFO (set filesystem info).
+****************************************************************************/
+
+static void call_trans2setfsinfo(connection_struct *conn,
+ struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct smbXsrv_connection *xconn = req->xconn;
+ char *pdata = *ppdata;
+ char *params = *pparams;
+ uint16_t info_level;
+
+ DEBUG(10,("call_trans2setfsinfo: for service [%s]\n",
+ lp_servicename(talloc_tos(), lp_sub, SNUM(conn))));
+
+ /* */
+ if (total_params < 4) {
+ DEBUG(0,("call_trans2setfsinfo: requires total_params(%d) >= 4 bytes!\n",
+ total_params));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ info_level = SVAL(params,2);
+
+ if (IS_IPC(conn)) {
+ if (info_level != SMB_REQUEST_TRANSPORT_ENCRYPTION &&
+ info_level != SMB_SET_CIFS_UNIX_INFO) {
+ DEBUG(0,("call_trans2setfsinfo: not an allowed "
+ "info level (0x%x) on IPC$.\n",
+ (unsigned int)info_level));
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+ }
+
+ if (ENCRYPTION_REQUIRED(conn) && !req->encrypted) {
+ if (info_level != SMB_REQUEST_TRANSPORT_ENCRYPTION) {
+ DEBUG(0,("call_trans2setfsinfo: encryption required "
+ "and info level 0x%x sent.\n",
+ (unsigned int)info_level));
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+ }
+
+ switch(info_level) {
+ case SMB_SET_CIFS_UNIX_INFO:
+ if (!lp_smb1_unix_extensions()) {
+ DEBUG(2,("call_trans2setfsinfo: "
+ "SMB_SET_CIFS_UNIX_INFO is invalid with "
+ "unix extensions off\n"));
+ reply_nterror(req,
+ NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ /* There should be 12 bytes of capabilities set. */
+ if (total_data < 12) {
+ reply_nterror(
+ req,
+ NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ xconn->smb1.unix_info.client_major = SVAL(pdata,0);
+ xconn->smb1.unix_info.client_minor = SVAL(pdata,2);
+ xconn->smb1.unix_info.client_cap_low = IVAL(pdata,4);
+ xconn->smb1.unix_info.client_cap_high = IVAL(pdata,8);
+
+ /* Just print these values for now. */
+ DBG_DEBUG("set unix_info info. "
+ "major = %"PRIu16", minor = %"PRIu16
+ "cap_low = 0x%"PRIx32", "
+ "cap_high = 0x%"PRIx32"\n",
+ xconn->smb1.unix_info.client_major,
+ xconn->smb1.unix_info.client_minor,
+ xconn->smb1.unix_info.client_cap_low,
+ xconn->smb1.unix_info.client_cap_high);
+
+ /*
+ * Here is where we must switch to posix
+ * pathname processing...
+ */
+ if (xconn->smb1.unix_info.client_cap_low &
+ CIFS_UNIX_POSIX_PATHNAMES_CAP)
+ {
+ lp_set_posix_pathnames();
+ mangle_change_to_posix();
+ }
+
+ if ((xconn->smb1.unix_info.client_cap_low &
+ CIFS_UNIX_FCNTL_LOCKS_CAP) &&
+ !(xconn->smb1.unix_info.client_cap_low &
+ CIFS_UNIX_POSIX_PATH_OPERATIONS_CAP))
+ {
+ /* Client that knows how to do posix locks,
+ * but not posix open/mkdir operations. Set a
+ * default type for read/write checks. */
+
+ lp_set_posix_default_cifsx_readwrite_locktype(
+ POSIX_LOCK);
+
+ }
+ break;
+
+ case SMB_REQUEST_TRANSPORT_ENCRYPTION:
+ {
+ NTSTATUS status;
+ size_t param_len = 0;
+ size_t data_len = total_data;
+
+ if (!lp_smb1_unix_extensions()) {
+ reply_nterror(
+ req,
+ NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ if (lp_server_smb_encrypt(SNUM(conn)) ==
+ SMB_ENCRYPTION_OFF) {
+ reply_nterror(
+ req,
+ NT_STATUS_NOT_SUPPORTED);
+ return;
+ }
+
+ if (xconn->smb1.echo_handler.trusted_fde) {
+ DEBUG( 2,("call_trans2setfsinfo: "
+ "request transport encryption disabled"
+ "with 'fork echo handler = yes'\n"));
+ reply_nterror(
+ req,
+ NT_STATUS_NOT_SUPPORTED);
+ return;
+ }
+
+ DEBUG( 4,("call_trans2setfsinfo: "
+ "request transport encryption.\n"));
+
+ status = srv_request_encryption_setup(conn,
+ (unsigned char **)ppdata,
+ &data_len,
+ (unsigned char **)pparams,
+ &param_len);
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED) &&
+ !NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ send_trans2_replies(conn, req,
+ NT_STATUS_OK,
+ *pparams,
+ param_len,
+ *ppdata,
+ data_len,
+ max_data_bytes);
+
+ if (NT_STATUS_IS_OK(status)) {
+ /* Server-side transport
+ * encryption is now *on*. */
+ status = srv_encryption_start(conn);
+ if (!NT_STATUS_IS_OK(status)) {
+ char *reason = talloc_asprintf(talloc_tos(),
+ "Failure in setting "
+ "up encrypted transport: %s",
+ nt_errstr(status));
+ exit_server_cleanly(reason);
+ }
+ }
+ return;
+ }
+
+ case SMB_FS_QUOTA_INFORMATION:
+ {
+ NTSTATUS status;
+ DATA_BLOB qdata = {
+ .data = (uint8_t *)pdata,
+ .length = total_data
+ };
+ files_struct *fsp = NULL;
+ fsp = file_fsp(req, SVAL(params,0));
+
+ status = smb_set_fsquota(conn,
+ req,
+ fsp,
+ &qdata);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+ break;
+ }
+ default:
+ DEBUG(3,("call_trans2setfsinfo: unknown level (0x%X) not implemented yet.\n",
+ info_level));
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ break;
+ }
+
+ /*
+ * sending this reply works fine,
+ * but I'm not sure it's the same
+ * like windows do...
+ * --metze
+ */
+ reply_smb1_outbuf(req, 10, 0);
+}
+
+/****************************************************************************
+ Reply to a TRANSACT2_QFILEINFO on a PIPE !
+****************************************************************************/
+
+static void call_trans2qpipeinfo(connection_struct *conn,
+ struct smb_request *req,
+ files_struct *fsp,
+ uint16_t info_level,
+ unsigned int tran_call,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ char *params = *pparams;
+ char *pdata = *ppdata;
+ unsigned int data_size = 0;
+ unsigned int param_size = 2;
+
+ if (!fsp_is_np(fsp)) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ *pparams = (char *)SMB_REALLOC(*pparams,2);
+ if (*pparams == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ params = *pparams;
+ SSVAL(params,0,0);
+ if (max_data_bytes + DIR_ENTRY_SAFETY_MARGIN < max_data_bytes) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ data_size = max_data_bytes + DIR_ENTRY_SAFETY_MARGIN;
+ *ppdata = (char *)SMB_REALLOC(*ppdata, data_size);
+ if (*ppdata == NULL ) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ pdata = *ppdata;
+
+ switch (info_level) {
+ case SMB_FILE_STANDARD_INFORMATION:
+ memset(pdata,0,24);
+ SOFF_T(pdata,0,4096LL);
+ SIVAL(pdata,16,1);
+ SIVAL(pdata,20,1);
+ data_size = 24;
+ break;
+
+ default:
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ send_trans2_replies(conn, req, NT_STATUS_OK, params, param_size, *ppdata, data_size,
+ max_data_bytes);
+}
+
+static void handle_trans2qfilepathinfo_result(
+ connection_struct *conn,
+ struct smb_request *req,
+ uint16_t info_level,
+ NTSTATUS status,
+ char *pdata,
+ int data_return_size,
+ size_t fixed_portion,
+ unsigned int max_data_bytes)
+{
+ char params[2] = { 0, 0, };
+ int param_size = 2;
+
+ /*
+ * draft-leach-cifs-v1-spec-02.txt
+ * 4.2.14 TRANS2_QUERY_PATH_INFORMATION: Get File Attributes given Path
+ * says:
+ *
+ * The requested information is placed in the Data portion of the
+ * transaction response. For the information levels greater than 0x100,
+ * the transaction response has 1 parameter word which should be
+ * ignored by the client.
+ *
+ * However Windows only follows this rule for the IS_NAME_VALID call.
+ */
+ switch (info_level) {
+ case SMB_INFO_IS_NAME_VALID:
+ param_size = 0;
+ break;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->xconn, req->mid)) {
+ /* We have re-scheduled this call. */
+ return;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ bool ok = defer_smb1_sharing_violation(req);
+ if (ok) {
+ return;
+ }
+ }
+ reply_nterror(req, status);
+ return;
+ }
+
+ if (fixed_portion > max_data_bytes) {
+ reply_nterror(req, NT_STATUS_INFO_LENGTH_MISMATCH);
+ return;
+ }
+
+ send_trans2_replies(
+ conn,
+ req,
+ NT_STATUS_OK,
+ params,
+ param_size,
+ pdata,
+ data_return_size,
+ max_data_bytes);
+}
+
+/****************************************************************************
+ Reply to a TRANS2_QFILEPATHINFO or TRANSACT2_QFILEINFO (query file info by
+ file name or file id).
+****************************************************************************/
+
+static void call_trans2qfilepathinfo(connection_struct *conn,
+ struct smb_request *req,
+ unsigned int tran_call,
+ uint16_t info_level,
+ struct smb_filename *smb_fname,
+ struct files_struct *fsp,
+ bool delete_pending,
+ struct timespec write_time_ts,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ char *params = *pparams;
+ char *pdata = *ppdata;
+ unsigned int data_size = 0;
+ struct ea_list *ea_list = NULL;
+ size_t fixed_portion;
+ NTSTATUS status = NT_STATUS_OK;
+
+ DEBUG(3,("call_trans2qfilepathinfo %s (%s) level=%d call=%d "
+ "total_data=%d\n", smb_fname_str_dbg(smb_fname),
+ fsp_fnum_dbg(fsp),
+ info_level,tran_call,total_data));
+
+ /* Pull out any data sent here before we realloc. */
+ switch (info_level) {
+ case SMB_INFO_QUERY_EAS_FROM_LIST:
+ {
+ /* Pull any EA list from the data portion. */
+ uint32_t ea_size;
+
+ if (total_data < 4) {
+ reply_nterror(
+ req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ ea_size = IVAL(pdata,0);
+
+ if (total_data > 0 && ea_size != total_data) {
+ DEBUG(4,("call_trans2qfilepathinfo: Rejecting EA request with incorrect \
+total_data=%u (should be %u)\n", (unsigned int)total_data, (unsigned int)IVAL(pdata,0) ));
+ reply_nterror(
+ req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if (!lp_ea_support(SNUM(conn))) {
+ reply_nterror(req, NT_STATUS_EAS_NOT_SUPPORTED);
+ return;
+ }
+
+ /* Pull out the list of names. */
+ ea_list = read_ea_name_list(req, pdata + 4, ea_size - 4);
+ if (!ea_list) {
+ reply_nterror(
+ req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ *pparams = (char *)SMB_REALLOC(*pparams,2);
+ if (*pparams == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ params = *pparams;
+ SSVAL(params,0,0);
+
+ if ((info_level & SMB2_INFO_SPECIAL) == SMB2_INFO_SPECIAL) {
+ /*
+ * We use levels that start with 0xFF00
+ * internally to represent SMB2 specific levels
+ */
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ status = smbd_do_qfilepathinfo(conn, req, req, info_level,
+ fsp, smb_fname,
+ delete_pending, write_time_ts,
+ ea_list,
+ req->flags2, max_data_bytes,
+ &fixed_portion,
+ ppdata, &data_size);
+
+ handle_trans2qfilepathinfo_result(
+ conn,
+ req,
+ info_level,
+ status,
+ *ppdata,
+ data_size,
+ fixed_portion,
+ max_data_bytes);
+}
+
+static NTSTATUS smb_q_unix_basic(
+ struct connection_struct *conn,
+ struct smb_request *req,
+ struct smb_filename *smb_fname,
+ struct files_struct *fsp,
+ char **ppdata,
+ int *ptotal_data)
+{
+ const int total_data = 100;
+
+ *ppdata = SMB_REALLOC(*ppdata, total_data);
+ if (*ppdata == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ store_file_unix_basic(conn, *ppdata, fsp, &smb_fname->st);
+
+ *ptotal_data = total_data;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smb_q_unix_info2(
+ struct connection_struct *conn,
+ struct smb_request *req,
+ struct smb_filename *smb_fname,
+ struct files_struct *fsp,
+ char **ppdata,
+ int *ptotal_data)
+{
+ const int total_data = 116;
+
+ *ppdata = SMB_REALLOC(*ppdata, total_data);
+ if (*ppdata == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ store_file_unix_basic_info2(conn, *ppdata, fsp, &smb_fname->st);
+
+ *ptotal_data = total_data;
+
+ return NT_STATUS_OK;
+}
+
+#if defined(HAVE_POSIX_ACLS)
+/****************************************************************************
+ Utility function to open a fsp for a POSIX handle operation.
+****************************************************************************/
+
+static NTSTATUS get_posix_fsp(connection_struct *conn,
+ struct smb_request *req,
+ struct smb_filename *smb_fname,
+ uint32_t access_mask,
+ files_struct **ret_fsp)
+{
+ NTSTATUS status;
+ uint32_t create_disposition = FILE_OPEN;
+ uint32_t share_access = FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE;
+ struct smb2_create_blobs *posx = NULL;
+
+ /*
+ * Only FILE_FLAG_POSIX_SEMANTICS matters on existing files,
+ * but set reasonable defaults.
+ */
+ uint32_t file_attributes = 0664;
+ uint32_t oplock = NO_OPLOCK;
+ uint32_t create_options = FILE_NON_DIRECTORY_FILE;
+
+ /* File or directory must exist. */
+ if (!VALID_STAT(smb_fname->st)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ /* Cannot be a symlink. */
+ if (S_ISLNK(smb_fname->st.st_ex_mode)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ /* Set options correctly for directory open. */
+ if (S_ISDIR(smb_fname->st.st_ex_mode)) {
+ /*
+ * Only FILE_FLAG_POSIX_SEMANTICS matters on existing
+ * directories, but set reasonable defaults.
+ */
+ file_attributes = 0775;
+ create_options = FILE_DIRECTORY_FILE;
+ }
+
+ status = make_smb2_posix_create_ctx(
+ talloc_tos(), &posx, file_attributes);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("make_smb2_posix_create_ctx failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ NULL, /* dirfsp */
+ smb_fname, /* fname */
+ access_mask, /* access_mask */
+ share_access, /* share_access */
+ create_disposition,/* create_disposition*/
+ create_options, /* create_options */
+ file_attributes,/* file_attributes */
+ oplock, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ ret_fsp, /* result */
+ NULL, /* pinfo */
+ posx, /* in_context */
+ NULL); /* out_context */
+
+done:
+ TALLOC_FREE(posx);
+ return status;
+}
+
+/****************************************************************************
+ Utility function to count the number of entries in a POSIX acl.
+****************************************************************************/
+
+static unsigned int count_acl_entries(connection_struct *conn, SMB_ACL_T posix_acl)
+{
+ unsigned int ace_count = 0;
+ int entry_id = SMB_ACL_FIRST_ENTRY;
+ SMB_ACL_ENTRY_T entry;
+
+ while ( posix_acl && (sys_acl_get_entry(posix_acl, entry_id, &entry) == 1)) {
+ entry_id = SMB_ACL_NEXT_ENTRY;
+ ace_count++;
+ }
+ return ace_count;
+}
+
+/****************************************************************************
+ Utility function to marshall a POSIX acl into wire format.
+****************************************************************************/
+
+static bool marshall_posix_acl(connection_struct *conn, char *pdata, SMB_STRUCT_STAT *pst, SMB_ACL_T posix_acl)
+{
+ int entry_id = SMB_ACL_FIRST_ENTRY;
+ SMB_ACL_ENTRY_T entry;
+
+ while ( posix_acl && (sys_acl_get_entry(posix_acl, entry_id, &entry) == 1)) {
+ SMB_ACL_TAG_T tagtype;
+ SMB_ACL_PERMSET_T permset;
+ unsigned char perms = 0;
+ unsigned int own_grp;
+
+ entry_id = SMB_ACL_NEXT_ENTRY;
+
+ if (sys_acl_get_tag_type(entry, &tagtype) == -1) {
+ DEBUG(0,("marshall_posix_acl: SMB_VFS_SYS_ACL_GET_TAG_TYPE failed.\n"));
+ return False;
+ }
+
+ if (sys_acl_get_permset(entry, &permset) == -1) {
+ DEBUG(0,("marshall_posix_acl: SMB_VFS_SYS_ACL_GET_PERMSET failed.\n"));
+ return False;
+ }
+
+ perms |= (sys_acl_get_perm(permset, SMB_ACL_READ) ? SMB_POSIX_ACL_READ : 0);
+ perms |= (sys_acl_get_perm(permset, SMB_ACL_WRITE) ? SMB_POSIX_ACL_WRITE : 0);
+ perms |= (sys_acl_get_perm(permset, SMB_ACL_EXECUTE) ? SMB_POSIX_ACL_EXECUTE : 0);
+
+ SCVAL(pdata,1,perms);
+
+ switch (tagtype) {
+ case SMB_ACL_USER_OBJ:
+ SCVAL(pdata,0,SMB_POSIX_ACL_USER_OBJ);
+ own_grp = (unsigned int)pst->st_ex_uid;
+ SIVAL(pdata,2,own_grp);
+ SIVAL(pdata,6,0);
+ break;
+ case SMB_ACL_USER:
+ {
+ uid_t *puid = (uid_t *)sys_acl_get_qualifier(entry);
+ if (!puid) {
+ DEBUG(0,("marshall_posix_acl: SMB_VFS_SYS_ACL_GET_QUALIFIER failed.\n"));
+ return False;
+ }
+ own_grp = (unsigned int)*puid;
+ SCVAL(pdata,0,SMB_POSIX_ACL_USER);
+ SIVAL(pdata,2,own_grp);
+ SIVAL(pdata,6,0);
+ break;
+ }
+ case SMB_ACL_GROUP_OBJ:
+ SCVAL(pdata,0,SMB_POSIX_ACL_GROUP_OBJ);
+ own_grp = (unsigned int)pst->st_ex_gid;
+ SIVAL(pdata,2,own_grp);
+ SIVAL(pdata,6,0);
+ break;
+ case SMB_ACL_GROUP:
+ {
+ gid_t *pgid= (gid_t *)sys_acl_get_qualifier(entry);
+ if (!pgid) {
+ DEBUG(0,("marshall_posix_acl: SMB_VFS_SYS_ACL_GET_QUALIFIER failed.\n"));
+ return False;
+ }
+ own_grp = (unsigned int)*pgid;
+ SCVAL(pdata,0,SMB_POSIX_ACL_GROUP);
+ SIVAL(pdata,2,own_grp);
+ SIVAL(pdata,6,0);
+ break;
+ }
+ case SMB_ACL_MASK:
+ SCVAL(pdata,0,SMB_POSIX_ACL_MASK);
+ SIVAL(pdata,2,0xFFFFFFFF);
+ SIVAL(pdata,6,0xFFFFFFFF);
+ break;
+ case SMB_ACL_OTHER:
+ SCVAL(pdata,0,SMB_POSIX_ACL_OTHER);
+ SIVAL(pdata,2,0xFFFFFFFF);
+ SIVAL(pdata,6,0xFFFFFFFF);
+ break;
+ default:
+ DEBUG(0,("marshall_posix_acl: unknown tagtype.\n"));
+ return False;
+ }
+ pdata += SMB_POSIX_ACL_ENTRY_SIZE;
+ }
+
+ return True;
+}
+#endif
+
+static NTSTATUS smb_q_posix_acl(
+ struct connection_struct *conn,
+ struct smb_request *req,
+ struct smb_filename *smb_fname,
+ struct files_struct *fsp,
+ char **ppdata,
+ int *ptotal_data)
+{
+#if !defined(HAVE_POSIX_ACLS)
+ return NT_STATUS_INVALID_LEVEL;
+#else
+ char *pdata = NULL;
+ SMB_ACL_T file_acl = NULL;
+ SMB_ACL_T def_acl = NULL;
+ uint16_t num_file_acls = 0;
+ uint16_t num_def_acls = 0;
+ unsigned int size_needed = 0;
+ NTSTATUS status;
+ bool ok;
+ bool close_fsp = false;
+
+ /*
+ * Ensure we always operate on a file descriptor, not just
+ * the filename.
+ */
+ if (fsp == NULL || !fsp->fsp_flags.is_fsa) {
+ uint32_t access_mask = SEC_STD_READ_CONTROL|
+ FILE_READ_ATTRIBUTES|
+ FILE_WRITE_ATTRIBUTES;
+
+ status = get_posix_fsp(conn,
+ req,
+ smb_fname,
+ access_mask,
+ &fsp);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ close_fsp = true;
+ }
+
+ SMB_ASSERT(fsp != NULL);
+
+ status = refuse_symlink_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ file_acl = SMB_VFS_SYS_ACL_GET_FD(fsp, SMB_ACL_TYPE_ACCESS,
+ talloc_tos());
+
+ if (file_acl == NULL && no_acl_syscall_error(errno)) {
+ DBG_INFO("ACLs not implemented on "
+ "filesystem containing %s\n",
+ fsp_str_dbg(fsp));
+ status = NT_STATUS_NOT_IMPLEMENTED;
+ goto out;
+ }
+
+ if (S_ISDIR(fsp->fsp_name->st.st_ex_mode)) {
+ /*
+ * We can only have default POSIX ACLs on
+ * directories.
+ */
+ if (!fsp->fsp_flags.is_directory) {
+ DBG_INFO("Non-directory open %s\n",
+ fsp_str_dbg(fsp));
+ status = NT_STATUS_INVALID_HANDLE;
+ goto out;
+ }
+ def_acl = SMB_VFS_SYS_ACL_GET_FD(fsp,
+ SMB_ACL_TYPE_DEFAULT,
+ talloc_tos());
+ def_acl = free_empty_sys_acl(conn, def_acl);
+ }
+
+ num_file_acls = count_acl_entries(conn, file_acl);
+ num_def_acls = count_acl_entries(conn, def_acl);
+
+ /* Wrap checks. */
+ if (num_file_acls + num_def_acls < num_file_acls) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ size_needed = num_file_acls + num_def_acls;
+
+ /*
+ * (size_needed * SMB_POSIX_ACL_ENTRY_SIZE) must be less
+ * than UINT_MAX, so check by division.
+ */
+ if (size_needed > (UINT_MAX/SMB_POSIX_ACL_ENTRY_SIZE)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ size_needed = size_needed*SMB_POSIX_ACL_ENTRY_SIZE;
+ if (size_needed + SMB_POSIX_ACL_HEADER_SIZE < size_needed) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+ size_needed += SMB_POSIX_ACL_HEADER_SIZE;
+
+ *ppdata = SMB_REALLOC(*ppdata, size_needed);
+ if (*ppdata == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ pdata = *ppdata;
+
+ SSVAL(pdata,0,SMB_POSIX_ACL_VERSION);
+ SSVAL(pdata,2,num_file_acls);
+ SSVAL(pdata,4,num_def_acls);
+ pdata += SMB_POSIX_ACL_HEADER_SIZE;
+
+ ok = marshall_posix_acl(conn,
+ pdata,
+ &fsp->fsp_name->st,
+ file_acl);
+ if (!ok) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto out;
+ }
+ pdata += (num_file_acls*SMB_POSIX_ACL_ENTRY_SIZE);
+
+ ok = marshall_posix_acl(conn,
+ pdata,
+ &fsp->fsp_name->st,
+ def_acl);
+ if (!ok) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto out;
+ }
+
+ *ptotal_data = size_needed;
+ status = NT_STATUS_OK;
+
+ out:
+
+ if (close_fsp) {
+ /*
+ * Ensure the stat struct in smb_fname is up to
+ * date. Structure copy.
+ */
+ smb_fname->st = fsp->fsp_name->st;
+ (void)close_file_free(req, &fsp, NORMAL_CLOSE);
+ }
+
+ TALLOC_FREE(file_acl);
+ TALLOC_FREE(def_acl);
+ return status;
+#endif
+}
+
+static NTSTATUS smb_q_posix_symlink(
+ struct connection_struct *conn,
+ struct smb_request *req,
+ struct smb_filename *smb_fname,
+ char **ppdata,
+ int *ptotal_data)
+{
+ char buffer[PATH_MAX+1];
+ size_t needed, len;
+ int link_len;
+ char *pdata = NULL;
+ struct smb_filename *parent_fname = NULL;
+ struct smb_filename *base_name = NULL;
+ NTSTATUS status;
+
+ DBG_DEBUG("SMB_QUERY_FILE_UNIX_LINK for file %s\n",
+ smb_fname_str_dbg(smb_fname));
+
+ if (!S_ISLNK(smb_fname->st.st_ex_mode)) {
+ return NT_STATUS_DOS(ERRSRV, ERRbadlink);
+ }
+
+ status = parent_pathref(
+ talloc_tos(),
+ conn->cwd_fsp,
+ smb_fname,
+ &parent_fname,
+ &base_name);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("parent_pathref failed: %s\n", nt_errstr(status));
+ return status;
+ }
+
+ link_len = SMB_VFS_READLINKAT(
+ conn,
+ parent_fname->fsp,
+ base_name,
+ buffer,
+ sizeof(buffer)-1);
+ TALLOC_FREE(parent_fname);
+
+ if (link_len == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_DEBUG("READLINKAT failed: %s\n", nt_errstr(status));
+ return status;
+ }
+ if (link_len >= sizeof(buffer)) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ buffer[link_len] = 0;
+
+ needed = (link_len+1)*2;
+
+ *ppdata = SMB_REALLOC(*ppdata, needed);
+ if (*ppdata == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ pdata = *ppdata;
+
+ status = srvstr_push(
+ pdata,
+ req->flags2,
+ pdata,
+ buffer,
+ needed,
+ STR_TERMINATE,
+ &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ *ptotal_data = len;
+
+ return NT_STATUS_OK;
+}
+
+static void call_trans2qpathinfo(
+ connection_struct *conn,
+ struct smb_request *req,
+ char **pparams,
+ int total_params,
+ char **ppdata,
+ int total_data,
+ unsigned int max_data_bytes)
+{
+ char *params = *pparams;
+ uint16_t info_level;
+ struct smb_filename *smb_fname = NULL;
+ bool delete_pending = False;
+ struct timespec write_time_ts = { .tv_sec = 0, };
+ struct files_struct *dirfsp = NULL;
+ files_struct *fsp = NULL;
+ struct file_id fileid;
+ uint32_t name_hash;
+ char *fname = NULL;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+ NTTIME twrp = 0;
+ bool info_level_handled;
+ NTSTATUS status = NT_STATUS_OK;
+ int ret;
+
+ if (!params) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+
+ /* qpathinfo */
+ if (total_params < 7) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ info_level = SVAL(params,0);
+
+ DBG_NOTICE("TRANSACT2_QPATHINFO: level = %d\n", info_level);
+
+ if (INFO_LEVEL_IS_UNIX(info_level)) {
+ if (!lp_smb1_unix_extensions()) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+ if (!req->posix_pathnames) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+ }
+
+ if (req->posix_pathnames) {
+ srvstr_get_path_posix(req,
+ params,
+ req->flags2,
+ &fname,
+ &params[6],
+ total_params - 6,
+ STR_TERMINATE,
+ &status);
+ } else {
+ srvstr_get_path(req,
+ params,
+ req->flags2,
+ &fname,
+ &params[6],
+ total_params - 6,
+ STR_TERMINATE,
+ &status);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(fname, &twrp);
+ }
+ status = smb1_strip_dfs_path(req, &ucf_flags, &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+ status = filename_convert_dirfsp(req,
+ conn,
+ fname,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req,
+ NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ return;
+ }
+ reply_nterror(req, status);
+ return;
+ }
+
+ /*
+ * qpathinfo must operate on an existing file, so we
+ * can exit early if filename_convert_dirfsp() returned the
+ * "new file" NT_STATUS_OK, !VALID_STAT case.
+ */
+
+ if (!VALID_STAT(smb_fname->st)) {
+ reply_nterror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND);
+ return;
+ }
+
+ /*
+ * smb_fname->fsp may be NULL if smb_fname points at a symlink
+ * and we're in POSIX context, so be careful when using fsp
+ * below, it can still be NULL.
+ */
+ fsp = smb_fname->fsp;
+
+ /* If this is a stream, check if there is a delete_pending. */
+ if ((conn->fs_capabilities & FILE_NAMED_STREAMS)
+ && is_ntfs_stream_smb_fname(smb_fname)) {
+ struct smb_filename *smb_fname_base;
+
+ /* Create an smb_filename with stream_name == NULL. */
+ smb_fname_base = synthetic_smb_fname(
+ talloc_tos(),
+ smb_fname->base_name,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (smb_fname_base == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ ret = vfs_stat(conn, smb_fname_base);
+ if (ret != 0) {
+ DBG_NOTICE("vfs_stat of %s failed "
+ "(%s)\n",
+ smb_fname_str_dbg(smb_fname_base),
+ strerror(errno));
+ TALLOC_FREE(smb_fname_base);
+ reply_nterror(req,
+ map_nt_error_from_unix(errno));
+ return;
+ }
+
+ status = file_name_hash(conn,
+ smb_fname_str_dbg(smb_fname_base),
+ &name_hash);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(smb_fname_base);
+ reply_nterror(req, status);
+ return;
+ }
+
+ fileid = vfs_file_id_from_sbuf(conn,
+ &smb_fname_base->st);
+ TALLOC_FREE(smb_fname_base);
+ get_file_infos(fileid, name_hash, &delete_pending, NULL);
+ if (delete_pending) {
+ reply_nterror(req, NT_STATUS_DELETE_PENDING);
+ return;
+ }
+ }
+
+ status = file_name_hash(conn,
+ smb_fname_str_dbg(smb_fname),
+ &name_hash);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ if (fsp_getinfo_ask_sharemode(fsp)) {
+ fileid = vfs_file_id_from_sbuf(conn, &smb_fname->st);
+ get_file_infos(fileid, name_hash, &delete_pending,
+ &write_time_ts);
+ }
+
+ if (delete_pending) {
+ reply_nterror(req, NT_STATUS_DELETE_PENDING);
+ return;
+ }
+
+ info_level_handled = true; /* Untouched in switch cases below */
+
+ switch (info_level) {
+
+ default:
+ info_level_handled = false;
+ break;
+
+ case SMB_QUERY_FILE_UNIX_BASIC:
+ status = smb_q_unix_basic(
+ conn,
+ req,
+ smb_fname,
+ smb_fname->fsp,
+ ppdata,
+ &total_data);
+ break;
+
+ case SMB_QUERY_FILE_UNIX_INFO2:
+ status = smb_q_unix_info2(
+ conn,
+ req,
+ smb_fname,
+ smb_fname->fsp,
+ ppdata,
+ &total_data);
+ break;
+
+ case SMB_QUERY_POSIX_ACL:
+ status = smb_q_posix_acl(
+ conn,
+ req,
+ smb_fname,
+ smb_fname->fsp,
+ ppdata,
+ &total_data);
+ break;
+
+ case SMB_QUERY_FILE_UNIX_LINK:
+ status = smb_q_posix_symlink(
+ conn,
+ req,
+ smb_fname,
+ ppdata,
+ &total_data);
+ break;
+ }
+
+ if (info_level_handled) {
+ handle_trans2qfilepathinfo_result(
+ conn,
+ req,
+ info_level,
+ status,
+ *ppdata,
+ total_data,
+ total_data,
+ max_data_bytes);
+ return;
+ }
+
+ call_trans2qfilepathinfo(
+ conn,
+ req,
+ TRANSACT2_QPATHINFO,
+ info_level,
+ smb_fname,
+ fsp,
+ false,
+ write_time_ts,
+ pparams,
+ total_params,
+ ppdata,
+ total_data,
+ max_data_bytes);
+}
+
+static NTSTATUS smb_q_posix_lock(
+ struct connection_struct *conn,
+ struct smb_request *req,
+ struct files_struct *fsp,
+ char **ppdata,
+ int *ptotal_data)
+{
+ char *pdata = *ppdata;
+ int total_data = *ptotal_data;
+ uint64_t count;
+ uint64_t offset;
+ uint64_t smblctx;
+ enum brl_type lock_type;
+ NTSTATUS status;
+
+ if (fsp->fsp_flags.is_pathref || (fsp_get_io_fd(fsp) == -1)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (total_data != POSIX_LOCK_DATA_SIZE) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ switch (SVAL(pdata, POSIX_LOCK_TYPE_OFFSET)) {
+ case POSIX_LOCK_TYPE_READ:
+ lock_type = READ_LOCK;
+ break;
+ case POSIX_LOCK_TYPE_WRITE:
+ lock_type = WRITE_LOCK;
+ break;
+ case POSIX_LOCK_TYPE_UNLOCK:
+ default:
+ /* There's no point in asking for an unlock... */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ smblctx = (uint64_t)IVAL(pdata, POSIX_LOCK_PID_OFFSET);
+ offset = BVAL(pdata,POSIX_LOCK_START_OFFSET);
+ count = BVAL(pdata,POSIX_LOCK_LEN_OFFSET);
+
+ status = query_lock(
+ fsp,
+ &smblctx,
+ &count,
+ &offset,
+ &lock_type,
+ POSIX_LOCK);
+
+ if (NT_STATUS_IS_OK(status)) {
+ /*
+ * For success we just return a copy of what we sent
+ * with the lock type set to POSIX_LOCK_TYPE_UNLOCK.
+ */
+ SSVAL(pdata, POSIX_LOCK_TYPE_OFFSET, POSIX_LOCK_TYPE_UNLOCK);
+ return NT_STATUS_OK;
+ }
+
+ if (!ERROR_WAS_LOCK_DENIED(status)) {
+ DBG_DEBUG("query_lock() failed: %s\n", nt_errstr(status));
+ return status;
+ }
+
+ /*
+ * Here we need to report who has it locked.
+ */
+
+ SSVAL(pdata, POSIX_LOCK_TYPE_OFFSET, lock_type);
+ SSVAL(pdata, POSIX_LOCK_FLAGS_OFFSET, 0);
+ SIVAL(pdata, POSIX_LOCK_PID_OFFSET, (uint32_t)smblctx);
+ SBVAL(pdata, POSIX_LOCK_START_OFFSET, offset);
+ SBVAL(pdata, POSIX_LOCK_LEN_OFFSET, count);
+
+ return NT_STATUS_OK;
+}
+
+static void call_trans2qfileinfo(
+ connection_struct *conn,
+ struct smb_request *req,
+ char **pparams,
+ int total_params,
+ char **ppdata,
+ int total_data,
+ unsigned int max_data_bytes)
+{
+ char *params = *pparams;
+ uint16_t info_level;
+ struct smb_filename *smb_fname = NULL;
+ bool delete_pending = False;
+ struct timespec write_time_ts = { .tv_sec = 0, };
+ files_struct *fsp = NULL;
+ struct file_id fileid;
+ bool info_level_handled;
+ NTSTATUS status = NT_STATUS_OK;
+ int ret;
+
+ if (params == NULL) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if (total_params < 4) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(params,0));
+ info_level = SVAL(params,2);
+
+ if (IS_IPC(conn)) {
+ call_trans2qpipeinfo(
+ conn,
+ req,
+ fsp,
+ info_level,
+ TRANSACT2_QFILEINFO,
+ pparams,
+ total_params,
+ ppdata,
+ total_data,
+ max_data_bytes);
+ return;
+ }
+
+ DBG_NOTICE("TRANSACT2_QFILEINFO: level = %d\n", info_level);
+
+ if (INFO_LEVEL_IS_UNIX(info_level)) {
+ if (!lp_smb1_unix_extensions()) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+ if (!req->posix_pathnames) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+ }
+
+ /* Initial check for valid fsp ptr. */
+ if (!check_fsp_open(conn, req, fsp)) {
+ return;
+ }
+
+ smb_fname = fsp->fsp_name;
+
+ if(fsp->fake_file_handle) {
+ /*
+ * This is actually for the QUOTA_FAKE_FILE --metze
+ */
+
+ /* We know this name is ok, it's already passed the checks. */
+
+ } else if(fsp_get_pathref_fd(fsp) == -1) {
+ /*
+ * This is actually a QFILEINFO on a directory
+ * handle (returned from an NT SMB). NT5.0 seems
+ * to do this call. JRA.
+ */
+ ret = vfs_stat(conn, smb_fname);
+ if (ret != 0) {
+ DBG_NOTICE("vfs_stat of %s failed (%s)\n",
+ smb_fname_str_dbg(smb_fname),
+ strerror(errno));
+ reply_nterror(req,
+ map_nt_error_from_unix(errno));
+ return;
+ }
+
+ if (fsp_getinfo_ask_sharemode(fsp)) {
+ fileid = vfs_file_id_from_sbuf(
+ conn, &smb_fname->st);
+ get_file_infos(fileid, fsp->name_hash,
+ &delete_pending,
+ &write_time_ts);
+ }
+ } else {
+ /*
+ * Original code - this is an open file.
+ */
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("fstat of %s failed (%s)\n",
+ fsp_fnum_dbg(fsp), nt_errstr(status)));
+ reply_nterror(req, status);
+ return;
+ }
+ if (fsp_getinfo_ask_sharemode(fsp)) {
+ fileid = vfs_file_id_from_sbuf(
+ conn, &smb_fname->st);
+ get_file_infos(fileid, fsp->name_hash,
+ &delete_pending,
+ &write_time_ts);
+ }
+ }
+
+ info_level_handled = true; /* Untouched in switch cases below */
+
+ switch (info_level) {
+
+ default:
+ info_level_handled = false;
+ break;
+
+ case SMB_QUERY_POSIX_LOCK:
+ status = smb_q_posix_lock(conn, req, fsp, ppdata, &total_data);
+ break;
+
+ case SMB_QUERY_FILE_UNIX_BASIC:
+ status = smb_q_unix_basic(
+ conn, req, fsp->fsp_name, fsp, ppdata, &total_data);
+ break;
+
+ case SMB_QUERY_FILE_UNIX_INFO2:
+ status = smb_q_unix_info2(
+ conn, req, fsp->fsp_name, fsp, ppdata, &total_data);
+ break;
+
+ case SMB_QUERY_POSIX_ACL:
+ status = smb_q_posix_acl(
+ conn, req, fsp->fsp_name, fsp, ppdata, &total_data);
+ break;
+ }
+
+ if (info_level_handled) {
+ handle_trans2qfilepathinfo_result(
+ conn,
+ req,
+ info_level,
+ status,
+ *ppdata,
+ total_data,
+ total_data,
+ max_data_bytes);
+ return;
+ }
+
+ call_trans2qfilepathinfo(
+ conn,
+ req,
+ TRANSACT2_QFILEINFO,
+ info_level,
+ smb_fname,
+ fsp,
+ delete_pending,
+ write_time_ts,
+ pparams,
+ total_params,
+ ppdata,
+ total_data,
+ max_data_bytes);
+}
+
+static void handle_trans2setfilepathinfo_result(
+ connection_struct *conn,
+ struct smb_request *req,
+ uint16_t info_level,
+ NTSTATUS status,
+ char *pdata,
+ int data_return_size,
+ unsigned int max_data_bytes)
+{
+ char params[2] = { 0, 0, };
+
+ if (NT_STATUS_IS_OK(status)) {
+ send_trans2_replies(
+ conn,
+ req,
+ NT_STATUS_OK,
+ params,
+ 2,
+ pdata,
+ data_return_size,
+ max_data_bytes);
+ return;
+ }
+
+ if (open_was_deferred(req->xconn, req->mid)) {
+ /* We have re-scheduled this call. */
+ return;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ bool ok = defer_smb1_sharing_violation(req);
+ if (ok) {
+ return;
+ }
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_EVENT_PENDING)) {
+ /* We have re-scheduled this call. */
+ return;
+ }
+
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(
+ req,
+ NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV,
+ ERRbadpath);
+ return;
+ }
+
+ if (info_level == SMB_POSIX_PATH_OPEN) {
+ reply_openerror(req, status);
+ return;
+ }
+
+ if (NT_STATUS_EQUAL(status, STATUS_INVALID_EA_NAME)) {
+ /*
+ * Invalid EA name needs to return 2 param bytes,
+ * not a zero-length error packet.
+ */
+
+ send_trans2_replies(
+ conn,
+ req,
+ status,
+ params,
+ 2,
+ NULL,
+ 0,
+ max_data_bytes);
+ return;
+ }
+
+ reply_nterror(req, status);
+}
+
+/****************************************************************************
+ Create a directory with POSIX semantics.
+****************************************************************************/
+
+static NTSTATUS smb_posix_mkdir(connection_struct *conn,
+ struct smb_request *req,
+ char **ppdata,
+ int total_data,
+ struct smb_filename *smb_fname,
+ int *pdata_return_size)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ uint32_t raw_unixmode = 0;
+ mode_t unixmode = (mode_t)0;
+ files_struct *fsp = NULL;
+ uint16_t info_level_return = 0;
+ int info;
+ char *pdata = *ppdata;
+ struct smb2_create_blobs *posx = NULL;
+
+ if (total_data < 18) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ raw_unixmode = IVAL(pdata,8);
+ /* Next 4 bytes are not yet defined. */
+
+ status = unix_perms_from_wire(conn, &smb_fname->st, raw_unixmode,
+ PERM_NEW_DIR, &unixmode);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = make_smb2_posix_create_ctx(talloc_tos(), &posx, unixmode);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("make_smb2_posix_create_ctx failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ DEBUG(10,("smb_posix_mkdir: file %s, mode 0%o\n",
+ smb_fname_str_dbg(smb_fname), (unsigned int)unixmode));
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ NULL, /* dirfsp */
+ smb_fname, /* fname */
+ FILE_READ_ATTRIBUTES, /* access_mask */
+ FILE_SHARE_NONE, /* share_access */
+ FILE_CREATE, /* create_disposition*/
+ FILE_DIRECTORY_FILE, /* create_options */
+ 0, /* file_attributes */
+ 0, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ &info, /* pinfo */
+ posx, /* in_context_blobs */
+ NULL); /* out_context_blobs */
+
+ TALLOC_FREE(posx);
+
+ if (NT_STATUS_IS_OK(status)) {
+ close_file_free(req, &fsp, NORMAL_CLOSE);
+ }
+
+ info_level_return = SVAL(pdata,16);
+
+ if (info_level_return == SMB_QUERY_FILE_UNIX_BASIC) {
+ *pdata_return_size = 12 + SMB_FILE_UNIX_BASIC_SIZE;
+ } else if (info_level_return == SMB_QUERY_FILE_UNIX_INFO2) {
+ *pdata_return_size = 12 + SMB_FILE_UNIX_INFO2_SIZE;
+ } else {
+ *pdata_return_size = 12;
+ }
+
+ /* Realloc the data size */
+ *ppdata = (char *)SMB_REALLOC(*ppdata,*pdata_return_size);
+ if (*ppdata == NULL) {
+ *pdata_return_size = 0;
+ return NT_STATUS_NO_MEMORY;
+ }
+ pdata = *ppdata;
+
+ SSVAL(pdata,0,NO_OPLOCK_RETURN);
+ SSVAL(pdata,2,0); /* No fnum. */
+ SIVAL(pdata,4,info); /* Was directory created. */
+
+ switch (info_level_return) {
+ case SMB_QUERY_FILE_UNIX_BASIC:
+ SSVAL(pdata,8,SMB_QUERY_FILE_UNIX_BASIC);
+ SSVAL(pdata,10,0); /* Padding. */
+ store_file_unix_basic(conn, pdata + 12, fsp,
+ &smb_fname->st);
+ break;
+ case SMB_QUERY_FILE_UNIX_INFO2:
+ SSVAL(pdata,8,SMB_QUERY_FILE_UNIX_INFO2);
+ SSVAL(pdata,10,0); /* Padding. */
+ store_file_unix_basic_info2(conn, pdata + 12, fsp,
+ &smb_fname->st);
+ break;
+ default:
+ SSVAL(pdata,8,SMB_NO_INFO_LEVEL_RETURNED);
+ SSVAL(pdata,10,0); /* Padding. */
+ break;
+ }
+
+ return status;
+}
+
+/****************************************************************************
+ Open/Create a file with POSIX semantics.
+****************************************************************************/
+
+#define SMB_O_RDONLY_MAPPING (FILE_READ_DATA|FILE_READ_ATTRIBUTES|FILE_READ_EA)
+#define SMB_O_WRONLY_MAPPING (FILE_WRITE_DATA|FILE_WRITE_ATTRIBUTES|FILE_WRITE_EA)
+
+static NTSTATUS smb_posix_open(connection_struct *conn,
+ struct smb_request *req,
+ char **ppdata,
+ int total_data,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ int *pdata_return_size)
+{
+ bool extended_oplock_granted = False;
+ char *pdata = *ppdata;
+ uint32_t flags = 0;
+ uint32_t wire_open_mode = 0;
+ uint32_t raw_unixmode = 0;
+ uint32_t attributes = 0;
+ uint32_t create_disp = 0;
+ uint32_t access_mask = 0;
+ uint32_t create_options = FILE_NON_DIRECTORY_FILE;
+ NTSTATUS status = NT_STATUS_OK;
+ mode_t unixmode = (mode_t)0;
+ files_struct *fsp = NULL;
+ int oplock_request = 0;
+ int info = 0;
+ uint16_t info_level_return = 0;
+ struct smb2_create_blobs *posx = NULL;
+
+ if (total_data < 18) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ flags = IVAL(pdata,0);
+ oplock_request = (flags & REQUEST_OPLOCK) ? EXCLUSIVE_OPLOCK : 0;
+ if (oplock_request) {
+ oplock_request |= (flags & REQUEST_BATCH_OPLOCK) ? BATCH_OPLOCK : 0;
+ }
+
+ wire_open_mode = IVAL(pdata,4);
+
+ if (wire_open_mode == (SMB_O_CREAT|SMB_O_DIRECTORY)) {
+ return smb_posix_mkdir(conn, req,
+ ppdata,
+ total_data,
+ smb_fname,
+ pdata_return_size);
+ }
+
+ switch (wire_open_mode & SMB_ACCMODE) {
+ case SMB_O_RDONLY:
+ access_mask = SMB_O_RDONLY_MAPPING;
+ break;
+ case SMB_O_WRONLY:
+ access_mask = SMB_O_WRONLY_MAPPING;
+ break;
+ case SMB_O_RDWR:
+ access_mask = (SMB_O_RDONLY_MAPPING|
+ SMB_O_WRONLY_MAPPING);
+ break;
+ default:
+ DEBUG(5,("smb_posix_open: invalid open mode 0x%x\n",
+ (unsigned int)wire_open_mode ));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ wire_open_mode &= ~SMB_ACCMODE;
+
+ /* First take care of O_CREAT|O_EXCL interactions. */
+ switch (wire_open_mode & (SMB_O_CREAT | SMB_O_EXCL)) {
+ case (SMB_O_CREAT | SMB_O_EXCL):
+ /* File exists fail. File not exist create. */
+ create_disp = FILE_CREATE;
+ break;
+ case SMB_O_CREAT:
+ /* File exists open. File not exist create. */
+ create_disp = FILE_OPEN_IF;
+ break;
+ case SMB_O_EXCL:
+ /* O_EXCL on its own without O_CREAT is undefined.
+ We deliberately ignore it as some versions of
+ Linux CIFSFS can send a bare O_EXCL on the
+ wire which other filesystems in the kernel
+ ignore. See bug 9519 for details. */
+
+ /* Fallthrough. */
+
+ case 0:
+ /* File exists open. File not exist fail. */
+ create_disp = FILE_OPEN;
+ break;
+ default:
+ DEBUG(5,("smb_posix_open: invalid create mode 0x%x\n",
+ (unsigned int)wire_open_mode ));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* Next factor in the effects of O_TRUNC. */
+ wire_open_mode &= ~(SMB_O_CREAT | SMB_O_EXCL);
+
+ if (wire_open_mode & SMB_O_TRUNC) {
+ switch (create_disp) {
+ case FILE_CREATE:
+ /* (SMB_O_CREAT | SMB_O_EXCL | O_TRUNC) */
+ /* Leave create_disp alone as
+ (O_CREAT|O_EXCL|O_TRUNC) == (O_CREAT|O_EXCL)
+ */
+ /* File exists fail. File not exist create. */
+ break;
+ case FILE_OPEN_IF:
+ /* SMB_O_CREAT | SMB_O_TRUNC */
+ /* File exists overwrite. File not exist create. */
+ create_disp = FILE_OVERWRITE_IF;
+ break;
+ case FILE_OPEN:
+ /* SMB_O_TRUNC */
+ /* File exists overwrite. File not exist fail. */
+ create_disp = FILE_OVERWRITE;
+ break;
+ default:
+ /* Cannot get here. */
+ smb_panic("smb_posix_open: logic error");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ }
+
+ raw_unixmode = IVAL(pdata,8);
+ /* Next 4 bytes are not yet defined. */
+
+ status = unix_perms_from_wire(conn, &smb_fname->st, raw_unixmode,
+ (VALID_STAT(smb_fname->st) ?
+ PERM_EXISTING_FILE : PERM_NEW_FILE),
+ &unixmode);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = make_smb2_posix_create_ctx(talloc_tos(), &posx, unixmode);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("make_smb2_posix_create_ctx failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ if (wire_open_mode & SMB_O_SYNC) {
+ create_options |= FILE_WRITE_THROUGH;
+ }
+ if (wire_open_mode & SMB_O_APPEND) {
+ access_mask |= FILE_APPEND_DATA;
+ }
+ if (wire_open_mode & SMB_O_DIRECT) {
+ /*
+ * BUG: this doesn't work anymore since
+ * e0814dc5082dd4ecca8a155e0ce24b073158fd92. But since
+ * FILE_FLAG_NO_BUFFERING isn't used at all in the IO codepath,
+ * it doesn't really matter.
+ */
+ attributes |= FILE_FLAG_NO_BUFFERING;
+ }
+
+ if ((wire_open_mode & SMB_O_DIRECTORY) ||
+ VALID_STAT_OF_DIR(smb_fname->st)) {
+ if (access_mask != SMB_O_RDONLY_MAPPING) {
+ return NT_STATUS_FILE_IS_A_DIRECTORY;
+ }
+ create_options &= ~FILE_NON_DIRECTORY_FILE;
+ create_options |= FILE_DIRECTORY_FILE;
+ }
+
+ DEBUG(10,("smb_posix_open: file %s, smb_posix_flags = %u, mode 0%o\n",
+ smb_fname_str_dbg(smb_fname),
+ (unsigned int)wire_open_mode,
+ (unsigned int)unixmode ));
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_fname, /* fname */
+ access_mask, /* access_mask */
+ (FILE_SHARE_READ | FILE_SHARE_WRITE | /* share_access */
+ FILE_SHARE_DELETE),
+ create_disp, /* create_disposition*/
+ create_options, /* create_options */
+ attributes, /* file_attributes */
+ oplock_request, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ &info, /* pinfo */
+ posx, /* in_context_blobs */
+ NULL); /* out_context_blobs */
+
+ TALLOC_FREE(posx);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (oplock_request && lp_fake_oplocks(SNUM(conn))) {
+ extended_oplock_granted = True;
+ }
+
+ if(oplock_request && EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
+ extended_oplock_granted = True;
+ }
+
+ info_level_return = SVAL(pdata,16);
+
+ /* Allocate the correct return size. */
+
+ if (info_level_return == SMB_QUERY_FILE_UNIX_BASIC) {
+ *pdata_return_size = 12 + SMB_FILE_UNIX_BASIC_SIZE;
+ } else if (info_level_return == SMB_QUERY_FILE_UNIX_INFO2) {
+ *pdata_return_size = 12 + SMB_FILE_UNIX_INFO2_SIZE;
+ } else {
+ *pdata_return_size = 12;
+ }
+
+ /* Realloc the data size */
+ *ppdata = (char *)SMB_REALLOC(*ppdata,*pdata_return_size);
+ if (*ppdata == NULL) {
+ close_file_free(req, &fsp, ERROR_CLOSE);
+ *pdata_return_size = 0;
+ return NT_STATUS_NO_MEMORY;
+ }
+ pdata = *ppdata;
+
+ if (extended_oplock_granted) {
+ if (flags & REQUEST_BATCH_OPLOCK) {
+ SSVAL(pdata,0, BATCH_OPLOCK_RETURN);
+ } else {
+ SSVAL(pdata,0, EXCLUSIVE_OPLOCK_RETURN);
+ }
+ } else if (fsp->oplock_type == LEVEL_II_OPLOCK) {
+ SSVAL(pdata,0, LEVEL_II_OPLOCK_RETURN);
+ } else {
+ SSVAL(pdata,0,NO_OPLOCK_RETURN);
+ }
+
+ SSVAL(pdata,2,fsp->fnum);
+ SIVAL(pdata,4,info); /* Was file created etc. */
+
+ switch (info_level_return) {
+ case SMB_QUERY_FILE_UNIX_BASIC:
+ SSVAL(pdata,8,SMB_QUERY_FILE_UNIX_BASIC);
+ SSVAL(pdata,10,0); /* padding. */
+ store_file_unix_basic(conn, pdata + 12, fsp,
+ &smb_fname->st);
+ break;
+ case SMB_QUERY_FILE_UNIX_INFO2:
+ SSVAL(pdata,8,SMB_QUERY_FILE_UNIX_INFO2);
+ SSVAL(pdata,10,0); /* padding. */
+ store_file_unix_basic_info2(conn, pdata + 12, fsp,
+ &smb_fname->st);
+ break;
+ default:
+ SSVAL(pdata,8,SMB_NO_INFO_LEVEL_RETURNED);
+ SSVAL(pdata,10,0); /* padding. */
+ break;
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Delete a file with POSIX semantics.
+****************************************************************************/
+
+struct smb_posix_unlink_state {
+ struct smb_filename *smb_fname;
+ struct files_struct *fsp;
+ NTSTATUS status;
+};
+
+static void smb_posix_unlink_locked(struct share_mode_lock *lck,
+ void *private_data)
+{
+ struct smb_posix_unlink_state *state = private_data;
+ char del = 1;
+ bool other_nonposix_opens;
+
+ other_nonposix_opens = has_other_nonposix_opens(lck, state->fsp);
+ if (other_nonposix_opens) {
+ /* Fail with sharing violation. */
+ state->status = NT_STATUS_SHARING_VIOLATION;
+ return;
+ }
+
+ /*
+ * Set the delete on close.
+ */
+ state->status = smb_set_file_disposition_info(state->fsp->conn,
+ &del,
+ 1,
+ state->fsp,
+ state->smb_fname);
+}
+
+static NTSTATUS smb_posix_unlink(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname)
+{
+ struct smb_posix_unlink_state state = {};
+ NTSTATUS status = NT_STATUS_OK;
+ files_struct *fsp = NULL;
+ uint16_t flags = 0;
+ int info = 0;
+ int create_options = FILE_OPEN_REPARSE_POINT;
+ struct smb2_create_blobs *posx = NULL;
+
+ if (!CAN_WRITE(conn)) {
+ return NT_STATUS_DOS(ERRSRV, ERRaccess);
+ }
+
+ if (total_data < 2) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ flags = SVAL(pdata,0);
+
+ if (!VALID_STAT(smb_fname->st)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if ((flags == SMB_POSIX_UNLINK_DIRECTORY_TARGET) &&
+ !VALID_STAT_OF_DIR(smb_fname->st)) {
+ return NT_STATUS_NOT_A_DIRECTORY;
+ }
+
+ DEBUG(10,("smb_posix_unlink: %s %s\n",
+ (flags == SMB_POSIX_UNLINK_DIRECTORY_TARGET) ? "directory" : "file",
+ smb_fname_str_dbg(smb_fname)));
+
+ if (S_ISDIR(smb_fname->st.st_ex_mode)) {
+ create_options |= FILE_DIRECTORY_FILE;
+ }
+
+ status = make_smb2_posix_create_ctx(talloc_tos(), &posx, 0777);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("make_smb2_posix_create_ctx failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_fname, /* fname */
+ DELETE_ACCESS, /* access_mask */
+ (FILE_SHARE_READ | FILE_SHARE_WRITE | /* share_access */
+ FILE_SHARE_DELETE),
+ FILE_OPEN, /* create_disposition*/
+ create_options, /* create_options */
+ 0, /* file_attributes */
+ 0, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ &info, /* pinfo */
+ posx, /* in_context_blobs */
+ NULL); /* out_context_blobs */
+
+ TALLOC_FREE(posx);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * Don't lie to client. If we can't really delete due to
+ * non-POSIX opens return SHARING_VIOLATION.
+ */
+
+ state = (struct smb_posix_unlink_state) {
+ .smb_fname = smb_fname,
+ .fsp = fsp,
+ };
+
+ status = share_mode_do_locked_vfs_allowed(fsp->file_id,
+ smb_posix_unlink_locked,
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("share_mode_do_locked_vfs_allowed(%s) failed - %s\n",
+ fsp_str_dbg(fsp), nt_errstr(status));
+ close_file_free(req, &fsp, NORMAL_CLOSE);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = state.status;
+ if (!NT_STATUS_IS_OK(status)) {
+ close_file_free(req, &fsp, NORMAL_CLOSE);
+ return status;
+ }
+ return close_file_free(req, &fsp, NORMAL_CLOSE);
+}
+
+/****************************************************************************
+ Deal with SMB_SET_FILE_UNIX_LINK (create a UNIX symlink).
+****************************************************************************/
+
+static NTSTATUS smb_set_file_unix_link(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ struct smb_filename *new_smb_fname)
+{
+ char *link_target = NULL;
+ struct smb_filename target_fname;
+ TALLOC_CTX *ctx = talloc_tos();
+ NTSTATUS status;
+ int ret;
+ struct smb_filename *parent_fname = NULL;
+ struct smb_filename *base_name = NULL;
+
+ if (!CAN_WRITE(conn)) {
+ return NT_STATUS_DOS(ERRSRV, ERRaccess);
+ }
+
+ /* Set a symbolic link. */
+ /* Don't allow this if follow links is false. */
+
+ if (total_data == 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!lp_follow_symlinks(SNUM(conn))) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ srvstr_pull_talloc(ctx, pdata, req->flags2, &link_target, pdata,
+ total_data, STR_TERMINATE);
+
+ if (!link_target) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ target_fname = (struct smb_filename) {
+ .base_name = link_target,
+ };
+
+ /* Removes @GMT tokens if any */
+ status = canonicalize_snapshot_path(&target_fname, UCF_GMT_PATHNAME, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(10,("smb_set_file_unix_link: SMB_SET_FILE_UNIX_LINK doing symlink %s -> %s\n",
+ new_smb_fname->base_name, link_target ));
+
+ status = parent_pathref(talloc_tos(),
+ conn->cwd_fsp,
+ new_smb_fname,
+ &parent_fname,
+ &base_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ ret = SMB_VFS_SYMLINKAT(conn,
+ &target_fname,
+ parent_fname->fsp,
+ base_name);
+ if (ret != 0) {
+ TALLOC_FREE(parent_fname);
+ return map_nt_error_from_unix(errno);
+ }
+
+ TALLOC_FREE(parent_fname);
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with SMB_SET_FILE_UNIX_HLINK (create a UNIX hard link).
+****************************************************************************/
+
+static NTSTATUS smb_set_file_unix_hlink(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata, int total_data,
+ struct smb_filename *smb_fname_new)
+{
+ char *oldname = NULL;
+ struct files_struct *src_dirfsp = NULL;
+ struct smb_filename *smb_fname_old = NULL;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+ NTTIME old_twrp = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (!CAN_WRITE(conn)) {
+ return NT_STATUS_DOS(ERRSRV, ERRaccess);
+ }
+
+ /* Set a hard link. */
+ if (total_data == 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (req->posix_pathnames) {
+ srvstr_get_path_posix(ctx,
+ pdata,
+ req->flags2,
+ &oldname,
+ pdata,
+ total_data,
+ STR_TERMINATE,
+ &status);
+ } else {
+ srvstr_get_path(ctx,
+ pdata,
+ req->flags2,
+ &oldname,
+ pdata,
+ total_data,
+ STR_TERMINATE,
+ &status);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(10,("smb_set_file_unix_hlink: SMB_SET_FILE_UNIX_LINK doing hard link %s -> %s\n",
+ smb_fname_str_dbg(smb_fname_new), oldname));
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(oldname, &old_twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &oldname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ oldname,
+ ucf_flags,
+ old_twrp,
+ &src_dirfsp,
+ &smb_fname_old);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return hardlink_internals(ctx,
+ conn,
+ req,
+ false,
+ smb_fname_old,
+ smb_fname_new);
+}
+
+/****************************************************************************
+ Allow a UNIX info mknod.
+****************************************************************************/
+
+static NTSTATUS smb_unix_mknod(connection_struct *conn,
+ const char *pdata,
+ int total_data,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname)
+{
+ uint32_t file_type = IVAL(pdata,56);
+#if defined(HAVE_MAKEDEV)
+ uint32_t dev_major = IVAL(pdata,60);
+ uint32_t dev_minor = IVAL(pdata,68);
+#endif
+ SMB_DEV_T dev = (SMB_DEV_T)0;
+ uint32_t raw_unixmode = IVAL(pdata,84);
+ NTSTATUS status;
+ mode_t unixmode;
+ int ret;
+ struct smb_filename *parent_fname = NULL;
+ struct smb_filename *atname = NULL;
+
+ if (total_data < 100) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = unix_perms_from_wire(conn, &smb_fname->st, raw_unixmode,
+ PERM_NEW_FILE, &unixmode);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+#if defined(HAVE_MAKEDEV)
+ dev = makedev(dev_major, dev_minor);
+#endif
+
+ switch (file_type) {
+ /* We can't create other objects here. */
+ case UNIX_TYPE_FILE:
+ case UNIX_TYPE_DIR:
+ case UNIX_TYPE_SYMLINK:
+ return NT_STATUS_ACCESS_DENIED;
+#if defined(S_IFIFO)
+ case UNIX_TYPE_FIFO:
+ unixmode |= S_IFIFO;
+ break;
+#endif
+#if defined(S_IFSOCK)
+ case UNIX_TYPE_SOCKET:
+ unixmode |= S_IFSOCK;
+ break;
+#endif
+#if defined(S_IFCHR)
+ case UNIX_TYPE_CHARDEV:
+ /* This is only allowed for root. */
+ if (get_current_uid(conn) != sec_initial_uid()) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ unixmode |= S_IFCHR;
+ break;
+#endif
+#if defined(S_IFBLK)
+ case UNIX_TYPE_BLKDEV:
+ if (get_current_uid(conn) != sec_initial_uid()) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ unixmode |= S_IFBLK;
+ break;
+#endif
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ DEBUG(10,("smb_unix_mknod: SMB_SET_FILE_UNIX_BASIC doing mknod dev "
+ "%.0f mode 0%o for file %s\n", (double)dev,
+ (unsigned int)unixmode, smb_fname_str_dbg(smb_fname)));
+
+ status = SMB_VFS_PARENT_PATHNAME(dirfsp->conn,
+ talloc_tos(),
+ smb_fname,
+ &parent_fname,
+ &atname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* Ok - do the mknod. */
+ ret = SMB_VFS_MKNODAT(conn,
+ dirfsp,
+ atname,
+ unixmode,
+ dev);
+
+ if (ret != 0) {
+ TALLOC_FREE(parent_fname);
+ return map_nt_error_from_unix(errno);
+ }
+
+ /* If any of the other "set" calls fail we
+ * don't want to end up with a half-constructed mknod.
+ */
+
+ if (lp_inherit_permissions(SNUM(conn))) {
+ inherit_access_posix_acl(conn,
+ dirfsp,
+ smb_fname,
+ unixmode);
+ }
+ TALLOC_FREE(parent_fname);
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with SMB_SET_FILE_UNIX_BASIC.
+****************************************************************************/
+
+static NTSTATUS smb_set_file_unix_basic(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ struct files_struct *dirfsp,
+ files_struct *fsp,
+ struct smb_filename *smb_fname)
+{
+ struct smb_file_time ft;
+ uint32_t raw_unixmode;
+ mode_t unixmode;
+ off_t size = 0;
+ uid_t set_owner = (uid_t)SMB_UID_NO_CHANGE;
+ gid_t set_grp = (uid_t)SMB_GID_NO_CHANGE;
+ NTSTATUS status = NT_STATUS_OK;
+ enum perm_type ptype;
+ files_struct *all_fsps = NULL;
+ bool modify_mtime = true;
+ struct file_id id;
+ SMB_STRUCT_STAT sbuf;
+
+ if (!CAN_WRITE(conn)) {
+ return NT_STATUS_DOS(ERRSRV, ERRaccess);
+ }
+
+ init_smb_file_time(&ft);
+
+ if (total_data < 100) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if(IVAL(pdata, 0) != SMB_SIZE_NO_CHANGE_LO &&
+ IVAL(pdata, 4) != SMB_SIZE_NO_CHANGE_HI) {
+ size=IVAL(pdata,0); /* first 8 Bytes are size */
+ size |= (((off_t)IVAL(pdata,4)) << 32);
+ }
+
+ ft.atime = pull_long_date_full_timespec(pdata+24); /* access_time */
+ ft.mtime = pull_long_date_full_timespec(pdata+32); /* modification_time */
+ set_owner = (uid_t)IVAL(pdata,40);
+ set_grp = (gid_t)IVAL(pdata,48);
+ raw_unixmode = IVAL(pdata,84);
+
+ if (VALID_STAT(smb_fname->st)) {
+ if (S_ISDIR(smb_fname->st.st_ex_mode)) {
+ ptype = PERM_EXISTING_DIR;
+ } else {
+ ptype = PERM_EXISTING_FILE;
+ }
+ } else {
+ ptype = PERM_NEW_FILE;
+ }
+
+ status = unix_perms_from_wire(conn, &smb_fname->st, raw_unixmode,
+ ptype, &unixmode);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(10,("smb_set_file_unix_basic: SMB_SET_FILE_UNIX_BASIC: name = "
+ "%s size = %.0f, uid = %u, gid = %u, raw perms = 0%o\n",
+ smb_fname_str_dbg(smb_fname), (double)size,
+ (unsigned int)set_owner, (unsigned int)set_grp,
+ (int)raw_unixmode));
+
+ sbuf = smb_fname->st;
+
+ if (!VALID_STAT(sbuf)) {
+ /*
+ * The only valid use of this is to create character and block
+ * devices, and named pipes. This is deprecated (IMHO) and
+ * a new info level should be used for mknod. JRA.
+ */
+
+ if (dirfsp == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ return smb_unix_mknod(conn,
+ pdata,
+ total_data,
+ dirfsp,
+ smb_fname);
+ }
+
+#if 1
+ /* Horrible backwards compatibility hack as an old server bug
+ * allowed a CIFS client bug to remain unnoticed :-(. JRA.
+ * */
+
+ if (!size) {
+ size = get_file_size_stat(&sbuf);
+ }
+#endif
+
+ /*
+ * Deal with the UNIX specific mode set.
+ */
+
+ if (raw_unixmode != SMB_MODE_NO_CHANGE) {
+ int ret;
+
+ if (fsp == NULL || S_ISLNK(smb_fname->st.st_ex_mode)) {
+ DBG_WARNING("Can't set mode on symlink %s\n",
+ smb_fname_str_dbg(smb_fname));
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ DEBUG(10,("smb_set_file_unix_basic: SMB_SET_FILE_UNIX_BASIC "
+ "setting mode 0%o for file %s\n",
+ (unsigned int)unixmode,
+ smb_fname_str_dbg(smb_fname)));
+ ret = SMB_VFS_FCHMOD(fsp, unixmode);
+ if (ret != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+ }
+
+ /*
+ * Deal with the UNIX specific uid set.
+ */
+
+ if ((set_owner != (uid_t)SMB_UID_NO_CHANGE) &&
+ (sbuf.st_ex_uid != set_owner)) {
+ int ret;
+
+ DEBUG(10,("smb_set_file_unix_basic: SMB_SET_FILE_UNIX_BASIC "
+ "changing owner %u for path %s\n",
+ (unsigned int)set_owner,
+ smb_fname_str_dbg(smb_fname)));
+
+ if (fsp &&
+ !fsp->fsp_flags.is_pathref &&
+ fsp_get_io_fd(fsp) != -1)
+ {
+ ret = SMB_VFS_FCHOWN(fsp, set_owner, (gid_t)-1);
+ } else {
+ /*
+ * UNIX extensions calls must always operate
+ * on symlinks.
+ */
+ ret = SMB_VFS_LCHOWN(conn, smb_fname,
+ set_owner, (gid_t)-1);
+ }
+
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ return status;
+ }
+ }
+
+ /*
+ * Deal with the UNIX specific gid set.
+ */
+
+ if ((set_grp != (uid_t)SMB_GID_NO_CHANGE) &&
+ (sbuf.st_ex_gid != set_grp)) {
+ int ret;
+
+ DEBUG(10,("smb_set_file_unix_basic: SMB_SET_FILE_UNIX_BASIC "
+ "changing group %u for file %s\n",
+ (unsigned int)set_grp,
+ smb_fname_str_dbg(smb_fname)));
+ if (fsp &&
+ !fsp->fsp_flags.is_pathref &&
+ fsp_get_io_fd(fsp) != -1)
+ {
+ ret = SMB_VFS_FCHOWN(fsp, (uid_t)-1, set_grp);
+ } else {
+ /*
+ * UNIX extensions calls must always operate
+ * on symlinks.
+ */
+ ret = SMB_VFS_LCHOWN(conn, smb_fname, (uid_t)-1,
+ set_grp);
+ }
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ return status;
+ }
+ }
+
+ /* Deal with any size changes. */
+
+ if (S_ISREG(sbuf.st_ex_mode)) {
+ status = smb_set_file_size(conn, req,
+ fsp,
+ smb_fname,
+ &sbuf,
+ size,
+ false);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ /* Deal with any time changes. */
+ if (is_omit_timespec(&ft.mtime) && is_omit_timespec(&ft.atime)) {
+ /* No change, don't cancel anything. */
+ return status;
+ }
+
+ id = vfs_file_id_from_sbuf(conn, &sbuf);
+ for(all_fsps = file_find_di_first(conn->sconn, id, true); all_fsps;
+ all_fsps = file_find_di_next(all_fsps, true)) {
+ /*
+ * We're setting the time explicitly for UNIX.
+ * Cancel any pending changes over all handles.
+ */
+ all_fsps->fsp_flags.update_write_time_on_close = false;
+ TALLOC_FREE(all_fsps->update_write_time_event);
+ }
+
+ /*
+ * Override the "setting_write_time"
+ * parameter here as it almost does what
+ * we need. Just remember if we modified
+ * mtime and send the notify ourselves.
+ */
+ if (is_omit_timespec(&ft.mtime)) {
+ modify_mtime = false;
+ }
+
+ status = smb_set_file_time(conn,
+ fsp,
+ smb_fname,
+ &ft,
+ false);
+ if (modify_mtime) {
+ notify_fname(conn, NOTIFY_ACTION_MODIFIED,
+ FILE_NOTIFY_CHANGE_LAST_WRITE, smb_fname->base_name);
+ }
+ return status;
+}
+
+/****************************************************************************
+ Deal with SMB_SET_FILE_UNIX_INFO2.
+****************************************************************************/
+
+static NTSTATUS smb_set_file_unix_info2(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ struct files_struct *dirfsp,
+ files_struct *fsp,
+ struct smb_filename *smb_fname)
+{
+ NTSTATUS status;
+ uint32_t smb_fflags;
+ uint32_t smb_fmask;
+
+ if (!CAN_WRITE(conn)) {
+ return NT_STATUS_DOS(ERRSRV, ERRaccess);
+ }
+
+ if (total_data < 116) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* Start by setting all the fields that are common between UNIX_BASIC
+ * and UNIX_INFO2.
+ */
+ status = smb_set_file_unix_basic(conn,
+ req,
+ pdata,
+ total_data,
+ dirfsp,
+ fsp,
+ smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ smb_fflags = IVAL(pdata, 108);
+ smb_fmask = IVAL(pdata, 112);
+
+ /* NB: We should only attempt to alter the file flags if the client
+ * sends a non-zero mask.
+ */
+ if (smb_fmask != 0) {
+ int stat_fflags = 0;
+
+ if (!map_info2_flags_to_sbuf(&smb_fname->st, smb_fflags,
+ smb_fmask, &stat_fflags)) {
+ /* Client asked to alter a flag we don't understand. */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (fsp == NULL || S_ISLNK(smb_fname->st.st_ex_mode)) {
+ DBG_WARNING("Can't change flags on symlink %s\n",
+ smb_fname_str_dbg(smb_fname));
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ if (SMB_VFS_FCHFLAGS(fsp, stat_fflags) != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+ }
+
+ /* XXX: need to add support for changing the create_time here. You
+ * can do this for paths on Darwin with setattrlist(2). The right way
+ * to hook this up is probably by extending the VFS utimes interface.
+ */
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with SMB_SET_POSIX_ACL.
+****************************************************************************/
+
+static NTSTATUS smb_set_posix_acl(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data_in,
+ files_struct *fsp,
+ struct smb_filename *smb_fname)
+{
+#if !defined(HAVE_POSIX_ACLS)
+ return NT_STATUS_INVALID_LEVEL;
+#else
+ uint16_t posix_acl_version;
+ uint16_t num_file_acls;
+ uint16_t num_def_acls;
+ bool valid_file_acls = true;
+ bool valid_def_acls = true;
+ NTSTATUS status;
+ unsigned int size_needed;
+ unsigned int total_data;
+ bool close_fsp = false;
+
+ if (total_data_in < 0) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ total_data = total_data_in;
+
+ if (total_data < SMB_POSIX_ACL_HEADER_SIZE) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+ posix_acl_version = SVAL(pdata,0);
+ num_file_acls = SVAL(pdata,2);
+ num_def_acls = SVAL(pdata,4);
+
+ if (num_file_acls == SMB_POSIX_IGNORE_ACE_ENTRIES) {
+ valid_file_acls = false;
+ num_file_acls = 0;
+ }
+
+ if (num_def_acls == SMB_POSIX_IGNORE_ACE_ENTRIES) {
+ valid_def_acls = false;
+ num_def_acls = 0;
+ }
+
+ if (posix_acl_version != SMB_POSIX_ACL_VERSION) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ /* Wrap checks. */
+ if (num_file_acls + num_def_acls < num_file_acls) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ size_needed = num_file_acls + num_def_acls;
+
+ /*
+ * (size_needed * SMB_POSIX_ACL_ENTRY_SIZE) must be less
+ * than UINT_MAX, so check by division.
+ */
+ if (size_needed > (UINT_MAX/SMB_POSIX_ACL_ENTRY_SIZE)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ size_needed = size_needed*SMB_POSIX_ACL_ENTRY_SIZE;
+ if (size_needed + SMB_POSIX_ACL_HEADER_SIZE < size_needed) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+ size_needed += SMB_POSIX_ACL_HEADER_SIZE;
+
+ if (total_data < size_needed) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ /*
+ * Ensure we always operate on a file descriptor, not just
+ * the filename.
+ */
+ if (fsp == NULL || !fsp->fsp_flags.is_fsa) {
+ uint32_t access_mask = SEC_STD_WRITE_OWNER|
+ SEC_STD_WRITE_DAC|
+ SEC_STD_READ_CONTROL|
+ FILE_READ_ATTRIBUTES|
+ FILE_WRITE_ATTRIBUTES;
+
+ status = get_posix_fsp(conn,
+ req,
+ smb_fname,
+ access_mask,
+ &fsp);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ close_fsp = true;
+ }
+
+ /* Here we know fsp != NULL */
+ SMB_ASSERT(fsp != NULL);
+
+ status = refuse_symlink_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ /* If we have a default acl, this *must* be a directory. */
+ if (valid_def_acls && !fsp->fsp_flags.is_directory) {
+ DBG_INFO("Can't set default acls on "
+ "non-directory %s\n",
+ fsp_str_dbg(fsp));
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ DBG_DEBUG("file %s num_file_acls = %"PRIu16", "
+ "num_def_acls = %"PRIu16"\n",
+ fsp_str_dbg(fsp),
+ num_file_acls,
+ num_def_acls);
+
+ /* Move pdata to the start of the file ACL entries. */
+ pdata += SMB_POSIX_ACL_HEADER_SIZE;
+
+ if (valid_file_acls) {
+ status = set_unix_posix_acl(conn,
+ fsp,
+ num_file_acls,
+ pdata);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ }
+
+ /* Move pdata to the start of the default ACL entries. */
+ pdata += (num_file_acls*SMB_POSIX_ACL_ENTRY_SIZE);
+
+ if (valid_def_acls) {
+ status = set_unix_posix_default_acl(conn,
+ fsp,
+ num_def_acls,
+ pdata);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ }
+
+ status = NT_STATUS_OK;
+
+ out:
+
+ if (close_fsp) {
+ (void)close_file_free(req, &fsp, NORMAL_CLOSE);
+ }
+ return status;
+#endif
+}
+
+static void call_trans2setpathinfo(
+ connection_struct *conn,
+ struct smb_request *req,
+ char **pparams,
+ int total_params,
+ char **ppdata,
+ int total_data,
+ unsigned int max_data_bytes)
+{
+ uint16_t info_level;
+ struct smb_filename *smb_fname = NULL;
+ struct files_struct *dirfsp = NULL;
+ struct files_struct *fsp = NULL;
+ char *params = *pparams;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+ NTTIME twrp = 0;
+ char *fname = NULL;
+ bool info_level_handled;
+ int data_return_size = 0;
+ NTSTATUS status;
+
+ if (params == NULL) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ /* set path info */
+ if (total_params < 7) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ info_level = SVAL(params,0);
+
+ if (INFO_LEVEL_IS_UNIX(info_level)) {
+ if (!lp_smb1_unix_extensions()) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+ if (!req->posix_pathnames) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+ }
+
+ if (req->posix_pathnames) {
+ srvstr_get_path_posix(req,
+ params,
+ req->flags2,
+ &fname,
+ &params[6],
+ total_params - 6,
+ STR_TERMINATE,
+ &status);
+ } else {
+ srvstr_get_path(req,
+ params,
+ req->flags2,
+ &fname,
+ &params[6],
+ total_params - 6,
+ STR_TERMINATE,
+ &status);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ DBG_NOTICE("fname=%s info_level=%d totdata=%d\n",
+ fname,
+ info_level,
+ total_data);
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(fname, &twrp);
+ }
+ status = smb1_strip_dfs_path(req, &ucf_flags, &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+ status = filename_convert_dirfsp(req,
+ conn,
+ fname,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req,
+ NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ return;
+ }
+ reply_nterror(req, status);
+ return;
+ }
+
+ info_level_handled = true; /* Untouched in switch cases below */
+
+ switch (info_level) {
+
+ default:
+ info_level_handled = false;
+ break;
+
+ case SMB_POSIX_PATH_OPEN:
+ status = smb_posix_open(conn,
+ req,
+ ppdata,
+ total_data,
+ dirfsp,
+ smb_fname,
+ &data_return_size);
+ break;
+
+ case SMB_POSIX_PATH_UNLINK:
+ status = smb_posix_unlink(conn,
+ req,
+ *ppdata,
+ total_data,
+ dirfsp,
+ smb_fname);
+ break;
+
+ case SMB_SET_FILE_UNIX_LINK:
+ status = smb_set_file_unix_link(
+ conn, req, *ppdata, total_data, smb_fname);
+ break;
+
+ case SMB_SET_FILE_UNIX_HLINK:
+ status = smb_set_file_unix_hlink(
+ conn, req, *ppdata, total_data, smb_fname);
+ break;
+
+ case SMB_SET_FILE_UNIX_BASIC:
+ status = smb_set_file_unix_basic(conn,
+ req,
+ *ppdata,
+ total_data,
+ dirfsp,
+ smb_fname->fsp,
+ smb_fname);
+ break;
+
+ case SMB_SET_FILE_UNIX_INFO2:
+ status = smb_set_file_unix_info2(conn,
+ req,
+ *ppdata,
+ total_data,
+ dirfsp,
+ smb_fname->fsp,
+ smb_fname);
+ break;
+ case SMB_SET_POSIX_ACL:
+ status = smb_set_posix_acl(
+ conn, req, *ppdata, total_data, NULL, smb_fname);
+ break;
+ }
+
+ if (info_level_handled) {
+ handle_trans2setfilepathinfo_result(
+ conn,
+ req,
+ info_level,
+ status,
+ *ppdata,
+ data_return_size,
+ max_data_bytes);
+ return;
+ }
+
+ /*
+ * smb_fname->fsp may be NULL if smb_fname points at a symlink
+ * and we're in POSIX context, so be careful when using fsp
+ * below, it can still be NULL.
+ */
+ fsp = smb_fname->fsp;
+
+ status = smbd_do_setfilepathinfo(
+ conn,
+ req,
+ req,
+ info_level,
+ fsp,
+ smb_fname,
+ ppdata,
+ total_data,
+ &data_return_size);
+
+ handle_trans2setfilepathinfo_result(
+ conn,
+ req,
+ info_level,
+ status,
+ *ppdata,
+ data_return_size,
+ max_data_bytes);
+}
+
+static void call_trans2setfileinfo(
+ connection_struct *conn,
+ struct smb_request *req,
+ char **pparams,
+ int total_params,
+ char **ppdata,
+ int total_data,
+ unsigned int max_data_bytes)
+{
+ char *pdata = *ppdata;
+ uint16_t info_level;
+ struct smb_filename *smb_fname = NULL;
+ struct files_struct *fsp = NULL;
+ char *params = *pparams;
+ int data_return_size = 0;
+ bool info_level_handled;
+ NTSTATUS status;
+ int ret;
+
+ if (params == NULL) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ if (total_params < 4) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(params,0));
+ /* Basic check for non-null fsp. */
+ if (!check_fsp_open(conn, req, fsp)) {
+ return;
+ }
+ info_level = SVAL(params,2);
+
+ if (INFO_LEVEL_IS_UNIX(info_level)) {
+ if (!lp_smb1_unix_extensions()) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+ if (!req->posix_pathnames) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+ }
+
+ smb_fname = fsp->fsp_name;
+
+ DBG_NOTICE("fnum=%s fname=%s info_level=%d totdata=%d\n",
+ fsp_fnum_dbg(fsp),
+ fsp_str_dbg(fsp),
+ info_level,
+ total_data);
+
+ if (fsp_get_pathref_fd(fsp) == -1) {
+ /*
+ * This is actually a SETFILEINFO on a directory
+ * handle (returned from an NT SMB). NT5.0 seems
+ * to do this call. JRA.
+ */
+ ret = vfs_stat(conn, smb_fname);
+ if (ret != 0) {
+ DBG_NOTICE("vfs_stat of %s failed (%s)\n",
+ smb_fname_str_dbg(smb_fname),
+ strerror(errno));
+ reply_nterror(req, map_nt_error_from_unix(errno));
+ return;
+ }
+ } else if (fsp->print_file) {
+ /*
+ * Doing a DELETE_ON_CLOSE should cancel a print job.
+ */
+ if ((info_level == SMB_SET_FILE_DISPOSITION_INFO) &&
+ CVAL(pdata,0)) {
+
+ fsp->fsp_flags.delete_on_close = true;
+
+ DBG_NOTICE("Cancelling print job (%s)\n",
+ fsp_str_dbg(fsp));
+
+ SSVAL(params,0,0);
+ send_trans2_replies(
+ conn,
+ req,
+ NT_STATUS_OK,
+ params,
+ 2,
+ *ppdata, 0,
+ max_data_bytes);
+ return;
+ } else {
+ reply_nterror(req, NT_STATUS_OBJECT_PATH_NOT_FOUND);
+ return;
+ }
+ } else {
+ /*
+ * Original code - this is an open file.
+ */
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_NOTICE("fstat of %s failed (%s)\n",
+ fsp_fnum_dbg(fsp),
+ nt_errstr(status));
+ reply_nterror(req, status);
+ return;
+ }
+ }
+
+ info_level_handled = true; /* Untouched in switch cases below */
+
+ switch (info_level) {
+
+ default:
+ info_level_handled = false;
+ break;
+
+ case SMB_SET_FILE_UNIX_BASIC:
+ status = smb_set_file_unix_basic(conn,
+ req,
+ pdata,
+ total_data,
+ NULL,
+ fsp,
+ smb_fname);
+ break;
+
+ case SMB_SET_FILE_UNIX_INFO2:
+ status = smb_set_file_unix_info2(conn,
+ req,
+ pdata,
+ total_data,
+ NULL,
+ fsp,
+ smb_fname);
+ break;
+
+ case SMB_SET_POSIX_LOCK:
+ status = smb_set_posix_lock(
+ conn, req, *ppdata, total_data, fsp);
+ break;
+ }
+
+ if (info_level_handled) {
+ handle_trans2setfilepathinfo_result(
+ conn,
+ req,
+ info_level,
+ status,
+ *ppdata,
+ data_return_size,
+ max_data_bytes);
+ return;
+ }
+
+ status = smbd_do_setfilepathinfo(
+ conn,
+ req,
+ req,
+ info_level,
+ fsp,
+ smb_fname,
+ ppdata,
+ total_data,
+ &data_return_size);
+
+ handle_trans2setfilepathinfo_result(
+ conn,
+ req,
+ info_level,
+ status,
+ *ppdata,
+ data_return_size,
+ max_data_bytes);
+}
+
+/****************************************************************************
+ Reply to a TRANS2_MKDIR (make directory with extended attributes).
+****************************************************************************/
+
+static void call_trans2mkdir(connection_struct *conn, struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ struct files_struct *dirfsp = NULL;
+ struct files_struct *fsp = NULL;
+ struct smb_filename *smb_dname = NULL;
+ char *params = *pparams;
+ char *pdata = *ppdata;
+ char *directory = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+ struct ea_list *ea_list = NULL;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+ NTTIME twrp = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ if (!CAN_WRITE(conn)) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+
+ if (total_params < 5) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if (req->posix_pathnames) {
+ srvstr_get_path_posix(ctx,
+ params,
+ req->flags2,
+ &directory,
+ &params[4],
+ total_params - 4,
+ STR_TERMINATE,
+ &status);
+ } else {
+ srvstr_get_path(ctx,
+ params,
+ req->flags2,
+ &directory,
+ &params[4],
+ total_params - 4,
+ STR_TERMINATE,
+ &status);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ DEBUG(3,("call_trans2mkdir : name = %s\n", directory));
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(directory, &twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &directory);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ directory,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_dname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req,
+ NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ return;
+ }
+ reply_nterror(req, status);
+ return;
+ }
+
+ /*
+ * OS/2 workplace shell seems to send SET_EA requests of "null"
+ * length (4 bytes containing IVAL 4).
+ * They seem to have no effect. Bug #3212. JRA.
+ */
+
+ if (total_data && (total_data != 4)) {
+ /* Any data in this call is an EA list. */
+ if (total_data < 10) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ if (IVAL(pdata,0) > total_data) {
+ DEBUG(10,("call_trans2mkdir: bad total data size (%u) > %u\n",
+ IVAL(pdata,0), (unsigned int)total_data));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ ea_list = read_ea_list(talloc_tos(), pdata + 4,
+ total_data - 4);
+ if (!ea_list) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ if (!lp_ea_support(SNUM(conn))) {
+ reply_nterror(req, NT_STATUS_EAS_NOT_SUPPORTED);
+ goto out;
+ }
+ }
+ /* If total_data == 4 Windows doesn't care what values
+ * are placed in that field, it just ignores them.
+ * The System i QNTC IBM SMB client puts bad values here,
+ * so ignore them. */
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_dname, /* fname */
+ MAXIMUM_ALLOWED_ACCESS, /* access_mask */
+ FILE_SHARE_NONE, /* share_access */
+ FILE_CREATE, /* create_disposition*/
+ FILE_DIRECTORY_FILE, /* create_options */
+ FILE_ATTRIBUTE_DIRECTORY, /* file_attributes */
+ 0, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* pinfo */
+ NULL, NULL); /* create context */
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ /* Try and set any given EA. */
+ if (ea_list) {
+ status = set_ea(conn, fsp, ea_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ }
+
+ /* Realloc the parameter and data sizes */
+ *pparams = (char *)SMB_REALLOC(*pparams,2);
+ if(*pparams == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ params = *pparams;
+
+ SSVAL(params,0,0);
+
+ send_trans2_replies(conn, req, NT_STATUS_OK, params, 2, *ppdata, 0, max_data_bytes);
+
+ out:
+ if (fsp != NULL) {
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ }
+ TALLOC_FREE(smb_dname);
+}
+
+/****************************************************************************
+ Reply to a TRANS2_FINDNOTIFYFIRST (start monitoring a directory for changes).
+ We don't actually do this - we just send a null response.
+****************************************************************************/
+
+static void call_trans2findnotifyfirst(connection_struct *conn,
+ struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ char *params = *pparams;
+ uint16_t info_level;
+
+ if (total_params < 6) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ info_level = SVAL(params,4);
+ DEBUG(3,("call_trans2findnotifyfirst - info_level %d\n", info_level));
+
+ switch (info_level) {
+ case 1:
+ case 2:
+ break;
+ default:
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ /* Realloc the parameter and data sizes */
+ *pparams = (char *)SMB_REALLOC(*pparams,6);
+ if (*pparams == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ params = *pparams;
+
+ SSVAL(params,0,fnf_handle);
+ SSVAL(params,2,0); /* No changes */
+ SSVAL(params,4,0); /* No EA errors */
+
+ fnf_handle++;
+
+ if(fnf_handle == 0)
+ fnf_handle = 257;
+
+ send_trans2_replies(conn, req, NT_STATUS_OK, params, 6, *ppdata, 0, max_data_bytes);
+}
+
+/****************************************************************************
+ Reply to a TRANS2_FINDNOTIFYNEXT (continue monitoring a directory for
+ changes). Currently this does nothing.
+****************************************************************************/
+
+static void call_trans2findnotifynext(connection_struct *conn,
+ struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ char *params = *pparams;
+
+ DEBUG(3,("call_trans2findnotifynext\n"));
+
+ /* Realloc the parameter and data sizes */
+ *pparams = (char *)SMB_REALLOC(*pparams,4);
+ if (*pparams == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ params = *pparams;
+
+ SSVAL(params,0,0); /* No changes */
+ SSVAL(params,2,0); /* No EA errors */
+
+ send_trans2_replies(conn, req, NT_STATUS_OK, params, 4, *ppdata, 0, max_data_bytes);
+}
+
+/****************************************************************************
+ Reply to a TRANS2_GET_DFS_REFERRAL - Shirish Kalele <kalele@veritas.com>.
+****************************************************************************/
+
+static void call_trans2getdfsreferral(connection_struct *conn,
+ struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ char *params = *pparams;
+ char *pathname = NULL;
+ int reply_size = 0;
+ int max_referral_level;
+ NTSTATUS status = NT_STATUS_OK;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ DEBUG(10,("call_trans2getdfsreferral\n"));
+
+ if (!IS_IPC(conn)) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+
+ if (total_params < 3) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ max_referral_level = SVAL(params,0);
+
+ if(!lp_host_msdfs()) {
+ reply_nterror(req, NT_STATUS_NOT_IMPLEMENTED);
+ return;
+ }
+
+ srvstr_pull_talloc(ctx, params, req->flags2, &pathname, &params[2],
+ total_params - 2, STR_TERMINATE);
+ if (!pathname) {
+ reply_nterror(req, NT_STATUS_NOT_FOUND);
+ return;
+ }
+ reply_size = setup_dfs_referral(
+ conn, pathname, max_referral_level, ppdata, &status);
+ if (reply_size < 0) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ SSVAL((discard_const_p(uint8_t, req->inbuf)), smb_flg2,
+ SVAL(req->inbuf,smb_flg2) | FLAGS2_DFS_PATHNAMES);
+ send_trans2_replies(conn, req, NT_STATUS_OK, 0,0,*ppdata,reply_size, max_data_bytes);
+}
+
+#define LMCAT_SPL 0x53
+#define LMFUNC_GETJOBID 0x60
+
+/****************************************************************************
+ Reply to a TRANS2_IOCTL - used for OS/2 printing.
+****************************************************************************/
+
+static void call_trans2ioctl(connection_struct *conn,
+ struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *pdata = *ppdata;
+ files_struct *fsp = file_fsp(req, SVAL(req->vwv+15, 0));
+ NTSTATUS status;
+ size_t len = 0;
+
+ /* check for an invalid fid before proceeding */
+
+ if (!fsp) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ if ((SVAL(req->vwv+16, 0) == LMCAT_SPL)
+ && (SVAL(req->vwv+17, 0) == LMFUNC_GETJOBID)) {
+ *ppdata = (char *)SMB_REALLOC(*ppdata, 32);
+ if (*ppdata == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ pdata = *ppdata;
+
+ /* NOTE - THIS IS ASCII ONLY AT THE MOMENT - NOT SURE IF OS/2
+ CAN ACCEPT THIS IN UNICODE. JRA. */
+
+ /* Job number */
+ SSVAL(pdata, 0, print_spool_rap_jobid(fsp->print_file));
+
+ status = srvstr_push(pdata, req->flags2, pdata + 2,
+ lp_netbios_name(), 15,
+ STR_ASCII|STR_TERMINATE, &len); /* Our NetBIOS name */
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+ status = srvstr_push(pdata, req->flags2, pdata+18,
+ lp_servicename(talloc_tos(), lp_sub, SNUM(conn)), 13,
+ STR_ASCII|STR_TERMINATE, &len); /* Service name */
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+ send_trans2_replies(conn, req, NT_STATUS_OK, *pparams, 0, *ppdata, 32,
+ max_data_bytes);
+ return;
+ }
+
+ DEBUG(2,("Unknown TRANS2_IOCTL\n"));
+ reply_nterror(req, NT_STATUS_NOT_IMPLEMENTED);
+}
+
+static void handle_trans2(connection_struct *conn, struct smb_request *req,
+ struct trans_state *state)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+
+ if (xconn->protocol >= PROTOCOL_NT1) {
+ req->flags2 |= 0x40; /* IS_LONG_NAME */
+ SSVAL((discard_const_p(uint8_t, req->inbuf)),smb_flg2,req->flags2);
+ }
+
+ if (ENCRYPTION_REQUIRED(conn) && !req->encrypted) {
+ if (state->call != TRANSACT2_QFSINFO &&
+ state->call != TRANSACT2_SETFSINFO) {
+ DEBUG(0,("handle_trans2: encryption required "
+ "with call 0x%x\n",
+ (unsigned int)state->call));
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+ }
+
+ /* Now we must call the relevant TRANS2 function */
+ switch(state->call) {
+ case TRANSACT2_OPEN:
+ {
+ START_PROFILE(Trans2_open);
+ call_trans2open(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_open);
+ break;
+ }
+
+ case TRANSACT2_FINDFIRST:
+ {
+ START_PROFILE(Trans2_findfirst);
+ call_trans2findfirst(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_findfirst);
+ break;
+ }
+
+ case TRANSACT2_FINDNEXT:
+ {
+ START_PROFILE(Trans2_findnext);
+ call_trans2findnext(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_findnext);
+ break;
+ }
+
+ case TRANSACT2_QFSINFO:
+ {
+ START_PROFILE(Trans2_qfsinfo);
+ call_trans2qfsinfo(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_qfsinfo);
+ break;
+ }
+
+ case TRANSACT2_SETFSINFO:
+ {
+ START_PROFILE(Trans2_setfsinfo);
+ call_trans2setfsinfo(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_setfsinfo);
+ break;
+ }
+
+ case TRANSACT2_QPATHINFO:
+ {
+ START_PROFILE(Trans2_qpathinfo);
+ call_trans2qpathinfo(
+ conn,
+ req,
+ &state->param,
+ state->total_param,
+ &state->data,
+ state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_qpathinfo);
+ break;
+ }
+
+ case TRANSACT2_QFILEINFO:
+ {
+ START_PROFILE(Trans2_qfileinfo);
+ call_trans2qfileinfo(
+ conn,
+ req,
+ &state->param,
+ state->total_param,
+ &state->data,
+ state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_qfileinfo);
+ break;
+ }
+
+ case TRANSACT2_SETPATHINFO:
+ {
+ START_PROFILE(Trans2_setpathinfo);
+ call_trans2setpathinfo(
+ conn,
+ req,
+ &state->param,
+ state->total_param,
+ &state->data,
+ state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_setpathinfo);
+ break;
+ }
+
+ case TRANSACT2_SETFILEINFO:
+ {
+ START_PROFILE(Trans2_setfileinfo);
+ call_trans2setfileinfo(
+ conn,
+ req,
+ &state->param,
+ state->total_param,
+ &state->data,
+ state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_setfileinfo);
+ break;
+ }
+
+ case TRANSACT2_FINDNOTIFYFIRST:
+ {
+ START_PROFILE(Trans2_findnotifyfirst);
+ call_trans2findnotifyfirst(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_findnotifyfirst);
+ break;
+ }
+
+ case TRANSACT2_FINDNOTIFYNEXT:
+ {
+ START_PROFILE(Trans2_findnotifynext);
+ call_trans2findnotifynext(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_findnotifynext);
+ break;
+ }
+
+ case TRANSACT2_MKDIR:
+ {
+ START_PROFILE(Trans2_mkdir);
+ call_trans2mkdir(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_mkdir);
+ break;
+ }
+
+ case TRANSACT2_GET_DFS_REFERRAL:
+ {
+ START_PROFILE(Trans2_get_dfs_referral);
+ call_trans2getdfsreferral(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_get_dfs_referral);
+ break;
+ }
+
+ case TRANSACT2_IOCTL:
+ {
+ START_PROFILE(Trans2_ioctl);
+ call_trans2ioctl(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_ioctl);
+ break;
+ }
+
+ default:
+ /* Error in request */
+ DEBUG(2,("Unknown request %d in trans2 call\n", state->call));
+ reply_nterror(req, NT_STATUS_NOT_IMPLEMENTED);
+ }
+}
+
+/****************************************************************************
+ Reply to a SMBtrans2.
+ ****************************************************************************/
+
+void reply_trans2(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ unsigned int dsoff;
+ unsigned int dscnt;
+ unsigned int psoff;
+ unsigned int pscnt;
+ unsigned int tran_call;
+ struct trans_state *state;
+ NTSTATUS result;
+
+ START_PROFILE(SMBtrans2);
+
+ if (req->wct < 14) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtrans2);
+ return;
+ }
+
+ dsoff = SVAL(req->vwv+12, 0);
+ dscnt = SVAL(req->vwv+11, 0);
+ psoff = SVAL(req->vwv+10, 0);
+ pscnt = SVAL(req->vwv+9, 0);
+ tran_call = SVAL(req->vwv+14, 0);
+
+ result = allow_new_trans(conn->pending_trans, req->mid);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(2, ("Got invalid trans2 request: %s\n",
+ nt_errstr(result)));
+ reply_nterror(req, result);
+ END_PROFILE(SMBtrans2);
+ return;
+ }
+
+ if (IS_IPC(conn)) {
+ switch (tran_call) {
+ /* List the allowed trans2 calls on IPC$ */
+ case TRANSACT2_OPEN:
+ case TRANSACT2_GET_DFS_REFERRAL:
+ case TRANSACT2_QFILEINFO:
+ case TRANSACT2_QFSINFO:
+ case TRANSACT2_SETFSINFO:
+ break;
+ default:
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ END_PROFILE(SMBtrans2);
+ return;
+ }
+ }
+
+ if ((state = talloc(conn, struct trans_state)) == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtrans2);
+ return;
+ }
+
+ state->cmd = SMBtrans2;
+
+ state->mid = req->mid;
+ state->vuid = req->vuid;
+ state->setup_count = SVAL(req->vwv+13, 0);
+ state->setup = NULL;
+ state->total_param = SVAL(req->vwv+0, 0);
+ state->param = NULL;
+ state->total_data = SVAL(req->vwv+1, 0);
+ state->data = NULL;
+ state->max_param_return = SVAL(req->vwv+2, 0);
+ state->max_data_return = SVAL(req->vwv+3, 0);
+ state->max_setup_return = SVAL(req->vwv+4, 0);
+ state->close_on_completion = BITSETW(req->vwv+5, 0);
+ state->one_way = BITSETW(req->vwv+5, 1);
+
+ state->call = tran_call;
+
+ /* All trans2 messages we handle have smb_sucnt == 1 - ensure this
+ is so as a sanity check */
+ if (state->setup_count != 1) {
+ /*
+ * Need to have rc=0 for ioctl to get job id for OS/2.
+ * Network printing will fail if function is not successful.
+ * Similar function in reply.c will be used if protocol
+ * is LANMAN1.0 instead of LM1.2X002.
+ * Until DosPrintSetJobInfo with PRJINFO3 is supported,
+ * outbuf doesn't have to be set(only job id is used).
+ */
+ if ( (state->setup_count == 4)
+ && (tran_call == TRANSACT2_IOCTL)
+ && (SVAL(req->vwv+16, 0) == LMCAT_SPL)
+ && (SVAL(req->vwv+17, 0) == LMFUNC_GETJOBID)) {
+ DEBUG(2,("Got Trans2 DevIOctl jobid\n"));
+ } else {
+ DEBUG(2,("Invalid smb_sucnt in trans2 call(%u)\n",state->setup_count));
+ DEBUG(2,("Transaction is %d\n",tran_call));
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtrans2);
+ return;
+ }
+ }
+
+ if ((dscnt > state->total_data) || (pscnt > state->total_param))
+ goto bad_param;
+
+ if (state->total_data) {
+
+ if (smb_buffer_oob(state->total_data, 0, dscnt)
+ || smb_buffer_oob(smb_len(req->inbuf), dsoff, dscnt)) {
+ goto bad_param;
+ }
+
+ /* Can't use talloc here, the core routines do realloc on the
+ * params and data. */
+ state->data = (char *)SMB_MALLOC(state->total_data);
+ if (state->data == NULL) {
+ DEBUG(0,("reply_trans2: data malloc fail for %u "
+ "bytes !\n", (unsigned int)state->total_data));
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtrans2);
+ return;
+ }
+
+ memcpy(state->data,smb_base(req->inbuf)+dsoff,dscnt);
+ }
+
+ if (state->total_param) {
+
+ if (smb_buffer_oob(state->total_param, 0, pscnt)
+ || smb_buffer_oob(smb_len(req->inbuf), psoff, pscnt)) {
+ goto bad_param;
+ }
+
+ /* Can't use talloc here, the core routines do realloc on the
+ * params and data. */
+ state->param = (char *)SMB_MALLOC(state->total_param);
+ if (state->param == NULL) {
+ DEBUG(0,("reply_trans: param malloc fail for %u "
+ "bytes !\n", (unsigned int)state->total_param));
+ SAFE_FREE(state->data);
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtrans2);
+ return;
+ }
+
+ memcpy(state->param,smb_base(req->inbuf)+psoff,pscnt);
+ }
+
+ state->received_data = dscnt;
+ state->received_param = pscnt;
+
+ if ((state->received_param == state->total_param) &&
+ (state->received_data == state->total_data)) {
+
+ handle_trans2(conn, req, state);
+
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ END_PROFILE(SMBtrans2);
+ return;
+ }
+
+ DLIST_ADD(conn->pending_trans, state);
+
+ /* We need to send an interim response then receive the rest
+ of the parameter/data bytes */
+ reply_smb1_outbuf(req, 0, 0);
+ show_msg((char *)req->outbuf);
+ END_PROFILE(SMBtrans2);
+ return;
+
+ bad_param:
+
+ DEBUG(0,("reply_trans2: invalid trans parameters\n"));
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ END_PROFILE(SMBtrans2);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+}
+
+/****************************************************************************
+ Reply to a SMBtranss2
+ ****************************************************************************/
+
+void reply_transs2(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ unsigned int pcnt,poff,dcnt,doff,pdisp,ddisp;
+ struct trans_state *state;
+
+ START_PROFILE(SMBtranss2);
+
+ show_msg((const char *)req->inbuf);
+
+ /* Windows clients expect all replies to
+ a transact secondary (SMBtranss2 0x33)
+ to have a command code of transact
+ (SMBtrans2 0x32). See bug #8989
+ and also [MS-CIFS] section 2.2.4.47.2
+ for details.
+ */
+ req->cmd = SMBtrans2;
+
+ if (req->wct < 8) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtranss2);
+ return;
+ }
+
+ for (state = conn->pending_trans; state != NULL;
+ state = state->next) {
+ if (state->mid == req->mid) {
+ break;
+ }
+ }
+
+ if ((state == NULL) || (state->cmd != SMBtrans2)) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtranss2);
+ return;
+ }
+
+ /* Revise state->total_param and state->total_data in case they have
+ changed downwards */
+
+ if (SVAL(req->vwv+0, 0) < state->total_param)
+ state->total_param = SVAL(req->vwv+0, 0);
+ if (SVAL(req->vwv+1, 0) < state->total_data)
+ state->total_data = SVAL(req->vwv+1, 0);
+
+ pcnt = SVAL(req->vwv+2, 0);
+ poff = SVAL(req->vwv+3, 0);
+ pdisp = SVAL(req->vwv+4, 0);
+
+ dcnt = SVAL(req->vwv+5, 0);
+ doff = SVAL(req->vwv+6, 0);
+ ddisp = SVAL(req->vwv+7, 0);
+
+ state->received_param += pcnt;
+ state->received_data += dcnt;
+
+ if ((state->received_data > state->total_data) ||
+ (state->received_param > state->total_param))
+ goto bad_param;
+
+ if (pcnt) {
+ if (smb_buffer_oob(state->total_param, pdisp, pcnt)
+ || smb_buffer_oob(smb_len(req->inbuf), poff, pcnt)) {
+ goto bad_param;
+ }
+ memcpy(state->param+pdisp,smb_base(req->inbuf)+poff,pcnt);
+ }
+
+ if (dcnt) {
+ if (smb_buffer_oob(state->total_data, ddisp, dcnt)
+ || smb_buffer_oob(smb_len(req->inbuf), doff, dcnt)) {
+ goto bad_param;
+ }
+ memcpy(state->data+ddisp, smb_base(req->inbuf)+doff,dcnt);
+ }
+
+ if ((state->received_param < state->total_param) ||
+ (state->received_data < state->total_data)) {
+ END_PROFILE(SMBtranss2);
+ return;
+ }
+
+ handle_trans2(conn, req, state);
+
+ DLIST_REMOVE(conn->pending_trans, state);
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+
+ END_PROFILE(SMBtranss2);
+ return;
+
+ bad_param:
+
+ DEBUG(0,("reply_transs2: invalid trans parameters\n"));
+ DLIST_REMOVE(conn->pending_trans, state);
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtranss2);
+}
diff --git a/source3/smbd/smb1_trans2.h b/source3/smbd/smb1_trans2.h
new file mode 100644
index 0000000..4b8b4b1
--- /dev/null
+++ b/source3/smbd/smb1_trans2.h
@@ -0,0 +1,27 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB transaction2 handling
+ Copyright (C) Jeremy Allison 1994-2007
+ Copyright (C) Stefan (metze) Metzmacher 2003
+ Copyright (C) Volker Lendecke 2005-2007
+ Copyright (C) Steve French 2005
+ Copyright (C) James Peach 2006-2007
+
+ Extensively modified by Andrew Tridgell, 1995
+
+ 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/>.
+*/
+
+void reply_trans2(struct smb_request *req);
+void reply_transs2(struct smb_request *req);
diff --git a/source3/smbd/smb1_utils.c b/source3/smbd/smb1_utils.c
new file mode 100644
index 0000000..bdd842c
--- /dev/null
+++ b/source3/smbd/smb1_utils.c
@@ -0,0 +1,261 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Util functions valid in the SMB1 server
+ *
+ * Copyright (C) Volker Lendecke 2019
+ * Copyright by the authors of the functions moved here eventually
+ *
+ * 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "libcli/security/security.h"
+#include "lib/util/sys_rw_data.h"
+#include "smbd/fd_handle.h"
+
+/****************************************************************************
+ Special FCB or DOS processing in the case of a sharing violation.
+ Try and find a duplicated file handle.
+****************************************************************************/
+
+struct files_struct *fcb_or_dos_open(
+ struct smb_request *req,
+ const struct smb_filename *smb_fname,
+ uint32_t access_mask,
+ uint32_t create_options,
+ uint32_t private_flags)
+{
+ struct connection_struct *conn = req->conn;
+ struct file_id id = vfs_file_id_from_sbuf(conn, &smb_fname->st);
+ struct files_struct *fsp = NULL, *new_fsp = NULL;
+ NTSTATUS status;
+
+ if ((private_flags &
+ (NTCREATEX_FLAG_DENY_DOS|
+ NTCREATEX_FLAG_DENY_FCB))
+ == 0) {
+ return NULL;
+ }
+
+ for(fsp = file_find_di_first(conn->sconn, id, true);
+ fsp != NULL;
+ fsp = file_find_di_next(fsp, true)) {
+
+ DBG_DEBUG("Checking file %s, fd = %d, vuid = %"PRIu64", "
+ "file_pid = %"PRIu16", "
+ "private_options = 0x%"PRIx32", "
+ "access_mask = 0x%"PRIx32"\n",
+ fsp_str_dbg(fsp),
+ fsp_get_pathref_fd(fsp),
+ fsp->vuid,
+ fsp->file_pid,
+ fh_get_private_options(fsp->fh),
+ fsp->access_mask);
+
+ if (fsp_get_pathref_fd(fsp) != -1 &&
+ fsp->vuid == req->vuid &&
+ fsp->file_pid == req->smbpid &&
+ (fh_get_private_options(fsp->fh) &
+ (NTCREATEX_FLAG_DENY_DOS |
+ NTCREATEX_FLAG_DENY_FCB)) &&
+ (fsp->access_mask & FILE_WRITE_DATA) &&
+ strequal(fsp->fsp_name->base_name, smb_fname->base_name) &&
+ strequal(fsp->fsp_name->stream_name,
+ smb_fname->stream_name)) {
+ DBG_DEBUG("file match\n");
+ break;
+ }
+ }
+
+ if (fsp == NULL) {
+ return NULL;
+ }
+
+ /* quite an insane set of semantics ... */
+ if (is_executable(smb_fname->base_name) &&
+ (fh_get_private_options(fsp->fh) & NTCREATEX_FLAG_DENY_DOS)) {
+ DBG_DEBUG("file fail due to is_executable.\n");
+ return NULL;
+ }
+
+ status = file_new(req, conn, &new_fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("file_new failed: %s\n", nt_errstr(status));
+ return NULL;
+ }
+
+ status = dup_file_fsp(fsp, access_mask, new_fsp);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dup_file_fsp failed: %s\n", nt_errstr(status));
+ file_free(req, new_fsp);
+ return NULL;
+ }
+
+ return new_fsp;
+}
+
+/****************************************************************************
+ Send a keepalive packet (rfc1002).
+****************************************************************************/
+
+bool send_keepalive(int client)
+{
+ unsigned char buf[4];
+
+ buf[0] = NBSSkeepalive;
+ buf[1] = buf[2] = buf[3] = 0;
+
+ return(write_data(client,(char *)buf,4) == 4);
+}
+
+/*******************************************************************
+ Add a string to the end of a smb_buf, adjusting bcc and smb_len.
+ Return the bytes added
+********************************************************************/
+
+ssize_t message_push_string(uint8_t **outbuf, const char *str, int flags)
+{
+ size_t buf_size = smb_len(*outbuf) + 4;
+ size_t grow_size;
+ size_t result = 0;
+ uint8_t *tmp;
+ NTSTATUS status;
+
+ /*
+ * We need to over-allocate, now knowing what srvstr_push will
+ * actually use. This is very generous by incorporating potential
+ * padding, the terminating 0 and at most 4 chars per UTF-16 code
+ * point.
+ */
+ grow_size = (strlen(str) + 2) * 4;
+
+ if (!(tmp = talloc_realloc(NULL, *outbuf, uint8_t,
+ buf_size + grow_size))) {
+ DEBUG(0, ("talloc failed\n"));
+ return -1;
+ }
+
+ status = srvstr_push((char *)tmp, SVAL(tmp, smb_flg2),
+ tmp + buf_size, str, grow_size, flags, &result);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("srvstr_push failed\n"));
+ return -1;
+ }
+
+ /*
+ * Ensure we clear out the extra data we have
+ * grown the buffer by, but not written to.
+ */
+ if (buf_size + result < buf_size) {
+ return -1;
+ }
+ if (grow_size < result) {
+ return -1;
+ }
+
+ memset(tmp + buf_size + result, '\0', grow_size - result);
+
+ set_message_bcc((char *)tmp, smb_buflen(tmp) + result);
+
+ *outbuf = tmp;
+
+ return result;
+}
+
+/*
+ * Deal with the SMB1 semantics of sending a pathname with a
+ * wildcard as the terminal component for a SMB1search or
+ * trans2 findfirst.
+ */
+
+NTSTATUS filename_convert_smb1_search_path(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ char *name_in,
+ uint32_t ucf_flags,
+ struct files_struct **_dirfsp,
+ struct smb_filename **_smb_fname_out,
+ char **_mask_out)
+{
+ NTSTATUS status;
+ char *p = NULL;
+ char *mask = NULL;
+ struct smb_filename *smb_fname = NULL;
+ NTTIME twrp = 0;
+
+ *_smb_fname_out = NULL;
+ *_dirfsp = NULL;
+ *_mask_out = NULL;
+
+ DBG_DEBUG("name_in: %s\n", name_in);
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(name_in, &twrp);
+ ucf_flags &= ~UCF_GMT_PATHNAME;
+ }
+
+ /* Get the original lcomp. */
+ mask = get_original_lcomp(ctx, conn, name_in, ucf_flags);
+ if (mask == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (mask[0] == '\0') {
+ /* Windows and OS/2 systems treat search on the root as * */
+ TALLOC_FREE(mask);
+ mask = talloc_strdup(ctx, "*");
+ if (mask == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ DBG_DEBUG("mask = %s\n", mask);
+
+ /*
+ * Remove the terminal component so
+ * filename_convert_dirfsp never sees the mask.
+ */
+ p = strrchr_m(name_in, '/');
+ if (p == NULL) {
+ /* filename_convert_dirfsp handles a '\0' name. */
+ name_in[0] = '\0';
+ } else {
+ *p = '\0';
+ }
+
+ DBG_DEBUG("For filename_convert_dirfsp: name_in = %s\n", name_in);
+
+ /* Convert the parent directory path. */
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ name_in,
+ ucf_flags,
+ twrp,
+ _dirfsp,
+ &smb_fname);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("filename_convert error for %s: %s\n",
+ name_in,
+ nt_errstr(status));
+ }
+
+ *_smb_fname_out = talloc_move(ctx, &smb_fname);
+ *_mask_out = talloc_move(ctx, &mask);
+
+ return status;
+}
diff --git a/source3/smbd/smb1_utils.h b/source3/smbd/smb1_utils.h
new file mode 100644
index 0000000..539648e
--- /dev/null
+++ b/source3/smbd/smb1_utils.h
@@ -0,0 +1,46 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Util functions valid in the SMB1 server
+ *
+ * Copyright (C) Volker Lendecke 2019
+ * Copyright by the authors of the functions moved here eventually
+ *
+ * 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/>.
+ */
+
+#ifndef __SMBD_SMB1_UTILS_H__
+#define __SMBD_SMB1_UTILS_H__
+
+#include "includes.h"
+#include "vfs.h"
+#include "proto.h"
+#include "lib/util/string_wrappers.h"
+
+struct files_struct *fcb_or_dos_open(
+ struct smb_request *req,
+ const struct smb_filename *smb_fname,
+ uint32_t access_mask,
+ uint32_t create_options,
+ uint32_t private_flags);
+bool send_keepalive(int client);
+ssize_t message_push_string(uint8_t **outbuf, const char *str, int flags);
+NTSTATUS filename_convert_smb1_search_path(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ char *name_in,
+ uint32_t ucf_flags,
+ struct files_struct **_dirfsp,
+ struct smb_filename **_smb_fname_out,
+ char **_mask_out);
+
+#endif
diff --git a/source3/smbd/smb2_aio.c b/source3/smbd/smb2_aio.c
new file mode 100644
index 0000000..88aa68d
--- /dev/null
+++ b/source3/smbd/smb2_aio.c
@@ -0,0 +1,608 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 3.0
+ async_io read handling using POSIX async io.
+ Copyright (C) Jeremy Allison 2005.
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "../lib/util/tevent_unix.h"
+
+/****************************************************************************
+ Accessor function to return write_through state.
+*****************************************************************************/
+
+bool aio_write_through_requested(struct aio_extra *aio_ex)
+{
+ return aio_ex->write_through;
+}
+
+/****************************************************************************
+ Create the extended aio struct we must keep around for the lifetime
+ of the aio call.
+*****************************************************************************/
+
+struct aio_extra *create_aio_extra(TALLOC_CTX *mem_ctx,
+ files_struct *fsp,
+ size_t buflen)
+{
+ struct aio_extra *aio_ex = talloc_zero(mem_ctx, struct aio_extra);
+
+ if (!aio_ex) {
+ return NULL;
+ }
+
+ /* The output buffer stored in the aio_ex is the start of
+ the smb return buffer. The buffer used in the acb
+ is the start of the reply data portion of that buffer. */
+
+ if (buflen) {
+ aio_ex->outbuf = data_blob_talloc(aio_ex, NULL, buflen);
+ if (!aio_ex->outbuf.data) {
+ TALLOC_FREE(aio_ex);
+ return NULL;
+ }
+ }
+ aio_ex->fsp = fsp;
+ return aio_ex;
+}
+
+struct aio_req_fsp_link {
+#ifdef DEVELOPER
+ struct smbd_server_connection *sconn;
+#endif
+ files_struct *fsp;
+ struct tevent_req *req;
+};
+
+static int aio_del_req_from_fsp(struct aio_req_fsp_link *lnk)
+{
+ unsigned i;
+ files_struct *fsp = lnk->fsp;
+ struct tevent_req *req = lnk->req;
+
+#ifdef DEVELOPER
+ struct files_struct *ifsp = NULL;
+ bool found = false;
+
+ /*
+ * When this is called, lnk->fsp must still exist
+ * on the files list for this connection. Panic if not.
+ */
+ for (ifsp = lnk->sconn->files; ifsp; ifsp = ifsp->next) {
+ if (ifsp == fsp) {
+ found = true;
+ }
+ }
+ if (!found) {
+ smb_panic("orphaned lnk on fsp aio list.\n");
+ }
+#endif
+
+ for (i=0; i<fsp->num_aio_requests; i++) {
+ if (fsp->aio_requests[i] == req) {
+ break;
+ }
+ }
+ if (i == fsp->num_aio_requests) {
+ DEBUG(1, ("req %p not found in fsp %p\n", req, fsp));
+ return 0;
+ }
+ fsp->num_aio_requests -= 1;
+ fsp->aio_requests[i] = fsp->aio_requests[fsp->num_aio_requests];
+
+ if (fsp->num_aio_requests == 0) {
+ TALLOC_FREE(fsp->aio_requests);
+ }
+ return 0;
+}
+
+bool aio_add_req_to_fsp(files_struct *fsp, struct tevent_req *req)
+{
+ size_t array_len;
+ struct aio_req_fsp_link *lnk;
+
+ lnk = talloc(req, struct aio_req_fsp_link);
+ if (lnk == NULL) {
+ return false;
+ }
+
+ array_len = talloc_array_length(fsp->aio_requests);
+ if (array_len <= fsp->num_aio_requests) {
+ struct tevent_req **tmp;
+
+ if (fsp->num_aio_requests + 10 < 10) {
+ /* Integer wrap. */
+ TALLOC_FREE(lnk);
+ return false;
+ }
+
+ /*
+ * Allocate in blocks of 10 so we don't allocate
+ * on every aio request.
+ */
+ tmp = talloc_realloc(
+ fsp, fsp->aio_requests, struct tevent_req *,
+ fsp->num_aio_requests+10);
+ if (tmp == NULL) {
+ TALLOC_FREE(lnk);
+ return false;
+ }
+ fsp->aio_requests = tmp;
+ }
+ fsp->aio_requests[fsp->num_aio_requests] = req;
+ fsp->num_aio_requests += 1;
+
+ lnk->fsp = fsp;
+ lnk->req = req;
+#ifdef DEVELOPER
+ lnk->sconn = fsp->conn->sconn;
+#endif
+ talloc_set_destructor(lnk, aio_del_req_from_fsp);
+
+ return true;
+}
+
+struct pwrite_fsync_state {
+ struct tevent_context *ev;
+ files_struct *fsp;
+ bool write_through;
+ ssize_t nwritten;
+};
+
+static void pwrite_fsync_write_done(struct tevent_req *subreq);
+static void pwrite_fsync_sync_done(struct tevent_req *subreq);
+
+struct tevent_req *pwrite_fsync_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ const void *data,
+ size_t n, off_t offset,
+ bool write_through)
+{
+ struct tevent_req *req, *subreq;
+ struct pwrite_fsync_state *state;
+ bool ok;
+
+ req = tevent_req_create(mem_ctx, &state, struct pwrite_fsync_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->fsp = fsp;
+ state->write_through = write_through;
+
+ ok = vfs_valid_pwrite_range(offset, n);
+ if (!ok) {
+ tevent_req_error(req, EINVAL);
+ return tevent_req_post(req, ev);
+ }
+
+ if (n == 0) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = SMB_VFS_PWRITE_SEND(state, ev, fsp, data, n, offset);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, pwrite_fsync_write_done, req);
+ return req;
+}
+
+static void pwrite_fsync_write_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct pwrite_fsync_state *state = tevent_req_data(
+ req, struct pwrite_fsync_state);
+ connection_struct *conn = state->fsp->conn;
+ bool do_sync;
+ struct vfs_aio_state vfs_aio_state;
+
+ state->nwritten = SMB_VFS_PWRITE_RECV(subreq, &vfs_aio_state);
+ TALLOC_FREE(subreq);
+ if (state->nwritten == -1) {
+ tevent_req_error(req, vfs_aio_state.error);
+ return;
+ }
+
+ do_sync = (lp_strict_sync(SNUM(conn)) &&
+ (lp_sync_always(SNUM(conn)) || state->write_through));
+ if (!do_sync) {
+ tevent_req_done(req);
+ return;
+ }
+
+ subreq = SMB_VFS_FSYNC_SEND(state, state->ev, state->fsp);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, pwrite_fsync_sync_done, req);
+}
+
+static void pwrite_fsync_sync_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ int ret;
+ struct vfs_aio_state vfs_aio_state;
+
+ ret = SMB_VFS_FSYNC_RECV(subreq, &vfs_aio_state);
+ TALLOC_FREE(subreq);
+ if (ret == -1) {
+ tevent_req_error(req, vfs_aio_state.error);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+ssize_t pwrite_fsync_recv(struct tevent_req *req, int *perr)
+{
+ struct pwrite_fsync_state *state = tevent_req_data(
+ req, struct pwrite_fsync_state);
+
+ if (tevent_req_is_unix_error(req, perr)) {
+ return -1;
+ }
+ return state->nwritten;
+}
+
+bool cancel_smb2_aio(struct smb_request *smbreq)
+{
+ struct smbd_smb2_request *smb2req = smbreq->smb2req;
+ struct aio_extra *aio_ex = NULL;
+
+ if (smb2req) {
+ aio_ex = talloc_get_type(smbreq->async_priv,
+ struct aio_extra);
+ }
+
+ if (aio_ex == NULL) {
+ return false;
+ }
+
+ if (aio_ex->fsp == NULL) {
+ return false;
+ }
+
+ /*
+ * We let the aio request run and don't try to cancel it which means
+ * processing of the SMB2 request must continue as normal, cf MS-SMB2
+ * 3.3.5.16:
+ *
+ * If the target request is not successfully canceled, processing of
+ * the target request MUST continue and no response is sent to the
+ * cancel request.
+ */
+
+ return false;
+}
+
+static void aio_pread_smb2_done(struct tevent_req *req);
+
+/****************************************************************************
+ Set up an aio request from a SMB2 read call.
+*****************************************************************************/
+
+NTSTATUS schedule_smb2_aio_read(connection_struct *conn,
+ struct smb_request *smbreq,
+ files_struct *fsp,
+ TALLOC_CTX *ctx,
+ DATA_BLOB *preadbuf,
+ off_t startpos,
+ size_t smb_maxcnt)
+{
+ struct aio_extra *aio_ex;
+ size_t min_aio_read_size = lp_aio_read_size(SNUM(conn));
+ struct tevent_req *req;
+ bool is_compound = false;
+ bool is_last_in_compound = false;
+ bool ok;
+
+ ok = vfs_valid_pread_range(startpos, smb_maxcnt);
+ if (!ok) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (fsp_is_alternate_stream(fsp)) {
+ DEBUG(10, ("AIO on streams not yet supported\n"));
+ return NT_STATUS_RETRY;
+ }
+
+ if (fsp->op == NULL) {
+ /* No AIO on internal opens. */
+ return NT_STATUS_RETRY;
+ }
+
+ if ((!min_aio_read_size || (smb_maxcnt < min_aio_read_size))
+ && !SMB_VFS_AIO_FORCE(fsp)) {
+ /* Too small a read for aio request. */
+ DEBUG(10,("smb2: read size (%u) too small "
+ "for minimum aio_read of %u\n",
+ (unsigned int)smb_maxcnt,
+ (unsigned int)min_aio_read_size ));
+ return NT_STATUS_RETRY;
+ }
+
+ is_compound = smbd_smb2_is_compound(smbreq->smb2req);
+ is_last_in_compound = smbd_smb2_is_last_in_compound(smbreq->smb2req);
+
+ if (is_compound && !is_last_in_compound) {
+ /*
+ * Only allow going async if this is the last
+ * request in a compound.
+ */
+ return NT_STATUS_RETRY;
+ }
+
+ /* Create the out buffer. */
+ *preadbuf = data_blob_talloc(ctx, NULL, smb_maxcnt);
+ if (preadbuf->data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!(aio_ex = create_aio_extra(smbreq->smb2req, fsp, 0))) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ init_strict_lock_struct(fsp,
+ fsp->op->global->open_persistent_id,
+ (uint64_t)startpos,
+ (uint64_t)smb_maxcnt,
+ READ_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &aio_ex->lock);
+
+ /* Take the lock until the AIO completes. */
+ if (!SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, &aio_ex->lock)) {
+ TALLOC_FREE(aio_ex);
+ return NT_STATUS_FILE_LOCK_CONFLICT;
+ }
+
+ aio_ex->nbyte = smb_maxcnt;
+ aio_ex->offset = startpos;
+
+ req = SMB_VFS_PREAD_SEND(aio_ex, fsp->conn->sconn->ev_ctx, fsp,
+ preadbuf->data, smb_maxcnt, startpos);
+ if (req == NULL) {
+ DEBUG(0, ("smb2: SMB_VFS_PREAD_SEND failed. "
+ "Error %s\n", strerror(errno)));
+ TALLOC_FREE(aio_ex);
+ return NT_STATUS_RETRY;
+ }
+ tevent_req_set_callback(req, aio_pread_smb2_done, aio_ex);
+
+ if (!aio_add_req_to_fsp(fsp, req)) {
+ DEBUG(1, ("Could not add req to fsp\n"));
+ TALLOC_FREE(aio_ex);
+ return NT_STATUS_RETRY;
+ }
+
+ /* We don't need talloc_move here as both aio_ex and
+ * smbreq are children of smbreq->smb2req. */
+ aio_ex->smbreq = smbreq;
+ smbreq->async_priv = aio_ex;
+
+ DEBUG(10,("smb2: scheduled aio_read for file %s, "
+ "offset %.0f, len = %u (mid = %u)\n",
+ fsp_str_dbg(fsp), (double)startpos, (unsigned int)smb_maxcnt,
+ (unsigned int)aio_ex->smbreq->mid ));
+
+ return NT_STATUS_OK;
+}
+
+static void aio_pread_smb2_done(struct tevent_req *req)
+{
+ struct aio_extra *aio_ex = tevent_req_callback_data(
+ req, struct aio_extra);
+ struct tevent_req *subreq = aio_ex->smbreq->smb2req->subreq;
+ files_struct *fsp = aio_ex->fsp;
+ NTSTATUS status;
+ ssize_t nread;
+ struct vfs_aio_state vfs_aio_state = { 0 };
+
+ nread = SMB_VFS_PREAD_RECV(req, &vfs_aio_state);
+ TALLOC_FREE(req);
+
+ DEBUG(10, ("pread_recv returned %d, err = %s\n", (int)nread,
+ (nread == -1) ? strerror(vfs_aio_state.error) : "no error"));
+
+ /* Common error or success code processing for async or sync
+ read returns. */
+
+ status = smb2_read_complete(subreq, nread, vfs_aio_state.error);
+
+ if (nread > 0) {
+ fh_set_pos(fsp->fh, aio_ex->offset + nread);
+ fh_set_position_information(fsp->fh,
+ fh_get_pos(fsp->fh));
+ }
+
+ DEBUG(10, ("smb2: scheduled aio_read completed "
+ "for file %s, offset %.0f, len = %u "
+ "(errcode = %d, NTSTATUS = %s)\n",
+ fsp_str_dbg(aio_ex->fsp),
+ (double)aio_ex->offset,
+ (unsigned int)nread,
+ vfs_aio_state.error, nt_errstr(status)));
+
+ if (tevent_req_nterror(subreq, status)) {
+ return;
+ }
+ tevent_req_done(subreq);
+}
+
+static void aio_pwrite_smb2_done(struct tevent_req *req);
+
+/****************************************************************************
+ Set up an aio request from a SMB2write call.
+*****************************************************************************/
+
+NTSTATUS schedule_aio_smb2_write(connection_struct *conn,
+ struct smb_request *smbreq,
+ files_struct *fsp,
+ uint64_t in_offset,
+ DATA_BLOB in_data,
+ bool write_through)
+{
+ struct aio_extra *aio_ex = NULL;
+ size_t min_aio_write_size = lp_aio_write_size(SNUM(conn));
+ struct tevent_req *req;
+ bool is_compound = false;
+ bool is_last_in_compound = false;
+
+ if (fsp_is_alternate_stream(fsp)) {
+ /* No AIO on streams yet */
+ DEBUG(10, ("AIO on streams not yet supported\n"));
+ return NT_STATUS_RETRY;
+ }
+
+ if (fsp->op == NULL) {
+ /* No AIO on internal opens. */
+ return NT_STATUS_RETRY;
+ }
+
+ if ((!min_aio_write_size || (in_data.length < min_aio_write_size))
+ && !SMB_VFS_AIO_FORCE(fsp)) {
+ /* Too small a write for aio request. */
+ DEBUG(10,("smb2: write size (%u) too "
+ "small for minimum aio_write of %u\n",
+ (unsigned int)in_data.length,
+ (unsigned int)min_aio_write_size ));
+ return NT_STATUS_RETRY;
+ }
+
+ is_compound = smbd_smb2_is_compound(smbreq->smb2req);
+ is_last_in_compound = smbd_smb2_is_last_in_compound(smbreq->smb2req);
+
+ if (is_compound && !is_last_in_compound) {
+ /*
+ * Only allow going async if this is the last
+ * request in a compound.
+ */
+ return NT_STATUS_RETRY;
+ }
+
+ if (smbreq->unread_bytes) {
+ /* Can't do async with recvfile. */
+ return NT_STATUS_RETRY;
+ }
+
+ if (!(aio_ex = create_aio_extra(smbreq->smb2req, fsp, 0))) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ aio_ex->write_through = write_through;
+
+ init_strict_lock_struct(fsp,
+ fsp->op->global->open_persistent_id,
+ in_offset,
+ (uint64_t)in_data.length,
+ WRITE_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &aio_ex->lock);
+
+ /* Take the lock until the AIO completes. */
+ if (!SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, &aio_ex->lock)) {
+ TALLOC_FREE(aio_ex);
+ return NT_STATUS_FILE_LOCK_CONFLICT;
+ }
+
+ aio_ex->nbyte = in_data.length;
+ aio_ex->offset = in_offset;
+
+ req = pwrite_fsync_send(aio_ex, fsp->conn->sconn->ev_ctx, fsp,
+ in_data.data, in_data.length, in_offset,
+ write_through);
+ if (req == NULL) {
+ DEBUG(3, ("smb2: SMB_VFS_PWRITE_SEND failed. "
+ "Error %s\n", strerror(errno)));
+ TALLOC_FREE(aio_ex);
+ return NT_STATUS_RETRY;
+ }
+ tevent_req_set_callback(req, aio_pwrite_smb2_done, aio_ex);
+
+ if (!aio_add_req_to_fsp(fsp, req)) {
+ DEBUG(1, ("Could not add req to fsp\n"));
+ TALLOC_FREE(aio_ex);
+ return NT_STATUS_RETRY;
+ }
+
+ /* We don't need talloc_move here as both aio_ex and
+ * smbreq are children of smbreq->smb2req. */
+ aio_ex->smbreq = smbreq;
+ smbreq->async_priv = aio_ex;
+
+ /* This should actually be improved to span the write. */
+ contend_level2_oplocks_begin(fsp, LEVEL2_CONTEND_WRITE);
+ contend_level2_oplocks_end(fsp, LEVEL2_CONTEND_WRITE);
+
+ /*
+ * We don't want to do write behind due to ownership
+ * issues of the request structs. Maybe add it if I
+ * figure those out. JRA.
+ */
+
+ DEBUG(10,("smb2: scheduled aio_write for file "
+ "%s, offset %.0f, len = %u (mid = %u)\n",
+ fsp_str_dbg(fsp),
+ (double)in_offset,
+ (unsigned int)in_data.length,
+ (unsigned int)aio_ex->smbreq->mid));
+
+ return NT_STATUS_OK;
+}
+
+static void aio_pwrite_smb2_done(struct tevent_req *req)
+{
+ struct aio_extra *aio_ex = tevent_req_callback_data(
+ req, struct aio_extra);
+ ssize_t numtowrite = aio_ex->nbyte;
+ struct tevent_req *subreq = aio_ex->smbreq->smb2req->subreq;
+ files_struct *fsp = aio_ex->fsp;
+ NTSTATUS status;
+ ssize_t nwritten;
+ int err = 0;
+
+ nwritten = pwrite_fsync_recv(req, &err);
+ TALLOC_FREE(req);
+
+ DEBUG(10, ("pwrite_recv returned %d, err = %s\n", (int)nwritten,
+ (nwritten == -1) ? strerror(err) : "no error"));
+
+ mark_file_modified(fsp);
+
+ status = smb2_write_complete_nosync(subreq, nwritten, err);
+
+ DEBUG(10, ("smb2: scheduled aio_write completed "
+ "for file %s, offset %.0f, requested %u, "
+ "written = %u (errcode = %d, NTSTATUS = %s)\n",
+ fsp_str_dbg(fsp),
+ (double)aio_ex->offset,
+ (unsigned int)numtowrite,
+ (unsigned int)nwritten,
+ err, nt_errstr(status)));
+
+ if (tevent_req_nterror(subreq, status)) {
+ return;
+ }
+ tevent_req_done(subreq);
+}
diff --git a/source3/smbd/smb2_break.c b/source3/smbd/smb2_break.c
new file mode 100644
index 0000000..f837b8e
--- /dev/null
+++ b/source3/smbd/smb2_break.c
@@ -0,0 +1,495 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+ Copyright (C) Jeremy Allison 2010
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "locking/leases_db.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static NTSTATUS smbd_smb2_request_process_lease_break(
+ struct smbd_smb2_request *req);
+
+static struct tevent_req *smbd_smb2_oplock_break_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *in_fsp,
+ uint8_t in_oplock_level);
+static NTSTATUS smbd_smb2_oplock_break_recv(struct tevent_req *req,
+ uint8_t *out_oplock_level);
+
+static void smbd_smb2_request_oplock_break_done(struct tevent_req *subreq);
+NTSTATUS smbd_smb2_request_process_break(struct smbd_smb2_request *req)
+{
+ NTSTATUS status;
+ const uint8_t *inbody;
+ uint8_t in_oplock_level;
+ uint64_t in_file_id_persistent;
+ uint64_t in_file_id_volatile;
+ struct files_struct *in_fsp;
+ struct tevent_req *subreq;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x18);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
+ /*
+ * Retry as a lease break
+ */
+ return smbd_smb2_request_process_lease_break(req);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ in_oplock_level = CVAL(inbody, 0x02);
+
+ /* 0x03 1 bytes reserved */
+ /* 0x04 4 bytes reserved */
+ in_file_id_persistent = BVAL(inbody, 0x08);
+ in_file_id_volatile = BVAL(inbody, 0x10);
+
+ in_fsp = file_fsp_smb2(req, in_file_id_persistent, in_file_id_volatile);
+ if (in_fsp == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED);
+ }
+
+ /* Are we awaiting a break message ? */
+ if (in_fsp->oplock_timeout == NULL) {
+ return smbd_smb2_request_error(
+ req, NT_STATUS_INVALID_OPLOCK_PROTOCOL);
+ }
+
+ if (in_oplock_level != SMB2_OPLOCK_LEVEL_NONE &&
+ in_oplock_level != SMB2_OPLOCK_LEVEL_II) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ subreq = smbd_smb2_oplock_break_send(req, req->sconn->ev_ctx,
+ req, in_fsp, in_oplock_level);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_oplock_break_done, req);
+
+ return smbd_smb2_request_pending_queue(req, subreq, 500);
+}
+
+static void smbd_smb2_request_oplock_break_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *req = tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ const uint8_t *inbody;
+ uint64_t in_file_id_persistent;
+ uint64_t in_file_id_volatile;
+ uint8_t out_oplock_level = 0;
+ DATA_BLOB outbody;
+ NTSTATUS status;
+ NTSTATUS error; /* transport error */
+
+ status = smbd_smb2_oplock_break_recv(subreq, &out_oplock_level);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ error = smbd_smb2_request_error(req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ in_file_id_persistent = BVAL(inbody, 0x08);
+ in_file_id_volatile = BVAL(inbody, 0x10);
+
+ outbody = smbd_smb2_generate_outbody(req, 0x18);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x18); /* struct size */
+ SCVAL(outbody.data, 0x02,
+ out_oplock_level); /* SMB2 oplock level */
+ SCVAL(outbody.data, 0x03, 0); /* reserved */
+ SIVAL(outbody.data, 0x04, 0); /* reserved */
+ SBVAL(outbody.data, 0x08,
+ in_file_id_persistent); /* file id (persistent) */
+ SBVAL(outbody.data, 0x10,
+ in_file_id_volatile); /* file id (volatile) */
+
+ error = smbd_smb2_request_done(req, outbody, NULL);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+struct smbd_smb2_oplock_break_state {
+ struct smbd_smb2_request *smb2req;
+ uint8_t out_oplock_level; /* SMB2 oplock level. */
+};
+
+static struct tevent_req *smbd_smb2_oplock_break_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *fsp,
+ uint8_t in_oplock_level)
+{
+ struct tevent_req *req;
+ struct smbd_smb2_oplock_break_state *state;
+ struct smb_request *smbreq;
+ int oplocklevel = map_smb2_oplock_levels_to_samba(in_oplock_level);
+ bool break_to_none = (oplocklevel == NO_OPLOCK);
+ bool result;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_oplock_break_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->smb2req = smb2req;
+ state->out_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+
+ DEBUG(10,("smbd_smb2_oplock_break_send: %s - %s, "
+ "samba level %d\n",
+ fsp_str_dbg(fsp), fsp_fnum_dbg(fsp),
+ oplocklevel));
+
+ smbreq = smbd_smb2_fake_smb_request(smb2req, fsp);
+ if (tevent_req_nomem(smbreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ DEBUG(5,("smbd_smb2_oplock_break_send: got SMB2 oplock break (%u) from client "
+ "for file %s, %s\n",
+ (unsigned int)in_oplock_level,
+ fsp_str_dbg(fsp),
+ fsp_fnum_dbg(fsp)));
+
+ if ((fsp->sent_oplock_break == BREAK_TO_NONE_SENT) ||
+ (break_to_none)) {
+ result = remove_oplock(fsp);
+ state->out_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+ } else {
+ result = downgrade_oplock(fsp);
+ state->out_oplock_level = SMB2_OPLOCK_LEVEL_II;
+ }
+
+ if (!result) {
+ DEBUG(0, ("smbd_smb2_oplock_break_send: error in removing "
+ "oplock on file %s\n", fsp_str_dbg(fsp)));
+ /* Hmmm. Is this panic justified? */
+ smb_panic("internal tdb error");
+ }
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static NTSTATUS smbd_smb2_oplock_break_recv(struct tevent_req *req,
+ uint8_t *out_oplock_level)
+{
+ NTSTATUS status;
+ struct smbd_smb2_oplock_break_state *state =
+ tevent_req_data(req,
+ struct smbd_smb2_oplock_break_state);
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *out_oplock_level = state->out_oplock_level;
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+static void smbd_smb2_request_lease_break_done(struct tevent_req *subreq);
+
+static struct tevent_req *smbd_smb2_lease_break_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct smbd_smb2_request *smb2_req, struct smb2_lease_key in_lease_key,
+ uint32_t in_lease_state);
+static NTSTATUS smbd_smb2_lease_break_recv(struct tevent_req *req,
+ uint32_t *out_lease_state);
+
+
+static NTSTATUS smbd_smb2_request_process_lease_break(
+ struct smbd_smb2_request *req)
+{
+ NTSTATUS status;
+ const uint8_t *inbody;
+ struct smb2_lease_key in_lease_key;
+ uint32_t in_lease_state;
+ struct tevent_req *subreq;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x24);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ in_lease_key.data[0] = BVAL(inbody, 8);
+ in_lease_key.data[1] = BVAL(inbody, 16);
+ in_lease_state = IVAL(inbody, 24);
+
+ subreq = smbd_smb2_lease_break_send(req, req->sconn->ev_ctx, req,
+ in_lease_key, in_lease_state);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_lease_break_done, req);
+
+ return smbd_smb2_request_pending_queue(req, subreq, 500);
+}
+
+static void smbd_smb2_request_lease_break_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *req = tevent_req_callback_data(
+ subreq, struct smbd_smb2_request);
+ const uint8_t *inbody;
+ struct smb2_lease_key in_lease_key;
+ uint32_t out_lease_state = 0;
+ DATA_BLOB outbody;
+ NTSTATUS status;
+ NTSTATUS error; /* transport error */
+
+ status = smbd_smb2_lease_break_recv(subreq, &out_lease_state);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ error = smbd_smb2_request_error(req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ in_lease_key.data[0] = BVAL(inbody, 8);
+ in_lease_key.data[1] = BVAL(inbody, 16);
+
+ outbody = smbd_smb2_generate_outbody(req, 0x24);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x24); /* struct size */
+ SSVAL(outbody.data, 0x02, 0); /* reserved */
+ SIVAL(outbody.data, 0x04, 0); /* flags, must be 0 */
+ SBVAL(outbody.data, 0x08, in_lease_key.data[0]);
+ SBVAL(outbody.data, 0x10, in_lease_key.data[1]);
+ SIVAL(outbody.data, 0x18, out_lease_state);
+ SBVAL(outbody.data, 0x1c, 0); /* leaseduration, must be 0 */
+
+ error = smbd_smb2_request_done(req, outbody, NULL);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+struct smbd_smb2_lease_break_state {
+ uint32_t lease_state;
+};
+
+struct lease_lookup_state {
+ TALLOC_CTX *mem_ctx;
+ /* Return parameters. */
+ uint32_t num_file_ids;
+ struct file_id *ids;
+ NTSTATUS status;
+};
+
+static void lease_parser(
+ uint32_t num_files,
+ const struct leases_db_file *files,
+ void *private_data)
+{
+ struct lease_lookup_state *lls =
+ (struct lease_lookup_state *)private_data;
+
+ lls->status = NT_STATUS_OK;
+ lls->num_file_ids = num_files;
+ lls->status = leases_db_copy_file_ids(lls->mem_ctx,
+ num_files,
+ files,
+ &lls->ids);
+}
+
+static struct tevent_req *smbd_smb2_lease_break_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct smbd_smb2_request *smb2_req, struct smb2_lease_key in_lease_key,
+ uint32_t in_lease_state)
+{
+ struct tevent_req *req;
+ struct smbd_smb2_lease_break_state *state;
+ struct lease_lookup_state lls = {.mem_ctx = mem_ctx};
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_lease_break_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->lease_state = in_lease_state;
+
+ /* Find any file ids with this lease key. */
+ status = leases_db_parse(&smb2_req->xconn->smb2.client.guid,
+ &in_lease_key,
+ lease_parser,
+ &lls);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ DEBUG(10, ("No record for lease key found\n"));
+ }
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ if (tevent_req_nterror(req, lls.status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (lls.num_file_ids == 0) {
+ tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND);
+ return tevent_req_post(req, ev);
+ }
+
+ status = downgrade_lease(smb2_req->xconn->client,
+ lls.num_file_ids,
+ lls.ids,
+ &in_lease_key,
+ in_lease_state);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_BREAK_IN_PROGRESS)) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+ if (tevent_req_nterror(req, status)) {
+ DEBUG(10, ("downgrade_lease returned %s\n",
+ nt_errstr(status)));
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static NTSTATUS smbd_smb2_lease_break_recv(struct tevent_req *req,
+ uint32_t *out_lease_state)
+{
+ struct smbd_smb2_lease_break_state *state = tevent_req_data(
+ req, struct smbd_smb2_lease_break_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *out_lease_state = state->lease_state;
+ return NT_STATUS_OK;
+}
+
+/*********************************************************
+ Create and send an asynchronous
+ SMB2 OPLOCK_BREAK_NOTIFICATION.
+*********************************************************/
+
+void send_break_message_smb2(files_struct *fsp,
+ uint32_t break_from,
+ uint32_t break_to)
+{
+ struct smbXsrv_client *client =
+ fsp->conn->sconn->client;
+ NTSTATUS status;
+
+ if (!NT_STATUS_IS_OK(fsp->op->status)) {
+ DBG_DEBUG("skip oplock break for file %s, %s, "
+ "smb2 level %u fsp status=%s\n",
+ fsp_str_dbg(fsp),
+ fsp_fnum_dbg(fsp),
+ (unsigned int)break_to,
+ nt_errstr(fsp->op->status));
+ return;
+ }
+
+ DBG_DEBUG("sending oplock break "
+ "for file %s, %s, smb2 level %u\n",
+ fsp_str_dbg(fsp),
+ fsp_fnum_dbg(fsp),
+ (unsigned int)break_to);
+
+ if (fsp->oplock_type == LEASE_OPLOCK) {
+ uint32_t break_flags = 0;
+ uint16_t new_epoch;
+
+ if (fsp->lease->lease.lease_state != SMB2_LEASE_NONE) {
+ break_flags = SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED;
+ }
+
+ if (fsp->lease->lease.lease_version > 1) {
+ new_epoch = fsp->lease->lease.lease_epoch;
+ } else {
+ new_epoch = 0;
+ }
+
+ status = smbd_smb2_send_lease_break(client, new_epoch, break_flags,
+ &fsp->lease->lease.lease_key,
+ break_from, break_to);
+ } else {
+ uint8_t smb2_oplock_level;
+ smb2_oplock_level = (break_to & SMB2_LEASE_READ) ?
+ SMB2_OPLOCK_LEVEL_II : SMB2_OPLOCK_LEVEL_NONE;
+ status = smbd_smb2_send_oplock_break(client,
+ fsp->op,
+ smb2_oplock_level);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_disconnect_client(client,
+ nt_errstr(status));
+ return;
+ }
+}
diff --git a/source3/smbd/smb2_close.c b/source3/smbd/smb2_close.c
new file mode 100644
index 0000000..996ca8b
--- /dev/null
+++ b/source3/smbd/smb2_close.c
@@ -0,0 +1,427 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../lib/util/tevent_ntstatus.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static struct tevent_req *smbd_smb2_close_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *in_fsp,
+ uint16_t in_flags);
+static NTSTATUS smbd_smb2_close_recv(struct tevent_req *req,
+ uint16_t *out_flags,
+ struct timespec *out_creation_ts,
+ struct timespec *out_last_access_ts,
+ struct timespec *out_last_write_ts,
+ struct timespec *out_change_ts,
+ uint64_t *out_allocation_size,
+ uint64_t *out_end_of_file,
+ uint32_t *out_file_attributes);
+
+static void smbd_smb2_request_close_done(struct tevent_req *subreq);
+
+NTSTATUS smbd_smb2_request_process_close(struct smbd_smb2_request *req)
+{
+ const uint8_t *inbody;
+ uint16_t in_flags;
+ uint64_t in_file_id_persistent;
+ uint64_t in_file_id_volatile;
+ struct files_struct *in_fsp;
+ NTSTATUS status;
+ struct tevent_req *subreq;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x18);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ in_flags = SVAL(inbody, 0x02);
+ in_file_id_persistent = BVAL(inbody, 0x08);
+ in_file_id_volatile = BVAL(inbody, 0x10);
+
+ in_fsp = file_fsp_smb2(req, in_file_id_persistent, in_file_id_volatile);
+ if (in_fsp == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED);
+ }
+
+ subreq = smbd_smb2_close_send(req, req->sconn->ev_ctx,
+ req, in_fsp, in_flags);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_close_done, req);
+
+ return smbd_smb2_request_pending_queue(req, subreq, 500);
+}
+
+static void smbd_smb2_request_close_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *req =
+ tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ DATA_BLOB outbody;
+ uint16_t out_flags = 0;
+ connection_struct *conn = req->tcon->compat;
+ struct timespec out_creation_ts = { 0, };
+ struct timespec out_last_access_ts = { 0, };
+ struct timespec out_last_write_ts = { 0, };
+ struct timespec out_change_ts = { 0, };
+ uint64_t out_allocation_size = 0;
+ uint64_t out_end_of_file = 0;
+ uint32_t out_file_attributes = 0;
+ NTSTATUS status;
+ NTSTATUS error;
+
+ status = smbd_smb2_close_recv(subreq,
+ &out_flags,
+ &out_creation_ts,
+ &out_last_access_ts,
+ &out_last_write_ts,
+ &out_change_ts,
+ &out_allocation_size,
+ &out_end_of_file,
+ &out_file_attributes);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ error = smbd_smb2_request_error(req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ outbody = smbd_smb2_generate_outbody(req, 0x3C);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x3C); /* struct size */
+ SSVAL(outbody.data, 0x02, out_flags);
+ SIVAL(outbody.data, 0x04, 0); /* reserved */
+ put_long_date_full_timespec(conn->ts_res,
+ (char *)outbody.data + 0x08, &out_creation_ts);
+ put_long_date_full_timespec(conn->ts_res,
+ (char *)outbody.data + 0x10, &out_last_access_ts);
+ put_long_date_full_timespec(conn->ts_res,
+ (char *)outbody.data + 0x18, &out_last_write_ts);
+ put_long_date_full_timespec(conn->ts_res,
+ (char *)outbody.data + 0x20, &out_change_ts);
+ SBVAL(outbody.data, 0x28, out_allocation_size);
+ SBVAL(outbody.data, 0x30, out_end_of_file);
+ SIVAL(outbody.data, 0x38, out_file_attributes);
+
+ error = smbd_smb2_request_done(req, outbody, NULL);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+static void setup_close_full_information(connection_struct *conn,
+ struct smb_filename *smb_fname,
+ struct timespec *out_creation_ts,
+ struct timespec *out_last_access_ts,
+ struct timespec *out_last_write_ts,
+ struct timespec *out_change_ts,
+ uint16_t *out_flags,
+ uint64_t *out_allocation_size,
+ uint64_t *out_end_of_file)
+{
+ *out_flags = SMB2_CLOSE_FLAGS_FULL_INFORMATION;
+ *out_last_write_ts = smb_fname->st.st_ex_mtime;
+ *out_last_access_ts = smb_fname->st.st_ex_atime;
+ *out_creation_ts = get_create_timespec(conn, NULL, smb_fname);
+ *out_change_ts = get_change_timespec(conn, NULL, smb_fname);
+
+ if (lp_dos_filetime_resolution(SNUM(conn))) {
+ dos_filetime_timespec(out_creation_ts);
+ dos_filetime_timespec(out_last_write_ts);
+ dos_filetime_timespec(out_last_access_ts);
+ dos_filetime_timespec(out_change_ts);
+ }
+ if (!S_ISDIR(smb_fname->st.st_ex_mode)) {
+ *out_end_of_file = get_file_size_stat(&smb_fname->st);
+ }
+
+ *out_allocation_size = SMB_VFS_GET_ALLOC_SIZE(conn, NULL, &smb_fname->st);
+}
+
+static NTSTATUS smbd_smb2_close(struct smbd_smb2_request *req,
+ struct files_struct **_fsp,
+ uint16_t in_flags,
+ uint16_t *out_flags,
+ struct timespec *out_creation_ts,
+ struct timespec *out_last_access_ts,
+ struct timespec *out_last_write_ts,
+ struct timespec *out_change_ts,
+ uint64_t *out_allocation_size,
+ uint64_t *out_end_of_file,
+ uint32_t *out_file_attributes)
+{
+ NTSTATUS status;
+ struct smb_request *smbreq;
+ connection_struct *conn = req->tcon->compat;
+ struct files_struct *fsp = *_fsp;
+ struct smb_filename *smb_fname = NULL;
+
+ *out_creation_ts = (struct timespec){0, SAMBA_UTIME_OMIT};
+ *out_last_access_ts = (struct timespec){0, SAMBA_UTIME_OMIT};
+ *out_last_write_ts = (struct timespec){0, SAMBA_UTIME_OMIT};
+ *out_change_ts = (struct timespec){0, SAMBA_UTIME_OMIT};
+
+ *out_flags = 0;
+ *out_allocation_size = 0;
+ *out_end_of_file = 0;
+ *out_file_attributes = 0;
+
+ DEBUG(10,("smbd_smb2_close: %s - %s\n",
+ fsp_str_dbg(fsp), fsp_fnum_dbg(fsp)));
+
+ smbreq = smbd_smb2_fake_smb_request(req, fsp);
+ if (smbreq == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (in_flags & SMB2_CLOSE_FLAGS_FULL_INFORMATION) {
+ *out_file_attributes = fdos_mode(fsp);
+ fsp->fsp_flags.fstat_before_close = true;
+ }
+
+ status = close_file_smb(smbreq, fsp, NORMAL_CLOSE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("smbd_smb2_close: close_file[%s]: %s\n",
+ smb_fname_str_dbg(smb_fname), nt_errstr(status)));
+ file_free(smbreq, fsp);
+ *_fsp = fsp = NULL;
+ return status;
+ }
+
+ if (in_flags & SMB2_CLOSE_FLAGS_FULL_INFORMATION) {
+ setup_close_full_information(conn,
+ fsp->fsp_name,
+ out_creation_ts,
+ out_last_access_ts,
+ out_last_write_ts,
+ out_change_ts,
+ out_flags,
+ out_allocation_size,
+ out_end_of_file);
+ }
+
+ file_free(smbreq, fsp);
+ *_fsp = fsp = NULL;
+ return NT_STATUS_OK;
+}
+
+struct smbd_smb2_close_state {
+ struct smbd_smb2_request *smb2req;
+ struct files_struct *in_fsp;
+ uint16_t in_flags;
+ uint16_t out_flags;
+ struct timespec out_creation_ts;
+ struct timespec out_last_access_ts;
+ struct timespec out_last_write_ts;
+ struct timespec out_change_ts;
+ uint64_t out_allocation_size;
+ uint64_t out_end_of_file;
+ uint32_t out_file_attributes;
+ struct tevent_queue *wait_queue;
+};
+
+static void smbd_smb2_close_wait_done(struct tevent_req *subreq);
+
+static struct tevent_req *smbd_smb2_close_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *in_fsp,
+ uint16_t in_flags)
+{
+ struct tevent_req *req;
+ struct smbd_smb2_close_state *state;
+ const char *fsp_name_str = NULL;
+ const char *fsp_fnum_str = NULL;
+ unsigned i;
+ NTSTATUS status;
+
+ if (CHECK_DEBUGLVL(DBGLVL_INFO)) {
+ fsp_name_str = fsp_str_dbg(in_fsp);
+ fsp_fnum_str = fsp_fnum_dbg(in_fsp);
+ }
+
+ DBG_DEBUG("%s - %s\n", fsp_name_str, fsp_fnum_str);
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_close_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->smb2req = smb2req;
+ state->in_fsp = in_fsp;
+ state->in_flags = in_flags;
+
+ in_fsp->fsp_flags.closing = true;
+
+ i = 0;
+ while (i < in_fsp->num_aio_requests) {
+ bool ok = tevent_req_cancel(in_fsp->aio_requests[i]);
+ if (ok) {
+ continue;
+ }
+ i += 1;
+ }
+
+ if (in_fsp->num_aio_requests != 0) {
+ struct tevent_req *subreq;
+
+ state->wait_queue = tevent_queue_create(state,
+ "smbd_smb2_close_send_wait_queue");
+ if (tevent_req_nomem(state->wait_queue, req)) {
+ return tevent_req_post(req, ev);
+ }
+ /*
+ * Now wait until all aio requests on this fsp are
+ * finished.
+ *
+ * We don't set a callback, as we just want to block the
+ * wait queue and the talloc_free() of fsp->aio_request
+ * will remove the item from the wait queue.
+ */
+ subreq = tevent_queue_wait_send(in_fsp->aio_requests,
+ smb2req->sconn->ev_ctx,
+ state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * Now we add our own waiter to the end of the queue,
+ * this way we get notified when all pending requests are
+ * finished.
+ */
+ subreq = tevent_queue_wait_send(state,
+ smb2req->sconn->ev_ctx,
+ state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_set_callback(subreq, smbd_smb2_close_wait_done, req);
+ return req;
+ }
+
+ status = smbd_smb2_close(smb2req,
+ &state->in_fsp,
+ state->in_flags,
+ &state->out_flags,
+ &state->out_creation_ts,
+ &state->out_last_access_ts,
+ &state->out_last_write_ts,
+ &state->out_change_ts,
+ &state->out_allocation_size,
+ &state->out_end_of_file,
+ &state->out_file_attributes);
+ if (tevent_req_nterror(req, status)) {
+ DBG_INFO("%s - %s: close file failed: %s\n",
+ fsp_name_str, fsp_fnum_str,
+ nt_errstr(status));
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static void smbd_smb2_close_wait_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smbd_smb2_close_state *state = tevent_req_data(
+ req, struct smbd_smb2_close_state);
+ NTSTATUS status;
+
+ tevent_queue_wait_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ status = smbd_smb2_close(state->smb2req,
+ &state->in_fsp,
+ state->in_flags,
+ &state->out_flags,
+ &state->out_creation_ts,
+ &state->out_last_access_ts,
+ &state->out_last_write_ts,
+ &state->out_change_ts,
+ &state->out_allocation_size,
+ &state->out_end_of_file,
+ &state->out_file_attributes);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS smbd_smb2_close_recv(struct tevent_req *req,
+ uint16_t *out_flags,
+ struct timespec *out_creation_ts,
+ struct timespec *out_last_access_ts,
+ struct timespec *out_last_write_ts,
+ struct timespec *out_change_ts,
+ uint64_t *out_allocation_size,
+ uint64_t *out_end_of_file,
+ uint32_t *out_file_attributes)
+{
+ struct smbd_smb2_close_state *state =
+ tevent_req_data(req,
+ struct smbd_smb2_close_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *out_flags = state->out_flags;
+ *out_creation_ts = state->out_creation_ts;
+ *out_last_access_ts = state->out_last_access_ts;
+ *out_last_write_ts = state->out_last_write_ts;
+ *out_change_ts = state->out_change_ts;
+ *out_allocation_size = state->out_allocation_size;
+ *out_end_of_file = state->out_end_of_file;
+ *out_file_attributes = state->out_file_attributes;
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/smb2_create.c b/source3/smbd/smb2_create.c
new file mode 100644
index 0000000..8a40717
--- /dev/null
+++ b/source3/smbd/smb2_create.c
@@ -0,0 +1,2113 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+ Copyright (C) Jeremy Allison 2010
+
+ 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 "printing.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "smbd/smbXsrv_open.h"
+#include "../libcli/smb/smb_common.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "../librpc/gen_ndr/ndr_smb2_lease_struct.h"
+#include "../librpc/gen_ndr/ndr_smb3posix.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "messages.h"
+#include "lib/util_ea.h"
+#include "source3/passdb/lookup_sid.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+int map_smb2_oplock_levels_to_samba(uint8_t in_oplock_level)
+{
+ switch(in_oplock_level) {
+ case SMB2_OPLOCK_LEVEL_NONE:
+ return NO_OPLOCK;
+ case SMB2_OPLOCK_LEVEL_II:
+ return LEVEL_II_OPLOCK;
+ case SMB2_OPLOCK_LEVEL_EXCLUSIVE:
+ return EXCLUSIVE_OPLOCK;
+ case SMB2_OPLOCK_LEVEL_BATCH:
+ return BATCH_OPLOCK;
+ case SMB2_OPLOCK_LEVEL_LEASE:
+ return LEASE_OPLOCK;
+ default:
+ DEBUG(2,("map_smb2_oplock_levels_to_samba: "
+ "unknown level %u\n",
+ (unsigned int)in_oplock_level));
+ return NO_OPLOCK;
+ }
+}
+
+static uint8_t map_samba_oplock_levels_to_smb2(int oplock_type)
+{
+ if (BATCH_OPLOCK_TYPE(oplock_type)) {
+ return SMB2_OPLOCK_LEVEL_BATCH;
+ } else if (EXCLUSIVE_OPLOCK_TYPE(oplock_type)) {
+ return SMB2_OPLOCK_LEVEL_EXCLUSIVE;
+ } else if (oplock_type == LEVEL_II_OPLOCK) {
+ return SMB2_OPLOCK_LEVEL_II;
+ } else if (oplock_type == LEASE_OPLOCK) {
+ return SMB2_OPLOCK_LEVEL_LEASE;
+ } else {
+ return SMB2_OPLOCK_LEVEL_NONE;
+ }
+}
+
+/*
+ MS-FSA 2.1.5.1 Server Requests an Open of a File
+ Trailing '/' or '\\' checker.
+ Must be done before the filename parser removes any
+ trailing characters. If we decide to add this to SMB1
+ NTCreate processing we can make this public.
+
+ Note this is Windows pathname processing only. When
+ POSIX pathnames are added to SMB2 this will not apply.
+*/
+
+static NTSTATUS windows_name_trailing_check(const char *name,
+ uint32_t create_options)
+{
+ size_t name_len = strlen(name);
+ char trail_c;
+
+ if (name_len <= 1) {
+ return NT_STATUS_OK;
+ }
+
+ trail_c = name[name_len-1];
+
+ /*
+ * Trailing '/' is always invalid.
+ */
+ if (trail_c == '/') {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+
+ if (create_options & FILE_NON_DIRECTORY_FILE) {
+ if (trail_c == '\\') {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ uint8_t in_oplock_level,
+ uint32_t in_impersonation_level,
+ uint32_t in_desired_access,
+ uint32_t in_file_attributes,
+ uint32_t in_share_access,
+ uint32_t in_create_disposition,
+ uint32_t _in_create_options,
+ const char *in_name,
+ struct smb2_create_blobs in_context_blobs);
+static NTSTATUS smbd_smb2_create_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ uint8_t *out_oplock_level,
+ uint32_t *out_create_action,
+ struct timespec *out_creation_ts,
+ struct timespec *out_last_access_ts,
+ struct timespec *out_last_write_ts,
+ struct timespec *out_change_ts,
+ uint64_t *out_allocation_size,
+ uint64_t *out_end_of_file,
+ uint32_t *out_file_attributes,
+ uint64_t *out_file_id_persistent,
+ uint64_t *out_file_id_volatile,
+ struct smb2_create_blobs *out_context_blobs);
+
+static void smbd_smb2_request_create_done(struct tevent_req *tsubreq);
+NTSTATUS smbd_smb2_request_process_create(struct smbd_smb2_request *smb2req)
+{
+ const uint8_t *inbody;
+ const struct iovec *indyniov;
+ uint8_t in_oplock_level;
+ uint32_t in_impersonation_level;
+ uint32_t in_desired_access;
+ uint32_t in_file_attributes;
+ uint32_t in_share_access;
+ uint32_t in_create_disposition;
+ uint32_t in_create_options;
+ uint16_t in_name_offset;
+ uint16_t in_name_length;
+ DATA_BLOB in_name_buffer;
+ char *in_name_string;
+ size_t in_name_string_size;
+ uint32_t name_offset = 0;
+ uint32_t name_available_length = 0;
+ uint32_t in_context_offset;
+ uint32_t in_context_length;
+ DATA_BLOB in_context_buffer;
+ struct smb2_create_blobs in_context_blobs;
+ uint32_t context_offset = 0;
+ uint32_t context_available_length = 0;
+ uint32_t dyn_offset;
+ NTSTATUS status;
+ bool ok;
+ struct tevent_req *tsubreq;
+
+ status = smbd_smb2_request_verify_sizes(smb2req, 0x39);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(smb2req, status);
+ }
+ inbody = SMBD_SMB2_IN_BODY_PTR(smb2req);
+
+ in_oplock_level = CVAL(inbody, 0x03);
+ in_impersonation_level = IVAL(inbody, 0x04);
+ in_desired_access = IVAL(inbody, 0x18);
+ in_file_attributes = IVAL(inbody, 0x1C);
+ in_share_access = IVAL(inbody, 0x20);
+ in_create_disposition = IVAL(inbody, 0x24);
+ in_create_options = IVAL(inbody, 0x28);
+ in_name_offset = SVAL(inbody, 0x2C);
+ in_name_length = SVAL(inbody, 0x2E);
+ in_context_offset = IVAL(inbody, 0x30);
+ in_context_length = IVAL(inbody, 0x34);
+
+ /*
+ * First check if the dynamic name and context buffers
+ * are correctly specified.
+ *
+ * Note: That we don't check if the name and context buffers
+ * overlap
+ */
+
+ dyn_offset = SMB2_HDR_BODY + SMBD_SMB2_IN_BODY_LEN(smb2req);
+
+ if (in_name_offset == 0 && in_name_length == 0) {
+ /* This is ok */
+ name_offset = 0;
+ } else if (in_name_offset < dyn_offset) {
+ return smbd_smb2_request_error(smb2req, NT_STATUS_INVALID_PARAMETER);
+ } else {
+ name_offset = in_name_offset - dyn_offset;
+ }
+
+ indyniov = SMBD_SMB2_IN_DYN_IOV(smb2req);
+
+ if (name_offset > indyniov->iov_len) {
+ return smbd_smb2_request_error(smb2req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ name_available_length = indyniov->iov_len - name_offset;
+
+ if (in_name_length > name_available_length) {
+ return smbd_smb2_request_error(smb2req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ in_name_buffer.data = (uint8_t *)indyniov->iov_base + name_offset;
+ in_name_buffer.length = in_name_length;
+
+ if (in_context_offset == 0 && in_context_length == 0) {
+ /* This is ok */
+ context_offset = 0;
+ } else if (in_context_offset < dyn_offset) {
+ return smbd_smb2_request_error(smb2req, NT_STATUS_INVALID_PARAMETER);
+ } else {
+ context_offset = in_context_offset - dyn_offset;
+ }
+
+ if (context_offset > indyniov->iov_len) {
+ return smbd_smb2_request_error(smb2req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ context_available_length = indyniov->iov_len - context_offset;
+
+ if (in_context_length > context_available_length) {
+ return smbd_smb2_request_error(smb2req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ in_context_buffer.data = (uint8_t *)indyniov->iov_base +
+ context_offset;
+ in_context_buffer.length = in_context_length;
+
+ /*
+ * Now interpret the name and context buffers
+ */
+
+ ok = convert_string_talloc(smb2req, CH_UTF16, CH_UNIX,
+ in_name_buffer.data,
+ in_name_buffer.length,
+ &in_name_string,
+ &in_name_string_size);
+ if (!ok) {
+ return smbd_smb2_request_error(smb2req, NT_STATUS_ILLEGAL_CHARACTER);
+ }
+
+ if (in_name_buffer.length == 0) {
+ in_name_string_size = 0;
+ }
+
+ if (strlen(in_name_string) != in_name_string_size) {
+ return smbd_smb2_request_error(smb2req, NT_STATUS_OBJECT_NAME_INVALID);
+ }
+
+ ZERO_STRUCT(in_context_blobs);
+ status = smb2_create_blob_parse(smb2req, in_context_buffer, &in_context_blobs);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(smb2req, status);
+ }
+
+ if (CHECK_DEBUGLVL(DBGLVL_DEBUG)) {
+ char *str = talloc_asprintf(
+ talloc_tos(),
+ "\nGot %"PRIu32" create blobs\n",
+ in_context_blobs.num_blobs);
+ uint32_t i;
+
+ for (i=0; i<in_context_blobs.num_blobs; i++) {
+ struct smb2_create_blob *b =
+ &in_context_blobs.blobs[i];
+ talloc_asprintf_addbuf(&str, "[%"PRIu32"]\n", i);
+ dump_data_addbuf(
+ (uint8_t *)b->tag, strlen(b->tag), &str);
+ dump_data_addbuf(
+ b->data.data, b->data.length, &str);
+ }
+ DBG_DEBUG("%s", str);
+ TALLOC_FREE(str);
+ }
+
+ tsubreq = smbd_smb2_create_send(smb2req,
+ smb2req->sconn->ev_ctx,
+ smb2req,
+ in_oplock_level,
+ in_impersonation_level,
+ in_desired_access,
+ in_file_attributes,
+ in_share_access,
+ in_create_disposition,
+ in_create_options,
+ in_name_string,
+ in_context_blobs);
+ if (tsubreq == NULL) {
+ smb2req->subreq = NULL;
+ return smbd_smb2_request_error(smb2req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(tsubreq, smbd_smb2_request_create_done, smb2req);
+
+ return smbd_smb2_request_pending_queue(smb2req, tsubreq, 500);
+}
+
+static uint64_t get_mid_from_smb2req(struct smbd_smb2_request *smb2req)
+{
+ uint8_t *reqhdr = SMBD_SMB2_OUT_HDR_PTR(smb2req);
+ return BVAL(reqhdr, SMB2_HDR_MESSAGE_ID);
+}
+
+static void smbd_smb2_request_create_done(struct tevent_req *tsubreq)
+{
+ struct smbd_smb2_request *smb2req = tevent_req_callback_data(tsubreq,
+ struct smbd_smb2_request);
+ DATA_BLOB outbody;
+ DATA_BLOB outdyn;
+ uint8_t out_oplock_level = 0;
+ uint32_t out_create_action = 0;
+ connection_struct *conn = smb2req->tcon->compat;
+ struct timespec out_creation_ts = { 0, };
+ struct timespec out_last_access_ts = { 0, };
+ struct timespec out_last_write_ts = { 0, };
+ struct timespec out_change_ts = { 0, };
+ uint64_t out_allocation_size = 0;
+ uint64_t out_end_of_file = 0;
+ uint32_t out_file_attributes = 0;
+ uint64_t out_file_id_persistent = 0;
+ uint64_t out_file_id_volatile = 0;
+ struct smb2_create_blobs out_context_blobs;
+ DATA_BLOB out_context_buffer;
+ uint16_t out_context_buffer_offset = 0;
+ NTSTATUS status;
+ NTSTATUS error; /* transport error */
+
+ status = smbd_smb2_create_recv(tsubreq,
+ smb2req,
+ &out_oplock_level,
+ &out_create_action,
+ &out_creation_ts,
+ &out_last_access_ts,
+ &out_last_write_ts,
+ &out_change_ts,
+ &out_allocation_size,
+ &out_end_of_file,
+ &out_file_attributes,
+ &out_file_id_persistent,
+ &out_file_id_volatile,
+ &out_context_blobs);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (smbd_smb2_is_compound(smb2req)) {
+ smb2req->compound_create_err = status;
+ }
+ error = smbd_smb2_request_error(smb2req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ status = smb2_create_blob_push(smb2req, &out_context_buffer, out_context_blobs);
+ if (!NT_STATUS_IS_OK(status)) {
+ error = smbd_smb2_request_error(smb2req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ if (out_context_buffer.length > 0) {
+ out_context_buffer_offset = SMB2_HDR_BODY + 0x58;
+ }
+
+ outbody = smbd_smb2_generate_outbody(smb2req, 0x58);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(smb2req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x58 + 1); /* struct size */
+ SCVAL(outbody.data, 0x02,
+ out_oplock_level); /* oplock level */
+ SCVAL(outbody.data, 0x03, 0); /* reserved */
+ SIVAL(outbody.data, 0x04,
+ out_create_action); /* create action */
+ put_long_date_full_timespec(conn->ts_res,
+ (char *)outbody.data + 0x08,
+ &out_creation_ts); /* creation time */
+ put_long_date_full_timespec(conn->ts_res,
+ (char *)outbody.data + 0x10,
+ &out_last_access_ts); /* last access time */
+ put_long_date_full_timespec(conn->ts_res,
+ (char *)outbody.data + 0x18,
+ &out_last_write_ts); /* last write time */
+ put_long_date_full_timespec(conn->ts_res,
+ (char *)outbody.data + 0x20,
+ &out_change_ts); /* change time */
+ SBVAL(outbody.data, 0x28,
+ out_allocation_size); /* allocation size */
+ SBVAL(outbody.data, 0x30,
+ out_end_of_file); /* end of file */
+ SIVAL(outbody.data, 0x38,
+ out_file_attributes); /* file attributes */
+ SIVAL(outbody.data, 0x3C, 0); /* reserved */
+ SBVAL(outbody.data, 0x40,
+ out_file_id_persistent); /* file id (persistent) */
+ SBVAL(outbody.data, 0x48,
+ out_file_id_volatile); /* file id (volatile) */
+ SIVAL(outbody.data, 0x50,
+ out_context_buffer_offset); /* create contexts offset */
+ SIVAL(outbody.data, 0x54,
+ out_context_buffer.length); /* create contexts length */
+
+ outdyn = out_context_buffer;
+
+ error = smbd_smb2_request_done(smb2req, outbody, &outdyn);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+static bool smb2_lease_key_valid(const struct smb2_lease_key *key)
+{
+ return ((key->data[0] != 0) || (key->data[1] != 0));
+}
+
+static NTSTATUS smbd_smb2_create_durable_lease_check(struct smb_request *smb1req,
+ const char *requested_filename, const struct files_struct *fsp,
+ const struct smb2_lease *lease_ptr)
+{
+ struct files_struct *dirfsp = NULL;
+ char *filename = NULL;
+ struct smb_filename *smb_fname = NULL;
+ uint32_t ucf_flags;
+ NTTIME twrp = fsp->fsp_name->twrp;
+ NTSTATUS status;
+ bool is_dfs = (smb1req->flags2 & FLAGS2_DFS_PATHNAMES);
+ bool is_posix = (fsp->fsp_name->flags & SMB_FILENAME_POSIX_PATH);
+
+ if (lease_ptr == NULL) {
+ if (fsp->oplock_type != LEASE_OPLOCK) {
+ return NT_STATUS_OK;
+ }
+ DEBUG(10, ("Reopened file has lease, but no lease "
+ "requested\n"));
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (fsp->oplock_type != LEASE_OPLOCK) {
+ DEBUG(10, ("Lease requested, but reopened file has no "
+ "lease\n"));
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (!smb2_lease_key_equal(&lease_ptr->lease_key,
+ &fsp->lease->lease.lease_key)) {
+ DEBUG(10, ("Different lease key requested than found "
+ "in reopened file\n"));
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (is_dfs) {
+ const char *non_dfs_requested_filename = NULL;
+ /*
+ * With a DFS flag set, remove any DFS prefix
+ * before further processing.
+ */
+ status = smb2_strip_dfs_path(requested_filename,
+ &non_dfs_requested_filename);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ /*
+ * TODO: Note for dealing with reparse point errors.
+ * We will need to remember and store the number of characters
+ * we have removed here, which is
+ * (requested_filename - non_dfs_requested_filename)
+ * in order to correctly report how many characters we
+ * have removed before hitting the reparse point.
+ * This will be a patch needed once we properly
+ * deal with reparse points later.
+ */
+ requested_filename = non_dfs_requested_filename;
+ /*
+ * Now we're no longer dealing with a DFS path, so
+ * remove the flag.
+ */
+ smb1req->flags2 &= ~FLAGS2_DFS_PATHNAMES;
+ is_dfs = false;
+ }
+
+ filename = talloc_strdup(talloc_tos(), requested_filename);
+ if (filename == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* This also converts '\' to '/' */
+ status = check_path_syntax(filename, is_posix);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(filename);
+ return status;
+ }
+
+ ucf_flags = filename_create_ucf_flags(smb1req, FILE_OPEN);
+ status = filename_convert_dirfsp(talloc_tos(),
+ fsp->conn,
+ filename,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_fname);
+ TALLOC_FREE(filename);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("filename_convert returned %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ if (!strequal(fsp->fsp_name->base_name, smb_fname->base_name)) {
+ DEBUG(10, ("Lease requested for file %s, reopened file "
+ "is named %s\n", smb_fname->base_name,
+ fsp->fsp_name->base_name));
+ TALLOC_FREE(smb_fname);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ TALLOC_FREE(smb_fname);
+
+ return NT_STATUS_OK;
+}
+
+struct smbd_smb2_create_state {
+ struct tevent_context *ev;
+ struct smbd_smb2_request *smb2req;
+ struct GUID req_guid;
+ struct smb_request *smb1req;
+ bool open_was_deferred;
+ struct tevent_immediate *im;
+ struct timeval request_time;
+ struct file_id id;
+ struct deferred_open_record *open_rec;
+ files_struct *result;
+ bool replay_operation;
+ uint8_t in_oplock_level;
+ uint32_t in_create_disposition;
+ uint32_t in_create_options;
+ int requested_oplock_level;
+ int info;
+ char *fname;
+ struct ea_list *ea_list;
+ NTTIME max_access_time;
+ struct security_descriptor *sec_desc;
+ uint64_t allocation_size;
+ struct GUID _create_guid;
+ struct GUID *create_guid;
+ struct GUID _purge_create_guid;
+ struct GUID *purge_create_guid;
+ bool update_open;
+ bool durable_requested;
+ uint32_t durable_timeout_msec;
+ bool do_durable_reconnect;
+ uint64_t persistent_id;
+ struct smb2_lease lease;
+ struct smb2_lease *lease_ptr;
+ ssize_t lease_len;
+ bool need_replay_cache;
+ struct smbXsrv_open *op;
+ NTTIME twrp_time;
+
+ struct smb2_create_blob *dhnc;
+ struct smb2_create_blob *dh2c;
+ struct smb2_create_blob *dhnq;
+ struct smb2_create_blob *dh2q;
+ struct smb2_create_blob *rqls;
+ struct smb2_create_blob *exta;
+ struct smb2_create_blob *mxac;
+ struct smb2_create_blob *secd;
+ struct smb2_create_blob *alsi;
+ struct smb2_create_blob *twrp;
+ struct smb2_create_blob *qfid;
+ struct smb2_create_blob *posx;
+ struct smb2_create_blob *svhdx;
+
+ uint8_t out_oplock_level;
+ uint32_t out_create_action;
+ struct timespec out_creation_ts;
+ struct timespec out_last_access_ts;
+ struct timespec out_last_write_ts;
+ struct timespec out_change_ts;
+ uint64_t out_allocation_size;
+ uint64_t out_end_of_file;
+ uint32_t out_file_attributes;
+ uint64_t out_file_id_persistent;
+ uint64_t out_file_id_volatile;
+ struct smb2_create_blobs *out_context_blobs;
+};
+
+static void smbd_smb2_create_purge_replay_cache(struct tevent_req *req,
+ const char *caller_func);
+
+static void smbd_smb2_create_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state)
+{
+ smbd_smb2_create_purge_replay_cache(req, __func__);
+}
+
+static NTSTATUS smbd_smb2_create_fetch_create_ctx(
+ struct tevent_req *req,
+ struct smb2_create_blobs *in_context_blobs)
+{
+ struct smbd_smb2_create_state *state = tevent_req_data(
+ req, struct smbd_smb2_create_state);
+ struct smbd_smb2_request *smb2req = state->smb2req;
+ struct smbXsrv_connection *xconn = smb2req->xconn;
+
+ state->dhnq = smb2_create_blob_find(in_context_blobs,
+ SMB2_CREATE_TAG_DHNQ);
+ state->dhnc = smb2_create_blob_find(in_context_blobs,
+ SMB2_CREATE_TAG_DHNC);
+ state->dh2q = smb2_create_blob_find(in_context_blobs,
+ SMB2_CREATE_TAG_DH2Q);
+ state->dh2c = smb2_create_blob_find(in_context_blobs,
+ SMB2_CREATE_TAG_DH2C);
+ if (xconn->smb2.server.capabilities & SMB2_CAP_LEASING) {
+ state->rqls = smb2_create_blob_find(in_context_blobs,
+ SMB2_CREATE_TAG_RQLS);
+ }
+
+ if (((state->dhnc != NULL) && (state->dh2c != NULL)) ||
+ ((state->dhnc != NULL) && (state->dh2q != NULL)) ||
+ ((state->dh2c != NULL) && (state->dhnq != NULL)) ||
+ ((state->dh2q != NULL) && (state->dh2c != NULL)))
+ {
+ /* not both are allowed at the same time */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (state->dhnc != NULL) {
+ uint32_t num_blobs_allowed;
+
+ if (state->dhnc->data.length != 16) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * According to MS-SMB2: 3.3.5.9.7, "Handling the
+ * SMB2_CREATE_DURABLE_HANDLE_RECONNECT Create Context",
+ * we should ignore an additional dhnq blob, but fail
+ * the request (with status OBJECT_NAME_NOT_FOUND) if
+ * any other extra create blob has been provided.
+ *
+ * (Note that the cases of an additional dh2q or dh2c blob
+ * which require a different error code, have been treated
+ * above.)
+ */
+
+ if (state->dhnq != NULL) {
+ num_blobs_allowed = 2;
+ } else {
+ num_blobs_allowed = 1;
+ }
+
+ if (state->rqls != NULL) {
+ num_blobs_allowed += 1;
+ }
+
+ if (in_context_blobs->num_blobs != num_blobs_allowed) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ }
+
+ if (state->dh2c!= NULL) {
+ uint32_t num_blobs_allowed;
+
+ if (state->dh2c->data.length != 36) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * According to MS-SMB2: 3.3.5.9.12, "Handling the
+ * SMB2_CREATE_DURABLE_HANDLE_RECONNECT_V2 Create Context",
+ * we should fail the request with status
+ * OBJECT_NAME_NOT_FOUND if any other create blob has been
+ * provided.
+ *
+ * (Note that the cases of an additional dhnq, dhnc or dh2q
+ * blob which require a different error code, have been
+ * treated above.)
+ */
+
+ num_blobs_allowed = 1;
+
+ if (state->rqls != NULL) {
+ num_blobs_allowed += 1;
+ }
+
+ if (in_context_blobs->num_blobs != num_blobs_allowed) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ }
+
+ state->exta = smb2_create_blob_find(in_context_blobs,
+ SMB2_CREATE_TAG_EXTA);
+ state->mxac = smb2_create_blob_find(in_context_blobs,
+ SMB2_CREATE_TAG_MXAC);
+ state->secd = smb2_create_blob_find(in_context_blobs,
+ SMB2_CREATE_TAG_SECD);
+ state->alsi = smb2_create_blob_find(in_context_blobs,
+ SMB2_CREATE_TAG_ALSI);
+ state->twrp = smb2_create_blob_find(in_context_blobs,
+ SMB2_CREATE_TAG_TWRP);
+ state->qfid = smb2_create_blob_find(in_context_blobs,
+ SMB2_CREATE_TAG_QFID);
+ if (xconn->protocol >= PROTOCOL_SMB3_02) {
+ /*
+ * This was introduced with SMB3_02
+ */
+ state->svhdx = smb2_create_blob_find(
+ in_context_blobs, SVHDX_OPEN_DEVICE_CONTEXT);
+ }
+ if (xconn->smb2.server.posix_extensions_negotiated &&
+ lp_smb3_unix_extensions(SNUM(state->smb1req->conn)))
+ {
+ /*
+ * Negprot only allowed this for proto>=3.11
+ */
+ SMB_ASSERT(xconn->protocol >= PROTOCOL_SMB3_11);
+
+ state->posx = smb2_create_blob_find(
+ in_context_blobs, SMB2_CREATE_TAG_POSIX);
+ /*
+ * Setting the bool below will cause
+ * ucf_flags_from_smb_request() to
+ * return UCF_POSIX_PATHNAMES in ucf_flags.
+ */
+ state->smb1req->posix_pathnames = (state->posx != NULL);
+ }
+
+ return NT_STATUS_OK;
+}
+
+static void smbd_smb2_create_before_exec(struct tevent_req *req);
+static void smbd_smb2_create_after_exec(struct tevent_req *req);
+static void smbd_smb2_create_finish(struct tevent_req *req);
+
+static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ uint8_t in_oplock_level,
+ uint32_t in_impersonation_level,
+ uint32_t in_desired_access,
+ uint32_t in_file_attributes,
+ uint32_t in_share_access,
+ uint32_t in_create_disposition,
+ uint32_t _in_create_options,
+ const char *in_name,
+ struct smb2_create_blobs in_context_blobs)
+{
+ struct tevent_req *req = NULL;
+ struct smbd_smb2_create_state *state = NULL;
+ NTSTATUS status;
+ struct smb_request *smb1req = NULL;
+ struct files_struct *dirfsp = NULL;
+ struct smb_filename *smb_fname = NULL;
+ uint32_t ucf_flags;
+ bool is_dfs = false;
+ bool is_posix = false;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_create_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ *state = (struct smbd_smb2_create_state) {
+ .ev = ev,
+ .smb2req = smb2req,
+ .in_oplock_level = in_oplock_level,
+ .in_create_disposition = in_create_disposition,
+ .in_create_options = _in_create_options,
+ };
+
+ smb1req = smbd_smb2_fake_smb_request(smb2req, NULL);
+ if (tevent_req_nomem(smb1req, req)) {
+ return tevent_req_post(req, state->ev);
+ }
+ state->smb1req = smb1req;
+
+ state->req_guid = smbd_request_guid(smb1req, 0);
+
+ tevent_req_set_cleanup_fn(req, smbd_smb2_create_cleanup);
+
+ if (smb2req->subreq == NULL) {
+ DBG_DEBUG("name [%s]\n", in_name);
+ } else {
+ struct smbd_smb2_create_state *old_state = tevent_req_data(
+ smb2req->subreq, struct smbd_smb2_create_state);
+
+ DBG_DEBUG("reentrant for file %s\n", in_name);
+
+ state->id = old_state->id;
+ state->request_time = old_state->request_time;
+ state->open_rec = talloc_move(state, &old_state->open_rec);
+ state->open_was_deferred = old_state->open_was_deferred;
+ state->_purge_create_guid = old_state->_purge_create_guid;
+ state->purge_create_guid = old_state->purge_create_guid;
+ old_state->purge_create_guid = NULL;
+ }
+
+ TALLOC_FREE(smb2req->subreq);
+ smb2req->subreq = req;
+
+ if (lp_fake_oplocks(SNUM(smb2req->tcon->compat))) {
+ state->requested_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+ } else {
+ state->requested_oplock_level = state->in_oplock_level;
+ }
+
+ /* these are ignored for SMB2 */
+ state->in_create_options &= ~(0x10); /* NTCREATEX_OPTIONS_SYNC_ALERT */
+ state->in_create_options &= ~(0x20); /* NTCREATEX_OPTIONS_ASYNC_ALERT */
+
+ in_file_attributes &= ~FILE_FLAG_POSIX_SEMANTICS;
+
+ is_dfs = (smb1req->flags2 & FLAGS2_DFS_PATHNAMES);
+ if (is_dfs) {
+ const char *non_dfs_in_name = NULL;
+ /*
+ * With a DFS flag set, remove any DFS prefix
+ * before further processing.
+ */
+ status = smb2_strip_dfs_path(in_name, &non_dfs_in_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, state->ev);
+ }
+ /*
+ * TODO: Note for dealing with reparse point errors.
+ * We will need to remember and store the number of characters
+ * we have removed here, which is (non_dfs_in_name - in_name)
+ * in order to correctly report how many characters we
+ * have removed before hitting the reparse point.
+ * This will be a patch needed once we properly
+ * deal with reparse points later.
+ */
+ in_name = non_dfs_in_name;
+ /*
+ * Now we're no longer dealing with a DFS path, so
+ * remove the flag.
+ */
+ smb1req->flags2 &= ~FLAGS2_DFS_PATHNAMES;
+ is_dfs = false;
+ }
+
+ state->fname = talloc_strdup(state, in_name);
+ if (tevent_req_nomem(state->fname, req)) {
+ return tevent_req_post(req, state->ev);
+ }
+
+ state->out_context_blobs = talloc_zero(state, struct smb2_create_blobs);
+ if (tevent_req_nomem(state->out_context_blobs, req)) {
+ return tevent_req_post(req, state->ev);
+ }
+
+ status = smbd_smb2_create_fetch_create_ctx(req, &in_context_blobs);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, state->ev);
+ }
+
+ if (IS_IPC(smb1req->conn)) {
+ const char *pipe_name = in_name;
+
+ if (state->dhnc != NULL || state->dh2c != NULL) {
+ /* durable handles are not supported on IPC$ */
+ tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND);
+ return tevent_req_post(req, state->ev);
+ }
+
+ if (!lp_nt_pipe_support()) {
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return tevent_req_post(req, state->ev);
+ }
+
+ status = open_np_file(smb1req, pipe_name, &state->result);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, state->ev);
+ }
+ state->info = FILE_WAS_OPENED;
+
+ smbd_smb2_create_finish(req);
+ return req;
+ }
+
+ if (CAN_PRINT(smb1req->conn)) {
+ if (state->dhnc != NULL || state->dh2c != NULL) {
+ /* durable handles are not supported on printers */
+ tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND);
+ return tevent_req_post(req, state->ev);
+ }
+
+ status = file_new(smb1req, smb1req->conn, &state->result);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, state->ev);
+ }
+
+ status = print_spool_open(state->result, in_name,
+ smb1req->vuid);
+ if (tevent_req_nterror(req, status)) {
+ file_free(smb1req, state->result);
+ return tevent_req_post(req, state->ev);
+ }
+ state->info = FILE_WAS_CREATED;
+
+ smbd_smb2_create_finish(req);
+ return req;
+ }
+
+ /* Check for trailing slash specific directory handling. */
+ status = windows_name_trailing_check(state->fname,
+ state->in_create_options);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, state->ev);
+ }
+
+ smbd_smb2_create_before_exec(req);
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, state->ev);
+ }
+
+ DBG_DEBUG("open execution phase\n");
+
+ /*
+ * For the backend file open procedure, there are
+ * three possible modes: replay operation (in which case
+ * there is nothing else to do), durable_reconnect or
+ * new open.
+ */
+ if (state->replay_operation) {
+ state->result = state->op->compat;
+ state->result->op = state->op;
+ state->update_open = false;
+ state->info = state->op->create_action;
+
+ smbd_smb2_create_after_exec(req);
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, state->ev);
+ }
+
+ smbd_smb2_create_finish(req);
+ return req;
+ }
+
+ if (state->do_durable_reconnect) {
+ DATA_BLOB new_cookie = data_blob_null;
+ NTTIME now = timeval_to_nttime(&smb2req->request_time);
+
+ status = smb2srv_open_recreate(smb2req->xconn,
+ smb1req->conn->session_info,
+ state->persistent_id,
+ state->create_guid,
+ now,
+ &state->op);
+ if (tevent_req_nterror(req, status)) {
+ DBG_NOTICE("smb2srv_open_recreate failed: %s\n",
+ nt_errstr(status));
+ return tevent_req_post(req, state->ev);
+ }
+
+ DBG_DEBUG("%s to recreate durable handle\n",
+ state->op->global->durable ? "succeeded" : "failed");
+
+ if (!state->op->global->durable) {
+ talloc_free(state->op);
+ tevent_req_nterror(req,
+ NT_STATUS_OBJECT_NAME_NOT_FOUND);
+ return tevent_req_post(req, state->ev);
+ }
+
+ status = SMB_VFS_DURABLE_RECONNECT(smb1req->conn,
+ smb1req,
+ state->op, /* smbXsrv_open input */
+ state->op->global->backend_cookie,
+ state->op, /* TALLOC_CTX */
+ &state->result,
+ &new_cookie);
+ if (!NT_STATUS_IS_OK(status)) {
+ NTSTATUS return_status;
+
+ return_status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+
+ DBG_NOTICE("durable_reconnect failed: %s => %s\n",
+ nt_errstr(status),
+ nt_errstr(return_status));
+
+ tevent_req_nterror(req, return_status);
+ return tevent_req_post(req, state->ev);
+ }
+
+ DBG_DEBUG("oplock_type=%u, lease_ptr==%p\n",
+ (unsigned)state->result->oplock_type, state->lease_ptr);
+
+ status = smbd_smb2_create_durable_lease_check(
+ smb1req, state->fname, state->result, state->lease_ptr);
+ if (tevent_req_nterror(req, status)) {
+ close_file_free(
+ smb1req, &state->result, SHUTDOWN_CLOSE);
+ return tevent_req_post(req, state->ev);
+ }
+
+ data_blob_free(&state->op->global->backend_cookie);
+ state->op->global->backend_cookie = new_cookie;
+
+ state->op->status = NT_STATUS_OK;
+ state->op->global->disconnect_time = 0;
+
+ /* save the timeout for later update */
+ state->durable_timeout_msec = state->op->global->durable_timeout_msec;
+
+ state->update_open = true;
+
+ state->info = FILE_WAS_OPENED;
+
+ smbd_smb2_create_after_exec(req);
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, state->ev);
+ }
+
+ smbd_smb2_create_finish(req);
+ return req;
+ }
+
+ if (state->requested_oplock_level == SMB2_OPLOCK_LEVEL_LEASE) {
+ if (state->lease_ptr == NULL) {
+ state->requested_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+ }
+ } else {
+ state->lease_ptr = NULL;
+ }
+
+ is_posix = (state->posx != NULL);
+
+ /* convert '\\' into '/' */
+ status = check_path_syntax(state->fname, is_posix);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, state->ev);
+ }
+
+ ucf_flags = filename_create_ucf_flags(
+ smb1req, state->in_create_disposition);
+
+ status = filename_convert_dirfsp(
+ req,
+ smb1req->conn,
+ state->fname,
+ ucf_flags,
+ state->twrp_time,
+ &dirfsp,
+ &smb_fname);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, state->ev);
+ }
+
+ /*
+ * MS-SMB2: 2.2.13 SMB2 CREATE Request
+ * ImpersonationLevel ... MUST contain one of the
+ * following values. The server MUST validate this
+ * field, but otherwise ignore it.
+ *
+ * NB. The source4/torture/smb2/durable_open.c test
+ * shows this check is only done on real opens, not
+ * on durable handle-reopens.
+ */
+
+ if (in_impersonation_level >
+ SMB2_IMPERSONATION_DELEGATE) {
+ tevent_req_nterror(req,
+ NT_STATUS_BAD_IMPERSONATION_LEVEL);
+ return tevent_req_post(req, state->ev);
+ }
+
+ /*
+ * We know we're going to do a local open, so now
+ * we must be protocol strict. JRA.
+ *
+ * MS-SMB2: 3.3.5.9 - Receiving an SMB2 CREATE Request
+ * If the file name length is greater than zero and the
+ * first character is a path separator character, the
+ * server MUST fail the request with
+ * STATUS_INVALID_PARAMETER.
+ */
+ if (in_name[0] == '/') {
+ /* Names starting with '/' are never allowed. */
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ if (!is_posix && (in_name[0] == '\\')) {
+ /*
+ * Windows names starting with '\' are not allowed.
+ */
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ status = SMB_VFS_CREATE_FILE(smb1req->conn,
+ smb1req,
+ dirfsp,
+ smb_fname,
+ in_desired_access,
+ in_share_access,
+ state->in_create_disposition,
+ state->in_create_options,
+ in_file_attributes,
+ map_smb2_oplock_levels_to_samba(
+ state->requested_oplock_level),
+ state->lease_ptr,
+ state->allocation_size,
+ 0, /* private_flags */
+ state->sec_desc,
+ state->ea_list,
+ &state->result,
+ &state->info,
+ &in_context_blobs,
+ state->out_context_blobs);
+ if (NT_STATUS_IS_OK(status) &&
+ !(state->in_create_options & FILE_OPEN_REPARSE_POINT))
+ {
+
+ mode_t mode = state->result->fsp_name->st.st_ex_mode;
+
+ if (!(S_ISREG(mode) || S_ISDIR(mode))) {
+ /*
+ * Only open files and dirs without
+ * FILE_OPEN_REPARSE_POINT
+ */
+ close_file_free(smb1req, &state->result, ERROR_CLOSE);
+ status = NT_STATUS_IO_REPARSE_TAG_NOT_HANDLED;
+ }
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(smb1req->xconn, smb1req->mid)) {
+ SMBPROFILE_IOBYTES_ASYNC_SET_IDLE(smb2req->profile);
+ return req;
+ }
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, state->ev);
+ }
+ state->op = state->result->op;
+
+ smbd_smb2_create_after_exec(req);
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, state->ev);
+ }
+
+ smbd_smb2_create_finish(req);
+ return req;
+}
+
+static void smbd_smb2_create_purge_replay_cache(struct tevent_req *req,
+ const char *caller_func)
+{
+ struct smbd_smb2_create_state *state = tevent_req_data(
+ req, struct smbd_smb2_create_state);
+ NTSTATUS status;
+
+ if (state->purge_create_guid == NULL) {
+ return;
+ }
+
+ status = smbXsrv_open_purge_replay_cache(state->smb2req->xconn->client,
+ state->purge_create_guid);
+ if (!NT_STATUS_IS_OK(status)) {
+ struct GUID_txt_buf buf;
+
+ D_ERR("%s: smbXsrv_open_purge_replay_cache(%s) %s\n",
+ caller_func,
+ GUID_buf_string(state->purge_create_guid, &buf),
+ nt_errstr(status));
+ }
+
+ state->purge_create_guid = NULL;
+}
+
+static void smbd_smb2_create_before_exec(struct tevent_req *req)
+{
+ struct smbd_smb2_create_state *state = tevent_req_data(
+ req, struct smbd_smb2_create_state);
+ struct smbd_smb2_request *smb2req = state->smb2req;
+ NTSTATUS status;
+
+ if (state->exta != NULL) {
+ if (!lp_ea_support(SNUM(smb2req->tcon->compat))) {
+ tevent_req_nterror(req, NT_STATUS_EAS_NOT_SUPPORTED);
+ return;
+ }
+
+ state->ea_list = read_nttrans_ea_list(
+ state,
+ (const char *)state->exta->data.data,
+ state->exta->data.length);
+ if (state->ea_list == NULL) {
+ DEBUG(10,("smbd_smb2_create_send: read_ea_name_list failed.\n"));
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if ((state->posx == NULL) &&
+ ea_list_has_invalid_name(state->ea_list)) {
+ tevent_req_nterror(req, STATUS_INVALID_EA_NAME);
+ return;
+ }
+ }
+
+ if (state->mxac != NULL) {
+ if (state->mxac->data.length == 0) {
+ state->max_access_time = 0;
+ } else if (state->mxac->data.length == 8) {
+ state->max_access_time = BVAL(state->mxac->data.data, 0);
+ } else {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ }
+
+ if (state->secd != NULL) {
+ enum ndr_err_code ndr_err;
+
+ state->sec_desc = talloc_zero(state, struct security_descriptor);
+ if (tevent_req_nomem(state->sec_desc, req)) {
+ return;
+ }
+
+ ndr_err = ndr_pull_struct_blob(&state->secd->data,
+ state->sec_desc, state->sec_desc,
+ (ndr_pull_flags_fn_t)ndr_pull_security_descriptor);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(2,("ndr_pull_security_descriptor failed: %s\n",
+ ndr_errstr(ndr_err)));
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ }
+
+ if (state->dhnq != NULL) {
+ if (state->dhnq->data.length != 16) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if (state->dh2q != NULL) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ /*
+ * durable handle request is processed below.
+ */
+ state->durable_requested = true;
+ /*
+ * Set the timeout to 16 mins.
+ *
+ * TODO: test this against Windows 2012
+ * as the default for durable v2 is 1 min.
+ */
+ state->durable_timeout_msec = (16*60*1000);
+ }
+
+ if (state->dh2q != NULL) {
+ const uint8_t *p = state->dh2q->data.data;
+ NTTIME now = timeval_to_nttime(&smb2req->request_time);
+ uint32_t durable_v2_timeout = 0;
+ DATA_BLOB create_guid_blob;
+ const uint8_t *hdr;
+ uint32_t flags;
+
+ if (state->dh2q->data.length != 32) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if (state->dhnq != NULL) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ durable_v2_timeout = IVAL(p, 0);
+ create_guid_blob = data_blob_const(p + 16, 16);
+
+ status = GUID_from_ndr_blob(&create_guid_blob,
+ &state->_create_guid);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ state->create_guid = &state->_create_guid;
+
+ /*
+ * we need to store the create_guid later
+ */
+ state->update_open = true;
+
+ /*
+ * And we need to create a cache for replaying the
+ * create.
+ */
+ state->need_replay_cache = true;
+
+ /*
+ * durable handle v2 request processed below
+ */
+ state->durable_requested = true;
+ state->durable_timeout_msec = MIN(durable_v2_timeout, 300*1000);
+ if (state->durable_timeout_msec == 0) {
+ /*
+ * Set the timeout to 1 min as default.
+ *
+ * This matches Windows 2012.
+ */
+ state->durable_timeout_msec = (60*1000);
+ }
+
+ /*
+ * Check for replay operation.
+ * Only consider it when we have dh2q.
+ * If we do not have a replay operation, verify that
+ * the create_guid is not cached for replay.
+ */
+ hdr = SMBD_SMB2_IN_HDR_PTR(smb2req);
+ flags = IVAL(hdr, SMB2_HDR_FLAGS);
+ state->replay_operation =
+ flags & SMB2_HDR_FLAG_REPLAY_OPERATION;
+
+ status = smb2srv_open_lookup_replay_cache(smb2req->xconn,
+ state->req_guid,
+ *state->create_guid,
+ state->fname,
+ now,
+ &state->op);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_FWP_RESERVED)) {
+ /*
+ * We've reserved the replay_cache record
+ * for ourself, indicating we're still
+ * in progress.
+ *
+ * It means the smbd_smb2_create_cleanup()
+ * may need to call smbXsrv_open_purge_replay_cache()
+ * in order to cleanup.
+ */
+ SMB_ASSERT(state->op == NULL);
+ state->_purge_create_guid = state->_create_guid;
+ state->purge_create_guid = &state->_purge_create_guid;
+ status = NT_STATUS_OK;
+ state->replay_operation = false;
+ } else if (NT_STATUS_EQUAL(status, NT_STATUS_FILE_NOT_AVAILABLE)) {
+ tevent_req_nterror(req, status);
+ return;
+ } else if (tevent_req_nterror(req, status)) {
+ DBG_WARNING("smb2srv_open_lookup_replay_cache "
+ "failed: %s\n", nt_errstr(status));
+ return;
+ } else if (!state->replay_operation) {
+ /*
+ * If a create without replay operation flag
+ * is sent but with a create_guid that is
+ * currently in the replay cache -- fail.
+ */
+ status = NT_STATUS_DUPLICATE_OBJECTID;
+ (void)tevent_req_nterror(req, status);
+ return;
+ }
+ }
+
+ if (state->dhnc != NULL) {
+ state->persistent_id = BVAL(state->dhnc->data.data, 0);
+ state->do_durable_reconnect = true;
+ }
+
+ if (state->dh2c != NULL) {
+ const uint8_t *p = state->dh2c->data.data;
+ DATA_BLOB create_guid_blob;
+
+ state->persistent_id = BVAL(p, 0);
+ create_guid_blob = data_blob_const(p + 16, 16);
+
+ status = GUID_from_ndr_blob(&create_guid_blob,
+ &state->_create_guid);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->create_guid = &state->_create_guid;
+ state->do_durable_reconnect = true;
+ }
+
+ if (state->alsi != NULL) {
+ if (state->alsi->data.length != 8) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ state->allocation_size = BVAL(state->alsi->data.data, 0);
+ }
+
+ if (state->twrp != NULL) {
+ if (state->twrp->data.length != 8) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ state->twrp_time = BVAL(state->twrp->data.data, 0);
+ }
+
+ if (state->qfid != NULL) {
+ if (state->qfid->data.length != 0) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ }
+
+ if (state->rqls != NULL) {
+ ssize_t lease_len = -1;
+
+ lease_len = smb2_lease_pull(state->rqls->data.data,
+ state->rqls->data.length,
+ &state->lease);
+ if (lease_len == -1) {
+ tevent_req_nterror(
+ req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ state->lease_ptr = &state->lease;
+
+ if (DEBUGLEVEL >= 10) {
+ DEBUG(10, ("Got lease request size %d\n",
+ (int)lease_len));
+ NDR_PRINT_DEBUG(smb2_lease, state->lease_ptr);
+ }
+
+ if (!smb2_lease_key_valid(&state->lease.lease_key)) {
+ state->lease_ptr = NULL;
+ state->requested_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+ }
+
+ if ((smb2req->xconn->protocol < PROTOCOL_SMB3_00) &&
+ (state->lease.lease_version != 1))
+ {
+ DEBUG(10, ("v2 lease key only for SMB3\n"));
+ state->lease_ptr = NULL;
+ }
+
+ /*
+ * Replay with a lease is only allowed if the
+ * established open carries a lease with the
+ * same lease key.
+ */
+ if (state->replay_operation) {
+ struct smb2_lease *op_ls =
+ &state->op->compat->lease->lease;
+ int op_oplock = state->op->compat->oplock_type;
+
+ if (map_samba_oplock_levels_to_smb2(op_oplock)
+ != SMB2_OPLOCK_LEVEL_LEASE)
+ {
+ status = NT_STATUS_ACCESS_DENIED;
+ (void)tevent_req_nterror(req, status);
+ return;
+ }
+ if (!smb2_lease_key_equal(&state->lease.lease_key,
+ &op_ls->lease_key))
+ {
+ status = NT_STATUS_ACCESS_DENIED;
+ (void)tevent_req_nterror(req, status);
+ return;
+ }
+ }
+ }
+
+ if (state->posx != NULL) {
+ if (state->posx->data.length != 4) {
+ DBG_DEBUG("Got %zu bytes POSX cctx, expected 4\n",
+ state->posx->data.length);
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ }
+}
+
+static void smbd_smb2_create_after_exec(struct tevent_req *req)
+{
+ struct smbd_smb2_create_state *state = tevent_req_data(
+ req, struct smbd_smb2_create_state);
+ connection_struct *conn = state->result->conn;
+ NTSTATUS status;
+
+ /*
+ * here we have op == result->op
+ */
+
+ DBG_DEBUG("response construction phase\n");
+
+ state->out_file_attributes = fdos_mode(state->result);
+
+ if (state->mxac != NULL) {
+ NTTIME last_write_time;
+
+ last_write_time = full_timespec_to_nt_time(
+ &state->result->fsp_name->st.st_ex_mtime);
+ if (last_write_time != state->max_access_time) {
+ uint8_t p[8];
+ uint32_t max_access_granted;
+ DATA_BLOB blob = data_blob_const(p, sizeof(p));
+
+ status = smbd_calculate_access_mask_fsp(
+ conn->cwd_fsp,
+ state->result,
+ false,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &max_access_granted);
+
+ SIVAL(p, 0, NT_STATUS_V(status));
+ SIVAL(p, 4, max_access_granted);
+
+ status = smb2_create_blob_add(
+ state->out_context_blobs,
+ state->out_context_blobs,
+ SMB2_CREATE_TAG_MXAC,
+ blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+ }
+
+ if (!state->replay_operation && state->durable_requested &&
+ (fsp_lease_type(state->result) & SMB2_LEASE_HANDLE))
+ {
+ status = SMB_VFS_DURABLE_COOKIE(
+ state->result,
+ state->op,
+ &state->op->global->backend_cookie);
+ if (!NT_STATUS_IS_OK(status)) {
+ state->op->global->backend_cookie = data_blob_null;
+ }
+ }
+ if (!state->replay_operation && state->op->global->backend_cookie.length > 0)
+ {
+ state->update_open = true;
+
+ state->op->global->durable = true;
+ state->op->global->durable_timeout_msec = state->durable_timeout_msec;
+ }
+
+ if (state->update_open) {
+ state->op->global->create_guid = state->_create_guid;
+ if (state->need_replay_cache) {
+ state->op->flags |= SMBXSRV_OPEN_NEED_REPLAY_CACHE;
+ }
+
+ status = smbXsrv_open_update(state->op);
+ DEBUG(10, ("smb2_create_send: smbXsrv_open_update "
+ "returned %s\n",
+ nt_errstr(status)));
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ /*
+ * We should not purge the replay cache anymore
+ * as it's attached to the smbXsrv_open record now.
+ */
+ state->purge_create_guid = NULL;
+ }
+
+ if (state->dhnq != NULL && state->op->global->durable) {
+ uint8_t p[8] = { 0, };
+ DATA_BLOB blob = data_blob_const(p, sizeof(p));
+
+ status = smb2_create_blob_add(state->out_context_blobs,
+ state->out_context_blobs,
+ SMB2_CREATE_TAG_DHNQ,
+ blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ if (state->dh2q != NULL && state->op->global->durable &&
+ /*
+ * For replay operations, we return the dh2q blob
+ * in the case of oplocks not based on the state of
+ * the open, but on whether it could have been granted
+ * for the request data. In the case of leases instead,
+ * the state of the open is used...
+ */
+ (!state->replay_operation ||
+ state->in_oplock_level == SMB2_OPLOCK_LEVEL_BATCH ||
+ state->in_oplock_level == SMB2_OPLOCK_LEVEL_LEASE))
+ {
+ uint8_t p[8] = { 0, };
+ DATA_BLOB blob = data_blob_const(p, sizeof(p));
+ uint32_t durable_v2_response_flags = 0;
+
+ SIVAL(p, 0, state->op->global->durable_timeout_msec);
+ SIVAL(p, 4, durable_v2_response_flags);
+
+ status = smb2_create_blob_add(state->out_context_blobs,
+ state->out_context_blobs,
+ SMB2_CREATE_TAG_DH2Q,
+ blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ if (state->qfid != NULL) {
+ uint8_t p[32];
+ SMB_STRUCT_STAT *base_sp = state->result->base_fsp ?
+ &state->result->base_fsp->fsp_name->st :
+ &state->result->fsp_name->st;
+ uint64_t file_id = SMB_VFS_FS_FILE_ID(conn, base_sp);
+ DATA_BLOB blob = data_blob_const(p, sizeof(p));
+
+ ZERO_STRUCT(p);
+
+ /* From conversations with Microsoft engineers at
+ the MS plugfest. The first 8 bytes are the "volume index"
+ == inode, the second 8 bytes are the "volume id",
+ == dev. This will be updated in the SMB2 doc. */
+ SBVAL(p, 0, file_id);
+ SIVAL(p, 8, base_sp->st_ex_dev);/* FileIndexHigh */
+
+ status = smb2_create_blob_add(state->out_context_blobs,
+ state->out_context_blobs,
+ SMB2_CREATE_TAG_QFID,
+ blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ if ((state->rqls != NULL) && (state->result->oplock_type == LEASE_OPLOCK)) {
+ uint8_t buf[52];
+ struct smb2_lease lease;
+ size_t lease_len;
+
+ lease = state->result->lease->lease;
+
+ lease_len = sizeof(buf);
+ if (lease.lease_version == 1) {
+ lease_len = 32;
+ }
+
+ if (!smb2_lease_push(&lease, buf, lease_len)) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto fail;
+ }
+
+ status = smb2_create_blob_add(
+ state, state->out_context_blobs,
+ SMB2_CREATE_TAG_RQLS,
+ data_blob_const(buf, lease_len));
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ if (state->posx != NULL) {
+ struct stat_ex *psbuf = &state->result->fsp_name->st;
+ struct smb3_posix_cc_info cc = {
+ .nlinks = psbuf->st_ex_nlink,
+ .posix_perms = unix_perms_to_wire(psbuf->st_ex_mode &
+ ~S_IFMT),
+ };
+ uint8_t buf[sizeof(struct smb3_posix_cc_info)];
+ struct ndr_push ndr = {
+ .data = buf,
+ .alloc_size = sizeof(buf),
+ .fixed_buf_size = true,
+ };
+ enum ndr_err_code ndr_err;
+
+ uid_to_sid(&cc.owner, psbuf->st_ex_uid);
+ gid_to_sid(&cc.group, psbuf->st_ex_gid);
+
+ ndr_err =
+ ndr_push_smb3_posix_cc_info(&ndr,
+ NDR_SCALARS | NDR_BUFFERS,
+ &cc);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = NT_STATUS_INSUFFICIENT_RESOURCES;
+ goto fail;
+ }
+
+ status = smb2_create_blob_add(state->out_context_blobs,
+ state->out_context_blobs,
+ SMB2_CREATE_TAG_POSIX,
+ (DATA_BLOB){
+ .data = buf,
+ .length = ndr.offset,
+ });
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ return;
+
+fail:
+ close_file_free(state->smb1req, &state->result, ERROR_CLOSE);
+ tevent_req_nterror(req, status);
+}
+
+static void smbd_smb2_create_finish(struct tevent_req *req)
+{
+ struct smbd_smb2_create_state *state = tevent_req_data(
+ req, struct smbd_smb2_create_state);
+ struct smbd_smb2_request *smb2req = state->smb2req;
+ struct smb_request *smb1req = state->smb1req;
+ files_struct *result = state->result;
+
+ smb2req->compat_chain_fsp = smb1req->chain_fsp;
+
+ if (state->replay_operation) {
+ state->out_oplock_level = state->in_oplock_level;
+ } else if (lp_fake_oplocks(SNUM(smb2req->tcon->compat))) {
+ state->out_oplock_level = state->in_oplock_level;
+ } else {
+ state->out_oplock_level = map_samba_oplock_levels_to_smb2(result->oplock_type);
+ }
+
+ if ((state->in_create_disposition == FILE_SUPERSEDE)
+ && (state->info == FILE_WAS_OVERWRITTEN)) {
+ state->out_create_action = FILE_WAS_SUPERSEDED;
+ } else {
+ state->out_create_action = state->info;
+ }
+ result->op->create_action = state->out_create_action;
+
+ state->out_creation_ts = get_create_timespec(smb1req->conn,
+ result, result->fsp_name);
+ state->out_last_access_ts = result->fsp_name->st.st_ex_atime;
+ state->out_last_write_ts = result->fsp_name->st.st_ex_mtime;
+ state->out_change_ts = get_change_timespec(smb1req->conn,
+ result, result->fsp_name);
+
+ if (lp_dos_filetime_resolution(SNUM(smb2req->tcon->compat))) {
+ dos_filetime_timespec(&state->out_creation_ts);
+ dos_filetime_timespec(&state->out_last_access_ts);
+ dos_filetime_timespec(&state->out_last_write_ts);
+ dos_filetime_timespec(&state->out_change_ts);
+ }
+
+ state->out_allocation_size =
+ SMB_VFS_GET_ALLOC_SIZE(smb1req->conn, result,
+ &(result->fsp_name->st));
+ state->out_end_of_file = result->fsp_name->st.st_ex_size;
+ if (state->out_file_attributes == 0) {
+ state->out_file_attributes = FILE_ATTRIBUTE_NORMAL;
+ }
+ state->out_file_id_persistent = result->op->global->open_persistent_id;
+ state->out_file_id_volatile = result->op->global->open_volatile_id;
+
+ DBG_DEBUG("%s - %s\n", fsp_str_dbg(result), fsp_fnum_dbg(result));
+
+ tevent_req_done(req);
+ tevent_req_post(req, state->ev);
+}
+
+static NTSTATUS smbd_smb2_create_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ uint8_t *out_oplock_level,
+ uint32_t *out_create_action,
+ struct timespec *out_creation_ts,
+ struct timespec *out_last_access_ts,
+ struct timespec *out_last_write_ts,
+ struct timespec *out_change_ts,
+ uint64_t *out_allocation_size,
+ uint64_t *out_end_of_file,
+ uint32_t *out_file_attributes,
+ uint64_t *out_file_id_persistent,
+ uint64_t *out_file_id_volatile,
+ struct smb2_create_blobs *out_context_blobs)
+{
+ NTSTATUS status;
+ struct smbd_smb2_create_state *state = tevent_req_data(req,
+ struct smbd_smb2_create_state);
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *out_oplock_level = state->out_oplock_level;
+ *out_create_action = state->out_create_action;
+ *out_creation_ts = state->out_creation_ts;
+ *out_last_access_ts = state->out_last_access_ts;
+ *out_last_write_ts = state->out_last_write_ts;
+ *out_change_ts = state->out_change_ts;
+ *out_allocation_size = state->out_allocation_size;
+ *out_end_of_file = state->out_end_of_file;
+ *out_file_attributes = state->out_file_attributes;
+ *out_file_id_persistent = state->out_file_id_persistent;
+ *out_file_id_volatile = state->out_file_id_volatile;
+ *out_context_blobs = *(state->out_context_blobs);
+
+ talloc_steal(mem_ctx, state->out_context_blobs->blobs);
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+/*********************************************************
+ Code for dealing with deferred opens.
+*********************************************************/
+
+bool get_deferred_open_message_state_smb2(struct smbd_smb2_request *smb2req,
+ struct timeval *p_request_time,
+ struct deferred_open_record **open_rec)
+{
+ struct smbd_smb2_create_state *state = NULL;
+ struct tevent_req *req = NULL;
+
+ if (!smb2req) {
+ return false;
+ }
+ req = smb2req->subreq;
+ if (!req) {
+ return false;
+ }
+ state = tevent_req_data(req, struct smbd_smb2_create_state);
+ if (!state) {
+ return false;
+ }
+ if (!state->open_was_deferred) {
+ return false;
+ }
+ if (p_request_time) {
+ *p_request_time = state->request_time;
+ }
+ if (open_rec != NULL) {
+ *open_rec = state->open_rec;
+ }
+ return true;
+}
+
+/*********************************************************
+ Re-process this call early - requested by message or
+ close.
+*********************************************************/
+
+static struct smbd_smb2_request *find_open_smb2req(
+ struct smbXsrv_connection *xconn, uint64_t mid)
+{
+ struct smbd_smb2_request *smb2req;
+
+ for (smb2req = xconn->smb2.requests; smb2req; smb2req = smb2req->next) {
+ uint64_t message_id;
+ if (smb2req->subreq == NULL) {
+ /* This message has been processed. */
+ continue;
+ }
+ if (!tevent_req_is_in_progress(smb2req->subreq)) {
+ /* This message has been processed. */
+ continue;
+ }
+ message_id = get_mid_from_smb2req(smb2req);
+ if (message_id == mid) {
+ return smb2req;
+ }
+ }
+ return NULL;
+}
+
+bool open_was_deferred_smb2(struct smbXsrv_connection *xconn, uint64_t mid)
+{
+ struct smbd_smb2_create_state *state = NULL;
+ struct smbd_smb2_request *smb2req;
+
+ smb2req = find_open_smb2req(xconn, mid);
+
+ if (!smb2req) {
+ DEBUG(10,("open_was_deferred_smb2: mid %llu smb2req == NULL\n",
+ (unsigned long long)mid));
+ return false;
+ }
+ if (!smb2req->subreq) {
+ return false;
+ }
+ if (!tevent_req_is_in_progress(smb2req->subreq)) {
+ return false;
+ }
+ state = tevent_req_data(smb2req->subreq,
+ struct smbd_smb2_create_state);
+ if (!state) {
+ return false;
+ }
+ /* It's not in progress if there's no timeout event. */
+ if (!state->open_was_deferred) {
+ return false;
+ }
+
+ DEBUG(10,("open_was_deferred_smb2: mid = %llu\n",
+ (unsigned long long)mid));
+
+ return true;
+}
+
+static void remove_deferred_open_message_smb2_internal(struct smbd_smb2_request *smb2req,
+ uint64_t mid)
+{
+ struct smbd_smb2_create_state *state = NULL;
+
+ if (!smb2req->subreq) {
+ return;
+ }
+ if (!tevent_req_is_in_progress(smb2req->subreq)) {
+ return;
+ }
+ state = tevent_req_data(smb2req->subreq,
+ struct smbd_smb2_create_state);
+ if (!state) {
+ return;
+ }
+
+ DEBUG(10,("remove_deferred_open_message_smb2_internal: "
+ "mid %llu\n",
+ (unsigned long long)mid ));
+
+ state->open_was_deferred = false;
+ /* Ensure we don't have any outstanding immediate event. */
+ TALLOC_FREE(state->im);
+ TALLOC_FREE(state->open_rec);
+}
+
+void remove_deferred_open_message_smb2(
+ struct smbXsrv_connection *xconn, uint64_t mid)
+{
+ struct smbd_smb2_request *smb2req;
+
+ smb2req = find_open_smb2req(xconn, mid);
+
+ if (!smb2req) {
+ DEBUG(10,("remove_deferred_open_message_smb2: "
+ "can't find mid %llu\n",
+ (unsigned long long)mid ));
+ return;
+ }
+ remove_deferred_open_message_smb2_internal(smb2req, mid);
+}
+
+static void smbd_smb2_create_request_dispatch_immediate(struct tevent_context *ctx,
+ struct tevent_immediate *im,
+ void *private_data)
+{
+ struct smbd_smb2_request *smb2req = talloc_get_type_abort(private_data,
+ struct smbd_smb2_request);
+ uint64_t mid = get_mid_from_smb2req(smb2req);
+ NTSTATUS status;
+
+ DEBUG(10,("smbd_smb2_create_request_dispatch_immediate: "
+ "re-dispatching mid %llu\n",
+ (unsigned long long)mid ));
+
+ status = smbd_smb2_request_dispatch(smb2req);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(status));
+ return;
+ }
+}
+
+bool schedule_deferred_open_message_smb2(
+ struct smbXsrv_connection *xconn, uint64_t mid)
+{
+ struct smbd_smb2_create_state *state = NULL;
+ struct smbd_smb2_request *smb2req;
+
+ smb2req = find_open_smb2req(xconn, mid);
+
+ if (!smb2req) {
+ DEBUG(10,("schedule_deferred_open_message_smb2: "
+ "can't find mid %llu\n",
+ (unsigned long long)mid ));
+ return false;
+ }
+ if (!smb2req->subreq) {
+ return false;
+ }
+ if (!tevent_req_is_in_progress(smb2req->subreq)) {
+ return false;
+ }
+ state = tevent_req_data(smb2req->subreq,
+ struct smbd_smb2_create_state);
+ if (!state) {
+ return false;
+ }
+
+ /* Ensure we don't have any outstanding immediate event. */
+ TALLOC_FREE(state->im);
+
+ /*
+ * This is subtle. We must null out the callback
+ * before rescheduling, else the first call to
+ * tevent_req_nterror() causes the _receive()
+ * function to be called, this causing tevent_req_post()
+ * to crash.
+ */
+ tevent_req_set_callback(smb2req->subreq, NULL, NULL);
+
+ state->im = tevent_create_immediate(smb2req);
+ if (!state->im) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(NT_STATUS_NO_MEMORY));
+ return false;
+ }
+
+ DEBUG(10,("schedule_deferred_open_message_smb2: "
+ "re-processing mid %llu\n",
+ (unsigned long long)mid ));
+
+ tevent_schedule_immediate(state->im,
+ smb2req->sconn->ev_ctx,
+ smbd_smb2_create_request_dispatch_immediate,
+ smb2req);
+
+ return true;
+}
+
+static bool smbd_smb2_create_cancel(struct tevent_req *req)
+{
+ struct smbd_smb2_request *smb2req = NULL;
+ struct smbd_smb2_create_state *state = tevent_req_data(req,
+ struct smbd_smb2_create_state);
+ uint64_t mid;
+
+ if (!state) {
+ return false;
+ }
+
+ if (!state->smb2req) {
+ return false;
+ }
+
+ smb2req = state->smb2req;
+ mid = get_mid_from_smb2req(smb2req);
+
+ if (is_deferred_open_async(state->open_rec)) {
+ /* Can't cancel an async create. */
+ return false;
+ }
+
+ remove_deferred_open_message_smb2_internal(smb2req, mid);
+
+ tevent_req_defer_callback(req, smb2req->sconn->ev_ctx);
+ tevent_req_nterror(req, NT_STATUS_CANCELLED);
+ return true;
+}
+
+bool push_deferred_open_message_smb2(struct smbd_smb2_request *smb2req,
+ struct timeval request_time,
+ struct timeval timeout,
+ struct file_id id,
+ struct deferred_open_record *open_rec)
+{
+ struct tevent_req *req = NULL;
+ struct smbd_smb2_create_state *state = NULL;
+ struct timeval end_time;
+
+ if (!smb2req) {
+ return false;
+ }
+ req = smb2req->subreq;
+ if (!req) {
+ return false;
+ }
+ state = tevent_req_data(req, struct smbd_smb2_create_state);
+ if (!state) {
+ return false;
+ }
+ state->id = id;
+ state->request_time = request_time;
+ state->open_rec = talloc_move(state, &open_rec);
+
+ /* Re-schedule us to retry on timer expiry. */
+ end_time = timeval_sum(&request_time, &timeout);
+
+ DEBUG(10,("push_deferred_open_message_smb2: "
+ "timeout at %s\n",
+ timeval_string(talloc_tos(),
+ &end_time,
+ true) ));
+
+ state->open_was_deferred = true;
+
+ /* allow this request to be canceled */
+ tevent_req_set_cancel_fn(req, smbd_smb2_create_cancel);
+
+ return true;
+}
diff --git a/source3/smbd/smb2_flush.c b/source3/smbd/smb2_flush.c
new file mode 100644
index 0000000..35f545a
--- /dev/null
+++ b/source3/smbd/smb2_flush.c
@@ -0,0 +1,257 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "libcli/security/security.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static struct tevent_req *smbd_smb2_flush_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *fsp);
+static NTSTATUS smbd_smb2_flush_recv(struct tevent_req *req);
+
+static void smbd_smb2_request_flush_done(struct tevent_req *subreq);
+NTSTATUS smbd_smb2_request_process_flush(struct smbd_smb2_request *req)
+{
+ NTSTATUS status;
+ const uint8_t *inbody;
+ uint64_t in_file_id_persistent;
+ uint64_t in_file_id_volatile;
+ struct files_struct *in_fsp;
+ struct tevent_req *subreq;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x18);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ in_file_id_persistent = BVAL(inbody, 0x08);
+ in_file_id_volatile = BVAL(inbody, 0x10);
+
+ in_fsp = file_fsp_smb2(req, in_file_id_persistent, in_file_id_volatile);
+ if (in_fsp == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED);
+ }
+
+ subreq = smbd_smb2_flush_send(req, req->sconn->ev_ctx,
+ req, in_fsp);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_flush_done, req);
+
+ return smbd_smb2_request_pending_queue(req, subreq, 500);
+}
+
+static void smbd_smb2_request_flush_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *req = tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ DATA_BLOB outbody;
+ NTSTATUS status;
+ NTSTATUS error; /* transport error */
+
+ status = smbd_smb2_flush_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ error = smbd_smb2_request_error(req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ outbody = smbd_smb2_generate_outbody(req, 0x04);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x04); /* struct size */
+ SSVAL(outbody.data, 0x02, 0); /* reserved */
+
+ error = smbd_smb2_request_done(req, outbody, NULL);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+struct smbd_smb2_flush_state {
+ struct smbd_smb2_request *smb2req;
+ struct files_struct *fsp;
+};
+
+static void smbd_smb2_flush_done(struct tevent_req *subreq);
+
+static struct tevent_req *smbd_smb2_flush_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *fsp)
+{
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct smbd_smb2_flush_state *state;
+ struct smb_request *smbreq;
+ bool is_compound = false;
+ bool is_last_in_compound = false;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_flush_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->smb2req = smb2req;
+ state->fsp = fsp;
+
+ DEBUG(10,("smbd_smb2_flush: %s - %s\n",
+ fsp_str_dbg(fsp), fsp_fnum_dbg(fsp)));
+
+ smbreq = smbd_smb2_fake_smb_request(smb2req, fsp);
+ if (tevent_req_nomem(smbreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (IS_IPC(smbreq->conn)) {
+ tevent_req_nterror(req, NT_STATUS_NOT_IMPLEMENTED);
+ return tevent_req_post(req, ev);
+ }
+
+ status = check_any_access_fsp(fsp, FILE_WRITE_DATA|FILE_APPEND_DATA);
+ if (!NT_STATUS_IS_OK(status)) {
+ bool allow_dir_flush = false;
+ uint32_t flush_access = FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY;
+
+ if (!fsp->fsp_flags.is_directory) {
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * Directories are not writable in the conventional
+ * sense, but if opened with *either*
+ * FILE_ADD_FILE or FILE_ADD_SUBDIRECTORY
+ * they can be flushed.
+ */
+
+ status = check_any_access_fsp(fsp, flush_access);
+ if (NT_STATUS_IS_OK(status)) {
+ allow_dir_flush = true;
+ }
+
+ if (allow_dir_flush == false) {
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ if (fsp_get_io_fd(fsp) == -1) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return tevent_req_post(req, ev);
+ }
+
+ if (!lp_strict_sync(SNUM(smbreq->conn))) {
+ /*
+ * No strict sync. Don't really do
+ * anything here.
+ */
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = SMB_VFS_FSYNC_SEND(state, ev, fsp);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_set_callback(subreq, smbd_smb2_flush_done, req);
+
+ is_compound = smbd_smb2_is_compound(smb2req);
+ is_last_in_compound = smbd_smb2_is_last_in_compound(smb2req);
+
+ if (is_compound && !is_last_in_compound) {
+ /*
+ * Can't go async if we're not the
+ * last request in a compound request.
+ * Cause this request to complete synchronously.
+ */
+ smb2_request_set_async_internal(state->smb2req, true);
+ }
+
+ /* Ensure any close request knows about this outstanding IO. */
+ if (!aio_add_req_to_fsp(fsp, req)) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+
+}
+
+static void smbd_smb2_flush_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smbd_smb2_flush_state *state = tevent_req_data(
+ req, struct smbd_smb2_flush_state);
+ int ret;
+ struct vfs_aio_state vfs_aio_state;
+
+ ret = SMB_VFS_FSYNC_RECV(subreq, &vfs_aio_state);
+ TALLOC_FREE(subreq);
+ if (ret == -1) {
+ tevent_req_nterror(req, map_nt_error_from_unix(vfs_aio_state.error));
+ return;
+ }
+ if (state->fsp->fsp_flags.modified) {
+ trigger_write_time_update_immediate(state->fsp);
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS smbd_smb2_flush_recv(struct tevent_req *req)
+{
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/smb2_getinfo.c b/source3/smbd/smb2_getinfo.c
new file mode 100644
index 0000000..7c43a4e
--- /dev/null
+++ b/source3/smbd/smb2_getinfo.c
@@ -0,0 +1,694 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+ Copyright (C) Jeremy Allison 2010
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "trans2.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "librpc/gen_ndr/ndr_quota.h"
+#include "librpc/gen_ndr/ndr_security.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static struct tevent_req *smbd_smb2_getinfo_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *in_fsp,
+ uint8_t in_info_type,
+ uint8_t in_file_info_class,
+ uint32_t in_output_buffer_length,
+ DATA_BLOB in_input_buffer,
+ uint32_t in_additional_information,
+ uint32_t in_flags);
+static NTSTATUS smbd_smb2_getinfo_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out_output_buffer,
+ NTSTATUS *p_call_status);
+
+static void smbd_smb2_request_getinfo_done(struct tevent_req *subreq);
+NTSTATUS smbd_smb2_request_process_getinfo(struct smbd_smb2_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ NTSTATUS status;
+ const uint8_t *inbody;
+ uint8_t in_info_type;
+ uint8_t in_file_info_class;
+ uint32_t in_output_buffer_length;
+ uint16_t in_input_buffer_offset;
+ uint32_t in_input_buffer_length;
+ DATA_BLOB in_input_buffer;
+ uint32_t in_additional_information;
+ uint32_t in_flags;
+ uint64_t in_file_id_persistent;
+ uint64_t in_file_id_volatile;
+ struct files_struct *in_fsp;
+ struct tevent_req *subreq;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x29);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ in_info_type = CVAL(inbody, 0x02);
+ in_file_info_class = CVAL(inbody, 0x03);
+ in_output_buffer_length = IVAL(inbody, 0x04);
+ in_input_buffer_offset = SVAL(inbody, 0x08);
+ /* 0x0A 2 bytes reserved */
+ in_input_buffer_length = IVAL(inbody, 0x0C);
+ in_additional_information = IVAL(inbody, 0x10);
+ in_flags = IVAL(inbody, 0x14);
+ in_file_id_persistent = BVAL(inbody, 0x18);
+ in_file_id_volatile = BVAL(inbody, 0x20);
+
+ if (in_input_buffer_offset == 0 && in_input_buffer_length == 0) {
+ /* This is ok */
+ } else if (in_input_buffer_offset !=
+ (SMB2_HDR_BODY + SMBD_SMB2_IN_BODY_LEN(req))) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ if (in_input_buffer_length > SMBD_SMB2_IN_DYN_LEN(req)) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ in_input_buffer.data = SMBD_SMB2_IN_DYN_PTR(req);
+ in_input_buffer.length = in_input_buffer_length;
+
+ if (in_input_buffer.length > xconn->smb2.server.max_trans) {
+ DEBUG(2,("smbd_smb2_request_process_getinfo: "
+ "client ignored max trans: %s: 0x%08X: 0x%08X\n",
+ __location__, (unsigned)in_input_buffer.length,
+ (unsigned)xconn->smb2.server.max_trans));
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+ if (in_output_buffer_length > xconn->smb2.server.max_trans) {
+ DEBUG(2,("smbd_smb2_request_process_getinfo: "
+ "client ignored max trans: %s: 0x%08X: 0x%08X\n",
+ __location__, in_output_buffer_length,
+ xconn->smb2.server.max_trans));
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ status = smbd_smb2_request_verify_creditcharge(req,
+ MAX(in_input_buffer.length,in_output_buffer_length));
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ in_fsp = file_fsp_smb2(req, in_file_id_persistent, in_file_id_volatile);
+ if (in_fsp == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED);
+ }
+
+ subreq = smbd_smb2_getinfo_send(req, req->sconn->ev_ctx,
+ req, in_fsp,
+ in_info_type,
+ in_file_info_class,
+ in_output_buffer_length,
+ in_input_buffer,
+ in_additional_information,
+ in_flags);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_getinfo_done, req);
+
+ return smbd_smb2_request_pending_queue(req, subreq, 500);
+}
+
+static void smbd_smb2_request_getinfo_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *req = tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ DATA_BLOB outbody;
+ DATA_BLOB outdyn;
+ uint16_t out_output_buffer_offset;
+ DATA_BLOB out_output_buffer = data_blob_null;
+ NTSTATUS status;
+ NTSTATUS call_status = NT_STATUS_OK;
+ NTSTATUS error; /* transport error */
+
+ status = smbd_smb2_getinfo_recv(subreq,
+ req,
+ &out_output_buffer,
+ &call_status);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ error = smbd_smb2_request_error(req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ /* some GetInfo responses set STATUS_BUFFER_OVERFLOW and return partial,
+ but valid data */
+ if (!(NT_STATUS_IS_OK(call_status) ||
+ NT_STATUS_EQUAL(call_status, STATUS_BUFFER_OVERFLOW))) {
+ /* Return a specific error with data. */
+ error = smbd_smb2_request_error_ex(req,
+ call_status,
+ 0,
+ &out_output_buffer,
+ __location__);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ out_output_buffer_offset = SMB2_HDR_BODY + 0x08;
+
+ outbody = smbd_smb2_generate_outbody(req, 0x08);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x08 + 1); /* struct size */
+ SSVAL(outbody.data, 0x02,
+ out_output_buffer_offset); /* output buffer offset */
+ SIVAL(outbody.data, 0x04,
+ out_output_buffer.length); /* output buffer length */
+
+ outdyn = out_output_buffer;
+
+ error = smbd_smb2_request_done_ex(req, call_status, outbody, &outdyn, __location__);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+struct smbd_smb2_getinfo_state {
+ struct smbd_smb2_request *smb2req;
+ NTSTATUS status;
+ DATA_BLOB out_output_buffer;
+};
+
+static void smb2_ipc_getinfo(struct tevent_req *req,
+ struct smbd_smb2_getinfo_state *state,
+ struct tevent_context *ev,
+ uint8_t in_info_type,
+ uint8_t in_file_info_class)
+{
+ /* We want to reply to SMB2_GETINFO_FILE
+ with a class of SMB2_FILE_STANDARD_INFO as
+ otherwise a Win7 client issues this request
+ twice (2xroundtrips) if we return NOT_SUPPORTED.
+ NB. We do the same for SMB1 in call_trans2qpipeinfo() */
+
+ if (in_info_type == 0x01 && /* SMB2_GETINFO_FILE */
+ in_file_info_class == 0x05) { /* SMB2_FILE_STANDARD_INFO */
+ state->out_output_buffer = data_blob_talloc(state,
+ NULL, 24);
+ if (tevent_req_nomem(state->out_output_buffer.data, req)) {
+ return;
+ }
+
+ memset(state->out_output_buffer.data,0,24);
+ SOFF_T(state->out_output_buffer.data,0,4096LL);
+ SIVAL(state->out_output_buffer.data,16,1);
+ SIVAL(state->out_output_buffer.data,20,1);
+ tevent_req_done(req);
+ } else {
+ tevent_req_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ }
+}
+
+static struct tevent_req *smbd_smb2_getinfo_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *fsp,
+ uint8_t in_info_type,
+ uint8_t in_file_info_class,
+ uint32_t in_output_buffer_length,
+ DATA_BLOB in_input_buffer,
+ uint32_t in_additional_information,
+ uint32_t in_flags)
+{
+ struct tevent_req *req;
+ struct smbd_smb2_getinfo_state *state;
+ struct smb_request *smbreq;
+ connection_struct *conn = smb2req->tcon->compat;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_getinfo_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->smb2req = smb2req;
+ state->status = NT_STATUS_OK;
+ state->out_output_buffer = data_blob_null;
+
+ DEBUG(10,("smbd_smb2_getinfo_send: %s - %s\n",
+ fsp_str_dbg(fsp), fsp_fnum_dbg(fsp)));
+
+ smbreq = smbd_smb2_fake_smb_request(smb2req, fsp);
+ if (tevent_req_nomem(smbreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (IS_IPC(conn)) {
+ smb2_ipc_getinfo(req, state, ev,
+ in_info_type, in_file_info_class);
+ return tevent_req_post(req, ev);
+ }
+
+ switch (in_info_type) {
+ case SMB2_0_INFO_FILE:
+ {
+ uint16_t file_info_level;
+ char *data = NULL;
+ unsigned int data_size = 0;
+ bool delete_pending = false;
+ struct timespec write_time_ts;
+ struct file_id fileid;
+ size_t fixed_portion;
+
+ ZERO_STRUCT(write_time_ts);
+
+ /*
+ * MS-SMB2 3.3.5.20.1 "Handling SMB2_0_INFO_FILE"
+ *
+ * FileBasicInformation, FileAllInformation,
+ * FileNetworkOpenInformation, FileAttributeTagInformation
+ * require FILE_READ_ATTRIBUTES.
+ *
+ * FileFullEaInformation requires FILE_READ_EA.
+ */
+ switch (in_file_info_class) {
+ case FSCC_FILE_BASIC_INFORMATION:
+ case FSCC_FILE_ALL_INFORMATION:
+ case FSCC_FILE_NETWORK_OPEN_INFORMATION:
+ case FSCC_FILE_ATTRIBUTE_TAG_INFORMATION:
+ status = check_any_access_fsp(fsp, SEC_FILE_READ_ATTRIBUTE);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ break;
+
+ case FSCC_FILE_FULL_EA_INFORMATION:
+ status = check_any_access_fsp(fsp, SEC_FILE_READ_EA);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ break;
+ }
+
+ switch (in_file_info_class) {
+ case FSCC_FILE_FULL_EA_INFORMATION:
+ file_info_level = SMB2_FILE_FULL_EA_INFORMATION;
+ break;
+
+ case FSCC_FILE_ALL_INFORMATION:
+ file_info_level = SMB2_FILE_ALL_INFORMATION;
+ break;
+
+ case SMB2_FILE_POSIX_INFORMATION:
+ if (!(fsp->posix_flags & FSP_POSIX_FLAGS_OPEN)) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return tevent_req_post(req, ev);
+ }
+ file_info_level = SMB2_FILE_POSIX_INFORMATION_INTERNAL;
+ break;
+
+ default:
+ /* the levels directly map to the passthru levels */
+ file_info_level = in_file_info_class + 1000;
+ break;
+ }
+
+ switch (file_info_level) {
+ case SMB_FILE_NORMALIZED_NAME_INFORMATION:
+ if (smb2req->xconn->protocol < PROTOCOL_SMB3_11) {
+ tevent_req_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return tevent_req_post(req, ev);
+ }
+ break;
+ }
+
+ if (fsp->fake_file_handle) {
+ /*
+ * This is actually for the QUOTA_FAKE_FILE --metze
+ */
+
+ /* We know this name is ok, it's already passed the checks. */
+
+ } else if (fsp_get_pathref_fd(fsp) == -1) {
+ /*
+ * This is actually a QFILEINFO on a directory
+ * handle (returned from an NT SMB). NT5.0 seems
+ * to do this call. JRA.
+ */
+ int ret = vfs_stat(conn, fsp->fsp_name);
+ if (ret != 0) {
+ DBG_NOTICE("vfs_stat of %s failed (%s)\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ status = map_nt_error_from_unix(errno);
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ if (fsp_getinfo_ask_sharemode(fsp)) {
+ fileid = vfs_file_id_from_sbuf(
+ conn, &fsp->fsp_name->st);
+ get_file_infos(fileid, fsp->name_hash,
+ &delete_pending,
+ &write_time_ts);
+ }
+ } else {
+ /*
+ * Original code - this is an open file.
+ */
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("smbd_smb2_getinfo_send: "
+ "fstat of %s failed (%s)\n",
+ fsp_fnum_dbg(fsp), nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ if (fsp_getinfo_ask_sharemode(fsp)) {
+ fileid = vfs_file_id_from_sbuf(
+ conn, &fsp->fsp_name->st);
+ get_file_infos(fileid, fsp->name_hash,
+ &delete_pending,
+ &write_time_ts);
+ }
+ }
+
+ status = smbd_do_qfilepathinfo(conn, state,
+ smbreq,
+ file_info_level,
+ fsp,
+ fsp->fsp_name,
+ delete_pending,
+ write_time_ts,
+ NULL,
+ STR_UNICODE,
+ in_output_buffer_length,
+ &fixed_portion,
+ &data,
+ &data_size);
+ if (!NT_STATUS_IS_OK(status)) {
+ SAFE_FREE(data);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_LEVEL)) {
+ status = NT_STATUS_INVALID_INFO_CLASS;
+ }
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ if (in_output_buffer_length < fixed_portion) {
+ SAFE_FREE(data);
+ tevent_req_nterror(
+ req, NT_STATUS_INFO_LENGTH_MISMATCH);
+ return tevent_req_post(req, ev);
+ }
+ if (data_size > 0) {
+ state->out_output_buffer = data_blob_talloc(state,
+ data,
+ data_size);
+ SAFE_FREE(data);
+ if (tevent_req_nomem(state->out_output_buffer.data, req)) {
+ return tevent_req_post(req, ev);
+ }
+ if (data_size > in_output_buffer_length) {
+ state->out_output_buffer.length =
+ in_output_buffer_length;
+ status = STATUS_BUFFER_OVERFLOW;
+ }
+ }
+ SAFE_FREE(data);
+ break;
+ }
+
+ case SMB2_0_INFO_FILESYSTEM:
+ {
+ uint16_t file_info_level;
+ char *data = NULL;
+ int data_size = 0;
+ size_t fixed_portion;
+
+ /* the levels directly map to the passthru levels */
+ file_info_level = in_file_info_class + 1000;
+
+ status = smbd_do_qfsinfo(smb2req->xconn, conn, state,
+ file_info_level,
+ STR_UNICODE,
+ in_output_buffer_length,
+ &fixed_portion,
+ fsp,
+ fsp->fsp_name,
+ &data,
+ &data_size);
+ /* some responses set STATUS_BUFFER_OVERFLOW and return
+ partial, but valid data */
+ if (!(NT_STATUS_IS_OK(status) ||
+ NT_STATUS_EQUAL(status, STATUS_BUFFER_OVERFLOW))) {
+ SAFE_FREE(data);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_LEVEL)) {
+ status = NT_STATUS_INVALID_INFO_CLASS;
+ }
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ if (in_output_buffer_length < fixed_portion) {
+ SAFE_FREE(data);
+ tevent_req_nterror(
+ req, NT_STATUS_INFO_LENGTH_MISMATCH);
+ return tevent_req_post(req, ev);
+ }
+ if (data_size > 0) {
+ state->out_output_buffer = data_blob_talloc(state,
+ data,
+ data_size);
+ SAFE_FREE(data);
+ if (tevent_req_nomem(state->out_output_buffer.data, req)) {
+ return tevent_req_post(req, ev);
+ }
+ if (data_size > in_output_buffer_length) {
+ state->out_output_buffer.length =
+ in_output_buffer_length;
+ status = STATUS_BUFFER_OVERFLOW;
+ }
+ }
+ SAFE_FREE(data);
+ break;
+ }
+
+ case SMB2_0_INFO_SECURITY:
+ {
+ uint8_t *p_marshalled_sd = NULL;
+ size_t sd_size = 0;
+
+ status = smbd_do_query_security_desc(conn,
+ state,
+ fsp,
+ /* Security info wanted. */
+ in_additional_information &
+ SMB_SUPPORTED_SECINFO_FLAGS,
+ in_output_buffer_length,
+ &p_marshalled_sd,
+ &sd_size);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_BUFFER_TOO_SMALL)) {
+ /* Return needed size. */
+ state->out_output_buffer = data_blob_talloc(state,
+ NULL,
+ 4);
+ if (tevent_req_nomem(state->out_output_buffer.data, req)) {
+ return tevent_req_post(req, ev);
+ }
+ SIVAL(state->out_output_buffer.data,0,(uint32_t)sd_size);
+ state->status = NT_STATUS_BUFFER_TOO_SMALL;
+ break;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10,("smbd_smb2_getinfo_send: "
+ "smbd_do_query_security_desc of %s failed "
+ "(%s)\n", fsp_str_dbg(fsp),
+ nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ if (sd_size > 0) {
+ state->out_output_buffer = data_blob_talloc(state,
+ p_marshalled_sd,
+ sd_size);
+ if (tevent_req_nomem(state->out_output_buffer.data, req)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+ break;
+ }
+
+ case SMB2_0_INFO_QUOTA: {
+#ifdef HAVE_SYS_QUOTAS
+ struct smb2_query_quota_info info;
+ enum ndr_err_code err;
+ uint8_t *data = NULL;
+ uint32_t data_size = 0;
+ struct ndr_pull *ndr_pull = NULL;
+ DATA_BLOB sid_buf = data_blob_null;
+ TALLOC_CTX *tmp_ctx = talloc_init("geninfo_quota");
+ bool ok;
+
+ if (!tmp_ctx) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return tevent_req_post(req, ev);
+ }
+
+ ok = check_fsp_ntquota_handle(conn, smbreq, fsp);
+ if (!ok) {
+ DBG_INFO("no valid QUOTA HANDLE\n");
+ TALLOC_FREE(tmp_ctx);
+ tevent_req_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return tevent_req_post(req, ev);
+ }
+
+ ndr_pull = ndr_pull_init_blob(&in_input_buffer, tmp_ctx);
+ if (!ndr_pull) {
+ TALLOC_FREE(tmp_ctx);
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return tevent_req_post(req, ev);
+ }
+
+ err = ndr_pull_smb2_query_quota_info(ndr_pull,
+ NDR_SCALARS | NDR_BUFFERS,
+ &info);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+ DBG_DEBUG("failed to pull smb2_query_quota_info\n");
+ TALLOC_FREE(tmp_ctx);
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return tevent_req_post(req, ev);
+ }
+
+ DBG_DEBUG("quota list returnsingle %u, restartscan %u, "
+ "sid_list_length %u, start_sid_length %u, "
+ "startsidoffset %u\n",
+ (unsigned int)info.return_single,
+ (unsigned int)info.restart_scan,
+ (unsigned int)info.sid_list_length,
+ (unsigned int)info.start_sid_length,
+ (unsigned int)info.start_sid_offset);
+
+ /* Currently we do not support the single start sid format */
+ if (info.start_sid_length != 0 || info.start_sid_offset != 0 ) {
+ DBG_INFO("illegal single sid query\n");
+ TALLOC_FREE(tmp_ctx);
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ if (in_input_buffer.length < ndr_pull->offset) {
+ DBG_INFO("Invalid buffer length\n");
+ TALLOC_FREE(tmp_ctx);
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ sid_buf.data = in_input_buffer.data + ndr_pull->offset;
+ sid_buf.length = in_input_buffer.length - ndr_pull->offset;
+
+ status = smbd_do_query_getinfo_quota(tmp_ctx,
+ fsp,
+ info.restart_scan,
+ info.return_single,
+ info.sid_list_length,
+ &sid_buf,
+ in_output_buffer_length,
+ &data,
+ &data_size);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(tmp_ctx);
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ state->out_output_buffer =
+ data_blob_talloc(state, data, data_size);
+ status = NT_STATUS_OK;
+ TALLOC_FREE(tmp_ctx);
+ break;
+#else
+ tevent_req_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return tevent_req_post(req, ev);
+#endif
+ }
+
+ default:
+ DEBUG(10,("smbd_smb2_getinfo_send: "
+ "unknown in_info_type of %u "
+ " for file %s\n",
+ (unsigned int)in_info_type,
+ fsp_str_dbg(fsp) ));
+
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ state->status = status;
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static NTSTATUS smbd_smb2_getinfo_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out_output_buffer,
+ NTSTATUS *pstatus)
+{
+ NTSTATUS status;
+ struct smbd_smb2_getinfo_state *state = tevent_req_data(req,
+ struct smbd_smb2_getinfo_state);
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *out_output_buffer = state->out_output_buffer;
+ talloc_steal(mem_ctx, out_output_buffer->data);
+ *pstatus = state->status;
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/smb2_glue.c b/source3/smbd/smb2_glue.c
new file mode 100644
index 0000000..563aaf8
--- /dev/null
+++ b/source3/smbd/smb2_glue.c
@@ -0,0 +1,118 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+struct smb_request *smbd_smb2_fake_smb_request(struct smbd_smb2_request *req,
+ struct files_struct *fsp)
+{
+ struct smb_request *smbreq;
+ const uint8_t *inhdr = SMBD_SMB2_IN_HDR_PTR(req);
+
+ if (req->smb1req) {
+ smbreq = req->smb1req;
+ } else {
+ smbreq = talloc_zero(req, struct smb_request);
+ if (smbreq == NULL) {
+ return NULL;
+ }
+ }
+
+ smbreq->request_time = req->request_time;
+ if (req->session != NULL) {
+ smbreq->vuid = req->session->global->session_wire_id;
+ }
+ if (req->tcon != NULL) {
+ smbreq->tid = req->tcon->compat->cnum;
+ smbreq->conn = req->tcon->compat;
+ }
+ smbreq->sconn = req->sconn;
+ smbreq->xconn = req->xconn;
+ smbreq->session = req->session;
+ smbreq->smbpid = (uint16_t)IVAL(inhdr, SMB2_HDR_PID);
+ smbreq->flags2 = FLAGS2_UNICODE_STRINGS |
+ FLAGS2_32_BIT_ERROR_CODES |
+ FLAGS2_LONG_PATH_COMPONENTS |
+ FLAGS2_IS_LONG_NAME;
+
+ /* Only set FLAGS2_DFS_PATHNAMES if it's really a DFS share */
+ if (smbreq->conn != NULL &&
+ lp_host_msdfs() &&
+ lp_msdfs_root(SNUM(smbreq->conn))) {
+ if (IVAL(inhdr, SMB2_HDR_FLAGS) & SMB2_HDR_FLAG_DFS) {
+ smbreq->flags2 |= FLAGS2_DFS_PATHNAMES;
+ }
+ }
+ smbreq->mid = BVAL(inhdr, SMB2_HDR_MESSAGE_ID);
+ smbreq->chain_fsp = req->compat_chain_fsp;
+ if (fsp != NULL) {
+ smbreq->posix_pathnames =
+ (fsp->fsp_name->flags & SMB_FILENAME_POSIX_PATH);
+ }
+ smbreq->smb2req = req;
+ req->smb1req = smbreq;
+
+ return smbreq;
+}
+
+/*********************************************************
+ Are there unread bytes for recvfile ?
+*********************************************************/
+
+size_t smbd_smb2_unread_bytes(struct smbd_smb2_request *req)
+{
+ if (req->smb1req) {
+ return req->smb1req->unread_bytes;
+ }
+ return 0;
+}
+
+/*********************************************************
+ Called from file_free() to remove any chained fsp pointers.
+*********************************************************/
+
+void remove_smb2_chained_fsp(files_struct *fsp)
+{
+ struct smbd_server_connection *sconn = fsp->conn->sconn;
+ struct smbXsrv_connection *xconn = NULL;
+
+ if (sconn->client != NULL) {
+ xconn = sconn->client->connections;
+ }
+
+ for (; xconn != NULL; xconn = xconn->next) {
+ struct smbd_smb2_request *smb2req;
+
+ for (smb2req = xconn->smb2.requests; smb2req; smb2req = smb2req->next) {
+ if (smb2req->compat_chain_fsp == fsp) {
+ smb2req->compat_chain_fsp = NULL;
+ }
+ if (smb2req->smb1req && smb2req->smb1req->chain_fsp == fsp) {
+ smb2req->smb1req->chain_fsp = NULL;
+ }
+ }
+ }
+}
diff --git a/source3/smbd/smb2_ioctl.c b/source3/smbd/smb2_ioctl.c
new file mode 100644
index 0000000..7d0f11d
--- /dev/null
+++ b/source3/smbd/smb2_ioctl.c
@@ -0,0 +1,505 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "include/ntioctl.h"
+#include "smb2_ioctl_private.h"
+#include "librpc/gen_ndr/ioctl.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static struct tevent_req *smbd_smb2_ioctl_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *in_fsp,
+ uint32_t in_ctl_code,
+ DATA_BLOB in_input,
+ uint32_t in_max_output,
+ uint32_t in_flags);
+static NTSTATUS smbd_smb2_ioctl_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out_output,
+ uint8_t *body_padding,
+ bool *disconnect);
+
+static void smbd_smb2_request_ioctl_done(struct tevent_req *subreq);
+NTSTATUS smbd_smb2_request_process_ioctl(struct smbd_smb2_request *req)
+{
+ NTSTATUS status;
+ const uint8_t *inbody;
+ uint32_t min_buffer_offset;
+ uint32_t max_buffer_offset;
+ uint32_t min_output_offset;
+ uint32_t allowed_length_in;
+ uint32_t allowed_length_out;
+ uint32_t in_ctl_code;
+ uint64_t in_file_id_persistent;
+ uint64_t in_file_id_volatile;
+ struct files_struct *in_fsp = NULL;
+ uint32_t in_input_offset;
+ uint32_t in_input_length;
+ DATA_BLOB in_input_buffer = data_blob_null;
+ uint32_t in_max_input_length;
+ uint32_t in_output_offset;
+ uint32_t in_output_length;
+ DATA_BLOB in_output_buffer = data_blob_null;
+ uint32_t in_max_output_length;
+ uint32_t in_flags;
+ uint32_t data_length_in;
+ uint32_t data_length_out;
+ uint32_t data_length_tmp;
+ uint32_t data_length_max;
+ struct tevent_req *subreq;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x39);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ in_ctl_code = IVAL(inbody, 0x04);
+ in_file_id_persistent = BVAL(inbody, 0x08);
+ in_file_id_volatile = BVAL(inbody, 0x10);
+ in_input_offset = IVAL(inbody, 0x18);
+ in_input_length = IVAL(inbody, 0x1C);
+ in_max_input_length = IVAL(inbody, 0x20);
+ in_output_offset = IVAL(inbody, 0x24);
+ in_output_length = IVAL(inbody, 0x28);
+ in_max_output_length = IVAL(inbody, 0x2C);
+ in_flags = IVAL(inbody, 0x30);
+
+ min_buffer_offset = SMB2_HDR_BODY + SMBD_SMB2_IN_BODY_LEN(req);
+ max_buffer_offset = min_buffer_offset + SMBD_SMB2_IN_DYN_LEN(req);
+ min_output_offset = min_buffer_offset;
+
+ /*
+ * InputOffset (4 bytes): The offset, in bytes, from the beginning of
+ * the SMB2 header to the input data buffer. If no input data is
+ * required for the FSCTL/IOCTL command being issued, the client SHOULD
+ * set this value to 0.<49>
+ * <49> If no input data is required for the FSCTL/IOCTL command being
+ * issued, Windows-based clients set this field to any value.
+ */
+ allowed_length_in = 0;
+ if ((in_input_offset > 0) && (in_input_length > 0)) {
+ uint32_t tmp_ofs;
+
+ if (in_input_offset < min_buffer_offset) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+ if (in_input_offset > max_buffer_offset) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+ allowed_length_in = max_buffer_offset - in_input_offset;
+
+ tmp_ofs = in_input_offset - min_buffer_offset;
+ in_input_buffer.data = SMBD_SMB2_IN_DYN_PTR(req);
+ in_input_buffer.data += tmp_ofs;
+ in_input_buffer.length = in_input_length;
+ min_output_offset += tmp_ofs;
+ min_output_offset += in_input_length;
+ }
+
+ if (in_input_length > allowed_length_in) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ allowed_length_out = 0;
+ if (in_output_offset > 0) {
+ if (in_output_offset < min_buffer_offset) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+ if (in_output_offset > max_buffer_offset) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+ allowed_length_out = max_buffer_offset - in_output_offset;
+ }
+
+ if (in_output_length > allowed_length_out) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ if (in_output_length > 0) {
+ uint32_t tmp_ofs;
+
+ if (in_output_offset < min_output_offset) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+
+ tmp_ofs = in_output_offset - min_buffer_offset;
+ in_output_buffer.data = SMBD_SMB2_IN_DYN_PTR(req);
+ in_output_buffer.data += tmp_ofs;
+ in_output_buffer.length = in_output_length;
+ }
+
+ /*
+ * verify the credits and avoid overflows
+ * in_input_buffer.length and in_output_buffer.length
+ * are already verified.
+ */
+ data_length_in = in_input_buffer.length + in_output_buffer.length;
+
+ data_length_out = in_max_input_length;
+ data_length_tmp = UINT32_MAX - data_length_out;
+ if (data_length_tmp < in_max_output_length) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+ data_length_out += in_max_output_length;
+
+ data_length_max = MAX(data_length_in, data_length_out);
+
+ status = smbd_smb2_request_verify_creditcharge(req, data_length_max);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ /*
+ * If the Flags field of the request is not SMB2_0_IOCTL_IS_FSCTL the
+ * server MUST fail the request with STATUS_NOT_SUPPORTED.
+ */
+ if (in_flags != SMB2_IOCTL_FLAG_IS_FSCTL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NOT_SUPPORTED);
+ }
+
+ switch (in_ctl_code) {
+ case FSCTL_DFS_GET_REFERRALS:
+ case FSCTL_DFS_GET_REFERRALS_EX:
+ case FSCTL_PIPE_WAIT:
+ case FSCTL_VALIDATE_NEGOTIATE_INFO_224:
+ case FSCTL_VALIDATE_NEGOTIATE_INFO:
+ case FSCTL_QUERY_NETWORK_INTERFACE_INFO:
+ case FSCTL_SMBTORTURE_FORCE_UNACKED_TIMEOUT:
+ case FSCTL_SMBTORTURE_IOCTL_RESPONSE_BODY_PADDING8:
+ case FSCTL_SMBTORTURE_GLOBAL_READ_RESPONSE_BODY_PADDING8:
+ /*
+ * Some SMB2 specific CtlCodes like FSCTL_DFS_GET_REFERRALS or
+ * FSCTL_PIPE_WAIT does not take a file handle.
+ *
+ * If FileId in the SMB2 Header of the request is not
+ * 0xFFFFFFFFFFFFFFFF, then the server MUST fail the request
+ * with STATUS_INVALID_PARAMETER.
+ */
+ if (in_file_id_persistent != UINT64_MAX ||
+ in_file_id_volatile != UINT64_MAX) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+ break;
+ default:
+ in_fsp = file_fsp_smb2(req, in_file_id_persistent,
+ in_file_id_volatile);
+ if (in_fsp == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED);
+ }
+ break;
+ }
+
+ subreq = smbd_smb2_ioctl_send(req, req->sconn->ev_ctx,
+ req, in_fsp,
+ in_ctl_code,
+ in_input_buffer,
+ in_max_output_length,
+ in_flags);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+
+ /*
+ * If the FSCTL has gone async on a file handle, remember
+ * to add it to the list of async requests we need to wait
+ * for on file handle close.
+ */
+ if (in_fsp != NULL && tevent_req_is_in_progress(subreq)) {
+ bool ok;
+
+ ok = aio_add_req_to_fsp(in_fsp, subreq);
+ if (!ok) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ }
+
+ tevent_req_set_callback(subreq, smbd_smb2_request_ioctl_done, req);
+
+ return smbd_smb2_request_pending_queue(req, subreq, 1000);
+}
+
+/*
+ * 3.3.4.4 Sending an Error Response
+ * An error code other than one of the following indicates a failure:
+ */
+static bool smbd_smb2_ioctl_is_failure(uint32_t ctl_code, NTSTATUS status,
+ size_t data_size)
+{
+ if (NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ /*
+ * STATUS_BUFFER_OVERFLOW in a FSCTL_PIPE_TRANSCEIVE, FSCTL_PIPE_PEEK or
+ * FSCTL_DFS_GET_REFERRALS Response specified in section 2.2.32.<153>
+ */
+ if (NT_STATUS_EQUAL(status, STATUS_BUFFER_OVERFLOW)
+ && ((ctl_code == FSCTL_PIPE_TRANSCEIVE)
+ || (ctl_code == FSCTL_PIPE_PEEK)
+ || (ctl_code == FSCTL_DFS_GET_REFERRALS))) {
+ return false;
+ }
+
+ /*
+ * Any status other than STATUS_SUCCESS in a FSCTL_SRV_COPYCHUNK or
+ * FSCTL_SRV_COPYCHUNK_WRITE Response, when returning an
+ * SRV_COPYCHUNK_RESPONSE as described in section 2.2.32.1.
+ */
+ if (((ctl_code == FSCTL_SRV_COPYCHUNK)
+ || (ctl_code == FSCTL_SRV_COPYCHUNK_WRITE))
+ && (data_size == sizeof(struct srv_copychunk_rsp))) {
+ return false;
+ }
+
+ return true;
+}
+
+static void smbd_smb2_request_ioctl_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *req = tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ const uint8_t *inbody;
+ DATA_BLOB outbody;
+ DATA_BLOB outdyn;
+ uint32_t in_ctl_code;
+ uint64_t in_file_id_persistent;
+ uint64_t in_file_id_volatile;
+ uint32_t in_max_output_length;
+ uint32_t out_input_offset;
+ uint32_t out_output_offset;
+ DATA_BLOB out_output_buffer = data_blob_null;
+ NTSTATUS status;
+ NTSTATUS error; /* transport error */
+ bool disconnect = false;
+ uint16_t body_size;
+ uint8_t body_padding = 0;
+
+ status = smbd_smb2_ioctl_recv(subreq, req,
+ &out_output_buffer,
+ &body_padding,
+ &disconnect);
+
+ DEBUG(10,("smbd_smb2_request_ioctl_done: smbd_smb2_ioctl_recv returned "
+ "%u status %s\n",
+ (unsigned int)out_output_buffer.length,
+ nt_errstr(status) ));
+
+ TALLOC_FREE(subreq);
+ if (disconnect) {
+ error = status;
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ in_ctl_code = IVAL(inbody, 0x04);
+ in_file_id_persistent = BVAL(inbody, 0x08);
+ in_file_id_volatile = BVAL(inbody, 0x10);
+ in_max_output_length = IVAL(inbody, 0x2C);
+
+ if (out_output_buffer.length > in_max_output_length) {
+ /*
+ * Return NT_STATUS_BUFFER_TOO_SMALL by
+ * default if the provided buffer doesn't
+ * fit.
+ *
+ * If someone wants truncated data
+ * together with STATUS_BUFFER_OVERFLOW
+ * it needs to be done explicitly in
+ * the backends. We currently do that
+ * in:
+ * - fsctl_dfs_get_refers()
+ * - smbd_smb2_ioctl_pipe_read_done()
+ */
+ status = NT_STATUS_BUFFER_TOO_SMALL;
+ }
+
+ if (smbd_smb2_ioctl_is_failure(in_ctl_code, status,
+ out_output_buffer.length)) {
+ error = smbd_smb2_request_error(req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ /*
+ * Only FSCTL_SMBTORTURE_IOCTL_RESPONSE_BODY_PADDING8
+ * sets body_padding to a value different from 0.
+ */
+ body_size = 0x30 + body_padding;
+ out_input_offset = SMB2_HDR_BODY + body_size;
+ out_output_offset = SMB2_HDR_BODY + body_size;
+
+ outbody = smbd_smb2_generate_outbody(req, body_size);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x30 + 1); /* struct size */
+ SSVAL(outbody.data, 0x02, 0); /* reserved */
+ SIVAL(outbody.data, 0x04,
+ in_ctl_code); /* ctl code */
+ SBVAL(outbody.data, 0x08,
+ in_file_id_persistent); /* file id (persistent) */
+ SBVAL(outbody.data, 0x10,
+ in_file_id_volatile); /* file id (volatile) */
+ SIVAL(outbody.data, 0x18,
+ out_input_offset); /* input offset */
+ SIVAL(outbody.data, 0x1C, 0); /* input count */
+ SIVAL(outbody.data, 0x20,
+ out_output_offset); /* output offset */
+ SIVAL(outbody.data, 0x24,
+ out_output_buffer.length); /* output count */
+ SIVAL(outbody.data, 0x28, 0); /* flags */
+ SIVAL(outbody.data, 0x2C, 0); /* reserved */
+ if (body_padding != 0) {
+ memset(outbody.data + 0x30, 0, body_padding);
+ }
+
+ /*
+ * Note: Windows Vista and 2008 send back also the
+ * input from the request. But it was fixed in
+ * Windows 7.
+ */
+ outdyn = out_output_buffer;
+
+ error = smbd_smb2_request_done_ex(req, status, outbody, &outdyn,
+ __location__);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+static struct tevent_req *smbd_smb2_ioctl_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *fsp,
+ uint32_t in_ctl_code,
+ DATA_BLOB in_input,
+ uint32_t in_max_output,
+ uint32_t in_flags)
+{
+ struct tevent_req *req;
+ struct smbd_smb2_ioctl_state *state;
+ struct smb_request *smbreq;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_ioctl_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->smb2req = smb2req;
+ state->smbreq = NULL;
+ state->fsp = fsp;
+ state->in_input = in_input;
+ state->in_max_output = in_max_output;
+ state->out_output = data_blob_null;
+
+ DEBUG(10, ("smbd_smb2_ioctl: ctl_code[0x%08x] %s, %s\n",
+ (unsigned)in_ctl_code,
+ fsp ? fsp_str_dbg(fsp) : "<no handle>",
+ fsp_fnum_dbg(fsp)));
+
+ smbreq = smbd_smb2_fake_smb_request(smb2req, fsp);
+ if (tevent_req_nomem(smbreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->smbreq = smbreq;
+
+ switch (in_ctl_code & IOCTL_DEV_TYPE_MASK) {
+ case FSCTL_DFS:
+ return smb2_ioctl_dfs(in_ctl_code, ev, req, state);
+ case FSCTL_FILESYSTEM:
+ return smb2_ioctl_filesys(in_ctl_code, ev, req, state);
+ case FSCTL_NAMED_PIPE:
+ return smb2_ioctl_named_pipe(in_ctl_code, ev, req, state);
+ case FSCTL_NETWORK_FILESYSTEM:
+ return smb2_ioctl_network_fs(in_ctl_code, ev, req, state);
+ case FSCTL_SMBTORTURE:
+ return smb2_ioctl_smbtorture(in_ctl_code, ev, req, state);
+ default:
+ if (IS_IPC(smbreq->conn)) {
+ tevent_req_nterror(req, NT_STATUS_FS_DRIVER_REQUIRED);
+ } else {
+ tevent_req_nterror(req, NT_STATUS_INVALID_DEVICE_REQUEST);
+ }
+
+ return tevent_req_post(req, ev);
+ }
+}
+
+static NTSTATUS smbd_smb2_ioctl_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out_output,
+ uint8_t *body_padding,
+ bool *disconnect)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ struct smbd_smb2_ioctl_state *state = tevent_req_data(req,
+ struct smbd_smb2_ioctl_state);
+ enum tevent_req_state req_state;
+ uint64_t err;
+
+ *body_padding = state->body_padding;
+ *disconnect = state->disconnect;
+
+ if ((tevent_req_is_error(req, &req_state, &err) == false)
+ || (req_state == TEVENT_REQ_USER_ERROR)) {
+ /*
+ * Return output buffer to caller if the ioctl was successfully
+ * processed, even if a user error occurred. Some ioctls return
+ * data on failure.
+ */
+ *out_output = state->out_output;
+ talloc_steal(mem_ctx, out_output->data);
+ }
+
+ tevent_req_is_nterror(req, &status);
+ tevent_req_received(req);
+ return status;
+}
diff --git a/source3/smbd/smb2_ioctl_dfs.c b/source3/smbd/smb2_ioctl_dfs.c
new file mode 100644
index 0000000..72893ca
--- /dev/null
+++ b/source3/smbd/smb2_ioctl_dfs.c
@@ -0,0 +1,157 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "include/ntioctl.h"
+#include "smb2_ioctl_private.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static NTSTATUS fsctl_dfs_get_refers(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct connection_struct *conn,
+ DATA_BLOB *in_input,
+ uint32_t in_max_output,
+ DATA_BLOB *out_output)
+{
+ uint16_t in_max_referral_level;
+ DATA_BLOB in_file_name_buffer;
+ char *in_file_name_string;
+ size_t in_file_name_string_size;
+ bool ok;
+ bool overflow = false;
+ NTSTATUS status;
+ int dfs_size;
+ char *dfs_data = NULL;
+ DATA_BLOB output;
+
+ if (!lp_host_msdfs()) {
+ return NT_STATUS_FS_DRIVER_REQUIRED;
+ }
+
+ if (in_input->length < (2 + 2)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ in_max_referral_level = SVAL(in_input->data, 0);
+ in_file_name_buffer.data = in_input->data + 2;
+ in_file_name_buffer.length = in_input->length - 2;
+
+ ok = convert_string_talloc(mem_ctx, CH_UTF16, CH_UNIX,
+ in_file_name_buffer.data,
+ in_file_name_buffer.length,
+ &in_file_name_string,
+ &in_file_name_string_size);
+ if (!ok) {
+ return NT_STATUS_ILLEGAL_CHARACTER;
+ }
+
+ dfs_size = setup_dfs_referral(conn,
+ in_file_name_string,
+ in_max_referral_level,
+ &dfs_data, &status);
+ if (dfs_size < 0) {
+ return status;
+ }
+
+ if (dfs_size > in_max_output) {
+ /*
+ * TODO: we need a testsuite for this
+ */
+ overflow = true;
+ dfs_size = in_max_output;
+ }
+
+ output = data_blob_talloc(mem_ctx, (uint8_t *)dfs_data, dfs_size);
+ SAFE_FREE(dfs_data);
+ if ((dfs_size > 0) && (output.data == NULL)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ *out_output = output;
+
+ if (overflow) {
+ return STATUS_BUFFER_OVERFLOW;
+ }
+ return NT_STATUS_OK;
+}
+
+struct tevent_req *smb2_ioctl_dfs(uint32_t ctl_code,
+ struct tevent_context *ev,
+ struct tevent_req *req,
+ struct smbd_smb2_ioctl_state *state)
+{
+ NTSTATUS status;
+
+ switch (ctl_code) {
+ case FSCTL_DFS_GET_REFERRALS:
+ status = fsctl_dfs_get_refers(state, ev, state->smbreq->conn,
+ &state->in_input,
+ state->in_max_output,
+ &state->out_output);
+ if (!tevent_req_nterror(req, status)) {
+ tevent_req_done(req);
+ }
+ return tevent_req_post(req, ev);
+ break;
+ default: {
+ uint8_t *out_data = NULL;
+ uint32_t out_data_len = 0;
+
+ if (state->fsp == NULL) {
+ status = NT_STATUS_NOT_SUPPORTED;
+ } else {
+ status = SMB_VFS_FSCTL(state->fsp,
+ state,
+ ctl_code,
+ state->smbreq->flags2,
+ state->in_input.data,
+ state->in_input.length,
+ &out_data,
+ state->in_max_output,
+ &out_data_len);
+ state->out_output = data_blob_const(out_data, out_data_len);
+ if (NT_STATUS_IS_OK(status)) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
+ if (IS_IPC(state->smbreq->conn)) {
+ status = NT_STATUS_FS_DRIVER_REQUIRED;
+ } else {
+ status = NT_STATUS_INVALID_DEVICE_REQUEST;
+ }
+ }
+
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ break;
+ }
+ }
+
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return tevent_req_post(req, ev);
+}
diff --git a/source3/smbd/smb2_ioctl_filesys.c b/source3/smbd/smb2_ioctl_filesys.c
new file mode 100644
index 0000000..6cc53d4
--- /dev/null
+++ b/source3/smbd/smb2_ioctl_filesys.c
@@ -0,0 +1,830 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+ Copyright (C) David Disseldorp 2013-2015
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../libcli/security/security.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "rpc_server/srv_pipe_hnd.h"
+#include "include/ntioctl.h"
+#include "../librpc/ndr/libndr.h"
+#include "librpc/gen_ndr/ndr_ioctl.h"
+#include "smb2_ioctl_private.h"
+#include "lib/util/sys_rw.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+/*
+ * XXX this may reduce dup_extents->byte_count so that it's less than the
+ * target file size.
+ */
+static NTSTATUS fsctl_dup_extents_check_lengths(struct files_struct *src_fsp,
+ struct files_struct *dst_fsp,
+ struct fsctl_dup_extents_to_file *dup_extents)
+{
+ NTSTATUS status;
+
+ if ((dup_extents->source_off + dup_extents->byte_count
+ < dup_extents->source_off)
+ || (dup_extents->target_off + dup_extents->byte_count
+ < dup_extents->target_off)) {
+ return NT_STATUS_INVALID_PARAMETER; /* wrap */
+ }
+
+ status = vfs_stat_fsp(src_fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * XXX vfs_btrfs and vfs_default have size checks in the copychunk
+ * handler, as this needs to be rechecked after the src has potentially
+ * been extended by a previous chunk in the compound copychunk req.
+ */
+ if (src_fsp->fsp_name->st.st_ex_size
+ < dup_extents->source_off + dup_extents->byte_count) {
+ DEBUG(2, ("dup_extents req exceeds src size\n"));
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ status = vfs_stat_fsp(dst_fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (dst_fsp->fsp_name->st.st_ex_size
+ < dup_extents->target_off + dup_extents->byte_count) {
+
+ if (dst_fsp->fsp_name->st.st_ex_size - dup_extents->target_off
+ > dst_fsp->fsp_name->st.st_ex_size) {
+ return NT_STATUS_INVALID_PARAMETER; /* wrap */
+ }
+
+ /*
+ * this server behaviour is pretty hairy, but we need to match
+ * Windows, so...
+ */
+ DEBUG(2, ("dup_extents req exceeds target size, capping\n"));
+ dup_extents->byte_count = dst_fsp->fsp_name->st.st_ex_size
+ - dup_extents->target_off;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fsctl_dup_extents_check_overlap(struct files_struct *src_fsp,
+ struct files_struct *dst_fsp,
+ struct fsctl_dup_extents_to_file *dup_extents)
+{
+ if (!file_id_equal(&src_fsp->file_id, &dst_fsp->file_id)) {
+ /* src and dest refer to different files */
+ return NT_STATUS_OK;
+ }
+
+ if (sys_io_ranges_overlap(dup_extents->byte_count,
+ dup_extents->source_off,
+ dup_extents->byte_count,
+ dup_extents->target_off))
+ {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fsctl_dup_extents_check_sparse(struct files_struct *src_fsp,
+ struct files_struct *dst_fsp)
+{
+ /*
+ * 2.3.8 FSCTL_DUPLICATE_EXTENTS_TO_FILE Reply...
+ * STATUS_NOT_SUPPORTED: Target file is sparse, while source
+ * is a non-sparse file.
+ *
+ * WS2016 has the following behaviour (MS are in the process of fixing
+ * the spec):
+ * STATUS_NOT_SUPPORTED is returned if the source is sparse, while the
+ * target is non-sparse. However, if target is sparse while the source
+ * is non-sparse, then FSCTL_DUPLICATE_EXTENTS_TO_FILE completes
+ * successfully.
+ */
+ if (src_fsp->fsp_flags.is_sparse && !dst_fsp->fsp_flags.is_sparse) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ return NT_STATUS_OK;
+}
+
+struct fsctl_dup_extents_state {
+ struct tevent_context *ev;
+ struct connection_struct *conn;
+ struct files_struct *dst_fsp;
+ struct fsctl_dup_extents_to_file dup_extents;
+};
+
+static void fsctl_dup_extents_offload_read_done(struct tevent_req *subreq);
+static void fsctl_dup_extents_vfs_done(struct tevent_req *subreq);
+
+static struct tevent_req *fsctl_dup_extents_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *dst_fsp,
+ DATA_BLOB *in_input,
+ struct smbd_smb2_request *smb2req)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct fsctl_dup_extents_state *state = NULL;
+ uint64_t src_fid_persistent = 0;
+ uint64_t src_fid_volatile = 0;
+ struct files_struct *src_fsp = NULL;
+ int ndr_ret;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct fsctl_dup_extents_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (dst_fsp == NULL) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ *state = (struct fsctl_dup_extents_state) {
+ .conn = dst_fsp->conn,
+ .ev = ev,
+ .dst_fsp = dst_fsp,
+ };
+
+ if ((dst_fsp->conn->fs_capabilities
+ & FILE_SUPPORTS_BLOCK_REFCOUNTING) == 0) {
+ DBG_INFO("FS does not advertise block refcounting support\n");
+ tevent_req_nterror(req, NT_STATUS_INVALID_DEVICE_REQUEST);
+ return tevent_req_post(req, ev);
+ }
+
+ ndr_ret = ndr_pull_struct_blob(in_input, state, &state->dup_extents,
+ (ndr_pull_flags_fn_t)ndr_pull_fsctl_dup_extents_to_file);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ DBG_ERR("failed to unmarshall dup extents to file req\n");
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ src_fid_persistent = BVAL(state->dup_extents.source_fid, 0);
+ src_fid_volatile = BVAL(state->dup_extents.source_fid, 8);
+ src_fsp = file_fsp_get(smb2req, src_fid_persistent, src_fid_volatile);
+ if ((src_fsp == NULL)
+ || (src_fsp->file_id.devid != dst_fsp->file_id.devid)) {
+ /*
+ * [MS-FSCC] 2.3.8 FSCTL_DUPLICATE_EXTENTS_TO_FILE Reply
+ * STATUS_INVALID_PARAMETER:
+ * The FileHandle parameter is either invalid or does not
+ * represent a handle to an opened file on the same volume.
+ *
+ * Windows Server responds with NT_STATUS_INVALID_HANDLE instead
+ * of STATUS_INVALID_PARAMETER here, despite the above spec.
+ */
+ DBG_ERR("invalid src_fsp for dup_extents\n");
+ tevent_req_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return tevent_req_post(req, ev);
+ }
+
+ status = fsctl_dup_extents_check_lengths(src_fsp, dst_fsp,
+ &state->dup_extents);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (state->dup_extents.byte_count == 0) {
+ DBG_ERR("skipping zero length dup extents\n");
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ status = fsctl_dup_extents_check_overlap(src_fsp, dst_fsp,
+ &state->dup_extents);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ status = fsctl_dup_extents_check_sparse(src_fsp, dst_fsp);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = SMB_VFS_OFFLOAD_READ_SEND(state, ev, src_fsp,
+ FSCTL_DUP_EXTENTS_TO_FILE,
+ 0, 0, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, fsctl_dup_extents_offload_read_done,
+ req);
+ return req;
+}
+
+static void fsctl_dup_extents_offload_read_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fsctl_dup_extents_state *state = tevent_req_data(
+ req, struct fsctl_dup_extents_state);
+ uint32_t flags;
+ uint64_t xferlen;
+ DATA_BLOB token;
+ NTSTATUS status;
+
+ /*
+ * Note that both flags and xferlen are not used with copy-chunk.
+ */
+
+ status = SMB_VFS_OFFLOAD_READ_RECV(subreq, state->dst_fsp->conn,
+ state, &flags, &xferlen, &token);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ /* tell the VFS to ignore locks across the clone, matching ReFS */
+ subreq = SMB_VFS_OFFLOAD_WRITE_SEND(state->dst_fsp->conn,
+ state,
+ state->ev,
+ FSCTL_DUP_EXTENTS_TO_FILE,
+ &token,
+ state->dup_extents.source_off,
+ state->dst_fsp,
+ state->dup_extents.target_off,
+ state->dup_extents.byte_count);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, fsctl_dup_extents_vfs_done, req);
+ return;
+}
+
+static void fsctl_dup_extents_vfs_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fsctl_dup_extents_state *state = tevent_req_data(
+ req, struct fsctl_dup_extents_state);
+ off_t nb_chunk;
+ NTSTATUS status;
+
+ status = SMB_VFS_OFFLOAD_WRITE_RECV(state->conn, subreq, &nb_chunk);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (nb_chunk != state->dup_extents.byte_count) {
+ tevent_req_nterror(req, NT_STATUS_IO_DEVICE_ERROR);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS fsctl_dup_extents_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static NTSTATUS fsctl_get_cmprn(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ size_t in_max_output,
+ DATA_BLOB *out_output)
+{
+ struct compression_state cmpr_state;
+ enum ndr_err_code ndr_ret;
+ DATA_BLOB output;
+ NTSTATUS status;
+
+ if (fsp == NULL) {
+ return NT_STATUS_FILE_CLOSED;
+ }
+
+ /* Windows doesn't check for SEC_FILE_READ_ATTRIBUTE permission here */
+
+ ZERO_STRUCT(cmpr_state);
+ if (fsp->conn->fs_capabilities & FILE_FILE_COMPRESSION) {
+ status = SMB_VFS_FGET_COMPRESSION(fsp->conn,
+ mem_ctx,
+ fsp,
+ &cmpr_state.format);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ } else {
+ /*
+ * bso#12144: The underlying filesystem doesn't support
+ * compression, so we should respond with "not-compressed"
+ * (like WS2016 ReFS) instead of STATUS_NOT_SUPPORTED or
+ * NT_STATUS_INVALID_DEVICE_REQUEST.
+ */
+ cmpr_state.format = COMPRESSION_FORMAT_NONE;
+ }
+
+ ndr_ret = ndr_push_struct_blob(&output, mem_ctx,
+ &cmpr_state,
+ (ndr_push_flags_fn_t)ndr_push_compression_state);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (in_max_output < output.length) {
+ DEBUG(1, ("max output %u too small for compression state %ld\n",
+ (unsigned int)in_max_output, (long int)output.length));
+ return NT_STATUS_INVALID_USER_BUFFER;
+ }
+ *out_output = output;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fsctl_set_cmprn(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ DATA_BLOB *in_input)
+{
+ struct compression_state cmpr_state;
+ enum ndr_err_code ndr_ret;
+ NTSTATUS status;
+
+ if (fsp == NULL) {
+ return NT_STATUS_FILE_CLOSED;
+ }
+
+ /* WRITE_DATA permission is required, WRITE_ATTRIBUTES is not */
+ status = check_any_access_fsp(fsp, FILE_WRITE_DATA);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ ndr_ret = ndr_pull_struct_blob(in_input, mem_ctx, &cmpr_state,
+ (ndr_pull_flags_fn_t)ndr_pull_compression_state);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ DEBUG(0, ("failed to unmarshall set compression req\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = NT_STATUS_NOT_SUPPORTED;
+ if (fsp->conn->fs_capabilities & FILE_FILE_COMPRESSION) {
+ status = SMB_VFS_SET_COMPRESSION(fsp->conn,
+ mem_ctx,
+ fsp,
+ cmpr_state.format);
+ } else if (cmpr_state.format == COMPRESSION_FORMAT_NONE) {
+ /*
+ * bso#12144: The underlying filesystem doesn't support
+ * compression. We should still accept set(FORMAT_NONE) requests
+ * (like WS2016 ReFS).
+ */
+ status = NT_STATUS_OK;
+ }
+
+ return status;
+}
+
+static NTSTATUS fsctl_zero_data(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ DATA_BLOB *in_input)
+{
+ struct file_zero_data_info zdata_info;
+ enum ndr_err_code ndr_ret;
+ struct lock_struct lck;
+ int mode;
+ uint64_t len;
+ int ret;
+ NTSTATUS status;
+
+ if (fsp == NULL) {
+ return NT_STATUS_FILE_CLOSED;
+ }
+
+ /* WRITE_DATA permission is required */
+ status = check_any_access_fsp(fsp, FILE_WRITE_DATA);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* allow regardless of whether FS supports sparse or not */
+
+ ndr_ret = ndr_pull_struct_blob(in_input, mem_ctx, &zdata_info,
+ (ndr_pull_flags_fn_t)ndr_pull_file_zero_data_info);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ DEBUG(0, ("failed to unmarshall zero data request\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (zdata_info.beyond_final_zero < zdata_info.file_off) {
+ DEBUG(0, ("invalid zero data params: off %lu, bfz, %lu\n",
+ (unsigned long)zdata_info.file_off,
+ (unsigned long)zdata_info.beyond_final_zero));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* convert strange "beyond final zero" param into length */
+ len = zdata_info.beyond_final_zero - zdata_info.file_off;
+
+ if (len == 0) {
+ DEBUG(2, ("zero data called with zero length range\n"));
+ return NT_STATUS_OK;
+ }
+
+ init_strict_lock_struct(fsp,
+ fsp->op->global->open_persistent_id,
+ zdata_info.file_off,
+ len,
+ WRITE_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &lck);
+
+ if (!SMB_VFS_STRICT_LOCK_CHECK(fsp->conn, fsp, &lck)) {
+ DEBUG(2, ("failed to lock range for zero-data\n"));
+ return NT_STATUS_FILE_LOCK_CONFLICT;
+ }
+
+ /*
+ * MS-FSCC <58> Section 2.3.67
+ * This FSCTL sets the range of bytes to zero (0) without extending the
+ * file size.
+ *
+ * The VFS_FALLOCATE_FL_KEEP_SIZE flag is used to satisfy this
+ * constraint.
+ */
+
+ mode = VFS_FALLOCATE_FL_PUNCH_HOLE | VFS_FALLOCATE_FL_KEEP_SIZE;
+ ret = SMB_VFS_FALLOCATE(fsp, mode, zdata_info.file_off, len);
+ if (ret == -1) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(2, ("zero-data fallocate(0x%x) failed: %s\n", mode,
+ strerror(errno)));
+ return status;
+ }
+
+ if (!fsp->fsp_flags.is_sparse && lp_strict_allocate(SNUM(fsp->conn))) {
+ /*
+ * File marked non-sparse and "strict allocate" is enabled -
+ * allocate the range that we just punched out.
+ * In future FALLOC_FL_ZERO_RANGE could be used exclusively for
+ * this, but it's currently only supported on XFS and ext4.
+ *
+ * The newly allocated range still won't be found by SEEK_DATA
+ * for QAR, but stat.st_blocks will reflect it.
+ */
+ ret = SMB_VFS_FALLOCATE(fsp, VFS_FALLOCATE_FL_KEEP_SIZE,
+ zdata_info.file_off, len);
+ if (ret == -1) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0, ("fallocate failed: %s\n", strerror(errno)));
+ return status;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fsctl_qar_buf_push(TALLOC_CTX *mem_ctx,
+ struct file_alloced_range_buf *qar_buf,
+ DATA_BLOB *qar_array_blob)
+{
+ DATA_BLOB new_slot;
+ enum ndr_err_code ndr_ret;
+ bool ok;
+
+ ndr_ret = ndr_push_struct_blob(&new_slot, mem_ctx, qar_buf,
+ (ndr_push_flags_fn_t)ndr_push_file_alloced_range_buf);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ DEBUG(0, ("failed to marshall QAR buf\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* TODO should be able to avoid copy by pushing into prealloced buf */
+ ok = data_blob_append(mem_ctx, qar_array_blob, new_slot.data,
+ new_slot.length);
+ data_blob_free(&new_slot);
+ if (!ok) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fsctl_qar_seek_fill(TALLOC_CTX *mem_ctx,
+ struct files_struct *fsp,
+ off_t curr_off,
+ off_t max_off,
+ DATA_BLOB *qar_array_blob)
+{
+ NTSTATUS status = NT_STATUS_NOT_SUPPORTED;
+
+#ifdef HAVE_LSEEK_HOLE_DATA
+ while (curr_off <= max_off) {
+ off_t data_off;
+ off_t hole_off;
+ struct file_alloced_range_buf qar_buf;
+
+ /* seek next data */
+ data_off = SMB_VFS_LSEEK(fsp, curr_off, SEEK_DATA);
+ if ((data_off == -1) && (errno == ENXIO)) {
+ /* no data from curr_off to EOF */
+ break;
+ } else if (data_off == -1) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(1, ("lseek data failed: %s\n", strerror(errno)));
+ return status;
+ }
+
+ if (data_off > max_off) {
+ /* found something, but passed range of interest */
+ break;
+ }
+
+ hole_off = SMB_VFS_LSEEK(fsp, data_off, SEEK_HOLE);
+ if (hole_off == -1) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(1, ("lseek hole failed: %s\n", strerror(errno)));
+ return status;
+ }
+
+ if (hole_off <= data_off) {
+ DEBUG(1, ("lseek inconsistent: hole %lu at or before "
+ "data %lu\n", (unsigned long)hole_off,
+ (unsigned long)data_off));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ qar_buf.file_off = data_off;
+ /* + 1 to convert maximum offset to length */
+ qar_buf.len = MIN(hole_off, max_off + 1) - data_off;
+
+ status = fsctl_qar_buf_push(mem_ctx, &qar_buf, qar_array_blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ curr_off = hole_off;
+ }
+ status = NT_STATUS_OK;
+#endif
+
+ return status;
+}
+
+static NTSTATUS fsctl_qar(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ DATA_BLOB *in_input,
+ size_t in_max_output,
+ DATA_BLOB *out_output)
+{
+ struct fsctl_query_alloced_ranges_req qar_req;
+ struct fsctl_query_alloced_ranges_rsp qar_rsp;
+ DATA_BLOB qar_array_blob = data_blob_null;
+ uint64_t max_off;
+ enum ndr_err_code ndr_ret;
+ int ret;
+ NTSTATUS status;
+ SMB_STRUCT_STAT sbuf;
+
+ if (fsp == NULL) {
+ return NT_STATUS_FILE_CLOSED;
+ }
+
+ /* READ_DATA permission is required */
+ status = check_any_access_fsp(fsp, FILE_READ_DATA);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ ndr_ret = ndr_pull_struct_blob(in_input, mem_ctx, &qar_req,
+ (ndr_pull_flags_fn_t)ndr_pull_fsctl_query_alloced_ranges_req);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ DEBUG(0, ("failed to unmarshall QAR req\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * XXX Windows Server 2008 & 2012 servers don't return lock-conflict
+ * for QAR requests over an exclusively locked range!
+ */
+
+ ret = SMB_VFS_FSTAT(fsp, &sbuf);
+ if (ret == -1) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(2, ("fstat failed: %s\n", strerror(errno)));
+ return status;
+ }
+
+ if ((qar_req.buf.len == 0)
+ || (sbuf.st_ex_size == 0)
+ || (qar_req.buf.file_off >= sbuf.st_ex_size)) {
+ /* zero length range or after EOF, no ranges to return */
+ return NT_STATUS_OK;
+ }
+
+ /* check for integer overflow */
+ if (qar_req.buf.file_off + qar_req.buf.len < qar_req.buf.file_off) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * Maximum offset is either the last valid offset _before_ EOF, or the
+ * last byte offset within the requested range. -1 converts length to
+ * offset, which is easier to work with for SEEK_DATA/SEEK_HOLE, E.g.:
+ *
+ * /off=0 /off=512K /st_ex_size=1M
+ * |-------------------------------------|
+ * | File data |
+ * |-------------------------------------|
+ * QAR end\
+ * |=====================================|
+ * | QAR off=512K, len=1M |
+ * |=================^===================|
+ * max_off=1M - 1
+ * QAR end\
+ * |==================|
+ * |QAR off=0 len=512K|
+ * |==================|
+ * ^
+ * max_off=512K - 1
+ */
+ max_off = MIN(sbuf.st_ex_size,
+ qar_req.buf.file_off + qar_req.buf.len) - 1;
+
+ if (!fsp->fsp_flags.is_sparse) {
+ struct file_alloced_range_buf qar_buf;
+
+ /* file is non-sparse, claim file_off->max_off is allocated */
+ qar_buf.file_off = qar_req.buf.file_off;
+ /* + 1 to convert maximum offset back to length */
+ qar_buf.len = max_off - qar_req.buf.file_off + 1;
+
+ status = fsctl_qar_buf_push(mem_ctx, &qar_buf, &qar_array_blob);
+ } else {
+ status = fsctl_qar_seek_fill(mem_ctx, fsp, qar_req.buf.file_off,
+ max_off, &qar_array_blob);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* marshall response buffer. */
+ qar_rsp.far_buf_array = qar_array_blob;
+
+ ndr_ret = ndr_push_struct_blob(out_output, mem_ctx, &qar_rsp,
+ (ndr_push_flags_fn_t)ndr_push_fsctl_query_alloced_ranges_rsp);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ DEBUG(0, ("failed to marshall QAR rsp\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (out_output->length > in_max_output) {
+ DEBUG(2, ("QAR output len %lu exceeds max %lu\n",
+ (unsigned long)out_output->length,
+ (unsigned long)in_max_output));
+ data_blob_free(out_output);
+ return NT_STATUS_BUFFER_TOO_SMALL;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static void smb2_ioctl_filesys_dup_extents_done(struct tevent_req *subreq);
+
+struct tevent_req *smb2_ioctl_filesys(uint32_t ctl_code,
+ struct tevent_context *ev,
+ struct tevent_req *req,
+ struct smbd_smb2_ioctl_state *state)
+{
+ NTSTATUS status;
+
+ switch (ctl_code) {
+ case FSCTL_GET_COMPRESSION:
+ status = fsctl_get_cmprn(state, ev, state->fsp,
+ state->in_max_output,
+ &state->out_output);
+ if (!tevent_req_nterror(req, status)) {
+ tevent_req_done(req);
+ }
+ return tevent_req_post(req, ev);
+ break;
+ case FSCTL_SET_COMPRESSION:
+ status = fsctl_set_cmprn(state, ev, state->fsp,
+ &state->in_input);
+ if (!tevent_req_nterror(req, status)) {
+ tevent_req_done(req);
+ }
+ return tevent_req_post(req, ev);
+ break;
+ case FSCTL_SET_ZERO_DATA:
+ status = fsctl_zero_data(state, ev, state->fsp,
+ &state->in_input);
+ if (!tevent_req_nterror(req, status)) {
+ tevent_req_done(req);
+ }
+ return tevent_req_post(req, ev);
+ break;
+ case FSCTL_QUERY_ALLOCATED_RANGES:
+ status = fsctl_qar(state, ev, state->fsp,
+ &state->in_input,
+ state->in_max_output,
+ &state->out_output);
+ if (!tevent_req_nterror(req, status)) {
+ tevent_req_done(req);
+ }
+ return tevent_req_post(req, ev);
+ break;
+ case FSCTL_DUP_EXTENTS_TO_FILE: {
+ struct tevent_req *subreq = NULL;
+
+ subreq = fsctl_dup_extents_send(state, ev,
+ state->fsp,
+ &state->in_input,
+ state->smb2req);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ smb2_ioctl_filesys_dup_extents_done,
+ req);
+ return req;
+ break;
+ }
+ default: {
+ uint8_t *out_data = NULL;
+ uint32_t out_data_len = 0;
+
+ if (state->fsp == NULL) {
+ status = NT_STATUS_NOT_SUPPORTED;
+ } else {
+ status = SMB_VFS_FSCTL(state->fsp,
+ state,
+ ctl_code,
+ state->smbreq->flags2,
+ state->in_input.data,
+ state->in_input.length,
+ &out_data,
+ state->in_max_output,
+ &out_data_len);
+ state->out_output = data_blob_const(out_data, out_data_len);
+ if (NT_STATUS_IS_OK(status)) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
+ if (IS_IPC(state->smbreq->conn)) {
+ status = NT_STATUS_FS_DRIVER_REQUIRED;
+ } else {
+ status = NT_STATUS_INVALID_DEVICE_REQUEST;
+ }
+ }
+
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ break;
+ }
+ }
+
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return tevent_req_post(req, ev);
+}
+
+static void smb2_ioctl_filesys_dup_extents_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ NTSTATUS status;
+
+ status = fsctl_dup_extents_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!tevent_req_nterror(req, status)) {
+ tevent_req_done(req);
+ }
+}
diff --git a/source3/smbd/smb2_ioctl_named_pipe.c b/source3/smbd/smb2_ioctl_named_pipe.c
new file mode 100644
index 0000000..f9e3dec
--- /dev/null
+++ b/source3/smbd/smb2_ioctl_named_pipe.c
@@ -0,0 +1,188 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "rpc_server/srv_pipe_hnd.h"
+#include "include/ntioctl.h"
+#include "smb2_ioctl_private.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static void smbd_smb2_ioctl_pipe_write_done(struct tevent_req *subreq);
+static void smbd_smb2_ioctl_pipe_read_done(struct tevent_req *subreq);
+
+struct tevent_req *smb2_ioctl_named_pipe(uint32_t ctl_code,
+ struct tevent_context *ev,
+ struct tevent_req *req,
+ struct smbd_smb2_ioctl_state *state)
+{
+ NTSTATUS status;
+ uint8_t *out_data = NULL;
+ uint32_t out_data_len = 0;
+
+ if (ctl_code == FSCTL_PIPE_TRANSCEIVE) {
+ struct tevent_req *subreq;
+
+ if (!IS_IPC(state->smbreq->conn)) {
+ tevent_req_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return tevent_req_post(req, ev);
+ }
+
+ if (state->fsp == NULL) {
+ tevent_req_nterror(req, NT_STATUS_FILE_CLOSED);
+ return tevent_req_post(req, ev);
+ }
+
+ if (!fsp_is_np(state->fsp)) {
+ tevent_req_nterror(req, NT_STATUS_FILE_CLOSED);
+ return tevent_req_post(req, ev);
+ }
+
+ DEBUG(10,("smbd_smb2_ioctl_send: np_write_send of size %u\n",
+ (unsigned int)state->in_input.length ));
+
+ subreq = np_write_send(state, ev,
+ state->fsp->fake_file_handle,
+ state->in_input.data,
+ state->in_input.length);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ smbd_smb2_ioctl_pipe_write_done,
+ req);
+ return req;
+ }
+
+ if (state->fsp == NULL) {
+ status = NT_STATUS_NOT_SUPPORTED;
+ } else {
+ status = SMB_VFS_FSCTL(state->fsp,
+ state,
+ ctl_code,
+ state->smbreq->flags2,
+ state->in_input.data,
+ state->in_input.length,
+ &out_data,
+ state->in_max_output,
+ &out_data_len);
+ state->out_output = data_blob_const(out_data, out_data_len);
+ if (NT_STATUS_IS_OK(status)) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
+ if (IS_IPC(state->smbreq->conn)) {
+ status = NT_STATUS_FS_DRIVER_REQUIRED;
+ } else {
+ status = NT_STATUS_INVALID_DEVICE_REQUEST;
+ }
+ }
+
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+}
+
+static void smbd_smb2_ioctl_pipe_write_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smbd_smb2_ioctl_state *state = tevent_req_data(req,
+ struct smbd_smb2_ioctl_state);
+ NTSTATUS status;
+ ssize_t nwritten = -1;
+
+ status = np_write_recv(subreq, &nwritten);
+
+ DEBUG(10,("smbd_smb2_ioctl_pipe_write_done: received %ld\n",
+ (long int)nwritten ));
+
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ if (nwritten != state->in_input.length) {
+ tevent_req_nterror(req, NT_STATUS_PIPE_NOT_AVAILABLE);
+ return;
+ }
+
+ state->out_output = data_blob_talloc(state, NULL, state->in_max_output);
+ if (state->in_max_output > 0 &&
+ tevent_req_nomem(state->out_output.data, req)) {
+ return;
+ }
+
+ DEBUG(10,("smbd_smb2_ioctl_pipe_write_done: issuing np_read_send "
+ "of size %u\n",
+ (unsigned int)state->out_output.length ));
+
+ subreq = np_read_send(state->smbreq->conn,
+ state->smb2req->sconn->ev_ctx,
+ state->fsp->fake_file_handle,
+ state->out_output.data,
+ state->out_output.length);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_ioctl_pipe_read_done, req);
+}
+
+static void smbd_smb2_ioctl_pipe_read_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smbd_smb2_ioctl_state *state = tevent_req_data(req,
+ struct smbd_smb2_ioctl_state);
+ NTSTATUS status;
+ ssize_t nread = -1;
+ bool is_data_outstanding = false;
+
+ status = np_read_recv(subreq, &nread, &is_data_outstanding);
+
+ DEBUG(10,("smbd_smb2_ioctl_pipe_read_done: np_read_recv nread = %d "
+ "is_data_outstanding = %d, status = %s\n",
+ (int)nread,
+ (int)is_data_outstanding,
+ nt_errstr(status) ));
+
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ state->out_output.length = nread;
+
+ if (is_data_outstanding) {
+ tevent_req_nterror(req, STATUS_BUFFER_OVERFLOW);
+ return;
+ }
+
+ tevent_req_done(req);
+}
diff --git a/source3/smbd/smb2_ioctl_network_fs.c b/source3/smbd/smb2_ioctl_network_fs.c
new file mode 100644
index 0000000..bcfa37f
--- /dev/null
+++ b/source3/smbd/smb2_ioctl_network_fs.c
@@ -0,0 +1,839 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+ Copyright (C) David Disseldorp 2012
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../libcli/security/security.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "include/ntioctl.h"
+#include "../librpc/ndr/libndr.h"
+#include "librpc/gen_ndr/ndr_ioctl.h"
+#include "smb2_ioctl_private.h"
+#include "../lib/tsocket/tsocket.h"
+#include "lib/messages_ctdb.h"
+#include "ctdbd_conn.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static void copychunk_pack_limits(struct srv_copychunk_rsp *cc_rsp)
+{
+ cc_rsp->chunks_written = COPYCHUNK_MAX_CHUNKS;
+ cc_rsp->chunk_bytes_written = COPYCHUNK_MAX_CHUNK_LEN;
+ cc_rsp->total_bytes_written = COPYCHUNK_MAX_TOTAL_LEN;
+}
+
+static NTSTATUS copychunk_check_limits(struct srv_copychunk_copy *cc_copy)
+{
+ uint32_t i;
+ uint32_t total_len = 0;
+
+ /*
+ * [MS-SMB2] 3.3.5.15.6 Handling a Server-Side Data Copy Request
+ * Send and invalid parameter response if:
+ * - The ChunkCount value is greater than
+ * ServerSideCopyMaxNumberofChunks
+ */
+ if (cc_copy->chunk_count > COPYCHUNK_MAX_CHUNKS) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ for (i = 0; i < cc_copy->chunk_count; i++) {
+ /*
+ * - The Length value in a single chunk is greater than
+ * ServerSideCopyMaxChunkSize or equal to zero.
+ */
+ if ((cc_copy->chunks[i].length == 0)
+ || (cc_copy->chunks[i].length > COPYCHUNK_MAX_CHUNK_LEN)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ total_len += cc_copy->chunks[i].length;
+ }
+ /*
+ * - Sum of Lengths in all chunks is greater than
+ * ServerSideCopyMaxDataSize
+ */
+ if (total_len > COPYCHUNK_MAX_TOTAL_LEN) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ return NT_STATUS_OK;
+}
+
+struct fsctl_srv_copychunk_state {
+ struct tevent_context *ev;
+ struct connection_struct *conn;
+ struct srv_copychunk_copy cc_copy;
+ uint32_t current_chunk;
+ NTSTATUS status;
+ off_t total_written;
+ uint32_t ctl_code;
+ DATA_BLOB token;
+ struct files_struct *src_fsp;
+ struct files_struct *dst_fsp;
+ enum {
+ COPYCHUNK_OUT_EMPTY = 0,
+ COPYCHUNK_OUT_LIMITS,
+ COPYCHUNK_OUT_RSP,
+ } out_data;
+};
+static void fsctl_srv_copychunk_vfs_done(struct tevent_req *subreq);
+
+static NTSTATUS fsctl_srv_copychunk_loop(struct tevent_req *req);
+
+static struct tevent_req *fsctl_srv_copychunk_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ uint32_t ctl_code,
+ struct files_struct *dst_fsp,
+ DATA_BLOB *in_input,
+ size_t in_max_output,
+ struct smbd_smb2_request *smb2req)
+{
+ struct tevent_req *req = NULL;
+ struct fsctl_srv_copychunk_state *state = NULL;
+ enum ndr_err_code ndr_ret;
+ NTSTATUS status;
+
+ /* handler for both copy-chunk variants */
+ SMB_ASSERT((ctl_code == FSCTL_SRV_COPYCHUNK)
+ || (ctl_code == FSCTL_SRV_COPYCHUNK_WRITE));
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct fsctl_srv_copychunk_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ *state = (struct fsctl_srv_copychunk_state) {
+ .conn = dst_fsp->conn,
+ .ev = ev,
+ .ctl_code = ctl_code,
+ .dst_fsp = dst_fsp,
+ };
+
+ if (in_max_output < sizeof(struct srv_copychunk_rsp)) {
+ DEBUG(3, ("max output %d not large enough to hold copy chunk "
+ "response %lu\n", (int)in_max_output,
+ (unsigned long)sizeof(struct srv_copychunk_rsp)));
+ state->status = NT_STATUS_INVALID_PARAMETER;
+ tevent_req_nterror(req, state->status);
+ return tevent_req_post(req, ev);
+ }
+
+ ndr_ret = ndr_pull_struct_blob(in_input, mem_ctx, &state->cc_copy,
+ (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_copy);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ DEBUG(0, ("failed to unmarshall copy chunk req\n"));
+ state->status = NT_STATUS_INVALID_PARAMETER;
+ tevent_req_nterror(req, state->status);
+ return tevent_req_post(req, ev);
+ }
+
+ state->token = data_blob_const(state->cc_copy.source_key,
+ sizeof(state->cc_copy.source_key));
+
+ state->status = copychunk_check_limits(&state->cc_copy);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ DEBUG(3, ("copy chunk req exceeds limits\n"));
+ state->out_data = COPYCHUNK_OUT_LIMITS;
+ tevent_req_nterror(req, state->status);
+ return tevent_req_post(req, ev);
+ }
+
+ /* any errors from here onwards should carry copychunk response data */
+ state->out_data = COPYCHUNK_OUT_RSP;
+
+ status = fsctl_srv_copychunk_loop(req);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static NTSTATUS fsctl_srv_copychunk_loop(struct tevent_req *req)
+{
+ struct fsctl_srv_copychunk_state *state = tevent_req_data(
+ req, struct fsctl_srv_copychunk_state);
+ struct tevent_req *subreq = NULL;
+ uint32_t length = 0;
+ off_t source_off = 0;
+ off_t target_off = 0;
+
+ /*
+ * chunk_count can be 0 which must either just do nothing returning
+ * success saying number of copied chunks is 0 (verified against
+ * Windows).
+ *
+ * Or it can be a special macOS copyfile request, so we send this into
+ * the VFS, vfs_fruit if loaded implements the macOS copyile semantics.
+ */
+ if (state->cc_copy.chunk_count > 0) {
+ struct srv_copychunk *chunk = NULL;
+
+ chunk = &state->cc_copy.chunks[state->current_chunk];
+ length = chunk->length;
+ source_off = chunk->source_off;
+ target_off = chunk->target_off;
+ }
+
+ subreq = SMB_VFS_OFFLOAD_WRITE_SEND(state->dst_fsp->conn,
+ state,
+ state->ev,
+ state->ctl_code,
+ &state->token,
+ source_off,
+ state->dst_fsp,
+ target_off,
+ length);
+ if (tevent_req_nomem(subreq, req)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_req_set_callback(subreq, fsctl_srv_copychunk_vfs_done, req);
+
+ return NT_STATUS_OK;
+}
+
+static void fsctl_srv_copychunk_vfs_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fsctl_srv_copychunk_state *state = tevent_req_data(
+ req, struct fsctl_srv_copychunk_state);
+ off_t chunk_nwritten;
+ NTSTATUS status;
+
+ status = SMB_VFS_OFFLOAD_WRITE_RECV(state->conn, subreq,
+ &chunk_nwritten);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("copy chunk failed [%s] chunk [%u] of [%u]\n",
+ nt_errstr(status),
+ (unsigned int)state->current_chunk,
+ (unsigned int)state->cc_copy.chunk_count);
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ DBG_DEBUG("good copy chunk [%u] of [%u]\n",
+ (unsigned int)state->current_chunk,
+ (unsigned int)state->cc_copy.chunk_count);
+ state->total_written += chunk_nwritten;
+
+ if (state->cc_copy.chunk_count == 0) {
+ /*
+ * This must not produce an error but just return a chunk count
+ * of 0 in the response.
+ */
+ tevent_req_done(req);
+ return;
+ }
+
+ state->current_chunk++;
+ if (state->current_chunk == state->cc_copy.chunk_count) {
+ tevent_req_done(req);
+ return;
+ }
+
+ status = fsctl_srv_copychunk_loop(req);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+}
+
+static NTSTATUS fsctl_srv_copychunk_recv(struct tevent_req *req,
+ struct srv_copychunk_rsp *cc_rsp,
+ bool *pack_rsp)
+{
+ struct fsctl_srv_copychunk_state *state = tevent_req_data(req,
+ struct fsctl_srv_copychunk_state);
+ NTSTATUS status;
+
+ switch (state->out_data) {
+ case COPYCHUNK_OUT_EMPTY:
+ *pack_rsp = false;
+ break;
+ case COPYCHUNK_OUT_LIMITS:
+ /* 2.2.32.1 - send back our maximum transfer size limits */
+ copychunk_pack_limits(cc_rsp);
+ *pack_rsp = true;
+ break;
+ case COPYCHUNK_OUT_RSP:
+ cc_rsp->chunks_written = state->current_chunk;
+ cc_rsp->chunk_bytes_written = 0;
+ cc_rsp->total_bytes_written = state->total_written;
+ *pack_rsp = true;
+ break;
+ default: /* not reached */
+ assert(1);
+ break;
+ }
+ status = tevent_req_simple_recv_ntstatus(req);
+ return status;
+}
+
+struct cluster_movable_ips {
+ uint32_t array_len;
+ uint32_t array_index;
+ struct sockaddr_storage *ips;
+};
+
+static int stash_cluster_movable_ips(uint32_t total_ip_count,
+ const struct sockaddr_storage *ip,
+ bool is_movable_ip,
+ void *private_data)
+{
+ struct cluster_movable_ips *cluster_movable_ips
+ = talloc_get_type_abort(private_data,
+ struct cluster_movable_ips);
+
+ if (!is_movable_ip) {
+ return 0;
+ }
+
+ if (cluster_movable_ips->array_len == 0) {
+ SMB_ASSERT(total_ip_count < INT_MAX);
+ cluster_movable_ips->ips
+ = talloc_zero_array(cluster_movable_ips,
+ struct sockaddr_storage,
+ total_ip_count);
+ if (cluster_movable_ips->ips == NULL) {
+ return ENOMEM;
+ }
+ cluster_movable_ips->array_len = total_ip_count;
+ }
+
+ SMB_ASSERT(cluster_movable_ips->array_index
+ < cluster_movable_ips->array_len);
+
+ cluster_movable_ips->ips[cluster_movable_ips->array_index] = *ip;
+ cluster_movable_ips->array_index++;
+
+ return 0;
+}
+
+static bool find_in_cluster_movable_ips(
+ struct cluster_movable_ips *cluster_movable_ips,
+ const struct sockaddr_storage *ifss)
+{
+ struct samba_sockaddr srv_ip = {
+ .u = {
+ .ss = *ifss,
+ },
+ };
+ uint32_t i;
+
+ for (i = 0; i < cluster_movable_ips->array_index; i++) {
+ struct samba_sockaddr pub_ip = {
+ .u = {
+ .ss = cluster_movable_ips->ips[i],
+ },
+ };
+ if (sockaddr_equal(&pub_ip.u.sa, &srv_ip.u.sa)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static NTSTATUS fsctl_network_iface_info(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbXsrv_connection *xconn,
+ DATA_BLOB *in_input,
+ uint32_t in_max_output,
+ DATA_BLOB *out_output)
+{
+ struct samba_sockaddr xconn_srv_addr = { .sa_socklen = 0, };
+ struct fsctl_net_iface_info *array = NULL;
+ struct fsctl_net_iface_info *first = NULL;
+ struct fsctl_net_iface_info *last = NULL;
+ size_t i;
+ size_t num_ifaces;
+ enum ndr_err_code ndr_err;
+ struct cluster_movable_ips *cluster_movable_ips = NULL;
+ ssize_t sret;
+ int ret;
+
+ if (in_input->length != 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * The list of probed interfaces might have changed, we might need to
+ * refresh local_interfaces to get up-to-date network information, and
+ * respond to clients which sent FSCTL_QUERY_NETWORK_INTERFACE_INFO.
+ * For example, network speed is changed, interfaces count is changed
+ * (some link down or link up), and etc.
+ */
+ load_interfaces();
+ num_ifaces = iface_count();
+
+ *out_output = data_blob_null;
+
+ array = talloc_zero_array(mem_ctx,
+ struct fsctl_net_iface_info,
+ num_ifaces);
+ if (array == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (lp_clustering()) {
+ cluster_movable_ips = talloc_zero(array,
+ struct cluster_movable_ips);
+ if (cluster_movable_ips == NULL) {
+ TALLOC_FREE(array);
+ return NT_STATUS_NO_MEMORY;
+ }
+ ret = ctdbd_public_ip_foreach(messaging_ctdb_connection(),
+ stash_cluster_movable_ips,
+ cluster_movable_ips);
+ if (ret != 0) {
+ TALLOC_FREE(array);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ sret = tsocket_address_bsd_sockaddr(xconn->local_address,
+ &xconn_srv_addr.u.sa,
+ sizeof(xconn_srv_addr.u.ss));
+ if (sret < 0) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ xconn_srv_addr.sa_socklen = sret;
+
+ for (i=0; i < num_ifaces; i++) {
+ struct fsctl_net_iface_info *cur = &array[i];
+ const struct interface *iface = get_interface(i);
+ const struct sockaddr_storage *ifss = &iface->ip;
+ const void *ifptr = ifss;
+ const struct sockaddr *ifsa = (const struct sockaddr *)ifptr;
+ struct tsocket_address *a = NULL;
+ char *addr;
+ bool ok;
+
+ ret = tsocket_address_bsd_from_sockaddr(array,
+ ifsa, sizeof(struct sockaddr_storage),
+ &a);
+ if (ret != 0) {
+ NTSTATUS status = map_nt_error_from_unix_common(errno);
+ TALLOC_FREE(array);
+ return status;
+ }
+
+ ok = tsocket_address_is_inet(a, "ip");
+ if (!ok) {
+ continue;
+ }
+
+ addr = tsocket_address_inet_addr_string(a, array);
+ if (addr == NULL) {
+ TALLOC_FREE(array);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (sockaddr_equal(ifsa, &xconn_srv_addr.u.sa)) {
+ /*
+ * We can announce the ip of the current connection even
+ * if it is a moveable cluster address... as the client
+ * is already connected to it.
+ *
+ * It means in a typical ctdb cluster, where we
+ * only have public addresses, the client can at least
+ * have more than one multichannel'ed connection to the
+ * public ip.
+ */
+ } else if (cluster_movable_ips != NULL) {
+ bool is_movable_ip = find_in_cluster_movable_ips(
+ cluster_movable_ips,
+ ifss);
+ if (is_movable_ip) {
+ DBG_DEBUG("Interface [%s] - "
+ "has movable public ip - "
+ "skipping address [%s].\n",
+ iface->name, addr);
+ continue;
+ }
+ }
+
+ cur->ifindex = iface->if_index;
+ if (cur->ifindex == 0) {
+ /*
+ * Did not get interface index from kernel,
+ * nor from the config. ==> Apply a common
+ * default value for these cases.
+ */
+ cur->ifindex = UINT32_MAX;
+ }
+ cur->capability = iface->capability;
+ cur->linkspeed = iface->linkspeed;
+ if (cur->linkspeed == 0) {
+ DBG_DEBUG("Link speed 0 on interface [%s] - skipping "
+ "address [%s].\n", iface->name, addr);
+ continue;
+ }
+
+ ok = tsocket_address_is_inet(a, "ipv4");
+ if (ok) {
+ cur->sockaddr.family = FSCTL_NET_IFACE_AF_INET;
+ cur->sockaddr.saddr.saddr_in.ipv4 = addr;
+ }
+ ok = tsocket_address_is_inet(a, "ipv6");
+ if (ok) {
+ cur->sockaddr.family = FSCTL_NET_IFACE_AF_INET6;
+ cur->sockaddr.saddr.saddr_in6.ipv6 = addr;
+ }
+
+ if (first == NULL) {
+ first = cur;
+ }
+ if (last != NULL) {
+ last->next = cur;
+ }
+ last = cur;
+ }
+
+ if (first == NULL) {
+ TALLOC_FREE(array);
+ return NT_STATUS_OK;
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(fsctl_net_iface_info, first);
+ }
+
+ ndr_err = ndr_push_struct_blob(out_output, mem_ctx, first,
+ (ndr_push_flags_fn_t)ndr_push_fsctl_net_iface_info);
+ TALLOC_FREE(array);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fsctl_validate_neg_info(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbXsrv_connection *conn,
+ DATA_BLOB *in_input,
+ uint32_t in_max_output,
+ DATA_BLOB *out_output,
+ bool *disconnect)
+{
+ uint32_t in_capabilities;
+ DATA_BLOB in_guid_blob;
+ struct GUID in_guid;
+ uint16_t in_security_mode;
+ uint16_t in_num_dialects;
+ uint16_t dialect;
+ struct GUID_ndr_buf out_guid_buf = { .buf = {0}, };
+ NTSTATUS status;
+ enum protocol_types protocol = PROTOCOL_NONE;
+
+ if (lp_server_max_protocol() <= PROTOCOL_SMB2_02) {
+ /*
+ * With SMB 2.02 we didn't get the
+ * capabitities, client guid, security mode
+ * and dialects the client would have offered.
+ *
+ * So we behave compatible with a true
+ * SMB 2.02 server and return NT_STATUS_FILE_CLOSED.
+ *
+ * As SMB >= 2.10 offers the two phase SMB2 Negotiate
+ * we keep supporting FSCTL_VALIDATE_NEGOTIATE_INFO
+ * starting with SMB 2.10, while Windows only supports
+ * it starting with SMB > 2.10.
+ */
+ return NT_STATUS_FILE_CLOSED;
+ }
+
+ if (in_input->length < 0x18) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ in_capabilities = IVAL(in_input->data, 0x00);
+ in_guid_blob = data_blob_const(in_input->data + 0x04, 16);
+ in_security_mode = SVAL(in_input->data, 0x14);
+ in_num_dialects = SVAL(in_input->data, 0x16);
+
+ if (in_input->length < (0x18 + in_num_dialects*2)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (in_max_output < 0x18) {
+ return NT_STATUS_BUFFER_TOO_SMALL;
+ }
+
+ status = GUID_from_ndr_blob(&in_guid_blob, &in_guid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * From: [MS-SMB2]
+ * 3.3.5.15.12 Handling a Validate Negotiate Info Request
+ *
+ * The server MUST determine the greatest common dialect
+ * between the dialects it implements and the Dialects array
+ * of the VALIDATE_NEGOTIATE_INFO request. If no dialect is
+ * matched, or if the value is not equal to Connection.Dialect,
+ * the server MUST terminate the transport connection
+ * and free the Connection object.
+ */
+ protocol = smbd_smb2_protocol_dialect_match(in_input->data + 0x18,
+ in_num_dialects,
+ &dialect);
+ if (conn->protocol != protocol) {
+ *disconnect = true;
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (!GUID_equal(&in_guid, &conn->smb2.client.guid)) {
+ *disconnect = true;
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (in_security_mode != conn->smb2.client.security_mode) {
+ *disconnect = true;
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (in_capabilities != conn->smb2.client.capabilities) {
+ *disconnect = true;
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ status = GUID_to_ndr_buf(&conn->smb2.server.guid, &out_guid_buf);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ *out_output = data_blob_talloc(mem_ctx, NULL, 0x18);
+ if (out_output->data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ SIVAL(out_output->data, 0x00, conn->smb2.server.capabilities);
+ memcpy(out_output->data+0x04, out_guid_buf.buf, 16);
+ SSVAL(out_output->data, 0x14, conn->smb2.server.security_mode);
+ SSVAL(out_output->data, 0x16, conn->smb2.server.dialect);
+
+ return NT_STATUS_OK;
+}
+
+static void smb2_ioctl_network_fs_copychunk_done(struct tevent_req *subreq);
+static void smb2_ioctl_network_fs_offload_read_done(struct tevent_req *subreq);
+
+struct tevent_req *smb2_ioctl_network_fs(uint32_t ctl_code,
+ struct tevent_context *ev,
+ struct tevent_req *req,
+ struct smbd_smb2_ioctl_state *state)
+{
+ struct tevent_req *subreq;
+ NTSTATUS status;
+
+ switch (ctl_code) {
+ /*
+ * [MS-SMB2] 2.2.31
+ * FSCTL_SRV_COPYCHUNK is issued when a handle has
+ * FILE_READ_DATA and FILE_WRITE_DATA access to the file;
+ * FSCTL_SRV_COPYCHUNK_WRITE is issued when a handle only has
+ * FILE_WRITE_DATA access.
+ */
+ case FSCTL_SRV_COPYCHUNK_WRITE: /* FALL THROUGH */
+ case FSCTL_SRV_COPYCHUNK:
+ subreq = fsctl_srv_copychunk_send(state, ev,
+ ctl_code,
+ state->fsp,
+ &state->in_input,
+ state->in_max_output,
+ state->smb2req);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ smb2_ioctl_network_fs_copychunk_done,
+ req);
+ return req;
+ break;
+ case FSCTL_QUERY_NETWORK_INTERFACE_INFO:
+ if (!state->smbreq->xconn->client->server_multi_channel_enabled)
+ {
+ if (IS_IPC(state->smbreq->conn)) {
+ status = NT_STATUS_FS_DRIVER_REQUIRED;
+ } else {
+ status = NT_STATUS_INVALID_DEVICE_REQUEST;
+ }
+
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ status = fsctl_network_iface_info(state, ev,
+ state->smbreq->xconn,
+ &state->in_input,
+ state->in_max_output,
+ &state->out_output);
+ if (!tevent_req_nterror(req, status)) {
+ tevent_req_done(req);
+ }
+ return tevent_req_post(req, ev);
+ break;
+ case FSCTL_VALIDATE_NEGOTIATE_INFO:
+ status = fsctl_validate_neg_info(state, ev,
+ state->smbreq->xconn,
+ &state->in_input,
+ state->in_max_output,
+ &state->out_output,
+ &state->disconnect);
+ if (!tevent_req_nterror(req, status)) {
+ tevent_req_done(req);
+ }
+ return tevent_req_post(req, ev);
+ break;
+ case FSCTL_SRV_REQUEST_RESUME_KEY:
+ subreq = SMB_VFS_OFFLOAD_READ_SEND(state,
+ ev,
+ state->fsp,
+ FSCTL_SRV_REQUEST_RESUME_KEY,
+ 0, 0, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ subreq, smb2_ioctl_network_fs_offload_read_done, req);
+ return req;
+
+ default: {
+ uint8_t *out_data = NULL;
+ uint32_t out_data_len = 0;
+
+ if (state->fsp == NULL) {
+ status = NT_STATUS_NOT_SUPPORTED;
+ } else {
+ status = SMB_VFS_FSCTL(state->fsp,
+ state,
+ ctl_code,
+ state->smbreq->flags2,
+ state->in_input.data,
+ state->in_input.length,
+ &out_data,
+ state->in_max_output,
+ &out_data_len);
+ state->out_output = data_blob_const(out_data, out_data_len);
+ if (NT_STATUS_IS_OK(status)) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
+ if (IS_IPC(state->smbreq->conn)) {
+ status = NT_STATUS_FS_DRIVER_REQUIRED;
+ } else {
+ status = NT_STATUS_INVALID_DEVICE_REQUEST;
+ }
+ }
+
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ break;
+ }
+ }
+
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return tevent_req_post(req, ev);
+}
+
+static void smb2_ioctl_network_fs_copychunk_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smbd_smb2_ioctl_state *ioctl_state = tevent_req_data(req,
+ struct smbd_smb2_ioctl_state);
+ struct srv_copychunk_rsp cc_rsp;
+ NTSTATUS status;
+ bool pack_rsp = false;
+
+ ZERO_STRUCT(cc_rsp);
+ status = fsctl_srv_copychunk_recv(subreq, &cc_rsp, &pack_rsp);
+ TALLOC_FREE(subreq);
+ if (pack_rsp == true) {
+ enum ndr_err_code ndr_ret;
+ ndr_ret = ndr_push_struct_blob(&ioctl_state->out_output,
+ ioctl_state,
+ &cc_rsp,
+ (ndr_push_flags_fn_t)ndr_push_srv_copychunk_rsp);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ if (!tevent_req_nterror(req, status)) {
+ tevent_req_done(req);
+ }
+}
+
+static void smb2_ioctl_network_fs_offload_read_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smbd_smb2_ioctl_state *state = tevent_req_data(
+ req, struct smbd_smb2_ioctl_state);
+ struct req_resume_key_rsp rkey_rsp;
+ enum ndr_err_code ndr_ret;
+ uint32_t flags;
+ uint64_t xferlen;
+ DATA_BLOB token;
+ NTSTATUS status;
+
+ /*
+ * Note that both flags and xferlen are not used with copy-chunk.
+ */
+ status = SMB_VFS_OFFLOAD_READ_RECV(subreq,
+ state->fsp->conn,
+ state,
+ &flags,
+ &xferlen,
+ &token);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (token.length != sizeof(rkey_rsp.resume_key)) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ ZERO_STRUCT(rkey_rsp);
+ memcpy(rkey_rsp.resume_key, token.data, token.length);
+
+ ndr_ret = ndr_push_struct_blob(&state->out_output, state, &rkey_rsp,
+ (ndr_push_flags_fn_t)ndr_push_req_resume_key_rsp);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
diff --git a/source3/smbd/smb2_ioctl_private.h b/source3/smbd/smb2_ioctl_private.h
new file mode 100644
index 0000000..d653c10
--- /dev/null
+++ b/source3/smbd/smb2_ioctl_private.h
@@ -0,0 +1,60 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+
+ 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/>.
+*/
+
+#ifndef __SMB2_IOCTL_PRIVATE_H__
+#define __SMB2_IOCTL_PRIVATE_H__
+
+struct smbd_smb2_ioctl_state {
+ struct smbd_smb2_request *smb2req;
+ struct smb_request *smbreq;
+ files_struct *fsp;
+ DATA_BLOB in_input;
+ uint32_t in_max_output;
+ DATA_BLOB out_output;
+ uint8_t body_padding;
+ bool disconnect;
+};
+
+struct tevent_req *smb2_ioctl_dfs(uint32_t,
+ struct tevent_context *,
+ struct tevent_req *,
+ struct smbd_smb2_ioctl_state *);
+
+struct tevent_req *smb2_ioctl_filesys(uint32_t,
+ struct tevent_context *,
+ struct tevent_req *,
+ struct smbd_smb2_ioctl_state *);
+
+struct tevent_req *smb2_ioctl_named_pipe(uint32_t,
+ struct tevent_context *,
+ struct tevent_req *,
+ struct smbd_smb2_ioctl_state *);
+
+struct tevent_req *smb2_ioctl_network_fs(uint32_t,
+ struct tevent_context *,
+ struct tevent_req *,
+ struct smbd_smb2_ioctl_state *);
+
+struct tevent_req *smb2_ioctl_smbtorture(uint32_t ctl_code,
+ struct tevent_context *ev,
+ struct tevent_req *req,
+ struct smbd_smb2_ioctl_state *state);
+
+#endif
diff --git a/source3/smbd/smb2_ioctl_smbtorture.c b/source3/smbd/smb2_ioctl_smbtorture.c
new file mode 100644
index 0000000..9a259fb
--- /dev/null
+++ b/source3/smbd/smb2_ioctl_smbtorture.c
@@ -0,0 +1,230 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+ Copyright (C) Jeremy Allison 2021
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "include/ntioctl.h"
+#include "smb2_ioctl_private.h"
+#include "librpc/gen_ndr/ioctl.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+struct async_sleep_state {
+ struct smbd_server_connection *sconn;
+ files_struct *fsp;
+};
+
+static void smbd_fsctl_torture_async_sleep_done(struct tevent_req *subreq);
+
+static struct tevent_req *smbd_fsctl_torture_async_sleep_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ files_struct *fsp,
+ uint8_t msecs)
+{
+ struct async_sleep_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+ bool ok;
+
+ subreq = tevent_req_create(mem_ctx,
+ &state,
+ struct async_sleep_state);
+ if (!subreq) {
+ return NULL;
+ }
+
+ /*
+ * Store the conn separately, as the test is to
+ * see if fsp is still a valid pointer, so we can't
+ * do anything other than test it for entry in the
+ * open files on this server connection.
+ */
+ state->sconn = fsp->conn->sconn;
+ state->fsp = fsp;
+
+ /*
+ * Just wait for the specified number of micro seconds,
+ * to allow the client time to close fsp.
+ */
+ ok = tevent_req_set_endtime(subreq,
+ ev,
+ timeval_current_ofs(0, msecs));
+ if (!ok) {
+ tevent_req_nterror(subreq, NT_STATUS_NO_MEMORY);
+ return tevent_req_post(subreq, ev);
+ }
+
+ return subreq;
+}
+
+static files_struct *find_my_fsp(struct files_struct *fsp,
+ void *private_data)
+{
+ struct files_struct *myfsp = (struct files_struct *)private_data;
+
+ if (fsp == myfsp) {
+ return myfsp;
+ }
+ return NULL;
+}
+
+static bool smbd_fsctl_torture_async_sleep_recv(struct tevent_req *subreq)
+{
+ tevent_req_received(subreq);
+ return true;
+}
+
+static void smbd_fsctl_torture_async_sleep_done(struct tevent_req *subreq)
+{
+ struct files_struct *found_fsp;
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq,
+ struct tevent_req);
+ struct async_sleep_state *state = tevent_req_data(
+ subreq,
+ struct async_sleep_state);
+
+ /* Does state->fsp still exist on state->sconn ? */
+ found_fsp = files_forall(state->sconn,
+ find_my_fsp,
+ state->fsp);
+
+ smbd_fsctl_torture_async_sleep_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ if (found_fsp == NULL) {
+ /*
+ * We didn't find it - return an error to the
+ * smb2 ioctl request. Use NT_STATUS_FILE_CLOSED so
+ * the client can tell the difference between
+ * a bad fsp handle and
+ *
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=14769
+ *
+ * This request should block file closure until it
+ * has completed.
+ */
+ tevent_req_nterror(req, NT_STATUS_FILE_CLOSED);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+struct tevent_req *smb2_ioctl_smbtorture(uint32_t ctl_code,
+ struct tevent_context *ev,
+ struct tevent_req *req,
+ struct smbd_smb2_ioctl_state *state)
+{
+ NTSTATUS status;
+ bool ok;
+
+ ok = lp_parm_bool(-1, "smbd", "FSCTL_SMBTORTURE", false);
+ if (!ok) {
+ goto not_supported;
+ }
+
+ switch (ctl_code) {
+ case FSCTL_SMBTORTURE_FORCE_UNACKED_TIMEOUT:
+ if (state->in_input.length != 0) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ state->smb2req->xconn->ack.force_unacked_timeout = true;
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+
+ case FSCTL_SMBTORTURE_IOCTL_RESPONSE_BODY_PADDING8:
+ if (state->in_input.length != 0) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ if (state->in_max_output > 0) {
+ uint32_t size = state->in_max_output;
+
+ state->out_output = data_blob_talloc(state, NULL, size);
+ if (tevent_req_nomem(state->out_output.data, req)) {
+ return tevent_req_post(req, ev);
+ }
+ memset(state->out_output.data, 8, size);
+ }
+
+ state->body_padding = 8;
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+
+ case FSCTL_SMBTORTURE_GLOBAL_READ_RESPONSE_BODY_PADDING8:
+ if (state->in_input.length != 0) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ state->smb2req->xconn->smb2.smbtorture.read_body_padding = 8;
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+
+ case FSCTL_SMBTORTURE_FSP_ASYNC_SLEEP: {
+ struct tevent_req *subreq = NULL;
+
+ /* Data is 1 byte of CVAL stored seconds to delay for. */
+ if (state->in_input.length != 1) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ if (state->fsp == NULL) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = smbd_fsctl_torture_async_sleep_send(
+ req,
+ ev,
+ state->fsp,
+ CVAL(state->in_input.data,0));
+ if (subreq == NULL) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ smbd_fsctl_torture_async_sleep_done,
+ req);
+ return req;
+ }
+
+ default:
+ goto not_supported;
+ }
+
+not_supported:
+ if (IS_IPC(state->smbreq->conn)) {
+ status = NT_STATUS_FS_DRIVER_REQUIRED;
+ } else {
+ status = NT_STATUS_INVALID_DEVICE_REQUEST;
+ }
+
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+}
diff --git a/source3/smbd/smb2_ipc.c b/source3/smbd/smb2_ipc.c
new file mode 100644
index 0000000..fbe2233
--- /dev/null
+++ b/source3/smbd/smb2_ipc.c
@@ -0,0 +1,40 @@
+/*
+ Unix SMB/CIFS implementation.
+ Inter-process communication and named pipe handling
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ SMB Version handling
+ Copyright (C) John H Terpstra 1995-1998
+
+ 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/>.
+ */
+/*
+ This file handles the named pipe and mailslot calls
+ in the SMBtrans protocol
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+
+NTSTATUS nt_status_np_pipe(NTSTATUS status)
+{
+ if (NT_STATUS_EQUAL(status, NT_STATUS_CONNECTION_DISCONNECTED)) {
+ status = NT_STATUS_PIPE_DISCONNECTED;
+ } else if (NT_STATUS_EQUAL(status, NT_STATUS_CONNECTION_RESET)) {
+ status = NT_STATUS_PIPE_BROKEN;
+ }
+
+ return status;
+}
diff --git a/source3/smbd/smb2_keepalive.c b/source3/smbd/smb2_keepalive.c
new file mode 100644
index 0000000..fac567c
--- /dev/null
+++ b/source3/smbd/smb2_keepalive.c
@@ -0,0 +1,50 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+NTSTATUS smbd_smb2_request_process_keepalive(struct smbd_smb2_request *req)
+{
+ DATA_BLOB outbody;
+ NTSTATUS status;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x04);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ /* TODO: update some time stamps */
+
+ outbody = smbd_smb2_generate_outbody(req, 0x04);
+ if (outbody.data == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+
+ SSVAL(outbody.data, 0x00, 0x04); /* struct size */
+ SSVAL(outbody.data, 0x02, 0); /* reserved */
+
+ return smbd_smb2_request_done(req, outbody, NULL);
+}
diff --git a/source3/smbd/smb2_lock.c b/source3/smbd/smb2_lock.c
new file mode 100644
index 0000000..c9d810f
--- /dev/null
+++ b/source3/smbd/smb2_lock.c
@@ -0,0 +1,782 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+ Copyright (C) Jeremy Allison 2010
+
+ 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 "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "lib/dbwrap/dbwrap_watch.h"
+#include "librpc/gen_ndr/open_files.h"
+#include "messages.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+struct smbd_smb2_lock_element {
+ uint64_t offset;
+ uint64_t length;
+ uint32_t flags;
+};
+
+struct smbd_smb2_lock_state {
+ struct tevent_context *ev;
+ struct smbd_smb2_request *smb2req;
+ struct smb_request *smb1req;
+ struct files_struct *fsp;
+ bool blocking;
+ uint32_t polling_msecs;
+ uint32_t retry_msecs;
+ uint16_t lock_count;
+ struct smbd_lock_element *locks;
+ uint8_t lock_sequence_value;
+ uint8_t *lock_sequence_element;
+};
+
+static struct tevent_req *smbd_smb2_lock_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *in_fsp,
+ uint32_t in_lock_sequence,
+ uint16_t in_lock_count,
+ struct smbd_smb2_lock_element *in_locks);
+static NTSTATUS smbd_smb2_lock_recv(struct tevent_req *req);
+
+static void smbd_smb2_request_lock_done(struct tevent_req *subreq);
+NTSTATUS smbd_smb2_request_process_lock(struct smbd_smb2_request *req)
+{
+ const uint8_t *inbody;
+ uint16_t in_lock_count;
+ uint32_t in_lock_sequence;
+ uint64_t in_file_id_persistent;
+ uint64_t in_file_id_volatile;
+ struct files_struct *in_fsp;
+ struct smbd_smb2_lock_element *in_locks;
+ struct tevent_req *subreq;
+ const uint8_t *lock_buffer;
+ uint16_t l;
+ NTSTATUS status;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x30);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ in_lock_count = CVAL(inbody, 0x02);
+ if (req->xconn->protocol >= PROTOCOL_SMB2_10) {
+ in_lock_sequence = IVAL(inbody, 0x04);
+ } else {
+ /* 0x04 - 4 bytes reserved */
+ in_lock_sequence = 0;
+ }
+ in_file_id_persistent = BVAL(inbody, 0x08);
+ in_file_id_volatile = BVAL(inbody, 0x10);
+
+ if (in_lock_count < 1) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ if (((in_lock_count - 1) * 0x18) > SMBD_SMB2_IN_DYN_LEN(req)) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ in_locks = talloc_array(req, struct smbd_smb2_lock_element,
+ in_lock_count);
+ if (in_locks == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+
+ l = 0;
+ lock_buffer = inbody + 0x18;
+
+ in_locks[l].offset = BVAL(lock_buffer, 0x00);
+ in_locks[l].length = BVAL(lock_buffer, 0x08);
+ in_locks[l].flags = IVAL(lock_buffer, 0x10);
+ /* 0x14 - 4 reserved bytes */
+
+ status = req->session->status;
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_SESSION_EXPIRED)) {
+ /*
+ * We need to catch NT_STATUS_NETWORK_SESSION_EXPIRED
+ * for lock requests only.
+ *
+ * Unlock requests still need to be processed!
+ *
+ * This means smbd_smb2_request_check_session()
+ * can't handle the difference and always
+ * allows SMB2_OP_LOCK.
+ */
+ if (in_locks[0].flags != SMB2_LOCK_FLAG_UNLOCK) {
+ return smbd_smb2_request_error(req, status);
+ }
+ }
+
+ lock_buffer = SMBD_SMB2_IN_DYN_PTR(req);
+
+ for (l=1; l < in_lock_count; l++) {
+ in_locks[l].offset = BVAL(lock_buffer, 0x00);
+ in_locks[l].length = BVAL(lock_buffer, 0x08);
+ in_locks[l].flags = IVAL(lock_buffer, 0x10);
+ /* 0x14 - 4 reserved bytes */
+
+ lock_buffer += 0x18;
+ }
+
+ in_fsp = file_fsp_smb2(req, in_file_id_persistent, in_file_id_volatile);
+ if (in_fsp == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED);
+ }
+
+ subreq = smbd_smb2_lock_send(req, req->sconn->ev_ctx,
+ req, in_fsp,
+ in_lock_sequence,
+ in_lock_count,
+ in_locks);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_lock_done, req);
+
+ return smbd_smb2_request_pending_queue(req, subreq, 500);
+}
+
+static void smbd_smb2_request_lock_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *smb2req = tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ DATA_BLOB outbody;
+ NTSTATUS status;
+ NTSTATUS error; /* transport error */
+
+ status = smbd_smb2_lock_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ error = smbd_smb2_request_error(smb2req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ outbody = smbd_smb2_generate_outbody(smb2req, 0x04);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(smb2req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x04); /* struct size */
+ SSVAL(outbody.data, 0x02, 0); /* reserved */
+
+ error = smbd_smb2_request_done(smb2req, outbody, NULL);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+static void smbd_smb2_lock_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state);
+static void smbd_smb2_lock_try(struct tevent_req *req);
+static void smbd_smb2_lock_retry(struct tevent_req *subreq);
+static bool smbd_smb2_lock_cancel(struct tevent_req *req);
+
+static struct tevent_req *smbd_smb2_lock_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *fsp,
+ uint32_t in_lock_sequence,
+ uint16_t in_lock_count,
+ struct smbd_smb2_lock_element *in_locks)
+{
+ struct tevent_req *req;
+ struct smbd_smb2_lock_state *state;
+ bool isunlock = false;
+ uint16_t i;
+ struct smbd_lock_element *locks;
+ NTSTATUS status;
+ bool check_lock_sequence = false;
+ uint32_t lock_sequence_bucket = 0;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_lock_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->fsp = fsp;
+ state->smb2req = smb2req;
+ smb2req->subreq = req; /* So we can find this when going async. */
+
+ tevent_req_set_cleanup_fn(req, smbd_smb2_lock_cleanup);
+
+ state->smb1req = smbd_smb2_fake_smb_request(smb2req, fsp);
+ if (tevent_req_nomem(state->smb1req, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ DEBUG(10,("smbd_smb2_lock_send: %s - %s\n",
+ fsp_str_dbg(fsp), fsp_fnum_dbg(fsp)));
+
+ /*
+ * Windows sets check_lock_sequence = true
+ * only for resilient and persistent handles.
+ *
+ * [MS-SMB2] 3.3.5.14 Receiving an SMB2 LOCK Request
+ *
+ * ... if Open.IsResilient or Open.IsDurable or Open.IsPersistent is
+ * TRUE or if Connection.Dialect belongs to the SMB 3.x dialect family
+ * and Connection.ServerCapabilities includes
+ * SMB2_GLOBAL_CAP_MULTI_CHANNEL bit, the server SHOULD<314>
+ * perform lock sequence * verification ...
+
+ * <314> Section 3.3.5.14: Windows 7 and Windows Server 2008 R2 perform
+ * lock sequence verification only when Open.IsResilient is TRUE.
+ * Windows 8 through Windows 10 v1909 and Windows Server 2012 through
+ * Windows Server v1909 perform lock sequence verification only when
+ * Open.IsResilient or Open.IsPersistent is TRUE.
+ *
+ * Note <314> also applies to all versions (at least) up to
+ * Windows Server v2004.
+ *
+ * Hopefully this will be fixed in future Windows versions and they
+ * will avoid Note <314>.
+ *
+ * We implement what the specification says by default, but
+ * allow "smb2 disable lock sequence checking = yes" to
+ * behave like Windows again.
+ *
+ * Note: that we already check the dialect before setting
+ * SMB2_CAP_MULTI_CHANNEL in smb2_negprot.c
+ */
+ if (smb2req->xconn->smb2.server.capabilities & SMB2_CAP_MULTI_CHANNEL) {
+ check_lock_sequence = true;
+ }
+ if (fsp->op->global->durable) {
+ check_lock_sequence = true;
+ }
+
+ if (check_lock_sequence) {
+ bool disable_lock_sequence_checking =
+ lp_smb2_disable_lock_sequence_checking();
+
+ if (disable_lock_sequence_checking) {
+ check_lock_sequence = false;
+ }
+ }
+
+ if (check_lock_sequence) {
+ state->lock_sequence_value = in_lock_sequence & 0xF;
+ lock_sequence_bucket = in_lock_sequence >> 4;
+ }
+ if ((lock_sequence_bucket > 0) &&
+ (lock_sequence_bucket <= sizeof(fsp->op->global->lock_sequence_array)))
+ {
+ uint32_t idx = lock_sequence_bucket - 1;
+ uint8_t *array = fsp->op->global->lock_sequence_array;
+
+ state->lock_sequence_element = &array[idx];
+ }
+
+ if (state->lock_sequence_element != NULL) {
+ /*
+ * The incoming 'state->lock_sequence_value' is masked with 0xF.
+ *
+ * Note per default '*state->lock_sequence_element'
+ * is invalid, a value of 0xFF that can never match on
+ * incoming value.
+ */
+ if (*state->lock_sequence_element == state->lock_sequence_value)
+ {
+ DBG_INFO("replayed smb2 lock request detected: "
+ "file %s, value %u, bucket %u\n",
+ fsp_str_dbg(fsp),
+ (unsigned)state->lock_sequence_value,
+ (unsigned)lock_sequence_bucket);
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+ /*
+ * If it's not a replay, mark the element as
+ * invalid again.
+ */
+ *state->lock_sequence_element = 0xFF;
+ }
+
+ locks = talloc_array(state, struct smbd_lock_element, in_lock_count);
+ if (locks == NULL) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return tevent_req_post(req, ev);
+ }
+
+ switch (in_locks[0].flags) {
+ case SMB2_LOCK_FLAG_SHARED:
+ case SMB2_LOCK_FLAG_EXCLUSIVE:
+ if (in_lock_count > 1) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ state->blocking = true;
+ break;
+
+ case SMB2_LOCK_FLAG_SHARED|SMB2_LOCK_FLAG_FAIL_IMMEDIATELY:
+ case SMB2_LOCK_FLAG_EXCLUSIVE|SMB2_LOCK_FLAG_FAIL_IMMEDIATELY:
+ break;
+
+ case SMB2_LOCK_FLAG_UNLOCK:
+ /* only the first lock gives the UNLOCK bit - see
+ MS-SMB2 3.3.5.14 */
+ isunlock = true;
+ break;
+
+ default:
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ if (!isunlock && (in_lock_count > 1)) {
+
+ /*
+ * 3.3.5.14.2 says we SHOULD fail with INVALID_PARAMETER if we
+ * have more than one lock and one of those is blocking.
+ */
+
+ for (i=0; i<in_lock_count; i++) {
+ uint32_t flags = in_locks[i].flags;
+
+ if ((flags & SMB2_LOCK_FLAG_FAIL_IMMEDIATELY) == 0) {
+ tevent_req_nterror(
+ req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ }
+ }
+
+ for (i=0; i<in_lock_count; i++) {
+ bool invalid = false;
+ bool posix_handle =(fsp->posix_flags & FSP_POSIX_FLAGS_OPEN);
+
+ switch (in_locks[i].flags) {
+ case SMB2_LOCK_FLAG_SHARED:
+ case SMB2_LOCK_FLAG_EXCLUSIVE:
+ if (isunlock) {
+ invalid = true;
+ break;
+ }
+ break;
+
+ case SMB2_LOCK_FLAG_SHARED|SMB2_LOCK_FLAG_FAIL_IMMEDIATELY:
+ case SMB2_LOCK_FLAG_EXCLUSIVE|SMB2_LOCK_FLAG_FAIL_IMMEDIATELY:
+ if (isunlock) {
+ invalid = true;
+ }
+ break;
+
+ case SMB2_LOCK_FLAG_UNLOCK:
+ if (!isunlock) {
+ tevent_req_nterror(req,
+ NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ break;
+
+ default:
+ if (isunlock) {
+ /*
+ * If the first element was a UNLOCK
+ * we need to defer the error response
+ * to the backend, because we need to process
+ * all unlock elements before
+ */
+ invalid = true;
+ break;
+ }
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ locks[i].req_guid = smbd_request_guid(smb2req->smb1req, i);
+ locks[i].smblctx = fsp->op->global->open_persistent_id;
+ locks[i].offset = in_locks[i].offset;
+ locks[i].count = in_locks[i].length;
+
+ if (posix_handle) {
+ locks[i].lock_flav = POSIX_LOCK;
+ } else {
+ locks[i].lock_flav = WINDOWS_LOCK;
+ }
+
+ if (in_locks[i].flags & SMB2_LOCK_FLAG_EXCLUSIVE) {
+ if (posix_handle && fsp->fsp_flags.can_write == false) {
+ /*
+ * Can't get a write lock on a posix
+ * read-only handle.
+ */
+ DBG_INFO("POSIX write lock requested "
+ "on read-only handle for file %s\n",
+ fsp_str_dbg(fsp));
+ tevent_req_nterror(req,
+ NT_STATUS_INVALID_HANDLE);
+ return tevent_req_post(req, ev);
+ }
+ locks[i].brltype = WRITE_LOCK;
+ } else if (in_locks[i].flags & SMB2_LOCK_FLAG_SHARED) {
+ locks[i].brltype = READ_LOCK;
+ } else if (invalid) {
+ /*
+ * this is an invalid UNLOCK element
+ * and the backend needs to test for
+ * brltype != UNLOCK_LOCK and return
+ * NT_STATUS_INVALID_PARAMETER
+ */
+ locks[i].brltype = READ_LOCK;
+ } else {
+ locks[i].brltype = UNLOCK_LOCK;
+ }
+ locks[i].lock_flav = WINDOWS_LOCK;
+
+ DBG_DEBUG("index %"PRIu16" offset=%"PRIu64", count=%"PRIu64", "
+ "smblctx = %"PRIu64" type %d\n",
+ i,
+ locks[i].offset,
+ locks[i].count,
+ locks[i].smblctx,
+ (int)locks[i].brltype);
+ }
+
+ state->locks = locks;
+ state->lock_count = in_lock_count;
+
+ if (isunlock) {
+ status = smbd_do_unlocking(
+ state->smb1req, fsp, in_lock_count, locks);
+
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ smbd_smb2_lock_try(req);
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_defer_callback(req, smb2req->sconn->ev_ctx);
+ aio_add_req_to_fsp(state->fsp, req);
+ tevent_req_set_cancel_fn(req, smbd_smb2_lock_cancel);
+
+ return req;
+}
+
+static void smbd_smb2_lock_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state)
+{
+ struct smbd_smb2_lock_state *state = tevent_req_data(
+ req, struct smbd_smb2_lock_state);
+
+ if (req_state != TEVENT_REQ_DONE) {
+ return;
+ }
+
+ if (state->lock_sequence_element != NULL) {
+ /*
+ * On success we remember the given/incoming
+ * value (which was masked with 0xF.
+ */
+ *state->lock_sequence_element = state->lock_sequence_value;
+ }
+}
+
+static void smbd_smb2_lock_update_retry_msecs(
+ struct smbd_smb2_lock_state *state)
+{
+ /*
+ * The default lp_lock_spin_time() is 200ms,
+ * we just use half of it to trigger the first retry.
+ *
+ * v_min is in the range of 0.001 to 10 secs
+ * (0.1 secs by default)
+ *
+ * v_max is in the range of 0.01 to 100 secs
+ * (1.0 secs by default)
+ *
+ * The typical steps are:
+ * 0.1, 0.2, 0.3, 0.4, ... 1.0
+ */
+ uint32_t v_min = MAX(2, MIN(20000, lp_lock_spin_time()))/2;
+ uint32_t v_max = 10 * v_min;
+
+ if (state->retry_msecs >= v_max) {
+ state->retry_msecs = v_max;
+ return;
+ }
+
+ state->retry_msecs += v_min;
+}
+
+static void smbd_smb2_lock_update_polling_msecs(
+ struct smbd_smb2_lock_state *state)
+{
+ /*
+ * The default lp_lock_spin_time() is 200ms.
+ *
+ * v_min is in the range of 0.002 to 20 secs
+ * (0.2 secs by default)
+ *
+ * v_max is in the range of 0.02 to 200 secs
+ * (2.0 secs by default)
+ *
+ * The typical steps are:
+ * 0.2, 0.4, 0.6, 0.8, ... 2.0
+ */
+ uint32_t v_min = MAX(2, MIN(20000, lp_lock_spin_time()));
+ uint32_t v_max = 10 * v_min;
+
+ if (state->polling_msecs >= v_max) {
+ state->polling_msecs = v_max;
+ return;
+ }
+
+ state->polling_msecs += v_min;
+}
+
+static void smbd_smb2_lock_try(struct tevent_req *req)
+{
+ struct smbd_smb2_lock_state *state = tevent_req_data(
+ req, struct smbd_smb2_lock_state);
+ struct share_mode_lock *lck = NULL;
+ uint16_t blocker_idx;
+ struct server_id blocking_pid = { 0 };
+ uint64_t blocking_smblctx;
+ NTSTATUS status;
+ struct tevent_req *subreq = NULL;
+ struct timeval endtime = { 0 };
+
+ lck = get_existing_share_mode_lock(
+ talloc_tos(), state->fsp->file_id);
+ if (tevent_req_nomem(lck, req)) {
+ return;
+ }
+
+ status = smbd_do_locks_try(
+ state->fsp,
+ state->lock_count,
+ state->locks,
+ &blocker_idx,
+ &blocking_pid,
+ &blocking_smblctx);
+ if (NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(lck);
+ tevent_req_done(req);
+ return;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
+ /*
+ * We got NT_STATUS_RETRY,
+ * we reset polling_msecs so that
+ * that the retries based on LOCK_NOT_GRANTED
+ * will later start with small intervals again.
+ */
+ state->polling_msecs = 0;
+
+ /*
+ * The backend wasn't able to decide yet.
+ * We need to wait even for non-blocking
+ * locks.
+ *
+ * The backend uses blocking_smblctx == UINT64_MAX
+ * to indicate that we should use retry timers.
+ *
+ * It uses blocking_smblctx == 0 to indicate
+ * it will use share_mode_wakeup_waiters()
+ * to wake us. Note that unrelated changes in
+ * locking.tdb may cause retries.
+ */
+
+ if (blocking_smblctx != UINT64_MAX) {
+ SMB_ASSERT(blocking_smblctx == 0);
+ goto setup_retry;
+ }
+
+ smbd_smb2_lock_update_retry_msecs(state);
+
+ DBG_DEBUG("Waiting for a backend decision. "
+ "Retry in %"PRIu32" msecs\n",
+ state->retry_msecs);
+
+ /*
+ * We completely ignore state->endtime here
+ * we we'll wait for a backend decision forever.
+ * If the backend is smart enough to implement
+ * some NT_STATUS_RETRY logic, it has to
+ * switch to any other status after in order
+ * to avoid waiting forever.
+ */
+ endtime = timeval_current_ofs_msec(state->retry_msecs);
+ goto setup_retry;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_FILE_LOCK_CONFLICT)) {
+ /*
+ * This is a bug and will be changed into an assert
+ * in future version. We should only
+ * ever get NT_STATUS_LOCK_NOT_GRANTED here!
+ */
+ static uint64_t _bug_count;
+ int _level = (_bug_count++ == 0) ? DBGLVL_ERR: DBGLVL_DEBUG;
+ DBG_PREFIX(_level, ("BUG: Got %s mapping to "
+ "NT_STATUS_LOCK_NOT_GRANTED\n",
+ nt_errstr(status)));
+ status = NT_STATUS_LOCK_NOT_GRANTED;
+ }
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_LOCK_NOT_GRANTED)) {
+ TALLOC_FREE(lck);
+ tevent_req_nterror(req, status);
+ return;
+ }
+ /*
+ * We got LOCK_NOT_GRANTED, make sure
+ * a following STATUS_RETRY will start
+ * with short intervals again.
+ */
+ state->retry_msecs = 0;
+
+ if (!state->blocking) {
+ TALLOC_FREE(lck);
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ if (blocking_smblctx == UINT64_MAX) {
+ smbd_smb2_lock_update_polling_msecs(state);
+
+ DBG_DEBUG("Blocked on a posix lock. Retry in %"PRIu32" msecs\n",
+ state->polling_msecs);
+
+ endtime = timeval_current_ofs_msec(state->polling_msecs);
+ }
+
+setup_retry:
+ DBG_DEBUG("Watching share mode lock\n");
+
+ subreq = share_mode_watch_send(
+ state, state->ev, lck, blocking_pid);
+ TALLOC_FREE(lck);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_lock_retry, req);
+
+ if (!timeval_is_zero(&endtime)) {
+ bool ok;
+
+ ok = tevent_req_set_endtime(subreq,
+ state->ev,
+ endtime);
+ if (!ok) {
+ tevent_req_oom(req);
+ return;
+ }
+ }
+}
+
+static void smbd_smb2_lock_retry(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smbd_smb2_lock_state *state = tevent_req_data(
+ req, struct smbd_smb2_lock_state);
+ NTSTATUS status;
+ bool ok;
+
+ /*
+ * Make sure we run as the user again
+ */
+ ok = change_to_user_and_service_by_fsp(state->fsp);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+
+ status = share_mode_watch_recv(subreq, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT)) {
+ /*
+ * This is just a trigger for a timed retry.
+ */
+ status = NT_STATUS_OK;
+ }
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ smbd_smb2_lock_try(req);
+}
+
+static NTSTATUS smbd_smb2_lock_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+/****************************************************************
+ Cancel an outstanding blocking lock request.
+*****************************************************************/
+
+static bool smbd_smb2_lock_cancel(struct tevent_req *req)
+{
+ struct smbd_smb2_request *smb2req = NULL;
+ struct smbd_smb2_lock_state *state = tevent_req_data(req,
+ struct smbd_smb2_lock_state);
+ if (!state) {
+ return false;
+ }
+
+ if (!state->smb2req) {
+ return false;
+ }
+
+ smb2req = state->smb2req;
+
+ /*
+ * If the request is canceled because of close, logoff or tdis
+ * the status is NT_STATUS_RANGE_NOT_LOCKED instead of
+ * NT_STATUS_CANCELLED.
+ */
+ if (state->fsp->fsp_flags.closing ||
+ !NT_STATUS_IS_OK(smb2req->session->status) ||
+ !NT_STATUS_IS_OK(smb2req->tcon->status)) {
+ tevent_req_nterror(req, NT_STATUS_RANGE_NOT_LOCKED);
+ return true;
+ }
+
+ tevent_req_nterror(req, NT_STATUS_CANCELLED);
+ return true;
+}
diff --git a/source3/smbd/smb2_negprot.c b/source3/smbd/smb2_negprot.c
new file mode 100644
index 0000000..8c50fc9
--- /dev/null
+++ b/source3/smbd/smb2_negprot.c
@@ -0,0 +1,1229 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../libcli/smb/smb2_negotiate_context.h"
+#include "../lib/tsocket/tsocket.h"
+#include "../librpc/ndr/libndr.h"
+#include "../libcli/smb/smb_signing.h"
+#include "auth.h"
+#include "auth/gensec/gensec.h"
+#include "lib/util/string_wrappers.h"
+#include "source3/lib/substitute.h"
+#ifdef HAVE_VALGRIND_CALLGRIND_H
+#include <valgrind/callgrind.h>
+#endif /* HAVE_VALGRIND_CALLGRIND_H */
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+/*
+ * this is the entry point if SMB2 is selected via
+ * the SMB negprot and the given dialect.
+ */
+static NTSTATUS reply_smb20xx(struct smb_request *req, uint16_t dialect)
+{
+ uint8_t *smb2_inpdu;
+ uint8_t *smb2_hdr;
+ uint8_t *smb2_body;
+ uint8_t *smb2_dyn;
+ size_t len = SMB2_HDR_BODY + 0x24 + 2;
+
+ smb2_inpdu = talloc_zero_array(talloc_tos(), uint8_t, len);
+ if (smb2_inpdu == NULL) {
+ DEBUG(0, ("Could not push spnego blob\n"));
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return NT_STATUS_NO_MEMORY;
+ }
+ smb2_hdr = smb2_inpdu;
+ smb2_body = smb2_hdr + SMB2_HDR_BODY;
+ smb2_dyn = smb2_body + 0x24;
+
+ SIVAL(smb2_hdr, SMB2_HDR_PROTOCOL_ID, SMB2_MAGIC);
+ SIVAL(smb2_hdr, SMB2_HDR_LENGTH, SMB2_HDR_BODY);
+
+ SSVAL(smb2_body, 0x00, 0x0024); /* struct size */
+ SSVAL(smb2_body, 0x02, 0x0001); /* dialect count */
+
+ SSVAL(smb2_dyn, 0x00, dialect);
+
+ req->outbuf = NULL;
+
+ return smbd_smb2_process_negprot(req->xconn, 0, smb2_inpdu, len);
+}
+
+/*
+ * this is the entry point if SMB2 is selected via
+ * the SMB negprot and the "SMB 2.002" dialect.
+ */
+NTSTATUS reply_smb2002(struct smb_request *req, uint16_t choice)
+{
+ return reply_smb20xx(req, SMB2_DIALECT_REVISION_202);
+}
+
+/*
+ * this is the entry point if SMB2 is selected via
+ * the SMB negprot and the "SMB 2.???" dialect.
+ */
+NTSTATUS reply_smb20ff(struct smb_request *req, uint16_t choice)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ xconn->smb2.allow_2ff = true;
+ return reply_smb20xx(req, SMB2_DIALECT_REVISION_2FF);
+}
+
+enum protocol_types smbd_smb2_protocol_dialect_match(const uint8_t *indyn,
+ const int dialect_count,
+ uint16_t *dialect)
+{
+ struct {
+ enum protocol_types proto;
+ uint16_t dialect;
+ } pd[] = {
+ { PROTOCOL_SMB3_11, SMB3_DIALECT_REVISION_311 },
+ { PROTOCOL_SMB3_02, SMB3_DIALECT_REVISION_302 },
+ { PROTOCOL_SMB3_00, SMB3_DIALECT_REVISION_300 },
+ { PROTOCOL_SMB2_10, SMB2_DIALECT_REVISION_210 },
+ { PROTOCOL_SMB2_02, SMB2_DIALECT_REVISION_202 },
+ };
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(pd); i ++) {
+ int c = 0;
+
+ if (lp_server_max_protocol() < pd[i].proto) {
+ continue;
+ }
+ if (lp_server_min_protocol() > pd[i].proto) {
+ continue;
+ }
+
+ for (c = 0; c < dialect_count; c++) {
+ *dialect = SVAL(indyn, c*2);
+ if (*dialect == pd[i].dialect) {
+ return pd[i].proto;
+ }
+ }
+ }
+
+ return PROTOCOL_NONE;
+}
+
+static NTSTATUS smb2_negotiate_context_process_posix(
+ const struct smb2_negotiate_contexts *in_c,
+ bool *posix)
+{
+ struct smb2_negotiate_context *in_posix = NULL;
+ const uint8_t *inbuf = NULL;
+ size_t inbuflen;
+ bool posix_found = false;
+ size_t ofs;
+ int cmp;
+
+ *posix = false;
+
+ if (!lp_smb3_unix_extensions(GLOBAL_SECTION_SNUM)) {
+ return NT_STATUS_OK;
+ }
+
+ in_posix = smb2_negotiate_context_find(in_c,
+ SMB2_POSIX_EXTENSIONS_AVAILABLE);
+ if (in_posix == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ inbuf = in_posix->data.data;
+ inbuflen = in_posix->data.length;
+
+ /*
+ * For now the server only supports one variant.
+ * Check it's the right one.
+ */
+ if ((inbuflen % 16) != 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ SMB_ASSERT(strlen(SMB2_CREATE_TAG_POSIX) == 16);
+
+ for (ofs = 0; ofs < inbuflen; ofs += 16) {
+ cmp = memcmp(inbuf+ofs, SMB2_CREATE_TAG_POSIX, 16);
+ if (cmp == 0) {
+ posix_found = true;
+ break;
+ }
+ }
+
+ if (!posix_found) {
+ DBG_DEBUG("Client requested unknown SMB3 Unix extensions:\n");
+ dump_data(10, inbuf, inbuflen);
+ return NT_STATUS_OK;
+ }
+
+ DBG_DEBUG("Client requested SMB3 Unix extensions\n");
+ *posix = true;
+ return NT_STATUS_OK;
+}
+
+struct smbd_smb2_request_process_negprot_state {
+ struct smbd_smb2_request *req;
+ DATA_BLOB outbody;
+ DATA_BLOB outdyn;
+};
+
+static void smbd_smb2_request_process_negprot_mc_done(struct tevent_req *subreq);
+
+NTSTATUS smbd_smb2_request_process_negprot(struct smbd_smb2_request *req)
+{
+ struct smbd_smb2_request_process_negprot_state *state = NULL;
+ struct smbXsrv_connection *xconn = req->xconn;
+ struct tevent_req *subreq = NULL;
+ NTSTATUS status;
+ const uint8_t *inbody;
+ const uint8_t *indyn = NULL;
+ DATA_BLOB outbody;
+ DATA_BLOB outdyn;
+ DATA_BLOB negprot_spnego_blob;
+ uint16_t security_offset;
+ DATA_BLOB security_buffer;
+ size_t expected_dyn_size = 0;
+ size_t c;
+ uint16_t security_mode;
+ uint16_t dialect_count;
+ uint16_t in_security_mode;
+ uint32_t in_capabilities;
+ DATA_BLOB in_guid_blob;
+ struct GUID in_guid;
+ struct smb2_negotiate_contexts in_c = { .num_contexts = 0, };
+ struct smb2_negotiate_context *in_preauth = NULL;
+ struct smb2_negotiate_context *in_cipher = NULL;
+ struct smb2_negotiate_context *in_sign_algo = NULL;
+ struct smb2_negotiate_contexts out_c = { .num_contexts = 0, };
+ const struct smb311_capabilities default_smb3_capabilities =
+ smb311_capabilities_parse("server",
+ lp_server_smb3_signing_algorithms(),
+ lp_server_smb3_encryption_algorithms());
+ DATA_BLOB out_negotiate_context_blob = data_blob_null;
+ uint32_t out_negotiate_context_offset = 0;
+ uint16_t out_negotiate_context_count = 0;
+ uint16_t dialect = 0;
+ uint32_t capabilities;
+ DATA_BLOB out_guid_blob;
+ struct GUID out_guid;
+ enum protocol_types protocol = PROTOCOL_NONE;
+ uint32_t max_limit;
+ uint32_t max_trans = lp_smb2_max_trans();
+ uint32_t max_read = lp_smb2_max_read();
+ uint32_t max_write = lp_smb2_max_write();
+ NTTIME now = timeval_to_nttime(&req->request_time);
+ bool posix = false;
+ bool ok;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x24);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ dialect_count = SVAL(inbody, 0x02);
+
+ in_security_mode = SVAL(inbody, 0x04);
+ in_capabilities = IVAL(inbody, 0x08);
+ in_guid_blob = data_blob_const(inbody + 0x0C, 16);
+
+ if (dialect_count == 0) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ status = GUID_from_ndr_blob(&in_guid_blob, &in_guid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ expected_dyn_size = dialect_count * 2;
+ if (SMBD_SMB2_IN_DYN_LEN(req) < expected_dyn_size) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+ indyn = SMBD_SMB2_IN_DYN_PTR(req);
+
+ protocol = smbd_smb2_protocol_dialect_match(indyn,
+ dialect_count,
+ &dialect);
+
+ for (c=0; protocol == PROTOCOL_NONE && c < dialect_count; c++) {
+ if (lp_server_max_protocol() < PROTOCOL_SMB2_10) {
+ break;
+ }
+
+ dialect = SVAL(indyn, c*2);
+ if (dialect == SMB2_DIALECT_REVISION_2FF) {
+ if (xconn->smb2.allow_2ff) {
+ xconn->smb2.allow_2ff = false;
+ protocol = PROTOCOL_SMB2_10;
+ break;
+ }
+ }
+ }
+
+ if (protocol == PROTOCOL_NONE) {
+ return smbd_smb2_request_error(req, NT_STATUS_NOT_SUPPORTED);
+ }
+
+ if (protocol >= PROTOCOL_SMB3_11) {
+ uint32_t in_negotiate_context_offset = 0;
+ uint16_t in_negotiate_context_count = 0;
+ DATA_BLOB in_negotiate_context_blob = data_blob_null;
+ size_t ofs;
+
+ in_negotiate_context_offset = IVAL(inbody, 0x1C);
+ in_negotiate_context_count = SVAL(inbody, 0x20);
+
+ ofs = SMB2_HDR_BODY;
+ ofs += SMBD_SMB2_IN_BODY_LEN(req);
+ ofs += expected_dyn_size;
+ if ((ofs % 8) != 0) {
+ ofs += 8 - (ofs % 8);
+ }
+
+ if (in_negotiate_context_offset != ofs) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+
+ ofs -= SMB2_HDR_BODY;
+ ofs -= SMBD_SMB2_IN_BODY_LEN(req);
+
+ if (SMBD_SMB2_IN_DYN_LEN(req) < ofs) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+
+ in_negotiate_context_blob = data_blob_const(indyn,
+ SMBD_SMB2_IN_DYN_LEN(req));
+
+ in_negotiate_context_blob.data += ofs;
+ in_negotiate_context_blob.length -= ofs;
+
+ status = smb2_negotiate_context_parse(req,
+ in_negotiate_context_blob,
+ in_negotiate_context_count,
+ &in_c);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ status = smb2_negotiate_context_process_posix(&in_c, &posix);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ }
+
+ if ((dialect != SMB2_DIALECT_REVISION_2FF) &&
+ (protocol >= PROTOCOL_SMB2_10) &&
+ !GUID_all_zero(&in_guid))
+ {
+ ok = remote_arch_cache_update(&in_guid);
+ if (!ok) {
+ return smbd_smb2_request_error(
+ req, NT_STATUS_UNSUCCESSFUL);
+ }
+ }
+
+ switch (get_remote_arch()) {
+ case RA_VISTA:
+ case RA_SAMBA:
+ case RA_CIFSFS:
+ case RA_OSX:
+ break;
+ default:
+ set_remote_arch(RA_VISTA);
+ break;
+ }
+
+ {
+ fstring proto;
+ fstr_sprintf(proto,
+ "SMB%X_%02X",
+ (dialect >> 8) & 0xFF, dialect & 0xFF);
+ set_remote_proto(proto);
+ DEBUG(3,("Selected protocol %s\n", proto));
+ }
+
+ reload_services(req->sconn, conn_snum_used, true);
+
+ in_preauth = smb2_negotiate_context_find(&in_c,
+ SMB2_PREAUTH_INTEGRITY_CAPABILITIES);
+ if (protocol >= PROTOCOL_SMB3_11 && in_preauth == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+ in_cipher = smb2_negotiate_context_find(&in_c,
+ SMB2_ENCRYPTION_CAPABILITIES);
+ in_sign_algo = smb2_negotiate_context_find(&in_c,
+ SMB2_SIGNING_CAPABILITIES);
+
+ /* negprot_spnego() returns the server guid in the first 16 bytes */
+ negprot_spnego_blob = negprot_spnego(req, xconn);
+ if (negprot_spnego_blob.data == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+
+ if (negprot_spnego_blob.length < 16) {
+ return smbd_smb2_request_error(req, NT_STATUS_INTERNAL_ERROR);
+ }
+
+ security_mode = SMB2_NEGOTIATE_SIGNING_ENABLED;
+ if (xconn->smb2.signing_mandatory) {
+ security_mode |= SMB2_NEGOTIATE_SIGNING_REQUIRED;
+ }
+
+ capabilities = 0;
+ if (lp_host_msdfs()) {
+ capabilities |= SMB2_CAP_DFS;
+ }
+
+ if (protocol >= PROTOCOL_SMB2_10 &&
+ lp_smb2_leases() &&
+ lp_oplocks(GLOBAL_SECTION_SNUM) &&
+ !lp_kernel_oplocks(GLOBAL_SECTION_SNUM))
+ {
+ capabilities |= SMB2_CAP_LEASING;
+ }
+
+ if ((protocol >= PROTOCOL_SMB3_00) &&
+ (lp_server_smb_encrypt(-1) != SMB_ENCRYPTION_OFF) &&
+ (in_capabilities & SMB2_CAP_ENCRYPTION)) {
+ capabilities |= SMB2_CAP_ENCRYPTION;
+ }
+
+ /*
+ * 0x10000 (65536) is the maximum allowed message size
+ * for SMB 2.0
+ */
+ max_limit = 0x10000;
+
+ if (protocol >= PROTOCOL_SMB2_10) {
+ int p = 0;
+
+ if (tsocket_address_is_inet(req->sconn->local_address, "ip")) {
+ p = tsocket_address_inet_port(req->sconn->local_address);
+ }
+
+ /* largeMTU is not supported over NBT (tcp port 139) */
+ if (p != NBT_SMB_PORT) {
+ capabilities |= SMB2_CAP_LARGE_MTU;
+ xconn->smb2.credits.multicredit = true;
+
+ /*
+ * We allow up to almost 16MB.
+ *
+ * The maximum PDU size is 0xFFFFFF (16776960)
+ * and we need some space for the header.
+ */
+ max_limit = 0xFFFF00;
+ }
+ }
+
+ /*
+ * the defaults are 8MB, but we'll limit this to max_limit based on
+ * the dialect (64kb for SMB 2.0, 8MB for SMB >= 2.1 with LargeMTU)
+ *
+ * user configured values exceeding the limits will be overwritten,
+ * only smaller values will be accepted
+ */
+
+ max_trans = MIN(max_limit, lp_smb2_max_trans());
+ max_read = MIN(max_limit, lp_smb2_max_read());
+ max_write = MIN(max_limit, lp_smb2_max_write());
+
+ if (in_preauth != NULL) {
+ size_t needed = 4;
+ uint16_t hash_count;
+ uint16_t salt_length;
+ uint16_t selected_preauth = 0;
+ const uint8_t *p;
+ uint8_t buf[38];
+ size_t i;
+
+ if (in_preauth->data.length < needed) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+
+ hash_count = SVAL(in_preauth->data.data, 0);
+ salt_length = SVAL(in_preauth->data.data, 2);
+
+ if (hash_count == 0) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+
+ p = in_preauth->data.data + needed;
+ needed += hash_count * 2;
+ needed += salt_length;
+
+ if (in_preauth->data.length < needed) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+
+ for (i=0; i < hash_count; i++) {
+ uint16_t v;
+
+ v = SVAL(p, 0);
+ p += 2;
+
+ if (v == SMB2_PREAUTH_INTEGRITY_SHA512) {
+ selected_preauth = v;
+ break;
+ }
+ }
+
+ if (selected_preauth == 0) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_SMB_NO_PREAUTH_INTEGRITY_HASH_OVERLAP);
+ }
+
+ SSVAL(buf, 0, 1); /* HashAlgorithmCount */
+ SSVAL(buf, 2, 32); /* SaltLength */
+ SSVAL(buf, 4, selected_preauth);
+ generate_random_buffer(buf + 6, 32);
+
+ status = smb2_negotiate_context_add(
+ req,
+ &out_c,
+ SMB2_PREAUTH_INTEGRITY_CAPABILITIES,
+ buf,
+ sizeof(buf));
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ req->preauth = &req->xconn->smb2.preauth;
+ }
+
+ if (protocol >= PROTOCOL_SMB3_00) {
+ xconn->smb2.server.sign_algo = SMB2_SIGNING_AES128_CMAC;
+ } else {
+ xconn->smb2.server.sign_algo = SMB2_SIGNING_HMAC_SHA256;
+ }
+
+ if ((capabilities & SMB2_CAP_ENCRYPTION) && (in_cipher != NULL)) {
+ const struct smb3_encryption_capabilities *srv_ciphers =
+ &default_smb3_capabilities.encryption;
+ uint16_t srv_preferred_idx = UINT16_MAX;
+ size_t needed = 2;
+ uint16_t cipher_count;
+ const uint8_t *p;
+ uint8_t buf[4];
+ size_t i;
+
+ capabilities &= ~SMB2_CAP_ENCRYPTION;
+
+ if (in_cipher->data.length < needed) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+
+ cipher_count = SVAL(in_cipher->data.data, 0);
+ if (cipher_count == 0) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+
+ p = in_cipher->data.data + needed;
+ needed += cipher_count * 2;
+
+ if (in_cipher->data.length < needed) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+
+ for (i=0; i < cipher_count; i++) {
+ uint16_t si;
+ uint16_t v;
+
+ v = SVAL(p, 0);
+ p += 2;
+
+ for (si = 0; si < srv_ciphers->num_algos; si++) {
+ if (srv_ciphers->algos[si] != v) {
+ continue;
+ }
+
+ /*
+ * The server ciphers are listed
+ * with the lowest idx being preferred.
+ */
+ if (si < srv_preferred_idx) {
+ srv_preferred_idx = si;
+ }
+ break;
+ }
+ }
+
+ if (srv_preferred_idx != UINT16_MAX) {
+ xconn->smb2.server.cipher =
+ srv_ciphers->algos[srv_preferred_idx];
+ }
+
+ SSVAL(buf, 0, 1); /* ChiperCount */
+ SSVAL(buf, 2, xconn->smb2.server.cipher);
+
+ status = smb2_negotiate_context_add(
+ req,
+ &out_c,
+ SMB2_ENCRYPTION_CAPABILITIES,
+ buf,
+ sizeof(buf));
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ }
+
+ if (capabilities & SMB2_CAP_ENCRYPTION) {
+ xconn->smb2.server.cipher = SMB2_ENCRYPTION_AES128_CCM;
+ }
+
+ if (in_sign_algo != NULL) {
+ const struct smb3_signing_capabilities *srv_sign_algos =
+ &default_smb3_capabilities.signing;
+ uint16_t srv_preferred_idx = UINT16_MAX;
+ size_t needed = 2;
+ uint16_t sign_algo_count;
+ const uint8_t *p;
+ size_t i;
+
+ if (in_sign_algo->data.length < needed) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+
+ sign_algo_count = SVAL(in_sign_algo->data.data, 0);
+ if (sign_algo_count == 0) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+
+ p = in_sign_algo->data.data + needed;
+ needed += sign_algo_count * 2;
+
+ if (in_sign_algo->data.length < needed) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+
+ for (i=0; i < sign_algo_count; i++) {
+ uint16_t si;
+ uint16_t v;
+
+ v = SVAL(p, 0);
+ p += 2;
+
+ for (si = 0; si < srv_sign_algos->num_algos; si++) {
+ if (srv_sign_algos->algos[si] != v) {
+ continue;
+ }
+
+ /*
+ * The server sign_algos are listed
+ * with the lowest idx being preferred.
+ */
+ if (si < srv_preferred_idx) {
+ srv_preferred_idx = si;
+ }
+ break;
+ }
+ }
+
+ /*
+ * If we found a match announce it
+ * otherwise we'll keep the default
+ * of SMB2_SIGNING_AES128_CMAC
+ */
+ if (srv_preferred_idx != UINT16_MAX) {
+ uint8_t buf[4];
+
+ xconn->smb2.server.sign_algo =
+ srv_sign_algos->algos[srv_preferred_idx];
+
+ SSVAL(buf, 0, 1); /* SigningAlgorithmCount */
+ SSVAL(buf, 2, xconn->smb2.server.sign_algo);
+
+ status = smb2_negotiate_context_add(
+ req,
+ &out_c,
+ SMB2_SIGNING_CAPABILITIES,
+ buf,
+ sizeof(buf));
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ }
+ }
+
+ status = smb311_capabilities_check(&default_smb3_capabilities,
+ "smb2srv_negprot",
+ DBGLVL_NOTICE,
+ NT_STATUS_INVALID_PARAMETER,
+ "server",
+ protocol,
+ xconn->smb2.server.sign_algo,
+ xconn->smb2.server.cipher);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ if (protocol >= PROTOCOL_SMB3_00 &&
+ xconn->client->server_multi_channel_enabled)
+ {
+ if (in_capabilities & SMB2_CAP_MULTI_CHANNEL) {
+ capabilities |= SMB2_CAP_MULTI_CHANNEL;
+ }
+ }
+
+ security_offset = SMB2_HDR_BODY + 0x40;
+
+#if 1
+ /* Try SPNEGO auth... */
+ security_buffer = data_blob_const(negprot_spnego_blob.data + 16,
+ negprot_spnego_blob.length - 16);
+#else
+ /* for now we want raw NTLMSSP */
+ security_buffer = data_blob_const(NULL, 0);
+#endif
+
+ if (posix) {
+ /* Client correctly negotiated SMB2 unix extensions. */
+ const uint8_t *buf = (const uint8_t *)SMB2_CREATE_TAG_POSIX;
+ status = smb2_negotiate_context_add(
+ req,
+ &out_c,
+ SMB2_POSIX_EXTENSIONS_AVAILABLE,
+ buf,
+ 16);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ xconn->smb2.server.posix_extensions_negotiated = true;
+ }
+
+ if (out_c.num_contexts != 0) {
+ status = smb2_negotiate_context_push(req,
+ &out_negotiate_context_blob,
+ out_c);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ }
+
+ if (out_negotiate_context_blob.length != 0) {
+ static const uint8_t zeros[8];
+ size_t pad = 0;
+ size_t ofs;
+
+ outdyn = data_blob_dup_talloc(req, security_buffer);
+ if (outdyn.length != security_buffer.length) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_NO_MEMORY);
+ }
+
+ ofs = security_offset + security_buffer.length;
+ if ((ofs % 8) != 0) {
+ pad = 8 - (ofs % 8);
+ }
+ ofs += pad;
+
+ ok = data_blob_append(req, &outdyn, zeros, pad);
+ if (!ok) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_NO_MEMORY);
+ }
+
+ ok = data_blob_append(req, &outdyn,
+ out_negotiate_context_blob.data,
+ out_negotiate_context_blob.length);
+ if (!ok) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_NO_MEMORY);
+ }
+
+ out_negotiate_context_offset = ofs;
+ out_negotiate_context_count = out_c.num_contexts;
+ } else {
+ outdyn = security_buffer;
+ }
+
+ out_guid_blob = data_blob_const(negprot_spnego_blob.data, 16);
+ status = GUID_from_ndr_blob(&out_guid_blob, &out_guid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ outbody = smbd_smb2_generate_outbody(req, 0x40);
+ if (outbody.data == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+
+ SSVAL(outbody.data, 0x00, 0x40 + 1); /* struct size */
+ SSVAL(outbody.data, 0x02,
+ security_mode); /* security mode */
+ SSVAL(outbody.data, 0x04, dialect); /* dialect revision */
+ SSVAL(outbody.data, 0x06,
+ out_negotiate_context_count); /* reserved/NegotiateContextCount */
+ memcpy(outbody.data + 0x08,
+ out_guid_blob.data, 16); /* server guid */
+ SIVAL(outbody.data, 0x18,
+ capabilities); /* capabilities */
+ SIVAL(outbody.data, 0x1C, max_trans); /* max transact size */
+ SIVAL(outbody.data, 0x20, max_read); /* max read size */
+ SIVAL(outbody.data, 0x24, max_write); /* max write size */
+ SBVAL(outbody.data, 0x28, now); /* system time */
+ SBVAL(outbody.data, 0x30, 0); /* server start time */
+ SSVAL(outbody.data, 0x38,
+ security_offset); /* security buffer offset */
+ SSVAL(outbody.data, 0x3A,
+ security_buffer.length); /* security buffer length */
+ SIVAL(outbody.data, 0x3C,
+ out_negotiate_context_offset); /* reserved/NegotiateContextOffset */
+
+ req->sconn->using_smb2 = true;
+
+ if (dialect == SMB2_DIALECT_REVISION_2FF) {
+ return smbd_smb2_request_done(req, outbody, &outdyn);
+ }
+
+ status = smbXsrv_connection_init_tables(xconn, protocol);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ xconn->smb2.client.capabilities = in_capabilities;
+ xconn->smb2.client.security_mode = in_security_mode;
+ xconn->smb2.client.guid = in_guid;
+ xconn->smb2.client.num_dialects = dialect_count;
+ xconn->smb2.client.dialects = talloc_array(xconn,
+ uint16_t,
+ dialect_count);
+ if (xconn->smb2.client.dialects == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ for (c=0; c < dialect_count; c++) {
+ xconn->smb2.client.dialects[c] = SVAL(indyn, c*2);
+ }
+
+ xconn->smb2.server.capabilities = capabilities;
+ xconn->smb2.server.security_mode = security_mode;
+ xconn->smb2.server.guid = out_guid;
+ xconn->smb2.server.dialect = dialect;
+ xconn->smb2.server.max_trans = max_trans;
+ xconn->smb2.server.max_read = max_read;
+ xconn->smb2.server.max_write = max_write;
+
+ if (xconn->protocol < PROTOCOL_SMB2_10) {
+ /*
+ * SMB2_02 doesn't support client guids
+ */
+ return smbd_smb2_request_done(req, outbody, &outdyn);
+ }
+
+ if (!xconn->client->server_multi_channel_enabled) {
+ /*
+ * Only deal with the client guid database
+ * if multi-channel is enabled.
+ *
+ * But we still need to setup
+ * xconn->client->global->client_guid to
+ * the correct value.
+ */
+ xconn->client->global->client_guid =
+ xconn->smb2.client.guid;
+ return smbd_smb2_request_done(req, outbody, &outdyn);
+ }
+
+ if (xconn->smb2.client.guid_verified) {
+ /*
+ * The connection was passed from another
+ * smbd process.
+ */
+ return smbd_smb2_request_done(req, outbody, &outdyn);
+ }
+
+ state = talloc_zero(req, struct smbd_smb2_request_process_negprot_state);
+ if (state == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ *state = (struct smbd_smb2_request_process_negprot_state) {
+ .req = req,
+ .outbody = outbody,
+ .outdyn = outdyn,
+ };
+
+ subreq = smb2srv_client_mc_negprot_send(state,
+ req->xconn->client->raw_ev_ctx,
+ req);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq,
+ smbd_smb2_request_process_negprot_mc_done,
+ state);
+ return NT_STATUS_OK;
+}
+
+static void smbd_smb2_request_process_negprot_mc_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request_process_negprot_state *state =
+ tevent_req_callback_data(subreq,
+ struct smbd_smb2_request_process_negprot_state);
+ struct smbd_smb2_request *req = state->req;
+ struct smbXsrv_connection *xconn = req->xconn;
+ NTSTATUS status;
+
+ status = smb2srv_client_mc_negprot_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_MESSAGE_RETRIEVED)) {
+ /*
+ * The connection was passed to another process
+ *
+ * We mark the error as NT_STATUS_CONNECTION_IN_USE,
+ * in order to indicate to low level code if
+ * ctdbd_unregister_ips() or ctdbd_passed_ips()
+ * is more useful.
+ */
+ smbXsrv_connection_disconnect_transport(xconn,
+ NT_STATUS_CONNECTION_IN_USE);
+ smbd_server_connection_terminate(xconn,
+ "passed connection");
+ /*
+ * smbd_server_connection_terminate() should not return!
+ */
+ smb_panic(__location__);
+ return;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ status = smbd_smb2_request_error(req, status);
+ if (NT_STATUS_IS_OK(status)) {
+ return;
+ }
+
+ /*
+ * The connection was passed to another process
+ */
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
+ /*
+ * smbd_server_connection_terminate() should not return!
+ */
+ smb_panic(__location__);
+ return;
+ }
+
+ /*
+ * We're the first connection...
+ */
+ status = smbd_smb2_request_done(req, state->outbody, &state->outdyn);
+ if (NT_STATUS_IS_OK(status)) {
+ /*
+ * This allows us to support starting smbd under
+ * callgrind and only start the overhead and
+ * instrumentation after the SMB2 negprot,
+ * this allows us to profile only useful
+ * stuff and not all the smbd startup, forking
+ * and multichannel handling.
+ *
+ * valgrind --tool=callgrind --instr-atstart=no smbd
+ */
+#ifdef CALLGRIND_START_INSTRUMENTATION
+ CALLGRIND_START_INSTRUMENTATION;
+#endif
+ return;
+ }
+
+ /*
+ * The connection was passed to another process
+ */
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
+ /*
+ * smbd_server_connection_terminate() should not return!
+ */
+ smb_panic(__location__);
+ return;
+}
+
+/****************************************************************************
+ Generate the spnego negprot reply blob. Return the number of bytes used.
+****************************************************************************/
+
+DATA_BLOB negprot_spnego(TALLOC_CTX *ctx, struct smbXsrv_connection *xconn)
+{
+ DATA_BLOB blob = data_blob_null;
+ DATA_BLOB blob_out = data_blob_null;
+ nstring dos_name;
+ fstring unix_name;
+ NTSTATUS status;
+#ifdef DEVELOPER
+ size_t slen;
+#endif
+ struct gensec_security *gensec_security;
+
+ /* See if we can get an SPNEGO blob */
+ status = auth_generic_prepare(talloc_tos(),
+ xconn->remote_address,
+ xconn->local_address,
+ "SMB",
+ &gensec_security);
+
+ /*
+ * Despite including it above, there is no need to set a
+ * remote address or similar as we are just interested in the
+ * SPNEGO blob, we never keep this context.
+ */
+
+ if (NT_STATUS_IS_OK(status)) {
+ status = gensec_start_mech_by_oid(gensec_security, GENSEC_OID_SPNEGO);
+ if (NT_STATUS_IS_OK(status)) {
+ status = gensec_update(gensec_security, ctx,
+ data_blob_null, &blob);
+ /* If we get the list of OIDs, the 'OK' answer
+ * is NT_STATUS_MORE_PROCESSING_REQUIRED */
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ DEBUG(0, ("Failed to start SPNEGO handler for negprot OID list!\n"));
+ blob = data_blob_null;
+ }
+ }
+ TALLOC_FREE(gensec_security);
+ }
+
+#if defined(WITH_SMB1SERVER)
+ xconn->smb1.negprot.spnego = true;
+#endif
+
+ /* strangely enough, NT does not sent the single OID NTLMSSP when
+ not a ADS member, it sends no OIDs at all
+
+ OLD COMMENT : "we can't do this until we teach our session setup parser to know
+ about raw NTLMSSP (clients send no ASN.1 wrapping if we do this)"
+
+ Our sessionsetup code now handles raw NTLMSSP connects, so we can go
+ back to doing what W2K3 does here. This is needed to make PocketPC 2003
+ CIFS connections work with SPNEGO. See bugzilla bugs #1828 and #3133
+ for details. JRA.
+
+ */
+
+ if (blob.length == 0 || blob.data == NULL) {
+ return data_blob_null;
+ }
+
+ blob_out = data_blob_talloc(ctx, NULL, 16 + blob.length);
+ if (blob_out.data == NULL) {
+ data_blob_free(&blob);
+ return data_blob_null;
+ }
+
+ memset(blob_out.data, '\0', 16);
+
+ checked_strlcpy(unix_name, lp_netbios_name(), sizeof(unix_name));
+ (void)strlower_m(unix_name);
+ push_ascii_nstring(dos_name, unix_name);
+ strlcpy((char *)blob_out.data, dos_name, 17);
+
+#ifdef DEVELOPER
+ /* Fix valgrind 'uninitialized bytes' issue. */
+ slen = strlen(dos_name);
+ if (slen < 16) {
+ memset(blob_out.data+slen, '\0', 16 - slen);
+ }
+#endif
+
+ memcpy(&blob_out.data[16], blob.data, blob.length);
+
+ data_blob_free(&blob);
+
+ return blob_out;
+}
+
+/*
+ * MS-CIFS, 2.2.4.52.2 SMB_COM_NEGOTIATE Response:
+ * If the server does not support any of the listed dialects, it MUST return a
+ * DialectIndex of 0XFFFF
+ */
+#define NO_PROTOCOL_CHOSEN 0xffff
+
+#define PROT_SMB_2_002 0x1000
+#define PROT_SMB_2_FF 0x2000
+
+/* List of supported SMB1 protocols, most desired first.
+ * This is for enabling multi-protocol negotiation in SMB2 when SMB1
+ * is disabled.
+ */
+static const struct {
+ const char *proto_name;
+ const char *short_name;
+ NTSTATUS (*proto_reply_fn)(struct smb_request *req, uint16_t choice);
+ int protocol_level;
+} supported_protocols[] = {
+ {"SMB 2.???", "SMB2_FF", reply_smb20ff, PROTOCOL_SMB2_10},
+ {"SMB 2.002", "SMB2_02", reply_smb2002, PROTOCOL_SMB2_02},
+ {NULL,NULL,NULL,0},
+};
+
+/****************************************************************************
+ Reply to a negprot.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+NTSTATUS smb2_multi_protocol_reply_negprot(struct smb_request *req)
+{
+ size_t choice = 0;
+ bool choice_set = false;
+ int protocol;
+ const char *p;
+ int num_cliprotos;
+ char **cliprotos;
+ size_t i;
+ size_t converted_size;
+ struct smbXsrv_connection *xconn = req->xconn;
+ struct smbd_server_connection *sconn = req->sconn;
+ int max_proto;
+ int min_proto;
+ NTSTATUS status;
+
+ START_PROFILE(SMBnegprot);
+
+ if (req->buflen == 0) {
+ DEBUG(0, ("negprot got no protocols\n"));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBnegprot);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (req->buf[req->buflen-1] != '\0') {
+ DEBUG(0, ("negprot protocols not 0-terminated\n"));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBnegprot);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ p = (const char *)req->buf + 1;
+
+ num_cliprotos = 0;
+ cliprotos = NULL;
+
+ while (smbreq_bufrem(req, p) > 0) {
+
+ char **tmp;
+
+ tmp = talloc_realloc(talloc_tos(), cliprotos, char *,
+ num_cliprotos+1);
+ if (tmp == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ TALLOC_FREE(cliprotos);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBnegprot);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ cliprotos = tmp;
+
+ if (!pull_ascii_talloc(cliprotos, &cliprotos[num_cliprotos], p,
+ &converted_size)) {
+ DEBUG(0, ("pull_ascii_talloc failed\n"));
+ TALLOC_FREE(cliprotos);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBnegprot);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(3, ("Requested protocol [%s]\n",
+ cliprotos[num_cliprotos]));
+
+ num_cliprotos += 1;
+ p += strlen(p) + 2;
+ }
+
+ /* possibly reload - change of architecture */
+ reload_services(sconn, conn_snum_used, true);
+
+ /*
+ * Anything higher than PROTOCOL_SMB2_10 still
+ * needs to go via "SMB 2.???", which is marked
+ * as PROTOCOL_SMB2_10.
+ *
+ * The real negotiation happens via reply_smb20ff()
+ * using SMB2 Negotiation.
+ */
+ max_proto = lp_server_max_protocol();
+ if (max_proto > PROTOCOL_SMB2_10) {
+ max_proto = PROTOCOL_SMB2_10;
+ }
+ min_proto = lp_server_min_protocol();
+ if (min_proto > PROTOCOL_SMB2_10) {
+ min_proto = PROTOCOL_SMB2_10;
+ }
+
+ /* Check for protocols, most desirable first */
+ for (protocol = 0; supported_protocols[protocol].proto_name; protocol++) {
+ i = 0;
+ if ((supported_protocols[protocol].protocol_level <= max_proto) &&
+ (supported_protocols[protocol].protocol_level >= min_proto))
+ while (i < num_cliprotos) {
+ if (strequal(cliprotos[i],supported_protocols[protocol].proto_name)) {
+ choice = i;
+ choice_set = true;
+ }
+ i++;
+ }
+ if (choice_set) {
+ break;
+ }
+ }
+
+ if (!choice_set) {
+ bool ok;
+
+ DBG_NOTICE("No protocol supported !\n");
+ reply_smb1_outbuf(req, 1, 0);
+ SSVAL(req->outbuf, smb_vwv0, NO_PROTOCOL_CHOSEN);
+
+ ok = smb1_srv_send(xconn, (char *)req->outbuf, false, 0, false);
+ if (!ok) {
+ DBG_NOTICE("smb1_srv_send failed\n");
+ }
+ exit_server_cleanly("no protocol supported\n");
+ }
+
+ set_remote_proto(supported_protocols[protocol].short_name);
+ reload_services(sconn, conn_snum_used, true);
+ status = supported_protocols[protocol].proto_reply_fn(req, choice);
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_server_cleanly("negprot function failed\n");
+ }
+
+ DEBUG(3,("Selected protocol %s\n",supported_protocols[protocol].proto_name));
+
+ DBG_INFO("negprot index=%zu\n", choice);
+
+ TALLOC_FREE(cliprotos);
+
+ END_PROFILE(SMBnegprot);
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/smb2_notify.c b/source3/smbd/smb2_notify.c
new file mode 100644
index 0000000..8cccd90
--- /dev/null
+++ b/source3/smbd/smb2_notify.c
@@ -0,0 +1,394 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+ Copyright (C) Jeremy Allison 2010
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../lib/util/tevent_ntstatus.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+struct smbd_smb2_notify_state {
+ struct smbd_smb2_request *smb2req;
+ struct smb_request *smbreq;
+ bool has_request;
+ bool skip_reply;
+ NTSTATUS status;
+ DATA_BLOB out_output_buffer;
+};
+
+static struct tevent_req *smbd_smb2_notify_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *in_fsp,
+ uint16_t in_flags,
+ uint32_t in_output_buffer_length,
+ uint64_t in_completion_filter);
+static NTSTATUS smbd_smb2_notify_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out_output_buffer);
+
+static void smbd_smb2_request_notify_done(struct tevent_req *subreq);
+NTSTATUS smbd_smb2_request_process_notify(struct smbd_smb2_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ NTSTATUS status;
+ const uint8_t *inbody;
+ uint16_t in_flags;
+ uint32_t in_output_buffer_length;
+ uint64_t in_file_id_persistent;
+ uint64_t in_file_id_volatile;
+ struct files_struct *in_fsp;
+ uint64_t in_completion_filter;
+ struct tevent_req *subreq;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x20);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ in_flags = SVAL(inbody, 0x02);
+ in_output_buffer_length = IVAL(inbody, 0x04);
+ in_file_id_persistent = BVAL(inbody, 0x08);
+ in_file_id_volatile = BVAL(inbody, 0x10);
+ in_completion_filter = IVAL(inbody, 0x18);
+
+ /*
+ * 0x00010000 is what Windows 7 uses,
+ * Windows 2008 uses 0x00080000
+ */
+ if (in_output_buffer_length > xconn->smb2.server.max_trans) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ status = smbd_smb2_request_verify_creditcharge(req,
+ in_output_buffer_length);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ in_fsp = file_fsp_smb2(req, in_file_id_persistent, in_file_id_volatile);
+ if (in_fsp == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED);
+ }
+
+ subreq = smbd_smb2_notify_send(req, req->sconn->ev_ctx,
+ req, in_fsp,
+ in_flags,
+ in_output_buffer_length,
+ in_completion_filter);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_notify_done, req);
+
+ return smbd_smb2_request_pending_queue(req, subreq, 500);
+}
+
+static void smbd_smb2_request_notify_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *req = tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ DATA_BLOB outbody;
+ DATA_BLOB outdyn;
+ uint16_t out_output_buffer_offset;
+ DATA_BLOB out_output_buffer = data_blob_null;
+ NTSTATUS status;
+ NTSTATUS error; /* transport error */
+
+ status = smbd_smb2_notify_recv(subreq,
+ req,
+ &out_output_buffer);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ error = smbd_smb2_request_error(req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ out_output_buffer_offset = SMB2_HDR_BODY + 0x08;
+
+ outbody = smbd_smb2_generate_outbody(req, 0x08);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x08 + 1); /* struct size */
+ SSVAL(outbody.data, 0x02,
+ out_output_buffer_offset); /* output buffer offset */
+ SIVAL(outbody.data, 0x04,
+ out_output_buffer.length); /* output buffer length */
+
+ outdyn = out_output_buffer;
+
+ error = smbd_smb2_request_done(req, outbody, &outdyn);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+static void smbd_smb2_notify_reply(struct smb_request *smbreq,
+ NTSTATUS error_code,
+ uint8_t *buf, size_t len);
+static bool smbd_smb2_notify_cancel(struct tevent_req *req);
+
+static int smbd_smb2_notify_state_destructor(struct smbd_smb2_notify_state *state)
+{
+ if (!state->has_request) {
+ return 0;
+ }
+
+ state->skip_reply = true;
+ smbd_notify_cancel_by_smbreq(state->smbreq);
+ return 0;
+}
+
+static int smbd_smb2_notify_smbreq_destructor(struct smb_request *smbreq)
+{
+ struct tevent_req *req = talloc_get_type_abort(smbreq->async_priv,
+ struct tevent_req);
+ struct smbd_smb2_notify_state *state = tevent_req_data(req,
+ struct smbd_smb2_notify_state);
+
+ /*
+ * Our temporary parent from change_notify_add_request()
+ * goes away.
+ */
+ state->has_request = false;
+
+ /*
+ * move it back to its original parent,
+ * which means we no longer need the destructor
+ * to protect it.
+ */
+ talloc_steal(smbreq->smb2req, smbreq);
+ talloc_set_destructor(smbreq, NULL);
+
+ /*
+ * We want to keep smbreq!
+ */
+ return -1;
+}
+
+static struct tevent_req *smbd_smb2_notify_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *fsp,
+ uint16_t in_flags,
+ uint32_t in_output_buffer_length,
+ uint64_t in_completion_filter)
+{
+ struct tevent_req *req;
+ struct smbd_smb2_notify_state *state;
+ struct smb_request *smbreq;
+ connection_struct *conn = smb2req->tcon->compat;
+ bool recursive = (in_flags & SMB2_WATCH_TREE) ? true : false;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_notify_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->smb2req = smb2req;
+ state->status = NT_STATUS_INTERNAL_ERROR;
+ state->out_output_buffer = data_blob_null;
+ talloc_set_destructor(state, smbd_smb2_notify_state_destructor);
+
+ DEBUG(10,("smbd_smb2_notify_send: %s - %s\n",
+ fsp_str_dbg(fsp), fsp_fnum_dbg(fsp)));
+
+ smbreq = smbd_smb2_fake_smb_request(smb2req, fsp);
+ if (tevent_req_nomem(smbreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->smbreq = smbreq;
+ smbreq->async_priv = (void *)req;
+
+ if (DEBUGLEVEL >= 3) {
+ char *filter_string;
+
+ filter_string = notify_filter_string(NULL, in_completion_filter);
+ if (tevent_req_nomem(filter_string, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ DEBUG(3,("smbd_smb2_notify_send: notify change "
+ "called on %s, filter = %s, recursive = %d\n",
+ fsp_str_dbg(fsp), filter_string, recursive));
+
+ TALLOC_FREE(filter_string);
+ }
+
+ if ((!fsp->fsp_flags.is_directory) || (conn != fsp->conn)) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ if (fsp->notify == NULL) {
+
+ status = change_notify_create(fsp,
+ in_output_buffer_length,
+ in_completion_filter,
+ recursive);
+ if (tevent_req_nterror(req, status)) {
+ DEBUG(10, ("change_notify_create returned %s\n",
+ nt_errstr(status)));
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ if (change_notify_fsp_has_changes(fsp)) {
+
+ /*
+ * We've got changes pending, respond immediately
+ */
+
+ /*
+ * TODO: write a torture test to check the filtering behaviour
+ * here.
+ */
+
+ change_notify_reply(smbreq,
+ NT_STATUS_OK,
+ in_output_buffer_length,
+ fsp->notify,
+ smbd_smb2_notify_reply);
+
+ /*
+ * change_notify_reply() above has independently
+ * called tevent_req_done().
+ */
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * No changes pending, queue the request
+ */
+
+ status = change_notify_add_request(smbreq,
+ in_output_buffer_length,
+ in_completion_filter,
+ recursive, fsp,
+ smbd_smb2_notify_reply);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * This is a HACK!
+ *
+ * change_notify_add_request() talloc_moves()
+ * smbreq away from us, so we need a destructor
+ * which moves it back at the end.
+ */
+ state->has_request = true;
+ talloc_set_destructor(smbreq, smbd_smb2_notify_smbreq_destructor);
+
+ /* allow this request to be canceled */
+ tevent_req_set_cancel_fn(req, smbd_smb2_notify_cancel);
+
+ SMBPROFILE_IOBYTES_ASYNC_SET_IDLE(state->smb2req->profile);
+ return req;
+}
+
+static void smbd_smb2_notify_reply(struct smb_request *smbreq,
+ NTSTATUS error_code,
+ uint8_t *buf, size_t len)
+{
+ struct tevent_req *req = talloc_get_type_abort(smbreq->async_priv,
+ struct tevent_req);
+ struct smbd_smb2_notify_state *state = tevent_req_data(req,
+ struct smbd_smb2_notify_state);
+
+ if (state->skip_reply) {
+ return;
+ }
+
+ SMBPROFILE_IOBYTES_ASYNC_SET_BUSY(state->smb2req->profile);
+
+ state->status = error_code;
+ if (!NT_STATUS_IS_OK(error_code)) {
+ /* nothing */
+ } else if (len == 0) {
+ state->status = NT_STATUS_NOTIFY_ENUM_DIR;
+ } else {
+ state->out_output_buffer = data_blob_talloc(state, buf, len);
+ if (state->out_output_buffer.data == NULL) {
+ state->status = NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ tevent_req_defer_callback(req, state->smb2req->sconn->ev_ctx);
+
+ if (tevent_req_nterror(req, state->status)) {
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static bool smbd_smb2_notify_cancel(struct tevent_req *req)
+{
+ struct smbd_smb2_notify_state *state = tevent_req_data(req,
+ struct smbd_smb2_notify_state);
+
+ smbd_notify_cancel_by_smbreq(state->smbreq);
+
+ return true;
+}
+
+static NTSTATUS smbd_smb2_notify_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out_output_buffer)
+{
+ NTSTATUS status;
+ struct smbd_smb2_notify_state *state = tevent_req_data(req,
+ struct smbd_smb2_notify_state);
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *out_output_buffer = state->out_output_buffer;
+ talloc_steal(mem_ctx, out_output_buffer->data);
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/smb2_nttrans.c b/source3/smbd/smb2_nttrans.c
new file mode 100644
index 0000000..49bddf5
--- /dev/null
+++ b/source3/smbd/smb2_nttrans.c
@@ -0,0 +1,911 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB NT transaction handling
+ Copyright (C) Jeremy Allison 1994-2007
+ Copyright (C) Stefan (metze) Metzmacher 2003
+
+ 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 "system/filesys.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "fake_file.h"
+#include "../libcli/security/security.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "passdb/lookup_sid.h"
+#include "auth.h"
+#include "smbprofile.h"
+#include "libsmb/libsmb.h"
+#include "lib/util_ea.h"
+#include "librpc/gen_ndr/ndr_quota.h"
+#include "librpc/gen_ndr/ndr_security.h"
+
+extern const struct generic_mapping file_generic_mapping;
+
+/*********************************************************************
+ Windows seems to do canonicalization of inheritance bits. Do the
+ same.
+*********************************************************************/
+
+static void canonicalize_inheritance_bits(struct files_struct *fsp,
+ struct security_descriptor *psd)
+{
+ bool set_auto_inherited = false;
+
+ /*
+ * We need to filter out the
+ * SEC_DESC_DACL_AUTO_INHERITED|SEC_DESC_DACL_AUTO_INHERIT_REQ
+ * bits. If both are set we store SEC_DESC_DACL_AUTO_INHERITED
+ * as this alters whether SEC_ACE_FLAG_INHERITED_ACE is set
+ * when an ACE is inherited. Otherwise we zero these bits out.
+ * See:
+ *
+ * http://social.msdn.microsoft.com/Forums/eu/os_fileservices/thread/11f77b68-731e-407d-b1b3-064750716531
+ *
+ * for details.
+ */
+
+ if (!lp_acl_flag_inherited_canonicalization(SNUM(fsp->conn))) {
+ psd->type &= ~SEC_DESC_DACL_AUTO_INHERIT_REQ;
+ return;
+ }
+
+ if ((psd->type & (SEC_DESC_DACL_AUTO_INHERITED|SEC_DESC_DACL_AUTO_INHERIT_REQ))
+ == (SEC_DESC_DACL_AUTO_INHERITED|SEC_DESC_DACL_AUTO_INHERIT_REQ)) {
+ set_auto_inherited = true;
+ }
+
+ psd->type &= ~(SEC_DESC_DACL_AUTO_INHERITED|SEC_DESC_DACL_AUTO_INHERIT_REQ);
+ if (set_auto_inherited) {
+ psd->type |= SEC_DESC_DACL_AUTO_INHERITED;
+ }
+}
+
+/****************************************************************************
+ Internal fn to set security descriptors.
+****************************************************************************/
+
+NTSTATUS set_sd(files_struct *fsp, struct security_descriptor *psd,
+ uint32_t security_info_sent)
+{
+ files_struct *sd_fsp = NULL;
+ NTSTATUS status;
+
+ if (!CAN_WRITE(fsp->conn)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (!lp_nt_acl_support(SNUM(fsp->conn))) {
+ return NT_STATUS_OK;
+ }
+
+ status = refuse_symlink_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("ACL set on symlink %s denied.\n",
+ fsp_str_dbg(fsp));
+ return status;
+ }
+
+ if (psd->owner_sid == NULL) {
+ security_info_sent &= ~SECINFO_OWNER;
+ }
+ if (psd->group_sid == NULL) {
+ security_info_sent &= ~SECINFO_GROUP;
+ }
+
+ /* Ensure we have at least one thing set. */
+ if ((security_info_sent & (SECINFO_OWNER|SECINFO_GROUP|SECINFO_DACL|SECINFO_SACL)) == 0) {
+ /* Just like W2K3 */
+ return NT_STATUS_OK;
+ }
+
+ /* Ensure we have the rights to do this. */
+ if (security_info_sent & SECINFO_OWNER) {
+ status = check_any_access_fsp(fsp, SEC_STD_WRITE_OWNER);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ if (security_info_sent & SECINFO_GROUP) {
+ status = check_any_access_fsp(fsp, SEC_STD_WRITE_OWNER);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ if (security_info_sent & SECINFO_DACL) {
+ status = check_any_access_fsp(fsp, SEC_STD_WRITE_DAC);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ /* Convert all the generic bits. */
+ if (psd->dacl) {
+ security_acl_map_generic(psd->dacl, &file_generic_mapping);
+ }
+ }
+
+ if (security_info_sent & SECINFO_SACL) {
+ status = check_any_access_fsp(fsp, SEC_FLAG_SYSTEM_SECURITY);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ /*
+ * Setting a SACL also requires WRITE_DAC.
+ * See the smbtorture3 SMB2-SACL test.
+ */
+ status = check_any_access_fsp(fsp, SEC_STD_WRITE_DAC);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ /* Convert all the generic bits. */
+ if (psd->sacl) {
+ security_acl_map_generic(psd->sacl, &file_generic_mapping);
+ }
+ }
+
+ canonicalize_inheritance_bits(fsp, psd);
+
+ if (DEBUGLEVEL >= 10) {
+ DEBUG(10,("set_sd for file %s\n", fsp_str_dbg(fsp)));
+ NDR_PRINT_DEBUG(security_descriptor, psd);
+ }
+
+ sd_fsp = metadata_fsp(fsp);
+ status = SMB_VFS_FSET_NT_ACL(sd_fsp, security_info_sent, psd);
+
+ TALLOC_FREE(psd);
+
+ return status;
+}
+
+static bool check_smb2_posix_chmod_ace(const struct files_struct *fsp,
+ uint32_t security_info_sent,
+ struct security_descriptor *psd,
+ mode_t *pmode)
+{
+ int cmp;
+
+ /*
+ * This must be an ACL with one ACE containing an
+ * MS NFS style mode entry coming in on a POSIX
+ * handle over SMB2+.
+ */
+ if (!fsp->conn->sconn->using_smb2) {
+ return false;
+ }
+
+ if (!(fsp->posix_flags & FSP_POSIX_FLAGS_OPEN)) {
+ return false;
+ }
+
+ if (!(security_info_sent & SECINFO_DACL)) {
+ return false;
+ }
+
+ if (psd->dacl == NULL) {
+ return false;
+ }
+
+ if (psd->dacl->num_aces != 1) {
+ return false;
+ }
+
+ cmp = dom_sid_compare_domain(&global_sid_Unix_NFS_Mode,
+ &psd->dacl->aces[0].trustee);
+ if (cmp != 0) {
+ return false;
+ }
+
+ *pmode = (mode_t)psd->dacl->aces[0].trustee.sub_auths[2];
+ *pmode &= (S_IRWXU | S_IRWXG | S_IRWXO);
+
+ return true;
+}
+
+/****************************************************************************
+ Internal fn to set security descriptors from a data blob.
+****************************************************************************/
+
+NTSTATUS set_sd_blob(files_struct *fsp, uint8_t *data, uint32_t sd_len,
+ uint32_t security_info_sent)
+{
+ struct security_descriptor *psd = NULL;
+ NTSTATUS status;
+ bool do_chmod = false;
+ mode_t smb2_posix_mode = 0;
+ int ret;
+
+ if (sd_len == 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = unmarshall_sec_desc(talloc_tos(), data, sd_len, &psd);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ do_chmod = check_smb2_posix_chmod_ace(fsp,
+ security_info_sent,
+ psd,
+ &smb2_posix_mode);
+ if (!do_chmod) {
+ return set_sd(fsp, psd, security_info_sent);
+ }
+
+ TALLOC_FREE(psd);
+
+ ret = SMB_VFS_FCHMOD(fsp, smb2_posix_mode);
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ DBG_ERR("smb2_posix_chmod [%s] [%04o] failed: %s\n",
+ fsp_str_dbg(fsp),
+ (unsigned)smb2_posix_mode,
+ nt_errstr(status));
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Copy a file.
+****************************************************************************/
+
+NTSTATUS copy_internals(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct smb_request *req,
+ struct files_struct *src_dirfsp,
+ struct smb_filename *smb_fname_src,
+ struct files_struct *dst_dirfsp,
+ struct smb_filename *smb_fname_dst,
+ uint32_t attrs)
+{
+ files_struct *fsp1,*fsp2;
+ uint32_t fattr;
+ int info;
+ off_t ret=-1;
+ NTSTATUS status = NT_STATUS_OK;
+ struct smb_filename *parent = NULL;
+ struct smb_filename *pathref = NULL;
+
+ if (!CAN_WRITE(conn)) {
+ status = NT_STATUS_MEDIA_WRITE_PROTECTED;
+ goto out;
+ }
+
+ /* Source must already exist. */
+ if (!VALID_STAT(smb_fname_src->st)) {
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ goto out;
+ }
+
+ /* Ensure attributes match. */
+ fattr = fdos_mode(smb_fname_src->fsp);
+ if ((fattr & ~attrs) & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) {
+ status = NT_STATUS_NO_SUCH_FILE;
+ goto out;
+ }
+
+ /* Disallow if dst file already exists. */
+ if (VALID_STAT(smb_fname_dst->st)) {
+ status = NT_STATUS_OBJECT_NAME_COLLISION;
+ goto out;
+ }
+
+ /* No links from a directory. */
+ if (S_ISDIR(smb_fname_src->st.st_ex_mode)) {
+ status = NT_STATUS_FILE_IS_A_DIRECTORY;
+ goto out;
+ }
+
+ DEBUG(10,("copy_internals: doing file copy %s to %s\n",
+ smb_fname_str_dbg(smb_fname_src),
+ smb_fname_str_dbg(smb_fname_dst)));
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ src_dirfsp, /* dirfsp */
+ smb_fname_src, /* fname */
+ FILE_READ_DATA|FILE_READ_ATTRIBUTES|
+ FILE_READ_EA, /* access_mask */
+ (FILE_SHARE_READ | FILE_SHARE_WRITE | /* share_access */
+ FILE_SHARE_DELETE),
+ FILE_OPEN, /* create_disposition*/
+ 0, /* create_options */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes */
+ NO_OPLOCK, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp1, /* result */
+ &info, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dst_dirfsp, /* dirfsp */
+ smb_fname_dst, /* fname */
+ FILE_WRITE_DATA|FILE_WRITE_ATTRIBUTES|
+ FILE_WRITE_EA, /* access_mask */
+ (FILE_SHARE_READ | FILE_SHARE_WRITE | /* share_access */
+ FILE_SHARE_DELETE),
+ FILE_CREATE, /* create_disposition*/
+ 0, /* create_options */
+ fattr, /* file_attributes */
+ NO_OPLOCK, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp2, /* result */
+ &info, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ close_file_free(NULL, &fsp1, ERROR_CLOSE);
+ goto out;
+ }
+
+ if (smb_fname_src->st.st_ex_size) {
+ ret = vfs_transfer_file(fsp1, fsp2, smb_fname_src->st.st_ex_size);
+ }
+
+ /*
+ * As we are opening fsp1 read-only we only expect
+ * an error on close on fsp2 if we are out of space.
+ * Thus we don't look at the error return from the
+ * close of fsp1.
+ */
+ close_file_free(NULL, &fsp1, NORMAL_CLOSE);
+
+ /* Ensure the modtime is set correctly on the destination file. */
+ set_close_write_time(fsp2, smb_fname_src->st.st_ex_mtime);
+
+ status = close_file_free(NULL, &fsp2, NORMAL_CLOSE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("close_file_free() failed: %s\n",
+ nt_errstr(status));
+ /*
+ * We can't do much but leak the fsp
+ */
+ goto out;
+ }
+
+ /* Grrr. We have to do this as open_file_ntcreate adds FILE_ATTRIBUTE_ARCHIVE when it
+ creates the file. This isn't the correct thing to do in the copy
+ case. JRA */
+
+ status = SMB_VFS_PARENT_PATHNAME(conn,
+ talloc_tos(),
+ smb_fname_dst,
+ &parent,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ if (smb_fname_dst->fsp == NULL) {
+ status = synthetic_pathref(parent,
+ conn->cwd_fsp,
+ smb_fname_dst->base_name,
+ smb_fname_dst->stream_name,
+ NULL,
+ smb_fname_dst->twrp,
+ smb_fname_dst->flags,
+ &pathref);
+
+ /* should we handle NT_STATUS_OBJECT_NAME_NOT_FOUND specially here ???? */
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(parent);
+ goto out;
+ }
+ file_set_dosmode(conn, pathref, fattr, parent, false);
+ smb_fname_dst->st.st_ex_mode = pathref->st.st_ex_mode;
+ } else {
+ file_set_dosmode(conn, smb_fname_dst, fattr, parent, false);
+ }
+ TALLOC_FREE(parent);
+
+ if (ret < (off_t)smb_fname_src->st.st_ex_size) {
+ status = NT_STATUS_DISK_FULL;
+ goto out;
+ }
+ out:
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3,("copy_internals: Error %s copy file %s to %s\n",
+ nt_errstr(status), smb_fname_str_dbg(smb_fname_src),
+ smb_fname_str_dbg(smb_fname_dst)));
+ }
+
+ return status;
+}
+
+/******************************************************************************
+ Fake up a completely empty SD.
+*******************************************************************************/
+
+static NTSTATUS get_null_nt_acl(TALLOC_CTX *mem_ctx, struct security_descriptor **ppsd)
+{
+ size_t sd_size;
+
+ *ppsd = make_standard_sec_desc( mem_ctx, &global_sid_World, &global_sid_World, NULL, &sd_size);
+ if(!*ppsd) {
+ DEBUG(0,("get_null_nt_acl: Unable to malloc space for security descriptor.\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Get a security descriptor from the file system, normalize for components
+ requested.
+****************************************************************************/
+
+static NTSTATUS smbd_fetch_security_desc(connection_struct *conn,
+ TALLOC_CTX *mem_ctx,
+ files_struct *fsp,
+ uint32_t security_info_wanted,
+ struct security_descriptor **ppsd)
+{
+ NTSTATUS status;
+ struct security_descriptor *psd = NULL;
+ bool need_to_read_sd = false;
+
+ /*
+ * Get the permissions to return.
+ */
+
+ if (security_info_wanted & SECINFO_SACL) {
+ status = check_any_access_fsp(fsp, SEC_FLAG_SYSTEM_SECURITY);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Access to SACL denied.\n");
+ return status;
+ }
+ }
+
+ if (security_info_wanted & (SECINFO_DACL|SECINFO_OWNER|SECINFO_GROUP)) {
+ status = check_any_access_fsp(fsp, SEC_STD_READ_CONTROL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Access to DACL, OWNER, or GROUP denied.\n");
+ return status;
+ }
+ }
+
+ status = refuse_symlink_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("ACL get on symlink %s denied.\n",
+ fsp_str_dbg(fsp));
+ return status;
+ }
+
+ if (security_info_wanted & (SECINFO_DACL|SECINFO_OWNER|
+ SECINFO_GROUP|SECINFO_SACL)) {
+ /* Don't return SECINFO_LABEL if anything else was
+ requested. See bug #8458. */
+ security_info_wanted &= ~SECINFO_LABEL;
+
+ /*
+ * Only query the file system SD if the caller asks
+ * for any bits. This allows a caller to open without
+ * READ_CONTROL but still issue a query sd. See
+ * smb2.sdread test.
+ */
+ need_to_read_sd = true;
+ }
+
+ if (lp_nt_acl_support(SNUM(conn)) &&
+ ((security_info_wanted & SECINFO_LABEL) == 0) &&
+ need_to_read_sd)
+ {
+ files_struct *sd_fsp = metadata_fsp(fsp);
+ status = SMB_VFS_FGET_NT_ACL(
+ sd_fsp, security_info_wanted, mem_ctx, &psd);
+ } else {
+ status = get_null_nt_acl(mem_ctx, &psd);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!(security_info_wanted & SECINFO_OWNER)) {
+ psd->owner_sid = NULL;
+ }
+ if (!(security_info_wanted & SECINFO_GROUP)) {
+ psd->group_sid = NULL;
+ }
+ if (!(security_info_wanted & SECINFO_DACL)) {
+ psd->type &= ~SEC_DESC_DACL_PRESENT;
+ psd->dacl = NULL;
+ }
+ if (!(security_info_wanted & SECINFO_SACL)) {
+ psd->type &= ~SEC_DESC_SACL_PRESENT;
+ psd->sacl = NULL;
+ }
+
+ /* If the SACL/DACL is NULL, but was requested, we mark that it is
+ * present in the reply to match Windows behavior */
+ if (psd->sacl == NULL &&
+ security_info_wanted & SECINFO_SACL)
+ psd->type |= SEC_DESC_SACL_PRESENT;
+ if (psd->dacl == NULL &&
+ security_info_wanted & SECINFO_DACL)
+ psd->type |= SEC_DESC_DACL_PRESENT;
+
+ if (security_info_wanted & SECINFO_LABEL) {
+ /* Like W2K3 return a null object. */
+ psd->owner_sid = NULL;
+ psd->group_sid = NULL;
+ psd->dacl = NULL;
+ psd->sacl = NULL;
+ psd->type &= ~(SEC_DESC_DACL_PRESENT|SEC_DESC_SACL_PRESENT);
+ }
+
+ *ppsd = psd;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Write a security descriptor into marshalled format.
+****************************************************************************/
+
+static NTSTATUS smbd_marshall_security_desc(TALLOC_CTX *mem_ctx,
+ files_struct *fsp,
+ struct security_descriptor *psd,
+ uint32_t max_data_count,
+ uint8_t **ppmarshalled_sd,
+ size_t *psd_size)
+{
+ *psd_size = ndr_size_security_descriptor(psd, 0);
+
+ DBG_NOTICE("sd_size = %zu.\n", *psd_size);
+
+ if (DEBUGLEVEL >= 10) {
+ DBG_DEBUG("security desc for file %s\n",
+ fsp_str_dbg(fsp));
+ NDR_PRINT_DEBUG(security_descriptor, psd);
+ }
+
+ if (max_data_count < *psd_size) {
+ return NT_STATUS_BUFFER_TOO_SMALL;
+ }
+
+ return marshall_sec_desc(mem_ctx,
+ psd,
+ ppmarshalled_sd,
+ psd_size);
+}
+
+/****************************************************************************
+ Reply to query a security descriptor.
+ Callable from SMB1 and SMB2.
+ If it returns NT_STATUS_BUFFER_TOO_SMALL, psd_size is initialized with
+ the required size.
+****************************************************************************/
+
+NTSTATUS smbd_do_query_security_desc(connection_struct *conn,
+ TALLOC_CTX *mem_ctx,
+ files_struct *fsp,
+ uint32_t security_info_wanted,
+ uint32_t max_data_count,
+ uint8_t **ppmarshalled_sd,
+ size_t *psd_size)
+{
+ NTSTATUS status;
+ struct security_descriptor *psd = NULL;
+
+ /*
+ * Get the permissions to return.
+ */
+
+ status = smbd_fetch_security_desc(conn,
+ mem_ctx,
+ fsp,
+ security_info_wanted,
+ &psd);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = smbd_marshall_security_desc(mem_ctx,
+ fsp,
+ psd,
+ max_data_count,
+ ppmarshalled_sd,
+ psd_size);
+ TALLOC_FREE(psd);
+ return status;
+}
+
+#ifdef HAVE_SYS_QUOTAS
+static enum ndr_err_code fill_qtlist_from_sids(TALLOC_CTX *mem_ctx,
+ struct files_struct *fsp,
+ SMB_NTQUOTA_HANDLE *qt_handle,
+ struct dom_sid *sids,
+ uint32_t elems)
+{
+ uint32_t i;
+ TALLOC_CTX *list_ctx = NULL;
+
+ list_ctx = talloc_init("quota_sid_list");
+
+ if (list_ctx == NULL) {
+ DBG_ERR("failed to allocate\n");
+ return NDR_ERR_ALLOC;
+ }
+
+ if (qt_handle->quota_list!=NULL) {
+ free_ntquota_list(&(qt_handle->quota_list));
+ }
+ for (i = 0; i < elems; i++) {
+ SMB_NTQUOTA_STRUCT qt;
+ SMB_NTQUOTA_LIST *list_item;
+ bool ok;
+
+ if (!NT_STATUS_IS_OK(vfs_get_ntquota(fsp,
+ SMB_USER_QUOTA_TYPE,
+ &sids[i], &qt))) {
+ /* non fatal error, return empty item in result */
+ ZERO_STRUCT(qt);
+ continue;
+ }
+
+
+ list_item = talloc_zero(list_ctx, SMB_NTQUOTA_LIST);
+ if (list_item == NULL) {
+ DBG_ERR("failed to allocate\n");
+ return NDR_ERR_ALLOC;
+ }
+
+ ok = sid_to_uid(&sids[i], &list_item->uid);
+ if (!ok) {
+ struct dom_sid_buf buf;
+ DBG_WARNING("Could not convert SID %s to uid\n",
+ dom_sid_str_buf(&sids[i], &buf));
+ /* No idea what to return here... */
+ return NDR_ERR_INVALID_POINTER;
+ }
+
+ list_item->quotas = talloc_zero(list_item, SMB_NTQUOTA_STRUCT);
+ if (list_item->quotas == NULL) {
+ DBG_ERR("failed to allocate\n");
+ return NDR_ERR_ALLOC;
+ }
+
+ *list_item->quotas = qt;
+ list_item->mem_ctx = list_ctx;
+ DLIST_ADD(qt_handle->quota_list, list_item);
+ }
+ qt_handle->tmp_list = qt_handle->quota_list;
+ return NDR_ERR_SUCCESS;
+}
+
+static enum ndr_err_code extract_sids_from_buf(TALLOC_CTX *mem_ctx,
+ uint32_t sidlistlength,
+ DATA_BLOB *sid_buf,
+ struct dom_sid **sids,
+ uint32_t *num)
+{
+ DATA_BLOB blob;
+ uint32_t i = 0;
+ enum ndr_err_code err;
+
+ struct sid_list_elem {
+ struct sid_list_elem *prev, *next;
+ struct dom_sid sid;
+ };
+
+ struct sid_list_elem *sid_list = NULL;
+ struct sid_list_elem *iter = NULL;
+ TALLOC_CTX *list_ctx = talloc_init("sid_list");
+ if (!list_ctx) {
+ DBG_ERR("OOM\n");
+ err = NDR_ERR_ALLOC;
+ goto done;
+ }
+
+ *num = 0;
+ *sids = NULL;
+
+ if (sidlistlength) {
+ uint32_t offset = 0;
+ struct ndr_pull *ndr_pull = NULL;
+
+ if (sidlistlength > sid_buf->length) {
+ DBG_ERR("sid_list_length 0x%x exceeds "
+ "available bytes %zx\n",
+ sidlistlength,
+ sid_buf->length);
+ err = NDR_ERR_OFFSET;
+ goto done;
+ }
+ while (true) {
+ struct file_get_quota_info info;
+ struct sid_list_elem *item = NULL;
+ uint32_t new_offset = 0;
+ blob.data = sid_buf->data + offset;
+ blob.length = sidlistlength - offset;
+ ndr_pull = ndr_pull_init_blob(&blob, list_ctx);
+ if (!ndr_pull) {
+ DBG_ERR("OOM\n");
+ err = NDR_ERR_ALLOC;
+ goto done;
+ }
+ err = ndr_pull_file_get_quota_info(ndr_pull,
+ NDR_SCALARS | NDR_BUFFERS, &info);
+ if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+ DBG_ERR("Failed to pull file_get_quota_info "
+ "from sidlist buffer\n");
+ goto done;
+ }
+ item = talloc_zero(list_ctx, struct sid_list_elem);
+ if (!item) {
+ DBG_ERR("OOM\n");
+ err = NDR_ERR_ALLOC;
+ goto done;
+ }
+ item->sid = info.sid;
+ DLIST_ADD(sid_list, item);
+ i++;
+ if (i == UINT32_MAX) {
+ DBG_ERR("Integer overflow\n");
+ err = NDR_ERR_ARRAY_SIZE;
+ goto done;
+ }
+ new_offset = info.next_entry_offset;
+
+ /* if new_offset == 0 no more sid(s) to read. */
+ if (new_offset == 0) {
+ break;
+ }
+
+ /* Integer wrap? */
+ if ((offset + new_offset) < offset) {
+ DBG_ERR("Integer wrap while adding "
+ "new_offset 0x%x to current "
+ "buffer offset 0x%x\n",
+ new_offset, offset);
+ err = NDR_ERR_OFFSET;
+ goto done;
+ }
+
+ offset += new_offset;
+
+ /* check if new offset is outside buffer boundary. */
+ if (offset >= sidlistlength) {
+ DBG_ERR("bufsize 0x%x exceeded by "
+ "new offset 0x%x)\n",
+ sidlistlength,
+ offset);
+ err = NDR_ERR_OFFSET;
+ goto done;
+ }
+ }
+ *sids = talloc_zero_array(mem_ctx, struct dom_sid, i);
+ if (*sids == NULL) {
+ DBG_ERR("OOM\n");
+ err = NDR_ERR_ALLOC;
+ goto done;
+ }
+
+ *num = i;
+
+ for (iter = sid_list, i = 0; iter; iter = iter->next, i++) {
+ struct dom_sid_buf buf;
+ (*sids)[i] = iter->sid;
+ DBG_DEBUG("quota SID[%u] %s\n",
+ (unsigned int)i,
+ dom_sid_str_buf(&iter->sid, &buf));
+ }
+ }
+ err = NDR_ERR_SUCCESS;
+done:
+ TALLOC_FREE(list_ctx);
+ return err;
+}
+
+NTSTATUS smbd_do_query_getinfo_quota(TALLOC_CTX *mem_ctx,
+ files_struct *fsp,
+ bool restart_scan,
+ bool return_single,
+ uint32_t sid_list_length,
+ DATA_BLOB *sid_buf,
+ uint32_t max_data_count,
+ uint8_t **p_data,
+ uint32_t *p_data_size)
+{
+ NTSTATUS status;
+ SMB_NTQUOTA_HANDLE *qt_handle = NULL;
+ SMB_NTQUOTA_LIST *qt_list = NULL;
+ DATA_BLOB blob = data_blob_null;
+ enum ndr_err_code err;
+
+ qt_handle =
+ (SMB_NTQUOTA_HANDLE *)fsp->fake_file_handle->private_data;
+
+ if (sid_list_length ) {
+ struct dom_sid *sids;
+ uint32_t elems = 0;
+ /*
+ * error check pulled offsets and lengths for wrap and
+ * exceeding available bytes.
+ */
+ if (sid_list_length > sid_buf->length) {
+ DBG_ERR("sid_list_length 0x%x exceeds "
+ "available bytes %zx\n",
+ sid_list_length,
+ sid_buf->length);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ err = extract_sids_from_buf(mem_ctx, sid_list_length,
+ sid_buf, &sids, &elems);
+ if (!NDR_ERR_CODE_IS_SUCCESS(err) || elems == 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ err = fill_qtlist_from_sids(mem_ctx,
+ fsp,
+ qt_handle,
+ sids,
+ elems);
+ if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ } else if (restart_scan) {
+ if (vfs_get_user_ntquota_list(fsp,
+ &(qt_handle->quota_list))!=0) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ } else {
+ if (qt_handle->quota_list!=NULL &&
+ qt_handle->tmp_list==NULL) {
+ free_ntquota_list(&(qt_handle->quota_list));
+ }
+ }
+
+ if (restart_scan !=0 ) {
+ qt_list = qt_handle->quota_list;
+ } else {
+ qt_list = qt_handle->tmp_list;
+ }
+ status = fill_quota_buffer(mem_ctx, qt_list,
+ return_single != 0,
+ max_data_count,
+ &blob,
+ &qt_handle->tmp_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (blob.length > max_data_count) {
+ return NT_STATUS_BUFFER_TOO_SMALL;
+ }
+
+ *p_data = blob.data;
+ *p_data_size = blob.length;
+ return NT_STATUS_OK;
+}
+#endif /* HAVE_SYS_QUOTAS */
diff --git a/source3/smbd/smb2_oplock.c b/source3/smbd/smb2_oplock.c
new file mode 100644
index 0000000..75d50b3
--- /dev/null
+++ b/source3/smbd/smb2_oplock.c
@@ -0,0 +1,1430 @@
+/*
+ Unix SMB/CIFS implementation.
+ oplock processing
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 1998 - 2001
+ Copyright (C) Volker Lendecke 2005
+
+ 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/>.
+*/
+
+#define DBGC_CLASS DBGC_LOCKING
+#include "includes.h"
+#include "lib/util/server_id.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "messages.h"
+#include "locking/leases_db.h"
+#include "../librpc/gen_ndr/ndr_open_files.h"
+
+/*
+ * helper function used by the kernel oplock backends to post the break message
+ */
+void break_kernel_oplock(struct messaging_context *msg_ctx, files_struct *fsp)
+{
+ uint8_t msg[MSG_SMB_KERNEL_BREAK_SIZE];
+
+ /* Put the kernel break info into the message. */
+ push_file_id_24((char *)msg, &fsp->file_id);
+ SIVAL(msg, 24, fh_get_gen_id(fsp->fh));
+
+ /* Don't need to be root here as we're only ever
+ sending to ourselves. */
+
+ messaging_send_buf(msg_ctx, messaging_server_id(msg_ctx),
+ MSG_SMB_KERNEL_BREAK,
+ msg, MSG_SMB_KERNEL_BREAK_SIZE);
+}
+
+/****************************************************************************
+ Attempt to set an oplock on a file. Succeeds if kernel oplocks are
+ disabled (just sets flags).
+****************************************************************************/
+
+NTSTATUS set_file_oplock(files_struct *fsp)
+{
+ struct smbd_server_connection *sconn = fsp->conn->sconn;
+ struct kernel_oplocks *koplocks = sconn->oplocks.kernel_ops;
+ bool use_kernel = lp_kernel_oplocks(SNUM(fsp->conn)) &&
+ (koplocks != NULL);
+ struct file_id_buf buf;
+
+ smb_vfs_assert_allowed();
+
+ if (fsp->oplock_type == LEVEL_II_OPLOCK && use_kernel) {
+ DEBUG(10, ("Refusing level2 oplock, kernel oplocks "
+ "don't support them\n"));
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if ((fsp->oplock_type != NO_OPLOCK) &&
+ use_kernel &&
+ !koplocks->ops->set_oplock(koplocks, fsp, fsp->oplock_type))
+ {
+ return map_nt_error_from_unix(errno);
+ }
+
+ fsp->sent_oplock_break = NO_BREAK_SENT;
+ if (fsp->oplock_type == LEVEL_II_OPLOCK) {
+ sconn->oplocks.level_II_open++;
+ } else if (EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
+ sconn->oplocks.exclusive_open++;
+ }
+
+ DBG_INFO("granted oplock on file %s, %s/%"PRIu64", "
+ "tv_sec = %x, tv_usec = %x\n",
+ fsp_str_dbg(fsp),
+ file_id_str_buf(fsp->file_id, &buf),
+ fh_get_gen_id(fsp->fh),
+ (int)fsp->open_time.tv_sec,
+ (int)fsp->open_time.tv_usec);
+
+ return NT_STATUS_OK;
+}
+
+static void release_fsp_kernel_oplock(files_struct *fsp)
+{
+ struct smbd_server_connection *sconn = fsp->conn->sconn;
+ struct kernel_oplocks *koplocks = sconn->oplocks.kernel_ops;
+ bool use_kernel;
+
+ smb_vfs_assert_allowed();
+
+ if (koplocks == NULL) {
+ return;
+ }
+ use_kernel = lp_kernel_oplocks(SNUM(fsp->conn));
+ if (!use_kernel) {
+ return;
+ }
+ if (fsp->oplock_type == NO_OPLOCK) {
+ return;
+ }
+ if (fsp->oplock_type == LEASE_OPLOCK) {
+ /*
+ * For leases we don't touch kernel oplocks at all
+ */
+ return;
+ }
+
+ koplocks->ops->release_oplock(koplocks, fsp, NO_OPLOCK);
+}
+
+/****************************************************************************
+ Attempt to release an oplock on a file. Decrements oplock count.
+****************************************************************************/
+
+void release_file_oplock(files_struct *fsp)
+{
+ struct smbd_server_connection *sconn = fsp->conn->sconn;
+
+ release_fsp_kernel_oplock(fsp);
+
+ if (fsp->oplock_type == LEVEL_II_OPLOCK) {
+ sconn->oplocks.level_II_open--;
+ } else if (EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
+ sconn->oplocks.exclusive_open--;
+ }
+
+ SMB_ASSERT(sconn->oplocks.exclusive_open>=0);
+ SMB_ASSERT(sconn->oplocks.level_II_open>=0);
+
+ fsp->oplock_type = NO_OPLOCK;
+ fsp->sent_oplock_break = NO_BREAK_SENT;
+
+ TALLOC_FREE(fsp->oplock_timeout);
+}
+
+/****************************************************************************
+ Attempt to downgrade an oplock on a file. Doesn't decrement oplock count.
+****************************************************************************/
+
+static void downgrade_file_oplock(files_struct *fsp)
+{
+ struct smbd_server_connection *sconn = fsp->conn->sconn;
+ struct kernel_oplocks *koplocks = sconn->oplocks.kernel_ops;
+ bool use_kernel = lp_kernel_oplocks(SNUM(fsp->conn)) &&
+ (koplocks != NULL);
+
+ smb_vfs_assert_allowed();
+
+ if (!EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
+ DEBUG(0, ("trying to downgrade an already-downgraded oplock!\n"));
+ return;
+ }
+
+ if (use_kernel) {
+ koplocks->ops->release_oplock(koplocks, fsp, LEVEL_II_OPLOCK);
+ }
+ fsp->oplock_type = LEVEL_II_OPLOCK;
+ sconn->oplocks.exclusive_open--;
+ sconn->oplocks.level_II_open++;
+ fsp->sent_oplock_break = NO_BREAK_SENT;
+
+ TALLOC_FREE(fsp->oplock_timeout);
+}
+
+uint32_t get_lease_type(struct share_mode_entry *e, struct file_id id)
+{
+ struct GUID_txt_buf guid_strbuf;
+ struct file_id_buf file_id_strbuf;
+ NTSTATUS status;
+ uint32_t current_state;
+
+ if (e->op_type != LEASE_OPLOCK) {
+ return map_oplock_to_lease_type(e->op_type);
+ }
+
+ status = leases_db_get(&e->client_guid,
+ &e->lease_key,
+ &id,
+ &current_state,
+ NULL, /* breaking */
+ NULL, /* breaking_to_requested */
+ NULL, /* breaking_to_required */
+ NULL, /* lease_version */
+ NULL); /* epoch */
+ if (NT_STATUS_IS_OK(status)) {
+ return current_state;
+ }
+
+ if (share_entry_stale_pid(e)) {
+ return 0;
+ }
+ DBG_ERR("leases_db_get for client_guid [%s] "
+ "lease_key [%"PRIu64"/%"PRIu64"] "
+ "file_id [%s] failed: %s\n",
+ GUID_buf_string(&e->client_guid, &guid_strbuf),
+ e->lease_key.data[0],
+ e->lease_key.data[1],
+ file_id_str_buf(id, &file_id_strbuf),
+ nt_errstr(status));
+ smb_panic("leases_db_get() failed");
+}
+
+/****************************************************************************
+ Remove a file oplock. Copes with level II and exclusive.
+ Locks then unlocks the share mode lock. Client can decide to go directly
+ to none even if a "break-to-level II" was sent.
+****************************************************************************/
+
+bool remove_oplock(files_struct *fsp)
+{
+ bool ret;
+ struct share_mode_lock *lck;
+
+ DBG_DEBUG("remove_oplock called for %s\n", fsp_str_dbg(fsp));
+
+ /* Remove the oplock flag from the sharemode. */
+ lck = get_existing_share_mode_lock(talloc_tos(), fsp->file_id);
+ if (lck == NULL) {
+ DBG_ERR("failed to lock share entry for "
+ "file %s\n", fsp_str_dbg(fsp));
+ return false;
+ }
+
+ ret = remove_share_oplock(lck, fsp);
+ if (!ret) {
+ struct file_id_buf buf;
+
+ DBG_ERR("failed to remove share oplock for "
+ "file %s, %s, %s\n",
+ fsp_str_dbg(fsp), fsp_fnum_dbg(fsp),
+ file_id_str_buf(fsp->file_id, &buf));
+ }
+ release_file_oplock(fsp);
+
+ TALLOC_FREE(lck);
+ return ret;
+}
+
+/*
+ * Deal with a reply when a break-to-level II was sent.
+ */
+bool downgrade_oplock(files_struct *fsp)
+{
+ bool ret;
+ struct share_mode_lock *lck;
+
+ DEBUG(10, ("downgrade_oplock called for %s\n",
+ fsp_str_dbg(fsp)));
+
+ lck = get_existing_share_mode_lock(talloc_tos(), fsp->file_id);
+ if (lck == NULL) {
+ DEBUG(0,("downgrade_oplock: failed to lock share entry for "
+ "file %s\n", fsp_str_dbg(fsp)));
+ return False;
+ }
+ ret = downgrade_share_oplock(lck, fsp);
+ if (!ret) {
+ struct file_id_buf idbuf;
+ DBG_ERR("failed to downgrade share oplock "
+ "for file %s, %s, file_id %s\n",
+ fsp_str_dbg(fsp),
+ fsp_fnum_dbg(fsp),
+ file_id_str_buf(fsp->file_id, &idbuf));
+ }
+ downgrade_file_oplock(fsp);
+
+ TALLOC_FREE(lck);
+ return ret;
+}
+
+static void lease_timeout_handler(struct tevent_context *ctx,
+ struct tevent_timer *te,
+ struct timeval now,
+ void *private_data)
+{
+ struct fsp_lease *lease =
+ talloc_get_type_abort(private_data,
+ struct fsp_lease);
+ struct files_struct *fsp;
+ struct share_mode_lock *lck;
+ uint16_t old_epoch = lease->lease.lease_epoch;
+
+ fsp = file_find_one_fsp_from_lease_key(lease->sconn,
+ &lease->lease.lease_key);
+ if (fsp == NULL) {
+ /* race? */
+ TALLOC_FREE(lease->timeout);
+ return;
+ }
+
+ /*
+ * Paranoia check: There can only be one fsp_lease per lease
+ * key
+ */
+ SMB_ASSERT(fsp->lease == lease);
+
+ lck = get_existing_share_mode_lock(
+ talloc_tos(), fsp->file_id);
+ if (lck == NULL) {
+ /* race? */
+ TALLOC_FREE(lease->timeout);
+ return;
+ }
+
+ fsp_lease_update(fsp);
+
+ if (lease->lease.lease_epoch != old_epoch) {
+ /*
+ * If the epoch changed we need to wait for
+ * the next timeout to happen.
+ */
+ DEBUG(10, ("lease break timeout race (epoch) for file %s - ignoring\n",
+ fsp_str_dbg(fsp)));
+ TALLOC_FREE(lck);
+ return;
+ }
+
+ if (!(lease->lease.lease_flags & SMB2_LEASE_FLAG_BREAK_IN_PROGRESS)) {
+ /*
+ * If the epoch changed we need to wait for
+ * the next timeout to happen.
+ */
+ DEBUG(10, ("lease break timeout race (flags) for file %s - ignoring\n",
+ fsp_str_dbg(fsp)));
+ TALLOC_FREE(lck);
+ return;
+ }
+
+ DEBUG(1, ("lease break timed out for file %s -- replying anyway\n",
+ fsp_str_dbg(fsp)));
+ (void)downgrade_lease(lease->sconn->client,
+ 1,
+ &fsp->file_id,
+ &lease->lease.lease_key,
+ SMB2_LEASE_NONE);
+
+ TALLOC_FREE(lck);
+}
+
+bool fsp_lease_update(struct files_struct *fsp)
+{
+ const struct GUID *client_guid = fsp_client_guid(fsp);
+ struct fsp_lease *lease = fsp->lease;
+ uint32_t current_state;
+ bool breaking;
+ uint16_t lease_version, epoch;
+ NTSTATUS status;
+
+ status = leases_db_get(client_guid,
+ &lease->lease.lease_key,
+ &fsp->file_id,
+ &current_state,
+ &breaking,
+ NULL, /* breaking_to_requested */
+ NULL, /* breaking_to_required */
+ &lease_version,
+ &epoch);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("Could not find lease entry: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(lease->timeout);
+ lease->lease.lease_state = SMB2_LEASE_NONE;
+ lease->lease.lease_epoch += 1;
+ lease->lease.lease_flags = 0;
+ return false;
+ }
+
+ DEBUG(10,("%s: refresh lease state\n", __func__));
+
+ /* Ensure we're in sync with current lease state. */
+ if (lease->lease.lease_epoch != epoch) {
+ DEBUG(10,("%s: cancel outdated timeout\n", __func__));
+ TALLOC_FREE(lease->timeout);
+ }
+ lease->lease.lease_epoch = epoch;
+ lease->lease.lease_state = current_state;
+
+ if (breaking) {
+ lease->lease.lease_flags |= SMB2_LEASE_FLAG_BREAK_IN_PROGRESS;
+
+ if (lease->timeout == NULL) {
+ struct timeval t = timeval_current_ofs(OPLOCK_BREAK_TIMEOUT, 0);
+
+ DEBUG(10,("%s: setup timeout handler\n", __func__));
+
+ lease->timeout = tevent_add_timer(lease->sconn->ev_ctx,
+ lease, t,
+ lease_timeout_handler,
+ lease);
+ if (lease->timeout == NULL) {
+ DEBUG(0, ("%s: Could not add lease timeout handler\n",
+ __func__));
+ }
+ }
+ } else {
+ lease->lease.lease_flags &= ~SMB2_LEASE_FLAG_BREAK_IN_PROGRESS;
+ TALLOC_FREE(lease->timeout);
+ }
+
+ return true;
+}
+
+struct downgrade_lease_additional_state {
+ struct tevent_immediate *im;
+ struct smbXsrv_client *client;
+ uint32_t break_flags;
+ struct smb2_lease_key lease_key;
+ uint32_t break_from;
+ uint32_t break_to;
+ uint16_t new_epoch;
+};
+
+static void downgrade_lease_additional_trigger(struct tevent_context *ev,
+ struct tevent_immediate *im,
+ void *private_data)
+{
+ struct downgrade_lease_additional_state *state =
+ talloc_get_type_abort(private_data,
+ struct downgrade_lease_additional_state);
+ NTSTATUS status;
+
+ status = smbd_smb2_send_lease_break(state->client,
+ state->new_epoch,
+ state->break_flags,
+ &state->lease_key,
+ state->break_from,
+ state->break_to);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_disconnect_client(state->client,
+ nt_errstr(status));
+ }
+ TALLOC_FREE(state);
+}
+
+struct fsps_lease_update_state {
+ const struct file_id *id;
+ const struct smb2_lease_key *key;
+};
+
+static struct files_struct *fsps_lease_update_fn(
+ struct files_struct *fsp, void *private_data)
+{
+ struct fsps_lease_update_state *state =
+ (struct fsps_lease_update_state *)private_data;
+
+ if (fsp->oplock_type != LEASE_OPLOCK) {
+ return NULL;
+ }
+ if (!smb2_lease_key_equal(&fsp->lease->lease.lease_key, state->key)) {
+ return NULL;
+ }
+ if (!file_id_equal(&fsp->file_id, state->id)) {
+ return NULL;
+ }
+
+ fsp_lease_update(fsp);
+
+ return NULL;
+}
+
+static void fsps_lease_update(struct smbd_server_connection *sconn,
+ const struct file_id *id,
+ const struct smb2_lease_key *key)
+{
+ struct fsps_lease_update_state state = { .id = id, .key = key };
+ files_forall(sconn, fsps_lease_update_fn, &state);
+}
+
+NTSTATUS downgrade_lease(struct smbXsrv_client *client,
+ uint32_t num_file_ids,
+ const struct file_id *ids,
+ const struct smb2_lease_key *key,
+ uint32_t lease_state)
+{
+ struct smbd_server_connection *sconn = client->sconn;
+ const struct GUID *client_guid = NULL;
+ struct share_mode_lock *lck;
+ const struct file_id id = ids[0];
+ uint32_t current_state, breaking_to_requested, breaking_to_required;
+ bool breaking;
+ uint16_t lease_version, epoch;
+ NTSTATUS status;
+ uint32_t i;
+ struct file_id_buf idbuf;
+
+ DBG_DEBUG("Downgrading %s to %"PRIu32"\n",
+ file_id_str_buf(id, &idbuf),
+ lease_state);
+
+ lck = get_existing_share_mode_lock(talloc_tos(), id);
+ if (lck == NULL) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ client_guid = &sconn->client->global->client_guid;
+
+ status = leases_db_get(client_guid,
+ key,
+ &id,
+ &current_state,
+ &breaking,
+ &breaking_to_requested,
+ &breaking_to_required,
+ &lease_version,
+ &epoch);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("leases_db_get returned %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(lck);
+ return status;
+ }
+
+ if (!breaking) {
+ DBG_WARNING("Attempt to break from %"PRIu32" to %"PRIu32" - "
+ "but we're not in breaking state\n",
+ current_state, lease_state);
+ TALLOC_FREE(lck);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ /*
+ * Can't upgrade anything: breaking_to_requested (and current_state)
+ * must be a strict bitwise superset of new_lease_state
+ */
+ if ((lease_state & breaking_to_requested) != lease_state) {
+ DBG_WARNING("Attempt to upgrade from %"PRIu32" to %"PRIu32" "
+ "- expected %"PRIu32"\n",
+ current_state, lease_state,
+ breaking_to_requested);
+ TALLOC_FREE(lck);
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ if (current_state != lease_state) {
+ current_state = lease_state;
+ }
+
+ status = NT_STATUS_OK;
+
+ if ((lease_state & ~breaking_to_required) != 0) {
+ struct downgrade_lease_additional_state *state;
+
+ DBG_INFO("lease state %"PRIu32" not fully broken from "
+ "%"PRIu32" to %"PRIu32"\n",
+ lease_state,
+ current_state,
+ breaking_to_required);
+
+ breaking_to_requested = breaking_to_required;
+
+ if (current_state & (SMB2_LEASE_WRITE|SMB2_LEASE_HANDLE)) {
+ /*
+ * Here we break in steps, as windows does
+ * see the breaking3 and v2_breaking3 tests.
+ */
+ breaking_to_requested |= SMB2_LEASE_READ;
+ }
+
+ state = talloc_zero(client,
+ struct downgrade_lease_additional_state);
+ if (state == NULL) {
+ TALLOC_FREE(lck);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ state->im = tevent_create_immediate(state);
+ if (state->im == NULL) {
+ TALLOC_FREE(state);
+ TALLOC_FREE(lck);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ state->client = client;
+ state->lease_key = *key;
+ state->break_from = current_state;
+ state->break_to = breaking_to_requested;
+ if (lease_version > 1) {
+ state->new_epoch = epoch;
+ }
+
+ if (current_state & (SMB2_LEASE_WRITE|SMB2_LEASE_HANDLE)) {
+ state->break_flags =
+ SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED;
+ } else {
+ /*
+ * This is an async break without
+ * SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED
+ *
+ * we need to store NONE state in the
+ * database.
+ */
+ current_state = 0;
+ breaking_to_requested = 0;
+ breaking_to_required = 0;
+ breaking = false;
+
+ {
+ NTSTATUS set_status;
+
+ set_status = leases_db_set(
+ &sconn->client->global->client_guid,
+ key,
+ current_state,
+ breaking,
+ breaking_to_requested,
+ breaking_to_required,
+ lease_version,
+ epoch);
+
+ if (!NT_STATUS_IS_OK(set_status)) {
+ DBG_DEBUG("leases_db_set failed: %s\n",
+ nt_errstr(set_status));
+ return set_status;
+ }
+ }
+ }
+
+ tevent_schedule_immediate(state->im,
+ client->raw_ev_ctx,
+ downgrade_lease_additional_trigger,
+ state);
+
+ status = NT_STATUS_OPLOCK_BREAK_IN_PROGRESS;
+ } else {
+ DBG_DEBUG("breaking from %"PRIu32" to %"PRIu32" - "
+ "expected %"PRIu32"\n",
+ current_state,
+ lease_state,
+ breaking_to_requested);
+
+ breaking_to_requested = 0;
+ breaking_to_required = 0;
+ breaking = false;
+ }
+
+ {
+ NTSTATUS set_status;
+
+ set_status = leases_db_set(
+ client_guid,
+ key,
+ current_state,
+ breaking,
+ breaking_to_requested,
+ breaking_to_required,
+ lease_version,
+ epoch);
+
+ if (!NT_STATUS_IS_OK(set_status)) {
+ DBG_DEBUG("leases_db_set failed: %s\n",
+ nt_errstr(set_status));
+ TALLOC_FREE(lck);
+ return set_status;
+ }
+ }
+
+ DBG_DEBUG("Downgrading %s to %"PRIu32" => %s\n",
+ file_id_str_buf(id, &idbuf),
+ lease_state,
+ nt_errstr(status));
+
+ share_mode_wakeup_waiters(id);
+
+ fsps_lease_update(sconn, &id, key);
+
+ TALLOC_FREE(lck);
+
+ DBG_DEBUG("Downgrading %s to %"PRIu32" => %s\n",
+ file_id_str_buf(id, &idbuf),
+ lease_state,
+ nt_errstr(status));
+
+ /*
+ * Dynamic share case. Ensure other opens are copies.
+ * This will only be breaking to NONE.
+ */
+
+ for (i = 1; i < num_file_ids; i++) {
+ lck = get_existing_share_mode_lock(talloc_tos(), ids[i]);
+ if (lck == NULL) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ fsps_lease_update(sconn, &ids[i], key);
+
+ DBG_DEBUG("Downgrading %s to %"PRIu32" => %s\n",
+ file_id_str_buf(ids[i], &idbuf),
+ lease_state,
+ nt_errstr(status));
+
+ TALLOC_FREE(lck);
+ }
+
+ return status;
+}
+
+#define SMB1_BREAK_MESSAGE_LENGTH (smb_size + 8*2)
+
+/****************************************************************************
+ Function to do the waiting before sending a local break.
+****************************************************************************/
+
+static void wait_before_sending_break(void)
+{
+ long wait_time = (long)lp_oplock_break_wait_time();
+
+ if (wait_time) {
+ smb_msleep(wait_time);
+ }
+}
+
+/****************************************************************************
+ Ensure that we have a valid oplock.
+****************************************************************************/
+
+static files_struct *initial_break_processing(
+ struct smbd_server_connection *sconn, struct file_id id,
+ unsigned long file_id)
+{
+ files_struct *fsp = NULL;
+ struct file_id_buf idbuf;
+
+ DBG_NOTICE("called for %s/%u\n"
+ "Current oplocks_open (exclusive = %d, levelII = %d)\n",
+ file_id_str_buf(id, &idbuf),
+ (int)file_id,
+ sconn->oplocks.exclusive_open,
+ sconn->oplocks.level_II_open);
+
+ /*
+ * We need to search the file open table for the
+ * entry containing this dev and inode, and ensure
+ * we have an oplock on it.
+ */
+
+ fsp = file_find_dif(sconn, id, file_id);
+
+ if(fsp == NULL) {
+ /* The file could have been closed in the meantime - return success. */
+ DBG_NOTICE("cannot find open file "
+ "with file_id %s gen_id = %lu, allowing break to "
+ "succeed.\n",
+ file_id_str_buf(id, &idbuf),
+ file_id);
+ return NULL;
+ }
+
+ /* Ensure we have an oplock on the file */
+
+ /*
+ * There is a potential race condition in that an oplock could
+ * have been broken due to another udp request, and yet there are
+ * still oplock break messages being sent in the udp message
+ * queue for this file. So return true if we don't have an oplock,
+ * as we may have just freed it.
+ */
+
+ if(fsp->oplock_type == NO_OPLOCK) {
+ DBG_NOTICE("file %s (file_id = %s gen_id = %"PRIu64") "
+ "has no oplock. "
+ "Allowing break to succeed regardless.\n",
+ fsp_str_dbg(fsp),
+ file_id_str_buf(id, &idbuf),
+ fh_get_gen_id(fsp->fh));
+ return NULL;
+ }
+
+ return fsp;
+}
+
+static void oplock_timeout_handler(struct tevent_context *ctx,
+ struct tevent_timer *te,
+ struct timeval now,
+ void *private_data)
+{
+ files_struct *fsp = (files_struct *)private_data;
+
+ SMB_ASSERT(fsp->sent_oplock_break != NO_BREAK_SENT);
+
+ /* Remove the timed event handler. */
+ TALLOC_FREE(fsp->oplock_timeout);
+ DEBUG(0, ("Oplock break failed for file %s -- replying anyway\n",
+ fsp_str_dbg(fsp)));
+ remove_oplock(fsp);
+}
+
+/*******************************************************************
+ Add a timeout handler waiting for the client reply.
+*******************************************************************/
+
+static void add_oplock_timeout_handler(files_struct *fsp)
+{
+ if (fsp->oplock_timeout != NULL) {
+ DEBUG(0, ("Logic problem -- have an oplock event hanging "
+ "around\n"));
+ }
+
+ fsp->oplock_timeout =
+ tevent_add_timer(fsp->conn->sconn->ev_ctx, fsp,
+ timeval_current_ofs(OPLOCK_BREAK_TIMEOUT, 0),
+ oplock_timeout_handler, fsp);
+
+ if (fsp->oplock_timeout == NULL) {
+ DEBUG(0, ("Could not add oplock timeout handler\n"));
+ }
+}
+
+/*******************************************************************
+ This handles the generic oplock break message from another smbd.
+*******************************************************************/
+
+static void process_oplock_break_message(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id src,
+ DATA_BLOB *data)
+{
+ struct oplock_break_message *msg = NULL;
+ enum ndr_err_code ndr_err;
+ files_struct *fsp;
+ bool use_kernel;
+ struct smbd_server_connection *sconn =
+ talloc_get_type_abort(private_data,
+ struct smbd_server_connection);
+ struct server_id self = messaging_server_id(sconn->msg_ctx);
+ struct kernel_oplocks *koplocks = sconn->oplocks.kernel_ops;
+ uint16_t break_from;
+ uint16_t break_to;
+ bool break_needed = true;
+
+ smb_vfs_assert_allowed();
+
+ msg = talloc(talloc_tos(), struct oplock_break_message);
+ if (msg == NULL) {
+ DBG_WARNING("talloc failed\n");
+ return;
+ }
+
+ ndr_err = ndr_pull_struct_blob_all(
+ data,
+ msg,
+ msg,
+ (ndr_pull_flags_fn_t)ndr_pull_oplock_break_message);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_DEBUG("ndr_pull_oplock_break_message failed: %s\n",
+ ndr_errstr(ndr_err));
+ TALLOC_FREE(msg);
+ return;
+ }
+ if (DEBUGLEVEL >= 10) {
+ struct server_id_buf buf;
+ DBG_DEBUG("Got break message from %s\n",
+ server_id_str_buf(src, &buf));
+ NDR_PRINT_DEBUG(oplock_break_message, msg);
+ }
+
+ break_to = msg->break_to;
+ fsp = initial_break_processing(sconn, msg->id, msg->share_file_id);
+
+ TALLOC_FREE(msg);
+
+ if (fsp == NULL) {
+ /* We hit a race here. Break messages are sent, and before we
+ * get to process this message, we have closed the file. */
+ DEBUG(3, ("Did not find fsp\n"));
+ return;
+ }
+
+ break_from = fsp_lease_type(fsp);
+
+ if (fsp->oplock_type != LEASE_OPLOCK) {
+ if (fsp->sent_oplock_break != NO_BREAK_SENT) {
+ /*
+ * Nothing to do anymore
+ */
+ DEBUG(10, ("fsp->sent_oplock_break = %d\n",
+ fsp->sent_oplock_break));
+ return;
+ }
+ }
+
+ if (!(global_client_caps & CAP_LEVEL_II_OPLOCKS)) {
+ DEBUG(10, ("client_caps without level2 oplocks\n"));
+ break_to &= ~SMB2_LEASE_READ;
+ }
+
+ use_kernel = lp_kernel_oplocks(SNUM(fsp->conn)) &&
+ (koplocks != NULL);
+ if (use_kernel) {
+ DEBUG(10, ("Kernel oplocks don't allow level2\n"));
+ break_to &= ~SMB2_LEASE_READ;
+ }
+
+ if (!lp_level2_oplocks(SNUM(fsp->conn))) {
+ DEBUG(10, ("no level2 oplocks by config\n"));
+ break_to &= ~SMB2_LEASE_READ;
+ }
+
+ if (fsp->oplock_type == LEASE_OPLOCK) {
+ const struct GUID *client_guid = fsp_client_guid(fsp);
+ struct share_mode_lock *lck;
+ uint32_t current_state;
+ uint32_t breaking_to_requested, breaking_to_required;
+ bool breaking;
+ uint16_t lease_version, epoch;
+ NTSTATUS status;
+
+ lck = get_existing_share_mode_lock(
+ talloc_tos(), fsp->file_id);
+ if (lck == NULL) {
+ /*
+ * We hit a race here. Break messages are sent, and
+ * before we get to process this message, we have closed
+ * the file.
+ */
+ DEBUG(3, ("Did not find share_mode\n"));
+ return;
+ }
+
+ status = leases_db_get(client_guid,
+ &fsp->lease->lease.lease_key,
+ &fsp->file_id,
+ &current_state,
+ &breaking,
+ &breaking_to_requested,
+ &breaking_to_required,
+ &lease_version,
+ &epoch);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("leases_db_get returned %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(lck);
+ return;
+ }
+
+ break_from = current_state;
+ break_to &= current_state;
+
+ if (breaking) {
+ break_to &= breaking_to_required;
+ if (breaking_to_required != break_to) {
+ /*
+ * Note we don't increment the epoch
+ * here, which might be a bug in
+ * Windows too...
+ */
+ breaking_to_required = break_to;
+ }
+ break_needed = false;
+ } else if (current_state == break_to) {
+ break_needed = false;
+ } else if (current_state == SMB2_LEASE_READ) {
+ current_state = SMB2_LEASE_NONE;
+ /* Need to increment the epoch */
+ epoch += 1;
+ } else {
+ breaking = true;
+ breaking_to_required = break_to;
+ breaking_to_requested = break_to;
+ /* Need to increment the epoch */
+ epoch += 1;
+ }
+
+ {
+ NTSTATUS set_status;
+
+ set_status = leases_db_set(
+ client_guid,
+ &fsp->lease->lease.lease_key,
+ current_state,
+ breaking,
+ breaking_to_requested,
+ breaking_to_required,
+ lease_version,
+ epoch);
+
+ if (!NT_STATUS_IS_OK(set_status)) {
+ DBG_DEBUG("leases_db_set failed: %s\n",
+ nt_errstr(set_status));
+ return;
+ }
+ }
+
+ /* Ensure we're in sync with current lease state. */
+ fsp_lease_update(fsp);
+
+ TALLOC_FREE(lck);
+ }
+
+ if (!break_needed) {
+ DEBUG(10,("%s: skip break\n", __func__));
+ return;
+ }
+
+ if (break_from == SMB2_LEASE_NONE) {
+ struct file_id_buf idbuf;
+ DBG_NOTICE("Already downgraded oplock to none on %s: %s\n",
+ file_id_str_buf(fsp->file_id, &idbuf),
+ fsp_str_dbg(fsp));
+ return;
+ }
+
+ DEBUG(10, ("break_from=%u, break_to=%u\n",
+ (unsigned)break_from, (unsigned)break_to));
+
+ if (break_from == break_to) {
+ struct file_id_buf idbuf;
+ DBG_NOTICE("Already downgraded oplock to %u on %s: %s\n",
+ (unsigned)break_to,
+ file_id_str_buf(fsp->file_id, &idbuf),
+ fsp_str_dbg(fsp));
+ return;
+ }
+
+ /* Need to wait before sending a break
+ message if we sent ourselves this message. */
+ if (server_id_equal(&self, &src)) {
+ wait_before_sending_break();
+ }
+
+#if defined(WITH_SMB1SERVER)
+ if (sconn->using_smb2) {
+#endif
+ send_break_message_smb2(fsp, break_from, break_to);
+#if defined(WITH_SMB1SERVER)
+ } else {
+ send_break_message_smb1(fsp, (break_to & SMB2_LEASE_READ) ?
+ OPLOCKLEVEL_II : OPLOCKLEVEL_NONE);
+ }
+#endif
+
+ if ((break_from == SMB2_LEASE_READ) &&
+ (break_to == SMB2_LEASE_NONE)) {
+ /*
+ * This is an async break without a reply and thus no timeout
+ *
+ * leases are handled above.
+ */
+ if (fsp->oplock_type != LEASE_OPLOCK) {
+ remove_oplock(fsp);
+ }
+ return;
+ }
+ if (fsp->oplock_type == LEASE_OPLOCK) {
+ return;
+ }
+
+ fsp->sent_oplock_break = (break_to & SMB2_LEASE_READ) ?
+ LEVEL_II_BREAK_SENT:BREAK_TO_NONE_SENT;
+
+ add_oplock_timeout_handler(fsp);
+}
+
+/*******************************************************************
+ This handles the kernel oplock break message.
+*******************************************************************/
+
+static void process_kernel_oplock_break(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id src,
+ DATA_BLOB *data)
+{
+ struct file_id id;
+ struct file_id_buf idbuf;
+ unsigned long file_id;
+ files_struct *fsp;
+ struct smbd_server_connection *sconn =
+ talloc_get_type_abort(private_data,
+ struct smbd_server_connection);
+ struct server_id_buf tmp;
+
+ if (data->data == NULL) {
+ DEBUG(0, ("Got NULL buffer\n"));
+ return;
+ }
+
+ if (data->length != MSG_SMB_KERNEL_BREAK_SIZE) {
+ DEBUG(0, ("Got invalid msg len %d\n", (int)data->length));
+ return;
+ }
+
+ /* Pull the data from the message. */
+ pull_file_id_24((char *)data->data, &id);
+ file_id = (unsigned long)IVAL(data->data, 24);
+
+ DBG_DEBUG("Got kernel oplock break message from pid %s: %s/%u\n",
+ server_id_str_buf(src, &tmp),
+ file_id_str_buf(id, &idbuf),
+ (unsigned int)file_id);
+
+ fsp = initial_break_processing(sconn, id, file_id);
+
+ if (fsp == NULL) {
+ DEBUG(3, ("Got a kernel oplock break message for a file "
+ "I don't know about\n"));
+ return;
+ }
+
+ if (fsp->sent_oplock_break != NO_BREAK_SENT) {
+ /* This is ok, kernel oplocks come in completely async */
+ DEBUG(3, ("Got a kernel oplock request while waiting for a "
+ "break reply\n"));
+ return;
+ }
+
+#if defined(WITH_SMB1SERVER)
+ if (sconn->using_smb2) {
+#endif
+ send_break_message_smb2(fsp, 0, OPLOCKLEVEL_NONE);
+#if defined(WITH_SMB1SERVER)
+ } else {
+ send_break_message_smb1(fsp, OPLOCKLEVEL_NONE);
+ }
+#endif
+
+ fsp->sent_oplock_break = BREAK_TO_NONE_SENT;
+
+ add_oplock_timeout_handler(fsp);
+}
+
+static void send_break_to_none(struct messaging_context *msg_ctx,
+ const struct file_id *id,
+ const struct share_mode_entry *e)
+{
+ NTSTATUS status;
+ status = send_break_message(msg_ctx, id, e, OPLOCK_NONE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("send_break_message failed: %s\n",
+ nt_errstr(status));
+ }
+}
+struct break_to_none_state {
+ struct smbd_server_connection *sconn;
+ struct file_id id;
+ struct smb2_lease_key lease_key;
+ struct GUID client_guid;
+ size_t num_read_leases;
+ uint32_t total_lease_types;
+};
+
+static bool do_break_lease_to_none(struct share_mode_entry *e,
+ void *private_data)
+{
+ struct break_to_none_state *state = private_data;
+ uint32_t current_state = 0;
+ bool our_own;
+ NTSTATUS status;
+
+ DBG_DEBUG("lease_key=%"PRIu64"/%"PRIu64"\n",
+ e->lease_key.data[0],
+ e->lease_key.data[1]);
+
+ status = leases_db_get(&e->client_guid,
+ &e->lease_key,
+ &state->id,
+ &current_state,
+ NULL, /* breaking */
+ NULL, /* breaking_to_requested */
+ NULL, /* breaking_to_required */
+ NULL, /* lease_version */
+ NULL); /* epoch */
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("leases_db_get failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ state->total_lease_types |= current_state;
+
+ if ((current_state & SMB2_LEASE_READ) == 0) {
+ return false;
+ }
+
+ state->num_read_leases += 1;
+
+ our_own = smb2_lease_equal(&state->client_guid,
+ &state->lease_key,
+ &e->client_guid,
+ &e->lease_key);
+ if (our_own) {
+ DEBUG(10, ("Don't break our own lease\n"));
+ return false;
+ }
+
+ DBG_DEBUG("Breaking %"PRIu64"/%"PRIu64" to none\n",
+ e->lease_key.data[0],
+ e->lease_key.data[1]);
+
+ send_break_to_none(state->sconn->msg_ctx, &state->id, e);
+
+ return false;
+}
+
+static bool do_break_oplock_to_none(struct share_mode_entry *e,
+ bool *modified,
+ void *private_data)
+{
+ struct break_to_none_state *state = private_data;
+
+ if (e->op_type == LEASE_OPLOCK) {
+ /*
+ * Already being taken care of
+ */
+ return false;
+ }
+
+ /*
+ * As there could have been multiple writes waiting at the
+ * lock_share_entry gate we may not be the first to
+ * enter. Hence the state of the op_types in the share mode
+ * entries may be partly NO_OPLOCK and partly LEVEL_II
+ * oplock. It will do no harm to re-send break messages to
+ * those smbd's that are still waiting their turn to remove
+ * their LEVEL_II state, and also no harm to ignore existing
+ * NO_OPLOCK states. JRA.
+ */
+
+ DBG_DEBUG("e->op_type == %d\n", e->op_type);
+
+ state->total_lease_types |= map_oplock_to_lease_type(e->op_type);
+
+ if (e->op_type == NO_OPLOCK) {
+ return false;
+ }
+
+ state->num_read_leases += 1;
+
+ /* Paranoia .... */
+ SMB_ASSERT(!EXCLUSIVE_OPLOCK_TYPE(e->op_type));
+
+ send_break_to_none(state->sconn->msg_ctx, &state->id, e);
+
+ return false;
+}
+
+/****************************************************************************
+ This function is called on any file modification or lock request. If a file
+ is level 2 oplocked then it must tell all other level 2 holders to break to
+ none.
+****************************************************************************/
+
+static void contend_level2_oplocks_begin_default(files_struct *fsp,
+ enum level2_contention_type type)
+{
+ struct break_to_none_state state = {
+ .sconn = fsp->conn->sconn, .id = fsp->file_id,
+ };
+ struct share_mode_lock *lck = NULL;
+ uint32_t fsp_lease = fsp_lease_type(fsp);
+ bool ok, has_read_lease;
+
+ /*
+ * If this file is level II oplocked then we need
+ * to grab the shared memory lock and inform all
+ * other files with a level II lock that they need
+ * to flush their read caches. We keep the lock over
+ * the shared memory area whilst doing this.
+ */
+
+ if (fsp_lease & SMB2_LEASE_WRITE) {
+ /*
+ * There can't be any level2 oplocks, we're alone.
+ */
+ return;
+ }
+
+ has_read_lease = file_has_read_lease(fsp);
+ if (!has_read_lease) {
+ DEBUG(10, ("No read oplocks around\n"));
+ return;
+ }
+
+ if (fsp->oplock_type == LEASE_OPLOCK) {
+ state.client_guid = *fsp_client_guid(fsp);
+ state.lease_key = fsp->lease->lease.lease_key;
+ DEBUG(10, ("Breaking through lease key %"PRIu64"/%"PRIu64"\n",
+ state.lease_key.data[0],
+ state.lease_key.data[1]));
+ }
+
+ lck = get_existing_share_mode_lock(talloc_tos(), fsp->file_id);
+ if (lck == NULL) {
+ struct file_id_buf idbuf;
+ DBG_WARNING("failed to lock share mode entry for file %s.\n",
+ file_id_str_buf(state.id, &idbuf));
+ return;
+ }
+
+ /*
+ * Walk leases and oplocks separately: We have to send one break per
+ * lease. If we have multiple share_mode_entry having a common lease,
+ * we would break the lease twice if we don't walk the leases list
+ * separately.
+ */
+
+ ok = share_mode_forall_leases(lck, do_break_lease_to_none, &state);
+ if (!ok) {
+ DBG_WARNING("share_mode_forall_leases failed\n");
+ }
+
+ ok = share_mode_forall_entries(lck, do_break_oplock_to_none, &state);
+ if (!ok) {
+ DBG_WARNING("share_mode_forall_entries failed\n");
+ }
+
+ {
+ /*
+ * Lazy update here. It might be that all leases
+ * have gone in the meantime.
+ */
+ uint32_t acc, sh, ls;
+ share_mode_flags_get(lck, &acc, &sh, &ls);
+ ls = state.total_lease_types;
+ share_mode_flags_set(lck, acc, sh, ls, NULL);
+ }
+
+ TALLOC_FREE(lck);
+}
+
+void smbd_contend_level2_oplocks_begin(files_struct *fsp,
+ enum level2_contention_type type)
+{
+ contend_level2_oplocks_begin_default(fsp, type);
+}
+
+void smbd_contend_level2_oplocks_end(files_struct *fsp,
+ enum level2_contention_type type)
+{
+ return;
+}
+
+/****************************************************************************
+ Linearize a share mode entry struct to an internal oplock break message.
+****************************************************************************/
+
+void share_mode_entry_to_message(char *msg, const struct file_id *id,
+ const struct share_mode_entry *e)
+{
+ SIVAL(msg,OP_BREAK_MSG_PID_OFFSET,(uint32_t)e->pid.pid);
+ SBVAL(msg,OP_BREAK_MSG_MID_OFFSET,e->op_mid);
+ SSVAL(msg,OP_BREAK_MSG_OP_TYPE_OFFSET,e->op_type);
+ SIVAL(msg,OP_BREAK_MSG_ACCESS_MASK_OFFSET,e->access_mask);
+ SIVAL(msg,OP_BREAK_MSG_SHARE_ACCESS_OFFSET,e->share_access);
+ SIVAL(msg,OP_BREAK_MSG_PRIV_OFFSET,e->private_options);
+ SIVAL(msg,OP_BREAK_MSG_TIME_SEC_OFFSET,(uint32_t)e->time.tv_sec);
+ SIVAL(msg,OP_BREAK_MSG_TIME_USEC_OFFSET,(uint32_t)e->time.tv_usec);
+ /*
+ * "id" used to be part of share_mode_entry, thus the strange
+ * place to put this. Feel free to move somewhere else :-)
+ */
+ push_file_id_24(msg+OP_BREAK_MSG_DEV_OFFSET, id);
+ SIVAL(msg,OP_BREAK_MSG_FILE_ID_OFFSET,e->share_file_id);
+ SIVAL(msg,OP_BREAK_MSG_UID_OFFSET,e->uid);
+ SSVAL(msg,OP_BREAK_MSG_FLAGS_OFFSET,e->flags);
+ SIVAL(msg,OP_BREAK_MSG_NAME_HASH_OFFSET,e->name_hash);
+ SIVAL(msg,OP_BREAK_MSG_VNN_OFFSET,e->pid.vnn);
+}
+
+/****************************************************************************
+ De-linearize an internal oplock break message to a share mode entry struct.
+****************************************************************************/
+
+void message_to_share_mode_entry(struct file_id *id,
+ struct share_mode_entry *e,
+ const char *msg)
+{
+ e->pid = (struct server_id){
+ .pid = (pid_t)IVAL(msg, OP_BREAK_MSG_PID_OFFSET),
+ .vnn = IVAL(msg, OP_BREAK_MSG_VNN_OFFSET),
+ };
+ e->op_mid = BVAL(msg,OP_BREAK_MSG_MID_OFFSET);
+ e->op_type = SVAL(msg,OP_BREAK_MSG_OP_TYPE_OFFSET);
+ e->access_mask = IVAL(msg,OP_BREAK_MSG_ACCESS_MASK_OFFSET);
+ e->share_access = IVAL(msg,OP_BREAK_MSG_SHARE_ACCESS_OFFSET);
+ e->private_options = IVAL(msg,OP_BREAK_MSG_PRIV_OFFSET);
+ e->time.tv_sec = (time_t)IVAL(msg,OP_BREAK_MSG_TIME_SEC_OFFSET);
+ e->time.tv_usec = (int)IVAL(msg,OP_BREAK_MSG_TIME_USEC_OFFSET);
+ /*
+ * "id" used to be part of share_mode_entry, thus the strange
+ * place to put this. Feel free to move somewhere else :-)
+ */
+ pull_file_id_24(msg+OP_BREAK_MSG_DEV_OFFSET, id);
+ e->share_file_id = (unsigned long)IVAL(msg,OP_BREAK_MSG_FILE_ID_OFFSET);
+ e->uid = (uint32_t)IVAL(msg,OP_BREAK_MSG_UID_OFFSET);
+ e->flags = (uint16_t)SVAL(msg,OP_BREAK_MSG_FLAGS_OFFSET);
+ e->name_hash = IVAL(msg, OP_BREAK_MSG_NAME_HASH_OFFSET);
+}
+
+/****************************************************************************
+ Setup oplocks for this process.
+****************************************************************************/
+
+bool init_oplocks(struct smbd_server_connection *sconn)
+{
+ DEBUG(3,("init_oplocks: initializing messages.\n"));
+
+ messaging_register(sconn->msg_ctx, sconn, MSG_SMB_BREAK_REQUEST,
+ process_oplock_break_message);
+ messaging_register(sconn->msg_ctx, sconn, MSG_SMB_KERNEL_BREAK,
+ process_kernel_oplock_break);
+ return true;
+}
+
+void init_kernel_oplocks(struct smbd_server_connection *sconn)
+{
+ struct kernel_oplocks *koplocks = sconn->oplocks.kernel_ops;
+
+ /* only initialize once */
+ if (koplocks == NULL) {
+#ifdef HAVE_KERNEL_OPLOCKS_LINUX
+ koplocks = linux_init_kernel_oplocks(sconn);
+#endif
+ sconn->oplocks.kernel_ops = koplocks;
+ }
+}
diff --git a/source3/smbd/smb2_pipes.c b/source3/smbd/smb2_pipes.c
new file mode 100644
index 0000000..8f87867
--- /dev/null
+++ b/source3/smbd/smb2_pipes.c
@@ -0,0 +1,150 @@
+/*
+ Unix SMB/CIFS implementation.
+ Pipe SMB reply routines
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1996-1998
+ Copyright (C) Paul Ashton 1997-1998.
+ Copyright (C) Jeremy Allison 2005.
+
+ 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/>.
+*/
+/*
+ This file handles reply_ calls on named pipes that the server
+ makes to handle specific protocols
+*/
+
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "libcli/security/security.h"
+#include "rpc_server/srv_pipe_hnd.h"
+#include "auth/auth_util.h"
+#include "librpc/rpc/dcerpc_helper.h"
+
+NTSTATUS open_np_file(struct smb_request *smb_req, const char *name,
+ struct files_struct **pfsp)
+{
+ struct smbXsrv_connection *xconn = smb_req->xconn;
+ struct connection_struct *conn = smb_req->conn;
+ struct files_struct *fsp;
+ struct smb_filename *smb_fname = NULL;
+ struct auth_session_info *session_info = conn->session_info;
+ NTSTATUS status;
+
+ status = file_new(smb_req, conn, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("file_new failed: %s\n", nt_errstr(status)));
+ return status;
+ }
+
+ fsp->conn = conn;
+ fsp_set_fd(fsp, -1);
+ fsp->vuid = smb_req->vuid;
+ fsp->fsp_flags.can_lock = false;
+ fsp->access_mask = FILE_READ_DATA | FILE_WRITE_DATA;
+
+ smb_fname = synthetic_smb_fname(talloc_tos(),
+ name,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname == NULL) {
+ file_free(smb_req, fsp);
+ return NT_STATUS_NO_MEMORY;
+ }
+ status = fsp_set_smb_fname(fsp, smb_fname);
+ TALLOC_FREE(smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ file_free(smb_req, fsp);
+ return status;
+ }
+
+ if (smb_req->smb2req != NULL && smb_req->smb2req->was_encrypted) {
+ struct security_token *security_token = NULL;
+ uint16_t dialect = xconn->smb2.server.dialect;
+ uint16_t srv_smb_encrypt = DCERPC_SMB_ENCRYPTION_REQUIRED;
+ uint16_t cipher = xconn->smb2.server.cipher;
+ struct dom_sid smb3_sid = global_sid_Samba_SMB3;
+ size_t num_smb3_sids;
+ bool ok;
+
+ session_info = copy_session_info(fsp, conn->session_info);
+ if (session_info == NULL) {
+ DBG_ERR("Failed to copy session info\n");
+ file_free(smb_req, fsp);
+ return NT_STATUS_NO_MEMORY;
+ }
+ security_token = session_info->security_token;
+
+ /*
+ * Security check:
+ *
+ * Make sure we don't have a SMB3 SID in the security token!
+ */
+ num_smb3_sids = security_token_count_flag_sids(security_token,
+ &smb3_sid,
+ 3,
+ NULL);
+ if (num_smb3_sids != 0) {
+ DBG_ERR("ERROR: %zu SMB3 SIDs have already been "
+ "detected in the security token!\n",
+ num_smb3_sids);
+ file_free(smb_req, fsp);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ ok = sid_append_rid(&smb3_sid, dialect);
+ ok &= sid_append_rid(&smb3_sid, srv_smb_encrypt);
+ ok &= sid_append_rid(&smb3_sid, cipher);
+
+ if (!ok) {
+ DBG_ERR("sid too small\n");
+ file_free(smb_req, fsp);
+ return NT_STATUS_BUFFER_TOO_SMALL;
+ }
+
+ status = add_sid_to_array_unique(security_token,
+ &smb3_sid,
+ &security_token->sids,
+ &security_token->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Failed to add SMB3 SID to security token\n");
+ file_free(smb_req, fsp);
+ return status;
+ }
+
+ fsp->fsp_flags.encryption_required = true;
+ }
+
+ status = np_open(fsp, name,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ session_info,
+ conn->sconn->ev_ctx,
+ conn->sconn->msg_ctx,
+ conn->sconn->dce_ctx,
+ &fsp->fake_file_handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("np_open(%s) returned %s\n", name,
+ nt_errstr(status)));
+ file_free(smb_req, fsp);
+ return status;
+ }
+
+ *pfsp = fsp;
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/smb2_posix.c b/source3/smbd/smb2_posix.c
new file mode 100644
index 0000000..9623e59
--- /dev/null
+++ b/source3/smbd/smb2_posix.c
@@ -0,0 +1,73 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB2 POSIX code.
+ Copyright (C) Jeremy Allison 2022
+
+ 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 "smbd/smbd.h"
+#include "passdb/lookup_sid.h"
+#include "librpc/gen_ndr/ndr_security.h"
+#include "librpc/gen_ndr/smb3posix.h"
+#include "libcli/security/security.h"
+
+void smb3_file_posix_information_init(
+ connection_struct *conn,
+ const struct stat_ex *st,
+ uint32_t reparse_tag,
+ uint32_t dos_attributes,
+ struct smb3_file_posix_information *dst)
+{
+ *dst = (struct smb3_file_posix_information) {
+ .end_of_file = get_file_size_stat(st),
+ .allocation_size = SMB_VFS_GET_ALLOC_SIZE(conn,NULL,st),
+ .inode = SMB_VFS_FS_FILE_ID(conn, st),
+ .device = st->st_ex_dev,
+ .creation_time = unix_timespec_to_nt_time(st->st_ex_btime),
+ .last_access_time = unix_timespec_to_nt_time(st->st_ex_atime),
+ .last_write_time = unix_timespec_to_nt_time(st->st_ex_mtime),
+ .change_time = unix_timespec_to_nt_time(st->st_ex_ctime),
+ .cc.nlinks = st->st_ex_nlink,
+ .cc.reparse_tag = reparse_tag,
+ .cc.posix_perms = unix_perms_to_wire(st->st_ex_mode & ~S_IFMT),
+ .cc.owner = global_sid_NULL,
+ .cc.group = global_sid_NULL,
+ };
+
+ if (st->st_ex_uid != (uid_t)-1) {
+ uid_to_sid(&dst->cc.owner, st->st_ex_uid);
+ }
+ if (st->st_ex_gid != (uid_t)-1) {
+ gid_to_sid(&dst->cc.group, st->st_ex_gid);
+ }
+
+ switch (st->st_ex_mode & S_IFMT) {
+ case S_IFREG:
+ dst->file_attributes = dos_attributes;
+ break;
+ case S_IFDIR:
+ dst->file_attributes = dos_attributes | FILE_ATTRIBUTE_DIRECTORY;
+ break;
+ default:
+ /*
+ * All non-directory or regular files are reported
+ * as reparse points. Client may or may not be able
+ * to access these.
+ */
+ dst->file_attributes = FILE_ATTRIBUTE_REPARSE_POINT;
+ break;
+ }
+}
diff --git a/source3/smbd/smb2_process.c b/source3/smbd/smb2_process.c
new file mode 100644
index 0000000..7366427
--- /dev/null
+++ b/source3/smbd/smb2_process.c
@@ -0,0 +1,2073 @@
+/*
+ Unix SMB/CIFS implementation.
+ process incoming packets - main loop
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Volker Lendecke 2005-2007
+
+ 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 "../lib/tsocket/tsocket.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "smbd/smbXsrv_open.h"
+#include "librpc/gen_ndr/netlogon.h"
+#include "../lib/async_req/async_sock.h"
+#include "ctdbd_conn.h"
+#include "../lib/util/select.h"
+#include "printing/queue_process.h"
+#include "system/select.h"
+#include "passdb.h"
+#include "auth.h"
+#include "messages.h"
+#include "lib/messages_ctdb.h"
+#include "smbprofile.h"
+#include "rpc_server/spoolss/srv_spoolss_nt.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "../libcli/security/dom_sid.h"
+#include "../libcli/security/security_token.h"
+#include "lib/id_cache.h"
+#include "lib/util/sys_rw_data.h"
+#include "system/threads.h"
+#include "lib/pthreadpool/pthreadpool_tevent.h"
+#include "util_event.h"
+#include "libcli/smb/smbXcli_base.h"
+#include "lib/util/time_basic.h"
+#include "source3/lib/substitute.h"
+#include "source3/smbd/dir.h"
+
+/* Internal message queue for deferred opens. */
+struct pending_message_list {
+ struct pending_message_list *next, *prev;
+ struct timeval request_time; /* When was this first issued? */
+ struct smbd_server_connection *sconn;
+ struct smbXsrv_connection *xconn;
+ struct tevent_timer *te;
+ uint32_t seqnum;
+ bool encrypted;
+ bool processed;
+ DATA_BLOB buf;
+ struct deferred_open_record *open_rec;
+};
+
+static struct pending_message_list *get_deferred_open_message_smb(
+ struct smbd_server_connection *sconn, uint64_t mid);
+
+#if !defined(WITH_SMB1SERVER)
+bool smb1_srv_send(struct smbXsrv_connection *xconn,
+ char *buffer,
+ bool do_signing,
+ uint32_t seqnum,
+ bool do_encrypt)
+{
+ size_t len = 0;
+ ssize_t ret;
+ len = smb_len_large(buffer) + 4;
+ ret = write_data(xconn->transport.sock, buffer, len);
+ return (ret > 0);
+}
+#endif
+
+/*******************************************************************
+ Setup the word count and byte count for a smb1 message.
+********************************************************************/
+
+size_t srv_smb1_set_message(char *buf,
+ size_t num_words,
+ size_t num_bytes,
+ bool zero)
+{
+ if (zero && (num_words || num_bytes)) {
+ memset(buf + smb_size,'\0',num_words*2 + num_bytes);
+ }
+ SCVAL(buf,smb_wct,num_words);
+ SSVAL(buf,smb_vwv + num_words*SIZEOFWORD,num_bytes);
+ smb_setlen(buf,(smb_size + num_words*2 + num_bytes - 4));
+ return (smb_size + num_words*2 + num_bytes);
+}
+
+NTSTATUS read_packet_remainder(int fd, char *buffer,
+ unsigned int timeout, ssize_t len)
+{
+ NTSTATUS status;
+
+ if (len <= 0) {
+ return NT_STATUS_OK;
+ }
+
+ status = read_fd_with_timeout(fd, buffer, len, len, timeout, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ char addr[INET6_ADDRSTRLEN];
+ DEBUG(0, ("read_fd_with_timeout failed for client %s read "
+ "error = %s.\n",
+ get_peer_addr(fd, addr, sizeof(addr)),
+ nt_errstr(status)));
+ }
+ return status;
+}
+
+#if !defined(WITH_SMB1SERVER)
+static NTSTATUS smb2_receive_raw_talloc(TALLOC_CTX *mem_ctx,
+ struct smbXsrv_connection *xconn,
+ int sock,
+ char **buffer, unsigned int timeout,
+ size_t *p_unread, size_t *plen)
+{
+ char lenbuf[4];
+ size_t len;
+ NTSTATUS status;
+
+ *p_unread = 0;
+
+ status = read_smb_length_return_keepalive(sock, lenbuf, timeout,
+ &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * The +4 here can't wrap, we've checked the length above already.
+ */
+
+ *buffer = talloc_array(mem_ctx, char, len+4);
+
+ if (*buffer == NULL) {
+ DEBUG(0, ("Could not allocate inbuf of length %d\n",
+ (int)len+4));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ memcpy(*buffer, lenbuf, sizeof(lenbuf));
+
+ status = read_packet_remainder(sock, (*buffer)+4, timeout, len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ *plen = len + 4;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smb2_receive_talloc(TALLOC_CTX *mem_ctx,
+ struct smbXsrv_connection *xconn,
+ int sock,
+ char **buffer, unsigned int timeout,
+ size_t *p_unread, bool *p_encrypted,
+ size_t *p_len,
+ uint32_t *seqnum,
+ bool trusted_channel)
+{
+ size_t len = 0;
+ NTSTATUS status;
+
+ *p_encrypted = false;
+
+ status = smb2_receive_raw_talloc(mem_ctx, xconn, sock, buffer, timeout,
+ p_unread, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(NT_STATUS_EQUAL(status, NT_STATUS_END_OF_FILE)?5:1,
+ ("smb2_receive_raw_talloc failed for client %s "
+ "read error = %s.\n",
+ smbXsrv_connection_dbg(xconn),
+ nt_errstr(status)) );
+ return status;
+ }
+
+ *p_len = len;
+ return NT_STATUS_OK;
+}
+#endif
+
+NTSTATUS receive_smb_talloc(TALLOC_CTX *mem_ctx,
+ struct smbXsrv_connection *xconn,
+ int sock,
+ char **buffer, unsigned int timeout,
+ size_t *p_unread, bool *p_encrypted,
+ size_t *p_len,
+ uint32_t *seqnum,
+ bool trusted_channel)
+{
+#if defined(WITH_SMB1SERVER)
+ return smb1_receive_talloc(mem_ctx, xconn, sock, buffer, timeout,
+ p_unread, p_encrypted, p_len, seqnum,
+ trusted_channel);
+#else
+ return smb2_receive_talloc(mem_ctx, xconn, sock, buffer, timeout,
+ p_unread, p_encrypted, p_len, seqnum,
+ trusted_channel);
+#endif
+}
+
+/****************************************************************************
+ Function to delete a sharing violation open message by mid.
+****************************************************************************/
+
+void remove_deferred_open_message_smb(struct smbXsrv_connection *xconn,
+ uint64_t mid)
+{
+ struct smbd_server_connection *sconn = xconn->client->sconn;
+ struct pending_message_list *pml;
+
+ if (sconn->using_smb2) {
+ remove_deferred_open_message_smb2(xconn, mid);
+ return;
+ }
+
+ for (pml = sconn->deferred_open_queue; pml; pml = pml->next) {
+ if (mid == (uint64_t)SVAL(pml->buf.data,smb_mid)) {
+ DEBUG(10,("remove_deferred_open_message_smb: "
+ "deleting mid %llu len %u\n",
+ (unsigned long long)mid,
+ (unsigned int)pml->buf.length ));
+ DLIST_REMOVE(sconn->deferred_open_queue, pml);
+ TALLOC_FREE(pml);
+ return;
+ }
+ }
+}
+
+static void smbd_deferred_open_timer(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval _tval,
+ void *private_data)
+{
+ struct pending_message_list *msg = talloc_get_type(private_data,
+ struct pending_message_list);
+ struct smbd_server_connection *sconn = msg->sconn;
+ struct smbXsrv_connection *xconn = msg->xconn;
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ uint64_t mid = (uint64_t)SVAL(msg->buf.data,smb_mid);
+ uint8_t *inbuf;
+
+ inbuf = (uint8_t *)talloc_memdup(mem_ctx, msg->buf.data,
+ msg->buf.length);
+ if (inbuf == NULL) {
+ exit_server("smbd_deferred_open_timer: talloc failed\n");
+ return;
+ }
+
+ /* We leave this message on the queue so the open code can
+ know this is a retry. */
+ DEBUG(5,("smbd_deferred_open_timer: trigger mid %llu.\n",
+ (unsigned long long)mid ));
+
+ /* Mark the message as processed so this is not
+ * re-processed in error. */
+ msg->processed = true;
+
+ process_smb(xconn,
+ inbuf,
+ msg->buf.length,
+ 0,
+ msg->seqnum,
+ msg->encrypted);
+
+ /* If it's still there and was processed, remove it. */
+ msg = get_deferred_open_message_smb(sconn, mid);
+ if (msg && msg->processed) {
+ remove_deferred_open_message_smb(xconn, mid);
+ }
+}
+
+/****************************************************************************
+ Move a sharing violation open retry message to the front of the list and
+ schedule it for immediate processing.
+****************************************************************************/
+
+bool schedule_deferred_open_message_smb(struct smbXsrv_connection *xconn,
+ uint64_t mid)
+{
+ struct smbd_server_connection *sconn = xconn->client->sconn;
+ struct pending_message_list *pml;
+ int i = 0;
+
+ if (sconn->using_smb2) {
+ return schedule_deferred_open_message_smb2(xconn, mid);
+ }
+
+ for (pml = sconn->deferred_open_queue; pml; pml = pml->next) {
+ uint64_t msg_mid = (uint64_t)SVAL(pml->buf.data,smb_mid);
+
+ DEBUG(10,("schedule_deferred_open_message_smb: [%d] "
+ "msg_mid = %llu\n",
+ i++,
+ (unsigned long long)msg_mid ));
+
+ if (mid == msg_mid) {
+ struct tevent_timer *te;
+
+ if (pml->processed) {
+ /* A processed message should not be
+ * rescheduled. */
+ DEBUG(0,("schedule_deferred_open_message_smb: LOGIC ERROR "
+ "message mid %llu was already processed\n",
+ (unsigned long long)msg_mid ));
+ continue;
+ }
+
+ DEBUG(10,("schedule_deferred_open_message_smb: "
+ "scheduling mid %llu\n",
+ (unsigned long long)mid ));
+
+ /*
+ * smbd_deferred_open_timer() calls
+ * process_smb() to redispatch the request
+ * including the required impersonation.
+ *
+ * So we can just use the raw tevent_context.
+ */
+ te = tevent_add_timer(xconn->client->raw_ev_ctx,
+ pml,
+ timeval_zero(),
+ smbd_deferred_open_timer,
+ pml);
+ if (!te) {
+ DEBUG(10,("schedule_deferred_open_message_smb: "
+ "event_add_timed() failed, "
+ "skipping mid %llu\n",
+ (unsigned long long)msg_mid ));
+ }
+
+ TALLOC_FREE(pml->te);
+ pml->te = te;
+ DLIST_PROMOTE(sconn->deferred_open_queue, pml);
+ return true;
+ }
+ }
+
+ DEBUG(10,("schedule_deferred_open_message_smb: failed to "
+ "find message mid %llu\n",
+ (unsigned long long)mid ));
+
+ return false;
+}
+
+/****************************************************************************
+ Return true if this mid is on the deferred queue and was not yet processed.
+****************************************************************************/
+
+bool open_was_deferred(struct smbXsrv_connection *xconn, uint64_t mid)
+{
+ struct smbd_server_connection *sconn = xconn->client->sconn;
+ struct pending_message_list *pml;
+
+ if (sconn->using_smb2) {
+ return open_was_deferred_smb2(xconn, mid);
+ }
+
+ for (pml = sconn->deferred_open_queue; pml; pml = pml->next) {
+ if (((uint64_t)SVAL(pml->buf.data,smb_mid)) == mid && !pml->processed) {
+ return True;
+ }
+ }
+ return False;
+}
+
+/****************************************************************************
+ Return the message queued by this mid.
+****************************************************************************/
+
+static struct pending_message_list *get_deferred_open_message_smb(
+ struct smbd_server_connection *sconn, uint64_t mid)
+{
+ struct pending_message_list *pml;
+
+ for (pml = sconn->deferred_open_queue; pml; pml = pml->next) {
+ if (((uint64_t)SVAL(pml->buf.data,smb_mid)) == mid) {
+ return pml;
+ }
+ }
+ return NULL;
+}
+
+/****************************************************************************
+ Get the state data queued by this mid.
+****************************************************************************/
+
+bool get_deferred_open_message_state(struct smb_request *smbreq,
+ struct timeval *p_request_time,
+ struct deferred_open_record **open_rec)
+{
+ struct pending_message_list *pml;
+
+ if (smbreq->sconn->using_smb2) {
+ return get_deferred_open_message_state_smb2(smbreq->smb2req,
+ p_request_time,
+ open_rec);
+ }
+
+ pml = get_deferred_open_message_smb(smbreq->sconn, smbreq->mid);
+ if (!pml) {
+ return false;
+ }
+ if (p_request_time) {
+ *p_request_time = pml->request_time;
+ }
+ if (open_rec != NULL) {
+ *open_rec = pml->open_rec;
+ }
+ return true;
+}
+
+bool push_deferred_open_message_smb(struct smb_request *req,
+ struct timeval timeout,
+ struct file_id id,
+ struct deferred_open_record *open_rec)
+{
+#if defined(WITH_SMB1SERVER)
+ if (req->smb2req) {
+#endif
+ return push_deferred_open_message_smb2(req->smb2req,
+ req->request_time,
+ timeout,
+ id,
+ open_rec);
+#if defined(WITH_SMB1SERVER)
+ } else {
+ return push_deferred_open_message_smb1(req, timeout,
+ id, open_rec);
+ }
+#endif
+}
+
+static void construct_smb1_reply_common(uint8_t cmd, const uint8_t *inbuf,
+ char *outbuf)
+{
+ uint16_t in_flags2 = SVAL(inbuf,smb_flg2);
+ uint16_t out_flags2 = common_flags2;
+
+ out_flags2 |= in_flags2 & FLAGS2_UNICODE_STRINGS;
+ out_flags2 |= in_flags2 & FLAGS2_SMB_SECURITY_SIGNATURES;
+ out_flags2 |= in_flags2 & FLAGS2_SMB_SECURITY_SIGNATURES_REQUIRED;
+
+ srv_smb1_set_message(outbuf,0,0,false);
+
+ SCVAL(outbuf, smb_com, cmd);
+ SIVAL(outbuf,smb_rcls,0);
+ SCVAL(outbuf,smb_flg, FLAG_REPLY | (CVAL(inbuf,smb_flg) & FLAG_CASELESS_PATHNAMES));
+ SSVAL(outbuf,smb_flg2, out_flags2);
+ memset(outbuf+smb_pidhigh,'\0',(smb_tid-smb_pidhigh));
+ memcpy(outbuf+smb_ss_field, inbuf+smb_ss_field, 8);
+
+ SSVAL(outbuf,smb_tid,SVAL(inbuf,smb_tid));
+ SSVAL(outbuf,smb_pid,SVAL(inbuf,smb_pid));
+ SSVAL(outbuf,smb_pidhigh,SVAL(inbuf,smb_pidhigh));
+ SSVAL(outbuf,smb_uid,SVAL(inbuf,smb_uid));
+ SSVAL(outbuf,smb_mid,SVAL(inbuf,smb_mid));
+}
+
+void construct_smb1_reply_common_req(struct smb_request *req, char *outbuf)
+{
+ construct_smb1_reply_common(req->cmd, req->inbuf, outbuf);
+}
+
+/*******************************************************************
+ allocate and initialize a reply packet
+********************************************************************/
+
+bool create_smb1_outbuf(TALLOC_CTX *mem_ctx, struct smb_request *req,
+ const uint8_t *inbuf, char **outbuf,
+ uint8_t num_words, uint32_t num_bytes)
+{
+ size_t smb_len = MIN_SMB_SIZE + VWV(num_words) + num_bytes;
+
+ /*
+ * Protect against integer wrap.
+ * The SMB layer reply can be up to 0xFFFFFF bytes.
+ */
+ if ((num_bytes > 0xffffff) || (smb_len > 0xffffff)) {
+ char *msg;
+ if (asprintf(&msg, "num_bytes too large: %u",
+ (unsigned)num_bytes) == -1) {
+ msg = discard_const_p(char, "num_bytes too large");
+ }
+ smb_panic(msg);
+ }
+
+ /*
+ * Here we include the NBT header for now.
+ */
+ *outbuf = talloc_array(mem_ctx, char,
+ NBT_HDR_SIZE + smb_len);
+ if (*outbuf == NULL) {
+ return false;
+ }
+
+ construct_smb1_reply_common(req->cmd, inbuf, *outbuf);
+ srv_smb1_set_message(*outbuf, num_words, num_bytes, false);
+ /*
+ * Zero out the word area, the caller has to take care of the bcc area
+ * himself
+ */
+ if (num_words != 0) {
+ memset(*outbuf + (NBT_HDR_SIZE + HDR_VWV), 0, VWV(num_words));
+ }
+
+ return true;
+}
+
+void reply_smb1_outbuf(struct smb_request *req, uint8_t num_words, uint32_t num_bytes)
+{
+ char *outbuf;
+ if (!create_smb1_outbuf(req, req, req->inbuf, &outbuf, num_words,
+ num_bytes)) {
+ smb_panic("could not allocate output buffer\n");
+ }
+ req->outbuf = (uint8_t *)outbuf;
+}
+
+bool valid_smb1_header(const uint8_t *inbuf)
+{
+ if (is_encrypted_packet(inbuf)) {
+ return true;
+ }
+ /*
+ * This used to be (strncmp(smb_base(inbuf),"\377SMB",4) == 0)
+ * but it just looks weird to call strncmp for this one.
+ */
+ return (IVAL(smb_base(inbuf), 0) == 0x424D53FF);
+}
+
+/****************************************************************************
+ Process an smb from the client
+****************************************************************************/
+
+static void process_smb2(struct smbXsrv_connection *xconn,
+ uint8_t *inbuf,
+ size_t nread,
+ size_t unread_bytes,
+ uint32_t seqnum,
+ bool encrypted)
+{
+ const uint8_t *inpdu = inbuf + NBT_HDR_SIZE;
+ size_t pdulen = nread - NBT_HDR_SIZE;
+ NTSTATUS status = smbd_smb2_process_negprot(xconn, 0, inpdu, pdulen);
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_server_cleanly("SMB2 negprot fail");
+ }
+}
+
+void process_smb(struct smbXsrv_connection *xconn,
+ uint8_t *inbuf,
+ size_t nread,
+ size_t unread_bytes,
+ uint32_t seqnum,
+ bool encrypted)
+{
+ struct smbd_server_connection *sconn = xconn->client->sconn;
+ int msg_type = CVAL(inbuf,0);
+
+ DO_PROFILE_INC(request);
+
+ DEBUG( 6, ( "got message type 0x%x of len 0x%x\n", msg_type,
+ smb_len(inbuf) ) );
+ DEBUG(3, ("Transaction %d of length %d (%u toread)\n",
+ sconn->trans_num, (int)nread, (unsigned int)unread_bytes));
+
+ if (msg_type != NBSSmessage) {
+ /*
+ * NetBIOS session request, keepalive, etc.
+ */
+ reply_special(xconn, (char *)inbuf, nread);
+ goto done;
+ }
+
+#if defined(WITH_SMB1SERVER)
+ if (sconn->using_smb2) {
+ /* At this point we're not really using smb2,
+ * we make the decision here.. */
+ if (smbd_is_smb2_header(inbuf, nread)) {
+#endif
+ process_smb2(xconn,
+ inbuf,
+ nread,
+ unread_bytes,
+ seqnum,
+ encrypted);
+ return;
+#if defined(WITH_SMB1SERVER)
+ }
+ if (nread >= smb_size && valid_smb1_header(inbuf)
+ && CVAL(inbuf, smb_com) != 0x72) {
+ /* This is a non-negprot SMB1 packet.
+ Disable SMB2 from now on. */
+ sconn->using_smb2 = false;
+ }
+ }
+ process_smb1(xconn, inbuf, nread, unread_bytes, seqnum, encrypted);
+#endif
+
+done:
+ sconn->num_requests++;
+
+ /* The timeout_processing function isn't run nearly
+ often enough to implement 'max log size' without
+ overrunning the size of the file by many megabytes.
+ This is especially true if we are running at debug
+ level 10. Checking every 50 SMBs is a nice
+ tradeoff of performance vs log file size overrun. */
+
+ if ((sconn->num_requests % 50) == 0 &&
+ need_to_check_log_size()) {
+ change_to_root_user();
+ check_log_size();
+ }
+}
+
+NTSTATUS smbXsrv_connection_init_tables(struct smbXsrv_connection *conn,
+ enum protocol_types protocol)
+{
+ NTSTATUS status;
+
+ conn->protocol = protocol;
+
+ if (conn->client->session_table != NULL) {
+ return NT_STATUS_OK;
+ }
+
+ if (protocol >= PROTOCOL_SMB2_02) {
+ status = smb2srv_session_table_init(conn);
+ if (!NT_STATUS_IS_OK(status)) {
+ conn->protocol = PROTOCOL_NONE;
+ return status;
+ }
+
+ status = smb2srv_open_table_init(conn);
+ if (!NT_STATUS_IS_OK(status)) {
+ conn->protocol = PROTOCOL_NONE;
+ return status;
+ }
+ } else {
+#if defined(WITH_SMB1SERVER)
+ status = smb1srv_session_table_init(conn);
+ if (!NT_STATUS_IS_OK(status)) {
+ conn->protocol = PROTOCOL_NONE;
+ return status;
+ }
+
+ status = smb1srv_tcon_table_init(conn);
+ if (!NT_STATUS_IS_OK(status)) {
+ conn->protocol = PROTOCOL_NONE;
+ return status;
+ }
+
+ status = smb1srv_open_table_init(conn);
+ if (!NT_STATUS_IS_OK(status)) {
+ conn->protocol = PROTOCOL_NONE;
+ return status;
+ }
+#else
+ conn->protocol = PROTOCOL_NONE;
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+#endif
+ }
+
+ set_Protocol(protocol);
+ return NT_STATUS_OK;
+}
+
+/**
+ * Create a debug string for the connection
+ *
+ * This is allocated to talloc_tos() or a string constant
+ * in certain corner cases. The returned string should
+ * hence not be free'd directly but only via the talloc stack.
+ */
+const char *smbXsrv_connection_dbg(const struct smbXsrv_connection *xconn)
+{
+ const char *ret = NULL;
+ char *raddr = NULL;
+ char *laddr = NULL;
+ struct GUID_txt_buf guid_buf = {};
+
+ /*
+ * TODO: this can be improved further later...
+ */
+
+ raddr = tsocket_address_string(xconn->remote_address, talloc_tos());
+ if (raddr == NULL) {
+ return "<tsocket_address_string() failed>";
+ }
+ laddr = tsocket_address_string(xconn->local_address, talloc_tos());
+ if (laddr == NULL) {
+ return "<tsocket_address_string() failed>";
+ }
+
+ ret = talloc_asprintf(talloc_tos(),
+ "PID=%d,CLIENT=%s,channel=%"PRIu64",remote=%s,local=%s",
+ getpid(),
+ GUID_buf_string(&xconn->smb2.client.guid, &guid_buf),
+ xconn->channel_id,
+ raddr,
+ laddr);
+ TALLOC_FREE(raddr);
+ TALLOC_FREE(laddr);
+ if (ret == NULL) {
+ return "<talloc_asprintf() failed>";
+ }
+
+ return ret;
+}
+
+/*
+ * Initialize a struct smb_request from an inbuf
+ */
+
+bool init_smb1_request(struct smb_request *req,
+ struct smbd_server_connection *sconn,
+ struct smbXsrv_connection *xconn,
+ const uint8_t *inbuf,
+ size_t unread_bytes, bool encrypted,
+ uint32_t seqnum)
+{
+ struct smbXsrv_tcon *tcon;
+ NTSTATUS status;
+ NTTIME now;
+ size_t req_size = smb_len(inbuf) + 4;
+
+ /* Ensure we have at least smb_size bytes. */
+ if (req_size < smb_size) {
+ DEBUG(0,("init_smb1_request: invalid request size %u\n",
+ (unsigned int)req_size ));
+ return false;
+ }
+
+ *req = (struct smb_request) { .cmd = 0};
+
+ req->request_time = timeval_current();
+ now = timeval_to_nttime(&req->request_time);
+
+ req->cmd = CVAL(inbuf, smb_com);
+ req->flags2 = SVAL(inbuf, smb_flg2);
+ req->smbpid = SVAL(inbuf, smb_pid);
+ req->mid = (uint64_t)SVAL(inbuf, smb_mid);
+ req->seqnum = seqnum;
+ req->vuid = SVAL(inbuf, smb_uid);
+ req->tid = SVAL(inbuf, smb_tid);
+ req->wct = CVAL(inbuf, smb_wct);
+ req->vwv = (const uint16_t *)(inbuf+smb_vwv);
+ req->buflen = smb_buflen(inbuf);
+ req->buf = (const uint8_t *)smb_buf_const(inbuf);
+ req->unread_bytes = unread_bytes;
+ req->encrypted = encrypted;
+ req->sconn = sconn;
+ req->xconn = xconn;
+ if (xconn != NULL) {
+ status = smb1srv_tcon_lookup(xconn, req->tid, now, &tcon);
+ if (NT_STATUS_IS_OK(status)) {
+ req->conn = tcon->compat;
+ }
+ }
+ req->posix_pathnames = lp_posix_pathnames();
+
+ /* Ensure we have at least wct words and 2 bytes of bcc. */
+ if (smb_size + req->wct*2 > req_size) {
+ DEBUG(0,("init_smb1_request: invalid wct number %u (size %u)\n",
+ (unsigned int)req->wct,
+ (unsigned int)req_size));
+ return false;
+ }
+ /* Ensure bcc is correct. */
+ if (((const uint8_t *)smb_buf_const(inbuf)) + req->buflen > inbuf + req_size) {
+ DEBUG(0,("init_smb1_request: invalid bcc number %u "
+ "(wct = %u, size %u)\n",
+ (unsigned int)req->buflen,
+ (unsigned int)req->wct,
+ (unsigned int)req_size));
+ return false;
+ }
+
+ return true;
+}
+
+/****************************************************************************
+ Construct a reply to the incoming packet.
+****************************************************************************/
+
+static void construct_reply_smb1negprot(struct smbXsrv_connection *xconn,
+ char *inbuf, int size,
+ size_t unread_bytes)
+{
+ struct smbd_server_connection *sconn = xconn->client->sconn;
+ struct smb_request *req;
+ NTSTATUS status;
+
+ if (!(req = talloc(talloc_tos(), struct smb_request))) {
+ smb_panic("could not allocate smb_request");
+ }
+
+ if (!init_smb1_request(req, sconn, xconn, (uint8_t *)inbuf, unread_bytes,
+ false, 0)) {
+ exit_server_cleanly("Invalid SMB request");
+ }
+
+ req->inbuf = (uint8_t *)talloc_move(req, &inbuf);
+
+ status = smb2_multi_protocol_reply_negprot(req);
+ if (req->outbuf == NULL) {
+ /*
+ * req->outbuf == NULL means we bootstrapped into SMB2.
+ */
+ return;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ if (!smb1_srv_send(req->xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(req->conn) ||
+ req->encrypted)) {
+ exit_server_cleanly("construct_reply_smb1negprot: "
+ "smb1_srv_send failed.");
+ }
+ TALLOC_FREE(req);
+ } else {
+ /* This code path should only *ever* bootstrap into SMB2. */
+ exit_server_cleanly("Internal error SMB1negprot didn't reply "
+ "with an SMB2 packet");
+ }
+}
+
+static void smbd_server_connection_write_handler(
+ struct smbXsrv_connection *xconn)
+{
+ /* TODO: make write nonblocking */
+}
+
+static void smbd_smb2_server_connection_read_handler(
+ struct smbXsrv_connection *xconn, int fd)
+{
+ char lenbuf[NBT_HDR_SIZE];
+ size_t len = 0;
+ uint8_t *buffer = NULL;
+ size_t bufferlen = 0;
+ NTSTATUS status;
+ uint8_t msg_type = 0;
+
+ /* Read the first 4 bytes - contains length of remainder. */
+ status = read_smb_length_return_keepalive(fd, lenbuf, 0, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_server_cleanly("failed to receive request length");
+ return;
+ }
+
+ /* Integer wrap check. */
+ if (len + NBT_HDR_SIZE < len) {
+ exit_server_cleanly("Invalid length on initial request");
+ return;
+ }
+
+ /*
+ * The +4 here can't wrap, we've checked the length above already.
+ */
+ bufferlen = len+NBT_HDR_SIZE;
+
+ buffer = talloc_array(talloc_tos(), uint8_t, bufferlen);
+ if (buffer == NULL) {
+ DBG_ERR("Could not allocate request inbuf of length %zu\n",
+ bufferlen);
+ exit_server_cleanly("talloc fail");
+ return;
+ }
+
+ /* Copy the NBT_HDR_SIZE length. */
+ memcpy(buffer, lenbuf, sizeof(lenbuf));
+
+ status = read_packet_remainder(fd, (char *)buffer+NBT_HDR_SIZE, 0, len);
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_server_cleanly("Failed to read remainder of initial request");
+ return;
+ }
+
+ /* Check the message type. */
+ msg_type = PULL_LE_U8(buffer,0);
+ if (msg_type == NBSSrequest) {
+ /*
+ * clients can send this request before
+ * bootstrapping into SMB2. Cope with this
+ * message only, don't allow any other strange
+ * NBSS types.
+ */
+ reply_special(xconn, (char *)buffer, bufferlen);
+ xconn->client->sconn->num_requests++;
+ return;
+ }
+
+ /* Only a 'normal' message type allowed now. */
+ if (msg_type != NBSSmessage) {
+ DBG_ERR("Invalid message type %d\n", msg_type);
+ exit_server_cleanly("Invalid message type for initial request");
+ return;
+ }
+
+ /* Could this be an SMB1 negprot bootstrap into SMB2 ? */
+ if (bufferlen < smb_size) {
+ exit_server_cleanly("Invalid initial SMB1 or SMB2 packet");
+ return;
+ }
+ if (valid_smb1_header(buffer)) {
+ /* Can *only* allow an SMB1 negprot here. */
+ uint8_t cmd = PULL_LE_U8(buffer, smb_com);
+ if (cmd != SMBnegprot) {
+ DBG_ERR("Incorrect SMB1 command 0x%hhx, "
+ "should be SMBnegprot (0x72)\n",
+ cmd);
+ exit_server_cleanly("Invalid initial SMB1 packet");
+ }
+ /* Minimal process_smb(). */
+ show_msg((char *)buffer);
+ construct_reply_smb1negprot(xconn, (char *)buffer,
+ bufferlen, 0);
+ xconn->client->sconn->trans_num++;
+ xconn->client->sconn->num_requests++;
+ return;
+
+ } else if (!smbd_is_smb2_header(buffer, bufferlen)) {
+ exit_server_cleanly("Invalid initial SMB2 packet");
+ return;
+ }
+
+ /* Here we know we're a valid SMB2 packet. */
+
+ /*
+ * Point at the start of the SMB2 PDU.
+ * len is the length of the SMB2 PDU.
+ */
+
+ status = smbd_smb2_process_negprot(xconn,
+ 0,
+ (const uint8_t *)buffer+NBT_HDR_SIZE,
+ len);
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_server_cleanly("SMB2 negprot fail");
+ }
+ return;
+}
+
+static void smbd_server_connection_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct smbXsrv_connection *xconn =
+ talloc_get_type_abort(private_data,
+ struct smbXsrv_connection);
+
+ if (!NT_STATUS_IS_OK(xconn->transport.status)) {
+ /*
+ * we're not supposed to do any io
+ */
+ TEVENT_FD_NOT_READABLE(xconn->transport.fde);
+ TEVENT_FD_NOT_WRITEABLE(xconn->transport.fde);
+ return;
+ }
+
+ if (flags & TEVENT_FD_WRITE) {
+ smbd_server_connection_write_handler(xconn);
+ return;
+ }
+ if (flags & TEVENT_FD_READ) {
+#if defined(WITH_SMB1SERVER)
+ if (lp_server_min_protocol() > PROTOCOL_NT1) {
+#endif
+ smbd_smb2_server_connection_read_handler(xconn,
+ xconn->transport.sock);
+#if defined(WITH_SMB1SERVER)
+ } else {
+ smbd_smb1_server_connection_read_handler(xconn,
+ xconn->transport.sock);
+ }
+#endif
+ return;
+ }
+}
+
+struct smbd_release_ip_state {
+ struct smbXsrv_connection *xconn;
+ struct tevent_immediate *im;
+ struct sockaddr_storage srv;
+ struct sockaddr_storage clnt;
+ char addr[INET6_ADDRSTRLEN];
+};
+
+static int release_ip(struct tevent_context *ev,
+ uint32_t src_vnn,
+ uint32_t dst_vnn,
+ uint64_t dst_srvid,
+ const uint8_t *msg,
+ size_t msglen,
+ void *private_data);
+
+static int smbd_release_ip_state_destructor(struct smbd_release_ip_state *s)
+{
+ struct ctdbd_connection *cconn = messaging_ctdb_connection();
+ struct smbXsrv_connection *xconn = s->xconn;
+
+ if (cconn == NULL) {
+ return 0;
+ }
+
+ if (NT_STATUS_EQUAL(xconn->transport.status, NT_STATUS_CONNECTION_IN_USE)) {
+ ctdbd_passed_ips(cconn, &s->srv, &s->clnt, release_ip, s);
+ } else {
+ ctdbd_unregister_ips(cconn, &s->srv, &s->clnt, release_ip, s);
+ }
+
+ return 0;
+}
+
+static void smbd_release_ip_immediate(struct tevent_context *ctx,
+ struct tevent_immediate *im,
+ void *private_data)
+{
+ struct smbd_release_ip_state *state =
+ talloc_get_type_abort(private_data,
+ struct smbd_release_ip_state);
+ struct smbXsrv_connection *xconn = state->xconn;
+
+ if (!NT_STATUS_EQUAL(xconn->transport.status, NT_STATUS_ADDRESS_CLOSED)) {
+ /*
+ * smbd_server_connection_terminate() already triggered ?
+ */
+ return;
+ }
+
+ smbd_server_connection_terminate(xconn, "CTDB_SRVID_RELEASE_IP");
+}
+
+/****************************************************************************
+received when we should release a specific IP
+****************************************************************************/
+static int release_ip(struct tevent_context *ev,
+ uint32_t src_vnn, uint32_t dst_vnn,
+ uint64_t dst_srvid,
+ const uint8_t *msg, size_t msglen,
+ void *private_data)
+{
+ struct smbd_release_ip_state *state =
+ talloc_get_type_abort(private_data,
+ struct smbd_release_ip_state);
+ struct smbXsrv_connection *xconn = state->xconn;
+ const char *ip;
+ const char *addr = state->addr;
+ const char *p = addr;
+
+ if (msglen == 0) {
+ return 0;
+ }
+ if (msg[msglen-1] != '\0') {
+ return 0;
+ }
+
+ ip = (const char *)msg;
+
+ if (!NT_STATUS_IS_OK(xconn->transport.status)) {
+ /* avoid recursion */
+ return 0;
+ }
+
+ if (strncmp("::ffff:", addr, 7) == 0) {
+ p = addr + 7;
+ }
+
+ DEBUG(10, ("Got release IP message for %s, "
+ "our address is %s\n", ip, p));
+
+ if ((strcmp(p, ip) == 0) || ((p != addr) && strcmp(addr, ip) == 0)) {
+ DEBUG(0,("Got release IP message for our IP %s - exiting immediately\n",
+ ip));
+ /*
+ * With SMB2 we should do a clean disconnect,
+ * the previous_session_id in the session setup
+ * will cleanup the old session, tcons and opens.
+ *
+ * A clean disconnect is needed in order to support
+ * durable handles.
+ *
+ * Note: typically this is never triggered
+ * as we got a TCP RST (triggered by ctdb event scripts)
+ * before we get CTDB_SRVID_RELEASE_IP.
+ *
+ * We used to call _exit(1) here, but as this was mostly never
+ * triggered and has implication on our process model,
+ * we can just use smbd_server_connection_terminate()
+ * (also for SMB1).
+ *
+ * We don't call smbd_server_connection_terminate() directly
+ * as we might be called from within ctdbd_migrate(),
+ * we need to defer our action to the next event loop
+ */
+ tevent_schedule_immediate(state->im,
+ xconn->client->raw_ev_ctx,
+ smbd_release_ip_immediate,
+ state);
+
+ /*
+ * Make sure we don't get any io on the connection.
+ */
+ xconn->transport.status = NT_STATUS_ADDRESS_CLOSED;
+ return EADDRNOTAVAIL;
+ }
+
+ return 0;
+}
+
+static int match_cluster_movable_ip(uint32_t total_ip_count,
+ const struct sockaddr_storage *ip,
+ bool is_movable_ip,
+ void *private_data)
+{
+ const struct sockaddr_storage *srv = private_data;
+ struct samba_sockaddr pub_ip = {
+ .u = {
+ .ss = *ip,
+ },
+ };
+ struct samba_sockaddr srv_ip = {
+ .u = {
+ .ss = *srv,
+ },
+ };
+
+ if (is_movable_ip && sockaddr_equal(&pub_ip.u.sa, &srv_ip.u.sa)) {
+ return EADDRNOTAVAIL;
+ }
+
+ return 0;
+}
+
+static NTSTATUS smbd_register_ips(struct smbXsrv_connection *xconn,
+ struct sockaddr_storage *srv,
+ struct sockaddr_storage *clnt)
+{
+ struct smbd_release_ip_state *state;
+ struct ctdbd_connection *cconn;
+ int ret;
+
+ cconn = messaging_ctdb_connection();
+ if (cconn == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ state = talloc_zero(xconn, struct smbd_release_ip_state);
+ if (state == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ state->xconn = xconn;
+ state->im = tevent_create_immediate(state);
+ if (state->im == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ state->srv = *srv;
+ state->clnt = *clnt;
+ if (print_sockaddr(state->addr, sizeof(state->addr), srv) == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (xconn->client->server_multi_channel_enabled) {
+ ret = ctdbd_public_ip_foreach(cconn,
+ match_cluster_movable_ip,
+ srv);
+ if (ret == EADDRNOTAVAIL) {
+ xconn->has_cluster_movable_ip = true;
+ DBG_DEBUG("cluster movable IP on %s\n",
+ smbXsrv_connection_dbg(xconn));
+ } else if (ret != 0) {
+ DBG_ERR("failed to iterate cluster IPs: %s\n",
+ strerror(ret));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ ret = ctdbd_register_ips(cconn, srv, clnt, release_ip, state);
+ if (ret != 0) {
+ return map_nt_error_from_unix(ret);
+ }
+
+ talloc_set_destructor(state, smbd_release_ip_state_destructor);
+
+ return NT_STATUS_OK;
+}
+
+static int smbXsrv_connection_destructor(struct smbXsrv_connection *xconn)
+{
+ DBG_DEBUG("xconn[%s]\n", smbXsrv_connection_dbg(xconn));
+ return 0;
+}
+
+NTSTATUS smbd_add_connection(struct smbXsrv_client *client, int sock_fd,
+ NTTIME now, struct smbXsrv_connection **_xconn)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct smbXsrv_connection *xconn;
+ struct sockaddr_storage ss_srv;
+ void *sp_srv = (void *)&ss_srv;
+ struct sockaddr *sa_srv = (struct sockaddr *)sp_srv;
+ struct sockaddr_storage ss_clnt;
+ void *sp_clnt = (void *)&ss_clnt;
+ struct sockaddr *sa_clnt = (struct sockaddr *)sp_clnt;
+ socklen_t sa_socklen;
+ struct tsocket_address *local_address = NULL;
+ struct tsocket_address *remote_address = NULL;
+ const char *remaddr = NULL;
+ char *p;
+ const char *rhost = NULL;
+ int ret;
+ int tmp;
+
+ *_xconn = NULL;
+
+ DO_PROFILE_INC(connect);
+
+ xconn = talloc_zero(client, struct smbXsrv_connection);
+ if (xconn == NULL) {
+ DEBUG(0,("talloc_zero(struct smbXsrv_connection)\n"));
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ talloc_set_destructor(xconn, smbXsrv_connection_destructor);
+ talloc_steal(frame, xconn);
+ xconn->client = client;
+ xconn->connect_time = now;
+ if (client->next_channel_id != 0) {
+ xconn->channel_id = client->next_channel_id++;
+ }
+
+ xconn->transport.sock = sock_fd;
+#if defined(WITH_SMB1SERVER)
+ smbd_echo_init(xconn);
+#endif
+ xconn->protocol = PROTOCOL_NONE;
+
+ /* Ensure child is set to blocking mode */
+ set_blocking(sock_fd,True);
+
+ set_socket_options(sock_fd, "SO_KEEPALIVE");
+ set_socket_options(sock_fd, lp_socket_options());
+
+ sa_socklen = sizeof(ss_clnt);
+ ret = getpeername(sock_fd, sa_clnt, &sa_socklen);
+ if (ret != 0) {
+ int saved_errno = errno;
+ int level = (errno == ENOTCONN)?2:0;
+ DEBUG(level,("getpeername() failed - %s\n",
+ strerror(saved_errno)));
+ TALLOC_FREE(frame);
+ return map_nt_error_from_unix_common(saved_errno);
+ }
+ ret = tsocket_address_bsd_from_sockaddr(xconn,
+ sa_clnt, sa_socklen,
+ &remote_address);
+ if (ret != 0) {
+ int saved_errno = errno;
+ DEBUG(0,("%s: tsocket_address_bsd_from_sockaddr remote failed - %s\n",
+ __location__, strerror(saved_errno)));
+ TALLOC_FREE(frame);
+ return map_nt_error_from_unix_common(saved_errno);
+ }
+
+ sa_socklen = sizeof(ss_srv);
+ ret = getsockname(sock_fd, sa_srv, &sa_socklen);
+ if (ret != 0) {
+ int saved_errno = errno;
+ int level = (errno == ENOTCONN)?2:0;
+ DEBUG(level,("getsockname() failed - %s\n",
+ strerror(saved_errno)));
+ TALLOC_FREE(frame);
+ return map_nt_error_from_unix_common(saved_errno);
+ }
+ ret = tsocket_address_bsd_from_sockaddr(xconn,
+ sa_srv, sa_socklen,
+ &local_address);
+ if (ret != 0) {
+ int saved_errno = errno;
+ DEBUG(0,("%s: tsocket_address_bsd_from_sockaddr remote failed - %s\n",
+ __location__, strerror(saved_errno)));
+ TALLOC_FREE(frame);
+ return map_nt_error_from_unix_common(saved_errno);
+ }
+
+ if (tsocket_address_is_inet(remote_address, "ip")) {
+ remaddr = tsocket_address_inet_addr_string(remote_address,
+ talloc_tos());
+ if (remaddr == NULL) {
+ DEBUG(0,("%s: tsocket_address_inet_addr_string remote failed - %s\n",
+ __location__, strerror(errno)));
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ remaddr = "0.0.0.0";
+ }
+
+ /*
+ * Before the first packet, check the global hosts allow/ hosts deny
+ * parameters before doing any parsing of packets passed to us by the
+ * client. This prevents attacks on our parsing code from hosts not in
+ * the hosts allow list.
+ */
+
+ ret = get_remote_hostname(remote_address,
+ &p, talloc_tos());
+ if (ret < 0) {
+ int saved_errno = errno;
+ DEBUG(0,("%s: get_remote_hostname failed - %s\n",
+ __location__, strerror(saved_errno)));
+ TALLOC_FREE(frame);
+ return map_nt_error_from_unix_common(saved_errno);
+ }
+ rhost = p;
+ if (strequal(rhost, "UNKNOWN")) {
+ rhost = remaddr;
+ }
+
+ xconn->local_address = local_address;
+ xconn->remote_address = remote_address;
+ xconn->remote_hostname = talloc_strdup(xconn, rhost);
+ if (xconn->remote_hostname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!srv_init_signing(xconn)) {
+ DEBUG(0, ("Failed to init smb_signing\n"));
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (!allow_access(lp_hosts_deny(-1), lp_hosts_allow(-1),
+ xconn->remote_hostname,
+ remaddr)) {
+ DEBUG( 1, ("Connection denied from %s to %s\n",
+ tsocket_address_string(remote_address, talloc_tos()),
+ tsocket_address_string(local_address, talloc_tos())));
+
+ /*
+ * We return a valid xconn
+ * so that the caller can return an error message
+ * to the client
+ */
+ DLIST_ADD_END(client->connections, xconn);
+ talloc_steal(client, xconn);
+
+ *_xconn = xconn;
+ TALLOC_FREE(frame);
+ return NT_STATUS_NETWORK_ACCESS_DENIED;
+ }
+
+ DEBUG(10, ("Connection allowed from %s to %s\n",
+ tsocket_address_string(remote_address, talloc_tos()),
+ tsocket_address_string(local_address, talloc_tos())));
+
+ if (lp_clustering()) {
+ /*
+ * We need to tell ctdb about our client's TCP
+ * connection, so that for failover ctdbd can send
+ * tickle acks, triggering a reconnection by the
+ * client.
+ */
+ NTSTATUS status;
+
+ status = smbd_register_ips(xconn, &ss_srv, &ss_clnt);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("ctdbd_register_ips failed: %s\n",
+ nt_errstr(status)));
+ }
+ }
+
+ tmp = lp_max_xmit();
+ tmp = MAX(tmp, SMB_BUFFER_SIZE_MIN);
+ tmp = MIN(tmp, SMB_BUFFER_SIZE_MAX);
+
+#if defined(WITH_SMB1SERVER)
+ xconn->smb1.negprot.max_recv = tmp;
+
+ xconn->smb1.sessions.done_sesssetup = false;
+ xconn->smb1.sessions.max_send = SMB_BUFFER_SIZE_MAX;
+#endif
+
+ xconn->transport.fde = tevent_add_fd(client->raw_ev_ctx,
+ xconn,
+ sock_fd,
+ TEVENT_FD_READ,
+ smbd_server_connection_handler,
+ xconn);
+ if (!xconn->transport.fde) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_fd_set_auto_close(xconn->transport.fde);
+
+ /* for now we only have one connection */
+ DLIST_ADD_END(client->connections, xconn);
+ talloc_steal(client, xconn);
+
+ *_xconn = xconn;
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
+
+static bool uid_in_use(struct auth_session_info *session_info,
+ uid_t uid)
+{
+ if (session_info->unix_token->uid == uid) {
+ return true;
+ }
+ return false;
+}
+
+static bool gid_in_use(struct auth_session_info *session_info,
+ gid_t gid)
+{
+ uint32_t i;
+ struct security_unix_token *utok = NULL;
+
+ utok = session_info->unix_token;
+ if (utok->gid == gid) {
+ return true;
+ }
+
+ for(i = 0; i < utok->ngroups; i++) {
+ if (utok->groups[i] == gid) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool sid_in_use(struct auth_session_info *session_info,
+ const struct dom_sid *psid)
+{
+ struct security_token *tok = NULL;
+
+ tok = session_info->security_token;
+ if (tok == NULL) {
+ /*
+ * Not sure session_info->security_token can
+ * ever be NULL. This check might be not
+ * necessary.
+ */
+ return false;
+ }
+ if (security_token_has_sid(tok, psid)) {
+ return true;
+ }
+ return false;
+}
+
+struct id_in_use_state {
+ const struct id_cache_ref *id;
+ bool match;
+};
+
+static int id_in_use_cb(struct smbXsrv_session *session,
+ void *private_data)
+{
+ struct id_in_use_state *state = (struct id_in_use_state *)
+ private_data;
+ struct auth_session_info *session_info =
+ session->global->auth_session_info;
+
+ switch(state->id->type) {
+ case UID:
+ state->match = uid_in_use(session_info, state->id->id.uid);
+ break;
+ case GID:
+ state->match = gid_in_use(session_info, state->id->id.gid);
+ break;
+ case SID:
+ state->match = sid_in_use(session_info, &state->id->id.sid);
+ break;
+ default:
+ state->match = false;
+ break;
+ }
+ if (state->match) {
+ return -1;
+ }
+ return 0;
+}
+
+static bool id_in_use(struct smbd_server_connection *sconn,
+ const struct id_cache_ref *id)
+{
+ struct id_in_use_state state;
+ NTSTATUS status;
+
+ state = (struct id_in_use_state) {
+ .id = id,
+ .match = false,
+ };
+
+ status = smbXsrv_session_local_traverse(sconn->client,
+ id_in_use_cb,
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ return state.match;
+}
+
+/****************************************************************************
+ Check if services need reloading.
+****************************************************************************/
+
+static void check_reload(struct smbd_server_connection *sconn, time_t t)
+{
+
+ if (last_smb_conf_reload_time == 0) {
+ last_smb_conf_reload_time = t;
+ }
+
+ if (t >= last_smb_conf_reload_time+SMBD_RELOAD_CHECK) {
+ reload_services(sconn, conn_snum_used, true);
+ last_smb_conf_reload_time = t;
+ }
+}
+
+static void msg_kill_client_ip(struct messaging_context *msg_ctx,
+ void *private_data, uint32_t msg_type,
+ struct server_id server_id, DATA_BLOB *data)
+{
+ struct smbd_server_connection *sconn = talloc_get_type_abort(
+ private_data, struct smbd_server_connection);
+ const char *ip = (char *) data->data;
+ char *client_ip;
+
+ DBG_DEBUG("Got kill request for client IP %s\n", ip);
+
+ client_ip = tsocket_address_inet_addr_string(sconn->remote_address,
+ talloc_tos());
+ if (client_ip == NULL) {
+ return;
+ }
+
+ if (strequal(ip, client_ip)) {
+ DBG_WARNING("Got kill client message for %s - "
+ "exiting immediately\n", ip);
+ exit_server_cleanly("Forced disconnect for client");
+ }
+
+ TALLOC_FREE(client_ip);
+}
+
+/*
+ * Do the recurring check if we're idle
+ */
+static bool deadtime_fn(const struct timeval *now, void *private_data)
+{
+ struct smbd_server_connection *sconn =
+ (struct smbd_server_connection *)private_data;
+
+ if ((conn_num_open(sconn) == 0)
+ || (conn_idle_all(sconn, now->tv_sec))) {
+ DEBUG( 2, ( "Closing idle connection\n" ) );
+ messaging_send(sconn->msg_ctx,
+ messaging_server_id(sconn->msg_ctx),
+ MSG_SHUTDOWN, &data_blob_null);
+ return False;
+ }
+
+ return True;
+}
+
+/*
+ * Do the recurring log file and smb.conf reload checks.
+ */
+
+static bool housekeeping_fn(const struct timeval *now, void *private_data)
+{
+ struct smbd_server_connection *sconn = talloc_get_type_abort(
+ private_data, struct smbd_server_connection);
+
+ DEBUG(5, ("housekeeping\n"));
+
+ change_to_root_user();
+
+ /* check if we need to reload services */
+ check_reload(sconn, time_mono(NULL));
+
+ /*
+ * Force a log file check.
+ */
+ force_check_log_size();
+ check_log_size();
+ return true;
+}
+
+static void smbd_sig_term_handler(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ exit_server_cleanly("termination signal");
+}
+
+static void smbd_setup_sig_term_handler(struct smbd_server_connection *sconn)
+{
+ struct tevent_signal *se;
+
+ se = tevent_add_signal(sconn->ev_ctx,
+ sconn,
+ SIGTERM, 0,
+ smbd_sig_term_handler,
+ sconn);
+ if (!se) {
+ exit_server("failed to setup SIGTERM handler");
+ }
+}
+
+static void smbd_sig_hup_handler(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ struct smbd_server_connection *sconn =
+ talloc_get_type_abort(private_data,
+ struct smbd_server_connection);
+
+ change_to_root_user();
+ DEBUG(1,("Reloading services after SIGHUP\n"));
+ reload_services(sconn, conn_snum_used, false);
+}
+
+static void smbd_setup_sig_hup_handler(struct smbd_server_connection *sconn)
+{
+ struct tevent_signal *se;
+
+ se = tevent_add_signal(sconn->ev_ctx,
+ sconn,
+ SIGHUP, 0,
+ smbd_sig_hup_handler,
+ sconn);
+ if (!se) {
+ exit_server("failed to setup SIGHUP handler");
+ }
+}
+
+static void smbd_conf_updated(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct smbd_server_connection *sconn =
+ talloc_get_type_abort(private_data,
+ struct smbd_server_connection);
+
+ DEBUG(10,("smbd_conf_updated: Got message saying smb.conf was "
+ "updated. Reloading.\n"));
+ change_to_root_user();
+ reload_services(sconn, conn_snum_used, false);
+}
+
+static void smbd_id_cache_kill(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB* data)
+{
+ const char *msg = (data && data->data)
+ ? (const char *)data->data : "<NULL>";
+ struct id_cache_ref id;
+ struct smbd_server_connection *sconn =
+ talloc_get_type_abort(private_data,
+ struct smbd_server_connection);
+
+ if (!id_cache_ref_parse(msg, &id)) {
+ DEBUG(0, ("Invalid ?ID: %s\n", msg));
+ return;
+ }
+
+ if (id_in_use(sconn, &id)) {
+ exit_server_cleanly(msg);
+ }
+ id_cache_delete_from_cache(&id);
+}
+
+struct smbd_tevent_trace_state {
+ struct tevent_context *ev;
+ TALLOC_CTX *frame;
+ SMBPROFILE_BASIC_ASYNC_STATE(profile_idle);
+};
+
+static inline void smbd_tevent_trace_callback_before_loop_once(
+ struct smbd_tevent_trace_state *state)
+{
+ talloc_free(state->frame);
+ state->frame = talloc_stackframe_pool(8192);
+}
+
+static inline void smbd_tevent_trace_callback_after_loop_once(
+ struct smbd_tevent_trace_state *state)
+{
+ TALLOC_FREE(state->frame);
+}
+
+static void smbd_tevent_trace_callback(enum tevent_trace_point point,
+ void *private_data)
+{
+ struct smbd_tevent_trace_state *state =
+ (struct smbd_tevent_trace_state *)private_data;
+
+ switch (point) {
+ case TEVENT_TRACE_BEFORE_WAIT:
+ break;
+ case TEVENT_TRACE_AFTER_WAIT:
+ break;
+ case TEVENT_TRACE_BEFORE_LOOP_ONCE:
+ smbd_tevent_trace_callback_before_loop_once(state);
+ break;
+ case TEVENT_TRACE_AFTER_LOOP_ONCE:
+ smbd_tevent_trace_callback_after_loop_once(state);
+ break;
+ }
+
+ errno = 0;
+}
+
+static void smbd_tevent_trace_callback_profile(enum tevent_trace_point point,
+ void *private_data)
+{
+ struct smbd_tevent_trace_state *state =
+ (struct smbd_tevent_trace_state *)private_data;
+
+ switch (point) {
+ case TEVENT_TRACE_BEFORE_WAIT:
+ if (!smbprofile_dump_pending()) {
+ /*
+ * If there's no dump pending
+ * we don't want to schedule a new 1 sec timer.
+ *
+ * Instead we want to sleep as long as nothing happens.
+ */
+ smbprofile_dump_setup(NULL);
+ }
+ SMBPROFILE_BASIC_ASYNC_START(idle, profile_p, state->profile_idle);
+ break;
+ case TEVENT_TRACE_AFTER_WAIT:
+ SMBPROFILE_BASIC_ASYNC_END(state->profile_idle);
+ if (!smbprofile_dump_pending()) {
+ /*
+ * We need to flush our state after sleeping
+ * (hopefully a long time).
+ */
+ smbprofile_dump();
+ /*
+ * future profiling events should trigger timers
+ * on our main event context.
+ */
+ smbprofile_dump_setup(state->ev);
+ }
+ break;
+ case TEVENT_TRACE_BEFORE_LOOP_ONCE:
+ smbd_tevent_trace_callback_before_loop_once(state);
+ break;
+ case TEVENT_TRACE_AFTER_LOOP_ONCE:
+ smbd_tevent_trace_callback_after_loop_once(state);
+ break;
+ }
+
+ errno = 0;
+}
+
+/****************************************************************************
+ Process commands from the client
+****************************************************************************/
+
+void smbd_process(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ int sock_fd,
+ bool interactive)
+{
+ struct smbd_tevent_trace_state trace_state = {
+ .ev = ev_ctx,
+ .frame = talloc_stackframe(),
+ };
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct smbXsrv_client *client = NULL;
+ struct smbd_server_connection *sconn = NULL;
+ struct smbXsrv_connection *xconn = NULL;
+ const char *locaddr = NULL;
+ const char *remaddr = NULL;
+ int ret;
+ NTSTATUS status;
+ struct timeval tv = timeval_current();
+ NTTIME now = timeval_to_nttime(&tv);
+ char *chroot_dir = NULL;
+ int rc;
+
+ status = smbXsrv_client_create(ev_ctx, ev_ctx, msg_ctx, now, &client);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smbXsrv_client_create(): %s\n", nt_errstr(status));
+ exit_server_cleanly("talloc_zero(struct smbXsrv_client).\n");
+ }
+
+ /*
+ * TODO: remove this...:-)
+ */
+ global_smbXsrv_client = client;
+
+ sconn = talloc_zero(client, struct smbd_server_connection);
+ if (sconn == NULL) {
+ exit_server("failed to create smbd_server_connection");
+ }
+
+ client->sconn = sconn;
+ sconn->client = client;
+
+ sconn->ev_ctx = ev_ctx;
+ sconn->msg_ctx = msg_ctx;
+
+ ret = pthreadpool_tevent_init(sconn, lp_aio_max_threads(),
+ &sconn->pool);
+ if (ret != 0) {
+ exit_server("pthreadpool_tevent_init() failed.");
+ }
+
+#if defined(WITH_SMB1SERVER)
+ if (lp_server_max_protocol() >= PROTOCOL_SMB2_02) {
+#endif
+ /*
+ * We're not making the decision here,
+ * we're just allowing the client
+ * to decide between SMB1 and SMB2
+ * with the first negprot
+ * packet.
+ */
+ sconn->using_smb2 = true;
+#if defined(WITH_SMB1SERVER)
+ }
+#endif
+
+ if (!interactive) {
+ smbd_setup_sig_term_handler(sconn);
+ smbd_setup_sig_hup_handler(sconn);
+ }
+
+ status = smbd_add_connection(client, sock_fd, now, &xconn);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED)) {
+ /*
+ * send a negative session response "not listening on calling
+ * name"
+ */
+ unsigned char buf[5] = {0x83, 0, 0, 1, 0x81};
+ (void)smb1_srv_send(xconn, (char *)buf, false, 0, false);
+ exit_server_cleanly("connection denied");
+ } else if (!NT_STATUS_IS_OK(status)) {
+ exit_server_cleanly(nt_errstr(status));
+ }
+
+ sconn->local_address =
+ tsocket_address_copy(xconn->local_address, sconn);
+ if (sconn->local_address == NULL) {
+ exit_server_cleanly("tsocket_address_copy() failed");
+ }
+ sconn->remote_address =
+ tsocket_address_copy(xconn->remote_address, sconn);
+ if (sconn->remote_address == NULL) {
+ exit_server_cleanly("tsocket_address_copy() failed");
+ }
+ sconn->remote_hostname =
+ talloc_strdup(sconn, xconn->remote_hostname);
+ if (sconn->remote_hostname == NULL) {
+ exit_server_cleanly("tsocket_strdup() failed");
+ }
+
+ client->global->local_address =
+ tsocket_address_string(sconn->local_address,
+ client->global);
+ if (client->global->local_address == NULL) {
+ exit_server_cleanly("tsocket_address_string() failed");
+ }
+ client->global->remote_address =
+ tsocket_address_string(sconn->remote_address,
+ client->global);
+ if (client->global->remote_address == NULL) {
+ exit_server_cleanly("tsocket_address_string() failed");
+ }
+ client->global->remote_name =
+ talloc_strdup(client->global, sconn->remote_hostname);
+ if (client->global->remote_name == NULL) {
+ exit_server_cleanly("tsocket_strdup() failed");
+ }
+
+ if (tsocket_address_is_inet(sconn->local_address, "ip")) {
+ locaddr = tsocket_address_inet_addr_string(
+ sconn->local_address,
+ talloc_tos());
+ if (locaddr == NULL) {
+ DEBUG(0,("%s: tsocket_address_inet_addr_string remote failed - %s\n",
+ __location__, strerror(errno)));
+ exit_server_cleanly("tsocket_address_inet_addr_string remote failed.\n");
+ }
+ } else {
+ locaddr = "0.0.0.0";
+ }
+
+ if (tsocket_address_is_inet(sconn->remote_address, "ip")) {
+ remaddr = tsocket_address_inet_addr_string(
+ sconn->remote_address,
+ talloc_tos());
+ if (remaddr == NULL) {
+ DEBUG(0,("%s: tsocket_address_inet_addr_string remote failed - %s\n",
+ __location__, strerror(errno)));
+ exit_server_cleanly("tsocket_address_inet_addr_string remote failed.\n");
+ }
+ } else {
+ remaddr = "0.0.0.0";
+ }
+
+ /* this is needed so that we get decent entries
+ in smbstatus for port 445 connects */
+ set_remote_machine_name(remaddr, false);
+ reload_services(sconn, conn_snum_used, true);
+ sub_set_socket_ids(remaddr,
+ sconn->remote_hostname,
+ locaddr);
+
+ if (lp_preload_modules()) {
+ smb_load_all_modules_absoute_path(lp_preload_modules());
+ }
+
+ if (!init_account_policy()) {
+ exit_server("Could not open account policy tdb.\n");
+ }
+
+ chroot_dir = lp_root_directory(talloc_tos(), lp_sub);
+ if (chroot_dir[0] != '\0') {
+ rc = chdir(chroot_dir);
+ if (rc != 0) {
+ DBG_ERR("Failed to chdir to %s\n", chroot_dir);
+ exit_server("Failed to chdir()");
+ }
+
+ rc = chroot(chroot_dir);
+ if (rc != 0) {
+ DBG_ERR("Failed to change root to %s\n", chroot_dir);
+ exit_server("Failed to chroot()");
+ }
+ DBG_WARNING("Changed root to %s\n", chroot_dir);
+
+ TALLOC_FREE(chroot_dir);
+ }
+
+ if (!file_init(sconn)) {
+ exit_server("file_init() failed");
+ }
+
+ /* Setup oplocks */
+ if (!init_oplocks(sconn))
+ exit_server("Failed to init oplocks");
+
+ /* register our message handlers */
+ messaging_register(sconn->msg_ctx, sconn,
+ MSG_SMB_FORCE_TDIS, msg_force_tdis);
+ messaging_register(
+ sconn->msg_ctx,
+ sconn,
+ MSG_SMB_FORCE_TDIS_DENIED,
+ msg_force_tdis_denied);
+ messaging_register(sconn->msg_ctx, sconn,
+ MSG_SMB_CLOSE_FILE, msg_close_file);
+ messaging_register(sconn->msg_ctx, sconn,
+ MSG_SMB_FILE_RENAME, msg_file_was_renamed);
+
+ id_cache_register_msgs(sconn->msg_ctx);
+ messaging_deregister(sconn->msg_ctx, ID_CACHE_KILL, NULL);
+ messaging_register(sconn->msg_ctx, sconn,
+ ID_CACHE_KILL, smbd_id_cache_kill);
+
+ messaging_deregister(sconn->msg_ctx,
+ MSG_SMB_CONF_UPDATED, sconn->ev_ctx);
+ messaging_register(sconn->msg_ctx, sconn,
+ MSG_SMB_CONF_UPDATED, smbd_conf_updated);
+
+ messaging_deregister(sconn->msg_ctx, MSG_SMB_KILL_CLIENT_IP,
+ NULL);
+ messaging_register(sconn->msg_ctx, sconn,
+ MSG_SMB_KILL_CLIENT_IP,
+ msg_kill_client_ip);
+
+ messaging_deregister(sconn->msg_ctx, MSG_SMB_TELL_NUM_CHILDREN, NULL);
+
+ /*
+ * Use the default MSG_DEBUG handler to avoid rebroadcasting
+ * MSGs to all child processes
+ */
+ messaging_deregister(sconn->msg_ctx,
+ MSG_DEBUG, NULL);
+ messaging_register(sconn->msg_ctx, NULL,
+ MSG_DEBUG, debug_message);
+
+#if defined(WITH_SMB1SERVER)
+ if ((lp_keepalive() != 0)
+ && !(event_add_idle(ev_ctx, NULL,
+ timeval_set(lp_keepalive(), 0),
+ "keepalive", keepalive_fn,
+ sconn))) {
+ DEBUG(0, ("Could not add keepalive event\n"));
+ exit(1);
+ }
+#endif
+
+ if (!(event_add_idle(ev_ctx, NULL,
+ timeval_set(IDLE_CLOSED_TIMEOUT, 0),
+ "deadtime", deadtime_fn, sconn))) {
+ DEBUG(0, ("Could not add deadtime event\n"));
+ exit(1);
+ }
+
+ if (!(event_add_idle(ev_ctx, NULL,
+ timeval_set(SMBD_HOUSEKEEPING_INTERVAL, 0),
+ "housekeeping", housekeeping_fn, sconn))) {
+ DEBUG(0, ("Could not add housekeeping event\n"));
+ exit(1);
+ }
+
+ smbprofile_dump_setup(ev_ctx);
+
+ if (!init_dptrs(sconn)) {
+ exit_server("init_dptrs() failed");
+ }
+
+ TALLOC_FREE(trace_state.frame);
+
+ if (smbprofile_active()) {
+ tevent_set_trace_callback(ev_ctx,
+ smbd_tevent_trace_callback_profile,
+ &trace_state);
+ } else {
+ tevent_set_trace_callback(ev_ctx,
+ smbd_tevent_trace_callback,
+ &trace_state);
+ }
+
+ ret = tevent_loop_wait(ev_ctx);
+ if (ret != 0) {
+ DEBUG(1, ("tevent_loop_wait failed: %d, %s,"
+ " exiting\n", ret, strerror(errno)));
+ }
+
+ TALLOC_FREE(trace_state.frame);
+
+ exit_server_cleanly(NULL);
+}
diff --git a/source3/smbd/smb2_query_directory.c b/source3/smbd/smb2_query_directory.c
new file mode 100644
index 0000000..ebbbbb3
--- /dev/null
+++ b/source3/smbd/smb2_query_directory.c
@@ -0,0 +1,1076 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+
+ 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 "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "trans2.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "system/filesys.h"
+#include "lib/pthreadpool/pthreadpool_tevent.h"
+#include "source3/smbd/dir.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static struct tevent_req *smbd_smb2_query_directory_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *in_fsp,
+ uint8_t in_file_info_class,
+ uint8_t in_flags,
+ uint32_t in_file_index,
+ uint32_t in_output_buffer_length,
+ const char *in_file_name);
+static NTSTATUS smbd_smb2_query_directory_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out_output_buffer);
+
+static void smbd_smb2_request_find_done(struct tevent_req *subreq);
+NTSTATUS smbd_smb2_request_process_query_directory(struct smbd_smb2_request *req)
+{
+ NTSTATUS status;
+ const uint8_t *inbody;
+ uint8_t in_file_info_class;
+ uint8_t in_flags;
+ uint32_t in_file_index;
+ uint64_t in_file_id_persistent;
+ uint64_t in_file_id_volatile;
+ struct files_struct *in_fsp;
+ uint16_t in_file_name_offset;
+ uint16_t in_file_name_length;
+ DATA_BLOB in_file_name_buffer;
+ char *in_file_name_string;
+ size_t in_file_name_string_size;
+ uint32_t in_output_buffer_length;
+ struct tevent_req *subreq;
+ bool ok;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x21);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ in_file_info_class = CVAL(inbody, 0x02);
+ in_flags = CVAL(inbody, 0x03);
+ in_file_index = IVAL(inbody, 0x04);
+ in_file_id_persistent = BVAL(inbody, 0x08);
+ in_file_id_volatile = BVAL(inbody, 0x10);
+ in_file_name_offset = SVAL(inbody, 0x18);
+ in_file_name_length = SVAL(inbody, 0x1A);
+ in_output_buffer_length = IVAL(inbody, 0x1C);
+
+ if (in_file_name_offset == 0 && in_file_name_length == 0) {
+ /* This is ok */
+ } else if (in_file_name_offset !=
+ (SMB2_HDR_BODY + SMBD_SMB2_IN_BODY_LEN(req))) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ if (in_file_name_length > SMBD_SMB2_IN_DYN_LEN(req)) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ /* The output header is 8 bytes. */
+ if (in_output_buffer_length <= 8) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ DEBUG(10,("smbd_smb2_request_find_done: in_output_buffer_length = %u\n",
+ (unsigned int)in_output_buffer_length ));
+
+ /* Take into account the output header. */
+ in_output_buffer_length -= 8;
+
+ in_file_name_buffer.data = SMBD_SMB2_IN_DYN_PTR(req);
+ in_file_name_buffer.length = in_file_name_length;
+
+ ok = convert_string_talloc(req, CH_UTF16, CH_UNIX,
+ in_file_name_buffer.data,
+ in_file_name_buffer.length,
+ &in_file_name_string,
+ &in_file_name_string_size);
+ if (!ok) {
+ return smbd_smb2_request_error(req, NT_STATUS_ILLEGAL_CHARACTER);
+ }
+
+ if (in_file_name_buffer.length == 0) {
+ in_file_name_string_size = 0;
+ }
+
+ if (strlen(in_file_name_string) != in_file_name_string_size) {
+ return smbd_smb2_request_error(req, NT_STATUS_OBJECT_NAME_INVALID);
+ }
+
+ in_fsp = file_fsp_smb2(req, in_file_id_persistent, in_file_id_volatile);
+ if (in_fsp == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED);
+ }
+
+ subreq = smbd_smb2_query_directory_send(req, req->sconn->ev_ctx,
+ req, in_fsp,
+ in_file_info_class,
+ in_flags,
+ in_file_index,
+ in_output_buffer_length,
+ in_file_name_string);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_find_done, req);
+
+ return smbd_smb2_request_pending_queue(req, subreq, 500);
+}
+
+static void smbd_smb2_request_find_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *req = tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ DATA_BLOB outbody;
+ DATA_BLOB outdyn;
+ uint16_t out_output_buffer_offset;
+ DATA_BLOB out_output_buffer = data_blob_null;
+ NTSTATUS status;
+ NTSTATUS error; /* transport error */
+
+ status = smbd_smb2_query_directory_recv(subreq,
+ req,
+ &out_output_buffer);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ error = smbd_smb2_request_error(req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ out_output_buffer_offset = SMB2_HDR_BODY + 0x08;
+
+ outbody = smbd_smb2_generate_outbody(req, 0x08);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x08 + 1); /* struct size */
+ SSVAL(outbody.data, 0x02,
+ out_output_buffer_offset); /* output buffer offset */
+ SIVAL(outbody.data, 0x04,
+ out_output_buffer.length); /* output buffer length */
+
+ DEBUG(10,("smbd_smb2_request_find_done: out_output_buffer.length = %u\n",
+ (unsigned int)out_output_buffer.length ));
+
+ outdyn = out_output_buffer;
+
+ error = smbd_smb2_request_done(req, outbody, &outdyn);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+static struct tevent_req *fetch_write_time_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ connection_struct *conn,
+ struct file_id id,
+ int info_level,
+ char *entry_marshall_buf,
+ bool *stop);
+static NTSTATUS fetch_write_time_recv(struct tevent_req *req);
+
+static struct tevent_req *fetch_dos_mode_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *dir_fsp,
+ struct smb_filename **smb_fname,
+ uint32_t info_level,
+ uint8_t *entry_marshall_buf);
+
+static NTSTATUS fetch_dos_mode_recv(struct tevent_req *req);
+
+struct smbd_smb2_query_directory_state {
+ struct tevent_context *ev;
+ struct smbd_smb2_request *smb2req;
+ uint64_t async_sharemode_count;
+ uint32_t find_async_delay_usec;
+ DATA_BLOB out_output_buffer;
+ struct smb_request *smbreq;
+ int in_output_buffer_length;
+ struct files_struct *dirfsp;
+ const char *in_file_name;
+ NTSTATUS empty_status;
+ uint32_t info_level;
+ uint32_t max_count;
+ char *pdata;
+ char *base_data;
+ char *end_data;
+ uint32_t num;
+ uint32_t dirtype;
+ bool dont_descend;
+ bool ask_sharemode;
+ bool async_dosmode;
+ bool async_ask_sharemode;
+ int last_entry_off;
+ size_t max_async_dosmode_active;
+ uint32_t async_dosmode_active;
+ bool done;
+};
+
+static bool smb2_query_directory_next_entry(struct tevent_req *req);
+static void smb2_query_directory_fetch_write_time_done(struct tevent_req *subreq);
+static void smb2_query_directory_dos_mode_done(struct tevent_req *subreq);
+static void smb2_query_directory_waited(struct tevent_req *subreq);
+
+static struct tevent_req *smbd_smb2_query_directory_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *fsp,
+ uint8_t in_file_info_class,
+ uint8_t in_flags,
+ uint32_t in_file_index,
+ uint32_t in_output_buffer_length,
+ const char *in_file_name)
+{
+ struct smbXsrv_connection *xconn = smb2req->xconn;
+ struct tevent_req *req;
+ struct smbd_smb2_query_directory_state *state;
+ connection_struct *conn = smb2req->tcon->compat;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ NTSTATUS status;
+ bool wcard_has_wild = false;
+ struct tm tm = {};
+ char *p;
+ bool stop = false;
+ bool ok;
+ bool posix_dir_handle = (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN);
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_query_directory_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->dirfsp = fsp;
+ state->smb2req = smb2req;
+ state->in_output_buffer_length = in_output_buffer_length;
+ state->in_file_name = in_file_name;
+ state->out_output_buffer = data_blob_null;
+ state->dirtype = FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_DIRECTORY;
+
+ DEBUG(10,("smbd_smb2_query_directory_send: %s - %s\n",
+ fsp_str_dbg(fsp), fsp_fnum_dbg(fsp)));
+
+ state->smbreq = smbd_smb2_fake_smb_request(smb2req, fsp);
+ if (tevent_req_nomem(state->smbreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (!fsp->fsp_flags.is_directory) {
+ tevent_req_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return tevent_req_post(req, ev);
+ }
+
+ if (strcmp(state->in_file_name, "") == 0) {
+ tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_INVALID);
+ return tevent_req_post(req, ev);
+ }
+ if (strchr_m(state->in_file_name, '\\') != NULL) {
+ tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_INVALID);
+ return tevent_req_post(req, ev);
+ }
+ if (strchr_m(state->in_file_name, '/') != NULL) {
+ tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_INVALID);
+ return tevent_req_post(req, ev);
+ }
+
+ p = strptime(state->in_file_name, GMT_FORMAT, &tm);
+ if ((p != NULL) && (*p =='\0')) {
+ /*
+ * Bogus find that asks for a shadow copy timestamp as a
+ * directory. The correct response is that it does not exist as
+ * a directory.
+ */
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_FILE);
+ return tevent_req_post(req, ev);
+ }
+
+ if (in_output_buffer_length > xconn->smb2.server.max_trans) {
+ DEBUG(2,("smbd_smb2_query_directory_send: "
+ "client ignored max trans:%s: 0x%08X: 0x%08X\n",
+ __location__, in_output_buffer_length,
+ xconn->smb2.server.max_trans));
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ status = smbd_smb2_request_verify_creditcharge(smb2req,
+ in_output_buffer_length);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ switch (in_file_info_class) {
+ case SMB2_FIND_DIRECTORY_INFO:
+ state->info_level = SMB_FIND_FILE_DIRECTORY_INFO;
+ break;
+
+ case SMB2_FIND_FULL_DIRECTORY_INFO:
+ state->info_level = SMB_FIND_FILE_FULL_DIRECTORY_INFO;
+ break;
+
+ case SMB2_FIND_BOTH_DIRECTORY_INFO:
+ state->info_level = SMB_FIND_FILE_BOTH_DIRECTORY_INFO;
+ break;
+
+ case SMB2_FIND_NAME_INFO:
+ state->info_level = SMB_FIND_FILE_NAMES_INFO;
+ break;
+
+ case SMB2_FIND_ID_BOTH_DIRECTORY_INFO:
+ state->info_level = SMB_FIND_ID_BOTH_DIRECTORY_INFO;
+ break;
+
+ case SMB2_FIND_ID_FULL_DIRECTORY_INFO:
+ state->info_level = SMB_FIND_ID_FULL_DIRECTORY_INFO;
+ break;
+
+ case SMB2_FIND_POSIX_INFORMATION:
+ if (!(fsp->posix_flags & FSP_POSIX_FLAGS_OPEN)) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return tevent_req_post(req, ev);
+ }
+ state->info_level = SMB2_FILE_POSIX_INFORMATION;
+ break;
+ default:
+ tevent_req_nterror(req, NT_STATUS_INVALID_INFO_CLASS);
+ return tevent_req_post(req, ev);
+ }
+
+ if (in_flags & SMB2_CONTINUE_FLAG_REOPEN) {
+ struct vfs_open_how how = { .flags = O_RDONLY, };
+
+ status = fd_close(fsp);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * fd_close() will close and invalidate the fsp's file
+ * descriptor. So we have to reopen it.
+ */
+
+#ifdef O_DIRECTORY
+ how.flags |= O_DIRECTORY;
+#endif
+ status = fd_openat(conn->cwd_fsp, fsp->fsp_name, fsp, &how);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ if (!state->smbreq->posix_pathnames) {
+ wcard_has_wild = ms_has_wild(state->in_file_name);
+ }
+
+ /* Ensure we've canonicalized any search path if not a wildcard. */
+ if (!wcard_has_wild) {
+ /*
+ * We still need to do the case processing
+ * to save off the client-supplied last component.
+ * At least we know there's no @GMT normalization
+ * or MS-DFS paths to do in a directory mask.
+ */
+ state->in_file_name = get_original_lcomp(state,
+ conn,
+ state->in_file_name,
+ 0);
+ if (tevent_req_nomem(state->in_file_name, req)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ if (fsp->dptr == NULL) {
+ status = dptr_create(conn,
+ NULL, /* req */
+ fsp,
+ false, /* old_handle */
+ state->in_file_name, /* wcard */
+ state->dirtype,
+ &fsp->dptr);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ state->empty_status = NT_STATUS_NO_SUCH_FILE;
+ } else {
+ state->empty_status = STATUS_NO_MORE_FILES;
+ }
+
+ if (in_flags & SMB2_CONTINUE_FLAG_RESTART) {
+ dptr_RewindDir(fsp->dptr);
+ }
+
+ if (in_flags & SMB2_CONTINUE_FLAG_SINGLE) {
+ state->max_count = 1;
+ } else {
+ state->max_count = UINT16_MAX;
+ }
+
+#define DIR_ENTRY_SAFETY_MARGIN 4096
+
+ state->out_output_buffer = data_blob_talloc(state, NULL,
+ in_output_buffer_length + DIR_ENTRY_SAFETY_MARGIN);
+ if (tevent_req_nomem(state->out_output_buffer.data, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->out_output_buffer.length = 0;
+ state->pdata = (char *)state->out_output_buffer.data;
+ state->base_data = state->pdata;
+ /*
+ * end_data must include the safety margin as it's what is
+ * used to determine if pushed strings have been truncated.
+ */
+ state->end_data = state->pdata + in_output_buffer_length + DIR_ENTRY_SAFETY_MARGIN - 1;
+
+ DEBUG(8,("smbd_smb2_query_directory_send: dirpath=<%s> dontdescend=<%s>, "
+ "in_output_buffer_length = %u\n",
+ fsp->fsp_name->base_name, lp_dont_descend(talloc_tos(), lp_sub, SNUM(conn)),
+ (unsigned int)in_output_buffer_length ));
+
+ state->dont_descend = in_list(
+ fsp->fsp_name->base_name,
+ lp_dont_descend(talloc_tos(), lp_sub, SNUM(conn)),
+ posix_dir_handle ? true : conn->case_sensitive);
+
+ /*
+ * SMB_FIND_FILE_NAMES_INFO doesn't need stat information
+ *
+ * This may change when we try to improve the delete on close
+ * handling in future.
+ */
+ if (state->info_level != SMB_FIND_FILE_NAMES_INFO) {
+ state->ask_sharemode = fsp_search_ask_sharemode(fsp);
+
+ state->async_dosmode = lp_smbd_async_dosmode(SNUM(conn));
+ }
+
+ if (state->ask_sharemode && lp_clustering()) {
+ state->ask_sharemode = false;
+ state->async_ask_sharemode = true;
+ }
+
+ if (state->async_dosmode) {
+ size_t max_threads;
+
+ max_threads = pthreadpool_tevent_max_threads(conn->sconn->pool);
+ if (max_threads == 0 || !per_thread_cwd_supported()) {
+ state->async_dosmode = false;
+ }
+
+ state->max_async_dosmode_active = lp_smbd_max_async_dosmode(
+ SNUM(conn));
+ if (state->max_async_dosmode_active == 0) {
+ state->max_async_dosmode_active = max_threads * 2;
+ }
+ }
+
+ if (state->async_dosmode || state->async_ask_sharemode) {
+ /*
+ * Should we only set async_internal
+ * if we're not the last request in
+ * a compound chain?
+ */
+ smb2_request_set_async_internal(smb2req, true);
+ }
+
+ /*
+ * This gets set in autobuild for some tests
+ */
+ state->find_async_delay_usec = lp_parm_ulong(SNUM(conn), "smbd",
+ "find async delay usec",
+ 0);
+
+ while (!stop) {
+ stop = smb2_query_directory_next_entry(req);
+ }
+
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ ok = aio_add_req_to_fsp(fsp, req);
+ if (!ok) {
+ DBG_ERR("Could not add req to fsp\n");
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static bool smb2_query_directory_next_entry(struct tevent_req *req)
+{
+ struct smbd_smb2_query_directory_state *state = tevent_req_data(
+ req, struct smbd_smb2_query_directory_state);
+ struct smb_filename *smb_fname = NULL; /* relative to fsp !! */
+ int off = state->out_output_buffer.length;
+ int space_remaining = state->in_output_buffer_length - off;
+ struct file_id file_id;
+ NTSTATUS status;
+ bool get_dosmode = !state->async_dosmode;
+ bool stop = false;
+
+ SMB_ASSERT(space_remaining >= 0);
+
+ status = smbd_dirptr_lanman2_entry(state,
+ state->dirfsp->conn,
+ state->dirfsp->dptr,
+ state->smbreq->flags2,
+ state->in_file_name,
+ state->dirtype,
+ state->info_level,
+ false, /* requires_resume_key */
+ state->dont_descend,
+ state->ask_sharemode,
+ get_dosmode,
+ 8, /* align to 8 bytes */
+ false, /* no padding */
+ &state->pdata,
+ state->base_data,
+ state->end_data,
+ space_remaining,
+ &smb_fname,
+ &state->last_entry_off,
+ NULL,
+ &file_id);
+
+ off = (int)PTR_DIFF(state->pdata, state->base_data);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_ILLEGAL_CHARACTER)) {
+ /*
+ * Bad character conversion on name. Ignore this
+ * entry.
+ */
+ return false;
+ }
+
+ if (state->num > 0) {
+ goto last_entry_done;
+ }
+
+ if (NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) {
+ tevent_req_nterror(req, NT_STATUS_INFO_LENGTH_MISMATCH);
+ return true;
+ }
+
+ tevent_req_nterror(req, state->empty_status);
+ return true;
+ }
+
+ if (state->async_ask_sharemode &&
+ !S_ISDIR(smb_fname->st.st_ex_mode))
+ {
+ struct tevent_req *subreq = NULL;
+ char *buf = state->base_data + state->last_entry_off;
+
+ subreq = fetch_write_time_send(state,
+ state->ev,
+ state->dirfsp->conn,
+ file_id,
+ state->info_level,
+ buf,
+ &stop);
+ if (tevent_req_nomem(subreq, req)) {
+ return true;
+ }
+ tevent_req_set_callback(
+ subreq,
+ smb2_query_directory_fetch_write_time_done,
+ req);
+ state->async_sharemode_count++;
+ }
+
+ if (state->async_dosmode) {
+ struct tevent_req *subreq = NULL;
+ uint8_t *buf = NULL;
+ size_t outstanding_aio;
+
+ buf = (uint8_t *)state->base_data + state->last_entry_off;
+
+ subreq = fetch_dos_mode_send(state,
+ state->ev,
+ state->dirfsp,
+ &smb_fname,
+ state->info_level,
+ buf);
+ if (tevent_req_nomem(subreq, req)) {
+ return true;
+ }
+ tevent_req_set_callback(subreq,
+ smb2_query_directory_dos_mode_done,
+ req);
+
+ state->async_dosmode_active++;
+
+ outstanding_aio = pthreadpool_tevent_queued_jobs(
+ state->dirfsp->conn->sconn->pool);
+
+ if (outstanding_aio > state->max_async_dosmode_active) {
+ stop = true;
+ }
+ }
+
+ TALLOC_FREE(smb_fname);
+
+ state->num++;
+ state->out_output_buffer.length = off;
+
+ if (!state->done && state->num < state->max_count) {
+ return stop;
+ }
+
+last_entry_done:
+ SIVAL(state->out_output_buffer.data, state->last_entry_off, 0);
+
+ state->done = true;
+
+ if (state->async_sharemode_count > 0) {
+ DBG_DEBUG("Stopping after %"PRIu64" async mtime "
+ "updates\n", state->async_sharemode_count);
+ return true;
+ }
+
+ if (state->async_dosmode_active > 0) {
+ return true;
+ }
+
+ if (state->find_async_delay_usec > 0) {
+ struct timeval tv;
+ struct tevent_req *subreq = NULL;
+
+ /*
+ * Should we only set async_internal
+ * if we're not the last request in
+ * a compound chain?
+ */
+ smb2_request_set_async_internal(state->smb2req, true);
+
+ tv = timeval_current_ofs(0, state->find_async_delay_usec);
+
+ subreq = tevent_wakeup_send(state, state->ev, tv);
+ if (tevent_req_nomem(subreq, req)) {
+ return true;
+ }
+ tevent_req_set_callback(subreq,
+ smb2_query_directory_waited,
+ req);
+ return true;
+ }
+
+ tevent_req_done(req);
+ return true;
+}
+
+static void smb2_query_directory_check_next_entry(struct tevent_req *req);
+
+static void smb2_query_directory_fetch_write_time_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smbd_smb2_query_directory_state *state = tevent_req_data(
+ req, struct smbd_smb2_query_directory_state);
+ NTSTATUS status;
+ bool ok;
+
+ /*
+ * Make sure we run as the user again
+ */
+ ok = change_to_user_and_service_by_fsp(state->dirfsp);
+ SMB_ASSERT(ok);
+
+ state->async_sharemode_count--;
+
+ status = fetch_write_time_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ smb2_query_directory_check_next_entry(req);
+ return;
+}
+
+static void smb2_query_directory_dos_mode_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smbd_smb2_query_directory_state *state =
+ tevent_req_data(req,
+ struct smbd_smb2_query_directory_state);
+ NTSTATUS status;
+ bool ok;
+
+ /*
+ * Make sure we run as the user again
+ */
+ ok = change_to_user_and_service_by_fsp(state->dirfsp);
+ SMB_ASSERT(ok);
+
+ status = fetch_dos_mode_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->async_dosmode_active--;
+
+ smb2_query_directory_check_next_entry(req);
+ return;
+}
+
+static void smb2_query_directory_check_next_entry(struct tevent_req *req)
+{
+ struct smbd_smb2_query_directory_state *state = tevent_req_data(
+ req, struct smbd_smb2_query_directory_state);
+ bool stop = false;
+
+ if (!state->done) {
+ while (!stop) {
+ stop = smb2_query_directory_next_entry(req);
+ }
+ return;
+ }
+
+ if (state->async_sharemode_count > 0 ||
+ state->async_dosmode_active > 0)
+ {
+ return;
+ }
+
+ if (state->find_async_delay_usec > 0) {
+ struct timeval tv;
+ struct tevent_req *subreq = NULL;
+
+ tv = timeval_current_ofs(0, state->find_async_delay_usec);
+
+ subreq = tevent_wakeup_send(state, state->ev, tv);
+ if (tevent_req_nomem(subreq, req)) {
+ tevent_req_post(req, state->ev);
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ smb2_query_directory_waited,
+ req);
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+static void smb2_query_directory_waited(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ bool ok;
+
+ ok = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ tevent_req_oom(req);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS smbd_smb2_query_directory_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out_output_buffer)
+{
+ NTSTATUS status;
+ struct smbd_smb2_query_directory_state *state = tevent_req_data(req,
+ struct smbd_smb2_query_directory_state);
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *out_output_buffer = state->out_output_buffer;
+ talloc_steal(mem_ctx, out_output_buffer->data);
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+struct fetch_write_time_state {
+ connection_struct *conn;
+ struct file_id id;
+ int info_level;
+ char *entry_marshall_buf;
+};
+
+static void fetch_write_time_done(struct tevent_req *subreq);
+
+static struct tevent_req *fetch_write_time_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ connection_struct *conn,
+ struct file_id id,
+ int info_level,
+ char *entry_marshall_buf,
+ bool *stop)
+{
+ struct tevent_req *req = NULL;
+ struct fetch_write_time_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+ bool req_queued;
+
+ *stop = false;
+
+ req = tevent_req_create(mem_ctx, &state, struct fetch_write_time_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ *state = (struct fetch_write_time_state) {
+ .conn = conn,
+ .id = id,
+ .info_level = info_level,
+ .entry_marshall_buf = entry_marshall_buf,
+ };
+
+ subreq = fetch_share_mode_send(state, ev, id, &req_queued);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, fetch_write_time_done, req);
+
+ if (req_queued) {
+ *stop = true;
+ }
+ return req;
+}
+
+static void fetch_write_time_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fetch_write_time_state *state = tevent_req_data(
+ req, struct fetch_write_time_state);
+ struct timespec write_time;
+ struct share_mode_lock *lck = NULL;
+ NTSTATUS status;
+ size_t off;
+
+ status = fetch_share_mode_recv(subreq, state, &lck);
+ TALLOC_FREE(subreq);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ tevent_req_done(req);
+ return;
+ }
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ write_time = get_share_mode_write_time(lck);
+ TALLOC_FREE(lck);
+
+ if (is_omit_timespec(&write_time)) {
+ tevent_req_done(req);
+ return;
+ }
+
+ switch (state->info_level) {
+ case SMB_FIND_FILE_DIRECTORY_INFO:
+ case SMB_FIND_FILE_FULL_DIRECTORY_INFO:
+ case SMB_FIND_FILE_BOTH_DIRECTORY_INFO:
+ case SMB_FIND_ID_FULL_DIRECTORY_INFO:
+ case SMB_FIND_ID_BOTH_DIRECTORY_INFO:
+ off = 24;
+ break;
+
+ default:
+ DBG_ERR("Unsupported info_level [%d]\n", state->info_level);
+ tevent_req_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ put_long_date_full_timespec(state->conn->ts_res,
+ state->entry_marshall_buf + off,
+ &write_time);
+
+ tevent_req_done(req);
+ return;
+}
+
+static NTSTATUS fetch_write_time_recv(struct tevent_req *req)
+{
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+struct fetch_dos_mode_state {
+ struct files_struct *dir_fsp;
+ struct smb_filename *smb_fname;
+ uint32_t info_level;
+ uint8_t *entry_marshall_buf;
+};
+
+static void fetch_dos_mode_done(struct tevent_req *subreq);
+
+static struct tevent_req *fetch_dos_mode_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *dir_fsp,
+ struct smb_filename **smb_fname,
+ uint32_t info_level,
+ uint8_t *entry_marshall_buf)
+{
+ struct tevent_req *req = NULL;
+ struct fetch_dos_mode_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct fetch_dos_mode_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ *state = (struct fetch_dos_mode_state) {
+ .dir_fsp = dir_fsp,
+ .info_level = info_level,
+ .entry_marshall_buf = entry_marshall_buf,
+ };
+
+ state->smb_fname = talloc_move(state, smb_fname);
+
+ subreq = dos_mode_at_send(state, ev, dir_fsp, state->smb_fname);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, fetch_dos_mode_done, req);
+
+ return req;
+}
+
+static void fetch_dos_mode_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct fetch_dos_mode_state *state =
+ tevent_req_data(req,
+ struct fetch_dos_mode_state);
+ uint32_t dfs_dosmode;
+ uint32_t dosmode;
+ struct timespec btime_ts = {0};
+ off_t dosmode_off;
+ off_t btime_off;
+ NTSTATUS status;
+
+ status = dos_mode_at_recv(subreq, &dosmode);
+ TALLOC_FREE(subreq);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ tevent_req_done(req);
+ return;
+ }
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ switch (state->info_level) {
+ case SMB_FIND_ID_BOTH_DIRECTORY_INFO:
+ case SMB_FIND_FILE_BOTH_DIRECTORY_INFO:
+ case SMB_FIND_FILE_DIRECTORY_INFO:
+ case SMB_FIND_FILE_FULL_DIRECTORY_INFO:
+ case SMB_FIND_ID_FULL_DIRECTORY_INFO:
+ btime_off = 8;
+ dosmode_off = 56;
+ break;
+
+ default:
+ DBG_ERR("Unsupported info_level [%u]\n", state->info_level);
+ tevent_req_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+
+ dfs_dosmode = IVAL(state->entry_marshall_buf, dosmode_off);
+ if (dfs_dosmode == 0) {
+ /*
+ * DOS mode for a DFS link, only overwrite if still set to 0 and
+ * not already populated by the lower layer for a DFS link in
+ * smbd_dirptr_lanman2_mode_fn().
+ */
+ SIVAL(state->entry_marshall_buf, dosmode_off, dosmode);
+ }
+
+ btime_ts = get_create_timespec(state->dir_fsp->conn,
+ NULL,
+ state->smb_fname);
+ if (lp_dos_filetime_resolution(SNUM(state->dir_fsp->conn))) {
+ dos_filetime_timespec(&btime_ts);
+ }
+
+ put_long_date_full_timespec(state->dir_fsp->conn->ts_res,
+ (char *)state->entry_marshall_buf + btime_off,
+ &btime_ts);
+
+ tevent_req_done(req);
+ return;
+}
+
+static NTSTATUS fetch_dos_mode_recv(struct tevent_req *req)
+{
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/smb2_read.c b/source3/smbd/smb2_read.c
new file mode 100644
index 0000000..bac78e4
--- /dev/null
+++ b/source3/smbd/smb2_read.c
@@ -0,0 +1,685 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+
+ 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 "system/filesys.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "libcli/security/security.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "rpc_server/srv_pipe_hnd.h"
+#include "lib/util/sys_rw_data.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static struct tevent_req *smbd_smb2_read_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *in_fsp,
+ uint8_t in_flags,
+ uint32_t in_length,
+ uint64_t in_offset,
+ uint32_t in_minimum,
+ uint32_t in_remaining);
+static NTSTATUS smbd_smb2_read_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out_data,
+ uint32_t *out_remaining);
+
+static void smbd_smb2_request_read_done(struct tevent_req *subreq);
+NTSTATUS smbd_smb2_request_process_read(struct smbd_smb2_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ NTSTATUS status;
+ const uint8_t *inbody;
+ uint8_t in_flags;
+ uint32_t in_length;
+ uint64_t in_offset;
+ uint64_t in_file_id_persistent;
+ uint64_t in_file_id_volatile;
+ struct files_struct *in_fsp;
+ uint32_t in_minimum_count;
+ uint32_t in_remaining_bytes;
+ struct tevent_req *subreq;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x31);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ if (xconn->protocol >= PROTOCOL_SMB3_02) {
+ in_flags = CVAL(inbody, 0x03);
+ } else {
+ in_flags = 0;
+ }
+ in_length = IVAL(inbody, 0x04);
+ in_offset = BVAL(inbody, 0x08);
+ in_file_id_persistent = BVAL(inbody, 0x10);
+ in_file_id_volatile = BVAL(inbody, 0x18);
+ in_minimum_count = IVAL(inbody, 0x20);
+ in_remaining_bytes = IVAL(inbody, 0x28);
+
+ /* check the max read size */
+ if (in_length > xconn->smb2.server.max_read) {
+ DEBUG(2,("smbd_smb2_request_process_read: "
+ "client ignored max read: %s: 0x%08X: 0x%08X\n",
+ __location__, in_length, xconn->smb2.server.max_read));
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ status = smbd_smb2_request_verify_creditcharge(req, in_length);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ in_fsp = file_fsp_smb2(req, in_file_id_persistent, in_file_id_volatile);
+ if (in_fsp == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED);
+ }
+
+ subreq = smbd_smb2_read_send(req, req->sconn->ev_ctx,
+ req, in_fsp,
+ in_flags,
+ in_length,
+ in_offset,
+ in_minimum_count,
+ in_remaining_bytes);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_read_done, req);
+
+ return smbd_smb2_request_pending_queue(req, subreq, 500);
+}
+
+static void smbd_smb2_request_read_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *req = tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ uint16_t body_size;
+ uint8_t body_padding = req->xconn->smb2.smbtorture.read_body_padding;
+ DATA_BLOB outbody;
+ DATA_BLOB outdyn;
+ uint8_t out_data_offset;
+ DATA_BLOB out_data_buffer = data_blob_null;
+ uint32_t out_data_remaining = 0;
+ NTSTATUS status;
+ NTSTATUS error; /* transport error */
+
+ status = smbd_smb2_read_recv(subreq,
+ req,
+ &out_data_buffer,
+ &out_data_remaining);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ error = smbd_smb2_request_error(req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ /*
+ * Only FSCTL_SMBTORTURE_GLOBAL_READ_RESPONSE_BODY_PADDING8
+ * sets body_padding to a value different from 0.
+ */
+ body_size = 0x10 + body_padding;
+ out_data_offset = SMB2_HDR_BODY + body_size;
+
+ outbody = smbd_smb2_generate_outbody(req, body_size);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x10 + 1); /* struct size */
+ SCVAL(outbody.data, 0x02,
+ out_data_offset); /* data offset */
+ SCVAL(outbody.data, 0x03, 0); /* reserved */
+ SIVAL(outbody.data, 0x04,
+ out_data_buffer.length); /* data length */
+ SIVAL(outbody.data, 0x08,
+ out_data_remaining); /* data remaining */
+ SIVAL(outbody.data, 0x0C, 0); /* reserved */
+ if (body_padding != 0) {
+ memset(outbody.data + 0x10, 0, body_padding);
+ }
+
+ outdyn = out_data_buffer;
+
+ error = smbd_smb2_request_done(req, outbody, &outdyn);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+struct smbd_smb2_read_state {
+ struct smbd_smb2_request *smb2req;
+ struct smb_request *smbreq;
+ files_struct *fsp;
+ uint8_t in_flags;
+ uint32_t in_length;
+ uint64_t in_offset;
+ uint32_t in_minimum;
+ DATA_BLOB out_headers;
+ uint8_t _out_hdr_buf[NBT_HDR_SIZE + SMB2_HDR_BODY + 0x10];
+ DATA_BLOB out_data;
+ uint32_t out_remaining;
+};
+
+static int smb2_smb2_read_state_deny_destructor(struct smbd_smb2_read_state *state)
+{
+ return -1;
+}
+
+/* struct smbd_smb2_read_state destructor. Send the SMB2_READ data. */
+static int smb2_sendfile_send_data(struct smbd_smb2_read_state *state)
+{
+ struct lock_struct lock;
+ uint32_t in_length = state->in_length;
+ uint64_t in_offset = state->in_offset;
+ files_struct *fsp = state->fsp;
+ const DATA_BLOB *hdr = state->smb2req->queue_entry.sendfile_header;
+ NTSTATUS *pstatus = state->smb2req->queue_entry.sendfile_status;
+ struct smbXsrv_connection *xconn = state->smb2req->xconn;
+ ssize_t nread;
+ ssize_t ret;
+ int saved_errno;
+
+ nread = SMB_VFS_SENDFILE(xconn->transport.sock,
+ fsp,
+ hdr,
+ in_offset,
+ in_length);
+ DEBUG(10,("smb2_sendfile_send_data: SMB_VFS_SENDFILE returned %d on file %s\n",
+ (int)nread,
+ fsp_str_dbg(fsp) ));
+
+ if (nread == -1) {
+ saved_errno = errno;
+
+ /*
+ * Returning ENOSYS means no data at all was sent.
+ Do this as a normal read. */
+ if (errno == ENOSYS) {
+ goto normal_read;
+ }
+
+ if (errno == ENOTSUP) {
+ set_use_sendfile(SNUM(fsp->conn), false);
+ DBG_WARNING("Disabling sendfile use as sendfile is "
+ "not supported by the system\n");
+ goto normal_read;
+ }
+
+ if (errno == EINTR) {
+ /*
+ * Special hack for broken Linux with no working sendfile. If we
+ * return EINTR we sent the header but not the rest of the data.
+ * Fake this up by doing read/write calls.
+ */
+ set_use_sendfile(SNUM(fsp->conn), false);
+ nread = fake_sendfile(xconn, fsp, in_offset, in_length);
+ if (nread == -1) {
+ saved_errno = errno;
+ DEBUG(0,("smb2_sendfile_send_data: fake_sendfile "
+ "failed for file %s (%s) for client %s. "
+ "Terminating\n",
+ fsp_str_dbg(fsp), strerror(saved_errno),
+ smbXsrv_connection_dbg(xconn)));
+ *pstatus = map_nt_error_from_unix_common(saved_errno);
+ return 0;
+ }
+ goto out;
+ }
+
+ DEBUG(0,("smb2_sendfile_send_data: sendfile failed for file "
+ "%s (%s) for client %s. Terminating\n",
+ fsp_str_dbg(fsp), strerror(saved_errno),
+ smbXsrv_connection_dbg(xconn)));
+ *pstatus = map_nt_error_from_unix_common(saved_errno);
+ return 0;
+ } else if (nread == 0) {
+ /*
+ * Some sendfile implementations return 0 to indicate
+ * that there was a short read, but nothing was
+ * actually written to the socket. In this case,
+ * fallback to the normal read path so the header gets
+ * the correct byte count.
+ */
+ DEBUG(3, ("send_file_readX: sendfile sent zero bytes "
+ "falling back to the normal read: %s\n",
+ fsp_str_dbg(fsp)));
+ goto normal_read;
+ }
+
+ /*
+ * We got a short read
+ */
+ goto out;
+
+normal_read:
+ /* Send out the header. */
+ ret = write_data(xconn->transport.sock,
+ (const char *)hdr->data, hdr->length);
+ if (ret != hdr->length) {
+ saved_errno = errno;
+ DEBUG(0,("smb2_sendfile_send_data: write_data failed for file "
+ "%s (%s) for client %s. Terminating\n",
+ fsp_str_dbg(fsp), strerror(saved_errno),
+ smbXsrv_connection_dbg(xconn)));
+ *pstatus = map_nt_error_from_unix_common(saved_errno);
+ return 0;
+ }
+ nread = fake_sendfile(xconn, fsp, in_offset, in_length);
+ if (nread == -1) {
+ saved_errno = errno;
+ DEBUG(0,("smb2_sendfile_send_data: fake_sendfile "
+ "failed for file %s (%s) for client %s. "
+ "Terminating\n",
+ fsp_str_dbg(fsp), strerror(saved_errno),
+ smbXsrv_connection_dbg(xconn)));
+ *pstatus = map_nt_error_from_unix_common(saved_errno);
+ return 0;
+ }
+
+ out:
+
+ if (nread < in_length) {
+ ret = sendfile_short_send(xconn, fsp, nread,
+ hdr->length, in_length);
+ if (ret == -1) {
+ saved_errno = errno;
+ DEBUG(0,("%s: sendfile_short_send "
+ "failed for file %s (%s) for client %s. "
+ "Terminating\n",
+ __func__,
+ fsp_str_dbg(fsp), strerror(saved_errno),
+ smbXsrv_connection_dbg(xconn)));
+ *pstatus = map_nt_error_from_unix_common(saved_errno);
+ return 0;
+ }
+ }
+
+ init_strict_lock_struct(fsp,
+ fsp->op->global->open_persistent_id,
+ in_offset,
+ in_length,
+ READ_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &lock);
+
+ *pstatus = NT_STATUS_OK;
+ return 0;
+}
+
+static NTSTATUS schedule_smb2_sendfile_read(struct smbd_smb2_request *smb2req,
+ struct smbd_smb2_read_state *state)
+{
+ files_struct *fsp = state->fsp;
+
+ /*
+ * We cannot use sendfile if...
+ * We were not configured to do so OR
+ * Signing is active OR
+ * This is a compound SMB2 operation OR
+ * fsp is a STREAM file OR
+ * It's not a regular file OR
+ * Requested offset is greater than file size OR
+ * there's not enough data in the file.
+ * Phew :-). Luckily this means most
+ * reads on most normal files. JRA.
+ */
+
+ if (!lp__use_sendfile(SNUM(fsp->conn)) ||
+ smb2req->do_signing ||
+ smb2req->do_encryption ||
+ smbd_smb2_is_compound(smb2req) ||
+ fsp_is_alternate_stream(fsp) ||
+ (!S_ISREG(fsp->fsp_name->st.st_ex_mode)) ||
+ (state->in_offset >= fsp->fsp_name->st.st_ex_size) ||
+ (fsp->fsp_name->st.st_ex_size < state->in_offset + state->in_length))
+ {
+ return NT_STATUS_RETRY;
+ }
+
+ /* We've already checked there's this amount of data
+ to read. */
+ state->out_data.length = state->in_length;
+ state->out_remaining = 0;
+
+ state->out_headers = data_blob_const(state->_out_hdr_buf,
+ sizeof(state->_out_hdr_buf));
+ return NT_STATUS_OK;
+}
+
+static void smbd_smb2_read_pipe_done(struct tevent_req *subreq);
+
+/*******************************************************************
+ Common read complete processing function for both synchronous and
+ asynchronous reads.
+*******************************************************************/
+
+NTSTATUS smb2_read_complete(struct tevent_req *req, ssize_t nread, int err)
+{
+ struct smbd_smb2_read_state *state = tevent_req_data(req,
+ struct smbd_smb2_read_state);
+ files_struct *fsp = state->fsp;
+
+ if (nread < 0) {
+ NTSTATUS status = map_nt_error_from_unix(err);
+
+ DEBUG( 3,( "smb2_read_complete: file %s nread = %d. "
+ "Error = %s (NTSTATUS %s)\n",
+ fsp_str_dbg(fsp),
+ (int)nread,
+ strerror(err),
+ nt_errstr(status)));
+
+ return status;
+ }
+ if (nread == 0 && state->in_length != 0) {
+ DEBUG(5,("smb2_read_complete: read_file[%s] end of file\n",
+ fsp_str_dbg(fsp)));
+ return NT_STATUS_END_OF_FILE;
+ }
+
+ if (nread < state->in_minimum) {
+ DEBUG(5,("smb2_read_complete: read_file[%s] read less %d than "
+ "minimum requested %u. Returning end of file\n",
+ fsp_str_dbg(fsp),
+ (int)nread,
+ (unsigned int)state->in_minimum));
+ return NT_STATUS_END_OF_FILE;
+ }
+
+ DEBUG(3,("smbd_smb2_read: %s, file %s, length=%lu offset=%lu read=%lu\n",
+ fsp_fnum_dbg(fsp),
+ fsp_str_dbg(fsp),
+ (unsigned long)state->in_length,
+ (unsigned long)state->in_offset,
+ (unsigned long)nread));
+
+ state->out_data.length = nread;
+ state->out_remaining = 0;
+
+ return NT_STATUS_OK;
+}
+
+static bool smbd_smb2_read_cancel(struct tevent_req *req)
+{
+ struct smbd_smb2_read_state *state =
+ tevent_req_data(req,
+ struct smbd_smb2_read_state);
+
+ return cancel_smb2_aio(state->smbreq);
+}
+
+static struct tevent_req *smbd_smb2_read_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *fsp,
+ uint8_t in_flags,
+ uint32_t in_length,
+ uint64_t in_offset,
+ uint32_t in_minimum,
+ uint32_t in_remaining)
+{
+ NTSTATUS status;
+ struct tevent_req *req = NULL;
+ struct smbd_smb2_read_state *state = NULL;
+ struct smb_request *smbreq = NULL;
+ connection_struct *conn = smb2req->tcon->compat;
+ ssize_t nread = -1;
+ struct lock_struct lock;
+ int saved_errno;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_read_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->smb2req = smb2req;
+ state->in_flags = in_flags;
+ state->in_length = in_length;
+ state->in_offset = in_offset;
+ state->in_minimum = in_minimum;
+ state->out_data = data_blob_null;
+ state->out_remaining = 0;
+
+ DEBUG(10,("smbd_smb2_read: %s - %s\n",
+ fsp_str_dbg(fsp), fsp_fnum_dbg(fsp)));
+
+ smbreq = smbd_smb2_fake_smb_request(smb2req, fsp);
+ if (tevent_req_nomem(smbreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->smbreq = smbreq;
+
+ if (fsp->fsp_flags.is_directory) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_DEVICE_REQUEST);
+ return tevent_req_post(req, ev);
+ }
+
+ state->fsp = fsp;
+
+ if (IS_IPC(smbreq->conn)) {
+ struct tevent_req *subreq = NULL;
+ bool ok;
+
+ state->out_data = data_blob_talloc(state, NULL, in_length);
+ if (in_length > 0 && tevent_req_nomem(state->out_data.data, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (!fsp_is_np(fsp)) {
+ tevent_req_nterror(req, NT_STATUS_FILE_CLOSED);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = np_read_send(state, ev,
+ fsp->fake_file_handle,
+ state->out_data.data,
+ state->out_data.length);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ smbd_smb2_read_pipe_done,
+ req);
+
+ /*
+ * Make sure we mark the fsp as having outstanding async
+ * activity so we don't crash on shutdown close.
+ */
+
+ ok = aio_add_req_to_fsp(fsp, req);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+ }
+
+ if (!CHECK_READ_SMB2(fsp)) {
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return tevent_req_post(req, ev);
+ }
+
+ status = schedule_smb2_aio_read(fsp->conn,
+ smbreq,
+ fsp,
+ state,
+ &state->out_data,
+ (off_t)in_offset,
+ (size_t)in_length);
+
+ if (NT_STATUS_IS_OK(status)) {
+ /*
+ * Doing an async read, allow this
+ * request to be canceled
+ */
+ tevent_req_set_cancel_fn(req, smbd_smb2_read_cancel);
+ return req;
+ }
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
+ /* Real error in setting up aio. Fail. */
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ /* Fallback to synchronous. */
+
+ init_strict_lock_struct(fsp,
+ fsp->op->global->open_persistent_id,
+ in_offset,
+ in_length,
+ READ_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &lock);
+
+ if (!SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, &lock)) {
+ tevent_req_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
+ return tevent_req_post(req, ev);
+ }
+
+ /* Try sendfile in preference. */
+ status = schedule_smb2_sendfile_read(smb2req, state);
+ if (NT_STATUS_IS_OK(status)) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ } else {
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ /* Ok, read into memory. Allocate the out buffer. */
+ state->out_data = data_blob_talloc(state, NULL, in_length);
+ if (in_length > 0 && tevent_req_nomem(state->out_data.data, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ nread = read_file(fsp,
+ (char *)state->out_data.data,
+ in_offset,
+ in_length);
+
+ saved_errno = errno;
+
+ DEBUG(10,("smbd_smb2_read: file %s, %s, offset=%llu "
+ "len=%llu returned %lld\n",
+ fsp_str_dbg(fsp),
+ fsp_fnum_dbg(fsp),
+ (unsigned long long)in_offset,
+ (unsigned long long)in_length,
+ (long long)nread));
+
+ status = smb2_read_complete(req, nread, saved_errno);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ } else {
+ /* Success. */
+ tevent_req_done(req);
+ }
+ return tevent_req_post(req, ev);
+}
+
+static void smbd_smb2_read_pipe_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smbd_smb2_read_state *state = tevent_req_data(req,
+ struct smbd_smb2_read_state);
+ NTSTATUS status;
+ ssize_t nread = -1;
+ bool is_data_outstanding;
+
+ status = np_read_recv(subreq, &nread, &is_data_outstanding);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ NTSTATUS old = status;
+ status = nt_status_np_pipe(old);
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ if (nread == 0 && state->out_data.length != 0) {
+ tevent_req_nterror(req, NT_STATUS_END_OF_FILE);
+ return;
+ }
+
+ state->out_data.length = nread;
+ state->out_remaining = 0;
+
+ /*
+ * TODO: add STATUS_BUFFER_OVERFLOW handling, once we also
+ * handle it in SMB1 pipe_read_andx_done().
+ */
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS smbd_smb2_read_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out_data,
+ uint32_t *out_remaining)
+{
+ NTSTATUS status;
+ struct smbd_smb2_read_state *state = tevent_req_data(req,
+ struct smbd_smb2_read_state);
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *out_data = state->out_data;
+ talloc_steal(mem_ctx, out_data->data);
+ *out_remaining = state->out_remaining;
+
+ if (state->out_headers.length > 0) {
+ talloc_steal(mem_ctx, state);
+ talloc_set_destructor(state, smb2_smb2_read_state_deny_destructor);
+ tevent_req_received(req);
+ state->smb2req->queue_entry.sendfile_header = &state->out_headers;
+ state->smb2req->queue_entry.sendfile_body_size = state->in_length;
+ talloc_set_destructor(state, smb2_sendfile_send_data);
+ } else {
+ tevent_req_received(req);
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/smb2_reply.c b/source3/smbd/smb2_reply.c
new file mode 100644
index 0000000..ab12399
--- /dev/null
+++ b/source3/smbd/smb2_reply.c
@@ -0,0 +1,2195 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main SMB reply routines
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Andrew Bartlett 2001
+ Copyright (C) Jeremy Allison 1992-2007.
+ Copyright (C) Volker Lendecke 2007
+
+ 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/>.
+*/
+/*
+ This file handles most of the reply_ calls that the server
+ makes to handle specific protocols
+*/
+
+#include "includes.h"
+#include "libsmb/namequery.h"
+#include "system/filesys.h"
+#include "printing.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "smbd/smbXsrv_open.h"
+#include "fake_file.h"
+#include "rpc_client/rpc_client.h"
+#include "../librpc/gen_ndr/ndr_spoolss_c.h"
+#include "rpc_client/cli_spoolss.h"
+#include "rpc_client/init_spoolss.h"
+#include "rpc_server/rpc_ncacn_np.h"
+#include "libcli/security/security.h"
+#include "libsmb/nmblib.h"
+#include "auth.h"
+#include "smbprofile.h"
+#include "../lib/tsocket/tsocket.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "libcli/smb/smb_signing.h"
+#include "lib/util/sys_rw_data.h"
+#include "librpc/gen_ndr/open_files.h"
+#include "libcli/smb/smb2_posix.h"
+#include "lib/util/string_wrappers.h"
+#include "source3/printing/rap_jobid.h"
+#include "source3/lib/substitute.h"
+#include "source3/smbd/dir.h"
+
+/****************************************************************************
+ Ensure we check the path in *exactly* the same way as W2K for a findfirst/findnext
+ path or anything including wildcards.
+ We're assuming here that '/' is not the second byte in any multibyte char
+ set (a safe assumption). '\\' *may* be the second byte in a multibyte char
+ set.
+****************************************************************************/
+
+/* Custom version for processing POSIX paths. */
+#define IS_PATH_SEP(c,posix_only) ((c) == '/' || (!(posix_only) && (c) == '\\'))
+
+NTSTATUS check_path_syntax(char *path, bool posix_path)
+{
+ char *d = path;
+ const char *s = path;
+ NTSTATUS ret = NT_STATUS_OK;
+ bool start_of_name_component = True;
+ bool stream_started = false;
+ bool last_component_contains_wcard = false;
+
+ while (*s) {
+ if (stream_started) {
+ switch (*s) {
+ case '/':
+ case '\\':
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ case ':':
+ if (s[1] == '\0') {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ if (strchr_m(&s[1], ':')) {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ break;
+ }
+ }
+
+ if ((*s == ':') && !posix_path && !stream_started) {
+ if (last_component_contains_wcard) {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ /* Stream names allow more characters than file names.
+ We're overloading posix_path here to allow a wider
+ range of characters. If stream_started is true this
+ is still a Windows path even if posix_path is true.
+ JRA.
+ */
+ stream_started = true;
+ start_of_name_component = false;
+ posix_path = true;
+
+ if (s[1] == '\0') {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ }
+
+ if (!stream_started && IS_PATH_SEP(*s,posix_path)) {
+ /*
+ * Safe to assume is not the second part of a mb char
+ * as this is handled below.
+ */
+ /* Eat multiple '/' or '\\' */
+ while (IS_PATH_SEP(*s,posix_path)) {
+ s++;
+ }
+ if ((d != path) && (*s != '\0')) {
+ /* We only care about non-leading or trailing '/' or '\\' */
+ *d++ = '/';
+ }
+
+ start_of_name_component = True;
+ /* New component. */
+ last_component_contains_wcard = false;
+ continue;
+ }
+
+ if (start_of_name_component) {
+ if ((s[0] == '.') && (s[1] == '.') && (IS_PATH_SEP(s[2],posix_path) || s[2] == '\0')) {
+ /* Uh oh - "/../" or "\\..\\" or "/..\0" or "\\..\0" ! */
+
+ /*
+ * No mb char starts with '.' so we're safe checking the directory separator here.
+ */
+
+ /* If we just added a '/' - delete it */
+ if ((d > path) && (*(d-1) == '/')) {
+ *(d-1) = '\0';
+ d--;
+ }
+
+ /* Are we at the start ? Can't go back further if so. */
+ if (d <= path) {
+ ret = NT_STATUS_OBJECT_PATH_SYNTAX_BAD;
+ break;
+ }
+ /* Go back one level... */
+ /* We know this is safe as '/' cannot be part of a mb sequence. */
+ /* NOTE - if this assumption is invalid we are not in good shape... */
+ /* Decrement d first as d points to the *next* char to write into. */
+ for (d--; d > path; d--) {
+ if (*d == '/')
+ break;
+ }
+ s += 2; /* Else go past the .. */
+ /* We're still at the start of a name component, just the previous one. */
+ continue;
+
+ } else if ((s[0] == '.') && ((s[1] == '\0') || IS_PATH_SEP(s[1],posix_path))) {
+ if (posix_path) {
+ /* Eat the '.' */
+ s++;
+ continue;
+ }
+ }
+
+ }
+
+ if (!(*s & 0x80)) {
+ if (!posix_path) {
+ if (*s <= 0x1f || *s == '|') {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ switch (*s) {
+ case '*':
+ case '?':
+ case '<':
+ case '>':
+ case '"':
+ last_component_contains_wcard = true;
+ break;
+ default:
+ break;
+ }
+ }
+ *d++ = *s++;
+ } else {
+ size_t ch_size;
+ /* Get the size of the next MB character. */
+ next_codepoint(s,&ch_size);
+ switch(ch_size) {
+ case 5:
+ *d++ = *s++;
+ FALL_THROUGH;
+ case 4:
+ *d++ = *s++;
+ FALL_THROUGH;
+ case 3:
+ *d++ = *s++;
+ FALL_THROUGH;
+ case 2:
+ *d++ = *s++;
+ FALL_THROUGH;
+ case 1:
+ *d++ = *s++;
+ break;
+ default:
+ DBG_ERR("character length assumptions invalid !\n");
+ *d = '\0';
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ }
+ start_of_name_component = False;
+ }
+
+ *d = '\0';
+
+ return ret;
+}
+
+/****************************************************************************
+ SMB2-only code to strip an MSDFS prefix from an incoming pathname.
+****************************************************************************/
+
+NTSTATUS smb2_strip_dfs_path(const char *in_path, const char **out_path)
+{
+ const char *path = in_path;
+
+ /* Match the Windows 2022 behavior for an empty DFS pathname. */
+ if (*path == '\0') {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ /* Strip any leading '\\' characters - MacOSX client behavior. */
+ while (*path == '\\') {
+ path++;
+ }
+ /* We should now be pointing at the server name. Go past it. */
+ for (;;) {
+ if (*path == '\0') {
+ /* End of complete path. Exit OK. */
+ goto out;
+ }
+ if (*path == '\\') {
+ /* End of server name. Go past and break. */
+ path++;
+ break;
+ }
+ path++; /* Continue looking for end of server name or string. */
+ }
+
+ /* We should now be pointing at the share name. Go past it. */
+ for (;;) {
+ if (*path == '\0') {
+ /* End of complete path. Exit OK. */
+ goto out;
+ }
+ if (*path == '\\') {
+ /* End of share name. Go past and break. */
+ path++;
+ break;
+ }
+ if (*path == ':') {
+ /* Only invalid character in sharename. */
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ path++; /* Continue looking for end of share name or string. */
+ }
+
+ /* path now points at the start of the real filename (if any). */
+
+ out:
+ /* We have stripped the DFS path prefix (if any). */
+ *out_path = path;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Pull a string and check the path allowing a wildcard - provide for error return.
+ Passes in posix flag.
+****************************************************************************/
+
+static size_t srvstr_get_path_internal(TALLOC_CTX *ctx,
+ const char *base_ptr,
+ uint16_t smb_flags2,
+ char **pp_dest,
+ const char *src,
+ size_t src_len,
+ int flags,
+ bool posix_pathnames,
+ NTSTATUS *err)
+{
+ size_t ret;
+ char *dst = NULL;
+
+ *pp_dest = NULL;
+
+ ret = srvstr_pull_talloc(ctx, base_ptr, smb_flags2, pp_dest, src,
+ src_len, flags);
+
+ if (!*pp_dest) {
+ *err = NT_STATUS_INVALID_PARAMETER;
+ return ret;
+ }
+
+ dst = *pp_dest;
+
+ if (smb_flags2 & FLAGS2_DFS_PATHNAMES) {
+ /*
+ * A valid DFS path looks either like
+ * /server/share
+ * \server\share
+ * (there may be more components after).
+ * Either way it must have at least two separators.
+ *
+ * Ensure we end up as /server/share
+ * so we don't need to special case
+ * separator characters elsewhere in
+ * the code.
+ */
+ char *server = NULL;
+ char *share = NULL;
+ char *remaining_path = NULL;
+ char path_sep = 0;
+ char *p = NULL;
+
+ if (posix_pathnames && (dst[0] == '/')) {
+ path_sep = dst[0];
+ } else if (dst[0] == '\\') {
+ path_sep = dst[0];
+ }
+
+ if (path_sep == 0) {
+ goto local_path;
+ }
+ /*
+ * May be a DFS path.
+ * We need some heuristics here,
+ * as clients differ on what constitutes
+ * a well-formed DFS path. If the path
+ * appears malformed, just fall back to
+ * processing as a local path.
+ */
+ server = dst;
+
+ /*
+ * Cosmetic fix for Linux-only DFS clients.
+ * The Linux kernel SMB1 client has a bug - it sends
+ * DFS pathnames as:
+ *
+ * \\server\share\path
+ *
+ * Causing us to mis-parse server,share,remaining_path here
+ * and jump into 'goto local_path' at 'share\path' instead
+ * of 'path'.
+ *
+ * This doesn't cause an error as the limits on share names
+ * are similar to those on pathnames.
+ *
+ * parse_dfs_path() which we call before filename parsing
+ * copes with this by calling trim_char on the leading '\'
+ * characters before processing.
+ * Do the same here so logging of pathnames looks better.
+ */
+ if (server[1] == path_sep) {
+ trim_char(&server[1], path_sep, '\0');
+ }
+
+ /*
+ * Look to see if we also have /share following.
+ */
+ share = strchr(server+1, path_sep);
+ if (share == NULL) {
+ goto local_path;
+ }
+ /*
+ * Ensure the server name does not contain
+ * any possible path components by converting
+ * them to _'s.
+ */
+ for (p = server + 1; p < share; p++) {
+ if (*p == '/' || *p == '\\') {
+ *p = '_';
+ }
+ }
+ /*
+ * It's a well formed DFS path with
+ * at least server and share components.
+ * Replace the slashes with '/' and
+ * pass the remainder to local_path.
+ */
+ *server = '/';
+ *share = '/';
+ /*
+ * Skip past share so we don't pass the
+ * sharename into check_path_syntax().
+ */
+ remaining_path = strchr(share+1, path_sep);
+ if (remaining_path == NULL) {
+ /*
+ * Ensure the share name does not contain
+ * any possible path components by converting
+ * them to _'s.
+ */
+ for (p = share + 1; *p; p++) {
+ if (*p == '/' || *p == '\\') {
+ *p = '_';
+ }
+ }
+ /*
+ * If no remaining path this was
+ * a bare /server/share path. Just return.
+ */
+ *err = NT_STATUS_OK;
+ return ret;
+ }
+ /*
+ * Ensure the share name does not contain
+ * any possible path components by converting
+ * them to _'s.
+ */
+ for (p = share + 1; p < remaining_path; p++) {
+ if (*p == '/' || *p == '\\') {
+ *p = '_';
+ }
+ }
+ *remaining_path = '/';
+ dst = remaining_path + 1;
+ /* dst now points at any following components. */
+ }
+
+ local_path:
+
+ *err = check_path_syntax(dst, posix_pathnames);
+
+ return ret;
+}
+
+/****************************************************************************
+ Pull a string and check the path - provide for error return.
+****************************************************************************/
+
+size_t srvstr_get_path(TALLOC_CTX *ctx,
+ const char *base_ptr,
+ uint16_t smb_flags2,
+ char **pp_dest,
+ const char *src,
+ size_t src_len,
+ int flags,
+ NTSTATUS *err)
+{
+ return srvstr_get_path_internal(ctx,
+ base_ptr,
+ smb_flags2,
+ pp_dest,
+ src,
+ src_len,
+ flags,
+ false,
+ err);
+}
+
+/****************************************************************************
+ Pull a string and check the path - provide for error return.
+ posix_pathnames version.
+****************************************************************************/
+
+size_t srvstr_get_path_posix(TALLOC_CTX *ctx,
+ const char *base_ptr,
+ uint16_t smb_flags2,
+ char **pp_dest,
+ const char *src,
+ size_t src_len,
+ int flags,
+ NTSTATUS *err)
+{
+ return srvstr_get_path_internal(ctx,
+ base_ptr,
+ smb_flags2,
+ pp_dest,
+ src,
+ src_len,
+ flags,
+ true,
+ err);
+}
+
+
+size_t srvstr_get_path_req(TALLOC_CTX *mem_ctx, struct smb_request *req,
+ char **pp_dest, const char *src, int flags,
+ NTSTATUS *err)
+{
+ ssize_t bufrem = smbreq_bufrem(req, src);
+
+ if (bufrem == 0) {
+ *err = NT_STATUS_INVALID_PARAMETER;
+ return 0;
+ }
+
+ if (req->posix_pathnames) {
+ return srvstr_get_path_internal(mem_ctx,
+ (const char *)req->inbuf,
+ req->flags2,
+ pp_dest,
+ src,
+ bufrem,
+ flags,
+ true,
+ err);
+ } else {
+ return srvstr_get_path_internal(mem_ctx,
+ (const char *)req->inbuf,
+ req->flags2,
+ pp_dest,
+ src,
+ bufrem,
+ flags,
+ false,
+ err);
+ }
+}
+
+/**
+ * pull a string from the smb_buf part of a packet. In this case the
+ * string can either be null terminated or it can be terminated by the
+ * end of the smbbuf area
+ */
+size_t srvstr_pull_req_talloc(TALLOC_CTX *ctx, struct smb_request *req,
+ char **dest, const uint8_t *src, int flags)
+{
+ ssize_t bufrem = smbreq_bufrem(req, src);
+
+ if (bufrem == 0) {
+ *dest = NULL;
+ return 0;
+ }
+
+ return pull_string_talloc(ctx, req->inbuf, req->flags2, dest, src,
+ bufrem, flags);
+}
+
+/****************************************************************************
+ Check if we have a correct fsp pointing to a quota fake file. Replacement for
+ the CHECK_NTQUOTA_HANDLE_OK macro.
+****************************************************************************/
+
+bool check_fsp_ntquota_handle(connection_struct *conn, struct smb_request *req,
+ files_struct *fsp)
+{
+ if ((fsp == NULL) || (conn == NULL)) {
+ return false;
+ }
+
+ if ((conn != fsp->conn) || (req->vuid != fsp->vuid)) {
+ return false;
+ }
+
+ if (fsp->fsp_flags.is_directory) {
+ return false;
+ }
+
+ if (fsp->fake_file_handle == NULL) {
+ return false;
+ }
+
+ if (fsp->fake_file_handle->type != FAKE_FILE_TYPE_QUOTA) {
+ return false;
+ }
+
+ if (fsp->fake_file_handle->private_data == NULL) {
+ return false;
+ }
+
+ return true;
+}
+
+/****************************************************************************
+ Return the port number we've bound to on a socket.
+****************************************************************************/
+
+static int get_socket_port(int fd)
+{
+ struct samba_sockaddr saddr = {
+ .sa_socklen = sizeof(struct sockaddr_storage),
+ };
+
+ if (fd == -1) {
+ return -1;
+ }
+
+ if (getsockname(fd, &saddr.u.sa, &saddr.sa_socklen) < 0) {
+ int level = (errno == ENOTCONN) ? 2 : 0;
+ DEBUG(level, ("getsockname failed. Error was %s\n",
+ strerror(errno)));
+ return -1;
+ }
+
+#if defined(HAVE_IPV6)
+ if (saddr.u.sa.sa_family == AF_INET6) {
+ return ntohs(saddr.u.in6.sin6_port);
+ }
+#endif
+ if (saddr.u.sa.sa_family == AF_INET) {
+ return ntohs(saddr.u.in.sin_port);
+ }
+ return -1;
+}
+
+static bool netbios_session_retarget(struct smbXsrv_connection *xconn,
+ const char *name, int name_type)
+{
+ char *trim_name;
+ char *trim_name_type;
+ const char *retarget_parm;
+ char *retarget;
+ char *p;
+ int retarget_type = 0x20;
+ int retarget_port = NBT_SMB_PORT;
+ struct sockaddr_storage retarget_addr;
+ struct sockaddr_in *in_addr;
+ bool ret = false;
+ uint8_t outbuf[10];
+
+ if (get_socket_port(xconn->transport.sock) != NBT_SMB_PORT) {
+ return false;
+ }
+
+ trim_name = talloc_strdup(talloc_tos(), name);
+ if (trim_name == NULL) {
+ goto fail;
+ }
+ trim_char(trim_name, ' ', ' ');
+
+ trim_name_type = talloc_asprintf(trim_name, "%s#%2.2x", trim_name,
+ name_type);
+ if (trim_name_type == NULL) {
+ goto fail;
+ }
+
+ retarget_parm = lp_parm_const_string(-1, "netbios retarget",
+ trim_name_type, NULL);
+ if (retarget_parm == NULL) {
+ retarget_parm = lp_parm_const_string(-1, "netbios retarget",
+ trim_name, NULL);
+ }
+ if (retarget_parm == NULL) {
+ goto fail;
+ }
+
+ retarget = talloc_strdup(trim_name, retarget_parm);
+ if (retarget == NULL) {
+ goto fail;
+ }
+
+ DEBUG(10, ("retargeting %s to %s\n", trim_name_type, retarget));
+
+ p = strchr(retarget, ':');
+ if (p != NULL) {
+ *p++ = '\0';
+ retarget_port = atoi(p);
+ }
+
+ p = strchr_m(retarget, '#');
+ if (p != NULL) {
+ *p++ = '\0';
+ if (sscanf(p, "%x", &retarget_type) != 1) {
+ goto fail;
+ }
+ }
+
+ ret = resolve_name(retarget, &retarget_addr, retarget_type, false);
+ if (!ret) {
+ DEBUG(10, ("could not resolve %s\n", retarget));
+ goto fail;
+ }
+
+ if (retarget_addr.ss_family != AF_INET) {
+ DEBUG(10, ("Retarget target not an IPv4 addr\n"));
+ goto fail;
+ }
+
+ in_addr = (struct sockaddr_in *)(void *)&retarget_addr;
+
+ _smb_setlen(outbuf, 6);
+ SCVAL(outbuf, 0, 0x84);
+ *(uint32_t *)(outbuf+4) = in_addr->sin_addr.s_addr;
+ *(uint16_t *)(outbuf+8) = htons(retarget_port);
+
+ if (!smb1_srv_send(xconn, (char *)outbuf, false, 0, false)) {
+ exit_server_cleanly("netbios_session_retarget: smb1_srv_send "
+ "failed.");
+ }
+
+ ret = true;
+ fail:
+ TALLOC_FREE(trim_name);
+ return ret;
+}
+
+static void reply_called_name_not_present(char *outbuf)
+{
+ smb_setlen(outbuf, 1);
+ SCVAL(outbuf, 0, 0x83);
+ SCVAL(outbuf, 4, 0x82);
+}
+
+/****************************************************************************
+ Reply to a (netbios-level) special message.
+****************************************************************************/
+
+void reply_special(struct smbXsrv_connection *xconn, char *inbuf, size_t inbuf_size)
+{
+ struct smbd_server_connection *sconn = xconn->client->sconn;
+ int msg_type = CVAL(inbuf,0);
+ int msg_flags = CVAL(inbuf,1);
+ /*
+ * We only really use 4 bytes of the outbuf, but for the smb_setlen
+ * calculation & friends (smb1_srv_send uses that) we need the full smb
+ * header.
+ */
+ char outbuf[smb_size];
+
+ memset(outbuf, '\0', sizeof(outbuf));
+
+ smb_setlen(outbuf,0);
+
+ switch (msg_type) {
+ case NBSSrequest: /* session request */
+ {
+ /* inbuf_size is guaranteed to be at least 4. */
+ fstring name1,name2;
+ int name_type1, name_type2;
+ int name_len1, name_len2;
+
+ *name1 = *name2 = 0;
+
+ if (xconn->transport.nbt.got_session) {
+ exit_server_cleanly("multiple session request not permitted");
+ }
+
+ SCVAL(outbuf,0,NBSSpositive);
+ SCVAL(outbuf,3,0);
+
+ /* inbuf_size is guaranteed to be at least 4. */
+ name_len1 = name_len((unsigned char *)(inbuf+4),inbuf_size - 4);
+ if (name_len1 <= 0 || name_len1 > inbuf_size - 4) {
+ DEBUG(0,("Invalid name length in session request\n"));
+ reply_called_name_not_present(outbuf);
+ break;
+ }
+ name_len2 = name_len((unsigned char *)(inbuf+4+name_len1),inbuf_size - 4 - name_len1);
+ if (name_len2 <= 0 || name_len2 > inbuf_size - 4 - name_len1) {
+ DEBUG(0,("Invalid name length in session request\n"));
+ reply_called_name_not_present(outbuf);
+ break;
+ }
+
+ name_type1 = name_extract((unsigned char *)inbuf,
+ inbuf_size,(unsigned int)4,name1);
+ name_type2 = name_extract((unsigned char *)inbuf,
+ inbuf_size,(unsigned int)(4 + name_len1),name2);
+
+ if (name_type1 == -1 || name_type2 == -1) {
+ DEBUG(0,("Invalid name type in session request\n"));
+ reply_called_name_not_present(outbuf);
+ break;
+ }
+
+ DEBUG(2,("netbios connect: name1=%s0x%x name2=%s0x%x\n",
+ name1, name_type1, name2, name_type2));
+
+ if (netbios_session_retarget(xconn, name1, name_type1)) {
+ exit_server_cleanly("retargeted client");
+ }
+
+ /*
+ * Windows NT/2k uses "*SMBSERVER" and XP uses
+ * "*SMBSERV" arrggg!!!
+ */
+ if (strequal(name1, "*SMBSERVER ")
+ || strequal(name1, "*SMBSERV ")) {
+ char *raddr;
+
+ raddr = tsocket_address_inet_addr_string(sconn->remote_address,
+ talloc_tos());
+ if (raddr == NULL) {
+ exit_server_cleanly("could not allocate raddr");
+ }
+
+ fstrcpy(name1, raddr);
+ }
+
+ set_local_machine_name(name1, True);
+ set_remote_machine_name(name2, True);
+
+ if (is_ipaddress(sconn->remote_hostname)) {
+ char *p = discard_const_p(char, sconn->remote_hostname);
+
+ talloc_free(p);
+
+ sconn->remote_hostname = talloc_strdup(sconn,
+ get_remote_machine_name());
+ if (sconn->remote_hostname == NULL) {
+ exit_server_cleanly("could not copy remote name");
+ }
+ xconn->remote_hostname = sconn->remote_hostname;
+ }
+
+ DEBUG(2,("netbios connect: local=%s remote=%s, name type = %x\n",
+ get_local_machine_name(), get_remote_machine_name(),
+ name_type2));
+
+ if (name_type2 == 'R') {
+ /* We are being asked for a pathworks session ---
+ no thanks! */
+ reply_called_name_not_present(outbuf);
+ break;
+ }
+
+ reload_services(sconn, conn_snum_used, true);
+ reopen_logs();
+
+ xconn->transport.nbt.got_session = true;
+ break;
+ }
+
+ case 0x89: /* session keepalive request
+ (some old clients produce this?) */
+ SCVAL(outbuf,0,NBSSkeepalive);
+ SCVAL(outbuf,3,0);
+ break;
+
+ case NBSSpositive: /* positive session response */
+ case NBSSnegative: /* negative session response */
+ case NBSSretarget: /* retarget session response */
+ DEBUG(0,("Unexpected session response\n"));
+ break;
+
+ case NBSSkeepalive: /* session keepalive */
+ default:
+ return;
+ }
+
+ DEBUG(5,("init msg_type=0x%x msg_flags=0x%x\n",
+ msg_type, msg_flags));
+
+ if (!smb1_srv_send(xconn, outbuf, false, 0, false)) {
+ exit_server_cleanly("reply_special: smb1_srv_send failed.");
+ }
+
+ if (CVAL(outbuf, 0) != 0x82) {
+ exit_server_cleanly("invalid netbios session");
+ }
+ return;
+}
+
+/*******************************************************************
+ * unlink a file with all relevant access checks
+ *******************************************************************/
+
+NTSTATUS unlink_internals(connection_struct *conn,
+ struct smb_request *req,
+ uint32_t dirtype,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname)
+{
+ uint32_t fattr;
+ files_struct *fsp;
+ uint32_t dirtype_orig = dirtype;
+ NTSTATUS status;
+ int ret;
+ struct smb2_create_blobs *posx = NULL;
+
+ if (dirtype == 0) {
+ dirtype = FILE_ATTRIBUTE_NORMAL;
+ }
+
+ DBG_DEBUG("%s, dirtype = %d\n",
+ smb_fname_str_dbg(smb_fname),
+ dirtype);
+
+ if (!CAN_WRITE(conn)) {
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+
+ ret = vfs_stat(conn, smb_fname);
+ if (ret != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ fattr = fdos_mode(smb_fname->fsp);
+
+ if (dirtype & FILE_ATTRIBUTE_NORMAL) {
+ dirtype = FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY;
+ }
+
+ dirtype &= (FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM);
+ if (!dirtype) {
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+
+ if (!dir_check_ftype(fattr, dirtype)) {
+ if (fattr & FILE_ATTRIBUTE_DIRECTORY) {
+ return NT_STATUS_FILE_IS_A_DIRECTORY;
+ }
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+
+ if (dirtype_orig & 0x8000) {
+ /* These will never be set for POSIX. */
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+
+#if 0
+ if ((fattr & dirtype) & FILE_ATTRIBUTE_DIRECTORY) {
+ return NT_STATUS_FILE_IS_A_DIRECTORY;
+ }
+
+ if ((fattr & ~dirtype) & (FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM)) {
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+
+ if (dirtype & 0xFF00) {
+ /* These will never be set for POSIX. */
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+
+ dirtype &= 0xFF;
+ if (!dirtype) {
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+
+ /* Can't delete a directory. */
+ if (fattr & FILE_ATTRIBUTE_DIRECTORY) {
+ return NT_STATUS_FILE_IS_A_DIRECTORY;
+ }
+#endif
+
+#if 0 /* JRATEST */
+ else if (dirtype & FILE_ATTRIBUTE_DIRECTORY) /* Asked for a directory and it isn't. */
+ return NT_STATUS_OBJECT_NAME_INVALID;
+#endif /* JRATEST */
+
+ if (smb_fname->flags & SMB_FILENAME_POSIX_PATH) {
+ status = make_smb2_posix_create_ctx(
+ talloc_tos(), &posx, 0777);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("make_smb2_posix_create_ctx failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+ }
+
+ /* On open checks the open itself will check the share mode, so
+ don't do it here as we'll get it wrong. */
+
+ status = SMB_VFS_CREATE_FILE
+ (conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_fname, /* fname */
+ DELETE_ACCESS, /* access_mask */
+ FILE_SHARE_NONE, /* share_access */
+ FILE_OPEN, /* create_disposition*/
+ FILE_NON_DIRECTORY_FILE |
+ FILE_OPEN_REPARSE_POINT, /* create_options */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes */
+ 0, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* pinfo */
+ posx, /* in_context_blobs */
+ NULL); /* out_context_blobs */
+
+ TALLOC_FREE(posx);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("SMB_VFS_CREATEFILE failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ status = can_set_delete_on_close(fsp, fattr);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("can_set_delete_on_close for file %s - "
+ "(%s)\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status));
+ close_file_free(req, &fsp, NORMAL_CLOSE);
+ return status;
+ }
+
+ /* The set is across all open files on this dev/inode pair. */
+ if (!set_delete_on_close(fsp, True,
+ conn->session_info->security_token,
+ conn->session_info->unix_token)) {
+ close_file_free(req, &fsp, NORMAL_CLOSE);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ return close_file_free(req, &fsp, NORMAL_CLOSE);
+}
+
+/****************************************************************************
+ Fake (read/write) sendfile. Returns -1 on read or write fail.
+****************************************************************************/
+
+ssize_t fake_sendfile(struct smbXsrv_connection *xconn, files_struct *fsp,
+ off_t startpos, size_t nread)
+{
+ size_t bufsize;
+ size_t tosend = nread;
+ char *buf;
+
+ if (nread == 0) {
+ return 0;
+ }
+
+ bufsize = MIN(nread, 65536);
+
+ if (!(buf = SMB_MALLOC_ARRAY(char, bufsize))) {
+ return -1;
+ }
+
+ while (tosend > 0) {
+ ssize_t ret;
+ size_t cur_read;
+
+ cur_read = MIN(tosend, bufsize);
+ ret = read_file(fsp,buf,startpos,cur_read);
+ if (ret == -1) {
+ SAFE_FREE(buf);
+ return -1;
+ }
+
+ /* If we had a short read, fill with zeros. */
+ if (ret < cur_read) {
+ memset(buf + ret, '\0', cur_read - ret);
+ }
+
+ ret = write_data(xconn->transport.sock, buf, cur_read);
+ if (ret != cur_read) {
+ int saved_errno = errno;
+ /*
+ * Try and give an error message saying what
+ * client failed.
+ */
+ DEBUG(0, ("write_data failed for client %s. "
+ "Error %s\n",
+ smbXsrv_connection_dbg(xconn),
+ strerror(saved_errno)));
+ SAFE_FREE(buf);
+ errno = saved_errno;
+ return -1;
+ }
+ tosend -= cur_read;
+ startpos += cur_read;
+ }
+
+ SAFE_FREE(buf);
+ return (ssize_t)nread;
+}
+
+/****************************************************************************
+ Deal with the case of sendfile reading less bytes from the file than
+ requested. Fill with zeros (all we can do). Returns 0 on success
+****************************************************************************/
+
+ssize_t sendfile_short_send(struct smbXsrv_connection *xconn,
+ files_struct *fsp,
+ ssize_t nread,
+ size_t headersize,
+ size_t smb_maxcnt)
+{
+#define SHORT_SEND_BUFSIZE 1024
+ if (nread < headersize) {
+ DEBUG(0,("sendfile_short_send: sendfile failed to send "
+ "header for file %s (%s). Terminating\n",
+ fsp_str_dbg(fsp), strerror(errno)));
+ return -1;
+ }
+
+ nread -= headersize;
+
+ if (nread < smb_maxcnt) {
+ char buf[SHORT_SEND_BUFSIZE] = { 0 };
+
+ DEBUG(0,("sendfile_short_send: filling truncated file %s "
+ "with zeros !\n", fsp_str_dbg(fsp)));
+
+ while (nread < smb_maxcnt) {
+ /*
+ * We asked for the real file size and told sendfile
+ * to not go beyond the end of the file. But it can
+ * happen that in between our fstat call and the
+ * sendfile call the file was truncated. This is very
+ * bad because we have already announced the larger
+ * number of bytes to the client.
+ *
+ * The best we can do now is to send 0-bytes, just as
+ * a read from a hole in a sparse file would do.
+ *
+ * This should happen rarely enough that I don't care
+ * about efficiency here :-)
+ */
+ size_t to_write;
+ ssize_t ret;
+
+ to_write = MIN(SHORT_SEND_BUFSIZE, smb_maxcnt - nread);
+ ret = write_data(xconn->transport.sock, buf, to_write);
+ if (ret != to_write) {
+ int saved_errno = errno;
+ /*
+ * Try and give an error message saying what
+ * client failed.
+ */
+ DEBUG(0, ("write_data failed for client %s. "
+ "Error %s\n",
+ smbXsrv_connection_dbg(xconn),
+ strerror(saved_errno)));
+ errno = saved_errno;
+ return -1;
+ }
+ nread += to_write;
+ }
+ }
+
+ return 0;
+}
+
+/*******************************************************************
+ Check if a user is allowed to rename a file.
+********************************************************************/
+
+static NTSTATUS can_rename(connection_struct *conn, files_struct *fsp,
+ uint16_t dirtype)
+{
+ NTSTATUS status;
+
+ if (fsp->fsp_name->twrp != 0) {
+ /* Get the error right, this is what Windows returns. */
+ return NT_STATUS_NOT_SAME_DEVICE;
+ }
+
+ if (!CAN_WRITE(conn)) {
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+
+ if ((dirtype & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) !=
+ (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) {
+ /* Only bother to read the DOS attribute if we might deny the
+ rename on the grounds of attribute mismatch. */
+ uint32_t fmode = fdos_mode(fsp);
+ if ((fmode & ~dirtype) & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) {
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+ }
+
+ if (S_ISDIR(fsp->fsp_name->st.st_ex_mode)) {
+ if (fsp->posix_flags & FSP_POSIX_FLAGS_RENAME) {
+ return NT_STATUS_OK;
+ }
+
+ /* If no pathnames are open below this
+ directory, allow the rename. */
+
+ if (lp_strict_rename(SNUM(conn))) {
+ /*
+ * Strict rename, check open file db.
+ */
+ if (have_file_open_below(fsp->conn, fsp->fsp_name)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ } else if (file_find_subpath(fsp)) {
+ /*
+ * No strict rename, just look in local process.
+ */
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ return NT_STATUS_OK;
+ }
+
+ status = check_any_access_fsp(fsp, DELETE_ACCESS | FILE_WRITE_ATTRIBUTES);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Ensure open files have their names updated. Updated to notify other smbd's
+ asynchronously.
+****************************************************************************/
+
+static void rename_open_files(connection_struct *conn,
+ struct share_mode_lock *lck,
+ struct file_id id,
+ uint32_t orig_name_hash,
+ const struct smb_filename *smb_fname_dst)
+{
+ files_struct *fsp;
+ bool did_rename = False;
+ NTSTATUS status;
+ uint32_t new_name_hash = 0;
+
+ for(fsp = file_find_di_first(conn->sconn, id, false); fsp;
+ fsp = file_find_di_next(fsp, false)) {
+ SMB_STRUCT_STAT fsp_orig_sbuf;
+ struct file_id_buf idbuf;
+ /* fsp_name is a relative path under the fsp. To change this for other
+ sharepaths we need to manipulate relative paths. */
+ /* TODO - create the absolute path and manipulate the newname
+ relative to the sharepath. */
+ if (!strequal(fsp->conn->connectpath, conn->connectpath)) {
+ continue;
+ }
+ if (fsp->name_hash != orig_name_hash) {
+ continue;
+ }
+ DBG_DEBUG("renaming file %s "
+ "(file_id %s) from %s -> %s\n",
+ fsp_fnum_dbg(fsp),
+ file_id_str_buf(fsp->file_id, &idbuf),
+ fsp_str_dbg(fsp),
+ smb_fname_str_dbg(smb_fname_dst));
+
+ /*
+ * The incoming smb_fname_dst here has an
+ * invalid stat struct (it must not have
+ * existed for the rename to succeed).
+ * Preserve the existing stat from the
+ * open fsp after fsp_set_smb_fname()
+ * overwrites with the invalid stat.
+ *
+ * We will do an fstat before returning
+ * any of this metadata to the client anyway.
+ */
+ fsp_orig_sbuf = fsp->fsp_name->st;
+ status = fsp_set_smb_fname(fsp, smb_fname_dst);
+ if (NT_STATUS_IS_OK(status)) {
+ did_rename = True;
+ new_name_hash = fsp->name_hash;
+ /* Restore existing stat. */
+ fsp->fsp_name->st = fsp_orig_sbuf;
+ }
+ }
+
+ if (!did_rename) {
+ struct file_id_buf idbuf;
+ DBG_DEBUG("no open files on file_id %s "
+ "for %s\n",
+ file_id_str_buf(id, &idbuf),
+ smb_fname_str_dbg(smb_fname_dst));
+ }
+
+ /* Send messages to all smbd's (not ourself) that the name has changed. */
+ rename_share_filename(conn->sconn->msg_ctx, lck, id, conn->connectpath,
+ orig_name_hash, new_name_hash,
+ smb_fname_dst);
+
+}
+
+/****************************************************************************
+ We need to check if the source path is a parent directory of the destination
+ (ie. a rename of /foo/bar/baz -> /foo/bar/baz/bibble/bobble. If so we must
+ refuse the rename with a sharing violation. Under UNIX the above call can
+ *succeed* if /foo/bar/baz is a symlink to another area in the share. We
+ probably need to check that the client is a Windows one before disallowing
+ this as a UNIX client (one with UNIX extensions) can know the source is a
+ symlink and make this decision intelligently. Found by an excellent bug
+ report from <AndyLiebman@aol.com>.
+****************************************************************************/
+
+static bool rename_path_prefix_equal(const struct smb_filename *smb_fname_src,
+ const struct smb_filename *smb_fname_dst)
+{
+ const char *psrc = smb_fname_src->base_name;
+ const char *pdst = smb_fname_dst->base_name;
+ size_t slen;
+
+ if (psrc[0] == '.' && psrc[1] == '/') {
+ psrc += 2;
+ }
+ if (pdst[0] == '.' && pdst[1] == '/') {
+ pdst += 2;
+ }
+ if ((slen = strlen(psrc)) > strlen(pdst)) {
+ return False;
+ }
+ return ((memcmp(psrc, pdst, slen) == 0) && pdst[slen] == '/');
+}
+
+/*
+ * Do the notify calls from a rename
+ */
+
+static void notify_rename(connection_struct *conn, bool is_dir,
+ const struct smb_filename *smb_fname_src,
+ const struct smb_filename *smb_fname_dst)
+{
+ char *parent_dir_src = NULL;
+ char *parent_dir_dst = NULL;
+ uint32_t mask;
+
+ mask = is_dir ? FILE_NOTIFY_CHANGE_DIR_NAME
+ : FILE_NOTIFY_CHANGE_FILE_NAME;
+
+ if (!parent_dirname(talloc_tos(), smb_fname_src->base_name,
+ &parent_dir_src, NULL) ||
+ !parent_dirname(talloc_tos(), smb_fname_dst->base_name,
+ &parent_dir_dst, NULL)) {
+ goto out;
+ }
+
+ if (strcmp(parent_dir_src, parent_dir_dst) == 0) {
+ notify_fname(conn, NOTIFY_ACTION_OLD_NAME, mask,
+ smb_fname_src->base_name);
+ notify_fname(conn, NOTIFY_ACTION_NEW_NAME, mask,
+ smb_fname_dst->base_name);
+ }
+ else {
+ notify_fname(conn, NOTIFY_ACTION_REMOVED, mask,
+ smb_fname_src->base_name);
+ notify_fname(conn, NOTIFY_ACTION_ADDED, mask,
+ smb_fname_dst->base_name);
+ }
+
+ /* this is a strange one. w2k3 gives an additional event for
+ CHANGE_ATTRIBUTES and CHANGE_CREATION on the new file when renaming
+ files, but not directories */
+ if (!is_dir) {
+ notify_fname(conn, NOTIFY_ACTION_MODIFIED,
+ FILE_NOTIFY_CHANGE_ATTRIBUTES
+ |FILE_NOTIFY_CHANGE_CREATION,
+ smb_fname_dst->base_name);
+ }
+ out:
+ TALLOC_FREE(parent_dir_src);
+ TALLOC_FREE(parent_dir_dst);
+}
+
+/****************************************************************************
+ Returns an error if the parent directory for a filename is open in an
+ incompatible way.
+****************************************************************************/
+
+static NTSTATUS parent_dirname_compatible_open(connection_struct *conn,
+ const struct smb_filename *smb_fname_dst_in)
+{
+ struct smb_filename *smb_fname_parent = NULL;
+ struct file_id id;
+ files_struct *fsp = NULL;
+ int ret;
+ NTSTATUS status;
+
+ status = SMB_VFS_PARENT_PATHNAME(conn,
+ talloc_tos(),
+ smb_fname_dst_in,
+ &smb_fname_parent,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ ret = vfs_stat(conn, smb_fname_parent);
+ if (ret == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ /*
+ * We're only checking on this smbd here, mostly good
+ * enough.. and will pass tests.
+ */
+
+ id = vfs_file_id_from_sbuf(conn, &smb_fname_parent->st);
+ for (fsp = file_find_di_first(conn->sconn, id, true); fsp;
+ fsp = file_find_di_next(fsp, true)) {
+ if (fsp->access_mask & DELETE_ACCESS) {
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Rename an open file - given an fsp.
+****************************************************************************/
+
+NTSTATUS rename_internals_fsp(connection_struct *conn,
+ files_struct *fsp,
+ struct smb_filename *smb_fname_dst_in,
+ const char *dst_original_lcomp,
+ uint32_t attrs,
+ bool replace_if_exists)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ struct smb_filename *parent_dir_fname_dst = NULL;
+ struct smb_filename *parent_dir_fname_dst_atname = NULL;
+ struct smb_filename *parent_dir_fname_src = NULL;
+ struct smb_filename *parent_dir_fname_src_atname = NULL;
+ struct smb_filename *smb_fname_dst = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+ struct share_mode_lock *lck = NULL;
+ uint32_t access_mask = SEC_DIR_ADD_FILE;
+ bool dst_exists, old_is_stream, new_is_stream;
+ int ret;
+ bool case_sensitive = (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) ?
+ true : conn->case_sensitive;
+ bool case_preserve = (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) ?
+ true : conn->case_preserve;
+
+ status = parent_dirname_compatible_open(conn, smb_fname_dst_in);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (file_has_open_streams(fsp)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /* Make a copy of the dst smb_fname structs */
+
+ smb_fname_dst = cp_smb_filename(ctx, smb_fname_dst_in);
+ if (smb_fname_dst == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ /*
+ * Check for special case with case preserving and not
+ * case sensitive. If the new last component differs from the original
+ * last component only by case, then we should allow
+ * the rename (user is trying to change the case of the
+ * filename).
+ */
+ if (!case_sensitive && case_preserve &&
+ strequal(fsp->fsp_name->base_name, smb_fname_dst->base_name) &&
+ strequal(fsp->fsp_name->stream_name, smb_fname_dst->stream_name)) {
+ char *fname_dst_parent = NULL;
+ const char *fname_dst_lcomp = NULL;
+ char *orig_lcomp_path = NULL;
+ char *orig_lcomp_stream = NULL;
+ bool ok = true;
+
+ /*
+ * Split off the last component of the processed
+ * destination name. We will compare this to
+ * the split components of dst_original_lcomp.
+ */
+ if (!parent_dirname(ctx,
+ smb_fname_dst->base_name,
+ &fname_dst_parent,
+ &fname_dst_lcomp)) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ /*
+ * The dst_original_lcomp component contains
+ * the last_component of the path + stream
+ * name (if a stream exists).
+ *
+ * Split off the stream name so we
+ * can check them separately.
+ */
+
+ if (fsp->posix_flags & FSP_POSIX_FLAGS_PATHNAMES) {
+ /* POSIX - no stream component. */
+ orig_lcomp_path = talloc_strdup(ctx,
+ dst_original_lcomp);
+ if (orig_lcomp_path == NULL) {
+ ok = false;
+ }
+ } else {
+ ok = split_stream_filename(ctx,
+ dst_original_lcomp,
+ &orig_lcomp_path,
+ &orig_lcomp_stream);
+ }
+
+ if (!ok) {
+ TALLOC_FREE(fname_dst_parent);
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ /* If the base names only differ by case, use original. */
+ if(!strcsequal(fname_dst_lcomp, orig_lcomp_path)) {
+ char *tmp;
+ /*
+ * Replace the modified last component with the
+ * original.
+ */
+ if (!ISDOT(fname_dst_parent)) {
+ tmp = talloc_asprintf(smb_fname_dst,
+ "%s/%s",
+ fname_dst_parent,
+ orig_lcomp_path);
+ } else {
+ tmp = talloc_strdup(smb_fname_dst,
+ orig_lcomp_path);
+ }
+ if (tmp == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ TALLOC_FREE(fname_dst_parent);
+ TALLOC_FREE(orig_lcomp_path);
+ TALLOC_FREE(orig_lcomp_stream);
+ goto out;
+ }
+ TALLOC_FREE(smb_fname_dst->base_name);
+ smb_fname_dst->base_name = tmp;
+ }
+
+ /* If the stream_names only differ by case, use original. */
+ if(!strcsequal(smb_fname_dst->stream_name,
+ orig_lcomp_stream)) {
+ /* Use the original stream. */
+ char *tmp = talloc_strdup(smb_fname_dst,
+ orig_lcomp_stream);
+ if (tmp == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ TALLOC_FREE(fname_dst_parent);
+ TALLOC_FREE(orig_lcomp_path);
+ TALLOC_FREE(orig_lcomp_stream);
+ goto out;
+ }
+ TALLOC_FREE(smb_fname_dst->stream_name);
+ smb_fname_dst->stream_name = tmp;
+ }
+ TALLOC_FREE(fname_dst_parent);
+ TALLOC_FREE(orig_lcomp_path);
+ TALLOC_FREE(orig_lcomp_stream);
+ }
+
+ /*
+ * If the src and dest names are identical - including case,
+ * don't do the rename, just return success.
+ */
+
+ if (strcsequal(fsp->fsp_name->base_name, smb_fname_dst->base_name) &&
+ strcsequal(fsp->fsp_name->stream_name,
+ smb_fname_dst->stream_name)) {
+ DEBUG(3, ("rename_internals_fsp: identical names in rename %s "
+ "- returning success\n",
+ smb_fname_str_dbg(smb_fname_dst)));
+ status = NT_STATUS_OK;
+ goto out;
+ }
+
+ old_is_stream = is_ntfs_stream_smb_fname(fsp->fsp_name);
+ new_is_stream = is_ntfs_stream_smb_fname(smb_fname_dst);
+
+ /* Return the correct error code if both names aren't streams. */
+ if (!old_is_stream && new_is_stream) {
+ status = NT_STATUS_OBJECT_NAME_INVALID;
+ goto out;
+ }
+
+ if (old_is_stream && !new_is_stream) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ dst_exists = vfs_stat(conn, smb_fname_dst) == 0;
+
+ if(!replace_if_exists && dst_exists) {
+ DEBUG(3, ("rename_internals_fsp: dest exists doing rename "
+ "%s -> %s\n", smb_fname_str_dbg(fsp->fsp_name),
+ smb_fname_str_dbg(smb_fname_dst)));
+ status = NT_STATUS_OBJECT_NAME_COLLISION;
+ goto out;
+ }
+
+ /*
+ * Drop the pathref fsp on the destination otherwise we trip upon in in
+ * the below check for open files check.
+ */
+ if (smb_fname_dst_in->fsp != NULL) {
+ fd_close(smb_fname_dst_in->fsp);
+ file_free(NULL, smb_fname_dst_in->fsp);
+ SMB_ASSERT(smb_fname_dst_in->fsp == NULL);
+ }
+
+ if (dst_exists) {
+ struct file_id fileid = vfs_file_id_from_sbuf(conn,
+ &smb_fname_dst->st);
+ files_struct *dst_fsp = file_find_di_first(conn->sconn,
+ fileid, true);
+ /* The file can be open when renaming a stream */
+ if (dst_fsp && !new_is_stream) {
+ DEBUG(3, ("rename_internals_fsp: Target file open\n"));
+ status = NT_STATUS_ACCESS_DENIED;
+ goto out;
+ }
+ }
+
+ /* Ensure we have a valid stat struct for the source. */
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = can_rename(conn, fsp, attrs);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("rename_internals_fsp: Error %s rename %s -> %s\n",
+ nt_errstr(status), smb_fname_str_dbg(fsp->fsp_name),
+ smb_fname_str_dbg(smb_fname_dst)));
+ if (NT_STATUS_EQUAL(status,NT_STATUS_SHARING_VIOLATION))
+ status = NT_STATUS_ACCESS_DENIED;
+ goto out;
+ }
+
+ if (rename_path_prefix_equal(fsp->fsp_name, smb_fname_dst)) {
+ status = NT_STATUS_ACCESS_DENIED;
+ goto out;
+ }
+
+ /* Do we have rights to move into the destination ? */
+ if (S_ISDIR(fsp->fsp_name->st.st_ex_mode)) {
+ /* We're moving a directory. */
+ access_mask = SEC_DIR_ADD_SUBDIR;
+ }
+
+ /*
+ * Get a pathref on the destination parent directory, so
+ * we can call check_parent_access_fsp().
+ */
+ status = parent_pathref(ctx,
+ conn->cwd_fsp,
+ smb_fname_dst,
+ &parent_dir_fname_dst,
+ &parent_dir_fname_dst_atname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = check_parent_access_fsp(parent_dir_fname_dst->fsp,
+ access_mask);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_INFO("check_parent_access_fsp on "
+ "dst %s returned %s\n",
+ smb_fname_str_dbg(smb_fname_dst),
+ nt_errstr(status));
+ goto out;
+ }
+
+ /*
+ * If the target existed, make sure the destination
+ * atname has the same stat struct.
+ */
+ parent_dir_fname_dst_atname->st = smb_fname_dst->st;
+
+ /*
+ * It's very common that source and
+ * destination directories are the same.
+ * Optimize by not opening the
+ * second parent_pathref if we know
+ * this is the case.
+ */
+
+ status = SMB_VFS_PARENT_PATHNAME(conn,
+ ctx,
+ fsp->fsp_name,
+ &parent_dir_fname_src,
+ &parent_dir_fname_src_atname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ /*
+ * We do a case-sensitive string comparison. We want to be *sure*
+ * this is the same path. The worst that can happen if
+ * the case doesn't match is we lose out on the optimization,
+ * the code still works.
+ *
+ * We can ignore twrp fields here. Rename is not allowed on
+ * shadow copy handles.
+ */
+
+ if (strcmp(parent_dir_fname_src->base_name,
+ parent_dir_fname_dst->base_name) == 0) {
+ /*
+ * parent directory is the same for source
+ * and destination.
+ */
+ /* Reparent the src_atname to the parent_dir_dest fname. */
+ parent_dir_fname_src_atname = talloc_move(
+ parent_dir_fname_dst,
+ &parent_dir_fname_src_atname);
+ /* Free the unneeded duplicate parent name. */
+ TALLOC_FREE(parent_dir_fname_src);
+ /*
+ * And make the source parent name a copy of the
+ * destination parent name.
+ */
+ parent_dir_fname_src = parent_dir_fname_dst;
+
+ /*
+ * Ensure we have a pathref fsp on the
+ * parent_dir_fname_src_atname to match the code in the else
+ * branch where we use parent_pathref().
+ */
+ status = reference_smb_fname_fsp_link(
+ parent_dir_fname_src_atname,
+ fsp->fsp_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ } else {
+ /*
+ * source and destination parent directories are
+ * different.
+ *
+ * Get a pathref on the source parent directory, so
+ * we can do a relative rename.
+ */
+ TALLOC_FREE(parent_dir_fname_src);
+ status = parent_pathref(ctx,
+ conn->cwd_fsp,
+ fsp->fsp_name,
+ &parent_dir_fname_src,
+ &parent_dir_fname_src_atname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ }
+
+ /*
+ * Some modules depend on the source smb_fname having a valid stat.
+ * The parent_dir_fname_src_atname is the relative name of the
+ * currently open file, so just copy the stat from the open fsp.
+ */
+ parent_dir_fname_src_atname->st = fsp->fsp_name->st;
+
+ lck = get_existing_share_mode_lock(talloc_tos(), fsp->file_id);
+
+ /*
+ * We have the file open ourselves, so not being able to get the
+ * corresponding share mode lock is a fatal error.
+ */
+
+ SMB_ASSERT(lck != NULL);
+
+ ret = SMB_VFS_RENAMEAT(conn,
+ parent_dir_fname_src->fsp,
+ parent_dir_fname_src_atname,
+ parent_dir_fname_dst->fsp,
+ parent_dir_fname_dst_atname);
+ if (ret == 0) {
+ uint32_t create_options = fh_get_private_options(fsp->fh);
+
+ DEBUG(3, ("rename_internals_fsp: succeeded doing rename on "
+ "%s -> %s\n", smb_fname_str_dbg(fsp->fsp_name),
+ smb_fname_str_dbg(smb_fname_dst)));
+
+ notify_rename(conn,
+ fsp->fsp_flags.is_directory,
+ fsp->fsp_name,
+ smb_fname_dst);
+
+ rename_open_files(conn, lck, fsp->file_id, fsp->name_hash,
+ smb_fname_dst);
+
+ if (!fsp->fsp_flags.is_directory &&
+ (lp_map_archive(SNUM(conn)) ||
+ lp_store_dos_attributes(SNUM(conn))))
+ {
+ /*
+ * We must set the archive bit on the newly renamed
+ * file.
+ */
+ status = vfs_stat_fsp(fsp);
+ if (NT_STATUS_IS_OK(status)) {
+ uint32_t old_dosmode;
+ old_dosmode = fdos_mode(fsp);
+ /*
+ * We can use fsp->fsp_name here as it has
+ * already been changed to the new name.
+ */
+ SMB_ASSERT(fsp->fsp_name->fsp == fsp);
+ file_set_dosmode(conn,
+ fsp->fsp_name,
+ old_dosmode | FILE_ATTRIBUTE_ARCHIVE,
+ NULL,
+ true);
+ }
+ }
+
+ /*
+ * A rename acts as a new file create w.r.t. allowing an initial delete
+ * on close, probably because in Windows there is a new handle to the
+ * new file. If initial delete on close was requested but not
+ * originally set, we need to set it here. This is probably not 100% correct,
+ * but will work for the CIFSFS client which in non-posix mode
+ * depends on these semantics. JRA.
+ */
+
+ if (create_options & FILE_DELETE_ON_CLOSE) {
+ status = can_set_delete_on_close(fsp, 0);
+
+ if (NT_STATUS_IS_OK(status)) {
+ /* Note that here we set the *initial* delete on close flag,
+ * not the regular one. The magic gets handled in close. */
+ fsp->fsp_flags.initial_delete_on_close = true;
+ }
+ }
+ TALLOC_FREE(lck);
+ status = NT_STATUS_OK;
+ goto out;
+ }
+
+ TALLOC_FREE(lck);
+
+ if (errno == ENOTDIR || errno == EISDIR) {
+ status = NT_STATUS_OBJECT_NAME_COLLISION;
+ } else {
+ status = map_nt_error_from_unix(errno);
+ }
+
+ DEBUG(3, ("rename_internals_fsp: Error %s rename %s -> %s\n",
+ nt_errstr(status), smb_fname_str_dbg(fsp->fsp_name),
+ smb_fname_str_dbg(smb_fname_dst)));
+
+ out:
+
+ /*
+ * parent_dir_fname_src may be a copy of parent_dir_fname_dst.
+ * See the optimization for same source and destination directory
+ * above. Only free one in that case.
+ */
+ if (parent_dir_fname_src != parent_dir_fname_dst) {
+ TALLOC_FREE(parent_dir_fname_src);
+ }
+ TALLOC_FREE(parent_dir_fname_dst);
+ TALLOC_FREE(smb_fname_dst);
+
+ return status;
+}
+
+/****************************************************************************
+ The guts of the rename command, split out so it may be called by the NT SMB
+ code.
+****************************************************************************/
+
+NTSTATUS rename_internals(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct smb_request *req,
+ struct files_struct *src_dirfsp,
+ struct smb_filename *smb_fname_src,
+ struct smb_filename *smb_fname_dst,
+ const char *dst_original_lcomp,
+ uint32_t attrs,
+ bool replace_if_exists,
+ uint32_t access_mask)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ int create_options = FILE_OPEN_REPARSE_POINT;
+ struct smb2_create_blobs *posx = NULL;
+ struct files_struct *fsp = NULL;
+ bool posix_pathname = (smb_fname_src->flags & SMB_FILENAME_POSIX_PATH);
+ bool case_sensitive = posix_pathname ? true : conn->case_sensitive;
+ bool case_preserve = posix_pathname ? true : conn->case_preserve;
+ bool short_case_preserve = posix_pathname ? true :
+ conn->short_case_preserve;
+
+ if (posix_pathname) {
+ status = make_smb2_posix_create_ctx(talloc_tos(), &posx, 0777);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("make_smb2_posix_create_ctx failed: %s\n",
+ nt_errstr(status));
+ goto out;
+ }
+ }
+
+ DBG_NOTICE("case_sensitive = %d, "
+ "case_preserve = %d, short case preserve = %d, "
+ "directory = %s, newname = %s, "
+ "last_component_dest = %s\n",
+ case_sensitive, case_preserve,
+ short_case_preserve,
+ smb_fname_str_dbg(smb_fname_src),
+ smb_fname_str_dbg(smb_fname_dst),
+ dst_original_lcomp);
+
+ ZERO_STRUCT(smb_fname_src->st);
+
+ status = openat_pathref_fsp(conn->cwd_fsp, smb_fname_src);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (!NT_STATUS_EQUAL(status,
+ NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ goto out;
+ }
+ /*
+ * Possible symlink src.
+ */
+ if (!(smb_fname_src->flags & SMB_FILENAME_POSIX_PATH)) {
+ goto out;
+ }
+ if (!S_ISLNK(smb_fname_src->st.st_ex_mode)) {
+ goto out;
+ }
+ }
+
+ if (S_ISDIR(smb_fname_src->st.st_ex_mode)) {
+ create_options |= FILE_DIRECTORY_FILE;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ src_dirfsp, /* dirfsp */
+ smb_fname_src, /* fname */
+ access_mask, /* access_mask */
+ (FILE_SHARE_READ | /* share_access */
+ FILE_SHARE_WRITE),
+ FILE_OPEN, /* create_disposition*/
+ create_options, /* create_options */
+ 0, /* file_attributes */
+ 0, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* pinfo */
+ posx, /* in_context_blobs */
+ NULL); /* out_context_blobs */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_NOTICE("Could not open rename source %s: %s\n",
+ smb_fname_str_dbg(smb_fname_src),
+ nt_errstr(status));
+ goto out;
+ }
+
+ status = rename_internals_fsp(conn,
+ fsp,
+ smb_fname_dst,
+ dst_original_lcomp,
+ attrs,
+ replace_if_exists);
+
+ close_file_free(req, &fsp, NORMAL_CLOSE);
+
+ DBG_NOTICE("Error %s rename %s -> %s\n",
+ nt_errstr(status), smb_fname_str_dbg(smb_fname_src),
+ smb_fname_str_dbg(smb_fname_dst));
+
+ out:
+ TALLOC_FREE(posx);
+ return status;
+}
+
+/*******************************************************************
+ Copy a file as part of a reply_copy.
+******************************************************************/
+
+/*
+ * TODO: check error codes on all callers
+ */
+
+NTSTATUS copy_file(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct smb_filename *smb_fname_src,
+ struct smb_filename *smb_fname_dst,
+ uint32_t new_create_disposition)
+{
+ struct smb_filename *smb_fname_dst_tmp = NULL;
+ off_t ret=-1;
+ files_struct *fsp1,*fsp2;
+ uint32_t dosattrs;
+ NTSTATUS status;
+
+
+ smb_fname_dst_tmp = cp_smb_filename(ctx, smb_fname_dst);
+ if (smb_fname_dst_tmp == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = vfs_file_exist(conn, smb_fname_src);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = openat_pathref_fsp(conn->cwd_fsp, smb_fname_src);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ /* Open the src file for reading. */
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ NULL, /* req */
+ NULL, /* dirfsp */
+ smb_fname_src, /* fname */
+ FILE_GENERIC_READ, /* access_mask */
+ FILE_SHARE_READ | FILE_SHARE_WRITE, /* share_access */
+ FILE_OPEN, /* create_disposition*/
+ 0, /* create_options */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes */
+ INTERNAL_OPEN_ONLY, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp1, /* result */
+ NULL, /* psbuf */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ dosattrs = fdos_mode(fsp1);
+
+ if (SMB_VFS_STAT(conn, smb_fname_dst_tmp) == -1) {
+ ZERO_STRUCTP(&smb_fname_dst_tmp->st);
+ }
+
+ status = openat_pathref_fsp(conn->cwd_fsp, smb_fname_dst);
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND))
+ {
+ goto out;
+ }
+
+ /* Open the dst file for writing. */
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ NULL, /* req */
+ NULL, /* dirfsp */
+ smb_fname_dst, /* fname */
+ FILE_GENERIC_WRITE, /* access_mask */
+ FILE_SHARE_READ | FILE_SHARE_WRITE, /* share_access */
+ new_create_disposition, /* create_disposition*/
+ 0, /* create_options */
+ dosattrs, /* file_attributes */
+ INTERNAL_OPEN_ONLY, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp2, /* result */
+ NULL, /* psbuf */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ close_file_free(NULL, &fsp1, ERROR_CLOSE);
+ goto out;
+ }
+
+ /* Do the actual copy. */
+ if (smb_fname_src->st.st_ex_size) {
+ ret = vfs_transfer_file(fsp1, fsp2, smb_fname_src->st.st_ex_size);
+ } else {
+ ret = 0;
+ }
+
+ close_file_free(NULL, &fsp1, NORMAL_CLOSE);
+
+ /* Ensure the modtime is set correctly on the destination file. */
+ set_close_write_time(fsp2, smb_fname_src->st.st_ex_mtime);
+
+ /*
+ * As we are opening fsp1 read-only we only expect
+ * an error on close on fsp2 if we are out of space.
+ * Thus we don't look at the error return from the
+ * close of fsp1.
+ */
+ status = close_file_free(NULL, &fsp2, NORMAL_CLOSE);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ if (ret != (off_t)smb_fname_src->st.st_ex_size) {
+ status = NT_STATUS_DISK_FULL;
+ goto out;
+ }
+
+ status = NT_STATUS_OK;
+
+ out:
+ TALLOC_FREE(smb_fname_dst_tmp);
+ return status;
+}
+
+/****************************************************************************
+ Get a lock offset, dealing with large offset requests.
+****************************************************************************/
+
+uint64_t get_lock_offset(const uint8_t *data, int data_offset,
+ bool large_file_format)
+{
+ uint64_t offset = 0;
+
+ if(!large_file_format) {
+ offset = (uint64_t)IVAL(data,SMB_LKOFF_OFFSET(data_offset));
+ } else {
+ /*
+ * No BVAL, this is reversed!
+ */
+ offset = (((uint64_t) IVAL(data,SMB_LARGE_LKOFF_OFFSET_HIGH(data_offset))) << 32) |
+ ((uint64_t) IVAL(data,SMB_LARGE_LKOFF_OFFSET_LOW(data_offset)));
+ }
+
+ return offset;
+}
+
+struct smbd_do_unlocking_state {
+ struct files_struct *fsp;
+ uint16_t num_ulocks;
+ struct smbd_lock_element *ulocks;
+ NTSTATUS status;
+};
+
+static void smbd_do_unlocking_fn(
+ struct share_mode_lock *lck,
+ void *private_data)
+{
+ struct smbd_do_unlocking_state *state = private_data;
+ struct files_struct *fsp = state->fsp;
+ uint16_t i;
+
+ for (i = 0; i < state->num_ulocks; i++) {
+ struct smbd_lock_element *e = &state->ulocks[i];
+
+ DBG_DEBUG("unlock start=%"PRIu64", len=%"PRIu64" for "
+ "pid %"PRIu64", file %s\n",
+ e->offset,
+ e->count,
+ e->smblctx,
+ fsp_str_dbg(fsp));
+
+ if (e->brltype != UNLOCK_LOCK) {
+ /* this can only happen with SMB2 */
+ state->status = NT_STATUS_INVALID_PARAMETER;
+ return;
+ }
+
+ state->status = do_unlock(
+ fsp, e->smblctx, e->count, e->offset, e->lock_flav);
+
+ DBG_DEBUG("do_unlock returned %s\n",
+ nt_errstr(state->status));
+
+ if (!NT_STATUS_IS_OK(state->status)) {
+ return;
+ }
+ }
+
+ share_mode_wakeup_waiters(fsp->file_id);
+}
+
+NTSTATUS smbd_do_unlocking(struct smb_request *req,
+ files_struct *fsp,
+ uint16_t num_ulocks,
+ struct smbd_lock_element *ulocks)
+{
+ struct smbd_do_unlocking_state state = {
+ .fsp = fsp,
+ .num_ulocks = num_ulocks,
+ .ulocks = ulocks,
+ };
+ NTSTATUS status;
+
+ DBG_NOTICE("%s num_ulocks=%"PRIu16"\n", fsp_fnum_dbg(fsp), num_ulocks);
+
+ status = share_mode_do_locked_vfs_allowed(
+ fsp->file_id, smbd_do_unlocking_fn, &state);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("share_mode_do_locked_vfs_allowed failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(state.status)) {
+ DBG_DEBUG("smbd_do_unlocking_fn failed: %s\n",
+ nt_errstr(status));
+ return state.status;
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/smb2_server.c b/source3/smbd/smb2_server.c
new file mode 100644
index 0000000..afb86ab
--- /dev/null
+++ b/source3/smbd/smb2_server.c
@@ -0,0 +1,5235 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+ Copyright (C) Jeremy Allison 2010
+
+ 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 "system/network.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "smbd/smbXsrv_open.h"
+#include "lib/param/param.h"
+#include "../libcli/smb/smb_common.h"
+#include "../lib/tsocket/tsocket.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "smbprofile.h"
+#include "../lib/util/bitmap.h"
+#include "../librpc/gen_ndr/krb5pac.h"
+#include "lib/util/iov_buf.h"
+#include "auth.h"
+#include "libcli/smb/smbXcli_base.h"
+#include "source3/lib/substitute.h"
+
+#if defined(LINUX)
+/* SIOCOUTQ TIOCOUTQ are the same */
+#define __IOCTL_SEND_QUEUE_SIZE_OPCODE TIOCOUTQ
+#define __HAVE_TCP_INFO_RTO 1
+#define __ALLOW_MULTI_CHANNEL_SUPPORT 1
+#elif defined(FREEBSD)
+#define __IOCTL_SEND_QUEUE_SIZE_OPCODE FIONWRITE
+#define __HAVE_TCP_INFO_RTO 1
+#define __ALLOW_MULTI_CHANNEL_SUPPORT 1
+#endif
+
+#include "lib/crypto/gnutls_helpers.h"
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static void smbd_smb2_connection_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data);
+static NTSTATUS smbd_smb2_flush_send_queue(struct smbXsrv_connection *xconn);
+
+static const struct smbd_smb2_dispatch_table {
+ uint16_t opcode;
+ uint16_t fileid_ofs;
+ bool need_session : 1;
+ bool need_tcon : 1;
+ bool as_root : 1;
+ bool modify : 1;
+} smbd_smb2_table[] = {
+ {
+ .opcode = SMB2_OP_NEGPROT,
+ .as_root = true,
+ },{
+ .opcode = SMB2_OP_SESSSETUP,
+ .as_root = true,
+ },{
+ .opcode = SMB2_OP_LOGOFF,
+ .need_session = true,
+ .as_root = true,
+ },{
+ .opcode = SMB2_OP_TCON,
+ .need_session = true,
+ /*
+ * This call needs to be run as root.
+ *
+ * smbd_smb2_request_process_tcon()
+ * calls make_connection_snum(), which will call
+ * change_to_user(), when needed.
+ */
+ .as_root = true,
+ },{
+ .opcode = SMB2_OP_TDIS,
+ .need_session = true,
+ .need_tcon = true,
+ .as_root = true,
+ },{
+ .opcode = SMB2_OP_CREATE,
+ .need_session = true,
+ .need_tcon = true,
+ },{
+ .opcode = SMB2_OP_CLOSE,
+ .need_session = true,
+ .need_tcon = true,
+ .fileid_ofs = 0x08,
+ },{
+ .opcode = SMB2_OP_FLUSH,
+ .need_session = true,
+ .need_tcon = true,
+ .fileid_ofs = 0x08,
+ },{
+ .opcode = SMB2_OP_READ,
+ .need_session = true,
+ .need_tcon = true,
+ .fileid_ofs = 0x10,
+ },{
+ .opcode = SMB2_OP_WRITE,
+ .need_session = true,
+ .need_tcon = true,
+ .fileid_ofs = 0x10,
+ .modify = true,
+ },{
+ .opcode = SMB2_OP_LOCK,
+ .need_session = true,
+ .need_tcon = true,
+ .fileid_ofs = 0x08,
+ },{
+ .opcode = SMB2_OP_IOCTL,
+ .need_session = true,
+ .need_tcon = true,
+ .fileid_ofs = 0x08,
+ .modify = true,
+ },{
+ .opcode = SMB2_OP_CANCEL,
+ .as_root = true,
+ },{
+ .opcode = SMB2_OP_KEEPALIVE,
+ },{
+ .opcode = SMB2_OP_QUERY_DIRECTORY,
+ .need_session = true,
+ .need_tcon = true,
+ .fileid_ofs = 0x08,
+ },{
+ .opcode = SMB2_OP_NOTIFY,
+ .need_session = true,
+ .need_tcon = true,
+ .fileid_ofs = 0x08,
+ },{
+ .opcode = SMB2_OP_GETINFO,
+ .need_session = true,
+ .need_tcon = true,
+ .fileid_ofs = 0x18,
+ },{
+ .opcode = SMB2_OP_SETINFO,
+ .need_session = true,
+ .need_tcon = true,
+ .fileid_ofs = 0x10,
+ .modify = true,
+ },{
+ .opcode = SMB2_OP_BREAK,
+ .need_session = true,
+ .need_tcon = true,
+ /*
+ * we do not set
+ * .fileid_ofs here
+ * as LEASE breaks does not
+ * have a file id
+ */
+ }
+};
+
+const char *smb2_opcode_name(uint16_t opcode)
+{
+ const char *result = "Bad SMB2 opcode";
+
+ switch (opcode) {
+ case SMB2_OP_NEGPROT:
+ result = "SMB2_OP_NEGPROT";
+ break;
+ case SMB2_OP_SESSSETUP:
+ result = "SMB2_OP_SESSSETUP";
+ break;
+ case SMB2_OP_LOGOFF:
+ result = "SMB2_OP_LOGOFF";
+ break;
+ case SMB2_OP_TCON:
+ result = "SMB2_OP_TCON";
+ break;
+ case SMB2_OP_TDIS:
+ result = "SMB2_OP_TDIS";
+ break;
+ case SMB2_OP_CREATE:
+ result = "SMB2_OP_CREATE";
+ break;
+ case SMB2_OP_CLOSE:
+ result = "SMB2_OP_CLOSE";
+ break;
+ case SMB2_OP_FLUSH:
+ result = "SMB2_OP_FLUSH";
+ break;
+ case SMB2_OP_READ:
+ result = "SMB2_OP_READ";
+ break;
+ case SMB2_OP_WRITE:
+ result = "SMB2_OP_WRITE";
+ break;
+ case SMB2_OP_LOCK:
+ result = "SMB2_OP_LOCK";
+ break;
+ case SMB2_OP_IOCTL:
+ result = "SMB2_OP_IOCTL";
+ break;
+ case SMB2_OP_CANCEL:
+ result = "SMB2_OP_CANCEL";
+ break;
+ case SMB2_OP_KEEPALIVE:
+ result = "SMB2_OP_KEEPALIVE";
+ break;
+ case SMB2_OP_QUERY_DIRECTORY:
+ result = "SMB2_OP_QUERY_DIRECTORY";
+ break;
+ case SMB2_OP_NOTIFY:
+ result = "SMB2_OP_NOTIFY";
+ break;
+ case SMB2_OP_GETINFO:
+ result = "SMB2_OP_GETINFO";
+ break;
+ case SMB2_OP_SETINFO:
+ result = "SMB2_OP_SETINFO";
+ break;
+ case SMB2_OP_BREAK:
+ result = "SMB2_OP_BREAK";
+ break;
+ default:
+ break;
+ }
+ return result;
+}
+
+static const struct smbd_smb2_dispatch_table *smbd_smb2_call(uint16_t opcode)
+{
+ const struct smbd_smb2_dispatch_table *ret = NULL;
+
+ if (opcode >= ARRAY_SIZE(smbd_smb2_table)) {
+ return NULL;
+ }
+
+ ret = &smbd_smb2_table[opcode];
+
+ SMB_ASSERT(ret->opcode == opcode);
+
+ return ret;
+}
+
+static void print_req_vectors(const struct smbd_smb2_request *req)
+{
+ int i;
+
+ for (i = 0; i < req->in.vector_count; i++) {
+ dbgtext("\treq->in.vector[%u].iov_len = %u\n",
+ (unsigned int)i,
+ (unsigned int)req->in.vector[i].iov_len);
+ }
+ for (i = 0; i < req->out.vector_count; i++) {
+ dbgtext("\treq->out.vector[%u].iov_len = %u\n",
+ (unsigned int)i,
+ (unsigned int)req->out.vector[i].iov_len);
+ }
+}
+
+bool smbd_is_smb2_header(const uint8_t *inbuf, size_t size)
+{
+ if (size < (4 + SMB2_HDR_BODY)) {
+ return false;
+ }
+
+ if (IVAL(inbuf, 4) != SMB2_MAGIC) {
+ return false;
+ }
+
+ return true;
+}
+
+bool smbd_smb2_is_compound(const struct smbd_smb2_request *req)
+{
+ return req->in.vector_count >= (2*SMBD_SMB2_NUM_IOV_PER_REQ);
+}
+
+bool smbd_smb2_is_last_in_compound(const struct smbd_smb2_request *req)
+{
+ return (req->current_idx + SMBD_SMB2_NUM_IOV_PER_REQ ==
+ req->in.vector_count);
+}
+
+static NTSTATUS smbd_initialize_smb2(struct smbXsrv_connection *xconn,
+ uint64_t expected_seq_low)
+{
+ int rc;
+
+ xconn->smb2.credits.seq_low = expected_seq_low;
+ xconn->smb2.credits.seq_range = 1;
+ xconn->smb2.credits.granted = 1;
+ xconn->smb2.credits.max = lp_smb2_max_credits();
+ xconn->smb2.credits.bitmap = bitmap_talloc(xconn,
+ xconn->smb2.credits.max);
+ if (xconn->smb2.credits.bitmap == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ tevent_fd_set_close_fn(xconn->transport.fde, NULL);
+ TALLOC_FREE(xconn->transport.fde);
+
+ xconn->transport.fde = tevent_add_fd(
+ xconn->client->raw_ev_ctx,
+ xconn,
+ xconn->transport.sock,
+ TEVENT_FD_ERROR | TEVENT_FD_READ,
+ smbd_smb2_connection_handler,
+ xconn);
+ if (xconn->transport.fde == NULL) {
+ close(xconn->transport.sock);
+ xconn->transport.sock = -1;
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_fd_set_auto_close(xconn->transport.fde);
+
+ /*
+ * Ensure child is set to non-blocking mode,
+ * unless the system supports MSG_DONTWAIT,
+ * if MSG_DONTWAIT is available we should force
+ * blocking mode.
+ */
+#ifdef MSG_DONTWAIT
+ rc = set_blocking(xconn->transport.sock, true);
+ if (rc < 0) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+#else
+ rc = set_blocking(xconn->transport.sock, false);
+ if (rc < 0) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+#endif
+
+ return NT_STATUS_OK;
+}
+
+#define smb2_len(buf) (PVAL(buf,3)|(PVAL(buf,2)<<8)|(PVAL(buf,1)<<16))
+#define _smb2_setlen(_buf,len) do { \
+ uint8_t *buf = (uint8_t *)_buf; \
+ buf[0] = 0; \
+ buf[1] = ((len)&0xFF0000)>>16; \
+ buf[2] = ((len)&0xFF00)>>8; \
+ buf[3] = (len)&0xFF; \
+} while (0)
+
+static bool smb2_setup_nbt_length(struct iovec *vector, int count)
+{
+ ssize_t len;
+
+ if (count == 0) {
+ return false;
+ }
+
+ len = iov_buflen(vector+1, count-1);
+
+ if ((len == -1) || (len > 0xFFFFFF)) {
+ return false;
+ }
+
+ _smb2_setlen(vector[0].iov_base, len);
+ return true;
+}
+
+static int smbd_smb2_request_destructor(struct smbd_smb2_request *req)
+{
+ TALLOC_FREE(req->first_enc_key);
+ TALLOC_FREE(req->last_sign_key);
+ return 0;
+}
+
+void smb2_request_set_async_internal(struct smbd_smb2_request *req,
+ bool async_internal)
+{
+ req->async_internal = async_internal;
+}
+
+static struct smbd_smb2_request *smbd_smb2_request_allocate(struct smbXsrv_connection *xconn)
+{
+ TALLOC_CTX *mem_pool;
+ struct smbd_smb2_request *req;
+
+#if 0
+ /* Enable this to find subtle valgrind errors. */
+ mem_pool = talloc_init("smbd_smb2_request_allocate");
+#else
+ mem_pool = talloc_tos();
+#endif
+ if (mem_pool == NULL) {
+ return NULL;
+ }
+
+ req = talloc(mem_pool, struct smbd_smb2_request);
+ if (req == NULL) {
+ talloc_free(mem_pool);
+ return NULL;
+ }
+ talloc_reparent(mem_pool, xconn, req);
+#if 0
+ TALLOC_FREE(mem_pool);
+#endif
+ *req = (struct smbd_smb2_request) {
+ .sconn = xconn->client->sconn,
+ .xconn = xconn,
+ .last_session_id = UINT64_MAX,
+ .last_tid = UINT32_MAX,
+ };
+
+ talloc_set_destructor(req, smbd_smb2_request_destructor);
+
+ return req;
+}
+
+static NTSTATUS smbd_smb2_inbuf_parse_compound(struct smbXsrv_connection *xconn,
+ NTTIME now,
+ uint8_t *buf,
+ size_t buflen,
+ struct smbd_smb2_request *req,
+ struct iovec **piov,
+ int *pnum_iov)
+{
+ TALLOC_CTX *mem_ctx = req;
+ struct iovec *iov;
+ int num_iov = 1;
+ size_t taken = 0;
+ uint8_t *first_hdr = buf;
+ size_t verified_buflen = 0;
+ uint8_t *tf = NULL;
+ size_t tf_len = 0;
+
+ /*
+ * Note: index '0' is reserved for the transport protocol
+ */
+ iov = req->in._vector;
+
+ while (taken < buflen) {
+ size_t len = buflen - taken;
+ uint8_t *hdr = first_hdr + taken;
+ struct iovec *cur;
+ size_t full_size;
+ size_t next_command_ofs;
+ uint16_t body_size;
+ uint8_t *body = NULL;
+ uint32_t dyn_size;
+ uint8_t *dyn = NULL;
+ struct iovec *iov_alloc = NULL;
+
+ if (iov != req->in._vector) {
+ iov_alloc = iov;
+ }
+
+ if (verified_buflen > taken) {
+ len = verified_buflen - taken;
+ } else {
+ tf = NULL;
+ tf_len = 0;
+ }
+
+ if (len < 4) {
+ DEBUG(10, ("%d bytes left, expected at least %d\n",
+ (int)len, 4));
+ goto inval;
+ }
+ if (IVAL(hdr, 0) == SMB2_TF_MAGIC) {
+ struct smbXsrv_session *s = NULL;
+ uint64_t uid;
+ struct iovec tf_iov[2];
+ NTSTATUS status;
+ size_t enc_len;
+
+ if (xconn->protocol < PROTOCOL_SMB3_00) {
+ DEBUG(10, ("Got SMB2_TRANSFORM header, "
+ "but dialect[0x%04X] is used\n",
+ xconn->smb2.server.dialect));
+ goto inval;
+ }
+
+ if (xconn->smb2.server.cipher == 0) {
+ DEBUG(10, ("Got SMB2_TRANSFORM header, "
+ "but not negotiated "
+ "client[0x%08X] server[0x%08X]\n",
+ xconn->smb2.client.capabilities,
+ xconn->smb2.server.capabilities));
+ goto inval;
+ }
+
+ if (len < SMB2_TF_HDR_SIZE) {
+ DEBUG(1, ("%d bytes left, expected at least %d\n",
+ (int)len, SMB2_TF_HDR_SIZE));
+ goto inval;
+ }
+ tf = hdr;
+ tf_len = SMB2_TF_HDR_SIZE;
+ taken += tf_len;
+
+ hdr = first_hdr + taken;
+ enc_len = IVAL(tf, SMB2_TF_MSG_SIZE);
+ uid = BVAL(tf, SMB2_TF_SESSION_ID);
+
+ if (len < SMB2_TF_HDR_SIZE + enc_len) {
+ DEBUG(1, ("%d bytes left, expected at least %d\n",
+ (int)len,
+ (int)(SMB2_TF_HDR_SIZE + enc_len)));
+ goto inval;
+ }
+
+ status = smb2srv_session_lookup_conn(xconn, uid, now,
+ &s);
+ if (!NT_STATUS_IS_OK(status)) {
+ status = smb2srv_session_lookup_global(xconn->client,
+ uid, req, &s);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("invalid session[%llu] in "
+ "SMB2_TRANSFORM header\n",
+ (unsigned long long)uid));
+ TALLOC_FREE(iov_alloc);
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+
+ tf_iov[0].iov_base = (void *)tf;
+ tf_iov[0].iov_len = tf_len;
+ tf_iov[1].iov_base = (void *)hdr;
+ tf_iov[1].iov_len = enc_len;
+
+ status = smb2_signing_decrypt_pdu(s->global->decryption_key,
+ tf_iov, 2);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(iov_alloc);
+ return status;
+ }
+
+ verified_buflen = taken + enc_len;
+ len = enc_len;
+ }
+
+ /*
+ * We need the header plus the body length field
+ */
+
+ if (len < SMB2_HDR_BODY + 2) {
+
+ if ((len == 5) &&
+ (IVAL(hdr, 0) == SMB_SUICIDE_PACKET) &&
+ lp_parm_bool(-1, "smbd", "suicide mode", false)) {
+ uint8_t exitcode = CVAL(hdr, 4);
+ DBG_WARNING("SUICIDE: Exiting immediately "
+ "with code %"PRIu8"\n",
+ exitcode);
+ exit(exitcode);
+ }
+
+ DEBUG(10, ("%d bytes left, expected at least %d\n",
+ (int)len, SMB2_HDR_BODY));
+ goto inval;
+ }
+ if (IVAL(hdr, 0) != SMB2_MAGIC) {
+ DEBUG(10, ("Got non-SMB2 PDU: %x\n",
+ IVAL(hdr, 0)));
+ goto inval;
+ }
+ if (SVAL(hdr, 4) != SMB2_HDR_BODY) {
+ DEBUG(10, ("Got HDR len %d, expected %d\n",
+ SVAL(hdr, 4), SMB2_HDR_BODY));
+ goto inval;
+ }
+
+ full_size = len;
+ next_command_ofs = IVAL(hdr, SMB2_HDR_NEXT_COMMAND);
+ body_size = SVAL(hdr, SMB2_HDR_BODY);
+
+ if (next_command_ofs != 0) {
+ if (next_command_ofs < (SMB2_HDR_BODY + 2)) {
+ goto inval;
+ }
+ if (next_command_ofs > full_size) {
+ goto inval;
+ }
+ full_size = next_command_ofs;
+ }
+ if (body_size < 2) {
+ goto inval;
+ }
+ body_size &= 0xfffe;
+
+ if (body_size > (full_size - SMB2_HDR_BODY)) {
+ /*
+ * let the caller handle the error
+ */
+ body_size = full_size - SMB2_HDR_BODY;
+ }
+ body = hdr + SMB2_HDR_BODY;
+ dyn = body + body_size;
+ dyn_size = full_size - (SMB2_HDR_BODY + body_size);
+
+ if (num_iov >= ARRAY_SIZE(req->in._vector)) {
+ struct iovec *iov_tmp = NULL;
+
+ iov_tmp = talloc_realloc(mem_ctx, iov_alloc,
+ struct iovec,
+ num_iov +
+ SMBD_SMB2_NUM_IOV_PER_REQ);
+ if (iov_tmp == NULL) {
+ TALLOC_FREE(iov_alloc);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (iov_alloc == NULL) {
+ memcpy(iov_tmp,
+ req->in._vector,
+ sizeof(req->in._vector));
+ }
+
+ iov = iov_tmp;
+ }
+ cur = &iov[num_iov];
+ num_iov += SMBD_SMB2_NUM_IOV_PER_REQ;
+
+ cur[SMBD_SMB2_TF_IOV_OFS].iov_base = tf;
+ cur[SMBD_SMB2_TF_IOV_OFS].iov_len = tf_len;
+ cur[SMBD_SMB2_HDR_IOV_OFS].iov_base = hdr;
+ cur[SMBD_SMB2_HDR_IOV_OFS].iov_len = SMB2_HDR_BODY;
+ cur[SMBD_SMB2_BODY_IOV_OFS].iov_base = body;
+ cur[SMBD_SMB2_BODY_IOV_OFS].iov_len = body_size;
+ cur[SMBD_SMB2_DYN_IOV_OFS].iov_base = dyn;
+ cur[SMBD_SMB2_DYN_IOV_OFS].iov_len = dyn_size;
+
+ taken += full_size;
+ }
+
+ *piov = iov;
+ *pnum_iov = num_iov;
+ return NT_STATUS_OK;
+
+inval:
+ if (iov != req->in._vector) {
+ TALLOC_FREE(iov);
+ }
+ return NT_STATUS_INVALID_PARAMETER;
+}
+
+static NTSTATUS smbd_smb2_request_create(struct smbXsrv_connection *xconn,
+ const uint8_t *_inpdu, size_t size,
+ struct smbd_smb2_request **_req)
+{
+ struct smbd_smb2_request *req;
+ uint32_t protocol_version;
+ uint8_t *inpdu = NULL;
+ const uint8_t *inhdr = NULL;
+ uint16_t cmd;
+ uint32_t next_command_ofs;
+ NTSTATUS status;
+ NTTIME now;
+
+ if (size < (SMB2_HDR_BODY + 2)) {
+ DEBUG(0,("Invalid SMB2 packet length count %ld\n", (long)size));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ inhdr = _inpdu;
+
+ protocol_version = IVAL(inhdr, SMB2_HDR_PROTOCOL_ID);
+ if (protocol_version != SMB2_MAGIC) {
+ DEBUG(0,("Invalid SMB packet: protocol prefix: 0x%08X\n",
+ protocol_version));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ cmd = SVAL(inhdr, SMB2_HDR_OPCODE);
+ if (cmd != SMB2_OP_NEGPROT) {
+ DEBUG(0,("Invalid SMB packet: first request: 0x%04X\n",
+ cmd));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ next_command_ofs = IVAL(inhdr, SMB2_HDR_NEXT_COMMAND);
+ if (next_command_ofs != 0) {
+ DEBUG(0,("Invalid SMB packet: next_command: 0x%08X\n",
+ next_command_ofs));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ req = smbd_smb2_request_allocate(xconn);
+ if (req == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ inpdu = talloc_memdup(req, _inpdu, size);
+ if (inpdu == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ req->request_time = timeval_current();
+ now = timeval_to_nttime(&req->request_time);
+
+ status = smbd_smb2_inbuf_parse_compound(xconn,
+ now,
+ inpdu,
+ size,
+ req, &req->in.vector,
+ &req->in.vector_count);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(req);
+ return status;
+ }
+
+ req->current_idx = 1;
+
+ *_req = req;
+ return NT_STATUS_OK;
+}
+
+static bool smb2_validate_sequence_number(struct smbXsrv_connection *xconn,
+ uint64_t message_id, uint64_t seq_id)
+{
+ struct bitmap *credits_bm = xconn->smb2.credits.bitmap;
+ unsigned int offset;
+ uint64_t seq_tmp;
+
+ seq_tmp = xconn->smb2.credits.seq_low;
+ if (seq_id < seq_tmp) {
+ DBGC_ERR(DBGC_SMB2_CREDITS,
+ "smb2_validate_sequence_number: bad message_id "
+ "%llu (sequence id %llu) "
+ "(granted = %u, low = %llu, range = %u)\n",
+ (unsigned long long)message_id,
+ (unsigned long long)seq_id,
+ (unsigned int)xconn->smb2.credits.granted,
+ (unsigned long long)xconn->smb2.credits.seq_low,
+ (unsigned int)xconn->smb2.credits.seq_range);
+ return false;
+ }
+
+ seq_tmp += xconn->smb2.credits.seq_range;
+ if (seq_id >= seq_tmp) {
+ DBGC_ERR(DBGC_SMB2_CREDITS,
+ "smb2_validate_sequence_number: bad message_id "
+ "%llu (sequence id %llu) "
+ "(granted = %u, low = %llu, range = %u)\n",
+ (unsigned long long)message_id,
+ (unsigned long long)seq_id,
+ (unsigned int)xconn->smb2.credits.granted,
+ (unsigned long long)xconn->smb2.credits.seq_low,
+ (unsigned int)xconn->smb2.credits.seq_range);
+ return false;
+ }
+
+ offset = seq_id % xconn->smb2.credits.max;
+
+ if (bitmap_query(credits_bm, offset)) {
+ DBGC_ERR(DBGC_SMB2_CREDITS,
+ "smb2_validate_sequence_number: duplicate message_id "
+ "%llu (sequence id %llu) "
+ "(granted = %u, low = %llu, range = %u) "
+ "(bm offset %u)\n",
+ (unsigned long long)message_id,
+ (unsigned long long)seq_id,
+ (unsigned int)xconn->smb2.credits.granted,
+ (unsigned long long)xconn->smb2.credits.seq_low,
+ (unsigned int)xconn->smb2.credits.seq_range,
+ offset);
+ return false;
+ }
+
+ /* Mark the message_ids as seen in the bitmap. */
+ bitmap_set(credits_bm, offset);
+
+ if (seq_id != xconn->smb2.credits.seq_low) {
+ return true;
+ }
+
+ /*
+ * Move the window forward by all the message_id's
+ * already seen.
+ */
+ while (bitmap_query(credits_bm, offset)) {
+ DBGC_DEBUG(DBGC_SMB2_CREDITS,
+ "smb2_validate_sequence_number: clearing "
+ "id %llu (position %u) from bitmap\n",
+ (unsigned long long)(xconn->smb2.credits.seq_low),
+ offset);
+ bitmap_clear(credits_bm, offset);
+
+ xconn->smb2.credits.seq_low += 1;
+ xconn->smb2.credits.seq_range -= 1;
+ offset = xconn->smb2.credits.seq_low % xconn->smb2.credits.max;
+ }
+
+ return true;
+}
+
+static bool smb2_validate_message_id(struct smbXsrv_connection *xconn,
+ const uint8_t *inhdr)
+{
+ uint64_t message_id = BVAL(inhdr, SMB2_HDR_MESSAGE_ID);
+ uint16_t opcode = SVAL(inhdr, SMB2_HDR_OPCODE);
+ uint16_t credit_charge = 1;
+ uint64_t i;
+
+ if (opcode == SMB2_OP_CANCEL) {
+ /* SMB2_CANCEL requests by definition resend messageids. */
+ return true;
+ }
+
+ if (xconn->smb2.credits.multicredit) {
+ credit_charge = SVAL(inhdr, SMB2_HDR_CREDIT_CHARGE);
+ credit_charge = MAX(credit_charge, 1);
+ }
+
+ DEBUGC(11,
+ DBGC_SMB2_CREDITS,
+ ("smb2_validate_message_id: mid %llu (charge %llu), "
+ "credits_granted %llu, "
+ "seqnum low/range: %llu/%llu\n",
+ (unsigned long long) message_id,
+ (unsigned long long) credit_charge,
+ (unsigned long long) xconn->smb2.credits.granted,
+ (unsigned long long) xconn->smb2.credits.seq_low,
+ (unsigned long long) xconn->smb2.credits.seq_range));
+
+ if (xconn->smb2.credits.granted < credit_charge) {
+ DBGC_ERR(DBGC_SMB2_CREDITS,
+ "smb2_validate_message_id: client used more "
+ "credits than granted, mid %llu, charge %llu, "
+ "credits_granted %llu, "
+ "seqnum low/range: %llu/%llu\n",
+ (unsigned long long) message_id,
+ (unsigned long long) credit_charge,
+ (unsigned long long) xconn->smb2.credits.granted,
+ (unsigned long long) xconn->smb2.credits.seq_low,
+ (unsigned long long) xconn->smb2.credits.seq_range);
+ return false;
+ }
+
+ /*
+ * now check the message ids
+ *
+ * for multi-credit requests we need to check all current mid plus
+ * the implicit mids caused by the credit charge
+ * e.g. current mid = 15, charge 5 => mark 15-19 as used
+ */
+
+ for (i = 0; i <= (credit_charge-1); i++) {
+ uint64_t id = message_id + i;
+ bool ok;
+
+ DEBUGC(11,
+ DBGC_SMB2_CREDITS,
+ ("Iterating mid %llu charge %u (sequence %llu)\n",
+ (unsigned long long)message_id,
+ credit_charge,
+ (unsigned long long)id));
+
+ ok = smb2_validate_sequence_number(xconn, message_id, id);
+ if (!ok) {
+ return false;
+ }
+ }
+
+ /* subtract used credits */
+ xconn->smb2.credits.granted -= credit_charge;
+
+ return true;
+}
+
+static NTSTATUS smbd_smb2_request_validate(struct smbd_smb2_request *req)
+{
+ int count;
+ int idx;
+
+ count = req->in.vector_count;
+
+ if (count < 1 + SMBD_SMB2_NUM_IOV_PER_REQ) {
+ /* It's not a SMB2 request */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ for (idx=1; idx < count; idx += SMBD_SMB2_NUM_IOV_PER_REQ) {
+ struct iovec *hdr = SMBD_SMB2_IDX_HDR_IOV(req,in,idx);
+ struct iovec *body = SMBD_SMB2_IDX_BODY_IOV(req,in,idx);
+ const uint8_t *inhdr = NULL;
+
+ if (hdr->iov_len != SMB2_HDR_BODY) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (body->iov_len < 2) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ inhdr = (const uint8_t *)hdr->iov_base;
+
+ /* Check the SMB2 header */
+ if (IVAL(inhdr, SMB2_HDR_PROTOCOL_ID) != SMB2_MAGIC) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!smb2_validate_message_id(req->xconn, inhdr)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+static void smb2_set_operation_credit(struct smbXsrv_connection *xconn,
+ const struct iovec *in_vector,
+ struct iovec *out_vector)
+{
+ const uint8_t *inhdr = (const uint8_t *)in_vector->iov_base;
+ uint8_t *outhdr = (uint8_t *)out_vector->iov_base;
+ uint16_t credit_charge = 1;
+ uint16_t credits_requested;
+ uint32_t out_flags;
+ uint16_t cmd;
+ NTSTATUS out_status;
+ uint16_t credits_granted = 0;
+ uint64_t credits_possible;
+ uint16_t current_max_credits;
+
+ /*
+ * first we grant only 1/16th of the max range.
+ *
+ * Windows also starts with the 1/16th and then grants
+ * more later. I was only able to trigger higher
+ * values, when using a very high credit charge.
+ *
+ * TODO: scale up depending on load, free memory
+ * or other stuff.
+ * Maybe also on the relationship between number
+ * of requests and the used sequence number.
+ * Which means we would grant more credits
+ * for client which use multi credit requests.
+ *
+ * The above is what Windows Server < 2016 is doing,
+ * but new servers use all credits (8192 by default).
+ */
+ current_max_credits = xconn->smb2.credits.max;
+ current_max_credits = MAX(current_max_credits, 1);
+
+ if (xconn->smb2.credits.multicredit) {
+ credit_charge = SVAL(inhdr, SMB2_HDR_CREDIT_CHARGE);
+ credit_charge = MAX(credit_charge, 1);
+ }
+
+ cmd = SVAL(inhdr, SMB2_HDR_OPCODE);
+ credits_requested = SVAL(inhdr, SMB2_HDR_CREDIT);
+ credits_requested = MAX(credits_requested, 1);
+ out_flags = IVAL(outhdr, SMB2_HDR_FLAGS);
+ out_status = NT_STATUS(IVAL(outhdr, SMB2_HDR_STATUS));
+
+ SMB_ASSERT(xconn->smb2.credits.max >= xconn->smb2.credits.granted);
+
+ if (xconn->smb2.credits.max < credit_charge) {
+ smbd_server_connection_terminate(xconn,
+ "client error: credit charge > max credits\n");
+ return;
+ }
+
+ if (out_flags & SMB2_HDR_FLAG_ASYNC) {
+ /*
+ * In case we already send an async interim
+ * response, we should not grant
+ * credits on the final response.
+ */
+ credits_granted = 0;
+ } else {
+ uint16_t additional_possible =
+ xconn->smb2.credits.max - credit_charge;
+ uint16_t additional_max = 0;
+ uint16_t additional_credits = credits_requested - 1;
+
+ switch (cmd) {
+ case SMB2_OP_NEGPROT:
+ break;
+ case SMB2_OP_SESSSETUP:
+ /*
+ * Windows 2012 RC1 starts to grant
+ * additional credits
+ * with a successful session setup
+ */
+ if (NT_STATUS_IS_OK(out_status)) {
+ additional_max = xconn->smb2.credits.max;
+ }
+ break;
+ default:
+ /*
+ * Windows Server < 2016 and older Samba versions
+ * used to only grant additional credits in
+ * chunks of 32 credits.
+ *
+ * But we match Windows Server 2016 and grant
+ * all credits as requested.
+ */
+ additional_max = xconn->smb2.credits.max;
+ break;
+ }
+
+ additional_max = MIN(additional_max, additional_possible);
+ additional_credits = MIN(additional_credits, additional_max);
+
+ credits_granted = credit_charge + additional_credits;
+ }
+
+ /*
+ * sequence numbers should not wrap
+ *
+ * 1. calculate the possible credits until
+ * the sequence numbers start to wrap on 64-bit.
+ *
+ * 2. UINT64_MAX is used for Break Notifications.
+ *
+ * 2. truncate the possible credits to the maximum
+ * credits we want to grant to the client in total.
+ *
+ * 3. remove the range we'll already granted to the client
+ * this makes sure the client consumes the lowest sequence
+ * number, before we can grant additional credits.
+ */
+ credits_possible = UINT64_MAX - xconn->smb2.credits.seq_low;
+ if (credits_possible > 0) {
+ /* remove UINT64_MAX */
+ credits_possible -= 1;
+ }
+ credits_possible = MIN(credits_possible, current_max_credits);
+ credits_possible -= xconn->smb2.credits.seq_range;
+
+ credits_granted = MIN(credits_granted, credits_possible);
+
+ SSVAL(outhdr, SMB2_HDR_CREDIT, credits_granted);
+ xconn->smb2.credits.granted += credits_granted;
+ xconn->smb2.credits.seq_range += credits_granted;
+
+ DBGC_DEBUG(DBGC_SMB2_CREDITS,
+ "smb2_set_operation_credit: requested %u, charge %u, "
+ "granted %u, current possible/max %u/%u, "
+ "total granted/max/low/range %u/%u/%llu/%u\n",
+ (unsigned int)credits_requested,
+ (unsigned int)credit_charge,
+ (unsigned int)credits_granted,
+ (unsigned int)credits_possible,
+ (unsigned int)current_max_credits,
+ (unsigned int)xconn->smb2.credits.granted,
+ (unsigned int)xconn->smb2.credits.max,
+ (unsigned long long)xconn->smb2.credits.seq_low,
+ (unsigned int)xconn->smb2.credits.seq_range);
+}
+
+static void smb2_calculate_credits(const struct smbd_smb2_request *inreq,
+ struct smbd_smb2_request *outreq)
+{
+ int count, idx;
+ uint16_t total_credits = 0;
+
+ count = outreq->out.vector_count;
+
+ for (idx=1; idx < count; idx += SMBD_SMB2_NUM_IOV_PER_REQ) {
+ struct iovec *inhdr_v = SMBD_SMB2_IDX_HDR_IOV(inreq,in,idx);
+ struct iovec *outhdr_v = SMBD_SMB2_IDX_HDR_IOV(outreq,out,idx);
+ uint8_t *outhdr = (uint8_t *)outhdr_v->iov_base;
+
+ smb2_set_operation_credit(outreq->xconn, inhdr_v, outhdr_v);
+
+ /* To match Windows, count up what we
+ just granted. */
+ total_credits += SVAL(outhdr, SMB2_HDR_CREDIT);
+ /* Set to zero in all but the last reply. */
+ if (idx + SMBD_SMB2_NUM_IOV_PER_REQ < count) {
+ SSVAL(outhdr, SMB2_HDR_CREDIT, 0);
+ } else {
+ SSVAL(outhdr, SMB2_HDR_CREDIT, total_credits);
+ }
+ }
+}
+
+DATA_BLOB smbd_smb2_generate_outbody(struct smbd_smb2_request *req, size_t size)
+{
+ if (req->current_idx <= 1) {
+ if (size <= sizeof(req->out._body)) {
+ return data_blob_const(req->out._body, size);
+ }
+ }
+
+ return data_blob_talloc(req, NULL, size);
+}
+
+static NTSTATUS smbd_smb2_request_setup_out(struct smbd_smb2_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ TALLOC_CTX *mem_ctx;
+ struct iovec *vector;
+ int count;
+ int idx;
+ bool ok;
+
+ count = req->in.vector_count;
+ if (count <= ARRAY_SIZE(req->out._vector)) {
+ mem_ctx = req;
+ vector = req->out._vector;
+ } else {
+ vector = talloc_zero_array(req, struct iovec, count);
+ if (vector == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ mem_ctx = vector;
+ }
+
+ vector[0].iov_base = req->out.nbt_hdr;
+ vector[0].iov_len = 4;
+ SIVAL(req->out.nbt_hdr, 0, 0);
+
+ for (idx=1; idx < count; idx += SMBD_SMB2_NUM_IOV_PER_REQ) {
+ struct iovec *inhdr_v = SMBD_SMB2_IDX_HDR_IOV(req,in,idx);
+ const uint8_t *inhdr = (const uint8_t *)inhdr_v->iov_base;
+ uint8_t *outhdr = NULL;
+ uint8_t *outbody = NULL;
+ uint32_t next_command_ofs = 0;
+ struct iovec *current = &vector[idx];
+
+ if ((idx + SMBD_SMB2_NUM_IOV_PER_REQ) < count) {
+ /* we have a next command -
+ * setup for the error case. */
+ next_command_ofs = SMB2_HDR_BODY + 9;
+ }
+
+ if (idx == 1) {
+ outhdr = req->out._hdr;
+ } else {
+ outhdr = talloc_zero_array(mem_ctx, uint8_t,
+ OUTVEC_ALLOC_SIZE);
+ if (outhdr == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ outbody = outhdr + SMB2_HDR_BODY;
+
+ /*
+ * SMBD_SMB2_TF_IOV_OFS might be used later
+ */
+ current[SMBD_SMB2_TF_IOV_OFS].iov_base = NULL;
+ current[SMBD_SMB2_TF_IOV_OFS].iov_len = 0;
+
+ current[SMBD_SMB2_HDR_IOV_OFS].iov_base = (void *)outhdr;
+ current[SMBD_SMB2_HDR_IOV_OFS].iov_len = SMB2_HDR_BODY;
+
+ current[SMBD_SMB2_BODY_IOV_OFS].iov_base = (void *)outbody;
+ current[SMBD_SMB2_BODY_IOV_OFS].iov_len = 8;
+
+ current[SMBD_SMB2_DYN_IOV_OFS].iov_base = NULL;
+ current[SMBD_SMB2_DYN_IOV_OFS].iov_len = 0;
+
+ /* setup the SMB2 header */
+ SIVAL(outhdr, SMB2_HDR_PROTOCOL_ID, SMB2_MAGIC);
+ SSVAL(outhdr, SMB2_HDR_LENGTH, SMB2_HDR_BODY);
+ SSVAL(outhdr, SMB2_HDR_CREDIT_CHARGE,
+ SVAL(inhdr, SMB2_HDR_CREDIT_CHARGE));
+ SIVAL(outhdr, SMB2_HDR_STATUS,
+ NT_STATUS_V(NT_STATUS_INTERNAL_ERROR));
+ SSVAL(outhdr, SMB2_HDR_OPCODE,
+ SVAL(inhdr, SMB2_HDR_OPCODE));
+ SIVAL(outhdr, SMB2_HDR_FLAGS,
+ IVAL(inhdr, SMB2_HDR_FLAGS) | SMB2_HDR_FLAG_REDIRECT);
+ SIVAL(outhdr, SMB2_HDR_NEXT_COMMAND, next_command_ofs);
+ SBVAL(outhdr, SMB2_HDR_MESSAGE_ID,
+ BVAL(inhdr, SMB2_HDR_MESSAGE_ID));
+ SIVAL(outhdr, SMB2_HDR_PID,
+ IVAL(inhdr, SMB2_HDR_PID));
+ SIVAL(outhdr, SMB2_HDR_TID,
+ IVAL(inhdr, SMB2_HDR_TID));
+ SBVAL(outhdr, SMB2_HDR_SESSION_ID,
+ BVAL(inhdr, SMB2_HDR_SESSION_ID));
+ memcpy(outhdr + SMB2_HDR_SIGNATURE,
+ inhdr + SMB2_HDR_SIGNATURE, 16);
+
+ /* setup error body header */
+ SSVAL(outbody, 0x00, 0x08 + 1);
+ SSVAL(outbody, 0x02, 0);
+ SIVAL(outbody, 0x04, 0);
+ }
+
+ req->out.vector = vector;
+ req->out.vector_count = count;
+
+ /* setup the length of the NBT packet */
+ ok = smb2_setup_nbt_length(req->out.vector, req->out.vector_count);
+ if (!ok) {
+ return NT_STATUS_INVALID_PARAMETER_MIX;
+ }
+
+ DLIST_ADD_END(xconn->smb2.requests, req);
+
+ return NT_STATUS_OK;
+}
+
+bool smbXsrv_server_multi_channel_enabled(void)
+{
+ bool enabled = lp_server_multi_channel_support();
+#ifndef __ALLOW_MULTI_CHANNEL_SUPPORT
+ bool forced = false;
+ struct loadparm_context *lp_ctx = loadparm_init_s3(NULL, loadparm_s3_helpers());
+ bool unspecified = lpcfg_parm_is_unspecified(lp_ctx, "server multi channel support");
+ if (unspecified) {
+ enabled = false;
+ }
+ /*
+ * If we don't have support from the kernel
+ * to ask for the un-acked number of bytes
+ * in the socket send queue, we better
+ * don't support multi-channel.
+ */
+ forced = lp_parm_bool(-1, "force", "server multi channel support", false);
+ if (enabled && !forced) {
+ D_NOTICE("'server multi channel support' enabled "
+ "but not supported on %s (%s)\n",
+ SYSTEM_UNAME_SYSNAME, SYSTEM_UNAME_RELEASE);
+ DEBUGADD(DBGLVL_NOTICE, ("Please report this on "
+ "https://bugzilla.samba.org/show_bug.cgi?id=11897\n"));
+ enabled = false;
+ }
+ TALLOC_FREE(lp_ctx);
+#endif /* ! __ALLOW_MULTI_CHANNEL_SUPPORT */
+ return enabled;
+}
+
+static NTSTATUS smbXsrv_connection_get_rto_usecs(struct smbXsrv_connection *xconn,
+ uint32_t *_rto_usecs)
+{
+ /*
+ * Define an Retransmission Timeout
+ * of 1 second, if there's no way for the
+ * kernel to tell us the current value.
+ */
+ uint32_t rto_usecs = 1000000;
+
+#ifdef __HAVE_TCP_INFO_RTO
+ {
+ struct tcp_info info;
+ socklen_t ilen = sizeof(info);
+ int ret;
+
+ ZERO_STRUCT(info);
+ ret = getsockopt(xconn->transport.sock,
+ IPPROTO_TCP, TCP_INFO,
+ (void *)&info, &ilen);
+ if (ret != 0) {
+ int saved_errno = errno;
+ NTSTATUS status = map_nt_error_from_unix(errno);
+ DBG_ERR("getsockopt(TCP_INFO) errno[%d/%s] -s %s\n",
+ saved_errno, strerror(saved_errno),
+ nt_errstr(status));
+ return status;
+ }
+
+ DBG_DEBUG("tcpi_rto[%u] tcpi_rtt[%u] tcpi_rttvar[%u]\n",
+ (unsigned)info.tcpi_rto,
+ (unsigned)info.tcpi_rtt,
+ (unsigned)info.tcpi_rttvar);
+ rto_usecs = info.tcpi_rto;
+ }
+#endif /* __HAVE_TCP_INFO_RTO */
+
+ rto_usecs = MAX(rto_usecs, 200000); /* at least 0.2s */
+ rto_usecs = MIN(rto_usecs, 1000000); /* at max 1.0s */
+ *_rto_usecs = rto_usecs;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbXsrv_connection_get_acked_bytes(struct smbXsrv_connection *xconn,
+ uint64_t *_acked_bytes)
+{
+ /*
+ * Unless the kernel has an interface
+ * to reveal the number of un-acked bytes
+ * in the socket send queue, we'll assume
+ * everything is already acked.
+ *
+ * But that would mean that we better don't
+ * pretent to support multi-channel.
+ */
+ uint64_t unacked_bytes = 0;
+
+ *_acked_bytes = 0;
+
+ if (xconn->ack.force_unacked_timeout) {
+ /*
+ * Smbtorture tries to test channel failures...
+ * Just pretend nothing was acked...
+ */
+ DBG_INFO("Simulating channel failure: "
+ "xconn->ack.unacked_bytes[%llu]\n",
+ (unsigned long long)xconn->ack.unacked_bytes);
+ return NT_STATUS_OK;
+ }
+
+#ifdef __IOCTL_SEND_QUEUE_SIZE_OPCODE
+ {
+ int value = 0;
+ int ret;
+
+ /*
+ * If we have kernel support to get
+ * the number of bytes waiting in
+ * the socket's send queue, we
+ * use that in order to find out
+ * the number of unacked bytes.
+ */
+ ret = ioctl(xconn->transport.sock,
+ __IOCTL_SEND_QUEUE_SIZE_OPCODE,
+ &value);
+ if (ret != 0) {
+ int saved_errno = errno;
+ NTSTATUS status = map_nt_error_from_unix(saved_errno);
+ DBG_ERR("Failed to get the SEND_QUEUE_SIZE - "
+ "errno %d (%s) - %s\n",
+ saved_errno, strerror(saved_errno),
+ nt_errstr(status));
+ return status;
+ }
+
+ if (value < 0) {
+ DBG_ERR("xconn->ack.unacked_bytes[%llu] value[%d]\n",
+ (unsigned long long)xconn->ack.unacked_bytes,
+ value);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ unacked_bytes = value;
+ }
+#endif
+ if (xconn->ack.unacked_bytes == 0) {
+ xconn->ack.unacked_bytes = unacked_bytes;
+ return NT_STATUS_OK;
+ }
+
+ if (xconn->ack.unacked_bytes < unacked_bytes) {
+ DBG_ERR("xconn->ack.unacked_bytes[%llu] unacked_bytes[%llu]\n",
+ (unsigned long long)xconn->ack.unacked_bytes,
+ (unsigned long long)unacked_bytes);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ *_acked_bytes = xconn->ack.unacked_bytes - unacked_bytes;
+ xconn->ack.unacked_bytes = unacked_bytes;
+ return NT_STATUS_OK;
+}
+
+static void smbd_smb2_send_queue_ack_fail(struct smbd_smb2_send_queue **queue,
+ NTSTATUS status)
+{
+ struct smbd_smb2_send_queue *e = NULL;
+ struct smbd_smb2_send_queue *n = NULL;
+
+ for (e = *queue; e != NULL; e = n) {
+ n = e->next;
+
+ DLIST_REMOVE(*queue, e);
+ if (e->ack.req != NULL) {
+ tevent_req_nterror(e->ack.req, status);
+ }
+ }
+}
+
+static NTSTATUS smbd_smb2_send_queue_ack_bytes(struct smbd_smb2_send_queue **queue,
+ uint64_t acked_bytes)
+{
+ struct smbd_smb2_send_queue *e = NULL;
+ struct smbd_smb2_send_queue *n = NULL;
+
+ for (e = *queue; e != NULL; e = n) {
+ bool expired;
+
+ n = e->next;
+
+ if (e->ack.req == NULL) {
+ continue;
+ }
+
+ if (e->ack.required_acked_bytes <= acked_bytes) {
+ e->ack.required_acked_bytes = 0;
+ DLIST_REMOVE(*queue, e);
+ tevent_req_done(e->ack.req);
+ continue;
+ }
+ e->ack.required_acked_bytes -= acked_bytes;
+
+ expired = timeval_expired(&e->ack.timeout);
+ if (expired) {
+ return NT_STATUS_IO_TIMEOUT;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbd_smb2_check_ack_queue(struct smbXsrv_connection *xconn)
+{
+ uint64_t acked_bytes = 0;
+ NTSTATUS status;
+
+ status = smbXsrv_connection_get_acked_bytes(xconn, &acked_bytes);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = smbd_smb2_send_queue_ack_bytes(&xconn->ack.queue, acked_bytes);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = smbd_smb2_send_queue_ack_bytes(&xconn->smb2.send_queue, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static void smbXsrv_connection_ack_checker(struct tevent_req *subreq)
+{
+ struct smbXsrv_connection *xconn =
+ tevent_req_callback_data(subreq,
+ struct smbXsrv_connection);
+ struct smbXsrv_client *client = xconn->client;
+ struct timeval next_check;
+ NTSTATUS status;
+ bool ok;
+
+ xconn->ack.checker_subreq = NULL;
+
+ ok = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ smbd_server_connection_terminate(xconn,
+ "tevent_wakeup_recv() failed");
+ return;
+ }
+
+ status = smbd_smb2_check_ack_queue(xconn);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
+ return;
+ }
+
+ next_check = timeval_current_ofs_usec(xconn->ack.rto_usecs);
+ xconn->ack.checker_subreq = tevent_wakeup_send(xconn,
+ client->raw_ev_ctx,
+ next_check);
+ if (xconn->ack.checker_subreq == NULL) {
+ smbd_server_connection_terminate(xconn,
+ "tevent_wakeup_send() failed");
+ return;
+ }
+ tevent_req_set_callback(xconn->ack.checker_subreq,
+ smbXsrv_connection_ack_checker,
+ xconn);
+}
+
+static NTSTATUS smbXsrv_client_pending_breaks_updated(struct smbXsrv_client *client)
+{
+ struct smbXsrv_connection *xconn = NULL;
+
+ for (xconn = client->connections; xconn != NULL; xconn = xconn->next) {
+ struct timeval next_check;
+ uint64_t acked_bytes = 0;
+ NTSTATUS status;
+
+ /*
+ * A new 'pending break cycle' starts
+ * with a first pending break and lasts until
+ * all pending breaks are finished.
+ *
+ * This is typically a very short time,
+ * the value of one retransmission timeout.
+ */
+
+ if (client->pending_breaks == NULL) {
+ /*
+ * No more pending breaks, remove a pending
+ * checker timer
+ */
+ TALLOC_FREE(xconn->ack.checker_subreq);
+ continue;
+ }
+
+ if (xconn->ack.checker_subreq != NULL) {
+ /*
+ * The cycle already started =>
+ * nothing todo
+ */
+ continue;
+ }
+
+ /*
+ * Get the current retransmission timeout value.
+ *
+ * It may change over time, but fetching it once
+ * per 'pending break' cycled should be enough.
+ */
+ status = smbXsrv_connection_get_rto_usecs(xconn,
+ &xconn->ack.rto_usecs);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * At the start of the cycle we reset the
+ * unacked_bytes counter (first to 0 and
+ * within smbXsrv_connection_get_acked_bytes()
+ * to the current value in the kernel
+ * send queue.
+ */
+ xconn->ack.unacked_bytes = 0;
+ status = smbXsrv_connection_get_acked_bytes(xconn, &acked_bytes);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * We setup a timer in order to check for
+ * acked bytes after one retransmission timeout.
+ *
+ * The code that sets up the send_queue.ack.timeout
+ * uses a multiple of the retransmission timeout.
+ */
+ next_check = timeval_current_ofs_usec(xconn->ack.rto_usecs);
+ xconn->ack.checker_subreq = tevent_wakeup_send(xconn,
+ client->raw_ev_ctx,
+ next_check);
+ if (xconn->ack.checker_subreq == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_req_set_callback(xconn->ack.checker_subreq,
+ smbXsrv_connection_ack_checker,
+ xconn);
+ }
+
+ return NT_STATUS_OK;
+}
+
+void smbXsrv_connection_disconnect_transport(struct smbXsrv_connection *xconn,
+ NTSTATUS status)
+{
+ if (!NT_STATUS_IS_OK(xconn->transport.status)) {
+ return;
+ }
+
+ xconn->transport.status = status;
+ TALLOC_FREE(xconn->transport.fde);
+ if (xconn->transport.sock != -1) {
+ xconn->transport.sock = -1;
+ }
+ smbd_smb2_send_queue_ack_fail(&xconn->ack.queue, status);
+ smbd_smb2_send_queue_ack_fail(&xconn->smb2.send_queue, status);
+ xconn->smb2.send_queue_len = 0;
+ DO_PROFILE_INC(disconnect);
+}
+
+size_t smbXsrv_client_valid_connections(struct smbXsrv_client *client)
+{
+ struct smbXsrv_connection *xconn = NULL;
+ size_t num_ok = 0;
+
+ for (xconn = client->connections; xconn != NULL; xconn = xconn->next) {
+ if (NT_STATUS_IS_OK(xconn->transport.status)) {
+ num_ok++;
+ }
+ }
+
+ return num_ok;
+}
+
+struct smbXsrv_connection_shutdown_state {
+ struct smbXsrv_connection *xconn;
+};
+
+static void smbXsrv_connection_shutdown_wait_done(struct tevent_req *subreq);
+
+static struct tevent_req *smbXsrv_connection_shutdown_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbXsrv_connection *xconn)
+{
+ struct tevent_req *req = NULL;
+ struct smbXsrv_connection_shutdown_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+ size_t len = 0;
+ struct smbd_smb2_request *preq = NULL;
+ NTSTATUS status;
+
+ /*
+ * The caller should have called
+ * smbXsrv_connection_disconnect_transport() before.
+ */
+ SMB_ASSERT(!NT_STATUS_IS_OK(xconn->transport.status));
+ SMB_ASSERT(xconn->transport.terminating);
+ SMB_ASSERT(xconn->transport.shutdown_wait_queue == NULL);
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbXsrv_connection_shutdown_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->xconn = xconn;
+ tevent_req_defer_callback(req, ev);
+
+ xconn->transport.shutdown_wait_queue =
+ tevent_queue_create(state, "smbXsrv_connection_shutdown_queue");
+ if (tevent_req_nomem(xconn->transport.shutdown_wait_queue, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ for (preq = xconn->smb2.requests; preq != NULL; preq = preq->next) {
+ /*
+ * Now wait until the request is finished.
+ *
+ * We don't set a callback, as we just want to block the
+ * wait queue and the talloc_free() of the request will
+ * remove the item from the wait queue.
+ *
+ * Note that we don't cancel the requests here
+ * in order to keep the replay detection logic correct.
+ *
+ * However if we teardown the last channel of
+ * a connection, we'll call some logic via
+ * smbXsrv_session_disconnect_xconn()
+ * -> smbXsrv_session_disconnect_xconn_callback()
+ * -> smbXsrv_session_remove_channel()
+ * -> smb2srv_session_shutdown_send()
+ * will indeed cancel the request.
+ */
+ subreq = tevent_queue_wait_send(preq, ev,
+ xconn->transport.shutdown_wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ /*
+ * This may attach sessions with num_channels == 0
+ * to xconn->transport.shutdown_wait_queue.
+ */
+ status = smbXsrv_session_disconnect_xconn(xconn);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ len = tevent_queue_length(xconn->transport.shutdown_wait_queue);
+ if (len == 0) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * Now we add our own waiter to the end of the queue,
+ * this way we get notified when all pending requests are finished
+ * and send to the socket.
+ */
+ subreq = tevent_queue_wait_send(state, ev, xconn->transport.shutdown_wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smbXsrv_connection_shutdown_wait_done, req);
+
+ return req;
+}
+
+static void smbXsrv_connection_shutdown_wait_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smbXsrv_connection_shutdown_state *state =
+ tevent_req_data(req,
+ struct smbXsrv_connection_shutdown_state);
+ struct smbXsrv_connection *xconn = state->xconn;
+
+ tevent_queue_wait_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ tevent_req_done(req);
+ /*
+ * make sure the xconn pointer is still valid,
+ * it should as we used tevent_req_defer_callback()
+ */
+ SMB_ASSERT(xconn->transport.terminating);
+}
+
+static NTSTATUS smbXsrv_connection_shutdown_recv(struct tevent_req *req)
+{
+ struct smbXsrv_connection_shutdown_state *state =
+ tevent_req_data(req,
+ struct smbXsrv_connection_shutdown_state);
+ struct smbXsrv_connection *xconn = state->xconn;
+ /*
+ * make sure the xconn pointer is still valid,
+ * it should as we used tevent_req_defer_callback()
+ */
+ SMB_ASSERT(xconn->transport.terminating);
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static void smbd_server_connection_terminate_done(struct tevent_req *subreq)
+{
+ struct smbXsrv_connection *xconn =
+ tevent_req_callback_data(subreq,
+ struct smbXsrv_connection);
+ struct smbXsrv_client *client = xconn->client;
+ NTSTATUS status;
+
+ status = smbXsrv_connection_shutdown_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_server("smbXsrv_connection_shutdown_recv failed");
+ }
+
+ DLIST_REMOVE(client->connections, xconn);
+ TALLOC_FREE(xconn);
+}
+
+void smbd_server_connection_terminate_ex(struct smbXsrv_connection *xconn,
+ const char *reason,
+ const char *location)
+{
+ struct smbXsrv_client *client = xconn->client;
+ size_t num_ok = 0;
+
+ /*
+ * Make sure that no new request will be able to use this session.
+ *
+ * smbXsrv_connection_disconnect_transport() might be called already,
+ * but calling it again is a no-op.
+ */
+ smbXsrv_connection_disconnect_transport(xconn,
+ NT_STATUS_CONNECTION_DISCONNECTED);
+
+ num_ok = smbXsrv_client_valid_connections(client);
+
+ if (xconn->transport.terminating) {
+ DBG_DEBUG("skip recursion conn[%s] num_ok[%zu] reason[%s] at %s\n",
+ smbXsrv_connection_dbg(xconn), num_ok,
+ reason, location);
+ return;
+ }
+ xconn->transport.terminating = true;
+
+ DBG_DEBUG("conn[%s] num_ok[%zu] reason[%s] at %s\n",
+ smbXsrv_connection_dbg(xconn), num_ok,
+ reason, location);
+
+ if (xconn->has_cluster_movable_ip) {
+ /*
+ * If the connection has a movable cluster public address
+ * we disconnect all client connections,
+ * as the public address might be moved to
+ * a different node.
+ *
+ * In future we may recheck which node currently
+ * holds this address, but for now we keep it simple.
+ */
+ smbd_server_disconnect_client_ex(xconn->client,
+ reason,
+ location);
+ return;
+ }
+
+ if (num_ok != 0) {
+ struct tevent_req *subreq = NULL;
+
+ subreq = smbXsrv_connection_shutdown_send(client,
+ client->raw_ev_ctx,
+ xconn);
+ if (subreq == NULL) {
+ exit_server("smbXsrv_connection_shutdown_send failed");
+ }
+ tevent_req_set_callback(subreq,
+ smbd_server_connection_terminate_done,
+ xconn);
+ return;
+ }
+
+ /*
+ * The last connection was disconnected
+ */
+ exit_server_cleanly(reason);
+}
+
+void smbd_server_disconnect_client_ex(struct smbXsrv_client *client,
+ const char *reason,
+ const char *location)
+{
+ size_t num_ok = 0;
+
+ num_ok = smbXsrv_client_valid_connections(client);
+
+ DBG_WARNING("client[%s] num_ok[%zu] reason[%s] at %s\n",
+ client->global->remote_address, num_ok,
+ reason, location);
+
+ /*
+ * Something bad happened we need to disconnect all connections.
+ */
+ exit_server_cleanly(reason);
+}
+
+static bool dup_smb2_vec4(TALLOC_CTX *ctx,
+ struct iovec *outvec,
+ const struct iovec *srcvec)
+{
+ const uint8_t *srctf;
+ size_t srctf_len;
+ const uint8_t *srchdr;
+ size_t srchdr_len;
+ const uint8_t *srcbody;
+ size_t srcbody_len;
+ const uint8_t *expected_srcbody;
+ const uint8_t *srcdyn;
+ size_t srcdyn_len;
+ const uint8_t *expected_srcdyn;
+ uint8_t *dsttf;
+ uint8_t *dsthdr;
+ uint8_t *dstbody;
+ uint8_t *dstdyn;
+
+ srctf = (const uint8_t *)srcvec[SMBD_SMB2_TF_IOV_OFS].iov_base;
+ srctf_len = srcvec[SMBD_SMB2_TF_IOV_OFS].iov_len;
+ srchdr = (const uint8_t *)srcvec[SMBD_SMB2_HDR_IOV_OFS].iov_base;
+ srchdr_len = srcvec[SMBD_SMB2_HDR_IOV_OFS].iov_len;
+ srcbody = (const uint8_t *)srcvec[SMBD_SMB2_BODY_IOV_OFS].iov_base;
+ srcbody_len = srcvec[SMBD_SMB2_BODY_IOV_OFS].iov_len;
+ expected_srcbody = srchdr + SMB2_HDR_BODY;
+ srcdyn = (const uint8_t *)srcvec[SMBD_SMB2_DYN_IOV_OFS].iov_base;
+ srcdyn_len = srcvec[SMBD_SMB2_DYN_IOV_OFS].iov_len;
+ expected_srcdyn = srcbody + 8;
+
+ if ((srctf_len != SMB2_TF_HDR_SIZE) && (srctf_len != 0)) {
+ return false;
+ }
+
+ if (srchdr_len != SMB2_HDR_BODY) {
+ return false;
+ }
+
+ if (srctf_len == SMB2_TF_HDR_SIZE) {
+ dsttf = talloc_memdup(ctx, srctf, SMB2_TF_HDR_SIZE);
+ if (dsttf == NULL) {
+ return false;
+ }
+ } else {
+ dsttf = NULL;
+ }
+ outvec[SMBD_SMB2_TF_IOV_OFS].iov_base = (void *)dsttf;
+ outvec[SMBD_SMB2_TF_IOV_OFS].iov_len = srctf_len;
+
+ /* vec[SMBD_SMB2_HDR_IOV_OFS] is always boilerplate and must
+ * be allocated with size OUTVEC_ALLOC_SIZE. */
+
+ dsthdr = talloc_memdup(ctx, srchdr, OUTVEC_ALLOC_SIZE);
+ if (dsthdr == NULL) {
+ return false;
+ }
+ outvec[SMBD_SMB2_HDR_IOV_OFS].iov_base = (void *)dsthdr;
+ outvec[SMBD_SMB2_HDR_IOV_OFS].iov_len = SMB2_HDR_BODY;
+
+ /*
+ * If this is a "standard" vec[SMBD_SMB2_BOFY_IOV_OFS] of length 8,
+ * pointing to srcvec[SMBD_SMB2_HDR_IOV_OFS].iov_base + SMB2_HDR_BODY,
+ * then duplicate this. Else use talloc_memdup().
+ */
+
+ if ((srcbody == expected_srcbody) && (srcbody_len == 8)) {
+ dstbody = dsthdr + SMB2_HDR_BODY;
+ } else {
+ dstbody = talloc_memdup(ctx, srcbody, srcbody_len);
+ if (dstbody == NULL) {
+ return false;
+ }
+ }
+ outvec[SMBD_SMB2_BODY_IOV_OFS].iov_base = (void *)dstbody;
+ outvec[SMBD_SMB2_BODY_IOV_OFS].iov_len = srcbody_len;
+
+ /*
+ * If this is a "standard" vec[SMBD_SMB2_DYN_IOV_OFS] of length 1,
+ * pointing to
+ * srcvec[SMBD_SMB2_HDR_IOV_OFS].iov_base + 8
+ * then duplicate this. Else use talloc_memdup().
+ */
+
+ if ((srcdyn == expected_srcdyn) && (srcdyn_len == 1)) {
+ dstdyn = dsthdr + SMB2_HDR_BODY + 8;
+ } else if (srcdyn == NULL) {
+ dstdyn = NULL;
+ } else {
+ dstdyn = talloc_memdup(ctx, srcdyn, srcdyn_len);
+ if (dstdyn == NULL) {
+ return false;
+ }
+ }
+ outvec[SMBD_SMB2_DYN_IOV_OFS].iov_base = (void *)dstdyn;
+ outvec[SMBD_SMB2_DYN_IOV_OFS].iov_len = srcdyn_len;
+
+ return true;
+}
+
+static struct smbd_smb2_request *dup_smb2_req(const struct smbd_smb2_request *req)
+{
+ struct smbd_smb2_request *newreq = NULL;
+ struct iovec *outvec = NULL;
+ int count = req->out.vector_count;
+ int i;
+ bool ok;
+
+ newreq = smbd_smb2_request_allocate(req->xconn);
+ if (!newreq) {
+ return NULL;
+ }
+
+ newreq->session = req->session;
+ newreq->do_encryption = req->do_encryption;
+ newreq->do_signing = req->do_signing;
+ newreq->current_idx = req->current_idx;
+
+ outvec = talloc_zero_array(newreq, struct iovec, count);
+ if (!outvec) {
+ TALLOC_FREE(newreq);
+ return NULL;
+ }
+ newreq->out.vector = outvec;
+ newreq->out.vector_count = count;
+
+ /* Setup the outvec's identically to req. */
+ outvec[0].iov_base = newreq->out.nbt_hdr;
+ outvec[0].iov_len = 4;
+ memcpy(newreq->out.nbt_hdr, req->out.nbt_hdr, 4);
+
+ /* Setup the vectors identically to the ones in req. */
+ for (i = 1; i < count; i += SMBD_SMB2_NUM_IOV_PER_REQ) {
+ if (!dup_smb2_vec4(outvec, &outvec[i], &req->out.vector[i])) {
+ break;
+ }
+ }
+
+ if (i < count) {
+ /* Alloc failed. */
+ TALLOC_FREE(newreq);
+ return NULL;
+ }
+
+ ok = smb2_setup_nbt_length(newreq->out.vector,
+ newreq->out.vector_count);
+ if (!ok) {
+ TALLOC_FREE(newreq);
+ return NULL;
+ }
+
+ return newreq;
+}
+
+static NTSTATUS smb2_send_async_interim_response(const struct smbd_smb2_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ int first_idx = 1;
+ struct iovec *firsttf = NULL;
+ struct iovec *outhdr_v = NULL;
+ uint8_t *outhdr = NULL;
+ struct smbd_smb2_request *nreq = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ /* Create a new smb2 request we'll use
+ for the interim return. */
+ nreq = dup_smb2_req(req);
+ if (!nreq) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Lose the last X out vectors. They're the
+ ones we'll be using for the async reply. */
+ nreq->out.vector_count -= SMBD_SMB2_NUM_IOV_PER_REQ;
+
+ ok = smb2_setup_nbt_length(nreq->out.vector,
+ nreq->out.vector_count);
+ if (!ok) {
+ return NT_STATUS_INVALID_PARAMETER_MIX;
+ }
+
+ /* Step back to the previous reply. */
+ nreq->current_idx -= SMBD_SMB2_NUM_IOV_PER_REQ;
+ firsttf = SMBD_SMB2_IDX_TF_IOV(nreq,out,first_idx);
+ outhdr_v = SMBD_SMB2_OUT_HDR_IOV(nreq);
+ outhdr = SMBD_SMB2_OUT_HDR_PTR(nreq);
+ /* And end the chain. */
+ SIVAL(outhdr, SMB2_HDR_NEXT_COMMAND, 0);
+
+ /* Calculate outgoing credits */
+ smb2_calculate_credits(req, nreq);
+
+ if (DEBUGLEVEL >= 10) {
+ dbgtext("smb2_send_async_interim_response: nreq->current_idx = %u\n",
+ (unsigned int)nreq->current_idx );
+ dbgtext("smb2_send_async_interim_response: returning %u vectors\n",
+ (unsigned int)nreq->out.vector_count );
+ print_req_vectors(nreq);
+ }
+
+ /*
+ * As we have changed the header (SMB2_HDR_NEXT_COMMAND),
+ * we need to sign/encrypt here with the last/first key we remembered
+ */
+ if (firsttf->iov_len == SMB2_TF_HDR_SIZE) {
+ status = smb2_signing_encrypt_pdu(req->first_enc_key,
+ firsttf,
+ nreq->out.vector_count - first_idx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ } else if (smb2_signing_key_valid(req->last_sign_key)) {
+ status = smb2_signing_sign_pdu(req->last_sign_key,
+ outhdr_v,
+ SMBD_SMB2_NUM_IOV_PER_REQ - 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ nreq->queue_entry.mem_ctx = nreq;
+ nreq->queue_entry.vector = nreq->out.vector;
+ nreq->queue_entry.count = nreq->out.vector_count;
+ DLIST_ADD_END(xconn->smb2.send_queue, &nreq->queue_entry);
+ xconn->smb2.send_queue_len++;
+
+ status = smbd_smb2_flush_send_queue(xconn);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+struct smbd_smb2_request_pending_state {
+ struct smbd_smb2_send_queue queue_entry;
+ uint8_t buf[NBT_HDR_SIZE + SMB2_TF_HDR_SIZE + SMB2_HDR_BODY + 0x08 + 1];
+ struct iovec vector[1 + SMBD_SMB2_NUM_IOV_PER_REQ];
+};
+
+static void smbd_smb2_request_pending_timer(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data);
+
+NTSTATUS smbd_smb2_request_pending_queue(struct smbd_smb2_request *req,
+ struct tevent_req *subreq,
+ uint32_t defer_time)
+{
+ NTSTATUS status;
+ struct timeval defer_endtime;
+ uint8_t *outhdr = NULL;
+ uint32_t flags;
+
+ if (!tevent_req_is_in_progress(subreq)) {
+ /*
+ * This is a performance optimization,
+ * it avoids one tevent_loop iteration,
+ * which means we avoid one
+ * talloc_stackframe_pool/talloc_free pair.
+ */
+ tevent_req_notify_callback(subreq);
+ return NT_STATUS_OK;
+ }
+
+ req->subreq = subreq;
+ subreq = NULL;
+
+ if (req->async_te) {
+ /* We're already async. */
+ return NT_STATUS_OK;
+ }
+
+ outhdr = SMBD_SMB2_OUT_HDR_PTR(req);
+ flags = IVAL(outhdr, SMB2_HDR_FLAGS);
+ if (flags & SMB2_HDR_FLAG_ASYNC) {
+ /* We're already async. */
+ return NT_STATUS_OK;
+ }
+
+ if (req->async_internal || defer_time == 0) {
+ /*
+ * An SMB2 request implementation wants to handle the request
+ * asynchronously "internally" while keeping synchronous
+ * behaviour for the SMB2 request. This means we don't send an
+ * interim response and we can allow processing of compound SMB2
+ * requests (cf the subsequent check) for all cases.
+ */
+ return NT_STATUS_OK;
+ }
+
+ if (req->in.vector_count > req->current_idx + SMBD_SMB2_NUM_IOV_PER_REQ) {
+ /*
+ * We're trying to go async in a compound request
+ * chain. This is only allowed for opens that cause an
+ * oplock break or for the last operation in the
+ * chain, otherwise it is not allowed. See
+ * [MS-SMB2].pdf note <206> on Section 3.3.5.2.7.
+ */
+ const uint8_t *inhdr = SMBD_SMB2_IN_HDR_PTR(req);
+
+ if (SVAL(inhdr, SMB2_HDR_OPCODE) != SMB2_OP_CREATE) {
+ /*
+ * Cancel the outstanding request.
+ */
+ bool ok = tevent_req_cancel(req->subreq);
+ if (ok) {
+ return NT_STATUS_OK;
+ }
+ TALLOC_FREE(req->subreq);
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INTERNAL_ERROR);
+ }
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ dbgtext("smbd_smb2_request_pending_queue: req->current_idx = %u\n",
+ (unsigned int)req->current_idx );
+ print_req_vectors(req);
+ }
+
+ if (req->current_idx > 1) {
+ /*
+ * We're going async in a compound
+ * chain after the first request has
+ * already been processed. Send an
+ * interim response containing the
+ * set of replies already generated.
+ */
+ int idx = req->current_idx;
+
+ status = smb2_send_async_interim_response(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ TALLOC_FREE(req->first_enc_key);
+
+ req->current_idx = 1;
+
+ /*
+ * Re-arrange the in.vectors to remove what
+ * we just sent.
+ */
+ memmove(&req->in.vector[1],
+ &req->in.vector[idx],
+ sizeof(req->in.vector[0])*(req->in.vector_count - idx));
+ req->in.vector_count = 1 + (req->in.vector_count - idx);
+
+ /* Re-arrange the out.vectors to match. */
+ memmove(&req->out.vector[1],
+ &req->out.vector[idx],
+ sizeof(req->out.vector[0])*(req->out.vector_count - idx));
+ req->out.vector_count = 1 + (req->out.vector_count - idx);
+
+ if (req->in.vector_count == 1 + SMBD_SMB2_NUM_IOV_PER_REQ) {
+ /*
+ * We only have one remaining request as
+ * we've processed everything else.
+ * This is no longer a compound request.
+ */
+ req->compound_related = false;
+ outhdr = SMBD_SMB2_OUT_HDR_PTR(req);
+ flags = (IVAL(outhdr, SMB2_HDR_FLAGS) & ~SMB2_HDR_FLAG_CHAINED);
+ SIVAL(outhdr, SMB2_HDR_FLAGS, flags);
+ }
+ }
+ TALLOC_FREE(req->last_sign_key);
+
+ /*
+ * smbd_smb2_request_pending_timer() just send a packet
+ * to the client and doesn't need any impersonation.
+ * So we use req->xconn->client->raw_ev_ctx instead
+ * of req->ev_ctx here.
+ */
+ defer_endtime = timeval_current_ofs_usec(defer_time);
+ req->async_te = tevent_add_timer(req->xconn->client->raw_ev_ctx,
+ req, defer_endtime,
+ smbd_smb2_request_pending_timer,
+ req);
+ if (req->async_te == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static
+struct smb2_signing_key *smbd_smb2_signing_key(struct smbXsrv_session *session,
+ struct smbXsrv_connection *xconn,
+ bool *_has_channel)
+{
+ struct smbXsrv_channel_global0 *c = NULL;
+ NTSTATUS status;
+ struct smb2_signing_key *key = NULL;
+ bool has_channel = false;
+
+ status = smbXsrv_session_find_channel(session, xconn, &c);
+ if (NT_STATUS_IS_OK(status)) {
+ key = c->signing_key;
+ has_channel = true;
+ }
+
+ if (!smb2_signing_key_valid(key)) {
+ key = session->global->signing_key;
+ has_channel = false;
+ }
+
+ if (_has_channel != NULL) {
+ *_has_channel = has_channel;
+ }
+
+ return key;
+}
+
+static NTSTATUS smb2_get_new_nonce(struct smbXsrv_session *session,
+ uint64_t *new_nonce_high,
+ uint64_t *new_nonce_low)
+{
+ uint64_t nonce_high;
+ uint64_t nonce_low;
+
+ session->nonce_low += 1;
+ if (session->nonce_low == 0) {
+ session->nonce_low += 1;
+ session->nonce_high += 1;
+ }
+
+ /*
+ * CCM and GCM algorithms must never have their
+ * nonce wrap, or the security of the whole
+ * communication and the keys is destroyed.
+ * We must drop the connection once we have
+ * transferred too much data.
+ *
+ * NOTE: We assume nonces greater than 8 bytes.
+ */
+ if (session->nonce_high >= session->nonce_high_max) {
+ return NT_STATUS_ENCRYPTION_FAILED;
+ }
+
+ nonce_high = session->nonce_high_random;
+ nonce_high += session->nonce_high;
+ nonce_low = session->nonce_low;
+
+ *new_nonce_high = nonce_high;
+ *new_nonce_low = nonce_low;
+ return NT_STATUS_OK;
+}
+
+static void smbd_smb2_request_pending_timer(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data)
+{
+ struct smbd_smb2_request *req =
+ talloc_get_type_abort(private_data,
+ struct smbd_smb2_request);
+ struct smbXsrv_connection *xconn = req->xconn;
+ struct smbd_smb2_request_pending_state *state = NULL;
+ uint8_t *outhdr = NULL;
+ const uint8_t *inhdr = NULL;
+ uint8_t *tf = NULL;
+ uint8_t *hdr = NULL;
+ uint8_t *body = NULL;
+ uint8_t *dyn = NULL;
+ uint32_t flags = 0;
+ uint64_t message_id = 0;
+ uint64_t async_id = 0;
+ NTSTATUS status;
+ bool ok;
+
+ TALLOC_FREE(req->async_te);
+
+ /* Ensure our final reply matches the interim one. */
+ inhdr = SMBD_SMB2_IN_HDR_PTR(req);
+ outhdr = SMBD_SMB2_OUT_HDR_PTR(req);
+ flags = IVAL(outhdr, SMB2_HDR_FLAGS);
+ message_id = BVAL(outhdr, SMB2_HDR_MESSAGE_ID);
+
+ async_id = message_id; /* keep it simple for now... */
+
+ SIVAL(outhdr, SMB2_HDR_FLAGS, flags | SMB2_HDR_FLAG_ASYNC);
+ SBVAL(outhdr, SMB2_HDR_ASYNC_ID, async_id);
+
+ DEBUG(10,("smbd_smb2_request_pending_queue: opcode[%s] mid %llu "
+ "going async\n",
+ smb2_opcode_name(SVAL(inhdr, SMB2_HDR_OPCODE)),
+ (unsigned long long)async_id ));
+
+ /*
+ * What we send is identical to a smbd_smb2_request_error
+ * packet with an error status of STATUS_PENDING. Make use
+ * of this fact sometime when refactoring. JRA.
+ */
+
+ state = talloc_zero(req->xconn, struct smbd_smb2_request_pending_state);
+ if (state == NULL) {
+ smbd_server_connection_terminate(xconn,
+ nt_errstr(NT_STATUS_NO_MEMORY));
+ return;
+ }
+
+ tf = state->buf + NBT_HDR_SIZE;
+
+ hdr = tf + SMB2_TF_HDR_SIZE;
+ body = hdr + SMB2_HDR_BODY;
+ dyn = body + 8;
+
+ if (req->do_encryption) {
+ uint64_t nonce_high = 0;
+ uint64_t nonce_low = 0;
+ uint64_t session_id = req->session->global->session_wire_id;
+
+ status = smb2_get_new_nonce(req->session,
+ &nonce_high,
+ &nonce_low);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(xconn,
+ nt_errstr(status));
+ return;
+ }
+
+ SIVAL(tf, SMB2_TF_PROTOCOL_ID, SMB2_TF_MAGIC);
+ SBVAL(tf, SMB2_TF_NONCE+0, nonce_low);
+ SBVAL(tf, SMB2_TF_NONCE+8, nonce_high);
+ SBVAL(tf, SMB2_TF_SESSION_ID, session_id);
+ }
+
+ SIVAL(hdr, SMB2_HDR_PROTOCOL_ID, SMB2_MAGIC);
+ SSVAL(hdr, SMB2_HDR_LENGTH, SMB2_HDR_BODY);
+ SSVAL(hdr, SMB2_HDR_EPOCH, 0);
+ SIVAL(hdr, SMB2_HDR_STATUS, NT_STATUS_V(NT_STATUS_PENDING));
+ SSVAL(hdr, SMB2_HDR_OPCODE, SVAL(outhdr, SMB2_HDR_OPCODE));
+
+ /*
+ * The STATUS_PENDING response has SMB2_HDR_FLAG_SIGNED
+ * clearedm, but echoes the signature field.
+ */
+ flags &= ~SMB2_HDR_FLAG_SIGNED;
+ SIVAL(hdr, SMB2_HDR_FLAGS, flags);
+ SIVAL(hdr, SMB2_HDR_NEXT_COMMAND, 0);
+ SBVAL(hdr, SMB2_HDR_MESSAGE_ID, message_id);
+ SBVAL(hdr, SMB2_HDR_PID, async_id);
+ SBVAL(hdr, SMB2_HDR_SESSION_ID,
+ BVAL(outhdr, SMB2_HDR_SESSION_ID));
+ memcpy(hdr+SMB2_HDR_SIGNATURE,
+ outhdr+SMB2_HDR_SIGNATURE, 16);
+
+ SSVAL(body, 0x00, 0x08 + 1);
+
+ SCVAL(body, 0x02, 0);
+ SCVAL(body, 0x03, 0);
+ SIVAL(body, 0x04, 0);
+ /* Match W2K8R2... */
+ SCVAL(dyn, 0x00, 0x21);
+
+ state->vector[0].iov_base = (void *)state->buf;
+ state->vector[0].iov_len = NBT_HDR_SIZE;
+
+ if (req->do_encryption) {
+ state->vector[1+SMBD_SMB2_TF_IOV_OFS].iov_base = tf;
+ state->vector[1+SMBD_SMB2_TF_IOV_OFS].iov_len =
+ SMB2_TF_HDR_SIZE;
+ } else {
+ state->vector[1+SMBD_SMB2_TF_IOV_OFS].iov_base = NULL;
+ state->vector[1+SMBD_SMB2_TF_IOV_OFS].iov_len = 0;
+ }
+
+ state->vector[1+SMBD_SMB2_HDR_IOV_OFS].iov_base = hdr;
+ state->vector[1+SMBD_SMB2_HDR_IOV_OFS].iov_len = SMB2_HDR_BODY;
+
+ state->vector[1+SMBD_SMB2_BODY_IOV_OFS].iov_base = body;
+ state->vector[1+SMBD_SMB2_BODY_IOV_OFS].iov_len = 8;
+
+ state->vector[1+SMBD_SMB2_DYN_IOV_OFS].iov_base = dyn;
+ state->vector[1+SMBD_SMB2_DYN_IOV_OFS].iov_len = 1;
+
+ ok = smb2_setup_nbt_length(state->vector,
+ 1 + SMBD_SMB2_NUM_IOV_PER_REQ);
+ if (!ok) {
+ smbd_server_connection_terminate(
+ xconn, nt_errstr(NT_STATUS_INTERNAL_ERROR));
+ return;
+ }
+
+ /* Ensure we correctly go through crediting. Grant
+ the credits now, and zero credits on the final
+ response. */
+ smb2_set_operation_credit(req->xconn,
+ SMBD_SMB2_IN_HDR_IOV(req),
+ &state->vector[1+SMBD_SMB2_HDR_IOV_OFS]);
+
+ /*
+ * We add SMB2_HDR_FLAG_ASYNC after smb2_set_operation_credit()
+ * as it reacts on it
+ */
+ SIVAL(hdr, SMB2_HDR_FLAGS, flags | SMB2_HDR_FLAG_ASYNC);
+
+ if (DEBUGLVL(10)) {
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(state->vector); i++) {
+ dbgtext("\tstate->vector[%u/%u].iov_len = %u\n",
+ (unsigned int)i,
+ (unsigned int)ARRAY_SIZE(state->vector),
+ (unsigned int)state->vector[i].iov_len);
+ }
+ }
+
+ if (req->do_encryption) {
+ struct smbXsrv_session *x = req->session;
+ struct smb2_signing_key *encryption_key = x->global->encryption_key;
+
+ status = smb2_signing_encrypt_pdu(encryption_key,
+ &state->vector[1+SMBD_SMB2_TF_IOV_OFS],
+ SMBD_SMB2_NUM_IOV_PER_REQ);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(xconn,
+ nt_errstr(status));
+ return;
+ }
+ }
+
+ state->queue_entry.mem_ctx = state;
+ state->queue_entry.vector = state->vector;
+ state->queue_entry.count = ARRAY_SIZE(state->vector);
+ DLIST_ADD_END(xconn->smb2.send_queue, &state->queue_entry);
+ xconn->smb2.send_queue_len++;
+
+ status = smbd_smb2_flush_send_queue(xconn);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(xconn,
+ nt_errstr(status));
+ return;
+ }
+}
+
+static NTSTATUS smbd_smb2_request_process_cancel(struct smbd_smb2_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ struct smbd_smb2_request *cur;
+ const uint8_t *inhdr;
+ uint32_t flags;
+ uint64_t search_message_id;
+ uint64_t search_async_id;
+ uint64_t found_id = 0;
+
+ inhdr = SMBD_SMB2_IN_HDR_PTR(req);
+
+ flags = IVAL(inhdr, SMB2_HDR_FLAGS);
+ search_message_id = BVAL(inhdr, SMB2_HDR_MESSAGE_ID);
+ search_async_id = BVAL(inhdr, SMB2_HDR_PID);
+
+ /*
+ * We don't need the request anymore cancel requests never
+ * have a response.
+ *
+ * We defer the TALLOC_FREE(req) to the caller.
+ */
+ DLIST_REMOVE(xconn->smb2.requests, req);
+
+ for (cur = xconn->smb2.requests; cur; cur = cur->next) {
+ const uint8_t *outhdr;
+ uint64_t message_id;
+ uint64_t async_id;
+
+ if (cur->session != req->session) {
+ continue;
+ }
+
+ if (cur->compound_related) {
+ /*
+ * Never cancel anything in a compound request.
+ * Way too hard to deal with the result.
+ */
+ continue;
+ }
+
+ outhdr = SMBD_SMB2_OUT_HDR_PTR(cur);
+
+ message_id = BVAL(outhdr, SMB2_HDR_MESSAGE_ID);
+ async_id = BVAL(outhdr, SMB2_HDR_PID);
+
+ if (flags & SMB2_HDR_FLAG_ASYNC) {
+ if (search_async_id == async_id) {
+ found_id = async_id;
+ break;
+ }
+ } else {
+ if (search_message_id == message_id) {
+ found_id = message_id;
+ break;
+ }
+ }
+ }
+
+ if (cur && cur->subreq) {
+ inhdr = SMBD_SMB2_IN_HDR_PTR(cur);
+ DEBUG(10,("smbd_smb2_request_process_cancel: attempting to "
+ "cancel opcode[%s] mid %llu\n",
+ smb2_opcode_name(SVAL(inhdr, SMB2_HDR_OPCODE)),
+ (unsigned long long)found_id ));
+ tevent_req_cancel(cur->subreq);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*************************************************************
+ Ensure an incoming tid is a valid one for us to access.
+ Change to the associated uid credentials and chdir to the
+ valid tid directory.
+*************************************************************/
+
+static NTSTATUS smbd_smb2_request_check_tcon(struct smbd_smb2_request *req)
+{
+ const uint8_t *inhdr;
+ uint32_t in_flags;
+ uint32_t in_tid;
+ struct smbXsrv_tcon *tcon;
+ NTSTATUS status;
+ NTTIME now = timeval_to_nttime(&req->request_time);
+
+ req->tcon = NULL;
+
+ inhdr = SMBD_SMB2_IN_HDR_PTR(req);
+
+ in_flags = IVAL(inhdr, SMB2_HDR_FLAGS);
+ in_tid = IVAL(inhdr, SMB2_HDR_TID);
+
+ if (in_flags & SMB2_HDR_FLAG_CHAINED) {
+ in_tid = req->last_tid;
+ }
+
+ req->last_tid = 0;
+
+ status = smb2srv_tcon_lookup(req->session,
+ in_tid, now, &tcon);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!change_to_user_and_service(
+ tcon->compat,
+ req->session->global->session_wire_id))
+ {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ req->tcon = tcon;
+ req->last_tid = in_tid;
+
+ return NT_STATUS_OK;
+}
+
+/*************************************************************
+ Ensure an incoming session_id is a valid one for us to access.
+*************************************************************/
+
+static NTSTATUS smbd_smb2_request_check_session(struct smbd_smb2_request *req)
+{
+ const uint8_t *inhdr;
+ uint32_t in_flags;
+ uint16_t in_opcode;
+ uint64_t in_session_id;
+ struct smbXsrv_session *session = NULL;
+ struct auth_session_info *session_info;
+ NTSTATUS status;
+ NTTIME now = timeval_to_nttime(&req->request_time);
+
+ req->session = NULL;
+ req->tcon = NULL;
+
+ inhdr = SMBD_SMB2_IN_HDR_PTR(req);
+
+ in_flags = IVAL(inhdr, SMB2_HDR_FLAGS);
+ in_opcode = SVAL(inhdr, SMB2_HDR_OPCODE);
+ in_session_id = BVAL(inhdr, SMB2_HDR_SESSION_ID);
+
+ if (in_flags & SMB2_HDR_FLAG_CHAINED) {
+ in_session_id = req->last_session_id;
+ }
+
+ req->last_session_id = 0;
+
+ /* look an existing session up */
+ switch (in_opcode) {
+ case SMB2_OP_SESSSETUP:
+ /*
+ * For a session bind request, we don't have the
+ * channel set up at this point yet, so we defer
+ * the verification that the connection belongs
+ * to the session to the session setup code, which
+ * can look at the session binding flags.
+ */
+ status = smb2srv_session_lookup_client(req->xconn->client,
+ in_session_id, now,
+ &session);
+ break;
+ default:
+ status = smb2srv_session_lookup_conn(req->xconn,
+ in_session_id, now,
+ &session);
+ break;
+ }
+ if (session) {
+ req->session = session;
+ req->last_session_id = in_session_id;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_USER_SESSION_DELETED)) {
+ switch (in_opcode) {
+ case SMB2_OP_SESSSETUP:
+ status = smb2srv_session_lookup_global(req->xconn->client,
+ in_session_id,
+ req,
+ &session);
+ if (NT_STATUS_IS_OK(status)) {
+ /*
+ * We fallback to a session of
+ * another process in order to
+ * get the signing correct.
+ *
+ * We don't set req->last_session_id here.
+ */
+ req->session = session;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_SESSION_EXPIRED)) {
+ switch (in_opcode) {
+ case SMB2_OP_SESSSETUP:
+ status = NT_STATUS_OK;
+ break;
+ case SMB2_OP_LOGOFF:
+ case SMB2_OP_CLOSE:
+ case SMB2_OP_LOCK:
+ case SMB2_OP_CANCEL:
+ case SMB2_OP_KEEPALIVE:
+ /*
+ * [MS-SMB2] 3.3.5.2.9 Verifying the Session
+ * specifies that LOGOFF, CLOSE and (UN)LOCK
+ * should always be processed even on expired sessions.
+ *
+ * Also see the logic in
+ * smbd_smb2_request_process_lock().
+ *
+ * The smb2.session.expire2 test shows that
+ * CANCEL and KEEPALIVE/ECHO should also
+ * be processed.
+ */
+ status = NT_STATUS_OK;
+ break;
+ default:
+ break;
+ }
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ switch (in_opcode) {
+ case SMB2_OP_TCON:
+ case SMB2_OP_CREATE:
+ case SMB2_OP_GETINFO:
+ case SMB2_OP_SETINFO:
+ return NT_STATUS_INVALID_HANDLE;
+ default:
+ /*
+ * Notice the check for
+ * (session_info == NULL)
+ * below.
+ */
+ status = NT_STATUS_OK;
+ break;
+ }
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ session_info = session->global->auth_session_info;
+ if (session_info == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smbd_smb2_request_verify_creditcharge(struct smbd_smb2_request *req,
+ uint32_t data_length)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ uint16_t needed_charge;
+ uint16_t credit_charge = 1;
+ const uint8_t *inhdr;
+
+ inhdr = SMBD_SMB2_IN_HDR_PTR(req);
+
+ if (xconn->smb2.credits.multicredit) {
+ credit_charge = SVAL(inhdr, SMB2_HDR_CREDIT_CHARGE);
+ credit_charge = MAX(credit_charge, 1);
+ }
+
+ needed_charge = (data_length - 1)/ 65536 + 1;
+
+ DBGC_DEBUG(DBGC_SMB2_CREDITS,
+ "mid %llu, CreditCharge: %d, NeededCharge: %d\n",
+ (unsigned long long) BVAL(inhdr, SMB2_HDR_MESSAGE_ID),
+ credit_charge, needed_charge);
+
+ if (needed_charge > credit_charge) {
+ DBGC_WARNING(DBGC_SMB2_CREDITS,
+ "CreditCharge too low, given %d, needed %d\n",
+ credit_charge, needed_charge);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smbd_smb2_request_verify_sizes(struct smbd_smb2_request *req,
+ size_t expected_body_size)
+{
+ struct iovec *inhdr_v;
+ const uint8_t *inhdr;
+ uint16_t opcode;
+ const uint8_t *inbody;
+ size_t body_size;
+ size_t min_dyn_size = expected_body_size & 0x00000001;
+ int max_idx = req->in.vector_count - SMBD_SMB2_NUM_IOV_PER_REQ;
+
+ /*
+ * The following should be checked already.
+ */
+ if (req->in.vector_count < SMBD_SMB2_NUM_IOV_PER_REQ) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ if (req->current_idx > max_idx) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ inhdr_v = SMBD_SMB2_IN_HDR_IOV(req);
+ if (inhdr_v->iov_len != SMB2_HDR_BODY) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ if (SMBD_SMB2_IN_BODY_LEN(req) < 2) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ inhdr = SMBD_SMB2_IN_HDR_PTR(req);
+ opcode = SVAL(inhdr, SMB2_HDR_OPCODE);
+
+ switch (opcode) {
+ case SMB2_OP_IOCTL:
+ case SMB2_OP_GETINFO:
+ case SMB2_OP_WRITE:
+ min_dyn_size = 0;
+ break;
+ }
+
+ /*
+ * Now check the expected body size,
+ * where the last byte might be in the
+ * dynamic section..
+ */
+ if (SMBD_SMB2_IN_BODY_LEN(req) != (expected_body_size & 0xFFFFFFFE)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ if (SMBD_SMB2_IN_DYN_LEN(req) < min_dyn_size) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ body_size = SVAL(inbody, 0x00);
+ if (body_size != expected_body_size) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ return NT_STATUS_OK;
+}
+
+bool smbXsrv_is_encrypted(uint8_t encryption_flags)
+{
+ return (!(encryption_flags & SMBXSRV_PROCESSED_UNENCRYPTED_PACKET)
+ &&
+ (encryption_flags & (SMBXSRV_PROCESSED_ENCRYPTED_PACKET |
+ SMBXSRV_ENCRYPTION_DESIRED |
+ SMBXSRV_ENCRYPTION_REQUIRED)));
+}
+
+bool smbXsrv_is_partially_encrypted(uint8_t encryption_flags)
+{
+ return ((encryption_flags & SMBXSRV_PROCESSED_ENCRYPTED_PACKET) &&
+ (encryption_flags & SMBXSRV_PROCESSED_UNENCRYPTED_PACKET));
+}
+
+/* Set a flag if not already set, return true if set */
+bool smbXsrv_set_crypto_flag(uint8_t *flags, uint8_t flag)
+{
+ if ((flag == 0) || (*flags & flag)) {
+ return false;
+ }
+
+ *flags |= flag;
+ return true;
+}
+
+/*
+ * Update encryption state tracking flags, this can be used to
+ * determine whether whether the session or tcon is "encrypted".
+ */
+static void smb2srv_update_crypto_flags(struct smbd_smb2_request *req,
+ uint16_t opcode,
+ bool *update_session_globalp,
+ bool *update_tcon_globalp)
+{
+ /* Default: assume unecrypted and unsigned */
+ struct smbXsrv_session *session = req->session;
+ struct smbXsrv_tcon *tcon = req->tcon;
+ uint8_t encrypt_flag = SMBXSRV_PROCESSED_UNENCRYPTED_PACKET;
+ uint8_t sign_flag = SMBXSRV_PROCESSED_UNSIGNED_PACKET;
+ bool update_session = false;
+ bool update_tcon = false;
+
+ if (session->table == NULL) {
+ /*
+ * sessions from smb2srv_session_lookup_global()
+ * have NT_STATUS_BAD_LOGON_SESSION_STATE
+ * and session->table == NULL.
+ *
+ * They only used to give the correct error
+ * status, we should not update any state.
+ */
+ goto out;
+ }
+
+ if (req->was_encrypted && req->do_encryption) {
+ encrypt_flag = SMBXSRV_PROCESSED_ENCRYPTED_PACKET;
+ sign_flag = SMBXSRV_PROCESSED_SIGNED_PACKET;
+ } else {
+ /* Unencrypted packet, can be signed */
+ if (req->do_signing) {
+ sign_flag = SMBXSRV_PROCESSED_SIGNED_PACKET;
+ }
+ }
+
+ update_session |= smbXsrv_set_crypto_flag(
+ &session->global->encryption_flags, encrypt_flag);
+ update_session |= smbXsrv_set_crypto_flag(
+ &session->global->signing_flags, sign_flag);
+
+ if (tcon) {
+ update_tcon |= smbXsrv_set_crypto_flag(
+ &tcon->global->encryption_flags, encrypt_flag);
+ update_tcon |= smbXsrv_set_crypto_flag(
+ &tcon->global->signing_flags, sign_flag);
+ }
+
+out:
+ *update_session_globalp = update_session;
+ *update_tcon_globalp = update_tcon;
+ return;
+}
+
+bool smbXsrv_is_signed(uint8_t signing_flags)
+{
+ /*
+ * Signing is always enabled, so unless we got an unsigned
+ * packet and at least one signed packet that was not
+ * encrypted, the session or tcon is "signed".
+ */
+ return (!(signing_flags & SMBXSRV_PROCESSED_UNSIGNED_PACKET) &&
+ (signing_flags & SMBXSRV_PROCESSED_SIGNED_PACKET));
+}
+
+bool smbXsrv_is_partially_signed(uint8_t signing_flags)
+{
+ return ((signing_flags & SMBXSRV_PROCESSED_UNSIGNED_PACKET) &&
+ (signing_flags & SMBXSRV_PROCESSED_SIGNED_PACKET));
+}
+
+static NTSTATUS smbd_smb2_request_dispatch_update_counts(
+ struct smbd_smb2_request *req,
+ bool modify_call)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ const uint8_t *inhdr;
+ uint16_t channel_sequence;
+ uint8_t generation_wrap = 0;
+ uint32_t flags;
+ int cmp;
+ struct smbXsrv_open *op;
+ bool update_open = false;
+ NTSTATUS status = NT_STATUS_OK;
+
+ SMB_ASSERT(!req->request_counters_updated);
+
+ if (xconn->protocol < PROTOCOL_SMB3_00) {
+ return NT_STATUS_OK;
+ }
+
+ if (req->compat_chain_fsp == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ op = req->compat_chain_fsp->op;
+ if (op == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ inhdr = SMBD_SMB2_IN_HDR_PTR(req);
+ flags = IVAL(inhdr, SMB2_HDR_FLAGS);
+ channel_sequence = SVAL(inhdr, SMB2_HDR_CHANNEL_SEQUENCE);
+
+ cmp = channel_sequence - op->global->channel_sequence;
+ if (cmp < 0) {
+ /*
+ * csn wrap. We need to watch out for long-running
+ * requests that are still sitting on a previously
+ * used csn. SMB2_OP_NOTIFY can take VERY long.
+ */
+ generation_wrap += 1;
+ }
+
+ if (abs(cmp) > INT16_MAX) {
+ /*
+ * [MS-SMB2] 3.3.5.2.10 - Verifying the Channel Sequence Number:
+ *
+ * If the channel sequence number of the request and the one
+ * known to the server are not equal, the channel sequence
+ * number and outstanding request counts are only updated
+ * "... if the unsigned difference using 16-bit arithmetic
+ * between ChannelSequence and Open.ChannelSequence is less than
+ * or equal to 0x7FFF ...".
+ * Otherwise, an error is returned for the modifying
+ * calls write, set_info, and ioctl.
+ *
+ * There are currently two issues with the description:
+ *
+ * * For the other calls, the document seems to imply
+ * that processing continues without adapting the
+ * counters (if the sequence numbers are not equal).
+ *
+ * TODO: This needs clarification!
+ *
+ * * Also, the behaviour if the difference is larger
+ * than 0x7FFF is not clear. The document seems to
+ * imply that if such a difference is reached,
+ * the server starts to ignore the counters or
+ * in the case of the modifying calls, return errors.
+ *
+ * TODO: This needs clarification!
+ *
+ * At this point Samba tries to be a little more
+ * clever than the description in the MS-SMB2 document
+ * by heuristically detecting and properly treating
+ * a 16 bit overflow of the client-submitted sequence
+ * number:
+ *
+ * If the stored channel sequence number is more than
+ * 0x7FFF larger than the one from the request, then
+ * the client-provided sequence number has likely
+ * overflown. We treat this case as valid instead
+ * of as failure.
+ *
+ * The MS-SMB2 behaviour would be setting cmp = -1.
+ */
+ cmp *= -1;
+ }
+
+ if (flags & SMB2_HDR_FLAG_REPLAY_OPERATION) {
+ if (cmp == 0 && op->pre_request_count == 0) {
+ op->request_count += 1;
+ req->request_counters_updated = true;
+ } else if (cmp > 0 && op->pre_request_count == 0) {
+ op->pre_request_count += op->request_count;
+ op->request_count = 1;
+ op->global->channel_sequence = channel_sequence;
+ op->global->channel_generation += generation_wrap;
+ update_open = true;
+ req->request_counters_updated = true;
+ } else if (modify_call) {
+ return NT_STATUS_FILE_NOT_AVAILABLE;
+ }
+ } else {
+ if (cmp == 0) {
+ op->request_count += 1;
+ req->request_counters_updated = true;
+ } else if (cmp > 0) {
+ op->pre_request_count += op->request_count;
+ op->request_count = 1;
+ op->global->channel_sequence = channel_sequence;
+ op->global->channel_generation += generation_wrap;
+ update_open = true;
+ req->request_counters_updated = true;
+ } else if (modify_call) {
+ return NT_STATUS_FILE_NOT_AVAILABLE;
+ }
+ }
+ req->channel_generation = op->global->channel_generation;
+
+ if (update_open) {
+ status = smbXsrv_open_update(op);
+ }
+
+ return status;
+}
+
+NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ const struct smbd_smb2_dispatch_table *call = NULL;
+ const struct iovec *intf_v = SMBD_SMB2_IN_TF_IOV(req);
+ const uint8_t *inhdr;
+ uint16_t opcode;
+ uint32_t flags;
+ uint64_t mid;
+ NTSTATUS status;
+ NTSTATUS session_status;
+ uint32_t allowed_flags;
+ NTSTATUS return_value;
+ struct smbXsrv_session *x = NULL;
+ bool signing_required = false;
+ bool encryption_desired = false;
+ bool encryption_required = false;
+
+ inhdr = SMBD_SMB2_IN_HDR_PTR(req);
+
+ DO_PROFILE_INC(request);
+
+ SMB_ASSERT(!req->request_counters_updated);
+
+ /* TODO: verify more things */
+
+ flags = IVAL(inhdr, SMB2_HDR_FLAGS);
+ opcode = SVAL(inhdr, SMB2_HDR_OPCODE);
+ mid = BVAL(inhdr, SMB2_HDR_MESSAGE_ID);
+ DBG_DEBUG("opcode[%s] mid = %"PRIu64"\n",
+ smb2_opcode_name(opcode),
+ mid);
+
+ if (xconn->protocol >= PROTOCOL_SMB2_02) {
+ /*
+ * once the protocol is negotiated
+ * SMB2_OP_NEGPROT is not allowed anymore
+ */
+ if (opcode == SMB2_OP_NEGPROT) {
+ /* drop the connection */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ } else {
+ /*
+ * if the protocol is not negotiated yet
+ * only SMB2_OP_NEGPROT is allowed.
+ */
+ if (opcode != SMB2_OP_NEGPROT) {
+ /* drop the connection */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ }
+
+ /*
+ * Check if the client provided a valid session id.
+ *
+ * As some command don't require a valid session id
+ * we defer the check of the session_status
+ */
+ session_status = smbd_smb2_request_check_session(req);
+ x = req->session;
+ if (x != NULL) {
+ signing_required = x->global->signing_flags & SMBXSRV_SIGNING_REQUIRED;
+ encryption_desired = x->global->encryption_flags & SMBXSRV_ENCRYPTION_DESIRED;
+ encryption_required = x->global->encryption_flags & SMBXSRV_ENCRYPTION_REQUIRED;
+ }
+
+ req->async_internal = false;
+ req->do_signing = false;
+ if (opcode != SMB2_OP_SESSSETUP) {
+ req->do_encryption = encryption_desired;
+ } else {
+ req->do_encryption = false;
+ }
+ req->was_encrypted = false;
+ if (intf_v->iov_len == SMB2_TF_HDR_SIZE) {
+ const uint8_t *intf = SMBD_SMB2_IN_TF_PTR(req);
+ uint64_t tf_session_id = BVAL(intf, SMB2_TF_SESSION_ID);
+
+ if (x != NULL && x->global->session_wire_id != tf_session_id) {
+ DEBUG(0,("smbd_smb2_request_dispatch: invalid session_id"
+ "in SMB2_HDR[%llu], SMB2_TF[%llu]\n",
+ (unsigned long long)x->global->session_wire_id,
+ (unsigned long long)tf_session_id));
+ /*
+ * TODO: windows allows this...
+ * should we drop the connection?
+ *
+ * For now we just return ACCESS_DENIED
+ * (Windows clients never trigger this)
+ * and wait for an update of [MS-SMB2].
+ */
+ return smbd_smb2_request_error(req,
+ NT_STATUS_ACCESS_DENIED);
+ }
+
+ req->was_encrypted = true;
+ req->do_encryption = true;
+ }
+
+ if (encryption_required && !req->was_encrypted) {
+ req->do_encryption = true;
+ return smbd_smb2_request_error(req,
+ NT_STATUS_ACCESS_DENIED);
+ }
+
+ call = smbd_smb2_call(opcode);
+ if (call == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ allowed_flags = SMB2_HDR_FLAG_CHAINED |
+ SMB2_HDR_FLAG_SIGNED |
+ SMB2_HDR_FLAG_DFS;
+ if (xconn->protocol >= PROTOCOL_SMB3_11) {
+ allowed_flags |= SMB2_HDR_FLAG_PRIORITY_MASK;
+ }
+ if (opcode == SMB2_OP_NEGPROT) {
+ if (lp_server_max_protocol() >= PROTOCOL_SMB3_11) {
+ allowed_flags |= SMB2_HDR_FLAG_PRIORITY_MASK;
+ }
+ }
+ if (opcode == SMB2_OP_CANCEL) {
+ allowed_flags |= SMB2_HDR_FLAG_ASYNC;
+ }
+ if (xconn->protocol >= PROTOCOL_SMB3_00) {
+ allowed_flags |= SMB2_HDR_FLAG_REPLAY_OPERATION;
+ }
+ if ((flags & ~allowed_flags) != 0) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ if (flags & SMB2_HDR_FLAG_CHAINED) {
+ /*
+ * This check is mostly for giving the correct error code
+ * for compounded requests.
+ */
+ if (!NT_STATUS_IS_OK(session_status)) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+ } else {
+ req->compat_chain_fsp = NULL;
+ }
+
+ if (req->was_encrypted) {
+ signing_required = false;
+ } else if (signing_required || (flags & SMB2_HDR_FLAG_SIGNED)) {
+ struct smb2_signing_key *signing_key = NULL;
+ bool has_channel = false;
+
+ if (x == NULL) {
+ /*
+ * MS-SMB2: 3.3.5.2.4 Verifying the Signature.
+ * If the SMB2 header of the SMB2 NEGOTIATE
+ * request has the SMB2_FLAGS_SIGNED bit set in the
+ * Flags field, the server MUST fail the request
+ * with STATUS_INVALID_PARAMETER.
+ *
+ * Microsoft test tool checks this.
+ */
+
+ if ((opcode == SMB2_OP_NEGPROT) &&
+ (flags & SMB2_HDR_FLAG_SIGNED)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ } else {
+ status = NT_STATUS_USER_SESSION_DELETED;
+ }
+ return smbd_smb2_request_error(req, status);
+ }
+
+ signing_key = smbd_smb2_signing_key(x, xconn, &has_channel);
+
+ /*
+ * If we have a signing key, we should
+ * sign the response
+ */
+ if (smb2_signing_key_valid(signing_key) && opcode != SMB2_OP_CANCEL) {
+ req->do_signing = true;
+ }
+
+ status = smb2_signing_check_pdu(signing_key,
+ SMBD_SMB2_IN_HDR_IOV(req),
+ SMBD_SMB2_NUM_IOV_PER_REQ - 1);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) &&
+ opcode == SMB2_OP_SESSSETUP && !has_channel &&
+ NT_STATUS_IS_OK(session_status))
+ {
+ if (!NT_STATUS_EQUAL(x->status, NT_STATUS_BAD_LOGON_SESSION_STATE)) {
+ struct smbXsrv_session *session = NULL;
+ NTSTATUS error;
+
+ error = smb2srv_session_lookup_global(req->xconn->client,
+ x->global->session_wire_id,
+ req,
+ &session);
+ if (!NT_STATUS_IS_OK(error)) {
+ return smbd_smb2_request_error(req, error);
+ }
+
+ /*
+ * We fallback to a session of
+ * another process in order to
+ * get the signing correct.
+ *
+ * We don't set req->last_session_id here.
+ */
+ req->session = x = session;
+ }
+ goto skipped_signing;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ /*
+ * Now that we know the request was correctly signed
+ * we have to sign the response too.
+ */
+ if (opcode != SMB2_OP_CANCEL) {
+ req->do_signing = true;
+ }
+
+ if (!NT_STATUS_IS_OK(session_status)) {
+ return smbd_smb2_request_error(req, session_status);
+ }
+ }
+
+ if (opcode == SMB2_OP_IOCTL) {
+ /*
+ * Some special IOCTL calls don't require
+ * file, tcon nor session.
+ *
+ * They typically don't do any real action
+ * on behalf of the client.
+ *
+ * They are mainly used to alter the behavior
+ * of the connection for testing. So we can
+ * run as root and skip all file, tcon and session
+ * checks below.
+ */
+ static const struct smbd_smb2_dispatch_table _root_ioctl_call = {
+ .opcode = SMB2_OP_IOCTL,
+ .as_root = true,
+ };
+ const uint8_t *body = SMBD_SMB2_IN_BODY_PTR(req);
+ size_t body_size = SMBD_SMB2_IN_BODY_LEN(req);
+ uint32_t in_ctl_code;
+ size_t needed = 8;
+
+ if (needed > body_size) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+
+ in_ctl_code = IVAL(body, 0x04);
+ /*
+ * Only add trusted IOCTL codes here!
+ */
+ switch (in_ctl_code) {
+ case FSCTL_SMBTORTURE_FORCE_UNACKED_TIMEOUT:
+ call = &_root_ioctl_call;
+ break;
+ case FSCTL_VALIDATE_NEGOTIATE_INFO:
+ call = &_root_ioctl_call;
+ break;
+ case FSCTL_QUERY_NETWORK_INTERFACE_INFO:
+ call = &_root_ioctl_call;
+ break;
+ }
+ }
+
+skipped_signing:
+
+ if (flags & SMB2_HDR_FLAG_CHAINED) {
+ req->compound_related = true;
+ }
+
+ if (call->need_session) {
+ if (!NT_STATUS_IS_OK(session_status)) {
+ return smbd_smb2_request_error(req, session_status);
+ }
+ }
+
+ if (call->need_tcon) {
+ SMB_ASSERT(call->need_session);
+
+ /*
+ * This call needs to be run as user.
+ *
+ * smbd_smb2_request_check_tcon()
+ * calls change_to_user() on success.
+ * Which implies set_current_user_info()
+ * and chdir_current_service().
+ */
+ status = smbd_smb2_request_check_tcon(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ if (req->tcon->global->encryption_flags & SMBXSRV_ENCRYPTION_DESIRED) {
+ encryption_desired = true;
+ }
+ if (req->tcon->global->encryption_flags & SMBXSRV_ENCRYPTION_REQUIRED) {
+ encryption_required = true;
+ }
+ if (encryption_required && !req->was_encrypted) {
+ req->do_encryption = true;
+ return smbd_smb2_request_error(req,
+ NT_STATUS_ACCESS_DENIED);
+ } else if (encryption_desired) {
+ req->do_encryption = true;
+ }
+ } else if (call->need_session) {
+ struct auth_session_info *session_info = NULL;
+
+ /*
+ * Unless we also have need_tcon (see above),
+ * we still need to call set_current_user_info().
+ */
+
+ session_info = req->session->global->auth_session_info;
+ if (session_info == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ set_current_user_info(session_info->unix_info->sanitized_username,
+ session_info->unix_info->unix_name,
+ session_info->info->domain_name);
+ }
+
+ if (req->session) {
+ bool update_session_global = false;
+ bool update_tcon_global = false;
+
+ smb2srv_update_crypto_flags(req, opcode,
+ &update_session_global,
+ &update_tcon_global);
+
+ if (update_session_global) {
+ status = smbXsrv_session_update(x);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ }
+ if (update_tcon_global) {
+ status = smbXsrv_tcon_update(req->tcon);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ }
+ }
+
+ if (call->fileid_ofs != 0) {
+ size_t needed = call->fileid_ofs + 16;
+ const uint8_t *body = SMBD_SMB2_IN_BODY_PTR(req);
+ size_t body_size = SMBD_SMB2_IN_BODY_LEN(req);
+ uint64_t file_id_persistent;
+ uint64_t file_id_volatile;
+ struct files_struct *fsp;
+
+ SMB_ASSERT(call->need_tcon);
+
+ if (needed > body_size) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+
+ file_id_persistent = BVAL(body, call->fileid_ofs + 0);
+ file_id_volatile = BVAL(body, call->fileid_ofs + 8);
+
+ fsp = file_fsp_smb2(req, file_id_persistent, file_id_volatile);
+ if (fsp == NULL) {
+ if (req->compound_related &&
+ !NT_STATUS_IS_OK(req->compound_create_err))
+ {
+ return smbd_smb2_request_error(req,
+ req->compound_create_err);
+ }
+ /*
+ * smbd_smb2_request_process_ioctl()
+ * has more checks in order to return more
+ * detailed error codes...
+ */
+ if (opcode != SMB2_OP_IOCTL) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_FILE_CLOSED);
+ }
+ } else {
+ if (fsp->fsp_flags.encryption_required && !req->was_encrypted) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_ACCESS_DENIED);
+ }
+ }
+ }
+
+ status = smbd_smb2_request_dispatch_update_counts(req, call->modify);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ if (call->as_root) {
+ SMB_ASSERT(call->fileid_ofs == 0);
+ /* This call needs to be run as root */
+ change_to_root_user();
+ } else if (opcode != SMB2_OP_KEEPALIVE) {
+ SMB_ASSERT(call->need_tcon);
+ }
+
+#define _INBYTES(_r) \
+ iov_buflen(SMBD_SMB2_IN_HDR_IOV(_r), SMBD_SMB2_NUM_IOV_PER_REQ-1)
+
+ switch (opcode) {
+ case SMB2_OP_NEGPROT:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_negprot, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_negprot(req);
+ break;
+
+ case SMB2_OP_SESSSETUP:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_sesssetup, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_sesssetup(req);
+ break;
+
+ case SMB2_OP_LOGOFF:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_logoff, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_logoff(req);
+ break;
+
+ case SMB2_OP_TCON:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_tcon, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_tcon(req);
+ break;
+
+ case SMB2_OP_TDIS:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_tdis, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_tdis(req);
+ break;
+
+ case SMB2_OP_CREATE:
+ if (req->subreq == NULL) {
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_create, profile_p,
+ req->profile, _INBYTES(req));
+ } else {
+ SMBPROFILE_IOBYTES_ASYNC_SET_BUSY(req->profile);
+ }
+ return_value = smbd_smb2_request_process_create(req);
+ break;
+
+ case SMB2_OP_CLOSE:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_close, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_close(req);
+ break;
+
+ case SMB2_OP_FLUSH:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_flush, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_flush(req);
+ break;
+
+ case SMB2_OP_READ:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_read, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_read(req);
+ break;
+
+ case SMB2_OP_WRITE:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_write, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_write(req);
+ break;
+
+ case SMB2_OP_LOCK:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_lock, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_lock(req);
+ break;
+
+ case SMB2_OP_IOCTL:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_ioctl, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_ioctl(req);
+ break;
+
+ case SMB2_OP_CANCEL:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_cancel, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_cancel(req);
+ SMBPROFILE_IOBYTES_ASYNC_END(req->profile, 0);
+
+ /*
+ * We don't need the request anymore cancel requests never
+ * have a response.
+ *
+ * smbd_smb2_request_process_cancel() already called
+ * DLIST_REMOVE(xconn->smb2.requests, req);
+ */
+ TALLOC_FREE(req);
+
+ break;
+
+ case SMB2_OP_KEEPALIVE:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_keepalive, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_keepalive(req);
+ break;
+
+ case SMB2_OP_QUERY_DIRECTORY:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_find, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_query_directory(req);
+ break;
+
+ case SMB2_OP_NOTIFY:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_notify, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_notify(req);
+ break;
+
+ case SMB2_OP_GETINFO:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_getinfo, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_getinfo(req);
+ break;
+
+ case SMB2_OP_SETINFO:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_setinfo, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_setinfo(req);
+ break;
+
+ case SMB2_OP_BREAK:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_break, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_break(req);
+ break;
+
+ default:
+ return_value = smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ break;
+ }
+ return return_value;
+}
+
+static void smbd_smb2_request_reply_update_counts(struct smbd_smb2_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ const uint8_t *inhdr;
+ uint16_t channel_sequence;
+ struct smbXsrv_open *op;
+
+ if (!req->request_counters_updated) {
+ return;
+ }
+
+ req->request_counters_updated = false;
+
+ if (xconn->protocol < PROTOCOL_SMB3_00) {
+ return;
+ }
+
+ if (req->compat_chain_fsp == NULL) {
+ return;
+ }
+
+ op = req->compat_chain_fsp->op;
+ if (op == NULL) {
+ return;
+ }
+
+ inhdr = SMBD_SMB2_IN_HDR_PTR(req);
+ channel_sequence = SVAL(inhdr, SMB2_HDR_CHANNEL_SEQUENCE);
+
+ if ((op->global->channel_sequence == channel_sequence) &&
+ (op->global->channel_generation == req->channel_generation)) {
+ SMB_ASSERT(op->request_count > 0);
+ op->request_count -= 1;
+ } else {
+ SMB_ASSERT(op->pre_request_count > 0);
+ op->pre_request_count -= 1;
+ }
+}
+
+static NTSTATUS smbd_smb2_request_reply(struct smbd_smb2_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ int first_idx = 1;
+ struct iovec *firsttf = SMBD_SMB2_IDX_TF_IOV(req,out,first_idx);
+ struct iovec *outhdr = SMBD_SMB2_OUT_HDR_IOV(req);
+ struct iovec *outdyn = SMBD_SMB2_OUT_DYN_IOV(req);
+ NTSTATUS status;
+ bool ok;
+
+ req->subreq = NULL;
+ TALLOC_FREE(req->async_te);
+
+ /* MS-SMB2: 3.3.4.1 Sending Any Outgoing Message */
+ smbd_smb2_request_reply_update_counts(req);
+
+ if (req->do_encryption &&
+ (firsttf->iov_len == 0) &&
+ (!smb2_signing_key_valid(req->first_enc_key)) &&
+ (req->session != NULL) &&
+ smb2_signing_key_valid(req->session->global->encryption_key))
+ {
+ struct smb2_signing_key *encryption_key =
+ req->session->global->encryption_key;
+ uint8_t *tf;
+ uint64_t session_id = req->session->global->session_wire_id;
+ uint64_t nonce_high;
+ uint64_t nonce_low;
+
+ status = smb2_get_new_nonce(req->session,
+ &nonce_high,
+ &nonce_low);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * We need to place the SMB2_TRANSFORM header before the
+ * first SMB2 header
+ */
+
+ /*
+ * we need to remember the encryption key
+ * and defer the signing/encryption until
+ * we are sure that we do not change
+ * the header again.
+ */
+ status = smb2_signing_key_copy(req,
+ encryption_key,
+ &req->first_enc_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ tf = talloc_zero_array(req, uint8_t,
+ SMB2_TF_HDR_SIZE);
+ if (tf == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ SIVAL(tf, SMB2_TF_PROTOCOL_ID, SMB2_TF_MAGIC);
+ SBVAL(tf, SMB2_TF_NONCE+0, nonce_low);
+ SBVAL(tf, SMB2_TF_NONCE+8, nonce_high);
+ SBVAL(tf, SMB2_TF_SESSION_ID, session_id);
+
+ firsttf->iov_base = (void *)tf;
+ firsttf->iov_len = SMB2_TF_HDR_SIZE;
+ }
+
+ if ((req->current_idx > SMBD_SMB2_NUM_IOV_PER_REQ) &&
+ (smb2_signing_key_valid(req->last_sign_key)) &&
+ (firsttf->iov_len == 0))
+ {
+ int last_idx = req->current_idx - SMBD_SMB2_NUM_IOV_PER_REQ;
+ struct iovec *lasthdr = SMBD_SMB2_IDX_HDR_IOV(req,out,last_idx);
+
+ /*
+ * As we are sure the header of the last request in the
+ * compound chain will not change, we can to sign here
+ * with the last signing key we remembered.
+ */
+ status = smb2_signing_sign_pdu(req->last_sign_key,
+ lasthdr,
+ SMBD_SMB2_NUM_IOV_PER_REQ - 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+ TALLOC_FREE(req->last_sign_key);
+
+ SMBPROFILE_IOBYTES_ASYNC_END(req->profile,
+ iov_buflen(outhdr, SMBD_SMB2_NUM_IOV_PER_REQ-1));
+
+ req->current_idx += SMBD_SMB2_NUM_IOV_PER_REQ;
+
+ if (req->current_idx < req->out.vector_count) {
+ /*
+ * We must process the remaining compound
+ * SMB2 requests before any new incoming SMB2
+ * requests. This is because incoming SMB2
+ * requests may include a cancel for a
+ * compound request we haven't processed
+ * yet.
+ */
+ struct tevent_immediate *im = tevent_create_immediate(req);
+ if (!im) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (req->do_signing && firsttf->iov_len == 0) {
+ struct smbXsrv_session *x = req->session;
+ struct smb2_signing_key *signing_key =
+ smbd_smb2_signing_key(x, xconn, NULL);
+
+ /*
+ * we need to remember the signing key
+ * and defer the signing until
+ * we are sure that we do not change
+ * the header again.
+ */
+ status = smb2_signing_key_copy(req,
+ signing_key,
+ &req->last_sign_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ /*
+ * smbd_smb2_request_dispatch() will redo the impersonation.
+ * So we use req->xconn->client->raw_ev_ctx instead
+ * of req->ev_ctx here.
+ */
+ tevent_schedule_immediate(im,
+ req->xconn->client->raw_ev_ctx,
+ smbd_smb2_request_dispatch_immediate,
+ req);
+ return NT_STATUS_OK;
+ }
+
+ if (req->compound_related) {
+ req->compound_related = false;
+ }
+
+ ok = smb2_setup_nbt_length(req->out.vector, req->out.vector_count);
+ if (!ok) {
+ return NT_STATUS_INVALID_PARAMETER_MIX;
+ }
+
+ /* Set credit for these operations (zero credits if this
+ is a final reply for an async operation). */
+ smb2_calculate_credits(req, req);
+
+ /*
+ * now check if we need to sign the current response
+ */
+ if (firsttf->iov_len == SMB2_TF_HDR_SIZE) {
+ status = smb2_signing_encrypt_pdu(req->first_enc_key,
+ firsttf,
+ req->out.vector_count - first_idx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ } else if (req->do_signing) {
+ struct smbXsrv_session *x = req->session;
+ struct smb2_signing_key *signing_key =
+ smbd_smb2_signing_key(x, xconn, NULL);
+
+ status = smb2_signing_sign_pdu(signing_key,
+ outhdr,
+ SMBD_SMB2_NUM_IOV_PER_REQ - 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+ TALLOC_FREE(req->first_enc_key);
+
+ if (req->preauth != NULL) {
+ gnutls_hash_hd_t hash_hnd = NULL;
+ size_t i;
+ int rc;
+
+ rc = gnutls_hash_init(&hash_hnd, GNUTLS_DIG_SHA512);
+ if (rc < 0) {
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
+ }
+ rc = gnutls_hash(hash_hnd,
+ req->preauth->sha512_value,
+ sizeof(req->preauth->sha512_value));
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
+ }
+ for (i = 1; i < req->in.vector_count; i++) {
+ rc = gnutls_hash(hash_hnd,
+ req->in.vector[i].iov_base,
+ req->in.vector[i].iov_len);
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
+ }
+ }
+ gnutls_hash_output(hash_hnd, req->preauth->sha512_value);
+
+ rc = gnutls_hash(hash_hnd,
+ req->preauth->sha512_value,
+ sizeof(req->preauth->sha512_value));
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
+ }
+ for (i = 1; i < req->out.vector_count; i++) {
+ rc = gnutls_hash(hash_hnd,
+ req->out.vector[i].iov_base,
+ req->out.vector[i].iov_len);
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
+ }
+ }
+
+ gnutls_hash_deinit(hash_hnd, req->preauth->sha512_value);
+
+ req->preauth = NULL;
+ }
+
+ /* I am a sick, sick man... :-). Sendfile hack ... JRA. */
+ if (req->out.vector_count < (2*SMBD_SMB2_NUM_IOV_PER_REQ) &&
+ outdyn->iov_base == NULL && outdyn->iov_len != 0) {
+ /* Dynamic part is NULL. Chop it off,
+ We're going to send it via sendfile. */
+ req->out.vector_count -= 1;
+ }
+
+ /*
+ * We're done with this request -
+ * move it off the "being processed" queue.
+ */
+ DLIST_REMOVE(xconn->smb2.requests, req);
+
+ req->queue_entry.mem_ctx = req;
+ req->queue_entry.vector = req->out.vector;
+ req->queue_entry.count = req->out.vector_count;
+ DLIST_ADD_END(xconn->smb2.send_queue, &req->queue_entry);
+ xconn->smb2.send_queue_len++;
+
+ status = smbd_smb2_flush_send_queue(xconn);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbd_smb2_request_next_incoming(struct smbXsrv_connection *xconn);
+
+void smbd_smb2_request_dispatch_immediate(struct tevent_context *ctx,
+ struct tevent_immediate *im,
+ void *private_data)
+{
+ struct smbd_smb2_request *req = talloc_get_type_abort(private_data,
+ struct smbd_smb2_request);
+ struct smbXsrv_connection *xconn = req->xconn;
+ NTSTATUS status;
+
+ TALLOC_FREE(im);
+
+ if (DEBUGLEVEL >= 10) {
+ DEBUG(10,("smbd_smb2_request_dispatch_immediate: idx[%d] of %d vectors\n",
+ req->current_idx, req->in.vector_count));
+ print_req_vectors(req);
+ }
+
+ status = smbd_smb2_request_dispatch(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
+ return;
+ }
+
+ status = smbd_smb2_request_next_incoming(xconn);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
+ return;
+ }
+}
+
+NTSTATUS smbd_smb2_request_done_ex(struct smbd_smb2_request *req,
+ NTSTATUS status,
+ DATA_BLOB body, DATA_BLOB *dyn,
+ const char *location)
+{
+ uint8_t *outhdr;
+ struct iovec *outbody_v;
+ struct iovec *outdyn_v;
+ uint32_t next_command_ofs;
+ uint64_t mid;
+
+ outhdr = SMBD_SMB2_OUT_HDR_PTR(req);
+ mid = BVAL(outhdr, SMB2_HDR_MESSAGE_ID);
+
+ DBG_DEBUG("mid [%"PRIu64"] idx[%d] status[%s] "
+ "body[%u] dyn[%s:%u] at %s\n",
+ mid,
+ req->current_idx,
+ nt_errstr(status),
+ (unsigned int)body.length,
+ dyn ? "yes" : "no",
+ (unsigned int)(dyn ? dyn->length : 0),
+ location);
+
+ if (body.length < 2) {
+ return smbd_smb2_request_error(req, NT_STATUS_INTERNAL_ERROR);
+ }
+
+ if ((body.length % 2) != 0) {
+ return smbd_smb2_request_error(req, NT_STATUS_INTERNAL_ERROR);
+ }
+
+ outbody_v = SMBD_SMB2_OUT_BODY_IOV(req);
+ outdyn_v = SMBD_SMB2_OUT_DYN_IOV(req);
+
+ next_command_ofs = IVAL(outhdr, SMB2_HDR_NEXT_COMMAND);
+ SIVAL(outhdr, SMB2_HDR_STATUS, NT_STATUS_V(status));
+
+ outbody_v->iov_base = (void *)body.data;
+ outbody_v->iov_len = body.length;
+
+ if (dyn) {
+ outdyn_v->iov_base = (void *)dyn->data;
+ outdyn_v->iov_len = dyn->length;
+ } else {
+ outdyn_v->iov_base = NULL;
+ outdyn_v->iov_len = 0;
+ }
+
+ /*
+ * See if we need to recalculate the offset to the next response
+ *
+ * Note that all responses may require padding (including the very last
+ * one).
+ */
+ if (req->out.vector_count >= (2 * SMBD_SMB2_NUM_IOV_PER_REQ)) {
+ next_command_ofs = SMB2_HDR_BODY;
+ next_command_ofs += SMBD_SMB2_OUT_BODY_LEN(req);
+ next_command_ofs += SMBD_SMB2_OUT_DYN_LEN(req);
+ }
+
+ if ((next_command_ofs % 8) != 0) {
+ size_t pad_size = 8 - (next_command_ofs % 8);
+ if (SMBD_SMB2_OUT_DYN_LEN(req) == 0) {
+ /*
+ * if the dyn buffer is empty
+ * we can use it to add padding
+ */
+ uint8_t *pad;
+
+ pad = talloc_zero_array(req,
+ uint8_t, pad_size);
+ if (pad == NULL) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_NO_MEMORY);
+ }
+
+ outdyn_v->iov_base = (void *)pad;
+ outdyn_v->iov_len = pad_size;
+ } else {
+ /*
+ * For now we copy the dynamic buffer
+ * and add the padding to the new buffer
+ */
+ size_t old_size;
+ uint8_t *old_dyn;
+ size_t new_size;
+ uint8_t *new_dyn;
+
+ old_size = SMBD_SMB2_OUT_DYN_LEN(req);
+ old_dyn = SMBD_SMB2_OUT_DYN_PTR(req);
+
+ new_size = old_size + pad_size;
+ new_dyn = talloc_zero_array(req,
+ uint8_t, new_size);
+ if (new_dyn == NULL) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_NO_MEMORY);
+ }
+
+ memcpy(new_dyn, old_dyn, old_size);
+ memset(new_dyn + old_size, 0, pad_size);
+
+ outdyn_v->iov_base = (void *)new_dyn;
+ outdyn_v->iov_len = new_size;
+ }
+ next_command_ofs += pad_size;
+ }
+
+ if ((req->current_idx + SMBD_SMB2_NUM_IOV_PER_REQ) >= req->out.vector_count) {
+ SIVAL(outhdr, SMB2_HDR_NEXT_COMMAND, 0);
+ } else {
+ SIVAL(outhdr, SMB2_HDR_NEXT_COMMAND, next_command_ofs);
+ }
+ return smbd_smb2_request_reply(req);
+}
+
+NTSTATUS smbd_smb2_request_error_ex(struct smbd_smb2_request *req,
+ NTSTATUS status,
+ uint8_t error_context_count,
+ DATA_BLOB *info,
+ const char *location)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ DATA_BLOB body;
+ DATA_BLOB _dyn;
+ uint8_t *outhdr = SMBD_SMB2_OUT_HDR_PTR(req);
+ size_t unread_bytes = smbd_smb2_unread_bytes(req);
+
+ DBG_NOTICE("smbd_smb2_request_error_ex: idx[%d] status[%s] |%s| "
+ "at %s\n", req->current_idx, nt_errstr(status),
+ info ? " +info" : "", location);
+
+ if (unread_bytes) {
+ /* Recvfile error. Drain incoming socket. */
+ size_t ret;
+
+ errno = 0;
+ ret = drain_socket(xconn->transport.sock, unread_bytes);
+ if (ret != unread_bytes) {
+ NTSTATUS error;
+
+ if (errno == 0) {
+ error = NT_STATUS_IO_DEVICE_ERROR;
+ } else {
+ error = map_nt_error_from_unix_common(errno);
+ }
+
+ DEBUG(2, ("Failed to drain %u bytes from SMB2 socket: "
+ "ret[%u] errno[%d] => %s\n",
+ (unsigned)unread_bytes,
+ (unsigned)ret, errno, nt_errstr(error)));
+ return error;
+ }
+ }
+
+ body.data = outhdr + SMB2_HDR_BODY;
+ body.length = 8;
+ SSVAL(body.data, 0, 9);
+ SCVAL(body.data, 2, error_context_count);
+
+ if (info) {
+ SIVAL(body.data, 0x04, info->length);
+ } else {
+ /* Allocated size of req->out.vector[i].iov_base
+ * *MUST BE* OUTVEC_ALLOC_SIZE. So we have room for
+ * 1 byte without having to do an alloc.
+ */
+ info = &_dyn;
+ info->data = ((uint8_t *)outhdr) +
+ OUTVEC_ALLOC_SIZE - 1;
+ info->length = 1;
+ SCVAL(info->data, 0, 0);
+ }
+
+ /*
+ * Note: Even if there is an error, continue to process the request.
+ * per MS-SMB2.
+ */
+
+ return smbd_smb2_request_done_ex(req, status, body, info, __location__);
+}
+
+struct smbd_smb2_break_state {
+ struct tevent_req *req;
+ struct smbd_smb2_send_queue queue_entry;
+ uint8_t nbt_hdr[NBT_HDR_SIZE];
+ uint8_t hdr[SMB2_HDR_BODY];
+ struct iovec vector[1+SMBD_SMB2_NUM_IOV_PER_REQ];
+};
+
+static struct tevent_req *smbd_smb2_break_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbXsrv_connection *xconn,
+ uint64_t session_id,
+ const uint8_t *body,
+ size_t body_len)
+{
+ struct tevent_req *req = NULL;
+ struct smbd_smb2_break_state *state = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_break_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->req = req;
+ tevent_req_defer_callback(req, ev);
+
+ SIVAL(state->hdr, 0, SMB2_MAGIC);
+ SSVAL(state->hdr, SMB2_HDR_LENGTH, SMB2_HDR_BODY);
+ SSVAL(state->hdr, SMB2_HDR_EPOCH, 0);
+ SIVAL(state->hdr, SMB2_HDR_STATUS, 0);
+ SSVAL(state->hdr, SMB2_HDR_OPCODE, SMB2_OP_BREAK);
+ SSVAL(state->hdr, SMB2_HDR_CREDIT, 0);
+ SIVAL(state->hdr, SMB2_HDR_FLAGS, SMB2_HDR_FLAG_REDIRECT);
+ SIVAL(state->hdr, SMB2_HDR_NEXT_COMMAND, 0);
+ SBVAL(state->hdr, SMB2_HDR_MESSAGE_ID, UINT64_MAX);
+ SIVAL(state->hdr, SMB2_HDR_PID, 0);
+ SIVAL(state->hdr, SMB2_HDR_TID, 0);
+ SBVAL(state->hdr, SMB2_HDR_SESSION_ID, session_id);
+ memset(state->hdr+SMB2_HDR_SIGNATURE, 0, 16);
+
+ state->vector[0] = (struct iovec) {
+ .iov_base = state->nbt_hdr,
+ .iov_len = sizeof(state->nbt_hdr)
+ };
+
+ state->vector[1+SMBD_SMB2_TF_IOV_OFS] = (struct iovec) {
+ .iov_base = NULL,
+ .iov_len = 0
+ };
+
+ state->vector[1+SMBD_SMB2_HDR_IOV_OFS] = (struct iovec) {
+ .iov_base = state->hdr,
+ .iov_len = sizeof(state->hdr)
+ };
+
+ state->vector[1+SMBD_SMB2_BODY_IOV_OFS] = (struct iovec) {
+ .iov_base = discard_const_p(uint8_t, body),
+ .iov_len = body_len,
+ };
+
+ /*
+ * state->vector[1+SMBD_SMB2_DYN_IOV_OFS] is NULL by talloc_zero above
+ */
+
+ ok = smb2_setup_nbt_length(state->vector,
+ 1 + SMBD_SMB2_NUM_IOV_PER_REQ);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * We require TCP acks for this PDU to the client!
+ * We want 5 retransmissions and timeout when the
+ * retransmission timeout (rto) passed 6 times.
+ *
+ * required_acked_bytes gets a dummy value of
+ * UINT64_MAX, as long it's in xconn->smb2.send_queue,
+ * it'll get the real value when it's moved to
+ * xconn->ack.queue.
+ *
+ * state->queue_entry.ack.req gets completed with
+ * 1. tevent_req_done(), when all bytes are acked.
+ * 2a. tevent_req_nterror(NT_STATUS_IO_TIMEOUT), when
+ * the timeout expired before all bytes were acked.
+ * 2b. tevent_req_nterror(transport_error), when the
+ * connection got a disconnect from the kernel.
+ */
+ state->queue_entry.ack.timeout =
+ timeval_current_ofs_usec(xconn->ack.rto_usecs * 6);
+ state->queue_entry.ack.required_acked_bytes = UINT64_MAX;
+ state->queue_entry.ack.req = req;
+ state->queue_entry.mem_ctx = state;
+ state->queue_entry.vector = state->vector;
+ state->queue_entry.count = ARRAY_SIZE(state->vector);
+ DLIST_ADD_END(xconn->smb2.send_queue, &state->queue_entry);
+ xconn->smb2.send_queue_len++;
+
+ status = smbd_smb2_flush_send_queue(xconn);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static NTSTATUS smbd_smb2_break_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+struct smbXsrv_pending_break {
+ struct smbXsrv_pending_break *prev, *next;
+ struct smbXsrv_client *client;
+ bool disable_oplock_break_retries;
+ uint64_t session_id;
+ uint64_t last_channel_id;
+ union {
+ uint8_t generic[1];
+ uint8_t oplock[0x18];
+ uint8_t lease[0x2c];
+ } body;
+ size_t body_len;
+};
+
+static void smbXsrv_pending_break_done(struct tevent_req *subreq);
+
+static struct smbXsrv_pending_break *smbXsrv_pending_break_create(
+ struct smbXsrv_client *client,
+ uint64_t session_id)
+{
+ struct smbXsrv_pending_break *pb = NULL;
+
+ pb = talloc_zero(client, struct smbXsrv_pending_break);
+ if (pb == NULL) {
+ return NULL;
+ }
+ pb->client = client;
+ pb->session_id = session_id;
+ pb->disable_oplock_break_retries = lp_smb2_disable_oplock_break_retry();
+
+ return pb;
+}
+
+static NTSTATUS smbXsrv_pending_break_submit(struct smbXsrv_pending_break *pb);
+
+static NTSTATUS smbXsrv_pending_break_schedule(struct smbXsrv_pending_break *pb)
+{
+ struct smbXsrv_client *client = pb->client;
+ NTSTATUS status;
+
+ DLIST_ADD_END(client->pending_breaks, pb);
+ status = smbXsrv_client_pending_breaks_updated(client);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = smbXsrv_pending_break_submit(pb);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbXsrv_pending_break_submit(struct smbXsrv_pending_break *pb)
+{
+ struct smbXsrv_client *client = pb->client;
+ struct smbXsrv_session *session = NULL;
+ struct smbXsrv_connection *xconn = NULL;
+ struct smbXsrv_connection *oplock_xconn = NULL;
+ struct tevent_req *subreq = NULL;
+ NTSTATUS status;
+
+ if (pb->session_id != 0) {
+ status = get_valid_smbXsrv_session(client,
+ pb->session_id,
+ &session);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_USER_SESSION_DELETED)) {
+ return NT_STATUS_ABANDONED;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (pb->last_channel_id != 0) {
+ /*
+ * This is what current Windows servers
+ * do, they don't retry on all available
+ * channels. They only use the last channel.
+ *
+ * But it doesn't match the specification in
+ * [MS-SMB2] "3.3.4.6 Object Store Indicates an
+ * Oplock Break"
+ *
+ * Per default disable_oplock_break_retries is false
+ * and we behave like the specification.
+ */
+ if (pb->disable_oplock_break_retries) {
+ return NT_STATUS_ABANDONED;
+ }
+ }
+ }
+
+ for (xconn = client->connections; xconn != NULL; xconn = xconn->next) {
+ if (!NT_STATUS_IS_OK(xconn->transport.status)) {
+ continue;
+ }
+
+ if (xconn->channel_id == 0) {
+ /*
+ * non-multichannel case
+ */
+ break;
+ }
+
+ if (session != NULL) {
+ struct smbXsrv_channel_global0 *c = NULL;
+
+ /*
+ * Having a session means we're handling
+ * an oplock break and we only need to
+ * use channels available on the
+ * session.
+ */
+ status = smbXsrv_session_find_channel(session, xconn, &c);
+ if (!NT_STATUS_IS_OK(status)) {
+ continue;
+ }
+
+ /*
+ * This is what current Windows servers
+ * do, they don't retry on all available
+ * channels. They only use the last channel.
+ *
+ * But it doesn't match the specification
+ * in [MS-SMB2] "3.3.4.6 Object Store Indicates an
+ * Oplock Break"
+ *
+ * Per default disable_oplock_break_retries is false
+ * and we behave like the specification.
+ */
+ if (pb->disable_oplock_break_retries) {
+ oplock_xconn = xconn;
+ continue;
+ }
+ }
+
+ if (xconn->channel_id > pb->last_channel_id) {
+ /*
+ * multichannel case
+ */
+ break;
+ }
+ }
+
+ if (xconn == NULL) {
+ xconn = oplock_xconn;
+ }
+
+ if (xconn == NULL) {
+ /*
+ * If there's no remaining connection available
+ * tell the caller to stop...
+ */
+ return NT_STATUS_ABANDONED;
+ }
+
+ pb->last_channel_id = xconn->channel_id;
+
+ subreq = smbd_smb2_break_send(pb,
+ client->raw_ev_ctx,
+ xconn,
+ pb->session_id,
+ pb->body.generic,
+ pb->body_len);
+ if (subreq == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_req_set_callback(subreq,
+ smbXsrv_pending_break_done,
+ pb);
+
+ return NT_STATUS_OK;
+}
+
+static void smbXsrv_pending_break_done(struct tevent_req *subreq)
+{
+ struct smbXsrv_pending_break *pb =
+ tevent_req_callback_data(subreq,
+ struct smbXsrv_pending_break);
+ struct smbXsrv_client *client = pb->client;
+ NTSTATUS status;
+
+ status = smbd_smb2_break_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ status = smbXsrv_pending_break_submit(pb);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_ABANDONED)) {
+ /*
+ * If there's no remaining connection
+ * there's no need to send a break again.
+ */
+ goto remove;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_disconnect_client(client, nt_errstr(status));
+ return;
+ }
+ return;
+ }
+
+remove:
+ DLIST_REMOVE(client->pending_breaks, pb);
+ TALLOC_FREE(pb);
+
+ status = smbXsrv_client_pending_breaks_updated(client);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_disconnect_client(client, nt_errstr(status));
+ return;
+ }
+}
+
+NTSTATUS smbd_smb2_send_oplock_break(struct smbXsrv_client *client,
+ struct smbXsrv_open *op,
+ uint8_t oplock_level)
+{
+ struct smbXsrv_pending_break *pb = NULL;
+ uint8_t *body = NULL;
+
+ pb = smbXsrv_pending_break_create(client,
+ op->compat->vuid);
+ if (pb == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ pb->body_len = sizeof(pb->body.oplock);
+ body = pb->body.oplock;
+
+ SSVAL(body, 0x00, pb->body_len);
+ SCVAL(body, 0x02, oplock_level);
+ SCVAL(body, 0x03, 0); /* reserved */
+ SIVAL(body, 0x04, 0); /* reserved */
+ SBVAL(body, 0x08, op->global->open_persistent_id);
+ SBVAL(body, 0x10, op->global->open_volatile_id);
+
+ return smbXsrv_pending_break_schedule(pb);
+}
+
+NTSTATUS smbd_smb2_send_lease_break(struct smbXsrv_client *client,
+ uint16_t new_epoch,
+ uint32_t lease_flags,
+ struct smb2_lease_key *lease_key,
+ uint32_t current_lease_state,
+ uint32_t new_lease_state)
+{
+ struct smbXsrv_pending_break *pb = NULL;
+ uint8_t *body = NULL;
+
+ pb = smbXsrv_pending_break_create(client,
+ 0); /* no session_id */
+ if (pb == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ pb->body_len = sizeof(pb->body.lease);
+ body = pb->body.lease;
+
+ SSVAL(body, 0x00, pb->body_len);
+ SSVAL(body, 0x02, new_epoch);
+ SIVAL(body, 0x04, lease_flags);
+ SBVAL(body, 0x08, lease_key->data[0]);
+ SBVAL(body, 0x10, lease_key->data[1]);
+ SIVAL(body, 0x18, current_lease_state);
+ SIVAL(body, 0x1c, new_lease_state);
+ SIVAL(body, 0x20, 0); /* BreakReason, MUST be 0 */
+ SIVAL(body, 0x24, 0); /* AccessMaskHint, MUST be 0 */
+ SIVAL(body, 0x28, 0); /* ShareMaskHint, MUST be 0 */
+
+ return smbXsrv_pending_break_schedule(pb);
+}
+
+static bool is_smb2_recvfile_write(struct smbd_smb2_request_read_state *state)
+{
+ NTSTATUS status;
+ uint32_t flags;
+ uint64_t file_id_persistent;
+ uint64_t file_id_volatile;
+ struct smbXsrv_open *op = NULL;
+ struct files_struct *fsp = NULL;
+ const uint8_t *body = NULL;
+
+ /*
+ * This is only called with a pktbuf
+ * of at least SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN
+ * bytes
+ */
+
+ if (IVAL(state->pktbuf, 0) == SMB2_TF_MAGIC) {
+ /* Transform header. Cannot recvfile. */
+ return false;
+ }
+ if (IVAL(state->pktbuf, 0) != SMB2_MAGIC) {
+ /* Not SMB2. Normal error path will cope. */
+ return false;
+ }
+ if (SVAL(state->pktbuf, 4) != SMB2_HDR_BODY) {
+ /* Not SMB2. Normal error path will cope. */
+ return false;
+ }
+ if (SVAL(state->pktbuf, SMB2_HDR_OPCODE) != SMB2_OP_WRITE) {
+ /* Needs to be a WRITE. */
+ return false;
+ }
+ if (IVAL(state->pktbuf, SMB2_HDR_NEXT_COMMAND) != 0) {
+ /* Chained. Cannot recvfile. */
+ return false;
+ }
+ flags = IVAL(state->pktbuf, SMB2_HDR_FLAGS);
+ if (flags & SMB2_HDR_FLAG_CHAINED) {
+ /* Chained. Cannot recvfile. */
+ return false;
+ }
+ if (flags & SMB2_HDR_FLAG_SIGNED) {
+ /* Signed. Cannot recvfile. */
+ return false;
+ }
+
+ body = &state->pktbuf[SMB2_HDR_BODY];
+
+ file_id_persistent = BVAL(body, 0x10);
+ file_id_volatile = BVAL(body, 0x18);
+
+ status = smb2srv_open_lookup(state->req->xconn,
+ file_id_persistent,
+ file_id_volatile,
+ 0, /* now */
+ &op);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ fsp = op->compat;
+ if (fsp == NULL) {
+ return false;
+ }
+ if (fsp->conn == NULL) {
+ return false;
+ }
+
+ if (IS_IPC(fsp->conn)) {
+ return false;
+ }
+ if (IS_PRINT(fsp->conn)) {
+ return false;
+ }
+ if (fsp_is_alternate_stream(fsp)) {
+ return false;
+ }
+
+ DEBUG(10,("Doing recvfile write len = %u\n",
+ (unsigned int)(state->pktfull - state->pktlen)));
+
+ return true;
+}
+
+static NTSTATUS smbd_smb2_request_next_incoming(struct smbXsrv_connection *xconn)
+{
+ struct smbd_smb2_request_read_state *state = &xconn->smb2.request_read_state;
+ struct smbd_smb2_request *req = NULL;
+ size_t max_send_queue_len;
+ size_t cur_send_queue_len;
+
+ if (!NT_STATUS_IS_OK(xconn->transport.status)) {
+ /*
+ * we're not supposed to do any io
+ */
+ return NT_STATUS_OK;
+ }
+
+ if (state->req != NULL) {
+ /*
+ * if there is already a tstream_readv_pdu
+ * pending, we are done.
+ */
+ return NT_STATUS_OK;
+ }
+
+ max_send_queue_len = MAX(1, xconn->smb2.credits.max/16);
+ cur_send_queue_len = xconn->smb2.send_queue_len;
+
+ if (cur_send_queue_len > max_send_queue_len) {
+ /*
+ * if we have a lot of requests to send,
+ * we wait until they are on the wire until we
+ * ask for the next request.
+ */
+ return NT_STATUS_OK;
+ }
+
+ /* ask for the next request */
+ req = smbd_smb2_request_allocate(xconn);
+ if (req == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ *state = (struct smbd_smb2_request_read_state) {
+ .req = req,
+ .min_recv_size = lp_min_receive_file_size(),
+ ._vector = {
+ [0] = (struct iovec) {
+ .iov_base = (void *)state->hdr.nbt,
+ .iov_len = NBT_HDR_SIZE,
+ },
+ },
+ .vector = state->_vector,
+ .count = 1,
+ };
+
+ TEVENT_FD_READABLE(xconn->transport.fde);
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smbd_smb2_process_negprot(struct smbXsrv_connection *xconn,
+ uint64_t expected_seq_low,
+ const uint8_t *inpdu, size_t size)
+{
+ struct smbd_server_connection *sconn = xconn->client->sconn;
+ NTSTATUS status;
+ struct smbd_smb2_request *req = NULL;
+
+ DEBUG(10,("smbd_smb2_first_negprot: packet length %u\n",
+ (unsigned int)size));
+
+ status = smbd_initialize_smb2(xconn, expected_seq_low);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
+ return status;
+ }
+
+ /*
+ * If a new connection joins the process, when we're
+ * already in a "pending break cycle", we need to
+ * turn on the ack checker on the new connection.
+ */
+ status = smbXsrv_client_pending_breaks_updated(xconn->client);
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * If there's a problem, we disconnect the whole
+ * client with all connections here!
+ *
+ * Instead of just the new connection.
+ */
+ smbd_server_disconnect_client(xconn->client, nt_errstr(status));
+ return status;
+ }
+
+ status = smbd_smb2_request_create(xconn, inpdu, size, &req);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
+ return status;
+ }
+
+ status = smbd_smb2_request_validate(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
+ return status;
+ }
+
+ status = smbd_smb2_request_setup_out(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
+ return status;
+ }
+
+#ifdef WITH_PROFILE
+ /*
+ * this was already counted at the SMB1 layer =>
+ * smbd_smb2_request_dispatch() should not count it twice.
+ */
+ if (profile_p->values.request_stats.count > 0) {
+ profile_p->values.request_stats.count--;
+ }
+#endif
+ status = smbd_smb2_request_dispatch(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
+ return status;
+ }
+
+ status = smbd_smb2_request_next_incoming(xconn);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
+ return status;
+ }
+
+ sconn->num_requests++;
+ return NT_STATUS_OK;
+}
+
+static int socket_error_from_errno(int ret,
+ int sys_errno,
+ bool *retry)
+{
+ *retry = false;
+
+ if (ret >= 0) {
+ return 0;
+ }
+
+ if (ret != -1) {
+ return EIO;
+ }
+
+ if (sys_errno == 0) {
+ return EIO;
+ }
+
+ if (sys_errno == EINTR) {
+ *retry = true;
+ return sys_errno;
+ }
+
+ if (sys_errno == EINPROGRESS) {
+ *retry = true;
+ return sys_errno;
+ }
+
+ if (sys_errno == EAGAIN) {
+ *retry = true;
+ return sys_errno;
+ }
+
+ /* ENOMEM is retryable on Solaris/illumos, and possibly other systems. */
+ if (sys_errno == ENOMEM) {
+ *retry = true;
+ return sys_errno;
+ }
+
+#ifdef EWOULDBLOCK
+#if EWOULDBLOCK != EAGAIN
+ if (sys_errno == EWOULDBLOCK) {
+ *retry = true;
+ return sys_errno;
+ }
+#endif
+#endif
+
+ return sys_errno;
+}
+
+static NTSTATUS smbd_smb2_advance_send_queue(struct smbXsrv_connection *xconn,
+ struct smbd_smb2_send_queue **_e,
+ size_t n)
+{
+ struct smbd_smb2_send_queue *e = *_e;
+ bool ok;
+
+ xconn->ack.unacked_bytes += n;
+
+ ok = iov_advance(&e->vector, &e->count, n);
+ if (!ok) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (e->count > 0) {
+ return NT_STATUS_RETRY;
+ }
+
+ xconn->smb2.send_queue_len--;
+ DLIST_REMOVE(xconn->smb2.send_queue, e);
+
+ if (e->ack.req == NULL) {
+ *_e = NULL;
+ talloc_free(e->mem_ctx);
+ return NT_STATUS_OK;
+ }
+
+ e->ack.required_acked_bytes = xconn->ack.unacked_bytes;
+ DLIST_ADD_END(xconn->ack.queue, e);
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbd_smb2_flush_with_sendmsg(struct smbXsrv_connection *xconn)
+{
+ int ret;
+ int err;
+ bool retry;
+ NTSTATUS status;
+
+ if (xconn->smb2.send_queue == NULL) {
+ TEVENT_FD_NOT_WRITEABLE(xconn->transport.fde);
+ return NT_STATUS_OK;
+ }
+
+ while (xconn->smb2.send_queue != NULL) {
+ struct smbd_smb2_send_queue *e = xconn->smb2.send_queue;
+ unsigned sendmsg_flags = 0;
+
+ if (!NT_STATUS_IS_OK(xconn->transport.status)) {
+ /*
+ * we're not supposed to do any io
+ * just flush all pending stuff.
+ */
+ xconn->smb2.send_queue_len--;
+ DLIST_REMOVE(xconn->smb2.send_queue, e);
+
+ talloc_free(e->mem_ctx);
+ continue;
+ }
+
+ if (e->sendfile_header != NULL) {
+ size_t size = 0;
+ size_t i = 0;
+ uint8_t *buf;
+
+ status = NT_STATUS_INTERNAL_ERROR;
+
+ for (i=0; i < e->count; i++) {
+ size += e->vector[i].iov_len;
+ }
+
+ if (size <= e->sendfile_header->length) {
+ buf = e->sendfile_header->data;
+ } else {
+ buf = talloc_array(e->mem_ctx, uint8_t, size);
+ if (buf == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ size = 0;
+ for (i=0; i < e->count; i++) {
+ memcpy(buf+size,
+ e->vector[i].iov_base,
+ e->vector[i].iov_len);
+ size += e->vector[i].iov_len;
+ }
+
+ e->sendfile_header->data = buf;
+ e->sendfile_header->length = size;
+ e->sendfile_status = &status;
+ e->count = 0;
+
+ xconn->smb2.send_queue_len--;
+ DLIST_REMOVE(xconn->smb2.send_queue, e);
+
+ size += e->sendfile_body_size;
+
+ /*
+ * This triggers the sendfile path via
+ * the destructor.
+ */
+ talloc_free(e->mem_ctx);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ smbXsrv_connection_disconnect_transport(xconn,
+ status);
+ return status;
+ }
+ xconn->ack.unacked_bytes += size;
+ continue;
+ }
+
+ e->msg = (struct msghdr) {
+ .msg_iov = e->vector,
+ .msg_iovlen = e->count,
+ };
+
+#ifdef MSG_NOSIGNAL
+ sendmsg_flags |= MSG_NOSIGNAL;
+#endif
+#ifdef MSG_DONTWAIT
+ sendmsg_flags |= MSG_DONTWAIT;
+#endif
+
+ ret = sendmsg(xconn->transport.sock, &e->msg, sendmsg_flags);
+ if (ret == 0) {
+ /* propagate end of file */
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ err = socket_error_from_errno(ret, errno, &retry);
+ if (retry) {
+ /* retry later */
+ TEVENT_FD_WRITEABLE(xconn->transport.fde);
+ return NT_STATUS_OK;
+ }
+ if (err != 0) {
+ status = map_nt_error_from_unix_common(err);
+ smbXsrv_connection_disconnect_transport(xconn,
+ status);
+ return status;
+ }
+
+ status = smbd_smb2_advance_send_queue(xconn, &e, ret);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
+ /* retry later */
+ TEVENT_FD_WRITEABLE(xconn->transport.fde);
+ return NT_STATUS_OK;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ smbXsrv_connection_disconnect_transport(xconn,
+ status);
+ return status;
+ }
+ }
+
+ return NT_STATUS_MORE_PROCESSING_REQUIRED;
+}
+
+static NTSTATUS smbd_smb2_flush_send_queue(struct smbXsrv_connection *xconn)
+{
+ NTSTATUS status;
+
+ status = smbd_smb2_flush_with_sendmsg(xconn);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ return status;
+ }
+
+ /*
+ * Restart reads if we were blocked on
+ * draining the send queue.
+ */
+
+ status = smbd_smb2_request_next_incoming(xconn);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbd_smb2_advance_incoming(struct smbXsrv_connection *xconn, size_t n)
+{
+ struct smbd_server_connection *sconn = xconn->client->sconn;
+ struct smbd_smb2_request_read_state *state = &xconn->smb2.request_read_state;
+ struct smbd_smb2_request *req = NULL;
+ size_t min_recvfile_size = UINT32_MAX;
+ NTSTATUS status;
+ NTTIME now;
+ bool ok;
+
+ ok = iov_advance(&state->vector, &state->count, n);
+ if (!ok) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (state->count > 0) {
+ return NT_STATUS_PENDING;
+ }
+
+ if (state->pktlen > 0) {
+ if (!state->doing_receivefile) {
+ /*
+ * we have all the data.
+ */
+ goto got_full;
+ }
+
+ if (!is_smb2_recvfile_write(state)) {
+ size_t ofs = state->pktlen;
+
+ /*
+ * Not a possible receivefile write.
+ * Read the rest of the data.
+ */
+ state->doing_receivefile = false;
+
+ state->pktbuf = talloc_realloc(state->req,
+ state->pktbuf,
+ uint8_t,
+ state->pktfull);
+ if (state->pktbuf == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ state->_vector[0] = (struct iovec) {
+ .iov_base = (void *)(state->pktbuf + ofs),
+ .iov_len = (state->pktfull - ofs),
+ };
+ state->vector = state->_vector;
+ state->count = 1;
+
+ state->pktlen = state->pktfull;
+ return NT_STATUS_RETRY;
+ }
+
+ /*
+ * This is a receivefile write so we've
+ * done a short read.
+ */
+ goto got_full;
+ }
+
+ /*
+ * Now we analyze the NBT header
+ */
+ if (state->hdr.nbt[0] != 0x00) {
+ state->min_recv_size = 0;
+ }
+ state->pktfull = smb2_len(state->hdr.nbt);
+ if (state->pktfull == 0) {
+ goto got_full;
+ }
+
+ if (state->min_recv_size != 0) {
+ min_recvfile_size = SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN;
+ min_recvfile_size += state->min_recv_size;
+ }
+
+ if (state->pktfull > min_recvfile_size) {
+ /*
+ * Might be a receivefile write. Read the SMB2 HEADER +
+ * SMB2_WRITE header first. Set 'doing_receivefile'
+ * as we're *attempting* receivefile write. If this
+ * turns out not to be a SMB2_WRITE request or otherwise
+ * not suitable then we'll just read the rest of the data
+ * the next time this function is called.
+ */
+ state->pktlen = SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN;
+ state->doing_receivefile = true;
+ } else {
+ state->pktlen = state->pktfull;
+ }
+
+ state->pktbuf = talloc_array(state->req, uint8_t, state->pktlen);
+ if (state->pktbuf == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ state->_vector[0] = (struct iovec) {
+ .iov_base = (void *)state->pktbuf,
+ .iov_len = state->pktlen,
+ };
+ state->vector = state->_vector;
+ state->count = 1;
+
+ return NT_STATUS_RETRY;
+
+got_full:
+
+ if (state->hdr.nbt[0] != 0x00) {
+ DEBUG(1,("ignore NBT[0x%02X] msg\n",
+ state->hdr.nbt[0]));
+
+ req = state->req;
+ *state = (struct smbd_smb2_request_read_state) {
+ .req = req,
+ .min_recv_size = lp_min_receive_file_size(),
+ ._vector = {
+ [0] = (struct iovec) {
+ .iov_base = (void *)state->hdr.nbt,
+ .iov_len = NBT_HDR_SIZE,
+ },
+ },
+ .vector = state->_vector,
+ .count = 1,
+ };
+ return NT_STATUS_RETRY;
+ }
+
+ req = state->req;
+
+ req->request_time = timeval_current();
+ now = timeval_to_nttime(&req->request_time);
+
+ status = smbd_smb2_inbuf_parse_compound(xconn,
+ now,
+ state->pktbuf,
+ state->pktlen,
+ req,
+ &req->in.vector,
+ &req->in.vector_count);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (state->doing_receivefile) {
+ req->smb1req = talloc_zero(req, struct smb_request);
+ if (req->smb1req == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ req->smb1req->unread_bytes = state->pktfull - state->pktlen;
+ }
+
+ *state = (struct smbd_smb2_request_read_state) {
+ .req = NULL,
+ };
+
+ req->current_idx = 1;
+
+ DEBUG(10,("smbd_smb2_request idx[%d] of %d vectors\n",
+ req->current_idx, req->in.vector_count));
+
+ status = smbd_smb2_request_validate(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = smbd_smb2_request_setup_out(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = smbd_smb2_request_dispatch(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ sconn->num_requests++;
+
+ /* The timeout_processing function isn't run nearly
+ often enough to implement 'max log size' without
+ overrunning the size of the file by many megabytes.
+ This is especially true if we are running at debug
+ level 10. Checking every 50 SMB2s is a nice
+ tradeoff of performance vs log file size overrun. */
+
+ if ((sconn->num_requests % 50) == 0 &&
+ need_to_check_log_size()) {
+ change_to_root_user();
+ check_log_size();
+ }
+
+ status = smbd_smb2_request_next_incoming(xconn);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbd_smb2_io_handler(struct smbXsrv_connection *xconn,
+ uint16_t fde_flags)
+{
+ struct smbd_smb2_request_read_state *state = &xconn->smb2.request_read_state;
+ unsigned recvmsg_flags = 0;
+ int ret;
+ int err;
+ bool retry;
+ NTSTATUS status;
+
+ if (!NT_STATUS_IS_OK(xconn->transport.status)) {
+ /*
+ * we're not supposed to do any io
+ */
+ TEVENT_FD_NOT_READABLE(xconn->transport.fde);
+ TEVENT_FD_NOT_WRITEABLE(xconn->transport.fde);
+ TEVENT_FD_NOT_WANTERROR(xconn->transport.fde);
+ return NT_STATUS_OK;
+ }
+
+ if (fde_flags & TEVENT_FD_ERROR) {
+ ret = samba_socket_poll_or_sock_error(xconn->transport.sock);
+ if (ret == -1) {
+ err = errno;
+ status = map_nt_error_from_unix_common(err);
+ smbXsrv_connection_disconnect_transport(xconn,
+ status);
+ return status;
+ }
+ /* This should not happen */
+ status = NT_STATUS_REMOTE_DISCONNECT;
+ smbXsrv_connection_disconnect_transport(xconn,
+ status);
+ return status;
+ }
+
+ if (fde_flags & TEVENT_FD_WRITE) {
+ status = smbd_smb2_flush_send_queue(xconn);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ if (!(fde_flags & TEVENT_FD_READ)) {
+ return NT_STATUS_OK;
+ }
+
+ if (state->req == NULL) {
+ TEVENT_FD_NOT_READABLE(xconn->transport.fde);
+ return NT_STATUS_OK;
+ }
+
+again:
+
+ state->msg = (struct msghdr) {
+ .msg_iov = state->vector,
+ .msg_iovlen = state->count,
+ };
+
+#ifdef MSG_NOSIGNAL
+ recvmsg_flags |= MSG_NOSIGNAL;
+#endif
+#ifdef MSG_DONTWAIT
+ recvmsg_flags |= MSG_DONTWAIT;
+#endif
+
+ ret = recvmsg(xconn->transport.sock, &state->msg, recvmsg_flags);
+ if (ret == 0) {
+ /* propagate end of file */
+ status = NT_STATUS_END_OF_FILE;
+ smbXsrv_connection_disconnect_transport(xconn,
+ status);
+ return status;
+ }
+ err = socket_error_from_errno(ret, errno, &retry);
+ if (retry) {
+ /* retry later */
+ TEVENT_FD_READABLE(xconn->transport.fde);
+ return NT_STATUS_OK;
+ }
+ if (err != 0) {
+ status = map_nt_error_from_unix_common(err);
+ smbXsrv_connection_disconnect_transport(xconn,
+ status);
+ return status;
+ }
+
+ status = smbd_smb2_advance_incoming(xconn, ret);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_PENDING)) {
+ /* we have more to read */
+ TEVENT_FD_READABLE(xconn->transport.fde);
+ return NT_STATUS_OK;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
+ /*
+ * smbd_smb2_advance_incoming setup a new vector
+ * that we should try to read immediately.
+ */
+ goto again;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static void smbd_smb2_connection_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct smbXsrv_connection *xconn =
+ talloc_get_type_abort(private_data,
+ struct smbXsrv_connection);
+ NTSTATUS status;
+
+ status = smbd_smb2_io_handler(xconn, flags);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
+ return;
+ }
+}
diff --git a/source3/smbd/smb2_service.c b/source3/smbd/smb2_service.c
new file mode 100644
index 0000000..856974c
--- /dev/null
+++ b/source3/smbd/smb2_service.c
@@ -0,0 +1,951 @@
+/*
+ Unix SMB/CIFS implementation.
+ service (connection) opening and closing
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ 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 "system/filesys.h"
+#include "system/passwd.h" /* uid_wrapper */
+#include "../lib/tsocket/tsocket.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../librpc/gen_ndr/netlogon.h"
+#include "../libcli/security/security.h"
+#include "printing/pcap.h"
+#include "passdb/lookup_sid.h"
+#include "auth.h"
+#include "../auth/auth_util.h"
+#include "lib/param/loadparm.h"
+#include "messages.h"
+#include "lib/afs/afs_funcs.h"
+#include "lib/util_path.h"
+#include "lib/util/string_wrappers.h"
+#include "source3/lib/substitute.h"
+
+bool canonicalize_connect_path(connection_struct *conn)
+{
+ bool ret;
+ struct smb_filename con_fname = { .base_name = conn->connectpath };
+ struct smb_filename *resolved_fname = SMB_VFS_REALPATH(conn, talloc_tos(),
+ &con_fname);
+ if (resolved_fname == NULL) {
+ return false;
+ }
+ ret = set_conn_connectpath(conn,resolved_fname->base_name);
+ TALLOC_FREE(resolved_fname);
+ return ret;
+}
+
+/****************************************************************************
+ Ensure when setting connectpath it is a canonicalized (no ./ // or ../)
+ absolute path stating in / and not ending in /.
+****************************************************************************/
+
+bool set_conn_connectpath(connection_struct *conn, const char *connectpath)
+{
+ char *destname;
+
+ if (connectpath == NULL || connectpath[0] == '\0') {
+ return false;
+ }
+
+ destname = canonicalize_absolute_path(conn, connectpath);
+ if (destname == NULL) {
+ return false;
+ }
+
+ DBG_DEBUG("service %s, connectpath = %s\n",
+ lp_const_servicename(SNUM(conn)), destname);
+
+ talloc_free(conn->connectpath);
+ conn->connectpath = destname;
+ /*
+ * Ensure conn->cwd_fsp->fsp_name is initialized.
+ * start as conn->connectpath.
+ */
+ TALLOC_FREE(conn->cwd_fsp->fsp_name);
+ conn->cwd_fsp->fsp_name = synthetic_smb_fname(conn,
+ conn->connectpath,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (conn->cwd_fsp->fsp_name == NULL) {
+ return false;
+ }
+ return true;
+}
+
+bool chdir_current_service(connection_struct *conn)
+{
+ const struct smb_filename connectpath_fname = {
+ .base_name = conn->connectpath,
+ };
+ int saved_errno = 0;
+ char *utok_str = NULL;
+ int ret;
+
+ conn->lastused_count++;
+
+ ret = vfs_ChDir(conn, &connectpath_fname);
+ if (ret == 0) {
+ return true;
+ }
+ saved_errno = errno;
+
+ utok_str = utok_string(talloc_tos(),
+ conn->session_info->unix_token);
+ if (utok_str == NULL) {
+ errno = saved_errno;
+ return false;
+ }
+
+ DBG_ERR("vfs_ChDir(%s) failed: %s. Current token: %s\n",
+ conn->connectpath,
+ strerror(saved_errno),
+ utok_str);
+
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return false;
+}
+
+/****************************************************************************
+ do some basic sainity checks on the share.
+ This function modifies dev, ecode.
+****************************************************************************/
+
+static NTSTATUS share_sanity_checks(const struct tsocket_address *local_address,
+ const struct tsocket_address *remote_address,
+ const char *rhost,
+ int snum,
+ fstring dev)
+{
+ char *raddr;
+
+ if (!lp_allow_local_address(snum, local_address)) {
+ char *laddr = NULL;
+
+ laddr = tsocket_address_inet_addr_string(
+ local_address, talloc_tos());
+ if (laddr == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ raddr = tsocket_address_inet_addr_string(
+ remote_address, laddr);
+ if (raddr == NULL) {
+ TALLOC_FREE(laddr);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DBG_ERR("Denied connection from %s (%s) to \\\\%s\\%s\n",
+ rhost, raddr, laddr, lp_const_servicename(snum));
+ TALLOC_FREE(laddr);
+
+ return NT_STATUS_BAD_NETWORK_NAME;
+ }
+
+ raddr = tsocket_address_inet_addr_string(remote_address,
+ talloc_tos());
+ if (raddr == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!lp_snum_ok(snum) ||
+ !allow_access(lp_hosts_deny(snum), lp_hosts_allow(snum),
+ rhost, raddr)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (dev[0] == '?' || !dev[0]) {
+ if (lp_printable(snum)) {
+ fstrcpy(dev,"LPT1:");
+ } else if (strequal(lp_fstype(snum), "IPC")) {
+ fstrcpy(dev, "IPC");
+ } else {
+ fstrcpy(dev,"A:");
+ }
+ }
+
+ if (!strupper_m(dev)) {
+ DBG_WARNING("strupper_m %s failed\n", dev);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (lp_printable(snum)) {
+ if (!strequal(dev, "LPT1:")) {
+ return NT_STATUS_BAD_DEVICE_TYPE;
+ }
+ } else if (strequal(lp_fstype(snum), "IPC")) {
+ if (!strequal(dev, "IPC")) {
+ return NT_STATUS_BAD_DEVICE_TYPE;
+ }
+ } else if (!strequal(dev, "A:")) {
+ return NT_STATUS_BAD_DEVICE_TYPE;
+ }
+
+ /* Behave as a printer if we are supposed to */
+ if (lp_printable(snum) && (strcmp(dev, "A:") == 0)) {
+ fstrcpy(dev, "LPT1:");
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * Go through lookup_name etc to find the force'd group.
+ *
+ * Create a new token from src_token, replacing the primary group sid with the
+ * one found.
+ */
+
+static NTSTATUS find_forced_group(bool force_user,
+ int snum, const char *username,
+ struct dom_sid *pgroup_sid,
+ gid_t *pgid)
+{
+ NTSTATUS result = NT_STATUS_NO_SUCH_GROUP;
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct dom_sid group_sid;
+ enum lsa_SidType type;
+ char *groupname;
+ bool user_must_be_member = False;
+ gid_t gid;
+
+ groupname = lp_force_group(talloc_tos(), lp_sub, snum);
+ if (groupname == NULL) {
+ DBG_WARNING("talloc_strdup failed\n");
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ if (groupname[0] == '+') {
+ user_must_be_member = True;
+ groupname += 1;
+ }
+
+ groupname = talloc_string_sub(talloc_tos(), groupname,
+ "%S", lp_const_servicename(snum));
+ if (groupname == NULL) {
+ DBG_WARNING("talloc_string_sub failed\n");
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ if (!lookup_name_smbconf(talloc_tos(), groupname,
+ LOOKUP_NAME_ALL|LOOKUP_NAME_GROUP,
+ NULL, NULL, &group_sid, &type)) {
+ DBG_DEBUG("lookup_name_smbconf(%s) failed\n",
+ groupname);
+ goto done;
+ }
+
+ if ((type != SID_NAME_DOM_GRP) && (type != SID_NAME_ALIAS) &&
+ (type != SID_NAME_WKN_GRP)) {
+ DBG_DEBUG("%s is a %s, not a group\n", groupname,
+ sid_type_lookup(type));
+ goto done;
+ }
+
+ if (!sid_to_gid(&group_sid, &gid)) {
+ struct dom_sid_buf buf;
+ DBG_DEBUG("sid_to_gid(%s) for %s failed\n",
+ dom_sid_str_buf(&group_sid, &buf), groupname);
+ goto done;
+ }
+
+ /*
+ * If the user has been forced and the forced group starts with a '+',
+ * then we only set the group to be the forced group if the forced
+ * user is a member of that group. Otherwise, the meaning of the '+'
+ * would be ignored.
+ */
+
+ if (force_user && user_must_be_member) {
+ if (user_in_group_sid(username, &group_sid)) {
+ sid_copy(pgroup_sid, &group_sid);
+ *pgid = gid;
+ DBG_INFO("Forced group %s for member %s\n",
+ groupname, username);
+ } else {
+ DBG_ERR("find_forced_group: forced user %s is not a member "
+ "of forced group %s. Disallowing access.\n",
+ username, groupname );
+ result = NT_STATUS_MEMBER_NOT_IN_GROUP;
+ goto done;
+ }
+ } else {
+ sid_copy(pgroup_sid, &group_sid);
+ *pgid = gid;
+ DBG_INFO("Forced group %s\n", groupname);
+ }
+
+ result = NT_STATUS_OK;
+ done:
+ TALLOC_FREE(frame);
+ return result;
+}
+
+/****************************************************************************
+ Create an auth_session_info structure for a connection_struct
+****************************************************************************/
+
+static NTSTATUS create_connection_session_info(struct smbd_server_connection *sconn,
+ TALLOC_CTX *mem_ctx, int snum,
+ struct auth_session_info *session_info,
+ struct auth_session_info **presult)
+{
+ struct auth_session_info *result;
+
+ if (lp_guest_only(snum)) {
+ return make_session_info_guest(mem_ctx, presult);
+ }
+
+ /*
+ * This is the normal security != share case where we have a
+ * valid vuid from the session setup. */
+
+ if (security_session_user_level(session_info, NULL) < SECURITY_USER) {
+ if (!lp_guest_ok(snum)) {
+ DBG_WARNING("guest user (from session setup) "
+ "not permitted to access this share "
+ "(%s)\n", lp_const_servicename(snum));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ } else {
+ if (!user_ok_token(session_info->unix_info->unix_name,
+ session_info->info->domain_name,
+ session_info->security_token, snum)) {
+ DBG_WARNING("user '%s' (from session setup) not "
+ "permitted to access this share "
+ "(%s)\n",
+ session_info->unix_info->unix_name,
+ lp_const_servicename(snum));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+
+ result = copy_session_info(mem_ctx, session_info);
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *presult = result;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Set relevant user and group settings corresponding to force user/group
+ configuration for the given snum.
+****************************************************************************/
+
+NTSTATUS set_conn_force_user_group(connection_struct *conn, int snum)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ NTSTATUS status;
+
+ if (*lp_force_user(talloc_tos(), lp_sub, snum)) {
+
+ /*
+ * Replace conn->session_info with a completely faked up one
+ * from the username we are forced into :-)
+ */
+
+ char *fuser;
+ char *sanitized_username;
+ struct auth_session_info *forced_serverinfo;
+ bool guest;
+
+ fuser = talloc_string_sub(conn, lp_force_user(talloc_tos(), lp_sub, snum), "%S",
+ lp_const_servicename(snum));
+ if (fuser == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ guest = security_session_user_level(conn->session_info, NULL) < SECURITY_USER;
+
+ status = make_session_info_from_username(
+ conn, fuser,
+ guest,
+ &forced_serverinfo);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* We don't want to replace the original sanitized_username
+ as it is the original user given in the connect attempt.
+ This is used in '%U' substitutions. */
+ sanitized_username = discard_const_p(char,
+ forced_serverinfo->unix_info->sanitized_username);
+ TALLOC_FREE(sanitized_username);
+ forced_serverinfo->unix_info->sanitized_username =
+ talloc_move(forced_serverinfo->unix_info,
+ &conn->session_info->unix_info->sanitized_username);
+
+ TALLOC_FREE(conn->session_info);
+ conn->session_info = forced_serverinfo;
+
+ conn->force_user = true;
+ DBG_INFO("Forced user %s\n", fuser);
+ }
+
+ /*
+ * If force group is true, then override
+ * any groupid stored for the connecting user.
+ */
+
+ if (*lp_force_group(talloc_tos(), lp_sub, snum)) {
+
+ status = find_forced_group(
+ conn->force_user, snum, conn->session_info->unix_info->unix_name,
+ &conn->session_info->security_token->sids[1],
+ &conn->session_info->unix_token->gid);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * We need to cache this gid, to use within
+ * change_to_user() separately from the conn->session_info
+ * struct. We only use conn->session_info directly if
+ * "force_user" was set.
+ */
+ conn->force_group_gid = conn->session_info->unix_token->gid;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS notify_init_sconn(struct smbd_server_connection *sconn)
+{
+ NTSTATUS status;
+
+ if (sconn->notify_ctx != NULL) {
+ return NT_STATUS_OK;
+ }
+
+ sconn->notify_ctx = notify_init(sconn, sconn->msg_ctx,
+ sconn, notify_callback);
+ if (sconn->notify_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = messaging_register(sconn->msg_ctx, sconn,
+ MSG_SMB_NOTIFY_CANCEL_DELETED,
+ smbd_notify_cancel_deleted);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("messaging_register failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(sconn->notify_ctx);
+ return status;
+ }
+
+ status = messaging_register(sconn->msg_ctx, sconn,
+ MSG_SMB_NOTIFY_STARTED,
+ smbd_notifyd_restarted);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("messaging_register failed: %s\n",
+ nt_errstr(status));
+ messaging_deregister(sconn->msg_ctx,
+ MSG_SMB_NOTIFY_CANCEL_DELETED, sconn);
+ TALLOC_FREE(sconn->notify_ctx);
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Make a connection, given the snum to connect to, and the vuser of the
+ connecting user if appropriate.
+****************************************************************************/
+
+NTSTATUS make_connection_snum(struct smbXsrv_connection *xconn,
+ connection_struct *conn,
+ int snum,
+ struct smbXsrv_session *session,
+ const char *pdev)
+{
+ struct smbd_server_connection *sconn = xconn->client->sconn;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct smb_filename *smb_fname_cpath = NULL;
+ fstring dev;
+ int ret;
+ bool on_err_call_dis_hook = false;
+ uid_t effuid;
+ gid_t effgid;
+ NTSTATUS status;
+ bool ok;
+
+ fstrcpy(dev, pdev);
+
+ status = share_sanity_checks(sconn->local_address,
+ sconn->remote_address,
+ sconn->remote_hostname,
+ snum,
+ dev);
+ if (NT_STATUS_IS_ERR(status)) {
+ goto err_root_exit;
+ }
+
+ conn->params->service = snum;
+
+ status = create_connection_session_info(sconn,
+ conn, snum, session->global->auth_session_info,
+ &conn->session_info);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("create_connection_session_info failed: %s\n",
+ nt_errstr(status));
+ goto err_root_exit;
+ }
+
+ if (lp_guest_only(snum)) {
+ conn->force_user = true;
+ }
+
+ conn->num_files_open = 0;
+ conn->lastused = conn->lastused_count = time(NULL);
+ conn->printer = (strncmp(dev,"LPT",3) == 0);
+ conn->ipc = ( (strncmp(dev,"IPC",3) == 0) ||
+ ( lp_enable_asu_support() && strequal(dev,"ADMIN$")) );
+
+ /* Case options for the share. */
+ conn_setup_case_options(conn);
+
+ conn->encrypt_level = lp_server_smb_encrypt(snum);
+ if (conn->encrypt_level > SMB_ENCRYPTION_OFF) {
+ if (lp_server_smb_encrypt(-1) == SMB_ENCRYPTION_OFF) {
+ if (conn->encrypt_level == SMB_ENCRYPTION_REQUIRED) {
+ DBG_ERR("Service [%s] requires encryption, but "
+ "it is disabled globally!\n",
+ lp_const_servicename(snum));
+ status = NT_STATUS_ACCESS_DENIED;
+ goto err_root_exit;
+ }
+ conn->encrypt_level = SMB_ENCRYPTION_OFF;
+ }
+ }
+
+ conn->veto_list = NULL;
+ conn->hide_list = NULL;
+ conn->veto_oplock_list = NULL;
+ conn->aio_write_behind_list = NULL;
+
+ conn->read_only = lp_read_only(SNUM(conn));
+
+ status = set_conn_force_user_group(conn, snum);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_root_exit;
+ }
+
+ conn->vuid = session->global->session_wire_id;
+
+ {
+ char *s = talloc_sub_full(talloc_tos(),
+ lp_const_servicename(SNUM(conn)),
+ conn->session_info->unix_info->unix_name,
+ conn->connectpath,
+ conn->session_info->unix_token->gid,
+ conn->session_info->unix_info->sanitized_username,
+ conn->session_info->info->domain_name,
+ lp_path(talloc_tos(), lp_sub, snum));
+ if (!s) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err_root_exit;
+ }
+
+ if (!set_conn_connectpath(conn,s)) {
+ TALLOC_FREE(s);
+ status = NT_STATUS_NO_MEMORY;
+ goto err_root_exit;
+ }
+ DBG_NOTICE("Connect path is '%s' for service [%s]\n", s,
+ lp_const_servicename(snum));
+ TALLOC_FREE(s);
+ }
+
+ /*
+ * Set up the share security descriptor.
+ * NOTE - we use the *INCOMING USER* session_info
+ * here, as does (indirectly) change_to_user(),
+ * which can be called on any incoming packet.
+ * This way we set up the share access based
+ * on the authenticated user, not the forced
+ * user. See bug:
+ *
+ * https://bugzilla.samba.org/show_bug.cgi?id=9878
+ */
+
+ status = check_user_share_access(conn,
+ session->global->auth_session_info,
+ &conn->share_access,
+ &conn->read_only);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_root_exit;
+ }
+
+ /* Initialise VFS function pointers */
+
+ if (!smbd_vfs_init(conn)) {
+ DBG_ERR("vfs_init failed for service %s\n",
+ lp_const_servicename(snum));
+ status = NT_STATUS_BAD_NETWORK_NAME;
+ goto err_root_exit;
+ }
+
+/* ROOT Activities: */
+ /* explicitly check widelinks here so that we can correctly warn
+ * in the logs. */
+ widelinks_warning(snum);
+
+ /* Invoke VFS make connection hook - this must be the first
+ filesystem operation that we do. */
+
+ if (SMB_VFS_CONNECT(conn, lp_const_servicename(snum),
+ conn->session_info->unix_info->unix_name) < 0) {
+ DBG_WARNING("SMB_VFS_CONNECT for service '%s' at '%s' failed: %s\n",
+ lp_const_servicename(snum), conn->connectpath,
+ strerror(errno));
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto err_root_exit;
+ }
+
+ /* Any error exit after here needs to call the disconnect hook. */
+ on_err_call_dis_hook = true;
+
+ if ((!conn->printer) && (!conn->ipc) &&
+ lp_change_notify()) {
+
+ status = notify_init_sconn(sconn);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_root_exit;
+ }
+ }
+
+ if (lp_kernel_oplocks(snum)) {
+ init_kernel_oplocks(conn->sconn);
+ }
+
+ /*
+ * Fix compatibility issue pointed out by Volker.
+ * We pass the conn->connectpath to the preexec
+ * scripts as a parameter, so attempt to canonicalize
+ * it here before calling the preexec scripts.
+ * We ignore errors here, as it is possible that
+ * the conn->connectpath doesn't exist yet and
+ * the preexec scripts will create them.
+ */
+
+ (void)canonicalize_connect_path(conn);
+
+ /* Preexecs are done here as they might make the dir we are to ChDir
+ * to below */
+ /* execute any "root preexec = " line */
+ if (*lp_root_preexec(talloc_tos(), lp_sub, snum)) {
+ char *cmd = talloc_sub_full(talloc_tos(),
+ lp_const_servicename(SNUM(conn)),
+ conn->session_info->unix_info->unix_name,
+ conn->connectpath,
+ conn->session_info->unix_token->gid,
+ conn->session_info->unix_info->sanitized_username,
+ conn->session_info->info->domain_name,
+ lp_root_preexec(talloc_tos(), lp_sub, snum));
+ DBG_INFO("cmd=%s\n",cmd);
+ ret = smbrun(cmd, NULL, NULL);
+ TALLOC_FREE(cmd);
+ if (ret != 0 && lp_root_preexec_close(snum)) {
+ DBG_WARNING("root preexec gave %d - failing "
+ "connection\n", ret);
+ status = NT_STATUS_ACCESS_DENIED;
+ goto err_root_exit;
+ }
+ }
+
+/* USER Activities: */
+ if (!change_to_user_and_service(conn, conn->vuid)) {
+ /* No point continuing if they fail the basic checks */
+ DBG_ERR("Can't become connected user!\n");
+ status = NT_STATUS_LOGON_FAILURE;
+ goto err_root_exit;
+ }
+
+ effuid = geteuid();
+ effgid = getegid();
+
+ /* Remember that a different vuid can connect later without these
+ * checks... */
+
+ /* Preexecs are done here as they might make the dir we are to ChDir
+ * to below */
+
+ /* execute any "preexec = " line */
+ if (*lp_preexec(talloc_tos(), lp_sub, snum)) {
+ char *cmd = talloc_sub_full(talloc_tos(),
+ lp_const_servicename(SNUM(conn)),
+ conn->session_info->unix_info->unix_name,
+ conn->connectpath,
+ conn->session_info->unix_token->gid,
+ conn->session_info->unix_info->sanitized_username,
+ conn->session_info->info->domain_name,
+ lp_preexec(talloc_tos(), lp_sub, snum));
+ ret = smbrun(cmd, NULL, NULL);
+ TALLOC_FREE(cmd);
+ if (ret != 0 && lp_preexec_close(snum)) {
+ DBG_WARNING("preexec gave %d - failing connection\n",
+ ret);
+ status = NT_STATUS_ACCESS_DENIED;
+ goto err_root_exit;
+ }
+ }
+
+#ifdef WITH_FAKE_KASERVER
+ if (lp_afs_share(snum)) {
+ afs_login(conn);
+ }
+#endif
+
+ /*
+ * we've finished with the user stuff - go back to root
+ * so the SMB_VFS_STAT call will only fail on path errors,
+ * not permission problems.
+ */
+ change_to_root_user();
+/* ROOT Activities: */
+
+ /*
+ * Canonicalise the connect
+ * path here to ensure we don't have any symlinks in the
+ * connectpath. We will be checking all paths on this connection are
+ * below this directory. We must do this after the VFS init as we
+ * depend on the realpath() pointer in the vfs table. JRA.
+ */
+ ok = canonicalize_connect_path(conn);
+ if (!ok) {
+ DBG_ERR("canonicalize_connect_path failed "
+ "for service %s, path %s\n",
+ lp_const_servicename(snum),
+ conn->connectpath);
+ status = NT_STATUS_BAD_NETWORK_NAME;
+ goto err_root_exit;
+ }
+
+ /* Add veto/hide lists */
+ if (!IS_IPC(conn) && !IS_PRINT(conn)) {
+ set_namearray( &conn->veto_list,
+ lp_veto_files(talloc_tos(), lp_sub, snum));
+ set_namearray( &conn->hide_list,
+ lp_hide_files(talloc_tos(), lp_sub, snum));
+ set_namearray( &conn->veto_oplock_list,
+ lp_veto_oplock_files(talloc_tos(), lp_sub, snum));
+ set_namearray( &conn->aio_write_behind_list,
+ lp_aio_write_behind(talloc_tos(), lp_sub, snum));
+ }
+ smb_fname_cpath = synthetic_smb_fname(talloc_tos(),
+ conn->connectpath,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname_cpath == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err_root_exit;
+ }
+
+ /* win2000 does not check the permissions on the directory
+ during the tree connect, instead relying on permission
+ check during individual operations. To match this behaviour
+ I have disabled this chdir check (tridge) */
+ /* the alternative is just to check the directory exists */
+
+ if ((ret = SMB_VFS_STAT(conn, smb_fname_cpath)) != 0 ||
+ !S_ISDIR(smb_fname_cpath->st.st_ex_mode)) {
+ if (ret == 0 && !S_ISDIR(smb_fname_cpath->st.st_ex_mode)) {
+ DBG_ERR("'%s' is not a directory, when connecting to "
+ "[%s]\n", conn->connectpath,
+ lp_const_servicename(snum));
+ } else {
+ DBG_ERR("'%s' does not exist or permission denied "
+ "when connecting to [%s] Error was %s\n",
+ conn->connectpath,
+ lp_const_servicename(snum),
+ strerror(errno));
+ }
+ status = NT_STATUS_BAD_NETWORK_NAME;
+ goto err_root_exit;
+ }
+ conn->base_share_dev = smb_fname_cpath->st.st_ex_dev;
+
+ /* Figure out the characteristics of the underlying filesystem. This
+ * assumes that all the filesystem mounted within a share path have
+ * the same characteristics, which is likely but not guaranteed.
+ */
+
+ if(!IS_IPC(conn) ){
+ conn->fs_capabilities = SMB_VFS_FS_CAPABILITIES(conn, &conn->ts_res);
+ }
+ /*
+ * Print out the 'connected as' stuff here as we need
+ * to know the effective uid and gid we will be using
+ * (at least initially).
+ */
+
+ if( DEBUGLVL( IS_IPC(conn) ? DBGLVL_INFO : DBGLVL_NOTICE ) ) {
+ bool signing_active;
+
+ dbgtext( "%s (%s) ", get_remote_machine_name(),
+ tsocket_address_string(conn->sconn->remote_address,
+ talloc_tos()) );
+#if defined(WITH_SMB1SERVER)
+ if (sconn->using_smb2) {
+#endif
+ signing_active = smb2_signing_key_valid(
+ session->global->encryption_key);
+#if defined(WITH_SMB1SERVER)
+ } else {
+ signing_active = smb1_srv_is_signing_active(xconn);
+ }
+#endif
+ dbgtext( "%s", signing_active ? "signed " : "");
+ dbgtext( "connect to service %s ",
+ lp_const_servicename(snum) );
+ dbgtext( "initially as user %s ",
+ conn->session_info->unix_info->unix_name );
+ dbgtext( "(uid=%d, gid=%d) ", (int)effuid, (int)effgid );
+ dbgtext( "(pid %d)\n", (int)getpid() );
+ }
+
+ conn->tcon_done = true;
+ return NT_STATUS_OK;
+
+ err_root_exit:
+
+ TALLOC_FREE(smb_fname_cpath);
+ /* We must exit this function as root. */
+ if (geteuid() != 0) {
+ change_to_root_user();
+ }
+ if (on_err_call_dis_hook) {
+ /* Call VFS disconnect hook */
+ SMB_VFS_DISCONNECT(conn);
+ }
+ return status;
+}
+
+/****************************************************************************
+ Make a connection to a service from SMB2. External SMB2 interface.
+ We must set cnum before claiming connection.
+****************************************************************************/
+
+connection_struct *make_connection_smb2(struct smbd_smb2_request *req,
+ struct smbXsrv_tcon *tcon,
+ int snum,
+ const char *pdev,
+ NTSTATUS *pstatus)
+{
+ struct smbd_server_connection *sconn = req->sconn;
+ connection_struct *conn = conn_new(sconn);
+ if (!conn) {
+ DBG_ERR("make_connection_smb2: Couldn't find free connection.\n");
+ *pstatus = NT_STATUS_INSUFFICIENT_RESOURCES;
+ return NULL;
+ }
+
+ conn->cnum = tcon->global->tcon_wire_id;
+ conn->tcon = tcon;
+
+ *pstatus = make_connection_snum(req->xconn,
+ conn,
+ snum,
+ req->session,
+ pdev);
+ if (!NT_STATUS_IS_OK(*pstatus)) {
+ conn_free(conn);
+ return NULL;
+ }
+ return conn;
+}
+
+/****************************************************************************
+ Close a cnum.
+****************************************************************************/
+
+void close_cnum(connection_struct *conn,
+ uint64_t vuid,
+ enum file_close_type close_type)
+{
+ char rootpath[2] = { '/', '\0'};
+ struct smb_filename root_fname = { .base_name = rootpath };
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ file_close_conn(conn, close_type);
+
+ change_to_root_user();
+
+ DEBUG(IS_IPC(conn)?DBGLVL_INFO:DBGLVL_NOTICE, ("%s (%s) closed connection to service %s\n",
+ get_remote_machine_name(),
+ tsocket_address_string(conn->sconn->remote_address,
+ talloc_tos()),
+ lp_const_servicename(SNUM(conn))));
+
+ /* make sure we leave the directory available for unmount */
+ vfs_ChDir(conn, &root_fname);
+
+ /* Call VFS disconnect hook */
+ SMB_VFS_DISCONNECT(conn);
+
+ /* execute any "postexec = " line */
+ if (*lp_postexec(talloc_tos(), lp_sub, SNUM(conn)) &&
+ change_to_user_and_service(conn, vuid)) {
+ char *cmd = talloc_sub_full(talloc_tos(),
+ lp_const_servicename(SNUM(conn)),
+ conn->session_info->unix_info->unix_name,
+ conn->connectpath,
+ conn->session_info->unix_token->gid,
+ conn->session_info->unix_info->sanitized_username,
+ conn->session_info->info->domain_name,
+ lp_postexec(talloc_tos(), lp_sub, SNUM(conn)));
+ smbrun(cmd, NULL, NULL);
+ TALLOC_FREE(cmd);
+ change_to_root_user();
+ }
+
+ change_to_root_user();
+ /* execute any "root postexec = " line */
+ if (*lp_root_postexec(talloc_tos(), lp_sub, SNUM(conn))) {
+ char *cmd = talloc_sub_full(talloc_tos(),
+ lp_const_servicename(SNUM(conn)),
+ conn->session_info->unix_info->unix_name,
+ conn->connectpath,
+ conn->session_info->unix_token->gid,
+ conn->session_info->unix_info->sanitized_username,
+ conn->session_info->info->domain_name,
+ lp_root_postexec(talloc_tos(), lp_sub, SNUM(conn)));
+ smbrun(cmd, NULL, NULL);
+ TALLOC_FREE(cmd);
+ }
+
+ conn_free(conn);
+}
diff --git a/source3/smbd/smb2_sesssetup.c b/source3/smbd/smb2_sesssetup.c
new file mode 100644
index 0000000..ac71e55
--- /dev/null
+++ b/source3/smbd/smb2_sesssetup.c
@@ -0,0 +1,1373 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+ Copyright (C) Jeremy Allison 2010
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../auth/gensec/gensec.h"
+#include "auth.h"
+#include "../lib/tsocket/tsocket.h"
+#include "../libcli/security/security.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "source3/lib/substitute.h"
+
+#include "lib/crypto/gnutls_helpers.h"
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static struct tevent_req *smbd_smb2_session_setup_wrap_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ uint64_t in_session_id,
+ uint8_t in_flags,
+ uint8_t in_security_mode,
+ uint64_t in_previous_session_id,
+ DATA_BLOB in_security_buffer);
+static NTSTATUS smbd_smb2_session_setup_wrap_recv(struct tevent_req *req,
+ uint16_t *out_session_flags,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out_security_buffer,
+ uint64_t *out_session_id);
+
+static void smbd_smb2_request_sesssetup_done(struct tevent_req *subreq);
+
+NTSTATUS smbd_smb2_request_process_sesssetup(struct smbd_smb2_request *smb2req)
+{
+ const uint8_t *inhdr;
+ const uint8_t *inbody;
+ uint64_t in_session_id;
+ uint8_t in_flags;
+ uint8_t in_security_mode;
+ uint64_t in_previous_session_id;
+ uint16_t in_security_offset;
+ uint16_t in_security_length;
+ DATA_BLOB in_security_buffer;
+ NTSTATUS status;
+ struct tevent_req *subreq;
+
+ status = smbd_smb2_request_verify_sizes(smb2req, 0x19);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(smb2req, status);
+ }
+ inhdr = SMBD_SMB2_IN_HDR_PTR(smb2req);
+ inbody = SMBD_SMB2_IN_BODY_PTR(smb2req);
+
+ in_session_id = BVAL(inhdr, SMB2_HDR_SESSION_ID);
+
+ in_flags = CVAL(inbody, 0x02);
+ in_security_mode = CVAL(inbody, 0x03);
+ /* Capabilities = IVAL(inbody, 0x04) */
+ /* Channel = IVAL(inbody, 0x08) */
+ in_security_offset = SVAL(inbody, 0x0C);
+ in_security_length = SVAL(inbody, 0x0E);
+ in_previous_session_id = BVAL(inbody, 0x10);
+
+ if (in_security_offset != (SMB2_HDR_BODY + SMBD_SMB2_IN_BODY_LEN(smb2req))) {
+ return smbd_smb2_request_error(smb2req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ if (in_security_length > SMBD_SMB2_IN_DYN_LEN(smb2req)) {
+ return smbd_smb2_request_error(smb2req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ in_security_buffer.data = SMBD_SMB2_IN_DYN_PTR(smb2req);
+ in_security_buffer.length = in_security_length;
+
+ subreq = smbd_smb2_session_setup_wrap_send(smb2req,
+ smb2req->sconn->ev_ctx,
+ smb2req,
+ in_session_id,
+ in_flags,
+ in_security_mode,
+ in_previous_session_id,
+ in_security_buffer);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(smb2req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_sesssetup_done, smb2req);
+
+ /*
+ * Avoid sending a STATUS_PENDING message, which
+ * matches a Windows Server and avoids problems with
+ * MacOS clients.
+ *
+ * Even after 90 seconds a Windows Server doesn't return
+ * STATUS_PENDING if using NTLMSSP against a non reachable
+ * trusted domain.
+ */
+ return smbd_smb2_request_pending_queue(smb2req, subreq, 0);
+}
+
+static void smbd_smb2_request_sesssetup_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *smb2req =
+ tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ uint8_t *outhdr;
+ DATA_BLOB outbody;
+ DATA_BLOB outdyn;
+ uint16_t out_session_flags = 0;
+ uint64_t out_session_id = 0;
+ uint16_t out_security_offset;
+ DATA_BLOB out_security_buffer = data_blob_null;
+ NTSTATUS status;
+ NTSTATUS error; /* transport error */
+
+ status = smbd_smb2_session_setup_wrap_recv(subreq,
+ &out_session_flags,
+ smb2req,
+ &out_security_buffer,
+ &out_session_id);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ status = nt_status_squash(status);
+ error = smbd_smb2_request_error(smb2req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ out_security_offset = SMB2_HDR_BODY + 0x08;
+
+ outhdr = SMBD_SMB2_OUT_HDR_PTR(smb2req);
+
+ outbody = smbd_smb2_generate_outbody(smb2req, 0x08);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(smb2req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SBVAL(outhdr, SMB2_HDR_SESSION_ID, out_session_id);
+
+ SSVAL(outbody.data, 0x00, 0x08 + 1); /* struct size */
+ SSVAL(outbody.data, 0x02,
+ out_session_flags); /* session flags */
+ SSVAL(outbody.data, 0x04,
+ out_security_offset); /* security buffer offset */
+ SSVAL(outbody.data, 0x06,
+ out_security_buffer.length); /* security buffer length */
+
+ outdyn = out_security_buffer;
+
+ error = smbd_smb2_request_done_ex(smb2req, status, outbody, &outdyn,
+ __location__);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+static NTSTATUS smbd_smb2_auth_generic_return(struct smbXsrv_session *session,
+ struct smbXsrv_session_auth0 **_auth,
+ struct smbd_smb2_request *smb2req,
+ uint8_t in_security_mode,
+ struct auth_session_info *session_info,
+ uint16_t *out_session_flags,
+ uint64_t *out_session_id)
+{
+ NTSTATUS status;
+ bool guest = false;
+ struct smbXsrv_session *x = session;
+ struct smbXsrv_session_auth0 *auth = *_auth;
+ struct smbXsrv_connection *xconn = smb2req->xconn;
+ size_t i;
+ struct smb2_signing_derivations derivations = {
+ .signing = NULL,
+ };
+ DATA_BLOB preauth_hash = data_blob_null;
+
+ *_auth = NULL;
+
+ if (xconn->protocol >= PROTOCOL_SMB3_11) {
+ struct smbXsrv_preauth *preauth;
+ gnutls_hash_hd_t hash_hnd;
+ int rc;
+
+ preauth = talloc_move(smb2req, &auth->preauth);
+
+ rc = gnutls_hash_init(&hash_hnd, GNUTLS_DIG_SHA512);
+ if (rc < 0) {
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
+ }
+ rc = gnutls_hash(hash_hnd,
+ preauth->sha512_value,
+ sizeof(preauth->sha512_value));
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ for (i = 1; i < smb2req->in.vector_count; i++) {
+ rc = gnutls_hash(hash_hnd,
+ smb2req->in.vector[i].iov_base,
+ smb2req->in.vector[i].iov_len);
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+ gnutls_hash_deinit(hash_hnd, preauth->sha512_value);
+
+ preauth_hash = data_blob_const(preauth->sha512_value,
+ sizeof(preauth->sha512_value));
+ }
+
+ smb2_signing_derivations_fill_const_stack(&derivations,
+ xconn->protocol,
+ preauth_hash);
+
+ if ((in_security_mode & SMB2_NEGOTIATE_SIGNING_REQUIRED) ||
+ (xconn->smb2.server.security_mode & SMB2_NEGOTIATE_SIGNING_REQUIRED))
+ {
+ x->global->signing_flags = SMBXSRV_SIGNING_REQUIRED;
+ }
+
+ if ((lp_server_smb_encrypt(-1) >= SMB_ENCRYPTION_DESIRED) &&
+ (xconn->smb2.client.capabilities & SMB2_CAP_ENCRYPTION)) {
+ x->global->encryption_flags = SMBXSRV_ENCRYPTION_DESIRED;
+ }
+
+ if (lp_server_smb_encrypt(-1) == SMB_ENCRYPTION_REQUIRED) {
+ x->global->encryption_flags = SMBXSRV_ENCRYPTION_REQUIRED |
+ SMBXSRV_ENCRYPTION_DESIRED;
+ }
+
+ if (security_session_user_level(session_info, NULL) < SECURITY_USER) {
+ if (security_session_user_level(session_info, NULL) == SECURITY_GUEST) {
+ *out_session_flags |= SMB2_SESSION_FLAG_IS_GUEST;
+ }
+ /* force no signing */
+ x->global->signing_flags &= ~SMBXSRV_SIGNING_REQUIRED;
+ /* we map anonymous to guest internally */
+ guest = true;
+ }
+
+ if (guest && (x->global->encryption_flags & SMBXSRV_ENCRYPTION_REQUIRED)) {
+ DEBUG(1,("reject guest session as encryption is required\n"));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (xconn->smb2.server.cipher == 0) {
+ if (x->global->encryption_flags & SMBXSRV_ENCRYPTION_REQUIRED) {
+ DEBUG(1,("reject session with dialect[0x%04X] "
+ "as encryption is required\n",
+ xconn->smb2.server.dialect));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+ x->global->signing_algo = xconn->smb2.server.sign_algo;
+ x->global->encryption_cipher = xconn->smb2.server.cipher;
+ if (guest) {
+ x->global->encryption_cipher = SMB2_ENCRYPTION_NONE;
+ }
+
+ if (x->global->encryption_flags & SMBXSRV_ENCRYPTION_DESIRED) {
+ *out_session_flags |= SMB2_SESSION_FLAG_ENCRYPT_DATA;
+ }
+
+ status = smb2_signing_key_sign_create(x->global,
+ x->global->signing_algo,
+ &session_info->session_key,
+ derivations.signing,
+ &x->global->signing_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ x->global->signing_key_blob = x->global->signing_key->blob;
+
+ if (x->global->encryption_cipher != SMB2_ENCRYPTION_NONE) {
+ size_t nonce_size;
+
+ status = smb2_signing_key_cipher_create(x->global,
+ x->global->encryption_cipher,
+ &session_info->session_key,
+ derivations.cipher_s2c,
+ &x->global->encryption_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ x->global->encryption_key_blob = x->global->encryption_key->blob;
+
+ status = smb2_signing_key_cipher_create(x->global,
+ x->global->encryption_cipher,
+ &session_info->session_key,
+ derivations.cipher_c2s,
+ &x->global->decryption_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ x->global->decryption_key_blob = x->global->decryption_key->blob;
+
+ /*
+ * CCM and GCM algorithms must never have their
+ * nonce wrap, or the security of the whole
+ * communication and the keys is destroyed.
+ * We must drop the connection once we have
+ * transferred too much data.
+ *
+ * NOTE: We assume nonces greater than 8 bytes.
+ */
+ generate_nonce_buffer((uint8_t *)&x->nonce_high_random,
+ sizeof(x->nonce_high_random));
+ switch (xconn->smb2.server.cipher) {
+ case SMB2_ENCRYPTION_AES128_CCM:
+ nonce_size = SMB2_AES_128_CCM_NONCE_SIZE;
+ break;
+ case SMB2_ENCRYPTION_AES128_GCM:
+ nonce_size = gnutls_cipher_get_iv_size(GNUTLS_CIPHER_AES_128_GCM);
+ break;
+ case SMB2_ENCRYPTION_AES256_CCM:
+ nonce_size = SMB2_AES_128_CCM_NONCE_SIZE;
+ break;
+ case SMB2_ENCRYPTION_AES256_GCM:
+ nonce_size = gnutls_cipher_get_iv_size(GNUTLS_CIPHER_AES_256_GCM);
+ break;
+ default:
+ nonce_size = 0;
+ break;
+ }
+ x->nonce_high_max = SMB2_NONCE_HIGH_MAX(nonce_size);
+ x->nonce_high = 0;
+ x->nonce_low = 0;
+ }
+
+ status = smb2_signing_key_sign_create(x->global,
+ x->global->signing_algo,
+ &session_info->session_key,
+ derivations.application,
+ &x->global->application_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ x->global->application_key_blob = x->global->application_key->blob;
+
+ if (xconn->protocol >= PROTOCOL_SMB3_00 && lp_debug_encryption()) {
+ DEBUG(0, ("debug encryption: dumping generated session keys\n"));
+ DEBUGADD(0, ("Session Id "));
+ dump_data(0, (uint8_t*)&session->global->session_wire_id,
+ sizeof(session->global->session_wire_id));
+ DEBUGADD(0, ("Session Key "));
+ dump_data(0, session_info->session_key.data,
+ session_info->session_key.length);
+ DEBUGADD(0, ("Signing Algo: %u\n", x->global->signing_algo));
+ DEBUGADD(0, ("Signing Key "));
+ dump_data(0, x->global->signing_key_blob.data,
+ x->global->signing_key_blob.length);
+ DEBUGADD(0, ("App Key "));
+ dump_data(0, x->global->application_key_blob.data,
+ x->global->application_key_blob.length);
+
+ /* In server code, ServerIn is the decryption key */
+
+ DEBUGADD(0, ("Cipher Algo: %u\n", x->global->encryption_cipher));
+ DEBUGADD(0, ("ServerIn Key "));
+ dump_data(0, x->global->decryption_key_blob.data,
+ x->global->decryption_key_blob.length);
+ DEBUGADD(0, ("ServerOut Key "));
+ dump_data(0, x->global->encryption_key_blob.data,
+ x->global->encryption_key_blob.length);
+ }
+
+ status = smb2_signing_key_copy(x->global->channels,
+ x->global->signing_key,
+ &x->global->channels[0].signing_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ x->global->channels[0].signing_key_blob =
+ x->global->channels[0].signing_key->blob;
+ x->global->channels[0].signing_algo = x->global->signing_algo;
+ x->global->channels[0].encryption_cipher = x->global->encryption_cipher;
+
+ data_blob_clear_free(&session_info->session_key);
+ session_info->session_key = data_blob_dup_talloc(session_info,
+ x->global->application_key_blob);
+ if (session_info->session_key.data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ talloc_keep_secret(session_info->session_key.data);
+
+ smb2req->sconn->num_users++;
+
+ if (security_session_user_level(session_info, NULL) >= SECURITY_USER) {
+ session->homes_snum =
+ register_homes_share(session_info->unix_info->unix_name);
+ }
+
+ set_current_user_info(session_info->unix_info->sanitized_username,
+ session_info->unix_info->unix_name,
+ session_info->info->domain_name);
+
+ reload_services(smb2req->sconn, conn_snum_used, true);
+
+ session->status = NT_STATUS_OK;
+ session->global->auth_session_info = talloc_move(session->global,
+ &session_info);
+ session->global->auth_session_info_seqnum += 1;
+ for (i=0; i < session->global->num_channels; i++) {
+ struct smbXsrv_channel_global0 *_c =
+ &session->global->channels[i];
+
+ _c->auth_session_info_seqnum =
+ session->global->auth_session_info_seqnum;
+ }
+ session->global->auth_time = timeval_to_nttime(&smb2req->request_time);
+ session->global->expiration_time = gensec_expire_time(auth->gensec);
+
+ if (!session_claim(session)) {
+ DEBUG(1, ("smb2: Failed to claim session "
+ "for vuid=%llu\n",
+ (unsigned long long)session->global->session_wire_id));
+ return NT_STATUS_LOGON_FAILURE;
+ }
+
+ TALLOC_FREE(auth);
+ status = smbXsrv_session_update(session);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("smb2: Failed to update session for vuid=%llu - %s\n",
+ (unsigned long long)session->global->session_wire_id,
+ nt_errstr(status)));
+ return NT_STATUS_LOGON_FAILURE;
+ }
+
+ /*
+ * we attach the session to the request
+ * so that the response can be signed
+ */
+ if (!guest) {
+ smb2req->do_signing = true;
+ }
+
+ global_client_caps |= (CAP_LEVEL_II_OPLOCKS|CAP_STATUS32);
+
+ *out_session_id = session->global->session_wire_id;
+ smb2req->last_session_id = session->global->session_wire_id;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbd_smb2_reauth_generic_return(struct smbXsrv_session *session,
+ struct smbXsrv_session_auth0 **_auth,
+ struct smbd_smb2_request *smb2req,
+ struct auth_session_info *session_info,
+ uint16_t *out_session_flags,
+ uint64_t *out_session_id)
+{
+ NTSTATUS status;
+ struct smbXsrv_session *x = session;
+ struct smbXsrv_session_auth0 *auth = *_auth;
+ struct smbXsrv_connection *xconn = smb2req->xconn;
+ size_t i;
+
+ *_auth = NULL;
+
+ data_blob_clear_free(&session_info->session_key);
+ session_info->session_key = data_blob_dup_talloc(session_info,
+ x->global->application_key_blob);
+ if (session_info->session_key.data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ talloc_keep_secret(session_info->session_key.data);
+
+ session->homes_snum =
+ register_homes_share(session_info->unix_info->unix_name);
+
+ set_current_user_info(session_info->unix_info->sanitized_username,
+ session_info->unix_info->unix_name,
+ session_info->info->domain_name);
+
+ reload_services(smb2req->sconn, conn_snum_used, true);
+
+ if (security_session_user_level(session_info, NULL) >= SECURITY_USER) {
+ smb2req->do_signing = true;
+ }
+
+ session->status = NT_STATUS_OK;
+ TALLOC_FREE(session->global->auth_session_info);
+ session->global->auth_session_info = talloc_move(session->global,
+ &session_info);
+ session->global->auth_session_info_seqnum += 1;
+ for (i=0; i < session->global->num_channels; i++) {
+ struct smbXsrv_channel_global0 *_c =
+ &session->global->channels[i];
+
+ _c->auth_session_info_seqnum =
+ session->global->auth_session_info_seqnum;
+ }
+ session->global->auth_time = timeval_to_nttime(&smb2req->request_time);
+ session->global->expiration_time = gensec_expire_time(auth->gensec);
+
+ TALLOC_FREE(auth);
+ status = smbXsrv_session_update(session);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("smb2: Failed to update session for vuid=%llu - %s\n",
+ (unsigned long long)session->global->session_wire_id,
+ nt_errstr(status)));
+ return NT_STATUS_LOGON_FAILURE;
+ }
+
+ conn_clear_vuid_caches(xconn->client->sconn,
+ session->global->session_wire_id);
+
+ *out_session_id = session->global->session_wire_id;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbd_smb2_bind_auth_return(struct smbXsrv_session *session,
+ struct smbXsrv_session_auth0 **_auth,
+ struct smbd_smb2_request *smb2req,
+ struct auth_session_info *session_info,
+ uint16_t *out_session_flags,
+ uint64_t *out_session_id)
+{
+ NTSTATUS status;
+ struct smbXsrv_session *x = session;
+ struct smbXsrv_session_auth0 *auth = *_auth;
+ struct smbXsrv_connection *xconn = smb2req->xconn;
+ struct smbXsrv_channel_global0 *c = NULL;
+ size_t i;
+ struct smb2_signing_derivations derivations = {
+ .signing = NULL,
+ };
+ DATA_BLOB preauth_hash = data_blob_null;
+ bool ok;
+
+ *_auth = NULL;
+
+ if (xconn->protocol >= PROTOCOL_SMB3_11) {
+ struct smbXsrv_preauth *preauth;
+ gnutls_hash_hd_t hash_hnd = NULL;
+ int rc;
+
+ preauth = talloc_move(smb2req, &auth->preauth);
+
+ rc = gnutls_hash_init(&hash_hnd, GNUTLS_DIG_SHA512);
+ if (rc < 0) {
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
+ }
+
+ rc = gnutls_hash(hash_hnd,
+ preauth->sha512_value,
+ sizeof(preauth->sha512_value));
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
+ }
+ for (i = 1; i < smb2req->in.vector_count; i++) {
+ rc = gnutls_hash(hash_hnd,
+ smb2req->in.vector[i].iov_base,
+ smb2req->in.vector[i].iov_len);
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
+ }
+ }
+ gnutls_hash_deinit(hash_hnd, preauth->sha512_value);
+
+ preauth_hash = data_blob_const(preauth->sha512_value,
+ sizeof(preauth->sha512_value));
+ }
+
+ smb2_signing_derivations_fill_const_stack(&derivations,
+ xconn->protocol,
+ preauth_hash);
+
+ status = smbXsrv_session_find_channel(session, xconn, &c);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ ok = security_token_is_sid(session_info->security_token,
+ &x->global->auth_session_info->security_token->sids[0]);
+ if (!ok) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (session_info->session_key.length == 0) {
+ /* See [MS-SMB2] 3.3.5.2.4 for the return code. */
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ c->signing_algo = xconn->smb2.server.sign_algo;
+ c->encryption_cipher = xconn->smb2.server.cipher;
+
+ status = smb2_signing_key_sign_create(x->global->channels,
+ c->signing_algo,
+ &session_info->session_key,
+ derivations.signing,
+ &c->signing_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ c->signing_key_blob = c->signing_key->blob;
+
+ TALLOC_FREE(auth);
+ status = smbXsrv_session_update(session);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("smb2: Failed to update session for vuid=%llu - %s\n",
+ (unsigned long long)session->global->session_wire_id,
+ nt_errstr(status)));
+ return NT_STATUS_LOGON_FAILURE;
+ }
+
+ *out_session_id = session->global->session_wire_id;
+
+ return NT_STATUS_OK;
+}
+
+struct smbd_smb2_session_setup_state {
+ struct tevent_context *ev;
+ struct smbd_smb2_request *smb2req;
+ uint64_t in_session_id;
+ uint8_t in_flags;
+ uint8_t in_security_mode;
+ uint64_t in_previous_session_id;
+ DATA_BLOB in_security_buffer;
+ struct smbXsrv_session *session;
+ struct smbXsrv_session_auth0 *auth;
+ struct auth_session_info *session_info;
+ uint16_t out_session_flags;
+ DATA_BLOB out_security_buffer;
+ uint64_t out_session_id;
+};
+
+static void smbd_smb2_session_setup_gensec_done(struct tevent_req *subreq);
+static void smbd_smb2_session_setup_previous_done(struct tevent_req *subreq);
+static void smbd_smb2_session_setup_auth_return(struct tevent_req *req);
+
+static struct tevent_req *smbd_smb2_session_setup_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ uint64_t in_session_id,
+ uint8_t in_flags,
+ uint8_t in_security_mode,
+ uint64_t in_previous_session_id,
+ DATA_BLOB in_security_buffer)
+{
+ struct tevent_req *req;
+ struct smbd_smb2_session_setup_state *state;
+ NTSTATUS status;
+ NTTIME now = timeval_to_nttime(&smb2req->request_time);
+ struct tevent_req *subreq;
+ struct smbXsrv_channel_global0 *c = NULL;
+ enum security_user_level seclvl;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_session_setup_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->smb2req = smb2req;
+ state->in_session_id = in_session_id;
+ state->in_flags = in_flags;
+ state->in_security_mode = in_security_mode;
+ state->in_previous_session_id = in_previous_session_id;
+ state->in_security_buffer = in_security_buffer;
+
+ if (in_flags & SMB2_SESSION_FLAG_BINDING) {
+ if (in_session_id == 0) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ if (smb2req->session == NULL) {
+ tevent_req_nterror(req, NT_STATUS_USER_SESSION_DELETED);
+ return tevent_req_post(req, ev);
+ }
+
+ if ((smb2req->session->global->signing_algo >= SMB2_SIGNING_AES128_GMAC) &&
+ (smb2req->xconn->smb2.server.sign_algo != smb2req->session->global->signing_algo))
+ {
+ tevent_req_nterror(req, NT_STATUS_REQUEST_OUT_OF_SEQUENCE);
+ return tevent_req_post(req, ev);
+ }
+ if ((smb2req->xconn->smb2.server.sign_algo >= SMB2_SIGNING_AES128_GMAC) &&
+ (smb2req->session->global->signing_algo != smb2req->xconn->smb2.server.sign_algo))
+ {
+ tevent_req_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return tevent_req_post(req, ev);
+ }
+
+ if (smb2req->xconn->protocol < PROTOCOL_SMB3_00) {
+ tevent_req_nterror(req, NT_STATUS_REQUEST_NOT_ACCEPTED);
+ return tevent_req_post(req, ev);
+ }
+
+ if (!smb2req->xconn->client->server_multi_channel_enabled) {
+ tevent_req_nterror(req, NT_STATUS_REQUEST_NOT_ACCEPTED);
+ return tevent_req_post(req, ev);
+ }
+
+ if (!smb2req->do_signing) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ if (smb2req->session->global->connection_dialect
+ != smb2req->xconn->smb2.server.dialect)
+ {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ if (smb2req->session->global->encryption_cipher
+ != smb2req->xconn->smb2.server.cipher)
+ {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ status = smb2req->session->status;
+ if (NT_STATUS_EQUAL(status, NT_STATUS_BAD_LOGON_SESSION_STATE)) {
+ /*
+ * This comes from smb2srv_session_lookup_global().
+ * And it's a cross node/cross smbd session bind,
+ * which can't work in our architecture.
+ *
+ * Returning NT_STATUS_REQUEST_NOT_ACCEPTED is better
+ * than NT_STATUS_USER_SESSION_DELETED in order to
+ * avoid a completely new session.
+ */
+ tevent_req_nterror(req, NT_STATUS_REQUEST_NOT_ACCEPTED);
+ return tevent_req_post(req, ev);
+ }
+
+ status = smbXsrv_session_find_channel(smb2req->session,
+ smb2req->xconn,
+ &c);
+ if (NT_STATUS_IS_OK(status)) {
+ if (!smb2_signing_key_valid(c->signing_key)) {
+ goto auth;
+ }
+ tevent_req_nterror(req, NT_STATUS_REQUEST_NOT_ACCEPTED);
+ return tevent_req_post(req, ev);
+ }
+
+ seclvl = security_session_user_level(
+ smb2req->session->global->auth_session_info,
+ NULL);
+ if (seclvl < SECURITY_USER) {
+ tevent_req_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return tevent_req_post(req, ev);
+ }
+
+ status = smbXsrv_session_add_channel(smb2req->session,
+ smb2req->xconn,
+ now,
+ &c);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ status = smbXsrv_session_update(smb2req->session);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+auth:
+
+ if (state->in_session_id == 0) {
+ /* create a new session */
+ status = smbXsrv_session_create(state->smb2req->xconn,
+ now, &state->session);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ smb2req->session = state->session;
+ } else {
+ if (smb2req->session == NULL) {
+ tevent_req_nterror(req, NT_STATUS_USER_SESSION_DELETED);
+ return tevent_req_post(req, ev);
+ }
+
+ state->session = smb2req->session;
+ status = state->session->status;
+ if (NT_STATUS_EQUAL(status, NT_STATUS_BAD_LOGON_SESSION_STATE)) {
+ /*
+ * This comes from smb2srv_session_lookup_global().
+ */
+ tevent_req_nterror(req, NT_STATUS_USER_SESSION_DELETED);
+ return tevent_req_post(req, ev);
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_SESSION_EXPIRED)) {
+ status = NT_STATUS_OK;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ status = NT_STATUS_OK;
+ }
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ status = smbXsrv_session_find_channel(smb2req->session,
+ smb2req->xconn, &c);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (!(in_flags & SMB2_SESSION_FLAG_BINDING)) {
+ state->session->status = NT_STATUS_MORE_PROCESSING_REQUIRED;
+ }
+
+ status = smbXsrv_session_find_auth(state->session, smb2req->xconn,
+ now, &state->auth);
+ if (!NT_STATUS_IS_OK(status)) {
+ status = smbXsrv_session_create_auth(state->session,
+ smb2req->xconn, now,
+ in_flags, in_security_mode,
+ &state->auth);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ if (state->auth->gensec == NULL) {
+ status = auth_generic_prepare(state->auth,
+ state->smb2req->xconn->remote_address,
+ state->smb2req->xconn->local_address,
+ "SMB2",
+ &state->auth->gensec);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ gensec_want_feature(state->auth->gensec, GENSEC_FEATURE_SESSION_KEY);
+ gensec_want_feature(state->auth->gensec, GENSEC_FEATURE_UNIX_TOKEN);
+ gensec_want_feature(state->auth->gensec, GENSEC_FEATURE_SMB_TRANSPORT);
+
+ status = gensec_start_mech_by_oid(state->auth->gensec,
+ GENSEC_OID_SPNEGO);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ status = smbXsrv_session_update(state->session);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ become_root();
+ subreq = gensec_update_send(state, state->ev,
+ state->auth->gensec,
+ state->in_security_buffer);
+ unbecome_root();
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_session_setup_gensec_done, req);
+
+ return req;
+}
+
+static void smbd_smb2_session_setup_gensec_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smbd_smb2_session_setup_state *state =
+ tevent_req_data(req,
+ struct smbd_smb2_session_setup_state);
+ NTSTATUS status;
+
+ become_root();
+ status = gensec_update_recv(subreq, state,
+ &state->out_security_buffer);
+ unbecome_root();
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED) &&
+ !NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ state->out_session_id = state->session->global->session_wire_id;
+ state->smb2req->preauth = state->auth->preauth;
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ status = gensec_session_info(state->auth->gensec,
+ state,
+ &state->session_info);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if ((state->in_previous_session_id != 0) &&
+ (state->session->global->session_wire_id !=
+ state->in_previous_session_id))
+ {
+ subreq = smb2srv_session_close_previous_send(state, state->ev,
+ state->smb2req->xconn,
+ state->session_info,
+ state->in_previous_session_id,
+ state->session->global->session_wire_id);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ smbd_smb2_session_setup_previous_done,
+ req);
+ return;
+ }
+
+ smbd_smb2_session_setup_auth_return(req);
+}
+
+static void smbd_smb2_session_setup_previous_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ NTSTATUS status;
+
+ status = smb2srv_session_close_previous_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ smbd_smb2_session_setup_auth_return(req);
+}
+
+static void smbd_smb2_session_setup_auth_return(struct tevent_req *req)
+{
+ struct smbd_smb2_session_setup_state *state =
+ tevent_req_data(req,
+ struct smbd_smb2_session_setup_state);
+ NTSTATUS status;
+
+ if (state->in_flags & SMB2_SESSION_FLAG_BINDING) {
+ status = smbd_smb2_bind_auth_return(state->session,
+ &state->auth,
+ state->smb2req,
+ state->session_info,
+ &state->out_session_flags,
+ &state->out_session_id);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+ return;
+ }
+
+ if (state->session->global->auth_session_info != NULL) {
+ status = smbd_smb2_reauth_generic_return(state->session,
+ &state->auth,
+ state->smb2req,
+ state->session_info,
+ &state->out_session_flags,
+ &state->out_session_id);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+ return;
+ }
+
+ status = smbd_smb2_auth_generic_return(state->session,
+ &state->auth,
+ state->smb2req,
+ state->in_security_mode,
+ state->session_info,
+ &state->out_session_flags,
+ &state->out_session_id);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+static NTSTATUS smbd_smb2_session_setup_recv(struct tevent_req *req,
+ uint16_t *out_session_flags,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out_security_buffer,
+ uint64_t *out_session_id)
+{
+ struct smbd_smb2_session_setup_state *state =
+ tevent_req_data(req,
+ struct smbd_smb2_session_setup_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ tevent_req_received(req);
+ return nt_status_squash(status);
+ }
+ } else {
+ status = NT_STATUS_OK;
+ }
+
+ *out_session_flags = state->out_session_flags;
+ *out_security_buffer = state->out_security_buffer;
+ *out_session_id = state->out_session_id;
+
+ talloc_steal(mem_ctx, out_security_buffer->data);
+ tevent_req_received(req);
+ return status;
+}
+
+struct smbd_smb2_session_setup_wrap_state {
+ struct tevent_context *ev;
+ struct smbd_smb2_request *smb2req;
+ uint64_t in_session_id;
+ uint8_t in_flags;
+ uint8_t in_security_mode;
+ uint64_t in_previous_session_id;
+ DATA_BLOB in_security_buffer;
+ uint16_t out_session_flags;
+ DATA_BLOB out_security_buffer;
+ uint64_t out_session_id;
+ NTSTATUS error;
+};
+
+static void smbd_smb2_session_setup_wrap_setup_done(struct tevent_req *subreq);
+static void smbd_smb2_session_setup_wrap_shutdown_done(struct tevent_req *subreq);
+
+static struct tevent_req *smbd_smb2_session_setup_wrap_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ uint64_t in_session_id,
+ uint8_t in_flags,
+ uint8_t in_security_mode,
+ uint64_t in_previous_session_id,
+ DATA_BLOB in_security_buffer)
+{
+ struct tevent_req *req;
+ struct smbd_smb2_session_setup_wrap_state *state;
+ struct tevent_req *subreq;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_session_setup_wrap_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->smb2req = smb2req;
+ state->in_session_id = in_session_id;
+ state->in_flags = in_flags;
+ state->in_security_mode = in_security_mode;
+ state->in_previous_session_id = in_previous_session_id;
+ state->in_security_buffer = in_security_buffer;
+
+ subreq = smbd_smb2_session_setup_send(state, state->ev,
+ state->smb2req,
+ state->in_session_id,
+ state->in_flags,
+ state->in_security_mode,
+ state->in_previous_session_id,
+ state->in_security_buffer);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ smbd_smb2_session_setup_wrap_setup_done, req);
+
+ return req;
+}
+
+static void smbd_smb2_session_setup_wrap_setup_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smbd_smb2_session_setup_wrap_state *state =
+ tevent_req_data(req,
+ struct smbd_smb2_session_setup_wrap_state);
+ NTSTATUS status;
+
+ status = smbd_smb2_session_setup_recv(subreq,
+ &state->out_session_flags,
+ state,
+ &state->out_security_buffer,
+ &state->out_session_id);
+ TALLOC_FREE(subreq);
+ if (NT_STATUS_IS_OK(status)) {
+ tevent_req_done(req);
+ return;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ if (state->smb2req->session == NULL) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ state->error = status;
+
+ if (state->in_flags & SMB2_SESSION_FLAG_BINDING) {
+ status = smbXsrv_session_remove_channel(state->smb2req->session,
+ state->smb2req->xconn);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_nterror(req, state->error);
+ return;
+ }
+
+ if (NT_STATUS_EQUAL(state->error, NT_STATUS_USER_SESSION_DELETED)) {
+ tevent_req_nterror(req, state->error);
+ return;
+ }
+
+ subreq = smb2srv_session_shutdown_send(state, state->ev,
+ state->smb2req->session,
+ state->smb2req);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ smbd_smb2_session_setup_wrap_shutdown_done,
+ req);
+}
+
+static void smbd_smb2_session_setup_wrap_shutdown_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smbd_smb2_session_setup_wrap_state *state =
+ tevent_req_data(req,
+ struct smbd_smb2_session_setup_wrap_state);
+ NTSTATUS status;
+
+ status = smb2srv_session_shutdown_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ /*
+ * we may need to sign the response, so we need to keep
+ * the session until the response is sent to the wire.
+ */
+ talloc_steal(state->smb2req, state->smb2req->session);
+
+ tevent_req_nterror(req, state->error);
+}
+
+static NTSTATUS smbd_smb2_session_setup_wrap_recv(struct tevent_req *req,
+ uint16_t *out_session_flags,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out_security_buffer,
+ uint64_t *out_session_id)
+{
+ struct smbd_smb2_session_setup_wrap_state *state =
+ tevent_req_data(req,
+ struct smbd_smb2_session_setup_wrap_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ tevent_req_received(req);
+ return nt_status_squash(status);
+ }
+ } else {
+ status = NT_STATUS_OK;
+ }
+
+ *out_session_flags = state->out_session_flags;
+ *out_security_buffer = state->out_security_buffer;
+ *out_session_id = state->out_session_id;
+
+ talloc_steal(mem_ctx, out_security_buffer->data);
+ tevent_req_received(req);
+ return status;
+}
+
+static struct tevent_req *smbd_smb2_logoff_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req);
+static NTSTATUS smbd_smb2_logoff_recv(struct tevent_req *req);
+static void smbd_smb2_request_logoff_done(struct tevent_req *subreq);
+
+NTSTATUS smbd_smb2_request_process_logoff(struct smbd_smb2_request *req)
+{
+ NTSTATUS status;
+ struct tevent_req *subreq = NULL;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x04);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ subreq = smbd_smb2_logoff_send(req, req->sconn->ev_ctx, req);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_logoff_done, req);
+
+ /*
+ * Avoid sending a STATUS_PENDING message, it's very likely
+ * the client won't expect that.
+ */
+ return smbd_smb2_request_pending_queue(req, subreq, 0);
+}
+
+static void smbd_smb2_request_logoff_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *smb2req =
+ tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ DATA_BLOB outbody;
+ NTSTATUS status;
+ NTSTATUS error;
+
+ status = smbd_smb2_logoff_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ error = smbd_smb2_request_error(smb2req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ outbody = smbd_smb2_generate_outbody(smb2req, 0x04);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(smb2req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x04); /* struct size */
+ SSVAL(outbody.data, 0x02, 0); /* reserved */
+
+ error = smbd_smb2_request_done(smb2req, outbody, NULL);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+struct smbd_smb2_logoff_state {
+ struct smbd_smb2_request *smb2req;
+};
+
+static void smbd_smb2_logoff_shutdown_done(struct tevent_req *subreq);
+
+static struct tevent_req *smbd_smb2_logoff_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req)
+{
+ struct tevent_req *req;
+ struct smbd_smb2_logoff_state *state;
+ struct tevent_req *subreq;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_logoff_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->smb2req = smb2req;
+
+ subreq = smb2srv_session_shutdown_send(state, ev,
+ smb2req->session,
+ smb2req);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_logoff_shutdown_done, req);
+
+ return req;
+}
+
+static void smbd_smb2_logoff_shutdown_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smbd_smb2_logoff_state *state = tevent_req_data(
+ req, struct smbd_smb2_logoff_state);
+ NTSTATUS status;
+ bool ok;
+ const struct GUID *client_guid =
+ &state->smb2req->session->client->global->client_guid;
+
+ status = smb2srv_session_shutdown_recv(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ TALLOC_FREE(subreq);
+
+ if (!GUID_all_zero(client_guid)) {
+ ok = remote_arch_cache_delete(client_guid);
+ if (!ok) {
+ /* Most likely not an error, but not in cache */
+ DBG_DEBUG("Deletion from remote arch cache failed\n");
+ }
+ }
+
+ /*
+ * As we've been awoken, we may have changed
+ * uid in the meantime. Ensure we're still
+ * root (SMB2_OP_LOGOFF has .as_root = true).
+ */
+ change_to_root_user();
+
+ status = smbXsrv_session_logoff(state->smb2req->session);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ /*
+ * we may need to sign the response, so we need to keep
+ * the session until the response is sent to the wire.
+ */
+ talloc_steal(state->smb2req, state->smb2req->session);
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS smbd_smb2_logoff_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
diff --git a/source3/smbd/smb2_setinfo.c b/source3/smbd/smb2_setinfo.c
new file mode 100644
index 0000000..f26fce7
--- /dev/null
+++ b/source3/smbd/smb2_setinfo.c
@@ -0,0 +1,628 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+ Copyright (C) Jeremy Allison 2010
+
+ 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 "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "trans2.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "../librpc/gen_ndr/open_files.h"
+#include "source3/lib/dbwrap/dbwrap_watch.h"
+#include "messages.h"
+#include "librpc/gen_ndr/ndr_quota.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static struct tevent_req *smbd_smb2_setinfo_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *in_fsp,
+ uint8_t in_info_type,
+ uint8_t in_file_info_class,
+ DATA_BLOB in_input_buffer,
+ uint32_t in_additional_information);
+static NTSTATUS smbd_smb2_setinfo_recv(struct tevent_req *req);
+
+static void smbd_smb2_request_setinfo_done(struct tevent_req *subreq);
+NTSTATUS smbd_smb2_request_process_setinfo(struct smbd_smb2_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ NTSTATUS status;
+ const uint8_t *inbody;
+ uint8_t in_info_type;
+ uint8_t in_file_info_class;
+ uint16_t in_input_buffer_offset;
+ uint32_t in_input_buffer_length;
+ DATA_BLOB in_input_buffer;
+ uint32_t in_additional_information;
+ uint64_t in_file_id_persistent;
+ uint64_t in_file_id_volatile;
+ struct files_struct *in_fsp;
+ struct tevent_req *subreq;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x21);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ in_info_type = CVAL(inbody, 0x02);
+ in_file_info_class = CVAL(inbody, 0x03);
+ in_input_buffer_length = IVAL(inbody, 0x04);
+ in_input_buffer_offset = SVAL(inbody, 0x08);
+ /* 0x0A 2 bytes reserved */
+ in_additional_information = IVAL(inbody, 0x0C);
+ in_file_id_persistent = BVAL(inbody, 0x10);
+ in_file_id_volatile = BVAL(inbody, 0x18);
+
+ if (in_input_buffer_offset == 0 && in_input_buffer_length == 0) {
+ /* This is ok */
+ } else if (in_input_buffer_offset !=
+ (SMB2_HDR_BODY + SMBD_SMB2_IN_BODY_LEN(req))) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ if (in_input_buffer_length > SMBD_SMB2_IN_DYN_LEN(req)) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ in_input_buffer.data = SMBD_SMB2_IN_DYN_PTR(req);
+ in_input_buffer.length = in_input_buffer_length;
+
+ if (in_input_buffer.length > xconn->smb2.server.max_trans) {
+ DEBUG(2,("smbd_smb2_request_process_setinfo: "
+ "client ignored max trans: %s: 0x%08X: 0x%08X\n",
+ __location__, (unsigned)in_input_buffer.length,
+ (unsigned)xconn->smb2.server.max_trans));
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ status = smbd_smb2_request_verify_creditcharge(req,
+ in_input_buffer.length);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ in_fsp = file_fsp_smb2(req, in_file_id_persistent, in_file_id_volatile);
+ if (in_fsp == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED);
+ }
+
+ subreq = smbd_smb2_setinfo_send(req, req->sconn->ev_ctx,
+ req, in_fsp,
+ in_info_type,
+ in_file_info_class,
+ in_input_buffer,
+ in_additional_information);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_setinfo_done, req);
+
+ return smbd_smb2_request_pending_queue(req, subreq, 500);
+}
+
+static void smbd_smb2_request_setinfo_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *req = tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ DATA_BLOB outbody;
+ NTSTATUS status;
+ NTSTATUS error; /* transport error */
+
+ status = smbd_smb2_setinfo_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ error = smbd_smb2_request_error(req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ outbody = smbd_smb2_generate_outbody(req, 0x02);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x02); /* struct size */
+
+ error = smbd_smb2_request_done(req, outbody, NULL);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+struct defer_rename_state {
+ struct tevent_req *req;
+ struct smbd_smb2_request *smb2req;
+ struct tevent_context *ev;
+ struct files_struct *fsp;
+ char *data;
+ int data_size;
+};
+
+static int defer_rename_state_destructor(struct defer_rename_state *rename_state)
+{
+ SAFE_FREE(rename_state->data);
+ return 0;
+}
+
+static void defer_rename_done(struct tevent_req *subreq);
+
+struct delay_rename_lease_break_state {
+ struct files_struct *fsp;
+ bool delay;
+};
+
+static bool delay_rename_lease_break_fn(
+ struct share_mode_entry *e,
+ void *private_data)
+{
+ struct delay_rename_lease_break_state *state = private_data;
+ struct files_struct *fsp = state->fsp;
+ uint32_t e_lease_type, break_to;
+ bool ours, stale;
+
+ ours = smb2_lease_equal(fsp_client_guid(fsp),
+ &fsp->lease->lease.lease_key,
+ &e->client_guid,
+ &e->lease_key);
+ if (ours) {
+ return false;
+ }
+
+ e_lease_type = get_lease_type(e, fsp->file_id);
+
+ if ((e_lease_type & SMB2_LEASE_HANDLE) == 0) {
+ return false;
+ }
+
+ stale = share_entry_stale_pid(e);
+ if (stale) {
+ return false;
+ }
+
+ state->delay = true;
+ break_to = (e_lease_type & ~SMB2_LEASE_HANDLE);
+
+ send_break_message(
+ fsp->conn->sconn->msg_ctx, &fsp->file_id, e, break_to);
+
+ return false;
+}
+
+static struct tevent_req *delay_rename_for_lease_break(struct tevent_req *req,
+ struct smbd_smb2_request *smb2req,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ struct share_mode_lock *lck,
+ char *data,
+ int data_size)
+
+{
+ struct tevent_req *subreq;
+ struct defer_rename_state *rename_state;
+ struct delay_rename_lease_break_state state = { .fsp = fsp };
+ struct timeval timeout;
+ bool ok;
+
+ if (fsp->oplock_type != LEASE_OPLOCK) {
+ return NULL;
+ }
+
+ ok = share_mode_forall_leases(
+ lck, delay_rename_lease_break_fn, &state);
+ if (!ok) {
+ return NULL;
+ }
+
+ if (!state.delay) {
+ return NULL;
+ }
+
+ /* Setup a watch on this record. */
+ rename_state = talloc_zero(req, struct defer_rename_state);
+ if (rename_state == NULL) {
+ return NULL;
+ }
+
+ rename_state->req = req;
+ rename_state->smb2req = smb2req;
+ rename_state->ev = ev;
+ rename_state->fsp = fsp;
+ rename_state->data = data;
+ rename_state->data_size = data_size;
+
+ talloc_set_destructor(rename_state, defer_rename_state_destructor);
+
+ subreq = share_mode_watch_send(
+ rename_state,
+ ev,
+ lck,
+ (struct server_id){0});
+
+ if (subreq == NULL) {
+ exit_server("Could not watch share mode record for rename\n");
+ }
+
+ tevent_req_set_callback(subreq, defer_rename_done, rename_state);
+
+ timeout = timeval_set(OPLOCK_BREAK_TIMEOUT*2, 0);
+ if (!tevent_req_set_endtime(subreq,
+ ev,
+ timeval_sum(&smb2req->request_time, &timeout))) {
+ exit_server("Could not set rename timeout\n");
+ }
+
+ return subreq;
+}
+
+static void defer_rename_done(struct tevent_req *subreq)
+{
+ struct defer_rename_state *state = tevent_req_callback_data(
+ subreq, struct defer_rename_state);
+ NTSTATUS status;
+ struct share_mode_lock *lck;
+ int ret_size = 0;
+ bool ok;
+
+ status = share_mode_watch_recv(subreq, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("dbwrap_record_watch_recv returned %s\n",
+ nt_errstr(status)));
+ tevent_req_nterror(state->req, status);
+ return;
+ }
+
+ /*
+ * Make sure we run as the user again
+ */
+ ok = change_to_user_and_service(
+ state->smb2req->tcon->compat,
+ state->smb2req->session->global->session_wire_id);
+ if (!ok) {
+ tevent_req_nterror(state->req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+
+ /* Do we still need to wait ? */
+ lck = get_existing_share_mode_lock(state->req, state->fsp->file_id);
+ if (lck == NULL) {
+ tevent_req_nterror(state->req, NT_STATUS_UNSUCCESSFUL);
+ return;
+ }
+ subreq = delay_rename_for_lease_break(state->req,
+ state->smb2req,
+ state->ev,
+ state->fsp,
+ lck,
+ state->data,
+ state->data_size);
+ if (subreq) {
+ /* Yep - keep waiting. */
+ state->data = NULL;
+ TALLOC_FREE(state);
+ TALLOC_FREE(lck);
+ return;
+ }
+
+ /* Do the rename under the lock. */
+ status = smbd_do_setfilepathinfo(state->fsp->conn,
+ state->smb2req->smb1req,
+ state,
+ SMB2_FILE_RENAME_INFORMATION_INTERNAL,
+ state->fsp,
+ state->fsp->fsp_name,
+ &state->data,
+ state->data_size,
+ &ret_size);
+
+ TALLOC_FREE(lck);
+ SAFE_FREE(state->data);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(state->req, status);
+ return;
+ }
+
+ tevent_req_done(state->req);
+}
+
+struct smbd_smb2_setinfo_state {
+ struct smbd_smb2_request *smb2req;
+};
+
+static struct tevent_req *smbd_smb2_setinfo_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *fsp,
+ uint8_t in_info_type,
+ uint8_t in_file_info_class,
+ DATA_BLOB in_input_buffer,
+ uint32_t in_additional_information)
+{
+ struct tevent_req *req = NULL;
+ struct smbd_smb2_setinfo_state *state = NULL;
+ struct smb_request *smbreq = NULL;
+ connection_struct *conn = smb2req->tcon->compat;
+ struct share_mode_lock *lck = NULL;
+ NTSTATUS status;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_setinfo_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->smb2req = smb2req;
+
+ DEBUG(10,("smbd_smb2_setinfo_send: %s - %s\n",
+ fsp_str_dbg(fsp), fsp_fnum_dbg(fsp)));
+
+ smbreq = smbd_smb2_fake_smb_request(smb2req, fsp);
+ if (tevent_req_nomem(smbreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (IS_IPC(conn)) {
+ tevent_req_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return tevent_req_post(req, ev);
+ }
+
+ switch (in_info_type) {
+ case SMB2_0_INFO_FILE:
+ {
+ uint16_t file_info_level;
+ char *data;
+ int data_size;
+ int ret_size = 0;
+
+
+ file_info_level = in_file_info_class + 1000;
+ if (file_info_level == SMB_FILE_RENAME_INFORMATION) {
+ /* SMB2_FILE_RENAME_INFORMATION_INTERNAL == 0xFF00 + in_file_info_class */
+ file_info_level = SMB2_FILE_RENAME_INFORMATION_INTERNAL;
+ }
+
+ if (fsp_get_pathref_fd(fsp) == -1) {
+ /*
+ * This is actually a SETFILEINFO on a directory
+ * handle (returned from an NT SMB). NT5.0 seems
+ * to do this call. JRA.
+ */
+ ret = vfs_stat(fsp->conn, fsp->fsp_name);
+ if (ret != 0) {
+ DBG_WARNING("vfs_stat() of %s failed (%s)\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ status = map_nt_error_from_unix(errno);
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ } else if (fsp->print_file) {
+ /*
+ * Doing a DELETE_ON_CLOSE should cancel a print job.
+ */
+ if ((file_info_level == SMB_SET_FILE_DISPOSITION_INFO)
+ && in_input_buffer.length >= 1
+ && CVAL(in_input_buffer.data,0)) {
+ fsp->fsp_flags.delete_on_close = true;
+
+ DEBUG(3,("smbd_smb2_setinfo_send: "
+ "Cancelling print job (%s)\n",
+ fsp_str_dbg(fsp)));
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_nterror(req, NT_STATUS_OBJECT_PATH_INVALID);
+ return tevent_req_post(req, ev);
+ } else {
+ /*
+ * Original code - this is an open file.
+ */
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3,("smbd_smb2_setinfo_send: fstat "
+ "of %s failed (%s)\n",
+ fsp_fnum_dbg(fsp),
+ nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ data = NULL;
+ data_size = in_input_buffer.length;
+ if (data_size > 0) {
+ data = (char *)SMB_MALLOC_ARRAY(char, data_size);
+ if (tevent_req_nomem(data, req)) {
+ return tevent_req_post(req, ev);
+ }
+ memcpy(data, in_input_buffer.data, data_size);
+ }
+
+ if (file_info_level == SMB2_FILE_RENAME_INFORMATION_INTERNAL) {
+ struct tevent_req *subreq;
+
+ lck = get_existing_share_mode_lock(mem_ctx,
+ fsp->file_id);
+ if (lck == NULL) {
+ SAFE_FREE(data);
+ tevent_req_nterror(req,
+ NT_STATUS_UNSUCCESSFUL);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = delay_rename_for_lease_break(req,
+ smb2req,
+ ev,
+ fsp,
+ lck,
+ data,
+ data_size);
+ if (subreq) {
+ /* Wait for lease break response. */
+
+ /* Ensure we can't be closed in flight. */
+ if (!aio_add_req_to_fsp(fsp, req)) {
+ TALLOC_FREE(lck);
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return tevent_req_post(req, ev);
+ }
+
+ TALLOC_FREE(lck);
+ return req;
+ }
+ }
+
+ status = smbd_do_setfilepathinfo(conn, smbreq, state,
+ file_info_level,
+ fsp,
+ fsp->fsp_name,
+ &data,
+ data_size,
+ &ret_size);
+ TALLOC_FREE(lck);
+ SAFE_FREE(data);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_LEVEL)) {
+ status = NT_STATUS_INVALID_INFO_CLASS;
+ }
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ break;
+ }
+
+ case SMB2_0_INFO_FILESYSTEM:
+ {
+ uint16_t file_info_level = in_file_info_class + 1000;
+
+ status = smbd_do_setfsinfo(conn, smbreq, state,
+ file_info_level,
+ fsp,
+ &in_input_buffer);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_LEVEL)) {
+ status = NT_STATUS_INVALID_INFO_CLASS;
+ }
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ break;
+ }
+
+ case SMB2_0_INFO_SECURITY:
+ {
+ if (!CAN_WRITE(conn)) {
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return tevent_req_post(req, ev);
+ }
+
+ status = set_sd_blob(fsp,
+ in_input_buffer.data,
+ in_input_buffer.length,
+ in_additional_information &
+ SMB_SUPPORTED_SECINFO_FLAGS);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ break;
+ }
+
+ case SMB2_0_INFO_QUOTA:
+ {
+#ifdef HAVE_SYS_QUOTAS
+ struct file_quota_information info = {0};
+ SMB_NTQUOTA_STRUCT qt = {0};
+ enum ndr_err_code err;
+
+ if (!fsp->fake_file_handle) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ err = ndr_pull_struct_blob(
+ &in_input_buffer, state, &info,
+ (ndr_pull_flags_fn_t)ndr_pull_file_quota_information);
+ if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+ tevent_req_nterror(req, NT_STATUS_UNSUCCESSFUL);
+ return tevent_req_post(req, ev);
+ }
+
+ qt.usedspace = info.quota_used;
+
+ qt.softlim = info.quota_threshold;
+
+ qt.hardlim = info.quota_limit;
+
+ qt.sid = info.sid;
+ ret = vfs_set_ntquota(fsp, SMB_USER_QUOTA_TYPE, &qt.sid, &qt);
+ if (ret !=0 ) {
+ status = map_nt_error_from_unix(errno);
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ status = NT_STATUS_OK;
+ break;
+#else
+ tevent_req_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return tevent_req_post(req, ev);
+#endif
+ }
+ default:
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static NTSTATUS smbd_smb2_setinfo_recv(struct tevent_req *req)
+{
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/smb2_signing.c b/source3/smbd/smb2_signing.c
new file mode 100644
index 0000000..73d0738
--- /dev/null
+++ b/source3/smbd/smb2_signing.c
@@ -0,0 +1,52 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB Signing Code
+ Copyright (C) Jeremy Allison 2003.
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2002-2003
+ Copyright (C) Stefan Metzmacher 2009
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_signing.h"
+#include "lib/param/param.h"
+#include "smb2_signing.h"
+
+bool srv_init_signing(struct smbXsrv_connection *conn)
+{
+ struct loadparm_context *lp_ctx = NULL;
+ bool ok = true;
+
+ lp_ctx = loadparm_init_s3(conn, loadparm_s3_helpers());
+ if (lp_ctx == NULL) {
+ DBG_DEBUG("loadparm_init_s3 failed\n");
+ return false;
+ }
+
+ /*
+ * For SMB2 all we need to know is if signing is mandatory.
+ * It is always allowed and desired, whatever the smb.conf says.
+ */
+ (void)lpcfg_server_signing_allowed(lp_ctx, &conn->smb2.signing_mandatory);
+
+#if defined(WITH_SMB1SERVER)
+ ok = smb1_srv_init_signing(lp_ctx, conn);
+#endif
+
+ talloc_unlink(conn, lp_ctx);
+ return ok;
+}
diff --git a/source3/smbd/smb2_tcon.c b/source3/smbd/smb2_tcon.c
new file mode 100644
index 0000000..b228036
--- /dev/null
+++ b/source3/smbd/smb2_tcon.c
@@ -0,0 +1,765 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../libcli/security/security.h"
+#include "auth.h"
+#include "lib/param/loadparm.h"
+#include "../lib/util/tevent_ntstatus.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static struct tevent_req *smbd_smb2_tree_connect_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ uint16_t in_flags,
+ const char *in_path);
+static NTSTATUS smbd_smb2_tree_connect_recv(struct tevent_req *req,
+ uint8_t *out_share_type,
+ uint32_t *out_share_flags,
+ uint32_t *out_capabilities,
+ uint32_t *out_maximal_access,
+ uint32_t *out_tree_id,
+ bool *disconnect);
+
+static void smbd_smb2_request_tcon_done(struct tevent_req *subreq);
+
+NTSTATUS smbd_smb2_request_process_tcon(struct smbd_smb2_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ const uint8_t *inbody;
+ uint16_t in_flags;
+ uint16_t in_path_offset;
+ uint16_t in_path_length;
+ DATA_BLOB in_path_buffer;
+ char *in_path_string;
+ size_t in_path_string_size;
+ NTSTATUS status;
+ bool ok;
+ struct tevent_req *subreq;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x09);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ if (xconn->protocol >= PROTOCOL_SMB3_11) {
+ in_flags = SVAL(inbody, 0x02);
+ } else {
+ in_flags = 0;
+ }
+ in_path_offset = SVAL(inbody, 0x04);
+ in_path_length = SVAL(inbody, 0x06);
+
+ if (in_path_offset != (SMB2_HDR_BODY + SMBD_SMB2_IN_BODY_LEN(req))) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ if (in_path_length > SMBD_SMB2_IN_DYN_LEN(req)) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ in_path_buffer.data = SMBD_SMB2_IN_DYN_PTR(req);
+ in_path_buffer.length = in_path_length;
+
+ ok = convert_string_talloc(req, CH_UTF16, CH_UNIX,
+ in_path_buffer.data,
+ in_path_buffer.length,
+ &in_path_string,
+ &in_path_string_size);
+ if (!ok) {
+ return smbd_smb2_request_error(req, NT_STATUS_ILLEGAL_CHARACTER);
+ }
+
+ if (in_path_buffer.length == 0) {
+ in_path_string_size = 0;
+ }
+
+ if (strlen(in_path_string) != in_path_string_size) {
+ return smbd_smb2_request_error(req, NT_STATUS_BAD_NETWORK_NAME);
+ }
+
+ subreq = smbd_smb2_tree_connect_send(req,
+ req->sconn->ev_ctx,
+ req,
+ in_flags,
+ in_path_string);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_tcon_done, req);
+
+ /*
+ * Avoid sending a STATUS_PENDING message, it's very likely
+ * the client won't expect that.
+ */
+ return smbd_smb2_request_pending_queue(req, subreq, 0);
+}
+
+static void smbd_smb2_request_tcon_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *req =
+ tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ uint8_t *outhdr;
+ DATA_BLOB outbody;
+ uint8_t out_share_type = 0;
+ uint32_t out_share_flags = 0;
+ uint32_t out_capabilities = 0;
+ uint32_t out_maximal_access = 0;
+ uint32_t out_tree_id = 0;
+ bool disconnect = false;
+ NTSTATUS status;
+ NTSTATUS error;
+
+ status = smbd_smb2_tree_connect_recv(subreq,
+ &out_share_type,
+ &out_share_flags,
+ &out_capabilities,
+ &out_maximal_access,
+ &out_tree_id,
+ &disconnect);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (disconnect) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(status));
+ return;
+ }
+ error = smbd_smb2_request_error(req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ outhdr = SMBD_SMB2_OUT_HDR_PTR(req);
+
+ outbody = smbd_smb2_generate_outbody(req, 0x10);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SIVAL(outhdr, SMB2_HDR_TID, out_tree_id);
+
+ SSVAL(outbody.data, 0x00, 0x10); /* struct size */
+ SCVAL(outbody.data, 0x02,
+ out_share_type); /* share type */
+ SCVAL(outbody.data, 0x03, 0); /* reserved */
+ SIVAL(outbody.data, 0x04,
+ out_share_flags); /* share flags */
+ SIVAL(outbody.data, 0x08,
+ out_capabilities); /* capabilities */
+ SIVAL(outbody.data, 0x0C,
+ out_maximal_access); /* maximal access */
+
+ error = smbd_smb2_request_done(req, outbody, NULL);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+static NTSTATUS smbd_smb2_tree_connect(struct smbd_smb2_request *req,
+ const char *in_path,
+ uint8_t *out_share_type,
+ uint32_t *out_share_flags,
+ uint32_t *out_capabilities,
+ uint32_t *out_maximal_access,
+ uint32_t *out_tree_id,
+ bool *disconnect)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct smbXsrv_connection *conn = req->xconn;
+ struct smbXsrv_session *session = req->session;
+ struct auth_session_info *session_info =
+ session->global->auth_session_info;
+ const char *share = in_path;
+ char *service = NULL;
+ int snum = -1;
+ struct smbXsrv_tcon *tcon;
+ NTTIME now = timeval_to_nttime(&req->request_time);
+ connection_struct *compat_conn = NULL;
+ NTSTATUS status;
+ bool encryption_desired = req->session->global->encryption_flags & SMBXSRV_ENCRYPTION_DESIRED;
+ bool encryption_required = req->session->global->encryption_flags & SMBXSRV_ENCRYPTION_REQUIRED;
+ bool guest_session = false;
+ bool require_signed_tcon = false;
+ uint32_t session_global_id;
+ char *share_name = NULL;
+ uint8_t encryption_flags = 0;
+
+ *disconnect = false;
+
+ if (strncmp(share, "\\\\", 2) == 0) {
+ const char *p = strchr(share+2, '\\');
+ if (p) {
+ share = p + 1;
+ }
+ }
+
+ DEBUG(10,("smbd_smb2_tree_connect: path[%s] share[%s]\n",
+ in_path, share));
+
+ if (security_session_user_level(session_info, NULL) < SECURITY_USER) {
+ guest_session = true;
+ }
+
+ if (conn->protocol >= PROTOCOL_SMB3_11 && !guest_session) {
+ require_signed_tcon = true;
+ }
+
+ if (require_signed_tcon && !req->do_encryption && !req->do_signing) {
+ DEBUG(1, ("smbd_smb2_tree_connect: reject request to share "
+ "[%s] as '%s\\%s' without encryption or signing. "
+ "Disconnecting.\n",
+ share,
+ req->session->global->auth_session_info->info->domain_name,
+ req->session->global->auth_session_info->info->account_name));
+ *disconnect = true;
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ service = talloc_strdup(talloc_tos(), share);
+ if(!service) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!strlower_m(service)) {
+ DEBUG(2, ("strlower_m %s failed\n", service));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* TODO: do more things... */
+ if (strequal(service,HOMES_NAME)) {
+ if (session->homes_snum == -1) {
+ DEBUG(2, ("[homes] share not available for "
+ "user %s because it was not found "
+ "or created at session setup "
+ "time\n",
+ session_info->unix_info->unix_name));
+ return NT_STATUS_BAD_NETWORK_NAME;
+ }
+ snum = session->homes_snum;
+ } else if ((session->homes_snum != -1)
+ && strequal(service,
+ lp_servicename(talloc_tos(), lp_sub, session->homes_snum))) {
+ snum = session->homes_snum;
+ } else {
+ snum = find_service(talloc_tos(), service, &service);
+ if (!service) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ if (snum < 0) {
+ DEBUG(3,("smbd_smb2_tree_connect: couldn't find service %s\n",
+ service));
+ return NT_STATUS_BAD_NETWORK_NAME;
+ }
+
+ /* Handle non-DFS clients attempting connections to msdfs proxy */
+ if (lp_host_msdfs()) {
+ char *proxy = lp_msdfs_proxy(talloc_tos(), lp_sub, snum);
+
+ if ((proxy != NULL) && (*proxy != '\0')) {
+ DBG_NOTICE("refusing connection to dfs proxy share "
+ "'%s' (pointing to %s)\n",
+ service,
+ proxy);
+ TALLOC_FREE(proxy);
+ return NT_STATUS_BAD_NETWORK_NAME;
+ }
+ TALLOC_FREE(proxy);
+ }
+
+ if ((lp_server_smb_encrypt(snum) >= SMB_ENCRYPTION_DESIRED) &&
+ (conn->smb2.server.cipher != 0))
+ {
+ encryption_desired = true;
+ }
+
+ if (lp_server_smb_encrypt(snum) == SMB_ENCRYPTION_REQUIRED) {
+ encryption_desired = true;
+ encryption_required = true;
+ }
+
+ if (guest_session && encryption_required) {
+ DEBUG(1,("reject guest as encryption is required for service %s\n",
+ service));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (conn->smb2.server.cipher == 0) {
+ if (encryption_required) {
+ DEBUG(1,("reject tcon with dialect[0x%04X] "
+ "as encryption is required for service %s\n",
+ conn->smb2.server.dialect, service));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+
+ if (encryption_desired) {
+ encryption_flags |= SMBXSRV_ENCRYPTION_DESIRED;
+ }
+ if (encryption_required) {
+ encryption_flags |= SMBXSRV_ENCRYPTION_REQUIRED;
+ }
+
+ session_global_id = req->session->global->session_global_id;
+ share_name = lp_servicename(talloc_tos(), lp_sub, snum);
+ if (share_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if ((lp_max_connections(snum) > 0)
+ && (count_current_connections(lp_const_servicename(snum), true) >=
+ lp_max_connections(snum))) {
+
+ DBG_WARNING("Max connections (%d) exceeded for [%s][%s]\n",
+ lp_max_connections(snum),
+ lp_const_servicename(snum), share_name);
+ TALLOC_FREE(share_name);
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ /* create a new tcon as child of the session */
+ status = smb2srv_tcon_create(req->session,
+ session_global_id,
+ encryption_flags,
+ share_name,
+ now, &tcon);
+ TALLOC_FREE(share_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ compat_conn = make_connection_smb2(req,
+ tcon, snum,
+ "???",
+ &status);
+ if (compat_conn == NULL) {
+ TALLOC_FREE(tcon);
+ return status;
+ }
+
+ tcon->compat = talloc_move(tcon, &compat_conn);
+
+ tcon->status = NT_STATUS_OK;
+
+ if (IS_PRINT(tcon->compat)) {
+ *out_share_type = SMB2_SHARE_TYPE_PRINT;
+ } else if (IS_IPC(tcon->compat)) {
+ *out_share_type = SMB2_SHARE_TYPE_PIPE;
+ } else {
+ *out_share_type = SMB2_SHARE_TYPE_DISK;
+ }
+
+ *out_share_flags = 0;
+
+ if (lp_msdfs_root(SNUM(tcon->compat)) && lp_host_msdfs()) {
+ *out_share_flags |= (SMB2_SHAREFLAG_DFS|SMB2_SHAREFLAG_DFS_ROOT);
+ *out_capabilities = SMB2_SHARE_CAP_DFS;
+ } else {
+ *out_capabilities = 0;
+ }
+
+ switch(lp_csc_policy(SNUM(tcon->compat))) {
+ case CSC_POLICY_MANUAL:
+ break;
+ case CSC_POLICY_DOCUMENTS:
+ *out_share_flags |= SMB2_SHAREFLAG_AUTO_CACHING;
+ break;
+ case CSC_POLICY_PROGRAMS:
+ *out_share_flags |= SMB2_SHAREFLAG_VDO_CACHING;
+ break;
+ case CSC_POLICY_DISABLE:
+ *out_share_flags |= SMB2_SHAREFLAG_NO_CACHING;
+ break;
+ default:
+ break;
+ }
+
+ if (lp_hide_unreadable(SNUM(tcon->compat)) ||
+ lp_hide_unwriteable_files(SNUM(tcon->compat))) {
+ *out_share_flags |= SMB2_SHAREFLAG_ACCESS_BASED_DIRECTORY_ENUM;
+ }
+
+ if (encryption_desired) {
+ *out_share_flags |= SMB2_SHAREFLAG_ENCRYPT_DATA;
+ }
+
+ /*
+ * For disk shares we can change the client
+ * behavior on a cluster...
+ */
+ if (conn->protocol >= PROTOCOL_SMB3_00 &&
+ *out_share_type == SMB2_SHARE_TYPE_DISK)
+ {
+ bool persistent = false; /* persistent handles not implemented yet */
+ bool cluster = lp_clustering();
+ bool scaleout = cluster;
+ bool witness = cluster && !lp_rpc_start_on_demand_helpers();
+ bool asymmetric = false; /* shares are symmetric by default */
+ bool announce;
+
+ /*
+ * In a ctdb cluster shares are continuously available,
+ * but windows clients mix this with the global persistent
+ * handles support.
+ *
+ * Persistent handles are requested if
+ * SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY is present
+ * even without SMB2_CAP_PERSISTENT_HANDLES.
+ *
+ * And SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY is
+ * required for SMB2_SHARE_CAP_CLUSTER to have
+ * an effect.
+ *
+ * So we better don't announce this by default
+ * until we support persistent handles.
+ */
+ announce = lp_parm_bool(SNUM(tcon->compat),
+ "smb3 share cap",
+ "CONTINUOUS AVAILABILITY",
+ persistent);
+ if (announce) {
+ *out_capabilities |= SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY;
+ }
+
+ /*
+ * ctdb clusters are always scale out...
+ */
+ announce = lp_parm_bool(SNUM(tcon->compat),
+ "smb3 share cap",
+ "SCALE OUT",
+ scaleout);
+ if (announce) {
+ *out_capabilities |= SMB2_SHARE_CAP_SCALEOUT;
+ }
+
+ /*
+ * We support the witness service when ctdb is active
+ */
+ announce = lp_parm_bool(SNUM(tcon->compat),
+ "smb3 share cap",
+ "CLUSTER",
+ witness);
+ if (announce) {
+ *out_capabilities |= SMB2_SHARE_CAP_CLUSTER;
+ }
+
+ /*
+ * Shares in a ctdb cluster are symmetric by design.
+ *
+ * But it might be useful to let the client use
+ * an isolated transport and witness registration for the
+ * specific share.
+ */
+ if (conn->protocol >= PROTOCOL_SMB3_02) {
+ announce = lp_parm_bool(SNUM(tcon->compat),
+ "smb3 share cap",
+ "ASYMMETRIC",
+ asymmetric);
+ }
+ if (announce) {
+ *out_capabilities |= SMB2_SHARE_CAP_ASYMMETRIC;
+ }
+ }
+
+ *out_maximal_access = tcon->compat->share_access;
+
+ *out_tree_id = tcon->global->tcon_wire_id;
+ req->last_tid = tcon->global->tcon_wire_id;
+
+ return NT_STATUS_OK;
+}
+
+struct smbd_smb2_tree_connect_state {
+ const char *in_path;
+ uint8_t out_share_type;
+ uint32_t out_share_flags;
+ uint32_t out_capabilities;
+ uint32_t out_maximal_access;
+ uint32_t out_tree_id;
+ bool disconnect;
+};
+
+static struct tevent_req *smbd_smb2_tree_connect_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ uint16_t in_flags,
+ const char *in_path)
+{
+ struct tevent_req *req;
+ struct smbd_smb2_tree_connect_state *state;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_tree_connect_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->in_path = in_path;
+
+ status = smbd_smb2_tree_connect(smb2req,
+ state->in_path,
+ &state->out_share_type,
+ &state->out_share_flags,
+ &state->out_capabilities,
+ &state->out_maximal_access,
+ &state->out_tree_id,
+ &state->disconnect);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static NTSTATUS smbd_smb2_tree_connect_recv(struct tevent_req *req,
+ uint8_t *out_share_type,
+ uint32_t *out_share_flags,
+ uint32_t *out_capabilities,
+ uint32_t *out_maximal_access,
+ uint32_t *out_tree_id,
+ bool *disconnect)
+{
+ struct smbd_smb2_tree_connect_state *state =
+ tevent_req_data(req,
+ struct smbd_smb2_tree_connect_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *out_share_type = state->out_share_type;
+ *out_share_flags = state->out_share_flags;
+ *out_capabilities = state->out_capabilities;
+ *out_maximal_access = state->out_maximal_access;
+ *out_tree_id = state->out_tree_id;
+ *disconnect = state->disconnect;
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+static struct tevent_req *smbd_smb2_tdis_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req);
+static NTSTATUS smbd_smb2_tdis_recv(struct tevent_req *req);
+static void smbd_smb2_request_tdis_done(struct tevent_req *subreq);
+
+NTSTATUS smbd_smb2_request_process_tdis(struct smbd_smb2_request *req)
+{
+ NTSTATUS status;
+ struct tevent_req *subreq = NULL;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x04);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ subreq = smbd_smb2_tdis_send(req, req->sconn->ev_ctx, req);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_tdis_done, req);
+
+ /*
+ * Avoid sending a STATUS_PENDING message, it's very likely
+ * the client won't expect that.
+ */
+ return smbd_smb2_request_pending_queue(req, subreq, 0);
+}
+
+static void smbd_smb2_request_tdis_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *smb2req =
+ tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ DATA_BLOB outbody;
+ NTSTATUS status;
+ NTSTATUS error;
+
+ status = smbd_smb2_tdis_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ error = smbd_smb2_request_error(smb2req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ outbody = smbd_smb2_generate_outbody(smb2req, 0x04);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(smb2req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x04); /* struct size */
+ SSVAL(outbody.data, 0x02, 0); /* reserved */
+
+ error = smbd_smb2_request_done(smb2req, outbody, NULL);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+struct smbd_smb2_tdis_state {
+ struct smbd_smb2_request *smb2req;
+ struct tevent_queue *wait_queue;
+};
+
+static void smbd_smb2_tdis_wait_done(struct tevent_req *subreq);
+
+static struct tevent_req *smbd_smb2_tdis_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req)
+{
+ struct tevent_req *req;
+ struct smbd_smb2_tdis_state *state;
+ struct tevent_req *subreq;
+ struct smbXsrv_connection *xconn = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_tdis_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->smb2req = smb2req;
+
+ state->wait_queue = tevent_queue_create(state, "tdis_wait_queue");
+ if (tevent_req_nomem(state->wait_queue, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * Make sure that no new request will be able to use this tcon.
+ */
+ smb2req->tcon->status = NT_STATUS_NETWORK_NAME_DELETED;
+
+ xconn = smb2req->xconn->client->connections;
+ for (; xconn != NULL; xconn = xconn->next) {
+ struct smbd_smb2_request *preq;
+
+ for (preq = xconn->smb2.requests; preq != NULL; preq = preq->next) {
+ if (preq == smb2req) {
+ /* Can't cancel current request. */
+ continue;
+ }
+ if (preq->tcon != smb2req->tcon) {
+ /* Request on different tcon. */
+ continue;
+ }
+
+ if (preq->subreq != NULL) {
+ tevent_req_cancel(preq->subreq);
+ }
+
+ /*
+ * Now wait until the request is finished.
+ *
+ * We don't set a callback, as we just want to block the
+ * wait queue and the talloc_free() of the request will
+ * remove the item from the wait queue.
+ */
+ subreq = tevent_queue_wait_send(preq, ev, state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+ }
+
+ /*
+ * Now we add our own waiter to the end of the queue,
+ * this way we get notified when all pending requests are finished
+ * and send to the socket.
+ */
+ subreq = tevent_queue_wait_send(state, ev, state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_tdis_wait_done, req);
+
+ return req;
+}
+
+static void smbd_smb2_tdis_wait_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smbd_smb2_tdis_state *state = tevent_req_data(
+ req, struct smbd_smb2_tdis_state);
+ NTSTATUS status;
+
+ tevent_queue_wait_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ /*
+ * As we've been awoken, we may have changed
+ * uid in the meantime. Ensure we're still
+ * root (SMB2_OP_TDIS has .as_root = true).
+ */
+ change_to_root_user();
+
+ status = smbXsrv_tcon_disconnect(state->smb2req->tcon,
+ state->smb2req->tcon->compat->vuid);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ /* We did tear down the tcon. */
+ TALLOC_FREE(state->smb2req->tcon);
+ tevent_req_done(req);
+}
+
+static NTSTATUS smbd_smb2_tdis_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
diff --git a/source3/smbd/smb2_trans2.c b/source3/smbd/smb2_trans2.c
new file mode 100644
index 0000000..8997c40
--- /dev/null
+++ b/source3/smbd/smb2_trans2.c
@@ -0,0 +1,5243 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB transaction2 handling
+ Copyright (C) Jeremy Allison 1994-2007
+ Copyright (C) Stefan (metze) Metzmacher 2003
+ Copyright (C) Volker Lendecke 2005-2007
+ Copyright (C) Steve French 2005
+ Copyright (C) James Peach 2006-2007
+
+ Extensively modified by Andrew Tridgell, 1995
+
+ 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 "ntioctl.h"
+#include "system/filesys.h"
+#include "lib/util/time_basic.h"
+#include "version.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "../librpc/gen_ndr/xattr.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "../librpc/gen_ndr/ndr_smb3posix.h"
+#include "libcli/security/security.h"
+#include "trans2.h"
+#include "auth.h"
+#include "smbprofile.h"
+#include "rpc_server/srv_pipe_hnd.h"
+#include "printing.h"
+#include "lib/util_ea.h"
+#include "lib/readdir_attr.h"
+#include "messages.h"
+#include "libcli/smb/smb2_posix.h"
+#include "lib/util/string_wrappers.h"
+#include "source3/lib/substitute.h"
+#include "source3/lib/adouble.h"
+#include "source3/smbd/dir.h"
+
+#define DIR_ENTRY_SAFETY_MARGIN 4096
+
+static uint32_t generate_volume_serial_number(
+ const struct loadparm_substitution *lp_sub,
+ int snum);
+
+/****************************************************************************
+ Check if an open file handle is a symlink.
+****************************************************************************/
+
+NTSTATUS refuse_symlink_fsp(const files_struct *fsp)
+{
+
+ if (!VALID_STAT(fsp->fsp_name->st)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ if (S_ISLNK(fsp->fsp_name->st.st_ex_mode)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ if (fsp_get_pathref_fd(fsp) == -1) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ return NT_STATUS_OK;
+}
+
+/**
+ * Check that one or more of the rights in access mask are
+ * allowed. Iow, access_requested can contain more then one right and
+ * it is sufficient having only one of those granted to pass.
+ **/
+NTSTATUS check_any_access_fsp(struct files_struct *fsp,
+ uint32_t access_requested)
+{
+ const uint32_t ro_access = SEC_RIGHTS_FILE_READ | SEC_FILE_EXECUTE;
+ uint32_t ro_access_granted = 0;
+ uint32_t access_granted = 0;
+ NTSTATUS status;
+
+ if (fsp->fsp_flags.is_fsa) {
+ access_granted = fsp->access_mask;
+ } else {
+ uint32_t mask = 1;
+
+ while (mask != 0) {
+ if (!(mask & access_requested)) {
+ mask <<= 1;
+ continue;
+ }
+
+ status = smbd_check_access_rights_fsp(
+ fsp->conn->cwd_fsp,
+ fsp,
+ false,
+ mask);
+ if (NT_STATUS_IS_OK(status)) {
+ access_granted |= mask;
+ if (fsp->fsp_name->twrp == 0) {
+ /*
+ * We can only optimize
+ * the non-snapshot case
+ */
+ break;
+ }
+ }
+ mask <<= 1;
+ }
+ }
+ if ((access_granted & access_requested) == 0) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (fsp->fsp_name->twrp == 0) {
+ return NT_STATUS_OK;
+ }
+
+ ro_access_granted = access_granted & ro_access;
+ if ((ro_access_granted & access_requested) == 0) {
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+ Roundup a value to the nearest allocation roundup size boundary.
+ Only do this for Windows clients.
+********************************************************************/
+
+uint64_t smb_roundup(connection_struct *conn, uint64_t val)
+{
+ uint64_t rval = lp_allocation_roundup_size(SNUM(conn));
+
+ /* Only roundup for Windows clients. */
+ enum remote_arch_types ra_type = get_remote_arch();
+ if (rval && (ra_type != RA_SAMBA) && (ra_type != RA_CIFSFS)) {
+ val = SMB_ROUNDUP(val,rval);
+ }
+ return val;
+}
+
+/****************************************************************************
+ Utility functions for dealing with extended attributes.
+****************************************************************************/
+
+/****************************************************************************
+ Refuse to allow clients to overwrite our private xattrs.
+****************************************************************************/
+
+bool samba_private_attr_name(const char *unix_ea_name)
+{
+ bool prohibited = false;
+
+ prohibited |= strequal(unix_ea_name, SAMBA_POSIX_INHERITANCE_EA_NAME);
+ prohibited |= strequal(unix_ea_name, SAMBA_XATTR_DOS_ATTRIB);
+ prohibited |= strequal(unix_ea_name, SAMBA_XATTR_MARKER);
+ prohibited |= strequal(unix_ea_name, XATTR_NTACL_NAME);
+ prohibited |= strequal(unix_ea_name, AFPINFO_EA_NETATALK);
+
+ if (prohibited) {
+ return true;
+ }
+
+ if (strncasecmp_m(unix_ea_name, SAMBA_XATTR_DOSSTREAM_PREFIX,
+ strlen(SAMBA_XATTR_DOSSTREAM_PREFIX)) == 0) {
+ return true;
+ }
+ return false;
+}
+
+/****************************************************************************
+ Get one EA value. Fill in a struct ea_struct.
+****************************************************************************/
+
+NTSTATUS get_ea_value_fsp(TALLOC_CTX *mem_ctx,
+ files_struct *fsp,
+ const char *ea_name,
+ struct ea_struct *pea)
+{
+ /* Get the value of this xattr. Max size is 64k. */
+ size_t attr_size = 256;
+ char *val = NULL;
+ ssize_t sizeret;
+ size_t max_xattr_size = 0;
+ NTSTATUS status;
+
+ if (fsp == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+ status = refuse_symlink_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ max_xattr_size = lp_smbd_max_xattr_size(SNUM(fsp->conn));
+
+ again:
+
+ val = talloc_realloc(mem_ctx, val, char, attr_size);
+ if (!val) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ sizeret = SMB_VFS_FGETXATTR(fsp, ea_name, val, attr_size);
+ if (sizeret == -1 && errno == ERANGE && attr_size < max_xattr_size) {
+ attr_size = max_xattr_size;
+ goto again;
+ }
+
+ if (sizeret == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ DEBUG(10,("get_ea_value: EA %s is of length %u\n", ea_name, (unsigned int)sizeret));
+ dump_data(10, (uint8_t *)val, sizeret);
+
+ pea->flags = 0;
+ if (strnequal(ea_name, "user.", 5)) {
+ pea->name = talloc_strdup(mem_ctx, &ea_name[5]);
+ } else {
+ pea->name = talloc_strdup(mem_ctx, ea_name);
+ }
+ if (pea->name == NULL) {
+ TALLOC_FREE(val);
+ return NT_STATUS_NO_MEMORY;
+ }
+ pea->value.data = (unsigned char *)val;
+ pea->value.length = (size_t)sizeret;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS get_ea_names_from_fsp(TALLOC_CTX *mem_ctx,
+ files_struct *fsp,
+ char ***pnames,
+ size_t *pnum_names)
+{
+ char smallbuf[1024];
+ /* Get a list of all xattrs. Max namesize is 64k. */
+ size_t ea_namelist_size = 1024;
+ char *ea_namelist = smallbuf;
+ char *to_free = NULL;
+
+ char *p;
+ char **names;
+ size_t num_names;
+ ssize_t sizeret = -1;
+ NTSTATUS status;
+
+ if (pnames) {
+ *pnames = NULL;
+ }
+ *pnum_names = 0;
+
+ if ((fsp == NULL) || !NT_STATUS_IS_OK(refuse_symlink_fsp(fsp))) {
+ /*
+ * Callers may pass fsp == NULL when passing smb_fname->fsp of a
+ * symlink. This is ok, handle it here, by just return no EA's
+ * on a symlink.
+ */
+ return NT_STATUS_OK;
+ }
+
+ sizeret = SMB_VFS_FLISTXATTR(fsp, ea_namelist,
+ ea_namelist_size);
+
+ if ((sizeret == -1) && (errno == ERANGE)) {
+ ea_namelist_size = 65536;
+ ea_namelist = talloc_array(mem_ctx, char, ea_namelist_size);
+ if (ea_namelist == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ to_free = ea_namelist;
+
+ sizeret = SMB_VFS_FLISTXATTR(fsp, ea_namelist,
+ ea_namelist_size);
+ }
+
+ if (sizeret == -1) {
+ status = map_nt_error_from_unix(errno);
+ TALLOC_FREE(to_free);
+ return status;
+ }
+
+ DBG_DEBUG("ea_namelist size = %zd\n", sizeret);
+
+ if (sizeret == 0) {
+ TALLOC_FREE(to_free);
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * Ensure the result is 0-terminated
+ */
+
+ if (ea_namelist[sizeret-1] != '\0') {
+ TALLOC_FREE(to_free);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ /*
+ * count the names
+ */
+ num_names = 0;
+
+ for (p = ea_namelist; p - ea_namelist < sizeret; p += strlen(p)+1) {
+ num_names += 1;
+ }
+
+ *pnum_names = num_names;
+
+ if (pnames == NULL) {
+ TALLOC_FREE(to_free);
+ return NT_STATUS_OK;
+ }
+
+ names = talloc_array(mem_ctx, char *, num_names);
+ if (names == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ TALLOC_FREE(to_free);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (ea_namelist == smallbuf) {
+ ea_namelist = talloc_memdup(names, smallbuf, sizeret);
+ if (ea_namelist == NULL) {
+ TALLOC_FREE(names);
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ talloc_steal(names, ea_namelist);
+
+ ea_namelist = talloc_realloc(names, ea_namelist, char,
+ sizeret);
+ if (ea_namelist == NULL) {
+ TALLOC_FREE(names);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ num_names = 0;
+
+ for (p = ea_namelist; p - ea_namelist < sizeret; p += strlen(p)+1) {
+ names[num_names++] = p;
+ }
+
+ *pnames = names;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Return a linked list of the total EA's. Plus the total size
+****************************************************************************/
+
+static NTSTATUS get_ea_list_from_fsp(TALLOC_CTX *mem_ctx,
+ files_struct *fsp,
+ size_t *pea_total_len,
+ struct ea_list **ea_list)
+{
+ /* Get a list of all xattrs. Max namesize is 64k. */
+ size_t i, num_names;
+ char **names;
+ struct ea_list *ea_list_head = NULL;
+ bool posix_pathnames = false;
+ NTSTATUS status;
+
+ *pea_total_len = 0;
+ *ea_list = NULL;
+
+ /* symlink */
+ if (fsp == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ if (!lp_ea_support(SNUM(fsp->conn))) {
+ return NT_STATUS_OK;
+ }
+
+ if (fsp_is_alternate_stream(fsp)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ posix_pathnames = (fsp->fsp_name->flags & SMB_FILENAME_POSIX_PATH);
+
+ status = get_ea_names_from_fsp(talloc_tos(),
+ fsp,
+ &names,
+ &num_names);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (num_names == 0) {
+ return NT_STATUS_OK;
+ }
+
+ for (i=0; i<num_names; i++) {
+ struct ea_list *listp;
+ fstring dos_ea_name;
+
+ /*
+ * POSIX EA names are divided into several namespaces by
+ * means of string prefixes. Usually, the system controls
+ * semantics for each namespace, but the 'user' namespace is
+ * available for arbitrary use, which comes closest to
+ * Windows EA semantics. Hence, we map POSIX EAs from the
+ * 'user' namespace to Windows EAs, and just ignore all the
+ * other namespaces. Also, a few specific names in the 'user'
+ * namespace are used by Samba internally. Filter them out as
+ * well, and only present the EAs that are available for
+ * arbitrary use.
+ */
+ if (!strnequal(names[i], "user.", 5)
+ || samba_private_attr_name(names[i]))
+ continue;
+
+ /*
+ * Filter out any underlying POSIX EA names
+ * that a Windows client can't handle.
+ */
+ if (!posix_pathnames &&
+ is_invalid_windows_ea_name(names[i])) {
+ continue;
+ }
+
+ listp = talloc(mem_ctx, struct ea_list);
+ if (listp == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = get_ea_value_fsp(listp,
+ fsp,
+ names[i],
+ &listp->ea);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(listp);
+ return status;
+ }
+
+ if (listp->ea.value.length == 0) {
+ /*
+ * We can never return a zero length EA.
+ * Windows reports the EA's as corrupted.
+ */
+ TALLOC_FREE(listp);
+ continue;
+ } else if (listp->ea.value.length > 65536) {
+ /*
+ * SMB clients may report error with file
+ * if large EA is presented to them.
+ */
+ DBG_ERR("EA [%s] on file [%s] exceeds "
+ "maximum permitted EA size of 64KiB: %zu\n.",
+ listp->ea.name, fsp_str_dbg(fsp),
+ listp->ea.value.length);
+ TALLOC_FREE(listp);
+ continue;
+ }
+
+ push_ascii_fstring(dos_ea_name, listp->ea.name);
+
+ *pea_total_len +=
+ 4 + strlen(dos_ea_name) + 1 + listp->ea.value.length;
+
+ DEBUG(10,("get_ea_list_from_file: total_len = %u, %s, val len "
+ "= %u\n", (unsigned int)*pea_total_len, dos_ea_name,
+ (unsigned int)listp->ea.value.length));
+
+ DLIST_ADD_END(ea_list_head, listp);
+
+ }
+
+ /* Add on 4 for total length. */
+ if (*pea_total_len) {
+ *pea_total_len += 4;
+ }
+
+ DEBUG(10, ("get_ea_list_from_file: total_len = %u\n",
+ (unsigned int)*pea_total_len));
+
+ *ea_list = ea_list_head;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Fill a qfilepathinfo buffer with EA's. Returns the length of the buffer
+ that was filled.
+****************************************************************************/
+
+static unsigned int fill_ea_buffer(TALLOC_CTX *mem_ctx, char *pdata, unsigned int total_data_size,
+ connection_struct *conn, struct ea_list *ea_list)
+{
+ unsigned int ret_data_size = 4;
+ char *p = pdata;
+
+ SMB_ASSERT(total_data_size >= 4);
+
+ if (!lp_ea_support(SNUM(conn))) {
+ SIVAL(pdata,4,0);
+ return 4;
+ }
+
+ for (p = pdata + 4; ea_list; ea_list = ea_list->next) {
+ size_t dos_namelen;
+ fstring dos_ea_name;
+ push_ascii_fstring(dos_ea_name, ea_list->ea.name);
+ dos_namelen = strlen(dos_ea_name);
+ if (dos_namelen > 255 || dos_namelen == 0) {
+ break;
+ }
+ if (ea_list->ea.value.length > 65535) {
+ break;
+ }
+ if (4 + dos_namelen + 1 + ea_list->ea.value.length > total_data_size) {
+ break;
+ }
+
+ /* We know we have room. */
+ SCVAL(p,0,ea_list->ea.flags);
+ SCVAL(p,1,dos_namelen);
+ SSVAL(p,2,ea_list->ea.value.length);
+ strlcpy(p+4, dos_ea_name, dos_namelen+1);
+ if (ea_list->ea.value.length > 0) {
+ memcpy(p + 4 + dos_namelen + 1,
+ ea_list->ea.value.data,
+ ea_list->ea.value.length);
+ }
+
+ total_data_size -= 4 + dos_namelen + 1 + ea_list->ea.value.length;
+ p += 4 + dos_namelen + 1 + ea_list->ea.value.length;
+ }
+
+ ret_data_size = PTR_DIFF(p, pdata);
+ DEBUG(10,("fill_ea_buffer: data_size = %u\n", ret_data_size ));
+ SIVAL(pdata,0,ret_data_size);
+ return ret_data_size;
+}
+
+static NTSTATUS fill_ea_chained_buffer(TALLOC_CTX *mem_ctx,
+ char *pdata,
+ unsigned int total_data_size,
+ unsigned int *ret_data_size,
+ connection_struct *conn,
+ struct ea_list *ea_list)
+{
+ uint8_t *p = (uint8_t *)pdata;
+ uint8_t *last_start = NULL;
+ bool do_store_data = (pdata != NULL);
+
+ *ret_data_size = 0;
+
+ if (!lp_ea_support(SNUM(conn))) {
+ return NT_STATUS_NO_EAS_ON_FILE;
+ }
+
+ for (; ea_list; ea_list = ea_list->next) {
+ size_t dos_namelen;
+ fstring dos_ea_name;
+ size_t this_size;
+ size_t pad = 0;
+
+ if (last_start != NULL && do_store_data) {
+ SIVAL(last_start, 0, PTR_DIFF(p, last_start));
+ }
+ last_start = p;
+
+ push_ascii_fstring(dos_ea_name, ea_list->ea.name);
+ dos_namelen = strlen(dos_ea_name);
+ if (dos_namelen > 255 || dos_namelen == 0) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ if (ea_list->ea.value.length > 65535) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ this_size = 0x08 + dos_namelen + 1 + ea_list->ea.value.length;
+
+ if (ea_list->next) {
+ pad = (4 - (this_size % 4)) % 4;
+ this_size += pad;
+ }
+
+ if (do_store_data) {
+ if (this_size > total_data_size) {
+ return NT_STATUS_INFO_LENGTH_MISMATCH;
+ }
+
+ /* We know we have room. */
+ SIVAL(p, 0x00, 0); /* next offset */
+ SCVAL(p, 0x04, ea_list->ea.flags);
+ SCVAL(p, 0x05, dos_namelen);
+ SSVAL(p, 0x06, ea_list->ea.value.length);
+ strlcpy((char *)(p+0x08), dos_ea_name, dos_namelen+1);
+ memcpy(p + 0x08 + dos_namelen + 1, ea_list->ea.value.data, ea_list->ea.value.length);
+ if (pad) {
+ memset(p + 0x08 + dos_namelen + 1 + ea_list->ea.value.length,
+ '\0',
+ pad);
+ }
+ total_data_size -= this_size;
+ }
+
+ p += this_size;
+ }
+
+ *ret_data_size = PTR_DIFF(p, pdata);
+ DEBUG(10,("fill_ea_chained_buffer: data_size = %u\n", *ret_data_size));
+ return NT_STATUS_OK;
+}
+
+unsigned int estimate_ea_size(files_struct *fsp)
+{
+ size_t total_ea_len = 0;
+ TALLOC_CTX *mem_ctx;
+ struct ea_list *ea_list = NULL;
+ NTSTATUS status;
+
+ /* symlink */
+ if (fsp == NULL) {
+ return 0;
+ }
+
+ if (!lp_ea_support(SNUM(fsp->conn))) {
+ return 0;
+ }
+
+ mem_ctx = talloc_stackframe();
+
+ /* If this is a stream fsp, then we need to instead find the
+ * estimated ea len from the main file, not the stream
+ * (streams cannot have EAs), but the estimate isn't just 0 in
+ * this case! */
+ fsp = metadata_fsp(fsp);
+ (void)get_ea_list_from_fsp(mem_ctx,
+ fsp,
+ &total_ea_len,
+ &ea_list);
+
+ if(fsp->conn->sconn->using_smb2) {
+ unsigned int ret_data_size;
+ /*
+ * We're going to be using fill_ea_chained_buffer() to
+ * marshall EA's - this size is significantly larger
+ * than the SMB1 buffer. Re-calculate the size without
+ * marshalling.
+ */
+ status = fill_ea_chained_buffer(mem_ctx,
+ NULL,
+ 0,
+ &ret_data_size,
+ fsp->conn,
+ ea_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ ret_data_size = 0;
+ }
+ total_ea_len = ret_data_size;
+ }
+ TALLOC_FREE(mem_ctx);
+ return total_ea_len;
+}
+
+/****************************************************************************
+ Ensure the EA name is case insensitive by matching any existing EA name.
+****************************************************************************/
+
+static void canonicalize_ea_name(files_struct *fsp,
+ fstring unix_ea_name)
+{
+ size_t total_ea_len;
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ struct ea_list *ea_list;
+ NTSTATUS status = get_ea_list_from_fsp(mem_ctx,
+ fsp,
+ &total_ea_len,
+ &ea_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ return;
+ }
+
+ for (; ea_list; ea_list = ea_list->next) {
+ if (strequal(&unix_ea_name[5], ea_list->ea.name)) {
+ DEBUG(10,("canonicalize_ea_name: %s -> %s\n",
+ &unix_ea_name[5], ea_list->ea.name));
+ strlcpy(&unix_ea_name[5], ea_list->ea.name, sizeof(fstring)-5);
+ break;
+ }
+ }
+}
+
+/****************************************************************************
+ Set or delete an extended attribute.
+****************************************************************************/
+
+NTSTATUS set_ea(connection_struct *conn, files_struct *fsp,
+ struct ea_list *ea_list)
+{
+ NTSTATUS status;
+ bool posix_pathnames = false;
+
+ if (!lp_ea_support(SNUM(conn))) {
+ return NT_STATUS_EAS_NOT_SUPPORTED;
+ }
+
+ if (fsp == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ posix_pathnames = (fsp->fsp_name->flags & SMB_FILENAME_POSIX_PATH);
+
+ status = refuse_symlink_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = check_any_access_fsp(fsp, FILE_WRITE_EA);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* Setting EAs on streams isn't supported. */
+ if (fsp_is_alternate_stream(fsp)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * Filter out invalid Windows EA names - before
+ * we set *any* of them.
+ */
+
+ if (!posix_pathnames && ea_list_has_invalid_name(ea_list)) {
+ return STATUS_INVALID_EA_NAME;
+ }
+
+ for (;ea_list; ea_list = ea_list->next) {
+ int ret;
+ fstring unix_ea_name;
+
+ /*
+ * Complementing the forward mapping from POSIX EAs to
+ * Windows EAs in get_ea_list_from_fsp(), here we map in the
+ * opposite direction from Windows EAs to the 'user' namespace
+ * of POSIX EAs. Hence, all POSIX EA names the we set here must
+ * start with a 'user.' prefix.
+ */
+ fstrcpy(unix_ea_name, "user.");
+ fstrcat(unix_ea_name, ea_list->ea.name);
+
+ canonicalize_ea_name(fsp, unix_ea_name);
+
+ DEBUG(10,("set_ea: ea_name %s ealen = %u\n", unix_ea_name, (unsigned int)ea_list->ea.value.length));
+
+ if (samba_private_attr_name(unix_ea_name)) {
+ DEBUG(10,("set_ea: ea name %s is a private Samba name.\n", unix_ea_name));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (ea_list->ea.value.length == 0) {
+ /* Remove the attribute. */
+ DBG_DEBUG("deleting ea name %s on "
+ "file %s by file descriptor.\n",
+ unix_ea_name, fsp_str_dbg(fsp));
+ ret = SMB_VFS_FREMOVEXATTR(fsp, unix_ea_name);
+#ifdef ENOATTR
+ /* Removing a non existent attribute always succeeds. */
+ if (ret == -1 && errno == ENOATTR) {
+ DEBUG(10,("set_ea: deleting ea name %s didn't exist - succeeding by default.\n",
+ unix_ea_name));
+ ret = 0;
+ }
+#endif
+ } else {
+ DEBUG(10,("set_ea: setting ea name %s on file "
+ "%s by file descriptor.\n",
+ unix_ea_name, fsp_str_dbg(fsp)));
+ ret = SMB_VFS_FSETXATTR(fsp, unix_ea_name,
+ ea_list->ea.value.data, ea_list->ea.value.length, 0);
+ }
+
+ if (ret == -1) {
+#ifdef ENOTSUP
+ if (errno == ENOTSUP) {
+ return NT_STATUS_EAS_NOT_SUPPORTED;
+ }
+#endif
+ return map_nt_error_from_unix(errno);
+ }
+
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Read a list of EA names and data from an incoming data buffer. Create an ea_list with them.
+****************************************************************************/
+
+struct ea_list *read_ea_list(TALLOC_CTX *ctx, const char *pdata, size_t data_size)
+{
+ struct ea_list *ea_list_head = NULL;
+ size_t offset = 0;
+ size_t bytes_used = 0;
+
+ while (offset < data_size) {
+ struct ea_list *eal = read_ea_list_entry(ctx, pdata + offset, data_size - offset, &bytes_used);
+
+ if (!eal) {
+ return NULL;
+ }
+
+ DLIST_ADD_END(ea_list_head, eal);
+ offset += bytes_used;
+ }
+
+ return ea_list_head;
+}
+
+/****************************************************************************
+ Count the total EA size needed.
+****************************************************************************/
+
+static size_t ea_list_size(struct ea_list *ealist)
+{
+ fstring dos_ea_name;
+ struct ea_list *listp;
+ size_t ret = 0;
+
+ for (listp = ealist; listp; listp = listp->next) {
+ push_ascii_fstring(dos_ea_name, listp->ea.name);
+ ret += 4 + strlen(dos_ea_name) + 1 + listp->ea.value.length;
+ }
+ /* Add on 4 for total length. */
+ if (ret) {
+ ret += 4;
+ }
+
+ return ret;
+}
+
+/****************************************************************************
+ Return a union of EA's from a file list and a list of names.
+ The TALLOC context for the two lists *MUST* be identical as we steal
+ memory from one list to add to another. JRA.
+****************************************************************************/
+
+static struct ea_list *ea_list_union(struct ea_list *name_list, struct ea_list *file_list, size_t *total_ea_len)
+{
+ struct ea_list *nlistp, *flistp;
+
+ for (nlistp = name_list; nlistp; nlistp = nlistp->next) {
+ for (flistp = file_list; flistp; flistp = flistp->next) {
+ if (strequal(nlistp->ea.name, flistp->ea.name)) {
+ break;
+ }
+ }
+
+ if (flistp) {
+ /* Copy the data from this entry. */
+ nlistp->ea.flags = flistp->ea.flags;
+ nlistp->ea.value = flistp->ea.value;
+ } else {
+ /* Null entry. */
+ nlistp->ea.flags = 0;
+ ZERO_STRUCT(nlistp->ea.value);
+ }
+ }
+
+ *total_ea_len = ea_list_size(name_list);
+ return name_list;
+}
+
+/****************************************************************************
+ Return the filetype for UNIX extensions.
+****************************************************************************/
+
+static uint32_t unix_filetype(mode_t mode)
+{
+ if(S_ISREG(mode))
+ return UNIX_TYPE_FILE;
+ else if(S_ISDIR(mode))
+ return UNIX_TYPE_DIR;
+#ifdef S_ISLNK
+ else if(S_ISLNK(mode))
+ return UNIX_TYPE_SYMLINK;
+#endif
+#ifdef S_ISCHR
+ else if(S_ISCHR(mode))
+ return UNIX_TYPE_CHARDEV;
+#endif
+#ifdef S_ISBLK
+ else if(S_ISBLK(mode))
+ return UNIX_TYPE_BLKDEV;
+#endif
+#ifdef S_ISFIFO
+ else if(S_ISFIFO(mode))
+ return UNIX_TYPE_FIFO;
+#endif
+#ifdef S_ISSOCK
+ else if(S_ISSOCK(mode))
+ return UNIX_TYPE_SOCKET;
+#endif
+
+ DEBUG(0,("unix_filetype: unknown filetype %u\n", (unsigned)mode));
+ return UNIX_TYPE_UNKNOWN;
+}
+
+/****************************************************************************
+ Map wire perms onto standard UNIX permissions. Obey share restrictions.
+****************************************************************************/
+
+NTSTATUS unix_perms_from_wire(connection_struct *conn,
+ const SMB_STRUCT_STAT *psbuf,
+ uint32_t perms,
+ enum perm_type ptype,
+ mode_t *ret_perms)
+{
+ mode_t ret = 0;
+
+ if (perms == SMB_MODE_NO_CHANGE) {
+ if (!VALID_STAT(*psbuf)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ } else {
+ *ret_perms = psbuf->st_ex_mode;
+ return NT_STATUS_OK;
+ }
+ }
+
+ ret = wire_perms_to_unix(perms);
+
+ if (ptype == PERM_NEW_FILE) {
+ /*
+ * "create mask"/"force create mode" are
+ * only applied to new files, not existing ones.
+ */
+ ret &= lp_create_mask(SNUM(conn));
+ /* Add in force bits */
+ ret |= lp_force_create_mode(SNUM(conn));
+ } else if (ptype == PERM_NEW_DIR) {
+ /*
+ * "directory mask"/"force directory mode" are
+ * only applied to new directories, not existing ones.
+ */
+ ret &= lp_directory_mask(SNUM(conn));
+ /* Add in force bits */
+ ret |= lp_force_directory_mode(SNUM(conn));
+ }
+
+ *ret_perms = ret;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Get a level dependent lanman2 dir entry.
+****************************************************************************/
+
+struct smbd_dirptr_lanman2_state {
+ connection_struct *conn;
+ uint32_t info_level;
+ bool check_mangled_names;
+ bool case_sensitive;
+};
+
+static bool smbd_dirptr_lanman2_match_fn(TALLOC_CTX *ctx,
+ void *private_data,
+ const char *dname,
+ const char *mask,
+ char **_fname)
+{
+ struct smbd_dirptr_lanman2_state *state =
+ (struct smbd_dirptr_lanman2_state *)private_data;
+ bool ok;
+ char mangled_name[13]; /* mangled 8.3 name. */
+ bool got_match;
+ const char *fname;
+
+ /* Mangle fname if it's an illegal name. */
+ if (mangle_must_mangle(dname, state->conn->params)) {
+ /*
+ * Slow path - ensure we can push the original name as UCS2. If
+ * not, then just don't return this name.
+ */
+ NTSTATUS status;
+ size_t ret_len = 0;
+ size_t len = (strlen(dname) + 2) * 4; /* Allow enough space. */
+ uint8_t *tmp = talloc_array(talloc_tos(),
+ uint8_t,
+ len);
+
+ status = srvstr_push(NULL,
+ FLAGS2_UNICODE_STRINGS,
+ tmp,
+ dname,
+ len,
+ STR_TERMINATE,
+ &ret_len);
+
+ TALLOC_FREE(tmp);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ ok = name_to_8_3(dname, mangled_name,
+ true, state->conn->params);
+ if (!ok) {
+ return false;
+ }
+ fname = mangled_name;
+ } else {
+ fname = dname;
+ }
+
+ got_match = mask_match(fname, mask,
+ state->case_sensitive);
+
+ if(!got_match && state->check_mangled_names &&
+ !mangle_is_8_3(fname, false, state->conn->params)) {
+ /*
+ * It turns out that NT matches wildcards against
+ * both long *and* short names. This may explain some
+ * of the wildcard weirdness from old DOS clients
+ * that some people have been seeing.... JRA.
+ */
+ /* Force the mangling into 8.3. */
+ ok = name_to_8_3(fname, mangled_name,
+ false, state->conn->params);
+ if (!ok) {
+ return false;
+ }
+
+ got_match = mask_match(mangled_name, mask,
+ state->case_sensitive);
+ }
+
+ if (!got_match) {
+ return false;
+ }
+
+ *_fname = talloc_strdup(ctx, fname);
+ if (*_fname == NULL) {
+ return false;
+ }
+
+ return true;
+}
+
+static uint32_t get_dirent_ea_size(uint32_t mode, files_struct *fsp)
+{
+ if (!(mode & FILE_ATTRIBUTE_REPARSE_POINT)) {
+ unsigned ea_size = estimate_ea_size(fsp);
+ return ea_size;
+ }
+ return IO_REPARSE_TAG_DFS;
+}
+
+static NTSTATUS smbd_marshall_dir_entry(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ uint16_t flags2,
+ uint32_t info_level,
+ struct ea_list *name_list,
+ bool check_mangled_names,
+ bool requires_resume_key,
+ uint32_t mode,
+ const char *fname,
+ const struct smb_filename *smb_fname,
+ int space_remaining,
+ uint8_t align,
+ bool do_pad,
+ char *base_data,
+ char **ppdata,
+ char *end_data,
+ uint64_t *last_entry_off)
+{
+ char *p, *q, *pdata = *ppdata;
+ uint32_t reskey=0;
+ uint64_t file_size = 0;
+ uint64_t allocation_size = 0;
+ uint64_t file_id = 0;
+ size_t len = 0;
+ struct timespec mdate_ts = {0};
+ struct timespec adate_ts = {0};
+ struct timespec cdate_ts = {0};
+ struct timespec create_date_ts = {0};
+ char *nameptr;
+ char *last_entry_ptr;
+ bool was_8_3;
+ int off;
+ int pad = 0;
+ NTSTATUS status;
+ struct readdir_attr_data *readdir_attr_data = NULL;
+ uint32_t ea_size;
+
+ if (!(mode & FILE_ATTRIBUTE_DIRECTORY)) {
+ file_size = get_file_size_stat(&smb_fname->st);
+ }
+ allocation_size = SMB_VFS_GET_ALLOC_SIZE(conn, NULL, &smb_fname->st);
+
+ /*
+ * Skip SMB_VFS_FREADDIR_ATTR if the directory entry is a symlink or
+ * a DFS symlink.
+ */
+ if (smb_fname->fsp != NULL &&
+ !(mode & FILE_ATTRIBUTE_REPARSE_POINT)) {
+ status = SMB_VFS_FREADDIR_ATTR(smb_fname->fsp,
+ ctx,
+ &readdir_attr_data);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (!NT_STATUS_EQUAL(NT_STATUS_NOT_SUPPORTED,
+ status)) {
+ return status;
+ }
+ }
+ }
+
+ file_id = SMB_VFS_FS_FILE_ID(conn, &smb_fname->st);
+
+ mdate_ts = smb_fname->st.st_ex_mtime;
+ adate_ts = smb_fname->st.st_ex_atime;
+ create_date_ts = get_create_timespec(conn, NULL, smb_fname);
+ cdate_ts = get_change_timespec(conn, NULL, smb_fname);
+
+ if (lp_dos_filetime_resolution(SNUM(conn))) {
+ dos_filetime_timespec(&create_date_ts);
+ dos_filetime_timespec(&mdate_ts);
+ dos_filetime_timespec(&adate_ts);
+ dos_filetime_timespec(&cdate_ts);
+ }
+
+ /* align the record */
+ SMB_ASSERT(align >= 1);
+
+ off = (int)PTR_DIFF(pdata, base_data);
+ pad = (off + (align-1)) & ~(align-1);
+ pad -= off;
+
+ if (pad && pad > space_remaining) {
+ DEBUG(9,("smbd_marshall_dir_entry: out of space "
+ "for padding (wanted %u, had %d)\n",
+ (unsigned int)pad,
+ space_remaining ));
+ return STATUS_MORE_ENTRIES; /* Not finished - just out of space */
+ }
+
+ off += pad;
+ /* initialize padding to 0 */
+ if (pad) {
+ memset(pdata, 0, pad);
+ }
+ space_remaining -= pad;
+
+ DEBUG(10,("smbd_marshall_dir_entry: space_remaining = %d\n",
+ space_remaining ));
+
+ pdata += pad;
+ p = pdata;
+ last_entry_ptr = p;
+
+ pad = 0;
+ off = 0;
+
+ switch (info_level) {
+ case SMB_FIND_INFO_STANDARD:
+ DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_INFO_STANDARD\n"));
+ if(requires_resume_key) {
+ SIVAL(p,0,reskey);
+ p += 4;
+ }
+ srv_put_dos_date2_ts(p, 0, create_date_ts);
+ srv_put_dos_date2_ts(p, 4, adate_ts);
+ srv_put_dos_date2_ts(p, 8, mdate_ts);
+ SIVAL(p,12,(uint32_t)file_size);
+ SIVAL(p,16,(uint32_t)allocation_size);
+ SSVAL(p,20,mode);
+ p += 23;
+ nameptr = p;
+ if (flags2 & FLAGS2_UNICODE_STRINGS) {
+ p += ucs2_align(base_data, p, 0);
+ }
+ status = srvstr_push(base_data, flags2, p,
+ fname, PTR_DIFF(end_data, p),
+ STR_TERMINATE, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (flags2 & FLAGS2_UNICODE_STRINGS) {
+ if (len > 2) {
+ SCVAL(nameptr, -1, len - 2);
+ } else {
+ SCVAL(nameptr, -1, 0);
+ }
+ } else {
+ if (len > 1) {
+ SCVAL(nameptr, -1, len - 1);
+ } else {
+ SCVAL(nameptr, -1, 0);
+ }
+ }
+ p += len;
+ break;
+
+ case SMB_FIND_EA_SIZE:
+ DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_EA_SIZE\n"));
+ if (requires_resume_key) {
+ SIVAL(p,0,reskey);
+ p += 4;
+ }
+ srv_put_dos_date2_ts(p, 0, create_date_ts);
+ srv_put_dos_date2_ts(p, 4, adate_ts);
+ srv_put_dos_date2_ts(p, 8, mdate_ts);
+ SIVAL(p,12,(uint32_t)file_size);
+ SIVAL(p,16,(uint32_t)allocation_size);
+ SSVAL(p,20,mode);
+ {
+ ea_size = estimate_ea_size(smb_fname->fsp);
+ SIVAL(p,22,ea_size); /* Extended attributes */
+ }
+ p += 27;
+ nameptr = p - 1;
+ status = srvstr_push(base_data, flags2,
+ p, fname, PTR_DIFF(end_data, p),
+ STR_TERMINATE | STR_NOALIGN, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (flags2 & FLAGS2_UNICODE_STRINGS) {
+ if (len > 2) {
+ len -= 2;
+ } else {
+ len = 0;
+ }
+ } else {
+ if (len > 1) {
+ len -= 1;
+ } else {
+ len = 0;
+ }
+ }
+ SCVAL(nameptr,0,len);
+ p += len;
+ SCVAL(p,0,0); p += 1; /* Extra zero byte ? - why.. */
+ break;
+
+ case SMB_FIND_EA_LIST:
+ {
+ struct ea_list *file_list = NULL;
+ size_t ea_len = 0;
+
+ DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_EA_LIST\n"));
+ if (!name_list) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ if (requires_resume_key) {
+ SIVAL(p,0,reskey);
+ p += 4;
+ }
+ srv_put_dos_date2_ts(p, 0, create_date_ts);
+ srv_put_dos_date2_ts(p, 4, adate_ts);
+ srv_put_dos_date2_ts(p, 8, mdate_ts);
+ SIVAL(p,12,(uint32_t)file_size);
+ SIVAL(p,16,(uint32_t)allocation_size);
+ SSVAL(p,20,mode);
+ p += 22; /* p now points to the EA area. */
+
+ status = get_ea_list_from_fsp(ctx,
+ smb_fname->fsp,
+ &ea_len, &file_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ file_list = NULL;
+ }
+ name_list = ea_list_union(name_list, file_list, &ea_len);
+
+ /* We need to determine if this entry will fit in the space available. */
+ /* Max string size is 255 bytes. */
+ if (PTR_DIFF(p + 255 + ea_len,pdata) > space_remaining) {
+ DEBUG(9,("smbd_marshall_dir_entry: out of space "
+ "(wanted %u, had %d)\n",
+ (unsigned int)PTR_DIFF(p + 255 + ea_len,pdata),
+ space_remaining ));
+ return STATUS_MORE_ENTRIES; /* Not finished - just out of space */
+ }
+
+ /* Push the ea_data followed by the name. */
+ p += fill_ea_buffer(ctx, p, space_remaining, conn, name_list);
+ nameptr = p;
+ status = srvstr_push(base_data, flags2,
+ p + 1, fname, PTR_DIFF(end_data, p+1),
+ STR_TERMINATE | STR_NOALIGN, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (flags2 & FLAGS2_UNICODE_STRINGS) {
+ if (len > 2) {
+ len -= 2;
+ } else {
+ len = 0;
+ }
+ } else {
+ if (len > 1) {
+ len -= 1;
+ } else {
+ len = 0;
+ }
+ }
+ SCVAL(nameptr,0,len);
+ p += len + 1;
+ SCVAL(p,0,0); p += 1; /* Extra zero byte ? - why.. */
+ break;
+ }
+
+ case SMB_FIND_FILE_BOTH_DIRECTORY_INFO:
+ DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_FILE_BOTH_DIRECTORY_INFO\n"));
+ was_8_3 = mangle_is_8_3(fname, True, conn->params);
+ p += 4;
+ SIVAL(p,0,reskey); p += 4;
+ put_long_date_full_timespec(conn->ts_res,p,&create_date_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&adate_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&mdate_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&cdate_ts); p += 8;
+ SOFF_T(p,0,file_size); p += 8;
+ SOFF_T(p,0,allocation_size); p += 8;
+ SIVAL(p,0,mode); p += 4;
+ q = p; p += 4; /* q is placeholder for name length. */
+ ea_size = get_dirent_ea_size(mode, smb_fname->fsp);
+ SIVAL(p, 0, ea_size);
+ p += 4;
+ /* Clear the short name buffer. This is
+ * IMPORTANT as not doing so will trigger
+ * a Win2k client bug. JRA.
+ */
+ if (!was_8_3 && check_mangled_names) {
+ char mangled_name[13]; /* mangled 8.3 name. */
+ if (!name_to_8_3(fname,mangled_name,True,
+ conn->params)) {
+ /* Error - mangle failed ! */
+ memset(mangled_name,'\0',12);
+ }
+ mangled_name[12] = 0;
+ status = srvstr_push(base_data, flags2,
+ p+2, mangled_name, 24,
+ STR_UPPER|STR_UNICODE, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (len < 24) {
+ memset(p + 2 + len,'\0',24 - len);
+ }
+ SSVAL(p, 0, len);
+ } else {
+ memset(p,'\0',26);
+ }
+ p += 2 + 24;
+ status = srvstr_push(base_data, flags2, p,
+ fname, PTR_DIFF(end_data, p),
+ STR_TERMINATE_ASCII, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ SIVAL(q,0,len);
+ p += len;
+
+ len = PTR_DIFF(p, pdata);
+ pad = (len + (align-1)) & ~(align-1);
+ /*
+ * offset to the next entry, the caller
+ * will overwrite it for the last entry
+ * that's why we always include the padding
+ */
+ SIVAL(pdata,0,pad);
+ /*
+ * set padding to zero
+ */
+ if (do_pad) {
+ memset(p, 0, pad - len);
+ p = pdata + pad;
+ } else {
+ p = pdata + len;
+ }
+ break;
+
+ case SMB_FIND_FILE_DIRECTORY_INFO:
+ DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_FILE_DIRECTORY_INFO\n"));
+ p += 4;
+ SIVAL(p,0,reskey); p += 4;
+ put_long_date_full_timespec(conn->ts_res,p,&create_date_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&adate_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&mdate_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&cdate_ts); p += 8;
+ SOFF_T(p,0,file_size); p += 8;
+ SOFF_T(p,0,allocation_size); p += 8;
+ SIVAL(p,0,mode); p += 4;
+ status = srvstr_push(base_data, flags2,
+ p + 4, fname, PTR_DIFF(end_data, p+4),
+ STR_TERMINATE_ASCII, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ SIVAL(p,0,len);
+ p += 4 + len;
+
+ len = PTR_DIFF(p, pdata);
+ pad = (len + (align-1)) & ~(align-1);
+ /*
+ * offset to the next entry, the caller
+ * will overwrite it for the last entry
+ * that's why we always include the padding
+ */
+ SIVAL(pdata,0,pad);
+ /*
+ * set padding to zero
+ */
+ if (do_pad) {
+ memset(p, 0, pad - len);
+ p = pdata + pad;
+ } else {
+ p = pdata + len;
+ }
+ break;
+
+ case SMB_FIND_FILE_FULL_DIRECTORY_INFO:
+ DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_FILE_FULL_DIRECTORY_INFO\n"));
+ p += 4;
+ SIVAL(p,0,reskey); p += 4;
+ put_long_date_full_timespec(conn->ts_res,p,&create_date_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&adate_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&mdate_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&cdate_ts); p += 8;
+ SOFF_T(p,0,file_size); p += 8;
+ SOFF_T(p,0,allocation_size); p += 8;
+ SIVAL(p,0,mode); p += 4;
+ q = p; p += 4; /* q is placeholder for name length. */
+ ea_size = get_dirent_ea_size(mode, smb_fname->fsp);
+ SIVAL(p, 0, ea_size);
+ p +=4;
+ status = srvstr_push(base_data, flags2, p,
+ fname, PTR_DIFF(end_data, p),
+ STR_TERMINATE_ASCII, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ SIVAL(q, 0, len);
+ p += len;
+
+ len = PTR_DIFF(p, pdata);
+ pad = (len + (align-1)) & ~(align-1);
+ /*
+ * offset to the next entry, the caller
+ * will overwrite it for the last entry
+ * that's why we always include the padding
+ */
+ SIVAL(pdata,0,pad);
+ /*
+ * set padding to zero
+ */
+ if (do_pad) {
+ memset(p, 0, pad - len);
+ p = pdata + pad;
+ } else {
+ p = pdata + len;
+ }
+ break;
+
+ case SMB_FIND_FILE_NAMES_INFO:
+ DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_FILE_NAMES_INFO\n"));
+ p += 4;
+ SIVAL(p,0,reskey); p += 4;
+ p += 4;
+ /* this must *not* be null terminated or w2k gets in a loop trying to set an
+ acl on a dir (tridge) */
+ status = srvstr_push(base_data, flags2, p,
+ fname, PTR_DIFF(end_data, p),
+ STR_TERMINATE_ASCII, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ SIVAL(p, -4, len);
+ p += len;
+
+ len = PTR_DIFF(p, pdata);
+ pad = (len + (align-1)) & ~(align-1);
+ /*
+ * offset to the next entry, the caller
+ * will overwrite it for the last entry
+ * that's why we always include the padding
+ */
+ SIVAL(pdata,0,pad);
+ /*
+ * set padding to zero
+ */
+ if (do_pad) {
+ memset(p, 0, pad - len);
+ p = pdata + pad;
+ } else {
+ p = pdata + len;
+ }
+ break;
+
+ case SMB_FIND_ID_FULL_DIRECTORY_INFO:
+ DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_ID_FULL_DIRECTORY_INFO\n"));
+ p += 4;
+ SIVAL(p,0,reskey); p += 4;
+ put_long_date_full_timespec(conn->ts_res,p,&create_date_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&adate_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&mdate_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&cdate_ts); p += 8;
+ SOFF_T(p,0,file_size); p += 8;
+ SOFF_T(p,0,allocation_size); p += 8;
+ SIVAL(p,0,mode); p += 4;
+ q = p; p += 4; /* q is placeholder for name length. */
+ ea_size = get_dirent_ea_size(mode, smb_fname->fsp);
+ SIVAL(p, 0, ea_size);
+ p += 4;
+ SIVAL(p,0,0); p += 4; /* Unknown - reserved ? */
+ SBVAL(p,0,file_id); p += 8;
+ status = srvstr_push(base_data, flags2, p,
+ fname, PTR_DIFF(end_data, p),
+ STR_TERMINATE_ASCII, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ SIVAL(q, 0, len);
+ p += len;
+
+ len = PTR_DIFF(p, pdata);
+ pad = (len + (align-1)) & ~(align-1);
+ /*
+ * offset to the next entry, the caller
+ * will overwrite it for the last entry
+ * that's why we always include the padding
+ */
+ SIVAL(pdata,0,pad);
+ /*
+ * set padding to zero
+ */
+ if (do_pad) {
+ memset(p, 0, pad - len);
+ p = pdata + pad;
+ } else {
+ p = pdata + len;
+ }
+ break;
+
+ case SMB_FIND_ID_BOTH_DIRECTORY_INFO:
+ DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_ID_BOTH_DIRECTORY_INFO\n"));
+ was_8_3 = mangle_is_8_3(fname, True, conn->params);
+ p += 4;
+ SIVAL(p,0,reskey); p += 4;
+ put_long_date_full_timespec(conn->ts_res,p,&create_date_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&adate_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&mdate_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&cdate_ts); p += 8;
+ SOFF_T(p,0,file_size); p += 8;
+ SOFF_T(p,0,allocation_size); p += 8;
+ SIVAL(p,0,mode); p += 4;
+ q = p; p += 4; /* q is placeholder for name length */
+ if (readdir_attr_data &&
+ readdir_attr_data->type == RDATTR_AAPL) {
+ /*
+ * OS X specific SMB2 extension negotiated via
+ * AAPL create context: return max_access in
+ * ea_size field.
+ */
+ ea_size = readdir_attr_data->attr_data.aapl.max_access;
+ } else {
+ ea_size = get_dirent_ea_size(mode, smb_fname->fsp);
+ }
+ SIVAL(p,0,ea_size); /* Extended attributes */
+ p += 4;
+
+ if (readdir_attr_data &&
+ readdir_attr_data->type == RDATTR_AAPL) {
+ /*
+ * OS X specific SMB2 extension negotiated via
+ * AAPL create context: return resource fork
+ * length and compressed FinderInfo in
+ * shortname field.
+ *
+ * According to documentation short_name_len
+ * should be 0, but on the wire behaviour
+ * shows its set to 24 by clients.
+ */
+ SSVAL(p, 0, 24);
+
+ /* Resourefork length */
+ SBVAL(p, 2, readdir_attr_data->attr_data.aapl.rfork_size);
+
+ /* Compressed FinderInfo */
+ memcpy(p + 10, &readdir_attr_data->attr_data.aapl.finder_info, 16);
+ } else if (!was_8_3 && check_mangled_names) {
+ char mangled_name[13]; /* mangled 8.3 name. */
+ if (!name_to_8_3(fname,mangled_name,True,
+ conn->params)) {
+ /* Error - mangle failed ! */
+ memset(mangled_name,'\0',12);
+ }
+ mangled_name[12] = 0;
+ status = srvstr_push(base_data, flags2,
+ p+2, mangled_name, 24,
+ STR_UPPER|STR_UNICODE, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ SSVAL(p, 0, len);
+ if (len < 24) {
+ memset(p + 2 + len,'\0',24 - len);
+ }
+ SSVAL(p, 0, len);
+ } else {
+ /* Clear the short name buffer. This is
+ * IMPORTANT as not doing so will trigger
+ * a Win2k client bug. JRA.
+ */
+ memset(p,'\0',26);
+ }
+ p += 26;
+
+ /* Reserved ? */
+ if (readdir_attr_data &&
+ readdir_attr_data->type == RDATTR_AAPL) {
+ /*
+ * OS X specific SMB2 extension negotiated via
+ * AAPL create context: return UNIX mode in
+ * reserved field.
+ */
+ uint16_t aapl_mode = (uint16_t)readdir_attr_data->attr_data.aapl.unix_mode;
+ SSVAL(p, 0, aapl_mode);
+ } else {
+ SSVAL(p, 0, 0);
+ }
+ p += 2;
+
+ SBVAL(p,0,file_id); p += 8;
+ status = srvstr_push(base_data, flags2, p,
+ fname, PTR_DIFF(end_data, p),
+ STR_TERMINATE_ASCII, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ SIVAL(q,0,len);
+ p += len;
+
+ len = PTR_DIFF(p, pdata);
+ pad = (len + (align-1)) & ~(align-1);
+ /*
+ * offset to the next entry, the caller
+ * will overwrite it for the last entry
+ * that's why we always include the padding
+ */
+ SIVAL(pdata,0,pad);
+ /*
+ * set padding to zero
+ */
+ if (do_pad) {
+ memset(p, 0, pad - len);
+ p = pdata + pad;
+ } else {
+ p = pdata + len;
+ }
+ break;
+
+ /* CIFS UNIX Extension. */
+
+ case SMB_FIND_FILE_UNIX:
+ case SMB_FIND_FILE_UNIX_INFO2:
+ p+= 4;
+ SIVAL(p,0,reskey); p+= 4; /* Used for continuing search. */
+
+ /* Begin of SMB_QUERY_FILE_UNIX_BASIC */
+
+ if (info_level == SMB_FIND_FILE_UNIX) {
+ DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_FILE_UNIX\n"));
+ p = store_file_unix_basic(conn, p,
+ NULL, &smb_fname->st);
+ status = srvstr_push(base_data, flags2, p,
+ fname, PTR_DIFF(end_data, p),
+ STR_TERMINATE, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ } else {
+ DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_FILE_UNIX_INFO2\n"));
+ p = store_file_unix_basic_info2(conn, p,
+ NULL, &smb_fname->st);
+ nameptr = p;
+ p += 4;
+ status = srvstr_push(base_data, flags2, p, fname,
+ PTR_DIFF(end_data, p), 0, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ SIVAL(nameptr, 0, len);
+ }
+
+ p += len;
+
+ len = PTR_DIFF(p, pdata);
+ pad = (len + (align-1)) & ~(align-1);
+ /*
+ * offset to the next entry, the caller
+ * will overwrite it for the last entry
+ * that's why we always include the padding
+ */
+ SIVAL(pdata,0,pad);
+ /*
+ * set padding to zero
+ */
+ if (do_pad) {
+ memset(p, 0, pad - len);
+ p = pdata + pad;
+ } else {
+ p = pdata + len;
+ }
+ /* End of SMB_QUERY_FILE_UNIX_BASIC */
+
+ break;
+
+ /* SMB2 UNIX Extension. */
+
+ case SMB2_FILE_POSIX_INFORMATION:
+ {
+ struct smb3_file_posix_information info = {};
+ uint8_t buf[sizeof(info)];
+ struct ndr_push ndr = {
+ .data = buf,
+ .alloc_size = sizeof(buf),
+ .fixed_buf_size = true,
+ };
+ enum ndr_err_code ndr_err;
+
+ p+= 4;
+ SIVAL(p,0,reskey); p+= 4;
+
+ DBG_DEBUG("SMB2_FILE_POSIX_INFORMATION\n");
+
+ if (!(conn->sconn->using_smb2)) {
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ smb3_file_posix_information_init(
+ conn, &smb_fname->st, 0, mode, &info);
+
+ ndr_err = ndr_push_smb3_file_posix_information(
+ &ndr, NDR_SCALARS|NDR_BUFFERS, &info);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ memcpy(p, buf, ndr.offset);
+ p += ndr.offset;
+
+ nameptr = p;
+ p += 4;
+ status = srvstr_push(base_data, flags2, p, fname,
+ PTR_DIFF(end_data, p), 0, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ SIVAL(nameptr, 0, len);
+
+ p += len;
+
+ len = PTR_DIFF(p, pdata);
+ pad = (len + (align-1)) & ~(align-1);
+ /*
+ * offset to the next entry, the caller
+ * will overwrite it for the last entry
+ * that's why we always include the padding
+ */
+ SIVAL(pdata,0,pad);
+ break;
+ }
+
+ default:
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ if (PTR_DIFF(p,pdata) > space_remaining) {
+ DEBUG(9,("smbd_marshall_dir_entry: out of space "
+ "(wanted %u, had %d)\n",
+ (unsigned int)PTR_DIFF(p,pdata),
+ space_remaining ));
+ return STATUS_MORE_ENTRIES; /* Not finished - just out of space */
+ }
+
+ /* Setup the last entry pointer, as an offset from base_data */
+ *last_entry_off = PTR_DIFF(last_entry_ptr,base_data);
+ /* Advance the data pointer to the next slot */
+ *ppdata = p;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smbd_dirptr_lanman2_entry(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct dptr_struct *dirptr,
+ uint16_t flags2,
+ const char *path_mask,
+ uint32_t dirtype,
+ int info_level,
+ int requires_resume_key,
+ bool dont_descend,
+ bool ask_sharemode,
+ bool get_dosmode,
+ uint8_t align,
+ bool do_pad,
+ char **ppdata,
+ char *base_data,
+ char *end_data,
+ int space_remaining,
+ struct smb_filename **_smb_fname,
+ int *_last_entry_off,
+ struct ea_list *name_list,
+ struct file_id *file_id)
+{
+ const char *p;
+ const char *mask = NULL;
+ uint32_t mode = 0;
+ char *fname = NULL;
+ struct smb_filename *smb_fname = NULL;
+ struct smbd_dirptr_lanman2_state state;
+ bool ok;
+ uint64_t last_entry_off = 0;
+ NTSTATUS status;
+ enum mangled_names_options mangled_names;
+ bool marshall_with_83_names;
+
+ mangled_names = lp_mangled_names(conn->params);
+
+ ZERO_STRUCT(state);
+ state.conn = conn;
+ state.info_level = info_level;
+ if (mangled_names != MANGLED_NAMES_NO) {
+ state.check_mangled_names = true;
+ }
+ state.case_sensitive = dptr_case_sensitive(dirptr);
+
+ p = strrchr_m(path_mask,'/');
+ if(p != NULL) {
+ if(p[1] == '\0') {
+ mask = "*.*";
+ } else {
+ mask = p+1;
+ }
+ } else {
+ mask = path_mask;
+ }
+
+ ok = smbd_dirptr_get_entry(ctx,
+ dirptr,
+ mask,
+ dirtype,
+ dont_descend,
+ ask_sharemode,
+ get_dosmode,
+ smbd_dirptr_lanman2_match_fn,
+ &state,
+ &fname,
+ &smb_fname,
+ &mode);
+ if (!ok) {
+ return NT_STATUS_END_OF_FILE;
+ }
+
+ marshall_with_83_names = (mangled_names == MANGLED_NAMES_YES);
+
+ status = smbd_marshall_dir_entry(ctx,
+ conn,
+ flags2,
+ info_level,
+ name_list,
+ marshall_with_83_names,
+ requires_resume_key,
+ mode,
+ fname,
+ smb_fname,
+ space_remaining,
+ align,
+ do_pad,
+ base_data,
+ ppdata,
+ end_data,
+ &last_entry_off);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_ILLEGAL_CHARACTER)) {
+ DEBUG(1,("Conversion error: illegal character: %s\n",
+ smb_fname_str_dbg(smb_fname)));
+ }
+
+ if (file_id != NULL) {
+ *file_id = vfs_file_id_from_sbuf(conn, &smb_fname->st);
+ }
+
+ if (NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) {
+ smbd_dirptr_push_overflow(dirptr, &fname, &smb_fname, mode);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(fname);
+ return status;
+ }
+
+ smbd_dirptr_set_last_name_sent(dirptr, &smb_fname->base_name);
+
+ if (_smb_fname != NULL) {
+ /*
+ * smb_fname is already talloc'ed off ctx.
+ * We just need to make sure we don't return
+ * any stream_name, and replace base_name
+ * with fname in case base_name got mangled.
+ * This allows us to preserve any smb_fname->fsp
+ * for asynchronous handle lookups.
+ */
+ TALLOC_FREE(smb_fname->stream_name);
+
+ /*
+ * smbd_dirptr_set_last_name_sent() above consumed
+ * base_name
+ */
+ smb_fname->base_name = talloc_strdup(smb_fname, fname);
+
+ if (smb_fname->base_name == NULL) {
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(fname);
+ return NT_STATUS_NO_MEMORY;
+ }
+ *_smb_fname = smb_fname;
+ } else {
+ TALLOC_FREE(smb_fname);
+ }
+ TALLOC_FREE(fname);
+
+ *_last_entry_off = last_entry_off;
+ return NT_STATUS_OK;
+}
+
+unsigned char *create_volume_objectid(connection_struct *conn, unsigned char objid[16])
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ E_md4hash(lp_servicename(talloc_tos(), lp_sub, SNUM(conn)),objid);
+ return objid;
+}
+
+static void samba_extended_info_version(struct smb_extended_info *extended_info)
+{
+ SMB_ASSERT(extended_info != NULL);
+
+ extended_info->samba_magic = SAMBA_EXTENDED_INFO_MAGIC;
+ extended_info->samba_version = ((SAMBA_VERSION_MAJOR & 0xff) << 24)
+ | ((SAMBA_VERSION_MINOR & 0xff) << 16)
+ | ((SAMBA_VERSION_RELEASE & 0xff) << 8);
+#ifdef SAMBA_VERSION_REVISION
+ extended_info->samba_version |= (tolower(*SAMBA_VERSION_REVISION) - 'a' + 1) & 0xff;
+#endif
+ extended_info->samba_subversion = 0;
+#ifdef SAMBA_VERSION_RC_RELEASE
+ extended_info->samba_subversion |= (SAMBA_VERSION_RC_RELEASE & 0xff) << 24;
+#else
+#ifdef SAMBA_VERSION_PRE_RELEASE
+ extended_info->samba_subversion |= (SAMBA_VERSION_PRE_RELEASE & 0xff) << 16;
+#endif
+#endif
+#ifdef SAMBA_VERSION_VENDOR_PATCH
+ extended_info->samba_subversion |= (SAMBA_VERSION_VENDOR_PATCH & 0xffff);
+#endif
+ extended_info->samba_gitcommitdate = 0;
+#ifdef SAMBA_VERSION_COMMIT_TIME
+ unix_to_nt_time(&extended_info->samba_gitcommitdate, SAMBA_VERSION_COMMIT_TIME);
+#endif
+
+ memset(extended_info->samba_version_string, 0,
+ sizeof(extended_info->samba_version_string));
+
+ snprintf (extended_info->samba_version_string,
+ sizeof(extended_info->samba_version_string),
+ "%s", samba_version_string());
+}
+
+static bool fsinfo_unix_valid_level(connection_struct *conn,
+ struct files_struct *fsp,
+ uint16_t info_level)
+{
+ if (conn->sconn->using_smb2 &&
+ fsp->posix_flags == FSP_POSIX_FLAGS_OPEN &&
+ info_level == SMB2_FS_POSIX_INFORMATION_INTERNAL)
+ {
+ return true;
+ }
+#if defined(SMB1SERVER)
+ if (lp_smb1_unix_extensions() &&
+ info_level == SMB_QUERY_POSIX_FS_INFO) {
+ return true;
+ }
+#endif
+ return false;
+}
+
+/*
+ * fsp is only valid for SMB2.
+ */
+NTSTATUS smbd_do_qfsinfo(struct smbXsrv_connection *xconn,
+ connection_struct *conn,
+ TALLOC_CTX *mem_ctx,
+ uint16_t info_level,
+ uint16_t flags2,
+ unsigned int max_data_bytes,
+ size_t *fixed_portion,
+ struct files_struct *fsp,
+ struct smb_filename *fname,
+ char **ppdata,
+ int *ret_data_len)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *pdata, *end_data;
+ int data_len = 0;
+ size_t len = 0;
+ const char *vname = volume_label(talloc_tos(), SNUM(conn));
+ int snum = SNUM(conn);
+ const char *fstype = lp_fstype(SNUM(conn));
+ const char *filename = NULL;
+ const uint64_t bytes_per_sector = 512;
+ uint32_t additional_flags = 0;
+ struct smb_filename smb_fname;
+ SMB_STRUCT_STAT st;
+ NTSTATUS status = NT_STATUS_OK;
+ uint64_t df_ret;
+ uint32_t serial;
+
+ if (fname == NULL || fname->base_name == NULL) {
+ filename = ".";
+ } else {
+ filename = fname->base_name;
+ }
+
+ if (IS_IPC(conn)) {
+ if (info_level != SMB_QUERY_CIFS_UNIX_INFO) {
+ DEBUG(0,("smbd_do_qfsinfo: not an allowed "
+ "info level (0x%x) on IPC$.\n",
+ (unsigned int)info_level));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+
+ DEBUG(3,("smbd_do_qfsinfo: level = %d\n", info_level));
+
+ smb_fname = (struct smb_filename) {
+ .base_name = discard_const_p(char, filename),
+ .flags = fname ? fname->flags : 0,
+ .twrp = fname ? fname->twrp : 0,
+ };
+
+ if(info_level != SMB_FS_QUOTA_INFORMATION
+ && SMB_VFS_STAT(conn, &smb_fname) != 0) {
+ DEBUG(2,("stat of . failed (%s)\n", strerror(errno)));
+ return map_nt_error_from_unix(errno);
+ }
+
+ st = smb_fname.st;
+
+ if (max_data_bytes + DIR_ENTRY_SAFETY_MARGIN < max_data_bytes) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ *ppdata = (char *)SMB_REALLOC(
+ *ppdata, max_data_bytes + DIR_ENTRY_SAFETY_MARGIN);
+ if (*ppdata == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ pdata = *ppdata;
+ memset((char *)pdata,'\0',max_data_bytes + DIR_ENTRY_SAFETY_MARGIN);
+ end_data = pdata + max_data_bytes + DIR_ENTRY_SAFETY_MARGIN - 1;
+
+ *fixed_portion = 0;
+
+ switch (info_level) {
+ case SMB_INFO_ALLOCATION:
+ {
+ uint64_t dfree,dsize,bsize,block_size,sectors_per_unit;
+ data_len = 18;
+ df_ret = get_dfree_info(conn, &smb_fname, &bsize,
+ &dfree, &dsize);
+ if (df_ret == (uint64_t)-1) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ block_size = lp_block_size(snum);
+ if (bsize < block_size) {
+ uint64_t factor = block_size/bsize;
+ bsize = block_size;
+ dsize /= factor;
+ dfree /= factor;
+ }
+ if (bsize > block_size) {
+ uint64_t factor = bsize/block_size;
+ bsize = block_size;
+ dsize *= factor;
+ dfree *= factor;
+ }
+ sectors_per_unit = bsize/bytes_per_sector;
+
+ DEBUG(5,("smbd_do_qfsinfo : SMB_INFO_ALLOCATION id=%x, bsize=%u, cSectorUnit=%u, \
+cBytesSector=%u, cUnitTotal=%u, cUnitAvail=%d\n", (unsigned int)st.st_ex_dev, (unsigned int)bsize, (unsigned int)sectors_per_unit,
+ (unsigned int)bytes_per_sector, (unsigned int)dsize, (unsigned int)dfree));
+
+ /*
+ * For large drives, return max values and not modulo.
+ */
+ dsize = MIN(dsize, UINT32_MAX);
+ dfree = MIN(dfree, UINT32_MAX);
+
+ SIVAL(pdata,l1_idFileSystem,st.st_ex_dev);
+ SIVAL(pdata,l1_cSectorUnit,sectors_per_unit);
+ SIVAL(pdata,l1_cUnit,dsize);
+ SIVAL(pdata,l1_cUnitAvail,dfree);
+ SSVAL(pdata,l1_cbSector,bytes_per_sector);
+ break;
+ }
+
+ case SMB_INFO_VOLUME:
+ /* Return volume name */
+ /*
+ * Add volume serial number - hash of a combination of
+ * the called hostname and the service name.
+ */
+ serial = generate_volume_serial_number(lp_sub, snum);
+ SIVAL(pdata,0,serial);
+ /*
+ * Win2k3 and previous mess this up by sending a name length
+ * one byte short. I believe only older clients (OS/2 Win9x) use
+ * this call so try fixing this by adding a terminating null to
+ * the pushed string. The change here was adding the STR_TERMINATE. JRA.
+ */
+ status = srvstr_push(
+ pdata, flags2,
+ pdata+l2_vol_szVolLabel, vname,
+ PTR_DIFF(end_data, pdata+l2_vol_szVolLabel),
+ STR_NOALIGN|STR_TERMINATE, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ SCVAL(pdata,l2_vol_cch,len);
+ data_len = l2_vol_szVolLabel + len;
+ DEBUG(5,("smbd_do_qfsinfo : time = %x, namelen = %u, "
+ "name = %s serial = 0x%04"PRIx32"\n",
+ (unsigned)convert_timespec_to_time_t(st.st_ex_ctime),
+ (unsigned)len, vname, serial));
+ break;
+
+ case SMB_QUERY_FS_ATTRIBUTE_INFO:
+ case SMB_FS_ATTRIBUTE_INFORMATION:
+
+ additional_flags = 0;
+#if defined(HAVE_SYS_QUOTAS)
+ additional_flags |= FILE_VOLUME_QUOTAS;
+#endif
+
+ if(lp_nt_acl_support(SNUM(conn))) {
+ additional_flags |= FILE_PERSISTENT_ACLS;
+ }
+
+ /* Capabilities are filled in at connection time through STATVFS call */
+ additional_flags |= conn->fs_capabilities;
+ additional_flags |= lp_parm_int(conn->params->service,
+ "share", "fake_fscaps",
+ 0);
+
+ SIVAL(pdata,0,FILE_CASE_PRESERVED_NAMES|FILE_CASE_SENSITIVE_SEARCH|
+ FILE_SUPPORTS_OBJECT_IDS|FILE_UNICODE_ON_DISK|
+ additional_flags); /* FS ATTRIBUTES */
+
+ SIVAL(pdata,4,255); /* Max filename component length */
+ /* NOTE! the fstype must *not* be null terminated or win98 won't recognise it
+ and will think we can't do long filenames */
+ status = srvstr_push(pdata, flags2, pdata+12, fstype,
+ PTR_DIFF(end_data, pdata+12),
+ STR_UNICODE, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ SIVAL(pdata,8,len);
+ data_len = 12 + len;
+ if (max_data_bytes >= 16 && data_len > max_data_bytes) {
+ /* the client only requested a portion of the
+ file system name */
+ data_len = max_data_bytes;
+ status = STATUS_BUFFER_OVERFLOW;
+ }
+ *fixed_portion = 16;
+ break;
+
+ case SMB_QUERY_FS_LABEL_INFO:
+ case SMB_FS_LABEL_INFORMATION:
+ status = srvstr_push(pdata, flags2, pdata+4, vname,
+ PTR_DIFF(end_data, pdata+4), 0, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ data_len = 4 + len;
+ SIVAL(pdata,0,len);
+ break;
+
+ case SMB_QUERY_FS_VOLUME_INFO:
+ case SMB_FS_VOLUME_INFORMATION:
+ put_long_date_full_timespec(TIMESTAMP_SET_NT_OR_BETTER,
+ pdata, &st.st_ex_btime);
+ /*
+ * Add volume serial number - hash of a combination of
+ * the called hostname and the service name.
+ */
+ serial = generate_volume_serial_number(lp_sub, snum);
+ SIVAL(pdata,8,serial);
+
+ /* Max label len is 32 characters. */
+ status = srvstr_push(pdata, flags2, pdata+18, vname,
+ PTR_DIFF(end_data, pdata+18),
+ STR_UNICODE, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ SIVAL(pdata,12,len);
+ data_len = 18+len;
+
+ DEBUG(5,("smbd_do_qfsinfo : SMB_QUERY_FS_VOLUME_INFO "
+ "namelen = %d, vol=%s serv=%s "
+ "serial=0x%04"PRIx32"\n",
+ (int)strlen(vname),vname,
+ lp_servicename(talloc_tos(), lp_sub, snum),
+ serial));
+ if (max_data_bytes >= 24 && data_len > max_data_bytes) {
+ /* the client only requested a portion of the
+ volume label */
+ data_len = max_data_bytes;
+ status = STATUS_BUFFER_OVERFLOW;
+ }
+ *fixed_portion = 24;
+ break;
+
+ case SMB_QUERY_FS_SIZE_INFO:
+ case SMB_FS_SIZE_INFORMATION:
+ {
+ uint64_t dfree,dsize,bsize,block_size,sectors_per_unit;
+ data_len = 24;
+ df_ret = get_dfree_info(conn, &smb_fname, &bsize,
+ &dfree, &dsize);
+ if (df_ret == (uint64_t)-1) {
+ return map_nt_error_from_unix(errno);
+ }
+ block_size = lp_block_size(snum);
+ if (bsize < block_size) {
+ uint64_t factor = block_size/bsize;
+ bsize = block_size;
+ dsize /= factor;
+ dfree /= factor;
+ }
+ if (bsize > block_size) {
+ uint64_t factor = bsize/block_size;
+ bsize = block_size;
+ dsize *= factor;
+ dfree *= factor;
+ }
+ sectors_per_unit = bsize/bytes_per_sector;
+ DEBUG(5,("smbd_do_qfsinfo : SMB_QUERY_FS_SIZE_INFO bsize=%u, cSectorUnit=%u, \
+cBytesSector=%u, cUnitTotal=%u, cUnitAvail=%d\n", (unsigned int)bsize, (unsigned int)sectors_per_unit,
+ (unsigned int)bytes_per_sector, (unsigned int)dsize, (unsigned int)dfree));
+ SBIG_UINT(pdata,0,dsize);
+ SBIG_UINT(pdata,8,dfree);
+ SIVAL(pdata,16,sectors_per_unit);
+ SIVAL(pdata,20,bytes_per_sector);
+ *fixed_portion = 24;
+ break;
+ }
+
+ case SMB_FS_FULL_SIZE_INFORMATION:
+ {
+ uint64_t dfree,dsize,bsize,block_size,sectors_per_unit;
+ data_len = 32;
+ df_ret = get_dfree_info(conn, &smb_fname, &bsize,
+ &dfree, &dsize);
+ if (df_ret == (uint64_t)-1) {
+ return map_nt_error_from_unix(errno);
+ }
+ block_size = lp_block_size(snum);
+ if (bsize < block_size) {
+ uint64_t factor = block_size/bsize;
+ bsize = block_size;
+ dsize /= factor;
+ dfree /= factor;
+ }
+ if (bsize > block_size) {
+ uint64_t factor = bsize/block_size;
+ bsize = block_size;
+ dsize *= factor;
+ dfree *= factor;
+ }
+ sectors_per_unit = bsize/bytes_per_sector;
+ DEBUG(5,("smbd_do_qfsinfo : SMB_QUERY_FS_FULL_SIZE_INFO bsize=%u, cSectorUnit=%u, \
+cBytesSector=%u, cUnitTotal=%u, cUnitAvail=%d\n", (unsigned int)bsize, (unsigned int)sectors_per_unit,
+ (unsigned int)bytes_per_sector, (unsigned int)dsize, (unsigned int)dfree));
+ SBIG_UINT(pdata,0,dsize); /* Total Allocation units. */
+ SBIG_UINT(pdata,8,dfree); /* Caller available allocation units. */
+ SBIG_UINT(pdata,16,dfree); /* Actual available allocation units. */
+ SIVAL(pdata,24,sectors_per_unit); /* Sectors per allocation unit. */
+ SIVAL(pdata,28,bytes_per_sector); /* Bytes per sector. */
+ *fixed_portion = 32;
+ break;
+ }
+
+ case SMB_QUERY_FS_DEVICE_INFO:
+ case SMB_FS_DEVICE_INFORMATION:
+ {
+ uint32_t characteristics = FILE_DEVICE_IS_MOUNTED;
+
+ if (!CAN_WRITE(conn)) {
+ characteristics |= FILE_READ_ONLY_DEVICE;
+ }
+ data_len = 8;
+ SIVAL(pdata,0,FILE_DEVICE_DISK); /* dev type */
+ SIVAL(pdata,4,characteristics);
+ *fixed_portion = 8;
+ break;
+ }
+
+#ifdef HAVE_SYS_QUOTAS
+ case SMB_FS_QUOTA_INFORMATION:
+ /*
+ * what we have to send --metze:
+ *
+ * Unknown1: 24 NULL bytes
+ * Soft Quota Threshold: 8 bytes seems like uint64_t or so
+ * Hard Quota Limit: 8 bytes seems like uint64_t or so
+ * Quota Flags: 2 byte :
+ * Unknown3: 6 NULL bytes
+ *
+ * 48 bytes total
+ *
+ * details for Quota Flags:
+ *
+ * 0x0020 Log Limit: log if the user exceeds his Hard Quota
+ * 0x0010 Log Warn: log if the user exceeds his Soft Quota
+ * 0x0002 Deny Disk: deny disk access when the user exceeds his Hard Quota
+ * 0x0001 Enable Quotas: enable quota for this fs
+ *
+ */
+ {
+ /* we need to fake up a fsp here,
+ * because its not send in this call
+ */
+ files_struct tmpfsp;
+ SMB_NTQUOTA_STRUCT quotas;
+
+ ZERO_STRUCT(tmpfsp);
+ ZERO_STRUCT(quotas);
+
+ tmpfsp.conn = conn;
+ tmpfsp.fnum = FNUM_FIELD_INVALID;
+
+ /* access check */
+ if (get_current_uid(conn) != 0) {
+ DEBUG(0,("get_user_quota: access_denied "
+ "service [%s] user [%s]\n",
+ lp_servicename(talloc_tos(), lp_sub, SNUM(conn)),
+ conn->session_info->unix_info->unix_name));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ status = vfs_get_ntquota(&tmpfsp, SMB_USER_FS_QUOTA_TYPE,
+ NULL, &quotas);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("vfs_get_ntquota() failed for service [%s]\n",lp_servicename(talloc_tos(), lp_sub, SNUM(conn))));
+ return status;
+ }
+
+ data_len = 48;
+
+ DEBUG(10,("SMB_FS_QUOTA_INFORMATION: for service [%s]\n",
+ lp_servicename(talloc_tos(), lp_sub, SNUM(conn))));
+
+ /* Unknown1 24 NULL bytes*/
+ SBIG_UINT(pdata,0,(uint64_t)0);
+ SBIG_UINT(pdata,8,(uint64_t)0);
+ SBIG_UINT(pdata,16,(uint64_t)0);
+
+ /* Default Soft Quota 8 bytes */
+ SBIG_UINT(pdata,24,quotas.softlim);
+
+ /* Default Hard Quota 8 bytes */
+ SBIG_UINT(pdata,32,quotas.hardlim);
+
+ /* Quota flag 2 bytes */
+ SSVAL(pdata,40,quotas.qflags);
+
+ /* Unknown3 6 NULL bytes */
+ SSVAL(pdata,42,0);
+ SIVAL(pdata,44,0);
+
+ break;
+ }
+#endif /* HAVE_SYS_QUOTAS */
+ case SMB_FS_OBJECTID_INFORMATION:
+ {
+ unsigned char objid[16];
+ struct smb_extended_info extended_info;
+ memcpy(pdata,create_volume_objectid(conn, objid),16);
+ samba_extended_info_version (&extended_info);
+ SIVAL(pdata,16,extended_info.samba_magic);
+ SIVAL(pdata,20,extended_info.samba_version);
+ SIVAL(pdata,24,extended_info.samba_subversion);
+ SBIG_UINT(pdata,28,extended_info.samba_gitcommitdate);
+ memcpy(pdata+36,extended_info.samba_version_string,28);
+ data_len = 64;
+ break;
+ }
+
+ case SMB_FS_SECTOR_SIZE_INFORMATION:
+ {
+ data_len = 28;
+ /*
+ * These values match a physical Windows Server 2012
+ * share backed by NTFS atop spinning rust.
+ */
+ DEBUG(5, ("SMB_FS_SECTOR_SIZE_INFORMATION:"));
+ /* logical_bytes_per_sector */
+ SIVAL(pdata, 0, bytes_per_sector);
+ /* phys_bytes_per_sector_atomic */
+ SIVAL(pdata, 4, bytes_per_sector);
+ /* phys_bytes_per_sector_perf */
+ SIVAL(pdata, 8, bytes_per_sector);
+ /* fs_effective_phys_bytes_per_sector_atomic */
+ SIVAL(pdata, 12, bytes_per_sector);
+ /* flags */
+ SIVAL(pdata, 16, SSINFO_FLAGS_ALIGNED_DEVICE
+ | SSINFO_FLAGS_PARTITION_ALIGNED_ON_DEVICE);
+ /* byte_off_sector_align */
+ SIVAL(pdata, 20, 0);
+ /* byte_off_partition_align */
+ SIVAL(pdata, 24, 0);
+ *fixed_portion = 28;
+ break;
+ }
+
+
+#if defined(WITH_SMB1SERVER)
+ /*
+ * Query the version and capabilities of the CIFS UNIX extensions
+ * in use.
+ */
+
+ case SMB_QUERY_CIFS_UNIX_INFO:
+ {
+ bool large_write = lp_min_receive_file_size() &&
+ !smb1_srv_is_signing_active(xconn);
+ bool large_read = !smb1_srv_is_signing_active(xconn);
+ int encrypt_caps = 0;
+
+ if (!lp_smb1_unix_extensions()) {
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ switch (conn->encrypt_level) {
+ case SMB_SIGNING_OFF:
+ encrypt_caps = 0;
+ break;
+ case SMB_SIGNING_DESIRED:
+ case SMB_SIGNING_IF_REQUIRED:
+ case SMB_SIGNING_DEFAULT:
+ encrypt_caps = CIFS_UNIX_TRANSPORT_ENCRYPTION_CAP;
+ break;
+ case SMB_SIGNING_REQUIRED:
+ encrypt_caps = CIFS_UNIX_TRANSPORT_ENCRYPTION_CAP|
+ CIFS_UNIX_TRANSPORT_ENCRYPTION_MANDATORY_CAP;
+ large_write = false;
+ large_read = false;
+ break;
+ }
+
+ data_len = 12;
+ SSVAL(pdata,0,CIFS_UNIX_MAJOR_VERSION);
+ SSVAL(pdata,2,CIFS_UNIX_MINOR_VERSION);
+
+ /* We have POSIX ACLs, pathname, encryption,
+ * large read/write, and locking capability. */
+
+ SBIG_UINT(pdata,4,((uint64_t)(
+ CIFS_UNIX_POSIX_ACLS_CAP|
+ CIFS_UNIX_POSIX_PATHNAMES_CAP|
+ CIFS_UNIX_FCNTL_LOCKS_CAP|
+ CIFS_UNIX_EXTATTR_CAP|
+ CIFS_UNIX_POSIX_PATH_OPERATIONS_CAP|
+ encrypt_caps|
+ (large_read ? CIFS_UNIX_LARGE_READ_CAP : 0) |
+ (large_write ?
+ CIFS_UNIX_LARGE_WRITE_CAP : 0))));
+ break;
+ }
+#endif
+
+ case SMB_QUERY_POSIX_FS_INFO:
+ case SMB2_FS_POSIX_INFORMATION_INTERNAL:
+ {
+ int rc;
+ struct vfs_statvfs_struct svfs;
+
+ if (!fsinfo_unix_valid_level(conn, fsp, info_level)) {
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ rc = SMB_VFS_STATVFS(conn, &smb_fname, &svfs);
+
+ if (!rc) {
+ data_len = 56;
+ SIVAL(pdata,0,svfs.OptimalTransferSize);
+ SIVAL(pdata,4,svfs.BlockSize);
+ SBIG_UINT(pdata,8,svfs.TotalBlocks);
+ SBIG_UINT(pdata,16,svfs.BlocksAvail);
+ SBIG_UINT(pdata,24,svfs.UserBlocksAvail);
+ SBIG_UINT(pdata,32,svfs.TotalFileNodes);
+ SBIG_UINT(pdata,40,svfs.FreeFileNodes);
+ SBIG_UINT(pdata,48,svfs.FsIdentifier);
+ DEBUG(5,("smbd_do_qfsinfo : SMB_QUERY_POSIX_FS_INFO successful\n"));
+#ifdef EOPNOTSUPP
+ } else if (rc == EOPNOTSUPP) {
+ return NT_STATUS_INVALID_LEVEL;
+#endif /* EOPNOTSUPP */
+ } else {
+ DEBUG(0,("vfs_statvfs() failed for service [%s]\n",lp_servicename(talloc_tos(), lp_sub, SNUM(conn))));
+ return NT_STATUS_DOS(ERRSRV, ERRerror);
+ }
+ break;
+ }
+
+ case SMB_QUERY_POSIX_WHOAMI:
+ {
+ uint32_t flags = 0;
+ uint32_t sid_bytes;
+ uint32_t i;
+
+ if (!lp_smb1_unix_extensions()) {
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ if (max_data_bytes < 40) {
+ return NT_STATUS_BUFFER_TOO_SMALL;
+ }
+
+ if (security_session_user_level(conn->session_info, NULL) < SECURITY_USER) {
+ flags |= SMB_WHOAMI_GUEST;
+ }
+
+ /* NOTE: 8 bytes for UID/GID, irrespective of native
+ * platform size. This matches
+ * SMB_QUERY_FILE_UNIX_BASIC and friends.
+ */
+ data_len = 4 /* flags */
+ + 4 /* flag mask */
+ + 8 /* uid */
+ + 8 /* gid */
+ + 4 /* ngroups */
+ + 4 /* num_sids */
+ + 4 /* SID bytes */
+ + 4 /* pad/reserved */
+ + (conn->session_info->unix_token->ngroups * 8)
+ /* groups list */
+ + (conn->session_info->security_token->num_sids *
+ SID_MAX_SIZE)
+ /* SID list */;
+
+ SIVAL(pdata, 0, flags);
+ SIVAL(pdata, 4, SMB_WHOAMI_MASK);
+ SBIG_UINT(pdata, 8,
+ (uint64_t)conn->session_info->unix_token->uid);
+ SBIG_UINT(pdata, 16,
+ (uint64_t)conn->session_info->unix_token->gid);
+
+
+ if (data_len >= max_data_bytes) {
+ /* Potential overflow, skip the GIDs and SIDs. */
+
+ SIVAL(pdata, 24, 0); /* num_groups */
+ SIVAL(pdata, 28, 0); /* num_sids */
+ SIVAL(pdata, 32, 0); /* num_sid_bytes */
+ SIVAL(pdata, 36, 0); /* reserved */
+
+ data_len = 40;
+ break;
+ }
+
+ SIVAL(pdata, 24, conn->session_info->unix_token->ngroups);
+ SIVAL(pdata, 28, conn->session_info->security_token->num_sids);
+
+ /* We walk the SID list twice, but this call is fairly
+ * infrequent, and I don't expect that it's performance
+ * sensitive -- jpeach
+ */
+ for (i = 0, sid_bytes = 0;
+ i < conn->session_info->security_token->num_sids; ++i) {
+ sid_bytes += ndr_size_dom_sid(
+ &conn->session_info->security_token->sids[i],
+ 0);
+ }
+
+ /* SID list byte count */
+ SIVAL(pdata, 32, sid_bytes);
+
+ /* 4 bytes pad/reserved - must be zero */
+ SIVAL(pdata, 36, 0);
+ data_len = 40;
+
+ /* GID list */
+ for (i = 0; i < conn->session_info->unix_token->ngroups; ++i) {
+ SBIG_UINT(pdata, data_len,
+ (uint64_t)conn->session_info->unix_token->groups[i]);
+ data_len += 8;
+ }
+
+ /* SID list */
+ for (i = 0;
+ i < conn->session_info->security_token->num_sids; ++i) {
+ int sid_len = ndr_size_dom_sid(
+ &conn->session_info->security_token->sids[i],
+ 0);
+
+ sid_linearize((uint8_t *)(pdata + data_len),
+ sid_len,
+ &conn->session_info->security_token->sids[i]);
+ data_len += sid_len;
+ }
+
+ break;
+ }
+
+ case SMB_MAC_QUERY_FS_INFO:
+ /*
+ * Thursby MAC extension... ONLY on NTFS filesystems
+ * once we do streams then we don't need this
+ */
+ if (strequal(lp_fstype(SNUM(conn)),"NTFS")) {
+ data_len = 88;
+ SIVAL(pdata,84,0x100); /* Don't support mac... */
+ break;
+ }
+
+ FALL_THROUGH;
+ default:
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ *ret_data_len = data_len;
+ return status;
+}
+
+NTSTATUS smb_set_fsquota(connection_struct *conn,
+ struct smb_request *req,
+ files_struct *fsp,
+ const DATA_BLOB *qdata)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ NTSTATUS status;
+ SMB_NTQUOTA_STRUCT quotas;
+
+ ZERO_STRUCT(quotas);
+
+ /* access check */
+ if ((get_current_uid(conn) != 0) || !CAN_WRITE(conn)) {
+ DBG_NOTICE("access_denied service [%s] user [%s]\n",
+ lp_servicename(talloc_tos(), lp_sub, SNUM(conn)),
+ conn->session_info->unix_info->unix_name);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (!check_fsp_ntquota_handle(conn, req,
+ fsp)) {
+ DBG_WARNING("no valid QUOTA HANDLE\n");
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ /* note: normally there're 48 bytes,
+ * but we didn't use the last 6 bytes for now
+ * --metze
+ */
+ if (qdata->length < 42) {
+ DBG_ERR("requires total_data(%zu) >= 42 bytes!\n",
+ qdata->length);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* unknown_1 24 NULL bytes in pdata*/
+
+ /* the soft quotas 8 bytes (uint64_t)*/
+ quotas.softlim = BVAL(qdata->data,24);
+
+ /* the hard quotas 8 bytes (uint64_t)*/
+ quotas.hardlim = BVAL(qdata->data,32);
+
+ /* quota_flags 2 bytes **/
+ quotas.qflags = SVAL(qdata->data,40);
+
+ /* unknown_2 6 NULL bytes follow*/
+
+ /* now set the quotas */
+ if (vfs_set_ntquota(fsp, SMB_USER_FS_QUOTA_TYPE, NULL, &quotas)!=0) {
+ DBG_WARNING("vfs_set_ntquota() failed for service [%s]\n",
+ lp_servicename(talloc_tos(), lp_sub, SNUM(conn)));
+ status = map_nt_error_from_unix(errno);
+ } else {
+ status = NT_STATUS_OK;
+ }
+ return status;
+}
+
+NTSTATUS smbd_do_setfsinfo(connection_struct *conn,
+ struct smb_request *req,
+ TALLOC_CTX *mem_ctx,
+ uint16_t info_level,
+ files_struct *fsp,
+ const DATA_BLOB *pdata)
+{
+ switch (info_level) {
+ case SMB_FS_QUOTA_INFORMATION:
+ {
+ return smb_set_fsquota(conn,
+ req,
+ fsp,
+ pdata);
+ }
+
+ default:
+ break;
+ }
+ return NT_STATUS_INVALID_LEVEL;
+}
+
+/****************************************************************************
+ Store the FILE_UNIX_BASIC info.
+****************************************************************************/
+
+char *store_file_unix_basic(connection_struct *conn,
+ char *pdata,
+ files_struct *fsp,
+ const SMB_STRUCT_STAT *psbuf)
+{
+ dev_t devno;
+
+ DBG_DEBUG("SMB_QUERY_FILE_UNIX_BASIC\n");
+ DBG_NOTICE("st_mode=%o\n", (int)psbuf->st_ex_mode);
+
+ SOFF_T(pdata,0,get_file_size_stat(psbuf)); /* File size 64 Bit */
+ pdata += 8;
+
+ SOFF_T(pdata,0,SMB_VFS_GET_ALLOC_SIZE(conn,fsp,psbuf)); /* Number of bytes used on disk - 64 Bit */
+ pdata += 8;
+
+ put_long_date_full_timespec(TIMESTAMP_SET_NT_OR_BETTER, pdata, &psbuf->st_ex_ctime); /* Change Time 64 Bit */
+ put_long_date_full_timespec(TIMESTAMP_SET_NT_OR_BETTER ,pdata+8, &psbuf->st_ex_atime); /* Last access time 64 Bit */
+ put_long_date_full_timespec(TIMESTAMP_SET_NT_OR_BETTER, pdata+16, &psbuf->st_ex_mtime); /* Last modification time 64 Bit */
+ pdata += 24;
+
+ SIVAL(pdata,0,psbuf->st_ex_uid); /* user id for the owner */
+ SIVAL(pdata,4,0);
+ pdata += 8;
+
+ SIVAL(pdata,0,psbuf->st_ex_gid); /* group id of owner */
+ SIVAL(pdata,4,0);
+ pdata += 8;
+
+ SIVAL(pdata,0,unix_filetype(psbuf->st_ex_mode));
+ pdata += 4;
+
+ if (S_ISBLK(psbuf->st_ex_mode) || S_ISCHR(psbuf->st_ex_mode)) {
+ devno = psbuf->st_ex_rdev;
+ } else {
+ devno = psbuf->st_ex_dev;
+ }
+
+ SIVAL(pdata,0,unix_dev_major(devno)); /* Major device number if type is device */
+ SIVAL(pdata,4,0);
+ pdata += 8;
+
+ SIVAL(pdata,0,unix_dev_minor(devno)); /* Minor device number if type is device */
+ SIVAL(pdata,4,0);
+ pdata += 8;
+
+ SINO_T_VAL(pdata, 0, psbuf->st_ex_ino); /* inode number */
+ pdata += 8;
+
+ SIVAL(pdata,0, unix_perms_to_wire(psbuf->st_ex_mode)); /* Standard UNIX file permissions */
+ SIVAL(pdata,4,0);
+ pdata += 8;
+
+ SIVAL(pdata,0,psbuf->st_ex_nlink); /* number of hard links */
+ SIVAL(pdata,4,0);
+ pdata += 8;
+
+ return pdata;
+}
+
+/* Forward and reverse mappings from the UNIX_INFO2 file flags field and
+ * the chflags(2) (or equivalent) flags.
+ *
+ * XXX: this really should be behind the VFS interface. To do this, we would
+ * need to alter SMB_STRUCT_STAT so that it included a flags and a mask field.
+ * Each VFS module could then implement its own mapping as appropriate for the
+ * platform. We would then pass the SMB flags into SMB_VFS_CHFLAGS.
+ */
+static const struct {unsigned stat_fflag; unsigned smb_fflag;}
+ info2_flags_map[] =
+{
+#ifdef UF_NODUMP
+ { UF_NODUMP, EXT_DO_NOT_BACKUP },
+#endif
+
+#ifdef UF_IMMUTABLE
+ { UF_IMMUTABLE, EXT_IMMUTABLE },
+#endif
+
+#ifdef UF_APPEND
+ { UF_APPEND, EXT_OPEN_APPEND_ONLY },
+#endif
+
+#ifdef UF_HIDDEN
+ { UF_HIDDEN, EXT_HIDDEN },
+#endif
+
+ /* Do not remove. We need to guarantee that this array has at least one
+ * entry to build on HP-UX.
+ */
+ { 0, 0 }
+
+};
+
+static void map_info2_flags_from_sbuf(const SMB_STRUCT_STAT *psbuf,
+ uint32_t *smb_fflags, uint32_t *smb_fmask)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(info2_flags_map); ++i) {
+ *smb_fmask |= info2_flags_map[i].smb_fflag;
+ if (psbuf->st_ex_flags & info2_flags_map[i].stat_fflag) {
+ *smb_fflags |= info2_flags_map[i].smb_fflag;
+ }
+ }
+}
+
+bool map_info2_flags_to_sbuf(const SMB_STRUCT_STAT *psbuf,
+ const uint32_t smb_fflags,
+ const uint32_t smb_fmask,
+ int *stat_fflags)
+{
+ uint32_t max_fmask = 0;
+ size_t i;
+
+ *stat_fflags = psbuf->st_ex_flags;
+
+ /* For each flags requested in smb_fmask, check the state of the
+ * corresponding flag in smb_fflags and set or clear the matching
+ * stat flag.
+ */
+
+ for (i = 0; i < ARRAY_SIZE(info2_flags_map); ++i) {
+ max_fmask |= info2_flags_map[i].smb_fflag;
+ if (smb_fmask & info2_flags_map[i].smb_fflag) {
+ if (smb_fflags & info2_flags_map[i].smb_fflag) {
+ *stat_fflags |= info2_flags_map[i].stat_fflag;
+ } else {
+ *stat_fflags &= ~info2_flags_map[i].stat_fflag;
+ }
+ }
+ }
+
+ /* If smb_fmask is asking to set any bits that are not supported by
+ * our flag mappings, we should fail.
+ */
+ if ((smb_fmask & max_fmask) != smb_fmask) {
+ return False;
+ }
+
+ return True;
+}
+
+
+/* Just like SMB_QUERY_FILE_UNIX_BASIC, but with the addition
+ * of file flags and birth (create) time.
+ */
+char *store_file_unix_basic_info2(connection_struct *conn,
+ char *pdata,
+ files_struct *fsp,
+ const SMB_STRUCT_STAT *psbuf)
+{
+ uint32_t file_flags = 0;
+ uint32_t flags_mask = 0;
+
+ pdata = store_file_unix_basic(conn, pdata, fsp, psbuf);
+
+ /* Create (birth) time 64 bit */
+ put_long_date_full_timespec(TIMESTAMP_SET_NT_OR_BETTER,pdata, &psbuf->st_ex_btime);
+ pdata += 8;
+
+ map_info2_flags_from_sbuf(psbuf, &file_flags, &flags_mask);
+ SIVAL(pdata, 0, file_flags); /* flags */
+ SIVAL(pdata, 4, flags_mask); /* mask */
+ pdata += 8;
+
+ return pdata;
+}
+
+static NTSTATUS marshall_stream_info(unsigned int num_streams,
+ const struct stream_struct *streams,
+ char *data,
+ unsigned int max_data_bytes,
+ unsigned int *data_size)
+{
+ unsigned int i;
+ unsigned int ofs = 0;
+
+ if (max_data_bytes < 32) {
+ return NT_STATUS_INFO_LENGTH_MISMATCH;
+ }
+
+ for (i = 0; i < num_streams; i++) {
+ unsigned int next_offset;
+ size_t namelen;
+ smb_ucs2_t *namebuf;
+
+ if (!push_ucs2_talloc(talloc_tos(), &namebuf,
+ streams[i].name, &namelen) ||
+ namelen <= 2)
+ {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * name_buf is now null-terminated, we need to marshall as not
+ * terminated
+ */
+
+ namelen -= 2;
+
+ /*
+ * We cannot overflow ...
+ */
+ if ((ofs + 24 + namelen) > max_data_bytes) {
+ DEBUG(10, ("refusing to overflow reply at stream %u\n",
+ i));
+ TALLOC_FREE(namebuf);
+ return STATUS_BUFFER_OVERFLOW;
+ }
+
+ SIVAL(data, ofs+4, namelen);
+ SOFF_T(data, ofs+8, streams[i].size);
+ SOFF_T(data, ofs+16, streams[i].alloc_size);
+ memcpy(data+ofs+24, namebuf, namelen);
+ TALLOC_FREE(namebuf);
+
+ next_offset = ofs + 24 + namelen;
+
+ if (i == num_streams-1) {
+ SIVAL(data, ofs, 0);
+ }
+ else {
+ unsigned int align = ndr_align_size(next_offset, 8);
+
+ if ((next_offset + align) > max_data_bytes) {
+ DEBUG(10, ("refusing to overflow align "
+ "reply at stream %u\n",
+ i));
+ TALLOC_FREE(namebuf);
+ return STATUS_BUFFER_OVERFLOW;
+ }
+
+ memset(data+next_offset, 0, align);
+ next_offset += align;
+
+ SIVAL(data, ofs, next_offset - ofs);
+ ofs = next_offset;
+ }
+
+ ofs = next_offset;
+ }
+
+ DEBUG(10, ("max_data: %u, data_size: %u\n", max_data_bytes, ofs));
+
+ *data_size = ofs;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smbd_do_qfilepathinfo(connection_struct *conn,
+ TALLOC_CTX *mem_ctx,
+ struct smb_request *req,
+ uint16_t info_level,
+ files_struct *fsp,
+ struct smb_filename *smb_fname,
+ bool delete_pending,
+ struct timespec write_time_ts,
+ struct ea_list *ea_list,
+ uint16_t flags2,
+ unsigned int max_data_bytes,
+ size_t *fixed_portion,
+ char **ppdata,
+ unsigned int *pdata_size)
+{
+ char *pdata = *ppdata;
+ char *dstart, *dend;
+ unsigned int data_size;
+ struct timespec create_time_ts, mtime_ts, atime_ts, ctime_ts;
+ SMB_STRUCT_STAT *psbuf = NULL;
+ SMB_STRUCT_STAT *base_sp = NULL;
+ char *p;
+ char *base_name;
+ char *dos_fname;
+ int mode;
+ int nlink;
+ NTSTATUS status;
+ uint64_t file_size = 0;
+ uint64_t pos = 0;
+ uint64_t allocation_size = 0;
+ uint64_t file_id = 0;
+ uint32_t access_mask = 0;
+ size_t len = 0;
+
+ if (INFO_LEVEL_IS_UNIX(info_level)) {
+ bool ok = false;
+
+ if (lp_smb1_unix_extensions() && req->posix_pathnames) {
+ DBG_DEBUG("SMB1 unix extensions activated\n");
+ ok = true;
+ }
+
+ if (conn->sconn->using_smb2 &&
+ (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN))
+ {
+ DBG_DEBUG("SMB2 posix open\n");
+ ok = true;
+ }
+
+ if (!ok) {
+ return NT_STATUS_INVALID_LEVEL;
+ }
+ }
+
+ DBG_INFO("%s (%s) level=%d max_data=%u\n",
+ smb_fname_str_dbg(smb_fname),
+ fsp_fnum_dbg(fsp),
+ info_level, max_data_bytes);
+
+ /*
+ * In case of querying a symlink in POSIX context,
+ * fsp will be NULL. fdos_mode() deals with it.
+ */
+ if (fsp != NULL) {
+ smb_fname = fsp->fsp_name;
+ }
+ mode = fdos_mode(fsp);
+ psbuf = &smb_fname->st;
+
+ if (fsp != NULL) {
+ base_sp = fsp->base_fsp ?
+ &fsp->base_fsp->fsp_name->st :
+ &fsp->fsp_name->st;
+ } else {
+ base_sp = &smb_fname->st;
+ }
+
+ nlink = psbuf->st_ex_nlink;
+
+ if (nlink && (mode&FILE_ATTRIBUTE_DIRECTORY)) {
+ nlink = 1;
+ }
+
+ if ((nlink > 0) && delete_pending) {
+ nlink -= 1;
+ }
+
+ if (max_data_bytes + DIR_ENTRY_SAFETY_MARGIN < max_data_bytes) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ data_size = max_data_bytes + DIR_ENTRY_SAFETY_MARGIN;
+ *ppdata = (char *)SMB_REALLOC(*ppdata, data_size);
+ if (*ppdata == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ pdata = *ppdata;
+ dstart = pdata;
+ dend = dstart + data_size - 1;
+
+ if (!is_omit_timespec(&write_time_ts) &&
+ !INFO_LEVEL_IS_UNIX(info_level))
+ {
+ update_stat_ex_mtime(psbuf, write_time_ts);
+ }
+
+ create_time_ts = get_create_timespec(conn, fsp, smb_fname);
+ mtime_ts = psbuf->st_ex_mtime;
+ atime_ts = psbuf->st_ex_atime;
+ ctime_ts = get_change_timespec(conn, fsp, smb_fname);
+
+ if (lp_dos_filetime_resolution(SNUM(conn))) {
+ dos_filetime_timespec(&create_time_ts);
+ dos_filetime_timespec(&mtime_ts);
+ dos_filetime_timespec(&atime_ts);
+ dos_filetime_timespec(&ctime_ts);
+ }
+
+ p = strrchr_m(smb_fname->base_name,'/');
+ if (p == NULL) {
+ base_name = smb_fname->base_name;
+ } else {
+ base_name = p+1;
+ }
+
+ /* NT expects the name to be in an exact form of the *full*
+ filename. See the trans2 torture test */
+ if (ISDOT(base_name)) {
+ dos_fname = talloc_strdup(mem_ctx, "\\");
+ if (!dos_fname) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ dos_fname = talloc_asprintf(mem_ctx,
+ "\\%s",
+ smb_fname->base_name);
+ if (!dos_fname) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ if (is_named_stream(smb_fname)) {
+ dos_fname = talloc_asprintf(dos_fname, "%s",
+ smb_fname->stream_name);
+ if (!dos_fname) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ string_replace(dos_fname, '/', '\\');
+ }
+
+ allocation_size = SMB_VFS_GET_ALLOC_SIZE(conn, fsp, psbuf);
+
+ if (fsp == NULL || !fsp->fsp_flags.is_fsa) {
+ /* Do we have this path open ? */
+ struct file_id fileid = vfs_file_id_from_sbuf(conn, psbuf);
+ files_struct *fsp1 = file_find_di_first(
+ conn->sconn, fileid, true);
+ if (fsp1 && fsp1->initial_allocation_size) {
+ allocation_size = SMB_VFS_GET_ALLOC_SIZE(conn, fsp1, psbuf);
+ }
+ }
+
+ if (!(mode & FILE_ATTRIBUTE_DIRECTORY)) {
+ file_size = get_file_size_stat(psbuf);
+ }
+
+ if (fsp) {
+ pos = fh_get_position_information(fsp->fh);
+ }
+
+ if (fsp) {
+ access_mask = fsp->access_mask;
+ } else {
+ /* GENERIC_EXECUTE mapping from Windows */
+ access_mask = 0x12019F;
+ }
+
+ /* This should be an index number - looks like
+ dev/ino to me :-)
+
+ I think this causes us to fail the IFSKIT
+ BasicFileInformationTest. -tpot */
+ file_id = SMB_VFS_FS_FILE_ID(conn, base_sp);
+
+ *fixed_portion = 0;
+
+ switch (info_level) {
+ case SMB_INFO_STANDARD:
+ DBG_DEBUG("SMB_INFO_STANDARD\n");
+ data_size = 22;
+ srv_put_dos_date2_ts(pdata,
+ l1_fdateCreation,
+ create_time_ts);
+ srv_put_dos_date2_ts(pdata,
+ l1_fdateLastAccess,
+ atime_ts);
+ srv_put_dos_date2_ts(pdata,
+ l1_fdateLastWrite,
+ mtime_ts); /* write time */
+ SIVAL(pdata,l1_cbFile,(uint32_t)file_size);
+ SIVAL(pdata,l1_cbFileAlloc,(uint32_t)allocation_size);
+ SSVAL(pdata,l1_attrFile,mode);
+ break;
+
+ case SMB_INFO_QUERY_EA_SIZE:
+ {
+ unsigned int ea_size =
+ estimate_ea_size(smb_fname->fsp);
+ DBG_DEBUG("SMB_INFO_QUERY_EA_SIZE\n");
+ data_size = 26;
+ srv_put_dos_date2_ts(pdata, 0, create_time_ts);
+ srv_put_dos_date2_ts(pdata, 4, atime_ts);
+ srv_put_dos_date2_ts(pdata,
+ 8,
+ mtime_ts); /* write time */
+ SIVAL(pdata,12,(uint32_t)file_size);
+ SIVAL(pdata,16,(uint32_t)allocation_size);
+ SSVAL(pdata,20,mode);
+ SIVAL(pdata,22,ea_size);
+ break;
+ }
+
+ case SMB_INFO_IS_NAME_VALID:
+ DBG_DEBUG("SMB_INFO_IS_NAME_VALID\n");
+ if (fsp) {
+ /* os/2 needs this ? really ?*/
+ return NT_STATUS_DOS(ERRDOS, ERRbadfunc);
+ }
+ /* This is only reached for qpathinfo */
+ data_size = 0;
+ break;
+
+ case SMB_INFO_QUERY_EAS_FROM_LIST:
+ {
+ size_t total_ea_len = 0;
+ struct ea_list *ea_file_list = NULL;
+ DBG_DEBUG("SMB_INFO_QUERY_EAS_FROM_LIST\n");
+
+ status =
+ get_ea_list_from_fsp(mem_ctx,
+ smb_fname->fsp,
+ &total_ea_len, &ea_file_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ ea_list = ea_list_union(ea_list, ea_file_list, &total_ea_len);
+
+ if (!ea_list || (total_ea_len > data_size)) {
+ data_size = 4;
+ SIVAL(pdata,0,4); /* EA List Length must be set to 4 if no EA's. */
+ break;
+ }
+
+ data_size = fill_ea_buffer(mem_ctx, pdata, data_size, conn, ea_list);
+ break;
+ }
+
+ case SMB_INFO_QUERY_ALL_EAS:
+ {
+ /* We have data_size bytes to put EA's into. */
+ size_t total_ea_len = 0;
+ DBG_DEBUG(" SMB_INFO_QUERY_ALL_EAS\n");
+
+ status = get_ea_list_from_fsp(mem_ctx,
+ smb_fname->fsp,
+ &total_ea_len, &ea_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!ea_list || (total_ea_len > data_size)) {
+ data_size = 4;
+ SIVAL(pdata,0,4); /* EA List Length must be set to 4 if no EA's. */
+ break;
+ }
+
+ data_size = fill_ea_buffer(mem_ctx, pdata, data_size, conn, ea_list);
+ break;
+ }
+
+ case SMB2_FILE_FULL_EA_INFORMATION:
+ {
+ /* We have data_size bytes to put EA's into. */
+ size_t total_ea_len = 0;
+ struct ea_list *ea_file_list = NULL;
+
+ DBG_DEBUG("SMB2_INFO_QUERY_ALL_EAS\n");
+
+ /*TODO: add filtering and index handling */
+
+ status =
+ get_ea_list_from_fsp(mem_ctx,
+ smb_fname->fsp,
+ &total_ea_len, &ea_file_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!ea_file_list) {
+ return NT_STATUS_NO_EAS_ON_FILE;
+ }
+
+ status = fill_ea_chained_buffer(mem_ctx,
+ pdata,
+ data_size,
+ &data_size,
+ conn, ea_file_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ break;
+ }
+
+ case SMB_FILE_BASIC_INFORMATION:
+ case SMB_QUERY_FILE_BASIC_INFO:
+
+ if (info_level == SMB_QUERY_FILE_BASIC_INFO) {
+ DBG_DEBUG("SMB_QUERY_FILE_BASIC_INFO\n");
+ data_size = 36; /* w95 returns 40 bytes not 36 - why ?. */
+ } else {
+ DBG_DEBUG("SMB_FILE_BASIC_INFORMATION\n");
+ data_size = 40;
+ SIVAL(pdata,36,0);
+ }
+ put_long_date_full_timespec(conn->ts_res,pdata,&create_time_ts);
+ put_long_date_full_timespec(conn->ts_res,pdata+8,&atime_ts);
+ put_long_date_full_timespec(conn->ts_res,pdata+16,&mtime_ts); /* write time */
+ put_long_date_full_timespec(conn->ts_res,pdata+24,&ctime_ts); /* change time */
+ SIVAL(pdata,32,mode);
+
+ DBG_INFO("SMB_QFBI - create: %s access: %s "
+ "write: %s change: %s mode: %x\n",
+ ctime(&create_time_ts.tv_sec),
+ ctime(&atime_ts.tv_sec),
+ ctime(&mtime_ts.tv_sec),
+ ctime(&ctime_ts.tv_sec),
+ mode);
+ *fixed_portion = data_size;
+ break;
+
+ case SMB_FILE_STANDARD_INFORMATION:
+ case SMB_QUERY_FILE_STANDARD_INFO:
+
+ DBG_DEBUG("SMB_FILE_STANDARD_INFORMATION\n");
+ data_size = 24;
+ SOFF_T(pdata,0,allocation_size);
+ SOFF_T(pdata,8,file_size);
+ SIVAL(pdata,16,nlink);
+ SCVAL(pdata,20,delete_pending?1:0);
+ SCVAL(pdata,21,(mode&FILE_ATTRIBUTE_DIRECTORY)?1:0);
+ SSVAL(pdata,22,0); /* Padding. */
+ *fixed_portion = 24;
+ break;
+
+ case SMB_FILE_EA_INFORMATION:
+ case SMB_QUERY_FILE_EA_INFO:
+ {
+ unsigned int ea_size =
+ estimate_ea_size(smb_fname->fsp);
+ DBG_DEBUG("SMB_FILE_EA_INFORMATION\n");
+ data_size = 4;
+ *fixed_portion = 4;
+ SIVAL(pdata,0,ea_size);
+ break;
+ }
+
+ /* Get the 8.3 name - used if NT SMB was negotiated. */
+ case SMB_QUERY_FILE_ALT_NAME_INFO:
+ case SMB_FILE_ALTERNATE_NAME_INFORMATION:
+ {
+ char mangled_name[13];
+ DBG_DEBUG("SMB_FILE_ALTERNATE_NAME_INFORMATION\n");
+ if (!name_to_8_3(base_name,mangled_name,
+ True,conn->params)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ status = srvstr_push(dstart, flags2,
+ pdata+4, mangled_name,
+ PTR_DIFF(dend, pdata+4),
+ STR_UNICODE, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ data_size = 4 + len;
+ SIVAL(pdata,0,len);
+ *fixed_portion = 8;
+ break;
+ }
+
+ case SMB_QUERY_FILE_NAME_INFO:
+ {
+ /*
+ this must be *exactly* right for ACLs on mapped drives to work
+ */
+ status = srvstr_push(dstart, flags2,
+ pdata+4, dos_fname,
+ PTR_DIFF(dend, pdata+4),
+ STR_UNICODE, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ DBG_DEBUG("SMB_QUERY_FILE_NAME_INFO\n");
+ data_size = 4 + len;
+ SIVAL(pdata,0,len);
+ break;
+ }
+
+ case SMB_FILE_NORMALIZED_NAME_INFORMATION:
+ {
+ char *nfname = NULL;
+
+ if (fsp == NULL || !fsp->conn->sconn->using_smb2) {
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ nfname = talloc_strdup(mem_ctx, smb_fname->base_name);
+ if (nfname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (ISDOT(nfname)) {
+ nfname[0] = '\0';
+ }
+ string_replace(nfname, '/', '\\');
+
+ if (fsp_is_alternate_stream(fsp)) {
+ const char *s = smb_fname->stream_name;
+ const char *e = NULL;
+ size_t n;
+
+ SMB_ASSERT(s[0] != '\0');
+
+ /*
+ * smb_fname->stream_name is in form
+ * of ':StrEam:$DATA', but we should only
+ * append ':StrEam' here.
+ */
+
+ e = strchr(&s[1], ':');
+ if (e == NULL) {
+ n = strlen(s);
+ } else {
+ n = PTR_DIFF(e, s);
+ }
+ nfname = talloc_strndup_append(nfname, s, n);
+ if (nfname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ status = srvstr_push(dstart, flags2,
+ pdata+4, nfname,
+ PTR_DIFF(dend, pdata+4),
+ STR_UNICODE, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ DBG_DEBUG("SMB_FILE_NORMALIZED_NAME_INFORMATION\n");
+ data_size = 4 + len;
+ SIVAL(pdata,0,len);
+ *fixed_portion = 8;
+ break;
+ }
+
+ case SMB_FILE_ALLOCATION_INFORMATION:
+ case SMB_QUERY_FILE_ALLOCATION_INFO:
+ DBG_DEBUG("SMB_FILE_ALLOCATION_INFORMATION\n");
+ data_size = 8;
+ SOFF_T(pdata,0,allocation_size);
+ break;
+
+ case SMB_FILE_END_OF_FILE_INFORMATION:
+ case SMB_QUERY_FILE_END_OF_FILEINFO:
+ DBG_DEBUG("SMB_FILE_END_OF_FILE_INFORMATION\n");
+ data_size = 8;
+ SOFF_T(pdata,0,file_size);
+ break;
+
+ case SMB_QUERY_FILE_ALL_INFO:
+ case SMB_FILE_ALL_INFORMATION:
+ {
+ unsigned int ea_size =
+ estimate_ea_size(smb_fname->fsp);
+ DBG_DEBUG("SMB_FILE_ALL_INFORMATION\n");
+ put_long_date_full_timespec(conn->ts_res,pdata,&create_time_ts);
+ put_long_date_full_timespec(conn->ts_res,pdata+8,&atime_ts);
+ put_long_date_full_timespec(conn->ts_res,pdata+16,&mtime_ts); /* write time */
+ put_long_date_full_timespec(conn->ts_res,pdata+24,&ctime_ts); /* change time */
+ SIVAL(pdata,32,mode);
+ SIVAL(pdata,36,0); /* padding. */
+ pdata += 40;
+ SOFF_T(pdata,0,allocation_size);
+ SOFF_T(pdata,8,file_size);
+ SIVAL(pdata,16,nlink);
+ SCVAL(pdata,20,delete_pending);
+ SCVAL(pdata,21,(mode&FILE_ATTRIBUTE_DIRECTORY)?1:0);
+ SSVAL(pdata,22,0);
+ pdata += 24;
+ SIVAL(pdata,0,ea_size);
+ pdata += 4; /* EA info */
+ status = srvstr_push(dstart, flags2,
+ pdata+4, dos_fname,
+ PTR_DIFF(dend, pdata+4),
+ STR_UNICODE, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ SIVAL(pdata,0,len);
+ pdata += 4 + len;
+ data_size = PTR_DIFF(pdata,(*ppdata));
+ *fixed_portion = 10;
+ break;
+ }
+
+ case SMB2_FILE_ALL_INFORMATION:
+ {
+ unsigned int ea_size =
+ estimate_ea_size(smb_fname->fsp);
+ DBG_DEBUG("SMB2_FILE_ALL_INFORMATION\n");
+ put_long_date_full_timespec(conn->ts_res,pdata+0x00,&create_time_ts);
+ put_long_date_full_timespec(conn->ts_res,pdata+0x08,&atime_ts);
+ put_long_date_full_timespec(conn->ts_res,pdata+0x10,&mtime_ts); /* write time */
+ put_long_date_full_timespec(conn->ts_res,pdata+0x18,&ctime_ts); /* change time */
+ SIVAL(pdata, 0x20, mode);
+ SIVAL(pdata, 0x24, 0); /* padding. */
+ SBVAL(pdata, 0x28, allocation_size);
+ SBVAL(pdata, 0x30, file_size);
+ SIVAL(pdata, 0x38, nlink);
+ SCVAL(pdata, 0x3C, delete_pending);
+ SCVAL(pdata, 0x3D, (mode&FILE_ATTRIBUTE_DIRECTORY)?1:0);
+ SSVAL(pdata, 0x3E, 0); /* padding */
+ SBVAL(pdata, 0x40, file_id);
+ SIVAL(pdata, 0x48, ea_size);
+ SIVAL(pdata, 0x4C, access_mask);
+ SBVAL(pdata, 0x50, pos);
+ SIVAL(pdata, 0x58, mode); /*TODO: mode != mode fix this!!! */
+ SIVAL(pdata, 0x5C, 0); /* No alignment needed. */
+
+ pdata += 0x60;
+
+ status = srvstr_push(dstart, flags2,
+ pdata+4, dos_fname,
+ PTR_DIFF(dend, pdata+4),
+ STR_UNICODE, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ SIVAL(pdata,0,len);
+ pdata += 4 + len;
+ data_size = PTR_DIFF(pdata,(*ppdata));
+ *fixed_portion = 104;
+ break;
+ }
+ case SMB_FILE_INTERNAL_INFORMATION:
+
+ DBG_DEBUG("SMB_FILE_INTERNAL_INFORMATION\n");
+ SBVAL(pdata, 0, file_id);
+ data_size = 8;
+ *fixed_portion = 8;
+ break;
+
+ case SMB_FILE_ACCESS_INFORMATION:
+ DBG_DEBUG("SMB_FILE_ACCESS_INFORMATION\n");
+ SIVAL(pdata, 0, access_mask);
+ data_size = 4;
+ *fixed_portion = 4;
+ break;
+
+ case SMB_FILE_NAME_INFORMATION:
+ /* Pathname with leading '\'. */
+ {
+ size_t byte_len;
+ byte_len = dos_PutUniCode(pdata+4,dos_fname,(size_t)max_data_bytes,False);
+ DBG_DEBUG("SMB_FILE_NAME_INFORMATION\n");
+ SIVAL(pdata,0,byte_len);
+ data_size = 4 + byte_len;
+ break;
+ }
+
+ case SMB_FILE_DISPOSITION_INFORMATION:
+ DBG_DEBUG("SMB_FILE_DISPOSITION_INFORMATION\n");
+ data_size = 1;
+ SCVAL(pdata,0,delete_pending);
+ *fixed_portion = 1;
+ break;
+
+ case SMB_FILE_POSITION_INFORMATION:
+ DBG_DEBUG("SMB_FILE_POSITION_INFORMATION\n");
+ data_size = 8;
+ SOFF_T(pdata,0,pos);
+ *fixed_portion = 8;
+ break;
+
+ case SMB_FILE_MODE_INFORMATION:
+ DBG_DEBUG("SMB_FILE_MODE_INFORMATION\n");
+ SIVAL(pdata,0,mode);
+ data_size = 4;
+ *fixed_portion = 4;
+ break;
+
+ case SMB_FILE_ALIGNMENT_INFORMATION:
+ DBG_DEBUG("SMB_FILE_ALIGNMENT_INFORMATION\n");
+ SIVAL(pdata,0,0); /* No alignment needed. */
+ data_size = 4;
+ *fixed_portion = 4;
+ break;
+
+ /*
+ * NT4 server just returns "invalid query" to this - if we try
+ * to answer it then NTws gets a BSOD! (tridge). W2K seems to
+ * want this. JRA.
+ */
+ /* The first statement above is false - verified using Thursby
+ * client against NT4 -- gcolley.
+ */
+ case SMB_QUERY_FILE_STREAM_INFO:
+ case SMB_FILE_STREAM_INFORMATION: {
+ unsigned int num_streams = 0;
+ struct stream_struct *streams = NULL;
+
+ DBG_DEBUG("SMB_FILE_STREAM_INFORMATION\n");
+
+ if (is_ntfs_stream_smb_fname(smb_fname)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = vfs_fstreaminfo(fsp,
+ mem_ctx,
+ &num_streams,
+ &streams);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("could not get stream info: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ status = marshall_stream_info(num_streams, streams,
+ pdata, max_data_bytes,
+ &data_size);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("marshall_stream_info failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(streams);
+ return status;
+ }
+
+ TALLOC_FREE(streams);
+
+ *fixed_portion = 32;
+
+ break;
+ }
+ case SMB_QUERY_COMPRESSION_INFO:
+ case SMB_FILE_COMPRESSION_INFORMATION:
+ DBG_DEBUG("SMB_FILE_COMPRESSION_INFORMATION\n");
+ SOFF_T(pdata,0,file_size);
+ SIVAL(pdata,8,0); /* ??? */
+ SIVAL(pdata,12,0); /* ??? */
+ data_size = 16;
+ *fixed_portion = 16;
+ break;
+
+ case SMB_FILE_NETWORK_OPEN_INFORMATION:
+ DBG_DEBUG("SMB_FILE_NETWORK_OPEN_INFORMATION\n");
+ put_long_date_full_timespec(conn->ts_res,pdata,&create_time_ts);
+ put_long_date_full_timespec(conn->ts_res,pdata+8,&atime_ts);
+ put_long_date_full_timespec(conn->ts_res,pdata+16,&mtime_ts); /* write time */
+ put_long_date_full_timespec(conn->ts_res,pdata+24,&ctime_ts); /* change time */
+ SOFF_T(pdata,32,allocation_size);
+ SOFF_T(pdata,40,file_size);
+ SIVAL(pdata,48,mode);
+ SIVAL(pdata,52,0); /* ??? */
+ data_size = 56;
+ *fixed_portion = 56;
+ break;
+
+ case SMB_FILE_ATTRIBUTE_TAG_INFORMATION:
+ DBG_DEBUG(" SMB_FILE_ATTRIBUTE_TAG_INFORMATION\n");
+ SIVAL(pdata,0,mode);
+ SIVAL(pdata,4,0);
+ data_size = 8;
+ *fixed_portion = 8;
+ break;
+
+ /*
+ * SMB2 UNIX Extensions.
+ */
+ case SMB2_FILE_POSIX_INFORMATION_INTERNAL:
+ {
+ struct smb3_file_posix_information info = {};
+ uint8_t buf[sizeof(info)];
+ struct ndr_push ndr = {
+ .data = buf,
+ .alloc_size = sizeof(buf),
+ .fixed_buf_size = true,
+ };
+ enum ndr_err_code ndr_err;
+
+ if (!(conn->sconn->using_smb2)) {
+ return NT_STATUS_INVALID_LEVEL;
+ }
+ if (fsp == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+ if (!(fsp->posix_flags & FSP_POSIX_FLAGS_OPEN)) {
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ smb3_file_posix_information_init(
+ conn, &smb_fname->st, 0, mode, &info);
+
+ ndr_err = ndr_push_smb3_file_posix_information(
+ &ndr, NDR_SCALARS|NDR_BUFFERS, &info);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ memcpy(pdata, buf, ndr.offset);
+ data_size = ndr.offset;
+ break;
+ }
+
+ default:
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ *pdata_size = data_size;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Set a hard link (called by UNIX extensions and by NT rename with HARD link
+ code.
+****************************************************************************/
+
+NTSTATUS hardlink_internals(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct smb_request *req,
+ bool overwrite_if_exists,
+ const struct smb_filename *smb_fname_old,
+ struct smb_filename *smb_fname_new)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ int ret;
+ bool ok;
+ struct smb_filename *parent_fname_old = NULL;
+ struct smb_filename *base_name_old = NULL;
+ struct smb_filename *parent_fname_new = NULL;
+ struct smb_filename *base_name_new = NULL;
+
+ /* source must already exist. */
+ if (!VALID_STAT(smb_fname_old->st)) {
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ goto out;
+ }
+
+ /* No links from a directory. */
+ if (S_ISDIR(smb_fname_old->st.st_ex_mode)) {
+ status = NT_STATUS_FILE_IS_A_DIRECTORY;
+ goto out;
+ }
+
+ /* Setting a hardlink to/from a stream isn't currently supported. */
+ ok = is_ntfs_stream_smb_fname(smb_fname_old);
+ if (ok) {
+ DBG_DEBUG("Old name has streams\n");
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+ ok = is_ntfs_stream_smb_fname(smb_fname_new);
+ if (ok) {
+ DBG_DEBUG("New name has streams\n");
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ if (smb_fname_old->twrp != 0) {
+ status = NT_STATUS_NOT_SAME_DEVICE;
+ goto out;
+ }
+
+ status = parent_pathref(talloc_tos(),
+ conn->cwd_fsp,
+ smb_fname_old,
+ &parent_fname_old,
+ &base_name_old);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = parent_pathref(talloc_tos(),
+ conn->cwd_fsp,
+ smb_fname_new,
+ &parent_fname_new,
+ &base_name_new);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ if (VALID_STAT(smb_fname_new->st)) {
+ if (overwrite_if_exists) {
+ if (S_ISDIR(smb_fname_new->st.st_ex_mode)) {
+ status = NT_STATUS_FILE_IS_A_DIRECTORY;
+ goto out;
+ }
+ status = unlink_internals(conn,
+ req,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL, /* new_dirfsp */
+ smb_fname_new);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ } else {
+ /* Disallow if newname already exists. */
+ status = NT_STATUS_OBJECT_NAME_COLLISION;
+ goto out;
+ }
+ }
+
+ DEBUG(10,("hardlink_internals: doing hard link %s -> %s\n",
+ smb_fname_old->base_name, smb_fname_new->base_name));
+
+ ret = SMB_VFS_LINKAT(conn,
+ parent_fname_old->fsp,
+ base_name_old,
+ parent_fname_new->fsp,
+ base_name_new,
+ 0);
+
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ DEBUG(3,("hardlink_internals: Error %s hard link %s -> %s\n",
+ nt_errstr(status), smb_fname_old->base_name,
+ smb_fname_new->base_name));
+ }
+
+ out:
+
+ TALLOC_FREE(parent_fname_old);
+ TALLOC_FREE(parent_fname_new);
+ return status;
+}
+
+/****************************************************************************
+ Deal with setting the time from any of the setfilepathinfo functions.
+ NOTE !!!! The check for FILE_WRITE_ATTRIBUTES access must be done *before*
+ calling this function.
+****************************************************************************/
+
+NTSTATUS smb_set_file_time(connection_struct *conn,
+ files_struct *fsp,
+ struct smb_filename *smb_fname,
+ struct smb_file_time *ft,
+ bool setting_write_time)
+{
+ struct files_struct *set_fsp = NULL;
+ struct timeval_buf tbuf[4];
+ uint32_t action =
+ FILE_NOTIFY_CHANGE_LAST_ACCESS
+ |FILE_NOTIFY_CHANGE_LAST_WRITE
+ |FILE_NOTIFY_CHANGE_CREATION;
+ int ret;
+
+ if (!VALID_STAT(smb_fname->st)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (fsp == NULL) {
+ /* A symlink */
+ return NT_STATUS_OK;
+ }
+
+ set_fsp = metadata_fsp(fsp);
+
+ /* get some defaults (no modifications) if any info is zero or -1. */
+ if (is_omit_timespec(&ft->create_time)) {
+ action &= ~FILE_NOTIFY_CHANGE_CREATION;
+ }
+
+ if (is_omit_timespec(&ft->atime)) {
+ action &= ~FILE_NOTIFY_CHANGE_LAST_ACCESS;
+ }
+
+ if (is_omit_timespec(&ft->mtime)) {
+ action &= ~FILE_NOTIFY_CHANGE_LAST_WRITE;
+ }
+
+ if (!setting_write_time) {
+ /* ft->mtime comes from change time, not write time. */
+ action &= ~FILE_NOTIFY_CHANGE_LAST_WRITE;
+ }
+
+ /* Ensure the resolution is the correct for
+ * what we can store on this filesystem. */
+
+ round_timespec(conn->ts_res, &ft->create_time);
+ round_timespec(conn->ts_res, &ft->ctime);
+ round_timespec(conn->ts_res, &ft->atime);
+ round_timespec(conn->ts_res, &ft->mtime);
+
+ DBG_DEBUG("smb_set_filetime: actime: %s\n ",
+ timespec_string_buf(&ft->atime, true, &tbuf[0]));
+ DBG_DEBUG("smb_set_filetime: modtime: %s\n ",
+ timespec_string_buf(&ft->mtime, true, &tbuf[1]));
+ DBG_DEBUG("smb_set_filetime: ctime: %s\n ",
+ timespec_string_buf(&ft->ctime, true, &tbuf[2]));
+ DBG_DEBUG("smb_set_file_time: createtime: %s\n ",
+ timespec_string_buf(&ft->create_time, true, &tbuf[3]));
+
+ if (setting_write_time) {
+ /*
+ * This was a Windows setfileinfo on an open file.
+ * NT does this a lot. We also need to
+ * set the time here, as it can be read by
+ * FindFirst/FindNext and with the patch for bug #2045
+ * in smbd/fileio.c it ensures that this timestamp is
+ * kept sticky even after a write. We save the request
+ * away and will set it on file close and after a write. JRA.
+ */
+
+ DBG_DEBUG("setting pending modtime to %s\n",
+ timespec_string_buf(&ft->mtime, true, &tbuf[0]));
+
+ if (set_fsp != NULL) {
+ set_sticky_write_time_fsp(set_fsp, ft->mtime);
+ } else {
+ set_sticky_write_time_path(
+ vfs_file_id_from_sbuf(conn, &smb_fname->st),
+ ft->mtime);
+ }
+ }
+
+ DEBUG(10,("smb_set_file_time: setting utimes to modified values.\n"));
+
+ ret = file_ntimes(conn, set_fsp, ft);
+ if (ret != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ notify_fname(conn, NOTIFY_ACTION_MODIFIED, action,
+ smb_fname->base_name);
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with setting the dosmode from any of the setfilepathinfo functions.
+ NB. The check for FILE_WRITE_ATTRIBUTES access on this path must have been
+ done before calling this function.
+****************************************************************************/
+
+static NTSTATUS smb_set_file_dosmode(connection_struct *conn,
+ struct files_struct *fsp,
+ uint32_t dosmode)
+{
+ struct files_struct *dos_fsp = NULL;
+ uint32_t current_dosmode;
+ int ret;
+
+ if (!VALID_STAT(fsp->fsp_name->st)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ dos_fsp = metadata_fsp(fsp);
+
+ if (dosmode != 0) {
+ if (S_ISDIR(fsp->fsp_name->st.st_ex_mode)) {
+ dosmode |= FILE_ATTRIBUTE_DIRECTORY;
+ } else {
+ dosmode &= ~FILE_ATTRIBUTE_DIRECTORY;
+ }
+ }
+
+ DBG_DEBUG("dosmode: 0x%" PRIx32 "\n", dosmode);
+
+ /* check the mode isn't different, before changing it */
+ if (dosmode == 0) {
+ return NT_STATUS_OK;
+ }
+ current_dosmode = fdos_mode(dos_fsp);
+ if (dosmode == current_dosmode) {
+ return NT_STATUS_OK;
+ }
+
+ DBG_DEBUG("file %s : setting dos mode 0x%" PRIx32 "\n",
+ fsp_str_dbg(dos_fsp), dosmode);
+
+ ret = file_set_dosmode(conn, dos_fsp->fsp_name, dosmode, NULL, false);
+ if (ret != 0) {
+ DBG_WARNING("file_set_dosmode of %s failed: %s\n",
+ fsp_str_dbg(dos_fsp), strerror(errno));
+ return map_nt_error_from_unix(errno);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with setting the size from any of the setfilepathinfo functions.
+****************************************************************************/
+
+NTSTATUS smb_set_file_size(connection_struct *conn,
+ struct smb_request *req,
+ files_struct *fsp,
+ struct smb_filename *smb_fname,
+ const SMB_STRUCT_STAT *psbuf,
+ off_t size,
+ bool fail_after_createfile)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ files_struct *new_fsp = NULL;
+
+ if (!VALID_STAT(*psbuf)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ DBG_INFO("size: %"PRIu64", file_size_stat=%"PRIu64"\n",
+ (uint64_t)size,
+ get_file_size_stat(psbuf));
+
+ if (size == get_file_size_stat(psbuf)) {
+ if (fsp == NULL) {
+ return NT_STATUS_OK;
+ }
+ if (!fsp->fsp_flags.modified) {
+ return NT_STATUS_OK;
+ }
+ trigger_write_time_update_immediate(fsp);
+ return NT_STATUS_OK;
+ }
+
+ DEBUG(10,("smb_set_file_size: file %s : setting new size to %.0f\n",
+ smb_fname_str_dbg(smb_fname), (double)size));
+
+ if (fsp &&
+ !fsp->fsp_flags.is_pathref &&
+ fsp_get_io_fd(fsp) != -1)
+ {
+ /* Handle based call. */
+ status = check_any_access_fsp(fsp, FILE_WRITE_DATA);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (vfs_set_filelen(fsp, size) == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+ trigger_write_time_update_immediate(fsp);
+ return NT_STATUS_OK;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ NULL, /* dirfsp */
+ smb_fname, /* fname */
+ FILE_WRITE_DATA, /* access_mask */
+ (FILE_SHARE_READ | FILE_SHARE_WRITE | /* share_access */
+ FILE_SHARE_DELETE),
+ FILE_OPEN, /* create_disposition*/
+ 0, /* create_options */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes */
+ 0, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &new_fsp, /* result */
+ NULL, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ /* NB. We check for open_was_deferred in the caller. */
+ return status;
+ }
+
+ /* See RAW-SFILEINFO-END-OF-FILE */
+ if (fail_after_createfile) {
+ close_file_free(req, &new_fsp, NORMAL_CLOSE);
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ if (vfs_set_filelen(new_fsp, size) == -1) {
+ status = map_nt_error_from_unix(errno);
+ close_file_free(req, &new_fsp, NORMAL_CLOSE);
+ return status;
+ }
+
+ trigger_write_time_update_immediate(new_fsp);
+ close_file_free(req, &new_fsp, NORMAL_CLOSE);
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with SMB_INFO_SET_EA.
+****************************************************************************/
+
+static NTSTATUS smb_info_set_ea(connection_struct *conn,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ struct smb_filename *smb_fname)
+{
+ struct ea_list *ea_list = NULL;
+ TALLOC_CTX *ctx = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (total_data < 10) {
+
+ /* OS/2 workplace shell seems to send SET_EA requests of "null"
+ length. They seem to have no effect. Bug #3212. JRA */
+
+ if ((total_data == 4) && (IVAL(pdata,0) == 4)) {
+ /* We're done. We only get EA info in this call. */
+ return NT_STATUS_OK;
+ }
+
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (IVAL(pdata,0) > total_data) {
+ DEBUG(10,("smb_info_set_ea: bad total data size (%u) > %u\n",
+ IVAL(pdata,0), (unsigned int)total_data));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ctx = talloc_tos();
+ ea_list = read_ea_list(ctx, pdata + 4, total_data - 4);
+ if (!ea_list) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (fsp == NULL) {
+ /*
+ * The only way fsp can be NULL here is if
+ * smb_fname points at a symlink and
+ * and we're in POSIX context.
+ * Ensure this is the case.
+ *
+ * In this case we cannot set the EA.
+ */
+ SMB_ASSERT(smb_fname->flags & SMB_FILENAME_POSIX_PATH);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ status = set_ea(conn, fsp, ea_list);
+
+ return status;
+}
+
+/****************************************************************************
+ Deal with SMB_FILE_FULL_EA_INFORMATION set.
+****************************************************************************/
+
+static NTSTATUS smb_set_file_full_ea_info(connection_struct *conn,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp)
+{
+ struct ea_list *ea_list = NULL;
+ NTSTATUS status;
+
+ if (fsp == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (!lp_ea_support(SNUM(conn))) {
+ DEBUG(10, ("smb_set_file_full_ea_info - ea_len = %u but "
+ "EA's not supported.\n",
+ (unsigned int)total_data));
+ return NT_STATUS_EAS_NOT_SUPPORTED;
+ }
+
+ if (total_data < 10) {
+ DEBUG(10, ("smb_set_file_full_ea_info - ea_len = %u "
+ "too small.\n",
+ (unsigned int)total_data));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ea_list = read_nttrans_ea_list(talloc_tos(),
+ pdata,
+ total_data);
+
+ if (!ea_list) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = set_ea(conn, fsp, ea_list);
+
+ DEBUG(10, ("smb_set_file_full_ea_info on file %s returned %s\n",
+ smb_fname_str_dbg(fsp->fsp_name),
+ nt_errstr(status) ));
+
+ return status;
+}
+
+
+/****************************************************************************
+ Deal with SMB_SET_FILE_DISPOSITION_INFO.
+****************************************************************************/
+
+NTSTATUS smb_set_file_disposition_info(connection_struct *conn,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ struct smb_filename *smb_fname)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ bool delete_on_close;
+ uint32_t dosmode = 0;
+
+ if (total_data < 1) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (fsp == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ delete_on_close = (CVAL(pdata,0) ? True : False);
+ dosmode = fdos_mode(fsp);
+
+ DEBUG(10,("smb_set_file_disposition_info: file %s, dosmode = %u, "
+ "delete_on_close = %u\n",
+ smb_fname_str_dbg(smb_fname),
+ (unsigned int)dosmode,
+ (unsigned int)delete_on_close ));
+
+ if (delete_on_close) {
+ status = can_set_delete_on_close(fsp, dosmode);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ /* The set is across all open files on this dev/inode pair. */
+ if (!set_delete_on_close(fsp, delete_on_close,
+ conn->session_info->security_token,
+ conn->session_info->unix_token)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with SMB_FILE_POSITION_INFORMATION.
+****************************************************************************/
+
+static NTSTATUS smb_file_position_information(connection_struct *conn,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp)
+{
+ uint64_t position_information;
+
+ if (total_data < 8) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (fsp == NULL) {
+ /* Ignore on pathname based set. */
+ return NT_STATUS_OK;
+ }
+
+ position_information = (uint64_t)IVAL(pdata,0);
+ position_information |= (((uint64_t)IVAL(pdata,4)) << 32);
+
+ DEBUG(10,("smb_file_position_information: Set file position "
+ "information for file %s to %.0f\n", fsp_str_dbg(fsp),
+ (double)position_information));
+ fh_set_position_information(fsp->fh, position_information);
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with SMB_FILE_MODE_INFORMATION.
+****************************************************************************/
+
+static NTSTATUS smb_file_mode_information(connection_struct *conn,
+ const char *pdata,
+ int total_data)
+{
+ uint32_t mode;
+
+ if (total_data < 4) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ mode = IVAL(pdata,0);
+ if (mode != 0 && mode != 2 && mode != 4 && mode != 6) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with SMB2_FILE_RENAME_INFORMATION_INTERNAL
+****************************************************************************/
+
+static NTSTATUS smb2_file_rename_information(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ struct smb_filename *smb_fname_src)
+{
+ bool overwrite;
+ uint32_t len;
+ char *newname = NULL;
+ struct files_struct *dst_dirfsp = NULL;
+ struct smb_filename *smb_fname_dst = NULL;
+ const char *dst_original_lcomp = NULL;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+ NTSTATUS status = NT_STATUS_OK;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ if (!fsp) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (total_data < 20) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ overwrite = (CVAL(pdata,0) ? True : False);
+ len = IVAL(pdata,16);
+
+ if (len > (total_data - 20) || (len == 0)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ (void)srvstr_pull_talloc(ctx,
+ pdata,
+ req->flags2,
+ &newname,
+ &pdata[20],
+ len,
+ STR_TERMINATE);
+
+ if (newname == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* SMB2 rename paths are never DFS. */
+ req->flags2 &= ~FLAGS2_DFS_PATHNAMES;
+ ucf_flags &= ~UCF_DFS_PATHNAME;
+
+ status = check_path_syntax(newname,
+ fsp->fsp_name->flags & SMB_FILENAME_POSIX_PATH);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(10,("smb2_file_rename_information: got name |%s|\n",
+ newname));
+
+ if (newname[0] == ':') {
+ /* Create an smb_fname to call rename_internals_fsp() with. */
+ smb_fname_dst = synthetic_smb_fname(talloc_tos(),
+ fsp->base_fsp->fsp_name->base_name,
+ newname,
+ NULL,
+ fsp->base_fsp->fsp_name->twrp,
+ fsp->base_fsp->fsp_name->flags);
+ if (smb_fname_dst == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ } else {
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ newname,
+ ucf_flags,
+ 0, /* Never a TWRP. */
+ &dst_dirfsp,
+ &smb_fname_dst);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ }
+
+ /*
+ * Set the original last component, since
+ * rename_internals_fsp() requires it.
+ */
+ dst_original_lcomp = get_original_lcomp(smb_fname_dst,
+ conn,
+ newname,
+ ucf_flags);
+ if (dst_original_lcomp == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ DEBUG(10,("smb2_file_rename_information: "
+ "SMB_FILE_RENAME_INFORMATION (%s) %s -> %s\n",
+ fsp_fnum_dbg(fsp), fsp_str_dbg(fsp),
+ smb_fname_str_dbg(smb_fname_dst)));
+ status = rename_internals_fsp(conn,
+ fsp,
+ smb_fname_dst,
+ dst_original_lcomp,
+ (FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM),
+ overwrite);
+
+ out:
+ TALLOC_FREE(smb_fname_dst);
+ return status;
+}
+
+static NTSTATUS smb2_file_link_information(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ struct smb_filename *smb_fname_src)
+{
+ bool overwrite;
+ uint32_t len;
+ char *newname = NULL;
+ struct files_struct *dst_dirfsp = NULL;
+ struct smb_filename *smb_fname_dst = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+ size_t ret;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ if (!fsp) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (total_data < 20) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ overwrite = (CVAL(pdata,0) ? true : false);
+ len = IVAL(pdata,16);
+
+ if (len > (total_data - 20) || (len == 0)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ret = srvstr_pull_talloc(ctx,
+ pdata,
+ req->flags2,
+ &newname,
+ &pdata[20],
+ len,
+ STR_TERMINATE);
+
+ if (ret == (size_t)-1 || newname == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* SMB2 hardlink paths are never DFS. */
+ req->flags2 &= ~FLAGS2_DFS_PATHNAMES;
+ ucf_flags &= ~UCF_DFS_PATHNAME;
+
+ status = check_path_syntax(newname,
+ fsp->fsp_name->flags & SMB_FILENAME_POSIX_PATH);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DBG_DEBUG("got name |%s|\n", newname);
+
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ newname,
+ ucf_flags,
+ 0, /* No TWRP. */
+ &dst_dirfsp,
+ &smb_fname_dst);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (fsp->base_fsp) {
+ /* No stream names. */
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ DBG_DEBUG("SMB_FILE_LINK_INFORMATION (%s) %s -> %s\n",
+ fsp_fnum_dbg(fsp), fsp_str_dbg(fsp),
+ smb_fname_str_dbg(smb_fname_dst));
+ status = hardlink_internals(ctx,
+ conn,
+ req,
+ overwrite,
+ fsp->fsp_name,
+ smb_fname_dst);
+
+ TALLOC_FREE(smb_fname_dst);
+ return status;
+}
+
+static NTSTATUS smb_file_link_information(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ struct smb_filename *smb_fname_src)
+{
+ bool overwrite;
+ uint32_t len;
+ char *newname = NULL;
+ struct files_struct *dst_dirfsp = NULL;
+ struct smb_filename *smb_fname_dst = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+ NTTIME dst_twrp = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ if (!fsp) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (total_data < 20) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ overwrite = (CVAL(pdata,0) ? true : false);
+ len = IVAL(pdata,16);
+
+ if (len > (total_data - 20) || (len == 0)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (smb_fname_src->flags & SMB_FILENAME_POSIX_PATH) {
+ srvstr_get_path_posix(ctx,
+ pdata,
+ req->flags2,
+ &newname,
+ &pdata[20],
+ len,
+ STR_TERMINATE,
+ &status);
+ ucf_flags |= UCF_POSIX_PATHNAMES;
+ } else {
+ srvstr_get_path(ctx,
+ pdata,
+ req->flags2,
+ &newname,
+ &pdata[20],
+ len,
+ STR_TERMINATE,
+ &status);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(10,("smb_file_link_information: got name |%s|\n",
+ newname));
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(newname, &dst_twrp);
+ }
+ /* hardlink paths are never DFS. */
+ ucf_flags &= ~UCF_DFS_PATHNAME;
+
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ newname,
+ ucf_flags,
+ dst_twrp,
+ &dst_dirfsp,
+ &smb_fname_dst);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (fsp->base_fsp) {
+ /* No stream names. */
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ DEBUG(10,("smb_file_link_information: "
+ "SMB_FILE_LINK_INFORMATION (%s) %s -> %s\n",
+ fsp_fnum_dbg(fsp), fsp_str_dbg(fsp),
+ smb_fname_str_dbg(smb_fname_dst)));
+ status = hardlink_internals(ctx,
+ conn,
+ req,
+ overwrite,
+ fsp->fsp_name,
+ smb_fname_dst);
+
+ TALLOC_FREE(smb_fname_dst);
+ return status;
+}
+
+
+/****************************************************************************
+ Deal with SMB_FILE_RENAME_INFORMATION.
+****************************************************************************/
+
+static NTSTATUS smb_file_rename_information(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ struct smb_filename *smb_fname_src)
+{
+ bool overwrite;
+ uint32_t root_fid;
+ uint32_t len;
+ char *newname = NULL;
+ struct files_struct *dst_dirfsp = NULL;
+ struct smb_filename *smb_fname_dst = NULL;
+ const char *dst_original_lcomp = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+ char *p;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ if (total_data < 13) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ overwrite = (CVAL(pdata,0) != 0);
+ root_fid = IVAL(pdata,4);
+ len = IVAL(pdata,8);
+
+ if (len > (total_data - 12) || (len == 0) || (root_fid != 0)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (req->posix_pathnames) {
+ srvstr_get_path_posix(ctx,
+ pdata,
+ req->flags2,
+ &newname,
+ &pdata[12],
+ len,
+ 0,
+ &status);
+ } else {
+ srvstr_get_path(ctx,
+ pdata,
+ req->flags2,
+ &newname,
+ &pdata[12],
+ len,
+ 0,
+ &status);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(10,("smb_file_rename_information: got name |%s|\n",
+ newname));
+
+ /* Check the new name has no '/' characters. */
+ if (strchr_m(newname, '/')) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if (fsp && fsp->base_fsp) {
+ /* newname must be a stream name. */
+ if (newname[0] != ':') {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ /* Create an smb_fname to call rename_internals_fsp() with. */
+ smb_fname_dst = synthetic_smb_fname(talloc_tos(),
+ fsp->base_fsp->fsp_name->base_name,
+ newname,
+ NULL,
+ fsp->base_fsp->fsp_name->twrp,
+ fsp->base_fsp->fsp_name->flags);
+ if (smb_fname_dst == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ /*
+ * Get the original last component, since
+ * rename_internals_fsp() requires it.
+ */
+ dst_original_lcomp = get_original_lcomp(smb_fname_dst,
+ conn,
+ newname,
+ 0);
+ if (dst_original_lcomp == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ } else {
+ /*
+ * Build up an smb_fname_dst based on the filename passed in.
+ * We basically just strip off the last component, and put on
+ * the newname instead.
+ */
+ char *base_name = NULL;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+ NTTIME dst_twrp = 0;
+
+ /* newname must *not* be a stream name. */
+ if (newname[0] == ':') {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ /*
+ * Strip off the last component (filename) of the path passed
+ * in.
+ */
+ base_name = talloc_strdup(ctx, smb_fname_src->base_name);
+ if (!base_name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ p = strrchr_m(base_name, '/');
+ if (p) {
+ p[1] = '\0';
+ } else {
+ base_name = talloc_strdup(ctx, "");
+ if (!base_name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ /* Append the new name. */
+ base_name = talloc_asprintf_append(base_name,
+ "%s",
+ newname);
+ if (!base_name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(base_name, &dst_twrp);
+ }
+
+ /* The newname is *not* a DFS path. */
+ ucf_flags &= ~UCF_DFS_PATHNAME;
+
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ base_name,
+ ucf_flags,
+ dst_twrp,
+ &dst_dirfsp,
+ &smb_fname_dst);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ dst_original_lcomp = get_original_lcomp(smb_fname_dst,
+ conn,
+ newname,
+ ucf_flags);
+ if (dst_original_lcomp == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+
+ if (fsp != NULL && fsp->fsp_flags.is_fsa) {
+ DEBUG(10,("smb_file_rename_information: "
+ "SMB_FILE_RENAME_INFORMATION (%s) %s -> %s\n",
+ fsp_fnum_dbg(fsp), fsp_str_dbg(fsp),
+ smb_fname_str_dbg(smb_fname_dst)));
+ status = rename_internals_fsp(conn,
+ fsp,
+ smb_fname_dst,
+ dst_original_lcomp,
+ 0,
+ overwrite);
+ } else {
+ DEBUG(10,("smb_file_rename_information: "
+ "SMB_FILE_RENAME_INFORMATION %s -> %s\n",
+ smb_fname_str_dbg(smb_fname_src),
+ smb_fname_str_dbg(smb_fname_dst)));
+ status = rename_internals(ctx,
+ conn,
+ req,
+ NULL, /* src_dirfsp */
+ smb_fname_src,
+ smb_fname_dst,
+ dst_original_lcomp,
+ 0,
+ overwrite,
+ FILE_WRITE_ATTRIBUTES);
+ }
+ out:
+ TALLOC_FREE(smb_fname_dst);
+ return status;
+}
+
+/****************************************************************************
+ Deal with SMB_SET_FILE_BASIC_INFO.
+****************************************************************************/
+
+static NTSTATUS smb_set_file_basic_info(connection_struct *conn,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ struct smb_filename *smb_fname)
+{
+ /* Patch to do this correctly from Paul Eggert <eggert@twinsun.com>. */
+ struct smb_file_time ft;
+ uint32_t dosmode = 0;
+ NTSTATUS status = NT_STATUS_OK;
+
+ init_smb_file_time(&ft);
+
+ if (total_data < 36) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (fsp == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ status = check_any_access_fsp(fsp, FILE_WRITE_ATTRIBUTES);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* Set the attributes */
+ dosmode = IVAL(pdata,32);
+ status = smb_set_file_dosmode(conn, fsp, dosmode);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* create time */
+ ft.create_time = pull_long_date_full_timespec(pdata);
+
+ /* access time */
+ ft.atime = pull_long_date_full_timespec(pdata+8);
+
+ /* write time. */
+ ft.mtime = pull_long_date_full_timespec(pdata+16);
+
+ /* change time. */
+ ft.ctime = pull_long_date_full_timespec(pdata+24);
+
+ DEBUG(10, ("smb_set_file_basic_info: file %s\n",
+ smb_fname_str_dbg(smb_fname)));
+
+ status = smb_set_file_time(conn, fsp, smb_fname, &ft, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (fsp->fsp_flags.modified) {
+ trigger_write_time_update_immediate(fsp);
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with SMB_INFO_STANDARD.
+****************************************************************************/
+
+static NTSTATUS smb_set_info_standard(connection_struct *conn,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ struct smb_filename *smb_fname)
+{
+ NTSTATUS status;
+ struct smb_file_time ft;
+
+ init_smb_file_time(&ft);
+
+ if (total_data < 12) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (fsp == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ /* create time */
+ ft.create_time = time_t_to_full_timespec(srv_make_unix_date2(pdata));
+ /* access time */
+ ft.atime = time_t_to_full_timespec(srv_make_unix_date2(pdata+4));
+ /* write time */
+ ft.mtime = time_t_to_full_timespec(srv_make_unix_date2(pdata+8));
+
+ DEBUG(10,("smb_set_info_standard: file %s\n",
+ smb_fname_str_dbg(smb_fname)));
+
+ status = check_any_access_fsp(fsp, FILE_WRITE_ATTRIBUTES);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = smb_set_file_time(conn, fsp, smb_fname, &ft, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (fsp->fsp_flags.modified) {
+ trigger_write_time_update_immediate(fsp);
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with SMB_SET_FILE_ALLOCATION_INFO.
+****************************************************************************/
+
+static NTSTATUS smb_set_file_allocation_info(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ struct smb_filename *smb_fname)
+{
+ uint64_t allocation_size = 0;
+ NTSTATUS status = NT_STATUS_OK;
+ files_struct *new_fsp = NULL;
+
+ if (!VALID_STAT(smb_fname->st)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (total_data < 8) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ allocation_size = (uint64_t)IVAL(pdata,0);
+ allocation_size |= (((uint64_t)IVAL(pdata,4)) << 32);
+ DEBUG(10,("smb_set_file_allocation_info: Set file allocation info for "
+ "file %s to %.0f\n", smb_fname_str_dbg(smb_fname),
+ (double)allocation_size));
+
+ if (allocation_size) {
+ allocation_size = smb_roundup(conn, allocation_size);
+ }
+
+ DEBUG(10,("smb_set_file_allocation_info: file %s : setting new "
+ "allocation size to %.0f\n", smb_fname_str_dbg(smb_fname),
+ (double)allocation_size));
+
+ if (fsp &&
+ !fsp->fsp_flags.is_pathref &&
+ fsp_get_io_fd(fsp) != -1)
+ {
+ /* Open file handle. */
+ status = check_any_access_fsp(fsp, FILE_WRITE_DATA);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* Only change if needed. */
+ if (allocation_size != get_file_size_stat(&smb_fname->st)) {
+ if (vfs_allocate_file_space(fsp, allocation_size) == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+ }
+ /* But always update the time. */
+ /*
+ * This is equivalent to a write. Ensure it's seen immediately
+ * if there are no pending writes.
+ */
+ trigger_write_time_update_immediate(fsp);
+ return NT_STATUS_OK;
+ }
+
+ /* Pathname or stat or directory file. */
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ NULL, /* dirfsp */
+ smb_fname, /* fname */
+ FILE_WRITE_DATA, /* access_mask */
+ (FILE_SHARE_READ | FILE_SHARE_WRITE | /* share_access */
+ FILE_SHARE_DELETE),
+ FILE_OPEN, /* create_disposition*/
+ 0, /* create_options */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes */
+ 0, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &new_fsp, /* result */
+ NULL, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ /* NB. We check for open_was_deferred in the caller. */
+ return status;
+ }
+
+ /* Only change if needed. */
+ if (allocation_size != get_file_size_stat(&smb_fname->st)) {
+ if (vfs_allocate_file_space(new_fsp, allocation_size) == -1) {
+ status = map_nt_error_from_unix(errno);
+ close_file_free(req, &new_fsp, NORMAL_CLOSE);
+ return status;
+ }
+ }
+
+ /* Changing the allocation size should set the last mod time. */
+ /*
+ * This is equivalent to a write. Ensure it's seen immediately
+ * if there are no pending writes.
+ */
+ trigger_write_time_update_immediate(new_fsp);
+ close_file_free(req, &new_fsp, NORMAL_CLOSE);
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with SMB_SET_FILE_END_OF_FILE_INFO.
+****************************************************************************/
+
+static NTSTATUS smb_set_file_end_of_file_info(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ struct smb_filename *smb_fname,
+ bool fail_after_createfile)
+{
+ off_t size;
+
+ if (total_data < 8) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ size = IVAL(pdata,0);
+ size |= (((off_t)IVAL(pdata,4)) << 32);
+ DEBUG(10,("smb_set_file_end_of_file_info: Set end of file info for "
+ "file %s to %.0f\n", smb_fname_str_dbg(smb_fname),
+ (double)size));
+
+ return smb_set_file_size(conn, req,
+ fsp,
+ smb_fname,
+ &smb_fname->st,
+ size,
+ fail_after_createfile);
+}
+
+NTSTATUS smbd_do_setfilepathinfo(connection_struct *conn,
+ struct smb_request *req,
+ TALLOC_CTX *mem_ctx,
+ uint16_t info_level,
+ files_struct *fsp,
+ struct smb_filename *smb_fname,
+ char **ppdata, int total_data,
+ int *ret_data_size)
+{
+ char *pdata = *ppdata;
+ NTSTATUS status = NT_STATUS_OK;
+ int data_return_size = 0;
+
+ *ret_data_size = 0;
+
+ DEBUG(3,("smbd_do_setfilepathinfo: %s (%s) info_level=%d "
+ "totdata=%d\n", smb_fname_str_dbg(smb_fname),
+ fsp_fnum_dbg(fsp),
+ info_level, total_data));
+
+ switch (info_level) {
+
+ case SMB_INFO_STANDARD:
+ {
+ status = smb_set_info_standard(conn,
+ pdata,
+ total_data,
+ fsp,
+ smb_fname);
+ break;
+ }
+
+ case SMB_INFO_SET_EA:
+ {
+ status = smb_info_set_ea(conn,
+ pdata,
+ total_data,
+ fsp,
+ smb_fname);
+ break;
+ }
+
+ case SMB_SET_FILE_BASIC_INFO:
+ case SMB_FILE_BASIC_INFORMATION:
+ {
+ status = smb_set_file_basic_info(conn,
+ pdata,
+ total_data,
+ fsp,
+ smb_fname);
+ break;
+ }
+
+ case SMB_FILE_ALLOCATION_INFORMATION:
+ case SMB_SET_FILE_ALLOCATION_INFO:
+ {
+ status = smb_set_file_allocation_info(conn, req,
+ pdata,
+ total_data,
+ fsp,
+ smb_fname);
+ break;
+ }
+
+ case SMB_FILE_END_OF_FILE_INFORMATION:
+ case SMB_SET_FILE_END_OF_FILE_INFO:
+ {
+ /*
+ * XP/Win7 both fail after the createfile with
+ * SMB_SET_FILE_END_OF_FILE_INFO but not
+ * SMB_FILE_END_OF_FILE_INFORMATION (pass-through).
+ * The level is known here, so pass it down
+ * appropriately.
+ */
+ bool should_fail =
+ (info_level == SMB_SET_FILE_END_OF_FILE_INFO);
+
+ status = smb_set_file_end_of_file_info(conn, req,
+ pdata,
+ total_data,
+ fsp,
+ smb_fname,
+ should_fail);
+ break;
+ }
+
+ case SMB_FILE_DISPOSITION_INFORMATION:
+ case SMB_SET_FILE_DISPOSITION_INFO: /* Set delete on close for open file. */
+ {
+#if 0
+ /* JRA - We used to just ignore this on a path ?
+ * Shouldn't this be invalid level on a pathname
+ * based call ?
+ */
+ if (tran_call != TRANSACT2_SETFILEINFO) {
+ return ERROR_NT(NT_STATUS_INVALID_LEVEL);
+ }
+#endif
+ status = smb_set_file_disposition_info(conn,
+ pdata,
+ total_data,
+ fsp,
+ smb_fname);
+ break;
+ }
+
+ case SMB_FILE_POSITION_INFORMATION:
+ {
+ status = smb_file_position_information(conn,
+ pdata,
+ total_data,
+ fsp);
+ break;
+ }
+
+ case SMB_FILE_FULL_EA_INFORMATION:
+ {
+ status = smb_set_file_full_ea_info(conn,
+ pdata,
+ total_data,
+ fsp);
+ break;
+ }
+
+ /* From tridge Samba4 :
+ * MODE_INFORMATION in setfileinfo (I have no
+ * idea what "mode information" on a file is - it takes a value of 0,
+ * 2, 4 or 6. What could it be?).
+ */
+
+ case SMB_FILE_MODE_INFORMATION:
+ {
+ status = smb_file_mode_information(conn,
+ pdata,
+ total_data);
+ break;
+ }
+
+ /* [MS-SMB2] 3.3.5.21.1 states we MUST fail with STATUS_NOT_SUPPORTED. */
+ case SMB_FILE_VALID_DATA_LENGTH_INFORMATION:
+ case SMB_FILE_SHORT_NAME_INFORMATION:
+ return NT_STATUS_NOT_SUPPORTED;
+
+ case SMB_FILE_RENAME_INFORMATION:
+ {
+ status = smb_file_rename_information(conn, req,
+ pdata, total_data,
+ fsp, smb_fname);
+ break;
+ }
+
+ case SMB2_FILE_RENAME_INFORMATION_INTERNAL:
+ {
+ /* SMB2 rename information. */
+ status = smb2_file_rename_information(conn, req,
+ pdata, total_data,
+ fsp, smb_fname);
+ break;
+ }
+
+ case SMB_FILE_LINK_INFORMATION:
+ {
+ if (conn->sconn->using_smb2) {
+ status = smb2_file_link_information(conn,
+ req,
+ pdata,
+ total_data,
+ fsp,
+ smb_fname);
+ } else {
+ status = smb_file_link_information(conn,
+ req,
+ pdata,
+ total_data,
+ fsp,
+ smb_fname);
+ }
+ break;
+ }
+
+ default:
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ *ret_data_size = data_return_size;
+ return NT_STATUS_OK;
+}
+
+static uint32_t generate_volume_serial_number(
+ const struct loadparm_substitution *lp_sub,
+ int snum)
+{
+ int serial = lp_volume_serial_number(snum);
+ return serial != -1 ? serial:
+ str_checksum(lp_servicename(talloc_tos(), lp_sub, snum)) ^
+ (str_checksum(get_local_machine_name())<<16);
+}
diff --git a/source3/smbd/smb2_write.c b/source3/smbd/smb2_write.c
new file mode 100644
index 0000000..2675997
--- /dev/null
+++ b/source3/smbd/smb2_write.c
@@ -0,0 +1,457 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "rpc_server/srv_pipe_hnd.h"
+#include "libcli/security/security.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static struct tevent_req *smbd_smb2_write_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *in_fsp,
+ DATA_BLOB in_data,
+ uint64_t in_offset,
+ uint32_t in_flags);
+static NTSTATUS smbd_smb2_write_recv(struct tevent_req *req,
+ uint32_t *out_count);
+
+static void smbd_smb2_request_write_done(struct tevent_req *subreq);
+NTSTATUS smbd_smb2_request_process_write(struct smbd_smb2_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ NTSTATUS status;
+ const uint8_t *inbody;
+ uint16_t in_data_offset;
+ uint32_t in_data_length;
+ DATA_BLOB in_data_buffer;
+ uint64_t in_offset;
+ uint64_t in_file_id_persistent;
+ uint64_t in_file_id_volatile;
+ struct files_struct *in_fsp;
+ uint32_t in_flags;
+ size_t in_dyn_len = 0;
+ uint8_t *in_dyn_ptr = NULL;
+ struct tevent_req *subreq;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x31);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ in_data_offset = SVAL(inbody, 0x02);
+ in_data_length = IVAL(inbody, 0x04);
+ in_offset = BVAL(inbody, 0x08);
+ in_file_id_persistent = BVAL(inbody, 0x10);
+ in_file_id_volatile = BVAL(inbody, 0x18);
+ in_flags = IVAL(inbody, 0x2C);
+
+ if (in_data_offset != (SMB2_HDR_BODY + SMBD_SMB2_IN_BODY_LEN(req))) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ if (req->smb1req != NULL && req->smb1req->unread_bytes > 0) {
+ in_dyn_ptr = NULL;
+ in_dyn_len = req->smb1req->unread_bytes;
+ } else {
+ in_dyn_ptr = SMBD_SMB2_IN_DYN_PTR(req);
+ in_dyn_len = SMBD_SMB2_IN_DYN_LEN(req);
+ }
+
+ if (in_data_length > in_dyn_len) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ /* check the max write size */
+ if (in_data_length > xconn->smb2.server.max_write) {
+ DEBUG(2,("smbd_smb2_request_process_write : "
+ "client ignored max write :%s: 0x%08X: 0x%08X\n",
+ __location__, in_data_length, xconn->smb2.server.max_write));
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ /*
+ * Note: that in_dyn_ptr is NULL for the recvfile case.
+ */
+ in_data_buffer.data = in_dyn_ptr;
+ in_data_buffer.length = in_data_length;
+
+ status = smbd_smb2_request_verify_creditcharge(req, in_data_length);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ in_fsp = file_fsp_smb2(req, in_file_id_persistent, in_file_id_volatile);
+ if (in_fsp == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED);
+ }
+
+ subreq = smbd_smb2_write_send(req, req->sconn->ev_ctx,
+ req, in_fsp,
+ in_data_buffer,
+ in_offset,
+ in_flags);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_write_done, req);
+
+ return smbd_smb2_request_pending_queue(req, subreq, 500);
+}
+
+static void smbd_smb2_request_write_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *req = tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ DATA_BLOB outbody;
+ DATA_BLOB outdyn;
+ uint32_t out_count = 0;
+ NTSTATUS status;
+ NTSTATUS error; /* transport error */
+
+ status = smbd_smb2_write_recv(subreq, &out_count);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ error = smbd_smb2_request_error(req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ outbody = smbd_smb2_generate_outbody(req, 0x10);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x10 + 1); /* struct size */
+ SSVAL(outbody.data, 0x02, 0); /* reserved */
+ SIVAL(outbody.data, 0x04, out_count); /* count */
+ SIVAL(outbody.data, 0x08, 0); /* remaining */
+ SSVAL(outbody.data, 0x0C, 0); /* write channel info offset */
+ SSVAL(outbody.data, 0x0E, 0); /* write channel info length */
+
+ outdyn = data_blob_const(NULL, 0);
+
+ error = smbd_smb2_request_done(req, outbody, &outdyn);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn, nt_errstr(error));
+ return;
+ }
+}
+
+struct smbd_smb2_write_state {
+ struct smbd_smb2_request *smb2req;
+ struct smb_request *smbreq;
+ files_struct *fsp;
+ bool write_through;
+ uint32_t in_length;
+ uint64_t in_offset;
+ uint32_t out_count;
+};
+
+static void smbd_smb2_write_pipe_done(struct tevent_req *subreq);
+
+static NTSTATUS smb2_write_complete_internal(struct tevent_req *req,
+ ssize_t nwritten, int err,
+ bool do_sync)
+{
+ NTSTATUS status;
+ struct smbd_smb2_write_state *state = tevent_req_data(req,
+ struct smbd_smb2_write_state);
+ files_struct *fsp = state->fsp;
+
+ if (nwritten == -1) {
+ if (err == EOVERFLOW && fsp_is_alternate_stream(fsp)) {
+ status = NT_STATUS_FILE_SYSTEM_LIMITATION;
+ } else {
+ status = map_nt_error_from_unix(err);
+ }
+
+ DEBUG(2, ("smb2_write failed: %s, file %s, "
+ "length=%lu offset=%lu nwritten=-1: %s\n",
+ fsp_fnum_dbg(fsp),
+ fsp_str_dbg(fsp),
+ (unsigned long)state->in_length,
+ (unsigned long)state->in_offset,
+ nt_errstr(status)));
+
+ return status;
+ }
+
+ DEBUG(3,("smb2: %s, file %s, "
+ "length=%lu offset=%lu wrote=%lu\n",
+ fsp_fnum_dbg(fsp),
+ fsp_str_dbg(fsp),
+ (unsigned long)state->in_length,
+ (unsigned long)state->in_offset,
+ (unsigned long)nwritten));
+
+ if ((nwritten == 0) && (state->in_length != 0)) {
+ DEBUG(5,("smb2: write [%s] disk full\n",
+ fsp_str_dbg(fsp)));
+ return NT_STATUS_DISK_FULL;
+ }
+
+ if (do_sync) {
+ status = sync_file(fsp->conn, fsp, state->write_through);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("smb2: sync_file for %s returned %s\n",
+ fsp_str_dbg(fsp),
+ nt_errstr(status)));
+ return status;
+ }
+ }
+
+ state->out_count = nwritten;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smb2_write_complete(struct tevent_req *req, ssize_t nwritten, int err)
+{
+ return smb2_write_complete_internal(req, nwritten, err, true);
+}
+
+NTSTATUS smb2_write_complete_nosync(struct tevent_req *req, ssize_t nwritten,
+ int err)
+{
+ return smb2_write_complete_internal(req, nwritten, err, false);
+}
+
+
+static bool smbd_smb2_write_cancel(struct tevent_req *req)
+{
+ struct smbd_smb2_write_state *state =
+ tevent_req_data(req,
+ struct smbd_smb2_write_state);
+
+ return cancel_smb2_aio(state->smbreq);
+}
+
+static struct tevent_req *smbd_smb2_write_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *fsp,
+ DATA_BLOB in_data,
+ uint64_t in_offset,
+ uint32_t in_flags)
+{
+ NTSTATUS status;
+ struct tevent_req *req = NULL;
+ struct smbd_smb2_write_state *state = NULL;
+ struct smb_request *smbreq = NULL;
+ connection_struct *conn = smb2req->tcon->compat;
+ ssize_t nwritten;
+ struct lock_struct lock;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_write_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->smb2req = smb2req;
+ if (smb2req->xconn->protocol >= PROTOCOL_SMB3_02) {
+ if (in_flags & SMB2_WRITEFLAG_WRITE_UNBUFFERED) {
+ state->write_through = true;
+ }
+ }
+ if (in_flags & SMB2_WRITEFLAG_WRITE_THROUGH) {
+ state->write_through = true;
+ }
+ state->in_length = in_data.length;
+ state->in_offset = in_offset;
+ state->out_count = 0;
+
+ DEBUG(10,("smbd_smb2_write: %s - %s\n",
+ fsp_str_dbg(fsp), fsp_fnum_dbg(fsp)));
+
+ smbreq = smbd_smb2_fake_smb_request(smb2req, fsp);
+ if (tevent_req_nomem(smbreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->smbreq = smbreq;
+
+ state->fsp = fsp;
+
+ if (IS_IPC(smbreq->conn)) {
+ struct tevent_req *subreq = NULL;
+ bool ok;
+
+ if (!fsp_is_np(fsp)) {
+ tevent_req_nterror(req, NT_STATUS_FILE_CLOSED);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = np_write_send(state, ev,
+ fsp->fake_file_handle,
+ in_data.data,
+ in_data.length);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ smbd_smb2_write_pipe_done,
+ req);
+
+ /*
+ * Make sure we mark the fsp as having outstanding async
+ * activity so we don't crash on shutdown close.
+ */
+
+ ok = aio_add_req_to_fsp(fsp, req);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+ }
+
+ status = check_any_access_fsp(fsp, FILE_WRITE_DATA|FILE_APPEND_DATA);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ /* Try and do an asynchronous write. */
+ status = schedule_aio_smb2_write(conn,
+ smbreq,
+ fsp,
+ in_offset,
+ in_data,
+ state->write_through);
+
+ if (NT_STATUS_IS_OK(status)) {
+ /*
+ * Doing an async write, allow this
+ * request to be canceled
+ */
+ tevent_req_set_cancel_fn(req, smbd_smb2_write_cancel);
+ return req;
+ }
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
+ /* Real error in setting up aio. Fail. */
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ /* Fallback to synchronous. */
+ init_strict_lock_struct(fsp,
+ fsp->op->global->open_persistent_id,
+ in_offset,
+ in_data.length,
+ WRITE_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &lock);
+
+ if (!SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, &lock)) {
+ tevent_req_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * Note: in_data.data is NULL for the recvfile case.
+ */
+ nwritten = write_file(smbreq, fsp,
+ (const char *)in_data.data,
+ in_offset,
+ in_data.length);
+
+ status = smb2_write_complete(req, nwritten, errno);
+
+ DEBUG(10,("smb2: write on "
+ "file %s, offset %.0f, requested %u, written = %u\n",
+ fsp_str_dbg(fsp),
+ (double)in_offset,
+ (unsigned int)in_data.length,
+ (unsigned int)nwritten ));
+
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ } else {
+ /* Success. */
+ tevent_req_done(req);
+ }
+
+ return tevent_req_post(req, ev);
+}
+
+static void smbd_smb2_write_pipe_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smbd_smb2_write_state *state = tevent_req_data(req,
+ struct smbd_smb2_write_state);
+ NTSTATUS status;
+ ssize_t nwritten = -1;
+
+ status = np_write_recv(subreq, &nwritten);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ NTSTATUS old = status;
+ status = nt_status_np_pipe(old);
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ if ((nwritten == 0 && state->in_length != 0) || (nwritten < 0)) {
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+
+ state->out_count = nwritten;
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS smbd_smb2_write_recv(struct tevent_req *req,
+ uint32_t *out_count)
+{
+ NTSTATUS status;
+ struct smbd_smb2_write_state *state = tevent_req_data(req,
+ struct smbd_smb2_write_state);
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *out_count = state->out_count;
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/smbXsrv_client.c b/source3/smbd/smbXsrv_client.c
new file mode 100644
index 0000000..27df107
--- /dev/null
+++ b/source3/smbd/smbXsrv_client.c
@@ -0,0 +1,1458 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Stefan Metzmacher 2014
+
+ 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 "system/filesys.h"
+#include <tevent.h>
+#include "lib/util/server_id.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_rbt.h"
+#include "dbwrap/dbwrap_open.h"
+#include "dbwrap/dbwrap_watch.h"
+#include "session.h"
+#include "auth.h"
+#include "auth/gensec/gensec.h"
+#include "../lib/tsocket/tsocket.h"
+#include "../libcli/security/security.h"
+#include "messages.h"
+#include "lib/util/util_tdb.h"
+#include "librpc/gen_ndr/ndr_smbXsrv.h"
+#include "serverid.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "lib/util/iov_buf.h"
+#include "lib/global_contexts.h"
+#include "source3/include/util_tdb.h"
+
+struct smbXsrv_client_table {
+ struct {
+ uint32_t max_clients;
+ uint32_t num_clients;
+ } local;
+ struct {
+ struct db_context *db_ctx;
+ } global;
+};
+
+static struct db_context *smbXsrv_client_global_db_ctx = NULL;
+
+NTSTATUS smbXsrv_client_global_init(void)
+{
+ const char *global_path = NULL;
+ struct db_context *backend = NULL;
+ struct db_context *db_ctx = NULL;
+
+ if (smbXsrv_client_global_db_ctx != NULL) {
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * This contains secret information like client keys!
+ */
+ global_path = lock_path(talloc_tos(), "smbXsrv_client_global.tdb");
+ if (global_path == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ backend = db_open(NULL, global_path,
+ 0, /* hash_size */
+ TDB_DEFAULT |
+ TDB_CLEAR_IF_FIRST |
+ TDB_INCOMPATIBLE_HASH,
+ O_RDWR | O_CREAT, 0600,
+ DBWRAP_LOCK_ORDER_1,
+ DBWRAP_FLAG_NONE);
+ if (backend == NULL) {
+ NTSTATUS status;
+
+ status = map_nt_error_from_unix_common(errno);
+
+ return status;
+ }
+
+ db_ctx = db_open_watched(NULL, &backend, global_messaging_context());
+ if (db_ctx == NULL) {
+ TALLOC_FREE(backend);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ smbXsrv_client_global_db_ctx = db_ctx;
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * NOTE:
+ * We need to store the keys in big endian so that dbwrap_rbt's memcmp
+ * has the same result as integer comparison between the uint32_t
+ * values.
+ *
+ * TODO: implement string based key
+ */
+
+#define SMBXSRV_CLIENT_GLOBAL_TDB_KEY_SIZE 16
+
+static TDB_DATA smbXsrv_client_global_id_to_key(const struct GUID *client_guid,
+ uint8_t *key_buf)
+{
+ TDB_DATA key = { .dsize = 0, };
+ NTSTATUS status;
+ struct GUID_ndr_buf buf = { .buf = {0}, };
+
+ status = GUID_to_ndr_buf(client_guid, &buf);
+ if (!NT_STATUS_IS_OK(status)) {
+ return key;
+ }
+ memcpy(key_buf, buf.buf, SMBXSRV_CLIENT_GLOBAL_TDB_KEY_SIZE);
+
+ key = make_tdb_data(key_buf, SMBXSRV_CLIENT_GLOBAL_TDB_KEY_SIZE);
+
+ return key;
+}
+
+static struct db_record *smbXsrv_client_global_fetch_locked(
+ struct db_context *db,
+ const struct GUID *client_guid,
+ TALLOC_CTX *mem_ctx)
+{
+ TDB_DATA key;
+ uint8_t key_buf[SMBXSRV_CLIENT_GLOBAL_TDB_KEY_SIZE];
+ struct db_record *rec = NULL;
+
+ key = smbXsrv_client_global_id_to_key(client_guid, key_buf);
+
+ rec = dbwrap_fetch_locked(db, mem_ctx, key);
+
+ if (rec == NULL) {
+ struct GUID_txt_buf buf;
+ DBG_DEBUG("Failed to lock guid [%s], key '%s'\n",
+ GUID_buf_string(client_guid, &buf),
+ tdb_data_dbg(key));
+ }
+
+ return rec;
+}
+
+static NTSTATUS smbXsrv_client_table_create(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx,
+ uint32_t max_clients,
+ struct smbXsrv_client_table **_table)
+{
+ struct smbXsrv_client_table *table;
+ NTSTATUS status;
+
+ if (max_clients > 1) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ table = talloc_zero(mem_ctx, struct smbXsrv_client_table);
+ if (table == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ table->local.max_clients = max_clients;
+
+ status = smbXsrv_client_global_init();
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(table);
+ return status;
+ }
+
+ table->global.db_ctx = smbXsrv_client_global_db_ctx;
+
+ *_table = table;
+ return NT_STATUS_OK;
+}
+
+static int smbXsrv_client_global_destructor(struct smbXsrv_client_global0 *global)
+{
+ return 0;
+}
+
+static void smbXsrv_client_global_verify_record(struct db_record *db_rec,
+ bool *is_free,
+ bool *was_free,
+ TALLOC_CTX *mem_ctx,
+ const struct server_id *dead_server_id,
+ struct smbXsrv_client_global0 **_g,
+ uint32_t *pseqnum)
+{
+ TDB_DATA key;
+ TDB_DATA val;
+ DATA_BLOB blob;
+ struct smbXsrv_client_globalB global_blob;
+ enum ndr_err_code ndr_err;
+ struct smbXsrv_client_global0 *global = NULL;
+ bool dead = false;
+ bool exists;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ *is_free = false;
+
+ if (was_free) {
+ *was_free = false;
+ }
+ if (_g) {
+ *_g = NULL;
+ }
+ if (pseqnum) {
+ *pseqnum = 0;
+ }
+
+ key = dbwrap_record_get_key(db_rec);
+
+ val = dbwrap_record_get_value(db_rec);
+ if (val.dsize == 0) {
+ TALLOC_FREE(frame);
+ *is_free = true;
+ if (was_free) {
+ *was_free = true;
+ }
+ return;
+ }
+
+ blob = data_blob_const(val.dptr, val.dsize);
+
+ ndr_err = ndr_pull_struct_blob(&blob, frame, &global_blob,
+ (ndr_pull_flags_fn_t)ndr_pull_smbXsrv_client_globalB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("key '%s' ndr_pull_struct_blob - %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ TALLOC_FREE(frame);
+ return;
+ }
+
+ DBG_DEBUG("client_global:\n");
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ NDR_PRINT_DEBUG(smbXsrv_client_globalB, &global_blob);
+ }
+
+ if (global_blob.version != SMBXSRV_VERSION_0) {
+ DBG_ERR("key '%s' uses unsupported version %u\n",
+ tdb_data_dbg(key),
+ global_blob.version);
+ NDR_PRINT_DEBUG(smbXsrv_client_globalB, &global_blob);
+ TALLOC_FREE(frame);
+ return;
+ }
+
+ global = global_blob.info.info0;
+
+ dead = server_id_equal(dead_server_id, &global->server_id);
+ if (dead) {
+ struct server_id_buf tmp;
+
+ DBG_NOTICE("key '%s' server_id %s is already dead.\n",
+ tdb_data_dbg(key),
+ server_id_str_buf(global->server_id, &tmp));
+ if (DEBUGLVL(DBGLVL_NOTICE)) {
+ NDR_PRINT_DEBUG(smbXsrv_client_globalB, &global_blob);
+ }
+ TALLOC_FREE(frame);
+ dbwrap_record_delete(db_rec);
+ *is_free = true;
+ return;
+ }
+
+ exists = serverid_exists(&global->server_id);
+ if (!exists) {
+ struct server_id_buf tmp;
+
+ DBG_NOTICE("key '%s' server_id %s does not exist.\n",
+ tdb_data_dbg(key),
+ server_id_str_buf(global->server_id, &tmp));
+ if (DEBUGLVL(DBGLVL_NOTICE)) {
+ NDR_PRINT_DEBUG(smbXsrv_client_globalB, &global_blob);
+ }
+ TALLOC_FREE(frame);
+ dbwrap_record_delete(db_rec);
+ *is_free = true;
+ return;
+ }
+
+ if (_g) {
+ *_g = talloc_move(mem_ctx, &global);
+ }
+ if (pseqnum) {
+ *pseqnum = global_blob.seqnum;
+ }
+ TALLOC_FREE(frame);
+}
+
+static NTSTATUS smb2srv_client_connection_pass(struct smbd_smb2_request *smb2req,
+ struct smbXsrv_client_global0 *global)
+{
+ DATA_BLOB blob;
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+ struct smbXsrv_connection_pass0 pass_info0;
+ struct smbXsrv_connection_passB pass_blob;
+ ssize_t reqlen;
+ struct iovec iov;
+
+ pass_info0 = (struct smbXsrv_connection_pass0) {
+ .client_guid = global->client_guid,
+ .src_server_id = smb2req->xconn->client->global->server_id,
+ .xconn_connect_time = smb2req->xconn->client->global->initial_connect_time,
+ .dst_server_id = global->server_id,
+ .client_connect_time = global->initial_connect_time,
+ };
+
+ reqlen = iov_buflen(smb2req->in.vector, smb2req->in.vector_count);
+ if (reqlen == -1) {
+ return NT_STATUS_INVALID_BUFFER_SIZE;
+ }
+
+ pass_info0.negotiate_request.length = reqlen;
+ pass_info0.negotiate_request.data = talloc_array(talloc_tos(), uint8_t,
+ reqlen);
+ if (pass_info0.negotiate_request.data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ iov_buf(smb2req->in.vector, smb2req->in.vector_count,
+ pass_info0.negotiate_request.data,
+ pass_info0.negotiate_request.length);
+
+ ZERO_STRUCT(pass_blob);
+ pass_blob.version = smbXsrv_version_global_current();
+ pass_blob.info.info0 = &pass_info0;
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &pass_blob);
+ }
+
+ ndr_err = ndr_push_struct_blob(&blob, talloc_tos(), &pass_blob,
+ (ndr_push_flags_fn_t)ndr_push_smbXsrv_connection_passB);
+ data_blob_free(&pass_info0.negotiate_request);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ return status;
+ }
+
+ iov.iov_base = blob.data;
+ iov.iov_len = blob.length;
+
+ status = messaging_send_iov(smb2req->xconn->client->msg_ctx,
+ global->server_id,
+ MSG_SMBXSRV_CONNECTION_PASS,
+ &iov, 1,
+ &smb2req->xconn->transport.sock, 1);
+ data_blob_free(&blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smb2srv_client_connection_drop(struct smbd_smb2_request *smb2req,
+ struct smbXsrv_client_global0 *global)
+{
+ DATA_BLOB blob;
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+ struct smbXsrv_connection_drop0 drop_info0;
+ struct smbXsrv_connection_dropB drop_blob;
+ struct iovec iov;
+
+ drop_info0 = (struct smbXsrv_connection_drop0) {
+ .client_guid = global->client_guid,
+ .src_server_id = smb2req->xconn->client->global->server_id,
+ .xconn_connect_time = smb2req->xconn->client->global->initial_connect_time,
+ .dst_server_id = global->server_id,
+ .client_connect_time = global->initial_connect_time,
+ };
+
+ ZERO_STRUCT(drop_blob);
+ drop_blob.version = smbXsrv_version_global_current();
+ drop_blob.info.info0 = &drop_info0;
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ NDR_PRINT_DEBUG(smbXsrv_connection_dropB, &drop_blob);
+ }
+
+ ndr_err = ndr_push_struct_blob(&blob, talloc_tos(), &drop_blob,
+ (ndr_push_flags_fn_t)ndr_push_smbXsrv_connection_dropB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ return status;
+ }
+
+ iov.iov_base = blob.data;
+ iov.iov_len = blob.length;
+
+ status = messaging_send_iov(smb2req->xconn->client->msg_ctx,
+ global->server_id,
+ MSG_SMBXSRV_CONNECTION_DROP,
+ &iov, 1,
+ NULL, 0);
+ data_blob_free(&blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbXsrv_client_global_store(struct smbXsrv_client_global0 *global)
+{
+ struct smbXsrv_client_globalB global_blob;
+ DATA_BLOB blob = data_blob_null;
+ TDB_DATA key;
+ TDB_DATA val;
+ NTSTATUS status;
+ enum ndr_err_code ndr_err;
+ bool saved_stored = global->stored;
+
+ /*
+ * TODO: if we use other versions than '0'
+ * we would add glue code here, that would be able to
+ * store the information in the old format.
+ */
+
+ SMB_ASSERT(global->local_address != NULL);
+ SMB_ASSERT(global->remote_address != NULL);
+ SMB_ASSERT(global->remote_name != NULL);
+
+ if (global->db_rec == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ key = dbwrap_record_get_key(global->db_rec);
+ val = dbwrap_record_get_value(global->db_rec);
+
+ ZERO_STRUCT(global_blob);
+ global_blob.version = smbXsrv_version_global_current();
+ if (val.dsize >= 8) {
+ global_blob.seqnum = IVAL(val.dptr, 4);
+ }
+ global_blob.seqnum += 1;
+ global_blob.info.info0 = global;
+
+ global->stored = true;
+ ndr_err = ndr_push_struct_blob(&blob, global->db_rec, &global_blob,
+ (ndr_push_flags_fn_t)ndr_push_smbXsrv_client_globalB);
+ global->stored = saved_stored;
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("key '%s' ndr_push - %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ TALLOC_FREE(global->db_rec);
+ return status;
+ }
+
+ val = make_tdb_data(blob.data, blob.length);
+ status = dbwrap_record_store(global->db_rec, val, TDB_REPLACE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("key '%s' store - %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ TALLOC_FREE(global->db_rec);
+ return status;
+ }
+
+ global->stored = true;
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ DBG_DEBUG("key '%s' stored\n",
+ tdb_data_dbg(key));
+ NDR_PRINT_DEBUG(smbXsrv_client_globalB, &global_blob);
+ }
+
+ TALLOC_FREE(global->db_rec);
+
+ return NT_STATUS_OK;
+}
+
+struct smb2srv_client_mc_negprot_state {
+ struct tevent_context *ev;
+ struct smbd_smb2_request *smb2req;
+ struct db_record *db_rec;
+ struct server_id sent_server_id;
+ uint64_t watch_instance;
+ uint32_t last_seqnum;
+ struct tevent_req *filter_subreq;
+};
+
+static void smb2srv_client_mc_negprot_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state)
+{
+ struct smb2srv_client_mc_negprot_state *state =
+ tevent_req_data(req,
+ struct smb2srv_client_mc_negprot_state);
+
+ if (state->db_rec != NULL) {
+ dbwrap_watched_watch_remove_instance(state->db_rec,
+ state->watch_instance);
+ state->watch_instance = 0;
+ TALLOC_FREE(state->db_rec);
+ }
+}
+
+static void smb2srv_client_mc_negprot_next(struct tevent_req *req);
+static bool smb2srv_client_mc_negprot_filter(struct messaging_rec *rec, void *private_data);
+static void smb2srv_client_mc_negprot_done(struct tevent_req *subreq);
+static void smb2srv_client_mc_negprot_watched(struct tevent_req *subreq);
+
+struct tevent_req *smb2srv_client_mc_negprot_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req)
+{
+ struct tevent_req *req = NULL;
+ struct smb2srv_client_mc_negprot_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb2srv_client_mc_negprot_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->smb2req = smb2req;
+
+ tevent_req_set_cleanup_fn(req, smb2srv_client_mc_negprot_cleanup);
+
+ server_id_set_disconnected(&state->sent_server_id);
+
+ smb2srv_client_mc_negprot_next(req);
+
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void smb2srv_client_mc_negprot_next(struct tevent_req *req)
+{
+ struct smb2srv_client_mc_negprot_state *state =
+ tevent_req_data(req,
+ struct smb2srv_client_mc_negprot_state);
+ struct smbXsrv_connection *xconn = state->smb2req->xconn;
+ struct smbXsrv_client *client = xconn->client;
+ struct smbXsrv_client_table *table = client->table;
+ struct GUID client_guid = xconn->smb2.client.guid;
+ struct smbXsrv_client_global0 *global = NULL;
+ bool is_free = false;
+ struct tevent_req *subreq = NULL;
+ NTSTATUS status;
+ uint32_t seqnum = 0;
+ struct server_id last_server_id = { .pid = 0, };
+
+ SMB_ASSERT(state->db_rec == NULL);
+ state->db_rec = smbXsrv_client_global_fetch_locked(table->global.db_ctx,
+ &client_guid,
+ state);
+ if (state->db_rec == NULL) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_DB_ERROR);
+ return;
+ }
+
+verify_again:
+ TALLOC_FREE(global);
+
+ smbXsrv_client_global_verify_record(state->db_rec,
+ &is_free,
+ NULL,
+ state,
+ &last_server_id,
+ &global,
+ &seqnum);
+ if (is_free) {
+ dbwrap_watched_watch_remove_instance(state->db_rec,
+ state->watch_instance);
+ state->watch_instance = 0;
+
+ /*
+ * This stores the new client information in
+ * smbXsrv_client_global.tdb
+ */
+ client->global->client_guid = xconn->smb2.client.guid;
+
+ client->global->db_rec = state->db_rec;
+ state->db_rec = NULL;
+ status = smbXsrv_client_global_store(client->global);
+ SMB_ASSERT(client->global->db_rec == NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ struct GUID_txt_buf buf;
+ DBG_ERR("client_guid[%s] store failed - %s\n",
+ GUID_buf_string(&client->global->client_guid,
+ &buf),
+ nt_errstr(status));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ struct smbXsrv_clientB client_blob = {
+ .version = SMBXSRV_VERSION_0,
+ .info.info0 = client,
+ };
+ struct GUID_txt_buf buf;
+
+ DBG_DEBUG("client_guid[%s] stored\n",
+ GUID_buf_string(&client->global->client_guid,
+ &buf));
+ NDR_PRINT_DEBUG(smbXsrv_clientB, &client_blob);
+ }
+
+ xconn->smb2.client.guid_verified = true;
+ tevent_req_done(req);
+ return;
+ }
+
+ if (global == NULL) {
+ /*
+ * most likely ndr_pull_struct_blob() failed
+ */
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_DB_CORRUPTION);
+ return;
+ }
+
+ if (server_id_equal(&state->sent_server_id, &global->server_id)) {
+ /*
+ * We hit a race with other concurrent connections,
+ * which have woken us.
+ *
+ * We already sent the pass or drop message to
+ * the process, so we need to wait for a
+ * response and not pass the connection
+ * again! Otherwise the process would
+ * receive the same tcp connection via
+ * more than one file descriptor and
+ * create more than one smbXsrv_connection
+ * structure for the same tcp connection,
+ * which means the client would see more
+ * than one SMB2 negprot response to its
+ * single SMB2 netprot request and we
+ * as server get the session keys and
+ * message id validation wrong
+ */
+ goto watch_again;
+ }
+
+ server_id_set_disconnected(&state->sent_server_id);
+
+ /*
+ * If last_server_id is set, we expect
+ * smbXsrv_client_global_verify_record()
+ * to detect the already dead global->server_id
+ * as state->db_rec is still locked and its
+ * value didn't change.
+ */
+ SMB_ASSERT(last_server_id.pid == 0);
+ last_server_id = global->server_id;
+
+ TALLOC_FREE(state->filter_subreq);
+ if (procid_is_local(&global->server_id)) {
+ subreq = messaging_filtered_read_send(state,
+ state->ev,
+ client->msg_ctx,
+ smb2srv_client_mc_negprot_filter,
+ NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, smb2srv_client_mc_negprot_done, req);
+ state->filter_subreq = subreq;
+ }
+
+ if (procid_is_local(&global->server_id)) {
+ status = smb2srv_client_connection_pass(state->smb2req,
+ global);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ /*
+ * We remembered last_server_id = global->server_id
+ * above, so we'll treat it as dead in the
+ * next round to smbXsrv_client_global_verify_record().
+ */
+ goto verify_again;
+ }
+ state->sent_server_id = global->server_id;
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ } else {
+ status = smb2srv_client_connection_drop(state->smb2req,
+ global);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ /*
+ * We remembered last_server_id = global->server_id
+ * above, so we'll treat it as dead in the
+ * next round to smbXsrv_client_global_verify_record().
+ */
+ goto verify_again;
+ }
+ state->sent_server_id = global->server_id;
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ }
+
+watch_again:
+
+ /*
+ * If the record changed, but we are not happy with the change yet,
+ * we better remove ourself from the waiter list
+ * (most likely the first position)
+ * and re-add us at the end of the list.
+ *
+ * This gives other waiters a change
+ * to make progress.
+ *
+ * Otherwise we'll keep our waiter instance alive,
+ * keep waiting (most likely at first position).
+ * It means the order of watchers stays fair.
+ */
+ if (state->last_seqnum != seqnum) {
+ state->last_seqnum = seqnum;
+ dbwrap_watched_watch_remove_instance(state->db_rec,
+ state->watch_instance);
+ state->watch_instance =
+ dbwrap_watched_watch_add_instance(state->db_rec);
+ }
+
+ subreq = dbwrap_watched_watch_send(state,
+ state->ev,
+ state->db_rec,
+ state->watch_instance,
+ global->server_id);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, smb2srv_client_mc_negprot_watched, req);
+
+ TALLOC_FREE(global);
+ TALLOC_FREE(state->db_rec);
+ return;
+}
+
+static bool smb2srv_client_mc_negprot_filter(struct messaging_rec *rec, void *private_data)
+{
+ if (rec->msg_type != MSG_SMBXSRV_CONNECTION_PASSED) {
+ return false;
+ }
+
+ if (rec->num_fds != 0) {
+ return false;
+ }
+
+ return true;
+}
+
+static void smb2srv_client_mc_negprot_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smb2srv_client_mc_negprot_state *state =
+ tevent_req_data(req,
+ struct smb2srv_client_mc_negprot_state);
+ struct smbXsrv_connection *xconn = state->smb2req->xconn;
+ struct smbXsrv_client *client = xconn->client;
+ struct messaging_rec *rec = NULL;
+ struct smbXsrv_connection_passB passed_blob;
+ enum ndr_err_code ndr_err;
+ struct smbXsrv_connection_pass0 *passed_info0 = NULL;
+ NTSTATUS status;
+ int ret;
+
+ SMB_ASSERT(state->filter_subreq == subreq);
+ state->filter_subreq = NULL;
+
+ ret = messaging_filtered_read_recv(subreq, state, &rec);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ status = map_nt_error_from_unix_common(ret);
+ DBG_ERR("messaging_filtered_read_recv() - %s\n",
+ nt_errstr(status));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ DBG_DEBUG("MSG_SMBXSRV_CONNECTION_PASSED: received...\n");
+
+ ndr_err = ndr_pull_struct_blob(&rec->buf, rec, &passed_blob,
+ (ndr_pull_flags_fn_t)ndr_pull_smbXsrv_connection_passB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ DBG_ERR("ndr_pull_struct_blob - %s\n", nt_errstr(status));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &passed_blob);
+ }
+
+ if (passed_blob.version != SMBXSRV_VERSION_0) {
+ DBG_ERR("ignore invalid version %u\n", passed_blob.version);
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &passed_blob);
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ passed_info0 = passed_blob.info.info0;
+ if (passed_info0 == NULL) {
+ DBG_ERR("ignore NULL info %u\n", passed_blob.version);
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &passed_blob);
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ if (!GUID_equal(&xconn->smb2.client.guid, &passed_info0->client_guid)) {
+ struct GUID_txt_buf buf1, buf2;
+
+ DBG_ERR("client's client_guid [%s] != passed guid [%s]\n",
+ GUID_buf_string(&xconn->smb2.client.guid,
+ &buf1),
+ GUID_buf_string(&passed_info0->client_guid,
+ &buf2));
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &passed_blob);
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ if (client->global->initial_connect_time !=
+ passed_info0->xconn_connect_time)
+ {
+ DBG_ERR("client's initial connect time [%s] (%llu) != "
+ "passed xconn connect time [%s] (%llu)\n",
+ nt_time_string(talloc_tos(),
+ client->global->initial_connect_time),
+ (unsigned long long)client->global->initial_connect_time,
+ nt_time_string(talloc_tos(),
+ passed_info0->xconn_connect_time),
+ (unsigned long long)passed_info0->xconn_connect_time);
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &passed_blob);
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ if (passed_info0->negotiate_request.length != 0) {
+ DBG_ERR("negotiate_request.length[%zu]\n",
+ passed_info0->negotiate_request.length);
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &passed_blob);
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ tevent_req_nterror(req, NT_STATUS_MESSAGE_RETRIEVED);
+}
+
+static void smb2srv_client_mc_negprot_watched(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smb2srv_client_mc_negprot_state *state =
+ tevent_req_data(req,
+ struct smb2srv_client_mc_negprot_state);
+ NTSTATUS status;
+ uint64_t instance = 0;
+
+ status = dbwrap_watched_watch_recv(subreq, &instance, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->watch_instance = instance;
+
+ smb2srv_client_mc_negprot_next(req);
+}
+
+NTSTATUS smb2srv_client_mc_negprot_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static NTSTATUS smbXsrv_client_global_remove(struct smbXsrv_client_global0 *global)
+{
+ TDB_DATA key;
+ NTSTATUS status;
+
+ /*
+ * TODO: if we use other versions than '0'
+ * we would add glue code here, that would be able to
+ * store the information in the old format.
+ */
+
+ if (global->db_rec == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ key = dbwrap_record_get_key(global->db_rec);
+
+ status = dbwrap_record_delete(global->db_rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("key '%s' delete - %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ TALLOC_FREE(global->db_rec);
+ return status;
+ }
+ global->stored = false;
+ DBG_DEBUG("key '%s' delete\n", tdb_data_dbg(key));
+
+ TALLOC_FREE(global->db_rec);
+
+ return NT_STATUS_OK;
+}
+
+static int smbXsrv_client_destructor(struct smbXsrv_client *client)
+{
+ NTSTATUS status;
+
+ status = smbXsrv_client_remove(client);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smbXsrv_client_remove() failed: %s\n",
+ nt_errstr(status));
+ }
+
+ TALLOC_FREE(client->global);
+
+ return 0;
+}
+
+static bool smbXsrv_client_connection_pass_filter(struct messaging_rec *rec, void *private_data);
+static void smbXsrv_client_connection_pass_loop(struct tevent_req *subreq);
+static bool smbXsrv_client_connection_drop_filter(struct messaging_rec *rec, void *private_data);
+static void smbXsrv_client_connection_drop_loop(struct tevent_req *subreq);
+
+NTSTATUS smbXsrv_client_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ NTTIME now,
+ struct smbXsrv_client **_client)
+{
+ struct smbXsrv_client_table *table;
+ struct smbXsrv_client *client = NULL;
+ struct smbXsrv_client_global0 *global = NULL;
+ NTSTATUS status;
+ struct tevent_req *subreq = NULL;
+
+ status = smbXsrv_client_table_create(mem_ctx,
+ msg_ctx,
+ 1, /* max_clients */
+ &table);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (table->local.num_clients >= table->local.max_clients) {
+ TALLOC_FREE(table);
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ client = talloc_zero(mem_ctx, struct smbXsrv_client);
+ if (client == NULL) {
+ TALLOC_FREE(table);
+ return NT_STATUS_NO_MEMORY;
+ }
+ client->raw_ev_ctx = ev_ctx;
+ client->msg_ctx = msg_ctx;
+
+ client->server_multi_channel_enabled =
+ smbXsrv_server_multi_channel_enabled();
+ if (client->server_multi_channel_enabled) {
+ client->next_channel_id = 1;
+ }
+ client->table = talloc_move(client, &table);
+ table = client->table;
+
+ global = talloc_zero(client, struct smbXsrv_client_global0);
+ if (global == NULL) {
+ TALLOC_FREE(client);
+ return NT_STATUS_NO_MEMORY;
+ }
+ talloc_set_destructor(global, smbXsrv_client_global_destructor);
+ client->global = global;
+
+ global->initial_connect_time = now;
+
+ global->server_id = messaging_server_id(client->msg_ctx);
+
+ table->local.num_clients += 1;
+
+ talloc_set_destructor(client, smbXsrv_client_destructor);
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ struct smbXsrv_clientB client_blob = {
+ .version = SMBXSRV_VERSION_0,
+ .info.info0 = client,
+ };
+ struct GUID_txt_buf buf;
+
+ DBG_DEBUG("client_guid[%s] created\n",
+ GUID_buf_string(&global->client_guid, &buf));
+ NDR_PRINT_DEBUG(smbXsrv_clientB, &client_blob);
+ }
+
+ subreq = messaging_filtered_read_send(client,
+ client->raw_ev_ctx,
+ client->msg_ctx,
+ smbXsrv_client_connection_pass_filter,
+ client);
+ if (subreq == NULL) {
+ TALLOC_FREE(client);
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_req_set_callback(subreq, smbXsrv_client_connection_pass_loop, client);
+ client->connection_pass_subreq = subreq;
+
+ subreq = messaging_filtered_read_send(client,
+ client->raw_ev_ctx,
+ client->msg_ctx,
+ smbXsrv_client_connection_drop_filter,
+ client);
+ if (subreq == NULL) {
+ TALLOC_FREE(client);
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_req_set_callback(subreq, smbXsrv_client_connection_drop_loop, client);
+ client->connection_drop_subreq = subreq;
+
+ *_client = client;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smb2srv_client_connection_passed(struct smbXsrv_client *client,
+ const struct smbXsrv_connection_pass0 *recv_info0)
+{
+ DATA_BLOB blob;
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+ struct smbXsrv_connection_pass0 passed_info0;
+ struct smbXsrv_connection_passB passed_blob;
+ struct iovec iov;
+
+ /*
+ * We echo back the message with a cleared negotiate_request
+ */
+ passed_info0 = *recv_info0;
+ passed_info0.negotiate_request = data_blob_null;
+
+ ZERO_STRUCT(passed_blob);
+ passed_blob.version = smbXsrv_version_global_current();
+ passed_blob.info.info0 = &passed_info0;
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &passed_blob);
+ }
+
+ ndr_err = ndr_push_struct_blob(&blob, talloc_tos(), &passed_blob,
+ (ndr_push_flags_fn_t)ndr_push_smbXsrv_connection_passB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ return status;
+ }
+
+ iov.iov_base = blob.data;
+ iov.iov_len = blob.length;
+
+ status = messaging_send_iov(client->msg_ctx,
+ recv_info0->src_server_id,
+ MSG_SMBXSRV_CONNECTION_PASSED,
+ &iov, 1,
+ NULL, 0);
+ data_blob_free(&blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static bool smbXsrv_client_connection_pass_filter(struct messaging_rec *rec, void *private_data)
+{
+ if (rec->msg_type != MSG_SMBXSRV_CONNECTION_PASS) {
+ return false;
+ }
+
+ if (rec->num_fds != 1) {
+ return false;
+ }
+
+ return true;
+}
+
+static void smbXsrv_client_connection_pass_loop(struct tevent_req *subreq)
+{
+ struct smbXsrv_client *client =
+ tevent_req_callback_data(subreq,
+ struct smbXsrv_client);
+ struct smbXsrv_connection *xconn = NULL;
+ int ret;
+ struct messaging_rec *rec = NULL;
+ struct smbXsrv_connection_passB pass_blob;
+ enum ndr_err_code ndr_err;
+ struct smbXsrv_connection_pass0 *pass_info0 = NULL;
+ NTSTATUS status;
+ int sock_fd = -1;
+ uint64_t seq_low;
+
+ client->connection_pass_subreq = NULL;
+
+ ret = messaging_filtered_read_recv(subreq, talloc_tos(), &rec);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ goto next;
+ }
+
+ if (rec->num_fds != 1) {
+ DBG_ERR("MSG_SMBXSRV_CONNECTION_PASS: num_fds[%u]\n",
+ rec->num_fds);
+ goto next;
+ }
+
+ sock_fd = rec->fds[0];
+ DBG_DEBUG("MSG_SMBXSRV_CONNECTION_PASS: got sock_fd[%d]\n", sock_fd);
+
+ ndr_err = ndr_pull_struct_blob(&rec->buf, rec, &pass_blob,
+ (ndr_pull_flags_fn_t)ndr_pull_smbXsrv_connection_passB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("ndr_pull_struct_blob - %s\n", nt_errstr(status));
+ goto next;
+ }
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &pass_blob);
+ }
+
+ if (pass_blob.version != SMBXSRV_VERSION_0) {
+ DBG_ERR("ignore invalid version %u\n", pass_blob.version);
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &pass_blob);
+ goto next;
+ }
+
+ pass_info0 = pass_blob.info.info0;
+ if (pass_info0 == NULL) {
+ DBG_ERR("ignore NULL info %u\n", pass_blob.version);
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &pass_blob);
+ goto next;
+ }
+
+ if (!GUID_equal(&client->global->client_guid, &pass_info0->client_guid))
+ {
+ struct GUID_txt_buf buf1, buf2;
+
+ DBG_WARNING("client's client_guid [%s] != passed guid [%s]\n",
+ GUID_buf_string(&client->global->client_guid,
+ &buf1),
+ GUID_buf_string(&pass_info0->client_guid,
+ &buf2));
+ if (DEBUGLVL(DBGLVL_WARNING)) {
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &pass_blob);
+ }
+ goto next;
+ }
+
+ if (client->global->initial_connect_time !=
+ pass_info0->client_connect_time)
+ {
+ DBG_WARNING("client's initial connect time [%s] (%llu) != "
+ "passed initial connect time [%s] (%llu)\n",
+ nt_time_string(talloc_tos(),
+ client->global->initial_connect_time),
+ (unsigned long long)client->global->initial_connect_time,
+ nt_time_string(talloc_tos(),
+ pass_info0->client_connect_time),
+ (unsigned long long)pass_info0->client_connect_time);
+ if (DEBUGLVL(DBGLVL_WARNING)) {
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &pass_blob);
+ }
+ goto next;
+ }
+
+ if (pass_info0->negotiate_request.length < SMB2_HDR_BODY) {
+ DBG_WARNING("negotiate_request.length[%zu]\n",
+ pass_info0->negotiate_request.length);
+ if (DEBUGLVL(DBGLVL_WARNING)) {
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &pass_blob);
+ }
+ goto next;
+ }
+
+ status = smb2srv_client_connection_passed(client, pass_info0);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ /*
+ * We hit a race where, the client dropped the connection
+ * while the socket was passed to us and the origin
+ * process already existed.
+ */
+ DBG_DEBUG("smb2srv_client_connection_passed() ignore %s\n",
+ nt_errstr(status));
+ status = NT_STATUS_OK;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ const char *r = "smb2srv_client_connection_passed() failed";
+ DBG_ERR("%s => %s\n", r, nt_errstr(status));
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &pass_blob);
+ exit_server_cleanly(r);
+ return;
+ }
+
+ status = smbd_add_connection(client,
+ sock_fd,
+ pass_info0->xconn_connect_time,
+ &xconn);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED)) {
+ rec->num_fds = 0;
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smbd_add_connection => %s\n", nt_errstr(status));
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &pass_blob);
+ goto next;
+ }
+ rec->num_fds = 0;
+
+ /*
+ * Set seq_low to mid received in negprot
+ */
+ seq_low = BVAL(pass_info0->negotiate_request.data,
+ SMB2_HDR_MESSAGE_ID);
+
+ xconn->smb2.client.guid_verified = true;
+ smbd_smb2_process_negprot(xconn, seq_low,
+ pass_info0->negotiate_request.data,
+ pass_info0->negotiate_request.length);
+
+next:
+ if (rec != NULL) {
+ uint8_t fd_idx;
+
+ for (fd_idx = 0; fd_idx < rec->num_fds; fd_idx++) {
+ sock_fd = rec->fds[fd_idx];
+ close(sock_fd);
+ }
+ rec->num_fds = 0;
+
+ TALLOC_FREE(rec);
+ }
+
+ subreq = messaging_filtered_read_send(client,
+ client->raw_ev_ctx,
+ client->msg_ctx,
+ smbXsrv_client_connection_pass_filter,
+ client);
+ if (subreq == NULL) {
+ const char *r;
+ r = "messaging_read_send(MSG_SMBXSRV_CONNECTION_PASS failed";
+ exit_server_cleanly(r);
+ return;
+ }
+ tevent_req_set_callback(subreq, smbXsrv_client_connection_pass_loop, client);
+ client->connection_pass_subreq = subreq;
+}
+
+static bool smbXsrv_client_connection_drop_filter(struct messaging_rec *rec, void *private_data)
+{
+ if (rec->msg_type != MSG_SMBXSRV_CONNECTION_DROP) {
+ return false;
+ }
+
+ if (rec->num_fds != 0) {
+ return false;
+ }
+
+ return true;
+}
+
+static void smbXsrv_client_connection_drop_loop(struct tevent_req *subreq)
+{
+ struct smbXsrv_client *client =
+ tevent_req_callback_data(subreq,
+ struct smbXsrv_client);
+ int ret;
+ struct messaging_rec *rec = NULL;
+ struct smbXsrv_connection_dropB drop_blob;
+ enum ndr_err_code ndr_err;
+ struct smbXsrv_connection_drop0 *drop_info0 = NULL;
+ struct server_id_buf src_server_id_buf = {};
+ NTSTATUS status;
+
+ client->connection_drop_subreq = NULL;
+
+ ret = messaging_filtered_read_recv(subreq, talloc_tos(), &rec);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ goto next;
+ }
+
+ if (rec->num_fds != 0) {
+ DBG_ERR("MSG_SMBXSRV_CONNECTION_DROP: num_fds[%u]\n",
+ rec->num_fds);
+ goto next;
+ }
+
+ ndr_err = ndr_pull_struct_blob(&rec->buf, rec, &drop_blob,
+ (ndr_pull_flags_fn_t)ndr_pull_smbXsrv_connection_dropB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("ndr_pull_struct_blob - %s\n", nt_errstr(status));
+ goto next;
+ }
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ NDR_PRINT_DEBUG(smbXsrv_connection_dropB, &drop_blob);
+ }
+
+ if (drop_blob.version != SMBXSRV_VERSION_0) {
+ DBG_ERR("ignore invalid version %u\n", drop_blob.version);
+ NDR_PRINT_DEBUG(smbXsrv_connection_dropB, &drop_blob);
+ goto next;
+ }
+
+ drop_info0 = drop_blob.info.info0;
+ if (drop_info0 == NULL) {
+ DBG_ERR("ignore NULL info %u\n", drop_blob.version);
+ NDR_PRINT_DEBUG(smbXsrv_connection_dropB, &drop_blob);
+ goto next;
+ }
+
+ if (!GUID_equal(&client->global->client_guid, &drop_info0->client_guid))
+ {
+ struct GUID_txt_buf buf1, buf2;
+
+ DBG_WARNING("client's client_guid [%s] != dropped guid [%s]\n",
+ GUID_buf_string(&client->global->client_guid,
+ &buf1),
+ GUID_buf_string(&drop_info0->client_guid,
+ &buf2));
+ if (DEBUGLVL(DBGLVL_WARNING)) {
+ NDR_PRINT_DEBUG(smbXsrv_connection_dropB, &drop_blob);
+ }
+ goto next;
+ }
+
+ if (client->global->initial_connect_time !=
+ drop_info0->client_connect_time)
+ {
+ DBG_WARNING("client's initial connect time [%s] (%llu) != "
+ "dropped initial connect time [%s] (%llu)\n",
+ nt_time_string(talloc_tos(),
+ client->global->initial_connect_time),
+ (unsigned long long)client->global->initial_connect_time,
+ nt_time_string(talloc_tos(),
+ drop_info0->client_connect_time),
+ (unsigned long long)drop_info0->client_connect_time);
+ if (DEBUGLVL(DBGLVL_WARNING)) {
+ NDR_PRINT_DEBUG(smbXsrv_connection_dropB, &drop_blob);
+ }
+ goto next;
+ }
+
+ /*
+ * Disconnect all client connections, which means we will tear down all
+ * sessions, tcons and non-durable opens. At the end we will remove our
+ * smbXsrv_client_global.tdb record, which will wake up the watcher on
+ * the other node in order to let it take over the client.
+ *
+ * The client will have to reopen all sessions, tcons and durable opens.
+ */
+ smbd_server_disconnect_client(client,
+ server_id_str_buf(drop_info0->src_server_id, &src_server_id_buf));
+ return;
+
+next:
+ if (rec != NULL) {
+ int sock_fd;
+ uint8_t fd_idx;
+
+ for (fd_idx = 0; fd_idx < rec->num_fds; fd_idx++) {
+ sock_fd = rec->fds[fd_idx];
+ close(sock_fd);
+ }
+ rec->num_fds = 0;
+
+ TALLOC_FREE(rec);
+ }
+
+ subreq = messaging_filtered_read_send(client,
+ client->raw_ev_ctx,
+ client->msg_ctx,
+ smbXsrv_client_connection_drop_filter,
+ client);
+ if (subreq == NULL) {
+ const char *r;
+ r = "messaging_read_send(MSG_SMBXSRV_CONNECTION_DROP failed";
+ exit_server_cleanly(r);
+ return;
+ }
+ tevent_req_set_callback(subreq, smbXsrv_client_connection_drop_loop, client);
+ client->connection_drop_subreq = subreq;
+}
+
+NTSTATUS smbXsrv_client_remove(struct smbXsrv_client *client)
+{
+ struct smbXsrv_client_table *table = client->table;
+ NTSTATUS status;
+
+ if (client->global->db_rec != NULL) {
+ struct GUID_txt_buf buf;
+ DBG_ERR("client_guid[%s]: Called with db_rec != NULL'\n",
+ GUID_buf_string(&client->global->client_guid,
+ &buf));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (!client->global->stored) {
+ return NT_STATUS_OK;
+ }
+
+ TALLOC_FREE(client->connection_pass_subreq);
+ TALLOC_FREE(client->connection_drop_subreq);
+
+ client->global->db_rec = smbXsrv_client_global_fetch_locked(
+ table->global.db_ctx,
+ &client->global->client_guid,
+ client->global /* TALLOC_CTX */);
+ if (client->global->db_rec == NULL) {
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ status = smbXsrv_client_global_remove(client->global);
+ if (!NT_STATUS_IS_OK(status)) {
+ struct GUID_txt_buf buf;
+ DBG_ERR("client_guid[%s] store failed - %s\n",
+ GUID_buf_string(&client->global->client_guid, &buf),
+ nt_errstr(status));
+ return status;
+ }
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ struct smbXsrv_clientB client_blob = {
+ .version = SMBXSRV_VERSION_0,
+ .info.info0 = client,
+ };
+ struct GUID_txt_buf buf;
+
+ DBG_DEBUG("client_guid[%s] stored\n",
+ GUID_buf_string(&client->global->client_guid, &buf));
+ NDR_PRINT_DEBUG(smbXsrv_clientB, &client_blob);
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/smbXsrv_open.c b/source3/smbd/smbXsrv_open.c
new file mode 100644
index 0000000..efabb73
--- /dev/null
+++ b/source3/smbd/smbXsrv_open.c
@@ -0,0 +1,1526 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Stefan Metzmacher 2012
+ Copyright (C) Michael Adam 2012
+
+ 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 "smbXsrv_open.h"
+#include "includes.h"
+#include "system/filesys.h"
+#include "lib/util/server_id.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_rbt.h"
+#include "dbwrap/dbwrap_open.h"
+#include "../libcli/security/security.h"
+#include "messages.h"
+#include "lib/util/util_tdb.h"
+#include "librpc/gen_ndr/ndr_smbXsrv.h"
+#include "serverid.h"
+#include "source3/include/util_tdb.h"
+#include "lib/util/idtree_random.h"
+#include "lib/util/time_basic.h"
+
+struct smbXsrv_open_table {
+ struct {
+ struct idr_context *idr;
+ struct db_context *replay_cache_db_ctx;
+ uint32_t lowest_id;
+ uint32_t highest_id;
+ uint32_t max_opens;
+ uint32_t num_opens;
+ } local;
+ struct {
+ struct db_context *db_ctx;
+ } global;
+};
+
+static struct db_context *smbXsrv_open_global_db_ctx = NULL;
+
+NTSTATUS smbXsrv_open_global_init(void)
+{
+ char *global_path = NULL;
+ struct db_context *db_ctx = NULL;
+
+ if (smbXsrv_open_global_db_ctx != NULL) {
+ return NT_STATUS_OK;
+ }
+
+ global_path = lock_path(talloc_tos(), "smbXsrv_open_global.tdb");
+ if (global_path == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ db_ctx = db_open(NULL, global_path,
+ SMBD_VOLATILE_TDB_HASH_SIZE,
+ SMBD_VOLATILE_TDB_FLAGS,
+ O_RDWR | O_CREAT, 0600,
+ DBWRAP_LOCK_ORDER_1,
+ DBWRAP_FLAG_NONE);
+ TALLOC_FREE(global_path);
+ if (db_ctx == NULL) {
+ NTSTATUS status;
+
+ status = map_nt_error_from_unix_common(errno);
+
+ return status;
+ }
+
+ smbXsrv_open_global_db_ctx = db_ctx;
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * NOTE:
+ * We need to store the keys in big endian so that dbwrap_rbt's memcmp
+ * has the same result as integer comparison between the uint32_t
+ * values.
+ *
+ * TODO: implement string based key
+ */
+
+struct smbXsrv_open_global_key_buf { uint8_t buf[sizeof(uint32_t)]; };
+
+static TDB_DATA smbXsrv_open_global_id_to_key(
+ uint32_t id, struct smbXsrv_open_global_key_buf *key_buf)
+{
+ RSIVAL(key_buf->buf, 0, id);
+
+ return (TDB_DATA) {
+ .dptr = key_buf->buf,
+ .dsize = sizeof(key_buf->buf),
+ };
+}
+
+static NTSTATUS smbXsrv_open_table_init(struct smbXsrv_connection *conn,
+ uint32_t lowest_id,
+ uint32_t highest_id,
+ uint32_t max_opens)
+{
+ struct smbXsrv_client *client = conn->client;
+ struct smbXsrv_open_table *table;
+ NTSTATUS status;
+ uint64_t max_range;
+
+ if (lowest_id > highest_id) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ max_range = highest_id;
+ max_range -= lowest_id;
+ max_range += 1;
+
+ if (max_opens > max_range) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ table = talloc_zero(client, struct smbXsrv_open_table);
+ if (table == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ table->local.idr = idr_init(table);
+ if (table->local.idr == NULL) {
+ TALLOC_FREE(table);
+ return NT_STATUS_NO_MEMORY;
+ }
+ table->local.replay_cache_db_ctx = db_open_rbt(table);
+ if (table->local.replay_cache_db_ctx == NULL) {
+ TALLOC_FREE(table);
+ return NT_STATUS_NO_MEMORY;
+ }
+ table->local.lowest_id = lowest_id;
+ table->local.highest_id = highest_id;
+ table->local.max_opens = max_opens;
+
+ status = smbXsrv_open_global_init();
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(table);
+ return status;
+ }
+
+ table->global.db_ctx = smbXsrv_open_global_db_ctx;
+
+ client->open_table = table;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbXsrv_open_local_lookup(struct smbXsrv_open_table *table,
+ uint32_t open_local_id,
+ uint32_t open_global_id,
+ NTTIME now,
+ struct smbXsrv_open **_open)
+{
+ struct smbXsrv_open *op = NULL;
+
+ *_open = NULL;
+
+ if (open_local_id == 0) {
+ return NT_STATUS_FILE_CLOSED;
+ }
+
+ if (table == NULL) {
+ /* this might happen before the end of negprot */
+ return NT_STATUS_FILE_CLOSED;
+ }
+
+ if (table->local.idr == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ op = idr_find(table->local.idr, open_local_id);
+ if (op == NULL) {
+ return NT_STATUS_FILE_CLOSED;
+ }
+
+ if (open_global_id == 0) {
+ /* make the global check a no-op for SMB1 */
+ open_global_id = op->global->open_global_id;
+ }
+
+ if (op->global->open_global_id != open_global_id) {
+ return NT_STATUS_FILE_CLOSED;
+ }
+
+ if (now != 0) {
+ op->idle_time = now;
+ }
+
+ *_open = op;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbXsrv_open_global_parse_record(
+ TALLOC_CTX *mem_ctx,
+ TDB_DATA key,
+ TDB_DATA val,
+ struct smbXsrv_open_global0 **global)
+{
+ DATA_BLOB blob = data_blob_const(val.dptr, val.dsize);
+ struct smbXsrv_open_globalB global_blob;
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ndr_err = ndr_pull_struct_blob(&blob, frame, &global_blob,
+ (ndr_pull_flags_fn_t)ndr_pull_smbXsrv_open_globalB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(1,("Invalid record in smbXsrv_open_global.tdb:"
+ "key '%s' ndr_pull_struct_blob - %s\n",
+ tdb_data_dbg(key),
+ ndr_errstr(ndr_err)));
+ status = ndr_map_error2ntstatus(ndr_err);
+ goto done;
+ }
+
+ DBG_DEBUG("\n");
+ if (CHECK_DEBUGLVL(10)) {
+ NDR_PRINT_DEBUG(smbXsrv_open_globalB, &global_blob);
+ }
+
+ if (global_blob.version != SMBXSRV_VERSION_0) {
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ DEBUG(1,("Invalid record in smbXsrv_open_global.tdb:"
+ "key '%s' unsupported version - %d - %s\n",
+ tdb_data_dbg(key),
+ (int)global_blob.version,
+ nt_errstr(status)));
+ goto done;
+ }
+
+ if (global_blob.info.info0 == NULL) {
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ DEBUG(1,("Invalid record in smbXsrv_open_global.tdb:"
+ "key '%s' info0 NULL pointer - %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status)));
+ goto done;
+ }
+
+ *global = talloc_move(mem_ctx, &global_blob.info.info0);
+ status = NT_STATUS_OK;
+done:
+ talloc_free(frame);
+ return status;
+}
+
+static NTSTATUS smbXsrv_open_global_verify_record(
+ TDB_DATA key,
+ TDB_DATA val,
+ TALLOC_CTX *mem_ctx,
+ struct smbXsrv_open_global0 **_global0)
+{
+ struct smbXsrv_open_global0 *global0 = NULL;
+ struct server_id_buf buf;
+ NTSTATUS status;
+
+ if (val.dsize == 0) {
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ status = smbXsrv_open_global_parse_record(mem_ctx, key, val, &global0);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("smbXsrv_open_global_parse_record for %s failed: "
+ "%s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ return status;
+ }
+ *_global0 = global0;
+
+ if (server_id_is_disconnected(&global0->server_id)) {
+ return NT_STATUS_OK;
+ }
+ if (serverid_exists(&global0->server_id)) {
+ return NT_STATUS_OK;
+ }
+
+ DBG_WARNING("smbd %s did not clean up record %s\n",
+ server_id_str_buf(global0->server_id, &buf),
+ tdb_data_dbg(key));
+
+ return NT_STATUS_FATAL_APP_EXIT;
+}
+
+static NTSTATUS smbXsrv_open_global_store(
+ struct db_record *rec,
+ TDB_DATA key,
+ TDB_DATA oldval,
+ struct smbXsrv_open_global0 *global)
+{
+ struct smbXsrv_open_globalB global_blob;
+ DATA_BLOB blob = data_blob_null;
+ TDB_DATA val = { .dptr = NULL, };
+ NTSTATUS status;
+ enum ndr_err_code ndr_err;
+
+ /*
+ * TODO: if we use other versions than '0'
+ * we would add glue code here, that would be able to
+ * store the information in the old format.
+ */
+
+ global_blob = (struct smbXsrv_open_globalB) {
+ .version = smbXsrv_version_global_current(),
+ };
+
+ if (oldval.dsize >= 8) {
+ global_blob.seqnum = IVAL(oldval.dptr, 4);
+ }
+ global_blob.seqnum += 1;
+ global_blob.info.info0 = global;
+
+ ndr_err = ndr_push_struct_blob(&blob, talloc_tos(), &global_blob,
+ (ndr_push_flags_fn_t)ndr_push_smbXsrv_open_globalB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_WARNING("key '%s' ndr_push - %s\n",
+ tdb_data_dbg(key),
+ ndr_map_error2string(ndr_err));
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ val = make_tdb_data(blob.data, blob.length);
+ status = dbwrap_record_store(rec, val, TDB_REPLACE);
+ TALLOC_FREE(blob.data);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("key '%s' store - %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ return status;
+ }
+
+ if (CHECK_DEBUGLVL(10)) {
+ DBG_DEBUG("key '%s' stored\n", tdb_data_dbg(key));
+ NDR_PRINT_DEBUG(smbXsrv_open_globalB, &global_blob);
+ }
+
+ return NT_STATUS_OK;
+}
+
+struct smbXsrv_open_global_allocate_state {
+ uint32_t id;
+ struct smbXsrv_open_global0 *global;
+ NTSTATUS status;
+};
+
+static void smbXsrv_open_global_allocate_fn(
+ struct db_record *rec, TDB_DATA oldval, void *private_data)
+{
+ struct smbXsrv_open_global_allocate_state *state = private_data;
+ struct smbXsrv_open_global0 *global = state->global;
+ struct smbXsrv_open_global0 *tmp_global0 = NULL;
+ TDB_DATA key = dbwrap_record_get_key(rec);
+
+ state->status = smbXsrv_open_global_verify_record(
+ key, oldval, talloc_tos(), &tmp_global0);
+
+ if (NT_STATUS_IS_OK(state->status)) {
+ /*
+ * Found an existing record
+ */
+ TALLOC_FREE(tmp_global0);
+ state->status = NT_STATUS_RETRY;
+ return;
+ }
+
+ if (NT_STATUS_EQUAL(state->status, NT_STATUS_NOT_FOUND)) {
+ /*
+ * Found an empty slot
+ */
+ global->open_global_id = state->id;
+ global->open_persistent_id = state->id;
+
+ state->status = smbXsrv_open_global_store(
+ rec, key, (TDB_DATA) { .dsize = 0, }, state->global);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ DBG_WARNING("smbXsrv_open_global_store() for "
+ "id %"PRIu32" failed: %s\n",
+ state->id,
+ nt_errstr(state->status));
+ }
+ return;
+ }
+
+ if (NT_STATUS_EQUAL(state->status, NT_STATUS_FATAL_APP_EXIT)) {
+ NTSTATUS status;
+
+ TALLOC_FREE(tmp_global0);
+
+ /*
+ * smbd crashed
+ */
+ status = dbwrap_record_delete(rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("dbwrap_record_delete() failed "
+ "for record %"PRIu32": %s\n",
+ state->id,
+ nt_errstr(status));
+ state->status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ return;
+ }
+ return;
+ }
+}
+
+static NTSTATUS smbXsrv_open_global_allocate(
+ struct db_context *db, struct smbXsrv_open_global0 *global)
+{
+ struct smbXsrv_open_global_allocate_state state = {
+ .global = global,
+ };
+ uint32_t i;
+ uint32_t last_free = 0;
+ const uint32_t min_tries = 3;
+
+ /*
+ * Here we just randomly try the whole 32-bit space
+ *
+ * We use just 32-bit, because we want to reuse the
+ * ID for SRVSVC.
+ */
+ for (i = 0; i < UINT32_MAX; i++) {
+ struct smbXsrv_open_global_key_buf key_buf;
+ TDB_DATA key;
+ NTSTATUS status;
+
+ if (i >= min_tries && last_free != 0) {
+ state.id = last_free;
+ } else {
+ generate_nonce_buffer(
+ (uint8_t *)&state.id, sizeof(state.id));
+ state.id = MAX(state.id, 1);
+ state.id = MIN(state.id, UINT32_MAX-1);
+ }
+
+ key = smbXsrv_open_global_id_to_key(state.id, &key_buf);
+
+ status = dbwrap_do_locked(
+ db, key, smbXsrv_open_global_allocate_fn, &state);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("dbwrap_do_locked() failed: %s\n",
+ nt_errstr(status));
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ if (NT_STATUS_IS_OK(state.status)) {
+ /*
+ * Found an empty slot, done.
+ */
+ DBG_DEBUG("Found slot %"PRIu32"\n", state.id);
+ return NT_STATUS_OK;
+ }
+
+ if (NT_STATUS_EQUAL(state.status, NT_STATUS_FATAL_APP_EXIT)) {
+
+ if ((i < min_tries) && (last_free == 0)) {
+ /*
+ * Remember "id" as free but also try
+ * others to not recycle ids too
+ * quickly.
+ */
+ last_free = state.id;
+ }
+ continue;
+ }
+
+ if (NT_STATUS_EQUAL(state.status, NT_STATUS_RETRY)) {
+ /*
+ * Normal collision, try next
+ */
+ DBG_DEBUG("Found record for id %"PRIu32"\n",
+ state.id);
+ continue;
+ }
+
+ DBG_WARNING("smbXsrv_open_global_allocate_fn() failed: %s\n",
+ nt_errstr(state.status));
+ return state.status;
+ }
+
+ /* should not be reached */
+ return NT_STATUS_INTERNAL_ERROR;
+}
+
+static int smbXsrv_open_destructor(struct smbXsrv_open *op)
+{
+ NTSTATUS status;
+
+ status = smbXsrv_open_close(op, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("smbXsrv_open_destructor: "
+ "smbXsrv_open_close() failed - %s\n",
+ nt_errstr(status)));
+ }
+
+ TALLOC_FREE(op->global);
+
+ return 0;
+}
+
+NTSTATUS smbXsrv_open_create(struct smbXsrv_connection *conn,
+ struct auth_session_info *session_info,
+ NTTIME now,
+ struct smbXsrv_open **_open)
+{
+ struct smbXsrv_open_table *table = conn->client->open_table;
+ struct smbXsrv_open *op = NULL;
+ struct smbXsrv_open_global0 *global = NULL;
+ NTSTATUS status;
+ struct dom_sid *current_sid = NULL;
+ struct security_token *current_token = NULL;
+ int local_id;
+
+ if (session_info == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+ current_token = session_info->security_token;
+
+ if (current_token == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (current_token->num_sids > PRIMARY_USER_SID_INDEX) {
+ current_sid = &current_token->sids[PRIMARY_USER_SID_INDEX];
+ }
+
+ if (current_sid == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (table->local.num_opens >= table->local.max_opens) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ op = talloc_zero(table, struct smbXsrv_open);
+ if (op == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ op->table = table;
+ op->status = NT_STATUS_OK; /* TODO: start with INTERNAL_ERROR */
+ op->idle_time = now;
+
+ global = talloc_zero(op, struct smbXsrv_open_global0);
+ if (global == NULL) {
+ TALLOC_FREE(op);
+ return NT_STATUS_NO_MEMORY;
+ }
+ op->global = global;
+
+ /*
+ * We mark every slot as invalid using 0xFF.
+ * Valid values are masked with 0xF.
+ */
+ memset(global->lock_sequence_array, 0xFF,
+ sizeof(global->lock_sequence_array));
+
+ local_id = idr_get_new_random(
+ table->local.idr,
+ op,
+ table->local.lowest_id,
+ table->local.highest_id);
+ if (local_id == -1) {
+ TALLOC_FREE(op);
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+ op->local_id = local_id;
+
+ global->open_volatile_id = op->local_id;
+
+ global->server_id = messaging_server_id(conn->client->msg_ctx);
+ global->open_time = now;
+ global->open_owner = *current_sid;
+ if (conn->protocol >= PROTOCOL_SMB2_10) {
+ global->client_guid = conn->smb2.client.guid;
+ }
+
+ status = smbXsrv_open_global_allocate(table->global.db_ctx,
+ global);
+ if (!NT_STATUS_IS_OK(status)) {
+ int ret = idr_remove(table->local.idr, local_id);
+ SMB_ASSERT(ret == 0);
+
+ DBG_WARNING("smbXsrv_open_global_allocate() failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(op);
+ return status;
+ }
+
+ table->local.num_opens += 1;
+ talloc_set_destructor(op, smbXsrv_open_destructor);
+
+ if (CHECK_DEBUGLVL(10)) {
+ struct smbXsrv_openB open_blob = {
+ .version = SMBXSRV_VERSION_0,
+ .info.info0 = op,
+ };
+
+ DEBUG(10,("smbXsrv_open_create: global_id (0x%08x) stored\n",
+ op->global->open_global_id));
+ NDR_PRINT_DEBUG(smbXsrv_openB, &open_blob);
+ }
+
+ *_open = op;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbXsrv_open_set_replay_cache(struct smbXsrv_open *op)
+{
+ struct GUID *create_guid;
+ struct GUID_txt_buf buf;
+ char *guid_string;
+ struct db_context *db = op->table->local.replay_cache_db_ctx;
+ struct smbXsrv_open_replay_cache rc = {
+ .idle_time = op->idle_time,
+ .local_id = op->local_id,
+ };
+ uint8_t data[SMBXSRV_OPEN_REPLAY_CACHE_FIXED_SIZE] = { 0 };
+ DATA_BLOB blob = { .data = data, .length = sizeof(data), };
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+ TDB_DATA val;
+
+ if (!(op->flags & SMBXSRV_OPEN_NEED_REPLAY_CACHE)) {
+ return NT_STATUS_OK;
+ }
+
+ if (op->flags & SMBXSRV_OPEN_HAVE_REPLAY_CACHE) {
+ return NT_STATUS_OK;
+ }
+
+ create_guid = &op->global->create_guid;
+ guid_string = GUID_buf_string(create_guid, &buf);
+
+ ndr_err = ndr_push_struct_into_fixed_blob(&blob, &rc,
+ (ndr_push_flags_fn_t)ndr_push_smbXsrv_open_replay_cache);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ return status;
+ }
+ val = make_tdb_data(blob.data, blob.length);
+
+ status = dbwrap_store_bystring(db, guid_string, val, TDB_REPLACE);
+
+ if (NT_STATUS_IS_OK(status)) {
+ op->flags |= SMBXSRV_OPEN_HAVE_REPLAY_CACHE;
+ op->flags &= ~SMBXSRV_OPEN_NEED_REPLAY_CACHE;
+ }
+
+ return status;
+}
+
+NTSTATUS smbXsrv_open_purge_replay_cache(struct smbXsrv_client *client,
+ const struct GUID *create_guid)
+{
+ struct GUID_txt_buf buf;
+ char *guid_string;
+ struct db_context *db;
+
+ if (client->open_table == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ db = client->open_table->local.replay_cache_db_ctx;
+
+ guid_string = GUID_buf_string(create_guid, &buf);
+ if (guid_string == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ return dbwrap_purge_bystring(db, guid_string);
+}
+
+static NTSTATUS smbXsrv_open_clear_replay_cache(struct smbXsrv_open *op)
+{
+ struct GUID *create_guid;
+ struct GUID_txt_buf buf;
+ char *guid_string;
+ struct db_context *db;
+ NTSTATUS status;
+
+ if (op->table == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ db = op->table->local.replay_cache_db_ctx;
+
+ if (!(op->flags & SMBXSRV_OPEN_HAVE_REPLAY_CACHE)) {
+ return NT_STATUS_OK;
+ }
+
+ create_guid = &op->global->create_guid;
+ if (GUID_all_zero(create_guid)) {
+ return NT_STATUS_OK;
+ }
+
+ guid_string = GUID_buf_string(create_guid, &buf);
+ if (guid_string == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = dbwrap_purge_bystring(db, guid_string);
+
+ if (NT_STATUS_IS_OK(status)) {
+ op->flags &= ~SMBXSRV_OPEN_HAVE_REPLAY_CACHE;
+ }
+
+ return status;
+}
+
+struct smbXsrv_open_update_state {
+ struct smbXsrv_open_global0 *global;
+ NTSTATUS status;
+};
+
+static void smbXsrv_open_update_fn(
+ struct db_record *rec, TDB_DATA oldval, void *private_data)
+{
+ struct smbXsrv_open_update_state *state = private_data;
+ TDB_DATA key = dbwrap_record_get_key(rec);
+
+ state->status = smbXsrv_open_global_store(
+ rec, key, oldval, state->global);
+}
+
+NTSTATUS smbXsrv_open_update(struct smbXsrv_open *op)
+{
+ struct smbXsrv_open_update_state state = { .global = op->global, };
+ struct smbXsrv_open_table *table = op->table;
+ struct smbXsrv_open_global_key_buf key_buf;
+ TDB_DATA key = smbXsrv_open_global_id_to_key(
+ op->global->open_global_id, &key_buf);
+ NTSTATUS status;
+
+ status = dbwrap_do_locked(
+ table->global.db_ctx, key, smbXsrv_open_update_fn, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("global_id (0x%08x) dbwrap_do_locked failed: %s\n",
+ op->global->open_global_id,
+ nt_errstr(status));
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ if (!NT_STATUS_IS_OK(state.status)) {
+ DBG_WARNING("global_id (0x%08x) smbXsrv_open_global_store "
+ "failed: %s\n",
+ op->global->open_global_id,
+ nt_errstr(state.status));
+ return state.status;
+ }
+
+ status = smbXsrv_open_set_replay_cache(op);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smbXsrv_open_set_replay_cache failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ if (CHECK_DEBUGLVL(10)) {
+ struct smbXsrv_openB open_blob = {
+ .version = SMBXSRV_VERSION_0,
+ .info.info0 = op,
+ };
+
+ DEBUG(10,("smbXsrv_open_update: global_id (0x%08x) stored\n",
+ op->global->open_global_id));
+ NDR_PRINT_DEBUG(smbXsrv_openB, &open_blob);
+ }
+
+ return NT_STATUS_OK;
+}
+
+struct smbXsrv_open_close_state {
+ struct smbXsrv_open *op;
+ NTSTATUS status;
+};
+
+static void smbXsrv_open_close_fn(
+ struct db_record *rec, TDB_DATA oldval, void *private_data)
+{
+ struct smbXsrv_open_close_state *state = private_data;
+ struct smbXsrv_open_global0 *global = state->op->global;
+ TDB_DATA key = dbwrap_record_get_key(rec);
+
+ if (global->durable) {
+ /*
+ * Durable open -- we need to update the global part
+ * instead of deleting it
+ */
+ state->status = smbXsrv_open_global_store(
+ rec, key, oldval, global);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ DBG_WARNING("failed to store global key '%s': %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(state->status));
+ return;
+ }
+
+ if (CHECK_DEBUGLVL(10)) {
+ struct smbXsrv_openB open_blob = {
+ .version = SMBXSRV_VERSION_0,
+ .info.info0 = state->op,
+ };
+
+ DBG_DEBUG("(0x%08x) stored disconnect\n",
+ global->open_global_id);
+ NDR_PRINT_DEBUG(smbXsrv_openB, &open_blob);
+ }
+ return;
+ }
+
+ state->status = dbwrap_record_delete(rec);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ DBG_WARNING("failed to delete global key '%s': %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(state->status));
+ }
+}
+
+NTSTATUS smbXsrv_open_close(struct smbXsrv_open *op, NTTIME now)
+{
+ struct smbXsrv_open_close_state state = { .op = op, };
+ struct smbXsrv_open_global0 *global = op->global;
+ struct smbXsrv_open_table *table;
+ NTSTATUS status;
+ NTSTATUS error = NT_STATUS_OK;
+ struct smbXsrv_open_global_key_buf key_buf;
+ TDB_DATA key = smbXsrv_open_global_id_to_key(
+ global->open_global_id, &key_buf);
+ int ret;
+
+ error = smbXsrv_open_clear_replay_cache(op);
+ if (!NT_STATUS_IS_OK(error)) {
+ DBG_ERR("smbXsrv_open_clear_replay_cache failed: %s\n",
+ nt_errstr(error));
+ }
+
+ if (op->table == NULL) {
+ return error;
+ }
+
+ table = op->table;
+ op->table = NULL;
+
+ op->status = NT_STATUS_FILE_CLOSED;
+ global->disconnect_time = now;
+ server_id_set_disconnected(&global->server_id);
+
+ status = dbwrap_do_locked(
+ table->global.db_ctx, key, smbXsrv_open_close_fn, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("dbwrap_do_locked() for %s failed: %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ error = status;
+ } else if (!NT_STATUS_IS_OK(state.status)) {
+ DBG_WARNING("smbXsrv_open_close_fn() for %s failed: %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(state.status));
+ error = state.status;
+ }
+
+ ret = idr_remove(table->local.idr, op->local_id);
+ SMB_ASSERT(ret == 0);
+
+ table->local.num_opens -= 1;
+
+ if (op->compat) {
+ op->compat->op = NULL;
+ file_free(NULL, op->compat);
+ op->compat = NULL;
+ }
+
+ return error;
+}
+
+NTSTATUS smb1srv_open_table_init(struct smbXsrv_connection *conn)
+{
+ uint32_t max_opens;
+
+ /*
+ * Allow a range from 1..65534.
+ *
+ * With real_max_open_files possible ids,
+ * truncated to the SMB1 limit of 16-bit.
+ *
+ * 0 and 0xFFFF are no valid ids.
+ */
+ max_opens = conn->client->sconn->real_max_open_files;
+ max_opens = MIN(max_opens, UINT16_MAX - 1);
+
+ return smbXsrv_open_table_init(conn, 1, UINT16_MAX - 1, max_opens);
+}
+
+NTSTATUS smb1srv_open_lookup(struct smbXsrv_connection *conn,
+ uint16_t fnum, NTTIME now,
+ struct smbXsrv_open **_open)
+{
+ struct smbXsrv_open_table *table = conn->client->open_table;
+ uint32_t local_id = fnum;
+ uint32_t global_id = 0;
+
+ return smbXsrv_open_local_lookup(table, local_id, global_id, now, _open);
+}
+
+NTSTATUS smb2srv_open_table_init(struct smbXsrv_connection *conn)
+{
+ uint32_t max_opens;
+ uint32_t highest_id;
+
+ /*
+ * Allow a range from 1..4294967294.
+ *
+ * With real_max_open_files possible ids,
+ * truncated to 16-bit (the same as SMB1 for now).
+ *
+ * 0 and 0xFFFFFFFF are no valid ids.
+ *
+ * The usage of conn->sconn->real_max_open_files
+ * is the reason that we use one open table per
+ * transport connection (as we still have a 1:1 mapping
+ * between process and transport connection).
+ */
+ max_opens = conn->client->sconn->real_max_open_files;
+ max_opens = MIN(max_opens, UINT16_MAX - 1);
+
+ /*
+ * idtree uses "int" for local IDs. Limit the maximum ID to
+ * what "int" can hold.
+ */
+ highest_id = UINT32_MAX-1;
+ highest_id = MIN(highest_id, INT_MAX);
+
+ return smbXsrv_open_table_init(conn, 1, highest_id, max_opens);
+}
+
+NTSTATUS smb2srv_open_lookup(struct smbXsrv_connection *conn,
+ uint64_t persistent_id,
+ uint64_t volatile_id,
+ NTTIME now,
+ struct smbXsrv_open **_open)
+{
+ struct smbXsrv_open_table *table = conn->client->open_table;
+ uint32_t local_id = volatile_id & UINT32_MAX;
+ uint64_t local_zeros = volatile_id & 0xFFFFFFFF00000000LLU;
+ uint32_t global_id = persistent_id & UINT32_MAX;
+ uint64_t global_zeros = persistent_id & 0xFFFFFFFF00000000LLU;
+ NTSTATUS status;
+
+ if (local_zeros != 0) {
+ return NT_STATUS_FILE_CLOSED;
+ }
+
+ if (global_zeros != 0) {
+ return NT_STATUS_FILE_CLOSED;
+ }
+
+ if (global_id == 0) {
+ return NT_STATUS_FILE_CLOSED;
+ }
+
+ status = smbXsrv_open_local_lookup(table, local_id, global_id, now,
+ _open);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * Clear the replay cache for this create_guid if it exists:
+ * This is based on the assumption that this lookup will be
+ * triggered by a client request using the file-id for lookup.
+ * Hence the client has proven that it has in fact seen the
+ * reply to its initial create call. So subsequent create replays
+ * should be treated as invalid. Hence the index for create_guid
+ * lookup needs to be removed.
+ */
+ status = smbXsrv_open_clear_replay_cache(*_open);
+
+ return status;
+}
+
+/*
+ * This checks or marks the replay cache, we have the following
+ * cases:
+ *
+ * 1. There is no record in the cache
+ * => we add the passes caller_req_guid as holder_req_guid
+ * together with local_id as 0.
+ * => We return STATUS_FWP_RESERVED in order to indicate
+ * that the caller holds the current reservation
+ *
+ * 2. There is a record in the cache and holder_req_guid
+ * is already the same as caller_req_guid and local_id is 0
+ * => We return STATUS_FWP_RESERVED in order to indicate
+ * that the caller holds the current reservation
+ *
+ * 3. There is a record in the cache with a holder_req_guid
+ * other than caller_req_guid (and local_id is 0):
+ * => We return NT_STATUS_FILE_NOT_AVAILABLE to indicate
+ * the original request is still pending
+ *
+ * 4. There is a record in the cache with a zero holder_req_guid
+ * and a valid local_id:
+ * => We lookup the existing open by local_id
+ * => We return NT_STATUS_OK together with the smbXsrv_open
+ *
+ *
+ * With NT_STATUS_OK the caller can continue the replay processing.
+ *
+ * With STATUS_FWP_RESERVED the caller should continue the normal
+ * open processing:
+ * - On success:
+ * - smbXsrv_open_update()/smbXsrv_open_set_replay_cache()
+ * will convert the record to a zero holder_req_guid
+ * with a valid local_id.
+ * - On failure:
+ * - smbXsrv_open_purge_replay_cache() should cleanup
+ * the reservation.
+ *
+ * All other values should be returned to the client,
+ * while NT_STATUS_FILE_NOT_AVAILABLE will trigger the
+ * retry loop on the client.
+ */
+NTSTATUS smb2srv_open_lookup_replay_cache(struct smbXsrv_connection *conn,
+ struct GUID caller_req_guid,
+ struct GUID create_guid,
+ const char *name,
+ NTTIME now,
+ struct smbXsrv_open **_open)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct smbXsrv_open_table *table = conn->client->open_table;
+ struct db_context *db = table->local.replay_cache_db_ctx;
+ struct GUID_txt_buf tmp_guid_buf;
+ struct GUID_txt_buf _create_guid_buf;
+ const char *create_guid_str = GUID_buf_string(&create_guid, &_create_guid_buf);
+ TDB_DATA create_guid_key = string_term_tdb_data(create_guid_str);
+ struct db_record *db_rec = NULL;
+ struct smbXsrv_open *op = NULL;
+ struct smbXsrv_open_replay_cache rc = {
+ .holder_req_guid = caller_req_guid,
+ .idle_time = now,
+ .local_id = 0,
+ };
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob = data_blob_null;
+ TDB_DATA val;
+
+ *_open = NULL;
+
+ db_rec = dbwrap_fetch_locked(db, frame, create_guid_key);
+ if (db_rec == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ val = dbwrap_record_get_value(db_rec);
+ if (val.dsize == 0) {
+ uint8_t data[SMBXSRV_OPEN_REPLAY_CACHE_FIXED_SIZE];
+
+ blob = data_blob_const(data, ARRAY_SIZE(data));
+ ndr_err = ndr_push_struct_into_fixed_blob(&blob, &rc,
+ (ndr_push_flags_fn_t)ndr_push_smbXsrv_open_replay_cache);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ val = make_tdb_data(blob.data, blob.length);
+ status = dbwrap_record_store(db_rec, val, TDB_REPLACE);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /*
+ * We're the new holder
+ */
+ *_open = NULL;
+ TALLOC_FREE(frame);
+ return NT_STATUS_FWP_RESERVED;
+ }
+
+ if (val.dsize != SMBXSRV_OPEN_REPLAY_CACHE_FIXED_SIZE) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ blob = data_blob_const(val.dptr, val.dsize);
+ ndr_err = ndr_pull_struct_blob_all_noalloc(&blob, &rc,
+ (ndr_pull_flags_fn_t)ndr_pull_smbXsrv_open_replay_cache);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ TALLOC_FREE(frame);
+ return status;
+ }
+ if (rc.local_id != 0) {
+ if (GUID_equal(&rc.holder_req_guid, &caller_req_guid)) {
+ /*
+ * This should not happen
+ */
+ status = NT_STATUS_INTERNAL_ERROR;
+ DBG_ERR("caller %s already holds local_id %u for create %s [%s] - %s\n",
+ GUID_buf_string(&caller_req_guid, &tmp_guid_buf),
+ (unsigned)rc.local_id,
+ create_guid_str,
+ name,
+ nt_errstr(status));
+
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ status = smbXsrv_open_local_lookup(table,
+ rc.local_id,
+ 0, /* global_id */
+ now,
+ &op);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("holder %s stale for local_id %u for create %s [%s] - %s\n",
+ GUID_buf_string(&rc.holder_req_guid, &tmp_guid_buf),
+ (unsigned)rc.local_id,
+ create_guid_str,
+ name,
+ nt_errstr(status));
+
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /*
+ * We found an open the caller can reuse.
+ */
+ SMB_ASSERT(op != NULL);
+ *_open = op;
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ if (GUID_equal(&rc.holder_req_guid, &caller_req_guid)) {
+ /*
+ * We're still the holder
+ */
+ *_open = NULL;
+ TALLOC_FREE(frame);
+ return NT_STATUS_FWP_RESERVED;
+ }
+
+ /*
+ * The original request (or a former replay) is still
+ * pending, ask the client to retry by sending FILE_NOT_AVAILABLE.
+ */
+ status = NT_STATUS_FILE_NOT_AVAILABLE;
+ DBG_DEBUG("holder %s still pending for create %s [%s] - %s\n",
+ GUID_buf_string(&rc.holder_req_guid, &tmp_guid_buf),
+ create_guid_str,
+ name,
+ nt_errstr(status));
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct smb2srv_open_recreate_state {
+ struct smbXsrv_open *op;
+ const struct GUID *create_guid;
+ struct security_token *current_token;
+ struct server_id me;
+
+ NTSTATUS status;
+};
+
+static void smb2srv_open_recreate_fn(
+ struct db_record *rec, TDB_DATA oldval, void *private_data)
+{
+ struct smb2srv_open_recreate_state *state = private_data;
+ TDB_DATA key = dbwrap_record_get_key(rec);
+ struct smbXsrv_open_global0 *global = NULL;
+
+ state->status = smbXsrv_open_global_verify_record(
+ key, oldval, state->op, &state->op->global);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ DBG_WARNING("smbXsrv_open_global_verify_record for %s "
+ "failed: %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(state->status));
+ goto not_found;
+ }
+ global = state->op->global;
+
+ /*
+ * If the provided create_guid is NULL, this means that
+ * the reconnect request was a v1 request. In that case
+ * we should skip the create GUID verification, since
+ * it is valid to v1-reconnect a v2-opened handle.
+ */
+ if ((state->create_guid != NULL) &&
+ !GUID_equal(&global->create_guid, state->create_guid)) {
+ struct GUID_txt_buf buf1, buf2;
+ DBG_NOTICE("%s != %s in %s\n",
+ GUID_buf_string(&global->create_guid, &buf1),
+ GUID_buf_string(state->create_guid, &buf2),
+ tdb_data_dbg(key));
+ goto not_found;
+ }
+
+ if (!security_token_is_sid(
+ state->current_token, &global->open_owner)) {
+ struct dom_sid_buf buf;
+ DBG_NOTICE("global owner %s not in our token in %s\n",
+ dom_sid_str_buf(&global->open_owner, &buf),
+ tdb_data_dbg(key));
+ goto not_found;
+ }
+
+ if (!global->durable) {
+ DBG_NOTICE("%"PRIu64"/%"PRIu64" not durable in %s\n",
+ global->open_persistent_id,
+ global->open_volatile_id,
+ tdb_data_dbg(key));
+ goto not_found;
+ }
+
+ global->open_volatile_id = state->op->local_id;
+ global->server_id = state->me;
+
+ state->status = smbXsrv_open_global_store(rec, key, oldval, global);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ DBG_WARNING("smbXsrv_open_global_store for %s failed: %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(state->status));
+ return;
+ }
+ return;
+
+not_found:
+ state->status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+}
+
+NTSTATUS smb2srv_open_recreate(struct smbXsrv_connection *conn,
+ struct auth_session_info *session_info,
+ uint64_t persistent_id,
+ const struct GUID *create_guid,
+ NTTIME now,
+ struct smbXsrv_open **_open)
+{
+ struct smbXsrv_open_table *table = conn->client->open_table;
+ struct smb2srv_open_recreate_state state = {
+ .create_guid = create_guid,
+ .me = messaging_server_id(conn->client->msg_ctx),
+ };
+ struct smbXsrv_open_global_key_buf key_buf;
+ TDB_DATA key = smbXsrv_open_global_id_to_key(
+ persistent_id & UINT32_MAX, &key_buf);
+ int ret, local_id;
+ NTSTATUS status;
+
+ if (session_info == NULL) {
+ DEBUG(10, ("session_info=NULL\n"));
+ return NT_STATUS_INVALID_HANDLE;
+ }
+ state.current_token = session_info->security_token;
+
+ if (state.current_token == NULL) {
+ DEBUG(10, ("current_token=NULL\n"));
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if ((persistent_id & 0xFFFFFFFF00000000LLU) != 0) {
+ /*
+ * We only use 32 bit for the persistent ID
+ */
+ DBG_DEBUG("persistent_id=%"PRIx64"\n", persistent_id);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (table->local.num_opens >= table->local.max_opens) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ state.op = talloc_zero(table, struct smbXsrv_open);
+ if (state.op == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ state.op->table = table;
+
+ local_id = idr_get_new_random(
+ table->local.idr,
+ state.op,
+ table->local.lowest_id,
+ table->local.highest_id);
+ if (local_id == -1) {
+ TALLOC_FREE(state.op);
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+ state.op->local_id = local_id;
+ SMB_ASSERT(state.op->local_id == local_id); /* No coercion loss */
+
+ table->local.num_opens += 1;
+
+ state.op->idle_time = now;
+ state.op->status = NT_STATUS_FILE_CLOSED;
+
+ status = dbwrap_do_locked(
+ table->global.db_ctx, key, smb2srv_open_recreate_fn, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dbwrap_do_locked() for %s failed: %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ goto fail;
+ }
+
+ if (!NT_STATUS_IS_OK(state.status)) {
+ status = state.status;
+ DBG_DEBUG("smb2srv_open_recreate_fn for %s failed: %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ goto fail;
+ }
+
+ talloc_set_destructor(state.op, smbXsrv_open_destructor);
+
+ if (CHECK_DEBUGLVL(10)) {
+ struct smbXsrv_openB open_blob = {
+ .info.info0 = state.op,
+ };
+ DBG_DEBUG("global_id (0x%08x) stored\n",
+ state.op->global->open_global_id);
+ NDR_PRINT_DEBUG(smbXsrv_openB, &open_blob);
+ }
+
+ *_open = state.op;
+
+ return NT_STATUS_OK;
+fail:
+ table->local.num_opens -= 1;
+
+ ret = idr_remove(table->local.idr, state.op->local_id);
+ SMB_ASSERT(ret == 0);
+ TALLOC_FREE(state.op);
+ return status;
+}
+
+struct smbXsrv_open_global_traverse_state {
+ int (*fn)(struct db_record *rec, struct smbXsrv_open_global0 *, void *);
+ void *private_data;
+};
+
+static int smbXsrv_open_global_traverse_fn(struct db_record *rec, void *data)
+{
+ struct smbXsrv_open_global_traverse_state *state =
+ (struct smbXsrv_open_global_traverse_state*)data;
+ struct smbXsrv_open_global0 *global = NULL;
+ TDB_DATA key = dbwrap_record_get_key(rec);
+ TDB_DATA val = dbwrap_record_get_value(rec);
+ NTSTATUS status;
+ int ret = -1;
+
+ status = smbXsrv_open_global_parse_record(
+ talloc_tos(), key, val, &global);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+
+ ret = state->fn(rec, global, state->private_data);
+ talloc_free(global);
+ return ret;
+}
+
+NTSTATUS smbXsrv_open_global_traverse(
+ int (*fn)(struct db_record *rec, struct smbXsrv_open_global0 *, void *),
+ void *private_data)
+{
+
+ NTSTATUS status;
+ int count = 0;
+ struct smbXsrv_open_global_traverse_state state = {
+ .fn = fn,
+ .private_data = private_data,
+ };
+
+ become_root();
+ status = smbXsrv_open_global_init();
+ if (!NT_STATUS_IS_OK(status)) {
+ unbecome_root();
+ DEBUG(0, ("Failed to initialize open_global: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ status = dbwrap_traverse_read(smbXsrv_open_global_db_ctx,
+ smbXsrv_open_global_traverse_fn,
+ &state,
+ &count);
+ unbecome_root();
+
+ return status;
+}
+
+struct smbXsrv_open_cleanup_state {
+ uint32_t global_id;
+ NTSTATUS status;
+};
+
+static void smbXsrv_open_cleanup_fn(
+ struct db_record *rec, TDB_DATA oldval, void *private_data)
+{
+ struct smbXsrv_open_cleanup_state *state = private_data;
+ struct smbXsrv_open_global0 *global = NULL;
+ TDB_DATA key = dbwrap_record_get_key(rec);
+ bool delete_open = false;
+
+ if (oldval.dsize == 0) {
+ DBG_DEBUG("[global: 0x%08x] "
+ "empty record in %s, skipping...\n",
+ state->global_id,
+ dbwrap_name(dbwrap_record_get_db(rec)));
+ state->status = NT_STATUS_OK;
+ return;
+ }
+
+ state->status = smbXsrv_open_global_parse_record(
+ talloc_tos(), key, oldval, &global);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ DBG_WARNING("[global: %x08x] "
+ "smbXsrv_open_global_parse_record() in %s "
+ "failed: %s, deleting record\n",
+ state->global_id,
+ dbwrap_name(dbwrap_record_get_db(rec)),
+ nt_errstr(state->status));
+ delete_open = true;
+ goto do_delete;
+ }
+
+ if (server_id_is_disconnected(&global->server_id)) {
+ struct timeval now = timeval_current();
+ struct timeval disconnect_time;
+ struct timeval_buf buf;
+ int64_t tdiff;
+
+ nttime_to_timeval(&disconnect_time, global->disconnect_time);
+ tdiff = usec_time_diff(&now, &disconnect_time);
+ delete_open = (tdiff >= 1000*global->durable_timeout_msec);
+
+ DBG_DEBUG("[global: 0x%08x] "
+ "disconnected at [%s] %"PRIi64"s ago with "
+ "timeout of %"PRIu32"s -%s reached\n",
+ state->global_id,
+ timeval_str_buf(&disconnect_time,
+ false,
+ false,
+ &buf),
+ tdiff/1000000,
+ global->durable_timeout_msec / 1000,
+ delete_open ? "" : " not");
+ } else if (!serverid_exists(&global->server_id)) {
+ struct server_id_buf idbuf;
+ DBG_DEBUG("[global: 0x%08x] "
+ "server[%s] does not exist\n",
+ state->global_id,
+ server_id_str_buf(global->server_id, &idbuf));
+ delete_open = true;
+ }
+
+ if (!delete_open) {
+ state->status = NT_STATUS_OK;
+ return;
+ }
+do_delete:
+ state->status = dbwrap_record_delete(rec);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ DBG_WARNING("[global: 0x%08x] "
+ "failed to delete record "
+ "from %s: %s\n",
+ state->global_id,
+ dbwrap_name(dbwrap_record_get_db(rec)),
+ nt_errstr(state->status));
+ return;
+ }
+
+ DBG_DEBUG("[global: 0x%08x] "
+ "deleted record from %s\n",
+ state->global_id,
+ dbwrap_name(dbwrap_record_get_db(rec)));
+}
+
+NTSTATUS smbXsrv_open_cleanup(uint64_t persistent_id)
+{
+ struct smbXsrv_open_cleanup_state state = {
+ .global_id = persistent_id & UINT32_MAX,
+ };
+ struct smbXsrv_open_global_key_buf key_buf;
+ TDB_DATA key = smbXsrv_open_global_id_to_key(
+ state.global_id, &key_buf);
+ NTSTATUS status;
+
+ status = dbwrap_do_locked(
+ smbXsrv_open_global_db_ctx,
+ key,
+ smbXsrv_open_cleanup_fn,
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("[global: 0x%08x] dbwrap_do_locked failed: %s\n",
+ state.global_id,
+ nt_errstr(status));
+ return status;
+ }
+
+ return state.status;
+}
diff --git a/source3/smbd/smbXsrv_open.h b/source3/smbd/smbXsrv_open.h
new file mode 100644
index 0000000..0257650
--- /dev/null
+++ b/source3/smbd/smbXsrv_open.h
@@ -0,0 +1,75 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Copyright (C) Stefan Metzmacher 2012
+ * Copyright (C) Michael Adam 2012
+ *
+ * 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/>.
+ */
+
+#ifndef __SMBXSRV_OPEN_H__
+#define __SMBXSRV_OPEN_H__
+
+#include "replace.h"
+#include "libcli/util/ntstatus.h"
+#include "lib/util/time.h"
+#include "lib/util/data_blob.h"
+#include "librpc/gen_ndr/misc.h"
+
+struct smbXsrv_connection;
+struct auth_session_info;
+struct smbXsrv_open;
+struct smbXsrv_open_global0;
+struct smbXsrv_client;
+
+NTSTATUS smbXsrv_open_global_init(void);
+NTSTATUS smbXsrv_open_create(struct smbXsrv_connection *conn,
+ struct auth_session_info *session_info,
+ NTTIME now,
+ struct smbXsrv_open **_open);
+NTSTATUS smbXsrv_open_update(struct smbXsrv_open *_open);
+NTSTATUS smbXsrv_open_close(struct smbXsrv_open *op, NTTIME now);
+NTSTATUS smb1srv_open_table_init(struct smbXsrv_connection *conn);
+NTSTATUS smb1srv_open_lookup(struct smbXsrv_connection *conn,
+ uint16_t fnum, NTTIME now,
+ struct smbXsrv_open **_open);
+NTSTATUS smb2srv_open_table_init(struct smbXsrv_connection *conn);
+NTSTATUS smb2srv_open_lookup(struct smbXsrv_connection *conn,
+ uint64_t persistent_id,
+ uint64_t volatile_id,
+ NTTIME now,
+ struct smbXsrv_open **_open);
+NTSTATUS smbXsrv_open_purge_replay_cache(struct smbXsrv_client *client,
+ const struct GUID *create_guid);
+NTSTATUS smb2srv_open_lookup_replay_cache(struct smbXsrv_connection *conn,
+ struct GUID caller_req_guid,
+ struct GUID create_guid,
+ const char *name,
+ NTTIME now,
+ struct smbXsrv_open **_open);
+NTSTATUS smb2srv_open_recreate(struct smbXsrv_connection *conn,
+ struct auth_session_info *session_info,
+ uint64_t persistent_id,
+ const struct GUID *create_guid,
+ NTTIME now,
+ struct smbXsrv_open **_open);
+
+struct db_record;
+NTSTATUS smbXsrv_open_global_traverse(
+ int (*fn)(struct db_record *rec, struct smbXsrv_open_global0 *, void *),
+ void *private_data);
+
+NTSTATUS smbXsrv_open_cleanup(uint64_t persistent_id);
+
+#endif
diff --git a/source3/smbd/smbXsrv_session.c b/source3/smbd/smbXsrv_session.c
new file mode 100644
index 0000000..a88b7c1
--- /dev/null
+++ b/source3/smbd/smbXsrv_session.c
@@ -0,0 +1,2527 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Stefan Metzmacher 2011-2012
+ Copyright (C) Michael Adam 2012
+
+ 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 "system/filesys.h"
+#include <tevent.h>
+#include "lib/util/server_id.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_rbt.h"
+#include "dbwrap/dbwrap_open.h"
+#include "dbwrap/dbwrap_watch.h"
+#include "session.h"
+#include "auth.h"
+#include "auth/gensec/gensec.h"
+#include "../lib/tsocket/tsocket.h"
+#include "../libcli/security/security.h"
+#include "messages.h"
+#include "lib/util/util_tdb.h"
+#include "librpc/gen_ndr/ndr_smbXsrv.h"
+#include "serverid.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "lib/global_contexts.h"
+#include "source3/include/util_tdb.h"
+
+struct smbXsrv_session_table {
+ struct {
+ struct db_context *db_ctx;
+ uint32_t lowest_id;
+ uint32_t highest_id;
+ uint32_t max_sessions;
+ uint32_t num_sessions;
+ } local;
+ struct {
+ struct db_context *db_ctx;
+ } global;
+};
+
+static struct db_context *smbXsrv_session_global_db_ctx = NULL;
+
+NTSTATUS smbXsrv_session_global_init(struct messaging_context *msg_ctx)
+{
+ char *global_path = NULL;
+ struct db_context *backend = NULL;
+ struct db_context *db_ctx = NULL;
+
+ if (smbXsrv_session_global_db_ctx != NULL) {
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * This contains secret information like session keys!
+ */
+ global_path = lock_path(talloc_tos(), "smbXsrv_session_global.tdb");
+ if (global_path == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ backend = db_open(NULL, global_path,
+ SMBD_VOLATILE_TDB_HASH_SIZE,
+ SMBD_VOLATILE_TDB_FLAGS,
+ O_RDWR | O_CREAT, 0600,
+ DBWRAP_LOCK_ORDER_1,
+ DBWRAP_FLAG_NONE);
+ TALLOC_FREE(global_path);
+ if (backend == NULL) {
+ NTSTATUS status;
+
+ status = map_nt_error_from_unix_common(errno);
+
+ return status;
+ }
+
+ db_ctx = db_open_watched(NULL, &backend, global_messaging_context());
+ if (db_ctx == NULL) {
+ TALLOC_FREE(backend);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ smbXsrv_session_global_db_ctx = db_ctx;
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * NOTE:
+ * We need to store the keys in big endian so that dbwrap_rbt's memcmp
+ * has the same result as integer comparison between the uint32_t
+ * values.
+ *
+ * TODO: implement string based key
+ */
+
+#define SMBXSRV_SESSION_GLOBAL_TDB_KEY_SIZE sizeof(uint32_t)
+
+static TDB_DATA smbXsrv_session_global_id_to_key(uint32_t id,
+ uint8_t *key_buf)
+{
+ TDB_DATA key;
+
+ RSIVAL(key_buf, 0, id);
+
+ key = make_tdb_data(key_buf, SMBXSRV_SESSION_GLOBAL_TDB_KEY_SIZE);
+
+ return key;
+}
+
+#if 0
+static NTSTATUS smbXsrv_session_global_key_to_id(TDB_DATA key, uint32_t *id)
+{
+ if (id == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (key.dsize != SMBXSRV_SESSION_GLOBAL_TDB_KEY_SIZE) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ *id = RIVAL(key.dptr, 0);
+
+ return NT_STATUS_OK;
+}
+#endif
+
+#define SMBXSRV_SESSION_LOCAL_TDB_KEY_SIZE sizeof(uint32_t)
+
+static TDB_DATA smbXsrv_session_local_id_to_key(uint32_t id,
+ uint8_t *key_buf)
+{
+ TDB_DATA key;
+
+ RSIVAL(key_buf, 0, id);
+
+ key = make_tdb_data(key_buf, SMBXSRV_SESSION_LOCAL_TDB_KEY_SIZE);
+
+ return key;
+}
+
+static NTSTATUS smbXsrv_session_local_key_to_id(TDB_DATA key, uint32_t *id)
+{
+ if (id == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (key.dsize != SMBXSRV_SESSION_LOCAL_TDB_KEY_SIZE) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ *id = RIVAL(key.dptr, 0);
+
+ return NT_STATUS_OK;
+}
+
+static struct db_record *smbXsrv_session_global_fetch_locked(
+ struct db_context *db,
+ uint32_t id,
+ TALLOC_CTX *mem_ctx)
+{
+ TDB_DATA key;
+ uint8_t key_buf[SMBXSRV_SESSION_GLOBAL_TDB_KEY_SIZE];
+ struct db_record *rec = NULL;
+
+ key = smbXsrv_session_global_id_to_key(id, key_buf);
+
+ rec = dbwrap_fetch_locked(db, mem_ctx, key);
+
+ if (rec == NULL) {
+ DBG_DEBUG("Failed to lock global id 0x%08x, key '%s'\n", id,
+ tdb_data_dbg(key));
+ }
+
+ return rec;
+}
+
+static struct db_record *smbXsrv_session_local_fetch_locked(
+ struct db_context *db,
+ uint32_t id,
+ TALLOC_CTX *mem_ctx)
+{
+ TDB_DATA key;
+ uint8_t key_buf[SMBXSRV_SESSION_LOCAL_TDB_KEY_SIZE];
+ struct db_record *rec = NULL;
+
+ key = smbXsrv_session_local_id_to_key(id, key_buf);
+
+ rec = dbwrap_fetch_locked(db, mem_ctx, key);
+
+ if (rec == NULL) {
+ DBG_DEBUG("Failed to lock local id 0x%08x, key '%s'\n", id,
+ tdb_data_dbg(key));
+ }
+
+ return rec;
+}
+
+static void smbXsrv_session_close_loop(struct tevent_req *subreq);
+
+static NTSTATUS smbXsrv_session_table_init(struct smbXsrv_connection *conn,
+ uint32_t lowest_id,
+ uint32_t highest_id,
+ uint32_t max_sessions)
+{
+ struct smbXsrv_client *client = conn->client;
+ struct smbXsrv_session_table *table;
+ NTSTATUS status;
+ struct tevent_req *subreq;
+ uint64_t max_range;
+
+ if (lowest_id > highest_id) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ max_range = highest_id;
+ max_range -= lowest_id;
+ max_range += 1;
+
+ if (max_sessions > max_range) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ table = talloc_zero(client, struct smbXsrv_session_table);
+ if (table == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ table->local.db_ctx = db_open_rbt(table);
+ if (table->local.db_ctx == NULL) {
+ TALLOC_FREE(table);
+ return NT_STATUS_NO_MEMORY;
+ }
+ table->local.lowest_id = lowest_id;
+ table->local.highest_id = highest_id;
+ table->local.max_sessions = max_sessions;
+
+ status = smbXsrv_session_global_init(client->msg_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(table);
+ return status;
+ }
+
+ table->global.db_ctx = smbXsrv_session_global_db_ctx;
+
+ subreq = messaging_read_send(table,
+ client->raw_ev_ctx,
+ client->msg_ctx,
+ MSG_SMBXSRV_SESSION_CLOSE);
+ if (subreq == NULL) {
+ TALLOC_FREE(table);
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_req_set_callback(subreq, smbXsrv_session_close_loop, client);
+
+ client->session_table = table;
+ return NT_STATUS_OK;
+}
+
+static void smbXsrv_session_close_shutdown_done(struct tevent_req *subreq);
+
+static void smbXsrv_session_close_loop(struct tevent_req *subreq)
+{
+ struct smbXsrv_client *client =
+ tevent_req_callback_data(subreq,
+ struct smbXsrv_client);
+ struct smbXsrv_session_table *table = client->session_table;
+ int ret;
+ struct messaging_rec *rec = NULL;
+ struct smbXsrv_session_closeB close_blob;
+ enum ndr_err_code ndr_err;
+ struct smbXsrv_session_close0 *close_info0 = NULL;
+ struct smbXsrv_session *session = NULL;
+ NTSTATUS status;
+ struct timeval tv = timeval_current();
+ NTTIME now = timeval_to_nttime(&tv);
+
+ ret = messaging_read_recv(subreq, talloc_tos(), &rec);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ goto next;
+ }
+
+ ndr_err = ndr_pull_struct_blob(&rec->buf, rec, &close_blob,
+ (ndr_pull_flags_fn_t)ndr_pull_smbXsrv_session_closeB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("smbXsrv_session_close_loop: "
+ "ndr_pull_struct_blob - %s\n",
+ nt_errstr(status));
+ goto next;
+ }
+
+ DBG_DEBUG("smbXsrv_session_close_loop: MSG_SMBXSRV_SESSION_CLOSE\n");
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ NDR_PRINT_DEBUG(smbXsrv_session_closeB, &close_blob);
+ }
+
+ if (close_blob.version != SMBXSRV_VERSION_0) {
+ DBG_ERR("smbXsrv_session_close_loop: "
+ "ignore invalid version %u\n", close_blob.version);
+ NDR_PRINT_DEBUG(smbXsrv_session_closeB, &close_blob);
+ goto next;
+ }
+
+ close_info0 = close_blob.info.info0;
+ if (close_info0 == NULL) {
+ DBG_ERR("smbXsrv_session_close_loop: "
+ "ignore NULL info %u\n", close_blob.version);
+ NDR_PRINT_DEBUG(smbXsrv_session_closeB, &close_blob);
+ goto next;
+ }
+
+ status = smb2srv_session_lookup_client(client,
+ close_info0->old_session_wire_id,
+ now, &session);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_USER_SESSION_DELETED)) {
+ DBG_INFO("smbXsrv_session_close_loop: "
+ "old_session_wire_id %llu not found\n",
+ (unsigned long long)close_info0->old_session_wire_id);
+ if (DEBUGLVL(DBGLVL_INFO)) {
+ NDR_PRINT_DEBUG(smbXsrv_session_closeB, &close_blob);
+ }
+ goto next;
+ }
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_SESSION_EXPIRED)) {
+ DBG_WARNING("smbXsrv_session_close_loop: "
+ "old_session_wire_id %llu - %s\n",
+ (unsigned long long)close_info0->old_session_wire_id,
+ nt_errstr(status));
+ if (DEBUGLVL(DBGLVL_WARNING)) {
+ NDR_PRINT_DEBUG(smbXsrv_session_closeB, &close_blob);
+ }
+ goto next;
+ }
+
+ if (session->global->session_global_id != close_info0->old_session_global_id) {
+ DBG_WARNING("smbXsrv_session_close_loop: "
+ "old_session_wire_id %llu - global %u != %u\n",
+ (unsigned long long)close_info0->old_session_wire_id,
+ session->global->session_global_id,
+ close_info0->old_session_global_id);
+ if (DEBUGLVL(DBGLVL_WARNING)) {
+ NDR_PRINT_DEBUG(smbXsrv_session_closeB, &close_blob);
+ }
+ goto next;
+ }
+
+ if (session->global->creation_time != close_info0->old_creation_time) {
+ DBG_WARNING("smbXsrv_session_close_loop: "
+ "old_session_wire_id %llu - "
+ "creation %s (%llu) != %s (%llu)\n",
+ (unsigned long long)close_info0->old_session_wire_id,
+ nt_time_string(rec, session->global->creation_time),
+ (unsigned long long)session->global->creation_time,
+ nt_time_string(rec, close_info0->old_creation_time),
+ (unsigned long long)close_info0->old_creation_time);
+ if (DEBUGLVL(DBGLVL_WARNING)) {
+ NDR_PRINT_DEBUG(smbXsrv_session_closeB, &close_blob);
+ }
+ goto next;
+ }
+
+ subreq = smb2srv_session_shutdown_send(session, client->raw_ev_ctx,
+ session, NULL);
+ if (subreq == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ DBG_ERR("smbXsrv_session_close_loop: "
+ "smb2srv_session_shutdown_send(%llu) failed: %s\n",
+ (unsigned long long)session->global->session_wire_id,
+ nt_errstr(status));
+ if (DEBUGLVL(DBGLVL_WARNING)) {
+ NDR_PRINT_DEBUG(smbXsrv_session_closeB, &close_blob);
+ }
+ goto next;
+ }
+ tevent_req_set_callback(subreq,
+ smbXsrv_session_close_shutdown_done,
+ session);
+
+next:
+ TALLOC_FREE(rec);
+
+ subreq = messaging_read_send(table,
+ client->raw_ev_ctx,
+ client->msg_ctx,
+ MSG_SMBXSRV_SESSION_CLOSE);
+ if (subreq == NULL) {
+ const char *r;
+ r = "messaging_read_send(MSG_SMBXSRV_SESSION_CLOSE) failed";
+ exit_server_cleanly(r);
+ return;
+ }
+ tevent_req_set_callback(subreq, smbXsrv_session_close_loop, client);
+}
+
+static void smbXsrv_session_close_shutdown_done(struct tevent_req *subreq)
+{
+ struct smbXsrv_session *session =
+ tevent_req_callback_data(subreq,
+ struct smbXsrv_session);
+ NTSTATUS status;
+
+ status = smb2srv_session_shutdown_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smbXsrv_session_close_loop: "
+ "smb2srv_session_shutdown_recv(%llu) failed: %s\n",
+ (unsigned long long)session->global->session_wire_id,
+ nt_errstr(status));
+ }
+
+ status = smbXsrv_session_logoff(session);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smbXsrv_session_close_loop: "
+ "smbXsrv_session_logoff(%llu) failed: %s\n",
+ (unsigned long long)session->global->session_wire_id,
+ nt_errstr(status));
+ }
+
+ TALLOC_FREE(session);
+}
+
+struct smb1srv_session_local_allocate_state {
+ const uint32_t lowest_id;
+ const uint32_t highest_id;
+ uint32_t last_id;
+ uint32_t useable_id;
+ NTSTATUS status;
+};
+
+static int smb1srv_session_local_allocate_traverse(struct db_record *rec,
+ void *private_data)
+{
+ struct smb1srv_session_local_allocate_state *state =
+ (struct smb1srv_session_local_allocate_state *)private_data;
+ TDB_DATA key = dbwrap_record_get_key(rec);
+ uint32_t id = 0;
+ NTSTATUS status;
+
+ status = smbXsrv_session_local_key_to_id(key, &id);
+ if (!NT_STATUS_IS_OK(status)) {
+ state->status = status;
+ return -1;
+ }
+
+ if (id <= state->last_id) {
+ state->status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ return -1;
+ }
+ state->last_id = id;
+
+ if (id > state->useable_id) {
+ state->status = NT_STATUS_OK;
+ return -1;
+ }
+
+ if (state->useable_id == state->highest_id) {
+ state->status = NT_STATUS_INSUFFICIENT_RESOURCES;
+ return -1;
+ }
+
+ state->useable_id +=1;
+ return 0;
+}
+
+static NTSTATUS smb1srv_session_local_allocate_id(struct db_context *db,
+ uint32_t lowest_id,
+ uint32_t highest_id,
+ TALLOC_CTX *mem_ctx,
+ struct db_record **_rec,
+ uint32_t *_id)
+{
+ struct smb1srv_session_local_allocate_state state = {
+ .lowest_id = lowest_id,
+ .highest_id = highest_id,
+ .last_id = 0,
+ .useable_id = lowest_id,
+ .status = NT_STATUS_INTERNAL_ERROR,
+ };
+ uint32_t i;
+ uint32_t range;
+ NTSTATUS status;
+ int count = 0;
+
+ *_rec = NULL;
+ *_id = 0;
+
+ if (lowest_id > highest_id) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ /*
+ * first we try randomly
+ */
+ range = (highest_id - lowest_id) + 1;
+
+ for (i = 0; i < (range / 2); i++) {
+ uint32_t id;
+ TDB_DATA val;
+ struct db_record *rec = NULL;
+
+ id = generate_random() % range;
+ id += lowest_id;
+
+ if (id < lowest_id) {
+ id = lowest_id;
+ }
+ if (id > highest_id) {
+ id = highest_id;
+ }
+
+ rec = smbXsrv_session_local_fetch_locked(db, id, mem_ctx);
+ if (rec == NULL) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ val = dbwrap_record_get_value(rec);
+ if (val.dsize != 0) {
+ TALLOC_FREE(rec);
+ continue;
+ }
+
+ *_rec = rec;
+ *_id = id;
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * if the range is almost full,
+ * we traverse the whole table
+ * (this relies on sorted behavior of dbwrap_rbt)
+ */
+ status = dbwrap_traverse_read(db, smb1srv_session_local_allocate_traverse,
+ &state, &count);
+ if (NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_IS_OK(state.status)) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (!NT_STATUS_EQUAL(state.status, NT_STATUS_INTERNAL_ERROR)) {
+ return state.status;
+ }
+
+ if (state.useable_id <= state.highest_id) {
+ state.status = NT_STATUS_OK;
+ } else {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+ } else if (!NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_DB_CORRUPTION)) {
+ /*
+ * Here we really expect NT_STATUS_INTERNAL_DB_CORRUPTION!
+ *
+ * If we get anything else it is an error, because it
+ * means we did not manage to find a free slot in
+ * the db.
+ */
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ if (NT_STATUS_IS_OK(state.status)) {
+ uint32_t id;
+ TDB_DATA val;
+ struct db_record *rec = NULL;
+
+ id = state.useable_id;
+
+ rec = smbXsrv_session_local_fetch_locked(db, id, mem_ctx);
+ if (rec == NULL) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ val = dbwrap_record_get_value(rec);
+ if (val.dsize != 0) {
+ TALLOC_FREE(rec);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ *_rec = rec;
+ *_id = id;
+ return NT_STATUS_OK;
+ }
+
+ return state.status;
+}
+
+struct smbXsrv_session_local_fetch_state {
+ struct smbXsrv_session *session;
+ NTSTATUS status;
+};
+
+static void smbXsrv_session_local_fetch_parser(TDB_DATA key, TDB_DATA data,
+ void *private_data)
+{
+ struct smbXsrv_session_local_fetch_state *state =
+ (struct smbXsrv_session_local_fetch_state *)private_data;
+ void *ptr;
+
+ if (data.dsize != sizeof(ptr)) {
+ state->status = NT_STATUS_INTERNAL_DB_ERROR;
+ return;
+ }
+
+ memcpy(&ptr, data.dptr, data.dsize);
+ state->session = talloc_get_type_abort(ptr, struct smbXsrv_session);
+ state->status = NT_STATUS_OK;
+}
+
+static NTSTATUS smbXsrv_session_local_lookup(struct smbXsrv_session_table *table,
+ /* conn: optional */
+ struct smbXsrv_connection *conn,
+ uint32_t session_local_id,
+ NTTIME now,
+ struct smbXsrv_session **_session)
+{
+ struct smbXsrv_session_local_fetch_state state = {
+ .session = NULL,
+ .status = NT_STATUS_INTERNAL_ERROR,
+ };
+ uint8_t key_buf[SMBXSRV_SESSION_LOCAL_TDB_KEY_SIZE];
+ TDB_DATA key;
+ NTSTATUS status;
+
+ *_session = NULL;
+
+ if (session_local_id == 0) {
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+
+ if (table == NULL) {
+ /* this might happen before the end of negprot */
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+
+ if (table->local.db_ctx == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ key = smbXsrv_session_local_id_to_key(session_local_id, key_buf);
+
+ status = dbwrap_parse_record(table->local.db_ctx, key,
+ smbXsrv_session_local_fetch_parser,
+ &state);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ return NT_STATUS_USER_SESSION_DELETED;
+ } else if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(state.status)) {
+ return state.status;
+ }
+
+ if (NT_STATUS_EQUAL(state.session->status, NT_STATUS_USER_SESSION_DELETED)) {
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+
+ /*
+ * If a connection is specified check if the session is
+ * valid on the channel.
+ */
+ if (conn != NULL) {
+ struct smbXsrv_channel_global0 *c = NULL;
+
+ status = smbXsrv_session_find_channel(state.session, conn, &c);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ state.session->idle_time = now;
+
+ if (!NT_STATUS_IS_OK(state.session->status)) {
+ *_session = state.session;
+ return state.session->status;
+ }
+
+ if (now > state.session->global->expiration_time) {
+ state.session->status = NT_STATUS_NETWORK_SESSION_EXPIRED;
+ }
+
+ *_session = state.session;
+ return state.session->status;
+}
+
+static int smbXsrv_session_global_destructor(struct smbXsrv_session_global0 *global)
+{
+ return 0;
+}
+
+static void smbXsrv_session_global_verify_record(struct db_record *db_rec,
+ bool *is_free,
+ bool *was_free,
+ TALLOC_CTX *mem_ctx,
+ struct smbXsrv_session_global0 **_g,
+ uint32_t *pseqnum);
+
+static NTSTATUS smbXsrv_session_global_allocate(struct db_context *db,
+ TALLOC_CTX *mem_ctx,
+ struct smbXsrv_session_global0 **_global)
+{
+ uint32_t i;
+ struct smbXsrv_session_global0 *global = NULL;
+ uint32_t last_free = 0;
+ const uint32_t min_tries = 3;
+
+ *_global = NULL;
+
+ global = talloc_zero(mem_ctx, struct smbXsrv_session_global0);
+ if (global == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ talloc_set_destructor(global, smbXsrv_session_global_destructor);
+
+ /*
+ * Here we just randomly try the whole 32-bit space
+ *
+ * We use just 32-bit, because we want to reuse the
+ * ID for SRVSVC.
+ */
+ for (i = 0; i < UINT32_MAX; i++) {
+ bool is_free = false;
+ bool was_free = false;
+ uint32_t id;
+
+ if (i >= min_tries && last_free != 0) {
+ id = last_free;
+ } else {
+ id = generate_random();
+ }
+ if (id == 0) {
+ id++;
+ }
+ if (id == UINT32_MAX) {
+ id--;
+ }
+
+ global->db_rec = smbXsrv_session_global_fetch_locked(db, id,
+ mem_ctx);
+ if (global->db_rec == NULL) {
+ talloc_free(global);
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ smbXsrv_session_global_verify_record(global->db_rec,
+ &is_free,
+ &was_free,
+ NULL, NULL, NULL);
+
+ if (!is_free) {
+ TALLOC_FREE(global->db_rec);
+ continue;
+ }
+
+ if (!was_free && i < min_tries) {
+ /*
+ * The session_id is free now,
+ * but was not free before.
+ *
+ * This happens if a smbd crashed
+ * and did not cleanup the record.
+ *
+ * If this is one of our first tries,
+ * then we try to find a real free one.
+ */
+ if (last_free == 0) {
+ last_free = id;
+ }
+ TALLOC_FREE(global->db_rec);
+ continue;
+ }
+
+ global->session_global_id = id;
+
+ *_global = global;
+ return NT_STATUS_OK;
+ }
+
+ /* should not be reached */
+ talloc_free(global);
+ return NT_STATUS_INTERNAL_ERROR;
+}
+
+static void smbXsrv_session_global_verify_record(struct db_record *db_rec,
+ bool *is_free,
+ bool *was_free,
+ TALLOC_CTX *mem_ctx,
+ struct smbXsrv_session_global0 **_g,
+ uint32_t *pseqnum)
+{
+ TDB_DATA key;
+ TDB_DATA val;
+ DATA_BLOB blob;
+ struct smbXsrv_session_globalB global_blob;
+ enum ndr_err_code ndr_err;
+ struct smbXsrv_session_global0 *global = NULL;
+ bool exists;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ *is_free = false;
+
+ if (was_free) {
+ *was_free = false;
+ }
+ if (_g) {
+ *_g = NULL;
+ }
+ if (pseqnum) {
+ *pseqnum = 0;
+ }
+
+ key = dbwrap_record_get_key(db_rec);
+
+ val = dbwrap_record_get_value(db_rec);
+ if (val.dsize == 0) {
+ TALLOC_FREE(frame);
+ *is_free = true;
+ if (was_free) {
+ *was_free = true;
+ }
+ return;
+ }
+
+ blob = data_blob_const(val.dptr, val.dsize);
+
+ ndr_err = ndr_pull_struct_blob(&blob, frame, &global_blob,
+ (ndr_pull_flags_fn_t)ndr_pull_smbXsrv_session_globalB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("smbXsrv_session_global_verify_record: "
+ "key '%s' ndr_pull_struct_blob - %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ TALLOC_FREE(frame);
+ *is_free = true;
+ if (was_free) {
+ *was_free = true;
+ }
+ return;
+ }
+
+ DBG_DEBUG("smbXsrv_session_global_verify_record\n");
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ NDR_PRINT_DEBUG(smbXsrv_session_globalB, &global_blob);
+ }
+
+ if (global_blob.version != SMBXSRV_VERSION_0) {
+ DBG_ERR("smbXsrv_session_global_verify_record: "
+ "key '%s' use unsupported version %u\n",
+ tdb_data_dbg(key),
+ global_blob.version);
+ NDR_PRINT_DEBUG(smbXsrv_session_globalB, &global_blob);
+ TALLOC_FREE(frame);
+ *is_free = true;
+ if (was_free) {
+ *was_free = true;
+ }
+ return;
+ }
+
+ global = global_blob.info.info0;
+
+#define __BLOB_KEEP_SECRET(__blob) do { \
+ if ((__blob).length != 0) { \
+ talloc_keep_secret((__blob).data); \
+ } \
+} while(0)
+ {
+ uint32_t i;
+ __BLOB_KEEP_SECRET(global->application_key_blob);
+ __BLOB_KEEP_SECRET(global->signing_key_blob);
+ __BLOB_KEEP_SECRET(global->encryption_key_blob);
+ __BLOB_KEEP_SECRET(global->decryption_key_blob);
+ for (i = 0; i < global->num_channels; i++) {
+ __BLOB_KEEP_SECRET(global->channels[i].signing_key_blob);
+ }
+ }
+#undef __BLOB_KEEP_SECRET
+
+ exists = serverid_exists(&global->channels[0].server_id);
+ if (!exists) {
+ struct server_id_buf idbuf;
+ DBG_NOTICE("smbXsrv_session_global_verify_record: "
+ "key '%s' server_id %s does not exist.\n",
+ tdb_data_dbg(key),
+ server_id_str_buf(global->channels[0].server_id,
+ &idbuf));
+ if (DEBUGLVL(DBGLVL_NOTICE)) {
+ NDR_PRINT_DEBUG(smbXsrv_session_globalB, &global_blob);
+ }
+ TALLOC_FREE(frame);
+ dbwrap_record_delete(db_rec);
+ *is_free = true;
+ return;
+ }
+
+ if (_g) {
+ *_g = talloc_move(mem_ctx, &global);
+ }
+ if (pseqnum) {
+ *pseqnum = global_blob.seqnum;
+ }
+ TALLOC_FREE(frame);
+}
+
+static NTSTATUS smbXsrv_session_global_store(struct smbXsrv_session_global0 *global)
+{
+ struct smbXsrv_session_globalB global_blob;
+ DATA_BLOB blob = data_blob_null;
+ TDB_DATA key;
+ TDB_DATA val;
+ NTSTATUS status;
+ enum ndr_err_code ndr_err;
+
+ /*
+ * TODO: if we use other versions than '0'
+ * we would add glue code here, that would be able to
+ * store the information in the old format.
+ */
+
+ if (global->db_rec == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ key = dbwrap_record_get_key(global->db_rec);
+ val = dbwrap_record_get_value(global->db_rec);
+
+ ZERO_STRUCT(global_blob);
+ global_blob.version = smbXsrv_version_global_current();
+ if (val.dsize >= 8) {
+ global_blob.seqnum = IVAL(val.dptr, 4);
+ }
+ global_blob.seqnum += 1;
+ global_blob.info.info0 = global;
+
+ ndr_err = ndr_push_struct_blob(&blob, global->db_rec, &global_blob,
+ (ndr_push_flags_fn_t)ndr_push_smbXsrv_session_globalB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("smbXsrv_session_global_store: key '%s' ndr_push - %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ TALLOC_FREE(global->db_rec);
+ return status;
+ }
+
+ val = make_tdb_data(blob.data, blob.length);
+ status = dbwrap_record_store(global->db_rec, val, TDB_REPLACE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("smbXsrv_session_global_store: key '%s' store - %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ TALLOC_FREE(global->db_rec);
+ return status;
+ }
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ DBG_DEBUG("smbXsrv_session_global_store: key '%s' stored\n",
+ tdb_data_dbg(key));
+ NDR_PRINT_DEBUG(smbXsrv_session_globalB, &global_blob);
+ }
+
+ TALLOC_FREE(global->db_rec);
+
+ return NT_STATUS_OK;
+}
+
+struct smb2srv_session_close_previous_state {
+ struct tevent_context *ev;
+ struct smbXsrv_connection *connection;
+ struct dom_sid *current_sid;
+ uint64_t previous_session_id;
+ uint64_t current_session_id;
+ struct db_record *db_rec;
+ uint64_t watch_instance;
+ uint32_t last_seqnum;
+};
+
+static void smb2srv_session_close_previous_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state)
+{
+ struct smb2srv_session_close_previous_state *state =
+ tevent_req_data(req,
+ struct smb2srv_session_close_previous_state);
+
+ if (state->db_rec != NULL) {
+ dbwrap_watched_watch_remove_instance(state->db_rec,
+ state->watch_instance);
+ state->watch_instance = 0;
+ TALLOC_FREE(state->db_rec);
+ }
+}
+
+static void smb2srv_session_close_previous_check(struct tevent_req *req);
+static void smb2srv_session_close_previous_modified(struct tevent_req *subreq);
+
+struct tevent_req *smb2srv_session_close_previous_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbXsrv_connection *conn,
+ struct auth_session_info *session_info,
+ uint64_t previous_session_id,
+ uint64_t current_session_id)
+{
+ struct tevent_req *req;
+ struct smb2srv_session_close_previous_state *state;
+ uint32_t global_id = previous_session_id & UINT32_MAX;
+ uint64_t global_zeros = previous_session_id & 0xFFFFFFFF00000000LLU;
+ struct smbXsrv_session_table *table = conn->client->session_table;
+ struct security_token *current_token = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb2srv_session_close_previous_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->connection = conn;
+ state->previous_session_id = previous_session_id;
+ state->current_session_id = current_session_id;
+
+ tevent_req_set_cleanup_fn(req, smb2srv_session_close_previous_cleanup);
+
+ if (global_zeros != 0) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ if (session_info == NULL) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+ current_token = session_info->security_token;
+
+ if (current_token->num_sids > PRIMARY_USER_SID_INDEX) {
+ state->current_sid = &current_token->sids[PRIMARY_USER_SID_INDEX];
+ }
+
+ if (state->current_sid == NULL) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ if (!security_token_has_nt_authenticated_users(current_token)) {
+ /* TODO */
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ state->db_rec = smbXsrv_session_global_fetch_locked(
+ table->global.db_ctx,
+ global_id,
+ state /* TALLOC_CTX */);
+ if (state->db_rec == NULL) {
+ tevent_req_nterror(req, NT_STATUS_UNSUCCESSFUL);
+ return tevent_req_post(req, ev);
+ }
+
+ smb2srv_session_close_previous_check(req);
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void smb2srv_session_close_previous_check(struct tevent_req *req)
+{
+ struct smb2srv_session_close_previous_state *state =
+ tevent_req_data(req,
+ struct smb2srv_session_close_previous_state);
+ struct smbXsrv_connection *conn = state->connection;
+ DATA_BLOB blob;
+ struct security_token *previous_token = NULL;
+ struct smbXsrv_session_global0 *global = NULL;
+ enum ndr_err_code ndr_err;
+ struct smbXsrv_session_close0 close_info0;
+ struct smbXsrv_session_closeB close_blob;
+ struct tevent_req *subreq = NULL;
+ NTSTATUS status;
+ bool is_free = false;
+ uint32_t seqnum = 0;
+
+ smbXsrv_session_global_verify_record(state->db_rec,
+ &is_free,
+ NULL,
+ state,
+ &global,
+ &seqnum);
+
+ if (is_free) {
+ tevent_req_done(req);
+ return;
+ }
+
+ if (global->auth_session_info == NULL) {
+ tevent_req_done(req);
+ return;
+ }
+
+ previous_token = global->auth_session_info->security_token;
+
+ if (!security_token_is_sid(previous_token, state->current_sid)) {
+ tevent_req_done(req);
+ return;
+ }
+
+ /*
+ * If the record changed, but we are not happy with the change yet,
+ * we better remove ourself from the waiter list
+ * (most likely the first position)
+ * and re-add us at the end of the list.
+ *
+ * This gives other waiters a change
+ * to make progress.
+ *
+ * Otherwise we'll keep our waiter instance alive,
+ * keep waiting (most likely at first position).
+ * It means the order of watchers stays fair.
+ */
+ if (state->last_seqnum != seqnum) {
+ state->last_seqnum = seqnum;
+ dbwrap_watched_watch_remove_instance(state->db_rec,
+ state->watch_instance);
+ state->watch_instance =
+ dbwrap_watched_watch_add_instance(state->db_rec);
+ }
+
+ subreq = dbwrap_watched_watch_send(state, state->ev, state->db_rec,
+ state->watch_instance,
+ (struct server_id){0});
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ smb2srv_session_close_previous_modified,
+ req);
+
+ close_info0.old_session_global_id = global->session_global_id;
+ close_info0.old_session_wire_id = global->session_wire_id;
+ close_info0.old_creation_time = global->creation_time;
+ close_info0.new_session_wire_id = state->current_session_id;
+
+ ZERO_STRUCT(close_blob);
+ close_blob.version = smbXsrv_version_global_current();
+ close_blob.info.info0 = &close_info0;
+
+ ndr_err = ndr_push_struct_blob(&blob, state, &close_blob,
+ (ndr_push_flags_fn_t)ndr_push_smbXsrv_session_closeB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("smb2srv_session_close_previous_check: "
+ "old_session[%llu] new_session[%llu] ndr_push - %s\n",
+ (unsigned long long)close_info0.old_session_wire_id,
+ (unsigned long long)close_info0.new_session_wire_id,
+ nt_errstr(status));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ status = messaging_send(conn->client->msg_ctx,
+ global->channels[0].server_id,
+ MSG_SMBXSRV_SESSION_CLOSE, &blob);
+ TALLOC_FREE(global);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ TALLOC_FREE(state->db_rec);
+ return;
+}
+
+static void smb2srv_session_close_previous_modified(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smb2srv_session_close_previous_state *state =
+ tevent_req_data(req,
+ struct smb2srv_session_close_previous_state);
+ uint32_t global_id;
+ NTSTATUS status;
+ uint64_t instance = 0;
+
+ status = dbwrap_watched_watch_recv(subreq, &instance, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->watch_instance = instance;
+
+ global_id = state->previous_session_id & UINT32_MAX;
+
+ state->db_rec = smbXsrv_session_global_fetch_locked(
+ state->connection->client->session_table->global.db_ctx,
+ global_id, state /* TALLOC_CTX */);
+ if (state->db_rec == NULL) {
+ tevent_req_nterror(req, NT_STATUS_UNSUCCESSFUL);
+ return;
+ }
+
+ smb2srv_session_close_previous_check(req);
+}
+
+NTSTATUS smb2srv_session_close_previous_recv(struct tevent_req *req)
+{
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbXsrv_session_clear_and_logoff(struct smbXsrv_session *session)
+{
+ NTSTATUS status;
+ struct smbXsrv_connection *xconn = NULL;
+
+ if (session->client != NULL) {
+ xconn = session->client->connections;
+ }
+
+ for (; xconn != NULL; xconn = xconn->next) {
+ struct smbd_smb2_request *preq;
+
+ for (preq = xconn->smb2.requests; preq != NULL; preq = preq->next) {
+ if (preq->session != session) {
+ continue;
+ }
+
+ preq->session = NULL;
+ /*
+ * If we no longer have a session we can't
+ * sign or encrypt replies.
+ */
+ preq->do_signing = false;
+ preq->do_encryption = false;
+ preq->preauth = NULL;
+ }
+ }
+
+ status = smbXsrv_session_logoff(session);
+ return status;
+}
+
+static int smbXsrv_session_destructor(struct smbXsrv_session *session)
+{
+ NTSTATUS status;
+
+ DBG_DEBUG("destructing session(%llu)\n",
+ (unsigned long long)session->global->session_wire_id);
+
+ status = smbXsrv_session_clear_and_logoff(session);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smbXsrv_session_destructor: "
+ "smbXsrv_session_logoff() failed: %s\n",
+ nt_errstr(status));
+ }
+
+ TALLOC_FREE(session->global);
+
+ return 0;
+}
+
+NTSTATUS smbXsrv_session_create(struct smbXsrv_connection *conn,
+ NTTIME now,
+ struct smbXsrv_session **_session)
+{
+ struct smbXsrv_session_table *table = conn->client->session_table;
+ struct db_record *local_rec = NULL;
+ struct smbXsrv_session *session = NULL;
+ void *ptr = NULL;
+ TDB_DATA val;
+ struct smbXsrv_session_global0 *global = NULL;
+ struct smbXsrv_channel_global0 *channel = NULL;
+ NTSTATUS status;
+
+ if (table->local.num_sessions >= table->local.max_sessions) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ session = talloc_zero(table, struct smbXsrv_session);
+ if (session == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ session->table = table;
+ session->idle_time = now;
+ session->status = NT_STATUS_MORE_PROCESSING_REQUIRED;
+ session->client = conn->client;
+ session->homes_snum = -1;
+
+ status = smbXsrv_session_global_allocate(table->global.db_ctx,
+ session,
+ &global);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(session);
+ return status;
+ }
+ session->global = global;
+
+ if (conn->protocol >= PROTOCOL_SMB2_02) {
+ uint64_t id = global->session_global_id;
+
+ global->connection_dialect = conn->smb2.server.dialect;
+ global->client_guid = conn->smb2.client.guid;
+
+ global->session_wire_id = id;
+
+ status = smb2srv_tcon_table_init(session);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(session);
+ return status;
+ }
+
+ session->local_id = global->session_global_id;
+
+ local_rec = smbXsrv_session_local_fetch_locked(
+ table->local.db_ctx,
+ session->local_id,
+ session /* TALLOC_CTX */);
+ if (local_rec == NULL) {
+ TALLOC_FREE(session);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ val = dbwrap_record_get_value(local_rec);
+ if (val.dsize != 0) {
+ TALLOC_FREE(session);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+ } else {
+
+ status = smb1srv_session_local_allocate_id(table->local.db_ctx,
+ table->local.lowest_id,
+ table->local.highest_id,
+ session,
+ &local_rec,
+ &session->local_id);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(session);
+ return status;
+ }
+
+ global->session_wire_id = session->local_id;
+ }
+
+ global->creation_time = now;
+ global->expiration_time = GENSEC_EXPIRE_TIME_INFINITY;
+
+ status = smbXsrv_session_add_channel(session, conn, now, &channel);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(session);
+ return status;
+ }
+
+ ptr = session;
+ val = make_tdb_data((uint8_t const *)&ptr, sizeof(ptr));
+ status = dbwrap_record_store(local_rec, val, TDB_REPLACE);
+ TALLOC_FREE(local_rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(session);
+ return status;
+ }
+ table->local.num_sessions += 1;
+
+ talloc_set_destructor(session, smbXsrv_session_destructor);
+
+ status = smbXsrv_session_global_store(global);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smbXsrv_session_create: "
+ "global_id (0x%08x) store failed - %s\n",
+ session->global->session_global_id,
+ nt_errstr(status));
+ TALLOC_FREE(session);
+ return status;
+ }
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ struct smbXsrv_sessionB session_blob = {
+ .version = SMBXSRV_VERSION_0,
+ .info.info0 = session,
+ };
+
+ DBG_DEBUG("smbXsrv_session_create: global_id (0x%08x) stored\n",
+ session->global->session_global_id);
+ NDR_PRINT_DEBUG(smbXsrv_sessionB, &session_blob);
+ }
+
+ *_session = session;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smbXsrv_session_add_channel(struct smbXsrv_session *session,
+ struct smbXsrv_connection *conn,
+ NTTIME now,
+ struct smbXsrv_channel_global0 **_c)
+{
+ struct smbXsrv_session_global0 *global = session->global;
+ struct smbXsrv_channel_global0 *c = NULL;
+
+ if (global->num_channels > 31) {
+ /*
+ * Windows allow up to 32 channels
+ */
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ c = talloc_realloc(global,
+ global->channels,
+ struct smbXsrv_channel_global0,
+ global->num_channels + 1);
+ if (c == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ global->channels = c;
+
+ c = &global->channels[global->num_channels];
+ ZERO_STRUCTP(c);
+
+ c->server_id = messaging_server_id(conn->client->msg_ctx);
+ c->channel_id = conn->channel_id;
+ c->creation_time = now;
+ c->local_address = tsocket_address_string(conn->local_address,
+ global->channels);
+ if (c->local_address == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ c->remote_address = tsocket_address_string(conn->remote_address,
+ global->channels);
+ if (c->remote_address == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ c->remote_name = talloc_strdup(global->channels,
+ conn->remote_hostname);
+ if (c->remote_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ c->connection = conn;
+
+ global->num_channels += 1;
+
+ *_c = c;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smbXsrv_session_update(struct smbXsrv_session *session)
+{
+ struct smbXsrv_session_table *table = session->table;
+ NTSTATUS status;
+
+ if (session->global->db_rec != NULL) {
+ DBG_ERR("smbXsrv_session_update(0x%08x): "
+ "Called with db_rec != NULL'\n",
+ session->global->session_global_id);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (table == NULL) {
+ DBG_ERR("smbXsrv_session_update(0x%08x): "
+ "Called with table == NULL'\n",
+ session->global->session_global_id);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ session->global->db_rec = smbXsrv_session_global_fetch_locked(
+ table->global.db_ctx,
+ session->global->session_global_id,
+ session->global /* TALLOC_CTX */);
+ if (session->global->db_rec == NULL) {
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ status = smbXsrv_session_global_store(session->global);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smbXsrv_session_update: "
+ "global_id (0x%08x) store failed - %s\n",
+ session->global->session_global_id,
+ nt_errstr(status));
+ return status;
+ }
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ struct smbXsrv_sessionB session_blob = {
+ .version = SMBXSRV_VERSION_0,
+ .info.info0 = session,
+ };
+
+ DBG_DEBUG("smbXsrv_session_update: global_id (0x%08x) stored\n",
+ session->global->session_global_id);
+ NDR_PRINT_DEBUG(smbXsrv_sessionB, &session_blob);
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smbXsrv_session_find_channel(const struct smbXsrv_session *session,
+ const struct smbXsrv_connection *conn,
+ struct smbXsrv_channel_global0 **_c)
+{
+ uint32_t i;
+
+ for (i=0; i < session->global->num_channels; i++) {
+ struct smbXsrv_channel_global0 *c = &session->global->channels[i];
+
+ if (c->channel_id != conn->channel_id) {
+ continue;
+ }
+
+ if (c->connection != conn) {
+ continue;
+ }
+
+ *_c = c;
+ return NT_STATUS_OK;
+ }
+
+ return NT_STATUS_USER_SESSION_DELETED;
+}
+
+NTSTATUS smbXsrv_session_find_auth(const struct smbXsrv_session *session,
+ const struct smbXsrv_connection *conn,
+ NTTIME now,
+ struct smbXsrv_session_auth0 **_a)
+{
+ struct smbXsrv_session_auth0 *a;
+
+ for (a = session->pending_auth; a != NULL; a = a->next) {
+ if (a->channel_id != conn->channel_id) {
+ continue;
+ }
+
+ if (a->connection == conn) {
+ if (now != 0) {
+ a->idle_time = now;
+ }
+ *_a = a;
+ return NT_STATUS_OK;
+ }
+ }
+
+ return NT_STATUS_USER_SESSION_DELETED;
+}
+
+static int smbXsrv_session_auth0_destructor(struct smbXsrv_session_auth0 *a)
+{
+ if (a->session == NULL) {
+ return 0;
+ }
+
+ DLIST_REMOVE(a->session->pending_auth, a);
+ a->session = NULL;
+ return 0;
+}
+
+NTSTATUS smbXsrv_session_create_auth(struct smbXsrv_session *session,
+ struct smbXsrv_connection *conn,
+ NTTIME now,
+ uint8_t in_flags,
+ uint8_t in_security_mode,
+ struct smbXsrv_session_auth0 **_a)
+{
+ struct smbXsrv_session_auth0 *a;
+ NTSTATUS status;
+
+ status = smbXsrv_session_find_auth(session, conn, 0, &a);
+ if (NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ a = talloc_zero(session, struct smbXsrv_session_auth0);
+ if (a == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ a->session = session;
+ a->connection = conn;
+ a->in_flags = in_flags;
+ a->in_security_mode = in_security_mode;
+ a->creation_time = now;
+ a->idle_time = now;
+ a->channel_id = conn->channel_id;
+
+ if (conn->protocol >= PROTOCOL_SMB3_11) {
+ a->preauth = talloc(a, struct smbXsrv_preauth);
+ if (a->preauth == NULL) {
+ TALLOC_FREE(session);
+ return NT_STATUS_NO_MEMORY;
+ }
+ *a->preauth = conn->smb2.preauth;
+ }
+
+ talloc_set_destructor(a, smbXsrv_session_auth0_destructor);
+ DLIST_ADD_END(session->pending_auth, a);
+
+ *_a = a;
+ return NT_STATUS_OK;
+}
+
+static void smbXsrv_session_remove_channel_done(struct tevent_req *subreq);
+
+NTSTATUS smbXsrv_session_remove_channel(struct smbXsrv_session *session,
+ struct smbXsrv_connection *xconn)
+{
+ struct smbXsrv_session_auth0 *a = NULL;
+ struct smbXsrv_channel_global0 *c = NULL;
+ NTSTATUS status;
+ bool need_update = false;
+
+ status = smbXsrv_session_find_auth(session, xconn, 0, &a);
+ if (!NT_STATUS_IS_OK(status)) {
+ a = NULL;
+ }
+ status = smbXsrv_session_find_channel(session, xconn, &c);
+ if (!NT_STATUS_IS_OK(status)) {
+ c = NULL;
+ }
+
+ if (a != NULL) {
+ smbXsrv_session_auth0_destructor(a);
+ a->connection = NULL;
+ need_update = true;
+ }
+
+ if (c != NULL) {
+ struct smbXsrv_session_global0 *global = session->global;
+ ptrdiff_t n;
+
+ n = (c - global->channels);
+ if (n >= global->num_channels || n < 0) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ ARRAY_DEL_ELEMENT(global->channels, n, global->num_channels);
+ global->num_channels--;
+ if (global->num_channels == 0) {
+ struct smbXsrv_client *client = session->client;
+ struct tevent_queue *xconn_wait_queue =
+ xconn->transport.shutdown_wait_queue;
+ struct tevent_req *subreq = NULL;
+
+ /*
+ * Let the connection wait until the session is
+ * destroyed.
+ *
+ * We don't set a callback, as we just want to block the
+ * wait queue and the talloc_free() of the session will
+ * remove the item from the wait queue in order
+ * to remove allow the connection to disappear.
+ */
+ if (xconn_wait_queue != NULL) {
+ subreq = tevent_queue_wait_send(session,
+ client->raw_ev_ctx,
+ xconn_wait_queue);
+ if (subreq == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ DBG_ERR("tevent_queue_wait_send() session(%llu) failed: %s\n",
+ (unsigned long long)session->global->session_wire_id,
+ nt_errstr(status));
+ return status;
+ }
+ }
+
+ /*
+ * This is guaranteed to set
+ * session->status = NT_STATUS_USER_SESSION_DELETED
+ * even if NULL is returned.
+ */
+ subreq = smb2srv_session_shutdown_send(session,
+ client->raw_ev_ctx,
+ session,
+ NULL);
+ if (subreq == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ DBG_ERR("smb2srv_session_shutdown_send(%llu) failed: %s\n",
+ (unsigned long long)session->global->session_wire_id,
+ nt_errstr(status));
+ return status;
+ }
+ tevent_req_set_callback(subreq,
+ smbXsrv_session_remove_channel_done,
+ session);
+ }
+ need_update = true;
+ }
+
+ if (!need_update) {
+ return NT_STATUS_OK;
+ }
+
+ return smbXsrv_session_update(session);
+}
+
+static void smbXsrv_session_remove_channel_done(struct tevent_req *subreq)
+{
+ struct smbXsrv_session *session =
+ tevent_req_callback_data(subreq,
+ struct smbXsrv_session);
+ NTSTATUS status;
+
+ status = smb2srv_session_shutdown_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smb2srv_session_shutdown_recv(%llu) failed: %s\n",
+ (unsigned long long)session->global->session_wire_id,
+ nt_errstr(status));
+ }
+
+ status = smbXsrv_session_logoff(session);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smbXsrv_session_logoff(%llu) failed: %s\n",
+ (unsigned long long)session->global->session_wire_id,
+ nt_errstr(status));
+ }
+
+ TALLOC_FREE(session);
+}
+
+struct smb2srv_session_shutdown_state {
+ struct tevent_queue *wait_queue;
+};
+
+static void smb2srv_session_shutdown_wait_done(struct tevent_req *subreq);
+
+struct tevent_req *smb2srv_session_shutdown_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbXsrv_session *session,
+ struct smbd_smb2_request *current_req)
+{
+ struct tevent_req *req;
+ struct smb2srv_session_shutdown_state *state;
+ struct tevent_req *subreq;
+ struct smbXsrv_connection *xconn = NULL;
+ size_t len = 0;
+
+ /*
+ * Make sure that no new request will be able to use this session.
+ */
+ session->status = NT_STATUS_USER_SESSION_DELETED;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb2srv_session_shutdown_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->wait_queue = tevent_queue_create(state, "smb2srv_session_shutdown_queue");
+ if (tevent_req_nomem(state->wait_queue, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ for (xconn = session->client->connections; xconn != NULL; xconn = xconn->next) {
+ struct smbd_smb2_request *preq;
+
+ for (preq = xconn->smb2.requests; preq != NULL; preq = preq->next) {
+ if (preq == current_req) {
+ /* Can't cancel current request. */
+ continue;
+ }
+ if (preq->session != session) {
+ /* Request on different session. */
+ continue;
+ }
+
+ if (preq->subreq != NULL) {
+ tevent_req_cancel(preq->subreq);
+ }
+
+ /*
+ * Now wait until the request is finished.
+ *
+ * We don't set a callback, as we just want to block the
+ * wait queue and the talloc_free() of the request will
+ * remove the item from the wait queue.
+ */
+ subreq = tevent_queue_wait_send(preq, ev, state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+ }
+
+ len = tevent_queue_length(state->wait_queue);
+ if (len == 0) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * Now we add our own waiter to the end of the queue,
+ * this way we get notified when all pending requests are finished
+ * and send to the socket.
+ */
+ subreq = tevent_queue_wait_send(state, ev, state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smb2srv_session_shutdown_wait_done, req);
+
+ return req;
+}
+
+static void smb2srv_session_shutdown_wait_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+
+ tevent_queue_wait_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ tevent_req_done(req);
+}
+
+NTSTATUS smb2srv_session_shutdown_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS smbXsrv_session_logoff(struct smbXsrv_session *session)
+{
+ struct smbXsrv_session_table *table;
+ struct db_record *local_rec = NULL;
+ struct db_record *global_rec = NULL;
+ struct smbd_server_connection *sconn = NULL;
+ NTSTATUS status;
+ NTSTATUS error = NT_STATUS_OK;
+
+ if (session->table == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ table = session->table;
+ session->table = NULL;
+
+ sconn = session->client->sconn;
+ session->client = NULL;
+ session->status = NT_STATUS_USER_SESSION_DELETED;
+
+ /*
+ * For SMB2 this is a bit redundant as files are also close
+ * below via smb2srv_tcon_disconnect_all() -> ... ->
+ * smbXsrv_tcon_disconnect() -> close_cnum() ->
+ * file_close_conn().
+ */
+ file_close_user(sconn, session->global->session_wire_id);
+
+ if (session->tcon_table != NULL) {
+ /*
+ * Note: We only have a tcon_table for SMB2.
+ */
+ status = smb2srv_tcon_disconnect_all(session);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smbXsrv_session_logoff(0x%08x): "
+ "smb2srv_tcon_disconnect_all() failed: %s\n",
+ session->global->session_global_id,
+ nt_errstr(status));
+ error = status;
+ }
+ }
+
+ invalidate_vuid(sconn, session->global->session_wire_id);
+
+ global_rec = session->global->db_rec;
+ session->global->db_rec = NULL;
+ if (global_rec == NULL) {
+ global_rec = smbXsrv_session_global_fetch_locked(
+ table->global.db_ctx,
+ session->global->session_global_id,
+ session->global /* TALLOC_CTX */);
+ if (global_rec == NULL) {
+ error = NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ if (global_rec != NULL) {
+ status = dbwrap_record_delete(global_rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ TDB_DATA key = dbwrap_record_get_key(global_rec);
+
+ DBG_ERR("smbXsrv_session_logoff(0x%08x): "
+ "failed to delete global key '%s': %s\n",
+ session->global->session_global_id,
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ error = status;
+ }
+ }
+ TALLOC_FREE(global_rec);
+
+ local_rec = session->db_rec;
+ if (local_rec == NULL) {
+ local_rec = smbXsrv_session_local_fetch_locked(
+ table->local.db_ctx,
+ session->local_id,
+ session /* TALLOC_CTX */);
+ if (local_rec == NULL) {
+ error = NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ if (local_rec != NULL) {
+ status = dbwrap_record_delete(local_rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ TDB_DATA key = dbwrap_record_get_key(local_rec);
+
+ DBG_ERR("smbXsrv_session_logoff(0x%08x): "
+ "failed to delete local key '%s': %s\n",
+ session->global->session_global_id,
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ error = status;
+ }
+ table->local.num_sessions -= 1;
+ }
+ if (session->db_rec == NULL) {
+ TALLOC_FREE(local_rec);
+ }
+ session->db_rec = NULL;
+
+ return error;
+}
+
+struct smbXsrv_session_logoff_all_state {
+ NTSTATUS first_status;
+ int errors;
+};
+
+static int smbXsrv_session_logoff_all_callback(struct db_record *local_rec,
+ void *private_data);
+
+NTSTATUS smbXsrv_session_logoff_all(struct smbXsrv_client *client)
+{
+ struct smbXsrv_session_table *table = client->session_table;
+ struct smbXsrv_session_logoff_all_state state;
+ NTSTATUS status;
+ int count = 0;
+
+ if (table == NULL) {
+ DBG_DEBUG("smbXsrv_session_logoff_all: "
+ "empty session_table, nothing to do.\n");
+ return NT_STATUS_OK;
+ }
+
+ ZERO_STRUCT(state);
+
+ status = dbwrap_traverse(table->local.db_ctx,
+ smbXsrv_session_logoff_all_callback,
+ &state, &count);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smbXsrv_session_logoff_all: "
+ "dbwrap_traverse() failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ if (!NT_STATUS_IS_OK(state.first_status)) {
+ DBG_ERR("smbXsrv_session_logoff_all: "
+ "count[%d] errors[%d] first[%s]\n",
+ count, state.errors,
+ nt_errstr(state.first_status));
+ return state.first_status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static int smbXsrv_session_logoff_all_callback(struct db_record *local_rec,
+ void *private_data)
+{
+ struct smbXsrv_session_logoff_all_state *state =
+ (struct smbXsrv_session_logoff_all_state *)private_data;
+ TDB_DATA val;
+ void *ptr = NULL;
+ struct smbXsrv_session *session = NULL;
+ NTSTATUS status;
+
+ val = dbwrap_record_get_value(local_rec);
+ if (val.dsize != sizeof(ptr)) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ if (NT_STATUS_IS_OK(state->first_status)) {
+ state->first_status = status;
+ }
+ state->errors++;
+ return 0;
+ }
+
+ memcpy(&ptr, val.dptr, val.dsize);
+ session = talloc_get_type_abort(ptr, struct smbXsrv_session);
+
+ session->db_rec = local_rec;
+ status = smbXsrv_session_clear_and_logoff(session);
+ session->db_rec = NULL;
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_IS_OK(state->first_status)) {
+ state->first_status = status;
+ }
+ state->errors++;
+ return 0;
+ }
+
+ return 0;
+}
+
+struct smbXsrv_session_local_trav_state {
+ NTSTATUS status;
+ int (*caller_cb)(struct smbXsrv_session *session,
+ void *caller_data);
+ void *caller_data;
+};
+
+static int smbXsrv_session_local_traverse_cb(struct db_record *local_rec,
+ void *private_data);
+
+NTSTATUS smbXsrv_session_local_traverse(
+ struct smbXsrv_client *client,
+ int (*caller_cb)(struct smbXsrv_session *session,
+ void *caller_data),
+ void *caller_data)
+{
+ struct smbXsrv_session_table *table = client->session_table;
+ struct smbXsrv_session_local_trav_state state;
+ NTSTATUS status;
+ int count = 0;
+
+ state = (struct smbXsrv_session_local_trav_state) {
+ .status = NT_STATUS_OK,
+ .caller_cb = caller_cb,
+ .caller_data = caller_data,
+ };
+
+ if (table == NULL) {
+ DBG_DEBUG("empty session_table, nothing to do.\n");
+ return NT_STATUS_OK;
+ }
+
+ status = dbwrap_traverse(table->local.db_ctx,
+ smbXsrv_session_local_traverse_cb,
+ &state,
+ &count);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("dbwrap_traverse() failed: %s\n", nt_errstr(status));
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(state.status)) {
+ DBG_ERR("count[%d] status[%s]\n",
+ count, nt_errstr(state.status));
+ return state.status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static int smbXsrv_session_local_traverse_cb(struct db_record *local_rec,
+ void *private_data)
+{
+ struct smbXsrv_session_local_trav_state *state =
+ (struct smbXsrv_session_local_trav_state *)private_data;
+ TDB_DATA val;
+ void *ptr = NULL;
+ struct smbXsrv_session *session = NULL;
+ int ret;
+
+ val = dbwrap_record_get_value(local_rec);
+ if (val.dsize != sizeof(ptr)) {
+ state->status = NT_STATUS_INTERNAL_ERROR;
+ return -1;
+ }
+
+ memcpy(&ptr, val.dptr, val.dsize);
+ session = talloc_get_type_abort(ptr, struct smbXsrv_session);
+
+ session->db_rec = local_rec;
+ ret = state->caller_cb(session, state->caller_data);
+ session->db_rec = NULL;
+
+ return ret;
+}
+
+struct smbXsrv_session_disconnect_xconn_state {
+ struct smbXsrv_connection *xconn;
+ NTSTATUS first_status;
+ int errors;
+};
+
+static int smbXsrv_session_disconnect_xconn_callback(struct db_record *local_rec,
+ void *private_data);
+
+NTSTATUS smbXsrv_session_disconnect_xconn(struct smbXsrv_connection *xconn)
+{
+ struct smbXsrv_client *client = xconn->client;
+ struct smbXsrv_session_table *table = client->session_table;
+ struct smbXsrv_session_disconnect_xconn_state state;
+ NTSTATUS status;
+ int count = 0;
+
+ if (table == NULL) {
+ DBG_ERR("empty session_table, nothing to do.\n");
+ return NT_STATUS_OK;
+ }
+
+ ZERO_STRUCT(state);
+ state.xconn = xconn;
+
+ status = dbwrap_traverse(table->local.db_ctx,
+ smbXsrv_session_disconnect_xconn_callback,
+ &state, &count);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("dbwrap_traverse() failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ if (!NT_STATUS_IS_OK(state.first_status)) {
+ DBG_ERR("count[%d] errors[%d] first[%s]\n",
+ count, state.errors,
+ nt_errstr(state.first_status));
+ return state.first_status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static int smbXsrv_session_disconnect_xconn_callback(struct db_record *local_rec,
+ void *private_data)
+{
+ struct smbXsrv_session_disconnect_xconn_state *state =
+ (struct smbXsrv_session_disconnect_xconn_state *)private_data;
+ TDB_DATA val;
+ void *ptr = NULL;
+ struct smbXsrv_session *session = NULL;
+ NTSTATUS status;
+
+ val = dbwrap_record_get_value(local_rec);
+ if (val.dsize != sizeof(ptr)) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ if (NT_STATUS_IS_OK(state->first_status)) {
+ state->first_status = status;
+ }
+ state->errors++;
+ return 0;
+ }
+
+ memcpy(&ptr, val.dptr, val.dsize);
+ session = talloc_get_type_abort(ptr, struct smbXsrv_session);
+
+ session->db_rec = local_rec;
+ status = smbXsrv_session_remove_channel(session, state->xconn);
+ session->db_rec = NULL;
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_IS_OK(state->first_status)) {
+ state->first_status = status;
+ }
+ state->errors++;
+ }
+
+ return 0;
+}
+
+NTSTATUS smb1srv_session_table_init(struct smbXsrv_connection *conn)
+{
+ /*
+ * Allow a range from 1..65534 with 65534 values.
+ */
+ return smbXsrv_session_table_init(conn, 1, UINT16_MAX - 1,
+ UINT16_MAX - 1);
+}
+
+NTSTATUS smb1srv_session_lookup(struct smbXsrv_connection *conn,
+ uint16_t vuid, NTTIME now,
+ struct smbXsrv_session **session)
+{
+ struct smbXsrv_session_table *table = conn->client->session_table;
+ uint32_t local_id = vuid;
+
+ return smbXsrv_session_local_lookup(table, conn, local_id, now,
+ session);
+}
+
+NTSTATUS smbXsrv_session_info_lookup(struct smbXsrv_client *client,
+ uint64_t session_wire_id,
+ struct auth_session_info **si)
+{
+ struct smbXsrv_session_table *table = client->session_table;
+ uint8_t key_buf[SMBXSRV_SESSION_LOCAL_TDB_KEY_SIZE];
+ struct smbXsrv_session_local_fetch_state state = {
+ .session = NULL,
+ .status = NT_STATUS_INTERNAL_ERROR,
+ };
+ TDB_DATA key;
+ NTSTATUS status;
+
+ if (session_wire_id == 0) {
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+
+ if (table == NULL) {
+ /* this might happen before the end of negprot */
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+
+ if (table->local.db_ctx == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ key = smbXsrv_session_local_id_to_key(session_wire_id, key_buf);
+
+ status = dbwrap_parse_record(table->local.db_ctx, key,
+ smbXsrv_session_local_fetch_parser,
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(state.status)) {
+ return state.status;
+ }
+ if (state.session->global->auth_session_info == NULL) {
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+
+ *si = state.session->global->auth_session_info;
+ return NT_STATUS_OK;
+}
+
+/*
+ * In memory of get_valid_user_struct()
+ *
+ * This function is similar to smbXsrv_session_local_lookup() and it's wrappers,
+ * but it doesn't implement the state checks of
+ * those. get_valid_smbXsrv_session() is NOT meant to be called to validate the
+ * session wire-id of incoming SMB requests, it MUST only be used in later
+ * internal processing where the session wire-id has already been validated.
+ */
+NTSTATUS get_valid_smbXsrv_session(struct smbXsrv_client *client,
+ uint64_t session_wire_id,
+ struct smbXsrv_session **session)
+{
+ struct smbXsrv_session_table *table = client->session_table;
+ uint8_t key_buf[SMBXSRV_SESSION_LOCAL_TDB_KEY_SIZE];
+ struct smbXsrv_session_local_fetch_state state = {
+ .session = NULL,
+ .status = NT_STATUS_INTERNAL_ERROR,
+ };
+ TDB_DATA key;
+ NTSTATUS status;
+
+ if (session_wire_id == 0) {
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+
+ if (table == NULL) {
+ /* this might happen before the end of negprot */
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+
+ if (table->local.db_ctx == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ key = smbXsrv_session_local_id_to_key(session_wire_id, key_buf);
+
+ status = dbwrap_parse_record(table->local.db_ctx, key,
+ smbXsrv_session_local_fetch_parser,
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(state.status)) {
+ return state.status;
+ }
+ if (state.session->global->auth_session_info == NULL) {
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+
+ *session = state.session;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smb2srv_session_lookup_global(struct smbXsrv_client *client,
+ uint64_t session_wire_id,
+ TALLOC_CTX *mem_ctx,
+ struct smbXsrv_session **_session)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct smbXsrv_session_table *table = client->session_table;
+ uint32_t global_id = session_wire_id & UINT32_MAX;
+ uint64_t global_zeros = session_wire_id & 0xFFFFFFFF00000000LLU;
+ struct smbXsrv_session *session = NULL;
+ struct db_record *global_rec = NULL;
+ bool is_free = false;
+ NTSTATUS status;
+
+ if (global_id == 0) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+ if (global_zeros != 0) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+
+ if (table == NULL) {
+ /* this might happen before the end of negprot */
+ TALLOC_FREE(frame);
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+
+ if (table->global.db_ctx == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ session = talloc_zero(mem_ctx, struct smbXsrv_session);
+ if (session == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ talloc_steal(frame, session);
+
+ session->client = client;
+ session->status = NT_STATUS_BAD_LOGON_SESSION_STATE;
+ session->local_id = global_id;
+
+ /*
+ * This means smb2_get_new_nonce() will return
+ * NT_STATUS_ENCRYPTION_FAILED.
+ *
+ * But we initialize some random parts just in case...
+ */
+ session->nonce_high_max = session->nonce_high = 0;
+ generate_nonce_buffer((uint8_t *)&session->nonce_high_random,
+ sizeof(session->nonce_high_random));
+ generate_nonce_buffer((uint8_t *)&session->nonce_low,
+ sizeof(session->nonce_low));
+
+ global_rec = smbXsrv_session_global_fetch_locked(table->global.db_ctx,
+ global_id,
+ frame);
+ if (global_rec == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ smbXsrv_session_global_verify_record(global_rec,
+ &is_free,
+ NULL,
+ session,
+ &session->global,
+ NULL);
+ if (is_free) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+
+ /*
+ * We don't have channels on this session
+ * and only the main signing key
+ */
+ session->global->num_channels = 0;
+ status = smb2_signing_key_sign_create(session->global,
+ session->global->signing_algo,
+ NULL, /* no master key */
+ NULL, /* derivations */
+ &session->global->signing_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ session->global->signing_key->blob = session->global->signing_key_blob;
+ session->global->signing_flags = 0;
+
+ status = smb2_signing_key_cipher_create(session->global,
+ session->global->encryption_cipher,
+ NULL, /* no master key */
+ NULL, /* derivations */
+ &session->global->decryption_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ session->global->decryption_key->blob = session->global->decryption_key_blob;
+ session->global->encryption_flags = 0;
+
+ *_session = talloc_move(mem_ctx, &session);
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smb2srv_session_table_init(struct smbXsrv_connection *conn)
+{
+ /*
+ * Allow a range from 1..4294967294 with 65534 (same as SMB1) values.
+ */
+ return smbXsrv_session_table_init(conn, 1, UINT32_MAX - 1,
+ UINT16_MAX - 1);
+}
+
+static NTSTATUS smb2srv_session_lookup_raw(struct smbXsrv_session_table *table,
+ /* conn: optional */
+ struct smbXsrv_connection *conn,
+ uint64_t session_id, NTTIME now,
+ struct smbXsrv_session **session)
+{
+ uint32_t local_id = session_id & UINT32_MAX;
+ uint64_t local_zeros = session_id & 0xFFFFFFFF00000000LLU;
+
+ if (local_zeros != 0) {
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+
+ return smbXsrv_session_local_lookup(table, conn, local_id, now,
+ session);
+}
+
+NTSTATUS smb2srv_session_lookup_conn(struct smbXsrv_connection *conn,
+ uint64_t session_id, NTTIME now,
+ struct smbXsrv_session **session)
+{
+ struct smbXsrv_session_table *table = conn->client->session_table;
+ return smb2srv_session_lookup_raw(table, conn, session_id, now,
+ session);
+}
+
+NTSTATUS smb2srv_session_lookup_client(struct smbXsrv_client *client,
+ uint64_t session_id, NTTIME now,
+ struct smbXsrv_session **session)
+{
+ struct smbXsrv_session_table *table = client->session_table;
+ return smb2srv_session_lookup_raw(table, NULL, session_id, now,
+ session);
+}
+
+struct smbXsrv_session_global_traverse_state {
+ int (*fn)(struct smbXsrv_session_global0 *, void *);
+ void *private_data;
+};
+
+static int smbXsrv_session_global_traverse_fn(struct db_record *rec, void *data)
+{
+ int ret = -1;
+ struct smbXsrv_session_global_traverse_state *state =
+ (struct smbXsrv_session_global_traverse_state*)data;
+ TDB_DATA key = dbwrap_record_get_key(rec);
+ TDB_DATA val = dbwrap_record_get_value(rec);
+ DATA_BLOB blob = data_blob_const(val.dptr, val.dsize);
+ struct smbXsrv_session_globalB global_blob;
+ enum ndr_err_code ndr_err;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ndr_err = ndr_pull_struct_blob(&blob, frame, &global_blob,
+ (ndr_pull_flags_fn_t)ndr_pull_smbXsrv_session_globalB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_WARNING("Invalid record in smbXsrv_session_global.tdb:"
+ "key '%s' ndr_pull_struct_blob - %s\n",
+ tdb_data_dbg(key),
+ ndr_errstr(ndr_err));
+ goto done;
+ }
+
+ if (global_blob.version != SMBXSRV_VERSION_0) {
+ DBG_WARNING("Invalid record in smbXsrv_session_global.tdb:"
+ "key '%s' unsupported version - %d\n",
+ tdb_data_dbg(key),
+ (int)global_blob.version);
+ goto done;
+ }
+
+ if (global_blob.info.info0 == NULL) {
+ DBG_WARNING("Invalid record in smbXsrv_tcon_global.tdb:"
+ "key '%s' info0 NULL pointer\n",
+ tdb_data_dbg(key));
+ goto done;
+ }
+
+ global_blob.info.info0->db_rec = rec;
+ ret = state->fn(global_blob.info.info0, state->private_data);
+done:
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+NTSTATUS smbXsrv_session_global_traverse(
+ int (*fn)(struct smbXsrv_session_global0 *, void *),
+ void *private_data)
+{
+
+ NTSTATUS status;
+ int count = 0;
+ struct smbXsrv_session_global_traverse_state state = {
+ .fn = fn,
+ .private_data = private_data,
+ };
+
+ become_root();
+ status = smbXsrv_session_global_init(NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ unbecome_root();
+ DBG_ERR("Failed to initialize session_global: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ status = dbwrap_traverse_read(smbXsrv_session_global_db_ctx,
+ smbXsrv_session_global_traverse_fn,
+ &state,
+ &count);
+ unbecome_root();
+
+ return status;
+}
diff --git a/source3/smbd/smbXsrv_tcon.c b/source3/smbd/smbXsrv_tcon.c
new file mode 100644
index 0000000..2f25246
--- /dev/null
+++ b/source3/smbd/smbXsrv_tcon.c
@@ -0,0 +1,1272 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Stefan Metzmacher 2011-2012
+ Copyright (C) Michael Adam 2012
+
+ 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 "system/filesys.h"
+#include "lib/util/server_id.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_rbt.h"
+#include "dbwrap/dbwrap_open.h"
+#include "messages.h"
+#include "lib/util/util_tdb.h"
+#include "librpc/gen_ndr/ndr_smbXsrv.h"
+#include "serverid.h"
+#include "source3/include/util_tdb.h"
+
+struct smbXsrv_tcon_table {
+ struct {
+ struct db_context *db_ctx;
+ uint32_t lowest_id;
+ uint32_t highest_id;
+ uint32_t max_tcons;
+ uint32_t num_tcons;
+ } local;
+ struct {
+ struct db_context *db_ctx;
+ } global;
+};
+
+static struct db_context *smbXsrv_tcon_global_db_ctx = NULL;
+
+NTSTATUS smbXsrv_tcon_global_init(void)
+{
+ char *global_path = NULL;
+ struct db_context *db_ctx = NULL;
+
+ if (smbXsrv_tcon_global_db_ctx != NULL) {
+ return NT_STATUS_OK;
+ }
+
+ global_path = lock_path(talloc_tos(), "smbXsrv_tcon_global.tdb");
+ if (global_path == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ db_ctx = db_open(NULL, global_path,
+ SMBD_VOLATILE_TDB_HASH_SIZE,
+ SMBD_VOLATILE_TDB_FLAGS,
+ O_RDWR | O_CREAT, 0600,
+ DBWRAP_LOCK_ORDER_1,
+ DBWRAP_FLAG_NONE);
+ TALLOC_FREE(global_path);
+ if (db_ctx == NULL) {
+ NTSTATUS status;
+
+ status = map_nt_error_from_unix_common(errno);
+
+ return status;
+ }
+
+ smbXsrv_tcon_global_db_ctx = db_ctx;
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * NOTE:
+ * We need to store the keys in big endian so that dbwrap_rbt's memcmp
+ * has the same result as integer comparison between the uint32_t
+ * values.
+ *
+ * TODO: implement string based key
+ */
+
+#define SMBXSRV_TCON_GLOBAL_TDB_KEY_SIZE sizeof(uint32_t)
+
+static TDB_DATA smbXsrv_tcon_global_id_to_key(uint32_t id,
+ uint8_t *key_buf)
+{
+ TDB_DATA key;
+
+ RSIVAL(key_buf, 0, id);
+
+ key = make_tdb_data(key_buf, SMBXSRV_TCON_GLOBAL_TDB_KEY_SIZE);
+
+ return key;
+}
+
+#if 0
+static NTSTATUS smbXsrv_tcon_global_key_to_id(TDB_DATA key, uint32_t *id)
+{
+ if (id == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (key.dsize != SMBXSRV_TCON_GLOBAL_TDB_KEY_SIZE) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ *id = RIVAL(key.dptr, 0);
+
+ return NT_STATUS_OK;
+}
+#endif
+
+#define SMBXSRV_TCON_LOCAL_TDB_KEY_SIZE sizeof(uint32_t)
+
+static TDB_DATA smbXsrv_tcon_local_id_to_key(uint32_t id,
+ uint8_t *key_buf)
+{
+ TDB_DATA key;
+
+ RSIVAL(key_buf, 0, id);
+
+ key = make_tdb_data(key_buf, SMBXSRV_TCON_LOCAL_TDB_KEY_SIZE);
+
+ return key;
+}
+
+static NTSTATUS smbXsrv_tcon_local_key_to_id(TDB_DATA key, uint32_t *id)
+{
+ if (id == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (key.dsize != SMBXSRV_TCON_LOCAL_TDB_KEY_SIZE) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ *id = RIVAL(key.dptr, 0);
+
+ return NT_STATUS_OK;
+}
+
+static struct db_record *smbXsrv_tcon_global_fetch_locked(
+ struct db_context *db,
+ uint32_t id,
+ TALLOC_CTX *mem_ctx)
+{
+ TDB_DATA key;
+ uint8_t key_buf[SMBXSRV_TCON_GLOBAL_TDB_KEY_SIZE];
+ struct db_record *rec = NULL;
+
+ key = smbXsrv_tcon_global_id_to_key(id, key_buf);
+
+ rec = dbwrap_fetch_locked(db, mem_ctx, key);
+
+ if (rec == NULL) {
+ DBG_DEBUG("Failed to lock global id 0x%08x, key '%s'\n", id,
+ tdb_data_dbg(key));
+ }
+
+ return rec;
+}
+
+static struct db_record *smbXsrv_tcon_local_fetch_locked(
+ struct db_context *db,
+ uint32_t id,
+ TALLOC_CTX *mem_ctx)
+{
+ TDB_DATA key;
+ uint8_t key_buf[SMBXSRV_TCON_LOCAL_TDB_KEY_SIZE];
+ struct db_record *rec = NULL;
+
+ key = smbXsrv_tcon_local_id_to_key(id, key_buf);
+
+ rec = dbwrap_fetch_locked(db, mem_ctx, key);
+
+ if (rec == NULL) {
+ DBG_DEBUG("Failed to lock local id 0x%08x, key '%s'\n", id,
+ tdb_data_dbg(key));
+ }
+
+ return rec;
+}
+
+static NTSTATUS smbXsrv_tcon_table_init(TALLOC_CTX *mem_ctx,
+ struct smbXsrv_tcon_table *table,
+ uint32_t lowest_id,
+ uint32_t highest_id,
+ uint32_t max_tcons)
+{
+ NTSTATUS status;
+ uint64_t max_range;
+
+ if (lowest_id > highest_id) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ max_range = highest_id;
+ max_range -= lowest_id;
+ max_range += 1;
+
+ if (max_tcons > max_range) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ ZERO_STRUCTP(table);
+ table->local.db_ctx = db_open_rbt(table);
+ if (table->local.db_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ table->local.lowest_id = lowest_id;
+ table->local.highest_id = highest_id;
+ table->local.max_tcons = max_tcons;
+
+ status = smbXsrv_tcon_global_init();
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ table->global.db_ctx = smbXsrv_tcon_global_db_ctx;
+
+ return NT_STATUS_OK;
+}
+
+struct smb1srv_tcon_local_allocate_state {
+ const uint32_t lowest_id;
+ const uint32_t highest_id;
+ uint32_t last_id;
+ uint32_t useable_id;
+ NTSTATUS status;
+};
+
+static int smb1srv_tcon_local_allocate_traverse(struct db_record *rec,
+ void *private_data)
+{
+ struct smb1srv_tcon_local_allocate_state *state =
+ (struct smb1srv_tcon_local_allocate_state *)private_data;
+ TDB_DATA key = dbwrap_record_get_key(rec);
+ uint32_t id = 0;
+ NTSTATUS status;
+
+ status = smbXsrv_tcon_local_key_to_id(key, &id);
+ if (!NT_STATUS_IS_OK(status)) {
+ state->status = status;
+ return -1;
+ }
+
+ if (id <= state->last_id) {
+ state->status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ return -1;
+ }
+ state->last_id = id;
+
+ if (id > state->useable_id) {
+ state->status = NT_STATUS_OK;
+ return -1;
+ }
+
+ if (state->useable_id == state->highest_id) {
+ state->status = NT_STATUS_INSUFFICIENT_RESOURCES;
+ return -1;
+ }
+
+ state->useable_id +=1;
+ return 0;
+}
+
+static NTSTATUS smb1srv_tcon_local_allocate_id(struct db_context *db,
+ uint32_t lowest_id,
+ uint32_t highest_id,
+ TALLOC_CTX *mem_ctx,
+ struct db_record **_rec,
+ uint32_t *_id)
+{
+ struct smb1srv_tcon_local_allocate_state state = {
+ .lowest_id = lowest_id,
+ .highest_id = highest_id,
+ .last_id = 0,
+ .useable_id = lowest_id,
+ .status = NT_STATUS_INTERNAL_ERROR,
+ };
+ uint32_t i;
+ uint32_t range;
+ NTSTATUS status;
+ int count = 0;
+
+ *_rec = NULL;
+ *_id = 0;
+
+ if (lowest_id > highest_id) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ /*
+ * first we try randomly
+ */
+ range = (highest_id - lowest_id) + 1;
+
+ for (i = 0; i < (range / 2); i++) {
+ uint32_t id;
+ TDB_DATA val;
+ struct db_record *rec = NULL;
+
+ id = generate_random() % range;
+ id += lowest_id;
+
+ if (id < lowest_id) {
+ id = lowest_id;
+ }
+ if (id > highest_id) {
+ id = highest_id;
+ }
+
+ rec = smbXsrv_tcon_local_fetch_locked(db, id, mem_ctx);
+ if (rec == NULL) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ val = dbwrap_record_get_value(rec);
+ if (val.dsize != 0) {
+ TALLOC_FREE(rec);
+ continue;
+ }
+
+ *_rec = rec;
+ *_id = id;
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * if the range is almost full,
+ * we traverse the whole table
+ * (this relies on sorted behavior of dbwrap_rbt)
+ */
+ status = dbwrap_traverse_read(db, smb1srv_tcon_local_allocate_traverse,
+ &state, &count);
+ if (NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_IS_OK(state.status)) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (!NT_STATUS_EQUAL(state.status, NT_STATUS_INTERNAL_ERROR)) {
+ return state.status;
+ }
+
+ if (state.useable_id <= state.highest_id) {
+ state.status = NT_STATUS_OK;
+ } else {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+ } else if (!NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_DB_CORRUPTION)) {
+ /*
+ * Here we really expect NT_STATUS_INTERNAL_DB_CORRUPTION!
+ *
+ * If we get anything else it is an error, because it
+ * means we did not manage to find a free slot in
+ * the db.
+ */
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ if (NT_STATUS_IS_OK(state.status)) {
+ uint32_t id;
+ TDB_DATA val;
+ struct db_record *rec = NULL;
+
+ id = state.useable_id;
+
+ rec = smbXsrv_tcon_local_fetch_locked(db, id, mem_ctx);
+ if (rec == NULL) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ val = dbwrap_record_get_value(rec);
+ if (val.dsize != 0) {
+ TALLOC_FREE(rec);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ *_rec = rec;
+ *_id = id;
+ return NT_STATUS_OK;
+ }
+
+ return state.status;
+}
+
+struct smbXsrv_tcon_local_fetch_state {
+ struct smbXsrv_tcon *tcon;
+ NTSTATUS status;
+};
+
+static void smbXsrv_tcon_local_fetch_parser(TDB_DATA key, TDB_DATA data,
+ void *private_data)
+{
+ struct smbXsrv_tcon_local_fetch_state *state =
+ (struct smbXsrv_tcon_local_fetch_state *)private_data;
+ void *ptr;
+
+ if (data.dsize != sizeof(ptr)) {
+ state->status = NT_STATUS_INTERNAL_DB_ERROR;
+ return;
+ }
+
+ memcpy(&ptr, data.dptr, data.dsize);
+ state->tcon = talloc_get_type_abort(ptr, struct smbXsrv_tcon);
+ state->status = NT_STATUS_OK;
+}
+
+static NTSTATUS smbXsrv_tcon_local_lookup(struct smbXsrv_tcon_table *table,
+ uint32_t tcon_local_id,
+ NTTIME now,
+ struct smbXsrv_tcon **_tcon)
+{
+ struct smbXsrv_tcon_local_fetch_state state = {
+ .tcon = NULL,
+ .status = NT_STATUS_INTERNAL_ERROR,
+ };
+ uint8_t key_buf[SMBXSRV_TCON_LOCAL_TDB_KEY_SIZE];
+ TDB_DATA key;
+ NTSTATUS status;
+
+ *_tcon = NULL;
+
+ if (tcon_local_id == 0) {
+ return NT_STATUS_NETWORK_NAME_DELETED;
+ }
+
+ if (table == NULL) {
+ /* this might happen before the end of negprot */
+ return NT_STATUS_NETWORK_NAME_DELETED;
+ }
+
+ if (table->local.db_ctx == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ key = smbXsrv_tcon_local_id_to_key(tcon_local_id, key_buf);
+
+ status = dbwrap_parse_record(table->local.db_ctx, key,
+ smbXsrv_tcon_local_fetch_parser,
+ &state);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ return NT_STATUS_NETWORK_NAME_DELETED;
+ } else if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(state.status)) {
+ return state.status;
+ }
+
+ if (NT_STATUS_EQUAL(state.tcon->status, NT_STATUS_NETWORK_NAME_DELETED)) {
+ return NT_STATUS_NETWORK_NAME_DELETED;
+ }
+
+ state.tcon->idle_time = now;
+
+ *_tcon = state.tcon;
+ return state.tcon->status;
+}
+
+static int smbXsrv_tcon_global_destructor(struct smbXsrv_tcon_global0 *global)
+{
+ return 0;
+}
+
+static void smbXsrv_tcon_global_verify_record(struct db_record *db_rec,
+ bool *is_free,
+ bool *was_free,
+ TALLOC_CTX *mem_ctx,
+ struct smbXsrv_tcon_global0 **_g);
+
+static NTSTATUS smbXsrv_tcon_global_allocate(struct db_context *db,
+ TALLOC_CTX *mem_ctx,
+ struct smbXsrv_tcon_global0 **_global)
+{
+ uint32_t i;
+ struct smbXsrv_tcon_global0 *global = NULL;
+ uint32_t last_free = 0;
+ const uint32_t min_tries = 3;
+
+ *_global = NULL;
+
+ global = talloc_zero(mem_ctx, struct smbXsrv_tcon_global0);
+ if (global == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ talloc_set_destructor(global, smbXsrv_tcon_global_destructor);
+
+ /*
+ * Here we just randomly try the whole 32-bit space
+ *
+ * We use just 32-bit, because we want to reuse the
+ * ID for SRVSVC.
+ */
+ for (i = 0; i < UINT32_MAX; i++) {
+ bool is_free = false;
+ bool was_free = false;
+ uint32_t id;
+
+ if (i >= min_tries && last_free != 0) {
+ id = last_free;
+ } else {
+ id = generate_random();
+ }
+ if (id == 0) {
+ id++;
+ }
+ if (id == UINT32_MAX) {
+ id--;
+ }
+
+ global->db_rec = smbXsrv_tcon_global_fetch_locked(db, id,
+ mem_ctx);
+ if (global->db_rec == NULL) {
+ talloc_free(global);
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ smbXsrv_tcon_global_verify_record(global->db_rec,
+ &is_free,
+ &was_free,
+ NULL, NULL);
+
+ if (!is_free) {
+ TALLOC_FREE(global->db_rec);
+ continue;
+ }
+
+ if (!was_free && i < min_tries) {
+ /*
+ * The session_id is free now,
+ * but was not free before.
+ *
+ * This happens if a smbd crashed
+ * and did not cleanup the record.
+ *
+ * If this is one of our first tries,
+ * then we try to find a real free one.
+ */
+ if (last_free == 0) {
+ last_free = id;
+ }
+ TALLOC_FREE(global->db_rec);
+ continue;
+ }
+
+ global->tcon_global_id = id;
+
+ *_global = global;
+ return NT_STATUS_OK;
+ }
+
+ /* should not be reached */
+ talloc_free(global);
+ return NT_STATUS_INTERNAL_ERROR;
+}
+
+static void smbXsrv_tcon_global_verify_record(struct db_record *db_rec,
+ bool *is_free,
+ bool *was_free,
+ TALLOC_CTX *mem_ctx,
+ struct smbXsrv_tcon_global0 **_g)
+{
+ TDB_DATA key;
+ TDB_DATA val;
+ DATA_BLOB blob;
+ struct smbXsrv_tcon_globalB global_blob;
+ enum ndr_err_code ndr_err;
+ struct smbXsrv_tcon_global0 *global = NULL;
+ bool exists;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ *is_free = false;
+
+ if (was_free) {
+ *was_free = false;
+ }
+ if (_g) {
+ *_g = NULL;
+ }
+
+ key = dbwrap_record_get_key(db_rec);
+
+ val = dbwrap_record_get_value(db_rec);
+ if (val.dsize == 0) {
+ TALLOC_FREE(frame);
+ *is_free = true;
+ if (was_free) {
+ *was_free = true;
+ }
+ return;
+ }
+
+ blob = data_blob_const(val.dptr, val.dsize);
+
+ ndr_err = ndr_pull_struct_blob(&blob, frame, &global_blob,
+ (ndr_pull_flags_fn_t)ndr_pull_smbXsrv_tcon_globalB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("key '%s' ndr_pull_struct_blob - %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ TALLOC_FREE(frame);
+ return;
+ }
+
+ DBG_DEBUG("smbXsrv_tcon_global_verify_record\n");
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ NDR_PRINT_DEBUG(smbXsrv_tcon_globalB, &global_blob);
+ }
+
+ if (global_blob.version != SMBXSRV_VERSION_0) {
+ DBG_ERR("key '%s' uses unsupported version %u\n",
+ tdb_data_dbg(key),
+ global_blob.version);
+ NDR_PRINT_DEBUG(smbXsrv_tcon_globalB, &global_blob);
+ TALLOC_FREE(frame);
+ return;
+ }
+
+ global = global_blob.info.info0;
+
+ exists = serverid_exists(&global->server_id);
+ if (!exists) {
+ struct server_id_buf idbuf;
+ DBG_NOTICE("key '%s' server_id %s does not exist.\n",
+ tdb_data_dbg(key),
+ server_id_str_buf(global->server_id, &idbuf));
+ if (DEBUGLVL(DBGLVL_NOTICE)) {
+ NDR_PRINT_DEBUG(smbXsrv_tcon_globalB, &global_blob);
+ }
+ TALLOC_FREE(frame);
+ dbwrap_record_delete(db_rec);
+ *is_free = true;
+ return;
+ }
+
+ if (_g) {
+ *_g = talloc_move(mem_ctx, &global);
+ }
+ TALLOC_FREE(frame);
+}
+
+static NTSTATUS smbXsrv_tcon_global_store(struct smbXsrv_tcon_global0 *global)
+{
+ struct smbXsrv_tcon_globalB global_blob;
+ DATA_BLOB blob = data_blob_null;
+ TDB_DATA key;
+ TDB_DATA val;
+ NTSTATUS status;
+ enum ndr_err_code ndr_err;
+
+ /*
+ * TODO: if we use other versions than '0'
+ * we would add glue code here, that would be able to
+ * store the information in the old format.
+ */
+
+ if (global->db_rec == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ key = dbwrap_record_get_key(global->db_rec);
+ val = dbwrap_record_get_value(global->db_rec);
+
+ ZERO_STRUCT(global_blob);
+ global_blob.version = smbXsrv_version_global_current();
+ if (val.dsize >= 8) {
+ global_blob.seqnum = IVAL(val.dptr, 4);
+ }
+ global_blob.seqnum += 1;
+ global_blob.info.info0 = global;
+
+ ndr_err = ndr_push_struct_blob(&blob, global->db_rec, &global_blob,
+ (ndr_push_flags_fn_t)ndr_push_smbXsrv_tcon_globalB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("key '%s' ndr_push - %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ TALLOC_FREE(global->db_rec);
+ return status;
+ }
+
+ val = make_tdb_data(blob.data, blob.length);
+ status = dbwrap_record_store(global->db_rec, val, TDB_REPLACE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("key '%s' store - %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ TALLOC_FREE(global->db_rec);
+ return status;
+ }
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ DBG_DEBUG("key '%s' stored\n", tdb_data_dbg(key));
+ NDR_PRINT_DEBUG(smbXsrv_tcon_globalB, &global_blob);
+ }
+
+ TALLOC_FREE(global->db_rec);
+
+ return NT_STATUS_OK;
+}
+
+static int smbXsrv_tcon_destructor(struct smbXsrv_tcon *tcon)
+{
+ NTSTATUS status;
+
+ status = smbXsrv_tcon_disconnect(tcon, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smbXsrv_tcon_disconnect() failed - %s\n",
+ nt_errstr(status));
+ }
+
+ TALLOC_FREE(tcon->global);
+
+ return 0;
+}
+
+static NTSTATUS smbXsrv_tcon_create(struct smbXsrv_tcon_table *table,
+ enum protocol_types protocol,
+ struct server_id server_id,
+ NTTIME now,
+ uint32_t session_global_id,
+ uint8_t encryption_flags,
+ const char *share_name,
+ struct smbXsrv_tcon **_tcon)
+{
+ struct db_record *local_rec = NULL;
+ struct smbXsrv_tcon *tcon = NULL;
+ void *ptr = NULL;
+ TDB_DATA val;
+ struct smbXsrv_tcon_global0 *global = NULL;
+ NTSTATUS status;
+
+ if (table->local.num_tcons >= table->local.max_tcons) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ tcon = talloc_zero(table, struct smbXsrv_tcon);
+ if (tcon == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ tcon->table = table;
+ tcon->status = NT_STATUS_INTERNAL_ERROR;
+ tcon->idle_time = now;
+
+ status = smbXsrv_tcon_global_allocate(table->global.db_ctx,
+ tcon, &global);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(tcon);
+ return status;
+ }
+ tcon->global = global;
+
+ global->session_global_id = session_global_id;
+ global->encryption_flags = encryption_flags;
+ global->share_name = talloc_strdup(global, share_name);
+ if (global->share_name == NULL) {
+ TALLOC_FREE(tcon);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (protocol >= PROTOCOL_SMB2_02) {
+ uint64_t id = global->tcon_global_id;
+
+ global->tcon_wire_id = id;
+
+ tcon->local_id = global->tcon_global_id;
+
+ local_rec = smbXsrv_tcon_local_fetch_locked(table->local.db_ctx,
+ tcon->local_id,
+ tcon /* TALLOC_CTX */);
+ if (local_rec == NULL) {
+ TALLOC_FREE(tcon);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ val = dbwrap_record_get_value(local_rec);
+ if (val.dsize != 0) {
+ TALLOC_FREE(tcon);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+ } else {
+
+ status = smb1srv_tcon_local_allocate_id(table->local.db_ctx,
+ table->local.lowest_id,
+ table->local.highest_id,
+ tcon,
+ &local_rec,
+ &tcon->local_id);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(tcon);
+ return status;
+ }
+
+ global->tcon_wire_id = tcon->local_id;
+ }
+
+ global->creation_time = now;
+
+ global->server_id = server_id;
+
+ ptr = tcon;
+ val = make_tdb_data((uint8_t const *)&ptr, sizeof(ptr));
+ status = dbwrap_record_store(local_rec, val, TDB_REPLACE);
+ TALLOC_FREE(local_rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(tcon);
+ return status;
+ }
+ table->local.num_tcons += 1;
+
+ talloc_set_destructor(tcon, smbXsrv_tcon_destructor);
+
+ status = smbXsrv_tcon_global_store(global);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("global_id (0x%08x) store failed - %s\n",
+ tcon->global->tcon_global_id,
+ nt_errstr(status));
+ TALLOC_FREE(tcon);
+ return status;
+ }
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ struct smbXsrv_tconB tcon_blob = {
+ .version = SMBXSRV_VERSION_0,
+ .info.info0 = tcon,
+ };
+
+ DBG_DEBUG("global_id (0x%08x) stored\n",
+ tcon->global->tcon_global_id);
+ NDR_PRINT_DEBUG(smbXsrv_tconB, &tcon_blob);
+ }
+
+ *_tcon = tcon;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smbXsrv_tcon_update(struct smbXsrv_tcon *tcon)
+{
+ struct smbXsrv_tcon_table *table = tcon->table;
+ NTSTATUS status;
+
+ if (tcon->global->db_rec != NULL) {
+ DBG_ERR("update(0x%08x): "
+ "Called with db_rec != NULL'\n",
+ tcon->global->tcon_global_id);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ tcon->global->db_rec = smbXsrv_tcon_global_fetch_locked(
+ table->global.db_ctx,
+ tcon->global->tcon_global_id,
+ tcon->global /* TALLOC_CTX */);
+ if (tcon->global->db_rec == NULL) {
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ status = smbXsrv_tcon_global_store(tcon->global);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("global_id (0x%08x) store failed - %s\n",
+ tcon->global->tcon_global_id,
+ nt_errstr(status));
+ return status;
+ }
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ struct smbXsrv_tconB tcon_blob = {
+ .version = SMBXSRV_VERSION_0,
+ .info.info0 = tcon,
+ };
+
+ DBG_DEBUG("global_id (0x%08x) stored\n",
+ tcon->global->tcon_global_id);
+ NDR_PRINT_DEBUG(smbXsrv_tconB, &tcon_blob);
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smbXsrv_tcon_disconnect(struct smbXsrv_tcon *tcon, uint64_t vuid)
+{
+ struct smbXsrv_tcon_table *table;
+ struct db_record *local_rec = NULL;
+ struct db_record *global_rec = NULL;
+ NTSTATUS status;
+ NTSTATUS error = NT_STATUS_OK;
+
+ if (tcon->table == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ table = tcon->table;
+ tcon->table = NULL;
+
+ if (tcon->compat) {
+ bool ok;
+
+ ok = chdir_current_service(tcon->compat);
+ if (!ok) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ DBG_ERR("disconnect(0x%08x, '%s'): "
+ "chdir_current_service() failed: %s\n",
+ tcon->global->tcon_global_id,
+ tcon->global->share_name,
+ nt_errstr(status));
+ /*
+ * We must call close_cnum() on
+ * error, as the caller is going
+ * to free tcon and tcon->compat
+ * so we must ensure tcon->compat is
+ * removed from the linked list
+ * conn->sconn->connections.
+ */
+ close_cnum(tcon->compat, vuid, ERROR_CLOSE);
+ tcon->compat = NULL;
+ return status;
+ }
+
+ close_cnum(tcon->compat, vuid, SHUTDOWN_CLOSE);
+ tcon->compat = NULL;
+ }
+
+ tcon->status = NT_STATUS_NETWORK_NAME_DELETED;
+
+ global_rec = tcon->global->db_rec;
+ tcon->global->db_rec = NULL;
+ if (global_rec == NULL) {
+ global_rec = smbXsrv_tcon_global_fetch_locked(
+ table->global.db_ctx,
+ tcon->global->tcon_global_id,
+ tcon->global /* TALLOC_CTX */);
+ if (global_rec == NULL) {
+ error = NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ if (global_rec != NULL) {
+ status = dbwrap_record_delete(global_rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ TDB_DATA key = dbwrap_record_get_key(global_rec);
+
+ DBG_ERR("disconnect(0x%08x, '%s'): "
+ "failed to delete global key '%s': %s\n",
+ tcon->global->tcon_global_id,
+ tcon->global->share_name,
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ error = status;
+ }
+ }
+ TALLOC_FREE(global_rec);
+
+ local_rec = tcon->db_rec;
+ if (local_rec == NULL) {
+ local_rec = smbXsrv_tcon_local_fetch_locked(table->local.db_ctx,
+ tcon->local_id,
+ tcon /* TALLOC_CTX */);
+ if (local_rec == NULL) {
+ error = NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ if (local_rec != NULL) {
+ status = dbwrap_record_delete(local_rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ TDB_DATA key = dbwrap_record_get_key(local_rec);
+
+ DBG_ERR("disconnect(0x%08x, '%s'): "
+ "failed to delete local key '%s': %s\n",
+ tcon->global->tcon_global_id,
+ tcon->global->share_name,
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ error = status;
+ }
+ table->local.num_tcons -= 1;
+ }
+ if (tcon->db_rec == NULL) {
+ TALLOC_FREE(local_rec);
+ }
+ tcon->db_rec = NULL;
+
+ return error;
+}
+
+struct smbXsrv_tcon_disconnect_all_state {
+ uint64_t vuid;
+ NTSTATUS first_status;
+ int errors;
+};
+
+static int smbXsrv_tcon_disconnect_all_callback(struct db_record *local_rec,
+ void *private_data);
+
+static NTSTATUS smbXsrv_tcon_disconnect_all(struct smbXsrv_tcon_table *table,
+ uint64_t vuid)
+{
+ struct smbXsrv_tcon_disconnect_all_state state = { .vuid = vuid };
+ NTSTATUS status;
+ int count = 0;
+
+ if (table == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ status = dbwrap_traverse(table->local.db_ctx,
+ smbXsrv_tcon_disconnect_all_callback,
+ &state, &count);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("dbwrap_traverse() failed: %s\n", nt_errstr(status));
+ return status;
+ }
+
+ if (!NT_STATUS_IS_OK(state.first_status)) {
+ DBG_ERR("count[%d] errors[%d] first[%s]\n",
+ count,
+ state.errors,
+ nt_errstr(state.first_status));
+ return state.first_status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static int smbXsrv_tcon_disconnect_all_callback(struct db_record *local_rec,
+ void *private_data)
+{
+ struct smbXsrv_tcon_disconnect_all_state *state =
+ (struct smbXsrv_tcon_disconnect_all_state *)private_data;
+ TDB_DATA val;
+ void *ptr = NULL;
+ struct smbXsrv_tcon *tcon = NULL;
+ uint64_t vuid;
+ NTSTATUS status;
+
+ val = dbwrap_record_get_value(local_rec);
+ if (val.dsize != sizeof(ptr)) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ if (NT_STATUS_IS_OK(state->first_status)) {
+ state->first_status = status;
+ }
+ state->errors++;
+ return 0;
+ }
+
+ memcpy(&ptr, val.dptr, val.dsize);
+ tcon = talloc_get_type_abort(ptr, struct smbXsrv_tcon);
+
+ vuid = state->vuid;
+ if (vuid == 0 && tcon->compat) {
+ vuid = tcon->compat->vuid;
+ }
+
+ tcon->db_rec = local_rec;
+ status = smbXsrv_tcon_disconnect(tcon, vuid);
+ tcon->db_rec = NULL;
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_IS_OK(state->first_status)) {
+ state->first_status = status;
+ }
+ state->errors++;
+ return 0;
+ }
+
+ return 0;
+}
+
+NTSTATUS smb1srv_tcon_table_init(struct smbXsrv_connection *conn)
+{
+ struct smbXsrv_client *client = conn->client;
+
+ /*
+ * Allow a range from 1..65534 with 65534 values.
+ */
+ client->tcon_table = talloc_zero(client, struct smbXsrv_tcon_table);
+ if (client->tcon_table == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return smbXsrv_tcon_table_init(client, client->tcon_table,
+ 1, UINT16_MAX - 1,
+ UINT16_MAX - 1);
+}
+
+NTSTATUS smb1srv_tcon_create(struct smbXsrv_connection *conn,
+ uint32_t session_global_id,
+ const char *share_name,
+ NTTIME now,
+ struct smbXsrv_tcon **_tcon)
+{
+ struct server_id id = messaging_server_id(conn->client->msg_ctx);
+ const uint8_t encryption_flags = 0;
+
+ return smbXsrv_tcon_create(conn->client->tcon_table,
+ conn->protocol,
+ id, now,
+ session_global_id,
+ encryption_flags,
+ share_name,
+ _tcon);
+}
+
+NTSTATUS smb1srv_tcon_lookup(struct smbXsrv_connection *conn,
+ uint16_t tree_id, NTTIME now,
+ struct smbXsrv_tcon **tcon)
+{
+ uint32_t local_id = tree_id;
+
+ return smbXsrv_tcon_local_lookup(conn->client->tcon_table,
+ local_id, now, tcon);
+}
+
+NTSTATUS smb1srv_tcon_disconnect_all(struct smbXsrv_client *client)
+{
+
+ /*
+ * We do not pass a vuid here,
+ * which means the vuid is taken from
+ * the tcon->compat->vuid.
+ *
+ * NOTE: that tcon->compat->vuid may point to
+ * a none existing vuid (or the wrong one)
+ * as the tcon can exist without a session
+ * in SMB1.
+ *
+ * This matches the old behavior of
+ * conn_close_all(), but we should think
+ * about how to fix this in future.
+ */
+ return smbXsrv_tcon_disconnect_all(client->tcon_table, 0);
+}
+
+NTSTATUS smb2srv_tcon_table_init(struct smbXsrv_session *session)
+{
+ /*
+ * Allow a range from 1..4294967294 with 65534 (same as SMB1) values.
+ */
+ session->tcon_table = talloc_zero(session, struct smbXsrv_tcon_table);
+ if (session->tcon_table == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return smbXsrv_tcon_table_init(session, session->tcon_table,
+ 1, UINT32_MAX - 1,
+ UINT16_MAX - 1);
+}
+
+NTSTATUS smb2srv_tcon_create(struct smbXsrv_session *session,
+ uint32_t session_global_id,
+ uint8_t encryption_flags,
+ const char *share_name,
+ NTTIME now,
+ struct smbXsrv_tcon **_tcon)
+{
+ struct server_id id = messaging_server_id(session->client->msg_ctx);
+
+ return smbXsrv_tcon_create(session->tcon_table,
+ PROTOCOL_SMB2_02,
+ id, now,
+ session_global_id,
+ encryption_flags,
+ share_name,
+ _tcon);
+}
+
+NTSTATUS smb2srv_tcon_lookup(struct smbXsrv_session *session,
+ uint32_t tree_id, NTTIME now,
+ struct smbXsrv_tcon **tcon)
+{
+ uint32_t local_id = tree_id;
+
+ return smbXsrv_tcon_local_lookup(session->tcon_table,
+ local_id, now, tcon);
+}
+
+NTSTATUS smb2srv_tcon_disconnect_all(struct smbXsrv_session *session)
+{
+ uint64_t vuid = session->global->session_wire_id;
+
+ return smbXsrv_tcon_disconnect_all(session->tcon_table, vuid);
+}
+
+struct smbXsrv_tcon_global_traverse_state {
+ int (*fn)(struct smbXsrv_tcon_global0 *, void *);
+ void *private_data;
+};
+
+static int smbXsrv_tcon_global_traverse_fn(struct db_record *rec, void *data)
+{
+ int ret = -1;
+ struct smbXsrv_tcon_global_traverse_state *state =
+ (struct smbXsrv_tcon_global_traverse_state*)data;
+ TDB_DATA key = dbwrap_record_get_key(rec);
+ TDB_DATA val = dbwrap_record_get_value(rec);
+ DATA_BLOB blob = data_blob_const(val.dptr, val.dsize);
+ struct smbXsrv_tcon_globalB global_blob;
+ enum ndr_err_code ndr_err;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ndr_err = ndr_pull_struct_blob(&blob, frame, &global_blob,
+ (ndr_pull_flags_fn_t)ndr_pull_smbXsrv_tcon_globalB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_WARNING("Invalid record in smbXsrv_tcon_global.tdb:"
+ "key '%s' ndr_pull_struct_blob - %s\n",
+ tdb_data_dbg(key),
+ ndr_errstr(ndr_err));
+ goto done;
+ }
+
+ if (global_blob.version != SMBXSRV_VERSION_0) {
+ DBG_WARNING("Invalid record in smbXsrv_tcon_global.tdb:"
+ "key '%s' unsupported version - %d\n",
+ tdb_data_dbg(key),
+ (int)global_blob.version);
+ goto done;
+ }
+
+ if (global_blob.info.info0 == NULL) {
+ DBG_WARNING("Invalid record in smbXsrv_tcon_global.tdb:"
+ "key '%s' info0 NULL pointer\n",
+ tdb_data_dbg(key));
+ goto done;
+ }
+
+ global_blob.info.info0->db_rec = rec;
+ ret = state->fn(global_blob.info.info0, state->private_data);
+done:
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+NTSTATUS smbXsrv_tcon_global_traverse(
+ int (*fn)(struct smbXsrv_tcon_global0 *, void *),
+ void *private_data)
+{
+ NTSTATUS status;
+ int count = 0;
+ struct smbXsrv_tcon_global_traverse_state state = {
+ .fn = fn,
+ .private_data = private_data,
+ };
+
+ become_root();
+ status = smbXsrv_tcon_global_init();
+ if (!NT_STATUS_IS_OK(status)) {
+ unbecome_root();
+ DBG_ERR("Failed to initialize tcon_global: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ status = dbwrap_traverse_read(smbXsrv_tcon_global_db_ctx,
+ smbXsrv_tcon_global_traverse_fn,
+ &state,
+ &count);
+ unbecome_root();
+
+ return status;
+}
diff --git a/source3/smbd/smbXsrv_version.c b/source3/smbd/smbXsrv_version.c
new file mode 100644
index 0000000..f2d138d
--- /dev/null
+++ b/source3/smbd/smbXsrv_version.c
@@ -0,0 +1,265 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Stefan Metzmacher 2012
+
+ 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 "system/filesys.h"
+#include "smbd/globals.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "lib/util/util_tdb.h"
+#include "librpc/gen_ndr/ndr_smbXsrv.h"
+#include "serverid.h"
+
+/*
+ * This implements a version scheme for file server internal
+ * states. smbXsrv_version_global.tdb stores the possible
+ * and current versions of structure formats (struct smbXsrv_*_global)
+ * per cluster node.
+ *
+ * If the supported versions doesn't match a version of any
+ * of the other nodes, it refused to start.
+ *
+ * This should prevent silent corruption of the internal
+ * databases and structures, if two incompatible implementations
+ * read and write.
+ *
+ * In future this can be used to implement rolling code upgrades
+ * in a cluster, but for now it is simple.
+ */
+
+static struct db_context *smbXsrv_version_global_db_ctx = NULL;
+static uint32_t smbXsrv_version_global_current_version = UINT32_MAX;
+
+NTSTATUS smbXsrv_version_global_init(const struct server_id *server_id)
+{
+ const char *global_path = NULL;
+ struct db_context *db_ctx = NULL;
+ struct db_record *db_rec = NULL;
+ TDB_DATA key;
+ TDB_DATA val;
+ DATA_BLOB blob;
+ struct smbXsrv_version_globalB global_blob;
+ enum ndr_err_code ndr_err;
+ struct smbXsrv_version_global0 *global = NULL;
+ uint32_t i;
+ uint32_t num_valid = 0;
+ struct smbXsrv_version_node0 *valid = NULL;
+ struct smbXsrv_version_node0 *local_node = NULL;
+ bool exists;
+ NTSTATUS status;
+ const char *key_string = "smbXsrv_version_global";
+ TALLOC_CTX *frame;
+
+ if (smbXsrv_version_global_db_ctx != NULL) {
+ return NT_STATUS_OK;
+ }
+
+ frame = talloc_stackframe();
+
+ global_path = lock_path(talloc_tos(), "smbXsrv_version_global.tdb");
+ if (global_path == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ db_ctx = db_open(NULL, global_path,
+ 0, /* hash_size */
+ TDB_DEFAULT |
+ TDB_CLEAR_IF_FIRST |
+ TDB_INCOMPATIBLE_HASH,
+ O_RDWR | O_CREAT, 0600,
+ DBWRAP_LOCK_ORDER_1,
+ DBWRAP_FLAG_NONE);
+ if (db_ctx == NULL) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("smbXsrv_version_global_init: "
+ "failed to open[%s] - %s\n",
+ global_path, nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ key = string_term_tdb_data(key_string);
+
+ db_rec = dbwrap_fetch_locked(db_ctx, db_ctx, key);
+ if (db_rec == NULL) {
+ status = NT_STATUS_INTERNAL_DB_ERROR;
+ DEBUG(0,("smbXsrv_version_global_init: "
+ "dbwrap_fetch_locked(%s) - %s\n",
+ key_string, nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ val = dbwrap_record_get_value(db_rec);
+ if (val.dsize == 0) {
+ global = talloc_zero(frame, struct smbXsrv_version_global0);
+ if (global == NULL) {
+ DEBUG(0,("smbXsrv_version_global_init: "
+ "talloc_zero failed - %s\n", __location__));
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ ZERO_STRUCT(global_blob);
+ global_blob.version = SMBXSRV_VERSION_CURRENT;
+ global_blob.info.info0 = global;
+ } else {
+ blob = data_blob_const(val.dptr, val.dsize);
+
+ ndr_err = ndr_pull_struct_blob(&blob, frame, &global_blob,
+ (ndr_pull_flags_fn_t)ndr_pull_smbXsrv_version_globalB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ DEBUG(0,("smbXsrv_version_global_init: "
+ "ndr_pull_smbXsrv_version_globalB - %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ switch (global_blob.version) {
+ case SMBXSRV_VERSION_0:
+ global = global_blob.info.info0;
+ if (global == NULL) {
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ break;
+ }
+ status = NT_STATUS_OK;
+ break;
+ default:
+ status = NT_STATUS_REVISION_MISMATCH;
+ break;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("smbXsrv_version_global_init - %s\n",
+ nt_errstr(status)));
+ NDR_PRINT_DEBUG(smbXsrv_version_globalB, &global_blob);
+ TALLOC_FREE(frame);
+ return status;
+ }
+ }
+
+ valid = talloc_zero_array(global,
+ struct smbXsrv_version_node0,
+ global->num_nodes + 1);
+ if (valid == NULL) {
+ DEBUG(0,("smbXsrv_version_global_init: "
+ "talloc_zero_array failed - %s\n", __location__));
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ num_valid = 0;
+ for (i=0; i < global->num_nodes; i++) {
+ struct smbXsrv_version_node0 *n = &global->nodes[i];
+
+ exists = serverid_exists(&n->server_id);
+ if (!exists) {
+ continue;
+ }
+
+ if (n->min_version > n->max_version) {
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ DEBUG(0,("smbXsrv_version_global_init - %s\n",
+ nt_errstr(status)));
+ NDR_PRINT_DEBUG(smbXsrv_version_globalB, &global_blob);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ if (n->min_version > global_blob.version) {
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ DEBUG(0,("smbXsrv_version_global_init - %s\n",
+ nt_errstr(status)));
+ NDR_PRINT_DEBUG(smbXsrv_version_globalB, &global_blob);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ if (n->max_version < global_blob.version) {
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ DEBUG(0,("smbXsrv_version_global_init - %s\n",
+ nt_errstr(status)));
+ NDR_PRINT_DEBUG(smbXsrv_version_globalB, &global_blob);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ valid[num_valid] = *n;
+ if (server_id->vnn == n->server_id.vnn) {
+ local_node = &valid[num_valid];
+ }
+ num_valid++;
+ }
+
+ if (local_node == NULL) {
+ local_node = &valid[num_valid];
+ num_valid++;
+ }
+
+ local_node->server_id = *server_id;
+ local_node->min_version = SMBXSRV_VERSION_0;
+ local_node->max_version = SMBXSRV_VERSION_CURRENT;
+ local_node->current_version = global_blob.version;
+
+ global->num_nodes = num_valid;
+ global->nodes = valid;
+
+ global_blob.seqnum += 1;
+ global_blob.info.info0 = global;
+
+ ndr_err = ndr_push_struct_blob(&blob, db_rec, &global_blob,
+ (ndr_push_flags_fn_t)ndr_push_smbXsrv_version_globalB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ DEBUG(0,("smbXsrv_version_global_init: "
+ "ndr_push_smbXsrv_version_globalB - %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ val = make_tdb_data(blob.data, blob.length);
+ status = dbwrap_record_store(db_rec, val, TDB_REPLACE);
+ TALLOC_FREE(db_rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("smbXsrv_version_global_init: "
+ "dbwrap_record_store - %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ DEBUG(10,("smbXsrv_version_global_init\n"));
+ if (DEBUGLVL(10)) {
+ NDR_PRINT_DEBUG(smbXsrv_version_globalB, &global_blob);
+ }
+
+ smbXsrv_version_global_db_ctx = db_ctx;
+ smbXsrv_version_global_current_version = global_blob.version;
+
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
+
+uint32_t smbXsrv_version_global_current(void)
+{
+ return smbXsrv_version_global_current_version;
+}
diff --git a/source3/smbd/smbd.h b/source3/smbd/smbd.h
new file mode 100644
index 0000000..1c98bea
--- /dev/null
+++ b/source3/smbd/smbd.h
@@ -0,0 +1,102 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main SMB server routines
+
+ 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/>.
+*/
+
+#ifndef _SMBD_SMBD_H
+#define _SMBD_SMBD_H
+
+struct dptr_struct;
+
+#include "smb_acls.h"
+#include "vfs.h"
+#include "smbd/proto.h"
+#include "locking/proto.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/fd_handle.h"
+#if defined(WITH_SMB1SERVER)
+#include "smbd/smb1_message.h"
+#include "smbd/smb1_sesssetup.h"
+#include "smbd/smb1_lanman.h"
+#include "smbd/smb1_aio.h"
+#include "smbd/smb1_ipc.h"
+#include "smbd/smb1_negprot.h"
+#include "smbd/smb1_nttrans.h"
+#include "smbd/smb1_oplock.h"
+#include "smbd/smb1_pipes.h"
+#include "smbd/smb1_reply.h"
+#include "smbd/smb1_service.h"
+#include "smbd/smb1_signing.h"
+#include "smbd/smb1_process.h"
+#include "smbd/smb1_utils.h"
+#include "smbd/smb1_trans2.h"
+#endif
+
+struct trans_state {
+ struct trans_state *next, *prev;
+ uint64_t vuid; /* SMB2 compat */
+ uint64_t mid;
+
+ uint32_t max_param_return;
+ uint32_t max_data_return;
+ uint32_t max_setup_return;
+
+ uint8_t cmd; /* SMBtrans or SMBtrans2 */
+
+ char *name; /* for trans requests */
+ uint16_t call; /* for trans2 and nttrans requests */
+
+ bool close_on_completion;
+ bool one_way;
+
+ unsigned int setup_count;
+ uint16_t *setup;
+
+ size_t received_data;
+ size_t received_param;
+
+ size_t total_param;
+ char *param;
+
+ size_t total_data;
+ char *data;
+};
+
+/*
+ * unix_convert_flags
+ */
+/* UCF_SAVE_LCOMP 0x00000001 is no longer used. */
+/* UCF_ALWAYS_ALLOW_WCARD_LCOMP 0x00000002 is no longer used. */
+/* UCF_COND_ALLOW_WCARD_LCOMP 0x00000004 is no longer used. */
+#define UCF_POSIX_PATHNAMES 0x00000008
+/* #define UCF_UNIX_NAME_LOOKUP 0x00000010 is no longer used. */
+#define UCF_PREP_CREATEFILE 0x00000020
+/*
+ * Return a non-fsp smb_fname for a symlink
+ */
+#define UCF_LCOMP_LNK_OK 0x00000040
+/*
+ * Use the same bit as FLAGS2_REPARSE_PATH
+ * which means the same thing.
+ */
+#define UCF_GMT_PATHNAME 0x00000400
+/*
+ * Use the same bit as FLAGS2_DFS_PATHNAMES
+ * which means the same thing.
+ */
+#define UCF_DFS_PATHNAME 0x00001000
+
+#endif /* _SMBD_SMBD_H */
diff --git a/source3/smbd/smbd_cleanupd.c b/source3/smbd/smbd_cleanupd.c
new file mode 100644
index 0000000..0a50b18
--- /dev/null
+++ b/source3/smbd/smbd_cleanupd.c
@@ -0,0 +1,182 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Copyright (C) Volker Lendecke 2015
+ *
+ * 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 "smbd_cleanupd.h"
+#include "lib/util_procid.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "lib/util/debug.h"
+#include "smbprofile.h"
+#include "serverid.h"
+#include "locking/proto.h"
+#include "cleanupdb.h"
+
+struct smbd_cleanupd_state {
+ pid_t parent_pid;
+};
+
+static void smbd_cleanupd_shutdown(struct messaging_context *msg,
+ void *private_data, uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+static void smbd_cleanupd_process_exited(struct messaging_context *msg,
+ void *private_data, uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+
+struct tevent_req *smbd_cleanupd_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg,
+ pid_t parent_pid)
+{
+ struct tevent_req *req;
+ struct smbd_cleanupd_state *state;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state, struct smbd_cleanupd_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->parent_pid = parent_pid;
+
+ status = messaging_register(msg, req, MSG_SHUTDOWN,
+ smbd_cleanupd_shutdown);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ status = messaging_register(msg, req, MSG_SMB_NOTIFY_CLEANUP,
+ smbd_cleanupd_process_exited);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void smbd_cleanupd_shutdown(struct messaging_context *msg,
+ void *private_data, uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
+ tevent_req_done(req);
+}
+
+struct cleanup_child {
+ struct cleanup_child *prev, *next;
+ pid_t pid;
+ bool unclean;
+};
+
+struct cleanupdb_traverse_state {
+ TALLOC_CTX *mem_ctx;
+ bool ok;
+ struct cleanup_child *children;
+};
+
+static int cleanupdb_traverse_fn(const pid_t pid,
+ const bool unclean,
+ void *private_data)
+{
+ struct cleanupdb_traverse_state *cleanup_state =
+ (struct cleanupdb_traverse_state *)private_data;
+ struct cleanup_child *child = NULL;
+
+ child = talloc_zero(cleanup_state->mem_ctx, struct cleanup_child);
+ if (child == NULL) {
+ DBG_ERR("talloc_zero failed\n");
+ return -1;
+ }
+
+ child->pid = pid;
+ child->unclean = unclean;
+ DLIST_ADD(cleanup_state->children, child);
+
+ return 0;
+}
+
+static void smbd_cleanupd_process_exited(struct messaging_context *msg,
+ void *private_data, uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
+ struct smbd_cleanupd_state *state = tevent_req_data(
+ req, struct smbd_cleanupd_state);
+ int ret;
+ struct cleanupdb_traverse_state cleanup_state;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct cleanup_child *child = NULL;
+
+ cleanup_state = (struct cleanupdb_traverse_state) {
+ .mem_ctx = frame
+ };
+
+ /*
+ * This merely collect children in a list, whatever we're
+ * supposed to cleanup for every child, it has to take place
+ * *after* the db traverse in a list loop. This is to minimize
+ * locking interaction between the traverse and writers (i.e.
+ * the parent smbd).
+ */
+ ret = cleanupdb_traverse_read(cleanupdb_traverse_fn, &cleanup_state);
+ if (ret < 0) {
+ DBG_ERR("cleanupdb_traverse_read failed\n");
+ TALLOC_FREE(frame);
+ return;
+ }
+
+ if (ret == 0) {
+ TALLOC_FREE(frame);
+ return;
+ }
+
+ for (child = cleanup_state.children;
+ child != NULL;
+ child = child->next)
+ {
+ bool ok;
+
+ ok = cleanupdb_delete_child(child->pid);
+ if (!ok) {
+ DBG_ERR("failed to delete pid %d\n", (int)child->pid);
+ }
+
+ smbprofile_cleanup(child->pid, state->parent_pid);
+
+ ret = messaging_cleanup(msg, child->pid);
+
+ if ((ret != 0) && (ret != ENOENT)) {
+ DBG_DEBUG("messaging_cleanup returned %s\n",
+ strerror(ret));
+ }
+
+ DBG_DEBUG("cleaned up pid %d\n", (int)child->pid);
+ }
+
+ TALLOC_FREE(frame);
+}
+
+NTSTATUS smbd_cleanupd_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
diff --git a/source3/smbd/smbd_cleanupd.h b/source3/smbd/smbd_cleanupd.h
new file mode 100644
index 0000000..6e5d87f
--- /dev/null
+++ b/source3/smbd/smbd_cleanupd.h
@@ -0,0 +1,33 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Copyright (C) Volker Lendecke 2014
+ *
+ * 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/>.
+ */
+
+#ifndef __SMBD_CLEANUPD_H__
+#define __SMBD_CLEANUPD_H__
+
+#include "replace.h"
+#include <tevent.h>
+#include "messages.h"
+
+struct tevent_req *smbd_cleanupd_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg,
+ pid_t parent_pid);
+NTSTATUS smbd_cleanupd_recv(struct tevent_req *req);
+
+#endif
diff --git a/source3/smbd/srvstr.c b/source3/smbd/srvstr.c
new file mode 100644
index 0000000..5298ea7
--- /dev/null
+++ b/source3/smbd/srvstr.c
@@ -0,0 +1,78 @@
+/*
+ Unix SMB/CIFS implementation.
+ server specific string routines
+ Copyright (C) Andrew Tridgell 2001
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2003
+
+ 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 "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "lib/util/string_wrappers.h"
+
+/* Make sure we can't write a string past the end of the buffer */
+
+NTSTATUS srvstr_push_fn(const char *base_ptr, uint16_t smb_flags2, void *dest,
+ const char *src, int dest_len, int flags, size_t *ret_len)
+{
+ size_t len;
+ int saved_errno;
+ NTSTATUS status;
+
+ if (dest_len < 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ saved_errno = errno;
+ errno = 0;
+
+ /* 'normal' push into size-specified buffer */
+ len = push_string_base(base_ptr, smb_flags2, dest, src,
+ dest_len, flags);
+
+ if (errno != 0) {
+ /*
+ * Special case E2BIG, EILSEQ, EINVAL
+ * as they mean conversion errors here,
+ * but we don't generically map them as
+ * they can mean different things in
+ * generic filesystem calls (such as
+ * read xattrs).
+ */
+ if (errno == E2BIG || errno == EILSEQ || errno == EINVAL) {
+ status = NT_STATUS_ILLEGAL_CHARACTER;
+ } else {
+ status = map_nt_error_from_unix_common(errno);
+ /*
+ * Paranoia - Filter out STATUS_MORE_ENTRIES.
+ * I don't think we can get this but it has a
+ * specific meaning to the client.
+ */
+ if (NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) {
+ status = NT_STATUS_UNSUCCESSFUL;
+ }
+ }
+ DEBUG(10,("character conversion failure "
+ "on string (%s) (%s)\n",
+ src, strerror(errno)));
+ } else {
+ /* Success - restore untouched errno. */
+ errno = saved_errno;
+ *ret_len = len;
+ status = NT_STATUS_OK;
+ }
+ return status;
+}
diff --git a/source3/smbd/statvfs.c b/source3/smbd/statvfs.c
new file mode 100644
index 0000000..03dacc4
--- /dev/null
+++ b/source3/smbd/statvfs.c
@@ -0,0 +1,182 @@
+/*
+ Unix SMB/CIFS implementation.
+ VFS API's statvfs abstraction
+ Copyright (C) Alexander Bokovoy 2005
+ Copyright (C) Steve French 2005
+ Copyright (C) James Peach 2006
+
+ 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 "system/filesys.h"
+#include "smbd/smbd.h"
+
+#if defined(DARWINOS)
+#include <sys/attr.h>
+
+static int darwin_fs_capabilities(const char * path)
+{
+ int caps = 0;
+ vol_capabilities_attr_t *vcaps;
+ struct attrlist attrlist;
+ char attrbuf[sizeof(u_int32_t) + sizeof(vol_capabilities_attr_t)];
+
+#define FORMAT_CAP(vinfo, cap) \
+ ( ((vinfo)->valid[VOL_CAPABILITIES_FORMAT] & (cap)) && \
+ ((vinfo)->capabilities[VOL_CAPABILITIES_FORMAT] & (cap)) )
+
+#define INTERFACE_CAP(vinfo, cap) \
+ ( ((vinfo)->valid[VOL_CAPABILITIES_INTERFACES] & (cap)) && \
+ ((vinfo)->capabilities[VOL_CAPABILITIES_INTERFACES] & (cap)) )
+
+ ZERO_STRUCT(attrlist);
+ attrlist.bitmapcount = ATTR_BIT_MAP_COUNT;
+ attrlist.volattr = ATTR_VOL_CAPABILITIES;
+
+ if (getattrlist(path, &attrlist, attrbuf, sizeof(attrbuf), 0) != 0) {
+ DEBUG(0, ("getattrlist for %s capabilities failed: %s\n",
+ path, strerror(errno)));
+ /* Return no capabilities on failure. */
+ return 0;
+ }
+
+ vcaps =
+ (vol_capabilities_attr_t *)(attrbuf + sizeof(u_int32_t));
+
+ if (FORMAT_CAP(vcaps, VOL_CAP_FMT_SPARSE_FILES)) {
+ caps |= FILE_SUPPORTS_SPARSE_FILES;
+ }
+
+ if (FORMAT_CAP(vcaps, VOL_CAP_FMT_CASE_SENSITIVE)) {
+ caps |= FILE_CASE_SENSITIVE_SEARCH;
+ }
+
+ if (FORMAT_CAP(vcaps, VOL_CAP_FMT_CASE_PRESERVING)) {
+ caps |= FILE_CASE_PRESERVED_NAMES;
+ }
+
+ if (INTERFACE_CAP(vcaps, VOL_CAP_INT_EXTENDED_SECURITY)) {
+ caps |= FILE_PERSISTENT_ACLS;
+ }
+
+ return caps;
+}
+#endif /* DARWINOS */
+
+#if defined(BSD_STYLE_STATVFS)
+static int bsd_statvfs(const char *path, struct vfs_statvfs_struct *statbuf)
+{
+ struct statfs sbuf;
+ int ret;
+
+ ret = statfs(path, &sbuf);
+ if (ret == 0) {
+ statbuf->OptimalTransferSize = sbuf.f_iosize;
+ statbuf->BlockSize = sbuf.f_bsize;
+ statbuf->TotalBlocks = sbuf.f_blocks;
+ statbuf->BlocksAvail = sbuf.f_bfree;
+ statbuf->UserBlocksAvail = sbuf.f_bavail;
+ statbuf->TotalFileNodes = sbuf.f_files;
+ statbuf->FreeFileNodes = sbuf.f_ffree;
+ statbuf->FsIdentifier =
+ (((uint64_t) sbuf.f_fsid.val[0] << 32) & 0xffffffff00000000LL) |
+ (uint64_t) sbuf.f_fsid.val[1];
+#ifdef DARWINOS
+ statbuf->FsCapabilities = darwin_fs_capabilities(sbuf.f_mntonname);
+#else
+ /* Try to extrapolate some of the fs flags into the
+ * capabilities
+ */
+ statbuf->FsCapabilities =
+ FILE_CASE_SENSITIVE_SEARCH | FILE_CASE_PRESERVED_NAMES;
+#ifdef MNT_ACLS
+ if (sbuf.f_flags & MNT_ACLS)
+ statbuf->FsCapabilities |= FILE_PERSISTENT_ACLS;
+#endif
+#endif
+ if (sbuf.f_flags & MNT_QUOTA)
+ statbuf->FsCapabilities |= FILE_VOLUME_QUOTAS;
+ if (sbuf.f_flags & MNT_RDONLY)
+ statbuf->FsCapabilities |= FILE_READ_ONLY_VOLUME;
+ }
+
+ return ret;
+}
+#elif defined(STAT_STATVFS) && defined(HAVE_FSID_INT)
+static int posix_statvfs(const char *path, struct vfs_statvfs_struct *statbuf)
+{
+ struct statvfs statvfs_buf;
+ int result;
+
+ result = statvfs(path, &statvfs_buf);
+
+ if (!result) {
+ /* statvfs bsize is not the statfs bsize, the naming is terrible,
+ * see bug 11810 */
+ statbuf->OptimalTransferSize = statvfs_buf.f_bsize;
+ statbuf->BlockSize = statvfs_buf.f_frsize;
+ statbuf->TotalBlocks = statvfs_buf.f_blocks;
+ statbuf->BlocksAvail = statvfs_buf.f_bfree;
+ statbuf->UserBlocksAvail = statvfs_buf.f_bavail;
+ statbuf->TotalFileNodes = statvfs_buf.f_files;
+ statbuf->FreeFileNodes = statvfs_buf.f_ffree;
+ statbuf->FsIdentifier = statvfs_buf.f_fsid;
+ /* Try to extrapolate some of the fs flags into the
+ * capabilities
+ */
+ statbuf->FsCapabilities =
+ FILE_CASE_SENSITIVE_SEARCH | FILE_CASE_PRESERVED_NAMES;
+#ifdef ST_QUOTA
+ if (statvfs_buf.f_flag & ST_QUOTA)
+ statbuf->FsCapabilities |= FILE_VOLUME_QUOTAS;
+#endif
+ if (statvfs_buf.f_flag & ST_RDONLY)
+ statbuf->FsCapabilities |= FILE_READ_ONLY_VOLUME;
+
+#if defined(HAVE_FALLOC_FL_PUNCH_HOLE) && defined(HAVE_LSEEK_HOLE_DATA)
+ /*
+ * Only flag sparse file support if ZERO_DATA can be used to
+ * deallocate blocks, and SEEK_HOLE / SEEK_DATA can be used
+ * to provide QUERY_ALLOCATED_RANGES information.
+ */
+ statbuf->FsCapabilities |= FILE_SUPPORTS_SPARSE_FILES;
+#endif
+ }
+ return result;
+}
+#endif
+
+/*
+ sys_statvfs() is an abstraction layer over system-dependent statvfs()/statfs()
+ for particular POSIX systems. Due to controversy of what is considered more important
+ between LSB and FreeBSD/POSIX.1 (IEEE Std 1003.1-2001) we need to abstract the interface
+ so that particular OS would use its preferred interface.
+*/
+int sys_statvfs(const char *path, struct vfs_statvfs_struct *statbuf)
+{
+#if defined(BSD_STYLE_STATVFS)
+ return bsd_statvfs(path, statbuf);
+#elif defined(STAT_STATVFS) && defined(HAVE_FSID_INT)
+ return posix_statvfs(path, statbuf);
+#else
+ /* BB change this to return invalid level */
+#ifdef EOPNOTSUPP
+ return EOPNOTSUPP;
+#else
+ return -1;
+#endif /* EOPNOTSUPP */
+#endif /* LINUX */
+
+}
diff --git a/source3/smbd/uid.c b/source3/smbd/uid.c
new file mode 100644
index 0000000..52918c4
--- /dev/null
+++ b/source3/smbd/uid.c
@@ -0,0 +1,752 @@
+/*
+ Unix SMB/CIFS implementation.
+ uid/user handling
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ 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 "system/passwd.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../librpc/gen_ndr/netlogon.h"
+#include "libcli/security/security.h"
+#include "passdb/lookup_sid.h"
+#include "auth.h"
+#include "../auth/auth_util.h"
+#include "source3/lib/substitute.h"
+
+/* what user is current? */
+extern struct current_user current_user;
+
+/****************************************************************************
+ Become the guest user without changing the security context stack.
+****************************************************************************/
+
+bool change_to_guest(void)
+{
+ struct passwd *pass;
+
+ pass = Get_Pwnam_alloc(talloc_tos(), lp_guest_account());
+ if (!pass) {
+ return false;
+ }
+
+#ifdef AIX
+ /* MWW: From AIX FAQ patch to WU-ftpd: call initgroups before
+ setting IDs */
+ initgroups(pass->pw_name, pass->pw_gid);
+#endif
+
+ set_sec_ctx(pass->pw_uid, pass->pw_gid, 0, NULL, NULL);
+
+ current_user.conn = NULL;
+ current_user.vuid = UID_FIELD_INVALID;
+
+ TALLOC_FREE(pass);
+
+ return true;
+}
+
+/****************************************************************************
+ talloc free the conn->session_info if not used in the vuid cache.
+****************************************************************************/
+
+static void free_conn_session_info_if_unused(connection_struct *conn)
+{
+ unsigned int i;
+
+ for (i = 0; i < VUID_CACHE_SIZE; i++) {
+ struct vuid_cache_entry *ent;
+ ent = &conn->vuid_cache->array[i];
+ if (ent->vuid != UID_FIELD_INVALID &&
+ conn->session_info == ent->session_info) {
+ return;
+ }
+ }
+ /* Not used, safe to free. */
+ TALLOC_FREE(conn->session_info);
+}
+
+/****************************************************************************
+ Setup the share access mask for a connection.
+****************************************************************************/
+
+static uint32_t create_share_access_mask(int snum,
+ bool readonly_share,
+ const struct security_token *token)
+{
+ uint32_t share_access = 0;
+
+ share_access_check(token,
+ lp_const_servicename(snum),
+ MAXIMUM_ALLOWED_ACCESS,
+ &share_access);
+
+ if (readonly_share) {
+ share_access &=
+ ~(SEC_FILE_WRITE_DATA | SEC_FILE_APPEND_DATA |
+ SEC_FILE_WRITE_EA | SEC_FILE_WRITE_ATTRIBUTE |
+ SEC_DIR_DELETE_CHILD );
+ }
+
+ if (security_token_has_privilege(token, SEC_PRIV_SECURITY)) {
+ share_access |= SEC_FLAG_SYSTEM_SECURITY;
+ }
+ if (security_token_has_privilege(token, SEC_PRIV_RESTORE)) {
+ share_access |= SEC_RIGHTS_PRIV_RESTORE;
+ }
+ if (security_token_has_privilege(token, SEC_PRIV_BACKUP)) {
+ share_access |= SEC_RIGHTS_PRIV_BACKUP;
+ }
+ if (security_token_has_privilege(token, SEC_PRIV_TAKE_OWNERSHIP)) {
+ share_access |= SEC_STD_WRITE_OWNER;
+ }
+
+ return share_access;
+}
+
+/*******************************************************************
+ Calculate access mask and if this user can access this share.
+********************************************************************/
+
+NTSTATUS check_user_share_access(connection_struct *conn,
+ const struct auth_session_info *session_info,
+ uint32_t *p_share_access,
+ bool *p_readonly_share)
+{
+ int snum = SNUM(conn);
+ uint32_t share_access = 0;
+ bool readonly_share = false;
+
+ if (!user_ok_token(session_info->unix_info->unix_name,
+ session_info->info->domain_name,
+ session_info->security_token, snum)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ readonly_share = is_share_read_only_for_token(
+ session_info->unix_info->unix_name,
+ session_info->info->domain_name,
+ session_info->security_token,
+ conn);
+
+ share_access = create_share_access_mask(snum,
+ readonly_share,
+ session_info->security_token);
+
+ if ((share_access & (FILE_READ_DATA|FILE_WRITE_DATA)) == 0) {
+ /* No access, read or write. */
+ DBG_NOTICE("user %s connection to %s denied due to share "
+ "security descriptor.\n",
+ session_info->unix_info->unix_name,
+ lp_const_servicename(snum));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (!readonly_share &&
+ !(share_access & FILE_WRITE_DATA)) {
+ /* smb.conf allows r/w, but the security descriptor denies
+ * write. Fall back to looking at readonly. */
+ readonly_share = true;
+ DBG_INFO("falling back to read-only access-evaluation due to "
+ "security descriptor\n");
+ }
+
+ *p_share_access = share_access;
+ *p_readonly_share = readonly_share;
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ Check if a username is OK.
+
+ This sets up conn->session_info with a copy related to this vuser that
+ later code can then mess with.
+********************************************************************/
+
+static bool check_user_ok(connection_struct *conn,
+ uint64_t vuid,
+ const struct auth_session_info *session_info,
+ int snum)
+{
+ unsigned int i;
+ bool readonly_share = false;
+ bool admin_user = false;
+ struct vuid_cache_entry *ent = NULL;
+ uint32_t share_access = 0;
+ NTSTATUS status;
+
+ for (i=0; i<VUID_CACHE_SIZE; i++) {
+ ent = &conn->vuid_cache->array[i];
+ if (ent->vuid == vuid) {
+ if (vuid == UID_FIELD_INVALID) {
+ /*
+ * Slow path, we don't care
+ * about the array traversal.
+ */
+ continue;
+ }
+ free_conn_session_info_if_unused(conn);
+ conn->session_info = ent->session_info;
+ conn->read_only = ent->read_only;
+ conn->share_access = ent->share_access;
+ conn->vuid = ent->vuid;
+ return(True);
+ }
+ }
+
+ status = check_user_share_access(conn,
+ session_info,
+ &share_access,
+ &readonly_share);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ admin_user = token_contains_name_in_list(
+ session_info->unix_info->unix_name,
+ session_info->info->domain_name,
+ NULL, session_info->security_token, lp_admin_users(snum));
+
+ ent = &conn->vuid_cache->array[conn->vuid_cache->next_entry];
+
+ conn->vuid_cache->next_entry =
+ (conn->vuid_cache->next_entry + 1) % VUID_CACHE_SIZE;
+
+ TALLOC_FREE(ent->session_info);
+
+ /*
+ * If force_user was set, all session_info's are based on the same
+ * username-based faked one.
+ */
+
+ ent->session_info = copy_session_info(
+ conn, conn->force_user ? conn->session_info : session_info);
+
+ if (ent->session_info == NULL) {
+ ent->vuid = UID_FIELD_INVALID;
+ return false;
+ }
+
+ if (admin_user) {
+ DEBUG(2,("check_user_ok: user %s is an admin user. "
+ "Setting uid as %d\n",
+ ent->session_info->unix_info->unix_name,
+ sec_initial_uid() ));
+ ent->session_info->unix_token->uid = sec_initial_uid();
+ }
+
+ /*
+ * It's actually OK to call check_user_ok() with
+ * vuid == UID_FIELD_INVALID as called from become_user_by_session().
+ * All this will do is throw away one entry in the cache.
+ */
+
+ ent->vuid = vuid;
+ ent->read_only = readonly_share;
+ ent->share_access = share_access;
+ free_conn_session_info_if_unused(conn);
+ conn->session_info = ent->session_info;
+ conn->vuid = ent->vuid;
+ if (vuid == UID_FIELD_INVALID) {
+ /*
+ * Not strictly needed, just make it really
+ * clear this entry is actually an unused one.
+ */
+ ent->read_only = false;
+ ent->share_access = 0;
+ ent->session_info = NULL;
+ }
+
+ conn->read_only = readonly_share;
+ conn->share_access = share_access;
+
+ return(True);
+}
+
+static void print_impersonation_info(connection_struct *conn)
+{
+ struct smb_filename *cwdfname = NULL;
+
+ if (!CHECK_DEBUGLVL(DBGLVL_INFO)) {
+ return;
+ }
+
+ cwdfname = vfs_GetWd(talloc_tos(), conn);
+ if (cwdfname == NULL) {
+ return;
+ }
+
+ DBG_INFO("Impersonated user: uid=(%d,%d), gid=(%d,%d), cwd=[%s]\n",
+ (int)getuid(),
+ (int)geteuid(),
+ (int)getgid(),
+ (int)getegid(),
+ cwdfname->base_name);
+ TALLOC_FREE(cwdfname);
+}
+
+/****************************************************************************
+ Become the user of a connection number without changing the security context
+ stack, but modify the current_user entries.
+****************************************************************************/
+
+static bool change_to_user_impersonate(connection_struct *conn,
+ const struct auth_session_info *session_info,
+ uint64_t vuid)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int snum;
+ gid_t gid;
+ uid_t uid;
+ const char *force_group_name;
+ char group_c;
+ int num_groups = 0;
+ gid_t *group_list = NULL;
+ bool ok;
+
+ if ((current_user.conn == conn) &&
+ (current_user.vuid == vuid) &&
+ (current_user.ut.uid == session_info->unix_token->uid))
+ {
+ DBG_INFO("Skipping user change - already user\n");
+ return true;
+ }
+
+ set_current_user_info(session_info->unix_info->sanitized_username,
+ session_info->unix_info->unix_name,
+ session_info->info->domain_name);
+
+ snum = SNUM(conn);
+
+ ok = check_user_ok(conn, vuid, session_info, snum);
+ if (!ok) {
+ DBG_WARNING("SMB user %s (unix user %s) "
+ "not permitted access to share %s.\n",
+ session_info->unix_info->sanitized_username,
+ session_info->unix_info->unix_name,
+ lp_const_servicename(snum));
+ return false;
+ }
+
+ uid = conn->session_info->unix_token->uid;
+ gid = conn->session_info->unix_token->gid;
+ num_groups = conn->session_info->unix_token->ngroups;
+ group_list = conn->session_info->unix_token->groups;
+
+ /*
+ * See if we should force group for this service. If so this overrides
+ * any group set in the force user code.
+ */
+ force_group_name = lp_force_group(talloc_tos(), lp_sub, snum);
+ group_c = *force_group_name;
+
+ if ((group_c != '\0') && (conn->force_group_gid == (gid_t)-1)) {
+ /*
+ * This can happen if "force group" is added to a
+ * share definition whilst an existing connection
+ * to that share exists. In that case, don't change
+ * the existing credentials for force group, only
+ * do so for new connections.
+ *
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=13690
+ */
+ DBG_INFO("Not forcing group %s on existing connection to "
+ "share %s for SMB user %s (unix user %s)\n",
+ force_group_name,
+ lp_const_servicename(snum),
+ session_info->unix_info->sanitized_username,
+ session_info->unix_info->unix_name);
+ }
+
+ if((group_c != '\0') && (conn->force_group_gid != (gid_t)-1)) {
+ /*
+ * Only force group for connections where
+ * conn->force_group_gid has already been set
+ * to the correct value (i.e. the connection
+ * happened after the 'force group' definition
+ * was added to the share definition. Connections
+ * that were made before force group was added
+ * should stay with their existing credentials.
+ *
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=13690
+ */
+
+ if (group_c == '+') {
+ int i;
+
+ /*
+ * Only force group if the user is a member of the
+ * service group. Check the group memberships for this
+ * user (we already have this) to see if we should force
+ * the group.
+ */
+ for (i = 0; i < num_groups; i++) {
+ if (group_list[i] == conn->force_group_gid) {
+ conn->session_info->unix_token->gid =
+ conn->force_group_gid;
+ gid = conn->force_group_gid;
+ gid_to_sid(&conn->session_info->security_token
+ ->sids[1], gid);
+ break;
+ }
+ }
+ } else {
+ conn->session_info->unix_token->gid = conn->force_group_gid;
+ gid = conn->force_group_gid;
+ gid_to_sid(&conn->session_info->security_token->sids[1],
+ gid);
+ }
+ }
+
+ set_sec_ctx(uid,
+ gid,
+ num_groups,
+ group_list,
+ conn->session_info->security_token);
+
+ current_user.conn = conn;
+ current_user.vuid = vuid;
+ return true;
+}
+
+/**
+ * Impersonate user and change directory to service
+ *
+ * change_to_user_and_service() is used to impersonate the user associated with
+ * the given vuid and to change the working directory of the process to the
+ * service base directory.
+ **/
+bool change_to_user_and_service(connection_struct *conn, uint64_t vuid)
+{
+ int snum = SNUM(conn);
+ struct auth_session_info *si = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ if (conn == NULL) {
+ DBG_WARNING("Connection not open\n");
+ return false;
+ }
+
+ status = smbXsrv_session_info_lookup(conn->sconn->client,
+ vuid,
+ &si);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("Invalid vuid %llu used on share %s.\n",
+ (unsigned long long)vuid,
+ lp_const_servicename(snum));
+ return false;
+ }
+
+ ok = change_to_user_impersonate(conn, si, vuid);
+ if (!ok) {
+ return false;
+ }
+
+ if (conn->tcon_done) {
+ ok = chdir_current_service(conn);
+ if (!ok) {
+ return false;
+ }
+ }
+
+ print_impersonation_info(conn);
+ return true;
+}
+
+/**
+ * Impersonate user and change directory to service
+ *
+ * change_to_user_and_service_by_fsp() is used to impersonate the user
+ * associated with the given vuid and to change the working directory of the
+ * process to the service base directory.
+ **/
+bool change_to_user_and_service_by_fsp(struct files_struct *fsp)
+{
+ return change_to_user_and_service(fsp->conn, fsp->vuid);
+}
+
+/****************************************************************************
+ Go back to being root without changing the security context stack,
+ but modify the current_user entries.
+****************************************************************************/
+
+bool smbd_change_to_root_user(void)
+{
+ set_root_sec_ctx();
+
+ DEBUG(5,("change_to_root_user: now uid=(%d,%d) gid=(%d,%d)\n",
+ (int)getuid(),(int)geteuid(),(int)getgid(),(int)getegid()));
+
+ current_user.conn = NULL;
+ current_user.vuid = UID_FIELD_INVALID;
+
+ return(True);
+}
+
+/****************************************************************************
+ Become the user of an authenticated connected named pipe.
+ When this is called we are currently running as the connection
+ user. Doesn't modify current_user.
+****************************************************************************/
+
+bool smbd_become_authenticated_pipe_user(struct auth_session_info *session_info)
+{
+ if (!push_sec_ctx())
+ return False;
+
+ set_current_user_info(session_info->unix_info->sanitized_username,
+ session_info->unix_info->unix_name,
+ session_info->info->domain_name);
+
+ set_sec_ctx(session_info->unix_token->uid, session_info->unix_token->gid,
+ session_info->unix_token->ngroups, session_info->unix_token->groups,
+ session_info->security_token);
+
+ DEBUG(5, ("Impersonated user: uid=(%d,%d), gid=(%d,%d)\n",
+ (int)getuid(),
+ (int)geteuid(),
+ (int)getgid(),
+ (int)getegid()));
+
+ return True;
+}
+
+/****************************************************************************
+ Unbecome the user of an authenticated connected named pipe.
+ When this is called we are running as the authenticated pipe
+ user and need to go back to being the connection user. Doesn't modify
+ current_user.
+****************************************************************************/
+
+bool smbd_unbecome_authenticated_pipe_user(void)
+{
+ return pop_sec_ctx();
+}
+
+/****************************************************************************
+ Utility functions used by become_xxx/unbecome_xxx.
+****************************************************************************/
+
+static void push_conn_ctx(void)
+{
+ struct conn_ctx *ctx_p;
+ extern userdom_struct current_user_info;
+
+ /* Check we don't overflow our stack */
+
+ if (conn_ctx_stack_ndx == MAX_SEC_CTX_DEPTH) {
+ DEBUG(0, ("Connection context stack overflow!\n"));
+ smb_panic("Connection context stack overflow!\n");
+ }
+
+ /* Store previous user context */
+ ctx_p = &conn_ctx_stack[conn_ctx_stack_ndx];
+
+ ctx_p->conn = current_user.conn;
+ ctx_p->vuid = current_user.vuid;
+ ctx_p->user_info = current_user_info;
+
+ DEBUG(4, ("push_conn_ctx(%llu) : conn_ctx_stack_ndx = %d\n",
+ (unsigned long long)ctx_p->vuid, conn_ctx_stack_ndx));
+
+ conn_ctx_stack_ndx++;
+}
+
+static void pop_conn_ctx(void)
+{
+ struct conn_ctx *ctx_p;
+
+ /* Check for stack underflow. */
+
+ if (conn_ctx_stack_ndx == 0) {
+ DEBUG(0, ("Connection context stack underflow!\n"));
+ smb_panic("Connection context stack underflow!\n");
+ }
+
+ conn_ctx_stack_ndx--;
+ ctx_p = &conn_ctx_stack[conn_ctx_stack_ndx];
+
+ set_current_user_info(ctx_p->user_info.smb_name,
+ ctx_p->user_info.unix_name,
+ ctx_p->user_info.domain);
+
+ current_user.conn = ctx_p->conn;
+ current_user.vuid = ctx_p->vuid;
+
+ *ctx_p = (struct conn_ctx) {
+ .vuid = UID_FIELD_INVALID,
+ };
+}
+
+/****************************************************************************
+ Temporarily become a root user. Must match with unbecome_root(). Saves and
+ restores the connection context.
+****************************************************************************/
+
+void smbd_become_root(void)
+{
+ /*
+ * no good way to handle push_sec_ctx() failing without changing
+ * the prototype of become_root()
+ */
+ if (!push_sec_ctx()) {
+ smb_panic("become_root: push_sec_ctx failed");
+ }
+ push_conn_ctx();
+ set_root_sec_ctx();
+}
+
+/* Unbecome the root user */
+
+void smbd_unbecome_root(void)
+{
+ pop_sec_ctx();
+ pop_conn_ctx();
+}
+
+/****************************************************************************
+ Push the current security context then force a change via change_to_user().
+ Saves and restores the connection context.
+****************************************************************************/
+
+bool become_user_without_service(connection_struct *conn, uint64_t vuid)
+{
+ struct auth_session_info *session_info = NULL;
+ int snum = SNUM(conn);
+ NTSTATUS status;
+ bool ok;
+
+ if (conn == NULL) {
+ DBG_WARNING("Connection not open\n");
+ return false;
+ }
+
+ status = smbXsrv_session_info_lookup(conn->sconn->client,
+ vuid,
+ &session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Invalid vuid sent */
+ DBG_WARNING("Invalid vuid %llu used on share %s.\n",
+ (unsigned long long)vuid,
+ lp_const_servicename(snum));
+ return false;
+ }
+
+ ok = push_sec_ctx();
+ if (!ok) {
+ return false;
+ }
+
+ push_conn_ctx();
+
+ ok = change_to_user_impersonate(conn, session_info, vuid);
+ if (!ok) {
+ pop_sec_ctx();
+ pop_conn_ctx();
+ return false;
+ }
+
+ return true;
+}
+
+bool become_user_without_service_by_fsp(struct files_struct *fsp)
+{
+ return become_user_without_service(fsp->conn, fsp->vuid);
+}
+
+bool become_user_without_service_by_session(connection_struct *conn,
+ const struct auth_session_info *session_info)
+{
+ bool ok;
+
+ SMB_ASSERT(conn != NULL);
+ SMB_ASSERT(session_info != NULL);
+
+ ok = push_sec_ctx();
+ if (!ok) {
+ return false;
+ }
+
+ push_conn_ctx();
+
+ ok = change_to_user_impersonate(conn, session_info, UID_FIELD_INVALID);
+ if (!ok) {
+ pop_sec_ctx();
+ pop_conn_ctx();
+ return false;
+ }
+
+ return true;
+}
+
+bool unbecome_user_without_service(void)
+{
+ pop_sec_ctx();
+ pop_conn_ctx();
+ return True;
+}
+
+/****************************************************************************
+ Return the current user we are running effectively as on this connection.
+ I'd like to make this return conn->session_info->unix_token->uid, but become_root()
+ doesn't alter this value.
+****************************************************************************/
+
+uid_t get_current_uid(connection_struct *conn)
+{
+ return current_user.ut.uid;
+}
+
+/****************************************************************************
+ Return the current group we are running effectively as on this connection.
+ I'd like to make this return conn->session_info->unix_token->gid, but become_root()
+ doesn't alter this value.
+****************************************************************************/
+
+gid_t get_current_gid(connection_struct *conn)
+{
+ return current_user.ut.gid;
+}
+
+/****************************************************************************
+ Return the UNIX token we are running effectively as on this connection.
+ I'd like to make this return &conn->session_info->unix_token-> but become_root()
+ doesn't alter this value.
+****************************************************************************/
+
+const struct security_unix_token *get_current_utok(connection_struct *conn)
+{
+ return &current_user.ut;
+}
+
+/****************************************************************************
+ Return the Windows token we are running effectively as on this connection.
+ If this is currently a NULL token as we're inside become_root() - a temporary
+ UNIX security override, then we search up the stack for the previous active
+ token.
+****************************************************************************/
+
+const struct security_token *get_current_nttok(connection_struct *conn)
+{
+ if (current_user.nt_user_token) {
+ return current_user.nt_user_token;
+ }
+ return sec_ctx_active_token();
+}
diff --git a/source3/smbd/utmp.c b/source3/smbd/utmp.c
new file mode 100644
index 0000000..4327301
--- /dev/null
+++ b/source3/smbd/utmp.c
@@ -0,0 +1,604 @@
+/*
+ Unix SMB/CIFS implementation.
+ utmp routines
+ Copyright (C) T.D.Lee@durham.ac.uk 1999
+ Heavily modified by Andrew Bartlett and Tridge, April 2001
+
+ 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 "system/filesys.h"
+#include "smbd/smbd.h"
+
+/****************************************************************************
+Reflect connection status in utmp/wtmp files.
+ T.D.Lee@durham.ac.uk September 1999
+
+ With grateful thanks since then to many who have helped port it to
+ different operating systems. The variety of OS quirks thereby
+ uncovered is amazing...
+
+Hints for porting:
+ o Always attempt to use programmatic interface (pututline() etc.)
+ Indeed, at present only programmatic use is supported.
+ o The only currently supported programmatic interface to "wtmp{,x}"
+ is through "updwtmp*()" routines.
+ o The "x" (utmpx/wtmpx; HAVE_UTMPX_H) seems preferable.
+ o The HAVE_* items should identify supported features.
+ o If at all possible, avoid "if defined(MY-OS)" constructions.
+
+OS observations and status:
+ Almost every OS seems to have its own quirks.
+
+ Solaris 2.x:
+ Tested on 2.6 and 2.7; should be OK on other flavours.
+ AIX:
+ Apparently has utmpx.h but doesn't implement.
+ OSF:
+ Has utmpx.h, but (e.g.) no "getutmpx()". (Is this like AIX ?)
+ Redhat 6:
+ utmpx.h seems not to set default filenames. non-x better.
+ IRIX 6.5:
+ Not tested. Appears to have "x".
+ HP-UX 9.x:
+ Not tested. Appears to lack "x".
+ HP-UX 10.x:
+ Not tested.
+ "updwtmp*()" routines seem absent, so no current wtmp* support.
+ Has "ut_addr": probably trivial to implement (although remember
+ that IPv6 is coming...).
+
+ FreeBSD:
+ No "putut*()" type of interface.
+ No "ut_type" and associated defines.
+ Write files directly. Alternatively use its login(3)/logout(3).
+ SunOS 4:
+ Not tested. Resembles FreeBSD, but no login()/logout().
+
+lastlog:
+ Should "lastlog" files, if any, be updated?
+ BSD systems (SunOS 4, FreeBSD):
+ o Prominent mention on man pages.
+ System-V (e.g. Solaris 2):
+ o No mention on man pages, even under "man -k".
+ o Has a "/var/adm/lastlog" file, but pututxline() etc. seem
+ not to touch it.
+ o Despite downplaying (above), nevertheless has <lastlog.h>.
+ So perhaps UN*X "lastlog" facility is intended for tty/terminal only?
+
+Notes:
+ Each connection requires a small number (starting at 0, working up)
+ to represent the line. This must be unique within and across all
+ smbd processes. It is the 'id_num' from Samba's session.c code.
+
+ The 4 byte 'ut_id' component is vital to distinguish connections,
+ of which there could be several hundred or even thousand.
+ Entries seem to be printable characters, with optional NULL pads.
+
+ We need to be distinct from other entries in utmp/wtmp.
+
+ Observed things: therefore avoid them. Add to this list please.
+ From Solaris 2.x (because that's what I have):
+ 'sN' : run-levels; N: [0-9]
+ 'co' : console
+ 'CC' : arbitrary things; C: [a-z]
+ 'rXNN' : rlogin; N: [0-9]; X: [0-9a-z]
+ 'tXNN' : rlogin; N: [0-9]; X: [0-9a-z]
+ '/NNN' : Solaris CDE
+ 'ftpZ' : ftp (Z is the number 255, aka 0377, aka 0xff)
+ Mostly a record uses the same 'ut_id' in both "utmp" and "wtmp",
+ but differences have been seen.
+
+ Arbitrarily I have chosen to use a distinctive 'SM' for the
+ first two bytes.
+
+ The remaining two bytes encode the session 'id_num' (see above).
+ Our caller (session.c) should note our 16-bit limitation.
+
+****************************************************************************/
+
+#ifndef WITH_UTMP
+/*
+ * Not WITH_UTMP? Simply supply dummy routines.
+ */
+
+void sys_utmp_claim(const char *username, const char *hostname,
+ const char *id_str, int id_num)
+{}
+
+void sys_utmp_yield(const char *username, const char *hostname,
+ const char *id_str, int id_num)
+{}
+
+#else /* WITH_UTMP */
+
+#ifdef HAVE_UTMP_H
+#include <utmp.h>
+#endif
+
+#ifdef HAVE_UTMPX_H
+#include <utmpx.h>
+#endif
+
+/* BSD systems: some may need lastlog.h (SunOS 4), some may not (FreeBSD) */
+/* Some System-V systems (e.g. Solaris 2) declare this too. */
+#ifdef HAVE_LASTLOG_H
+#include <lastlog.h>
+#endif
+
+/****************************************************************************
+ Default paths to various {u,w}tmp{,x} files.
+****************************************************************************/
+
+#ifdef HAVE_UTMPX_H
+
+static const char * const ux_pathname =
+# if defined (UTMPX_FILE)
+ UTMPX_FILE ;
+# elif defined (_UTMPX_FILE)
+ _UTMPX_FILE ;
+# elif defined (_PATH_UTMPX)
+ _PATH_UTMPX ;
+# else
+ "" ;
+# endif
+
+static const char * const wx_pathname =
+# if defined (WTMPX_FILE)
+ WTMPX_FILE ;
+# elif defined (_WTMPX_FILE)
+ _WTMPX_FILE ;
+# elif defined (_PATH_WTMPX)
+ _PATH_WTMPX ;
+# else
+ "" ;
+# endif
+
+#endif /* HAVE_UTMPX_H */
+
+static const char * const ut_pathname =
+# if defined (UTMP_FILE)
+ UTMP_FILE ;
+# elif defined (_UTMP_FILE)
+ _UTMP_FILE ;
+# elif defined (_PATH_UTMP)
+ _PATH_UTMP ;
+# else
+ "" ;
+# endif
+
+static const char * const wt_pathname =
+# if defined (WTMP_FILE)
+ WTMP_FILE ;
+# elif defined (_WTMP_FILE)
+ _WTMP_FILE ;
+# elif defined (_PATH_WTMP)
+ _PATH_WTMP ;
+# else
+ "" ;
+# endif
+
+/* BSD-like systems might want "lastlog" support. */
+#if 0 /* *** Not yet implemented */
+#ifndef HAVE_PUTUTLINE /* see "pututline_my()" */
+static const char *ll_pathname =
+# if defined (_PATH_LASTLOG) /* what other names (if any?) */
+ _PATH_LASTLOG ;
+# else
+ "" ;
+# endif /* _PATH_LASTLOG */
+#endif /* HAVE_PUTUTLINE */
+#endif
+
+/*
+ * Get name of {u,w}tmp{,x} file.
+ * return: fname contains filename
+ * Possibly empty if this code not yet ported to this system.
+ *
+ * utmp{,x}: try "utmp dir", then default (a define)
+ * wtmp{,x}: try "wtmp dir", then "utmp dir", then default (a define)
+ */
+static char *uw_pathname(TALLOC_CTX *ctx,
+ const char *uw_name,
+ const char *uw_default)
+{
+ char *dirname = NULL;
+
+ /* For w-files, first look for explicit "wtmp dir" */
+ if (uw_name[0] == 'w') {
+ dirname = talloc_strdup(ctx, lp_wtmp_directory());
+ if (!dirname) {
+ return NULL;
+ }
+ trim_char(dirname,'\0','/');
+ }
+
+ /* For u-files and non-explicit w-dir, look for "utmp dir" */
+ if ((dirname == NULL) || (strlen(dirname) == 0)) {
+ dirname = talloc_strdup(ctx, lp_utmp_directory());
+ if (!dirname) {
+ return NULL;
+ }
+ trim_char(dirname,'\0','/');
+ }
+
+ /* If explicit directory above, use it */
+ if (dirname && strlen(dirname) != 0) {
+ return talloc_asprintf(ctx,
+ "%s/%s",
+ dirname,
+ uw_name);
+ }
+
+ /* No explicit directory: attempt to use default paths */
+ if (strlen(uw_default) == 0) {
+ /* No explicit setting, no known default.
+ * Has it yet been ported to this OS?
+ */
+ DEBUG(2,("uw_pathname: unable to determine pathname\n"));
+ }
+ return talloc_strdup(ctx, uw_default);
+}
+
+#ifndef HAVE_PUTUTLINE
+/****************************************************************************
+ Update utmp file directly. No subroutine interface: probably a BSD system.
+****************************************************************************/
+
+static void pututline_my(const char *uname, struct utmp *u, bool claim)
+{
+ DEBUG(1,("pututline_my: not yet implemented\n"));
+ /* BSD implementor: may want to consider (or not) adjusting "lastlog" */
+}
+#endif /* HAVE_PUTUTLINE */
+
+#ifndef HAVE_UPDWTMP
+
+/****************************************************************************
+ Update wtmp file directly. No subroutine interface: probably a BSD system.
+ Credit: Michail Vidiassov <master@iaas.msu.ru>
+****************************************************************************/
+
+static void updwtmp_my(const char *wname, struct utmp *u, bool claim)
+{
+ int fd;
+ struct stat buf;
+
+ if (! claim) {
+ /*
+ * BSD-like systems:
+ * may use empty ut_name to distinguish a logout record.
+ *
+ * May need "if defined(SUNOS4)" etc. around some of these,
+ * but try to avoid if possible.
+ *
+ * SunOS 4:
+ * man page indicates ut_name and ut_host both NULL
+ * FreeBSD 4.0:
+ * man page appears not to specify (hints non-NULL)
+ * A correspondent suggest at least ut_name should be NULL
+ */
+#if defined(HAVE_UT_UT_NAME)
+ memset((char *)&u->ut_name, '\0', sizeof(u->ut_name));
+#endif
+#if defined(HAVE_UT_UT_HOST)
+ memset((char *)&u->ut_host, '\0', sizeof(u->ut_host));
+#endif
+ }
+ /* Stolen from logwtmp function in libutil.
+ * May be more locking/blocking is needed?
+ */
+ if ((fd = open(wname, O_WRONLY|O_APPEND, 0)) < 0)
+ return;
+ if (fstat(fd, &buf) == 0) {
+ if (write(fd, (char *)u, sizeof(struct utmp)) != sizeof(struct utmp))
+ (void) ftruncate(fd, buf.st_size);
+ }
+ (void) close(fd);
+}
+#endif /* HAVE_UPDWTMP */
+
+/****************************************************************************
+ Update via utmp/wtmp (not utmpx/wtmpx).
+****************************************************************************/
+
+static void utmp_nox_update(struct utmp *u, bool claim)
+{
+ char *uname = NULL;
+ char *wname = NULL;
+#if defined(PUTUTLINE_RETURNS_UTMP)
+ struct utmp *urc;
+#endif /* PUTUTLINE_RETURNS_UTMP */
+
+ uname = uw_pathname(talloc_tos(), "utmp", ut_pathname);
+ if (!uname) {
+ return;
+ }
+ DEBUG(2,("utmp_nox_update: uname:%s\n", uname));
+
+#ifdef HAVE_PUTUTLINE
+ if (strlen(uname) != 0) {
+ utmpname(uname);
+ }
+
+# if defined(PUTUTLINE_RETURNS_UTMP)
+ setutent();
+ urc = pututline(u);
+ endutent();
+ if (urc == NULL) {
+ DEBUG(2,("utmp_nox_update: pututline() failed\n"));
+ return;
+ }
+# else /* PUTUTLINE_RETURNS_UTMP */
+ setutent();
+ pututline(u);
+ endutent();
+# endif /* PUTUTLINE_RETURNS_UTMP */
+
+#else /* HAVE_PUTUTLINE */
+ if (strlen(uname) != 0) {
+ pututline_my(uname, u, claim);
+ }
+#endif /* HAVE_PUTUTLINE */
+
+ wname = uw_pathname(talloc_tos(), "wtmp", wt_pathname);
+ if (!wname) {
+ return;
+ }
+ DEBUG(2,("utmp_nox_update: wname:%s\n", wname));
+ if (strlen(wname) != 0) {
+#ifdef HAVE_UPDWTMP
+ updwtmp(wname, u);
+ /*
+ * updwtmp() and the newer updwtmpx() may be unsymmetrical.
+ * At least one OS, Solaris 2.x declares the former in the
+ * "utmpx" (latter) file and context.
+ * In the Solaris case this is irrelevant: it has both and
+ * we always prefer the "x" case, so doesn't come here.
+ * But are there other systems, with no "x", which lack
+ * updwtmp() perhaps?
+ */
+#else
+ updwtmp_my(wname, u, claim);
+#endif /* HAVE_UPDWTMP */
+ }
+}
+
+/****************************************************************************
+ Copy a string in the utmp structure.
+****************************************************************************/
+
+static void utmp_strcpy(char *dest, const char *src, size_t n)
+{
+ size_t len = 0;
+
+ memset(dest, '\0', n);
+ if (src)
+ len = strlen(src);
+ if (len >= n) {
+ memcpy(dest, src, n);
+ } else {
+ if (len)
+ memcpy(dest, src, len);
+ }
+}
+
+/****************************************************************************
+ Update via utmpx/wtmpx (preferred) or via utmp/wtmp.
+****************************************************************************/
+
+static void sys_utmp_update(struct utmp *u, const char *hostname, bool claim)
+{
+#if !defined(HAVE_UTMPX_H)
+ /* No utmpx stuff. Drop to non-x stuff */
+ utmp_nox_update(u, claim);
+#elif !defined(HAVE_PUTUTXLINE)
+ /* Odd. Have utmpx.h but no "pututxline()". Drop to non-x stuff */
+ DEBUG(1,("utmp_update: have utmpx.h but no pututxline() function\n"));
+ utmp_nox_update(u, claim);
+#elif !defined(HAVE_GETUTMPX)
+ /* Odd. Have utmpx.h but no "getutmpx()". Drop to non-x stuff */
+ DEBUG(1,("utmp_update: have utmpx.h but no getutmpx() function\n"));
+ utmp_nox_update(u, claim);
+#elif !defined(HAVE_UPDWTMPX)
+ /* Have utmpx.h but no "updwtmpx()". Drop to non-x stuff */
+ DEBUG(1,("utmp_update: have utmpx.h but no updwtmpx() function\n"));
+ utmp_nox_update(u, claim);
+#else
+ char *uname = NULL;
+ char *wname = NULL;
+ struct utmpx ux, *uxrc;
+
+ getutmpx(u, &ux);
+
+#if defined(HAVE_UX_UT_SYSLEN)
+ if (hostname)
+ ux.ut_syslen = strlen(hostname) + 1; /* include end NULL */
+ else
+ ux.ut_syslen = 0;
+#endif
+#if defined(HAVE_UX_UT_HOST)
+ utmp_strcpy(ux.ut_host, hostname, sizeof(ux.ut_host));
+#endif
+
+ uname = uw_pathname(talloc_tos(), "utmpx", ux_pathname);
+ wname = uw_pathname(talloc_tos(), "wtmpx", wx_pathname);
+ if (uname && wname) {
+ DEBUG(2,("utmp_update: uname:%s wname:%s\n", uname, wname));
+ }
+
+ /*
+ * Check for either uname or wname being empty.
+ * Some systems, such as Redhat 6, have a "utmpx.h" which doesn't
+ * define default filenames.
+ * Also, our local installation has not provided an override.
+ * Drop to non-x method. (E.g. RH6 has good defaults in "utmp.h".)
+ */
+ if (!uname || !wname || (strlen(uname) == 0) || (strlen(wname) == 0)) {
+ utmp_nox_update(u, claim);
+ } else {
+ utmpxname(uname);
+ setutxent();
+ uxrc = pututxline(&ux);
+ endutxent();
+ if (uxrc == NULL) {
+ DEBUG(2,("utmp_update: pututxline() failed\n"));
+ return;
+ }
+ updwtmpx(wname, &ux);
+ }
+#endif /* HAVE_UTMPX_H */
+}
+
+#if defined(HAVE_UT_UT_ID)
+/****************************************************************************
+ Encode the unique connection number into "ut_id".
+****************************************************************************/
+
+static int ut_id_encode(int i, char *fourbyte)
+{
+ int nbase;
+ const char *ut_id_encstr = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+/*
+ * 'ut_id_encstr' is the character set on which modulo arithmetic is done.
+ * Example: digits would produce the base-10 numbers from '001'.
+ */
+ nbase = strlen(ut_id_encstr);
+
+ fourbyte[0] = ut_id_encstr[i % nbase];
+ i /= nbase;
+ fourbyte[1] = ut_id_encstr[i % nbase];
+ i /= nbase;
+ fourbyte[3] = ut_id_encstr[i % nbase];
+ i /= nbase;
+ fourbyte[2] = ut_id_encstr[i % nbase];
+ i /= nbase;
+
+ /* we do not care about overflows as i is a random number */
+ return 0;
+}
+#endif /* defined(HAVE_UT_UT_ID) */
+
+
+/*
+ fill a system utmp structure given all the info we can gather
+*/
+static bool sys_utmp_fill(struct utmp *u,
+ const char *username, const char *hostname,
+ const char *id_str, int id_num)
+{
+ struct timeval timeval;
+
+ /*
+ * ut_name, ut_user:
+ * Several (all?) systems seems to define one as the other.
+ * It is easier and clearer simply to let the following take its course,
+ * rather than to try to detect and optimise.
+ */
+#if defined(HAVE_UT_UT_USER)
+ utmp_strcpy(u->ut_user, username, sizeof(u->ut_user));
+#elif defined(HAVE_UT_UT_NAME)
+ utmp_strcpy(u->ut_name, username, sizeof(u->ut_name));
+#endif
+
+ /*
+ * ut_line:
+ * If size limit proves troublesome, then perhaps use "ut_id_encode()".
+ */
+ utmp_strcpy(u->ut_line, id_str, sizeof(u->ut_line));
+
+#if defined(HAVE_UT_UT_PID)
+ u->ut_pid = getpid();
+#endif
+
+/*
+ * ut_time, ut_tv:
+ * Some have one, some the other. Many have both, but defined (aliased).
+ * It is easier and clearer simply to let the following take its course.
+ * But note that we do the more precise ut_tv as the final assignment.
+ */
+#if defined(HAVE_UT_UT_TIME)
+ GetTimeOfDay(&timeval);
+ u->ut_time = timeval.tv_sec;
+#elif defined(HAVE_UT_UT_TV)
+ GetTimeOfDay(&timeval);
+ u->ut_tv = timeval;
+#else
+#error "with-utmp must have UT_TIME or UT_TV"
+#endif
+
+#if defined(HAVE_UT_UT_HOST)
+ utmp_strcpy(u->ut_host, hostname, sizeof(u->ut_host));
+#endif
+
+#if defined(HAVE_UT_UT_ID)
+ if (ut_id_encode(id_num, u->ut_id) != 0) {
+ DEBUG(1,("utmp_fill: cannot encode id %d\n", id_num));
+ return False;
+ }
+#endif
+
+ return True;
+}
+
+/****************************************************************************
+ Close a connection.
+****************************************************************************/
+
+void sys_utmp_yield(const char *username, const char *hostname,
+ const char *id_str, int id_num)
+{
+ struct utmp u;
+
+ ZERO_STRUCT(u);
+
+#if defined(HAVE_UT_UT_EXIT)
+ u.ut_exit.e_termination = 0;
+ u.ut_exit.e_exit = 0;
+#endif
+
+#if defined(HAVE_UT_UT_TYPE)
+ u.ut_type = DEAD_PROCESS;
+#endif
+
+ if (!sys_utmp_fill(&u, username, hostname, id_str, id_num))
+ return;
+
+ sys_utmp_update(&u, NULL, False);
+}
+
+/****************************************************************************
+ Claim a entry in whatever utmp system the OS uses.
+****************************************************************************/
+
+void sys_utmp_claim(const char *username, const char *hostname,
+ const char *id_str, int id_num)
+{
+ struct utmp u;
+
+ ZERO_STRUCT(u);
+
+#if defined(HAVE_UT_UT_TYPE)
+ u.ut_type = USER_PROCESS;
+#endif
+
+ if (!sys_utmp_fill(&u, username, hostname, id_str, id_num))
+ return;
+
+ sys_utmp_update(&u, hostname, True);
+}
+
+#endif /* WITH_UTMP */
diff --git a/source3/smbd/vfs.c b/source3/smbd/vfs.c
new file mode 100644
index 0000000..0b061f1
--- /dev/null
+++ b/source3/smbd/vfs.c
@@ -0,0 +1,2661 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 1.9.
+ VFS initialisation and support functions
+ Copyright (C) Tim Potter 1999
+ Copyright (C) Alexander Bokovoy 2002
+ Copyright (C) James Peach 2006
+ Copyright (C) Volker Lendecke 2009
+
+ 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/>.
+
+ This work was sponsored by Optifacio Software Services, Inc.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../lib/util/memcache.h"
+#include "transfer_file.h"
+#include "ntioctl.h"
+#include "lib/util/tevent_unix.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "lib/util/sys_rw.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+static_decl_vfs;
+
+struct vfs_fsp_data {
+ struct vfs_fsp_data *next;
+ struct vfs_handle_struct *owner;
+ void (*destroy)(void *p_data);
+ void *_dummy_;
+ /* NOTE: This structure contains four pointers so that we can guarantee
+ * that the end of the structure is always both 4-byte and 8-byte aligned.
+ */
+};
+
+struct vfs_init_function_entry {
+ char *name;
+ struct vfs_init_function_entry *prev, *next;
+ const struct vfs_fn_pointers *fns;
+};
+
+/****************************************************************************
+ maintain the list of available backends
+****************************************************************************/
+
+static struct vfs_init_function_entry *vfs_find_backend_entry(const char *name)
+{
+ struct vfs_init_function_entry *entry = backends;
+
+ DEBUG(10, ("vfs_find_backend_entry called for %s\n", name));
+
+ while(entry) {
+ if (strcmp(entry->name, name)==0) return entry;
+ entry = entry->next;
+ }
+
+ return NULL;
+}
+
+NTSTATUS smb_register_vfs(int version, const char *name,
+ const struct vfs_fn_pointers *fns)
+{
+ struct vfs_init_function_entry *entry = backends;
+
+ if ((version != SMB_VFS_INTERFACE_VERSION)) {
+ DEBUG(0, ("Failed to register vfs module.\n"
+ "The module was compiled against SMB_VFS_INTERFACE_VERSION %d,\n"
+ "current SMB_VFS_INTERFACE_VERSION is %d.\n"
+ "Please recompile against the current Samba Version!\n",
+ version, SMB_VFS_INTERFACE_VERSION));
+ return NT_STATUS_OBJECT_TYPE_MISMATCH;
+ }
+
+ if (!name || !name[0]) {
+ DEBUG(0,("smb_register_vfs() called with NULL pointer or empty name!\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (vfs_find_backend_entry(name)) {
+ DEBUG(0,("VFS module %s already loaded!\n", name));
+ return NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+
+ entry = SMB_XMALLOC_P(struct vfs_init_function_entry);
+ entry->name = smb_xstrdup(name);
+ entry->fns = fns;
+
+ DLIST_ADD(backends, entry);
+ DEBUG(5, ("Successfully added vfs backend '%s'\n", name));
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ initialise default vfs hooks
+****************************************************************************/
+
+static void vfs_init_default(connection_struct *conn)
+{
+ DEBUG(3, ("Initialising default vfs hooks\n"));
+ vfs_init_custom(conn, DEFAULT_VFS_MODULE_NAME);
+}
+
+/****************************************************************************
+ initialise custom vfs hooks
+ ****************************************************************************/
+
+bool vfs_init_custom(connection_struct *conn, const char *vfs_object)
+{
+ char *module_path = NULL;
+ char *module_name = NULL;
+ char *module_param = NULL, *p;
+ vfs_handle_struct *handle;
+ const struct vfs_init_function_entry *entry;
+
+ if (!conn||!vfs_object||!vfs_object[0]) {
+ DEBUG(0, ("vfs_init_custom() called with NULL pointer or "
+ "empty vfs_object!\n"));
+ return False;
+ }
+
+ if(!backends) {
+ static_init_vfs(NULL);
+ }
+
+ DEBUG(3, ("Initialising custom vfs hooks from [%s]\n", vfs_object));
+
+ module_path = smb_xstrdup(vfs_object);
+
+ p = strchr_m(module_path, ':');
+
+ if (p) {
+ *p = 0;
+ module_param = p+1;
+ trim_char(module_param, ' ', ' ');
+ }
+
+ trim_char(module_path, ' ', ' ');
+
+ module_name = smb_xstrdup(module_path);
+
+ if ((module_name[0] == '/') &&
+ (strcmp(module_path, DEFAULT_VFS_MODULE_NAME) != 0)) {
+
+ /*
+ * Extract the module name from the path. Just use the base
+ * name of the last path component.
+ */
+
+ SAFE_FREE(module_name);
+ module_name = smb_xstrdup(strrchr_m(module_path, '/')+1);
+
+ p = strchr_m(module_name, '.');
+
+ if (p != NULL) {
+ *p = '\0';
+ }
+ }
+
+ /* First, try to load the module with the new module system */
+ entry = vfs_find_backend_entry(module_name);
+ if (!entry) {
+ NTSTATUS status;
+
+ DEBUG(5, ("vfs module [%s] not loaded - trying to load...\n",
+ vfs_object));
+
+ status = smb_load_module("vfs", module_path);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("error probing vfs module '%s': %s\n",
+ module_path, nt_errstr(status)));
+ goto fail;
+ }
+
+ entry = vfs_find_backend_entry(module_name);
+ if (!entry) {
+ DEBUG(0,("Can't find a vfs module [%s]\n",vfs_object));
+ goto fail;
+ }
+ }
+
+ DEBUGADD(5,("Successfully loaded vfs module [%s] with the new modules system\n", vfs_object));
+
+ handle = talloc_zero(conn, vfs_handle_struct);
+ if (!handle) {
+ DEBUG(0,("TALLOC_ZERO() failed!\n"));
+ goto fail;
+ }
+ handle->conn = conn;
+ handle->fns = entry->fns;
+ if (module_param) {
+ handle->param = talloc_strdup(conn, module_param);
+ }
+ DLIST_ADD(conn->vfs_handles, handle);
+
+ SAFE_FREE(module_path);
+ SAFE_FREE(module_name);
+ return True;
+
+ fail:
+ SAFE_FREE(module_path);
+ SAFE_FREE(module_name);
+ return False;
+}
+
+/*****************************************************************
+ Allow VFS modules to extend files_struct with VFS-specific state.
+ This will be ok for small numbers of extensions, but might need to
+ be refactored if it becomes more widely used.
+******************************************************************/
+
+#define EXT_DATA_AREA(e) ((uint8_t *)(e) + sizeof(struct vfs_fsp_data))
+
+void *vfs_add_fsp_extension_notype(vfs_handle_struct *handle,
+ files_struct *fsp, size_t ext_size,
+ void (*destroy_fn)(void *p_data))
+{
+ struct vfs_fsp_data *ext;
+ void * ext_data;
+
+ /* Prevent VFS modules adding multiple extensions. */
+ if ((ext_data = vfs_fetch_fsp_extension(handle, fsp))) {
+ return ext_data;
+ }
+
+ ext = talloc_zero_size(
+ handle->conn, sizeof(struct vfs_fsp_data) + ext_size);
+ if (ext == NULL) {
+ return NULL;
+ }
+
+ ext->owner = handle;
+ ext->next = fsp->vfs_extension;
+ ext->destroy = destroy_fn;
+ fsp->vfs_extension = ext;
+ return EXT_DATA_AREA(ext);
+}
+
+void vfs_remove_fsp_extension(vfs_handle_struct *handle, files_struct *fsp)
+{
+ struct vfs_fsp_data *curr;
+ struct vfs_fsp_data *prev;
+
+ for (curr = fsp->vfs_extension, prev = NULL;
+ curr;
+ prev = curr, curr = curr->next) {
+ if (curr->owner == handle) {
+ if (prev) {
+ prev->next = curr->next;
+ } else {
+ fsp->vfs_extension = curr->next;
+ }
+ if (curr->destroy) {
+ curr->destroy(EXT_DATA_AREA(curr));
+ }
+ TALLOC_FREE(curr);
+ return;
+ }
+ }
+}
+
+void vfs_remove_all_fsp_extensions(files_struct *fsp)
+{
+ struct vfs_fsp_data *curr;
+ struct vfs_fsp_data *next;
+
+ for (curr = fsp->vfs_extension; curr; curr = next) {
+
+ next = curr->next;
+ fsp->vfs_extension = next;
+
+ if (curr->destroy) {
+ curr->destroy(EXT_DATA_AREA(curr));
+ }
+ TALLOC_FREE(curr);
+ }
+}
+
+void *vfs_memctx_fsp_extension(vfs_handle_struct *handle,
+ const struct files_struct *fsp)
+{
+ struct vfs_fsp_data *head;
+
+ for (head = fsp->vfs_extension; head; head = head->next) {
+ if (head->owner == handle) {
+ return head;
+ }
+ }
+
+ return NULL;
+}
+
+void *vfs_fetch_fsp_extension(vfs_handle_struct *handle,
+ const struct files_struct *fsp)
+{
+ struct vfs_fsp_data *head;
+
+ head = (struct vfs_fsp_data *)vfs_memctx_fsp_extension(handle, fsp);
+ if (head != NULL) {
+ return EXT_DATA_AREA(head);
+ }
+
+ return NULL;
+}
+
+#undef EXT_DATA_AREA
+
+/*
+ * Ensure this module catches all VFS functions.
+ */
+#ifdef DEVELOPER
+void smb_vfs_assert_all_fns(const struct vfs_fn_pointers* fns,
+ const char *module)
+{
+ bool missing_fn = false;
+ unsigned int idx;
+ const uintptr_t *end = (const uintptr_t *)(fns + 1);
+
+ for (idx = 0; ((const uintptr_t *)fns + idx) < end; idx++) {
+ if (*((const uintptr_t *)fns + idx) == 0) {
+ DBG_ERR("VFS function at index %d not implemented "
+ "in module %s\n", idx, module);
+ missing_fn = true;
+ }
+ }
+
+ if (missing_fn) {
+ smb_panic("Required VFS function not implemented in module.\n");
+ }
+}
+#else
+void smb_vfs_assert_all_fns(const struct vfs_fn_pointers* fns,
+ const char *module)
+{
+}
+#endif
+
+/*****************************************************************
+ Generic VFS init.
+******************************************************************/
+
+bool smbd_vfs_init(connection_struct *conn)
+{
+ const char **vfs_objects;
+ unsigned int i = 0;
+ int j = 0;
+
+ /* Normal share - initialise with disk access functions */
+ vfs_init_default(conn);
+
+ /* No need to load vfs modules for printer connections */
+ if (conn->printer) {
+ return True;
+ }
+
+ if (lp_widelinks(SNUM(conn))) {
+ /*
+ * As the widelinks logic is now moving into a
+ * vfs_widelinks module, we need to custom load
+ * it after the default module is initialized.
+ * That way no changes to smb.conf files are
+ * needed.
+ */
+ bool ok = vfs_init_custom(conn, "widelinks");
+ if (!ok) {
+ DBG_ERR("widelinks enabled and vfs_init_custom "
+ "failed for vfs_widelinks module\n");
+ return false;
+ }
+ }
+
+ vfs_objects = lp_vfs_objects(SNUM(conn));
+
+ /* Override VFS functions if 'vfs object' was not specified*/
+ if (!vfs_objects || !vfs_objects[0])
+ return True;
+
+ for (i=0; vfs_objects[i] ;) {
+ i++;
+ }
+
+ for (j=i-1; j >= 0; j--) {
+ if (!vfs_init_custom(conn, vfs_objects[j])) {
+ DEBUG(0, ("smbd_vfs_init: vfs_init_custom failed for %s\n", vfs_objects[j]));
+ return False;
+ }
+ }
+ return True;
+}
+
+/*******************************************************************
+ Check if a file exists in the vfs.
+********************************************************************/
+
+NTSTATUS vfs_file_exist(connection_struct *conn, struct smb_filename *smb_fname)
+{
+ /* Only return OK if stat was successful and S_ISREG */
+ if ((SMB_VFS_STAT(conn, smb_fname) != -1) &&
+ S_ISREG(smb_fname->st.st_ex_mode)) {
+ return NT_STATUS_OK;
+ }
+
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+}
+
+bool vfs_valid_pread_range(off_t offset, size_t length)
+{
+ return sys_valid_io_range(offset, length);
+}
+
+bool vfs_valid_pwrite_range(off_t offset, size_t length)
+{
+ /*
+ * See MAXFILESIZE in [MS-FSA] 2.1.5.3 Server Requests a Write
+ */
+ static const uint64_t maxfilesize = 0xfffffff0000;
+ uint64_t last_byte_ofs;
+ bool ok;
+
+ ok = sys_valid_io_range(offset, length);
+ if (!ok) {
+ return false;
+ }
+
+ if (length == 0) {
+ return true;
+ }
+
+ last_byte_ofs = offset + length;
+ if (last_byte_ofs > maxfilesize) {
+ return false;
+ }
+
+ return true;
+}
+
+ssize_t vfs_pwrite_data(struct smb_request *req,
+ files_struct *fsp,
+ const char *buffer,
+ size_t N,
+ off_t offset)
+{
+ size_t total=0;
+ ssize_t ret;
+ bool ok;
+
+ ok = vfs_valid_pwrite_range(offset, N);
+ if (!ok) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (req && req->unread_bytes) {
+ int sockfd = req->xconn->transport.sock;
+ SMB_ASSERT(req->unread_bytes == N);
+ /* VFS_RECVFILE must drain the socket
+ * before returning. */
+ req->unread_bytes = 0;
+ /*
+ * Leave the socket non-blocking and
+ * use SMB_VFS_RECVFILE. If it returns
+ * EAGAIN || EWOULDBLOCK temporarily set
+ * the socket blocking and retry
+ * the RECVFILE.
+ */
+ while (total < N) {
+ ret = SMB_VFS_RECVFILE(sockfd,
+ fsp,
+ offset + total,
+ N - total);
+ if (ret == 0 || (ret == -1 &&
+ (errno == EAGAIN ||
+ errno == EWOULDBLOCK))) {
+ int old_flags;
+ /* Ensure the socket is blocking. */
+ old_flags = fcntl(sockfd, F_GETFL, 0);
+ if (set_blocking(sockfd, true) == -1) {
+ return (ssize_t)-1;
+ }
+ ret = SMB_VFS_RECVFILE(sockfd,
+ fsp,
+ offset + total,
+ N - total);
+ if (fcntl(sockfd, F_SETFL, old_flags) == -1) {
+ return (ssize_t)-1;
+ }
+ if (ret == -1) {
+ return (ssize_t)-1;
+ }
+ total += ret;
+ return (ssize_t)total;
+ }
+ /* Any other error case. */
+ if (ret == -1) {
+ return ret;
+ }
+ total += ret;
+ }
+ return (ssize_t)total;
+ }
+
+ while (total < N) {
+ ret = SMB_VFS_PWRITE(fsp, buffer + total, N - total,
+ offset + total);
+
+ if (ret == -1)
+ return -1;
+ if (ret == 0)
+ return total;
+
+ total += ret;
+ }
+ return (ssize_t)total;
+}
+/****************************************************************************
+ An allocate file space call using the vfs interface.
+ Allocates space for a file from a filedescriptor.
+ Returns 0 on success, -1 on failure.
+****************************************************************************/
+
+int vfs_allocate_file_space(files_struct *fsp, uint64_t len)
+{
+ int ret;
+ connection_struct *conn = fsp->conn;
+ uint64_t space_avail;
+ uint64_t bsize,dfree,dsize;
+ NTSTATUS status;
+ bool ok;
+
+ /*
+ * Actually try and commit the space on disk....
+ */
+
+ DEBUG(10,("vfs_allocate_file_space: file %s, len %.0f\n",
+ fsp_str_dbg(fsp), (double)len));
+
+ ok = vfs_valid_pwrite_range((off_t)len, 0);
+ if (!ok) {
+ DEBUG(0,("vfs_allocate_file_space: %s negative/invalid len "
+ "requested.\n", fsp_str_dbg(fsp)));
+ errno = EINVAL;
+ return -1;
+ }
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+
+ if (len == (uint64_t)fsp->fsp_name->st.st_ex_size)
+ return 0;
+
+ if (len < (uint64_t)fsp->fsp_name->st.st_ex_size) {
+ /* Shrink - use ftruncate. */
+
+ DEBUG(10,("vfs_allocate_file_space: file %s, shrink. Current "
+ "size %.0f\n", fsp_str_dbg(fsp),
+ (double)fsp->fsp_name->st.st_ex_size));
+
+ contend_level2_oplocks_begin(fsp, LEVEL2_CONTEND_ALLOC_SHRINK);
+
+ ret = SMB_VFS_FTRUNCATE(fsp, (off_t)len);
+
+ contend_level2_oplocks_end(fsp, LEVEL2_CONTEND_ALLOC_SHRINK);
+
+ return ret;
+ }
+
+ /* Grow - we need to test if we have enough space. */
+
+ contend_level2_oplocks_begin(fsp, LEVEL2_CONTEND_ALLOC_GROW);
+
+ if (lp_strict_allocate(SNUM(fsp->conn))) {
+ /* See if we have a syscall that will allocate beyond
+ end-of-file without changing EOF. */
+ ret = SMB_VFS_FALLOCATE(fsp, VFS_FALLOCATE_FL_KEEP_SIZE,
+ 0, len);
+ } else {
+ ret = 0;
+ }
+
+ contend_level2_oplocks_end(fsp, LEVEL2_CONTEND_ALLOC_GROW);
+
+ if (ret == 0) {
+ /* We changed the allocation size on disk, but not
+ EOF - exactly as required. We're done ! */
+ return 0;
+ }
+
+ if (ret == -1 && errno == ENOSPC) {
+ return -1;
+ }
+
+ len -= fsp->fsp_name->st.st_ex_size;
+ len /= 1024; /* Len is now number of 1k blocks needed. */
+ space_avail =
+ get_dfree_info(conn, fsp->fsp_name, &bsize, &dfree, &dsize);
+ if (space_avail == (uint64_t)-1) {
+ return -1;
+ }
+
+ DEBUG(10,("vfs_allocate_file_space: file %s, grow. Current size %.0f, "
+ "needed blocks = %.0f, space avail = %.0f\n",
+ fsp_str_dbg(fsp), (double)fsp->fsp_name->st.st_ex_size, (double)len,
+ (double)space_avail));
+
+ if (len > space_avail) {
+ errno = ENOSPC;
+ return -1;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ A vfs set_filelen call.
+ set the length of a file from a filedescriptor.
+ Returns 0 on success, -1 on failure.
+****************************************************************************/
+
+int vfs_set_filelen(files_struct *fsp, off_t len)
+{
+ int ret;
+ bool ok;
+
+ ok = vfs_valid_pwrite_range(len, 0);
+ if (!ok) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ contend_level2_oplocks_begin(fsp, LEVEL2_CONTEND_SET_FILE_LEN);
+
+ DEBUG(10,("vfs_set_filelen: ftruncate %s to len %.0f\n",
+ fsp_str_dbg(fsp), (double)len));
+ if ((ret = SMB_VFS_FTRUNCATE(fsp, len)) != -1) {
+ notify_fname(fsp->conn, NOTIFY_ACTION_MODIFIED,
+ FILE_NOTIFY_CHANGE_SIZE
+ | FILE_NOTIFY_CHANGE_ATTRIBUTES,
+ fsp->fsp_name->base_name);
+ }
+
+ contend_level2_oplocks_end(fsp, LEVEL2_CONTEND_SET_FILE_LEN);
+
+ return ret;
+}
+
+/****************************************************************************
+ A slow version of fallocate. Fallback code if SMB_VFS_FALLOCATE
+ fails. Needs to be outside of the default version of SMB_VFS_FALLOCATE
+ as this is also called from the default SMB_VFS_FTRUNCATE code.
+ Always extends the file size.
+ Returns 0 on success, -1 on failure.
+****************************************************************************/
+
+#define SPARSE_BUF_WRITE_SIZE (32*1024)
+
+int vfs_slow_fallocate(files_struct *fsp, off_t offset, off_t len)
+{
+ ssize_t pwrite_ret;
+ size_t total = 0;
+ bool ok;
+
+ ok = vfs_valid_pwrite_range(offset, len);
+ if (!ok) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (!sparse_buf) {
+ sparse_buf = SMB_CALLOC_ARRAY(char, SPARSE_BUF_WRITE_SIZE);
+ if (!sparse_buf) {
+ errno = ENOMEM;
+ return -1;
+ }
+ }
+
+ while (total < len) {
+ size_t curr_write_size = MIN(SPARSE_BUF_WRITE_SIZE, (len - total));
+
+ pwrite_ret = SMB_VFS_PWRITE(fsp, sparse_buf, curr_write_size, offset + total);
+ if (pwrite_ret == -1) {
+ int saved_errno = errno;
+ DEBUG(10,("vfs_slow_fallocate: SMB_VFS_PWRITE for file "
+ "%s failed with error %s\n",
+ fsp_str_dbg(fsp), strerror(saved_errno)));
+ errno = saved_errno;
+ return -1;
+ }
+ total += pwrite_ret;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ A vfs fill sparse call.
+ Writes zeros from the end of file to len, if len is greater than EOF.
+ Used only by strict_sync.
+ Returns 0 on success, -1 on failure.
+****************************************************************************/
+
+int vfs_fill_sparse(files_struct *fsp, off_t len)
+{
+ int ret;
+ NTSTATUS status;
+ off_t offset;
+ size_t num_to_write;
+ bool ok;
+
+ ok = vfs_valid_pwrite_range(len, 0);
+ if (!ok) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+
+ if (len <= fsp->fsp_name->st.st_ex_size) {
+ return 0;
+ }
+
+#ifdef S_ISFIFO
+ if (S_ISFIFO(fsp->fsp_name->st.st_ex_mode)) {
+ return 0;
+ }
+#endif
+
+ DEBUG(10,("vfs_fill_sparse: write zeros in file %s from len %.0f to "
+ "len %.0f (%.0f bytes)\n", fsp_str_dbg(fsp),
+ (double)fsp->fsp_name->st.st_ex_size, (double)len,
+ (double)(len - fsp->fsp_name->st.st_ex_size)));
+
+ contend_level2_oplocks_begin(fsp, LEVEL2_CONTEND_FILL_SPARSE);
+
+ offset = fsp->fsp_name->st.st_ex_size;
+ num_to_write = len - fsp->fsp_name->st.st_ex_size;
+
+ /* Only do this on non-stream file handles. */
+ if (!fsp_is_alternate_stream(fsp)) {
+ /* for allocation try fallocate first. This can fail on some
+ * platforms e.g. when the filesystem doesn't support it and no
+ * emulation is being done by the libc (like on AIX with JFS1). In that
+ * case we do our own emulation. fallocate implementations can
+ * return ENOTSUP or EINVAL in cases like that. */
+ ret = SMB_VFS_FALLOCATE(fsp, 0, offset, num_to_write);
+ if (ret == -1 && errno == ENOSPC) {
+ goto out;
+ }
+ if (ret == 0) {
+ goto out;
+ }
+ DEBUG(10,("vfs_fill_sparse: SMB_VFS_FALLOCATE failed with "
+ "error %d. Falling back to slow manual allocation\n", ret));
+ }
+
+ ret = vfs_slow_fallocate(fsp, offset, num_to_write);
+
+ out:
+
+ contend_level2_oplocks_end(fsp, LEVEL2_CONTEND_FILL_SPARSE);
+ return ret;
+}
+
+/*******************************************************************************
+ Set a fd into blocking/nonblocking mode through VFS
+*******************************************************************************/
+
+int vfs_set_blocking(files_struct *fsp, bool set)
+{
+ int val;
+#ifdef O_NONBLOCK
+#define FLAG_TO_SET O_NONBLOCK
+#else
+#ifdef SYSV
+#define FLAG_TO_SET O_NDELAY
+#else /* BSD */
+#define FLAG_TO_SET FNDELAY
+#endif
+#endif
+
+ if (fsp->fsp_flags.is_pathref) {
+ return 0;
+ }
+
+ val = SMB_VFS_FCNTL(fsp, F_GETFL, 0);
+ if (val == -1) {
+ return -1;
+ }
+
+ if (set) {
+ val &= ~FLAG_TO_SET;
+ } else {
+ val |= FLAG_TO_SET;
+ }
+
+ return SMB_VFS_FCNTL(fsp, F_SETFL, val);
+#undef FLAG_TO_SET
+}
+
+/****************************************************************************
+ Transfer some data (n bytes) between two file_struct's.
+****************************************************************************/
+
+static ssize_t vfs_pread_fn(void *file, void *buf, size_t len, off_t offset)
+{
+ struct files_struct *fsp = (struct files_struct *)file;
+
+ return SMB_VFS_PREAD(fsp, buf, len, offset);
+}
+
+static ssize_t vfs_pwrite_fn(void *file, const void *buf, size_t len, off_t offset)
+{
+ struct files_struct *fsp = (struct files_struct *)file;
+
+ return SMB_VFS_PWRITE(fsp, buf, len, offset);
+}
+
+off_t vfs_transfer_file(files_struct *in, files_struct *out, off_t n)
+{
+ return transfer_file_internal((void *)in, (void *)out, n,
+ vfs_pread_fn, vfs_pwrite_fn);
+}
+
+/*******************************************************************
+ A vfs_readdir wrapper which just returns the file name.
+********************************************************************/
+
+const char *vfs_readdirname(connection_struct *conn,
+ struct files_struct *dirfsp,
+ DIR *d,
+ char **talloced)
+{
+ struct dirent *ptr= NULL;
+ const char *dname;
+ char *translated;
+ NTSTATUS status;
+
+ if (d == NULL) {
+ return(NULL);
+ }
+
+ ptr = SMB_VFS_READDIR(conn, dirfsp, d);
+ if (!ptr)
+ return(NULL);
+
+ dname = ptr->d_name;
+
+ status = SMB_VFS_TRANSLATE_NAME(conn, dname, vfs_translate_to_windows,
+ talloc_tos(), &translated);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) {
+ *talloced = NULL;
+ return dname;
+ }
+ *talloced = translated;
+ if (!NT_STATUS_IS_OK(status)) {
+ return NULL;
+ }
+ return translated;
+}
+
+/*******************************************************************
+ A wrapper for vfs_chdir().
+********************************************************************/
+
+int vfs_ChDir(connection_struct *conn, const struct smb_filename *smb_fname)
+{
+ int ret;
+ struct smb_filename *cwd = NULL;
+
+ if (!LastDir) {
+ LastDir = SMB_STRDUP("");
+ }
+
+ if (ISDOT(smb_fname->base_name)) {
+ /*
+ * passing a '.' is a noop,
+ * and we only expect this after
+ * everything is initialized.
+ *
+ * So the first vfs_ChDir() on a given
+ * connection_struct must not be '.'.
+ *
+ * Note: conn_new() sets
+ * conn->cwd_fsp->fh->fd = -1
+ * and vfs_ChDir() leaves with
+ * conn->cwd_fsp->fh->fd = AT_FDCWD
+ * on success!
+ */
+ if (fsp_get_pathref_fd(conn->cwd_fsp) != AT_FDCWD) {
+ /*
+ * This should never happen and
+ * we might change this to
+ * SMB_ASSERT() in future.
+ */
+ DBG_ERR("Called with '.' as first operation!\n");
+ log_stack_trace();
+ errno = EINVAL;
+ return -1;
+ }
+ return 0;
+ }
+
+ if (smb_fname->base_name[0] == '/' &&
+ strcsequal(LastDir,smb_fname->base_name))
+ {
+ /*
+ * conn->cwd_fsp->fsp_name and the kernel
+ * are already correct, but conn->cwd_fsp->fh->fd
+ * might still be -1 as initialized in conn_new().
+ *
+ * This can happen when a client made a 2nd
+ * tree connect to a share with the same underlying
+ * path (may or may not the same share).
+ */
+ fsp_set_fd(conn->cwd_fsp, AT_FDCWD);
+ return 0;
+ }
+
+ DEBUG(4,("vfs_ChDir to %s\n", smb_fname->base_name));
+
+ ret = SMB_VFS_CHDIR(conn, smb_fname);
+ if (ret != 0) {
+ return -1;
+ }
+
+ /*
+ * Always replace conn->cwd_fsp. We
+ * don't know if it's been modified by
+ * VFS modules in the stack.
+ */
+ fsp_set_fd(conn->cwd_fsp, AT_FDCWD);
+
+ /* conn cache. */
+ cwd = vfs_GetWd(conn, conn);
+ if (cwd == NULL) {
+ /*
+ * vfs_GetWd() failed.
+ * We must be able to read cwd.
+ * Return to original directory
+ * and return -1.
+ */
+ int saved_errno = errno;
+
+ if (conn->cwd_fsp->fsp_name == NULL) {
+ /*
+ * Failed on the very first chdir()+getwd()
+ * for this connection. We can't
+ * continue.
+ */
+ smb_panic("conn->cwd getwd failed\n");
+ /* NOTREACHED */
+ return -1;
+ }
+
+ /* Return to the previous $cwd. */
+ ret = SMB_VFS_CHDIR(conn, conn->cwd_fsp->fsp_name);
+ if (ret != 0) {
+ smb_panic("conn->cwd getwd failed\n");
+ /* NOTREACHED */
+ return -1;
+ }
+ errno = saved_errno;
+ /* And fail the chdir(). */
+ return -1;
+ }
+
+ /* vfs_GetWd() succeeded. */
+ /* Replace global cache. */
+ SAFE_FREE(LastDir);
+ LastDir = SMB_STRDUP(smb_fname->base_name);
+
+ /*
+ * (Indirect) Callers of vfs_ChDir() may still hold references to the
+ * old conn->cwd_fsp->fsp_name. Move it to talloc_tos(), that way
+ * callers can use it for the lifetime of the SMB request.
+ */
+ talloc_move(talloc_tos(), &conn->cwd_fsp->fsp_name);
+
+ conn->cwd_fsp->fsp_name = talloc_move(conn->cwd_fsp, &cwd);
+
+ DBG_INFO("vfs_ChDir got %s\n", fsp_str_dbg(conn->cwd_fsp));
+
+ return ret;
+}
+
+/*******************************************************************
+ Return the absolute current directory path - given a UNIX pathname.
+ Note that this path is returned in DOS format, not UNIX
+ format. Note this can be called with conn == NULL.
+********************************************************************/
+
+struct smb_filename *vfs_GetWd(TALLOC_CTX *ctx, connection_struct *conn)
+{
+ struct smb_filename *current_dir_fname = NULL;
+ struct file_id key;
+ struct smb_filename *smb_fname_dot = NULL;
+ struct smb_filename *smb_fname_full = NULL;
+ struct smb_filename *result = NULL;
+
+ if (!lp_getwd_cache()) {
+ goto nocache;
+ }
+
+ smb_fname_dot = synthetic_smb_fname(ctx,
+ ".",
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname_dot == NULL) {
+ errno = ENOMEM;
+ goto out;
+ }
+
+ if (SMB_VFS_STAT(conn, smb_fname_dot) == -1) {
+ /*
+ * Known to fail for root: the directory may be NFS-mounted
+ * and exported with root_squash (so has no root access).
+ */
+ DEBUG(1,("vfs_GetWd: couldn't stat \".\" error %s "
+ "(NFS problem ?)\n", strerror(errno) ));
+ goto nocache;
+ }
+
+ key = vfs_file_id_from_sbuf(conn, &smb_fname_dot->st);
+
+ smb_fname_full = (struct smb_filename *)memcache_lookup_talloc(
+ smbd_memcache(),
+ GETWD_CACHE,
+ data_blob_const(&key, sizeof(key)));
+
+ if (smb_fname_full == NULL) {
+ goto nocache;
+ }
+
+ if ((SMB_VFS_STAT(conn, smb_fname_full) == 0) &&
+ (smb_fname_dot->st.st_ex_dev == smb_fname_full->st.st_ex_dev) &&
+ (smb_fname_dot->st.st_ex_ino == smb_fname_full->st.st_ex_ino) &&
+ (S_ISDIR(smb_fname_dot->st.st_ex_mode))) {
+ /*
+ * Ok, we're done
+ * Note: smb_fname_full is owned by smbd_memcache()
+ * so we must make a copy to return.
+ */
+ result = cp_smb_filename(ctx, smb_fname_full);
+ if (result == NULL) {
+ errno = ENOMEM;
+ }
+ goto out;
+ }
+
+ nocache:
+
+ /*
+ * We don't have the information to hand so rely on traditional
+ * methods. The very slow getcwd, which spawns a process on some
+ * systems, or the not quite so bad getwd.
+ */
+
+ current_dir_fname = SMB_VFS_GETWD(conn, ctx);
+ if (current_dir_fname == NULL) {
+ DEBUG(0, ("vfs_GetWd: SMB_VFS_GETWD call failed: %s\n",
+ strerror(errno)));
+ goto out;
+ }
+
+ if (lp_getwd_cache() && VALID_STAT(smb_fname_dot->st)) {
+ key = vfs_file_id_from_sbuf(conn, &smb_fname_dot->st);
+
+ /*
+ * smbd_memcache() will own current_dir_fname after the
+ * memcache_add_talloc call, so we must make
+ * a copy on ctx to return.
+ */
+ result = cp_smb_filename(ctx, current_dir_fname);
+ if (result == NULL) {
+ errno = ENOMEM;
+ }
+
+ /*
+ * Ensure the memory going into the cache
+ * doesn't have a destructor so it can be
+ * cleanly freed.
+ */
+ talloc_set_destructor(current_dir_fname, NULL);
+
+ memcache_add_talloc(smbd_memcache(),
+ GETWD_CACHE,
+ data_blob_const(&key, sizeof(key)),
+ &current_dir_fname);
+ /* current_dir_fname is now == NULL here. */
+ } else {
+ /* current_dir_fname is already allocated on ctx. */
+ result = current_dir_fname;
+ }
+
+ out:
+ TALLOC_FREE(smb_fname_dot);
+ /*
+ * Don't free current_dir_fname here. It's either been moved
+ * to the memcache or is being returned in result.
+ */
+ return result;
+}
+
+/*
+ * Ensure LSTAT is called for POSIX paths.
+ */
+int vfs_stat(struct connection_struct *conn,
+ struct smb_filename *smb_fname)
+{
+ if (smb_fname->flags & SMB_FILENAME_POSIX_PATH) {
+ return SMB_VFS_LSTAT(conn, smb_fname);
+ }
+ return SMB_VFS_STAT(conn, smb_fname);
+}
+
+/**
+ * XXX: This is temporary and there should be no callers of this once
+ * smb_filename is plumbed through all path based operations.
+ *
+ * Called when we know stream name parsing has already been done.
+ */
+int vfs_stat_smb_basename(struct connection_struct *conn,
+ const struct smb_filename *smb_fname_in,
+ SMB_STRUCT_STAT *psbuf)
+{
+ struct smb_filename smb_fname = {
+ .base_name = discard_const_p(char, smb_fname_in->base_name),
+ .flags = smb_fname_in->flags,
+ .twrp = smb_fname_in->twrp,
+ };
+ int ret;
+
+ ret = vfs_stat(conn, &smb_fname);
+ if (ret != -1) {
+ *psbuf = smb_fname.st;
+ }
+ return ret;
+}
+
+/**
+ * Ensure LSTAT is called for POSIX paths.
+ */
+
+NTSTATUS vfs_stat_fsp(files_struct *fsp)
+{
+ int ret;
+ struct stat_ex saved_stat = fsp->fsp_name->st;
+
+ if (fsp->fake_file_handle != NULL) {
+ return NT_STATUS_OK;
+ }
+
+ if (fsp_get_pathref_fd(fsp) == -1) {
+ if (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) {
+ ret = SMB_VFS_LSTAT(fsp->conn, fsp->fsp_name);
+ } else {
+ ret = SMB_VFS_STAT(fsp->conn, fsp->fsp_name);
+ }
+ } else {
+ ret = SMB_VFS_FSTAT(fsp, &fsp->fsp_name->st);
+ }
+ if (ret == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+ update_stat_ex_from_saved_stat(&fsp->fsp_name->st, &saved_stat);
+ fsp->fsp_flags.is_directory = S_ISDIR(fsp->fsp_name->st.st_ex_mode);
+ return NT_STATUS_OK;
+}
+
+void init_smb_file_time(struct smb_file_time *ft)
+{
+ *ft = (struct smb_file_time) {
+ .atime = make_omit_timespec(),
+ .ctime = make_omit_timespec(),
+ .mtime = make_omit_timespec(),
+ .create_time = make_omit_timespec()
+ };
+}
+
+/**
+ * Initialize num_streams and streams, then call VFS op streaminfo
+ */
+
+NTSTATUS vfs_fstreaminfo(struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *num_streams,
+ struct stream_struct **streams)
+{
+ *num_streams = 0;
+ *streams = NULL;
+
+ if (fsp == NULL) {
+ /*
+ * Callers may pass fsp == NULL when passing smb_fname->fsp of a
+ * symlink. This is ok, handle it here, by just return no
+ * streams on a symlink.
+ */
+ return NT_STATUS_OK;
+ }
+
+ if (fsp_get_pathref_fd(fsp) == -1) {
+ /*
+ * No streams on non-real files/directories.
+ */
+ return NT_STATUS_OK;
+ }
+
+ return SMB_VFS_FSTREAMINFO(fsp,
+ mem_ctx,
+ num_streams,
+ streams);
+}
+
+int vfs_fake_fd(void)
+{
+ int pipe_fds[2];
+ int ret;
+
+ /*
+ * Return a valid fd, but ensure any attempt to use
+ * it returns an error (EPIPE).
+ */
+ ret = pipe(pipe_fds);
+ if (ret != 0) {
+ return -1;
+ }
+
+ close(pipe_fds[1]);
+ return pipe_fds[0];
+}
+
+/*
+ * This is just a helper to make
+ * users of vfs_fake_fd() more symmetric
+ */
+int vfs_fake_fd_close(int fd)
+{
+ return close(fd);
+}
+
+/*
+ generate a file_id from a stat structure
+ */
+struct file_id vfs_file_id_from_sbuf(connection_struct *conn, const SMB_STRUCT_STAT *sbuf)
+{
+ return SMB_VFS_FILE_ID_CREATE(conn, sbuf);
+}
+
+NTSTATUS vfs_at_fspcwd(TALLOC_CTX *mem_ctx,
+ struct connection_struct *conn,
+ struct files_struct **_fsp)
+{
+ struct files_struct *fsp = NULL;
+
+ fsp = talloc_zero(mem_ctx, struct files_struct);
+ if (fsp == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ fsp->fsp_name = synthetic_smb_fname(fsp, ".", NULL, NULL, 0, 0);
+ if (fsp->fsp_name == NULL) {
+ TALLOC_FREE(fsp);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ fsp->fh = fd_handle_create(fsp);
+ if (fsp->fh == NULL) {
+ TALLOC_FREE(fsp);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ fsp_set_fd(fsp, AT_FDCWD);
+ fsp->fnum = FNUM_FIELD_INVALID;
+ fsp->conn = conn;
+
+ *_fsp = fsp;
+ return NT_STATUS_OK;
+}
+
+static struct smb_vfs_deny_state *smb_vfs_deny_global;
+
+void smb_vfs_assert_allowed(void)
+{
+ if (unlikely(smb_vfs_deny_global != NULL)) {
+ DBG_ERR("Called with VFS denied by %s\n",
+ smb_vfs_deny_global->location);
+ smb_panic("Called with VFS denied!");
+ }
+}
+
+void _smb_vfs_deny_push(struct smb_vfs_deny_state *state, const char *location)
+{
+ SMB_ASSERT(smb_vfs_deny_global != state);
+
+ *state = (struct smb_vfs_deny_state) {
+ .parent = smb_vfs_deny_global,
+ .location = location,
+ };
+
+ smb_vfs_deny_global = state;
+}
+
+void _smb_vfs_deny_pop(struct smb_vfs_deny_state *state, const char *location)
+{
+ SMB_ASSERT(smb_vfs_deny_global == state);
+
+ smb_vfs_deny_global = state->parent;
+
+ *state = (struct smb_vfs_deny_state) { .parent = NULL, };
+}
+
+#define VFS_FIND(__fn__) do { \
+ if (unlikely(smb_vfs_deny_global != NULL)) { \
+ DBG_ERR("Called with VFS denied by %s\n", \
+ smb_vfs_deny_global->location); \
+ smb_panic("Called with VFS denied!"); \
+ } \
+ while (handle->fns->__fn__##_fn==NULL) { \
+ handle = handle->next; \
+ } \
+} while(0)
+
+int smb_vfs_call_connect(struct vfs_handle_struct *handle,
+ const char *service, const char *user)
+{
+ VFS_FIND(connect);
+ return handle->fns->connect_fn(handle, service, user);
+}
+
+void smb_vfs_call_disconnect(struct vfs_handle_struct *handle)
+{
+ VFS_FIND(disconnect);
+ handle->fns->disconnect_fn(handle);
+}
+
+uint64_t smb_vfs_call_disk_free(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uint64_t *bsize,
+ uint64_t *dfree,
+ uint64_t *dsize)
+{
+ VFS_FIND(disk_free);
+ return handle->fns->disk_free_fn(handle, smb_fname,
+ bsize, dfree, dsize);
+}
+
+int smb_vfs_call_get_quota(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ enum SMB_QUOTA_TYPE qtype,
+ unid_t id,
+ SMB_DISK_QUOTA *qt)
+{
+ VFS_FIND(get_quota);
+ return handle->fns->get_quota_fn(handle, smb_fname, qtype, id, qt);
+}
+
+int smb_vfs_call_set_quota(struct vfs_handle_struct *handle,
+ enum SMB_QUOTA_TYPE qtype, unid_t id,
+ SMB_DISK_QUOTA *qt)
+{
+ VFS_FIND(set_quota);
+ return handle->fns->set_quota_fn(handle, qtype, id, qt);
+}
+
+int smb_vfs_call_get_shadow_copy_data(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ struct shadow_copy_data *shadow_copy_data,
+ bool labels)
+{
+ VFS_FIND(get_shadow_copy_data);
+ return handle->fns->get_shadow_copy_data_fn(handle, fsp,
+ shadow_copy_data,
+ labels);
+}
+int smb_vfs_call_statvfs(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ struct vfs_statvfs_struct *statbuf)
+{
+ VFS_FIND(statvfs);
+ return handle->fns->statvfs_fn(handle, smb_fname, statbuf);
+}
+
+uint32_t smb_vfs_call_fs_capabilities(struct vfs_handle_struct *handle,
+ enum timestamp_set_resolution *p_ts_res)
+{
+ VFS_FIND(fs_capabilities);
+ return handle->fns->fs_capabilities_fn(handle, p_ts_res);
+}
+
+NTSTATUS smb_vfs_call_get_dfs_referrals(struct vfs_handle_struct *handle,
+ struct dfs_GetDFSReferral *r)
+{
+ VFS_FIND(get_dfs_referrals);
+ return handle->fns->get_dfs_referrals_fn(handle, r);
+}
+
+NTSTATUS smb_vfs_call_create_dfs_pathat(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ const struct referral *reflist,
+ size_t referral_count)
+{
+ VFS_FIND(create_dfs_pathat);
+ return handle->fns->create_dfs_pathat_fn(handle,
+ dirfsp,
+ smb_fname,
+ reflist,
+ referral_count);
+}
+
+NTSTATUS smb_vfs_call_read_dfs_pathat(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ struct referral **ppreflist,
+ size_t *preferral_count)
+{
+ VFS_FIND(read_dfs_pathat);
+ return handle->fns->read_dfs_pathat_fn(handle,
+ mem_ctx,
+ dirfsp,
+ smb_fname,
+ ppreflist,
+ preferral_count);
+}
+
+DIR *smb_vfs_call_fdopendir(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const char *mask,
+ uint32_t attributes)
+{
+ VFS_FIND(fdopendir);
+ return handle->fns->fdopendir_fn(handle, fsp, mask, attributes);
+}
+
+struct dirent *smb_vfs_call_readdir(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ DIR *dirp)
+{
+ VFS_FIND(readdir);
+ return handle->fns->readdir_fn(handle, dirfsp, dirp);
+}
+
+void smb_vfs_call_rewind_dir(struct vfs_handle_struct *handle,
+ DIR *dirp)
+{
+ VFS_FIND(rewind_dir);
+ handle->fns->rewind_dir_fn(handle, dirp);
+}
+
+int smb_vfs_call_mkdirat(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode)
+{
+ VFS_FIND(mkdirat);
+ return handle->fns->mkdirat_fn(handle,
+ dirfsp,
+ smb_fname,
+ mode);
+}
+
+int smb_vfs_call_closedir(struct vfs_handle_struct *handle,
+ DIR *dir)
+{
+ VFS_FIND(closedir);
+ return handle->fns->closedir_fn(handle, dir);
+}
+
+int smb_vfs_call_openat(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ struct files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ VFS_FIND(openat);
+ return handle->fns->openat_fn(handle,
+ dirfsp,
+ smb_fname,
+ fsp,
+ how);
+}
+
+NTSTATUS smb_vfs_call_create_file(struct vfs_handle_struct *handle,
+ struct smb_request *req,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ uint32_t access_mask,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ uint32_t file_attributes,
+ uint32_t oplock_request,
+ const struct smb2_lease *lease,
+ uint64_t allocation_size,
+ uint32_t private_flags,
+ struct security_descriptor *sd,
+ struct ea_list *ea_list,
+ files_struct **result,
+ int *pinfo,
+ const struct smb2_create_blobs *in_context_blobs,
+ struct smb2_create_blobs *out_context_blobs)
+{
+ VFS_FIND(create_file);
+ return handle->fns->create_file_fn(
+ handle, req, dirfsp, smb_fname,
+ access_mask, share_access, create_disposition, create_options,
+ file_attributes, oplock_request, lease, allocation_size,
+ private_flags, sd, ea_list,
+ result, pinfo, in_context_blobs, out_context_blobs);
+}
+
+int smb_vfs_call_close(struct vfs_handle_struct *handle,
+ struct files_struct *fsp)
+{
+ VFS_FIND(close);
+ return handle->fns->close_fn(handle, fsp);
+}
+
+ssize_t smb_vfs_call_pread(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, void *data, size_t n,
+ off_t offset)
+{
+ VFS_FIND(pread);
+ return handle->fns->pread_fn(handle, fsp, data, n, offset);
+}
+
+struct smb_vfs_call_pread_state {
+ ssize_t (*recv_fn)(struct tevent_req *req, struct vfs_aio_state *vfs_aio_state);
+ ssize_t retval;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void smb_vfs_call_pread_done(struct tevent_req *subreq);
+
+struct tevent_req *smb_vfs_call_pread_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ void *data,
+ size_t n, off_t offset)
+{
+ struct tevent_req *req, *subreq;
+ struct smb_vfs_call_pread_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb_vfs_call_pread_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ VFS_FIND(pread_send);
+ state->recv_fn = handle->fns->pread_recv_fn;
+
+ subreq = handle->fns->pread_send_fn(handle, state, ev, fsp, data, n,
+ offset);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smb_vfs_call_pread_done, req);
+ return req;
+}
+
+static void smb_vfs_call_pread_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smb_vfs_call_pread_state *state = tevent_req_data(
+ req, struct smb_vfs_call_pread_state);
+
+ state->retval = state->recv_fn(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+ if (state->retval == -1) {
+ tevent_req_error(req, state->vfs_aio_state.error);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+ssize_t SMB_VFS_PREAD_RECV(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct smb_vfs_call_pread_state *state = tevent_req_data(
+ req, struct smb_vfs_call_pread_state);
+ ssize_t retval;
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ tevent_req_received(req);
+ return -1;
+ }
+ *vfs_aio_state = state->vfs_aio_state;
+ retval = state->retval;
+ tevent_req_received(req);
+ return retval;
+}
+
+ssize_t smb_vfs_call_pwrite(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, const void *data,
+ size_t n, off_t offset)
+{
+ VFS_FIND(pwrite);
+ return handle->fns->pwrite_fn(handle, fsp, data, n, offset);
+}
+
+struct smb_vfs_call_pwrite_state {
+ ssize_t (*recv_fn)(struct tevent_req *req, struct vfs_aio_state *vfs_aio_state);
+ ssize_t retval;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void smb_vfs_call_pwrite_done(struct tevent_req *subreq);
+
+struct tevent_req *smb_vfs_call_pwrite_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ const void *data,
+ size_t n, off_t offset)
+{
+ struct tevent_req *req, *subreq;
+ struct smb_vfs_call_pwrite_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb_vfs_call_pwrite_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ VFS_FIND(pwrite_send);
+ state->recv_fn = handle->fns->pwrite_recv_fn;
+
+ subreq = handle->fns->pwrite_send_fn(handle, state, ev, fsp, data, n,
+ offset);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smb_vfs_call_pwrite_done, req);
+ return req;
+}
+
+static void smb_vfs_call_pwrite_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smb_vfs_call_pwrite_state *state = tevent_req_data(
+ req, struct smb_vfs_call_pwrite_state);
+
+ state->retval = state->recv_fn(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+ if (state->retval == -1) {
+ tevent_req_error(req, state->vfs_aio_state.error);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+ssize_t SMB_VFS_PWRITE_RECV(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct smb_vfs_call_pwrite_state *state = tevent_req_data(
+ req, struct smb_vfs_call_pwrite_state);
+ ssize_t retval;
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ tevent_req_received(req);
+ return -1;
+ }
+ *vfs_aio_state = state->vfs_aio_state;
+ retval = state->retval;
+ tevent_req_received(req);
+ return retval;
+}
+
+off_t smb_vfs_call_lseek(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, off_t offset,
+ int whence)
+{
+ VFS_FIND(lseek);
+ return handle->fns->lseek_fn(handle, fsp, offset, whence);
+}
+
+ssize_t smb_vfs_call_sendfile(struct vfs_handle_struct *handle, int tofd,
+ files_struct *fromfsp, const DATA_BLOB *header,
+ off_t offset, size_t count)
+{
+ VFS_FIND(sendfile);
+ return handle->fns->sendfile_fn(handle, tofd, fromfsp, header, offset,
+ count);
+}
+
+ssize_t smb_vfs_call_recvfile(struct vfs_handle_struct *handle, int fromfd,
+ files_struct *tofsp, off_t offset,
+ size_t count)
+{
+ VFS_FIND(recvfile);
+ return handle->fns->recvfile_fn(handle, fromfd, tofsp, offset, count);
+}
+
+int smb_vfs_call_renameat(struct vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *smb_fname_src,
+ files_struct *dstfsp,
+ const struct smb_filename *smb_fname_dst)
+{
+ VFS_FIND(renameat);
+ return handle->fns->renameat_fn(handle,
+ srcfsp,
+ smb_fname_src,
+ dstfsp,
+ smb_fname_dst);
+}
+
+struct smb_vfs_call_fsync_state {
+ int (*recv_fn)(struct tevent_req *req, struct vfs_aio_state *vfs_aio_state);
+ int retval;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void smb_vfs_call_fsync_done(struct tevent_req *subreq);
+
+struct tevent_req *smb_vfs_call_fsync_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp)
+{
+ struct tevent_req *req, *subreq;
+ struct smb_vfs_call_fsync_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb_vfs_call_fsync_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ VFS_FIND(fsync_send);
+ state->recv_fn = handle->fns->fsync_recv_fn;
+
+ subreq = handle->fns->fsync_send_fn(handle, state, ev, fsp);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smb_vfs_call_fsync_done, req);
+ return req;
+}
+
+static void smb_vfs_call_fsync_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smb_vfs_call_fsync_state *state = tevent_req_data(
+ req, struct smb_vfs_call_fsync_state);
+
+ state->retval = state->recv_fn(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+ if (state->retval == -1) {
+ tevent_req_error(req, state->vfs_aio_state.error);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+int SMB_VFS_FSYNC_RECV(struct tevent_req *req, struct vfs_aio_state *vfs_aio_state)
+{
+ struct smb_vfs_call_fsync_state *state = tevent_req_data(
+ req, struct smb_vfs_call_fsync_state);
+ ssize_t retval;
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ tevent_req_received(req);
+ return -1;
+ }
+ *vfs_aio_state = state->vfs_aio_state;
+ retval = state->retval;
+ tevent_req_received(req);
+ return retval;
+}
+
+/*
+ * Synchronous version of fsync, built from backend
+ * async VFS primitives. Uses a temporary sub-event
+ * context (NOT NESTED).
+ */
+
+int smb_vfs_fsync_sync(files_struct *fsp)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_req *req = NULL;
+ struct vfs_aio_state aio_state = { 0 };
+ int ret = -1;
+ bool ok;
+ struct tevent_context *ev = samba_tevent_context_init(frame);
+
+ if (ev == NULL) {
+ goto out;
+ }
+
+ req = SMB_VFS_FSYNC_SEND(talloc_tos(), ev, fsp);
+ if (req == NULL) {
+ goto out;
+ }
+
+ ok = tevent_req_poll(req, ev);
+ if (!ok) {
+ goto out;
+ }
+
+ ret = SMB_VFS_FSYNC_RECV(req, &aio_state);
+
+ out:
+
+ TALLOC_FREE(frame);
+ if (aio_state.error != 0) {
+ errno = aio_state.error;
+ }
+ return ret;
+}
+
+int smb_vfs_call_stat(struct vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ VFS_FIND(stat);
+ return handle->fns->stat_fn(handle, smb_fname);
+}
+
+int smb_vfs_call_fstat(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, SMB_STRUCT_STAT *sbuf)
+{
+ VFS_FIND(fstat);
+ return handle->fns->fstat_fn(handle, fsp, sbuf);
+}
+
+int smb_vfs_call_lstat(struct vfs_handle_struct *handle,
+ struct smb_filename *smb_filename)
+{
+ VFS_FIND(lstat);
+ return handle->fns->lstat_fn(handle, smb_filename);
+}
+
+int smb_vfs_call_fstatat(
+ struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ SMB_STRUCT_STAT *sbuf,
+ int flags)
+{
+ VFS_FIND(fstatat);
+ return handle->fns->fstatat_fn(handle, dirfsp, smb_fname, sbuf, flags);
+}
+
+uint64_t smb_vfs_call_get_alloc_size(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const SMB_STRUCT_STAT *sbuf)
+{
+ VFS_FIND(get_alloc_size);
+ return handle->fns->get_alloc_size_fn(handle, fsp, sbuf);
+}
+
+int smb_vfs_call_unlinkat(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ VFS_FIND(unlinkat);
+ return handle->fns->unlinkat_fn(handle,
+ dirfsp,
+ smb_fname,
+ flags);
+}
+
+int smb_vfs_call_fchmod(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, mode_t mode)
+{
+ VFS_FIND(fchmod);
+ return handle->fns->fchmod_fn(handle, fsp, mode);
+}
+
+int smb_vfs_call_fchown(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, uid_t uid, gid_t gid)
+{
+ VFS_FIND(fchown);
+ return handle->fns->fchown_fn(handle, fsp, uid, gid);
+}
+
+int smb_vfs_call_lchown(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uid_t uid,
+ gid_t gid)
+{
+ VFS_FIND(lchown);
+ return handle->fns->lchown_fn(handle, smb_fname, uid, gid);
+}
+
+int smb_vfs_call_chdir(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname)
+{
+ VFS_FIND(chdir);
+ return handle->fns->chdir_fn(handle, smb_fname);
+}
+
+struct smb_filename *smb_vfs_call_getwd(struct vfs_handle_struct *handle,
+ TALLOC_CTX *ctx)
+{
+ VFS_FIND(getwd);
+ return handle->fns->getwd_fn(handle, ctx);
+}
+
+int smb_vfs_call_fntimes(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ struct smb_file_time *ft)
+{
+ VFS_FIND(fntimes);
+ return handle->fns->fntimes_fn(handle, fsp, ft);
+}
+
+int smb_vfs_call_ftruncate(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, off_t offset)
+{
+ VFS_FIND(ftruncate);
+ return handle->fns->ftruncate_fn(handle, fsp, offset);
+}
+
+int smb_vfs_call_fallocate(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t mode,
+ off_t offset,
+ off_t len)
+{
+ VFS_FIND(fallocate);
+ return handle->fns->fallocate_fn(handle, fsp, mode, offset, len);
+}
+
+int smb_vfs_call_filesystem_sharemode(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t share_mode,
+ uint32_t access_mask)
+{
+ VFS_FIND(filesystem_sharemode);
+ return handle->fns->filesystem_sharemode_fn(handle,
+ fsp,
+ share_mode,
+ access_mask);
+}
+
+int smb_vfs_call_fcntl(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, int cmd, ...)
+{
+ int result;
+ va_list cmd_arg;
+
+ VFS_FIND(fcntl);
+
+ va_start(cmd_arg, cmd);
+ result = handle->fns->fcntl_fn(handle, fsp, cmd, cmd_arg);
+ va_end(cmd_arg);
+
+ return result;
+}
+
+int smb_vfs_call_linux_setlease(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, int leasetype)
+{
+ VFS_FIND(linux_setlease);
+ return handle->fns->linux_setlease_fn(handle, fsp, leasetype);
+}
+
+int smb_vfs_call_symlinkat(struct vfs_handle_struct *handle,
+ const struct smb_filename *link_target,
+ struct files_struct *dirfsp,
+ const struct smb_filename *new_smb_fname)
+{
+ VFS_FIND(symlinkat);
+ return handle->fns->symlinkat_fn(handle,
+ link_target,
+ dirfsp,
+ new_smb_fname);
+}
+
+int smb_vfs_call_readlinkat(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ char *buf,
+ size_t bufsiz)
+{
+ VFS_FIND(readlinkat);
+ return handle->fns->readlinkat_fn(handle,
+ dirfsp,
+ smb_fname,
+ buf,
+ bufsiz);
+}
+
+int smb_vfs_call_linkat(struct vfs_handle_struct *handle,
+ struct files_struct *srcfsp,
+ const struct smb_filename *old_smb_fname,
+ struct files_struct *dstfsp,
+ const struct smb_filename *new_smb_fname,
+ int flags)
+{
+ VFS_FIND(linkat);
+ return handle->fns->linkat_fn(handle,
+ srcfsp,
+ old_smb_fname,
+ dstfsp,
+ new_smb_fname,
+ flags);
+}
+
+int smb_vfs_call_mknodat(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode,
+ SMB_DEV_T dev)
+{
+ VFS_FIND(mknodat);
+ return handle->fns->mknodat_fn(handle,
+ dirfsp,
+ smb_fname,
+ mode,
+ dev);
+}
+
+struct smb_filename *smb_vfs_call_realpath(struct vfs_handle_struct *handle,
+ TALLOC_CTX *ctx,
+ const struct smb_filename *smb_fname)
+{
+ VFS_FIND(realpath);
+ return handle->fns->realpath_fn(handle, ctx, smb_fname);
+}
+
+int smb_vfs_call_fchflags(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ unsigned int flags)
+{
+ VFS_FIND(fchflags);
+ return handle->fns->fchflags_fn(handle, fsp, flags);
+}
+
+struct file_id smb_vfs_call_file_id_create(struct vfs_handle_struct *handle,
+ const SMB_STRUCT_STAT *sbuf)
+{
+ VFS_FIND(file_id_create);
+ return handle->fns->file_id_create_fn(handle, sbuf);
+}
+
+uint64_t smb_vfs_call_fs_file_id(struct vfs_handle_struct *handle,
+ const SMB_STRUCT_STAT *sbuf)
+{
+ VFS_FIND(fs_file_id);
+ return handle->fns->fs_file_id_fn(handle, sbuf);
+}
+
+NTSTATUS smb_vfs_call_fstreaminfo(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *num_streams,
+ struct stream_struct **streams)
+{
+ VFS_FIND(fstreaminfo);
+ return handle->fns->fstreaminfo_fn(handle, fsp, mem_ctx,
+ num_streams, streams);
+}
+
+NTSTATUS smb_vfs_call_get_real_filename_at(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const char *name,
+ TALLOC_CTX *mem_ctx,
+ char **found_name)
+{
+ VFS_FIND(get_real_filename_at);
+ return handle->fns->get_real_filename_at_fn(
+ handle, dirfsp, name, mem_ctx, found_name);
+}
+
+const char *smb_vfs_call_connectpath(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname)
+{
+ VFS_FIND(connectpath);
+ return handle->fns->connectpath_fn(handle, dirfsp, smb_fname);
+}
+
+bool smb_vfs_call_strict_lock_check(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ struct lock_struct *plock)
+{
+ VFS_FIND(strict_lock_check);
+ return handle->fns->strict_lock_check_fn(handle, fsp, plock);
+}
+
+NTSTATUS smb_vfs_call_translate_name(struct vfs_handle_struct *handle,
+ const char *name,
+ enum vfs_translate_direction direction,
+ TALLOC_CTX *mem_ctx,
+ char **mapped_name)
+{
+ VFS_FIND(translate_name);
+ return handle->fns->translate_name_fn(handle, name, direction, mem_ctx,
+ mapped_name);
+}
+
+NTSTATUS smb_vfs_call_parent_pathname(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const struct smb_filename *smb_fname_in,
+ struct smb_filename **parent_dir_out,
+ struct smb_filename **atname_out)
+{
+ VFS_FIND(parent_pathname);
+ return handle->fns->parent_pathname_fn(handle,
+ mem_ctx,
+ smb_fname_in,
+ parent_dir_out,
+ atname_out);
+}
+
+NTSTATUS smb_vfs_call_fsctl(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *ctx,
+ uint32_t function,
+ uint16_t req_flags,
+ const uint8_t *in_data,
+ uint32_t in_len,
+ uint8_t **out_data,
+ uint32_t max_out_len,
+ uint32_t *out_len)
+{
+ VFS_FIND(fsctl);
+ return handle->fns->fsctl_fn(handle, fsp, ctx, function, req_flags,
+ in_data, in_len, out_data, max_out_len,
+ out_len);
+}
+
+NTSTATUS smb_vfs_call_fget_dos_attributes(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t *dosmode)
+{
+ VFS_FIND(fget_dos_attributes);
+ return handle->fns->fget_dos_attributes_fn(handle, fsp, dosmode);
+}
+
+NTSTATUS smb_vfs_call_fset_dos_attributes(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t dosmode)
+{
+ VFS_FIND(fset_dos_attributes);
+ return handle->fns->fset_dos_attributes_fn(handle, fsp, dosmode);
+}
+
+struct tevent_req *smb_vfs_call_offload_read_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t fsctl,
+ uint32_t ttl,
+ off_t offset,
+ size_t to_copy)
+{
+ VFS_FIND(offload_read_send);
+ return handle->fns->offload_read_send_fn(mem_ctx, ev, handle,
+ fsp, fsctl,
+ ttl, offset, to_copy);
+}
+
+NTSTATUS smb_vfs_call_offload_read_recv(struct tevent_req *req,
+ struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *flags,
+ uint64_t *xferlen,
+ DATA_BLOB *token_blob)
+{
+ VFS_FIND(offload_read_recv);
+ return handle->fns->offload_read_recv_fn(req, handle, mem_ctx, flags, xferlen, token_blob);
+}
+
+struct tevent_req *smb_vfs_call_offload_write_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ uint32_t fsctl,
+ DATA_BLOB *token,
+ off_t transfer_offset,
+ struct files_struct *dest_fsp,
+ off_t dest_off,
+ off_t num)
+{
+ VFS_FIND(offload_write_send);
+ return handle->fns->offload_write_send_fn(handle, mem_ctx, ev, fsctl,
+ token, transfer_offset,
+ dest_fsp, dest_off, num);
+}
+
+NTSTATUS smb_vfs_call_offload_write_recv(struct vfs_handle_struct *handle,
+ struct tevent_req *req,
+ off_t *copied)
+{
+ VFS_FIND(offload_write_recv);
+ return handle->fns->offload_write_recv_fn(handle, req, copied);
+}
+
+struct smb_vfs_call_get_dos_attributes_state {
+ files_struct *dir_fsp;
+ NTSTATUS (*recv_fn)(struct tevent_req *req,
+ struct vfs_aio_state *aio_state,
+ uint32_t *dosmode);
+ struct vfs_aio_state aio_state;
+ uint32_t dos_attributes;
+};
+
+static void smb_vfs_call_get_dos_attributes_done(struct tevent_req *subreq);
+
+struct tevent_req *smb_vfs_call_get_dos_attributes_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ files_struct *dir_fsp,
+ struct smb_filename *smb_fname)
+{
+ struct tevent_req *req = NULL;
+ struct smb_vfs_call_get_dos_attributes_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb_vfs_call_get_dos_attributes_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ VFS_FIND(get_dos_attributes_send);
+
+ *state = (struct smb_vfs_call_get_dos_attributes_state) {
+ .dir_fsp = dir_fsp,
+ .recv_fn = handle->fns->get_dos_attributes_recv_fn,
+ };
+
+ subreq = handle->fns->get_dos_attributes_send_fn(mem_ctx,
+ ev,
+ handle,
+ dir_fsp,
+ smb_fname);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_defer_callback(req, ev);
+
+ tevent_req_set_callback(subreq,
+ smb_vfs_call_get_dos_attributes_done,
+ req);
+
+ return req;
+}
+
+static void smb_vfs_call_get_dos_attributes_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smb_vfs_call_get_dos_attributes_state *state =
+ tevent_req_data(req,
+ struct smb_vfs_call_get_dos_attributes_state);
+ NTSTATUS status;
+ bool ok;
+
+ /*
+ * Make sure we run as the user again
+ */
+ ok = change_to_user_and_service_by_fsp(state->dir_fsp);
+ SMB_ASSERT(ok);
+
+ status = state->recv_fn(subreq,
+ &state->aio_state,
+ &state->dos_attributes);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+NTSTATUS smb_vfs_call_get_dos_attributes_recv(
+ struct tevent_req *req,
+ struct vfs_aio_state *aio_state,
+ uint32_t *dos_attributes)
+{
+ struct smb_vfs_call_get_dos_attributes_state *state =
+ tevent_req_data(req,
+ struct smb_vfs_call_get_dos_attributes_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *aio_state = state->aio_state;
+ *dos_attributes = state->dos_attributes;
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smb_vfs_call_fget_compression(vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *fsp,
+ uint16_t *_compression_fmt)
+{
+ VFS_FIND(fget_compression);
+ return handle->fns->fget_compression_fn(handle, mem_ctx, fsp,
+ _compression_fmt);
+}
+
+NTSTATUS smb_vfs_call_set_compression(vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *fsp,
+ uint16_t compression_fmt)
+{
+ VFS_FIND(set_compression);
+ return handle->fns->set_compression_fn(handle, mem_ctx, fsp,
+ compression_fmt);
+}
+
+NTSTATUS smb_vfs_call_snap_check_path(vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const char *service_path,
+ char **base_volume)
+{
+ VFS_FIND(snap_check_path);
+ return handle->fns->snap_check_path_fn(handle, mem_ctx, service_path,
+ base_volume);
+}
+
+NTSTATUS smb_vfs_call_snap_create(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const char *base_volume,
+ time_t *tstamp,
+ bool rw,
+ char **base_path,
+ char **snap_path)
+{
+ VFS_FIND(snap_create);
+ return handle->fns->snap_create_fn(handle, mem_ctx, base_volume, tstamp,
+ rw, base_path, snap_path);
+}
+
+NTSTATUS smb_vfs_call_snap_delete(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ char *base_path,
+ char *snap_path)
+{
+ VFS_FIND(snap_delete);
+ return handle->fns->snap_delete_fn(handle, mem_ctx, base_path,
+ snap_path);
+}
+
+NTSTATUS smb_vfs_call_fget_nt_acl(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc)
+{
+ VFS_FIND(fget_nt_acl);
+ return handle->fns->fget_nt_acl_fn(handle, fsp, security_info,
+ mem_ctx, ppdesc);
+}
+
+NTSTATUS smb_vfs_call_fset_nt_acl(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t security_info_sent,
+ const struct security_descriptor *psd)
+{
+ VFS_FIND(fset_nt_acl);
+ return handle->fns->fset_nt_acl_fn(handle, fsp, security_info_sent,
+ psd);
+}
+
+NTSTATUS smb_vfs_call_audit_file(struct vfs_handle_struct *handle,
+ struct smb_filename *file,
+ struct security_acl *sacl,
+ uint32_t access_requested,
+ uint32_t access_denied)
+{
+ VFS_FIND(audit_file);
+ return handle->fns->audit_file_fn(handle,
+ file,
+ sacl,
+ access_requested,
+ access_denied);
+}
+
+SMB_ACL_T smb_vfs_call_sys_acl_get_fd(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx)
+{
+ VFS_FIND(sys_acl_get_fd);
+ return handle->fns->sys_acl_get_fd_fn(handle, fsp, type, mem_ctx);
+}
+
+int smb_vfs_call_sys_acl_blob_get_fd(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ char **blob_description,
+ DATA_BLOB *blob)
+{
+ VFS_FIND(sys_acl_blob_get_fd);
+ return handle->fns->sys_acl_blob_get_fd_fn(handle, fsp, mem_ctx, blob_description, blob);
+}
+
+int smb_vfs_call_sys_acl_set_fd(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T theacl)
+{
+ VFS_FIND(sys_acl_set_fd);
+ return handle->fns->sys_acl_set_fd_fn(handle, fsp, type, theacl);
+}
+
+int smb_vfs_call_sys_acl_delete_def_fd(struct vfs_handle_struct *handle,
+ struct files_struct *fsp)
+{
+ VFS_FIND(sys_acl_delete_def_fd);
+ return handle->fns->sys_acl_delete_def_fd_fn(handle, fsp);
+}
+
+struct smb_vfs_call_getxattrat_state {
+ files_struct *dir_fsp;
+ ssize_t (*recv_fn)(struct tevent_req *req,
+ struct vfs_aio_state *aio_state,
+ TALLOC_CTX *mem_ctx,
+ uint8_t **xattr_value);
+ ssize_t retval;
+ uint8_t *xattr_value;
+ struct vfs_aio_state aio_state;
+};
+
+static void smb_vfs_call_getxattrat_done(struct tevent_req *subreq);
+
+struct tevent_req *smb_vfs_call_getxattrat_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ files_struct *dir_fsp,
+ const struct smb_filename *smb_fname,
+ const char *xattr_name,
+ size_t alloc_hint)
+{
+ struct tevent_req *req = NULL;
+ struct smb_vfs_call_getxattrat_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb_vfs_call_getxattrat_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ VFS_FIND(getxattrat_send);
+
+ *state = (struct smb_vfs_call_getxattrat_state) {
+ .dir_fsp = dir_fsp,
+ .recv_fn = handle->fns->getxattrat_recv_fn,
+ };
+
+ subreq = handle->fns->getxattrat_send_fn(mem_ctx,
+ ev,
+ handle,
+ dir_fsp,
+ smb_fname,
+ xattr_name,
+ alloc_hint);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_defer_callback(req, ev);
+
+ tevent_req_set_callback(subreq, smb_vfs_call_getxattrat_done, req);
+ return req;
+}
+
+static void smb_vfs_call_getxattrat_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smb_vfs_call_getxattrat_state *state = tevent_req_data(
+ req, struct smb_vfs_call_getxattrat_state);
+ bool ok;
+
+ /*
+ * Make sure we run as the user again
+ */
+ ok = change_to_user_and_service_by_fsp(state->dir_fsp);
+ SMB_ASSERT(ok);
+
+ state->retval = state->recv_fn(subreq,
+ &state->aio_state,
+ state,
+ &state->xattr_value);
+ TALLOC_FREE(subreq);
+ if (state->retval == -1) {
+ tevent_req_error(req, state->aio_state.error);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+ssize_t smb_vfs_call_getxattrat_recv(struct tevent_req *req,
+ struct vfs_aio_state *aio_state,
+ TALLOC_CTX *mem_ctx,
+ uint8_t **xattr_value)
+{
+ struct smb_vfs_call_getxattrat_state *state = tevent_req_data(
+ req, struct smb_vfs_call_getxattrat_state);
+ size_t xattr_size;
+
+ if (tevent_req_is_unix_error(req, &aio_state->error)) {
+ tevent_req_received(req);
+ return -1;
+ }
+
+ *aio_state = state->aio_state;
+ xattr_size = state->retval;
+ if (xattr_value != NULL) {
+ *xattr_value = talloc_move(mem_ctx, &state->xattr_value);
+ }
+
+ tevent_req_received(req);
+ return xattr_size;
+}
+
+ssize_t smb_vfs_call_fgetxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, const char *name,
+ void *value, size_t size)
+{
+ VFS_FIND(fgetxattr);
+ return handle->fns->fgetxattr_fn(handle, fsp, name, value, size);
+}
+
+ssize_t smb_vfs_call_flistxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, char *list,
+ size_t size)
+{
+ VFS_FIND(flistxattr);
+ return handle->fns->flistxattr_fn(handle, fsp, list, size);
+}
+
+int smb_vfs_call_fremovexattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, const char *name)
+{
+ VFS_FIND(fremovexattr);
+ return handle->fns->fremovexattr_fn(handle, fsp, name);
+}
+
+int smb_vfs_call_fsetxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, const char *name,
+ const void *value, size_t size, int flags)
+{
+ VFS_FIND(fsetxattr);
+ return handle->fns->fsetxattr_fn(handle, fsp, name, value, size, flags);
+}
+
+bool smb_vfs_call_aio_force(struct vfs_handle_struct *handle,
+ struct files_struct *fsp)
+{
+ VFS_FIND(aio_force);
+ return handle->fns->aio_force_fn(handle, fsp);
+}
+
+NTSTATUS smb_vfs_call_durable_cookie(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *cookie)
+{
+ VFS_FIND(durable_cookie);
+ return handle->fns->durable_cookie_fn(handle, fsp, mem_ctx, cookie);
+}
+
+NTSTATUS smb_vfs_call_durable_disconnect(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *new_cookie)
+{
+ VFS_FIND(durable_disconnect);
+ return handle->fns->durable_disconnect_fn(handle, fsp, old_cookie,
+ mem_ctx, new_cookie);
+}
+
+NTSTATUS smb_vfs_call_durable_reconnect(struct vfs_handle_struct *handle,
+ struct smb_request *smb1req,
+ struct smbXsrv_open *op,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct **fsp,
+ DATA_BLOB *new_cookie)
+{
+ VFS_FIND(durable_reconnect);
+ return handle->fns->durable_reconnect_fn(handle, smb1req, op,
+ old_cookie, mem_ctx, fsp,
+ new_cookie);
+}
+
+NTSTATUS smb_vfs_call_freaddir_attr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ struct readdir_attr_data **attr_data)
+{
+ VFS_FIND(freaddir_attr);
+ return handle->fns->freaddir_attr_fn(handle,
+ fsp,
+ mem_ctx,
+ attr_data);
+}
+
+bool smb_vfs_call_lock(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, int op, off_t offset,
+ off_t count, int type)
+{
+ VFS_FIND(lock);
+ return handle->fns->lock_fn(handle, fsp, op, offset, count, type);
+}
+
+bool smb_vfs_call_getlock(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, off_t *poffset,
+ off_t *pcount, int *ptype, pid_t *ppid)
+{
+ VFS_FIND(getlock);
+ return handle->fns->getlock_fn(handle, fsp, poffset, pcount, ptype,
+ ppid);
+}
+
+NTSTATUS smb_vfs_call_brl_lock_windows(struct vfs_handle_struct *handle,
+ struct byte_range_lock *br_lck,
+ struct lock_struct *plock)
+{
+ VFS_FIND(brl_lock_windows);
+ return handle->fns->brl_lock_windows_fn(handle, br_lck, plock);
+}
+
+bool smb_vfs_call_brl_unlock_windows(struct vfs_handle_struct *handle,
+ struct byte_range_lock *br_lck,
+ const struct lock_struct *plock)
+{
+ VFS_FIND(brl_unlock_windows);
+ return handle->fns->brl_unlock_windows_fn(handle, br_lck, plock);
+}