addisc.c revision 0dcc71495bad040a0c83830efc85acf8d897350d
/*
* 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 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* 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 <stdlib.h>
#include <resolv.h>
#include <netdb.h>
#include <ctype.h>
#include <errno.h>
#include <ldap.h>
#include "idmapd.h"
#include "addisc.h"
#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."
#define me "idmapd"
enum ad_item_type {
AD_TYPE_INVALID = 0, /* The value is not valid */
AD_TYPE_FIXED, /* The value was fixed by caller */
AD_TYPE_AUTO /* The value is auto discovered */
};
typedef struct ad_item {
enum ad_item_type type;
union {
char *str;
} 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_subnet {
char subnet[24];
} ad_subnet_t;
struct ad_disc {
struct __res_state state;
int res_ninitted;
int subnets_changed;
/* Site specfic versions */
};
/*
* 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 void
{
}
static int
{
return (FALSE);
return (FALSE);
return (TRUE);
}
static void
{
if (ttl == 0)
else
}
static void
{
if (ttl == 0)
else
}
static ad_item_t *
{
return (global);
if (req == AD_DISC_GLOBAL)
else if (req == AD_DISC_SITE_SPECIFIC)
else
return (NULL);
return (item);
}
/*
* The last subnet is NULL
*/
static ad_subnet_t *
{
int sock, n, i;
char *s;
return (NULL);
}
lifn.lifn_flags = 0;
"%s: 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 matched;
int i, j;
;
num_subnets1 = i;
;
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 */
static char *
{
char dns[DNS_MAX_NAME];
char *dns_name;
int i, j;
int num = 0;
j = 0;
i = 0;
/*
* Find all DC=<value> and form DNS name of the
* form <value1>.<value2>...
*/
i += 3;
dns[j++] = '.';
num++;
} else {
/* Skip attr=value as it is not DC= */
i++;
}
/* Skip over separator ',' or '+' */
}
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;
;
return (NULL);
== NULL) {
for (j = 0; j < i; j++)
return (NULL);
}
}
return (results);
}
/* Compare DS lists */
int
{
int i, j;
int num_ds1;
int num_ds2;
int match;
;
num_ds1 = i;
;
num_ds2 = j;
return (1);
for (i = 0; i < num_ds1; i++) {
for (j = 0; j < num_ds1; j++) {
break;
}
}
if (!match)
return (1);
}
return (0);
}
/* Copy a list of DSs */
static ad_disc_ds_t *
{
int i;
int size;
;
return (new);
}
/* 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 *query_type;
char namebuf[NS_MAXDNAME];
return (NULL);
/* Set negative result TTL */
/* 1. query necessary resource records */
/* Search, querydomain or query */
query_type = "search";
query_type = "query";
}
if (len < 0) {
return (NULL);
}
" into %ib buffer",
return (NULL);
}
/* 2. parse the reply, skip header and question sections */
"%s: DNS query invalid message format", me);
return (NULL);
}
}
/* 3. walk through the answer section */
sizeof (namebuf));
if (len < 0) {
"%s: DNS query invalid message format", me);
return (NULL);
}
"%s: DNS query invalid message format", me);
return (NULL);
}
continue;
}
if (len < 0) {
me);
return (NULL);
}
"%s: Found %s %d IN SRV [%d][%d] %s:%d", me,
/* 3. move ptr to the end of current record */
}
if (ancount > 1)
(int (*)(const void *, const void *))srvcmp);
return (srv_res);
}
static int
/* ARGSUSED */
{
return (LDAP_PARAM_ERROR);
}
return (LDAP_SUCCESS);
}
/*
* 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 zero = 0;
char *saslmech = "GSSAPI";
int scope = LDAP_SCOPE_BASE;
char *attrs[2];
domainControllers[i].port);
"AD DC %s:%d (%s)", me,
continue;
}
&ldversion);
&timeoutms);
NULL /* defaults */);
if (rc == LDAP_SUCCESS)
break;
(void) ldap_unbind(*ld);
}
}
"connections to any domain controllers; discovery of "
"some items will fail", me);
return (NULL);
}
if (rc == LDAP_SUCCESS) {
}
(void) ldap_msgfree(results);
return (val);
}
}
(void) ldap_msgfree(results);
}
}
(void) ldap_unbind(*ld);
return (NULL);
}
ad_disc_init(void)
{
return (ctx);
}
void
{
return;
if (ctx->res_ninitted)
}
void
{
if (ctx->res_ninitted)
}
/* Discover joined Active Directory domainName */
static void
{
return;
return;
/* 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;
/*
* 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;
}
/* Eat any trailing dot */
}
char *
{
char *domain_name = NULL;
return (domain_name);
}
/* Discover domain controllers */
static void
{
int validate_global = FALSE;
int validate_site = FALSE;
return;
if (req == AD_DISC_GLOBAL)
else {
if (req == AD_DISC_PREFER_SITE)
}
&ctx->domain_name);
/*
* Lookup DNS SRV RR named
* _ldap._tcp.dc._msdcs.<DomainName>
*/
}
AD_TYPE_AUTO, ttl);
}
&ctx->domain_name) ||
&ctx->domain_name);
char rr_name[DNS_MAX_NAME];
/*
* Lookup DNS SRV RR named
* _ldap._tcp.<SiteName>._sites.dc._msdcs.<DomainName>
*/
}
AD_TYPE_AUTO, ttl);
}
}
{
return (domain_controller);
}
/* Discover site name (for multi-homed systems the first one found wins) */
static void
{
char **dn_subnets = NULL;
char *dn_root[2];
char *config_naming_context = NULL;
char *site_object = NULL;
char *forest_name;
int len;
int i;
int update_required = FALSE;
return;
/* Can't rely on site-specific DCs */
subnets = find_subnets();
subnets = find_subnets();
}
if (!update_required) {
return;
}
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,";
forest_name, AD_TYPE_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>
*/
for (len = 0;
;
}
}
}
}
out:
(void) ldap_unbind(ld);
/* No site name -> no site-specific DSs */
}
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)
}
char *
{
return (site_name);
}
/* Discover forest name */
static void
{
char *config_naming_context;
char *forest_name = NULL;
char *dn_list[2];
return;
/*
* 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()?)
*/
&ctx->domain_controller);
dn_list[0] = "";
"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);
}
}
}
char *
{
char *forest_name = NULL;
return (forest_name);
}
/* Discover global catalog servers */
static void
{
int validate_global = FALSE;
int validate_site = FALSE;
return;
if (req == AD_DISC_GLOBAL)
else {
if (req == AD_DISC_PREFER_SITE)
}
/*
* Lookup DNS SRV RR named
* _ldap._tcp.gc._msdcs.<ForestName>
*/
}
AD_TYPE_AUTO, ttl);
}
&ctx->forest_name);
char rr_name[DNS_MAX_NAME];
/*
* Lookup DNS SRV RR named:
* _ldap._tcp.<siteName>._sites.gc.
* _msdcs.<ForestName>
*/
sizeof (rr_name),
}
AD_TYPE_AUTO, ttl);
}
}
{
return (global_catalog);
}
int
{
char *domain_name = NULL;
if (domainName != NULL) {
if (domain_name == NULL)
return (-1);
AD_TYPE_FIXED, 0);
return (0);
}
int
const ad_disc_ds_t *domainController)
{
if (domainController != NULL) {
if (domain_controller == NULL)
return (-1);
AD_TYPE_FIXED, 0);
return (0);
}
int
{
return (-1);
return (0);
}
int
{
char *forest_name = NULL;
if (forestName != NULL) {
if (forest_name == NULL)
return (-1);
AD_TYPE_FIXED, 0);
return (0);
}
int
{
if (globalCatalog != NULL) {
if (global_catalog == NULL)
return (-1);
AD_TYPE_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);
}
int
{
return (TRUE);
}
return (ctx->subnets_changed);
}