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