2 * cache.c: Offers ability to cache /etc/group, /etc/passwd, /etc/shadow,
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 appears
11 * to be fast enough. It's generally a small enough file that hashing is
14 * (c) 1999 Author - Jeff Carneal, Apex Internet Services, Inc.
16 * Version: cache.c 0.99 04-13-1999 jeff@apex.net
30 #include <sys/types.h>
48 /* Make the tables global since so many functions rely on them */
49 static struct mypasswd *hashtable[HASHTABLESIZE];
50 static struct mygroup *grphead = NULL;
52 /* Builds the hash table up by storing passwd/shadow fields
53 * in memory. Returns -1 on failure, 0 on success.
55 int buildHashTable(void) {
62 char username[MAXUSERNAME];
64 int len, hashindex, numread=0;
65 struct mypasswd *new, *cur, *next;
67 memset((char *)username, 0, MAXUSERNAME);
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];
84 memset((struct mypasswd *)hashtable, 0, (HASHTABLESIZE*(sizeof(struct mypasswd *))));
86 if( (passwd = fopen(PASSWDFILE, "r")) == NULL) {
87 log(L_ERR, "HASH: Can't open file %s: %s", PASSWDFILE, strerror(errno));
90 while(fgets(buffer, BUFSIZE , passwd) != (char *)NULL) {
94 /* Get usernames from password file */
95 for(ptr = bufptr; *ptr!=':'; ptr++);
97 if((len+1) > MAXUSERNAME) {
98 log(L_ERR, "HASH: Username too long in line: %s", buffer);
100 strncpy(username, buffer, len);
101 username[len] = '\0';
103 /* Hash the username */
104 hashindex = hashUserName(username);
105 /*printf("%s:%d\n", username, hashindex);*/
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!");
112 memset((struct mypasswd *)new, 0, sizeof(struct mypasswd));
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!");
119 strncpy(new->pw_name, username, strlen(username)+1);
121 /* Put passwords into array, if not shadowed */
122 /* Get passwords from password file (shadow comes later) */
129 /* Put passwords into new structure (*/
131 if((new->pw_passwd = (char *)malloc(len+1)) == NULL) {
132 log(L_ERR, "HASH: Out of memory!");
135 strncpy(new->pw_passwd, bufptr, len);
136 new->pw_passwd[len] = '\0';
137 #endif /* !HAVE_SHADOW_H */
140 * Put uid into structure. Not sure why, but
141 * at least we'll have it later if we need it
148 strncpy(idtmp, bufptr, len);
150 new->pw_uid = (uid_t)atoi(idtmp);
153 * Put gid into structure.
160 strncpy(idtmp, bufptr, len);
162 new->pw_gid = (gid_t)atoi(idtmp);
165 * We're skipping name, home dir, and shell
166 * as I can't think of any use for storing them
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) { */
178 * FIXME: Check for password expiry!
180 if( (shadow = fopen(SHADOWFILE, "r")) == NULL) {
181 log(L_ERR, "HASH: Can't open file %s: %s", SHADOWFILE, strerror(errno));
184 while(fgets(buffer, BUFSIZE , shadow) != (char *)NULL) {
187 /* Get usernames from shadow file */
188 for(ptr = bufptr; *ptr!=':'; ptr++);
190 if((len+1) > MAXUSERNAME) {
191 log(L_ERR, "HASH: Username too long in line: %s", buffer);
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);
200 /* Put passwords into struct from shadow file */
207 if((new->pw_passwd = (char *)malloc(len+1)) == NULL) {
208 log(L_ERR, "HASH: Out of memory!");
211 strncpy(new->pw_passwd, bufptr, len);
212 new->pw_passwd[len] = '\0';
218 /* Finally, let's look at radutmp and make a record of everyone
219 * that's logged in currently */
222 /* log how many entries we stored from the passwd file */
223 log(L_INFO, "HASH: Stored %d entries from %s", numread, PASSWDFILE);
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
236 int buildGrpList(void) {
238 int len, len2, index, numread=0;
240 struct mygroup *new, *cur, *next;
245 /* Free up former grp list (we can use this as a rebuild function too */
249 /* Free name, name, member list */
250 for(member = cur->gr_mem; *member; member++) {
255 free(cur->gr_passwd);
261 /* Make sure to begin at beginning */
264 /* Get next entry from the group file */
265 while((grp = getgrent()) != NULL) {
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!");
272 memset((struct mygroup*)new, 0, sizeof(struct mygroup));
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!");
280 strncpy(new->gr_name, grp->gr_name, len);
281 new->gr_name[len] = '\0';
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!");
288 strncpy(new->gr_passwd, grp->gr_passwd, len);
289 new->gr_passwd[len] = '\0';
291 new->gr_gid = grp->gr_gid;
293 /* Allocate space for user list, as much as I hate doing groups
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!");
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!");
310 strncpy(new->gr_mem[index], *member, len2);
311 new->gr_mem[index][len2] = '\0';
313 /* Make sure last entry in user list is 0 so we can loop thru it */
314 new->gr_mem[len] = 0;
316 /* Insert at beginning of list */
326 log(L_INFO, "HASH: Stored %d entries from /etc/group", numread);
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
335 void chgLoggedin(char *user, int diff) {
336 struct mypasswd *cur;
338 cur = findHashUser(user);
340 cur->loggedin += diff;
341 /* Can't have less than 0 logins */
342 if(cur->loggedin<0) {
345 DEBUG2(" HASH: Changed user %s to %d logins", user, cur->loggedin);
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
353 struct mypasswd *findHashUser(const char *user) {
355 struct mypasswd *cur;
358 /* first hash the username and get the index into the hashtable */
359 index = hashUserName(user);
361 cur = hashtable[index];
363 while((cur != NULL) && (strcmp(cur->pw_name, user))) {
368 DEBUG2(" HASH: user %s found in hashtable bucket %d", user, index);
372 return (struct mypasswd *)0;
376 /* Stores the username sent into the hashtable */
377 int storeHashUser(struct mypasswd *new, int index) {
379 /* store new record at beginning of list */
380 new->next = hashtable[index];
381 hashtable[index] = new;
386 /* Hashes the username sent to it and returns index into hashtable */
387 int hashUserName(const char *s) {
388 unsigned long hash = 0;
391 hash = hash * 7907 + (unsigned char)*s++;
394 return (hash % HASHTABLESIZE);
397 /* Reads radutmp file, and increments the loggedin variable
398 * for every login a user has...assuming we can find the user
401 int hashradutmp(void) {
405 struct mypasswd *cur;
407 if ((fd = open(RADUTMP, O_CREAT|O_RDONLY, 0644)) < 0)
410 while(read(fd, &u, sizeof(u)) == sizeof(u)) {
411 if ((u.login) && (u.type == P_LOGIN)) {
412 cur = findHashUser(u.login);
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)
430 int H_unix_pass(char *name, char *passwd) {
431 struct mypasswd *pwd;
432 char *encrypted_pass;
436 * Get encrypted password from password file
438 if ((pwd = findHashUser(name)) == NULL) {
439 /* Default to old way if user isn't hashed */
442 encrypted_pass = pwd->pw_passwd;
445 * We might have a passwordless account.
447 if (encrypted_pass[0] == 0) return 0;
450 * Check encrypted password.
452 encpw = (char *)crypt(passwd, encrypted_pass);
453 if (strcmp(encpw, encrypted_pass))
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
464 int H_groupcmp(VALUE_PAIR *check, char *username) {
465 struct mypasswd *pwd;
469 /* get the user from the hash */
470 if (!(pwd = findHashUser(username)))
473 /* let's find this group */
476 while((cur) && (strcmp(cur->gr_name, check->strvalue))) {
479 /* found the group, now compare it */
481 /* Default to old function if we can't find it */
484 if(pwd->pw_gid == cur->gr_gid) {
485 DEBUG2(" HASH: matched user %s in group %s", username, cur->gr_name);
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);