/* Unix SMB/CIFS implementation. 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 "smbd/smbd.h" #include "smbd/globals.h" #include "../libcli/security/security.h" #include "dbwrap/dbwrap.h" #include "dbwrap/dbwrap_rbt.h" #include "dbwrap/dbwrap_open.h" #include "../lib/util/util_tdb.h" #include "librpc/gen_ndr/ndr_ioctl.h" #include "librpc/gen_ndr/ioctl.h" #include "offload_token.h" struct vfs_offload_ctx { bool initialized; struct db_context *db_ctx; }; NTSTATUS vfs_offload_token_ctx_init(TALLOC_CTX *mem_ctx, struct vfs_offload_ctx **_ctx) { struct vfs_offload_ctx *ctx = *_ctx; if (ctx != NULL) { if (!ctx->initialized) { return NT_STATUS_INTERNAL_ERROR; } return NT_STATUS_OK; } ctx = talloc_zero(mem_ctx, struct vfs_offload_ctx); if (ctx == NULL) { return NT_STATUS_NO_MEMORY; } ctx->db_ctx = db_open_rbt(mem_ctx); if (ctx->db_ctx == NULL) { TALLOC_FREE(ctx); return NT_STATUS_INTERNAL_ERROR; } ctx->initialized = true; *_ctx = ctx; return NT_STATUS_OK; } struct fsp_token_link { struct vfs_offload_ctx *ctx; DATA_BLOB token_blob; }; static int fsp_token_link_destructor(struct fsp_token_link *link) { DATA_BLOB token_blob = link->token_blob; TDB_DATA key = make_tdb_data(token_blob.data, token_blob.length); NTSTATUS status; status = dbwrap_delete(link->ctx->db_ctx, key); if (!NT_STATUS_IS_OK(status)) { DBG_ERR("dbwrap_delete failed: %s. Token:\n", nt_errstr(status)); dump_data(0, token_blob.data, token_blob.length); return -1; } return 0; } NTSTATUS vfs_offload_token_db_store_fsp(struct vfs_offload_ctx *ctx, const files_struct *fsp, const DATA_BLOB *token_blob) { struct db_record *rec = NULL; struct fsp_token_link *link = NULL; TDB_DATA key = make_tdb_data(token_blob->data, token_blob->length); TDB_DATA value; NTSTATUS status; rec = dbwrap_fetch_locked(ctx->db_ctx, talloc_tos(), key); if (rec == NULL) { return NT_STATUS_INTERNAL_ERROR; } value = dbwrap_record_get_value(rec); if (value.dsize != 0) { void *ptr = NULL; files_struct *token_db_fsp = NULL; if (value.dsize != sizeof(ptr)) { DBG_ERR("Bad db entry for token:\n"); dump_data(1, token_blob->data, token_blob->length); TALLOC_FREE(rec); return NT_STATUS_INTERNAL_ERROR; } memcpy(&ptr, value.dptr, value.dsize); TALLOC_FREE(rec); token_db_fsp = talloc_get_type_abort(ptr, struct files_struct); if (token_db_fsp != fsp) { DBG_ERR("token for fsp [%s] matches already known " "but different fsp [%s]:\n", fsp_str_dbg(fsp), fsp_str_dbg(token_db_fsp)); dump_data(1, token_blob->data, token_blob->length); return NT_STATUS_INTERNAL_ERROR; } return NT_STATUS_OK; } link = talloc_zero(fsp, struct fsp_token_link); if (link == NULL) { return NT_STATUS_NO_MEMORY; } link->ctx = ctx; link->token_blob = data_blob_talloc(link, token_blob->data, token_blob->length); if (link->token_blob.data == NULL) { TALLOC_FREE(link); return NT_STATUS_NO_MEMORY; } talloc_set_destructor(link, fsp_token_link_destructor); value = make_tdb_data((uint8_t *)&fsp, sizeof(files_struct *)); status = dbwrap_record_store(rec, value, 0); if (!NT_STATUS_IS_OK(status)) { DBG_ERR("dbwrap_record_store for [%s] failed: %s. Token\n", fsp_str_dbg(fsp), nt_errstr(status)); dump_data(0, token_blob->data, token_blob->length); TALLOC_FREE(link); TALLOC_FREE(rec); return status; } TALLOC_FREE(rec); return NT_STATUS_OK; } NTSTATUS vfs_offload_token_db_fetch_fsp(struct vfs_offload_ctx *ctx, const DATA_BLOB *token_blob, files_struct **fsp) { struct db_record *rec = NULL; TDB_DATA key = make_tdb_data(token_blob->data, token_blob->length); TDB_DATA value; void *ptr = NULL; rec = dbwrap_fetch_locked(ctx->db_ctx, talloc_tos(), key); if (rec == NULL) { return NT_STATUS_INTERNAL_ERROR; } value = dbwrap_record_get_value(rec); if (value.dsize == 0) { DBG_DEBUG("Unknown token:\n"); dump_data(10, token_blob->data, token_blob->length); TALLOC_FREE(rec); return NT_STATUS_OBJECT_NAME_NOT_FOUND; } if (value.dsize != sizeof(ptr)) { DBG_ERR("Bad db entry for token:\n"); dump_data(1, token_blob->data, token_blob->length); TALLOC_FREE(rec); return NT_STATUS_INTERNAL_ERROR; } memcpy(&ptr, value.dptr, value.dsize); TALLOC_FREE(rec); *fsp = talloc_get_type_abort(ptr, struct files_struct); return NT_STATUS_OK; } NTSTATUS vfs_offload_token_create_blob(TALLOC_CTX *mem_ctx, const files_struct *fsp, uint32_t fsctl, DATA_BLOB *token_blob) { size_t len; switch (fsctl) { case FSCTL_DUP_EXTENTS_TO_FILE: len = 20; break; case FSCTL_SRV_REQUEST_RESUME_KEY: len = 24; break; default: DBG_ERR("Invalid fsctl [%" PRIu32 "]\n", fsctl); return NT_STATUS_NOT_SUPPORTED; } *token_blob = data_blob_talloc_zero(mem_ctx, len); if (token_blob->length == 0) { return NT_STATUS_NO_MEMORY; } /* combine persistent and volatile handles for the resume key */ SBVAL(token_blob->data, 0, fsp->op->global->open_persistent_id); SBVAL(token_blob->data, 8, fsp->op->global->open_volatile_id); SIVAL(token_blob->data, 16, fsctl); return NT_STATUS_OK; } NTSTATUS vfs_offload_token_check_handles(uint32_t fsctl, files_struct *src_fsp, files_struct *dst_fsp) { if (src_fsp->vuid != dst_fsp->vuid) { DBG_INFO("copy chunk handles not in the same session.\n"); return NT_STATUS_ACCESS_DENIED; } if (!NT_STATUS_IS_OK(src_fsp->op->status)) { DBG_INFO("copy chunk source handle invalid: %s\n", nt_errstr(src_fsp->op->status)); return NT_STATUS_ACCESS_DENIED; } if (!NT_STATUS_IS_OK(dst_fsp->op->status)) { DBG_INFO("copy chunk destination handle invalid: %s\n", nt_errstr(dst_fsp->op->status)); return NT_STATUS_ACCESS_DENIED; } if (src_fsp->deferred_close != NULL) { DBG_INFO("copy chunk src handle with deferred close.\n"); return NT_STATUS_ACCESS_DENIED; } if (dst_fsp->deferred_close != NULL) { DBG_INFO("copy chunk dst handle with deferred close.\n"); return NT_STATUS_ACCESS_DENIED; } if (src_fsp->is_directory) { DBG_INFO("copy chunk no read on src directory handle (%s).\n", smb_fname_str_dbg(src_fsp->fsp_name)); return NT_STATUS_ACCESS_DENIED; } if (dst_fsp->is_directory) { DBG_INFO("copy chunk no read on dst directory handle (%s).\n", smb_fname_str_dbg(dst_fsp->fsp_name)); return NT_STATUS_ACCESS_DENIED; } if (IS_IPC(src_fsp->conn) || IS_IPC(dst_fsp->conn)) { DBG_INFO("copy chunk no access on IPC$ handle.\n"); return NT_STATUS_ACCESS_DENIED; } if (IS_PRINT(src_fsp->conn) || IS_PRINT(dst_fsp->conn)) { DBG_INFO("copy chunk no access on PRINT handle.\n"); return NT_STATUS_ACCESS_DENIED; } /* * [MS-SMB2] 3.3.5.15.6 Handling a Server-Side Data Copy Request * The server MUST fail the request with STATUS_ACCESS_DENIED if any of * the following are true: * - The Open.GrantedAccess of the destination file does not include * FILE_WRITE_DATA or FILE_APPEND_DATA. * * A non writable dst handle also doesn't make sense for other fsctls. */ if (!CHECK_WRITE(dst_fsp)) { DBG_INFO("dest handle not writable (%s).\n", smb_fname_str_dbg(dst_fsp->fsp_name)); return NT_STATUS_ACCESS_DENIED; } /* * - The Open.GrantedAccess of the destination file does not include * FILE_READ_DATA, and the CtlCode is FSCTL_SRV_COPYCHUNK. */ if ((fsctl == FSCTL_SRV_COPYCHUNK) && !CHECK_READ_IOCTL(dst_fsp)) { DBG_INFO("copy chunk no read on dest handle (%s).\n", smb_fname_str_dbg(dst_fsp->fsp_name)); return NT_STATUS_ACCESS_DENIED; } /* * - The Open.GrantedAccess of the source file does not include * FILE_READ_DATA access. */ if (!CHECK_READ_SMB2(src_fsp)) { DBG_INFO("src handle not readable (%s).\n", smb_fname_str_dbg(src_fsp->fsp_name)); return NT_STATUS_ACCESS_DENIED; } return NT_STATUS_OK; }