ClearBox Server™ v1.2 Developer's Guide

Step 4. Implementing Core Interfaces

On this step we continue to implement ICommonExtenderEx.

1. Find CExtension class in the Class View window and right-click it. Select Add -> Add Variable... from the context menu. Type the following data in the wizard dialog:

  • Access - private
  • Variable type - CINI
  • Variable name - m_INI

Click Finish. Then find CExtension class default constructor, and change it:

	CExtension(): m_INI(_AtlModule.GetModuleInstance())
	{
	}

It passes an instance of the dll-module to the CINI constructor necessary to get extension path.

2. Now we implement GetClientConnectionKey that is responsible for managing keys shared by server with RADIUS and TACACS clients. In Class View find and double-click CExtension's child node GetClientConnectionKey([skipped]). Replace the default method implementation with this code:

STDMETHOD(GetClientConnectionKey)(long clientIPAddr, VARIANT_BOOL tacConnection, 
	VARIANT_BOOL authenPacket, BSTR * connKey)
{
	USES_CONVERSION;

	in_addr ina;
	ina.S_un.S_addr=clientIPAddr;
	char * ipAddr=inet_ntoa(ina);

	CString keyName;
	if (tacConnection==VARIANT_TRUE)
		keyName=_T("tacKey");
	else
	{
		if (authenPacket==VARIANT_TRUE)
			keyName=_T("radAuthenKey");
		else
			keyName=_T("radAcctKey");
	}

	CString retKeyVal;
	if (m_INI.GetString(A2T(ipAddr),keyName,retKeyVal))
		*connKey=retKeyVal.AllocSysString();
	else
		*connKey=NULL;
	return S_OK;
}
		
In this method first IP-address of a client is converted to a string. Then, depending on the packet type (TACACS request, RADIUS Access-Request, RADIUS Accounting-Request) a key is searched for in the INI-file with an IP-address as a section name. If is is found, it's given to the server.

3. Create a method that reads accounting settings from ini-file.

First, add the following variable to the CExtension class:

class ATL_NO_VTABLE CExtension : 
	public IExtension,
	public ICommonExtenderEx
{
...
private:
...
enum acctMethods {csv,livingston,database,none} m_AcctMethod;
	  

m_AcctMethod variable stores the typeof accounting logging and is read in the method we create now.

Right-click CExtension class in the Class View window and select Add -> Add Function... from the context menu. Specify the following parameters:

  • Return type - bool
  • Function name - ReadAccountingSettings
  • Access - protected

Click Finish. Replace this method declaration with the following code:

bool ReadAccountingSettings(void)
{
	USES_CONVERSION;

	CString logPath;
	CString logExtens;

	CString rollover;
	//Default rollover mode is RM_NONE
	ROLLOVERMODE rm=RM_NONE;

	int fileSize;
	CString logInterim;
	bool logI=true;

	CString separator;
	CString logNames;
	bool logN=true;

	// Read accounting logging method
	CString accM;
	m_AcctMethod=none;
	if (m_INI.GetString(_T("Accounting"),_T("LogType"),accM))
	{
		accM.MakeLower();
		if (accM==_T("csv")) m_AcctMethod=csv;
		if (accM==_T("livingston")) m_AcctMethod=livingston;
		if (accM==_T("database")) m_AcctMethod=database;
	}

	if (m_AcctMethod==database || m_AcctMethod==none)
		return true;

	if (!m_INI.GetString(_T("Accounting"),_T("LogFilePath"),logPath)
		|| logPath.GetLength()==0)
	{
		//This parameter is required, so if not found, server can't start
		m_pServ2->LogExtensionError(E_INVALIDARG,
			_T("LogFilePath parameter is missing in settings.ini file"));
		return false;
	}
	if (!m_INI.GetString(_T("Accounting"),_T("LogFileExtension"),logExtens))
		//Default file extensionis "txt"
		logExtens=_T("txt");
	if (m_INI.GetString(_T("Accounting"),_T("Rollover"),rollover))
	{
		//Get rollover mode constant from a string
		rollover.MakeLower();
		if(rollover==_T("hourly")) rm=RM_HOURLY;
		if(rollover==_T("daily")) rm=RM_DAILY;
		if(rollover==_T("weekly")) rm=RM_WEEKLY;
		if(rollover==_T("monthly")) rm=RM_MONTHLY;
		if(rollover==_T("onsize")) rm=RM_ONSIZE;
	}
	if (!m_INI.GetNumber(_T("Accounting"),_T("MaxFileSize"),fileSize))
		fileSize=5242880;

	if (m_AcctMethod==livingston)
	{
		if (m_INI.GetString(_T("Accounting"),_T("LogInterim"),logInterim))
			if (logInterim.MakeLower()==_T("false"))
				logI=false;
		m_pLivAcct->SetLoggingOptionsEx((LPCTSTR)logPath,(LPCTSTR)logExtens,rm,
		logI?VARIANT_TRUE:VARIANT_FALSE,VARIANT_FALSE,0,
		_T(""),fileSize);
	}
	if (m_AcctMethod==csv)
	{
		if (m_INI.GetString(_T("Accounting"),_T("LogNames"),logNames))
			if (logNames.MakeLower()==_T("false"))
				logN=false;
		if (!m_INI.GetString(_T("Accounting"),_T("Separator"),separator)
			|| separator.GetLength()!=1)
			separator=_T(",");

		m_pLivAcct->SetLoggingOptionsEx((LPCTSTR)logPath,(LPCTSTR)logExtens,rm,
			logI?VARIANT_TRUE:VARIANT_FALSE,VARIANT_FALSE,0,
			_T(""),fileSize);
		m_pCSVAcct->SetCSVOptions((LPCTSTR)separator,VARIANT_TRUE,logN?VARIANT_TRUE:VARIANT_FALSE);

		// Parse the list of attributes to be logged in csv files
		CString radAttrList, tacAttrList;
		bool loggedRAD=m_INI.GetString(_T("Accounting"),_T("LoggedRADIUSAttributes"),radAttrList);
		bool loggedTAC=m_INI.GetString(_T("Accounting"),_T("LoggedTACACSAttributes"),tacAttrList);
		if (!loggedRAD && !loggedTAC && m_AcctMethod==csv)
		{
			m_pServ2->LogExtensionError(E_INVALIDARG,
				_T("CSV accounting was set, but no \"LoggedRADIUSAttributes\" or \"LoggedTACACSAttributes\" key was found."));
			return false;
		}

		CString parsed=loggedRAD?radAttrList:tacAttrList;
		CString token;
		int curPos=0;
		std::list<CString> al;
		token=parsed.Tokenize(separator,curPos);
		while(token!=_T(""))
		{
			al.push_back(token);
			token=parsed.Tokenize(separator,curPos);
		}
		BSTR* pNames=(BSTR*)CoTaskMemAlloc(sizeof(BSTR)*al.size());
		std::list<CString>::iterator it=al.begin();
		for (int i=0;i<al.size();i++)
		{
			pNames[i]=SysAllocString(CT2W((*it)));
			it++;
		}
		bool res=m_pCSVAcct->SetLoggedAttributesNames(al.size(),
			pNames,loggedRAD?VARIANT_TRUE:VARIANT_FALSE)==S_OK;
		for (i=0;i<al.size();i++)
			SysFreeString(pNames[i]);
		CoTaskMemFree(pNames);
		if (!res)
		{
			m_pServ2->LogExtensionError(E_INVALIDARG,
				_T("Attribute names were not parsed"));
			return false;
		}
	}

	return true;
}

In this method we read strings from the ini-file. If some parameters are not found, default values are used. Then, when all parameters are read, we call ICSVAccounting::SetCSVOptions, ILivingstonAccounting::SetLoggingOptionsEx. Finally we parse list of attributes and call ICSVAccounting::SetLoggedAttributesNames, choosing if RADIUS or TACACS+ attributes will be logged.

4. Add the following variables to the CExtension class setting their access type to private:

enum dbTypes {access,mssql,odbc} m_DBType;
enum dbAuthenTypes {db,ntsam,ad} m_AuthenType;

CString m_AuthenDomain;

bool m_PAPallowed;
bool m_CHAPallowed;
bool m_MSCHAPallowed;
bool m_MSCHAP2allowed;

CString m_DSN; // ODBC DSN alias
CString m_DBPath; //Path to Access database file
CString m_DBServer; // MS SQL server
CString m_DBCatalog; // MS SQL database
CString m_DBUser; // MS SQL/ODBC user
CString m_DBPassword; // MS SQL/ODBC password
bool m_bUseNTSecurity; // Use Windows or MS SQL authentication
		

m_DBType stores type of database server extension will connect to. m_AuthenType stores authentication type to perform.

5. Create a method to read authentication settings from ini-file. With the wizard add a new function with

  • Return type - bool
  • Function name - ReadAuthenticationSettings
  • Access - protected

Set its body as follows:

bool ReadAuthenticationSettings(void)
{
	CString dbType;
	
	CString authenType;
	m_AuthenType=db;

	if (m_INI.GetString(_T("Authentication"),_T("AuthenType"),authenType))
	{
		authenType.MakeLower();
		if (authenType==_T("db")) m_AuthenType=db;
		if (authenType==_T("ad")) m_AuthenType=ad;
		if (authenType==_T("ntsam")) m_AuthenType=ntsam;
	}
	if (m_AuthenType==db)
	{
		if (!m_INI.GetString(_T("Authentication"),_T("DBType"),dbType))
		{
			m_pServ2->LogExtensionError(E_INVALIDARG,
				_T("AuthenType is set to \"DB\" but no DBType key is found"));
			return false;
		}
		m_DBType=access; //Default value
		dbType.MakeLower();
		if(dbType==_T("access")) m_DBType=access;
		if(dbType==_T("mssql")) m_DBType=mssql;
	}
	if (m_AuthenType==db && m_DBType==access)
	{
		if (!m_INI.GetString(_T("Access"),_T("DBPath"),m_DBPath))
		{
			m_pServ2->LogExtensionError(E_INVALIDARG,
				_T("DBPath key is required but not found"));
			return false;
		}
	}
	if (m_AuthenType==db && m_DBType==odbc)
	{
		if (!m_INI.GetString(_T("Database"),_T("DSN"),m_DSN))
		{
			m_pServ2->LogExtensionError(E_INVALIDARG,
				_T("DSN key is required but not found"));
			return false;
		}
		if (!m_INI.GetString(_T("Database"),_T("DBUser"),m_DBUser))
			m_DBUser=_T("");
		if (!m_INI.GetString(_T("Database"),_T("DBPassword"),m_DBPassword))
			m_DBPassword=_T("");
	}
	if (m_AuthenType==db && m_DBType==mssql)
	{
		bool valid=false;
		CString missingKey;
		do
		{
			if (!m_INI.GetString(_T("MSSQL"),_T("DBServer"),m_DBServer))
			{
				missingKey=_T("DBServer");
				break;
			}
			if (!m_INI.GetString(_T("MSSQL"),_T("DBCatalog"),m_DBCatalog))
			{
				missingKey=_T("DBCatalog");
				break;
			}
			CString msAuthen;
			m_bUseNTSecurity=true;
			if (!m_INI.GetString(_T("MSSQL"),_T("DBSecurity"),msAuthen))
			{
				missingKey=_T("DBSecurity");
				break;
			}
			msAuthen.MakeLower();
			if(msAuthen==_T("win")) m_bUseNTSecurity=true;
			if(msAuthen==_T("mssql")) m_bUseNTSecurity=false;

			if (!m_bUseNTSecurity)
			{
				if (!m_INI.GetString(_T("MSSQL"),_T("DBUser"),m_DBUser))
				{
					missingKey=_T("DBUser");
					break;
				}
				if (!m_INI.GetString(_T("MSSQL"),_T("DBPassword"),m_DBPassword))
				{
					missingKey=_T("DBPassword");
					break;
				}
			}
			valid=true;
		} while(false);
		if (!valid)
		{
			CString errStr;
			errStr.Format(_T("%s key is required but not found"),
				missingKey);
			m_pServ2->LogExtensionError(E_INVALIDARG,(LPCTSTR)errStr);
			return false;
		}
	}
	if (m_AuthenType!=db)
	{
		if(!m_INI.GetString(_T("Authentication"),_T("AuthenDomain"),m_AuthenDomain))
		{
			m_pServ2->LogExtensionError(E_INVALIDARG,
				_T("AuthenDomian key required but not found"));
			return false;
		}
	}
	return true;
}
	  

In this method first we read type of authentication source that is used to authenticate users (m_AuthenType variable). Then, if "database" authentication is configured, server extension reads what type of a database it should connect to (m_DBType variable). If type is "Access" then path to the Access database file is looked for. If it's not found, server fails to start. If typeis "ODBC", than mandatory parameter "DSN" is read and then optional database user and password are read. Finally if database type is "MSSQL", its connection properties are read from ini-file.

6. Next, we create a method that reads allowed authentication methods from ini-file. Add a new function to the CExtension class using the wizard with

  • Return type - bool
  • Function name - ReadAuthenMethodsSettings
  • Access - protected

Set its body as follows:

bool ReadAuthenMethodsSettings(void)
{
	m_PAPallowed=true;
	m_CHAPallowed=true;
	m_MSCHAPallowed=true;
	m_MSCHAP2allowed=true;

	CString access;

	if (m_INI.GetString(_T("Authentication"),_T("PAPAuthen"),access))
		if (access.MakeLower()==_T("false"))
			m_PAPallowed=false;
	if (m_INI.GetString(_T("Authentication"),_T("CHAPAuthen"),access))
		if (access.MakeLower()==_T("false"))
			m_CHAPallowed=false;
	if (m_INI.GetString(_T("Authentication"),_T("MSCHAPAuthen"),access))
		if (access.MakeLower()==_T("false"))
			m_MSCHAPallowed=false;
	if (m_INI.GetString(_T("Authentication"),_T("MSCHAP2Authen"),access))
		if (access.MakeLower()==_T("false"))
			m_MSCHAP2allowed=false;

	if (!m_PAPallowed && !m_CHAPallowed && !m_MSCHAPallowed && !m_MSCHAP2allowed)
	{
		m_pServ2->LogExtensionError(E_INVALIDARG,
			_T("None of authentication methods is allowed"));
		return false;
	}
	return true;
}
	  

In this method we read sequentially variables that define whether a user can authenticate through PAP, CHAP, MS-CHAP and MS-CHAP methods. If none of them is allowed, server extension will fail to initialize.

7. The last task on this step is to call all these functions we've created from InitializeEx. Find its definition and change it:

STDMETHOD(InitializeEx)(VARIANT_BOOL start, IServer * pServer)
{
	if (start==VARIANT_TRUE)
	{
		m_pServ2=pServer;
		m_pLivAcct=pServer;
		m_pCSVAcct=pServer;

		if (!ReadAuthenticationSettings())
			return E_INVALIDARG;
		if (!ReadAuthenMethodsSettings())
			return E_INVALIDARG;
		if (!ReadAccountingSettings())
			return E_INVALIDARG;
	}
	return S_OK;
}


This method will be updated later to include database initialization.

Go to the next step.


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

Created by chm2web html help conversion utility.