/*
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 0213 9 , 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 ) - 1 ,
" %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 ) - 1 , " %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 ) - 1 ,
" %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 ;
}