From: Quanah Gibson-Mount Date: Wed, 22 Jul 2009 22:02:20 +0000 (+0000) Subject: Add sssvlv overlay X-Git-Tag: OPENLDAP_REL_ENG_2_4_18~101 X-Git-Url: https://git.sur5r.net/?a=commitdiff_plain;h=b20a6b0ff88d3b948bd36481f52bb7bbaa8f0efb;p=openldap Add sssvlv overlay --- diff --git a/clients/tools/common.c b/clients/tools/common.c index 1715aefee0..a75f8bfe98 100644 --- a/clients/tools/common.c +++ b/clients/tools/common.c @@ -120,6 +120,9 @@ static int chainingContinuation = -1; static int sessionTracking = 0; struct berval stValue; #endif /* LDAP_CONTROL_X_SESSION_TRACKING */ +ber_int_t vlvPos; +ber_int_t vlvCount; +struct berval *vlvContext; LDAPControl *unknown_ctrls = NULL; int unknown_ctrls_num = 0; @@ -136,6 +139,7 @@ static int print_paged_results( LDAP *ld, LDAPControl *ctrl ); static int print_ppolicy( LDAP *ld, LDAPControl *ctrl ); #endif static int print_sss( LDAP *ld, LDAPControl *ctrl ); +static int print_vlv( LDAP *ld, LDAPControl *ctrl ); #ifdef LDAP_CONTROL_X_DEREF static int print_deref( LDAP *ld, LDAPControl *ctrl ); #endif @@ -155,6 +159,7 @@ static struct tool_ctrls_t { { LDAP_CONTROL_PASSWORDPOLICYRESPONSE, TOOL_ALL, print_ppolicy }, #endif { LDAP_CONTROL_SORTRESPONSE, TOOL_SEARCH, print_sss }, + { LDAP_CONTROL_VLVRESPONSE, TOOL_SEARCH, print_vlv }, #ifdef LDAP_CONTROL_X_DEREF { LDAP_CONTROL_X_DEREF, TOOL_SEARCH, print_deref }, #endif @@ -1927,6 +1932,46 @@ print_sss( LDAP *ld, LDAPControl *ctrl ) return rc; } +static int +print_vlv( LDAP *ld, LDAPControl *ctrl ) +{ + int rc; + ber_int_t err; + struct berval bv; + + rc = ldap_parse_vlvresponse_control( ld, ctrl, &vlvPos, &vlvCount, + &vlvContext, &err ); + if ( rc == LDAP_SUCCESS ) { + char buf[ BUFSIZ ]; + + if ( vlvContext && vlvContext->bv_len > 0 ) { + bv.bv_len = LUTIL_BASE64_ENCODE_LEN( + vlvContext->bv_len ) + 1; + bv.bv_val = ber_memalloc( bv.bv_len + 1 ); + + bv.bv_len = lutil_b64_ntop( + (unsigned char *) vlvContext->bv_val, + vlvContext->bv_len, + bv.bv_val, bv.bv_len ); + } else { + bv.bv_val = ""; + bv.bv_len = 0; + } + + rc = snprintf( buf, sizeof(buf), "pos=%d count=%d context=%s (%d) %s", + vlvPos, vlvCount, bv.bv_val, + err, ldap_err2string(err)); + + if ( bv.bv_len ) + ber_memfree( bv.bv_val ); + + tool_write_ldif( ldif ? LDIF_PUT_COMMENT : LDIF_PUT_VALUE, + "vlvResult", buf, rc ); + } + + return rc; +} + #ifdef LDAP_CONTROL_X_DEREF static int print_deref( LDAP *ld, LDAPControl *ctrl ) diff --git a/clients/tools/common.h b/clients/tools/common.h index 2d3bf62278..0ddeeed1ea 100644 --- a/clients/tools/common.h +++ b/clients/tools/common.h @@ -93,6 +93,9 @@ extern struct berval pr_cookie; #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR extern int chaining; #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ +extern ber_int_t vlvPos; +extern ber_int_t vlvCount; +extern struct berval *vlvContext; /* options */ extern struct timeval nettimeout; diff --git a/clients/tools/ldapsearch.c b/clients/tools/ldapsearch.c index 7c5963be91..ee2c8bad0d 100644 --- a/clients/tools/ldapsearch.c +++ b/clients/tools/ldapsearch.c @@ -133,6 +133,8 @@ usage( void ) fprintf( stderr, _(" [!]subentries[=true|false] (RFC 3672 subentries)\n")); fprintf( stderr, _(" [!]sync=ro[/] (RFC 4533 LDAP Sync refreshOnly)\n")); fprintf( stderr, _(" rp[/][/] (refreshAndPersist)\n")); + fprintf( stderr, _(" [!]vlv=/(//|:)\n")); + fprintf( stderr, _(" (ldapv3-vlv-09 virtual list views)\n")); #ifdef LDAP_CONTROL_X_DEREF fprintf( stderr, _(" [!]deref=derefAttr:attr[,...][;derefAttr:attr[,...][;...]]\n")); #endif @@ -207,6 +209,10 @@ static int domainScope = 0; static int sss = 0; static LDAPSortKey **sss_keys = NULL; +static int vlv = 0; +static LDAPVLVInfo vlvInfo; +static struct berval vlvValue; + static int ldapsync = 0; static struct berval sync_cookie = { 0, NULL }; static int sync_slimit = -1; @@ -263,6 +269,47 @@ urlize(char *url) } } +static int +parse_vlv(char *cvalue) +{ + char *keyp, *key2; + int num1, num2; + + keyp = cvalue; + if ( sscanf( keyp, "%d/%d", &num1, &num2 ) != 2 ) { + fprintf( stderr, + _("VLV control value \"%s\" invalid\n"), + cvalue ); + return -1; + } + vlvInfo.ldvlv_before_count = num1; + vlvInfo.ldvlv_after_count = num2; + keyp = strchr( keyp, '/' ) + 1; + key2 = strchr( keyp, '/' ); + if ( key2 ) { + keyp = key2 + 1; + if ( sscanf( keyp, "%d/%d", &num1, &num2 ) != 2 ) { + fprintf( stderr, + _("VLV control value \"%s\" invalid\n"), + cvalue ); + return -1; + } + vlvInfo.ldvlv_offset = num1; + vlvInfo.ldvlv_count = num2; + vlvInfo.ldvlv_attrvalue = NULL; + } else { + key2 = strchr( keyp, ':' ); + if ( !key2 ) { + fprintf( stderr, + _("VLV control value \"%s\" invalid\n"), + cvalue ); + return -1; + } + ber_str2bv( key2+1, 0, 0, &vlvValue ); + vlvInfo.ldvlv_attrvalue = &vlvValue; + } + return 0; +} const char options[] = "a:Ab:cE:F:l:Ls:S:tT:uz:" "Cd:D:e:f:h:H:IMnNO:o:p:P:QR:U:vVw:WxX:y:Y:Z"; @@ -343,6 +390,11 @@ handle_private_option( int i ) _("PagedResultsControl previously specified\n") ); exit( EXIT_FAILURE ); } + if ( vlv != 0 ) { + fprintf( stderr, + _("PagedResultsControl incompatible with VLV\n") ); + exit( EXIT_FAILURE ); + } if( cvalue != NULL ) { char *promptp; @@ -500,6 +552,27 @@ handle_private_option( int i ) } if ( crit ) ldapsync *= -1; + } else if ( strcasecmp( control, "vlv" ) == 0 ) { + if( vlv ) { + fprintf( stderr, + _("virtual list view control previously specified\n")); + exit( EXIT_FAILURE ); + } + if ( pagedResults != 0 ) { + fprintf( stderr, + _("PagedResultsControl incompatible with VLV\n") ); + exit( EXIT_FAILURE ); + } + if( cvalue == NULL ) { + fprintf( stderr, + _("missing specification of vlv control\n") ); + exit( EXIT_FAILURE ); + } + if ( parse_vlv( cvalue )) + exit( EXIT_FAILURE ); + + vlv = 1 + crit; + #ifdef LDAP_CONTROL_X_DEREF } else if ( strcasecmp( control, "deref" ) == 0 ) { int ispecs; @@ -748,6 +821,12 @@ main( int argc, char **argv ) tool_args( argc, argv ); + if ( vlv && !sss ) { + fprintf( stderr, + _("VLV control requires server side sort control\n" )); + return EXIT_FAILURE; + } + if (( argc - optind < 1 ) || ( *argv[optind] != '(' /*')'*/ && ( strchr( argv[optind], '=' ) == NULL ) ) ) @@ -843,7 +922,8 @@ getNextPage: || ldapsync || sss || subentries - || valuesReturnFilter ) + || valuesReturnFilter + || vlv ) { #ifdef LDAP_CONTROL_DONTUSECOPY @@ -991,6 +1071,21 @@ getNextPage: i++; } + if ( vlv ) { + if ( ctrl_add() ) { + return EXIT_FAILURE; + } + + if ( ldap_create_vlv_control_value( ld, + &vlvInfo, &c[i].ldctl_value ) ) + { + return EXIT_FAILURE; + } + + c[i].ldctl_oid = LDAP_CONTROL_VLVREQUEST; + c[i].ldctl_iscritical = sss > 1; + i++; + } #ifdef LDAP_CONTROL_X_DEREF if ( derefcrit ) { if ( derefval.bv_val == NULL ) { @@ -1106,6 +1201,15 @@ getNextPage: printf(_("\n# with server side sorting %scontrol"), sss > 1 ? _("critical ") : "" ); } + if ( vlv ) { + printf(_("\n# with virtual list view %scontrol: %d/%d"), + vlv > 1 ? _("critical ") : "", + vlvInfo.ldvlv_before_count, vlvInfo.ldvlv_after_count); + if ( vlvInfo.ldvlv_attrvalue ) + printf(":%s", vlvInfo.ldvlv_attrvalue->bv_val ); + else + printf("/%d/%d", vlvInfo.ldvlv_offset, vlvInfo.ldvlv_count ); + } #ifdef LDAP_CONTROL_X_DEREF if ( derefcrit ) { printf(_("\n# with dereference %scontrol"), @@ -1149,7 +1253,7 @@ getNextPage: } if (( rc == LDAP_SUCCESS ) && pageSize && pr_morePagedResults ) { - char buf[6]; + char buf[12]; int i, moreEntries, tmpSize; /* Loop to get the next pages when @@ -1187,6 +1291,41 @@ getNextPage: goto getNextPage; } + if (( rc == LDAP_SUCCESS ) && vlv ) { + char buf[BUFSIZ]; + int i, moreEntries, tmpSize; + + /* Loop to get the next window when + * enter is pressed on the terminal. + */ + printf( _("Press [before/after(/offset/count|:value)] Enter for the next window.\n")); + i = 0; + moreEntries = getchar(); + while ( moreEntries != EOF && moreEntries != '\n' ) { + if ( i < (int)sizeof(buf) - 1 ) { + buf[i] = moreEntries; + i++; + } + moreEntries = getchar(); + } + buf[i] = '\0'; + if ( buf[0] ) { + i = parse_vlv( strdup( buf )); + if ( i ) + return EXIT_FAILURE; + } else { + vlvInfo.ldvlv_attrvalue = NULL; + vlvInfo.ldvlv_count = vlvCount; + vlvInfo.ldvlv_offset += vlvInfo.ldvlv_after_count; + } + + if ( vlvInfo.ldvlv_context ) + ber_bvfree( vlvInfo.ldvlv_context ); + vlvInfo.ldvlv_context = vlvContext; + + goto getNextPage; + } + tool_unbind( ld ); tool_destroy(); if ( base != NULL ) { diff --git a/configure.in b/configure.in index ec9c68f8e2..86d7c4fc30 100644 --- a/configure.in +++ b/configure.in @@ -346,6 +346,7 @@ Overlays="accesslog \ retcode \ rwm \ seqmod \ + sssvlv \ syncprov \ translucent \ unique \ @@ -385,7 +386,9 @@ OL_ARG_ENABLE(retcode,[ --enable-retcode Return Code testing overlay], OL_ARG_ENABLE(rwm,[ --enable-rwm Rewrite/Remap overlay], no, [no yes mod], ol_enable_overlays) OL_ARG_ENABLE(seqmod,[ --enable-seqmod Sequential Modify overlay], - yes, [no yes mod], ol_enable_overlays) + no, [no yes mod], ol_enable_overlays) +OL_ARG_ENABLE(sssvlv,[ --enable-sssvlv ServerSideSort/VLV overlay], + no, [no yes mod], ol_enable_overlays) OL_ARG_ENABLE(syncprov,[ --enable-syncprov Syncrepl Provider overlay], yes, [no yes mod], ol_enable_overlays) OL_ARG_ENABLE(translucent,[ --enable-translucent Translucent Proxy overlay], @@ -554,6 +557,7 @@ BUILD_REFINT=no BUILD_RETCODE=no BUILD_RWM=no BUILD_SEQMOD=no +BUILD_SSSVLV=no BUILD_SYNCPROV=no BUILD_TRANSLUCENT=no BUILD_UNIQUE=no @@ -2938,6 +2942,18 @@ if test "$ol_enable_seqmod" != no ; then AC_DEFINE_UNQUOTED(SLAPD_OVER_SEQMOD,$MFLAG,[define for Sequential Modify overlay]) fi +if test "$ol_enable_sssvlv" != no ; then + BUILD_SSSVLV=$ol_enable_sssvlv + if test "$ol_enable_sssvlv" = mod ; then + MFLAG=SLAPD_MOD_DYNAMIC + SLAPD_DYNAMIC_OVERLAYS="$SLAPD_DYNAMIC_OVERLAYS sssvlv.la" + else + MFLAG=SLAPD_MOD_STATIC + SLAPD_STATIC_OVERLAYS="$SLAPD_STATIC_OVERLAYS sssvlv.o" + fi + AC_DEFINE_UNQUOTED(SLAPD_OVER_SSSVLV,$MFLAG,[define for ServerSideSort/VLV overlay]) +fi + if test "$ol_enable_syncprov" != no ; then BUILD_SYNCPROV=$ol_enable_syncprov if test "$ol_enable_syncprov" = mod ; then @@ -3056,6 +3072,7 @@ dnl overlays AC_SUBST(BUILD_RETCODE) AC_SUBST(BUILD_RWM) AC_SUBST(BUILD_SEQMOD) + AC_SUBST(BUILD_SSSVLV) AC_SUBST(BUILD_SYNCPROV) AC_SUBST(BUILD_TRANSLUCENT) AC_SUBST(BUILD_UNIQUE) diff --git a/doc/man/man1/ldapsearch.1 b/doc/man/man1/ldapsearch.1 index 8133aaf2b7..f2d34d7636 100644 --- a/doc/man/man1/ldapsearch.1 +++ b/doc/man/man1/ldapsearch.1 @@ -93,13 +93,13 @@ library call. opens a connection to an LDAP server, binds, and performs a search using specified parameters. The \fIfilter\fP should conform to the string representation for search filters as defined in RFC 4515. -If not provided, the default filter, (objectClass=*), is used. +If not provided, the default filter, \fB(objectClass=*)\fP, is used. .LP If .B ldapsearch finds one or more entries, the attributes specified by -\fIattrs\fP are returned. If * is listed, all user attributes are -returned. If + is listed, all operational attributes are returned. +\fIattrs\fP are returned. If \fB*\fP is listed, all user attributes are +returned. If \fB+\fP is listed, all operational attributes are returned. If no \fIattrs\fP are listed, all user attributes are returned. If only 1.1 is listed, no attributes will be returned. .LP @@ -109,12 +109,12 @@ Option \fI\-L\fP controls the format of the output. .TP .B \-n Show what would be done, but don't actually perform the search. Useful for -debugging in conjunction with -v. +debugging in conjunction with \fB\-v\fP. .TP .B \-c Continuous operation mode. Errors are reported, but ldapsearch will continue with searches. The default is to exit after reporting an error. Only useful -in conjunction with -f. +in conjunction with \fB\-f\fP. .TP .B \-u Include the User Friendly Name form of the Distinguished Name (DN) @@ -274,13 +274,14 @@ General extensions: Search extensions: .nf - [!]domainScope (domain scope) - [!]mv= (matched values filter) + [!]domainScope (domain scope) + [!]mv= (matched values filter) [!]pr=[/prompt|noprompt] (paged results/prompt) [!]sss=[\-][/[\-]...] (server side sorting) [!]subentries[=true|false] (subentries) - [!]sync=ro[/] (LDAP Sync refreshOnly) + [!]sync=ro[/] (LDAP Sync refreshOnly) rp[/][/] (LDAP Sync refreshAndPersist) + [!]vlv=/(//|:) (virtual list view) .fi .TP .BI \-l \ timelimit diff --git a/doc/man/man5/slapo-sssvlv.5 b/doc/man/man5/slapo-sssvlv.5 new file mode 100644 index 0000000000..a3cb1bfd14 --- /dev/null +++ b/doc/man/man5/slapo-sssvlv.5 @@ -0,0 +1,53 @@ +.TH SLAPO-SSSVLV 5 "RELEASEDATE" "OpenLDAP LDVERSION" +.\" Copyright 2009 The OpenLDAP Foundation All Rights Reserved. +.\" Copyright 2009 Symas Corporation All Rights Reserved. +.\" Copying restrictions apply. See COPYRIGHT/LICENSE. +.\" $OpenLDAP$ +.SH NAME +slapo\-sssvlv \- Server Side Sorting and Virtual List View overlay to slapd +.SH SYNOPSIS +ETCDIR/slapd.conf +.SH DESCRIPTION +This overlay implements the LDAP Server Side Sorting (RFC2891) control +as well as the Virtual List View control. It also replaces the default +implementation of the LDAP PagedResults (RFC2696) control, to ensure +that it works with Sorting. The overlay can be used with any backend +or globally for all backends. + +Since a complete result set must be generated in memory before sorting can +be performed, processing sort requests can have a large impact on the +server's memory use. As such, any connection is limited to having only +one sort request active at a time. Additional limits may be configured +as described below. + +.SH CONFIGURATION +These +.B slapd.conf +options apply to the SSSVLV overlay. +They should appear after the +.B overlay +directive. +.TP +.B sssvlv\-max +Set the maximum number of concurrent sort requests allowed across all +connections. The default is one half of the number of server threads. +.TP +.B sssvlv\-maxkeys +Set the maximum number of keys allowed in a sort request. The default is 5. +.SH FILES +.TP +ETCDIR/slapd.conf +default slapd configuration file +.TP +ETCDIR/slapd.d +default slapd configuration directory +.SH SEE ALSO +.BR slapd.conf (5), +.BR slapd\-config (5). +.LP +"OpenLDAP Administrator's Guide" (http://www.OpenLDAP.org/doc/admin/) +.LP +IETF LDAP Virtual List View proposal by D. Boreham, J. Sermersheim, +and A. Kashi in IETF document "draft-ietf-ldapext-ldapv3-vlv-09.txt". +.SH AUTHOR +Howard Chu diff --git a/include/ldap.h b/include/ldap.h index 695996c1a8..b9f8d49bc7 100644 --- a/include/ldap.h +++ b/include/ldap.h @@ -246,7 +246,6 @@ typedef struct ldapcontrol { #define LDAP_CONTROL_PRE_READ "1.3.6.1.1.13.1" /* RFC 4527 */ #define LDAP_CONTROL_POST_READ "1.3.6.1.1.13.2" /* RFC 4527 */ -/* standard track - not implemented in slapd(8) */ #define LDAP_CONTROL_SORTREQUEST "1.2.840.113556.1.4.473" /* RFC 2891 */ #define LDAP_CONTROL_SORTRESPONSE "1.2.840.113556.1.4.474" /* RFC 2891 */ @@ -349,11 +348,10 @@ typedef struct ldapcontrol { #define LDAP_CONTROL_PERSIST_ENTRY_CHANGE_MODIFY 0x4 #define LDAP_CONTROL_PERSIST_ENTRY_CHANGE_RENAME 0x8 -/* LDAP VLV *//* not implemented in slapd(8) */ +/* LDAP VLV */ #define LDAP_CONTROL_VLVREQUEST "2.16.840.1.113730.3.4.9" #define LDAP_CONTROL_VLVRESPONSE "2.16.840.1.113730.3.4.10" - /* LDAP Unsolicited Notifications */ #define LDAP_NOTICE_OF_DISCONNECTION "1.3.6.1.4.1.1466.20036" /* RFC 4511 */ #define LDAP_NOTICE_DISCONNECT LDAP_NOTICE_OF_DISCONNECTION @@ -618,6 +616,8 @@ typedef struct ldapcontrol { #define LDAP_RESULTS_TOO_LARGE 0x46 /* CLDAP */ #define LDAP_AFFECTS_MULTIPLE_DSAS 0x47 +#define LDAP_VLV_ERROR 0x4C + #define LDAP_OTHER 0x50 /* LCUP operation codes (113-117) - not implemented */ diff --git a/include/portable.hin b/include/portable.hin index 3b0d91cce9..53631699f8 100644 --- a/include/portable.hin +++ b/include/portable.hin @@ -1014,6 +1014,9 @@ /* define for Sequential Modify overlay */ #undef SLAPD_OVER_SEQMOD +/* define for ServerSideSort/VLV overlay */ +#undef SLAPD_OVER_SSSVLV + /* define for Syncrepl Provider overlay */ #undef SLAPD_OVER_SYNCPROV diff --git a/libraries/libldap/error.c b/libraries/libldap/error.c index 85cfce6adc..62aaa2b65c 100644 --- a/libraries/libldap/error.c +++ b/libraries/libldap/error.c @@ -79,6 +79,9 @@ static struct ldaperror ldap_builtin_errlist[] = { {LDAP_RESULTS_TOO_LARGE, N_("Results too large")}, {LDAP_AFFECTS_MULTIPLE_DSAS, N_("Operation affects multiple DSAs")}, + /* Virtual List View draft */ + {LDAP_VLV_ERROR, N_("Virtual List View error")}, + {LDAP_OTHER, N_("Other (e.g., implementation specific) error")}, {LDAP_CANCELLED, N_("Cancelled")}, diff --git a/servers/slapd/bconfig.c b/servers/slapd/bconfig.c index f60f9f1744..ad3649bbbd 100644 --- a/servers/slapd/bconfig.c +++ b/servers/slapd/bconfig.c @@ -261,6 +261,7 @@ static OidRec OidMacros[] = { * OLcfgOv{Oc|At}:18 -> memberof * OLcfgOv{Oc|At}:19 -> collect * OLcfgOv{Oc|At}:20 -> retcode + * OLcfgOv{Oc|At}:21 -> sssvlv */ /* alphabetical ordering */ diff --git a/servers/slapd/overlays/Makefile.in b/servers/slapd/overlays/Makefile.in index 4bef075dde..6fbf2b15a9 100644 --- a/servers/slapd/overlays/Makefile.in +++ b/servers/slapd/overlays/Makefile.in @@ -29,6 +29,7 @@ SRCS = overlays.c \ retcode.c \ rwm.c rwmconf.c rwmdn.c rwmmap.c \ seqmod.c \ + sssvlv.c \ syncprov.c \ translucent.c \ unique.c \ @@ -109,6 +110,9 @@ rwm.la : rwm.lo rwmconf.lo rwmdn.lo rwmmap.lo seqmod.la : seqmod.lo $(LTLINK_MOD) -module -o $@ seqmod.lo version.lo $(LINK_LIBS) +sssvlv.la : sssvlv.lo + $(LTLINK_MOD) -module -o $@ sssvlv.lo version.lo $(LINK_LIBS) + syncprov.la : syncprov.lo $(LTLINK_MOD) -module -o $@ syncprov.lo version.lo $(LINK_LIBS) diff --git a/servers/slapd/overlays/sssvlv.c b/servers/slapd/overlays/sssvlv.c new file mode 100644 index 0000000000..e7d3b853f2 --- /dev/null +++ b/servers/slapd/overlays/sssvlv.c @@ -0,0 +1,1237 @@ +/* sssvlv.c - server side sort / virtual list view */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2009 The OpenLDAP Foundation. + * Portions copyright 2009 Symas Corporation. + * 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 work was initially developed by Howard Chu for inclusion in + * OpenLDAP Software. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_SSSVLV + +#include + +#include +#include + +#include + +#include "slap.h" +#include "lutil.h" +#include "config.h" + +/* RFC2891: Server Side Sorting + * RFC2696: Paged Results + */ +#ifndef LDAP_MATCHRULE_IDENTIFIER +#define LDAP_MATCHRULE_IDENTIFIER 0x80L +#define LDAP_REVERSEORDER_IDENTIFIER 0x81L +#define LDAP_ATTRTYPES_IDENTIFIER 0x80L +#endif + +/* draft-ietf-ldapext-ldapv3-vlv-09.txt: Virtual List Views + */ +#ifndef LDAP_VLVBYINDEX_IDENTIFIER +#define LDAP_VLVBYINDEX_IDENTIFIER 0xa0L +#define LDAP_VLVBYVALUE_IDENTIFIER 0x81L +#define LDAP_VLVCONTEXT_IDENTIFIER 0x04L + +#define LDAP_VLV_SSS_MISSING 0x4C +#define LDAP_VLV_RANGE_ERROR 0x4D +#endif + +#define SAFESTR(macro_str, macro_def) ((macro_str) ? (macro_str) : (macro_def)) + +#define SSSVLV_DEFAULT_MAX_KEYS 5 + +typedef struct vlv_ctrl { + int vc_before; + int vc_after; + int vc_offset; + int vc_count; + struct berval vc_value; + unsigned long vc_context; +} vlv_ctrl; + +typedef struct sort_key +{ + AttributeDescription *sk_ad; + MatchingRule *sk_ordering; + int sk_direction; /* 1=normal, -1=reverse */ +} sort_key; + +typedef struct sort_ctrl { + int sc_nkeys; + sort_key sc_keys[0]; +} sort_ctrl; + + +typedef struct sort_node +{ + int sn_conn; + struct berval sn_dn; + struct berval *sn_vals; +} sort_node; + +typedef struct sssvlv_info +{ + int svi_max; /* max concurrent sorts */ + int svi_num; /* current # sorts */ + int svi_max_keys; /* max sort keys per request */ +} sssvlv_info; + +typedef struct sort_op +{ + Avlnode *so_tree; + sort_ctrl *so_ctrl; + sssvlv_info *so_info; + int so_paged; + int so_page_size; + int so_nentries; + int so_vlv; + int so_vlv_rc; + int so_vlv_target; + unsigned long so_vcontext; +} sort_op; + +/* There is only one conn table for all overlay instances */ +static sort_op **sort_conns; +static ldap_pvt_thread_mutex_t sort_conns_mutex; +static const char *debug_header = "sssvlv"; + +static int sss_cid; +static int vlv_cid; + +/* RFC 2981 Section 2.2 + * If a sort key is a multi-valued attribute, and an entry happens to + * have multiple values for that attribute and no other controls are + * present that affect the sorting order, then the server SHOULD use the + * least value (according to the ORDERING rule for that attribute). + */ +static struct berval* select_value( + Attribute *attr, + sort_key *key ) +{ + struct berval* ber1, *ber2; + MatchingRule *mr = key->sk_ordering; + int i, cmp; + + ber1 = &(attr->a_nvals[0]); + ber2 = ber1+1; + for ( i = 1; i < attr->a_numvals; i++,ber2++ ) { + mr->smr_match( &cmp, 0, mr->smr_syntax, mr, ber1, ber2 ); + if ( cmp > 0 ) { + ber1 = ber2; + } + } + + Debug(LDAP_DEBUG_TRACE, "%s: value selected for compare: %s\n", + debug_header, + SAFESTR(ber1->bv_val, ""), + 0); + + return ber1; +} + +static int node_cmp( const void* val1, const void* val2 ) +{ + sort_node *sn1 = (sort_node *)val1; + sort_node *sn2 = (sort_node *)val2; + sort_ctrl *sc = sort_conns[sn1->sn_conn]->so_ctrl; + MatchingRule *mr; + int i, cmp = 0; + + for ( i=0; cmp == 0 && isc_nkeys; i++ ) { + if ( BER_BVISNULL( &sn1->sn_vals[i] )) { + if ( BER_BVISNULL( &sn2->sn_vals[i] )) + cmp = 0; + else + cmp = sc->sc_keys[i].sk_direction; + } else if ( BER_BVISNULL( &sn2->sn_vals[i] )) { + cmp = sc->sc_keys[i].sk_direction * -1; + } else { + mr = sc->sc_keys[i].sk_ordering; + mr->smr_match( &cmp, 0, mr->smr_syntax, mr, + &sn1->sn_vals[i], &sn2->sn_vals[i] ); + if ( cmp ) + cmp *= sc->sc_keys[i].sk_direction; + } + } + return cmp; +} + +static int node_insert( const void *val1, const void *val2 ) +{ + /* Never return equal so that new entries are always inserted */ + return node_cmp( val1, val2 ) < 0 ? -1 : 1; +} + +static int pack_vlv_response_control( + Operation *op, + SlapReply *rs, + sort_op *so, + LDAPControl **ctrlsp ) +{ + LDAPControl *ctrl; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + struct berval cookie, bv; + int rc; + + ber_init2( ber, NULL, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + + rc = ber_printf( ber, "{iii", so->so_vlv_target, so->so_nentries, + so->so_vlv_rc ); + + if ( rc != -1 && so->so_vcontext ) { + cookie.bv_val = (char *)&so->so_vcontext; + cookie.bv_len = sizeof(so->so_vcontext); + rc = ber_printf( ber, "tO", LDAP_VLVCONTEXT_IDENTIFIER, &cookie ); + } + + if ( rc != -1 ) { + rc = ber_printf( ber, "}" ); + } + + if ( rc != -1 ) { + rc = ber_flatten2( ber, &bv, 0 ); + } + + if ( rc != -1 ) { + ctrl = (LDAPControl *)op->o_tmpalloc( sizeof(LDAPControl)+ + bv.bv_len, op->o_tmpmemctx ); + ctrl->ldctl_oid = LDAP_CONTROL_VLVRESPONSE; + ctrl->ldctl_iscritical = 0; + ctrl->ldctl_value.bv_val = (char *)(ctrl+1); + ctrl->ldctl_value.bv_len = bv.bv_len; + AC_MEMCPY( ctrl->ldctl_value.bv_val, bv.bv_val, bv.bv_len ); + ctrlsp[0] = ctrl; + } else { + ctrlsp[0] = NULL; + rs->sr_err = LDAP_OTHER; + } + + ber_free_buf( ber ); + + return rs->sr_err; +} + +static int pack_pagedresult_response_control( + Operation *op, + SlapReply *rs, + sort_op *so, + LDAPControl **ctrlsp ) +{ + LDAPControl *ctrl; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + PagedResultsCookie resp_cookie; + struct berval cookie, bv; + int rc; + + ber_init2( ber, NULL, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + + if ( so->so_nentries > 0 ) { + resp_cookie = ( PagedResultsCookie )so->so_tree; + cookie.bv_len = sizeof( PagedResultsCookie ); + cookie.bv_val = (char *)&resp_cookie; + } else { + resp_cookie = ( PagedResultsCookie )0; + BER_BVZERO( &cookie ); + } + + op->o_conn->c_pagedresults_state.ps_cookie = resp_cookie; + op->o_conn->c_pagedresults_state.ps_count + = ((PagedResultsState *)op->o_pagedresults_state)->ps_count + + rs->sr_nentries; + + rc = ber_printf( ber, "{iO}", so->so_nentries, &cookie ); + if ( rc != -1 ) { + rc = ber_flatten2( ber, &bv, 0 ); + } + + if ( rc != -1 ) { + ctrl = (LDAPControl *)op->o_tmpalloc( sizeof(LDAPControl)+ + bv.bv_len, op->o_tmpmemctx ); + ctrl->ldctl_oid = LDAP_CONTROL_PAGEDRESULTS; + ctrl->ldctl_iscritical = 0; + ctrl->ldctl_value.bv_val = (char *)(ctrl+1); + ctrl->ldctl_value.bv_len = bv.bv_len; + AC_MEMCPY( ctrl->ldctl_value.bv_val, bv.bv_val, bv.bv_len ); + ctrlsp[0] = ctrl; + } else { + ctrlsp[0] = NULL; + rs->sr_err = LDAP_OTHER; + } + + ber_free_buf( ber ); + + return rs->sr_err; +} + +static int pack_sss_response_control( + Operation *op, + SlapReply *rs, + LDAPControl **ctrlsp ) +{ + LDAPControl *ctrl; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + struct berval bv; + int rc; + + ber_init2( ber, NULL, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + + /* Pack error code */ + rc = ber_printf(ber, "{e}", rs->sr_err); + + if ( rc != -1) + rc = ber_flatten2( ber, &bv, 0 ); + + if ( rc != -1 ) { + ctrl = (LDAPControl *)op->o_tmpalloc( sizeof(LDAPControl)+ + bv.bv_len, op->o_tmpmemctx ); + ctrl->ldctl_oid = LDAP_CONTROL_SORTRESPONSE; + ctrl->ldctl_iscritical = 0; + ctrl->ldctl_value.bv_val = (char *)(ctrl+1); + ctrl->ldctl_value.bv_len = bv.bv_len; + AC_MEMCPY( ctrl->ldctl_value.bv_val, bv.bv_val, bv.bv_len ); + ctrlsp[0] = ctrl; + } else { + ctrlsp[0] = NULL; + rs->sr_err = LDAP_OTHER; + } + + ber_free_buf( ber ); + + return rs->sr_err; +} + +static void free_sort_op( Connection *conn, sort_op *so ) +{ + if ( so->so_tree ) { + tavl_free( so->so_tree, ch_free ); + so->so_tree = NULL; + } + + ldap_pvt_thread_mutex_lock( &sort_conns_mutex ); + sort_conns[conn->c_conn_idx] = NULL; + so->so_info->svi_num--; + ldap_pvt_thread_mutex_unlock( &sort_conns_mutex ); + + ch_free( so ); +} + +static int send_list( + Operation *op, + SlapReply *rs, + sort_op *so) +{ + Avlnode *cur_node, *tmp_node; + vlv_ctrl *vc = op->o_controls[vlv_cid]; + int i, j, dir, rc; + BackendDB *be; + Entry *e; + LDAPControl *ctrls[2]; + + /* FIXME: it may be better to just flatten the tree into + * an array before doing all of this... + */ + + /* Are we just counting an offset? */ + if ( BER_BVISNULL( &vc->vc_value )) { + if ( vc->vc_offset == vc->vc_count ) { + /* wants the last entry in the list */ + cur_node = tavl_end(so->so_tree, TAVL_DIR_RIGHT); + so->so_vlv_target = so->so_nentries; + } else if ( vc->vc_offset == 1 ) { + /* wants the first entry in the list */ + cur_node = tavl_end(so->so_tree, TAVL_DIR_LEFT); + so->so_vlv_target = 1; + } else { + int target; + /* Just iterate to the right spot */ + if ( vc->vc_count && vc->vc_count != so->so_nentries ) { + if ( vc->vc_offset > vc->vc_count ) + goto range_err; + target = so->so_nentries * vc->vc_offset / vc->vc_count; + } else { + if ( vc->vc_offset > so->so_nentries ) { +range_err: + so->so_vlv_rc = LDAP_VLV_RANGE_ERROR; + pack_vlv_response_control( op, rs, so, ctrls ); + ctrls[1] = NULL; + slap_add_ctrls( op, rs, ctrls ); + rs->sr_err = LDAP_VLV_ERROR; + return; + } + target = vc->vc_offset; + } + so->so_vlv_target = target; + /* Start at left and go right, or start at right and go left? */ + if ( target < so->so_nentries / 2 ) { + cur_node = tavl_end(so->so_tree, TAVL_DIR_LEFT); + dir = TAVL_DIR_RIGHT; + } else { + cur_node = tavl_end(so->so_tree, TAVL_DIR_RIGHT); + dir = TAVL_DIR_LEFT; + target = so->so_nentries - target + 1; + } + for ( i=1; iso_ctrl; + MatchingRule *mr = sc->sc_keys[0].sk_ordering; + sort_node *sn; + struct berval bv; + + if ( mr->smr_normalize ) { + rc = mr->smr_normalize( SLAP_MR_VALUE_OF_SYNTAX, + mr->smr_syntax, mr, &vc->vc_value, &bv, op->o_tmpmemctx ); + if ( rc ) { + so->so_vlv_rc = LDAP_INAPPROPRIATE_MATCHING; + pack_vlv_response_control( op, rs, so, ctrls ); + ctrls[1] = NULL; + slap_add_ctrls( op, rs, ctrls ); + rs->sr_err = LDAP_VLV_ERROR; + return; + } + } else { + bv = vc->vc_value; + } + + sn = op->o_tmpalloc( sizeof(sort_node) + + sc->sc_nkeys * sizeof(struct berval), op->o_tmpmemctx ); + sn->sn_vals = (struct berval *)(sn+1); + sn->sn_conn = op->o_conn->c_conn_idx; + sn->sn_vals[0] = bv; + for (i=1; isc_nkeys; i++) { + BER_BVZERO( &sn->sn_vals[i] ); + } + cur_node = tavl_find3( so->so_tree, sn, node_cmp, &j ); + /* didn't find >= match */ + if ( j > 0 ) + cur_node = NULL; + op->o_tmpfree( sn, op->o_tmpmemctx ); + + if ( !cur_node ) { + so->so_vlv_target = so->so_nentries + 1; + } else { + sort_node *sn = so->so_tree->avl_data; + /* start from the left or the right side? */ + mr->smr_match( &i, 0, mr->smr_syntax, mr, &bv, &sn->sn_vals[0] ); + if ( i > 0 ) { + tmp_node = tavl_end(so->so_tree, TAVL_DIR_RIGHT); + dir = TAVL_DIR_LEFT; + } else { + tmp_node = tavl_end(so->so_tree, TAVL_DIR_LEFT); + dir = TAVL_DIR_RIGHT; + } + for (i=0; tmp_node != cur_node; + tmp_node = tavl_next( tmp_node, dir ), i++); + so->so_vlv_target = i; + } + if ( bv.bv_val != vc->vc_value.bv_val ) + op->o_tmpfree( bv.bv_val, op->o_tmpmemctx ); + } + if ( !cur_node ) { + i = 1; + cur_node = tavl_end(so->so_tree, TAVL_DIR_RIGHT); + } else { + i = 0; + } + for ( ; ivc_before; i++ ) { + tmp_node = tavl_next( cur_node, TAVL_DIR_LEFT ); + if ( !tmp_node ) break; + cur_node = tmp_node; + } + j = i + vc->vc_after + 1; + be = op->o_bd; + for ( i=0; iavl_data; + + op->o_bd = select_backend( &sn->sn_dn, 0 ); + e = NULL; + rc = be_entry_get_rw( op, &sn->sn_dn, NULL, NULL, 0, &e ); + + if ( e && rc == LDAP_SUCCESS ) { + rs->sr_entry = e; + rs->sr_flags = REP_ENTRY_MUSTRELEASE; + rs->sr_err = send_search_entry( op, rs ); + if ( rs->sr_err == LDAP_UNAVAILABLE ) + break; + } + cur_node = tavl_next( cur_node, TAVL_DIR_RIGHT ); + if ( !cur_node ) break; + } + so->so_vlv_rc = LDAP_SUCCESS; + + op->o_bd = be; +} + +static void send_page( Operation *op, SlapReply *rs, sort_op *so ) +{ + Avlnode *cur_node = so->so_tree; + Avlnode *next_node = NULL; + BackendDB *be = op->o_bd; + sort_node *sn; + Entry *e; + int rc; + + while ( cur_node && rs->sr_nentries < so->so_page_size ) { + sort_node *sn = cur_node->avl_data; + + next_node = tavl_next( cur_node, TAVL_DIR_RIGHT ); + + op->o_bd = select_backend( &sn->sn_dn, 0 ); + e = NULL; + rc = be_entry_get_rw( op, &sn->sn_dn, NULL, NULL, 0, &e ); + + ch_free( cur_node->avl_data ); + ber_memfree( cur_node ); + + cur_node = next_node; + so->so_nentries--; + + if ( e && rc == LDAP_SUCCESS ) { + rs->sr_entry = e; + rs->sr_flags = REP_ENTRY_MUSTRELEASE; + rs->sr_err = send_search_entry( op, rs ); + if ( rs->sr_err == LDAP_UNAVAILABLE ) + break; + } + } + + /* Set the first entry to send for the next page */ + so->so_tree = next_node; + + op->o_bd = be; +} + +static void send_entry( + Operation *op, + SlapReply *rs, + sort_op *so) +{ + Debug(LDAP_DEBUG_TRACE, + "%s: response control: status=%d, text=%s\n", + debug_header, rs->sr_err, SAFESTR(rs->sr_text, "")); + + if ( !so->so_tree ) + return; + + /* RFC 2891: If critical then send the entries iff they were + * succesfully sorted. If non-critical send all entries + * whether they were sorted or not. + */ + if ( (op->o_ctrlflag[sss_cid] != SLAP_CONTROL_CRITICAL) || + (rs->sr_err == LDAP_SUCCESS) ) + { + if ( so->so_vlv > SLAP_CONTROL_IGNORED ) { + send_list( op, rs, so ); + } else { + /* Get the first node to send */ + Avlnode *start_node = tavl_end(so->so_tree, TAVL_DIR_LEFT); + so->so_tree = start_node; + + if ( so->so_paged <= SLAP_CONTROL_IGNORED ) { + /* Not paged result search. Send all entries. + * Set the page size to the number of entries + * so that send_page() will send all entries. + */ + so->so_page_size = so->so_nentries; + } + + send_page( op, rs, so ); + } + } +} + +static void send_result( + Operation *op, + SlapReply *rs, + sort_op *so) +{ + LDAPControl *ctrls[3]; + int rc, i = 0; + + rc = pack_sss_response_control( op, rs, ctrls ); + if ( rc == LDAP_SUCCESS ) { + i++; + rc = -1; + if ( so->so_paged > SLAP_CONTROL_IGNORED ) { + rc = pack_pagedresult_response_control( op, rs, so, ctrls+1 ); + } else if ( so->so_vlv > SLAP_CONTROL_IGNORED ) { + rc = pack_vlv_response_control( op, rs, so, ctrls+1 ); + } + if ( rc == LDAP_SUCCESS ) + i++; + } + ctrls[i] = NULL; + + if ( ctrls[0] != NULL ) + slap_add_ctrls( op, rs, ctrls ); + send_ldap_result( op, rs ); + + if ( so->so_tree == NULL ) { + /* Search finished, so clean up */ + free_sort_op( op->o_conn, so ); + } +} + +static int sssvlv_op_response( + Operation *op, + SlapReply *rs ) +{ + sort_ctrl *sc = op->o_controls[sss_cid]; + sort_op *so = op->o_callback->sc_private; + + if ( rs->sr_type == REP_SEARCH ) { + int i; + size_t len; + sort_node *sn, *sn2; + struct berval *bv; + char *ptr; + + len = sizeof(sort_node) + sc->sc_nkeys * sizeof(struct berval) + + rs->sr_entry->e_nname.bv_len + 1; + sn = op->o_tmpalloc( len, op->o_tmpmemctx ); + sn->sn_vals = (struct berval *)(sn+1); + + /* Build tmp list of key values */ + for ( i=0; isc_nkeys; i++ ) { + Attribute *a = attr_find( rs->sr_entry->e_attrs, + sc->sc_keys[i].sk_ad ); + if ( a ) { + if ( a->a_numvals > 1 ) { + bv = select_value( a, &sc->sc_keys[i] ); + } else { + bv = a->a_nvals; + } + sn->sn_vals[i] = *bv; + len += bv->bv_len + 1; + } else { + BER_BVZERO( &sn->sn_vals[i] ); + } + } + + /* Now dup into regular memory */ + sn2 = ch_malloc( len ); + sn2->sn_vals = (struct berval *)(sn2+1); + AC_MEMCPY( sn2->sn_vals, sn->sn_vals, + sc->sc_nkeys * sizeof(struct berval)); + + ptr = (char *)(sn2->sn_vals + sc->sc_nkeys); + sn2->sn_dn.bv_val = ptr; + sn2->sn_dn.bv_len = rs->sr_entry->e_nname.bv_len; + AC_MEMCPY( ptr, rs->sr_entry->e_nname.bv_val, + rs->sr_entry->e_nname.bv_len ); + ptr += rs->sr_entry->e_nname.bv_len; + *ptr++ = '\0'; + for ( i=0; isc_nkeys; i++ ) { + if ( !BER_BVISNULL( &sn2->sn_vals[i] )) { + AC_MEMCPY(ptr, sn2->sn_vals[i].bv_val, sn2->sn_vals[i].bv_len); + sn2->sn_vals[i].bv_val = ptr; + ptr += sn2->sn_vals[i].bv_len; + *ptr++ = '\0'; + } + } + op->o_tmpfree( sn, op->o_tmpmemctx ); + sn = sn2; + sn->sn_conn = op->o_conn->c_conn_idx; + + /* Insert into the AVL tree */ + tavl_insert(&(so->so_tree), sn, node_insert, avl_dup_error); + + so->so_nentries++; + + /* Collected the keys so that they can be sorted. Thus, stop + * the entry from propagating. + */ + rs->sr_err = LDAP_SUCCESS; + } + else if ( rs->sr_type == REP_RESULT ) { + /* Remove serversort response callback. + * We don't want the entries that we are about to send to be + * processed by serversort response again. + */ + if ( op->o_callback->sc_response == sssvlv_op_response ) { + op->o_callback = op->o_callback->sc_next; + } + + send_entry( op, rs, so ); + send_result( op, rs, so ); + } + + return rs->sr_err; +} + +static int sssvlv_op_search( + Operation *op, + SlapReply *rs) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + sssvlv_info *si = on->on_bi.bi_private; + int rc = SLAP_CB_CONTINUE; + int ok; + sort_op *so, so2; + sort_ctrl *sc = op->o_controls[sss_cid]; + PagedResultsState *ps; + vlv_ctrl *vc; + + if ( op->o_ctrlflag[sss_cid] <= SLAP_CONTROL_IGNORED ) { + if ( op->o_ctrlflag[vlv_cid] > SLAP_CONTROL_IGNORED ) { + LDAPControl *ctrls[2]; + so2.so_vcontext = 0; + so2.so_vlv_target = 0; + so2.so_nentries = 0; + so2.so_vlv_rc = LDAP_VLV_SSS_MISSING; + rc = pack_vlv_response_control( op, rs, &so2, ctrls ); + if ( rc == LDAP_SUCCESS ) { + ctrls[1] = NULL; + slap_add_ctrls( op, rs, ctrls ); + } + rs->sr_err = LDAP_VLV_ERROR; + rs->sr_text = "Sort control is required with VLV"; + goto leave; + } + /* Not server side sort so just continue */ + return SLAP_CB_CONTINUE; + } + + Debug(LDAP_DEBUG_TRACE, + "==> sssvlv_search: <%s> %s, control flag: %d\n", + op->o_req_dn.bv_val, op->ors_filterstr.bv_val, + op->o_ctrlflag[sss_cid]); + + if ( sc->sc_nkeys > si->svi_max_keys ) { + rs->sr_text = "Too many sort keys"; + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + goto leave; + } + + ps = ( op->o_pagedresults > SLAP_CONTROL_IGNORED ) ? + (PagedResultsState*)(op->o_pagedresults_state) : NULL; + vc = op->o_ctrlflag[vlv_cid] > SLAP_CONTROL_IGNORED ? + op->o_controls[vlv_cid] : NULL; + + if ( ps && vc ) { + rs->sr_text = "VLV incompatible with PagedResults"; + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + goto leave; + } + + ok = 1; + ldap_pvt_thread_mutex_lock( &sort_conns_mutex ); + so = sort_conns[op->o_conn->c_conn_idx]; + /* Is there already a sort running on this conn? */ + if ( so ) { + /* Is it a continuation of a VLV search? */ + if ( !vc || so->so_vlv <= SLAP_CONTROL_IGNORED || + vc->vc_context != so->so_vcontext ) { + /* Is it a continuation of a paged search? */ + if ( !ps || so->so_paged <= SLAP_CONTROL_IGNORED || + op->o_conn->c_pagedresults_state.ps_cookie != ps->ps_cookie ) { + ok = 0; + } else if ( !ps->ps_size ) { + /* Abandoning current request */ + ok = 0; + so->so_nentries = 0; + rs->sr_err = LDAP_SUCCESS; + } + } + if (( vc && so->so_paged > SLAP_CONTROL_IGNORED ) || + ( ps && so->so_vlv > SLAP_CONTROL_IGNORED )) { + /* changed from paged to vlv or vice versa, abandon */ + ok = 0; + so->so_nentries = 0; + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + } + /* Are there too many running overall? */ + } else if ( si->svi_num >= si->svi_max ) { + ok = 0; + } else { + /* OK, this connection now has a sort running */ + si->svi_num++; + sort_conns[op->o_conn->c_conn_idx] = &so2; + } + ldap_pvt_thread_mutex_unlock( &sort_conns_mutex ); + if ( ok ) { + /* are we continuing a VLV search? */ + if ( vc && vc->vc_context ) { + so->so_ctrl = sc; + send_list( op, rs, so ); + send_result( op, rs, so ); + rc = LDAP_SUCCESS; + /* are we continuing a paged search? */ + } else if ( ps && ps->ps_cookie ) { + so->so_ctrl = sc; + send_page( op, rs, so ); + send_result( op, rs, so ); + rc = LDAP_SUCCESS; + } else { + slap_callback *cb = op->o_tmpalloc( sizeof(slap_callback), + op->o_tmpmemctx ); + /* Install serversort response callback to handle a new search */ + if ( ps || vc ) { + so = ch_malloc( sizeof(sort_op)); + } else { + so = op->o_tmpalloc( sizeof(sort_op), op->o_tmpmemctx ); + } + sort_conns[op->o_conn->c_conn_idx] = so; + + cb->sc_cleanup = NULL; + cb->sc_response = sssvlv_op_response; + cb->sc_next = op->o_callback; + cb->sc_private = so; + + so->so_tree = NULL; + so->so_ctrl = sc; + so->so_info = si; + if ( ps ) { + so->so_paged = op->o_pagedresults; + so->so_page_size = ps->ps_size; + op->o_pagedresults = SLAP_CONTROL_IGNORED; + } else { + so->so_paged = 0; + so->so_page_size = 0; + if ( vc ) { + so->so_vlv = op->o_ctrlflag[vlv_cid]; + so->so_vlv_target = 0; + so->so_vlv_rc = 0; + } + } + so->so_vcontext = (unsigned long)so; + so->so_nentries = 0; + + op->o_callback = cb; + } + } else { + if ( so && !so->so_nentries ) { + free_sort_op( op->o_conn, so ); + } else { + rs->sr_text = "Other sort requests already in progress"; + rs->sr_err = LDAP_BUSY; + } +leave: + rc = rs->sr_err; + send_ldap_result( op, rs ); + } + + return rc; +} + +static int get_ordering_rule( + AttributeDescription *ad, + struct berval *matchrule, + SlapReply *rs, + MatchingRule **ordering ) +{ + MatchingRule* mr; + + if ( matchrule && matchrule->bv_val ) { + mr = mr_find( matchrule->bv_val ); + if ( mr == NULL ) { + rs->sr_err = LDAP_INAPPROPRIATE_MATCHING; + rs->sr_text = "serverSort control: No ordering rule"; + Debug(LDAP_DEBUG_TRACE, "%s: no ordering rule function for %s\n", + debug_header, matchrule->bv_val, 0); + } + } + else { + mr = ad->ad_type->sat_ordering; + if ( mr == NULL ) { + rs->sr_err = LDAP_INAPPROPRIATE_MATCHING; + rs->sr_text = "serverSort control: No ordering rule"; + Debug(LDAP_DEBUG_TRACE, + "%s: no ordering rule specified and no default ordering rule for attribute %s\n", + debug_header, ad->ad_cname.bv_val, 0); + } + } + + *ordering = mr; + return rs->sr_err; +} + +static int count_key(BerElement *ber) +{ + char *end; + ber_len_t len; + ber_tag_t tag; + int count = 0; + + /* Server Side Sort Control is a SEQUENCE of SEQUENCE */ + for ( tag = ber_first_element( ber, &len, &end ); + tag == LBER_SEQUENCE; + tag = ber_next_element( ber, &len, end )) + { + tag = ber_skip_tag( ber, &len ); + ber_skip_data( ber, len ); + ++count; + } + ber_rewind( ber ); + + return count; +} + +static int build_key( + BerElement *ber, + SlapReply *rs, + sort_key *key ) +{ + struct berval attr; + struct berval matchrule = BER_BVNULL; + ber_int_t reverse = 0; + ber_tag_t tag; + ber_len_t len; + MatchingRule *ordering = NULL; + AttributeDescription *ad = NULL; + const char *text; + + if (( tag = ber_scanf( ber, "{" )) == LBER_ERROR ) { + rs->sr_text = "serverSort control: decoding error"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + return rs->sr_err; + } + + if (( tag = ber_scanf( ber, "m", &attr )) == LBER_ERROR ) { + rs->sr_text = "serverSort control: attribute decoding error"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + return rs->sr_err; + } + + tag = ber_peek_tag( ber, &len ); + if ( tag == LDAP_MATCHRULE_IDENTIFIER ) { + if (( tag = ber_scanf( ber, "m", &matchrule )) == LBER_ERROR ) { + rs->sr_text = "serverSort control: matchrule decoding error"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + return rs->sr_err; + } + tag = ber_peek_tag( ber, &len ); + } + + if ( tag == LDAP_REVERSEORDER_IDENTIFIER ) { + if (( tag = ber_scanf( ber, "b", &reverse )) == LBER_ERROR ) { + rs->sr_text = "serverSort control: reverse decoding error"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + return rs->sr_err; + } + } + + if (( tag = ber_scanf( ber, "}" )) == LBER_ERROR ) { + rs->sr_text = "serverSort control: decoding error"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + return rs->sr_err; + } + + if ( slap_bv2ad( &attr, &ad, &text ) != LDAP_SUCCESS ) { + rs->sr_text = + "serverSort control: Unrecognized attribute type in sort key"; + Debug(LDAP_DEBUG_TRACE, + "%s: Unrecognized attribute type in sort key: %s\n", + debug_header, SAFESTR(attr.bv_val, ""), 0); + rs->sr_err = LDAP_NO_SUCH_ATTRIBUTE; + return rs->sr_err; + } + + /* get_ordering_rule will set sr_err and sr_text */ + get_ordering_rule( ad, &matchrule, rs, &ordering ); + if ( rs->sr_err != LDAP_SUCCESS ) { + return rs->sr_err; + } + + key->sk_ad = ad; + key->sk_ordering = ordering; + key->sk_direction = reverse ? -1 : 1; + + return rs->sr_err; +} + +static int sss_parseCtrl( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + BerElementBuffer berbuf; + BerElement *ber; + ber_tag_t tag; + ber_len_t len; + int i; + sort_ctrl *sc; + + rs->sr_err = LDAP_PROTOCOL_ERROR; + + if ( op->o_ctrlflag[sss_cid] > SLAP_CONTROL_IGNORED ) { + rs->sr_text = "sorted results control specified multiple times"; + } else if ( BER_BVISNULL( &ctrl->ldctl_value ) ) { + rs->sr_text = "sorted results control value is absent"; + } else if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) { + rs->sr_text = "sorted results control value is empty"; + } else { + rs->sr_err = LDAP_SUCCESS; + } + if ( rs->sr_err != LDAP_SUCCESS ) + return rs->sr_err; + + op->o_ctrlflag[sss_cid] = ctrl->ldctl_iscritical ? + SLAP_CONTROL_CRITICAL : SLAP_CONTROL_NONCRITICAL; + + ber = (BerElement *)&berbuf; + ber_init2( ber, &ctrl->ldctl_value, 0 ); + i = count_key( ber ); + + sc = op->o_tmpalloc( sizeof(sort_ctrl) + + i * sizeof(sort_key), op->o_tmpmemctx ); + sc->sc_nkeys = i; + op->o_controls[sss_cid] = sc; + + /* peel off initial sequence */ + ber_scanf( ber, "{" ); + + i = 0; + do { + if ( build_key( ber, rs, &sc->sc_keys[i] ) != LDAP_SUCCESS ) + break; + i++; + tag = ber_peek_tag( ber, &len ); + } while ( tag != LBER_DEFAULT ); + + return rs->sr_err; +} + +static int vlv_parseCtrl( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + BerElementBuffer berbuf; + BerElement *ber; + ber_tag_t tag; + ber_len_t len; + int i; + vlv_ctrl *vc, vc2; + + rs->sr_err = LDAP_PROTOCOL_ERROR; + rs->sr_text = NULL; + + if ( op->o_ctrlflag[vlv_cid] > SLAP_CONTROL_IGNORED ) { + rs->sr_text = "vlv control specified multiple times"; + } else if ( BER_BVISNULL( &ctrl->ldctl_value ) ) { + rs->sr_text = "vlv control value is absent"; + } else if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) { + rs->sr_text = "vlv control value is empty"; + } + if ( rs->sr_text != NULL ) + return rs->sr_err; + + op->o_ctrlflag[vlv_cid] = ctrl->ldctl_iscritical ? + SLAP_CONTROL_CRITICAL : SLAP_CONTROL_NONCRITICAL; + + ber = (BerElement *)&berbuf; + ber_init2( ber, &ctrl->ldctl_value, 0 ); + + rs->sr_err = LDAP_PROTOCOL_ERROR; + + tag = ber_scanf( ber, "{ii", &vc2.vc_before, &vc2.vc_after ); + if ( tag == LBER_ERROR ) { + return rs->sr_err; + } + + tag = ber_peek_tag( ber, &len ); + if ( tag == LDAP_VLVBYINDEX_IDENTIFIER ) { + tag = ber_scanf( ber, "{ii}", &vc2.vc_offset, &vc2.vc_count ); + if ( tag == LBER_ERROR ) + return rs->sr_err; + BER_BVZERO( &vc2.vc_value ); + } else if ( tag == LDAP_VLVBYVALUE_IDENTIFIER ) { + tag = ber_scanf( ber, "m", &vc2.vc_value ); + if ( tag == LBER_ERROR || BER_BVISNULL( &vc2.vc_value )) + return rs->sr_err; + } else { + return rs->sr_err; + } + tag = ber_peek_tag( ber, &len ); + if ( tag == LDAP_VLVCONTEXT_IDENTIFIER ) { + struct berval bv; + tag = ber_scanf( ber, "m", &bv ); + if ( tag == LBER_ERROR || bv.bv_len != sizeof(vc2.vc_context)) + return rs->sr_err; + AC_MEMCPY( &vc2.vc_context, bv.bv_val, bv.bv_len ); + } else { + vc2.vc_context = 0; + } + + vc = op->o_tmpalloc( sizeof(vlv_ctrl), op->o_tmpmemctx ); + *vc = vc2; + op->o_controls[vlv_cid] = vc; + rs->sr_err = LDAP_SUCCESS; + + return rs->sr_err; +} + +static int sssvlv_connection_destroy( BackendDB *be, Connection *conn ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + + if ( sort_conns[conn->c_conn_idx] ) + free_sort_op( conn, sort_conns[conn->c_conn_idx] ); + + return LDAP_SUCCESS; +} + +static int sssvlv_db_open( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + sssvlv_info *si = on->on_bi.bi_private; + + /* If not set, default to 1/2 of available threads */ + if ( !si->svi_max ) + si->svi_max = connection_pool_max / 2; + + int rc = overlay_register_control( be, LDAP_CONTROL_SORTREQUEST ); + if ( rc == LDAP_SUCCESS ) + rc = overlay_register_control( be, LDAP_CONTROL_VLVREQUEST ); + return rc; +} + +static ConfigTable sssvlv_cfg[] = { + { "sssvlv-max", "num", + 2, 2, 0, ARG_INT|ARG_OFFSET, + (void *)offsetof(sssvlv_info, svi_max), + "( OLcfgOvAt:21.1 NAME 'olcSssVlvMax' " + "DESC 'Maximum number of concurrent Sort requests' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "sssvlv-maxkeys", "num", + 2, 2, 0, ARG_INT|ARG_OFFSET, + (void *)offsetof(sssvlv_info, svi_max_keys), + "( OLcfgOvAt:21.2 NAME 'olcSssVlvMaxKeys' " + "DESC 'Maximum number of Keys in a Sort request' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs sssvlv_ocs[] = { + { "( OLcfgOvOc:21.1 " + "NAME 'olcSssVlvConfig' " + "DESC 'SSS VLV configuration' " + "SUP olcOverlayConfig " + "MAY ( olcSssVlvMax $ olcSssVlvMaxKeys ) )", + Cft_Overlay, sssvlv_cfg, NULL, NULL }, + { NULL, 0, NULL } +}; + +static int sssvlv_db_init( + BackendDB *be, + ConfigReply *cr) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + sssvlv_info *si; + + si = (sssvlv_info *)ch_malloc(sizeof(sssvlv_info)); + on->on_bi.bi_private = si; + + si->svi_max = 0; + si->svi_num = 0; + si->svi_max_keys = SSSVLV_DEFAULT_MAX_KEYS; + + if ( dtblsize && !sort_conns ) { + ldap_pvt_thread_mutex_init( &sort_conns_mutex ); + /* accommodate for c_conn_idx == -1 */ + sort_conns = ch_calloc( sizeof(sort_op *), dtblsize + 1 ); + sort_conns++; + } + + return LDAP_SUCCESS; +} + +static int sssvlv_db_destroy( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + sssvlv_info *si = (sssvlv_info *)on->on_bi.bi_private; + + if ( si ) { + ch_free( si ); + on->on_bi.bi_private = NULL; + } + return LDAP_SUCCESS; +} + +static slap_overinst sssvlv; + +int sssvlv_initialize() +{ + int rc; + + sssvlv.on_bi.bi_type = "sssvlv"; + sssvlv.on_bi.bi_db_init = sssvlv_db_init; + sssvlv.on_bi.bi_db_destroy = sssvlv_db_destroy; + sssvlv.on_bi.bi_db_open = sssvlv_db_open; + sssvlv.on_bi.bi_connection_destroy = sssvlv_connection_destroy; + sssvlv.on_bi.bi_op_search = sssvlv_op_search; + + sssvlv.on_bi.bi_cf_ocs = sssvlv_ocs; + + rc = config_register_schema( sssvlv_cfg, sssvlv_ocs ); + if ( rc ) + return rc; + + rc = register_supported_control2( LDAP_CONTROL_SORTREQUEST, + SLAP_CTRL_SEARCH, + NULL, + sss_parseCtrl, + 1 /* replace */, + &sss_cid ); + + if ( rc == LDAP_SUCCESS ) { + rc = register_supported_control2( LDAP_CONTROL_VLVREQUEST, + SLAP_CTRL_SEARCH, + NULL, + vlv_parseCtrl, + 1 /* replace */, + &vlv_cid ); + } + + if ( rc == LDAP_SUCCESS ) { + rc = overlay_register( &sssvlv ); + if ( rc != LDAP_SUCCESS ) { + fprintf( stderr, "Failed to register server side sort overlay\n" ); + } + } + else { + fprintf( stderr, "Failed to register control %d\n", rc ); + } + + return rc; +} + +#if SLAPD_OVER_SSSVLV == SLAPD_MOD_DYNAMIC +int init_module( int argc, char *argv[]) +{ + return sssvlv_initialize(); +} +#endif + +#endif /* SLAPD_OVER_SSSVLV */ diff --git a/servers/slapd/slap.h b/servers/slapd/slap.h index a89d7124da..3fb72f0370 100644 --- a/servers/slapd/slap.h +++ b/servers/slapd/slap.h @@ -62,7 +62,6 @@ LDAP_BEGIN_DECL #define LDAP_COLLECTIVE_ATTRIBUTES #define LDAP_COMP_MATCH #define LDAP_SYNC_TIMESTAMP -#define SLAP_CONTROL_X_SORTEDRESULTS #define SLAP_CONTROL_X_SESSION_TRACKING #define SLAP_CONTROL_X_WHATFAILED #define SLAP_CONFIG_DELETE