r2070: Let's try to overload srnlen and strndup for AIX where they are natly broken.
[tprouty/samba.git] / source / 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         if ( *user == '\0' ) {
287                 DEBUG(10,("Get_Pwnam: empty username!\n"));
288                 return NULL;
289         }
290
291         fstrcpy(user2, user);
292
293         DEBUG(5,("Finding user %s\n", user));
294
295         ret = Get_Pwnam_internals(user, user2);
296         
297         return ret;  
298 }
299
300 /****************************************************************************
301  Check if a user is in a netgroup user list. If at first we don't succeed,
302  try lower case.
303 ****************************************************************************/
304
305 static BOOL user_in_netgroup_list(const char *user, const char *ngname)
306 {
307 #ifdef HAVE_NETGROUP
308         static char *mydomain = NULL;
309         fstring lowercase_user;
310
311         if (mydomain == NULL)
312                 yp_get_default_domain(&mydomain);
313
314         if(mydomain == NULL) {
315                 DEBUG(5,("Unable to get default yp domain\n"));
316                 return False;
317         }
318
319         DEBUG(5,("looking for user %s of domain %s in netgroup %s\n",
320                 user, mydomain, ngname));
321
322         if (innetgr(ngname, NULL, user, mydomain)) {
323                 DEBUG(5,("user_in_netgroup_list: Found\n"));
324                 return (True);
325         } else {
326
327                 /*
328                  * Ok, innetgr is case sensitive. Try once more with lowercase
329                  * just in case. Attempt to fix #703. JRA.
330                  */
331
332                 fstrcpy(lowercase_user, user);
333                 strlower_m(lowercase_user);
334         
335                 DEBUG(5,("looking for user %s of domain %s in netgroup %s\n",
336                         lowercase_user, mydomain, ngname));
337
338                 if (innetgr(ngname, NULL, lowercase_user, mydomain)) {
339                         DEBUG(5,("user_in_netgroup_list: Found\n"));
340                         return (True);
341                 }
342         }
343 #endif /* HAVE_NETGROUP */
344         return False;
345 }
346
347 /****************************************************************************
348  Check if a user is in a winbind group.
349 ****************************************************************************/
350   
351 static BOOL user_in_winbind_group_list(const char *user, const char *gname, BOOL *winbind_answered)
352 {
353         int i;
354         gid_t gid, gid_low, gid_high;
355         BOOL ret = False;
356         static gid_t *groups = NULL;
357         static int num_groups = 0;
358         static fstring last_user = "";
359  
360         *winbind_answered = False;
361  
362         if ((gid = nametogid(gname)) == (gid_t)-1) {
363                 DEBUG(0,("user_in_winbind_group_list: nametogid for group %s failed.\n",
364                         gname ));
365                 goto err;
366         }
367
368         if (!lp_idmap_gid(&gid_low, &gid_high)) {
369                 DEBUG(4, ("winbind gid range not configured, therefore %s cannot be a winbind group\n", gname));
370                 goto err;
371         }
372
373         if (gid < gid_low || gid > gid_high) {
374                 DEBUG(4, ("group %s is not a winbind group\n", gname));
375                 goto err;
376         }
377  
378         /* try to user the last user we looked up */
379         /* otherwise fall back to lookups */
380         
381         if ( !strequal( last_user, user ) || !groups )
382         {
383                 /* clear any cached information */
384                 
385                 SAFE_FREE(groups);
386                 fstrcpy( last_user, "" );
387
388                 /*
389                  * Get the gid's that this user belongs to.
390                  */
391  
392                 if ((num_groups = winbind_getgroups(user, &groups)) == -1)
393                         return False;
394                         
395                 if ( num_groups == -1 )
396                         return False;
397  
398                 if ( num_groups == 0 ) {
399                         *winbind_answered = True;
400                         return False;
401                 }
402                 
403                 /* save the last username */
404                 
405                 fstrcpy( last_user, user );
406                 
407         }
408         else 
409                 DEBUG(10,("user_in_winbind_group_list: using cached user groups for [%s]\n", user));
410  
411         if ( DEBUGLEVEL >= 10 ) {
412                 DEBUG(10,("user_in_winbind_group_list: using groups -- "));
413                 for ( i=0; i<num_groups; i++ )
414                         DEBUGADD(10,("%lu ", (unsigned long)groups[i]));
415                 DEBUGADD(10,("\n"));    
416         }
417  
418         /*
419          * Now we have the gid list for this user - convert the gname
420          * to a gid_t via either winbind or the local UNIX lookup and do the comparison.
421          */
422  
423         for (i = 0; i < num_groups; i++) {
424                 if (gid == groups[i]) {
425                         ret = True;
426                         break;
427                 }
428         }
429  
430         *winbind_answered = True;
431         SAFE_FREE(groups);
432         return ret;
433  
434    err:
435  
436         *winbind_answered = False;
437         SAFE_FREE(groups);
438         return False;
439 }             
440  
441 /****************************************************************************
442  Check if a user is in a UNIX group.
443 ****************************************************************************/
444
445 BOOL user_in_unix_group_list(const char *user,const char *gname)
446 {
447         struct passwd *pass = Get_Pwnam(user);
448         struct sys_userlist *user_list;
449         struct sys_userlist *member;
450
451         DEBUG(10,("user_in_unix_group_list: checking user %s in group %s\n", user, gname));
452
453         /*
454          * We need to check the users primary group as this
455          * group is implicit and often not listed in the group database.
456          */
457  
458         if (pass) {
459                 if (strequal(gname,gidtoname(pass->pw_gid))) {
460                         DEBUG(10,("user_in_unix_group_list: group %s is primary group.\n", gname ));
461                         return True;
462                 }
463         }
464  
465         user_list = get_users_in_group(gname);
466         if (user_list == NULL) {
467                 DEBUG(10,("user_in_unix_group_list: no such group %s\n", gname ));
468                 return False;
469         }
470
471         for (member = user_list; member; member = member->next) {
472                 DEBUG(10,("user_in_unix_group_list: checking user %s against member %s\n",
473                         user, member->unix_name ));
474                 if (strequal(member->unix_name,user)) {
475                         free_userlist(user_list);
476                         return(True);
477                 }
478         }
479
480         free_userlist(user_list);
481         return False;
482 }             
483
484 /****************************************************************************
485  Check if a user is in a group list. Ask winbind first, then use UNIX.
486 ****************************************************************************/
487
488 BOOL user_in_group_list(const char *user, const char *gname, gid_t *groups, size_t n_groups)
489 {
490         BOOL winbind_answered = False;
491         BOOL ret;
492         gid_t gid;
493         unsigned i;
494
495         gid = nametogid(gname);
496         if (gid == (gid_t)-1) 
497                 return False;
498
499         if (groups && n_groups > 0) {
500                 for (i=0; i < n_groups; i++) {
501                         if (groups[i] == gid) {
502                                 return True;
503                         }
504                 }
505                 return False;
506         }
507
508         /* fallback if we don't yet have the group list */
509
510         ret = user_in_winbind_group_list(user, gname, &winbind_answered);
511         if (!winbind_answered)
512                 ret = user_in_unix_group_list(user, gname);
513
514         if (ret)
515                 DEBUG(10,("user_in_group_list: user |%s| is in group |%s|\n", user, gname));
516         return ret;
517 }
518
519 /****************************************************************************
520  Check if a user is in a user list - can check combinations of UNIX
521  and netgroup lists.
522 ****************************************************************************/
523
524 BOOL user_in_list(const char *user,const char **list, gid_t *groups, size_t n_groups)
525 {
526         if (!list || !*list)
527                 return False;
528
529         DEBUG(10,("user_in_list: checking user %s in list\n", user));
530
531         while (*list) {
532
533                 DEBUG(10,("user_in_list: checking user |%s| against |%s|\n", user, *list));
534
535                 /*
536                  * Check raw username.
537                  */
538                 if (strequal(user, *list))
539                         return(True);
540
541                 /*
542                  * Now check to see if any combination
543                  * of UNIX and netgroups has been specified.
544                  */
545
546                 if(**list == '@') {
547                         /*
548                          * Old behaviour. Check netgroup list
549                          * followed by UNIX list.
550                          */
551                         if(user_in_netgroup_list(user, *list +1))
552                                 return True;
553                         if(user_in_group_list(user, *list +1, groups, n_groups))
554                                 return True;
555                 } else if (**list == '+') {
556
557                         if((*(*list +1)) == '&') {
558                                 /*
559                                  * Search UNIX list followed by netgroup.
560                                  */
561                                 if(user_in_group_list(user, *list +2, groups, n_groups))
562                                         return True;
563                                 if(user_in_netgroup_list(user, *list +2))
564                                         return True;
565
566                         } else {
567
568                                 /*
569                                  * Just search UNIX list.
570                                  */
571
572                                 if(user_in_group_list(user, *list +1, groups, n_groups))
573                                         return True;
574                         }
575
576                 } else if (**list == '&') {
577
578                         if(*(*list +1) == '+') {
579                                 /*
580                                  * Search netgroup list followed by UNIX list.
581                                  */
582                                 if(user_in_netgroup_list(user, *list +2))
583                                         return True;
584                                 if(user_in_group_list(user, *list +2, groups, n_groups))
585                                         return True;
586                         } else {
587                                 /*
588                                  * Just search netgroup list.
589                                  */
590                                 if(user_in_netgroup_list(user, *list +1))
591                                         return True;
592                         }
593                 } else if (!name_is_local(*list)) {
594                         /*
595                          * If user name did not match and token is not
596                          * a unix group and the token has a winbind separator in the
597                          * name then see if it is a Windows group.
598                          */
599
600                         DOM_SID g_sid;
601                         enum SID_NAME_USE name_type;
602                         BOOL winbind_answered = False;
603                         BOOL ret;
604                         fstring groupname, domain;
605                         
606                         /* Parse a string of the form DOMAIN/user into a domain and a user */
607
608                         char *p = strchr(*list,*lp_winbind_separator());
609                         
610                         DEBUG(10,("user_in_list: checking if user |%s| is in winbind group |%s|\n", user, *list));
611
612                         if (p) {
613                                 fstrcpy(groupname, p+1);
614                                 fstrcpy(domain, *list);
615                                 domain[PTR_DIFF(p, *list)] = 0;
616
617                                 /* Check to see if name is a Windows group;  Win2k native mode DCs
618                                    will return domain local groups; while NT4 or mixed mode 2k DCs
619                                    will not */
620                         
621                                 if ( winbind_lookup_name(domain, groupname, &g_sid, &name_type) 
622                                         && ( name_type==SID_NAME_DOM_GRP || 
623                                            (strequal(lp_workgroup(), domain) && name_type==SID_NAME_ALIAS) ) )
624                                 {
625                                         
626                                         /* Check if user name is in the Windows group */
627                                         ret = user_in_winbind_group_list(user, *list, &winbind_answered);
628                                         
629                                         if (winbind_answered && ret == True) {
630                                                 DEBUG(10,("user_in_list: user |%s| is in winbind group |%s|\n", user, *list));
631                                                 return ret;
632                                         }
633                                 }
634                         }
635                 }
636     
637                 list++;
638         }
639         return(False);
640 }
641
642 /* The functions below have been taken from password.c and slightly modified */
643 /****************************************************************************
644  Apply a function to upper/lower case combinations
645  of a string and return true if one of them returns true.
646  Try all combinations with N uppercase letters.
647  offset is the first char to try and change (start with 0)
648  it assumes the string starts lowercased
649 ****************************************************************************/
650
651 static struct passwd *uname_string_combinations2(char *s,int offset,struct passwd *(*fn)(const char *),int N)
652 {
653         ssize_t len = (ssize_t)strlen(s);
654         int i;
655         struct passwd *ret;
656
657         if (N <= 0 || offset >= len)
658                 return(fn(s));
659
660         for (i=offset;i<(len-(N-1));i++) {
661                 char c = s[i];
662                 if (!islower((int)c))
663                         continue;
664                 s[i] = toupper(c);
665                 ret = uname_string_combinations2(s,i+1,fn,N-1);
666                 if(ret)
667                         return(ret);
668                 s[i] = c;
669         }
670         return(NULL);
671 }
672
673 /****************************************************************************
674  Apply a function to upper/lower case combinations
675  of a string and return true if one of them returns true.
676  Try all combinations with up to N uppercase letters.
677  offset is the first char to try and change (start with 0)
678  it assumes the string starts lowercased
679 ****************************************************************************/
680
681 static struct passwd * uname_string_combinations(char *s,struct passwd * (*fn)(const char *),int N)
682 {
683         int n;
684         struct passwd *ret;
685
686         for (n=1;n<=N;n++) {
687                 ret = uname_string_combinations2(s,0,fn,n);
688                 if(ret)
689                         return(ret);
690         }  
691         return(NULL);
692 }
693