ClearBox Server™ v1.2 Developer's Guide

Step 9. Implementing Accounting

On this step we implement IRADIUSAccounting and ITACACSAccounting interfaces that process accounting records. We need to find type of the accounting request (accounting start, update or stop record). If the type is"stop" then we need to log attributes according to the selected accounting logging type.

1. Implement IRADIUSAccounting and ITACACSAccounting. Right-click CExtension class in Class View and select Add... -> Implement Interface... Specify File option and select <server installation path>\SDK\TACRADServer.tlb. Move these interfaces to the right column and click Finish.

2. Next,we create to helper functions that find specified attribute in the array of attributes. One version looks through TACACS+ AV pairs, another - within RADIUS attributes.

With the Add Function... wizard create a function in CExtension class with:

  • Return type - int
  • Function name - FindAttribute
  • Access type - protected
  • Parameter 1 - AVPAIR* avPairs
  • Parameter 2 - int numPairs
  • Parameter 3 - BSTR attrFind

and set its definition to the following:

int FindAttribute(AVPAIR* avPairs, int numPairs, BSTR attrFind)
{
	for (int i=0;i<numPairs;i++)
	{
		if (wcsicmp(avPairs[i].attribute,attrFind)==0)
			return i;
	}
	return -1;

}

It returns index of the necessary attribute in the array or -1 if an attribute with the name specified was not found.

3. Create a function almost similar to the function we've just created, but with parameters slightly different:

  • Return type - int
  • Function name - FindAttribute
  • Access type - protected
  • Parameter 1 - RADIUS_ATTRIBUTE* radAttrs
  • Parameter 2 - int numAttr
  • Parameter 3 - BYTE attrFind

and set its code to the following:

4. Double-click the first ProcessAccounting node of CExtension class in the Class View window. IRADIUSAccounting::ProcessAccounting method implementation is opened. Change its definition to this code:

STDMETHOD(ProcessAccounting)(long tag, USERINFOLITE * userInf, unsigned long attributesNum, 
	RADIUS_ATTRIBUTE * inpAttributes, USERADDRESS * userAddr, ACCOUNTINGSTATUS * status)
{
	USES_CONVERSION;
	*status=A_OK;

	int ind=FindAttribute(inpAttributes,attributesNum,40); // Accounting-Status-Type
	if (ind==-1)
		return S_OK;

	if (m_AcctMethod==livingston)
		return m_pLivAcct->LogPacket(attributesNum,inpAttributes);
	if (m_AcctMethod==none)
		return S_OK;

	if (inpAttributes[ind].dwValue!=2) // Stop
		return S_OK;
	if (m_AcctMethod==csv)
		return m_pCSVAcct->LogRADPacketCSV(attributesNum,inpAttributes,userInf);

	CCommand<CDynamicAccessor> cmd;
	CString cmdStr;
	cmdStr.Format(_T("select ID, TimeCredit from Users where Name='%s'"),W2T(userInf->userName));
	HRESULT hr=cmd.Open(m_DB.Session(),cmdStr);
	if (FAILED(hr))
		return hr;

	int userID=0;
	int timeCredit=0;
	if (cmd.MoveFirst()==S_OK)
	{
		userID=*((int*)cmd.GetValue(1));
		timeCredit=*((int*)cmd.GetValue(2));
	}

	int sessTime=0;
	ind=FindAttribute(inpAttributes,attributesNum,46); // Session-Time
	if (ind!=-1)
		sessTime=inpAttributes[ind].dwValue;

	int bytesIn=0;
	ind=FindAttribute(inpAttributes,attributesNum,42); // Acct-Input-Octets
	if (ind!=-1)
		bytesIn=inpAttributes[ind].dwValue;

	int bytesOut=0;
	ind=FindAttribute(inpAttributes,attributesNum,43); // Acct-Output-Octets
	if (ind!=-1)
		bytesOut=inpAttributes[ind].dwValue;

	char* ipAddr="";
	ind=FindAttribute(inpAttributes,attributesNum,8); // Framed-IP-Address
	if (ind!=-1)
	{
		in_addr ina;
		ina.S_un.S_addr=inpAttributes[ind].dwValue;
		ipAddr=inet_ntoa(ina);
	}

	char caller[200];
	caller[0]='\0';
	ind=FindAttribute(inpAttributes,attributesNum,31); // Calling-Station-Id
	if (ind!=-1)
	{
		memcpy(caller,inpAttributes[ind].strValue,inpAttributes[ind].strSize);
		caller[inpAttributes[ind].strSize]='\0';
	}

	cmdStr.Format(_T("insert into Accounting(UserID,SessionTime,BytesIn,BytesOut,IPAddress,\
	CallerID) values(%d,%d,%d,%d,'%s','%s')"),
		userID,sessTime,bytesIn,bytesOut,A2T(ipAddr),(LPCTSTR)A2T(caller));


	CCommand<CNoAccessor,CNoRowset> insCmd;
	hr=insCmd.Open(m_DB.Session(),cmdStr);
	if (FAILED(hr))
		return hr;

	if (m_AuthenType==db && timeCredit>0)
	{
		timeCredit-=sessTime;
		if (timeCredit==0)
			timeCredit=-1;

		cmdStr.Format(_T("update Users set TimeCredit=%d where ID=%d"),
			timeCredit,userID);
		CCommand<CNoAccessor,CNoRowset> updCmd;
		hr=updCmd.Open(m_DB.Session(),cmdStr);
		if (FAILED(hr))
			return hr;
	}

	return S_OK;
}
		

If accounting logging type is not 'database" then appropriate methods of server-provided interfaces are called. Then this method does nothing for accounting packets with status type other than Stop.

If accounting records should be written into the database table then the method first finds ID of the user in the Users table, finds in the request packet all the necessary attributes, creates a SQL command and inserts a new record into the table. If user is authenticated with a database and his time credit is active, it's updated.

5. Now we implement ITACACSAccounting::ProcessAccounting in almost the same way:

STDMETHOD(ProcessAccounting)(long tag, TAC_AUTHORPARAMS * accParams, unsigned long avSize, 
	AVPAIR * avPairs, VARIANT_BOOL start, VARIANT_BOOL update, USERADDRESS * userAddr, 
	ACCOUNTINGSTATUS * status)
{
	USES_CONVERSION;
	*status=A_OK;

	if (m_AcctMethod==livingston || m_AcctMethod==none)
		return S_OK;
	if (start==VARIANT_TRUE || update==VARIANT_TRUE)
		return S_OK;
	if (m_AcctMethod==csv)
		return m_pCSVAcct->LogTACPacketCSV(avSize,avPairs,accParams);

	CCommand<CDynamicAccessor> cmd;
	CString cmdStr;
	cmdStr.Format(_T("select ID from Users where Name='%s'"),W2T(accParams->user));
	HRESULT hr=cmd.Open(m_DB.Session(),cmdStr);
	if (FAILED(hr))
		return hr;

	int userID=0;
	int timeCredit=0;
	if (cmd.MoveFirst()==S_OK)
	{
		userID=*((int*)cmd.GetValue(1));
		timeCredit=*((int*)cmd.GetValue(2));
	}

	int sessTime=0;
	int ind=FindAttribute(avPairs,avSize,L"elapsed_time");
	if (ind!=-1)
		sessTime=_wtoi(avPairs[ind].value);

	int bytesIn=0;
	ind=FindAttribute(avPairs,avSize,L"bytes_in");
	if (ind!=-1)
		bytesIn=_wtoi(avPairs[ind].value);

	int bytesOut=0;
	ind=FindAttribute(avPairs,avSize,L"bytes_out");
	if (ind!=-1)
		bytesOut=_wtoi(avPairs[ind].value);

	TCHAR* ipAddr=_T("");
	ind=FindAttribute(avPairs,avSize,L"addr");
	if (ind!=-1)
		ipAddr=W2T(avPairs[ind].value);

	TCHAR* caller=W2T(accParams->remote_address);

	cmdStr.Format(_T("insert into Accounting(UserID,SessionTime,BytesIn,BytesOut,\
	IPAddress,CallerID)	values(%d,%d,%d,%d,'%s','%s')"),
		userID,sessTime,bytesIn,bytesOut,(LPCTSTR)ipAddr,(LPCTSTR)caller);

	CCommand<CNoAccessor,CNoRowset> insCmd;
	hr=insCmd.Open(m_DB.Session(),cmdStr);
	if (FAILED(hr))
		return hr;

	if (m_AuthenType==db && timeCredit>0)
	{
		timeCredit-=sessTime;
		if (timeCredit==0)
			timeCredit=-1;

		cmdStr.Format(_T("update Users set TimeCredit=%d where ID=%d"),
			timeCredit,userID);
		CCommand<CNoAccessor,CNoRowset> updCmd;
		hr=updCmd.Open(m_DB.Session(),cmdStr);
		if (FAILED(hr))
			return hr;
	}

	return S_OK;
}

Go to the next step.


© 2001-2003 XPerience Technologies. www.xperiencetech.com

Created by chm2web html help conversion utility.