1 /* modrdn.cpp - ndb backend modrdn routine */
2 /* $OpenLDAP$ */
3 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
4  *
5  * Copyright 2008-2021 The OpenLDAP Foundation.
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted only as authorized by the OpenLDAP
10  * Public License.
11  *
12  * A copy of this license is available in the file LICENSE in the
13  * top-level directory of the distribution or, alternatively, at
14  * <http://www.OpenLDAP.org/license.html>.
15  */
16 /* ACKNOWLEDGEMENTS:
17  * This work was initially developed by Howard Chu for inclusion
18  * in OpenLDAP Software. This work was sponsored by MySQL.
19  */
20 
21 #include "portable.h"
22 
23 #include <stdio.h>
24 #include <ac/string.h>
25 
26 #include "back-ndb.h"
27 
28 int
ndb_back_modrdn(Operation * op,SlapReply * rs)29 ndb_back_modrdn( Operation *op, SlapReply *rs )
30 {
31 	struct ndb_info *ni = (struct ndb_info *) op->o_bd->be_private;
32 	AttributeDescription *children = slap_schema.si_ad_children;
33 	AttributeDescription *entry = slap_schema.si_ad_entry;
34 	struct berval	new_dn = BER_BVNULL, new_ndn = BER_BVNULL;
35 	Entry		e = {0};
36 	Entry		e2 = {0};
37 	char textbuf[SLAP_TEXT_BUFLEN];
38 	size_t textlen = sizeof textbuf;
39 
40 	struct berval	*np_dn = NULL;			/* newSuperior dn */
41 	struct berval	*np_ndn = NULL;			/* newSuperior ndn */
42 
43 	int		manageDSAit = get_manageDSAit( op );
44 	int		num_retries = 0;
45 
46 	NdbArgs NA, NA2;
47 	NdbRdns rdns, rdn2;
48 	struct berval matched;
49 
50 	LDAPControl **preread_ctrl = NULL;
51 	LDAPControl **postread_ctrl = NULL;
52 	LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS];
53 	int num_ctrls = 0;
54 
55 	int	rc;
56 
57 	Debug( LDAP_DEBUG_ARGS, "==>" LDAP_XSTRING(ndb_back_modrdn) "(%s,%s,%s)\n",
58 		op->o_req_dn.bv_val,op->oq_modrdn.rs_newrdn.bv_val,
59 		op->oq_modrdn.rs_newSup ? op->oq_modrdn.rs_newSup->bv_val : "NULL" );
60 
61 	ctrls[num_ctrls] = NULL;
62 
63 	slap_mods_opattrs( op, &op->orr_modlist, 1 );
64 
65 	e.e_name = op->o_req_dn;
66 	e.e_nname = op->o_req_ndn;
67 
68 	/* Get our NDB handle */
69 	rs->sr_err = ndb_thread_handle( op, &NA.ndb );
70 	rdns.nr_num = 0;
71 	NA.rdns = &rdns;
72 	NA.e = &e;
73 	NA2.ndb = NA.ndb;
74 	NA2.e = &e2;
75 	NA2.rdns = &rdn2;
76 
77 	if( 0 ) {
78 retry:	/* transaction retry */
79 		NA.txn->close();
80 		NA.txn = NULL;
81 		if ( e.e_attrs ) {
82 			attrs_free( e.e_attrs );
83 			e.e_attrs = NULL;
84 		}
85 		Debug( LDAP_DEBUG_TRACE, "==>" LDAP_XSTRING(ndb_back_modrdn)
86 				": retrying...\n", 0, 0, 0 );
87 		if ( op->o_abandon ) {
88 			rs->sr_err = SLAPD_ABANDON;
89 			goto return_results;
90 		}
91 		if ( NA2.ocs ) {
92 			ber_bvarray_free_x( NA2.ocs, op->o_tmpmemctx );
93 		}
94 		if ( NA.ocs ) {
95 			ber_bvarray_free_x( NA.ocs, op->o_tmpmemctx );
96 		}
97 		ndb_trans_backoff( ++num_retries );
98 	}
99 	NA.ocs = NULL;
100 	NA2.ocs = NULL;
101 
102 	/* begin transaction */
103 	NA.txn = NA.ndb->startTransaction();
104 	rs->sr_text = NULL;
105 	if( !NA.txn ) {
106 		Debug( LDAP_DEBUG_TRACE,
107 			LDAP_XSTRING(ndb_back_modrdn) ": startTransaction failed: %s (%d)\n",
108 			NA.ndb->getNdbError().message, NA.ndb->getNdbError().code, 0 );
109 		rs->sr_err = LDAP_OTHER;
110 		rs->sr_text = "internal error";
111 		goto return_results;
112 	}
113 	NA2.txn = NA.txn;
114 
115 	/* get entry */
116 	rs->sr_err = ndb_entry_get_info( op, &NA, 1, &matched );
117 	switch( rs->sr_err ) {
118 	case 0:
119 		break;
120 	case LDAP_NO_SUCH_OBJECT:
121 		Debug( LDAP_DEBUG_ARGS,
122 			"<=- ndb_back_modrdn: no such object %s\n",
123 			op->o_req_dn.bv_val, 0, 0 );
124 		rs->sr_matched = matched.bv_val;
125 		if ( NA.ocs )
126 			ndb_check_referral( op, rs, &NA );
127 		goto return_results;
128 #if 0
129 	case DB_LOCK_DEADLOCK:
130 	case DB_LOCK_NOTGRANTED:
131 		goto retry;
132 #endif
133 	case LDAP_BUSY:
134 		rs->sr_text = "ldap server busy";
135 		goto return_results;
136 	default:
137 		rs->sr_err = LDAP_OTHER;
138 		rs->sr_text = "internal error";
139 		goto return_results;
140 	}
141 
142 	/* acquire and lock entry */
143 	rs->sr_err = ndb_entry_get_data( op, &NA, 1 );
144 	if ( rs->sr_err )
145 		goto return_results;
146 
147 	if ( !manageDSAit && is_entry_glue( &e )) {
148 		rs->sr_err = LDAP_NO_SUCH_OBJECT;
149 		goto return_results;
150 	}
151 
152 	if ( get_assert( op ) &&
153 		( test_filter( op, &e, (Filter *)get_assertion( op )) != LDAP_COMPARE_TRUE ))
154 	{
155 		rs->sr_err = LDAP_ASSERTION_FAILED;
156 		goto return_results;
157 	}
158 
159 	/* check write on old entry */
160 	rs->sr_err = access_allowed( op, &e, entry, NULL, ACL_WRITE, NULL );
161 	if ( ! rs->sr_err ) {
162 		Debug( LDAP_DEBUG_TRACE, "no access to entry\n", 0,
163 			0, 0 );
164 		rs->sr_text = "no write access to old entry";
165 		rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
166 		goto return_results;
167 	}
168 
169 	/* Can't do it if we have kids */
170 	rs->sr_err = ndb_has_children( &NA, &rc );
171 	if ( rs->sr_err ) {
172 		Debug(LDAP_DEBUG_ARGS,
173 			"<=- " LDAP_XSTRING(ndb_back_modrdn)
174 			": has_children failed: %s (%d)\n",
175 			NA.txn->getNdbError().message, NA.txn->getNdbError().code, 0 );
176 		rs->sr_err = LDAP_OTHER;
177 		rs->sr_text = "internal error";
178 		goto return_results;
179 	}
180 	if ( rc == LDAP_COMPARE_TRUE ) {
181 		Debug(LDAP_DEBUG_ARGS,
182 			"<=- " LDAP_XSTRING(ndb_back_modrdn)
183 			": non-leaf %s\n",
184 			op->o_req_dn.bv_val, 0, 0);
185 		rs->sr_err = LDAP_NOT_ALLOWED_ON_NONLEAF;
186 		rs->sr_text = "subtree rename not supported";
187 		goto return_results;
188 	}
189 
190 	if (!manageDSAit && is_entry_referral( &e ) ) {
191 		/* entry is a referral, don't allow modrdn */
192 		rs->sr_ref = get_entry_referrals( op, &e );
193 
194 		Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(ndb_back_modrdn)
195 			": entry %s is referral\n", e.e_dn, 0, 0 );
196 
197 		rs->sr_err = LDAP_REFERRAL,
198 		rs->sr_matched = op->o_req_dn.bv_val;
199 		rs->sr_flags = REP_REF_MUSTBEFREED;
200 		goto return_results;
201 	}
202 
203 	if ( be_issuffix( op->o_bd, &e.e_nname ) ) {
204 		/* There can only be one suffix entry */
205 		rs->sr_err = LDAP_NAMING_VIOLATION;
206 		rs->sr_text = "cannot rename suffix entry";
207 		goto return_results;
208 	} else {
209 		dnParent( &e.e_nname, &e2.e_nname );
210 		dnParent( &e.e_name, &e2.e_name );
211 	}
212 
213 	/* check parent for "children" acl */
214 	rs->sr_err = access_allowed( op, &e2,
215 		children, NULL,
216 		op->oq_modrdn.rs_newSup == NULL ?
217 			ACL_WRITE : ACL_WDEL,
218 		NULL );
219 
220 	if ( ! rs->sr_err ) {
221 		rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
222 		Debug( LDAP_DEBUG_TRACE, "no access to parent\n", 0,
223 			0, 0 );
224 		rs->sr_text = "no write access to old parent's children";
225 		goto return_results;
226 	}
227 
228 	Debug( LDAP_DEBUG_TRACE,
229 		LDAP_XSTRING(ndb_back_modrdn) ": wr to children "
230 		"of entry %s OK\n", e2.e_name.bv_val, 0, 0 );
231 
232 	if ( op->oq_modrdn.rs_newSup != NULL ) {
233 		Debug( LDAP_DEBUG_TRACE,
234 			LDAP_XSTRING(ndb_back_modrdn)
235 			": new parent \"%s\" requested...\n",
236 			op->oq_modrdn.rs_newSup->bv_val, 0, 0 );
237 
238 		/*  newSuperior == oldParent? */
239 		if( dn_match( &e2.e_nname, op->oq_modrdn.rs_nnewSup ) ) {
240 			Debug( LDAP_DEBUG_TRACE, "bdb_back_modrdn: "
241 				"new parent \"%s\" same as the old parent \"%s\"\n",
242 				op->oq_modrdn.rs_newSup->bv_val, e2.e_name.bv_val, 0 );
243 			op->oq_modrdn.rs_newSup = NULL; /* ignore newSuperior */
244 		}
245 	}
246 
247 	if ( op->oq_modrdn.rs_newSup != NULL ) {
248 		if ( op->oq_modrdn.rs_newSup->bv_len ) {
249 			rdn2.nr_num = 0;
250 			np_dn = op->oq_modrdn.rs_newSup;
251 			np_ndn = op->oq_modrdn.rs_nnewSup;
252 
253 			/* newSuperior == oldParent? - checked above */
254 			/* newSuperior == entry being moved?, if so ==> ERROR */
255 			if ( dnIsSuffix( np_ndn, &e.e_nname )) {
256 				rs->sr_err = LDAP_NO_SUCH_OBJECT;
257 				rs->sr_text = "new superior not found";
258 				goto return_results;
259 			}
260 			/* Get Entry with dn=newSuperior. Does newSuperior exist? */
261 
262 			e2.e_name = *np_dn;
263 			e2.e_nname = *np_ndn;
264 			rs->sr_err = ndb_entry_get_info( op, &NA2, 1, NULL );
265 			switch( rs->sr_err ) {
266 			case 0:
267 				break;
268 			case LDAP_NO_SUCH_OBJECT:
269 				Debug( LDAP_DEBUG_TRACE,
270 					LDAP_XSTRING(ndb_back_modrdn)
271 					": newSup(ndn=%s) not here!\n",
272 					np_ndn->bv_val, 0, 0);
273 				rs->sr_text = "new superior not found";
274 				goto return_results;
275 #if 0
276 			case DB_LOCK_DEADLOCK:
277 			case DB_LOCK_NOTGRANTED:
278 				goto retry;
279 #endif
280 			case LDAP_BUSY:
281 				rs->sr_text = "ldap server busy";
282 				goto return_results;
283 			default:
284 				rs->sr_err = LDAP_OTHER;
285 				rs->sr_text = "internal error";
286 				goto return_results;
287 			}
288 			if ( NA2.ocs ) {
289 				Attribute a;
290 				int i;
291 
292 				for ( i=0; !BER_BVISNULL( &NA2.ocs[i] ); i++);
293 				a.a_numvals = i;
294 				a.a_desc = slap_schema.si_ad_objectClass;
295 				a.a_vals = NA2.ocs;
296 				a.a_nvals = NA2.ocs;
297 				a.a_next = NULL;
298 				e2.e_attrs = &a;
299 
300 				if ( is_entry_alias( &e2 )) {
301 					/* parent is an alias, don't allow move */
302 					Debug( LDAP_DEBUG_TRACE,
303 						LDAP_XSTRING(ndb_back_modrdn)
304 						": entry is alias\n",
305 						0, 0, 0 );
306 					rs->sr_text = "new superior is an alias";
307 					rs->sr_err = LDAP_ALIAS_PROBLEM;
308 					goto return_results;
309 				}
310 
311 				if ( is_entry_referral( &e2 ) ) {
312 					/* parent is a referral, don't allow move */
313 					Debug( LDAP_DEBUG_TRACE,
314 						LDAP_XSTRING(ndb_back_modrdn)
315 						": entry is referral\n",
316 						0, 0, 0 );
317 					rs->sr_text = "new superior is a referral";
318 					rs->sr_err = LDAP_OTHER;
319 					goto return_results;
320 				}
321 			}
322 		}
323 
324 		/* check newSuperior for "children" acl */
325 		rs->sr_err = access_allowed( op, &e2, children,
326 			NULL, ACL_WADD, NULL );
327 		if( ! rs->sr_err ) {
328 			Debug( LDAP_DEBUG_TRACE,
329 				LDAP_XSTRING(ndb_back_modrdn)
330 				": no wr to newSup children\n",
331 				0, 0, 0 );
332 			rs->sr_text = "no write access to new superior's children";
333 			rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
334 			goto return_results;
335 		}
336 
337 		Debug( LDAP_DEBUG_TRACE,
338 			LDAP_XSTRING(ndb_back_modrdn)
339 			": wr to new parent OK id=%ld\n",
340 			(long) e2.e_id, 0, 0 );
341 	}
342 
343 	/* Build target dn and make sure target entry doesn't exist already. */
344 	if (!new_dn.bv_val) {
345 		build_new_dn( &new_dn, &e2.e_name, &op->oq_modrdn.rs_newrdn, NULL );
346 	}
347 
348 	if (!new_ndn.bv_val) {
349 		build_new_dn( &new_ndn, &e2.e_nname, &op->oq_modrdn.rs_nnewrdn, NULL );
350 	}
351 
352 	Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(ndb_back_modrdn) ": new ndn=%s\n",
353 		new_ndn.bv_val, 0, 0 );
354 
355 	/* Allow rename to same DN */
356 	if ( !bvmatch ( &new_ndn, &e.e_nname )) {
357 		rdn2.nr_num = 0;
358 		e2.e_name = new_dn;
359 		e2.e_nname = new_ndn;
360 		NA2.ocs = &matched;
361 		rs->sr_err = ndb_entry_get_info( op, &NA2, 1, NULL );
362 		NA2.ocs = NULL;
363 		switch( rs->sr_err ) {
364 #if 0
365 		case DB_LOCK_DEADLOCK:
366 		case DB_LOCK_NOTGRANTED:
367 			goto retry;
368 #endif
369 		case LDAP_NO_SUCH_OBJECT:
370 			break;
371 		case 0:
372 			rs->sr_err = LDAP_ALREADY_EXISTS;
373 			goto return_results;
374 		default:
375 			rs->sr_err = LDAP_OTHER;
376 			rs->sr_text = "internal error";
377 			goto return_results;
378 		}
379 	}
380 
381 	assert( op->orr_modlist != NULL );
382 
383 	if( op->o_preread ) {
384 		if( preread_ctrl == NULL ) {
385 			preread_ctrl = &ctrls[num_ctrls++];
386 			ctrls[num_ctrls] = NULL;
387 		}
388 		if( slap_read_controls( op, rs, &e,
389 			&slap_pre_read_bv, preread_ctrl ) )
390 		{
391 			Debug( LDAP_DEBUG_TRACE,
392 				"<=- " LDAP_XSTRING(ndb_back_modrdn)
393 				": pre-read failed!\n", 0, 0, 0 );
394 			if ( op->o_preread & SLAP_CONTROL_CRITICAL ) {
395 				/* FIXME: is it correct to abort
396 				 * operation if control fails? */
397 				goto return_results;
398 			}
399 		}
400 	}
401 
402 	/* delete old DN */
403 	rs->sr_err = ndb_entry_del_info( op->o_bd, &NA );
404 	if ( rs->sr_err != 0 ) {
405 		Debug(LDAP_DEBUG_TRACE,
406 			"<=- " LDAP_XSTRING(ndb_back_modrdn)
407 			": dn2id del failed: %s (%d)\n",
408 			NA.txn->getNdbError().message, NA.txn->getNdbError().code, 0 );
409 #if 0
410 		switch( rs->sr_err ) {
411 		case DB_LOCK_DEADLOCK:
412 		case DB_LOCK_NOTGRANTED:
413 			goto retry;
414 		}
415 #endif
416 		rs->sr_err = LDAP_OTHER;
417 		rs->sr_text = "DN index delete fail";
418 		goto return_results;
419 	}
420 
421 	/* copy entry fields */
422 	e2.e_attrs = e.e_attrs;
423 	e2.e_id = e.e_id;
424 
425 	/* add new DN */
426 	rs->sr_err = ndb_entry_put_info( op->o_bd, &NA2, 0 );
427 	if ( rs->sr_err != 0 ) {
428 		Debug(LDAP_DEBUG_TRACE,
429 			"<=- " LDAP_XSTRING(ndb_back_modrdn)
430 			": dn2id add failed: %s (%d)\n",
431 			NA.txn->getNdbError().message, NA.txn->getNdbError().code, 0 );
432 #if 0
433 		switch( rs->sr_err ) {
434 		case DB_LOCK_DEADLOCK:
435 		case DB_LOCK_NOTGRANTED:
436 			goto retry;
437 		}
438 #endif
439 		rs->sr_err = LDAP_OTHER;
440 		rs->sr_text = "DN index add failed";
441 		goto return_results;
442 	}
443 
444 	/* modify entry */
445 	rs->sr_err = ndb_modify_internal( op, &NA2,
446 		&rs->sr_text, textbuf, textlen );
447 	if( rs->sr_err != LDAP_SUCCESS ) {
448 		Debug(LDAP_DEBUG_TRACE,
449 			"<=- " LDAP_XSTRING(ndb_back_modrdn)
450 			": modify failed: %s (%d)\n",
451 			NA.txn->getNdbError().message, NA.txn->getNdbError().code, 0 );
452 #if 0
453 		switch( rs->sr_err ) {
454 		case DB_LOCK_DEADLOCK:
455 		case DB_LOCK_NOTGRANTED:
456 			goto retry;
457 		}
458 #endif
459 		goto return_results;
460 	}
461 
462 	e.e_attrs = e2.e_attrs;
463 
464 	if( op->o_postread ) {
465 		if( postread_ctrl == NULL ) {
466 			postread_ctrl = &ctrls[num_ctrls++];
467 			ctrls[num_ctrls] = NULL;
468 		}
469 		if( slap_read_controls( op, rs, &e2,
470 			&slap_post_read_bv, postread_ctrl ) )
471 		{
472 			Debug( LDAP_DEBUG_TRACE,
473 				"<=- " LDAP_XSTRING(ndb_back_modrdn)
474 				": post-read failed!\n", 0, 0, 0 );
475 			if ( op->o_postread & SLAP_CONTROL_CRITICAL ) {
476 				/* FIXME: is it correct to abort
477 				 * operation if control fails? */
478 				goto return_results;
479 			}
480 		}
481 	}
482 
483 	if( op->o_noop ) {
484 		if (( rs->sr_err=NA.txn->execute( NdbTransaction::Rollback,
485 			NdbOperation::AbortOnError, 1 )) != 0 ) {
486 			rs->sr_text = "txn_abort (no-op) failed";
487 		} else {
488 			rs->sr_err = LDAP_X_NO_OPERATION;
489 		}
490 	} else {
491 		if (( rs->sr_err=NA.txn->execute( NdbTransaction::Commit,
492 			NdbOperation::AbortOnError, 1 )) != 0 ) {
493 			rs->sr_text = "txn_commit failed";
494 		} else {
495 			rs->sr_err = LDAP_SUCCESS;
496 		}
497 	}
498 
499 	if( rs->sr_err != LDAP_SUCCESS && rs->sr_err != LDAP_X_NO_OPERATION ) {
500 		Debug( LDAP_DEBUG_TRACE,
501 			LDAP_XSTRING(ndb_back_modrdn) ": txn_%s failed: %s (%d)\n",
502 			op->o_noop ? "abort (no-op)" : "commit",
503 			NA.txn->getNdbError().message, NA.txn->getNdbError().code );
504 		rs->sr_err = LDAP_OTHER;
505 		goto return_results;
506 	}
507 	NA.txn->close();
508 	NA.txn = NULL;
509 
510 	Debug(LDAP_DEBUG_TRACE,
511 		LDAP_XSTRING(ndb_back_modrdn)
512 		": rdn modified%s id=%08lx dn=\"%s\"\n",
513 		op->o_noop ? " (no-op)" : "",
514 		e.e_id, op->o_req_dn.bv_val );
515 
516 	rs->sr_err = LDAP_SUCCESS;
517 	rs->sr_text = NULL;
518 	if( num_ctrls ) rs->sr_ctrls = ctrls;
519 
520 return_results:
521 	if ( NA2.ocs ) {
522 		ber_bvarray_free_x( NA2.ocs, op->o_tmpmemctx );
523 		NA2.ocs = NULL;
524 	}
525 
526 	if ( NA.ocs ) {
527 		ber_bvarray_free_x( NA.ocs, op->o_tmpmemctx );
528 		NA.ocs = NULL;
529 	}
530 
531 	if ( e.e_attrs ) {
532 		attrs_free( e.e_attrs );
533 		e.e_attrs = NULL;
534 	}
535 
536 	if( NA.txn != NULL ) {
537 		NA.txn->execute( Rollback );
538 		NA.txn->close();
539 	}
540 
541 	send_ldap_result( op, rs );
542 	slap_graduate_commit_csn( op );
543 
544 	if( new_dn.bv_val != NULL ) free( new_dn.bv_val );
545 	if( new_ndn.bv_val != NULL ) free( new_ndn.bv_val );
546 
547 	if( preread_ctrl != NULL && (*preread_ctrl) != NULL ) {
548 		slap_sl_free( (*preread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx );
549 		slap_sl_free( *preread_ctrl, op->o_tmpmemctx );
550 	}
551 	if( postread_ctrl != NULL && (*postread_ctrl) != NULL ) {
552 		slap_sl_free( (*postread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx );
553 		slap_sl_free( *postread_ctrl, op->o_tmpmemctx );
554 	}
555 
556 	rs->sr_text = NULL;
557 	return rs->sr_err;
558 }
559