From 8daa83a594a2e98f39d764422bfbdbc62c9efd44 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 19:20:00 +0200 Subject: Adding upstream version 2:4.20.0+dfsg. Signed-off-by: Daniel Baumann --- source3/smbd/avahi_register.c | 300 ++ source3/smbd/blocking.c | 763 ++++ source3/smbd/close.c | 1742 ++++++++ source3/smbd/conn.c | 267 ++ source3/smbd/conn_idle.c | 277 ++ source3/smbd/conn_msg.c | 148 + source3/smbd/connection.c | 97 + source3/smbd/dfree.c | 296 ++ source3/smbd/dir.c | 1504 +++++++ source3/smbd/dir.h | 89 + source3/smbd/dmapi.c | 357 ++ source3/smbd/dnsregister.c | 200 + source3/smbd/dosmode.c | 1305 ++++++ source3/smbd/durable.c | 938 +++++ source3/smbd/error.c | 175 + source3/smbd/fake_file.c | 213 + source3/smbd/fd_handle.c | 147 + source3/smbd/fd_handle.h | 49 + source3/smbd/file_access.c | 247 ++ source3/smbd/fileio.c | 318 ++ source3/smbd/filename.c | 1271 ++++++ source3/smbd/files.c | 2651 ++++++++++++ source3/smbd/globals.c | 123 + source3/smbd/globals.h | 912 ++++ source3/smbd/mangle.c | 153 + source3/smbd/mangle_hash.c | 1132 +++++ source3/smbd/mangle_hash2.c | 875 ++++ source3/smbd/msdfs.c | 1774 ++++++++ source3/smbd/notify.c | 964 +++++ source3/smbd/notify_fam.c | 307 ++ source3/smbd/notify_inotify.c | 457 ++ source3/smbd/notify_msg.c | 247 ++ source3/smbd/notifyd/fcn_wait.c | 270 ++ source3/smbd/notifyd/fcn_wait.h | 38 + source3/smbd/notifyd/notifyd.c | 1428 +++++++ source3/smbd/notifyd/notifyd.h | 145 + source3/smbd/notifyd/notifyd_db.c | 165 + source3/smbd/notifyd/notifyd_db.h | 27 + source3/smbd/notifyd/notifyd_entry.c | 42 + source3/smbd/notifyd/notifyd_private.h | 49 + source3/smbd/notifyd/notifydd.c | 98 + source3/smbd/notifyd/test_notifyd.c | 347 ++ source3/smbd/notifyd/tests.c | 118 + source3/smbd/notifyd/wscript_build | 44 + source3/smbd/ntquotas.c | 265 ++ source3/smbd/open.c | 6676 +++++++++++++++++++++++++++++ source3/smbd/oplock_linux.c | 243 ++ source3/smbd/password.c | 91 + source3/smbd/posix_acls.c | 4862 +++++++++++++++++++++ source3/smbd/proto.h | 1224 ++++++ source3/smbd/pysmbd.c | 1305 ++++++ source3/smbd/quotas.c | 497 +++ source3/smbd/scavenger.c | 731 ++++ source3/smbd/scavenger.h | 31 + source3/smbd/seal.c | 313 ++ source3/smbd/sec_ctx.c | 513 +++ source3/smbd/server.c | 2136 ++++++++++ source3/smbd/server_exit.c | 262 ++ source3/smbd/server_reload.c | 176 + source3/smbd/session.c | 218 + source3/smbd/share_access.c | 291 ++ source3/smbd/smb1_aio.c | 409 ++ source3/smbd/smb1_aio.h | 29 + source3/smbd/smb1_ipc.c | 949 +++++ source3/smbd/smb1_ipc.h | 33 + source3/smbd/smb1_lanman.c | 5920 ++++++++++++++++++++++++++ source3/smbd/smb1_lanman.h | 28 + source3/smbd/smb1_message.c | 334 ++ source3/smbd/smb1_message.h | 23 + source3/smbd/smb1_negprot.c | 706 ++++ source3/smbd/smb1_negprot.h | 21 + source3/smbd/smb1_nttrans.c | 2718 ++++++++++++ source3/smbd/smb1_nttrans.h | 25 + source3/smbd/smb1_oplock.c | 72 + source3/smbd/smb1_oplock.h | 26 + source3/smbd/smb1_pipes.c | 447 ++ source3/smbd/smb1_pipes.h | 26 + source3/smbd/smb1_process.c | 2718 ++++++++++++ source3/smbd/smb1_process.h | 72 + source3/smbd/smb1_reply.c | 7178 ++++++++++++++++++++++++++++++++ source3/smbd/smb1_reply.h | 80 + source3/smbd/smb1_service.c | 241 ++ source3/smbd/smb1_service.h | 24 + source3/smbd/smb1_sesssetup.c | 1118 +++++ source3/smbd/smb1_sesssetup.h | 25 + source3/smbd/smb1_signing.c | 290 ++ source3/smbd/smb1_signing.h | 37 + source3/smbd/smb1_trans2.c | 5703 +++++++++++++++++++++++++ source3/smbd/smb1_trans2.h | 27 + source3/smbd/smb1_utils.c | 261 ++ source3/smbd/smb1_utils.h | 46 + source3/smbd/smb2_aio.c | 608 +++ source3/smbd/smb2_break.c | 495 +++ source3/smbd/smb2_close.c | 427 ++ source3/smbd/smb2_create.c | 2113 ++++++++++ source3/smbd/smb2_flush.c | 257 ++ source3/smbd/smb2_getinfo.c | 694 +++ source3/smbd/smb2_glue.c | 118 + source3/smbd/smb2_ioctl.c | 505 +++ source3/smbd/smb2_ioctl_dfs.c | 157 + source3/smbd/smb2_ioctl_filesys.c | 830 ++++ source3/smbd/smb2_ioctl_named_pipe.c | 188 + source3/smbd/smb2_ioctl_network_fs.c | 839 ++++ source3/smbd/smb2_ioctl_private.h | 60 + source3/smbd/smb2_ioctl_smbtorture.c | 230 + source3/smbd/smb2_ipc.c | 40 + source3/smbd/smb2_keepalive.c | 50 + source3/smbd/smb2_lock.c | 782 ++++ source3/smbd/smb2_negprot.c | 1229 ++++++ source3/smbd/smb2_notify.c | 394 ++ source3/smbd/smb2_nttrans.c | 911 ++++ source3/smbd/smb2_oplock.c | 1430 +++++++ source3/smbd/smb2_pipes.c | 150 + source3/smbd/smb2_posix.c | 73 + source3/smbd/smb2_process.c | 2073 +++++++++ source3/smbd/smb2_query_directory.c | 1076 +++++ source3/smbd/smb2_read.c | 685 +++ source3/smbd/smb2_reply.c | 2195 ++++++++++ source3/smbd/smb2_server.c | 5235 +++++++++++++++++++++++ source3/smbd/smb2_service.c | 951 +++++ source3/smbd/smb2_sesssetup.c | 1373 ++++++ source3/smbd/smb2_setinfo.c | 628 +++ source3/smbd/smb2_signing.c | 52 + source3/smbd/smb2_tcon.c | 765 ++++ source3/smbd/smb2_trans2.c | 5243 +++++++++++++++++++++++ source3/smbd/smb2_write.c | 457 ++ source3/smbd/smbXsrv_client.c | 1458 +++++++ source3/smbd/smbXsrv_open.c | 1526 +++++++ source3/smbd/smbXsrv_open.h | 75 + source3/smbd/smbXsrv_session.c | 2527 +++++++++++ source3/smbd/smbXsrv_tcon.c | 1272 ++++++ source3/smbd/smbXsrv_version.c | 265 ++ source3/smbd/smbd.h | 102 + source3/smbd/smbd_cleanupd.c | 182 + source3/smbd/smbd_cleanupd.h | 33 + source3/smbd/srvstr.c | 78 + source3/smbd/statvfs.c | 182 + source3/smbd/uid.c | 752 ++++ source3/smbd/utmp.c | 604 +++ source3/smbd/vfs.c | 2661 ++++++++++++ 140 files changed, 117519 insertions(+) create mode 100644 source3/smbd/avahi_register.c create mode 100644 source3/smbd/blocking.c create mode 100644 source3/smbd/close.c create mode 100644 source3/smbd/conn.c create mode 100644 source3/smbd/conn_idle.c create mode 100644 source3/smbd/conn_msg.c create mode 100644 source3/smbd/connection.c create mode 100644 source3/smbd/dfree.c create mode 100644 source3/smbd/dir.c create mode 100644 source3/smbd/dir.h create mode 100644 source3/smbd/dmapi.c create mode 100644 source3/smbd/dnsregister.c create mode 100644 source3/smbd/dosmode.c create mode 100644 source3/smbd/durable.c create mode 100644 source3/smbd/error.c create mode 100644 source3/smbd/fake_file.c create mode 100644 source3/smbd/fd_handle.c create mode 100644 source3/smbd/fd_handle.h create mode 100644 source3/smbd/file_access.c create mode 100644 source3/smbd/fileio.c create mode 100644 source3/smbd/filename.c create mode 100644 source3/smbd/files.c create mode 100644 source3/smbd/globals.c create mode 100644 source3/smbd/globals.h create mode 100644 source3/smbd/mangle.c create mode 100644 source3/smbd/mangle_hash.c create mode 100644 source3/smbd/mangle_hash2.c create mode 100644 source3/smbd/msdfs.c create mode 100644 source3/smbd/notify.c create mode 100644 source3/smbd/notify_fam.c create mode 100644 source3/smbd/notify_inotify.c create mode 100644 source3/smbd/notify_msg.c create mode 100644 source3/smbd/notifyd/fcn_wait.c create mode 100644 source3/smbd/notifyd/fcn_wait.h create mode 100644 source3/smbd/notifyd/notifyd.c create mode 100644 source3/smbd/notifyd/notifyd.h create mode 100644 source3/smbd/notifyd/notifyd_db.c create mode 100644 source3/smbd/notifyd/notifyd_db.h create mode 100644 source3/smbd/notifyd/notifyd_entry.c create mode 100644 source3/smbd/notifyd/notifyd_private.h create mode 100644 source3/smbd/notifyd/notifydd.c create mode 100644 source3/smbd/notifyd/test_notifyd.c create mode 100644 source3/smbd/notifyd/tests.c create mode 100644 source3/smbd/notifyd/wscript_build create mode 100644 source3/smbd/ntquotas.c create mode 100644 source3/smbd/open.c create mode 100644 source3/smbd/oplock_linux.c create mode 100644 source3/smbd/password.c create mode 100644 source3/smbd/posix_acls.c create mode 100644 source3/smbd/proto.h create mode 100644 source3/smbd/pysmbd.c create mode 100644 source3/smbd/quotas.c create mode 100644 source3/smbd/scavenger.c create mode 100644 source3/smbd/scavenger.h create mode 100644 source3/smbd/seal.c create mode 100644 source3/smbd/sec_ctx.c create mode 100644 source3/smbd/server.c create mode 100644 source3/smbd/server_exit.c create mode 100644 source3/smbd/server_reload.c create mode 100644 source3/smbd/session.c create mode 100644 source3/smbd/share_access.c create mode 100644 source3/smbd/smb1_aio.c create mode 100644 source3/smbd/smb1_aio.h create mode 100644 source3/smbd/smb1_ipc.c create mode 100644 source3/smbd/smb1_ipc.h create mode 100644 source3/smbd/smb1_lanman.c create mode 100644 source3/smbd/smb1_lanman.h create mode 100644 source3/smbd/smb1_message.c create mode 100644 source3/smbd/smb1_message.h create mode 100644 source3/smbd/smb1_negprot.c create mode 100644 source3/smbd/smb1_negprot.h create mode 100644 source3/smbd/smb1_nttrans.c create mode 100644 source3/smbd/smb1_nttrans.h create mode 100644 source3/smbd/smb1_oplock.c create mode 100644 source3/smbd/smb1_oplock.h create mode 100644 source3/smbd/smb1_pipes.c create mode 100644 source3/smbd/smb1_pipes.h create mode 100644 source3/smbd/smb1_process.c create mode 100644 source3/smbd/smb1_process.h create mode 100644 source3/smbd/smb1_reply.c create mode 100644 source3/smbd/smb1_reply.h create mode 100644 source3/smbd/smb1_service.c create mode 100644 source3/smbd/smb1_service.h create mode 100644 source3/smbd/smb1_sesssetup.c create mode 100644 source3/smbd/smb1_sesssetup.h create mode 100644 source3/smbd/smb1_signing.c create mode 100644 source3/smbd/smb1_signing.h create mode 100644 source3/smbd/smb1_trans2.c create mode 100644 source3/smbd/smb1_trans2.h create mode 100644 source3/smbd/smb1_utils.c create mode 100644 source3/smbd/smb1_utils.h create mode 100644 source3/smbd/smb2_aio.c create mode 100644 source3/smbd/smb2_break.c create mode 100644 source3/smbd/smb2_close.c create mode 100644 source3/smbd/smb2_create.c create mode 100644 source3/smbd/smb2_flush.c create mode 100644 source3/smbd/smb2_getinfo.c create mode 100644 source3/smbd/smb2_glue.c create mode 100644 source3/smbd/smb2_ioctl.c create mode 100644 source3/smbd/smb2_ioctl_dfs.c create mode 100644 source3/smbd/smb2_ioctl_filesys.c create mode 100644 source3/smbd/smb2_ioctl_named_pipe.c create mode 100644 source3/smbd/smb2_ioctl_network_fs.c create mode 100644 source3/smbd/smb2_ioctl_private.h create mode 100644 source3/smbd/smb2_ioctl_smbtorture.c create mode 100644 source3/smbd/smb2_ipc.c create mode 100644 source3/smbd/smb2_keepalive.c create mode 100644 source3/smbd/smb2_lock.c create mode 100644 source3/smbd/smb2_negprot.c create mode 100644 source3/smbd/smb2_notify.c create mode 100644 source3/smbd/smb2_nttrans.c create mode 100644 source3/smbd/smb2_oplock.c create mode 100644 source3/smbd/smb2_pipes.c create mode 100644 source3/smbd/smb2_posix.c create mode 100644 source3/smbd/smb2_process.c create mode 100644 source3/smbd/smb2_query_directory.c create mode 100644 source3/smbd/smb2_read.c create mode 100644 source3/smbd/smb2_reply.c create mode 100644 source3/smbd/smb2_server.c create mode 100644 source3/smbd/smb2_service.c create mode 100644 source3/smbd/smb2_sesssetup.c create mode 100644 source3/smbd/smb2_setinfo.c create mode 100644 source3/smbd/smb2_signing.c create mode 100644 source3/smbd/smb2_tcon.c create mode 100644 source3/smbd/smb2_trans2.c create mode 100644 source3/smbd/smb2_write.c create mode 100644 source3/smbd/smbXsrv_client.c create mode 100644 source3/smbd/smbXsrv_open.c create mode 100644 source3/smbd/smbXsrv_open.h create mode 100644 source3/smbd/smbXsrv_session.c create mode 100644 source3/smbd/smbXsrv_tcon.c create mode 100644 source3/smbd/smbXsrv_version.c create mode 100644 source3/smbd/smbd.h create mode 100644 source3/smbd/smbd_cleanupd.c create mode 100644 source3/smbd/smbd_cleanupd.h create mode 100644 source3/smbd/srvstr.c create mode 100644 source3/smbd/statvfs.c create mode 100644 source3/smbd/uid.c create mode 100644 source3/smbd/utmp.c create mode 100644 source3/smbd/vfs.c (limited to 'source3/smbd') 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 . + */ + +#include "includes.h" +#include "smbd/smbd.h" + +#include +#include +#include +#include +#include + +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 . +*/ + +#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; ireq_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; iblocked_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; ilarge_offset != large_offset) { + continue; + } + + for (j=0; jnum_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; ismbreq; + + 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 . +*/ + +#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; icwd_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 . +*/ + +#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; ivuid_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 . +*/ + +#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 . +*/ + +#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 . +*/ + +#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 . +*/ + +#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 . +*/ + +#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 . + */ + +#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 . +*/ + +#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 +#elif defined(HAVE_SYS_DMI_H) +#include +#elif defined(HAVE_SYS_JFSDMAPI_H) +#include +#elif defined(HAVE_SYS_DMAPI_H) +#include +#elif defined(HAVE_DMAPI_H) +#include +#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 . +*/ + +#include +#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 + +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 . +*/ + +#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 + */ + + 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 . +*/ + +#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, + ¤t_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 . +*/ + +#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 . +*/ + +#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 . +*/ + +#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 . +*/ + +#ifndef FD_HANDLE_H +#define FD_HANDLE_H + +#include "replace.h" +#include + +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 . +*/ + +#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 + * 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 . +*/ + +#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 . +*/ + +/* + * 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; icase_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 . +*/ + +#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 . +*/ + +#include "includes.h" +#include "smbd/smbd.h" +#include "smbd/globals.h" +#include "../lib/util/memcache.h" +#include "messages.h" +#include + +#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 . +*/ + +#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 . +*/ + +#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 . +*/ + +#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 + * array. The scope of 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 . 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 . +*/ + +/* + 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 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; 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 . + +*/ + +#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((countconn, + 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 . +*/ + +#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; iname; + 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 . + */ + +#include "includes.h" +#include "smbd/smbd.h" +#include "librpc/gen_ndr/notify.h" + +#include + +#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 . +*/ + +/* + 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 + +/* 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;ifd); + 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 . + */ + +#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 . + */ + +#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 . + */ + +#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 . + */ + +#include "replace.h" +#include +#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; iclient, 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; inum_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; icovered_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; inum_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; iinstance.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; istate; + size_t i; + + if (p->db != NULL) { + dbwrap_traverse_read(p->db, notifyd_db_del_syswatches, + NULL, NULL); + } + + for (i = 0; inum_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; inum_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; inum_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 . + */ + +#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 . + */ + +#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; ifn(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", ¬ifyd); + 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 . + */ + +#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 . + */ + +#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 . + */ + +#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 . + */ + +#include "replace.h" +#include "notifyd.h" +#include "lib/messages_ctdb.h" +#include +#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 . + */ + +#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", ¬ifyd); + 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", ¬ifyd); + 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 . + */ + +#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 \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", ¬ifyd); + 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 . +*/ + +#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 . +*/ + +#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." + * 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, + ¤t_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 . 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; ibase_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 . + */ + + 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 . +*/ + +#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 . +*/ + +#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 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 . +*/ + +#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(¤t_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(¤t_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(¤t_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(¤t_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(¤t_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(¤t_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(¤t_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(¤t_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(¤t_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(¤t_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(¤t_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(¤t_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" . +****************************************************************************/ + +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 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 . + */ + +#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 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 . +*/ + +#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 +#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 . +*/ + + +/* + * 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 +#include +#include +#include +#include + +/**************************************************************************** + Allows querying of remote hosts for quotas on NFS mounted shares. + Supports normal NFS and AMD mounts. + Alan Romeril July 2K. +****************************************************************************/ + +#include +#include +#include +#include +#include + +static int my_xdr_getquota_rslt(XDR *xdrsp, struct getquota_rslt *gqr) +{ + int quotastat; + + if (!xdr_int(xdrsp, "astat)) { + 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 . +*/ + +#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 . +*/ + +#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 . +*/ + +#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' */ + 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 . +*/ + +#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 + +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 . +*/ + +#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 : ""; + 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 . +*/ + +#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, ("\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 . +*/ + +#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 . +*/ + +/* 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 . +*/ + +#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 . +*/ + +#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 . +*/ + +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 . + */ +/* + 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;isetup_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 . + */ +/* + 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 . + */ +/* + 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 : "" )); + return False; + } + if (desc->subformat && (id2 == NULL || strcmp(desc->subformat,id2) != 0)) { + DEBUG(0,("check_printq_info: invalid subformat %s\n", + id2 ? id2 : "" )); + 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; idependent_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 + * 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 + * 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;iname)) { + 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;iname)) { + 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= 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; ientries[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; icount; 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; ientries[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: <> + ****************************************************************************/ + +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: + or +****************************************************************************/ + +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 : "" )); + 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 : "" )); + 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 . + */ + +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 . +*/ +/* + 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 . +*/ + +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 . +*/ + +#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 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 . +*/ + +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 . +*/ + +#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 . +*/ + +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 . +*/ + +#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 . +*/ + +#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 . +*/ +/* + 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 . +*/ + +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 . +*/ + +#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; ioutbuf, 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; ioutbuf, 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; ichain = 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 . +*/ + +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 . +*/ +/* + 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;(idptr, + 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 + * 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 . +*/ + +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 . +*/ + +#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 . +*/ + +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 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 . +*/ + +#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 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 . +*/ + +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 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 . +*/ + +#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 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 . +*/ + +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 . +*/ + +#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 = ¶ms[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;(idptr, + 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, + ¶m_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, + ¶ms[6], + total_params - 6, + STR_TERMINATE, + &status); + } else { + srvstr_get_path(req, + params, + req->flags2, + &fname, + ¶ms[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, + ¶ms[6], + total_params - 6, + STR_TERMINATE, + &status); + } else { + srvstr_get_path(req, + params, + req->flags2, + &fname, + ¶ms[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, + ¶ms[4], + total_params - 4, + STR_TERMINATE, + &status); + } else { + srvstr_get_path(ctx, + params, + req->flags2, + &directory, + ¶ms[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 . +****************************************************************************/ + +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, ¶ms[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 . +*/ + +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 . + */ + +#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 . + */ + +#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 . +*/ + +#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; inum_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 . +*/ + +#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 . +*/ + +#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 . +*/ + +#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; itag, 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 . +*/ + +#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 . +*/ + +#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 . +*/ + +#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 . +*/ + +#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) : "", + 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 . +*/ + +#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 . +*/ + +#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 . +*/ + +#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 . +*/ + +#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 . +*/ + +#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 . +*/ + +#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 . + */ +/* + 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 . +*/ + +#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 . +*/ + +#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; iposix_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 . +*/ + +#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 +#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 . +*/ + +#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 . +*/ + +#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 . +*/ + +#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, + ¤t_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, + ¤t_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, + ¤t_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, + ¤t_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, + ¤t_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 . +*/ +/* + 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 . +*/ + +#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 . +*/ + +#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 ""; + } + laddr = tsocket_address_string(xconn->local_address, talloc_tos()); + if (laddr == NULL) { + return ""; + } + + 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 ""; + } + + 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 : ""; + 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 . +*/ + +#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 . +*/ + +#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 . +*/ +/* + 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 . +****************************************************************************/ + +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 . +*/ + +#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 +#include + +#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 . +*/ + +#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 . +*/ + +#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 +#include + +#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 . +*/ + +#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 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 . +*/ + +#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 . +*/ + +#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 . +*/ + +#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; iea); + + 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, "as); + 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, "as)!=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 . */ + 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 . +*/ + +#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 . +*/ + +#include "includes.h" +#include "system/filesys.h" +#include +#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 . +*/ + +#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 = ¤t_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 . + */ + +#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 . +*/ + +#include "includes.h" +#include "system/filesys.h" +#include +#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 = ¤t_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 . +*/ + +#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 . +*/ + +#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 . +*/ + +#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 . + */ + +#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 . + */ + +#ifndef __SMBD_CLEANUPD_H__ +#define __SMBD_CLEANUPD_H__ + +#include "replace.h" +#include +#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 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 . +*/ + +#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 . +*/ + +#include "includes.h" +#include "system/filesys.h" +#include "smbd/smbd.h" + +#if defined(DARWINOS) +#include + +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 . +*/ + +#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; ivuid_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 ¤t_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 . +*/ + +#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 . + 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 +#endif + +#ifdef HAVE_UTMPX_H +#include +#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 +#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 +****************************************************************************/ + +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 . + + 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)), + ¤t_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); +} -- cgit v1.2.3