2 * rlm_fastusers.c authorization: Find a user in the hashed "users" file.
3 * accounting: Do nothing. Auth module only.
10 #include <sys/socket.h>
13 #include <netinet/in.h>
34 struct fastuser_instance {
40 PAIR_LIST **hashtable;
42 PAIR_LIST *default_entry;
47 /* Function declarations */
48 static int fastuser_getfile(const char *filename, PAIR_LIST **hashtable);
49 static int fastuser_hash(const char *s, long hashtablesize);
50 static int fastuser_store(PAIR_LIST **hashtable, PAIR_LIST *entry, int idx);
51 static PAIR_LIST *fastuser_find(PAIR_LIST **hashtable, const char *user,
55 * A temporary holding area for config values to be extracted
56 * into, before they are copied into the instance data
58 static struct fastuser_instance config;
60 static CONF_PARSER module_config[] = {
61 { "usersfile", PW_TYPE_STRING_PTR, &config.usersfile, RADIUS_USERS },
62 { "hashsize", PW_TYPE_INTEGER, &config.hashsize, "100000" },
63 { "compat", PW_TYPE_STRING_PTR, &config.compat_mode, "cistron" },
64 { "normal_defaults", PW_TYPE_BOOLEAN, &config.normal_defaults, "yes" },
65 { NULL, -1, NULL, NULL }
68 static int fastuser_getfile(const char *filename, PAIR_LIST **hashtable)
71 PAIR_LIST *users = NULL;
72 int compat_mode = FALSE;
73 PAIR_LIST *entry, *next, *cur;
78 rcode = pairlist_read(filename, &users, 1);
83 if (strcmp(config.compat_mode, "cistron") == 0) {
90 DEBUG("[%s]:%d Cistron compatibility checks for entry %s ...",
91 filename, entry->lineno, entry->name);
95 * Look for improper use of '=' in the
96 * check items. They should be using
97 * '==' for on-the-wire RADIUS attributes,
98 * and probably ':=' for server
99 * configuration items.
101 for (vp = entry->check; vp != NULL; vp = vp->next) {
103 * Ignore attributes which are set
106 if (vp->operator != T_OP_EQ)
111 * If it's a vendor attribute,
112 * or it's a wire protocol,
113 * ensure it has '=='.
115 if (((vp->attribute & ~0xffff) != 0) ||
116 (vp->attribute < 0x100)) {
118 DEBUG("[%s]:%d WARNING! Changing '%s =' to '%s =='\n\tfor comparing RADIUS attribute in check item list for user %s",
119 filename, entry->lineno, vp->name, vp->name, entry->name);
121 DEBUG("\tChanging '%s =' to '%s =='",
124 vp->operator = T_OP_CMP_EQ;
129 * Cistron Compatibility mode.
131 * Re-write selected attributes
132 * to be '+=', instead of '='.
134 * All others get set to '=='
138 * Non-wire attributes become +=
140 * On the write attributes
143 if ((vp->attribute >= 0x100) &&
144 (vp->attribute <= 0xffff) &&
145 (vp->attribute != PW_HINT) &&
146 (vp->attribute != PW_HUNTGROUP_NAME)) {
147 DEBUG("\tChanging '%s =' to '%s +='",
149 vp->operator = T_OP_ADD;
151 DEBUG("\tChanging '%s =' to '%s =='",
153 vp->operator = T_OP_CMP_EQ;
157 } /* end of loop over check items */
161 * Look for server configuration items
164 * It's a common enough mistake, that it's
167 for (vp = entry->reply; vp != NULL; vp = vp->next) {
169 * If it's NOT a vendor attribute,
170 * and it's NOT a wire protocol
171 * and we ignore Fall-Through,
172 * then bitch about it, giving a
173 * good warning message.
175 if (!(vp->attribute & ~0xffff) &&
176 (vp->attribute > 0xff) &&
177 (vp->attribute > 1000)) {
178 log_debug("[%s]:%d WARNING! Check item \"%s\"\n"
179 "\tfound in reply item list for user \"%s\".\n"
180 "\tThis attribute MUST go on the first line"
181 " with the other check items",
182 filename, entry->lineno, vp->name,
188 * Ok, we've done all the same BS as
189 * rlm_users, so here we tear apart the
190 * linked list, and store our users in
191 * the hashtable we've built instead
194 /* Save what was next */
197 /* Save the DEFAULT entry specially */
198 if(strcmp(entry->name, "DEFAULT")==0) {
200 /* put it at the end of the list */
201 if(config.default_entry) {
202 for(cur=config.default_entry; cur->next; cur=cur->next);
206 config.default_entry = entry;
207 config.default_entry->next = NULL;
212 /* Hash the username */
213 hashindex = fastuser_hash(entry->name, config.hashsize);
215 /* Store user in the hash */
216 fastuser_store(hashtable, entry, hashindex);
218 /* Restore entry to next pair_list */
222 } /* while(entry) loop */
224 if(!config.normal_defaults && (numdefaults>1)) {
225 radlog(L_INFO, "Warning: fastusers found multiple DEFAULT entries. Using the first.");
229 * We *should* do this to help out clueless admins
230 * but it's documented, so it will confuse those who
231 * do read the docs if we do it here as well
232 if(!config.normal_defaults) {
233 pairdelete(&config.default_entry->check, PW_AUTHTYPE);
240 /* Hashes the username sent to it and returns index into hashtable */
241 int fastuser_hash(const char *s, long hashtablesize) {
242 unsigned long hash = 0;
245 hash = hash * 7907 + (unsigned char)*s++;
248 return (hash % hashtablesize);
251 /* Stores the username sent into the hashtable */
252 static int fastuser_store(PAIR_LIST **hashtable, PAIR_LIST *new, int idx) {
254 /* store new record at beginning of list */
255 new->next = hashtable[idx];
256 hashtable[idx] = new;
262 * Looks up user in hashtable. If user can't be found, returns 0.
263 * Otherwise returns a pointer to the structure for the user
265 static PAIR_LIST *fastuser_find(PAIR_LIST **hashtable,
266 const char *user, long hashsize)
272 /* first hash the username and get the index into the hashtable */
273 idx = fastuser_hash(user, hashsize);
275 cur = hashtable[idx];
277 while((cur != NULL) && (strcmp(cur->name, user))) {
282 DEBUG2(" fastusers: user %s found in hashtable bucket %d", user, idx);
286 return (PAIR_LIST *)0;
292 * (Re-)read the "users" file into memory.
294 static int fastuser_instantiate(CONF_SECTION *conf, void **instance)
296 struct fastuser_instance *inst=0;
300 inst = malloc(sizeof *inst);
302 radlog(L_ERR|L_CONS, "Out of memory\n");
305 memset(inst, 0, sizeof(inst));
307 if (cf_section_parse(conf, module_config) < 0) {
313 * Sue me. The tradeoff for this extra variable
314 * is clean code below
316 memsize = sizeof(PAIR_LIST *) * config.hashsize;
318 * Allocate space for hash table here
320 if( (inst->hashtable = (PAIR_LIST **)malloc(memsize)) == NULL) {
321 radlog(L_ERR, "fastusers: Can't build hashtable, out of memory!");
324 memset((PAIR_LIST *)inst->hashtable, 0, memsize);
326 rcode = fastuser_getfile(config.usersfile, inst->hashtable);
328 radlog(L_ERR|L_CONS, "Errors reading %s", config.usersfile);
332 inst->usersfile = config.usersfile;
333 inst->hashsize = config.hashsize;
334 inst->default_entry = config.default_entry;
335 inst->compat_mode = config.compat_mode;
336 inst->normal_defaults = config.normal_defaults;
339 config.usersfile = NULL;
340 config.hashtable = NULL;
341 config.default_entry = NULL;
343 config.compat_mode = NULL;
350 * Find the named user in the database. Create the
351 * set of attribute-value pairs to check and reply with
352 * for this user from the database. The main code only
353 * needs to check the password, the rest is done here.
355 static int fastuser_authorize(void *instance, REQUEST *request)
358 VALUE_PAIR *namepair;
359 VALUE_PAIR *request_pairs;
360 VALUE_PAIR *check_tmp;
361 VALUE_PAIR *reply_tmp;
362 VALUE_PAIR **check_pairs;
363 VALUE_PAIR **reply_pairs;
364 VALUE_PAIR *check_save;
368 int checkdefault = 0;
369 struct fastuser_instance *inst = instance;
371 request_pairs = request->packet->vps;
372 check_pairs = &request->config_items;
373 reply_pairs = &request->reply->vps;
376 * Grab the canonical user name.
378 namepair = request->username;
379 name = namepair ? (char *) namepair->strvalue : "NONE";
382 * Find the entry for the user.
384 if((user=fastuser_find(inst->hashtable, name, inst->hashsize))==NULL) {
385 if(inst->normal_defaults) {
388 return RLM_MODULE_NOTFOUND;
393 * Usercollide means we have to compare check pairs
396 if(mainconfig.do_usercollide && !checkdefault) {
397 /* Save the orginal config items */
398 check_save = paircopy(request->config_items);
400 while((user) && (!found) && (strcmp(user->name, name)==0)) {
401 if(paircmp(request_pairs, user->check, reply_pairs) != 0) {
405 DEBUG2(" fastusers(uc): Checking %s at %d", user->name, user->lineno);
407 /* Copy this users check pairs to the request */
408 check_tmp = paircopy(user->check);
409 pairmove(check_pairs, &check_tmp);
412 /* Check the req to see if we matched */
413 if(rad_check_password(request)==0) {
416 /* We didn't match here */
418 /* Restore check items */
419 pairfree(request->config_items);
420 request->config_items = paircopy(check_save);
421 check_pairs = &request->config_items;
426 /* Free our saved config items */
427 pairfree(check_save);
431 * No usercollide, just compare check pairs
433 if(!mainconfig.do_usercollide && !checkdefault) {
434 while((user) && (!found) && (strcmp(user->name, name)==0)) {
435 if(paircmp(request_pairs, user->check, reply_pairs) == 0) {
437 DEBUG2(" fastusers: Matched %s at %d", user->name, user->lineno);
445 * When we get here, we've either found the user or not
446 * and we either do normal DEFAULTs or not.
450 * We found the user & normal default
451 * copy relevant pairs and return
453 if(found && inst->normal_defaults) {
454 check_tmp = paircopy(user->check);
455 pairmove(check_pairs, &check_tmp);
457 reply_tmp = paircopy(user->reply);
458 pairmove(reply_pairs, &reply_tmp);
460 return RLM_MODULE_UPDATED;
464 * We didn't find the user, and we aren't supposed to
465 * check defaults. So just report not found.
467 if(!found && !inst->normal_defaults) {
468 return RLM_MODULE_NOTFOUND;
472 * We didn't find the user, but we should
473 * check the defaults.
475 if(!found && inst->normal_defaults) {
476 user = inst->default_entry;
477 while((user) && (!found)) {
478 if(paircmp(request_pairs, user->check, reply_pairs) == 0) {
479 DEBUG2(" fastusers: Matched %s at %d", user->name, user->lineno);
487 check_tmp = paircopy(user->check);
488 pairmove(check_pairs, &check_tmp);
490 reply_tmp = paircopy(user->reply);
491 pairmove(reply_pairs, &reply_tmp);
493 return RLM_MODULE_UPDATED;
496 return RLM_MODULE_NOTFOUND;
501 * We found the user, and we don't use normal defaults.
502 * So copy the check and reply pairs from the default
503 * entry to the request
505 if(found && !inst->normal_defaults) {
507 /* We've already done this above if(mainconfig.do_usercollide) */
508 if(!mainconfig.do_usercollide) {
509 check_tmp = paircopy(user->check);
510 pairmove(check_pairs, &check_tmp);
513 reply_tmp = paircopy(user->reply);
514 pairmove(reply_pairs, &reply_tmp);
518 * We also need to add the pairs from
519 * inst->default_entry if the vp is
520 * not already present.
523 if(inst->default_entry) {
524 check_tmp = paircopy(inst->default_entry->check);
525 reply_tmp = paircopy(inst->default_entry->reply);
526 pairmove(reply_pairs, &reply_tmp);
527 pairmove(check_pairs, &check_tmp);
532 return RLM_MODULE_UPDATED;
538 * Authentication - unused.
540 static int fastuser_authenticate(void *instance, REQUEST *request)
544 return RLM_MODULE_OK;
550 static int fastuser_detach(void *instance)
552 struct fastuser_instance *inst = instance;
557 /* Free hash table */
558 for(hashindex=0; hashindex<inst->hashsize; hashindex++) {
559 if(inst->hashtable[hashindex]) {
560 cur = inst->hashtable[hashindex];
565 free(inst->hashtable);
566 pairlist_free(&inst->users);
567 pairlist_free(&inst->default_entry);
568 free(inst->usersfile);
569 free(inst->compat_mode);
575 * This function is unused
577 static int fastuser_preacct(void *instance, REQUEST *request)
579 return RLM_MODULE_FAIL;
583 * This function is unused
585 static int fastuser_accounting(void *instance, REQUEST *request)
587 return RLM_MODULE_FAIL;
590 /* globally exported name */
591 module_t rlm_fastusers = {
593 0, /* type: reserved */
594 NULL, /* initialization */
595 fastuser_instantiate, /* instantiation */
596 fastuser_authorize, /* authorization */
597 fastuser_authenticate, /* authentication */
598 fastuser_preacct, /* preaccounting */
599 fastuser_accounting, /* accounting */
600 NULL, /* checksimul */
601 fastuser_detach, /* detach */