]> git.sur5r.net Git - openldap/blob - servers/slapd/overlays/valsort.c
0428157732a9e2846974447f531c3e71024bdd45
[openldap] / servers / slapd / overlays / valsort.c
1 /* valsort.c - sort attribute values */
2 /* $OpenLDAP$ */
3 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
4  *
5  * Copyright 2005 The OpenLDAP Foundation.
6  * Portions copyright 2005 Symas Corporation.
7  * All rights reserved.
8  *
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.
12  *
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>.
16  */
17 /* ACKNOWLEDGEMENTS:
18  * This work was initially developed by Howard Chu for inclusion in
19  * OpenLDAP Software. This work was sponsored by Stanford University.
20  */
21
22 /*
23  * This overlay sorts the values of multi-valued attributes when returning
24  * them in a search response.
25  */
26 #include "portable.h"
27
28 #ifdef SLAPD_OVER_VALSORT
29
30 #include <stdio.h>
31
32 #include <ac/string.h>
33 #include <ac/ctype.h>
34
35 #include "slap.h"
36 #include "config.h"
37 #include "lutil.h"
38
39 #define VALSORT_ASCEND  0
40 #define VALSORT_DESCEND 1
41
42 #define VALSORT_ALPHA   2
43 #define VALSORT_NUMERIC 4
44
45 #define VALSORT_WEIGHTED        8
46
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;
53
54 static int valsort_cid;
55
56 static ConfigDriver valsort_cf_func;
57
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 }
65 };
66
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 }
75 };
76
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-ascend"), VALSORT_DESCEND|VALSORT_NUMERIC },
82         { BER_BVC("weighted"), VALSORT_WEIGHTED },
83         { BER_BVNULL, 0 }
84 };
85
86 static Syntax *syn_numericString;
87
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;
95
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;
101                         
102                         len = vi->vi_ad->ad_cname.bv_len + 1 + vi->vi_dn.bv_len + 3;
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;
108                         }
109                         BER_BVZERO( &bv );
110                         enum_to_verb( sorts, i, &bv );
111                         len += bv.bv_len;
112                         bvret.bv_val = ch_malloc( len+1 );
113                         bvret.bv_len = len;
114
115                         ptr = lutil_strcopy( bvret.bv_val, vi->vi_ad->ad_cname.bv_val );
116                         *ptr++ = ' ';
117                         *ptr++ = '"';
118                         ptr = lutil_strcopy( ptr, vi->vi_dn.bv_val );
119                         *ptr++ = '"';
120                         if ( vi->vi_sort & VALSORT_WEIGHTED ) {
121                                 *ptr++ = ' ';
122                                 ptr = lutil_strcopy( ptr, bv2.bv_val );
123                         }
124                         if ( !BER_BVISNULL( &bv )) {
125                                 *ptr++ = ' ';
126                                 strcpy( ptr, bv.bv_val );
127                         }
128                         ber_bvarray_add( &c->rvalue_vals, &bvret );
129                 }
130                 i = ( c->rvalue_vals != NULL ) ? 0 : 1;
131                 return i;
132         } else if ( c->op == LDAP_MOD_DELETE ) {
133                 if ( c->valx < 0 ) {
134                         for ( vi = on->on_bi.bi_private; vi; vi = c->be->be_private ) {
135                                 on->on_bi.bi_private = vi->vi_next;
136                                 ch_free( vi->vi_dn.bv_val );
137                                 ch_free( vi );
138                         }
139                 } else {
140                         valsort_info **prev;
141
142                         for (i=0, prev = (valsort_info **)&on->on_bi.bi_private,
143                                 vi = *prev; vi && i<c->valx;
144                                 prev = &vi->vi_next, vi = vi->vi_next, i++ );
145                         (*prev)->vi_next = vi->vi_next;
146                         ch_free( vi->vi_dn.bv_val );
147                         ch_free( vi );
148                 }
149                 return 0;
150         }
151         vitmp.vi_ad = NULL;
152         i = slap_str2ad( c->argv[1], &vitmp.vi_ad, &text );
153         if ( i ) {
154                 sprintf( c->msg, "<%s> %s", c->argv[0], text );
155                 Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
156                         c->log, c->msg, c->argv[1] );
157                 return(1);
158         }
159         if ( is_at_single_value( vitmp.vi_ad->ad_type )) {
160                 sprintf( c->msg, "<%s> %s is single-valued, ignoring", c->argv[0],
161                         vitmp.vi_ad->ad_cname.bv_val );
162                 Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
163                         c->log, c->msg, c->argv[1] );
164                 return(0);
165         }
166         is_numeric = ( vitmp.vi_ad->ad_type->sat_syntax == syn_numericString ||
167                 vitmp.vi_ad->ad_type->sat_syntax == slap_schema.si_syn_integer ) ? 1
168                 : 0;
169         ber_str2bv( c->argv[2], 0, 0, &bv );
170         i = dnNormalize( 0, NULL, NULL, &bv, &vitmp.vi_dn, NULL );
171         if ( i ) {
172                 sprintf( c->msg, "<%s> unable to normalize DN", c->argv[0] );
173                 Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
174                         c->log, c->msg, c->argv[2] );
175                 return(1);
176         }
177         i = verb_to_mask( c->argv[3], sorts );
178         if ( BER_BVISNULL( &sorts[i].word )) {
179                 sprintf( c->msg, "<%s> unrecognized sort type", c->argv[0] );
180                 Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
181                         c->log, c->msg, c->argv[3] );
182                 return(1);
183         }
184         vitmp.vi_sort = sorts[i].mask;
185         if ( sorts[i].mask == VALSORT_WEIGHTED && c->argc == 5 ) {
186                 i = verb_to_mask( c->argv[4], sorts );
187                 if ( BER_BVISNULL( &sorts[i].word )) {
188                         sprintf( c->msg, "<%s> unrecognized sort type", c->argv[0] );
189                         Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
190                                 c->log, c->msg, c->argv[4] );
191                         return(1);
192                 }
193                 vitmp.vi_sort |= sorts[i].mask;
194         }
195         if (( vitmp.vi_sort & VALSORT_NUMERIC ) && !is_numeric ) {
196                 sprintf( c->msg, "<%s> numeric sort specified for non-numeric syntax",
197                         c->argv[0] );
198                 Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
199                         c->log, c->msg, c->argv[1] );
200                 return(1);
201         }
202         vi = ch_malloc( sizeof(valsort_info) );
203         *vi = vitmp;
204         vi->vi_next = on->on_bi.bi_private;
205         on->on_bi.bi_private = vi;
206         return 0;
207 }
208
209 /* Use Insertion Sort algorithm on selected values */
210 static void
211 do_sort( Operation *op, Attribute *a, int beg, int num, slap_mask_t sort )
212 {
213         int i, j, gotnvals;
214         struct berval tmp, ntmp, *vals, *nvals;
215
216         gotnvals = (a->a_vals != a->a_nvals );
217
218         nvals = a->a_nvals + beg;
219         if ( gotnvals )
220                 vals = a->a_vals + beg;
221
222         if ( sort & VALSORT_NUMERIC ) {
223                 long *numbers = op->o_tmpalloc( num * sizeof(long), op->o_tmpmemctx ),
224                         idx;
225                 for (i=0; i<num; i++)
226                         numbers[i] = strtol( nvals[i].bv_val, NULL, 0 );
227
228                 for (i=1; i<num; i++) {
229                         idx = numbers[i];
230                         ntmp = nvals[i];
231                         if ( gotnvals ) tmp = vals[i];
232                         j = i;
233                         while ( j>0 ) {
234                                 int cmp = (sort & VALSORT_DESCEND) ? numbers[j-1] < idx :
235                                         numbers[j-1] > idx;
236                                 if ( !cmp ) break;
237                                 numbers[j] = numbers[j-1];
238                                 nvals[j] = nvals[j-1];
239                                 if ( gotnvals ) vals[j] = vals[j-1];
240                                 j--;
241                         }
242                         numbers[j] = idx;
243                         nvals[j] = ntmp;
244                         if ( gotnvals ) vals[j] = tmp;
245                 }
246                 op->o_tmpfree( numbers, op->o_tmpmemctx );
247         } else {
248                 for (i=1; i<num; i++) {
249                         ntmp = nvals[i];
250                         if ( gotnvals ) tmp = vals[i];
251                         j = i;
252                         while ( j>0 ) {
253                                 int cmp = strcmp( nvals[j-1].bv_val, ntmp.bv_val );
254                                 cmp = (sort & VALSORT_DESCEND) ? (cmp < 0) : (cmp > 0);
255                                 if ( !cmp ) break;
256
257                                 nvals[j] = nvals[j-1];
258                                 if ( gotnvals ) vals[j] = vals[j-1];
259                                 j--;
260                         }
261                         nvals[j] = ntmp;
262                         if ( gotnvals ) vals[j] = tmp;
263                 }
264         }
265 }
266
267 static int
268 valsort_response( Operation *op, SlapReply *rs )
269 {
270         slap_overinst *on;
271         valsort_info *vi;
272         Attribute *a;
273
274         /* We only want search responses */
275         if ( rs->sr_type != REP_SEARCH ) return SLAP_CB_CONTINUE;
276
277         on = (slap_overinst *) op->o_bd->bd_info;
278         vi = on->on_bi.bi_private;
279
280         /* And we must have something configured */
281         if ( !vi ) return SLAP_CB_CONTINUE;
282
283         /* Find a rule whose baseDN matches this entry */
284         for (; vi; vi = vi->vi_next ) {
285                 int i, n;
286
287                 if ( !dnIsSuffix( &rs->sr_entry->e_nname, &vi->vi_dn ))
288                         continue;
289
290                 /* Find attr that this rule affects */
291                 a = attr_find( rs->sr_entry->e_attrs, vi->vi_ad );
292                 if ( !a ) continue;
293
294                 if (( rs->sr_flags & ( REP_ENTRY_MODIFIABLE|REP_ENTRY_MUSTBEFREED )) !=
295                         ( REP_ENTRY_MODIFIABLE|REP_ENTRY_MUSTBEFREED )) {
296                         rs->sr_entry = entry_dup( rs->sr_entry );
297                         rs->sr_flags |= REP_ENTRY_MODIFIABLE|REP_ENTRY_MUSTBEFREED;
298                         a = attr_find( rs->sr_entry->e_attrs, vi->vi_ad );
299                 }
300
301                 /* count values */
302                 for ( n = 0; !BER_BVISNULL( &a->a_vals[n] ); n++ );
303
304                 if ( vi->vi_sort & VALSORT_WEIGHTED ) {
305                         int j, gotnvals;
306                         long *index = op->o_tmpalloc( n * sizeof(long), op->o_tmpmemctx );
307
308                         gotnvals = (a->a_vals != a->a_nvals );
309
310                         for (i=0; i<n; i++) {
311                                 char *ptr = strchr( a->a_nvals[i].bv_val, '{' );
312                                 char *end = NULL;
313                                 if ( !ptr ) {
314                                         Debug(LDAP_DEBUG_TRACE, "weights missing from attr %s "
315                                                 "in entry %s\n", vi->vi_ad->ad_cname.bv_val,
316                                                 rs->sr_entry->e_name.bv_val, 0 );
317                                         break;
318                                 }
319                                 index[i] = strtol( ptr+1, &end, 0 );
320                                 if ( *end != '}' ) {
321                                         Debug(LDAP_DEBUG_TRACE, "weights misformatted "
322                                                 "in entry %s\n", 
323                                                 rs->sr_entry->e_name.bv_val, 0, 0 );
324                                         break;
325                                 }
326                                 /* Strip out weights */
327                                 ptr = a->a_nvals[i].bv_val;
328                                 end++;
329                                 for (;*end;)
330                                         *ptr++ = *end++;
331                                 *ptr = '\0';
332                                 a->a_nvals[i].bv_len = ptr - a->a_nvals[i].bv_val;
333
334                                 if ( a->a_vals != a->a_nvals ) {
335                                         ptr = a->a_vals[i].bv_val;
336                                         end = strchr( ptr, '}' ) + 1;
337                                         for (;*end;)
338                                                 *ptr++ = *end++;
339                                         *ptr = '\0';
340                                         a->a_vals[i].bv_len = ptr - a->a_vals[i].bv_val;
341                                 }
342                         }
343                         /* An attr was missing weights here, ignore it */
344                         if ( i<n ) {
345                                 op->o_tmpfree( index, op->o_tmpmemctx );
346                                 continue;
347                         }
348                         /* Insertion sort */
349                         for ( i=1; i<n; i++) {
350                                 long idx = index[i];
351                                 struct berval tmp = a->a_vals[i], ntmp;
352                                 if ( gotnvals ) ntmp = a->a_nvals[i];
353                                 j = i;
354                                 while (( j>0 ) && (index[j-1] > idx )) {
355                                         index[j] = index[j-1];
356                                         a->a_vals[j] = a->a_vals[j-1];
357                                         if ( gotnvals ) a->a_nvals[j] = a->a_nvals[j-1];
358                                         j--;
359                                 }
360                                 index[j] = idx;
361                                 a->a_vals[j] = tmp;
362                                 if ( gotnvals ) a->a_nvals[j] = ntmp;
363                         }
364                         /* Check for secondary sort */
365                         if ( vi->vi_sort ^ VALSORT_WEIGHTED ) {
366                                 for ( i=0; i<n;) {
367                                         for (j=i+1; j<n; j++) {
368                                                 if (index[i] != index[j])
369                                                         break;
370                                         }
371                                         if( j-i > 1 )
372                                                 do_sort( op, a, i, j-i, vi->vi_sort );
373                                         i = j;
374                                 }
375                         }
376                         op->o_tmpfree( index, op->o_tmpmemctx );
377                 } else {
378                         do_sort( op, a, 0, n, vi->vi_sort );
379                 }
380         }
381         return SLAP_CB_CONTINUE;
382 }
383
384 static int
385 valsort_add( Operation *op, SlapReply *rs )
386 {
387         slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
388         valsort_info *vi = on->on_bi.bi_private;
389
390         Attribute *a;
391         int i;
392         char *ptr, *end;
393
394         /* See if any weighted sorting applies to this entry */
395         for ( ;vi;vi=vi->vi_next ) {
396                 if ( !dnIsSuffix( &op->o_req_ndn, &vi->vi_dn ))
397                         continue;
398                 if ( !(vi->vi_sort & VALSORT_WEIGHTED ))
399                         continue;
400                 a = attr_find( op->ora_e->e_attrs, vi->vi_ad );
401                 if ( !a )
402                         continue;
403                 for (i=0; !BER_BVISNULL( &a->a_vals[i] ); i++) {
404                         ptr = strchr(a->a_vals[i].bv_val, '{' );
405                         if ( !ptr ) {
406                                 Debug(LDAP_DEBUG_TRACE, "weight missing from attribute %s\n",
407                                         vi->vi_ad->ad_cname.bv_val, 0, 0);
408                                 send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION,
409                                         "weight missing from attribute" );
410                                 return rs->sr_err;
411                         }
412                         strtol( ptr+1, &end, 0 );
413                         if ( *end != '}' ) {
414                                 Debug(LDAP_DEBUG_TRACE, "weight is misformatted in %s\n",
415                                         vi->vi_ad->ad_cname.bv_val, 0, 0);
416                                 send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION,
417                                         "weight is misformatted" );
418                                 return rs->sr_err;
419                         }
420                 }
421         }
422         return SLAP_CB_CONTINUE;
423 }
424
425 static int
426 valsort_modify( Operation *op, SlapReply *rs )
427 {
428         slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
429         valsort_info *vi = on->on_bi.bi_private;
430
431         Modifications *ml;
432         int i;
433         char *ptr, *end;
434
435         /* See if any weighted sorting applies to this entry */
436         for ( ;vi;vi=vi->vi_next ) {
437                 if ( !dnIsSuffix( &op->o_req_ndn, &vi->vi_dn ))
438                         continue;
439                 if ( !(vi->vi_sort & VALSORT_WEIGHTED ))
440                         continue;
441                 for (ml = op->orm_modlist; ml; ml=ml->sml_next ) {
442                         if ( ml->sml_desc == vi->vi_ad )
443                                 break;
444                 }
445                 if ( !ml )
446                         continue;
447                 for (i=0; !BER_BVISNULL( &ml->sml_values[i] ); i++) {
448                         ptr = strchr(ml->sml_values[i].bv_val, '{' );
449                         if ( !ptr ) {
450                                 Debug(LDAP_DEBUG_TRACE, "weight missing from attribute %s\n",
451                                         vi->vi_ad->ad_cname.bv_val, 0, 0);
452                                 send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION,
453                                         "weight missing from attribute" );
454                                 return rs->sr_err;
455                         }
456                         strtol( ptr+1, &end, 0 );
457                         if ( *end != '}' ) {
458                                 Debug(LDAP_DEBUG_TRACE, "weight is misformatted in %s\n",
459                                         vi->vi_ad->ad_cname.bv_val, 0, 0);
460                                 send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION,
461                                         "weight is misformatted" );
462                                 return rs->sr_err;
463                         }
464                 }
465         }
466         return SLAP_CB_CONTINUE;
467 }
468
469 static int
470 valsort_destroy(
471         BackendDB *be
472 )
473 {
474         slap_overinst *on = (slap_overinst *)be->bd_info;
475         valsort_info *vi = on->on_bi.bi_private, *next;
476
477         for (; vi; vi = next) {
478                 next = vi->vi_next;
479                 ch_free( vi->vi_dn.bv_val );
480                 ch_free( vi );
481         }
482
483         return 0;
484 }
485
486 static int
487 valsort_parseCtrl(
488         Operation *op,
489         SlapReply *rs,
490         LDAPControl *ctrl )
491 {
492         if ( ctrl->ldctl_value.bv_len ) {
493                 rs->sr_text = "valSort control value not empty";
494                 return LDAP_PROTOCOL_ERROR;
495         }
496         op->o_ctrlflag[valsort_cid] = ctrl->ldctl_iscritical ?
497                 SLAP_CONTROL_CRITICAL : SLAP_CONTROL_NONCRITICAL;
498
499         return LDAP_SUCCESS;
500 }
501
502 static slap_overinst valsort;
503
504 int valsort_init()
505 {
506         int i, rc;
507
508         valsort.on_bi.bi_type = "valsort";
509         valsort.on_bi.bi_db_destroy = valsort_destroy;
510
511         valsort.on_bi.bi_op_add = valsort_add;
512         valsort.on_bi.bi_op_modify = valsort_modify;
513
514         valsort.on_response = valsort_response;
515
516         valsort.on_bi.bi_cf_ocs = valsort_cfocs;
517
518         rc = register_supported_control( LDAP_CONTROL_VALSORT,
519                 SLAP_CTRL_SEARCH | SLAP_CTRL_HIDE, NULL, valsort_parseCtrl,
520                 &valsort_cid );
521         if ( rc != LDAP_SUCCESS ) {
522                 fprintf( stderr, "Failed to register control %d\n", rc );
523                 return rc;
524         }
525
526         syn_numericString = syn_find( "1.3.6.1.4.1.1466.115.121.1.36" );
527
528         rc = config_register_schema( valsort_cfats, valsort_cfocs );
529         if ( rc ) return rc;
530
531         return overlay_register(&valsort);
532 }
533
534 #if SLAPD_OVER_VALSORT == SLAPD_MOD_DYNAMIC
535 int init_module( int argc, char *argv[]) {
536         return valsort_init();
537 }
538 #endif
539
540 #endif /* SLAPD_OVER_VALSORT */