idmap_hash: remember new domain sids in idmap_hash_sid_to_id()
[metze/samba/wip.git] / source3 / winbindd / idmap_hash / idmap_hash.c
index f6c86524635192f7dbf59d8dff08e63debdf1153..23cf917c03025d568af7c5fed84ccd78a6fc37d9 100644 (file)
@@ -24,6 +24,8 @@
 #include "idmap_hash.h"
 #include "ads.h"
 #include "nss_info.h"
+#include "../libcli/security/dom_sid.h"
+#include "libsmb/samlogon_cache.h"
 
 #undef DBGC_CLASS
 #define DBGC_CLASS DBGC_IDMAP
@@ -32,8 +34,6 @@ struct sid_hash_table {
        struct dom_sid *sid;
 };
 
-struct sid_hash_table *hashed_domains = NULL;
-
 /*********************************************************************
  Hash a domain SID (S-1-5-12-aaa-bbb-ccc) to a 12bit number
  ********************************************************************/
@@ -61,13 +61,16 @@ static uint32_t hash_domain_sid(const struct dom_sid *sid)
 }
 
 /*********************************************************************
- Hash a Relative ID to a 20 bit number
+ Hash a Relative ID to a 19 bit number
  ********************************************************************/
 
 static uint32_t hash_rid(uint32_t rid)
 {
-       /* 20 bits for the rid which allows us to support
-          the first 100K users/groups in a domain */
+       /*
+        * 19 bits for the rid which allows us to support
+        * the first 50K users/groups in a domain
+        *
+        */
 
        return (rid & 0x0007FFFF);
 }
@@ -80,8 +83,13 @@ static uint32_t combine_hashes(uint32_t h_domain,
 {
        uint32_t return_id = 0;
 
-       /* shift the hash_domain 19 bits to the left and OR with the
-          hash_rid */
+       /*
+        * shift the hash_domain 19 bits to the left and OR with the
+        * hash_rid
+        *
+        * This will generate a 31 bit number out of
+        * 12 bit domain and 19 bit rid.
+        */
 
        return_id = ((h_domain<<19) | h_rid);
 
@@ -105,20 +113,23 @@ static void separate_hashes(uint32_t id,
 /*********************************************************************
  ********************************************************************/
 
-static NTSTATUS be_init(struct idmap_domain *dom,
-                       const char *params)
+static NTSTATUS idmap_hash_initialize(struct idmap_domain *dom)
 {
+       struct sid_hash_table *hashed_domains;
        NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
        struct winbindd_tdc_domain *dom_list = NULL;
        size_t num_domains = 0;
-       int i;
+       size_t i;
 
-       /* If the domain SID hash talbe has been initialized, assume
-          that we completed this function previously */
+       DBG_ERR("The idmap_hash module is deprecated and should not be used. "
+               "Please migrate to a different plugin. This module will be "
+               "removed in a future version of Samba\n");
 
-       if ( hashed_domains ) {
-               nt_status = NT_STATUS_OK;
-               goto done;
+       if (!strequal(dom->name, "*")) {
+               DBG_ERR("Error: idmap_hash configured for domain '%s'. "
+                       "But the hash module can only be used for the default "
+                       "idmap configuration.\n", dom->name);
+               return NT_STATUS_INVALID_PARAMETER;
        }
 
        if (!wcache_tdc_fetch_list(&dom_list, &num_domains)) {
@@ -128,28 +139,44 @@ static NTSTATUS be_init(struct idmap_domain *dom,
 
        /* Create the hash table of domain SIDs */
 
-       hashed_domains = TALLOC_ZERO_ARRAY(NULL, struct sid_hash_table, 4096);
+       hashed_domains = talloc_zero_array(dom, struct sid_hash_table, 4096);
        BAIL_ON_PTR_NT_ERROR(hashed_domains, nt_status);
 
        /* create the hash table of domain SIDs */
 
        for (i=0; i<num_domains; i++) {
+               struct dom_sid_buf buf;
                uint32_t hash;
 
                if (is_null_sid(&dom_list[i].sid))
                        continue;
+
+               /*
+                * Check if the domain from the list is not already configured
+                * to use another idmap backend. Not checking this makes the
+                * idmap_hash module map IDs for *all* domains implicitly.  This
+                * is quite dangerous in setups that use multiple idmap
+                * configurations.
+                */
+
+               if (domain_has_idmap_config(dom_list[i].domain_name)) {
+                       continue;
+               }
+
                if ((hash = hash_domain_sid(&dom_list[i].sid)) == 0)
                        continue;
 
-               DEBUG(5,("hash:be_init() Adding %s (%s) -> %d\n",
+               DBG_INFO("Adding %s (%s) -> %d\n",
                         dom_list[i].domain_name,
-                        sid_string_dbg(&dom_list[i].sid),
-                        hash));
+                        dom_sid_str_buf(&dom_list[i].sid, &buf),
+                        hash);
 
                hashed_domains[hash].sid = talloc(hashed_domains, struct dom_sid);
                sid_copy(hashed_domains[hash].sid, &dom_list[i].sid);
        }
 
+       dom->private_data = hashed_domains;
+
 done:
        return nt_status;
 }
@@ -157,159 +184,219 @@ done:
 /*********************************************************************
  ********************************************************************/
 
+static NTSTATUS idmap_hash_id_to_sid(struct sid_hash_table *hashed_domains,
+                                    struct idmap_domain *dom,
+                                    struct id_map *id)
+{
+       uint32_t h_domain, h_rid;
+
+       id->status = ID_UNMAPPED;
+
+       separate_hashes(id->xid.id, &h_domain, &h_rid);
+
+       /*
+        * If the domain hash doesn't find a SID in the table,
+        * skip it
+        */
+       if (hashed_domains[h_domain].sid == NULL) {
+               return NT_STATUS_NONE_MAPPED;
+       }
+
+       sid_compose(id->sid, hashed_domains[h_domain].sid, h_rid);
+       id->status = ID_MAPPED;
+
+       return NT_STATUS_OK;
+}
+
 static NTSTATUS unixids_to_sids(struct idmap_domain *dom,
                                struct id_map **ids)
 {
-       NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
+       struct sid_hash_table *hashed_domains = talloc_get_type_abort(
+               dom->private_data, struct sid_hash_table);
        int i;
+       int num_tomap = 0;
+       int num_mapped = 0;
 
-       /* initialize the status to avoid suprise */
+       /* initialize the status to avoid surprise */
        for (i = 0; ids[i]; i++) {
                ids[i]->status = ID_UNKNOWN;
-       }
-       
-       nt_status = be_init(dom, NULL);
-       BAIL_ON_NTSTATUS_ERROR(nt_status);
-
-       if (!ids) {
-               nt_status = NT_STATUS_INVALID_PARAMETER;
-               BAIL_ON_NTSTATUS_ERROR(nt_status);
+               num_tomap++;
        }
 
        for (i=0; ids[i]; i++) {
-               uint32_t h_domain, h_rid;
+               NTSTATUS ret;
 
-               ids[i]->status = ID_UNMAPPED;
+               ret = idmap_hash_id_to_sid(hashed_domains, dom, ids[i]);
 
-               separate_hashes(ids[i]->xid.id, &h_domain, &h_rid);
-
-               /* Make sure the caller allocated memor for us */
-
-               if (!ids[i]->sid) {
-                       nt_status = NT_STATUS_INVALID_PARAMETER;
-                       BAIL_ON_NTSTATUS_ERROR(nt_status);
+               if ((!NT_STATUS_IS_OK(ret)) &&
+                   (!NT_STATUS_EQUAL(ret, NT_STATUS_NONE_MAPPED))) {
+                       /* some fatal error occurred, log it */
+                       DBG_NOTICE("Unexpected error resolving an ID "
+                                  "(%d): %s\n", ids[i]->xid.id,
+                                  nt_errstr(ret));
+                       return ret;
                }
 
-               /* If the domain hash doesn't find a SID in the table,
-                  skip it */
-
-               if (!hashed_domains[h_domain].sid)
-                       continue;
+               if (NT_STATUS_IS_OK(ret) && ids[i]->status == ID_MAPPED) {
+                       num_mapped++;
+               }
+       }
 
-               sid_compose(ids[i]->sid, hashed_domains[h_domain].sid, h_rid);
-               ids[i]->status = ID_MAPPED;
+       if (num_tomap == num_mapped) {
+               return NT_STATUS_OK;
+       } else if (num_mapped == 0) {
+               return NT_STATUS_NONE_MAPPED;
        }
 
-done:
-       return nt_status;
+       return STATUS_SOME_UNMAPPED;
 }
 
 /*********************************************************************
  ********************************************************************/
 
-static NTSTATUS sids_to_unixids(struct idmap_domain *dom,
-                               struct id_map **ids)
+static NTSTATUS idmap_hash_sid_to_id(struct sid_hash_table *hashed_domains,
+                                    struct idmap_domain *dom,
+                                    struct id_map *id)
 {
-       NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
-       int i;
+       struct dom_sid sid;
+       uint32_t rid;
+       uint32_t h_domain, h_rid;
 
-       /* initialize the status to avoid suprise */
-       for (i = 0; ids[i]; i++) {
-               ids[i]->status = ID_UNKNOWN;
-       }
-       
-       nt_status = be_init(dom, NULL);
-       BAIL_ON_NTSTATUS_ERROR(nt_status);
+       id->status = ID_UNMAPPED;
 
-       if (!ids) {
-               nt_status = NT_STATUS_INVALID_PARAMETER;
-               BAIL_ON_NTSTATUS_ERROR(nt_status);
-       }
-
-       for (i=0; ids[i]; i++) {
-               struct dom_sid sid;
-               uint32_t rid;
-               uint32_t h_domain, h_rid;
+       sid_copy(&sid, id->sid);
+       sid_split_rid(&sid, &rid);
 
-               ids[i]->status = ID_UNMAPPED;
+       h_domain = hash_domain_sid(&sid);
+       h_rid = hash_rid(rid);
 
-               sid_copy(&sid, ids[i]->sid);
-               sid_split_rid(&sid, &rid);
-
-               h_domain = hash_domain_sid(&sid);
-               h_rid = hash_rid(rid);
+       /* Check that both hashes are non-zero*/
+       if (h_domain == 0) {
+               return NT_STATUS_NONE_MAPPED;
+       }
+       if (h_rid == 0) {
+               return NT_STATUS_NONE_MAPPED;
+       }
 
-               /* Check that both hashes are non-zero*/
+       /*
+        * If the domain hash already exists find a SID in the table,
+        * just return the mapping.
+        */
+       if (hashed_domains[h_domain].sid != NULL) {
+               goto return_mapping;
+       }
 
-               if (h_domain && h_rid) {
-                       ids[i]->xid.id = combine_hashes(h_domain, h_rid);
-                       ids[i]->status = ID_MAPPED;
-               }
+       /*
+        * If the caller already did a lookup sid and made sure the
+        * domain sid is valid, we can allocate a new range.
+        *
+        * Currently the winbindd parent already does a lookup sids
+        * first, but hopefully changes in future. If the
+        * caller knows the domain sid, ID_TYPE_BOTH should be
+        * passed instead of ID_TYPE_NOT_SPECIFIED.
+        */
+       if (id->xid.type == ID_TYPE_NOT_SPECIFIED) {
+               /*
+                * We keep the legacy behavior and
+                * just return the mapping, but
+                * the reverse mapping would not
+                * still not work.
+                *
+                * Maybe we should return NT_STATUS_NONE_MAPPED,
+                * but for now we keep what we already have.
+                */
+               goto return_mapping;
        }
 
-done:
-       return nt_status;
-}
+       /*
+        * Check of last resort: A domain is valid if a user from that
+        * domain has recently logged in. The samlogon_cache these
+        * days also stores the domain sid.
+        *
+        * We used to check the list of trusted domains we received
+        * from "our" dc, but this is not reliable enough.
+        */
+       if (!netsamlogon_cache_have(&sid)) {
+               /*
+                * We keep the legacy behavior and
+                * just return the mapping, but
+                * the reverse mapping would not
+                * still not work.
+                *
+                * Maybe we should return NT_STATUS_NONE_MAPPED,
+                * but for now we keep what we already have.
+                */
+               goto return_mapping;
+       }
 
-/*********************************************************************
- ********************************************************************/
+       /*
+        * Now we're sure the domain exist, remember
+        * the domain in order to return reverse mappings
+        * in future.
+        */
+       hashed_domains[h_domain].sid = dom_sid_dup(hashed_domains, &sid);
+       if (hashed_domains[h_domain].sid == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
 
-static NTSTATUS be_close(struct idmap_domain *dom)
-{
-       if (hashed_domains)
-               talloc_free(hashed_domains);
+return_mapping:
+       id->xid.id = combine_hashes(h_domain, h_rid);
+       id->status = ID_MAPPED;
 
        return NT_STATUS_OK;
 }
 
-/*********************************************************************
- ********************************************************************/
-
-static NTSTATUS nss_hash_init(struct nss_domain_entry *e )
-{
-       return be_init(NULL, NULL);
-}
-
-/**********************************************************************
- *********************************************************************/
-
-static NTSTATUS nss_hash_get_info(struct nss_domain_entry *e,
-                                   const struct dom_sid *sid,
-                                   TALLOC_CTX *ctx,
-                                   ADS_STRUCT *ads,
-                                   LDAPMessage *msg,
-                                   const char **homedir,
-                                   const char **shell,
-                                   const char **gecos,
-                                   gid_t *p_gid )
+static NTSTATUS sids_to_unixids(struct idmap_domain *dom,
+                               struct id_map **ids)
 {
-       NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
-
-       nt_status = nss_hash_init(e);
-       BAIL_ON_NTSTATUS_ERROR(nt_status);
+       struct sid_hash_table *hashed_domains = talloc_get_type_abort(
+               dom->private_data, struct sid_hash_table);
+       int i;
+       int num_tomap = 0;
+       int num_mapped = 0;
 
-       if (!homedir || !shell || !gecos) {
-               nt_status = NT_STATUS_INVALID_PARAMETER;
-               BAIL_ON_NTSTATUS_ERROR(nt_status);
+       /* initialize the status to avoid surprise */
+       for (i = 0; ids[i]; i++) {
+               ids[i]->status = ID_UNKNOWN;
+               num_tomap++;
        }
 
-       *homedir = talloc_strdup(ctx, lp_template_homedir());
-       BAIL_ON_PTR_NT_ERROR(*homedir, nt_status);
+       for (i=0; ids[i]; i++) {
+               NTSTATUS ret;
+
+               ret = idmap_hash_sid_to_id(hashed_domains, dom, ids[i]);
+
+               if ((!NT_STATUS_IS_OK(ret)) &&
+                   (!NT_STATUS_EQUAL(ret, NT_STATUS_NONE_MAPPED))) {
+                       struct dom_sid_buf buf;
+                       /* some fatal error occurred, log it */
+                       DBG_NOTICE("Unexpected error resolving a SID "
+                                  "(%s): %s\n",
+                                  dom_sid_str_buf(ids[i]->sid, &buf),
+                                  nt_errstr(ret));
+                       return ret;
+               }
 
-       *shell   = talloc_strdup(ctx, lp_template_shell());
-       BAIL_ON_PTR_NT_ERROR(*shell, nt_status);
+               if (NT_STATUS_IS_OK(ret) && ids[i]->status == ID_MAPPED) {
+                       num_mapped++;
+               }
+       }
 
-       *gecos   = NULL;
+       if (num_tomap == num_mapped) {
+               return NT_STATUS_OK;
+       } else if (num_mapped == 0) {
+               return NT_STATUS_NONE_MAPPED;
+       }
 
-       /* Initialize the gid so that the upper layer fills
-          in the proper Windows primary group */
+       return STATUS_SOME_UNMAPPED;
+}
 
-       if (*p_gid) {
-               *p_gid = (gid_t)-1;
-       }
+/*********************************************************************
+ ********************************************************************/
 
-done:
-       return nt_status;
+static NTSTATUS nss_hash_init(struct nss_domain_entry *e )
+{
+       return NT_STATUS_OK;
 }
 
 /**********************************************************************
@@ -356,16 +443,14 @@ static NTSTATUS nss_hash_close(void)
  Dispatch Tables for IDMap and NssInfo Methods
 ********************************************************************/
 
-static struct idmap_methods hash_idmap_methods = {
-       .init            = be_init,
+static const struct idmap_methods hash_idmap_methods = {
+       .init            = idmap_hash_initialize,
        .unixids_to_sids = unixids_to_sids,
        .sids_to_unixids = sids_to_unixids,
-       .close_fn        = be_close
 };
 
-static struct nss_info_methods hash_nss_methods = {
+static const struct nss_info_methods hash_nss_methods = {
        .init           = nss_hash_init,
-       .get_nss_info   = nss_hash_get_info,
        .map_to_alias   = nss_hash_map_to_alias,
        .map_from_alias = nss_hash_map_from_alias,
        .close_fn       = nss_hash_close
@@ -377,7 +462,8 @@ static struct nss_info_methods hash_nss_methods = {
  state.
  **********************************************************************/
 
-NTSTATUS idmap_hash_init(void)
+static_decl_idmap;
+NTSTATUS idmap_hash_init(TALLOC_CTX *ctx)
 {
        static NTSTATUS idmap_status = NT_STATUS_UNSUCCESSFUL;
        static NTSTATUS nss_status = NT_STATUS_UNSUCCESSFUL;