cachemgr_discovery.c revision 11262:b7ebfbf2359e
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. */
91struct 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
102extern 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 */
109static 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 */
139static 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*/
236static 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*/
299static 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
345cleanup:
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 */
357static 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
423cleanup:
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*/
435static 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 */
474static 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
532done:
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 */
542void 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