Fix up 'net ads join' to delete and rejoin if the account already exists.
[ira/wip.git] / source3 / libads / ldap.c
index 34ca2ad04dbdfca00f1eb1954ef6d01148eaffba..2e93e11603a61bcd15c1b1edb492a9841965e1bd 100644 (file)
@@ -3,6 +3,8 @@
    Version 3.0
    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
 
 #ifdef HAVE_ADS
 
-/* return a dn of the form "dc=AA,dc=BB,dc=CC" from a 
-   realm of the form AA.BB.CC 
-   caller must free
-*/
-/*
-  return a string for an error from a ads routine
-*/
-char *ads_errstr(int rc)
-{
-       return ldap_err2string(rc);
-}
-
-/*
-  this is a minimal interact function, just enough for SASL to talk
-  GSSAPI/kerberos to W2K
-  Error handling is a bit of a problem. I can't see how to get Cyrus-sasl
-  to give sensible errors
-*/
-static int sasl_interact(LDAP *ld,unsigned flags,void *defaults,void *in)
-{
-       sasl_interact_t *interact = in;
-
-       while (interact->id != SASL_CB_LIST_END) {
-               interact->result = strdup("");
-               interact->len = strlen(interact->result);
-               interact++;
-       }
-       
-       return LDAP_SUCCESS;
-}
-
 /*
   connect to the LDAP server
 */
-int ads_connect(ADS_STRUCT *ads)
+ADS_STATUS ads_connect(ADS_STRUCT *ads)
 {
        int version = LDAP_VERSION3;
-       int rc;
+       ADS_STATUS status;
+
+       ads->last_attempt = time(NULL);
 
        ads->ld = ldap_open(ads->ldap_server, ads->ldap_port);
        if (!ads->ld) {
-               return errno;
+               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);
 
-       rc = ldap_sasl_interactive_bind_s(ads->ld, NULL, NULL, NULL, NULL, 
-                                         LDAP_SASL_QUIET,
-                                         sasl_interact, NULL);
+       if (ads->password) {
+               ads_kinit_password(ads);
+       }
 
-       return rc;
+       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
 */
-int ads_search(ADS_STRUCT *ads, void **res, 
-              const char *exp, 
-              const char **attrs)
+ADS_STATUS ads_search(ADS_STRUCT *ads, void **res, 
+                     const char *exp, 
+                     const char **attrs)
 {
-       *res = NULL;
-       return ldap_search_s(ads->ld, ads->bind_path, 
-                            LDAP_SCOPE_SUBTREE, exp, (char **)attrs, 0, (LDAPMessage **)res);
+       return ads_do_search(ads, ads->bind_path, LDAP_SCOPE_SUBTREE, 
+                            exp, attrs, res);
 }
 
 /*
   do a search on a specific DistinguishedName
 */
-int ads_search_dn(ADS_STRUCT *ads, void **res, 
-                 const char *dn, 
-                 const char **attrs)
+ADS_STATUS ads_search_dn(ADS_STRUCT *ads, void **res, 
+                        const char *dn, 
+                        const char **attrs)
 {
-       *res = NULL;
-       return ldap_search_s(ads->ld, dn, 
-                            LDAP_SCOPE_BASE, "(objectclass=*)", (char **)attrs, 0, (LDAPMessage **)res);
+       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 
 */
-int ads_find_machine_acct(ADS_STRUCT *ads, void **res, const char *host)
+ADS_STATUS ads_find_machine_acct(ADS_STRUCT *ads, void **res, const char *host)
 {
-       int ret;
+       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);
-       ret = ads_search(ads, res, exp, NULL);
+       status = ads_search(ads, res, exp, attrs);
        free(exp);
-       return ret;
+       return status;
 }
 
 
 /*
   a convenient routine for adding a generic LDAP record 
 */
-int ads_gen_add(ADS_STRUCT *ads, const char *new_dn, ...)
+ADS_STATUS ads_gen_add(ADS_STRUCT *ads, const char *new_dn, ...)
 {
        int i;
        va_list ap;
@@ -165,20 +169,21 @@ int ads_gen_add(ADS_STRUCT *ads, const char *new_dn, ...)
        }
        free(mods);
        
-       return ret;
+       return ADS_ERROR(ret);
 }
 
 /*
   add a machine account to the ADS server
 */
-static int ads_add_machine_acct(ADS_STRUCT *ads, const char *hostname)
+static ADS_STATUS ads_add_machine_acct(ADS_STRUCT *ads, const char *hostname, 
+                                      const char *org_unit)
 {
-       int ret;
+       ADS_STATUS ret;
        char *host_spn, *host_upn, *new_dn, *samAccountName, *controlstr;
 
        asprintf(&host_spn, "HOST/%s", hostname);
        asprintf(&host_upn, "%s@%s", host_spn, ads->realm);
-       asprintf(&new_dn, "cn=%s,cn=Computers,%s", hostname, ads->bind_path);
+       asprintf(&new_dn, "cn=%s,cn=%s,%s", hostname, org_unit, ads->bind_path);
        asprintf(&samAccountName, "%s$", hostname);
        asprintf(&controlstr, "%u", 
                UF_DONT_EXPIRE_PASSWD | UF_WORKSTATION_TRUST_ACCOUNT | 
@@ -222,6 +227,19 @@ static void dump_binary(const char *field, struct berval **values)
        }
 }
 
+/*
+  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
 */
@@ -247,7 +265,8 @@ void ads_dump(ADS_STRUCT *ads, void *res)
                void (*handler)(const char *, struct berval **);
        } handlers[] = {
                {"objectGUID", dump_binary},
-               {"objectSid", dump_binary},
+               {"nTSecurityDescriptor", dump_binary},
+               {"objectSid", dump_sid},
                {NULL, NULL}
        };
     
@@ -290,9 +309,9 @@ int ads_count_replies(ADS_STRUCT *ads, void *res)
   join a machine to a realm, creating the machine account
   and setting the machine password
 */
-int ads_join_realm(ADS_STRUCT *ads, const char *hostname)
+ADS_STATUS ads_join_realm(ADS_STRUCT *ads, const char *hostname, const char *org_unit)
 {
-       int rc;
+       ADS_STATUS status;
        LDAPMessage *res;
        char *host;
 
@@ -300,119 +319,91 @@ int ads_join_realm(ADS_STRUCT *ads, const char *hostname)
        host = strdup(hostname);
        strlower(host);
 
-       rc = ads_find_machine_acct(ads, (void **)&res, host);
-       if (rc == LDAP_SUCCESS && ads_count_replies(ads, res) == 1) {
-               DEBUG(0, ("Host account for %s already exists\n", host));
-               return LDAP_SUCCESS;
+       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;
+               }
        }
 
-       rc = ads_add_machine_acct(ads, host);
-       if (rc != LDAP_SUCCESS) {
-               DEBUG(0, ("ads_add_machine_acct: %s\n", ads_errstr(rc)));
-               return rc;
+       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;
        }
 
-       rc = ads_find_machine_acct(ads, (void **)&res, host);
-       if (rc != LDAP_SUCCESS || ads_count_replies(ads, res) != 1) {
+       status = ads_find_machine_acct(ads, (void **)&res, host);
+       if (!ADS_ERR_OK(status)) {
                DEBUG(0, ("Host account test failed\n"));
-               /* hmmm, we need NTSTATUS */
-               return -1;
+               return status;
        }
 
        free(host);
 
-       return LDAP_SUCCESS;
+       return status;
 }
 
 /*
   delete a machine from the realm
 */
-int ads_leave_realm(ADS_STRUCT *ads, const char *hostname)
+ADS_STATUS ads_leave_realm(ADS_STRUCT *ads, const char *hostname)
 {
-       int rc;
+       ADS_STATUS status;
        void *res;
        char *hostnameDN, *host; 
+       int rc;
 
        /* hostname must be lowercase */
        host = strdup(hostname);
        strlower(host);
 
-       rc = ads_find_machine_acct(ads, &res, host);
-       if (rc != LDAP_SUCCESS || ads_count_replies(ads, res) != 1) {
+       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 -1;
+           return status;
        }
 
        hostnameDN = ldap_get_dn(ads->ld, (LDAPMessage *)res);
        rc = ldap_delete_s(ads->ld, hostnameDN);
        ldap_memfree(hostnameDN);
        if (rc != LDAP_SUCCESS) {
-           DEBUG(0, ("ldap_delete_s: %s\n", ads_errstr(rc)));
-           return rc;
+               return ADS_ERROR(rc);
        }
 
-       rc = ads_find_machine_acct(ads, &res, host);
-       if (rc == LDAP_SUCCESS && ads_count_replies(ads, res) == 1 ) {
-           DEBUG(0, ("Failed to remove host account.\n"));
-           /*hmmm, we need NTSTATUS */
-           return -1;
+       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 LDAP_SUCCESS;
+       return status;
 }
 
 
-NTSTATUS ads_set_machine_password(ADS_STRUCT *ads,
-                                 const char *hostname, 
-                                 const char *password)
+ADS_STATUS ads_set_machine_password(ADS_STRUCT *ads,
+                                   const char *hostname, 
+                                   const char *password)
 {
-       NTSTATUS ret;
+       ADS_STATUS status;
        char *host = strdup(hostname);
-       strlower(host);
-       ret = krb5_set_password(ads->kdc_server, host, ads->realm, password);
-       free(host);
-       return ret;
-}
+       char *principal; 
 
+       strlower(host);
 
-/*
-  return a RFC2254 binary string representation of a buffer
-  used in filters
-  caller must free
-*/
-char *ads_binary_string(char *buf, int len)
-{
-       char *s;
-       int i, j;
-       const char *hex = "0123456789ABCDEF";
-       s = malloc(len * 3 + 1);
-       if (!s) return NULL;
-       for (j=i=0;i<len;i++) {
-               s[j] = '\\';
-               s[j+1] = hex[((unsigned char)buf[i]) >> 4];
-               s[j+2] = hex[((unsigned char)buf[i]) & 0xF];
-               j += 3;
-       }
-       s[j] = 0;
-       return s;
-}
+       asprintf(&principal, "%s@%s", host, ads->realm);
+       
+       status = krb5_set_password(ads->kdc_server, principal, password);
+       
+       free(host);
+       free(principal);
 
-/*
-  return the binary string representation of a DOM_SID
-  caller must free
-*/
-char *ads_sid_binstring(DOM_SID *sid)
-{
-       char *buf, *s;
-       int len = sid_size(sid);
-       buf = malloc(len);
-       if (!buf) return NULL;
-       sid_linearize(buf, len, sid);
-       s = ads_binary_string(buf, len);
-       free(buf);
-       return s;
+       return status;
 }
 
 /*
@@ -438,13 +429,14 @@ char *ads_pull_string(ADS_STRUCT *ads,
                      TALLOC_CTX *mem_ctx, void *msg, const char *field)
 {
        char **values;
-       char *ret;
+       char *ret = NULL;
 
        values = ldap_get_values(ads->ld, msg, field);
-
-       if (!values || !values[0]) return NULL;
-
-       ret = talloc_strdup(mem_ctx, values[0]);
+       if (!values) return NULL;
+       
+       if (values[0]) {
+               ret = talloc_strdup(mem_ctx, values[0]);
+       }
        ldap_value_free(values);
        return ret;
 }
@@ -458,8 +450,11 @@ BOOL ads_pull_uint32(ADS_STRUCT *ads,
        char **values;
 
        values = ldap_get_values(ads->ld, msg, field);
-
-       if (!values || !values[0]) return False;
+       if (!values) return False;
+       if (!values[0]) {
+               ldap_value_free(values);
+               return False;
+       }
 
        *v = atoi(values[0]);
        ldap_value_free(values);
@@ -473,13 +468,15 @@ BOOL ads_pull_sid(ADS_STRUCT *ads,
                  void *msg, const char *field, DOM_SID *sid)
 {
        struct berval **values;
-       BOOL ret;
+       BOOL ret = False;
 
        values = ldap_get_values_len(ads->ld, msg, field);
 
-       if (!values || !values[0]) return False;
+       if (!values) return False;
 
-       ret = sid_parse(values[0]->bv_val, values[0]->bv_len, sid);
+       if (values[0]) {
+               ret = sid_parse(values[0]->bv_val, values[0]->bv_len, sid);
+       }
        
        ldap_value_free_len(values);
        return ret;
@@ -516,18 +513,137 @@ int ads_pull_sids(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx,
 
 
 /* find the update serial number - this is the core of the ldap cache */
-BOOL ads_USN(ADS_STRUCT *ads, uint32 *usn)
+ADS_STATUS ads_USN(ADS_STRUCT *ads, uint32 *usn)
 {
        const char *attrs[] = {"highestCommittedUSN", NULL};
-       int rc;
+       ADS_STATUS status;
        void *res;
 
-       rc = ldap_search_s(ads->ld, ads->bind_path, 
-                          LDAP_SCOPE_BASE, "(objectclass=*)", attrs, 0, (LDAPMessage **)&res);
-       if (rc || ads_count_replies(ads, res) != 1) return False;
-       return ads_pull_uint32(ads, res, "highestCommittedUSN", usn);
+       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