Crypt thread patch from Oliver Graf <ograf@rz-online.net> (KEVAG Telekom GmbH /...
[freeradius.git] / src / modules / rlm_unix / cache.c
1 /*
2  * cache.c      Offers ability to cache /etc/group, /etc/passwd, 
3  *              /etc/shadow,
4  *
5  *              All users in the passwd/shadow files are stored in a hash table.
6  *              the hash lookup is VERY fast,  generally 1.0673 comparisons per
7  *              lookup.  For the unitiated, that's blazing.  You can't have less
8  *              than one comparison, for example.
9  *
10  *              The /etc/group file is stored in a singly linked list, as that 
11  *              appears to be fast enough.  It's generally a small enough file 
12  *              that hashing is unnecessary.
13  *
14  * Version: $Id$
15  *
16  *   This program is is free software; you can redistribute it and/or modify
17  *   it under the terms of the GNU General Public License, version 2 if the
18  *   License as published by the Free Software Foundation.
19  * 
20  *   This program is distributed in the hope that it will be useful,
21  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
22  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23  *   GNU General Public License for more details.
24  *  
25  *   You should have received a copy of the GNU General Public License
26  *   along with this program; if not, write to the Free Software
27  *   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
28  *
29  * Copyright 2000  The FreeRADIUS server project.
30  * Copyright 1999  Jeff Carneal <jeff@apex.com>, Apex Internet Services, Inc.
31  * Copyright 2000  Alan DeKok <aland@ox.org>
32  */    
33 static const char rcsid[] = "$Id$";
34
35 #include "autoconf.h"
36 #include        "libradius.h"
37 #include "config.h"
38
39 #include <stdio.h>
40 #include <string.h>
41 #include <fcntl.h>
42 #include <grp.h>
43 #include <unistd.h>
44 #include <stdlib.h>
45 #include <errno.h>
46 #include <sys/stat.h>
47 #include <sys/types.h>
48
49 #if HAVE_SHADOW_H
50 #  include <shadow.h>
51 #endif
52
53 #include "radiusd.h"
54 #include "cache.h"
55 #include "compat.h"
56
57 /*
58  *  Static prototypes
59  */
60 static struct mypasswd *findHashUser(struct pwcache *cache, const char *user);
61 static int storeHashUser(struct pwcache *cache, struct mypasswd *new, int idx);
62 static int hashUserName(const char *s);
63
64 /* Builds the hash table up by storing passwd/shadow fields
65  * in memory.  Returns NULL on failure, pointer to the cache on success.
66  */
67 struct pwcache *unix_buildpwcache(const char *passwd_file,
68                                   const char *shadow_file,
69                                   const char *group_file)
70 {
71         FILE *passwd;
72 #if HAVE_SHADOW_H
73         FILE *shadow;
74 #endif
75         FILE *group;
76         char buffer[BUFSIZE];
77         char idtmp[10];
78         char username[256];
79         char *ptr, *bufptr;
80         int len, hashindex, numread=0;
81         struct mypasswd *new, *cur;
82
83         int len2, idx;
84         struct group *grp;
85         struct mygroup *g_new;
86         char **member;
87
88         struct pwcache *cache;
89
90         if (!passwd_file) {
91                 radlog(L_ERR, "rlm_unix:  You MUST specify a password file!");
92                 return NULL;
93         }
94
95         if (!group_file) {
96                 radlog(L_ERR, "rlm_unix:  You MUST specify a group file!");
97                 return NULL;
98         }
99
100 #if HAVE_SHADOW_H
101         if (!shadow_file) {
102                 radlog(L_ERR, "rlm_unix:  You MUST specify a shadow password file!");
103                 return NULL;
104         }
105 #endif
106
107         cache = rad_malloc(sizeof(*cache));
108
109         memset(username, 0, sizeof(username));
110
111         /* Init hash array */
112         memset(cache->hashtable, 0, sizeof cache->hashtable);
113         cache->grphead = NULL;
114
115         if ((passwd = fopen(passwd_file, "r")) == NULL) {
116                 radlog(L_ERR, "rlm_unix:  Can't open file password file %s: %s",
117                     passwd_file, strerror(errno));
118                 unix_freepwcache(cache);
119                 return NULL;
120         }
121
122         while(fgets(buffer, BUFSIZE , passwd) != (char *)NULL) {
123                 numread++;
124                 
125                 bufptr = buffer;
126                 /* Get usernames from password file */
127                 for(ptr = bufptr; *ptr!=':'; ptr++);
128                 len = ptr - bufptr;
129                 if((len+1) > MAX_STRING_LEN) {
130                         radlog(L_ERR, "rlm_unix:  Username too long in line: %s", buffer);
131                 }
132                 strncpy(username, buffer, len);
133                 username[len] = '\0';
134                 
135                 /* Hash the username */
136                 hashindex = hashUserName(username);     
137                 /*printf("%s:%d\n", username, hashindex);*/
138                 
139                 /* Allocate space for structure to go in hashtable */
140                 new = (struct mypasswd *)rad_malloc(sizeof(struct mypasswd));
141                 memset(new, 0, sizeof(struct mypasswd));
142                 
143                 /* Put username into new structure */
144                 new->pw_name = (char *)rad_malloc(strlen(username)+1);
145                 strncpy(new->pw_name, username, strlen(username)+1);
146                 
147                 /* Put passwords into array, if not shadowed */
148                 /* Get passwords from password file (shadow comes later) */
149                 ptr++;
150                 bufptr = ptr;
151                 while(*ptr!=':')
152                         ptr++;
153                 
154 #if !HAVE_SHADOW_H
155                 /* Put passwords into new structure (*/
156                 len = ptr - bufptr;
157                 
158                 if (len > 0) {
159                         new->pw_passwd = (char *)rad_malloc(len+1);
160                         strncpy(new->pw_passwd, bufptr, len);
161                         new->pw_passwd[len] = '\0';
162                 } else {
163                         new->pw_passwd = NULL;
164                 }
165                 
166 #endif /* !HAVE_SHADOW_H */  
167                 
168                 /* 
169                  * Put uid into structure.  Not sure why, but 
170                  * at least we'll have it later if we need it
171                  */
172                 ptr++;
173                 bufptr = ptr;
174                 while(*ptr!=':')
175                         ptr++;
176                 len = ptr - bufptr;
177                 strncpy(idtmp, bufptr, len);
178                 idtmp[len] = '\0';
179                 new->pw_uid = (uid_t)atoi(idtmp);       
180                 
181                 /* 
182                  * Put gid into structure.  
183                  */
184                 ptr++;
185                 bufptr = ptr;
186                 while(*ptr!=':')
187                         ptr++;
188                 len = ptr - bufptr;
189                 strncpy(idtmp, bufptr, len);
190                 idtmp[len] = '\0';
191                 new->pw_gid = (gid_t)atoi(idtmp);       
192                 
193                 /* 
194                  * Put name into structure.  
195                  */
196                 ptr++;
197                 bufptr = ptr;
198                 while(*ptr!=':')
199                         ptr++;
200                 
201                 len = ptr - bufptr;
202                 new->pw_gecos = (char *)rad_malloc(len+1);
203                 strncpy(new->pw_gecos, bufptr, len);
204                 new->pw_gecos[len] = '\0';
205                 
206                 /* 
207                  * We'll skip home dir and shell
208                  * as I can't think of any use for storing them
209                  */
210                 
211                 /*printf("User:  %s, UID:  %d, GID:  %d\n", new->pw_name, new->pw_uid, new->pw_gid);*/
212                 /* Store user in the hash */
213                 storeHashUser(cache, new, hashindex);
214         }       /* End while(fgets(buffer, BUFSIZE , passwd) != (char *)NULL) */
215         fclose(passwd);
216
217 #if HAVE_SHADOW_H
218         /*
219          *      FIXME: Check for password expiry!
220          */
221         if ((shadow = fopen(shadow_file, "r")) == NULL) {
222                 radlog(L_ERR, "HASH:  Can't open file %s: %s",
223                     shadow_file, strerror(errno));
224                 unix_freepwcache(cache);
225                 return NULL;
226         } else {
227                 while(fgets(buffer, BUFSIZE , shadow) != (char *)NULL) {
228
229                         bufptr = buffer;
230                         /* Get usernames from shadow file */
231                         for(ptr = bufptr; *ptr!=':'; ptr++);
232                         len = ptr - bufptr;
233                         if((len+1) > MAX_STRING_LEN) {
234                                 radlog(L_ERR, "HASH:  Username too long in line: %s", buffer);
235                         }
236                         strncpy(username, buffer, len);
237                         username[len] = '\0';
238                         if((new = findHashUser(cache, username)) == NULL) {
239                                 radlog(L_ERR, "HASH:  Username %s in shadow but not passwd??", username);
240                                 continue;
241                         }
242
243                         /* 
244                          * In order to put passwd in correct structure, we have
245                          * to skip any struct that has a passwd already for that
246                          * user
247                          */ 
248                         cur = new;
249                         while(new && (strcmp(new->pw_name, username)<=0) 
250                                                 && (new->pw_passwd == NULL)) {
251                                 cur = new;
252                                 new = new->next;
253                         }               
254                         /* Go back one, we passed it in the above loop */
255                         new = cur;
256
257                         /*
258                          * When we get here, we should be at the last duplicate
259                          * user structure in this hash bucket
260                          */ 
261
262                         /* Put passwords into struct from shadow file */
263                         ptr++;
264                         bufptr = ptr;
265                         while(*ptr!=':')
266                                 ptr++;
267                         len = ptr - bufptr;
268
269                         if (len > 0) {
270                                 new->pw_passwd = (char *)rad_malloc(len+1);
271                                 strncpy(new->pw_passwd, bufptr, len);
272                                 new->pw_passwd[len] = '\0';
273                         } else {
274                                 new->pw_passwd = NULL;
275                         }
276                 }
277         }
278         fclose(shadow);
279 #endif
280
281         /* log how many entries we stored from the passwd file */
282         radlog(L_INFO, "HASH:  Stored %d entries from %s", numread, passwd_file);
283
284         /* The remainder of this function caches the /etc/group or equivalent
285          * file, so it's one less thing we have to lookup on disk.  it uses
286          * fgetgrent(), which is quite slow, but the group file is generally
287          * small enough that it won't matter
288          * As a side note, caching the user list per group was a major pain
289          * in the ass, and I won't even need it.  I really hope that somebody
290          * out there needs and appreciates it.
291          */
292
293         if ((group = fopen(group_file, "r")) == NULL) {
294                 radlog(L_ERR, "rlm_unix:  Can't open file group file %s: %s",
295                     group_file, strerror(errno));
296                 unix_freepwcache(cache);
297                 return NULL;
298         }
299         numread = 0;
300
301         /* Get next entry from the group file */
302         while((grp = fgetgrent(group)) != NULL) {
303
304                 /* Make new mygroup structure in mem */
305                 g_new = (struct mygroup *)rad_malloc(sizeof(struct mygroup));
306                 memset(g_new, 0, sizeof(struct mygroup));
307         
308                 /* copy grp entries to my structure */
309                 len = strlen(grp->gr_name);
310                 g_new->gr_name = (char *)rad_malloc(len+1);
311                 strncpy(g_new->gr_name, grp->gr_name, len);
312                 g_new->gr_name[len] = '\0';
313                 
314                 len = strlen(grp->gr_passwd);
315                 g_new->gr_passwd= (char *)rad_malloc(len+1);
316                 strncpy(g_new->gr_passwd, grp->gr_passwd, len);
317                 g_new->gr_passwd[len] = '\0';
318
319                 g_new->gr_gid = grp->gr_gid;    
320                 
321                 /* Allocate space for user list, as much as I hate doing groups
322                  * that way.  
323                  */
324                 for(member = grp->gr_mem; *member!=NULL; member++);
325                 len = member - grp->gr_mem;
326                 g_new->gr_mem = (char **)rad_malloc((len+1)*sizeof(char **));
327
328                 /* Now go back and copy individual users into it */
329                 for(member = grp->gr_mem; *member; member++) {
330                         len2 = strlen(*member);
331                         idx = member - grp->gr_mem;
332                         g_new->gr_mem[idx] = (char *)rad_malloc(len2+1);
333                         strncpy(g_new->gr_mem[idx], *member, len2);
334                         g_new->gr_mem[idx][len2] = '\0';
335                 }
336                 /* Make sure last entry in user list is 0 so we can loop thru it */
337                 g_new->gr_mem[len] = 0;
338
339                 /* Insert at beginning of list */
340                 g_new->next = cache->grphead;
341                 cache->grphead = g_new;
342
343                 numread++;
344         }
345
346         /* End */
347         fclose(group);
348
349         radlog(L_INFO, "HASH:  Stored %d entries from %s", numread, group_file);
350
351         return cache;
352 }
353
354 void unix_freepwcache(struct pwcache *cache)
355 {
356         int hashindex;
357         struct mypasswd *cur, *next;
358
359         struct mygroup *g_cur, *g_next;
360         char **member;
361
362         for(hashindex=0; hashindex<HASHTABLESIZE; hashindex++) {
363                 if(cache->hashtable[hashindex]) {
364                         cur = cache->hashtable[hashindex];
365                         while(cur) {
366                                 next = cur->next;
367                                 free(cur->pw_name);
368                                 if (cur->pw_passwd) free(cur->pw_passwd);
369                                 free(cur->pw_gecos);
370                                 free(cur);
371                                 cur = next;
372                         }
373                 }
374         }       
375
376         g_cur = cache->grphead;
377
378         while(g_cur) {
379                 g_next = g_cur->next;
380
381                 /* Free name, name, member list */
382                 for(member = g_cur->gr_mem; *member; member++) {
383                         free(*member);
384                 }
385                 free(g_cur->gr_mem);
386                 free(g_cur->gr_name);
387                 free(g_cur->gr_passwd);
388                 free(g_cur);
389                 g_cur = g_next;
390         }                                  
391
392         free(cache);
393 }
394
395 /*
396  * Looks up user in hashtable.  If user can't be found, returns 0.  
397  * Otherwise returns a pointer to the structure for the user
398  */
399 static struct mypasswd *findHashUser(struct pwcache *cache, const char *user)
400 {
401
402         struct mypasswd *cur;
403         int idx;
404
405         /* first hash the username and get the index into the hashtable */
406         idx = hashUserName(user);
407
408         cur = cache->hashtable[idx];
409
410         while((cur != NULL) && (strcmp(cur->pw_name, user))) {
411                 cur = cur->next;
412         }
413
414         if(cur) {
415                 DEBUG2("  HASH:  user %s found in hashtable bucket %d", user, idx);
416                 return cur;
417         }
418
419         return (struct mypasswd *)0;
420
421 }
422
423 /* Stores the username sent into the hashtable */
424 static int storeHashUser(struct pwcache *cache, struct mypasswd *new, int idx)
425 {
426
427         /* store new record at beginning of list */
428         new->next = cache->hashtable[idx];
429         cache->hashtable[idx] = new;
430
431         return 1;
432 }
433
434 /* Hashes the username sent to it and returns index into hashtable */
435 static int hashUserName(const char *s) {
436         unsigned long hash = 0;
437
438         while (*s != '\0') {
439                 hash = hash * 7907 + (unsigned char)*s++;
440         }
441
442         return (hash % HASHTABLESIZE);
443 }              
444
445 /*
446  *      Emulate the cistron unix_pass function, but do it using 
447  *      our hashtable (iow, make it blaze).
448  * return  0 on success
449  * return -1 on failure
450  * return -2 on error (let caller fall back to old method)
451  */
452 int H_unix_pass(struct pwcache *cache, char *name, char *passwd,
453                 VALUE_PAIR **reply_items)
454 {
455         struct mypasswd *pwd;
456         char *encrypted_pass;
457
458         /*
459          *      Get encrypted password from password file
460          */
461         if ((pwd = findHashUser(cache, name)) == NULL) {
462                 /* Default to old way if user isn't hashed */
463                 return -2;
464         }
465         encrypted_pass = pwd->pw_passwd;
466
467         /*
468          *      We might have a passwordless account.
469          */
470         if(encrypted_pass == NULL) return 0;
471
472         if(mainconfig.do_usercollide) {
473                 while(pwd) {
474                         /* 
475                          * Make sure same user still.  If not, return as if
476                          * wrong pass given 
477                          */
478                         if(strcmp(name, pwd->pw_name)) 
479                                 return -1;      
480         
481                         /* 
482                          * Could still be null passwd
483                          */
484                         encrypted_pass = pwd->pw_passwd;
485                         if (encrypted_pass == NULL) {
486                                 return 0;
487                         }
488         
489                         /* 
490                          * Check password
491                          */
492                         if(lrad_crypt_check(passwd, encrypted_pass) == 0) {
493                                 /* 
494                                  * Add 'Class' pair here with value of full
495                                  * name from passwd
496                                  */
497                                 if(strlen(pwd->pw_gecos))
498                                         pairadd(reply_items, pairmake("Class", pwd->pw_gecos, T_OP_EQ));
499                                 
500                                 return 0;       
501                         }
502                         pwd = pwd->next;
503                 }
504                 /* 
505                  * If we get here, pwd is null, and no users matched 
506                  */
507                 return -1;
508         } else {
509                 /*
510                  *      Check encrypted password.
511                  */
512                 if (lrad_crypt_check(passwd, encrypted_pass))
513                         return -1;
514
515                 return 0;
516         }
517 }
518
519 /*
520  * Emulate groupcmp in files.c, but do it (much) faster
521  * return -2 on error (let caller fall back to old method),
522  * -1 on match fail, or 0 on success
523  */
524 int H_groupcmp(struct pwcache *cache, VALUE_PAIR *check, char *username)
525 {
526         struct mypasswd *pwd;
527         struct mygroup *cur;
528         char **member;
529
530         /* get the user from the hash */
531         if (!(pwd = findHashUser(cache, username)))
532                 return -2;
533
534         /* let's find this group */
535         if(cache->grphead) {
536                 cur = cache->grphead;
537                 while((cur) && (strcmp(cur->gr_name, (char *)check->strvalue))){
538                         cur = cur->next;        
539                 }       
540                 /* found the group, now compare it */
541                 if(!cur) {
542                         /* Default to old function if we can't find it */
543                         return -2;
544                 } else {
545                         if(pwd->pw_gid == cur->gr_gid) {
546                                 DEBUG2("  HASH:  matched user %s in group %s", username, cur->gr_name);
547                                 return 0;
548                         } else {
549                                 for(member = cur->gr_mem; *member; member++) {
550                                         if (strcmp(*member, pwd->pw_name) == 0) {
551                                                 DEBUG2("  HASH:  matched user %s in group %s", username, cur->gr_name);
552                                                 return 0;
553                                         }
554                                 }
555                         }
556                 }
557         }
558
559         return -1;
560 }