diff options
author | Gordon Ross <gwr@nexenta.com> | 2014-06-05 14:30:31 -0400 |
---|---|---|
committer | Gordon Ross <gwr@nexenta.com> | 2015-10-26 10:17:47 -0400 |
commit | b3700b074e637f8c6991b70754c88a2cfffb246b (patch) | |
tree | c979fb7c426aec884413fae889fab8356ca9ef17 /usr/src/lib/libadutils/common | |
parent | ed81dd52230eff1a7c7625caad21af232c36f6cb (diff) | |
download | illumos-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.c | 1238 | ||||
-rw-r--r-- | usr/src/lib/libadutils/common/addisc.h | 55 | ||||
-rw-r--r-- | usr/src/lib/libadutils/common/addisc_impl.h | 130 | ||||
-rw-r--r-- | usr/src/lib/libadutils/common/adutils_impl.h | 2 | ||||
-rw-r--r-- | usr/src/lib/libadutils/common/ldap_ping.c | 677 | ||||
-rw-r--r-- | usr/src/lib/libadutils/common/mapfile-vers | 10 | ||||
-rw-r--r-- | usr/src/lib/libadutils/common/srv_query.c | 612 |
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); +} |