Line data Source code
1 : /*
2 : ldb database library
3 :
4 : Copyright (C) Kamen Mazdrashki <kamenim@samba.org> 2014
5 :
6 : This program is free software; you can redistribute it and/or modify
7 : it under the terms of the GNU General Public License as published by
8 : the Free Software Foundation; either version 3 of the License, or
9 : (at your option) any later version.
10 :
11 : This program is distributed in the hope that it will be useful,
12 : but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : GNU General Public License for more details.
15 :
16 : You should have received a copy of the GNU General Public License
17 : along with this program. If not, see <http://www.gnu.org/licenses/>.
18 : */
19 :
20 : /*
21 : * Name: tombstone_reanimate
22 : *
23 : * Component: Handle Tombstone reanimation requests
24 : *
25 : * Description:
26 : * Tombstone reanimation requests are plain ldap modify request like:
27 : * dn: CN=tombi 1\0ADEL:e6e17ff7-8986-4cdd-87ad-afb683ccbb89,CN=Deleted Objects,DC=samba4,DC=devel
28 : * changetype: modify
29 : * delete: isDeleted
30 : * -
31 : * replace: distinguishedName
32 : * distinguishedName: CN=Tombi 1,CN=Users,DC=samba4,DC=devel
33 : * -
34 : *
35 : * Usually we don't allow distinguishedName modifications (see rdn_name.c)
36 : * Reanimating Tombstones is described here:
37 : * - http://msdn.microsoft.com/en-us/library/cc223467.aspx
38 : *
39 : * Author: Kamen Mazdrashki
40 : */
41 :
42 :
43 : #include "includes.h"
44 : #include "ldb_module.h"
45 : #include "dsdb/samdb/samdb.h"
46 : #include "librpc/ndr/libndr.h"
47 : #include "librpc/gen_ndr/ndr_security.h"
48 : #include "libcli/security/security.h"
49 : #include "auth/auth.h"
50 : #include "param/param.h"
51 : #include "../libds/common/flags.h"
52 : #include "dsdb/samdb/ldb_modules/util.h"
53 : #include "libds/common/flag_mapping.h"
54 :
55 : struct tr_context {
56 : struct ldb_module *module;
57 :
58 : struct ldb_request *req;
59 : const struct ldb_message *req_msg;
60 :
61 : struct ldb_result *search_res;
62 : const struct ldb_message *search_msg;
63 :
64 : struct ldb_message *mod_msg;
65 : struct ldb_result *mod_res;
66 : struct ldb_request *mod_req;
67 :
68 : struct ldb_dn *rename_dn;
69 : struct ldb_result *rename_res;
70 : struct ldb_request *rename_req;
71 :
72 : const struct dsdb_schema *schema;
73 : };
74 :
75 274 : static struct tr_context *tr_init_context(struct ldb_module *module,
76 : struct ldb_request *req)
77 : {
78 274 : struct ldb_context *ldb = ldb_module_get_ctx(module);
79 0 : struct tr_context *ac;
80 :
81 274 : ac = talloc_zero(req, struct tr_context);
82 274 : if (ac == NULL) {
83 0 : ldb_oom(ldb);
84 0 : return NULL;
85 : }
86 :
87 274 : ac->module = module;
88 274 : ac->req = req;
89 274 : ac->req_msg = req->op.mod.message;
90 274 : ac->schema = dsdb_get_schema(ldb, ac);
91 :
92 274 : return ac;
93 : }
94 :
95 :
96 320649 : static bool is_tombstone_reanimate_request(struct ldb_request *req,
97 : const struct ldb_message_element **pel_dn)
98 : {
99 17149 : struct ldb_message_element *el_dn;
100 17149 : struct ldb_message_element *el_deleted;
101 :
102 : /* check distinguishedName requirement */
103 320649 : el_dn = ldb_msg_find_element(req->op.mod.message, "distinguishedName");
104 320649 : if (el_dn == NULL) {
105 303223 : return false;
106 : }
107 277 : if (LDB_FLAG_MOD_TYPE(el_dn->flags) != LDB_FLAG_MOD_REPLACE) {
108 2 : return false;
109 : }
110 275 : if (el_dn->num_values != 1) {
111 0 : return false;
112 : }
113 :
114 : /* check isDeleted requirement */
115 275 : el_deleted = ldb_msg_find_element(req->op.mod.message, "isDeleted");
116 275 : if (el_deleted == NULL) {
117 1 : return false;
118 : }
119 :
120 274 : if (LDB_FLAG_MOD_TYPE(el_deleted->flags) != LDB_FLAG_MOD_DELETE) {
121 0 : return false;
122 : }
123 :
124 274 : *pel_dn = el_dn;
125 274 : return true;
126 : }
127 :
128 : /**
129 : * Local rename implementation based on dsdb_module_rename()
130 : * so we could fine tune it and add more controls
131 : */
132 274 : static int tr_prepare_rename(struct tr_context *ac,
133 : const struct ldb_message_element *new_dn)
134 : {
135 274 : struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
136 0 : int ret;
137 :
138 274 : ac->rename_dn = ldb_dn_from_ldb_val(ac, ldb, &new_dn->values[0]);
139 274 : if (ac->rename_dn == NULL) {
140 0 : return ldb_module_oom(ac->module);
141 : }
142 :
143 274 : ac->rename_res = talloc_zero(ac, struct ldb_result);
144 274 : if (ac->rename_res == NULL) {
145 0 : return ldb_module_oom(ac->module);
146 : }
147 :
148 274 : ret = ldb_build_rename_req(&ac->rename_req, ldb, ac,
149 274 : ac->req_msg->dn,
150 : ac->rename_dn,
151 : NULL,
152 274 : ac->rename_res,
153 : ldb_modify_default_callback,
154 : ac->req);
155 274 : LDB_REQ_SET_LOCATION(ac->rename_req);
156 274 : if (ret != LDB_SUCCESS) {
157 0 : return ret;
158 : }
159 :
160 274 : return ret;
161 : }
162 :
163 : /**
164 : * Local rename implementation based on dsdb_module_modify()
165 : * so we could fine tune it and add more controls
166 : */
167 539 : static int tr_do_down_req(struct tr_context *ac, struct ldb_request *down_req)
168 : {
169 0 : int ret;
170 :
171 : /* We need this since object is 'delete' atm */
172 539 : ret = ldb_request_add_control(down_req,
173 : LDB_CONTROL_SHOW_DELETED_OID,
174 : false, NULL);
175 539 : if (ret != LDB_SUCCESS) {
176 0 : return ret;
177 : }
178 :
179 : /* mark request as part of Tombstone reanimation */
180 539 : ret = ldb_request_add_control(down_req,
181 : DSDB_CONTROL_RESTORE_TOMBSTONE_OID,
182 : false, NULL);
183 539 : if (ret != LDB_SUCCESS) {
184 0 : return ret;
185 : }
186 :
187 : /* Run request from Next module */
188 539 : ret = ldb_next_request(ac->module, down_req);
189 539 : if (ret == LDB_SUCCESS) {
190 503 : ret = ldb_wait(down_req->handle, LDB_WAIT_ALL);
191 : }
192 :
193 539 : return ret;
194 : }
195 :
196 274 : static int tr_prepare_attributes(struct tr_context *ac)
197 : {
198 274 : struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
199 0 : int ret;
200 274 : struct ldb_message_element *el = NULL;
201 0 : uint32_t account_type, user_account_control;
202 274 : struct ldb_dn *objectcategory = NULL;
203 :
204 274 : ac->mod_msg = ldb_msg_copy_shallow(ac, ac->req_msg);
205 274 : if (ac->mod_msg == NULL) {
206 0 : return ldb_oom(ldb);
207 : }
208 :
209 274 : ac->mod_res = talloc_zero(ac, struct ldb_result);
210 274 : if (ac->mod_res == NULL) {
211 0 : return ldb_oom(ldb);
212 : }
213 :
214 274 : ret = ldb_build_mod_req(&ac->mod_req, ldb, ac,
215 274 : ac->mod_msg,
216 : NULL,
217 274 : ac->mod_res,
218 : ldb_modify_default_callback,
219 : ac->req);
220 274 : LDB_REQ_SET_LOCATION(ac->mod_req);
221 274 : if (ret != LDB_SUCCESS) {
222 0 : return ret;
223 : }
224 :
225 : /* - remove distinguishedName - we don't need it */
226 274 : ldb_msg_remove_attr(ac->mod_msg, "distinguishedName");
227 :
228 : /* remove isRecycled */
229 274 : ret = ldb_msg_add_empty(ac->mod_msg, "isRecycled",
230 : LDB_FLAG_MOD_DELETE, NULL);
231 274 : if (ret != LDB_SUCCESS) {
232 0 : ldb_asprintf_errstring(ldb, "Failed to reset isRecycled attribute: %s", ldb_strerror(ret));
233 0 : return LDB_ERR_OPERATIONS_ERROR;
234 : }
235 :
236 : /* objectClass is USER */
237 274 : if (samdb_find_attribute(ldb, ac->search_msg, "objectclass", "user") != NULL) {
238 0 : uint32_t primary_group_rid;
239 : /* restoring 'user' instance attribute is heavily borrowed from samldb.c */
240 :
241 : /* Default values */
242 60 : ret = dsdb_user_obj_set_defaults(ldb, ac->mod_msg, ac->mod_req);
243 60 : if (ret != LDB_SUCCESS) return ret;
244 :
245 : /* "userAccountControl" must exists on deleted object */
246 60 : user_account_control = ldb_msg_find_attr_as_uint(ac->search_msg,
247 : "userAccountControl",
248 : (uint32_t)-1);
249 60 : if (user_account_control == (uint32_t)-1) {
250 0 : return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR,
251 : "reanimate: No 'userAccountControl' attribute found!");
252 : }
253 :
254 : /* restore "sAMAccountType" */
255 60 : ret = dsdb_user_obj_set_account_type(ldb, ac->mod_msg,
256 : user_account_control, NULL);
257 60 : if (ret != LDB_SUCCESS) {
258 0 : return ret;
259 : }
260 :
261 : /* "userAccountControl" -> "primaryGroupID" mapping */
262 60 : ret = dsdb_user_obj_set_primary_group_id(ldb, ac->mod_msg,
263 : user_account_control,
264 : &primary_group_rid);
265 60 : if (ret != LDB_SUCCESS) {
266 0 : return ret;
267 : }
268 : /*
269 : * Older AD deployments don't know about the
270 : * RODC group
271 : */
272 60 : if (primary_group_rid == DOMAIN_RID_READONLY_DCS) {
273 : /* TODO: check group exists */
274 0 : }
275 :
276 : }
277 :
278 : /* objectClass is GROUP */
279 274 : if (samdb_find_attribute(ldb, ac->search_msg, "objectclass", "group") != NULL) {
280 : /* "groupType" -> "sAMAccountType" */
281 0 : uint32_t group_type;
282 :
283 2 : el = ldb_msg_find_element(ac->search_msg, "groupType");
284 2 : if (el == NULL) {
285 0 : return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR,
286 : "reanimate: Unexpected: missing groupType attribute.");
287 : }
288 :
289 2 : group_type = ldb_msg_find_attr_as_uint(ac->search_msg,
290 : "groupType", 0);
291 :
292 2 : account_type = ds_gtype2atype(group_type);
293 2 : if (account_type == 0) {
294 0 : return ldb_error(ldb, LDB_ERR_UNWILLING_TO_PERFORM,
295 : "reanimate: Unrecognized account type!");
296 : }
297 2 : ret = samdb_msg_append_uint(ldb, ac->mod_msg, ac->mod_msg,
298 : "sAMAccountType", account_type,
299 : LDB_FLAG_MOD_REPLACE);
300 2 : if (ret != LDB_SUCCESS) {
301 0 : return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR,
302 : "reanimate: Failed to add sAMAccountType to restored object.");
303 : }
304 :
305 : /* Default values set by Windows */
306 2 : ret = samdb_find_or_add_attribute(ldb, ac->mod_msg,
307 : "adminCount", "0");
308 2 : if (ret != LDB_SUCCESS) return ret;
309 2 : ret = samdb_find_or_add_attribute(ldb, ac->mod_msg,
310 : "operatorCount", "0");
311 2 : if (ret != LDB_SUCCESS) return ret;
312 : }
313 :
314 : /* - restore objectCategory if not present */
315 274 : objectcategory = ldb_msg_find_attr_as_dn(ldb, ac, ac->search_msg,
316 : "objectCategory");
317 274 : if (objectcategory == NULL) {
318 0 : const char *value;
319 :
320 274 : ret = dsdb_make_object_category(ldb, ac->schema, ac->search_msg,
321 274 : ac->mod_msg, &value);
322 274 : if (ret != LDB_SUCCESS) {
323 0 : return ret;
324 : }
325 :
326 274 : ret = ldb_msg_append_string(ac->mod_msg, "objectCategory", value,
327 : LDB_FLAG_MOD_ADD);
328 274 : if (ret != LDB_SUCCESS) {
329 0 : return ret;
330 : }
331 : }
332 :
333 274 : return LDB_SUCCESS;
334 : }
335 :
336 : /**
337 : * Handle special LDAP modify request to restore deleted objects
338 : */
339 321364 : static int tombstone_reanimate_modify(struct ldb_module *module, struct ldb_request *req)
340 : {
341 321364 : struct ldb_context *ldb = ldb_module_get_ctx(module);
342 321364 : const struct ldb_message_element *el_dn = NULL;
343 321364 : struct tr_context *ac = NULL;
344 17207 : int ret;
345 :
346 321364 : ldb_debug(ldb, LDB_DEBUG_TRACE, "%s\n", __PRETTY_FUNCTION__);
347 :
348 : /* do not manipulate our control entries */
349 321364 : if (ldb_dn_is_special(req->op.mod.message->dn)) {
350 715 : return ldb_next_request(module, req);
351 : }
352 :
353 : /* Check if this is a reanimate request */
354 320649 : if (!is_tombstone_reanimate_request(req, &el_dn)) {
355 320375 : return ldb_next_request(module, req);
356 : }
357 :
358 274 : ac = tr_init_context(module, req);
359 274 : if (ac == NULL) {
360 0 : return ldb_operr(ldb);
361 : }
362 :
363 : /* Load original object */
364 274 : ret = dsdb_module_search_dn(module, ac, &ac->search_res,
365 274 : ac->req_msg->dn, NULL,
366 : DSDB_FLAG_TOP_MODULE |
367 : DSDB_SEARCH_SHOW_DELETED,
368 : req);
369 274 : if (ret != LDB_SUCCESS) {
370 0 : return ldb_operr(ldb);
371 : }
372 274 : ac->search_msg = ac->search_res->msgs[0];
373 :
374 : /* check if it a Deleted Object */
375 274 : if (!ldb_msg_find_attr_as_bool(ac->search_msg, "isDeleted", false)) {
376 0 : return ldb_error(ldb, LDB_ERR_UNWILLING_TO_PERFORM, "Trying to restore not deleted object\n");
377 : }
378 :
379 : /* Simple implementation */
380 :
381 : /* prepare attributed depending on objectClass */
382 274 : ret = tr_prepare_attributes(ac);
383 274 : if (ret != LDB_SUCCESS) {
384 0 : return ret;
385 : }
386 :
387 : /* Rename request to modify distinguishedName */
388 274 : ret = tr_prepare_rename(ac, el_dn);
389 274 : if (ret != LDB_SUCCESS) {
390 0 : return ret;
391 : }
392 :
393 : /* restore attributed depending on objectClass */
394 274 : ret = tr_do_down_req(ac, ac->mod_req);
395 274 : if (ret != LDB_SUCCESS) {
396 9 : return ret;
397 : }
398 :
399 : /* Rename request to modify distinguishedName */
400 265 : ret = tr_do_down_req(ac, ac->rename_req);
401 265 : if (ret != LDB_SUCCESS) {
402 30 : ldb_debug(ldb, LDB_DEBUG_ERROR, "Renaming object to %s has failed with %s\n", el_dn->values[0].data, ldb_strerror(ret));
403 30 : if (ret != LDB_ERR_ENTRY_ALREADY_EXISTS && ret != LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS ) {
404 : /* Windows returns Operations Error in case we can't rename the object */
405 2 : return LDB_ERR_OPERATIONS_ERROR;
406 : }
407 28 : return ret;
408 : }
409 :
410 235 : return ldb_module_done(ac->req, NULL, NULL, LDB_SUCCESS);
411 : }
412 :
413 :
414 : static const struct ldb_module_ops ldb_reanimate_module_ops = {
415 : .name = "tombstone_reanimate",
416 : .modify = tombstone_reanimate_modify,
417 : };
418 :
419 6040 : int ldb_tombstone_reanimate_module_init(const char *version)
420 : {
421 6040 : LDB_MODULE_CHECK_VERSION(version);
422 6040 : return ldb_register_module(&ldb_reanimate_module_ops);
423 : }
|