debugsrv/runmodedebug/securityserver/src/c_security_svr_server.cpp
branchRCL_3
changeset 21 52e343bb8f80
parent 20 ca8a1b6995f6
child 22 e26895079d7c
--- a/debugsrv/runmodedebug/securityserver/src/c_security_svr_server.cpp	Tue Aug 31 16:45:49 2010 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,685 +0,0 @@
-// Copyright (c) 2006-2009 Nokia Corporation and/or its subsidiary(-ies).
-// All rights reserved.
-// This component and the accompanying materials are made available
-// under the terms of the License "Eclipse Public License v1.0"
-// which accompanies this distribution, and is available
-// at the URL "http://www.eclipse.org/legal/epl-v10.html".
-//
-// Initial Contributors:
-// Nokia Corporation - initial contribution.
-//
-// Contributors:
-//
-// Description:
-// Provides the debug security server server implementation.
-// 
-//
-
-/**
- @file
- @internalTechnology
- @released
-*/
-
-#include <e32base.h>
-#include <e32base_private.h>
-#include <rm_debug_api.h>
-#include "c_process_pair.h"
-#include "c_security_svr_session.h"
-#include "c_security_svr_server.h"
-#include "rm_debug_logging.h"
-
-using namespace Debug;
-
-/**
-Server constructor, sessions are created as ESharableSessions, meaning that
-each session will be used by at most one debug agent
-*/
-CSecuritySvrServer::CSecuritySvrServer(CActive::TPriority aActiveObjectPriority)
-	: CServer2(aActiveObjectPriority, ESharableSessions),
-	  iSessionCount(0),
-	  iShutdown()
-	{
-	LOG_MSG("CSecuritySvrServer::CSecuritySvrServer()\n");
-	}
-
-/**
-Standard implementation
-
-@return pointer to new CSecuritySvrServer object
-*/
-CSecuritySvrServer* CSecuritySvrServer::NewLC()
-	{
-	LOG_MSG("CSecuritySvrServer::NewLC()\n");
-
-	CSecuritySvrServer* self=new(ELeave) CSecuritySvrServer(EPriorityStandard);
-	CleanupStack::PushL(self);
-	self->ConstructL();
-	return self;
-	}
-
-/**
-Server destructor, performs cleanup for the server
-*/
-CSecuritySvrServer::~CSecuritySvrServer()
-	{
-	LOG_MSG("CSecuritySvrServer::~CSecuritySvrServer()\n");
-
-	// stop the kernel side driver
-	iKernelDriver.Close();
-	User::FreeLogicalDevice(KDebugDriverName);
-
-	//deallocate both the debug maps
-	iPassiveDebugMap.ResetAndDestroy();
-	iActiveDebugMap.ResetAndDestroy();
-	}
-
-/**
-Starts the server and constructs and starts the servers shutdown timer
-*/
-void CSecuritySvrServer::ConstructL()
-	{
-	LOG_MSG("CSecuritySvrServer::ConstructL()");
-
-	StartL(KSecurityServerName);
-	iShutdown.ConstructL();
-	iShutdown.Start();
-
-	//load the kernel driver
-	TInt err = User::LoadLogicalDevice(KDebugDriverFileName);
-	if(! ((KErrNone == err) || (KErrAlreadyExists == err)))
-		{
-		User::Leave(err);
-		}
-	//create an information object for initialising the driver
-	TRM_DebugDriverInfo driverInfo;
-	driverInfo.iUserLibraryEnd = 0;
-	User::LeaveIfError(iKernelDriver.Open(driverInfo));
-	}
-
-/**
-Creates a new session with the DSS. A version check is done to ensure that an
-up to date version of the DSS is available (according to the DA's needs).
-The device driver is loaded if necessary and a session with the server and a 
-handle to the driver opened.
-
-@param aRequiredVersion the minimal version of the DSS required by the DA
-
-@return a pointer to the new sever session, or NULL if any of the 
-        initialisation process failed
-*/
-CSession2* CSecuritySvrServer::NewSessionL(const TVersion& aRequiredVersion, const RMessage2& aMessage) const
-//
-// Session constructor
-//
-	{
-	LOG_ARGS("version=%d.%d.%d", aRequiredVersion.iMajor, aRequiredVersion.iMinor, aRequiredVersion.iBuild);
-
-	//assert compatible version
-	TVersion currentVersion(KDebugServMajorVersionNumber, KDebugServMinorVersionNumber, KDebugServPatchVersionNumber);
-	if(!User::QueryVersionSupported(currentVersion, aRequiredVersion))
-		{
-		LOG_MSG("Requested version not compatible with this version. Asked for %d.%d.%d but this is %d.%d.%d", aRequiredVersion.iMajor, aRequiredVersion.iMinor, aRequiredVersion.iBuild, KDebugServMajorVersionNumber, KDebugServMinorVersionNumber, KDebugServPatchVersionNumber);
-		User::Leave(KErrNotSupported);
-		}
-	
-	//get the debug agent's process id
-	RThread clientThread;
-	User::LeaveIfError(aMessage.Client(clientThread));
-	CleanupClosePushL(clientThread);
-	RProcess clientProcess;
-	User::LeaveIfError(clientThread.Process(clientProcess));
-	CleanupStack::PopAndDestroy(&clientThread);
-	TProcessId processId = clientProcess.Id();
-	clientProcess.Close();
-
-	//create session
-	LOG_MSG("About to call new(ELeave) CSecuritySvrSession()");
-	CSecuritySvrSession* servSession = new(ELeave) CSecuritySvrSession(processId);
-
-	CleanupStack::PushL(servSession);
-	servSession->ConstructL();
-	CleanupStack::Pop(servSession);
-	return servSession;
-	}
-
-/**
-Manages requests from debug agents to attach to target debug processes
-
-Given the debug agent process ID and the target process name:
-(1) checks whether the pair is already in either of the debug maps, if so
-    then returns KErrAlreadyExists
-(2) if aPassive == ETrue then just add the pair to the passive map and return
-    whatever the return value of the array write was
-(3) if aPassive == EFalse then check whether the target debug process is 
-    already reserved by another debug agent. If it is then return KErrInUse,
-    otherwise add the pair to the active debug map and return the status 
-    value of the array write.
-
-@param aTargetProcessName original FileName of the process to attach to
-@param aDebugAgentProcessId process ID of the debug agent
-@param aPassive ETrue if wish to attach passively, EFalse otherwise
-
-@return KErrNone if successfully attached, otherwise another system wide error
-        code as above
-*/
-TInt CSecuritySvrServer::AttachProcessL(const TDesC& aTargetProcessName, const TProcessId aDebugAgentProcessId, const TBool aPassive)
-	{
-	//store the pair of values
-	LOG_MSG3("CSecuritySvrServer::AttachProcessL() 0x%lx aPassive=%d", aDebugAgentProcessId.Id(), aPassive);
-
-	CProcessPair *processPair = CProcessPair::NewL(aTargetProcessName, aDebugAgentProcessId);
-	if(processPair == NULL)
-		return KErrNoMemory;
-
-	//check whether the pair already exists in the active debug map
-	for(TInt i=0; i<iActiveDebugMap.Count(); i++)
-		{
-        // Note that the equality is of the debug agent id and the proc name
-		if(*processPair == *(iActiveDebugMap[i]))
-			{
-			//process already exists
-			LOG_MSG( "  AttachProcessL() error : KErrAlreadyExists in active map\n" );
-			delete processPair;
-			return KErrAlreadyExists;
-			}
-		}
-
-	//check whether the pair already exists in the passive map
-	for(TInt i=0; i<iPassiveDebugMap.Count(); i++)
-		{
-		if(*processPair == *(iPassiveDebugMap[i]))
-			{
-			//process already exists
-			LOG_MSG( "  AttachProcessL() error : KErrAlreadyExists in passive map\n" );
-			delete processPair;
-			return KErrAlreadyExists;
-			}
-		}
-
-	if(aPassive)
-		{
-		//just add the pair and return
-		TInt err = iPassiveDebugMap.Append(processPair);
-		if(err != KErrNone)
-			{
-			// couldn't add pair for some unknown reason, so delete the pair
-			LOG_MSG2( "  AttachProcessL() error %d appending passive process pair", err );
-			delete processPair;
-			}
-		return err;
-		}
-	else
-		{
-		//check whether the process Id has already been reserved
-		for(TInt i=0; i<iActiveDebugMap.Count(); i++)
-			{
-            // Now check if this is already debugged, but only if its for a particular process.
-            // We can have several entries from AttachAll calls, and its ok to add them
-			if( (!processPair->ProcessNameMatches(_L("*"))) &&
-			    (processPair->ProcessNameMatches(*(iActiveDebugMap[i])) ) )
-				{
-				//process already being debugged
-				LOG_MSG( "  AttachProcessL() error : process already being debugged" );
-				delete processPair;
-				return KErrInUse;
-				}
-			}
-		//try to add the pair. 
-		TInt err = iActiveDebugMap.Append(processPair);
-		if(err != KErrNone)
-			{
-			// couldn't add pair for some unknown reason, so delete the pair
-			LOG_MSG2( "  AttachProcessL() error %d inserting active process pair", err );
-			delete processPair;
-			}
-		return err;
-		}
-	}
-
-/*
-Detach from debugging the specified process
-
-@param aTargetProcessName name of the process to detach from
-@param aDebugAgentProcessId process ID of the debug agent
-
-@return KErrNone if successfully detached, KErrNotFound if an attempt is made
-        to detach from a process which the debug agent hasn't previously attached to
-*/
-TInt CSecuritySvrServer::DetachProcess(const TDesC& aTargetProcessName, const TProcessId aDebugAgentProcessId)
-	{
-    LOG_MSG2( "CSecuritySvrServer::DetachProcess() for agent with id 0x%lx", aDebugAgentProcessId.Id() );
-
-	//check whether the pair is in the active debug map
-	for(TInt i=0; i<iActiveDebugMap.Count(); i++)
-		{
-		if(iActiveDebugMap[i]->Equals(aTargetProcessName, aDebugAgentProcessId))
-			{
-			//remove the process pair from the active debug map
-			delete iActiveDebugMap[i];
-			iActiveDebugMap.Remove(i);
-			return KErrNone;
-			}
-		}
-
-	//check whether the pair is in the passive debug map
-	for(TInt i=0; i<iPassiveDebugMap.Count(); i++)
-		{
-		if(iPassiveDebugMap[i]->Equals(aTargetProcessName, aDebugAgentProcessId))
-			{
-			//remove the process pair from the active debug map
-			delete iPassiveDebugMap[i];
-			iPassiveDebugMap.Remove(i);
-			return KErrNone;
-			}
-		}
-
-	//process pair wasn't in either map
-	return KErrNotFound;
-	}
-
-/**
-Given a debug agent process ID, removes all references to that debug agent
-from the debug maps
-
-@param aMessage message from the debug agent
-
-@return returns KErrNone if successful, another system wide error code otherwise
-*/
-void CSecuritySvrServer::DetachAllProcesses(const TProcessId aDebugAgentProcessId)
-	{
-	//check whether the debug agent process ID is in the active debug map
-	for(TInt i=iActiveDebugMap.Count()-1; i>=0; i--)
-		{
-		if(iActiveDebugMap[i]->ProcessIdMatches(aDebugAgentProcessId))
-			{
-			//remove the process pair from the active debug map
-			delete iActiveDebugMap[i];
-			iActiveDebugMap.Remove(i);
-			}
-		}
-
-	//check whether the debug agent process ID is in the passive debug map
-	for(TInt i=iPassiveDebugMap.Count()-1; i>=0; i--)
-		{
-		if(iPassiveDebugMap[i]->ProcessIdMatches(aDebugAgentProcessId))
-			{
-			//remove the process pair from the passive debug map
-			delete iPassiveDebugMap[i];
-			iPassiveDebugMap.Remove(i);
-			}
-		}
-	}
-
-/*
-Check whether the specified debug agent is attaced to the specfied target
-process.
-
-@param aTargetThreadId thread ID of a thread in the target process
-@param aMessage a message which originates with the debug agent
-@param aPassive if EFalse then checks whether the debug agent is the active
-       debugger of the target process. If ETrue then checks whether the debug
-       agent is attached to the target process, irrespective of whether it is
-       attached passively or actively
-
-@return ETrue if attached, EFalse otherwise
-*/
-TBool CSecuritySvrServer::CheckAttached(const TThreadId aTargetThreadId, const RMessage2& aMessage, const TBool aPassive)
-	{
-	
-	//get a handle to the target thread
-	RThread targetThread;
-	TInt err = targetThread.Open(aTargetThreadId);
-	if(err != KErrNone)
-		{
-		return EFalse;
-		}
-
-	//get a handle to the target process
-	RProcess targetProcess;
-	err = targetThread.Process(targetProcess);
-	//finshed with the thread handle so close it
-	targetThread.Close();
-	if(err != KErrNone)
-		return EFalse;
-
-	//get the target process' file name
-	TFileName targetFileName = targetProcess.FileName();
-
-	// Tamperproofing. Ensure that the debug agent really has a superset
-	// of the target process PlatSec capabilities, as authorised
-	// by an OEM Debug Token (if any)
-
-	TSecurityInfo targetSecInfo(targetProcess);
-
-	// Now compare the capabilities, to ensure the DebugAgent has been authorised with
-	// sufficient capabilities from its OEM Debug token
-	CSecuritySvrSession* session = (CSecuritySvrSession*)aMessage.Session();
-
-	// Presume we need to check the target process is debuggable unless a valid OEM Debug token in effect?
-	if (!OEMTokenPermitsDebugL(session->GetOEMDebugCapabilities(), targetSecInfo.iCaps) )
-		{
-		// No debug token therefore check if the process is debuggable
-		err = iKernelDriver.IsDebuggable(targetProcess.Id());
-		}
-
-	//finished with the process handle so close it
-	targetProcess.Close();
-
-	if (err != KErrNone)
-	{
-		// The process was not marked as debuggable by the loader, and the OEM
-		// debug token did not override the lack of a debuggable bit.
-		// The process was not marked as debuggable by the loader
-		return EFalse;
-	}
-
-	return CheckAttachedProcess(targetFileName, aMessage, aPassive);
-	}
-
-/*
-Check whether the specified debug agent is attaced to the specfied target
-process.
-
-@param aTargetProcessId process ID of the target process
-@param aMessage a message which originates with the debug agent
-@param aPassive if EFalse then checks whether the debug agent is the active
-       debugger of the target process. If ETrue then checks whether the debug
-       agent is attached to the target process, irrespective of whether it is
-       attached passively or actively
-
-@return ETrue if attached, EFalse otherwise
-*/
-TBool CSecuritySvrServer::CheckAttached(const TProcessId aTargetProcessId, const RMessage2& aMessage, const TBool aPassive)
-	{
-	//get a handle to the target process
-	RProcess targetProcess;
-	TInt err =targetProcess.Open(aTargetProcessId);
-	if(err != KErrNone)
-		{
-		return EFalse;
-		}
-
-	//get the target process' file name
-	TFileName targetFileName = targetProcess.FileName();
-
-	// Tamperproofing. Ensure that the debug agent really has a superset
-	// of the target process PlatSec capabilities, as authorised
-	// by an OEM Debug Token (if any)
-
-	TSecurityInfo targetSecInfo(targetProcess);
-
-	// Now compare the capabilities, to ensure the DebugAgent has been authorised with
-	// sufficient capabilities from its OEM Debug token
-	CSecuritySvrSession* session = (CSecuritySvrSession*)aMessage.Session();
-
-	// Presume we need to check the target process is debuggable unless a valid OEM Debug token in effect?
-	if ( !OEMTokenPermitsDebugL(session->GetOEMDebugCapabilities(), targetSecInfo.iCaps) )
-		{
-		// No debug token therefore check if the process is debuggable
-		err = iKernelDriver.IsDebuggable(targetProcess.Id());
-		}
-
-	//finished with the process handle so close it
-	targetProcess.Close();
-
-	if (err != KErrNone)
-	{
-		return EFalse;
-	}
-
-	return CheckAttachedProcess(targetFileName, aMessage, aPassive);
-	}
-
-/*
-Check whether the specified debug agent is attaced to the specfied target
-process.
-
-@param aTargetProcessName 
-@param aMessage a message which originates with the debug agent
-
-@return ETrue if attached, EFalse otherwise
-*/
-TBool CSecuritySvrServer::CheckAttachedProcess(const TDesC& aTargetProcessName, const RMessage2& aMessage, const TBool aPassive) const
-	{
-	//get the debug agent's process id
-	TProcessId clientProcessId = 0;
-	TInt err = GetProcessIdFromMessage(clientProcessId, aMessage);
-	if(err != KErrNone)
-		return EFalse;
-
-	//check permissions
-	if(aPassive)
-		return IsDebugger(aTargetProcessName, clientProcessId);
-	else
-		return IsActiveDebugger(aTargetProcessName, clientProcessId);
-	}
-
-/**
-Tests whether the debug agent is attached actively to the target debug process
-
-@param aTargetProcessName target debug process' FileName
-@param aDebugAgentProcessId process ID of a debug agent
-
-@return ETrue if the specified debug agent is actively attached to the 
-        specified target debug process, EFalse otherwise
-*/
-TBool CSecuritySvrServer::IsActiveDebugger(const TDesC& aTargetProcessName, const TProcessId aDebugAgentProcessId) const
-	{
-	//check whether the pair is in the active debug map
-	for(TInt i=0; i<iActiveDebugMap.Count(); i++)
-		{
-		// If both match, which can include aTargetProcessName being "*"
-		if(iActiveDebugMap[i]->Equals(aTargetProcessName, aDebugAgentProcessId))
-			{
-			LOG_MSG2( "CSecuritySvrServer::IsActiveDebugger() for agent id 0x%lx matches name and pid", 
-					aDebugAgentProcessId.Id() );			
-			return ETrue;
-			}
-		// if the pid matches and the name in the map is "*", indicating that 
-		// this is an attach all agent, and thus debugging 
-		if( iActiveDebugMap[i]->ProcessIdMatches(aDebugAgentProcessId) &&
-			iActiveDebugMap[i]->ProcessNameMatches(_L("*")) )
-			{
-			LOG_MSG2( "CSecuritySvrServer::IsActiveDebugger() for AttachAll agent id 0x%lx", 
-				aDebugAgentProcessId.Id() );
-			return ETrue;
-			}
-		}
-	//not found so return false
-	return EFalse;
-	}
-
-/**
-Tests whether the target process is being debugged
-
-@param aTargetProcessName target process' FileName
-@param aPassive indicates whether to check for the process being actively debugged,
-or passively debugged
-
-@return ETrue if the specified target process is being debugged,
-        EFalse otherwise
-*/
-TBool CSecuritySvrServer::IsDebugged(const TDesC& aTargetProcessName, const TBool aPassive) const
-	{
-	//get a reference to the appropriate list
-	const RPointerArray<CProcessPair>& map = (aPassive) ? iPassiveDebugMap : iActiveDebugMap;
-
-	//iterate through the map trying to match the aTargetProcessName
-	for(TInt i=0; i<map.Count(); i++)
-		{
-		if(map[i]->ProcessNameMatches(aTargetProcessName))
-			{
-			return ETrue;
-			}
-		}
-	return EFalse;
-	}
-
-/**
-Tests whether the debug agent is attached to the target debug process
-
-@param aTargetProcessName target debug process' FileName
-@param aDebugAgentProcessId process ID of a debug agent
-
-@return ETrue if the specified debug agent is attached to the 
-        specified target debug process (regardless of whether it is attached
-	passively or actively), EFalse otherwise
-*/
-TBool CSecuritySvrServer::IsDebugger(const TDesC& aTargetProcessName, const TProcessId aDebugAgentProcessId) const
-	{
-	//check whether the pair is in the active debug map
-	if(IsActiveDebugger(aTargetProcessName, aDebugAgentProcessId))
-		return ETrue; 
-
-	//check whether the pair is in the passive debug map
-	for(TInt i=0; i<iPassiveDebugMap.Count(); i++)
-		{
-		if(iPassiveDebugMap[i]->Equals(aTargetProcessName, aDebugAgentProcessId))
-			return ETrue;
-		}
-	//not found so return false
-	return EFalse;
-	}
-
-/**
-Decrements the server's count of how many sessions are connected to it and
-starts the shutdown timer if there are no sessions connected
-*/
-void CSecuritySvrServer::SessionClosed()
-	{
-	if(--iSessionCount < 1)
-		{
-		iShutdown.Start();
-		}
-	}
-
-/**
-Increments the servers count of how many sessions are connected to it and
-cancels the shutdown timer if it is running
-*/
-void CSecuritySvrServer::SessionOpened()
-	{
-	iSessionCount++;
-	iShutdown.Cancel();
-	}
-
-/** 
-  Get the process id of the thread which sent aMessage
-  @param aProcessId process id of the thread which sent aMessage
-  @param aMessage message object sent by thread 
-
-  @return KErrNone if aProcessId could be set, or one of the system wide error codes if not
-  */
-TInt CSecuritySvrServer::GetProcessIdFromMessage(TProcessId& aProcessId, const RMessage2& aMessage) const
-	{
-	//get the debug agent's thread
-	RThread clientThread;
-	TInt err = aMessage.Client(clientThread);
-	if(err != KErrNone)
-		{
-		return err;
-		}
-
-	//get the debug agent's process
-	RProcess clientProcess;
-	err = clientThread.Process(clientProcess);
-
-	//finished with the thread handle so close it
-	clientThread.Close();
-
-	//check if there was an error from getting the process
-	if(err != KErrNone)
-		{
-		return err;
-		}
-
-	//get the debug agent's process id
-	aProcessId = clientProcess.Id();
-
-	//finished with the process handle so close it
-	clientProcess.Close();
-
-	return KErrNone;
-	}
-
-/**
-  Helper function which determines whether the capabilities of the
-  OEM Token are sufficient to permit debug of an application.
-
-  Normally, we use the AllFiles capability as a proxy which
-  means a Debug Agent can debug non-debuggable executables,
-  provided it has a superset of the capabilities of the executable
-  to be debugged.
- 
-  However, this causes the problem that all OEM Debug Tokens implicitly
-  give the power to debug an AllFiles executable, even if all that
-  is really needed is the power to debug an app with no capabilities,
-  or capabilities other than AllFiles.
-  
-  To address this, we treat the AllFiles capability in a special way.
-  The AllFiles capability in a token is taken to mean that an OEM has
-  signed the token, and hence can debug other executables. But this does
-  not inclue the ability to debug an AllFiles executable. To debug an AllFiles
-  executable, the token must also have TCB.
-
-  @param aTokenCaps - The PlatSec capabilities of a token
-  @param aTargetCaps - The PlatSec capabilities of a target app to be debugged
-
-  @return ETrue if authorised for debugging, EFalse otherwise.
-
-  @leave Any system error code.
-  */
-TBool CSecuritySvrServer::OEMTokenPermitsDebugL(const TCapabilitySet aTokenCaps, const TCapabilitySet aTargetCaps)
-	{	
-	LOG_MSG("CSecuritySvrSession::OEMTokenPermitsDebugL\n");
-
-	// Is the token valid - i.e. does it have AllFiles.
-	if ( !aTokenCaps.HasCapability(ECapabilityAllFiles) )
-		{
-		// Token is not valid, as it does not have AllFiles.
-		LOG_MSG("CSecuritySvrSession::OEMTokenPermitsDebugL - Token does not have AllFiles\n");
-			
-		return EFalse;
-		}
-
-	// Token MUST have a strict superset of capabilities
-	if ( !aTokenCaps.HasCapabilities(aTargetCaps) )
-		{
-		// Token does not have at least all the capabilities of the target
-		LOG_MSG("CSecuritySvrSession::OEMTokenPermitsDebugL - Token does not have superset of target capabilities\n");
-
-		return EFalse;
-		}
-
-	// Special case: If the target has AllFiles, the Token must have TCB
-	if ( aTargetCaps.HasCapability(ECapabilityAllFiles) )
-		{
-		// Target has AllFiles, so does the Token have TCB?
-		if ( !aTokenCaps.HasCapability(ECapabilityTCB) )
-			{
-			// Token does not have TCB.
-			LOG_MSG("CSecuritySvrSession::OEMTokenPermitsDebugL - Token does not have TCB when target has AllFiles\n");
-	
-			return EFalse;
-			}
-		}
-
-	// If we have passed all the above tests, the token permits debug
-	return ETrue;
-	}
-
-/**
- * This looks at a debug tokens capability and ensures it is sufficient 
- * to provide access to the flash partition
- * @param aTokenCaps Capabilties of the Token
- * @return TBool Whether or not flash access is permitted
- */
-TBool CSecuritySvrServer::OEMTokenPermitsFlashAccessL(const TCapabilitySet aTokenCaps)
-	{	
-	//Must have TCB to access flash
-	return aTokenCaps.HasCapability(ECapabilityTCB);
-	}
-
-//eof
-