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