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, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright (c) 1999,2001 by Sun Microsystems, Inc. 24 * All rights reserved. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 #ifdef SLP 30 31 /* 32 * This file contains all the dynamic server discovery functionality 33 * for ldap_cachemgr. SLP is used to query the network for any changes 34 * in the set of deployed LDAP servers. 35 * 36 * The algorithm used is outlined here: 37 * 38 * 1. Find all naming contexts with SLPFindAttrs. (See 39 * find_all_contexts()) 40 * 2. For each context, find all servers which serve that context 41 * with SLPFindSrvs. (See foreach_context()) 42 * 3. For each server, retrieve that server's attributes with 43 * SLPFindAttributes. (See foreach_server()) 44 * 4. Aggregate the servers' attributes into a config object. There 45 * is one config object associated with each context found in 46 * step 1. (See aggregate_attrs()) 47 * 5. Update the global config cache for each found context and its 48 * associated servers and attributes. (See update_config()) 49 * 50 * The entry point for ldap_cachemgr is discover(). The actual entry 51 * point into the discovery routine is find_all_contexts(); the 52 * code thereafter is actually not specific to LDAP, and could also 53 * be used to discover YP, NIS+, or any other server which conforms 54 * to the SLP Naming and Directory abstract service type. 55 * 56 * find_all_attributes() takes as parameters three callback routines 57 * which are used to report all information back to the caller. The 58 * signatures and synopses of these routines are: 59 * 60 * void *get_cfghandle(const char *domain); 61 * 62 * Returns an opaque handle to a configuration object specific 63 * to the 'domain' parameter. 'domain' will be a naming context 64 * string, i.e. foo.bar.sun.com ( i.e. a secure-RPC domain- 65 * name). 66 * 67 * void aggregate(void *handle, const char *tag, const char *value); 68 * 69 * Adds this tag / value pair to the set of aggregated attributes 70 * associated with the given handle. 71 * 72 * void set_cfghandle(void *handle); 73 * 74 * Sets and destroys the config object; SLP will no longer attempt 75 * to use this handle after this call. Thus, this call marks the 76 * end of configuration information for this handle. 77 */ 78 79 #include <stdio.h> 80 #include <slp.h> 81 #include <stdlib.h> 82 #include <string.h> 83 #include <door.h> 84 #include <unistd.h> 85 #include "ns_sldap.h" 86 #include "ns_internal.h" 87 #include "cachemgr.h" 88 89 #define ABSTYPE "service:naming-directory" 90 #define CONTEXT_ATTR "naming-context" 91 #define LDAP_DOMAIN_ATTR "x-sun-rpcdomain" 92 93 /* The configuration cookie passed along through all SLP callbacks. */ 94 struct config_cookie { 95 SLPHandle h; /* An open SLPHandle */ 96 const char *type; /* The full service type to use */ 97 char *scopes; /* A list of scopes to use */ 98 const char *context_attr; /* Which attr to use for the ctx */ 99 void *cache_cfg; /* caller-supplied config object */ 100 void *(*get_cfghandle)(const char *); 101 void (*aggregate)(void *, const char *, const char *); 102 void (*set_cfghandle)(void *); 103 }; 104 105 extern admin_t current_admin; /* ldap_cachemgr's admin struct */ 106 107 /* 108 * Utility routine: getlocale(): 109 * Returns the locale specified by the SLP locale property, or just 110 * returns the default SLP locale if the property was not set. 111 */ 112 static const char *getlocale() { 113 const char *locale = SLPGetProperty("net.slp.locale"); 114 return (locale ? locale : "en"); 115 } 116 117 /* 118 * Utility routine: next_attr(): 119 * Parses an SLP attribute string. On the first call, *type 120 * must be set to 0, and *s_inout must point to the beginning 121 * of the attr string. The following results are possible: 122 * 123 * If the term is of the form 'tag' only, *t_inout is set to tag, 124 * and *v_inout is set to NULL. 125 * If the term is of the form '(tag=val)', *t_inout and *v_inout 126 * are set to the tag and val strings, respectively. 127 * If the term is of the form '(tag=val1,val2,..,valN)', on each 128 * successive call, next_attr will return the next value. On the 129 * first invocation, tag is set to 'tag'; on successive invocations, 130 * tag is set to *t_inout. 131 * 132 * The string passed in *s_inout is destructively modified; all values 133 * returned simply point into the initial string. Hence the caller 134 * is responsible for all memory management. The type parameter is 135 * for internal use only and should be set to 0 by the caller only 136 * on the first invocation. 137 * 138 * If more attrs are available, returns SLP_TRUE, otherwise returns 139 * SLP_FALSE. If SLP_FALSE is returned, all value-result parameters 140 * will be undefined, and should not be used. 141 */ 142 static SLPBoolean next_attr(char **t_inout, char **v_inout, 143 char **s_inout, int *type) { 144 char *end = NULL; 145 char *tag = NULL; 146 char *val = NULL; 147 char *state = NULL; 148 149 if (!t_inout || !v_inout) 150 return (SLP_FALSE); 151 152 if (!s_inout || !*s_inout || !**s_inout) 153 return (SLP_FALSE); 154 155 state = *s_inout; 156 157 /* type: 0 = start, 1 = '(tag=val)' type, 2 = 'tag' type */ 158 switch (*type) { 159 case 0: 160 switch (*state) { 161 case '(': 162 *type = 1; 163 break; 164 case ',': 165 state++; 166 *type = 0; 167 break; 168 default: 169 *type = 2; 170 } 171 *s_inout = state; 172 return (next_attr(t_inout, v_inout, s_inout, type)); 173 break; 174 case 1: 175 switch (*state) { 176 case '(': 177 /* start of attr of the form (tag=val[,val]) */ 178 state++; 179 tag = state; 180 end = strchr(state, ')'); /* for sanity checking */ 181 if (!end) 182 return (SLP_FALSE); /* fatal parse error */ 183 184 state = strchr(tag, '='); 185 if (state) { 186 if (state > end) 187 return (SLP_FALSE); /* fatal parse err */ 188 *state++ = 0; 189 } else { 190 return (SLP_FALSE); /* fatal parse error */ 191 } 192 /* fallthru to default case, which handles multivals */ 193 default: 194 /* somewhere in a multivalued attr */ 195 if (!end) { /* did not fallthru from '(' case */ 196 tag = *t_inout; /* leave tag as it was */ 197 end = strchr(state, ')'); 198 if (!end) 199 return (SLP_FALSE); /* fatal parse error */ 200 } 201 202 val = state; 203 state = strchr(val, ','); /* is this attr multivalued? */ 204 if (!state || state > end) { 205 /* no, so skip to the next attr */ 206 state = end; 207 *type = 0; 208 } /* else attr is multivalued */ 209 *state++ = 0; 210 break; 211 } 212 break; 213 case 2: 214 /* attr term with tag only */ 215 tag = state; 216 state = strchr(tag, ','); 217 if (state) { 218 *state++ = 0; 219 } 220 val = NULL; 221 *type = 0; 222 break; 223 default: 224 return (SLP_FALSE); 225 } 226 227 *t_inout = tag; 228 *v_inout = val; 229 *s_inout = state; 230 231 return (SLP_TRUE); 232 } 233 234 /* 235 * The SLP callback routine for foreach_server(). Aggregates each 236 * server's attributes into the caller-specified config object. 237 */ 238 /*ARGSUSED*/ 239 static SLPBoolean aggregate_attrs(SLPHandle h, const char *attrs_in, 240 SLPError errin, void *cookie) { 241 char *tag, *val, *state; 242 char *unesc_tag, *unesc_val; 243 int type = 0; 244 char *attrs; 245 SLPError err; 246 struct config_cookie *cfg = (struct config_cookie *)cookie; 247 248 if (errin != SLP_OK) { 249 return (SLP_TRUE); 250 } 251 252 attrs = strdup(attrs_in); 253 state = attrs; 254 255 while (next_attr(&tag, &val, &state, &type)) { 256 unesc_tag = unesc_val = NULL; 257 258 if (tag) { 259 if ((err = SLPUnescape(tag, &unesc_tag, SLP_TRUE)) != SLP_OK) { 260 unesc_tag = NULL; 261 if (current_admin.debug_level >= DBG_ALL) { 262 (void) logit("aggregate_attrs: ", 263 "could not unescape attr tag %s:%s\n", 264 tag, slp_strerror(err)); 265 } 266 } 267 } 268 if (val) { 269 if ((err = SLPUnescape(val, &unesc_val, SLP_FALSE)) 270 != SLP_OK) { 271 unesc_val = NULL; 272 if (current_admin.debug_level >= DBG_ALL) { 273 (void) logit("aggregate_attrs: ", 274 "could not unescape attr val %s:%s\n", 275 val, slp_strerror(err)); 276 } 277 } 278 } 279 280 if (current_admin.debug_level >= DBG_ALL) { 281 (void) logit("discovery:\t\t%s=%s\n", 282 (unesc_tag ? unesc_tag : "NULL"), 283 (unesc_val ? unesc_val : "NULL")); 284 } 285 286 cfg->aggregate(cfg->cache_cfg, unesc_tag, unesc_val); 287 288 if (unesc_tag) free(unesc_tag); 289 if (unesc_val) free(unesc_val); 290 } 291 292 if (attrs) free(attrs); 293 294 return (SLP_TRUE); 295 } 296 297 /* 298 * The SLP callback routine for update_config(). For each 299 * server found, retrieves that server's attributes. 300 */ 301 /*ARGSUSED*/ 302 static SLPBoolean foreach_server(SLPHandle hin, const char *u, 303 unsigned short life, 304 SLPError errin, void *cookie) { 305 SLPError err; 306 struct config_cookie *cfg = (struct config_cookie *)cookie; 307 SLPHandle h = cfg->h; /* an open handle */ 308 SLPSrvURL *surl = NULL; 309 char *url = NULL; 310 311 if (errin != SLP_OK) { 312 return (SLP_TRUE); 313 } 314 315 /* dup url so we can slice 'n dice */ 316 if (!(url = strdup(u))) { 317 (void) logit("foreach_server: no memory"); 318 return (SLP_FALSE); 319 } 320 321 if ((err = SLPParseSrvURL(url, &surl)) != SLP_OK) { 322 free(url); 323 if (current_admin.debug_level >= DBG_NETLOOKUPS) { 324 (void) logit("foreach_server: ", 325 "dropping unparsable URL %s: %s\n", 326 url, slp_strerror(err)); 327 return (SLP_TRUE); 328 } 329 } 330 331 if (current_admin.debug_level >= DBG_ALL) { 332 (void) logit("discovery:\tserver: %s\n", surl->s_pcHost); 333 } 334 335 /* retrieve all attrs for this server */ 336 err = SLPFindAttrs(h, u, cfg->scopes, "", aggregate_attrs, cookie); 337 if (err != SLP_OK) { 338 if (current_admin.debug_level >= DBG_NETLOOKUPS) { 339 (void) logit("foreach_server: FindAttrs failed: %s\n", 340 slp_strerror(err)); 341 } 342 goto cleanup; 343 } 344 345 /* add this server and its attrs to the config object */ 346 cfg->aggregate(cfg->cache_cfg, "_,_xservers_,_", surl->s_pcHost); 347 348 cleanup: 349 if (url) free(url); 350 if (surl) SLPFree(surl); 351 352 return (SLP_TRUE); 353 } 354 355 /* 356 * This routine does the dirty work of finding all servers for a 357 * given domain and injecting this information into the caller's 358 * configuration namespace via callbacks. 359 */ 360 static void update_config(const char *context, struct config_cookie *cookie) { 361 SLPHandle h = NULL; 362 SLPHandle persrv_h = NULL; 363 SLPError err; 364 char *search = NULL; 365 char *unesc_domain = NULL; 366 367 /* Unescape the naming context string */ 368 if ((err = SLPUnescape(context, &unesc_domain, SLP_FALSE)) != SLP_OK) { 369 if (current_admin.debug_level >= DBG_ALL) { 370 (void) logit("update_config: ", 371 "dropping unparsable domain: %s: %s\n", 372 context, slp_strerror(err)); 373 } 374 return; 375 } 376 377 cookie->cache_cfg = cookie->get_cfghandle(unesc_domain); 378 379 /* Open a handle which all attrs calls can use */ 380 if ((err = SLPOpen(getlocale(), SLP_FALSE, &persrv_h)) != SLP_OK) { 381 if (current_admin.debug_level >= DBG_NETLOOKUPS) { 382 (void) logit("update_config: SLPOpen failed: %s\n", 383 slp_strerror(err)); 384 } 385 goto cleanup; 386 } 387 388 cookie->h = persrv_h; 389 390 if (current_admin.debug_level >= DBG_ALL) { 391 (void) logit("discovery: found naming context %s\n", context); 392 } 393 394 /* (re)construct the search filter form the input context */ 395 search = malloc(strlen(cookie->context_attr) + 396 strlen(context) + 397 strlen("(=)") + 1); 398 if (!search) { 399 (void) logit("update_config: no memory\n"); 400 goto cleanup; 401 } 402 (void) sprintf(search, "(%s=%s)", cookie->context_attr, context); 403 404 /* Find all servers which serve this context */ 405 if ((err = SLPOpen(getlocale(), SLP_FALSE, &h)) != SLP_OK) { 406 if (current_admin.debug_level >= DBG_NETLOOKUPS) { 407 (void) logit("upate_config: SLPOpen failed: %s\n", 408 slp_strerror(err)); 409 } 410 goto cleanup; 411 } 412 413 err = SLPFindSrvs(h, cookie->type, cookie->scopes, 414 search, foreach_server, cookie); 415 if (err != SLP_OK) { 416 if (current_admin.debug_level >= DBG_NETLOOKUPS) { 417 (void) logit("update_config: SLPFindSrvs failed: %s\n", 418 slp_strerror(err)); 419 } 420 goto cleanup; 421 } 422 423 /* update the config cache with the new info */ 424 cookie->set_cfghandle(cookie->cache_cfg); 425 426 cleanup: 427 if (h) SLPClose(h); 428 if (persrv_h) SLPClose(persrv_h); 429 if (search) free(search); 430 if (unesc_domain) free(unesc_domain); 431 } 432 433 /* 434 * The SLP callback routine for find_all_contexts(). For each context 435 * found, finds all the servers and their attributes. 436 */ 437 /*ARGSUSED*/ 438 static SLPBoolean foreach_context(SLPHandle h, const char *attrs_in, 439 SLPError err, void *cookie) { 440 char *attrs, *tag, *val, *state; 441 int type = 0; 442 443 if (err != SLP_OK) { 444 return (SLP_TRUE); 445 } 446 447 /* 448 * Parse out each context. Attrs will be of the following form: 449 * (naming-context=dc\3deng\2c dc\3dsun\2c dc\3dcom) 450 * Note that ',' and '=' are reserved in SLP, so they are escaped. 451 */ 452 attrs = strdup(attrs_in); /* so we can slice'n'dice */ 453 if (!attrs) { 454 (void) logit("foreach_context: no memory\n"); 455 return (SLP_FALSE); 456 } 457 state = attrs; 458 459 while (next_attr(&tag, &val, &state, &type)) { 460 update_config(val, cookie); 461 } 462 463 free(attrs); 464 465 return (SLP_TRUE); 466 } 467 468 /* 469 * Initiates server and attribute discovery for the concrete type 470 * 'type'. Currently the only useful type is "ldap", but perhaps 471 * "nis" and "nisplus" will also be useful in the future. 472 * 473 * get_cfghandle, aggregate, and set_cfghandle are callback routines 474 * used to pass any discovered configuration information back to the 475 * caller. See the introduction at the top of this file for more info. 476 */ 477 static void find_all_contexts(const char *type, 478 void *(*get_cfghandle)(const char *), 479 void (*aggregate)( 480 void *, const char *, const char *), 481 void (*set_cfghandle)(void *)) { 482 SLPHandle h = NULL; 483 SLPError err; 484 struct config_cookie cookie[1]; 485 char *fulltype = NULL; 486 char *scope = (char *)SLPGetProperty("net.slp.useScopes"); 487 488 if (!scope || !*scope) { 489 scope = "default"; 490 } 491 492 /* construct the full type from the partial type parameter */ 493 fulltype = malloc(strlen(ABSTYPE) + strlen(type) + 2); 494 if (!fulltype) { 495 (void) logit("find_all_contexts: no memory"); 496 goto done; 497 } 498 (void) sprintf(fulltype, "%s:%s", ABSTYPE, type); 499 500 /* set up the cookie for this discovery operation */ 501 memset(cookie, 0, sizeof (*cookie)); 502 cookie->type = fulltype; 503 cookie->scopes = scope; 504 if (strcasecmp(type, "ldap") == 0) { 505 /* Sun LDAP is special */ 506 cookie->context_attr = LDAP_DOMAIN_ATTR; 507 } else { 508 cookie->context_attr = CONTEXT_ATTR; 509 } 510 cookie->get_cfghandle = get_cfghandle; 511 cookie->aggregate = aggregate; 512 cookie->set_cfghandle = set_cfghandle; 513 514 if ((err = SLPOpen(getlocale(), SLP_FALSE, &h)) != SLP_OK) { 515 if (current_admin.debug_level >= DBG_CANT_FIND) { 516 (void) logit("discover: %s", 517 "Aborting discovery: SLPOpen failed: %s\n", 518 slp_strerror(err)); 519 } 520 goto done; 521 } 522 523 /* use find attrs to get a list of all available contexts */ 524 err = SLPFindAttrs(h, fulltype, scope, cookie->context_attr, 525 foreach_context, cookie); 526 if (err != SLP_OK) { 527 if (current_admin.debug_level >= DBG_CANT_FIND) { 528 (void) logit( 529 "discover: Aborting discovery: SLPFindAttrs failed: %s\n", 530 slp_strerror(err)); 531 } 532 goto done; 533 } 534 535 done: 536 if (h) SLPClose(h); 537 if (fulltype) free(fulltype); 538 } 539 540 /* 541 * This is the ldap_cachemgr entry point into SLP dynamic discovery. The 542 * parameter 'r' should be a pointer to an unsigned int containing 543 * the requested interval at which the network should be queried. 544 */ 545 void discover(void *r) { 546 unsigned short reqrefresh = *((unsigned int *)r); 547 548 for (;;) { 549 find_all_contexts("ldap", 550 __cache_get_cfghandle, 551 __cache_aggregate_params, 552 __cache_set_cfghandle); 553 554 if (current_admin.debug_level >= DBG_ALL) { 555 (void) logit( 556 "dynamic discovery: using refresh interval %d\n", 557 reqrefresh); 558 } 559 560 (void) sleep(reqrefresh); 561 } 562 } 563 564 #endif /* SLP */ 565