2 * rlm_files.c authorization: Find a user in the "users" file.
3 * accounting: Write the "detail" files.
9 static const char rcsid[] = "$Id$";
13 #include <sys/socket.h>
15 #include <netinet/in.h>
39 struct file_instance {
51 #if defined(WITH_DBM) || defined(WITH_NDBM)
53 * See if a potential DBM file is present.
55 static int checkdbm(char *users, char *ext)
60 strcpy(buffer, users);
63 return stat(buffer, &st);
67 * Find the named user in the DBM user database.
68 * Returns: -1 not found
69 * 0 found but doesn't match.
70 * 1 found and matches.
72 static int dbm_find(DBM *dbmfile, char *name, VALUE_PAIR *request_pairs,
73 VALUE_PAIR **check_pairs, VALUE_PAIR **reply_pairs)
78 VALUE_PAIR *check_tmp;
79 VALUE_PAIR *reply_tmp;
83 named.dsize = strlen(name);
85 contentd = fetch(named);
88 contentd = dbm_fetch(dbmfile, named);
90 if(contentd.dptr == NULL)
97 * Parse the check values
100 contentd.dptr[contentd.dsize] = '\0';
102 if (*ptr != '\n' && userparse(ptr, &check_tmp) != 0) {
103 radlog(L_ERR|L_CONS, "Parse error (check) for user %s", name);
107 while(*ptr != '\n' && *ptr != '\0') {
111 radlog(L_ERR|L_CONS, "Parse error (no reply pairs) for user %s",
119 * Parse the reply values
121 if (userparse(ptr, &reply_tmp) != 0) {
122 radlog(L_ERR|L_CONS, "Parse error (reply) for user %s", name);
129 * See if the check_pairs match.
131 if (paircmp(request_pairs, check_tmp, reply_pairs) == 0) {
133 pairmove(reply_pairs, &reply_tmp);
134 pairmove2(reply_pairs, &reply_tmp, PW_FALL_THROUGH);
135 pairmove(check_pairs, &check_tmp);
145 * See if a VALUE_PAIR list contains Fall-Through = Yes
147 static int fallthrough(VALUE_PAIR *vp)
151 tmp = pairfind(vp, PW_FALL_THROUGH);
153 return tmp ? tmp->lvalue : 0;
158 static int file_init(void)
164 * A temporary holding area for config values to be extracted
165 * into, before they are copied into the instance data
167 static struct file_instance config;
169 static CONF_PARSER module_config[] = {
170 { "usersfile", PW_TYPE_STRING_PTR, &config.usersfile, RADIUS_USERS },
171 { "acctusersfile", PW_TYPE_STRING_PTR, &config.acctusersfile, RADIUS_ACCT_USERS },
172 { "compat", PW_TYPE_STRING_PTR, &config.compat_mode, "cistron" },
173 { NULL, -1, NULL, NULL }
176 static int getusersfile(const char *filename, PAIR_LIST **pair_list)
179 PAIR_LIST *users = NULL;
180 #if defined(WITH_DBM) || defined(WITH_NDBM)
182 (checkdbm(filename, ".dir") == 0 ||
183 checkdbm(filename, ".db") == 0)) {
184 radlog(L_INFO|L_CONS, "DBM files found but no -b flag " "given - NOT using DBM");
189 rcode = pairlist_read(filename, &users, 1);
196 * Walk through the 'users' file list, if we're debugging,
197 * or if we're in compat_mode.
200 (strcmp(config.compat_mode, "cistron") == 0)) {
203 int compat_mode = FALSE;
205 if (strcmp(config.compat_mode, "cistron") == 0) {
212 DEBUG("[%s]:%d Cistron compatibility checks for entry %s ...",
213 filename, entry->lineno,
218 * Look for improper use of '=' in the
219 * check items. They should be using
220 * '==' for on-the-wire RADIUS attributes,
221 * and probably ':=' for server
222 * configuration items.
224 for (vp = entry->check; vp != NULL; vp = vp->next) {
226 * Ignore attributes which are set
229 if (vp->operator != T_OP_EQ) {
234 * If it's a vendor attribute,
235 * or it's a wire protocol,
236 * ensure it has '=='.
238 if (((vp->attribute & ~0xffff) != 0) ||
239 (vp->attribute < 0x100)) {
241 DEBUG("[%s]:%d WARNING! Changing '%s =' to '%s =='\n\tfor comparing RADIUS attribute in check item list for user %s",
242 filename, entry->lineno,
246 DEBUG("\tChanging '%s =' to '%s =='",
249 vp->operator = T_OP_CMP_EQ;
254 * Cistron Compatibility mode.
256 * Re-write selected attributes
257 * to be '+=', instead of '='.
259 * All others get set to '=='
263 * Non-wire attributes become +=
265 * On the write attributes
268 if ((vp->attribute >= 0x100) &&
269 (vp->attribute <= 0xffff) &&
270 (vp->attribute != PW_HINT) &&
271 (vp->attribute != PW_HUNTGROUP_NAME)) {
272 DEBUG("\tChanging '%s =' to '%s +='",
274 vp->operator = T_OP_ADD;
276 DEBUG("\tChanging '%s =' to '%s =='",
278 vp->operator = T_OP_CMP_EQ;
282 } /* end of loop over check items */
286 * Look for server configuration items
289 * It's a common enough mistake, that it's
292 for (vp = entry->reply; vp != NULL; vp = vp->next) {
294 * If it's NOT a vendor attribute,
295 * and it's NOT a wire protocol
296 * and we ignore Fall-Through,
297 * then bitch about it, giving a
298 * good warning message.
300 if (!(vp->attribute & ~0xffff) &&
301 (vp->attribute > 0xff) &&
302 (vp->attribute > 1000)) {
303 log_debug("[%s]:%d WARNING! Check item \"%s\"\n"
304 "\tfound in reply item list for user \"%s\".\n"
305 "\tThis attribute MUST go on the first line"
306 " with the other check items",
307 filename, entry->lineno, vp->name,
322 * (Re-)read the "users" file into memory.
324 static int file_instantiate(CONF_SECTION *conf, void **instance)
326 struct file_instance *inst;
329 inst = malloc(sizeof *inst);
331 radlog(L_ERR|L_CONS, "Out of memory\n");
335 if (cf_section_parse(conf, module_config) < 0) {
340 inst->usersfile = config.usersfile;
341 inst->acctusersfile = config.acctusersfile;
342 config.usersfile = NULL;
343 config.acctusersfile = NULL;
345 rcode = getusersfile(inst->usersfile, &inst->users);
347 radlog(L_ERR|L_CONS, "Errors reading %s", inst->usersfile);
348 free(inst->usersfile);
349 free(inst->acctusersfile);
354 rcode = getusersfile(inst->acctusersfile, &inst->acctusers);
356 radlog(L_ERR|L_CONS, "Errors reading %s", inst->acctusersfile);
357 pairlist_free(&inst->users);
358 free(inst->usersfile);
359 free(inst->acctusersfile);
369 * Find the named user in the database. Create the
370 * set of attribute-value pairs to check and reply with
371 * for this user from the database. The main code only
372 * needs to check the password, the rest is done here.
374 static int file_authorize(void *instance, REQUEST *request)
376 VALUE_PAIR *namepair;
377 VALUE_PAIR *request_pairs;
378 VALUE_PAIR *check_tmp;
379 VALUE_PAIR *reply_tmp;
382 #if defined(WITH_DBM) || defined(WITH_NDBM)
387 struct file_instance *inst = instance;
388 #ifdef WITH_USERCOLLIDE
389 VALUE_PAIR *auth_type_pair;
390 VALUE_PAIR *password_pair;
391 VALUE_PAIR *auth_item;
395 VALUE_PAIR **check_pairs, **reply_pairs;
398 request_pairs = request->packet->vps;
399 check_pairs = &request->config_items;
400 reply_pairs = &request->reply->vps;
403 * Grab the canonical user name.
405 namepair = request->username;
406 name = namepair ? (char *) namepair->strvalue : "NONE";
409 * Find the entry for the user.
411 #if defined(WITH_DBM) || defined(WITH_NDBM)
413 * FIXME: move to rlm_dbm.c
417 * FIXME: No Prefix / Suffix support for DBM.
420 if (dbminit(inst->usersfile) != 0)
423 if ((dbmfile = dbm_open(inst->usersfile, O_RDONLY, 0)) == NULL)
426 radlog(L_ERR|L_CONS, "cannot open dbm file %s",
428 return RLM_MODULE_FAIL;
431 r = dbm_find(dbmfile, name, request_pairs, check_pairs,
433 if (r > 0) found = 1;
434 if (r <= 0 || fallthrough(*reply_pairs)) {
436 pairdelete(reply_pairs, PW_FALL_THROUGH);
438 sprintf(buffer, "DEFAULT");
440 while ((r = dbm_find(dbmfile, buffer, request_pairs,
441 check_pairs, reply_pairs)) >= 0 || i < 2) {
444 if (!fallthrough(*reply_pairs))
446 pairdelete(reply_pairs,PW_FALL_THROUGH);
448 sprintf(buffer, "DEFAULT%d", i++);
459 * Note the fallthrough through the #endif.
463 for(pl = inst->users; pl; pl = pl->next) {
464 #ifdef WITH_USERCOLLIDE
468 * If the current entry is NOT a default,
469 * AND the name does NOT match the current entry,
470 * then skip to the next entry.
472 if ((strcmp(pl->name, "DEFAULT") != 0) &&
473 (strcmp(name, pl->name) != 0)) {
478 * If the current request matches against the
479 * check pairs, then add the reply pairs from the
480 * entry to the current list of reply pairs.
482 if ((paircmp(request_pairs, pl->check, reply_pairs) == 0)) {
483 #ifdef WITH_USERCOLLIDE
485 * We don't compare pass on default users
486 * or they never match. Oops.
488 if(strcmp(pl->name, "DEFAULT")) {
490 * We check the pass as a config
491 * item with user collisions Most
492 * of this is stolen out of
493 * rad_check_password()
495 if ((auth_type_pair = pairfind(pl->check, PW_AUTHTYPE)) != NULL) {
496 auth_type = auth_type_pair->lvalue;
497 DEBUG2(" file_auth (Usercollide): auth_type %d", auth_type);
500 /* Find pass in the REQ */
501 auth_item = request->password;
502 if (auth_item == NULL) {
503 DEBUG2(" file_auth (Usercollide): No password in the request");
504 return RLM_MODULE_OK;
507 /* Find the password from the users file. */
508 if ((password_pair = pairfind(pl->check, PW_CRYPT_PASSWORD)) != NULL)
509 auth_type = PW_AUTHTYPE_CRYPT;
511 password_pair = pairfind(pl->check, PW_PASSWORD);
514 case PW_AUTHTYPE_CRYPT:
515 DEBUG2(" file_auth (Usercollide): Checking Crypt");
516 if (password_pair == NULL) {
517 result = auth_item->strvalue ? 0 : 1;
520 if (strcmp(password_pair->strvalue,
521 crypt(auth_item->strvalue,
522 password_pair->strvalue)) != 0)
525 case PW_AUTHTYPE_LOCAL:
526 DEBUG2(" file_auth (Usercollide): Checking Local");
527 if (auth_item->attribute != PW_CHAP_PASSWORD) {
528 if (password_pair == NULL ||
529 strcmp(password_pair->strvalue,
530 auth_item->strvalue)!=0)
534 case PW_AUTHTYPE_ACCEPT:
538 } /* switch(auth_type) */
543 DEBUG2(" users: Matched %s at %d", pl->name, pl->lineno);
545 check_tmp = paircopy(pl->check);
546 reply_tmp = paircopy(pl->reply);
547 pairmove(reply_pairs, &reply_tmp);
548 pairmove(check_pairs, &check_tmp);
550 pairfree(check_tmp); /* should be NULL */
554 if (!fallthrough(pl->reply))
556 #ifdef WITH_USERCOLLIDE
563 * See if we succeeded. If we didn't find the user,
564 * then exit from the module.
567 return RLM_MODULE_NOTFOUND;
570 * Remove server internal parameters.
572 pairdelete(reply_pairs, PW_FALL_THROUGH);
574 return RLM_MODULE_OK;
578 * Authentication - unused.
580 static int file_authenticate(void *instance, REQUEST *request)
584 return RLM_MODULE_OK;
588 * Pre-Accounting - read the acct_users file for check_items and
589 * config_items. Reply items are Not Recommended(TM) in acct_users,
590 * except for Fallthrough, which should work
592 * This function is mostly a copy of file_authorize
594 static int file_preacct(void *instance, REQUEST *request)
596 VALUE_PAIR *namepair;
598 VALUE_PAIR *request_pairs;
599 VALUE_PAIR **config_pairs;
600 VALUE_PAIR *reply_pairs = NULL;
601 VALUE_PAIR *check_tmp;
602 VALUE_PAIR *reply_tmp;
605 #if defined(WITH_DBM) || defined(WITH_NDBM)
609 struct file_instance *inst = instance;
611 namepair = request->username;
612 name = namepair ? (char *) namepair->strvalue : "NONE";
613 request_pairs = request->packet->vps;
614 config_pairs = &request->config_items;
617 * Find the entry for the user.
619 #if defined(WITH_DBM) || defined(WITH_NDBM)
621 * FIXME: move to rlm_dbm.c
625 * FIXME: No Prefix / Suffix support for DBM.
628 if (dbminit(inst->acctusersfile) != 0)
631 if ((dbmfile = dbm_open(inst->acctusersfile, O_RDONLY, 0)) == NULL)
634 radlog(L_ERR|L_CONS, "cannot open dbm file %s",
636 return RLM_MODULE_FAIL;
639 r = dbm_find(dbmfile, name, request_pairs, config_pairs,
641 if (r > 0) found = 1;
642 if (r <= 0 || fallthrough(*reply_pairs)) {
644 pairdelete(reply_pairs, PW_FALL_THROUGH);
646 sprintf(buffer, "DEFAULT");
648 while ((r = dbm_find(dbmfile, buffer, request_pairs,
649 config_pairs, &reply_pairs)) >= 0 || i < 2) {
652 if (!fallthrough(*reply_pairs))
654 pairdelete(reply_pairs,PW_FALL_THROUGH);
656 sprintf(buffer, "DEFAULT%d", i++);
667 * Note the fallthrough through the #endif.
671 for(pl = inst->acctusers; pl; pl = pl->next) {
673 if (strcmp(name, pl->name) && strcmp(pl->name, "DEFAULT"))
676 if (paircmp(request_pairs, pl->check, &reply_pairs) == 0) {
677 DEBUG2(" acct_users: Matched %s at %d",
678 pl->name, pl->lineno);
680 check_tmp = paircopy(pl->check);
681 reply_tmp = paircopy(pl->reply);
682 pairmove(&reply_pairs, &reply_tmp);
683 pairmove(config_pairs, &check_tmp);
685 pairfree(check_tmp); /* should be NULL */
689 if (!fallthrough(pl->reply))
695 * See if we succeeded.
698 return RLM_MODULE_NOOP; /* on to the next module */
701 * FIXME: log a warning if there are any reply items other than
704 pairfree(reply_pairs); /* Don't need these */
706 return RLM_MODULE_OK;
712 static int file_detach(void *instance)
714 struct file_instance *inst = instance;
715 pairlist_free(&inst->users);
716 pairlist_free(&inst->acctusers);
717 free(inst->usersfile);
718 free(inst->acctusersfile);
724 /* globally exported name */
725 module_t rlm_files = {
727 0, /* type: reserved */
728 file_init, /* initialization */
729 file_instantiate, /* instantiation */
730 file_authorize, /* authorization */
731 file_authenticate, /* authentication */
732 file_preacct, /* preaccounting */
733 NULL, /* accounting */
734 NULL, /* checksimul */
735 file_detach, /* detach */