diff options
Diffstat (limited to 'source/libads/ldap.c')
-rw-r--r-- | source/libads/ldap.c | 670 |
1 files changed, 670 insertions, 0 deletions
diff --git a/source/libads/ldap.c b/source/libads/ldap.c new file mode 100644 index 00000000000..0028d11b8fd --- /dev/null +++ b/source/libads/ldap.c @@ -0,0 +1,670 @@ +/* + Unix SMB/CIFS implementation. + ads (active directory) utility library + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Remus Koos 2001 + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" + +#ifdef HAVE_ADS + +/* + connect to the LDAP server +*/ +ADS_STATUS ads_connect(ADS_STRUCT *ads) +{ + int version = LDAP_VERSION3; + int code; + ADS_STATUS status; + + ads->last_attempt = time(NULL); + + ads->ld = ldap_open(ads->ldap_server, ads->ldap_port); + if (!ads->ld) { + return ADS_ERROR_SYSTEM(errno) + } + status = ads_server_info(ads); + if (!ADS_ERR_OK(status)) { + DEBUG(1,("Failed to get ldap server info\n")); + return status; + } + + ldap_set_option(ads->ld, LDAP_OPT_PROTOCOL_VERSION, &version); + + if (ads->password) { + if ((code = ads_kinit_password(ads))) + return ADS_ERROR_KRB5(code); + } + + return ads_sasl_bind(ads); +} + +/* + do a search with a timeout +*/ +ADS_STATUS ads_do_search(ADS_STRUCT *ads, const char *bind_path, int scope, + const char *exp, + const char **attrs, void **res) +{ + struct timeval timeout; + int rc; + + timeout.tv_sec = ADS_SEARCH_TIMEOUT; + timeout.tv_usec = 0; + *res = NULL; + + rc = ldap_search_ext_s(ads->ld, + bind_path, scope, + exp, attrs, 0, NULL, NULL, + &timeout, LDAP_NO_LIMIT, (LDAPMessage **)res); + return ADS_ERROR(rc); +} +/* + do a general ADS search +*/ +ADS_STATUS ads_search(ADS_STRUCT *ads, void **res, + const char *exp, + const char **attrs) +{ + return ads_do_search(ads, ads->bind_path, LDAP_SCOPE_SUBTREE, + exp, attrs, res); +} + +/* + do a search on a specific DistinguishedName +*/ +ADS_STATUS ads_search_dn(ADS_STRUCT *ads, void **res, + const char *dn, + const char **attrs) +{ + return ads_do_search(ads, dn, LDAP_SCOPE_BASE, "(objectclass=*)", attrs, res); +} + +/* + free up memory from a ads_search +*/ +void ads_msgfree(ADS_STRUCT *ads, void *msg) +{ + if (!msg) return; + ldap_msgfree(msg); +} + +/* + find a machine account given a hostname +*/ +ADS_STATUS ads_find_machine_acct(ADS_STRUCT *ads, void **res, const char *host) +{ + ADS_STATUS status; + char *exp; + const char *attrs[] = {"*", "nTSecurityDescriptor", NULL}; + + /* the easiest way to find a machine account anywhere in the tree + is to look for hostname$ */ + asprintf(&exp, "(samAccountName=%s$)", host); + status = ads_search(ads, res, exp, attrs); + free(exp); + return status; +} + + +/* + a convenient routine for adding a generic LDAP record +*/ +ADS_STATUS ads_gen_add(ADS_STRUCT *ads, const char *new_dn, ...) +{ + int i; + va_list ap; + LDAPMod **mods; + char *name, *value; + int ret; +#define MAX_MOD_VALUES 10 + + /* count the number of attributes */ + va_start(ap, new_dn); + for (i=0; va_arg(ap, char *); i++) { + /* skip the values */ + while (va_arg(ap, char *)) ; + } + va_end(ap); + + mods = malloc(sizeof(LDAPMod *) * (i+1)); + + va_start(ap, new_dn); + for (i=0; (name=va_arg(ap, char *)); i++) { + char **values; + int j; + values = (char **)malloc(sizeof(char *) * (MAX_MOD_VALUES+1)); + for (j=0; (value=va_arg(ap, char *)) && j < MAX_MOD_VALUES; j++) { + values[j] = value; + } + values[j] = NULL; + mods[i] = malloc(sizeof(LDAPMod)); + mods[i]->mod_type = name; + mods[i]->mod_op = LDAP_MOD_ADD; + mods[i]->mod_values = values; + } + mods[i] = NULL; + va_end(ap); + + ret = ldap_add_s(ads->ld, new_dn, mods); + + for (i=0; mods[i]; i++) { + free(mods[i]->mod_values); + free(mods[i]); + } + free(mods); + + return ADS_ERROR(ret); +} + +/* + build an org unit string + if org unit is Computers or blank then assume a container, otherwise + assume a \ separated list of organisational units + caller must free +*/ +char *ads_ou_string(const char *org_unit) +{ + if (!org_unit || !*org_unit || strcasecmp(org_unit, "Computers") == 0) { + return strdup("cn=Computers"); + } + + return ads_build_path(org_unit, "\\/", "ou=", 1); +} + + + +/* + add a machine account to the ADS server +*/ +static ADS_STATUS ads_add_machine_acct(ADS_STRUCT *ads, const char *hostname, + const char *org_unit) +{ + ADS_STATUS ret; + char *host_spn, *host_upn, *new_dn, *samAccountName, *controlstr; + char *ou_str; + + asprintf(&host_spn, "HOST/%s", hostname); + asprintf(&host_upn, "%s@%s", host_spn, ads->realm); + ou_str = ads_ou_string(org_unit); + asprintf(&new_dn, "cn=%s,%s,%s", hostname, ou_str, ads->bind_path); + free(ou_str); + asprintf(&samAccountName, "%s$", hostname); + asprintf(&controlstr, "%u", + UF_DONT_EXPIRE_PASSWD | UF_WORKSTATION_TRUST_ACCOUNT | + UF_TRUSTED_FOR_DELEGATION | UF_USE_DES_KEY_ONLY); + + ret = ads_gen_add(ads, new_dn, + "cn", hostname, NULL, + "sAMAccountName", samAccountName, NULL, + "objectClass", + "top", "person", "organizationalPerson", + "user", "computer", NULL, + "userPrincipalName", host_upn, NULL, + "servicePrincipalName", host_spn, NULL, + "dNSHostName", hostname, NULL, + "userAccountControl", controlstr, NULL, + "operatingSystem", "Samba", NULL, + "operatingSystemVersion", VERSION, NULL, + NULL); + + free(host_spn); + free(host_upn); + free(new_dn); + free(samAccountName); + free(controlstr); + + return ret; +} + +/* + dump a binary result from ldap +*/ +static void dump_binary(const char *field, struct berval **values) +{ + int i, j; + for (i=0; values[i]; i++) { + printf("%s: ", field); + for (j=0; j<values[i]->bv_len; j++) { + printf("%02X", (unsigned char)values[i]->bv_val[j]); + } + printf("\n"); + } +} + +/* + dump a sid result from ldap +*/ +static void dump_sid(const char *field, struct berval **values) +{ + int i; + for (i=0; values[i]; i++) { + DOM_SID sid; + sid_parse(values[i]->bv_val, values[i]->bv_len, &sid); + printf("%s: %s\n", field, sid_string_static(&sid)); + } +} + +/* + dump a string result from ldap +*/ +static void dump_string(const char *field, struct berval **values) +{ + int i; + for (i=0; values[i]; i++) { + printf("%s: %s\n", field, values[i]->bv_val); + } +} + +/* + dump a record from LDAP on stdout + used for debugging +*/ +void ads_dump(ADS_STRUCT *ads, void *res) +{ + char *field; + void *msg; + BerElement *b; + struct { + char *name; + void (*handler)(const char *, struct berval **); + } handlers[] = { + {"objectGUID", dump_binary}, + {"nTSecurityDescriptor", dump_binary}, + {"objectSid", dump_sid}, + {NULL, NULL} + }; + + for (msg = ads_first_entry(ads, res); msg; msg = ads_next_entry(ads, msg)) { + for (field = ldap_first_attribute(ads->ld, (LDAPMessage *)msg, &b); + field; + field = ldap_next_attribute(ads->ld, (LDAPMessage *)msg, b)) { + struct berval **values; + int i; + + values = ldap_get_values_len(ads->ld, (LDAPMessage *)msg, field); + + for (i=0; handlers[i].name; i++) { + if (StrCaseCmp(handlers[i].name, field) == 0) { + handlers[i].handler(field, values); + break; + } + } + if (!handlers[i].name) { + dump_string(field, values); + } + ldap_value_free_len(values); + ldap_memfree(field); + } + + ber_free(b, 1); + printf("\n"); + } +} + +/* + count how many replies are in a LDAPMessage +*/ +int ads_count_replies(ADS_STRUCT *ads, void *res) +{ + return ldap_count_entries(ads->ld, (LDAPMessage *)res); +} + +/* + join a machine to a realm, creating the machine account + and setting the machine password +*/ +ADS_STATUS ads_join_realm(ADS_STRUCT *ads, const char *hostname, const char *org_unit) +{ + ADS_STATUS status; + LDAPMessage *res; + char *host; + + /* hostname must be lowercase */ + host = strdup(hostname); + strlower(host); + + status = ads_find_machine_acct(ads, (void **)&res, host); + if (ADS_ERR_OK(status) && ads_count_replies(ads, res) == 1) { + DEBUG(0, ("Host account for %s already exists - deleting for readd\n", host)); + status = ads_leave_realm(ads, host); + if (!ADS_ERR_OK(status)) { + DEBUG(0, ("Failed to delete host '%s' from the '%s' realm.\n", + host, ads->realm)); + return status; + } + } + + status = ads_add_machine_acct(ads, host, org_unit); + if (!ADS_ERR_OK(status)) { + DEBUG(0, ("ads_add_machine_acct: %s\n", ads_errstr(status))); + return status; + } + + status = ads_find_machine_acct(ads, (void **)&res, host); + if (!ADS_ERR_OK(status)) { + DEBUG(0, ("Host account test failed\n")); + return status; + } + + free(host); + + return status; +} + +/* + delete a machine from the realm +*/ +ADS_STATUS ads_leave_realm(ADS_STRUCT *ads, const char *hostname) +{ + ADS_STATUS status; + void *res; + char *hostnameDN, *host; + int rc; + + /* hostname must be lowercase */ + host = strdup(hostname); + strlower(host); + + status = ads_find_machine_acct(ads, &res, host); + if (!ADS_ERR_OK(status)) { + DEBUG(0, ("Host account for %s does not exist.\n", host)); + return status; + } + + hostnameDN = ldap_get_dn(ads->ld, (LDAPMessage *)res); + rc = ldap_delete_s(ads->ld, hostnameDN); + ldap_memfree(hostnameDN); + if (rc != LDAP_SUCCESS) { + return ADS_ERROR(rc); + } + + status = ads_find_machine_acct(ads, &res, host); + if (ADS_ERR_OK(status) && ads_count_replies(ads, res) == 1) { + DEBUG(0, ("Failed to remove host account.\n")); + return status; + } + + free(host); + + return status; +} + + +ADS_STATUS ads_set_machine_password(ADS_STRUCT *ads, + const char *hostname, + const char *password) +{ + ADS_STATUS status; + char *host = strdup(hostname); + char *principal; + + strlower(host); + + asprintf(&principal, "%s@%s", host, ads->realm); + + status = krb5_set_password(ads->kdc_server, principal, password); + + free(host); + free(principal); + + return status; +} + +/* + pull the first entry from a ADS result +*/ +void *ads_first_entry(ADS_STRUCT *ads, void *res) +{ + return (void *)ldap_first_entry(ads->ld, (LDAPMessage *)res); +} + +/* + pull the next entry from a ADS result +*/ +void *ads_next_entry(ADS_STRUCT *ads, void *res) +{ + return (void *)ldap_next_entry(ads->ld, (LDAPMessage *)res); +} + +/* + pull a single string from a ADS result +*/ +char *ads_pull_string(ADS_STRUCT *ads, + TALLOC_CTX *mem_ctx, void *msg, const char *field) +{ + char **values; + char *ret = NULL; + + values = ldap_get_values(ads->ld, msg, field); + if (!values) return NULL; + + if (values[0]) { + ret = talloc_strdup(mem_ctx, values[0]); + } + ldap_value_free(values); + return ret; +} + +/* + pull a single uint32 from a ADS result +*/ +BOOL ads_pull_uint32(ADS_STRUCT *ads, + void *msg, const char *field, uint32 *v) +{ + char **values; + + values = ldap_get_values(ads->ld, msg, field); + if (!values) return False; + if (!values[0]) { + ldap_value_free(values); + return False; + } + + *v = atoi(values[0]); + ldap_value_free(values); + return True; +} + +/* + pull a single DOM_SID from a ADS result +*/ +BOOL ads_pull_sid(ADS_STRUCT *ads, + void *msg, const char *field, DOM_SID *sid) +{ + struct berval **values; + BOOL ret = False; + + values = ldap_get_values_len(ads->ld, msg, field); + + if (!values) return False; + + if (values[0]) { + ret = sid_parse(values[0]->bv_val, values[0]->bv_len, sid); + } + + ldap_value_free_len(values); + return ret; +} + +/* + pull an array of DOM_SIDs from a ADS result + return the count of SIDs pulled +*/ +int ads_pull_sids(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, + void *msg, const char *field, DOM_SID **sids) +{ + struct berval **values; + BOOL ret; + int count, i; + + values = ldap_get_values_len(ads->ld, msg, field); + + if (!values) return 0; + + for (i=0; values[i]; i++) /* nop */ ; + + (*sids) = talloc(mem_ctx, sizeof(DOM_SID) * i); + + count = 0; + for (i=0; values[i]; i++) { + ret = sid_parse(values[i]->bv_val, values[i]->bv_len, &(*sids)[count]); + if (ret) count++; + } + + ldap_value_free_len(values); + return count; +} + + +/* find the update serial number - this is the core of the ldap cache */ +ADS_STATUS ads_USN(ADS_STRUCT *ads, uint32 *usn) +{ + const char *attrs[] = {"highestCommittedUSN", NULL}; + ADS_STATUS status; + void *res; + + status = ads_do_search(ads, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, &res); + if (!ADS_ERR_OK(status)) return status; + + if (ads_count_replies(ads, res) != 1) { + return ADS_ERROR(LDAP_NO_RESULTS_RETURNED); + } + + ads_pull_uint32(ads, res, "highestCommittedUSN", usn); + ads_msgfree(ads, res); + return ADS_SUCCESS; +} + + +/* find the servers name and realm - this can be done before authentication + The ldapServiceName field on w2k looks like this: + vnet3.home.samba.org:win2000-vnet3$@VNET3.HOME.SAMBA.ORG +*/ +ADS_STATUS ads_server_info(ADS_STRUCT *ads) +{ + const char *attrs[] = {"ldapServiceName", NULL}; + ADS_STATUS status; + void *res; + char **values; + char *p; + + status = ads_do_search(ads, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, &res); + if (!ADS_ERR_OK(status)) return status; + + values = ldap_get_values(ads->ld, res, "ldapServiceName"); + if (!values || !values[0]) return ADS_ERROR(LDAP_NO_RESULTS_RETURNED); + + p = strchr(values[0], ':'); + if (!p) { + ldap_value_free(values); + ldap_msgfree(res); + return ADS_ERROR(LDAP_DECODING_ERROR); + } + + SAFE_FREE(ads->ldap_server_name); + + ads->ldap_server_name = strdup(p+1); + p = strchr(ads->ldap_server_name, '$'); + if (!p || p[1] != '@') { + ldap_value_free(values); + ldap_msgfree(res); + SAFE_FREE(ads->ldap_server_name); + return ADS_ERROR(LDAP_DECODING_ERROR); + } + + *p = 0; + + SAFE_FREE(ads->server_realm); + SAFE_FREE(ads->bind_path); + + ads->server_realm = strdup(p+2); + ads->bind_path = ads_build_dn(ads->server_realm); + + /* in case the realm isn't configured in smb.conf */ + if (!ads->realm || !ads->realm[0]) { + SAFE_FREE(ads->realm); + ads->realm = strdup(ads->server_realm); + } + + DEBUG(3,("got ldap server name %s@%s\n", + ads->ldap_server_name, ads->realm)); + + return ADS_SUCCESS; +} + + +/* + find the list of trusted domains +*/ +ADS_STATUS ads_trusted_domains(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, + int *num_trusts, char ***names, DOM_SID **sids) +{ + const char *attrs[] = {"flatName", "securityIdentifier", NULL}; + ADS_STATUS status; + void *res, *msg; + int count, i; + + *num_trusts = 0; + + status = ads_search(ads, &res, "(objectcategory=trustedDomain)", attrs); + if (!ADS_ERR_OK(status)) return status; + + count = ads_count_replies(ads, res); + if (count == 0) { + ads_msgfree(ads, res); + return ADS_ERROR(LDAP_NO_RESULTS_RETURNED); + } + + (*names) = talloc(mem_ctx, sizeof(char *) * count); + (*sids) = talloc(mem_ctx, sizeof(DOM_SID) * count); + if (! *names || ! *sids) return ADS_ERROR(LDAP_NO_MEMORY); + + for (i=0, msg = ads_first_entry(ads, res); msg; msg = ads_next_entry(ads, msg)) { + (*names)[i] = ads_pull_string(ads, mem_ctx, msg, "flatName"); + ads_pull_sid(ads, msg, "securityIdentifier", &(*sids)[i]); + i++; + } + + ads_msgfree(ads, res); + + *num_trusts = i; + + return ADS_SUCCESS; +} + +/* find the domain sid for our domain */ +ADS_STATUS ads_domain_sid(ADS_STRUCT *ads, DOM_SID *sid) +{ + const char *attrs[] = {"objectSid", NULL}; + void *res; + ADS_STATUS rc; + + rc = ads_do_search(ads, ads->bind_path, LDAP_SCOPE_BASE, "(objectclass=*)", + attrs, &res); + if (!ADS_ERR_OK(rc)) return rc; + if (!ads_pull_sid(ads, res, "objectSid", sid)) { + return ADS_ERROR_SYSTEM(ENOENT); + } + ads_msgfree(ads, res); + + return ADS_SUCCESS; +} + +#endif |