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