1 /* $NetBSD: ldapmodify.c,v 1.3 2021/08/14 16:14:49 christos Exp $ */
2
3 /* ldapmodify.c - generic program to modify or add entries using LDAP */
4 /* $OpenLDAP$ */
5 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
6 *
7 * Copyright 1998-2021 The OpenLDAP Foundation.
8 * Portions Copyright 2006 Howard Chu.
9 * Portions Copyright 1998-2003 Kurt D. Zeilenga.
10 * Portions Copyright 1998-2001 Net Boolean Incorporated.
11 * Portions Copyright 2001-2003 IBM Corporation.
12 * All rights reserved.
13 *
14 * Redistribution and use in source and binary forms, with or without
15 * modification, are permitted only as authorized by the OpenLDAP
16 * Public License.
17 *
18 * A copy of this license is available in the file LICENSE in the
19 * top-level directory of the distribution or, alternatively, at
20 * <http://www.OpenLDAP.org/license.html>.
21 */
22 /* Portions Copyright (c) 1992-1996 Regents of the University of Michigan.
23 * All rights reserved.
24 *
25 * Redistribution and use in source and binary forms are permitted
26 * provided that this notice is preserved and that due credit is given
27 * to the University of Michigan at Ann Arbor. The name of the
28 * University may not be used to endorse or promote products derived
29 * from this software without specific prior written permission. This
30 * software is provided ``as is'' without express or implied warranty.
31 */
32 /* ACKNOWLEDGEMENTS:
33 * This work was originally developed by the University of Michigan
34 * (as part of U-MICH LDAP). Additional significant contributors
35 * include:
36 * Kurt D. Zeilenga
37 * Norbert Klasen
38 * Howard Chu
39 */
40
41 #include <sys/cdefs.h>
42 __RCSID("$NetBSD: ldapmodify.c,v 1.3 2021/08/14 16:14:49 christos Exp $");
43
44 #include "portable.h"
45
46 #include <stdio.h>
47
48 #include <ac/stdlib.h>
49 #include <ac/ctype.h>
50 #include <ac/string.h>
51 #include <ac/unistd.h>
52 #include <ac/socket.h>
53 #include <ac/time.h>
54
55 #ifdef HAVE_SYS_STAT_H
56 #include <sys/stat.h>
57 #endif
58
59 #ifdef HAVE_SYS_FILE_H
60 #include <sys/file.h>
61 #endif
62 #ifdef HAVE_FCNTL_H
63 #include <fcntl.h>
64 #endif
65
66 #include <ldap.h>
67
68 #include "lutil.h"
69 #include "lutil_ldap.h"
70 #include "ldif.h"
71 #include "ldap_defaults.h"
72 #include "ldap_pvt.h"
73 #include "lber_pvt.h"
74
75 #include "common.h"
76
77 static int ldapadd;
78 static char *rejfile = NULL;
79 static LDAP *ld = NULL;
80
81 static int process_ldif_rec LDAP_P(( char *rbuf, unsigned long lineno ));
82 static int domodify LDAP_P((
83 const struct berval *dn,
84 LDAPMod **pmods,
85 LDAPControl **pctrls,
86 int newentry ));
87 static int dodelete LDAP_P((
88 const struct berval *dn,
89 LDAPControl **pctrls ));
90 static int dorename LDAP_P((
91 const struct berval *dn,
92 const struct berval *newrdn,
93 const struct berval *newsup,
94 int deleteoldrdn,
95 LDAPControl **pctrls ));
96 static int process_response(
97 LDAP *ld,
98 int msgid,
99 int res,
100 const struct berval *dn );
101
102 static int txn = 0;
103 static int txnabort = 0;
104 struct berval *txn_id = NULL;
105
106 void
usage(void)107 usage( void )
108 {
109 fprintf( stderr, _("Add or modify entries from an LDAP server\n\n"));
110 fprintf( stderr, _("usage: %s [options]\n"), prog);
111 fprintf( stderr, _(" The list of desired operations are read from stdin"
112 " or from the file\n"));
113 fprintf( stderr, _(" specified by \"-f file\".\n"));
114 fprintf( stderr, _("Add or modify options:\n"));
115 fprintf( stderr, _(" -a add values (%s)\n"),
116 (ldapadd ? _("default") : _("default is to replace")));
117 fprintf( stderr, _(" -c continuous operation mode (do not stop on errors)\n"));
118 fprintf( stderr, _(" -E [!]ext=extparam modify extensions"
119 " (! indicate s criticality)\n"));
120 fprintf( stderr, _(" -f file read operations from `file'\n"));
121 fprintf( stderr, _(" -M enable Manage DSA IT control (-MM to make critical)\n"));
122 fprintf( stderr, _(" -P version protocol version (default: 3)\n"));
123 fprintf( stderr,
124 _(" [!]txn=<commit|abort> (transaction)\n"));
125 fprintf( stderr, _(" -S file write skipped modifications to `file'\n"));
126
127 tool_common_usage();
128 exit( EXIT_FAILURE );
129 }
130
131
132 const char options[] = "aE:rS:"
133 "cd:D:e:f:h:H:IMnNO:o:p:P:QR:U:vVw:WxX:y:Y:Z";
134
135 int
handle_private_option(int i)136 handle_private_option( int i )
137 {
138 char *control, *cvalue;
139 int crit;
140
141 switch ( i ) {
142 case 'E': /* modify extensions */
143 if( protocol == LDAP_VERSION2 ) {
144 fprintf( stderr, _("%s: -E incompatible with LDAPv%d\n"),
145 prog, protocol );
146 exit( EXIT_FAILURE );
147 }
148
149 /* should be extended to support comma separated list of
150 * [!]key[=value] parameters, e.g. -E !foo,bar=567
151 */
152
153 crit = 0;
154 cvalue = NULL;
155 if( optarg[0] == '!' ) {
156 crit = 1;
157 optarg++;
158 }
159
160 control = optarg;
161 if ( (cvalue = strchr( control, '=' )) != NULL ) {
162 *cvalue++ = '\0';
163 }
164
165 if( strcasecmp( control, "txn" ) == 0 ) {
166 /* Transaction */
167 if( txn ) {
168 fprintf( stderr,
169 _("txn control previously specified\n"));
170 exit( EXIT_FAILURE );
171 }
172 if( cvalue != NULL ) {
173 if( strcasecmp( cvalue, "abort" ) == 0 ) {
174 txnabort=1;
175 } else if( strcasecmp( cvalue, "commit" ) != 0 ) {
176 fprintf( stderr, _("Invalid value for txn control, %s\n"),
177 cvalue );
178 exit( EXIT_FAILURE );
179 }
180 }
181
182 txn = 1 + crit;
183 } else
184 {
185 fprintf( stderr, _("Invalid modify extension name: %s\n"),
186 control );
187 usage();
188 }
189 break;
190
191 case 'a': /* add */
192 ldapadd = 1;
193 break;
194
195 case 'r': /* replace (obsolete) */
196 break;
197
198 case 'S': /* skipped modifications to file */
199 if( rejfile != NULL ) {
200 fprintf( stderr, _("%s: -S previously specified\n"), prog );
201 exit( EXIT_FAILURE );
202 }
203 rejfile = optarg;
204 break;
205
206 default:
207 return 0;
208 }
209 return 1;
210 }
211
212
213 int
main(int argc,char ** argv)214 main( int argc, char **argv )
215 {
216 char *rbuf = NULL, *rejbuf = NULL;
217 FILE *rejfp;
218 struct LDIFFP *ldiffp = NULL, ldifdummy = {0};
219 char *matched_msg, *error_msg;
220 int rc, retval, ldifrc;
221 int len;
222 int i = 0, lmax = 0;
223 unsigned long lineno, nextline = 0;
224 LDAPControl c[1];
225
226 prog = lutil_progname( "ldapmodify", argc, argv );
227
228 /* strncmp instead of strcmp since NT binaries carry .exe extension */
229 ldapadd = ( strncasecmp( prog, "ldapadd", sizeof("ldapadd")-1 ) == 0 );
230
231 tool_init( ldapadd ? TOOL_ADD : TOOL_MODIFY );
232
233 tool_args( argc, argv );
234
235 if ( argc != optind ) usage();
236
237 if ( rejfile != NULL ) {
238 if (( rejfp = fopen( rejfile, "w" )) == NULL ) {
239 perror( rejfile );
240 retval = EXIT_FAILURE;
241 goto fail;
242 }
243 } else {
244 rejfp = NULL;
245 }
246
247 if ( infile != NULL ) {
248 if (( ldiffp = ldif_open( infile, "r" )) == NULL ) {
249 perror( infile );
250 retval = EXIT_FAILURE;
251 goto fail;
252 }
253 } else {
254 ldifdummy.fp = stdin;
255 ldiffp = &ldifdummy;
256 }
257
258 if ( debug ) ldif_debug = debug;
259
260 ld = tool_conn_setup( dont, 0 );
261
262 if ( !dont ) {
263 tool_bind( ld );
264 }
265
266 if( txn ) {
267 /* start transaction */
268 rc = ldap_txn_start_s( ld, NULL, NULL, &txn_id );
269 if( rc != LDAP_SUCCESS || !txn_id ) {
270 tool_perror( "ldap_txn_start_s", rc, NULL, NULL, NULL, NULL );
271 if( txn > 1 ) {
272 retval = EXIT_FAILURE;
273 goto fail;
274 }
275 txn = 0;
276 }
277 }
278
279 if( txn ) {
280 c[i].ldctl_oid = LDAP_CONTROL_TXN_SPEC;
281 c[i].ldctl_value = *txn_id;
282 c[i].ldctl_iscritical = 1;
283 i++;
284 }
285
286 tool_server_controls( ld, c, i );
287
288 rc = 0;
289 retval = 0;
290 lineno = 1;
291 while (( rc == 0 || contoper ) && ( ldifrc = ldif_read_record( ldiffp, &nextline,
292 &rbuf, &lmax )) > 0 )
293 {
294 if ( rejfp ) {
295 len = strlen( rbuf );
296 if (( rejbuf = (char *)ber_memalloc( len+1 )) == NULL ) {
297 perror( "malloc" );
298 retval = EXIT_FAILURE;
299 goto fail;
300 }
301 memcpy( rejbuf, rbuf, len+1 );
302 }
303
304 rc = process_ldif_rec( rbuf, lineno );
305 lineno = nextline+1;
306
307 if ( rc ) retval = rc;
308 if ( rc && rejfp ) {
309 fprintf(rejfp, _("# Error: %s (%d)"), ldap_err2string(rc), rc);
310
311 matched_msg = NULL;
312 ldap_get_option(ld, LDAP_OPT_MATCHED_DN, &matched_msg);
313 if ( matched_msg != NULL ) {
314 if ( *matched_msg != '\0' ) {
315 fprintf( rejfp, _(", matched DN: %s"), matched_msg );
316 }
317 ldap_memfree( matched_msg );
318 }
319
320 error_msg = NULL;
321 ldap_get_option(ld, LDAP_OPT_DIAGNOSTIC_MESSAGE, &error_msg);
322 if ( error_msg != NULL ) {
323 if ( *error_msg != '\0' ) {
324 fprintf( rejfp, _(", additional info: %s"), error_msg );
325 }
326 ldap_memfree( error_msg );
327 }
328 fprintf( rejfp, "\n%s\n", rejbuf );
329 }
330
331 if (rejfp) ber_memfree( rejbuf );
332 }
333 ber_memfree( rbuf );
334
335 if ( ldifrc < 0 )
336 retval = LDAP_OTHER;
337
338 if( retval == 0 && txn ) {
339 rc = ldap_set_option( ld, LDAP_OPT_SERVER_CONTROLS, NULL );
340 if ( rc != LDAP_OPT_SUCCESS ) {
341 fprintf( stderr, "Could not unset controls for ldap_txn_end\n");
342 }
343
344 /* create transaction */
345 rc = ldap_txn_end_s( ld, !txnabort, txn_id, NULL, NULL, NULL );
346 if( rc != LDAP_SUCCESS ) {
347 tool_perror( "ldap_txn_end_s", rc, NULL, NULL, NULL, NULL );
348 retval = rc;
349 }
350 }
351
352 fail:;
353 if ( rejfp != NULL ) {
354 fclose( rejfp );
355 }
356
357 if ( ldiffp != NULL && ldiffp != &ldifdummy ) {
358 ldif_close( ldiffp );
359 }
360
361 tool_exit( ld, retval );
362 }
363
364
365 static int
process_ldif_rec(char * rbuf,unsigned long linenum)366 process_ldif_rec( char *rbuf, unsigned long linenum )
367 {
368 LDIFRecord lr;
369 int lrflags = ldapadd ? LDIF_DEFAULT_ADD : 0;
370 int rc;
371 struct berval rbuf_bv;
372
373 #ifdef TEST_LDIF_API
374 if ( getenv( "LDIF_ENTRIES_ONLY" ) ) {
375 lrflags |= LDIF_ENTRIES_ONLY;
376 }
377 if ( getenv( "LDIF_NO_CONTROLS" ) ) {
378 lrflags |= LDIF_NO_CONTROLS;
379 }
380 #endif /* TEST_LDIF_API */
381
382 rbuf_bv.bv_val = rbuf;
383 rbuf_bv.bv_len = 0; /* not used */
384 rc = ldap_parse_ldif_record( &rbuf_bv, linenum, &lr, prog, lrflags );
385
386 /* If default controls are set (as with -M option) and controls are
387 specified in the LDIF file, we must add the default controls to
388 the list of controls sent with the ldap operation.
389 */
390 if ( rc == 0 ) {
391 if (lr.lr_ctrls) {
392 LDAPControl **defctrls = NULL; /* Default server controls */
393 LDAPControl **newctrls = NULL;
394 ldap_get_option(ld, LDAP_OPT_SERVER_CONTROLS, &defctrls);
395 if (defctrls) {
396 int npc=0; /* Num of LDIF controls */
397 int ndefc=0; /* Num of default controls */
398 while (lr.lr_ctrls[npc]) npc++; /* Count LDIF controls */
399 while (defctrls[ndefc]) ndefc++; /* Count default controls */
400 newctrls = ber_memrealloc(lr.lr_ctrls,
401 (npc+ndefc+1)*sizeof(LDAPControl*));
402
403 if (newctrls == NULL) {
404 rc = LDAP_NO_MEMORY;
405 } else {
406 int i;
407 lr.lr_ctrls = newctrls;
408 for (i=npc; i<npc+ndefc; i++) {
409 lr.lr_ctrls[i] = ldap_control_dup(defctrls[i-npc]);
410 if (lr.lr_ctrls[i] == NULL) {
411 rc = LDAP_NO_MEMORY;
412 break;
413 }
414 }
415 lr.lr_ctrls[npc+ndefc] = NULL;
416 }
417 ldap_controls_free(defctrls); /* Must be freed by library */
418 }
419 }
420 }
421
422 if ( rc == 0 ) {
423 if ( LDAP_REQ_DELETE == lr.lr_op ) {
424 rc = dodelete( &lr.lr_dn, lr.lr_ctrls );
425 } else if ( LDAP_REQ_RENAME == lr.lr_op ) {
426 rc = dorename( &lr.lr_dn, &lr.lrop_newrdn, &lr.lrop_newsup, lr.lrop_delold, lr.lr_ctrls );
427 } else if ( ( LDAP_REQ_ADD == lr.lr_op ) || ( LDAP_REQ_MODIFY == lr.lr_op ) ) {
428 rc = domodify( &lr.lr_dn, lr.lrop_mods, lr.lr_ctrls, LDAP_REQ_ADD == lr.lr_op );
429 } else {
430 /* record skipped e.g. version: or comment or something we don't handle yet */
431 }
432
433 if ( rc == LDAP_SUCCESS ) {
434 rc = 0;
435 }
436 }
437
438 ldap_ldif_record_done( &lr );
439
440 return( rc );
441 }
442
443
444 static int
domodify(const struct berval * dn,LDAPMod ** pmods,LDAPControl ** pctrls,int newentry)445 domodify(
446 const struct berval *dn,
447 LDAPMod **pmods,
448 LDAPControl **pctrls,
449 int newentry )
450 {
451 int rc, i, j, k, notascii, op;
452 struct berval *bvp;
453
454 if ( ( dn == NULL ) || ( dn->bv_val == NULL ) ) {
455 fprintf( stderr, _("%s: no DN specified\n"), prog );
456 return( LDAP_PARAM_ERROR );
457 }
458
459 if ( pmods == NULL ) {
460 /* implement "touch" (empty sequence)
461 * modify operation (note that there
462 * is no symmetry with the UNIX command,
463 * since \"touch\" on a non-existent entry
464 * will fail)*/
465 printf( "warning: no attributes to %sadd (entry=\"%s\")\n",
466 newentry ? "" : "change or ", dn->bv_val );
467
468 } else {
469 for ( i = 0; pmods[ i ] != NULL; ++i ) {
470 op = pmods[ i ]->mod_op & ~LDAP_MOD_BVALUES;
471 if( op == LDAP_MOD_ADD && ( pmods[i]->mod_bvalues == NULL )) {
472 fprintf( stderr,
473 _("%s: attribute \"%s\" has no values (entry=\"%s\")\n"),
474 prog, pmods[i]->mod_type, dn->bv_val );
475 return LDAP_PARAM_ERROR;
476 }
477 }
478
479 if ( verbose ) {
480 for ( i = 0; pmods[ i ] != NULL; ++i ) {
481 op = pmods[ i ]->mod_op & ~LDAP_MOD_BVALUES;
482 printf( "%s %s:\n",
483 op == LDAP_MOD_REPLACE ? _("replace") :
484 op == LDAP_MOD_ADD ? _("add") :
485 op == LDAP_MOD_INCREMENT ? _("increment") :
486 op == LDAP_MOD_DELETE ? _("delete") :
487 _("unknown"),
488 pmods[ i ]->mod_type );
489
490 if ( pmods[ i ]->mod_bvalues != NULL ) {
491 for ( j = 0; pmods[ i ]->mod_bvalues[ j ] != NULL; ++j ) {
492 bvp = pmods[ i ]->mod_bvalues[ j ];
493 notascii = 0;
494 for ( k = 0; (unsigned long) k < bvp->bv_len; ++k ) {
495 if ( !isascii( bvp->bv_val[ k ] )) {
496 notascii = 1;
497 break;
498 }
499 }
500 if ( notascii ) {
501 printf( _("\tNOT ASCII (%ld bytes)\n"), bvp->bv_len );
502 } else {
503 printf( "\t%s\n", bvp->bv_val );
504 }
505 }
506 }
507 }
508 }
509 }
510
511 if ( newentry ) {
512 printf( "%sadding new entry \"%s\"\n", dont ? "!" : "", dn->bv_val );
513 } else {
514 printf( "%smodifying entry \"%s\"\n", dont ? "!" : "", dn->bv_val );
515 }
516
517 if ( !dont ) {
518 int msgid;
519 if ( newentry ) {
520 rc = ldap_add_ext( ld, dn->bv_val, pmods, pctrls, NULL, &msgid );
521 } else {
522 rc = ldap_modify_ext( ld, dn->bv_val, pmods, pctrls, NULL, &msgid );
523 }
524
525 if ( rc != LDAP_SUCCESS ) {
526 /* print error message about failed update including DN */
527 fprintf( stderr, _("%s: update failed: %s\n"), prog, dn->bv_val );
528 tool_perror( newentry ? "ldap_add" : "ldap_modify",
529 rc, NULL, NULL, NULL, NULL );
530 goto done;
531 }
532 rc = process_response( ld, msgid,
533 newentry ? LDAP_RES_ADD : LDAP_RES_MODIFY, dn );
534
535 if ( verbose && rc == LDAP_SUCCESS ) {
536 printf( _("modify complete\n") );
537 }
538
539 } else {
540 rc = LDAP_SUCCESS;
541 }
542
543 done:
544 putchar( '\n' );
545 return rc;
546 }
547
548
549 static int
dodelete(const struct berval * dn,LDAPControl ** pctrls)550 dodelete(
551 const struct berval *dn,
552 LDAPControl **pctrls )
553 {
554 int rc;
555 int msgid;
556
557 assert( dn != NULL );
558 assert( dn->bv_val != NULL );
559 printf( _("%sdeleting entry \"%s\"\n"), dont ? "!" : "", dn->bv_val );
560 if ( !dont ) {
561 rc = ldap_delete_ext( ld, dn->bv_val, pctrls, NULL, &msgid );
562 if ( rc != LDAP_SUCCESS ) {
563 fprintf( stderr, _("%s: delete failed: %s\n"), prog, dn->bv_val );
564 tool_perror( "ldap_delete", rc, NULL, NULL, NULL, NULL );
565 goto done;
566 }
567 rc = process_response( ld, msgid, LDAP_RES_DELETE, dn );
568
569 if ( verbose && rc == LDAP_SUCCESS ) {
570 printf( _("delete complete\n") );
571 }
572 } else {
573 rc = LDAP_SUCCESS;
574 }
575
576 done:
577 putchar( '\n' );
578 return( rc );
579 }
580
581
582 static int
dorename(const struct berval * dn,const struct berval * newrdn,const struct berval * newsup,int deleteoldrdn,LDAPControl ** pctrls)583 dorename(
584 const struct berval *dn,
585 const struct berval *newrdn,
586 const struct berval *newsup,
587 int deleteoldrdn,
588 LDAPControl **pctrls )
589 {
590 int rc;
591 int msgid;
592
593 assert( dn != NULL );
594 assert( dn->bv_val != NULL );
595 assert( newrdn != NULL );
596 assert( newrdn->bv_val != NULL );
597 printf( _("%smodifying rdn of entry \"%s\"\n"), dont ? "!" : "", dn->bv_val );
598 if ( verbose ) {
599 printf( _("\tnew RDN: \"%s\" (%skeep existing values)\n"),
600 newrdn->bv_val, deleteoldrdn ? _("do not ") : "" );
601 }
602 if ( !dont ) {
603 rc = ldap_rename( ld, dn->bv_val, newrdn->bv_val,
604 ( newsup && newsup->bv_val ) ? newsup->bv_val : NULL,
605 deleteoldrdn, pctrls, NULL, &msgid );
606 if ( rc != LDAP_SUCCESS ) {
607 fprintf( stderr, _("%s: rename failed: %s\n"), prog, dn->bv_val );
608 tool_perror( "ldap_rename", rc, NULL, NULL, NULL, NULL );
609 goto done;
610 }
611 rc = process_response( ld, msgid, LDAP_RES_RENAME, dn );
612
613 if ( verbose && rc == LDAP_SUCCESS ) {
614 printf( _("rename complete\n") );
615 }
616 } else {
617 rc = LDAP_SUCCESS;
618 }
619
620 done:
621 putchar( '\n' );
622 return( rc );
623 }
624
625 static const char *
res2str(int res)626 res2str( int res ) {
627 switch ( res ) {
628 case LDAP_RES_ADD:
629 return "ldap_add";
630 case LDAP_RES_DELETE:
631 return "ldap_delete";
632 case LDAP_RES_MODIFY:
633 return "ldap_modify";
634 case LDAP_RES_MODRDN:
635 return "ldap_rename";
636 default:
637 assert( 0 );
638 }
639
640 return "ldap_unknown";
641 }
642
process_response(LDAP * ld,int msgid,int op,const struct berval * dn)643 static int process_response(
644 LDAP *ld,
645 int msgid,
646 int op,
647 const struct berval *dn )
648 {
649 LDAPMessage *res;
650 int rc = LDAP_OTHER, msgtype;
651 struct timeval tv = { 0, 0 };
652 int err;
653 char *text = NULL, *matched = NULL, **refs = NULL;
654 LDAPControl **ctrls = NULL;
655
656 assert( dn != NULL );
657 for ( ; ; ) {
658 tv.tv_sec = 0;
659 tv.tv_usec = 100000;
660
661 rc = ldap_result( ld, msgid, LDAP_MSG_ALL, &tv, &res );
662 if ( tool_check_abandon( ld, msgid ) ) {
663 return LDAP_CANCELLED;
664 }
665
666 if ( rc == -1 ) {
667 ldap_get_option( ld, LDAP_OPT_RESULT_CODE, &rc );
668 tool_perror( "ldap_result", rc, NULL, NULL, NULL, NULL );
669 return rc;
670 }
671
672 if ( rc != 0 ) {
673 break;
674 }
675 }
676
677 msgtype = ldap_msgtype( res );
678
679 rc = ldap_parse_result( ld, res, &err, &matched, &text, &refs, &ctrls, 1 );
680 if ( rc == LDAP_SUCCESS ) rc = err;
681
682 if ( rc == LDAP_TXN_SPECIFY_OKAY ) {
683 rc = LDAP_SUCCESS;
684 } else if ( rc != LDAP_SUCCESS ) {
685 tool_perror( res2str( op ), rc, NULL, matched, text, refs );
686 } else if ( msgtype != op ) {
687 fprintf( stderr, "%s: msgtype: expected %d got %d\n",
688 res2str( op ), op, msgtype );
689 rc = LDAP_OTHER;
690 }
691
692 if ( text ) ldap_memfree( text );
693 if ( matched ) ldap_memfree( matched );
694 if ( refs ) ber_memvfree( (void **)refs );
695
696 if ( ctrls ) {
697 tool_print_ctrls( ld, ctrls );
698 ldap_controls_free( ctrls );
699 }
700
701 return rc;
702 }
703