xref: /illumos-gate/usr/src/cmd/ldapcachemgr/cachemgr_discovery.c (revision 581cede61ac9c14d8d4ea452562a567189eead78)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright (c) 1999,2001 by Sun Microsystems, Inc.
24  * All rights reserved.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #ifdef SLP
30 
31 /*
32  * This file contains all the dynamic server discovery functionality
33  * for ldap_cachemgr. SLP is used to query the network for any changes
34  * in the set of deployed LDAP servers.
35  *
36  * The algorithm used is outlined here:
37  *
38  *   1. Find all naming contexts with SLPFindAttrs. (See
39  *      find_all_contexts())
40  *   2. For each context, find all servers which serve that context
41  *      with SLPFindSrvs. (See foreach_context())
42  *   3. For each server, retrieve that server's attributes with
43  *      SLPFindAttributes. (See foreach_server())
44  *   4. Aggregate the servers' attributes into a config object. There
45  *      is one config object associated with each context found in
46  *      step 1. (See aggregate_attrs())
47  *   5. Update the global config cache for each found context and its
48  *      associated servers and attributes. (See update_config())
49  *
50  * The entry point for ldap_cachemgr is discover(). The actual entry
51  * point into the discovery routine is find_all_contexts(); the
52  * code thereafter is actually not specific to LDAP, and could also
53  * be used to discover YP, NIS+, or any other server which conforms
54  * to the SLP Naming and Directory abstract service type.
55  *
56  * find_all_attributes() takes as parameters three callback routines
57  * which are used to report all information back to the caller. The
58  * signatures and synopses of these routines are:
59  *
60  * void *get_cfghandle(const char *domain);
61  *
62  *   Returns an opaque handle to a configuration object specific
63  *   to the 'domain' parameter. 'domain' will be a naming context
64  *   string, i.e. foo.bar.sun.com ( i.e. a secure-RPC domain-
65  *   name).
66  *
67  * void aggregate(void *handle, const char *tag, const char *value);
68  *
69  *   Adds this tag / value pair to the set of aggregated attributes
70  *   associated with the given handle.
71  *
72  * void set_cfghandle(void *handle);
73  *
74  *   Sets and destroys the config object; SLP will no longer attempt
75  *   to use this handle after this call. Thus, this call marks the
76  *   end of configuration information for this handle.
77  */
78 
79 #include <stdio.h>
80 #include <slp.h>
81 #include <stdlib.h>
82 #include <string.h>
83 #include <door.h>
84 #include <unistd.h>
85 #include "ns_sldap.h"
86 #include "ns_internal.h"
87 #include "cachemgr.h"
88 
89 #define	ABSTYPE		"service:naming-directory"
90 #define	CONTEXT_ATTR	"naming-context"
91 #define	LDAP_DOMAIN_ATTR "x-sun-rpcdomain"
92 
93 /* The configuration cookie passed along through all SLP callbacks. */
94 struct config_cookie {
95 	SLPHandle	h;		/* An open SLPHandle */
96 	const char	*type;		/* The full service type to use */
97 	char		*scopes;	/* A list of scopes to use */
98 	const char	*context_attr;	/* Which attr to use for the ctx */
99 	void		*cache_cfg;	/* caller-supplied config object */
100 	void *(*get_cfghandle)(const char *);
101 	void (*aggregate)(void *, const char *, const char *);
102 	void (*set_cfghandle)(void *);
103 };
104 
105 extern admin_t current_admin;	/* ldap_cachemgr's admin struct */
106 
107 /*
108  * Utility routine: getlocale():
109  * Returns the locale specified by the SLP locale property, or just
110  * returns the default SLP locale if the property was not set.
111  */
112 static const char *getlocale() {
113 	const char *locale = SLPGetProperty("net.slp.locale");
114 	return (locale ? locale : "en");
115 }
116 
117 /*
118  * Utility routine: next_attr():
119  * Parses an SLP attribute string. On the first call, *type
120  * must be set to 0, and *s_inout must point to the beginning
121  * of the attr string. The following results are possible:
122  *
123  *   If the term is of the form 'tag' only, *t_inout is set to tag,
124  *     and *v_inout is set to NULL.
125  *   If the term is of the form '(tag=val)', *t_inout and *v_inout
126  *     are set to the tag and val strings, respectively.
127  *   If the term is of the form '(tag=val1,val2,..,valN)', on each
128  *     successive call, next_attr will return the next value. On the
129  *     first invocation, tag is set to 'tag'; on successive invocations,
130  *     tag is set to *t_inout.
131  *
132  * The string passed in *s_inout is destructively modified; all values
133  * returned simply point into the initial string. Hence the caller
134  * is responsible for all memory management. The type parameter is
135  * for internal use only and should be set to 0 by the caller only
136  * on the first invocation.
137  *
138  * If more attrs are available, returns SLP_TRUE, otherwise returns
139  * SLP_FALSE. If SLP_FALSE is returned, all value-result parameters
140  * will be undefined, and should not be used.
141  */
142 static SLPBoolean next_attr(char **t_inout, char **v_inout,
143 			    char **s_inout, int *type) {
144 	char *end = NULL;
145 	char *tag = NULL;
146 	char *val = NULL;
147 	char *state = NULL;
148 
149 	if (!t_inout || !v_inout)
150 	    return (SLP_FALSE);
151 
152 	if (!s_inout || !*s_inout || !**s_inout)
153 	    return (SLP_FALSE);
154 
155 	state = *s_inout;
156 
157 	/* type: 0 = start, 1 = '(tag=val)' type, 2 = 'tag' type */
158 	switch (*type) {
159 	case 0:
160 	    switch (*state) {
161 	    case '(':
162 		*type = 1;
163 		break;
164 	    case ',':
165 		state++;
166 		*type = 0;
167 		break;
168 	    default:
169 		*type = 2;
170 	    }
171 	    *s_inout = state;
172 	    return (next_attr(t_inout, v_inout, s_inout, type));
173 	    break;
174 	case 1:
175 	    switch (*state) {
176 	    case '(':
177 		/* start of attr of the form (tag=val[,val]) */
178 		state++;
179 		tag = state;
180 		end = strchr(state, ')');	/* for sanity checking */
181 		if (!end)
182 		    return (SLP_FALSE);	/* fatal parse error */
183 
184 		state = strchr(tag, '=');
185 		if (state) {
186 		    if (state > end)
187 			return (SLP_FALSE);  /* fatal parse err */
188 		    *state++ = 0;
189 		} else {
190 		    return (SLP_FALSE);	/* fatal parse error */
191 		}
192 		/* fallthru to default case, which handles multivals */
193 	    default:
194 		/* somewhere in a multivalued attr */
195 		if (!end) {	/* did not fallthru from '(' case */
196 		    tag = *t_inout;	/* leave tag as it was */
197 		    end = strchr(state, ')');
198 		    if (!end)
199 			return (SLP_FALSE);	/* fatal parse error */
200 		}
201 
202 		val = state;
203 		state = strchr(val, ',');	/* is this attr multivalued? */
204 		if (!state || state > end) {
205 		    /* no, so skip to the next attr */
206 		    state = end;
207 		    *type = 0;
208 		}	/* else attr is multivalued */
209 		*state++ = 0;
210 		break;
211 	    }
212 	    break;
213 	case 2:
214 	    /* attr term with tag only */
215 	    tag = state;
216 	    state = strchr(tag, ',');
217 	    if (state) {
218 		*state++ = 0;
219 	    }
220 	    val = NULL;
221 	    *type = 0;
222 	    break;
223 	default:
224 	    return (SLP_FALSE);
225 	}
226 
227 	*t_inout = tag;
228 	*v_inout = val;
229 	*s_inout = state;
230 
231 	return (SLP_TRUE);
232 }
233 
234 /*
235  * The SLP callback routine for foreach_server(). Aggregates each
236  * server's attributes into the caller-specified config object.
237  */
238 /*ARGSUSED*/
239 static SLPBoolean aggregate_attrs(SLPHandle h, const char *attrs_in,
240 				    SLPError errin, void *cookie) {
241 	char *tag, *val, *state;
242 	char *unesc_tag, *unesc_val;
243 	int type = 0;
244 	char *attrs;
245 	SLPError err;
246 	struct config_cookie *cfg = (struct config_cookie *)cookie;
247 
248 	if (errin != SLP_OK) {
249 	    return (SLP_TRUE);
250 	}
251 
252 	attrs = strdup(attrs_in);
253 	state = attrs;
254 
255 	while (next_attr(&tag, &val, &state, &type)) {
256 	    unesc_tag = unesc_val = NULL;
257 
258 	    if (tag) {
259 		if ((err = SLPUnescape(tag, &unesc_tag, SLP_TRUE)) != SLP_OK) {
260 		    unesc_tag = NULL;
261 		    if (current_admin.debug_level >= DBG_ALL) {
262 			(void) logit("aggregate_attrs: ",
263 				"could not unescape attr tag %s:%s\n",
264 				tag, slp_strerror(err));
265 		    }
266 		}
267 	    }
268 	    if (val) {
269 		if ((err = SLPUnescape(val, &unesc_val, SLP_FALSE))
270 		    != SLP_OK) {
271 		    unesc_val = NULL;
272 		    if (current_admin.debug_level >= DBG_ALL) {
273 			(void) logit("aggregate_attrs: ",
274 				"could not unescape attr val %s:%s\n",
275 				val, slp_strerror(err));
276 		    }
277 		}
278 	    }
279 
280 	    if (current_admin.debug_level >= DBG_ALL) {
281 		(void) logit("discovery:\t\t%s=%s\n",
282 			(unesc_tag ? unesc_tag : "NULL"),
283 			(unesc_val ? unesc_val : "NULL"));
284 	    }
285 
286 	    cfg->aggregate(cfg->cache_cfg, unesc_tag, unesc_val);
287 
288 	    if (unesc_tag) free(unesc_tag);
289 	    if (unesc_val) free(unesc_val);
290 	}
291 
292 	if (attrs) free(attrs);
293 
294 	return (SLP_TRUE);
295 }
296 
297 /*
298  * The SLP callback routine for update_config(). For each
299  * server found, retrieves that server's attributes.
300  */
301 /*ARGSUSED*/
302 static SLPBoolean foreach_server(SLPHandle hin, const char *u,
303 				unsigned short life,
304 				SLPError errin, void *cookie) {
305 	SLPError err;
306 	struct config_cookie *cfg = (struct config_cookie *)cookie;
307 	SLPHandle h = cfg->h;	/* an open handle */
308 	SLPSrvURL *surl = NULL;
309 	char *url = NULL;
310 
311 	if (errin != SLP_OK) {
312 	    return (SLP_TRUE);
313 	}
314 
315 	/* dup url so we can slice 'n dice */
316 	if (!(url = strdup(u))) {
317 	    (void) logit("foreach_server: no memory");
318 	    return (SLP_FALSE);
319 	}
320 
321 	if ((err = SLPParseSrvURL(url, &surl)) != SLP_OK) {
322 	    free(url);
323 	    if (current_admin.debug_level >= DBG_NETLOOKUPS) {
324 		(void) logit("foreach_server: ",
325 				"dropping unparsable URL %s: %s\n",
326 				url, slp_strerror(err));
327 		return (SLP_TRUE);
328 	    }
329 	}
330 
331 	if (current_admin.debug_level >= DBG_ALL) {
332 	    (void) logit("discovery:\tserver: %s\n", surl->s_pcHost);
333 	}
334 
335 	/* retrieve all attrs for this server */
336 	err = SLPFindAttrs(h, u, cfg->scopes, "", aggregate_attrs, cookie);
337 	if (err != SLP_OK) {
338 	    if (current_admin.debug_level >= DBG_NETLOOKUPS) {
339 		(void) logit("foreach_server: FindAttrs failed: %s\n",
340 				slp_strerror(err));
341 	    }
342 	    goto cleanup;
343 	}
344 
345 	/* add this server and its attrs to the config object */
346 	cfg->aggregate(cfg->cache_cfg, "_,_xservers_,_", surl->s_pcHost);
347 
348 cleanup:
349 	if (url) free(url);
350 	if (surl) SLPFree(surl);
351 
352 	return (SLP_TRUE);
353 }
354 
355 /*
356  * This routine does the dirty work of finding all servers for a
357  * given domain and injecting this information into the caller's
358  * configuration namespace via callbacks.
359  */
360 static void update_config(const char *context, struct config_cookie *cookie) {
361 	SLPHandle h = NULL;
362 	SLPHandle persrv_h = NULL;
363 	SLPError err;
364 	char *search = NULL;
365 	char *unesc_domain = NULL;
366 
367 	/* Unescape the naming context string */
368 	if ((err = SLPUnescape(context, &unesc_domain, SLP_FALSE)) != SLP_OK) {
369 	    if (current_admin.debug_level >= DBG_ALL) {
370 		(void) logit("update_config: ",
371 				"dropping unparsable domain: %s: %s\n",
372 				context, slp_strerror(err));
373 	    }
374 	    return;
375 	}
376 
377 	cookie->cache_cfg = cookie->get_cfghandle(unesc_domain);
378 
379 	/* Open a handle which all attrs calls can use */
380 	if ((err = SLPOpen(getlocale(), SLP_FALSE, &persrv_h)) != SLP_OK) {
381 	    if (current_admin.debug_level >= DBG_NETLOOKUPS) {
382 		(void) logit("update_config: SLPOpen failed: %s\n",
383 				slp_strerror(err));
384 	    }
385 	    goto cleanup;
386 	}
387 
388 	cookie->h = persrv_h;
389 
390 	if (current_admin.debug_level >= DBG_ALL) {
391 	    (void) logit("discovery: found naming context %s\n", context);
392 	}
393 
394 	/* (re)construct the search filter form the input context */
395 	search = malloc(strlen(cookie->context_attr) +
396 			strlen(context) +
397 			strlen("(=)") + 1);
398 	if (!search) {
399 	    (void) logit("update_config: no memory\n");
400 	    goto cleanup;
401 	}
402 	(void) sprintf(search, "(%s=%s)", cookie->context_attr, context);
403 
404 	/* Find all servers which serve this context */
405 	if ((err = SLPOpen(getlocale(), SLP_FALSE, &h)) != SLP_OK) {
406 	    if (current_admin.debug_level >= DBG_NETLOOKUPS) {
407 		(void) logit("upate_config: SLPOpen failed: %s\n",
408 				slp_strerror(err));
409 	    }
410 	    goto cleanup;
411 	}
412 
413 	err = SLPFindSrvs(h, cookie->type, cookie->scopes,
414 				search, foreach_server, cookie);
415 	if (err != SLP_OK) {
416 	    if (current_admin.debug_level >= DBG_NETLOOKUPS) {
417 		(void) logit("update_config: SLPFindSrvs failed: %s\n",
418 				slp_strerror(err));
419 	    }
420 	    goto cleanup;
421 	}
422 
423 	/* update the config cache with the new info */
424 	cookie->set_cfghandle(cookie->cache_cfg);
425 
426 cleanup:
427 	if (h) SLPClose(h);
428 	if (persrv_h) SLPClose(persrv_h);
429 	if (search) free(search);
430 	if (unesc_domain) free(unesc_domain);
431 }
432 
433 /*
434  * The SLP callback routine for find_all_contexts(). For each context
435  * found, finds all the servers and their attributes.
436  */
437 /*ARGSUSED*/
438 static SLPBoolean foreach_context(SLPHandle h, const char *attrs_in,
439 				    SLPError err, void *cookie) {
440 	char *attrs, *tag, *val, *state;
441 	int type = 0;
442 
443 	if (err != SLP_OK) {
444 	    return (SLP_TRUE);
445 	}
446 
447 	/*
448 	 * Parse out each context. Attrs will be of the following form:
449 	 *   (naming-context=dc\3deng\2c dc\3dsun\2c dc\3dcom)
450 	 * Note that ',' and '=' are reserved in SLP, so they are escaped.
451 	 */
452 	attrs = strdup(attrs_in);	/* so we can slice'n'dice */
453 	if (!attrs) {
454 	    (void) logit("foreach_context: no memory\n");
455 	    return (SLP_FALSE);
456 	}
457 	state = attrs;
458 
459 	while (next_attr(&tag, &val, &state, &type)) {
460 	    update_config(val, cookie);
461 	}
462 
463 	free(attrs);
464 
465 	return (SLP_TRUE);
466 }
467 
468 /*
469  * Initiates server and attribute discovery for the concrete type
470  * 'type'. Currently the only useful type is "ldap", but perhaps
471  * "nis" and "nisplus" will also be useful in the future.
472  *
473  * get_cfghandle, aggregate, and set_cfghandle are callback routines
474  * used to pass any discovered configuration information back to the
475  * caller. See the introduction at the top of this file for more info.
476  */
477 static void find_all_contexts(const char *type,
478 				void *(*get_cfghandle)(const char *),
479 				void (*aggregate)(
480 					void *, const char *, const char *),
481 				void (*set_cfghandle)(void *)) {
482 	SLPHandle h = NULL;
483 	SLPError err;
484 	struct config_cookie cookie[1];
485 	char *fulltype = NULL;
486 	char *scope = (char *)SLPGetProperty("net.slp.useScopes");
487 
488 	if (!scope || !*scope) {
489 	    scope = "default";
490 	}
491 
492 	/* construct the full type from the partial type parameter */
493 	fulltype = malloc(strlen(ABSTYPE) + strlen(type) + 2);
494 	if (!fulltype) {
495 	    (void) logit("find_all_contexts: no memory");
496 	    goto done;
497 	}
498 	(void) sprintf(fulltype, "%s:%s", ABSTYPE, type);
499 
500 	/* set up the cookie for this discovery operation */
501 	memset(cookie, 0, sizeof (*cookie));
502 	cookie->type = fulltype;
503 	cookie->scopes = scope;
504 	if (strcasecmp(type, "ldap") == 0) {
505 		/* Sun LDAP is special */
506 	    cookie->context_attr = LDAP_DOMAIN_ATTR;
507 	} else {
508 	    cookie->context_attr = CONTEXT_ATTR;
509 	}
510 	cookie->get_cfghandle = get_cfghandle;
511 	cookie->aggregate = aggregate;
512 	cookie->set_cfghandle = set_cfghandle;
513 
514 	if ((err = SLPOpen(getlocale(), SLP_FALSE, &h)) != SLP_OK) {
515 	    if (current_admin.debug_level >= DBG_CANT_FIND) {
516 		(void) logit("discover: %s",
517 			    "Aborting discovery: SLPOpen failed: %s\n",
518 			    slp_strerror(err));
519 	    }
520 	    goto done;
521 	}
522 
523 	/* use find attrs to get a list of all available contexts */
524 	err = SLPFindAttrs(h, fulltype, scope, cookie->context_attr,
525 			    foreach_context, cookie);
526 	if (err != SLP_OK) {
527 	    if (current_admin.debug_level >= DBG_CANT_FIND) {
528 		(void) logit(
529 		"discover: Aborting discovery: SLPFindAttrs failed: %s\n",
530 			slp_strerror(err));
531 	    }
532 	    goto done;
533 	}
534 
535 done:
536 	if (h) SLPClose(h);
537 	if (fulltype) free(fulltype);
538 }
539 
540 /*
541  * This is the ldap_cachemgr entry point into SLP dynamic discovery. The
542  * parameter 'r' should be a pointer to an unsigned int containing
543  * the requested interval at which the network should be queried.
544  */
545 void discover(void *r) {
546 	unsigned short reqrefresh = *((unsigned int *)r);
547 
548 	for (;;) {
549 	    find_all_contexts("ldap",
550 				__cache_get_cfghandle,
551 				__cache_aggregate_params,
552 				__cache_set_cfghandle);
553 
554 	    if (current_admin.debug_level >= DBG_ALL) {
555 		(void) logit(
556 			"dynamic discovery: using refresh interval %d\n",
557 			reqrefresh);
558 	    }
559 
560 	    (void) sleep(reqrefresh);
561 	}
562 }
563 
564 #endif /* SLP */
565