ldb: Build lmdb backend also in non-AD case
[samba.git] / source3 / passdb / lookup_sid.c
index 3cc64de36748319e6d4c8520990722c4a2f0f9af..7a29bb1abb5becf30dca26818e0abf94d9a76d37 100644 (file)
@@ -1,4 +1,4 @@
-/* 
+/*
    Unix SMB/CIFS implementation.
    uid/user handling
    Copyright (C) Andrew Tridgell         1992-1998
@@ -21,6 +21,7 @@
 
 #include "includes.h"
 #include "passdb.h"
+#include "lib/util_unixsids.h"
 #include "../librpc/gen_ndr/ndr_security.h"
 #include "secrets.h"
 #include "../lib/util/memcache.h"
 #include "../libcli/security/security.h"
 #include "lib/winbind_util.h"
 #include "../librpc/gen_ndr/idmap.h"
+#include "lib/util/bitmap.h"
+
+static bool lookup_unix_user_name(const char *name, struct dom_sid *sid)
+{
+       struct passwd *pwd;
+       bool ret;
+
+       pwd = Get_Pwnam_alloc(talloc_tos(), name);
+       if (pwd == NULL) {
+               return False;
+       }
+
+       /*
+        * For 64-bit uid's we have enough space in the whole SID,
+        * should they become necessary
+        */
+       ret = sid_compose(sid, &global_sid_Unix_Users, pwd->pw_uid);
+       TALLOC_FREE(pwd);
+       return ret;
+}
+
+static bool lookup_unix_group_name(const char *name, struct dom_sid *sid)
+{
+       struct group *grp;
+
+       grp = getgrnam(name);
+       if (grp == NULL) {
+               return False;
+       }
+
+       /*
+        * For 64-bit gid's we have enough space in the whole SID,
+        * should they become necessary
+        */
+       return sid_compose(sid, &global_sid_Unix_Groups, grp->gr_gid);
+}
 
 /*****************************************************************
  Dissect a user-provided name into domain, name, sid and type.
  If an explicit domain name was given in the form domain\user, it
  has to try that. If no explicit domain name was given, we have
  to do guesswork.
-*****************************************************************/  
-
-bool lookup_name(TALLOC_CTX *mem_ctx,
-                const char *full_name, int flags,
-                const char **ret_domain, const char **ret_name,
-                struct dom_sid *ret_sid, enum lsa_SidType *ret_type)
+*****************************************************************/
+
+static NTSTATUS lookup_name_internal(TALLOC_CTX *mem_ctx,
+                                    const char *full_name,
+                                    int flags,
+                                    const char **ret_domain,
+                                    const char **ret_name,
+                                    struct dom_sid *ret_sid,
+                                    enum lsa_SidType *ret_type)
 {
        char *p;
        const char *tmp;
@@ -50,10 +90,11 @@ bool lookup_name(TALLOC_CTX *mem_ctx,
        struct dom_sid sid;
        enum lsa_SidType type;
        TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+       NTSTATUS status;
 
        if (tmp_ctx == NULL) {
                DEBUG(0, ("talloc_new failed\n"));
-               return false;
+               return NT_STATUS_NO_MEMORY;
        }
 
        p = strchr_m(full_name, '\\');
@@ -63,31 +104,61 @@ bool lookup_name(TALLOC_CTX *mem_ctx,
                                        PTR_DIFF(p, full_name));
                name = talloc_strdup(tmp_ctx, p+1);
        } else {
-               domain = talloc_strdup(tmp_ctx, "");
-               name = talloc_strdup(tmp_ctx, full_name);
+               char *q = strchr_m(full_name, '@');
+
+               /* Set the domain for UPNs */
+               if (q != NULL) {
+                       name = talloc_strndup(tmp_ctx,
+                                             full_name,
+                                             PTR_DIFF(q, full_name));
+                       domain = talloc_strdup(tmp_ctx, q + 1);
+               } else {
+                       domain = talloc_strdup(tmp_ctx, "");
+                       name = talloc_strdup(tmp_ctx, full_name);
+               }
        }
 
        if ((domain == NULL) || (name == NULL)) {
                DEBUG(0, ("talloc failed\n"));
                TALLOC_FREE(tmp_ctx);
-               return false;
+               return NT_STATUS_NO_MEMORY;
        }
 
        DEBUG(10,("lookup_name: %s => domain=[%s], name=[%s]\n",
                full_name, domain, name));
        DEBUG(10, ("lookup_name: flags = 0x0%x\n", flags));
 
-       if ((flags & LOOKUP_NAME_DOMAIN) &&
-           strequal(domain, get_global_sam_name()))
-       {
+       if ((flags & LOOKUP_NAME_DOMAIN) || (flags == 0)) {
+               bool check_global_sam = false;
 
-               /* It's our own domain, lookup the name in passdb */
-               if (lookup_global_sam_name(name, flags, &rid, &type)) {
-                       sid_compose(&sid, get_global_sam_sid(), rid);
-                       goto ok;
+               check_global_sam = strequal(domain, get_global_sam_name());
+
+               /* If we are running on a DC that has PASSDB module with domain
+                * information, check if DNS forest name is matching the domain
+                * name. This is the case of IPA domain controller when
+                * trusted AD DC looks up users found in a Global Catalog of
+                * the forest root domain. */
+               if (!check_global_sam && (IS_DC)) {
+                       struct pdb_domain_info *dom_info = NULL;
+                       dom_info = pdb_get_domain_info(tmp_ctx);
+
+                       if ((dom_info != NULL) && (dom_info->dns_forest != NULL)) {
+                               check_global_sam = strequal(domain, dom_info->dns_forest);
+                       }
+
+                       TALLOC_FREE(dom_info);
+               }
+
+               if (check_global_sam) {
+                       /* It's our own domain, lookup the name in passdb */
+                       if (lookup_global_sam_name(name, flags, &rid, &type)) {
+                               sid_compose(&sid, get_global_sam_sid(), rid);
+                               goto ok;
+                       }
+                       TALLOC_FREE(tmp_ctx);
+                       *ret_type = SID_NAME_UNKNOWN;
+                       return NT_STATUS_OK;
                }
-               TALLOC_FREE(tmp_ctx);
-               return false;
        }
 
        if ((flags & LOOKUP_NAME_BUILTIN) &&
@@ -108,26 +179,34 @@ bool lookup_name(TALLOC_CTX *mem_ctx,
                        goto ok;
                }
                TALLOC_FREE(tmp_ctx);
-               return false;
+               *ret_type = SID_NAME_UNKNOWN;
+               return NT_STATUS_OK;
        }
 
        /* Try the explicit winbind lookup first, don't let it guess the
         * domain yet at this point yet. This comes later. */
 
        if ((domain[0] != '\0') &&
-           (flags & ~(LOOKUP_NAME_DOMAIN|LOOKUP_NAME_ISOLATED)) &&
-           (winbind_lookup_name(domain, name, &sid, &type))) {
+           (flags & ~(LOOKUP_NAME_DOMAIN|LOOKUP_NAME_ISOLATED)))
+       {
+               status = winbind_lookup_name_ex(domain, name, &sid, &type);
+               if (!NT_STATUS_IS_OK(status)) {
+                       return status;
+               }
+               if (type != SID_NAME_UNKNOWN) {
                        goto ok;
+               }
        }
 
-       if (((flags & LOOKUP_NAME_NO_NSS) == 0)
+       if (((flags & (LOOKUP_NAME_NO_NSS|LOOKUP_NAME_GROUP)) == 0)
            && strequal(domain, unix_users_domain_name())) {
                if (lookup_unix_user_name(name, &sid)) {
                        type = SID_NAME_USER;
                        goto ok;
                }
                TALLOC_FREE(tmp_ctx);
-               return false;
+               *ret_type = SID_NAME_UNKNOWN;
+               return NT_STATUS_OK;
        }
 
        if (((flags & LOOKUP_NAME_NO_NSS) == 0)
@@ -137,12 +216,39 @@ bool lookup_name(TALLOC_CTX *mem_ctx,
                        goto ok;
                }
                TALLOC_FREE(tmp_ctx);
-               return false;
+               *ret_type = SID_NAME_UNKNOWN;
+               return NT_STATUS_OK;
+       }
+
+       /*
+        * Finally check for a well known domain name ("NT Authority"),
+        * this is being taken care of in lookup_wellknown_name().
+        */
+       if ((domain[0] != '\0') &&
+           (flags & LOOKUP_NAME_WKN) &&
+           lookup_wellknown_name(tmp_ctx, name, &sid, &domain))
+       {
+               type = SID_NAME_WKN_GRP;
+               goto ok;
        }
 
-       if ((domain[0] == '\0') && (!(flags & LOOKUP_NAME_ISOLATED))) {
+       /*
+        * If we're told not to look up 'isolated' names then we're
+        * done.
+        */
+       if (!(flags & LOOKUP_NAME_ISOLATED)) {
                TALLOC_FREE(tmp_ctx);
-               return false;
+               *ret_type = SID_NAME_UNKNOWN;
+               return NT_STATUS_OK;
+       }
+
+       /*
+        * No domain names beyond this point
+        */
+       if (domain[0] != '\0') {
+               TALLOC_FREE(tmp_ctx);
+               *ret_type = SID_NAME_UNKNOWN;
+               return NT_STATUS_OK;
        }
 
        /* Now the guesswork begins, we haven't been given an explicit
@@ -152,6 +258,11 @@ bool lookup_name(TALLOC_CTX *mem_ctx,
 
        /* 1. well-known names */
 
+       /*
+        * Check for well known names without a domain name.
+        * e.g. \Creator Owner.
+        */
+
        if ((flags & LOOKUP_NAME_WKN) &&
            lookup_wellknown_name(tmp_ctx, name, &sid, &domain))
        {
@@ -179,7 +290,7 @@ bool lookup_name(TALLOC_CTX *mem_ctx,
                if (!secrets_fetch_domain_sid(name, &sid)) {
                        DEBUG(3, ("Could not fetch my SID\n"));
                        TALLOC_FREE(tmp_ctx);
-                       return false;
+                       return NT_STATUS_INTERNAL_DB_CORRUPTION;
                }
                /* Swap domain and name */
                tmp = name; name = domain; domain = tmp;
@@ -195,7 +306,7 @@ bool lookup_name(TALLOC_CTX *mem_ctx,
                if (!secrets_fetch_domain_sid(name, &sid)) {
                        DEBUG(3, ("Could not fetch the domain SID\n"));
                        TALLOC_FREE(tmp_ctx);
-                       return false;
+                       return NT_STATUS_INTERNAL_DB_CORRUPTION;
                }
                /* Swap domain and name */
                tmp = name; name = domain; domain = tmp;
@@ -215,7 +326,7 @@ bool lookup_name(TALLOC_CTX *mem_ctx,
                goto ok;
        }
 
-       /* 6. Builtin aliases */        
+       /* 6. Builtin aliases */
 
        if ((flags & LOOKUP_NAME_BUILTIN) &&
            lookup_builtin_name(name, &rid))
@@ -243,16 +354,21 @@ bool lookup_name(TALLOC_CTX *mem_ctx,
 
        if (!(flags & LOOKUP_NAME_REMOTE)) {
                TALLOC_FREE(tmp_ctx);
-               return false;
+               *ret_type = SID_NAME_UNKNOWN;
+               return NT_STATUS_OK;
        }
 
        /* If we are not a DC, we have to ask in our primary domain. Let
         * winbind do that. */
 
-       if (!IS_DC &&
-           (winbind_lookup_name(lp_workgroup(), name, &sid, &type))) {
-               domain = talloc_strdup(tmp_ctx, lp_workgroup());
-               goto ok;
+       if (!IS_DC) {
+               status = winbind_lookup_name_ex(lp_workgroup(), name, &sid, &type);
+               if (!NT_STATUS_IS_OK(status)) {
+                       return status;
+               }
+               if (type != SID_NAME_UNKNOWN) {
+                       goto ok;
+               }
        }
 
        /* 9. Trusted domains */
@@ -260,32 +376,43 @@ bool lookup_name(TALLOC_CTX *mem_ctx,
        /* If we're a DC we have to ask all trusted DC's. Winbind does not do
         * that (yet), but give it a chance. */
 
-       if (IS_DC && winbind_lookup_name("", name, &sid, &type)) {
+       if (IS_DC) {
                struct dom_sid dom_sid;
                enum lsa_SidType domain_type;
 
-               if (type == SID_NAME_DOMAIN) {
-                       /* Swap name and type */
-                       tmp = name; name = domain; domain = tmp;
-                       goto ok;
+               status = winbind_lookup_name_ex("", name, &sid, &type);
+               if (!NT_STATUS_IS_OK(status)) {
+                       return status;
                }
+               if (type != SID_NAME_UNKNOWN) {
+                       if (type == SID_NAME_DOMAIN) {
+                               /* Swap name and type */
+                               tmp = name; name = domain; domain = tmp;
+                               goto ok;
+                       }
 
-               /* Here we have to cope with a little deficiency in the
-                * winbind API: We have to ask it again for the name of the
-                * domain it figured out itself. Maybe fix that later... */
-
-               sid_copy(&dom_sid, &sid);
-               sid_split_rid(&dom_sid, NULL);
-
-               if (!winbind_lookup_sid(tmp_ctx, &dom_sid, &domain, NULL,
-                                       &domain_type) ||
-                   (domain_type != SID_NAME_DOMAIN)) {
-                       DEBUG(2, ("winbind could not find the domain's name "
-                                 "it just looked up for us\n"));
-                       TALLOC_FREE(tmp_ctx);
-                       return false;
+                       /* Here we have to cope with a little deficiency
+                        * in the winbind API: We have to ask it again
+                        * for the name of the domain it figured out
+                        * itself. Maybe fix that later... */
+
+                       sid_copy(&dom_sid, &sid);
+                       sid_split_rid(&dom_sid, NULL);
+
+                       if (!winbind_lookup_sid(tmp_ctx, &dom_sid,
+                                               &domain, NULL,
+                                               &domain_type) ||
+                           (domain_type != SID_NAME_DOMAIN))
+                       {
+                               DBG_INFO("winbind could not find the "
+                                        "domain's name it just looked "
+                                        "up for us\n");
+                               TALLOC_FREE(tmp_ctx);
+                               *ret_type = SID_NAME_UNKNOWN;
+                               return NT_STATUS_OK;
+                       }
+                       goto ok;
                }
-               goto ok;
        }
 
        /* 10. Don't translate */
@@ -293,7 +420,7 @@ bool lookup_name(TALLOC_CTX *mem_ctx,
        /* 11. Ok, windows would end here. Samba has two more options:
                Unmapped users and unmapped groups */
 
-       if (((flags & LOOKUP_NAME_NO_NSS) == 0)
+       if (((flags & (LOOKUP_NAME_NO_NSS|LOOKUP_NAME_GROUP)) == 0)
            && lookup_unix_user_name(name, &sid)) {
                domain = talloc_strdup(tmp_ctx, unix_users_domain_name());
                type = SID_NAME_USER;
@@ -312,13 +439,14 @@ bool lookup_name(TALLOC_CTX *mem_ctx,
         */
 
        TALLOC_FREE(tmp_ctx);
-       return false;
+       *ret_type = SID_NAME_UNKNOWN;
+       return NT_STATUS_OK;
 
  ok:
        if ((domain == NULL) || (name == NULL)) {
                DEBUG(0, ("talloc failed\n"));
                TALLOC_FREE(tmp_ctx);
-               return false;
+               return NT_STATUS_NO_MEMORY;
        }
 
        /*
@@ -329,7 +457,7 @@ bool lookup_name(TALLOC_CTX *mem_ctx,
            !(*ret_name = talloc_strdup(mem_ctx, name))) {
                DEBUG(0, ("talloc failed\n"));
                TALLOC_FREE(tmp_ctx);
-               return false;
+               return NT_STATUS_NO_MEMORY;
        }
 
        if (ret_domain != NULL) {
@@ -337,11 +465,11 @@ bool lookup_name(TALLOC_CTX *mem_ctx,
                if (!(tmp_dom = talloc_strdup(mem_ctx, domain))) {
                        DEBUG(0, ("talloc failed\n"));
                        TALLOC_FREE(tmp_ctx);
-                       return false;
+                       return NT_STATUS_NO_MEMORY;
                }
                if (!strupper_m(tmp_dom)) {
                        TALLOC_FREE(tmp_ctx);
-                       return false;
+                       return NT_STATUS_INTERNAL_ERROR;
                }
                *ret_domain = tmp_dom;
        }
@@ -350,11 +478,39 @@ bool lookup_name(TALLOC_CTX *mem_ctx,
                sid_copy(ret_sid, &sid);
        }
 
+       *ret_type = type;
+
+       TALLOC_FREE(tmp_ctx);
+       return NT_STATUS_OK;
+}
+
+bool lookup_name(TALLOC_CTX *mem_ctx,
+                const char *full_name,
+                int flags,
+                const char **ret_domain,
+                const char **ret_name,
+                struct dom_sid *ret_sid,
+                enum lsa_SidType *ret_type)
+{
+       enum lsa_SidType type;
+       NTSTATUS status;
+
+       status = lookup_name_internal(mem_ctx,
+                                     full_name,
+                                     flags,
+                                     ret_domain,
+                                     ret_name,
+                                     ret_sid,
+                                     &type);
+       if (!NT_STATUS_IS_OK(status)) {
+               return false;
+       }
        if (ret_type != NULL) {
                *ret_type = type;
        }
-
-       TALLOC_FREE(tmp_ctx);
+       if (type == SID_NAME_UNKNOWN) {
+               return false;
+       }
        return true;
 }
 
@@ -364,58 +520,65 @@ bool lookup_name(TALLOC_CTX *mem_ctx,
  and then "Unix Users"\foo (or "Unix Groups"\foo).
 ************************************************************************/
 
-bool lookup_name_smbconf(TALLOC_CTX *mem_ctx,
-                const char *full_name, int flags,
-                const char **ret_domain, const char **ret_name,
-                struct dom_sid *ret_sid, enum lsa_SidType *ret_type)
+NTSTATUS lookup_name_smbconf_ex(TALLOC_CTX *mem_ctx,
+                               const char *full_name,
+                               int flags,
+                               const char **ret_domain,
+                               const char **ret_name,
+                               struct dom_sid *ret_sid,
+                               enum lsa_SidType *ret_type)
 {
-       char *qualified_name;
-       const char *p;
+       char *qualified_name = NULL;
+       const char *p = strchr_m(full_name, *lp_winbind_separator());
+       bool is_qualified = p != NULL || strchr_m(full_name, '@') != NULL;
+       NTSTATUS status;
 
-       /* NB. No winbindd_separator here as lookup_name needs \\' */
-       if ((p = strchr_m(full_name, *lp_winbind_separator())) != NULL) {
+       /* For DOMAIN\user or user@REALM directly call lookup_name(). */
+       if (is_qualified) {
 
                /* The name is already qualified with a domain. */
 
-               if (*lp_winbind_separator() != '\\') {
-                       char *tmp;
-
+               if (p != NULL && *lp_winbind_separator() != '\\') {
                        /* lookup_name() needs '\\' as a separator */
 
-                       tmp = talloc_strdup(mem_ctx, full_name);
-                       if (!tmp) {
-                               return false;
+                       qualified_name = talloc_strdup(mem_ctx, full_name);
+                       if (qualified_name == NULL) {
+                               return NT_STATUS_NO_MEMORY;
                        }
-                       tmp[p - full_name] = '\\';
-                       full_name = tmp;
+                       qualified_name[p - full_name] = '\\';
+                       full_name = qualified_name;
                }
 
-               return lookup_name(mem_ctx, full_name, flags,
-                               ret_domain, ret_name,
-                               ret_sid, ret_type);
+               return lookup_name_internal(mem_ctx,
+                                           full_name,
+                                           flags,
+                                           ret_domain,
+                                           ret_name,
+                                           ret_sid,
+                                           ret_type);
        }
 
        /* Try with winbind default domain name. */
        if (lp_winbind_use_default_domain()) {
-               bool ok;
-
                qualified_name = talloc_asprintf(mem_ctx,
                                                 "%s\\%s",
                                                 lp_workgroup(),
                                                 full_name);
                if (qualified_name == NULL) {
-                       return false;
+                       return NT_STATUS_NO_MEMORY;
                }
 
-               ok = lookup_name(mem_ctx,
-                                qualified_name,
-                                flags,
-                                ret_domain,
-                                ret_name,
-                                ret_sid,
-                                ret_type);
-               if (ok) {
-                       return true;
+               status= lookup_name_internal(mem_ctx,
+                                            qualified_name,
+                                            flags,
+                                            ret_domain,
+                                            ret_name,
+                                            ret_sid,
+                                            ret_type);
+               if (NT_STATUS_IS_OK(status) &&
+                   *ret_type != SID_NAME_UNKNOWN)
+               {
+                       return NT_STATUS_OK;
                }
        }
 
@@ -424,13 +587,20 @@ bool lookup_name_smbconf(TALLOC_CTX *mem_ctx,
                                get_global_sam_name(),
                                full_name );
        if (!qualified_name) {
-               return false;
+               return NT_STATUS_NO_MEMORY;
        }
 
-       if (lookup_name(mem_ctx, qualified_name, flags,
-                               ret_domain, ret_name,
-                               ret_sid, ret_type)) {
-               return true;
+       status = lookup_name_internal(mem_ctx,
+                                     qualified_name,
+                                     flags,
+                                     ret_domain,
+                                     ret_name,
+                                     ret_sid,
+                                     ret_type);
+       if (NT_STATUS_IS_OK(status) &&
+           *ret_type != SID_NAME_UNKNOWN)
+       {
+               return NT_STATUS_OK;
        }
 
        /* Finally try with "Unix Users" or "Unix Group" */
@@ -440,12 +610,43 @@ bool lookup_name_smbconf(TALLOC_CTX *mem_ctx,
                                        unix_users_domain_name(),
                                full_name );
        if (!qualified_name) {
-               return false;
+               return NT_STATUS_NO_MEMORY;
        }
 
-       return lookup_name(mem_ctx, qualified_name, flags,
-                               ret_domain, ret_name,
-                               ret_sid, ret_type);
+       return lookup_name_internal(mem_ctx,
+                                   qualified_name,
+                                   flags,
+                                   ret_domain,
+                                   ret_name,
+                                   ret_sid,
+                                   ret_type);
+}
+
+bool lookup_name_smbconf(TALLOC_CTX *mem_ctx,
+                const char *full_name, int flags,
+                const char **ret_domain, const char **ret_name,
+                struct dom_sid *ret_sid, enum lsa_SidType *ret_type)
+{
+       enum lsa_SidType type;
+       NTSTATUS status;
+
+       status = lookup_name_smbconf_ex(mem_ctx,
+                                       full_name,
+                                       flags,
+                                       ret_domain,
+                                       ret_name,
+                                       ret_sid,
+                                       &type);
+       if (!NT_STATUS_IS_OK(status)) {
+               return false;
+       }
+       if (ret_type != NULL) {
+               *ret_type = type;
+       }
+       if (type == SID_NAME_UNKNOWN) {
+               return false;
+       }
+       return true;
 }
 
 static bool wb_lookup_rids(TALLOC_CTX *mem_ctx,
@@ -505,9 +706,10 @@ static bool lookup_rids(TALLOC_CTX *mem_ctx, const struct dom_sid *domain_sid,
                        const char ***names, enum lsa_SidType **types)
 {
        int i;
+       struct dom_sid_buf buf;
 
        DEBUG(10, ("lookup_rids called for domain sid '%s'\n",
-                  sid_string_dbg(domain_sid)));
+                  dom_sid_str_buf(domain_sid, &buf)));
 
        if (num_rids) {
                *names = talloc_zero_array(mem_ctx, const char *, num_rids);
@@ -724,6 +926,7 @@ static bool lookup_as_domain(const struct dom_sid *sid, TALLOC_CTX *mem_ctx,
 
 static bool check_dom_sid_to_level(const struct dom_sid *sid, int level)
 {
+       struct dom_sid_buf buf;
        int ret = false;
 
        switch(level) {
@@ -746,7 +949,8 @@ static bool check_dom_sid_to_level(const struct dom_sid *sid, int level)
 
        DEBUG(10, ("%s SID %s in level %d\n",
                   ret ? "Accepting" : "Rejecting",
-                  sid_string_dbg(sid), level));
+                  dom_sid_str_buf(sid, &buf),
+                  level));
        return ret;
 }
 
@@ -766,7 +970,7 @@ NTSTATUS lookup_sids(TALLOC_CTX *mem_ctx, int num_sids,
                     struct lsa_name_info **ret_names)
 {
        TALLOC_CTX *tmp_ctx;
-       NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+       NTSTATUS result;
        struct lsa_name_info *name_infos;
        struct lsa_dom_info *dom_infos = NULL;
 
@@ -795,7 +999,7 @@ NTSTATUS lookup_sids(TALLOC_CTX *mem_ctx, int num_sids,
        }
 
        /* First build up the data structures:
-        * 
+        *
         * dom_infos is a list of domains referenced in the list of
         * SIDs. Later we will walk the list of domains and look up the RIDs
         * in bulk.
@@ -983,7 +1187,7 @@ NTSTATUS lookup_sids(TALLOC_CTX *mem_ctx, int num_sids,
 
 /*****************************************************************
  *THE CANONICAL* convert SID to name function.
-*****************************************************************/  
+*****************************************************************/
 
 bool lookup_sid(TALLOC_CTX *mem_ctx, const struct dom_sid *sid,
                const char **ret_domain, const char **ret_name,
@@ -991,10 +1195,12 @@ bool lookup_sid(TALLOC_CTX *mem_ctx, const struct dom_sid *sid,
 {
        struct lsa_dom_info *domain;
        struct lsa_name_info *name;
+       struct dom_sid_buf buf;
        TALLOC_CTX *tmp_ctx;
        bool ret = false;
 
-       DEBUG(10, ("lookup_sid called for SID '%s'\n", sid_string_dbg(sid)));
+       DEBUG(10, ("lookup_sid called for SID '%s'\n",
+                  dom_sid_str_buf(sid, &buf)));
 
        if (!(tmp_ctx = talloc_new(mem_ctx))) {
                DEBUG(0, ("talloc_new failed\n"));
@@ -1015,7 +1221,7 @@ bool lookup_sid(TALLOC_CTX *mem_ctx, const struct dom_sid *sid,
                goto done;
        }
 
-       if ((ret_name != NULL) && 
+       if ((ret_name != NULL) &&
            !(*ret_name = talloc_strdup(mem_ctx, name->name))) {
                goto done;
        }
@@ -1028,109 +1234,20 @@ bool lookup_sid(TALLOC_CTX *mem_ctx, const struct dom_sid *sid,
 
  done:
        if (ret) {
-               DEBUG(10, ("Sid %s -> %s\\%s(%d)\n", sid_string_dbg(sid),
+               DEBUG(10, ("Sid %s -> %s\\%s(%d)\n",
+                          dom_sid_str_buf(sid, &buf),
                           domain->name, name->name, name->type));
        } else {
-               DEBUG(10, ("failed to lookup sid %s\n", sid_string_dbg(sid)));
+               DEBUG(10, ("failed to lookup sid %s\n",
+                          dom_sid_str_buf(sid, &buf)));
        }
        TALLOC_FREE(tmp_ctx);
        return ret;
 }
 
-/*****************************************************************
- Id mapping cache.  This is to avoid Winbind mappings already
- seen by smbd to be queried too frequently, keeping winbindd
- busy, and blocking smbd while winbindd is busy with other
- stuff. Written by Michael Steffens <michael.steffens@hp.com>,
- modified to use linked lists by jra.
-*****************************************************************/  
-
-
-/*****************************************************************
- *THE LEGACY* convert uid_t to SID function.
-*****************************************************************/  
-
-static void legacy_uid_to_sid(struct dom_sid *psid, uid_t uid)
-{
-       bool ret;
-       struct unixid id;
-
-       ZERO_STRUCTP(psid);
-
-       id.id = uid;
-       id.type = ID_TYPE_UID;
-
-       become_root();
-       ret = pdb_id_to_sid(&id, psid);
-       unbecome_root();
-
-       if (ret) {
-               /* This is a mapped user */
-               goto done;
-       }
-
-       /* This is an unmapped user */
-
-       uid_to_unix_users_sid(uid, psid);
-
-       {
-               struct unixid xid = {
-                       .id = uid, .type = ID_TYPE_UID
-               };
-               idmap_cache_set_sid2unixid(psid, &xid);
-       }
-
- done:
-       DEBUG(10,("LEGACY: uid %u -> sid %s\n", (unsigned int)uid,
-                 sid_string_dbg(psid)));
-
-       return;
-}
-
-/*****************************************************************
- *THE LEGACY* convert gid_t to SID function.
-*****************************************************************/  
-
-static void legacy_gid_to_sid(struct dom_sid *psid, gid_t gid)
-{
-       bool ret;
-       struct unixid id;
-
-       ZERO_STRUCTP(psid);
-
-       id.id = gid;
-       id.type = ID_TYPE_GID;
-
-       become_root();
-       ret = pdb_id_to_sid(&id, psid);
-       unbecome_root();
-
-       if (ret) {
-               /* This is a mapped group */
-               goto done;
-       }
-
-       /* This is an unmapped group */
-
-       gid_to_unix_groups_sid(gid, psid);
-
-       {
-               struct unixid xid = {
-                       .id = gid, .type = ID_TYPE_GID
-               };
-               idmap_cache_set_sid2unixid(psid, &xid);
-       }
-
- done:
-       DEBUG(10,("LEGACY: gid %u -> sid %s\n", (unsigned int)gid,
-                 sid_string_dbg(psid)));
-
-       return;
-}
-
 /*****************************************************************
  *THE LEGACY* convert SID to id function.
-*****************************************************************/  
+*****************************************************************/
 
 static bool legacy_sid_to_unixid(const struct dom_sid *psid, struct unixid *id)
 {
@@ -1141,8 +1258,9 @@ static bool legacy_sid_to_unixid(const struct dom_sid *psid, struct unixid *id)
        unbecome_root();
 
        if (!ret) {
+               struct dom_sid_buf buf;
                DEBUG(10,("LEGACY: mapping failed for sid %s\n",
-                         sid_string_dbg(psid)));
+                         dom_sid_str_buf(psid, &buf)));
                return false;
        }
 
@@ -1175,102 +1293,90 @@ static bool legacy_sid_to_uid(const struct dom_sid *psid, uid_t *puid)
        return false;
 }
 
-/*****************************************************************
- *THE CANONICAL* convert uid_t to SID function.
-*****************************************************************/  
-
-void uid_to_sid(struct dom_sid *psid, uid_t uid)
+void xid_to_sid(struct dom_sid *psid, const struct unixid *xid)
 {
        bool expired = true;
        bool ret;
-       ZERO_STRUCTP(psid);
+       struct dom_sid_buf buf;
 
-       /* Check the winbindd cache directly. */
-       ret = idmap_cache_find_uid2sid(uid, psid, &expired);
+       SMB_ASSERT(xid->type == ID_TYPE_UID || xid->type == ID_TYPE_GID);
+
+       *psid = (struct dom_sid) {0};
+
+       ret = idmap_cache_find_xid2sid(xid, psid, &expired);
+       if (ret && !expired) {
+               DBG_DEBUG("%cID %"PRIu32" -> %s from cache\n",
+                         xid->type == ID_TYPE_UID ? 'U' : 'G',
+                         xid->id,
+                         dom_sid_str_buf(psid, &buf));
+               goto done;
+       }
 
-       if (ret && !expired && is_null_sid(psid)) {
+       ret = winbind_xid_to_sid(psid, xid);
+       if (ret) {
                /*
-                * Negative cache entry, we already asked.
-                * do legacy.
+                * winbind can return an explicit negative mapping
+                * here. It's up to winbind to prime the cache either
+                * positively or negatively, don't mess with the cache
+                * here.
                 */
-               legacy_uid_to_sid(psid, uid);
-               return;
+               DBG_DEBUG("%cID %"PRIu32" -> %s from cache\n",
+                         xid->type == ID_TYPE_UID ? 'U' : 'G',
+                         xid->id,
+                         dom_sid_str_buf(psid, &buf));
+               goto done;
        }
 
-       if (!ret || expired) {
-               /* Not in cache. Ask winbindd. */
-               if (!winbind_uid_to_sid(psid, uid)) {
-                       /*
-                        * We shouldn't return the NULL SID
-                        * here if winbind was running and
-                        * couldn't map, as winbind will have
-                        * added a negative entry that will
-                        * cause us to go though the
-                        * legacy_uid_to_sid()
-                        * function anyway in the case above
-                        * the next time we ask.
-                        */
-                       DEBUG(5, ("uid_to_sid: winbind failed to find a sid "
-                                 "for uid %u\n", (unsigned int)uid));
+       {
+               /*
+                * Make a copy, pdb_id_to_sid might want to turn
+                * xid->type into ID_TYPE_BOTH, which we ignore here.
+                */
+               struct unixid rw_xid = *xid;
 
-                       legacy_uid_to_sid(psid, uid);
-                       return;
-               }
+               become_root();
+               ret = pdb_id_to_sid(&rw_xid, psid);
+               unbecome_root();
        }
 
-       DEBUG(10,("uid %u -> sid %s\n", (unsigned int)uid,
-                 sid_string_dbg(psid)));
-
-       return;
-}
-
-/*****************************************************************
- *THE CANONICAL* convert gid_t to SID function.
-*****************************************************************/  
-
-void gid_to_sid(struct dom_sid *psid, gid_t gid)
-{
-       bool expired = true;
-       bool ret;
-       ZERO_STRUCTP(psid);
-
-       /* Check the winbindd cache directly. */
-       ret = idmap_cache_find_gid2sid(gid, psid, &expired);
+       if (ret) {
+               DBG_DEBUG("%cID %"PRIu32" -> %s from passdb\n",
+                         xid->type == ID_TYPE_UID ? 'U' : 'G',
+                         xid->id,
+                         dom_sid_str_buf(psid, &buf));
+               goto done;
+       }
 
-       if (ret && !expired && is_null_sid(psid)) {
+done:
+       if (is_null_sid(psid)) {
                /*
-                * Negative cache entry, we already asked.
-                * do legacy.
+                * Nobody found anything: Return S-1-22-xx-yy. Don't
+                * store that in caches, this is up to the layers
+                * beneath us.
                 */
-               legacy_gid_to_sid(psid, gid);
-               return;
-       }
-
-       if (!ret || expired) {
-               /* Not in cache. Ask winbindd. */
-               if (!winbind_gid_to_sid(psid, gid)) {
-                       /*
-                        * We shouldn't return the NULL SID
-                        * here if winbind was running and
-                        * couldn't map, as winbind will have
-                        * added a negative entry that will
-                        * cause us to go though the
-                        * legacy_gid_to_sid()
-                        * function anyway in the case above
-                        * the next time we ask.
-                        */
-                       DEBUG(5, ("gid_to_sid: winbind failed to find a sid "
-                                 "for gid %u\n", (unsigned int)gid));
-
-                       legacy_gid_to_sid(psid, gid);
-                       return;
+               if (xid->type == ID_TYPE_UID) {
+                       uid_to_unix_users_sid(xid->id, psid);
+               } else {
+                       gid_to_unix_groups_sid(xid->id, psid);
                }
+
+               DBG_DEBUG("%cID %"PRIu32" -> %s fallback\n",
+                         xid->type == ID_TYPE_UID ? 'U' : 'G',
+                         xid->id,
+                         dom_sid_str_buf(psid, &buf));
        }
+}
 
-       DEBUG(10,("gid %u -> sid %s\n", (unsigned int)gid,
-                 sid_string_dbg(psid)));
+void uid_to_sid(struct dom_sid *psid, uid_t uid)
+{
+       struct unixid xid = { .type = ID_TYPE_UID, .id = uid};
+       xid_to_sid(psid, &xid);
+}
 
-       return;
+void gid_to_sid(struct dom_sid *psid, gid_t gid)
+{
+       struct unixid xid = { .type = ID_TYPE_GID, .id = gid};
+       xid_to_sid(psid, &xid);
 }
 
 bool sids_to_unixids(const struct dom_sid *sids, uint32_t num_sids,
@@ -1278,7 +1384,9 @@ bool sids_to_unixids(const struct dom_sid *sids, uint32_t num_sids,
 {
        struct wbcDomainSid *wbc_sids = NULL;
        struct wbcUnixId *wbc_ids = NULL;
+       struct bitmap *found = NULL;
        uint32_t i, num_not_cached;
+       uint32_t wbc_ids_size = 0;
        wbcErr err;
        bool ret = false;
 
@@ -1286,6 +1394,20 @@ bool sids_to_unixids(const struct dom_sid *sids, uint32_t num_sids,
        if (wbc_sids == NULL) {
                return false;
        }
+       found = bitmap_talloc(wbc_sids, num_sids);
+       if (found == NULL) {
+               goto fail;
+       }
+
+       /*
+        * We go through the requested SID array three times.
+        * First time to look for global_sid_Unix_Users
+        * and global_sid_Unix_Groups SIDS, and to look
+        * for mappings cached in the idmap_cache.
+        *
+        * Use bitmap_set() to mark an ids[] array entry as
+        * being mapped.
+        */
 
        num_not_cached = 0;
 
@@ -1297,17 +1419,20 @@ bool sids_to_unixids(const struct dom_sid *sids, uint32_t num_sids,
                                       &sids[i], &rid)) {
                        ids[i].type = ID_TYPE_UID;
                        ids[i].id = rid;
+                       bitmap_set(found, i);
                        continue;
                }
                if (sid_peek_check_rid(&global_sid_Unix_Groups,
                                       &sids[i], &rid)) {
                        ids[i].type = ID_TYPE_GID;
                        ids[i].id = rid;
+                       bitmap_set(found, i);
                        continue;
                }
                if (idmap_cache_find_sid2unixid(&sids[i], &ids[i], &expired)
                    && !expired)
                {
+                       bitmap_set(found, i);
                        continue;
                }
                ids[i].type = ID_TYPE_NOT_SPECIFIED;
@@ -1318,66 +1443,132 @@ bool sids_to_unixids(const struct dom_sid *sids, uint32_t num_sids,
        if (num_not_cached == 0) {
                goto done;
        }
-       wbc_ids = talloc_array(talloc_tos(), struct wbcUnixId, num_not_cached);
+
+       /*
+        * For the ones that we couldn't map in the loop above, query winbindd
+        * via wbcSidsToUnixIds().
+        */
+
+       wbc_ids_size = num_not_cached;
+       wbc_ids = talloc_array(talloc_tos(), struct wbcUnixId, wbc_ids_size);
        if (wbc_ids == NULL) {
                goto fail;
        }
-       for (i=0; i<num_not_cached; i++) {
+       for (i=0; i<wbc_ids_size; i++) {
                wbc_ids[i].type = WBC_ID_TYPE_NOT_SPECIFIED;
+               wbc_ids[i].id.gid = (uint32_t)-1;
        }
-       err = wbcSidsToUnixIds(wbc_sids, num_not_cached, wbc_ids);
+       err = wbcSidsToUnixIds(wbc_sids, wbc_ids_size, wbc_ids);
        if (!WBC_ERROR_IS_OK(err)) {
                DEBUG(10, ("wbcSidsToUnixIds returned %s\n",
                           wbcErrorString(err)));
        }
 
+       /*
+        * Second time through the SID array, replace
+        * the ids[] entries that wbcSidsToUnixIds() was able to
+        * map.
+        *
+        * Use bitmap_set() to mark an ids[] array entry as
+        * being mapped.
+        */
+
        num_not_cached = 0;
 
        for (i=0; i<num_sids; i++) {
-               if (ids[i].type == ID_TYPE_NOT_SPECIFIED) {
-                       switch (wbc_ids[num_not_cached].type) {
-                       case WBC_ID_TYPE_UID:
-                               ids[i].type = ID_TYPE_UID;
-                               ids[i].id = wbc_ids[num_not_cached].id.uid;
-                               break;
-                       case WBC_ID_TYPE_GID:
-                               ids[i].type = ID_TYPE_GID;
-                               ids[i].id = wbc_ids[num_not_cached].id.gid;
-                               break;
-                       default:
-                               /* The types match, and wbcUnixId -> id is a union anyway */
-                               ids[i].type = (enum id_type)wbc_ids[num_not_cached].type;
-                               ids[i].id = wbc_ids[num_not_cached].id.gid;
-                               break;
-                       }
-                       num_not_cached += 1;
+               if (bitmap_query(found, i)) {
+                       continue;
                }
+
+               SMB_ASSERT(num_not_cached < wbc_ids_size);
+
+               switch (wbc_ids[num_not_cached].type) {
+               case WBC_ID_TYPE_UID:
+                       ids[i].type = ID_TYPE_UID;
+                       ids[i].id = wbc_ids[num_not_cached].id.uid;
+                       bitmap_set(found, i);
+                       break;
+               case WBC_ID_TYPE_GID:
+                       ids[i].type = ID_TYPE_GID;
+                       ids[i].id = wbc_ids[num_not_cached].id.gid;
+                       bitmap_set(found, i);
+                       break;
+               case WBC_ID_TYPE_BOTH:
+                       ids[i].type = ID_TYPE_BOTH;
+                       ids[i].id = wbc_ids[num_not_cached].id.uid;
+                       bitmap_set(found, i);
+                       break;
+               case WBC_ID_TYPE_NOT_SPECIFIED:
+                       /*
+                        * wbcSidsToUnixIds() wasn't able to map this
+                        * so we still need to check legacy_sid_to_XXX()
+                        * below. Don't mark the bitmap entry
+                        * as being found so the final loop knows
+                        * to try and map this entry.
+                        */
+                       ids[i].type = ID_TYPE_NOT_SPECIFIED;
+                       ids[i].id = (uint32_t)-1;
+                       break;
+               default:
+                       /*
+                        * A successful return from wbcSidsToUnixIds()
+                        * cannot return anything other than the values
+                        * checked for above. Ensure this is so.
+                        */
+                       smb_panic(__location__);
+                       break;
+               }
+               num_not_cached += 1;
        }
 
+       /*
+        * Third and final time through the SID array,
+        * try legacy_sid_to_gid()/legacy_sid_to_uid()
+        * for entries we haven't already been able to
+        * map.
+        *
+        * Use bitmap_set() to mark an ids[] array entry as
+        * being mapped.
+        */
+
        for (i=0; i<num_sids; i++) {
-               if (ids[i].type != ID_TYPE_NOT_SPECIFIED) {
+               if (bitmap_query(found, i)) {
                        continue;
                }
                if (legacy_sid_to_gid(&sids[i], &ids[i].id)) {
                        ids[i].type = ID_TYPE_GID;
+                       bitmap_set(found, i);
                        continue;
                }
                if (legacy_sid_to_uid(&sids[i], &ids[i].id)) {
                        ids[i].type = ID_TYPE_UID;
+                       bitmap_set(found, i);
                        continue;
                }
        }
 done:
+       /*
+        * Pass through the return array for consistency.
+        * Any ids[].id mapped to (uint32_t)-1 must be returned
+        * as ID_TYPE_NOT_SPECIFIED.
+        */
        for (i=0; i<num_sids; i++) {
                switch(ids[i].type) {
-               case WBC_ID_TYPE_GID:
-               case WBC_ID_TYPE_UID:
-               case WBC_ID_TYPE_BOTH:
-                       if (ids[i].id == -1) {
+               case ID_TYPE_GID:
+               case ID_TYPE_UID:
+               case ID_TYPE_BOTH:
+                       if (ids[i].id == (uint32_t)-1) {
                                ids[i].type = ID_TYPE_NOT_SPECIFIED;
                        }
                        break;
-               case WBC_ID_TYPE_NOT_SPECIFIED:
+               case ID_TYPE_NOT_SPECIFIED:
+                       break;
+               case ID_TYPE_WB_REQUIRE_TYPE:
+                       /*
+                        * these are internal between winbindd
+                        * parent and child.
+                        */
+                       smb_panic(__location__);
                        break;
                }
        }
@@ -1391,13 +1582,14 @@ fail:
 
 /*****************************************************************
  *THE CANONICAL* convert SID to uid function.
-*****************************************************************/  
+*****************************************************************/
 
 bool sid_to_uid(const struct dom_sid *psid, uid_t *puid)
 {
        bool expired = true;
        bool ret;
        uint32_t rid;
+       struct dom_sid_buf buf;
 
        /* Optimize for the Unix Users Domain
         * as the conversion is straightforward */
@@ -1406,11 +1598,18 @@ bool sid_to_uid(const struct dom_sid *psid, uid_t *puid)
                *puid = uid;
 
                /* return here, don't cache */
-               DEBUG(10,("sid %s -> uid %u\n", sid_string_dbg(psid),
-                       (unsigned int)*puid ));
+               DEBUG(10,("sid %s -> uid %u\n",
+                         dom_sid_str_buf(psid, &buf),
+                         (unsigned int)*puid ));
                return true;
        }
 
+       if (sid_check_is_in_unix_groups(psid)) {
+               DBG_DEBUG("SID %s is a group, failing\n",
+                         dom_sid_str_buf(psid, &buf));
+               return false;
+       }
+
        /* Check the winbindd cache directly. */
        ret = idmap_cache_find_sid2uid(psid, puid, &expired);
 
@@ -1426,7 +1625,7 @@ bool sid_to_uid(const struct dom_sid *psid, uid_t *puid)
                /* Not in cache. Ask winbindd. */
                if (!winbind_sid_to_uid(puid, psid)) {
                        DEBUG(5, ("winbind failed to find a uid for sid %s\n",
-                                 sid_string_dbg(psid)));
+                                 dom_sid_str_buf(psid, &buf)));
                        /* winbind failed. do legacy */
                        return legacy_sid_to_uid(psid, puid);
                }
@@ -1435,7 +1634,8 @@ bool sid_to_uid(const struct dom_sid *psid, uid_t *puid)
        /* TODO: Here would be the place to allocate both a gid and a uid for
         * the SID in question */
 
-       DEBUG(10,("sid %s -> uid %u\n", sid_string_dbg(psid),
+       DEBUG(10,("sid %s -> uid %u\n",
+                 dom_sid_str_buf(psid, &buf),
                (unsigned int)*puid ));
 
        return true;
@@ -1444,13 +1644,14 @@ bool sid_to_uid(const struct dom_sid *psid, uid_t *puid)
 /*****************************************************************
  *THE CANONICAL* convert SID to gid function.
  Group mapping is used for gids that maps to Wellknown SIDs
-*****************************************************************/  
+*****************************************************************/
 
 bool sid_to_gid(const struct dom_sid *psid, gid_t *pgid)
 {
        bool expired = true;
        bool ret;
        uint32_t rid;
+       struct dom_sid_buf buf;
 
        /* Optimize for the Unix Groups Domain
         * as the conversion is straightforward */
@@ -1459,11 +1660,18 @@ bool sid_to_gid(const struct dom_sid *psid, gid_t *pgid)
                *pgid = gid;
 
                /* return here, don't cache */
-               DEBUG(10,("sid %s -> gid %u\n", sid_string_dbg(psid),
+               DEBUG(10,("sid %s -> gid %u\n",
+                         dom_sid_str_buf(psid, &buf),
                        (unsigned int)*pgid ));
                return true;
        }
 
+       if (sid_check_is_in_unix_users(psid)) {
+               DBG_DEBUG("SID %s is a user, failing\n",
+                         dom_sid_str_buf(psid, &buf));
+               return false;
+       }
+
        /* Check the winbindd cache directly. */
        ret = idmap_cache_find_sid2gid(psid, pgid, &expired);
 
@@ -1483,13 +1691,14 @@ bool sid_to_gid(const struct dom_sid *psid, gid_t *pgid)
                if ( !winbind_sid_to_gid(pgid, psid) ) {
 
                        DEBUG(10,("winbind failed to find a gid for sid %s\n",
-                                 sid_string_dbg(psid)));
+                                 dom_sid_str_buf(psid, &buf)));
                        /* winbind failed. do legacy */
                        return legacy_sid_to_gid(psid, pgid);
                }
        }
 
-       DEBUG(10,("sid %s -> gid %u\n", sid_string_dbg(psid),
+       DEBUG(10,("sid %s -> gid %u\n",
+                 dom_sid_str_buf(psid, &buf),
                  (unsigned int)*pgid ));
 
        return true;
@@ -1590,9 +1799,11 @@ NTSTATUS get_primary_group_sid(TALLOC_CTX *mem_ctx,
        if (need_lookup_sid) {
                enum lsa_SidType type = SID_NAME_UNKNOWN;
                bool lookup_ret;
+               struct dom_sid_buf buf;
 
                DEBUG(10, ("do lookup_sid(%s) for group of user %s\n",
-                          sid_string_dbg(group_sid), username));
+                          dom_sid_str_buf(group_sid, &buf),
+                          username));
 
                /* Now check that it's actually a domain group and
                 * not something else */
@@ -1605,7 +1816,8 @@ NTSTATUS get_primary_group_sid(TALLOC_CTX *mem_ctx,
 
                DEBUG(3, ("Primary group %s for user %s is"
                          " a %s and not a domain group\n",
-                         sid_string_dbg(group_sid), username,
+                         dom_sid_str_buf(group_sid, &buf),
+                         username,
                          sid_type_lookup(type)));
        }