1 /* $NetBSD: limits.c,v 1.3 2021/08/14 16:14:58 christos Exp $ */
2
3 /* limits.c - routines to handle regex-based size and time limits */
4 /* $OpenLDAP$ */
5 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
6 *
7 * Copyright 1998-2021 The OpenLDAP Foundation.
8 * All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted only as authorized by the OpenLDAP
12 * Public License.
13 *
14 * A copy of this license is available in the file LICENSE in the
15 * top-level directory of the distribution or, alternatively, at
16 * <http://www.OpenLDAP.org/license.html>.
17 */
18
19 #include <sys/cdefs.h>
20 __RCSID("$NetBSD: limits.c,v 1.3 2021/08/14 16:14:58 christos Exp $");
21
22 #include "portable.h"
23
24 #include <stdio.h>
25
26 #include <ac/ctype.h>
27 #include <ac/regex.h>
28 #include <ac/string.h>
29
30 #include "slap.h"
31 #include "lutil.h"
32
33 /* define to get an error if requesting limit higher than hard */
34 #undef ABOVE_HARD_LIMIT_IS_ERROR
35
36 static const struct berval lmpats[] = {
37 BER_BVC( "base" ),
38 BER_BVC( "base" ),
39 BER_BVC( "onelevel" ),
40 BER_BVC( "subtree" ),
41 BER_BVC( "children" ),
42 BER_BVC( "regex" ),
43 BER_BVC( "anonymous" ),
44 BER_BVC( "users" ),
45 BER_BVC( "*" )
46 };
47
48 #ifdef LDAP_DEBUG
49 static const char *const dn_source[2] = { "DN", "DN.THIS" };
50 static const char *const lmpats_out[] = {
51 "UNDEFINED",
52 "EXACT",
53 "ONELEVEL",
54 "SUBTREE",
55 "CHILDREN",
56 "REGEX",
57 "ANONYMOUS",
58 "USERS",
59 "ANY"
60 };
61
62 static const char *
limits2str(unsigned i)63 limits2str( unsigned i )
64 {
65 return i < (sizeof( lmpats_out ) / sizeof( lmpats_out[0] ))
66 ? lmpats_out[i] : "UNKNOWN";
67 }
68 #endif /* LDAP_DEBUG */
69
70 static int
limits_get(Operation * op,struct slap_limits_set ** limit)71 limits_get(
72 Operation *op,
73 struct slap_limits_set **limit
74 )
75 {
76 static struct berval empty_dn = BER_BVC( "" );
77 struct slap_limits **lm;
78 struct berval *ndns[2];
79
80 assert( op != NULL );
81 assert( limit != NULL );
82
83 ndns[0] = &op->o_ndn;
84 ndns[1] = &op->o_req_ndn;
85
86 Debug( LDAP_DEBUG_TRACE, "==> limits_get: %s self=\"%s\" this=\"%s\"\n",
87 op->o_log_prefix,
88 BER_BVISNULL( ndns[0] ) ? "[anonymous]" : ndns[0]->bv_val,
89 BER_BVISNULL( ndns[1] ) ? "" : ndns[1]->bv_val );
90 /*
91 * default values
92 */
93 *limit = &op->o_bd->be_def_limit;
94
95 if ( op->o_bd->be_limits == NULL ) {
96 return( 0 );
97 }
98
99 for ( lm = op->o_bd->be_limits; lm[0] != NULL; lm++ ) {
100 unsigned style = lm[0]->lm_flags & SLAP_LIMITS_MASK;
101 unsigned type = lm[0]->lm_flags & SLAP_LIMITS_TYPE_MASK;
102 unsigned isthis = type == SLAP_LIMITS_TYPE_THIS;
103 struct berval *ndn = ndns[isthis];
104
105 if ( style == SLAP_LIMITS_ANY )
106 goto found_any;
107
108 if ( BER_BVISEMPTY( ndn ) ) {
109 if ( style == SLAP_LIMITS_ANONYMOUS )
110 goto found_nodn;
111 if ( !isthis )
112 continue;
113 ndn = &empty_dn;
114 }
115
116 switch ( style ) {
117 case SLAP_LIMITS_EXACT:
118 if ( type == SLAP_LIMITS_TYPE_GROUP ) {
119 int rc = backend_group( op, NULL,
120 &lm[0]->lm_pat, ndn,
121 lm[0]->lm_group_oc,
122 lm[0]->lm_group_ad );
123 if ( rc == 0 ) {
124 goto found_group;
125 }
126 } else {
127 if ( dn_match( &lm[0]->lm_pat, ndn ) ) {
128 goto found_dn;
129 }
130 }
131 break;
132
133 case SLAP_LIMITS_ONE:
134 case SLAP_LIMITS_SUBTREE:
135 case SLAP_LIMITS_CHILDREN: {
136 ber_len_t d;
137
138 /* ndn shorter than lm_pat */
139 if ( ndn->bv_len < lm[0]->lm_pat.bv_len ) {
140 break;
141 }
142 d = ndn->bv_len - lm[0]->lm_pat.bv_len;
143
144 if ( d == 0 ) {
145 /* allow exact match for SUBTREE only */
146 if ( style != SLAP_LIMITS_SUBTREE ) {
147 break;
148 }
149 } else {
150 /* check for unescaped rdn separator */
151 if ( !DN_SEPARATOR( ndn->bv_val[d - 1] ) ) {
152 break;
153 }
154 }
155
156 /* check that ndn ends with lm_pat */
157 if ( strcmp( lm[0]->lm_pat.bv_val, &ndn->bv_val[d] ) != 0 ) {
158 break;
159 }
160
161 /* in case of ONE, require exactly one rdn below lm_pat */
162 if ( style == SLAP_LIMITS_ONE ) {
163 if ( dn_rdnlen( NULL, ndn ) != d - 1 ) {
164 break;
165 }
166 }
167
168 goto found_dn;
169 }
170
171 case SLAP_LIMITS_REGEX:
172 if ( regexec( &lm[0]->lm_regex, ndn->bv_val, 0, NULL, 0 ) == 0 ) {
173 goto found_dn;
174 }
175 break;
176
177 case SLAP_LIMITS_ANONYMOUS:
178 break;
179
180 case SLAP_LIMITS_USERS:
181 found_nodn:
182 Debug( LDAP_DEBUG_TRACE, "<== limits_get: type=%s match=%s\n",
183 dn_source[isthis], limits2str( style ) );
184 found_any:
185 *limit = &lm[0]->lm_limits;
186 return( 0 );
187
188 found_dn:
189 Debug( LDAP_DEBUG_TRACE,
190 "<== limits_get: type=%s match=%s dn=\"%s\"\n",
191 dn_source[isthis], limits2str( style ), lm[0]->lm_pat.bv_val );
192 *limit = &lm[0]->lm_limits;
193 return( 0 );
194
195 found_group:
196 Debug( LDAP_DEBUG_TRACE, "<== limits_get: type=GROUP match=EXACT "
197 "dn=\"%s\" oc=\"%s\" ad=\"%s\"\n",
198 lm[0]->lm_pat.bv_val,
199 lm[0]->lm_group_oc->soc_cname.bv_val,
200 lm[0]->lm_group_ad->ad_cname.bv_val );
201 *limit = &lm[0]->lm_limits;
202 return( 0 );
203
204 default:
205 assert( 0 ); /* unreachable */
206 return( -1 );
207 }
208 }
209
210 return( 0 );
211 }
212
213 static int
limits_add(Backend * be,unsigned flags,const char * pattern,ObjectClass * group_oc,AttributeDescription * group_ad,struct slap_limits_set * limit)214 limits_add(
215 Backend *be,
216 unsigned flags,
217 const char *pattern,
218 ObjectClass *group_oc,
219 AttributeDescription *group_ad,
220 struct slap_limits_set *limit
221 )
222 {
223 int i;
224 struct slap_limits *lm;
225 unsigned type, style;
226
227 assert( be != NULL );
228 assert( limit != NULL );
229
230 type = flags & SLAP_LIMITS_TYPE_MASK;
231 style = flags & SLAP_LIMITS_MASK;
232
233 switch ( style ) {
234 case SLAP_LIMITS_ANONYMOUS:
235 case SLAP_LIMITS_USERS:
236 case SLAP_LIMITS_ANY:
237 /* For these styles, type == 0 (SLAP_LIMITS_TYPE_SELF). */
238 for ( i = 0; be->be_limits && be->be_limits[ i ]; i++ ) {
239 if ( be->be_limits[ i ]->lm_flags == style ) {
240 return( -1 );
241 }
242 }
243 break;
244 }
245
246
247 lm = ( struct slap_limits * )ch_calloc( sizeof( struct slap_limits ), 1 );
248
249 switch ( style ) {
250 case SLAP_LIMITS_UNDEFINED:
251 style = SLAP_LIMITS_EXACT;
252 /* continue to next cases */
253 case SLAP_LIMITS_EXACT:
254 case SLAP_LIMITS_ONE:
255 case SLAP_LIMITS_SUBTREE:
256 case SLAP_LIMITS_CHILDREN:
257 {
258 int rc;
259 struct berval bv;
260
261 ber_str2bv( pattern, 0, 0, &bv );
262
263 rc = dnNormalize( 0, NULL, NULL, &bv, &lm->lm_pat, NULL );
264 if ( rc != LDAP_SUCCESS ) {
265 ch_free( lm );
266 return( -1 );
267 }
268 }
269 break;
270
271 case SLAP_LIMITS_REGEX:
272 ber_str2bv( pattern, 0, 1, &lm->lm_pat );
273 if ( regcomp( &lm->lm_regex, lm->lm_pat.bv_val,
274 REG_EXTENDED | REG_ICASE ) ) {
275 free( lm->lm_pat.bv_val );
276 ch_free( lm );
277 return( -1 );
278 }
279 break;
280
281 case SLAP_LIMITS_ANONYMOUS:
282 case SLAP_LIMITS_USERS:
283 case SLAP_LIMITS_ANY:
284 BER_BVZERO( &lm->lm_pat );
285 break;
286 }
287
288 switch ( type ) {
289 case SLAP_LIMITS_TYPE_GROUP:
290 assert( group_oc != NULL );
291 assert( group_ad != NULL );
292 lm->lm_group_oc = group_oc;
293 lm->lm_group_ad = group_ad;
294 break;
295 }
296
297 lm->lm_flags = style | type;
298 lm->lm_limits = *limit;
299
300 i = 0;
301 if ( be->be_limits != NULL ) {
302 for ( ; be->be_limits[i]; i++ );
303 }
304
305 be->be_limits = ( struct slap_limits ** )ch_realloc( be->be_limits,
306 sizeof( struct slap_limits * ) * ( i + 2 ) );
307 be->be_limits[i] = lm;
308 be->be_limits[i+1] = NULL;
309
310 return( 0 );
311 }
312
313 #define STRSTART( s, m ) (strncasecmp( s, m, STRLENOF( "" m "" )) == 0)
314
315 int
limits_parse(Backend * be,const char * fname,int lineno,int argc,char ** argv)316 limits_parse(
317 Backend *be,
318 const char *fname,
319 int lineno,
320 int argc,
321 char **argv
322 )
323 {
324 int flags = SLAP_LIMITS_UNDEFINED;
325 char *pattern;
326 struct slap_limits_set limit;
327 int i, rc = 0;
328 ObjectClass *group_oc = NULL;
329 AttributeDescription *group_ad = NULL;
330
331 assert( be != NULL );
332
333 if ( argc < 3 ) {
334 Debug( LDAP_DEBUG_ANY,
335 "%s : line %d: missing arg(s) in "
336 "\"limits <pattern> <limits>\" line.\n",
337 fname, lineno );
338 return( -1 );
339 }
340
341 limit = be->be_def_limit;
342
343 /*
344 * syntax:
345 *
346 * "limits" <pattern> <limit> [ ... ]
347 *
348 *
349 * <pattern>:
350 *
351 * "anonymous"
352 * "users"
353 * [ "dn" [ "." { "this" | "self" } ] [ "." { "exact" | "base" |
354 * "onelevel" | "subtree" | "children" | "regex" | "anonymous" } ]
355 * "=" ] <dn pattern>
356 *
357 * Note:
358 * "this" is the baseobject, "self" (the default) is the bound DN
359 * "exact" and "base" are the same (exact match);
360 * "onelevel" means exactly one rdn below, NOT including pattern
361 * "subtree" means any rdn below, including pattern
362 * "children" means any rdn below, NOT including pattern
363 *
364 * "anonymous" may be deprecated in favour
365 * of the pattern = "anonymous" form
366 *
367 * "group[/objectClass[/attributeType]]" "=" "<dn pattern>"
368 *
369 * <limit>:
370 *
371 * "time" [ "." { "soft" | "hard" } ] "=" <integer>
372 *
373 * "size" [ "." { "soft" | "hard" | "unchecked" } ] "=" <integer>
374 */
375
376 pattern = argv[1];
377 if ( strcmp( pattern, "*" ) == 0) {
378 flags = SLAP_LIMITS_ANY;
379
380 } else if ( strcasecmp( pattern, "anonymous" ) == 0 ) {
381 flags = SLAP_LIMITS_ANONYMOUS;
382
383 } else if ( strcasecmp( pattern, "users" ) == 0 ) {
384 flags = SLAP_LIMITS_USERS;
385
386 } else if ( STRSTART( pattern, "dn" ) ) {
387 pattern += STRLENOF( "dn" );
388 flags = SLAP_LIMITS_TYPE_SELF;
389 if ( pattern[0] == '.' ) {
390 pattern++;
391 if ( STRSTART( pattern, "this" ) ) {
392 flags = SLAP_LIMITS_TYPE_THIS;
393 pattern += STRLENOF( "this" );
394 } else if ( STRSTART( pattern, "self" ) ) {
395 pattern += STRLENOF( "self" );
396 } else {
397 goto got_dn_dot;
398 }
399 }
400 if ( pattern[0] == '.' ) {
401 pattern++;
402 got_dn_dot:
403 if ( STRSTART( pattern, "exact" ) ) {
404 flags |= SLAP_LIMITS_EXACT;
405 pattern += STRLENOF( "exact" );
406
407 } else if ( STRSTART( pattern, "base" ) ) {
408 flags |= SLAP_LIMITS_BASE;
409 pattern += STRLENOF( "base" );
410
411 } else if ( STRSTART( pattern, "one" ) ) {
412 flags |= SLAP_LIMITS_ONE;
413 pattern += STRLENOF( "one" );
414 if ( STRSTART( pattern, "level" ) ) {
415 pattern += STRLENOF( "level" );
416
417 } else {
418 Debug( LDAP_DEBUG_ANY,
419 "%s : line %d: deprecated \"one\" style "
420 "\"limits <pattern> <limits>\" line; "
421 "use \"onelevel\" instead.\n", fname, lineno );
422 }
423
424 } else if ( STRSTART( pattern, "sub" ) ) {
425 flags |= SLAP_LIMITS_SUBTREE;
426 pattern += STRLENOF( "sub" );
427 if ( STRSTART( pattern, "tree" ) ) {
428 pattern += STRLENOF( "tree" );
429
430 } else {
431 Debug( LDAP_DEBUG_ANY,
432 "%s : line %d: deprecated \"sub\" style "
433 "\"limits <pattern> <limits>\" line; "
434 "use \"subtree\" instead.\n", fname, lineno );
435 }
436
437 } else if ( STRSTART( pattern, "children" ) ) {
438 flags |= SLAP_LIMITS_CHILDREN;
439 pattern += STRLENOF( "children" );
440
441 } else if ( STRSTART( pattern, "regex" ) ) {
442 flags |= SLAP_LIMITS_REGEX;
443 pattern += STRLENOF( "regex" );
444
445 /*
446 * this could be deprecated in favour
447 * of the pattern = "anonymous" form
448 */
449 } else if ( STRSTART( pattern, "anonymous" )
450 && flags == SLAP_LIMITS_TYPE_SELF )
451 {
452 flags = SLAP_LIMITS_ANONYMOUS;
453 pattern = NULL;
454
455 } else {
456 /* force error below */
457 if ( *pattern == '=' )
458 --pattern;
459 }
460 }
461
462 /* pre-check the data */
463 if ( pattern != NULL ) {
464 if ( pattern[0] != '=' ) {
465 Debug( LDAP_DEBUG_ANY,
466 "%s : line %d: %s in "
467 "\"dn[.{this|self}][.{exact|base"
468 "|onelevel|subtree|children|regex"
469 "|anonymous}]=<pattern>\" in "
470 "\"limits <pattern> <limits>\" line.\n",
471 fname, lineno,
472 isalnum( (unsigned char)pattern[0] )
473 ? "unknown DN modifier" : "missing '='" );
474 return( -1 );
475 }
476
477 /* skip '=' (required) */
478 pattern++;
479
480 /* trim obvious cases */
481 if ( strcmp( pattern, "*" ) == 0 ) {
482 flags = SLAP_LIMITS_ANY;
483 pattern = NULL;
484
485 } else if ( (flags & SLAP_LIMITS_MASK) == SLAP_LIMITS_REGEX
486 && strcmp( pattern, ".*" ) == 0 ) {
487 flags = SLAP_LIMITS_ANY;
488 pattern = NULL;
489 }
490 }
491
492 } else if (STRSTART( pattern, "group" ) ) {
493 pattern += STRLENOF( "group" );
494
495 if ( pattern[0] == '/' ) {
496 struct berval oc, ad;
497
498 oc.bv_val = pattern + 1;
499 pattern = strchr( pattern, '=' );
500 if ( pattern == NULL ) {
501 return -1;
502 }
503
504 ad.bv_val = strchr( oc.bv_val, '/' );
505 if ( ad.bv_val != NULL ) {
506 const char *text = NULL;
507
508 oc.bv_len = ad.bv_val - oc.bv_val;
509
510 ad.bv_val++;
511 ad.bv_len = pattern - ad.bv_val;
512 rc = slap_bv2ad( &ad, &group_ad, &text );
513 if ( rc != LDAP_SUCCESS ) {
514 goto no_ad;
515 }
516
517 } else {
518 oc.bv_len = pattern - oc.bv_val;
519 }
520
521 group_oc = oc_bvfind( &oc );
522 if ( group_oc == NULL ) {
523 goto no_oc;
524 }
525 }
526
527 if ( group_oc == NULL ) {
528 group_oc = oc_find( SLAPD_GROUP_CLASS );
529 if ( group_oc == NULL ) {
530 no_oc:;
531 return( -1 );
532 }
533 }
534
535 if ( group_ad == NULL ) {
536 const char *text = NULL;
537
538 rc = slap_str2ad( SLAPD_GROUP_ATTR, &group_ad, &text );
539
540 if ( rc != LDAP_SUCCESS ) {
541 no_ad:;
542 return( -1 );
543 }
544 }
545
546 flags = SLAP_LIMITS_TYPE_GROUP | SLAP_LIMITS_EXACT;
547
548 if ( pattern[0] != '=' ) {
549 Debug( LDAP_DEBUG_ANY,
550 "%s : line %d: missing '=' in "
551 "\"group[/objectClass[/attributeType]]"
552 "=<pattern>\" in "
553 "\"limits <pattern> <limits>\" line.\n",
554 fname, lineno );
555 return( -1 );
556 }
557
558 /* skip '=' (required) */
559 pattern++;
560 }
561
562 /* get the limits */
563 for ( i = 2; i < argc; i++ ) {
564 if ( limits_parse_one( argv[i], &limit ) ) {
565
566 Debug( LDAP_DEBUG_ANY,
567 "%s : line %d: unknown limit values \"%s\" in "
568 "\"limits <pattern> <limits>\" line.\n",
569 fname, lineno, argv[i] );
570
571 return( 1 );
572 }
573 }
574
575 /*
576 * sanity checks ...
577 *
578 * FIXME: add warnings?
579 */
580 if ( limit.lms_t_hard > 0 &&
581 ( limit.lms_t_hard < limit.lms_t_soft
582 || limit.lms_t_soft == -1 ) ) {
583 limit.lms_t_hard = limit.lms_t_soft;
584 }
585
586 if ( limit.lms_s_hard > 0 &&
587 ( limit.lms_s_hard < limit.lms_s_soft
588 || limit.lms_s_soft == -1 ) ) {
589 limit.lms_s_hard = limit.lms_s_soft;
590 }
591
592 /*
593 * defaults ...
594 *
595 * lms_t_hard:
596 * -1 => no limits
597 * 0 => same as soft
598 * > 0 => limit (in seconds)
599 *
600 * lms_s_hard:
601 * -1 => no limits
602 * 0 0> same as soft
603 * > 0 => limit (in entries)
604 *
605 * lms_s_pr_total:
606 * -2 => disable the control
607 * -1 => no limits
608 * 0 => same as soft
609 * > 0 => limit (in entries)
610 *
611 * lms_s_pr:
612 * -1 => no limits
613 * 0 => no limits?
614 * > 0 => limit size (in entries)
615 */
616 if ( limit.lms_s_pr_total > 0 &&
617 limit.lms_s_pr > limit.lms_s_pr_total ) {
618 limit.lms_s_pr = limit.lms_s_pr_total;
619 }
620
621 rc = limits_add( be, flags, pattern, group_oc, group_ad, &limit );
622 if ( rc ) {
623
624 Debug( LDAP_DEBUG_ANY,
625 "%s : line %d: unable to add limit in "
626 "\"limits <pattern> <limits>\" line.\n",
627 fname, lineno );
628 }
629
630 return( rc );
631 }
632
633 int
limits_parse_one(const char * arg,struct slap_limits_set * limit)634 limits_parse_one(
635 const char *arg,
636 struct slap_limits_set *limit
637 )
638 {
639 assert( arg != NULL );
640 assert( limit != NULL );
641
642 if ( STRSTART( arg, "time" ) ) {
643 arg += STRLENOF( "time" );
644
645 if ( arg[0] == '.' ) {
646 arg++;
647 if ( STRSTART( arg, "soft=" ) ) {
648 arg += STRLENOF( "soft=" );
649 if ( strcasecmp( arg, "unlimited" ) == 0
650 || strcasecmp( arg, "none" ) == 0 )
651 {
652 limit->lms_t_soft = -1;
653
654 } else {
655 int soft;
656
657 if ( lutil_atoi( &soft, arg ) != 0 || soft < -1 ) {
658 return( 1 );
659 }
660
661 if ( soft == -1 ) {
662 /* FIXME: use "unlimited" instead; issue warning? */
663 }
664
665 limit->lms_t_soft = soft;
666 }
667
668 } else if ( STRSTART( arg, "hard=" ) ) {
669 arg += STRLENOF( "hard=" );
670 if ( strcasecmp( arg, "soft" ) == 0 ) {
671 limit->lms_t_hard = 0;
672
673 } else if ( strcasecmp( arg, "unlimited" ) == 0
674 || strcasecmp( arg, "none" ) == 0 )
675 {
676 limit->lms_t_hard = -1;
677
678 } else {
679 int hard;
680
681 if ( lutil_atoi( &hard, arg ) != 0 || hard < -1 ) {
682 return( 1 );
683 }
684
685 if ( hard == -1 ) {
686 /* FIXME: use "unlimited" instead */
687 }
688
689 if ( hard == 0 ) {
690 /* FIXME: use "soft" instead */
691 }
692
693 limit->lms_t_hard = hard;
694 }
695
696 } else {
697 return( 1 );
698 }
699
700 } else if ( arg[0] == '=' ) {
701 arg++;
702 if ( strcasecmp( arg, "unlimited" ) == 0
703 || strcasecmp( arg, "none" ) == 0 )
704 {
705 limit->lms_t_soft = -1;
706
707 } else {
708 if ( lutil_atoi( &limit->lms_t_soft, arg ) != 0
709 || limit->lms_t_soft < -1 )
710 {
711 return( 1 );
712 }
713 }
714 limit->lms_t_hard = 0;
715
716 } else {
717 return( 1 );
718 }
719
720 } else if ( STRSTART( arg, "size" ) ) {
721 arg += STRLENOF( "size" );
722
723 if ( arg[0] == '.' ) {
724 arg++;
725 if ( STRSTART( arg, "soft=" ) ) {
726 arg += STRLENOF( "soft=" );
727 if ( strcasecmp( arg, "unlimited" ) == 0
728 || strcasecmp( arg, "none" ) == 0 )
729 {
730 limit->lms_s_soft = -1;
731
732 } else {
733 int soft;
734
735 if ( lutil_atoi( &soft, arg ) != 0 || soft < -1 ) {
736 return( 1 );
737 }
738
739 if ( soft == -1 ) {
740 /* FIXME: use "unlimited" instead */
741 }
742
743 limit->lms_s_soft = soft;
744 }
745
746 } else if ( STRSTART( arg, "hard=" ) ) {
747 arg += STRLENOF( "hard=" );
748 if ( strcasecmp( arg, "soft" ) == 0 ) {
749 limit->lms_s_hard = 0;
750
751 } else if ( strcasecmp( arg, "unlimited" ) == 0
752 || strcasecmp( arg, "none" ) == 0 )
753 {
754 limit->lms_s_hard = -1;
755
756 } else {
757 int hard;
758
759 if ( lutil_atoi( &hard, arg ) != 0 || hard < -1 ) {
760 return( 1 );
761 }
762
763 if ( hard == -1 ) {
764 /* FIXME: use "unlimited" instead */
765 }
766
767 if ( hard == 0 ) {
768 /* FIXME: use "soft" instead */
769 }
770
771 limit->lms_s_hard = hard;
772 }
773
774 } else if ( STRSTART( arg, "unchecked=" ) ) {
775 arg += STRLENOF( "unchecked=" );
776 if ( strcasecmp( arg, "unlimited" ) == 0
777 || strcasecmp( arg, "none" ) == 0 )
778 {
779 limit->lms_s_unchecked = -1;
780
781 } else if ( strcasecmp( arg, "disabled" ) == 0 ) {
782 limit->lms_s_unchecked = 0;
783
784 } else {
785 int unchecked;
786
787 if ( lutil_atoi( &unchecked, arg ) != 0 || unchecked < -1 ) {
788 return( 1 );
789 }
790
791 if ( unchecked == -1 ) {
792 /* FIXME: use "unlimited" instead */
793 }
794
795 limit->lms_s_unchecked = unchecked;
796 }
797
798 } else if ( STRSTART( arg, "pr=" ) ) {
799 arg += STRLENOF( "pr=" );
800 if ( strcasecmp( arg, "noEstimate" ) == 0 ) {
801 limit->lms_s_pr_hide = 1;
802
803 } else if ( strcasecmp( arg, "unlimited" ) == 0
804 || strcasecmp( arg, "none" ) == 0 )
805 {
806 limit->lms_s_pr = -1;
807
808 } else {
809 int pr;
810
811 if ( lutil_atoi( &pr, arg ) != 0 || pr < -1 ) {
812 return( 1 );
813 }
814
815 if ( pr == -1 ) {
816 /* FIXME: use "unlimited" instead */
817 }
818
819 limit->lms_s_pr = pr;
820 }
821
822 } else if ( STRSTART( arg, "prtotal=" ) ) {
823 arg += STRLENOF( "prtotal=" );
824
825 if ( strcasecmp( arg, "unlimited" ) == 0
826 || strcasecmp( arg, "none" ) == 0 )
827 {
828 limit->lms_s_pr_total = -1;
829
830 } else if ( strcasecmp( arg, "disabled" ) == 0 ) {
831 limit->lms_s_pr_total = -2;
832
833 } else if ( strcasecmp( arg, "hard" ) == 0 ) {
834 limit->lms_s_pr_total = 0;
835
836 } else {
837 int total;
838
839 if ( lutil_atoi( &total, arg ) != 0 || total < -1 ) {
840 return( 1 );
841 }
842
843 if ( total == -1 ) {
844 /* FIXME: use "unlimited" instead */
845 }
846
847 if ( total == 0 ) {
848 /* FIXME: use "pr=disable" instead */
849 }
850
851 limit->lms_s_pr_total = total;
852 }
853
854 } else {
855 return( 1 );
856 }
857
858 } else if ( arg[0] == '=' ) {
859 arg++;
860 if ( strcasecmp( arg, "unlimited" ) == 0
861 || strcasecmp( arg, "none" ) == 0 )
862 {
863 limit->lms_s_soft = -1;
864
865 } else {
866 if ( lutil_atoi( &limit->lms_s_soft, arg ) != 0
867 || limit->lms_s_soft < -1 )
868 {
869 return( 1 );
870 }
871 }
872 limit->lms_s_hard = 0;
873
874 } else {
875 return( 1 );
876 }
877 }
878
879 return 0;
880 }
881
882 /* Helper macros for limits_unparse() and limits_unparse_one():
883 * Write to ptr, but not past bufEnd. Move ptr past the new text.
884 * Return (success && enough room ? 0 : -1).
885 */
886 #define ptr_APPEND_BV(bv) /* Append a \0-terminated berval */ \
887 (WHATSLEFT <= (bv).bv_len ? -1 : \
888 ((void) (ptr = lutil_strcopy( ptr, (bv).bv_val )), 0))
889 #define ptr_APPEND_LIT(str) /* Append a string literal */ \
890 (WHATSLEFT <= STRLENOF( "" str "" ) ? -1 : \
891 ((void) (ptr = lutil_strcopy( ptr, str )), 0))
892 #define ptr_APPEND_FMT(args) /* Append formatted text */ \
893 (WHATSLEFT <= (tmpLen = snprintf args) ? -1 : ((void) (ptr += tmpLen), 0))
894 #define ptr_APPEND_FMT1(fmt, arg) ptr_APPEND_FMT(( ptr, WHATSLEFT, fmt, arg ))
895 #define WHATSLEFT ((ber_len_t) (bufEnd - ptr))
896
897 /* Caller must provide an adequately sized buffer in bv */
898 int
limits_unparse(struct slap_limits * lim,struct berval * bv,ber_len_t buflen)899 limits_unparse( struct slap_limits *lim, struct berval *bv, ber_len_t buflen )
900 {
901 struct berval btmp;
902 char *ptr, *bufEnd; /* Updated/used by ptr_APPEND_*()/WHATSLEFT */
903 ber_len_t tmpLen; /* Used by ptr_APPEND_FMT*() */
904 unsigned type, style;
905 int rc = 0;
906
907 if ( !bv || !bv->bv_val ) return -1;
908
909 ptr = bv->bv_val;
910 bufEnd = ptr + buflen;
911 type = lim->lm_flags & SLAP_LIMITS_TYPE_MASK;
912
913 if ( type == SLAP_LIMITS_TYPE_GROUP ) {
914 rc = ptr_APPEND_FMT(( ptr, WHATSLEFT, "group/%s/%s=\"%s\"",
915 lim->lm_group_oc->soc_cname.bv_val,
916 lim->lm_group_ad->ad_cname.bv_val,
917 lim->lm_pat.bv_val ));
918 } else {
919 style = lim->lm_flags & SLAP_LIMITS_MASK;
920 switch( style ) {
921 case SLAP_LIMITS_ANONYMOUS:
922 case SLAP_LIMITS_USERS:
923 case SLAP_LIMITS_ANY:
924 rc = ptr_APPEND_BV( lmpats[style] );
925 break;
926 case SLAP_LIMITS_UNDEFINED:
927 case SLAP_LIMITS_EXACT:
928 case SLAP_LIMITS_ONE:
929 case SLAP_LIMITS_SUBTREE:
930 case SLAP_LIMITS_CHILDREN:
931 case SLAP_LIMITS_REGEX:
932 rc = ptr_APPEND_FMT(( ptr, WHATSLEFT, "dn.%s%s=\"%s\"",
933 type == SLAP_LIMITS_TYPE_SELF ? "" : "this.",
934 lmpats[style].bv_val, lim->lm_pat.bv_val ));
935 break;
936 }
937 }
938 if ( rc == 0 ) {
939 bv->bv_len = ptr - bv->bv_val;
940 btmp.bv_val = ptr;
941 btmp.bv_len = 0;
942 rc = limits_unparse_one( &lim->lm_limits,
943 SLAP_LIMIT_SIZE | SLAP_LIMIT_TIME,
944 &btmp, WHATSLEFT );
945 if ( rc == 0 )
946 bv->bv_len += btmp.bv_len;
947 }
948 return rc;
949 }
950
951 /* Caller must provide an adequately sized buffer in bv */
952 int
limits_unparse_one(struct slap_limits_set * lim,int which,struct berval * bv,ber_len_t buflen)953 limits_unparse_one(
954 struct slap_limits_set *lim,
955 int which,
956 struct berval *bv,
957 ber_len_t buflen )
958 {
959 char *ptr, *bufEnd; /* Updated/used by ptr_APPEND_*()/WHATSLEFT */
960 ber_len_t tmpLen; /* Used by ptr_APPEND_FMT*() */
961
962 if ( !bv || !bv->bv_val ) return -1;
963
964 ptr = bv->bv_val;
965 bufEnd = ptr + buflen;
966
967 if ( which & SLAP_LIMIT_SIZE ) {
968 if ( lim->lms_s_soft != SLAPD_DEFAULT_SIZELIMIT ) {
969
970 /* If same as global limit, drop it */
971 if ( lim != &frontendDB->be_def_limit &&
972 lim->lms_s_soft == frontendDB->be_def_limit.lms_s_soft )
973 {
974 goto s_hard;
975 /* If there's also a hard limit, fully qualify this one */
976 } else if ( lim->lms_s_hard ) {
977 if ( ptr_APPEND_LIT( " size.soft=" ) ) return -1;
978
979 /* If doing both size & time, qualify this */
980 } else if ( which & SLAP_LIMIT_TIME ) {
981 if ( ptr_APPEND_LIT( " size=" ) ) return -1;
982 }
983
984 if ( lim->lms_s_soft == -1
985 ? ptr_APPEND_LIT( "unlimited " )
986 : ptr_APPEND_FMT1( "%d ", lim->lms_s_soft ) )
987 return -1;
988 }
989 s_hard:
990 if ( lim->lms_s_hard ) {
991 if ( ptr_APPEND_LIT( " size.hard=" ) ) return -1;
992 if ( lim->lms_s_hard == -1
993 ? ptr_APPEND_LIT( "unlimited " )
994 : ptr_APPEND_FMT1( "%d ", lim->lms_s_hard ) )
995 return -1;
996 }
997 if ( lim->lms_s_unchecked != -1 ) {
998 if ( ptr_APPEND_LIT( " size.unchecked=" ) ) return -1;
999 if ( lim->lms_s_unchecked == 0
1000 ? ptr_APPEND_LIT( "disabled " )
1001 : ptr_APPEND_FMT1( "%d ", lim->lms_s_unchecked ) )
1002 return -1;
1003 }
1004 if ( lim->lms_s_pr_hide ) {
1005 if ( ptr_APPEND_LIT( " size.pr=noEstimate " ) ) return -1;
1006 }
1007 if ( lim->lms_s_pr ) {
1008 if ( ptr_APPEND_LIT( " size.pr=" ) ) return -1;
1009 if ( lim->lms_s_pr == -1
1010 ? ptr_APPEND_LIT( "unlimited " )
1011 : ptr_APPEND_FMT1( "%d ", lim->lms_s_pr ) )
1012 return -1;
1013 }
1014 if ( lim->lms_s_pr_total ) {
1015 if ( ptr_APPEND_LIT( " size.prtotal=" ) ) return -1;
1016 if ( lim->lms_s_pr_total == -1 ? ptr_APPEND_LIT( "unlimited " )
1017 : lim->lms_s_pr_total == -2 ? ptr_APPEND_LIT( "disabled " )
1018 : ptr_APPEND_FMT1( "%d ", lim->lms_s_pr_total ) )
1019 return -1;
1020 }
1021 }
1022
1023 if ( which & SLAP_LIMIT_TIME ) {
1024 if ( lim->lms_t_soft != SLAPD_DEFAULT_TIMELIMIT ) {
1025
1026 /* If same as global limit, drop it */
1027 if ( lim != &frontendDB->be_def_limit &&
1028 lim->lms_t_soft == frontendDB->be_def_limit.lms_t_soft )
1029 {
1030 goto t_hard;
1031
1032 /* If there's also a hard limit, fully qualify this one */
1033 } else if ( lim->lms_t_hard ) {
1034 if ( ptr_APPEND_LIT( " time.soft=" ) ) return -1;
1035
1036 /* If doing both size & time, qualify this */
1037 } else if ( which & SLAP_LIMIT_SIZE ) {
1038 if ( ptr_APPEND_LIT( " time=" ) ) return -1;
1039 }
1040
1041 if ( lim->lms_t_soft == -1
1042 ? ptr_APPEND_LIT( "unlimited " )
1043 : ptr_APPEND_FMT1( "%d ", lim->lms_t_soft ) )
1044 return -1;
1045 }
1046 t_hard:
1047 if ( lim->lms_t_hard ) {
1048 if ( ptr_APPEND_LIT( " time.hard=" ) ) return -1;
1049 if ( lim->lms_t_hard == -1
1050 ? ptr_APPEND_LIT( "unlimited " )
1051 : ptr_APPEND_FMT1( "%d ", lim->lms_t_hard ) )
1052 return -1;
1053 }
1054 }
1055 if ( ptr != bv->bv_val ) {
1056 ptr--;
1057 *ptr = '\0';
1058 bv->bv_len = ptr - bv->bv_val;
1059 }
1060
1061 return 0;
1062 }
1063
1064 int
limits_check(Operation * op,SlapReply * rs)1065 limits_check( Operation *op, SlapReply *rs )
1066 {
1067 assert( op != NULL );
1068 assert( rs != NULL );
1069 /* FIXME: should this be always true? */
1070 assert( op->o_tag == LDAP_REQ_SEARCH);
1071
1072 /* protocol only allows 0..maxInt;
1073 *
1074 * internal searches:
1075 * - may use SLAP_NO_LIMIT ( = -1 ) to indicate no limits;
1076 * - should use slimit = N and tlimit = SLAP_NO_LIMIT to
1077 * indicate searches that should return exactly N matches,
1078 * and handle errors thru a callback (see for instance
1079 * slap_sasl_match() and slap_sasl2dn())
1080 */
1081 if ( op->ors_tlimit == SLAP_NO_LIMIT && op->ors_slimit == SLAP_NO_LIMIT ) {
1082 return 0;
1083 }
1084
1085 /* allow root to set no limit */
1086 if ( be_isroot( op ) ) {
1087 op->ors_limit = NULL;
1088
1089 if ( op->ors_tlimit == 0 ) {
1090 op->ors_tlimit = SLAP_NO_LIMIT;
1091 }
1092
1093 if ( op->ors_slimit == 0 ) {
1094 op->ors_slimit = SLAP_NO_LIMIT;
1095 }
1096
1097 /* if paged results and slimit are requested */
1098 if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED &&
1099 op->ors_slimit != SLAP_NO_LIMIT ) {
1100 PagedResultsState *ps = op->o_pagedresults_state;
1101 int total = op->ors_slimit - ps->ps_count;
1102 if ( total > 0 ) {
1103 op->ors_slimit = total;
1104 } else {
1105 op->ors_slimit = 0;
1106 }
1107 }
1108
1109 /* if not root, get appropriate limits */
1110 } else {
1111 ( void ) limits_get( op, &op->ors_limit );
1112
1113 assert( op->ors_limit != NULL );
1114
1115 /* if no limit is required, use soft limit */
1116 if ( op->ors_tlimit == 0 ) {
1117 op->ors_tlimit = op->ors_limit->lms_t_soft;
1118
1119 /* limit required: check if legal */
1120 } else {
1121 if ( op->ors_limit->lms_t_hard == 0 ) {
1122 if ( op->ors_limit->lms_t_soft > 0
1123 && ( op->ors_tlimit > op->ors_limit->lms_t_soft ) ) {
1124 op->ors_tlimit = op->ors_limit->lms_t_soft;
1125 }
1126
1127 } else if ( op->ors_limit->lms_t_hard > 0 ) {
1128 #ifdef ABOVE_HARD_LIMIT_IS_ERROR
1129 if ( op->ors_tlimit == SLAP_MAX_LIMIT ) {
1130 op->ors_tlimit = op->ors_limit->lms_t_hard;
1131
1132 } else if ( op->ors_tlimit > op->ors_limit->lms_t_hard ) {
1133 /* error if exceeding hard limit */
1134 rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED;
1135 send_ldap_result( op, rs );
1136 rs->sr_err = LDAP_SUCCESS;
1137 return -1;
1138 }
1139 #else /* ! ABOVE_HARD_LIMIT_IS_ERROR */
1140 if ( op->ors_tlimit > op->ors_limit->lms_t_hard ) {
1141 op->ors_tlimit = op->ors_limit->lms_t_hard;
1142 }
1143 #endif /* ! ABOVE_HARD_LIMIT_IS_ERROR */
1144 }
1145 }
1146
1147 /* else leave as is */
1148
1149 /* don't even get to backend if candidate check is disabled */
1150 if ( op->ors_limit->lms_s_unchecked == 0 ) {
1151 rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED;
1152 send_ldap_result( op, rs );
1153 rs->sr_err = LDAP_SUCCESS;
1154 return -1;
1155 }
1156
1157 /* if paged results is requested */
1158 if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED ) {
1159 int slimit = -2;
1160 int pr_total;
1161 PagedResultsState *ps = op->o_pagedresults_state;
1162
1163 /* paged results is not allowed */
1164 if ( op->ors_limit->lms_s_pr_total == -2 ) {
1165 rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED;
1166 rs->sr_text = "pagedResults control not allowed";
1167 send_ldap_result( op, rs );
1168 rs->sr_err = LDAP_SUCCESS;
1169 rs->sr_text = NULL;
1170 return -1;
1171 }
1172
1173 if ( op->ors_limit->lms_s_pr > 0
1174 && ps->ps_size > op->ors_limit->lms_s_pr )
1175 {
1176 rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED;
1177 rs->sr_text = "illegal pagedResults page size";
1178 send_ldap_result( op, rs );
1179 rs->sr_err = LDAP_SUCCESS;
1180 rs->sr_text = NULL;
1181 return -1;
1182 }
1183
1184 if ( op->ors_limit->lms_s_pr_total == 0 ) {
1185 if ( op->ors_limit->lms_s_hard == 0 ) {
1186 pr_total = op->ors_limit->lms_s_soft;
1187 } else {
1188 pr_total = op->ors_limit->lms_s_hard;
1189 }
1190 } else {
1191 pr_total = op->ors_limit->lms_s_pr_total;
1192 }
1193
1194 if ( pr_total == -1 ) {
1195 if ( op->ors_slimit == 0 || op->ors_slimit == SLAP_MAX_LIMIT ) {
1196 slimit = -1;
1197
1198 } else {
1199 slimit = op->ors_slimit - ps->ps_count;
1200 }
1201
1202 #ifdef ABOVE_HARD_LIMIT_IS_ERROR
1203 } else if ( pr_total > 0 && op->ors_slimit != SLAP_MAX_LIMIT
1204 && ( op->ors_slimit == SLAP_NO_LIMIT
1205 || op->ors_slimit > pr_total ) )
1206 {
1207 rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED;
1208 send_ldap_result( op, rs );
1209 rs->sr_err = LDAP_SUCCESS;
1210 return -1;
1211 #endif /* ! ABOVE_HARD_LIMIT_IS_ERROR */
1212
1213 } else {
1214 /* if no limit is required, use soft limit */
1215 int total;
1216 int slimit2;
1217
1218 /* first round of pagedResults:
1219 * set count to any appropriate limit */
1220
1221 /* if the limit is set, check that it does
1222 * not violate any server-side limit */
1223 #ifdef ABOVE_HARD_LIMIT_IS_ERROR
1224 if ( op->ors_slimit == SLAP_MAX_LIMIT )
1225 #else /* ! ABOVE_HARD_LIMIT_IS_ERROR */
1226 if ( op->ors_slimit == SLAP_MAX_LIMIT
1227 || op->ors_slimit > pr_total )
1228 #endif /* ! ABOVE_HARD_LIMIT_IS_ERROR */
1229 {
1230 slimit2 = op->ors_slimit = pr_total;
1231
1232 } else if ( op->ors_slimit == 0 ) {
1233 slimit2 = pr_total;
1234
1235 } else {
1236 slimit2 = op->ors_slimit;
1237 }
1238
1239 total = slimit2 - ps->ps_count;
1240
1241 if ( total >= 0 ) {
1242 if ( op->ors_limit->lms_s_pr > 0 ) {
1243 /* use the smallest limit set by total/per page */
1244 if ( total < op->ors_limit->lms_s_pr ) {
1245 slimit = total;
1246
1247 } else {
1248 /* use the perpage limit if any
1249 * NOTE: + 1 because given value must be legal */
1250 slimit = op->ors_limit->lms_s_pr + 1;
1251 }
1252
1253 } else {
1254 /* use the total limit if any */
1255 slimit = total;
1256 }
1257
1258 } else if ( op->ors_limit->lms_s_pr > 0 ) {
1259 /* use the perpage limit if any
1260 * NOTE: + 1 because the given value must be legal */
1261 slimit = op->ors_limit->lms_s_pr + 1;
1262
1263 } else {
1264 /* use the standard hard/soft limit if any */
1265 slimit = op->ors_limit->lms_s_hard;
1266 }
1267 }
1268
1269 /* if got any limit, use it */
1270 if ( slimit != -2 ) {
1271 if ( op->ors_slimit == 0 ) {
1272 op->ors_slimit = slimit;
1273
1274 } else if ( slimit > 0 ) {
1275 if ( op->ors_slimit - ps->ps_count > slimit ) {
1276 rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED;
1277 send_ldap_result( op, rs );
1278 rs->sr_err = LDAP_SUCCESS;
1279 return -1;
1280 }
1281 op->ors_slimit = slimit;
1282
1283 } else if ( slimit == 0 ) {
1284 op->ors_slimit = 0;
1285 }
1286
1287 } else {
1288 /* use the standard hard/soft limit if any */
1289 op->ors_slimit = pr_total;
1290 }
1291
1292 /* no limit requested: use soft, whatever it is */
1293 } else if ( op->ors_slimit == 0 ) {
1294 op->ors_slimit = op->ors_limit->lms_s_soft;
1295
1296 /* limit requested: check if legal */
1297 } else {
1298 /* hard limit as soft (traditional behavior) */
1299 if ( op->ors_limit->lms_s_hard == 0 ) {
1300 if ( op->ors_limit->lms_s_soft > 0
1301 && op->ors_slimit > op->ors_limit->lms_s_soft ) {
1302 op->ors_slimit = op->ors_limit->lms_s_soft;
1303 }
1304
1305 /* explicit hard limit: error if violated */
1306 } else if ( op->ors_limit->lms_s_hard > 0 ) {
1307 #ifdef ABOVE_HARD_LIMIT_IS_ERROR
1308 if ( op->ors_slimit == SLAP_MAX_LIMIT ) {
1309 op->ors_slimit = op->ors_limit->lms_s_hard;
1310
1311 } else if ( op->ors_slimit > op->ors_limit->lms_s_hard ) {
1312 /* if limit exceeds hard, error */
1313 rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED;
1314 send_ldap_result( op, rs );
1315 rs->sr_err = LDAP_SUCCESS;
1316 return -1;
1317 }
1318 #else /* ! ABOVE_HARD_LIMIT_IS_ERROR */
1319 if ( op->ors_slimit > op->ors_limit->lms_s_hard ) {
1320 op->ors_slimit = op->ors_limit->lms_s_hard;
1321 }
1322 #endif /* ! ABOVE_HARD_LIMIT_IS_ERROR */
1323 }
1324 }
1325
1326 /* else leave as is */
1327 }
1328
1329 return 0;
1330 }
1331
1332 void
limits_free_one(struct slap_limits * lm)1333 limits_free_one(
1334 struct slap_limits *lm )
1335 {
1336 if ( ( lm->lm_flags & SLAP_LIMITS_MASK ) == SLAP_LIMITS_REGEX )
1337 regfree( &lm->lm_regex );
1338
1339 if ( !BER_BVISNULL( &lm->lm_pat ) )
1340 ch_free( lm->lm_pat.bv_val );
1341
1342 ch_free( lm );
1343 }
1344
1345 void
limits_destroy(struct slap_limits ** lm)1346 limits_destroy(
1347 struct slap_limits **lm )
1348 {
1349 int i;
1350
1351 if ( lm == NULL ) {
1352 return;
1353 }
1354
1355 for ( i = 0; lm[ i ]; i++ ) {
1356 limits_free_one( lm[ i ] );
1357 }
1358
1359 ch_free( lm );
1360 }
1361