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 : }
|