diff options
Diffstat (limited to '')
-rw-r--r-- | source4/dsdb/kcc/garbage_collect_tombstones.c | 347 | ||||
-rw-r--r-- | source4/dsdb/kcc/garbage_collect_tombstones.h | 34 | ||||
-rw-r--r-- | source4/dsdb/kcc/kcc_connection.c | 252 | ||||
-rw-r--r-- | source4/dsdb/kcc/kcc_connection.h | 39 | ||||
-rw-r--r-- | source4/dsdb/kcc/kcc_drs_replica_info.c | 911 | ||||
-rw-r--r-- | source4/dsdb/kcc/kcc_periodic.c | 825 | ||||
-rw-r--r-- | source4/dsdb/kcc/kcc_service.c | 364 | ||||
-rw-r--r-- | source4/dsdb/kcc/kcc_service.h | 101 | ||||
-rw-r--r-- | source4/dsdb/kcc/scavenge_dns_records.c | 531 | ||||
-rw-r--r-- | source4/dsdb/kcc/scavenge_dns_records.h | 36 |
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..d8d0a59 --- /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; + } + + DEBUG(1, ("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) { + DEBUG(1,(__location__ ": Failed to remove " + "deleted object %s\n", + ldb_dn_get_linearized(res-> + msgs[i]->dn))); + } else { + DEBUG(4,("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) { + DEBUG(1, ("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)) { + DEBUG(1, ("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)) { + DEBUG(1, ("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) { + DEBUG(1,(__location__ ": Failed to remove deleted object %s\n", + ldb_dn_get_linearized(res->msgs[i]->dn))); + } else { + DEBUG(4,("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 databse 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..f387dfc --- /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")); + return NULL; + } + list->servers = talloc_array(list, struct kcc_connection, + res->count); + if (!list->servers) { + DEBUG(0, ("out of memory")); + 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..edd0710 --- /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", + 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", + 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\"", + "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\"", + "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\"", + "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", + 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", + 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..428f21d --- /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"); + 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", + err); + return status; + } + } + DBG_INFO("Successfully tombstoned stale DNS records"); + 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", err); + return status; + } + } + DBG_INFO("Successfully deleted DNS tombstones"); + 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..0e64741 --- /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 timstamp 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..65cf552 --- /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 accomodate 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); |