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. */
Geoff Silver <geoff+freeradius@uslinux.net> wrote:
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.
radrelay? Have a central RADIUS server that does nothing but accounting, and use radrelay to send the data there. That server can be made to do syslog (if you care). But I wouldn't suggest anyone use syslog like this.
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.
Many syslog servers have size limitations on the strings they can handle. They may not be able to take a full RADIUS attribute, much less a list of RADIUS attributes in one string. Alan DeKok.
Alan DeKok wrote:
Geoff Silver <geoff+freeradius@uslinux.net> wrote:
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.
radrelay? Have a central RADIUS server that does nothing but accounting, and use radrelay to send the data there. That server can be made to do syslog (if you care).
But I wouldn't suggest anyone use syslog like this.
There are a couple issues with relaying our accounting logs. One is security (it's far easier for me to get firewall holes open from India or Brzail for syslog than for radius - actually, we already have these open) and another (more importantly) is redundancy (using syslog lets me use syslog-ng for instance, which tcp-based is far less likely to drop records). I presume it's possible to have radrelay send to multiple end radiusd's? That's something I know syslog can do for us. Also a plus, the patch lets me relay all auth failures and successes, so we can monitor for brute force password cracking and do real-time trending. I certainly wasn't trying to impose syslog on people, but I know I've seen discussions about patching radiusd or writing rlm's to do syslog previously on the mailing lists, so obviously people *want* to do this. I'd be happy to make some changes to it if it would help get it accepted, but if what I'm hearing is "no freakin' way" then I'll just maintain the patch seperately. For us, it's important to get this data into a syslog feed so that we can leverage the tools that already exist across our organization. Yes, we might truncate a few messages, but overall it's a huge win because nobody has the time or resources to write tools to index and parse terrabytes of radius accounting logs every month or watch them in real-time, yet we *have* tools designed to do just that.
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.
Many syslog servers have size limitations on the strings they can handle. They may not be able to take a full RADIUS attribute, much less a list of RADIUS attributes in one string.
Alan DeKok.
I did consider that, though in testing I didn't have any issues on Linux. I do agree for it to be right, it would probably need to split the message based on the max syslog message size if necessary, though I wasn't sure what that max message size should be.
Geoff Silver <geoff+freeradius@uslinux.net> wrote:
I certainly wasn't trying to impose syslog on people, but I know I've seen discussions about patching radiusd or writing rlm's to do syslog previously on the mailing lists, so obviously people *want* to do this. I'd be happy to make some changes to it if it would help get it accepted, but if what I'm hearing is "no freakin' way" then I'll just maintain the patch seperately.
I'd be much happier if the syslog functionality was in a separate module, that didn't touch the "detail" module. The functionality is really quite separate, even if there is some overlap in the code. I'd be OK with adding a "rlm_syslog" to the server, especially if it could log authentication success/fail, etc. in place of what's currently in the server core. The current syslog functionality is pretty bad, so replacing it with a module is a big win. Alan DeKok.
On Tue 28 Feb 2006 00:19, Alan DeKok wrote:
Geoff Silver <geoff+freeradius@uslinux.net> wrote:
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.
radrelay? Have a central RADIUS server that does nothing but accounting, and use radrelay to send the data there. That server can be made to do syslog (if you care).
But I wouldn't suggest anyone use syslog like this.
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.
Many syslog servers have size limitations on the strings they can handle. They may not be able to take a full RADIUS attribute, much less a list of RADIUS attributes in one string.
For what it's worth, I don't think its a good idea to use syslog (Especially for Accounting) in place of RADIUS relay but it actually does make sense for some cases. In particular sending Authentication failures via syslog-ng to your central syslog server is a nice idea. I think that this functionality should probably go into FreeRADIUS (Although I haven't checked the patch itself). Regards -- Peter Nixon http://www.peternixon.net/ PGP Key: http://www.peternixon.net/public.asc
participants (3)
-
Alan DeKok -
Geoff Silver -
Peter Nixon