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 */ 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 */ 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*/ 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*/ 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 */ 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*/ 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 */ 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 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 */