// borfmpr.cpp:  Routing and Remote Access routines.


#include "stdafx.h"
#include "borfrras.h"

using namespace std;

// Global options.
extern BORF_OPTIONS options;


void BorfMprWarning( DWORD dwError, LPCTSTR lpwszSource )
{
/**
 *  Generates an event log warning for the given source.
 *
 *    dwError     [in]  The MPR result code.
 *    lpwszSource [in]  A desription about where the error happened.
 */

	// A buffer for the error string.
	LPWSTR lpwszBuffer;

	// Another error buffer.
	DWORD dwRorre;

	// Get the MPR error string.
	dwRorre = MprAdminGetErrorString( dwError, &lpwszBuffer );

	if( dwRorre == NO_ERROR )
	{
		// DEBUG:
		wcerr << lpwszSource << L": " << dwError << L": " << lpwszBuffer << endl;

		// Log the warning.
		BorfEvent(
			EVENTLOG_WARNING_TYPE,
			IDM_WARNING_MPR,
			sizeof( dwError ),
			&dwError,
			lpwszSource,
			lpwszBuffer,
			NULL );
	}

	else
	{
		BorfApiWarning( dwRorre, L"BorfMprWarning: MprAdminGetErrorString" );
	}

} // BofMprWarning



HRESULT BorfConnectionEnumerate( const CListMprServerHandle& oMprServerHandleList, CListBorfConnection& oConnectionList )
{
/**
 *  Populates oConnectionList using the server handles given in oMprServerHandleList.
 *  This function is a wrapper for MprAdminConnectionEnum() and MprAdminConnectionGetInfo().
 *
 *    oMprServerHandle [in]   A list of handles as returned by MprAdminServerConnect().
 *    oConnectionList  [out]  A list of populated BORF_CONNECTION structs.
 *
 *  Clear oConnectionList with BorfConnectionFree() to avoid memory leaks.
 *
 */

	// The list iterator.
	POSITION borfPosition = oMprServerHandleList.GetHeadPosition();

	// The element buffer.
	BORF_CONNECTION borfConnection;

	// A buffer for MPR API results.
	DWORD dwResult;
	
	// MPR API parameters.
	DWORD dwEntriesRead;
	DWORD dwTotalEntries;
	DWORD dwResumeHandle;


	while( borfPosition )
	{
		// Initialize the MPR resume parameter.
		dwResumeHandle = NULL;

		// Get the next server handle.
		borfConnection.hServer = oMprServerHandleList.GetNext( borfPosition );

		do // while dwResumeHandle
		{
			// Initialize the element buffer.
			borfConnection.pConnection0 = NULL;
			borfConnection.pConnection1 = NULL;
			borfConnection.pConnection2 = NULL;

			// Enumerate the next connection and get the RAS_CONNECTION_0 struct.
			dwResult = MprAdminConnectionEnum(
				borfConnection.hServer,
				0,
				(LPBYTE*)&borfConnection.pConnection0,
				sizeof( RAS_CONNECTION_0 ),
				&dwEntriesRead,
				&dwTotalEntries,
				&dwResumeHandle	);

/*
			wcerr << L" BorfConnectionEnumerate: MprAdminConnectionEnum:";
			wcerr << L" dwEntriesRead " << dwEntriesRead;
			wcerr << L", dwTotalEntries " << dwTotalEntries;
			wcerr << L", dwResumeHandle " << dwResumeHandle << endl;
*/

			if( dwResult != NO_ERROR && dwResult != ERROR_MORE_DATA )
			{
				// Log a warning.
				BorfMprWarning( dwResult, L"BorfConnectionEnumerate: MprAdminConnectionEnum" );
			}

/*
			switch( dwResult )
			{
				case NO_ERROR:
					wcout << L"BorfConnectionEnumerate: MprAdminConnectionEnum: NO_ERROR" << endl;
					break;

				// Not applicable.
				//
				// case ERROR_DDM_NOT_RUNNING:
				//	wcout << L"BorfConnectionEnumerate: MprAdminConnectionEnum: ERROR_DDM_NOT_RUNNING" << endl;
				//	break;
				//

				case ERROR_INVALID_LEVEL:
					wcout << L"BorfConnectionEnumerate: MprAdminConnectionEnum: ERROR_INVALID_LEVEL" << endl;
					break;

				case ERROR_INVALID_PARAMETER:
					wcout << L"BorfConnectionEnumerate: MprAdminConnectionEnum: ERROR_INVALID_PARAMETER" << endl;
					break;

				case ERROR_MORE_DATA:
					wcout << L"BorfConnectionEnumerate: MprAdminConnectionEnum: ERROR_MORE_DATA" << endl;
					break;

				case RPC_S_INVALID_BINDING:
					wcout << L"BorfConnectionEnumerate: MprAdminConnectionEnum: RPC_S_INVALID_BINDING" << endl;
					break;

				default:
					wcout << L"BorfConnectionEnumerate: MprAdminConnectionEnum: " << dwResult << endl;
					break;

			} // switch
*/

			// DEBUG:
			// wcout << L"BorfConnectionEnumerate: MprAdminConnectionEnum: dwEntriesRead " << dwEntriesRead << endl;
				
			if( dwEntriesRead == 0 )
			{
				// Corner case: This only happens when the RRAS server has no connections.
				break;
			}


			if( dwEntriesRead != 1 )
			{
				// Sanity error.
				wcout << L"BorfConnectionEnumerate: MprAdminConnectionEnum: dwEntriesRead " << dwEntriesRead << endl;
			}


			// Get RAS_CONNECTION_1 for the previously loaded struct.
			dwResult = MprAdminConnectionGetInfo(
				borfConnection.hServer,
				1,
				borfConnection.pConnection0->hConnection,
				(LPBYTE*)&borfConnection.pConnection1 );


			if( dwResult != NO_ERROR )
			{
				BorfMprWarning( dwResult, L"BorfConnectionEnumerate: MrAdminConnectionGetInfo1" );
			}

			// Get RAS_CONNECTION_2 for the previously loaded struct.
			dwResult = MprAdminConnectionGetInfo(
				borfConnection.hServer,
				2,
				borfConnection.pConnection1->hConnection,
				(LPBYTE*)&borfConnection.pConnection2 );

			if( dwResult != NO_ERROR )
			{
				BorfMprWarning( dwResult, L"BorfConnectionEnumerate: MrAdminConnectionGetInfo1" );
			}


			// Add this connection to the list.
			oConnectionList.AddTail( borfConnection );


		} while( dwEntriesRead < dwTotalEntries );

	} // while borfPosition

	wcout << L"BorfConnectionEnumerate: Enumerated " << oConnectionList.GetCount() << L" connections." << endl;
	
	return S_OK;

} // BorfConnectionEnumerate



HRESULT BorfConnectionFree( CListBorfConnection& oConnectionList )
{
/**
 *  Frees oConnectionList element buffers and clears the list.
 *  This function is a wrapper for MprAdminBufferFree().
 *
 *    oConnectionList [in,out]  A list of BORF_CONNECTION structs.
 *
 */

	// The list iterator.
	POSITION borfPosition = oConnectionList.GetHeadPosition();

	// The element buffer.
	BORF_CONNECTION borfConnection;

	while( borfPosition )
	{
		// Get the next element.
		borfConnection = oConnectionList.GetNext( borfPosition );

		// Free all connection buffers.
		MprAdminBufferFree( borfConnection.pConnection0 );
		MprAdminBufferFree( borfConnection.pConnection1 );
		MprAdminBufferFree( borfConnection.pConnection2 );
	}

	// Clear the list.
	oConnectionList.RemoveAll();

	return S_OK;

} // BorfConnectionFree



HRESULT BorfConnectionDisconnect( BORF_CONNECTION& borfConnection )
{
/**
 *  Disconnects all ports that are participating in the given connection.
 *  The function wraps MprAdminPortEnum() and MprAdminPortDisconnect().
 *
 *    borfConnection [in]  The connection to disconnect.
 *
 */

	// A buffer for MPR API results.
	DWORD dwResult;

	// A pointer for the port array.
	RAS_PORT_0* pPort = NULL;
	
	// MPR API output parameters.
	DWORD dwEntriesRead  = 0;
	DWORD dwTotalEntries = 0;
	DWORD dwResumeHandle = NULL;

	// Get all ports that are being used by this connection.
	dwResult = MprAdminPortEnum(
		borfConnection.hServer,                    // The server handle.
		0,                                         // Request a level-zero return.
		borfConnection.pConnection0->hConnection,  // The connection handle.
		(LPBYTE*)&pPort,                           // The output buffer pointer.
		-1,                                        // Request all ports.
		&dwEntriesRead,                            // The return count.
		&dwTotalEntries,                           // Total participating ports.
		&dwResumeHandle );                         // Not used here..

	if( FAILED( dwResult ) )
	{
		BorfMprWarning( dwResult, L"BorfConnectionDisconnect: MprAdminPortEnum" );
		return dwResult;
	}


	// Disconnect all ports that are being used by this connection.
	for( DWORD i = 0; i < dwEntriesRead; i++ )
	{
		// Get the next port and disconnect it.
		dwResult = MprAdminPortDisconnect( borfConnection.hServer, pPort[i].hPort );

		if( FAILED( dwResult ) )
		{
			BorfMprWarning( dwResult, L"BorfConnectionDisconnect: MprAdminPortDisconnect" );
		}
	}

	wcout  << L"BorfConnectionDisconnect: MprAdminPortDisconnect: ";
	wcout  << L"Disconnected " << borfConnection.pConnection0->wszUserName << L"." << endl;

	// Log that we disconnected a user.
	BorfEvent(
		EVENTLOG_INFORMATION_TYPE,
		IDM_MPR_DISCONNECT,
		0,
		NULL,
		borfConnection.pConnection0->wszUserName,
		NULL );

	// Release the port buffer.
	MprAdminBufferFree( pPort );

	return S_OK;

} // BorfDisconnect



HRESULT BorfServerConnect( const CStringList& oServerNameList, CListMprServerHandle& oServerHandleList )
{
/**
 *  Returns a list of MPR server handles for the given RRAS server names.
 *  This function wraps MprAdminServerConnect().
 *
 *    oServerNameList   [in]   A list of server names in DNS or NetBIOS format.
 *    oServerHandleList [out]  A list of MPR_SERVER_HANDLE structs.
 *
 *  Clear oServerHandle list with oServerDisconnect() to avoid memory leaks.
 */

	// The list iterator.
	POSITION borfPosition = oServerNameList.GetHeadPosition();

	// The server name buffer.
	CString sName;

	// The server handle buffer.
	MPR_SERVER_HANDLE hHandle;

	// A buffer for MPR API results.
	DWORD dwResult;


	while( borfPosition )
	{
		// Get the next server name.
		sName = oServerNameList.GetNext( borfPosition );

		// Attempt a server connection.
		dwResult = MprAdminServerConnect( (LPTSTR)(LPCTSTR)sName, &hHandle );

		if( dwResult != NO_ERROR )
		{
			BorfMprWarning( dwResult, L"BorfServerConnect: MprAdminServerConnect" );
		}

		switch( dwResult )
		{
			case ERROR_ACCESS_DENIED:

				wcerr << L"BorfServerConnect: MprAdminServerConnect: ";
				wcerr << (LPCTSTR)sName;
				wcerr << L" access denied.";
				wcerr << endl;
				break;

			case 1722:  // FIXME: Find the appropriate symbol for this error code.
			case RPC_S_UNKNOWN_IF:

				wcerr << L"BorfServerConnect: MprAdminServerConnect: ";
				wcerr << (LPCTSTR)sName;
				wcerr << L" is offline or is not running RRAS.";
				wcerr << endl;
				break;
			
			case NO_ERROR:
				
				oServerHandleList.AddTail( hHandle );
				wcout << L"BorfServerConnect: MprAdminServerConnect: ";
				wcout << (LPCTSTR)sName;
				wcout << L" is ready.";
				wcout << endl;
				break;

			default:

				wcerr << L"BorfServerConnect: MprAdminServerConnect: " << dwResult << endl;

		} // switch

	} // while


	if( oServerNameList.GetCount() == oServerHandleList.GetCount() )
	{
		// We were able to connect to servers for all of the given names.
		return S_OK;
	}

	else
	{
		// We connected to some of the servers.
		return S_FALSE;
	}

} // BorfServerConnect



HRESULT BorfServerDisconnect( CListMprServerHandle& oServerHandleList )
{
/**
 *  Disconnects every server handle in the given list and clears the list.
 *  This function wraps MprAdminServerDisconnect().
 *
 *    oServerHandleList [in]  A list of MPR_SERVER_HANDLE structs.
 *
 */


	// The list iterator.
	POSITION borfPosition = oServerHandleList.GetHeadPosition();

	// The server handle buffer.
	MPR_SERVER_HANDLE hHandle;

	while( borfPosition )
	{
		// Get the next server handle.
		hHandle = oServerHandleList.GetNext( borfPosition );

		// Disconnect from the server. (This API has no return.)
		MprAdminServerDisconnect( hHandle );
	}

	// Clear the list.
	oServerHandleList.RemoveAll();

	// Nothing can go wrong.
	return S_OK;

} // BorfServerDisconnect



HRESULT BorfDisconnectDuplicate( CListBorfConnection& oConnectionList )
{
/**
 *  Disconnects users with multiple connections.
 *  Adds disconnected users to the "Borf Duplicate Lockout" group.
 *
 *    oConnectionList [in]  A list of MPR connections.
 *
 */
	
	// The list iterator.
	POSITION borfPosition = oConnectionList.GetHeadPosition();

	// The duplicate map.
	CMapStringToDword oMap;

	// The username buffer.
	CString sUser;

	// The connection iteration buffer.
	BORF_CONNECTION borfConnection;

	// The map lookup buffer.
	DWORD dwCount;



	while( borfPosition )
	{
		// Get the next connection.
		borfConnection = oConnectionList.GetNext( borfPosition );

		// Check whether this user is already in the map.
		if( oMap.Lookup( borfConnection.pConnection0->wszUserName, dwCount ) )
		{
			// This user name was already in the map.

			// Increment their connection count.
			dwCount += 1;

			// Put the incremented connection count back into the map.
			oMap.SetAt( borfConnection.pConnection0->wszUserName, dwCount );
		}

		else
		{
			// This user name was not in the map.

			// Initialize the count.
			dwCount = 1;

			// Add the user to the map.
			oMap.SetAt( borfConnection.pConnection0->wszUserName, dwCount );
		}

	} // while


	// Start at the top of the list again.
	borfPosition = oConnectionList.GetHeadPosition();

	while( borfPosition )
	{
		// Get the next connection.
		borfConnection = oConnectionList.GetAt( borfPosition );

		// Get the connection count for this user.
		oMap.Lookup( borfConnection.pConnection0->wszUserName, dwCount );

		if( dwCount > options.dwDuplicateLimit )
		{
			// Remove the connection from the list.
			oConnectionList.RemoveAt( borfPosition );

			// FIXME: This needs to be done right.
			borfPosition = oConnectionList.GetHeadPosition();

			// Add the user to the lockout group.
			BorfADsAddNameToGroup( borfConnection.pConnection0->wszUserName, options.sDuplicateLockoutGroup );

			// Log that we applied duplicate logon rules to this user.
			BorfEvent(
				EVENTLOG_INFORMATION_TYPE,
				IDM_DUPLICATE_RULES,
				0,
				NULL,
				borfConnection.pConnection0->wszUserName,
				NULL );

			// Disconnect the connection.
			BorfConnectionDisconnect( borfConnection );
		}

		else
		{
			// Increment the position.
			oConnectionList.GetNext( borfPosition );
		}

	} // while

	
	return S_OK;

} // BorfDuplicateConnection



HRESULT BorfDisconnectTime( CListBorfConnection oConnectionList, DWORD dwCount = -1 )
{
/**
 *  Disconnects connections that have been online for more than the time limit.
 *  Connections are disconnected in the order of descending duration.
 *  Disconnected users are added to the "Borf Time Lockout" group.
 *
 *    oConnectionList [in]  A list of MPR connections.
 *    oCount          [in]  The maximum number of connections to disconnect.
 *
 *  TODO: Implement a sort method for CListBorfConnection to improve efficiency.
 *
 *  If dwCount is negative, then all connections that have durations of
 *  more than the time limit will be disconnected.
 *
 *  Notice that oConnectionList is *not* passed by reference.
 *  A copy constructor for it must be invoked when this function is called.
 *
 */

	// The list iterator.
	POSITION borfPosition;

	// The list position of the next disconnection candidate.
	POSITION borfPositionCandidate;

	// The connection iteration buffer.
	BORF_CONNECTION borfConnection;

	// The next disconnection candidate buffer.
	BORF_CONNECTION borfConnectionCandidate;

	// A result buffer.
	DWORD dwResult;

	// A pointer to the port information buffer.
	RAS_PORT_0* pRasPorts;

	// MPR enumeration parameters.
	DWORD dwEntriesRead;
	DWORD dwTotalEntries;
	DWORD dwResumeHandle;

	// The device type that we know about.
	CString sDeviceType = BORF_KNOB_RRAS_MODEM;

	
	while( oConnectionList.GetCount() > 0 && dwCount-- )
	{
		// Start at the head of the list.
		borfPosition = oConnectionList.GetHeadPosition();

		// Initialize the candidate position.
		borfPositionCandidate = borfPosition;

		// Initialize the candidate buffer.
		borfConnectionCandidate = oConnectionList.GetNext( borfPosition );

		// Find the connection with the greatest duration.
		while( borfPosition )
		{
			// Get the next connection.
			borfConnection = oConnectionList.GetAt( borfPosition );

			if( borfConnection.pConnection0->dwConnectDuration >
				borfConnectionCandidate.pConnection0->dwConnectDuration )
			{
/*
				// Now check whether this is a modem connection.
				DWORD dwModemCount = 0;

				// Get all ports that are participating in this connection.
				dwResult = MprAdminPortEnum(
					borfConnection.hServer,                    // The server handle.
					0,                                         // Request a level-zero return.
					borfConnection.pConnection0->hConnection,  // The connection handle.
					(LPBYTE*)&pRasPorts,                       // The output buffer pointer.
					-1,                                        // Request all ports in the connection.
					&dwEntriesRead,                            // The number of entries returned.
					&dwTotalEntries,                           // The total number of ports in the connection.
					&dwResumeHandle	);                         // Not used here.

				for( DWORD i = 0 ; i < dwEntriesRead ; i++ )
				{
					// Check whether this connection is using a modem port.
					if( sDeviceType == pRasPorts[i].wszDeviceType )
					{
						// Increment the modem count.
						dwModemCount += 1;
					}

				} // for 

				if( dwModemCount > 0 )
				{
					// This connection is using at least on modem port so make it a borf candidate.

					// Set the candidate position.
					borfPositionCandidate = borfPosition;

					// Set the candidate buffer.
					borfConnectionCandidate = borfConnection;

				} // if

				// Release the port array buffer.
				MprAdminBufferFree( pRasPorts );
*/

// Set the candidate position.
borfPositionCandidate = borfPosition;

// Set the candidate buffer.
borfConnectionCandidate = borfConnection;

			} // if dwConnectDuration

			// Iterate the position.
			oConnectionList.GetNext( borfPosition );
		}


		if( borfConnectionCandidate.pConnection0->dwConnectDuration < options.dwTimeLimit )
		{
			// All connections have durations less than the time limit.
			oConnectionList.RemoveAll();
			return S_FALSE;
		}

		// Remove the connection from the list.
		oConnectionList.RemoveAt( borfPositionCandidate );

		// Add the user to the lockout group.
		BorfADsAddNameToGroup( borfConnectionCandidate.pConnection0->wszUserName, options.sTimeLockoutGroup );

		// Log that we applied time rules to the user.
		BorfEvent(
			EVENTLOG_INFORMATION_TYPE,
			IDM_TIME_RULES,
			0,
			NULL,
			borfConnectionCandidate.pConnection0->wszUserName,
			NULL );

		// Disconnect the connection.
		BorfConnectionDisconnect( borfConnectionCandidate );

	} // while

	oConnectionList.RemoveAll();
	return S_OK;

} // BorfDisconnectTime



HRESULT BorfServerIsBusy( const CListMprServerHandle& oServerHandleList, DWORD& dwBusyCount )
{
/**
 *  Determines whether the given set of servers is busy.
 * 
 *    oServerHandleList [in]   A list of open MPR server handles.
 *    dwBusyCount       [out]  The number of connections over the busy threshold.
 *
 *  This function sets dwBusyCount to zero if the server set is not busy,
 *  else dwBusyCount is set to the number of connections over the busy threshold.
 *
 */

	// The list iterator.
	POSITION borfPosition = oServerHandleList.GetHeadPosition();

	// A server handle buffer.
	MPR_SERVER_HANDLE hMprServerHandle;

	// A pointer to the server information buffer.
	MPR_SERVER_0* pMprServer;

	// A pointer to the port information buffer.
	RAS_PORT_0* pRasPorts;

	// The total number of ports across all servers.
	DWORD dwTotalPorts = 0;

	// The total number of active ports across all servers.
	DWORD dwPortsInUse = 0;

	// A buffer for API results.
	DWORD dwResult;

	// The busy threshold buffer.
	DOUBLE dThreshold;

	// MPR enumeration parameters.
	DWORD dwEntriesRead;
	DWORD dwTotalEntries;
	DWORD dwResumeHandle;

	// The device type that we know about.
	CString sDeviceType = BORF_KNOB_RRAS_MODEM;


	// Initialize the output parameter.
	dwBusyCount = 0;

	while( borfPosition )
	{
		// Initialize the resume handle.
		dwResumeHandle = NULL;

		// Get the next server handle.
		hMprServerHandle = oServerHandleList.GetNext( borfPosition );
		
		// Get the next server struct.
		dwResult = MprAdminServerGetInfo(
			hMprServerHandle,
			0,
			(LPBYTE*)&pMprServer );

		// Get all ports on this server.
		dwResult = MprAdminPortEnum(
			hMprServerHandle,
			0,
			INVALID_HANDLE_VALUE,
			(LPBYTE*)&pRasPorts,
			-1,
			&dwEntriesRead,
			&dwTotalEntries,
			&dwResumeHandle	);


		for( DWORD i = 0 ; i < dwEntriesRead ; i++ )
		{
			if( sDeviceType == pRasPorts[i].wszDeviceType )
			{
				// This port is a modem.
				dwTotalPorts += 1;

				if( pRasPorts[i].dwPortCondition != RAS_PORT_LISTENING )
				{
					// This port is busy or otherwise offline.
					dwPortsInUse += 1;
				}

			} // if 

		} // for

		// Release the port buffer.
		MprAdminBufferFree( pRasPorts );

		// Release the server buffer.
		MprAdminBufferFree( pMprServer );

	} // while

	// DEBUG:
	wcout << L"BorfServerIsBusy:";
	wcout << L" There are " << dwPortsInUse << L" of " << dwTotalPorts;
	wcout << L" ports in use." << endl;

	// DEBUG:
	dThreshold = (DOUBLE) dwPortsInUse / (DOUBLE) dwTotalPorts;
	wcout << L"BorfServerIsBusy: The system is " << dThreshold * 100 << L"% busy." << endl;

	// Handle the corner case.
	if( dwTotalPorts <= 0 ) { return S_FALSE; }
	
	
	while( dwPortsInUse > 0 )
	{
		// Compute the percentage of ports that are in use.
		dThreshold = (DOUBLE) dwPortsInUse / (DOUBLE) dwTotalPorts;

		// I hate fencepost math.
		if( dThreshold < options.dTimeBusyThreshold ) { break; }

		// Increment the overload count.
		dwBusyCount += 1;

		// Decrement the logical port count;
		dwPortsInUse -= 1;
	}

	
	// I love iterative computation.
	return S_OK;

} // borfServerIsBusy

// eof