2 * rlm_fastusers.c authorization: Find a user in the hashed "users" file.
3 * accounting: Do nothing. Auth module only.
10 /***********************************************************************
11 * Copyright (C) 2000 The FreeRADIUS server project.
13 * This program is is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License, version 2 if the
15 * License as published by the Free Software Foundation.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25 ***********************************************************************/
27 #include <sys/socket.h>
47 struct fastuser_instance {
53 PAIR_LIST **hashtable;
63 /* Function declarations */
64 static int fallthrough(VALUE_PAIR *vp);
65 static int fastuser_buildhash(struct fastuser_instance *inst);
66 static int fastuser_getfile(struct fastuser_instance *inst, const char *filename,
67 PAIR_LIST **default_list, PAIR_LIST **pair_list,
69 static int fastuser_hash(const char *s, long hashtablesize);
70 static int fastuser_store(PAIR_LIST **hashtable, PAIR_LIST *entry, int idx);
71 static PAIR_LIST *fastuser_find(REQUEST *request, PAIR_LIST *user,
72 const char *username);
73 static void fastuser_tablestats(PAIR_LIST **hashtable, long size);
74 static int fastuser_passcheck(REQUEST *request, PAIR_LIST *user, const char *name);
77 * A temporary holding area for config values to be extracted
78 * into, before they are copied into the instance data
80 static struct fastuser_instance config;
82 static CONF_PARSER module_config[] = {
83 { "usersfile", PW_TYPE_STRING_PTR, &config.usersfile, "${raddbdir}/users_fast" },
84 { "acctusersfile", PW_TYPE_STRING_PTR, &config.acctusersfile, "${raddbdir}/acct_users" },
85 { "hashsize", PW_TYPE_INTEGER, &config.hashsize, "100000" },
86 { "stats", PW_TYPE_BOOLEAN, &config.stats, "no" },
87 { "compat", PW_TYPE_STRING_PTR, &config.compat_mode, "cistron" },
88 { "hash_reload", PW_TYPE_INTEGER, &config.hash_reload, "600" },
89 { NULL, -1, NULL, NULL }
93 * See if a VALUE_PAIR list contains Fall-Through = Yes
95 static int fallthrough(VALUE_PAIR *vp)
98 tmp = pairfind(vp, PW_FALL_THROUGH);
99 return tmp ? tmp->lvalue : 0;
102 static int fastuser_buildhash(struct fastuser_instance *inst) {
104 int rcode, hashindex;
105 PAIR_LIST **newhash=NULL, **oldhash=NULL;
106 PAIR_LIST *newdefaults=NULL, *newacctusers, *cur=NULL;
107 PAIR_LIST *olddefaults=NULL, *oldacctusers=NULL;
110 * Allocate space for hash table here
112 memsize = sizeof(PAIR_LIST *) * inst->hashsize;
113 if( (newhash = (PAIR_LIST **)malloc(memsize)) == NULL) {
114 radlog(L_ERR, "rlm_fastusers: Can't build hashtable, out of memory!");
117 memset((PAIR_LIST *)newhash, 0, memsize);
119 /* Read acct_users */
120 rcode = fastuser_getfile(inst, inst->acctusersfile, NULL, &newacctusers, 1);
122 radlog(L_ERR|L_CONS, "rlm_fastusers: Errors reading %s", inst->usersfile);
127 rcode = fastuser_getfile(inst, inst->usersfile, &newdefaults, newhash, 0);
129 radlog(L_ERR|L_CONS, "rlm_fastusers: Errors reading %s", inst->usersfile);
134 * We need to do this now so that users auths
135 * aren't blocked while we free the old table
138 oldacctusers = inst->acctusers;
139 inst->acctusers = newacctusers;
140 oldhash = inst->hashtable;
141 inst->hashtable = newhash;
142 olddefaults = inst->defaults;
143 inst->defaults = newdefaults;
146 * When we get here, we assume the hash built properly.
147 * So we begin to tear down the old one
150 for(hashindex=0; hashindex<inst->hashsize; hashindex++) {
151 if(oldhash[hashindex]) {
152 cur = oldhash[hashindex];
157 pairlist_free(&olddefaults);
158 pairlist_free(&oldacctusers);
162 fastuser_tablestats(inst->hashtable, inst->hashsize);
167 static int fastuser_getfile(struct fastuser_instance *inst, const char *filename,
168 PAIR_LIST **default_list, PAIR_LIST **pair_list,
171 PAIR_LIST *users = NULL;
172 PAIR_LIST *entry=NULL, *next=NULL, *cur=NULL, *defaults=NULL, *lastdefault=NULL;
173 int compat_mode = FALSE;
176 long numdefaults = 0, numusers=0;
178 radlog(L_INFO, " fastusers: Reading %s", filename);
179 rcode = pairlist_read(filename, &users, 1);
184 if (strcmp(inst->compat_mode, "cistron") == 0) {
191 DEBUG("[%s]:%d Cistron compatibility checks for entry %s ...",
192 filename, entry->lineno, entry->name);
196 * Look for improper use of '=' in the
197 * check items. They should be using
198 * '==' for on-the-wire RADIUS attributes,
199 * and probably ':=' for server
200 * configuration items.
202 for (vp = entry->check; vp != NULL; vp = vp->next) {
204 * Ignore attributes which are set
207 if (vp->operator != T_OP_EQ)
212 * If it's a vendor attribute,
213 * or it's a wire protocol,
214 * ensure it has '=='.
216 if (((vp->attribute & ~0xffff) != 0) ||
217 (vp->attribute < 0x100)) {
219 DEBUG("[%s]:%d WARNING! Changing '%s =' to '%s =='\n\tfor comparing RADIUS attribute in check item list for user %s",
220 filename, entry->lineno, vp->name, vp->name, entry->name);
222 DEBUG("\tChanging '%s =' to '%s =='",
225 vp->operator = T_OP_CMP_EQ;
230 * Cistron Compatibility mode.
232 * Re-write selected attributes
233 * to be '+=', instead of '='.
235 * All others get set to '=='
239 * Non-wire attributes become +=
241 * On the write attributes
244 if ((vp->attribute >= 0x100) &&
245 (vp->attribute <= 0xffff) &&
246 (vp->attribute != PW_HINT) &&
247 (vp->attribute != PW_HUNTGROUP_NAME)) {
248 DEBUG("\tChanging '%s =' to '%s +='",
250 vp->operator = T_OP_ADD;
252 DEBUG("\tChanging '%s =' to '%s =='",
254 vp->operator = T_OP_CMP_EQ;
258 } /* end of loop over check items */
262 * Look for server configuration items
265 * It's a common enough mistake, that it's
268 for (vp = entry->reply; vp != NULL; vp = vp->next) {
270 * If it's NOT a vendor attribute,
271 * and it's NOT a wire protocol
272 * and we ignore Fall-Through,
273 * then bitch about it, giving a
274 * good warning message.
276 if (!(vp->attribute & ~0xffff) &&
277 (vp->attribute > 0xff) &&
278 (vp->attribute > 1000)) {
279 log_debug("[%s]:%d WARNING! Check item \"%s\"\n"
280 "\tfound in reply item list for user \"%s\".\n"
281 "\tThis attribute MUST go on the first line"
282 " with the other check items",
283 filename, entry->lineno, vp->name,
289 * Ok, we've done all the same BS as
290 * rlm_users, so here we tear apart the
291 * linked list, and store our users in
292 * the hashtable we've built instead
295 /* Save what was next */
299 /* Save the DEFAULT entry specially */
300 if(strcmp(entry->name, "DEFAULT")==0) {
302 /* Save this as the last default we've seen */
306 /* put it at the end of the list */
308 for(cur=defaults; cur->next; cur=cur->next);
313 defaults->next = NULL;
319 /* Hash the username */
320 hashindex = fastuser_hash(entry->name, inst->hashsize);
322 /* Store the last default before this entry */
323 entry->lastdefault = lastdefault;
325 /* Store user in the hash */
326 fastuser_store(pair_list, entry, hashindex);
329 /* Restore entry to next pair_list */
332 } /* while(entry) loop */
334 if(!isacctfile && (default_list)) {
335 *default_list = defaults;
336 radlog(L_INFO, "rlm_fastusers: Loaded %ld users and %ld defaults",
337 numusers, numdefaults);
345 /* Hashes the username sent to it and returns index into hashtable */
346 int fastuser_hash(const char *s, long hashtablesize) {
347 unsigned long hash = 0;
350 hash = hash * 7907 + (unsigned char)*s++;
353 return (hash % hashtablesize);
356 /* Stores the username sent into the hashtable */
357 static int fastuser_store(PAIR_LIST **hashtable, PAIR_LIST *new, int idx) {
360 cur = hashtable[idx];
361 /* store new record at end of list */
363 while (cur->next != NULL)
368 new->next = hashtable[idx];
369 hashtable[idx] = new;
375 * Looks up user in hashtable. If user can't be found, returns 0.
376 * Otherwise returns a pointer to the structure for the user
378 static PAIR_LIST *fastuser_find(REQUEST *request, PAIR_LIST *user,
379 const char *username)
385 * Now we have to make sure it's the right user by
386 * comparing the check pairs
388 while((cur) && (!userfound)) {
389 if((strcmp(cur->name, username)==0) &&
390 paircmp(request->packet->vps, cur->check, &request->reply->vps) == 0) {
392 * Usercollide means we have to compare check pairs
395 if(mainconfig.do_usercollide) {
396 if((userfound = fastuser_passcheck(request, cur, username))==0) {
402 DEBUG2(" fastusers: Matched %s at %d", cur->name, cur->lineno);
413 return (PAIR_LIST *)0;
417 * Generate and log statistics about our hash table
419 static void fastuser_tablestats(PAIR_LIST **hashtable, long size) {
425 memset(countarray, 0, sizeof(countarray));
427 for(i=0; i<size; i++) {
429 for(cur=hashtable[i]; cur; cur=cur->next) {
441 radlog(L_INFO, "rlm_fastusers: Hash buckets with %d users: %d",
446 radlog(L_INFO, "rlm_fastusers: Hash buckets with more than 256: %d",
451 static int fastuser_passcheck(REQUEST *request, PAIR_LIST *user, const char *name)
454 VALUE_PAIR *check_save;
457 * We check for REJECT specially here or a REJECT
458 * user will never match
460 check_save = pairfind(user->check, PW_AUTHTYPE);
461 if(check_save->lvalue == PW_AUTHTYPE_REJECT) {
462 DEBUG2(" fastusers(uc): User '%s' line %d is Auth-Type Reject, but usercollide match",
463 user->name, user->lineno);
467 /* Save the orginal config items */
468 check_save = request->config_items;
469 request->config_items = NULL;
471 DEBUG2(" fastusers(uc): Checking %s at %d", user->name, user->lineno);
473 /* Copy this users check pairs to the request */
474 request->config_items = paircopy(user->check);
476 /* Check the req to see if we matched */
477 if(rad_check_password(request)==0) {
478 DEBUG2(" fastusers(uc): Matched %s at %d", user->name, user->lineno);
482 /* Restore check items */
483 pairfree(&request->config_items);
484 request->config_items = check_save;
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 = malloc(sizeof *inst);
498 radlog(L_ERR|L_CONS, "Out of memory\n");
501 memset(inst, 0, sizeof(inst));
503 if (cf_section_parse(conf, module_config) < 0) {
508 inst->usersfile = config.usersfile;
509 inst->acctusersfile = config.acctusersfile;
510 inst->hashsize = config.hashsize;
511 inst->defaults = config.defaults;
512 inst->stats = config.stats;
513 inst->compat_mode = config.compat_mode;
514 inst->hash_reload = config.hash_reload;
515 inst->next_reload = time(NULL) + inst->hash_reload;
516 inst->hashtable = NULL;
517 if(fastuser_buildhash(inst) < 0) {
518 radlog(L_ERR, "rlm_fastusers: error building user hash. aborting");
523 * Need code here to read acct_users file
526 config.usersfile = NULL;
527 config.acctusersfile = NULL;
528 config.hashtable = NULL;
529 config.defaults = NULL;
530 config.compat_mode = NULL;
537 * Find the named user in the database. Create the
538 * set of attribute-value pairs to check and reply with
539 * for this user from the database. The main code only
540 * needs to check the password, the rest is done here.
542 static int fastuser_authorize(void *instance, REQUEST *request)
545 VALUE_PAIR *namepair;
546 VALUE_PAIR *check_tmp;
547 VALUE_PAIR *reply_tmp;
549 PAIR_LIST *curdefault;
554 struct fastuser_instance *inst = instance;
557 * Do we need to reload the cache?
558 * Really we should spawn a thread to do this
560 if((inst->hash_reload) && (request->timestamp > inst->next_reload)) {
561 inst->next_reload = request->timestamp + inst->hash_reload;
562 radlog(L_INFO, "rlm_fastusers: Reloading fastusers hash");
563 if(fastuser_buildhash(inst) < 0) {
564 radlog(L_ERR, "rlm_fastusers: error building user hash. aborting");
570 * Grab the canonical user name.
572 namepair = request->username;
573 name = namepair ? (char *) namepair->strvalue : "NONE";
576 * Find the entry for the user.
578 hashidx = fastuser_hash(name, inst->hashsize);
579 user = inst->hashtable[hashidx];
580 if((user=fastuser_find(request, user, name))!=NULL) {
585 * If there's no lastdefault and we
586 * don't fallthrough, just copy the
587 * pairs for this user and return
589 if((user) && (userfound) && (user->lastdefault == NULL)) {
590 DEBUG2("rlm_fastusers: user found before DEFAULT");
592 check_tmp = paircopy(user->check);
593 pairmove(&request->config_items, &check_tmp);
594 pairfree(&check_tmp);
596 reply_tmp = paircopy(user->reply);
597 pairmove(&request->reply->vps, &reply_tmp);
598 pairfree(&reply_tmp);
600 if(!fallthrough(user->reply)) {
601 pairdelete(&request->reply->vps, PW_FALL_THROUGH);
602 return RLM_MODULE_UPDATED;
605 user=fastuser_find(request, user, name);
610 * When we get here, we've either found
611 * the user or not, but to preserve order
612 * we start at the top of the default
613 * list and work our way thru
614 * When we get to the user's 'lastdefault'
615 * we check to see if we should stop
618 DEBUG2("rlm_fastusers: checking defaults");
620 curdefault = inst->defaults;
622 if(paircmp(request->packet->vps, curdefault->check,
623 &request->reply->vps) == 0) {
624 DEBUG2(" fastusers: Matched %s at %d",
625 curdefault->name, curdefault->lineno);
628 check_tmp = paircopy(curdefault->check);
629 pairmove(&request->config_items, &check_tmp);
630 pairfree(&check_tmp);
632 reply_tmp = paircopy(curdefault->reply);
633 pairmove(&request->reply->vps, &reply_tmp);
634 pairfree(&reply_tmp);
639 * There's no fallthru on this default which
640 * is *before* we find the user in the file,
641 * so we know it's safe to quit here
643 if (!fallthrough(curdefault->reply))
647 * If we found the user, we want to stop
648 * processing once we get to 'lastdefault'
649 * This way we can process this user's entry
650 * in the order it was found in the file
652 while((userfound && (user) && (curdefault == user->lastdefault))) {
653 DEBUG2(" fastusers: found lastdefault at line %d",
656 check_tmp = paircopy(user->check);
657 pairmove(&request->config_items, &check_tmp);
658 pairfree(&check_tmp);
660 reply_tmp = paircopy(user->reply);
661 pairmove(&request->reply->vps, &reply_tmp);
662 pairfree(&reply_tmp);
664 if(!fallthrough(user->reply)) {
665 pairdelete(&request->reply->vps, PW_FALL_THROUGH);
666 return RLM_MODULE_UPDATED;
670 * Find next occurence of THIS user in
674 user=fastuser_find(request, user, name);
677 curdefault = curdefault->next;
680 if(userfound || defaultfound) {
681 pairdelete(&request->reply->vps, PW_FALL_THROUGH);
682 return RLM_MODULE_UPDATED;
684 DEBUG2("rlm_fastusers: user not found");
685 return RLM_MODULE_NOTFOUND;
690 * Authentication - unused.
692 static int fastuser_authenticate(void *instance, REQUEST *request)
696 return RLM_MODULE_OK;
700 * Pre-Accounting - read the acct_users file for check_items and
701 * config_items. Reply items are Not Recommended(TM) in acct_users,
702 * except for Fallthrough, which should work
704 * This function is mostly a copy of file_authorize
706 static int fastuser_preacct(void *instance, REQUEST *request)
708 VALUE_PAIR *namepair;
710 VALUE_PAIR *request_pairs;
711 VALUE_PAIR **config_pairs;
712 VALUE_PAIR *reply_pairs = NULL;
713 VALUE_PAIR *check_tmp;
714 VALUE_PAIR *reply_tmp;
715 PAIR_LIST *pl = NULL;
717 struct fastuser_instance *inst = instance;
719 namepair = request->username;
720 name = namepair ? (char *) namepair->strvalue : "NONE";
721 request_pairs = request->packet->vps;
722 config_pairs = &request->config_items;
725 * Find the entry for the user.
727 for (pl = inst->acctusers; pl; pl = pl->next) {
729 if (strcmp(name, pl->name) && strcmp(pl->name, "DEFAULT"))
732 if (paircmp(request_pairs, pl->check, &reply_pairs) == 0) {
733 DEBUG2(" acct_users: Matched %s at %d",
734 pl->name, pl->lineno);
736 check_tmp = paircopy(pl->check);
737 reply_tmp = paircopy(pl->reply);
738 pairmove(&reply_pairs, &reply_tmp);
739 pairmove(config_pairs, &check_tmp);
740 pairfree(&reply_tmp);
741 pairfree(&check_tmp); /* should be NULL */
745 if (!fallthrough(pl->reply))
751 * See if we succeeded.
754 return RLM_MODULE_NOOP; /* on to the next module */
757 * FIXME: log a warning if there are any reply items other than
760 pairfree(&reply_pairs); /* Don't need these */
762 return RLM_MODULE_OK;
768 static int fastuser_detach(void *instance)
770 struct fastuser_instance *inst = instance;
774 /* Free hash table */
775 for(hashindex=0; hashindex<inst->hashsize; hashindex++) {
776 if(inst->hashtable[hashindex]) {
777 cur = inst->hashtable[hashindex];
782 free(inst->compat_mode);
783 free(inst->hashtable);
784 pairlist_free(&inst->defaults);
785 pairlist_free(&inst->acctusers);
786 free(inst->usersfile);
787 free(inst->acctusersfile);
793 * This function is unused
795 static int fastuser_accounting(void *instance, REQUEST *request)
797 return RLM_MODULE_FAIL;
800 /* globally exported name */
801 module_t rlm_fastusers = {
803 0, /* type: reserved */
804 NULL, /* initialization */
805 fastuser_instantiate, /* instantiation */
806 fastuser_authorize, /* authorization */
807 fastuser_authenticate, /* authentication */
808 fastuser_preacct, /* preaccounting */
809 fastuser_accounting, /* accounting */
810 NULL, /* checksimul */
811 fastuser_detach, /* detach */