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 */
getlocale()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 */
next_attr(char ** t_inout,char ** v_inout,char ** s_inout,int * type)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*/
aggregate_attrs(SLPHandle h,const char * attrs_in,SLPError errin,void * cookie)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*/
foreach_server(SLPHandle hin,const char * u,unsigned short life,SLPError errin,void * cookie)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 */
update_config(const char * context,struct config_cookie * cookie)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*/
foreach_context(SLPHandle h,const char * attrs_in,SLPError err,void * cookie)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 */
find_all_contexts(const char * type,void * (* get_cfghandle)(const char *),void (* aggregate)(void *,const char *,const char *),void (* set_cfghandle)(void *))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 */
discover(void * r)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