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 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.
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.
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.
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>
33 static const char rcsid[] = "$Id$";
36 #include "libradius.h"
47 #include <sys/types.h>
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);
64 /* Builds the hash table up by storing passwd/shadow fields
65 * in memory. Returns NULL on failure, pointer to the cache on success.
67 struct pwcache *unix_buildpwcache(const char *passwd_file,
68 const char *shadow_file,
69 const char *group_file)
80 int len, hashindex, numread=0;
81 struct mypasswd *new, *cur;
85 struct mygroup *g_new;
88 struct pwcache *cache;
91 radlog(L_ERR, "rlm_unix: You MUST specify a password file!");
96 radlog(L_ERR, "rlm_unix: You MUST specify a group file!");
102 radlog(L_ERR, "rlm_unix: You MUST specify a shadow password file!");
107 cache = rad_malloc(sizeof(*cache));
109 memset(username, 0, sizeof(username));
111 /* Init hash array */
112 memset(cache->hashtable, 0, sizeof cache->hashtable);
113 cache->grphead = NULL;
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);
122 while(fgets(buffer, BUFSIZE , passwd) != (char *)NULL) {
126 /* Get usernames from password file */
127 for(ptr = bufptr; *ptr!=':'; ptr++);
129 if((len+1) > MAX_STRING_LEN) {
130 radlog(L_ERR, "rlm_unix: Username too long in line: %s", buffer);
132 strncpy(username, buffer, len);
133 username[len] = '\0';
135 /* Hash the username */
136 hashindex = hashUserName(username);
137 /*printf("%s:%d\n", username, hashindex);*/
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));
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);
147 /* Put passwords into array, if not shadowed */
148 /* Get passwords from password file (shadow comes later) */
155 /* Put passwords into new structure (*/
159 new->pw_passwd = (char *)rad_malloc(len+1);
160 strncpy(new->pw_passwd, bufptr, len);
161 new->pw_passwd[len] = '\0';
163 new->pw_passwd = NULL;
166 #endif /* !HAVE_SHADOW_H */
169 * Put uid into structure. Not sure why, but
170 * at least we'll have it later if we need it
177 strncpy(idtmp, bufptr, len);
179 new->pw_uid = (uid_t)atoi(idtmp);
182 * Put gid into structure.
189 strncpy(idtmp, bufptr, len);
191 new->pw_gid = (gid_t)atoi(idtmp);
194 * Put name into structure.
202 new->pw_gecos = (char *)rad_malloc(len+1);
203 strncpy(new->pw_gecos, bufptr, len);
204 new->pw_gecos[len] = '\0';
207 * We'll skip home dir and shell
208 * as I can't think of any use for storing them
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) */
219 * FIXME: Check for password expiry!
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);
227 while(fgets(buffer, BUFSIZE , shadow) != (char *)NULL) {
230 /* Get usernames from shadow file */
231 for(ptr = bufptr; *ptr!=':'; ptr++);
233 if((len+1) > MAX_STRING_LEN) {
234 radlog(L_ERR, "HASH: Username too long in line: %s", buffer);
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);
244 * In order to put passwd in correct structure, we have
245 * to skip any struct that has a passwd already for that
249 while(new && (strcmp(new->pw_name, username)<=0)
250 && (new->pw_passwd == NULL)) {
254 /* Go back one, we passed it in the above loop */
258 * When we get here, we should be at the last duplicate
259 * user structure in this hash bucket
262 /* Put passwords into struct from shadow file */
270 new->pw_passwd = (char *)rad_malloc(len+1);
271 strncpy(new->pw_passwd, bufptr, len);
272 new->pw_passwd[len] = '\0';
274 new->pw_passwd = NULL;
281 /* log how many entries we stored from the passwd file */
282 radlog(L_INFO, "HASH: Stored %d entries from %s", numread, passwd_file);
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.
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);
301 /* Get next entry from the group file */
302 while((grp = fgetgrent(group)) != NULL) {
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));
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';
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';
319 g_new->gr_gid = grp->gr_gid;
321 /* Allocate space for user list, as much as I hate doing groups
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 **));
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';
336 /* Make sure last entry in user list is 0 so we can loop thru it */
337 g_new->gr_mem[len] = 0;
339 /* Insert at beginning of list */
340 g_new->next = cache->grphead;
341 cache->grphead = g_new;
349 radlog(L_INFO, "HASH: Stored %d entries from %s", numread, group_file);
354 void unix_freepwcache(struct pwcache *cache)
357 struct mypasswd *cur, *next;
359 struct mygroup *g_cur, *g_next;
362 for(hashindex=0; hashindex<HASHTABLESIZE; hashindex++) {
363 if(cache->hashtable[hashindex]) {
364 cur = cache->hashtable[hashindex];
368 if (cur->pw_passwd) free(cur->pw_passwd);
376 g_cur = cache->grphead;
379 g_next = g_cur->next;
381 /* Free name, name, member list */
382 for(member = g_cur->gr_mem; *member; member++) {
386 free(g_cur->gr_name);
387 free(g_cur->gr_passwd);
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
399 static struct mypasswd *findHashUser(struct pwcache *cache, const char *user)
402 struct mypasswd *cur;
405 /* first hash the username and get the index into the hashtable */
406 idx = hashUserName(user);
408 cur = cache->hashtable[idx];
410 while((cur != NULL) && (strcmp(cur->pw_name, user))) {
415 DEBUG2(" HASH: user %s found in hashtable bucket %d", user, idx);
419 return (struct mypasswd *)0;
423 /* Stores the username sent into the hashtable */
424 static int storeHashUser(struct pwcache *cache, struct mypasswd *new, int idx)
427 /* store new record at beginning of list */
428 new->next = cache->hashtable[idx];
429 cache->hashtable[idx] = new;
434 /* Hashes the username sent to it and returns index into hashtable */
435 static int hashUserName(const char *s) {
436 unsigned long hash = 0;
439 hash = hash * 7907 + (unsigned char)*s++;
442 return (hash % HASHTABLESIZE);
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)
452 int H_unix_pass(struct pwcache *cache, char *name, char *passwd,
453 VALUE_PAIR **reply_items)
455 struct mypasswd *pwd;
456 char *encrypted_pass;
459 * Get encrypted password from password file
461 if ((pwd = findHashUser(cache, name)) == NULL) {
462 /* Default to old way if user isn't hashed */
465 encrypted_pass = pwd->pw_passwd;
468 * We might have a passwordless account.
470 if(encrypted_pass == NULL) return 0;
472 if(mainconfig.do_usercollide) {
475 * Make sure same user still. If not, return as if
478 if(strcmp(name, pwd->pw_name))
482 * Could still be null passwd
484 encrypted_pass = pwd->pw_passwd;
485 if (encrypted_pass == NULL) {
492 if(lrad_crypt_check(passwd, encrypted_pass) == 0) {
494 * Add 'Class' pair here with value of full
497 if(strlen(pwd->pw_gecos))
498 pairadd(reply_items, pairmake("Class", pwd->pw_gecos, T_OP_EQ));
505 * If we get here, pwd is null, and no users matched
510 * Check encrypted password.
512 if (lrad_crypt_check(passwd, encrypted_pass))
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
524 int H_groupcmp(struct pwcache *cache, VALUE_PAIR *check, char *username)
526 struct mypasswd *pwd;
530 /* get the user from the hash */
531 if (!(pwd = findHashUser(cache, username)))
534 /* let's find this group */
536 cur = cache->grphead;
537 while((cur) && (strcmp(cur->gr_name, (char *)check->strvalue))){
540 /* found the group, now compare it */
542 /* Default to old function if we can't find it */
545 if(pwd->pw_gid == cur->gr_gid) {
546 DEBUG2(" HASH: matched user %s in group %s", username, cur->gr_name);
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);