xref: /netbsd-src/external/bsd/openldap/dist/servers/slapd/overlays/collect.c (revision 549b59ed3ccf0d36d3097190a0db27b770f3a839)
1 /*	$NetBSD: collect.c,v 1.3 2021/08/14 16:15:02 christos Exp $	*/
2 
3 /* collect.c - Demonstration of overlay code */
4 /* $OpenLDAP$ */
5 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
6  *
7  * Copyright 2003-2021 The OpenLDAP Foundation.
8  * Portions Copyright 2003 Howard Chu.
9  * All rights reserved.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted only as authorized by the OpenLDAP
13  * Public License.
14  *
15  * A copy of this license is available in the file LICENSE in the
16  * top-level directory of the distribution or, alternatively, at
17  * <http://www.OpenLDAP.org/license.html>.
18  */
19 /* ACKNOWLEDGEMENTS:
20  * This work was initially developed by the Howard Chu for inclusion
21  * in OpenLDAP Software.
22  */
23 
24 #include <sys/cdefs.h>
25 __RCSID("$NetBSD: collect.c,v 1.3 2021/08/14 16:15:02 christos Exp $");
26 
27 #include "portable.h"
28 
29 #ifdef SLAPD_OVER_COLLECT
30 
31 #include <stdio.h>
32 
33 #include <ac/string.h>
34 #include <ac/socket.h>
35 
36 #include "slap.h"
37 #include "slap-config.h"
38 
39 #include "lutil.h"
40 
41 /* This is a cheap hack to implement a collective attribute.
42  *
43  * This demonstration overlay looks for a specified attribute in an
44  * ancestor of a given entry and adds that attribute to the given
45  * entry when it is returned in a search response. It takes no effect
46  * for any other operations. If the ancestor does not exist, there
47  * is no effect. If no attribute was configured, there is no effect.
48  */
49 
50 typedef struct collect_info {
51 	struct collect_info *ci_next;
52 	struct berval ci_dn;
53 	int ci_ad_num;
54 	AttributeDescription *ci_ad[1];
55 } collect_info;
56 
57 static int collect_cf( ConfigArgs *c );
58 
59 static ConfigTable collectcfg[] = {
60 	{ "collectinfo", "dn> <attribute", 3, 3, 0,
61 	  ARG_MAGIC, collect_cf,
62 	  "( OLcfgOvAt:19.1 NAME 'olcCollectInfo' "
63 	  "DESC 'DN of entry and attribute to distribute' "
64 	  "EQUALITY caseIgnoreMatch "
65 	  "SYNTAX OMsDirectoryString )", NULL, NULL },
66 	{ NULL, NULL, 0, 0, 0, ARG_IGNORED }
67 };
68 
69 static ConfigOCs collectocs[] = {
70 	{ "( OLcfgOvOc:19.1 "
71 	  "NAME 'olcCollectConfig' "
72 	  "DESC 'Collective Attribute configuration' "
73 	  "SUP olcOverlayConfig "
74 	  "MAY olcCollectInfo )",
75 	  Cft_Overlay, collectcfg },
76 	{ NULL, 0, NULL }
77 };
78 
79 /*
80  * inserts a collect_info into on->on_bi.bi_private taking into account
81  * order. this means longer dn's (i.e. more specific dn's) will be found
82  * first when searching, allowing some limited overlap of dn's
83  */
84 static void
insert_ordered(slap_overinst * on,collect_info * ci)85 insert_ordered( slap_overinst *on, collect_info *ci ) {
86 	collect_info *find = on->on_bi.bi_private;
87 	collect_info *prev = NULL;
88 	int found = 0;
89 
90 	while (!found) {
91 		if (find == NULL) {
92 			if (prev == NULL) {
93 				/* base case - empty list */
94 				on->on_bi.bi_private = ci;
95 				ci->ci_next = NULL;
96 			} else {
97 				/* final case - end of list */
98 				prev->ci_next = ci;
99 				ci->ci_next = NULL;
100 			}
101 			found = 1;
102 		} else if (find->ci_dn.bv_len < ci->ci_dn.bv_len) {
103 			/* insert into list here */
104 			if (prev == NULL) {
105 				/* entry is head of list */
106 				ci->ci_next = on->on_bi.bi_private;
107 				on->on_bi.bi_private = ci;
108 			} else {
109 				/* entry is not head of list */
110 				prev->ci_next = ci;
111 				ci->ci_next = find;
112 			}
113 			found = 1;
114 		} else {
115 			/* keep looking */
116 			prev = find;
117 			find = find->ci_next;
118 		}
119 	}
120 }
121 
122 static int
collect_cf(ConfigArgs * c)123 collect_cf( ConfigArgs *c )
124 {
125 	slap_overinst *on = (slap_overinst *)c->bi;
126 	int rc = 1, idx;
127 
128 	switch( c->op ) {
129 	case SLAP_CONFIG_EMIT:
130 		{
131 		collect_info *ci;
132 		for ( ci = on->on_bi.bi_private; ci; ci = ci->ci_next ) {
133 			struct berval bv;
134 			char *ptr;
135 			int len;
136 
137 			/* calculate the length & malloc memory */
138 			bv.bv_len = ci->ci_dn.bv_len + STRLENOF("\"\" ");
139 			for (idx=0; idx<ci->ci_ad_num; idx++) {
140 				bv.bv_len += ci->ci_ad[idx]->ad_cname.bv_len;
141 				if (idx<(ci->ci_ad_num-1)) {
142 					bv.bv_len++;
143 				}
144 			}
145 			bv.bv_val = ch_malloc( bv.bv_len + 1 );
146 
147 			/* copy the value and update len */
148 			len = snprintf( bv.bv_val, bv.bv_len + 1, "\"%s\" ",
149 				ci->ci_dn.bv_val);
150 			ptr = bv.bv_val + len;
151 			for (idx=0; idx<ci->ci_ad_num; idx++) {
152 				ptr = lutil_strncopy( ptr,
153 					ci->ci_ad[idx]->ad_cname.bv_val,
154 					ci->ci_ad[idx]->ad_cname.bv_len);
155 				if (idx<(ci->ci_ad_num-1)) {
156 					*ptr++ = ',';
157 				}
158 			}
159 			*ptr = '\0';
160 			bv.bv_len = ptr - bv.bv_val;
161 
162 			ber_bvarray_add( &c->rvalue_vals, &bv );
163 			rc = 0;
164 		}
165 		}
166 		break;
167 	case LDAP_MOD_DELETE:
168 		if ( c->valx == -1 ) {
169 		/* Delete entire attribute */
170 			collect_info *ci;
171 			while (( ci = on->on_bi.bi_private )) {
172 				on->on_bi.bi_private = ci->ci_next;
173 				ch_free( ci->ci_dn.bv_val );
174 				ch_free( ci );
175 			}
176 		} else {
177 		/* Delete just one value */
178 			collect_info **cip, *ci;
179 			int i;
180 			cip = (collect_info **)&on->on_bi.bi_private;
181 			ci = *cip;
182 			for ( i=0; i < c->valx; i++ ) {
183 				cip = &ci->ci_next;
184 				ci = *cip;
185 			}
186 			*cip = ci->ci_next;
187 			ch_free( ci->ci_dn.bv_val );
188 			ch_free( ci );
189 		}
190 		rc = 0;
191 		break;
192 	case SLAP_CONFIG_ADD:
193 	case LDAP_MOD_ADD:
194 		{
195 		collect_info *ci;
196 		struct berval bv, dn;
197 		const char *text;
198 		int idx, count=0;
199 		char *arg;
200 
201 		/* count delimiters in attribute argument */
202 		arg = strtok(c->argv[2], ",");
203 		while (arg!=NULL) {
204 			count++;
205 			arg = strtok(NULL, ",");
206 		}
207 
208 		/* validate and normalize dn */
209 		ber_str2bv( c->argv[1], 0, 0, &bv );
210 		if ( dnNormalize( 0, NULL, NULL, &bv, &dn, NULL ) ) {
211 			snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s invalid DN: \"%s\"",
212 				c->argv[0], c->argv[1] );
213 			Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
214 				"%s: %s\n", c->log, c->cr_msg );
215 			return ARG_BAD_CONF;
216 		}
217 
218 		/* check for duplicate DNs */
219 		for ( ci = (collect_info *)on->on_bi.bi_private; ci;
220 			ci = ci->ci_next ) {
221 			/* If new DN is longest, there are no possible matches */
222 			if ( dn.bv_len > ci->ci_dn.bv_len ) {
223 				ci = NULL;
224 				break;
225 			}
226 			if ( bvmatch( &dn, &ci->ci_dn )) {
227 				break;
228 			}
229 		}
230 		if ( ci ) {
231 			snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s DN already configured: \"%s\"",
232 				c->argv[0], c->argv[1] );
233 			Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
234 				"%s: %s\n", c->log, c->cr_msg );
235 			return ARG_BAD_CONF;
236 		}
237 
238 		/* allocate config info with room for attribute array */
239 		ci = ch_malloc( sizeof( collect_info ) +
240 			sizeof( AttributeDescription * ) * count );
241 
242 		/* load attribute description for attribute list */
243 		arg = c->argv[2];
244 		for( idx=0; idx<count; idx++) {
245 			ci->ci_ad[idx] = NULL;
246 
247 			if ( slap_str2ad( arg, &ci->ci_ad[idx], &text ) ) {
248 				snprintf( c->cr_msg, sizeof( c->cr_msg ),
249 					"%s attribute description unknown: \"%s\"",
250 					c->argv[0], arg);
251 				Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
252 					"%s: %s\n", c->log, c->cr_msg );
253 				ch_free( ci );
254 				return ARG_BAD_CONF;
255 			}
256 			while(*arg!='\0') {
257 				arg++; /* skip to end of argument */
258 			}
259 			if (idx<count-1) {
260 				arg++; /* skip inner delimiters */
261 			}
262 		}
263 
264 		/* The on->on_bi.bi_private pointer can be used for
265 		 * anything this instance of the overlay needs.
266 		 */
267 		ci->ci_ad[count] = NULL;
268 		ci->ci_ad_num = count;
269 		ci->ci_dn = dn;
270 
271 		/* creates list of ci's ordered by dn length */
272 		insert_ordered ( on, ci );
273 
274 		/* New ci wasn't simply appended to end, adjust its
275 		 * position in the config entry's a_vals
276 		 */
277 		if ( c->ca_entry && ci->ci_next ) {
278 			Attribute *a = attr_find( c->ca_entry->e_attrs,
279 				collectcfg[0].ad );
280 			if ( a ) {
281 				struct berval bv, nbv;
282 				collect_info *c2 = (collect_info *)on->on_bi.bi_private;
283 				int i, j;
284 				for ( i=0; c2 != ci; i++, c2 = c2->ci_next );
285 				bv = a->a_vals[a->a_numvals-1];
286 				nbv = a->a_nvals[a->a_numvals-1];
287 				for ( j=a->a_numvals-1; j>i; j-- ) {
288 					a->a_vals[j] = a->a_vals[j-1];
289 					a->a_nvals[j] = a->a_nvals[j-1];
290 				}
291 				a->a_vals[j] = bv;
292 				a->a_nvals[j] = nbv;
293 			}
294 		}
295 
296 		rc = 0;
297 		}
298 	}
299 	return rc;
300 }
301 
302 static int
collect_destroy(BackendDB * be,ConfigReply * cr)303 collect_destroy(
304 	BackendDB *be,
305 	ConfigReply *cr
306 )
307 {
308 	slap_overinst *on = (slap_overinst *)be->bd_info;
309 	collect_info *ci;
310 
311 	while (( ci = on->on_bi.bi_private )) {
312 		on->on_bi.bi_private = ci->ci_next;
313 		ch_free( ci->ci_dn.bv_val );
314 		ch_free( ci );
315 	}
316 	return 0;
317 }
318 
319 static int
collect_modify(Operation * op,SlapReply * rs)320 collect_modify( Operation *op, SlapReply *rs)
321 {
322 	slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
323 	collect_info *ci = on->on_bi.bi_private;
324 	Modifications *ml;
325 	char errMsg[100];
326 	int idx;
327 
328 	for ( ml = op->orm_modlist; ml != NULL; ml = ml->sml_next) {
329 		for (; ci; ci=ci->ci_next ) {
330 			/* Is this entry an ancestor of this collectinfo ? */
331 			if (!dnIsSuffix(&op->o_req_ndn, &ci->ci_dn)) {
332 				/* this collectinfo does not match */
333 				continue;
334 			}
335 
336 			/* Is this entry the same as the template DN ? */
337 			if ( dn_match(&op->o_req_ndn, &ci->ci_dn)) {
338 				/* all changes in this ci are allowed */
339 				continue;
340 			}
341 
342 			/* check for collect attributes - disallow modify if present */
343 			for(idx=0; idx<ci->ci_ad_num; idx++) {
344 				if (ml->sml_desc == ci->ci_ad[idx]) {
345 					rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
346 					snprintf( errMsg, sizeof( errMsg ),
347 						"cannot change virtual attribute '%s'",
348 						ci->ci_ad[idx]->ad_cname.bv_val);
349 					rs->sr_text = errMsg;
350 					send_ldap_result( op, rs );
351 					return rs->sr_err;
352 				}
353 			}
354 		}
355 
356 	}
357 
358 	return SLAP_CB_CONTINUE;
359 }
360 
361 static int
collect_response(Operation * op,SlapReply * rs)362 collect_response( Operation *op, SlapReply *rs )
363 {
364 	slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
365 	collect_info *ci = on->on_bi.bi_private;
366 
367 	/* If we've been configured and the current response is
368 	 * a search entry
369 	 */
370 	if ( ci && rs->sr_type == REP_SEARCH ) {
371 		int rc;
372 
373 		op->o_bd->bd_info = (BackendInfo *)on->on_info;
374 
375 		for (; ci; ci=ci->ci_next ) {
376 			int idx=0;
377 
378 			/* Is this entry an ancestor of this collectinfo ? */
379 			if (!dnIsSuffix(&rs->sr_entry->e_nname, &ci->ci_dn)) {
380 				/* collectinfo does not match */
381 				continue;
382 			}
383 
384 			/* Is this entry the same as the template DN ? */
385 			if ( dn_match(&rs->sr_entry->e_nname, &ci->ci_dn)) {
386 				/* dont apply change to parent */
387 				continue;
388 			}
389 
390 			/* The current entry may live in a cache, so
391 			* don't modify it directly. Make a copy and
392 			* work with that instead.
393 			*/
394 			rs_entry2modifiable( op, rs, on );
395 
396 			/* Loop for each attribute in this collectinfo */
397 			for(idx=0; idx<ci->ci_ad_num; idx++) {
398 				BerVarray vals = NULL;
399 
400 				/* Extract the values of the desired attribute from
401 			 	 * the ancestor entry */
402 				rc = backend_attribute( op, NULL, &ci->ci_dn,
403 					ci->ci_ad[idx], &vals, ACL_READ );
404 
405 				/* If there are any values, merge them into the
406 			 	 * current search result
407 			 	 */
408 				if ( vals ) {
409 					attr_merge_normalize( rs->sr_entry, ci->ci_ad[idx],
410 						vals, op->o_tmpmemctx );
411 					ber_bvarray_free_x( vals, op->o_tmpmemctx );
412 				}
413 			}
414 		}
415 	}
416 
417 	/* Default is to just fall through to the normal processing */
418 	return SLAP_CB_CONTINUE;
419 }
420 
421 static slap_overinst collect;
422 
collect_initialize()423 int collect_initialize() {
424 	int code;
425 
426 	collect.on_bi.bi_type = "collect";
427 	collect.on_bi.bi_flags = SLAPO_BFLAG_SINGLE;
428 	collect.on_bi.bi_db_destroy = collect_destroy;
429 	collect.on_bi.bi_op_modify = collect_modify;
430 	collect.on_response = collect_response;
431 
432 	collect.on_bi.bi_cf_ocs = collectocs;
433 	code = config_register_schema( collectcfg, collectocs );
434 	if ( code ) return code;
435 
436 	return overlay_register( &collect );
437 }
438 
439 #if SLAPD_OVER_COLLECT == SLAPD_MOD_DYNAMIC
init_module(int argc,char * argv[])440 int init_module(int argc, char *argv[]) {
441 	return collect_initialize();
442 }
443 #endif
444 
445 #endif /* SLAPD_OVER_COLLECT */
446