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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 * Copyright 2000,2006 The FreeRADIUS server project
22 * Copyright 2000 Jeff Carneal <jeff@apex.net>
25 #include <freeradius-devel/ident.h>
28 #include <freeradius-devel/radiusd.h>
29 #include <freeradius-devel/modules.h>
37 struct fastuser_instance {
45 PAIR_LIST **hashtable;
54 time_t lastacctusersload;
57 /* Function declarations */
58 static int fallthrough(VALUE_PAIR *vp);
59 static int fastuser_buildhash(struct fastuser_instance *inst);
60 static int fastuser_getfile(struct fastuser_instance *inst, const char *filename,
61 PAIR_LIST **default_list, PAIR_LIST **pair_list,
63 static int fastuser_hash(const char *s, int hashtablesize);
64 static int fastuser_store(PAIR_LIST **hashtable, PAIR_LIST *entry, int idx);
65 static PAIR_LIST *fastuser_find(REQUEST *request, PAIR_LIST *user,
66 const char *username);
67 static void fastuser_tablestats(PAIR_LIST **hashtable, int size);
69 static const CONF_PARSER module_config[] = {
70 { "usersfile", PW_TYPE_FILENAME,
71 offsetof(struct fastuser_instance,usersfile), NULL, "${raddbdir}/users_fast" },
72 { "acctusersfile", PW_TYPE_FILENAME,
73 offsetof(struct fastuser_instance,acctusersfile), NULL, "${raddbdir}/acct_users" },
74 { "hashsize", PW_TYPE_INTEGER,
75 offsetof(struct fastuser_instance,hashsize), NULL, "100000" },
76 { "stats", PW_TYPE_BOOLEAN,
77 offsetof(struct fastuser_instance,stats), NULL, "no" },
78 { "compat", PW_TYPE_STRING_PTR,
79 offsetof(struct fastuser_instance,compat_mode), NULL, "cistron" },
80 { "hash_reload", PW_TYPE_INTEGER,
81 offsetof(struct fastuser_instance,hash_reload), NULL, "600" },
82 { "key", PW_TYPE_STRING_PTR,
83 offsetof(struct fastuser_instance,key), NULL, NULL },
84 { NULL, -1, 0, NULL, NULL }
88 * See if a VALUE_PAIR list contains Fall-Through = Yes
90 static int fallthrough(VALUE_PAIR *vp)
93 tmp = pairfind(vp, PW_FALL_THROUGH, 0);
94 return tmp ? tmp->vp_integer : 0;
98 * returncheck - Check for Auth-Type = Reject and return appropriate
99 * module return code if it is found.
101 static int rad_check_return(VALUE_PAIR *list)
103 VALUE_PAIR *authtype;
106 * We check for Auth-Type = Reject here
109 authtype = pairfind(list, PW_AUTHTYPE, 0);
110 if((authtype) && authtype->vp_integer == PW_AUTHTYPE_REJECT) {
111 DEBUG2("rad_check_return: Auth-Type is Reject");
112 return RLM_MODULE_REJECT;
115 return RLM_MODULE_UPDATED;
118 static int fastuser_buildhash(struct fastuser_instance *inst) {
120 int rcode, hashindex;
121 PAIR_LIST **newhash=NULL, **oldhash=NULL;
122 PAIR_LIST *newdefaults=NULL, *newacctusers, *cur=NULL;
123 PAIR_LIST *olddefaults=NULL, *oldacctusers=NULL;
126 int reloadacctusers = 1;
129 * Allocate space for hash table here
131 memsize = sizeof(PAIR_LIST *) * inst->hashsize;
133 newhash = (PAIR_LIST **) rad_malloc(memsize);
135 memset((PAIR_LIST *)newhash, 0, memsize);
137 /* Check acct_users last modification time */
138 if ((stat(inst->acctusersfile, &statbuf) != -1)
139 && (statbuf.st_mtime <= inst->lastacctusersload)) {
140 DEBUG2("rlm_fastusers: File %s was unchanged. Not reloading.",
141 inst->acctusersfile);
146 /* Read acct_users */
147 rcode = fastuser_getfile(inst, inst->acctusersfile, NULL, &newacctusers, 1);
151 radlog(L_ERR|L_CONS, "rlm_fastusers: Errors reading %s", inst->usersfile);
155 /* Check users last modification time */
156 if ((stat(inst->usersfile, &statbuf) != -1)
157 && (statbuf.st_mtime <= inst->lastusersload)) {
158 DEBUG2("rlm_fastusers: File %s was unchanged. Not reloading.",
162 /* This was allocated earlier but will remain unused */
168 rcode = fastuser_getfile(inst, inst->usersfile, &newdefaults, newhash, 0);
172 radlog(L_ERR|L_CONS, "rlm_fastusers: Errors reading %s", inst->usersfile);
178 * We need to do this now so that users auths
179 * aren't blocked while we free the old table
182 inst->lastusersload = time(NULL);
183 oldhash = inst->hashtable;
184 inst->hashtable = newhash;
185 olddefaults = inst->defaults;
186 inst->defaults = newdefaults;
189 * When we get here, we assume the hash built properly.
190 * So we begin to tear down the old one
193 for(hashindex=0; hashindex<inst->hashsize; hashindex++) {
194 if(oldhash[hashindex]) {
195 cur = oldhash[hashindex];
201 pairlist_free(&olddefaults);
203 if (reloadacctusers) {
204 inst->lastacctusersload = time(NULL);
205 oldacctusers = inst->acctusers;
206 inst->acctusers = newacctusers;
207 pairlist_free(&oldacctusers);
211 fastuser_tablestats(inst->hashtable, inst->hashsize);
216 static int fastuser_getfile(struct fastuser_instance *inst, const char *filename,
217 PAIR_LIST **default_list, PAIR_LIST **pair_list,
220 PAIR_LIST *users = NULL;
221 PAIR_LIST *entry=NULL, *next=NULL, *cur=NULL, *defaults=NULL, *lastdefault=NULL;
222 int compat_mode = FALSE;
225 int numdefaults = 0, numusers=0;
227 radlog(L_INFO, " fastusers: Reading %s", filename);
228 rcode = pairlist_read(filename, &users, 1);
233 if (strcmp(inst->compat_mode, "cistron") == 0) {
240 DEBUG("[%s]:%d Cistron compatibility checks for entry %s ...",
241 filename, entry->lineno, entry->name);
245 * Look for improper use of '=' in the
246 * check items. They should be using
247 * '==' for on-the-wire RADIUS attributes,
248 * and probably ':=' for server
249 * configuration items.
251 for (vp = entry->check; vp != NULL; vp = vp->next) {
253 * Ignore attributes which are set
256 if (vp->operator != T_OP_EQ)
261 * If it's a vendor attribute,
262 * or it's a wire protocol,
263 * ensure it has '=='.
265 if ((vp->vendor != 0) ||
266 (vp->attribute < 0x100)) {
268 DEBUG("[%s]:%d WARNING! Changing '%s =' to '%s =='\n\tfor comparing RADIUS attribute in check item list for user %s",
269 filename, entry->lineno, vp->name, vp->name, entry->name);
271 DEBUG("\tChanging '%s =' to '%s =='",
274 vp->operator = T_OP_CMP_EQ;
279 * Cistron Compatibility mode.
281 * Re-write selected attributes
282 * to be '+=', instead of '='.
284 * All others get set to '=='
288 * Non-wire attributes become +=
290 * On the write attributes
293 if ((vp->attribute >= 0x100) &&
294 (vp->attribute <= 0xffff) &&
295 (vp->attribute != PW_HINT) &&
296 (vp->attribute != PW_HUNTGROUP_NAME)) {
297 DEBUG("\tChanging '%s =' to '%s +='",
299 vp->operator = T_OP_ADD;
301 DEBUG("\tChanging '%s =' to '%s =='",
303 vp->operator = T_OP_CMP_EQ;
307 } /* end of loop over check items */
311 * Look for server configuration items
314 * It's a common enough mistake, that it's
317 for (vp = entry->reply; vp != NULL; vp = vp->next) {
319 * If it's NOT a vendor attribute,
320 * and it's NOT a wire protocol
321 * and we ignore Fall-Through,
322 * then bitch about it, giving a
323 * good warning message.
325 if ((vp->vendor == 0) &&
326 (vp->attribute > 0xff) &&
327 (vp->attribute > 1000)) {
328 log_debug("[%s]:%d WARNING! Check item \"%s\"\n"
329 "\tfound in reply item list for user \"%s\".\n"
330 "\tThis attribute MUST go on the first line"
331 " with the other check items",
332 filename, entry->lineno, vp->name,
338 * Ok, we've done all the same BS as
339 * rlm_users, so here we tear apart the
340 * linked list, and store our users in
341 * the hashtable we've built instead
344 /* Save what was next */
348 /* Save the DEFAULT entry specially */
349 if(strcmp(entry->name, "DEFAULT")==0) {
351 /* Save this as the last default we've seen */
355 /* put it at the end of the list */
357 for(cur=defaults; cur->next; cur=cur->next);
362 defaults->next = NULL;
368 /* Hash the username */
369 hashindex = fastuser_hash(entry->name, inst->hashsize);
371 /* Store the last default before this entry */
372 entry->lastdefault = lastdefault;
374 /* Store user in the hash */
375 fastuser_store(pair_list, entry, hashindex);
378 /* Restore entry to next pair_list */
381 } /* while(entry) loop */
383 if(!isacctfile && (default_list)) {
384 *default_list = defaults;
385 radlog(L_INFO, "rlm_fastusers: Loaded %d users and %d defaults",
386 numusers, numdefaults);
394 /* Hashes the username sent to it and returns index into hashtable */
395 int fastuser_hash(const char *s, int hashtablesize) {
396 unsigned int hash = 0;
399 hash = hash * 7907 + (unsigned char)*s++;
402 return (hash % hashtablesize);
405 /* Stores the username sent into the hashtable */
406 static int fastuser_store(PAIR_LIST **hashtable, PAIR_LIST *new, int idx) {
409 cur = hashtable[idx];
410 /* store new record at end of list */
412 while (cur->next != NULL)
417 new->next = hashtable[idx];
418 hashtable[idx] = new;
424 * Looks up user in hashtable. If user can't be found, returns 0.
425 * Otherwise returns a pointer to the structure for the user
427 static PAIR_LIST *fastuser_find(REQUEST *request, PAIR_LIST *user,
428 const char *username)
434 * Now we have to make sure it's the right user by
435 * comparing the check pairs
437 while((cur) && (!userfound)) {
438 if((strcmp(cur->name, username)==0) &&
439 paircompare(request, request->packet->vps, cur->check, &request->reply->vps) == 0) {
441 DEBUG2(" fastusers: Matched %s at %d", cur->name, cur->lineno);
451 return (PAIR_LIST *)0;
455 * Generate and log statistics about our hash table
457 static void fastuser_tablestats(PAIR_LIST **hashtable, int size) {
463 memset(countarray, 0, sizeof(countarray));
465 for(i=0; i<size; i++) {
467 for(cur=hashtable[i]; cur; cur=cur->next) {
479 radlog(L_INFO, "rlm_fastusers: Hash buckets with %d users: %d",
484 radlog(L_INFO, "rlm_fastusers: Hash buckets with more than 256: %d",
490 * (Re-)read the "users" file into memory.
492 static int fastuser_instantiate(CONF_SECTION *conf, void **instance)
494 struct fastuser_instance *inst=0;
496 inst = rad_malloc(sizeof *inst);
499 memset(inst, 0, sizeof(*inst));
501 if (cf_section_parse(conf, inst, module_config) < 0) {
506 inst->next_reload = time(NULL) + inst->hash_reload;
507 inst->hashtable = NULL;
508 inst->lastusersload = 0;
509 inst->lastacctusersload = 0;
510 if(fastuser_buildhash(inst) < 0) {
511 radlog(L_ERR, "rlm_fastusers: error building user hash. aborting");
516 * Need code here to read acct_users file
524 * Find the named user in the database. Create the
525 * set of attribute-value pairs to check and reply with
526 * for this user from the database. The main code only
527 * needs to check the password, the rest is done here.
529 static int fastuser_authorize(void *instance, REQUEST *request)
532 VALUE_PAIR *namepair;
533 VALUE_PAIR *check_tmp;
534 VALUE_PAIR *reply_tmp;
536 PAIR_LIST *curdefault;
541 struct fastuser_instance *inst = instance;
545 * Do we need to reload the cache?
546 * Really we should spawn a thread to do this
548 if((inst->hash_reload) && (request->timestamp > inst->next_reload)) {
549 inst->next_reload = request->timestamp + inst->hash_reload;
550 radlog(L_INFO, "rlm_fastusers: Reloading fastusers hash");
551 if(fastuser_buildhash(inst) < 0) {
552 radlog(L_ERR, "rlm_fastusers: error building user hash. aborting");
553 return RLM_MODULE_FAIL;
558 * Grab the canonical user name.
561 namepair = request->username;
562 name = namepair ? (char *) namepair->vp_strvalue : "NONE";
566 len = radius_xlat(buffer, sizeof(buffer), inst->key,
567 request, NULL, NULL);
568 if (len) name = buffer;
573 * Find the entry for the user.
575 hashidx = fastuser_hash(name, inst->hashsize);
576 user = inst->hashtable[hashidx];
577 if((user=fastuser_find(request, user, name))!=NULL) {
582 * If there's no lastdefault and we
583 * don't fallthrough, just copy the
584 * pairs for this user and return
586 if((user) && (userfound) && (user->lastdefault == NULL)) {
587 DEBUG2("rlm_fastusers: user found before DEFAULT");
589 check_tmp = paircopy(user->check);
590 pairmove(&request->config_items, &check_tmp);
591 pairfree(&check_tmp);
593 reply_tmp = paircopy(user->reply);
594 pairmove(&request->reply->vps, &reply_tmp);
595 pairfree(&reply_tmp);
597 if(!fallthrough(user->reply)) {
598 pairdelete(&request->reply->vps, PW_FALL_THROUGH, 0, -1);
599 return(rad_check_return(user->check));
602 user=fastuser_find(request, user, name);
607 * When we get here, we've either found
608 * the user or not, but to preserve order
609 * we start at the top of the default
610 * list and work our way thru
611 * When we get to the user's 'lastdefault'
612 * we check to see if we should stop
615 DEBUG2("rlm_fastusers: checking defaults");
617 curdefault = inst->defaults;
619 if(paircompare(request, request->packet->vps, curdefault->check,
620 &request->reply->vps) == 0) {
621 DEBUG2(" fastusers: Matched %s at %d",
622 curdefault->name, curdefault->lineno);
625 check_tmp = paircopy(curdefault->check);
626 pairmove(&request->config_items, &check_tmp);
627 pairfree(&check_tmp);
629 reply_tmp = paircopy(curdefault->reply);
630 pairmove(&request->reply->vps, &reply_tmp);
631 pairfree(&reply_tmp);
634 * There's no fallthru on this default which
635 * is *before* we find the user in the file,
636 * so we know it's safe to quit here
638 if (!fallthrough(curdefault->reply))
644 * If we found the user, we want to stop
645 * processing once we get to 'lastdefault'
646 * This way we can process this user's entry
647 * in the order it was found in the file
649 while((userfound && (user) && (curdefault == user->lastdefault))) {
650 DEBUG2(" fastusers: found lastdefault at line %d",
653 check_tmp = paircopy(user->check);
654 pairmove(&request->config_items, &check_tmp);
655 pairfree(&check_tmp);
657 reply_tmp = paircopy(user->reply);
658 pairmove(&request->reply->vps, &reply_tmp);
659 pairfree(&reply_tmp);
661 if(!fallthrough(user->reply)) {
662 pairdelete(&request->reply->vps, PW_FALL_THROUGH, 0, -1);
663 return(rad_check_return(user->check));
667 * Find next occurence of THIS user in
671 user=fastuser_find(request, user, name);
674 curdefault = curdefault->next;
677 if(userfound || defaultfound) {
678 pairdelete(&request->reply->vps, PW_FALL_THROUGH, 0, -1);
679 return(rad_check_return(request->config_items));
681 DEBUG2("rlm_fastusers: user not found");
682 return RLM_MODULE_NOTFOUND;
687 * Authentication - unused.
689 static int fastuser_authenticate(void *instance, REQUEST *request)
693 return RLM_MODULE_OK;
697 * Pre-Accounting - read the acct_users file for check_items and
698 * config_items. Reply items are Not Recommended(TM) in acct_users,
699 * except for Fallthrough, which should work
701 * This function is mostly a copy of file_authorize
703 static int fastuser_preacct(void *instance, REQUEST *request)
705 VALUE_PAIR *namepair;
707 VALUE_PAIR *request_pairs;
708 VALUE_PAIR **config_pairs;
709 VALUE_PAIR *reply_pairs = NULL;
710 VALUE_PAIR *check_tmp;
711 VALUE_PAIR *reply_tmp;
712 PAIR_LIST *pl = NULL;
714 struct fastuser_instance *inst = instance;
718 namepair = request->username;
719 name = namepair ? (char *) namepair->vp_strvalue : "NONE";
723 len = radius_xlat(buffer, sizeof(buffer), inst->key,
724 request, NULL, NULL);
725 if (len) name = buffer;
728 request_pairs = request->packet->vps;
729 config_pairs = &request->config_items;
732 * Find the entry for the user.
734 for (pl = inst->acctusers; pl; pl = pl->next) {
736 if (strcmp(name, pl->name) && strcmp(pl->name, "DEFAULT"))
739 if (paircompare(request, request_pairs, pl->check, &reply_pairs) == 0) {
740 DEBUG2(" acct_users: Matched %s at %d",
741 pl->name, pl->lineno);
743 check_tmp = paircopy(pl->check);
744 reply_tmp = paircopy(pl->reply);
745 pairmove(&reply_pairs, &reply_tmp);
746 pairmove(config_pairs, &check_tmp);
747 pairfree(&reply_tmp);
748 pairfree(&check_tmp); /* should be NULL */
752 if (!fallthrough(pl->reply))
758 * See if we succeeded.
761 return RLM_MODULE_NOOP; /* on to the next module */
764 * FIXME: log a warning if there are any reply items other than
767 pairfree(&reply_pairs); /* Don't need these */
769 return RLM_MODULE_OK;
775 static int fastuser_detach(void *instance)
777 struct fastuser_instance *inst = instance;
781 /* Free hash table */
782 for(hashindex=0; hashindex<inst->hashsize; hashindex++) {
783 if(inst->hashtable[hashindex]) {
784 cur = inst->hashtable[hashindex];
789 free(inst->hashtable);
790 pairlist_free(&inst->defaults);
791 pairlist_free(&inst->acctusers);
796 * This function is unused
798 static int fastuser_accounting(void *instance UNUSED, REQUEST *request UNUSED)
801 * FIXME: should re rather return RLM_MODULE_NOOP here?
803 return RLM_MODULE_FAIL;
806 /* globally exported name */
807 module_t rlm_fastusers = {
810 0, /* type: reserved */
811 fastuser_instantiate, /* instantiation */
812 fastuser_detach, /* detach */
814 fastuser_authenticate, /* authentication */
815 fastuser_authorize, /* authorization */
816 fastuser_preacct, /* preaccounting */
817 fastuser_accounting, /* accounting */
818 NULL, /* checksimul */
819 NULL, /* pre-proxy */
820 NULL, /* post-proxy */