summaryrefslogtreecommitdiffstats
path: root/source4/dsdb/kcc
diff options
context:
space:
mode:
Diffstat (limited to 'source4/dsdb/kcc')
-rw-r--r--source4/dsdb/kcc/garbage_collect_tombstones.c347
-rw-r--r--source4/dsdb/kcc/garbage_collect_tombstones.h34
-rw-r--r--source4/dsdb/kcc/kcc_connection.c252
-rw-r--r--source4/dsdb/kcc/kcc_connection.h39
-rw-r--r--source4/dsdb/kcc/kcc_drs_replica_info.c911
-rw-r--r--source4/dsdb/kcc/kcc_periodic.c825
-rw-r--r--source4/dsdb/kcc/kcc_service.c364
-rw-r--r--source4/dsdb/kcc/kcc_service.h101
-rw-r--r--source4/dsdb/kcc/scavenge_dns_records.c531
-rw-r--r--source4/dsdb/kcc/scavenge_dns_records.h36
10 files changed, 3440 insertions, 0 deletions
diff --git a/source4/dsdb/kcc/garbage_collect_tombstones.c b/source4/dsdb/kcc/garbage_collect_tombstones.c
new file mode 100644
index 0000000..14ee2d4
--- /dev/null
+++ b/source4/dsdb/kcc/garbage_collect_tombstones.c
@@ -0,0 +1,347 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ handle removal of deleted objects
+
+ Copyright (C) 2009 Andrew Tridgell
+ Copyright (C) 2016 Andrew Bartlett
+ Copyright (C) 2016 Catalyst.NET Ltd
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include <ldb_errors.h>
+#include "../lib/util/dlinklist.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_drsuapi.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "param/param.h"
+#include "lib/util/dlinklist.h"
+#include "ldb.h"
+#include "dsdb/kcc/garbage_collect_tombstones.h"
+#include "lib/ldb-samba/ldb_matching_rules.h"
+#include "lib/util/time.h"
+
+static NTSTATUS garbage_collect_tombstones_part(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ struct dsdb_ldb_dn_list_node *part,
+ char *filter,
+ unsigned int *num_links_removed,
+ unsigned int *num_objects_removed,
+ struct dsdb_schema *schema,
+ const char **attrs,
+ char **error_string,
+ NTTIME expunge_time_nttime)
+{
+ int ret;
+ struct ldb_dn *do_dn;
+ struct ldb_result *res;
+ unsigned int i, j, k;
+ uint32_t flags;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dsdb_get_deleted_objects_dn(samdb, tmp_ctx, part->dn, &do_dn);
+ if (ret != LDB_SUCCESS) {
+ TALLOC_FREE(tmp_ctx);
+ /* some partitions have no Deleted Objects
+ container */
+ return NT_STATUS_OK;
+ }
+
+ DBG_INFO("Doing a full scan on %s and looking for deleted objects\n",
+ ldb_dn_get_linearized(part->dn));
+
+ flags = DSDB_SEARCH_SHOW_RECYCLED |
+ DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT |
+ DSDB_SEARCH_REVEAL_INTERNALS;
+ ret = dsdb_search(samdb, tmp_ctx, &res, part->dn, LDB_SCOPE_SUBTREE,
+ attrs, flags, "%s", filter);
+
+ if (ret != LDB_SUCCESS) {
+ *error_string = talloc_asprintf(mem_ctx,
+ "Failed to search for deleted "
+ "objects in %s: %s",
+ ldb_dn_get_linearized(do_dn),
+ ldb_errstring(samdb));
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ for (i=0; i<res->count; i++) {
+ struct ldb_message *cleanup_msg = NULL;
+ unsigned int num_modified = 0;
+
+ bool isDeleted = ldb_msg_find_attr_as_bool(res->msgs[i],
+ "isDeleted", false);
+ if (isDeleted) {
+ if (ldb_dn_compare(do_dn, res->msgs[i]->dn) == 0) {
+ /* Skip the Deleted Object Container */
+ continue;
+ }
+
+ ret = dsdb_delete(samdb, res->msgs[i]->dn,
+ DSDB_SEARCH_SHOW_RECYCLED
+ |DSDB_MODIFY_RELAX);
+ if (ret != LDB_SUCCESS) {
+ DBG_WARNING(__location__ ": Failed to remove "
+ "deleted object %s\n",
+ ldb_dn_get_linearized(res->
+ msgs[i]->dn));
+ } else {
+ DBG_INFO("Removed deleted object %s\n",
+ ldb_dn_get_linearized(res->
+ msgs[i]->dn));
+ (*num_objects_removed)++;
+ }
+ continue;
+ }
+
+ /* This must have a linked attribute */
+
+ /*
+ * From MS-ADTS 3.1.1.1.9 DCs, usn Counters, and
+ * the Originating Update Stamp
+ *
+ * "A link value r is deleted, but exists as a
+ * tombstone, if r.stamp.timeDeleted ≠ 0. When
+ * the current time minus r.stamp.timeDeleted
+ * exceeds the tombstone lifetime, the link
+ * value r is garbage-collected; that is,
+ * removed from its containing forward link
+ * attribute. "
+ */
+
+ for (j=0; j < res->msgs[i]->num_elements; j++) {
+ struct ldb_message_element *element = NULL;
+ /* TODO this is O(log n) per attribute with deleted values */
+ const struct dsdb_attribute *attrib = NULL;
+
+ element = &res->msgs[i]->elements[j];
+ attrib = dsdb_attribute_by_lDAPDisplayName(schema,
+ element->name);
+
+ /* This avoids parsing isDeleted as a link */
+ if (attrib == NULL ||
+ attrib->linkID == 0 ||
+ ((attrib->linkID & 1) == 1)) {
+ continue;
+ }
+
+ for (k = 0; k < element->num_values; k++) {
+ struct ldb_val *value = &element->values[k];
+ uint64_t whenChanged = 0;
+ NTSTATUS status;
+ struct dsdb_dn *dn;
+ struct ldb_message_element *cleanup_elem = NULL;
+ char *guid_search_str = NULL;
+ char *guid_buf_str = NULL;
+ struct ldb_val cleanup_val;
+ struct GUID_txt_buf buf_guid;
+ struct GUID guid;
+ const struct ldb_val *guid_blob;
+
+ if (dsdb_dn_is_deleted_val(value) == false) {
+ continue;
+ }
+
+ dn = dsdb_dn_parse(tmp_ctx, samdb,
+ &element->values[k],
+ attrib->syntax->ldap_oid);
+ if (dn == NULL) {
+ DBG_WARNING("Failed to parse linked attribute blob of "
+ "%s on %s while expunging expired links\n",
+ element->name,
+ ldb_dn_get_linearized(res->msgs[i]->dn));
+ continue;
+ }
+
+ status = dsdb_get_extended_dn_uint64(dn->dn,
+ &whenChanged,
+ "RMD_CHANGETIME");
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("Error: RMD_CHANGETIME is missing on a forward link.\n");
+ talloc_free(dn);
+ continue;
+ }
+
+ if (whenChanged >= expunge_time_nttime) {
+ talloc_free(dn);
+ continue;
+ }
+
+ guid_blob = ldb_dn_get_extended_component(dn->dn, "GUID");
+ status = GUID_from_ndr_blob(guid_blob, &guid);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("Error: Invalid GUID on link target.\n");
+ talloc_free(dn);
+ continue;
+ }
+
+ guid_buf_str = GUID_buf_string(&guid, &buf_guid);
+ guid_search_str = talloc_asprintf(mem_ctx,
+ "<GUID=%s>;%s",
+ guid_buf_str,
+ dsdb_dn_get_linearized(mem_ctx, dn));
+ cleanup_val = data_blob_string_const(guid_search_str);
+
+ talloc_free(dn);
+
+ if (cleanup_msg == NULL) {
+ cleanup_msg = ldb_msg_new(mem_ctx);
+ if (cleanup_msg == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ cleanup_msg->dn = res->msgs[i]->dn;
+ }
+
+ ret = ldb_msg_add_value(cleanup_msg,
+ element->name,
+ &cleanup_val,
+ &cleanup_elem);
+ if (ret != LDB_SUCCESS) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ cleanup_elem->flags = LDB_FLAG_MOD_DELETE;
+ num_modified++;
+ }
+ }
+
+ if (num_modified > 0) {
+ ret = dsdb_modify(samdb, cleanup_msg,
+ DSDB_REPLMD_VANISH_LINKS);
+ if (ret != LDB_SUCCESS) {
+ DBG_WARNING(__location__ ": Failed to remove deleted object %s\n",
+ ldb_dn_get_linearized(res->msgs[i]->dn));
+ } else {
+ DBG_INFO("Removed deleted object %s\n",
+ ldb_dn_get_linearized(res->msgs[i]->dn));
+ *num_links_removed = *num_links_removed + num_modified;
+ }
+
+ }
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
+/*
+ * Per MS-ADTS 3.1.1.5.5 Delete Operation
+ *
+ * "Tombstones are a type of deleted object distinguished from
+ * existing-objects by the presence of the isDeleted attribute with the
+ * value true."
+ *
+ * "After a time period at least as large as a tombstone lifetime, the
+ * tombstone is removed from the directory."
+ *
+ * The purpose of this routine is to remove such objects. It is
+ * called from a timed event in the KCC, and from samba-tool domain
+ * expunge tombstones.
+ *
+ * Additionally, linked attributes have similar properties.
+ */
+NTSTATUS dsdb_garbage_collect_tombstones(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ struct dsdb_ldb_dn_list_node *part,
+ time_t current_time,
+ uint32_t tombstoneLifetime,
+ unsigned int *num_objects_removed,
+ unsigned int *num_links_removed,
+ char **error_string)
+{
+ const char **attrs = NULL;
+ char *filter = NULL;
+ NTSTATUS status;
+ unsigned int i;
+ struct dsdb_attribute *next_attr;
+ unsigned int num_link_attrs;
+ struct dsdb_schema *schema = dsdb_get_schema(samdb, mem_ctx);
+ unsigned long long expunge_time = current_time - tombstoneLifetime*60*60*24;
+ char *expunge_time_string = ldb_timestring_utc(mem_ctx, expunge_time);
+ NTTIME expunge_time_nttime;
+ unix_to_nt_time(&expunge_time_nttime, expunge_time);
+
+ *num_objects_removed = 0;
+ *num_links_removed = 0;
+ *error_string = NULL;
+ num_link_attrs = 0;
+
+ /*
+ * This filter is a bit strange, but the idea is to filter for
+ * objects that need to have tombstones expunged without
+ * bringing a potentially large database all into memory. To
+ * do that, we could use callbacks, but instead we use a
+ * custom match rule to triage the objects during the search,
+ * and ideally avoid memory allocation for most of the
+ * un-matched objects.
+ *
+ * The parameter to DSDB_MATCH_FOR_EXPUNGE is the NTTIME, we
+ * return records with deleted links deleted before this time.
+ *
+ * We use a date comparison on whenChanged to avoid returning
+ * all isDeleted records
+ */
+
+ filter = talloc_asprintf(mem_ctx, "(|");
+ for (next_attr = schema->attributes; next_attr != NULL; next_attr = next_attr->next) {
+ if (next_attr->linkID != 0 && ((next_attr->linkID & 1) == 0)) {
+ num_link_attrs++;
+ filter = talloc_asprintf_append(filter,
+ "(%s:" DSDB_MATCH_FOR_EXPUNGE ":=%llu)",
+ next_attr->lDAPDisplayName,
+ (unsigned long long)expunge_time_nttime);
+ if (filter == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ }
+
+ attrs = talloc_array(mem_ctx, const char *, num_link_attrs + 2);
+ i = 0;
+ for (next_attr = schema->attributes; next_attr != NULL; next_attr = next_attr->next) {
+ if (next_attr->linkID != 0 && ((next_attr->linkID & 1) == 0)) {
+ attrs[i++] = next_attr->lDAPDisplayName;
+ }
+ }
+ attrs[i] = "isDeleted";
+ attrs[i+1] = NULL;
+
+ filter = talloc_asprintf_append(filter,
+ "(&(isDeleted=TRUE)(whenChanged<=%s)))",
+ expunge_time_string);
+ if (filter == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (; part != NULL; part = part->next) {
+ status = garbage_collect_tombstones_part(mem_ctx, samdb, part,
+ filter,
+ num_links_removed,
+ num_objects_removed,
+ schema, attrs,
+ error_string,
+ expunge_time_nttime);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source4/dsdb/kcc/garbage_collect_tombstones.h b/source4/dsdb/kcc/garbage_collect_tombstones.h
new file mode 100644
index 0000000..ce62f5d
--- /dev/null
+++ b/source4/dsdb/kcc/garbage_collect_tombstones.h
@@ -0,0 +1,34 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ handle removal of deleted objects
+
+ Copyright (C) 2009 Andrew Tridgell
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+#include "param/param.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/common/util.h"
+
+
+NTSTATUS dsdb_garbage_collect_tombstones(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ struct dsdb_ldb_dn_list_node *part,
+ time_t current_time,
+ uint32_t tombstoneLifetime,
+ unsigned int *num_objects_removed,
+ unsigned int *num_links_removed,
+ char **error_string);
diff --git a/source4/dsdb/kcc/kcc_connection.c b/source4/dsdb/kcc/kcc_connection.c
new file mode 100644
index 0000000..78d853e
--- /dev/null
+++ b/source4/dsdb/kcc/kcc_connection.c
@@ -0,0 +1,252 @@
+/*
+ Unix SMB/CIFS implementation.
+ KCC service periodic handling
+
+ Copyright (C) Crístian Deives
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "lib/events/events.h"
+#include "dsdb/samdb/samdb.h"
+#include "auth/auth.h"
+#include "samba/service.h"
+#include "lib/messaging/irpc.h"
+#include "dsdb/kcc/kcc_service.h"
+#include "dsdb/kcc/kcc_connection.h"
+#include <ldb_errors.h>
+#include "../lib/util/dlinklist.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_drsuapi.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "param/param.h"
+
+static int kccsrv_add_connection(struct kccsrv_service *s,
+ struct kcc_connection *conn)
+{
+ struct ldb_message *msg;
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_dn *new_dn, *server_dn;
+ struct GUID guid;
+ /* struct ldb_val schedule_val; */
+ int ret;
+ bool ok;
+
+ tmp_ctx = talloc_new(s);
+ if (!tmp_ctx) {
+ DEBUG(0, ("failed to talloc\n"));
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ goto done;
+ }
+ new_dn = samdb_ntds_settings_dn(s->samdb, tmp_ctx);
+ if (!new_dn) {
+ DEBUG(0, ("failed to find NTDS settings\n"));
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ goto done;
+ }
+ new_dn = ldb_dn_copy(tmp_ctx, new_dn);
+ if (!new_dn) {
+ DEBUG(0, ("failed to copy NTDS settings\n"));
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ goto done;
+ }
+ guid = GUID_random();
+ ok = ldb_dn_add_child_fmt(new_dn, "CN=%s", GUID_string(tmp_ctx, &guid));
+ if (!ok) {
+ DEBUG(0, ("failed to create nTDSConnection DN\n"));
+ ret = LDB_ERR_INVALID_DN_SYNTAX;
+ goto done;
+ }
+ ret = dsdb_find_dn_by_guid(s->samdb, tmp_ctx, &conn->dsa_guid, 0, &server_dn);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0, ("failed to find fromServer DN '%s'\n",
+ GUID_string(tmp_ctx, &conn->dsa_guid)));
+ goto done;
+ }
+ /*schedule_val = data_blob_const(r1->schedule, sizeof(r1->schedule));*/
+
+ msg = ldb_msg_new(tmp_ctx);
+ msg->dn = new_dn;
+ ldb_msg_add_string(msg, "objectClass", "nTDSConnection");
+ ldb_msg_add_string(msg, "showInAdvancedViewOnly", "TRUE");
+ ldb_msg_add_string(msg, "enabledConnection", "TRUE");
+ ldb_msg_add_linearized_dn(msg, "fromServer", server_dn);
+ /* ldb_msg_add_value(msg, "schedule", &schedule_val, NULL); */
+
+ samdb_msg_add_uint(s->samdb, msg, msg,
+ "options", NTDSCONN_OPT_IS_GENERATED);
+
+ ret = ldb_add(s->samdb, msg);
+ if (ret == LDB_SUCCESS) {
+ DEBUG(2, ("added nTDSConnection object '%s'\n",
+ ldb_dn_get_linearized(new_dn)));
+ } else {
+ DEBUG(0, ("failed to add an nTDSConnection object: %s\n",
+ ldb_strerror(ret)));
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static int kccsrv_delete_connection(struct kccsrv_service *s,
+ struct kcc_connection *conn)
+{
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_dn *dn;
+ int ret;
+
+ tmp_ctx = talloc_new(s);
+ ret = dsdb_find_dn_by_guid(s->samdb, tmp_ctx, &conn->obj_guid, 0, &dn);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0, ("failed to find nTDSConnection's DN: %s\n",
+ ldb_strerror(ret)));
+ goto done;
+ }
+
+ ret = ldb_delete(s->samdb, dn);
+ if (ret == LDB_SUCCESS) {
+ DEBUG(2, ("deleted nTDSConnection object '%s'\n",
+ ldb_dn_get_linearized(dn)));
+ } else {
+ DEBUG(0, ("failed to delete an nTDSConnection object: %s\n",
+ ldb_strerror(ret)));
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+void kccsrv_apply_connections(struct kccsrv_service *s,
+ struct kcc_connection_list *ntds_list,
+ struct kcc_connection_list *dsa_list)
+{
+ unsigned int i, j, deleted = 0, added = 0;
+ int ret;
+
+ /* XXX
+ *
+ * This routine is not respecting connections that the
+ * administrator can specifically create (NTDSCONN_OPT_IS_GENERATED
+ * bit will not be set)
+ */
+ for (i = 0; ntds_list && i < ntds_list->count; i++) {
+ struct kcc_connection *ntds = &ntds_list->servers[i];
+ for (j = 0; j < dsa_list->count; j++) {
+ struct kcc_connection *dsa = &dsa_list->servers[j];
+ if (GUID_equal(&ntds->dsa_guid, &dsa->dsa_guid)) {
+ break;
+ }
+ }
+ if (j == dsa_list->count) {
+ ret = kccsrv_delete_connection(s, ntds);
+ if (ret == LDB_SUCCESS) {
+ deleted++;
+ }
+ }
+ }
+ DEBUG(4, ("%d connections have been deleted\n", deleted));
+
+ for (i = 0; i < dsa_list->count; i++) {
+ struct kcc_connection *dsa = &dsa_list->servers[i];
+ for (j = 0; ntds_list && j < ntds_list->count; j++) {
+ struct kcc_connection *ntds = &ntds_list->servers[j];
+ if (GUID_equal(&dsa->dsa_guid, &ntds->dsa_guid)) {
+ break;
+ }
+ }
+ if (ntds_list == NULL || j == ntds_list->count) {
+ ret = kccsrv_add_connection(s, dsa);
+ if (ret == LDB_SUCCESS) {
+ added++;
+ }
+ }
+ }
+ DEBUG(4, ("%d connections have been added\n", added));
+}
+
+struct kcc_connection_list *kccsrv_find_connections(struct kccsrv_service *s,
+ TALLOC_CTX *mem_ctx)
+{
+ unsigned int i;
+ int ret;
+ struct ldb_dn *base_dn;
+ struct ldb_result *res;
+ const char *attrs[] = { "objectGUID", "fromServer", NULL };
+ struct kcc_connection_list *list;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) {
+ DEBUG(0, ("failed to talloc\n"));
+ return NULL;
+ }
+
+ base_dn = samdb_ntds_settings_dn(s->samdb, tmp_ctx);
+ if (!base_dn) {
+ DEBUG(0, ("failed to find our own NTDS settings DN\n"));
+ talloc_free(tmp_ctx);
+ return NULL;
+ }
+
+ ret = ldb_search(s->samdb, tmp_ctx, &res, base_dn, LDB_SCOPE_ONELEVEL,
+ attrs, "objectClass=nTDSConnection");
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0, ("failed nTDSConnection search: %s\n",
+ ldb_strerror(ret)));
+ talloc_free(tmp_ctx);
+ return NULL;
+ }
+
+ list = talloc(tmp_ctx, struct kcc_connection_list);
+ if (!list) {
+ DEBUG(0, ("out of memory\n"));
+ return NULL;
+ }
+ list->servers = talloc_array(list, struct kcc_connection,
+ res->count);
+ if (!list->servers) {
+ DEBUG(0, ("out of memory\n"));
+ talloc_free(tmp_ctx);
+ return NULL;
+ }
+ list->count = 0;
+
+ for (i = 0; i < res->count; i++) {
+ struct ldb_dn *server_dn;
+
+ list->servers[i].obj_guid = samdb_result_guid(res->msgs[i],
+ "objectGUID");
+ server_dn = samdb_result_dn(s->samdb, mem_ctx, res->msgs[i],
+ "fromServer", NULL);
+ ret = dsdb_find_guid_by_dn(s->samdb, server_dn,
+ &list->servers[i].dsa_guid);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0, ("Failed to find connection server's GUID by "
+ "DN=%s: %s\n",
+ ldb_dn_get_linearized(server_dn),
+ ldb_strerror(ret)));
+ continue;
+ }
+ list->count++;
+ }
+ DEBUG(4, ("found %d existing nTDSConnection objects\n", list->count));
+ talloc_steal(mem_ctx, list);
+ talloc_free(tmp_ctx);
+ return list;
+}
diff --git a/source4/dsdb/kcc/kcc_connection.h b/source4/dsdb/kcc/kcc_connection.h
new file mode 100644
index 0000000..b15e8ec
--- /dev/null
+++ b/source4/dsdb/kcc/kcc_connection.h
@@ -0,0 +1,39 @@
+/*
+ Unix SMB/CIFS Implementation.
+
+ KCC service
+
+ Copyright (C) Crístian Deives 2009
+ based on drepl service code
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#ifndef _DSDB_REPL_KCC_CONNECTION_H_
+#define _DSDB_REPL_KCC_CONNECTION_H_
+
+struct kcc_connection {
+ struct GUID obj_guid;
+ struct GUID dsa_guid;
+ struct GUID invocation_id;
+ uint8_t schedule[84];
+};
+
+struct kcc_connection_list {
+ unsigned count;
+ struct kcc_connection *servers;
+};
+
+#endif /* _DSDB_REPL_KCC_CONNECTION_H_ */
diff --git a/source4/dsdb/kcc/kcc_drs_replica_info.c b/source4/dsdb/kcc/kcc_drs_replica_info.c
new file mode 100644
index 0000000..5975926
--- /dev/null
+++ b/source4/dsdb/kcc/kcc_drs_replica_info.c
@@ -0,0 +1,911 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ DRS Replica Information
+
+ Copyright (C) Erick Nogueira do Nascimento 2009-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 <http://www.gnu.org/licenses/>.
+
+ */
+
+#include "includes.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/common/proto.h"
+#include "auth/auth.h"
+#include "samba/service.h"
+#include "lib/events/events.h"
+#include "lib/messaging/irpc.h"
+#include "dsdb/kcc/kcc_service.h"
+#include <ldb_errors.h>
+#include "../lib/util/dlinklist.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_drsuapi.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "param/param.h"
+#include "dsdb/common/util.h"
+
+
+/*
+ get the stamp values for the linked attribute 'linked_attr_name' of the object 'dn'
+*/
+static WERROR get_linked_attribute_value_stamp(TALLOC_CTX *mem_ctx, struct ldb_context *samdb,
+ struct ldb_dn *dn, const char *linked_attr_name,
+ uint32_t *attr_version, NTTIME *attr_change_time, uint32_t *attr_orig_usn)
+{
+ struct ldb_result *res;
+ int ret;
+ const char *attrs[2];
+ struct ldb_dn *attr_ext_dn;
+ NTSTATUS ntstatus;
+
+ attrs[0] = linked_attr_name;
+ attrs[1] = NULL;
+
+ ret = dsdb_search_dn(samdb, mem_ctx, &res, dn, attrs,
+ DSDB_SEARCH_SHOW_EXTENDED_DN | DSDB_SEARCH_REVEAL_INTERNALS);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0, (__location__ ": Failed search for attribute %s on %s\n",
+ linked_attr_name, ldb_dn_get_linearized(dn)));
+ return WERR_INTERNAL_ERROR;
+ }
+
+ attr_ext_dn = ldb_msg_find_attr_as_dn(samdb, mem_ctx, res->msgs[0], linked_attr_name);
+ if (!attr_ext_dn) {
+ DEBUG(0, (__location__ ": Failed search for attribute %s on %s\n",
+ linked_attr_name, ldb_dn_get_linearized(dn)));
+ return WERR_INTERNAL_ERROR;
+ }
+
+ DEBUG(0, ("linked_attr_name = %s, attr_ext_dn = %s", linked_attr_name,
+ ldb_dn_get_extended_linearized(mem_ctx, attr_ext_dn, 1)));
+
+ ntstatus = dsdb_get_extended_dn_uint32(attr_ext_dn, attr_version, "RMD_VERSION");
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ DEBUG(0, (__location__ ": Could not extract component %s from dn \"%s\"\n",
+ "RMD_VERSION", ldb_dn_get_extended_linearized(mem_ctx, attr_ext_dn, 1)));
+ return WERR_INTERNAL_ERROR;
+ }
+
+ ntstatus = dsdb_get_extended_dn_nttime(attr_ext_dn, attr_change_time, "RMD_CHANGETIME");
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ DEBUG(0, (__location__ ": Could not extract component %s from dn \"%s\"\n",
+ "RMD_CHANGETIME", ldb_dn_get_extended_linearized(mem_ctx, attr_ext_dn, 1)));
+ return WERR_INTERNAL_ERROR;
+ }
+
+ ntstatus = dsdb_get_extended_dn_uint32(attr_ext_dn, attr_version, "RMD_ORIGINATING_USN");
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ DEBUG(0, (__location__ ": Could not extract component %s from dn \"%s\"\n",
+ "RMD_ORIGINATING_USN", ldb_dn_get_extended_linearized(mem_ctx, attr_ext_dn, 1)));
+ return WERR_INTERNAL_ERROR;
+ }
+
+ return WERR_OK;
+}
+
+static WERROR get_repl_prop_metadata_ctr(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ struct ldb_dn *dn,
+ struct replPropertyMetaDataBlob *obj_metadata_ctr)
+{
+ int ret;
+ struct ldb_result *res;
+ const char *attrs[] = { "replPropertyMetaData", NULL };
+ const struct ldb_val *omd_value;
+ enum ndr_err_code ndr_err;
+
+ ret = ldb_search(samdb, mem_ctx, &res, dn, LDB_SCOPE_BASE, attrs, NULL);
+ if (ret != LDB_SUCCESS || res->count != 1) {
+ DEBUG(0, (__location__ ": Failed search for replPropertyMetaData attribute on %s\n",
+ ldb_dn_get_linearized(dn)));
+ return WERR_INTERNAL_ERROR;
+ }
+
+ omd_value = ldb_msg_find_ldb_val(res->msgs[0], "replPropertyMetaData");
+ if (!omd_value) {
+ DEBUG(0,(__location__ ": Object %s does not have a replPropertyMetaData attribute\n",
+ ldb_dn_get_linearized(dn)));
+ talloc_free(res);
+ return WERR_INTERNAL_ERROR;
+ }
+
+ ndr_err = ndr_pull_struct_blob(omd_value, mem_ctx,
+ obj_metadata_ctr,
+ (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s\n",
+ ldb_dn_get_linearized(dn)));
+ talloc_free(res);
+ return WERR_INTERNAL_ERROR;
+ }
+
+ talloc_free(res);
+ return WERR_OK;
+}
+
+/*
+ get the DN of the nTDSDSA object from the configuration partition
+ whose invocationId is 'invocation_id'
+ put the value on 'dn_str'
+*/
+static WERROR get_dn_from_invocation_id(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ struct GUID *invocation_id,
+ const char **dn_str)
+{
+ char *invocation_id_str;
+ const char *attrs_invocation[] = { NULL };
+ struct ldb_message *msg;
+ int ret;
+
+ invocation_id_str = GUID_string(mem_ctx, invocation_id);
+ W_ERROR_HAVE_NO_MEMORY(invocation_id_str);
+
+ ret = dsdb_search_one(samdb, invocation_id_str, &msg, ldb_get_config_basedn(samdb), LDB_SCOPE_SUBTREE,
+ attrs_invocation, 0, "(&(objectClass=nTDSDSA)(invocationId=%s))", invocation_id_str);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0, (__location__ ": Failed search for the object DN under %s whose invocationId is %s\n",
+ invocation_id_str, ldb_dn_get_linearized(ldb_get_config_basedn(samdb))));
+ talloc_free(invocation_id_str);
+ return WERR_INTERNAL_ERROR;
+ }
+
+ *dn_str = ldb_dn_alloc_linearized(mem_ctx, msg->dn);
+ talloc_free(invocation_id_str);
+ return WERR_OK;
+}
+
+/*
+ get metadata version 2 info for a specified object DN
+*/
+static WERROR kccdrs_replica_get_info_obj_metadata2(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ struct drsuapi_DsReplicaGetInfo *r,
+ union drsuapi_DsReplicaInfo *reply,
+ struct ldb_dn *dn,
+ uint32_t base_index)
+{
+ WERROR status;
+ struct replPropertyMetaDataBlob omd_ctr;
+ struct replPropertyMetaData1 *attr;
+ struct drsuapi_DsReplicaObjMetaData2Ctr *metadata2;
+ const struct dsdb_schema *schema;
+
+ uint32_t i, j;
+
+ DEBUG(0, ("kccdrs_replica_get_info_obj_metadata2() called\n"));
+
+ if (!dn) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (!ldb_dn_validate(dn)) {
+ return WERR_DS_DRA_BAD_DN;
+ }
+
+ status = get_repl_prop_metadata_ctr(mem_ctx, samdb, dn, &omd_ctr);
+ W_ERROR_NOT_OK_RETURN(status);
+
+ schema = dsdb_get_schema(samdb, reply);
+ if (!schema) {
+ DEBUG(0,(__location__": Failed to get the schema\n"));
+ return WERR_INTERNAL_ERROR;
+ }
+
+ reply->objmetadata2 = talloc_zero(mem_ctx, struct drsuapi_DsReplicaObjMetaData2Ctr);
+ W_ERROR_HAVE_NO_MEMORY(reply->objmetadata2);
+ metadata2 = reply->objmetadata2;
+ metadata2->enumeration_context = 0;
+
+ /* For each replicated attribute of the object */
+ for (i = 0, j = 0; i < omd_ctr.ctr.ctr1.count; i++) {
+ const struct dsdb_attribute *schema_attr;
+ uint32_t attr_version;
+ NTTIME attr_change_time;
+ uint32_t attr_originating_usn = 0;
+
+ /*
+ attr := attrsSeq[i]
+ s := AttrStamp(object, attr)
+ */
+ /* get a reference to the attribute on 'omd_ctr' */
+ attr = &omd_ctr.ctr.ctr1.array[j];
+
+ schema_attr = dsdb_attribute_by_attributeID_id(schema, attr->attid);
+
+ DEBUG(0, ("attribute_id = %d, attribute_name: %s\n", attr->attid, schema_attr->lDAPDisplayName));
+
+ /*
+ if (attr in Link Attributes of object and
+ dwInVersion = 2 and DS_REPL_INFO_FLAG_IMPROVE_LINKED_ATTRS in msgIn.ulFlags)
+ */
+ if (schema_attr &&
+ schema_attr->linkID != 0 && /* Checks if attribute is a linked attribute */
+ (schema_attr->linkID % 2) == 0 && /* is it a forward link? only forward links have the LinkValueStamp */
+ r->in.level == 2 &&
+ (r->in.req->req2.flags & DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE)) /* on MS-DRSR it is DS_REPL_INFO_FLAG_IMPROVE_LINKED_ATTRS */
+ {
+ /*
+ ls := LinkValueStamp of the most recent
+ value change in object!attr
+ */
+ status = get_linked_attribute_value_stamp(mem_ctx, samdb, dn, schema_attr->lDAPDisplayName,
+ &attr_version, &attr_change_time, &attr_originating_usn);
+ W_ERROR_NOT_OK_RETURN(status);
+
+ /*
+ Aligning to MS-DRSR 4.1.13.3:
+ 's' on the doc is 'attr->originating_change_time' here
+ 'ls' on the doc is 'attr_change_time' here
+ */
+
+ /* if (ls is more recent than s (based on order in which the change was applied on server)) then */
+ if (attr_change_time > attr->originating_change_time) {
+ /*
+ Improve the stamp with the link value stamp.
+ s.dwVersion := ls.dwVersion
+ s.timeChanged := ls.timeChanged
+ s.uuidOriginating := NULLGUID
+ s.usnOriginating := ls.usnOriginating
+ */
+ attr->version = attr_version;
+ attr->originating_change_time = attr_change_time;
+ attr->originating_invocation_id = GUID_zero();
+ attr->originating_usn = attr_originating_usn;
+ }
+ }
+
+ if (i < base_index) {
+ continue;
+ }
+
+ metadata2->array = talloc_realloc(mem_ctx, metadata2->array,
+ struct drsuapi_DsReplicaObjMetaData2, j + 1);
+ W_ERROR_HAVE_NO_MEMORY(metadata2->array);
+ metadata2->array[j].attribute_name = schema_attr->lDAPDisplayName;
+ metadata2->array[j].local_usn = attr->local_usn;
+ metadata2->array[j].originating_change_time = attr->originating_change_time;
+ metadata2->array[j].originating_invocation_id = attr->originating_invocation_id;
+ metadata2->array[j].originating_usn = attr->originating_usn;
+ metadata2->array[j].version = attr->version;
+
+ /*
+ originating_dsa_dn := GetDNFromInvocationID(originating_invocation_id)
+ GetDNFromInvocationID() should return the DN of the nTDSDSAobject that has the specified invocation ID
+ See MS-DRSR 4.1.13.3 and 4.1.13.2.1
+ */
+ status = get_dn_from_invocation_id(mem_ctx, samdb,
+ &attr->originating_invocation_id,
+ &metadata2->array[j].originating_dsa_dn);
+ W_ERROR_NOT_OK_RETURN(status);
+ j++;
+ metadata2->count = j;
+
+ }
+
+ return WERR_OK;
+}
+
+/*
+ get cursors info for a specified DN
+*/
+static WERROR kccdrs_replica_get_info_cursors(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ struct drsuapi_DsReplicaGetInfo *r,
+ union drsuapi_DsReplicaInfo *reply,
+ struct ldb_dn *dn)
+{
+ int ret;
+
+ if (!ldb_dn_validate(dn)) {
+ return WERR_INVALID_PARAMETER;
+ }
+ reply->cursors = talloc(mem_ctx, struct drsuapi_DsReplicaCursorCtr);
+ W_ERROR_HAVE_NO_MEMORY(reply->cursors);
+
+ reply->cursors->reserved = 0;
+
+ ret = dsdb_load_udv_v1(samdb, dn, reply->cursors, &reply->cursors->array, &reply->cursors->count);
+ if (ret != LDB_SUCCESS) {
+ return WERR_DS_DRA_BAD_NC;
+ }
+ return WERR_OK;
+}
+
+/*
+ get cursors2 info for a specified DN
+*/
+static WERROR kccdrs_replica_get_info_cursors2(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ struct drsuapi_DsReplicaGetInfo *r,
+ union drsuapi_DsReplicaInfo *reply,
+ struct ldb_dn *dn)
+{
+ int ret;
+
+ if (!ldb_dn_validate(dn)) {
+ return WERR_INVALID_PARAMETER;
+ }
+ reply->cursors2 = talloc(mem_ctx, struct drsuapi_DsReplicaCursor2Ctr);
+ W_ERROR_HAVE_NO_MEMORY(reply->cursors2);
+
+ ret = dsdb_load_udv_v2(samdb, dn, reply->cursors2, &reply->cursors2->array, &reply->cursors2->count);
+ if (ret != LDB_SUCCESS) {
+ return WERR_DS_DRA_BAD_NC;
+ }
+
+ reply->cursors2->enumeration_context = reply->cursors2->count;
+ return WERR_OK;
+}
+
+/*
+ get pending ops info for a specified DN
+*/
+static WERROR kccdrs_replica_get_info_pending_ops(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ struct drsuapi_DsReplicaGetInfo *r,
+ union drsuapi_DsReplicaInfo *reply,
+ struct ldb_dn *dn)
+{
+ struct timeval now = timeval_current();
+
+ if (!ldb_dn_validate(dn)) {
+ return WERR_INVALID_PARAMETER;
+ }
+ reply->pendingops = talloc(mem_ctx, struct drsuapi_DsReplicaOpCtr);
+ W_ERROR_HAVE_NO_MEMORY(reply->pendingops);
+
+ /* claim no pending ops for now */
+ reply->pendingops->time = timeval_to_nttime(&now);
+ reply->pendingops->count = 0;
+ reply->pendingops->array = NULL;
+
+ return WERR_OK;
+}
+
+struct ncList {
+ struct ldb_dn *dn;
+ struct ncList *prev, *next;
+};
+
+/*
+ Fill 'master_nc_list' with the master ncs hosted by this server
+*/
+static WERROR get_master_ncs(TALLOC_CTX *mem_ctx, struct ldb_context *samdb,
+ const char *ntds_guid_str, struct ncList **master_nc_list)
+{
+ const char *post_2003_attrs[] = { "msDS-hasMasterNCs", "hasPartialReplicaNCs", NULL };
+ const char *pre_2003_attrs[] = { "hasMasterNCs", "hasPartialReplicaNCs", NULL };
+ const char **attrs = post_2003_attrs;
+ struct ldb_result *res;
+ struct ncList *nc_list = NULL;
+ struct ncList *nc_list_elem;
+ int ret;
+ unsigned int i;
+ char *nc_str;
+
+ /* In W2003 and greater, msDS-hasMasterNCs attribute lists the writable NC replicas */
+ ret = ldb_search(samdb, mem_ctx, &res, ldb_get_config_basedn(samdb),
+ LDB_SCOPE_DEFAULT, post_2003_attrs, "(objectguid=%s)", ntds_guid_str);
+
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0,(__location__ ": Failed objectguid search - %s\n", ldb_errstring(samdb)));
+
+ attrs = post_2003_attrs;
+ ret = ldb_search(samdb, mem_ctx, &res, ldb_get_config_basedn(samdb),
+ LDB_SCOPE_DEFAULT, pre_2003_attrs, "(objectguid=%s)", ntds_guid_str);
+ }
+
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0,(__location__ ": Failed objectguid search - %s\n", ldb_errstring(samdb)));
+ return WERR_INTERNAL_ERROR;
+ }
+
+ if (res->count == 0) {
+ DEBUG(0,(__location__ ": Failed: objectguid=%s not found\n", ntds_guid_str));
+ return WERR_INTERNAL_ERROR;
+ }
+
+ for (i = 0; i < res->count; i++) {
+ struct ldb_message_element *msg_elem;
+ unsigned int k, a;
+
+ for (a=0; attrs[a]; a++) {
+ msg_elem = ldb_msg_find_element(res->msgs[i], attrs[a]);
+ if (!msg_elem || msg_elem->num_values == 0) {
+ continue;
+ }
+
+ for (k = 0; k < msg_elem->num_values; k++) {
+ /* copy the string on msg_elem->values[k]->data to nc_str */
+ nc_str = talloc_strndup(mem_ctx, (char *)msg_elem->values[k].data, msg_elem->values[k].length);
+ W_ERROR_HAVE_NO_MEMORY(nc_str);
+
+ nc_list_elem = talloc_zero(mem_ctx, struct ncList);
+ W_ERROR_HAVE_NO_MEMORY(nc_list_elem);
+ nc_list_elem->dn = ldb_dn_new(mem_ctx, samdb, nc_str);
+ W_ERROR_HAVE_NO_MEMORY(nc_list_elem);
+ DLIST_ADD(nc_list, nc_list_elem);
+ }
+ }
+ }
+
+ *master_nc_list = nc_list;
+ return WERR_OK;
+}
+
+/*
+ Fill 'nc_list' with the ncs list. (MS-DRSR 4.1.13.3)
+ if the object dn is specified, fill 'nc_list' only with this dn
+ otherwise, fill 'nc_list' with all master ncs hosted by this server
+*/
+static WERROR get_ncs_list(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ struct kccsrv_service *service,
+ const char *object_dn_str,
+ struct ncList **nc_list)
+{
+ WERROR status;
+ struct ncList *nc_list_elem;
+ struct ldb_dn *nc_dn;
+
+ if (object_dn_str != NULL) {
+ /* ncs := { object_dn } */
+ *nc_list = NULL;
+ nc_dn = ldb_dn_new(mem_ctx, samdb, object_dn_str);
+ nc_list_elem = talloc_zero(mem_ctx, struct ncList);
+ W_ERROR_HAVE_NO_MEMORY(nc_list_elem);
+ nc_list_elem->dn = nc_dn;
+ DLIST_ADD_END(*nc_list, nc_list_elem);
+ } else {
+ /* ncs := getNCs() from ldb database.
+ * getNCs() must return an array containing
+ * the DSNames of all NCs hosted by this
+ * server.
+ */
+ char *ntds_guid_str = GUID_string(mem_ctx, &service->ntds_guid);
+ W_ERROR_HAVE_NO_MEMORY(ntds_guid_str);
+ status = get_master_ncs(mem_ctx, samdb, ntds_guid_str, nc_list);
+ W_ERROR_NOT_OK_RETURN(status);
+ }
+
+ return WERR_OK;
+}
+
+/*
+ Copy the fields from 'reps1' to 'reps2', leaving zeroed the fields on
+ 'reps2' that aren't available on 'reps1'.
+*/
+static WERROR copy_repsfrom_1_to_2(TALLOC_CTX *mem_ctx,
+ struct repsFromTo2 **reps2,
+ struct repsFromTo1 *reps1)
+{
+ struct repsFromTo2* reps;
+
+ reps = talloc_zero(mem_ctx, struct repsFromTo2);
+ W_ERROR_HAVE_NO_MEMORY(reps);
+
+ reps->blobsize = reps1->blobsize;
+ reps->consecutive_sync_failures = reps1->consecutive_sync_failures;
+ reps->last_attempt = reps1->last_attempt;
+ reps->last_success = reps1->last_success;
+ reps->result_last_attempt = reps1->result_last_attempt;
+ reps->other_info = talloc_zero(mem_ctx, struct repsFromTo2OtherInfo);
+ W_ERROR_HAVE_NO_MEMORY(reps->other_info);
+ reps->other_info->dns_name1 = reps1->other_info->dns_name;
+ reps->replica_flags = reps1->replica_flags;
+ memcpy(reps->schedule, reps1->schedule, sizeof(reps1->schedule));
+ reps->reserved = reps1->reserved;
+ reps->highwatermark = reps1->highwatermark;
+ reps->source_dsa_obj_guid = reps1->source_dsa_obj_guid;
+ reps->source_dsa_invocation_id = reps1->source_dsa_invocation_id;
+ reps->transport_guid = reps1->transport_guid;
+
+ *reps2 = reps;
+ return WERR_OK;
+}
+
+static WERROR fill_neighbor_from_repsFrom(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ struct ldb_dn *nc_dn,
+ struct drsuapi_DsReplicaNeighbour *neigh,
+ struct repsFromTo2 *reps_from)
+{
+ struct ldb_dn *source_dsa_dn;
+ int ret;
+ struct ldb_dn *transport_obj_dn = NULL;
+
+ neigh->source_dsa_address = reps_from->other_info->dns_name1;
+ neigh->replica_flags = reps_from->replica_flags;
+ neigh->last_attempt = reps_from->last_attempt;
+ neigh->source_dsa_obj_guid = reps_from->source_dsa_obj_guid;
+
+ ret = dsdb_find_dn_by_guid(samdb, mem_ctx, &reps_from->source_dsa_obj_guid,
+ DSDB_SEARCH_SHOW_RECYCLED,
+ &source_dsa_dn);
+
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0,(__location__ ": Failed to find DN for neighbor GUID %s\n",
+ GUID_string(mem_ctx, &reps_from->source_dsa_obj_guid)));
+ return WERR_DS_DRA_INTERNAL_ERROR;
+ }
+
+ neigh->source_dsa_obj_dn = ldb_dn_get_linearized(source_dsa_dn);
+ neigh->naming_context_dn = ldb_dn_get_linearized(nc_dn);
+
+ if (dsdb_find_guid_by_dn(samdb, nc_dn,
+ &neigh->naming_context_obj_guid)
+ != LDB_SUCCESS) {
+ return WERR_DS_DRA_INTERNAL_ERROR;
+ }
+
+ if (!GUID_all_zero(&reps_from->transport_guid)) {
+ ret = dsdb_find_dn_by_guid(samdb, mem_ctx, &reps_from->transport_guid,
+ DSDB_SEARCH_SHOW_RECYCLED,
+ &transport_obj_dn);
+ if (ret != LDB_SUCCESS) {
+ return WERR_DS_DRA_INTERNAL_ERROR;
+ }
+ }
+
+ neigh->transport_obj_dn = ldb_dn_get_linearized(transport_obj_dn);
+ neigh->source_dsa_invocation_id = reps_from->source_dsa_invocation_id;
+ neigh->transport_obj_guid = reps_from->transport_guid;
+ neigh->highest_usn = reps_from->highwatermark.highest_usn;
+ neigh->tmp_highest_usn = reps_from->highwatermark.tmp_highest_usn;
+ neigh->last_success = reps_from->last_success;
+ neigh->result_last_attempt = reps_from->result_last_attempt;
+ neigh->consecutive_sync_failures = reps_from->consecutive_sync_failures;
+ neigh->reserved = 0; /* Unused. MUST be 0. */
+
+ return WERR_OK;
+}
+
+/*
+ Get the inbound neighbours of this DC
+ See details on MS-DRSR 4.1.13.3, for infoType DS_REPL_INFO_NEIGHBORS
+*/
+static WERROR kccdrs_replica_get_info_neighbours(TALLOC_CTX *mem_ctx,
+ struct kccsrv_service *service,
+ struct ldb_context *samdb,
+ struct drsuapi_DsReplicaGetInfo *r,
+ union drsuapi_DsReplicaInfo *reply,
+ uint32_t base_index,
+ struct GUID req_src_dsa_guid,
+ const char *object_dn_str)
+{
+ WERROR status;
+ uint32_t i, j;
+ struct ldb_dn *nc_dn = NULL;
+ struct ncList *p_nc_list = NULL;
+ struct repsFromToBlob *reps_from_blob = NULL;
+ struct repsFromTo2 *reps_from = NULL;
+ uint32_t c_reps_from;
+ uint32_t i_rep;
+ struct ncList *nc_list = NULL;
+
+ status = get_ncs_list(mem_ctx, samdb, service, object_dn_str, &nc_list);
+ W_ERROR_NOT_OK_RETURN(status);
+
+ i = j = 0;
+
+ reply->neighbours = talloc_zero(mem_ctx, struct drsuapi_DsReplicaNeighbourCtr);
+ W_ERROR_HAVE_NO_MEMORY(reply->neighbours);
+ reply->neighbours->reserved = 0;
+ reply->neighbours->count = 0;
+
+ /* foreach nc in ncs */
+ for (p_nc_list = nc_list; p_nc_list != NULL; p_nc_list = p_nc_list->next) {
+
+ nc_dn = p_nc_list->dn;
+
+ /* load the nc's repsFromTo blob */
+ status = dsdb_loadreps(samdb, mem_ctx, nc_dn, "repsFrom",
+ &reps_from_blob, &c_reps_from);
+ W_ERROR_NOT_OK_RETURN(status);
+
+ /* foreach r in nc!repsFrom */
+ for (i_rep = 0; i_rep < c_reps_from; i_rep++) {
+
+ /* put all info on reps_from */
+ if (reps_from_blob[i_rep].version == 1) {
+ status = copy_repsfrom_1_to_2(mem_ctx, &reps_from,
+ &reps_from_blob[i_rep].ctr.ctr1);
+ W_ERROR_NOT_OK_RETURN(status);
+ } else { /* reps_from->version == 2 */
+ reps_from = &reps_from_blob[i_rep].ctr.ctr2;
+ }
+
+ if (GUID_all_zero(&req_src_dsa_guid) ||
+ GUID_equal(&req_src_dsa_guid,
+ &reps_from->source_dsa_obj_guid)) {
+
+ if (i >= base_index) {
+ struct drsuapi_DsReplicaNeighbour neigh;
+ ZERO_STRUCT(neigh);
+ status = fill_neighbor_from_repsFrom(mem_ctx, samdb,
+ nc_dn, &neigh,
+ reps_from);
+ W_ERROR_NOT_OK_RETURN(status);
+
+ /* append the neighbour to the neighbours array */
+ reply->neighbours->array = talloc_realloc(mem_ctx,
+ reply->neighbours->array,
+ struct drsuapi_DsReplicaNeighbour,
+ reply->neighbours->count + 1);
+ reply->neighbours->array[reply->neighbours->count] = neigh;
+ reply->neighbours->count++;
+ j++;
+ }
+
+ i++;
+ }
+ }
+ }
+
+ return WERR_OK;
+}
+
+static WERROR fill_neighbor_from_repsTo(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb, struct ldb_dn *nc_dn,
+ struct drsuapi_DsReplicaNeighbour *neigh,
+ struct repsFromTo2 *reps_to)
+{
+ int ret;
+ struct ldb_dn *source_dsa_dn;
+
+ neigh->source_dsa_address = reps_to->other_info->dns_name1;
+ neigh->replica_flags = reps_to->replica_flags;
+ neigh->last_attempt = reps_to->last_attempt;
+ neigh->source_dsa_obj_guid = reps_to->source_dsa_obj_guid;
+
+ ret = dsdb_find_dn_by_guid(samdb, mem_ctx,
+ &reps_to->source_dsa_obj_guid,
+ DSDB_SEARCH_SHOW_RECYCLED,
+ &source_dsa_dn);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0,(__location__ ": Failed to find DN for neighbor GUID %s\n",
+ GUID_string(mem_ctx, &reps_to->source_dsa_obj_guid)));
+ return WERR_DS_DRA_INTERNAL_ERROR;
+ }
+
+ neigh->source_dsa_obj_dn = ldb_dn_get_linearized(source_dsa_dn);
+ neigh->naming_context_dn = ldb_dn_get_linearized(nc_dn);
+
+ ret = dsdb_find_guid_by_dn(samdb, nc_dn,
+ &neigh->naming_context_obj_guid);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0,(__location__ ": Failed to find GUID for DN %s\n",
+ ldb_dn_get_linearized(nc_dn)));
+ return WERR_DS_DRA_INTERNAL_ERROR;
+ }
+
+ neigh->last_success = reps_to->last_success;
+ neigh->result_last_attempt = reps_to->result_last_attempt;
+ neigh->consecutive_sync_failures = reps_to->consecutive_sync_failures;
+ return WERR_OK;
+}
+
+/*
+ Get the outbound neighbours of this DC
+ See details on MS-DRSR 4.1.13.3, for infoType DS_REPL_INFO_REPSTO
+*/
+static WERROR kccdrs_replica_get_info_repsto(TALLOC_CTX *mem_ctx,
+ struct kccsrv_service *service,
+ struct ldb_context *samdb,
+ struct drsuapi_DsReplicaGetInfo *r,
+ union drsuapi_DsReplicaInfo *reply,
+ uint32_t base_index,
+ struct GUID req_src_dsa_guid,
+ const char *object_dn_str)
+{
+ WERROR status;
+ uint32_t i, j;
+ struct ncList *p_nc_list = NULL;
+ struct ldb_dn *nc_dn = NULL;
+ struct repsFromToBlob *reps_to_blob;
+ struct repsFromTo2 *reps_to;
+ uint32_t c_reps_to;
+ uint32_t i_rep;
+ struct ncList *nc_list = NULL;
+
+ status = get_ncs_list(mem_ctx, samdb, service, object_dn_str, &nc_list);
+ W_ERROR_NOT_OK_RETURN(status);
+
+ i = j = 0;
+
+ reply->repsto = talloc_zero(mem_ctx, struct drsuapi_DsReplicaNeighbourCtr);
+ W_ERROR_HAVE_NO_MEMORY(reply->repsto);
+ reply->repsto->reserved = 0;
+ reply->repsto->count = 0;
+
+ /* foreach nc in ncs */
+ for (p_nc_list = nc_list; p_nc_list != NULL; p_nc_list = p_nc_list->next) {
+
+ nc_dn = p_nc_list->dn;
+
+ status = dsdb_loadreps(samdb, mem_ctx, nc_dn, "repsTo",
+ &reps_to_blob, &c_reps_to);
+ W_ERROR_NOT_OK_RETURN(status);
+
+ /* foreach r in nc!repsTo */
+ for (i_rep = 0; i_rep < c_reps_to; i_rep++) {
+ struct drsuapi_DsReplicaNeighbour neigh;
+ ZERO_STRUCT(neigh);
+
+ /* put all info on reps_to */
+ if (reps_to_blob[i_rep].version == 1) {
+ status = copy_repsfrom_1_to_2(mem_ctx,
+ &reps_to,
+ &reps_to_blob[i_rep].ctr.ctr1);
+ W_ERROR_NOT_OK_RETURN(status);
+ } else { /* reps_to->version == 2 */
+ reps_to = &reps_to_blob[i_rep].ctr.ctr2;
+ }
+
+ if (i >= base_index) {
+ status = fill_neighbor_from_repsTo(mem_ctx,
+ samdb, nc_dn,
+ &neigh, reps_to);
+ W_ERROR_NOT_OK_RETURN(status);
+
+ /* append the neighbour to the neighbours array */
+ reply->repsto->array = talloc_realloc(mem_ctx,
+ reply->repsto->array,
+ struct drsuapi_DsReplicaNeighbour,
+ reply->repsto->count + 1);
+ reply->repsto->array[reply->repsto->count++] = neigh;
+ j++;
+ }
+ i++;
+ }
+ }
+
+ return WERR_OK;
+}
+
+NTSTATUS kccdrs_replica_get_info(struct irpc_message *msg,
+ struct drsuapi_DsReplicaGetInfo *req)
+{
+ WERROR status;
+ struct drsuapi_DsReplicaGetInfoRequest1 *req1;
+ struct drsuapi_DsReplicaGetInfoRequest2 *req2;
+ uint32_t base_index;
+ union drsuapi_DsReplicaInfo *reply;
+ struct GUID req_src_dsa_guid;
+ const char *object_dn_str = NULL;
+ struct kccsrv_service *service;
+ struct ldb_context *samdb;
+ TALLOC_CTX *mem_ctx;
+ enum drsuapi_DsReplicaInfoType info_type;
+
+ service = talloc_get_type(msg->private_data, struct kccsrv_service);
+ samdb = service->samdb;
+ mem_ctx = talloc_new(msg);
+ NT_STATUS_HAVE_NO_MEMORY(mem_ctx);
+
+#if 0
+ NDR_PRINT_IN_DEBUG(drsuapi_DsReplicaGetInfo, req);
+#endif
+
+ /* check request version */
+ if (req->in.level != DRSUAPI_DS_REPLICA_GET_INFO &&
+ req->in.level != DRSUAPI_DS_REPLICA_GET_INFO2)
+ {
+ DEBUG(1,(__location__ ": Unsupported DsReplicaGetInfo level %u\n",
+ req->in.level));
+ status = WERR_REVISION_MISMATCH;
+ goto done;
+ }
+
+ if (req->in.level == DRSUAPI_DS_REPLICA_GET_INFO) {
+ req1 = &req->in.req->req1;
+ base_index = 0;
+ info_type = req1->info_type;
+ object_dn_str = req1->object_dn;
+ req_src_dsa_guid = req1->source_dsa_guid;
+ } else { /* r->in.level == DRSUAPI_DS_REPLICA_GET_INFO2 */
+ req2 = &req->in.req->req2;
+ if (req2->enumeration_context == 0xffffffff) {
+ /* no more data is available */
+ status = WERR_NO_MORE_ITEMS; /* on MS-DRSR it is ERROR_NO_MORE_ITEMS */
+ goto done;
+ }
+
+ base_index = req2->enumeration_context;
+ info_type = req2->info_type;
+ object_dn_str = req2->object_dn;
+ req_src_dsa_guid = req2->source_dsa_guid;
+ }
+
+ reply = req->out.info;
+ *req->out.info_type = info_type;
+
+ /* Based on the infoType requested, retrieve the corresponding
+ * information and construct the response message */
+ switch (info_type) {
+
+ case DRSUAPI_DS_REPLICA_INFO_NEIGHBORS:
+ status = kccdrs_replica_get_info_neighbours(mem_ctx, service, samdb, req,
+ reply, base_index, req_src_dsa_guid,
+ object_dn_str);
+ break;
+ case DRSUAPI_DS_REPLICA_INFO_REPSTO:
+ status = kccdrs_replica_get_info_repsto(mem_ctx, service, samdb, req,
+ reply, base_index, req_src_dsa_guid,
+ object_dn_str);
+ break;
+ case DRSUAPI_DS_REPLICA_INFO_CURSORS: /* On MS-DRSR it is DS_REPL_INFO_CURSORS_FOR_NC */
+ status = kccdrs_replica_get_info_cursors(mem_ctx, samdb, req, reply,
+ ldb_dn_new(mem_ctx, samdb, object_dn_str));
+ break;
+ case DRSUAPI_DS_REPLICA_INFO_CURSORS2: /* On MS-DRSR it is DS_REPL_INFO_CURSORS_2_FOR_NC */
+ status = kccdrs_replica_get_info_cursors2(mem_ctx, samdb, req, reply,
+ ldb_dn_new(mem_ctx, samdb, object_dn_str));
+ break;
+ case DRSUAPI_DS_REPLICA_INFO_PENDING_OPS:
+ status = kccdrs_replica_get_info_pending_ops(mem_ctx, samdb, req, reply,
+ ldb_dn_new(mem_ctx, samdb, object_dn_str));
+ break;
+ case DRSUAPI_DS_REPLICA_INFO_CURSORS3: /* On MS-DRSR it is DS_REPL_INFO_CURSORS_3_FOR_NC */
+ status = WERR_NOT_SUPPORTED;
+ break;
+ case DRSUAPI_DS_REPLICA_INFO_UPTODATE_VECTOR_V1: /* On MS-DRSR it is DS_REPL_INFO_UPTODATE_VECTOR_V1 */
+ status = WERR_NOT_SUPPORTED;
+ break;
+ case DRSUAPI_DS_REPLICA_INFO_OBJ_METADATA: /* On MS-DRSR it is DS_REPL_INFO_METADATA_FOR_OBJ */
+ /*
+ * It should be too complicated to filter the metadata2 to remove the additional data
+ * as metadata2 is a superset of metadata
+ */
+ status = WERR_NOT_SUPPORTED;
+ break;
+ case DRSUAPI_DS_REPLICA_INFO_OBJ_METADATA2: /* On MS-DRSR it is DS_REPL_INFO_METADATA_FOR_OBJ */
+ status = kccdrs_replica_get_info_obj_metadata2(mem_ctx, samdb, req, reply,
+ ldb_dn_new(mem_ctx, samdb, object_dn_str), base_index);
+ break;
+ case DRSUAPI_DS_REPLICA_INFO_ATTRIBUTE_VALUE_METADATA: /* On MS-DRSR it is DS_REPL_INFO_METADATA_FOR_ATTR_VALUE */
+ status = WERR_NOT_SUPPORTED;
+ break;
+ case DRSUAPI_DS_REPLICA_INFO_ATTRIBUTE_VALUE_METADATA2: /* On MS-DRSR it is DS_REPL_INFO_METADATA_2_FOR_ATTR_VALUE */
+ status = WERR_NOT_SUPPORTED;
+ break;
+ case DRSUAPI_DS_REPLICA_INFO_KCC_DSA_CONNECT_FAILURES: /* On MS-DRSR it is DS_REPL_INFO_KCC_DSA_CONNECT_FAILURES */
+ status = WERR_NOT_SUPPORTED;
+ break;
+ case DRSUAPI_DS_REPLICA_INFO_KCC_DSA_LINK_FAILURES: /* On MS-DRSR it is DS_REPL_INFO_KCC_LINK_FAILURES */
+ status = WERR_NOT_SUPPORTED;
+ break;
+ case DRSUAPI_DS_REPLICA_INFO_CLIENT_CONTEXTS: /* On MS-DRSR it is DS_REPL_INFO_CLIENT_CONTEXTS */
+ status = WERR_NOT_SUPPORTED;
+ break;
+ case DRSUAPI_DS_REPLICA_INFO_SERVER_OUTGOING_CALLS: /* On MS-DRSR it is DS_REPL_INFO_SERVER_OUTGOING_CALLS */
+ status = WERR_NOT_SUPPORTED;
+ break;
+ default:
+ DEBUG(1,(__location__ ": Unsupported DsReplicaGetInfo info_type %u\n",
+ info_type));
+ status = WERR_INVALID_LEVEL;
+ break;
+ }
+
+done:
+ /* put the status on the result field of the reply */
+ req->out.result = status;
+#if 0
+ NDR_PRINT_OUT_DEBUG(drsuapi_DsReplicaGetInfo, req);
+#endif
+ return NT_STATUS_OK;
+}
diff --git a/source4/dsdb/kcc/kcc_periodic.c b/source4/dsdb/kcc/kcc_periodic.c
new file mode 100644
index 0000000..7f0a532
--- /dev/null
+++ b/source4/dsdb/kcc/kcc_periodic.c
@@ -0,0 +1,825 @@
+/*
+ Unix SMB/CIFS Implementation.
+ KCC service periodic handling
+
+ Copyright (C) Andrew Tridgell 2009
+ based on repl service code
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "lib/events/events.h"
+#include "dsdb/samdb/samdb.h"
+#include "auth/auth.h"
+#include "samba/service.h"
+#include "lib/messaging/irpc.h"
+#include "dsdb/kcc/kcc_connection.h"
+#include "dsdb/kcc/kcc_service.h"
+#include <ldb_errors.h>
+#include "../lib/util/dlinklist.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_drsuapi.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "librpc/gen_ndr/ndr_irpc_c.h"
+#include "param/param.h"
+#include "dsdb/common/util.h"
+
+/*
+ * see if two repsFromToBlob blobs are for the same source DSA
+ */
+static bool kccsrv_same_source_dsa(struct repsFromToBlob *r1, struct repsFromToBlob *r2)
+{
+ return GUID_equal(&r1->ctr.ctr1.source_dsa_obj_guid,
+ &r2->ctr.ctr1.source_dsa_obj_guid);
+}
+
+/*
+ * see if a repsFromToBlob is in a list
+ */
+static bool reps_in_list(struct repsFromToBlob *r, struct repsFromToBlob *reps, uint32_t count)
+{
+ uint32_t i;
+ for (i=0; i<count; i++) {
+ if (kccsrv_same_source_dsa(r, &reps[i])) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/*
+ make sure we only add repsFrom entries for DCs who are masters for
+ the partition
+ */
+static bool check_MasterNC(struct kccsrv_service *service, struct dsdb_ldb_dn_list_node *p, struct repsFromToBlob *r,
+ struct ldb_result *res)
+{
+ struct repsFromTo1 *r1 = &r->ctr.ctr1;
+ struct GUID invocation_id = r1->source_dsa_invocation_id;
+ unsigned int i, j;
+ TALLOC_CTX *tmp_ctx;
+
+ /* we are expecting only version 1 */
+ SMB_ASSERT(r->version == 1);
+
+ tmp_ctx = talloc_new(p);
+ if (!tmp_ctx) {
+ return false;
+ }
+
+ for (i=0; i<res->count; i++) {
+ struct ldb_message *msg = res->msgs[i];
+ struct ldb_message_element *el;
+ struct ldb_dn *dn;
+
+ struct GUID id2 = samdb_result_guid(msg, "invocationID");
+ if (GUID_all_zero(&id2) ||
+ !GUID_equal(&invocation_id, &id2)) {
+ continue;
+ }
+
+ el = ldb_msg_find_element(msg, "msDS-hasMasterNCs");
+ if (!el || el->num_values == 0) {
+ el = ldb_msg_find_element(msg, "hasMasterNCs");
+ if (!el || el->num_values == 0) {
+ continue;
+ }
+ }
+ for (j=0; j<el->num_values; j++) {
+ dn = ldb_dn_from_ldb_val(tmp_ctx, service->samdb, &el->values[j]);
+ if (!ldb_dn_validate(dn)) {
+ talloc_free(dn);
+ continue;
+ }
+ if (ldb_dn_compare(dn, p->dn) == 0) {
+ DEBUG(5,("%s %s match on %s in %s\n",
+ r1->other_info->dns_name,
+ el->name,
+ ldb_dn_get_linearized(dn),
+ ldb_dn_get_linearized(msg->dn)));
+ talloc_free(tmp_ctx);
+ return true;
+ }
+ talloc_free(dn);
+ }
+ }
+ talloc_free(tmp_ctx);
+ return false;
+}
+
+struct kccsrv_notify_drepl_server_state {
+ struct dreplsrv_refresh r;
+};
+
+static void kccsrv_notify_drepl_server_done(struct tevent_req *subreq);
+
+/**
+ * Force dreplsrv to update its state as topology is changed
+ */
+static void kccsrv_notify_drepl_server(struct kccsrv_service *s,
+ TALLOC_CTX *mem_ctx)
+{
+ struct kccsrv_notify_drepl_server_state *state;
+ struct dcerpc_binding_handle *irpc_handle;
+ struct tevent_req *subreq;
+
+ state = talloc_zero(s, struct kccsrv_notify_drepl_server_state);
+ if (state == NULL) {
+ return;
+ }
+
+ irpc_handle = irpc_binding_handle_by_name(state, s->task->msg_ctx,
+ "dreplsrv", &ndr_table_irpc);
+ if (irpc_handle == NULL) {
+ /* dreplsrv is not running yet */
+ TALLOC_FREE(state);
+ return;
+ }
+
+ subreq = dcerpc_dreplsrv_refresh_r_send(state, s->task->event_ctx,
+ irpc_handle, &state->r);
+ if (subreq == NULL) {
+ TALLOC_FREE(state);
+ return;
+ }
+ tevent_req_set_callback(subreq, kccsrv_notify_drepl_server_done, state);
+}
+
+static void kccsrv_notify_drepl_server_done(struct tevent_req *subreq)
+{
+ struct kccsrv_notify_drepl_server_state *state =
+ tevent_req_callback_data(subreq,
+ struct kccsrv_notify_drepl_server_state);
+
+ dcerpc_dreplsrv_refresh_r_recv(subreq, state);
+ TALLOC_FREE(subreq);
+
+ /* we don't care about errors */
+ TALLOC_FREE(state);
+}
+
+uint32_t kccsrv_replica_flags(struct kccsrv_service *s)
+{
+ if (s->am_rodc) {
+ return DRSUAPI_DRS_INIT_SYNC |
+ DRSUAPI_DRS_PER_SYNC |
+ DRSUAPI_DRS_ADD_REF |
+ DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
+ DRSUAPI_DRS_NONGC_RO_REP;
+ }
+ return DRSUAPI_DRS_INIT_SYNC |
+ DRSUAPI_DRS_PER_SYNC |
+ DRSUAPI_DRS_ADD_REF |
+ DRSUAPI_DRS_WRIT_REP;
+}
+
+/*
+ * add any missing repsFrom structures to our partitions
+ */
+NTSTATUS kccsrv_add_repsFrom(struct kccsrv_service *s, TALLOC_CTX *mem_ctx,
+ struct repsFromToBlob *reps, uint32_t count,
+ struct ldb_result *res)
+{
+ struct dsdb_ldb_dn_list_node *p;
+ bool notify_dreplsrv = false;
+ uint32_t replica_flags = kccsrv_replica_flags(s);
+
+ /* update the repsFrom on all partitions */
+ for (p=s->partitions; p; p=p->next) {
+ struct repsFromToBlob *our_reps;
+ uint32_t our_count;
+ WERROR werr;
+ uint32_t i, j;
+ bool modified = false;
+
+ werr = dsdb_loadreps(s->samdb, mem_ctx, p->dn, "repsFrom", &our_reps, &our_count);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(0,(__location__ ": Failed to load repsFrom from %s - %s\n",
+ ldb_dn_get_linearized(p->dn), ldb_errstring(s->samdb)));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ /* see if the entry already exists */
+ for (i=0; i<count; i++) {
+ for (j=0; j<our_count; j++) {
+ if (kccsrv_same_source_dsa(&reps[i], &our_reps[j])) {
+ /* we already have this one -
+ check the replica_flags are right */
+ if (replica_flags != our_reps[j].ctr.ctr1.replica_flags) {
+ /* we need to update the old one with
+ * the new flags
+ */
+ our_reps[j].ctr.ctr1.replica_flags = replica_flags;
+ modified = true;
+ }
+ break;
+ }
+ }
+ if (j == our_count) {
+ /* we don't have the new one - add it
+ * if it is a master
+ */
+ if (res && !check_MasterNC(s, p, &reps[i], res)) {
+ /* its not a master, we don't
+ want to pull from it */
+ continue;
+ }
+ /* we need to add it to our repsFrom */
+ our_reps = talloc_realloc(mem_ctx, our_reps, struct repsFromToBlob, our_count+1);
+ NT_STATUS_HAVE_NO_MEMORY(our_reps);
+ our_reps[our_count] = reps[i];
+ our_reps[our_count].ctr.ctr1.replica_flags = replica_flags;
+ our_count++;
+ modified = true;
+ DEBUG(4,(__location__ ": Added repsFrom for %s\n",
+ reps[i].ctr.ctr1.other_info->dns_name));
+ }
+ }
+
+ /* remove any stale ones */
+ for (i=0; i<our_count; i++) {
+ if (!reps_in_list(&our_reps[i], reps, count) ||
+ (res && !check_MasterNC(s, p, &our_reps[i], res))) {
+ DEBUG(4,(__location__ ": Removed repsFrom for %s\n",
+ our_reps[i].ctr.ctr1.other_info->dns_name));
+ memmove(&our_reps[i], &our_reps[i+1], (our_count-(i+1))*sizeof(our_reps[0]));
+ our_count--;
+ i--;
+ modified = true;
+ }
+ }
+
+ if (modified) {
+ werr = dsdb_savereps(s->samdb, mem_ctx, p->dn, "repsFrom", our_reps, our_count);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(0,(__location__ ": Failed to save repsFrom to %s - %s\n",
+ ldb_dn_get_linearized(p->dn), ldb_errstring(s->samdb)));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+ /* dreplsrv should refresh its state */
+ notify_dreplsrv = true;
+ }
+
+ /* remove stale repsTo entries */
+ modified = false;
+ werr = dsdb_loadreps(s->samdb, mem_ctx, p->dn, "repsTo", &our_reps, &our_count);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(0,(__location__ ": Failed to load repsTo from %s - %s\n",
+ ldb_dn_get_linearized(p->dn), ldb_errstring(s->samdb)));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ /* remove any stale ones */
+ for (i=0; i<our_count; i++) {
+ if (!reps_in_list(&our_reps[i], reps, count)) {
+ DEBUG(4,(__location__ ": Removed repsTo for %s\n",
+ our_reps[i].ctr.ctr1.other_info->dns_name));
+ memmove(&our_reps[i], &our_reps[i+1], (our_count-(i+1))*sizeof(our_reps[0]));
+ our_count--;
+ i--;
+ modified = true;
+ }
+ }
+
+ if (modified) {
+ werr = dsdb_savereps(s->samdb, mem_ctx, p->dn, "repsTo", our_reps, our_count);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(0,(__location__ ": Failed to save repsTo to %s - %s\n",
+ ldb_dn_get_linearized(p->dn), ldb_errstring(s->samdb)));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+ /* dreplsrv should refresh its state */
+ notify_dreplsrv = true;
+ }
+ }
+
+ /* notify dreplsrv toplogy has changed */
+ if (notify_dreplsrv) {
+ kccsrv_notify_drepl_server(s, mem_ctx);
+ }
+
+ return NT_STATUS_OK;
+
+}
+
+
+/*
+ form a unique list of DNs from a search result and a given set of attributes
+ */
+static int kccsrv_dn_list(struct ldb_context *ldb, struct ldb_result *res,
+ TALLOC_CTX *mem_ctx,
+ const char **attrs,
+ struct ldb_dn ***dn_list, int *dn_count)
+{
+ int i;
+ struct ldb_dn **nc_list = NULL;
+ int nc_count = 0;
+
+ nc_list = talloc_array(mem_ctx, struct ldb_dn *, 0);
+ if (nc_list == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* gather up a list of all NCs in this forest */
+ for (i=0; i<res->count; i++) {
+ struct ldb_message *msg = res->msgs[i];
+ int j;
+ for (j=0; attrs[j]; j++) {
+ struct ldb_message_element *el;
+ int k;
+
+ el = ldb_msg_find_element(msg, attrs[j]);
+ if (el == NULL) continue;
+ for (k=0; k<el->num_values; k++) {
+ struct ldb_dn *dn;
+ dn = ldb_dn_from_ldb_val(nc_list, ldb, &el->values[k]);
+ if (dn != NULL) {
+ int l;
+ for (l=0; l<nc_count; l++) {
+ if (ldb_dn_compare(nc_list[l], dn) == 0) break;
+ }
+ if (l < nc_count) continue;
+ nc_list = talloc_realloc(mem_ctx, nc_list, struct ldb_dn *, nc_count+1);
+ if (nc_list == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ nc_list[nc_count] = dn;
+ nc_count++;
+ }
+ }
+ }
+ }
+
+ (*dn_list) = nc_list;
+ (*dn_count) = nc_count;
+ return LDB_SUCCESS;
+}
+
+
+/*
+ look for any additional global catalog partitions that we should be
+ replicating (by looking for msDS-HasDomainNCs), and add them to our
+ hasPartialReplicaNCs NTDS attribute
+ */
+static int kccsrv_gc_update(struct kccsrv_service *s, struct ldb_result *res)
+{
+ int i;
+ struct ldb_dn **nc_list = NULL;
+ int nc_count = 0;
+ struct ldb_dn **our_nc_list = NULL;
+ int our_nc_count = 0;
+ const char *attrs1[] = { "msDS-hasMasterNCs", "hasMasterNCs", "msDS-HasDomainNCs", NULL };
+ const char *attrs2[] = { "msDS-hasMasterNCs", "hasMasterNCs", "msDS-HasDomainNCs", "hasPartialReplicaNCs", NULL };
+ int ret;
+ TALLOC_CTX *tmp_ctx = talloc_new(res);
+ struct ldb_result *res2;
+ struct ldb_message *msg;
+
+ /* get a complete list of NCs for the forest */
+ ret = kccsrv_dn_list(s->samdb, res, tmp_ctx, attrs1, &nc_list, &nc_count);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(1,("Failed to get NC list for GC update - %s\n", ldb_errstring(s->samdb)));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /* get a list of what NCs we are already replicating */
+ ret = dsdb_search_dn(s->samdb, tmp_ctx, &res2, samdb_ntds_settings_dn(s->samdb, tmp_ctx), attrs2, 0);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(1,("Failed to get our NC list attributes for GC update - %s\n", ldb_errstring(s->samdb)));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = kccsrv_dn_list(s->samdb, res2, tmp_ctx, attrs2, &our_nc_list, &our_nc_count);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(1,("Failed to get our NC list for GC update - %s\n", ldb_errstring(s->samdb)));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ msg = ldb_msg_new(tmp_ctx);
+ if (msg == NULL) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ msg->dn = res2->msgs[0]->dn;
+
+ /* see if we are missing any */
+ for (i=0; i<nc_count; i++) {
+ int j;
+ for (j=0; j<our_nc_count; j++) {
+ if (ldb_dn_compare(nc_list[i], our_nc_list[j]) == 0) break;
+ }
+ if (j == our_nc_count) {
+ /* its a new one */
+ ret = ldb_msg_add_string(msg, "hasPartialReplicaNCs",
+ ldb_dn_get_extended_linearized(msg, nc_list[i], 1));
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ }
+ }
+
+ if (msg->num_elements == 0) {
+ /* none to add */
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+
+ if (s->am_rodc) {
+ DEBUG(5, ("%d partial replica should be added but we are RODC so we skip\n", msg->num_elements));
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+
+ msg->elements[0].flags = LDB_FLAG_MOD_ADD;
+
+ ret = dsdb_modify(s->samdb, msg, 0);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0,("Failed to add hasPartialReplicaNCs - %s\n",
+ ldb_errstring(s->samdb)));
+ }
+
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+
+/*
+ this is the core of our initial simple KCC
+ We just add a repsFrom entry for all DCs we find that have nTDSDSA
+ objects, except for ourselves
+ */
+NTSTATUS kccsrv_simple_update(struct kccsrv_service *s, TALLOC_CTX *mem_ctx)
+{
+ struct ldb_result *res;
+ unsigned int i;
+ int ret;
+ const char *attrs[] = { "objectGUID", "invocationID", "msDS-hasMasterNCs", "hasMasterNCs", "msDS-HasDomainNCs", NULL };
+ struct repsFromToBlob *reps = NULL;
+ uint32_t count = 0;
+ struct kcc_connection_list *ntds_conn, *dsa_conn;
+
+ ret = dsdb_search(s->samdb, mem_ctx, &res, s->config_dn, LDB_SCOPE_SUBTREE,
+ attrs, DSDB_SEARCH_SHOW_EXTENDED_DN, "objectClass=nTDSDSA");
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0,(__location__ ": Failed nTDSDSA search - %s\n", ldb_errstring(s->samdb)));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ if (samdb_is_gc(s->samdb)) {
+ kccsrv_gc_update(s, res);
+ }
+
+ /* get the current list of connections */
+ ntds_conn = kccsrv_find_connections(s, mem_ctx);
+
+ dsa_conn = talloc_zero(mem_ctx, struct kcc_connection_list);
+
+ for (i=0; i<res->count; i++) {
+ struct repsFromTo1 *r1;
+ struct GUID ntds_guid, invocation_id;
+
+ ntds_guid = samdb_result_guid(res->msgs[i], "objectGUID");
+ if (GUID_equal(&ntds_guid, &s->ntds_guid)) {
+ /* don't replicate with ourselves */
+ continue;
+ }
+
+ invocation_id = samdb_result_guid(res->msgs[i], "invocationID");
+
+ reps = talloc_realloc(mem_ctx, reps, struct repsFromToBlob, count+1);
+ NT_STATUS_HAVE_NO_MEMORY(reps);
+
+ ZERO_STRUCT(reps[count]);
+ reps[count].version = 1;
+ r1 = &reps[count].ctr.ctr1;
+
+ r1->other_info = talloc_zero(reps, struct repsFromTo1OtherInfo);
+ r1->other_info->dns_name = samdb_ntds_msdcs_dns_name(s->samdb, reps, &ntds_guid);
+ r1->source_dsa_obj_guid = ntds_guid;
+ r1->source_dsa_invocation_id = invocation_id;
+ r1->replica_flags = kccsrv_replica_flags(s);
+ memset(r1->schedule, 0x11, sizeof(r1->schedule));
+
+ dsa_conn->servers = talloc_realloc(dsa_conn, dsa_conn->servers,
+ struct kcc_connection,
+ dsa_conn->count + 1);
+ NT_STATUS_HAVE_NO_MEMORY(dsa_conn->servers);
+ dsa_conn->servers[dsa_conn->count].dsa_guid = r1->source_dsa_obj_guid;
+ dsa_conn->count++;
+
+ count++;
+ }
+
+ kccsrv_apply_connections(s, ntds_conn, dsa_conn);
+
+ return kccsrv_add_repsFrom(s, mem_ctx, reps, count, res);
+}
+
+
+static void kccsrv_periodic_run(struct kccsrv_service *service);
+
+static void kccsrv_periodic_handler_te(struct tevent_context *ev, struct tevent_timer *te,
+ struct timeval t, void *ptr)
+{
+ struct kccsrv_service *service = talloc_get_type(ptr, struct kccsrv_service);
+ WERROR status;
+
+ service->periodic.te = NULL;
+
+ kccsrv_periodic_run(service);
+
+ status = kccsrv_periodic_schedule(service, service->periodic.interval);
+ if (!W_ERROR_IS_OK(status)) {
+ task_server_terminate(service->task, win_errstr(status), true);
+ return;
+ }
+}
+
+WERROR kccsrv_periodic_schedule(struct kccsrv_service *service, uint32_t next_interval)
+{
+ TALLOC_CTX *tmp_mem;
+ struct tevent_timer *new_te;
+ struct timeval next_time;
+
+ /* prevent looping */
+ if (next_interval == 0) next_interval = 1;
+
+ next_time = timeval_current_ofs(next_interval, 50);
+
+ if (service->periodic.te) {
+ /*
+ * if the timestamp of the new event is higher,
+ * as current next we don't need to reschedule
+ */
+ if (timeval_compare(&next_time, &service->periodic.next_event) > 0) {
+ return WERR_OK;
+ }
+ }
+
+ /* reset the next scheduled timestamp */
+ service->periodic.next_event = next_time;
+
+ new_te = tevent_add_timer(service->task->event_ctx, service,
+ service->periodic.next_event,
+ kccsrv_periodic_handler_te, service);
+ W_ERROR_HAVE_NO_MEMORY(new_te);
+
+ tmp_mem = talloc_new(service);
+ DEBUG(4,("kccsrv_periodic_schedule(%u) %sscheduled for: %s\n",
+ next_interval,
+ (service->periodic.te?"re":""),
+ nt_time_string(tmp_mem, timeval_to_nttime(&next_time))));
+ talloc_free(tmp_mem);
+
+ talloc_free(service->periodic.te);
+ service->periodic.te = new_te;
+
+ return WERR_OK;
+}
+
+/*
+ * Check to see if any dns entries need scavenging. This only occurs if aging
+ * is enabled in general ("zone scavenging" lpcfg) and on the zone
+ * (zone->fAging is true).
+ */
+static NTSTATUS kccsrv_dns_zone_scavenging(
+ struct kccsrv_service *s,
+ TALLOC_CTX *mem_ctx)
+{
+
+ time_t current_time = time(NULL);
+ time_t dns_scavenge_interval;
+ NTSTATUS status;
+ char *error_string = NULL;
+
+ /*
+ * Only perform zone scavenging if it's been enabled.
+ * (it still might be disabled on all zones).
+ */
+ if (!lpcfg_dns_zone_scavenging(s->task->lp_ctx)) {
+ DBG_INFO("DNS scavenging not enabled\n");
+ return NT_STATUS_OK;
+ }
+
+ dns_scavenge_interval = lpcfg_parm_int(s->task->lp_ctx,
+ NULL,
+ "dnsserver",
+ "scavenging_interval",
+ 2 * 60 * 60);
+ if ((current_time - s->last_dns_scavenge) > dns_scavenge_interval) {
+ s->last_dns_scavenge = current_time;
+ status = dns_tombstone_records(mem_ctx, s->samdb,
+ &error_string);
+ if (!NT_STATUS_IS_OK(status)) {
+ const char *err = NULL;
+ if (error_string != NULL) {
+ err = error_string;
+ } else {
+ err = nt_errstr(status);
+ }
+ DBG_ERR("DNS record scavenging process failed: %s\n",
+ err);
+ return status;
+ }
+ }
+ DBG_INFO("Successfully tombstoned stale DNS records\n");
+ return NT_STATUS_OK;
+}
+/*
+ * check to see if any dns tombstones should be deleted. This is not optional
+ * ([MS-DNSP] "DsTombstoneInterval") -- stale tombstones are useless clutter.
+ *
+ * Windows does it daily at 2am; we do it roughly daily at an uncontrolled
+ * time.
+ */
+static NTSTATUS kccsrv_dns_zone_tombstone_deletion(struct kccsrv_service *s,
+ TALLOC_CTX *mem_ctx)
+{
+ time_t current_time = time(NULL);
+ NTSTATUS status;
+ char *error_string = NULL;
+ time_t dns_collection_interval =
+ lpcfg_parm_int(s->task->lp_ctx,
+ NULL,
+ "dnsserver",
+ "tombstone_collection_interval",
+ 24 * 60 * 60);
+
+ if ((current_time - s->last_dns_tombstone_collection) >
+ dns_collection_interval) {
+ s->last_dns_tombstone_collection = current_time;
+ status = dns_delete_tombstones(mem_ctx, s->samdb,
+ &error_string);
+ if (!NT_STATUS_IS_OK(status)) {
+ const char *err = NULL;
+ if (error_string != NULL) {
+ err = error_string;
+ } else {
+ err = nt_errstr(status);
+ }
+ DBG_ERR("DNS tombstone deletion failed: %s\n", err);
+ return status;
+ }
+ }
+ DBG_INFO("Successfully deleted DNS tombstones\n");
+ return NT_STATUS_OK;
+}
+
+/*
+ check to see if any deleted objects need scavenging
+ */
+static NTSTATUS kccsrv_check_deleted(struct kccsrv_service *s, TALLOC_CTX *mem_ctx)
+{
+ time_t current_time = time(NULL);
+ time_t interval = lpcfg_parm_int(
+ s->task->lp_ctx, NULL, "kccsrv", "check_deleted_interval", 86400);
+ uint32_t tombstoneLifetime;
+ int ret;
+ unsigned int num_objects_removed = 0;
+ unsigned int num_links_removed = 0;
+ NTSTATUS status;
+ char *error_string = NULL;
+
+ if (current_time - s->last_deleted_check < interval) {
+ return NT_STATUS_OK;
+ }
+
+ ret = dsdb_tombstone_lifetime(s->samdb, &tombstoneLifetime);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(1,(__location__ ": Failed to get tombstone lifetime\n"));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ s->last_deleted_check = current_time;
+
+ status = dsdb_garbage_collect_tombstones(mem_ctx, s->samdb,
+ s->partitions,
+ current_time, tombstoneLifetime,
+ &num_objects_removed,
+ &num_links_removed,
+ &error_string);
+
+ if (NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("garbage_collect_tombstones: Removed %u tombstone objects "
+ "and %u tombstone links successfully\n",
+ num_objects_removed, num_links_removed));
+ } else {
+ DEBUG(2, ("garbage_collect_tombstones: Failure removing tombstone "
+ "objects and links after removing %u tombstone objects "
+ "and %u tombstone links successfully: %s\n",
+ num_objects_removed, num_links_removed,
+ error_string ? error_string : nt_errstr(status)));
+ }
+ return status;
+}
+
+static void kccsrv_periodic_run(struct kccsrv_service *service)
+{
+ TALLOC_CTX *mem_ctx;
+ NTSTATUS status;
+
+ DEBUG(4,("kccsrv_periodic_run(): update\n"));
+
+ mem_ctx = talloc_new(service);
+
+ if (service->samba_kcc_code)
+ status = kccsrv_samba_kcc(service);
+ else {
+ status = kccsrv_simple_update(service, mem_ctx);
+ if (!NT_STATUS_IS_OK(status))
+ DEBUG(0,("kccsrv_simple_update failed - %s\n",
+ nt_errstr(status)));
+ }
+
+ status = kccsrv_check_deleted(service, mem_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("kccsrv_check_deleted failed - %s\n", nt_errstr(status)));
+ }
+ status = kccsrv_dns_zone_scavenging(service, mem_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("kccsrv_dns_zone_aging failed - %s\n",
+ nt_errstr(status));
+ }
+ status = kccsrv_dns_zone_tombstone_deletion(service, mem_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("kccsrv_dns_zone_tombstone_scavenging failed - %s\n",
+ nt_errstr(status));
+ }
+ talloc_free(mem_ctx);
+}
+
+/* Called when samba_kcc script has finished
+ */
+static void samba_kcc_done(struct tevent_req *subreq)
+{
+ struct kccsrv_service *service =
+ tevent_req_callback_data(subreq, struct kccsrv_service);
+ int rc;
+ int sys_errno;
+
+ service->periodic.subreq = NULL;
+
+ rc = samba_runcmd_recv(subreq, &sys_errno);
+ TALLOC_FREE(subreq);
+
+ if (rc != 0)
+ service->periodic.status =
+ map_nt_error_from_unix_common(sys_errno);
+ else
+ service->periodic.status = NT_STATUS_OK;
+
+ if (!NT_STATUS_IS_OK(service->periodic.status))
+ DEBUG(0,(__location__ ": Failed samba_kcc - %s\n",
+ nt_errstr(service->periodic.status)));
+ else
+ DEBUG(3,("Completed samba_kcc OK\n"));
+}
+
+/* Invocation of the samba_kcc python script for replication
+ * topology generation.
+ */
+NTSTATUS kccsrv_samba_kcc(struct kccsrv_service *service)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ const char * const *samba_kcc_command =
+ lpcfg_samba_kcc_command(service->task->lp_ctx);
+
+ /* kill any existing child */
+ TALLOC_FREE(service->periodic.subreq);
+
+ DEBUG(2, ("Calling samba_kcc script\n"));
+ service->periodic.subreq = samba_runcmd_send(service,
+ service->task->event_ctx,
+ timeval_current_ofs(40, 0),
+ 2, 0, samba_kcc_command, NULL);
+
+ if (service->periodic.subreq == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto xerror;
+ }
+ tevent_req_set_callback(service->periodic.subreq,
+ samba_kcc_done, service);
+
+xerror:
+ if (!NT_STATUS_IS_OK(status))
+ DEBUG(0,(__location__ ": failed - %s\n", nt_errstr(status)));
+ return status;
+}
diff --git a/source4/dsdb/kcc/kcc_service.c b/source4/dsdb/kcc/kcc_service.c
new file mode 100644
index 0000000..066cc07
--- /dev/null
+++ b/source4/dsdb/kcc/kcc_service.c
@@ -0,0 +1,364 @@
+/*
+ Unix SMB/CIFS Implementation.
+
+ KCC service
+
+ Copyright (C) Andrew Tridgell 2009
+ based on repl service code
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "dsdb/samdb/samdb.h"
+#include "auth/auth.h"
+#include "samba/service.h"
+#include "lib/events/events.h"
+#include "lib/messaging/irpc.h"
+#include "dsdb/kcc/kcc_service.h"
+#include <ldb_errors.h>
+#include "../lib/util/dlinklist.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_drsuapi.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "param/param.h"
+#include "libds/common/roles.h"
+
+/*
+ establish system creds
+ */
+static WERROR kccsrv_init_creds(struct kccsrv_service *service)
+{
+ service->system_session_info = system_session(service->task->lp_ctx);
+ if (!service->system_session_info) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ return WERR_OK;
+}
+
+/*
+ connect to the local SAM
+ */
+static WERROR kccsrv_connect_samdb(struct kccsrv_service *service, struct loadparm_context *lp_ctx)
+{
+ const struct GUID *ntds_guid;
+
+ service->samdb = samdb_connect(service,
+ service->task->event_ctx,
+ lp_ctx,
+ service->system_session_info,
+ NULL,
+ 0);
+ if (!service->samdb) {
+ return WERR_DS_UNAVAILABLE;
+ }
+
+ ntds_guid = samdb_ntds_objectGUID(service->samdb);
+ if (!ntds_guid) {
+ DBG_ERR("Failed to determine own NTDS objectGUID\n");
+ return WERR_DS_UNAVAILABLE;
+ }
+
+ service->ntds_guid = *ntds_guid;
+
+ if (samdb_rodc(service->samdb, &service->am_rodc) != LDB_SUCCESS) {
+ DEBUG(0,(__location__ ": Failed to determine RODC status\n"));
+ return WERR_DS_UNAVAILABLE;
+ }
+
+ return WERR_OK;
+}
+
+
+/*
+ load our local partition list
+ */
+static WERROR kccsrv_load_partitions(struct kccsrv_service *s)
+{
+ struct ldb_dn *basedn;
+ struct ldb_result *r;
+ struct ldb_message_element *el;
+ static const char *attrs[] = { "namingContexts", "configurationNamingContext", NULL };
+ unsigned int i;
+ int ret;
+
+ basedn = ldb_dn_new(s, s->samdb, NULL);
+ W_ERROR_HAVE_NO_MEMORY(basedn);
+
+ ret = ldb_search(s->samdb, s, &r, basedn, LDB_SCOPE_BASE, attrs,
+ "(objectClass=*)");
+ talloc_free(basedn);
+ if (ret != LDB_SUCCESS) {
+ return WERR_FOOBAR;
+ } else if (r->count != 1) {
+ talloc_free(r);
+ return WERR_FOOBAR;
+ }
+
+ el = ldb_msg_find_element(r->msgs[0], "namingContexts");
+ if (!el) {
+ return WERR_FOOBAR;
+ }
+
+ for (i=0; i < el->num_values; i++) {
+ const char *v = (const char *)el->values[i].data;
+ struct ldb_dn *pdn;
+ struct dsdb_ldb_dn_list_node *p;
+
+ pdn = ldb_dn_new(s, s->samdb, v);
+ if (!ldb_dn_validate(pdn)) {
+ return WERR_FOOBAR;
+ }
+
+ p = talloc_zero(s, struct dsdb_ldb_dn_list_node);
+ W_ERROR_HAVE_NO_MEMORY(p);
+
+ p->dn = talloc_steal(p, pdn);
+
+ DLIST_ADD(s->partitions, p);
+
+ DEBUG(2, ("kccsrv_partition[%s] loaded\n", v));
+ }
+
+ el = ldb_msg_find_element(r->msgs[0], "configurationNamingContext");
+ if (!el) {
+ return WERR_FOOBAR;
+ }
+ s->config_dn = ldb_dn_new(s, s->samdb, (const char *)el->values[0].data);
+ if (!ldb_dn_validate(s->config_dn)) {
+ return WERR_FOOBAR;
+ }
+
+ talloc_free(r);
+
+ return WERR_OK;
+}
+
+
+struct kcc_manual_runcmd_state {
+ struct irpc_message *msg;
+ struct drsuapi_DsExecuteKCC *r;
+ struct kccsrv_service *service;
+};
+
+
+/*
+ * Called when samba_kcc script has finished
+ */
+static void manual_samba_kcc_done(struct tevent_req *subreq)
+{
+ struct kcc_manual_runcmd_state *st =
+ tevent_req_callback_data(subreq,
+ struct kcc_manual_runcmd_state);
+ int rc;
+ int sys_errno;
+ NTSTATUS status;
+
+ st->service->periodic.subreq = NULL;
+
+ rc = samba_runcmd_recv(subreq, &sys_errno);
+ TALLOC_FREE(subreq);
+
+ if (rc != 0) {
+ status = map_nt_error_from_unix_common(sys_errno);
+ } else {
+ status = NT_STATUS_OK;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,(__location__ ": Failed manual run of samba_kcc - %s\n",
+ nt_errstr(status)));
+ } else {
+ DEBUG(3,("Completed manual run of samba_kcc OK\n"));
+ }
+
+ if (!(st->r->in.req->ctr1.flags & DRSUAPI_DS_EXECUTE_KCC_ASYNCHRONOUS_OPERATION)) {
+ irpc_send_reply(st->msg, status);
+ }
+}
+
+static NTSTATUS kccsrv_execute_kcc(struct irpc_message *msg, struct drsuapi_DsExecuteKCC *r)
+{
+ TALLOC_CTX *mem_ctx;
+ NTSTATUS status = NT_STATUS_OK;
+ struct kccsrv_service *service = talloc_get_type(msg->private_data, struct kccsrv_service);
+
+ const char * const *samba_kcc_command;
+ struct kcc_manual_runcmd_state *st;
+
+ if (!service->samba_kcc_code) {
+ mem_ctx = talloc_new(service);
+
+ status = kccsrv_simple_update(service, mem_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("kccsrv_execute_kcc failed - %s\n",
+ nt_errstr(status)));
+ }
+ talloc_free(mem_ctx);
+
+ return NT_STATUS_OK;
+ }
+
+ /* Invocation of the samba_kcc python script for replication
+ * topology generation.
+ */
+
+ samba_kcc_command =
+ lpcfg_samba_kcc_command(service->task->lp_ctx);
+
+ st = talloc(msg, struct kcc_manual_runcmd_state);
+ if (st == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ st->msg = msg;
+ st->r = r;
+ st->service = service;
+
+ /* don't run at the same time as an existing child */
+ if (service->periodic.subreq) {
+ status = NT_STATUS_DS_BUSY;
+ return status;
+ }
+
+ DEBUG(2, ("Calling samba_kcc script\n"));
+ service->periodic.subreq = samba_runcmd_send(service,
+ service->task->event_ctx,
+ timeval_current_ofs(40, 0),
+ 2, 0, samba_kcc_command, NULL);
+
+ if (service->periodic.subreq == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ DEBUG(0,(__location__ ": failed - %s\n", nt_errstr(status)));
+ return status;
+ } else {
+ tevent_req_set_callback(service->periodic.subreq,
+ manual_samba_kcc_done, st);
+ }
+
+ if (r->in.req->ctr1.flags & DRSUAPI_DS_EXECUTE_KCC_ASYNCHRONOUS_OPERATION) {
+ /* This actually means reply right away, let it run in the background */
+ } else {
+ /* mark the request as replied async, the caller wants to know when this is finished */
+ msg->defer_reply = true;
+ }
+ return status;
+
+}
+
+static NTSTATUS kccsrv_replica_get_info(struct irpc_message *msg, struct drsuapi_DsReplicaGetInfo *r)
+{
+ return kccdrs_replica_get_info(msg, r);
+}
+
+/*
+ startup the kcc service task
+*/
+static NTSTATUS kccsrv_task_init(struct task_server *task)
+{
+ WERROR status;
+ struct kccsrv_service *service;
+ uint32_t periodic_startup_interval;
+
+ switch (lpcfg_server_role(task->lp_ctx)) {
+ case ROLE_STANDALONE:
+ task_server_terminate(task, "kccsrv: no KCC required in standalone configuration", false);
+ return NT_STATUS_INVALID_DOMAIN_ROLE;
+ case ROLE_DOMAIN_MEMBER:
+ task_server_terminate(task, "kccsrv: no KCC required in domain member configuration", false);
+ return NT_STATUS_INVALID_DOMAIN_ROLE;
+ case ROLE_ACTIVE_DIRECTORY_DC:
+ /* Yes, we want a KCC */
+ break;
+ }
+
+ task_server_set_title(task, "task[kccsrv]");
+
+ service = talloc_zero(task, struct kccsrv_service);
+ if (!service) {
+ task_server_terminate(task, "kccsrv_task_init: out of memory", true);
+ return NT_STATUS_NO_MEMORY;
+ }
+ service->task = task;
+ service->startup_time = timeval_current();
+ task->private_data = service;
+
+ status = kccsrv_init_creds(service);
+ if (!W_ERROR_IS_OK(status)) {
+ task_server_terminate(task,
+ talloc_asprintf(task,
+ "kccsrv: Failed to obtain server credentials: %s\n",
+ win_errstr(status)), true);
+ return werror_to_ntstatus(status);
+ }
+
+ status = kccsrv_connect_samdb(service, task->lp_ctx);
+ if (!W_ERROR_IS_OK(status)) {
+ task_server_terminate(task, talloc_asprintf(task,
+ "kccsrv: Failed to connect to local samdb: %s\n",
+ win_errstr(status)), true);
+ return werror_to_ntstatus(status);
+ }
+
+ status = kccsrv_load_partitions(service);
+ if (!W_ERROR_IS_OK(status)) {
+ task_server_terminate(task, talloc_asprintf(task,
+ "kccsrv: Failed to load partitions: %s\n",
+ win_errstr(status)), true);
+ return werror_to_ntstatus(status);
+ }
+
+ periodic_startup_interval =
+ lpcfg_parm_int(task->lp_ctx, NULL, "kccsrv",
+ "periodic_startup_interval", 15); /* in seconds */
+ service->periodic.interval =
+ lpcfg_parm_int(task->lp_ctx, NULL, "kccsrv",
+ "periodic_interval", 300); /* in seconds */
+
+ /* (kccsrv:samba_kcc=true) will run newer samba_kcc replication
+ * topology generation code.
+ */
+ service->samba_kcc_code = lpcfg_parm_bool(task->lp_ctx, NULL,
+ "kccsrv", "samba_kcc", true);
+
+ status = kccsrv_periodic_schedule(service, periodic_startup_interval);
+ if (!W_ERROR_IS_OK(status)) {
+ task_server_terminate(task, talloc_asprintf(task,
+ "kccsrv: Failed to periodic schedule: %s\n",
+ win_errstr(status)), true);
+ return werror_to_ntstatus(status);
+ }
+
+ irpc_add_name(task->msg_ctx, "kccsrv");
+
+ IRPC_REGISTER(task->msg_ctx, drsuapi, DRSUAPI_DSEXECUTEKCC, kccsrv_execute_kcc, service);
+ IRPC_REGISTER(task->msg_ctx, drsuapi, DRSUAPI_DSREPLICAGETINFO, kccsrv_replica_get_info, service);
+ return NT_STATUS_OK;
+}
+
+/*
+ register ourselves as a available server
+*/
+NTSTATUS server_service_kcc_init(TALLOC_CTX *ctx)
+{
+ static const struct service_details details = {
+ .inhibit_fork_on_accept = true,
+ .inhibit_pre_fork = true,
+ .task_init = kccsrv_task_init,
+ .post_fork = NULL
+ };
+ return register_server_service(ctx, "kcc", &details);
+}
diff --git a/source4/dsdb/kcc/kcc_service.h b/source4/dsdb/kcc/kcc_service.h
new file mode 100644
index 0000000..d702b5a
--- /dev/null
+++ b/source4/dsdb/kcc/kcc_service.h
@@ -0,0 +1,101 @@
+/*
+ Unix SMB/CIFS Implementation.
+
+ KCC service
+
+ Copyright (C) Andrew Tridgell 2009
+ based on drepl service code
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#ifndef _DSDB_REPL_KCC_SERVICE_H_
+#define _DSDB_REPL_KCC_SERVICE_H_
+
+#include "librpc/gen_ndr/ndr_drsuapi_c.h"
+#include "dsdb/common/util.h"
+
+struct kccsrv_service {
+ /* the whole kcc service is in one task */
+ struct task_server *task;
+
+ /* the time the service was started */
+ struct timeval startup_time;
+
+ /* dn of our configuration partition */
+ struct ldb_dn *config_dn;
+
+ /*
+ * system session info
+ * with machine account credentials
+ */
+ struct auth_session_info *system_session_info;
+
+ /* list of local partitions */
+ struct dsdb_ldb_dn_list_node *partitions;
+
+ /*
+ * a connection to the local samdb
+ */
+ struct ldb_context *samdb;
+
+ /* the guid of our NTDS Settings object, which never changes! */
+ struct GUID ntds_guid;
+
+ /* some stuff for periodic processing */
+ struct {
+ /*
+ * the interval between to periodic runs
+ */
+ uint32_t interval;
+
+ /*
+ * the timestamp for the next event,
+ * this is the timestamp passed to event_add_timed()
+ */
+ struct timeval next_event;
+
+ /* here we have a reference to the timed event the schedules the periodic stuff */
+ struct tevent_timer *te;
+
+ /* samba_runcmd_send service for samba_kcc */
+ struct tevent_req *subreq;
+
+ /* return status of samba_kcc */
+ NTSTATUS status;
+
+ } periodic;
+
+ time_t last_deleted_check;
+
+ time_t last_dns_scavenge;
+
+ time_t last_dns_tombstone_collection;
+
+ time_t last_full_scan_deleted_check;
+
+ bool am_rodc;
+
+ /* run new samba_kcc topology generator code */
+ bool samba_kcc_code;
+};
+
+struct kcc_connection_list;
+
+#include "dsdb/kcc/garbage_collect_tombstones.h"
+#include "dsdb/kcc/scavenge_dns_records.h"
+#include "dsdb/kcc/kcc_service_proto.h"
+
+#endif /* _DSDB_REPL_KCC_SERVICE_H_ */
diff --git a/source4/dsdb/kcc/scavenge_dns_records.c b/source4/dsdb/kcc/scavenge_dns_records.c
new file mode 100644
index 0000000..f41250c
--- /dev/null
+++ b/source4/dsdb/kcc/scavenge_dns_records.c
@@ -0,0 +1,531 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ DNS tombstoning routines
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include <ldb_errors.h>
+#include "../lib/util/dlinklist.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_drsuapi.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "param/param.h"
+#include "lib/util/dlinklist.h"
+#include "ldb.h"
+#include "dsdb/kcc/scavenge_dns_records.h"
+#include "lib/ldb-samba/ldb_matching_rules.h"
+#include "lib/util/time.h"
+#include "dns_server/dnsserver_common.h"
+#include "librpc/gen_ndr/ndr_dnsp.h"
+#include "param/param.h"
+
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_drsuapi.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+
+/*
+ * Copy only non-expired dns records from one message element to another.
+ */
+static NTSTATUS copy_current_records(TALLOC_CTX *mem_ctx,
+ struct ldb_message_element *old_el,
+ struct ldb_message_element *el,
+ uint32_t dns_timestamp)
+{
+ unsigned int i;
+ struct dnsp_DnssrvRpcRecord rec;
+ enum ndr_err_code ndr_err;
+
+ el->values = talloc_zero_array(mem_ctx, struct ldb_val,
+ old_el->num_values);
+ if (el->values == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; i < old_el->num_values; i++) {
+ ndr_err = ndr_pull_struct_blob(
+ &(old_el->values[i]),
+ mem_ctx,
+ &rec,
+ (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_ERR("Failed to pull dns rec blob.\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ if (rec.dwTimeStamp > dns_timestamp ||
+ rec.dwTimeStamp == 0) {
+ el->values[el->num_values] = old_el->values[i];
+ el->num_values++;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * Check all records in a zone and tombstone them if they're expired.
+ */
+static NTSTATUS dns_tombstone_records_zone(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ struct dns_server_zone *zone,
+ uint32_t dns_timestamp,
+ NTTIME entombed_time,
+ char **error_string)
+{
+ WERROR werr;
+ NTSTATUS status;
+ unsigned int i;
+ struct dnsserver_zoneinfo *zi = NULL;
+ struct ldb_result *res = NULL;
+ struct ldb_message_element *el = NULL;
+ struct ldb_message_element *tombstone_el = NULL;
+ struct ldb_message_element *old_el = NULL;
+ struct ldb_message *new_msg = NULL;
+ enum ndr_err_code ndr_err;
+ int ret;
+ struct GUID guid;
+ struct GUID_txt_buf buf_guid;
+ const char *attrs[] = {"dnsRecord",
+ "dNSTombstoned",
+ "objectGUID",
+ NULL};
+
+ struct ldb_val true_val = {
+ .data = discard_const_p(uint8_t, "TRUE"),
+ .length = 4
+ };
+
+ struct ldb_val tombstone_blob;
+ struct dnsp_DnssrvRpcRecord tombstone_struct = {
+ .wType = DNS_TYPE_TOMBSTONE,
+ .data = {.EntombedTime = entombed_time}
+ };
+
+ ndr_err = ndr_push_struct_blob(
+ &tombstone_blob,
+ mem_ctx,
+ &tombstone_struct,
+ (ndr_push_flags_fn_t)ndr_push_dnsp_DnssrvRpcRecord);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ *error_string = discard_const_p(char,
+ "Failed to push TOMBSTONE"
+ "dnsp_DnssrvRpcRecord\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ *error_string = NULL;
+
+ /* Get NoRefreshInterval and RefreshInterval from zone properties.*/
+ zi = talloc(mem_ctx, struct dnsserver_zoneinfo);
+ if (zi == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ werr = dns_get_zone_properties(samdb, mem_ctx, zone->dn, zi);
+ if (W_ERROR_EQUAL(DNS_ERR(NOTZONE), werr)) {
+ return NT_STATUS_PROPSET_NOT_FOUND;
+ } else if (!W_ERROR_IS_OK(werr)) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ /* Subtract them from current time to get the earliest possible.
+ * timestamp allowed for a non-expired DNS record. */
+ dns_timestamp -= zi->dwNoRefreshInterval + zi->dwRefreshInterval;
+
+ /* Custom match gets dns records in the zone with dwTimeStamp < t. */
+ ret = ldb_search(samdb,
+ mem_ctx,
+ &res,
+ zone->dn,
+ LDB_SCOPE_SUBTREE,
+ attrs,
+ "(&(objectClass=dnsNode)"
+ "(&(!(dnsTombstoned=TRUE))"
+ "(dnsRecord:" DSDB_MATCH_FOR_DNS_TO_TOMBSTONE_TIME
+ ":=%"PRIu32")))",
+ dns_timestamp);
+ if (ret != LDB_SUCCESS) {
+ *error_string = talloc_asprintf(mem_ctx,
+ "Failed to search for dns "
+ "objects in zone %s: %s",
+ ldb_dn_get_linearized(zone->dn),
+ ldb_errstring(samdb));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ /*
+ * Do a constrained update on each expired DNS node. To do a constrained
+ * update we leave the dnsRecord element as is, and just change the flag
+ * to MOD_DELETE, then add a new element with the changes we want. LDB
+ * will run the deletion first, and bail out if a binary comparison
+ * between the attribute we pass and the one in the database shows a
+ * change. This prevents race conditions.
+ */
+ for (i = 0; i < res->count; i++) {
+ new_msg = ldb_msg_copy(mem_ctx, res->msgs[i]);
+ if (new_msg == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ old_el = ldb_msg_find_element(new_msg, "dnsRecord");
+ if (old_el == NULL) {
+ TALLOC_FREE(new_msg);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ old_el->flags = LDB_FLAG_MOD_DELETE;
+
+ ret = ldb_msg_add_empty(
+ new_msg, "dnsRecord", LDB_FLAG_MOD_ADD, &el);
+ if (ret != LDB_SUCCESS) {
+ TALLOC_FREE(new_msg);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ status = copy_current_records(new_msg, old_el, el, dns_timestamp);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(new_msg);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ /* If nothing was expired, do nothing. */
+ if (el->num_values == old_el->num_values &&
+ el->num_values != 0) {
+ TALLOC_FREE(new_msg);
+ continue;
+ }
+
+ /*
+ * If everything was expired, we tombstone the node, which
+ * involves adding a tombstone dnsRecord and a 'dnsTombstoned:
+ * TRUE' attribute. That is, we want to end up with this:
+ *
+ * objectClass: dnsNode
+ * dnsRecord: { .wType = DNSTYPE_TOMBSTONE,
+ * .data.EntombedTime = <now> }
+ * dnsTombstoned: TRUE
+ *
+ * and no other dnsRecords.
+ */
+ if (el->num_values == 0) {
+ struct ldb_val *vals = talloc_realloc(new_msg->elements,
+ el->values,
+ struct ldb_val,
+ 1);
+ if (!vals) {
+ TALLOC_FREE(new_msg);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ el->values = vals;
+ el->values[0] = tombstone_blob;
+ el->num_values = 1;
+
+ tombstone_el = ldb_msg_find_element(new_msg,
+ "dnsTombstoned");
+
+ if (tombstone_el == NULL) {
+ ret = ldb_msg_add_value(new_msg,
+ "dnsTombstoned",
+ &true_val,
+ &tombstone_el);
+ if (ret != LDB_SUCCESS) {
+ TALLOC_FREE(new_msg);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ tombstone_el->flags = LDB_FLAG_MOD_ADD;
+ } else {
+ if (tombstone_el->num_values != 1) {
+ vals = talloc_realloc(
+ new_msg->elements,
+ tombstone_el->values,
+ struct ldb_val,
+ 1);
+ if (!vals) {
+ TALLOC_FREE(new_msg);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ tombstone_el->values = vals;
+ tombstone_el->num_values = 1;
+ }
+ tombstone_el->flags = LDB_FLAG_MOD_REPLACE;
+ tombstone_el->values[0] = true_val;
+ }
+ } else {
+ /*
+ * Do not change the status of dnsTombstoned if we
+ * found any live records. If it exists, its value
+ * will be the harmless "FALSE", which is what we end
+ * up with when a tombstoned record is untombstoned.
+ * (in dns_common_replace).
+ */
+ ldb_msg_remove_attr(new_msg,
+ "dnsTombstoned");
+ }
+
+ /* Set DN to the GUID in case the object was moved. */
+ el = ldb_msg_find_element(new_msg, "objectGUID");
+ if (el == NULL) {
+ TALLOC_FREE(new_msg);
+ *error_string =
+ talloc_asprintf(mem_ctx,
+ "record has no objectGUID "
+ "in zone %s",
+ ldb_dn_get_linearized(zone->dn));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ status = GUID_from_ndr_blob(el->values, &guid);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(new_msg);
+ *error_string =
+ discard_const_p(char, "Error: Invalid GUID.\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ GUID_buf_string(&guid, &buf_guid);
+ new_msg->dn =
+ ldb_dn_new_fmt(mem_ctx, samdb, "<GUID=%s>", buf_guid.buf);
+
+ /* Remove the GUID so we're not trying to modify it. */
+ ldb_msg_remove_attr(new_msg, "objectGUID");
+
+ ret = ldb_modify(samdb, new_msg);
+ if (ret != LDB_SUCCESS) {
+ TALLOC_FREE(new_msg);
+ *error_string =
+ talloc_asprintf(mem_ctx,
+ "Failed to modify dns record "
+ "in zone %s: %s",
+ ldb_dn_get_linearized(zone->dn),
+ ldb_errstring(samdb));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ TALLOC_FREE(new_msg);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * Tombstone all expired DNS records.
+ */
+NTSTATUS dns_tombstone_records(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ char **error_string)
+{
+ struct dns_server_zone *zones = NULL;
+ struct dns_server_zone *z = NULL;
+ NTSTATUS ret;
+ uint32_t dns_timestamp;
+ NTTIME entombed_time;
+ TALLOC_CTX *tmp_ctx = NULL;
+ time_t unix_now = time(NULL);
+
+ unix_to_nt_time(&entombed_time, unix_now);
+ dns_timestamp = unix_to_dns_timestamp(unix_now);
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dns_common_zones(samdb, tmp_ctx, NULL, &zones);
+ if (!NT_STATUS_IS_OK(ret)) {
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+ }
+
+ for (z = zones; z; z = z->next) {
+ ret = dns_tombstone_records_zone(tmp_ctx,
+ samdb,
+ z,
+ dns_timestamp,
+ entombed_time,
+ error_string);
+ if (NT_STATUS_EQUAL(ret, NT_STATUS_PROPSET_NOT_FOUND)) {
+ continue;
+ } else if (!NT_STATUS_IS_OK(ret)) {
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+ }
+ }
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
+/*
+ * Delete all DNS tombstones that have been around for longer than the server
+ * property 'dns_tombstone_interval' which we store in smb.conf, which
+ * corresponds to DsTombstoneInterval in [MS-DNSP] 3.1.1.1.1 "DNS Server
+ * Integer Properties".
+ */
+NTSTATUS dns_delete_tombstones(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ char **error_string)
+{
+ struct dns_server_zone *zones = NULL;
+ struct dns_server_zone *z = NULL;
+ int ret, i;
+ NTSTATUS status;
+ uint32_t current_time;
+ uint32_t tombstone_interval;
+ uint32_t tombstone_hours;
+ NTTIME tombstone_nttime;
+ enum ndr_err_code ndr_err;
+ struct ldb_result *res = NULL;
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct loadparm_context *lp_ctx = NULL;
+ struct ldb_message_element *el = NULL;
+ struct dnsp_DnssrvRpcRecord rec = {0};
+ const char *attrs[] = {"dnsRecord", "dNSTombstoned", NULL};
+
+ current_time = unix_to_dns_timestamp(time(NULL));
+
+ lp_ctx = (struct loadparm_context *)ldb_get_opaque(samdb, "loadparm");
+ tombstone_interval = lpcfg_parm_ulong(lp_ctx, NULL,
+ "dnsserver",
+ "dns_tombstone_interval",
+ 24 * 14);
+
+ tombstone_hours = current_time - tombstone_interval;
+ status = dns_timestamp_to_nt_time(&tombstone_nttime,
+ tombstone_hours);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("DNS timestamp exceeds NTTIME epoch.\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ status = dns_common_zones(samdb, tmp_ctx, NULL, &zones);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(tmp_ctx);
+ return status;
+ }
+
+ for (z = zones; z; z = z->next) {
+ /*
+ * This can load a very large set, but on the
+ * assumption that the number of tombstones is
+ * relatively small compared with the number of active
+ * records, and that this is an indexed lookup, this
+ * should be OK. We can make a match rule if
+ * returning the set of tombstones becomes an issue.
+ */
+
+ ret = ldb_search(samdb,
+ tmp_ctx,
+ &res,
+ z->dn,
+ LDB_SCOPE_SUBTREE,
+ attrs,
+ "(&(objectClass=dnsNode)(dNSTombstoned=TRUE))");
+
+ if (ret != LDB_SUCCESS) {
+ *error_string =
+ talloc_asprintf(mem_ctx,
+ "Failed to "
+ "search for tombstoned "
+ "dns objects in zone %s: %s",
+ ldb_dn_get_linearized(z->dn),
+ ldb_errstring(samdb));
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ for (i = 0; i < res->count; i++) {
+ struct ldb_message *msg = res->msgs[i];
+ el = ldb_msg_find_element(msg, "dnsRecord");
+ if (el == NULL) {
+ DBG_ERR("The tombstoned dns node %s has no dns "
+ "records, which should not happen.\n",
+ ldb_dn_get_linearized(msg->dn)
+ );
+ continue;
+ }
+ /*
+ * Below we assume the element has one value, which we
+ * expect because when we tombstone a node we remove
+ * all the records except for the tombstone.
+ */
+ if (el->num_values != 1) {
+ DBG_ERR("The tombstoned dns node %s has %u "
+ "dns records, expected one.\n",
+ ldb_dn_get_linearized(msg->dn),
+ el->num_values
+ );
+ continue;
+ }
+
+ ndr_err = ndr_pull_struct_blob(
+ el->values,
+ tmp_ctx,
+ &rec,
+ (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ TALLOC_FREE(tmp_ctx);
+ DBG_ERR("Failed to pull dns rec blob.\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (rec.wType != DNS_TYPE_TOMBSTONE) {
+ DBG_ERR("A tombstoned dnsNode has non-tombstoned"
+ " records, which should not happen.\n");
+ continue;
+ }
+
+ if (rec.data.EntombedTime > tombstone_nttime) {
+ continue;
+ }
+ /*
+ * Between 4.9 and 4.14 in some places we saved the
+ * tombstone time as hours since the start of 1601,
+ * not in NTTIME ten-millionths of a second units.
+ *
+ * We can accommodate these bad values by noting that
+ * all the realistic timestamps in that measurement
+ * fall within the first *second* of NTTIME, that is,
+ * before 1601-01-01 00:00:01; and that these
+ * timestamps are not realistic for NTTIME timestamps.
+ *
+ * Calculation: there are roughly 365.25 * 24 = 8766
+ * hours per year, and < 500 years since 1601, so
+ * 4383000 would be a fine threshold. We round up to
+ * the crore-second (c. 2741CE) in honour of NTTIME.
+ */
+ if ((rec.data.EntombedTime < 10000000) &&
+ (rec.data.EntombedTime > tombstone_hours)) {
+ continue;
+ }
+
+ ret = dsdb_delete(samdb, msg->dn, 0);
+ if (ret != LDB_ERR_NO_SUCH_OBJECT &&
+ ret != LDB_SUCCESS) {
+ TALLOC_FREE(tmp_ctx);
+ DBG_ERR("Failed to delete dns node \n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ }
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_OK;
+}
diff --git a/source4/dsdb/kcc/scavenge_dns_records.h b/source4/dsdb/kcc/scavenge_dns_records.h
new file mode 100644
index 0000000..e065fed
--- /dev/null
+++ b/source4/dsdb/kcc/scavenge_dns_records.h
@@ -0,0 +1,36 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ DNS tombstoning routines
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+#include "param/param.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/common/util.h"
+#include "dns_server/dnsserver_common.h"
+
+NTSTATUS dns_tombstone_records(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ char **error_string);
+
+NTSTATUS dns_delete_tombstones(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ char **error_string);
+NTSTATUS remove_expired_records(TALLOC_CTX *mem_ctx,
+ struct ldb_message_element *el,
+ NTTIME t);