More Realloc fixes.
[tprouty/samba.git] / source / nsswitch / winbindd_user.c
1 /* 
2    Unix SMB/Netbios implementation.
3    Version 2.0
4
5    Winbind daemon - user related functions
6
7    Copyright (C) Tim Potter 2000
8    
9    This program is free software; you can redistribute it and/or modify
10    it under the terms of the GNU General Public License as published by
11    the Free Software Foundation; either version 2 of the License, or
12    (at your option) any later version.
13    
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18    
19    You should have received a copy of the GNU General Public License
20    along with this program; if not, write to the Free Software
21    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 */
23
24 #include "winbindd.h"
25
26 /* Fill a pwent structure with information we have obtained */
27
28 static BOOL winbindd_fill_pwent(char *domain_name, char *name, 
29                                 uint32 user_rid, uint32 group_rid, 
30                                 char *full_name, struct winbindd_pw *pw)
31 {
32         extern userdom_struct current_user_info;
33         fstring name_domain, name_user;
34         pstring homedir;
35         
36         if (!pw || !name) {
37                 return False;
38         }
39         
40         /* Resolve the uid number */
41         
42         if (!winbindd_idmap_get_uid_from_rid(domain_name, user_rid, 
43                                              &pw->pw_uid)) {
44                 DEBUG(1, ("error getting user id for rid %d\n", user_rid));
45                 return False;
46         }
47         
48         /* Resolve the gid number */   
49         
50         if (!winbindd_idmap_get_gid_from_rid(domain_name, group_rid, 
51                                              &pw->pw_gid)) {
52                 DEBUG(1, ("error getting group id for rid %d\n", group_rid));
53                 return False;
54         }
55
56         /* Username */
57         
58         safe_strcpy(pw->pw_name, name, sizeof(pw->pw_name) - 1);
59         
60         /* Full name (gecos) */
61         
62         safe_strcpy(pw->pw_gecos, full_name, sizeof(pw->pw_gecos) - 1);
63         
64         /* Home directory and shell - use template config parameters.  The
65            defaults are /tmp for the home directory and /bin/false for
66            shell. */
67         
68         parse_domain_user(name, name_domain, name_user);
69         
70         /* The substitution of %U and %D in the 'template homedir' is done
71            by lp_string() calling standard_sub_basic(). */
72
73         fstrcpy(current_user_info.smb_name, name_user);
74         fstrcpy(current_user_info.domain, name_domain);
75
76         pstrcpy(homedir, lp_template_homedir());
77         
78         safe_strcpy(pw->pw_dir, homedir, sizeof(pw->pw_dir) - 1);
79         
80         safe_strcpy(pw->pw_shell, lp_template_shell(), 
81                     sizeof(pw->pw_shell) - 1);
82         
83         /* Password - set to "x" as we can't generate anything useful here.
84            Authentication can be done using the pam_ntdom module. */
85
86         safe_strcpy(pw->pw_passwd, "x", sizeof(pw->pw_passwd) - 1);
87         
88         return True;
89 }
90
91 /* Return a password structure from a username.  Specify whether cached data 
92    can be returned. */
93
94 enum winbindd_result winbindd_getpwnam_from_user(struct winbindd_cli_state 
95                                                  *state) 
96 {
97         uint32 name_type, user_rid, group_rid;
98         SAM_USERINFO_CTR *user_info;
99         DOM_SID user_sid;
100         fstring name_domain, name_user, name, gecos_name;
101         struct winbindd_domain *domain;
102         
103         DEBUG(3, ("[%5d]: getpwnam %s\n", state->pid,
104                   state->request.data.username));
105         
106         /* Parse domain and username */
107
108         parse_domain_user(state->request.data.username, name_domain, 
109                           name_user);
110
111         /* Reject names that don't have a domain - i.e name_domain contains 
112            the entire name. */
113  
114         if (strequal(name_domain, "")) {
115                 return WINBINDD_ERROR;
116         }
117         
118         /* Get info for the domain */
119         
120         if ((domain = find_domain_from_name(name_domain)) == NULL) {
121                 DEBUG(0, ("could not find domain entry for domain %s\n", 
122                           name_domain));
123                 return WINBINDD_ERROR;
124         }
125
126         if (!domain_handles_open(domain)) {
127                 return WINBINDD_ERROR;
128         }
129
130         /* Check for cached user entry */
131
132         if (winbindd_fetch_user_cache_entry(name_domain, name_user,
133                                             &state->response.data.pw)) {
134                 return WINBINDD_OK;
135         }
136         
137         slprintf(name, sizeof(name) - 1, "%s\\%s", name_domain, name_user);
138         
139         /* Get rid and name type from name.  The following costs 1 packet */
140
141         if (!winbindd_lookup_sid_by_name(name, &user_sid, &name_type)) {
142                 DEBUG(1, ("user '%s' does not exist\n", name_user));
143                 return WINBINDD_ERROR;
144         }
145
146         if (name_type != SID_NAME_USER) {
147                 DEBUG(1, ("name '%s' is not a user name: %d\n", name_user, 
148                           name_type));
149                 return WINBINDD_ERROR;
150         }
151         
152         /* Get some user info.  Split the user rid from the sid obtained
153            from the winbind_lookup_by_name() call and use it in a
154            winbind_lookup_userinfo() */
155     
156         sid_split_rid(&user_sid, &user_rid);
157         
158         /* The following costs 3 packets */
159
160         if (!winbindd_lookup_userinfo(domain, user_rid, &user_info)) {
161                 DEBUG(1, ("pwnam_from_user(): error getting user info for "
162                           "user '%s'\n", name_user));
163                 return WINBINDD_ERROR;
164         }
165     
166         group_rid = user_info->info.id21->group_rid;
167         unistr2_to_ascii(gecos_name, &user_info->info.id21->uni_full_name,
168                          sizeof(gecos_name) - 1);
169         
170         /* Now take all this information and fill in a passwd structure */
171         
172         if (!winbindd_fill_pwent(domain->name, state->request.data.username, 
173                                  user_rid, group_rid, gecos_name,
174                                  &state->response.data.pw)) {
175                 return WINBINDD_ERROR;
176         }
177         
178         winbindd_store_user_cache_entry(name_domain, name_user, 
179                                         &state->response.data.pw);
180         
181         return WINBINDD_OK;
182 }       
183
184 /* Return a password structure given a uid number */
185
186 enum winbindd_result winbindd_getpwnam_from_uid(struct winbindd_cli_state 
187                                                 *state)
188 {
189         DOM_SID user_sid;
190         struct winbindd_domain *domain;
191         uint32 user_rid, group_rid;
192         fstring user_name, gecos_name;
193         enum SID_NAME_USE name_type;
194         SAM_USERINFO_CTR *user_info;
195         gid_t gid;
196         
197         /* Bug out if the uid isn't in the winbind range */
198
199         if ((state->request.data.uid < server_state.uid_low ) ||
200             (state->request.data.uid > server_state.uid_high)) {
201                 return WINBINDD_ERROR;
202         }
203
204         DEBUG(3, ("[%5d]: getpwuid %d\n", state->pid, 
205                   state->request.data.uid));
206         
207         /* Get rid from uid */
208
209         if (!winbindd_idmap_get_rid_from_uid(state->request.data.uid, 
210                                              &user_rid, &domain)) {
211                 DEBUG(1, ("Could not convert uid %d to rid\n", 
212                           state->request.data.uid));
213                 return WINBINDD_ERROR;
214         }
215         
216         if (!domain_handles_open(domain)) {
217                 return WINBINDD_ERROR;
218         }
219
220         /* Check for cached uid entry */
221
222         if (winbindd_fetch_uid_cache_entry(domain->name, 
223                                            state->request.data.uid,
224                                            &state->response.data.pw)) {
225                 return WINBINDD_OK;
226         }
227         
228         /* Get name and name type from rid */
229
230         sid_copy(&user_sid, &domain->sid);
231         sid_append_rid(&user_sid, user_rid);
232         
233         if (!winbindd_lookup_name_by_sid(&user_sid, user_name, &name_type)) {
234                 fstring temp;
235                 
236                 sid_to_string(temp, &user_sid);
237                 DEBUG(1, ("Could not lookup sid %s\n", temp));
238                 return WINBINDD_ERROR;
239         }
240         
241         if (strcmp("\\", lp_winbind_separator())) {
242                 string_sub(user_name, "\\", lp_winbind_separator(), 
243                            sizeof(fstring));
244         }
245
246         /* Get some user info */
247         
248         if (!winbindd_lookup_userinfo(domain, user_rid, &user_info)) {
249                 DEBUG(1, ("pwnam_from_uid(): error getting user info for "
250                           "user '%s'\n", user_name));
251                 return WINBINDD_ERROR;
252         }
253         
254         group_rid = user_info->info.id21->group_rid;
255         unistr2_to_ascii(gecos_name, &user_info->info.id21->uni_full_name,
256                          sizeof(gecos_name) - 1);
257
258         /* Resolve gid number */
259
260         if (!winbindd_idmap_get_gid_from_rid(domain->name, group_rid, &gid)) {
261                 DEBUG(1, ("error getting group id for user %s\n", user_name));
262                 return WINBINDD_ERROR;
263         }
264
265         /* Fill in password structure */
266
267         if (!winbindd_fill_pwent(domain->name, user_name, user_rid, group_rid,
268                                  gecos_name, &state->response.data.pw)) {
269                 return WINBINDD_ERROR;
270         }
271         
272         winbindd_store_uid_cache_entry(domain->name, state->request.data.uid,
273                                        &state->response.data.pw);
274         
275         return WINBINDD_OK;
276 }
277
278 /*
279  * set/get/endpwent functions
280  */
281
282 /* Rewind file pointer for ntdom passwd database */
283
284 enum winbindd_result winbindd_setpwent(struct winbindd_cli_state *state)
285 {
286     struct winbindd_domain *tmp;
287
288     DEBUG(3, ("[%5d]: setpwent\n", state->pid));
289
290     if (state == NULL) return WINBINDD_ERROR;
291     
292     /* Check user has enabled this */
293
294     if (!lp_winbind_enum_users()) {
295             return WINBINDD_ERROR;
296     }
297
298     /* Free old static data if it exists */
299
300     if (state->getpwent_state != NULL) {
301         free_getent_state(state->getpwent_state);
302         state->getpwent_state = NULL;
303     }
304
305     /* Create sam pipes for each domain we know about */
306
307     for(tmp = domain_list; tmp != NULL; tmp = tmp->next) {
308         struct getent_state *domain_state;
309
310         /* Skip domains other than WINBINDD_DOMAIN environment variable */
311
312         if ((strcmp(state->request.domain, "") != 0) &&
313             !check_domain_env(state->request.domain, tmp->name)) {
314                 continue;
315         }
316
317         /* Create a state record for this domain */
318
319         if ((domain_state = (struct getent_state *)
320              malloc(sizeof(struct getent_state))) == NULL) {
321
322             return WINBINDD_ERROR;
323         }
324
325         ZERO_STRUCTP(domain_state);
326         domain_state->domain = tmp;
327
328         /* Add to list of open domains */
329
330         DLIST_ADD(state->getpwent_state, domain_state)
331     }
332
333     return WINBINDD_OK;
334 }
335
336 /* Close file pointer to ntdom passwd database */
337
338 enum winbindd_result winbindd_endpwent(struct winbindd_cli_state *state)
339 {
340     DEBUG(3, ("[%5d]: endpwent\n", state->pid));
341
342     if (state == NULL) return WINBINDD_ERROR;
343
344     free_getent_state(state->getpwent_state);    
345     state->getpwent_state = NULL;
346
347     return WINBINDD_OK;
348 }
349
350 /* Get partial list of domain users for a domain.  We fill in the sam_entries,
351    and num_sam_entries fields with domain user information.  The dispinfo_ndx
352    field is incremented to the index of the next user to fetch.  Return True if
353    some users were returned, False otherwise. */
354
355 #define MAX_FETCH_SAM_ENTRIES 100
356
357 static BOOL get_sam_user_entries(struct getent_state *ent)
358 {
359         uint32 status, num_entries;
360         SAM_DISPINFO_1 info1;
361         SAM_DISPINFO_CTR ctr;
362         struct getpwent_user *name_list = NULL;
363         uint32 group_rid;
364
365         if (ent->got_all_sam_entries) {
366                 return False;
367         }
368
369         ZERO_STRUCT(info1);
370         ZERO_STRUCT(ctr);
371
372         ctr.sam.info1 = &info1;
373
374 #if 0
375         /* Look in cache for entries, else get them direct */
376                     
377         if (winbindd_fetch_user_cache(ent->domain->name,
378                                       (struct getpwent_user **)
379                                       &ent->sam_entries, 
380                                       &ent->num_sam_entries)) {
381                 return True;
382         }
383 #endif
384
385         /* For the moment we set the primary group for every user to be the
386            Domain Users group.  There are serious problems with determining
387            the actual primary group for large domains.  This should really
388            be made into a 'winbind force group' smb.conf parameter or
389            something like that. */ 
390
391         group_rid = DOMAIN_GROUP_RID_USERS;
392
393         if (!domain_handles_open(ent->domain)) {
394                 return WINBINDD_ERROR;
395         }
396
397         /* Free any existing user info */
398
399         if (ent->sam_entries) {
400                 free(ent->sam_entries);
401                 ent->sam_entries = NULL;
402                 ent->num_sam_entries = 0;
403         }
404
405         /* Call query_dispinfo to get a list of usernames and user rids */
406
407         do {
408                 int i;
409                                         
410                 num_entries = 0;
411
412                 status = winbindd_query_dispinfo(ent->domain, 
413                                                  &ent->dispinfo_ndx, 1,
414                                                  &num_entries, &ctr);
415                 
416                 if (num_entries) {
417                         struct getpwent_user *tnl;
418
419                         tnl = (struct getpwent_user *)Realloc(name_list, 
420                                             sizeof(struct getpwent_user) *
421                                             (ent->num_sam_entries + 
422                                              num_entries));
423
424                         if (!tnl) {
425                                 DEBUG(0,("get_sam_user_entries: Realloc failed.\n"));
426                                 if (name_list)
427                                         free(name_list);
428                                 return WINBINDD_ERROR;
429                         } else
430                                 name_list = tnl;
431                 }
432
433                 for (i = 0; i < num_entries; i++) {
434
435                         /* Store account name and gecos */
436
437                         unistr2_to_ascii(
438                                 name_list[ent->num_sam_entries + i].name, 
439                                 &info1.str[i].uni_acct_name, 
440                                 sizeof(fstring));
441
442                         unistr2_to_ascii(
443                                 name_list[ent->num_sam_entries + i].gecos, 
444                                 &info1.str[i].uni_full_name, 
445                                 sizeof(fstring));
446
447                         /* User and group ids */
448
449                         name_list[ent->num_sam_entries + i].user_rid =
450                                 info1.sam[i].rid_user;
451
452                         name_list[ent->num_sam_entries + i].
453                                 group_rid = group_rid;
454                 }
455                 
456                 ent->num_sam_entries += num_entries;
457
458                 if (status != STATUS_MORE_ENTRIES) {
459                         break;
460                 }
461
462         } while (ent->num_sam_entries < MAX_FETCH_SAM_ENTRIES);
463         
464 #if 0
465         /* Fill cache with received entries */
466         
467         winbindd_store_user_cache(ent->domain->name, ent->sam_entries, 
468                                   ent->num_sam_entries);
469 #endif
470
471         /* Fill in remaining fields */
472         
473         ent->sam_entries = name_list;
474         ent->sam_entry_index = 0;
475         ent->got_all_sam_entries = (status != STATUS_MORE_ENTRIES);
476
477         return ent->num_sam_entries > 0;
478 }
479
480 /* Fetch next passwd entry from ntdom database */
481
482 #define MAX_GETPWENT_USERS 500
483
484 enum winbindd_result winbindd_getpwent(struct winbindd_cli_state *state)
485 {
486         struct getent_state *ent;
487         struct winbindd_pw *user_list;
488         int num_users, user_list_ndx = 0, i;
489         char *sep;
490
491         DEBUG(3, ("[%5d]: getpwent\n", state->pid));
492
493         if (state == NULL) return WINBINDD_ERROR;
494
495         /* Check user has enabled this */
496
497         if (!lp_winbind_enum_users()) {
498                 return WINBINDD_ERROR;
499         }
500
501         /* Allocate space for returning a chunk of users */
502
503         num_users = MIN(MAX_GETPWENT_USERS, state->request.data.num_entries);
504         
505         if ((state->response.extra_data = 
506              malloc(num_users * sizeof(struct winbindd_pw))) == NULL) {
507                 return WINBINDD_ERROR;
508         }
509
510         memset(state->response.extra_data, 0, num_users * 
511                sizeof(struct winbindd_pw));
512
513         user_list = (struct winbindd_pw *)state->response.extra_data;
514         sep = lp_winbind_separator();
515         
516         if (!(ent = state->getpwent_state)) {
517                 return WINBINDD_ERROR;
518         }
519
520         /* Start sending back users */
521
522         for (i = 0; i < num_users; i++) {
523                 struct getpwent_user *name_list = NULL;
524                 fstring domain_user_name;
525                 uint32 result;
526
527                 /* Do we need to fetch another chunk of users? */
528
529                 if (ent->num_sam_entries == ent->sam_entry_index) {
530
531                         while(ent && !get_sam_user_entries(ent)) {
532                                 struct getent_state *next_ent;
533
534                                 /* Free state information for this domain */
535
536                                 safe_free(ent->sam_entries);
537                                 ent->sam_entries = NULL;
538
539                                 next_ent = ent->next;
540                                 DLIST_REMOVE(state->getpwent_state, ent);
541
542                                 free(ent);
543                                 ent = next_ent;
544                         }
545  
546                         /* No more domains */
547
548                         if (!ent) break;
549                 }
550
551                 name_list = ent->sam_entries;
552
553                 /* Skip machine accounts */
554
555                 if (name_list[ent->sam_entry_index].
556                     name[strlen(name_list[ent->sam_entry_index].name) - 1] 
557                     == '$') {
558                         ent->sam_entry_index++;
559                         continue;
560                 }
561
562                 /* Lookup user info */
563                 
564                 slprintf(domain_user_name, sizeof(domain_user_name) - 1,
565                          "%s%s%s", ent->domain->name, sep,
566                          name_list[ent->sam_entry_index].name);
567                 
568                 result = winbindd_fill_pwent(
569                         ent->domain->name, 
570                         domain_user_name,
571                         name_list[ent->sam_entry_index].user_rid,
572                         name_list[ent->sam_entry_index].group_rid,
573                         name_list[ent->sam_entry_index].gecos,
574                         &user_list[user_list_ndx]);
575                 
576                 ent->sam_entry_index++;
577                 
578                 /* Add user to return list */
579                 
580                 if (result) {
581                                 
582                         user_list_ndx++;
583                         state->response.data.num_entries++;
584                         state->response.length += 
585                                 sizeof(struct winbindd_pw);
586
587                 } else {
588                         DEBUG(1, ("could not lookup domain user %s\n",
589                                   domain_user_name));
590                 }
591                 
592         }
593
594         /* Out of domains */
595
596         return (user_list_ndx > 0) ? WINBINDD_OK : WINBINDD_ERROR;
597 }
598
599 /* List domain users without mapping to unix ids */
600
601 enum winbindd_result winbindd_list_users(struct winbindd_cli_state *state)
602 {
603         struct winbindd_domain *domain;
604         SAM_DISPINFO_CTR ctr;
605         SAM_DISPINFO_1 info1;
606         uint32 num_entries = 0, total_entries = 0;
607         char *ted, *extra_data = NULL;
608         int extra_data_len = 0;
609
610         DEBUG(3, ("[%5d]: list users\n", state->pid));
611
612         /* Enumerate over trusted domains */
613
614         ctr.sam.info1 = &info1;
615
616         for (domain = domain_list; domain; domain = domain->next) {
617                 uint32 status, start_ndx = 0;
618
619                 /* Skip domains other than WINBINDD_DOMAIN environment
620                    variable */ 
621
622                 if ((strcmp(state->request.domain, "") != 0) &&
623                     !check_domain_env(state->request.domain, domain->name)) {
624                         continue;
625                 }
626
627                 if (!domain_handles_open(domain)) {
628                         continue;
629                 }
630
631                 /* Query display info */
632
633                 do {
634                         int i;
635
636                         status = winbindd_query_dispinfo(domain, &start_ndx, 
637                                                          1, &num_entries, 
638                                                          &ctr);
639
640                         if (num_entries == 0) {
641                                 continue;
642                         }
643
644                         /* Allocate some memory for extra data */
645
646                         total_entries += num_entries;
647                         
648                         ted = Realloc(extra_data, sizeof(fstring) * 
649                                              total_entries);
650                         
651                         if (!ted) {
652                                 DEBUG(0,("winbindd_list_users: failed to enlarge buffer!\n"));
653                                 if (extra_data) free(extra_data);
654                                 return WINBINDD_ERROR;
655                         }
656                         else extra_data = ted;
657                         
658                         /* Pack user list into extra data fields */
659                         
660                         for (i = 0; i < num_entries; i++) {
661                                 UNISTR2 *uni_acct_name;
662                                 fstring acct_name, name;
663
664                                 /* Convert unistring to ascii */
665                                 
666                                 uni_acct_name = &ctr.sam.info1->str[i]. 
667                                         uni_acct_name;
668                                 unistr2_to_ascii(acct_name, uni_acct_name,
669                                                  sizeof(acct_name) - 1);
670                                                  
671                                 slprintf(name, sizeof(name) - 1, "%s%s%s",
672                                          domain->name, lp_winbind_separator(),
673                                          acct_name);
674
675                                 /* Append to extra data */
676                         
677                                 memcpy(&extra_data[extra_data_len], name, 
678                                        strlen(name));
679                                 extra_data_len += strlen(name);
680                                 
681                                 extra_data[extra_data_len++] = ',';
682                         }   
683                 } while (status == STATUS_MORE_ENTRIES);
684         }
685
686         /* Assign extra_data fields in response structure */
687
688         if (extra_data) {
689                 extra_data[extra_data_len - 1] = '\0';
690                 state->response.extra_data = extra_data;
691                 state->response.length += extra_data_len;
692         }
693
694         /* No domains responded but that's still OK so don't return an
695            error. */
696
697         return WINBINDD_OK;
698 }