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