void GrpcHealthCheckerImpl::GrpcActiveHealthCheckSession::logHealthCheckStatus(
    Grpc::Status::GrpcStatus grpc_status, const std::string& grpc_message) {
  const char* service_status;
  if (!health_check_response_) {
    service_status = "rpc_error";
  } else {
    switch (health_check_response_->status()) {
    case grpc::health::v1::HealthCheckResponse::SERVING:
      service_status = "serving";
      break;
    case grpc::health::v1::HealthCheckResponse::NOT_SERVING:
      service_status = "not_serving";
      break;
    case grpc::health::v1::HealthCheckResponse::UNKNOWN:
      service_status = "unknown";
      break;
    case grpc::health::v1::HealthCheckResponse::SERVICE_UNKNOWN:
      service_status = "service_unknown";
      break;
    default:
      service_status = "unknown_healthcheck_response";
      break;
    }
  }
  std::string grpc_status_message;
  if (grpc_status != Grpc::Status::WellKnownGrpcStatus::Ok && !grpc_message.empty()) {
    grpc_status_message = fmt::format("{} ({})", grpc_status, grpc_message);
  } else {
    grpc_status_message = absl::StrCat("", grpc_status);
  }

  ENVOY_CONN_LOG(debug, "hc grpc_status={} service_status={} health_flags={}", *client_,
                 grpc_status_message, service_status, HostUtility::healthFlagsToString(*host_));
}


TEST_F(HttpHealthCheckerImplTest, ConnectionClose) {
  setupNoServiceValidationHC();
  EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged));

  cluster_->prioritySet().getMockHostSet(0)->hosts_ = {
      makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())};
  expectSessionCreate();
  expectStreamCreate(0);
  EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _));
  health_checker_->start();

  EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _));
  EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer());
  respond(0, "200", true);
  EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health());

  expectClientCreate(0);
  expectStreamCreate(0);
  EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _));
  test_sessions_[0]->interval_timer_->invokeCallback();
}


  void expectChangePending(size_t index) {
    EXPECT_CALL(*this, onHostStatus(_, HealthTransition::ChangePending));
    EXPECT_CALL(*test_sessions_[index]->timeout_timer_, disableTimer());
    EXPECT_CALL(*test_sessions_[index]->interval_timer_, enableTimer(_, _));
  }


transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
{
	bool		is_serial;
	bool		saw_nullable;
	bool		saw_default;
	bool		saw_identity;
	ListCell   *clist;

	cxt->columns = lappend(cxt->columns, column);

	/* Check for SERIAL pseudo-types */
	is_serial = false;
	if (column->typeName
		&& list_length(column->typeName->names) == 1
		&& !column->typeName->pct_type)
	{
		char	   *typname = strVal(linitial(column->typeName->names));

		if (strcmp(typname, "smallserial") == 0 ||
			strcmp(typname, "serial2") == 0)
		{
			is_serial = true;
			column->typeName->names = NIL;
			column->typeName->typeOid = INT2OID;
		}
		else if (strcmp(typname, "serial") == 0 ||
				 strcmp(typname, "serial4") == 0)
		{
			is_serial = true;
			column->typeName->names = NIL;
			column->typeName->typeOid = INT4OID;
		}
		else if (strcmp(typname, "bigserial") == 0 ||
				 strcmp(typname, "serial8") == 0)
		{
			is_serial = true;
			column->typeName->names = NIL;
			column->typeName->typeOid = INT8OID;
		}

		/*
		 * We have to reject "serial[]" explicitly, because once we've set
		 * typeid, LookupTypeName won't notice arrayBounds.  We don't need any
		 * special coding for serial(typmod) though.
		 */
		if (is_serial && column->typeName->arrayBounds != NIL)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("array of serial is not implemented"),
					 parser_errposition(cxt->pstate,
										column->typeName->location)));
	}

	/* Do necessary work on the column type declaration */
	if (column->typeName)
		transformColumnType(cxt, column);

	/* Special actions for SERIAL pseudo-types */
	if (is_serial)
	{
		char	   *snamespace;
		char	   *sname;
		char	   *qstring;
		A_Const    *snamenode;
		TypeCast   *castnode;
		FuncCall   *funccallnode;
		Constraint *constraint;

		generateSerialExtraStmts(cxt, column,
								 column->typeName->typeOid, NIL, false,
								 &snamespace, &sname);

		/*
		 * Create appropriate constraints for SERIAL.  We do this in full,
		 * rather than shortcutting, so that we will detect any conflicting
		 * constraints the user wrote (like a different DEFAULT).
		 *
		 * Create an expression tree representing the function call
		 * nextval('sequencename').  We cannot reduce the raw tree to cooked
		 * form until after the sequence is created, but there's no need to do
		 * so.
		 */
		qstring = quote_qualified_identifier(snamespace, sname);
		snamenode = makeNode(A_Const);
		snamenode->val.type = T_String;
		snamenode->val.val.str = qstring;
		snamenode->location = -1;
		castnode = makeNode(TypeCast);
		castnode->typeName = SystemTypeName("regclass");
		castnode->arg = (Node *) snamenode;
		castnode->location = -1;
		funccallnode = makeFuncCall(SystemFuncName("nextval"),
									list_make1(castnode),
									-1);
		constraint = makeNode(Constraint);
		constraint->contype = CONSTR_DEFAULT;
		constraint->location = -1;
		constraint->raw_expr = (Node *) funccallnode;
		constraint->cooked_expr = NULL;
		column->constraints = lappend(column->constraints, constraint);

		constraint = makeNode(Constraint);
		constraint->contype = CONSTR_NOTNULL;
		constraint->location = -1;
		column->constraints = lappend(column->constraints, constraint);
	}

	/* Process column constraints, if any... */
	transformConstraintAttrs(cxt, column->constraints);

	saw_nullable = false;
	saw_default = false;
	saw_identity = false;

	foreach(clist, column->constraints)
	{
		Constraint *constraint = lfirst_node(Constraint, clist);

		switch (constraint->contype)
		{
			case CONSTR_NULL:
				if (saw_nullable && column->is_not_null)
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("conflicting NULL/NOT NULL declarations for column \"%s\" of table \"%s\"",
									column->colname, cxt->relation->relname),
							 parser_errposition(cxt->pstate,
												constraint->location)));
				column->is_not_null = FALSE;
				saw_nullable = true;
				break;

			case CONSTR_NOTNULL:
				if (saw_nullable && !column->is_not_null)
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("conflicting NULL/NOT NULL declarations for column \"%s\" of table \"%s\"",
									column->colname, cxt->relation->relname),
							 parser_errposition(cxt->pstate,
												constraint->location)));
				column->is_not_null = TRUE;
				saw_nullable = true;
				break;

			case CONSTR_DEFAULT:
				if (saw_default)
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("multiple default values specified for column \"%s\" of table \"%s\"",
									column->colname, cxt->relation->relname),
							 parser_errposition(cxt->pstate,
												constraint->location)));
				column->raw_default = constraint->raw_expr;
				Assert(constraint->cooked_expr == NULL);
				saw_default = true;
				break;

			case CONSTR_IDENTITY:
				{
					Type		ctype;
					Oid			typeOid;

					if (cxt->ofType)
						ereport(ERROR,
								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
								 errmsg("identity columns are not supported on typed tables")));
					if (cxt->partbound)
						ereport(ERROR,
								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
								 errmsg("identity columns are not supported on partitions")));

					ctype = typenameType(cxt->pstate, column->typeName, NULL);
					typeOid = HeapTupleGetOid(ctype);
					ReleaseSysCache(ctype);

					if (saw_identity)
						ereport(ERROR,
								(errcode(ERRCODE_SYNTAX_ERROR),
								 errmsg("multiple identity specifications for column \"%s\" of table \"%s\"",
										column->colname, cxt->relation->relname),
								 parser_errposition(cxt->pstate,
													constraint->location)));

					generateSerialExtraStmts(cxt, column,
											 typeOid, constraint->options, true,
											 NULL, NULL);

					column->identity = constraint->generated_when;
					saw_identity = true;

					/* An identity column is implicitly NOT NULL */
					if (saw_nullable && !column->is_not_null)
						ereport(ERROR,
								(errcode(ERRCODE_SYNTAX_ERROR),
								 errmsg("conflicting NULL/NOT NULL declarations for column \"%s\" of table \"%s\"",
										column->colname, cxt->relation->relname),
								 parser_errposition(cxt->pstate,
													constraint->location)));
					column->is_not_null = true;
					saw_nullable = true;
					break;
				}

			case CONSTR_CHECK:
				cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
				break;

			case CONSTR_PRIMARY:
				if (cxt->isforeign)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("primary key constraints are not supported on foreign tables"),
							 parser_errposition(cxt->pstate,
												constraint->location)));
				if (cxt->ispartitioned)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("primary key constraints are not supported on partitioned tables"),
							 parser_errposition(cxt->pstate,
												constraint->location)));
				/* FALL THRU */

			case CONSTR_UNIQUE:
				if (cxt->isforeign)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("unique constraints are not supported on foreign tables"),
							 parser_errposition(cxt->pstate,
												constraint->location)));
				if (cxt->ispartitioned)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("unique constraints are not supported on partitioned tables"),
							 parser_errposition(cxt->pstate,
												constraint->location)));
				if (constraint->keys == NIL)
					constraint->keys = list_make1(makeString(column->colname));
				cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
				break;

			case CONSTR_EXCLUSION:
				/* grammar does not allow EXCLUDE as a column constraint */
				elog(ERROR, "column exclusion constraints are not supported");
				break;

			case CONSTR_FOREIGN:
				if (cxt->isforeign)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("foreign key constraints are not supported on foreign tables"),
							 parser_errposition(cxt->pstate,
												constraint->location)));
				if (cxt->ispartitioned)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("foreign key constraints are not supported on partitioned tables"),
							 parser_errposition(cxt->pstate,
												constraint->location)));

				/*
				 * Fill in the current attribute's name and throw it into the
				 * list of FK constraints to be processed later.
				 */
				constraint->fk_attrs = list_make1(makeString(column->colname));
				cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
				break;

			case CONSTR_ATTR_DEFERRABLE:
			case CONSTR_ATTR_NOT_DEFERRABLE:
			case CONSTR_ATTR_DEFERRED:
			case CONSTR_ATTR_IMMEDIATE:
				/* transformConstraintAttrs took care of these */
				break;

			default:
				elog(ERROR, "unrecognized constraint type: %d",
					 constraint->contype);
				break;
		}

		if (saw_default && saw_identity)
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("both default and identity specified for column \"%s\" of table \"%s\"",
							column->colname, cxt->relation->relname),
					 parser_errposition(cxt->pstate,
										constraint->location)));
	}

	/*
	 * If needed, generate ALTER FOREIGN TABLE ALTER COLUMN statement to add
	 * per-column foreign data wrapper options to this column after creation.
	 */
	if (column->fdwoptions != NIL)
	{
		AlterTableStmt *stmt;
		AlterTableCmd *cmd;

		cmd = makeNode(AlterTableCmd);
		cmd->subtype = AT_AlterColumnGenericOptions;
		cmd->name = column->colname;
		cmd->def = (Node *) column->fdwoptions;
		cmd->behavior = DROP_RESTRICT;
		cmd->missing_ok = false;

		stmt = makeNode(AlterTableStmt);
		stmt->relation = cxt->relation;
		stmt->cmds = NIL;
		stmt->relkind = OBJECT_FOREIGN_TABLE;
		stmt->cmds = lappend(stmt->cmds, cmd);

		cxt->alist = lappend(cxt->alist, stmt);
	}
}


AlterForeignServer(AlterForeignServerStmt *stmt)
{
	Relation	rel;
	HeapTuple	tp;
	Datum		repl_val[Natts_pg_foreign_server];
	bool		repl_null[Natts_pg_foreign_server];
	bool		repl_repl[Natts_pg_foreign_server];
	Oid			srvId;
	Form_pg_foreign_server srvForm;
	ObjectAddress address;

	rel = heap_open(ForeignServerRelationId, RowExclusiveLock);

	tp = SearchSysCacheCopy1(FOREIGNSERVERNAME,
							 CStringGetDatum(stmt->servername));

	if (!HeapTupleIsValid(tp))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("server \"%s\" does not exist", stmt->servername)));

	srvId = HeapTupleGetOid(tp);
	srvForm = (Form_pg_foreign_server) GETSTRUCT(tp);

	/*
	 * Only owner or a superuser can ALTER a SERVER.
	 */
	if (!pg_foreign_server_ownercheck(srvId, GetUserId()))
		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FOREIGN_SERVER,
					   stmt->servername);

	memset(repl_val, 0, sizeof(repl_val));
	memset(repl_null, false, sizeof(repl_null));
	memset(repl_repl, false, sizeof(repl_repl));

	if (stmt->has_version)
	{
		/*
		 * Change the server VERSION string.
		 */
		if (stmt->version)
			repl_val[Anum_pg_foreign_server_srvversion - 1] =
				CStringGetTextDatum(stmt->version);
		else
			repl_null[Anum_pg_foreign_server_srvversion - 1] = true;

		repl_repl[Anum_pg_foreign_server_srvversion - 1] = true;
	}

	if (stmt->options)
	{
		ForeignDataWrapper *fdw = GetForeignDataWrapper(srvForm->srvfdw);
		Datum		datum;
		bool		isnull;

		/* Extract the current srvoptions */
		datum = SysCacheGetAttr(FOREIGNSERVEROID,
								tp,
								Anum_pg_foreign_server_srvoptions,
								&isnull);
		if (isnull)
			datum = PointerGetDatum(NULL);

		/* Prepare the options array */
		datum = transformGenericOptions(ForeignServerRelationId,
										datum,
										stmt->options,
										fdw->fdwvalidator);

		if (PointerIsValid(DatumGetPointer(datum)))
			repl_val[Anum_pg_foreign_server_srvoptions - 1] = datum;
		else
			repl_null[Anum_pg_foreign_server_srvoptions - 1] = true;

		repl_repl[Anum_pg_foreign_server_srvoptions - 1] = true;
	}

	/* Everything looks good - update the tuple */
	tp = heap_modify_tuple(tp, RelationGetDescr(rel),
						   repl_val, repl_null, repl_repl);

	CatalogTupleUpdate(rel, &tp->t_self, tp);

	InvokeObjectPostAlterHook(ForeignServerRelationId, srvId, 0);

	ObjectAddressSet(address, ForeignServerRelationId, srvId);

	heap_freetuple(tp);

	heap_close(rel, RowExclusiveLock);

	return address;
}


generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx,
						const AttrNumber *attmap, int attmap_length)
{
	Oid			source_relid = RelationGetRelid(source_idx);
	Form_pg_attribute *attrs = RelationGetDescr(source_idx)->attrs;
	HeapTuple	ht_idxrel;
	HeapTuple	ht_idx;
	HeapTuple	ht_am;
	Form_pg_class idxrelrec;
	Form_pg_index idxrec;
	Form_pg_am	amrec;
	oidvector  *indcollation;
	oidvector  *indclass;
	IndexStmt  *index;
	List	   *indexprs;
	ListCell   *indexpr_item;
	Oid			indrelid;
	int			keyno;
	Oid			keycoltype;
	Datum		datum;
	bool		isnull;

	/*
	 * Fetch pg_class tuple of source index.  We can't use the copy in the
	 * relcache entry because it doesn't include optional fields.
	 */
	ht_idxrel = SearchSysCache1(RELOID, ObjectIdGetDatum(source_relid));
	if (!HeapTupleIsValid(ht_idxrel))
		elog(ERROR, "cache lookup failed for relation %u", source_relid);
	idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);

	/* Fetch pg_index tuple for source index from relcache entry */
	ht_idx = source_idx->rd_indextuple;
	idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
	indrelid = idxrec->indrelid;

	/* Fetch the pg_am tuple of the index' access method */
	ht_am = SearchSysCache1(AMOID, ObjectIdGetDatum(idxrelrec->relam));
	if (!HeapTupleIsValid(ht_am))
		elog(ERROR, "cache lookup failed for access method %u",
			 idxrelrec->relam);
	amrec = (Form_pg_am) GETSTRUCT(ht_am);

	/* Extract indcollation from the pg_index tuple */
	datum = SysCacheGetAttr(INDEXRELID, ht_idx,
							Anum_pg_index_indcollation, &isnull);
	Assert(!isnull);
	indcollation = (oidvector *) DatumGetPointer(datum);

	/* Extract indclass from the pg_index tuple */
	datum = SysCacheGetAttr(INDEXRELID, ht_idx,
							Anum_pg_index_indclass, &isnull);
	Assert(!isnull);
	indclass = (oidvector *) DatumGetPointer(datum);

	/* Begin building the IndexStmt */
	index = makeNode(IndexStmt);
	index->relation = heapRel;
	index->accessMethod = pstrdup(NameStr(amrec->amname));
	if (OidIsValid(idxrelrec->reltablespace))
		index->tableSpace = get_tablespace_name(idxrelrec->reltablespace);
	else
		index->tableSpace = NULL;
	index->excludeOpNames = NIL;
	index->idxcomment = NULL;
	index->indexOid = InvalidOid;
	index->oldNode = InvalidOid;
	index->unique = idxrec->indisunique;
	index->primary = idxrec->indisprimary;
	index->transformed = true;	/* don't need transformIndexStmt */
	index->concurrent = false;
	index->if_not_exists = false;

	/*
	 * We don't try to preserve the name of the source index; instead, just
	 * let DefineIndex() choose a reasonable name.  (If we tried to preserve
	 * the name, we'd get duplicate-relation-name failures unless the source
	 * table was in a different schema.)
	 */
	index->idxname = NULL;

	/*
	 * If the index is marked PRIMARY or has an exclusion condition, it's
	 * certainly from a constraint; else, if it's not marked UNIQUE, it
	 * certainly isn't.  If it is or might be from a constraint, we have to
	 * fetch the pg_constraint record.
	 */
	if (index->primary || index->unique || idxrec->indisexclusion)
	{
		Oid			constraintId = get_index_constraint(source_relid);

		if (OidIsValid(constraintId))
		{
			HeapTuple	ht_constr;
			Form_pg_constraint conrec;

			ht_constr = SearchSysCache1(CONSTROID,
										ObjectIdGetDatum(constraintId));
			if (!HeapTupleIsValid(ht_constr))
				elog(ERROR, "cache lookup failed for constraint %u",
					 constraintId);
			conrec = (Form_pg_constraint) GETSTRUCT(ht_constr);

			index->isconstraint = true;
			index->deferrable = conrec->condeferrable;
			index->initdeferred = conrec->condeferred;

			/* If it's an exclusion constraint, we need the operator names */
			if (idxrec->indisexclusion)
			{
				Datum	   *elems;
				int			nElems;
				int			i;

				Assert(conrec->contype == CONSTRAINT_EXCLUSION);
				/* Extract operator OIDs from the pg_constraint tuple */
				datum = SysCacheGetAttr(CONSTROID, ht_constr,
										Anum_pg_constraint_conexclop,
										&isnull);
				if (isnull)
					elog(ERROR, "null conexclop for constraint %u",
						 constraintId);

				deconstruct_array(DatumGetArrayTypeP(datum),
								  OIDOID, sizeof(Oid), true, 'i',
								  &elems, NULL, &nElems);

				for (i = 0; i < nElems; i++)
				{
					Oid			operid = DatumGetObjectId(elems[i]);
					HeapTuple	opertup;
					Form_pg_operator operform;
					char	   *oprname;
					char	   *nspname;
					List	   *namelist;

					opertup = SearchSysCache1(OPEROID,
											  ObjectIdGetDatum(operid));
					if (!HeapTupleIsValid(opertup))
						elog(ERROR, "cache lookup failed for operator %u",
							 operid);
					operform = (Form_pg_operator) GETSTRUCT(opertup);
					oprname = pstrdup(NameStr(operform->oprname));
					/* For simplicity we always schema-qualify the op name */
					nspname = get_namespace_name(operform->oprnamespace);
					namelist = list_make2(makeString(nspname),
										  makeString(oprname));
					index->excludeOpNames = lappend(index->excludeOpNames,
													namelist);
					ReleaseSysCache(opertup);
				}
			}

			ReleaseSysCache(ht_constr);
		}
		else
			index->isconstraint = false;
	}
	else
		index->isconstraint = false;

	/* Get the index expressions, if any */
	datum = SysCacheGetAttr(INDEXRELID, ht_idx,
							Anum_pg_index_indexprs, &isnull);
	if (!isnull)
	{
		char	   *exprsString;

		exprsString = TextDatumGetCString(datum);
		indexprs = (List *) stringToNode(exprsString);
	}
	else
		indexprs = NIL;

	/* Build the list of IndexElem */
	index->indexParams = NIL;

	indexpr_item = list_head(indexprs);
	for (keyno = 0; keyno < idxrec->indnatts; keyno++)
	{
		IndexElem  *iparam;
		AttrNumber	attnum = idxrec->indkey.values[keyno];
		int16		opt = source_idx->rd_indoption[keyno];

		iparam = makeNode(IndexElem);

		if (AttributeNumberIsValid(attnum))
		{
			/* Simple index column */
			char	   *attname;

			attname = get_relid_attribute_name(indrelid, attnum);
			keycoltype = get_atttype(indrelid, attnum);

			iparam->name = attname;
			iparam->expr = NULL;
		}
		else
		{
			/* Expressional index */
			Node	   *indexkey;
			bool		found_whole_row;

			if (indexpr_item == NULL)
				elog(ERROR, "too few entries in indexprs list");
			indexkey = (Node *) lfirst(indexpr_item);
			indexpr_item = lnext(indexpr_item);

			/* Adjust Vars to match new table's column numbering */
			indexkey = map_variable_attnos(indexkey,
										   1, 0,
										   attmap, attmap_length,
										   InvalidOid, &found_whole_row);

			/* As in transformTableLikeClause, reject whole-row variables */
			if (found_whole_row)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cannot convert whole-row table reference"),
						 errdetail("Index \"%s\" contains a whole-row table reference.",
								   RelationGetRelationName(source_idx))));

			iparam->name = NULL;
			iparam->expr = indexkey;

			keycoltype = exprType(indexkey);
		}

		/* Copy the original index column name */
		iparam->indexcolname = pstrdup(NameStr(attrs[keyno]->attname));

		/* Add the collation name, if non-default */
		iparam->collation = get_collation(indcollation->values[keyno], keycoltype);

		/* Add the operator class name, if non-default */
		iparam->opclass = get_opclass(indclass->values[keyno], keycoltype);

		iparam->ordering = SORTBY_DEFAULT;
		iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;

		/* Adjust options if necessary */
		if (source_idx->rd_amroutine->amcanorder)
		{
			/*
			 * If it supports sort ordering, copy DESC and NULLS opts. Don't
			 * set non-default settings unnecessarily, though, so as to
			 * improve the chance of recognizing equivalence to constraint
			 * indexes.
			 */
			if (opt & INDOPTION_DESC)
			{
				iparam->ordering = SORTBY_DESC;
				if ((opt & INDOPTION_NULLS_FIRST) == 0)
					iparam->nulls_ordering = SORTBY_NULLS_LAST;
			}
			else
			{
				if (opt & INDOPTION_NULLS_FIRST)
					iparam->nulls_ordering = SORTBY_NULLS_FIRST;
			}
		}

		index->indexParams = lappend(index->indexParams, iparam);
	}

	/* Copy reloptions if any */
	datum = SysCacheGetAttr(RELOID, ht_idxrel,
							Anum_pg_class_reloptions, &isnull);
	if (!isnull)
		index->options = untransformRelOptions(datum);

	/* If it's a partial index, decompile and append the predicate */
	datum = SysCacheGetAttr(INDEXRELID, ht_idx,
							Anum_pg_index_indpred, &isnull);
	if (!isnull)
	{
		char	   *pred_str;
		Node	   *pred_tree;
		bool		found_whole_row;

		/* Convert text string to node tree */
		pred_str = TextDatumGetCString(datum);
		pred_tree = (Node *) stringToNode(pred_str);

		/* Adjust Vars to match new table's column numbering */
		pred_tree = map_variable_attnos(pred_tree,
										1, 0,
										attmap, attmap_length,
										InvalidOid, &found_whole_row);

		/* As in transformTableLikeClause, reject whole-row variables */
		if (found_whole_row)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("cannot convert whole-row table reference"),
					 errdetail("Index \"%s\" contains a whole-row table reference.",
							   RelationGetRelationName(source_idx))));

		index->whereClause = pred_tree;
	}

	/* Clean up */
	ReleaseSysCache(ht_idxrel);
	ReleaseSysCache(ht_am);

	return index;
}


static void strcpy_url(char *output, const char *url, bool relative)
{
  /* we must add this with whitespace-replacing */
  bool left = TRUE;
  const unsigned char *iptr;
  char *optr = output;
  const unsigned char *host_sep = (const unsigned char *) url;

  if(!relative)
    host_sep = (const unsigned char *) find_host_sep(url);

  for(iptr = (unsigned char *)url;    /* read from here */
      *iptr;         /* until zero byte */
      iptr++) {

    if(iptr < host_sep) {
      *optr++ = *iptr;
      continue;
    }

    if(*iptr == ' ') {
      if(left) {
        *optr++='%'; /* add a '%' */
        *optr++='2'; /* add a '2' */
        *optr++='0'; /* add a '0' */
      }
      else
        *optr++='+'; /* add a '+' here */
      continue;
    }

    if(*iptr == '?')
      left = FALSE;

    if(urlchar_needs_escaping(*iptr)) {
      msnprintf(optr, 4, "%%%02x", *iptr);
      optr += 3;
    }
    else
      *optr++ = *iptr;
  }
  *optr = 0; /* null-terminate output buffer */

}


static bool urlchar_needs_escaping(int c)
{
  return !(ISCNTRL(c) || ISSPACE(c) || ISGRAPH(c));
}


static bool ipv4_normalize(const char *hostname, char *outp, size_t olen)
{
  bool done = FALSE;
  int n = 0;
  const char *c = hostname;
  unsigned long parts[4] = {0, 0, 0, 0};

  while(!done) {
    char *endp;
    unsigned long l;
    if((*c < '0') || (*c > '9'))
      /* most importantly this doesn't allow a leading plus or minus */
      return FALSE;
    l = strtoul(c, &endp, 0);

    /* overflow or nothing parsed at all */
    if(((l == ULONG_MAX) && (errno == ERANGE)) ||  (endp == c))
      return FALSE;

#if SIZEOF_LONG > 4
    /* a value larger than 32 bits */
    if(l > UINT_MAX)
      return FALSE;
#endif

    parts[n] = l;
    c = endp;

    switch (*c) {
    case '.' :
      if(n == 3)
        return FALSE;
      n++;
      c++;
      break;

    case '\0':
      done = TRUE;
      break;

    default:
      return FALSE;
    }
  }

  /* this is deemed a valid IPv4 numerical address */

  switch(n) {
  case 0: /* a -- 32 bits */
    msnprintf(outp, olen, "%u.%u.%u.%u",
              parts[0] >> 24, (parts[0] >> 16) & 0xff,
              (parts[0] >> 8) & 0xff, parts[0] & 0xff);
    break;
  case 1: /* a.b -- 8.24 bits */
    if((parts[0] > 0xff) || (parts[1] > 0xffffff))
      return FALSE;
    msnprintf(outp, olen, "%u.%u.%u.%u",
              parts[0], (parts[1] >> 16) & 0xff,
              (parts[1] >> 8) & 0xff, parts[1] & 0xff);
    break;
  case 2: /* a.b.c -- 8.8.16 bits */
    if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xffff))
      return FALSE;
    msnprintf(outp, olen, "%u.%u.%u.%u",
              parts[0], parts[1], (parts[2] >> 8) & 0xff,
              parts[2] & 0xff);
    break;
  case 3: /* a.b.c.d -- 8.8.8.8 bits */
    if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xff) ||
       (parts[3] > 0xff))
      return FALSE;
    msnprintf(outp, olen, "%u.%u.%u.%u",
              parts[0], parts[1], parts[2], parts[3]);
    break;
  }
  return TRUE;
}


struct ldb_dn *samdb_infrastructure_dn(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx)
{
       struct ldb_dn *new_dn;

       new_dn = ldb_dn_copy(mem_ctx, ldb_get_default_basedn(sam_ctx));
       if ( ! ldb_dn_add_child_fmt(new_dn, "CN=Infrastructure")) {
               talloc_free(new_dn);
               return NULL;
       }
       return new_dn;
}


static int compare_DomainInfo(struct lsa_DomainInfo *e1, struct lsa_DomainInfo *e2)
{
	return strcasecmp_m(e1->name.string, e2->name.string);
}


static int samldb_check_pwd_last_set_acl(struct samldb_ctx *ac,
					 struct dom_sid *sid)
{
	struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
	int ret = 0;
	struct security_token *user_token = NULL;
	struct security_descriptor *domain_sd = NULL;
	struct ldb_dn *domain_dn = ldb_get_default_basedn(ldb_module_get_ctx(ac->module));
	const char *operation = "";
	const struct dsdb_class *objectclass = NULL;

	if (dsdb_module_am_system(ac->module)) {
		return LDB_SUCCESS;
	}

	switch (ac->req->operation) {
	case LDB_ADD:
		operation = "add";
		break;
	case LDB_MODIFY:
		operation = "modify";
		break;
	default:
		return ldb_module_operr(ac->module);
	}

	user_token = acl_user_token(ac->module);
	if (user_token == NULL) {
		return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
	}

	ret = samldb_get_domain_secdesc_and_oc(ac, &domain_sd, &objectclass);
	if (ret != LDB_SUCCESS) {
		return ret;
	}
	ret = acl_check_extended_right(ac,
				       ac->module,
				       ac->req,
				       objectclass,
				       domain_sd,
				       user_token,
				       GUID_DRS_UNEXPIRE_PASSWORD,
				       SEC_ADS_CONTROL_ACCESS,
				       sid);
	if (ret != LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) {
		return ret;
	}

	ldb_debug_set(ldb, LDB_DEBUG_WARNING,
		      "Failed to %s %s: "
		      "Setting pwdLastSet to -1 requires the "
		      "Unexpire-Password right that was not given "
		      "on the Domain object",
		      operation,
		      ldb_dn_get_linearized(ac->msg->dn));
	dsdb_acl_debug(domain_sd, user_token,
		       domain_dn, true, 10);

	return ret;
}


static int replmd_op_callback(struct ldb_request *req, struct ldb_reply *ares)
{
	int ret;
	struct replmd_replicated_request *ac =
		talloc_get_type_abort(req->context, struct replmd_replicated_request);
	struct replmd_private *replmd_private =
		talloc_get_type_abort(ldb_module_get_private(ac->module), struct replmd_private);
	struct nc_entry *modified_partition;
	struct ldb_control *partition_ctrl;
	const struct dsdb_control_current_partition *partition;

	struct ldb_control **controls;

	partition_ctrl = ldb_reply_get_control(ares, DSDB_CONTROL_CURRENT_PARTITION_OID);

	controls = ares->controls;
	if (ldb_request_get_control(ac->req,
				    DSDB_CONTROL_CURRENT_PARTITION_OID) == NULL) {
		/*
		 * Remove the current partition control from what we pass up
		 * the chain if it hasn't been requested manually.
		 */
		controls = ldb_controls_except_specified(ares->controls, ares,
							 partition_ctrl);
	}

	if (ares->error != LDB_SUCCESS) {
		struct GUID_txt_buf guid_txt;
		struct ldb_message *msg = NULL;
		char *s = NULL;

		if (ac->apply_mode == false) {
			DBG_NOTICE("Originating update failure. Error is: %s\n",
				   ldb_strerror(ares->error));
			return ldb_module_done(ac->req, controls,
					       ares->response, ares->error);
		}

		msg = ac->objs->objects[ac->index_current].msg;
		/*
		 * Set at DBG_NOTICE as once these start to happe, they
		 * will happen a lot until resolved, due to repeated
		 * replication.  The caller will probably print the
		 * ldb error string anyway.
		 */
		DBG_NOTICE("DRS replication apply failure for %s. Error is: %s\n",
			   ldb_dn_get_linearized(msg->dn),
			   ldb_strerror(ares->error));

		s = ldb_ldif_message_redacted_string(ldb_module_get_ctx(ac->module),
						     ac,
						     LDB_CHANGETYPE_ADD,
						     msg);

		DBG_INFO("Failing DRS %s replication message was %s:\n%s\n",
			 ac->search_msg == NULL ? "ADD" : "MODIFY",
			 GUID_buf_string(&ac->objs->objects[ac->index_current].object_guid,
					 &guid_txt),
			 s);
		talloc_free(s);
		return ldb_module_done(ac->req, controls,
				       ares->response, ares->error);
	}

	if (ares->type != LDB_REPLY_DONE) {
		ldb_set_errstring(ldb_module_get_ctx(ac->module), "Invalid reply type for notify\n!");
		return ldb_module_done(ac->req, NULL,
				       NULL, LDB_ERR_OPERATIONS_ERROR);
	}

	if (ac->apply_mode == false) {
		struct la_backlink *bl;
		/*
		 * process our backlink list after an replmd_add(),
		 * creating and deleting backlinks as necessary (this
		 * code is sync).  The other cases are handled inline
		 * with the modify.
		 */
		for (bl=ac->la_backlinks; bl; bl=bl->next) {
			ret = replmd_process_backlink(ac->module, bl, ac->req);
			if (ret != LDB_SUCCESS) {
				return ldb_module_done(ac->req, NULL,
						       NULL, ret);
			}
		}
	}

	if (!partition_ctrl) {
		ldb_set_errstring(ldb_module_get_ctx(ac->module),"No partition control on reply");
		return ldb_module_done(ac->req, NULL,
				       NULL, LDB_ERR_OPERATIONS_ERROR);
	}

	partition = talloc_get_type_abort(partition_ctrl->data,
				    struct dsdb_control_current_partition);

	if (ac->seq_num > 0) {
		for (modified_partition = replmd_private->ncs; modified_partition;
		     modified_partition = modified_partition->next) {
			if (ldb_dn_compare(modified_partition->dn, partition->dn) == 0) {
				break;
			}
		}

		if (modified_partition == NULL) {
			modified_partition = talloc_zero(replmd_private, struct nc_entry);
			if (!modified_partition) {
				ldb_oom(ldb_module_get_ctx(ac->module));
				return ldb_module_done(ac->req, NULL,
						       NULL, LDB_ERR_OPERATIONS_ERROR);
			}
			modified_partition->dn = ldb_dn_copy(modified_partition, partition->dn);
			if (!modified_partition->dn) {
				ldb_oom(ldb_module_get_ctx(ac->module));
				return ldb_module_done(ac->req, NULL,
						       NULL, LDB_ERR_OPERATIONS_ERROR);
			}
			DLIST_ADD(replmd_private->ncs, modified_partition);
		}

		if (ac->seq_num > modified_partition->mod_usn) {
			modified_partition->mod_usn = ac->seq_num;
			if (ac->is_urgent) {
				modified_partition->mod_usn_urgent = ac->seq_num;
			}
		}
		if (!ac->apply_mode) {
			replmd_private->originating_updates = true;
		}
	}

	if (ac->apply_mode) {
		ret = replmd_replicated_apply_isDeleted(ac);
		if (ret != LDB_SUCCESS) {
			return ldb_module_done(ac->req, NULL, NULL, ret);
		}
		return ret;
	} else {
		/* free the partition control container here, for the
		 * common path.  Other cases will have it cleaned up
		 * eventually with the ares */
		talloc_free(partition_ctrl);
		return ldb_module_done(ac->req, controls,
				       ares->response, LDB_SUCCESS);
	}
}


static int partition_load_modules(struct ldb_context *ldb,
				  struct partition_private_data *data, struct ldb_message *msg)
{
	unsigned int i;
	struct ldb_message_element *modules_attributes = ldb_msg_find_element(msg, "modules");
	talloc_free(data->modules);
	if (!modules_attributes) {
		return LDB_SUCCESS;
	}

	data->modules = talloc_array(data, struct partition_module *, modules_attributes->num_values + 1);
	if (!data->modules) {
		return ldb_oom(ldb);
	}

	for (i=0; i < modules_attributes->num_values; i++) {
		char *p;
		DATA_BLOB dn_blob;
		data->modules[i] = talloc(data->modules, struct partition_module);
		if (!data->modules[i]) {
			return ldb_oom(ldb);
		}

		dn_blob = modules_attributes->values[i];

		p = strchr((const char *)dn_blob.data, ':');
		if (!p) {
			ldb_asprintf_errstring(ldb,
					       "partition_load_modules: "
					       "invalid form for partition module record (missing ':'): %s", (const char *)dn_blob.data);
			return LDB_ERR_CONSTRAINT_VIOLATION;
		}
		/* Now trim off the filename */
		dn_blob.length = ((uint8_t *)p - dn_blob.data);

		p++;
		data->modules[i]->modules = ldb_modules_list_from_string(ldb, data->modules[i],
									 p);

		if (dn_blob.length == 1 && dn_blob.data[0] == '*') {
			data->modules[i]->dn = NULL;
		} else {
			data->modules[i]->dn = ldb_dn_from_ldb_val(data->modules[i], ldb, &dn_blob);
			if (!data->modules[i]->dn || !ldb_dn_validate(data->modules[i]->dn)) {
				return ldb_operr(ldb);
			}
		}
	}
	data->modules[i] = NULL;
	return LDB_SUCCESS;
}


static NTSTATUS dcesrv_lsa_QueryTrustedDomainInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx,
					   struct lsa_QueryTrustedDomainInfo *r)
{
	union lsa_TrustedDomainInfo *info = NULL;
	struct dcesrv_handle *h;
	struct lsa_trusted_domain_state *trusted_domain_state;
	struct ldb_message *msg;
	int ret;
	struct ldb_message **res;
	const char *attrs[] = {
		"flatname",
		"trustPartner",
		"securityIdentifier",
		"trustDirection",
		"trustType",
		"trustAttributes",
		"msDs-supportedEncryptionTypes",
		NULL
	};

	DCESRV_PULL_HANDLE(h, r->in.trustdom_handle, LSA_HANDLE_TRUSTED_DOMAIN);

	trusted_domain_state = talloc_get_type(h->data, struct lsa_trusted_domain_state);

	/* pull all the user attributes */
	ret = gendb_search_dn(trusted_domain_state->policy->sam_ldb, mem_ctx,
			      trusted_domain_state->trusted_domain_dn, &res, attrs);
	if (ret != 1) {
		return NT_STATUS_INTERNAL_DB_CORRUPTION;
	}
	msg = res[0];

	info = talloc_zero(mem_ctx, union lsa_TrustedDomainInfo);
	if (!info) {
		return NT_STATUS_NO_MEMORY;
	}
	*r->out.info = info;

	switch (r->in.level) {
	case LSA_TRUSTED_DOMAIN_INFO_NAME:
		info->name.netbios_name.string
			= ldb_msg_find_attr_as_string(msg, "flatname", NULL);
		break;
	case LSA_TRUSTED_DOMAIN_INFO_POSIX_OFFSET:
		info->posix_offset.posix_offset
			= ldb_msg_find_attr_as_uint(msg, "posixOffset", 0);
		break;
#if 0  /* Win2k3 doesn't implement this */
	case LSA_TRUSTED_DOMAIN_INFO_BASIC:
		r->out.info->info_basic.netbios_name.string
			= ldb_msg_find_attr_as_string(msg, "flatname", NULL);
		r->out.info->info_basic.sid
			= samdb_result_dom_sid(mem_ctx, msg, "securityIdentifier");
		break;
#endif
	case LSA_TRUSTED_DOMAIN_INFO_INFO_EX:
		return fill_trust_domain_ex(mem_ctx, msg, &info->info_ex);

	case LSA_TRUSTED_DOMAIN_INFO_FULL_INFO:
		ZERO_STRUCT(info->full_info);
		return fill_trust_domain_ex(mem_ctx, msg, &info->full_info.info_ex);
	case LSA_TRUSTED_DOMAIN_INFO_FULL_INFO_2_INTERNAL:
		ZERO_STRUCT(info->full_info2_internal);
		info->full_info2_internal.posix_offset.posix_offset
			= ldb_msg_find_attr_as_uint(msg, "posixOffset", 0);
		return fill_trust_domain_ex(mem_ctx, msg, &info->full_info2_internal.info.info_ex);

	case LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES:
		info->enc_types.enc_types
			= ldb_msg_find_attr_as_uint(msg, "msDs-supportedEncryptionTypes", KERB_ENCTYPE_RC4_HMAC_MD5);
		break;

	case LSA_TRUSTED_DOMAIN_INFO_CONTROLLERS:
	case LSA_TRUSTED_DOMAIN_INFO_INFO_EX2_INTERNAL:
		/* oops, we don't want to return the info after all */
		talloc_free(info);
		*r->out.info = NULL;
		return NT_STATUS_INVALID_PARAMETER;
	default:
		/* oops, we don't want to return the info after all */
		talloc_free(info);
		*r->out.info = NULL;
		return NT_STATUS_INVALID_INFO_CLASS;
	}

	return NT_STATUS_OK;
}


static int samldb_rodc_add(struct samldb_ctx *ac)
{
	struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
	uint32_t krbtgt_number, i_start, i;
	int ret;
	struct ldb_val newpass_utf16;

	/* find a unused msDS-SecondaryKrbTgtNumber */
	i_start = generate_random() & 0xFFFF;
	if (i_start == 0) {
		i_start = 1;
	}

	for (i=i_start; i<=0xFFFF; i++) {
		if (samldb_krbtgtnumber_available(ac, i)) {
			krbtgt_number = i;
			goto found;
		}
	}
	for (i=1; i<i_start; i++) {
		if (samldb_krbtgtnumber_available(ac, i)) {
			krbtgt_number = i;
			goto found;
		}
	}

	ldb_asprintf_errstring(ldb,
			       "%08X: Unable to find available msDS-SecondaryKrbTgtNumber",
			       W_ERROR_V(WERR_NO_SYSTEM_RESOURCES));
	return LDB_ERR_OTHER;

found:

	ldb_msg_remove_attr(ac->msg, "msDS-SecondaryKrbTgtNumber");
	ret = samdb_msg_append_uint(ldb, ac->msg, ac->msg,
				    "msDS-SecondaryKrbTgtNumber", krbtgt_number,
				    LDB_FLAG_INTERNAL_DISABLE_VALIDATION);
	if (ret != LDB_SUCCESS) {
		return ldb_operr(ldb);
	}

	ret = ldb_msg_add_fmt(ac->msg, "sAMAccountName", "krbtgt_%u",
			      krbtgt_number);
	if (ret != LDB_SUCCESS) {
		return ldb_operr(ldb);
	}

	newpass_utf16 = data_blob_talloc_zero(ac->module, 256);
	if (newpass_utf16.data == NULL) {
		return ldb_oom(ldb);
	}
	/*
	 * Note that the password_hash module will ignore
	 * this value and use it's own generate_secret_buffer()
	 * that's why we can just use generate_random_buffer()
	 * here.
	 */
	generate_random_buffer(newpass_utf16.data, newpass_utf16.length);
	ret = ldb_msg_add_steal_value(ac->msg, "clearTextPassword", &newpass_utf16);
	if (ret != LDB_SUCCESS) {
		return ldb_operr(ldb);
	}

	return samldb_next_step(ac);
}


create_la_entry(struct replmd_private *replmd_private,
		struct drsuapi_DsReplicaLinkedAttribute *la,
		uint32_t dsdb_repl_flags)
{
	struct la_entry *la_entry;

	if (replmd_private->la_ctx == NULL) {
		replmd_private->la_ctx = talloc_new(replmd_private);
	}
	la_entry = talloc(replmd_private->la_ctx, struct la_entry);
	if (la_entry == NULL) {
		return NULL;
	}
	la_entry->la = talloc(la_entry,
			      struct drsuapi_DsReplicaLinkedAttribute);
	if (la_entry->la == NULL) {
		talloc_free(la_entry);
		return NULL;
	}
	*la_entry->la = *la;
	la_entry->dsdb_repl_flags = dsdb_repl_flags;

	/*
	 * we need to steal the non-scalars so they stay
	 * around until the end of the transaction
	 */
	talloc_steal(la_entry->la, la_entry->la->identifier);
	talloc_steal(la_entry->la, la_entry->la->value.blob);

	return la_entry;
}


const char *samdb_forest_name(struct ldb_context *ldb, TALLOC_CTX *mem_ctx)
{
	const char *forest_name = ldb_dn_canonical_string(mem_ctx,
							  ldb_get_root_basedn(ldb));
	char *p;

	if (forest_name == NULL) {
		return NULL;
	}

	p = strchr(forest_name, '/');
	if (p) {
		*p = '\0';
	}

	return forest_name;
}


static int replmd_update_la_val(TALLOC_CTX *mem_ctx, struct ldb_val *v, struct dsdb_dn *dsdb_dn,
				struct dsdb_dn *old_dsdb_dn, const struct GUID *invocation_id,
				uint64_t usn, uint64_t local_usn, NTTIME nttime,
				bool deleted)
{
	uint32_t old_version;
	uint32_t version = RMD_VERSION_INITIAL;
	NTSTATUS status;

	/*
	 * We're updating the linked attribute locally, so increase the version
	 * by 1 so that other DCs will see the change when it gets replicated out
	 */
	status = dsdb_get_extended_dn_uint32(old_dsdb_dn->dn, &old_version,
					     "RMD_VERSION");

	if (NT_STATUS_IS_OK(status)) {
		version = old_version + 1;
	}

	return replmd_set_la_val(mem_ctx, v, dsdb_dn, old_dsdb_dn, invocation_id,
				 usn, local_usn, nttime, version, deleted);
}


int samdb_msg_add_addval(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx,
			 struct ldb_message *msg, const char *attr_name,
			 const char *value)
{
	struct ldb_message_element *el;
	struct ldb_val val;
	char *v;
	unsigned int i;
	bool found = false;
	int ret;

	v = talloc_strdup(mem_ctx, value);
	if (v == NULL) {
		return ldb_oom(sam_ldb);
	}

	val.data = (uint8_t *) v;
	val.length = strlen(v);

	if (val.length == 0) {
		/* allow empty strings as non-existent attributes */
		return LDB_SUCCESS;
	}

	for (i = 0; i < msg->num_elements; i++) {
		el = &msg->elements[i];
		if ((ldb_attr_cmp(el->name, attr_name) == 0) &&
		    (LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_ADD)) {
			found = true;
			break;
		}
	}
	if (!found) {
		ret = ldb_msg_add_empty(msg, attr_name, LDB_FLAG_MOD_ADD,
					&el);
		if (ret != LDB_SUCCESS) {
			return ret;
		}
	}

	ret = ldb_msg_element_add_value(msg->elements, el, &val);
	if (ret != LDB_SUCCESS) {
		return ldb_oom(sam_ldb);
	}

	return LDB_SUCCESS;
}


static int map_modify_do_local(struct map_context *ac)
{
	struct ldb_request *local_req;
	struct ldb_context *ldb;
	int ret;

	ldb = ldb_module_get_ctx(ac->module);

	if (ac->local_dn == NULL) {
		/* No local record present, add it instead */
		/* Add local 'IS_MAPPED' */
		/* TODO: use GUIDs here instead */
		ret = ldb_msg_append_linearized_dn(ac->local_msg, IS_MAPPED,
						   ac->remote_req->op.mod.message->dn,
						   LDB_FLAG_MOD_ADD);
		if (ret != 0) {
			return LDB_ERR_OPERATIONS_ERROR;
		}

		/* Prepare the local operation */
		ret = ldb_build_add_req(&local_req, ldb, ac,
					ac->local_msg,
					ac->req->controls,
					ac,
					map_op_local_callback,
					ac->req);
		LDB_REQ_SET_LOCATION(local_req);
		if (ret != LDB_SUCCESS) {
			return LDB_ERR_OPERATIONS_ERROR;
		}
	} else {
		/* Prepare the local operation */
		ret = ldb_build_mod_req(&local_req, ldb, ac,
					ac->local_msg,
					ac->req->controls,
					ac,
					map_op_local_callback,
					ac->req);
		LDB_REQ_SET_LOCATION(local_req);
		if (ret != LDB_SUCCESS) {
			return LDB_ERR_OPERATIONS_ERROR;
		}
	}

	return ldb_next_request(ac->module, local_req);
}


static NTSTATUS dcesrv_lsa_CREDRDELETE(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx,
		       struct lsa_CREDRDELETE *r)
{
	DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR);
}


static NTSTATUS pdb_samba_dsdb_enum_trusted_domains(struct pdb_methods *m,
						    TALLOC_CTX *mem_ctx,
						    uint32_t *_num_domains,
						    struct pdb_trusted_domain ***_domains)
{
	struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
		m->private_data, struct pdb_samba_dsdb_state);
	TALLOC_CTX *tmp_ctx = talloc_stackframe();
	const char * const attrs[] = {
		"securityIdentifier",
		"flatName",
		"trustPartner",
		"trustAuthOutgoing",
		"trustAuthIncoming",
		"trustAttributes",
		"trustDirection",
		"trustType",
		"trustPosixOffset",
		"msDS-SupportedEncryptionTypes",
		"msDS-TrustForestTrustInfo",
		NULL
	};
	struct ldb_result *res = NULL;
	unsigned int i;
	struct pdb_trusted_domain **domains = NULL;
	NTSTATUS status;
	uint32_t di = 0;

	*_num_domains = 0;
	*_domains = NULL;

	status = dsdb_trust_search_tdos(state->ldb, NULL,
					attrs, tmp_ctx, &res);
	if (!NT_STATUS_IS_OK(status)) {
		DBG_ERR("dsdb_trust_search_tdos() - %s ", nt_errstr(status));
		TALLOC_FREE(tmp_ctx);
		return status;
	}

	if (res->count == 0) {
		TALLOC_FREE(tmp_ctx);
		return NT_STATUS_OK;
	}

	domains = talloc_zero_array(tmp_ctx, struct pdb_trusted_domain *,
				    res->count);
	if (domains == NULL) {
		TALLOC_FREE(tmp_ctx);
		return NT_STATUS_NO_MEMORY;
	}

	for (i = 0; i < res->count; i++) {
		struct ldb_message *msg = res->msgs[i];
		struct pdb_trusted_domain *d = NULL;

		status = pdb_samba_dsdb_msg_to_trusted_domain(msg, domains, &d);
		if (!NT_STATUS_IS_OK(status)) {
			DBG_ERR("pdb_samba_dsdb_msg_to_trusted_domain() - %s ",
				nt_errstr(status));
			TALLOC_FREE(tmp_ctx);
			return status;
		}

		domains[di++] = d;
	}

	domains = talloc_realloc(domains, domains, struct pdb_trusted_domain *,
				 di);
	*_domains = talloc_move(mem_ctx, &domains);
	*_num_domains = di;
	TALLOC_FREE(tmp_ctx);
	return NT_STATUS_OK;
}


static NTSTATUS fake_enum_group_memberships(struct pdb_samba_dsdb_state *state,
					    TALLOC_CTX *mem_ctx,
					    struct samu *user,
					    struct dom_sid **pp_sids,
					    gid_t **pp_gids,
					    uint32_t *p_num_groups)
{
	NTSTATUS status;
	size_t num_groups = 0;
	struct dom_sid *group_sids = NULL;
	gid_t *gids = NULL;
	TALLOC_CTX *tmp_ctx;

	tmp_ctx = talloc_new(mem_ctx);
	NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);

	if (user->group_sid) {
		struct id_map *id_maps[2];
		struct id_map id_map;

		num_groups = 1;

		group_sids = talloc_array(tmp_ctx, struct dom_sid, num_groups);
		if (group_sids == NULL) {
			talloc_free(tmp_ctx);
			return NT_STATUS_NO_MEMORY;
		}
		gids = talloc_array(tmp_ctx, gid_t, num_groups);
		if (gids == NULL) {
			talloc_free(tmp_ctx);
			return NT_STATUS_NO_MEMORY;
		}

		group_sids[0] = *user->group_sid;

		ZERO_STRUCT(id_map);
		id_map.sid = &group_sids[0];
		id_maps[0] = &id_map;
		id_maps[1] = NULL;

		status = idmap_sids_to_xids(state->idmap_ctx, tmp_ctx, id_maps);
		if (!NT_STATUS_IS_OK(status)) {
			talloc_free(tmp_ctx);
			return status;
		}
		if (id_map.xid.type == ID_TYPE_GID || id_map.xid.type == ID_TYPE_BOTH) {
			gids[0] = id_map.xid.id;
		} else {
			struct dom_sid_buf buf1, buf2;
			DEBUG(1, (__location__
				  "Group %s, of which %s is a member, could not be converted to a GID\n",
				  dom_sid_str_buf(&group_sids[0], &buf1),
				  dom_sid_str_buf(&user->user_sid, &buf2)));
			talloc_free(tmp_ctx);
			/* We must error out, otherwise a user might
			 * avoid a DENY acl based on a group they
			 * missed out on */
			return NT_STATUS_NO_SUCH_GROUP;
		}
	}

	*pp_sids = talloc_steal(mem_ctx, group_sids);
	*pp_gids = talloc_steal(mem_ctx, gids);
	*p_num_groups = num_groups;
	talloc_free(tmp_ctx);
	return NT_STATUS_OK;
}


static NTSTATUS pdb_samba_dsdb_get_trusted_domain(struct pdb_methods *m,
						  TALLOC_CTX *mem_ctx,
						  const char *domain,
						  struct pdb_trusted_domain **td)
{
	struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
		m->private_data, struct pdb_samba_dsdb_state);
	TALLOC_CTX *tmp_ctx = talloc_stackframe();
	const char * const attrs[] = {
		"securityIdentifier",
		"flatName",
		"trustPartner",
		"trustAuthOutgoing",
		"trustAuthIncoming",
		"trustAttributes",
		"trustDirection",
		"trustType",
		"trustPosixOffset",
		"msDS-SupportedEncryptionTypes",
		"msDS-TrustForestTrustInfo",
		NULL
	};
	struct ldb_message *msg = NULL;
	struct pdb_trusted_domain *d = NULL;
	NTSTATUS status;

	status = dsdb_trust_search_tdo(state->ldb, domain, NULL,
				       attrs, tmp_ctx, &msg);
	if (!NT_STATUS_IS_OK(status)) {
		DBG_ERR("dsdb_trust_search_tdo(%s) - %s ",
			domain, nt_errstr(status));
		TALLOC_FREE(tmp_ctx);
		return status;
	}

	status = pdb_samba_dsdb_msg_to_trusted_domain(msg, mem_ctx, &d);
	if (!NT_STATUS_IS_OK(status)) {
		DBG_ERR("pdb_samba_dsdb_msg_to_trusted_domain(%s) - %s ",
			domain, nt_errstr(status));
		TALLOC_FREE(tmp_ctx);
		return status;
	}

	*td = d;
	TALLOC_FREE(tmp_ctx);
	return NT_STATUS_OK;
}


int samdb_msg_add_delete(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, struct ldb_message *msg,
			 const char *attr_name)
{
	/* we use an empty replace rather than a delete, as it allows for
	   dsdb_replace() to be used everywhere */
	return ldb_msg_add_empty(msg, attr_name, LDB_FLAG_MOD_REPLACE, NULL);
}


static int replmd_make_deleted_child_dn(TALLOC_CTX *tmp_ctx,
					struct ldb_context *ldb,
					struct ldb_dn *dn,
					const char *rdn_name,
					const struct ldb_val *rdn_value,
					struct GUID guid)
{
	return replmd_make_prefix_child_dn(tmp_ctx,
					   ldb, dn,
					   "DEL:",
					   rdn_name,
					   rdn_value,
					   guid);
}


WERROR dsdb_savereps(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx, struct ldb_dn *dn,
		     const char *attr, struct repsFromToBlob *r, uint32_t count)
{
	TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
	struct ldb_message *msg;
	struct ldb_message_element *el;
	unsigned int i;

	msg = ldb_msg_new(tmp_ctx);
	msg->dn = dn;
	if (ldb_msg_add_empty(msg, attr, LDB_FLAG_MOD_REPLACE, &el) != LDB_SUCCESS) {
		goto failed;
	}

	el->values = talloc_array(msg, struct ldb_val, count);
	if (!el->values) {
		goto failed;
	}

	for (i=0; i<count; i++) {
		struct ldb_val v;
		enum ndr_err_code ndr_err;

		ndr_err = ndr_push_struct_blob(&v, tmp_ctx,
					       &r[i],
					       (ndr_push_flags_fn_t)ndr_push_repsFromToBlob);
		if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
			goto failed;
		}

		el->num_values++;
		el->values[i] = v;
	}

	if (dsdb_modify(sam_ctx, msg, 0) != LDB_SUCCESS) {
		DEBUG(0,("Failed to store %s - %s\n", attr, ldb_errstring(sam_ctx)));
		goto failed;
	}

	talloc_free(tmp_ctx);

	return WERR_OK;

failed:
	talloc_free(tmp_ctx);
	return WERR_DS_DRA_INTERNAL_ERROR;
}


static int check_rename_constraints(struct ldb_message *msg,
				    struct samldb_ctx *ac,
				    struct ldb_dn *olddn, struct ldb_dn *newdn)
{
	struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
	struct ldb_dn *dn1, *dn2, *nc_root;
	int32_t systemFlags;
	bool move_op = false;
	bool rename_op = false;
	int ret;

	/* Skip the checks if old and new DN are the same, or if we have the
	 * relax control specified or if the returned objects is already
	 * deleted and needs only to be moved for consistency. */

	if (ldb_dn_compare(olddn, newdn) == 0) {
		return LDB_SUCCESS;
	}
	if (ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID) != NULL) {
		return LDB_SUCCESS;
	}

	if (ldb_msg_find_attr_as_bool(msg, "isDeleted", false)) {
		/*
		 * check originating request if we are supposed
		 * to "see" this record in first place.
		 */
		if (ldb_request_get_control(ac->req, LDB_CONTROL_SHOW_DELETED_OID) == NULL) {
			return LDB_ERR_NO_SUCH_OBJECT;
		}
		return LDB_ERR_UNWILLING_TO_PERFORM;
	}

	/* Objects under CN=System */

	dn1 = ldb_dn_copy(ac, ldb_get_default_basedn(ldb));
	if (dn1 == NULL) return ldb_oom(ldb);

	if ( ! ldb_dn_add_child_fmt(dn1, "CN=System")) {
		talloc_free(dn1);
		return LDB_ERR_OPERATIONS_ERROR;
	}

	if ((ldb_dn_compare_base(dn1, olddn) == 0) &&
	    (ldb_dn_compare_base(dn1, newdn) != 0)) {
		talloc_free(dn1);
		ldb_asprintf_errstring(ldb,
				       "subtree_rename: Cannot move/rename %s. Objects under CN=System have to stay under it!",
				       ldb_dn_get_linearized(olddn));
		return LDB_ERR_OTHER;
	}

	talloc_free(dn1);

	/* LSA objects */

	if ((samdb_find_attribute(ldb, msg, "objectClass", "secret") != NULL) ||
	    (samdb_find_attribute(ldb, msg, "objectClass", "trustedDomain") != NULL)) {
		ldb_asprintf_errstring(ldb,
				       "subtree_rename: Cannot move/rename %s. It's an LSA-specific object!",
				       ldb_dn_get_linearized(olddn));
		return LDB_ERR_UNWILLING_TO_PERFORM;
	}

	/* subnet objects */
	if (samdb_find_attribute(ldb, msg, "objectclass", "subnet") != NULL) {
		ret = samldb_verify_subnet(ac, newdn);
		if (ret != LDB_SUCCESS) {
			return ret;
		}
	}

	/* systemFlags */

	dn1 = ldb_dn_get_parent(ac, olddn);
	if (dn1 == NULL) return ldb_oom(ldb);
	dn2 = ldb_dn_get_parent(ac, newdn);
	if (dn2 == NULL) return ldb_oom(ldb);

	if (ldb_dn_compare(dn1, dn2) == 0) {
		rename_op = true;
	} else {
		move_op = true;
	}

	talloc_free(dn1);
	talloc_free(dn2);

	systemFlags = ldb_msg_find_attr_as_int(msg, "systemFlags", 0);

	/* Fetch name context */

	ret = dsdb_find_nc_root(ldb, ac, olddn, &nc_root);
	if (ret != LDB_SUCCESS) {
		return ret;
	}

	if (ldb_dn_compare(nc_root, ldb_get_schema_basedn(ldb)) == 0) {
		if (move_op) {
			ldb_asprintf_errstring(ldb,
					       "subtree_rename: Cannot move %s within schema partition",
					       ldb_dn_get_linearized(olddn));
			return LDB_ERR_UNWILLING_TO_PERFORM;
		}
		if (rename_op &&
		    (systemFlags & SYSTEM_FLAG_SCHEMA_BASE_OBJECT) != 0) {
			ldb_asprintf_errstring(ldb,
					       "subtree_rename: Cannot rename %s within schema partition",
					       ldb_dn_get_linearized(olddn));
			return LDB_ERR_UNWILLING_TO_PERFORM;
		}
	} else if (ldb_dn_compare(nc_root, ldb_get_config_basedn(ldb)) == 0) {
		if (move_op &&
		    (systemFlags & SYSTEM_FLAG_CONFIG_ALLOW_MOVE) == 0) {
			/* Here we have to do more: control the
			 * "ALLOW_LIMITED_MOVE" flag. This means that the
			 * grand-grand-parents of two objects have to be equal
			 * in order to perform the move (this is used for
			 * moving "server" objects in the "sites" container). */
			bool limited_move =
				systemFlags & SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE;

			if (limited_move) {
				dn1 = ldb_dn_copy(ac, olddn);
				if (dn1 == NULL) return ldb_oom(ldb);
				dn2 = ldb_dn_copy(ac, newdn);
				if (dn2 == NULL) return ldb_oom(ldb);

				limited_move &= ldb_dn_remove_child_components(dn1, 3);
				limited_move &= ldb_dn_remove_child_components(dn2, 3);
				limited_move &= ldb_dn_compare(dn1, dn2) == 0;

				talloc_free(dn1);
				talloc_free(dn2);
			}

			if (!limited_move
			    && ldb_request_get_control(ac->req, DSDB_CONTROL_RESTORE_TOMBSTONE_OID) == NULL) {
				ldb_asprintf_errstring(ldb,
						       "subtree_rename: Cannot move %s to %s in config partition",
						       ldb_dn_get_linearized(olddn), ldb_dn_get_linearized(newdn));
				return LDB_ERR_UNWILLING_TO_PERFORM;
			}
		}
		if (rename_op &&
		    (systemFlags & SYSTEM_FLAG_CONFIG_ALLOW_RENAME) == 0) {
			ldb_asprintf_errstring(ldb,
					       "subtree_rename: Cannot rename %s to %s within config partition",
					       ldb_dn_get_linearized(olddn), ldb_dn_get_linearized(newdn));
			return LDB_ERR_UNWILLING_TO_PERFORM;
		}
	} else if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) == 0) {
		if (move_op &&
		    (systemFlags & SYSTEM_FLAG_DOMAIN_DISALLOW_MOVE) != 0) {
			ldb_asprintf_errstring(ldb,
					       "subtree_rename: Cannot move %s to %s - DISALLOW_MOVE set",
					       ldb_dn_get_linearized(olddn), ldb_dn_get_linearized(newdn));
			return LDB_ERR_UNWILLING_TO_PERFORM;
		}
		if (rename_op &&
		    (systemFlags & SYSTEM_FLAG_DOMAIN_DISALLOW_RENAME) != 0) {
			ldb_asprintf_errstring(ldb,
						       "subtree_rename: Cannot rename %s to %s - DISALLOW_RENAME set",
					       ldb_dn_get_linearized(olddn), ldb_dn_get_linearized(newdn));
			return LDB_ERR_UNWILLING_TO_PERFORM;
		}
	}

	talloc_free(nc_root);

	return LDB_SUCCESS;
}


static int replmd_ldb_message_element_attid_sort(const struct ldb_message_element *e1,
						 const struct ldb_message_element *e2,
						 const struct dsdb_schema *schema)
{
	const struct dsdb_attribute *a1;
	const struct dsdb_attribute *a2;

	/*
	 * TODO: make this faster by caching the dsdb_attribute pointer
	 *       on the ldb_messag_element
	 */

	a1 = dsdb_attribute_by_lDAPDisplayName(schema, e1->name);
	a2 = dsdb_attribute_by_lDAPDisplayName(schema, e2->name);

	/*
	 * TODO: remove this check, we should rely on e1 and e2 having valid attribute names
	 *       in the schema
	 */
	if (!a1 || !a2) {
		return strcasecmp(e1->name, e2->name);
	}
	if (a1->attributeID_id == a2->attributeID_id) {
		return 0;
	}
	return a1->attributeID_id > a2->attributeID_id ? 1 : -1;
}


int dsdb_wellknown_dn(struct ldb_context *samdb, TALLOC_CTX *mem_ctx,
		      struct ldb_dn *nc_root, const char *wk_guid,
		      struct ldb_dn **wkguid_dn)
{
	TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
	const char *attrs[] = { NULL };
	int ret;
	struct ldb_dn *dn;
	struct ldb_result *res = NULL;

	/* construct the magic WKGUID DN */
	dn = ldb_dn_new_fmt(tmp_ctx, samdb, "<WKGUID=%s,%s>",
			    wk_guid, ldb_dn_get_linearized(nc_root));
	if (!wkguid_dn) {
		talloc_free(tmp_ctx);
		return ldb_operr(samdb);
	}

	ret = dsdb_search_dn(samdb, tmp_ctx, &res, dn, attrs,
			     DSDB_SEARCH_SHOW_DELETED |
			     DSDB_SEARCH_SHOW_RECYCLED);
	if (ret != LDB_SUCCESS) {
		talloc_free(tmp_ctx);
		return ret;
	}
	/* fix clang warning */
	if (res == NULL){
		talloc_free(tmp_ctx);
		return LDB_ERR_OTHER;
	}

	(*wkguid_dn) = talloc_steal(mem_ctx, res->msgs[0]->dn);
	talloc_free(tmp_ctx);
	return LDB_SUCCESS;
}


static int descriptor_extended(struct ldb_module *module, struct ldb_request *req)
{
	if (strcmp(req->op.extended.oid, DSDB_EXTENDED_SEC_DESC_PROPAGATION_OID) == 0) {
		return descriptor_extended_sec_desc_propagation(module, req);
	}

	return ldb_next_request(module, req);
}


static int map_rename_do_local(struct map_context *ac)
{
	struct ldb_request *local_req;
	struct ldb_context *ldb;
	int ret;

	ldb = ldb_module_get_ctx(ac->module);

	/* No local record, continue remotely */
	if (ac->local_dn == NULL) {
		/* Do the remote request. */
		return ldb_next_remote_request(ac->module, ac->remote_req);
	}

	/* Prepare the local operation */
	ret = ldb_build_rename_req(&local_req, ldb, ac,
				   ac->req->op.rename.olddn,
				   ac->req->op.rename.newdn,
				   ac->req->controls,
				   ac,
				   map_rename_local_callback,
				   ac->req);
	LDB_REQ_SET_LOCATION(local_req);
	if (ret != LDB_SUCCESS) {
		return LDB_ERR_OPERATIONS_ERROR;
	}

	return ldb_next_request(ac->module, local_req);
}


int dsdb_user_obj_set_primary_group_id(struct ldb_context *ldb, struct ldb_message *usr_obj,
				       uint32_t user_account_control, uint32_t *group_rid_p)
{
	int ret;
	uint32_t rid;

	rid = ds_uf2prim_group_rid(user_account_control);

	ret = samdb_msg_add_uint_flags(ldb, usr_obj, usr_obj,
				       "primaryGroupID", rid,
				       LDB_FLAG_MOD_REPLACE);
	if (ret != LDB_SUCCESS) {
		return ret;
	}

	if (group_rid_p) {
		*group_rid_p = rid;
	}

	return LDB_SUCCESS;
}


static int descriptor_rename(struct ldb_module *module, struct ldb_request *req)
{
	struct ldb_context *ldb = ldb_module_get_ctx(module);
	struct ldb_dn *olddn = req->op.rename.olddn;
	struct ldb_dn *newdn = req->op.rename.newdn;
	int ret;

	/* do not manipulate our control entries */
	if (ldb_dn_is_special(req->op.rename.olddn)) {
		return ldb_next_request(module, req);
	}

	ldb_debug(ldb, LDB_DEBUG_TRACE,"descriptor_rename: %s\n",
		  ldb_dn_get_linearized(olddn));

	if (ldb_dn_compare(olddn, newdn) != 0) {
		struct ldb_dn *nc_root;
		struct GUID guid;

		ret = dsdb_find_nc_root(ldb, req, newdn, &nc_root);
		if (ret != LDB_SUCCESS) {
			return ldb_oom(ldb);
		}

		ret = dsdb_module_guid_by_dn(module,
					     olddn,
					     &guid,
					     req);
		if (ret == LDB_SUCCESS) {
			/*
			 * Without disturbing any errors if the olddn
			 * does not exit, force SD propagation on
			 * this record (get a new inherited SD from
			 * the potentially new parent
			 *
			 * We don't now the parent guid here,
			 * but we're not in a hot code path here,
			 * as the "descriptor" module is located
			 * above the "repl_meta_data", only
			 * originating changes are handled here.
			 *
			 * If it turns out to be a problem we may
			 * search for the new parent guid.
			 */
			struct GUID parent_guid = { .time_low = 0 };

			ret = dsdb_module_schedule_sd_propagation(module,
								  nc_root,
								  guid,
								  parent_guid,
								  true);
			if (ret != LDB_SUCCESS) {
				return ldb_operr(ldb);
			}
		}
	}

	return ldb_next_request(module, req);
}


static int replmd_replicated_apply_search_callback(struct ldb_request *req,
						   struct ldb_reply *ares)
{
	struct replmd_replicated_request *ar = talloc_get_type(req->context,
					       struct replmd_replicated_request);
	int ret;

	if (!ares) {
		return ldb_module_done(ar->req, NULL, NULL,
					LDB_ERR_OPERATIONS_ERROR);
	}
	if (ares->error != LDB_SUCCESS &&
	    ares->error != LDB_ERR_NO_SUCH_OBJECT) {
		return ldb_module_done(ar->req, ares->controls,
					ares->response, ares->error);
	}

	switch (ares->type) {
	case LDB_REPLY_ENTRY:
		ar->search_msg = talloc_steal(ar, ares->message);
		break;

	case LDB_REPLY_REFERRAL:
		/* we ignore referrals */
		break;

	case LDB_REPLY_DONE:
	{
		struct replPropertyMetaData1 *md_remote;
		struct replPropertyMetaData1 *md_local;

		struct replPropertyMetaDataBlob omd;
		const struct ldb_val *omd_value;
		struct replPropertyMetaDataBlob *rmd;
		struct ldb_message *msg;
		int instanceType;
		ar->objs->objects[ar->index_current].local_parent_dn = NULL;
		ar->objs->objects[ar->index_current].last_known_parent = NULL;

		/*
		 * This is the ADD case, find the appropriate parent,
		 * as this object doesn't exist locally:
		 */
		if (ar->search_msg == NULL) {
			ret = replmd_replicated_apply_search_for_parent(ar);
			if (ret != LDB_SUCCESS) {
				return ldb_module_done(ar->req, NULL, NULL, ret);
			}
			talloc_free(ares);
			return LDB_SUCCESS;
		}

		/*
		 * Otherwise, in the MERGE case, work out if we are
		 * attempting a rename, and if so find the parent the
		 * newly renamed object wants to belong under (which
		 * may not be the parent in it's attached string DN
		 */
		rmd = ar->objs->objects[ar->index_current].meta_data;
		ZERO_STRUCT(omd);
		omd.version = 1;

		/* find existing meta data */
		omd_value = ldb_msg_find_ldb_val(ar->search_msg, "replPropertyMetaData");
		if (omd_value) {
			enum ndr_err_code ndr_err;
			ndr_err = ndr_pull_struct_blob(omd_value, ar, &omd,
						       (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
			if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
				NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
				return replmd_replicated_request_werror(ar, ntstatus_to_werror(nt_status));
			}

			if (omd.version != 1) {
				return replmd_replicated_request_werror(ar, WERR_DS_DRA_INTERNAL_ERROR);
			}
		}

		ar->local_parent_guid = samdb_result_guid(ar->search_msg, "parentGUID");

		instanceType = ldb_msg_find_attr_as_int(ar->search_msg, "instanceType", 0);
		if (((instanceType & INSTANCE_TYPE_IS_NC_HEAD) == 0)
		    && GUID_all_zero(&ar->local_parent_guid)) {
			DEBUG(0, ("Refusing to replicate new version of %s "
				  "as local object has an all-zero parentGUID attribute, "
				  "despite not being an NC root\n",
				  ldb_dn_get_linearized(ar->search_msg->dn)));
			return replmd_replicated_request_werror(ar, WERR_DS_DRA_INTERNAL_ERROR);
		}

		/*
		 * now we need to check for double renames. We could have a
		 * local rename pending which our replication partner hasn't
		 * received yet. We choose which one wins by looking at the
		 * attribute stamps on the two objects, the newer one wins.
		 *
		 * This also simply applies the correct algorithms for
		 * determining if a change was made to name at all, or
		 * if the object has just been renamed under the same
		 * parent.
		 */
		md_remote = replmd_replPropertyMetaData1_find_attid(rmd, DRSUAPI_ATTID_name);
		md_local = replmd_replPropertyMetaData1_find_attid(&omd, DRSUAPI_ATTID_name);
		if (!md_local) {
			DEBUG(0,(__location__ ": Failed to find name attribute in local LDB replPropertyMetaData for %s\n",
				 ldb_dn_get_linearized(ar->search_msg->dn)));
			return replmd_replicated_request_werror(ar, WERR_DS_DRA_DB_ERROR);
		}

		/*
		 * if there is no name attribute given then we have to assume the
		 *  object we've received has the older name
		 */
		if (replmd_replPropertyMetaData1_new_should_be_taken(
			    ar->objs->dsdb_repl_flags & DSDB_REPL_FLAG_PRIORITISE_INCOMING,
			    md_local, md_remote)) {
			struct GUID_txt_buf p_guid_local;
			struct GUID_txt_buf p_guid_remote;
			msg = ar->objs->objects[ar->index_current].msg;

			/* Merge on the existing object, with rename */

			DEBUG(4,(__location__ ": Looking for new parent for object %s currently under %s "
				 "as incoming object changing to %s under %s\n",
				 ldb_dn_get_linearized(ar->search_msg->dn),
				 GUID_buf_string(&ar->local_parent_guid, &p_guid_local),
				 ldb_dn_get_linearized(msg->dn),
				 GUID_buf_string(ar->objs->objects[ar->index_current].parent_guid,
						 &p_guid_remote)));
			ret = replmd_replicated_apply_search_for_parent(ar);
		} else {
			struct GUID_txt_buf p_guid_local;
			struct GUID_txt_buf p_guid_remote;
			msg = ar->objs->objects[ar->index_current].msg;

			/*
			 * Merge on the existing object, force no
			 * rename (code below just to explain why in
			 * the DEBUG() logs)
			 */

			if (strcmp(ldb_dn_get_linearized(ar->search_msg->dn),
				   ldb_dn_get_linearized(msg->dn)) == 0) {
				if (ar->objs->objects[ar->index_current].parent_guid != NULL &&
				    GUID_equal(&ar->local_parent_guid,
					       ar->objs->objects[ar->index_current].parent_guid)
				    == false) {
					DEBUG(4,(__location__ ": Keeping object %s at under %s "
						 "despite incoming object changing parent to %s\n",
						 ldb_dn_get_linearized(ar->search_msg->dn),
						 GUID_buf_string(&ar->local_parent_guid, &p_guid_local),
						 GUID_buf_string(ar->objs->objects[ar->index_current].parent_guid,
								 &p_guid_remote)));
				}
			} else {
				DEBUG(4,(__location__ ": Keeping object %s at under %s "
					 " and rejecting older rename to %s under %s\n",
					 ldb_dn_get_linearized(ar->search_msg->dn),
					 GUID_buf_string(&ar->local_parent_guid, &p_guid_local),
					 ldb_dn_get_linearized(msg->dn),
					 GUID_buf_string(ar->objs->objects[ar->index_current].parent_guid,
							 &p_guid_remote)));
			}
			/*
			 * This assignment ensures that the strcmp()
			 * and GUID_equal() calls in
			 * replmd_replicated_apply_merge() avoids the
			 * rename call
			 */
			ar->objs->objects[ar->index_current].parent_guid =
				&ar->local_parent_guid;

			msg->dn = ar->search_msg->dn;
			ret = replmd_replicated_apply_merge(ar);
		}
		if (ret != LDB_SUCCESS) {
			return ldb_module_done(ar->req, NULL, NULL, ret);
		}
	}
	}

	talloc_free(ares);
	return LDB_SUCCESS;
}


static NTSTATUS dcesrv_lsa_SetInfoPolicy2(struct dcesrv_call_state *dce_call,
				   TALLOC_CTX *mem_ctx,
				   struct lsa_SetInfoPolicy2 *r)
{
	/* need to support these */
	DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR);
}


int samdb_find_or_add_attribute(struct ldb_context *ldb, struct ldb_message *msg, const char *name, const char *set_value)
{
	return samdb_find_or_add_attribute_ex(ldb, msg, name, set_value, LDB_FLAG_MOD_ADD, NULL);
}


static int replmd_op_possible_conflict_callback(struct ldb_request *req, struct ldb_reply *ares, int (*callback)(struct ldb_request *req, struct ldb_reply *ares))
{
	struct ldb_dn *conflict_dn;
	struct replmd_replicated_request *ar =
		talloc_get_type_abort(req->context, struct replmd_replicated_request);
	struct ldb_result *res;
	int ret;
	bool rename_incoming_record;
	struct ldb_message *msg;
	struct ldb_request *down_req = NULL;

	/* call the normal callback for success */
	if (ares->error == LDB_SUCCESS) {
		return callback(req, ares);
	}

	/*
	 * we have a conflict, and need to decide if we will keep the
	 * new record or the old record
	 */

	msg = ar->objs->objects[ar->index_current].msg;
	conflict_dn = msg->dn;

	/* For failures other than conflicts, fail the whole operation here */
	if (ares->error != LDB_ERR_ENTRY_ALREADY_EXISTS) {
		ldb_asprintf_errstring(ldb_module_get_ctx(ar->module), "Failed to locally apply remote add of %s: %s",
				       ldb_dn_get_linearized(conflict_dn),
				       ldb_errstring(ldb_module_get_ctx(ar->module)));

		return ldb_module_done(ar->req, NULL, NULL,
				       LDB_ERR_OPERATIONS_ERROR);
	}

	ret = incoming_dn_should_be_renamed(req, ar, conflict_dn, &res,
					    &rename_incoming_record);
	if (ret != LDB_SUCCESS) {
		goto failed;
	}

	if (rename_incoming_record) {
		struct GUID guid;
		struct ldb_dn *new_dn;

		guid = samdb_result_guid(msg, "objectGUID");
		if (GUID_all_zero(&guid)) {
			DEBUG(0,(__location__ ": Failed to find objectGUID for conflicting incoming record %s\n",
				 ldb_dn_get_linearized(conflict_dn)));
			goto failed;
		}
		new_dn = replmd_conflict_dn(req,
					    ldb_module_get_ctx(ar->module),
					    conflict_dn, &guid);
		if (new_dn == NULL) {
			DEBUG(0,(__location__ ": Failed to form conflict DN for %s\n",
				 ldb_dn_get_linearized(conflict_dn)));
			goto failed;
		}

		DEBUG(2,(__location__ ": Resolving conflict record via incoming rename '%s' -> '%s'\n",
			 ldb_dn_get_linearized(conflict_dn), ldb_dn_get_linearized(new_dn)));

		/* re-submit the request, but with the new DN */
		callback = replmd_op_name_modify_callback;
		msg->dn = new_dn;
	} else {
		/* we are renaming the existing record */
		struct GUID guid;
		struct ldb_dn *new_dn;

		guid = samdb_result_guid(res->msgs[0], "objectGUID");
		if (GUID_all_zero(&guid)) {
			DEBUG(0,(__location__ ": Failed to find objectGUID for existing conflict record %s\n",
				 ldb_dn_get_linearized(conflict_dn)));
			goto failed;
		}

		new_dn = replmd_conflict_dn(req,
					    ldb_module_get_ctx(ar->module),
					    conflict_dn, &guid);
		if (new_dn == NULL) {
			DEBUG(0,(__location__ ": Failed to form conflict DN for %s\n",
				 ldb_dn_get_linearized(conflict_dn)));
			goto failed;
		}

		DEBUG(2,(__location__ ": Resolving conflict record via existing-record rename '%s' -> '%s'\n",
			 ldb_dn_get_linearized(conflict_dn), ldb_dn_get_linearized(new_dn)));

		ret = dsdb_module_rename(ar->module, conflict_dn, new_dn,
					 DSDB_FLAG_OWN_MODULE, req);
		if (ret != LDB_SUCCESS) {
			DEBUG(0,(__location__ ": Failed to rename conflict dn '%s' to '%s' - %s\n",
				 ldb_dn_get_linearized(conflict_dn),
				 ldb_dn_get_linearized(new_dn),
				 ldb_errstring(ldb_module_get_ctx(ar->module))));
			goto failed;
		}

		/*
		 * now we need to ensure that the rename is seen as an
		 * originating update. We do that with a modify.
		 */
		ret = replmd_name_modify(ar, req, new_dn);
		if (ret != LDB_SUCCESS) {
			goto failed;
		}

		DEBUG(2,(__location__ ": With conflicting record renamed, re-apply replicated creation of '%s'\n",
			 ldb_dn_get_linearized(req->op.add.message->dn)));
	}

	ret = ldb_build_add_req(&down_req,
				ldb_module_get_ctx(ar->module),
				req,
				msg,
				ar->controls,
				ar,
				callback,
				req);
	if (ret != LDB_SUCCESS) {
		goto failed;
	}
	LDB_REQ_SET_LOCATION(down_req);

	/* current partition control needed by "repmd_op_callback" */
	ret = ldb_request_add_control(down_req,
				      DSDB_CONTROL_CURRENT_PARTITION_OID,
				      false, NULL);
	if (ret != LDB_SUCCESS) {
		return replmd_replicated_request_error(ar, ret);
	}

	if (ar->objs->dsdb_repl_flags & DSDB_REPL_FLAG_PARTIAL_REPLICA) {
		/* this tells the partition module to make it a
		   partial replica if creating an NC */
		ret = ldb_request_add_control(down_req,
					      DSDB_CONTROL_PARTIAL_REPLICA,
					      false, NULL);
		if (ret != LDB_SUCCESS) {
			return replmd_replicated_request_error(ar, ret);
		}
	}

	/*
	 * Finally we re-run the add, otherwise the new record won't
	 * exist, as we are here because of that exact failure!
	 */
	return ldb_next_request(ar->module, down_req);
failed:

	/* on failure make the caller get the error. This means
	 * replication will stop with an error, but there is not much
	 * else we can do.
	 */
	if (ret == LDB_SUCCESS) {
		ret = LDB_ERR_OPERATIONS_ERROR;
	}
	return ldb_module_done(ar->req, NULL, NULL,
			       ret);
}


static int map_search_self_req(struct ldb_request **req,
				struct map_context *ac,
				struct ldb_dn *dn)
{
	/* attrs[] is returned from this function in
	 * ac->search_req->op.search.attrs, so it must be static, as
	 * otherwise the compiler can put it on the stack */
	static const char * const attrs[] = { IS_MAPPED, NULL };
	struct ldb_parse_tree *tree;

	/* Limit search to records with 'IS_MAPPED' present */
	tree = ldb_parse_tree(ac, "(" IS_MAPPED "=*)");
	if (tree == NULL) {
		map_oom(ac->module);
		return LDB_ERR_OPERATIONS_ERROR;
	}

	*req = map_search_base_req(ac, dn, attrs, tree,
				   ac, map_search_self_callback);
	if (*req == NULL) {
		return LDB_ERR_OPERATIONS_ERROR;
	}

	return LDB_SUCCESS;
}


static NTSTATUS pdb_samba_dsdb_getgrsid(struct pdb_methods *m, GROUP_MAP *map,
				 struct dom_sid sid)
{
	char *filter;
	NTSTATUS status;
	struct dom_sid_buf buf;

	filter = talloc_asprintf(talloc_tos(),
				 "(&(objectsid=%s)(objectclass=group))",
				 dom_sid_str_buf(&sid, &buf));
	if (filter == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	status = pdb_samba_dsdb_getgrfilter(m, map, "%s", filter);
	TALLOC_FREE(filter);
	return status;
}


static WERROR dcesrv_dssetup_DsRoleGetPrimaryDomainInformation(struct dcesrv_call_state *dce_call,
						 TALLOC_CTX *mem_ctx,
						 struct dssetup_DsRoleGetPrimaryDomainInformation *r)
{
	union dssetup_DsRoleInfo *info;

	info = talloc_zero(mem_ctx, union dssetup_DsRoleInfo);
	W_ERROR_HAVE_NO_MEMORY(info);

	switch (r->in.level) {
	case DS_ROLE_BASIC_INFORMATION:
	{
		enum dssetup_DsRole role = DS_ROLE_STANDALONE_SERVER;
		uint32_t flags = 0;
		const char *domain = NULL;
		const char *dns_domain = NULL;
		const char *forest = NULL;
		struct GUID domain_guid;
		struct lsa_policy_state *state;

		NTSTATUS status = dcesrv_lsa_get_policy_state(dce_call, mem_ctx,
							      0, /* we skip access checks */
							      &state);
		if (!NT_STATUS_IS_OK(status)) {
			return ntstatus_to_werror(status);
		}

		ZERO_STRUCT(domain_guid);

		switch (lpcfg_server_role(dce_call->conn->dce_ctx->lp_ctx)) {
		case ROLE_STANDALONE:
			role		= DS_ROLE_STANDALONE_SERVER;
			break;
		case ROLE_DOMAIN_MEMBER:
			role		= DS_ROLE_MEMBER_SERVER;
			break;
		case ROLE_ACTIVE_DIRECTORY_DC:
			if (samdb_is_pdc(state->sam_ldb)) {
				role	= DS_ROLE_PRIMARY_DC;
			} else {
				role    = DS_ROLE_BACKUP_DC;
			}
			break;
		}

		switch (lpcfg_server_role(dce_call->conn->dce_ctx->lp_ctx)) {
		case ROLE_STANDALONE:
			domain		= talloc_strdup(mem_ctx, lpcfg_workgroup(dce_call->conn->dce_ctx->lp_ctx));
			W_ERROR_HAVE_NO_MEMORY(domain);
			break;
		case ROLE_DOMAIN_MEMBER:
			domain		= talloc_strdup(mem_ctx, lpcfg_workgroup(dce_call->conn->dce_ctx->lp_ctx));
			W_ERROR_HAVE_NO_MEMORY(domain);
			/* TODO: what is with dns_domain and forest and guid? */
			break;
		case ROLE_ACTIVE_DIRECTORY_DC:
			flags		= DS_ROLE_PRIMARY_DS_RUNNING;

			if (state->mixed_domain == 1) {
				flags	|= DS_ROLE_PRIMARY_DS_MIXED_MODE;
			}

			domain		= state->domain_name;
			dns_domain	= state->domain_dns;
			forest		= state->forest_dns;

			domain_guid	= state->domain_guid;
			flags	|= DS_ROLE_PRIMARY_DOMAIN_GUID_PRESENT;
			break;
		}

		info->basic.role        = role;
		info->basic.flags       = flags;
		info->basic.domain      = domain;
		info->basic.dns_domain  = dns_domain;
		info->basic.forest      = forest;
		info->basic.domain_guid = domain_guid;

		r->out.info = info;
		return WERR_OK;
	}
	case DS_ROLE_UPGRADE_STATUS:
	{
		info->upgrade.upgrading     = DS_ROLE_NOT_UPGRADING;
		info->upgrade.previous_role = DS_ROLE_PREVIOUS_UNKNOWN;

		r->out.info = info;
		return WERR_OK;
	}
	case DS_ROLE_OP_STATUS:
	{
		info->opstatus.status = DS_ROLE_OP_IDLE;

		r->out.info = info;
		return WERR_OK;
	}
	default:
		return WERR_INVALID_PARAMETER;
	}
}


static int objectguid_add(struct ldb_module *module, struct ldb_request *req)
{
	struct ldb_context *ldb;
	struct ldb_request *down_req;
	struct ldb_message *msg;
	struct ldb_message_element *el;
	struct GUID guid;
	uint64_t seq_num;
	int ret;
	time_t t = time(NULL);
	struct og_context *ac;

	ldb = ldb_module_get_ctx(module);

	ldb_debug(ldb, LDB_DEBUG_TRACE, "objectguid_add_record\n");

	/* do not manipulate our control entries */
	if (ldb_dn_is_special(req->op.add.message->dn)) {
		return ldb_next_request(module, req);
	}

	el = ldb_msg_find_element(req->op.add.message, "objectGUID");
	if (el != NULL) {
		ldb_set_errstring(ldb,
				  "objectguid: objectGUID must not be specified!");
		return LDB_ERR_UNWILLING_TO_PERFORM;
	}

	ac = talloc(req, struct og_context);
	if (ac == NULL) {
		return ldb_oom(ldb);
	}
	ac->module = module;
	ac->req = req;

	/* we have to copy the message as the caller might have it as a const */
	msg = ldb_msg_copy_shallow(ac, req->op.add.message);
	if (msg == NULL) {
		talloc_free(ac);
		return ldb_operr(ldb);
	}

	/* a new GUID */
	guid = GUID_random();

	ret = dsdb_msg_add_guid(msg, &guid, "objectGUID");
	if (ret != LDB_SUCCESS) {
		return ret;
	}

	if (add_time_element(msg, "whenCreated", t) != LDB_SUCCESS ||
	    add_time_element(msg, "whenChanged", t) != LDB_SUCCESS) {
		return ldb_operr(ldb);
	}

	/* Get a sequence number from the backend */
	ret = ldb_sequence_number(ldb, LDB_SEQ_NEXT, &seq_num);
	if (ret == LDB_SUCCESS) {
		if (add_uint64_element(ldb, msg, "uSNCreated",
				       seq_num) != LDB_SUCCESS ||
		    add_uint64_element(ldb, msg, "uSNChanged",
				       seq_num) != LDB_SUCCESS) {
			return ldb_operr(ldb);
		}
	}

	ret = ldb_build_add_req(&down_req, ldb, ac,
				msg,
				req->controls,
				req, dsdb_next_callback,
				req);
	LDB_REQ_SET_LOCATION(down_req);
	if (ret != LDB_SUCCESS) {
		return ret;
	}

	/* go on with the call chain */
	return ldb_next_request(module, down_req);
}


static NTSTATUS dcesrv_lsa_QuerySecret(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx,
				struct lsa_QuerySecret *r)
{
	struct auth_session_info *session_info =
		dcesrv_call_session_info(dce_call);
	struct dcesrv_handle *h;
	struct lsa_secret_state *secret_state;
	struct ldb_message *msg;
	DATA_BLOB session_key;
	DATA_BLOB crypt_secret, secret;
	int ret;
	struct ldb_message **res;
	const char *attrs[] = {
		"currentValue",
		"priorValue",
		"lastSetTime",
		"priorSetTime",
		NULL
	};

	NTSTATUS nt_status;

	DCESRV_PULL_HANDLE(h, r->in.sec_handle, LSA_HANDLE_SECRET);

	/* Ensure user is permitted to read this... */
	switch (security_session_user_level(session_info, NULL))
	{
	case SECURITY_SYSTEM:
	case SECURITY_ADMINISTRATOR:
		break;
	default:
		/* Users and annonymous are not allowed to read secrets */
		return NT_STATUS_ACCESS_DENIED;
	}

	secret_state = h->data;

	/* pull all the user attributes */
	ret = gendb_search_dn(secret_state->sam_ldb, mem_ctx,
			      secret_state->secret_dn, &res, attrs);
	if (ret != 1) {
		return NT_STATUS_INTERNAL_DB_CORRUPTION;
	}
	msg = res[0];

	nt_status = dcesrv_transport_session_key(dce_call, &session_key);
	if (!NT_STATUS_IS_OK(nt_status)) {
		return nt_status;
	}

	if (r->in.old_val) {
		const struct ldb_val *prior_val;
		r->out.old_val = talloc_zero(mem_ctx, struct lsa_DATA_BUF_PTR);
		if (!r->out.old_val) {
			return NT_STATUS_NO_MEMORY;
		}
		prior_val = ldb_msg_find_ldb_val(msg, "priorValue");

		if (prior_val && prior_val->length) {
			secret.data = prior_val->data;
			secret.length = prior_val->length;

			/* Encrypt */
			crypt_secret = sess_encrypt_blob(mem_ctx, &secret, &session_key);
			if (!crypt_secret.length) {
				return NT_STATUS_NO_MEMORY;
			}
			r->out.old_val->buf = talloc(mem_ctx, struct lsa_DATA_BUF);
			if (!r->out.old_val->buf) {
				return NT_STATUS_NO_MEMORY;
			}
			r->out.old_val->buf->size = crypt_secret.length;
			r->out.old_val->buf->length = crypt_secret.length;
			r->out.old_val->buf->data = crypt_secret.data;
		}
	}

	if (r->in.old_mtime) {
		r->out.old_mtime = talloc(mem_ctx, NTTIME);
		if (!r->out.old_mtime) {
			return NT_STATUS_NO_MEMORY;
		}
		*r->out.old_mtime = ldb_msg_find_attr_as_uint64(msg, "priorSetTime", 0);
	}

	if (r->in.new_val) {
		const struct ldb_val *new_val;
		r->out.new_val = talloc_zero(mem_ctx, struct lsa_DATA_BUF_PTR);
		if (!r->out.new_val) {
			return NT_STATUS_NO_MEMORY;
		}

		new_val = ldb_msg_find_ldb_val(msg, "currentValue");

		if (new_val && new_val->length) {
			secret.data = new_val->data;
			secret.length = new_val->length;

			/* Encrypt */
			crypt_secret = sess_encrypt_blob(mem_ctx, &secret, &session_key);
			if (!crypt_secret.length) {
				return NT_STATUS_NO_MEMORY;
			}
			r->out.new_val->buf = talloc(mem_ctx, struct lsa_DATA_BUF);
			if (!r->out.new_val->buf) {
				return NT_STATUS_NO_MEMORY;
			}
			r->out.new_val->buf->length = crypt_secret.length;
			r->out.new_val->buf->size = crypt_secret.length;
			r->out.new_val->buf->data = crypt_secret.data;
		}
	}

	if (r->in.new_mtime) {
		r->out.new_mtime = talloc(mem_ctx, NTTIME);
		if (!r->out.new_mtime) {
			return NT_STATUS_NO_MEMORY;
		}
		*r->out.new_mtime = ldb_msg_find_attr_as_uint64(msg, "lastSetTime", 0);
	}

	return NT_STATUS_OK;
}


static int samldb_add(struct ldb_module *module, struct ldb_request *req)
{
	struct ldb_context *ldb;
	struct samldb_ctx *ac;
	struct ldb_message_element *el;
	int ret;
	char *referral = NULL;

	ldb = ldb_module_get_ctx(module);
	ldb_debug(ldb, LDB_DEBUG_TRACE, "samldb_add\n");

	/* do not manipulate our control entries */
	if (ldb_dn_is_special(req->op.add.message->dn)) {
		return ldb_next_request(module, req);
	}

	referral = refer_if_rodc(ldb, req, req->op.add.message->dn);
	if (referral != NULL) {
		ret = ldb_module_send_referral(req, referral);
		return ret;
	}

	el = ldb_msg_find_element(req->op.add.message, "userParameters");
	if (el != NULL && ldb_req_is_untrusted(req)) {
		const char *reason = "samldb_add: "
			"setting userParameters is not supported over LDAP, "
			"see https://bugzilla.samba.org/show_bug.cgi?id=8077";
		ldb_debug(ldb, LDB_DEBUG_WARNING, "%s", reason);
		return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION, reason);
	}

	ac = samldb_ctx_init(module, req);
	if (ac == NULL) {
		return ldb_operr(ldb);
	}

	/* build the new msg */
	ac->msg = ldb_msg_copy_shallow(ac, req->op.add.message);
	if (ac->msg == NULL) {
		talloc_free(ac);
		ldb_debug(ldb, LDB_DEBUG_FATAL,
			  "samldb_add: ldb_msg_copy_shallow failed!\n");
		return ldb_operr(ldb);
	}

	ret = samldb_check_sensitive_attributes(ac);
	if (ret != LDB_SUCCESS) {
		talloc_free(ac);
		return ret;
	}

	el = ldb_msg_find_element(ac->msg, "fSMORoleOwner");
	if (el != NULL) {
		ret = samldb_fsmo_role_owner_check(ac);
		if (ret != LDB_SUCCESS) {
			return ret;
		}
	}

	el = ldb_msg_find_element(ac->msg, "servicePrincipalName");
	if ((el != NULL)) {
		/*
		 * We need to check whether the SPN collides with an existing
		 * one (anywhere) including via aliases.
		 */
		ret = samldb_spn_uniqueness_check(ac, el);
		if (ret != LDB_SUCCESS) {
			return ret;
		}
	}

	if (samdb_find_attribute(ldb, ac->msg,
				 "objectclass", "user") != NULL) {
		ac->type = SAMLDB_TYPE_USER;

		ret = samldb_prim_group_trigger(ac);
		if (ret != LDB_SUCCESS) {
			return ret;
		}

		ret = samldb_objectclass_trigger(ac);
		if (ret != LDB_SUCCESS) {
			return ret;
		}

		return samldb_fill_object(ac);
	}

	if (samdb_find_attribute(ldb, ac->msg,
				 "objectclass", "group") != NULL) {
		ac->type = SAMLDB_TYPE_GROUP;

		ret = samldb_objectclass_trigger(ac);
		if (ret != LDB_SUCCESS) {
			return ret;
		}

		return samldb_fill_object(ac);
	}

	/* perhaps a foreignSecurityPrincipal? */
	if (samdb_find_attribute(ldb, ac->msg,
				 "objectclass",
				 "foreignSecurityPrincipal") != NULL) {
		return samldb_fill_foreignSecurityPrincipal_object(ac);
	}

	if (samdb_find_attribute(ldb, ac->msg,
				 "objectclass", "classSchema") != NULL) {
		ac->type = SAMLDB_TYPE_CLASS;

		/* If in provision, these checks are too slow to do */
		if (!ldb_request_get_control(req, DSDB_CONTROL_SKIP_DUPLICATES_CHECK_OID)) {
			ret = samldb_schema_governsid_valid_check(ac);
			if (ret != LDB_SUCCESS) {
				return ret;
			}
		}

		ret = samldb_schema_ldapdisplayname_valid_check(ac);
		if (ret != LDB_SUCCESS) {
			return ret;
		}

		ret = samldb_schema_info_update(ac);
		if (ret != LDB_SUCCESS) {
			talloc_free(ac);
			return ret;
		}

		return samldb_fill_object(ac);
	}

	if (samdb_find_attribute(ldb, ac->msg,
				 "objectclass", "attributeSchema") != NULL) {
		ac->type = SAMLDB_TYPE_ATTRIBUTE;

		/* If in provision, these checks are too slow to do */
		if (!ldb_request_get_control(req, DSDB_CONTROL_SKIP_DUPLICATES_CHECK_OID)) {
			ret = samldb_schema_attributeid_valid_check(ac);
			if (ret != LDB_SUCCESS) {
				return ret;
			}

			ret = samldb_schema_add_handle_linkid(ac);
			if (ret != LDB_SUCCESS) {
				return ret;
			}

			ret = samldb_schema_add_handle_mapiid(ac);
			if (ret != LDB_SUCCESS) {
				return ret;
			}
		}

		ret = samldb_schema_ldapdisplayname_valid_check(ac);
		if (ret != LDB_SUCCESS) {
			return ret;
		}

		ret = samldb_schema_info_update(ac);
		if (ret != LDB_SUCCESS) {
			talloc_free(ac);
			return ret;
		}

		return samldb_fill_object(ac);
	}

	if (samdb_find_attribute(ldb, ac->msg,
				 "objectclass", "subnet") != NULL) {
		ret = samldb_verify_subnet(ac, ac->msg->dn);
		if (ret != LDB_SUCCESS) {
			talloc_free(ac);
			return ret;
		}
		/* We are just checking the value is valid, and there are no
		   values to fill in. */
	}

	talloc_free(ac);

	/* nothing matched, go on */
	return ldb_next_request(module, req);
}


bool samdb_set_ntds_invocation_id(struct ldb_context *ldb, const struct GUID *invocation_id_in)
{
	return samdb_set_ntds_GUID(ldb,
				   invocation_id_in,
				   "invocationId",
				   "cache.invocation_id");
}


static NTSTATUS pdb_samba_dsdb_get_trusteddom_creds(struct pdb_methods *m,
						    const char *domain,
						    TALLOC_CTX *mem_ctx,
						    struct cli_credentials **_creds)
{
	struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
		m->private_data, struct pdb_samba_dsdb_state);
	TALLOC_CTX *tmp_ctx = talloc_stackframe();
	const char * const attrs[] = {
		"securityIdentifier",
		"flatName",
		"trustPartner",
		"trustAuthOutgoing",
		"whenCreated",
		"msDS-SupportedEncryptionTypes",
		"trustAttributes",
		"trustDirection",
		"trustType",
		NULL
	};
	struct ldb_message *msg;
	const struct ldb_val *password_val;
	int trust_direction_flags;
	int trust_type;
	int i;
	DATA_BLOB password_utf16 = {};
	struct samr_Password *password_nt = NULL;
	uint32_t password_version = 0;
	DATA_BLOB old_password_utf16 = {};
	struct samr_Password *old_password_nt = NULL;
	struct trustAuthInOutBlob password_blob;
	enum ndr_err_code ndr_err;
	NTSTATUS status;
	time_t last_set_time = 0;
	struct cli_credentials *creds = NULL;
	bool ok;
	const char *my_netbios_name = NULL;
	const char *my_netbios_domain = NULL;
	const char *my_dns_domain = NULL;
	const char *netbios_domain = NULL;
	char *account_name = NULL;
	char *principal_name = NULL;
	const char *dns_domain = NULL;

	status = dsdb_trust_search_tdo(state->ldb, domain, NULL,
				       attrs, tmp_ctx, &msg);
	if (!NT_STATUS_IS_OK(status)) {
		/*
		 * This can be called to work out of a domain is
		 * trusted, rather than just to get the password
		 */
		DEBUG(2, ("Failed to get trusted domain password for %s - %s "
			  "It may not be a trusted domain.\n", domain,
			  nt_errstr(status)));
		TALLOC_FREE(tmp_ctx);
		return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
	}

	netbios_domain = ldb_msg_find_attr_as_string(msg, "flatName", NULL);
	if (netbios_domain == NULL) {
		DEBUG(2, ("Trusted domain %s has to flatName defined.\n",
			  domain));
		TALLOC_FREE(tmp_ctx);
		return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
	}

	dns_domain = ldb_msg_find_attr_as_string(msg, "trustPartner", NULL);

	trust_direction_flags = ldb_msg_find_attr_as_int(msg, "trustDirection", 0);
	if (!(trust_direction_flags & LSA_TRUST_DIRECTION_OUTBOUND)) {
		DBG_WARNING("Trusted domain %s is not an outbound trust.\n",
			    domain);
		TALLOC_FREE(tmp_ctx);
		return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
	}

	trust_type = ldb_msg_find_attr_as_int(msg, "trustType", 0);
	if (trust_type == LSA_TRUST_TYPE_MIT) {
		DBG_WARNING("Trusted domain %s is not an AD trust "
			    "(trustType == LSA_TRUST_TYPE_MIT).\n", domain);
		TALLOC_FREE(tmp_ctx);
		return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
	}

	password_val = ldb_msg_find_ldb_val(msg, "trustAuthOutgoing");
	if (password_val == NULL) {
		DEBUG(2, ("Failed to get trusted domain password for %s, "
			  "attribute trustAuthOutgoing not returned.\n", domain));
		TALLOC_FREE(tmp_ctx);
		return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
	}

	ndr_err = ndr_pull_struct_blob(password_val, tmp_ctx, &password_blob,
				(ndr_pull_flags_fn_t)ndr_pull_trustAuthInOutBlob);
	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
		DEBUG(0, ("Failed to get trusted domain password for %s, "
			  "attribute trustAuthOutgoing could not be parsed %s.\n",
			  domain,
			  ndr_map_error2string(ndr_err)));
		TALLOC_FREE(tmp_ctx);
		return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
	}

	for (i=0; i < password_blob.current.count; i++) {
		struct AuthenticationInformation *a =
			&password_blob.current.array[i];

		switch (a->AuthType) {
		case TRUST_AUTH_TYPE_NONE:
			break;

		case TRUST_AUTH_TYPE_VERSION:
			password_version = a->AuthInfo.version.version;
			break;

		case TRUST_AUTH_TYPE_CLEAR:
			last_set_time = nt_time_to_unix(a->LastUpdateTime);

			password_utf16 = data_blob_const(a->AuthInfo.clear.password,
							 a->AuthInfo.clear.size);
			password_nt = NULL;
			break;

		case TRUST_AUTH_TYPE_NT4OWF:
			if (password_utf16.length != 0) {
				break;
			}

			last_set_time = nt_time_to_unix(a->LastUpdateTime);

			password_nt = &a->AuthInfo.nt4owf.password;
			break;
		}
	}

	for (i=0; i < password_blob.previous.count; i++) {
		struct AuthenticationInformation *a = &password_blob.previous.array[i];

		switch (a->AuthType) {
		case TRUST_AUTH_TYPE_NONE:
			break;

		case TRUST_AUTH_TYPE_VERSION:
			break;

		case TRUST_AUTH_TYPE_CLEAR:
			old_password_utf16 = data_blob_const(a->AuthInfo.clear.password,
							 a->AuthInfo.clear.size);
			old_password_nt = NULL;
			break;

		case TRUST_AUTH_TYPE_NT4OWF:
			if (old_password_utf16.length != 0) {
				break;
			}

			old_password_nt = &a->AuthInfo.nt4owf.password;
			break;
		}
	}

	if (password_utf16.length == 0 && password_nt == NULL) {
		DEBUG(0, ("Trusted domain %s does not have a "
			  "clear-text nor nt password stored\n",
			  domain));
		TALLOC_FREE(tmp_ctx);
		return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
	}

	my_netbios_name = lpcfg_netbios_name(state->lp_ctx);
	my_netbios_domain = lpcfg_workgroup(state->lp_ctx);
	my_dns_domain = lpcfg_dnsdomain(state->lp_ctx);

	creds = cli_credentials_init(tmp_ctx);
	if (creds == NULL) {
		TALLOC_FREE(tmp_ctx);
		return NT_STATUS_NO_MEMORY;
	}

	ok = cli_credentials_set_workstation(creds, my_netbios_name, CRED_SPECIFIED);
	if (!ok) {
		TALLOC_FREE(tmp_ctx);
		return NT_STATUS_NO_MEMORY;
	}

	ok = cli_credentials_set_domain(creds, netbios_domain, CRED_SPECIFIED);
	if (!ok) {
		TALLOC_FREE(tmp_ctx);
		return NT_STATUS_NO_MEMORY;
	}
	ok = cli_credentials_set_realm(creds, dns_domain, CRED_SPECIFIED);
	if (!ok) {
		TALLOC_FREE(tmp_ctx);
		return NT_STATUS_NO_MEMORY;
	}

	if (my_dns_domain != NULL && dns_domain != NULL) {
		cli_credentials_set_secure_channel_type(creds, SEC_CHAN_DNS_DOMAIN);
		account_name = talloc_asprintf(tmp_ctx, "%s.", my_dns_domain);
		if (account_name == NULL) {
			TALLOC_FREE(tmp_ctx);
			return NT_STATUS_NO_MEMORY;
		}
		principal_name = talloc_asprintf(tmp_ctx, "%s$@%s", my_netbios_domain,
						 cli_credentials_get_realm(creds));
		if (principal_name == NULL) {
			TALLOC_FREE(tmp_ctx);
			return NT_STATUS_NO_MEMORY;
		}
	} else {
		cli_credentials_set_secure_channel_type(creds, SEC_CHAN_DOMAIN);
		account_name = talloc_asprintf(tmp_ctx, "%s$", my_netbios_domain);
		if (account_name == NULL) {
			TALLOC_FREE(tmp_ctx);
			return NT_STATUS_NO_MEMORY;
		}
		principal_name = NULL;
	}

	ok = cli_credentials_set_username(creds, account_name, CRED_SPECIFIED);
	if (!ok) {
		TALLOC_FREE(tmp_ctx);
		return NT_STATUS_NO_MEMORY;
	}

	if (principal_name != NULL) {
		ok = cli_credentials_set_principal(creds, principal_name,
						   CRED_SPECIFIED);
		if (!ok) {
			TALLOC_FREE(tmp_ctx);
			return NT_STATUS_NO_MEMORY;
		}
	}

	if (old_password_nt != NULL) {
		ok = cli_credentials_set_old_nt_hash(creds, old_password_nt);
		if (!ok) {
			TALLOC_FREE(tmp_ctx);
			return NT_STATUS_NO_MEMORY;
		}
	}

	if (old_password_utf16.length > 0) {
		ok = cli_credentials_set_old_utf16_password(creds,
							    &old_password_utf16);
		if (!ok) {
			TALLOC_FREE(tmp_ctx);
			return NT_STATUS_NO_MEMORY;
		}
	}

	if (password_nt != NULL) {
		ok = cli_credentials_set_nt_hash(creds, password_nt,
						 CRED_SPECIFIED);
		if (!ok) {
			TALLOC_FREE(tmp_ctx);
			return NT_STATUS_NO_MEMORY;
		}
	}

	if (password_utf16.length > 0) {
		ok = cli_credentials_set_utf16_password(creds,
							&password_utf16,
							CRED_SPECIFIED);
		if (!ok) {
			TALLOC_FREE(tmp_ctx);
			return NT_STATUS_NO_MEMORY;
		}
	}

	cli_credentials_set_password_last_changed_time(creds, last_set_time);
	cli_credentials_set_kvno(creds, password_version);

	if (password_utf16.length > 0 && dns_domain != NULL) {
		/*
		 * Force kerberos if this is an active directory domain
		 */
		cli_credentials_set_kerberos_state(creds,
						   CRED_USE_KERBEROS_REQUIRED,
						   CRED_SPECIFIED);
	} else  {
		/*
		 * TODO: we should allow krb5 with the raw nt hash.
		 */
		cli_credentials_set_kerberos_state(creds,
						   CRED_USE_KERBEROS_DISABLED,
						   CRED_SPECIFIED);
	}

	*_creds = talloc_move(mem_ctx, &creds);
	TALLOC_FREE(tmp_ctx);
	return NT_STATUS_OK;
}


static int samdb_find_or_add_attribute_ex(struct ldb_context *ldb,
					  struct ldb_message *msg,
					  const char *name,
					  const char *set_value,
					  unsigned attr_flags,
					  bool *added)
{
	int ret;
	struct ldb_message_element *el;

	SMB_ASSERT(attr_flags != 0);

       	el = ldb_msg_find_element(msg, name);
	if (el) {
		if (added != NULL) {
			*added = false;
		}

		return LDB_SUCCESS;
	}

	ret = ldb_msg_add_empty(msg, name,
				attr_flags,
				&el);
	if (ret != LDB_SUCCESS) {
		return ret;
	}

	if (set_value != NULL) {
		ret = ldb_msg_add_string(msg, name, set_value);
		if (ret != LDB_SUCCESS) {
			return ret;
		}
	}

	if (added != NULL) {
		*added = true;
	}
	return LDB_SUCCESS;
}


static int map_op_local_callback(struct ldb_request *req,
				 struct ldb_reply *ares)
{
	struct ldb_context *ldb;
	struct map_context *ac;
	int ret;

	ac = talloc_get_type(req->context, struct map_context);
	ldb = ldb_module_get_ctx(ac->module);

	if (!ares) {
		return ldb_module_done(ac->req, NULL, NULL,
					LDB_ERR_OPERATIONS_ERROR);
	}
	if (ares->error != LDB_SUCCESS) {
		return ldb_module_done(ac->req, ares->controls,
					ares->response, ares->error);
	}

	if (ares->type != LDB_REPLY_DONE) {
		ldb_asprintf_errstring(ldb, "Invalid LDB reply type %d", ares->type);
		return ldb_module_done(ac->req, NULL, NULL,
					LDB_ERR_OPERATIONS_ERROR);
	}

	/* Do the remote request. */
	ret = ldb_next_remote_request(ac->module, ac->remote_req);
	if (ret != LDB_SUCCESS) {
		return ldb_module_done(ac->req, NULL, NULL,
					LDB_ERR_OPERATIONS_ERROR);
	}

	return LDB_SUCCESS;
}


uint32_t samdb_result_rid_from_sid(TALLOC_CTX *mem_ctx, const struct ldb_message *msg,
				   const char *attr, uint32_t default_value)
{
	struct dom_sid *sid;
	uint32_t rid;

	sid = samdb_result_dom_sid(mem_ctx, msg, attr);
	if (sid == NULL) {
		return default_value;
	}
	rid = sid->sub_auths[sid->num_auths-1];
	talloc_free(sid);
	return rid;
}


unsigned int samdb_result_hashes(TALLOC_CTX *mem_ctx, const struct ldb_message *msg,
			   const char *attr, struct samr_Password **hashes)
{
	unsigned int count, i;
	const struct ldb_val *val = ldb_msg_find_ldb_val(msg, attr);

	*hashes = NULL;
	if (!val) {
		return 0;
	}
	count = val->length / 16;
	if (count == 0) {
		return 0;
	}

	*hashes = talloc_array(mem_ctx, struct samr_Password, count);
	if (! *hashes) {
		return 0;
	}

	for (i=0;i<count;i++) {
		memcpy((*hashes)[i].hash, (i*16)+(char *)val->data, 16);
	}

	return count;
}


static int dsdb_count_domain_callback(
	struct ldb_request *req,
	struct ldb_reply *ares)
{

	if (ares == NULL) {
		return ldb_request_done(req, LDB_ERR_OPERATIONS_ERROR);
	}
	if (ares->error != LDB_SUCCESS) {
		int error = ares->error;
		TALLOC_FREE(ares);
		return ldb_request_done(req, error);
	}

	switch (ares->type) {
	case LDB_REPLY_ENTRY:
	{
		struct dsdb_count_domain_context *context = NULL;
		ssize_t ret;
		bool in_domain;
		struct dom_sid sid;
		const struct ldb_val *v;

		context = req->context;
		if (context->dom_sid == NULL) {
			context->count++;
			break;
		}

		v = ldb_msg_find_ldb_val(ares->message, "objectSid");
		if (v == NULL) {
			break;
		}

		ret = sid_parse(v->data, v->length, &sid);
		if (ret == -1) {
			break;
		}

		in_domain = dom_sid_in_domain(context->dom_sid, &sid);
		if (!in_domain) {
			break;
		}

		context->count++;
		break;
	}
	case LDB_REPLY_REFERRAL:
		break;

	case LDB_REPLY_DONE:
		TALLOC_FREE(ares);
		return ldb_request_done(req, LDB_SUCCESS);
	}

	TALLOC_FREE(ares);

	return LDB_SUCCESS;
}


static int rdn_modify_callback(struct ldb_request *req, struct ldb_reply *ares)
{
	struct rename_context *ac;

	ac = talloc_get_type(req->context, struct rename_context);

	if (!ares) {
		return ldb_module_done(ac->req, NULL, NULL,
					LDB_ERR_OPERATIONS_ERROR);
	}

	if (ares->type == LDB_REPLY_REFERRAL) {
		return ldb_module_send_referral(ac->req, ares->referral);
	}

	if (ares->error != LDB_SUCCESS) {
		return ldb_module_done(ac->req, ares->controls,
					ares->response, ares->error);
	}

	/* the only supported reply right now is a LDB_REPLY_DONE */
	if (ares->type != LDB_REPLY_DONE) {
		return ldb_module_done(ac->req, NULL, NULL,
					LDB_ERR_OPERATIONS_ERROR);
	}

	/* send saved controls eventually */
	return ldb_module_done(ac->req, ac->ares->controls,
				ac->ares->response, LDB_SUCCESS);
}


static int replmd_replicated_uptodate_modify_callback(struct ldb_request *req,
						      struct ldb_reply *ares)
{
	struct ldb_context *ldb;
	struct replmd_replicated_request *ar = talloc_get_type(req->context,
					       struct replmd_replicated_request);
	ldb = ldb_module_get_ctx(ar->module);

	if (!ares) {
		return ldb_module_done(ar->req, NULL, NULL,
					LDB_ERR_OPERATIONS_ERROR);
	}
	if (ares->error != LDB_SUCCESS) {
		return ldb_module_done(ar->req, ares->controls,
					ares->response, ares->error);
	}

	if (ares->type != LDB_REPLY_DONE) {
		ldb_asprintf_errstring(ldb, "Invalid LDB reply type %d", ares->type);
		return ldb_module_done(ar->req, NULL, NULL,
					LDB_ERR_OPERATIONS_ERROR);
	}

	talloc_free(ares);

	return ldb_module_done(ar->req, NULL, NULL, LDB_SUCCESS);
}


bool dsdb_request_has_control(struct ldb_request *req, const char *oid)
{
	return (ldb_request_get_control(req, oid) != NULL);
}