I had to patch rlm_detail.c so that our auth and accounting logs are sent to a central syslog server. We have about two dozen radius servers around the world, so auditing their access is painful. I didn't know if anyone would be interested in putting this patch into the mainstream release, but I thought I'd toss it out there. If nobody is interested, then I'll just maintain it internally for us. This adds the following configuration options to rlm_digest: detailfile = syslog - If set to syslog, use syslog. Otherwise it's a file hidepasswd = {yes,no} - Replace the user's User-Password value with (hidden). logfacility = <string> - Should be a syslog facility such as auth or local0. loglevel = <string> - Should be a syslog level such as notice or info. logname = <string> - Whatever you want to log as (default is "radiusd"). logextra = <string> - Comma-seperated list of additional attributes to log (since syslog isn't breaking it apart by directory). ie. "Client-IP-Address = %{Client-IP-Address},NAS-Port=%{NAS-Port}" In order to avoid duplicating code, rlm_detail now builds a string and then prints it or syslog's it, rather than printing as it goes along. Anyhow, here goes: --- src/modules/rlm_detail/rlm_detail.c.orig 2006-02-27 15:03:45.000000000 -0500 +++ src/modules/rlm_detail/rlm_detail.c 2006-02-27 15:02:49.000000000 -0500 @@ -37,6 +37,14 @@ #include "modules.h" #define DIRLEN 8192 +#ifdef HAVE_SYSLOG_H +#define BUFSIZE sizeof(char)*1024 +#define SYSLOG_NAMES +#include <syslog.h> +static int pencode(char *); +static int decode(char *, CODE *); +#endif + static const char *packet_codes[] = { "", "Access-Request", @@ -68,6 +76,21 @@ /* if we want file locking */ int locking; + + /* If logging full info, do we want to hide the password? */ + char *hidepasswd; + + /* Special info we want logged */ + char *logextra; + + /* Syslog facility */ + char *logfacility; + + /* Syslog level */ + char *loglevel; + + /* Type/Name of log message app */ + char *logname; }; static CONF_PARSER module_config[] = { @@ -79,6 +102,16 @@ offsetof(struct detail_instance,dirperm), NULL, "0755" }, { "locking", PW_TYPE_BOOLEAN, offsetof(struct detail_instance,locking), NULL, "no" }, + { "hidepasswd", PW_TYPE_STRING_PTR, + offsetof(struct detail_instance,hidepasswd), NULL, "yes" }, + { "logextra", PW_TYPE_STRING_PTR, + offsetof(struct detail_instance,logextra), NULL, NULL }, + { "logfacility", PW_TYPE_STRING_PTR, + offsetof(struct detail_instance,logfacility), NULL, "local0" }, + { "loglevel", PW_TYPE_STRING_PTR, + offsetof(struct detail_instance,loglevel), NULL, "notice" }, + { "logname", PW_TYPE_STRING_PTR, + offsetof(struct detail_instance,logname), NULL, "radiusd" }, { NULL, -1, 0, NULL, NULL } }; @@ -115,6 +148,14 @@ int outfd; FILE *outfp; char buffer[DIRLEN]; + char buffer2[DIRLEN]; + char extra[DIRLEN]; + char divider[8]; + char divider2[8]; + char *result; + char *tostring; + char *splitstr; + char *buf; char *p; struct stat st; int locked; @@ -125,6 +166,7 @@ VALUE_PAIR *pair = packet->vps; struct detail_instance *inst = instance; + result = (char *)malloc(BUFSIZE); /* * Nothing to log: don't do anything. @@ -196,6 +238,138 @@ } /* else there was no directory delimiter. */ /* + * Determine formatting between attributes, based on method + */ + if (strcmp(buffer, "syslog") == 0) { + strcpy(divider, ", "); + strcpy(divider2, ", "); + } else { + strcpy(divider, "\n\t"); + strcpy(divider2, "\n"); + } + + /* + * Build the output string + */ + /* + * Print out names, if they're OK. + * Numbers, if not. + */ + strcpy(result, "Packet-Type = "); + if ((packet->code > 0) && + (packet->code <= PW_ACCESS_CHALLENGE)) { + strcat(result, packet_codes[packet->code]); + } else { + tostring = (char *)malloc(BUFSIZE); + sprintf(tostring, "%d", packet->code); + strcat(result, tostring); + free(tostring); + } + /* + * Post a timestamp + */ + if (strcmp(buffer, "syslog") != 0) { + strcat(result, divider2); + strcat(result, strtok(CTIME_R(&request->timestamp, buffer2, DIRLEN), "\n")); + } + strcat(result, divider); + + + /* Write each attribute/value to the log file */ + while (pair) { + /* + * Don't print passwords in old format... + */ + if (compat && (pair->attribute == PW_PASSWORD)) { + pair = pair->next; + continue; + } + + /* + * Print all of the attributes. + */ + buf = (char *)malloc(BUFSIZE); + vp_prints(buf, BUFSIZE, pair); + if ((strstr(buf, "User-Password") != 0) && + (strcmp(inst->hidepasswd, "yes") == 0)) { + strcat(result, "User-Password = (hidden)"); + } else { + strcat(result, buf); + } + strcat(result, divider); + pair = pair->next; + free(buf); + } + + /* + * Add non-protocol attibutes. + */ + if (compat) { + if ((pair = pairfind(request->config_items, + PW_PROXY_TO_REALM)) != NULL) { + proxy_realm = realm_find(pair->strvalue, TRUE); + if (proxy_realm) { + memset((char *) proxy_buffer, 0, 16); + ip_ntoa(proxy_buffer, proxy_realm->acct_ipaddr); + strcat(result, "Freeradius-Proxied-To = "); + strcat(result, proxy_buffer); + strcat(result, divider); + DEBUG("rlm_detail: Freeradius-Proxied-To set to %s", + proxy_buffer); + } + } + tostring = (char *)malloc(BUFSIZE); + strcat(result, "Timestamp = "); + sprintf(tostring, "%ld", (unsigned long) request->timestamp); + strcat(result, tostring); + strcat(result, divider); + free(tostring); + + if (request->packet->verified == 2) { + strcat(result, "Request-Authenticator = Verified"); + strcat(result, divider); + } else if (request->packet->verified == 1) { + strcat(result, "Request-Authenticator = None"); + strcat(result, divider); + } + } + + if (inst->logextra != NULL) { + radius_xlat(extra, sizeof(extra), inst->logextra, request, NULL); + DEBUG2("rlm_detail: %s expands to %s", inst->logextra, extra); + splitstr = strtok(extra, ","); + while (splitstr != NULL) + { + strcat(result, splitstr); + strcat(result, divider); + splitstr = strtok(NULL, ","); + } + } + +#ifdef HAVE_SYSLOG_H + /* + * If the log file name is "syslog", then the user is + * requesting this message go to syslog, not to a local + * file. + */ + if (strcmp(buffer, "syslog") == 0) { + int level; + char facil[16]; + + strcpy(facil, inst->logfacility); + strcat(facil, "."); + strcat(facil, inst->loglevel); + level = pencode(facil); + + openlog(inst->logname, LOG_CONS | LOG_PID, (level | LOG_FACMASK)), + syslog(level, "%s", result); + closelog(); + + return RLM_MODULE_OK; + } +#endif + + /* * Open & create the file, with the given permissions. */ if ((outfd = open(buffer, O_WRONLY | O_APPEND | O_CREAT, @@ -253,74 +427,8 @@ return RLM_MODULE_FAIL; } - /* - * Write the information to the file. - */ - if (!compat) { - /* - * Print out names, if they're OK. - * Numbers, if not. - */ - if ((packet->code > 0) && - (packet->code <= PW_ACCESS_CHALLENGE)) { - fprintf(outfp, "Packet-Type = %s\n", - packet_codes[packet->code]); - } else { - fprintf(outfp, "Packet-Type = %d\n", packet->code); - } - } - - /* - * Post a timestamp - */ - fseek(outfp, 0L, SEEK_END); - fputs(CTIME_R(&request->timestamp, buffer, DIRLEN), outfp); - - /* Write each attribute/value to the log file */ - while (pair) { - /* - * Don't print passwords in old format... - */ - if (compat && (pair->attribute == PW_PASSWORD)) { - pair = pair->next; - continue; - } - - /* - * Print all of the attributes. - */ - fputs("\t", outfp); - vp_print(outfp, pair); - fputs("\n", outfp); - pair = pair->next; - } - - /* - * Add non-protocol attibutes. - */ - if (compat) { - if ((pair = pairfind(request->config_items, - PW_PROXY_TO_REALM)) != NULL) { - proxy_realm = realm_find(pair->strvalue, TRUE); - if (proxy_realm) { - memset((char *) proxy_buffer, 0, 16); - ip_ntoa(proxy_buffer, proxy_realm->acct_ipaddr); - fprintf(outfp, "\tFreeradius-Proxied-To = %s\n", - proxy_buffer); - DEBUG("rlm_detail: Freeradius-Proxied-To set to %s", - proxy_buffer); - } - } - fprintf(outfp, "\tTimestamp = %ld\n", - (unsigned long) request->timestamp); - - if (request->packet->verified == 2) - fputs("\tRequest-Authenticator = Verified\n", outfp); - else if (request->packet->verified == 1) - fputs("\tRequest-Authenticator = None\n", outfp); - } - - fputs("\n", outfp); + /* send buffered output to file */ + fprintf(outfp, "%s\n", result); if (inst->locking) { fflush(outfp); @@ -337,6 +445,52 @@ return RLM_MODULE_OK; } +/* Decode a symbolic name to a numeric value + * this function is based on code from logger + */ +static int pencode(char *s) +{ + char *save; + int lev, fac = LOG_USER; + + for (save = s; *s && *s != '.'; ++s); + if (*s) { + *s = '\0'; + fac = decode(save, facilitynames); + if (fac < 0) { + fprintf(stderr, "unknown facility name: %s\n", save); + exit(FALSE); + } + *s++ = '.'; + } else { + s = save; + } + lev = decode(s, prioritynames); + if (lev < 0) { + fprintf(stderr, "unknown priority name: %s\n", save); + exit(FALSE); + } + return ((lev & LOG_PRIMASK) | (fac & LOG_FACMASK)); +} + +/* Help decode a symbolic name to a numeric value + * this function is based on code from logger + */ +static int decode(char *name, CODE * codetab) +{ + CODE *c; + + if (isdigit(*name)) + return (atoi(name)); + for (c = codetab; c->c_name; c++) { + if (!strcasecmp(name, c->c_name)) { + return (c->c_val); + } + } + + return (-1); +} + /* * Accounting - write the detail files. */