/* * 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 2019 Nexenta Systems, Inc. All rights reserved. */ /* * Active Directory Auto-Discovery. * * This [project private] API allows the caller to provide whatever * details it knows a priori (i.e., provided via configuration so as to * override auto-discovery) and in any order. Then the caller can ask * for any of the auto-discoverable parameters in any order. * * But there is an actual order in which discovery must be done. Given * the discovery mechanism implemented here, that order is: * * - the domain name joined must be discovered first * - then the domain controllers * - then the forest name and site name * - then the global catalog servers, and site-specific domain * controllers and global catalog servers. * * The API does not require it be called in the same order because there * may be other discovery mechanisms in the future, and exposing * ordering requirements of the current mechanism now can create trouble * down the line. Also, this makes the API easier to use now, which * means less work to do some day when we make this a public API. * * Domain discovery is done by res_nsearch() of the DNS SRV RR name for * domain controllers. As long as the joined domain appears in the DNS * resolver's search list then we'll find it. * * Domain controller discovery is a matter of formatting the DNS SRV RR * FQDN for domain controllers and doing a lookup for them. Knowledge * of the domain name is not fundamentally required, but we separate the * two processes, which in practice can lead to one more DNS lookup than * is strictly required. * * Forest and site name discovery require an LDAP search of the AD * "configuration partition" at a domain controller for the joined * domain. Forest and site name discovery depend on knowing the joined * domain name and domain controllers for that domain. * * Global catalog server discovery requires knowledge of the forest * name in order to format the DNS SRV RR FQDN to lookup. Site-specific * domain controller discovery depends on knowing the site name (and, * therefore, joined domain, ...). Site-specific global catalog server * discovery depends on knowledge of the forest and site names, which * depend on... * * All the work of discovering particular items is done by functions * named validate_(). Each such function calls validate_() * for any items that it depends on. * * This API is not thread-safe. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "adutils_impl.h" #include "addisc_impl.h" /* * These set some sanity policies for discovery. After a discovery * cycle, we will consider the results (successful or unsuccessful) * to be valid for at least MINIMUM_TTL seconds, and for at most * MAXIMUM_TTL seconds. Note that the caller is free to request * discovery cycles sooner than MINIMUM_TTL if it has reason to believe * that the situation has changed. */ #define MINIMUM_TTL (5 * 60) #define MAXIMUM_TTL (20 * 60) #define DNS_MAX_NAME NS_MAXDNAME #define GC_PORT 3268 /* SRV RR names for various queries */ #define LDAP_SRV_HEAD "_ldap._tcp." #define SITE_SRV_MIDDLE "%s._sites." #define GC_SRV_TAIL "gc._msdcs" #define DC_SRV_TAIL "dc._msdcs" #define ALL_GC_SRV_TAIL "_gc._tcp" #define PDC_SRV "_ldap._tcp.pdc._msdcs.%s" /* A RR name for all GCs -- last resort this works */ #define GC_ALL_A_NAME_FSTR "gc._msdcs.%s." /* * We try res_ninit() whenever we don't have one. res_ninit() fails if * idmapd is running before the network is up! */ #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) #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 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) { item->param_version[num] = param->version; } static boolean_t is_valid(ad_item_t *item) { if (item->value != NULL) { if (item->state == AD_STATE_FIXED) return (B_TRUE); if (item->state == AD_STATE_AUTO && (item->expires == 0 || item->expires > time(NULL))) return (B_TRUE); } return (B_FALSE); } static void update_item(ad_item_t *item, void *value, enum ad_item_state state, uint32_t ttl) { 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 && ad_disc_compare_domainsinforest(item->value, value) != 0) || (item->type == AD_TRUSTED_DOMAINS && ad_disc_compare_trusteddomains(item->value, value) != 0)) item->version++; } else if (item->value != value) item->version++; if (item->value != NULL) free(item->value); item->value = value; item->state = state; if (ttl == 0) item->expires = 0; else 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(ad_disc_ds_t *ds1, ad_disc_ds_t *ds2) { int i, j; int num_ds1; int num_ds2; boolean_t match; for (i = 0; ds1[i].host[0] != '\0'; i++) continue; num_ds1 = i; for (j = 0; ds2[j].host[0] != '\0'; j++) continue; num_ds2 = j; if (num_ds1 != num_ds2) return (1); for (i = 0; i < num_ds1; i++) { match = B_FALSE; for (j = 0; j < num_ds2; j++) { if (strcmp(ds1[i].host, ds2[j].host) == 0 && ds1[i].port == ds2[j].port) { match = B_TRUE; break; } } if (!match) return (1); } return (0); } /* Copy a list of DSs */ static ad_disc_ds_t * ds_dup(const ad_disc_ds_t *srv) { int i; int size; ad_disc_ds_t *new = NULL; for (i = 0; srv[i].host[0] != '\0'; i++) continue; size = (i + 1) * sizeof (ad_disc_ds_t); new = malloc(size); if (new != NULL) (void) memcpy(new, srv, size); return (new); } int ad_disc_compare_trusteddomains(ad_disc_trusteddomains_t *td1, ad_disc_trusteddomains_t *td2) { int i, j; int num_td1; int num_td2; boolean_t match; for (i = 0; td1[i].domain[0] != '\0'; i++) continue; num_td1 = i; for (j = 0; td2[j].domain[0] != '\0'; j++) continue; num_td2 = j; if (num_td1 != num_td2) return (1); for (i = 0; i < num_td1; i++) { match = B_FALSE; for (j = 0; j < num_td2; j++) { if (domain_eq(td1[i].domain, td2[j].domain)) { match = B_TRUE; break; } } if (!match) return (1); } return (0); } /* Copy a list of Trusted Domains */ static ad_disc_trusteddomains_t * td_dup(const ad_disc_trusteddomains_t *td) { int i; int size; ad_disc_trusteddomains_t *new = NULL; for (i = 0; td[i].domain[0] != '\0'; i++) continue; size = (i + 1) * sizeof (ad_disc_trusteddomains_t); new = malloc(size); if (new != NULL) (void) memcpy(new, td, size); return (new); } int ad_disc_compare_domainsinforest(ad_disc_domainsinforest_t *df1, ad_disc_domainsinforest_t *df2) { int i, j; int num_df1; int num_df2; boolean_t match; for (i = 0; df1[i].domain[0] != '\0'; i++) continue; num_df1 = i; for (j = 0; df2[j].domain[0] != '\0'; j++) continue; num_df2 = j; if (num_df1 != num_df2) return (1); for (i = 0; i < num_df1; i++) { match = B_FALSE; for (j = 0; j < num_df2; j++) { if (domain_eq(df1[i].domain, df2[j].domain) && strcmp(df1[i].sid, df2[j].sid) == 0) { match = B_TRUE; break; } } if (!match) return (1); } return (0); } /* Copy a list of Trusted Domains */ static ad_disc_domainsinforest_t * df_dup(const ad_disc_domainsinforest_t *df) { int i; int size; ad_disc_domainsinforest_t *new = NULL; for (i = 0; df[i].domain[0] != '\0'; i++) continue; size = (i + 1) * sizeof (ad_disc_domainsinforest_t); new = malloc(size); if (new != NULL) (void) memcpy(new, df, size); return (new); } /* * Returns an array of IPv4 address/prefix length * The last subnet is NULL */ static ad_subnet_t * find_subnets() { int sock, n, i; struct lifconf lifc; struct lifreq lifr, *lifrp; struct lifnum lifn; uint32_t prefix_len; char *s; ad_subnet_t *results; lifrp = &lifr; if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { logger(LOG_ERR, "Failed to open IPv4 socket for " "listing network interfaces (%s)", strerror(errno)); return (NULL); } lifn.lifn_family = AF_INET; lifn.lifn_flags = 0; if (ioctl(sock, SIOCGLIFNUM, (char *)&lifn) < 0) { logger(LOG_ERR, "Failed to find the number of network interfaces (%s)", strerror(errno)); (void) close(sock); return (NULL); } if (lifn.lifn_count < 1) { logger(LOG_ERR, "No IPv4 network interfaces found"); (void) close(sock); return (NULL); } lifc.lifc_family = AF_INET; lifc.lifc_flags = 0; lifc.lifc_len = lifn.lifn_count * sizeof (struct lifreq); lifc.lifc_buf = malloc(lifc.lifc_len); if (lifc.lifc_buf == NULL) { logger(LOG_ERR, "Out of memory"); (void) close(sock); return (NULL); } if (ioctl(sock, SIOCGLIFCONF, (char *)&lifc) < 0) { logger(LOG_ERR, "Failed to list network interfaces (%s)", strerror(errno)); free(lifc.lifc_buf); (void) close(sock); return (NULL); } n = lifc.lifc_len / (int)sizeof (struct lifreq); if ((results = calloc(n + 1, sizeof (ad_subnet_t))) == NULL) { free(lifc.lifc_buf); (void) close(sock); return (NULL); } for (i = 0, lifrp = lifc.lifc_req; i < n; i++, lifrp++) { if (ioctl(sock, SIOCGLIFFLAGS, lifrp) < 0) continue; if ((lifrp->lifr_flags & IFF_UP) == 0) continue; if (ioctl(sock, SIOCGLIFSUBNET, lifrp) < 0) continue; prefix_len = lifrp->lifr_addrlen; s = inet_ntoa(((struct sockaddr_in *) &lifrp->lifr_addr)->sin_addr); (void) snprintf(results[i].subnet, sizeof (ad_subnet_t), "%s/%d", s, prefix_len); } free(lifc.lifc_buf); (void) close(sock); return (results); } static int cmpsubnets(ad_subnet_t *subnets1, ad_subnet_t *subnets2) { int num_subnets1; int num_subnets2; boolean_t matched; int i, j; for (i = 0; subnets1[i].subnet[0] != '\0'; i++) continue; num_subnets1 = i; for (i = 0; subnets2[i].subnet[0] != '\0'; i++) continue; num_subnets2 = i; if (num_subnets1 != num_subnets2) return (1); for (i = 0; i < num_subnets1; i++) { matched = B_FALSE; for (j = 0; j < num_subnets2; j++) { if (strcmp(subnets1[i].subnet, subnets2[j].subnet) == 0) { matched = B_TRUE; break; } } if (!matched) return (1); } return (0); } /* Convert a DN's DC components into a DNS domainname */ char * DN_to_DNS(const char *dn_name) { char dns[DNS_MAX_NAME]; char *dns_name; int i, j; int num = 0; j = 0; i = 0; if (dn_name == NULL) return (NULL); /* * Find all DC= and form DNS name of the * form .... */ while (dn_name[i] != '\0') { if (strncasecmp(&dn_name[i], "DC=", 3) == 0) { i += 3; if (dn_name[i] != '\0' && num > 0) dns[j++] = '.'; while (dn_name[i] != '\0' && dn_name[i] != ',' && dn_name[i] != '+') dns[j++] = dn_name[i++]; num++; } else { /* Skip attr=value as it is not DC= */ while (dn_name[i] != '\0' && dn_name[i] != ',' && dn_name[i] != '+') i++; } /* Skip over separator ',' or '+' */ if (dn_name[i] != '\0') i++; } dns[j] = '\0'; dns_name = malloc(j + 1); if (dns_name != NULL) (void) strlcpy(dns_name, dns, j + 1); return (dns_name); } /* * A utility function to bind to a Directory server */ static LDAP * ldap_lookup_init(ad_disc_ds_t *ds) { int i; int rc, ldversion; int zero = 0; int timeoutms = 5 * 1000; char *saslmech = "GSSAPI"; uint32_t saslflags = LDAP_SASL_INTERACTIVE; 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)) { logger(LOG_DEBUG, "Couldn't connect to AD DC %s:%d (%s)", ds[i].host, ds[i].port, strerror(errno)); } continue; } ldversion = LDAP_VERSION3; (void) ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &ldversion); (void) ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF); (void) ldap_set_option(ld, LDAP_OPT_TIMELIMIT, &zero); (void) ldap_set_option(ld, LDAP_OPT_SIZELIMIT, &zero); /* setup TCP/IP connect timeout */ (void) ldap_set_option(ld, LDAP_X_OPT_CONNECT_TIMEOUT, &timeoutms); (void) ldap_set_option(ld, LDAP_OPT_RESTART, LDAP_OPT_ON); rc = adutils_set_thread_functions(ld); if (rc != LDAP_SUCCESS) { /* Error has already been logged */ (void) ldap_unbind(ld); ld = NULL; continue; } rc = ldap_sasl_interactive_bind_s(ld, "" /* binddn */, saslmech, NULL, NULL, saslflags, &saslcallback, NULL /* defaults */); if (rc == LDAP_SUCCESS) break; if (DBG(LDAP, 0)) { logger(LOG_INFO, "LDAP: %s:%d: %s", ds[i].host, ds[i].port, ldap_err2string(rc)); ldap_perror(ld, ds[i].host); } (void) ldap_unbind(ld); ld = NULL; } return (ld); } /* * Lookup the trusted domains in the global catalog. * * Returns: * array of trusted domains which is terminated by * an empty trusted domain. * NULL an error occured */ ad_disc_trusteddomains_t * ldap_lookup_trusted_domains(LDAP **ld, ad_disc_ds_t *globalCatalog, char *base_dn) { int scope = LDAP_SCOPE_SUBTREE; char *attrs[3]; int rc; LDAPMessage *results = NULL; LDAPMessage *entry; char *filter; char **partner = NULL; char **direction = NULL; int num = 0; ad_disc_trusteddomains_t *trusted_domains = NULL; if (DBG(DISC, 1)) logger(LOG_DEBUG, "Looking for trusted domains..."); if (*ld == NULL) *ld = ldap_lookup_init(globalCatalog); if (*ld == NULL) { logger(LOG_ERR, "adutils: ldap_lookup_init failed"); return (NULL); } attrs[0] = "trustPartner"; attrs[1] = "trustDirection"; attrs[2] = NULL; /* * Trust direction values: * 1 - inbound (they trust us) * 2 - outbound (we trust them) * 3 - bidirectional (we trust each other) */ filter = "(&(objectclass=trustedDomain)" "(|(trustDirection=3)(trustDirection=2)))"; rc = ldap_search_s(*ld, base_dn, scope, filter, attrs, 0, &results); if (DBG(DISC, 1)) logger(LOG_DEBUG, "Trusted domains:"); if (rc == LDAP_SUCCESS) { for (entry = ldap_first_entry(*ld, results); entry != NULL; entry = ldap_next_entry(*ld, entry)) { partner = ldap_get_values(*ld, entry, "trustPartner"); direction = ldap_get_values( *ld, entry, "trustDirection"); if (partner != NULL && direction != NULL) { if (DBG(DISC, 1)) { logger(LOG_DEBUG, " %s (%s)", partner[0], direction[0]); } num++; void *tmp = realloc(trusted_domains, (num + 1) * sizeof (ad_disc_trusteddomains_t)); if (tmp == NULL) { free(trusted_domains); ldap_value_free(partner); ldap_value_free(direction); (void) ldap_msgfree(results); return (NULL); } trusted_domains = tmp; /* Last element should be zero */ (void) memset(&trusted_domains[num], 0, sizeof (ad_disc_trusteddomains_t)); (void) strcpy(trusted_domains[num - 1].domain, partner[0]); trusted_domains[num - 1].direction = atoi(direction[0]); } if (partner != NULL) ldap_value_free(partner); if (direction != NULL) ldap_value_free(direction); } } else if (rc == LDAP_NO_RESULTS_RETURNED) { /* This is not an error - return empty trusted domain */ 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); return (trusted_domains); } /* * This functions finds all the domains in a forest. */ ad_disc_domainsinforest_t * ldap_lookup_domains_in_forest(LDAP **ld, ad_disc_ds_t *globalCatalogs) { static char *attrs[] = { "objectSid", NULL, }; int rc; LDAPMessage *result = NULL; LDAPMessage *entry; int ndomains = 0; int nresults; ad_disc_domainsinforest_t *domains = NULL; if (DBG(DISC, 1)) logger(LOG_DEBUG, "Looking for domains in forest..."); if (*ld == NULL) *ld = ldap_lookup_init(globalCatalogs); 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:"); nresults = ldap_count_entries(*ld, result); domains = calloc(nresults + 1, sizeof (*domains)); if (domains == NULL) { if (DBG(DISC, 1)) logger(LOG_DEBUG, " (nomem)"); goto err; } for (entry = ldap_first_entry(*ld, result); entry != NULL; entry = ldap_next_entry(*ld, entry)) { struct berval **sid_ber; adutils_sid_t sid; char *sid_str; char *name; char *dn; sid_ber = ldap_get_values_len(*ld, entry, "objectSid"); if (sid_ber == NULL) continue; rc = adutils_getsid(sid_ber[0], &sid); ldap_value_free_len(sid_ber); if (rc < 0) goto err; if ((sid_str = adutils_sid2txt(&sid)) == NULL) goto err; (void) strcpy(domains[ndomains].sid, sid_str); free(sid_str); dn = ldap_get_dn(*ld, entry); name = DN_to_DNS(dn); free(dn); if (name == NULL) goto err; (void) strcpy(domains[ndomains].domain, name); free(name); if (DBG(DISC, 1)) logger(LOG_DEBUG, " %s", domains[ndomains].domain); ndomains++; } if (ndomains == 0) { if (DBG(DISC, 1)) logger(LOG_DEBUG, " not found"); goto err; } if (ndomains < nresults) { ad_disc_domainsinforest_t *tmp; tmp = realloc(domains, (ndomains + 1) * sizeof (*domains)); if (tmp == NULL) goto err; domains = tmp; } if (result != NULL) (void) ldap_msgfree(result); return (domains); err: free(domains); if (result != NULL) (void) ldap_msgfree(result); return (NULL); } ad_disc_t ad_disc_init(void) { struct ad_disc *ctx; ctx = calloc(1, sizeof (struct ad_disc)); if (ctx != NULL) 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; ctx->domains_in_forest.type = AD_DOMAINS_IN_FOREST; ctx->trusted_domains.type = AD_TRUSTED_DOMAINS; /* Site specific versions */ ctx->site_domain_controller.type = AD_DIRECTORY; ctx->site_global_catalog.type = AD_DIRECTORY; return (ctx); } void ad_disc_fini(ad_disc_t ctx) { if (ctx == NULL) return; if (ctx->res_ninitted) res_ndestroy(&ctx->res_state); if (ctx->subnets != NULL) free(ctx->subnets); 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); if (ctx->forest_name.value != NULL) free(ctx->forest_name.value); if (ctx->global_catalog.value != NULL) free(ctx->global_catalog.value); if (ctx->domains_in_forest.value != NULL) free(ctx->domains_in_forest.value); if (ctx->trusted_domains.value != NULL) free(ctx->trusted_domains.value); /* Site specific versions */ if (ctx->site_domain_controller.value != NULL) free(ctx->site_domain_controller.value); if (ctx->site_global_catalog.value != NULL) free(ctx->site_global_catalog.value); free(ctx); } void ad_disc_refresh(ad_disc_t ctx) { if (ctx->res_ninitted) { res_ndestroy(&ctx->res_state); ctx->res_ninitted = 0; } (void) memset(&ctx->res_state, 0, sizeof (ctx->res_state)); 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; if (ctx->forest_name.state == AD_STATE_AUTO) ctx->forest_name.state = AD_STATE_INVALID; if (ctx->global_catalog.state == AD_STATE_AUTO) ctx->global_catalog.state = AD_STATE_INVALID; if (ctx->domains_in_forest.state == AD_STATE_AUTO) ctx->domains_in_forest.state = AD_STATE_INVALID; if (ctx->trusted_domains.state == AD_STATE_AUTO) ctx->trusted_domains.state = AD_STATE_INVALID; if (ctx->site_domain_controller.state == AD_STATE_AUTO) ctx->site_domain_controller.state = AD_STATE_INVALID; if (ctx->site_global_catalog.state == AD_STATE_AUTO) ctx->site_global_catalog.state = AD_STATE_INVALID; } /* * Called when the discovery cycle is done. Sets a master TTL * that will avoid doing new time-based discoveries too soon after * the last discovery cycle. Most interesting when the discovery * cycle failed, because then the TTLs on the individual items will * not be updated and may go stale. */ void ad_disc_done(ad_disc_t ctx) { time_t now = time(NULL); ctx->expires_not_before = now + MINIMUM_TTL; 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) { char *dname, *srvname; int len, rc; if (is_valid(&ctx->domain_name)) return (&ctx->domain_name); /* Try to find our domain by searching for DCs for it */ DO_RES_NINIT(ctx); if (DBG(DISC, 1)) logger(LOG_DEBUG, "Looking for our AD domain name..."); 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 (rc < 0) { if (DBG(DISC, 1)) logger(LOG_DEBUG, "Can't find our domain name."); return (NULL); } /* * We have the FQDN of the SRV RR name, so now we extract the * domainname suffix from it. */ dname = strdup(srvname + strlen(LDAP_SRV_HEAD DC_SRV_TAIL) + 1 /* for the dot between RR name and domainname */); free(srvname); if (dname == NULL) { logger(LOG_ERR, "Out of memory"); return (NULL); } /* Eat any trailing dot */ len = strlen(dname); if (len > 0 && dname[len - 1] == '.') dname[len - 1] = '\0'; if (DBG(DISC, 1)) logger(LOG_DEBUG, "Our domain name: %s", dname); /* * 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); } char * ad_disc_get_DomainName(ad_disc_t ctx, boolean_t *auto_discovered) { char *domain_name = NULL; ad_item_t *domain_name_item; domain_name_item = validate_DomainName(ctx); if (domain_name_item) { domain_name = strdup(domain_name_item->value); if (auto_discovered != NULL) *auto_discovered = (domain_name_item->state == AD_STATE_AUTO); } else if (auto_discovered != NULL) *auto_discovered = B_FALSE; return (domain_name); } /* Discover domain controllers */ static ad_item_t * validate_DomainController(ad_disc_t ctx, enum ad_disc_req req) { 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)) return (&ctx->domain_controller); domain_name_item = validate_DomainName(ctx); if (domain_name_item == NULL) { DEBUG1STATUS(ctx, "(no domain name)"); 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 { if (is_fixed(&ctx->site_name)) validate_site = B_TRUE; if (req == AD_DISC_PREFER_SITE) validate_global = B_TRUE; } /* * If we're trying both site-specific and global, * try the site-specific first, then fall-back. */ 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]; /* * Lookup DNS SRV RR named * _ldap._tcp.._sites.dc._msdcs. */ 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); DO_RES_NINIT(ctx); cdc = srv_query(&ctx->res_state, rr_name, domain_name, prefer_dc); if (cdc == NULL) { DEBUG1STATUS(ctx, "(no DNS response)"); goto try_global; } log_cds(ctx, cdc); /* * 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)"); goto try_global; } log_ds(ctx, dc); 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, site_name_item); } return (&ctx->site_domain_controller); } try_global: if (validate_global) { if (!is_valid(&ctx->domain_controller) || is_changed(&ctx->domain_controller, PARAM1, domain_name_item)) { /* * Lookup DNS SRV RR named * _ldap._tcp.dc._msdcs. */ DEBUG1STATUS(ctx, "DNS SRV query, dom=%s", domain_name); DO_RES_NINIT(ctx); cdc = srv_query(&ctx->res_state, LDAP_SRV_HEAD DC_SRV_TAIL, domain_name, prefer_dc); if (cdc == NULL) { DEBUG1STATUS(ctx, "(no DNS response)"); return (NULL); } log_cds(ctx, cdc); /* * 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, dc, AD_STATE_AUTO, dc->ttl); update_version(&ctx->domain_controller, PARAM1, domain_name_item); } return (&ctx->domain_controller); } return (NULL); } ad_disc_ds_t * ad_disc_get_DomainController(ad_disc_t ctx, enum ad_disc_req req, boolean_t *auto_discovered) { ad_item_t *domain_controller_item; ad_disc_ds_t *domain_controller = NULL; domain_controller_item = validate_DomainController(ctx, req); if (domain_controller_item != NULL) { domain_controller = ds_dup(domain_controller_item->value); if (auto_discovered != NULL) *auto_discovered = (domain_controller_item->state == AD_STATE_AUTO); } else if (auto_discovered != NULL) *auto_discovered = B_FALSE; return (domain_controller); } /* * Discover the Domain GUID * This info comes from validate_DomainController() */ static ad_item_t * validate_DomainGUID(ad_disc_t ctx) { ad_item_t *domain_controller_item; if (is_fixed(&ctx->domain_guid)) return (&ctx->domain_guid); domain_controller_item = validate_DomainController(ctx, AD_DISC_GLOBAL); if (domain_controller_item == NULL) return (NULL); if (!is_valid(&ctx->domain_guid)) return (NULL); return (&ctx->domain_guid); } 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; 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; return (domain_guid); } /* * Discover site name (for multi-homed systems the first one found wins) * This info comes from validate_DomainController() */ static ad_item_t * validate_SiteName(ad_disc_t ctx) { ad_item_t *domain_controller_item; if (is_fixed(&ctx->site_name)) return (&ctx->site_name); domain_controller_item = validate_DomainController(ctx, AD_DISC_GLOBAL); if (domain_controller_item == NULL) return (NULL); if (!is_valid(&ctx->site_name)) return (NULL); return (&ctx->site_name); } char * ad_disc_get_SiteName(ad_disc_t ctx, boolean_t *auto_discovered) { ad_item_t *site_name_item; char *site_name = NULL; site_name_item = validate_SiteName(ctx); if (site_name_item != NULL) { site_name = strdup(site_name_item->value); if (auto_discovered != NULL) *auto_discovered = (site_name_item->state == AD_STATE_AUTO); } else if (auto_discovered != NULL) *auto_discovered = B_FALSE; return (site_name); } /* * Discover forest name * This info comes from validate_DomainController() */ static ad_item_t * validate_ForestName(ad_disc_t ctx) { ad_item_t *domain_controller_item; if (is_fixed(&ctx->forest_name)) return (&ctx->forest_name); domain_controller_item = validate_DomainController(ctx, AD_DISC_GLOBAL); if (domain_controller_item == NULL) return (NULL); if (!is_valid(&ctx->forest_name)) return (NULL); return (&ctx->forest_name); } char * ad_disc_get_ForestName(ad_disc_t ctx, boolean_t *auto_discovered) { ad_item_t *forest_name_item; char *forest_name = NULL; forest_name_item = validate_ForestName(ctx); if (forest_name_item != NULL) { forest_name = strdup(forest_name_item->value); if (auto_discovered != NULL) *auto_discovered = (forest_name_item->state == AD_STATE_AUTO); } else if (auto_discovered != NULL) *auto_discovered = B_FALSE; return (forest_name); } /* Discover global catalog servers */ static ad_item_t * validate_GlobalCatalog(ad_disc_t ctx, enum ad_disc_req req) { 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)) return (&ctx->global_catalog); forest_name_item = validate_ForestName(ctx); if (forest_name_item == NULL) { DEBUG1STATUS(ctx, "(no forrest name)"); return (NULL); } forest_name = (char *)forest_name_item->value; if (req == AD_DISC_GLOBAL) validate_global = B_TRUE; else { if (is_fixed(&ctx->site_name)) validate_site = B_TRUE; if (req == AD_DISC_PREFER_SITE) validate_global = B_TRUE; } /* * If we're trying both site-specific and global, * try the site-specific first, then fall-back. */ 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]; /* * 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.._sites.gc. * _msdcs. */ 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, site_name); DO_RES_NINIT(ctx); cgc = srv_query(&ctx->res_state, rr_name, forest_name, NULL); if (cgc == NULL) { DEBUG1STATUS(ctx, "(no DNS response)"); goto try_global; } log_cds(ctx, cgc); /* * 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)"); goto try_global; } log_ds(ctx, gc); 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, site_name_item); } return (&ctx->site_global_catalog); } try_global: if (validate_global) { if (!is_valid(&ctx->global_catalog) || is_changed(&ctx->global_catalog, PARAM1, forest_name_item)) { /* * 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. */ DEBUG1STATUS(ctx, "DNS SRV query, forest=%s", forest_name); DO_RES_NINIT(ctx); cgc = srv_query(&ctx->res_state, LDAP_SRV_HEAD GC_SRV_TAIL, forest_name, NULL); if (cgc == NULL) { DEBUG1STATUS(ctx, "(no DNS response)"); return (NULL); } log_cds(ctx, cgc); /* * 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_global: update_item(&ctx->global_catalog, gc, AD_STATE_AUTO, gc->ttl); update_version(&ctx->global_catalog, PARAM1, forest_name_item); } return (&ctx->global_catalog); } return (NULL); } ad_disc_ds_t * ad_disc_get_GlobalCatalog(ad_disc_t ctx, enum ad_disc_req req, boolean_t *auto_discovered) { ad_disc_ds_t *global_catalog = NULL; ad_item_t *global_catalog_item; global_catalog_item = validate_GlobalCatalog(ctx, req); if (global_catalog_item != NULL) { global_catalog = ds_dup(global_catalog_item->value); if (auto_discovered != NULL) *auto_discovered = (global_catalog_item->state == AD_STATE_AUTO); } else if (auto_discovered != NULL) *auto_discovered = B_FALSE; return (global_catalog); } static ad_item_t * validate_TrustedDomains(ad_disc_t ctx) { LDAP *ld = NULL; ad_item_t *global_catalog_item; ad_item_t *forest_name_item; ad_disc_trusteddomains_t *trusted_domains; char *dn = NULL; char *forest_name_dn; int len; int num_parts; if (is_fixed(&ctx->trusted_domains)) return (&ctx->trusted_domains); global_catalog_item = validate_GlobalCatalog(ctx, AD_DISC_GLOBAL); if (global_catalog_item == NULL) return (NULL); forest_name_item = validate_ForestName(ctx); if (forest_name_item == NULL) return (NULL); if (!is_valid(&ctx->trusted_domains) || is_changed(&ctx->trusted_domains, PARAM1, global_catalog_item) || is_changed(&ctx->trusted_domains, PARAM2, forest_name_item)) { forest_name_dn = ldap_dns_to_dn(forest_name_item->value, &num_parts); if (forest_name_dn == NULL) return (NULL); len = snprintf(NULL, 0, "CN=System,%s", forest_name_dn) + 1; dn = malloc(len); if (dn == NULL) { free(forest_name_dn); return (NULL); } (void) snprintf(dn, len, "CN=System,%s", forest_name_dn); free(forest_name_dn); trusted_domains = ldap_lookup_trusted_domains( &ld, global_catalog_item->value, dn); if (ld != NULL) (void) ldap_unbind(ld); free(dn); if (trusted_domains == NULL) return (NULL); update_item(&ctx->trusted_domains, trusted_domains, AD_STATE_AUTO, 0); update_version(&ctx->trusted_domains, PARAM1, global_catalog_item); update_version(&ctx->trusted_domains, PARAM2, forest_name_item); } return (&ctx->trusted_domains); } ad_disc_trusteddomains_t * ad_disc_get_TrustedDomains(ad_disc_t ctx, boolean_t *auto_discovered) { ad_disc_trusteddomains_t *trusted_domains = NULL; ad_item_t *trusted_domains_item; trusted_domains_item = validate_TrustedDomains(ctx); if (trusted_domains_item != NULL) { trusted_domains = td_dup(trusted_domains_item->value); if (auto_discovered != NULL) *auto_discovered = (trusted_domains_item->state == AD_STATE_AUTO); } else if (auto_discovered != NULL) *auto_discovered = B_FALSE; return (trusted_domains); } static ad_item_t * validate_DomainsInForest(ad_disc_t ctx) { ad_item_t *global_catalog_item; LDAP *ld = NULL; ad_disc_domainsinforest_t *domains_in_forest; if (is_fixed(&ctx->domains_in_forest)) return (&ctx->domains_in_forest); global_catalog_item = validate_GlobalCatalog(ctx, AD_DISC_GLOBAL); if (global_catalog_item == NULL) return (NULL); if (!is_valid(&ctx->domains_in_forest) || is_changed(&ctx->domains_in_forest, PARAM1, global_catalog_item)) { domains_in_forest = ldap_lookup_domains_in_forest( &ld, global_catalog_item->value); if (ld != NULL) (void) ldap_unbind(ld); if (domains_in_forest == NULL) return (NULL); update_item(&ctx->domains_in_forest, domains_in_forest, AD_STATE_AUTO, 0); update_version(&ctx->domains_in_forest, PARAM1, global_catalog_item); } return (&ctx->domains_in_forest); } ad_disc_domainsinforest_t * ad_disc_get_DomainsInForest(ad_disc_t ctx, boolean_t *auto_discovered) { ad_disc_domainsinforest_t *domains_in_forest = NULL; ad_item_t *domains_in_forest_item; domains_in_forest_item = validate_DomainsInForest(ctx); if (domains_in_forest_item != NULL) { domains_in_forest = df_dup(domains_in_forest_item->value); if (auto_discovered != NULL) *auto_discovered = (domains_in_forest_item->state == AD_STATE_AUTO); } else if (auto_discovered != NULL) *auto_discovered = B_FALSE; 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); } int ad_disc_set_DomainName(ad_disc_t ctx, const char *domainName) { char *domain_name = NULL; if (domainName != NULL) { domain_name = strdup(domainName); if (domain_name == NULL) return (-1); update_item(&ctx->domain_name, domain_name, AD_STATE_FIXED, 0); } else if (ctx->domain_name.state == AD_STATE_FIXED) ctx->domain_name.state = AD_STATE_INVALID; 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 ad_disc_ds_t *domainController) { ad_disc_ds_t *domain_controller = NULL; if (domainController != NULL) { domain_controller = ds_dup(domainController); if (domain_controller == NULL) return (-1); update_item(&ctx->domain_controller, domain_controller, AD_STATE_FIXED, 0); } else if (ctx->domain_controller.state == AD_STATE_FIXED) ctx->domain_controller.state = AD_STATE_INVALID; return (0); } int ad_disc_set_SiteName(ad_disc_t ctx, const char *siteName) { char *site_name = NULL; if (siteName != NULL) { site_name = strdup(siteName); if (site_name == NULL) return (-1); update_item(&ctx->site_name, site_name, AD_STATE_FIXED, 0); } else if (ctx->site_name.state == AD_STATE_FIXED) ctx->site_name.state = AD_STATE_INVALID; 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) { char *forest_name = NULL; if (forestName != NULL) { forest_name = strdup(forestName); if (forest_name == NULL) return (-1); update_item(&ctx->forest_name, forest_name, AD_STATE_FIXED, 0); } else if (ctx->forest_name.state == AD_STATE_FIXED) ctx->forest_name.state = AD_STATE_INVALID; 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 ad_disc_ds_t *globalCatalog) { ad_disc_ds_t *global_catalog = NULL; if (globalCatalog != NULL) { global_catalog = ds_dup(globalCatalog); if (global_catalog == NULL) return (-1); update_item(&ctx->global_catalog, global_catalog, AD_STATE_FIXED, 0); } else if (ctx->global_catalog.state == AD_STATE_FIXED) ctx->global_catalog.state = AD_STATE_INVALID; 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) { if (ctx->domain_name.state == AD_STATE_FIXED) ctx->domain_name.state = AD_STATE_INVALID; 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; if (ctx->forest_name.state == AD_STATE_FIXED) ctx->forest_name.state = AD_STATE_INVALID; if (ctx->global_catalog.state == AD_STATE_FIXED) ctx->global_catalog.state = AD_STATE_INVALID; return (0); } /* * ad_disc_get_TTL * * This routines the time to live for AD * auto discovered items. * * Returns: * -1 if there are no TTL items * 0 if there are expired items * else the number of seconds * * The MIN_GT_ZERO(x, y) macro return the lesser of x and y, provided it * is positive -- min() greater than zero. */ #define MIN_GT_ZERO(x, y) (((x) <= 0) ? (((y) <= 0) ? \ (-1) : (y)) : (((y) <= 0) ? (x) : (((x) > (y)) ? (y) : (x)))) int ad_disc_get_TTL(ad_disc_t ctx) { time_t expires; int ttl; expires = MIN_GT_ZERO(ctx->domain_controller.expires, ctx->global_catalog.expires); expires = MIN_GT_ZERO(expires, ctx->site_domain_controller.expires); expires = MIN_GT_ZERO(expires, ctx->site_global_catalog.expires); if (expires == -1) { return (-1); } if (ctx->expires_not_before != 0 && expires < ctx->expires_not_before) { expires = ctx->expires_not_before; } if (ctx->expires_not_after != 0 && expires > ctx->expires_not_after) { expires = ctx->expires_not_after; } ttl = expires - time(NULL); if (ttl < 0) { return (0); } return (ttl); } boolean_t ad_disc_SubnetChanged(ad_disc_t ctx) { ad_subnet_t *subnets; if (ctx->subnets_changed || ctx->subnets == NULL) return (B_TRUE); if ((subnets = find_subnets()) != NULL) { if (cmpsubnets(subnets, ctx->subnets) != 0) ctx->subnets_changed = B_TRUE; free(subnets); } return (ctx->subnets_changed); }