we need the Deleted Objects container for replication
[metze/samba/wip.git] / source3 / winbindd / winbindd_group.c
1 /* 
2    Unix SMB/CIFS implementation.
3
4    Winbind daemon for ntdom nss module
5
6    Copyright (C) Tim Potter 2000
7    Copyright (C) Jeremy Allison 2001.
8    Copyright (C) Gerald (Jerry) Carter 2003.
9    Copyright (C) Volker Lendecke 2005
10    
11    This program is free software; you can redistribute it and/or modify
12    it under the terms of the GNU General Public License as published by
13    the Free Software Foundation; either version 3 of the License, or
14    (at your option) any later version.
15    
16    This program is distributed in the hope that it will be useful,
17    but WITHOUT ANY WARRANTY; without even the implied warranty of
18    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19    GNU General Public License for more details.
20    
21    You should have received a copy of the GNU General Public License
22    along with this program.  If not, see <http://www.gnu.org/licenses/>.
23 */
24
25 #include "includes.h"
26 #include "winbindd.h"
27
28 #undef DBGC_CLASS
29 #define DBGC_CLASS DBGC_WINBIND
30
31 static void add_member(const char *domain, const char *user,
32            char **pp_members, size_t *p_num_members)
33 {
34         fstring name;
35
36         if (domain != NULL) {
37                 fill_domain_username(name, domain, user, True);
38         } else {
39                 fstrcpy(name, user);
40         }
41         safe_strcat(name, ",", sizeof(name)-1);
42         string_append(pp_members, name);
43         *p_num_members += 1;
44 }
45
46 /**********************************************************************
47  Add member users resulting from sid. Expand if it is a domain group.
48 **********************************************************************/
49
50 static void add_expanded_sid(const DOM_SID *sid,
51                              char **pp_members,
52                              size_t *p_num_members)
53 {
54         DOM_SID dom_sid;
55         uint32 rid;
56         struct winbindd_domain *domain;
57         size_t i;
58
59         char *domain_name = NULL;
60         char *name = NULL;
61         enum lsa_SidType type;
62
63         uint32 num_names;
64         DOM_SID *sid_mem;
65         char **names;
66         uint32 *types;
67
68         NTSTATUS result;
69
70         TALLOC_CTX *mem_ctx = talloc_init("add_expanded_sid");
71
72         if (mem_ctx == NULL) {
73                 DEBUG(1, ("talloc_init failed\n"));
74                 return;
75         }
76
77         sid_copy(&dom_sid, sid);
78         sid_split_rid(&dom_sid, &rid);
79
80         domain = find_lookup_domain_from_sid(sid);
81
82         if (domain == NULL) {
83                 DEBUG(3, ("Could not find domain for sid %s\n",
84                           sid_string_dbg(sid)));
85                 goto done;
86         }
87
88         result = domain->methods->sid_to_name(domain, mem_ctx, sid,
89                                               &domain_name, &name, &type);
90
91         if (!NT_STATUS_IS_OK(result)) {
92                 DEBUG(3, ("sid_to_name failed for sid %s\n",
93                           sid_string_dbg(sid)));
94                 goto done;
95         }
96
97         DEBUG(10, ("Found name %s, type %d\n", name, type));
98
99         if (type == SID_NAME_USER) {
100                 add_member(domain_name, name, pp_members, p_num_members);
101                 goto done;
102         }
103
104         if (type != SID_NAME_DOM_GRP) {
105                 DEBUG(10, ("Alias member %s neither user nor group, ignore\n",
106                            name));
107                 goto done;
108         }
109
110         /* Expand the domain group, this must be done via the target domain */
111
112         domain = find_domain_from_sid(sid);
113
114         if (domain == NULL) {
115                 DEBUG(3, ("Could not find domain from SID %s\n",
116                           sid_string_dbg(sid)));
117                 goto done;
118         }
119
120         result = domain->methods->lookup_groupmem(domain, mem_ctx,
121                                                   sid, &num_names,
122                                                   &sid_mem, &names,
123                                                   &types);
124
125         if (!NT_STATUS_IS_OK(result)) {
126                 DEBUG(10, ("Could not lookup group members for %s: %s\n",
127                            name, nt_errstr(result)));
128                 goto done;
129         }
130
131         for (i=0; i<num_names; i++) {
132                 DEBUG(10, ("Adding group member SID %s\n",
133                            sid_string_dbg(&sid_mem[i])));
134
135                 if (types[i] != SID_NAME_USER) {
136                         DEBUG(1, ("Hmmm. Member %s of group %s is no user. "
137                                   "Ignoring.\n", names[i], name));
138                         continue;
139                 }
140
141                 add_member(NULL, names[i], pp_members, p_num_members);
142         }
143
144  done:
145         talloc_destroy(mem_ctx);
146         return;
147 }
148
149 static bool fill_passdb_alias_grmem(struct winbindd_domain *domain,
150                                     DOM_SID *group_sid, size_t *num_gr_mem,
151                                     char **gr_mem, size_t *gr_mem_len)
152 {
153         DOM_SID *members;
154         size_t i, num_members;
155
156         *num_gr_mem = 0;
157         *gr_mem = NULL;
158         *gr_mem_len = 0;
159
160         if (!NT_STATUS_IS_OK(pdb_enum_aliasmem(group_sid, talloc_tos(),
161                                                &members, &num_members)))
162                 return True;
163
164         for (i=0; i<num_members; i++) {
165                 add_expanded_sid(&members[i], gr_mem, num_gr_mem);
166         }
167
168         TALLOC_FREE(members);
169
170         if (*gr_mem != NULL) {
171                 size_t len;
172
173                 /* We have at least one member, strip off the last "," */
174                 len = strlen(*gr_mem);
175                 (*gr_mem)[len-1] = '\0';
176                 *gr_mem_len = len;
177         }
178
179         return True;
180 }
181
182 /* Fill a grent structure from various other information */
183
184 bool fill_grent(TALLOC_CTX *mem_ctx, struct winbindd_gr *gr,
185                 const char *dom_name, const char *gr_name, gid_t unix_gid)
186 {
187         fstring full_group_name;
188         char *mapped_name = NULL;
189         struct winbindd_domain *domain = find_domain_from_name_noinit(dom_name);
190         NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
191
192         nt_status = normalize_name_map(mem_ctx, domain, gr_name,
193                                        &mapped_name);
194
195         /* Basic whitespace replacement */
196         if (NT_STATUS_IS_OK(nt_status)) {
197                 fill_domain_username(full_group_name, dom_name,
198                                      mapped_name, true);
199         }
200         /* Mapped to an aliase */
201         else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_FILE_RENAMED)) {
202                 fstrcpy(full_group_name, mapped_name);
203         }
204         /* no change */
205         else {
206                 fill_domain_username( full_group_name, dom_name,
207                                       gr_name, True );
208         }
209
210         gr->gr_gid = unix_gid;
211
212         /* Group name and password */
213
214         safe_strcpy(gr->gr_name, full_group_name, sizeof(gr->gr_name) - 1);
215         safe_strcpy(gr->gr_passwd, "x", sizeof(gr->gr_passwd) - 1);
216
217         return True;
218 }
219
220 /***********************************************************************
221  If "enum users" is set to false, and the group being looked
222  up is the Domain Users SID: S-1-5-domain-513, then for the
223  list of members check if the querying user is in that group,
224  and if so only return that user as the gr_mem array.
225  We can change this to a different parameter than "enum users"
226  if neccessaey, or parameterize the group list we do this for.
227 ***********************************************************************/
228
229 static bool fill_grent_mem_domusers( TALLOC_CTX *mem_ctx,
230                                      struct winbindd_domain *domain,
231                                      struct winbindd_cli_state *state,
232                                      DOM_SID *group_sid,
233                                      enum lsa_SidType group_name_type,
234                                      size_t *num_gr_mem, char **gr_mem,
235                                      size_t *gr_mem_len)
236 {
237         DOM_SID querying_user_sid;
238         DOM_SID *pquerying_user_sid = NULL;
239         uint32 num_groups = 0;
240         DOM_SID *user_sids = NULL;
241         bool u_in_group = False;
242         NTSTATUS status;
243         int i;
244         unsigned int buf_len = 0;
245         char *buf = NULL;
246
247         DEBUG(10,("fill_grent_mem_domain_users: domain %s\n",
248                   domain->name ));
249
250         if (state) {
251                 uid_t ret_uid = (uid_t)-1;
252                 if (sys_getpeereid(state->sock, &ret_uid)==0) {
253                         /* We know who's asking - look up their SID if
254                            it's one we've mapped before. */
255                         status = idmap_uid_to_sid(domain->name,
256                                                   &querying_user_sid, ret_uid);
257                         if (NT_STATUS_IS_OK(status)) {
258                                 pquerying_user_sid = &querying_user_sid;
259                                 DEBUG(10,("fill_grent_mem_domain_users: "
260                                           "querying uid %u -> %s\n",
261                                           (unsigned int)ret_uid,
262                                           sid_string_dbg(pquerying_user_sid)));
263                         }
264                 }
265         }
266
267         /* Only look up if it was a winbindd user in this domain. */
268         if (pquerying_user_sid &&
269             (sid_compare_domain(pquerying_user_sid, &domain->sid) == 0)) {
270
271                 DEBUG(10,("fill_grent_mem_domain_users: querying user = %s\n",
272                           sid_string_dbg(pquerying_user_sid) ));
273
274                 status = domain->methods->lookup_usergroups(domain,
275                                                             mem_ctx,
276                                                             pquerying_user_sid,
277                                                             &num_groups,
278                                                             &user_sids);
279                 if (!NT_STATUS_IS_OK(status)) {
280                         DEBUG(1, ("fill_grent_mem_domain_users: "
281                                   "lookup_usergroups failed "
282                                   "for sid %s in domain %s (error: %s)\n",
283                                   sid_string_dbg(pquerying_user_sid),
284                                   domain->name,
285                                   nt_errstr(status)));
286                         return False;
287                 }
288
289                 for (i = 0; i < num_groups; i++) {
290                         if (sid_equal(group_sid, &user_sids[i])) {
291                                 /* User is in Domain Users, add their name
292                                    as the only group member. */
293                                 u_in_group = True;
294                                 break;
295                         }
296                 }
297         }
298
299         if (u_in_group) {
300                 size_t len = 0;
301                 char *domainname = NULL;
302                 char *username = NULL;
303                 fstring name;
304                 char *mapped_name = NULL;
305                 enum lsa_SidType type;
306                 struct winbindd_domain *target_domain = NULL;
307                 NTSTATUS name_map_status = NT_STATUS_UNSUCCESSFUL;
308
309                 DEBUG(10,("fill_grent_mem_domain_users: "
310                           "sid %s in 'Domain Users' in domain %s\n",
311                           sid_string_dbg(pquerying_user_sid),
312                           domain->name ));
313
314                 status = domain->methods->sid_to_name(domain, mem_ctx,
315                                                       pquerying_user_sid,
316                                                       &domainname,
317                                                       &username,
318                                                       &type);
319                 if (!NT_STATUS_IS_OK(status)) {
320                         DEBUG(1, ("could not lookup username for user "
321                                   "sid %s in domain %s (error: %s)\n",
322                                   sid_string_dbg(pquerying_user_sid),
323                                   domain->name,
324                                   nt_errstr(status)));
325                         return False;
326                 }
327
328                 target_domain = find_domain_from_name_noinit(domainname);
329                 name_map_status = normalize_name_map(mem_ctx, target_domain,
330                                                      username, &mapped_name);
331
332                 /* Basic whitespace replacement */
333                 if (NT_STATUS_IS_OK(name_map_status)) {
334                         fill_domain_username(name, domainname, mapped_name, true);
335                 }
336                 /* Mapped to an alias */
337                 else if (NT_STATUS_EQUAL(name_map_status, NT_STATUS_FILE_RENAMED)) {
338                         fstrcpy(name, mapped_name);
339                 }
340                 /* no mapping done...use original name */
341                 else {
342                         fill_domain_username(name, domainname, username, true);
343                 }
344
345                 len = strlen(name);
346                 buf_len = len + 1;
347                 if (!(buf = (char *)SMB_MALLOC(buf_len))) {
348                         DEBUG(1, ("out of memory\n"));
349                         return False;
350                 }
351                 memcpy(buf, name, buf_len);
352
353                 DEBUG(10,("fill_grent_mem_domain_users: user %s in "
354                           "'Domain Users' in domain %s\n",
355                           name, domain->name ));
356
357                 /* user is the only member */
358                 *num_gr_mem = 1;
359         }
360
361         *gr_mem = buf;
362         *gr_mem_len = buf_len;
363
364         DEBUG(10, ("fill_grent_mem_domain_users: "
365                    "num_mem = %u, len = %u, mem = %s\n",
366                    (unsigned int)*num_gr_mem,
367                    (unsigned int)buf_len, *num_gr_mem ? buf : "NULL"));
368
369         return True;
370 }
371
372 /***********************************************************************
373  Add names to a list.  Assumes  a canonical version of the string
374  in DOMAIN\user
375 ***********************************************************************/
376
377 static int namecmp( const void *a, const void *b )
378 {
379         return StrCaseCmp( * (char * const *) a, * (char * const *) b);
380 }
381
382 static void sort_unique_list(char ***list, uint32 *n_list)
383 {
384         uint32_t i;
385
386         /* search for duplicates for sorting and looking for matching
387            neighbors */
388
389         qsort(*list, *n_list, sizeof(char*), QSORT_CAST namecmp);
390
391         for (i=1; i < *n_list; i++) {
392                 if (strcmp((*list)[i-1], (*list)[i]) == 0) {
393                         memmove(&((*list)[i-1]), &((*list)[i]),
394                                  sizeof(char*)*((*n_list)-i));
395                         (*n_list)--;
396                 }
397         }
398 }
399
400 static NTSTATUS add_names_to_list( TALLOC_CTX *ctx,
401                                    char ***list, uint32 *n_list,
402                                    char **names, uint32 n_names )
403 {
404         char **new_list = NULL;
405         uint32 n_new_list = 0;
406         int i, j;
407
408         if ( !names || (n_names == 0) )
409                 return NT_STATUS_OK;
410
411         /* Alloc the maximum size we'll need */
412
413         if ( *list == NULL ) {
414                 if ((new_list = TALLOC_ARRAY(ctx, char *, n_names)) == NULL) {
415                         return NT_STATUS_NO_MEMORY;
416                 }
417                 n_new_list = n_names;
418         } else {
419                 new_list = TALLOC_REALLOC_ARRAY( ctx, *list, char *,
420                                                  (*n_list) + n_names );
421                 if ( !new_list )
422                         return NT_STATUS_NO_MEMORY;
423                 n_new_list = (*n_list) + n_names;
424         }
425
426         /* Add all names */
427
428         for ( i=*n_list, j=0; i<n_new_list; i++, j++ ) {
429                 new_list[i] = talloc_strdup( new_list, names[j] );
430         }
431
432         *list = new_list;
433         *n_list = n_new_list;
434
435         return NT_STATUS_OK;
436 }
437
438 /***********************************************************************
439 ***********************************************************************/
440
441 static NTSTATUS expand_groups( TALLOC_CTX *ctx,
442                                struct winbindd_domain *d,
443                                DOM_SID *glist, uint32 n_glist,
444                                DOM_SID **new_glist, uint32 *n_new_glist,
445                                char ***members, uint32 *n_members )
446 {
447         int i, j;
448         NTSTATUS status = NT_STATUS_OK;
449         uint32 num_names = 0;
450         uint32 *name_types = NULL;
451         char **names = NULL;
452         DOM_SID *sid_mem = NULL;
453         TALLOC_CTX *tmp_ctx = NULL;
454         DOM_SID *new_groups = NULL;
455         size_t new_groups_size = 0;
456
457         *members = NULL;
458         *n_members = 0;
459         *new_glist = NULL;
460         *n_new_glist = 0;
461
462         DEBUG(10,("expand_groups:\n"));
463
464         for ( i=0; i<n_glist; i++ ) {
465
466                 NTSTATUS lookup_status;
467
468                 tmp_ctx = talloc_new( ctx );
469
470                 /* Lookup the group membership */
471
472                 lookup_status = d->methods->lookup_groupmem(d, tmp_ctx,
473                                                      &glist[i], &num_names,
474                                                      &sid_mem, &names,
475                                                      &name_types);
476                 if (!NT_STATUS_IS_OK(lookup_status)) {
477                         DEBUG(10,("expand_groups: lookup_groupmem for "
478                                 "sid %s failed with: %s\n",
479                                 sid_string_dbg(&glist[i]),
480                                 nt_errstr(lookup_status)));
481
482                         /* we might have hit a logic error when called for an
483                          * alias, in that case just continue with group
484                          * expansion - Guenther */
485
486                         if (NT_STATUS_EQUAL(lookup_status, NT_STATUS_NO_SUCH_GROUP)) {
487                                 continue;
488                         }
489                         status = lookup_status;
490                         goto out;
491                 }
492
493                 /* Separate users and groups into two lists */
494
495                 for ( j=0; j<num_names; j++ ) {
496
497                         /* Users */
498                         if ( name_types[j] == SID_NAME_USER ||
499                              name_types[j] == SID_NAME_COMPUTER )
500                         {
501                                 status = add_names_to_list( ctx, members,
502                                                             n_members,
503                                                             names+j, 1 );
504                                 if ( !NT_STATUS_IS_OK(status) )
505                                         goto out;
506
507                                 continue;
508                         }
509
510                         /* Groups */
511                         if ( name_types[j] == SID_NAME_DOM_GRP ||
512                              name_types[j] == SID_NAME_ALIAS )
513                         {
514                                 status = add_sid_to_array_unique(ctx,
515                                                                  &sid_mem[j],
516                                                                  &new_groups,
517                                                                  &new_groups_size);
518                                 if (!NT_STATUS_IS_OK(status)) {
519                                         goto out;
520                                 }
521
522                                 continue;
523                         }
524                 }
525
526                 TALLOC_FREE( tmp_ctx );
527         }
528
529         *new_glist = new_groups;
530         *n_new_glist = (uint32)new_groups_size;
531
532  out:
533         TALLOC_FREE( tmp_ctx );
534
535         if (!NT_STATUS_IS_OK(status)) {
536                 DEBUG(10,("expand_groups: returning with %s\n",
537                         nt_errstr(status)));
538         }
539
540         return status;
541 }
542
543 /***********************************************************************
544  Fill in the group membership field of a NT group given by group_sid
545 ***********************************************************************/
546
547 static bool fill_grent_mem(struct winbindd_domain *domain,
548                            struct winbindd_cli_state *state,
549                            DOM_SID *group_sid,
550                            enum lsa_SidType group_name_type,
551                            size_t *num_gr_mem, char **gr_mem,
552                            size_t *gr_mem_len)
553 {
554         uint32 num_names = 0;
555         unsigned int buf_len = 0, buf_ndx = 0, i;
556         char **names = NULL, *buf = NULL;
557         bool result = False;
558         TALLOC_CTX *mem_ctx;
559         uint32 group_rid;
560         DOM_SID *glist = NULL;
561         DOM_SID *new_glist = NULL;
562         uint32 n_glist, n_new_glist;
563         int max_depth = lp_winbind_expand_groups();
564
565         if (!(mem_ctx = talloc_init("fill_grent_mem(%s)", domain->name)))
566                 return False;
567
568         DEBUG(10, ("group SID %s\n", sid_string_dbg(group_sid)));
569
570         /* Initialize with no members */
571
572         *num_gr_mem = 0;
573
574         /* HACK ALERT!! This whole routine does not cope with group members
575          * from more than one domain, ie aliases. Thus we have to work it out
576          * ourselves in a special routine. */
577
578         if (domain->internal) {
579                 result = fill_passdb_alias_grmem(domain, group_sid,
580                                                num_gr_mem,
581                                                gr_mem, gr_mem_len);
582                 goto done;
583         }
584
585         /* Verify name type */
586
587         if ( !((group_name_type==SID_NAME_DOM_GRP) ||
588                ((group_name_type==SID_NAME_ALIAS) && domain->primary)) )
589         {
590                 DEBUG(1, ("SID %s in domain %s isn't a domain group (%d)\n",
591                           sid_string_dbg(group_sid),
592                           domain->name, group_name_type));
593                 goto done;
594         }
595
596         /* OPTIMIZATION / HACK. See comment in
597            fill_grent_mem_domusers() */
598
599         sid_peek_rid( group_sid, &group_rid );
600         if (!lp_winbind_enum_users() && group_rid == DOMAIN_GROUP_RID_USERS) {
601                 result = fill_grent_mem_domusers( mem_ctx, domain, state,
602                                                   group_sid, group_name_type,
603                                                   num_gr_mem, gr_mem,
604                                                   gr_mem_len );
605                 goto done;
606         }
607
608         /* Real work goes here.  Create a list of group names to
609            expand starting with the initial one.  Pass that to
610            expand_groups() which returns a list of more group names
611            to expand.  Do this up to the max search depth. */
612
613         if ( (glist = TALLOC_ARRAY(mem_ctx, DOM_SID, 1 )) == NULL ) {
614                 result = False;
615                 DEBUG(0,("fill_grent_mem: talloc failure!\n"));
616                 goto done;
617         }
618         sid_copy( &glist[0], group_sid );
619         n_glist = 1;
620
621         for ( i=0; i<max_depth && glist; i++ ) {
622                 uint32 n_members = 0;
623                 char **members = NULL;
624                 NTSTATUS nt_status;
625                 int j;
626
627                 nt_status = expand_groups( mem_ctx, domain,
628                                            glist, n_glist,
629                                            &new_glist, &n_new_glist,
630                                            &members, &n_members);
631                 if ( !NT_STATUS_IS_OK(nt_status) ) {
632                         result = False;
633                         goto done;
634                 }
635
636                 /* Add new group members to list.  Pass through the
637                    alias mapping function */
638
639                 for (j=0; j<n_members; j++) {
640                         fstring name_domain, name_acct;
641                         fstring qualified_name;
642                         char *mapped_name = NULL;
643                         NTSTATUS name_map_status = NT_STATUS_UNSUCCESSFUL;
644                         struct winbindd_domain *target_domain = NULL;
645
646                         if (parse_domain_user(members[j], name_domain, name_acct)) {
647                                 target_domain = find_domain_from_name_noinit(name_domain);
648                                 /* NOW WHAT ? */
649                         }
650                         if (!target_domain) {
651                                 target_domain = domain;
652                         }
653
654                         name_map_status = normalize_name_map(members, target_domain,
655                                                              name_acct, &mapped_name);
656
657                         /* Basic whitespace replacement */
658                         if (NT_STATUS_IS_OK(name_map_status)) {
659                                 fill_domain_username(qualified_name, name_domain,
660                                                      mapped_name, true);
661                                 mapped_name = qualified_name;
662                         }
663                         /* no mapping at all */
664                         else if (!NT_STATUS_EQUAL(name_map_status, NT_STATUS_FILE_RENAMED)) {
665                                 mapped_name = members[j];
666                         }
667
668                         nt_status = add_names_to_list( mem_ctx, &names,
669                                                        &num_names,
670                                                        &mapped_name, 1);
671                         if ( !NT_STATUS_IS_OK(nt_status) ) {
672                                 result = False;
673                                 goto done;
674                         }
675                 }
676
677                 TALLOC_FREE( members );
678
679                 /* If we have no more groups to expand, break out
680                    early */
681
682                 if (new_glist == NULL)
683                         break;
684
685                 /* One more round */
686                 TALLOC_FREE(glist);
687                 glist = new_glist;
688                 n_glist = n_new_glist;
689         }
690         TALLOC_FREE( glist );
691
692         sort_unique_list(&names, &num_names);
693
694         DEBUG(10, ("looked up %d names\n", num_names));
695
696  again:
697         /* Add members to list */
698
699         for (i = 0; i < num_names; i++) {
700                 int len;
701
702                 DEBUG(10, ("processing name %s\n", names[i]));
703
704                 len = strlen(names[i]);
705
706                 /* Add to list or calculate buffer length */
707
708                 if (!buf) {
709                         buf_len += len + 1; /* List is comma separated */
710                         (*num_gr_mem)++;
711                         DEBUG(10, ("buf_len + %d = %d\n", len + 1, buf_len));
712                 } else {
713                         DEBUG(10, ("appending %s at ndx %d\n",
714                                    names[i], buf_ndx));
715                         parse_add_domuser(&buf[buf_ndx], names[i], &len);
716                         buf_ndx += len;
717                         buf[buf_ndx] = ',';
718                         buf_ndx++;
719                 }
720         }
721
722         /* Allocate buffer */
723
724         if (!buf && buf_len != 0) {
725                 if (!(buf = (char *)SMB_MALLOC(buf_len))) {
726                         DEBUG(1, ("out of memory\n"));
727                         result = False;
728                         goto done;
729                 }
730                 memset(buf, 0, buf_len);
731                 goto again;
732         }
733
734         /* Now we're done */
735
736         if (buf && buf_ndx > 0) {
737                 buf[buf_ndx - 1] = '\0';
738         }
739
740         *gr_mem = buf;
741         *gr_mem_len = buf_len;
742
743         DEBUG(10, ("num_mem = %u, len = %u, mem = %s\n",
744                    (unsigned int)*num_gr_mem,
745                    (unsigned int)buf_len, *num_gr_mem ? buf : "NULL"));
746         result = True;
747
748 done:
749
750         talloc_destroy(mem_ctx);
751
752         DEBUG(10,("fill_grent_mem returning %s\n",
753                 result == true ? "true" : "false"));
754
755         return result;
756 }
757
758 /*
759  * set/get/endgrent functions
760  */
761
762 /* "Rewind" file pointer for group database enumeration */
763
764 static bool winbindd_setgrent_internal(struct winbindd_cli_state *state)
765 {
766         struct winbindd_domain *domain;
767
768         DEBUG(3, ("[%5lu]: setgrent\n", (unsigned long)state->pid));
769
770         /* Check user has enabled this */
771
772         if (!lp_winbind_enum_groups()) {
773                 return False;
774         }
775
776         /* Free old static data if it exists */
777
778         if (state->getgrent_state != NULL) {
779                 free_getent_state(state->getgrent_state);
780                 state->getgrent_state = NULL;
781         }
782
783         /* Create sam pipes for each domain we know about */
784
785         for (domain = domain_list(); domain != NULL; domain = domain->next) {
786                 struct getent_state *domain_state;
787
788                 /* Create a state record for this domain */
789
790                 /* don't add our domaina if we are a PDC or if we
791                    are a member of a Samba domain */
792
793                 if ( lp_winbind_trusted_domains_only() && domain->primary )
794                 {
795                         continue;
796                 }
797
798                 domain_state = SMB_MALLOC_P(struct getent_state);
799                 if (!domain_state) {
800                         DEBUG(1, ("winbindd_setgrent: "
801                                   "malloc failed for domain_state!\n"));
802                         return False;
803                 }
804
805                 ZERO_STRUCTP(domain_state);
806
807                 fstrcpy(domain_state->domain_name, domain->name);
808
809                 /* Add to list of open domains */
810
811                 DLIST_ADD(state->getgrent_state, domain_state);
812         }
813
814         state->getgrent_initialized = True;
815         return True;
816 }
817
818 void winbindd_setgrent(struct winbindd_cli_state *state)
819 {
820         if (winbindd_setgrent_internal(state)) {
821                 request_ok(state);
822         } else {
823                 request_error(state);
824         }
825 }
826
827 /* Close file pointer to ntdom group database */
828
829 void winbindd_endgrent(struct winbindd_cli_state *state)
830 {
831         DEBUG(3, ("[%5lu]: endgrent\n", (unsigned long)state->pid));
832
833         free_getent_state(state->getgrent_state);
834         state->getgrent_initialized = False;
835         state->getgrent_state = NULL;
836         request_ok(state);
837 }
838
839 /* Get the list of domain groups and domain aliases for a domain.  We fill in
840    the sam_entries and num_sam_entries fields with domain group information.
841    Return True if some groups were returned, False otherwise. */
842
843 bool get_sam_group_entries(struct getent_state *ent)
844 {
845         NTSTATUS status;
846         uint32 num_entries;
847         struct acct_info *name_list = NULL;
848         TALLOC_CTX *mem_ctx;
849         bool result = False;
850         struct acct_info *sam_grp_entries = NULL;
851         struct winbindd_domain *domain;
852
853         if (ent->got_sam_entries)
854                 return False;
855
856         if (!(mem_ctx = talloc_init("get_sam_group_entries(%s)",
857                                           ent->domain_name))) {
858                 DEBUG(1, ("get_sam_group_entries: "
859                           "could not create talloc context!\n"));
860                 return False;
861         }
862
863         /* Free any existing group info */
864
865         SAFE_FREE(ent->sam_entries);
866         ent->num_sam_entries = 0;
867         ent->got_sam_entries = True;
868
869         /* Enumerate domain groups */
870
871         num_entries = 0;
872
873         if (!(domain = find_domain_from_name(ent->domain_name))) {
874                 DEBUG(3, ("no such domain %s in get_sam_group_entries\n",
875                           ent->domain_name));
876                 goto done;
877         }
878
879         /* always get the domain global groups */
880
881         status = domain->methods->enum_dom_groups(domain, mem_ctx, &num_entries,
882                                                   &sam_grp_entries);
883
884         if (!NT_STATUS_IS_OK(status)) {
885                 DEBUG(3, ("get_sam_group_entries: "
886                           "could not enumerate domain groups! Error: %s\n",
887                           nt_errstr(status)));
888                 result = False;
889                 goto done;
890         }
891
892         /* Copy entries into return buffer */
893
894         if (num_entries) {
895                 name_list = SMB_MALLOC_ARRAY(struct acct_info, num_entries);
896                 if (!name_list) {
897                         DEBUG(0,("get_sam_group_entries: Failed to malloc "
898                                  "memory for %d domain groups!\n",
899                                  num_entries));
900                         result = False;
901                         goto done;
902                 }
903                 memcpy(name_list, sam_grp_entries,
904                         num_entries * sizeof(struct acct_info));
905         }
906
907         ent->num_sam_entries = num_entries;
908
909         /* get the domain local groups if we are a member of a native win2k
910          * domain and are not using LDAP to get the groups */
911
912         if ( ( lp_security() != SEC_ADS && domain->native_mode
913                 && domain->primary) || domain->internal )
914         {
915                 DEBUG(4,("get_sam_group_entries: %s domain; "
916                          "enumerating local groups as well\n",
917                          domain->native_mode ? "Native Mode 2k":
918                                                 "BUILTIN or local"));
919
920                 status = domain->methods->enum_local_groups(domain, mem_ctx,
921                                                             &num_entries,
922                                                             &sam_grp_entries);
923
924                 if ( !NT_STATUS_IS_OK(status) ) {
925                         DEBUG(3,("get_sam_group_entries: "
926                                 "Failed to enumerate "
927                                 "domain local groups with error %s!\n",
928                                 nt_errstr(status)));
929                         num_entries = 0;
930                 }
931                 else
932                         DEBUG(4,("get_sam_group_entries: "
933                                  "Returned %d local groups\n",
934                                  num_entries));
935
936                 /* Copy entries into return buffer */
937
938                 if ( num_entries ) {
939                         name_list = SMB_REALLOC_ARRAY(name_list,
940                                                       struct acct_info,
941                                                       ent->num_sam_entries+
942                                                         num_entries);
943                         if (!name_list) {
944                                 DEBUG(0,("get_sam_group_entries: "
945                                          "Failed to realloc more memory "
946                                          "for %d local groups!\n",
947                                          num_entries));
948                                 result = False;
949                                 goto done;
950                         }
951
952                         memcpy(&name_list[ent->num_sam_entries],
953                                 sam_grp_entries,
954                                 num_entries * sizeof(struct acct_info));
955                 }
956
957                 ent->num_sam_entries += num_entries;
958         }
959
960
961         /* Fill in remaining fields */
962
963         ent->sam_entries = name_list;
964         ent->sam_entry_index = 0;
965
966         result = (ent->num_sam_entries > 0);
967
968  done:
969         talloc_destroy(mem_ctx);
970
971         return result;
972 }
973
974 /* Fetch next group entry from ntdom database */
975
976 #define MAX_GETGRENT_GROUPS 500
977
978 void winbindd_getgrent(struct winbindd_cli_state *state)
979 {
980         struct getent_state *ent;
981         struct winbindd_gr *group_list = NULL;
982         int num_groups, group_list_ndx, gr_mem_list_len = 0;
983         char *gr_mem_list = NULL;
984
985         DEBUG(3, ("[%5lu]: getgrent\n", (unsigned long)state->pid));
986
987         /* Check user has enabled this */
988
989         if (!lp_winbind_enum_groups()) {
990                 request_error(state);
991                 return;
992         }
993
994         num_groups = MIN(MAX_GETGRENT_GROUPS, state->request->data.num_entries);
995
996         if (num_groups == 0) {
997                 request_error(state);
998                 return;
999         }
1000
1001         group_list = talloc_zero_array(state->mem_ctx, struct winbindd_gr,
1002                                        num_groups);
1003         if (!group_list) {
1004                 request_error(state);
1005                 return;
1006         }
1007         state->response->extra_data.data = group_list;
1008
1009         state->response->data.num_entries = 0;
1010
1011         if (!state->getgrent_initialized)
1012                 winbindd_setgrent_internal(state);
1013
1014         if (!(ent = state->getgrent_state)) {
1015                 request_error(state);
1016                 return;
1017         }
1018
1019         /* Start sending back groups */
1020
1021         for (group_list_ndx = 0; group_list_ndx < num_groups; ) {
1022                 struct acct_info *name_list = NULL;
1023                 fstring domain_group_name;
1024                 uint32 result;
1025                 gid_t group_gid;
1026                 size_t gr_mem_len;
1027                 char *gr_mem;
1028                 DOM_SID group_sid;
1029                 struct winbindd_domain *domain;
1030
1031                 /* Do we need to fetch another chunk of groups? */
1032
1033         tryagain:
1034
1035                 DEBUG(10, ("entry_index = %d, num_entries = %d\n",
1036                            ent->sam_entry_index, ent->num_sam_entries));
1037
1038                 if (ent->num_sam_entries == ent->sam_entry_index) {
1039
1040                         while(ent && !get_sam_group_entries(ent)) {
1041                                 struct getent_state *next_ent;
1042
1043                                 DEBUG(10, ("freeing state info for domain %s\n",
1044                                            ent->domain_name));
1045
1046                                 /* Free state information for this domain */
1047
1048                                 SAFE_FREE(ent->sam_entries);
1049
1050                                 next_ent = ent->next;
1051                                 DLIST_REMOVE(state->getgrent_state, ent);
1052
1053                                 SAFE_FREE(ent);
1054                                 ent = next_ent;
1055                         }
1056
1057                         /* No more domains */
1058
1059                         if (!ent)
1060                                 break;
1061                 }
1062
1063                 name_list = (struct acct_info *)ent->sam_entries;
1064
1065                 if (!(domain = find_domain_from_name(ent->domain_name))) {
1066                         DEBUG(3, ("No such domain %s in winbindd_getgrent\n",
1067                                   ent->domain_name));
1068                         result = False;
1069                         goto done;
1070                 }
1071
1072                 /* Lookup group info */
1073
1074                 sid_copy(&group_sid, &domain->sid);
1075                 sid_append_rid(&group_sid, name_list[ent->sam_entry_index].rid);
1076
1077                 if (!NT_STATUS_IS_OK(idmap_sid_to_gid(domain->have_idmap_config
1078                                                       ? domain->name : "",
1079                                                       &group_sid, &group_gid)))
1080                 {
1081                         union unid_t id;
1082                         enum lsa_SidType type;
1083
1084                         DEBUG(10, ("SID %s not in idmap\n",
1085                                    sid_string_dbg(&group_sid)));
1086
1087                         if (!pdb_sid_to_id(&group_sid, &id, &type)) {
1088                                 DEBUG(1,("could not look up gid for group %s\n",
1089                                          name_list[ent->sam_entry_index].acct_name));
1090                                 ent->sam_entry_index++;
1091                                 goto tryagain;
1092                         }
1093
1094                         if ((type != SID_NAME_DOM_GRP) &&
1095                             (type != SID_NAME_ALIAS) &&
1096                             (type != SID_NAME_WKN_GRP)) {
1097                                 DEBUG(1, ("Group %s is a %s, not a group\n",
1098                                           sid_type_lookup(type),
1099                                           name_list[ent->sam_entry_index].acct_name));
1100                                 ent->sam_entry_index++;
1101                                 goto tryagain;
1102                         }
1103                         group_gid = id.gid;
1104                 }
1105
1106                 DEBUG(10, ("got gid %lu for group %lu\n",
1107                            (unsigned long)group_gid,
1108                            (unsigned long)name_list[ent->sam_entry_index].rid));
1109
1110                 /* Fill in group entry */
1111
1112                 fill_domain_username(domain_group_name, ent->domain_name,
1113                          name_list[ent->sam_entry_index].acct_name, True);
1114
1115                 result = fill_grent(state->mem_ctx, &group_list[group_list_ndx],
1116                                     ent->domain_name,
1117                                     name_list[ent->sam_entry_index].acct_name,
1118                                     group_gid);
1119
1120                 /* Fill in group membership entry */
1121
1122                 if (result) {
1123                         size_t num_gr_mem = 0;
1124                         DOM_SID member_sid;
1125                         group_list[group_list_ndx].num_gr_mem = 0;
1126                         gr_mem = NULL;
1127                         gr_mem_len = 0;
1128
1129                         /* Get group membership */
1130                         if (state->request->cmd == WINBINDD_GETGRLST) {
1131                                 result = True;
1132                         } else {
1133                                 sid_copy(&member_sid, &domain->sid);
1134                                 sid_append_rid(&member_sid, name_list[ent->sam_entry_index].rid);
1135                                 result = fill_grent_mem(
1136                                         domain,
1137                                         NULL,
1138                                         &member_sid,
1139                                         SID_NAME_DOM_GRP,
1140                                         &num_gr_mem,
1141                                         &gr_mem, &gr_mem_len);
1142
1143                                 group_list[group_list_ndx].num_gr_mem = (uint32)num_gr_mem;
1144                         }
1145                 }
1146
1147                 if (result) {
1148                         /* Append to group membership list */
1149                         gr_mem_list = (char *)SMB_REALLOC(
1150                                 gr_mem_list, gr_mem_list_len + gr_mem_len);
1151
1152                         if (!gr_mem_list &&
1153                             (group_list[group_list_ndx].num_gr_mem != 0)) {
1154                                 DEBUG(0, ("out of memory\n"));
1155                                 gr_mem_list_len = 0;
1156                                 break;
1157                         }
1158
1159                         DEBUG(10, ("list_len = %d, mem_len = %u\n",
1160                                    gr_mem_list_len, (unsigned int)gr_mem_len));
1161
1162                         memcpy(&gr_mem_list[gr_mem_list_len], gr_mem,
1163                                gr_mem_len);
1164
1165                         SAFE_FREE(gr_mem);
1166
1167                         group_list[group_list_ndx].gr_mem_ofs =
1168                                 gr_mem_list_len;
1169
1170                         gr_mem_list_len += gr_mem_len;
1171                 }
1172
1173                 ent->sam_entry_index++;
1174
1175                 /* Add group to return list */
1176
1177                 if (result) {
1178
1179                         DEBUG(10, ("adding group num_entries = %d\n",
1180                                    state->response->data.num_entries));
1181
1182                         group_list_ndx++;
1183                         state->response->data.num_entries++;
1184
1185                         state->response->length +=
1186                                 sizeof(struct winbindd_gr);
1187
1188                 } else {
1189                         DEBUG(0, ("could not lookup domain group %s\n",
1190                                   domain_group_name));
1191                 }
1192         }
1193
1194         /* Copy the list of group memberships to the end of the extra data */
1195
1196         if (group_list_ndx == 0)
1197                 goto done;
1198
1199         state->response->extra_data.data = talloc_realloc_size(
1200                 state->mem_ctx, state->response->extra_data.data,
1201                 group_list_ndx * sizeof(struct winbindd_gr) + gr_mem_list_len);
1202
1203         if (!state->response->extra_data.data) {
1204                 DEBUG(0, ("out of memory\n"));
1205                 group_list_ndx = 0;
1206                 SAFE_FREE(gr_mem_list);
1207                 request_error(state);
1208                 return;
1209         }
1210
1211         memcpy(&((char *)state->response->extra_data.data)
1212                [group_list_ndx * sizeof(struct winbindd_gr)],
1213                gr_mem_list, gr_mem_list_len);
1214
1215         state->response->length += gr_mem_list_len;
1216
1217         DEBUG(10, ("returning %d groups, length = %d\n",
1218                    group_list_ndx, gr_mem_list_len));
1219
1220         /* Out of domains */
1221
1222  done:
1223
1224         SAFE_FREE(gr_mem_list);
1225
1226         if (group_list_ndx > 0)
1227                 request_ok(state);
1228         else
1229                 request_error(state);
1230 }
1231
1232 /* List domain groups without mapping to unix ids */
1233 void winbindd_list_groups(struct winbindd_cli_state *state)
1234 {
1235         winbindd_list_ent(state, LIST_GROUPS);
1236 }
1237
1238 /* Get user supplementary groups.  This is much quicker than trying to
1239    invert the groups database.  We merge the groups from the gids and
1240    other_sids info3 fields as trusted domain, universal group
1241    memberships, and nested groups (win2k native mode only) are not
1242    returned by the getgroups RPC call but are present in the info3. */
1243
1244 struct getgroups_state {
1245         struct winbindd_cli_state *state;
1246         struct winbindd_domain *domain;
1247         char *domname;
1248         char *username;
1249         DOM_SID user_sid;
1250
1251         const DOM_SID *token_sids;
1252         size_t i, num_token_sids;
1253
1254         gid_t *token_gids;
1255         size_t num_token_gids;
1256 };
1257
1258
1259 /* Get user supplementary sids. This is equivalent to the
1260    winbindd_getgroups() function but it involves a SID->SIDs mapping
1261    rather than a NAME->SID->SIDS->GIDS mapping, which means we avoid
1262    idmap. This call is designed to be used with applications that need
1263    to do ACL evaluation themselves. Note that the cached info3 data is
1264    not used
1265
1266    this function assumes that the SID that comes in is a user SID. If
1267    you pass in another type of SID then you may get unpredictable
1268    results.
1269 */
1270
1271 static void getusersids_recv(void *private_data, bool success, DOM_SID *sids,
1272                              size_t num_sids);
1273
1274 void winbindd_getusersids(struct winbindd_cli_state *state)
1275 {
1276         DOM_SID *user_sid;
1277
1278         /* Ensure null termination */
1279         state->request->data.sid[sizeof(state->request->data.sid)-1]='\0';
1280
1281         user_sid = TALLOC_P(state->mem_ctx, DOM_SID);
1282         if (user_sid == NULL) {
1283                 DEBUG(1, ("talloc failed\n"));
1284                 request_error(state);
1285                 return;
1286         }
1287
1288         if (!string_to_sid(user_sid, state->request->data.sid)) {
1289                 DEBUG(1, ("Could not get convert sid %s from string\n",
1290                           state->request->data.sid));
1291                 request_error(state);
1292                 return;
1293         }
1294
1295         winbindd_gettoken_async(state->mem_ctx, user_sid, getusersids_recv,
1296                                 state);
1297 }
1298
1299 static void getusersids_recv(void *private_data, bool success, DOM_SID *sids,
1300                              size_t num_sids)
1301 {
1302         struct winbindd_cli_state *state =
1303                 (struct winbindd_cli_state *)private_data;
1304         char *ret = NULL;
1305         unsigned ofs, ret_size = 0;
1306         size_t i;
1307
1308         if (!success) {
1309                 request_error(state);
1310                 return;
1311         }
1312
1313         /* work out the response size */
1314         for (i = 0; i < num_sids; i++) {
1315                 fstring s;
1316                 sid_to_fstring(s, &sids[i]);
1317                 ret_size += strlen(s) + 1;
1318         }
1319
1320         /* build the reply */
1321         ret = talloc_array(state->mem_ctx, char, ret_size);
1322         if (!ret) {
1323                 DEBUG(0, ("malloc failed\n"));
1324                 request_error(state);
1325                 return;
1326         }
1327         ofs = 0;
1328         for (i = 0; i < num_sids; i++) {
1329                 fstring s;
1330                 sid_to_fstring(s, &sids[i]);
1331                 safe_strcpy(ret + ofs, s, ret_size - ofs - 1);
1332                 ofs += strlen(ret+ofs) + 1;
1333         }
1334
1335         /* Send data back to client */
1336         state->response->data.num_entries = num_sids;
1337         state->response->extra_data.data = ret;
1338         state->response->length += ret_size;
1339         request_ok(state);
1340 }
1341
1342 enum winbindd_result winbindd_dual_getuserdomgroups(struct winbindd_domain *domain,
1343                                                     struct winbindd_cli_state *state)
1344 {
1345         DOM_SID user_sid;
1346         NTSTATUS status;
1347
1348         char *sidstring;
1349         ssize_t len;
1350         DOM_SID *groups;
1351         uint32 num_groups;
1352
1353         /* Ensure null termination */
1354         state->request->data.sid[sizeof(state->request->data.sid)-1]='\0';
1355
1356         if (!string_to_sid(&user_sid, state->request->data.sid)) {
1357                 DEBUG(1, ("Could not get convert sid %s from string\n",
1358                           state->request->data.sid));
1359                 return WINBINDD_ERROR;
1360         }
1361
1362         status = domain->methods->lookup_usergroups(domain, state->mem_ctx,
1363                                                     &user_sid, &num_groups,
1364                                                     &groups);
1365         if (!NT_STATUS_IS_OK(status))
1366                 return WINBINDD_ERROR;
1367
1368         if (num_groups == 0) {
1369                 state->response->data.num_entries = 0;
1370                 state->response->extra_data.data = NULL;
1371                 return WINBINDD_OK;
1372         }
1373
1374         if (!print_sidlist(state->mem_ctx,
1375                            groups, num_groups,
1376                            &sidstring, &len)) {
1377                 DEBUG(0, ("talloc failed\n"));
1378                 return WINBINDD_ERROR;
1379         }
1380
1381         state->response->extra_data.data = sidstring;
1382         state->response->length += len+1;
1383         state->response->data.num_entries = num_groups;
1384
1385         return WINBINDD_OK;
1386 }
1387
1388 enum winbindd_result winbindd_dual_getsidaliases(struct winbindd_domain *domain,
1389                                                  struct winbindd_cli_state *state)
1390 {
1391         DOM_SID *sids = NULL;
1392         size_t num_sids = 0;
1393         char *sidstr = NULL;
1394         ssize_t len;
1395         size_t i;
1396         uint32 num_aliases;
1397         uint32 *alias_rids;
1398         NTSTATUS result;
1399
1400         DEBUG(3, ("[%5lu]: getsidaliases\n", (unsigned long)state->pid));
1401
1402         sidstr = state->request->extra_data.data;
1403         if (sidstr == NULL) {
1404                 sidstr = talloc_strdup(state->mem_ctx, "\n"); /* No SID */
1405                 if (!sidstr) {
1406                         DEBUG(0, ("Out of memory\n"));
1407                         return WINBINDD_ERROR;
1408                 }
1409         }
1410
1411         DEBUG(10, ("Sidlist: %s\n", sidstr));
1412
1413         if (!parse_sidlist(state->mem_ctx, sidstr, &sids, &num_sids)) {
1414                 DEBUG(0, ("Could not parse SID list: %s\n", sidstr));
1415                 return WINBINDD_ERROR;
1416         }
1417
1418         num_aliases = 0;
1419         alias_rids = NULL;
1420
1421         result = domain->methods->lookup_useraliases(domain,
1422                                                      state->mem_ctx,
1423                                                      num_sids, sids,
1424                                                      &num_aliases,
1425                                                      &alias_rids);
1426
1427         if (!NT_STATUS_IS_OK(result)) {
1428                 DEBUG(3, ("Could not lookup_useraliases: %s\n",
1429                           nt_errstr(result)));
1430                 return WINBINDD_ERROR;
1431         }
1432
1433         num_sids = 0;
1434         sids = NULL;
1435         sidstr = NULL;
1436
1437         DEBUG(10, ("Got %d aliases\n", num_aliases));
1438
1439         for (i=0; i<num_aliases; i++) {
1440                 DOM_SID sid;
1441                 DEBUGADD(10, (" rid %d\n", alias_rids[i]));
1442                 sid_copy(&sid, &domain->sid);
1443                 sid_append_rid(&sid, alias_rids[i]);
1444                 result = add_sid_to_array(state->mem_ctx, &sid, &sids,
1445                                           &num_sids);
1446                 if (!NT_STATUS_IS_OK(result)) {
1447                         return WINBINDD_ERROR;
1448                 }
1449         }
1450
1451
1452         if (!print_sidlist(state->mem_ctx, sids, num_sids, &sidstr, &len)) {
1453                 DEBUG(0, ("Could not print_sidlist\n"));
1454                 state->response->extra_data.data = NULL;
1455                 return WINBINDD_ERROR;
1456         }
1457
1458         state->response->extra_data.data = NULL;
1459
1460         if (sidstr) {
1461                 state->response->extra_data.data = sidstr;
1462                 DEBUG(10, ("aliases_list: %s\n",
1463                            (char *)state->response->extra_data.data));
1464                 state->response->length += len+1;
1465                 state->response->data.num_entries = num_sids;
1466         }
1467
1468         return WINBINDD_OK;
1469 }
1470
1471 struct getgr_countmem {
1472         int num;
1473         size_t len;
1474 };
1475
1476 static int getgr_calc_memberlen(DATA_BLOB key, void *data, void *priv)
1477 {
1478         struct wbint_GroupMember *m = talloc_get_type_abort(
1479                 data, struct wbint_GroupMember);
1480         struct getgr_countmem *buf = (struct getgr_countmem *)priv;
1481
1482         buf->num += 1;
1483         buf->len += strlen(m->name) + 1;
1484         return 0;
1485 }
1486
1487 struct getgr_stringmem {
1488         size_t ofs;
1489         char *buf;
1490 };
1491
1492 static int getgr_unparse_members(DATA_BLOB key, void *data, void *priv)
1493 {
1494         struct wbint_GroupMember *m = talloc_get_type_abort(
1495                 data, struct wbint_GroupMember);
1496         struct getgr_stringmem *buf = (struct getgr_stringmem *)priv;
1497         int len;
1498
1499         len = strlen(m->name);
1500
1501         memcpy(buf->buf + buf->ofs, m->name, len);
1502         buf->ofs += len;
1503         buf->buf[buf->ofs] = ',';
1504         buf->ofs += 1;
1505         return 0;
1506 }
1507
1508 NTSTATUS winbindd_print_groupmembers(struct talloc_dict *members,
1509                                      TALLOC_CTX *mem_ctx,
1510                                      int *num_members, char **result)
1511 {
1512         struct getgr_countmem c;
1513         struct getgr_stringmem m;
1514         int res;
1515
1516         c.num = 0;
1517         c.len = 0;
1518
1519         res = talloc_dict_traverse(members, getgr_calc_memberlen, &c);
1520         if (res != 0) {
1521                 DEBUG(5, ("talloc_dict_traverse failed\n"));
1522                 return NT_STATUS_INTERNAL_ERROR;
1523         }
1524
1525         m.ofs = 0;
1526         m.buf = talloc_array(mem_ctx, char, c.len);
1527         if (m.buf == NULL) {
1528                 DEBUG(5, ("talloc failed\n"));
1529                 return NT_STATUS_NO_MEMORY;
1530         }
1531
1532         res = talloc_dict_traverse(members, getgr_unparse_members, &m);
1533         if (res != 0) {
1534                 DEBUG(5, ("talloc_dict_traverse failed\n"));
1535                 TALLOC_FREE(m.buf);
1536                 return NT_STATUS_INTERNAL_ERROR;
1537         }
1538         m.buf[c.len-1] = '\0';
1539
1540         *num_members = c.num;
1541         *result = m.buf;
1542         return NT_STATUS_OK;
1543 }