2 * rlm_fastusers.c authorization: Find a user in the hashed "users" file.
3 * accounting: Do nothing. Auth module only.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 * Copyright 2000 The FreeRADIUS server project
22 * Copyright 2000 Jeff Carneal <jeff@apex.net>
26 #include "libradius.h"
28 #include <sys/socket.h>
44 struct fastuser_instance {
50 PAIR_LIST **hashtable;
60 /* Function declarations */
61 static int fallthrough(VALUE_PAIR *vp);
62 static int fastuser_buildhash(struct fastuser_instance *inst);
63 static int fastuser_getfile(struct fastuser_instance *inst, const char *filename,
64 PAIR_LIST **default_list, PAIR_LIST **pair_list,
66 static int fastuser_hash(const char *s, long hashtablesize);
67 static int fastuser_store(PAIR_LIST **hashtable, PAIR_LIST *entry, int idx);
68 static PAIR_LIST *fastuser_find(REQUEST *request, PAIR_LIST *user,
69 const char *username);
70 static void fastuser_tablestats(PAIR_LIST **hashtable, long size);
71 static int fastuser_passcheck(REQUEST *request, PAIR_LIST *user, const char *name);
73 static CONF_PARSER module_config[] = {
74 { "usersfile", PW_TYPE_STRING_PTR,
75 offsetof(struct fastuser_instance,usersfile), NULL, "${raddbdir}/users_fast" },
76 { "acctusersfile", PW_TYPE_STRING_PTR,
77 offsetof(struct fastuser_instance,acctusersfile), NULL, "${raddbdir}/acct_users" },
78 { "hashsize", PW_TYPE_INTEGER,
79 offsetof(struct fastuser_instance,hashsize), NULL, "100000" },
80 { "stats", PW_TYPE_BOOLEAN,
81 offsetof(struct fastuser_instance,stats), NULL, "no" },
82 { "compat", PW_TYPE_STRING_PTR,
83 offsetof(struct fastuser_instance,compat_mode), NULL, "cistron" },
84 { "hash_reload", PW_TYPE_INTEGER,
85 offsetof(struct fastuser_instance,hash_reload), NULL, "600" },
86 { NULL, -1, 0, NULL, NULL }
90 * See if a VALUE_PAIR list contains Fall-Through = Yes
92 static int fallthrough(VALUE_PAIR *vp)
95 tmp = pairfind(vp, PW_FALL_THROUGH);
96 return tmp ? tmp->lvalue : 0;
99 static int fastuser_buildhash(struct fastuser_instance *inst) {
101 int rcode, hashindex;
102 PAIR_LIST **newhash=NULL, **oldhash=NULL;
103 PAIR_LIST *newdefaults=NULL, *newacctusers, *cur=NULL;
104 PAIR_LIST *olddefaults=NULL, *oldacctusers=NULL;
107 * Allocate space for hash table here
109 memsize = sizeof(PAIR_LIST *) * inst->hashsize;
111 newhash = (PAIR_LIST **) rad_malloc(memsize);
113 memset((PAIR_LIST *)newhash, 0, memsize);
115 /* Read acct_users */
116 rcode = fastuser_getfile(inst, inst->acctusersfile, NULL, &newacctusers, 1);
118 radlog(L_ERR|L_CONS, "rlm_fastusers: Errors reading %s", inst->usersfile);
123 rcode = fastuser_getfile(inst, inst->usersfile, &newdefaults, newhash, 0);
125 radlog(L_ERR|L_CONS, "rlm_fastusers: Errors reading %s", inst->usersfile);
130 * We need to do this now so that users auths
131 * aren't blocked while we free the old table
134 oldacctusers = inst->acctusers;
135 inst->acctusers = newacctusers;
136 oldhash = inst->hashtable;
137 inst->hashtable = newhash;
138 olddefaults = inst->defaults;
139 inst->defaults = newdefaults;
142 * When we get here, we assume the hash built properly.
143 * So we begin to tear down the old one
146 for(hashindex=0; hashindex<inst->hashsize; hashindex++) {
147 if(oldhash[hashindex]) {
148 cur = oldhash[hashindex];
153 pairlist_free(&olddefaults);
154 pairlist_free(&oldacctusers);
158 fastuser_tablestats(inst->hashtable, inst->hashsize);
163 static int fastuser_getfile(struct fastuser_instance *inst, const char *filename,
164 PAIR_LIST **default_list, PAIR_LIST **pair_list,
167 PAIR_LIST *users = NULL;
168 PAIR_LIST *entry=NULL, *next=NULL, *cur=NULL, *defaults=NULL, *lastdefault=NULL;
169 int compat_mode = FALSE;
172 long numdefaults = 0, numusers=0;
174 radlog(L_INFO, " fastusers: Reading %s", filename);
175 rcode = pairlist_read(filename, &users, 1);
180 if (strcmp(inst->compat_mode, "cistron") == 0) {
187 DEBUG("[%s]:%d Cistron compatibility checks for entry %s ...",
188 filename, entry->lineno, entry->name);
192 * Look for improper use of '=' in the
193 * check items. They should be using
194 * '==' for on-the-wire RADIUS attributes,
195 * and probably ':=' for server
196 * configuration items.
198 for (vp = entry->check; vp != NULL; vp = vp->next) {
200 * Ignore attributes which are set
203 if (vp->operator != T_OP_EQ)
208 * If it's a vendor attribute,
209 * or it's a wire protocol,
210 * ensure it has '=='.
212 if (((vp->attribute & ~0xffff) != 0) ||
213 (vp->attribute < 0x100)) {
215 DEBUG("[%s]:%d WARNING! Changing '%s =' to '%s =='\n\tfor comparing RADIUS attribute in check item list for user %s",
216 filename, entry->lineno, vp->name, vp->name, entry->name);
218 DEBUG("\tChanging '%s =' to '%s =='",
221 vp->operator = T_OP_CMP_EQ;
226 * Cistron Compatibility mode.
228 * Re-write selected attributes
229 * to be '+=', instead of '='.
231 * All others get set to '=='
235 * Non-wire attributes become +=
237 * On the write attributes
240 if ((vp->attribute >= 0x100) &&
241 (vp->attribute <= 0xffff) &&
242 (vp->attribute != PW_HINT) &&
243 (vp->attribute != PW_HUNTGROUP_NAME)) {
244 DEBUG("\tChanging '%s =' to '%s +='",
246 vp->operator = T_OP_ADD;
248 DEBUG("\tChanging '%s =' to '%s =='",
250 vp->operator = T_OP_CMP_EQ;
254 } /* end of loop over check items */
258 * Look for server configuration items
261 * It's a common enough mistake, that it's
264 for (vp = entry->reply; vp != NULL; vp = vp->next) {
266 * If it's NOT a vendor attribute,
267 * and it's NOT a wire protocol
268 * and we ignore Fall-Through,
269 * then bitch about it, giving a
270 * good warning message.
272 if (!(vp->attribute & ~0xffff) &&
273 (vp->attribute > 0xff) &&
274 (vp->attribute > 1000)) {
275 log_debug("[%s]:%d WARNING! Check item \"%s\"\n"
276 "\tfound in reply item list for user \"%s\".\n"
277 "\tThis attribute MUST go on the first line"
278 " with the other check items",
279 filename, entry->lineno, vp->name,
285 * Ok, we've done all the same BS as
286 * rlm_users, so here we tear apart the
287 * linked list, and store our users in
288 * the hashtable we've built instead
291 /* Save what was next */
295 /* Save the DEFAULT entry specially */
296 if(strcmp(entry->name, "DEFAULT")==0) {
298 /* Save this as the last default we've seen */
302 /* put it at the end of the list */
304 for(cur=defaults; cur->next; cur=cur->next);
309 defaults->next = NULL;
315 /* Hash the username */
316 hashindex = fastuser_hash(entry->name, inst->hashsize);
318 /* Store the last default before this entry */
319 entry->lastdefault = lastdefault;
321 /* Store user in the hash */
322 fastuser_store(pair_list, entry, hashindex);
325 /* Restore entry to next pair_list */
328 } /* while(entry) loop */
330 if(!isacctfile && (default_list)) {
331 *default_list = defaults;
332 radlog(L_INFO, "rlm_fastusers: Loaded %ld users and %ld defaults",
333 numusers, numdefaults);
341 /* Hashes the username sent to it and returns index into hashtable */
342 int fastuser_hash(const char *s, long hashtablesize) {
343 unsigned long hash = 0;
346 hash = hash * 7907 + (unsigned char)*s++;
349 return (hash % hashtablesize);
352 /* Stores the username sent into the hashtable */
353 static int fastuser_store(PAIR_LIST **hashtable, PAIR_LIST *new, int idx) {
356 cur = hashtable[idx];
357 /* store new record at end of list */
359 while (cur->next != NULL)
364 new->next = hashtable[idx];
365 hashtable[idx] = new;
371 * Looks up user in hashtable. If user can't be found, returns 0.
372 * Otherwise returns a pointer to the structure for the user
374 static PAIR_LIST *fastuser_find(REQUEST *request, PAIR_LIST *user,
375 const char *username)
381 * Now we have to make sure it's the right user by
382 * comparing the check pairs
384 while((cur) && (!userfound)) {
385 if((strcmp(cur->name, username)==0) &&
386 paircmp(request->packet->vps, cur->check, &request->reply->vps) == 0) {
388 * Usercollide means we have to compare check pairs
391 if(mainconfig.do_usercollide) {
392 if((userfound = fastuser_passcheck(request, cur, username))==0) {
398 DEBUG2(" fastusers: Matched %s at %d", cur->name, cur->lineno);
409 return (PAIR_LIST *)0;
413 * Generate and log statistics about our hash table
415 static void fastuser_tablestats(PAIR_LIST **hashtable, long size) {
421 memset(countarray, 0, sizeof(countarray));
423 for(i=0; i<size; i++) {
425 for(cur=hashtable[i]; cur; cur=cur->next) {
437 radlog(L_INFO, "rlm_fastusers: Hash buckets with %d users: %d",
442 radlog(L_INFO, "rlm_fastusers: Hash buckets with more than 256: %d",
447 static int fastuser_passcheck(REQUEST *request, PAIR_LIST *user, const char *name)
450 VALUE_PAIR *check_save;
453 * We check for REJECT specially here or a REJECT
454 * user will never match
456 check_save = pairfind(user->check, PW_AUTHTYPE);
457 if(check_save->lvalue == PW_AUTHTYPE_REJECT) {
458 DEBUG2(" fastusers(uc): User '%s' line %d is Auth-Type Reject, but usercollide match",
459 user->name, user->lineno);
463 /* Save the orginal config items */
464 check_save = request->config_items;
465 request->config_items = NULL;
467 DEBUG2(" fastusers(uc): Checking %s at %d", user->name, user->lineno);
469 /* Copy this users check pairs to the request */
470 request->config_items = paircopy(user->check);
472 /* Check the req to see if we matched */
473 if(rad_check_password(request)==0) {
474 DEBUG2(" fastusers(uc): Matched %s at %d", user->name, user->lineno);
478 /* Restore check items */
479 pairfree(&request->config_items);
480 request->config_items = check_save;
486 * (Re-)read the "users" file into memory.
488 static int fastuser_instantiate(CONF_SECTION *conf, void **instance)
490 struct fastuser_instance *inst=0;
492 inst = rad_malloc(sizeof *inst);
494 memset(inst, 0, sizeof(*inst));
496 if (cf_section_parse(conf, inst, module_config) < 0) {
501 inst->next_reload = time(NULL) + inst->hash_reload;
502 inst->hashtable = NULL;
503 if(fastuser_buildhash(inst) < 0) {
504 radlog(L_ERR, "rlm_fastusers: error building user hash. aborting");
509 * Need code here to read acct_users file
517 * Find the named user in the database. Create the
518 * set of attribute-value pairs to check and reply with
519 * for this user from the database. The main code only
520 * needs to check the password, the rest is done here.
522 static int fastuser_authorize(void *instance, REQUEST *request)
525 VALUE_PAIR *namepair;
526 VALUE_PAIR *check_tmp;
527 VALUE_PAIR *reply_tmp;
529 PAIR_LIST *curdefault;
534 struct fastuser_instance *inst = instance;
537 * Do we need to reload the cache?
538 * Really we should spawn a thread to do this
540 if((inst->hash_reload) && (request->timestamp > inst->next_reload)) {
541 inst->next_reload = request->timestamp + inst->hash_reload;
542 radlog(L_INFO, "rlm_fastusers: Reloading fastusers hash");
543 if(fastuser_buildhash(inst) < 0) {
544 radlog(L_ERR, "rlm_fastusers: error building user hash. aborting");
550 * Grab the canonical user name.
552 namepair = request->username;
553 name = namepair ? (char *) namepair->strvalue : "NONE";
556 * Find the entry for the user.
558 hashidx = fastuser_hash(name, inst->hashsize);
559 user = inst->hashtable[hashidx];
560 if((user=fastuser_find(request, user, name))!=NULL) {
565 * If there's no lastdefault and we
566 * don't fallthrough, just copy the
567 * pairs for this user and return
569 if((user) && (userfound) && (user->lastdefault == NULL)) {
570 DEBUG2("rlm_fastusers: user found before DEFAULT");
572 check_tmp = paircopy(user->check);
573 pairmove(&request->config_items, &check_tmp);
574 pairfree(&check_tmp);
576 reply_tmp = paircopy(user->reply);
577 pairmove(&request->reply->vps, &reply_tmp);
578 pairfree(&reply_tmp);
580 if(!fallthrough(user->reply)) {
581 pairdelete(&request->reply->vps, PW_FALL_THROUGH);
582 return RLM_MODULE_UPDATED;
585 user=fastuser_find(request, user, name);
590 * When we get here, we've either found
591 * the user or not, but to preserve order
592 * we start at the top of the default
593 * list and work our way thru
594 * When we get to the user's 'lastdefault'
595 * we check to see if we should stop
598 DEBUG2("rlm_fastusers: checking defaults");
600 curdefault = inst->defaults;
602 if(paircmp(request->packet->vps, curdefault->check,
603 &request->reply->vps) == 0) {
604 DEBUG2(" fastusers: Matched %s at %d",
605 curdefault->name, curdefault->lineno);
608 check_tmp = paircopy(curdefault->check);
609 pairmove(&request->config_items, &check_tmp);
610 pairfree(&check_tmp);
612 reply_tmp = paircopy(curdefault->reply);
613 pairmove(&request->reply->vps, &reply_tmp);
614 pairfree(&reply_tmp);
619 * There's no fallthru on this default which
620 * is *before* we find the user in the file,
621 * so we know it's safe to quit here
623 if (!fallthrough(curdefault->reply))
627 * If we found the user, we want to stop
628 * processing once we get to 'lastdefault'
629 * This way we can process this user's entry
630 * in the order it was found in the file
632 while((userfound && (user) && (curdefault == user->lastdefault))) {
633 DEBUG2(" fastusers: found lastdefault at line %d",
636 check_tmp = paircopy(user->check);
637 pairmove(&request->config_items, &check_tmp);
638 pairfree(&check_tmp);
640 reply_tmp = paircopy(user->reply);
641 pairmove(&request->reply->vps, &reply_tmp);
642 pairfree(&reply_tmp);
644 if(!fallthrough(user->reply)) {
645 pairdelete(&request->reply->vps, PW_FALL_THROUGH);
646 return RLM_MODULE_UPDATED;
650 * Find next occurence of THIS user in
654 user=fastuser_find(request, user, name);
657 curdefault = curdefault->next;
660 if(userfound || defaultfound) {
661 pairdelete(&request->reply->vps, PW_FALL_THROUGH);
662 return RLM_MODULE_UPDATED;
664 DEBUG2("rlm_fastusers: user not found");
665 return RLM_MODULE_NOTFOUND;
670 * Authentication - unused.
672 static int fastuser_authenticate(void *instance, REQUEST *request)
676 return RLM_MODULE_OK;
680 * Pre-Accounting - read the acct_users file for check_items and
681 * config_items. Reply items are Not Recommended(TM) in acct_users,
682 * except for Fallthrough, which should work
684 * This function is mostly a copy of file_authorize
686 static int fastuser_preacct(void *instance, REQUEST *request)
688 VALUE_PAIR *namepair;
690 VALUE_PAIR *request_pairs;
691 VALUE_PAIR **config_pairs;
692 VALUE_PAIR *reply_pairs = NULL;
693 VALUE_PAIR *check_tmp;
694 VALUE_PAIR *reply_tmp;
695 PAIR_LIST *pl = NULL;
697 struct fastuser_instance *inst = instance;
699 namepair = request->username;
700 name = namepair ? (char *) namepair->strvalue : "NONE";
701 request_pairs = request->packet->vps;
702 config_pairs = &request->config_items;
705 * Find the entry for the user.
707 for (pl = inst->acctusers; pl; pl = pl->next) {
709 if (strcmp(name, pl->name) && strcmp(pl->name, "DEFAULT"))
712 if (paircmp(request_pairs, pl->check, &reply_pairs) == 0) {
713 DEBUG2(" acct_users: Matched %s at %d",
714 pl->name, pl->lineno);
716 check_tmp = paircopy(pl->check);
717 reply_tmp = paircopy(pl->reply);
718 pairmove(&reply_pairs, &reply_tmp);
719 pairmove(config_pairs, &check_tmp);
720 pairfree(&reply_tmp);
721 pairfree(&check_tmp); /* should be NULL */
725 if (!fallthrough(pl->reply))
731 * See if we succeeded.
734 return RLM_MODULE_NOOP; /* on to the next module */
737 * FIXME: log a warning if there are any reply items other than
740 pairfree(&reply_pairs); /* Don't need these */
742 return RLM_MODULE_OK;
748 static int fastuser_detach(void *instance)
750 struct fastuser_instance *inst = instance;
754 /* Free hash table */
755 for(hashindex=0; hashindex<inst->hashsize; hashindex++) {
756 if(inst->hashtable[hashindex]) {
757 cur = inst->hashtable[hashindex];
762 free(inst->compat_mode);
763 free(inst->hashtable);
764 pairlist_free(&inst->defaults);
765 pairlist_free(&inst->acctusers);
766 free(inst->usersfile);
767 free(inst->acctusersfile);
773 * This function is unused
775 static int fastuser_accounting(void *instance, REQUEST *request)
777 return RLM_MODULE_FAIL;
780 /* globally exported name */
781 module_t rlm_fastusers = {
783 0, /* type: reserved */
784 NULL, /* initialization */
785 fastuser_instantiate, /* instantiation */
786 fastuser_authorize, /* authorization */
787 fastuser_authenticate, /* authentication */
788 fastuser_preacct, /* preaccounting */
789 fastuser_accounting, /* accounting */
790 NULL, /* checksimul */
791 fastuser_detach, /* detach */