/*
 * Copyright (C) 2009 - 2010 Funambol, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by
 * the Free Software Foundation with the addition of the following permission
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA.
 *
 * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite
 * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "Powered by Funambol" logo. If the display of the logo is not reasonably
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Powered by Funambol".
 */

/* $Id$ */

#include "serverexchange/session/MD5Session.h"
#include "Logger/LoggerMacroses.h"
#include "Utils.h"
#include <syncml/core/NextNonce.h>

using namespace NS_DM_Client;
using namespace NS_DM_Client::NS_Communication;
using namespace NS_DM_Client::NS_SyncMLCommand;

static const char * c_LogName = "MD5Session";

MD5Session::MD5Session(ConnectionInfo &info) :
	Session(info), m_credentialsSent(false)
{
}


MD5Session::~MD5Session()
{
}


SStatusCommand* MD5Session::CreateSyncHdrStatus(int code, const char * responseURI)
{
	SStatusCommand *status = new SStatusCommand(code);
	status->SetMsgRef(m_connectionInfo.LastServerMessageID());
	status->SetCmdRef("0");
	status->SetCmd("SyncHdr");
	
	if (m_connectionInfo.devinf.get())//->GetDevInf())
	{
		status->SetTargetRef(m_connectionInfo.devinf->getDevID());
		status->SetSourceRef(m_connectionInfo.GetSessionURL());
	}
	
	Funambol::Meta meta;
	Funambol::NextNonce nonce(m_connectionInfo.acconfig.getServerNonce());
	meta.setType(GetTypeName());
	meta.setFormat("b64");
	meta.setNextNonce(&nonce);
	
	Funambol::Chal chal(&meta);
	status->Prepare();
	status->Internal()->setChal(&chal);

	return status;
}


bool MD5Session::CredentialsSent()
{
	return m_credentialsSent;
}


bool MD5Session::IsServerCredValid(Funambol::Cred *cred)
{
	if (cred && cred->getData() && cred->getFormat() && cred->getType())
	{
		const char *data   = cred->getData();
		
		// check format and data are b64 text
		const char *format = cred->getFormat();
		const char *type   = cred->getType();
		
		if (!strcmp(AUTH_TYPE_MD5, type) && !strcmp("b64", format))
		{
			char *localmd5 = Funambol::MD5CredentialData(m_connectionInfo.acconfig.getServerID(), 
														 m_connectionInfo.acconfig.getServerPWD(), 
														 m_connectionInfo.acconfig.getServerNonce());
			GDLDEBUG("Local md5 calc: %s", localmd5);
//			GDLDEBUG("on auth data: ['%s' '%s' '%s']", 
//					 m_connectionInfo.acconfig.getServerID(), 
//					 m_connectionInfo.acconfig.getServerPWD(), 
//					 m_connectionInfo.acconfig.getServerNonce());
			
			bool res = localmd5 && data && !strcmp(localmd5, data);
			SAFE_DELETE_ARR(localmd5);
			return res;
		}
	}
	return false;
}


bool MD5Session::ReadChal(Funambol::Chal &chal)
{
	Funambol::Meta *pMeta  = chal.getMeta();
	if (!pMeta) 
		return false;

	const char *pType = pMeta->getType();
		
	if (!strcmp(AUTH_TYPE_MD5, pType) || !strcmp(AUTH_TYPE_MAC, pType))
	{
		if (strcmp(pType, GetTypeName()))			
			m_connectionInfo.SwitchToSessionType(pType);

		// todo remove
		m_connectionInfo.SetUseHMAC(!strcmp(AUTH_TYPE_MAC, pType)); // switch to HMAC checking auth scheme
		
		// Note: b64NextNoce string is allocated with new[] inside of NextNonce class!
		const char *b64NextNoce = NULL;
		if (pMeta->getNextNonce())
		{
			b64NextNoce = pMeta->getNextNonce()->getValueAsBase64();
		}
		else
			return false;
		
		if (!b64NextNoce)
		{
			return false;
		}
		else
		{
			m_connectionInfo.acconfig.setClientNonce(b64NextNoce); // TODO? store NextNonce into tree
			GDLDEBUG("NextNonce >%s< will be used for the next connection to server", b64NextNoce);

			// also set the auth type to the pType - this will auto trigger certain connection type on next tryout
			m_connectionInfo.acconfig.setClientAuthType(pType); // AUTH_TYPE_MD5 || AUTH_TYPE_MAC
			
			if (!strcmp(AUTH_TYPE_MAC, pType))
			{
				// set nonce for server
				Funambol::NextNonce *pNonce = CreateNonce();
				const char *nonce64 = pNonce->getValueAsBase64();
				m_connectionInfo.acconfig.setServerNonce(nonce64);
				SAFE_DELETE_ARR(nonce64);
				SAFE_DELETE(pNonce);
			}
		}
		
		SAFE_DELETE_ARR(b64NextNoce);
	}
	return true;
}


void MD5Session::insertSessionInitiationCommands(int code)
{
	GDLDEBUG("m_firstMessage %d", m_firstMessage);
	if (m_firstMessage)
	{
		insertAlertReplace(code);
		m_firstMessage = false;
	}
	else
	{
		if (m_connectionInfo.repeatAuthentication)
		{
			GDLDEBUG("Post Alert&Replace");
			insertAlertReplace(code);
		}

		
		if (m_state.ServerAuthenticated)
		{
			GDLDEBUG("ServerAuthenticated, do not send Status with Nonce");
		}
		else
		{
			Funambol::NextNonce *pNonce = CreateNonce();
			const char *nonce64 = pNonce->getValueAsBase64();
			m_connectionInfo.acconfig.setServerNonce(nonce64);
			SAFE_DELETE_ARR(nonce64);
			SAFE_DELETE(pNonce);

			GDLDEBUG("Post status for SyncHdr");
			SStatusCommand *status = 
				CreateSyncHdrStatus(m_state.ClientAuthenticated ? AUTHENTICATION_ACCEPTED : INVALID_CREDENTIALS, 
									m_connectionInfo.GetSessionURL());
			m_commandsToSend.InsertFirst(SCommandPtr(status));
			m_credentialsSent = true;
		}
	}
}


const char * MD5Session::GetTypeName()
{
	return AUTH_TYPE_MD5;
}
