r21613: perform variable subsitution on home directories and shells provided by the...
[samba.git] / source3 / nsswitch / winbindd_user.c
1 /* 
2    Unix SMB/CIFS implementation.
3
4    Winbind daemon - user related functions
5
6    Copyright (C) Tim Potter 2000
7    Copyright (C) Jeremy Allison 2001.
8    Copyright (C) Gerald (Jerry) Carter 2003.
9    
10    This program is free software; you can redistribute it and/or modify
11    it under the terms of the GNU General Public License as published by
12    the Free Software Foundation; either version 2 of the License, or
13    (at your option) any later version.
14    
15    This program is distributed in the hope that it will be useful,
16    but WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18    GNU General Public License for more details.
19    
20    You should have received a copy of the GNU General Public License
21    along with this program; if not, write to the Free Software
22    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 */
24
25 #include "includes.h"
26 #include "winbindd.h"
27
28 #undef DBGC_CLASS
29 #define DBGC_CLASS DBGC_WINBIND
30
31 static BOOL fillup_pw_field(const char *lp_template, 
32                             const char *username, 
33                             const char *domname,
34                             uid_t uid,
35                             gid_t gid,
36                             const char *in, 
37                             fstring out)
38 {
39         char *templ;
40
41         if (out == NULL)
42                 return False;
43
44         /* The substitution of %U and %D in the 'template 
45            homedir' is done by talloc_sub_specified() below.
46            If we have an in string (which means the value has already
47            been set in the nss_info backend), then use that.
48            Otherwise use the template value passed in. */
49
50         if ( in && !strequal(in,"") && lp_security() == SEC_ADS ) {
51                 templ = talloc_sub_specified(NULL, in, 
52                                              username, domname,
53                                      uid, gid);
54         } else {
55                 templ = talloc_sub_specified(NULL, lp_template, 
56                                              username, domname,
57                                              uid, gid);         
58         }
59                 
60         if (!templ)
61                 return False;
62
63         safe_strcpy(out, templ, sizeof(fstring) - 1);
64         TALLOC_FREE(templ);
65                 
66         return True;
67         
68 }
69 /* Fill a pwent structure with information we have obtained */
70
71 static BOOL winbindd_fill_pwent(char *dom_name, char *user_name, 
72                                 DOM_SID *user_sid, DOM_SID *group_sid,
73                                 char *full_name, char *homedir, char *shell,
74                                 struct winbindd_pw *pw)
75 {
76         fstring output_username;
77         fstring sid_string;
78         
79         if (!pw || !dom_name || !user_name)
80                 return False;
81         
82         /* Resolve the uid number */
83
84         if (!NT_STATUS_IS_OK(idmap_sid_to_uid(user_sid, &pw->pw_uid))) {
85                 DEBUG(1, ("error getting user id for sid %s\n", sid_to_string(sid_string, user_sid)));
86                 return False;
87         }
88         
89         /* Resolve the gid number */   
90
91         if (!NT_STATUS_IS_OK(idmap_sid_to_gid(group_sid, &pw->pw_gid))) {
92                 DEBUG(1, ("error getting group id for sid %s\n", sid_to_string(sid_string, group_sid)));
93                 return False;
94         }
95
96         strlower_m(user_name);
97
98         /* Username */
99
100         fill_domain_username(output_username, dom_name, user_name, True); 
101
102         safe_strcpy(pw->pw_name, output_username, sizeof(pw->pw_name) - 1);
103         
104         /* Full name (gecos) */
105         
106         safe_strcpy(pw->pw_gecos, full_name, sizeof(pw->pw_gecos) - 1);
107
108         /* Home directory and shell - use template config parameters.  The
109            defaults are /tmp for the home directory and /bin/false for
110            shell. */
111         
112         if (!fillup_pw_field(lp_template_homedir(), user_name, dom_name, 
113                              pw->pw_uid, pw->pw_gid, homedir, pw->pw_dir))
114                 return False;
115
116         if (!fillup_pw_field(lp_template_shell(), user_name, dom_name, 
117                              pw->pw_uid, pw->pw_gid, shell, pw->pw_shell))
118                 return False;
119
120         /* Password - set to "*" as we can't generate anything useful here.
121            Authentication can be done using the pam_winbind module. */
122
123         safe_strcpy(pw->pw_passwd, "*", sizeof(pw->pw_passwd) - 1);
124
125         return True;
126 }
127
128 /* Wrapper for domain->methods->query_user, only on the parent->child pipe */
129
130 enum winbindd_result winbindd_dual_userinfo(struct winbindd_domain *domain,
131                                             struct winbindd_cli_state *state)
132 {
133         DOM_SID sid;
134         WINBIND_USERINFO user_info;
135         NTSTATUS status;
136
137         /* Ensure null termination */
138         state->request.data.sid[sizeof(state->request.data.sid)-1]='\0';
139
140         DEBUG(3, ("[%5lu]: lookupsid %s\n", (unsigned long)state->pid, 
141                   state->request.data.sid));
142
143         if (!string_to_sid(&sid, state->request.data.sid)) {
144                 DEBUG(5, ("%s not a SID\n", state->request.data.sid));
145                 return WINBINDD_ERROR;
146         }
147
148         status = domain->methods->query_user(domain, state->mem_ctx,
149                                              &sid, &user_info);
150         if (!NT_STATUS_IS_OK(status)) {
151                 DEBUG(1, ("error getting user info for sid %s\n",
152                           sid_string_static(&sid)));
153                 return WINBINDD_ERROR;
154         }
155
156         fstrcpy(state->response.data.user_info.acct_name, user_info.acct_name);
157         fstrcpy(state->response.data.user_info.full_name, user_info.full_name);
158         fstrcpy(state->response.data.user_info.homedir, user_info.homedir);
159         fstrcpy(state->response.data.user_info.shell, user_info.shell);
160         state->response.data.user_info.primary_gid = user_info.primary_gid;     
161         if (!sid_peek_check_rid(&domain->sid, &user_info.group_sid,
162                                 &state->response.data.user_info.group_rid)) {
163                 DEBUG(1, ("Could not extract group rid out of %s\n",
164                           sid_string_static(&sid)));
165                 return WINBINDD_ERROR;
166         }
167
168         return WINBINDD_OK;
169 }
170
171 struct getpwsid_state {
172         struct winbindd_cli_state *state;
173         struct winbindd_domain *domain;
174         char *username;
175         char *fullname;
176         char *homedir;
177         char *shell;
178         DOM_SID user_sid;
179         uid_t uid;
180         DOM_SID group_sid;
181         gid_t gid;
182 };
183
184 static void getpwsid_queryuser_recv(void *private_data, BOOL success,
185                                     const char *acct_name,
186                                     const char *full_name, 
187                                     const char *homedir,
188                                     const char *shell,
189                                     uint32 gid,
190                                     uint32 group_rid);
191 static void getpwsid_sid2uid_recv(void *private_data, BOOL success, uid_t uid);
192 static void getpwsid_sid2gid_recv(void *private_data, BOOL success, gid_t gid);
193
194 static void winbindd_getpwsid(struct winbindd_cli_state *state,
195                               const DOM_SID *sid)
196 {
197         struct getpwsid_state *s;
198
199         s = TALLOC_ZERO_P(state->mem_ctx, struct getpwsid_state);
200         if (s == NULL) {
201                 DEBUG(0, ("talloc failed\n"));
202                 goto error;
203         }
204
205         s->state = state;
206         s->domain = find_domain_from_sid_noinit(sid);
207         if (s->domain == NULL) {
208                 DEBUG(3, ("Could not find domain for sid %s\n",
209                           sid_string_static(sid)));
210                 goto error;
211         }
212
213         sid_copy(&s->user_sid, sid);
214
215         query_user_async(s->state->mem_ctx, s->domain, sid,
216                          getpwsid_queryuser_recv, s);
217         return;
218
219  error:
220         request_error(state);
221 }
222         
223 static void getpwsid_queryuser_recv(void *private_data, BOOL success,
224                                     const char *acct_name,
225                                     const char *full_name, 
226                                     const char *homedir,
227                                     const char *shell,
228                                     uint32 gid,
229                                     uint32 group_rid)
230 {
231         fstring username;
232         struct getpwsid_state *s =
233                 talloc_get_type_abort(private_data, struct getpwsid_state);
234
235         if (!success) {
236                 DEBUG(5, ("Could not query domain %s SID %s\n", s->domain->name,
237                           sid_string_static(&s->user_sid)));
238                 request_error(s->state);
239                 return;
240         }
241
242         fstrcpy( username, acct_name );
243         strlower_m( username );
244         s->username = talloc_strdup(s->state->mem_ctx, username);
245
246         ws_name_replace( s->username, '_' );
247          
248         s->fullname = talloc_strdup(s->state->mem_ctx, full_name);
249         s->homedir = talloc_strdup(s->state->mem_ctx, homedir);
250         s->shell = talloc_strdup(s->state->mem_ctx, shell);
251         s->gid = gid;   
252         sid_copy(&s->group_sid, &s->domain->sid);
253         sid_append_rid(&s->group_sid, group_rid);
254
255         winbindd_sid2uid_async(s->state->mem_ctx, &s->user_sid,
256                                getpwsid_sid2uid_recv, s);
257 }
258
259 static void getpwsid_sid2uid_recv(void *private_data, BOOL success, uid_t uid)
260 {
261         struct getpwsid_state *s =
262                 talloc_get_type_abort(private_data, struct getpwsid_state);
263
264         if (!success) {
265                 DEBUG(5, ("Could not query user's %s\\%s uid\n",
266                           s->domain->name, s->username));
267                 request_error(s->state);
268                 return;
269         }
270
271         s->uid = uid;
272         winbindd_sid2gid_async(s->state->mem_ctx, &s->group_sid,
273                                getpwsid_sid2gid_recv, s);
274 }
275
276 static void getpwsid_sid2gid_recv(void *private_data, BOOL success, gid_t gid)
277 {
278         struct getpwsid_state *s =
279                 talloc_get_type_abort(private_data, struct getpwsid_state);
280         struct winbindd_pw *pw;
281         fstring output_username;
282
283         /* allow the nss backend to override the primary group ID.
284            If the gid has already been set, then keep it.
285            This makes me feel dirty.  If the nss backend already
286            gave us a gid, we don't really care whether the sid2gid()
287            call worked or not.   --jerry  */
288
289         if ( s->gid == (gid_t)-1 ) {
290
291                 if (!success) {
292                         DEBUG(5, ("Could not query user's %s\\%s\n gid",
293                                   s->domain->name, s->username));
294                         goto failed;
295                 }
296
297                 /* take what the sid2gid() call gave us */
298                 s->gid = gid;
299         }
300
301         pw = &s->state->response.data.pw;
302         pw->pw_uid = s->uid;
303         pw->pw_gid = s->gid;
304         fill_domain_username(output_username, s->domain->name, s->username, True); 
305         safe_strcpy(pw->pw_name, output_username, sizeof(pw->pw_name) - 1);
306         safe_strcpy(pw->pw_gecos, s->fullname, sizeof(pw->pw_gecos) - 1);
307
308         if (!fillup_pw_field(lp_template_homedir(), s->username, s->domain->name, 
309                              pw->pw_uid, pw->pw_gid, s->homedir, pw->pw_dir)) {
310                 DEBUG(5, ("Could not compose homedir\n"));
311                 goto failed;
312         }
313
314         if (!fillup_pw_field(lp_template_shell(), s->username, s->domain->name, 
315                              pw->pw_uid, pw->pw_gid, s->shell, pw->pw_shell)) {
316                 DEBUG(5, ("Could not compose shell\n"));
317                 goto failed;
318         }
319
320         /* Password - set to "*" as we can't generate anything useful here.
321            Authentication can be done using the pam_winbind module. */
322
323         safe_strcpy(pw->pw_passwd, "*", sizeof(pw->pw_passwd) - 1);
324
325         request_ok(s->state);
326         return;
327
328  failed:
329         request_error(s->state);
330 }
331
332 /* Return a password structure from a username.  */
333
334 static void getpwnam_name2sid_recv(void *private_data, BOOL success,
335                                    const DOM_SID *sid, enum lsa_SidType type);
336
337 void winbindd_getpwnam(struct winbindd_cli_state *state)
338 {
339         struct winbindd_domain *domain;
340         fstring domname, username;
341
342         /* Ensure null termination */
343         state->request.data.username[sizeof(state->request.data.username)-1]='\0';
344
345         DEBUG(3, ("[%5lu]: getpwnam %s\n", (unsigned long)state->pid,
346                   state->request.data.username));
347
348         if (!parse_domain_user(state->request.data.username, domname,
349                                username)) {
350                 DEBUG(5, ("Could not parse domain user: %s\n",
351                           state->request.data.username));
352                 request_error(state);
353                 return;
354         }
355         
356         /* Get info for the domain */
357
358         domain = find_domain_from_name(domname);
359
360         if (domain == NULL) {
361                 DEBUG(7, ("could not find domain entry for domain %s\n",
362                           domname));
363                 request_error(state);
364                 return;
365         }
366
367         if ( strequal(domname, lp_workgroup()) && lp_winbind_trusted_domains_only() ) {
368                 DEBUG(7,("winbindd_getpwnam: My domain -- rejecting getpwnam() for %s\\%s.\n", 
369                         domname, username));
370                 request_error(state);
371                 return;
372         }       
373
374         /* Get rid and name type from name.  The following costs 1 packet */
375
376         winbindd_lookupname_async(state->mem_ctx, domname, username,
377                                   getpwnam_name2sid_recv, state);
378 }
379
380 static void getpwnam_name2sid_recv(void *private_data, BOOL success,
381                                    const DOM_SID *sid, enum lsa_SidType type)
382 {
383         struct winbindd_cli_state *state =
384                 (struct winbindd_cli_state *)private_data;
385
386         if (!success) {
387                 DEBUG(5, ("Could not lookup name for user %s\n",
388                           state->request.data.username));
389                 request_error(state);
390                 return;
391         }
392
393         if ((type != SID_NAME_USER) && (type != SID_NAME_COMPUTER)) {
394                 DEBUG(5, ("%s is not a user\n", state->request.data.username));
395                 request_error(state);
396                 return;
397         }
398
399         winbindd_getpwsid(state, sid);
400 }
401
402 static void getpwuid_recv(void *private_data, BOOL success, const char *sid)
403 {
404         struct winbindd_cli_state *state =
405                 (struct winbindd_cli_state *)private_data;
406         DOM_SID user_sid;
407
408         if (!success) {
409                 DEBUG(10,("uid2sid_recv: uid [%lu] to sid mapping failed\n.",
410                           (unsigned long)(state->request.data.uid)));
411                 request_error(state);
412                 return;
413         }
414         
415         DEBUG(10,("uid2sid_recv: uid %lu has sid %s\n",
416                   (unsigned long)(state->request.data.uid), sid));
417
418         string_to_sid(&user_sid, sid);
419         winbindd_getpwsid(state, &user_sid);
420 }
421
422 /* Return a password structure given a uid number */
423 void winbindd_getpwuid(struct winbindd_cli_state *state)
424 {
425         DEBUG(3, ("[%5lu]: getpwuid %lu\n", (unsigned long)state->pid, 
426                   (unsigned long)state->request.data.uid));
427
428         /* always query idmap via the async interface */
429         /* if this turns to be too slow we will add here a direct query to the cache */
430         winbindd_uid2sid_async(state->mem_ctx, state->request.data.uid, getpwuid_recv, state);
431 }
432
433 /*
434  * set/get/endpwent functions
435  */
436
437 /* Rewind file pointer for ntdom passwd database */
438
439 static BOOL winbindd_setpwent_internal(struct winbindd_cli_state *state)
440 {
441         struct winbindd_domain *domain;
442         
443         DEBUG(3, ("[%5lu]: setpwent\n", (unsigned long)state->pid));
444         
445         /* Check user has enabled this */
446         
447         if (!lp_winbind_enum_users()) {
448                 return False;
449         }
450
451         /* Free old static data if it exists */
452         
453         if (state->getpwent_state != NULL) {
454                 free_getent_state(state->getpwent_state);
455                 state->getpwent_state = NULL;
456         }
457
458 #if 0   /* JERRY */
459         /* add any local users we have */
460                 
461         if ( (domain_state = (struct getent_state *)malloc(sizeof(struct getent_state))) == NULL )
462                 return False;
463                 
464         ZERO_STRUCTP(domain_state);
465
466         /* Add to list of open domains */
467                 
468         DLIST_ADD(state->getpwent_state, domain_state);
469 #endif
470         
471         /* Create sam pipes for each domain we know about */
472         
473         for(domain = domain_list(); domain != NULL; domain = domain->next) {
474                 struct getent_state *domain_state;
475                 
476                 
477                 /* don't add our domaina if we are a PDC or if we 
478                    are a member of a Samba domain */
479                 
480                 if ( (IS_DC || lp_winbind_trusted_domains_only())
481                         && strequal(domain->name, lp_workgroup()) )
482                 {
483                         continue;
484                 }
485                                                 
486                 /* Create a state record for this domain */
487                 
488                 if ((domain_state = SMB_MALLOC_P(struct getent_state)) == NULL) {
489                         DEBUG(0, ("malloc failed\n"));
490                         return False;
491                 }
492                 
493                 ZERO_STRUCTP(domain_state);
494
495                 fstrcpy(domain_state->domain_name, domain->name);
496
497                 /* Add to list of open domains */
498                 
499                 DLIST_ADD(state->getpwent_state, domain_state);
500         }
501         
502         state->getpwent_initialized = True;
503         return True;
504 }
505
506 void winbindd_setpwent(struct winbindd_cli_state *state)
507 {
508         if (winbindd_setpwent_internal(state)) {
509                 request_ok(state);
510         } else {
511                 request_error(state);
512         }
513 }
514
515 /* Close file pointer to ntdom passwd database */
516
517 void winbindd_endpwent(struct winbindd_cli_state *state)
518 {
519         DEBUG(3, ("[%5lu]: endpwent\n", (unsigned long)state->pid));
520
521         free_getent_state(state->getpwent_state);    
522         state->getpwent_initialized = False;
523         state->getpwent_state = NULL;
524         request_ok(state);
525 }
526
527 /* Get partial list of domain users for a domain.  We fill in the sam_entries,
528    and num_sam_entries fields with domain user information.  The dispinfo_ndx
529    field is incremented to the index of the next user to fetch.  Return True if
530    some users were returned, False otherwise. */
531
532 static BOOL get_sam_user_entries(struct getent_state *ent, TALLOC_CTX *mem_ctx)
533 {
534         NTSTATUS status;
535         uint32 num_entries;
536         WINBIND_USERINFO *info;
537         struct getpwent_user *name_list = NULL;
538         struct winbindd_domain *domain;
539         struct winbindd_methods *methods;
540         unsigned int i;
541
542         if (ent->num_sam_entries)
543                 return False;
544
545         if (!(domain = find_domain_from_name(ent->domain_name))) {
546                 DEBUG(3, ("no such domain %s in get_sam_user_entries\n",
547                           ent->domain_name));
548                 return False;
549         }
550
551         methods = domain->methods;
552
553         /* Free any existing user info */
554
555         SAFE_FREE(ent->sam_entries);
556         ent->num_sam_entries = 0;
557         
558         /* Call query_user_list to get a list of usernames and user rids */
559
560         num_entries = 0;
561
562         status = methods->query_user_list(domain, mem_ctx, &num_entries, 
563                                           &info);
564                 
565         if (!NT_STATUS_IS_OK(status)) {
566                 DEBUG(10,("get_sam_user_entries: query_user_list failed with %s\n",
567                         nt_errstr(status) ));
568                 return False;
569         }
570
571         if (num_entries) {
572                 name_list = SMB_REALLOC_ARRAY(name_list, struct getpwent_user, ent->num_sam_entries + num_entries);
573                 
574                 if (!name_list) {
575                         DEBUG(0,("get_sam_user_entries realloc failed.\n"));
576                         return False;
577                 }
578         }
579
580         for (i = 0; i < num_entries; i++) {
581                 /* Store account name and gecos */
582                 if (!info[i].acct_name) {
583                         fstrcpy(name_list[ent->num_sam_entries + i].name, "");
584                 } else {
585                         fstrcpy(name_list[ent->num_sam_entries + i].name, 
586                                 info[i].acct_name); 
587                 }
588                 if (!info[i].full_name) {
589                         fstrcpy(name_list[ent->num_sam_entries + i].gecos, "");
590                 } else {
591                         fstrcpy(name_list[ent->num_sam_entries + i].gecos, 
592                                 info[i].full_name); 
593                 }
594                 if (!info[i].homedir) {
595                         fstrcpy(name_list[ent->num_sam_entries + i].homedir, "");
596                 } else {
597                         fstrcpy(name_list[ent->num_sam_entries + i].homedir, 
598                                 info[i].homedir); 
599                 }
600                 if (!info[i].shell) {
601                         fstrcpy(name_list[ent->num_sam_entries + i].shell, "");
602                 } else {
603                         fstrcpy(name_list[ent->num_sam_entries + i].shell, 
604                                 info[i].shell); 
605                 }
606         
607         
608                 /* User and group ids */
609                 sid_copy(&name_list[ent->num_sam_entries+i].user_sid,
610                          &info[i].user_sid);
611                 sid_copy(&name_list[ent->num_sam_entries+i].group_sid,
612                          &info[i].group_sid);
613         }
614                 
615         ent->num_sam_entries += num_entries;
616         
617         /* Fill in remaining fields */
618         
619         ent->sam_entries = name_list;
620         ent->sam_entry_index = 0;
621         return ent->num_sam_entries > 0;
622 }
623
624 /* Fetch next passwd entry from ntdom database */
625
626 #define MAX_GETPWENT_USERS 500
627
628 void winbindd_getpwent(struct winbindd_cli_state *state)
629 {
630         struct getent_state *ent;
631         struct winbindd_pw *user_list;
632         int num_users, user_list_ndx = 0, i;
633
634         DEBUG(3, ("[%5lu]: getpwent\n", (unsigned long)state->pid));
635
636         /* Check user has enabled this */
637
638         if (!lp_winbind_enum_users()) {
639                 request_error(state);
640                 return;
641         }
642
643         /* Allocate space for returning a chunk of users */
644
645         num_users = MIN(MAX_GETPWENT_USERS, state->request.data.num_entries);
646         
647         if ((state->response.extra_data.data = SMB_MALLOC_ARRAY(struct winbindd_pw, num_users)) == NULL) {
648                 request_error(state);
649                 return;
650         }
651
652         memset(state->response.extra_data.data, 0, num_users * 
653                sizeof(struct winbindd_pw));
654
655         user_list = (struct winbindd_pw *)state->response.extra_data.data;
656
657         if (!state->getpwent_initialized)
658                 winbindd_setpwent_internal(state);
659         
660         if (!(ent = state->getpwent_state)) {
661                 request_error(state);
662                 return;
663         }
664
665         /* Start sending back users */
666
667         for (i = 0; i < num_users; i++) {
668                 struct getpwent_user *name_list = NULL;
669                 uint32 result;
670
671                 /* Do we need to fetch another chunk of users? */
672
673                 if (ent->num_sam_entries == ent->sam_entry_index) {
674
675                         while(ent &&
676                               !get_sam_user_entries(ent, state->mem_ctx)) {
677                                 struct getent_state *next_ent;
678
679                                 /* Free state information for this domain */
680
681                                 SAFE_FREE(ent->sam_entries);
682
683                                 next_ent = ent->next;
684                                 DLIST_REMOVE(state->getpwent_state, ent);
685
686                                 SAFE_FREE(ent);
687                                 ent = next_ent;
688                         }
689  
690                         /* No more domains */
691
692                         if (!ent) 
693                                 break;
694                 }
695
696                 name_list = (struct getpwent_user *)ent->sam_entries;
697
698                 /* Lookup user info */
699                 
700                 result = winbindd_fill_pwent(
701                         ent->domain_name, 
702                         name_list[ent->sam_entry_index].name,
703                         &name_list[ent->sam_entry_index].user_sid,
704                         &name_list[ent->sam_entry_index].group_sid,
705                         name_list[ent->sam_entry_index].gecos,
706                         name_list[ent->sam_entry_index].homedir,
707                         name_list[ent->sam_entry_index].shell,
708                         &user_list[user_list_ndx]);
709                 
710                 ent->sam_entry_index++;
711                 
712                 /* Add user to return list */
713                 
714                 if (result) {
715                                 
716                         user_list_ndx++;
717                         state->response.data.num_entries++;
718                         state->response.length += 
719                                 sizeof(struct winbindd_pw);
720
721                 } else
722                         DEBUG(1, ("could not lookup domain user %s\n",
723                                   name_list[ent->sam_entry_index].name));
724         }
725
726         /* Out of domains */
727
728         if (user_list_ndx > 0)
729                 request_ok(state);
730         else
731                 request_error(state);
732 }
733
734 /* List domain users without mapping to unix ids */
735
736 void winbindd_list_users(struct winbindd_cli_state *state)
737 {
738         struct winbindd_domain *domain;
739         WINBIND_USERINFO *info;
740         const char *which_domain;
741         uint32 num_entries = 0, total_entries = 0;
742         char *extra_data = NULL;
743         int extra_data_len = 0;
744         enum winbindd_result rv = WINBINDD_ERROR;
745
746         DEBUG(3, ("[%5lu]: list users\n", (unsigned long)state->pid));
747
748         /* Ensure null termination */
749         state->request.domain_name[sizeof(state->request.domain_name)-1]='\0';  
750         which_domain = state->request.domain_name;
751         
752         /* Enumerate over trusted domains */
753
754         for (domain = domain_list(); domain; domain = domain->next) {
755                 NTSTATUS status;
756                 struct winbindd_methods *methods;
757                 unsigned int i;
758                 
759                 /* if we have a domain name restricting the request and this
760                    one in the list doesn't match, then just bypass the remainder
761                    of the loop */
762                    
763                 if ( *which_domain && !strequal(which_domain, domain->name) )
764                         continue;
765                         
766                 methods = domain->methods;
767
768                 /* Query display info */
769                 status = methods->query_user_list(domain, state->mem_ctx, 
770                                                   &num_entries, &info);
771
772                 if (!NT_STATUS_IS_OK(status)) {
773                         continue;
774                 }
775
776                 if (num_entries == 0)
777                         continue;
778
779                 /* Allocate some memory for extra data */
780                 total_entries += num_entries;
781                         
782                 extra_data = (char *)SMB_REALLOC(
783                         extra_data, sizeof(fstring) * total_entries);
784                         
785                 if (!extra_data) {
786                         DEBUG(0,("failed to enlarge buffer!\n"));
787                         goto done;
788                 }
789
790                 /* Pack user list into extra data fields */
791                         
792                 for (i = 0; i < num_entries; i++) {
793                         fstring acct_name, name;
794                         
795                         if (!info[i].acct_name) {
796                                 fstrcpy(acct_name, "");
797                         } else {
798                                 fstrcpy(acct_name, info[i].acct_name);
799                         }
800                         
801                         fill_domain_username(name, domain->name, acct_name, True);
802                         
803                                 /* Append to extra data */
804                         memcpy(&extra_data[extra_data_len], name, 
805                                strlen(name));
806                         extra_data_len += strlen(name);
807                         extra_data[extra_data_len++] = ',';
808                 }   
809         }
810
811         /* Assign extra_data fields in response structure */
812
813         if (extra_data) {
814                 extra_data[extra_data_len - 1] = '\0';
815                 state->response.extra_data.data = extra_data;
816                 state->response.length += extra_data_len;
817         }
818
819         /* No domains responded but that's still OK so don't return an
820            error. */
821
822         rv = WINBINDD_OK;
823
824  done:
825
826         if (rv == WINBINDD_OK)
827                 request_ok(state);
828         else
829                 request_error(state);
830 }