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.
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.
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]
22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
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.
33 * The algorithm used is outlined here:
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())
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.
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:
57 * void *get_cfghandle(const char *domain);
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-
64 * void aggregate(void *handle, const char *tag, const char *value);
66 * Adds this tag / value pair to the set of aggregated attributes
67 * associated with the given handle.
69 * void set_cfghandle(void *handle);
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.
83 #include "ns_internal.h"
86 #define ABSTYPE "service:naming-directory"
87 #define CONTEXT_ATTR "naming-context"
88 #define LDAP_DOMAIN_ATTR "x-sun-rpcdomain"
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 *);
102 extern admin_t current_admin
; /* ldap_cachemgr's admin struct */
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.
109 static const char *getlocale() {
110 const char *locale
= SLPGetProperty("net.slp.locale");
111 return (locale
? locale
: "en");
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:
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.
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.
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.
139 static SLPBoolean
next_attr(char **t_inout
, char **v_inout
,
140 char **s_inout
, int *type
) {
146 if (!t_inout
|| !v_inout
)
149 if (!s_inout
|| !*s_inout
|| !**s_inout
)
154 /* type: 0 = start, 1 = '(tag=val)' type, 2 = 'tag' type */
169 return (next_attr(t_inout
, v_inout
, s_inout
, type
));
174 /* start of attr of the form (tag=val[,val]) */
177 end
= strchr(state
, ')'); /* for sanity checking */
179 return (SLP_FALSE
); /* fatal parse error */
181 state
= strchr(tag
, '=');
184 return (SLP_FALSE
); /* fatal parse err */
187 return (SLP_FALSE
); /* fatal parse error */
189 /* fallthru to default case, which handles multivals */
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
, ')');
196 return (SLP_FALSE
); /* fatal parse error */
200 state
= strchr(val
, ','); /* is this attr multivalued? */
201 if (!state
|| state
> end
) {
202 /* no, so skip to the next attr */
205 } /* else attr is multivalued */
211 /* attr term with tag only */
213 state
= strchr(tag
, ',');
232 * The SLP callback routine for foreach_server(). Aggregates each
233 * server's attributes into the caller-specified config object.
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
;
243 struct config_cookie
*cfg
= (struct config_cookie
*)cookie
;
245 if (errin
!= SLP_OK
) {
249 attrs
= strdup(attrs_in
);
252 while (next_attr(&tag
, &val
, &state
, &type
)) {
253 unesc_tag
= unesc_val
= NULL
;
256 if ((err
= SLPUnescape(tag
, &unesc_tag
, SLP_TRUE
)) != SLP_OK
) {
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
));
266 if ((err
= SLPUnescape(val
, &unesc_val
, SLP_FALSE
))
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
));
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"));
283 cfg
->aggregate(cfg
->cache_cfg
, unesc_tag
, unesc_val
);
285 if (unesc_tag
) free(unesc_tag
);
286 if (unesc_val
) free(unesc_val
);
289 if (attrs
) free(attrs
);
295 * The SLP callback routine for update_config(). For each
296 * server found, retrieves that server's attributes.
299 static SLPBoolean
foreach_server(SLPHandle hin
, const char *u
,
301 SLPError errin
, void *cookie
) {
303 struct config_cookie
*cfg
= (struct config_cookie
*)cookie
;
304 SLPHandle h
= cfg
->h
; /* an open handle */
305 SLPSrvURL
*surl
= NULL
;
308 if (errin
!= SLP_OK
) {
312 /* dup url so we can slice 'n dice */
313 if (!(url
= strdup(u
))) {
314 (void) logit("foreach_server: no memory");
318 if ((err
= SLPParseSrvURL(url
, &surl
)) != SLP_OK
) {
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
));
328 if (current_admin
.debug_level
>= DBG_ALL
) {
329 (void) logit("discovery:\tserver: %s\n", surl
->s_pcHost
);
332 /* retrieve all attrs for this server */
333 err
= SLPFindAttrs(h
, u
, cfg
->scopes
, "", aggregate_attrs
, cookie
);
335 if (current_admin
.debug_level
>= DBG_NETLOOKUPS
) {
336 (void) logit("foreach_server: FindAttrs failed: %s\n",
342 /* add this server and its attrs to the config object */
343 cfg
->aggregate(cfg
->cache_cfg
, "_,_xservers_,_", surl
->s_pcHost
);
347 if (surl
) SLPFree(surl
);
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.
357 static void update_config(const char *context
, struct config_cookie
*cookie
) {
359 SLPHandle persrv_h
= NULL
;
362 char *unesc_domain
= NULL
;
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
));
374 cookie
->cache_cfg
= cookie
->get_cfghandle(unesc_domain
);
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",
385 cookie
->h
= persrv_h
;
387 if (current_admin
.debug_level
>= DBG_ALL
) {
388 (void) logit("discovery: found naming context %s\n", context
);
391 /* (re)construct the search filter form the input context */
392 search
= malloc(strlen(cookie
->context_attr
) +
396 (void) logit("update_config: no memory\n");
399 (void) sprintf(search
, "(%s=%s)", cookie
->context_attr
, context
);
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",
410 err
= SLPFindSrvs(h
, cookie
->type
, cookie
->scopes
,
411 search
, foreach_server
, cookie
);
413 if (current_admin
.debug_level
>= DBG_NETLOOKUPS
) {
414 (void) logit("update_config: SLPFindSrvs failed: %s\n",
420 /* update the config cache with the new info */
421 cookie
->set_cfghandle(cookie
->cache_cfg
);
425 if (persrv_h
) SLPClose(persrv_h
);
426 if (search
) free(search
);
427 if (unesc_domain
) free(unesc_domain
);
431 * The SLP callback routine for find_all_contexts(). For each context
432 * found, finds all the servers and their attributes.
435 static SLPBoolean
foreach_context(SLPHandle h
, const char *attrs_in
,
436 SLPError err
, void *cookie
) {
437 char *attrs
, *tag
, *val
, *state
;
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.
449 attrs
= strdup(attrs_in
); /* so we can slice'n'dice */
451 (void) logit("foreach_context: no memory\n");
456 while (next_attr(&tag
, &val
, &state
, &type
)) {
457 update_config(val
, cookie
);
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.
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.
474 static void find_all_contexts(const char *type
,
475 void *(*get_cfghandle
)(const char *),
477 void *, const char *, const char *),
478 void (*set_cfghandle
)(void *)) {
481 struct config_cookie cookie
[1];
482 char *fulltype
= NULL
;
483 char *scope
= (char *)SLPGetProperty("net.slp.useScopes");
485 if (!scope
|| !*scope
) {
489 /* construct the full type from the partial type parameter */
490 fulltype
= malloc(strlen(ABSTYPE
) + strlen(type
) + 2);
492 (void) logit("find_all_contexts: no memory");
495 (void) sprintf(fulltype
, "%s:%s", ABSTYPE
, type
);
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
;
505 cookie
->context_attr
= CONTEXT_ATTR
;
507 cookie
->get_cfghandle
= get_cfghandle
;
508 cookie
->aggregate
= aggregate
;
509 cookie
->set_cfghandle
= set_cfghandle
;
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",
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
);
524 if (current_admin
.debug_level
>= DBG_CANT_FIND
) {
526 "discover: Aborting discovery: SLPFindAttrs failed: %s\n",
534 if (fulltype
) free(fulltype
);
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.
542 void discover(void *r
) {
543 unsigned short reqrefresh
= *((unsigned int *)r
);
546 find_all_contexts("ldap",
547 __cache_get_cfghandle
,
548 __cache_aggregate_params
,
549 __cache_set_cfghandle
);
551 if (current_admin
.debug_level
>= DBG_ALL
) {
553 "dynamic discovery: using refresh interval %d\n",
557 (void) sleep(reqrefresh
);