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/types.h>
14 #include <sys/socket.h>
17 #include <netinet/in.h>
46 struct file_instance {
61 #if defined(WITH_DBM) || defined(WITH_NDBM)
63 * See if a potential DBM file is present.
65 static int checkdbm(char *users, char *ext)
70 strcpy(buffer, users);
73 return stat(buffer, &st);
77 * Find the named user in the DBM user database.
78 * Returns: -1 not found
79 * 0 found but doesn't match.
80 * 1 found and matches.
82 static int dbm_find(DBM *dbmfile, char *name, VALUE_PAIR *request_pairs,
83 VALUE_PAIR **check_pairs, VALUE_PAIR **reply_pairs)
88 VALUE_PAIR *check_tmp;
89 VALUE_PAIR *reply_tmp;
93 named.dsize = strlen(name);
95 contentd = fetch(named);
98 contentd = dbm_fetch(dbmfile, named);
100 if(contentd.dptr == NULL)
107 * Parse the check values
110 contentd.dptr[contentd.dsize] = '\0';
112 if (*ptr != '\n' && userparse(ptr, &check_tmp) != 0) {
113 radlog(L_ERR|L_CONS, "Parse error (check) for user %s", name);
117 while(*ptr != '\n' && *ptr != '\0') {
121 radlog(L_ERR|L_CONS, "Parse error (no reply pairs) for user %s",
129 * Parse the reply values
131 if (userparse(ptr, &reply_tmp) != 0) {
132 radlog(L_ERR|L_CONS, "Parse error (reply) for user %s", name);
139 * See if the check_pairs match.
141 if (paircmp(request_pairs, check_tmp, reply_pairs) == 0) {
143 pairmove(reply_pairs, &reply_tmp);
144 pairmove2(reply_pairs, &reply_tmp, PW_FALL_THROUGH);
145 pairmove(check_pairs, &check_tmp);
155 * See if a VALUE_PAIR list contains Fall-Through = Yes
157 static int fallthrough(VALUE_PAIR *vp)
161 tmp = pairfind(vp, PW_FALL_THROUGH);
163 return tmp ? tmp->lvalue : 0;
167 * USE_DYNAMIC LOGS is set to FALSE, as this code should be rewritten.
169 #define USER_DYNAMIC_LOGS 0
172 #define DL_FLAG_START 1
173 #define DL_FLAG_STOP 2
174 #define DL_FLAG_ACCT_ON 4
175 #define DL_FLAG_ACCT_OFF 8
176 #define DL_FLAG_ALIVE 16
178 typedef struct dyn_log {
186 static DYN_LOG logcfg[MAX_LOGS];
190 * Initialize dynamic logging
192 static void file_getline(FILE *f,char * buff,int len)
205 while (tmp[i] != '\n') {
212 static void file_dynamic_log_init(void)
217 sprintf(fn,"%s/%s",radius_dir,"rlm_files_log.cfg");
221 log_debug("Loading %s",fn);
222 while (logcnt < MAX_LOGS) {
223 file_getline(f,logcfg[logcnt].dir,sizeof(logcfg[logcnt].dir));
224 file_getline(f,logcfg[logcnt].fname,sizeof(logcfg[logcnt].fname));
225 file_getline(f,logcfg[logcnt].fmt,sizeof(logcfg[logcnt].fmt));
226 file_getline(f,logcfg[logcnt].mode,sizeof(logcfg[logcnt].mode));
227 file_getline(f,fn,sizeof(fn));
228 logcfg[logcnt].flags = atoi(fn);
229 if ((logcfg[logcnt].flags != 0) &&
230 (strlen(logcfg[logcnt].mode) != 0)) {
236 log_debug("%d logs configured",logcnt);
239 log_debug("Error loading %s: %s",fn, strerror(errno));
247 static int file_init(void)
250 file_dynamic_log_init();
257 * A temporary holding area for config values to be extracted
258 * into, before they are copied into the instance data
260 static struct file_instance config;
262 static CONF_PARSER module_config[] = {
263 { "usersfile", PW_TYPE_STRING_PTR, &config.usersfile, RADIUS_USERS },
264 { "acctusersfile", PW_TYPE_STRING_PTR, &config.acctusersfile, RADIUS_ACCT_USERS },
265 { "detailperm", PW_TYPE_INTEGER, &config.detailperm, "0600" },
266 { "compat", PW_TYPE_STRING_PTR, &config.compat_mode, "cistron" },
267 { NULL, -1, NULL, NULL }
270 static int getusersfile(const char *filename, PAIR_LIST **pair_list)
273 PAIR_LIST *users = NULL;
274 #if defined(WITH_DBM) || defined(WITH_NDBM)
276 (checkdbm(filename, ".dir") == 0 ||
277 checkdbm(filename, ".db") == 0)) {
278 radlog(L_INFO|L_CONS, "DBM files found but no -b flag " "given - NOT using DBM");
283 rcode = pairlist_read(filename, &users, 1);
290 * Walk through the 'users' file list, if we're debugging,
291 * or if we're in compat_mode.
294 (strcmp(config.compat_mode, "cistron") == 0)) {
297 int compat_mode = FALSE;
299 if (strcmp(config.compat_mode, "cistron") == 0) {
306 DEBUG("[%s]:%d Cistron compatibility checks for entry %s ...",
307 filename, entry->lineno,
312 * Look for improper use of '=' in the
313 * check items. They should be using
314 * '==' for on-the-wire RADIUS attributes,
315 * and probably ':=' for server
316 * configuration items.
318 for (vp = entry->check; vp != NULL; vp = vp->next) {
320 * Ignore attributes which are set
323 if (vp->operator != T_OP_EQ) {
328 * If it's a vendor attribute,
329 * or it's a wire protocol,
330 * ensure it has '=='.
332 if (((vp->attribute & ~0xffff) != 0) ||
333 (vp->attribute < 0x100)) {
335 DEBUG("[%s]:%d WARNING! Changing '%s =' to '%s =='\n\tfor comparing RADIUS attribute in check item list for user %s",
336 filename, entry->lineno,
340 DEBUG("\tChanging '%s =' to '%s =='",
343 vp->operator = T_OP_CMP_EQ;
348 * Cistron Compatibility mode.
350 * Re-write selected attributes
351 * to be '+=', instead of '='.
353 * All others get set to '=='
357 * Non-wire attributes become +=
359 * On the write attributes
362 if ((vp->attribute >= 0x100) &&
363 (vp->attribute <= 0xffff) &&
364 (vp->attribute != PW_HINT) &&
365 (vp->attribute != PW_HUNTGROUP_NAME)) {
366 DEBUG("\tChanging '%s =' to '%s +='",
368 vp->operator = T_OP_ADD;
370 DEBUG("\tChanging '%s =' to '%s =='",
372 vp->operator = T_OP_CMP_EQ;
376 } /* end of loop over check items */
380 * Look for server configuration items
383 * It's a common enough mistake, that it's
386 for (vp = entry->reply; vp != NULL; vp = vp->next) {
388 * If it's NOT a vendor attribute,
389 * and it's NOT a wire protocol
390 * and we ignore Fall-Through,
391 * then bitch about it, giving a
392 * good warning message.
394 if (!(vp->attribute & ~0xffff) &&
395 (vp->attribute > 0xff) &&
396 (vp->attribute > 1000)) {
397 log_debug("[%s]:%d WARNING! Check item \"%s\"\n"
398 "\tfound in reply item list for user \"%s\".\n"
399 "\tThis attribute MUST go on the first line"
400 " with the other check items",
401 filename, entry->lineno, vp->name,
416 * (Re-)read the "users" file into memory.
418 static int file_instantiate(CONF_SECTION *conf, void **instance)
420 struct file_instance *inst;
423 inst = malloc(sizeof *inst);
425 radlog(L_ERR|L_CONS, "Out of memory\n");
429 if (cf_section_parse(conf, module_config) < 0) {
434 inst->detailperm = config.detailperm;
435 inst->usersfile = config.usersfile;
436 inst->acctusersfile = config.acctusersfile;
437 config.usersfile = NULL;
438 config.acctusersfile = NULL;
440 rcode = getusersfile(inst->usersfile, &inst->users);
442 radlog(L_ERR|L_CONS, "Errors reading %s", inst->usersfile);
443 free(inst->usersfile);
444 free(inst->acctusersfile);
449 rcode = getusersfile(inst->acctusersfile, &inst->acctusers);
451 radlog(L_ERR|L_CONS, "Errors reading %s", inst->acctusersfile);
452 pairlist_free(&inst->users);
453 free(inst->usersfile);
454 free(inst->acctusersfile);
464 * Find the named user in the database. Create the
465 * set of attribute-value pairs to check and reply with
466 * for this user from the database. The main code only
467 * needs to check the password, the rest is done here.
469 static int file_authorize(void *instance, REQUEST *request)
472 VALUE_PAIR *namepair;
473 VALUE_PAIR *request_pairs;
474 VALUE_PAIR *check_tmp;
475 VALUE_PAIR *reply_tmp;
479 #if defined(WITH_DBM) || defined(WITH_NDBM)
484 struct file_instance *inst = instance;
485 #ifdef WITH_USERCOLLIDE
486 VALUE_PAIR *auth_type_pair;
487 VALUE_PAIR *password_pair;
488 VALUE_PAIR *auth_item;
492 VALUE_PAIR **check_pairs, **reply_pairs;
495 request_pairs = request->packet->vps;
496 check_pairs = &request->config_items;
497 reply_pairs = &request->reply->vps;
500 * Grab the canonical user name.
502 namepair = request->username;
503 name = namepair ? (char *) namepair->strvalue : "NONE";
506 * Find the NAS port ID.
508 if ((tmp = pairfind(request_pairs, PW_NAS_PORT_ID)) != NULL)
509 nas_port = tmp->lvalue;
512 * Find the entry for the user.
514 #if defined(WITH_DBM) || defined(WITH_NDBM)
516 * FIXME: move to rlm_dbm.c
520 * FIXME: No Prefix / Suffix support for DBM.
523 if (dbminit(inst->usersfile) != 0)
526 if ((dbmfile = dbm_open(inst->usersfile, O_RDONLY, 0)) == NULL)
529 radlog(L_ERR|L_CONS, "cannot open dbm file %s",
531 return RLM_MODULE_FAIL;
534 r = dbm_find(dbmfile, name, request_pairs, check_pairs,
536 if (r > 0) found = 1;
537 if (r <= 0 || fallthrough(*reply_pairs)) {
539 pairdelete(reply_pairs, PW_FALL_THROUGH);
541 sprintf(buffer, "DEFAULT");
543 while ((r = dbm_find(dbmfile, buffer, request_pairs,
544 check_pairs, reply_pairs)) >= 0 || i < 2) {
547 if (!fallthrough(*reply_pairs))
549 pairdelete(reply_pairs,PW_FALL_THROUGH);
551 sprintf(buffer, "DEFAULT%d", i++);
562 * Note the fallthrough through the #endif.
566 for(pl = inst->users; pl; pl = pl->next) {
567 #ifdef WITH_USERCOLLIDE
571 * If the current entry is NOT a default,
572 * AND the name does NOT match the current entry,
573 * then skip to the next entry.
575 if ((strcmp(pl->name, "DEFAULT") != 0) &&
576 (strcmp(name, pl->name) != 0)) {
581 * If the current request matches against the
582 * check pairs, then add the reply pairs from the
583 * entry to the current list of reply pairs.
585 #ifdef WITH_USERCOLLIDE
586 if ((paircmp(request_pairs, pl->check, reply_pairs) == 0)) {
588 * We don't compare pass on default users
589 * or they never match. Oops.
591 if(strcmp(pl->name, "DEFAULT")) {
593 * We check the pass as a config
594 * item with user collisions Most
595 * of this is stolen out of
596 * rad_check_password()
598 if ((auth_type_pair = pairfind(pl->check, PW_AUTHTYPE)) != NULL) {
599 auth_type = auth_type_pair->lvalue;
600 DEBUG2(" file_auth (Usercollide): auth_type %d", auth_type);
603 /* Find pass in the REQ */
604 auth_item = request->password;
605 if (auth_item == NULL) {
606 DEBUG2(" file_auth (Usercollide): No password in the request");
607 return RLM_MODULE_OK;
610 /* Find the password from the users file. */
611 if ((password_pair = pairfind(pl->check, PW_CRYPT_PASSWORD)) != NULL)
612 auth_type = PW_AUTHTYPE_CRYPT;
614 password_pair = pairfind(pl->check, PW_PASSWORD);
617 case PW_AUTHTYPE_CRYPT:
618 DEBUG2(" file_auth (Usercollide): Checking Crypt");
619 if (password_pair == NULL) {
620 result = auth_item->strvalue ? 0 : 1;
623 if (strcmp(password_pair->strvalue,
624 crypt(auth_item->strvalue,
625 password_pair->strvalue)) != 0)
628 case PW_AUTHTYPE_LOCAL:
629 DEBUG2(" file_auth (Usercollide): Checking Local");
630 if (auth_item->attribute != PW_CHAP_PASSWORD) {
631 if (password_pair == NULL ||
632 strcmp(password_pair->strvalue,
633 auth_item->strvalue)!=0)
637 case PW_AUTHTYPE_ACCEPT:
641 } /* switch(auth_type) */
646 DEBUG2(" users: Matched %s at %d", pl->name, pl->lineno);
648 check_tmp = paircopy(pl->check);
649 reply_tmp = paircopy(pl->reply);
650 pairmove(reply_pairs, &reply_tmp);
651 pairmove(check_pairs, &check_tmp);
653 pairfree(check_tmp); /* should be NULL */
657 if (!fallthrough(pl->reply))
659 #ifdef WITH_USERCOLLIDE
666 * See if we succeeded. If we didn't find the user,
667 * then exit from the module.
670 return RLM_MODULE_OK;
673 * Add the port number to the Framed-IP-Address if
674 * vp->addport is set, or if the Add-Port-To-IP-Address
677 * FIXME: this should not happen here, but
678 * after module_authorize in the main code!
680 if ((tmp = pairfind(*reply_pairs, PW_FRAMED_IP_ADDRESS)) != NULL) {
683 tmp2 = pairfind(*reply_pairs, PW_ADD_PORT_TO_IP_ADDRESS);
684 if (tmp->addport || (tmp2 && tmp2->lvalue)) {
685 tmp->lvalue = htonl(ntohl(tmp->lvalue) + nas_port);
688 pairdelete(reply_pairs, PW_ADD_PORT_TO_IP_ADDRESS);
692 * Remove server internal parameters.
694 pairdelete(reply_pairs, PW_FALL_THROUGH);
696 return RLM_MODULE_OK;
700 * Authentication - unused.
702 static int file_authenticate(void *instance, REQUEST *request)
706 return RLM_MODULE_OK;
711 * Write the dynamic log files
713 static void file_write_dynamic_log(REQUEST * request)
721 pair = pairfind(request->packet->vps,PW_ACCT_STATUS_TYPE);
722 for (x = 0; x < logcnt; x++) {
723 if (((pair->lvalue == PW_STATUS_START) && (logcfg[x].flags & DL_FLAG_START)) ||
724 ((pair->lvalue == PW_STATUS_STOP) && (logcfg[x].flags & DL_FLAG_STOP)) ||
725 ((pair->lvalue == PW_STATUS_ACCOUNTING_ON) && (logcfg[x].flags & DL_FLAG_ACCT_ON)) ||
726 ((pair->lvalue == PW_STATUS_ACCOUNTING_OFF) && (logcfg[x].flags & DL_FLAG_ACCT_OFF)) ||
727 ((pair->lvalue == PW_STATUS_ALIVE) && (logcfg[x].flags & DL_FLAG_ALIVE))) {
728 y = radius_xlat2(fn,sizeof(fn),logcfg[x].dir,request,request->packet->vps);
729 (void) mkdir(fn, 0755);
732 /* FIXME must get the reply packet */
733 radius_xlat2(&fn[y],sizeof(fn)-y,logcfg[x].fname,request,request->packet->vps);
734 if (strcasecmp(logcfg[x].mode,"d") == 0) {
738 f = popen(&fn[y+1],logcfg[x].mode);
740 /* FIXME: permissions? */
741 f = fopen(fn,logcfg[x].mode);
744 /* FIXME must get the reply packet */
745 radius_xlat2(buffer,sizeof(buffer),logcfg[x].fmt,request,request->packet->vps);
746 fprintf(f,"%s\n",buffer);
754 log_debug("Error opening pipe %s",fn[y+1]);
756 log_debug("Error opening log %s",fn);
765 #endif /* USE_DYNAMIC_LOGS */
768 * Pre-Accounting - read the acct_users file for check_items and
769 * config_items. Reply items are Not Recommended(TM) in acct_users,
770 * except for Fallthrough, which should work
772 * This function is mostly a copy of file_authorize
774 static int file_preacct(void *instance, REQUEST *request)
776 VALUE_PAIR *namepair;
778 VALUE_PAIR *request_pairs;
779 VALUE_PAIR **config_pairs;
780 VALUE_PAIR *reply_pairs = NULL;
781 VALUE_PAIR *check_tmp;
782 VALUE_PAIR *reply_tmp;
785 #if defined(WITH_DBM) || defined(WITH_NDBM)
789 struct file_instance *inst = instance;
791 namepair = request->username;
792 name = namepair ? (char *) namepair->strvalue : "NONE";
793 request_pairs = request->packet->vps;
794 config_pairs = &request->config_items;
797 * Find the entry for the user.
799 #if defined(WITH_DBM) || defined(WITH_NDBM)
801 * FIXME: move to rlm_dbm.c
805 * FIXME: No Prefix / Suffix support for DBM.
808 if (dbminit(inst->acctusersfile) != 0)
811 if ((dbmfile = dbm_open(inst->acctusersfile, O_RDONLY, 0)) == NULL)
814 radlog(L_ERR|L_CONS, "cannot open dbm file %s",
816 return RLM_MODULE_FAIL;
819 r = dbm_find(dbmfile, name, request_pairs, config_pairs,
821 if (r > 0) found = 1;
822 if (r <= 0 || fallthrough(*reply_pairs)) {
824 pairdelete(reply_pairs, PW_FALL_THROUGH);
826 sprintf(buffer, "DEFAULT");
828 while ((r = dbm_find(dbmfile, buffer, request_pairs,
829 config_pairs, &reply_pairs)) >= 0 || i < 2) {
832 if (!fallthrough(*reply_pairs))
834 pairdelete(reply_pairs,PW_FALL_THROUGH);
836 sprintf(buffer, "DEFAULT%d", i++);
847 * Note the fallthrough through the #endif.
851 for(pl = inst->acctusers; pl; pl = pl->next) {
853 if (strcmp(name, pl->name) && strcmp(pl->name, "DEFAULT"))
856 if (paircmp(request_pairs, pl->check, &reply_pairs) == 0) {
857 DEBUG2(" acct_users: Matched %s at %d",
858 pl->name, pl->lineno);
860 check_tmp = paircopy(pl->check);
861 reply_tmp = paircopy(pl->reply);
862 pairmove(&reply_pairs, &reply_tmp);
863 pairmove(config_pairs, &check_tmp);
865 pairfree(check_tmp); /* should be NULL */
869 if (!fallthrough(pl->reply))
875 * See if we succeeded.
878 return RLM_MODULE_OK; /* on to the next module */
881 * FIXME: log a warning if there are any reply items other than
884 pairfree(reply_pairs); /* Don't need these */
886 return RLM_MODULE_OK;
890 * Accounting - write the detail files.
892 static int file_accounting(void *instance, REQUEST *request)
902 int ret = RLM_MODULE_OK;
905 struct file_instance *inst = instance;
908 * See if we have an accounting directory. If not,
911 if (stat(radacct_dir, &st) < 0) {
912 DEBUG("No accounting directory %s", radacct_dir);
913 return RLM_MODULE_OK;
918 * Find out the name of this terminal server. We try
919 * to find the PW_NAS_IP_ADDRESS in the naslist file.
920 * If that fails, we look for the originating address.
921 * Only if that fails we resort to a name lookup.
924 nas = request->packet->src_ipaddr;
925 if ((pair = pairfind(request->packet->vps, PW_NAS_IP_ADDRESS)) != NULL)
927 if (request->proxy && request->proxy->src_ipaddr)
928 nas = request->proxy->src_ipaddr;
930 if ((cl = nas_find(nas)) != NULL) {
931 if (cl->shortname[0])
932 strcpy(nasname, cl->shortname);
934 strcpy(nasname, cl->longname);
938 ip_hostname(nasname, sizeof(nasname), nas);
942 * Create a directory for this nas.
944 sprintf(buffer, "%s/%s", radacct_dir, nasname);
945 (void) mkdir(buffer, 0755);
950 sprintf(buffer, "%s/%s/%s", radacct_dir, nasname, "detail");
951 if ((outfd = open(buffer, O_WRONLY|O_APPEND|O_CREAT,
952 inst->detailperm)) < 0) {
953 radlog(L_ERR, "Acct: Couldn't open file %s", buffer);
954 ret = RLM_MODULE_FAIL;
955 } else if ((outfp = fdopen(outfd, "a")) == NULL) {
956 radlog(L_ERR, "Acct: Couldn't open file %s: %s",
957 buffer, strerror(errno));
958 ret = RLM_MODULE_FAIL;
962 /* Post a timestamp */
963 fputs(ctime(&curtime), outfp);
965 /* Write each attribute/value to the log file */
966 pair = request->packet->vps;
968 if (pair->attribute != PW_PASSWORD) {
970 fprint_attr_val(outfp, pair);
977 * Add non-protocol attibutes.
979 fprintf(outfp, "\tTimestamp = %ld\n", curtime);
980 if (request->packet->verified)
981 fputs("\tRequest-Authenticator = Verified\n", outfp);
983 fputs("\tRequest-Authenticator = None\n", outfp);
989 file_write_dynamic_log(request);
990 #endif /* USE_DYNAMIC_LOGS */
998 static int file_detach(void *instance)
1000 struct file_instance *inst = instance;
1001 pairlist_free(&inst->users);
1002 pairlist_free(&inst->acctusers);
1003 free(inst->usersfile);
1004 free(inst->acctusersfile);
1010 /* globally exported name */
1011 module_t rlm_files = {
1013 0, /* type: reserved */
1014 file_init, /* initialization */
1015 file_instantiate, /* instantiation */
1016 file_authorize, /* authorization */
1017 file_authenticate, /* authentication */
1018 file_preacct, /* preaccounting */
1019 file_accounting, /* accounting */
1020 file_detach, /* detach */