Patrick Welche <prlw1@cam.ac.uk>
[netbsd-mini2440.git] / external / bsd / openldap / dist / servers / slapd / overlays / valsort.c
blob814256a301a55e84b2e2ce781e2d04d49e3f8f6d
1 /* valsort.c - sort attribute values */
2 /* $OpenLDAP: pkg/ldap/servers/slapd/overlays/valsort.c,v 1.17.2.5 2008/02/11 23:26:49 kurt Exp $ */
3 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
5 * Copyright 2005-2008 The OpenLDAP Foundation.
6 * Portions copyright 2005 Symas Corporation.
7 * All rights reserved.
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted only as authorized by the OpenLDAP
11 * Public License.
13 * A copy of this license is available in the file LICENSE in the
14 * top-level directory of the distribution or, alternatively, at
15 * <http://www.OpenLDAP.org/license.html>.
17 /* ACKNOWLEDGEMENTS:
18 * This work was initially developed by Howard Chu for inclusion in
19 * OpenLDAP Software. This work was sponsored by Stanford University.
23 * This overlay sorts the values of multi-valued attributes when returning
24 * them in a search response.
26 #include "portable.h"
28 #ifdef SLAPD_OVER_VALSORT
30 #include <stdio.h>
32 #include <ac/string.h>
33 #include <ac/ctype.h>
35 #include "slap.h"
36 #include "config.h"
37 #include "lutil.h"
39 #define VALSORT_ASCEND 0
40 #define VALSORT_DESCEND 1
42 #define VALSORT_ALPHA 2
43 #define VALSORT_NUMERIC 4
45 #define VALSORT_WEIGHTED 8
47 typedef struct valsort_info {
48 struct valsort_info *vi_next;
49 struct berval vi_dn;
50 AttributeDescription *vi_ad;
51 slap_mask_t vi_sort;
52 } valsort_info;
54 static int valsort_cid;
56 static ConfigDriver valsort_cf_func;
58 static ConfigTable valsort_cfats[] = {
59 { "valsort-attr", "attribute> <dn> <sort-type", 4, 5, 0, ARG_MAGIC,
60 valsort_cf_func, "( OLcfgOvAt:5.1 NAME 'olcValSortAttr' "
61 "DESC 'Sorting rule for attribute under given DN' "
62 "EQUALITY caseIgnoreMatch "
63 "SYNTAX OMsDirectoryString )", NULL, NULL },
64 { NULL }
67 static ConfigOCs valsort_cfocs[] = {
68 { "( OLcfgOvOc:5.1 "
69 "NAME 'olcValSortConfig' "
70 "DESC 'Value Sorting configuration' "
71 "SUP olcOverlayConfig "
72 "MUST olcValSortAttr )",
73 Cft_Overlay, valsort_cfats },
74 { NULL }
77 static slap_verbmasks sorts[] = {
78 { BER_BVC("alpha-ascend"), VALSORT_ASCEND|VALSORT_ALPHA },
79 { BER_BVC("alpha-descend"), VALSORT_DESCEND|VALSORT_ALPHA },
80 { BER_BVC("numeric-ascend"), VALSORT_ASCEND|VALSORT_NUMERIC },
81 { BER_BVC("numeric-descend"), VALSORT_DESCEND|VALSORT_NUMERIC },
82 { BER_BVC("weighted"), VALSORT_WEIGHTED },
83 { BER_BVNULL, 0 }
86 static Syntax *syn_numericString;
88 static int
89 valsort_cf_func(ConfigArgs *c) {
90 slap_overinst *on = (slap_overinst *)c->bi;
91 valsort_info vitmp, *vi;
92 const char *text = NULL;
93 int i, is_numeric;
94 struct berval bv = BER_BVNULL;
96 if ( c->op == SLAP_CONFIG_EMIT ) {
97 for ( vi = on->on_bi.bi_private; vi; vi = vi->vi_next ) {
98 struct berval bv2 = BER_BVNULL, bvret;
99 char *ptr;
100 int len;
102 len = vi->vi_ad->ad_cname.bv_len + 1 + vi->vi_dn.bv_len + 2;
103 i = vi->vi_sort;
104 if ( i & VALSORT_WEIGHTED ) {
105 enum_to_verb( sorts, VALSORT_WEIGHTED, &bv2 );
106 len += bv2.bv_len + 1;
107 i ^= VALSORT_WEIGHTED;
109 if ( i ) {
110 enum_to_verb( sorts, i, &bv );
111 len += bv.bv_len + 1;
113 bvret.bv_val = ch_malloc( len+1 );
114 bvret.bv_len = len;
116 ptr = lutil_strcopy( bvret.bv_val, vi->vi_ad->ad_cname.bv_val );
117 *ptr++ = ' ';
118 *ptr++ = '"';
119 ptr = lutil_strcopy( ptr, vi->vi_dn.bv_val );
120 *ptr++ = '"';
121 if ( vi->vi_sort & VALSORT_WEIGHTED ) {
122 *ptr++ = ' ';
123 ptr = lutil_strcopy( ptr, bv2.bv_val );
125 if ( i ) {
126 *ptr++ = ' ';
127 strcpy( ptr, bv.bv_val );
129 ber_bvarray_add( &c->rvalue_vals, &bvret );
131 i = ( c->rvalue_vals != NULL ) ? 0 : 1;
132 return i;
133 } else if ( c->op == LDAP_MOD_DELETE ) {
134 if ( c->valx < 0 ) {
135 for ( vi = on->on_bi.bi_private; vi; vi = on->on_bi.bi_private ) {
136 on->on_bi.bi_private = vi->vi_next;
137 ch_free( vi->vi_dn.bv_val );
138 ch_free( vi );
140 } else {
141 valsort_info **prev;
143 for (i=0, prev = (valsort_info **)&on->on_bi.bi_private,
144 vi = *prev; vi && i<c->valx;
145 prev = &vi->vi_next, vi = vi->vi_next, i++ );
146 (*prev)->vi_next = vi->vi_next;
147 ch_free( vi->vi_dn.bv_val );
148 ch_free( vi );
150 return 0;
152 vitmp.vi_ad = NULL;
153 i = slap_str2ad( c->argv[1], &vitmp.vi_ad, &text );
154 if ( i ) {
155 snprintf( c->cr_msg, sizeof( c->cr_msg), "<%s> %s", c->argv[0], text );
156 Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
157 c->log, c->cr_msg, c->argv[1] );
158 return(1);
160 if ( is_at_single_value( vitmp.vi_ad->ad_type )) {
161 snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> %s is single-valued, ignoring", c->argv[0],
162 vitmp.vi_ad->ad_cname.bv_val );
163 Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
164 c->log, c->cr_msg, c->argv[1] );
165 return(0);
167 is_numeric = ( vitmp.vi_ad->ad_type->sat_syntax == syn_numericString ||
168 vitmp.vi_ad->ad_type->sat_syntax == slap_schema.si_syn_integer ) ? 1
169 : 0;
170 ber_str2bv( c->argv[2], 0, 0, &bv );
171 i = dnNormalize( 0, NULL, NULL, &bv, &vitmp.vi_dn, NULL );
172 if ( i ) {
173 snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unable to normalize DN", c->argv[0] );
174 Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
175 c->log, c->cr_msg, c->argv[2] );
176 return(1);
178 i = verb_to_mask( c->argv[3], sorts );
179 if ( BER_BVISNULL( &sorts[i].word )) {
180 snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unrecognized sort type", c->argv[0] );
181 Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
182 c->log, c->cr_msg, c->argv[3] );
183 return(1);
185 vitmp.vi_sort = sorts[i].mask;
186 if ( sorts[i].mask == VALSORT_WEIGHTED && c->argc == 5 ) {
187 i = verb_to_mask( c->argv[4], sorts );
188 if ( BER_BVISNULL( &sorts[i].word )) {
189 snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unrecognized sort type", c->argv[0] );
190 Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
191 c->log, c->cr_msg, c->argv[4] );
192 return(1);
194 vitmp.vi_sort |= sorts[i].mask;
196 if (( vitmp.vi_sort & VALSORT_NUMERIC ) && !is_numeric ) {
197 snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> numeric sort specified for non-numeric syntax",
198 c->argv[0] );
199 Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
200 c->log, c->cr_msg, c->argv[1] );
201 return(1);
203 vi = ch_malloc( sizeof(valsort_info) );
204 *vi = vitmp;
205 vi->vi_next = on->on_bi.bi_private;
206 on->on_bi.bi_private = vi;
207 return 0;
210 /* Use Insertion Sort algorithm on selected values */
211 static void
212 do_sort( Operation *op, Attribute *a, int beg, int num, slap_mask_t sort )
214 int i, j, gotnvals;
215 struct berval tmp, ntmp, *vals = NULL, *nvals;
217 gotnvals = (a->a_vals != a->a_nvals );
219 nvals = a->a_nvals + beg;
220 if ( gotnvals )
221 vals = a->a_vals + beg;
223 if ( sort & VALSORT_NUMERIC ) {
224 long *numbers = op->o_tmpalloc( num * sizeof(long), op->o_tmpmemctx ),
225 idx;
226 for (i=0; i<num; i++)
227 numbers[i] = strtol( nvals[i].bv_val, NULL, 0 );
229 for (i=1; i<num; i++) {
230 idx = numbers[i];
231 ntmp = nvals[i];
232 if ( gotnvals ) tmp = vals[i];
233 j = i;
234 while ( j>0 ) {
235 int cmp = (sort & VALSORT_DESCEND) ? numbers[j-1] < idx :
236 numbers[j-1] > idx;
237 if ( !cmp ) break;
238 numbers[j] = numbers[j-1];
239 nvals[j] = nvals[j-1];
240 if ( gotnvals ) vals[j] = vals[j-1];
241 j--;
243 numbers[j] = idx;
244 nvals[j] = ntmp;
245 if ( gotnvals ) vals[j] = tmp;
247 op->o_tmpfree( numbers, op->o_tmpmemctx );
248 } else {
249 for (i=1; i<num; i++) {
250 ntmp = nvals[i];
251 if ( gotnvals ) tmp = vals[i];
252 j = i;
253 while ( j>0 ) {
254 int cmp = strcmp( nvals[j-1].bv_val, ntmp.bv_val );
255 cmp = (sort & VALSORT_DESCEND) ? (cmp < 0) : (cmp > 0);
256 if ( !cmp ) break;
258 nvals[j] = nvals[j-1];
259 if ( gotnvals ) vals[j] = vals[j-1];
260 j--;
262 nvals[j] = ntmp;
263 if ( gotnvals ) vals[j] = tmp;
268 static int
269 valsort_response( Operation *op, SlapReply *rs )
271 slap_overinst *on;
272 valsort_info *vi;
273 Attribute *a;
275 /* If this is not a search response, or it is a syncrepl response,
276 * or the valsort control wants raw results, pass thru unmodified.
278 if ( rs->sr_type != REP_SEARCH ||
279 ( _SCM(op->o_sync) > SLAP_CONTROL_IGNORED ) ||
280 ( op->o_ctrlflag[valsort_cid] & SLAP_CONTROL_DATA0))
281 return SLAP_CB_CONTINUE;
283 on = (slap_overinst *) op->o_bd->bd_info;
284 vi = on->on_bi.bi_private;
286 /* And we must have something configured */
287 if ( !vi ) return SLAP_CB_CONTINUE;
289 /* Find a rule whose baseDN matches this entry */
290 for (; vi; vi = vi->vi_next ) {
291 int i, n;
293 if ( !dnIsSuffix( &rs->sr_entry->e_nname, &vi->vi_dn ))
294 continue;
296 /* Find attr that this rule affects */
297 a = attr_find( rs->sr_entry->e_attrs, vi->vi_ad );
298 if ( !a ) continue;
300 if (( rs->sr_flags & ( REP_ENTRY_MODIFIABLE|REP_ENTRY_MUSTBEFREED )) !=
301 ( REP_ENTRY_MODIFIABLE|REP_ENTRY_MUSTBEFREED )) {
302 rs->sr_entry = entry_dup( rs->sr_entry );
303 rs->sr_flags |= REP_ENTRY_MODIFIABLE|REP_ENTRY_MUSTBEFREED;
304 a = attr_find( rs->sr_entry->e_attrs, vi->vi_ad );
307 n = a->a_numvals;
308 if ( vi->vi_sort & VALSORT_WEIGHTED ) {
309 int j, gotnvals;
310 long *index = op->o_tmpalloc( n * sizeof(long), op->o_tmpmemctx );
312 gotnvals = (a->a_vals != a->a_nvals );
314 for (i=0; i<n; i++) {
315 char *ptr = ber_bvchr( &a->a_nvals[i], '{' );
316 char *end = NULL;
317 if ( !ptr ) {
318 Debug(LDAP_DEBUG_TRACE, "weights missing from attr %s "
319 "in entry %s\n", vi->vi_ad->ad_cname.bv_val,
320 rs->sr_entry->e_name.bv_val, 0 );
321 break;
323 index[i] = strtol( ptr+1, &end, 0 );
324 if ( *end != '}' ) {
325 Debug(LDAP_DEBUG_TRACE, "weights misformatted "
326 "in entry %s\n",
327 rs->sr_entry->e_name.bv_val, 0, 0 );
328 break;
330 /* Strip out weights */
331 ptr = a->a_nvals[i].bv_val;
332 end++;
333 for (;*end;)
334 *ptr++ = *end++;
335 *ptr = '\0';
336 a->a_nvals[i].bv_len = ptr - a->a_nvals[i].bv_val;
338 if ( a->a_vals != a->a_nvals ) {
339 ptr = a->a_vals[i].bv_val;
340 end = ber_bvchr( &a->a_vals[i], '}' );
341 assert( end != NULL );
342 end++;
343 for (;*end;)
344 *ptr++ = *end++;
345 *ptr = '\0';
346 a->a_vals[i].bv_len = ptr - a->a_vals[i].bv_val;
349 /* An attr was missing weights here, ignore it */
350 if ( i<n ) {
351 op->o_tmpfree( index, op->o_tmpmemctx );
352 continue;
354 /* Insertion sort */
355 for ( i=1; i<n; i++) {
356 long idx = index[i];
357 struct berval tmp = a->a_vals[i], ntmp;
358 if ( gotnvals ) ntmp = a->a_nvals[i];
359 j = i;
360 while (( j>0 ) && (index[j-1] > idx )) {
361 index[j] = index[j-1];
362 a->a_vals[j] = a->a_vals[j-1];
363 if ( gotnvals ) a->a_nvals[j] = a->a_nvals[j-1];
364 j--;
366 index[j] = idx;
367 a->a_vals[j] = tmp;
368 if ( gotnvals ) a->a_nvals[j] = ntmp;
370 /* Check for secondary sort */
371 if ( vi->vi_sort ^ VALSORT_WEIGHTED ) {
372 for ( i=0; i<n;) {
373 for (j=i+1; j<n; j++) {
374 if (index[i] != index[j])
375 break;
377 if( j-i > 1 )
378 do_sort( op, a, i, j-i, vi->vi_sort );
379 i = j;
382 op->o_tmpfree( index, op->o_tmpmemctx );
383 } else {
384 do_sort( op, a, 0, n, vi->vi_sort );
387 return SLAP_CB_CONTINUE;
390 static int
391 valsort_add( Operation *op, SlapReply *rs )
393 slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
394 valsort_info *vi = on->on_bi.bi_private;
396 Attribute *a;
397 int i;
398 char *ptr, *end;
400 /* See if any weighted sorting applies to this entry */
401 for ( ;vi;vi=vi->vi_next ) {
402 if ( !dnIsSuffix( &op->o_req_ndn, &vi->vi_dn ))
403 continue;
404 if ( !(vi->vi_sort & VALSORT_WEIGHTED ))
405 continue;
406 a = attr_find( op->ora_e->e_attrs, vi->vi_ad );
407 if ( !a )
408 continue;
409 for (i=0; !BER_BVISNULL( &a->a_vals[i] ); i++) {
410 ptr = ber_bvchr(&a->a_vals[i], '{' );
411 if ( !ptr ) {
412 Debug(LDAP_DEBUG_TRACE, "weight missing from attribute %s\n",
413 vi->vi_ad->ad_cname.bv_val, 0, 0);
414 send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION,
415 "weight missing from attribute" );
416 return rs->sr_err;
418 strtol( ptr+1, &end, 0 );
419 if ( *end != '}' ) {
420 Debug(LDAP_DEBUG_TRACE, "weight is misformatted in %s\n",
421 vi->vi_ad->ad_cname.bv_val, 0, 0);
422 send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION,
423 "weight is misformatted" );
424 return rs->sr_err;
428 return SLAP_CB_CONTINUE;
431 static int
432 valsort_modify( Operation *op, SlapReply *rs )
434 slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
435 valsort_info *vi = on->on_bi.bi_private;
437 Modifications *ml;
438 int i;
439 char *ptr, *end;
441 /* See if any weighted sorting applies to this entry */
442 for ( ;vi;vi=vi->vi_next ) {
443 if ( !dnIsSuffix( &op->o_req_ndn, &vi->vi_dn ))
444 continue;
445 if ( !(vi->vi_sort & VALSORT_WEIGHTED ))
446 continue;
447 for (ml = op->orm_modlist; ml; ml=ml->sml_next ) {
448 /* Must be a Delete Attr op, so no values to consider */
449 if ( !ml->sml_values )
450 continue;
451 if ( ml->sml_desc == vi->vi_ad )
452 break;
454 if ( !ml )
455 continue;
456 for (i=0; !BER_BVISNULL( &ml->sml_values[i] ); i++) {
457 ptr = ber_bvchr(&ml->sml_values[i], '{' );
458 if ( !ptr ) {
459 Debug(LDAP_DEBUG_TRACE, "weight missing from attribute %s\n",
460 vi->vi_ad->ad_cname.bv_val, 0, 0);
461 send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION,
462 "weight missing from attribute" );
463 return rs->sr_err;
465 strtol( ptr+1, &end, 0 );
466 if ( *end != '}' ) {
467 Debug(LDAP_DEBUG_TRACE, "weight is misformatted in %s\n",
468 vi->vi_ad->ad_cname.bv_val, 0, 0);
469 send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION,
470 "weight is misformatted" );
471 return rs->sr_err;
475 return SLAP_CB_CONTINUE;
478 static int
479 valsort_db_open(
480 BackendDB *be,
481 ConfigReply *cr
484 return overlay_register_control( be, LDAP_CONTROL_VALSORT );
487 static int
488 valsort_destroy(
489 BackendDB *be,
490 ConfigReply *cr
493 slap_overinst *on = (slap_overinst *)be->bd_info;
494 valsort_info *vi = on->on_bi.bi_private, *next;
496 for (; vi; vi = next) {
497 next = vi->vi_next;
498 ch_free( vi->vi_dn.bv_val );
499 ch_free( vi );
502 return 0;
505 static int
506 valsort_parseCtrl(
507 Operation *op,
508 SlapReply *rs,
509 LDAPControl *ctrl )
511 ber_tag_t tag;
512 BerElementBuffer berbuf;
513 BerElement *ber = (BerElement *)&berbuf;
514 ber_int_t flag = 0;
516 if ( BER_BVISNULL( &ctrl->ldctl_value )) {
517 rs->sr_text = "valSort control value is absent";
518 return LDAP_PROTOCOL_ERROR;
521 if ( BER_BVISEMPTY( &ctrl->ldctl_value )) {
522 rs->sr_text = "valSort control value is empty";
523 return LDAP_PROTOCOL_ERROR;
526 ber_init2( ber, &ctrl->ldctl_value, 0 );
527 if (( tag = ber_scanf( ber, "{b}", &flag )) == LBER_ERROR ) {
528 rs->sr_text = "valSort control: flag decoding error";
529 return LDAP_PROTOCOL_ERROR;
532 op->o_ctrlflag[valsort_cid] = ctrl->ldctl_iscritical ?
533 SLAP_CONTROL_CRITICAL : SLAP_CONTROL_NONCRITICAL;
534 if ( flag )
535 op->o_ctrlflag[valsort_cid] |= SLAP_CONTROL_DATA0;
537 return LDAP_SUCCESS;
540 static slap_overinst valsort;
542 int valsort_initialize( void )
544 int rc;
546 valsort.on_bi.bi_type = "valsort";
547 valsort.on_bi.bi_db_destroy = valsort_destroy;
548 valsort.on_bi.bi_db_open = valsort_db_open;
550 valsort.on_bi.bi_op_add = valsort_add;
551 valsort.on_bi.bi_op_modify = valsort_modify;
553 valsort.on_response = valsort_response;
555 valsort.on_bi.bi_cf_ocs = valsort_cfocs;
557 rc = register_supported_control( LDAP_CONTROL_VALSORT,
558 SLAP_CTRL_SEARCH | SLAP_CTRL_HIDE, NULL, valsort_parseCtrl,
559 &valsort_cid );
560 if ( rc != LDAP_SUCCESS ) {
561 fprintf( stderr, "Failed to register control %d\n", rc );
562 return rc;
565 syn_numericString = syn_find( "1.3.6.1.4.1.1466.115.121.1.36" );
567 rc = config_register_schema( valsort_cfats, valsort_cfocs );
568 if ( rc ) return rc;
570 return overlay_register(&valsort);
573 #if SLAPD_OVER_VALSORT == SLAPD_MOD_DYNAMIC
574 int init_module( int argc, char *argv[]) {
575 return valsort_initialize();
577 #endif
579 #endif /* SLAPD_OVER_VALSORT */