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 * Copyright 2018 Joyent, Inc.
26 */
27
28 #ifdef SLP
29
30 /*
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.
34 *
35 * The algorithm used is outlined here:
36 *
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())
48 *
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.
54 *
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:
58 *
59 * void *get_cfghandle(const char *domain);
60 *
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-
64 * name).
65 *
66 * void aggregate(void *handle, const char *tag, const char *value);
67 *
68 * Adds this tag / value pair to the set of aggregated attributes
69 * associated with the given handle.
70 *
71 * void set_cfghandle(void *handle);
72 *
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.
76 */
77
78 #include <stdio.h>
79 #include <slp.h>
80 #include <stdlib.h>
81 #include <string.h>
82 #include <door.h>
83 #include <unistd.h>
84 #include "ns_sldap.h"
85 #include "ns_internal.h"
86 #include "cachemgr.h"
87
88 #define ABSTYPE "service:naming-directory"
89 #define CONTEXT_ATTR "naming-context"
90 #define LDAP_DOMAIN_ATTR "x-sun-rpcdomain"
91
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 *);
102 };
103
104 extern admin_t current_admin; /* ldap_cachemgr's admin struct */
105
106 /*
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.
110 */
getlocale()111 static const char *getlocale() {
112 const char *locale = SLPGetProperty("net.slp.locale");
113 return (locale ? locale : "en");
114 }
115
116 /*
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:
121 *
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.
130 *
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.
136 *
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.
140 */
next_attr(char ** t_inout,char ** v_inout,char ** s_inout,int * type)141 static SLPBoolean next_attr(char **t_inout, char **v_inout,
142 char **s_inout, int *type) {
143 char *end = NULL;
144 char *tag = NULL;
145 char *val = NULL;
146 char *state = NULL;
147
148 if (!t_inout || !v_inout)
149 return (SLP_FALSE);
150
151 if (!s_inout || !*s_inout || !**s_inout)
152 return (SLP_FALSE);
153
154 state = *s_inout;
155
156 /* type: 0 = start, 1 = '(tag=val)' type, 2 = 'tag' type */
157 switch (*type) {
158 case 0:
159 switch (*state) {
160 case '(':
161 *type = 1;
162 break;
163 case ',':
164 state++;
165 *type = 0;
166 break;
167 default:
168 *type = 2;
169 }
170 *s_inout = state;
171 return (next_attr(t_inout, v_inout, s_inout, type));
172 break;
173 case 1:
174 switch (*state) {
175 case '(':
176 /* start of attr of the form (tag=val[,val]) */
177 state++;
178 tag = state;
179 end = strchr(state, ')'); /* for sanity checking */
180 if (!end)
181 return (SLP_FALSE); /* fatal parse error */
182
183 state = strchr(tag, '=');
184 if (state) {
185 if (state > end)
186 return (SLP_FALSE); /* fatal parse err */
187 *state++ = 0;
188 } else {
189 return (SLP_FALSE); /* fatal parse error */
190 }
191 /* fallthru to default case, which handles multivals */
192 default:
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, ')');
197 if (!end)
198 return (SLP_FALSE); /* fatal parse error */
199 }
200
201 val = state;
202 state = strchr(val, ','); /* is this attr multivalued? */
203 if (!state || state > end) {
204 /* no, so skip to the next attr */
205 state = end;
206 *type = 0;
207 } /* else attr is multivalued */
208 *state++ = 0;
209 break;
210 }
211 break;
212 case 2:
213 /* attr term with tag only */
214 tag = state;
215 state = strchr(tag, ',');
216 if (state) {
217 *state++ = 0;
218 }
219 val = NULL;
220 *type = 0;
221 break;
222 default:
223 return (SLP_FALSE);
224 }
225
226 *t_inout = tag;
227 *v_inout = val;
228 *s_inout = state;
229
230 return (SLP_TRUE);
231 }
232
233 /*
234 * The SLP callback routine for foreach_server(). Aggregates each
235 * server's attributes into the caller-specified config object.
236 */
237 /*ARGSUSED*/
aggregate_attrs(SLPHandle h,const char * attrs_in,SLPError errin,void * cookie)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;
242 int type = 0;
243 char *attrs;
244 SLPError err;
245 struct config_cookie *cfg = (struct config_cookie *)cookie;
246
247 if (errin != SLP_OK) {
248 return (SLP_TRUE);
249 }
250
251 attrs = strdup(attrs_in);
252 state = attrs;
253
254 while (next_attr(&tag, &val, &state, &type)) {
255 unesc_tag = unesc_val = NULL;
256
257 if (tag) {
258 if ((err = SLPUnescape(tag, &unesc_tag, SLP_TRUE)) != SLP_OK) {
259 unesc_tag = NULL;
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));
264 }
265 }
266 }
267 if (val) {
268 if ((err = SLPUnescape(val, &unesc_val, SLP_FALSE))
269 != SLP_OK) {
270 unesc_val = NULL;
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));
275 }
276 }
277 }
278
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"));
283 }
284
285 cfg->aggregate(cfg->cache_cfg, unesc_tag, unesc_val);
286
287 if (unesc_tag) free(unesc_tag);
288 if (unesc_val) free(unesc_val);
289 }
290
291 if (attrs) free(attrs);
292
293 return (SLP_TRUE);
294 }
295
296 /*
297 * The SLP callback routine for update_config(). For each
298 * server found, retrieves that server's attributes.
299 */
300 /*ARGSUSED*/
foreach_server(SLPHandle hin,const char * u,unsigned short life,SLPError errin,void * cookie)301 static SLPBoolean foreach_server(SLPHandle hin, const char *u,
302 unsigned short life,
303 SLPError errin, void *cookie) {
304 SLPError err;
305 struct config_cookie *cfg = (struct config_cookie *)cookie;
306 SLPHandle h = cfg->h; /* an open handle */
307 SLPSrvURL *surl = NULL;
308 char *url = NULL;
309
310 if (errin != SLP_OK) {
311 return (SLP_TRUE);
312 }
313
314 /* dup url so we can slice 'n dice */
315 if (!(url = strdup(u))) {
316 (void) logit("foreach_server: no memory");
317 return (SLP_FALSE);
318 }
319
320 if ((err = SLPParseSrvURL(url, &surl)) != SLP_OK) {
321 free(url);
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));
326 return (SLP_TRUE);
327 }
328 }
329
330 if (current_admin.debug_level >= DBG_ALL) {
331 (void) logit("discovery:\tserver: %s\n", surl->s_pcHost);
332 }
333
334 /* retrieve all attrs for this server */
335 err = SLPFindAttrs(h, u, cfg->scopes, "", aggregate_attrs, cookie);
336 if (err != SLP_OK) {
337 if (current_admin.debug_level >= DBG_NETLOOKUPS) {
338 (void) logit("foreach_server: FindAttrs failed: %s\n",
339 slp_strerror(err));
340 }
341 goto cleanup;
342 }
343
344 /* add this server and its attrs to the config object */
345 cfg->aggregate(cfg->cache_cfg, "_,_xservers_,_", surl->s_pcHost);
346
347 cleanup:
348 if (url) free(url);
349 if (surl) SLPFree(surl);
350
351 return (SLP_TRUE);
352 }
353
354 /*
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.
358 */
update_config(const char * context,struct config_cookie * cookie)359 static void update_config(const char *context, struct config_cookie *cookie) {
360 SLPHandle h = NULL;
361 SLPHandle persrv_h = NULL;
362 SLPError err;
363 char *search = NULL;
364 char *unesc_domain = NULL;
365
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));
372 }
373 return;
374 }
375
376 cookie->cache_cfg = cookie->get_cfghandle(unesc_domain);
377
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",
382 slp_strerror(err));
383 }
384 goto cleanup;
385 }
386
387 cookie->h = persrv_h;
388
389 if (current_admin.debug_level >= DBG_ALL) {
390 (void) logit("discovery: found naming context %s\n", context);
391 }
392
393 /* (re)construct the search filter form the input context */
394 search = malloc(strlen(cookie->context_attr) +
395 strlen(context) +
396 strlen("(=)") + 1);
397 if (!search) {
398 (void) logit("update_config: no memory\n");
399 goto cleanup;
400 }
401 (void) sprintf(search, "(%s=%s)", cookie->context_attr, context);
402
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",
407 slp_strerror(err));
408 }
409 goto cleanup;
410 }
411
412 err = SLPFindSrvs(h, cookie->type, cookie->scopes,
413 search, foreach_server, cookie);
414 if (err != SLP_OK) {
415 if (current_admin.debug_level >= DBG_NETLOOKUPS) {
416 (void) logit("update_config: SLPFindSrvs failed: %s\n",
417 slp_strerror(err));
418 }
419 goto cleanup;
420 }
421
422 /* update the config cache with the new info */
423 cookie->set_cfghandle(cookie->cache_cfg);
424
425 cleanup:
426 if (h) SLPClose(h);
427 if (persrv_h) SLPClose(persrv_h);
428 if (search) free(search);
429 if (unesc_domain) free(unesc_domain);
430 }
431
432 /*
433 * The SLP callback routine for find_all_contexts(). For each context
434 * found, finds all the servers and their attributes.
435 */
436 /*ARGSUSED*/
foreach_context(SLPHandle h,const char * attrs_in,SLPError err,void * cookie)437 static SLPBoolean foreach_context(SLPHandle h, const char *attrs_in,
438 SLPError err, void *cookie) {
439 char *attrs, *tag, *val, *state;
440 int type = 0;
441
442 if (err != SLP_OK) {
443 return (SLP_TRUE);
444 }
445
446 /*
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.
450 */
451 attrs = strdup(attrs_in); /* so we can slice'n'dice */
452 if (!attrs) {
453 (void) logit("foreach_context: no memory\n");
454 return (SLP_FALSE);
455 }
456 state = attrs;
457
458 while (next_attr(&tag, &val, &state, &type)) {
459 update_config(val, cookie);
460 }
461
462 free(attrs);
463
464 return (SLP_TRUE);
465 }
466
467 /*
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.
471 *
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.
475 */
find_all_contexts(const char * type,void * (* get_cfghandle)(const char *),void (* aggregate)(void *,const char *,const char *),void (* set_cfghandle)(void *))476 static void find_all_contexts(const char *type,
477 void *(*get_cfghandle)(const char *),
478 void (*aggregate)(
479 void *, const char *, const char *),
480 void (*set_cfghandle)(void *)) {
481 SLPHandle h = NULL;
482 SLPError err;
483 struct config_cookie cookie[1];
484 char *fulltype = NULL;
485 char *scope = (char *)SLPGetProperty("net.slp.useScopes");
486
487 if (!scope || !*scope) {
488 scope = "default";
489 }
490
491 /* construct the full type from the partial type parameter */
492 fulltype = malloc(strlen(ABSTYPE) + strlen(type) + 2);
493 if (!fulltype) {
494 (void) logit("find_all_contexts: no memory");
495 goto done;
496 }
497 (void) sprintf(fulltype, "%s:%s", ABSTYPE, type);
498
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;
506 } else {
507 cookie->context_attr = CONTEXT_ATTR;
508 }
509 cookie->get_cfghandle = get_cfghandle;
510 cookie->aggregate = aggregate;
511 cookie->set_cfghandle = set_cfghandle;
512
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",
517 slp_strerror(err));
518 }
519 goto done;
520 }
521
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);
525 if (err != SLP_OK) {
526 if (current_admin.debug_level >= DBG_CANT_FIND) {
527 (void) logit(
528 "discover: Aborting discovery: SLPFindAttrs failed: %s\n",
529 slp_strerror(err));
530 }
531 goto done;
532 }
533
534 done:
535 if (h) SLPClose(h);
536 if (fulltype) free(fulltype);
537 }
538
539 /*
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.
543 */
544 void
discover(void * r)545 discover(void *r) {
546 unsigned short reqrefresh = *((unsigned int *)r);
547
548 (void) pthread_setname_np(pthread_self(), "discover");
549
550 for (;;) {
551 find_all_contexts("ldap",
552 __cache_get_cfghandle,
553 __cache_aggregate_params,
554 __cache_set_cfghandle);
555
556 if (current_admin.debug_level >= DBG_ALL) {
557 (void) logit(
558 "dynamic discovery: using refresh interval %d\n",
559 reqrefresh);
560 }
561
562 (void) sleep(reqrefresh);
563 }
564 }
565
566 #endif /* SLP */
567