1/* modrdn.cpp - ndb backend modrdn routine */
2/* OpenLDAP: pkg/ldap/servers/slapd/back-ndb/modrdn.cpp,v 1.3.2.3 2010/04/13 20:23:35 kurt Exp */
3/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
4 *
5 * Copyright 2008-2010 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
28int
29ndb_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 ) {
78retry:	/* 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
520return_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