/*
 * KClientSession
 *
 * Implementation of KClient session abstraction
 *
 * $Header: /cvs/kfm/KerberosFramework/KClient/Sources/KClient/KClientSession.cp,v 1.29 2003/02/26 04:09:24 lxs Exp $
 */

#include "KClientSession.h"
#include "KClientLoginIntf.h"
#include "KClientKerberosIntf.h"
#include "KClientCCacheIntf.h"
#include "KClientAddress.h"

// New client session
KClientSessionPriv::KClientSessionPriv ():
	mMagic (class_ID),
	mSessionType (session_Client),
	mLocalAddress (),
	mRemoteAddress (),
	mClientPrincipal (),
	mServerPrincipal (),
	mHaveSessionKey (false),
	mHaveChecksum (false),
	mChecksum (0),
	mCCache (),
	mCCacheContext (),
	mServerAuthenticatorData (nil)
{
	memset (&mSessionKey, 0, sizeof (mSessionKey));
	memset (&mKeySchedule, 0, sizeof (mKeySchedule));
}

// New server session
KClientSessionPriv::KClientSessionPriv (
	const UPrincipal&	inServicePrincipal):

	mMagic (class_ID),
	mSessionType (session_Server),
	mLocalAddress (),
	mRemoteAddress (),
	mClientPrincipal (),
	mServerPrincipal (inServicePrincipal.Clone ()),
	mHaveSessionKey (false),
	mHaveChecksum (false),
	mChecksum (0),
	mCCache (),
	mCCacheContext (),
	mServerAuthenticatorData (nil)
{
	memset (&mSessionKey, 0, sizeof (mSessionKey));
	memset (&mKeySchedule, 0, sizeof (mKeySchedule));
}


KClientSessionPriv::~KClientSessionPriv ()
{
}
	
const KClientAddressPriv&
KClientSessionPriv::GetLocalAddress () const
{
	return mLocalAddress;
}

void
KClientSessionPriv::SetLocalAddress (
	const KClientAddressPriv&	inLocalAddress)
{
	mLocalAddress = inLocalAddress;
}

const KClientAddressPriv&
KClientSessionPriv::GetRemoteAddress () const
{
	return mRemoteAddress;
}

void
KClientSessionPriv::SetRemoteAddress (
	const KClientAddressPriv&	inLocalAddress)
{
	mRemoteAddress = inLocalAddress;
}

const UPrincipal&
KClientSessionPriv::GetClientPrincipal () const
{
	return mClientPrincipal;
}

void
KClientSessionPriv::SetClientPrincipal (
	const UPrincipal& inPrincipal)
{
	mClientPrincipal = inPrincipal.Clone ();
	mCCache.Release ();
}

const UPrincipal&
KClientSessionPriv::GetServerPrincipal () const
{
	return mServerPrincipal;
}

void
KClientSessionPriv::SetServerPrincipal (
	const UPrincipal& inPrincipal)
{
	mServerPrincipal = inPrincipal.Clone ();
	memset (&mSessionKey, 0, sizeof (mSessionKey));
	memset (&mKeySchedule, 0, sizeof (mKeySchedule));
	mHaveSessionKey = 0;
}

void
KClientSessionPriv::GetExpirationTime (
	UInt32&						outExpiration)
{
	if (mSessionType == session_Client) {
		KClientLoginInterface::GetTicketExpiration (mClientPrincipal, outExpiration);
	} else {
		if (mServerAuthenticatorData == nil) {
			DebugThrow_ (KClientError (kcErrNoClientPrincipal));
		}
		outExpiration = mServerAuthenticatorData -> time_sec + 5 * 60 * mServerAuthenticatorData -> life;
	}
}

void
KClientSessionPriv::GetTicketForService (
			UInt32				inChecksum,
			void*				outBuffer,
			UInt32&				ioBufferLength)
{
	UInt32 checksum = inChecksum;
	// If the checksum is 0, generate a random one
	if (checksum == 0)
		checksum = ::PseudoRandom (outBuffer);

	// Login makes an implicit change to the state of the session, but we don't 
	// want that change to be permanent unless everything succeeds. So, on failure,
	// we restore the state
	StLoginHelper		loginHelper (this);
	
	try {
		Login (loginHelper);
		KClientKerberosInterface::GetTicketForService (GetServerPrincipal (),
			checksum, outBuffer, ioBufferLength);
		// Need to remember the checksum to verify the ticket later
		SetChecksum (checksum);
		UpdateSessionKey (loginHelper);
	} catch (...) {
		loginHelper.RestoreState ();
		throw;
	}
}	

void
KClientSessionPriv::Login ()
{
	// See comment in  GetTicketForService
	StLoginHelper	loginHelper (this);
	try {
		Login (loginHelper);
	} catch (...) {
		loginHelper.RestoreState ();
		throw;
	}
}

void
KClientSessionPriv::Login (
	const char*			inPassword)
{
	// See comment in  GetTicketForService
	StLoginHelper	loginHelper (this);
	try {
		Login (loginHelper, inPassword);
	} catch (...) {
		loginHelper.RestoreState ();
		throw;
	}
}

void
KClientSessionPriv::KeyFileLogin ()
{
	// See comment in  GetTicketForService
	StLoginHelper	loginHelper (this);
	try {
		KeyFileLogin (loginHelper);
	} catch (...) {
		loginHelper.RestoreState ();
		throw;
	}
}

void
KClientSessionPriv::Login (
	StLoginHelper&		inLoginHelper)
{
	// Save the state of the current default principal so that it can be
	// restored on failure (see comment in GetTicketForService)
	inLoginHelper.SaveState ();
	UpdateCCache (KClientLoginInterface::AcquireInitialTickets (inLoginHelper.GetClientPrincipal ()));
	UpdateClientPrincipal ();
}

void
KClientSessionPriv::Login (
	StLoginHelper&		inLoginHelper,
	const char*			inPassword)
{
	// Save the state of the current default principal so that it can be
	// restored on failure (see comment in GetTicketForService)
	inLoginHelper.SaveState ();
	UpdateCCache (KClientLoginInterface::AcquireInitialTicketsWithPassword (inLoginHelper.GetClientPrincipal (), inPassword));
	UpdateClientPrincipal ();
}

void
KClientSessionPriv::KeyFileLogin (
	StLoginHelper&		inLoginHelper)
{
	// Save the state of the current default principal so that it can be
	// restored on failure (see comment in GetTicketForService)
	inLoginHelper.SaveState ();
	KClientKerberosInterface::AcquireInitialTicketsFromKeyFile (inLoginHelper.GetClientPrincipal (), GetServerPrincipal (), mKeyFile);
	UpdateCCache (tkt_string ());
	UpdateClientPrincipal ();
}

void
KClientSessionPriv::Logout ()
{
	KClientLoginInterface::Logout (GetClientPrincipal ());
}

const KClientKey&
KClientSessionPriv::GetSessionKey () const
{
	if (!mHaveSessionKey)
		DebugThrow_ (KClientLogicError (kcErrNoSessionKey));
	return mSessionKey;
}

void
KClientSessionPriv::GetAuthenticatorForService (
	UInt32					inChecksum,
	const char*				inApplicationVersion,
	void*					outBuffer,
	UInt32&					ioBufferLength)
{
	UInt32 checksum = inChecksum;
	if (checksum == 0)
		checksum = ::PseudoRandom (outBuffer);
		
	// Login makes an implicit change to the state of the session, but we don't 
	// want that change to be permanent unless everything succeeds. So, on failure,
	// we restore the state
	StLoginHelper		loginHelper (this);
	
	try {
		Login (loginHelper);
		KClientKerberosInterface::GetAuthenticatorForService (GetServerPrincipal (),
			checksum, inApplicationVersion, outBuffer, ioBufferLength);
		// Need to remember the checksum to verify the ticket later
		SetChecksum (checksum);
		UpdateSessionKey (loginHelper);
	} catch (...) {
		loginHelper.RestoreState ();
		throw;
	}
}

void
KClientSessionPriv::VerifyEncryptedServiceReply (
	const void*				inBuffer,
	UInt32					inBufferLength)
{		
	KClientKerberosInterface::VerifyEncryptedServiceReply (inBuffer, inBufferLength, GetSessionKey (),
		GetKeySchedule (), GetLocalAddress (), GetRemoteAddress (), GetChecksum ());
}

void
KClientSessionPriv::VerifyProtectedServiceReply (
	const void*				inBuffer,
	UInt32					inBufferLength)
{		
	KClientKerberosInterface::VerifyProtectedServiceReply (inBuffer, inBufferLength, GetSessionKey (),
		GetLocalAddress (), GetRemoteAddress (), GetChecksum ());
}

void
KClientSessionPriv::Encrypt (
	const void*				inPlainBuffer,
	UInt32					inPlainBufferLength,
	void*					outEncryptedBuffer,
	UInt32&					ioEncryptedBufferLength)
{
	KClientKerberosInterface::Encrypt (inPlainBuffer, inPlainBufferLength, GetSessionKey (), GetKeySchedule (),
		GetLocalAddress (), GetRemoteAddress (), outEncryptedBuffer, ioEncryptedBufferLength);
}

void
KClientSessionPriv::Decrypt (
	void*					inEncryptedBuffer,
	UInt32					inEncryptedBufferLength,
	UInt32&					outPlainBufferOffset,
	UInt32&					outPlainBufferLength)
{
	KClientKerberosInterface::Decrypt (inEncryptedBuffer, inEncryptedBufferLength, GetSessionKey (), GetKeySchedule (),
		GetLocalAddress (), GetRemoteAddress (), outPlainBufferOffset, outPlainBufferLength);
}

void
KClientSessionPriv::ProtectIntegrity (
	const void*				inPlainBuffer,
	UInt32					inPlainBufferLength,
	void*					outProtectedBuffer,
	UInt32&					ioProtectedBufferLength)
{
	KClientKerberosInterface::ProtectIntegrity (inPlainBuffer, inPlainBufferLength, GetSessionKey (),
		GetLocalAddress (), GetRemoteAddress (), outProtectedBuffer, ioProtectedBufferLength);
}

void
KClientSessionPriv::VerifyIntegrity (
	void*					inProtectedBuffer,
	UInt32					inProtectedBufferLength,
	UInt32&					outPlainBufferOffset,
	UInt32&					outPlainBufferLength)
{
	KClientKerberosInterface::VerifyIntegrity (inProtectedBuffer, inProtectedBufferLength, GetSessionKey (),
		GetLocalAddress (), GetRemoteAddress (), outPlainBufferOffset, outPlainBufferLength);
}

void
KClientSessionPriv::VerifyAuthenticator (
	const void*				inBuffer,
	UInt32					inBufferLength)
{
	AUTH_DAT authenticatorData;

	KClientKerberosInterface::VerifyAuthenticator (GetServerPrincipal (), GetRemoteAddress (),
		inBuffer, inBufferLength, authenticatorData, GetKeyFile ());
	if (mServerAuthenticatorData == nil) {
		mServerAuthenticatorData = new AUTH_DAT;
	}
	*mServerAuthenticatorData = authenticatorData;
	mClientPrincipal = UPrincipal (
		UPrincipal::kerberosV4,
		mServerAuthenticatorData -> pname,
		mServerAuthenticatorData -> pinst,
		mServerAuthenticatorData -> prealm);
	
	SetSessionKey (*(KClientKey*) &mServerAuthenticatorData -> session);
}

void
KClientSessionPriv::GetEncryptedServiceReply (
	void*					inBuffer,
	UInt32&					ioBufferLength)
{
	KClientKerberosInterface::GetEncryptedServiceReply (GetChecksum () + 1, GetSessionKey (),
		GetKeySchedule (), GetLocalAddress (), GetRemoteAddress (), inBuffer, ioBufferLength);
}

void
KClientSessionPriv::GetProtectedServiceReply (
	void*					inBuffer,
	UInt32&					ioBufferLength)
{
	KClientKerberosInterface::GetProtectedServiceReply (GetChecksum () + 1, GetSessionKey (),
		GetLocalAddress (), GetRemoteAddress (), inBuffer, ioBufferLength);
}

void
KClientSessionPriv::AddServiceKey (
	UInt32					inVersion,
	const KClientKey&		inServiceKey)
{
	KClientKerberosInterface::AddServiceKey (GetKeyFile (), GetServerPrincipal (),
		inVersion, inServiceKey);
	SetSessionKey (inServiceKey);
}

// Update the session ccache to be the application default ccache
// Call this when this session should start using the tickets in the
// application default ccache
void
KClientSessionPriv::UpdateCCache (
	const std::string& inCCacheName)
{
	mCCache = mCCacheContext.Get ().OpenCCache (inCCacheName.c_str ());
	KClientKerberosInterface::SetDefaultCCache (inCCacheName);
}

// Update the session key from the credentials cache
void
KClientSessionPriv::UpdateSessionKey (
	StLoginHelper&			inLoginHelper)
{
	if (mSessionType == session_Client) {
		KClientKey	sessionKey;
		mCCache.GetCredentialsForService (GetServerPrincipal (), UPrincipal::kerberosV4).GetV4SessionKey (sessionKey.key);
		SetSessionKey (sessionKey);
	} else {
		UInt32	keyVersion = 0;
		GetServiceKey (keyVersion);
	}
}

// Freeze the session client principal to the current ccache principal
// Do not use SetClientPrincipal, because it resets the cache
void
KClientSessionPriv::UpdateClientPrincipal ()
{
	try {
		mClientPrincipal = mCCache.GetPrincipal (UPrincipal::kerberosV4);
	} catch (UCCacheLogicError& e) {
		if (e.Error () == ccErrBadCredentialsVersion) {
			// Loos like no v4 realm?
			throw KClientRuntimeError (kcErrInvalidPreferences);
		} else {
			throw;
		}
	}
}

void
KClientSessionPriv::SetChecksum (
	UInt32					inChecksum)
{
	mChecksum = inChecksum;
	mHaveChecksum = true;
}

const KClientKeySchedule& 
KClientSessionPriv::GetKeySchedule() const
{
	if (!mHaveSessionKey)
		DebugThrow_ (KClientLogicError (kcErrNoSessionKey));
	return mKeySchedule;
}

UInt32
KClientSessionPriv::GetChecksum () const
{
	if (!mHaveChecksum)
		DebugThrow_ (KClientLogicError (kcErrNoChecksum));
	return mChecksum;
}

void
KClientSessionPriv::SetSessionKey (
	const KClientKey&		inSessionKey)
{
	mSessionKey = inSessionKey;
	mHaveSessionKey = true;
	des_key_sched (mSessionKey.key, mKeySchedule.keySchedule);
}

void
KClientSessionPriv::SetKeyFile (
	const KClientFile&				inKeyFile)
{
	mKeyFile = inKeyFile;
}

const KClientKey&
KClientSessionPriv::GetServiceKey (
			UInt32&					inVersion)
{
	KClientKey newKey;
	KClientKerberosInterface::GetServiceKey (mKeyFile, mServerPrincipal, inVersion, newKey);
	SetSessionKey (newKey);
	return GetSessionKey ();
}

UCCache
KClientSessionPriv::GetCCacheReference ()
{
	// Reopen the cache, the caller closes it
	return mCCacheContext.GetCCacheForPrincipal (mClientPrincipal);
}

const UCCache&
KClientSessionPriv::GetCCache () {
	// Check that the ccache is still valid, and if not, reset it
	try {
		if (mCCache.Get () != nil) 
			UPrincipal::EVersion __attribute__ ((unused)) version = mCCache.GetCredentialsVersion ();
	} catch (UCCacheRuntimeError&	e) {
		// Rethrow anything that's not ccErrCCacheNotFound
		if (e.Error () != ccErrCCacheNotFound) 
			throw;
			
		// Handle ccErrCCacheNotFound by setting mCCache to nil; it will get reestablished
		// and pointed to a real ccache the next time it's needed
		mCCache.Reset (nil);
		mHaveChecksum = mHaveSessionKey = false;
	}
	
	return mCCache;
}


// When we are setting up a session, set the default ccache
StKClientSession::StKClientSession (
	KClientSession					inSession):
	mDefaultCCache (KClientKerberosInterface::DefaultCCache ())
{
	mSession = reinterpret_cast <KClientSessionPriv*> (inSession);
	if (!ValidateSession ())
		DebugThrow_ (KClientLogicError (kcErrInvalidSession));
		
	const UCCache&	ccache = mSession -> GetCCache ();
	if (ccache.Get () != nil)
		KClientKerberosInterface::SetDefaultCCache (ccache.GetName ().CString ());
}

StKClientSession::StKClientSession (
	KClientSessionPriv*				inSession):
	mDefaultCCache (KClientKerberosInterface::DefaultCCache ())
{
	mSession = inSession;

	const UCCache&	ccache = mSession -> GetCCache ();

	if (ccache.Get () != nil)
		KClientKerberosInterface::SetDefaultCCache (ccache.GetName ().CString ());
}

StKClientSession::~StKClientSession ()
{
	KClientKerberosInterface::SetDefaultCCache (mDefaultCCache);
}
	
KClientSessionPriv* 
StKClientSession::operator -> ()
{
	return mSession;
}

const KClientSessionPriv* 
StKClientSession::operator -> () const
{
	return mSession;
}

StKClientSession::operator KClientSession ()
{
	return reinterpret_cast <KClientSession> (mSession);
}

StKClientSession::operator KClientSessionPriv& ()
{
	return *mSession;
}

Boolean
StKClientSession::ValidateSession () const
{
	return mSession -> mMagic == KClientSessionPriv::class_ID;
}

UInt32
PseudoRandom (
	UInt32				inSeed)
{
	static Boolean	gHaveSeed = false;

	if (!gHaveSeed) {
		srand (inSeed);
		gHaveSeed = true;
	}

	return (UInt32) rand ();
}

UInt32
PseudoRandom (
	void*				inSeed)
{
	return PseudoRandom ((UInt32) inSeed);
}

KClientSessionPriv::StLoginHelper::StLoginHelper (
	KClientSessionPriv*			inSession):
	
	mSession (inSession) {
}

void
KClientSessionPriv::StLoginHelper::SaveState () {
	mSavedClientPrincipal = mSession -> mClientPrincipal;
	mSavedCCache = mSession -> mCCache;
}

void
KClientSessionPriv::StLoginHelper::RestoreState () {
	mSession -> mClientPrincipal = mSavedClientPrincipal;
	mSession -> mCCache = mSavedCCache;
}