Fix for bug #703, try lowercase netgroups lookups.
[jra/samba/.git] / source3 / lib / username.c
1 /* 
2    Unix SMB/CIFS implementation.
3    Username handling
4    Copyright (C) Andrew Tridgell 1992-1998
5    Copyright (C) Jeremy Allison 1997-2001.
6    
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11    
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16    
17    You should have received a copy of the GNU General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 */
21
22 #include "includes.h"
23
24 /* internal functions */
25 static struct passwd *uname_string_combinations(char *s, struct passwd * (*fn) (const char *), int N);
26 static struct passwd *uname_string_combinations2(char *s, int offset, struct passwd * (*fn) (const char *), int N);
27
28 /*****************************************************************
29  Check if a user or group name is local (this is a *local* name for
30  *local* people, there's nothing for you here...).
31 *****************************************************************/
32
33 static BOOL name_is_local(const char *name)
34 {
35         return !(strchr_m(name, *lp_winbind_separator()));
36 }
37
38 /*****************************************************************
39  Splits passed user or group name to domain and user/group name parts
40  Returns True if name was splitted and False otherwise.
41 *****************************************************************/
42
43 BOOL split_domain_and_name(const char *name, char *domain, char* username)
44 {
45         char *p = strchr(name,*lp_winbind_separator());
46         
47         
48         /* Parse a string of the form DOMAIN/user into a domain and a user */
49         DEBUG(10,("split_domain_and_name: checking whether name |%s| local or not\n", name));
50         
51         if (p) {
52                 fstrcpy(username, p+1);
53                 fstrcpy(domain, name);
54                 domain[PTR_DIFF(p, name)] = 0;
55         } else if (lp_winbind_use_default_domain()) {
56                 fstrcpy(username, name);
57                 fstrcpy(domain, lp_workgroup());
58         } else {
59                 return False;
60         }
61
62         DEBUG(10,("split_domain_and_name: all is fine, domain is |%s| and name is |%s|\n", domain, username));
63         return True;
64 }
65
66 /****************************************************************************
67  Get a users home directory.
68 ****************************************************************************/
69
70 char *get_user_home_dir(const char *user)
71 {
72         static struct passwd *pass;
73
74         /* Ensure the user exists. */
75
76         pass = Get_Pwnam(user);
77
78         if (!pass)
79                 return(NULL);
80         /* Return home directory from struct passwd. */
81
82         return(pass->pw_dir);      
83 }
84
85 /*******************************************************************
86  Map a username from a dos name to a unix name by looking in the username
87  map. Note that this modifies the name in place.
88  This is the main function that should be called *once* on
89  any incoming or new username - in order to canonicalize the name.
90  This is being done to de-couple the case conversions from the user mapping
91  function. Previously, the map_username was being called
92  every time Get_Pwnam was called.
93  Returns True if username was changed, false otherwise.
94 ********************************************************************/
95
96 BOOL map_username(char *user)
97 {
98         static BOOL initialised=False;
99         static fstring last_from,last_to;
100         XFILE *f;
101         char *mapfile = lp_username_map();
102         char *s;
103         pstring buf;
104         BOOL mapped_user = False;
105
106         if (!*user)
107                 return False;
108
109         if (!*mapfile)
110                 return False;
111
112         if (!initialised) {
113                 *last_from = *last_to = 0;
114                 initialised = True;
115         }
116
117         if (strequal(user,last_to))
118                 return False;
119
120         if (strequal(user,last_from)) {
121                 DEBUG(3,("Mapped user %s to %s\n",user,last_to));
122                 fstrcpy(user,last_to);
123                 return True;
124         }
125   
126         f = x_fopen(mapfile,O_RDONLY, 0);
127         if (!f) {
128                 DEBUG(0,("can't open username map %s. Error %s\n",mapfile, strerror(errno) ));
129                 return False;
130         }
131
132         DEBUG(4,("Scanning username map %s\n",mapfile));
133
134         while((s=fgets_slash(buf,sizeof(buf),f))!=NULL) {
135                 char *unixname = s;
136                 char *dosname = strchr_m(unixname,'=');
137                 char **dosuserlist;
138                 BOOL return_if_mapped = False;
139
140                 if (!dosname)
141                         continue;
142
143                 *dosname++ = 0;
144
145                 while (isspace((int)*unixname))
146                         unixname++;
147
148                 if ('!' == *unixname) {
149                         return_if_mapped = True;
150                         unixname++;
151                         while (*unixname && isspace((int)*unixname))
152                                 unixname++;
153                 }
154     
155                 if (!*unixname || strchr_m("#;",*unixname))
156                         continue;
157
158                 {
159                         int l = strlen(unixname);
160                         while (l && isspace((int)unixname[l-1])) {
161                                 unixname[l-1] = 0;
162                                 l--;
163                         }
164                 }
165
166                 dosuserlist = str_list_make(dosname, NULL);
167                 if (!dosuserlist) {
168                         DEBUG(0,("Unable to build user list\n"));
169                         return False;
170                 }
171
172                 if (strchr_m(dosname,'*') || user_in_list(user, (const char **)dosuserlist, NULL, 0)) {
173                         DEBUG(3,("Mapped user %s to %s\n",user,unixname));
174                         mapped_user = True;
175                         fstrcpy(last_from,user);
176                         sscanf(unixname,"%s",user);
177                         fstrcpy(last_to,user);
178                         if(return_if_mapped) {
179                                 str_list_free (&dosuserlist);
180                                 x_fclose(f);
181                                 return True;
182                         }
183                 }
184     
185                 str_list_free (&dosuserlist);
186         }
187
188         x_fclose(f);
189
190         /*
191          * Setup the last_from and last_to as an optimization so 
192          * that we don't scan the file again for the same user.
193          */
194         fstrcpy(last_from,user);
195         fstrcpy(last_to,user);
196
197         return mapped_user;
198 }
199
200 /****************************************************************************
201  * A wrapper for sys_getpwnam().  The following variations are tried:
202  *   - as transmitted
203  *   - in all lower case if this differs from transmitted
204  *   - in all upper case if this differs from transmitted
205  *   - using lp_usernamelevel() for permutations.
206 ****************************************************************************/
207
208 static struct passwd *Get_Pwnam_ret = NULL;
209
210 static struct passwd *Get_Pwnam_internals(const char *user, char *user2)
211 {
212         struct passwd *ret = NULL;
213
214         if (!user2 || !(*user2))
215                 return(NULL);
216
217         if (!user || !(*user))
218                 return(NULL);
219
220         /* Try in all lower case first as this is the most 
221            common case on UNIX systems */
222         strlower_m(user2);
223         DEBUG(5,("Trying _Get_Pwnam(), username as lowercase is %s\n",user2));
224         ret = getpwnam_alloc(user2);
225         if(ret)
226                 goto done;
227
228         /* Try as given, if username wasn't originally lowercase */
229         if(strcmp(user, user2) != 0) {
230                 DEBUG(5,("Trying _Get_Pwnam(), username as given is %s\n", user));
231                 ret = getpwnam_alloc(user);
232                 if(ret)
233                         goto done;
234         }
235
236         /* Try as uppercase, if username wasn't originally uppercase */
237         strupper_m(user2);
238         if(strcmp(user, user2) != 0) {
239                 DEBUG(5,("Trying _Get_Pwnam(), username as uppercase is %s\n", user2));
240                 ret = getpwnam_alloc(user2);
241                 if(ret)
242                         goto done;
243         }
244
245         /* Try all combinations up to usernamelevel */
246         strlower_m(user2);
247         DEBUG(5,("Checking combinations of %d uppercase letters in %s\n", lp_usernamelevel(), user2));
248         ret = uname_string_combinations(user2, getpwnam_alloc, lp_usernamelevel());
249
250 done:
251         DEBUG(5,("Get_Pwnam_internals %s find user [%s]!\n",ret ? "did":"didn't", user));
252
253         /* This call used to just return the 'passwd' static buffer.
254            This could then have accidental reuse implications, so 
255            we now malloc a copy, and free it in the next use.
256
257            This should cause the (ab)user to segfault if it 
258            uses an old struct. 
259            
260            This is better than useing the wrong data in security
261            critical operations.
262
263            The real fix is to make the callers free the returned 
264            malloc'ed data.
265         */
266
267         if (Get_Pwnam_ret) {
268                 passwd_free(&Get_Pwnam_ret);
269         }
270         
271         Get_Pwnam_ret = ret;
272
273         return ret;
274 }
275
276 /****************************************************************************
277  Get_Pwnam wrapper without modification.
278   NOTE: This with NOT modify 'user'! 
279 ****************************************************************************/
280
281 struct passwd *Get_Pwnam(const char *user)
282 {
283         fstring user2;
284         struct passwd *ret;
285
286         fstrcpy(user2, user);
287
288         DEBUG(5,("Finding user %s\n", user));
289
290         ret = Get_Pwnam_internals(user, user2);
291         
292         return ret;  
293 }
294
295 /****************************************************************************
296  Check if a user is in a netgroup user list. If at first we don't succeed,
297  try lower case.
298 ****************************************************************************/
299
300 static BOOL user_in_netgroup_list(const char *user, const char *ngname)
301 {
302 #ifdef HAVE_NETGROUP
303         static char *mydomain = NULL;
304         fstring lowercase_user, lowercase_ngname;
305
306         if (mydomain == NULL)
307                 yp_get_default_domain(&mydomain);
308
309         if(mydomain == NULL) {
310                 DEBUG(5,("Unable to get default yp domain\n"));
311                 return False;
312         }
313
314         DEBUG(5,("looking for user %s of domain %s in netgroup %s\n",
315                 user, mydomain, ngname));
316         DEBUG(5,("innetgr is %s\n", innetgr(ngname, NULL, user, mydomain)
317                 ? "TRUE" : "FALSE"));
318
319         if (innetgr(ngname, NULL, user, mydomain))
320                 return (True);
321
322         /*
323          * Ok, innetgr is case sensitive. Try once more with lowercase
324          * just in case. Attempt to fix #703. JRA.
325          */
326
327         fstrcpy(lowercase_user, user);
328         strlower_m(lowercase_user);
329         fstrcpy(lowercase_ngname, ngname);
330         strlower_m(lowercase_ngname);
331         
332         if (innetgr(lowercase_ngname, NULL, lowercase_user, mydomain))
333                 return (True);
334
335 #endif /* HAVE_NETGROUP */
336         return False;
337 }
338
339 /****************************************************************************
340  Check if a user is in a winbind group.
341 ****************************************************************************/
342   
343 static BOOL user_in_winbind_group_list(const char *user, const char *gname, BOOL *winbind_answered)
344 {
345         int i;
346         gid_t gid, gid_low, gid_high;
347         BOOL ret = False;
348         static gid_t *groups = NULL;
349         static int num_groups = 0;
350         static fstring last_user = "";
351  
352         *winbind_answered = False;
353  
354         if ((gid = nametogid(gname)) == (gid_t)-1) {
355                 DEBUG(0,("user_in_winbind_group_list: nametogid for group %s failed.\n",
356                         gname ));
357                 goto err;
358         }
359
360         if (!lp_idmap_gid(&gid_low, &gid_high)) {
361                 DEBUG(4, ("winbind gid range not configured, therefore %s cannot be a winbind group\n", gname));
362                 goto err;
363         }
364
365         if (gid < gid_low || gid > gid_high) {
366                 DEBUG(4, ("group %s is not a winbind group\n", gname));
367                 goto err;
368         }
369  
370         /* try to user the last user we looked up */
371         /* otherwise fall back to lookups */
372         
373         if ( !strequal( last_user, user ) || !groups )
374         {
375                 /* clear any cached information */
376                 
377                 SAFE_FREE(groups);
378                 fstrcpy( last_user, "" );
379
380                 /*
381                  * Get the gid's that this user belongs to.
382                  */
383  
384                 if ((num_groups = winbind_getgroups(user, &groups)) == -1)
385                         return False;
386                         
387                 if ( num_groups == -1 )
388                         return False;
389  
390                 if ( num_groups == 0 ) {
391                         *winbind_answered = True;
392                         return False;
393                 }
394                 
395                 /* save the last username */
396                 
397                 fstrcpy( last_user, user );
398                 
399         }
400         else 
401                 DEBUG(10,("user_in_winbind_group_list: using cached user groups for [%s]\n", user));
402  
403         if ( DEBUGLEVEL >= 10 ) {
404                 DEBUG(10,("user_in_winbind_group_list: using groups -- "));
405                 for ( i=0; i<num_groups; i++ )
406                         DEBUGADD(10,("%lu ", (unsigned long)groups[i]));
407                 DEBUGADD(10,("\n"));    
408         }
409  
410         /*
411          * Now we have the gid list for this user - convert the gname
412          * to a gid_t via either winbind or the local UNIX lookup and do the comparison.
413          */
414  
415         for (i = 0; i < num_groups; i++) {
416                 if (gid == groups[i]) {
417                         ret = True;
418                         break;
419                 }
420         }
421  
422         *winbind_answered = True;
423         SAFE_FREE(groups);
424         return ret;
425  
426    err:
427  
428         *winbind_answered = False;
429         SAFE_FREE(groups);
430         return False;
431 }             
432  
433 /****************************************************************************
434  Check if a user is in a UNIX group.
435 ****************************************************************************/
436
437 BOOL user_in_unix_group_list(const char *user,const char *gname)
438 {
439         struct passwd *pass = Get_Pwnam(user);
440         struct sys_userlist *user_list;
441         struct sys_userlist *member;
442
443         DEBUG(10,("user_in_unix_group_list: checking user %s in group %s\n", user, gname));
444
445         /*
446          * We need to check the users primary group as this
447          * group is implicit and often not listed in the group database.
448          */
449  
450         if (pass) {
451                 if (strequal(gname,gidtoname(pass->pw_gid))) {
452                         DEBUG(10,("user_in_unix_group_list: group %s is primary group.\n", gname ));
453                         return True;
454                 }
455         }
456  
457         user_list = get_users_in_group(gname);
458         if (user_list == NULL) {
459                 DEBUG(10,("user_in_unix_group_list: no such group %s\n", gname ));
460                 return False;
461         }
462
463         for (member = user_list; member; member = member->next) {
464                 DEBUG(10,("user_in_unix_group_list: checking user %s against member %s\n",
465                         user, member->unix_name ));
466                 if (strequal(member->unix_name,user)) {
467                         free_userlist(user_list);
468                         return(True);
469                 }
470         }
471
472         free_userlist(user_list);
473         return False;
474 }             
475
476 /****************************************************************************
477  Check if a user is in a group list. Ask winbind first, then use UNIX.
478 ****************************************************************************/
479
480 BOOL user_in_group_list(const char *user, const char *gname, gid_t *groups, size_t n_groups)
481 {
482         BOOL winbind_answered = False;
483         BOOL ret;
484         gid_t gid;
485         unsigned i;
486
487         gid = nametogid(gname);
488         if (gid == (gid_t)-1) 
489                 return False;
490
491         if (groups && n_groups > 0) {
492                 for (i=0; i < n_groups; i++) {
493                         if (groups[i] == gid) {
494                                 return True;
495                         }
496                 }
497                 return False;
498         }
499
500         /* fallback if we don't yet have the group list */
501
502         ret = user_in_winbind_group_list(user, gname, &winbind_answered);
503         if (!winbind_answered)
504                 ret = user_in_unix_group_list(user, gname);
505
506         if (ret)
507                 DEBUG(10,("user_in_group_list: user |%s| is in group |%s|\n", user, gname));
508         return ret;
509 }
510
511 /****************************************************************************
512  Check if a user is in a user list - can check combinations of UNIX
513  and netgroup lists.
514 ****************************************************************************/
515
516 BOOL user_in_list(const char *user,const char **list, gid_t *groups, size_t n_groups)
517 {
518         if (!list || !*list)
519                 return False;
520
521         DEBUG(10,("user_in_list: checking user %s in list\n", user));
522
523         while (*list) {
524
525                 DEBUG(10,("user_in_list: checking user |%s| against |%s|\n", user, *list));
526
527                 /*
528                  * Check raw username.
529                  */
530                 if (strequal(user, *list))
531                         return(True);
532
533                 /*
534                  * Now check to see if any combination
535                  * of UNIX and netgroups has been specified.
536                  */
537
538                 if(**list == '@') {
539                         /*
540                          * Old behaviour. Check netgroup list
541                          * followed by UNIX list.
542                          */
543                         if(user_in_netgroup_list(user, *list +1))
544                                 return True;
545                         if(user_in_group_list(user, *list +1, groups, n_groups))
546                                 return True;
547                 } else if (**list == '+') {
548
549                         if((*(*list +1)) == '&') {
550                                 /*
551                                  * Search UNIX list followed by netgroup.
552                                  */
553                                 if(user_in_group_list(user, *list +2, groups, n_groups))
554                                         return True;
555                                 if(user_in_netgroup_list(user, *list +2))
556                                         return True;
557
558                         } else {
559
560                                 /*
561                                  * Just search UNIX list.
562                                  */
563
564                                 if(user_in_group_list(user, *list +1, groups, n_groups))
565                                         return True;
566                         }
567
568                 } else if (**list == '&') {
569
570                         if(*(*list +1) == '+') {
571                                 /*
572                                  * Search netgroup list followed by UNIX list.
573                                  */
574                                 if(user_in_netgroup_list(user, *list +2))
575                                         return True;
576                                 if(user_in_group_list(user, *list +2, groups, n_groups))
577                                         return True;
578                         } else {
579                                 /*
580                                  * Just search netgroup list.
581                                  */
582                                 if(user_in_netgroup_list(user, *list +1))
583                                         return True;
584                         }
585                 } else if (!name_is_local(*list)) {
586                         /*
587                          * If user name did not match and token is not
588                          * a unix group and the token has a winbind separator in the
589                          * name then see if it is a Windows group.
590                          */
591
592                         DOM_SID g_sid;
593                         enum SID_NAME_USE name_type;
594                         BOOL winbind_answered = False;
595                         BOOL ret;
596                         fstring groupname, domain;
597                         
598                         /* Parse a string of the form DOMAIN/user into a domain and a user */
599
600                         char *p = strchr(*list,*lp_winbind_separator());
601                         
602                         DEBUG(10,("user_in_list: checking if user |%s| is in winbind group |%s|\n", user, *list));
603
604                         if (p) {
605                                 fstrcpy(groupname, p+1);
606                                 fstrcpy(domain, *list);
607                                 domain[PTR_DIFF(p, *list)] = 0;
608
609                                 /* Check to see if name is a Windows group;  Win2k native mode DCs
610                                    will return domain local groups; while NT4 or mixed mode 2k DCs
611                                    will not */
612                         
613                                 if ( winbind_lookup_name(domain, groupname, &g_sid, &name_type) 
614                                         && ( name_type==SID_NAME_DOM_GRP || 
615                                            (strequal(lp_workgroup(), domain) && name_type==SID_NAME_ALIAS) ) )
616                                 {
617                                         
618                                         /* Check if user name is in the Windows group */
619                                         ret = user_in_winbind_group_list(user, *list, &winbind_answered);
620                                         
621                                         if (winbind_answered && ret == True) {
622                                                 DEBUG(10,("user_in_list: user |%s| is in winbind group |%s|\n", user, *list));
623                                                 return ret;
624                                         }
625                                 }
626                         }
627                 }
628     
629                 list++;
630         }
631         return(False);
632 }
633
634 /* The functions below have been taken from password.c and slightly modified */
635 /****************************************************************************
636  Apply a function to upper/lower case combinations
637  of a string and return true if one of them returns true.
638  Try all combinations with N uppercase letters.
639  offset is the first char to try and change (start with 0)
640  it assumes the string starts lowercased
641 ****************************************************************************/
642
643 static struct passwd *uname_string_combinations2(char *s,int offset,struct passwd *(*fn)(const char *),int N)
644 {
645         ssize_t len = (ssize_t)strlen(s);
646         int i;
647         struct passwd *ret;
648
649         if (N <= 0 || offset >= len)
650                 return(fn(s));
651
652         for (i=offset;i<(len-(N-1));i++) {
653                 char c = s[i];
654                 if (!islower((int)c))
655                         continue;
656                 s[i] = toupper(c);
657                 ret = uname_string_combinations2(s,i+1,fn,N-1);
658                 if(ret)
659                         return(ret);
660                 s[i] = c;
661         }
662         return(NULL);
663 }
664
665 /****************************************************************************
666  Apply a function to upper/lower case combinations
667  of a string and return true if one of them returns true.
668  Try all combinations with up to N uppercase letters.
669  offset is the first char to try and change (start with 0)
670  it assumes the string starts lowercased
671 ****************************************************************************/
672
673 static struct passwd * uname_string_combinations(char *s,struct passwd * (*fn)(const char *),int N)
674 {
675         int n;
676         struct passwd *ret;
677
678         for (n=1;n<=N;n++) {
679                 ret = uname_string_combinations2(s,0,fn,n);
680                 if(ret)
681                         return(ret);
682         }  
683         return(NULL);
684 }
685