/* $OpenLDAP$ */ /* This work is part of OpenLDAP Software . * * Copyright 2006-2008 The OpenLDAP Foundation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted only as authorized by the OpenLDAP * Public License. * * A copy of this license is available in the file LICENSE in the * top-level directory of the distribution or, alternatively, at * . */ /* ACKNOWLEDGEMENTS: * This program was originally developed by Pierangelo Masarati * for inclusion in OpenLDAP Software. */ /* * Proof-of-concept API that implement the client-side * of the "LDAP Content Sync Operation" (RFC 4533) */ #include "portable.h" #include #include "ldap-int.h" #ifdef LDAP_SYNC_TRACE static const char * ldap_sync_state2str( int state ) { switch ( state ) { case LDAP_SYNC_PRESENT: return "LDAP_SYNC_PRESENT"; case LDAP_SYNC_ADD: return "LDAP_SYNC_ADD"; case LDAP_SYNC_MODIFY: return "LDAP_SYNC_MODIFY"; case LDAP_SYNC_DELETE: return "LDAP_SYNC_DELETE"; default: return "(unknown)"; } } #endif /* * initialize the persistent search structure */ ldap_sync_t * ldap_sync_initialize( ldap_sync_t *ls_in ) { ldap_sync_t *ls = ls_in; if ( ls == NULL ) { ls = ldap_memalloc( sizeof( ldap_sync_t ) ); if ( ls == NULL ) { return NULL; } } else { memset( ls, 0, sizeof( ldap_sync_t ) ); } ls->ls_scope = LDAP_SCOPE_SUBTREE; ls->ls_timeout = -1; return ls; } /* * destroy the persistent search structure */ void ldap_sync_destroy( ldap_sync_t *ls, int freeit ) { assert( ls != NULL ); if ( ls->ls_base != NULL ) { ldap_memfree( ls->ls_base ); ls->ls_base = NULL; } if ( ls->ls_filter != NULL ) { ldap_memfree( ls->ls_filter ); ls->ls_filter = NULL; } if ( ls->ls_attrs != NULL ) { int i; for ( i = 0; ls->ls_attrs[ i ] != NULL; i++ ) { ldap_memfree( ls->ls_attrs[ i ] ); } ldap_memfree( ls->ls_attrs ); ls->ls_attrs = NULL; } if ( ls->ls_ld != NULL ) { (void)ldap_unbind_ext( ls->ls_ld, NULL, NULL ); #ifdef LDAP_SYNC_TRACE fprintf( stderr, "ldap_unbind_ext()\n" ); #endif /* LDAP_SYNC_TRACE */ ls->ls_ld = NULL; } if ( ls->ls_cookie.bv_val != NULL ) { ldap_memfree( ls->ls_cookie.bv_val ); ls->ls_cookie.bv_val = NULL; } if ( freeit ) { ldap_memfree( ls ); } } /* * handle the LDAP_RES_SEARCH_ENTRY response */ static int ldap_sync_search_entry( ldap_sync_t *ls, LDAPMessage *res ) { LDAPControl **ctrls = NULL; int rc = LDAP_SUCCESS, i; BerElement *ber = NULL; struct berval entryUUID = { 0 }, cookie = { 0 }; int state = -1; ber_len_t len; ldap_sync_refresh_t phase = ls->ls_refreshPhase; #ifdef LDAP_SYNC_TRACE fprintf( stderr, "\tgot LDAP_RES_SEARCH_ENTRY\n" ); #endif /* LDAP_SYNC_TRACE */ assert( ls != NULL ); assert( res != NULL ); /* OK */ /* extract: * - data * - entryUUID * * check that: * - Sync State Control is "add" */ /* the control MUST be present */ /* extract controls */ ldap_get_entry_controls( ls->ls_ld, res, &ctrls ); if ( ctrls == NULL ) { rc = LDAP_OTHER; goto done; } /* lookup the sync state control */ for ( i = 0; ctrls[ i ] != NULL; i++ ) { if ( strcmp( ctrls[ i ]->ldctl_oid, LDAP_CONTROL_SYNC_STATE ) == 0 ) { break; } } /* control must be present; there might be other... */ if ( ctrls[ i ] == NULL ) { rc = LDAP_OTHER; goto done; } /* extract data */ ber = ber_init( &ctrls[ i ]->ldctl_value ); /* scan entryUUID in-place ("m") */ ber_scanf( ber, "{em" /*"}"*/, &state, &entryUUID ); if ( entryUUID.bv_len == 0 ) { rc = LDAP_OTHER; goto done; } if ( ber_peek_tag( ber, &len ) == LDAP_TAG_SYNC_COOKIE ) { /* scan cookie in-place ("m") */ ber_scanf( ber, /*"{"*/ "m}", &cookie ); if ( cookie.bv_val != NULL ) { ber_bvreplace( &ls->ls_cookie, &cookie ); } #ifdef LDAP_SYNC_TRACE fprintf( stderr, "\t\tgot cookie=%s\n", cookie.bv_val ? cookie.bv_val : "(null)" ); #endif /* LDAP_SYNC_TRACE */ } switch ( state ) { case LDAP_SYNC_PRESENT: case LDAP_SYNC_DELETE: case LDAP_SYNC_ADD: case LDAP_SYNC_MODIFY: /* NOTE: ldap_sync_refresh_t is defined * as the corresponding LDAP_SYNC_* * for the 4 above cases */ phase = state; #ifdef LDAP_SYNC_TRACE fprintf( stderr, "\t\tgot syncState=%s\n", ldap_sync_state2str( state ) ); #endif /* LDAP_SYNC_TRACE */ break; default: rc = LDAP_OTHER; #ifdef LDAP_SYNC_TRACE fprintf( stderr, "\t\tgot unknown syncState=%d\n", state ); #endif /* LDAP_SYNC_TRACE */ goto done; } if ( ls->ls_search_entry ) { rc = ls->ls_search_entry( ls, res, &entryUUID, phase ); } done:; if ( ber != NULL ) { ber_free( ber, 1 ); } if ( ctrls != NULL ) { ldap_controls_free( ctrls ); } return rc; } /* * handle the LDAP_RES_SEARCH_REFERENCE response * (to be implemented yet) */ static int ldap_sync_search_reference( ldap_sync_t *ls, LDAPMessage *res ) { int rc = 0; #ifdef LDAP_SYNC_TRACE fprintf( stderr, "\tgot LDAP_RES_SEARCH_REFERENCE\n" ); #endif /* LDAP_SYNC_TRACE */ assert( ls != NULL ); assert( res != NULL ); if ( ls->ls_search_reference ) { rc = ls->ls_search_reference( ls, res ); } return rc; } /* * handle the LDAP_RES_SEARCH_RESULT response */ static int ldap_sync_search_result( ldap_sync_t *ls, LDAPMessage *res ) { int err; char *matched = NULL, *msg = NULL; LDAPControl **ctrls = NULL; int rc; int refreshDeletes = -1; #ifdef LDAP_SYNC_TRACE fprintf( stderr, "\tgot LDAP_RES_SEARCH_RESULT\n" ); #endif /* LDAP_SYNC_TRACE */ assert( ls != NULL ); assert( res != NULL ); /* should not happen in refreshAndPersist... */ rc = ldap_parse_result( ls->ls_ld, res, &err, &matched, &msg, NULL, &ctrls, 0 ); #ifdef LDAP_SYNC_TRACE fprintf( stderr, "\tldap_parse_result(%d, \"%s\", \"%s\") == %d\n", err, matched ? matched : "", msg ? msg : "", rc ); #endif /* LDAP_SYNC_TRACE */ if ( rc == LDAP_SUCCESS ) { rc = err; } ls->ls_refreshPhase = LDAP_SYNC_CAPI_DONE; switch ( rc ) { case LDAP_SUCCESS: { int i; BerElement *ber = NULL; ber_len_t len; struct berval cookie = { 0 }; /* deal with control; then fallthru to handler */ if ( ctrls == NULL ) { rc = LDAP_OTHER; goto done; } /* lookup the sync state control */ for ( i = 0; ctrls[ i ] != NULL; i++ ) { if ( strcmp( ctrls[ i ]->ldctl_oid, LDAP_CONTROL_SYNC_DONE ) == 0 ) { break; } } /* control must be present; there might be other... */ if ( ctrls[ i ] == NULL ) { rc = LDAP_OTHER; goto done; } /* extract data */ ber = ber_init( &ctrls[ i ]->ldctl_value ); ber_scanf( ber, "{" /*"}"*/); if ( ber_peek_tag( ber, &len ) == LDAP_TAG_SYNC_COOKIE ) { ber_scanf( ber, "m", &cookie ); if ( cookie.bv_val != NULL ) { ber_bvreplace( &ls->ls_cookie, &cookie ); } #ifdef LDAP_SYNC_TRACE fprintf( stderr, "\t\tgot cookie=%s\n", cookie.bv_val ? cookie.bv_val : "(null)" ); #endif /* LDAP_SYNC_TRACE */ } refreshDeletes = 0; if ( ber_peek_tag( ber, &len ) == LDAP_TAG_REFRESHDELETES ) { ber_scanf( ber, "b", &refreshDeletes ); if ( refreshDeletes ) { refreshDeletes = 1; } } ber_scanf( ber, /*"{"*/ "}" ); /* NOTE: if any goto/return between ber_init() and here * is introduced, don't forget to ber_free() */ ber_free( ber, 1 ); #ifdef LDAP_SYNC_TRACE fprintf( stderr, "\t\tgot refreshDeletes=%s\n", refreshDeletes ? "TRUE" : "FALSE" ); #endif /* LDAP_SYNC_TRACE */ /* FIXME: what should we do with the refreshDelete? */ switch ( refreshDeletes ) { case 0: ls->ls_refreshPhase = LDAP_SYNC_CAPI_PRESENTS; break; default: ls->ls_refreshPhase = LDAP_SYNC_CAPI_DELETES; break; } } /* fallthru */ case LDAP_SYNC_REFRESH_REQUIRED: /* TODO: check for Sync Done Control */ /* FIXME: perhaps the handler should be called * also in case of failure; we'll deal with this * later when implementing refreshOnly */ if ( ls->ls_search_result ) { err = ls->ls_search_result( ls, res, refreshDeletes ); } break; default: break; } done:; if ( matched != NULL ) { ldap_memfree( matched ); } if ( msg != NULL ) { ldap_memfree( msg ); } if ( ctrls != NULL ) { ldap_controls_free( ctrls ); } ls->ls_refreshPhase = LDAP_SYNC_CAPI_DONE; return rc; } /* * handle the LDAP_RES_INTERMEDIATE response */ static int ldap_sync_search_intermediate( ldap_sync_t *ls, LDAPMessage *res, int *refreshDone ) { int rc; char *retoid = NULL; struct berval *retdata = NULL; BerElement *ber = NULL; ber_len_t len; ber_tag_t tag, syncinfo_tag; struct berval cookie; int refreshDeletes = 0; BerVarray syncUUIDs = NULL; ldap_sync_refresh_t phase; #ifdef LDAP_SYNC_TRACE fprintf( stderr, "\tgot LDAP_RES_INTERMEDIATE\n" ); #endif /* LDAP_SYNC_TRACE */ assert( ls != NULL ); assert( res != NULL ); assert( refreshDone != NULL ); *refreshDone = 0; rc = ldap_parse_intermediate( ls->ls_ld, res, &retoid, &retdata, NULL, 0 ); #ifdef LDAP_SYNC_TRACE fprintf( stderr, "\t%sldap_parse_intermediate(%s) == %d\n", rc != LDAP_SUCCESS ? "!!! " : "", retoid == NULL ? "\"\"" : retoid, rc ); #endif /* LDAP_SYNC_TRACE */ /* parsing must be successful, and yield the OID * of the sync info intermediate response */ if ( rc != LDAP_SUCCESS ) { goto done; } if ( retoid == NULL || strcmp( retoid, LDAP_SYNC_INFO ) != 0 ) { rc = LDAP_OTHER; goto done; } /* init ber using the value in the response */ ber = ber_init( retdata ); if ( ber == NULL ) { goto done; } syncinfo_tag = ber_peek_tag( ber, &len ); switch ( syncinfo_tag ) { case LDAP_TAG_SYNC_NEW_COOKIE: ber_scanf( ber, "tm", &tag, &cookie ); if ( cookie.bv_val != NULL ) { ber_bvreplace( &ls->ls_cookie, &cookie ); } #ifdef LDAP_SYNC_TRACE fprintf( stderr, "\t\tgot cookie=%s\n", cookie.bv_val ? cookie.bv_val : "(null)" ); #endif /* LDAP_SYNC_TRACE */ break; case LDAP_TAG_SYNC_REFRESH_DELETE: case LDAP_TAG_SYNC_REFRESH_PRESENT: if ( syncinfo_tag == LDAP_TAG_SYNC_REFRESH_DELETE ) { #ifdef LDAP_SYNC_TRACE fprintf( stderr, "\t\tgot refreshDelete\n" ); #endif /* LDAP_SYNC_TRACE */ switch ( ls->ls_refreshPhase ) { case LDAP_SYNC_CAPI_NONE: case LDAP_SYNC_CAPI_PRESENTS: ls->ls_refreshPhase = LDAP_SYNC_CAPI_DELETES; break; default: /* TODO: impossible; handle */ rc = LDAP_OTHER; goto done; } } else { #ifdef LDAP_SYNC_TRACE fprintf( stderr, "\t\tgot refreshPresent\n" ); #endif /* LDAP_SYNC_TRACE */ switch ( ls->ls_refreshPhase ) { case LDAP_SYNC_CAPI_NONE: ls->ls_refreshPhase = LDAP_SYNC_CAPI_PRESENTS; break; default: /* TODO: impossible; handle */ rc = LDAP_OTHER; goto done; } } ber_scanf( ber, "t{" /*"}"*/, &tag ); if ( ber_peek_tag( ber, &len ) == LDAP_TAG_SYNC_COOKIE ) { ber_scanf( ber, "m", &cookie ); if ( cookie.bv_val != NULL ) { ber_bvreplace( &ls->ls_cookie, &cookie ); } #ifdef LDAP_SYNC_TRACE fprintf( stderr, "\t\tgot cookie=%s\n", cookie.bv_val ? cookie.bv_val : "(null)" ); #endif /* LDAP_SYNC_TRACE */ } *refreshDone = 1; if ( ber_peek_tag( ber, &len ) == LDAP_TAG_REFRESHDONE ) { ber_scanf( ber, "b", refreshDone ); } #ifdef LDAP_SYNC_TRACE fprintf( stderr, "\t\tgot refreshDone=%s\n", *refreshDone ? "TRUE" : "FALSE" ); #endif /* LDAP_SYNC_TRACE */ ber_scanf( ber, /*"{"*/ "}" ); if ( *refreshDone ) { ls->ls_refreshPhase = LDAP_SYNC_CAPI_DONE; } if ( ls->ls_intermediate ) { ls->ls_intermediate( ls, res, NULL, ls->ls_refreshPhase ); } break; case LDAP_TAG_SYNC_ID_SET: #ifdef LDAP_SYNC_TRACE fprintf( stderr, "\t\tgot syncIdSet\n" ); #endif /* LDAP_SYNC_TRACE */ ber_scanf( ber, "t{" /*"}"*/, &tag ); if ( ber_peek_tag( ber, &len ) == LDAP_TAG_SYNC_COOKIE ) { ber_scanf( ber, "m", &cookie ); if ( cookie.bv_val != NULL ) { ber_bvreplace( &ls->ls_cookie, &cookie ); } #ifdef LDAP_SYNC_TRACE fprintf( stderr, "\t\tgot cookie=%s\n", cookie.bv_val ? cookie.bv_val : "(null)" ); #endif /* LDAP_SYNC_TRACE */ } if ( ber_peek_tag( ber, &len ) == LDAP_TAG_REFRESHDELETES ) { ber_scanf( ber, "b", &refreshDeletes ); } ber_scanf( ber, "[W]", &syncUUIDs ); ber_scanf( ber, /*"{"*/ "}" ); if ( syncUUIDs == NULL ) { rc = LDAP_OTHER; goto done; } #ifdef LDAP_SYNC_TRACE { int i; fprintf( stderr, "\t\tgot refreshDeletes=%s\n", refreshDeletes ? "TRUE" : "FALSE" ); for ( i = 0; syncUUIDs[ i ].bv_val != NULL; i++ ) { char buf[ BUFSIZ ]; fprintf( stderr, "\t\t%s\n", lutil_uuidstr_from_normalized( syncUUIDs[ i ].bv_val, syncUUIDs[ i ].bv_len, buf, sizeof( buf ) ) ); } } #endif /* LDAP_SYNC_TRACE */ if ( refreshDeletes ) { phase = LDAP_SYNC_CAPI_DELETES_IDSET; } else { phase = LDAP_SYNC_CAPI_PRESENTS_IDSET; } /* FIXME: should touch ls->ls_refreshPhase? */ if ( ls->ls_intermediate ) { ls->ls_intermediate( ls, res, syncUUIDs, phase ); } ber_bvarray_free( syncUUIDs ); break; default: #ifdef LDAP_SYNC_TRACE fprintf( stderr, "\t\tunknown tag!\n" ); #endif /* LDAP_SYNC_TRACE */ goto done; } done:; if ( ber != NULL ) { ber_free( ber, 1 ); } if ( retoid != NULL ) { ldap_memfree( retoid ); } if ( retdata != NULL ) { ber_bvfree( retdata ); } return rc; } /* * initialize the sync */ int ldap_sync_init( ldap_sync_t *ls, int mode ) { LDAPControl ctrl = { 0 }, *ctrls[ 2 ]; BerElement *ber = NULL; int rc; struct timeval tv = { 0 }, *tvp = NULL; LDAPMessage *res = NULL; #ifdef LDAP_SYNC_TRACE fprintf( stderr, "ldap_sync_init(%s)...\n", mode == LDAP_SYNC_REFRESH_AND_PERSIST ? "LDAP_SYNC_REFRESH_AND_PERSIST" : ( mode == LDAP_SYNC_REFRESH_ONLY ? "LDAP_SYNC_REFRESH_ONLY" : "unknown" ) ); #endif /* LDAP_SYNC_TRACE */ assert( ls != NULL ); assert( ls->ls_ld != NULL ); /* support both refreshOnly and refreshAndPersist */ switch ( mode ) { case LDAP_SYNC_REFRESH_AND_PERSIST: case LDAP_SYNC_REFRESH_ONLY: break; default: fprintf( stderr, "ldap_sync_init: unknown mode=%d\n", mode ); return LDAP_PARAM_ERROR; } /* check consistency of cookie and reloadHint at initial refresh */ if ( ls->ls_cookie.bv_val == NULL && ls->ls_reloadHint != 0 ) { fprintf( stderr, "ldap_sync_init: inconsistent cookie/rhint\n" ); return LDAP_PARAM_ERROR; } ctrls[ 0 ] = &ctrl; ctrls[ 1 ] = NULL; /* prepare the Sync Request control */ ber = ber_alloc_t( LBER_USE_DER ); #ifdef LDAP_SYNC_TRACE fprintf( stderr, "%sber_alloc_t() %s= NULL\n", ber == NULL ? "!!! " : "", ber == NULL ? "=" : "!" ); #endif /* LDAP_SYNC_TRACE */ if ( ber == NULL ) { rc = LDAP_NO_MEMORY; goto done; } ls->ls_refreshPhase = LDAP_SYNC_CAPI_NONE; if ( ls->ls_cookie.bv_val != NULL ) { ber_printf( ber, "{eOb}", mode, &ls->ls_cookie, ls->ls_reloadHint ); } else { ber_printf( ber, "{eb}", mode, ls->ls_reloadHint ); } rc = ber_flatten2( ber, &ctrl.ldctl_value, 0 ); #ifdef LDAP_SYNC_TRACE fprintf( stderr, "%sber_flatten2() == %d\n", rc ? "!!! " : "", rc ); #endif /* LDAP_SYNC_TRACE */ if ( rc == LBER_ERROR ) { rc = LDAP_OTHER; goto done; } /* make the control critical, as we cannot proceed without */ ctrl.ldctl_oid = LDAP_CONTROL_SYNC; ctrl.ldctl_iscritical = 1; /* timelimit? */ if ( ls->ls_timelimit ) { tv.tv_sec = ls->ls_timelimit; tvp = &tv; } /* actually run the search */ rc = ldap_search_ext( ls->ls_ld, ls->ls_base, ls->ls_scope, ls->ls_filter, ls->ls_attrs, 0, ctrls, NULL, tvp, ls->ls_sizelimit, &ls->ls_msgid ); #ifdef LDAP_SYNC_TRACE fprintf( stderr, "%sldap_search_ext(\"%s\", %d, \"%s\") == %d\n", rc ? "!!! " : "", ls->ls_base, ls->ls_scope, ls->ls_filter, rc ); #endif /* LDAP_SYNC_TRACE */ if ( rc != LDAP_SUCCESS ) { goto done; } /* initial content/content update phase */ for ( ; ; ) { LDAPMessage *msg = NULL; /* NOTE: this very short timeout is just to let * ldap_result() yield long enough to get something */ tv.tv_sec = 0; tv.tv_usec = 100000; rc = ldap_result( ls->ls_ld, ls->ls_msgid, LDAP_MSG_RECEIVED, &tv, &res ); #ifdef LDAP_SYNC_TRACE fprintf( stderr, "\t%sldap_result(%d) == %d\n", rc == -1 ? "!!! " : "", ls->ls_msgid, rc ); #endif /* LDAP_SYNC_TRACE */ switch ( rc ) { case 0: /* * timeout * * TODO: can do something else in the meanwhile) */ break; case -1: /* smtg bad! */ goto done; default: for ( msg = ldap_first_message( ls->ls_ld, res ); msg != NULL; msg = ldap_next_message( ls->ls_ld, msg ) ) { int refreshDone; switch ( ldap_msgtype( msg ) ) { case LDAP_RES_SEARCH_ENTRY: rc = ldap_sync_search_entry( ls, res ); break; case LDAP_RES_SEARCH_REFERENCE: rc = ldap_sync_search_reference( ls, res ); break; case LDAP_RES_SEARCH_RESULT: rc = ldap_sync_search_result( ls, res ); goto done_search; case LDAP_RES_INTERMEDIATE: rc = ldap_sync_search_intermediate( ls, res, &refreshDone ); if ( rc != LDAP_SUCCESS || refreshDone ) { goto done_search; } break; default: #ifdef LDAP_SYNC_TRACE fprintf( stderr, "\tgot something unexpected...\n" ); #endif /* LDAP_SYNC_TRACE */ ldap_msgfree( res ); rc = LDAP_OTHER; goto done; } } ldap_msgfree( res ); res = NULL; break; } } done_search:; ldap_msgfree( res ); done:; if ( ber != NULL ) { ber_free( ber, 1 ); } return rc; } /* * initialize the refreshOnly sync */ int ldap_sync_init_refresh_only( ldap_sync_t *ls ) { return ldap_sync_init( ls, LDAP_SYNC_REFRESH_ONLY ); } /* * initialize the refreshAndPersist sync */ int ldap_sync_init_refresh_and_persist( ldap_sync_t *ls ) { return ldap_sync_init( ls, LDAP_SYNC_REFRESH_AND_PERSIST ); } /* * poll for new responses */ int ldap_sync_poll( ldap_sync_t *ls ) { struct timeval tv, *tvp = NULL; LDAPMessage *res = NULL, *msg; int rc = 0; #ifdef LDAP_SYNC_TRACE fprintf( stderr, "ldap_sync_poll...\n" ); #endif /* LDAP_SYNC_TRACE */ assert( ls != NULL ); assert( ls->ls_ld != NULL ); if ( ls->ls_timeout != -1 ) { tv.tv_sec = ls->ls_timeout; tv.tv_usec = 0; tvp = &tv; } rc = ldap_result( ls->ls_ld, ls->ls_msgid, LDAP_MSG_RECEIVED, tvp, &res ); if ( rc <= 0 ) { return rc; } for ( msg = ldap_first_message( ls->ls_ld, res ); msg; msg = ldap_next_message( ls->ls_ld, msg ) ) { int refreshDone; switch ( ldap_msgtype( msg ) ) { case LDAP_RES_SEARCH_ENTRY: rc = ldap_sync_search_entry( ls, res ); break; case LDAP_RES_SEARCH_REFERENCE: rc = ldap_sync_search_reference( ls, res ); break; case LDAP_RES_SEARCH_RESULT: rc = ldap_sync_search_result( ls, res ); goto done_search; case LDAP_RES_INTERMEDIATE: rc = ldap_sync_search_intermediate( ls, res, &refreshDone ); if ( rc != LDAP_SUCCESS || refreshDone ) { goto done_search; } break; default: #ifdef LDAP_SYNC_TRACE fprintf( stderr, "\tgot something unexpected...\n" ); #endif /* LDAP_SYNC_TRACE */ ldap_msgfree( res ); rc = LDAP_OTHER; goto done; } } done_search:; ldap_msgfree( res ); done:; return rc; }