2 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
4 * Copyright 2006-2011 The OpenLDAP Foundation.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted only as authorized by the OpenLDAP
11 * A copy of this license is available in the file LICENSE in the
12 * top-level directory of the distribution or, alternatively, at
13 * <http://www.OpenLDAP.org/license.html>.
16 * This program was originally developed by Pierangelo Masarati
17 * for inclusion in OpenLDAP Software.
21 * Proof-of-concept API that implement the client-side
22 * of the "LDAP Content Sync Operation" (RFC 4533)
31 #ifdef LDAP_SYNC_TRACE
33 ldap_sync_state2str( int state )
36 case LDAP_SYNC_PRESENT:
37 return "LDAP_SYNC_PRESENT";
40 return "LDAP_SYNC_ADD";
42 case LDAP_SYNC_MODIFY:
43 return "LDAP_SYNC_MODIFY";
45 case LDAP_SYNC_DELETE:
46 return "LDAP_SYNC_DELETE";
55 * initialize the persistent search structure
58 ldap_sync_initialize( ldap_sync_t *ls_in )
60 ldap_sync_t *ls = ls_in;
63 ls = ldap_memalloc( sizeof( ldap_sync_t ) );
69 memset( ls, 0, sizeof( ldap_sync_t ) );
72 ls->ls_scope = LDAP_SCOPE_SUBTREE;
79 * destroy the persistent search structure
82 ldap_sync_destroy( ldap_sync_t *ls, int freeit )
86 if ( ls->ls_base != NULL ) {
87 ldap_memfree( ls->ls_base );
91 if ( ls->ls_filter != NULL ) {
92 ldap_memfree( ls->ls_filter );
96 if ( ls->ls_attrs != NULL ) {
99 for ( i = 0; ls->ls_attrs[ i ] != NULL; i++ ) {
100 ldap_memfree( ls->ls_attrs[ i ] );
102 ldap_memfree( ls->ls_attrs );
106 if ( ls->ls_ld != NULL ) {
107 (void)ldap_unbind_ext( ls->ls_ld, NULL, NULL );
108 #ifdef LDAP_SYNC_TRACE
109 fprintf( stderr, "ldap_unbind_ext()\n" );
110 #endif /* LDAP_SYNC_TRACE */
114 if ( ls->ls_cookie.bv_val != NULL ) {
115 ldap_memfree( ls->ls_cookie.bv_val );
116 ls->ls_cookie.bv_val = NULL;
125 * handle the LDAP_RES_SEARCH_ENTRY response
128 ldap_sync_search_entry( ldap_sync_t *ls, LDAPMessage *res )
130 LDAPControl **ctrls = NULL;
133 BerElement *ber = NULL;
134 struct berval entryUUID = { 0 },
138 ldap_sync_refresh_t phase;
140 #ifdef LDAP_SYNC_TRACE
141 fprintf( stderr, "\tgot LDAP_RES_SEARCH_ENTRY\n" );
142 #endif /* LDAP_SYNC_TRACE */
144 assert( ls != NULL );
145 assert( res != NULL );
147 phase = ls->ls_refreshPhase;
156 * - Sync State Control is "add"
159 /* the control MUST be present */
161 /* extract controls */
162 ldap_get_entry_controls( ls->ls_ld, res, &ctrls );
163 if ( ctrls == NULL ) {
167 /* lookup the sync state control */
168 for ( i = 0; ctrls[ i ] != NULL; i++ ) {
169 if ( strcmp( ctrls[ i ]->ldctl_oid, LDAP_CONTROL_SYNC_STATE ) == 0 ) {
174 /* control must be present; there might be other... */
175 if ( ctrls[ i ] == NULL ) {
180 ber = ber_init( &ctrls[ i ]->ldctl_value );
184 /* scan entryUUID in-place ("m") */
185 if ( ber_scanf( ber, "{em" /*"}"*/, &state, &entryUUID ) == LBER_ERROR
186 || entryUUID.bv_len == 0 )
191 if ( ber_peek_tag( ber, &len ) == LDAP_TAG_SYNC_COOKIE ) {
192 /* scan cookie in-place ("m") */
193 if ( ber_scanf( ber, /*"{"*/ "m}", &cookie ) == LBER_ERROR ) {
196 if ( cookie.bv_val != NULL ) {
197 ber_bvreplace( &ls->ls_cookie, &cookie );
199 #ifdef LDAP_SYNC_TRACE
200 fprintf( stderr, "\t\tgot cookie=%s\n",
201 cookie.bv_val ? cookie.bv_val : "(null)" );
202 #endif /* LDAP_SYNC_TRACE */
206 case LDAP_SYNC_PRESENT:
207 case LDAP_SYNC_DELETE:
209 case LDAP_SYNC_MODIFY:
210 /* NOTE: ldap_sync_refresh_t is defined
211 * as the corresponding LDAP_SYNC_*
212 * for the 4 above cases */
214 #ifdef LDAP_SYNC_TRACE
215 fprintf( stderr, "\t\tgot syncState=%s\n", ldap_sync_state2str( state ) );
216 #endif /* LDAP_SYNC_TRACE */
220 #ifdef LDAP_SYNC_TRACE
221 fprintf( stderr, "\t\tgot unknown syncState=%d\n", state );
222 #endif /* LDAP_SYNC_TRACE */
226 rc = ls->ls_search_entry
227 ? ls->ls_search_entry( ls, res, &entryUUID, phase )
235 if ( ctrls != NULL ) {
236 ldap_controls_free( ctrls );
243 * handle the LDAP_RES_SEARCH_REFERENCE response
244 * (to be implemented yet)
247 ldap_sync_search_reference( ldap_sync_t *ls, LDAPMessage *res )
251 #ifdef LDAP_SYNC_TRACE
252 fprintf( stderr, "\tgot LDAP_RES_SEARCH_REFERENCE\n" );
253 #endif /* LDAP_SYNC_TRACE */
255 assert( ls != NULL );
256 assert( res != NULL );
258 if ( ls->ls_search_reference ) {
259 rc = ls->ls_search_reference( ls, res );
266 * handle the LDAP_RES_SEARCH_RESULT response
269 ldap_sync_search_result( ldap_sync_t *ls, LDAPMessage *res )
272 char *matched = NULL,
274 LDAPControl **ctrls = NULL;
276 int refreshDeletes = -1;
278 #ifdef LDAP_SYNC_TRACE
279 fprintf( stderr, "\tgot LDAP_RES_SEARCH_RESULT\n" );
280 #endif /* LDAP_SYNC_TRACE */
282 assert( ls != NULL );
283 assert( res != NULL );
285 /* should not happen in refreshAndPersist... */
286 rc = ldap_parse_result( ls->ls_ld,
287 res, &err, &matched, &msg, NULL, &ctrls, 0 );
288 #ifdef LDAP_SYNC_TRACE
290 "\tldap_parse_result(%d, \"%s\", \"%s\") == %d\n",
292 matched ? matched : "",
295 #endif /* LDAP_SYNC_TRACE */
296 if ( rc == LDAP_SUCCESS ) {
300 ls->ls_refreshPhase = LDAP_SYNC_CAPI_DONE;
305 BerElement *ber = NULL;
307 struct berval cookie = { 0 };
311 /* deal with control; then fallthru to handler */
312 if ( ctrls == NULL ) {
316 /* lookup the sync state control */
317 for ( i = 0; ctrls[ i ] != NULL; i++ ) {
318 if ( strcmp( ctrls[ i ]->ldctl_oid,
319 LDAP_CONTROL_SYNC_DONE ) == 0 )
325 /* control must be present; there might be other... */
326 if ( ctrls[ i ] == NULL ) {
331 ber = ber_init( &ctrls[ i ]->ldctl_value );
336 if ( ber_scanf( ber, "{" /*"}"*/) == LBER_ERROR ) {
339 if ( ber_peek_tag( ber, &len ) == LDAP_TAG_SYNC_COOKIE ) {
340 if ( ber_scanf( ber, "m", &cookie ) == LBER_ERROR ) {
343 if ( cookie.bv_val != NULL ) {
344 ber_bvreplace( &ls->ls_cookie, &cookie );
346 #ifdef LDAP_SYNC_TRACE
347 fprintf( stderr, "\t\tgot cookie=%s\n",
348 cookie.bv_val ? cookie.bv_val : "(null)" );
349 #endif /* LDAP_SYNC_TRACE */
353 if ( ber_peek_tag( ber, &len ) == LDAP_TAG_REFRESHDELETES ) {
354 if ( ber_scanf( ber, "b", &refreshDeletes ) == LBER_ERROR ) {
357 if ( refreshDeletes ) {
362 if ( ber_scanf( ber, /*"{"*/ "}" ) != LBER_ERROR ) {
368 if ( rc != LDAP_SUCCESS ) {
372 #ifdef LDAP_SYNC_TRACE
373 fprintf( stderr, "\t\tgot refreshDeletes=%s\n",
374 refreshDeletes ? "TRUE" : "FALSE" );
375 #endif /* LDAP_SYNC_TRACE */
377 /* FIXME: what should we do with the refreshDelete? */
378 switch ( refreshDeletes ) {
380 ls->ls_refreshPhase = LDAP_SYNC_CAPI_PRESENTS;
384 ls->ls_refreshPhase = LDAP_SYNC_CAPI_DELETES;
390 case LDAP_SYNC_REFRESH_REQUIRED:
391 /* TODO: check for Sync Done Control */
392 /* FIXME: perhaps the handler should be called
393 * also in case of failure; we'll deal with this
394 * later when implementing refreshOnly */
395 if ( ls->ls_search_result ) {
396 err = ls->ls_search_result( ls, res, refreshDeletes );
402 if ( matched != NULL ) {
403 ldap_memfree( matched );
410 if ( ctrls != NULL ) {
411 ldap_controls_free( ctrls );
414 ls->ls_refreshPhase = LDAP_SYNC_CAPI_DONE;
420 * handle the LDAP_RES_INTERMEDIATE response
423 ldap_sync_search_intermediate( ldap_sync_t *ls, LDAPMessage *res, int *refreshDone )
427 struct berval *retdata = NULL;
428 BerElement *ber = NULL;
430 ber_tag_t syncinfo_tag;
431 struct berval cookie;
432 int refreshDeletes = 0;
433 BerVarray syncUUIDs = NULL;
434 ldap_sync_refresh_t phase;
436 #ifdef LDAP_SYNC_TRACE
437 fprintf( stderr, "\tgot LDAP_RES_INTERMEDIATE\n" );
438 #endif /* LDAP_SYNC_TRACE */
440 assert( ls != NULL );
441 assert( res != NULL );
442 assert( refreshDone != NULL );
446 rc = ldap_parse_intermediate( ls->ls_ld, res,
447 &retoid, &retdata, NULL, 0 );
448 #ifdef LDAP_SYNC_TRACE
449 fprintf( stderr, "\t%sldap_parse_intermediate(%s) == %d\n",
450 rc != LDAP_SUCCESS ? "!!! " : "",
451 retoid == NULL ? "\"\"" : retoid,
453 #endif /* LDAP_SYNC_TRACE */
454 /* parsing must be successful, and yield the OID
455 * of the sync info intermediate response */
456 if ( rc != LDAP_SUCCESS ) {
462 if ( retoid == NULL || strcmp( retoid, LDAP_SYNC_INFO ) != 0 ) {
466 /* init ber using the value in the response */
467 ber = ber_init( retdata );
472 syncinfo_tag = ber_peek_tag( ber, &len );
473 switch ( syncinfo_tag ) {
474 case LDAP_TAG_SYNC_NEW_COOKIE:
475 if ( ber_scanf( ber, "m", &cookie ) == LBER_ERROR ) {
478 if ( cookie.bv_val != NULL ) {
479 ber_bvreplace( &ls->ls_cookie, &cookie );
481 #ifdef LDAP_SYNC_TRACE
482 fprintf( stderr, "\t\tgot cookie=%s\n",
483 cookie.bv_val ? cookie.bv_val : "(null)" );
484 #endif /* LDAP_SYNC_TRACE */
487 case LDAP_TAG_SYNC_REFRESH_DELETE:
488 case LDAP_TAG_SYNC_REFRESH_PRESENT:
489 if ( syncinfo_tag == LDAP_TAG_SYNC_REFRESH_DELETE ) {
490 #ifdef LDAP_SYNC_TRACE
491 fprintf( stderr, "\t\tgot refreshDelete\n" );
492 #endif /* LDAP_SYNC_TRACE */
493 switch ( ls->ls_refreshPhase ) {
494 case LDAP_SYNC_CAPI_NONE:
495 case LDAP_SYNC_CAPI_PRESENTS:
496 ls->ls_refreshPhase = LDAP_SYNC_CAPI_DELETES;
500 /* TODO: impossible; handle */
505 #ifdef LDAP_SYNC_TRACE
506 fprintf( stderr, "\t\tgot refreshPresent\n" );
507 #endif /* LDAP_SYNC_TRACE */
508 switch ( ls->ls_refreshPhase ) {
509 case LDAP_SYNC_CAPI_NONE:
510 ls->ls_refreshPhase = LDAP_SYNC_CAPI_PRESENTS;
514 /* TODO: impossible; handle */
519 if ( ber_scanf( ber, "{" /*"}"*/ ) == LBER_ERROR ) {
522 if ( ber_peek_tag( ber, &len ) == LDAP_TAG_SYNC_COOKIE ) {
523 if ( ber_scanf( ber, "m", &cookie ) == LBER_ERROR ) {
526 if ( cookie.bv_val != NULL ) {
527 ber_bvreplace( &ls->ls_cookie, &cookie );
529 #ifdef LDAP_SYNC_TRACE
530 fprintf( stderr, "\t\tgot cookie=%s\n",
531 cookie.bv_val ? cookie.bv_val : "(null)" );
532 #endif /* LDAP_SYNC_TRACE */
536 if ( ber_peek_tag( ber, &len ) == LDAP_TAG_REFRESHDONE ) {
537 if ( ber_scanf( ber, "b", refreshDone ) == LBER_ERROR ) {
542 #ifdef LDAP_SYNC_TRACE
543 fprintf( stderr, "\t\tgot refreshDone=%s\n",
544 *refreshDone ? "TRUE" : "FALSE" );
545 #endif /* LDAP_SYNC_TRACE */
547 if ( ber_scanf( ber, /*"{"*/ "}" ) == LBER_ERROR ) {
551 if ( *refreshDone ) {
552 ls->ls_refreshPhase = LDAP_SYNC_CAPI_DONE;
555 if ( ls->ls_intermediate ) {
556 ls->ls_intermediate( ls, res, NULL, ls->ls_refreshPhase );
561 case LDAP_TAG_SYNC_ID_SET:
562 #ifdef LDAP_SYNC_TRACE
563 fprintf( stderr, "\t\tgot syncIdSet\n" );
564 #endif /* LDAP_SYNC_TRACE */
565 if ( ber_scanf( ber, "{" /*"}"*/ ) == LBER_ERROR ) {
568 if ( ber_peek_tag( ber, &len ) == LDAP_TAG_SYNC_COOKIE ) {
569 if ( ber_scanf( ber, "m", &cookie ) == LBER_ERROR ) {
572 if ( cookie.bv_val != NULL ) {
573 ber_bvreplace( &ls->ls_cookie, &cookie );
575 #ifdef LDAP_SYNC_TRACE
576 fprintf( stderr, "\t\tgot cookie=%s\n",
577 cookie.bv_val ? cookie.bv_val : "(null)" );
578 #endif /* LDAP_SYNC_TRACE */
581 if ( ber_peek_tag( ber, &len ) == LDAP_TAG_REFRESHDELETES ) {
582 if ( ber_scanf( ber, "b", &refreshDeletes ) == LBER_ERROR ) {
587 if ( ber_scanf( ber, /*"{"*/ "[W]}", &syncUUIDs ) == LBER_ERROR
588 || syncUUIDs == NULL )
593 #ifdef LDAP_SYNC_TRACE
597 fprintf( stderr, "\t\tgot refreshDeletes=%s\n",
598 refreshDeletes ? "TRUE" : "FALSE" );
599 for ( i = 0; syncUUIDs[ i ].bv_val != NULL; i++ ) {
601 fprintf( stderr, "\t\t%s\n",
602 lutil_uuidstr_from_normalized(
603 syncUUIDs[ i ].bv_val, syncUUIDs[ i ].bv_len,
604 buf, sizeof( buf ) ) );
607 #endif /* LDAP_SYNC_TRACE */
609 if ( refreshDeletes ) {
610 phase = LDAP_SYNC_CAPI_DELETES_IDSET;
613 phase = LDAP_SYNC_CAPI_PRESENTS_IDSET;
616 /* FIXME: should touch ls->ls_refreshPhase? */
617 if ( ls->ls_intermediate ) {
618 ls->ls_intermediate( ls, res, syncUUIDs, phase );
621 ber_bvarray_free( syncUUIDs );
625 #ifdef LDAP_SYNC_TRACE
626 fprintf( stderr, "\t\tunknown tag!\n" );
627 #endif /* LDAP_SYNC_TRACE */
638 if ( retoid != NULL ) {
639 ldap_memfree( retoid );
642 if ( retdata != NULL ) {
643 ber_bvfree( retdata );
650 * initialize the sync
653 ldap_sync_init( ldap_sync_t *ls, int mode )
655 LDAPControl ctrl = { 0 },
657 BerElement *ber = NULL;
659 struct timeval tv = { 0 },
661 LDAPMessage *res = NULL;
663 #ifdef LDAP_SYNC_TRACE
664 fprintf( stderr, "ldap_sync_init(%s)...\n",
665 mode == LDAP_SYNC_REFRESH_AND_PERSIST ?
666 "LDAP_SYNC_REFRESH_AND_PERSIST" :
667 ( mode == LDAP_SYNC_REFRESH_ONLY ?
668 "LDAP_SYNC_REFRESH_ONLY" : "unknown" ) );
669 #endif /* LDAP_SYNC_TRACE */
671 assert( ls != NULL );
672 assert( ls->ls_ld != NULL );
674 /* support both refreshOnly and refreshAndPersist */
676 case LDAP_SYNC_REFRESH_AND_PERSIST:
677 case LDAP_SYNC_REFRESH_ONLY:
681 fprintf( stderr, "ldap_sync_init: unknown mode=%d\n", mode );
682 return LDAP_PARAM_ERROR;
685 /* check consistency of cookie and reloadHint at initial refresh */
686 if ( ls->ls_cookie.bv_val == NULL && ls->ls_reloadHint != 0 ) {
687 fprintf( stderr, "ldap_sync_init: inconsistent cookie/rhint\n" );
688 return LDAP_PARAM_ERROR;
694 /* prepare the Sync Request control */
695 ber = ber_alloc_t( LBER_USE_DER );
696 #ifdef LDAP_SYNC_TRACE
697 fprintf( stderr, "%sber_alloc_t() %s= NULL\n",
698 ber == NULL ? "!!! " : "",
699 ber == NULL ? "=" : "!" );
700 #endif /* LDAP_SYNC_TRACE */
706 ls->ls_refreshPhase = LDAP_SYNC_CAPI_NONE;
708 if ( ls->ls_cookie.bv_val != NULL ) {
709 ber_printf( ber, "{eOb}", mode,
710 &ls->ls_cookie, ls->ls_reloadHint );
713 ber_printf( ber, "{eb}", mode, ls->ls_reloadHint );
716 rc = ber_flatten2( ber, &ctrl.ldctl_value, 0 );
717 #ifdef LDAP_SYNC_TRACE
719 "%sber_flatten2() == %d\n",
722 #endif /* LDAP_SYNC_TRACE */
728 /* make the control critical, as we cannot proceed without */
729 ctrl.ldctl_oid = LDAP_CONTROL_SYNC;
730 ctrl.ldctl_iscritical = 1;
733 if ( ls->ls_timelimit ) {
734 tv.tv_sec = ls->ls_timelimit;
738 /* actually run the search */
739 rc = ldap_search_ext( ls->ls_ld,
740 ls->ls_base, ls->ls_scope, ls->ls_filter,
741 ls->ls_attrs, 0, ctrls, NULL,
742 tvp, ls->ls_sizelimit, &ls->ls_msgid );
743 #ifdef LDAP_SYNC_TRACE
745 "%sldap_search_ext(\"%s\", %d, \"%s\") == %d\n",
747 ls->ls_base, ls->ls_scope, ls->ls_filter, rc );
748 #endif /* LDAP_SYNC_TRACE */
749 if ( rc != LDAP_SUCCESS ) {
753 /* initial content/content update phase */
755 LDAPMessage *msg = NULL;
757 /* NOTE: this very short timeout is just to let
758 * ldap_result() yield long enough to get something */
762 rc = ldap_result( ls->ls_ld, ls->ls_msgid,
763 LDAP_MSG_RECEIVED, &tv, &res );
764 #ifdef LDAP_SYNC_TRACE
766 "\t%sldap_result(%d) == %d\n",
767 rc == -1 ? "!!! " : "",
769 #endif /* LDAP_SYNC_TRACE */
775 * TODO: can do something else in the meanwhile)
784 for ( msg = ldap_first_message( ls->ls_ld, res );
786 msg = ldap_next_message( ls->ls_ld, msg ) )
790 switch ( ldap_msgtype( msg ) ) {
791 case LDAP_RES_SEARCH_ENTRY:
792 rc = ldap_sync_search_entry( ls, res );
795 case LDAP_RES_SEARCH_REFERENCE:
796 rc = ldap_sync_search_reference( ls, res );
799 case LDAP_RES_SEARCH_RESULT:
800 rc = ldap_sync_search_result( ls, res );
803 case LDAP_RES_INTERMEDIATE:
804 rc = ldap_sync_search_intermediate( ls, res, &refreshDone );
805 if ( rc != LDAP_SUCCESS || refreshDone ) {
811 #ifdef LDAP_SYNC_TRACE
812 fprintf( stderr, "\tgot something unexpected...\n" );
813 #endif /* LDAP_SYNC_TRACE */
839 * initialize the refreshOnly sync
842 ldap_sync_init_refresh_only( ldap_sync_t *ls )
844 return ldap_sync_init( ls, LDAP_SYNC_REFRESH_ONLY );
848 * initialize the refreshAndPersist sync
851 ldap_sync_init_refresh_and_persist( ldap_sync_t *ls )
853 return ldap_sync_init( ls, LDAP_SYNC_REFRESH_AND_PERSIST );
857 * poll for new responses
860 ldap_sync_poll( ldap_sync_t *ls )
864 LDAPMessage *res = NULL,
868 #ifdef LDAP_SYNC_TRACE
869 fprintf( stderr, "ldap_sync_poll...\n" );
870 #endif /* LDAP_SYNC_TRACE */
872 assert( ls != NULL );
873 assert( ls->ls_ld != NULL );
875 if ( ls->ls_timeout != -1 ) {
876 tv.tv_sec = ls->ls_timeout;
881 rc = ldap_result( ls->ls_ld, ls->ls_msgid,
882 LDAP_MSG_RECEIVED, tvp, &res );
887 for ( msg = ldap_first_message( ls->ls_ld, res );
889 msg = ldap_next_message( ls->ls_ld, msg ) )
893 switch ( ldap_msgtype( msg ) ) {
894 case LDAP_RES_SEARCH_ENTRY:
895 rc = ldap_sync_search_entry( ls, res );
898 case LDAP_RES_SEARCH_REFERENCE:
899 rc = ldap_sync_search_reference( ls, res );
902 case LDAP_RES_SEARCH_RESULT:
903 rc = ldap_sync_search_result( ls, res );
906 case LDAP_RES_INTERMEDIATE:
907 rc = ldap_sync_search_intermediate( ls, res, &refreshDone );
908 if ( rc != LDAP_SUCCESS || refreshDone ) {
914 #ifdef LDAP_SYNC_TRACE
915 fprintf( stderr, "\tgot something unexpected...\n" );
916 #endif /* LDAP_SYNC_TRACE */