/*
   Unix SMB/CIFS implementation.

   Copyright (C) Stefan Metzmacher 2014

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "includes.h"
#include "system/filesys.h"
#include <tevent.h>
#include "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"

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 *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("smbXsrv_client_global.tdb");
	if (global_path == NULL) {
		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) {
		NTSTATUS status;

		status = map_nt_error_from_unix_common(errno);

		return status;
	}

	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;
	DATA_BLOB b;

	status = GUID_to_ndr_blob(client_guid, talloc_tos(), &b);
	if (!NT_STATUS_IS_OK(status)) {
		return key;
	}
	memcpy(key_buf, b.data, SMBXSRV_CLIENT_GLOBAL_TDB_KEY_SIZE);
	data_blob_free(&b);

	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) {
		DBG_DEBUG("Failed to lock guid [%s], key '%s'\n",
			  GUID_string(talloc_tos(), client_guid),
			  hex_encode_talloc(talloc_tos(), key.dptr, key.dsize));
	}

	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;

	dbwrap_watch_db(table->global.db_ctx, msg_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,
					struct smbXsrv_client_global0 **_g)
{
	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 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_client_globalB);
	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
		NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
		DBG_WARNING("smbXsrv_client_global_verify_record: "
			    "key '%s' ndr_pull_struct_blob - %s\n",
			    hex_encode_talloc(frame, key.dptr, key.dsize),
			    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' use unsupported version %u\n",
			hex_encode_talloc(frame, key.dptr, key.dsize),
			global_blob.version);
		NDR_PRINT_DEBUG(smbXsrv_client_globalB, &global_blob);
		TALLOC_FREE(frame);
		return;
	}

	global = global_blob.info.info0;

	exists = serverid_exists(&global->server_id);
	if (!exists) {
		struct server_id_buf tmp;

		DBG_NOTICE("key '%s' server_id %s does not exist.\n",
			   hex_encode_talloc(frame, key.dptr, key.dsize),
			   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);
	}
	TALLOC_FREE(frame);
}

NTSTATUS smb2srv_client_lookup_global(struct smbXsrv_client *client,
				      struct GUID client_guid,
				      TALLOC_CTX *mem_ctx,
				      struct smbXsrv_client_global0 **_global)
{
	struct smbXsrv_client_table *table = client->table;
	struct smbXsrv_client_global0 *global = NULL;
	bool is_free = false;
	struct db_record *db_rec;

	db_rec = smbXsrv_client_global_fetch_locked(table->global.db_ctx,
						    &client_guid,
						    talloc_tos());
	if (db_rec == NULL) {
		return NT_STATUS_INTERNAL_DB_ERROR;
	}

	smbXsrv_client_global_verify_record(db_rec,
					    &is_free,
					    NULL,
					    mem_ctx,
					    &global);
	TALLOC_FREE(db_rec);

	if (is_free) {
		return NT_STATUS_OBJECTID_NOT_FOUND;
	}

	*_global = global;
	return NT_STATUS_OK;
}

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.initial_connect_time = global->initial_connect_time;
	pass_info0.client_guid = global->client_guid;

	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->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 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.
	 */

	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",
			hex_encode_talloc(global->db_rec, key.dptr, key.dsize),
			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",
			hex_encode_talloc(global->db_rec, key.dptr, key.dsize),
			nt_errstr(status));
		TALLOC_FREE(global->db_rec);
		return status;
	}

	global->stored = true;

	if (DEBUGLVL(DBGLVL_DEBUG)) {
		DBG_DEBUG("key '%s' stored\n",
			hex_encode_talloc(global->db_rec, key.dptr, key.dsize));
		NDR_PRINT_DEBUG(smbXsrv_client_globalB, &global_blob);
	}

	TALLOC_FREE(global->db_rec);

	return NT_STATUS_OK;
}

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",
			hex_encode_talloc(global->db_rec, key.dptr, key.dsize),
			nt_errstr(status));
		TALLOC_FREE(global->db_rec);
		return status;
	}
	global->stored = false;
	DBG_DEBUG("key '%s' delete\n",
		  hex_encode_talloc(global->db_rec, key.dptr, key.dsize));

	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);

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->ev_ctx = ev_ctx;
	client->msg_ctx = msg_ctx;

	client->server_multi_channel_enabled = lp_server_multi_channel_support();

	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;

		ZERO_STRUCT(client_blob);
		client_blob.version = SMBXSRV_VERSION_0;
		client_blob.info.info0 = client;

		DBG_DEBUG("client_guid[%s] stored\n",
			  GUID_string(talloc_tos(), &global->client_guid));
		NDR_PRINT_DEBUG(smbXsrv_clientB, &client_blob);
	}

	subreq = messaging_filtered_read_send(client, client->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 = client;
	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;
	}

	if (rec->buf.length < SMB2_HDR_BODY) {
		return false;
	}

	/* TODO: verify client_guid...? */

	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;

	ret = messaging_filtered_read_recv(subreq, talloc_tos(), &rec);
	TALLOC_FREE(subreq);
	if (ret != 0) {
		goto next;
	}

	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;
	}

	DBG_DEBUG("MSG_SMBXSRV_CLIENT_CLOSE\n");
	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))
	{
		DBG_WARNING("client's client_guid [%s] != passed guid [%s]\n",
			GUID_string(talloc_tos(), &client->global->client_guid),
			GUID_string(talloc_tos(), &pass_info0->client_guid));
		if (DEBUGLVL(DBGLVL_WARNING)) {
			NDR_PRINT_DEBUG(smbXsrv_connection_passB, &pass_blob);
		}
		goto next;
	}

	if (client->global->initial_connect_time !=
	    pass_info0->initial_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->initial_connect_time),
			(unsigned long long)pass_info0->initial_connect_time);
		if (DEBUGLVL(DBGLVL_WARNING)) {
			NDR_PRINT_DEBUG(smbXsrv_connection_passB, &pass_blob);
		}
		goto next;
	}

	SMB_ASSERT(rec->num_fds == 1);
	sock_fd = rec->fds[0];

	DBG_ERR("got connection sockfd[%d]\n", sock_fd);
	NDR_PRINT_DEBUG(smbXsrv_connection_passB, &pass_blob);
	status = smbd_add_connection(client, sock_fd, &xconn);
	if (!NT_STATUS_IS_OK(status)) {
		close(sock_fd);
		sock_fd = -1;
		DBG_ERR("smbd_add_connection => %s\n", nt_errstr(status));
		NDR_PRINT_DEBUG(smbXsrv_connection_passB, &pass_blob);
		goto next;
	}

	/*
	 * 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:
	TALLOC_FREE(rec);

	subreq = messaging_filtered_read_send(client, client->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);
}

NTSTATUS smbXsrv_client_update(struct smbXsrv_client *client)
{
	struct smbXsrv_client_table *table = client->table;
	NTSTATUS status;

	if (client->global->db_rec != NULL) {
		DBG_ERR("guid [%s]: Called with db_rec != NULL'\n",
			GUID_string(talloc_tos(),
			&client->global->client_guid));
		return NT_STATUS_INTERNAL_ERROR;
	}

	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_store(client->global);
	if (!NT_STATUS_IS_OK(status)) {
		DBG_ERR("client_guid[%s] store failed - %s\n",
			GUID_string(talloc_tos(), &client->global->client_guid),
			nt_errstr(status));
		return status;
	}

	if (DEBUGLVL(DBGLVL_DEBUG)) {
		struct smbXsrv_clientB client_blob;

		ZERO_STRUCT(client_blob);
		client_blob.version = SMBXSRV_VERSION_0;
		client_blob.info.info0 = client;

		DBG_DEBUG("client_guid[%s] stored\n",
			GUID_string(talloc_tos(), &client->global->client_guid));
		NDR_PRINT_DEBUG(smbXsrv_clientB, &client_blob);
	}

	return NT_STATUS_OK;
}

NTSTATUS smbXsrv_client_remove(struct smbXsrv_client *client)
{
	struct smbXsrv_client_table *table = client->table;
	NTSTATUS status;

	if (client->global->db_rec != NULL) {
		DBG_ERR("client_guid[%s]: Called with db_rec != NULL'\n",
			GUID_string(talloc_tos(), &client->global->client_guid));
		return NT_STATUS_INTERNAL_ERROR;
	}

	if (!client->global->stored) {
		return NT_STATUS_OK;
	}

	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)) {
		DBG_ERR("client_guid[%s] store failed - %s\n",
			GUID_string(talloc_tos(), &client->global->client_guid),
			nt_errstr(status));
		return status;
	}

	if (DEBUGLVL(DBGLVL_DEBUG)) {
		struct smbXsrv_clientB client_blob;

		ZERO_STRUCT(client_blob);
		client_blob.version = SMBXSRV_VERSION_0;
		client_blob.info.info0 = client;

		DBG_DEBUG("client_guid[%s] stored\n",
			GUID_string(talloc_tos(), &client->global->client_guid));
		NDR_PRINT_DEBUG(smbXsrv_clientB, &client_blob);
	}

	return NT_STATUS_OK;
}
