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 VALUE_PAIR **check_pairs, **reply_pairs;
389 VALUE_PAIR *check_save;
392 request_pairs = request->packet->vps;
393 check_pairs = &request->config_items;
394 reply_pairs = &request->reply->vps;
397 * Grab the canonical user name.
399 namepair = request->username;
400 name = namepair ? (char *) namepair->strvalue : "NONE";
403 * Find the entry for the user.
405 #if defined(WITH_DBM) || defined(WITH_NDBM)
407 * FIXME: move to rlm_dbm.c
411 * FIXME: No Prefix / Suffix support for DBM.
414 if (dbminit(inst->usersfile) != 0)
417 if ((dbmfile = dbm_open(inst->usersfile, O_RDONLY, 0)) == NULL)
420 radlog(L_ERR|L_CONS, "cannot open dbm file %s",
422 return RLM_MODULE_FAIL;
425 r = dbm_find(dbmfile, name, request_pairs, check_pairs,
427 if (r > 0) found = 1;
428 if (r <= 0 || fallthrough(*reply_pairs)) {
430 pairdelete(reply_pairs, PW_FALL_THROUGH);
432 sprintf(buffer, "DEFAULT");
434 while ((r = dbm_find(dbmfile, buffer, request_pairs,
435 check_pairs, reply_pairs)) >= 0 || i < 2) {
438 if (!fallthrough(*reply_pairs))
440 pairdelete(reply_pairs,PW_FALL_THROUGH);
442 sprintf(buffer, "DEFAULT%d", i++);
453 * Note the fallthrough through the #endif.
457 for(pl = inst->users; pl; pl = pl->next) {
459 * If the current entry is NOT a default,
460 * AND the name does NOT match the current entry,
461 * then skip to the next entry.
463 if ((strcmp(pl->name, "DEFAULT") != 0) &&
464 (strcmp(name, pl->name) != 0)) {
469 * If the current request matches against the
470 * check pairs, then add the reply pairs from the
471 * entry to the current list of reply pairs.
473 if ((paircmp(request_pairs, pl->check, reply_pairs) == 0)) {
475 if((mainconfig.do_usercollide) && (strcmp(pl->name, "DEFAULT"))) {
478 * We have to make sure the password
482 /* Save the orginal config items */
483 check_save = paircopy(request->config_items);
485 /* Copy this users check pairs to the request */
486 check_tmp = paircopy(pl->check);
487 pairmove(check_pairs, &check_tmp);
490 DEBUG2(" users: Checking %s at %d", pl->name, pl->lineno);
491 /* Check the req to see if we matched */
492 if(rad_check_password(request)==0) {
493 DEBUG2(" users: Matched %s at %d", pl->name, pl->lineno);
497 /* Free our saved config items */
498 pairfree(check_save);
501 * Already copied check items, so
502 * just copy reply here
504 reply_tmp = paircopy(pl->reply);
505 pairmove(reply_pairs, &reply_tmp);
508 /* We didn't match here */
510 /* Restore check items */
511 pairfree(request->config_items);
512 request->config_items = paircopy(check_save);
513 check_pairs = &request->config_items;
520 DEBUG2(" users: Matched %s at %d", pl->name, pl->lineno);
522 check_tmp = paircopy(pl->check);
523 reply_tmp = paircopy(pl->reply);
524 pairmove(reply_pairs, &reply_tmp);
525 pairmove(check_pairs, &check_tmp);
527 pairfree(check_tmp); /* should be NULL */
532 if (!fallthrough(pl->reply))
538 * See if we succeeded. If we didn't find the user,
539 * then exit from the module.
542 return RLM_MODULE_NOTFOUND;
545 * Remove server internal parameters.
547 pairdelete(reply_pairs, PW_FALL_THROUGH);
549 return RLM_MODULE_OK;
553 * Authentication - unused.
555 static int file_authenticate(void *instance, REQUEST *request)
559 return RLM_MODULE_OK;
563 * Pre-Accounting - read the acct_users file for check_items and
564 * config_items. Reply items are Not Recommended(TM) in acct_users,
565 * except for Fallthrough, which should work
567 * This function is mostly a copy of file_authorize
569 static int file_preacct(void *instance, REQUEST *request)
571 VALUE_PAIR *namepair;
573 VALUE_PAIR *request_pairs;
574 VALUE_PAIR **config_pairs;
575 VALUE_PAIR *reply_pairs = NULL;
576 VALUE_PAIR *check_tmp;
577 VALUE_PAIR *reply_tmp;
580 #if defined(WITH_DBM) || defined(WITH_NDBM)
584 struct file_instance *inst = instance;
586 namepair = request->username;
587 name = namepair ? (char *) namepair->strvalue : "NONE";
588 request_pairs = request->packet->vps;
589 config_pairs = &request->config_items;
592 * Find the entry for the user.
594 #if defined(WITH_DBM) || defined(WITH_NDBM)
596 * FIXME: move to rlm_dbm.c
600 * FIXME: No Prefix / Suffix support for DBM.
603 if (dbminit(inst->acctusersfile) != 0)
606 if ((dbmfile = dbm_open(inst->acctusersfile, O_RDONLY, 0)) == NULL)
609 radlog(L_ERR|L_CONS, "cannot open dbm file %s",
611 return RLM_MODULE_FAIL;
614 r = dbm_find(dbmfile, name, request_pairs, config_pairs,
616 if (r > 0) found = 1;
617 if (r <= 0 || fallthrough(*reply_pairs)) {
619 pairdelete(reply_pairs, PW_FALL_THROUGH);
621 sprintf(buffer, "DEFAULT");
623 while ((r = dbm_find(dbmfile, buffer, request_pairs,
624 config_pairs, &reply_pairs)) >= 0 || i < 2) {
627 if (!fallthrough(*reply_pairs))
629 pairdelete(reply_pairs,PW_FALL_THROUGH);
631 sprintf(buffer, "DEFAULT%d", i++);
642 * Note the fallthrough through the #endif.
646 for(pl = inst->acctusers; pl; pl = pl->next) {
648 if (strcmp(name, pl->name) && strcmp(pl->name, "DEFAULT"))
651 if (paircmp(request_pairs, pl->check, &reply_pairs) == 0) {
652 DEBUG2(" acct_users: Matched %s at %d",
653 pl->name, pl->lineno);
655 check_tmp = paircopy(pl->check);
656 reply_tmp = paircopy(pl->reply);
657 pairmove(&reply_pairs, &reply_tmp);
658 pairmove(config_pairs, &check_tmp);
660 pairfree(check_tmp); /* should be NULL */
664 if (!fallthrough(pl->reply))
670 * See if we succeeded.
673 return RLM_MODULE_NOOP; /* on to the next module */
676 * FIXME: log a warning if there are any reply items other than
679 pairfree(reply_pairs); /* Don't need these */
681 return RLM_MODULE_OK;
687 static int file_detach(void *instance)
689 struct file_instance *inst = instance;
690 pairlist_free(&inst->users);
691 pairlist_free(&inst->acctusers);
692 free(inst->usersfile);
693 free(inst->acctusersfile);
699 /* globally exported name */
700 module_t rlm_files = {
702 0, /* type: reserved */
703 file_init, /* initialization */
704 file_instantiate, /* instantiation */
705 file_authorize, /* authorization */
706 file_authenticate, /* authentication */
707 file_preacct, /* preaccounting */
708 NULL, /* accounting */
709 NULL, /* checksimul */
710 file_detach, /* detach */