/*
Unix SMB/CIFS implementation.
KCC service
Copyright (C) CrÃstian Deives 2010
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include "includes.h"
#include "dsdb/samdb/samdb.h"
#include "lib/messaging/irpc.h"
#include "librpc/gen_ndr/ndr_misc.h"
#include "dsdb/kcc/kcc_service.h"
#define FLAG_CR_NTDS_NC 0x00000001
#define FLAG_CR_NTDS_DOMAIN 0x00000002
#define NTDSCONN_OPT_IS_GENERATED 0x00000001
#define NTDSCONN_OPT_TWOWAY_SYNC 0x00000002
#define NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT 0x00000004
#define NTDSCONN_OPT_USE_NOTIFY 0x00000008
#define NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION 0x00000010
#define NTDSCONN_OPT_USER_OWNED_SCHEDULE 0x00000020
#define NTDSCONN_OPT_RODC_TOPOLOGY 0x00000040
#define NTDSDSA_OPT_IS_GC 0x00000001
#define NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED 0x00000008
#define NTDSSETTINGS_OPT_IS_RAND_BH_SELECTION_DISABLED 0x00000100
#define NTDSSETTINGS_OPT_W2K3_BRIDGES_REQUIRED 0x00001000
#define NTDSSITELINK_OPT_USE_NOTIFY 0x00000001
#define NTDSSITELINK_OPT_TWOWAY_SYNC 0x00000002
#define NTDSSITELINK_OPT_DISABLE_COMPRESSION 0x00000004
#define NTDSTRANSPORT_OPT_BRIDGES_REQUIRED 0x00000002
#define DS_BEHAVIOR_WIN2008 3
/** replication parameters of a graph edge */
struct kcctpl_repl_info {
uint32_t cost;
uint32_t interval;
uint32_t options;
uint8_t schedule[84];
};
/** color of a vertex */
enum kcctpl_color { RED, BLACK, WHITE };
/** a GUID array list */
struct GUID_list {
struct GUID *data;
uint32_t count;
};
/** a vertex in the site graph */
struct kcctpl_vertex {
struct GUID id;
struct GUID_list edge_ids;
enum kcctpl_color color;
struct GUID_list accept_red_red;
struct GUID_list accept_black;
struct kcctpl_repl_info repl_info;
uint32_t dist_to_red;
/* Dijkstra data */
struct GUID root_id;
bool demoted;
/* Kruskal data */
struct GUID component_id;
uint32_t component_index;
};
/** fully connected subgraph of vertices */
struct kcctpl_multi_edge {
struct GUID id;
struct GUID_list vertex_ids;
struct GUID type;
struct kcctpl_repl_info repl_info;
bool directed;
};
/** set of transitively connected kcc_multi_edge's. all edges within the set
* have the same type. */
struct kcctpl_multi_edge_set {
struct GUID id;
struct GUID_list edge_ids;
};
/** a vertices array list */
struct kcctpl_vertex_list {
struct kcctpl_vertex *data;
uint32_t count;
};
/** an edges array list */
struct kcctpl_multi_edge_list {
struct kcctpl_multi_edge *data;
uint32_t count;
};
/** an edge sets array list */
struct kcctpl_multi_edge_set_list {
struct kcctpl_multi_edge_set *data;
uint32_t count;
};
/** a site graph */
struct kcctpl_graph {
struct kcctpl_vertex_list vertices;
struct kcctpl_multi_edge_list edges;
struct kcctpl_multi_edge_set_list edge_sets;
};
/** path found in the graph between two non-white vertices */
struct kcctpl_internal_edge {
struct GUID v1id;
struct GUID v2id;
bool red_red;
struct kcctpl_repl_info repl_info;
struct GUID type;
};
/** an internal edges array list */
struct kcctpl_internal_edge_list {
struct kcctpl_internal_edge *data;
uint32_t count;
};
/** an LDB messages array list */
struct message_list {
struct ldb_message *data;
uint32_t count;
};
/**
* sort internal edges based on:
* - descending red_red,
* - ascending repl_info.cost,
* - descending available time in repl_info.schedule,
* - ascending v1id,
* - ascending v2id,
* - ascending type.
*
* this function is used in 'kcctpl_kruskal'.
*/
static int kcctpl_sort_internal_edges(const void *internal_edge1,
const void *internal_edge2)
{
const struct kcctpl_internal_edge *ie1, *ie2;
int cmp_red_red;
ie1 = (const struct kcctpl_internal_edge *) internal_edge1;
ie2 = (const struct kcctpl_internal_edge *) internal_edge2;
cmp_red_red = ie2->red_red - ie1->red_red;
if (cmp_red_red == 0) {
int cmp_cost = ie1->repl_info.cost - ie2->repl_info.cost;
if (cmp_cost == 0) {
uint32_t available1, available2, i;
int cmp_schedule;
available1 = available2 = 0;
for (i = 0; i < 84; i++) {
if (ie1->repl_info.schedule[i] == 0) {
available1++;
}
if (ie2->repl_info.schedule[i] == 0) {
available2++;
}
}
cmp_schedule = available2 - available1;
if (cmp_schedule == 0) {
int cmp_v1id = GUID_compare(&ie1->v1id,
&ie2->v1id);
if (cmp_v1id == 0) {
int cmp_v2id = GUID_compare(&ie1->v2id,
&ie2->v2id);
if (cmp_v2id == 0) {
return GUID_compare(&ie1->type,
&ie2->type);
} else {
return cmp_v2id;
}
} else {
return cmp_v1id;
}
} else {
return cmp_schedule;
}
} else {
return cmp_cost;
}
} else {
return cmp_red_red;
}
}
/**
* sort vertices based on the following criteria:
* - ascending color (RED < BLACK),
* - ascending repl_info.cost,
* - ascending id.
*
* this function is used in 'kcctpl_process_edge'.
*/
static int kcctpl_sort_vertices(const void *vertex1, const void *vertex2)
{
const struct kcctpl_vertex *v1, *v2;
int cmp_color;
v1 = (const struct kcctpl_vertex *) vertex1;
v2 = (const struct kcctpl_vertex *) vertex2;
cmp_color = v1->color - v2->color;
if (cmp_color == 0) {
int cmp_cost = v1->repl_info.cost - v2->repl_info.cost;
if (cmp_cost == 0) {
return GUID_compare(&v1->id, &v2->id);
} else {
return cmp_cost;
}
} else {
return cmp_color;
}
}
/**
* sort bridgehead elements (nTDSDSA) based on the following criteria:
* - GC servers precede non-GC servers
* - ascending objectGUID
*
* this function is used in 'kcctpl_get_all_bridgehead_dcs'.
*/
static int kcctpl_sort_bridgeheads(const void *bridgehead1,
const void *bridgehead2)
{
const struct ldb_message *bh1, *bh2;
uint32_t bh1_opts, bh2_opts;
int cmp_gc;
bh1 = (const struct ldb_message *) bridgehead1;
bh2 = (const struct ldb_message *) bridgehead2;
bh1_opts = ldb_msg_find_attr_as_uint(bh1, "options", 0);
bh2_opts = ldb_msg_find_attr_as_uint(bh2, "options", 0);
cmp_gc = (bh1_opts & NTDSDSA_OPT_IS_GC) -
(bh2_opts & NTDSDSA_OPT_IS_GC);
if (cmp_gc == 0) {
struct GUID bh1_id, bh2_id;
bh1_id = samdb_result_guid(bh1, "objectGUID");
bh2_id = samdb_result_guid(bh2, "objectGUID");
return GUID_compare(&bh1_id, &bh2_id);
} else {
return cmp_gc;
}
}
/**
* sort bridgehead elements (nTDSDSA) in a random order.
*
* this function is used in 'kcctpl_get_all_bridgehead_dcs'.
*/
static void kcctpl_shuffle_bridgeheads(struct message_list bridgeheads)
{
uint32_t i;
srandom(time(NULL));
for (i = bridgeheads.count; i > 1; i--) {
uint32_t r;
struct ldb_message tmp;
r = random() % i;
tmp = bridgeheads.data[i - 1];
bridgeheads.data[i - 1] = bridgeheads.data[r];
bridgeheads.data[r] = tmp;
}
}
/**
* find a graph vertex based on its GUID.
*/
static struct kcctpl_vertex *kcctpl_find_vertex_by_guid(struct kcctpl_graph *graph,
struct GUID guid)
{
uint32_t i;
for (i = 0; i < graph->vertices.count; i++) {
if (GUID_equal(&graph->vertices.data[i].id, &guid)) {
return &graph->vertices.data[i];
}
}
return NULL;
}
/**
* find a graph edge based on its GUID.
*/
static struct kcctpl_multi_edge *kcctpl_find_edge_by_guid(struct kcctpl_graph *graph,
struct GUID guid)
{
uint32_t i;
for (i = 0; i < graph->edges.count; i++) {
if (GUID_equal(&graph->edges.data[i].id, &guid)) {
return &graph->edges.data[i];
}
}
return NULL;
}
/**
* find a graph edge that contains a vertex with the specified GUID. the first
* occurrence will be returned.
*/
static struct kcctpl_multi_edge *kcctpl_find_edge_by_vertex_guid(struct kcctpl_graph *graph,
struct GUID guid)
{
uint32_t i;
for (i = 0; i < graph->edges.count; i++) {
struct kcctpl_multi_edge *edge;
uint32_t j;
edge = &graph->edges.data[i];
for (j = 0; j < edge->vertex_ids.count; j++) {
struct GUID vertex_guid = edge->vertex_ids.data[j];
struct GUID *p = &guid;
if (GUID_equal(&vertex_guid, p)) {
return edge;
}
}
}
return NULL;
}
/**
* search for an occurrence of a GUID inside a list of GUIDs.
*/
static bool kcctpl_guid_list_contains(struct GUID_list list, struct GUID guid)
{
uint32_t i;
for (i = 0; i < list.count; i++) {
if (GUID_equal(&list.data[i], &guid)) {
return true;
}
}
return false;
}
/**
* search for an occurrence of an ldb_message inside a list of ldb_messages,
* based on the ldb_message's DN.
*/
static bool kcctpl_message_list_contains_dn(struct message_list list,
struct ldb_dn *dn)
{
uint32_t i;
for (i = 0; i < list.count; i++) {
struct ldb_message *message = &list.data[i];
if (ldb_dn_compare(message->dn, dn) == 0) {
return true;
}
}
return false;
}
/**
* get the Transports DN
* (CN=Inter-Site Transports,CN=Sites,CN=Configuration,DC=).
*/
static struct ldb_dn *kcctpl_transports_dn(struct ldb_context *ldb,
TALLOC_CTX *mem_ctx)
{
struct ldb_dn *sites_dn;
bool ok;
sites_dn = samdb_sites_dn(ldb, mem_ctx);
if (!sites_dn) {
return NULL;
}
ok = ldb_dn_add_child_fmt(sites_dn, "CN=Inter-Site Transports");
if (!ok) {
talloc_free(sites_dn);
return NULL;
}
return sites_dn;
}
/**
* get the domain local site object.
*/
static struct ldb_message *kcctpl_local_site(struct ldb_context *ldb,
TALLOC_CTX *mem_ctx)
{
int ret;
TALLOC_CTX *tmp_ctx;
struct ldb_dn *sites_dn;
struct ldb_result *res;
const char * const attrs[] = { "objectGUID", "options", NULL };
tmp_ctx = talloc_new(ldb);
sites_dn = samdb_sites_dn(ldb, tmp_ctx);
if (!sites_dn) {
talloc_free(tmp_ctx);
return NULL;
}
ret = ldb_search(ldb, tmp_ctx, &res, sites_dn, LDB_SCOPE_SUBTREE, attrs,
"objectClass=site");
if (ret != LDB_SUCCESS || res->count == 0) {
talloc_free(tmp_ctx);
return NULL;
}
talloc_steal(mem_ctx, res);
talloc_free(tmp_ctx);
return res->msgs[0];
}
/*
* compare two internal edges for equality. every field of the structure will be
* compared.
*/
static bool kcctpl_internal_edge_equal(struct kcctpl_internal_edge *edge1,
struct kcctpl_internal_edge *edge2)
{
if (!edge1 || !edge2) {
return false;
}
if (!GUID_equal(&edge1->v1id, &edge2->v1id)) {
return false;
}
if (!GUID_equal(&edge1->v2id, &edge2->v2id)) {
return false;
}
if (edge1->red_red != edge2->red_red) {
return false;
}
if (edge1->repl_info.cost != edge2->repl_info.cost ||
edge1->repl_info.interval != edge2->repl_info.interval ||
edge1->repl_info.options != edge2->repl_info.options ||
memcmp(&edge1->repl_info.schedule,
&edge2->repl_info.schedule, 84) != 0) {
return false;
}
if (!GUID_equal(&edge1->type, &edge2->type)) {
return false;
}
return true;
}
/**
* create a kcctpl_graph instance.
*/
static NTSTATUS kcctpl_create_graph(TALLOC_CTX *mem_ctx,
struct GUID_list guids,
struct kcctpl_graph **_graph)
{
struct kcctpl_graph *graph;
uint32_t i;
graph = talloc_zero(mem_ctx, struct kcctpl_graph);
NT_STATUS_HAVE_NO_MEMORY(graph);
graph->vertices.count = guids.count;
graph->vertices.data = talloc_zero_array(graph, struct kcctpl_vertex,
guids.count);
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(graph->vertices.data, graph);
TYPESAFE_QSORT(guids.data, guids.count, GUID_compare);
for (i = 0; i < guids.count; i++) {
graph->vertices.data[i].id = guids.data[i];
}
*_graph = graph;
return NT_STATUS_OK;
}
/**
* create a kcctpl_multi_edge instance.
*/
static NTSTATUS kcctpl_create_edge(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
struct GUID type,
struct ldb_message *site_link,
struct kcctpl_multi_edge **_edge)
{
struct kcctpl_multi_edge *edge;
TALLOC_CTX *tmp_ctx;
struct ldb_dn *sites_dn;
struct ldb_result *res;
const char * const attrs[] = { "siteList", NULL };
int ret;
struct ldb_message_element *el;
unsigned int i;
struct ldb_val val;
tmp_ctx = talloc_new(mem_ctx);
NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
edge = talloc_zero(tmp_ctx, struct kcctpl_multi_edge);
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(edge, tmp_ctx);
edge->id = samdb_result_guid(site_link, "objectGUID");
sites_dn = samdb_sites_dn(ldb, tmp_ctx);
if (!sites_dn) {
DEBUG(1, (__location__ ": failed to find our own Sites DN\n"));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
ret = ldb_search(ldb, tmp_ctx, &res, sites_dn, LDB_SCOPE_SUBTREE, attrs,
"objectGUID=%s", GUID_string(tmp_ctx, &edge->id));
if (ret != LDB_SUCCESS) {
DEBUG(1, (__location__ ": failed to find siteLink object %s: "
"%s\n", GUID_string(tmp_ctx, &edge->id),
ldb_strerror(ret)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
if (res->count == 0) {
DEBUG(1, (__location__ ": failed to find siteLink object %s\n",
GUID_string(tmp_ctx, &edge->id)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
el = ldb_msg_find_element(res->msgs[0], "siteList");
if (!el) {
DEBUG(1, (__location__ ": failed to find siteList attribute of "
"object %s\n",
ldb_dn_get_linearized(res->msgs[0]->dn)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
edge->vertex_ids.data = talloc_array(edge, struct GUID, el->num_values);
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(edge->vertex_ids.data, tmp_ctx);
edge->vertex_ids.count = el->num_values;
for (i = 0; i < el->num_values; i++) {
struct ldb_dn *dn;
struct GUID guid;
val = el->values[i];
dn = ldb_dn_from_ldb_val(tmp_ctx, ldb, &val);
if (!dn) {
DEBUG(1, (__location__ ": failed to read a DN from "
"siteList attribute of %s\n",
ldb_dn_get_linearized(res->msgs[0]->dn)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
ret = dsdb_find_guid_by_dn(ldb, dn, &guid);
if (ret != LDB_SUCCESS) {
DEBUG(1, (__location__ ": failed to find objectGUID "
"for object %s: %s\n",
ldb_dn_get_linearized(dn),
ldb_strerror(ret)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
edge->vertex_ids.data[i] = guid;
}
edge->repl_info.cost = ldb_msg_find_attr_as_int64(site_link, "cost", 0);
edge->repl_info.options = ldb_msg_find_attr_as_uint(site_link, "options", 0);
edge->repl_info.interval = ldb_msg_find_attr_as_int64(site_link,
"replInterval", 0);
/* TODO: edge->repl_info.schedule = site_link!schedule */
edge->type = type;
edge->directed = false;
*_edge = talloc_steal(mem_ctx, edge);
talloc_free(tmp_ctx);
return NT_STATUS_OK;
}
/**
* create a kcctpl_multi_edge_set instance containing edges for all siteLink
* objects.
*/
static NTSTATUS kcctpl_create_auto_edge_set(struct kcctpl_graph *graph,
struct GUID type,
struct ldb_result *res_site_link,
struct kcctpl_multi_edge_set **_set)
{
struct kcctpl_multi_edge_set *set;
TALLOC_CTX *tmp_ctx;
uint32_t i;
tmp_ctx = talloc_new(graph);
NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
set = talloc_zero(tmp_ctx, struct kcctpl_multi_edge_set);
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(set, tmp_ctx);
for (i = 0; i < res_site_link->count; i++) {
struct GUID site_link_guid;
struct kcctpl_multi_edge *edge;
site_link_guid = samdb_result_guid(res_site_link->msgs[i],
"objectGUID");
edge = kcctpl_find_edge_by_guid(graph, site_link_guid);
if (!edge) {
DEBUG(1, (__location__ ": failed to find a graph edge "
"with ID=%s\n",
GUID_string(tmp_ctx, &site_link_guid)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
if (GUID_equal(&edge->type, &type)) {
struct GUID *new_data;
new_data = talloc_realloc(set, set->edge_ids.data,
struct GUID,
set->edge_ids.count + 1);
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(new_data, tmp_ctx);
new_data[set->edge_ids.count] = site_link_guid;
set->edge_ids.data = new_data;
set->edge_ids.count++;
}
}
*_set = talloc_steal(graph, set);
return NT_STATUS_OK;
}
/**
* create a kcctpl_multi_edge_set instance.
*/
static NTSTATUS kcctpl_create_edge_set(struct ldb_context *ldb,
struct kcctpl_graph *graph,
struct GUID type,
struct ldb_message *bridge,
struct kcctpl_multi_edge_set **_set)
{
struct kcctpl_multi_edge_set *set;
TALLOC_CTX *tmp_ctx;
struct ldb_message_element *el;
unsigned int i;
tmp_ctx = talloc_new(ldb);
NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
set = talloc_zero(tmp_ctx, struct kcctpl_multi_edge_set);
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(set, tmp_ctx);
set->id = samdb_result_guid(bridge, "objectGUID");
el = ldb_msg_find_element(bridge, "siteLinkList");
if (!el) {
DEBUG(1, (__location__ ": failed to find siteLinkList "
"attribute of object %s\n",
ldb_dn_get_linearized(bridge->dn)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
for (i = 0; i < el->num_values; i++) {
struct ldb_val val;
struct ldb_dn *dn;
struct GUID site_link_guid;
int ret;
struct kcctpl_multi_edge *edge;
val = el->values[i];
dn = ldb_dn_from_ldb_val(tmp_ctx, ldb, &val);
if (!dn) {
DEBUG(1, (__location__ ": failed to read a DN from "
"siteList attribute of %s\n",
ldb_dn_get_linearized(bridge->dn)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
ret = dsdb_find_guid_by_dn(ldb, dn, &site_link_guid);
if (ret != LDB_SUCCESS) {
DEBUG(1, (__location__ ": failed to find objectGUID "
"for object %s: %s\n",
ldb_dn_get_linearized(dn),
ldb_strerror(ret)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
edge = kcctpl_find_edge_by_guid(graph, site_link_guid);
if (!edge) {
DEBUG(1, (__location__ ": failed to find a graph edge "
"with ID=%s\n",
GUID_string(tmp_ctx, &site_link_guid)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
if (GUID_equal(&edge->type, &type)) {
struct GUID *new_data;
new_data = talloc_realloc(set, set->edge_ids.data,
struct GUID,
set->edge_ids.count + 1);
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(new_data, tmp_ctx);
new_data[set->edge_ids.count] = site_link_guid;
set->edge_ids.data = new_data;
set->edge_ids.count++;
}
}
*_set = talloc_steal(graph, set);
talloc_free(tmp_ctx);
return NT_STATUS_OK;
}
/**
* set up a kcctpl_graph, populated with a kcctpl_vertex for each site object, a
* kcctpl_multi_edge for each siteLink object, and a kcctpl_multi_edge_set for
* each siteLinkBridge object (or implied siteLinkBridge).
*/
static NTSTATUS kcctpl_setup_graph(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
struct kcctpl_graph **_graph)
{
struct kcctpl_graph *graph;
struct ldb_dn *sites_dn, *transports_dn;
TALLOC_CTX *tmp_ctx;
struct ldb_result *res;
const char * const transport_attrs[] = { "objectGUID", NULL };
const char * const site_attrs[] = { "objectGUID", "options", NULL };
const char * const attrs[] = { "objectGUID", "cost", "options",
"replInterval", "schedule", NULL };
const char * const site_link_bridge_attrs[] = { "objectGUID",
"siteLinkList",
NULL };
int ret;
struct GUID_list vertex_ids;
unsigned int i;
NTSTATUS status;
struct ldb_message *site;
uint32_t site_opts;
tmp_ctx = talloc_new(mem_ctx);
NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
sites_dn = samdb_sites_dn(ldb, tmp_ctx);
if (!sites_dn) {
DEBUG(1, (__location__ ": failed to find our own Sites DN\n"));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
ret = ldb_search(ldb, tmp_ctx, &res, sites_dn, LDB_SCOPE_SUBTREE,
site_attrs, "objectClass=site");
if (ret != LDB_SUCCESS) {
DEBUG(1, (__location__ ": failed to find site objects under "
"%s: %s\n", ldb_dn_get_linearized(sites_dn),
ldb_strerror(ret)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
ZERO_STRUCT(vertex_ids);
for (i = 0; i < res->count; i++) {
struct GUID guid, *new_data;
guid = samdb_result_guid(res->msgs[i], "objectGUID");
new_data = talloc_realloc(tmp_ctx, vertex_ids.data, struct GUID,
vertex_ids.count + 1);
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(new_data, tmp_ctx);
new_data[vertex_ids.count] = guid;
vertex_ids.data = new_data;
vertex_ids.count++;
}
status = kcctpl_create_graph(tmp_ctx, vertex_ids, &graph);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed to create graph: %s\n",
nt_errstr(status)));
talloc_free(tmp_ctx);
return status;
}
site = kcctpl_local_site(ldb, tmp_ctx);
if (!site) {
DEBUG(1, (__location__ ": failed to find our own local DC's "
"site\n"));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
site_opts = ldb_msg_find_attr_as_uint(site, "options", 0);
transports_dn = kcctpl_transports_dn(ldb, tmp_ctx);
if (!transports_dn) {
DEBUG(1, (__location__ ": failed to find our own Inter-Site "
"Transports DN\n"));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
ret = ldb_search(ldb, tmp_ctx, &res, transports_dn, LDB_SCOPE_ONELEVEL,
transport_attrs, "objectClass=interSiteTransport");
if (ret != LDB_SUCCESS) {
DEBUG(1, (__location__ ": failed to find interSiteTransport "
"objects under %s: %s\n",
ldb_dn_get_linearized(transports_dn),
ldb_strerror(ret)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
for (i = 0; i < res->count; i++) {
struct ldb_message *transport;
struct ldb_result *res_site_link;
struct GUID transport_guid;
unsigned int j;
uint32_t transport_opts;
transport = res->msgs[i];
ret = ldb_search(ldb, tmp_ctx, &res_site_link, transport->dn,
LDB_SCOPE_SUBTREE, attrs,
"objectClass=siteLink");
if (ret != LDB_SUCCESS) {
DEBUG(1, (__location__ ": failed to find siteLink "
"objects under %s: %s\n",
ldb_dn_get_linearized(transport->dn),
ldb_strerror(ret)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
transport_guid = samdb_result_guid(transport, "objectGUID");
for (j = 0; j < res_site_link->count; j++) {
struct kcctpl_multi_edge *edge, *new_data;
status = kcctpl_create_edge(ldb, graph, transport_guid,
res_site_link->msgs[j],
&edge);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed to create "
"edge: %s\n", nt_errstr(status)));
talloc_free(tmp_ctx);
return status;
}
new_data = talloc_realloc(graph, graph->edges.data,
struct kcctpl_multi_edge,
graph->edges.count + 1);
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(new_data, tmp_ctx);
new_data[graph->edges.count] = *edge;
graph->edges.data = new_data;
graph->edges.count++;
}
transport_opts = ldb_msg_find_attr_as_uint(transport, "options", 0);
if (!(transport_opts & NTDSTRANSPORT_OPT_BRIDGES_REQUIRED) &&
!(site_opts & NTDSSETTINGS_OPT_W2K3_BRIDGES_REQUIRED)) {
struct kcctpl_multi_edge_set *edge_set, *new_data;
status = kcctpl_create_auto_edge_set(graph,
transport_guid,
res_site_link,
&edge_set);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed to create "
"edge set: %s\n", nt_errstr(status)));
talloc_free(tmp_ctx);
return status;
}
new_data = talloc_realloc(graph, graph->edge_sets.data,
struct kcctpl_multi_edge_set,
graph->edge_sets.count + 1);
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(new_data, tmp_ctx);
new_data[graph->edge_sets.count] = *edge_set;
graph->edge_sets.data = new_data;
graph->edge_sets.count++;
} else {
ret = ldb_search(ldb, tmp_ctx, &res_site_link,
transport->dn, LDB_SCOPE_SUBTREE,
site_link_bridge_attrs,
"objectClass=siteLinkBridge");
if (ret != LDB_SUCCESS) {
DEBUG(1, (__location__ ": failed to find "
"siteLinkBridge objects under %s: "
"%s\n",
ldb_dn_get_linearized(transport->dn),
ldb_strerror(ret)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
for (j = 0; j < res_site_link->count; j++) {
struct ldb_message *bridge;
struct kcctpl_multi_edge_set *edge_set,
*new_data;
bridge = res_site_link->msgs[j];
status = kcctpl_create_edge_set(ldb, graph,
transport_guid,
bridge,
&edge_set);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed to "
"create edge set: %s\n",
nt_errstr(status)));
talloc_free(tmp_ctx);
return status;
}
new_data = talloc_realloc(graph,
graph->edge_sets.data,
struct kcctpl_multi_edge_set,
graph->edge_sets.count + 1);
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(new_data,
tmp_ctx);
new_data[graph->edge_sets.count] = *edge_set;
graph->edge_sets.data = new_data;
graph->edge_sets.count++;
}
}
}
*_graph = talloc_steal(mem_ctx, graph);
talloc_free(tmp_ctx);
return NT_STATUS_OK;
}
/**
* determine whether a given DC is known to be in a failed state.
*/
static NTSTATUS kcctpl_bridgehead_dc_failed(struct ldb_context *ldb,
struct GUID guid,
bool detect_failed_dcs,
bool *_failed)
{
TALLOC_CTX *tmp_ctx;
struct ldb_dn *settings_dn;
struct ldb_result *res;
const char * const attrs[] = { "options", NULL };
int ret;
struct ldb_message *settings;
uint32_t settings_opts;
bool failed;
tmp_ctx = talloc_new(ldb);
NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
settings_dn = samdb_ntds_settings_dn(ldb);
if (!settings_dn) {
DEBUG(1, (__location__ ": failed to find our own NTDS Settings "
"DN\n"));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
ret = ldb_search(ldb, tmp_ctx, &res, settings_dn, LDB_SCOPE_BASE, attrs,
"objectClass=nTDSSiteSettings");
if (ret != LDB_SUCCESS) {
DEBUG(1, (__location__ ": failed to find site settings object "
"%s: %s\n", ldb_dn_get_linearized(settings_dn),
ldb_strerror(ret)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
if (res->count == 0) {
DEBUG(1, ("failed to find site settings object %s\n",
ldb_dn_get_linearized(settings_dn)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
settings = res->msgs[0];
settings_opts = ldb_msg_find_attr_as_uint(settings, "options", 0);
if (settings_opts & NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED) {
failed = false;
} else if (true) { /* TODO: how to get kCCFailedLinks and
kCCFailedConnections? */
failed = true;
} else {
failed = detect_failed_dcs;
}
*_failed = failed;
talloc_free(tmp_ctx);
return NT_STATUS_OK;
}
/**
* get all bridgehead DCs satisfying the given criteria.
*/
static NTSTATUS kcctpl_get_all_bridgehead_dcs(struct kccsrv_service *service,
TALLOC_CTX *mem_ctx,
struct GUID site_guid,
struct ldb_message *cross_ref,
struct ldb_message *transport,
bool partial_replica_okay,
bool detect_failed_dcs,
struct message_list *_bridgeheads)
{
struct message_list bridgeheads, all_dcs_in_site;
TALLOC_CTX *tmp_ctx;
struct ldb_result *res;
struct ldb_dn *sites_dn, *schemas_dn;
const char * const attrs[] = { "options", NULL };
int ret;
struct ldb_message *site, *schema;
const char * const dc_attrs[] = { "objectGUID", "options", NULL };
struct ldb_message_element *el;
unsigned int i;
const char *transport_name, *transport_address_attr;
uint32_t site_opts;
ZERO_STRUCT(bridgeheads);
tmp_ctx = talloc_new(mem_ctx);
NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
sites_dn = samdb_sites_dn(service->samdb, tmp_ctx);
if (!sites_dn) {
DEBUG(1, (__location__ ": failed to find our own Sites DN\n"));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
ret = ldb_search(service->samdb, tmp_ctx, &res, sites_dn, LDB_SCOPE_ONELEVEL,
attrs, "(&(objectClass=site)(objectGUID=%s))",
GUID_string(tmp_ctx, &site_guid));
if (ret != LDB_SUCCESS) {
DEBUG(1, (__location__ ": failed to find site object %s: %s\n",
GUID_string(tmp_ctx, &site_guid),
ldb_strerror(ret)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
if (res->count == 0) {
DEBUG(1, (__location__ ": failed to find site object %s\n",
GUID_string(tmp_ctx, &site_guid)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
site = res->msgs[0];
schemas_dn = ldb_get_schema_basedn(service->samdb);
if (!schemas_dn) {
DEBUG(1, (__location__ ": failed to find our own Schemas DN\n"));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
ret = ldb_search(service->samdb, tmp_ctx, &res, schemas_dn, LDB_SCOPE_SUBTREE,
NULL,
"(&(lDAPDisplayName=nTDSDSA)(objectClass=classSchema))");
if (ret != LDB_SUCCESS) {
DEBUG(1, (__location__ ": failed to find classSchema object :"
"%s\n", ldb_strerror(ret)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
if (res->count == 0) {
DEBUG(1, (__location__ ": failed to find classSchema "
"object\n"));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
schema = res->msgs[0];
ZERO_STRUCT(all_dcs_in_site);
ret = ldb_search(service->samdb, tmp_ctx, &res, site->dn, LDB_SCOPE_SUBTREE,
dc_attrs, "objectCategory=%s",
ldb_dn_get_linearized(schema->dn));
if (ret != LDB_SUCCESS) {
DEBUG(1, (__location__ ": failed to find DCs objects :%s\n",
ldb_strerror(ret)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
el = ldb_msg_find_element(transport, "bridgeheadServerListBL");
transport_name = ldb_msg_find_attr_as_string(transport, "name", NULL);
if (!transport_name) {
DEBUG(1, (__location__ ": failed to find name attribute of "
"object %s\n", ldb_dn_get_linearized(transport->dn)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
transport_address_attr = ldb_msg_find_attr_as_string(transport,
"transportAddressAttribute",
NULL);
if (!transport_address_attr) {
DEBUG(1, (__location__ ": failed to find "
"transportAddressAttribute attribute of object %s\n",
ldb_dn_get_linearized(transport->dn)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
site_opts = ldb_msg_find_attr_as_uint(site, "options", 0);
for (i = 0; i < res->count; i++) {
struct ldb_message *dc, *new_data;
struct ldb_dn *parent_dn;
uint64_t behavior_version;
const char *dc_transport_address;
struct ldb_result *parent_res;
const char *parent_attrs[] = { transport_address_attr, NULL };
NTSTATUS status;
struct GUID dc_guid;
bool failed;
dc = res->msgs[i];
parent_dn = ldb_dn_get_parent(tmp_ctx, dc->dn);
if (!parent_dn) {
DEBUG(1, (__location__ ": failed to get parent DN of "
"%s\n", ldb_dn_get_linearized(dc->dn)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
if (el && (el->num_values >= 1)) {
bool contains;
unsigned int j;
contains = false;
for (j = 0; j < el->num_values; j++) {
struct ldb_val val;
struct ldb_dn *dn;
val = el->values[j];
dn = ldb_dn_from_ldb_val(tmp_ctx, service->samdb, &val);
if (!dn) {
DEBUG(1, (__location__ ": failed to read a DN "
"from bridgeheadServerListBL "
"attribute of %s\n",
ldb_dn_get_linearized(transport->dn)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
if (ldb_dn_compare(dn, parent_dn) == 0) {
contains = true;
break;
}
}
if (!contains) {
continue;
}
}
/* TODO: if dc is in the same site as the local DC */
if (true) {
/* TODO: if a replica of cr!nCName is not in the set of
* NC replicas that "should be present" on 'dc' */
/* TODO: a partial replica of the NC "should be
present" */
if (true || (true && !partial_replica_okay)) {
continue;
}
} else {
/* TODO: if an NC replica of cr!nCName is not in the set
* of NC replicas that "are present" on 'dc' */
/* TODO: a partial replica of the NC "is present" */
if (true || (true && !partial_replica_okay)) {
continue;
}
}
behavior_version = ldb_msg_find_attr_as_int64(dc,
"msDS-Behavior-Version", 0);
/* TODO: cr!nCName corresponds to default NC */
if (service->am_rodc && true && behavior_version < DS_BEHAVIOR_WIN2008) {
continue;
}
ret = ldb_search(service->samdb, tmp_ctx, &parent_res, parent_dn,
LDB_SCOPE_BASE, parent_attrs , NULL);
dc_transport_address = ldb_msg_find_attr_as_string(parent_res->msgs[0],
transport_address_attr,
NULL);
if (strncmp(transport_name, "IP", 2) != 0 &&
dc_transport_address == NULL) {
continue;
}
dc_guid = samdb_result_guid(dc, "objectGUID");
status = kcctpl_bridgehead_dc_failed(service->samdb, dc_guid,
detect_failed_dcs,
&failed);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed to check if "
"bridgehead DC has failed: %s\n",
nt_errstr(status)));
talloc_free(tmp_ctx);
return status;
}
if (failed) {
continue;
}
new_data = talloc_realloc(tmp_ctx, bridgeheads.data,
struct ldb_message,
bridgeheads.count + 1);
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(new_data, tmp_ctx);
new_data[bridgeheads.count + 1] = *dc;
bridgeheads.data = new_data;
bridgeheads.count++;
}
if (site_opts & NTDSSETTINGS_OPT_IS_RAND_BH_SELECTION_DISABLED) {
qsort(bridgeheads.data, bridgeheads.count,
sizeof(struct ldb_message), kcctpl_sort_bridgeheads);
} else {
kcctpl_shuffle_bridgeheads(bridgeheads);
}
talloc_steal(mem_ctx, bridgeheads.data);
*_bridgeheads = bridgeheads;
talloc_free(tmp_ctx);
return NT_STATUS_OK;
}
/**
* get a bridgehead DC.
*/
static NTSTATUS kcctpl_get_bridgehead_dc(struct kccsrv_service *service,
TALLOC_CTX *mem_ctx,
struct GUID site_guid,
struct ldb_message *cross_ref,
struct ldb_message *transport,
bool partial_replica_okay,
bool detect_failed_dcs,
struct ldb_message **_dsa)
{
struct message_list dsa_list;
NTSTATUS status;
status = kcctpl_get_all_bridgehead_dcs(service, mem_ctx,
site_guid, cross_ref, transport,
partial_replica_okay,
detect_failed_dcs, &dsa_list);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed to get all bridgehead DCs: "
"%s\n", nt_errstr(status)));
return status;
}
*_dsa = (dsa_list.count == 0) ? NULL : &dsa_list.data[0];
return NT_STATUS_OK;
}
/*
* color each vertex to indicate which kinds of NC replicas it contains.
*/
static NTSTATUS kcctpl_color_vertices(struct kccsrv_service *service,
struct kcctpl_graph *graph,
struct ldb_message *cross_ref,
bool detect_failed_dcs,
bool *_found_failed_dcs)
{
TALLOC_CTX *tmp_ctx;
struct ldb_dn *sites_dn;
bool found_failed_dcs, partial_replica_okay;
uint32_t i;
struct ldb_message *site;
struct ldb_result *res;
int ret, cr_flags;
struct GUID site_guid;
struct kcctpl_vertex *site_vertex;
found_failed_dcs = false;
tmp_ctx = talloc_new(service);
NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
sites_dn = samdb_sites_dn(service->samdb, tmp_ctx);
if (!sites_dn) {
DEBUG(1, (__location__ ": failed to find our own Sites DN\n"));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
for (i = 0; i < graph->vertices.count; i++) {
struct kcctpl_vertex *vertex;
struct ldb_dn *nc_name;
/* TODO: set 'attrs' with its corresponding values */
const char * const attrs[] = { NULL };
vertex = &graph->vertices.data[i];
ret = ldb_search(service->samdb, tmp_ctx, &res, sites_dn,
LDB_SCOPE_SUBTREE, attrs, "objectGUID=%s",
GUID_string(tmp_ctx, &vertex->id));
if (ret != LDB_SUCCESS) {
DEBUG(1, (__location__ ": failed to find site object "
"%s: %s\n", GUID_string(tmp_ctx, &vertex->id),
ldb_strerror(ret)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
if (res->count == 0) {
DEBUG(1, (__location__ ": failed to find site object "
"%s\n", GUID_string(tmp_ctx, &vertex->id)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
site = res->msgs[0];
nc_name = samdb_result_dn(service->samdb, tmp_ctx, cross_ref,
"nCName", NULL);
if (!nc_name) {
DEBUG(1, (__location__ ": failed to find nCName "
"attribute of object %s\n",
ldb_dn_get_linearized(cross_ref->dn)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
if (true) { /* TODO: site contains 1+ DCs with full replicas of
'nc_name' */
vertex->color = RED;
} else if (true) { /* TODO: site contains 1+ partial replicas of
'nc_name' */
vertex->color = BLACK;
} else {
vertex->color = WHITE;
}
}
site = kcctpl_local_site(service->samdb, tmp_ctx);
if (!site) {
DEBUG(1, (__location__ ": failed to find our own local DC's "
"site\n"));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
site_guid = samdb_result_guid(site, "objectGUID");
site_vertex = kcctpl_find_vertex_by_guid(graph, site_guid);
if (!site_vertex) {
DEBUG(1, (__location__ ": failed to find a vertex edge with "
"GUID=%s\n", GUID_string(tmp_ctx, &site_guid)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
partial_replica_okay = (site_vertex->color == BLACK);
cr_flags = ldb_msg_find_attr_as_int64(cross_ref, "systemFlags", 0);
for (i = 0; i < graph->vertices.count; i++) {
struct kcctpl_vertex *vertex;
struct ldb_dn *transports_dn;
const char * const attrs[] = { "objectGUID", "name",
"transportAddressAttribute",
NULL };
unsigned int j;
vertex = &graph->vertices.data[i];
transports_dn = kcctpl_transports_dn(service->samdb, tmp_ctx);
if (!transports_dn) {
DEBUG(1, (__location__ ": failed to find our own "
"Inter-Site Transports DN\n"));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
ret = ldb_search(service->samdb, tmp_ctx, &res, transports_dn,
LDB_SCOPE_ONELEVEL, attrs,
"objectClass=interSiteTransport");
if (ret != LDB_SUCCESS) {
DEBUG(1, (__location__ ": failed to find "
"interSiteTransport objects under %s: %s\n",
ldb_dn_get_linearized(transports_dn),
ldb_strerror(ret)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
for (j = 0; j < res->count; j++) {
struct ldb_message *transport, *bridgehead;
const char *transport_name;
struct GUID transport_guid, *new_data;
NTSTATUS status;
transport = res->msgs[j];
transport_name = ldb_msg_find_attr_as_string(transport,
"name", NULL);
if (!transport_name) {
DEBUG(1, (__location__ ": failed to find name "
"attribute of object %s\n",
ldb_dn_get_linearized(transport->dn)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
transport_guid = samdb_result_guid(transport,
"objectGUID");
if (site_vertex->color == RED &&
strncmp(transport_name, "IP", 2) != 0 &&
(cr_flags & FLAG_CR_NTDS_DOMAIN)) {
continue;
}
if (!kcctpl_find_edge_by_vertex_guid(graph,
vertex->id)) {
continue;
}
status = kcctpl_get_bridgehead_dc(service, tmp_ctx,
site_vertex->id,
cross_ref, transport,
partial_replica_okay,
detect_failed_dcs,
&bridgehead);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed to get a "
"bridgehead DC: %s\n",
nt_errstr(status)));
talloc_free(tmp_ctx);
return status;
}
if (!bridgehead) {
found_failed_dcs = true;
continue;
}
new_data = talloc_realloc(vertex,
vertex->accept_red_red.data,
struct GUID,
vertex->accept_red_red.count + 1);
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(new_data, tmp_ctx);
new_data[vertex->accept_red_red.count + 1] = transport_guid;
vertex->accept_red_red.data = new_data;
vertex->accept_red_red.count++;
new_data = talloc_realloc(vertex,
vertex->accept_black.data,
struct GUID,
vertex->accept_black.count + 1);
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(new_data, tmp_ctx);
new_data[vertex->accept_black.count + 1] = transport_guid;
vertex->accept_black.data = new_data;
vertex->accept_black.count++;
}
}
*_found_failed_dcs = found_failed_dcs;
talloc_free(tmp_ctx);
return NT_STATUS_OK;
}
/**
* setup the fields of the vertices that are relevant to Phase I (Dijkstra's
* Algorithm). for each vertex, set up its cost, root vertex and component. this
* defines the shortest-path forest structures.
*/
static void kcctpl_setup_vertices(struct kcctpl_graph *graph)
{
uint32_t i;
for (i = 0; i < graph->vertices.count; i++) {
struct kcctpl_vertex *vertex = &graph->vertices.data[i];
if (vertex->color == WHITE) {
vertex->repl_info.cost = UINT32_MAX;
vertex->root_id = vertex->component_id = GUID_zero();
} else {
vertex->repl_info.cost = 0;
vertex->root_id = vertex->component_id = vertex->id;
}
vertex->repl_info.interval = 0;
vertex->repl_info.options = 0xFFFFFFFF;
ZERO_STRUCT(vertex->repl_info.schedule);
vertex->demoted = false;
}
}
/**
* demote one vertex if necessary.
*/
static void kcctpl_check_demote_one_vertex(struct kcctpl_vertex *vertex,
struct GUID type)
{
if (vertex->color == WHITE) {
return;
}
if (!kcctpl_guid_list_contains(vertex->accept_black, type) &&
!kcctpl_guid_list_contains(vertex->accept_red_red, type)) {
vertex->repl_info.cost = UINT32_MAX;
vertex->root_id = GUID_zero();
vertex->demoted = true;
}
}
/**
* clear the demoted state of a vertex.
*/
static void kcctpl_undemote_one_vertex(struct kcctpl_vertex *vertex)
{
if (vertex->color == WHITE) {
return;
}
vertex->repl_info.cost = 0;
vertex->root_id = vertex->id;
vertex->demoted = false;
}
/**
* returns the id of the component containing 'vertex' by traversing the up-tree
* implied by the component pointers.
*/
static struct GUID kcctpl_get_component_id(struct kcctpl_graph *graph,
struct kcctpl_vertex *vertex)
{
struct kcctpl_vertex *u;
struct GUID root;
u = vertex;
while (!GUID_equal(&u->component_id, &u->id)) {
u = kcctpl_find_vertex_by_guid(graph, u->component_id);
}
root = u->id;
u = vertex;
while (!GUID_equal(&u->component_id, &u->id)) {
struct kcctpl_vertex *w;
w = kcctpl_find_vertex_by_guid(graph, u->component_id);
u->component_id = root;
u = w;
}
return root;
}
/**
* copy all spanning tree edges from 'output_edges' that contain the vertex for
* DCs in the local DC's site.
*/
static NTSTATUS kcctpl_copy_output_edges(struct kccsrv_service *service,
TALLOC_CTX *mem_ctx,
struct kcctpl_graph *graph,
struct kcctpl_multi_edge_list output_edges,
struct kcctpl_multi_edge_list *_copy)
{
struct kcctpl_multi_edge_list copy;
TALLOC_CTX *tmp_ctx;
struct ldb_message *site;
struct GUID site_guid;
uint32_t i;
ZERO_STRUCT(copy);
tmp_ctx = talloc_new(service);
NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
site = kcctpl_local_site(service->samdb, tmp_ctx);
if (!site) {
DEBUG(1, (__location__ ": failed to find our own local DC's "
"site\n"));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
site_guid = samdb_result_guid(site, "objectGUID");
for (i = 0; i < output_edges.count; i++) {
struct kcctpl_multi_edge *edge;
struct kcctpl_vertex *vertex1, *vertex2;
edge = &output_edges.data[i];
vertex1 = kcctpl_find_vertex_by_guid(graph,
edge->vertex_ids.data[0]);
if (!vertex1) {
DEBUG(1, (__location__ ": failed to find vertex %s\n",
GUID_string(tmp_ctx,
&edge->vertex_ids.data[0])));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
vertex2 = kcctpl_find_vertex_by_guid(graph,
edge->vertex_ids.data[1]);
if (!vertex2) {
DEBUG(1, (__location__ ": failed to find vertex %s\n",
GUID_string(tmp_ctx,
&edge->vertex_ids.data[1])));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
if (GUID_equal(&vertex1->id, &site_guid) ||
GUID_equal(&vertex2->id, &site_guid)) {
struct kcctpl_multi_edge *new_data;
if ((vertex1->color == BLACK ||
vertex2->color == BLACK) &&
vertex1->dist_to_red != UINT32_MAX) {
edge->directed = true;
if (vertex2->dist_to_red <
vertex1->dist_to_red) {
struct GUID tmp;
tmp = edge->vertex_ids.data[0];
edge->vertex_ids.data[0] = edge->vertex_ids.data[1];
edge->vertex_ids.data[1] = tmp;
}
}
new_data = talloc_realloc(tmp_ctx, copy.data,
struct kcctpl_multi_edge,
copy.count + 1);
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(new_data, tmp_ctx);
new_data[copy.count + 1] = *edge;
copy.data = new_data;
copy.count++;
}
}
talloc_steal(mem_ctx, copy.data);
talloc_free(tmp_ctx);
*_copy = copy;
return NT_STATUS_OK;
}
/**
* build the initial sequence for use with Dijkstra's algorithm. it will contain
* the red and black vertices as root vertices, unless these vertices accept no
* edges of the current 'type', or unless black vertices are not being
* including.
*/
static NTSTATUS kcctpl_setup_dijkstra(TALLOC_CTX *mem_ctx,
struct kcctpl_graph *graph,
struct GUID type, bool include_black,
struct kcctpl_vertex_list *_vertices)
{
struct kcctpl_vertex_list vertices;
uint32_t i;
kcctpl_setup_vertices(graph);
ZERO_STRUCT(vertices);
for (i = 0; i < graph->vertices.count; i++) {
struct kcctpl_vertex *vertex = &graph->vertices.data[i];
if (vertex->color == WHITE) {
continue;
}
if ((vertex->color == BLACK && !include_black) ||
!kcctpl_guid_list_contains(vertex->accept_black, type) ||
!kcctpl_guid_list_contains(vertex->accept_red_red, type)) {
vertex->repl_info.cost = UINT32_MAX;
vertex->root_id = GUID_zero();
vertex->demoted = true;
} else {
struct kcctpl_vertex *new_data;
new_data = talloc_realloc(mem_ctx, vertices.data,
struct kcctpl_vertex,
vertices.count + 1);
NT_STATUS_HAVE_NO_MEMORY(new_data);
new_data[vertices.count] = *vertex;
vertices.data = new_data;
vertices.count++;
}
}
*_vertices = vertices;
return NT_STATUS_OK;
}
/**
* merge schedules, replication intervals, options and costs.
*/
static bool kcctpl_combine_repl_info(struct kcctpl_graph *graph,
struct kcctpl_repl_info *ria,
struct kcctpl_repl_info *rib,
struct kcctpl_repl_info *ric)
{
uint8_t schedule[84];
bool is_available;
uint32_t i;
int32_t ric_cost;
is_available = false;
for (i = 0; i < 84; i++) {
schedule[i] = ria->schedule[i] & rib->schedule[i];
if (schedule[i] == 1) {
is_available = true;
}
}
if (!is_available) {
return false;
}
ric_cost = ria->cost + rib->cost;
ric->cost = (ric_cost < 0) ? UINT32_MAX : ric_cost;
ric->interval = MAX(ria->interval, rib->interval);
ric->options = ria->options & rib->options;
memcpy(&ric->schedule, &schedule, 84);
return true;
}
/**
* helper function for Dijkstra's algorithm. a new path has been found from a
* root vertex to vertex 'vertex2'. this path is ('vertex1->root, ..., vertex1,
* vertex2'). 'edge' is the edge connecting 'vertex1' and 'vertex2'. if this new
* path is better (in this case cheaper, or has a longer schedule), update
* 'vertex2' to use the new path.
*/
static NTSTATUS kcctpl_try_new_path(TALLOC_CTX *mem_ctx,
struct kcctpl_graph *graph,
struct kcctpl_vertex_list vertices,
struct kcctpl_vertex *vertex1,
struct kcctpl_multi_edge *edge,
struct kcctpl_vertex *vertex2)
{
struct kcctpl_repl_info new_repl_info;
bool intersect;
uint32_t i, new_duration, old_duration;
ZERO_STRUCT(new_repl_info);
intersect = kcctpl_combine_repl_info(graph, &vertex1->repl_info,
&edge->repl_info, &new_repl_info);
if (new_repl_info.cost > vertex2->repl_info.cost) {
return NT_STATUS_OK;
}
if (new_repl_info.cost < vertex2->repl_info.cost && !intersect) {
return NT_STATUS_OK;
}
new_duration = old_duration = 0;
for (i = 0; i < 84; i++) {
if (new_repl_info.schedule[i] == 1) {
new_duration++;
}
if (vertex2->repl_info.schedule[i] == 1) {
old_duration++;
}
}
if (new_repl_info.cost < vertex2->repl_info.cost ||
new_duration > old_duration) {
struct kcctpl_vertex *new_data;
vertex2->root_id = vertex1->root_id;
vertex2->component_id = vertex1->component_id;
vertex2->repl_info = new_repl_info;
new_data = talloc_realloc(mem_ctx, vertices.data,
struct kcctpl_vertex,
vertices.count + 1);
NT_STATUS_HAVE_NO_MEMORY(new_data);
new_data[vertices.count + 1] = *vertex2;
vertices.data = new_data;
vertices.count++;
}
return NT_STATUS_OK;
}
/**
* run Dijkstra's algorithm with the red (and possibly black) vertices as the
* root vertices, and build up a shortest-path forest.
*/
static NTSTATUS kcctpl_dijkstra(struct kcctpl_graph *graph, struct GUID type,
bool include_black)
{
TALLOC_CTX *tmp_ctx;
struct kcctpl_vertex_list vertices;
NTSTATUS status;
tmp_ctx = talloc_new(graph);
NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
status = kcctpl_setup_dijkstra(tmp_ctx, graph, type, include_black,
&vertices);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed to build the initial sequence "
"for Dijkstra's algorithm: %s\n", nt_errstr(status)));
talloc_free(tmp_ctx);
return status;
}
while (vertices.count > 0) {
uint32_t minimum_cost, minimum_index, i;
struct kcctpl_vertex *minimum_vertex, *new_data;
minimum_cost = UINT32_MAX;
minimum_index = -1;
minimum_vertex = NULL;
for (i = 0; i < vertices.count; i++) {
struct kcctpl_vertex *vertex = &vertices.data[i];
if (vertex->repl_info.cost < minimum_cost) {
minimum_cost = vertex->repl_info.cost;
minimum_vertex = vertex;
minimum_index = i;
} else if (vertex->repl_info.cost == minimum_cost &&
GUID_compare(&vertex->id,
&minimum_vertex->id) < 0) {
minimum_vertex = vertex;
minimum_index = i;
}
}
if (minimum_index < vertices.count - 1) {
memcpy(&vertices.data[minimum_index + 1],
&vertices.data[minimum_index],
vertices.count - minimum_index - 1);
}
new_data = talloc_realloc(tmp_ctx, vertices.data,
struct kcctpl_vertex,
vertices.count - 1);
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(new_data, tmp_ctx);
talloc_free(vertices.data);
vertices.data = new_data;
vertices.count--;
for (i = 0; i < graph->edges.count; i++) {
struct kcctpl_multi_edge *edge = &graph->edges.data[i];
if (kcctpl_guid_list_contains(minimum_vertex->edge_ids,
edge->id)) {
uint32_t j;
for (j = 0; j < edge->vertex_ids.count; j++) {
struct GUID vertex_id;
struct kcctpl_vertex *vertex;
vertex_id = edge->vertex_ids.data[j];
vertex = kcctpl_find_vertex_by_guid(graph,
vertex_id);
if (!vertex) {
DEBUG(1, (__location__
": failed to find "
"vertex %s\n",
GUID_string(tmp_ctx,
&vertex_id)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
kcctpl_try_new_path(tmp_ctx, graph,
vertices,
minimum_vertex,
edge, vertex);
}
}
}
}
talloc_free(tmp_ctx);
return NT_STATUS_OK;
}
/**
* add an edge to the list of edges that will be processed with Kruskal's. the
* endpoints are in fact the root of the vertices to pass in, so the endpoints
* are always colored vertices.
*/
static NTSTATUS kcctpl_add_int_edge(TALLOC_CTX *mem_ctx,
struct kcctpl_graph *graph,
struct kcctpl_internal_edge_list internal_edges,
struct kcctpl_multi_edge *edge,
struct kcctpl_vertex *vertex1,
struct kcctpl_vertex *vertex2)
{
struct kcctpl_vertex *root1, *root2;
bool red_red, found;
struct kcctpl_repl_info repl_info1, repl_info2;
struct kcctpl_internal_edge new_internal_edge, *new_data;
uint32_t i;
root1 = kcctpl_find_vertex_by_guid(graph, vertex1->root_id);
if (!root1) {
TALLOC_CTX *tmp_ctx = talloc_new(graph);
NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
DEBUG(1, (__location__ ": failed to find vertex %s\n",
GUID_string(tmp_ctx, &vertex1->root_id)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
root2 = kcctpl_find_vertex_by_guid(graph, vertex2->root_id);
if (!root2) {
TALLOC_CTX *tmp_ctx = talloc_new(graph);
NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
DEBUG(1, (__location__ ": failed to find vertex %s\n",
GUID_string(tmp_ctx, &vertex2->root_id)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
red_red = (root1->color == RED && root2->color == RED);
if (red_red) {
if (!kcctpl_guid_list_contains(root1->accept_red_red,
edge->type) ||
!kcctpl_guid_list_contains(root2->accept_red_red,
edge->type)) {
return NT_STATUS_OK;
}
} else if (!kcctpl_guid_list_contains(root1->accept_black,
edge->type) ||
!kcctpl_guid_list_contains(root2->accept_black,
edge->type)) {
return NT_STATUS_OK;
}
if (!kcctpl_combine_repl_info(graph, &vertex1->repl_info,
&vertex2->repl_info, &repl_info1) ||
!kcctpl_combine_repl_info(graph, &repl_info1, &edge->repl_info,
&repl_info2)) {
return NT_STATUS_OK;
}
new_internal_edge.v1id = root1->id;
new_internal_edge.v2id = root2->id;
new_internal_edge.red_red = red_red;
new_internal_edge.repl_info = repl_info2;
new_internal_edge.type = edge->type;
if (GUID_compare(&new_internal_edge.v1id,
&new_internal_edge.v2id) > 0) {
struct GUID tmp_guid = new_internal_edge.v1id;
new_internal_edge.v1id = new_internal_edge.v2id;
new_internal_edge.v2id = tmp_guid;
}
found = false;
for (i = 0; i < internal_edges.count; i++) {
struct kcctpl_internal_edge *ie = &internal_edges.data[i];
if (kcctpl_internal_edge_equal(ie, &new_internal_edge)) {
found = true;
}
}
if (found) {
return NT_STATUS_OK;
}
new_data = talloc_realloc(mem_ctx, internal_edges.data,
struct kcctpl_internal_edge,
internal_edges.count + 1);
NT_STATUS_HAVE_NO_MEMORY(new_data);
new_data[internal_edges.count + 1] = new_internal_edge;
internal_edges.data = new_data;
internal_edges.count++;
return NT_STATUS_OK;
}
/**
* after running Dijkstra's algorithm, this function examines a multi-edge and
* adds internal edges between every tree connected by this edge.
*/
static NTSTATUS kcctpl_process_edge(TALLOC_CTX *mem_ctx,
struct kcctpl_graph *graph,
struct kcctpl_multi_edge *edge,
struct kcctpl_internal_edge_list internal_edges)
{
TALLOC_CTX *tmp_ctx;
struct kcctpl_vertex_list vertices;
uint32_t i;
struct kcctpl_vertex *best_vertex;
ZERO_STRUCT(vertices);
tmp_ctx = talloc_new(mem_ctx);
NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
for (i = 0; i < edge->vertex_ids.count; i++) {
struct GUID id;
struct kcctpl_vertex *vertex, *new_data;
id = edge->vertex_ids.data[i];
vertex = kcctpl_find_vertex_by_guid(graph, id);
if (!vertex) {
DEBUG(1, (__location__ ": failed to find vertex %s\n",
GUID_string(tmp_ctx, &id)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
new_data = talloc_realloc(tmp_ctx, vertices.data,
struct kcctpl_vertex,
vertices.count + 1);
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(new_data, tmp_ctx);
new_data[vertices.count] = *vertex;
vertices.data = new_data;
vertices.count++;
}
qsort(vertices.data, vertices.count, sizeof(struct kcctpl_vertex),
kcctpl_sort_vertices);
best_vertex = &vertices.data[0];
for (i = 0; i < edge->vertex_ids.count; i++) {
struct GUID id, empty_id = GUID_zero();
struct kcctpl_vertex *vertex = &graph->vertices.data[i];
id = edge->vertex_ids.data[i];
vertex = kcctpl_find_vertex_by_guid(graph, id);
if (!vertex) {
DEBUG(1, (__location__ ": failed to find vertex %s\n",
GUID_string(tmp_ctx, &id)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
if (!GUID_equal(&vertex->component_id, &empty_id) &&
!GUID_equal(&vertex->root_id, &empty_id)) {
continue;
}
if (!GUID_equal(&best_vertex->component_id,
&empty_id) &&
!GUID_equal(&best_vertex->root_id, &empty_id) &&
!GUID_equal(&vertex->component_id, &empty_id) &&
!GUID_equal(&vertex->root_id, &empty_id) &&
!GUID_equal(&best_vertex->component_id,
&vertex->component_id)) {
NTSTATUS status;
status = kcctpl_add_int_edge(mem_ctx, graph,
internal_edges,
edge, best_vertex,
vertex);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed to add an "
"internal edge for %s: %s\n",
GUID_string(tmp_ctx, &vertex->id),
nt_errstr(status)));
talloc_free(tmp_ctx);
return status;
}
}
}
talloc_free(tmp_ctx);
return NT_STATUS_OK;
}
/**
* after running Dijkstra's algorithm to determine the shortest-path forest,
* examine all edges in this edge set. find all inter-tree edges, from which to
* build the list of 'internal edges', which will later be passed on to
* Kruskal's algorithm.
*/
static NTSTATUS kcctpl_process_edge_set(TALLOC_CTX *mem_ctx,
struct kcctpl_graph *graph,
struct kcctpl_multi_edge_set *set,
struct kcctpl_internal_edge_list internal_edges)
{
uint32_t i;
if (!set) {
for (i = 0; i < graph->edges.count; i++) {
struct kcctpl_multi_edge *edge;
uint32_t j;
NTSTATUS status;
edge = &graph->edges.data[i];
for (j = 0; j < edge->vertex_ids.count; j++) {
struct GUID id;
struct kcctpl_vertex *vertex;
id = edge->vertex_ids.data[j];
vertex = kcctpl_find_vertex_by_guid(graph, id);
if (!vertex) {
TALLOC_CTX *tmp_ctx;
tmp_ctx = talloc_new(mem_ctx);
NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
DEBUG(1, (__location__ ": failed to "
"find vertex %s\n",
GUID_string(tmp_ctx, &id)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
kcctpl_check_demote_one_vertex(vertex,
edge->type);
}
status = kcctpl_process_edge(mem_ctx, graph, edge,
internal_edges);
if (NT_STATUS_IS_ERR(status)) {
TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
DEBUG(1, (__location__ ": failed to process "
"edge %s: %s\n",
GUID_string(tmp_ctx, &edge->id),
nt_errstr(status)));
talloc_free(tmp_ctx);
return status;
}
for (j = 0; j < edge->vertex_ids.count; j++) {
struct GUID id;
struct kcctpl_vertex *vertex;
id = edge->vertex_ids.data[j];
vertex = kcctpl_find_vertex_by_guid(graph, id);
if (!vertex) {
TALLOC_CTX *tmp_ctx;
tmp_ctx = talloc_new(mem_ctx);
NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
DEBUG(1, (__location__ ": failed to "
"find vertex %s\n",
GUID_string(tmp_ctx, &id)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
kcctpl_undemote_one_vertex(vertex);
}
}
} else {
for (i = 0; i < graph->edges.count; i++) {
struct kcctpl_multi_edge *edge = &graph->edges.data[i];
if (kcctpl_guid_list_contains(set->edge_ids,
edge->id)) {
NTSTATUS status;
status = kcctpl_process_edge(mem_ctx, graph,
edge,
internal_edges);
if (NT_STATUS_IS_ERR(status)) {
TALLOC_CTX *tmp_ctx;
tmp_ctx = talloc_new(mem_ctx);
NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
DEBUG(1, (__location__ ": failed to "
"process edge %s: %s\n",
GUID_string(tmp_ctx,
&edge->id),
nt_errstr(status)));
talloc_free(tmp_ctx);
return status;
}
}
}
}
return NT_STATUS_OK;
}
/**
* a new edge, 'internal_edge', has been found for the spanning tree edge. add
* this edge to the list of output edges.
*/
static NTSTATUS kcctpl_add_out_edge(TALLOC_CTX *mem_ctx,
struct kcctpl_graph *graph,
struct kcctpl_multi_edge_list output_edges,
struct kcctpl_internal_edge *internal_edge)
{
struct kcctpl_vertex *vertex1, *vertex2;
TALLOC_CTX *tmp_ctx;
struct kcctpl_multi_edge *new_edge, *new_data;
struct GUID *new_data_id;
tmp_ctx = talloc_new(mem_ctx);
NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
vertex1 = kcctpl_find_vertex_by_guid(graph, internal_edge->v1id);
if (!vertex1) {
DEBUG(1, (__location__ ": failed to find vertex %s\n",
GUID_string(tmp_ctx, &internal_edge->v1id)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
vertex2 = kcctpl_find_vertex_by_guid(graph, internal_edge->v2id);
if (!vertex2) {
DEBUG(1, (__location__ ": failed to find vertex %s\n",
GUID_string(tmp_ctx, &internal_edge->v2id)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
new_edge = talloc(tmp_ctx, struct kcctpl_multi_edge);
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(new_edge, tmp_ctx);
new_edge->id = GUID_random(); /* TODO: what should be new_edge->GUID? */
new_edge->directed = false;
new_edge->vertex_ids.data = talloc_array(new_edge, struct GUID, 2);
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(new_edge->vertex_ids.data, tmp_ctx);
new_edge->vertex_ids.data[0] = vertex1->id;
new_edge->vertex_ids.data[1] = vertex2->id;
new_edge->vertex_ids.count = 2;
new_edge->type = internal_edge->type;
new_edge->repl_info = internal_edge->repl_info;
new_data = talloc_realloc(tmp_ctx, output_edges.data,
struct kcctpl_multi_edge,
output_edges.count + 1);
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(new_data, tmp_ctx);
new_data[output_edges.count + 1] = *new_edge;
output_edges.data = new_data;
output_edges.count++;
new_data_id = talloc_realloc(vertex1, vertex1->edge_ids.data,
struct GUID, vertex1->edge_ids.count);
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(new_data_id, tmp_ctx);
new_data_id[vertex1->edge_ids.count] = new_edge->id;
talloc_free(vertex1->edge_ids.data);
vertex1->edge_ids.data = new_data_id;
vertex1->edge_ids.count++;
new_data_id = talloc_realloc(vertex2, vertex2->edge_ids.data,
struct GUID, vertex2->edge_ids.count);
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(new_data_id, tmp_ctx);
new_data_id[vertex2->edge_ids.count] = new_edge->id;
talloc_free(vertex2->edge_ids.data);
vertex2->edge_ids.data = new_data_id;
vertex2->edge_ids.count++;
talloc_steal(graph, new_edge);
talloc_steal(mem_ctx, output_edges.data);
talloc_free(tmp_ctx);
return NT_STATUS_OK;
}
/**
* run Kruskal's minimum-cost spanning tree algorithm on the internal edges
* (that represent shortest paths in the original graph between colored
* vertices).
*/
static NTSTATUS kcctpl_kruskal(TALLOC_CTX *mem_ctx, struct kcctpl_graph *graph,
struct kcctpl_internal_edge_list internal_edges,
struct kcctpl_multi_edge_list *_output_edges)
{
uint32_t i, num_expected_tree_edges, cst_edges;
struct kcctpl_multi_edge_list output_edges;
num_expected_tree_edges = 0;
for (i = 0; i < graph->vertices.count; i++) {
struct kcctpl_vertex *vertex = &graph->vertices.data[i];
talloc_free(vertex->edge_ids.data);
ZERO_STRUCT(vertex->edge_ids);
if (vertex->color == RED || vertex->color == WHITE) {
num_expected_tree_edges++;
}
}
qsort(internal_edges.data, internal_edges.count,
sizeof(struct kcctpl_internal_edge), kcctpl_sort_internal_edges);
cst_edges = 0;
ZERO_STRUCT(output_edges);
while (internal_edges.count > 0 &&
cst_edges < num_expected_tree_edges) {
struct kcctpl_internal_edge *edge, *new_data;
struct kcctpl_vertex *vertex1, *vertex2;
struct GUID comp1, comp2;
edge = &internal_edges.data[0];
vertex1 = kcctpl_find_vertex_by_guid(graph, edge->v1id);
if (!vertex1) {
TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
DEBUG(1, (__location__ ": failed to find vertex %s\n",
GUID_string(tmp_ctx, &edge->v1id)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
vertex2 = kcctpl_find_vertex_by_guid(graph, edge->v2id);
if (!vertex2) {
TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
DEBUG(1, (__location__ ": failed to find vertex %s\n",
GUID_string(tmp_ctx, &edge->v2id)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
comp1 = kcctpl_get_component_id(graph, vertex1);
comp2 = kcctpl_get_component_id(graph, vertex2);
if (!GUID_equal(&comp1, &comp2)) {
NTSTATUS status;
struct kcctpl_vertex *vertex;
cst_edges++;
status = kcctpl_add_out_edge(mem_ctx, graph,
output_edges, edge);
if (NT_STATUS_IS_ERR(status)) {
TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
DEBUG(1, (__location__ ": failed to add an "
"output edge between %s and %s: %s\n",
GUID_string(tmp_ctx, &edge->v1id),
GUID_string(tmp_ctx, &edge->v2id),
nt_errstr(status)));
talloc_free(tmp_ctx);
return status;
}
vertex = kcctpl_find_vertex_by_guid(graph, comp1);
if (!vertex) {
TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
DEBUG(1, (__location__ ": failed to find "
"vertex %s\n", GUID_string(tmp_ctx,
&comp1)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
vertex->component_id = comp2;
}
internal_edges.data = internal_edges.data + 1;
new_data = talloc_realloc(mem_ctx, internal_edges.data,
struct kcctpl_internal_edge,
internal_edges.count - 1);
NT_STATUS_HAVE_NO_MEMORY(new_data);
talloc_free(internal_edges.data);
internal_edges.data = new_data;
internal_edges.count--;
}
*_output_edges = output_edges;
return NT_STATUS_OK;
}
/**
* count the number of components. a component is considered to be a bunch of
* colored vertices that are connected by the spanning tree. vertices whose
* component ID is the same as their vertex ID are the root of the connected
* component.
*/
static uint32_t kcctpl_count_components(struct kcctpl_graph *graph)
{
uint32_t num_components = 0, i;
for (i = 0; i < graph->vertices.count; i++) {
struct kcctpl_vertex *vertex;
struct GUID component_id;
vertex = &graph->vertices.data[i];
if (vertex->color == WHITE) {
continue;
}
component_id = kcctpl_get_component_id(graph, vertex);
if (GUID_equal(&component_id, &vertex->id)) {
vertex->component_index = num_components;
num_components++;
}
}
return num_components;
}
/**
* calculate the spanning tree and return the edges that include the vertex for
* the local site.
*/
static NTSTATUS kcctpl_get_spanning_tree_edges(struct kccsrv_service *service,
TALLOC_CTX *mem_ctx,
struct kcctpl_graph *graph,
uint32_t *_component_count,
struct kcctpl_multi_edge_list *_st_edge_list)
{
TALLOC_CTX *tmp_ctx;
struct kcctpl_internal_edge_list internal_edges;
uint32_t i, component_count;
NTSTATUS status;
struct kcctpl_multi_edge_list output_edges, st_edge_list;
ZERO_STRUCT(internal_edges);
tmp_ctx = talloc_new(mem_ctx);
NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
for (i = 0; i < graph->edge_sets.count; i++) {
struct kcctpl_multi_edge_set *set;
struct GUID edge_type;
uint32_t j;
set = &graph->edge_sets.data[i];
edge_type = GUID_zero();
for (j = 0; j < graph->vertices.count; j++) {
struct kcctpl_vertex *vertex = &graph->vertices.data[j];
talloc_free(vertex->edge_ids.data);
ZERO_STRUCT(vertex->edge_ids.data);
}
for (j = 0; j < set->edge_ids.count; j++) {
struct GUID edge_id;
struct kcctpl_multi_edge *edge;
uint32_t k;
edge_id = set->edge_ids.data[j];
edge = kcctpl_find_edge_by_guid(graph, edge_id);
if (!edge) {
DEBUG(1, (__location__ ": failed to find a "
"graph edge with ID=%s\n",
GUID_string(tmp_ctx, &edge_id)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
edge_type = edge->type;
for (k = 0; k < edge->vertex_ids.count; k++) {
struct GUID vertex_id, *new_data;
struct kcctpl_vertex *vertex;
vertex_id = edge->vertex_ids.data[k];
vertex = kcctpl_find_vertex_by_guid(graph,
vertex_id);
if (!vertex) {
DEBUG(1, (__location__ ": failed to "
"find a graph vertex with "
"ID=%s\n",
GUID_string(tmp_ctx,
&edge_id)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
new_data = talloc_realloc(tmp_ctx,
vertex->edge_ids.data,
struct GUID,
vertex->edge_ids.count + 1);
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(new_data,
tmp_ctx);
new_data[vertex->edge_ids.count] = edge->id;
vertex->edge_ids.data = new_data;
vertex->edge_ids.count++;
}
}
status = kcctpl_dijkstra(graph, edge_type, false);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed to run Dijkstra's "
"algorithm: %s\n", nt_errstr(status)));
talloc_free(tmp_ctx);
return status;
}
status = kcctpl_process_edge_set(tmp_ctx, graph, set,
internal_edges);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed to process edge set "
"%s: %s\n", GUID_string(tmp_ctx, &set->id),
nt_errstr(status)));
talloc_free(tmp_ctx);
return status;
}
status = kcctpl_dijkstra(graph, edge_type, true);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed to run Dijkstra's "
"algorithm: %s\n", nt_errstr(status)));
talloc_free(tmp_ctx);
return status;
}
status = kcctpl_process_edge_set(tmp_ctx, graph, set,
internal_edges);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed to process edge set "
"%s: %s\n", GUID_string(tmp_ctx, &set->id),
nt_errstr(status)));
talloc_free(tmp_ctx);
return status;
}
}
kcctpl_setup_vertices(graph);
status = kcctpl_process_edge_set(tmp_ctx, graph, NULL, internal_edges);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed to process empty edge set: "
"%s\n", nt_errstr(status)));
talloc_free(tmp_ctx);
return status;
}
status = kcctpl_kruskal(tmp_ctx, graph, internal_edges, &output_edges);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed to run Kruskal's algorithm: "
"%s\n", nt_errstr(status)));
talloc_free(tmp_ctx);
return status;
}
for (i = 0; i < graph->vertices.count; i++) {
struct kcctpl_vertex *vertex = &graph->vertices.data[i];
if (vertex->color == RED) {
vertex->dist_to_red = 0;
} else if (true) { /* TODO: if there exists a path from 'vertex'
to a RED vertex */
vertex->dist_to_red = -1; /* TODO: the length of the
shortest such path */
} else {
vertex->dist_to_red = UINT32_MAX;
}
}
component_count = kcctpl_count_components(graph);
status = kcctpl_copy_output_edges(service, tmp_ctx, graph, output_edges,
&st_edge_list);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed to copy edge list: %s\n",
nt_errstr(status)));
talloc_free(tmp_ctx);
return status;
}
*_component_count = component_count;
talloc_steal(mem_ctx, st_edge_list.data);
*_st_edge_list = st_edge_list;
talloc_free(tmp_ctx);
return NT_STATUS_OK;
}
/**
* creat an nTDSConnection object with the given parameters if one does not
* already exist.
*/
static NTSTATUS kcctpl_create_connection(struct kccsrv_service *service,
TALLOC_CTX *mem_ctx,
struct ldb_message *cross_ref,
struct ldb_message *r_bridgehead,
struct ldb_message *transport,
struct ldb_message *l_bridgehead,
struct kcctpl_repl_info repl_info,
uint8_t schedule[84],
bool detect_failed_dcs,
bool partial_replica_okay,
struct GUID_list *_keep_connections)
{
TALLOC_CTX *tmp_ctx;
struct ldb_dn *r_site_dn, *l_site_dn, *servers_dn;
bool ok;
struct GUID r_site_guid, l_site_guid;
int ret;
struct message_list r_bridgeheads_all, l_bridgeheads_all,
r_bridgeheads_available, l_bridgeheads_available;
NTSTATUS status;
struct ldb_result *res;
const char * const attrs[] = { "objectGUID", "parent", "fromServer",
"transportType", "schedule", "options",
"enabledConnection", NULL };
unsigned int i, valid_connections;
struct GUID_list keep_connections;
tmp_ctx = talloc_new(service);
NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
r_site_dn = ldb_dn_copy(tmp_ctx, r_bridgehead->dn);
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(r_site_dn, tmp_ctx);
ok = ldb_dn_remove_child_components(r_site_dn, 3);
if (!ok) {
talloc_free(tmp_ctx);
return NT_STATUS_NO_MEMORY;
}
ret = dsdb_find_guid_by_dn(service->samdb, r_site_dn, &r_site_guid);
if (ret != LDB_SUCCESS) {
DEBUG(1, (__location__ ": failed to find objectGUID for object "
"%s: %s\n", ldb_dn_get_linearized(r_site_dn),
ldb_strerror(ret)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
l_site_dn = ldb_dn_copy(tmp_ctx, l_bridgehead->dn);
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(l_site_dn, tmp_ctx);
ok = ldb_dn_remove_child_components(l_site_dn, 3);
if (!ok) {
talloc_free(tmp_ctx);
return NT_STATUS_NO_MEMORY;
}
ret = dsdb_find_guid_by_dn(service->samdb, l_site_dn, &l_site_guid);
if (ret != LDB_SUCCESS) {
DEBUG(1, (__location__ ": failed to find objectGUID for object "
"%s: %s\n", ldb_dn_get_linearized(l_site_dn),
ldb_strerror(ret)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
status = kcctpl_get_all_bridgehead_dcs(service, tmp_ctx,
r_site_guid, cross_ref,
transport, partial_replica_okay,
false, &r_bridgeheads_all);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed to get all bridgehead DCs: "
"%s\n", nt_errstr(status)));
return status;
}
status = kcctpl_get_all_bridgehead_dcs(service, tmp_ctx,
r_site_guid, cross_ref,
transport, partial_replica_okay,
detect_failed_dcs,
&r_bridgeheads_available);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed to get all bridgehead DCs: "
"%s\n", nt_errstr(status)));
return status;
}
status = kcctpl_get_all_bridgehead_dcs(service, tmp_ctx,
l_site_guid, cross_ref,
transport, partial_replica_okay,
false, &l_bridgeheads_all);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed to get all bridgehead DCs: "
"%s\n", nt_errstr(status)));
return status;
}
status = kcctpl_get_all_bridgehead_dcs(service, tmp_ctx,
l_site_guid, cross_ref,
transport, partial_replica_okay,
detect_failed_dcs,
&l_bridgeheads_available);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed to get all bridgehead DCs: "
"%s\n", nt_errstr(status)));
return status;
}
servers_dn = samdb_sites_dn(service->samdb, tmp_ctx);
if (!servers_dn) {
DEBUG(1, (__location__ ": failed to find our own Sites DN\n"));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
ok = ldb_dn_add_child_fmt(servers_dn, "CN=Servers");
if (!ok) {
talloc_free(tmp_ctx);
return NT_STATUS_NO_MEMORY;
}
ret = ldb_search(service->samdb, tmp_ctx, &res, servers_dn, LDB_SCOPE_SUBTREE,
attrs, "objectClass=nTDSConnection");
if (ret != LDB_SUCCESS) {
DEBUG(1, (__location__ ": failed to find nTDSConnection "
"objects: %s\n", ldb_strerror(ret)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
for (i = 0; i < res->count; i++) {
struct ldb_message *connection;
struct ldb_dn *parent_dn, *from_server;
connection = res->msgs[i];
parent_dn = ldb_dn_get_parent(tmp_ctx, connection->dn);
if (!parent_dn) {
DEBUG(1, (__location__ ": failed to get parent DN of "
"%s\n",
ldb_dn_get_linearized(connection->dn)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
from_server = samdb_result_dn(service->samdb, tmp_ctx, connection,
"fromServer", NULL);
if (!from_server) {
DEBUG(1, (__location__ ": failed to find fromServer "
"attribute of object %s\n",
ldb_dn_get_linearized(connection->dn)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
if (kcctpl_message_list_contains_dn(l_bridgeheads_all,
parent_dn) &&
kcctpl_message_list_contains_dn(r_bridgeheads_all,
from_server)) {
uint32_t conn_opts;
/* TODO: initialize conn_schedule from connection */
uint8_t conn_schedule[84];
struct ldb_dn *conn_transport_type;
conn_opts = ldb_msg_find_attr_as_uint(connection,
"options", 0);
conn_transport_type = samdb_result_dn(service->samdb, tmp_ctx,
connection,
"transportType",
NULL);
if (!conn_transport_type) {
DEBUG(1, (__location__ ": failed to find "
"transportType attribute of object "
"%s\n",
ldb_dn_get_linearized(connection->dn)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
if ((conn_opts & NTDSCONN_OPT_IS_GENERATED) &&
!(conn_opts & NTDSCONN_OPT_RODC_TOPOLOGY) &&
ldb_dn_compare(conn_transport_type,
transport->dn) == 0) {
if (!(conn_opts & NTDSCONN_OPT_USER_OWNED_SCHEDULE) &&
memcmp(&conn_schedule, &schedule, 84) != 0) {
/* TODO: perform an originating update
to set conn!schedule to schedule */
}
if ((conn_opts & NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT) &&
(conn_opts & NTDSCONN_OPT_USE_NOTIFY)) {
if (!(repl_info.options & NTDSSITELINK_OPT_USE_NOTIFY)) {
/* TODO: perform an originating
update to clear bits
NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT
and NTDSCONN_OPT_USE_NOTIFY
in conn!options */
}
} else if (repl_info.options & NTDSSITELINK_OPT_USE_NOTIFY) {
/* TODO: perform an originating update
to set bits
NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT
and NTDSCONN_OPT_USE_NOTIFY in
conn!options */
}
if (conn_opts & NTDSCONN_OPT_TWOWAY_SYNC) {
if (!(repl_info.options & NTDSSITELINK_OPT_TWOWAY_SYNC)) {
/* TODO: perform an originating
update to clear bit
NTDSCONN_OPT_TWOWAY_SYNC in
conn!options. */
}
} else if (repl_info.options & NTDSSITELINK_OPT_TWOWAY_SYNC) {
/* TODO: perform an originating update
to set bit NTDSCONN_OPT_TWOWAY_SYNC
in conn!options. */
}
if (conn_opts & NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION) {
if (!(repl_info.options & NTDSSITELINK_OPT_DISABLE_COMPRESSION)) {
/* TODO: perform an originating
update to clear bit
NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION
in conn!options. */
}
} else if (repl_info.options & NTDSSITELINK_OPT_DISABLE_COMPRESSION) {
/* TODO: perform an originating update
to set bit
NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION
in conn!options. */
}
}
}
}
ZERO_STRUCT(keep_connections);
valid_connections = 0;
for (i = 0; i < res->count; i++) {
struct ldb_message *connection;
struct ldb_dn *parent_dn, *from_server;
connection = res->msgs[i];
parent_dn = ldb_dn_get_parent(tmp_ctx, connection->dn);
if (!parent_dn) {
DEBUG(1, (__location__ ": failed to get parent DN of "
"%s\n",
ldb_dn_get_linearized(connection->dn)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
from_server = samdb_result_dn(service->samdb, tmp_ctx, connection,
"fromServer", NULL);
if (!from_server) {
DEBUG(1, (__location__ ": failed to find fromServer "
"attribute of object %s\n",
ldb_dn_get_linearized(connection->dn)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
if (kcctpl_message_list_contains_dn(l_bridgeheads_all,
parent_dn) &&
kcctpl_message_list_contains_dn(r_bridgeheads_all,
from_server)) {
uint32_t conn_opts;
struct ldb_dn *conn_transport_type;
conn_opts = ldb_msg_find_attr_as_uint(connection,
"options", 0);
conn_transport_type = samdb_result_dn(service->samdb, tmp_ctx,
connection,
"transportType",
NULL);
if (!conn_transport_type) {
DEBUG(1, (__location__ ": failed to find "
"transportType attribute of object "
"%s\n",
ldb_dn_get_linearized(connection->dn)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
if ((!(conn_opts & NTDSCONN_OPT_IS_GENERATED) ||
ldb_dn_compare(conn_transport_type,
transport->dn) == 0) &&
!(conn_opts & NTDSCONN_OPT_RODC_TOPOLOGY)) {
struct GUID r_guid, l_guid, conn_guid;
bool failed_state_r, failed_state_l;
ret = dsdb_find_guid_by_dn(service->samdb, from_server,
&r_guid);
if (ret != LDB_SUCCESS) {
DEBUG(1, (__location__ ": failed to "
"find GUID for object %s\n",
ldb_dn_get_linearized(from_server)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
ret = dsdb_find_guid_by_dn(service->samdb, parent_dn,
&l_guid);
if (ret != LDB_SUCCESS) {
DEBUG(1, (__location__ ": failed to "
"find GUID for object %s\n",
ldb_dn_get_linearized(parent_dn)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
status = kcctpl_bridgehead_dc_failed(service->samdb,
r_guid,
detect_failed_dcs,
&failed_state_r);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed to "
"check if bridgehead DC has "
"failed: %s\n",
nt_errstr(status)));
talloc_free(tmp_ctx);
return status;
}
status = kcctpl_bridgehead_dc_failed(service->samdb,
l_guid,
detect_failed_dcs,
&failed_state_l);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed to "
"check if bridgehead DC has "
"failed: %s\n",
nt_errstr(status)));
talloc_free(tmp_ctx);
return status;
}
if (!failed_state_r && !failed_state_l) {
valid_connections++;
}
conn_guid = samdb_result_guid(connection,
"objectGUID");
if (!kcctpl_guid_list_contains(keep_connections,
conn_guid)) {
struct GUID *new_data;
new_data = talloc_realloc(tmp_ctx,
keep_connections.data,
struct GUID,
keep_connections.count + 1);
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(new_data,
tmp_ctx);
new_data[keep_connections.count] = conn_guid;
keep_connections.data = new_data;
keep_connections.count++;
}
}
}
}
if (valid_connections == 0) {
uint64_t opts = NTDSCONN_OPT_IS_GENERATED;
struct GUID new_guid, *new_data;
if (repl_info.options & NTDSSITELINK_OPT_USE_NOTIFY) {
opts |= NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT;
opts |= NTDSCONN_OPT_USE_NOTIFY;
}
if (repl_info.options & NTDSSITELINK_OPT_TWOWAY_SYNC) {
opts |= NTDSCONN_OPT_TWOWAY_SYNC;
}
if (repl_info.options & NTDSSITELINK_OPT_DISABLE_COMPRESSION) {
opts |= NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION;
}
/* perform an originating update to create a new nTDSConnection
* object cn that is:
*
* - child of l_bridgehead
* - cn!enabledConnection = true
* - cn!options = opts
* - cn!transportType = t
* - cn!fromServer = r_bridgehead
* - cn!schedule = schedule
*/
/* TODO: what should be the new connection's GUID? */
new_guid = GUID_random();
new_data = talloc_realloc(tmp_ctx, keep_connections.data,
struct GUID,
keep_connections.count + 1);
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(new_data, tmp_ctx);
new_data[keep_connections.count] = new_guid;
keep_connections.data = new_data;
keep_connections.count++;
}
talloc_steal(mem_ctx, keep_connections.data);
*_keep_connections = keep_connections;
talloc_free(tmp_ctx);
return NT_STATUS_OK;
}
/**
* construct an NC replica graph for the NC identified by the given 'cross_ref',
* then create any additional nTDSConnection objects required.
*/
static NTSTATUS kcctpl_create_connections(struct kccsrv_service *service,
TALLOC_CTX *mem_ctx,
struct kcctpl_graph *graph,
struct ldb_message *cross_ref,
bool detect_failed_dcs,
struct GUID_list keep_connections,
bool *_found_failed_dcs,
bool *_connected)
{
bool connected, found_failed_dcs, partial_replica_okay;
NTSTATUS status;
struct ldb_message *site;
TALLOC_CTX *tmp_ctx;
struct GUID site_guid;
struct kcctpl_vertex *site_vertex;
uint32_t component_count, i;
struct kcctpl_multi_edge_list st_edge_list;
struct ldb_dn *transports_dn;
const char * const attrs[] = { "bridgeheadServerListBL", "name",
"transportAddressAttribute", NULL };
int ret;
connected = true;
status = kcctpl_color_vertices(service, graph, cross_ref, detect_failed_dcs,
&found_failed_dcs);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed to color vertices: %s\n",
nt_errstr(status)));
return status;
}
tmp_ctx = talloc_new(mem_ctx);
NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
site = kcctpl_local_site(service->samdb, tmp_ctx);
if (!site) {
DEBUG(1, (__location__ ": failed to find our own local DC's "
"site\n"));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
site_guid = samdb_result_guid(site, "objectGUID");
site_vertex = kcctpl_find_vertex_by_guid(graph, site_guid);
if (!site_vertex) {
DEBUG(1, (__location__ ": failed to find vertex %s\n",
GUID_string(tmp_ctx, &site_guid)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
if (site_vertex->color == WHITE) {
*_found_failed_dcs = found_failed_dcs;
*_connected = true;
talloc_free(tmp_ctx);
return NT_STATUS_OK;
}
status = kcctpl_get_spanning_tree_edges(service, tmp_ctx, graph,
&component_count,
&st_edge_list);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed get spanning tree edges: %s\n",
nt_errstr(status)));
talloc_free(tmp_ctx);
return status;
}
if (component_count > 1) {
connected = false;
}
partial_replica_okay = (site_vertex->color == BLACK);
transports_dn = kcctpl_transports_dn(service->samdb, tmp_ctx);
if (!transports_dn) {
DEBUG(1, (__location__ ": failed to find our own Inter-Site "
"Transports DN\n"));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
for (i = 0; i < st_edge_list.count; i++) {
struct kcctpl_multi_edge *edge;
struct GUID other_site_id;
struct kcctpl_vertex *other_site_vertex;
struct ldb_result *res;
struct ldb_message *transport, *r_bridgehead, *l_bridgehead;
uint8_t schedule[84];
uint32_t first_available, j, interval;
edge = &st_edge_list.data[i];
if (edge->directed && !GUID_equal(&edge->vertex_ids.data[1],
&site_vertex->id)) {
continue;
}
if (GUID_equal(&edge->vertex_ids.data[0], &site_vertex->id)) {
other_site_id = edge->vertex_ids.data[1];
} else {
other_site_id = edge->vertex_ids.data[0];
}
other_site_vertex = kcctpl_find_vertex_by_guid(graph,
other_site_id);
if (!other_site_vertex) {
DEBUG(1, (__location__ ": failed to find vertex %s\n",
GUID_string(tmp_ctx, &other_site_id)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
ret = ldb_search(service->samdb, tmp_ctx, &res, transports_dn,
LDB_SCOPE_ONELEVEL, attrs,
"(&(objectClass=interSiteTransport)"
"(objectGUID=%s))", GUID_string(tmp_ctx,
&edge->type));
if (ret != LDB_SUCCESS) {
DEBUG(1, (__location__ ": failed to find "
"interSiteTransport object %s: %s\n",
GUID_string(tmp_ctx, &edge->type),
ldb_strerror(ret)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
if (res->count == 0) {
DEBUG(1, (__location__ ": failed to find "
"interSiteTransport object %s\n",
GUID_string(tmp_ctx, &edge->type)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
transport = res->msgs[0];
status = kcctpl_get_bridgehead_dc(service, tmp_ctx,
other_site_vertex->id,
cross_ref, transport,
partial_replica_okay,
detect_failed_dcs,
&r_bridgehead);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed to get a bridgehead "
"DC: %s\n", nt_errstr(status)));
talloc_free(tmp_ctx);
return status;
}
if (service->am_rodc) {
/* TODO: l_bridgehad = nTDSDSA of local DC */
} else {
status = kcctpl_get_bridgehead_dc(service, tmp_ctx,
site_vertex->id,
cross_ref, transport,
partial_replica_okay,
detect_failed_dcs,
&l_bridgehead);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed to get a "
"bridgehead DC: %s\n",
nt_errstr(status)));
talloc_free(tmp_ctx);
return status;
}
}
ZERO_ARRAY(schedule);
first_available = 84;
interval = edge->repl_info.interval / 15;
for (j = 0; j < 84; j++) {
if (edge->repl_info.schedule[j] == 1) {
first_available = j;
break;
}
}
for (j = first_available; j < 84; j += interval) {
schedule[j] = 1;
}
status = kcctpl_create_connection(service, mem_ctx, cross_ref,
r_bridgehead, transport,
l_bridgehead, edge->repl_info,
schedule, detect_failed_dcs,
partial_replica_okay,
&keep_connections);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed to create a "
"connection: %s\n", nt_errstr(status)));
talloc_free(tmp_ctx);
return status;
}
}
*_found_failed_dcs = found_failed_dcs;
*_connected = connected;
talloc_free(tmp_ctx);
return NT_STATUS_OK;
}
/**
* computes an NC replica graph for each NC replica that "should be present" on
* the local DC or "is present" on any DC in the same site as the local DC. for
* each edge directed to an NC replica on such a DC from an NC replica on a DC
* in another site, the KCC creates and nTDSConnection object to imply that edge
* if one does not already exist.
*/
static NTSTATUS kcctpl_create_intersite_connections(struct kccsrv_service *service,
TALLOC_CTX *mem_ctx,
struct GUID_list *_keep_connections,
bool *_all_connected)
{
struct GUID_list keep_connections;
bool all_connected;
TALLOC_CTX *tmp_ctx;
struct ldb_dn *partitions_dn;
struct ldb_result *res;
const char * const attrs[] = { "enabled", "systemFlags", "nCName",
NULL };
int ret;
unsigned int i;
all_connected = true;
ZERO_STRUCT(keep_connections);
tmp_ctx = talloc_new(mem_ctx);
NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
partitions_dn = samdb_partitions_dn(service->samdb, tmp_ctx);
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(partitions_dn, tmp_ctx);
ret = ldb_search(service->samdb, tmp_ctx, &res, partitions_dn, LDB_SCOPE_ONELEVEL,
attrs, "objectClass=crossRef");
if (ret != LDB_SUCCESS) {
DEBUG(1, (__location__ ": failed to find crossRef objects: "
"%s\n", ldb_strerror(ret)));
talloc_free(tmp_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
for (i = 0; i < res->count; i++) {
struct ldb_message *cross_ref;
unsigned int cr_enabled;
int64_t cr_flags;
struct kcctpl_graph *graph;
bool found_failed_dc, connected;
NTSTATUS status;
cross_ref = res->msgs[i];
cr_enabled = ldb_msg_find_attr_as_uint(cross_ref, "enabled", -1);
cr_flags = ldb_msg_find_attr_as_int64(cross_ref, "systemFlags", 0);
if ((cr_enabled == 0) || !(cr_flags & FLAG_CR_NTDS_NC)) {
continue;
}
status = kcctpl_setup_graph(service->samdb, tmp_ctx, &graph);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed to create a graph: "
"%s\n", nt_errstr(status)));
talloc_free(tmp_ctx);
return status;
}
status = kcctpl_create_connections(service, mem_ctx, graph,
cross_ref, true,
keep_connections,
&found_failed_dc,
&connected);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed to create "
"connections: %s\n", nt_errstr(status)));
talloc_free(tmp_ctx);
return status;
}
if (!connected) {
all_connected = false;
if (found_failed_dc) {
status = kcctpl_create_connections(service, mem_ctx,
graph,
cross_ref,
true,
keep_connections,
&found_failed_dc,
&connected);
if (NT_STATUS_IS_ERR(status)) {
DEBUG(1, (__location__ ": failed to "
"create connections: %s\n",
nt_errstr(status)));
talloc_free(tmp_ctx);
return status;
}
}
}
}
*_keep_connections = keep_connections;
*_all_connected = all_connected;
talloc_free(tmp_ctx);
return NT_STATUS_OK;
}
NTSTATUS kcctpl_test(struct kccsrv_service *service)
{
NTSTATUS status;
TALLOC_CTX *tmp_ctx = talloc_new(service);
struct GUID_list keep;
bool all_connected;
DEBUG(5, ("Testing kcctpl_create_intersite_connections\n"));
status = kcctpl_create_intersite_connections(service, tmp_ctx, &keep,
&all_connected);
DEBUG(4, ("%s\n", nt_errstr(status)));
if (NT_STATUS_IS_OK(status)) {
uint32_t i;
DEBUG(4, ("all_connected=%d, %d GUIDs returned\n",
all_connected, keep.count));
for (i = 0; i < keep.count; i++) {
DEBUG(4, ("GUID %d: %s\n", i + 1,
GUID_string(tmp_ctx, &keep.data[i])));
}
}
talloc_free(tmp_ctx);
return status;
}