John Reilly @ HP (who is a wonderful human being and *definately* needs
[gd/samba-autobuild/.git] / source3 / lib / username.c
1 /* 
2    Unix SMB/Netbios implementation.
3    Version 1.9.
4    Username handling
5    Copyright (C) Andrew Tridgell 1992-1998
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 extern int DEBUGLEVEL;
24
25 /* internal functions */
26 static struct passwd *uname_string_combinations(char *s, struct passwd * (*fn) (char *), int N);
27 static struct passwd *uname_string_combinations2(char *s, int offset, struct passwd * (*fn) (char *), int N);
28
29 /****************************************************************************
30  Get a users home directory.
31 ****************************************************************************/
32
33 char *get_user_home_dir(char *user)
34 {
35   static struct passwd *pass;
36
37   pass = Get_Pwnam(user, False);
38
39   if (!pass) return(NULL);
40   return(pass->pw_dir);      
41 }
42
43
44 /*******************************************************************
45  Map a username from a dos name to a unix name by looking in the username
46  map. Note that this modifies the name in place.
47  This is the main function that should be called *once* on
48  any incoming or new username - in order to canonicalize the name.
49  This is being done to de-couple the case conversions from the user mapping
50  function. Previously, the map_username was being called
51  every time Get_Pwnam was called.
52  Returns True if username was changed, false otherwise.
53 ********************************************************************/
54
55 BOOL map_username(char *user)
56 {
57   static BOOL initialised=False;
58   static fstring last_from,last_to;
59   FILE *f;
60   char *mapfile = lp_username_map();
61   char *s;
62   pstring buf;
63   BOOL mapped_user = False;
64
65   if (!*user)
66     return False;
67
68   if (!*mapfile)
69     return False;
70
71   if (!initialised) {
72     *last_from = *last_to = 0;
73     initialised = True;
74   }
75
76   if (strequal(user,last_to))
77     return False;
78
79   if (strequal(user,last_from)) {
80     DEBUG(3,("Mapped user %s to %s\n",user,last_to));
81     fstrcpy(user,last_to);
82     return True;
83   }
84   
85   f = sys_fopen(mapfile,"r");
86   if (!f) {
87     DEBUG(0,("can't open username map %s\n",mapfile));
88     return False;
89   }
90
91   DEBUG(4,("Scanning username map %s\n",mapfile));
92
93   while((s=fgets_slash(buf,sizeof(buf),f))!=NULL) {
94     char *unixname = s;
95     char *dosname = strchr(unixname,'=');
96     BOOL return_if_mapped = False;
97
98     if (!dosname)
99       continue;
100
101     *dosname++ = 0;
102
103     while (isspace(*unixname))
104       unixname++;
105     if ('!' == *unixname) {
106       return_if_mapped = True;
107       unixname++;
108       while (*unixname && isspace(*unixname))
109         unixname++;
110     }
111     
112     if (!*unixname || strchr("#;",*unixname))
113       continue;
114
115     {
116       int l = strlen(unixname);
117       while (l && isspace(unixname[l-1])) {
118         unixname[l-1] = 0;
119         l--;
120       }
121     }
122
123     if (strchr(dosname,'*') || user_in_list(user,dosname)) {
124       DEBUG(3,("Mapped user %s to %s\n",user,unixname));
125       mapped_user = True;
126       fstrcpy(last_from,user);
127       sscanf(unixname,"%s",user);
128       fstrcpy(last_to,user);
129       if(return_if_mapped) { 
130         fclose(f);
131         return True;
132       }
133     }
134   }
135
136   fclose(f);
137
138   /*
139    * Setup the last_from and last_to as an optimization so 
140    * that we don't scan the file again for the same user.
141    */
142   fstrcpy(last_from,user);
143   fstrcpy(last_to,user);
144
145   return mapped_user;
146 }
147
148 /****************************************************************************
149  Get_Pwnam wrapper
150 ****************************************************************************/
151
152 static struct passwd *_Get_Pwnam(char *s)
153 {
154   struct passwd *ret;
155
156   ret = sys_getpwnam(s);
157   if (ret) {
158 #ifdef HAVE_GETPWANAM
159     struct passwd_adjunct *pwret;
160     pwret = getpwanam(s);
161     if (pwret && pwret->pwa_passwd) {
162       pstrcpy(ret->pw_passwd,pwret->pwa_passwd);
163     }
164 #endif
165   }
166
167   return(ret);
168 }
169
170
171 /****************************************************************************
172  A wrapper for getpwnam() that tries with all lower and all upper case 
173  if the initial name fails. Also tried with first letter capitalised
174  Note that this can change user!
175 ****************************************************************************/
176
177 struct passwd *Get_Pwnam(char *user,BOOL allow_change)
178 {
179   fstring user2;
180   int last_char;
181   int usernamelevel = lp_usernamelevel();
182
183   struct passwd *ret;  
184
185   if (!user || !(*user))
186     return(NULL);
187
188   StrnCpy(user2,user,sizeof(user2)-1);
189
190   if (!allow_change) {
191     user = &user2[0];
192   }
193
194   ret = _Get_Pwnam(user);
195   if (ret)
196     return(ret);
197
198   strlower(user);
199   ret = _Get_Pwnam(user);
200   if (ret)
201     return(ret);
202
203   strupper(user);
204   ret = _Get_Pwnam(user);
205   if (ret)
206     return(ret);
207
208   /* Try with first letter capitalised. */
209   if (strlen(user) > 1)
210     strlower(user+1);  
211   ret = _Get_Pwnam(user);
212   if (ret)
213     return(ret);
214
215   /* try with last letter capitalised */
216   strlower(user);
217   last_char = strlen(user)-1;
218   user[last_char] = toupper(user[last_char]);
219   ret = _Get_Pwnam(user);
220   if (ret)
221     return(ret);
222
223   /* Try all combinations up to usernamelevel. */
224   strlower(user);
225   ret = uname_string_combinations(user, _Get_Pwnam, usernamelevel);
226   if (ret)
227     return(ret);
228
229   if (allow_change)
230     fstrcpy(user,user2);
231
232   return(NULL);
233 }
234
235 /****************************************************************************
236  Check if a user is in a netgroup user list.
237 ****************************************************************************/
238
239 static BOOL user_in_netgroup_list(char *user,char *ngname)
240 {
241 #ifdef HAVE_NETGROUP
242   static char *mydomain = NULL;
243   if (mydomain == NULL)
244     yp_get_default_domain(&mydomain);
245
246   if(mydomain == NULL) {
247     DEBUG(5,("Unable to get default yp domain\n"));
248   } else {
249     DEBUG(5,("looking for user %s of domain %s in netgroup %s\n",
250           user, mydomain, ngname));
251     DEBUG(5,("innetgr is %s\n",
252           innetgr(ngname, NULL, user, mydomain)
253           ? "TRUE" : "FALSE"));
254
255     if (innetgr(ngname, NULL, user, mydomain))
256       return (True);
257   }
258 #endif /* HAVE_NETGROUP */
259   return False;
260 }
261
262 /****************************************************************************
263  Check if a user is in a winbind group.
264 ****************************************************************************/
265   
266 static BOOL user_in_winbind_group_list(char *user,char *gname, BOOL *winbind_answered)
267 {
268         int num_groups;
269         int i;
270         gid_t *groups = NULL;
271         gid_t gid;
272         BOOL ret = False;
273  
274         *winbind_answered = False;
275  
276         /*
277          * Get the gid's that this user belongs to.
278          */
279  
280         if ((num_groups = winbind_getgroups(user, 0, NULL)) == -1)
281                 return False;
282  
283         if (num_groups == 0) {
284                 *winbind_answered = True;
285                 return False;
286         }
287  
288         if ((groups = (gid_t *)malloc(sizeof(gid_t) * num_groups )) == NULL) {
289                 DEBUG(0,("user_in_winbind_group_list: malloc fail.\n"));
290                 goto err;
291         }
292  
293         if ((num_groups = winbind_getgroups(user, num_groups, groups)) == -1) {
294                 DEBUG(0,("user_in_winbind_group_list: second winbind_getgroups call \
295 failed with error %s\n", strerror(errno) ));
296                 goto err;
297         }
298  
299         /*
300          * Now we have the gid list for this user - convert the gname
301          * to a gid_t via winbind and do the comparison.
302          */
303  
304         if (!winbind_nametogid(&gid, gname)) {
305                 DEBUG(0,("user_in_winbind_group_list: winbind_lookup_name for group %s failed.\n",
306                         gname ));
307                 goto err;
308         }
309  
310         for (i = 0; i < num_groups; i++) {
311                 if (gid == groups[i]) {
312                         ret = True;
313                         break;
314                 }
315         }
316  
317         *winbind_answered = True;
318         safe_free(groups);
319         return ret;
320  
321    err:
322  
323         *winbind_answered = False;
324         safe_free(groups);
325         return False;
326 }             
327  
328 /****************************************************************************
329  Check if a user is in a UNIX group.
330 ****************************************************************************/
331
332 static BOOL user_in_unix_group_list(char *user,char *gname)
333 {
334         struct group *gptr;
335         char **member;  
336         struct passwd *pass = Get_Pwnam(user,False);
337
338         DEBUG(10,("user_in_unix_group_list: checking user %s in group %s\n", user, gname));
339
340         /*
341          * We need to check the users primary group as this
342          * group is implicit and often not listed in the group database.
343          */
344  
345         if (pass) {
346                 gptr = getgrgid(pass->pw_gid);
347                 if (gptr && strequal(gptr->gr_name,gname)) {
348                         DEBUG(10,("user_in_unix_group_list: group %s is primary group.\n", gname ));
349                         return True;
350                 }
351         }
352  
353         if ((gptr = (struct group *)getgrnam(gname)) == NULL) {
354                 DEBUG(10,("user_in_unix_group_list: no such group %s\n", gname ));
355                 return False;
356         }
357  
358         member = gptr->gr_mem;
359         while (member && *member) {
360                 DEBUG(10,("user_in_unix_group_list: checking user %s against member %s\n", user, *member ));
361                 if (strequal(*member,user)) {
362                         return(True);
363                 }
364                 member++;
365         }
366
367         return False;
368 }             
369
370 /****************************************************************************
371  Check if a user is in a group list. Ask winbind first, then use UNIX.
372 ****************************************************************************/
373
374 BOOL user_in_group_list(char *user,char *gname)
375 {
376         BOOL winbind_answered = False;
377         BOOL ret = user_in_winbind_group_list(user, gname, &winbind_answered);
378
379         if (winbind_answered)
380                 return ret;
381
382         return user_in_unix_group_list(user, gname);    
383 }
384
385 /****************************************************************************
386  Check if a user is in a user list - can check combinations of UNIX
387  and netgroup lists.
388 ****************************************************************************/
389
390 BOOL user_in_list(char *user,char *list)
391 {
392   pstring tok;
393   char *p=list;
394
395   DEBUG(10,("user_in_list: checking user %s in list %s\n", user, list));
396
397   while (next_token(&p,tok,LIST_SEP, sizeof(tok))) {
398     /*
399      * Check raw username.
400      */
401     if (strequal(user,tok))
402       return(True);
403
404     /*
405      * Now check to see if any combination
406      * of UNIX and netgroups has been specified.
407      */
408
409     if(*tok == '@') {
410       /*
411        * Old behaviour. Check netgroup list
412        * followed by UNIX list.
413        */
414       if(user_in_netgroup_list(user,&tok[1]))
415         return True;
416       if(user_in_group_list(user,&tok[1]))
417         return True;
418     } else if (*tok == '+') {
419
420       if(tok[1] == '&') {
421         /*
422          * Search UNIX list followed by netgroup.
423          */
424         if(user_in_group_list(user,&tok[2]))
425           return True;
426         if(user_in_netgroup_list(user,&tok[2]))
427           return True;
428
429       } else {
430
431         /*
432          * Just search UNIX list.
433          */
434
435         if(user_in_group_list(user,&tok[1]))
436           return True;
437       }
438
439     } else if (*tok == '&') {
440
441       if(tok[1] == '+') {
442         /*
443          * Search netgroup list followed by UNIX list.
444          */
445         if(user_in_netgroup_list(user,&tok[2]))
446           return True;
447         if(user_in_group_list(user,&tok[2]))
448           return True;
449       } else {
450         /*
451          * Just search netgroup list.
452          */
453         if(user_in_netgroup_list(user,&tok[1]))
454           return True;
455       }
456     }
457   }
458   return(False);
459 }
460
461 /* The functions below have been taken from password.c and slightly modified */
462 /****************************************************************************
463  Apply a function to upper/lower case combinations
464  of a string and return true if one of them returns true.
465  Try all combinations with N uppercase letters.
466  offset is the first char to try and change (start with 0)
467  it assumes the string starts lowercased
468 ****************************************************************************/
469
470 static struct passwd *uname_string_combinations2(char *s,int offset,struct passwd *(*fn)(char *),int N)
471 {
472   ssize_t len = (ssize_t)strlen(s);
473   int i;
474   struct passwd *ret;
475
476 #ifdef PASSWORD_LENGTH
477   len = MIN(len,PASSWORD_LENGTH);
478 #endif
479
480   if (N <= 0 || offset >= len)
481     return(fn(s));
482
483   for (i=offset;i<(len-(N-1));i++) {
484     char c = s[i];
485     if (!islower(c))
486       continue;
487     s[i] = toupper(c);
488     ret = uname_string_combinations2(s,i+1,fn,N-1);
489     if(ret)
490       return(ret);
491     s[i] = c;
492   }
493   return(NULL);
494 }
495
496 /****************************************************************************
497  Apply a function to upper/lower case combinations
498  of a string and return true if one of them returns true.
499  Try all combinations with up to N uppercase letters.
500  offset is the first char to try and change (start with 0)
501  it assumes the string starts lowercased
502 ****************************************************************************/
503
504 static struct passwd * uname_string_combinations(char *s,struct passwd * (*fn)(char *),int N)
505 {
506   int n;
507   struct passwd *ret;
508
509   for (n=1;n<=N;n++) {
510     ret = uname_string_combinations2(s,0,fn,n);
511     if(ret)
512       return(ret);
513   }
514   return(NULL);
515 }
516
517
518 /****************************************************************************
519 these wrappers allow appliance mode to work. In appliance mode the username
520 takes the form DOMAIN/user
521 ****************************************************************************/
522 struct passwd *smb_getpwnam(char *user, BOOL allow_change)
523 {
524         struct passwd *pw;
525         char *p;
526         char *sep;
527         extern pstring global_myname;
528
529         pw = Get_Pwnam(user, allow_change);
530         if (pw) return pw;
531
532         /* if it is a domain qualified name and it isn't in our password
533            database but the domain portion matches our local machine name then
534            lookup just the username portion locally */
535         sep = lp_winbind_separator();
536         if (!sep || !*sep) sep = "\\";
537         p = strchr(user,*sep);
538         if (p && 
539             strncasecmp(global_myname, user, strlen(global_myname))==0) {
540                 return Get_Pwnam(p+1, allow_change);
541         }
542
543         return NULL;
544 }