addisc.c revision 7a8a68f5e3efbaec1a375c2d50bd20b566631755
/*
* 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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Active Directory Auto-Discovery.
*
* This [project private] API allows the caller to provide whatever
* details it knows a priori (i.e., provided via configuration so as to
* override auto-discovery) and in any order. Then the caller can ask
* for any of the auto-discoverable parameters in any order.
*
* But there is an actual order in which discovery must be done. Given
* the discovery mechanism implemented here, that order is:
*
* - the domain name joined must be discovered first
* - then the domain controllers
* - then the forest name and site name
* - then the global catalog servers, and site-specific domain
* controllers and global catalog servers.
*
* The API does not require it be called in the same order because there
* may be other discovery mechanisms in the future, and exposing
* ordering requirements of the current mechanism now can create trouble
* down the line. Also, this makes the API easier to use now, which
* means less work to do some day when we make this a public API.
*
* Domain discovery is done by res_nsearch() of the DNS SRV RR name for
* domain controllers. As long as the joined domain appears in the DNS
* resolver's search list then we'll find it.
*
* Domain controller discovery is a matter of formatting the DNS SRV RR
* FQDN for domain controllers and doing a lookup for them. Knowledge
* of the domain name is not fundamentally required, but we separate the
* two processes, which in practice can lead to one more DNS lookup than
* is strictly required.
*
* Forest and site name discovery require an LDAP search of the AD
* "configuration partition" at a domain controller for the joined
* domain. Forest and site name discovery depend on knowing the joined
* domain name and domain controllers for that domain.
*
* Global catalog server discovery requires knowledge of the forest
* name in order to format the DNS SRV RR FQDN to lookup. Site-specific
* domain controller discovery depends on knowing the site name (and,
* therefore, joined domain, ...). Site-specific global catalog server
* discovery depends on knowledge of the forest and site names, which
* depend on...
*
* All the work of discovering particular items is done by functions
* named validate_<item>(). Each such function calls validate_<item>()
* for any items that it depends on.
*
* This API is not thread-safe.
*/
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <assert.h>
#include <stdlib.h>
#include <resolv.h>
#include <netdb.h>
#include <ctype.h>
#include <errno.h>
#include <ldap.h>
#include <sys/u8_textprep.h>
#include <syslog.h>
#include "adutils_impl.h"
#include "addisc.h"
enum ad_item_state {
AD_STATE_INVALID = 0, /* The value is not valid */
AD_STATE_FIXED, /* The value was fixed by caller */
AD_STATE_AUTO /* The value is auto discovered */
};
enum ad_data_type {
AD_STRING = 123,
};
typedef struct ad_subnet {
char subnet[24];
} ad_subnet_t;
typedef struct ad_item {
enum ad_item_state state;
enum ad_data_type type;
void *value;
unsigned int version; /* Version is only changed */
/* if the value changes */
#define PARAM1 0
#define PARAM2 1
int param_version[2];
/* These holds the version of */
/* dependents so that a dependent */
/* change can be detected */
} ad_item_t;
typedef struct ad_disc {
struct __res_state res_state;
int res_ninitted;
/* port array */
/* port array */
/* array */
/* direction array */
/* Site specfic versions */
/* port array */
/* port array */
} ad_disc;
#define DNS_MAX_NAME NS_MAXDNAME
/* SRV RR names for various queries */
#define LDAP_SRV_HEAD "_ldap._tcp."
#define SITE_SRV_MIDDLE "%s._sites."
#define GC_SRV_TAIL "gc._msdcs"
#define DC_SRV_TAIL "dc._msdcs"
#define ALL_GC_SRV_TAIL "_gc._tcp"
#define PDC_SRV "_ldap._tcp.pdc._msdcs.%s"
/* A RR name for all GCs -- last resort this works */
#define GC_ALL_A_NAME_FSTR "gc._msdcs.%s."
/*
* We try res_ninit() whenever we don't have one. res_ninit() fails if
* idmapd is running before the network is up!
*/
/*LINTLIBRARY*/
/*
* Function definitions
*/
static ad_item_t *
static void
{
}
static boolean_t
{
return (B_TRUE);
return (B_TRUE);
}
return (B_FALSE);
}
static void
{
if (ttl == 0)
else
}
/* Compare DS lists */
int
{
int i, j;
int num_ds1;
int num_ds2;
continue;
num_ds1 = i;
continue;
num_ds2 = j;
return (1);
for (i = 0; i < num_ds1; i++) {
for (j = 0; j < num_ds2; j++) {
break;
}
}
if (!match)
return (1);
}
return (0);
}
/* Copy a list of DSs */
static idmap_ad_disc_ds_t *
{
int i;
int size;
continue;
return (new);
}
int
{
int i, j;
int num_td1;
int num_td2;
int err;
continue;
num_td1 = i;
continue;
num_td2 = j;
return (1);
for (i = 0; i < num_td1; i++) {
for (j = 0; j < num_td2; j++) {
err == 0) {
break;
}
}
if (!match)
return (1);
}
return (0);
}
/* Copy a list of Trusted Domains */
static ad_disc_trusteddomains_t *
{
int i;
int size;
continue;
return (new);
}
int
{
int i, j;
int num_df1;
int num_df2;
int err;
continue;
num_df1 = i;
continue;
num_df2 = j;
return (1);
for (i = 0; i < num_df1; i++) {
for (j = 0; j < num_df2; j++) {
err == 0 &&
break;
}
}
if (!match)
return (1);
}
return (0);
}
/* Copy a list of Trusted Domains */
static ad_disc_domainsinforest_t *
{
int i;
int size;
continue;
return (new);
}
/*
* The last subnet is NULL
*/
static ad_subnet_t *
{
int sock, n, i;
char *s;
return (NULL);
}
lifn.lifn_flags = 0;
"Failed to find the number of network interfaces (%s)",
return (NULL);
}
return (NULL);
}
lifc.lifc_flags = 0;
return (NULL);
}
return (NULL);
}
return (NULL);
}
continue;
continue;
continue;
s = inet_ntoa(((struct sockaddr_in *)
"%s/%d", s, prefix_len);
}
return (results);
}
static int
{
int num_subnets1;
int num_subnets2;
int i, j;
continue;
num_subnets1 = i;
continue;
num_subnets2 = i;
if (num_subnets1 != num_subnets2)
return (1);
for (i = 0; i < num_subnets1; i++) {
for (j = 0; j < num_subnets2; j++) {
break;
}
}
if (!matched)
return (1);
}
return (0);
}
/* Convert a DN's DC components into a DNS domainname */
char *
{
char dns[DNS_MAX_NAME];
char *dns_name;
int i, j;
int num = 0;
j = 0;
i = 0;
return (NULL);
/*
* Find all DC=<value> and form DNS name of the
* form <value1>.<value2>...
*/
while (dn_name[i] != '\0') {
i += 3;
dns[j++] = '.';
while (dn_name[i] != '\0' &&
num++;
} else {
/* Skip attr=value as it is not DC= */
while (dn_name[i] != '\0' &&
i++;
}
/* Skip over separator ',' or '+' */
if (dn_name[i] != '\0') i++;
}
dns[j] = '\0';
return (dns_name);
}
/* Format the DN of an AD LDAP subnet object for some subnet */
static char *
{
char *result;
int len;
"CN=%s,CN=Subnets,CN=Sites,%s",
"CN=%s,CN=Subnets,CN=Sites,%s",
return (result);
}
/* Make a list of subnet object DNs from a list of subnets */
static char **
{
char **results;
int i, j;
continue;
return (NULL);
== NULL) {
for (j = 0; j < i; j++)
return (NULL);
}
}
return (results);
}
/* Compare SRC RRs; used with qsort() */
static int
{
return (1);
return (-1);
return (1);
return (-1);
return (0);
}
/*
* Query or search the SRV RRs for a given name.
*
* If name == NULL then search (as in res_nsearch(3RESOLV), honoring any
*
* The output TTL will be the one of the SRV RR with the lowest TTL.
*/
{
union {
} msg;
/* LINTED E_FUNC_SET_NOT_USED */
char namebuf[NS_MAXDNAME];
return (NULL);
/* Set negative result TTL */
/* 1. query necessary resource records */
/* Search, querydomain or query */
svc_name);
if (len < 0) {
return (NULL);
}
"Querying DNS for SRV RRs named '%s' for '%s' ",
if (len < 0) {
"DNS query for '%s' for '%s' failed (%s)",
return (NULL);
}
}
" into %ib buffer",
return (NULL);
}
/* 2. parse the reply, skip header and question sections */
return (NULL);
}
}
/* 3. walk through the answer section */
sizeof (namebuf));
if (len < 0) {
return (NULL);
}
return (NULL);
}
continue;
}
if (len < 0) {
return (NULL);
}
/* 3. move ptr to the end of current record */
}
if (ancount > 1)
(int (*)(const void *, const void *))srvcmp);
return (srv_res);
}
/*
* A utility function to bind to a Directory server
*/
static LDAP*
{
int i;
int zero = 0;
char *saslmech = "GSSAPI";
"AD DC %s:%d (%s)",
continue;
}
&ldversion);
&timeoutms);
NULL /* defaults */);
if (rc == LDAP_SUCCESS)
break;
(void) ldap_unbind(ld);
}
return (ld);
}
/*
* A utility function to get the value of some attribute of one of one
* or more AD LDAP objects named by the dn_list; first found one wins.
*/
static char *
{
int i;
int rc;
int scope = LDAP_SCOPE_BASE;
char *attrs[2];
return (NULL);
if (rc == LDAP_SUCCESS) {
}
(void) ldap_msgfree(results);
return (val);
}
}
(void) ldap_msgfree(results);
}
}
return (NULL);
}
/*
* Lookup the trusted domains in the global catalog.
*
* Returns:
* array of trusted domains which is terminated by
* an empty trusted domain.
* NULL an error occured
*/
char *base_dn)
{
int scope = LDAP_SCOPE_SUBTREE;
char *attrs[3];
int rc;
char *filter;
int num = 0;
return (NULL);
attrs[0] = "trustPartner";
/* trustDirection values - inbound = 1 and bidirectional = 3 */
filter = "(&(objectclass=trustedDomain)"
"(|(trustDirection=3)(trustDirection=1)))";
if (rc == LDAP_SUCCESS) {
num++;
(num + 1) *
sizeof (ad_disc_trusteddomains_t));
if (trusted_domains == NULL) {
return (NULL);
}
/* Last element should be zero */
sizeof (ad_disc_trusteddomains_t));
partner[0]);
}
}
} else if (rc == LDAP_NO_RESULTS_RETURNED) {
/* This is not an error - return empty trusted domain */
}
return (trusted_domains);
}
/*
* This functions finds all the domains in a forest.
* It first finds all the naming contexts by finding the
* root DSE attribute namingContext. For each naming context
* it performes an entry search looking for Domain object class
* returning the attribute objectSid.
*/
{
int scope = LDAP_SCOPE_BASE;
char *attrs[2];
char *root_attrs[2];
int rc;
char *filter;
int num = 0;
int i;
char *name;
char *sid_str;
return (NULL);
root_attrs[0] = "namingContexts";
attrs[0] = "objectSid";
filter = "(objectclass=Domain)";
/* Find naming contexts */
root_attrs, 0, &result);
if (rc == LDAP_SUCCESS) {
}
}
return (NULL);
/* Find domains */
&result);
if (rc == LDAP_SUCCESS) {
"objectSid");
num++;
(num + 1) *
sizeof (ad_disc_domainsinforest_t));
return (NULL);
}
sizeof (ad_disc_domainsinforest_t));
< 0) {
return (NULL);
}
== NULL) {
return (NULL);
}
return (NULL);
}
}
}
}
}
return (domains);
}
ad_disc_init(void)
{
/* Site specific versions */
return (ctx);
}
void
{
return;
if (ctx->res_ninitted)
/* Site specific versions */
}
void
{
if (ctx->res_ninitted)
}
/* Discover joined Active Directory domainName */
static ad_item_t *
{
return (&ctx->domain_name);
/* Try to find our domain by searching for DCs for it */
/*
* If we can't find DCs by via res_nsearch() then there's no
* point in trying anything else to discover the AD domain name.
*/
if (domain_controller == NULL)
return (NULL);
/*
* We have the FQDN of the SRV RR name, so now we extract the
* domainname suffix from it.
*/
1 /* for the dot between RR name and domainname */);
return (NULL);
}
/* Eat any trailing dot */
return (&ctx->domain_name);
}
char *
{
char *domain_name = NULL;
if (domain_name_item) {
if (auto_discovered != NULL)
} else if (auto_discovered != NULL)
return (domain_name);
}
/* Discover domain controllers */
static ad_item_t *
{
/* If the values is fixed there will not be a site specific version */
return (&ctx->domain_controller);
if (domain_name_item == NULL)
return (NULL);
if (req == AD_DISC_GLOBAL)
else {
if (site_name_item != NULL)
else if (req == AD_DISC_PREFER_SITE)
}
if (validate_global) {
domain_name_item)) {
/*
* Lookup DNS SRV RR named
* _ldap._tcp.dc._msdcs.<DomainName>
*/
if (domain_controller == NULL)
return (NULL);
AD_STATE_AUTO, ttl);
}
return (&ctx->domain_controller);
}
if (validate_site) {
domain_name_item) ||
site_name_item)) {
char rr_name[DNS_MAX_NAME];
/*
* Lookup DNS SRV RR named
* _ldap._tcp.<SiteName>._sites.dc._msdcs.<DomainName>
*/
if (domain_controller == NULL)
return (NULL);
}
return (&ctx->site_domain_controller);
}
return (NULL);
}
{
if (domain_controller_item != NULL) {
if (auto_discovered != NULL)
} else if (auto_discovered != NULL)
return (domain_controller);
}
/* Discover site name (for multi-homed systems the first one found wins) */
static ad_item_t *
{
char **dn_subnets = NULL;
char *dn_root[2];
char *config_naming_context = NULL;
char *site_object = NULL;
char *forest_name;
int len;
int i;
/* Can't rely on site-specific DCs */
if (domain_controller_item == NULL)
return (NULL);
subnets = find_subnets();
subnets = find_subnets();
}
if (!update_required) {
}
return (NULL);
dn_root[0] = "";
dn_root, "configurationNamingContext");
if (config_naming_context == NULL)
goto out;
/*
* configurationNamingContext also provides the Forest
* Name.
*/
/*
* The configurationNamingContext should be of
* form:
* CN=Configuration,<DNforestName>
* Remove the first part and convert to DNS form
* (replace ",DC=" with ".")
*/
char *str = "CN=Configuration,";
AD_STATE_AUTO, 0);
}
}
if (dn_subnets == NULL)
goto out;
dn_subnets, "siteobject");
if (site_object != NULL) {
/*
* The site object should be of the form
* CN=<site>,CN=Sites,CN=Configuration,
* <DN Domain>
*/
;
AD_STATE_AUTO, 0);
}
}
}
out:
(void) ldap_unbind(ld);
if (dn_subnets != NULL) {
for (i = 0; dn_subnets[i] != NULL; i++)
free(dn_subnets[i]);
}
if (config_naming_context != NULL)
if (site_object != NULL)
return (NULL);
}
char *
{
if (site_name_item != NULL) {
if (auto_discovered != NULL)
} else if (auto_discovered != NULL)
return (site_name);
}
/* Discover forest name */
static ad_item_t *
{
char *config_naming_context;
char *forest_name = NULL;
char *dn_list[2];
return (&ctx->forest_name);
/*
* We may not have a site name yet, so we won't rely on
* site-specific DCs. (But maybe we could replace
* validate_ForestName() with validate_siteName()?)
*/
if (domain_controller_item == NULL)
return (NULL);
dn_list[0] = "";
dn_list, "configurationNamingContext");
if (config_naming_context != NULL) {
/*
* The configurationNamingContext should be of
* form:
* CN=Configuration,<DNforestName>
* Remove the first part and convert to DNS form
* (replace ",DC=" with ".")
*/
char *str = "CN=Configuration,";
}
}
(void) ldap_unbind(ld);
if (forest_name == NULL)
return (NULL);
}
return (&ctx->forest_name);
}
char *
{
char *forest_name = NULL;
if (forest_name_item != NULL) {
if (auto_discovered != NULL)
} else if (auto_discovered != NULL)
return (forest_name);
}
/* Discover global catalog servers */
static ad_item_t *
{
/* If the values is fixed there will not be a site specific version */
return (&ctx->global_catalog);
if (forest_name_item == NULL)
return (NULL);
if (req == AD_DISC_GLOBAL)
else {
if (site_name_item != NULL)
else if (req == AD_DISC_PREFER_SITE)
}
if (validate_global) {
forest_name_item)) {
/*
* Lookup DNS SRV RR named
* _ldap._tcp.gc._msdcs.<ForestName>
*/
if (global_catalog == NULL)
return (NULL);
AD_STATE_AUTO, ttl);
}
return (&ctx->global_catalog);
}
if (validate_site) {
forest_name_item) ||
site_name_item)) {
char rr_name[DNS_MAX_NAME];
/*
* Lookup DNS SRV RR named:
* _ldap._tcp.<siteName>._sites.gc.
* _msdcs.<ForestName>
*/
sizeof (rr_name),
if (global_catalog == NULL)
return (NULL);
AD_STATE_AUTO, ttl);
}
return (&ctx->site_global_catalog);
}
return (NULL);
}
{
if (global_catalog_item != NULL) {
if (auto_discovered != NULL)
} else if (auto_discovered != NULL)
return (global_catalog);
}
static ad_item_t *
{
char *forest_name_dn;
int len;
int num_parts;
return (&ctx->trusted_domains);
if (global_catalog_item == NULL)
return (NULL);
if (forest_name_item == NULL)
return (NULL);
&num_parts);
if (forest_name_dn == NULL)
return (NULL);
return (NULL);
}
(void) ldap_unbind(ld);
if (trusted_domains == NULL)
return (NULL);
AD_STATE_AUTO, 0);
}
return (&ctx->trusted_domains);
}
{
if (trusted_domains_item != NULL) {
if (auto_discovered != NULL)
} else if (auto_discovered != NULL)
return (trusted_domains);
}
static ad_item_t *
{
return (&ctx->domains_in_forest);
if (global_catalog_item == NULL)
return (NULL);
(void) ldap_unbind(ld);
if (domains_in_forest == NULL)
return (NULL);
AD_STATE_AUTO, 0);
}
return (&ctx->domains_in_forest);
}
{
if (domains_in_forest_item != NULL) {
if (auto_discovered != NULL)
} else if (auto_discovered != NULL)
return (domains_in_forest);
}
int
{
char *domain_name = NULL;
if (domainName != NULL) {
if (domain_name == NULL)
return (-1);
AD_STATE_FIXED, 0);
return (0);
}
int
const idmap_ad_disc_ds_t *domainController)
{
if (domainController != NULL) {
if (domain_controller == NULL)
return (-1);
AD_STATE_FIXED, 0);
return (0);
}
int
{
return (-1);
return (0);
}
int
{
char *forest_name = NULL;
if (forestName != NULL) {
if (forest_name == NULL)
return (-1);
AD_STATE_FIXED, 0);
return (0);
}
int
const idmap_ad_disc_ds_t *globalCatalog)
{
if (globalCatalog != NULL) {
if (global_catalog == NULL)
return (-1);
AD_STATE_FIXED, 0);
return (0);
}
int
{
return (0);
}
/*
* ad_disc_get_TTL
*
* This routines the time to live for AD
* auto discovered items.
*
* Returns:
* -1 if there are no TTL items
* 0 if there are expired items
* else the number of seconds
*
* The MIN_GT_ZERO(x, y) macro return the lesser of x and y, provided it
* is positive -- min() greater than zero.
*/
#define MIN_GT_ZERO(x, y) (((x) <= 0) ? (((y) <= 0) ? \
(-1) : (y)) : (((y) <= 0) ? (x) : (((x) > (y)) ? (y) : (x))))
int
{
int ttl;
if (ttl == -1)
return (-1);
if (ttl < 0)
return (0);
return (ttl);
}
{
return (B_TRUE);
}
return (ctx->subnets_changed);
}