1
0
mirror of https://github.com/samba-team/samba.git synced 2025-01-28 17:47:29 +03:00
samba-mirror/source/nsswitch/winbindd_ads.c
Andrew Bartlett a074f74e62 Fix a really nasty bug where some users in AD domains (particularly child
domains) would not have the tokenGroups or memberOf attributes filled in.

This would cause a user to have no supplementary group membership.

Detect this by the fact that the primaryGid must be present in the tokenGroups,
and if it isn't (ie, if there is no tokenGroups at all), do a server-side
search on all groups using the 'member' attribute and the user's DN.

Andrew Bartlett
-

800 lines
19 KiB
C

/*
Unix SMB/CIFS implementation.
Winbind ADS backend functions
Copyright (C) Andrew Tridgell 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 "winbindd.h"
#ifdef HAVE_ADS
#undef DBGC_CLASS
#define DBGC_CLASS DBGC_WINBIND
/* the realm of our primary LDAP server */
static char *primary_realm;
/*
return our ads connections structure for a domain. We keep the connection
open to make things faster
*/
static ADS_STRUCT *ads_cached_connection(struct winbindd_domain *domain)
{
ADS_STRUCT *ads;
ADS_STATUS status;
if (domain->private) {
return (ADS_STRUCT *)domain->private;
}
/* we don't want this to affect the users ccache */
setenv("KRB5CCNAME", "MEMORY:winbind_ccache", 1);
ads = ads_init(domain->alt_name, domain->name, NULL);
if (!ads) {
DEBUG(1,("ads_init for domain %s failed\n", domain->name));
return NULL;
}
/* the machine acct password might have change - fetch it every time */
SAFE_FREE(ads->auth.password);
ads->auth.password = secrets_fetch_machine_password();
if (primary_realm) {
SAFE_FREE(ads->auth.realm);
ads->auth.realm = strdup(primary_realm);
}
status = ads_connect(ads);
if (!ADS_ERR_OK(status) || !ads->config.realm) {
extern struct winbindd_methods msrpc_methods;
DEBUG(1,("ads_connect for domain %s failed: %s\n",
domain->name, ads_errstr(status)));
ads_destroy(&ads);
/* if we get ECONNREFUSED then it might be a NT4
server, fall back to MSRPC */
if (status.error_type == ADS_ERROR_SYSTEM &&
status.err.rc == ECONNREFUSED) {
DEBUG(1,("Trying MSRPC methods\n"));
domain->methods = &msrpc_methods;
}
return NULL;
}
/* remember our primary realm for trusted domain support */
if (!primary_realm) {
primary_realm = strdup(ads->config.realm);
}
domain->private = (void *)ads;
return ads;
}
/* useful utility */
static void sid_from_rid(struct winbindd_domain *domain, uint32 rid, DOM_SID *sid)
{
sid_copy(sid, &domain->sid);
sid_append_rid(sid, rid);
}
/* Query display info for a realm. This is the basic user list fn */
static NTSTATUS query_user_list(struct winbindd_domain *domain,
TALLOC_CTX *mem_ctx,
uint32 *num_entries,
WINBIND_USERINFO **info)
{
ADS_STRUCT *ads = NULL;
const char *attrs[] = {"userPrincipalName",
"sAMAccountName",
"name", "objectSid", "primaryGroupID",
"sAMAccountType", NULL};
int i, count;
ADS_STATUS rc;
void *res = NULL;
void *msg = NULL;
NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
*num_entries = 0;
DEBUG(3,("ads: query_user_list\n"));
ads = ads_cached_connection(domain);
if (!ads) goto done;
rc = ads_search_retry(ads, &res, "(objectCategory=user)", attrs);
if (!ADS_ERR_OK(rc)) {
DEBUG(1,("query_user_list ads_search: %s\n", ads_errstr(rc)));
goto done;
}
count = ads_count_replies(ads, res);
if (count == 0) {
DEBUG(1,("query_user_list: No users found\n"));
goto done;
}
(*info) = talloc_zero(mem_ctx, count * sizeof(**info));
if (!*info) {
status = NT_STATUS_NO_MEMORY;
goto done;
}
i = 0;
for (msg = ads_first_entry(ads, res); msg; msg = ads_next_entry(ads, msg)) {
char *name, *gecos;
DOM_SID sid;
uint32 rid, group;
uint32 atype;
if (!ads_pull_uint32(ads, msg, "sAMAccountType", &atype) ||
ads_atype_map(atype) != SID_NAME_USER) {
DEBUG(1,("Not a user account? atype=0x%x\n", atype));
continue;
}
name = ads_pull_username(ads, mem_ctx, msg);
gecos = ads_pull_string(ads, mem_ctx, msg, "name");
if (!ads_pull_sid(ads, msg, "objectSid", &sid)) {
DEBUG(1,("No sid for %s !?\n", name));
continue;
}
if (!ads_pull_uint32(ads, msg, "primaryGroupID", &group)) {
DEBUG(1,("No primary group for %s !?\n", name));
continue;
}
if (!sid_peek_check_rid(&domain->sid, &sid, &rid)) {
DEBUG(1,("No rid for %s !?\n", name));
continue;
}
(*info)[i].acct_name = name;
(*info)[i].full_name = gecos;
(*info)[i].user_rid = rid;
(*info)[i].group_rid = group;
i++;
}
(*num_entries) = i;
status = NT_STATUS_OK;
DEBUG(3,("ads query_user_list gave %d entries\n", (*num_entries)));
done:
if (res) ads_msgfree(ads, res);
return status;
}
/* list all domain groups */
static NTSTATUS enum_dom_groups(struct winbindd_domain *domain,
TALLOC_CTX *mem_ctx,
uint32 *num_entries,
struct acct_info **info)
{
ADS_STRUCT *ads = NULL;
const char *attrs[] = {"userPrincipalName", "sAMAccountName",
"name", "objectSid",
"sAMAccountType", NULL};
int i, count;
ADS_STATUS rc;
void *res = NULL;
void *msg = NULL;
NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
uint32 group_flags;
*num_entries = 0;
DEBUG(3,("ads: enum_dom_groups\n"));
ads = ads_cached_connection(domain);
if (!ads) goto done;
rc = ads_search_retry(ads, &res, "(objectCategory=group)", attrs);
if (!ADS_ERR_OK(rc)) {
DEBUG(1,("enum_dom_groups ads_search: %s\n", ads_errstr(rc)));
goto done;
}
count = ads_count_replies(ads, res);
if (count == 0) {
DEBUG(1,("enum_dom_groups: No groups found\n"));
goto done;
}
(*info) = talloc_zero(mem_ctx, count * sizeof(**info));
if (!*info) {
status = NT_STATUS_NO_MEMORY;
goto done;
}
i = 0;
group_flags = ATYPE_GLOBAL_GROUP;
if ( domain->native_mode )
group_flags |= ATYPE_LOCAL_GROUP;
for (msg = ads_first_entry(ads, res); msg; msg = ads_next_entry(ads, msg)) {
char *name, *gecos;
DOM_SID sid;
uint32 rid;
uint32 account_type;
if (!ads_pull_uint32(ads, msg, "sAMAccountType", &account_type) || !(account_type & group_flags) )
continue;
name = ads_pull_username(ads, mem_ctx, msg);
gecos = ads_pull_string(ads, mem_ctx, msg, "name");
if (!ads_pull_sid(ads, msg, "objectSid", &sid)) {
DEBUG(1,("No sid for %s !?\n", name));
continue;
}
if (!sid_peek_check_rid(&domain->sid, &sid, &rid)) {
DEBUG(1,("No rid for %s !?\n", name));
continue;
}
fstrcpy((*info)[i].acct_name, name);
fstrcpy((*info)[i].acct_desc, gecos);
(*info)[i].rid = rid;
i++;
}
(*num_entries) = i;
status = NT_STATUS_OK;
DEBUG(3,("ads enum_dom_groups gave %d entries\n", (*num_entries)));
done:
if (res) ads_msgfree(ads, res);
return status;
}
/* list all domain local groups */
static NTSTATUS enum_local_groups(struct winbindd_domain *domain,
TALLOC_CTX *mem_ctx,
uint32 *num_entries,
struct acct_info **info)
{
/*
* This is a stub function only as we returned the domain
* ocal groups in enum_dom_groups() if the domain->native field
* was true. This is a simple performance optimization when
* using LDAP.
*
* if we ever need to enumerate domain local groups separately,
* then this the optimization in enum_dom_groups() will need
* to be split out
*/
*num_entries = 0;
return NT_STATUS_OK;
}
/* convert a single name to a sid in a domain */
static NTSTATUS name_to_sid(struct winbindd_domain *domain,
const char *name,
DOM_SID *sid,
enum SID_NAME_USE *type)
{
ADS_STRUCT *ads;
DEBUG(3,("ads: name_to_sid\n"));
ads = ads_cached_connection(domain);
if (!ads)
return NT_STATUS_UNSUCCESSFUL;
return ads_name_to_sid(ads, name, sid, type);
}
/* convert a sid to a user or group name */
static NTSTATUS sid_to_name(struct winbindd_domain *domain,
TALLOC_CTX *mem_ctx,
DOM_SID *sid,
char **name,
enum SID_NAME_USE *type)
{
ADS_STRUCT *ads = NULL;
DEBUG(3,("ads: sid_to_name\n"));
ads = ads_cached_connection(domain);
if (!ads)
return NT_STATUS_UNSUCCESSFUL;
return ads_sid_to_name(ads, mem_ctx, sid, name, type);
}
/* convert a DN to a name, rid and name type
this might become a major speed bottleneck if groups have
lots of users, in which case we could cache the results
*/
static BOOL dn_lookup(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx,
const char *dn,
char **name, uint32 *name_type, uint32 *rid)
{
char *exp;
void *res = NULL;
const char *attrs[] = {"userPrincipalName", "sAMAccountName",
"objectSid", "sAMAccountType", NULL};
ADS_STATUS rc;
uint32 atype;
DOM_SID sid;
char *escaped_dn = escape_ldap_string_alloc(dn);
if (!escaped_dn) {
return False;
}
asprintf(&exp, "(distinguishedName=%s)", dn);
rc = ads_search_retry(ads, &res, exp, attrs);
SAFE_FREE(exp);
SAFE_FREE(escaped_dn);
if (!ADS_ERR_OK(rc)) {
goto failed;
}
(*name) = ads_pull_username(ads, mem_ctx, res);
if (!ads_pull_uint32(ads, res, "sAMAccountType", &atype)) {
goto failed;
}
(*name_type) = ads_atype_map(atype);
if (!ads_pull_sid(ads, res, "objectSid", &sid) ||
!sid_peek_rid(&sid, rid)) {
goto failed;
}
if (res) ads_msgfree(ads, res);
return True;
failed:
if (res) ads_msgfree(ads, res);
return False;
}
/* Lookup user information from a rid */
static NTSTATUS query_user(struct winbindd_domain *domain,
TALLOC_CTX *mem_ctx,
uint32 user_rid,
WINBIND_USERINFO *info)
{
ADS_STRUCT *ads = NULL;
const char *attrs[] = {"userPrincipalName",
"sAMAccountName",
"name", "objectSid",
"primaryGroupID", NULL};
ADS_STATUS rc;
int count;
void *msg = NULL;
char *exp;
DOM_SID sid;
char *sidstr;
NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
DEBUG(3,("ads: query_user\n"));
sid_from_rid(domain, user_rid, &sid);
ads = ads_cached_connection(domain);
if (!ads) goto done;
sidstr = sid_binstring(&sid);
asprintf(&exp, "(objectSid=%s)", sidstr);
rc = ads_search_retry(ads, &msg, exp, attrs);
free(exp);
free(sidstr);
if (!ADS_ERR_OK(rc)) {
DEBUG(1,("query_user(rid=%d) ads_search: %s\n", user_rid, ads_errstr(rc)));
goto done;
}
count = ads_count_replies(ads, msg);
if (count != 1) {
DEBUG(1,("query_user(rid=%d): Not found\n", user_rid));
goto done;
}
info->acct_name = ads_pull_username(ads, mem_ctx, msg);
info->full_name = ads_pull_string(ads, mem_ctx, msg, "name");
if (!ads_pull_sid(ads, msg, "objectSid", &sid)) {
DEBUG(1,("No sid for %d !?\n", user_rid));
goto done;
}
if (!ads_pull_uint32(ads, msg, "primaryGroupID", &info->group_rid)) {
DEBUG(1,("No primary group for %d !?\n", user_rid));
goto done;
}
if (!sid_peek_check_rid(&domain->sid,&sid, &info->user_rid)) {
DEBUG(1,("No rid for %d !?\n", user_rid));
goto done;
}
status = NT_STATUS_OK;
DEBUG(3,("ads query_user gave %s\n", info->acct_name));
done:
if (msg) ads_msgfree(ads, msg);
return status;
}
/* Lookup groups a user is a member of. */
static NTSTATUS lookup_usergroups(struct winbindd_domain *domain,
TALLOC_CTX *mem_ctx,
uint32 user_rid,
uint32 *num_groups, uint32 **user_gids)
{
ADS_STRUCT *ads = NULL;
const char *attrs[] = {"distinguishedName", NULL};
const char *attrs2[] = {"tokenGroups", "primaryGroupID", NULL};
const char *group_attrs[] = {"objectSid", "cn", NULL};
ADS_STATUS rc;
int count;
void *res = NULL;
void *msg = NULL;
char *exp;
char *user_dn;
DOM_SID *sids;
int i;
uint32 primary_group;
DOM_SID sid;
char *sidstr;
NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
DEBUG(3,("ads: lookup_usergroups\n"));
*num_groups = 0;
sid_from_rid(domain, user_rid, &sid);
ads = ads_cached_connection(domain);
if (!ads) goto done;
if (!(sidstr = sid_binstring(&sid))) {
DEBUG(1,("lookup_usergroups(rid=%d) sid_binstring returned NULL\n", user_rid));
status = NT_STATUS_NO_MEMORY;
goto done;
}
if (asprintf(&exp, "(objectSid=%s)", sidstr) == -1) {
free(sidstr);
DEBUG(1,("lookup_usergroups(rid=%d) asprintf failed!\n", user_rid));
status = NT_STATUS_NO_MEMORY;
goto done;
}
rc = ads_search_retry(ads, &msg, exp, attrs);
free(exp);
free(sidstr);
if (!ADS_ERR_OK(rc)) {
DEBUG(1,("lookup_usergroups(rid=%d) ads_search: %s\n", user_rid, ads_errstr(rc)));
goto done;
}
user_dn = ads_pull_string(ads, mem_ctx, msg, "distinguishedName");
if (!user_dn) {
DEBUG(1,("lookup_usergroups(rid=%d) ads_search did not return a a distinguishedName!\n", user_rid));
goto done;
}
if (msg) ads_msgfree(ads, msg);
rc = ads_search_retry_dn(ads, &msg, user_dn, attrs2);
if (!ADS_ERR_OK(rc)) {
DEBUG(1,("lookup_usergroups(rid=%d) ads_search tokenGroups: %s\n", user_rid, ads_errstr(rc)));
goto done;
}
if (!ads_pull_uint32(ads, msg, "primaryGroupID", &primary_group)) {
DEBUG(1,("%s: No primary group for rid=%d !?\n", domain->name, user_rid));
goto done;
}
count = ads_pull_sids(ads, mem_ctx, msg, "tokenGroups", &sids);
if (msg) ads_msgfree(ads, msg);
/* there must always be at least one group in the token,
unless we are talking to a buggy Win2k server */
if (count == 0) {
/* buggy server, no tokenGroups. Instead lookup what groups this user
is a member of by DN search on member*/
if (asprintf(&exp, "(&(member=%s)(objectClass=group))", user_dn) == -1) {
free(sidstr);
DEBUG(1,("lookup_usergroups(rid=%d) asprintf failed!\n", user_rid));
status = NT_STATUS_NO_MEMORY;
goto done;
}
rc = ads_search_retry(ads, &res, exp, group_attrs);
free(exp);
if (!ADS_ERR_OK(rc)) {
DEBUG(1,("lookup_usergroups(rid=%d) ads_search member=%s: %s\n", user_rid, user_dn, ads_errstr(rc)));
goto done;
}
count = ads_count_replies(ads, res);
if (count == 0) {
DEBUG(5,("lookup_usergroups: No supp groups found\n"));
goto done;
}
(*user_gids) = (uint32 *)talloc_zero(mem_ctx, sizeof(uint32) * (count + 1));
(*user_gids)[0] = primary_group;
*num_groups = 1;
for (msg = ads_first_entry(ads, res); msg; msg = ads_next_entry(ads, msg)) {
uint32 rid;
DOM_SID group_sid;
fstring sid_string;
const char *cn;
cn = ads_pull_string(ads, mem_ctx, msg, "cn");
if (!cn) {
cn = "<CN NOT AVAILABLE>";
}
if (!ads_pull_sid(ads, msg, "objectSid", &group_sid)) {
DEBUG(1,("No sid for %s !?\n", cn));
continue;
}
if (!sid_peek_check_rid(&domain->sid, &group_sid, &rid)) {
DEBUG(5,("sid for %s is out of domain or invalid\n", sid_to_string(sid_string, &sid)));
continue;
}
if (rid == primary_group) continue;
(*user_gids)[*num_groups] = rid;
(*num_groups)++;
}
} else {
(*user_gids) = (uint32 *)talloc_zero(mem_ctx, sizeof(uint32) * (count + 1));
(*user_gids)[0] = primary_group;
*num_groups = 1;
for (i=0;i<count;i++) {
uint32 rid;
if (!sid_peek_check_rid(&domain->sid, &sids[i-1], &rid)) continue;
if (rid == primary_group) continue;
(*user_gids)[*num_groups] = rid;
(*num_groups)++;
}
}
status = NT_STATUS_OK;
DEBUG(3,("ads lookup_usergroups for rid=%d\n", user_rid));
done:
if (res) ads_msgfree(ads, res);
if (msg) ads_msgfree(ads, msg);
return status;
}
/*
find the members of a group, given a group rid and domain
*/
static NTSTATUS lookup_groupmem(struct winbindd_domain *domain,
TALLOC_CTX *mem_ctx,
uint32 group_rid, uint32 *num_names,
uint32 **rid_mem, char ***names,
uint32 **name_types)
{
DOM_SID group_sid;
ADS_STATUS rc;
int count;
void *res=NULL;
ADS_STRUCT *ads = NULL;
char *exp;
NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
char *sidstr;
const char *attrs[] = {"member", NULL};
char **members;
int i, num_members;
*num_names = 0;
ads = ads_cached_connection(domain);
if (!ads) goto done;
sid_from_rid(domain, group_rid, &group_sid);
sidstr = sid_binstring(&group_sid);
/* search for all members of the group */
asprintf(&exp, "(objectSid=%s)",sidstr);
rc = ads_search_retry(ads, &res, exp, attrs);
free(exp);
free(sidstr);
if (!ADS_ERR_OK(rc)) {
DEBUG(1,("query_user_list ads_search: %s\n", ads_errstr(rc)));
goto done;
}
count = ads_count_replies(ads, res);
if (count == 0) {
status = NT_STATUS_OK;
goto done;
}
members = ads_pull_strings(ads, mem_ctx, res, "member");
if (!members) {
/* no members? ok ... */
status = NT_STATUS_OK;
goto done;
}
/* now we need to turn a list of members into rids, names and name types
the problem is that the members are in the form of distinguised names
*/
for (i=0;members[i];i++) /* noop */ ;
num_members = i;
(*rid_mem) = talloc_zero(mem_ctx, sizeof(uint32) * num_members);
(*name_types) = talloc_zero(mem_ctx, sizeof(uint32) * num_members);
(*names) = talloc_zero(mem_ctx, sizeof(char *) * num_members);
for (i=0;i<num_members;i++) {
uint32 name_type, rid;
char *name;
if (dn_lookup(ads, mem_ctx, members[i], &name, &name_type, &rid)) {
(*names)[*num_names] = name;
(*name_types)[*num_names] = name_type;
(*rid_mem)[*num_names] = rid;
(*num_names)++;
}
}
status = NT_STATUS_OK;
DEBUG(3,("ads lookup_groupmem for rid=%d\n", group_rid));
done:
if (res) ads_msgfree(ads, res);
return status;
}
/* find the sequence number for a domain */
static NTSTATUS sequence_number(struct winbindd_domain *domain, uint32 *seq)
{
ADS_STRUCT *ads = NULL;
ADS_STATUS rc;
*seq = DOM_SEQUENCE_NONE;
ads = ads_cached_connection(domain);
if (!ads) return NT_STATUS_UNSUCCESSFUL;
rc = ads_USN(ads, seq);
if (!ADS_ERR_OK(rc)) {
/* its a dead connection */
ads_destroy(&ads);
domain->private = NULL;
}
return ads_ntstatus(rc);
}
/* get a list of trusted domains */
static NTSTATUS trusted_domains(struct winbindd_domain *domain,
TALLOC_CTX *mem_ctx,
uint32 *num_domains,
char ***names,
char ***alt_names,
DOM_SID **dom_sids)
{
ADS_STRUCT *ads;
ADS_STATUS rc;
*num_domains = 0;
*names = NULL;
ads = ads_cached_connection(domain);
if (!ads) return NT_STATUS_UNSUCCESSFUL;
rc = ads_trusted_domains(ads, mem_ctx, num_domains, names, alt_names, dom_sids);
return ads_ntstatus(rc);
}
/* find the domain sid for a domain */
static NTSTATUS domain_sid(struct winbindd_domain *domain, DOM_SID *sid)
{
ADS_STRUCT *ads;
ADS_STATUS rc;
ads = ads_cached_connection(domain);
if (!ads) return NT_STATUS_UNSUCCESSFUL;
rc = ads_domain_sid(ads, sid);
if (!ADS_ERR_OK(rc)) {
/* its a dead connection */
ads_destroy(&ads);
domain->private = NULL;
}
return ads_ntstatus(rc);
}
/* find alternate names list for the domain - for ADS this is the
netbios name */
static NTSTATUS alternate_name(struct winbindd_domain *domain)
{
ADS_STRUCT *ads;
ADS_STATUS rc;
TALLOC_CTX *ctx;
char *workgroup;
ads = ads_cached_connection(domain);
if (!ads) return NT_STATUS_UNSUCCESSFUL;
if (!(ctx = talloc_init("alternate_name"))) {
return NT_STATUS_NO_MEMORY;
}
rc = ads_workgroup_name(ads, ctx, &workgroup);
if (ADS_ERR_OK(rc)) {
fstrcpy(domain->name, workgroup);
fstrcpy(domain->alt_name, ads->config.realm);
strupper(domain->alt_name);
strupper(domain->name);
}
talloc_destroy(ctx);
return ads_ntstatus(rc);
}
/* the ADS backend methods are exposed via this structure */
struct winbindd_methods ads_methods = {
True,
query_user_list,
enum_dom_groups,
enum_local_groups,
name_to_sid,
sid_to_name,
query_user,
lookup_usergroups,
lookup_groupmem,
sequence_number,
trusted_domains,
domain_sid,
alternate_name
};
#endif