/*
   Unix SMB/CIFS implementation.

   Copyright (C) Guenther Deschner <gd@samba.org> 2008

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

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

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

#include "includes.h"
#include "system/passwd.h"
#include "libnet/libnet_dssync.h"
#include "../libcli/security/security.h"
#include "../libds/common/flags.h"
#include "../librpc/gen_ndr/ndr_drsuapi.h"
#include "util_tdb.h"
#include "dbwrap/dbwrap.h"
#include "dbwrap/dbwrap_rbt.h"
#include "../libds/common/flag_mapping.h"
#include "passdb.h"
#include "lib/util/base64.h"

/****************************************************************
****************************************************************/

struct dssync_passdb {
	struct pdb_methods *methods;
	struct db_context *all;
	struct db_context *aliases;
	struct db_context *groups;
};

struct dssync_passdb_obj {
	struct dssync_passdb_obj *self;
	uint32_t type;
	struct drsuapi_DsReplicaObjectListItemEx *cur;
	TDB_DATA key;
	TDB_DATA data;
	struct db_context *members;
};

struct dssync_passdb_mem {
	struct dssync_passdb_mem *self;
	bool active;
	struct drsuapi_DsReplicaObjectIdentifier3 *cur;
	struct dssync_passdb_obj *obj;
	TDB_DATA key;
	TDB_DATA data;
};

static NTSTATUS dssync_insert_obj(struct dssync_passdb *pctx,
				  struct db_context *db,
				  struct dssync_passdb_obj *obj)
{
	NTSTATUS status;
	struct db_record *rec;
	TDB_DATA value;

	rec = dbwrap_fetch_locked(db, talloc_tos(), obj->key);
	if (rec == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	value = dbwrap_record_get_value(rec);
	if (value.dsize != 0) {
		abort();
	}

	status = dbwrap_record_store(rec, obj->data, TDB_INSERT);
	if (!NT_STATUS_IS_OK(status)) {
		TALLOC_FREE(rec);
		return status;
	}
	TALLOC_FREE(rec);
	return NT_STATUS_OK;
}

static struct dssync_passdb_obj *dssync_parse_obj(const TDB_DATA data)
{
	struct dssync_passdb_obj *obj;

	if (data.dsize != sizeof(obj)) {
		return NULL;
	}

	/*
	 * we need to copy the pointer to avoid alignment problems
	 * on some systems.
	 */
	memcpy(&obj, data.dptr, sizeof(obj));

	return talloc_get_type_abort(obj, struct dssync_passdb_obj);
}

static struct dssync_passdb_obj *dssync_search_obj_by_guid(struct dssync_passdb *pctx,
							   struct db_context *db,
							   const struct GUID *guid)
{
	struct dssync_passdb_obj *obj;
	TDB_DATA key;
	TDB_DATA data;
	NTSTATUS status;

	key = make_tdb_data((const uint8_t *)(const void *)guid,
			     sizeof(*guid));

	status = dbwrap_fetch(db, talloc_tos(), key, &data);
	if (!NT_STATUS_IS_OK(status)) {
		return NULL;
	}

	obj = dssync_parse_obj(data);
	return obj;
}

static NTSTATUS dssync_create_obj(struct dssync_passdb *pctx,
				  struct db_context *db,
				  uint32_t type,
				  struct drsuapi_DsReplicaObjectListItemEx *cur,
				  struct dssync_passdb_obj **_obj)
{
	NTSTATUS status;
	struct dssync_passdb_obj *obj;

	obj = talloc_zero(pctx, struct dssync_passdb_obj);
	if (obj == NULL) {
		return NT_STATUS_NO_MEMORY;
	}
	obj->self = obj;
	obj->cur = cur;
	obj->type = type;
	obj->key = make_tdb_data((const uint8_t *)(void *)&cur->object.identifier->guid,
				   sizeof(cur->object.identifier->guid));
	obj->data = make_tdb_data((const uint8_t *)(void *)&obj->self,
				  sizeof(obj->self));

	obj->members = db_open_rbt(obj);
	if (obj->members == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	status = dssync_insert_obj(pctx, db, obj);
	if (!NT_STATUS_IS_OK(status)) {
		TALLOC_FREE(obj);
		return status;
	}
	*_obj = obj;
	return NT_STATUS_OK;
}

static NTSTATUS dssync_insert_mem(struct dssync_passdb *pctx,
				  struct dssync_passdb_obj *obj,
				  struct dssync_passdb_mem *mem)
{
	NTSTATUS status;
	struct db_record *rec;
	TDB_DATA value;

	rec = dbwrap_fetch_locked(obj->members, talloc_tos(), mem->key);
	if (rec == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	value = dbwrap_record_get_value(rec);
	if (value.dsize != 0) {
		abort();
	}

	status = dbwrap_record_store(rec, mem->data, TDB_INSERT);
	if (!NT_STATUS_IS_OK(status)) {
		TALLOC_FREE(rec);
		return status;
	}
	TALLOC_FREE(rec);
	return NT_STATUS_OK;
}

static NTSTATUS dssync_create_mem(struct dssync_passdb *pctx,
				  struct dssync_passdb_obj *obj,
				  bool active,
				  struct drsuapi_DsReplicaObjectIdentifier3 *cur,
				  struct dssync_passdb_mem **_mem)
{
	NTSTATUS status;
	struct dssync_passdb_mem *mem;

	mem = talloc_zero(pctx, struct dssync_passdb_mem);
	if (mem == NULL) {
		return NT_STATUS_NO_MEMORY;
	}
	mem->self = mem;
	mem->cur = cur;
	mem->active = active;
	mem->obj = NULL;
	mem->key = make_tdb_data((const uint8_t *)(void *)&cur->guid,
				   sizeof(cur->guid));
	mem->data = make_tdb_data((const uint8_t *)(void *)&mem->self,
				  sizeof(mem->self));

	status = dssync_insert_mem(pctx, obj, mem);
	if (!NT_STATUS_IS_OK(status)) {
		TALLOC_FREE(obj);
		return status;
	}
	*_mem = mem;
	return NT_STATUS_OK;
}

static struct dssync_passdb_mem *dssync_parse_mem(const TDB_DATA data)
{
	struct dssync_passdb_mem *mem;

	if (data.dsize != sizeof(mem)) {
		return NULL;
	}

	/*
	 * we need to copy the pointer to avoid alignment problems
	 * on some systems.
	 */
	memcpy(&mem, data.dptr, sizeof(mem));

	return talloc_get_type_abort(mem, struct dssync_passdb_mem);
}

static NTSTATUS passdb_startup(struct dssync_context *ctx, TALLOC_CTX *mem_ctx,
			       struct replUpToDateVectorBlob **pold_utdv)
{
	NTSTATUS status;
	struct dssync_passdb *pctx;

	pctx = talloc_zero(mem_ctx, struct dssync_passdb);
	if (pctx == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	if (ctx->output_filename) {
		status = make_pdb_method_name(&pctx->methods, ctx->output_filename);
	} else {
		status = make_pdb_method_name(&pctx->methods, lp_passdb_backend());
	}

	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	pctx->all = db_open_rbt(pctx);
	if (pctx->all == NULL) {
		return NT_STATUS_NO_MEMORY;
	}
	pctx->aliases = db_open_rbt(pctx);
	if (pctx->aliases == NULL) {
		return NT_STATUS_NO_MEMORY;
	}
	pctx->groups = db_open_rbt(pctx);
	if (pctx->groups == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	ctx->private_data = pctx;

	return status;
}

/****************************************************************
****************************************************************/

struct dssync_passdb_traverse_amembers {
	struct dssync_context *ctx;
	struct dssync_passdb_obj *obj;
	const char *name;
	uint32_t idx;
};

struct dssync_passdb_traverse_aliases {
	struct dssync_context *ctx;
	const char *name;
	uint32_t idx;
};

static int dssync_passdb_traverse_amembers(struct db_record *rec,
					   void *private_data)
{
	struct dssync_passdb_traverse_amembers *state =
		(struct dssync_passdb_traverse_amembers *)private_data;
	struct dssync_passdb *pctx =
		talloc_get_type_abort(state->ctx->private_data,
		struct dssync_passdb);
	struct dssync_passdb_mem *mem;
	NTSTATUS status;
	struct dom_sid alias_sid;
	struct dom_sid member_sid;
	struct dom_sid_buf buf1, buf2;
	const char *member_dn;
	size_t num_members;
	size_t i;
	struct dom_sid *members;
	bool is_member = false;
	const char *action;
	TDB_DATA value;

	state->idx++;

	alias_sid = state->obj->cur->object.identifier->sid;

	value = dbwrap_record_get_value(rec);
	mem = dssync_parse_mem(value);
	if (mem == NULL) {
		return -1;
	}

	member_sid = mem->cur->sid;
	member_dn = mem->cur->dn;

	mem->obj = dssync_search_obj_by_guid(pctx, pctx->all, &mem->cur->guid);
	if (mem->obj == NULL) {
		DEBUG(0,("alias[%s] member[%s] can't resolve member - ignoring\n",
			 dom_sid_str_buf(&alias_sid, &buf1),
			 is_null_sid(&member_sid)?
			 dom_sid_str_buf(&member_sid, &buf2):
			 member_dn));
		return 0;
	}

	switch (mem->obj->type) {
	case ATYPE_DISTRIBUTION_LOCAL_GROUP:
	case ATYPE_DISTRIBUTION_GLOBAL_GROUP:
		DEBUG(0, ("alias[%s] ignore distribution group [%s]\n",
			  dom_sid_str_buf(&alias_sid, &buf1),
			  member_dn));
		return 0;
	default:
		break;
	}

	DEBUG(0,("alias[%s] member[%s]\n",
		 dom_sid_str_buf(&alias_sid, &buf1),
		 dom_sid_str_buf(&member_sid, &buf2)));

	status = pdb_enum_aliasmem(&alias_sid, talloc_tos(),
				   &members, &num_members);
	if (!NT_STATUS_IS_OK(status)) {
		DEBUG(0, ("Could not find current alias members %s - %s\n",
			  dom_sid_str_buf(&alias_sid, &buf1),
			  nt_errstr(status)));
		return -1;
	}

	for (i=0; i < num_members; i++) {
		bool match;

		match = dom_sid_equal(&members[i], &member_sid);
		if (match) {
			is_member = true;
			break;
		}
	}

	status = NT_STATUS_OK;
	action = "none";
	if (!is_member && mem->active) {
		action = "add";
		pdb_add_aliasmem(&alias_sid, &member_sid);
	} else if (is_member && !mem->active) {
		action = "delete";
		pdb_del_aliasmem(&alias_sid, &member_sid);
	}
	if (!NT_STATUS_IS_OK(status)) {
		DEBUG(0, ("Could not %s %s as alias members of %s - %s\n",
			  action,
			  dom_sid_str_buf(&member_sid, &buf1),
			  dom_sid_str_buf(&alias_sid, &buf2),
			  nt_errstr(status)));
		return -1;
	}

	return 0;
}

static int dssync_passdb_traverse_aliases(struct db_record *rec,
					  void *private_data)
{
	struct dssync_passdb_traverse_aliases *state =
		(struct dssync_passdb_traverse_aliases *)private_data;
	struct dssync_passdb *pctx =
		talloc_get_type_abort(state->ctx->private_data,
		struct dssync_passdb);
	struct dssync_passdb_traverse_amembers mstate;
	struct dssync_passdb_obj *obj;
	TDB_DATA value;
	NTSTATUS status;

	state->idx++;
	if (pctx->methods == NULL) {
		return -1;
	}

	value = dbwrap_record_get_value(rec);
	obj = dssync_parse_obj(value);
	if (obj == NULL) {
		return -1;
	}

	ZERO_STRUCT(mstate);
	mstate.ctx = state->ctx;
	mstate.name = "members";
	mstate.obj = obj;
	status = dbwrap_traverse_read(obj->members,
				      dssync_passdb_traverse_amembers,
				      &mstate, NULL);
	if (!NT_STATUS_IS_OK(status)) {
		return -1;
	}

	return 0;
}

struct dssync_passdb_traverse_gmembers {
	struct dssync_context *ctx;
	struct dssync_passdb_obj *obj;
	const char *name;
	uint32_t idx;
};

struct dssync_passdb_traverse_groups {
	struct dssync_context *ctx;
	const char *name;
	uint32_t idx;
};

static int dssync_passdb_traverse_gmembers(struct db_record *rec,
					   void *private_data)
{
	struct dssync_passdb_traverse_gmembers *state =
		(struct dssync_passdb_traverse_gmembers *)private_data;
	struct dssync_passdb *pctx =
		talloc_get_type_abort(state->ctx->private_data,
		struct dssync_passdb);
	struct dssync_passdb_mem *mem;
	NTSTATUS status;
	char *nt_member = NULL;
	char **unix_members;
	struct dom_sid group_sid;
	struct dom_sid member_sid;
	struct dom_sid_buf buf1, buf2;
	struct samu *member = NULL;
	const char *member_dn = NULL;
	GROUP_MAP *map;
	struct group *grp;
	uint32_t rid;
	bool is_unix_member = false;
	TDB_DATA value;

	state->idx++;

	group_sid = state->obj->cur->object.identifier->sid;

	status = dom_sid_split_rid(talloc_tos(), &group_sid, NULL, &rid);
	if (!NT_STATUS_IS_OK(status)) {
		return -1;
	}

	value = dbwrap_record_get_value(rec);

	mem = dssync_parse_mem(value);
	if (mem == NULL) {
		return -1;
	}

	member_sid = mem->cur->sid;
	member_dn = mem->cur->dn;

	mem->obj = dssync_search_obj_by_guid(pctx, pctx->all, &mem->cur->guid);
	if (mem->obj == NULL) {
		DEBUG(0,("group[%s] member[%s] can't resolve member - ignoring\n",
			 dom_sid_str_buf(&group_sid, &buf1),
			 is_null_sid(&member_sid)?
			 dom_sid_str_buf(&member_sid, &buf2):
			 member_dn));
		return 0;
	}

	member_sid = mem->obj->cur->object.identifier->sid;
	member_dn = mem->obj->cur->object.identifier->dn;

	switch (mem->obj->type) {
	case ATYPE_SECURITY_LOCAL_GROUP:
	case ATYPE_SECURITY_GLOBAL_GROUP:
		DEBUG(0, ("Group[%s] ignore member group [%s]\n",
			  dom_sid_str_buf(&group_sid, &buf1),
			  dom_sid_str_buf(&member_sid, &buf2)));
		return 0;

	case ATYPE_DISTRIBUTION_LOCAL_GROUP:
	case ATYPE_DISTRIBUTION_GLOBAL_GROUP:
		DEBUG(0, ("Group[%s] ignore distribution group [%s]\n",
			  dom_sid_str_buf(&group_sid, &buf1),
			  member_dn));
		return 0;
	default:
		break;
	}

	map = talloc_zero(NULL, GROUP_MAP);
	if (!map) {
		return -1;
	}

	if (!get_domain_group_from_sid(group_sid, map)) {
		DEBUG(0, ("Could not find global group %s\n",
			  dom_sid_str_buf(&group_sid, &buf1)));
		//return NT_STATUS_NO_SUCH_GROUP;
		TALLOC_FREE(map);
		return -1;
	}

	if (!(grp = getgrgid(map->gid))) {
		DEBUG(0, ("Could not find unix group %lu\n",
						(unsigned long)map->gid));
		//return NT_STATUS_NO_SUCH_GROUP;
		TALLOC_FREE(map);
		return -1;
	}

	TALLOC_FREE(map);

	DEBUG(0,("Group members of %s: ", grp->gr_name));

	if ( !(member = samu_new(talloc_tos())) ) {
		//return NT_STATUS_NO_MEMORY;
		return -1;
	}

	if (!pdb_getsampwsid(member, &member_sid)) {
		DEBUG(1, ("Found bogus group member: (member_sid=%s group=%s)\n",
			  dom_sid_str_buf(&member_sid, &buf1),
			  grp->gr_name));
		TALLOC_FREE(member);
		return -1;
	}

	if (pdb_get_group_rid(member) == rid) {
		DEBUGADD(0,("%s(primary),", pdb_get_username(member)));
		TALLOC_FREE(member);
		return -1;
	}

	DEBUGADD(0,("%s,", pdb_get_username(member)));
	nt_member = talloc_strdup(talloc_tos(), pdb_get_username(member));
	TALLOC_FREE(member);

	DEBUGADD(0,("\n"));

	unix_members = grp->gr_mem;

	while (*unix_members) {
		if (strcmp(*unix_members, nt_member) == 0) {
			is_unix_member = true;
			break;
		}
		unix_members += 1;
	}

	if (!is_unix_member && mem->active) {
		smb_add_user_group(grp->gr_name, nt_member);
	} else if (is_unix_member && !mem->active) {
		smb_delete_user_group(grp->gr_name, nt_member);
	}

	return 0;
}

static int dssync_passdb_traverse_groups(struct db_record *rec,
					 void *private_data)
{
	struct dssync_passdb_traverse_groups *state =
		(struct dssync_passdb_traverse_groups *)private_data;
	struct dssync_passdb *pctx =
		talloc_get_type_abort(state->ctx->private_data,
		struct dssync_passdb);
	struct dssync_passdb_traverse_gmembers mstate;
	struct dssync_passdb_obj *obj;
	TDB_DATA value;
	NTSTATUS status;

	state->idx++;
	if (pctx->methods == NULL) {
		return -1;
	}

	value = dbwrap_record_get_value(rec);

	obj = dssync_parse_obj(value);
	if (obj == NULL) {
		return -1;
	}

	ZERO_STRUCT(mstate);
	mstate.ctx = state->ctx;
	mstate.name = "members";
	mstate.obj = obj;
	status = dbwrap_traverse_read(obj->members,
				      dssync_passdb_traverse_gmembers,
				      &mstate, NULL);
	if (!NT_STATUS_IS_OK(status)) {
		return -1;
	}

	return 0;
}

static NTSTATUS passdb_finish(struct dssync_context *ctx, TALLOC_CTX *mem_ctx,
			      struct replUpToDateVectorBlob *new_utdv)
{
	struct dssync_passdb *pctx =
		talloc_get_type_abort(ctx->private_data,
		struct dssync_passdb);
	struct dssync_passdb_traverse_aliases astate;
	struct dssync_passdb_traverse_groups gstate;
	NTSTATUS status;

	ZERO_STRUCT(astate);
	astate.ctx = ctx;
	astate.name = "aliases";
	status = dbwrap_traverse_read(pctx->aliases,
				      dssync_passdb_traverse_aliases,
				      &astate, NULL);
	if (!NT_STATUS_IS_OK(status)) {
		return NT_STATUS_INTERNAL_ERROR;
	}

	ZERO_STRUCT(gstate);
	gstate.ctx = ctx;
	gstate.name = "groups";
	status = dbwrap_traverse_read(pctx->groups,
				      dssync_passdb_traverse_groups,
				      &gstate, NULL);
	if (!NT_STATUS_IS_OK(status)) {
		return NT_STATUS_INTERNAL_ERROR;
	}

	TALLOC_FREE(pctx->methods);
	TALLOC_FREE(pctx);

	return NT_STATUS_OK;
}

/****************************************************************
****************************************************************/

static NTSTATUS smb_create_user(TALLOC_CTX *mem_ctx,
				uint32_t acct_flags,
				const char *account,
				struct passwd **passwd_p)
{
	struct passwd *passwd;
	char *add_script = NULL;

	passwd = Get_Pwnam_alloc(mem_ctx, account);
	if (passwd) {
		*passwd_p = passwd;
		return NT_STATUS_OK;
	}

	/* Create appropriate user */
	if (acct_flags & ACB_NORMAL) {
		add_script = lp_add_user_script(mem_ctx);
	} else if ( (acct_flags & ACB_WSTRUST) ||
		    (acct_flags & ACB_SVRTRUST) ||
		    (acct_flags & ACB_DOMTRUST) ) {
		add_script = lp_add_machine_script(mem_ctx);
	} else {
		DEBUG(1, ("Unknown user type: %s\n",
			  pdb_encode_acct_ctrl(acct_flags, NEW_PW_FORMAT_SPACE_PADDED_LEN)));
		return NT_STATUS_UNSUCCESSFUL;
	}

	if (!add_script) {
		return NT_STATUS_NO_MEMORY;
	}

	if (*add_script) {
		int add_ret;
		add_script = talloc_all_string_sub(mem_ctx, add_script,
						   "%u", account);
		if (!add_script) {
			return NT_STATUS_NO_MEMORY;
		}
		add_ret = smbrun(add_script, NULL, NULL);
		DEBUG(add_ret ? 0 : 1,("fetch_account: Running the command `%s' "
			 "gave %d\n", add_script, add_ret));
		if (add_ret == 0) {
			smb_nscd_flush_user_cache();
		}
	}

	/* try and find the possible unix account again */
	passwd = Get_Pwnam_alloc(mem_ctx, account);
	if (!passwd) {
		return NT_STATUS_NO_SUCH_USER;
	}

	*passwd_p = passwd;

	return NT_STATUS_OK;
}

static struct drsuapi_DsReplicaAttribute *find_drsuapi_attr(
			const struct drsuapi_DsReplicaObjectListItemEx *cur,
			uint32_t attid)
{
	int i = 0;

	for (i = 0; i < cur->object.attribute_ctr.num_attributes; i++) {
		struct drsuapi_DsReplicaAttribute *attr;

		attr = &cur->object.attribute_ctr.attributes[i];

		if (attr->attid == attid) {
			return attr;
		}
	}

	return NULL;
}

static NTSTATUS find_drsuapi_attr_string(TALLOC_CTX *mem_ctx,
					 const struct drsuapi_DsReplicaObjectListItemEx *cur,
					 uint32_t attid,
					 uint32_t *_count,
					 char ***_array)
{
	struct drsuapi_DsReplicaAttribute *attr;
	char **array;
	uint32_t a;

	attr = find_drsuapi_attr(cur, attid);
	if (attr == NULL) {
		return NT_STATUS_PROPSET_NOT_FOUND;
	}

	array = talloc_array(mem_ctx, char *, attr->value_ctr.num_values);
	if (array == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	for (a = 0; a < attr->value_ctr.num_values; a++) {
		const DATA_BLOB *blob;
		ssize_t ret;

		blob = attr->value_ctr.values[a].blob;

		if (blob == NULL) {
			return NT_STATUS_INTERNAL_DB_CORRUPTION;
		}

		ret = pull_string_talloc(array, NULL, 0, &array[a],
					 blob->data, blob->length,
					 STR_UNICODE);
		if (ret == -1) {
			//TODO
			return NT_STATUS_INTERNAL_ERROR;
		}
	}

	*_count = attr->value_ctr.num_values;
	*_array = array;
	return NT_STATUS_OK;
}

static NTSTATUS find_drsuapi_attr_int32(TALLOC_CTX *mem_ctx,
					const struct drsuapi_DsReplicaObjectListItemEx *cur,
					uint32_t attid,
					uint32_t *_count,
					int32_t **_array)
{
	struct drsuapi_DsReplicaAttribute *attr;
	int32_t *array;
	uint32_t a;

	attr = find_drsuapi_attr(cur, attid);
	if (attr == NULL) {
		return NT_STATUS_PROPSET_NOT_FOUND;
	}

	array = talloc_array(mem_ctx, int32_t, attr->value_ctr.num_values);
	if (array == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	for (a = 0; a < attr->value_ctr.num_values; a++) {
		const DATA_BLOB *blob;

		blob = attr->value_ctr.values[a].blob;

		if (blob == NULL) {
			return NT_STATUS_INTERNAL_DB_CORRUPTION;
		}

		if (blob->length != 4) {
			return NT_STATUS_INTERNAL_DB_CORRUPTION;
		}

		array[a] = IVAL(blob->data, 0);
	}

	*_count = attr->value_ctr.num_values;
	*_array = array;
	return NT_STATUS_OK;
}

static NTSTATUS find_drsuapi_attr_blob(TALLOC_CTX *mem_ctx,
				       const struct drsuapi_DsReplicaObjectListItemEx *cur,
				       uint32_t attid,
				       uint32_t *_count,
				       DATA_BLOB **_array)
{
	struct drsuapi_DsReplicaAttribute *attr;
	DATA_BLOB *array;
	uint32_t a;

	attr = find_drsuapi_attr(cur, attid);
	if (attr == NULL) {
		return NT_STATUS_PROPSET_NOT_FOUND;
	}

	array = talloc_array(mem_ctx, DATA_BLOB, attr->value_ctr.num_values);
	if (array == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	for (a = 0; a < attr->value_ctr.num_values; a++) {
		const DATA_BLOB *blob;

		blob = attr->value_ctr.values[a].blob;

		if (blob == NULL) {
			return NT_STATUS_INTERNAL_DB_CORRUPTION;
		}

		array[a] = data_blob_talloc(array, blob->data, blob->length);
		if (array[a].length != blob->length) {
			return NT_STATUS_NO_MEMORY;
		}
	}
	*_count = attr->value_ctr.num_values;
	*_array = array;
	return NT_STATUS_OK;
}

static NTSTATUS find_drsuapi_attr_int64(TALLOC_CTX *mem_ctx,
					const struct drsuapi_DsReplicaObjectListItemEx *cur,
					uint32_t attid,
					uint32_t *_count,
					int64_t **_array)
{
	struct drsuapi_DsReplicaAttribute *attr;
	int64_t *array;
	uint32_t a;

	attr = find_drsuapi_attr(cur, attid);
	if (attr == NULL) {
		return NT_STATUS_PROPSET_NOT_FOUND;
	}

	array = talloc_array(mem_ctx, int64_t, attr->value_ctr.num_values);
	if (array == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	for (a = 0; a < attr->value_ctr.num_values; a++) {
		const DATA_BLOB *blob;

		blob = attr->value_ctr.values[a].blob;

		if (blob == NULL) {
			return NT_STATUS_INTERNAL_DB_CORRUPTION;
		}

		if (blob->length != 8) {
			return NT_STATUS_INTERNAL_DB_CORRUPTION;
		}

		array[a] = BVAL(blob->data, 0);
	}
	*_count = attr->value_ctr.num_values;
	*_array = array;
	return NT_STATUS_OK;
}

static NTSTATUS find_drsuapi_attr_dn(TALLOC_CTX *mem_ctx,
				     const struct drsuapi_DsReplicaObjectListItemEx *cur,
				     uint32_t attid,
				     uint32_t *_count,
				     struct drsuapi_DsReplicaObjectIdentifier3 **_array)
{
	struct drsuapi_DsReplicaAttribute *attr;
	struct drsuapi_DsReplicaObjectIdentifier3 *array;
	uint32_t a;

	attr = find_drsuapi_attr(cur, attid);
	if (attr == NULL) {
		return NT_STATUS_PROPSET_NOT_FOUND;
	}

	array = talloc_array(mem_ctx,
			     struct drsuapi_DsReplicaObjectIdentifier3,
			     attr->value_ctr.num_values);
	if (array == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	for (a = 0; a < attr->value_ctr.num_values; a++) {
		const DATA_BLOB *blob;
		enum ndr_err_code ndr_err;
		NTSTATUS status;

		blob = attr->value_ctr.values[a].blob;

		if (blob == NULL) {
			return NT_STATUS_INTERNAL_DB_CORRUPTION;
		}

		/* windows sometimes sends an extra two pad bytes here */
		ndr_err = ndr_pull_struct_blob(blob, array, &array[a],
				(ndr_pull_flags_fn_t)ndr_pull_drsuapi_DsReplicaObjectIdentifier3);
		if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
			status = ndr_map_error2ntstatus(ndr_err);
			return status;
		}
	}
	*_count = attr->value_ctr.num_values;
	*_array = array;
	return NT_STATUS_OK;
}

#define GET_BLOB_EX(attr, needed) do { \
	NTSTATUS _status; \
	uint32_t _cnt; \
	DATA_BLOB *_vals = NULL; \
	attr = data_blob_null; \
	_status = find_drsuapi_attr_blob(mem_ctx, cur, \
					 DRSUAPI_ATTID_ ## attr, \
					 &_cnt, &_vals); \
	if (NT_STATUS_EQUAL(_status, NT_STATUS_PROPSET_NOT_FOUND)) { \
		if (!needed) { \
			_status = NT_STATUS_OK; \
			_cnt = 0; \
		} \
	} \
	if (!NT_STATUS_IS_OK(_status)) { \
		DEBUG(0,(__location__ "attr[%s] %s\n", \
			#attr, nt_errstr(_status))); \
		return _status; \
	} \
	if (_cnt == 0) { \
		if (needed) { \
			talloc_free(_vals); \
			DEBUG(0,(__location__ "attr[%s] count[%u]\n", #attr, _cnt)); \
			return NT_STATUS_OBJECT_NAME_NOT_FOUND; \
		} \
	} else if (_cnt > 1) { \
		talloc_free(_vals); \
		DEBUG(0,(__location__ "attr[%s] count[%u]\n", #attr, _cnt)); \
		return NT_STATUS_INTERNAL_DB_CORRUPTION; \
	} else { \
		attr = _vals[0]; \
		(void)talloc_steal(mem_ctx, _vals[0].data); \
	} \
	talloc_free(_vals); \
} while(0)

#define GET_STRING_EX(attr, needed) do { \
	NTSTATUS _status; \
	uint32_t _cnt; \
	char **_vals = NULL; \
	attr = NULL; \
	_status = find_drsuapi_attr_string(mem_ctx, cur, \
					   DRSUAPI_ATTID_ ## attr, \
					   &_cnt, &_vals); \
	if (NT_STATUS_EQUAL(_status, NT_STATUS_PROPSET_NOT_FOUND)) { \
		if (!needed) { \
			_status = NT_STATUS_OK; \
			_cnt = 0; \
		} \
	} \
	if (!NT_STATUS_IS_OK(_status)) { \
		DEBUG(0,(__location__ "attr[%s] %s\n", \
			#attr, nt_errstr(_status))); \
		return _status; \
	} \
	if (_cnt == 0) { \
		if (needed) { \
			talloc_free(_vals); \
			DEBUG(0,(__location__ "attr[%s] count[%u]\n", #attr, _cnt)); \
			return NT_STATUS_OBJECT_NAME_NOT_FOUND; \
		} \
	} else if (_cnt > 1) { \
		talloc_free(_vals); \
		DEBUG(0,(__location__ "attr[%s] count[%u]\n", #attr, _cnt)); \
		return NT_STATUS_INTERNAL_DB_CORRUPTION; \
	} else { \
		attr = talloc_move(mem_ctx, &_vals[0]); \
	} \
	talloc_free(_vals); \
} while(0)

#define GET_UINT32_EX(attr, needed) do { \
	NTSTATUS _status; \
	uint32_t _cnt; \
	int32_t*_vals = NULL; \
	attr = 0; \
	_status = find_drsuapi_attr_int32(mem_ctx, cur, \
					  DRSUAPI_ATTID_ ## attr, \
					  &_cnt, &_vals); \
	if (NT_STATUS_EQUAL(_status, NT_STATUS_PROPSET_NOT_FOUND)) { \
		if (!needed) { \
			_status = NT_STATUS_OK; \
			_cnt = 0; \
		} \
	} \
	if (!NT_STATUS_IS_OK(_status)) { \
		DEBUG(0,(__location__ "attr[%s] %s\n", \
			#attr, nt_errstr(_status))); \
		return _status; \
	} \
	if (_cnt == 0) { \
		if (needed) { \
			talloc_free(_vals); \
			DEBUG(0,(__location__ "attr[%s] count[%u]\n", #attr, _cnt)); \
			return NT_STATUS_OBJECT_NAME_NOT_FOUND; \
		} \
	} else if (_cnt > 1) { \
		talloc_free(_vals); \
		DEBUG(0,(__location__ "attr[%s] count[%u]\n", #attr, _cnt)); \
		return NT_STATUS_INTERNAL_DB_CORRUPTION; \
	} else { \
		attr = (uint32_t)_vals[0]; \
	} \
	talloc_free(_vals); \
} while(0)

#define GET_UINT64_EX(attr, needed) do { \
	NTSTATUS _status; \
	uint32_t _cnt; \
	int64_t *_vals = NULL; \
	attr = 0; \
	_status = find_drsuapi_attr_int64(mem_ctx, cur, \
					  DRSUAPI_ATTID_ ## attr, \
					  &_cnt, &_vals); \
	if (NT_STATUS_EQUAL(_status, NT_STATUS_PROPSET_NOT_FOUND)) { \
		if (!needed) { \
			_status = NT_STATUS_OK; \
			_cnt = 0; \
		} \
	} \
	if (!NT_STATUS_IS_OK(_status)) { \
		DEBUG(0,(__location__ "attr[%s] %s\n", \
			#attr, nt_errstr(_status))); \
		return _status; \
	} \
	if (_cnt == 0) { \
		if (needed) { \
			talloc_free(_vals); \
			DEBUG(0,(__location__ "attr[%s] count[%u]\n", #attr, _cnt)); \
			return NT_STATUS_OBJECT_NAME_NOT_FOUND; \
		} \
	} else if (_cnt > 1) { \
		talloc_free(_vals); \
		DEBUG(0,(__location__ "attr[%s] count[%u]\n", #attr, _cnt)); \
		return NT_STATUS_INTERNAL_DB_CORRUPTION; \
	} else { \
		attr = (uint64_t)_vals[0]; \
	} \
	talloc_free(_vals); \
} while(0)

#define GET_BLOB(attr) GET_BLOB_EX(attr, false)
#define GET_STRING(attr) GET_STRING_EX(attr, false)
#define GET_UINT32(attr) GET_UINT32_EX(attr, false)
#define GET_UINT64(attr) GET_UINT64_EX(attr, false)

/* Convert a struct samu_DELTA to a struct samu. */
#define STRING_CHANGED (old_string && !new_string) ||\
		    (!old_string && new_string) ||\
		(old_string && new_string && (strcmp(old_string, new_string) != 0))

#define STRING_CHANGED_NC(s1,s2) ((s1) && !(s2)) ||\
		    (!(s1) && (s2)) ||\
		((s1) && (s2) && (strcmp((s1), (s2)) != 0))

/****************************************************************
****************************************************************/

static NTSTATUS sam_account_from_object(struct samu *account,
				struct drsuapi_DsReplicaObjectListItemEx *cur)
{
	TALLOC_CTX *mem_ctx = account;
	const char *old_string, *new_string;
	struct dom_sid_buf buf;
	time_t unix_time, stored_time;
	NTSTATUS status;

	NTTIME lastLogon;
	NTTIME lastLogoff;
	NTTIME pwdLastSet;
	NTTIME accountExpires;
	const char *sAMAccountName;
	const char *displayName;
	const char *homeDirectory;
	const char *homeDrive;
	const char *scriptPath;
	const char *profilePath;
	const char *description;
	const char *userWorkstations;
	DATA_BLOB userParameters;
	struct dom_sid objectSid;
	uint32_t primaryGroupID;
	uint32_t userAccountControl;
	DATA_BLOB logonHours;
	uint32_t badPwdCount;
	uint32_t logonCount;
	DATA_BLOB unicodePwd;
	DATA_BLOB dBCSPwd;

	uint32_t rid = 0;
	uint32_t acct_flags;
	uint32_t units_per_week;

	objectSid = cur->object.identifier->sid;
	GET_STRING_EX(sAMAccountName, true);
	DEBUG(0,("sam_account_from_object(%s, %s) start\n",
		 sAMAccountName,
		 dom_sid_str_buf(&objectSid, &buf)));
	GET_UINT64(lastLogon);
	GET_UINT64(lastLogoff);
	GET_UINT64(pwdLastSet);
	GET_UINT64(accountExpires);
	GET_STRING(displayName);
	GET_STRING(homeDirectory);
	GET_STRING(homeDrive);
	GET_STRING(scriptPath);
	GET_STRING(profilePath);
	GET_STRING(description);
	GET_STRING(userWorkstations);
	GET_BLOB(userParameters);
	GET_UINT32(primaryGroupID);
	GET_UINT32(userAccountControl);
	GET_BLOB(logonHours);
	GET_UINT32(badPwdCount);
	GET_UINT32(logonCount);
	GET_BLOB(unicodePwd);
	GET_BLOB(dBCSPwd);

	status = dom_sid_split_rid(mem_ctx, &objectSid, NULL, &rid);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}
	acct_flags = ds_uf2acb(userAccountControl);

	/* Username, fullname, home dir, dir drive, logon script, acct
	   desc, workstations, profile. */

	if (sAMAccountName) {
		old_string = pdb_get_nt_username(account);
		new_string = sAMAccountName;

		if (STRING_CHANGED) {
			pdb_set_nt_username(account, new_string, PDB_CHANGED);
		}

		/* Unix username is the same - for sanity */
		old_string = pdb_get_username( account );
		if (STRING_CHANGED) {
			pdb_set_username(account, new_string, PDB_CHANGED);
		}
	}

	if (displayName) {
		old_string = pdb_get_fullname(account);
		new_string = displayName;

		if (STRING_CHANGED)
			pdb_set_fullname(account, new_string, PDB_CHANGED);
	}

	if (homeDirectory) {
		old_string = pdb_get_homedir(account);
		new_string = homeDirectory;

		if (STRING_CHANGED)
			pdb_set_homedir(account, new_string, PDB_CHANGED);
	}

	if (homeDrive) {
		old_string = pdb_get_dir_drive(account);
		new_string = homeDrive;

		if (STRING_CHANGED)
			pdb_set_dir_drive(account, new_string, PDB_CHANGED);
	}

	if (scriptPath) {
		old_string = pdb_get_logon_script(account);
		new_string = scriptPath;

		if (STRING_CHANGED)
			pdb_set_logon_script(account, new_string, PDB_CHANGED);
	}

	if (description) {
		old_string = pdb_get_acct_desc(account);
		new_string = description;

		if (STRING_CHANGED)
			pdb_set_acct_desc(account, new_string, PDB_CHANGED);
	}

	if (userWorkstations) {
		old_string = pdb_get_workstations(account);
		new_string = userWorkstations;

		if (STRING_CHANGED)
			pdb_set_workstations(account, new_string, PDB_CHANGED);
	}

	if (profilePath) {
		old_string = pdb_get_profile_path(account);
		new_string = profilePath;

		if (STRING_CHANGED)
			pdb_set_profile_path(account, new_string, PDB_CHANGED);
	}

	if (userParameters.data) {
		char *newstr = NULL;
		old_string = pdb_get_munged_dial(account);

		if (userParameters.length != 0) {
			newstr = base64_encode_data_blob(talloc_tos(),
							 userParameters);
			SMB_ASSERT(newstr != NULL);
		}

		if (STRING_CHANGED_NC(old_string, newstr))
			pdb_set_munged_dial(account, newstr, PDB_CHANGED);
		TALLOC_FREE(newstr);
	}

	/* User and group sid */
	if (rid != 0 && pdb_get_user_rid(account) != rid) {
		pdb_set_user_sid_from_rid(account, rid, PDB_CHANGED);
	}
	if (primaryGroupID != 0 && pdb_get_group_rid(account) != primaryGroupID) {
		pdb_set_group_sid_from_rid(account, primaryGroupID, PDB_CHANGED);
	}

	/* Logon and password information */
	if (!nt_time_is_zero(&lastLogon)) {
		unix_time = nt_time_to_unix(lastLogon);
		stored_time = pdb_get_logon_time(account);
		if (stored_time != unix_time)
			pdb_set_logon_time(account, unix_time, PDB_CHANGED);
	}

	if (!nt_time_is_zero(&lastLogoff)) {
		unix_time = nt_time_to_unix(lastLogoff);
		stored_time = pdb_get_logoff_time(account);
		if (stored_time != unix_time)
			pdb_set_logoff_time(account, unix_time,PDB_CHANGED);
	}

	/* Logon Divs */
	units_per_week = logonHours.length * 8;

	if (pdb_get_logon_divs(account) != units_per_week) {
		pdb_set_logon_divs(account, units_per_week, PDB_CHANGED);
	}

	/* Logon Hours Len */
	if (units_per_week/8 != pdb_get_hours_len(account)) {
		pdb_set_hours_len(account, units_per_week/8, PDB_CHANGED);
	}

	/* Logon Hours */
	if (logonHours.data) {
		char oldstr[44], newstr[44];
		pdb_sethexhours(oldstr, pdb_get_hours(account));
		pdb_sethexhours(newstr, logonHours.data);
		if (!strequal(oldstr, newstr)) {
			pdb_set_hours(account, logonHours.data,
				      logonHours.length, PDB_CHANGED);
		}
	}

	if (pdb_get_bad_password_count(account) != badPwdCount)
		pdb_set_bad_password_count(account, badPwdCount, PDB_CHANGED);

	if (pdb_get_logon_count(account) != logonCount)
		pdb_set_logon_count(account, logonCount, PDB_CHANGED);

	if (!nt_time_is_zero(&pwdLastSet)) {
		unix_time = nt_time_to_unix(pwdLastSet);
		stored_time = pdb_get_pass_last_set_time(account);
		if (stored_time != unix_time)
			pdb_set_pass_last_set_time(account, unix_time, PDB_CHANGED);
	} else {
		/* no last set time, make it now */
		pdb_set_pass_last_set_time(account, time(NULL), PDB_CHANGED);
	}

	if (!nt_time_is_zero(&accountExpires)) {
		unix_time = nt_time_to_unix(accountExpires);
		stored_time = pdb_get_kickoff_time(account);
		if (stored_time != unix_time)
			pdb_set_kickoff_time(account, unix_time, PDB_CHANGED);
	}

	/* Decode hashes from password hash
	   Note that win2000 may send us all zeros for the hashes if it doesn't
	   think this channel is secure enough - don't set the passwords at all
	   in that case
	*/
	if (dBCSPwd.length == 16 && !all_zero(dBCSPwd.data, 16)) {
		pdb_set_lanman_passwd(account, dBCSPwd.data, PDB_CHANGED);
	}

	if (unicodePwd.length == 16 && !all_zero(unicodePwd.data, 16)) {
		pdb_set_nt_passwd(account, unicodePwd.data, PDB_CHANGED);
	}

	/* TODO: history */

	/* TODO: account expiry time */

	pdb_set_acct_ctrl(account, acct_flags, PDB_CHANGED);

	pdb_set_domain(account, lp_workgroup(), PDB_CHANGED);

	DEBUG(0,("sam_account_from_object(%s, %s) done\n",
		 sAMAccountName,
		 dom_sid_str_buf(&objectSid, &buf)));
	return NT_STATUS_OK;
}

/****************************************************************
****************************************************************/

static NTSTATUS handle_account_object(struct dssync_passdb *pctx,
				      TALLOC_CTX *mem_ctx,
				      struct dssync_passdb_obj *obj)
{
	struct drsuapi_DsReplicaObjectListItemEx *cur = obj->cur;
	NTSTATUS status;
	fstring account;
	struct samu *sam_account=NULL;
	GROUP_MAP *map;
	struct group *grp;
	struct dom_sid user_sid;
	struct dom_sid group_sid;
	struct dom_sid_buf buf;
	struct passwd *passwd = NULL;
	uint32_t acct_flags;
	uint32_t rid;

	const char *sAMAccountName;
	uint32_t userAccountControl;

	user_sid = cur->object.identifier->sid;
	GET_STRING_EX(sAMAccountName, true);
	GET_UINT32_EX(userAccountControl, true);

	status = dom_sid_split_rid(mem_ctx, &user_sid, NULL, &rid);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	fstrcpy(account, sAMAccountName);
	if (rid == DOMAIN_RID_GUEST) {
		/*
		 * pdb_getsampwsid() has special handling for DOMAIN_RID_GUEST
		 * that's why we need to ignore it here.
		 *
		 * pdb_smbpasswd.c also has some DOMAIN_RID_GUEST related
		 * code...
		 */
		DEBUG(0,("Ignore %s - %s\n",
			 account,
			 dom_sid_str_buf(&user_sid, &buf)));
		return NT_STATUS_OK;
	}
	DEBUG(0,("Creating account: %s\n", account));

	if ( !(sam_account = samu_new(mem_ctx)) ) {
		return NT_STATUS_NO_MEMORY;
	}

	acct_flags = ds_uf2acb(userAccountControl);
	status = smb_create_user(sam_account, acct_flags, account, &passwd);
	if (!NT_STATUS_IS_OK(status)) {
		DEBUG(0,("Could not create posix account info for '%s'- %s\n",
			account, nt_errstr(status)));
		TALLOC_FREE(sam_account);
		return status;
	}

	DEBUG(3, ("Attempting to find SID %s for user %s in the passdb\n",
		  dom_sid_str_buf(&user_sid, &buf),
		  account));
	if (!pdb_getsampwsid(sam_account, &user_sid)) {
		sam_account_from_object(sam_account, cur);
		DEBUG(3, ("Attempting to add user SID %s for user %s in the passdb\n",
			  dom_sid_str_buf(&user_sid, &buf),
			  pdb_get_username(sam_account)));
		if (!NT_STATUS_IS_OK(pdb_add_sam_account(sam_account))) {
			DEBUG(1, ("SAM Account for %s failed to be added to the passdb!\n",
				  account));
			TALLOC_FREE(sam_account);
			return NT_STATUS_ACCESS_DENIED;
		}
	} else {
		sam_account_from_object(sam_account, cur);
		DEBUG(3, ("Attempting to update user SID %s for user %s in the passdb\n",
			  dom_sid_str_buf(&user_sid, &buf),
			  pdb_get_username(sam_account)));
		if (!NT_STATUS_IS_OK(pdb_update_sam_account(sam_account))) {
			DEBUG(1, ("SAM Account for %s failed to be updated in the passdb!\n",
				  account));
			TALLOC_FREE(sam_account);
			return NT_STATUS_ACCESS_DENIED;
		}
	}

	if (pdb_get_group_sid(sam_account) == NULL) {
		TALLOC_FREE(sam_account);
		return NT_STATUS_UNSUCCESSFUL;
	}

	group_sid = *pdb_get_group_sid(sam_account);

	map = talloc_zero(NULL, GROUP_MAP);
	if (!map) {
		return NT_STATUS_NO_MEMORY;
	}

	if (!pdb_getgrsid(map, group_sid)) {
		DEBUG(0, ("Primary group of %s has no mapping!\n",
			  pdb_get_username(sam_account)));
	} else {
		if (map->gid != passwd->pw_gid) {
			if (!(grp = getgrgid(map->gid))) {
				DEBUG(0, ("Could not find unix group %lu for user %s (group SID=%s)\n",
					  (unsigned long)map->gid, pdb_get_username(sam_account),
					  dom_sid_str_buf(&group_sid, &buf)));
			} else {
				smb_set_primary_group(grp->gr_name, pdb_get_username(sam_account));
			}
		}
	}

	TALLOC_FREE(map);

	if ( !passwd ) {
		DEBUG(1, ("No unix user for this account (%s), cannot adjust mappings\n",
			pdb_get_username(sam_account)));
	}

	TALLOC_FREE(sam_account);
	return NT_STATUS_OK;
}

/****************************************************************
****************************************************************/

static NTSTATUS handle_alias_object(struct dssync_passdb *pctx,
				    TALLOC_CTX *mem_ctx,
				    struct dssync_passdb_obj *obj)
{
	struct drsuapi_DsReplicaObjectListItemEx *cur = obj->cur;
	NTSTATUS status;
	struct group *grp = NULL;
	struct dom_sid group_sid;
	uint32_t rid = 0;
	struct dom_sid *dom_sid = NULL;
	struct dom_sid_buf sid_str;
	GROUP_MAP *map;
	bool insert = true;

	const char *sAMAccountName;
	const char *description;
	uint32_t i;
	uint32_t num_members = 0;
	struct drsuapi_DsReplicaObjectIdentifier3 *members = NULL;

	group_sid = cur->object.identifier->sid;
	GET_STRING_EX(sAMAccountName, true);
	GET_STRING(description);

	status = find_drsuapi_attr_dn(obj, cur, DRSUAPI_ATTID_member,
				      &num_members, &members);
	if (NT_STATUS_EQUAL(status, NT_STATUS_PROPSET_NOT_FOUND)) {
		status = NT_STATUS_OK;
	}
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	dom_sid_split_rid(mem_ctx, &group_sid, &dom_sid, &rid);

	map = talloc_zero(mem_ctx, GROUP_MAP);
	if (map == NULL) {
		status = NT_STATUS_NO_MEMORY;
		goto done;
	}

	map->nt_name = talloc_strdup(map, sAMAccountName);
	if (map->nt_name == NULL) {
		status = NT_STATUS_NO_MEMORY;
		goto done;
	}

	if (description) {
		map->comment = talloc_strdup(map, description);
	} else {
		map->comment = talloc_strdup(map, "");
	}
	if (map->comment == NULL) {
		status = NT_STATUS_NO_MEMORY;
		goto done;
	}

	DEBUG(0,("Creating alias[%s] - %s members[%u]\n",
		 map->nt_name,
		 dom_sid_str_buf(&group_sid, &sid_str),
		 num_members));

	status = dssync_insert_obj(pctx, pctx->aliases, obj);
	if (!NT_STATUS_IS_OK(status)) {
		goto done;
	}

	if (pdb_getgrsid(map, group_sid)) {
		if (map->gid != -1) {
			grp = getgrgid(map->gid);
		}
		insert = false;
	}

	if (grp == NULL) {
		gid_t gid;

		/* No group found from mapping, find it from its name. */
		if ((grp = getgrnam(map->nt_name)) == NULL) {

			/* No appropriate group found, create one */

			DEBUG(0, ("Creating unix group: '%s'\n",
							map->nt_name));

			if (smb_create_group(map->nt_name, &gid) != 0) {
				status = NT_STATUS_ACCESS_DENIED;
				goto done;
			}

			if ((grp = getgrgid(gid)) == NULL) {
				status = NT_STATUS_ACCESS_DENIED;
				goto done;
			}
		}
	}

	map->gid = grp->gr_gid;
	map->sid = group_sid;

	if (dom_sid_equal(dom_sid, &global_sid_Builtin)) {
		/*
		 * pdb_ldap does not like SID_NAME_WKN_GRP...
		 *
		 * map.sid_name_use = SID_NAME_WKN_GRP;
		 */
		map->sid_name_use = SID_NAME_ALIAS;
	} else {
		map->sid_name_use = SID_NAME_ALIAS;
	}

	if (insert) {
		pdb_add_group_mapping_entry(map);
	} else {
		pdb_update_group_mapping_entry(map);
	}

	for (i=0; i < num_members; i++) {
		struct dssync_passdb_mem *mem;

		status = dssync_create_mem(pctx, obj,
					   true /* active */,
					   &members[i], &mem);
		if (!NT_STATUS_IS_OK(status)) {
			goto done;
		}
	}

	status = NT_STATUS_OK;

done:
	TALLOC_FREE(map);
	return status;
}

/****************************************************************
****************************************************************/

static NTSTATUS handle_group_object(struct dssync_passdb *pctx,
				    TALLOC_CTX *mem_ctx,
				    struct dssync_passdb_obj *obj)
{
	struct drsuapi_DsReplicaObjectListItemEx *cur = obj->cur;
	NTSTATUS status;
	struct group *grp = NULL;
	struct dom_sid group_sid;
	struct dom_sid_buf sid_str;
	GROUP_MAP *map;
	bool insert = true;

	const char *sAMAccountName;
	const char *description;
	uint32_t i;
	uint32_t num_members = 0;
	struct drsuapi_DsReplicaObjectIdentifier3 *members = NULL;

	group_sid = cur->object.identifier->sid;
	GET_STRING_EX(sAMAccountName, true);
	GET_STRING(description);

	status = find_drsuapi_attr_dn(obj, cur, DRSUAPI_ATTID_member,
				      &num_members, &members);
	if (NT_STATUS_EQUAL(status, NT_STATUS_PROPSET_NOT_FOUND)) {
		status = NT_STATUS_OK;
	}
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	map = talloc_zero(NULL, GROUP_MAP);
	if (!map) {
		return NT_STATUS_NO_MEMORY;
	}

	map->nt_name = talloc_strdup(map, sAMAccountName);
	if (!map->nt_name) {
		status = NT_STATUS_NO_MEMORY;
		goto done;
	}
	if (description) {
		map->comment = talloc_strdup(map, description);
	} else {
		map->comment = talloc_strdup(map, "");
	}
	if (!map->comment) {
		status = NT_STATUS_NO_MEMORY;
		goto done;
	}

	DEBUG(0,("Creating group[%s] - %s members [%u]\n",
		 map->nt_name,
		 dom_sid_str_buf(&group_sid, &sid_str),
		 num_members));

	status = dssync_insert_obj(pctx, pctx->groups, obj);
	if (!NT_STATUS_IS_OK(status)) {
		goto done;
	}

	if (pdb_getgrsid(map, group_sid)) {
		if (map->gid != -1) {
			grp = getgrgid(map->gid);
		}
		insert = false;
	}

	if (grp == NULL) {
		gid_t gid;

		/* No group found from mapping, find it from its name. */
		if ((grp = getgrnam(map->nt_name)) == NULL) {

			/* No appropriate group found, create one */

			DEBUG(0, ("Creating unix group: '%s'\n",
							map->nt_name));

			if (smb_create_group(map->nt_name, &gid) != 0) {
				status = NT_STATUS_ACCESS_DENIED;
				goto done;
			}

			if ((grp = getgrnam(map->nt_name)) == NULL) {
				status = NT_STATUS_ACCESS_DENIED;
				goto done;
			}
		}
	}

	map->gid = grp->gr_gid;
	map->sid = group_sid;
	map->sid_name_use = SID_NAME_DOM_GRP;

	if (insert) {
		pdb_add_group_mapping_entry(map);
	} else {
		pdb_update_group_mapping_entry(map);
	}

	for (i=0; i < num_members; i++) {
		struct dssync_passdb_mem *mem;

		status = dssync_create_mem(pctx, obj,
					   true /* active */,
					   &members[i], &mem);
		if (!NT_STATUS_IS_OK(status)) {
			goto done;
		}
	}

	status = NT_STATUS_OK;

done:
	TALLOC_FREE(map);
	return status;
}

/****************************************************************
****************************************************************/

static NTSTATUS handle_interdomain_trust_object(struct dssync_passdb *pctx,
						TALLOC_CTX *mem_ctx,
						struct dssync_passdb_obj *obj)
{
	struct drsuapi_DsReplicaObjectListItemEx *cur = obj->cur;
	DEBUG(0,("trust: %s\n", cur->object.identifier->dn));
	return NT_STATUS_NOT_IMPLEMENTED;
}

/****************************************************************
****************************************************************/

struct dssync_object_table_t {
	uint32_t type;
	NTSTATUS (*fn) (struct dssync_passdb *pctx,
			TALLOC_CTX *mem_ctx,
			struct dssync_passdb_obj *obj);
};

static const struct dssync_object_table_t dssync_object_table[] = {
	{ ATYPE_NORMAL_ACCOUNT,		handle_account_object },
	{ ATYPE_WORKSTATION_TRUST,	handle_account_object },
	{ ATYPE_SECURITY_LOCAL_GROUP,	handle_alias_object },
	{ ATYPE_SECURITY_GLOBAL_GROUP,	handle_group_object },
	{ ATYPE_INTERDOMAIN_TRUST,	handle_interdomain_trust_object },
};

/****************************************************************
****************************************************************/

static NTSTATUS parse_object(struct dssync_passdb *pctx,
			     TALLOC_CTX *mem_ctx,
			     struct drsuapi_DsReplicaObjectListItemEx *cur)
{
	NTSTATUS status = NT_STATUS_OK;
	DATA_BLOB *blob;
	int i = 0;
	int a = 0;
	struct drsuapi_DsReplicaAttribute *attr;

	char *name = NULL;
	uint32_t sam_type = 0;

	DEBUG(3, ("parsing object '%s'\n", cur->object.identifier->dn));

	for (i=0; i < cur->object.attribute_ctr.num_attributes; i++) {

		attr = &cur->object.attribute_ctr.attributes[i];

		if (attr->value_ctr.num_values != 1) {
			continue;
		}

		if (!attr->value_ctr.values[0].blob) {
			continue;
		}

		blob = attr->value_ctr.values[0].blob;

		switch (attr->attid) {
			case DRSUAPI_ATTID_sAMAccountName:
				pull_string_talloc(mem_ctx, NULL, 0, &name,
						   blob->data, blob->length,
						   STR_UNICODE);
				break;
			case DRSUAPI_ATTID_sAMAccountType:
				sam_type = IVAL(blob->data, 0);
				break;
			default:
				break;
		}
	}

	for (a=0; a < ARRAY_SIZE(dssync_object_table); a++) {
		if (sam_type == dssync_object_table[a].type) {
			if (dssync_object_table[a].fn) {
				struct dssync_passdb_obj *obj = NULL;
				status = dssync_create_obj(pctx, pctx->all,
							   sam_type, cur, &obj);
				if (!NT_STATUS_IS_OK(status)) {
					break;
				}
				status = dssync_object_table[a].fn(pctx,
								   mem_ctx,
								   obj);
				break;
			}
		}
	}

	return status;
}

static NTSTATUS parse_link(struct dssync_passdb *pctx,
			   TALLOC_CTX *mem_ctx,
			   struct drsuapi_DsReplicaLinkedAttribute *cur)
{
	struct drsuapi_DsReplicaObjectIdentifier3 *id3;
	const DATA_BLOB *blob;
	enum ndr_err_code ndr_err;
	NTSTATUS status;
	bool active = false;
	struct dssync_passdb_mem *mem;
	struct dssync_passdb_obj *obj;

	if (cur->attid != DRSUAPI_ATTID_member) {
		return NT_STATUS_OK;
	}

	if (cur->flags & DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE) {
		active = true;
	}

	DEBUG(3, ("parsing link '%s' - %s\n",
		  cur->identifier->dn, active?"adding":"deleting"));

	blob = cur->value.blob;

	if (blob == NULL) {
		return NT_STATUS_INTERNAL_DB_CORRUPTION;
	}

	obj = dssync_search_obj_by_guid(pctx, pctx->all, &cur->identifier->guid);
	if (obj == NULL) {
		return NT_STATUS_INTERNAL_DB_CORRUPTION;
	}

	id3 = talloc_zero(obj, struct drsuapi_DsReplicaObjectIdentifier3);
	if (id3 == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	/* windows sometimes sends an extra two pad bytes here */
	ndr_err = ndr_pull_struct_blob(blob, id3, id3,
			(ndr_pull_flags_fn_t)ndr_pull_drsuapi_DsReplicaObjectIdentifier3);
	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
		status = ndr_map_error2ntstatus(ndr_err);
		return status;
	}

	status = dssync_create_mem(pctx, obj,
				   active,
				   id3, &mem);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	return NT_STATUS_OK;
}

/****************************************************************
****************************************************************/

static NTSTATUS passdb_process_objects(struct dssync_context *ctx,
				       TALLOC_CTX *mem_ctx,
				       struct drsuapi_DsReplicaObjectListItemEx *cur,
				       struct drsuapi_DsReplicaOIDMapping_Ctr *mapping_ctr)
{
	NTSTATUS status = NT_STATUS_OK;
	struct dssync_passdb *pctx =
		talloc_get_type_abort(ctx->private_data,
		struct dssync_passdb);

	for (; cur; cur = cur->next_object) {
		status = parse_object(pctx, mem_ctx, cur);
		if (!NT_STATUS_IS_OK(status)) {
			goto out;
		}
	}

 out:
	return status;
}

/****************************************************************
****************************************************************/

static NTSTATUS passdb_process_links(struct dssync_context *ctx,
				     TALLOC_CTX *mem_ctx,
				     uint32_t count,
				     struct drsuapi_DsReplicaLinkedAttribute *links,
				     struct drsuapi_DsReplicaOIDMapping_Ctr *mapping_ctr)
{
	NTSTATUS status = NT_STATUS_OK;
	struct dssync_passdb *pctx =
		talloc_get_type_abort(ctx->private_data,
		struct dssync_passdb);
	uint32_t i;

	for (i = 0; i < count; i++) {
		status = parse_link(pctx, mem_ctx, &links[i]);
		if (!NT_STATUS_IS_OK(status)) {
			goto out;
		}
	}

 out:
	return status;
}

/****************************************************************
****************************************************************/

const struct dssync_ops libnet_dssync_passdb_ops = {
	.startup		= passdb_startup,
	.process_objects	= passdb_process_objects,
	.process_links		= passdb_process_links,
	.finish			= passdb_finish,
};
