Initial revision
[freeradius.git] / src / modules / rlm_unix / cache.c
1 /*
2  * cache.c:  Offers ability to cache /etc/group, /etc/passwd, /etc/shadow,
3  *           and /var/log/radutmp 
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 appears
11  * to be fast enough.  It's generally a small enough file that hashing is
12  * unnecessary.
13  *
14  *      (c) 1999 Author - Jeff Carneal, Apex Internet Services, Inc.
15  *
16  * Version: cache.c  0.99  04-13-1999  jeff@apex.net
17  *
18  */    
19
20 #include "autoconf.h"
21
22 #include <stdio.h>
23 #include <string.h>
24 #include <fcntl.h>
25 #include <grp.h>
26 #include <unistd.h>
27 #include <stdlib.h>
28 #include <errno.h>
29 #include <sys/stat.h>
30 #include <sys/types.h>
31
32 #if HAVE_MALLOC_H
33 #  include <malloc.h>
34 #endif
35
36 #if HAVE_SHADOW_H
37 #  include <shadow.h>
38 #endif
39
40 #if HAVE_CRYPT_H
41 #  include <crypt.h>
42 #endif 
43
44 #include "radiusd.h"
45 #include "radutmp.h"
46 #include "cache.h"
47
48 /* Make the tables global since so many functions rely on them */
49 static struct mypasswd *hashtable[HASHTABLESIZE];
50 static struct mygroup *grphead = NULL;
51
52 /* Builds the hash table up by storing passwd/shadow fields
53  * in memory.  Returns -1 on failure, 0 on success.
54  */
55 int buildHashTable(void) {
56         FILE *passwd;
57 #if HAVE_SHADOW_H
58         FILE *shadow;
59 #endif
60         char buffer[BUFSIZE];
61         char idtmp[10];
62         char username[MAXUSERNAME];
63         char *ptr, *bufptr;
64         int len, hashindex, numread=0;
65         struct mypasswd *new, *cur, *next;
66
67         memset((char *)username, 0, MAXUSERNAME);
68
69         /* Initialize the table.  This works even if we're rebuilding it */
70         for(hashindex=0; hashindex<HASHTABLESIZE; hashindex++) {
71                 if(hashtable[hashindex]) {
72                         cur = hashtable[hashindex];
73                         while(cur) {
74                                 next = cur->next;
75                                 free(cur->pw_name);     
76                                 free(cur->pw_passwd);   
77                                 free(cur);      
78                                 cur = next;
79                         }
80                 }
81         }       
82
83         /* Init hash array */
84         memset((struct mypasswd *)hashtable, 0, (HASHTABLESIZE*(sizeof(struct mypasswd *))));
85
86         if( (passwd = fopen(PASSWDFILE, "r")) == NULL) {
87                 log(L_ERR, "HASH:  Can't open file %s:  %s", PASSWDFILE, strerror(errno));
88                 return -1;
89         } else {
90                 while(fgets(buffer, BUFSIZE , passwd) != (char *)NULL) {
91                         numread++;
92
93                         bufptr = buffer;
94                         /* Get usernames from password file */
95                         for(ptr = bufptr; *ptr!=':'; ptr++);
96                         len = ptr - bufptr;
97                         if((len+1) > MAXUSERNAME) {
98                                 log(L_ERR, "HASH:  Username too long in line: %s", buffer);
99                         }
100                         strncpy(username, buffer, len);
101                         username[len] = '\0';
102
103                         /* Hash the username */
104                         hashindex = hashUserName(username);     
105                         /*printf("%s:%d\n", username, hashindex);*/
106         
107                         /* Allocate space for structure to go in hashtable */
108                         if((new = (struct mypasswd *)malloc(sizeof(struct mypasswd))) == NULL) {
109                                 log(L_ERR, "HASH:  Out of memory!");
110                                 return -1;
111                         }
112                         memset((struct mypasswd *)new, 0, sizeof(struct mypasswd));
113
114                         /* Put username into new structure */
115                         if((new->pw_name = (char *)malloc(strlen(username)+1)) == NULL) {
116                                 log(L_ERR, "HASH:  Out of memory!");
117                                 return -1;
118                         }
119                         strncpy(new->pw_name, username, strlen(username)+1);
120
121                         /* Put passwords into array, if not shadowed */
122                         /* Get passwords from password file (shadow comes later) */
123                         ptr++;
124                         bufptr = ptr;
125                         while(*ptr!=':')
126                                 ptr++;
127
128 #if !HAVE_SHADOW_H
129                         /* Put passwords into new structure (*/
130                         len = ptr - bufptr;
131                         if((new->pw_passwd = (char *)malloc(len+1)) == NULL) {
132                                 log(L_ERR, "HASH:  Out of memory!");
133                                 return -1;
134                         }
135                         strncpy(new->pw_passwd, bufptr, len);
136                         new->pw_passwd[len] = '\0';
137 #endif /* !HAVE_SHADOW_H */  
138
139                         /* 
140                     * Put uid into structure.  Not sure why, but 
141                          * at least we'll have it later if we need it
142                          */
143                         ptr++;
144                         bufptr = ptr;
145                         while(*ptr!=':')
146                                 ptr++;
147                         len = ptr - bufptr;
148                         strncpy(idtmp, bufptr, len);
149                         idtmp[len] = '\0';
150                         new->pw_uid = (uid_t)atoi(idtmp);       
151
152                         /* 
153                     * Put gid into structure.  
154                          */
155                         ptr++;
156                         bufptr = ptr;
157                         while(*ptr!=':')
158                                 ptr++;
159                         len = ptr - bufptr;
160                         strncpy(idtmp, bufptr, len);
161                         idtmp[len] = '\0';
162                         new->pw_gid = (gid_t)atoi(idtmp);       
163
164                         /* 
165                          * We're skipping name, home dir, and shell
166                          * as I can't think of any use for storing them
167                          */
168
169                         /*printf("User:  %s, UID:  %d, GID:  %d\n", new->pw_name, new->pw_uid, new->pw_gid);*/
170                         /* Store user in the hash */
171                         storeHashUser(new, hashindex);
172                 }       /* End while(fgets(buffer, BUFSIZE , passwd) != (char *)NULL) { */
173         } /* End if */
174         fclose(passwd);
175
176 #if HAVE_SHADOW_H
177         /*
178          *      FIXME: Check for password expiry!
179          */
180         if( (shadow = fopen(SHADOWFILE, "r")) == NULL) {
181                 log(L_ERR, "HASH:  Can't open file %s: %s", SHADOWFILE, strerror(errno));
182                 return -1;
183         } else {
184                 while(fgets(buffer, BUFSIZE , shadow) != (char *)NULL) {
185
186                         bufptr = buffer;
187                         /* Get usernames from shadow file */
188                         for(ptr = bufptr; *ptr!=':'; ptr++);
189                         len = ptr - bufptr;
190                         if((len+1) > MAXUSERNAME) {
191                                 log(L_ERR, "HASH:  Username too long in line:  %s", buffer);
192                         }
193                         strncpy(username, buffer, len);
194                         username[len] = '\0';
195                         if((new = findHashUser(username)) == NULL) {
196                                 log(L_ERR, "HASH:  Username %s in shadow but not passwd??", username);
197                                 continue;
198                         }
199
200                         /* Put passwords into struct from shadow file */
201                         ptr++;
202                         bufptr = ptr;
203                         while(*ptr!=':')
204                                 ptr++;
205                         len = ptr - bufptr;
206
207                         if((new->pw_passwd = (char *)malloc(len+1)) == NULL) {
208                                 log(L_ERR, "HASH:  Out of memory!");
209                                 return -1;
210                         }
211                         strncpy(new->pw_passwd, bufptr, len);
212                         new->pw_passwd[len] = '\0';
213                 }
214         }
215         fclose(shadow);
216 #endif
217
218         /* Finally, let's look at radutmp and make a record of everyone
219     * that's logged in currently */
220         hashradutmp();
221
222         /* log how many entries we stored from the passwd file */
223         log(L_INFO, "HASH:  Stored %d entries from %s", numread, PASSWDFILE);
224
225         return 0;
226 }
227
228 /* This function caches the /etc/group file, so it's one less thing
229  * we have to lookup on disk.  it uses getgrent(), which is quite slow,
230  * but the group file is generally small enough that it won't matter
231  * As a side note, caching the user list per group was a major pain
232  * in the ass, and I won't even need it.  I really hope that somebody
233  * out there needs and appreciates it.
234  * Returns -1 on failure, and 0 on success
235  */
236 int buildGrpList(void) {
237
238         int len, len2, index, numread=0;
239         struct group *grp;
240         struct mygroup *new, *cur, *next;
241         char **member;
242
243         cur = grphead;
244
245         /* Free up former grp list (we can use this as a rebuild function too */
246         while(cur) {
247                 next = cur->next;
248
249                 /* Free name, name, member list */
250                 for(member = cur->gr_mem; *member; member++) {
251                         free(*member);
252                 }
253                 free(cur->gr_mem);
254                 free(cur->gr_name);
255                 free(cur->gr_passwd);
256                 free(cur);
257                 cur = next;
258         }                                  
259         grphead = NULL;
260         
261         /* Make sure to begin at beginning */
262         setgrent();
263
264         /* Get next entry from the group file */
265         while((grp = getgrent()) != NULL) {
266
267                 /* Make new mygroup structure in mem */
268                 if((new = (struct mygroup *)malloc(sizeof(struct mygroup))) == NULL) {
269                         log(L_ERR, "HASH:  (buildGrplist) Out of memory!");
270                         return -1;
271                 }
272                 memset((struct mygroup*)new, 0, sizeof(struct mygroup));
273         
274                 /* copy grp entries to my structure */
275                 len = strlen(grp->gr_name);
276                 if((new->gr_name = (char *)malloc(len+1)) == NULL) {
277                         log(L_ERR, "HASH:  (buildGrplist) Out of memory!");
278                         return -1;
279                 }
280                 strncpy(new->gr_name, grp->gr_name, len);
281                 new->gr_name[len] = '\0';
282                 
283                 len = strlen(grp->gr_passwd);
284                 if((new->gr_passwd= (char *)malloc(len+1)) == NULL) {
285                         log(L_ERR, "HASH:  (buildGrplist) Out of memory!");
286                         return -1;
287                 }
288                 strncpy(new->gr_passwd, grp->gr_passwd, len);
289                 new->gr_passwd[len] = '\0';
290
291                 new->gr_gid = grp->gr_gid;      
292                 
293                 /* Allocate space for user list, as much as I hate doing groups
294                  * that way.  
295                  */
296                 for(member = grp->gr_mem; *member!=NULL; member++);
297                 len = member - grp->gr_mem;
298                 if((new->gr_mem = (char **)malloc((len+1)*sizeof(char **))) == NULL) {
299                         log(L_ERR, "HASH:  (buildGrplist) Out of memory!");
300                         return -1;
301                 }
302                 /* Now go back and copy individual users into it */
303                 for(member = grp->gr_mem; *member; member++) {
304                         len2 = strlen(*member);
305                         index = member - grp->gr_mem;
306                         if((new->gr_mem[index] = (char *)malloc(len2+1)) == NULL) {
307                                 log(L_ERR, "HASH:  (buildGrplist) Out of memory!");
308                                 return -1;
309                         }
310                         strncpy(new->gr_mem[index], *member, len2);
311                         new->gr_mem[index][len2] = '\0';
312                 }
313                 /* Make sure last entry in user list is 0 so we can loop thru it */
314                 new->gr_mem[len] = 0;
315
316                 /* Insert at beginning of list */
317                 new->next = grphead;
318                 grphead = new;
319
320                 numread++;
321         }
322
323         /* End */
324         endgrent();
325
326         log(L_INFO, "HASH:  Stored %d entries from /etc/group", numread);
327
328         return 0;
329 }
330
331 /* This function changes the loggedin variable for a user
332  * when they login or out.  This lets us keep track of 
333  * what radutmp is doing without having to read it
334  */
335 void chgLoggedin(char *user, int diff) {
336         struct mypasswd *cur;
337
338         cur = findHashUser(user);
339         if(cur) {
340                 cur->loggedin += diff;
341                 /* Can't have less than 0 logins */
342                 if(cur->loggedin<0) {
343                         cur->loggedin = 0;
344                 }
345                 DEBUG2("  HASH:  Changed user %s to %d logins", user, cur->loggedin);
346         }
347 }
348
349 /*
350  * Looks up user in hashtable.  If user can't be found, returns 0.  
351  * Otherwise returns a pointer to the structure for the user
352  */
353 struct mypasswd *findHashUser(const char *user) {
354
355         struct mypasswd *cur;
356         int index;
357
358         /* first hash the username and get the index into the hashtable */
359         index = hashUserName(user);
360
361         cur = hashtable[index];
362
363         while((cur != NULL) && (strcmp(cur->pw_name, user))) {
364                 cur = cur->next;
365         }
366
367         if(cur) {
368                 DEBUG2("  HASH:  user %s found in hashtable bucket %d", user, index);
369                 return cur;
370         }
371
372         return (struct mypasswd *)0;
373
374 }
375
376 /* Stores the username sent into the hashtable */
377 int storeHashUser(struct mypasswd *new, int index) {
378
379         /* store new record at beginning of list */
380         new->next = hashtable[index];
381         hashtable[index] = new;
382
383         return 1;
384 }
385
386 /* Hashes the username sent to it and returns index into hashtable */
387 int hashUserName(const char *s) {
388      unsigned long hash = 0;
389
390      while (*s != '\0') {
391          hash = hash * 7907 + (unsigned char)*s++;
392                 }
393
394      return (hash % HASHTABLESIZE);
395 }              
396
397 /* Reads radutmp file, and increments the loggedin variable
398  * for every login a user has...assuming we can find the user
399  * in the hashtable
400  */
401 int hashradutmp(void) {
402
403         int fd;
404         struct radutmp u;
405         struct mypasswd *cur;
406
407    if ((fd = open(RADUTMP, O_CREAT|O_RDONLY, 0644)) < 0)
408       return 0;
409
410    while(read(fd, &u, sizeof(u)) == sizeof(u)) {
411       if ((u.login) && (u.type == P_LOGIN)) {
412                         cur = findHashUser(u.login);
413                         if(cur) {
414                                 cur->loggedin++;                
415                         }
416                 }
417         }
418         close(fd);
419
420         return 1;
421 }
422
423 /*
424  *      Emulate the cistron unix_pass function, but do it using 
425  *      our hashtable (iow, make it blaze).
426  * return  0 on success
427  * return -1 on failure
428  * return -2 on error (let caller fall back to old method)
429  */
430 int H_unix_pass(char *name, char *passwd) {
431         struct mypasswd *pwd;
432         char *encrypted_pass;
433         char *encpw;
434
435         /*
436          *      Get encrypted password from password file
437          */
438         if ((pwd = findHashUser(name)) == NULL) {
439                 /* Default to old way if user isn't hashed */
440                 return -2;
441         }
442         encrypted_pass = pwd->pw_passwd;
443
444         /*
445          *      We might have a passwordless account.
446          */
447         if (encrypted_pass[0] == 0) return 0;
448
449         /*
450          *      Check encrypted password.
451          */
452         encpw = (char *)crypt(passwd, encrypted_pass);
453         if (strcmp(encpw, encrypted_pass))
454                 return -1;
455
456         return 0;
457 }
458
459 /*
460  * Emulate groupcmp in files.c, but do it (much) faster
461  * return -2 on error (let caller fall back to old method),
462  * -1 on match fail, or 0 on success
463  */
464 int H_groupcmp(VALUE_PAIR *check, char *username) {
465         struct mypasswd *pwd;
466         struct mygroup *cur;
467         char **member;
468
469         /* get the user from the hash */
470         if (!(pwd = findHashUser(username)))
471                 return -2;
472
473         /* let's find this group */
474         if(grphead) {
475                 cur = grphead;
476                 while((cur) && (strcmp(cur->gr_name, check->strvalue))) {
477                         cur = cur->next;        
478                 }       
479                 /* found the group, now compare it */
480                 if(!cur) {
481                         /* Default to old function if we can't find it */
482                         return -2;
483                 } else {
484                         if(pwd->pw_gid == cur->gr_gid) {
485                                 DEBUG2("  HASH:  matched user %s in group %s", username, cur->gr_name);
486                                 return 0;
487                         } else {
488                                 for(member = cur->gr_mem; *member; member++) {
489                                         if (strcmp(*member, pwd->pw_name) == 0) {
490                                                 DEBUG2("  HASH:  matched user %s in group %s", username, cur->gr_name);
491                                                 return 0;
492                                         }
493                                 }
494                         }
495                 }
496         }
497
498         return -1;
499 }