2 * rlm_files.c authorization: Find a user in the "users" file.
3 * accounting: Write the "detail" files.
9 static const char rcsid[] = "$Id$";
15 #include <sys/socket.h>
17 #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)
377 VALUE_PAIR *namepair;
378 VALUE_PAIR *request_pairs;
379 VALUE_PAIR *check_tmp;
380 VALUE_PAIR *reply_tmp;
384 #if defined(WITH_DBM) || defined(WITH_NDBM)
389 struct file_instance *inst = instance;
390 #ifdef WITH_USERCOLLIDE
391 VALUE_PAIR *auth_type_pair;
392 VALUE_PAIR *password_pair;
393 VALUE_PAIR *auth_item;
397 VALUE_PAIR **check_pairs, **reply_pairs;
400 request_pairs = request->packet->vps;
401 check_pairs = &request->config_items;
402 reply_pairs = &request->reply->vps;
405 * Grab the canonical user name.
407 namepair = request->username;
408 name = namepair ? (char *) namepair->strvalue : "NONE";
411 * Find the NAS port ID.
413 if ((tmp = pairfind(request_pairs, PW_NAS_PORT_ID)) != NULL)
414 nas_port = tmp->lvalue;
417 * Find the entry for the user.
419 #if defined(WITH_DBM) || defined(WITH_NDBM)
421 * FIXME: move to rlm_dbm.c
425 * FIXME: No Prefix / Suffix support for DBM.
428 if (dbminit(inst->usersfile) != 0)
431 if ((dbmfile = dbm_open(inst->usersfile, O_RDONLY, 0)) == NULL)
434 radlog(L_ERR|L_CONS, "cannot open dbm file %s",
436 return RLM_MODULE_FAIL;
439 r = dbm_find(dbmfile, name, request_pairs, check_pairs,
441 if (r > 0) found = 1;
442 if (r <= 0 || fallthrough(*reply_pairs)) {
444 pairdelete(reply_pairs, PW_FALL_THROUGH);
446 sprintf(buffer, "DEFAULT");
448 while ((r = dbm_find(dbmfile, buffer, request_pairs,
449 check_pairs, reply_pairs)) >= 0 || i < 2) {
452 if (!fallthrough(*reply_pairs))
454 pairdelete(reply_pairs,PW_FALL_THROUGH);
456 sprintf(buffer, "DEFAULT%d", i++);
467 * Note the fallthrough through the #endif.
471 for(pl = inst->users; pl; pl = pl->next) {
472 #ifdef WITH_USERCOLLIDE
476 * If the current entry is NOT a default,
477 * AND the name does NOT match the current entry,
478 * then skip to the next entry.
480 if ((strcmp(pl->name, "DEFAULT") != 0) &&
481 (strcmp(name, pl->name) != 0)) {
486 * If the current request matches against the
487 * check pairs, then add the reply pairs from the
488 * entry to the current list of reply pairs.
490 if ((paircmp(request_pairs, pl->check, reply_pairs) == 0)) {
491 #ifdef WITH_USERCOLLIDE
493 * We don't compare pass on default users
494 * or they never match. Oops.
496 if(strcmp(pl->name, "DEFAULT")) {
498 * We check the pass as a config
499 * item with user collisions Most
500 * of this is stolen out of
501 * rad_check_password()
503 if ((auth_type_pair = pairfind(pl->check, PW_AUTHTYPE)) != NULL) {
504 auth_type = auth_type_pair->lvalue;
505 DEBUG2(" file_auth (Usercollide): auth_type %d", auth_type);
508 /* Find pass in the REQ */
509 auth_item = request->password;
510 if (auth_item == NULL) {
511 DEBUG2(" file_auth (Usercollide): No password in the request");
512 return RLM_MODULE_OK;
515 /* Find the password from the users file. */
516 if ((password_pair = pairfind(pl->check, PW_CRYPT_PASSWORD)) != NULL)
517 auth_type = PW_AUTHTYPE_CRYPT;
519 password_pair = pairfind(pl->check, PW_PASSWORD);
522 case PW_AUTHTYPE_CRYPT:
523 DEBUG2(" file_auth (Usercollide): Checking Crypt");
524 if (password_pair == NULL) {
525 result = auth_item->strvalue ? 0 : 1;
528 if (strcmp(password_pair->strvalue,
529 crypt(auth_item->strvalue,
530 password_pair->strvalue)) != 0)
533 case PW_AUTHTYPE_LOCAL:
534 DEBUG2(" file_auth (Usercollide): Checking Local");
535 if (auth_item->attribute != PW_CHAP_PASSWORD) {
536 if (password_pair == NULL ||
537 strcmp(password_pair->strvalue,
538 auth_item->strvalue)!=0)
542 case PW_AUTHTYPE_ACCEPT:
546 } /* switch(auth_type) */
551 DEBUG2(" users: Matched %s at %d", pl->name, pl->lineno);
553 check_tmp = paircopy(pl->check);
554 reply_tmp = paircopy(pl->reply);
555 pairmove(reply_pairs, &reply_tmp);
556 pairmove(check_pairs, &check_tmp);
558 pairfree(check_tmp); /* should be NULL */
562 if (!fallthrough(pl->reply))
564 #ifdef WITH_USERCOLLIDE
571 * See if we succeeded. If we didn't find the user,
572 * then exit from the module.
575 return RLM_MODULE_NOTFOUND;
578 * Add the port number to the Framed-IP-Address if
579 * vp->addport is set, or if the Add-Port-To-IP-Address
582 * FIXME: this should not happen here, but
583 * after module_authorize in the main code!
585 if ((tmp = pairfind(*reply_pairs, PW_FRAMED_IP_ADDRESS)) != NULL) {
588 tmp2 = pairfind(*reply_pairs, PW_ADD_PORT_TO_IP_ADDRESS);
589 if (tmp->addport || (tmp2 && tmp2->lvalue)) {
590 tmp->lvalue = htonl(ntohl(tmp->lvalue) + nas_port);
593 pairdelete(reply_pairs, PW_ADD_PORT_TO_IP_ADDRESS);
597 * Remove server internal parameters.
599 pairdelete(reply_pairs, PW_FALL_THROUGH);
601 return RLM_MODULE_OK;
605 * Authentication - unused.
607 static int file_authenticate(void *instance, REQUEST *request)
611 return RLM_MODULE_OK;
615 * Pre-Accounting - read the acct_users file for check_items and
616 * config_items. Reply items are Not Recommended(TM) in acct_users,
617 * except for Fallthrough, which should work
619 * This function is mostly a copy of file_authorize
621 static int file_preacct(void *instance, REQUEST *request)
623 VALUE_PAIR *namepair;
625 VALUE_PAIR *request_pairs;
626 VALUE_PAIR **config_pairs;
627 VALUE_PAIR *reply_pairs = NULL;
628 VALUE_PAIR *check_tmp;
629 VALUE_PAIR *reply_tmp;
632 #if defined(WITH_DBM) || defined(WITH_NDBM)
636 struct file_instance *inst = instance;
638 namepair = request->username;
639 name = namepair ? (char *) namepair->strvalue : "NONE";
640 request_pairs = request->packet->vps;
641 config_pairs = &request->config_items;
644 * Find the entry for the user.
646 #if defined(WITH_DBM) || defined(WITH_NDBM)
648 * FIXME: move to rlm_dbm.c
652 * FIXME: No Prefix / Suffix support for DBM.
655 if (dbminit(inst->acctusersfile) != 0)
658 if ((dbmfile = dbm_open(inst->acctusersfile, O_RDONLY, 0)) == NULL)
661 radlog(L_ERR|L_CONS, "cannot open dbm file %s",
663 return RLM_MODULE_FAIL;
666 r = dbm_find(dbmfile, name, request_pairs, config_pairs,
668 if (r > 0) found = 1;
669 if (r <= 0 || fallthrough(*reply_pairs)) {
671 pairdelete(reply_pairs, PW_FALL_THROUGH);
673 sprintf(buffer, "DEFAULT");
675 while ((r = dbm_find(dbmfile, buffer, request_pairs,
676 config_pairs, &reply_pairs)) >= 0 || i < 2) {
679 if (!fallthrough(*reply_pairs))
681 pairdelete(reply_pairs,PW_FALL_THROUGH);
683 sprintf(buffer, "DEFAULT%d", i++);
694 * Note the fallthrough through the #endif.
698 for(pl = inst->acctusers; pl; pl = pl->next) {
700 if (strcmp(name, pl->name) && strcmp(pl->name, "DEFAULT"))
703 if (paircmp(request_pairs, pl->check, &reply_pairs) == 0) {
704 DEBUG2(" acct_users: Matched %s at %d",
705 pl->name, pl->lineno);
707 check_tmp = paircopy(pl->check);
708 reply_tmp = paircopy(pl->reply);
709 pairmove(&reply_pairs, &reply_tmp);
710 pairmove(config_pairs, &check_tmp);
712 pairfree(check_tmp); /* should be NULL */
716 if (!fallthrough(pl->reply))
722 * See if we succeeded.
725 return RLM_MODULE_NOOP; /* on to the next module */
728 * FIXME: log a warning if there are any reply items other than
731 pairfree(reply_pairs); /* Don't need these */
733 return RLM_MODULE_OK;
739 static int file_detach(void *instance)
741 struct file_instance *inst = instance;
742 pairlist_free(&inst->users);
743 pairlist_free(&inst->acctusers);
744 free(inst->usersfile);
745 free(inst->acctusersfile);
751 /* globally exported name */
752 module_t rlm_files = {
754 0, /* type: reserved */
755 file_init, /* initialization */
756 file_instantiate, /* instantiation */
757 file_authorize, /* authorization */
758 file_authenticate, /* authentication */
759 file_preacct, /* preaccounting */
760 NULL, /* accounting */
761 file_detach, /* detach */