summaryrefslogtreecommitdiff
path: root/usr/src/lib/libadutils/common
diff options
context:
space:
mode:
authorGordon Ross <gwr@nexenta.com>2014-06-05 14:30:31 -0400
committerGordon Ross <gwr@nexenta.com>2015-10-26 10:17:47 -0400
commitb3700b074e637f8c6991b70754c88a2cfffb246b (patch)
treec979fb7c426aec884413fae889fab8356ca9ef17 /usr/src/lib/libadutils/common
parented81dd52230eff1a7c7625caad21af232c36f6cb (diff)
downloadillumos-joyent-b3700b074e637f8c6991b70754c88a2cfffb246b.tar.gz
6352 Updated DC locator for SMB and idmap
Portions contributed by: Matt Barden <Matt.Barden@nexenta.com> Portions contributed by: Kevin Crowe <kevin.crowe@nexenta.com> Portions contributed by: Alek Pinchuk <alek@nexenta.com> Reviewed by: Bayard Bell <bayard.bell@nexenta.com> Reviewed by: Alek Pinchuk <alek.pinchuk@nexenta.com> Reviewed by: Rick McNeal <rick.mcneal@nexenta.com> Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com> Reviewed by: Tony Nguyen <tony.nguyen@nexenta.com> Approved by: Robert Mustacchi <rm@joyent.com>
Diffstat (limited to 'usr/src/lib/libadutils/common')
-rw-r--r--usr/src/lib/libadutils/common/addisc.c1238
-rw-r--r--usr/src/lib/libadutils/common/addisc.h55
-rw-r--r--usr/src/lib/libadutils/common/addisc_impl.h130
-rw-r--r--usr/src/lib/libadutils/common/adutils_impl.h2
-rw-r--r--usr/src/lib/libadutils/common/ldap_ping.c677
-rw-r--r--usr/src/lib/libadutils/common/mapfile-vers10
-rw-r--r--usr/src/lib/libadutils/common/srv_query.c612
7 files changed, 2038 insertions, 686 deletions
diff --git a/usr/src/lib/libadutils/common/addisc.c b/usr/src/lib/libadutils/common/addisc.c
index a0bbfe1c0f..f6ac98d11a 100644
--- a/usr/src/lib/libadutils/common/addisc.c
+++ b/usr/src/lib/libadutils/common/addisc.c
@@ -21,6 +21,7 @@
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
*/
/*
@@ -83,12 +84,10 @@
#include <assert.h>
#include <stdlib.h>
#include <net/if.h>
-#include <net/if.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <netinet/in.h>
-#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <resolv.h>
@@ -96,11 +95,14 @@
#include <ctype.h>
#include <errno.h>
#include <ldap.h>
+#include <note.h>
#include <sasl/sasl.h>
#include <sys/u8_textprep.h>
#include <syslog.h>
+#include <uuid/uuid.h>
+#include <ads/dsgetdc.h>
#include "adutils_impl.h"
-#include "addisc.h"
+#include "addisc_impl.h"
/*
* These set some sanity policies for discovery. After a discovery
@@ -113,70 +115,10 @@
#define MINIMUM_TTL (5 * 60)
#define MAXIMUM_TTL (20 * 60)
-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,
- AD_DIRECTORY,
- AD_DOMAINS_IN_FOREST,
- AD_TRUSTED_DOMAINS
- };
-
-
-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;
- time_t expires;
- 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;
- ad_subnet_t *subnets;
- boolean_t subnets_changed;
- time_t subnets_last_check;
- time_t expires_not_before;
- time_t expires_not_after;
- ad_item_t domain_name; /* DNS hostname string */
- ad_item_t domain_controller; /* Directory hostname and */
- /* port array */
- ad_item_t site_name; /* String */
- ad_item_t forest_name; /* DNS forestname string */
- ad_item_t global_catalog; /* Directory hostname and */
- /* port array */
- ad_item_t domains_in_forest; /* DNS domainname and SID */
- /* array */
- ad_item_t trusted_domains; /* DNS domainname and trust */
- /* direction array */
- /* Site specfic versions */
- ad_item_t site_domain_controller; /* Directory hostname and */
- /* port array */
- ad_item_t site_global_catalog; /* Directory hostname and */
- /* port array */
- int debug[AD_DEBUG_MAX+1]; /* Debug levels */
-} ad_disc;
-
#define DNS_MAX_NAME NS_MAXDNAME
+#define GC_PORT 3268
/* SRV RR names for various queries */
#define LDAP_SRV_HEAD "_ldap._tcp."
@@ -194,8 +136,23 @@ typedef struct ad_disc {
* We try res_ninit() whenever we don't have one. res_ninit() fails if
* idmapd is running before the network is up!
*/
-#define DO_RES_NINIT(ctx) if (!(ctx)->res_ninitted) \
- (ctx)->res_ninitted = (res_ninit(&ctx->res_state) != -1)
+#define DO_RES_NINIT(ctx) \
+ 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)
+
+#define DEBUG1STATUS(ctx, ...) do { \
+ if (DBG(DISC, 1)) \
+ logger(LOG_DEBUG, __VA_ARGS__); \
+ if (ctx->status_fp) { \
+ (void) fprintf(ctx->status_fp, __VA_ARGS__); \
+ (void) fprintf(ctx->status_fp, "\n"); \
+ } \
+ _NOTE(CONSTCOND) \
+} while (0)
#define is_fixed(item) \
((item)->state == AD_STATE_FIXED)
@@ -203,13 +160,65 @@ typedef struct ad_disc {
#define is_changed(item, num, param) \
((item)->param_version[num] != (param)->version)
+void * uuid_dup(void *);
+
+static ad_item_t *validate_SiteName(ad_disc_t ctx);
+static ad_item_t *validate_PreferredDC(ad_disc_t ctx);
+
/*
* Function definitions
*/
-static ad_item_t *
-validate_SiteName(ad_disc_t ctx);
+static int
+do_res_ninit(ad_disc_t ctx)
+{
+ int rc;
+
+ rc = res_ninit(&ctx->res_state);
+ if (rc != 0)
+ return (rc);
+ ctx->res_ninitted = 1;
+ /*
+ * 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
+ ctx->res_state.options |= RES_USEVC;
+#endif
+ return (0);
+}
+
+/*
+ * Private getnameinfo(3socket) variant tailored to our needs.
+ */
+int
+ad_disc_getnameinfo(char *obuf, int olen, struct sockaddr_storage *ss)
+{
+ struct sockaddr *sa;
+ int eai, slen;
+
+ sa = (void *)ss;
+ switch (sa->sa_family) {
+ case AF_INET:
+ slen = sizeof (struct sockaddr_in);
+ break;
+ case AF_INET6:
+ slen = sizeof (struct sockaddr_in6);
+ break;
+ default:
+ return (EAI_FAMILY);
+ }
+
+ eai = getnameinfo(sa, slen, obuf, olen, NULL, 0, NI_NUMERICHOST);
+
+ return (eai);
+}
static void
update_version(ad_item_t *item, int num, ad_item_t *param)
@@ -240,6 +249,8 @@ update_item(ad_item_t *item, void *value, enum ad_item_state state,
if (item->value != NULL && value != NULL) {
if ((item->type == AD_STRING &&
strcmp(item->value, value) != 0) ||
+ (item->type == AD_UUID &&
+ ad_disc_compare_uuid(item->value, value) != 0)||
(item->type == AD_DIRECTORY &&
ad_disc_compare_ds(item->value, value) != 0)||
(item->type == AD_DOMAINS_IN_FOREST &&
@@ -262,10 +273,29 @@ update_item(ad_item_t *item, void *value, enum ad_item_state state,
item->expires = time(NULL) + ttl;
}
+/* Compare UUIDs */
+int
+ad_disc_compare_uuid(uuid_t *u1, uuid_t *u2)
+{
+ int rc;
+
+ rc = memcmp(u1, u2, UUID_LEN);
+ return (rc);
+}
+
+void *
+uuid_dup(void *src)
+{
+ void *dst;
+ dst = malloc(UUID_LEN);
+ if (dst != NULL)
+ (void) memcpy(dst, src, UUID_LEN);
+ return (dst);
+}
/* Compare DS lists */
int
-ad_disc_compare_ds(idmap_ad_disc_ds_t *ds1, idmap_ad_disc_ds_t *ds2)
+ad_disc_compare_ds(ad_disc_ds_t *ds1, ad_disc_ds_t *ds2)
{
int i, j;
int num_ds1;
@@ -298,17 +328,17 @@ ad_disc_compare_ds(idmap_ad_disc_ds_t *ds1, idmap_ad_disc_ds_t *ds2)
/* Copy a list of DSs */
-static idmap_ad_disc_ds_t *
-ds_dup(const idmap_ad_disc_ds_t *srv)
+static ad_disc_ds_t *
+ds_dup(const ad_disc_ds_t *srv)
{
int i;
int size;
- idmap_ad_disc_ds_t *new = NULL;
+ ad_disc_ds_t *new = NULL;
for (i = 0; srv[i].host[0] != '\0'; i++)
continue;
- size = (i + 1) * sizeof (idmap_ad_disc_ds_t);
+ size = (i + 1) * sizeof (ad_disc_ds_t);
new = malloc(size);
if (new != NULL)
(void) memcpy(new, srv, size);
@@ -603,237 +633,13 @@ DN_to_DNS(const char *dn_name)
}
-/* Make a list of subnet object DNs from a list of subnets */
-static char **
-subnets_to_DNs(ad_subnet_t *subnets, const char *base_dn)
-{
- char **results;
- int i, j;
-
- for (i = 0; subnets[i].subnet[0] != '\0'; i++)
- continue;
-
- results = calloc(i + 1, sizeof (char *));
- if (results == NULL)
- return (NULL);
-
- for (i = 0; subnets[i].subnet[0] != '\0'; i++) {
- (void) asprintf(&results[i], "CN=%s,CN=Subnets,CN=Sites,%s",
- subnets[i].subnet, base_dn);
- if (results[i] == NULL) {
- for (j = 0; j < i; j++)
- free(results[j]);
- free(results);
- return (NULL);
- }
- }
-
- return (results);
-}
-
-
-/* Compare SRC RRs; used with qsort() */
-static int
-srvcmp(idmap_ad_disc_ds_t *s1, idmap_ad_disc_ds_t *s2)
-{
- if (s1->priority < s2->priority)
- return (1);
- else if (s1->priority > s2->priority)
- return (-1);
-
- if (s1->weight < s2->weight)
- return (1);
- else if (s1->weight > s2->weight)
- 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
- * search list/option), else query (as in res_nquery(3RESOLV)).
- *
- * The output TTL will be the one of the SRV RR with the lowest TTL.
- */
-idmap_ad_disc_ds_t *
-srv_query(res_state state, const char *svc_name, const char *dname,
- char **rrname, uint32_t *ttl)
-{
- idmap_ad_disc_ds_t *srv;
- idmap_ad_disc_ds_t *srv_res = NULL;
- union {
- HEADER hdr;
- uchar_t buf[NS_MAXMSG];
- } msg;
- int len, cnt, qdcount, ancount;
- uchar_t *ptr, *eom;
- uchar_t *end;
- uint16_t type;
- /* LINTED E_FUNC_SET_NOT_USED */
- uint16_t class;
- uint32_t rttl;
- uint16_t size;
- char namebuf[NS_MAXDNAME];
-
- if (state == NULL)
- return (NULL);
-
- /* Set negative result TTL */
- *ttl = 5 * 60;
-
- /* 1. query necessary resource records */
-
- /* Search, querydomain or query */
- if (rrname != NULL) {
- *rrname = NULL;
- if (DBG(DNS, 1)) {
- logger(LOG_DEBUG, "Looking for SRV RRs '%s.*'",
- svc_name);
- }
- len = res_nsearch(state, svc_name, C_IN, T_SRV,
- msg.buf, sizeof (msg.buf));
- if (len < 0) {
- if (DBG(DNS, 0)) {
- logger(LOG_DEBUG,
- "DNS search for '%s' failed (%s)",
- svc_name, hstrerror(state->res_h_errno));
- }
- return (NULL);
- }
- } else if (dname != NULL) {
- if (DBG(DNS, 1)) {
- logger(LOG_DEBUG, "Looking for SRV RRs '%s.%s' ",
- svc_name, dname);
- }
-
- len = res_nquerydomain(state, svc_name, dname, C_IN, T_SRV,
- msg.buf, sizeof (msg.buf));
-
- if (len < 0) {
- if (DBG(DNS, 0)) {
- logger(LOG_DEBUG, "DNS: %s.%s: %s",
- svc_name, dname,
- hstrerror(state->res_h_errno));
- }
- return (NULL);
- }
- }
-
- if (len > sizeof (msg.buf)) {
- logger(LOG_ERR,
- "DNS query %ib message doesn't fit into %ib buffer",
- len, sizeof (msg.buf));
- return (NULL);
- }
-
- /* 2. parse the reply, skip header and question sections */
-
- ptr = msg.buf + sizeof (msg.hdr);
- eom = msg.buf + len;
- qdcount = ntohs(msg.hdr.qdcount);
- ancount = ntohs(msg.hdr.ancount);
-
- for (cnt = qdcount; cnt > 0; --cnt) {
- if ((len = dn_skipname(ptr, eom)) < 0) {
- logger(LOG_ERR, "DNS query invalid message format");
- return (NULL);
- }
- ptr += len + QFIXEDSZ;
- }
-
- /* 3. walk through the answer section */
-
- srv_res = calloc(ancount + 1, sizeof (idmap_ad_disc_ds_t));
- if (srv_res == NULL) {
- logger(LOG_ERR, "Out of memory");
- return (NULL);
- }
-
- *ttl = (uint32_t)-1;
-
- for (srv = srv_res, cnt = ancount;
- cnt > 0; --cnt, srv++) {
-
- len = dn_expand(msg.buf, eom, ptr, namebuf,
- sizeof (namebuf));
- if (len < 0) {
- logger(LOG_ERR, "DNS query invalid message format");
- goto err;
- }
- if (rrname != NULL && *rrname == NULL) {
- *rrname = strdup(namebuf);
- if (*rrname == NULL) {
- logger(LOG_ERR, "Out of memory");
- goto err;
- }
- }
- ptr += len;
- NS_GET16(type, ptr);
- NS_GET16(class, ptr);
- NS_GET32(rttl, ptr);
- NS_GET16(size, ptr);
- if ((end = ptr + size) > eom) {
- logger(LOG_ERR, "DNS query invalid message format");
- goto err;
- }
-
- if (type != T_SRV) {
- ptr = end;
- continue;
- }
-
- NS_GET16(srv->priority, ptr);
- NS_GET16(srv->weight, ptr);
- NS_GET16(srv->port, ptr);
- len = dn_expand(msg.buf, eom, ptr, srv->host,
- sizeof (srv->host));
- if (len < 0) {
- logger(LOG_ERR, "DNS query invalid SRV record");
- goto err;
- }
-
- if (rttl < *ttl)
- *ttl = rttl;
-
- if (DBG(DNS, 1)) {
- logger(LOG_DEBUG, " %s", namebuf);
- logger(LOG_DEBUG,
- " ttl=%d pri=%d weight=%d %s:%d",
- rttl, srv->priority, srv->weight,
- srv->host, srv->port);
- }
-
- /* 3. move ptr to the end of current record */
-
- ptr = end;
- }
-
- if (ancount > 1)
- qsort(srv_res, ancount, sizeof (*srv_res),
- (int (*)(const void *, const void *))srvcmp);
-
- return (srv_res);
-
-err:
- free(srv_res);
- if (rrname != NULL) {
- free(*rrname);
- *rrname = NULL;
- }
- return (NULL);
-}
-
-
/*
* A utility function to bind to a Directory server
*/
static
LDAP *
-ldap_lookup_init(idmap_ad_disc_ds_t *ds)
+ldap_lookup_init(ad_disc_ds_t *ds)
{
int i;
int rc, ldversion;
@@ -844,6 +650,11 @@ ldap_lookup_init(idmap_ad_disc_ds_t *ds)
LDAP *ld = NULL;
for (i = 0; ds[i].host[0] != '\0'; i++) {
+ if (DBG(LDAP, 2)) {
+ logger(LOG_DEBUG, "adutils: ldap_lookup_init, host %s",
+ ds[i].host);
+ }
+
ld = ldap_init(ds[i].host, ds[i].port);
if (ld == NULL) {
if (DBG(LDAP, 1)) {
@@ -896,60 +707,6 @@ ldap_lookup_init(idmap_ad_disc_ds_t *ds)
/*
- * 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 *
-ldap_lookup_entry_attr(LDAP **ld, idmap_ad_disc_ds_t *domainControllers,
- char **dn_list, char *attr)
-{
- int i;
- int rc;
- int scope = LDAP_SCOPE_BASE;
- char *attrs[2];
- LDAPMessage *results = NULL;
- LDAPMessage *entry;
- char **values = NULL;
- char *val = NULL;
-
- attrs[0] = attr;
- attrs[1] = NULL;
-
- if (*ld == NULL)
- *ld = ldap_lookup_init(domainControllers);
-
- if (*ld == NULL)
- return (NULL);
-
- for (i = 0; dn_list[i] != NULL; i++) {
- rc = ldap_search_s(*ld, dn_list[i], scope,
- "(objectclass=*)", attrs, 0, &results);
- if (rc == LDAP_SUCCESS) {
- for (entry = ldap_first_entry(*ld, results);
- entry != NULL && values == NULL;
- entry = ldap_next_entry(*ld, entry)) {
- values = ldap_get_values(
- *ld, entry, attr);
- }
-
- if (values != NULL) {
- (void) ldap_msgfree(results);
- val = strdup(values[0]);
- ldap_value_free(values);
- return (val);
- }
- }
- if (results != NULL) {
- (void) ldap_msgfree(results);
- results = NULL;
- }
- }
-
- return (NULL);
-}
-
-
-/*
* Lookup the trusted domains in the global catalog.
*
* Returns:
@@ -958,7 +715,7 @@ ldap_lookup_entry_attr(LDAP **ld, idmap_ad_disc_ds_t *domainControllers,
* NULL an error occured
*/
ad_disc_trusteddomains_t *
-ldap_lookup_trusted_domains(LDAP **ld, idmap_ad_disc_ds_t *globalCatalog,
+ldap_lookup_trusted_domains(LDAP **ld, ad_disc_ds_t *globalCatalog,
char *base_dn)
{
int scope = LDAP_SCOPE_SUBTREE;
@@ -978,8 +735,10 @@ ldap_lookup_trusted_domains(LDAP **ld, idmap_ad_disc_ds_t *globalCatalog,
if (*ld == NULL)
*ld = ldap_lookup_init(globalCatalog);
- if (*ld == NULL)
+ if (*ld == NULL) {
+ logger(LOG_ERR, "adutils: ldap_lookup_init failed");
return (NULL);
+ }
attrs[0] = "trustPartner";
attrs[1] = "trustDirection";
@@ -1039,6 +798,9 @@ ldap_lookup_trusted_domains(LDAP **ld, idmap_ad_disc_ds_t *globalCatalog,
trusted_domains = calloc(1, sizeof (ad_disc_trusteddomains_t));
if (DBG(DISC, 1))
logger(LOG_DEBUG, " not found");
+ } else {
+ if (DBG(DISC, 1))
+ logger(LOG_DEBUG, " rc=%d", rc);
}
if (results != NULL)
(void) ldap_msgfree(results);
@@ -1051,7 +813,7 @@ ldap_lookup_trusted_domains(LDAP **ld, idmap_ad_disc_ds_t *globalCatalog,
* This functions finds all the domains in a forest.
*/
ad_disc_domainsinforest_t *
-ldap_lookup_domains_in_forest(LDAP **ld, idmap_ad_disc_ds_t *globalCatalogs)
+ldap_lookup_domains_in_forest(LDAP **ld, ad_disc_ds_t *globalCatalogs)
{
static char *attrs[] = {
"objectSid",
@@ -1064,27 +826,34 @@ ldap_lookup_domains_in_forest(LDAP **ld, idmap_ad_disc_ds_t *globalCatalogs)
int nresults;
ad_disc_domainsinforest_t *domains = NULL;
- if (DBG(DISC, 2))
+ if (DBG(DISC, 1))
logger(LOG_DEBUG, "Looking for domains in forest...");
if (*ld == NULL)
*ld = ldap_lookup_init(globalCatalogs);
- if (*ld == NULL)
+ if (*ld == NULL) {
+ logger(LOG_ERR, "adutils: ldap_lookup_init failed");
return (NULL);
+ }
/* Find domains */
rc = ldap_search_s(*ld, "", LDAP_SCOPE_SUBTREE,
"(objectClass=Domain)", attrs, 0, &result);
+ if (rc != LDAP_SUCCESS) {
+ logger(LOG_ERR, "adutils: ldap_search, rc=%d", rc);
+ goto err;
+ }
if (DBG(DISC, 1))
logger(LOG_DEBUG, "Domains in forest:");
- if (rc != LDAP_SUCCESS)
- goto err;
nresults = ldap_count_entries(*ld, result);
domains = calloc(nresults + 1, sizeof (*domains));
- if (domains == NULL)
+ if (domains == NULL) {
+ if (DBG(DISC, 1))
+ logger(LOG_DEBUG, " (nomem)");
goto err;
+ }
for (entry = ldap_first_entry(*ld, result);
entry != NULL;
@@ -1162,7 +931,9 @@ ad_disc_init(void)
DO_RES_NINIT(ctx);
ctx->domain_name.type = AD_STRING;
+ ctx->domain_guid.type = AD_UUID;
ctx->domain_controller.type = AD_DIRECTORY;
+ ctx->preferred_dc.type = AD_DIRECTORY;
ctx->site_name.type = AD_STRING;
ctx->forest_name.type = AD_STRING;
ctx->global_catalog.type = AD_DIRECTORY;
@@ -1189,9 +960,15 @@ ad_disc_fini(ad_disc_t ctx)
if (ctx->domain_name.value != NULL)
free(ctx->domain_name.value);
+ if (ctx->domain_guid.value != NULL)
+ free(ctx->domain_guid.value);
+
if (ctx->domain_controller.value != NULL)
free(ctx->domain_controller.value);
+ if (ctx->preferred_dc.value != NULL)
+ free(ctx->preferred_dc.value);
+
if (ctx->site_name.value != NULL)
free(ctx->site_name.value);
@@ -1220,17 +997,25 @@ ad_disc_fini(ad_disc_t ctx)
void
ad_disc_refresh(ad_disc_t ctx)
{
- if (ctx->res_ninitted)
+ if (ctx->res_ninitted) {
res_ndestroy(&ctx->res_state);
+ ctx->res_ninitted = 0;
+ }
(void) memset(&ctx->res_state, 0, sizeof (ctx->res_state));
- ctx->res_ninitted = res_ninit(&ctx->res_state) != -1;
+ DO_RES_NINIT(ctx);
if (ctx->domain_name.state == AD_STATE_AUTO)
ctx->domain_name.state = AD_STATE_INVALID;
+ if (ctx->domain_guid.state == AD_STATE_AUTO)
+ ctx->domain_guid.state = AD_STATE_INVALID;
+
if (ctx->domain_controller.state == AD_STATE_AUTO)
ctx->domain_controller.state = AD_STATE_INVALID;
+ if (ctx->preferred_dc.state == AD_STATE_AUTO)
+ ctx->preferred_dc.state = AD_STATE_INVALID;
+
if (ctx->site_name.state == AD_STATE_AUTO)
ctx->site_name.state = AD_STATE_INVALID;
@@ -1270,15 +1055,77 @@ ad_disc_done(ad_disc_t ctx)
ctx->expires_not_after = now + MAXIMUM_TTL;
}
+static void
+log_cds(ad_disc_t ctx, ad_disc_cds_t *cds)
+{
+ char buf[INET6_ADDRSTRLEN];
+ struct addrinfo *ai;
+
+ if (!DBG(DISC, 1) && ctx->status_fp == NULL)
+ return;
+
+ DEBUG1STATUS(ctx, "Candidate servers:");
+ if (cds->cds_ds.host[0] == '\0') {
+ DEBUG1STATUS(ctx, " (empty list)");
+ return;
+ }
+
+ while (cds->cds_ds.host[0] != '\0') {
+
+ DEBUG1STATUS(ctx, " %s p=%d w=%d",
+ cds->cds_ds.host,
+ cds->cds_ds.priority,
+ cds->cds_ds.weight);
+
+ ai = cds->cds_ai;
+ if (ai == NULL) {
+ DEBUG1STATUS(ctx, " (no address)");
+ }
+ while (ai != NULL) {
+ int eai;
+
+ eai = getnameinfo(ai->ai_addr, ai->ai_addrlen,
+ buf, sizeof (buf), NULL, 0, NI_NUMERICHOST);
+ if (eai != 0)
+ (void) strlcpy(buf, "?", sizeof (buf));
+
+ DEBUG1STATUS(ctx, " %s", buf);
+ ai = ai->ai_next;
+ }
+ cds++;
+ }
+}
+
+static void
+log_ds(ad_disc_t ctx, ad_disc_ds_t *ds)
+{
+ char buf[INET6_ADDRSTRLEN];
+
+ if (!DBG(DISC, 1) && ctx->status_fp == NULL)
+ return;
+
+ DEBUG1STATUS(ctx, "Responding servers:");
+ if (ds->host[0] == '\0') {
+ DEBUG1STATUS(ctx, " (empty list)");
+ return;
+ }
+
+ while (ds->host[0] != '\0') {
+
+ DEBUG1STATUS(ctx, " %s", ds->host);
+ DO_GETNAMEINFO(buf, sizeof (buf), &ds->addr);
+ DEBUG1STATUS(ctx, " %s", buf);
+
+ ds++;
+ }
+}
/* Discover joined Active Directory domainName */
static ad_item_t *
validate_DomainName(ad_disc_t ctx)
{
- idmap_ad_disc_ds_t *domain_controller = NULL;
char *dname, *srvname;
- uint32_t ttl = 0;
- int len;
+ int len, rc;
if (is_valid(&ctx->domain_name))
return (&ctx->domain_name);
@@ -1286,23 +1133,21 @@ validate_DomainName(ad_disc_t ctx)
/* Try to find our domain by searching for DCs for it */
DO_RES_NINIT(ctx);
- if (DBG(DISC, 2))
+ if (DBG(DISC, 1))
logger(LOG_DEBUG, "Looking for our AD domain name...");
- domain_controller = srv_query(&ctx->res_state,
- LDAP_SRV_HEAD DC_SRV_TAIL,
- ctx->domain_name.value, &srvname, &ttl);
+ rc = srv_getdom(&ctx->res_state,
+ LDAP_SRV_HEAD DC_SRV_TAIL, &srvname);
/*
* 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) {
+ if (rc < 0) {
if (DBG(DISC, 1))
logger(LOG_DEBUG, "Can't find our domain name.");
return (NULL);
}
- free(domain_controller);
/*
* We have the FQDN of the SRV RR name, so now we extract the
* domainname suffix from it.
@@ -1324,7 +1169,14 @@ validate_DomainName(ad_disc_t ctx)
if (DBG(DISC, 1))
logger(LOG_DEBUG, "Our domain name: %s", dname);
- update_item(&ctx->domain_name, dname, AD_STATE_AUTO, ttl);
+
+ /*
+ * 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.
+ */
+ update_item(&ctx->domain_name, dname, AD_STATE_AUTO, 0);
return (&ctx->domain_name);
}
@@ -1354,12 +1206,16 @@ ad_disc_get_DomainName(ad_disc_t ctx, boolean_t *auto_discovered)
static ad_item_t *
validate_DomainController(ad_disc_t ctx, enum ad_disc_req req)
{
- uint32_t ttl = 0;
- idmap_ad_disc_ds_t *domain_controller = NULL;
+ ad_disc_ds_t *dc = NULL;
+ ad_disc_cds_t *cdc = NULL;
boolean_t validate_global = B_FALSE;
boolean_t validate_site = B_FALSE;
ad_item_t *domain_name_item;
+ char *domain_name;
ad_item_t *site_name_item = NULL;
+ char *site_name;
+ ad_item_t *prefer_dc_item;
+ ad_disc_ds_t *prefer_dc = NULL;
/* If the values is fixed there will not be a site specific version */
if (is_fixed(&ctx->domain_controller))
@@ -1368,12 +1224,17 @@ validate_DomainController(ad_disc_t ctx, enum ad_disc_req req)
domain_name_item = validate_DomainName(ctx);
if (domain_name_item == NULL)
return (NULL);
+ domain_name = (char *)domain_name_item->value;
+
+ /* Get (optional) preferred DC. */
+ prefer_dc_item = validate_PreferredDC(ctx);
+ if (prefer_dc_item != NULL)
+ prefer_dc = prefer_dc_item->value;
if (req == AD_DISC_GLOBAL)
validate_global = B_TRUE;
else {
- site_name_item = validate_SiteName(ctx);
- if (site_name_item != NULL)
+ if (is_fixed(&ctx->site_name))
validate_site = B_TRUE;
else if (req == AD_DISC_PREFER_SITE)
validate_global = B_TRUE;
@@ -1383,43 +1244,44 @@ validate_DomainController(ad_disc_t ctx, enum ad_disc_req req)
if (!is_valid(&ctx->domain_controller) ||
is_changed(&ctx->domain_controller, PARAM1,
domain_name_item)) {
- if (DBG(DISC, 2)) {
- logger(LOG_DEBUG, "Looking for DCs for %s",
- domain_name_item->value);
- }
+
/*
* Lookup DNS SRV RR named
* _ldap._tcp.dc._msdcs.<DomainName>
*/
+ DEBUG1STATUS(ctx, "DNS SRV query, dom=%s",
+ domain_name);
DO_RES_NINIT(ctx);
- domain_controller = srv_query(&ctx->res_state,
+ cdc = srv_query(&ctx->res_state,
LDAP_SRV_HEAD DC_SRV_TAIL,
- domain_name_item->value, NULL, &ttl);
+ domain_name, prefer_dc);
- if (DBG(DISC, 1)) {
- logger(LOG_DEBUG, "DCs for %s:",
- domain_name_item->value);
- }
- if (domain_controller == NULL) {
- if (DBG(DISC, 1))
- logger(LOG_DEBUG, " not found");
+ if (cdc == NULL) {
+ DEBUG1STATUS(ctx, "(no DNS response)");
return (NULL);
}
+ log_cds(ctx, cdc);
- if (DBG(DISC, 1)) {
- int i;
-
- for (i = 0;
- domain_controller[i].host[0] != '\0';
- i++) {
- logger(LOG_DEBUG, " %s:%d",
- domain_controller[i].host,
- domain_controller[i].port);
- }
+ /*
+ * Filter out unresponsive servers, and
+ * save the domain info we get back.
+ */
+ dc = ldap_ping(
+ ctx,
+ cdc,
+ domain_name,
+ DS_DS_FLAG);
+ srv_free(cdc);
+ cdc = NULL;
+
+ if (dc == NULL) {
+ DEBUG1STATUS(ctx, "(no LDAP response)");
+ return (NULL);
}
+ log_ds(ctx, dc);
- update_item(&ctx->domain_controller, domain_controller,
- AD_STATE_AUTO, ttl);
+ update_item(&ctx->domain_controller, dc,
+ AD_STATE_AUTO, dc->ttl);
update_version(&ctx->domain_controller, PARAM1,
domain_name_item);
}
@@ -1427,54 +1289,55 @@ validate_DomainController(ad_disc_t ctx, enum ad_disc_req req)
}
if (validate_site) {
+ site_name_item = &ctx->site_name;
+ site_name = (char *)site_name_item->value;
+
if (!is_valid(&ctx->site_domain_controller) ||
is_changed(&ctx->site_domain_controller, PARAM1,
domain_name_item) ||
is_changed(&ctx->site_domain_controller, PARAM2,
site_name_item)) {
char rr_name[DNS_MAX_NAME];
- if (DBG(DISC, 2)) {
- logger(LOG_DEBUG,
- "Looking for DCs for %s in %s",
- domain_name_item->value,
- site_name_item->value);
- }
+
/*
* Lookup DNS SRV RR named
* _ldap._tcp.<SiteName>._sites.dc._msdcs.<DomainName>
*/
+ DEBUG1STATUS(ctx, "DNS SRV query, dom=%s, site=%s",
+ domain_name, site_name);
(void) snprintf(rr_name, sizeof (rr_name),
LDAP_SRV_HEAD SITE_SRV_MIDDLE DC_SRV_TAIL,
- site_name_item->value);
+ site_name);
DO_RES_NINIT(ctx);
- domain_controller = srv_query(&ctx->res_state, rr_name,
- domain_name_item->value, NULL, &ttl);
- if (DBG(DISC, 1)) {
- logger(LOG_DEBUG,
- "DCs for %s in %s",
- domain_name_item->value,
- site_name_item->value);
- }
- if (domain_controller == NULL) {
- if (DBG(DISC, 1))
- logger(LOG_DEBUG, " not found");
+ cdc = srv_query(&ctx->res_state, rr_name,
+ domain_name, prefer_dc);
+
+ if (cdc == NULL) {
+ DEBUG1STATUS(ctx, "(no DNS response)");
return (NULL);
}
+ log_cds(ctx, cdc);
- if (DBG(DISC, 1)) {
- int i;
-
- for (i = 0;
- domain_controller[i].host[0] != '\0';
- i++) {
- logger(LOG_DEBUG, " %s:%d",
- domain_controller[i].host,
- domain_controller[i].port);
- }
+ /*
+ * Filter out unresponsive servers, and
+ * save the domain info we get back.
+ */
+ dc = ldap_ping(
+ ctx,
+ cdc,
+ domain_name,
+ DS_DS_FLAG);
+ srv_free(cdc);
+ cdc = NULL;
+
+ if (dc == NULL) {
+ DEBUG1STATUS(ctx, "(no LDAP response)");
+ return (NULL);
}
+ log_ds(ctx, dc);
- update_item(&ctx->site_domain_controller,
- domain_controller, AD_STATE_AUTO, ttl);
+ update_item(&ctx->site_domain_controller, dc,
+ AD_STATE_AUTO, dc->ttl);
update_version(&ctx->site_domain_controller, PARAM1,
domain_name_item);
update_version(&ctx->site_domain_controller, PARAM2,
@@ -1485,12 +1348,12 @@ validate_DomainController(ad_disc_t ctx, enum ad_disc_req req)
return (NULL);
}
-idmap_ad_disc_ds_t *
+ad_disc_ds_t *
ad_disc_get_DomainController(ad_disc_t ctx, enum ad_disc_req req,
- boolean_t *auto_discovered)
+ boolean_t *auto_discovered)
{
ad_item_t *domain_controller_item;
- idmap_ad_disc_ds_t *domain_controller = NULL;
+ ad_disc_ds_t *domain_controller = NULL;
domain_controller_item = validate_DomainController(ctx, req);
@@ -1506,161 +1369,68 @@ ad_disc_get_DomainController(ad_disc_t ctx, enum ad_disc_req req,
}
-/* Discover site name (for multi-homed systems the first one found wins) */
+/*
+ * Discover the Domain GUID
+ * This info comes from validate_DomainController()
+ */
static ad_item_t *
-validate_SiteName(ad_disc_t ctx)
+validate_DomainGUID(ad_disc_t ctx)
{
- LDAP *ld = NULL;
- ad_subnet_t *subnets = NULL;
- char **dn_subnets = NULL;
- char *dn_root[2];
- char *config_naming_context = NULL;
- char *site_object = NULL;
- char *site_name = NULL;
- char *forest_name;
- int len;
- boolean_t update_required = B_FALSE;
ad_item_t *domain_controller_item;
- if (is_fixed(&ctx->site_name))
- return (&ctx->site_name);
+ if (is_fixed(&ctx->domain_guid))
+ return (&ctx->domain_guid);
- /* Can't rely on site-specific DCs */
domain_controller_item = validate_DomainController(ctx, AD_DISC_GLOBAL);
if (domain_controller_item == NULL)
return (NULL);
- if (!is_valid(&ctx->site_name) ||
- is_changed(&ctx->site_name, PARAM1, domain_controller_item) ||
- ctx->subnets == NULL || ctx->subnets_changed) {
- subnets = find_subnets();
- ctx->subnets_last_check = time(NULL);
- update_required = B_TRUE;
- } else if (ctx->subnets_last_check + 60 < time(NULL)) {
- /* NEEDSWORK magic constant 60 above */
- subnets = find_subnets();
- ctx->subnets_last_check = time(NULL);
- if (cmpsubnets(ctx->subnets, subnets) != 0)
- update_required = B_TRUE;
- }
-
- if (!update_required) {
- free(subnets);
- return (&ctx->site_name);
- }
-
- if (subnets == NULL)
+ if (!is_valid(&ctx->domain_guid))
return (NULL);
- dn_root[0] = "";
- dn_root[1] = NULL;
+ return (&ctx->domain_guid);
+}
- if (DBG(DISC, 1))
- logger(LOG_DEBUG, "Getting site name");
- config_naming_context = ldap_lookup_entry_attr(
- &ld, ctx->domain_controller.value,
- dn_root, "configurationNamingContext");
- if (config_naming_context == NULL)
- goto out;
- /*
- * configurationNamingContext also provides the Forest
- * Name.
- */
- if (!is_fixed(&ctx->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,";
- int len = strlen(str);
- if (strncasecmp(config_naming_context, str, len) == 0) {
- forest_name = DN_to_DNS(config_naming_context + len);
- if (DBG(DISC, 1)) {
- logger(LOG_DEBUG, " forest: %s",
- forest_name);
- }
- update_item(&ctx->forest_name, forest_name,
- AD_STATE_AUTO, 0);
- update_version(&ctx->forest_name, PARAM1,
- domain_controller_item);
- }
- }
+uchar_t *
+ad_disc_get_DomainGUID(ad_disc_t ctx, boolean_t *auto_discovered)
+{
+ ad_item_t *domain_guid_item;
+ uchar_t *domain_guid = NULL;
- if (DBG(DISC, 2))
- logger(LOG_DEBUG, " CNC: %s", config_naming_context);
+ domain_guid_item = validate_DomainGUID(ctx);
+ if (domain_guid_item != NULL) {
+ domain_guid = uuid_dup(domain_guid_item->value);
+ if (auto_discovered != NULL)
+ *auto_discovered =
+ (domain_guid_item->state == AD_STATE_AUTO);
+ } else if (auto_discovered != NULL)
+ *auto_discovered = B_FALSE;
- if (DBG(DISC, 2)) {
- int i;
- logger(LOG_DEBUG, " Looking for sites for subnets:");
- for (i = 0; subnets[i].subnet[0] != '\0'; i++) {
- logger(LOG_DEBUG, " %s", subnets[i].subnet);
- }
- }
+ return (domain_guid);
+}
- dn_subnets = subnets_to_DNs(subnets, config_naming_context);
- if (dn_subnets == NULL)
- goto out;
-
- site_object = ldap_lookup_entry_attr(
- &ld, domain_controller_item->value,
- dn_subnets, "siteobject");
- if (site_object != NULL) {
- /*
- * The site object should be of the form
- * CN=<site>,CN=Sites,CN=Configuration,
- * <DN Domain>
- */
- if (DBG(DISC, 2))
- logger(LOG_DEBUG, " Site object: %s", site_object);
- if (strncasecmp(site_object, "CN=", 3) == 0) {
- for (len = 0; site_object[len + 3] != ','; len++)
- ;
- site_name = malloc(len + 1);
- (void) strncpy(site_name, &site_object[3], len);
- site_name[len] = '\0';
- if (DBG(DISC, 1)) {
- logger(LOG_DEBUG, " Site name \"%s\"",
- site_name);
- }
- update_item(&ctx->site_name, site_name,
- AD_STATE_AUTO, 0);
- update_version(&ctx->site_name, PARAM1,
- domain_controller_item);
- }
- }
- if (ctx->subnets != NULL) {
- free(ctx->subnets);
- ctx->subnets = NULL;
- }
- ctx->subnets = subnets;
- subnets = NULL;
- ctx->subnets_changed = B_FALSE;
+/*
+ * Discover site name (for multi-homed systems the first one found wins)
+ * This info comes from validate_DomainController()
+ */
+static ad_item_t *
+validate_SiteName(ad_disc_t ctx)
+{
+ ad_item_t *domain_controller_item;
-out:
- if (ld != NULL)
- (void) ldap_unbind(ld);
+ if (is_fixed(&ctx->site_name))
+ return (&ctx->site_name);
- if (dn_subnets != NULL) {
- int i;
- for (i = 0; dn_subnets[i] != NULL; i++)
- free(dn_subnets[i]);
- free(dn_subnets);
- }
- if (config_naming_context != NULL)
- free(config_naming_context);
- if (site_object != NULL)
- free(site_object);
+ domain_controller_item = validate_DomainController(ctx, AD_DISC_GLOBAL);
+ if (domain_controller_item == NULL)
+ return (NULL);
- free(subnets);
- if (site_name == NULL)
+ if (!is_valid(&ctx->site_name))
return (NULL);
- return (&ctx->site_name);
+ return (&ctx->site_name);
}
@@ -1684,69 +1454,25 @@ ad_disc_get_SiteName(ad_disc_t ctx, boolean_t *auto_discovered)
-/* Discover forest name */
+/*
+ * Discover forest name
+ * This info comes from validate_DomainController()
+ */
static ad_item_t *
validate_ForestName(ad_disc_t ctx)
{
- LDAP *ld = NULL;
- char *config_naming_context;
- char *forest_name = NULL;
- char *dn_list[2];
ad_item_t *domain_controller_item;
if (is_fixed(&ctx->forest_name))
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()?)
- */
+
domain_controller_item = validate_DomainController(ctx, AD_DISC_GLOBAL);
if (domain_controller_item == NULL)
return (NULL);
- if (!is_valid(&ctx->forest_name) ||
- is_changed(&ctx->forest_name, PARAM1, domain_controller_item)) {
-
- dn_list[0] = "";
- dn_list[1] = NULL;
- if (DBG(DISC, 1))
- logger(LOG_DEBUG, "Getting forest name");
- config_naming_context = ldap_lookup_entry_attr(
- &ld, ctx->domain_controller.value,
- 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,";
- int len = strlen(str);
- if (strncasecmp(config_naming_context, str, len) == 0) {
- forest_name = DN_to_DNS(
- config_naming_context + len);
- }
- free(config_naming_context);
- }
- if (ld != NULL)
- (void) ldap_unbind(ld);
-
- if (forest_name == NULL) {
- if (DBG(DISC, 1))
- logger(LOG_DEBUG, " not found");
- return (NULL);
- }
-
- if (DBG(DISC, 1))
- logger(LOG_DEBUG, " %s", forest_name);
+ if (!is_valid(&ctx->forest_name))
+ return (NULL);
- update_item(&ctx->forest_name, forest_name, AD_STATE_AUTO, 0);
- update_version(&ctx->forest_name, PARAM1,
- domain_controller_item);
- }
return (&ctx->forest_name);
}
@@ -1775,12 +1501,15 @@ ad_disc_get_ForestName(ad_disc_t ctx, boolean_t *auto_discovered)
static ad_item_t *
validate_GlobalCatalog(ad_disc_t ctx, enum ad_disc_req req)
{
- idmap_ad_disc_ds_t *global_catalog = NULL;
- uint32_t ttl = 0;
+ ad_disc_ds_t *gc = NULL;
+ ad_disc_cds_t *cgc = NULL;
boolean_t validate_global = B_FALSE;
boolean_t validate_site = B_FALSE;
+ ad_item_t *dc_item;
ad_item_t *forest_name_item;
ad_item_t *site_name_item;
+ char *forest_name;
+ char *site_name;
/* If the values is fixed there will not be a site specific version */
if (is_fixed(&ctx->global_catalog))
@@ -1789,12 +1518,12 @@ validate_GlobalCatalog(ad_disc_t ctx, enum ad_disc_req req)
forest_name_item = validate_ForestName(ctx);
if (forest_name_item == NULL)
return (NULL);
+ forest_name = (char *)forest_name_item->value;
if (req == AD_DISC_GLOBAL)
validate_global = B_TRUE;
else {
- site_name_item = validate_SiteName(ctx);
- if (site_name_item != NULL)
+ if (is_fixed(&ctx->site_name))
validate_site = B_TRUE;
else if (req == AD_DISC_PREFER_SITE)
validate_global = B_TRUE;
@@ -1804,40 +1533,63 @@ validate_GlobalCatalog(ad_disc_t ctx, enum ad_disc_req req)
if (!is_valid(&ctx->global_catalog) ||
is_changed(&ctx->global_catalog, PARAM1,
forest_name_item)) {
+
/*
- * Lookup DNS SRV RR named
+ * See if our DC is also a GC.
+ */
+ dc_item = validate_DomainController(ctx, req);
+ if (dc_item != NULL) {
+ ad_disc_ds_t *ds = dc_item->value;
+ if ((ds->flags & DS_GC_FLAG) != 0) {
+ DEBUG1STATUS(ctx,
+ "DC is also a GC for %s",
+ forest_name);
+ gc = ds_dup(ds);
+ if (gc != NULL) {
+ gc->port = GC_PORT;
+ goto update_global;
+ }
+ }
+ }
+
+ /*
+ * Lookup DNS SRV RR named:
* _ldap._tcp.gc._msdcs.<ForestName>
*/
+ DEBUG1STATUS(ctx, "DNS SRV query, forest=%s",
+ forest_name);
DO_RES_NINIT(ctx);
- global_catalog =
- srv_query(&ctx->res_state,
+ cgc = srv_query(&ctx->res_state,
LDAP_SRV_HEAD GC_SRV_TAIL,
- ctx->forest_name.value, NULL, &ttl);
+ forest_name, NULL);
- if (DBG(DISC, 1)) {
- logger(LOG_DEBUG,
- "GC servers for %s:",
- ctx->forest_name.value);
- }
- if (global_catalog == NULL) {
- if (DBG(DISC, 1))
- logger(LOG_DEBUG, " not found");
+ if (cgc == NULL) {
+ DEBUG1STATUS(ctx, "(no DNS response)");
return (NULL);
}
+ log_cds(ctx, cgc);
- if (DBG(DISC, 1)) {
- int i;
- for (i = 0;
- global_catalog[i].host[0] != '\0';
- i++) {
- logger(LOG_DEBUG, " %s:%d",
- global_catalog[i].host,
- global_catalog[i].port);
- }
+ /*
+ * Filter out unresponsive servers, and
+ * save the domain info we get back.
+ */
+ gc = ldap_ping(
+ NULL,
+ cgc,
+ forest_name,
+ DS_GC_FLAG);
+ srv_free(cgc);
+ cgc = NULL;
+
+ if (gc == NULL) {
+ DEBUG1STATUS(ctx, "(no LDAP response)");
+ return (NULL);
}
+ log_ds(ctx, gc);
- update_item(&ctx->global_catalog, global_catalog,
- AD_STATE_AUTO, ttl);
+ update_global:
+ update_item(&ctx->global_catalog, gc,
+ AD_STATE_AUTO, gc->ttl);
update_version(&ctx->global_catalog, PARAM1,
forest_name_item);
}
@@ -1845,51 +1597,75 @@ validate_GlobalCatalog(ad_disc_t ctx, enum ad_disc_req req)
}
if (validate_site) {
+ site_name_item = &ctx->site_name;
+ site_name = (char *)site_name_item->value;
+
if (!is_valid(&ctx->site_global_catalog) ||
is_changed(&ctx->site_global_catalog, PARAM1,
forest_name_item) ||
is_changed(&ctx->site_global_catalog, PARAM2,
site_name_item)) {
- char rr_name[DNS_MAX_NAME];
+ char rr_name[DNS_MAX_NAME];
+
+ /*
+ * See if our DC is also a GC.
+ */
+ dc_item = validate_DomainController(ctx, req);
+ if (dc_item != NULL) {
+ ad_disc_ds_t *ds = dc_item->value;
+ if ((ds->flags & DS_GC_FLAG) != 0) {
+ DEBUG1STATUS(ctx,
+ "DC is also a GC for %s in %s",
+ forest_name, site_name);
+ gc = ds_dup(ds);
+ if (gc != NULL) {
+ gc->port = GC_PORT;
+ goto update_site;
+ }
+ }
+ }
/*
* Lookup DNS SRV RR named:
* _ldap._tcp.<siteName>._sites.gc.
* _msdcs.<ForestName>
*/
- (void) snprintf(rr_name,
- sizeof (rr_name),
+ DEBUG1STATUS(ctx, "DNS SRV query, forest=%s, site=%s",
+ forest_name, site_name);
+ (void) snprintf(rr_name, sizeof (rr_name),
LDAP_SRV_HEAD SITE_SRV_MIDDLE GC_SRV_TAIL,
- ctx->site_name.value);
+ site_name);
DO_RES_NINIT(ctx);
- global_catalog = srv_query(&ctx->res_state, rr_name,
- ctx->forest_name.value, NULL, &ttl);
+ cgc = srv_query(&ctx->res_state, rr_name,
+ forest_name, NULL);
- if (DBG(DISC, 1)) {
- logger(LOG_DEBUG,
- "GC servers for %s in %s",
- ctx->forest_name.value,
- ctx->site_name.value);
- }
- if (global_catalog == NULL) {
- if (DBG(DISC, 1))
- logger(LOG_DEBUG, " not found");
+ if (cgc == NULL) {
+ DEBUG1STATUS(ctx, "(no DNS response)");
return (NULL);
}
+ log_cds(ctx, cgc);
- if (DBG(DISC, 1)) {
- int i;
- for (i = 0;
- global_catalog[i].host[0] != '\0';
- i++) {
- logger(LOG_DEBUG, " %s:%d",
- global_catalog[i].host,
- global_catalog[i].port);
- }
+ /*
+ * Filter out unresponsive servers, and
+ * save the domain info we get back.
+ */
+ gc = ldap_ping(
+ NULL,
+ cgc,
+ forest_name,
+ DS_GC_FLAG);
+ srv_free(cgc);
+ cgc = NULL;
+
+ if (gc == NULL) {
+ DEBUG1STATUS(ctx, "(no LDAP response)");
+ return (NULL);
}
+ log_ds(ctx, gc);
- update_item(&ctx->site_global_catalog, global_catalog,
- AD_STATE_AUTO, ttl);
+ update_site:
+ update_item(&ctx->site_global_catalog, gc,
+ AD_STATE_AUTO, gc->ttl);
update_version(&ctx->site_global_catalog, PARAM1,
forest_name_item);
update_version(&ctx->site_global_catalog, PARAM2,
@@ -1901,11 +1677,11 @@ validate_GlobalCatalog(ad_disc_t ctx, enum ad_disc_req req)
}
-idmap_ad_disc_ds_t *
+ad_disc_ds_t *
ad_disc_get_GlobalCatalog(ad_disc_t ctx, enum ad_disc_req req,
boolean_t *auto_discovered)
{
- idmap_ad_disc_ds_t *global_catalog = NULL;
+ ad_disc_ds_t *global_catalog = NULL;
ad_item_t *global_catalog_item;
global_catalog_item = validate_GlobalCatalog(ctx, req);
@@ -2059,6 +1835,33 @@ ad_disc_get_DomainsInForest(ad_disc_t ctx, boolean_t *auto_discovered)
return (domains_in_forest);
}
+static ad_item_t *
+validate_PreferredDC(ad_disc_t ctx)
+{
+ if (is_valid(&ctx->preferred_dc))
+ return (&ctx->preferred_dc);
+
+ return (NULL);
+}
+
+ad_disc_ds_t *
+ad_disc_get_PreferredDC(ad_disc_t ctx, boolean_t *auto_discovered)
+{
+ ad_disc_ds_t *preferred_dc = NULL;
+ ad_item_t *preferred_dc_item;
+
+ preferred_dc_item = validate_PreferredDC(ctx);
+
+ if (preferred_dc_item != NULL) {
+ preferred_dc = ds_dup(preferred_dc_item->value);
+ if (auto_discovered != NULL)
+ *auto_discovered =
+ (preferred_dc_item->state == AD_STATE_AUTO);
+ } else if (auto_discovered != NULL)
+ *auto_discovered = B_FALSE;
+
+ return (preferred_dc);
+}
@@ -2077,12 +1880,40 @@ ad_disc_set_DomainName(ad_disc_t ctx, const char *domainName)
return (0);
}
+int
+ad_disc_set_DomainGUID(ad_disc_t ctx, uchar_t *u)
+{
+ char *domain_guid = NULL;
+ if (u != NULL) {
+ domain_guid = uuid_dup(u);
+ if (domain_guid == NULL)
+ return (-1);
+ update_item(&ctx->domain_guid, domain_guid,
+ AD_STATE_FIXED, 0);
+ } else if (ctx->domain_guid.state == AD_STATE_FIXED)
+ ctx->domain_guid.state = AD_STATE_INVALID;
+ return (0);
+}
+
+void
+auto_set_DomainGUID(ad_disc_t ctx, uchar_t *u)
+{
+ char *domain_guid = NULL;
+
+ if (is_fixed(&ctx->domain_guid))
+ return;
+
+ domain_guid = uuid_dup(u);
+ if (domain_guid == NULL)
+ return;
+ update_item(&ctx->domain_guid, domain_guid, AD_STATE_AUTO, 0);
+}
int
ad_disc_set_DomainController(ad_disc_t ctx,
- const idmap_ad_disc_ds_t *domainController)
+ const ad_disc_ds_t *domainController)
{
- idmap_ad_disc_ds_t *domain_controller = NULL;
+ ad_disc_ds_t *domain_controller = NULL;
if (domainController != NULL) {
domain_controller = ds_dup(domainController);
if (domain_controller == NULL)
@@ -2094,7 +1925,6 @@ ad_disc_set_DomainController(ad_disc_t ctx,
return (0);
}
-
int
ad_disc_set_SiteName(ad_disc_t ctx, const char *siteName)
{
@@ -2109,6 +1939,20 @@ ad_disc_set_SiteName(ad_disc_t ctx, const char *siteName)
return (0);
}
+void
+auto_set_SiteName(ad_disc_t ctx, char *siteName)
+{
+ char *site_name = NULL;
+
+ if (is_fixed(&ctx->site_name))
+ return;
+
+ site_name = strdup(siteName);
+ if (site_name == NULL)
+ return;
+ update_item(&ctx->site_name, site_name, AD_STATE_AUTO, 0);
+}
+
int
ad_disc_set_ForestName(ad_disc_t ctx, const char *forestName)
{
@@ -2124,11 +1968,25 @@ ad_disc_set_ForestName(ad_disc_t ctx, const char *forestName)
return (0);
}
+void
+auto_set_ForestName(ad_disc_t ctx, char *forestName)
+{
+ char *forest_name = NULL;
+
+ if (is_fixed(&ctx->forest_name))
+ return;
+
+ forest_name = strdup(forestName);
+ if (forest_name == NULL)
+ return;
+ update_item(&ctx->forest_name, forest_name, AD_STATE_AUTO, 0);
+}
+
int
ad_disc_set_GlobalCatalog(ad_disc_t ctx,
- const idmap_ad_disc_ds_t *globalCatalog)
+ const ad_disc_ds_t *globalCatalog)
{
- idmap_ad_disc_ds_t *global_catalog = NULL;
+ ad_disc_ds_t *global_catalog = NULL;
if (globalCatalog != NULL) {
global_catalog = ds_dup(globalCatalog);
if (global_catalog == NULL)
@@ -2140,6 +1998,27 @@ ad_disc_set_GlobalCatalog(ad_disc_t ctx,
return (0);
}
+int
+ad_disc_set_PreferredDC(ad_disc_t ctx, const ad_disc_ds_t *pref_dc)
+{
+ ad_disc_ds_t *new_pref_dc = NULL;
+ if (pref_dc != NULL) {
+ new_pref_dc = ds_dup(pref_dc);
+ if (new_pref_dc == NULL)
+ return (-1);
+ update_item(&ctx->preferred_dc, new_pref_dc,
+ AD_STATE_FIXED, 0);
+ } else if (ctx->preferred_dc.state == AD_STATE_FIXED)
+ ctx->preferred_dc.state = AD_STATE_INVALID;
+ return (0);
+}
+
+void
+ad_disc_set_StatusFP(ad_disc_t ctx, struct __FILE_TAG *fp)
+{
+ ctx->status_fp = fp;
+}
+
int
ad_disc_unset(ad_disc_t ctx)
@@ -2150,6 +2029,9 @@ ad_disc_unset(ad_disc_t ctx)
if (ctx->domain_controller.state == AD_STATE_FIXED)
ctx->domain_controller.state = AD_STATE_INVALID;
+ if (ctx->preferred_dc.state == AD_STATE_FIXED)
+ ctx->preferred_dc.state = AD_STATE_INVALID;
+
if (ctx->site_name.state == AD_STATE_FIXED)
ctx->site_name.state = AD_STATE_INVALID;
diff --git a/usr/src/lib/libadutils/common/addisc.h b/usr/src/lib/libadutils/common/addisc.h
index 786e79a66d..b3a0b44cc3 100644
--- a/usr/src/lib/libadutils/common/addisc.h
+++ b/usr/src/lib/libadutils/common/addisc.h
@@ -21,12 +21,14 @@
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
*/
#ifndef _ADINFO_H
#define _ADINFO_H
-#include <rpcsvc/idmap_prot.h>
+#include <sys/socket.h>
+#include <sys/uuid.h>
#include "libadutils.h"
@@ -41,6 +43,7 @@ extern "C" {
*/
#define MAXSTRSID 185
#define MAXDOMAINNAME 256
+#define AD_DISC_MAXHOSTNAME 256
typedef struct ad_disc *ad_disc_t;
@@ -66,6 +69,24 @@ enum ad_disc_req {
AD_DISC_GLOBAL /* Request global version */
};
+/*
+ * First four members of this are like idmap_ad_disc_ds_t
+ * (for compatiblity) until that can be eliminated.
+ * See PROP_DOMAIN_CONTROLLER in idmapd/server.c
+ */
+typedef struct ad_disc_ds {
+ /* Keep these first four in sync with idmap_ad_disc_ds_t */
+ int port;
+ int priority;
+ int weight;
+ char host[AD_DISC_MAXHOSTNAME];
+ /* Members after this are private and free to change. */
+ char site[AD_DISC_MAXHOSTNAME];
+ struct sockaddr_storage addr;
+ uint32_t flags;
+ uint32_t ttl;
+} ad_disc_ds_t;
+
ad_disc_t ad_disc_init(void);
void ad_disc_fini(ad_disc_t);
@@ -76,17 +97,23 @@ void ad_disc_fini(ad_disc_t);
char *
ad_disc_get_DomainName(ad_disc_t ctx, boolean_t *auto_discovered);
-idmap_ad_disc_ds_t *
+uchar_t *
+ad_disc_get_DomainGUID(ad_disc_t ctx, boolean_t *auto_discovered);
+
+ad_disc_ds_t *
ad_disc_get_DomainController(ad_disc_t ctx,
enum ad_disc_req req, boolean_t *auto_discovered);
+ad_disc_ds_t *
+ad_disc_get_PreferredDC(ad_disc_t ctx, boolean_t *auto_discovered);
+
char *
ad_disc_get_SiteName(ad_disc_t ctx, boolean_t *auto_discovered);
char *
ad_disc_get_ForestName(ad_disc_t ctx, boolean_t *auto_discovered);
-idmap_ad_disc_ds_t *
+ad_disc_ds_t *
ad_disc_get_GlobalCatalog(ad_disc_t ctx, enum ad_disc_req,
boolean_t *auto_discovered);
@@ -105,8 +132,13 @@ int
ad_disc_set_DomainName(ad_disc_t ctx, const char *domainName);
int
+ad_disc_set_DomainGUID(ad_disc_t ctx, uchar_t *u);
+
+int
ad_disc_set_DomainController(ad_disc_t ctx,
- const idmap_ad_disc_ds_t *domainController);
+ const ad_disc_ds_t *domainController);
+int
+ad_disc_set_PreferredDC(ad_disc_t ctx, const ad_disc_ds_t *dc);
int
ad_disc_set_SiteName(ad_disc_t ctx, const char *siteName);
@@ -116,8 +148,17 @@ ad_disc_set_ForestName(ad_disc_t ctx, const char *forestName);
int
ad_disc_set_GlobalCatalog(ad_disc_t ctx,
- const idmap_ad_disc_ds_t *globalCatalog);
+ const ad_disc_ds_t *globalCatalog);
+
+/*
+ * This function sets a FILE * on which this library will write
+ * progress information during DC Location.
+ */
+void
+ad_disc_set_StatusFP(ad_disc_t ctx, struct __FILE_TAG *);
+int
+ad_disc_getnameinfo(char *, int, struct sockaddr_storage *);
/*
* This routine forces all auto discovery item to be recomputed
@@ -140,7 +181,9 @@ boolean_t ad_disc_SubnetChanged(ad_disc_t);
/* This routine returns the Time To Live for auto discovered items */
int ad_disc_get_TTL(ad_disc_t);
-int ad_disc_compare_ds(idmap_ad_disc_ds_t *ds1, idmap_ad_disc_ds_t *ds2);
+int ad_disc_compare_uuid(uuid_t *u1, uuid_t *u2);
+
+int ad_disc_compare_ds(ad_disc_ds_t *ds1, ad_disc_ds_t *ds2);
int ad_disc_compare_trusteddomains(ad_disc_trusteddomains_t *td1,
ad_disc_trusteddomains_t *td2);
diff --git a/usr/src/lib/libadutils/common/addisc_impl.h b/usr/src/lib/libadutils/common/addisc_impl.h
new file mode 100644
index 0000000000..ce26ce256b
--- /dev/null
+++ b/usr/src/lib/libadutils/common/addisc_impl.h
@@ -0,0 +1,130 @@
+/*
+ * 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 (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
+ */
+
+#ifndef _ADDISC_IMPL_H
+#define _ADDISC_IMPL_H
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <resolv.h>
+#include <ldap.h>
+#include <pthread.h>
+#include "addisc.h"
+#include "libadutils.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+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,
+ AD_UUID,
+ AD_DIRECTORY,
+ AD_DOMAINS_IN_FOREST,
+ AD_TRUSTED_DOMAINS
+ };
+
+
+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;
+ time_t expires;
+ 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;
+ ad_subnet_t *subnets;
+ boolean_t subnets_changed;
+ time_t subnets_last_check;
+ time_t expires_not_before;
+ time_t expires_not_after;
+ ad_item_t domain_name; /* DNS hostname string */
+ ad_item_t domain_guid; /* Domain UUID (binary) */
+ ad_item_t domain_controller; /* Directory hostname and */
+ /* port array */
+ ad_item_t preferred_dc;
+ ad_item_t site_name; /* String */
+ ad_item_t forest_name; /* DNS forestname string */
+ ad_item_t global_catalog; /* Directory hostname and */
+ /* port array */
+ ad_item_t domains_in_forest; /* DNS domainname and SID */
+ /* array */
+ ad_item_t trusted_domains; /* DNS domainname and trust */
+ /* direction array */
+ /* Site specfic versions */
+ ad_item_t site_domain_controller; /* Directory hostname and */
+ /* port array */
+ ad_item_t site_global_catalog; /* Directory hostname and */
+ /* port array */
+ /* Optional FILE * for DC Location status. */
+ struct __FILE_TAG *status_fp;
+
+ int debug[AD_DEBUG_MAX+1]; /* Debug levels */
+} ad_disc;
+
+/* Candidate Directory Servers (CDS) */
+typedef struct ad_disc_cds {
+ struct ad_disc_ds cds_ds;
+ struct addrinfo *cds_ai;
+} ad_disc_cds_t;
+
+ad_disc_ds_t *ldap_ping(ad_disc_t, ad_disc_cds_t *, char *, int);
+
+int srv_getdom(res_state, const char *, char **);
+ad_disc_cds_t *srv_query(res_state, const char *, const char *,
+ ad_disc_ds_t *);
+void srv_free(ad_disc_cds_t *);
+
+void auto_set_DomainGUID(ad_disc_t, uchar_t *);
+void auto_set_ForestName(ad_disc_t, char *);
+void auto_set_SiteName(ad_disc_t, char *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ADDISC_IMPL_H */
diff --git a/usr/src/lib/libadutils/common/adutils_impl.h b/usr/src/lib/libadutils/common/adutils_impl.h
index f82282ff97..d5d41a6768 100644
--- a/usr/src/lib/libadutils/common/adutils_impl.h
+++ b/usr/src/lib/libadutils/common/adutils_impl.h
@@ -20,6 +20,7 @@
*/
/*
* Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
*/
#ifndef _ADUTILS_IMPL_H
@@ -31,7 +32,6 @@
#include <ldap.h>
#include <pthread.h>
#include "addisc.h"
-#include <rpcsvc/idmap_prot.h>
#include "libadutils.h"
#ifdef __cplusplus
diff --git a/usr/src/lib/libadutils/common/ldap_ping.c b/usr/src/lib/libadutils/common/ldap_ping.c
new file mode 100644
index 0000000000..5008372d74
--- /dev/null
+++ b/usr/src/lib/libadutils/common/ldap_ping.c
@@ -0,0 +1,677 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <net/if.h>
+#include <net/if.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <resolv.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+#include <ldap.h>
+#include <lber.h>
+#include <syslog.h>
+#include "adutils_impl.h"
+#include "addisc_impl.h"
+
+#define LDAP_PORT 389
+
+#define NETLOGON_ATTR_NAME "NetLogon"
+#define NETLOGON_NT_VERSION_1 0x00000001
+#define NETLOGON_NT_VERSION_5 0x00000002
+#define NETLOGON_NT_VERSION_5EX 0x00000004
+#define NETLOGON_NT_VERSION_5EX_WITH_IP 0x00000008
+#define NETLOGON_NT_VERSION_WITH_CLOSEST_SITE 0x00000010
+#define NETLOGON_NT_VERSION_AVOID_NT4EMUL 0x01000000
+
+typedef enum {
+ OPCODE = 0,
+ SBZ,
+ FLAGS,
+ DOMAIN_GUID,
+ FOREST_NAME,
+ DNS_DOMAIN_NAME,
+ DNS_HOST_NAME,
+ NET_DOMAIN_NAME,
+ NET_COMP_NAME,
+ USER_NAME,
+ DC_SITE_NAME,
+ CLIENT_SITE_NAME,
+ SOCKADDR_SIZE,
+ SOCKADDR,
+ NEXT_CLOSEST_SITE_NAME,
+ NTVER,
+ LM_NT_TOKEN,
+ LM_20_TOKEN
+} field_5ex_t;
+
+struct _berelement {
+ char *ber_buf;
+ char *ber_ptr;
+ char *ber_end;
+};
+
+extern int ldap_put_filter(BerElement *ber, char *);
+static void send_to_cds(ad_disc_cds_t *, char *, size_t, int);
+static ad_disc_cds_t *find_cds_by_addr(ad_disc_cds_t *, struct sockaddr_in6 *);
+static boolean_t addrmatch(struct addrinfo *, struct sockaddr_in6 *);
+static void save_ai(ad_disc_cds_t *, struct addrinfo *);
+
+static void
+cldap_escape_le64(char *buf, uint64_t val, int bytes)
+{
+ char *p = buf;
+
+ while (bytes != 0) {
+ p += sprintf(p, "\\%.2x", (uint8_t)(val & 0xff));
+ val >>= 8;
+ bytes--;
+ }
+ *p = '\0';
+}
+
+/*
+ * Construct CLDAPMessage PDU for NetLogon search request.
+ *
+ * CLDAPMessage ::= SEQUENCE {
+ * messageID MessageID,
+ * protocolOp searchRequest SearchRequest;
+ * }
+ *
+ * SearchRequest ::=
+ * [APPLICATION 3] SEQUENCE {
+ * baseObject LDAPDN,
+ * scope ENUMERATED {
+ * baseObject (0),
+ * singleLevel (1),
+ * wholeSubtree (2)
+ * },
+ * derefAliases ENUMERATED {
+ * neverDerefAliases (0),
+ * derefInSearching (1),
+ * derefFindingBaseObj (2),
+ * derefAlways (3)
+ * },
+ * sizeLimit INTEGER (0 .. MaxInt),
+ * timeLimit INTEGER (0 .. MaxInt),
+ * attrsOnly BOOLEAN,
+ * filter Filter,
+ * attributes SEQUENCE OF AttributeType
+ * }
+ */
+BerElement *
+cldap_build_request(const char *dname,
+ const char *host, uint32_t ntver, uint16_t msgid)
+{
+ BerElement *ber;
+ int len = 0;
+ char *basedn = "";
+ int scope = LDAP_SCOPE_BASE, deref = LDAP_DEREF_NEVER,
+ sizelimit = 0, timelimit = 0, attrsonly = 0;
+ char filter[512];
+ char ntver_esc[13];
+ char *p, *pend;
+
+ /*
+ * Construct search filter in LDAP format.
+ */
+ p = filter;
+ pend = p + sizeof (filter);
+
+ len = snprintf(p, pend - p, "(&(DnsDomain=%s)", dname);
+ if (len >= (pend - p))
+ goto fail;
+ p += len;
+
+ if (host != NULL) {
+ len = snprintf(p, (pend - p), "(Host=%s)", host);
+ if (len >= (pend - p))
+ goto fail;
+ p += len;
+ }
+
+ if (ntver != 0) {
+ /*
+ * Format NtVer as little-endian with LDAPv3 escapes.
+ */
+ cldap_escape_le64(ntver_esc, ntver, sizeof (ntver));
+ len = snprintf(p, (pend - p), "(NtVer=%s)", ntver_esc);
+ if (len >= (pend - p))
+ goto fail;
+ p += len;
+ }
+
+ len = snprintf(p, pend - p, ")");
+ if (len >= (pend - p))
+ goto fail;
+ p += len;
+
+ /*
+ * Encode CLDAPMessage and beginning of SearchRequest sequence.
+ */
+
+ if ((ber = ber_alloc()) == NULL)
+ goto fail;
+
+ if (ber_printf(ber, "{it{seeiib", msgid,
+ LDAP_REQ_SEARCH, basedn, scope, deref,
+ sizelimit, timelimit, attrsonly) < 0)
+ goto fail;
+
+ /*
+ * Encode Filter sequence.
+ */
+ if (ldap_put_filter(ber, filter) < 0)
+ goto fail;
+ /*
+ * Encode attribute and close Filter and SearchRequest sequences.
+ */
+ if (ber_printf(ber, "{s}}}", NETLOGON_ATTR_NAME) < 0)
+ goto fail;
+
+ /*
+ * Success
+ */
+ return (ber);
+
+fail:
+ if (ber != NULL)
+ ber_free(ber, 1);
+ return (NULL);
+}
+
+/*
+ * Parse incoming search responses and attribute to correct hosts.
+ *
+ * CLDAPMessage ::= SEQUENCE {
+ * messageID MessageID,
+ * searchResponse SEQUENCE OF
+ * SearchResponse;
+ * }
+ *
+ * SearchResponse ::=
+ * CHOICE {
+ * entry [APPLICATION 4] SEQUENCE {
+ * objectName LDAPDN,
+ * attributes SEQUENCE OF SEQUENCE {
+ * AttributeType,
+ * SET OF
+ * AttributeValue
+ * }
+ * },
+ * resultCode [APPLICATION 5] LDAPResult
+ * }
+ */
+
+static int
+decode_name(uchar_t *base, uchar_t *cp, char *str)
+{
+ uchar_t *tmp = NULL, *st = cp;
+ uint8_t len;
+
+ /*
+ * there should probably be some boundary checks on str && cp
+ * maybe pass in strlen && msglen ?
+ */
+ while (*cp != 0) {
+ if (*cp == 0xc0) {
+ if (tmp == NULL)
+ tmp = cp + 2;
+ cp = base + *(cp + 1);
+ }
+ for (len = *cp++; len > 0; len--)
+ *str++ = *cp++;
+ *str++ = '.';
+ }
+ if (cp != st)
+ *(str-1) = '\0';
+ else
+ *str = '\0';
+
+ return ((tmp == NULL ? cp + 1 : tmp) - st);
+}
+
+static int
+cldap_parse(ad_disc_t ctx, ad_disc_cds_t *cds, BerElement *ber)
+{
+ ad_disc_ds_t *dc = &cds->cds_ds;
+ uchar_t *base = NULL, *cp = NULL;
+ char val[512]; /* how big should val be? */
+ int l, msgid, rc = 0;
+ uint16_t opcode;
+ field_5ex_t f = OPCODE;
+
+ /*
+ * Later, compare msgid's/some validation?
+ */
+
+ if (ber_scanf(ber, "{i{x{{x[la", &msgid, &l, &cp) == LBER_ERROR) {
+ rc = 1;
+ goto out;
+ }
+
+ for (base = cp; ((cp - base) < l) && (f <= LM_20_TOKEN); f++) {
+ val[0] = '\0';
+ switch (f) {
+ case OPCODE:
+ /* opcode = *(uint16_t *)cp; */
+ /* cp +=2; */
+ opcode = *cp++;
+ opcode |= (*cp++ << 8);
+ break;
+ case SBZ:
+ cp += 2;
+ break;
+ case FLAGS:
+ /* dci->Flags = *(uint32_t *)cp; */
+ /* cp +=4; */
+ dc->flags = *cp++;
+ dc->flags |= (*cp++ << 8);
+ dc->flags |= (*cp++ << 16);
+ dc->flags |= (*cp++ << 26);
+ break;
+ case DOMAIN_GUID:
+ if (ctx != NULL)
+ auto_set_DomainGUID(ctx, cp);
+ cp += 16;
+ break;
+ case FOREST_NAME:
+ cp += decode_name(base, cp, val);
+ if (ctx != NULL)
+ auto_set_ForestName(ctx, val);
+ break;
+ case DNS_DOMAIN_NAME:
+ /*
+ * We always have this already.
+ * (Could validate it here.)
+ */
+ cp += decode_name(base, cp, val);
+ break;
+ case DNS_HOST_NAME:
+ cp += decode_name(base, cp, val);
+ if (0 != strcasecmp(val, dc->host)) {
+ logger(LOG_ERR, "DC name %s != %s?",
+ val, dc->host);
+ }
+ break;
+ case NET_DOMAIN_NAME:
+ /*
+ * This is the "Flat" domain name.
+ * (i.e. the NetBIOS name)
+ * ignore for now.
+ */
+ cp += decode_name(base, cp, val);
+ break;
+ case NET_COMP_NAME:
+ /* not needed */
+ cp += decode_name(base, cp, val);
+ break;
+ case USER_NAME:
+ /* not needed */
+ cp += decode_name(base, cp, val);
+ break;
+ case DC_SITE_NAME:
+ cp += decode_name(base, cp, val);
+ (void) strlcpy(dc->site, val, sizeof (dc->site));
+ break;
+ case CLIENT_SITE_NAME:
+ cp += decode_name(base, cp, val);
+ if (ctx != NULL)
+ auto_set_SiteName(ctx, val);
+ break;
+ /*
+ * These are all possible, but we don't really care about them.
+ * Sockaddr_size && sockaddr might be useful at some point
+ */
+ case SOCKADDR_SIZE:
+ case SOCKADDR:
+ case NEXT_CLOSEST_SITE_NAME:
+ case NTVER:
+ case LM_NT_TOKEN:
+ case LM_20_TOKEN:
+ break;
+ default:
+ rc = 3;
+ goto out;
+ }
+ }
+
+out:
+ if (base)
+ free(base);
+ else if (cp)
+ free(cp);
+ return (rc);
+}
+
+
+/*
+ * Filter out unresponsive servers, and save the domain info
+ * returned by the "LDAP ping" in the returned object.
+ * If ctx != NULL, this is a query for a DC, in which case we
+ * also save the Domain GUID, Site name, and Forest name as
+ * "auto" (discovered) values in the ctx.
+ *
+ * Only return the "winner". (We only want one DC/GC)
+ */
+ad_disc_ds_t *
+ldap_ping(ad_disc_t ctx, ad_disc_cds_t *dclist, char *dname, int reqflags)
+{
+ struct sockaddr_in6 addr6;
+ socklen_t addrlen;
+ struct pollfd pingchk;
+ ad_disc_cds_t *send_ds;
+ ad_disc_cds_t *recv_ds = NULL;
+ ad_disc_ds_t *ret_ds = NULL;
+ BerElement *req = NULL;
+ BerElement *res = NULL;
+ struct _berelement *be, *rbe;
+ size_t be_len, rbe_len;
+ int fd = -1;
+ int tries = 3;
+ int waitsec;
+ int r;
+ uint16_t msgid;
+
+ /* One plus a null entry. */
+ ret_ds = calloc(2, sizeof (ad_disc_ds_t));
+ if (ret_ds == NULL)
+ goto fail;
+
+ if ((fd = socket(PF_INET6, SOCK_DGRAM, 0)) < 0)
+ goto fail;
+
+ (void) memset(&addr6, 0, sizeof (addr6));
+ addr6.sin6_family = AF_INET6;
+ addr6.sin6_addr = in6addr_any;
+ if (bind(fd, (struct sockaddr *)&addr6, sizeof (addr6)) < 0)
+ goto fail;
+
+ /*
+ * semi-unique msgid...
+ */
+ msgid = gethrtime() & 0xffff;
+
+ /*
+ * Is ntver right? It certainly works on w2k8... If others are needed,
+ * that might require changes to cldap_parse
+ */
+ req = cldap_build_request(dname, NULL,
+ NETLOGON_NT_VERSION_5EX, msgid);
+ if (req == NULL)
+ goto fail;
+ be = (struct _berelement *)req;
+ be_len = be->ber_end - be->ber_buf;
+
+ if ((res = ber_alloc()) == NULL)
+ goto fail;
+ rbe = (struct _berelement *)res;
+ rbe_len = rbe->ber_end - rbe->ber_buf;
+
+ pingchk.fd = fd;
+ pingchk.events = POLLIN;
+ pingchk.revents = 0;
+
+try_again:
+ send_ds = dclist;
+ waitsec = 5;
+ while (recv_ds == NULL && waitsec > 0) {
+
+ /*
+ * If there is another candidate, send to it.
+ */
+ if (send_ds->cds_ds.host[0] != '\0') {
+ send_to_cds(send_ds, be->ber_buf, be_len, fd);
+ send_ds++;
+
+ /*
+ * Wait 1/10 sec. before the next send.
+ */
+ r = poll(&pingchk, 1, 100);
+#if 0 /* DEBUG */
+ /* Drop all responses 1st pass. */
+ if (waitsec == 5)
+ r = 0;
+#endif
+ } else {
+ /*
+ * No more candidates to "ping", so
+ * just wait a sec for responses.
+ */
+ r = poll(&pingchk, 1, 1000);
+ if (r == 0)
+ --waitsec;
+ }
+
+ if (r > 0) {
+ /*
+ * Got a response.
+ */
+ (void) memset(&addr6, 0, addrlen = sizeof (addr6));
+ r = recvfrom(fd, rbe->ber_buf, rbe_len, 0,
+ (struct sockaddr *)&addr6, &addrlen);
+
+ recv_ds = find_cds_by_addr(dclist, &addr6);
+ if (recv_ds == NULL)
+ continue;
+
+ (void) cldap_parse(ctx, recv_ds, res);
+ if ((recv_ds->cds_ds.flags & reqflags) != reqflags) {
+ logger(LOG_ERR, "Skip %s"
+ "due to flags 0x%X",
+ recv_ds->cds_ds.host,
+ recv_ds->cds_ds.flags);
+ recv_ds = NULL;
+ }
+ }
+ }
+
+ if (recv_ds == NULL) {
+ if (--tries <= 0)
+ goto fail;
+ goto try_again;
+ }
+
+ (void) memcpy(ret_ds, recv_ds, sizeof (*ret_ds));
+
+ ber_free(res, 1);
+ ber_free(req, 1);
+ (void) close(fd);
+ return (ret_ds);
+
+fail:
+ ber_free(res, 1);
+ ber_free(req, 1);
+ (void) close(fd);
+ free(ret_ds);
+ return (NULL);
+}
+
+/*
+ * Attempt a send of the LDAP request to all known addresses
+ * for this candidate server.
+ */
+static void
+send_to_cds(ad_disc_cds_t *send_cds, char *ber_buf, size_t be_len, int fd)
+{
+ struct sockaddr_in6 addr6;
+ struct addrinfo *ai;
+ int err;
+
+ if (DBG(DISC, 2)) {
+ logger(LOG_DEBUG, "send to: %s", send_cds->cds_ds.host);
+ }
+
+ for (ai = send_cds->cds_ai; ai != NULL; ai = ai->ai_next) {
+
+ /*
+ * Build the "to" address.
+ */
+ (void) memset(&addr6, 0, sizeof (addr6));
+ if (ai->ai_family == AF_INET6) {
+ (void) memcpy(&addr6, ai->ai_addr, sizeof (addr6));
+ } else if (ai->ai_family == AF_INET) {
+ struct sockaddr_in *sin =
+ (void *)ai->ai_addr;
+ addr6.sin6_family = AF_INET6;
+ IN6_INADDR_TO_V4MAPPED(&sin->sin_addr,
+ &addr6.sin6_addr);
+ } else {
+ continue;
+ }
+ addr6.sin6_port = htons(LDAP_PORT);
+
+ /*
+ * Send the "ping" to this address.
+ */
+ err = sendto(fd, ber_buf, be_len, 0,
+ (struct sockaddr *)&addr6, sizeof (addr6));
+ err = (err < 0) ? errno : 0;
+
+ if (DBG(DISC, 2)) {
+ char abuf[INET6_ADDRSTRLEN];
+ const char *pa;
+
+ pa = inet_ntop(AF_INET6,
+ &addr6.sin6_addr,
+ abuf, sizeof (abuf));
+ logger(LOG_ERR, " > %s rc=%d",
+ pa ? pa : "?", err);
+ }
+ }
+}
+
+/*
+ * We have a response from some address. Find the candidate with
+ * this address. In case a candidate had multiple addresses, we
+ * keep track of which the response came from.
+ */
+static ad_disc_cds_t *
+find_cds_by_addr(ad_disc_cds_t *dclist, struct sockaddr_in6 *sin6from)
+{
+ char abuf[INET6_ADDRSTRLEN];
+ ad_disc_cds_t *ds;
+ struct addrinfo *ai;
+ int eai;
+
+ if (DBG(DISC, 1)) {
+ eai = getnameinfo((void *)sin6from, sizeof (*sin6from),
+ abuf, sizeof (abuf), NULL, 0, NI_NUMERICHOST);
+ if (eai != 0)
+ (void) strlcpy(abuf, "?", sizeof (abuf));
+ logger(LOG_DEBUG, "LDAP ping resp: addr=%s", abuf);
+ }
+
+ /*
+ * Find the DS this response came from.
+ * (don't accept unexpected responses)
+ */
+ for (ds = dclist; ds->cds_ds.host[0] != '\0'; ds++) {
+ ai = ds->cds_ai;
+ while (ai != NULL) {
+ if (addrmatch(ai, sin6from))
+ goto found;
+ ai = ai->ai_next;
+ }
+ }
+ if (DBG(DISC, 1)) {
+ logger(LOG_DEBUG, " (unexpected)");
+ }
+ return (NULL);
+
+found:
+ if (DBG(DISC, 2)) {
+ logger(LOG_DEBUG, " from %s", ds->cds_ds.host);
+ }
+ save_ai(ds, ai);
+ return (ds);
+}
+
+static boolean_t
+addrmatch(struct addrinfo *ai, struct sockaddr_in6 *sin6from)
+{
+
+ /*
+ * Note: on a GC query, the ds->addr port numbers are
+ * the GC port, and our from addr has the LDAP port.
+ * Just compare the IP addresses.
+ */
+
+ if (ai->ai_family == AF_INET6) {
+ struct sockaddr_in6 *sin6p = (void *)ai->ai_addr;
+
+ if (!memcmp(&sin6from->sin6_addr, &sin6p->sin6_addr,
+ sizeof (struct in6_addr)))
+ return (B_TRUE);
+ }
+
+ if (ai->ai_family == AF_INET) {
+ struct in6_addr in6;
+ struct sockaddr_in *sin4p = (void *)ai->ai_addr;
+
+ IN6_INADDR_TO_V4MAPPED(&sin4p->sin_addr, &in6);
+ if (!memcmp(&sin6from->sin6_addr, &in6,
+ sizeof (struct in6_addr)))
+ return (B_TRUE);
+ }
+
+ return (B_FALSE);
+}
+
+static void
+save_ai(ad_disc_cds_t *cds, struct addrinfo *ai)
+{
+ ad_disc_ds_t *ds = &cds->cds_ds;
+ struct sockaddr_in *sin;
+ struct sockaddr_in6 *sin6;
+
+ /*
+ * If this DS already saw a response, keep the first
+ * address from which we received a response.
+ */
+ if (ds->addr.ss_family != 0) {
+ if (DBG(DISC, 2))
+ logger(LOG_DEBUG, "already have an address");
+ return;
+ }
+
+ switch (ai->ai_family) {
+ case AF_INET:
+ sin = (void *)&ds->addr;
+ (void) memcpy(sin, ai->ai_addr, sizeof (*sin));
+ sin->sin_port = htons(ds->port);
+ break;
+
+ case AF_INET6:
+ sin6 = (void *)&ds->addr;
+ (void) memcpy(sin6, ai->ai_addr, sizeof (*sin6));
+ sin6->sin6_port = htons(ds->port);
+ break;
+
+ default:
+ logger(LOG_ERR, "bad AF %d", ai->ai_family);
+ }
+}
diff --git a/usr/src/lib/libadutils/common/mapfile-vers b/usr/src/lib/libadutils/common/mapfile-vers
index a05bcb4aff..2d029e3ce5 100644
--- a/usr/src/lib/libadutils/common/mapfile-vers
+++ b/usr/src/lib/libadutils/common/mapfile-vers
@@ -20,6 +20,7 @@
#
#
# Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
+# Copyright 2014 Nexenta Systems, Inc. All rights reserved.
#
#
@@ -66,6 +67,8 @@ SYMBOL_VERSION SUNWprivate {
ad_disc_init;
ad_disc_get_DomainName;
ad_disc_set_DomainName;
+ ad_disc_get_DomainGUID;
+ ad_disc_set_DomainGUID;
ad_disc_compare_ds;
ad_disc_compare_trusteddomains;
ad_disc_compare_domainsinforest;
@@ -78,9 +81,14 @@ SYMBOL_VERSION SUNWprivate {
ad_disc_get_ForestName;
ad_disc_get_DomainController;
ad_disc_set_DomainController;
+ ad_disc_get_PreferredDC;
+ ad_disc_set_PreferredDC;
+ ad_disc_get_SiteName;
ad_disc_set_SiteName;
ad_disc_refresh;
- ad_disc_get_SiteName;
+ ad_disc_unset;
+ ad_disc_getnameinfo;
+ ad_disc_set_StatusFP;
ad_disc_get_TrustedDomains;
ad_disc_get_DomainsInForest;
domain_eq;
diff --git a/usr/src/lib/libadutils/common/srv_query.c b/usr/src/lib/libadutils/common/srv_query.c
new file mode 100644
index 0000000000..aa7a833421
--- /dev/null
+++ b/usr/src/lib/libadutils/common/srv_query.c
@@ -0,0 +1,612 @@
+/*
+ * 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 (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
+ */
+
+/*
+ * DNS query helper functions for addisc.c
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <net/if.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <resolv.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+#include <ldap.h>
+#include <sasl/sasl.h>
+#include <sys/u8_textprep.h>
+#include <syslog.h>
+#include <uuid/uuid.h>
+#include <ads/dsgetdc.h>
+#include "adutils_impl.h"
+#include "addisc_impl.h"
+
+static void save_addr(ad_disc_cds_t *, sa_family_t, uchar_t *, size_t);
+static struct addrinfo *make_addrinfo(sa_family_t, uchar_t *, size_t);
+
+static void do_getaddrinfo(ad_disc_cds_t *);
+static ad_disc_cds_t *srv_parse(uchar_t *, int, int *, int *);
+static void add_preferred(ad_disc_cds_t *, ad_disc_ds_t *, int *, int);
+static void get_addresses(ad_disc_cds_t *, int);
+
+/*
+ * Simplified version of srv_query() for domain auto-discovery.
+ */
+int
+srv_getdom(res_state state, const char *svc_name, char **rrname)
+{
+ union {
+ HEADER hdr;
+ uchar_t buf[NS_MAXMSG];
+ } msg;
+ int len, qdcount, ancount;
+ uchar_t *ptr, *eom;
+ char namebuf[NS_MAXDNAME];
+
+ /* query necessary resource records */
+
+ *rrname = NULL;
+ if (DBG(DNS, 1)) {
+ logger(LOG_DEBUG, "Looking for SRV RRs '%s.*'", svc_name);
+ }
+ len = res_nsearch(state, svc_name, C_IN, T_SRV,
+ msg.buf, sizeof (msg.buf));
+ if (len < 0) {
+ if (DBG(DNS, 0)) {
+ logger(LOG_DEBUG,
+ "DNS search for '%s' failed (%s)",
+ svc_name, hstrerror(state->res_h_errno));
+ }
+ return (-1);
+ }
+
+ if (len > sizeof (msg.buf)) {
+ logger(LOG_WARNING,
+ "DNS query %ib message doesn't fit into %ib buffer",
+ len, sizeof (msg.buf));
+ len = sizeof (msg.buf);
+ }
+
+ /* parse the reply header */
+
+ ptr = msg.buf + sizeof (msg.hdr);
+ eom = msg.buf + len;
+ qdcount = ntohs(msg.hdr.qdcount);
+ ancount = ntohs(msg.hdr.ancount);
+
+ /* skip the question section */
+
+ len = ns_skiprr(ptr, eom, ns_s_qd, qdcount);
+ if (len < 0) {
+ logger(LOG_ERR, "DNS query invalid message format");
+ return (-1);
+ }
+ ptr += len;
+
+ /* parse the answer section */
+ if (ancount < 1) {
+ logger(LOG_ERR, "DNS query - no answers");
+ return (-1);
+ }
+
+ len = dn_expand(msg.buf, eom, ptr, namebuf, sizeof (namebuf));
+ if (len < 0) {
+ logger(LOG_ERR, "DNS query invalid message format");
+ return (-1);
+ }
+ *rrname = strdup(namebuf);
+ if (*rrname == NULL) {
+ logger(LOG_ERR, "Out of memory");
+ return (-1);
+ }
+
+ return (0);
+}
+
+
+/*
+ * Compare SRC RRs; used with qsort(). Sort order:
+ * "Earliest" (lowest number) priority first,
+ * then weight highest to lowest.
+ */
+static int
+srvcmp(ad_disc_ds_t *s1, ad_disc_ds_t *s2)
+{
+ if (s1->priority < s2->priority)
+ return (-1);
+ else if (s1->priority > s2->priority)
+ return (1);
+
+ if (s1->weight < s2->weight)
+ return (1);
+ else if (s1->weight > s2->weight)
+ return (-1);
+
+ return (0);
+}
+
+/*
+ * Query or search the SRV RRs for a given name.
+ *
+ * If dname == NULL then search (as in res_nsearch(3RESOLV), honoring any
+ * search list/option), else query (as in res_nquery(3RESOLV)).
+ *
+ * The output TTL will be the one of the SRV RR with the lowest TTL.
+ */
+ad_disc_cds_t *
+srv_query(res_state state, const char *svc_name, const char *dname,
+ ad_disc_ds_t *prefer)
+{
+ ad_disc_cds_t *cds_res = NULL;
+ uchar_t *msg = NULL;
+ int len, scnt, maxcnt;
+
+ msg = malloc(NS_MAXMSG);
+ if (msg == NULL) {
+ logger(LOG_ERR, "Out of memory");
+ return (NULL);
+ }
+
+ /* query necessary resource records */
+
+ /* Search, querydomain or query */
+ if (dname == NULL) {
+ dname = "*";
+ if (DBG(DNS, 1)) {
+ logger(LOG_DEBUG, "Looking for SRV RRs '%s.*'",
+ svc_name);
+ }
+ len = res_nsearch(state, svc_name, C_IN, T_SRV,
+ msg, NS_MAXMSG);
+ if (len < 0) {
+ if (DBG(DNS, 0)) {
+ logger(LOG_DEBUG,
+ "DNS search for '%s' failed (%s)",
+ svc_name, hstrerror(state->res_h_errno));
+ }
+ goto errout;
+ }
+ } else { /* dname != NULL */
+ if (DBG(DNS, 1)) {
+ logger(LOG_DEBUG, "Looking for SRV RRs '%s.%s' ",
+ svc_name, dname);
+ }
+
+ len = res_nquerydomain(state, svc_name, dname, C_IN, T_SRV,
+ msg, NS_MAXMSG);
+
+ if (len < 0) {
+ if (DBG(DNS, 0)) {
+ logger(LOG_DEBUG, "DNS: %s.%s: %s",
+ svc_name, dname,
+ hstrerror(state->res_h_errno));
+ }
+ goto errout;
+ }
+ }
+
+ if (len > NS_MAXMSG) {
+ logger(LOG_WARNING,
+ "DNS query %ib message doesn't fit into %ib buffer",
+ len, NS_MAXMSG);
+ len = NS_MAXMSG;
+ }
+
+
+ /* parse the reply header */
+
+ cds_res = srv_parse(msg, len, &scnt, &maxcnt);
+ if (cds_res == NULL)
+ goto errout;
+
+ if (prefer != NULL)
+ add_preferred(cds_res, prefer, &scnt, maxcnt);
+
+ get_addresses(cds_res, scnt);
+
+ /* sort list of candidates */
+ if (scnt > 1)
+ qsort(cds_res, scnt, sizeof (*cds_res),
+ (int (*)(const void *, const void *))srvcmp);
+
+ free(msg);
+ return (cds_res);
+
+errout:
+ free(msg);
+ return (NULL);
+}
+
+static ad_disc_cds_t *
+srv_parse(uchar_t *msg, int len, int *scnt, int *maxcnt)
+{
+ ad_disc_cds_t *cds;
+ ad_disc_cds_t *cds_res = NULL;
+ HEADER *hdr;
+ int i, qdcount, ancount, nscount, arcount;
+ uchar_t *ptr, *eom;
+ uchar_t *end;
+ uint16_t type;
+ /* LINTED E_FUNC_SET_NOT_USED */
+ uint16_t class;
+ uint32_t rttl;
+ uint16_t size;
+ char namebuf[NS_MAXDNAME];
+
+ eom = msg + len;
+ hdr = (void *)msg;
+ ptr = msg + sizeof (HEADER);
+
+ qdcount = ntohs(hdr->qdcount);
+ ancount = ntohs(hdr->ancount);
+ nscount = ntohs(hdr->nscount);
+ arcount = ntohs(hdr->arcount);
+
+ /* skip the question section */
+
+ len = ns_skiprr(ptr, eom, ns_s_qd, qdcount);
+ if (len < 0) {
+ logger(LOG_ERR, "DNS query invalid message format");
+ return (NULL);
+ }
+ ptr += len;
+
+ /*
+ * Walk through the answer section, building the result array.
+ * The array size is +2 because we (possibly) add the preferred
+ * DC if it was not there, and an empty one (null termination).
+ */
+
+ *maxcnt = ancount + 2;
+ cds_res = calloc(*maxcnt, sizeof (*cds_res));
+ if (cds_res == NULL) {
+ logger(LOG_ERR, "Out of memory");
+ return (NULL);
+ }
+
+ cds = cds_res;
+ for (i = 0; i < ancount; i++) {
+
+ len = dn_expand(msg, eom, ptr, namebuf,
+ sizeof (namebuf));
+ if (len < 0) {
+ logger(LOG_ERR, "DNS query invalid message format");
+ goto err;
+ }
+ ptr += len;
+ NS_GET16(type, ptr);
+ NS_GET16(class, ptr);
+ NS_GET32(rttl, ptr);
+ NS_GET16(size, ptr);
+ if ((end = ptr + size) > eom) {
+ logger(LOG_ERR, "DNS query invalid message format");
+ goto err;
+ }
+
+ if (type != T_SRV) {
+ ptr = end;
+ continue;
+ }
+
+ NS_GET16(cds->cds_ds.priority, ptr);
+ NS_GET16(cds->cds_ds.weight, ptr);
+ NS_GET16(cds->cds_ds.port, ptr);
+ len = dn_expand(msg, eom, ptr, cds->cds_ds.host,
+ sizeof (cds->cds_ds.host));
+ if (len < 0) {
+ logger(LOG_ERR, "DNS query invalid SRV record");
+ goto err;
+ }
+
+ cds->cds_ds.ttl = rttl;
+
+ if (DBG(DNS, 2)) {
+ logger(LOG_DEBUG, " %s", namebuf);
+ logger(LOG_DEBUG,
+ " ttl=%d pri=%d weight=%d %s:%d",
+ rttl, cds->cds_ds.priority, cds->cds_ds.weight,
+ cds->cds_ds.host, cds->cds_ds.port);
+ }
+ cds++;
+
+ /* move ptr to the end of current record */
+ ptr = end;
+ }
+ *scnt = (cds - cds_res);
+
+ /* skip the nameservers section (if any) */
+
+ len = ns_skiprr(ptr, eom, ns_s_ns, nscount);
+ if (len < 0) {
+ logger(LOG_ERR, "DNS query invalid message format");
+ goto err;
+ }
+ ptr += len;
+
+ /* walk through the additional records */
+ for (i = 0; i < arcount; i++) {
+ sa_family_t af;
+
+ len = dn_expand(msg, eom, ptr, namebuf,
+ sizeof (namebuf));
+ if (len < 0) {
+ logger(LOG_ERR, "DNS query invalid message format");
+ goto err;
+ }
+ ptr += len;
+ NS_GET16(type, ptr);
+ NS_GET16(class, ptr);
+ NS_GET32(rttl, ptr);
+ NS_GET16(size, ptr);
+ if ((end = ptr + size) > eom) {
+ logger(LOG_ERR, "DNS query invalid message format");
+ goto err;
+ }
+ switch (type) {
+ case ns_t_a:
+ af = AF_INET;
+ break;
+ case ns_t_aaaa:
+ af = AF_INET6;
+ break;
+ default:
+ continue;
+ }
+
+ if (DBG(DNS, 2)) {
+ char abuf[INET6_ADDRSTRLEN];
+ const char *ap;
+
+ ap = inet_ntop(af, ptr, abuf, sizeof (abuf));
+ logger(LOG_DEBUG, " %s %s %s",
+ (af == AF_INET) ? "A " : "AAAA",
+ (ap) ? ap : "?", namebuf);
+ }
+
+ /* Find the server, add to its address list. */
+ for (cds = cds_res; cds->cds_ds.host[0] != '\0'; cds++)
+ if (0 == strcmp(namebuf, cds->cds_ds.host))
+ save_addr(cds, af, ptr, size);
+
+ /* move ptr to the end of current record */
+ ptr = end;
+ }
+
+ return (cds_res);
+
+err:
+ free(cds_res);
+ return (NULL);
+}
+
+/*
+ * Save this address on the server, if not already there.
+ */
+static void
+save_addr(ad_disc_cds_t *cds, sa_family_t af, uchar_t *addr, size_t alen)
+{
+ struct addrinfo *ai, *new_ai, *last_ai;
+
+ new_ai = make_addrinfo(af, addr, alen);
+ if (new_ai == NULL)
+ return;
+
+ last_ai = NULL;
+ for (ai = cds->cds_ai; ai != NULL; ai = ai->ai_next) {
+ last_ai = ai;
+
+ if (new_ai->ai_family == ai->ai_family &&
+ new_ai->ai_addrlen == ai->ai_addrlen &&
+ 0 == memcmp(new_ai->ai_addr, ai->ai_addr,
+ ai->ai_addrlen)) {
+ /* it's already there */
+ freeaddrinfo(new_ai);
+ return;
+ }
+ }
+
+ /* Not found. Append. */
+ if (last_ai != NULL) {
+ last_ai->ai_next = new_ai;
+ } else {
+ cds->cds_ai = new_ai;
+ }
+}
+
+static struct addrinfo *
+make_addrinfo(sa_family_t af, uchar_t *addr, size_t alen)
+{
+ struct addrinfo *ai;
+ struct sockaddr *sa;
+ struct sockaddr_in *sin;
+ struct sockaddr_in6 *sin6;
+ int slen;
+
+ ai = calloc(1, sizeof (*ai));
+ sa = calloc(1, sizeof (struct sockaddr_in6));
+
+ if (ai == NULL || sa == NULL) {
+ logger(LOG_ERR, "Out of memory");
+ goto errout;
+ }
+
+ switch (af) {
+ case AF_INET:
+ sin = (void *)sa;
+ if (alen < sizeof (in_addr_t)) {
+ logger(LOG_ERR, "bad IPv4 addr len");
+ goto errout;
+ }
+ alen = sizeof (in_addr_t);
+ sin->sin_family = af;
+ (void) memcpy(&sin->sin_addr, addr, alen);
+ slen = sizeof (*sin);
+ break;
+
+ case AF_INET6:
+ sin6 = (void *)sa;
+ if (alen < sizeof (in6_addr_t)) {
+ logger(LOG_ERR, "bad IPv6 addr len");
+ goto errout;
+ }
+ alen = sizeof (in6_addr_t);
+ sin6->sin6_family = af;
+ (void) memcpy(&sin6->sin6_addr, addr, alen);
+ slen = sizeof (*sin6);
+ break;
+
+ default:
+ goto errout;
+ }
+
+ ai->ai_family = af;
+ ai->ai_addrlen = slen;
+ ai->ai_addr = sa;
+ sa->sa_family = af;
+ return (ai);
+
+errout:
+ free(ai);
+ free(sa);
+ return (NULL);
+}
+
+/*
+ * Set a preferred candidate, which may already be in the list,
+ * in which case we just bump its priority, or else add it.
+ */
+static void
+add_preferred(ad_disc_cds_t *cds, ad_disc_ds_t *prefer, int *nds, int maxds)
+{
+ ad_disc_ds_t *ds;
+ int i;
+
+ assert(*nds < maxds);
+ for (i = 0; i < *nds; i++) {
+ ds = &cds[i].cds_ds;
+
+ if (strcasecmp(ds->host, prefer->host) == 0) {
+ /* Force this element to be sorted first. */
+ ds->priority = 0;
+ ds->weight = 200;
+ return;
+ }
+ }
+
+ /*
+ * The preferred DC was not found in this DNS response,
+ * so add it. Again arrange for it to be sorted first.
+ * Address info. is added later.
+ */
+ ds = &cds[i].cds_ds;
+ (void) memcpy(ds, prefer, sizeof (*ds));
+ ds->priority = 0;
+ ds->weight = 200;
+ *nds = i + 1;
+}
+
+/*
+ * Do another pass over the array to check for missing addresses and
+ * try resolving the names. Normally, the DNS response from AD will
+ * have supplied additional address records for all the SRV records.
+ */
+static void
+get_addresses(ad_disc_cds_t *cds, int cnt)
+{
+ int i;
+
+ for (i = 0; i < cnt; i++) {
+ if (cds[i].cds_ai == NULL) {
+ do_getaddrinfo(&cds[i]);
+ }
+ }
+}
+
+static void
+do_getaddrinfo(ad_disc_cds_t *cds)
+{
+ struct addrinfo hints;
+ struct addrinfo *ai;
+ ad_disc_ds_t *ds;
+ time_t t0, t1;
+ int err;
+
+ (void) memset(&hints, 0, sizeof (hints));
+ hints.ai_protocol = IPPROTO_TCP;
+ hints.ai_socktype = SOCK_STREAM;
+ ds = &cds->cds_ds;
+
+ /*
+ * This getaddrinfo call may take a LONG time, i.e. if our
+ * DNS servers are misconfigured or not responding.
+ * We need something like getaddrinfo_a(), with a timeout.
+ * For now, just log when this happens so we'll know
+ * if these calls are taking a long time.
+ */
+ if (DBG(DNS, 2))
+ logger(LOG_DEBUG, "getaddrinfo %s ...", ds->host);
+ t0 = time(NULL);
+ err = getaddrinfo(cds->cds_ds.host, NULL, &hints, &ai);
+ t1 = time(NULL);
+ if (DBG(DNS, 2))
+ logger(LOG_DEBUG, "getaddrinfo %s rc=%d", ds->host, err);
+ if (t1 > (t0 + 5)) {
+ logger(LOG_WARNING, "Lookup host (%s) took %u sec. "
+ "(Check DNS settings)", ds->host, (int)(t1 - t0));
+ }
+ if (err != 0) {
+ logger(LOG_ERR, "No address for host: %s (%s)",
+ ds->host, gai_strerror(err));
+ /* Make this sort at the end. */
+ ds->priority = 1 << 16;
+ return;
+ }
+
+ cds->cds_ai = ai;
+}
+
+void
+srv_free(ad_disc_cds_t *cds_vec)
+{
+ ad_disc_cds_t *cds;
+
+ for (cds = cds_vec; cds->cds_ds.host[0] != '\0'; cds++) {
+ if (cds->cds_ai != NULL) {
+ freeaddrinfo(cds->cds_ai);
+ }
+ }
+ free(cds_vec);
+}