2 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
4 * Copyright 2006-2007 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 * used for debug purposes
36 print_UUID( char *buf, size_t len, unsigned char *UUID )
39 "%02x%02x%02x%02x-%02x%02x-%02x%02x-"
40 "%02x%02x-%02x%02x%02x%02x%02x%02x",
61 ldap_sync_state2str( int state )
64 case LDAP_SYNC_PRESENT:
65 return "LDAP_SYNC_PRESENT";
68 return "LDAP_SYNC_ADD";
70 case LDAP_SYNC_MODIFY:
71 return "LDAP_SYNC_MODIFY";
73 case LDAP_SYNC_DELETE:
74 return "LDAP_SYNC_DELETE";
83 * initialize the persistent search structure
86 ldap_sync_initialize( ldap_sync_t *ls_in )
88 ldap_sync_t *ls = ls_in;
91 ls = ldap_memalloc( sizeof( ldap_sync_t ) );
97 memset( ls, 0, sizeof( ldap_sync_t ) );
100 ls->ls_scope = LDAP_SCOPE_SUBTREE;
107 * destroy the persistent search structure
110 ldap_sync_destroy( ldap_sync_t *ls, int freeit )
112 assert( ls != NULL );
114 if ( ls->ls_base != NULL ) {
115 ldap_memfree( ls->ls_base );
119 if ( ls->ls_filter != NULL ) {
120 ldap_memfree( ls->ls_filter );
121 ls->ls_filter = NULL;
124 if ( ls->ls_attrs != NULL ) {
127 for ( i = 0; ls->ls_attrs[ i ] != NULL; i++ ) {
128 ldap_memfree( ls->ls_attrs[ i ] );
130 ldap_memfree( ls->ls_attrs );
134 if ( ls->ls_ld != NULL ) {
135 (void)ldap_unbind_ext( ls->ls_ld, NULL, NULL );
136 #ifdef LDAP_SYNC_TRACE
137 fprintf( stderr, "ldap_unbind_ext()\n" );
138 #endif /* LDAP_SYNC_TRACE */
142 if ( ls->ls_cookie.bv_val != NULL ) {
143 ldap_memfree( ls->ls_cookie.bv_val );
144 ls->ls_cookie.bv_val = NULL;
153 * handle the LDAP_RES_SEARCH_ENTRY response
156 ldap_sync_search_entry( ldap_sync_t *ls, LDAPMessage *res )
158 LDAPControl **ctrls = NULL;
159 int rc = LDAP_SUCCESS,
161 BerElement *ber = NULL;
162 struct berval entryUUID = { 0 },
166 ldap_sync_refresh_t phase = ls->ls_refreshPhase;
168 #ifdef LDAP_SYNC_TRACE
169 fprintf( stderr, "\tgot LDAP_RES_SEARCH_ENTRY\n" );
170 #endif /* LDAP_SYNC_TRACE */
172 assert( ls != NULL );
173 assert( res != NULL );
182 * - Sync State Control is "add"
185 /* the control MUST be present */
187 /* extract controls */
188 ldap_get_entry_controls( ls->ls_ld, res, &ctrls );
189 if ( ctrls == NULL ) {
194 /* lookup the sync state control */
195 for ( i = 0; ctrls[ i ] != NULL; i++ ) {
196 if ( strcmp( ctrls[ i ]->ldctl_oid, LDAP_CONTROL_SYNC_STATE ) == 0 ) {
201 /* control must be present; there might be other... */
202 if ( ctrls[ i ] == NULL ) {
208 ber = ber_init( &ctrls[ i ]->ldctl_value );
209 /* scan entryUUID in-place ("m") */
210 ber_scanf( ber, "{em" /*"}"*/, &state, &entryUUID );
211 if ( entryUUID.bv_len == 0 ) {
216 if ( ber_peek_tag( ber, &len ) == LDAP_TAG_SYNC_COOKIE ) {
217 /* scan cookie in-place ("m") */
218 ber_scanf( ber, /*"{"*/ "m}", &cookie );
219 if ( cookie.bv_val != NULL ) {
220 ber_bvreplace( &ls->ls_cookie, &cookie );
222 #ifdef LDAP_SYNC_TRACE
223 fprintf( stderr, "\t\tgot cookie=%s\n",
224 cookie.bv_val ? cookie.bv_val : "(null)" );
225 #endif /* LDAP_SYNC_TRACE */
229 case LDAP_SYNC_PRESENT:
230 case LDAP_SYNC_DELETE:
232 case LDAP_SYNC_MODIFY:
233 /* NOTE: ldap_sync_refresh_t is defined
234 * as the corresponding LDAP_SYNC_*
235 * for the 4 above cases */
237 #ifdef LDAP_SYNC_TRACE
238 fprintf( stderr, "\t\tgot syncState=%s\n", ldap_sync_state2str( state ) );
239 #endif /* LDAP_SYNC_TRACE */
244 #ifdef LDAP_SYNC_TRACE
245 fprintf( stderr, "\t\tgot unknown syncState=%d\n", state );
246 #endif /* LDAP_SYNC_TRACE */
250 if ( ls->ls_search_entry ) {
251 rc = ls->ls_search_entry( ls, res, &entryUUID, phase );
259 if ( ctrls != NULL ) {
260 ldap_controls_free( ctrls );
267 * handle the LDAP_RES_SEARCH_REFERENCE response
268 * (to be implemented yet)
271 ldap_sync_search_reference( ldap_sync_t *ls, LDAPMessage *res )
275 #ifdef LDAP_SYNC_TRACE
276 fprintf( stderr, "\tgot LDAP_RES_SEARCH_REFERENCE\n" );
277 #endif /* LDAP_SYNC_TRACE */
279 assert( ls != NULL );
280 assert( res != NULL );
282 if ( ls->ls_search_reference ) {
283 rc = ls->ls_search_reference( ls, res );
290 * handle the LDAP_RES_SEARCH_RESULT response
293 ldap_sync_search_result( ldap_sync_t *ls, LDAPMessage *res )
296 char *matched = NULL,
298 LDAPControl **ctrls = NULL;
300 int refreshDeletes = -1;
302 #ifdef LDAP_SYNC_TRACE
303 fprintf( stderr, "\tgot LDAP_RES_SEARCH_RESULT\n" );
304 #endif /* LDAP_SYNC_TRACE */
306 assert( ls != NULL );
307 assert( res != NULL );
309 /* should not happen in refreshAndPersist... */
310 rc = ldap_parse_result( ls->ls_ld,
311 res, &err, &matched, &msg, NULL, &ctrls, 0 );
312 #ifdef LDAP_SYNC_TRACE
314 "\tldap_parse_result(%d, \"%s\", \"%s\") == %d\n",
316 matched ? matched : "",
319 #endif /* LDAP_SYNC_TRACE */
320 if ( rc == LDAP_SUCCESS ) {
324 ls->ls_refreshPhase = LDAP_SYNC_CAPI_DONE;
329 BerElement *ber = NULL;
331 struct berval cookie = { 0 };
333 /* deal with control; then fallthru to handler */
334 if ( ctrls == NULL ) {
339 /* lookup the sync state control */
340 for ( i = 0; ctrls[ i ] != NULL; i++ ) {
341 if ( strcmp( ctrls[ i ]->ldctl_oid,
342 LDAP_CONTROL_SYNC_DONE ) == 0 )
348 /* control must be present; there might be other... */
349 if ( ctrls[ i ] == NULL ) {
355 ber = ber_init( &ctrls[ i ]->ldctl_value );
357 ber_scanf( ber, "{" /*"}"*/);
358 if ( ber_peek_tag( ber, &len ) == LDAP_TAG_SYNC_COOKIE ) {
359 ber_scanf( ber, "m", &cookie );
360 if ( cookie.bv_val != NULL ) {
361 ber_bvreplace( &ls->ls_cookie, &cookie );
363 #ifdef LDAP_SYNC_TRACE
364 fprintf( stderr, "\t\tgot cookie=%s\n",
365 cookie.bv_val ? cookie.bv_val : "(null)" );
366 #endif /* LDAP_SYNC_TRACE */
370 if ( ber_peek_tag( ber, &len ) == LDAP_TAG_REFRESHDELETES ) {
371 ber_scanf( ber, "b", &refreshDeletes );
372 if ( refreshDeletes ) {
377 ber_scanf( ber, /*"{"*/ "}" );
379 /* NOTE: if any goto/return between ber_init() and here
380 * is introduced, don't forget to ber_free() */
383 #ifdef LDAP_SYNC_TRACE
384 fprintf( stderr, "\t\tgot refreshDeletes=%s\n",
385 refreshDeletes ? "TRUE" : "FALSE" );
386 #endif /* LDAP_SYNC_TRACE */
388 /* FIXME: what should we do with the refreshDelete? */
389 switch ( refreshDeletes ) {
391 ls->ls_refreshPhase = LDAP_SYNC_CAPI_PRESENTS;
395 ls->ls_refreshPhase = LDAP_SYNC_CAPI_DELETES;
401 case LDAP_SYNC_REFRESH_REQUIRED:
402 /* TODO: check for Sync Done Control */
403 /* FIXME: perhaps the handler should be called
404 * also in case of failure; we'll deal with this
405 * later when implementing refreshOnly */
406 if ( ls->ls_search_result ) {
407 err = ls->ls_search_result( ls, res, refreshDeletes );
416 if ( matched != NULL ) {
417 ldap_memfree( matched );
424 if ( ctrls != NULL ) {
425 ldap_controls_free( ctrls );
428 ls->ls_refreshPhase = LDAP_SYNC_CAPI_DONE;
434 * handle the LDAP_RES_INTERMEDIATE response
437 ldap_sync_search_intermediate( ldap_sync_t *ls, LDAPMessage *res, int *refreshDone )
441 struct berval *retdata = NULL;
442 BerElement *ber = NULL;
446 struct berval cookie;
447 int refreshDeletes = 0;
448 BerVarray syncUUIDs = NULL;
449 ldap_sync_refresh_t phase;
451 #ifdef LDAP_SYNC_TRACE
452 fprintf( stderr, "\tgot LDAP_RES_INTERMEDIATE\n" );
453 #endif /* LDAP_SYNC_TRACE */
455 assert( ls != NULL );
456 assert( res != NULL );
457 assert( refreshDone != NULL );
461 rc = ldap_parse_intermediate( ls->ls_ld, res,
462 &retoid, &retdata, NULL, 0 );
463 #ifdef LDAP_SYNC_TRACE
464 fprintf( stderr, "\t%sldap_parse_intermediate(%s) == %d\n",
465 rc != LDAP_SUCCESS ? "!!! " : "",
466 retoid == NULL ? "\"\"" : retoid,
468 #endif /* LDAP_SYNC_TRACE */
469 /* parsing must be successful, and yield the OID
470 * of the sync info intermediate response */
471 if ( rc != LDAP_SUCCESS ) {
475 if ( retoid == NULL || strcmp( retoid, LDAP_SYNC_INFO ) != 0 ) {
480 /* init ber using the value in the response */
481 ber = ber_init( retdata );
486 syncinfo_tag = ber_peek_tag( ber, &len );
487 switch ( syncinfo_tag ) {
488 case LDAP_TAG_SYNC_NEW_COOKIE:
489 ber_scanf( ber, "tm", &tag, &cookie );
490 if ( cookie.bv_val != NULL ) {
491 ber_bvreplace( &ls->ls_cookie, &cookie );
493 #ifdef LDAP_SYNC_TRACE
494 fprintf( stderr, "\t\tgot cookie=%s\n",
495 cookie.bv_val ? cookie.bv_val : "(null)" );
496 #endif /* LDAP_SYNC_TRACE */
499 case LDAP_TAG_SYNC_REFRESH_DELETE:
500 case LDAP_TAG_SYNC_REFRESH_PRESENT:
501 if ( syncinfo_tag == LDAP_TAG_SYNC_REFRESH_DELETE ) {
502 #ifdef LDAP_SYNC_TRACE
503 fprintf( stderr, "\t\tgot refreshDelete\n" );
504 #endif /* LDAP_SYNC_TRACE */
505 switch ( ls->ls_refreshPhase ) {
506 case LDAP_SYNC_CAPI_NONE:
507 case LDAP_SYNC_CAPI_PRESENTS:
508 ls->ls_refreshPhase = LDAP_SYNC_CAPI_DELETES;
512 /* TODO: impossible; handle */
518 #ifdef LDAP_SYNC_TRACE
519 fprintf( stderr, "\t\tgot refreshPresent\n" );
520 #endif /* LDAP_SYNC_TRACE */
521 switch ( ls->ls_refreshPhase ) {
522 case LDAP_SYNC_CAPI_NONE:
523 ls->ls_refreshPhase = LDAP_SYNC_CAPI_PRESENTS;
527 /* TODO: impossible; handle */
533 ber_scanf( ber, "t{" /*"}"*/, &tag );
534 if ( ber_peek_tag( ber, &len ) == LDAP_TAG_SYNC_COOKIE ) {
535 ber_scanf( ber, "m", &cookie );
536 if ( cookie.bv_val != NULL ) {
537 ber_bvreplace( &ls->ls_cookie, &cookie );
539 #ifdef LDAP_SYNC_TRACE
540 fprintf( stderr, "\t\tgot cookie=%s\n",
541 cookie.bv_val ? cookie.bv_val : "(null)" );
542 #endif /* LDAP_SYNC_TRACE */
546 if ( ber_peek_tag( ber, &len ) == LDAP_TAG_REFRESHDONE ) {
547 ber_scanf( ber, "b", refreshDone );
550 #ifdef LDAP_SYNC_TRACE
551 fprintf( stderr, "\t\tgot refreshDone=%s\n",
552 *refreshDone ? "TRUE" : "FALSE" );
553 #endif /* LDAP_SYNC_TRACE */
555 ber_scanf( ber, /*"{"*/ "}" );
557 if ( *refreshDone ) {
558 ls->ls_refreshPhase = LDAP_SYNC_CAPI_DONE;
561 if ( ls->ls_intermediate ) {
562 ls->ls_intermediate( ls, res, NULL, ls->ls_refreshPhase );
567 case LDAP_TAG_SYNC_ID_SET:
568 #ifdef LDAP_SYNC_TRACE
569 fprintf( stderr, "\t\tgot syncIdSet\n" );
570 #endif /* LDAP_SYNC_TRACE */
571 ber_scanf( ber, "t{" /*"}"*/, &tag );
572 if ( ber_peek_tag( ber, &len ) == LDAP_TAG_SYNC_COOKIE ) {
573 ber_scanf( ber, "m", &cookie );
574 if ( cookie.bv_val != NULL ) {
575 ber_bvreplace( &ls->ls_cookie, &cookie );
577 #ifdef LDAP_SYNC_TRACE
578 fprintf( stderr, "\t\tgot cookie=%s\n",
579 cookie.bv_val ? cookie.bv_val : "(null)" );
580 #endif /* LDAP_SYNC_TRACE */
583 if ( ber_peek_tag( ber, &len ) == LDAP_TAG_REFRESHDELETES ) {
584 ber_scanf( ber, "b", &refreshDeletes );
587 ber_scanf( ber, "[W]", &syncUUIDs );
588 ber_scanf( ber, /*"{"*/ "}" );
589 if ( syncUUIDs == NULL ) {
594 #ifdef LDAP_SYNC_TRACE
598 fprintf( stderr, "\t\tgot refreshDeletes=%s\n",
599 refreshDeletes ? "TRUE" : "FALSE" );
600 for ( i = 0; syncUUIDs[ i ].bv_val != NULL; i++ ) {
602 fprintf( stderr, "\t\t%s\n",
603 print_UUID( buf, sizeof( buf ),
604 (unsigned char *)syncUUIDs[ i ].bv_val ) );
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 */
636 if ( retoid != NULL ) {
637 ldap_memfree( retoid );
640 if ( retdata != NULL ) {
641 ber_bvfree( retdata );
648 * initialize the sync
651 ldap_sync_init( ldap_sync_t *ls, int mode )
653 LDAPControl ctrl = { 0 },
655 BerElement *ber = NULL;
657 struct timeval tv = { 0 },
659 LDAPMessage *res = NULL;
661 #ifdef LDAP_SYNC_TRACE
662 fprintf( stderr, "ldap_sync_init(%s)...\n",
663 mode == LDAP_SYNC_REFRESH_AND_PERSIST ?
664 "LDAP_SYNC_REFRESH_AND_PERSIST" :
665 ( mode == LDAP_SYNC_REFRESH_ONLY ?
666 "LDAP_SYNC_REFRESH_ONLY" : "unknown" ) );
667 #endif /* LDAP_SYNC_TRACE */
669 assert( ls != NULL );
670 assert( ls->ls_ld != NULL );
672 /* support both refreshOnly and refreshAndPersist */
674 case LDAP_SYNC_REFRESH_AND_PERSIST:
675 case LDAP_SYNC_REFRESH_ONLY:
679 fprintf( stderr, "ldap_sync_init: unknown mode=%d\n", mode );
680 return LDAP_PARAM_ERROR;
683 /* check consistency of cookie and reloadHint at initial refresh */
684 if ( ls->ls_cookie.bv_val == NULL && ls->ls_reloadHint != 0 ) {
685 fprintf( stderr, "ldap_sync_init: inconsistent cookie/rhint\n" );
686 return LDAP_PARAM_ERROR;
692 /* prepare the Sync Request control */
693 ber = ber_alloc_t( LBER_USE_DER );
694 #ifdef LDAP_SYNC_TRACE
695 fprintf( stderr, "%sber_alloc_t() %s= NULL\n",
696 ber == NULL ? "!!! " : "",
697 ber == NULL ? "=" : "!" );
698 #endif /* LDAP_SYNC_TRACE */
704 ls->ls_refreshPhase = LDAP_SYNC_CAPI_NONE;
706 if ( ls->ls_cookie.bv_val != NULL ) {
707 ber_printf( ber, "{eOb}", mode,
708 &ls->ls_cookie, ls->ls_reloadHint );
711 ber_printf( ber, "{eb}", mode, ls->ls_reloadHint );
714 rc = ber_flatten2( ber, &ctrl.ldctl_value, 0 );
715 #ifdef LDAP_SYNC_TRACE
717 "%sber_flatten2() == %d\n",
720 #endif /* LDAP_SYNC_TRACE */
721 if ( rc == LBER_ERROR ) {
726 /* make the control critical, as we cannot proceed without */
727 ctrl.ldctl_oid = LDAP_CONTROL_SYNC;
728 ctrl.ldctl_iscritical = 1;
731 if ( ls->ls_timelimit ) {
732 tv.tv_sec = ls->ls_timelimit;
736 /* actually run the search */
737 rc = ldap_search_ext( ls->ls_ld,
738 ls->ls_base, ls->ls_scope, ls->ls_filter,
739 ls->ls_attrs, 0, ctrls, NULL,
740 tvp, ls->ls_sizelimit, &ls->ls_msgid );
741 #ifdef LDAP_SYNC_TRACE
743 "%sldap_search_ext(\"%s\", %d, \"%s\") == %d\n",
745 ls->ls_base, ls->ls_scope, ls->ls_filter, rc );
746 #endif /* LDAP_SYNC_TRACE */
747 if ( rc != LDAP_SUCCESS ) {
751 /* initial content/content update phase */
753 LDAPMessage *msg = NULL;
755 /* NOTE: this very short timeout is just to let
756 * ldap_result() yield long enough to get something */
760 rc = ldap_result( ls->ls_ld, ls->ls_msgid,
761 LDAP_MSG_RECEIVED, &tv, &res );
762 #ifdef LDAP_SYNC_TRACE
764 "\t%sldap_result(%d) == %d\n",
765 rc == -1 ? "!!! " : "",
767 #endif /* LDAP_SYNC_TRACE */
773 * TODO: can do something else in the meanwhile)
782 for ( msg = ldap_first_message( ls->ls_ld, res );
784 msg = ldap_next_message( ls->ls_ld, msg ) )
788 switch ( ldap_msgtype( msg ) ) {
789 case LDAP_RES_SEARCH_ENTRY:
790 rc = ldap_sync_search_entry( ls, res );
793 case LDAP_RES_SEARCH_REFERENCE:
794 rc = ldap_sync_search_reference( ls, res );
797 case LDAP_RES_SEARCH_RESULT:
798 rc = ldap_sync_search_result( ls, res );
801 case LDAP_RES_INTERMEDIATE:
802 rc = ldap_sync_search_intermediate( ls, res, &refreshDone );
803 if ( rc != LDAP_SUCCESS || refreshDone ) {
809 #ifdef LDAP_SYNC_TRACE
810 fprintf( stderr, "\tgot something unexpected...\n" );
811 #endif /* LDAP_SYNC_TRACE */
837 * initialize the refreshOnly sync
840 ldap_sync_init_refresh_only( ldap_sync_t *ls )
842 return ldap_sync_init( ls, LDAP_SYNC_REFRESH_ONLY );
846 * initialize the refreshAndPersist sync
849 ldap_sync_init_refresh_and_persist( ldap_sync_t *ls )
851 return ldap_sync_init( ls, LDAP_SYNC_REFRESH_AND_PERSIST );
855 * poll for new responses
858 ldap_sync_poll( ldap_sync_t *ls )
862 LDAPMessage *res = NULL,
866 #ifdef LDAP_SYNC_TRACE
867 fprintf( stderr, "ldap_sync_poll...\n" );
868 #endif /* LDAP_SYNC_TRACE */
870 assert( ls != NULL );
871 assert( ls->ls_ld != NULL );
873 if ( ls->ls_timeout != -1 ) {
874 tv.tv_sec = ls->ls_timeout;
879 rc = ldap_result( ls->ls_ld, ls->ls_msgid,
880 LDAP_MSG_RECEIVED, tvp, &res );
885 for ( msg = ldap_first_message( ls->ls_ld, res );
887 msg = ldap_next_message( ls->ls_ld, msg ) )
891 switch ( ldap_msgtype( msg ) ) {
892 case LDAP_RES_SEARCH_ENTRY:
893 rc = ldap_sync_search_entry( ls, res );
896 case LDAP_RES_SEARCH_REFERENCE:
897 rc = ldap_sync_search_reference( ls, res );
900 case LDAP_RES_SEARCH_RESULT:
901 rc = ldap_sync_search_result( ls, res );
904 case LDAP_RES_INTERMEDIATE:
905 rc = ldap_sync_search_intermediate( ls, res, &refreshDone );
906 if ( rc != LDAP_SUCCESS || refreshDone ) {
912 #ifdef LDAP_SYNC_TRACE
913 fprintf( stderr, "\tgot something unexpected...\n" );
914 #endif /* LDAP_SYNC_TRACE */