1 /* $NetBSD: sssvlv.c,v 1.1.1.2 2010/12/12 15:23:44 adam Exp $ */ 2 3 /* sssvlv.c - server side sort / virtual list view */ 4 /* OpenLDAP: pkg/ldap/servers/slapd/overlays/sssvlv.c,v 1.9.2.9 2010/06/10 17:37:40 quanah Exp */ 5 /* This work is part of OpenLDAP Software <http://www.openldap.org/>. 6 * 7 * Copyright 2009-2010 The OpenLDAP Foundation. 8 * Portions copyright 2009 Symas Corporation. 9 * All rights reserved. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted only as authorized by the OpenLDAP 13 * Public License. 14 * 15 * A copy of this license is available in the file LICENSE in the 16 * top-level directory of the distribution or, alternatively, at 17 * <http://www.OpenLDAP.org/license.html>. 18 */ 19 /* ACKNOWLEDGEMENTS: 20 * This work was initially developed by Howard Chu for inclusion in 21 * OpenLDAP Software. 22 */ 23 24 #include "portable.h" 25 26 #ifdef SLAPD_OVER_SSSVLV 27 28 #include <stdio.h> 29 30 #include <ac/string.h> 31 #include <ac/ctype.h> 32 33 #include <avl.h> 34 35 #include "slap.h" 36 #include "lutil.h" 37 #include "config.h" 38 39 #include "../../../libraries/liblber/lber-int.h" /* ber_rewind */ 40 41 /* RFC2891: Server Side Sorting 42 * RFC2696: Paged Results 43 */ 44 #ifndef LDAP_MATCHRULE_IDENTIFIER 45 #define LDAP_MATCHRULE_IDENTIFIER 0x80L 46 #define LDAP_REVERSEORDER_IDENTIFIER 0x81L 47 #define LDAP_ATTRTYPES_IDENTIFIER 0x80L 48 #endif 49 50 /* draft-ietf-ldapext-ldapv3-vlv-09.txt: Virtual List Views 51 */ 52 #ifndef LDAP_VLVBYINDEX_IDENTIFIER 53 #define LDAP_VLVBYINDEX_IDENTIFIER 0xa0L 54 #define LDAP_VLVBYVALUE_IDENTIFIER 0x81L 55 #define LDAP_VLVCONTEXT_IDENTIFIER 0x04L 56 57 #define LDAP_VLV_SSS_MISSING 0x4C 58 #define LDAP_VLV_RANGE_ERROR 0x4D 59 #endif 60 61 #define SAFESTR(macro_str, macro_def) ((macro_str) ? (macro_str) : (macro_def)) 62 63 #define SSSVLV_DEFAULT_MAX_KEYS 5 64 65 typedef struct vlv_ctrl { 66 int vc_before; 67 int vc_after; 68 int vc_offset; 69 int vc_count; 70 struct berval vc_value; 71 unsigned long vc_context; 72 } vlv_ctrl; 73 74 typedef struct sort_key 75 { 76 AttributeDescription *sk_ad; 77 MatchingRule *sk_ordering; 78 int sk_direction; /* 1=normal, -1=reverse */ 79 } sort_key; 80 81 typedef struct sort_ctrl { 82 int sc_nkeys; 83 sort_key sc_keys[1]; 84 } sort_ctrl; 85 86 87 typedef struct sort_node 88 { 89 int sn_conn; 90 struct berval sn_dn; 91 struct berval *sn_vals; 92 } sort_node; 93 94 typedef struct sssvlv_info 95 { 96 int svi_max; /* max concurrent sorts */ 97 int svi_num; /* current # sorts */ 98 int svi_max_keys; /* max sort keys per request */ 99 } sssvlv_info; 100 101 typedef struct sort_op 102 { 103 Avlnode *so_tree; 104 sort_ctrl *so_ctrl; 105 sssvlv_info *so_info; 106 int so_paged; 107 int so_page_size; 108 int so_nentries; 109 int so_vlv; 110 int so_vlv_rc; 111 int so_vlv_target; 112 unsigned long so_vcontext; 113 } sort_op; 114 115 /* There is only one conn table for all overlay instances */ 116 static sort_op **sort_conns; 117 static ldap_pvt_thread_mutex_t sort_conns_mutex; 118 static int ov_count; 119 static const char *debug_header = "sssvlv"; 120 121 static int sss_cid; 122 static int vlv_cid; 123 124 /* RFC 2981 Section 2.2 125 * If a sort key is a multi-valued attribute, and an entry happens to 126 * have multiple values for that attribute and no other controls are 127 * present that affect the sorting order, then the server SHOULD use the 128 * least value (according to the ORDERING rule for that attribute). 129 */ 130 static struct berval* select_value( 131 Attribute *attr, 132 sort_key *key ) 133 { 134 struct berval* ber1, *ber2; 135 MatchingRule *mr = key->sk_ordering; 136 int i, cmp; 137 138 ber1 = &(attr->a_nvals[0]); 139 ber2 = ber1+1; 140 for ( i = 1; i < attr->a_numvals; i++,ber2++ ) { 141 mr->smr_match( &cmp, 0, mr->smr_syntax, mr, ber1, ber2 ); 142 if ( cmp > 0 ) { 143 ber1 = ber2; 144 } 145 } 146 147 Debug(LDAP_DEBUG_TRACE, "%s: value selected for compare: %s\n", 148 debug_header, 149 SAFESTR(ber1->bv_val, "<Empty>"), 150 0); 151 152 return ber1; 153 } 154 155 static int node_cmp( const void* val1, const void* val2 ) 156 { 157 sort_node *sn1 = (sort_node *)val1; 158 sort_node *sn2 = (sort_node *)val2; 159 sort_ctrl *sc = sort_conns[sn1->sn_conn]->so_ctrl; 160 MatchingRule *mr; 161 int i, cmp = 0; 162 163 for ( i=0; cmp == 0 && i<sc->sc_nkeys; i++ ) { 164 if ( BER_BVISNULL( &sn1->sn_vals[i] )) { 165 if ( BER_BVISNULL( &sn2->sn_vals[i] )) 166 cmp = 0; 167 else 168 cmp = sc->sc_keys[i].sk_direction; 169 } else if ( BER_BVISNULL( &sn2->sn_vals[i] )) { 170 cmp = sc->sc_keys[i].sk_direction * -1; 171 } else { 172 mr = sc->sc_keys[i].sk_ordering; 173 mr->smr_match( &cmp, 0, mr->smr_syntax, mr, 174 &sn1->sn_vals[i], &sn2->sn_vals[i] ); 175 if ( cmp ) 176 cmp *= sc->sc_keys[i].sk_direction; 177 } 178 } 179 return cmp; 180 } 181 182 static int node_insert( const void *val1, const void *val2 ) 183 { 184 /* Never return equal so that new entries are always inserted */ 185 return node_cmp( val1, val2 ) < 0 ? -1 : 1; 186 } 187 188 static int pack_vlv_response_control( 189 Operation *op, 190 SlapReply *rs, 191 sort_op *so, 192 LDAPControl **ctrlsp ) 193 { 194 LDAPControl *ctrl; 195 BerElementBuffer berbuf; 196 BerElement *ber = (BerElement *)&berbuf; 197 struct berval cookie, bv; 198 int rc; 199 200 ber_init2( ber, NULL, LBER_USE_DER ); 201 ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); 202 203 rc = ber_printf( ber, "{iii", so->so_vlv_target, so->so_nentries, 204 so->so_vlv_rc ); 205 206 if ( rc != -1 && so->so_vcontext ) { 207 cookie.bv_val = (char *)&so->so_vcontext; 208 cookie.bv_len = sizeof(so->so_vcontext); 209 rc = ber_printf( ber, "tO", LDAP_VLVCONTEXT_IDENTIFIER, &cookie ); 210 } 211 212 if ( rc != -1 ) { 213 rc = ber_printf( ber, "}" ); 214 } 215 216 if ( rc != -1 ) { 217 rc = ber_flatten2( ber, &bv, 0 ); 218 } 219 220 if ( rc != -1 ) { 221 ctrl = (LDAPControl *)op->o_tmpalloc( sizeof(LDAPControl)+ 222 bv.bv_len, op->o_tmpmemctx ); 223 ctrl->ldctl_oid = LDAP_CONTROL_VLVRESPONSE; 224 ctrl->ldctl_iscritical = 0; 225 ctrl->ldctl_value.bv_val = (char *)(ctrl+1); 226 ctrl->ldctl_value.bv_len = bv.bv_len; 227 AC_MEMCPY( ctrl->ldctl_value.bv_val, bv.bv_val, bv.bv_len ); 228 ctrlsp[0] = ctrl; 229 } else { 230 ctrlsp[0] = NULL; 231 rs->sr_err = LDAP_OTHER; 232 } 233 234 ber_free_buf( ber ); 235 236 return rs->sr_err; 237 } 238 239 static int pack_pagedresult_response_control( 240 Operation *op, 241 SlapReply *rs, 242 sort_op *so, 243 LDAPControl **ctrlsp ) 244 { 245 LDAPControl *ctrl; 246 BerElementBuffer berbuf; 247 BerElement *ber = (BerElement *)&berbuf; 248 PagedResultsCookie resp_cookie; 249 struct berval cookie, bv; 250 int rc; 251 252 ber_init2( ber, NULL, LBER_USE_DER ); 253 ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); 254 255 if ( so->so_nentries > 0 ) { 256 resp_cookie = ( PagedResultsCookie )so->so_tree; 257 cookie.bv_len = sizeof( PagedResultsCookie ); 258 cookie.bv_val = (char *)&resp_cookie; 259 } else { 260 resp_cookie = ( PagedResultsCookie )0; 261 BER_BVZERO( &cookie ); 262 } 263 264 op->o_conn->c_pagedresults_state.ps_cookie = resp_cookie; 265 op->o_conn->c_pagedresults_state.ps_count 266 = ((PagedResultsState *)op->o_pagedresults_state)->ps_count 267 + rs->sr_nentries; 268 269 rc = ber_printf( ber, "{iO}", so->so_nentries, &cookie ); 270 if ( rc != -1 ) { 271 rc = ber_flatten2( ber, &bv, 0 ); 272 } 273 274 if ( rc != -1 ) { 275 ctrl = (LDAPControl *)op->o_tmpalloc( sizeof(LDAPControl)+ 276 bv.bv_len, op->o_tmpmemctx ); 277 ctrl->ldctl_oid = LDAP_CONTROL_PAGEDRESULTS; 278 ctrl->ldctl_iscritical = 0; 279 ctrl->ldctl_value.bv_val = (char *)(ctrl+1); 280 ctrl->ldctl_value.bv_len = bv.bv_len; 281 AC_MEMCPY( ctrl->ldctl_value.bv_val, bv.bv_val, bv.bv_len ); 282 ctrlsp[0] = ctrl; 283 } else { 284 ctrlsp[0] = NULL; 285 rs->sr_err = LDAP_OTHER; 286 } 287 288 ber_free_buf( ber ); 289 290 return rs->sr_err; 291 } 292 293 static int pack_sss_response_control( 294 Operation *op, 295 SlapReply *rs, 296 LDAPControl **ctrlsp ) 297 { 298 LDAPControl *ctrl; 299 BerElementBuffer berbuf; 300 BerElement *ber = (BerElement *)&berbuf; 301 struct berval bv; 302 int rc; 303 304 ber_init2( ber, NULL, LBER_USE_DER ); 305 ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); 306 307 /* Pack error code */ 308 rc = ber_printf(ber, "{e}", rs->sr_err); 309 310 if ( rc != -1) 311 rc = ber_flatten2( ber, &bv, 0 ); 312 313 if ( rc != -1 ) { 314 ctrl = (LDAPControl *)op->o_tmpalloc( sizeof(LDAPControl)+ 315 bv.bv_len, op->o_tmpmemctx ); 316 ctrl->ldctl_oid = LDAP_CONTROL_SORTRESPONSE; 317 ctrl->ldctl_iscritical = 0; 318 ctrl->ldctl_value.bv_val = (char *)(ctrl+1); 319 ctrl->ldctl_value.bv_len = bv.bv_len; 320 AC_MEMCPY( ctrl->ldctl_value.bv_val, bv.bv_val, bv.bv_len ); 321 ctrlsp[0] = ctrl; 322 } else { 323 ctrlsp[0] = NULL; 324 rs->sr_err = LDAP_OTHER; 325 } 326 327 ber_free_buf( ber ); 328 329 return rs->sr_err; 330 } 331 332 static void free_sort_op( Connection *conn, sort_op *so ) 333 { 334 if ( so->so_tree ) { 335 tavl_free( so->so_tree, ch_free ); 336 so->so_tree = NULL; 337 } 338 339 ldap_pvt_thread_mutex_lock( &sort_conns_mutex ); 340 sort_conns[conn->c_conn_idx] = NULL; 341 so->so_info->svi_num--; 342 ldap_pvt_thread_mutex_unlock( &sort_conns_mutex ); 343 344 ch_free( so ); 345 } 346 347 static void send_list( 348 Operation *op, 349 SlapReply *rs, 350 sort_op *so) 351 { 352 Avlnode *cur_node, *tmp_node; 353 vlv_ctrl *vc = op->o_controls[vlv_cid]; 354 int i, j, dir, rc; 355 BackendDB *be; 356 Entry *e; 357 LDAPControl *ctrls[2]; 358 359 /* FIXME: it may be better to just flatten the tree into 360 * an array before doing all of this... 361 */ 362 363 /* Are we just counting an offset? */ 364 if ( BER_BVISNULL( &vc->vc_value )) { 365 if ( vc->vc_offset == vc->vc_count ) { 366 /* wants the last entry in the list */ 367 cur_node = tavl_end(so->so_tree, TAVL_DIR_RIGHT); 368 so->so_vlv_target = so->so_nentries; 369 } else if ( vc->vc_offset == 1 ) { 370 /* wants the first entry in the list */ 371 cur_node = tavl_end(so->so_tree, TAVL_DIR_LEFT); 372 so->so_vlv_target = 1; 373 } else { 374 int target; 375 /* Just iterate to the right spot */ 376 if ( vc->vc_count && vc->vc_count != so->so_nentries ) { 377 if ( vc->vc_offset > vc->vc_count ) 378 goto range_err; 379 target = so->so_nentries * vc->vc_offset / vc->vc_count; 380 } else { 381 if ( vc->vc_offset > so->so_nentries ) { 382 range_err: 383 so->so_vlv_rc = LDAP_VLV_RANGE_ERROR; 384 pack_vlv_response_control( op, rs, so, ctrls ); 385 ctrls[1] = NULL; 386 slap_add_ctrls( op, rs, ctrls ); 387 rs->sr_err = LDAP_VLV_ERROR; 388 return; 389 } 390 target = vc->vc_offset; 391 } 392 so->so_vlv_target = target; 393 /* Start at left and go right, or start at right and go left? */ 394 if ( target < so->so_nentries / 2 ) { 395 cur_node = tavl_end(so->so_tree, TAVL_DIR_LEFT); 396 dir = TAVL_DIR_RIGHT; 397 } else { 398 cur_node = tavl_end(so->so_tree, TAVL_DIR_RIGHT); 399 dir = TAVL_DIR_LEFT; 400 target = so->so_nentries - target + 1; 401 } 402 for ( i=1; i<target; i++ ) 403 cur_node = tavl_next( cur_node, dir ); 404 } 405 } else { 406 /* we're looking for a specific value */ 407 sort_ctrl *sc = so->so_ctrl; 408 MatchingRule *mr = sc->sc_keys[0].sk_ordering; 409 sort_node *sn; 410 struct berval bv; 411 412 if ( mr->smr_normalize ) { 413 rc = mr->smr_normalize( SLAP_MR_VALUE_OF_SYNTAX, 414 mr->smr_syntax, mr, &vc->vc_value, &bv, op->o_tmpmemctx ); 415 if ( rc ) { 416 so->so_vlv_rc = LDAP_INAPPROPRIATE_MATCHING; 417 pack_vlv_response_control( op, rs, so, ctrls ); 418 ctrls[1] = NULL; 419 slap_add_ctrls( op, rs, ctrls ); 420 rs->sr_err = LDAP_VLV_ERROR; 421 return; 422 } 423 } else { 424 bv = vc->vc_value; 425 } 426 427 sn = op->o_tmpalloc( sizeof(sort_node) + 428 sc->sc_nkeys * sizeof(struct berval), op->o_tmpmemctx ); 429 sn->sn_vals = (struct berval *)(sn+1); 430 sn->sn_conn = op->o_conn->c_conn_idx; 431 sn->sn_vals[0] = bv; 432 for (i=1; i<sc->sc_nkeys; i++) { 433 BER_BVZERO( &sn->sn_vals[i] ); 434 } 435 cur_node = tavl_find3( so->so_tree, sn, node_cmp, &j ); 436 /* didn't find >= match */ 437 if ( j > 0 ) 438 cur_node = NULL; 439 op->o_tmpfree( sn, op->o_tmpmemctx ); 440 441 if ( !cur_node ) { 442 so->so_vlv_target = so->so_nentries + 1; 443 } else { 444 sort_node *sn = so->so_tree->avl_data; 445 /* start from the left or the right side? */ 446 mr->smr_match( &i, 0, mr->smr_syntax, mr, &bv, &sn->sn_vals[0] ); 447 if ( i > 0 ) { 448 tmp_node = tavl_end(so->so_tree, TAVL_DIR_RIGHT); 449 dir = TAVL_DIR_LEFT; 450 } else { 451 tmp_node = tavl_end(so->so_tree, TAVL_DIR_LEFT); 452 dir = TAVL_DIR_RIGHT; 453 } 454 for (i=0; tmp_node != cur_node; 455 tmp_node = tavl_next( tmp_node, dir ), i++); 456 so->so_vlv_target = i; 457 } 458 if ( bv.bv_val != vc->vc_value.bv_val ) 459 op->o_tmpfree( bv.bv_val, op->o_tmpmemctx ); 460 } 461 if ( !cur_node ) { 462 i = 1; 463 cur_node = tavl_end(so->so_tree, TAVL_DIR_RIGHT); 464 } else { 465 i = 0; 466 } 467 for ( ; i<vc->vc_before; i++ ) { 468 tmp_node = tavl_next( cur_node, TAVL_DIR_LEFT ); 469 if ( !tmp_node ) break; 470 cur_node = tmp_node; 471 } 472 j = i + vc->vc_after + 1; 473 be = op->o_bd; 474 for ( i=0; i<j; i++ ) { 475 sort_node *sn = cur_node->avl_data; 476 477 if ( slapd_shutdown ) break; 478 479 op->o_bd = select_backend( &sn->sn_dn, 0 ); 480 e = NULL; 481 rc = be_entry_get_rw( op, &sn->sn_dn, NULL, NULL, 0, &e ); 482 483 if ( e && rc == LDAP_SUCCESS ) { 484 rs->sr_entry = e; 485 rs->sr_flags = REP_ENTRY_MUSTRELEASE; 486 rs->sr_err = send_search_entry( op, rs ); 487 if ( rs->sr_err == LDAP_UNAVAILABLE ) 488 break; 489 } 490 cur_node = tavl_next( cur_node, TAVL_DIR_RIGHT ); 491 if ( !cur_node ) break; 492 } 493 so->so_vlv_rc = LDAP_SUCCESS; 494 495 op->o_bd = be; 496 } 497 498 static void send_page( Operation *op, SlapReply *rs, sort_op *so ) 499 { 500 Avlnode *cur_node = so->so_tree; 501 Avlnode *next_node = NULL; 502 BackendDB *be = op->o_bd; 503 sort_node *sn; 504 Entry *e; 505 int rc; 506 507 while ( cur_node && rs->sr_nentries < so->so_page_size ) { 508 sort_node *sn = cur_node->avl_data; 509 510 if ( slapd_shutdown ) break; 511 512 next_node = tavl_next( cur_node, TAVL_DIR_RIGHT ); 513 514 op->o_bd = select_backend( &sn->sn_dn, 0 ); 515 e = NULL; 516 rc = be_entry_get_rw( op, &sn->sn_dn, NULL, NULL, 0, &e ); 517 518 ch_free( cur_node->avl_data ); 519 ber_memfree( cur_node ); 520 521 cur_node = next_node; 522 so->so_nentries--; 523 524 if ( e && rc == LDAP_SUCCESS ) { 525 rs->sr_entry = e; 526 rs->sr_flags = REP_ENTRY_MUSTRELEASE; 527 rs->sr_err = send_search_entry( op, rs ); 528 if ( rs->sr_err == LDAP_UNAVAILABLE ) 529 break; 530 } 531 } 532 533 /* Set the first entry to send for the next page */ 534 so->so_tree = next_node; 535 536 op->o_bd = be; 537 } 538 539 static void send_entry( 540 Operation *op, 541 SlapReply *rs, 542 sort_op *so) 543 { 544 Debug(LDAP_DEBUG_TRACE, 545 "%s: response control: status=%d, text=%s\n", 546 debug_header, rs->sr_err, SAFESTR(rs->sr_text, "<None>")); 547 548 if ( !so->so_tree ) 549 return; 550 551 /* RFC 2891: If critical then send the entries iff they were 552 * succesfully sorted. If non-critical send all entries 553 * whether they were sorted or not. 554 */ 555 if ( (op->o_ctrlflag[sss_cid] != SLAP_CONTROL_CRITICAL) || 556 (rs->sr_err == LDAP_SUCCESS) ) 557 { 558 if ( so->so_vlv > SLAP_CONTROL_IGNORED ) { 559 send_list( op, rs, so ); 560 } else { 561 /* Get the first node to send */ 562 Avlnode *start_node = tavl_end(so->so_tree, TAVL_DIR_LEFT); 563 so->so_tree = start_node; 564 565 if ( so->so_paged <= SLAP_CONTROL_IGNORED ) { 566 /* Not paged result search. Send all entries. 567 * Set the page size to the number of entries 568 * so that send_page() will send all entries. 569 */ 570 so->so_page_size = so->so_nentries; 571 } 572 573 send_page( op, rs, so ); 574 } 575 } 576 } 577 578 static void send_result( 579 Operation *op, 580 SlapReply *rs, 581 sort_op *so) 582 { 583 LDAPControl *ctrls[3]; 584 int rc, i = 0; 585 586 rc = pack_sss_response_control( op, rs, ctrls ); 587 if ( rc == LDAP_SUCCESS ) { 588 i++; 589 rc = -1; 590 if ( so->so_paged > SLAP_CONTROL_IGNORED ) { 591 rc = pack_pagedresult_response_control( op, rs, so, ctrls+1 ); 592 } else if ( so->so_vlv > SLAP_CONTROL_IGNORED ) { 593 rc = pack_vlv_response_control( op, rs, so, ctrls+1 ); 594 } 595 if ( rc == LDAP_SUCCESS ) 596 i++; 597 } 598 ctrls[i] = NULL; 599 600 if ( ctrls[0] != NULL ) 601 slap_add_ctrls( op, rs, ctrls ); 602 send_ldap_result( op, rs ); 603 604 if ( so->so_tree == NULL ) { 605 /* Search finished, so clean up */ 606 free_sort_op( op->o_conn, so ); 607 } 608 } 609 610 static int sssvlv_op_response( 611 Operation *op, 612 SlapReply *rs ) 613 { 614 sort_ctrl *sc = op->o_controls[sss_cid]; 615 sort_op *so = op->o_callback->sc_private; 616 617 if ( rs->sr_type == REP_SEARCH ) { 618 int i; 619 size_t len; 620 sort_node *sn, *sn2; 621 struct berval *bv; 622 char *ptr; 623 624 len = sizeof(sort_node) + sc->sc_nkeys * sizeof(struct berval) + 625 rs->sr_entry->e_nname.bv_len + 1; 626 sn = op->o_tmpalloc( len, op->o_tmpmemctx ); 627 sn->sn_vals = (struct berval *)(sn+1); 628 629 /* Build tmp list of key values */ 630 for ( i=0; i<sc->sc_nkeys; i++ ) { 631 Attribute *a = attr_find( rs->sr_entry->e_attrs, 632 sc->sc_keys[i].sk_ad ); 633 if ( a ) { 634 if ( a->a_numvals > 1 ) { 635 bv = select_value( a, &sc->sc_keys[i] ); 636 } else { 637 bv = a->a_nvals; 638 } 639 sn->sn_vals[i] = *bv; 640 len += bv->bv_len + 1; 641 } else { 642 BER_BVZERO( &sn->sn_vals[i] ); 643 } 644 } 645 646 /* Now dup into regular memory */ 647 sn2 = ch_malloc( len ); 648 sn2->sn_vals = (struct berval *)(sn2+1); 649 AC_MEMCPY( sn2->sn_vals, sn->sn_vals, 650 sc->sc_nkeys * sizeof(struct berval)); 651 652 ptr = (char *)(sn2->sn_vals + sc->sc_nkeys); 653 sn2->sn_dn.bv_val = ptr; 654 sn2->sn_dn.bv_len = rs->sr_entry->e_nname.bv_len; 655 AC_MEMCPY( ptr, rs->sr_entry->e_nname.bv_val, 656 rs->sr_entry->e_nname.bv_len ); 657 ptr += rs->sr_entry->e_nname.bv_len; 658 *ptr++ = '\0'; 659 for ( i=0; i<sc->sc_nkeys; i++ ) { 660 if ( !BER_BVISNULL( &sn2->sn_vals[i] )) { 661 AC_MEMCPY(ptr, sn2->sn_vals[i].bv_val, sn2->sn_vals[i].bv_len); 662 sn2->sn_vals[i].bv_val = ptr; 663 ptr += sn2->sn_vals[i].bv_len; 664 *ptr++ = '\0'; 665 } 666 } 667 op->o_tmpfree( sn, op->o_tmpmemctx ); 668 sn = sn2; 669 sn->sn_conn = op->o_conn->c_conn_idx; 670 671 /* Insert into the AVL tree */ 672 tavl_insert(&(so->so_tree), sn, node_insert, avl_dup_error); 673 674 so->so_nentries++; 675 676 /* Collected the keys so that they can be sorted. Thus, stop 677 * the entry from propagating. 678 */ 679 rs->sr_err = LDAP_SUCCESS; 680 } 681 else if ( rs->sr_type == REP_RESULT ) { 682 /* Remove serversort response callback. 683 * We don't want the entries that we are about to send to be 684 * processed by serversort response again. 685 */ 686 if ( op->o_callback->sc_response == sssvlv_op_response ) { 687 op->o_callback = op->o_callback->sc_next; 688 } 689 690 send_entry( op, rs, so ); 691 send_result( op, rs, so ); 692 } 693 694 return rs->sr_err; 695 } 696 697 static int sssvlv_op_search( 698 Operation *op, 699 SlapReply *rs) 700 { 701 slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; 702 sssvlv_info *si = on->on_bi.bi_private; 703 int rc = SLAP_CB_CONTINUE; 704 int ok; 705 sort_op *so, so2; 706 sort_ctrl *sc; 707 PagedResultsState *ps; 708 vlv_ctrl *vc; 709 710 if ( op->o_ctrlflag[sss_cid] <= SLAP_CONTROL_IGNORED ) { 711 if ( op->o_ctrlflag[vlv_cid] > SLAP_CONTROL_IGNORED ) { 712 LDAPControl *ctrls[2]; 713 so2.so_vcontext = 0; 714 so2.so_vlv_target = 0; 715 so2.so_nentries = 0; 716 so2.so_vlv_rc = LDAP_VLV_SSS_MISSING; 717 rc = pack_vlv_response_control( op, rs, &so2, ctrls ); 718 if ( rc == LDAP_SUCCESS ) { 719 ctrls[1] = NULL; 720 slap_add_ctrls( op, rs, ctrls ); 721 } 722 rs->sr_err = LDAP_VLV_ERROR; 723 rs->sr_text = "Sort control is required with VLV"; 724 goto leave; 725 } 726 /* Not server side sort so just continue */ 727 return SLAP_CB_CONTINUE; 728 } 729 730 Debug(LDAP_DEBUG_TRACE, 731 "==> sssvlv_search: <%s> %s, control flag: %d\n", 732 op->o_req_dn.bv_val, op->ors_filterstr.bv_val, 733 op->o_ctrlflag[sss_cid]); 734 735 sc = op->o_controls[sss_cid]; 736 if ( sc->sc_nkeys > si->svi_max_keys ) { 737 rs->sr_text = "Too many sort keys"; 738 rs->sr_err = LDAP_UNWILLING_TO_PERFORM; 739 goto leave; 740 } 741 742 ps = ( op->o_pagedresults > SLAP_CONTROL_IGNORED ) ? 743 (PagedResultsState*)(op->o_pagedresults_state) : NULL; 744 vc = op->o_ctrlflag[vlv_cid] > SLAP_CONTROL_IGNORED ? 745 op->o_controls[vlv_cid] : NULL; 746 747 if ( ps && vc ) { 748 rs->sr_text = "VLV incompatible with PagedResults"; 749 rs->sr_err = LDAP_UNWILLING_TO_PERFORM; 750 goto leave; 751 } 752 753 ok = 1; 754 ldap_pvt_thread_mutex_lock( &sort_conns_mutex ); 755 so = sort_conns[op->o_conn->c_conn_idx]; 756 /* Is there already a sort running on this conn? */ 757 if ( so ) { 758 /* Is it a continuation of a VLV search? */ 759 if ( !vc || so->so_vlv <= SLAP_CONTROL_IGNORED || 760 vc->vc_context != so->so_vcontext ) { 761 /* Is it a continuation of a paged search? */ 762 if ( !ps || so->so_paged <= SLAP_CONTROL_IGNORED || 763 op->o_conn->c_pagedresults_state.ps_cookie != ps->ps_cookie ) { 764 ok = 0; 765 } else if ( !ps->ps_size ) { 766 /* Abandoning current request */ 767 ok = 0; 768 so->so_nentries = 0; 769 rs->sr_err = LDAP_SUCCESS; 770 } 771 } 772 if (( vc && so->so_paged > SLAP_CONTROL_IGNORED ) || 773 ( ps && so->so_vlv > SLAP_CONTROL_IGNORED )) { 774 /* changed from paged to vlv or vice versa, abandon */ 775 ok = 0; 776 so->so_nentries = 0; 777 rs->sr_err = LDAP_UNWILLING_TO_PERFORM; 778 } 779 /* Are there too many running overall? */ 780 } else if ( si->svi_num >= si->svi_max ) { 781 ok = 0; 782 } else { 783 /* OK, this connection now has a sort running */ 784 si->svi_num++; 785 sort_conns[op->o_conn->c_conn_idx] = &so2; 786 } 787 ldap_pvt_thread_mutex_unlock( &sort_conns_mutex ); 788 if ( ok ) { 789 /* are we continuing a VLV search? */ 790 if ( vc && vc->vc_context ) { 791 so->so_ctrl = sc; 792 send_list( op, rs, so ); 793 send_result( op, rs, so ); 794 rc = LDAP_SUCCESS; 795 /* are we continuing a paged search? */ 796 } else if ( ps && ps->ps_cookie ) { 797 so->so_ctrl = sc; 798 send_page( op, rs, so ); 799 send_result( op, rs, so ); 800 rc = LDAP_SUCCESS; 801 } else { 802 slap_callback *cb = op->o_tmpalloc( sizeof(slap_callback), 803 op->o_tmpmemctx ); 804 /* Install serversort response callback to handle a new search */ 805 if ( ps || vc ) { 806 so = ch_malloc( sizeof(sort_op)); 807 } else { 808 so = op->o_tmpalloc( sizeof(sort_op), op->o_tmpmemctx ); 809 } 810 sort_conns[op->o_conn->c_conn_idx] = so; 811 812 cb->sc_cleanup = NULL; 813 cb->sc_response = sssvlv_op_response; 814 cb->sc_next = op->o_callback; 815 cb->sc_private = so; 816 817 so->so_tree = NULL; 818 so->so_ctrl = sc; 819 so->so_info = si; 820 if ( ps ) { 821 so->so_paged = op->o_pagedresults; 822 so->so_page_size = ps->ps_size; 823 op->o_pagedresults = SLAP_CONTROL_IGNORED; 824 } else { 825 so->so_paged = 0; 826 so->so_page_size = 0; 827 if ( vc ) { 828 so->so_vlv = op->o_ctrlflag[vlv_cid]; 829 so->so_vlv_target = 0; 830 so->so_vlv_rc = 0; 831 } else { 832 so->so_vlv = SLAP_CONTROL_NONE; 833 } 834 } 835 so->so_vcontext = (unsigned long)so; 836 so->so_nentries = 0; 837 838 op->o_callback = cb; 839 } 840 } else { 841 if ( so && !so->so_nentries ) { 842 free_sort_op( op->o_conn, so ); 843 } else { 844 rs->sr_text = "Other sort requests already in progress"; 845 rs->sr_err = LDAP_BUSY; 846 } 847 leave: 848 rc = rs->sr_err; 849 send_ldap_result( op, rs ); 850 } 851 852 return rc; 853 } 854 855 static int get_ordering_rule( 856 AttributeDescription *ad, 857 struct berval *matchrule, 858 SlapReply *rs, 859 MatchingRule **ordering ) 860 { 861 MatchingRule* mr; 862 863 if ( matchrule && matchrule->bv_val ) { 864 mr = mr_find( matchrule->bv_val ); 865 if ( mr == NULL ) { 866 rs->sr_err = LDAP_INAPPROPRIATE_MATCHING; 867 rs->sr_text = "serverSort control: No ordering rule"; 868 Debug(LDAP_DEBUG_TRACE, "%s: no ordering rule function for %s\n", 869 debug_header, matchrule->bv_val, 0); 870 } 871 } 872 else { 873 mr = ad->ad_type->sat_ordering; 874 if ( mr == NULL ) { 875 rs->sr_err = LDAP_INAPPROPRIATE_MATCHING; 876 rs->sr_text = "serverSort control: No ordering rule"; 877 Debug(LDAP_DEBUG_TRACE, 878 "%s: no ordering rule specified and no default ordering rule for attribute %s\n", 879 debug_header, ad->ad_cname.bv_val, 0); 880 } 881 } 882 883 *ordering = mr; 884 return rs->sr_err; 885 } 886 887 static int count_key(BerElement *ber) 888 { 889 char *end; 890 ber_len_t len; 891 ber_tag_t tag; 892 int count = 0; 893 894 /* Server Side Sort Control is a SEQUENCE of SEQUENCE */ 895 for ( tag = ber_first_element( ber, &len, &end ); 896 tag == LBER_SEQUENCE; 897 tag = ber_next_element( ber, &len, end )) 898 { 899 tag = ber_skip_tag( ber, &len ); 900 ber_skip_data( ber, len ); 901 ++count; 902 } 903 ber_rewind( ber ); 904 905 return count; 906 } 907 908 static int build_key( 909 BerElement *ber, 910 SlapReply *rs, 911 sort_key *key ) 912 { 913 struct berval attr; 914 struct berval matchrule = BER_BVNULL; 915 ber_int_t reverse = 0; 916 ber_tag_t tag; 917 ber_len_t len; 918 MatchingRule *ordering = NULL; 919 AttributeDescription *ad = NULL; 920 const char *text; 921 922 if (( tag = ber_scanf( ber, "{" )) == LBER_ERROR ) { 923 rs->sr_text = "serverSort control: decoding error"; 924 rs->sr_err = LDAP_PROTOCOL_ERROR; 925 return rs->sr_err; 926 } 927 928 if (( tag = ber_scanf( ber, "m", &attr )) == LBER_ERROR ) { 929 rs->sr_text = "serverSort control: attribute decoding error"; 930 rs->sr_err = LDAP_PROTOCOL_ERROR; 931 return rs->sr_err; 932 } 933 934 tag = ber_peek_tag( ber, &len ); 935 if ( tag == LDAP_MATCHRULE_IDENTIFIER ) { 936 if (( tag = ber_scanf( ber, "m", &matchrule )) == LBER_ERROR ) { 937 rs->sr_text = "serverSort control: matchrule decoding error"; 938 rs->sr_err = LDAP_PROTOCOL_ERROR; 939 return rs->sr_err; 940 } 941 tag = ber_peek_tag( ber, &len ); 942 } 943 944 if ( tag == LDAP_REVERSEORDER_IDENTIFIER ) { 945 if (( tag = ber_scanf( ber, "b", &reverse )) == LBER_ERROR ) { 946 rs->sr_text = "serverSort control: reverse decoding error"; 947 rs->sr_err = LDAP_PROTOCOL_ERROR; 948 return rs->sr_err; 949 } 950 } 951 952 if (( tag = ber_scanf( ber, "}" )) == LBER_ERROR ) { 953 rs->sr_text = "serverSort control: decoding error"; 954 rs->sr_err = LDAP_PROTOCOL_ERROR; 955 return rs->sr_err; 956 } 957 958 if ( slap_bv2ad( &attr, &ad, &text ) != LDAP_SUCCESS ) { 959 rs->sr_text = 960 "serverSort control: Unrecognized attribute type in sort key"; 961 Debug(LDAP_DEBUG_TRACE, 962 "%s: Unrecognized attribute type in sort key: %s\n", 963 debug_header, SAFESTR(attr.bv_val, "<None>"), 0); 964 rs->sr_err = LDAP_NO_SUCH_ATTRIBUTE; 965 return rs->sr_err; 966 } 967 968 /* get_ordering_rule will set sr_err and sr_text */ 969 get_ordering_rule( ad, &matchrule, rs, &ordering ); 970 if ( rs->sr_err != LDAP_SUCCESS ) { 971 return rs->sr_err; 972 } 973 974 key->sk_ad = ad; 975 key->sk_ordering = ordering; 976 key->sk_direction = reverse ? -1 : 1; 977 978 return rs->sr_err; 979 } 980 981 static int sss_parseCtrl( 982 Operation *op, 983 SlapReply *rs, 984 LDAPControl *ctrl ) 985 { 986 BerElementBuffer berbuf; 987 BerElement *ber; 988 ber_tag_t tag; 989 ber_len_t len; 990 int i; 991 sort_ctrl *sc; 992 993 rs->sr_err = LDAP_PROTOCOL_ERROR; 994 995 if ( op->o_ctrlflag[sss_cid] > SLAP_CONTROL_IGNORED ) { 996 rs->sr_text = "sorted results control specified multiple times"; 997 } else if ( BER_BVISNULL( &ctrl->ldctl_value ) ) { 998 rs->sr_text = "sorted results control value is absent"; 999 } else if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) { 1000 rs->sr_text = "sorted results control value is empty"; 1001 } else { 1002 rs->sr_err = LDAP_SUCCESS; 1003 } 1004 if ( rs->sr_err != LDAP_SUCCESS ) 1005 return rs->sr_err; 1006 1007 op->o_ctrlflag[sss_cid] = ctrl->ldctl_iscritical ? 1008 SLAP_CONTROL_CRITICAL : SLAP_CONTROL_NONCRITICAL; 1009 1010 ber = (BerElement *)&berbuf; 1011 ber_init2( ber, &ctrl->ldctl_value, 0 ); 1012 i = count_key( ber ); 1013 1014 sc = op->o_tmpalloc( sizeof(sort_ctrl) + 1015 (i-1) * sizeof(sort_key), op->o_tmpmemctx ); 1016 sc->sc_nkeys = i; 1017 op->o_controls[sss_cid] = sc; 1018 1019 /* peel off initial sequence */ 1020 ber_scanf( ber, "{" ); 1021 1022 i = 0; 1023 do { 1024 if ( build_key( ber, rs, &sc->sc_keys[i] ) != LDAP_SUCCESS ) 1025 break; 1026 i++; 1027 tag = ber_peek_tag( ber, &len ); 1028 } while ( tag != LBER_DEFAULT ); 1029 1030 return rs->sr_err; 1031 } 1032 1033 static int vlv_parseCtrl( 1034 Operation *op, 1035 SlapReply *rs, 1036 LDAPControl *ctrl ) 1037 { 1038 BerElementBuffer berbuf; 1039 BerElement *ber; 1040 ber_tag_t tag; 1041 ber_len_t len; 1042 int i; 1043 vlv_ctrl *vc, vc2; 1044 1045 rs->sr_err = LDAP_PROTOCOL_ERROR; 1046 rs->sr_text = NULL; 1047 1048 if ( op->o_ctrlflag[vlv_cid] > SLAP_CONTROL_IGNORED ) { 1049 rs->sr_text = "vlv control specified multiple times"; 1050 } else if ( BER_BVISNULL( &ctrl->ldctl_value ) ) { 1051 rs->sr_text = "vlv control value is absent"; 1052 } else if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) { 1053 rs->sr_text = "vlv control value is empty"; 1054 } 1055 if ( rs->sr_text != NULL ) 1056 return rs->sr_err; 1057 1058 op->o_ctrlflag[vlv_cid] = ctrl->ldctl_iscritical ? 1059 SLAP_CONTROL_CRITICAL : SLAP_CONTROL_NONCRITICAL; 1060 1061 ber = (BerElement *)&berbuf; 1062 ber_init2( ber, &ctrl->ldctl_value, 0 ); 1063 1064 rs->sr_err = LDAP_PROTOCOL_ERROR; 1065 1066 tag = ber_scanf( ber, "{ii", &vc2.vc_before, &vc2.vc_after ); 1067 if ( tag == LBER_ERROR ) { 1068 return rs->sr_err; 1069 } 1070 1071 tag = ber_peek_tag( ber, &len ); 1072 if ( tag == LDAP_VLVBYINDEX_IDENTIFIER ) { 1073 tag = ber_scanf( ber, "{ii}", &vc2.vc_offset, &vc2.vc_count ); 1074 if ( tag == LBER_ERROR ) 1075 return rs->sr_err; 1076 BER_BVZERO( &vc2.vc_value ); 1077 } else if ( tag == LDAP_VLVBYVALUE_IDENTIFIER ) { 1078 tag = ber_scanf( ber, "m", &vc2.vc_value ); 1079 if ( tag == LBER_ERROR || BER_BVISNULL( &vc2.vc_value )) 1080 return rs->sr_err; 1081 } else { 1082 return rs->sr_err; 1083 } 1084 tag = ber_peek_tag( ber, &len ); 1085 if ( tag == LDAP_VLVCONTEXT_IDENTIFIER ) { 1086 struct berval bv; 1087 tag = ber_scanf( ber, "m", &bv ); 1088 if ( tag == LBER_ERROR || bv.bv_len != sizeof(vc2.vc_context)) 1089 return rs->sr_err; 1090 AC_MEMCPY( &vc2.vc_context, bv.bv_val, bv.bv_len ); 1091 } else { 1092 vc2.vc_context = 0; 1093 } 1094 1095 vc = op->o_tmpalloc( sizeof(vlv_ctrl), op->o_tmpmemctx ); 1096 *vc = vc2; 1097 op->o_controls[vlv_cid] = vc; 1098 rs->sr_err = LDAP_SUCCESS; 1099 1100 return rs->sr_err; 1101 } 1102 1103 static int sssvlv_connection_destroy( BackendDB *be, Connection *conn ) 1104 { 1105 slap_overinst *on = (slap_overinst *)be->bd_info; 1106 1107 if ( sort_conns[conn->c_conn_idx] ) 1108 free_sort_op( conn, sort_conns[conn->c_conn_idx] ); 1109 1110 return LDAP_SUCCESS; 1111 } 1112 1113 static int sssvlv_db_open( 1114 BackendDB *be, 1115 ConfigReply *cr ) 1116 { 1117 slap_overinst *on = (slap_overinst *)be->bd_info; 1118 sssvlv_info *si = on->on_bi.bi_private; 1119 int rc; 1120 1121 /* If not set, default to 1/2 of available threads */ 1122 if ( !si->svi_max ) 1123 si->svi_max = connection_pool_max / 2; 1124 1125 rc = overlay_register_control( be, LDAP_CONTROL_SORTREQUEST ); 1126 if ( rc == LDAP_SUCCESS ) 1127 rc = overlay_register_control( be, LDAP_CONTROL_VLVREQUEST ); 1128 return rc; 1129 } 1130 1131 static ConfigTable sssvlv_cfg[] = { 1132 { "sssvlv-max", "num", 1133 2, 2, 0, ARG_INT|ARG_OFFSET, 1134 (void *)offsetof(sssvlv_info, svi_max), 1135 "( OLcfgOvAt:21.1 NAME 'olcSssVlvMax' " 1136 "DESC 'Maximum number of concurrent Sort requests' " 1137 "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, 1138 { "sssvlv-maxkeys", "num", 1139 2, 2, 0, ARG_INT|ARG_OFFSET, 1140 (void *)offsetof(sssvlv_info, svi_max_keys), 1141 "( OLcfgOvAt:21.2 NAME 'olcSssVlvMaxKeys' " 1142 "DESC 'Maximum number of Keys in a Sort request' " 1143 "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, 1144 { NULL, NULL, 0, 0, 0, ARG_IGNORED } 1145 }; 1146 1147 static ConfigOCs sssvlv_ocs[] = { 1148 { "( OLcfgOvOc:21.1 " 1149 "NAME 'olcSssVlvConfig' " 1150 "DESC 'SSS VLV configuration' " 1151 "SUP olcOverlayConfig " 1152 "MAY ( olcSssVlvMax $ olcSssVlvMaxKeys ) )", 1153 Cft_Overlay, sssvlv_cfg, NULL, NULL }, 1154 { NULL, 0, NULL } 1155 }; 1156 1157 static int sssvlv_db_init( 1158 BackendDB *be, 1159 ConfigReply *cr) 1160 { 1161 slap_overinst *on = (slap_overinst *)be->bd_info; 1162 sssvlv_info *si; 1163 1164 si = (sssvlv_info *)ch_malloc(sizeof(sssvlv_info)); 1165 on->on_bi.bi_private = si; 1166 1167 si->svi_max = 0; 1168 si->svi_num = 0; 1169 si->svi_max_keys = SSSVLV_DEFAULT_MAX_KEYS; 1170 1171 if ( dtblsize && !sort_conns ) { 1172 ldap_pvt_thread_mutex_init( &sort_conns_mutex ); 1173 /* accommodate for c_conn_idx == -1 */ 1174 sort_conns = ch_calloc( sizeof(sort_op *), dtblsize + 1 ); 1175 sort_conns++; 1176 } 1177 ov_count++; 1178 1179 return LDAP_SUCCESS; 1180 } 1181 1182 static int sssvlv_db_destroy( 1183 BackendDB *be, 1184 ConfigReply *cr ) 1185 { 1186 slap_overinst *on = (slap_overinst *)be->bd_info; 1187 sssvlv_info *si = (sssvlv_info *)on->on_bi.bi_private; 1188 1189 ov_count--; 1190 if ( !ov_count && sort_conns) { 1191 sort_conns--; 1192 ch_free(sort_conns); 1193 ldap_pvt_thread_mutex_destroy( &sort_conns_mutex ); 1194 } 1195 1196 if ( si ) { 1197 ch_free( si ); 1198 on->on_bi.bi_private = NULL; 1199 } 1200 return LDAP_SUCCESS; 1201 } 1202 1203 static slap_overinst sssvlv; 1204 1205 int sssvlv_initialize() 1206 { 1207 int rc; 1208 1209 sssvlv.on_bi.bi_type = "sssvlv"; 1210 sssvlv.on_bi.bi_db_init = sssvlv_db_init; 1211 sssvlv.on_bi.bi_db_destroy = sssvlv_db_destroy; 1212 sssvlv.on_bi.bi_db_open = sssvlv_db_open; 1213 sssvlv.on_bi.bi_connection_destroy = sssvlv_connection_destroy; 1214 sssvlv.on_bi.bi_op_search = sssvlv_op_search; 1215 1216 sssvlv.on_bi.bi_cf_ocs = sssvlv_ocs; 1217 1218 rc = config_register_schema( sssvlv_cfg, sssvlv_ocs ); 1219 if ( rc ) 1220 return rc; 1221 1222 rc = register_supported_control2( LDAP_CONTROL_SORTREQUEST, 1223 SLAP_CTRL_SEARCH, 1224 NULL, 1225 sss_parseCtrl, 1226 1 /* replace */, 1227 &sss_cid ); 1228 1229 if ( rc == LDAP_SUCCESS ) { 1230 rc = register_supported_control2( LDAP_CONTROL_VLVREQUEST, 1231 SLAP_CTRL_SEARCH, 1232 NULL, 1233 vlv_parseCtrl, 1234 1 /* replace */, 1235 &vlv_cid ); 1236 } 1237 1238 if ( rc == LDAP_SUCCESS ) { 1239 rc = overlay_register( &sssvlv ); 1240 if ( rc != LDAP_SUCCESS ) { 1241 Debug( LDAP_DEBUG_ANY, "Failed to register server side sort overlay\n", 0, 0, 0 ); 1242 } 1243 } 1244 else { 1245 Debug( LDAP_DEBUG_ANY, "Failed to register control %d\n", rc, 0, 0 ); 1246 } 1247 1248 return rc; 1249 } 1250 1251 #if SLAPD_OVER_SSSVLV == SLAPD_MOD_DYNAMIC 1252 int init_module( int argc, char *argv[]) 1253 { 1254 return sssvlv_initialize(); 1255 } 1256 #endif 1257 1258 #endif /* SLAPD_OVER_SSSVLV */ 1259