LCOV - code coverage report
Current view: top level - source4/dsdb/kcc - garbage_collect_tombstones.c (source / functions) Hit Total Coverage
Test: coverage report for master 2f515e9b Lines: 114 136 83.8 %
Date: 2024-04-21 15:09:00 Functions: 2 2 100.0 %

          Line data    Source code
       1             : /*
       2             :    Unix SMB/CIFS implementation.
       3             : 
       4             :    handle removal of deleted objects
       5             : 
       6             :    Copyright (C) 2009 Andrew Tridgell
       7             :    Copyright (C) 2016 Andrew Bartlett
       8             :    Copyright (C) 2016 Catalyst.NET Ltd
       9             : 
      10             :    This program is free software; you can redistribute it and/or modify
      11             :    it under the terms of the GNU General Public License as published by
      12             :    the Free Software Foundation; either version 3 of the License, or
      13             :    (at your option) any later version.
      14             : 
      15             :    This program is distributed in the hope that it will be useful,
      16             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
      17             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      18             :    GNU General Public License for more details.
      19             : 
      20             :    You should have received a copy of the GNU General Public License
      21             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.
      22             : 
      23             : */
      24             : 
      25             : #include "includes.h"
      26             : #include <ldb_errors.h>
      27             : #include "../lib/util/dlinklist.h"
      28             : #include "librpc/gen_ndr/ndr_misc.h"
      29             : #include "librpc/gen_ndr/ndr_drsuapi.h"
      30             : #include "librpc/gen_ndr/ndr_drsblobs.h"
      31             : #include "param/param.h"
      32             : #include "lib/util/dlinklist.h"
      33             : #include "ldb.h"
      34             : #include "dsdb/kcc/garbage_collect_tombstones.h"
      35             : #include "lib/ldb-samba/ldb_matching_rules.h"
      36             : #include "lib/util/time.h"
      37             : 
      38         303 : static NTSTATUS garbage_collect_tombstones_part(TALLOC_CTX *mem_ctx,
      39             :                                                 struct ldb_context *samdb,
      40             :                                                 struct dsdb_ldb_dn_list_node *part,
      41             :                                                 char *filter,
      42             :                                                 unsigned int *num_links_removed,
      43             :                                                 unsigned int *num_objects_removed,
      44             :                                                 struct dsdb_schema *schema,
      45             :                                                 const char **attrs,
      46             :                                                 char **error_string,
      47             :                                                 NTTIME expunge_time_nttime)
      48             : {
      49          15 :         int ret;
      50          15 :         struct ldb_dn *do_dn;
      51          15 :         struct ldb_result *res;
      52          15 :         unsigned int i, j, k;
      53          15 :         uint32_t flags;
      54         303 :         TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
      55         303 :         if (!tmp_ctx) {
      56           0 :                 return NT_STATUS_NO_MEMORY;
      57             :         }
      58             : 
      59         303 :         ret = dsdb_get_deleted_objects_dn(samdb, tmp_ctx, part->dn, &do_dn);
      60         303 :         if (ret != LDB_SUCCESS) {
      61          62 :                 TALLOC_FREE(tmp_ctx);
      62             :                 /* some partitions have no Deleted Objects
      63             :                    container */
      64          62 :                 return NT_STATUS_OK;
      65             :         }
      66             : 
      67         241 :         DBG_INFO("Doing a full scan on %s and looking for deleted objects\n",
      68             :                   ldb_dn_get_linearized(part->dn));
      69             : 
      70         241 :         flags = DSDB_SEARCH_SHOW_RECYCLED |
      71             :                 DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT |
      72             :                 DSDB_SEARCH_REVEAL_INTERNALS;
      73         241 :         ret = dsdb_search(samdb, tmp_ctx, &res, part->dn, LDB_SCOPE_SUBTREE,
      74             :                           attrs, flags, "%s", filter);
      75             : 
      76         241 :         if (ret != LDB_SUCCESS) {
      77           0 :                 *error_string = talloc_asprintf(mem_ctx,
      78             :                                                 "Failed to search for deleted "
      79             :                                                 "objects in %s: %s",
      80             :                                                 ldb_dn_get_linearized(do_dn),
      81             :                                                 ldb_errstring(samdb));
      82           0 :                 TALLOC_FREE(tmp_ctx);
      83           0 :                 return NT_STATUS_INTERNAL_ERROR;
      84             :         }
      85             : 
      86         270 :         for (i=0; i<res->count; i++) {
      87          29 :                 struct ldb_message *cleanup_msg = NULL;
      88          29 :                 unsigned int num_modified = 0;
      89             : 
      90          29 :                 bool isDeleted = ldb_msg_find_attr_as_bool(res->msgs[i],
      91             :                                                            "isDeleted", false);
      92          29 :                 if (isDeleted) {
      93          24 :                         if (ldb_dn_compare(do_dn, res->msgs[i]->dn) == 0) {
      94             :                                 /* Skip the Deleted Object Container */
      95          13 :                                 continue;
      96             :                         }
      97             : 
      98          11 :                         ret = dsdb_delete(samdb, res->msgs[i]->dn,
      99             :                                           DSDB_SEARCH_SHOW_RECYCLED
     100             :                                           |DSDB_MODIFY_RELAX);
     101          11 :                         if (ret != LDB_SUCCESS) {
     102           0 :                                 DBG_WARNING(__location__ ": Failed to remove "
     103             :                                          "deleted object %s\n",
     104             :                                          ldb_dn_get_linearized(res->
     105             :                                                                msgs[i]->dn));
     106             :                         } else {
     107          11 :                                 DBG_INFO("Removed deleted object %s\n",
     108             :                                          ldb_dn_get_linearized(res->
     109             :                                                                msgs[i]->dn));
     110          11 :                                 (*num_objects_removed)++;
     111             :                         }
     112          11 :                         continue;
     113             :                 }
     114             : 
     115             :                 /* This must have a linked attribute */
     116             : 
     117             :                 /*
     118             :                  * From MS-ADTS 3.1.1.1.9 DCs, usn Counters, and
     119             :                  * the Originating Update Stamp
     120             :                  *
     121             :                  * "A link value r is deleted, but exists as a
     122             :                  *  tombstone, if r.stamp.timeDeleted ≠ 0. When
     123             :                  *  the current time minus r.stamp.timeDeleted
     124             :                  *  exceeds the tombstone lifetime, the link
     125             :                  *  value r is garbage-collected; that is,
     126             :                  *  removed from its containing forward link
     127             :                  *  attribute. "
     128             :                  */
     129             : 
     130          10 :                 for (j=0; j < res->msgs[i]->num_elements; j++) {
     131           5 :                         struct ldb_message_element *element = NULL;
     132             :                         /* TODO this is O(log n) per attribute with deleted values */
     133           5 :                         const struct dsdb_attribute *attrib = NULL;
     134             : 
     135           5 :                         element = &res->msgs[i]->elements[j];
     136           5 :                         attrib = dsdb_attribute_by_lDAPDisplayName(schema,
     137             :                                                                    element->name);
     138             : 
     139             :                         /* This avoids parsing isDeleted as a link */
     140           5 :                         if (attrib == NULL ||
     141           5 :                             attrib->linkID == 0 ||
     142           5 :                             ((attrib->linkID & 1) == 1)) {
     143           0 :                                 continue;
     144             :                         }
     145             : 
     146          19 :                         for (k = 0; k < element->num_values; k++) {
     147          14 :                                 struct ldb_val *value = &element->values[k];
     148          14 :                                 uint64_t whenChanged = 0;
     149          12 :                                 NTSTATUS status;
     150          12 :                                 struct dsdb_dn *dn;
     151          14 :                                 struct ldb_message_element *cleanup_elem = NULL;
     152          14 :                                 char *guid_search_str = NULL;
     153          14 :                                 char *guid_buf_str = NULL;
     154          12 :                                 struct ldb_val cleanup_val;
     155          12 :                                 struct GUID_txt_buf buf_guid;
     156          12 :                                 struct GUID guid;
     157          12 :                                 const struct ldb_val *guid_blob;
     158             : 
     159          14 :                                 if (dsdb_dn_is_deleted_val(value) == false) {
     160           9 :                                         continue;
     161             :                                 }
     162             : 
     163           8 :                                 dn = dsdb_dn_parse(tmp_ctx, samdb,
     164           5 :                                                    &element->values[k],
     165           5 :                                                    attrib->syntax->ldap_oid);
     166           5 :                                 if (dn == NULL) {
     167           0 :                                         DBG_WARNING("Failed to parse linked attribute blob of "
     168             :                                                   "%s on %s while expunging expired links\n",
     169             :                                                   element->name,
     170             :                                                   ldb_dn_get_linearized(res->msgs[i]->dn));
     171           0 :                                         continue;
     172             :                                 }
     173             : 
     174           5 :                                 status = dsdb_get_extended_dn_uint64(dn->dn,
     175             :                                                                      &whenChanged,
     176             :                                                                      "RMD_CHANGETIME");
     177           5 :                                 if (!NT_STATUS_IS_OK(status)) {
     178           0 :                                         DBG_WARNING("Error: RMD_CHANGETIME is missing on a forward link.\n");
     179           0 :                                         talloc_free(dn);
     180           0 :                                         continue;
     181             :                                 }
     182             : 
     183           5 :                                 if (whenChanged >= expunge_time_nttime) {
     184           0 :                                         talloc_free(dn);
     185           0 :                                         continue;
     186             :                                 }
     187             : 
     188           5 :                                 guid_blob = ldb_dn_get_extended_component(dn->dn, "GUID");
     189           5 :                                 status = GUID_from_ndr_blob(guid_blob, &guid);
     190           5 :                                 if (!NT_STATUS_IS_OK(status)) {
     191           0 :                                         DBG_WARNING("Error: Invalid GUID on link target.\n");
     192           0 :                                         talloc_free(dn);
     193           0 :                                         continue;
     194             :                                 }
     195             : 
     196           5 :                                 guid_buf_str = GUID_buf_string(&guid, &buf_guid);
     197           5 :                                 guid_search_str = talloc_asprintf(mem_ctx,
     198             :                                                                   "<GUID=%s>;%s",
     199             :                                                                   guid_buf_str,
     200             :                                                                   dsdb_dn_get_linearized(mem_ctx, dn));
     201           5 :                                 cleanup_val = data_blob_string_const(guid_search_str);
     202             : 
     203           5 :                                 talloc_free(dn);
     204             : 
     205           5 :                                 if (cleanup_msg == NULL) {
     206           5 :                                         cleanup_msg = ldb_msg_new(mem_ctx);
     207           5 :                                         if (cleanup_msg == NULL) {
     208           0 :                                                 return NT_STATUS_NO_MEMORY;
     209             :                                         }
     210           5 :                                         cleanup_msg->dn = res->msgs[i]->dn;
     211             :                                 }
     212             : 
     213           5 :                                 ret = ldb_msg_add_value(cleanup_msg,
     214             :                                                         element->name,
     215             :                                                         &cleanup_val,
     216             :                                                         &cleanup_elem);
     217           5 :                                 if (ret != LDB_SUCCESS) {
     218           0 :                                         return NT_STATUS_NO_MEMORY;
     219             :                                 }
     220           5 :                                 cleanup_elem->flags = LDB_FLAG_MOD_DELETE;
     221           5 :                                 num_modified++;
     222             :                         }
     223             :                 }
     224             : 
     225           5 :                 if (num_modified > 0) {
     226           5 :                         ret = dsdb_modify(samdb, cleanup_msg,
     227             :                                           DSDB_REPLMD_VANISH_LINKS);
     228           5 :                         if (ret != LDB_SUCCESS) {
     229           0 :                                 DBG_WARNING(__location__ ": Failed to remove deleted object %s\n",
     230             :                                          ldb_dn_get_linearized(res->msgs[i]->dn));
     231             :                         } else {
     232           5 :                                 DBG_INFO("Removed deleted object %s\n",
     233             :                                          ldb_dn_get_linearized(res->msgs[i]->dn));
     234           5 :                                 *num_links_removed = *num_links_removed + num_modified;
     235             :                         }
     236             : 
     237             :                 }
     238             :         }
     239             : 
     240         241 :         TALLOC_FREE(tmp_ctx);
     241         241 :         return NT_STATUS_OK;
     242             : }
     243             : 
     244             : /*
     245             :  * Per MS-ADTS 3.1.1.5.5 Delete Operation
     246             :  *
     247             :  * "Tombstones are a type of deleted object distinguished from
     248             :  *  existing-objects by the presence of the isDeleted attribute with the
     249             :  *  value true."
     250             :  *
     251             :  * "After a time period at least as large as a tombstone lifetime, the
     252             :  *  tombstone is removed from the directory."
     253             :  *
     254             :  * The purpose of this routine is to remove such objects.  It is
     255             :  * called from a timed event in the KCC, and from samba-tool domain
     256             :  * expunge tombstones.
     257             :  *
     258             :  * Additionally, linked attributes have similar properties.
     259             :  */
     260          63 : NTSTATUS dsdb_garbage_collect_tombstones(TALLOC_CTX *mem_ctx,
     261             :                                          struct ldb_context *samdb,
     262             :                                          struct dsdb_ldb_dn_list_node *part,
     263             :                                          time_t current_time,
     264             :                                          uint32_t tombstoneLifetime,
     265             :                                          unsigned int *num_objects_removed,
     266             :                                          unsigned int *num_links_removed,
     267             :                                          char **error_string)
     268             : {
     269          63 :         const char **attrs = NULL;
     270          63 :         char *filter = NULL;
     271           3 :         NTSTATUS status;
     272           3 :         unsigned int i;
     273           3 :         struct dsdb_attribute *next_attr;
     274           3 :         unsigned int num_link_attrs;
     275          63 :         struct dsdb_schema *schema = dsdb_get_schema(samdb, mem_ctx);
     276          63 :         unsigned long long expunge_time = current_time - tombstoneLifetime*60*60*24;
     277          63 :         char *expunge_time_string = ldb_timestring_utc(mem_ctx, expunge_time);
     278           3 :         NTTIME expunge_time_nttime;
     279          63 :         unix_to_nt_time(&expunge_time_nttime, expunge_time);
     280             : 
     281          63 :         *num_objects_removed = 0;
     282          63 :         *num_links_removed = 0;
     283          63 :         *error_string = NULL;
     284          63 :         num_link_attrs = 0;
     285             : 
     286             :         /*
     287             :          * This filter is a bit strange, but the idea is to filter for
     288             :          * objects that need to have tombstones expunged without
     289             :          * bringing a potentially large database all into memory.  To
     290             :          * do that, we could use callbacks, but instead we use a
     291             :          * custom match rule to triage the objects during the search,
     292             :          * and ideally avoid memory allocation for most of the
     293             :          * un-matched objects.
     294             :          *
     295             :          * The parameter to DSDB_MATCH_FOR_EXPUNGE is the NTTIME, we
     296             :          * return records with deleted links deleted before this time.
     297             :          *
     298             :          * We use a date comparison on whenChanged to avoid returning
     299             :          * all isDeleted records
     300             :          */
     301             : 
     302          63 :         filter = talloc_asprintf(mem_ctx, "(|");
     303       88952 :         for (next_attr = schema->attributes; next_attr != NULL; next_attr = next_attr->next) {
     304       88889 :                 if (next_attr->linkID != 0 && ((next_attr->linkID & 1) == 0)) {
     305        4152 :                         num_link_attrs++;
     306        4152 :                         filter = talloc_asprintf_append(filter,
     307             :                                                         "(%s:" DSDB_MATCH_FOR_EXPUNGE ":=%llu)",
     308             :                                                         next_attr->lDAPDisplayName,
     309             :                                                         (unsigned long long)expunge_time_nttime);
     310        4152 :                         if (filter == NULL) {
     311           0 :                                 return NT_STATUS_NO_MEMORY;
     312             :                         }
     313             :                 }
     314             :         }
     315             : 
     316          63 :         attrs = talloc_array(mem_ctx, const char *, num_link_attrs + 2);
     317          63 :         i = 0;
     318       88952 :         for (next_attr = schema->attributes; next_attr != NULL; next_attr = next_attr->next) {
     319       88889 :                 if (next_attr->linkID != 0 && ((next_attr->linkID & 1) == 0)) {
     320        4152 :                         attrs[i++] = next_attr->lDAPDisplayName;
     321             :                 }
     322             :         }
     323          63 :         attrs[i] = "isDeleted";
     324          63 :         attrs[i+1] = NULL;
     325             : 
     326          63 :         filter = talloc_asprintf_append(filter,
     327             :                                         "(&(isDeleted=TRUE)(whenChanged<=%s)))",
     328             :                                         expunge_time_string);
     329          63 :         if (filter == NULL) {
     330           0 :                 return NT_STATUS_NO_MEMORY;
     331             :         }
     332             : 
     333         366 :         for (; part != NULL; part = part->next) {
     334         303 :                 status = garbage_collect_tombstones_part(mem_ctx, samdb, part,
     335             :                                                          filter,
     336             :                                                          num_links_removed,
     337             :                                                          num_objects_removed,
     338             :                                                          schema, attrs,
     339             :                                                          error_string,
     340             :                                                          expunge_time_nttime);
     341         303 :                 if (!NT_STATUS_IS_OK(status)) {
     342           0 :                         return status;
     343             :                 }
     344             :         }
     345             : 
     346          63 :         return NT_STATUS_OK;
     347             : }

Generated by: LCOV version 1.14