(PM) Radius Log Processing Library

James Campbell (gandalf@telalink.net)
Tue, 11 Nov 1997 17:00:24 -0600 (CST)

I propose that the body of the portmaster-users (most of whom use Radius for
their accounting), collectivly modify a c++ class used for Radius Management.
Telalink has already written the base of this class which parses radius detail
files and formats then into usable output.

Ideally others will follow suit and contribute changes to existing functions
as well as new functions to do tasks that are required. Also, hopefully we
will be able to maintain a version of the library that is 'distribution'.
Until that time, let the source that follows, radius.h, be version .1.

--- radius.h ---

/************************************************************************\
* *
* Radius Log Proccessing Library *
* *
* Written for: Telalink Corporation <http://www.telalink.net> *
* by: James Campbell <james@telalink.net> *
* *
* Notes: *
* This library defines a class, RadiusManagement, which can be *
* used to handle the radius detail logs generated by a *
* portmaster. *
* *
* Document Conventions: *
* holdSomething *
* Variable names are long, the first letter of each *
* word is capitalized, except the first. *
* DoSomething *
* Functions are the same as variables, but with all *
* words are capitalized. *
* type_of_something *
* typedef's are all lowercase, underscored. *
* file_handle *
* file streams are lowercase, underscored. *
* Always_Be_Something *
* Consts are capitalized words, separated by underscore *
* *
* *
\************************************************************************/

#include<iostream.h>
#include<fstream.h>
#include<time.h>
#include<stdlib.h>

const short Bad_Record = 21;
const short No_More_Records = 0;
const short Good_Record = 1;
const short More_Records = 1;

#undef DEBUG
#define TIMEDEBUG

/************************************************************************\
* Definitions for the radius dictionary. If verbage changes, change *
* it here. Regretfully, we are not using this section yet. It *
* needs to be integrated. *
* *
* Running Radius version 1.6 right now. *
* *
* First, an example Radius STOP record *

Fri Nov 7 11:50:10 1997
Acct-Session-Id = "240019D3"
User-Name = "username"
NAS-IP-Address = 129.1.1.1
NAS-Port = 7
NAS-Port-Type = Async
Acct-Status-Type = Stop
Acct-Session-Time = 1075
Acct-Authentic = RADIUS
Connect-Info = "28800 LAPM/V42BIS"
Acct-Input-Octets = 6964
Acct-Output-Octets = 58425
Called-Station-Id = "1234567"
Calling-Station-Id = "1234567890"
Acct-Terminate-Cause = User-Request
Service-Type = Framed-User
Framed-Protocol = PPP
Framed-IP-Address = 129.1.1.1
Acct-Delay-Time = 0

* *
* Some const definitions are in order. Add them below as needed. *
* *
\************************************************************************/

struct radius_record {
char uid[11];
char date[21];
char duration[11];
char callingNumber[13];
char calledNumber[13];
/* These sections of the record are not used. Uncomment as needed.
char sessionID[9];
char nasIP[14];
char nasPort[4];
char nasPortType[7];
char authentic[8];
char connectInfo[30];
char inputOctets[15];
char outputOctets[15];
char terminateCause[30];
char serviceType[30];
char framedProtocol[8];
char framedIP[14];
char delayTime[5];
*/
};

typedef char expansion_record[70];

class RadiusManagement {
private:
short moreRecords;
expansion_record currentFormattedEntry;
radius_record currentEntry;
ifstream radius_file;

void FormatRecord(expansion_record output, radius_record inputRecord);
short ParseNextEntry(void);
void SyncError(char where[30], char badData[80]);
public:
RadiusManagement(const char * fileName);
void GetNextRecord(expansion_record output);
short MoreRecords(); // returns 1 if there are more entries, 0 otherwise
};

/***********************************************\
* Name: RadiusManagement (constructor) *
* Purpose: Parses the first entry and gets *
* ready to serve data. *
* *
* Input: <none> *
* *
* Output: <none> *
* *
* Notes: Parses first entry. *
* *
* A part of radius.h. *
\***********************************************/

RadiusManagement::RadiusManagement(const char * fileName) {

short worked = Bad_Record;
moreRecords = More_Records;

radius_file.open(fileName);

if (radius_file.bad()) {
cerr << "Unable to open radius file.\n";
exit(8);
}

while ((worked != Good_Record) && (moreRecords == More_Records)) {
worked = ParseNextEntry();
#ifdef DEBUG
cout << "worked = " << worked << ".\n";
#endif /* DEBUG */
}

FormatRecord(currentFormattedEntry, currentEntry);
}

/***********************************************\
* Name: ~RadiusManagement (destructor) *
* Purpose: Finishes the job. *
* *
* Input: <none> *
* *
* Output: <none> *
* *
* Notes: Closes the radius file we were *
* created to handle. *
* *
* A part of radius.h. *
\***********************************************/

RadiusManagement::~RadiusManagement() {
// all we have to do is close the radius file.
radius_file.close();
}

/***********************************************\
* Name: MoreRecords *
* Purpose: Tells the user if there are more *
* entries waiting for them *
* *
* Input: <none> *
* *
* Output: A short that is 1 if there are *
* more entries to be proccessed and *
* zero otherwise *
* *
* A part of radius.h. *
\***********************************************/

inline short RadiusManagement::MoreRecords() {
return(moreRecords);
}

/***********************************************\
* Name: GetNextRecord *
* Purpose: Returns an expansion_record for use *
* and gets the next one ready. *
* *
* Input: <none> *
* *
* Output: expansion_record (char [65]) *
* *
* Notes: First we copy the current formatted *
* record into a buffer. Then we get the *
* next record ready (replacing the value *
* in CurrentEntry and *
* CurrentFormattedEntry) and finally, *
* return the value of the initial record *
* *
* A part of radius.h. *
\***********************************************/

void RadiusManagement::GetNextRecord(expansion_record output) {
short worked=0;

strncpy(output, currentFormattedEntry, 70 );

while ((worked != Good_Record) && (moreRecords == More_Records)) {
worked = ParseNextEntry();
#ifdef DEBUG
cout << "worked = " << worked << ".\n";
#endif /* DEBUG */
}

FormatRecord(currentFormattedEntry, currentEntry);

}

/***********************************************\
* Name: FormatRecord *
* Purpose: Formats a radrecord for export into *
* the Expansion database. *
* *
* Input: struct radius_record *
* *
* Output: expansion_record (char [65]) *
* *
* A part of radius.h. *
\***********************************************/

void RadiusManagement::FormatRecord(expansion_record output, radius_record inputRecord) {
expansion_record inProgress;
double durationFloat; // duration converted to hours
char buffer[11];

strncpy(inProgress, "", 70);

strncpy(inProgress, "\"", 1); // first, build the username
strncat(inProgress, inputRecord.uid, 10);

strncat(inProgress, "\",\"", 3); // next, we have the type
strncat(inProgress, "001", 3); // type is a constant
strncat(inProgress, "\",\"", 3); // now date
strncat(inProgress, inputRecord.date, 19);
strncat(inProgress, "\",\"", 3); // next, duration
/* convert duration to hours. */
durationFloat = (atof(inputRecord.duration)) / 3600.0;
sprintf(buffer, "%10.2f", durationFloat);
strncat(inProgress, buffer, 10);
strncat(inProgress, "\",\"", 3); // finally, resourceID
strncat(inProgress, "123456789012", 12); // resourceID is a constant
strncat(inProgress, "\"", 1); // now top it all off...
// we have just written 64 characters which leaves one for the null
// until I standardize the usernames to six characters, I am allowing 70.
// DEBUG ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
strncpy(output, inProgress, 65);
}

/***********************************************\
* Name: ParseNextEntry *
* Purpose: Parses an entry from the detail *
* file "radius_file" *
* *
* Input: <none> *
* *
* Output: short worked -- whether we got a good *
* record or not. *
* *
* Notes: Parses an entry. *
* *
* A part of radius.h. *
\***********************************************/

short RadiusManagement::ParseNextEntry(void) {
char lineBuffer[80];
int lineBufferSize;
char *beginStringIsolator; // used to isolate strings
char *endStringIsolator;
struct tm timeStruct; // holds the converted date
time_t timeSecs; // Number of seconds
int cnt;

lineBufferSize = sizeof(lineBuffer);

radius_file.getline(lineBuffer, lineBufferSize); // Time, date
while (strcasecmp(lineBuffer, "") == 0) { // check for extra blanks
radius_file.getline(lineBuffer, lineBufferSize);
if (cnt > 10) { // more than ten times through...
moreRecords = No_More_Records; // give up. No more in this file.
return(No_More_Records);
}
++cnt;
}
#ifdef DEBUG
cout << "We think we've found the time in this line:\n" << lineBuffer << "\n";
#endif /* DEBUG */
strptime(lineBuffer," %a %B %d %T %Y ", &timeStruct);
timeSecs = mktime(&timeStruct); // timeSecs = num of secs since 1970

while (1) { // Looking for username
#ifdef DEBUG
cout << "We're looking for username in this line:\n" << lineBuffer << "\n";
#endif /* DEBUG */
radius_file.getline(lineBuffer, lineBufferSize);
if (strcasecmp(lineBuffer, "") == 0) { return (Bad_Record); }// record end
beginStringIsolator = (lineBuffer + 1);
endStringIsolator = (beginStringIsolator + 9);
*endStringIsolator = '\0';
if (strcasecmp(beginStringIsolator, "User-Name") == 0) {
#ifdef DEBUG
cout << "We think we've found the username in this line:\n"
<< lineBuffer << "\n";
#endif /* DEBUG */
beginStringIsolator += 13;
endStringIsolator = strchr(beginStringIsolator, '\"');
if (endStringIsolator == NULL) {
SyncError("Username", lineBuffer);
} else {
*endStringIsolator = '\0';
}
strncpy(currentEntry.uid,beginStringIsolator, 10);
break; // of loop looking for username
} // if it's not username, keep trying
} // of while

while (1) { // Looking for Record Type
#ifdef DEBUG
cout << "We're looking for type in this line:\n" << lineBuffer << "\n";
#endif /* DEBUG */
radius_file.getline(lineBuffer, lineBufferSize);
if (strcasecmp(lineBuffer, "") == 0) { return (Bad_Record); }//record end
beginStringIsolator = (lineBuffer + 1);
endStringIsolator = (beginStringIsolator + 7);
*endStringIsolator = '\0';
if (strcasecmp(beginStringIsolator, "Acct-St") == 0) { // it's the type
#ifdef DEBUG
cout << "We think we've found the type in this line:\n"
<< lineBuffer << "\n";
#endif /* DEBUG */
beginStringIsolator += 19;
endStringIsolator = (beginStringIsolator + 4);
*endStringIsolator = '\0';
if ( strcasecmp(beginStringIsolator, "Stop") != 0 ) {
/* We don't care about start records. Eat them */
while (1) {
radius_file.getline(lineBuffer, lineBufferSize);
if (strcasecmp(lineBuffer, "") == 0) { break; }
}
#ifdef DEBUG
cout << "We are about to return b/c of start record:\n"
<< lineBuffer << "\n";
#endif /* DEBUG */
return(Bad_Record); // Tell them it's a start record
} // end of the case that this is a start record
break; // from the loop of finding the type
} // of the case that we found the line on which the type is located
} // of the loop to find the line containing the type

/* This will be executed only if it is a stop record */
while (1) { // looking for duration
#ifdef DEBUG
cout << "We're looking for duration in this line:\n" << lineBuffer << "\n";
#endif /* DEBUG */
radius_file.getline(lineBuffer, lineBufferSize);
if (strcasecmp(lineBuffer, "") == 0) { return (Bad_Record); }//record end
beginStringIsolator = (lineBuffer + 1);
endStringIsolator = (beginStringIsolator + 7);
*endStringIsolator = '\0';
if (strcasecmp(beginStringIsolator, "Acct-Se") == 0) { // this is duration
#ifdef DEBUG
cout << "We think we've found the duration in this line:\n"
<< lineBuffer << "\n";
#endif /* DEBUG */
beginStringIsolator += 20;
#ifdef TIMEDEBUG
cout << "We think that the duration will be "
<< beginStringIsolator << ".\n";
#endif /* TIMEDEBUG */
strncpy(currentEntry.duration, beginStringIsolator, 11);
break; // from while of getting duration
} // of getting duration
} // of while for finding duration

while (1) { // looking for called-num
#ifdef DEBUG
cout << "We're looking for called-num in this line:\n"
<< lineBuffer << "\n";
#endif /* DEBUG */
radius_file.getline(lineBuffer, lineBufferSize);
if (strcasecmp(lineBuffer, "") == 0) { return (Bad_Record); }//record end
beginStringIsolator = (lineBuffer + 1);
endStringIsolator = (beginStringIsolator + 5);
*endStringIsolator = '\0';
if (strcasecmp(beginStringIsolator, "Calle") == 0) { // this is called-num
#ifdef DEBUG
cout << "We think we've found the called-num in this line:\n"
<< lineBuffer << "\n";
#endif /* DEBUG */
beginStringIsolator += 21;
endStringIsolator = strchr(beginStringIsolator, '\"');
if (endStringIsolator == NULL) {
SyncError("Called-num", lineBuffer);
} else {
*endStringIsolator = '\0';
}
strncpy(currentEntry.calledNumber, beginStringIsolator, 13);
break; // from loop of finding called number
} // of case that we found called-num
} // of loop for finding line that has called-num

while (1) { // looking for calling-num
#ifdef DEBUG
cout << "We're looking for calling-num in this line:\n"
<< lineBuffer << "\n";
#endif /* DEBUG */
radius_file.getline(lineBuffer, lineBufferSize);
if (strcasecmp(lineBuffer, "") == 0) { return (Bad_Record); }//record end
beginStringIsolator = (lineBuffer + 1);
endStringIsolator = (beginStringIsolator + 5);
*endStringIsolator = '\0';
if (strcasecmp(beginStringIsolator, "Calli") == 0) { // this is calling-num
#ifdef DEBUG
cout << "We think we've found the calling-num in this line:\n"
<< lineBuffer << "\n";
#endif /* DEBUG */
beginStringIsolator += 22;
endStringIsolator = strchr(beginStringIsolator, '\"');
if (endStringIsolator == NULL) {
SyncError("Calling-num", lineBuffer);
} else {
*endStringIsolator = '\0';
}
strncpy(currentEntry.callingNumber, beginStringIsolator, 13);
break; // from look looking for calling-num
} // of the case that we found calling-num
} // of while looking for calling-num

while (1) { // looking for end of record
#ifdef DEBUG
cout << "We're looking for the end in this line:\n" << lineBuffer << "\n";
#endif /* DEBUG */
radius_file.getline(lineBuffer, lineBufferSize);
if (strcasecmp(lineBuffer, "") == 0) { // it's blank, we're done
break; // from looking for end of record
}
} // of while that looks for end of record

#ifdef TIMEDEBUG
char timeTestBuffer[30];
cout << timeSecs << " - timeSecs (When we think it was logged)\n";
cout << atol(currentEntry.duration) << " - what we think the duration is.\n";
cftime(timeTestBuffer, "%m-%d-%Y:%T" , &timeSecs);
cout << timeTestBuffer << " - what we think the logging time was.\n";
timeSecs -= atol(currentEntry.duration);
cftime(timeTestBuffer, "%m-%d-%Y:%T" , &timeSecs);
cout << timeTestBuffer << " - what we think the starting time was.\n";
#endif /* TIMEDEBUG */

timeSecs -= atol(currentEntry.duration);
cftime(currentEntry.date, "%m-%d-%Y:%T" , &timeSecs);// fills in the date

#ifdef DEBUG
cout << "We think we've finshed one record at this line:\n"
<< lineBuffer << "\n";
#endif /* DEBUG */

moreRecords = 1; // we just made one good record
return(1); // This means that we got a record!
} // of ParseNextEntry

/***********************************************\
* Name: SyncError *
* Purpose: Report a reading sync error and *
* abort. *
* *
* Input: char where[30] where the error was *
* char badData[80] the line of bad data *
* *
* Output: <none> *
* *
* Notes: Exit at the end so we don't produce *
* bad accounting data. *
* *
* A part of radius.h. *
\***********************************************/

void RadiusManagement::SyncError(char where[30], char badData[80]) {

cerr << "Out of sync in " << where << " read. Aborting.\n";
cerr << " The bad data was: " << badData << ".\n";
exit(8);

}

---
James Campbell
james@telalink.net

- To unsubscribe, email 'majordomo@livingston.com' with 'unsubscribe portmaster-users' in the body of the message.