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.
25 * Copyright 2018 Joyent, Inc.
31 * This file contains all the dynamic server discovery functionality
32 * for ldap_cachemgr. SLP is used to query the network for any changes
33 * in the set of deployed LDAP servers.
35 * The algorithm used is outlined here:
37 * 1. Find all naming contexts with SLPFindAttrs. (See
38 * find_all_contexts())
39 * 2. For each context, find all servers which serve that context
40 * with SLPFindSrvs. (See foreach_context())
41 * 3. For each server, retrieve that server's attributes with
42 * SLPFindAttributes. (See foreach_server())
43 * 4. Aggregate the servers' attributes into a config object. There
44 * is one config object associated with each context found in
45 * step 1. (See aggregate_attrs())
46 * 5. Update the global config cache for each found context and its
47 * associated servers and attributes. (See update_config())
49 * The entry point for ldap_cachemgr is discover(). The actual entry
50 * point into the discovery routine is find_all_contexts(); the
51 * code thereafter is actually not specific to LDAP, and could also
52 * be used to discover YP, or any other server which conforms
53 * to the SLP Naming and Directory abstract service type.
55 * find_all_attributes() takes as parameters three callback routines
56 * which are used to report all information back to the caller. The
57 * signatures and synopses of these routines are:
59 * void *get_cfghandle(const char *domain);
61 * Returns an opaque handle to a configuration object specific
62 * to the 'domain' parameter. 'domain' will be a naming context
63 * string, i.e. foo.bar.sun.com ( i.e. a secure-RPC domain-
66 * void aggregate(void *handle, const char *tag, const char *value);
68 * Adds this tag / value pair to the set of aggregated attributes
69 * associated with the given handle.
71 * void set_cfghandle(void *handle);
73 * Sets and destroys the config object; SLP will no longer attempt
74 * to use this handle after this call. Thus, this call marks the
75 * end of configuration information for this handle.
85 #include "ns_internal.h"
88 #define ABSTYPE "service:naming-directory"
89 #define CONTEXT_ATTR "naming-context"
90 #define LDAP_DOMAIN_ATTR "x-sun-rpcdomain"
92 /* The configuration cookie passed along through all SLP callbacks. */
93 struct config_cookie
{
94 SLPHandle h
; /* An open SLPHandle */
95 const char *type
; /* The full service type to use */
96 char *scopes
; /* A list of scopes to use */
97 const char *context_attr
; /* Which attr to use for the ctx */
98 void *cache_cfg
; /* caller-supplied config object */
99 void *(*get_cfghandle
)(const char *);
100 void (*aggregate
)(void *, const char *, const char *);
101 void (*set_cfghandle
)(void *);
104 extern admin_t current_admin
; /* ldap_cachemgr's admin struct */
107 * Utility routine: getlocale():
108 * Returns the locale specified by the SLP locale property, or just
109 * returns the default SLP locale if the property was not set.
111 static const char *getlocale() {
112 const char *locale
= SLPGetProperty("net.slp.locale");
113 return (locale
? locale
: "en");
117 * Utility routine: next_attr():
118 * Parses an SLP attribute string. On the first call, *type
119 * must be set to 0, and *s_inout must point to the beginning
120 * of the attr string. The following results are possible:
122 * If the term is of the form 'tag' only, *t_inout is set to tag,
123 * and *v_inout is set to NULL.
124 * If the term is of the form '(tag=val)', *t_inout and *v_inout
125 * are set to the tag and val strings, respectively.
126 * If the term is of the form '(tag=val1,val2,..,valN)', on each
127 * successive call, next_attr will return the next value. On the
128 * first invocation, tag is set to 'tag'; on successive invocations,
129 * tag is set to *t_inout.
131 * The string passed in *s_inout is destructively modified; all values
132 * returned simply point into the initial string. Hence the caller
133 * is responsible for all memory management. The type parameter is
134 * for internal use only and should be set to 0 by the caller only
135 * on the first invocation.
137 * If more attrs are available, returns SLP_TRUE, otherwise returns
138 * SLP_FALSE. If SLP_FALSE is returned, all value-result parameters
139 * will be undefined, and should not be used.
141 static SLPBoolean
next_attr(char **t_inout
, char **v_inout
,
142 char **s_inout
, int *type
) {
148 if (!t_inout
|| !v_inout
)
151 if (!s_inout
|| !*s_inout
|| !**s_inout
)
156 /* type: 0 = start, 1 = '(tag=val)' type, 2 = 'tag' type */
171 return (next_attr(t_inout
, v_inout
, s_inout
, type
));
176 /* start of attr of the form (tag=val[,val]) */
179 end
= strchr(state
, ')'); /* for sanity checking */
181 return (SLP_FALSE
); /* fatal parse error */
183 state
= strchr(tag
, '=');
186 return (SLP_FALSE
); /* fatal parse err */
189 return (SLP_FALSE
); /* fatal parse error */
191 /* fallthru to default case, which handles multivals */
193 /* somewhere in a multivalued attr */
194 if (!end
) { /* did not fallthru from '(' case */
195 tag
= *t_inout
; /* leave tag as it was */
196 end
= strchr(state
, ')');
198 return (SLP_FALSE
); /* fatal parse error */
202 state
= strchr(val
, ','); /* is this attr multivalued? */
203 if (!state
|| state
> end
) {
204 /* no, so skip to the next attr */
207 } /* else attr is multivalued */
213 /* attr term with tag only */
215 state
= strchr(tag
, ',');
234 * The SLP callback routine for foreach_server(). Aggregates each
235 * server's attributes into the caller-specified config object.
238 static SLPBoolean
aggregate_attrs(SLPHandle h
, const char *attrs_in
,
239 SLPError errin
, void *cookie
) {
240 char *tag
, *val
, *state
;
241 char *unesc_tag
, *unesc_val
;
245 struct config_cookie
*cfg
= (struct config_cookie
*)cookie
;
247 if (errin
!= SLP_OK
) {
251 attrs
= strdup(attrs_in
);
254 while (next_attr(&tag
, &val
, &state
, &type
)) {
255 unesc_tag
= unesc_val
= NULL
;
258 if ((err
= SLPUnescape(tag
, &unesc_tag
, SLP_TRUE
)) != SLP_OK
) {
260 if (current_admin
.debug_level
>= DBG_ALL
) {
261 (void) logit("aggregate_attrs: ",
262 "could not unescape attr tag %s:%s\n",
263 tag
, slp_strerror(err
));
268 if ((err
= SLPUnescape(val
, &unesc_val
, SLP_FALSE
))
271 if (current_admin
.debug_level
>= DBG_ALL
) {
272 (void) logit("aggregate_attrs: ",
273 "could not unescape attr val %s:%s\n",
274 val
, slp_strerror(err
));
279 if (current_admin
.debug_level
>= DBG_ALL
) {
280 (void) logit("discovery:\t\t%s=%s\n",
281 (unesc_tag
? unesc_tag
: "NULL"),
282 (unesc_val
? unesc_val
: "NULL"));
285 cfg
->aggregate(cfg
->cache_cfg
, unesc_tag
, unesc_val
);
287 if (unesc_tag
) free(unesc_tag
);
288 if (unesc_val
) free(unesc_val
);
291 if (attrs
) free(attrs
);
297 * The SLP callback routine for update_config(). For each
298 * server found, retrieves that server's attributes.
301 static SLPBoolean
foreach_server(SLPHandle hin
, const char *u
,
303 SLPError errin
, void *cookie
) {
305 struct config_cookie
*cfg
= (struct config_cookie
*)cookie
;
306 SLPHandle h
= cfg
->h
; /* an open handle */
307 SLPSrvURL
*surl
= NULL
;
310 if (errin
!= SLP_OK
) {
314 /* dup url so we can slice 'n dice */
315 if (!(url
= strdup(u
))) {
316 (void) logit("foreach_server: no memory");
320 if ((err
= SLPParseSrvURL(url
, &surl
)) != SLP_OK
) {
322 if (current_admin
.debug_level
>= DBG_NETLOOKUPS
) {
323 (void) logit("foreach_server: ",
324 "dropping unparsable URL %s: %s\n",
325 url
, slp_strerror(err
));
330 if (current_admin
.debug_level
>= DBG_ALL
) {
331 (void) logit("discovery:\tserver: %s\n", surl
->s_pcHost
);
334 /* retrieve all attrs for this server */
335 err
= SLPFindAttrs(h
, u
, cfg
->scopes
, "", aggregate_attrs
, cookie
);
337 if (current_admin
.debug_level
>= DBG_NETLOOKUPS
) {
338 (void) logit("foreach_server: FindAttrs failed: %s\n",
344 /* add this server and its attrs to the config object */
345 cfg
->aggregate(cfg
->cache_cfg
, "_,_xservers_,_", surl
->s_pcHost
);
349 if (surl
) SLPFree(surl
);
355 * This routine does the dirty work of finding all servers for a
356 * given domain and injecting this information into the caller's
357 * configuration namespace via callbacks.
359 static void update_config(const char *context
, struct config_cookie
*cookie
) {
361 SLPHandle persrv_h
= NULL
;
364 char *unesc_domain
= NULL
;
366 /* Unescape the naming context string */
367 if ((err
= SLPUnescape(context
, &unesc_domain
, SLP_FALSE
)) != SLP_OK
) {
368 if (current_admin
.debug_level
>= DBG_ALL
) {
369 (void) logit("update_config: ",
370 "dropping unparsable domain: %s: %s\n",
371 context
, slp_strerror(err
));
376 cookie
->cache_cfg
= cookie
->get_cfghandle(unesc_domain
);
378 /* Open a handle which all attrs calls can use */
379 if ((err
= SLPOpen(getlocale(), SLP_FALSE
, &persrv_h
)) != SLP_OK
) {
380 if (current_admin
.debug_level
>= DBG_NETLOOKUPS
) {
381 (void) logit("update_config: SLPOpen failed: %s\n",
387 cookie
->h
= persrv_h
;
389 if (current_admin
.debug_level
>= DBG_ALL
) {
390 (void) logit("discovery: found naming context %s\n", context
);
393 /* (re)construct the search filter form the input context */
394 search
= malloc(strlen(cookie
->context_attr
) +
398 (void) logit("update_config: no memory\n");
401 (void) sprintf(search
, "(%s=%s)", cookie
->context_attr
, context
);
403 /* Find all servers which serve this context */
404 if ((err
= SLPOpen(getlocale(), SLP_FALSE
, &h
)) != SLP_OK
) {
405 if (current_admin
.debug_level
>= DBG_NETLOOKUPS
) {
406 (void) logit("upate_config: SLPOpen failed: %s\n",
412 err
= SLPFindSrvs(h
, cookie
->type
, cookie
->scopes
,
413 search
, foreach_server
, cookie
);
415 if (current_admin
.debug_level
>= DBG_NETLOOKUPS
) {
416 (void) logit("update_config: SLPFindSrvs failed: %s\n",
422 /* update the config cache with the new info */
423 cookie
->set_cfghandle(cookie
->cache_cfg
);
427 if (persrv_h
) SLPClose(persrv_h
);
428 if (search
) free(search
);
429 if (unesc_domain
) free(unesc_domain
);
433 * The SLP callback routine for find_all_contexts(). For each context
434 * found, finds all the servers and their attributes.
437 static SLPBoolean
foreach_context(SLPHandle h
, const char *attrs_in
,
438 SLPError err
, void *cookie
) {
439 char *attrs
, *tag
, *val
, *state
;
447 * Parse out each context. Attrs will be of the following form:
448 * (naming-context=dc\3deng\2c dc\3dsun\2c dc\3dcom)
449 * Note that ',' and '=' are reserved in SLP, so they are escaped.
451 attrs
= strdup(attrs_in
); /* so we can slice'n'dice */
453 (void) logit("foreach_context: no memory\n");
458 while (next_attr(&tag
, &val
, &state
, &type
)) {
459 update_config(val
, cookie
);
468 * Initiates server and attribute discovery for the concrete type
469 * 'type'. Currently the only useful type is "ldap", but perhaps
470 * "nis" and "nisplus" will also be useful in the future.
472 * get_cfghandle, aggregate, and set_cfghandle are callback routines
473 * used to pass any discovered configuration information back to the
474 * caller. See the introduction at the top of this file for more info.
476 static void find_all_contexts(const char *type
,
477 void *(*get_cfghandle
)(const char *),
479 void *, const char *, const char *),
480 void (*set_cfghandle
)(void *)) {
483 struct config_cookie cookie
[1];
484 char *fulltype
= NULL
;
485 char *scope
= (char *)SLPGetProperty("net.slp.useScopes");
487 if (!scope
|| !*scope
) {
491 /* construct the full type from the partial type parameter */
492 fulltype
= malloc(strlen(ABSTYPE
) + strlen(type
) + 2);
494 (void) logit("find_all_contexts: no memory");
497 (void) sprintf(fulltype
, "%s:%s", ABSTYPE
, type
);
499 /* set up the cookie for this discovery operation */
500 memset(cookie
, 0, sizeof (*cookie
));
501 cookie
->type
= fulltype
;
502 cookie
->scopes
= scope
;
503 if (strcasecmp(type
, "ldap") == 0) {
504 /* Sun LDAP is special */
505 cookie
->context_attr
= LDAP_DOMAIN_ATTR
;
507 cookie
->context_attr
= CONTEXT_ATTR
;
509 cookie
->get_cfghandle
= get_cfghandle
;
510 cookie
->aggregate
= aggregate
;
511 cookie
->set_cfghandle
= set_cfghandle
;
513 if ((err
= SLPOpen(getlocale(), SLP_FALSE
, &h
)) != SLP_OK
) {
514 if (current_admin
.debug_level
>= DBG_CANT_FIND
) {
515 (void) logit("discover: %s",
516 "Aborting discovery: SLPOpen failed: %s\n",
522 /* use find attrs to get a list of all available contexts */
523 err
= SLPFindAttrs(h
, fulltype
, scope
, cookie
->context_attr
,
524 foreach_context
, cookie
);
526 if (current_admin
.debug_level
>= DBG_CANT_FIND
) {
528 "discover: Aborting discovery: SLPFindAttrs failed: %s\n",
536 if (fulltype
) free(fulltype
);
540 * This is the ldap_cachemgr entry point into SLP dynamic discovery. The
541 * parameter 'r' should be a pointer to an unsigned int containing
542 * the requested interval at which the network should be queried.
546 unsigned short reqrefresh
= *((unsigned int *)r
);
548 (void) pthread_setname_np(pthread_self(), "discover");
551 find_all_contexts("ldap",
552 __cache_get_cfghandle
,
553 __cache_aggregate_params
,
554 __cache_set_cfghandle
);
556 if (current_admin
.debug_level
>= DBG_ALL
) {
558 "dynamic discovery: using refresh interval %d\n",
562 (void) sleep(reqrefresh
);