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/locking/leases_db.c | 724 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 724 insertions(+) create mode 100644 source3/locking/leases_db.c (limited to 'source3/locking/leases_db.c') diff --git a/source3/locking/leases_db.c b/source3/locking/leases_db.c new file mode 100644 index 0000000..eae58f5 --- /dev/null +++ b/source3/locking/leases_db.c @@ -0,0 +1,724 @@ +/* + Unix SMB/CIFS implementation. + Map lease keys to file ids + Copyright (C) Volker Lendecke 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 "system/filesys.h" +#include "locking/leases_db.h" +#include "dbwrap/dbwrap.h" +#include "dbwrap/dbwrap_open.h" +#include "util_tdb.h" +#include "ndr.h" +#include "librpc/gen_ndr/ndr_leases_db.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_LOCKING + +/* the leases database handle */ +static struct db_context *leases_db; + +bool leases_db_init(bool read_only) +{ + char *db_path; + + if (leases_db) { + return true; + } + + db_path = lock_path(talloc_tos(), "leases.tdb"); + if (db_path == NULL) { + return false; + } + + leases_db = db_open(NULL, db_path, + SMBD_VOLATILE_TDB_HASH_SIZE, + SMBD_VOLATILE_TDB_FLAGS | + TDB_SEQNUM, + read_only ? O_RDONLY : O_RDWR|O_CREAT, 0644, + DBWRAP_LOCK_ORDER_4, DBWRAP_FLAG_NONE); + TALLOC_FREE(db_path); + if (leases_db == NULL) { + DEBUG(1, ("ERROR: Failed to initialise leases database\n")); + return false; + } + + return true; +} + +struct leases_db_key_buf { + uint8_t buf[32]; +}; + +static TDB_DATA leases_db_key(struct leases_db_key_buf *buf, + const struct GUID *client_guid, + const struct smb2_lease_key *lease_key) +{ + struct leases_db_key db_key = { + .client_guid = *client_guid, + .lease_key = *lease_key }; + DATA_BLOB blob = { .data = buf->buf, .length = sizeof(buf->buf) }; + enum ndr_err_code ndr_err; + + if (DEBUGLEVEL >= 10) { + DBG_DEBUG("\n"); + NDR_PRINT_DEBUG(leases_db_key, &db_key); + } + + ndr_err = ndr_push_struct_into_fixed_blob( + &blob, &db_key, (ndr_push_flags_fn_t)ndr_push_leases_db_key); + SMB_ASSERT(NDR_ERR_CODE_IS_SUCCESS(ndr_err)); + + return (TDB_DATA) { .dptr = buf->buf, .dsize = sizeof(buf->buf) }; +} + +struct leases_db_do_locked_state { + void (*fn)(struct leases_db_value *value, + bool *modified, + void *private_data); + void *private_data; + NTSTATUS status; +}; + +static void leases_db_do_locked_fn( + struct db_record *rec, + TDB_DATA db_value, + void *private_data) +{ + struct leases_db_do_locked_state *state = private_data; + DATA_BLOB blob = { .data = db_value.dptr, .length = db_value.dsize }; + struct leases_db_value *value = NULL; + enum ndr_err_code ndr_err; + bool modified = false; + + value = talloc_zero(talloc_tos(), struct leases_db_value); + if (value == NULL) { + state->status = NT_STATUS_NO_MEMORY; + goto done; + } + + if (blob.length != 0) { + ndr_err = ndr_pull_struct_blob_all( + &blob, + value, + value, + (ndr_pull_flags_fn_t)ndr_pull_leases_db_value); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DBG_ERR("ndr_pull_struct_blob_failed: %s\n", + ndr_errstr(ndr_err)); + state->status = ndr_map_error2ntstatus(ndr_err); + goto done; + } + } + + state->fn(value, &modified, state->private_data); + + if (!modified) { + goto done; + } + + if (value->num_files == 0) { + state->status = dbwrap_record_delete(rec); + if (!NT_STATUS_IS_OK(state->status)) { + DBG_ERR("dbwrap_record_delete returned %s\n", + nt_errstr(state->status)); + } + goto done; + } + + ndr_err = ndr_push_struct_blob( + &blob, + value, + value, + (ndr_push_flags_fn_t)ndr_push_leases_db_value); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DBG_ERR("ndr_push_struct_blob_failed: %s\n", + ndr_errstr(ndr_err)); + state->status = ndr_map_error2ntstatus(ndr_err); + goto done; + } + + if (DEBUGLEVEL >= 10) { + DBG_DEBUG("\n"); + NDR_PRINT_DEBUG(leases_db_value, value); + } + + db_value = make_tdb_data(blob.data, blob.length); + + state->status = dbwrap_record_store(rec, db_value, 0); + if (!NT_STATUS_IS_OK(state->status)) { + DBG_ERR("dbwrap_record_store returned %s\n", + nt_errstr(state->status)); + } + +done: + TALLOC_FREE(value); +} + +static NTSTATUS leases_db_do_locked( + const struct GUID *client_guid, + const struct smb2_lease_key *lease_key, + void (*fn)(struct leases_db_value *value, + bool *modified, + void *private_data), + void *private_data) +{ + struct leases_db_key_buf keybuf; + TDB_DATA db_key = leases_db_key(&keybuf, client_guid, lease_key); + struct leases_db_do_locked_state state = { + .fn = fn, .private_data = private_data, + }; + NTSTATUS status; + + if (!leases_db_init(false)) { + return NT_STATUS_INTERNAL_ERROR; + } + + status = dbwrap_do_locked( + leases_db, db_key, leases_db_do_locked_fn, &state); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + return state.status; +} + +struct leases_db_add_state { + const struct file_id *id; + uint32_t current_state; + uint16_t lease_version; + uint16_t epoch; + const char *servicepath; + const char *base_name; + const char *stream_name; + NTSTATUS status; +}; + +static void leases_db_add_fn( + struct leases_db_value *value, bool *modified, void *private_data) +{ + struct leases_db_add_state *state = private_data; + struct leases_db_file *tmp = NULL; + uint32_t i; + + /* id must be unique. */ + for (i = 0; i < value->num_files; i++) { + if (file_id_equal(state->id, &value->files[i].id)) { + state->status = NT_STATUS_OBJECT_NAME_COLLISION; + return; + } + } + + if (value->num_files == 0) { + /* new record */ + value->current_state = state->current_state; + value->lease_version = state->lease_version; + value->epoch = state->epoch; + } + + tmp = talloc_realloc( + value, + value->files, + struct leases_db_file, + value->num_files + 1); + if (tmp == NULL) { + state->status = NT_STATUS_NO_MEMORY; + return; + } + value->files = tmp; + + value->files[value->num_files] = (struct leases_db_file) { + .id = *state->id, + .servicepath = state->servicepath, + .base_name = state->base_name, + .stream_name = state->stream_name, + }; + value->num_files += 1; + + *modified = true; +} + +NTSTATUS leases_db_add(const struct GUID *client_guid, + const struct smb2_lease_key *lease_key, + const struct file_id *id, + uint32_t current_state, + uint16_t lease_version, + uint16_t epoch, + const char *servicepath, + const char *base_name, + const char *stream_name) +{ + struct leases_db_add_state state = { + .id = id, + .current_state = current_state, + .lease_version = lease_version, + .epoch = epoch, + .servicepath = servicepath, + .base_name = base_name, + .stream_name = stream_name, + }; + NTSTATUS status; + + status = leases_db_do_locked( + client_guid, lease_key, leases_db_add_fn, &state); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("leases_db_do_locked failed: %s\n", + nt_errstr(status)); + return status; + } + return state.status; +} + +struct leases_db_del_state { + const struct file_id *id; + NTSTATUS status; +}; + +static void leases_db_del_fn( + struct leases_db_value *value, bool *modified, void *private_data) +{ + struct leases_db_del_state *state = private_data; + uint32_t i; + + for (i = 0; i < value->num_files; i++) { + if (file_id_equal(state->id, &value->files[i].id)) { + break; + } + } + if (i == value->num_files) { + state->status = NT_STATUS_NOT_FOUND; + return; + } + + value->files[i] = value->files[value->num_files-1]; + value->num_files -= 1; + + *modified = true; +} + +NTSTATUS leases_db_del(const struct GUID *client_guid, + const struct smb2_lease_key *lease_key, + const struct file_id *id) +{ + struct leases_db_del_state state = { .id = id }; + NTSTATUS status; + + status = leases_db_do_locked( + client_guid, lease_key, leases_db_del_fn, &state); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("leases_db_do_locked failed: %s\n", + nt_errstr(status)); + return status; + } + return state.status; +} + +struct leases_db_fetch_state { + void (*parser)(uint32_t num_files, + const struct leases_db_file *files, + void *private_data); + void *private_data; + NTSTATUS status; +}; + +static void leases_db_parser(TDB_DATA key, TDB_DATA data, void *private_data) +{ + struct leases_db_fetch_state *state = + (struct leases_db_fetch_state *)private_data; + DATA_BLOB blob = { .data = data.dptr, .length = data.dsize }; + enum ndr_err_code ndr_err; + struct leases_db_value *value; + + value = talloc(talloc_tos(), struct leases_db_value); + if (value == NULL) { + state->status = NT_STATUS_NO_MEMORY; + return; + } + + ndr_err = ndr_pull_struct_blob_all( + &blob, value, value, + (ndr_pull_flags_fn_t)ndr_pull_leases_db_value); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DEBUG(10, ("%s: ndr_pull_struct_blob_failed: %s\n", + __func__, ndr_errstr(ndr_err))); + TALLOC_FREE(value); + state->status = ndr_map_error2ntstatus(ndr_err); + return; + } + + if (DEBUGLEVEL >= 10) { + DEBUG(10, ("%s:\n", __func__)); + NDR_PRINT_DEBUG(leases_db_value, value); + } + + state->parser(value->num_files, + value->files, + state->private_data); + + TALLOC_FREE(value); + state->status = NT_STATUS_OK; +} + +NTSTATUS leases_db_parse(const struct GUID *client_guid, + const struct smb2_lease_key *lease_key, + void (*parser)(uint32_t num_files, + const struct leases_db_file *files, + void *private_data), + void *private_data) +{ + struct leases_db_key_buf keybuf; + TDB_DATA db_key = leases_db_key(&keybuf, client_guid, lease_key); + struct leases_db_fetch_state state; + NTSTATUS status; + + if (!leases_db_init(true)) { + return NT_STATUS_INTERNAL_ERROR; + } + + state = (struct leases_db_fetch_state) { + .parser = parser, + .private_data = private_data, + .status = NT_STATUS_OK + }; + + status = dbwrap_parse_record(leases_db, db_key, leases_db_parser, + &state); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + return state.status; +} + +struct leases_db_rename_state { + const struct file_id *id; + const char *servicename_new; + const char *filename_new; + const char *stream_name_new; + NTSTATUS status; +}; + +static void leases_db_rename_fn( + struct leases_db_value *value, bool *modified, void *private_data) +{ + struct leases_db_rename_state *state = private_data; + struct leases_db_file *file = NULL; + uint32_t i; + + /* id must exist. */ + for (i = 0; i < value->num_files; i++) { + if (file_id_equal(state->id, &value->files[i].id)) { + break; + } + } + if (i == value->num_files) { + state->status = NT_STATUS_NOT_FOUND; + return; + } + + file = &value->files[i]; + file->servicepath = state->servicename_new; + file->base_name = state->filename_new; + file->stream_name = state->stream_name_new; + + *modified = true; +} + +NTSTATUS leases_db_rename(const struct GUID *client_guid, + const struct smb2_lease_key *lease_key, + const struct file_id *id, + const char *servicename_new, + const char *filename_new, + const char *stream_name_new) +{ + struct leases_db_rename_state state = { + .id = id, + .servicename_new = servicename_new, + .filename_new = filename_new, + .stream_name_new = stream_name_new, + }; + NTSTATUS status; + + status = leases_db_do_locked( + client_guid, lease_key, leases_db_rename_fn, &state); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("leases_db_do_locked failed: %s\n", + nt_errstr(status)); + return status; + } + return state.status; +} + +struct leases_db_set_state { + uint32_t current_state; + bool breaking; + uint32_t breaking_to_requested; + uint32_t breaking_to_required; + uint16_t lease_version; + uint16_t epoch; +}; + +static void leases_db_set_fn( + struct leases_db_value *value, bool *modified, void *private_data) +{ + struct leases_db_set_state *state = private_data; + + if (value->num_files == 0) { + DBG_WARNING("leases_db_set on new entry\n"); + return; + } + value->current_state = state->current_state; + value->breaking = state->breaking; + value->breaking_to_requested = state->breaking_to_requested; + value->breaking_to_required = state->breaking_to_required; + value->lease_version = state->lease_version; + value->epoch = state->epoch; + *modified = true; +} + +NTSTATUS leases_db_set(const struct GUID *client_guid, + const struct smb2_lease_key *lease_key, + uint32_t current_state, + bool breaking, + uint32_t breaking_to_requested, + uint32_t breaking_to_required, + uint16_t lease_version, + uint16_t epoch) +{ + struct leases_db_set_state state = { + .current_state = current_state, + .breaking = breaking, + .breaking_to_requested = breaking_to_requested, + .breaking_to_required = breaking_to_required, + .lease_version = lease_version, + .epoch = epoch, + }; + NTSTATUS status; + + status = leases_db_do_locked( + client_guid, lease_key, leases_db_set_fn, &state); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("leases_db_do_locked failed: %s\n", + nt_errstr(status)); + return status; + } + return NT_STATUS_OK; +} + +struct leases_db_get_state { + const struct file_id *file_id; + uint32_t *current_state; + bool *breaking; + uint32_t *breaking_to_requested; + uint32_t *breaking_to_required; + uint16_t *lease_version; + uint16_t *epoch; + NTSTATUS status; +}; + +static void leases_db_get_fn(TDB_DATA key, TDB_DATA data, void *private_data) +{ + struct leases_db_get_state *state = private_data; + DATA_BLOB blob = { .data = data.dptr, .length = data.dsize }; + enum ndr_err_code ndr_err; + struct leases_db_value *value; + uint32_t i; + + value = talloc(talloc_tos(), struct leases_db_value); + if (value == NULL) { + state->status = NT_STATUS_NO_MEMORY; + return; + } + + ndr_err = ndr_pull_struct_blob_all( + &blob, value, value, + (ndr_pull_flags_fn_t)ndr_pull_leases_db_value); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DBG_ERR("ndr_pull_struct_blob_failed: %s\n", + ndr_errstr(ndr_err)); + TALLOC_FREE(value); + state->status = ndr_map_error2ntstatus(ndr_err); + return; + } + + if (DEBUGLEVEL >= 10) { + DBG_DEBUG("\n"); + NDR_PRINT_DEBUG(leases_db_value, value); + } + + /* id must exist. */ + for (i = 0; i < value->num_files; i++) { + if (file_id_equal(state->file_id, &value->files[i].id)) { + break; + } + } + + if (i == value->num_files) { + state->status = NT_STATUS_NOT_FOUND; + TALLOC_FREE(value); + return; + } + + if (state->current_state != NULL) { + *state->current_state = value->current_state; + }; + if (state->breaking != NULL) { + *state->breaking = value->breaking; + }; + if (state->breaking_to_requested != NULL) { + *state->breaking_to_requested = value->breaking_to_requested; + }; + if (state->breaking_to_required != NULL) { + *state->breaking_to_required = value->breaking_to_required; + }; + if (state->lease_version != NULL) { + *state->lease_version = value->lease_version; + }; + if (state->epoch != NULL) { + *state->epoch = value->epoch; + }; + + TALLOC_FREE(value); + state->status = NT_STATUS_OK; +} + +NTSTATUS leases_db_get(const struct GUID *client_guid, + const struct smb2_lease_key *lease_key, + const struct file_id *file_id, + uint32_t *current_state, + bool *breaking, + uint32_t *breaking_to_requested, + uint32_t *breaking_to_required, + uint16_t *lease_version, + uint16_t *epoch) +{ + struct leases_db_get_state state = { + .file_id = file_id, + .current_state = current_state, + .breaking = breaking, + .breaking_to_requested = breaking_to_requested, + .breaking_to_required = breaking_to_required, + .lease_version = lease_version, + .epoch = epoch, + }; + struct leases_db_key_buf keybuf; + TDB_DATA db_key = leases_db_key(&keybuf, client_guid, lease_key); + NTSTATUS status; + + if (!leases_db_init(true)) { + return NT_STATUS_INTERNAL_ERROR; + } + + status = dbwrap_parse_record( + leases_db, db_key, leases_db_get_fn, &state); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + return state.status; +} + +struct leases_db_get_current_state_state { + int seqnum; + uint32_t current_state; + NTSTATUS status; +}; + +/* + * This function is an optimization that + * relies on the fact that the + * smb2_lease_state current_state + * (which is a uint32_t size) + * from struct leases_db_value is the first + * entry in the ndr-encoded struct leases_db_value. + * Read it without having to ndr decode all + * the values in struct leases_db_value. + */ + +static void leases_db_get_current_state_fn( + TDB_DATA key, TDB_DATA data, void *private_data) +{ + struct leases_db_get_current_state_state *state = private_data; + struct ndr_pull ndr; + enum ndr_err_code ndr_err; + + if (data.dsize < sizeof(uint32_t)) { + state->status = NT_STATUS_INTERNAL_DB_CORRUPTION; + return; + } + + state->seqnum = dbwrap_get_seqnum(leases_db); + + ndr = (struct ndr_pull) { + .data = data.dptr, .data_size = data.dsize, + }; + ndr_err = ndr_pull_uint32(&ndr, NDR_SCALARS, &state->current_state); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + state->status = ndr_map_error2ntstatus(ndr_err); + } +} + +NTSTATUS leases_db_get_current_state( + const struct GUID *client_guid, + const struct smb2_lease_key *lease_key, + int *database_seqnum, + uint32_t *current_state) +{ + struct leases_db_get_current_state_state state = { 0 }; + struct leases_db_key_buf keybuf; + TDB_DATA db_key = { 0 }; + NTSTATUS status; + + if (!leases_db_init(true)) { + return NT_STATUS_INTERNAL_ERROR; + } + + state.seqnum = dbwrap_get_seqnum(leases_db); + if (*database_seqnum == state.seqnum) { + return NT_STATUS_OK; + } + + db_key = leases_db_key(&keybuf, client_guid, lease_key); + + status = dbwrap_parse_record( + leases_db, db_key, leases_db_get_current_state_fn, &state); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + *database_seqnum = state.seqnum; + *current_state = state.current_state; + + return NT_STATUS_OK; +} + +NTSTATUS leases_db_copy_file_ids(TALLOC_CTX *mem_ctx, + uint32_t num_files, + const struct leases_db_file *files, + struct file_id **pp_ids) +{ + uint32_t i; + struct file_id *ids = talloc_array(mem_ctx, + struct file_id, + num_files); + if (ids == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i = 0; i < num_files; i++) { + ids[i] = files[i].id; + } + *pp_ids = ids; + return NT_STATUS_OK; +} -- cgit v1.2.3