1/*	$NetBSD: add.c,v 1.1.1.3 2010/12/12 15:22:16 adam Exp $	*/
2
3/* OpenLDAP: pkg/ldap/servers/slapd/add.c,v 1.244.2.10 2010/04/19 16:53:01 quanah Exp */
4/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
5 *
6 * Copyright 1998-2010 The OpenLDAP Foundation.
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted only as authorized by the OpenLDAP
11 * Public License.
12 *
13 * A copy of this license is available in the file LICENSE in the
14 * top-level directory of the distribution or, alternatively, at
15 * <http://www.OpenLDAP.org/license.html>.
16 */
17/* Portions Copyright (c) 1995 Regents of the University of Michigan.
18 * All rights reserved.
19 *
20 * Redistribution and use in source and binary forms are permitted
21 * provided that this notice is preserved and that due credit is given
22 * to the University of Michigan at Ann Arbor. The name of the University
23 * may not be used to endorse or promote products derived from this
24 * software without specific prior written permission. This software
25 * is provided ``as is'' without express or implied warranty.
26 */
27
28#include "portable.h"
29
30#include <stdio.h>
31#include <ac/string.h>
32#include <ac/time.h>
33#include <ac/socket.h>
34
35#include "lutil.h"
36#include "slap.h"
37
38int
39do_add( Operation *op, SlapReply *rs )
40{
41	BerElement	*ber = op->o_ber;
42	char		*last;
43	struct berval	dn = BER_BVNULL;
44	ber_len_t	len;
45	ber_tag_t	tag;
46	Modifications	*modlist = NULL;
47	Modifications	**modtail = &modlist;
48	Modifications	tmp;
49	char		textbuf[ SLAP_TEXT_BUFLEN ];
50	size_t		textlen = sizeof( textbuf );
51	int		rc = 0;
52	int		freevals = 1;
53	OpExtraDB oex;
54
55	Debug( LDAP_DEBUG_TRACE, "%s do_add\n",
56		op->o_log_prefix, 0, 0 );
57
58	/*
59	 * Parse the add request.  It looks like this:
60	 *
61	 *	AddRequest := [APPLICATION 14] SEQUENCE {
62	 *		name	DistinguishedName,
63	 *		attrs	SEQUENCE OF SEQUENCE {
64	 *			type	AttributeType,
65	 *			values	SET OF AttributeValue
66	 *		}
67	 *	}
68	 */
69
70	/* get the name */
71	if ( ber_scanf( ber, "{m", /*}*/ &dn ) == LBER_ERROR ) {
72		Debug( LDAP_DEBUG_ANY, "%s do_add: ber_scanf failed\n",
73			op->o_log_prefix, 0, 0 );
74		send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" );
75		return SLAPD_DISCONNECT;
76	}
77
78	Debug( LDAP_DEBUG_ARGS, "%s do_add: dn (%s)\n",
79		op->o_log_prefix, dn.bv_val, 0 );
80
81	/* get the attrs */
82	for ( tag = ber_first_element( ber, &len, &last ); tag != LBER_DEFAULT;
83	    tag = ber_next_element( ber, &len, last ) )
84	{
85		Modifications *mod;
86		ber_tag_t rtag;
87
88		tmp.sml_nvalues = NULL;
89
90		rtag = ber_scanf( ber, "{m{W}}", &tmp.sml_type, &tmp.sml_values );
91
92		if ( rtag == LBER_ERROR ) {
93			Debug( LDAP_DEBUG_ANY, "%s do_add: decoding error\n",
94				op->o_log_prefix, 0, 0 );
95			send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" );
96			rs->sr_err = SLAPD_DISCONNECT;
97			goto done;
98		}
99
100		if ( tmp.sml_values == NULL ) {
101			Debug( LDAP_DEBUG_ANY, "%s do_add: no values for type %s\n",
102				op->o_log_prefix, tmp.sml_type.bv_val, 0 );
103			send_ldap_error( op, rs, LDAP_PROTOCOL_ERROR,
104				"no values for attribute type" );
105			goto done;
106		}
107
108		mod  = (Modifications *) ch_malloc( sizeof(Modifications) );
109		mod->sml_op = LDAP_MOD_ADD;
110		mod->sml_flags = 0;
111		mod->sml_next = NULL;
112		mod->sml_desc = NULL;
113		mod->sml_type = tmp.sml_type;
114		mod->sml_values = tmp.sml_values;
115		mod->sml_nvalues = NULL;
116
117		*modtail = mod;
118		modtail = &mod->sml_next;
119	}
120
121	if ( ber_scanf( ber, /*{*/ "}") == LBER_ERROR ) {
122		Debug( LDAP_DEBUG_ANY, "%s do_add: ber_scanf failed\n",
123			op->o_log_prefix, 0, 0 );
124		send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" );
125		rs->sr_err = SLAPD_DISCONNECT;
126		goto done;
127	}
128
129	if ( get_ctrls( op, rs, 1 ) != LDAP_SUCCESS ) {
130		Debug( LDAP_DEBUG_ANY, "%s do_add: get_ctrls failed\n",
131			op->o_log_prefix, 0, 0 );
132		goto done;
133	}
134
135	rs->sr_err = dnPrettyNormal( NULL, &dn, &op->o_req_dn, &op->o_req_ndn,
136		op->o_tmpmemctx );
137
138	if ( rs->sr_err != LDAP_SUCCESS ) {
139		Debug( LDAP_DEBUG_ANY, "%s do_add: invalid dn (%s)\n",
140			op->o_log_prefix, dn.bv_val, 0 );
141		send_ldap_error( op, rs, LDAP_INVALID_DN_SYNTAX, "invalid DN" );
142		goto done;
143	}
144
145	op->ora_e = entry_alloc();
146	ber_dupbv( &op->ora_e->e_name, &op->o_req_dn );
147	ber_dupbv( &op->ora_e->e_nname, &op->o_req_ndn );
148
149	Statslog( LDAP_DEBUG_STATS, "%s ADD dn=\"%s\"\n",
150	    op->o_log_prefix, op->o_req_dn.bv_val, 0, 0, 0 );
151
152	if ( modlist == NULL ) {
153		send_ldap_error( op, rs, LDAP_PROTOCOL_ERROR,
154			"no attributes provided" );
155		goto done;
156	}
157
158	if ( dn_match( &op->ora_e->e_nname, &slap_empty_bv ) ) {
159		/* protocolError may be a more appropriate error */
160		send_ldap_error( op, rs, LDAP_ALREADY_EXISTS,
161			"root DSE already exists" );
162		goto done;
163
164	} else if ( dn_match( &op->ora_e->e_nname, &frontendDB->be_schemandn ) ) {
165		send_ldap_error( op, rs, LDAP_ALREADY_EXISTS,
166			"subschema subentry already exists" );
167		goto done;
168	}
169
170	rs->sr_err = slap_mods_check( op, modlist, &rs->sr_text,
171		textbuf, textlen, NULL );
172
173	if ( rs->sr_err != LDAP_SUCCESS ) {
174		send_ldap_result( op, rs );
175		goto done;
176	}
177
178	/* temporary; remove if not invoking backend function */
179	op->ora_modlist = modlist;
180
181	/* call this so global overlays/SLAPI have access to ora_e */
182	rs->sr_err = slap_mods2entry( op->ora_modlist, &op->ora_e,
183		1, 0, &rs->sr_text, textbuf, textlen );
184	if ( rs->sr_err != LDAP_SUCCESS ) {
185		send_ldap_result( op, rs );
186		goto done;
187	}
188
189	freevals = 0;
190
191	oex.oe.oe_key = (void *)do_add;
192	oex.oe_db = NULL;
193	LDAP_SLIST_INSERT_HEAD(&op->o_extra, &oex.oe, oe_next);
194
195	op->o_bd = frontendDB;
196	rc = frontendDB->be_add( op, rs );
197	LDAP_SLIST_REMOVE(&op->o_extra, &oex.oe, OpExtra, oe_next);
198
199#ifdef LDAP_X_TXN
200	if ( rc == LDAP_X_TXN_SPECIFY_OKAY ) {
201		/* skip cleanup */
202		return rc;
203	} else
204#endif
205	if ( rc == 0 ) {
206		if ( op->ora_e != NULL && oex.oe_db != NULL ) {
207			BackendDB	*bd = op->o_bd;
208
209			op->o_bd = oex.oe_db;
210
211			be_entry_release_w( op, op->ora_e );
212
213			op->ora_e = NULL;
214			op->o_bd = bd;
215		}
216	}
217
218done:;
219	if ( modlist != NULL ) {
220		/* in case of error, free the values as well */
221		slap_mods_free( modlist, freevals );
222	}
223
224	if ( op->ora_e != NULL ) {
225		entry_free( op->ora_e );
226	}
227	op->o_tmpfree( op->o_req_dn.bv_val, op->o_tmpmemctx );
228	op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx );
229
230	return rc;
231}
232
233int
234fe_op_add( Operation *op, SlapReply *rs )
235{
236	Modifications	**modtail = &op->ora_modlist;
237	int		rc = 0;
238	BackendDB	*op_be, *bd = op->o_bd;
239	char		textbuf[ SLAP_TEXT_BUFLEN ];
240	size_t		textlen = sizeof( textbuf );
241
242	/*
243	 * We could be serving multiple database backends.  Select the
244	 * appropriate one, or send a referral to our "referral server"
245	 * if we don't hold it.
246	 */
247	op->o_bd = select_backend( &op->ora_e->e_nname, 1 );
248	if ( op->o_bd == NULL ) {
249		op->o_bd = bd;
250		rs->sr_ref = referral_rewrite( default_referral,
251			NULL, &op->ora_e->e_name, LDAP_SCOPE_DEFAULT );
252		if ( !rs->sr_ref ) rs->sr_ref = default_referral;
253		if ( rs->sr_ref ) {
254			rs->sr_err = LDAP_REFERRAL;
255			send_ldap_result( op, rs );
256
257			if ( rs->sr_ref != default_referral ) {
258				ber_bvarray_free( rs->sr_ref );
259			}
260		} else {
261			send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
262				"no global superior knowledge" );
263		}
264		goto done;
265	}
266
267	/* If we've got a glued backend, check the real backend */
268	op_be = op->o_bd;
269	if ( SLAP_GLUE_INSTANCE( op->o_bd )) {
270		op->o_bd = select_backend( &op->ora_e->e_nname, 0 );
271	}
272
273	/* check restrictions */
274	if( backend_check_restrictions( op, rs, NULL ) != LDAP_SUCCESS ) {
275		send_ldap_result( op, rs );
276		goto done;
277	}
278
279	/* check for referrals */
280	if( backend_check_referrals( op, rs ) != LDAP_SUCCESS ) {
281		goto done;
282	}
283
284	rs->sr_err = slap_mods_obsolete_check( op, op->ora_modlist,
285		&rs->sr_text, textbuf, textlen );
286
287	if ( rs->sr_err != LDAP_SUCCESS ) {
288		send_ldap_result( op, rs );
289		goto done;
290	}
291
292	/*
293	 * do the add if 1 && (2 || 3)
294	 * 1) there is an add function implemented in this backend;
295	 * 2) this backend is master for what it holds;
296	 * 3) it's a replica and the dn supplied is the updatedn.
297	 */
298	if ( op->o_bd->be_add ) {
299		/* do the update here */
300		int repl_user = be_isupdate( op );
301		if ( !SLAP_SINGLE_SHADOW(op->o_bd) || repl_user ) {
302			int		update = !BER_BVISEMPTY( &op->o_bd->be_update_ndn );
303
304			op->o_bd = op_be;
305
306			if ( !update ) {
307				rs->sr_err = slap_mods_no_user_mod_check( op, op->ora_modlist,
308					&rs->sr_text, textbuf, textlen );
309
310				if ( rs->sr_err != LDAP_SUCCESS ) {
311					send_ldap_result( op, rs );
312					goto done;
313				}
314			}
315
316			if ( !repl_user ) {
317				/* go to the last mod */
318				for ( modtail = &op->ora_modlist;
319						*modtail != NULL;
320						modtail = &(*modtail)->sml_next )
321				{
322					assert( (*modtail)->sml_op == LDAP_MOD_ADD );
323					assert( (*modtail)->sml_desc != NULL );
324				}
325
326
327				/* check for unmodifiable attributes */
328				rs->sr_err = slap_mods_no_repl_user_mod_check( op,
329					op->ora_modlist, &rs->sr_text, textbuf, textlen );
330				if ( rs->sr_err != LDAP_SUCCESS ) {
331					send_ldap_result( op, rs );
332					goto done;
333				}
334			}
335
336			rc = op->o_bd->be_add( op, rs );
337			if ( rc == LDAP_SUCCESS ) {
338				OpExtra *oex;
339				/* NOTE: be_entry_release_w() is
340				 * called by do_add(), so that global
341				 * overlays on the way back can
342				 * at least read the entry */
343				LDAP_SLIST_FOREACH(oex, &op->o_extra, oe_next) {
344					if ( oex->oe_key == (void *)do_add ) {
345						((OpExtraDB *)oex)->oe_db = op->o_bd;
346						break;
347					}
348				}
349			}
350
351		} else {
352			BerVarray defref = NULL;
353
354			defref = op->o_bd->be_update_refs
355				? op->o_bd->be_update_refs : default_referral;
356
357			if ( defref != NULL ) {
358				rs->sr_ref = referral_rewrite( defref,
359					NULL, &op->ora_e->e_name, LDAP_SCOPE_DEFAULT );
360				if ( rs->sr_ref == NULL ) rs->sr_ref = defref;
361				rs->sr_err = LDAP_REFERRAL;
362				if (!rs->sr_ref) rs->sr_ref = default_referral;
363				send_ldap_result( op, rs );
364
365				if ( rs->sr_ref != default_referral ) {
366					ber_bvarray_free( rs->sr_ref );
367				}
368			} else {
369				send_ldap_error( op, rs,
370					LDAP_UNWILLING_TO_PERFORM,
371					"shadow context; no update referral" );
372			}
373		}
374	} else {
375		Debug( LDAP_DEBUG_ARGS, "do_add: no backend support\n", 0, 0, 0 );
376		send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
377			"operation not supported within namingContext" );
378	}
379
380done:;
381	op->o_bd = bd;
382	return rc;
383}
384
385int
386slap_mods2entry(
387	Modifications *mods,
388	Entry **e,
389	int initial,
390	int dup,
391	const char **text,
392	char *textbuf, size_t textlen )
393{
394	Attribute **tail;
395	int i;
396
397	if ( initial ) {
398		assert( (*e)->e_attrs == NULL );
399	}
400
401	for ( tail = &(*e)->e_attrs; *tail != NULL; tail = &(*tail)->a_next )
402		;
403
404	*text = textbuf;
405
406	for( ; mods != NULL; mods = mods->sml_next ) {
407		Attribute *attr;
408
409		assert( mods->sml_desc != NULL );
410
411		attr = attr_find( (*e)->e_attrs, mods->sml_desc );
412
413		if( attr != NULL ) {
414#define SLURPD_FRIENDLY
415#ifdef SLURPD_FRIENDLY
416			int j;
417
418			if ( !initial ) {
419				/*
420				 * This check allows overlays to override operational
421				 * attributes by setting them directly in the entry.
422				 * We assume slap_mods_no_user_mod_check() was called
423				 * with the user modifications.
424				 */
425				*text = NULL;
426				return LDAP_SUCCESS;
427			}
428
429			i = attr->a_numvals;
430			j = mods->sml_numvals;
431			attr->a_numvals += j;
432			j++;	/* NULL */
433
434			attr->a_vals = ch_realloc( attr->a_vals,
435				sizeof( struct berval ) * (i+j) );
436
437			/* checked for duplicates in slap_mods_check */
438
439			if ( dup ) {
440				for ( j = 0; mods->sml_values[j].bv_val; j++ ) {
441					ber_dupbv( &attr->a_vals[i+j], &mods->sml_values[j] );
442				}
443				BER_BVZERO( &attr->a_vals[i+j] );
444				j++;
445			} else {
446				AC_MEMCPY( &attr->a_vals[i], mods->sml_values,
447					sizeof( struct berval ) * j );
448			}
449
450			if( mods->sml_nvalues ) {
451				attr->a_nvals = ch_realloc( attr->a_nvals,
452					sizeof( struct berval ) * (i+j) );
453				if ( dup ) {
454					for ( j = 0; mods->sml_nvalues[j].bv_val; j++ ) {
455						ber_dupbv( &attr->a_nvals[i+j], &mods->sml_nvalues[j] );
456					}
457					BER_BVZERO( &attr->a_nvals[i+j] );
458				} else {
459					AC_MEMCPY( &attr->a_nvals[i], mods->sml_nvalues,
460						sizeof( struct berval ) * j );
461				}
462			} else {
463				attr->a_nvals = attr->a_vals;
464			}
465
466			continue;
467#else
468			snprintf( textbuf, textlen,
469				"attribute '%s' provided more than once",
470				mods->sml_desc->ad_cname.bv_val );
471			*text = textbuf;
472			return LDAP_TYPE_OR_VALUE_EXISTS;
473#endif
474		}
475
476		attr = attr_alloc( mods->sml_desc );
477
478		/* move values to attr structure */
479		i = mods->sml_numvals;
480		attr->a_numvals = mods->sml_numvals;
481		if ( dup ) {
482			attr->a_vals = (BerVarray) ch_calloc( i+1, sizeof( BerValue ));
483			for ( i = 0; mods->sml_values[i].bv_val; i++ ) {
484				ber_dupbv( &attr->a_vals[i], &mods->sml_values[i] );
485			}
486			BER_BVZERO( &attr->a_vals[i] );
487		} else {
488			attr->a_vals = mods->sml_values;
489		}
490
491		if ( mods->sml_nvalues ) {
492			if ( dup ) {
493				i = mods->sml_numvals;
494				attr->a_nvals = (BerVarray) ch_calloc( i+1, sizeof( BerValue ));
495				for ( i = 0; mods->sml_nvalues[i].bv_val; i++ ) {
496					ber_dupbv( &attr->a_nvals[i], &mods->sml_nvalues[i] );
497				}
498				BER_BVZERO( &attr->a_nvals[i] );
499			} else {
500				attr->a_nvals = mods->sml_nvalues;
501			}
502		} else {
503			attr->a_nvals = attr->a_vals;
504		}
505		/* slap_mods_check() gives us sorted results */
506		if ( attr->a_desc->ad_type->sat_flags & SLAP_AT_SORTED_VAL )
507			attr->a_flags |= SLAP_ATTR_SORTED_VALS;
508
509		*tail = attr;
510		tail = &attr->a_next;
511	}
512
513	*text = NULL;
514
515	return LDAP_SUCCESS;
516}
517
518int
519slap_entry2mods(
520	Entry *e,
521	Modifications **mods,
522	const char **text,
523	char *textbuf, size_t textlen )
524{
525	Modifications	*modhead = NULL;
526	Modifications	*mod;
527	Modifications	**modtail = &modhead;
528	Attribute		*a_new;
529	AttributeDescription	*a_new_desc;
530	int				i, count;
531
532	a_new = e->e_attrs;
533
534	while ( a_new != NULL ) {
535		a_new_desc = a_new->a_desc;
536		mod = (Modifications *) ch_malloc( sizeof( Modifications ));
537
538		mod->sml_op = LDAP_MOD_REPLACE;
539		mod->sml_flags = 0;
540
541		mod->sml_type = a_new_desc->ad_cname;
542
543		count = a_new->a_numvals;
544		mod->sml_numvals = a_new->a_numvals;
545
546		mod->sml_values = (struct berval*) ch_malloc(
547			(count+1) * sizeof( struct berval) );
548
549		/* see slap_mods_check() comments...
550		 * if a_vals == a_nvals, there is no normalizer.
551		 * in this case, mod->sml_nvalues must be left NULL.
552		 */
553		if ( a_new->a_vals != a_new->a_nvals ) {
554			mod->sml_nvalues = (struct berval*) ch_malloc(
555				(count+1) * sizeof( struct berval) );
556		} else {
557			mod->sml_nvalues = NULL;
558		}
559
560		for ( i = 0; i < count; i++ ) {
561			ber_dupbv(mod->sml_values+i, a_new->a_vals+i);
562			if ( mod->sml_nvalues ) {
563				ber_dupbv( mod->sml_nvalues+i, a_new->a_nvals+i );
564			}
565		}
566
567		mod->sml_values[count].bv_val = NULL;
568		mod->sml_values[count].bv_len = 0;
569
570		if ( mod->sml_nvalues ) {
571			mod->sml_nvalues[count].bv_val = NULL;
572			mod->sml_nvalues[count].bv_len = 0;
573		}
574
575		mod->sml_desc = a_new_desc;
576		mod->sml_next =NULL;
577		*modtail = mod;
578		modtail = &mod->sml_next;
579		a_new = a_new->a_next;
580	}
581
582	*mods = modhead;
583
584	return LDAP_SUCCESS;
585}
586
587int slap_add_opattrs(
588	Operation *op,
589	const char **text,
590	char *textbuf,
591	size_t textlen,
592	int manage_ctxcsn )
593{
594	struct berval name, timestamp, csn = BER_BVNULL;
595	struct berval nname, tmp;
596	char timebuf[ LDAP_LUTIL_GENTIME_BUFSIZE ];
597	char csnbuf[ LDAP_PVT_CSNSTR_BUFSIZE ];
598	Attribute *a;
599
600	if ( SLAP_LASTMOD( op->o_bd ) ) {
601		char *ptr;
602		int gotcsn = 0;
603
604		timestamp.bv_val = timebuf;
605		a = attr_find( op->ora_e->e_attrs, slap_schema.si_ad_entryCSN );
606		if ( a ) {
607			gotcsn = 1;
608			csn = a->a_vals[0];
609		}
610		if ( BER_BVISEMPTY( &op->o_csn )) {
611			if ( !gotcsn ) {
612				csn.bv_val = csnbuf;
613				csn.bv_len = sizeof(csnbuf);
614				slap_get_csn( op, &csn, manage_ctxcsn );
615			} else {
616				if ( manage_ctxcsn )
617					slap_queue_csn( op, &csn );
618			}
619		} else {
620			csn = op->o_csn;
621		}
622		ptr = ber_bvchr( &csn, '#' );
623		if ( ptr ) {
624			timestamp.bv_len = STRLENOF("YYYYMMDDHHMMSSZ");
625			AC_MEMCPY( timebuf, csn.bv_val, timestamp.bv_len );
626			timebuf[timestamp.bv_len-1] = 'Z';
627			timebuf[timestamp.bv_len] = '\0';
628		} else {
629			time_t now = slap_get_time();
630
631			timestamp.bv_len = sizeof(timebuf);
632
633			slap_timestamp( &now, &timestamp );
634		}
635
636		if ( BER_BVISEMPTY( &op->o_dn ) ) {
637			BER_BVSTR( &name, SLAPD_ANONYMOUS );
638			nname = name;
639		} else {
640			name = op->o_dn;
641			nname = op->o_ndn;
642		}
643
644		a = attr_find( op->ora_e->e_attrs,
645			slap_schema.si_ad_entryUUID );
646		if ( !a ) {
647			char uuidbuf[ LDAP_LUTIL_UUIDSTR_BUFSIZE ];
648
649			tmp.bv_len = lutil_uuidstr( uuidbuf, sizeof( uuidbuf ) );
650			tmp.bv_val = uuidbuf;
651
652			attr_merge_normalize_one( op->ora_e,
653				slap_schema.si_ad_entryUUID, &tmp, op->o_tmpmemctx );
654		}
655
656		a = attr_find( op->ora_e->e_attrs,
657			slap_schema.si_ad_creatorsName );
658		if ( !a ) {
659			attr_merge_one( op->ora_e,
660				slap_schema.si_ad_creatorsName, &name, &nname );
661		}
662
663		a = attr_find( op->ora_e->e_attrs,
664			slap_schema.si_ad_createTimestamp );
665		if ( !a ) {
666			attr_merge_one( op->ora_e,
667				slap_schema.si_ad_createTimestamp, &timestamp, NULL );
668		}
669
670		if ( !gotcsn ) {
671			attr_merge_one( op->ora_e,
672				slap_schema.si_ad_entryCSN, &csn, NULL );
673		}
674
675		a = attr_find( op->ora_e->e_attrs,
676			slap_schema.si_ad_modifiersName );
677		if ( !a ) {
678			attr_merge_one( op->ora_e,
679				slap_schema.si_ad_modifiersName, &name, &nname );
680		}
681
682		a = attr_find( op->ora_e->e_attrs,
683			slap_schema.si_ad_modifyTimestamp );
684		if ( !a ) {
685			attr_merge_one( op->ora_e,
686				slap_schema.si_ad_modifyTimestamp, &timestamp, NULL );
687		}
688	}
689
690	return LDAP_SUCCESS;
691}
692