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