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