2 * cache.c Offers ability to cache /etc/group, /etc/passwd,
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.
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.
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.
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.
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.
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>
34 static const char rcsid[] = "$Id$";
37 #include "libradius.h"
48 #include <sys/types.h>
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);
65 /* Builds the hash table up by storing passwd/shadow fields
66 * in memory. Returns NULL on failure, pointer to the cache on success.
68 struct pwcache *unix_buildpwcache(const char *passwd_file,
69 const char *shadow_file,
70 const char *group_file)
81 int len, hashindex, numread=0;
82 struct mypasswd *new, *cur;
86 struct mygroup *g_new;
89 struct pwcache *cache;
92 radlog(L_ERR, "rlm_unix: You MUST specify a password file!");
97 radlog(L_ERR, "rlm_unix: You MUST specify a group file!");
103 radlog(L_ERR, "rlm_unix: You MUST specify a shadow password file!");
108 cache = rad_malloc(sizeof(*cache));
110 memset(username, 0, sizeof(username));
112 /* Init hash array */
113 memset(cache->hashtable, 0, sizeof cache->hashtable);
114 cache->grphead = NULL;
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);
123 while(fgets(buffer, BUFSIZE , passwd) != (char *)NULL) {
127 /* Get usernames from password file */
128 for(ptr = bufptr; *ptr!=':'; ptr++);
130 if((len+1) > MAX_STRING_LEN) {
131 radlog(L_ERR, "rlm_unix: Username too long in line: %s", buffer);
133 strncpy(username, buffer, len);
134 username[len] = '\0';
136 /* Hash the username */
137 hashindex = hashUserName(username);
138 /*printf("%s:%d\n", username, hashindex);*/
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));
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);
148 /* Put passwords into array, if not shadowed */
149 /* Get passwords from password file (shadow comes later) */
156 /* Put passwords into new structure (*/
160 new->pw_passwd = (char *)rad_malloc(len+1);
161 strncpy(new->pw_passwd, bufptr, len);
162 new->pw_passwd[len] = '\0';
164 new->pw_passwd = NULL;
167 #endif /* !HAVE_SHADOW_H */
170 * Put uid into structure. Not sure why, but
171 * at least we'll have it later if we need it
178 strncpy(idtmp, bufptr, len);
180 new->pw_uid = (uid_t)atoi(idtmp);
183 * Put gid into structure.
190 strncpy(idtmp, bufptr, len);
192 new->pw_gid = (gid_t)atoi(idtmp);
195 * Put name into structure.
203 new->pw_gecos = (char *)rad_malloc(len+1);
204 strncpy(new->pw_gecos, bufptr, len);
205 new->pw_gecos[len] = '\0';
208 * We'll skip home dir and shell
209 * as I can't think of any use for storing them
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) */
220 * FIXME: Check for password expiry!
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);
228 while(fgets(buffer, BUFSIZE , shadow) != (char *)NULL) {
231 /* Get usernames from shadow file */
232 for(ptr = bufptr; *ptr!=':'; ptr++);
234 if((len+1) > MAX_STRING_LEN) {
235 radlog(L_ERR, "HASH: Username too long in line: %s", buffer);
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);
245 * In order to put passwd in correct structure, we have
246 * to skip any struct that has a passwd already for that
250 while(new && (strcmp(new->pw_name, username)<=0)
251 && (new->pw_passwd == NULL)) {
255 /* Go back one, we passed it in the above loop */
259 * When we get here, we should be at the last duplicate
260 * user structure in this hash bucket
263 /* Put passwords into struct from shadow file */
271 new->pw_passwd = (char *)rad_malloc(len+1);
272 strncpy(new->pw_passwd, bufptr, len);
273 new->pw_passwd[len] = '\0';
275 new->pw_passwd = NULL;
282 /* log how many entries we stored from the passwd file */
283 radlog(L_INFO, "HASH: Stored %d entries from %s", numread, passwd_file);
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.
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);
302 /* Get next entry from the group file */
303 while((grp = fgetgrent(group)) != NULL) {
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));
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';
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';
320 g_new->gr_gid = grp->gr_gid;
322 /* Allocate space for user list, as much as I hate doing groups
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 **));
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';
337 /* Make sure last entry in user list is 0 so we can loop thru it */
338 g_new->gr_mem[len] = 0;
340 /* Insert at beginning of list */
341 g_new->next = cache->grphead;
342 cache->grphead = g_new;
350 radlog(L_INFO, "HASH: Stored %d entries from %s", numread, group_file);
355 void unix_freepwcache(struct pwcache *cache)
358 struct mypasswd *cur, *next;
360 struct mygroup *g_cur, *g_next;
363 for(hashindex=0; hashindex<HASHTABLESIZE; hashindex++) {
364 if(cache->hashtable[hashindex]) {
365 cur = cache->hashtable[hashindex];
369 if (cur->pw_passwd) free(cur->pw_passwd);
377 g_cur = cache->grphead;
380 g_next = g_cur->next;
382 /* Free name, name, member list */
383 for(member = g_cur->gr_mem; *member; member++) {
387 free(g_cur->gr_name);
388 free(g_cur->gr_passwd);
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
400 static struct mypasswd *findHashUser(struct pwcache *cache, const char *user)
403 struct mypasswd *cur;
406 /* first hash the username and get the index into the hashtable */
407 idx = hashUserName(user);
409 cur = cache->hashtable[idx];
411 while((cur != NULL) && (strcmp(cur->pw_name, user))) {
416 DEBUG2(" HASH: user %s found in hashtable bucket %d", user, idx);
420 return (struct mypasswd *)0;
424 /* Stores the username sent into the hashtable */
425 static int storeHashUser(struct pwcache *cache, struct mypasswd *new, int idx)
428 /* store new record at beginning of list */
429 new->next = cache->hashtable[idx];
430 cache->hashtable[idx] = new;
435 /* Hashes the username sent to it and returns index into hashtable */
436 static int hashUserName(const char *s) {
437 unsigned long hash = 0;
440 hash = hash * 7907 + (unsigned char)*s++;
443 return (hash % HASHTABLESIZE);
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)
453 int H_unix_pass(struct pwcache *cache, char *name, char *passwd,
454 VALUE_PAIR **reply_items)
456 struct mypasswd *pwd;
457 char *encrypted_pass;
460 * Get encrypted password from password file
462 if ((pwd = findHashUser(cache, name)) == NULL) {
463 /* Default to old way if user isn't hashed */
466 encrypted_pass = pwd->pw_passwd;
469 * We might have a passwordless account.
471 if(encrypted_pass == NULL) return 0;
473 if(mainconfig.do_usercollide) {
476 * Make sure same user still. If not, return as if
479 if(strcmp(name, pwd->pw_name))
483 * Could still be null passwd
485 encrypted_pass = pwd->pw_passwd;
486 if (encrypted_pass == NULL) {
493 if(lrad_crypt_check(passwd, encrypted_pass) == 0) {
495 * Add 'Class' pair here with value of full
498 if(strlen(pwd->pw_gecos))
499 pairadd(reply_items, pairmake("Class", pwd->pw_gecos, T_OP_EQ));
506 * If we get here, pwd is null, and no users matched
511 * Check encrypted password.
513 if (lrad_crypt_check(passwd, encrypted_pass))
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
525 int H_groupcmp(struct pwcache *cache, VALUE_PAIR *check, char *username)
527 struct mypasswd *pwd;
531 /* get the user from the hash */
532 if (!(pwd = findHashUser(cache, username)))
535 /* let's find this group */
537 cur = cache->grphead;
538 while((cur) && (strcmp(cur->gr_name, (char *)check->strvalue))){
541 /* found the group, now compare it */
543 /* Default to old function if we can't find it */
546 if(pwd->pw_gid == cur->gr_gid) {
547 DEBUG2(" HASH: matched user %s in group %s", username, cur->gr_name);
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);