/* Unix SMB/Netbios implementation. Version 2.0 Winbind daemon for ntdom nss module Copyright (C) Tim Potter 2000 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" /* Fill a grent structure from various other information */ static void winbindd_fill_grent(struct winbindd_gr *gr, char *gr_name, gid_t unix_gid) { /* Fill in uid/gid */ gr->gr_gid = unix_gid; /* Group name and password */ safe_strcpy(gr->gr_name, gr_name, sizeof(gr->gr_name) - 1); safe_strcpy(gr->gr_passwd, "x", sizeof(gr->gr_passwd) - 1); } /* Fill in group membership */ struct grent_mem_group { uint32 rid; enum SID_NAME_USE name_type; fstring domain_name; struct winbindd_domain *domain; struct grent_mem_group *prev, *next; }; struct grent_mem_list { fstring name; struct grent_mem_list *prev, *next; }; /* Name comparison function for qsort() */ static int name_comp(struct grent_mem_list *n1, struct grent_mem_list *n2) { /* Silly cases */ if (!n1 && !n2) return 0; if (!n1) return -1; if (!n2) return 1; return strcmp(n1->name, n2->name); } static struct grent_mem_list *sort_groupmem_list(struct grent_mem_list *list, int num_gr_mem) { struct grent_mem_list *groupmem_array, *temp; int i; /* Allocate and zero an array to hold sorted entries */ if ((groupmem_array = malloc(num_gr_mem * sizeof(struct grent_mem_list))) == NULL) { return NULL; } memset((char *)groupmem_array, 0, num_gr_mem * sizeof(struct grent_mem_list)); /* Copy list to array */ for(temp = list, i = 0; temp && i < num_gr_mem; temp = temp->next, i++) { fstrcpy(groupmem_array[i].name, temp->name); } /* Sort array */ qsort(groupmem_array, num_gr_mem, sizeof(struct grent_mem_list), name_comp); /* Fix up resulting array to a linked list and return it */ for(i = 0; i < num_gr_mem; i++) { /* Fix up previous link */ if (i != 0) { groupmem_array[i].prev = &groupmem_array[i - 1]; } /* Fix up next link */ if (i != (num_gr_mem - 1)) { groupmem_array[i].next = &groupmem_array[i + 1]; } } return groupmem_array; } static BOOL winbindd_fill_grent_mem(struct winbindd_domain *domain, uint32 group_rid, enum SID_NAME_USE group_name_type, struct winbindd_response *response) { struct grent_mem_group *done_groups = NULL, *todo_groups = NULL; struct grent_mem_group *temp_group; struct grent_mem_list *groupmem_list = NULL; struct winbindd_gr *gr; if (response) { gr = &response->data.gr; } else { return False; } /* Initialise group membership information */ gr->num_gr_mem = 0; /* Add first group to todo_groups list */ if ((temp_group = (struct grent_mem_group *)malloc(sizeof(*temp_group))) == NULL) { return False; } ZERO_STRUCTP(temp_group); temp_group->rid = group_rid; temp_group->name_type = group_name_type; temp_group->domain = domain; fstrcpy(temp_group->domain_name, domain->name); DLIST_ADD(todo_groups, temp_group); /* Iterate over all groups to find members of */ while(todo_groups != NULL) { struct grent_mem_group *current_group = todo_groups; uint32 num_names = 0, *rid_mem = NULL; enum SID_NAME_USE *name_types = NULL; DOM_SID **sids = NULL; char **names = NULL; BOOL done_group; int i; /* Check we haven't looked up this group before */ done_group = 0; for (temp_group = done_groups; temp_group != NULL; temp_group = temp_group->next) { if ((temp_group->rid == current_group->rid) && (strcmp(temp_group->domain_name, current_group->domain_name) == 0)) { done_group = 1; } } if (done_group) goto cleanup; /* Lookup group membership for the current group */ if (current_group->name_type == SID_NAME_DOM_GRP) { if (!winbindd_lookup_groupmem(current_group->domain, current_group->rid, &num_names, &rid_mem, &names, &name_types)) { DEBUG(1, ("fill_grent_mem(): could not lookup membership " "for group rid %d in domain %s\n", current_group->rid, current_group->domain->name)); /* Exit if we cannot lookup the membership for the group this function was called to look at */ if (current_group->rid == group_rid) { return False; } else { goto cleanup; } } } if (current_group->name_type == SID_NAME_ALIAS) { if (!winbindd_lookup_aliasmem(current_group->domain, current_group->rid, &num_names, &sids, &names, &name_types)) { DEBUG(1, ("fill_grent_mem(): group rid %d not a local group\n", group_rid)); /* Exit if we cannot lookup the membership for the group this function was called to look at */ if (current_group->rid == group_rid) { return False; } else { goto cleanup; } } } /* Now for each member of the group, add it to the group list if it is a user, otherwise push it onto the todo_group list if it is a group or an alias. */ for (i = 0; i < num_names; i++) { enum SID_NAME_USE name_type; fstring name_part1, name_part2; char *name_dom, *name_user, *the_name; struct winbindd_domain *name_domain; /* Lookup name */ ZERO_STRUCT(name_part1); ZERO_STRUCT(name_part2); the_name = names[i]; parse_domain_user(the_name, name_part1, name_part2); if (strcmp(name_part1, "") != 0) { name_dom = name_part1; name_user = name_part2; if ((name_domain = find_domain_from_name(name_dom)) == NULL) { DEBUG(0, ("unable to look up domain record for domain " "%s\n", name_dom)); continue; } } else { name_dom = current_group->domain->name; name_user = name_part2; name_domain = current_group->domain; } if (winbindd_lookup_sid_by_name(name_domain, name_user, NULL, &name_type) == WINBINDD_OK) { /* Check name type */ if (name_type == SID_NAME_USER) { struct grent_mem_list *entry; /* Add to group membership list */ if ((entry = (struct grent_mem_list *) malloc(sizeof(*entry))) != NULL) { /* Create name */ slprintf(entry->name, sizeof(entry->name), "%s%s%s", name_dom, lp_winbind_separator(), name_user); /* Add to list */ DLIST_ADD(groupmem_list, entry); gr->num_gr_mem++; } } else { struct grent_mem_group *todo_group; DOM_SID todo_sid; uint32 todo_rid; /* Add group to todo list */ if (winbindd_lookup_sid_by_name(name_domain, names[i], &todo_sid, &name_type) == WINBINDD_OK) { /* Fill in group entry */ sid_split_rid(&todo_sid, &todo_rid); if ((todo_group = (struct grent_mem_group *) malloc(sizeof(*todo_group))) != NULL) { ZERO_STRUCTP(todo_group); todo_group->rid = todo_rid; todo_group->name_type = name_type; todo_group->domain = name_domain; fstrcpy(todo_group->domain_name, name_dom); DLIST_ADD(todo_groups, todo_group); } } } } } cleanup: /* Remove group from todo list and add to done_groups list */ DLIST_REMOVE(todo_groups, current_group); DLIST_ADD(done_groups, current_group); /* Free memory allocated in winbindd_lookup_{alias,group}mem() */ safe_free(name_types); safe_free(rid_mem); free_char_array(num_names, names); free_sid_array(num_names, sids); } /* Free done groups list */ temp_group = done_groups; if (temp_group != NULL) { while (temp_group != NULL) { struct grent_mem_group *next; DLIST_REMOVE(done_groups, temp_group); next = temp_group->next; free(temp_group); temp_group = next; } } /* Remove duplicates from group member list. */ if (gr->num_gr_mem > 0) { struct grent_mem_list *sorted_groupmem_list, *temp; int extra_data_len = 0; fstring prev_name; char *head; /* Sort list */ sorted_groupmem_list = sort_groupmem_list(groupmem_list, gr->num_gr_mem); /* Remove duplicates by iteration */ fstrcpy(prev_name, ""); for(temp = sorted_groupmem_list; temp; temp = temp->next) { if (strequal(temp->name, prev_name)) { /* Got a duplicate name - delete it. Don't panic as we're only adjusting the prev and next pointers so memory allocation is not messed up. */ DLIST_REMOVE(sorted_groupmem_list, temp); gr->num_gr_mem--; } else { /* Got a unique name - count how long it is */ extra_data_len += strlen(temp->name) + 1; } } extra_data_len++; /* Don't forget null a terminator */ /* Convert sorted list into extra data field to send back to ntdom client. Add one to extra_data_len for null termination */ if ((response->extra_data = malloc(extra_data_len))) { /* Initialise extra data */ memset(response->extra_data, 0, extra_data_len); head = response->extra_data; /* Fill in extra data */ for(temp = sorted_groupmem_list; temp; temp = temp->next) { int len = strlen(temp->name) + 1; safe_strcpy(head, temp->name, len); head[len - 1] = ','; head += len; } *head = '\0'; /* Update response length */ response->length = sizeof(struct winbindd_response) + extra_data_len; } /* Free memory for sorted_groupmem_list. It was allocated as an array in sort_groupmem_list() so can be freed in one go. */ free(sorted_groupmem_list); /* Free groupmem_list */ temp = groupmem_list; while (temp != NULL) { struct grent_mem_list *next; DLIST_REMOVE(groupmem_list, temp); next = temp->next; free(temp); temp = next; } } return True; } /* Return a group structure from a group name */ enum winbindd_result winbindd_getgrnam_from_group(struct winbindd_cli_state *state) { DOM_SID group_sid; struct winbindd_domain *domain; enum SID_NAME_USE name_type; uint32 group_rid; fstring name_domain, name_group, name; char *tmp; gid_t gid; int extra_data_len; /* Parse domain and groupname */ memset(name_group, 0, sizeof(fstring)); tmp = state->request.data.groupname; parse_domain_user(tmp, name_domain, name_group); /* Reject names that don't have a domain - i.e name_domain contains the entire name. */ if (strequal(name_group, "")) { return WINBINDD_ERROR; } /* Get info for the domain */ if ((domain = find_domain_from_name(name_domain)) == NULL) { DEBUG(0, ("getgrname_from_group(): could not get domain sid for " "domain %s\n", name_domain)); return WINBINDD_ERROR; } /* Check for cached user entry */ if (winbindd_fetch_group_cache_entry(name_domain, name_group, &state->response.data.gr, &state->response.extra_data, &extra_data_len)) { state->response.length += extra_data_len; return WINBINDD_OK; } slprintf(name, sizeof(name), "%s\\%s", name_domain, name_group); /* Get rid and name type from name */ if (!winbindd_lookup_sid_by_name(domain, name, &group_sid, &name_type)) { DEBUG(1, ("group %s in domain %s does not exist\n", name_group, name_domain)); return WINBINDD_ERROR; } if ((name_type != SID_NAME_ALIAS) && (name_type != SID_NAME_DOM_GRP)) { DEBUG(1, ("from_group: name '%s' is not a local or domain group: %d\n", name_group, name_type)); return WINBINDD_ERROR; } /* Fill in group structure */ sid_split_rid(&group_sid, &group_rid); if (!winbindd_idmap_get_gid_from_rid(domain->name, group_rid, &gid)) { DEBUG(1, ("error sursing unix gid for sid\n")); return WINBINDD_ERROR; } winbindd_fill_grent(&state->response.data.gr, state->request.data.groupname, gid); if (!winbindd_fill_grent_mem(domain, group_rid, name_type, &state->response)) { return WINBINDD_ERROR; } /* Update cached group info */ winbindd_fill_group_cache_entry(name_domain, name_group, &state->response.data.gr, state->response.extra_data, state->response.length - sizeof(struct winbindd_response)); return WINBINDD_OK; } /* Return a group structure from a gid number */ enum winbindd_result winbindd_getgrnam_from_gid(struct winbindd_cli_state *state) { struct winbindd_domain *domain; DOM_SID group_sid; enum SID_NAME_USE name_type; fstring group_name; uint32 group_rid; int extra_data_len; /* Get rid from gid */ if (!winbindd_idmap_get_rid_from_gid(state->request.data.gid, &group_rid, &domain)) { DEBUG(1, ("Could not convert gid %d to rid\n", state->request.data.gid)); return WINBINDD_ERROR; } /* try a cached entry */ if (winbindd_fetch_gid_cache_entry(domain->name, state->request.data.gid, &state->response.data.gr, &state->response.extra_data, &extra_data_len)) { state->response.length += extra_data_len; return WINBINDD_OK; } /* Get sid from gid */ sid_copy(&group_sid, &domain->sid); sid_append_rid(&group_sid, group_rid); if (!winbindd_lookup_name_by_sid(domain, &group_sid, group_name, &name_type)) { DEBUG(1, ("Could not lookup sid\n")); return WINBINDD_ERROR; } if (strcmp(lp_winbind_separator(),"\\")) { string_sub(group_name, "\\", lp_winbind_separator(), sizeof(fstring)); } if (!((name_type == SID_NAME_ALIAS) || (name_type == SID_NAME_DOM_GRP))) { DEBUG(1, ("from_gid: name '%s' is not a local or domain group: %d\n", group_name, name_type)); return WINBINDD_ERROR; } /* Fill in group structure */ winbindd_fill_grent(&state->response.data.gr, group_name, state->request.data.gid); if (!winbindd_fill_grent_mem(domain, group_rid, name_type, &state->response)) { return WINBINDD_ERROR; } /* Update cached group info */ winbindd_fill_gid_cache_entry(domain->name, state->request.data.gid, &state->response.data.gr, state->response.extra_data, state->response.length - sizeof(struct winbindd_response)); return WINBINDD_OK; } /* * set/get/endgrent functions */ /* "Rewind" file pointer for group database enumeration */ enum winbindd_result winbindd_setgrent(struct winbindd_cli_state *state) { struct winbindd_domain *tmp; if (state == NULL) return WINBINDD_ERROR; /* Free old static data if it exists */ if (state->getgrent_state != NULL) { free_getent_state(state->getgrent_state); state->getgrent_state = NULL; } /* Create sam pipes for each domain we know about */ for (tmp = domain_list; tmp != NULL; tmp = tmp->next) { struct getent_state *domain_state; /* Skip domains other than WINBINDD_DOMAIN environment variable */ if ((strcmp(state->request.domain, "") != 0) && (strcmp(state->request.domain, tmp->name) != 0)) { continue; } /* Create a state record for this domain */ if ((domain_state = (struct getent_state *) malloc(sizeof(struct getent_state))) == NULL) { return WINBINDD_ERROR; } ZERO_STRUCTP(domain_state); /* Add to list of open domains */ domain_state->domain = tmp; DLIST_ADD(state->getgrent_state, domain_state); } return WINBINDD_OK; } /* Close file pointer to ntdom group database */ enum winbindd_result winbindd_endgrent(struct winbindd_cli_state *state) { if (state == NULL) return WINBINDD_ERROR; free_getent_state(state->getgrent_state); state->getgrent_state = NULL; return WINBINDD_OK; } /* Fetch next group entry from netdom database */ enum winbindd_result winbindd_getgrent(struct winbindd_cli_state *state) { if (state == NULL) return WINBINDD_ERROR; /* Process the current head of the getent_state list */ while(state->getgrent_state != NULL) { struct getent_state *ent = state->getgrent_state; /* Get list of entries if we haven't already got them */ if (!ent->got_sam_entries) { uint32 status, start_ndx = 0, start_ndx2 = 0; if (!winbindd_fetch_group_cache(ent->domain->name, &ent->sam_entries, &ent->num_sam_entries)) { /* Fetch group entries */ if (!domain_handles_open(ent->domain)) goto cleanup; /* Enumerate domain groups */ do { status = samr_enum_dom_groups(&ent->domain->sam_dom_handle, &start_ndx, 0x100000, &ent->sam_entries, &ent->num_sam_entries); } while (status == STATUS_MORE_ENTRIES); /* Enumerate domain aliases */ do { status = samr_enum_dom_aliases(&ent->domain->sam_dom_handle, &start_ndx2, 0x100000, &ent->sam_entries, &ent->num_sam_entries); } while (status == STATUS_MORE_ENTRIES); /* Fill cache with received entries */ winbindd_fill_group_cache(ent->domain->name, ent->sam_entries, ent->num_sam_entries); } ent->got_sam_entries = True; } /* Send back a group */ while (ent->sam_entry_index < ent->num_sam_entries) { enum winbindd_result result; fstring domain_group_name; char *group_name = (ent->sam_entries) [ent->sam_entry_index].acct_name; /* Prepend domain to name */ slprintf(domain_group_name, sizeof(domain_group_name), "%s%s%s", ent->domain->name, lp_winbind_separator(), group_name); /* Get group entry from group name */ fstrcpy(state->request.data.groupname, domain_group_name); result = winbindd_getgrnam_from_group(state); ent->sam_entry_index++; if (result == WINBINDD_OK) { return result; } /* Try next group */ DEBUG(1, ("could not getgrnam_from_group for group name %s\n", domain_group_name)); } /* We've exhausted all users for this pipe - close it down and start on the next one. */ cleanup: /* Free mallocated memory for sam entries. The data stored here may have been allocated from the cache. */ if (ent->sam_entries != NULL) free(ent->sam_entries); ent->sam_entries = NULL; /* Free state information for this domain */ { struct getent_state *old_ent; old_ent = state->getgrent_state; DLIST_REMOVE(state->getgrent_state, state->getgrent_state); free(old_ent); } } /* Out of pipes so we're done */ return WINBINDD_ERROR; }