/*
* 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 2014 Nexenta Systems, Inc. All rights reserved.
*/
/*
* 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 <note.h>
#include <sys/u8_textprep.h>
#include <syslog.h>
#include "adutils_impl.h"
#include "addisc_impl.h"
/*
* These set some sanity policies for discovery. After a discovery
* cycle, we will consider the results (successful or unsuccessful)
* to be valid for at least MINIMUM_TTL seconds, and for at most
* MAXIMUM_TTL seconds. Note that the caller is free to request
* discovery cycles sooner than MINIMUM_TTL if it has reason to believe
* that the situation has changed.
*/
/* SRV RR names for various queries */
/* A RR name for all GCs -- last resort this works */
/*
* We try res_ninit() whenever we don't have one. res_ninit() fails if
* idmapd is running before the network is up!
*/
if (!(ctx)->res_ninitted) \
(void) do_res_ninit(ctx)
#define DO_GETNAMEINFO(b, l, s) \
if (ad_disc_getnameinfo(b, l, s) != 0) \
(void) strlcpy(b, "?", l)
} \
} while (0)
void * uuid_dup(void *);
/*
* Function definitions
*/
static int
{
int rc;
if (rc != 0)
return (rc);
/*
* The SRV records returnd by AD can be larger than 512 bytes,
* so we'd like to use TCP for those searches. Unfortunately,
* the TCP connect timeout seen by the resolver is very long
* (more than a couple minutes) and we can't wait that long.
* Don't do use TCP until we can override the timeout.
*
* Note that some queries will try TCP anyway.
*/
#if 0
#endif
return (0);
}
/*
* Private getnameinfo(3socket) variant tailored to our needs.
*/
int
{
case AF_INET:
slen = sizeof (struct sockaddr_in);
break;
case AF_INET6:
slen = sizeof (struct sockaddr_in6);
break;
default:
return (EAI_FAMILY);
}
return (eai);
}
static void
{
}
static boolean_t
{
return (B_TRUE);
return (B_TRUE);
}
return (B_FALSE);
}
static void
{
if (ttl == 0)
else
}
/* Compare UUIDs */
int
{
int rc;
return (rc);
}
void *
{
void *dst;
return (dst);
}
/* 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 ad_disc_ds_t *
{
int i;
int size;
continue;
return (new);
}
int
{
int i, j;
int num_td1;
int num_td2;
continue;
num_td1 = i;
continue;
num_td2 = j;
return (1);
for (i = 0; i < num_td1; i++) {
for (j = 0; j < num_td2; j++) {
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;
continue;
num_df1 = i;
continue;
num_df2 = j;
return (1);
for (i = 0; i < num_df1; i++) {
for (j = 0; j < num_df2; j++) {
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_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);
}
/*
* A utility function to bind to a Directory server
*/
static
LDAP *
{
int i;
int zero = 0;
}
"Couldn't connect to AD DC %s:%d (%s)",
}
continue;
}
&ldversion);
&timeoutms);
if (rc != LDAP_SUCCESS) {
/* Error has already been logged */
(void) ldap_unbind(ld);
continue;
}
NULL /* defaults */);
if (rc == LDAP_SUCCESS)
break;
}
(void) ldap_unbind(ld);
}
return (ld);
}
/*
* 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 rc;
char *filter;
int num = 0;
return (NULL);
}
attrs[0] = "trustPartner";
/*
* Trust direction values:
* 1 - inbound (they trust us)
* 2 - outbound (we trust them)
* 3 - bidirectional (we trust each other)
*/
filter = "(&(objectclass=trustedDomain)"
"(|(trustDirection=3)(trustDirection=2)))";
if (rc == LDAP_SUCCESS) {
}
num++;
(num + 1) *
sizeof (ad_disc_trusteddomains_t));
(void) ldap_msgfree(results);
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 */
} else {
}
(void) ldap_msgfree(results);
return (trusted_domains);
}
/*
* This functions finds all the domains in a forest.
*/
{
static char *attrs[] = {
"objectSid",
NULL,
};
int rc;
int ndomains = 0;
int nresults;
return (NULL);
}
/* Find domains */
if (rc != LDAP_SUCCESS) {
goto err;
}
goto err;
}
char *sid_str;
char *name;
char *dn;
"objectSid");
continue;
if (rc < 0)
goto err;
goto err;
goto err;
ndomains++;
}
if (ndomains == 0) {
goto err;
}
goto err;
}
(void) ldap_msgfree(result);
return (domains);
err:
(void) ldap_msgfree(result);
return (NULL);
}
ad_disc_init(void)
{
/* Site specific versions */
return (ctx);
}
void
{
return;
if (ctx->res_ninitted)
/* Site specific versions */
}
void
{
if (ctx->res_ninitted) {
ctx->res_ninitted = 0;
}
}
/*
* Called when the discovery cycle is done. Sets a master TTL
* that will avoid doing new time-based discoveries too soon after
* the last discovery cycle. Most interesting when the discovery
* cycle failed, because then the TTLs on the individual items will
* not be updated and may go stale.
*/
void
{
}
static void
{
return;
return;
}
}
int eai;
if (eai != 0)
}
cds++;
}
}
static void
{
return;
return;
}
ds++;
}
}
/* 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 (rc < 0) {
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 */
/*
* There is no "time to live" on the discovered domain,
* so passing zero as TTL here, making it non-expiring.
* Note that current consumers do not auto-discover the
* domain name, though a future installer could.
*/
return (&ctx->domain_name);
}
char *
{
if (domain_name_item) {
if (auto_discovered != NULL)
} else if (auto_discovered != NULL)
return (domain_name);
}
/* Discover domain controllers */
static ad_item_t *
{
char *domain_name;
char *site_name;
/* If the values is fixed there will not be a site specific version */
return (&ctx->domain_controller);
if (domain_name_item == NULL)
return (NULL);
/* Get (optional) preferred DC. */
if (prefer_dc_item != NULL)
if (req == AD_DISC_GLOBAL)
else {
else if (req == AD_DISC_PREFER_SITE)
}
if (validate_global) {
domain_name_item)) {
/*
* Lookup DNS SRV RR named
* _ldap._tcp.dc._msdcs.<DomainName>
*/
return (NULL);
}
/*
* Filter out unresponsive servers, and
* save the domain info we get back.
*/
ctx,
cdc,
return (NULL);
}
}
return (&ctx->domain_controller);
}
if (validate_site) {
domain_name_item) ||
site_name_item)) {
/*
* Lookup DNS SRV RR named
* _ldap._tcp.<SiteName>._sites.dc._msdcs.<DomainName>
*/
return (NULL);
}
/*
* Filter out unresponsive servers, and
* save the domain info we get back.
*/
ctx,
cdc,
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 the Domain GUID
* This info comes from validate_DomainController()
*/
static ad_item_t *
{
return (&ctx->domain_guid);
if (domain_controller_item == NULL)
return (NULL);
return (NULL);
return (&ctx->domain_guid);
}
uchar_t *
{
if (domain_guid_item != NULL) {
if (auto_discovered != NULL)
} else if (auto_discovered != NULL)
return (domain_guid);
}
/*
* Discover site name (for multi-homed systems the first one found wins)
* This info comes from validate_DomainController()
*/
static ad_item_t *
{
if (domain_controller_item == NULL)
return (NULL);
return (NULL);
}
char *
{
if (site_name_item != NULL) {
if (auto_discovered != NULL)
} else if (auto_discovered != NULL)
return (site_name);
}
/*
* Discover forest name
* This info comes from validate_DomainController()
*/
static ad_item_t *
{
return (&ctx->forest_name);
if (domain_controller_item == NULL)
return (NULL);
return (NULL);
return (&ctx->forest_name);
}
char *
{
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 *
{
char *forest_name;
char *site_name;
/* 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 {
else if (req == AD_DISC_PREFER_SITE)
}
if (validate_global) {
forest_name_item)) {
/*
* See if our DC is also a GC.
*/
"DC is also a GC for %s",
goto update_global;
}
}
}
/*
* Lookup DNS SRV RR named:
* _ldap._tcp.gc._msdcs.<ForestName>
*/
forest_name, NULL);
return (NULL);
}
/*
* Filter out unresponsive servers, and
* save the domain info we get back.
*/
NULL,
cgc,
return (NULL);
}
}
return (&ctx->global_catalog);
}
if (validate_site) {
forest_name_item) ||
site_name_item)) {
/*
* See if our DC is also a GC.
*/
"DC is also a GC for %s in %s",
goto update_site;
}
}
}
/*
* Lookup DNS SRV RR named:
* _ldap._tcp.<siteName>._sites.gc.
* _msdcs.<ForestName>
*/
forest_name, NULL);
return (NULL);
}
/*
* Filter out unresponsive servers, and
* save the domain info we get back.
*/
NULL,
cgc,
return (NULL);
}
}
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);
}
static ad_item_t *
{
return (&ctx->preferred_dc);
return (NULL);
}
{
if (preferred_dc_item != NULL) {
if (auto_discovered != NULL)
} else if (auto_discovered != NULL)
return (preferred_dc);
}
int
{
if (domainName != NULL) {
if (domain_name == NULL)
return (-1);
AD_STATE_FIXED, 0);
return (0);
}
int
{
if (u != NULL) {
domain_guid = uuid_dup(u);
if (domain_guid == NULL)
return (-1);
AD_STATE_FIXED, 0);
return (0);
}
void
{
return;
domain_guid = uuid_dup(u);
if (domain_guid == NULL)
return;
}
int
const 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);
}
void
{
return;
return;
}
int
{
if (forestName != NULL) {
if (forest_name == NULL)
return (-1);
AD_STATE_FIXED, 0);
return (0);
}
void
{
return;
if (forest_name == NULL)
return;
}
int
const ad_disc_ds_t *globalCatalog)
{
if (globalCatalog != NULL) {
if (global_catalog == NULL)
return (-1);
AD_STATE_FIXED, 0);
return (0);
}
int
{
if (new_pref_dc == NULL)
return (-1);
AD_STATE_FIXED, 0);
return (0);
}
void
{
}
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 (expires == -1) {
return (-1);
}
if (ctx->expires_not_before != 0 &&
}
if (ctx->expires_not_after != 0 &&
}
if (ttl < 0) {
return (0);
}
return (ttl);
}
{
return (B_TRUE);
}
return (ctx->subnets_changed);
}