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