clean-up of cache-getpw-hash code needed (make proto showed up loads
[ira/wip.git] / source / 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   Since getpwnam() makes samba really slow with the NT-domain code
31   (reading /etc/passwd again and again and again), here is an implementation
32   of very simple passwd cache
33 ****************************************************************************/
34 #define PASSWD_HASH_SIZE 1009
35 /* The hashtable is rebuild every 15 seconds */
36 #define PASSWD_HASH_AGE 15
37 struct passwd_hash_entry {
38   int entry;
39   int next;
40 };
41
42 struct passwd_hash_table_s {
43   struct passwd *passwds;
44   int passwds_size;  
45   int *names;
46   int *uids;
47   struct passwd_hash_entry *entries;
48   int entries_size;
49   struct timeval build_time;
50 } passwd_hash_table = {
51   NULL,0,NULL,NULL,NULL,0,{0,0}
52 };
53
54 static int name_hash_function(const char *name) 
55 {
56   /* I guess that there must be better hash functions. This one was the
57    * first to come into mind :) */
58   unsigned int value=0;
59   while (*name) {
60     value=(value<<8)|(unsigned char)(*name);
61     if (value>1048576) value=value%PASSWD_HASH_SIZE;
62     name++;
63   }
64   value=value%PASSWD_HASH_SIZE;
65   return value;
66 }
67     
68 static int uid_hash_function(uid_t uid) 
69 {
70   return uid%PASSWD_HASH_SIZE;
71 }
72
73
74 static BOOL build_passwd_hash_table(void) 
75 {
76   struct passwd_hash_table_s *pht=&passwd_hash_table; /* Convenience */
77   int num_passwds=0;
78   int num_entries=0;
79   struct passwd *pass;
80   int i;
81   int name_i,uid_i;
82
83   DEBUG(3,("Building passwd hash table\n"));
84   /* Free the allocated strings in old hash table */
85   for (i=0;i<pht->passwds_size;i++) {
86     free(pht->passwds[i].pw_name);
87     free(pht->passwds[i].pw_passwd);
88     free(pht->passwds[i].pw_gecos);
89     free(pht->passwds[i].pw_dir);
90     free(pht->passwds[i].pw_shell);    
91   }
92
93   /* Initialize hash table if first table build */
94   if (pht->passwds_size==0) {
95     DEBUG(3,("Building passwd hash table for the first time\n"));
96     pht->passwds=malloc(sizeof(struct passwd)*64); /* A reasonable default */
97     pht->passwds_size=64;
98   }
99   if (pht->names==NULL) {
100     pht->names=malloc(sizeof(struct passwd_hash_entry *)*PASSWD_HASH_SIZE);
101   }
102   if (pht->uids==NULL) {
103     pht->uids=malloc(sizeof(struct passwd_hash_entry *)*PASSWD_HASH_SIZE);
104   }
105   if (pht->entries==NULL) {
106     pht->entries=malloc(sizeof(struct passwd_hash_entry)*128);
107     pht->entries_size=128;
108   }
109   if (pht->passwds==NULL || pht->names==NULL || 
110       pht->uids==NULL || pht->entries==NULL) {
111     goto fail;
112   }
113   
114   /* Clear out the hash table */
115   for(i=0;i<PASSWD_HASH_SIZE;i++) pht->uids[i]=-1;
116   for(i=0;i<PASSWD_HASH_SIZE;i++) pht->names[i]=-1;
117
118   /* Now do the build */
119   setpwent();
120
121   while((pass=getpwent())) {
122
123     /* Check that we have enough space */
124     if (num_passwds==pht->passwds_size) {
125       struct passwd *new_passwds=NULL;
126       pht->passwds_size+=pht->passwds_size/2;
127       new_passwds=realloc(pht->passwds,
128                            sizeof(struct passwd)*pht->passwds_size);
129       if (new_passwds==NULL) goto fail;
130       pht->passwds=new_passwds;
131     }
132     if (num_entries+1>=pht->entries_size) {
133       pht->entries_size+=pht->entries_size/2;
134       pht->entries=realloc(pht->entries,
135                            sizeof(struct passwd_hash_entry)*pht->entries_size);
136       if (pht->entries==NULL) goto fail;
137     }
138
139     /* Copy the passwd struct */
140     memset(&pht->passwds[num_passwds],0,sizeof(struct passwd));
141     pht->passwds[num_passwds].pw_uid=pass->pw_uid;
142     pht->passwds[num_passwds].pw_gid=pass->pw_gid;  
143     if (
144         (pht->passwds[num_passwds].pw_name=strdup(pass->pw_name))==NULL ||
145         (pht->passwds[num_passwds].pw_passwd=strdup(pass->pw_passwd))==NULL ||
146         (pht->passwds[num_passwds].pw_gecos=strdup(pass->pw_gecos))==NULL ||
147         (pht->passwds[num_passwds].pw_dir=strdup(pass->pw_dir))==NULL ||
148         (pht->passwds[num_passwds].pw_shell=strdup(pass->pw_shell))==NULL ) {
149       num_passwds++;
150       goto fail;
151     }
152     
153     /* Add to the hash table */
154     /* Add the name */
155     pht->entries[num_entries].entry=num_passwds;
156     name_i=name_hash_function(pass->pw_name);
157     pht->entries[num_entries].next=pht->names[name_i];
158     pht->names[name_i]=num_entries;
159     num_entries++;
160     /* Add the uid */
161     pht->entries[num_entries].entry=num_passwds;
162     uid_i=uid_hash_function(pass->pw_uid);
163     pht->entries[num_entries].next=pht->uids[uid_i];
164     pht->uids[uid_i]=num_entries;
165     num_entries++;
166
167     /* This entry has been done */
168     num_passwds++;
169   }    
170   endpwent();
171   
172   if (pht->passwds_size>num_passwds) {
173     struct passwd *passwds;
174     passwds=realloc(pht->passwds,sizeof(pht->passwds[0])*num_passwds);
175     if (passwds==NULL) goto fail;
176     pht->passwds=passwds;
177     pht->passwds_size=num_passwds;
178   }
179   if (pht->entries_size>num_entries) {
180     struct passwd_hash_entry *entries;
181     entries=realloc(pht->entries,sizeof(pht->entries[0])*num_entries);
182     if (entries==NULL) goto fail;
183     pht->entries=entries;
184     pht->entries_size=num_entries;
185   }
186
187   /* Mark the creation time */
188   GetTimeOfDay(&pht->build_time);
189   /* Everything went smoothly. */
190   return True;
191
192  fail:
193   DEBUG(0,("Failed to create passwd hash table: %s",strerror(errno)));
194   /* OK: now the untested part. Normally this should never happen:
195    * Only running out of memory could cause this and even then
196    * we have enough trouble already. */
197   while (num_passwds>0) {
198     num_passwds--;
199     free(pht->passwds[num_passwds].pw_name);
200     free(pht->passwds[num_passwds].pw_passwd);
201     free(pht->passwds[num_passwds].pw_gecos);
202     free(pht->passwds[num_passwds].pw_dir);
203     free(pht->passwds[num_passwds].pw_shell);    
204   }
205   free(pht->entries);
206   free(pht->uids);
207   free(pht->names);
208   free(pht->passwds);
209   pht->passwds_size=0;
210   pht->entries_size=0;    
211   /* Also mark fail time, so that retry will happen after PASSWD_HASH_AGE */
212   GetTimeOfDay(&pht->build_time);
213   return False;
214 }
215
216 static BOOL have_passwd_hash(void)
217 {
218   struct passwd_hash_table_s *pht=&passwd_hash_table;
219   struct timeval tv;
220   GetTimeOfDay(&tv);
221   /* I'm ignoring microseconds. If you think they matter, go ahead
222    * and implement them */
223   if (tv.tv_sec - pht->build_time.tv_sec > PASSWD_HASH_AGE) {
224     return build_passwd_hash_table();
225   }
226   return pht->passwds_size>0;
227 }
228
229 struct passwd *hashed_getpwnam(const char *name)
230 {
231   struct passwd_hash_table_s *pht=&passwd_hash_table;
232
233   DEBUG(5,("getpwnam(%s)\n", name));
234
235   if (have_passwd_hash()) {
236     int name_i=name_hash_function(name);
237     int hash_index=pht->names[name_i];
238     while(hash_index!=-1) {
239       struct passwd *pass=&pht->passwds[pht->entries[hash_index].entry];
240       if (strcmp(name,pass->pw_name)==0) {
241         DEBUG(5,("Found: %s:%s:%d:%d:%s:%s:%s\n",
242                  pass->pw_name,
243                  pass->pw_passwd,
244                  pass->pw_uid,
245                  pass->pw_gid,
246                  pass->pw_gecos,
247                  pass->pw_dir,
248                  pass->pw_shell));
249         return pass;      
250       }
251       hash_index=pht->entries[hash_index].next;
252     }
253
254     /* Not found */
255     DEBUG(5,("%s not found\n",name));
256     return NULL;
257   } 
258   /* Fall back to real getpwnam() */
259   return getpwnam(name);
260 }
261
262 /*******************************************************************
263 turn a uid into a user name
264 ********************************************************************/
265 char *uidtoname(uid_t uid)
266 {
267   static char name[40];
268   struct passwd_hash_table_s *pht=&passwd_hash_table;
269   struct passwd *pass=NULL;
270
271   DEBUG(5,("uidtoname(%d)\n",uid));
272   if (have_passwd_hash()) {
273     int hash_index=pht->uids[uid_hash_function(uid)];
274     while(hash_index!=-1) {
275       pass=&pht->passwds[pht->entries[hash_index].entry];
276       if (pass->pw_uid==uid) {
277         DEBUG(5,("Found: %s:%s:%d:%d:%s:%s:%s\n",
278                  pass->pw_name,
279                  pass->pw_passwd,
280                  pass->pw_uid,
281                  pass->pw_gid,
282                  pass->pw_gecos,
283                  pass->pw_dir,
284                  pass->pw_shell));
285         return pass->pw_name;      
286       }
287       hash_index=pht->entries[hash_index].next;
288     }
289     DEBUG(5,("Hash miss"));
290     pass=NULL;
291   } else {
292     /* No hash table, fall back to getpwuid */
293     pass = getpwuid(uid);
294   }
295   if (pass) return(pass->pw_name);
296   slprintf(name, sizeof(name) - 1, "%d",(int)uid);
297   return(name);
298 }
299
300 /****************************************************************************
301 get a users home directory.
302 ****************************************************************************/
303 char *get_home_dir(char *user)
304 {
305         struct passwd *pass;
306         static pstring home_dir;
307
308         pass = Get_Pwnam(user, False);
309
310         if (pass == NULL || pass->pw_dir == NULL) return(NULL);
311
312         pstrcpy(home_dir, pass->pw_dir);
313         DEBUG(10,("get_home_dir: returning %s for user %s\n", home_dir, user));
314         return home_dir;
315 }
316
317
318 /*******************************************************************
319 map a username from a dos name to a unix name by looking in the username
320 map. Note that this modifies the name in place.
321 This is the main function that should be called *once* on
322 any incoming or new username - in order to canonicalize the name.
323 This is being done to de-couple the case conversions from the user mapping
324 function. Previously, the map_username was being called
325 every time Get_Pwnam was called.
326 Returns True if username was changed, false otherwise.
327 ********************************************************************/
328 BOOL map_username(char *user)
329 {
330   static BOOL initialised=False;
331   static fstring last_from,last_to;
332   FILE *f;
333   char *mapfile = lp_username_map();
334   char *s;
335   pstring buf;
336   BOOL mapped_user = False;
337
338   if (!*user)
339     return False;
340
341   if (!*mapfile)
342     return False;
343
344   if (!initialised) {
345     *last_from = *last_to = 0;
346     initialised = True;
347   }
348
349   if (strequal(user,last_to))
350     return False;
351
352   if (strequal(user,last_from)) {
353     DEBUG(3,("Mapped user %s to %s\n",user,last_to));
354     fstrcpy(user,last_to);
355     return True;
356   }
357   
358   f = sys_fopen(mapfile,"r");
359   if (!f) {
360     DEBUG(0,("can't open username map %s\n",mapfile));
361     return False;
362   }
363
364   DEBUG(4,("Scanning username map %s\n",mapfile));
365
366   while((s=fgets_slash(buf,sizeof(buf),f))!=NULL) {
367     char *unixname = s;
368     char *dosname = strchr(unixname,'=');
369     BOOL return_if_mapped = False;
370
371     if (!dosname)
372       continue;
373
374     *dosname++ = 0;
375
376     while (isspace(*unixname))
377       unixname++;
378     if ('!' == *unixname) {
379       return_if_mapped = True;
380       unixname++;
381       while (*unixname && isspace(*unixname))
382         unixname++;
383     }
384     
385     if (!*unixname || strchr("#;",*unixname))
386       continue;
387
388     {
389       int l = strlen(unixname);
390       while (l && isspace(unixname[l-1])) {
391         unixname[l-1] = 0;
392         l--;
393       }
394     }
395
396     if (strchr(dosname,'*') || user_in_list(user,dosname)) {
397       DEBUG(3,("Mapped user %s to %s\n",user,unixname));
398       mapped_user = True;
399       fstrcpy(last_from,user);
400       sscanf(unixname,"%s",user);
401       fstrcpy(last_to,user);
402       if(return_if_mapped) { 
403         fclose(f);
404         return True;
405       }
406     }
407   }
408
409   fclose(f);
410
411   /*
412    * Setup the last_from and last_to as an optimization so 
413    * that we don't scan the file again for the same user.
414    */
415   fstrcpy(last_from,user);
416   fstrcpy(last_to,user);
417
418   return mapped_user;
419 }
420
421 /****************************************************************************
422 Get_Pwnam wrapper
423 ****************************************************************************/
424 static struct passwd *_Get_Pwnam(char *s)
425 {
426   struct passwd *ret;
427
428   ret = hashed_getpwnam(s);
429   if (ret)
430     {
431 #ifdef HAVE_GETPWANAM
432       struct passwd_adjunct *pwret;
433       pwret = getpwanam(s);
434       if (pwret)
435         {
436           free(ret->pw_passwd);
437           ret->pw_passwd = pwret->pwa_passwd;
438         }
439 #endif
440
441     }
442
443   return(ret);
444 }
445
446
447 /****************************************************************************
448 a wrapper for getpwnam() that tries with all lower and all upper case 
449 if the initial name fails. Also tried with first letter capitalised
450 Note that this can change user!
451 ****************************************************************************/
452 struct passwd *Get_Pwnam(char *user,BOOL allow_change)
453 {
454   fstring user2;
455   int last_char;
456   int usernamelevel = lp_usernamelevel();
457
458   struct passwd *ret;  
459
460   if (!user || !(*user))
461     return(NULL);
462
463   StrnCpy(user2,user,sizeof(user2)-1);
464
465   if (!allow_change) {
466     user = &user2[0];
467   }
468
469   ret = _Get_Pwnam(user);
470   if (ret) return(ret);
471
472   strlower(user);
473   ret = _Get_Pwnam(user);
474   if (ret)  return(ret);
475
476   strupper(user);
477   ret = _Get_Pwnam(user);
478   if (ret) return(ret);
479
480   /* try with first letter capitalised */
481   if (strlen(user) > 1)
482     strlower(user+1);  
483   ret = _Get_Pwnam(user);
484   if (ret) return(ret);
485
486   /* try with last letter capitalised */
487   strlower(user);
488   last_char = strlen(user)-1;
489   user[last_char] = toupper(user[last_char]);
490   ret = _Get_Pwnam(user);
491   if (ret) return(ret);
492
493   /* try all combinations up to usernamelevel */
494   strlower(user);
495   ret = uname_string_combinations(user, _Get_Pwnam, usernamelevel);
496   if (ret) return(ret);
497
498   if (allow_change)
499     fstrcpy(user,user2);
500
501   return(NULL);
502 }
503
504 /****************************************************************************
505 check if a user is in a netgroup user list
506 ****************************************************************************/
507 static BOOL user_in_netgroup_list(char *user,char *ngname)
508 {
509 #ifdef HAVE_NETGROUP
510   static char *mydomain = NULL;
511   if (mydomain == NULL)
512     yp_get_default_domain(&mydomain);
513
514   if(mydomain == NULL)
515   {
516     DEBUG(5,("Unable to get default yp domain\n"));
517   }
518   else
519   {
520     DEBUG(5,("looking for user %s of domain %s in netgroup %s\n",
521           user, mydomain, ngname));
522     DEBUG(5,("innetgr is %s\n",
523           innetgr(ngname, NULL, user, mydomain)
524           ? "TRUE" : "FALSE"));
525
526     if (innetgr(ngname, NULL, user, mydomain))
527       return (True);
528   }
529 #endif /* HAVE_NETGROUP */
530   return False;
531 }
532
533 /****************************************************************************
534 check if a user is in a UNIX user list
535 ****************************************************************************/
536 static BOOL user_in_group_list(char *user,char *gname)
537 {
538 #ifdef HAVE_GETGRNAM 
539   struct group *gptr;
540   char **member;  
541   struct passwd *pass = Get_Pwnam(user,False);
542
543   if (pass)
544   { 
545     gptr = getgrgid(pass->pw_gid);
546     if (gptr && strequal(gptr->gr_name,gname))
547       return(True); 
548   } 
549
550   gptr = (struct group *)getgrnam(gname);
551
552   if (gptr)
553   {
554     member = gptr->gr_mem;
555     while (member && *member)
556     {
557       if (strequal(*member,user))
558         return(True);
559       member++;
560     }
561   }
562 #endif /* HAVE_GETGRNAM */
563   return False;
564 }             
565
566 /****************************************************************************
567 check if a user is in a user list - can check combinations of UNIX
568 and netgroup lists.
569 ****************************************************************************/
570 BOOL user_in_list(char *user,char *list)
571 {
572   pstring tok;
573   char *p=list;
574
575   while (next_token(&p,tok,LIST_SEP, sizeof(tok)))
576   {
577     /*
578      * Check raw username.
579      */
580     if (strequal(user,tok))
581       return(True);
582
583     /*
584      * Now check to see if any combination
585      * of UNIX and netgroups has been specified.
586      */
587
588     if(*tok == '@')
589     {
590       /*
591        * Old behaviour. Check netgroup list
592        * followed by UNIX list.
593        */
594       if(user_in_netgroup_list(user,&tok[1]))
595         return True;
596       if(user_in_group_list(user,&tok[1]))
597         return True;
598     }
599     else if (*tok == '+')
600     {
601       if(tok[1] == '&')
602       {
603         /*
604          * Search UNIX list followed by netgroup.
605          */
606         if(user_in_group_list(user,&tok[2]))
607           return True;
608         if(user_in_netgroup_list(user,&tok[2]))
609           return True;
610       }
611       else
612       {
613         /*
614          * Just search UNIX list.
615          */
616         if(user_in_group_list(user,&tok[1]))
617           return True;
618       }
619     }
620     else if (*tok == '&')
621     {
622       if(tok[1] == '&')
623       {
624         /*
625          * Search netgroup list followed by UNIX list.
626          */
627         if(user_in_netgroup_list(user,&tok[2]))
628           return True;
629         if(user_in_group_list(user,&tok[2]))
630           return True;
631       }
632       else
633       {
634         /*
635          * Just search netgroup list.
636          */
637         if(user_in_netgroup_list(user,&tok[1]))
638           return True;
639       }
640     }
641   }
642   return(False);
643 }
644
645 /* The functions below have been taken from password.c and slightly modified */
646 /****************************************************************************
647 apply a function to upper/lower case combinations
648 of a string and return true if one of them returns true.
649 try all combinations with N uppercase letters.
650 offset is the first char to try and change (start with 0)
651 it assumes the string starts lowercased
652 ****************************************************************************/
653 static struct passwd *uname_string_combinations2(char *s,int offset,struct passwd *(*fn)(char *),int N)
654 {
655   int len = strlen(s);
656   int i;
657   struct passwd *ret;
658
659 #ifdef PASSWORD_LENGTH
660   len = MIN(len,PASSWORD_LENGTH);
661 #endif
662
663   if (N <= 0 || offset >= len)
664     return(fn(s));
665
666
667   for (i=offset;i<(len-(N-1));i++)
668
669     {
670       char c = s[i];
671       if (!islower(c)) continue;
672       s[i] = toupper(c);
673       ret = uname_string_combinations2(s,i+1,fn,N-1);
674       if(ret) return(ret);
675       s[i] = c;
676     }
677   return(NULL);
678 }
679
680 /****************************************************************************
681 apply a function to upper/lower case combinations
682 of a string and return true if one of them returns true.
683 try all combinations with up to N uppercase letters.
684 offset is the first char to try and change (start with 0)
685 it assumes the string starts lowercased
686 ****************************************************************************/
687 static struct passwd * uname_string_combinations(char *s,struct passwd * (*fn)(char *),int N)
688 {
689   int n;
690   struct passwd *ret;
691
692   for (n=1;n<=N;n++)
693   {
694     ret = uname_string_combinations2(s,0,fn,n);
695     if(ret) return(ret);
696   }
697   return(NULL);
698 }