idmap_config.c revision 3f1de28d23e9724b017260ef6b282b278b6e38f9
/*
* 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
* 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 2011 Nexenta Systems, Inc. All rights reserved.
*/
/*
* Config routines common to idmap(1M) and idmapd(1M)
*/
#include <stdlib.h>
#include <strings.h>
#include <libintl.h>
#include <ctype.h>
#include <errno.h>
#include "idmapd.h"
#include <stdio.h>
#include <stdarg.h>
#include <pthread.h>
#include <port.h>
#include <sys/u8_textprep.h>
#include <note.h>
#include "addisc.h"
#define CONFIG_PG "config"
#define DEBUG_PG "debug"
#define RECONFIGURE 1
#define POKE_AUTO_DISCOVERY 2
/*
* Default cache timeouts. Can override via svccfg
* config/id_cache_timeout = count: seconds
* config/name_cache_timeout = count: seconds
*/
#define ID_CACHE_TMO_DEFAULT 86400
#define NAME_CACHE_TMO_DEFAULT 604800
enum event_type {
EVENT_NOTHING, /* Woke up for no good reason */
EVENT_TIMEOUT, /* Timeout expired */
EVENT_ROUTING, /* An interesting routing event happened */
EVENT_DEGRADE, /* An error occurred in the mainline */
EVENT_REFRESH, /* SMF refresh */
};
static pthread_t update_thread_handle = 0;
static int idmapd_ev_port = -1;
static int rt_sock = -1;
struct enum_lookup_map directory_mapping_map[] = {
{ DIRECTORY_MAPPING_NONE, "none" },
{ DIRECTORY_MAPPING_NAME, "name" },
{ DIRECTORY_MAPPING_IDMU, "idmu" },
{ 0, NULL },
};
struct enum_lookup_map trust_dir_map[] = {
{ 1, "they trust us" },
{ 2, "we trust them" },
{ 3, "we trust each other" },
{ 0, NULL },
};
static int
generate_machine_sid(char **machine_sid)
{
char *p;
/*
* Generate and split 128-bit UUID into three 32-bit RIDs The
* machine_sid will be of the form S-1-5-21-N1-N2-N3 (that's
* four RIDs altogether).
*
* Technically we could use up to 14 random RIDs here, but it
* turns out that with some versions of Windows using SIDs with
* more than five RIDs in security descriptors causes problems.
*/
if (*machine_sid == NULL) {
return (-1);
}
uuid_clear(uu);
#if UUID_LEN != 16
#endif
for (i = 0; i < 3; i++) {
j = i * 4;
p += rlen;
}
return (0);
}
/* In the case of error, exists is set to FALSE anyway */
static int
{
scf_strerror(scf_error()));
return (-1);
}
return (0);
}
static int
{
scf_strerror(scf_error()));
abort();
}
scf_strerror(scf_error()));
abort();
}
/* this is OK: the property is just undefined */
goto destruction;
}
/* It is still OK when a property doesn't have any value */
goto destruction;
}
abort();
}
return ((int)i64);
}
static int
{
int rc = 0;
*val = default_val;
scf_strerror(scf_error()));
return (-1);
}
scf_strerror(scf_error()));
return (-1);
}
/* It is OK if the property is undefined */
goto destruction;
/* It is still OK when a property doesn't have any value */
goto destruction;
uint8_t b;
if (rc == 0)
return (rc);
}
static int
{
int rc = 0;
switch (type) {
case SCF_TYPE_COUNT:
break;
case SCF_TYPE_INTEGER:
break;
default:
type);
abort();
}
scf_strerror(scf_error()));
return (-1);
}
scf_strerror(scf_error()));
return (-1);
}
/* this is OK: the property is just undefined */
goto destruction;
/* It is still OK when a property doesn't have any value */
goto destruction;
switch (type) {
case SCF_TYPE_COUNT:
break;
case SCF_TYPE_INTEGER:
break;
default:
abort(); /* tested above */
/* NOTREACHED */
}
if (rc != 0) {
}
return (rc);
}
static char *
{
if (max_val == 0)
return (NULL);
}
if (s == NULL)
return (s);
}
static int
{
int len, i;
int count = 0;
int rc = -1;
scf_strerror(scf_error()));
return (-1);
}
scf_strerror(scf_error()));
return (-1);
}
scf_strerror(scf_error()));
return (-1);
}
/* this is OK: the property is just undefined */
rc = 0;
goto destruction;
}
"scf_iter_property_values(%s) failed: %s",
goto destruction;
}
/* Workaround scf bugs -- can't reset an iteration */
if (count == 0) {
count++;
if (count == 0) {
/* no values */
rc = 0;
goto destruction;
}
goto restart;
}
goto destruction;
}
i = 0;
goto destruction;
}
*portstr++ = '\0';
(char **)NULL, 10);
}
/* Ignore this server if the hostname is too long */
i++;
}
rc = 0;
if (rc < 0) {
if (servers)
}
return (rc);
}
static int
{
int rc = 0;
scf_strerror(scf_error()));
return (-1);
}
scf_strerror(scf_error()));
return (-1);
}
/* this is OK: the property is just undefined */
goto destruction;
"scf_property_get_value(%s) failed: %s",
rc = -1;
goto destruction;
}
rc = -1;
if (rc < 0) {
if (*val)
}
return (rc);
}
static int
const char *name)
{
int rc = -1;
int ret;
"scf_transaction_create() failed: %s",
scf_strerror(scf_error()));
goto destruction;
}
"scf_entry_create() failed: %s",
scf_strerror(scf_error()));
goto destruction;
}
do {
"scf_pg_update(%s) failed: %s",
goto destruction;
}
"scf_transaction_start(%s) failed: %s",
goto destruction;
}
/* Don't complain if it already doesn't exist. */
if (scf_error() != SCF_ERROR_NOT_FOUND) {
"scf_transaction_property_delete() failed:"
" %s",
scf_strerror(scf_error()));
}
goto destruction;
}
if (ret == 0)
} while (ret == 0);
if (ret == -1) {
"scf_transaction_commit(%s) failed: %s",
goto destruction;
}
rc = 0;
return (rc);
}
static int
const char *name,
{
int rc = -1;
int i;
goto destruction;
}
for (i = 0; i < MAX_TRIES; i++) {
int ret;
"scf_pg_update() failed: %s",
scf_strerror(scf_error()));
goto destruction;
}
"scf_transaction_start(%s) failed: %s",
goto destruction;
}
if (ret == SCF_SUCCESS) {
scf_value_type(value)) < 0) {
"scf_transaction_property_change_type(%s)"
" failed: %s",
goto destruction;
}
} else if (scf_error() == SCF_ERROR_NOT_FOUND) {
scf_value_type(value)) < 0) {
"scf_transaction_property_new() failed: %s",
scf_strerror(scf_error()));
goto destruction;
}
} else {
"scf_pg_get_property(%s) failed: %s",
goto destruction;
}
"scf_entry_add_value() failed: %s",
scf_strerror(scf_error()));
goto destruction;
}
if (ret == 0) {
/*
* Property group set in scf_transaction_start()
* is not the most recent. Update pg, reset tx and
* retry tx.
*/
"scf_transaction_commit(%s) failed: %s",
continue;
}
if (ret != 1) {
"scf_transaction_commit(%s) failed: %s",
goto destruction;
}
/* Success! */
rc = 0;
break;
}
return (rc);
}
static int
const char *name,
{
int rc;
return (-1);
}
return (rc);
}
static int
const char *name,
const char *val)
{
int rc = -1;
goto out;
}
"scf_value_set_astring() failed: %s",
scf_strerror(scf_error()));
goto out;
}
out:
return (rc);
}
/*
* This function updates a boolean value.
* If nothing has changed it returns 0 else 1
*/
static int
{
return (0);
}
return (1);
}
/*
* This function updates a uint64_t value.
* If nothing has changed it returns 0 else 1
*/
static int
{
return (0);
return (1);
}
/*
* This function updates a string value.
* If nothing has changed it returns 0 else 1
*/
static int
{
int changed;
changed = 1;
changed = 1;
changed = 1;
else
changed = 0;
/*
* Note that even if unchanged we can't just return; we must free one
* of the values.
*/
return (changed);
}
static int
{
return (0);
}
return (1);
}
/*
* This function updates a directory service structure.
* If nothing has changed it returns 0 else 1
*/
static int
{
int i;
/* Nothing to do */
return (0);
return (0);
}
/* We're unsetting this DS property */
return (1);
}
/* List all the new DSs */
}
}
return (1);
}
/*
* This function updates a trusted domains structure.
* If nothing has changed it returns 0 else 1
*/
static int
{
int i;
/* Nothing to do */
return (0);
return (0);
}
/* We're unsetting this DS property */
return (1);
}
/* List all the new domains */
}
}
return (1);
}
/*
* This function updates a domains in a forest structure.
* If nothing has changed it returns 0 else 1
*/
static int
{
int i;
/* Nothing to do */
return (0);
return (0);
}
/* We're unsetting this DS property */
return (1);
}
/* List all the new domains */
}
}
return (1);
}
static void
{
int i;
for (i = 0; i < *num_values; i++) {
}
*num_values = 0;
}
static int
{
int i, j;
int num_df1 = 0;
int num_df2 = 0;
num_df1++;
num_df2++;
return (1);
break;
}
}
if (!match)
return (1);
}
}
return (0);
}
/*
* This function updates trusted forest structure.
* If nothing has changed it returns 0 else 1
*/
static int
{
int i, j;
/* Nothing to do */
return (0);
goto not_equal;
for (i = 0; i < *num_value; i++) {
for (j = 0; j < *num_new; j++) {
(*new)[j].forest_name) == 0 &&
(*value)[i].global_catalog,
(*new)[j].global_catalog) == 0 &&
(*value)[i].domains_in_forest,
(*new)[j].domains_in_forest) == 0) {
break;
}
}
if (!match)
goto not_equal;
}
return (0);
}
*num_new = 0;
/* We're unsetting this DS property */
return (1);
}
/* List all the trusted forests */
for (i = 0; i < *num_value; i++) {
idmap_trustedforest_t *f = &(*value)[i];
for (j = 0;
j++) {
/* List trusted Domains in the forest. */
if (f->domains_in_forest[j].trusted)
"change %s=%s domain=%s",
name, f->forest_name,
f->domains_in_forest[j].domain);
}
/* List the hosts */
for (j = 0;
j++) {
"change %s=%s host=%s port=%d",
name, f->forest_name,
f->global_catalog[j].host,
f->global_catalog[j].port);
}
}
}
return (1);
}
const char *
{
}
}
return ("(invalid)");
}
/*
* Returns 1 if the PF_ROUTE socket event indicates that we should rescan the
* interfaces.
*
* Shamelessly based on smb_nics_changed() and other PF_ROUTE uses in ON.
*/
static
{
int nbytes;
for (;;) {
break;
continue;
continue;
case RTM_NEWADDR:
case RTM_DELADDR:
case RTM_IFINFO:
break;
default:
break;
}
}
return (is_interesting);
}
/*
* Wait for an event, and report what kind of event occurred.
*
* Note that there are cases where we are awoken but don't care about
* the lower-level event. We can't just loop here because we can't
* readily calculate how long to sleep the next time. We return
* EVENT_NOTHING and let the caller loop.
*/
static
enum event_type
{
switch (errno) {
case EINTR:
return (EVENT_NOTHING);
case ETIME:
/* Timeout */
return (EVENT_TIMEOUT);
default:
/* EBADF, EBADFD, EFAULT, EINVAL (end of time?)? */
exit(1);
/* NOTREACHED */
}
}
switch (pe.portev_source) {
case 0:
/*
* This isn't documented, but seems to be what you get if
* the timeout is zero seconds and there are no events
* pending.
*/
return (EVENT_TIMEOUT);
case PORT_SOURCE_USER:
return (EVENT_DEGRADE);
return (EVENT_REFRESH);
break;
case PORT_SOURCE_FD:
/*
* PF_ROUTE socket read event:
* re-associate fd
* handle event
*/
"routing socket with the event port: %s",
abort();
}
/*
* The network configuration may still be in flux.
* No matter, the resolver will re-transmit and
* timeout if need be.
*/
if (pfroute_event_is_interesting(rt_sock)) {
"Interesting routing event");
}
return (EVENT_ROUTING);
} else {
"Boring routing event");
}
return (EVENT_NOTHING);
}
}
/* Event on an FD other than the routing FD? Ignore it. */
break;
}
return (EVENT_NOTHING);
}
void *
idmap_cfg_update_thread(void *arg)
{
for (;;) {
int rc;
int ttl;
(void) ad_disc_SubnetChanged(ad_ctx);
if (rc < -1) {
"SMF properties");
exit(1);
} else if (rc == -1) {
"Errors re-loading configuration may cause AD "
"lookups to fail");
}
/*
* Wait for an interesting event. Note that we might get
* boring events between interesting events. If so, we loop.
*/
for (;;) {
if (ttl < 0) {
} else {
}
switch (wait_for_event(timeoutp)) {
case EVENT_NOTHING:
continue;
case EVENT_REFRESH:
/*
* Blow away the ccache, we might have
* re-joined the domain or joined a new one
*/
break;
case EVENT_DEGRADE:
"Service degraded");
}
break;
case EVENT_TIMEOUT:
break;
case EVENT_ROUTING:
/* Already logged to DEBUG */
break;
}
/* An interesting event! */
break;
}
}
/*
* Lint isn't happy with the concept of a function declared to
* return something, that doesn't return. Of course, merely adding
* the return isn't enough, because it's never reached...
*/
/*NOTREACHED*/
return (NULL);
}
int
idmap_cfg_start_updates(void)
{
if ((idmapd_ev_port = port_create()) < 0) {
return (-1);
}
(void) close(idmapd_ev_port);
return (-1);
}
(void) close(idmapd_ev_port);
return (-1);
}
(void) close(idmapd_ev_port);
return (-1);
}
idmap_cfg_update_thread, NULL)) != 0) {
(void) close(idmapd_ev_port);
return (-1);
}
return (0);
}
/*
* Reject attribute names with invalid characters.
*/
static
int
valid_ldap_attr(const char *attr) {
return (0);
}
return (1);
}
static
void
enum idmapd_debug item,
const char *name)
{
int val;
return;
}
static
void
{
}
/*
* This is the half of idmap_cfg_load() that loads property values from
* SMF (using the config/ property group of the idmap FMRI).
*
* Return values: 0 -> success, -1 -> failure, -2 -> hard failures
* -3 -> hard smf config failures
* reading from SMF.
*/
static
int
int * const errors)
{
int rc;
char *s;
*errors = 0;
scf_strerror(scf_error()));
return (-2);
}
scf_strerror(scf_error()));
return (-2);
}
if (rc != 0)
(*errors)++;
if (rc != 0)
(*errors)++;
if (rc != 0)
(*errors)++;
if (rc != 0)
(*errors)++;
else if (strcasecmp(s, "name") == 0)
else if (strcasecmp(s, "idmu") == 0)
else {
"config/directory_based_mapping: invalid value \"%s\" ignored",
s);
(*errors)++;
}
free(s);
if (rc != 0)
(*errors)++;
if (rc != 0)
(*errors)++;
if (pgcfg->id_cache_timeout == 0)
if (rc != 0)
(*errors)++;
if (pgcfg->name_cache_timeout == 0)
&pgcfg->domain_name);
if (rc != 0)
(*errors)++;
else {
}
pgcfg->domain_name);
}
&pgcfg->default_domain);
if (rc != 0) {
/*
* SCF failures fetching config/default_domain we treat
* as fatal as they may leave ID mapping rules that
* match unqualified winnames flapping in the wind.
*/
return (-2);
}
}
if (rc != 0)
(*errors)++;
/* If machine_sid not configured, generate one */
return (-2);
if (rc != 0)
(*errors)++;
}
if (rc != 0)
(*errors)++;
else {
}
if (rc != 0)
(*errors)++;
else {
pgcfg->forest_name);
}
if (rc != 0)
(*errors)++;
else
&pgcfg->global_catalog);
if (rc != 0)
(*errors)++;
else {
}
/* Unless we're doing directory-based name mapping, we're done. */
return (0);
if (rc != 0)
return (-2);
return (-3);
}
if (rc != 0)
return (-2);
return (-3);
}
if (rc != 0)
return (-2);
return (-3);
}
"If config/directory_based_mapping property is set to "
"\"name\" then at least one of the following name mapping "
"attributes must be specified. (config/ad_unixuser_attr OR "
return (-3);
}
return (rc);
}
static
void
{
}
}
static
void
{
int i, j, k, l;
char *forestname;
int num_trusteddomains;
char *trusteddomain;
/*
* We have trusted domains. We need to go through every
* one and find its forest. If it is a new forest we then need
* to find its Global Catalog and the domains in the forest
*/
continue;
num_trusteddomains = i;
sizeof (idmap_trustedforest_t));
j = 0;
trusted_ctx = ad_disc_init();
(void) ad_disc_set_DomainName(trusted_ctx,
if (forestname == NULL) {
"unable to discover Forest Name"
" for the trusted domain %s",
}
continue;
}
/*
* Ignore the domain as it is part of
* the primary forest
*/
continue;
}
/* Is this a new forest? */
new_forest = B_TRUE;
for (k = 0; k < j; k++) {
if (strcasecmp(forestname,
trustedforests[k].forest_name) == 0) {
break;
}
}
if (!new_forest) {
/* Mark the domain as trusted */
for (l = 0;
if (domain_eq(trusteddomain,
domainsinforest[l].domain)) {
domainsinforest[l].trusted =
TRUE;
break;
}
}
continue;
}
/*
* Get the Global Catalog and the domains in
* this new forest.
*/
if (globalcatalog == NULL) {
"unable to discover Global Catalog"
" for the trusted domain %s",
}
continue;
}
NULL);
if (domainsinforest == NULL) {
"unable to discover Domains in the"
" Forest for the trusted domain %s",
}
continue;
}
j++;
/* Mark the domain as trusted */
l++) {
if (domain_eq(trusteddomain,
domainsinforest[l].domain)) {
break;
}
}
}
if (j > 0) {
pgcfg->num_trusted_forests = j;
} else {
}
}
}
/*
* This is the half of idmap_cfg_load() that auto-discovers values of
* discoverable properties that weren't already set via SMF properties.
*
* idmap_cfg_discover() is called *after* idmap_cfg_load_smf(), so it
* needs to be careful not to overwrite any properties set in SMF.
*/
static
void
{
} else {
if (!pgcfg->disable_cross_forest_trusts)
"Domain Controller");
"Domains in the Forest");
if (!pgcfg->disable_cross_forest_trusts) {
"Trusted Domains");
}
}
}
}
/*
* idmap_cfg_load() is called at startup, and periodically via the
* update thread when the auto-discovery TTLs expire, as well as part of
* the refresh method, to update the current configuration. It always
* reads from SMF, but you still have to refresh the service after
* changing the config pg in order for the changes to take effect.
*
* There is one flag:
*
* - CFG_DISCOVER
*
* If CFG_DISCOVER is set then idmap_cfg_load() calls
* idmap_cfg_discover() to discover, via DNS and LDAP lookups, property
* values that weren't set in SMF.
*
* idmap_cfg_load() will log (to LOG_NOTICE) whether the configuration
* changed.
*
* Return values: 0 -> success, -1 -> failure, -2 -> hard failures
* reading from SMF.
*/
int
{
int rc = 0;
int errors;
int changed = 0;
int ad_reload_required = 0;
goto err;
if (flags & CFG_DISCOVER)
/* Non-discoverable props updated here */
"disable_cross_forest_trusts");
/* Props that can be discovered and set in SMF updated here */
changed++;
}
changed++;
}
changed++;
}
changed++;
}
if (ad_reload_required)
reload_ad();
if (changed)
else
}
err:
if (rc < -1)
return (rc);
return ((errors == 0) ? 0 : -1);
}
/*
* Initialize 'cfg'.
*/
{
/* First the smf repository handles: */
if (!cfg) {
return (NULL);
}
scf_strerror(scf_error()));
goto error;
}
scf_strerror(scf_error()));
goto error;
}
scf_strerror(scf_error()));
goto error;
}
NULL, /* scope */
NULL, /* prop */
SCF_DECODE_FMRI_EXACT) < 0) {
scf_strerror(scf_error()));
goto error;
}
goto error;
}
/* Initialize AD Auto Discovery context */
goto error;
return (cfg);
(void) idmap_cfg_fini(cfg);
return (NULL);
}
void
{
if (pgcfg->default_domain) {
}
if (pgcfg->domain_name) {
}
if (pgcfg->machine_sid) {
}
if (pgcfg->domain_controller) {
}
if (pgcfg->forest_name) {
}
}
if (pgcfg->global_catalog) {
}
if (pgcfg->trusted_domains) {
}
if (pgcfg->trusted_forests)
if (pgcfg->ad_unixuser_attr) {
}
if (pgcfg->ad_unixgroup_attr) {
}
if (pgcfg->nldap_winname_attr) {
}
}
int
{
return (0);
}
void
idmap_cfg_poke_updates(void)
{
if (idmapd_ev_port != -1)
}
/*ARGSUSED*/
void
idmap_cfg_hup_handler(int sig)
{
if (idmapd_ev_port >= 0)
}
/*
* Upgrade the debug flags.
*
* We're replacing a single debug flag with a fine-grained mechanism that
* is also capable of considerably more verbosity. We'll take a stab at
* producing roughly the same level of output.
*/
static
int
{
const char DEBUG_PROP[] = "debug";
int rc;
if (rc != 0)
return (rc);
if (!debug_present)
return (0);
"Upgrading old %s/%s setting to %s/* settings.",
if (rc != 0)
return (rc);
if (rc != 0)
return (rc);
if (rc != 0)
return (rc);
return (0);
}
/*
* Upgrade the DS mapping flags.
*
* If the old ds_name_mapping_enabled flag is present, then
* if the new directory_based_mapping value is present, then
* if the two are compatible, delete the old and note it
* else delete the old and warn
* else
* set the new based on the old, and note it
* delete the old
*/
static
int
{
const char DS_NAME_MAPPING_ENABLED[] = "ds_name_mapping_enabled";
const char DIRECTORY_BASED_MAPPING[] = "directory_based_mapping";
int rc;
if (rc != 0)
return (rc);
return (0);
if (rc != 0)
return (rc);
char *legacy_mode;
char *legacy_bool_string;
legacy_mode = "name";
legacy_bool_string = "true";
} else {
legacy_mode = "none";
legacy_bool_string = "false";
}
char *directory_based_mapping;
if (rc != 0)
return (rc);
if (directory_based_mapping == NULL) {
"Upgrading old %s=%s setting\n"
"to %s=%s.",
if (rc != 0)
return (rc);
} else {
else
"Automatically removing old %s=%s setting\n"
"in favor of %s=%s.",
} else {
"Removing conflicting %s=%s setting\n"
"in favor of %s=%s.",
}
}
if (rc != 0)
return (rc);
return (0);
}
/*
* Do whatever is necessary to upgrade idmap's configuration before
* we load it.
*/
int
{
int rc;
if (rc != 0)
return (rc);
if (rc != 0)
return (rc);
return (0);
}