/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#ifdef SLP
/*
* This file contains all the dynamic server discovery functionality
* for ldap_cachemgr. SLP is used to query the network for any changes
* in the set of deployed LDAP servers.
*
* The algorithm used is outlined here:
*
* 1. Find all naming contexts with SLPFindAttrs. (See
* find_all_contexts())
* 2. For each context, find all servers which serve that context
* with SLPFindSrvs. (See foreach_context())
* 3. For each server, retrieve that server's attributes with
* SLPFindAttributes. (See foreach_server())
* 4. Aggregate the servers' attributes into a config object. There
* is one config object associated with each context found in
* step 1. (See aggregate_attrs())
* 5. Update the global config cache for each found context and its
* associated servers and attributes. (See update_config())
*
* The entry point for ldap_cachemgr is discover(). The actual entry
* point into the discovery routine is find_all_contexts(); the
* code thereafter is actually not specific to LDAP, and could also
* be used to discover YP, or any other server which conforms
* to the SLP Naming and Directory abstract service type.
*
* find_all_attributes() takes as parameters three callback routines
* which are used to report all information back to the caller. The
* signatures and synopses of these routines are:
*
* void *get_cfghandle(const char *domain);
*
* Returns an opaque handle to a configuration object specific
* to the 'domain' parameter. 'domain' will be a naming context
* string, i.e. foo.bar.sun.com ( i.e. a secure-RPC domain-
* name).
*
* void aggregate(void *handle, const char *tag, const char *value);
*
* Adds this tag / value pair to the set of aggregated attributes
* associated with the given handle.
*
* void set_cfghandle(void *handle);
*
* Sets and destroys the config object; SLP will no longer attempt
* to use this handle after this call. Thus, this call marks the
* end of configuration information for this handle.
*/
#include <stdio.h>
#include <slp.h>
#include <stdlib.h>
#include <string.h>
#include <door.h>
#include <unistd.h>
#include "ns_sldap.h"
#include "ns_internal.h"
#include "cachemgr.h"
#define ABSTYPE "service:naming-directory"
#define CONTEXT_ATTR "naming-context"
#define LDAP_DOMAIN_ATTR "x-sun-rpcdomain"
/* The configuration cookie passed along through all SLP callbacks. */
struct config_cookie {
SLPHandle h; /* An open SLPHandle */
const char *type; /* The full service type to use */
char *scopes; /* A list of scopes to use */
const char *context_attr; /* Which attr to use for the ctx */
void *cache_cfg; /* caller-supplied config object */
void *(*get_cfghandle)(const char *);
void (*aggregate)(void *, const char *, const char *);
void (*set_cfghandle)(void *);
};
extern admin_t current_admin; /* ldap_cachemgr's admin struct */
/*
* Utility routine: getlocale():
* Returns the locale specified by the SLP locale property, or just
* returns the default SLP locale if the property was not set.
*/
static const char *getlocale() {
const char *locale = SLPGetProperty("net.slp.locale");
return (locale ? locale : "en");
}
/*
* Utility routine: next_attr():
* Parses an SLP attribute string. On the first call, *type
* must be set to 0, and *s_inout must point to the beginning
* of the attr string. The following results are possible:
*
* If the term is of the form 'tag' only, *t_inout is set to tag,
* and *v_inout is set to NULL.
* If the term is of the form '(tag=val)', *t_inout and *v_inout
* are set to the tag and val strings, respectively.
* If the term is of the form '(tag=val1,val2,..,valN)', on each
* successive call, next_attr will return the next value. On the
* first invocation, tag is set to 'tag'; on successive invocations,
* tag is set to *t_inout.
*
* The string passed in *s_inout is destructively modified; all values
* returned simply point into the initial string. Hence the caller
* is responsible for all memory management. The type parameter is
* for internal use only and should be set to 0 by the caller only
* on the first invocation.
*
* If more attrs are available, returns SLP_TRUE, otherwise returns
* SLP_FALSE. If SLP_FALSE is returned, all value-result parameters
* will be undefined, and should not be used.
*/
static SLPBoolean next_attr(char **t_inout, char **v_inout,
char **s_inout, int *type) {
char *end = NULL;
char *tag = NULL;
char *val = NULL;
char *state = NULL;
if (!t_inout || !v_inout)
return (SLP_FALSE);
if (!s_inout || !*s_inout || !**s_inout)
return (SLP_FALSE);
state = *s_inout;
/* type: 0 = start, 1 = '(tag=val)' type, 2 = 'tag' type */
switch (*type) {
case 0:
switch (*state) {
case '(':
*type = 1;
break;
case ',':
state++;
*type = 0;
break;
default:
*type = 2;
}
*s_inout = state;
return (next_attr(t_inout, v_inout, s_inout, type));
break;
case 1:
switch (*state) {
case '(':
/* start of attr of the form (tag=val[,val]) */
state++;
tag = state;
end = strchr(state, ')'); /* for sanity checking */
if (!end)
return (SLP_FALSE); /* fatal parse error */
state = strchr(tag, '=');
if (state) {
if (state > end)
return (SLP_FALSE); /* fatal parse err */
*state++ = 0;
} else {
return (SLP_FALSE); /* fatal parse error */
}
/* fallthru to default case, which handles multivals */
default:
/* somewhere in a multivalued attr */
if (!end) { /* did not fallthru from '(' case */
tag = *t_inout; /* leave tag as it was */
end = strchr(state, ')');
if (!end)
return (SLP_FALSE); /* fatal parse error */
}
val = state;
state = strchr(val, ','); /* is this attr multivalued? */
if (!state || state > end) {
/* no, so skip to the next attr */
state = end;
*type = 0;
} /* else attr is multivalued */
*state++ = 0;
break;
}
break;
case 2:
/* attr term with tag only */
tag = state;
state = strchr(tag, ',');
if (state) {
*state++ = 0;
}
val = NULL;
*type = 0;
break;
default:
return (SLP_FALSE);
}
*t_inout = tag;
*v_inout = val;
*s_inout = state;
return (SLP_TRUE);
}
/*
* The SLP callback routine for foreach_server(). Aggregates each
* server's attributes into the caller-specified config object.
*/
/*ARGSUSED*/
static SLPBoolean aggregate_attrs(SLPHandle h, const char *attrs_in,
SLPError errin, void *cookie) {
char *tag, *val, *state;
char *unesc_tag, *unesc_val;
int type = 0;
char *attrs;
SLPError err;
struct config_cookie *cfg = (struct config_cookie *)cookie;
if (errin != SLP_OK) {
return (SLP_TRUE);
}
attrs = strdup(attrs_in);
state = attrs;
while (next_attr(&tag, &val, &state, &type)) {
unesc_tag = unesc_val = NULL;
if (tag) {
if ((err = SLPUnescape(tag, &unesc_tag, SLP_TRUE)) != SLP_OK) {
unesc_tag = NULL;
if (current_admin.debug_level >= DBG_ALL) {
(void) logit("aggregate_attrs: ",
"could not unescape attr tag %s:%s\n",
tag, slp_strerror(err));
}
}
}
if (val) {
if ((err = SLPUnescape(val, &unesc_val, SLP_FALSE))
!= SLP_OK) {
unesc_val = NULL;
if (current_admin.debug_level >= DBG_ALL) {
(void) logit("aggregate_attrs: ",
"could not unescape attr val %s:%s\n",
val, slp_strerror(err));
}
}
}
if (current_admin.debug_level >= DBG_ALL) {
(void) logit("discovery:\t\t%s=%s\n",
(unesc_tag ? unesc_tag : "NULL"),
(unesc_val ? unesc_val : "NULL"));
}
cfg->aggregate(cfg->cache_cfg, unesc_tag, unesc_val);
if (unesc_tag) free(unesc_tag);
if (unesc_val) free(unesc_val);
}
if (attrs) free(attrs);
return (SLP_TRUE);
}
/*
* The SLP callback routine for update_config(). For each
* server found, retrieves that server's attributes.
*/
/*ARGSUSED*/
static SLPBoolean foreach_server(SLPHandle hin, const char *u,
unsigned short life,
SLPError errin, void *cookie) {
SLPError err;
struct config_cookie *cfg = (struct config_cookie *)cookie;
SLPHandle h = cfg->h; /* an open handle */
SLPSrvURL *surl = NULL;
char *url = NULL;
if (errin != SLP_OK) {
return (SLP_TRUE);
}
/* dup url so we can slice 'n dice */
if (!(url = strdup(u))) {
(void) logit("foreach_server: no memory");
return (SLP_FALSE);
}
if ((err = SLPParseSrvURL(url, &surl)) != SLP_OK) {
free(url);
if (current_admin.debug_level >= DBG_NETLOOKUPS) {
(void) logit("foreach_server: ",
"dropping unparsable URL %s: %s\n",
url, slp_strerror(err));
return (SLP_TRUE);
}
}
if (current_admin.debug_level >= DBG_ALL) {
(void) logit("discovery:\tserver: %s\n", surl->s_pcHost);
}
/* retrieve all attrs for this server */
err = SLPFindAttrs(h, u, cfg->scopes, "", aggregate_attrs, cookie);
if (err != SLP_OK) {
if (current_admin.debug_level >= DBG_NETLOOKUPS) {
(void) logit("foreach_server: FindAttrs failed: %s\n",
slp_strerror(err));
}
goto cleanup;
}
/* add this server and its attrs to the config object */
cfg->aggregate(cfg->cache_cfg, "_,_xservers_,_", surl->s_pcHost);
cleanup:
if (url) free(url);
if (surl) SLPFree(surl);
return (SLP_TRUE);
}
/*
* This routine does the dirty work of finding all servers for a
* given domain and injecting this information into the caller's
* configuration namespace via callbacks.
*/
static void update_config(const char *context, struct config_cookie *cookie) {
SLPHandle h = NULL;
SLPHandle persrv_h = NULL;
SLPError err;
char *search = NULL;
char *unesc_domain = NULL;
/* Unescape the naming context string */
if ((err = SLPUnescape(context, &unesc_domain, SLP_FALSE)) != SLP_OK) {
if (current_admin.debug_level >= DBG_ALL) {
(void) logit("update_config: ",
"dropping unparsable domain: %s: %s\n",
context, slp_strerror(err));
}
return;
}
cookie->cache_cfg = cookie->get_cfghandle(unesc_domain);
/* Open a handle which all attrs calls can use */
if ((err = SLPOpen(getlocale(), SLP_FALSE, &persrv_h)) != SLP_OK) {
if (current_admin.debug_level >= DBG_NETLOOKUPS) {
(void) logit("update_config: SLPOpen failed: %s\n",
slp_strerror(err));
}
goto cleanup;
}
cookie->h = persrv_h;
if (current_admin.debug_level >= DBG_ALL) {
(void) logit("discovery: found naming context %s\n", context);
}
/* (re)construct the search filter form the input context */
search = malloc(strlen(cookie->context_attr) +
strlen(context) +
strlen("(=)") + 1);
if (!search) {
(void) logit("update_config: no memory\n");
goto cleanup;
}
(void) sprintf(search, "(%s=%s)", cookie->context_attr, context);
/* Find all servers which serve this context */
if ((err = SLPOpen(getlocale(), SLP_FALSE, &h)) != SLP_OK) {
if (current_admin.debug_level >= DBG_NETLOOKUPS) {
(void) logit("upate_config: SLPOpen failed: %s\n",
slp_strerror(err));
}
goto cleanup;
}
err = SLPFindSrvs(h, cookie->type, cookie->scopes,
search, foreach_server, cookie);
if (err != SLP_OK) {
if (current_admin.debug_level >= DBG_NETLOOKUPS) {
(void) logit("update_config: SLPFindSrvs failed: %s\n",
slp_strerror(err));
}
goto cleanup;
}
/* update the config cache with the new info */
cookie->set_cfghandle(cookie->cache_cfg);
cleanup:
if (h) SLPClose(h);
if (persrv_h) SLPClose(persrv_h);
if (search) free(search);
if (unesc_domain) free(unesc_domain);
}
/*
* The SLP callback routine for find_all_contexts(). For each context
* found, finds all the servers and their attributes.
*/
/*ARGSUSED*/
static SLPBoolean foreach_context(SLPHandle h, const char *attrs_in,
SLPError err, void *cookie) {
char *attrs, *tag, *val, *state;
int type = 0;
if (err != SLP_OK) {
return (SLP_TRUE);
}
/*
* Parse out each context. Attrs will be of the following form:
* (naming-context=dc\3deng\2c dc\3dsun\2c dc\3dcom)
* Note that ',' and '=' are reserved in SLP, so they are escaped.
*/
attrs = strdup(attrs_in); /* so we can slice'n'dice */
if (!attrs) {
(void) logit("foreach_context: no memory\n");
return (SLP_FALSE);
}
state = attrs;
while (next_attr(&tag, &val, &state, &type)) {
update_config(val, cookie);
}
free(attrs);
return (SLP_TRUE);
}
/*
* Initiates server and attribute discovery for the concrete type
* 'type'. Currently the only useful type is "ldap", but perhaps
* "nis" and "nisplus" will also be useful in the future.
*
* get_cfghandle, aggregate, and set_cfghandle are callback routines
* used to pass any discovered configuration information back to the
* caller. See the introduction at the top of this file for more info.
*/
static void find_all_contexts(const char *type,
void *(*get_cfghandle)(const char *),
void (*aggregate)(
void *, const char *, const char *),
void (*set_cfghandle)(void *)) {
SLPHandle h = NULL;
SLPError err;
struct config_cookie cookie[1];
char *fulltype = NULL;
char *scope = (char *)SLPGetProperty("net.slp.useScopes");
if (!scope || !*scope) {
scope = "default";
}
/* construct the full type from the partial type parameter */
fulltype = malloc(strlen(ABSTYPE) + strlen(type) + 2);
if (!fulltype) {
(void) logit("find_all_contexts: no memory");
goto done;
}
(void) sprintf(fulltype, "%s:%s", ABSTYPE, type);
/* set up the cookie for this discovery operation */
memset(cookie, 0, sizeof (*cookie));
cookie->type = fulltype;
cookie->scopes = scope;
if (strcasecmp(type, "ldap") == 0) {
/* Sun LDAP is special */
cookie->context_attr = LDAP_DOMAIN_ATTR;
} else {
cookie->context_attr = CONTEXT_ATTR;
}
cookie->get_cfghandle = get_cfghandle;
cookie->aggregate = aggregate;
cookie->set_cfghandle = set_cfghandle;
if ((err = SLPOpen(getlocale(), SLP_FALSE, &h)) != SLP_OK) {
if (current_admin.debug_level >= DBG_CANT_FIND) {
(void) logit("discover: %s",
"Aborting discovery: SLPOpen failed: %s\n",
slp_strerror(err));
}
goto done;
}
/* use find attrs to get a list of all available contexts */
err = SLPFindAttrs(h, fulltype, scope, cookie->context_attr,
foreach_context, cookie);
if (err != SLP_OK) {
if (current_admin.debug_level >= DBG_CANT_FIND) {
(void) logit(
"discover: Aborting discovery: SLPFindAttrs failed: %s\n",
slp_strerror(err));
}
goto done;
}
done:
if (h) SLPClose(h);
if (fulltype) free(fulltype);
}
/*
* This is the ldap_cachemgr entry point into SLP dynamic discovery. The
* parameter 'r' should be a pointer to an unsigned int containing
* the requested interval at which the network should be queried.
*/
void discover(void *r) {
unsigned short reqrefresh = *((unsigned int *)r);
for (;;) {
find_all_contexts("ldap",
__cache_get_cfghandle,
__cache_aggregate_params,
__cache_set_cfghandle);
if (current_admin.debug_level >= DBG_ALL) {
(void) logit(
"dynamic discovery: using refresh interval %d\n",
reqrefresh);
}
(void) sleep(reqrefresh);
}
}
#endif /* SLP */