/*
 * 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 "hash.h"
#include "serverexchange/session/HMACSession.h"
#include "Logger/LoggerMacroses.h"
#include "Utils.h"

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

#define PROPERTY_HMAC          "X-Syncml-Hmac"
static const char * c_LogName = "HMACSession";

HMACSession::HMACSession(ConnectionInfo &info) :
	MD5Session(info)
{
}


HMACSession::~HMACSession()
{
}


bool HMACSession::IsResponseValid(const char *reponse, uint resplength)
{
	const char * hmac = m_pTransportAgent->getResponseProperty(PROPERTY_HMAC);
	
	if (!hmac) {
		GDLDEBUG("HMAC string is empty; authenticated %s", State().ServerAuthenticated ? "in previous messages" : "- no");
		if (State().ServerAuthenticated)
			return true;
		else
			return false;
	}

	const char *username = m_connectionInfo.acconfig.getServerID();
	const char *password = m_connectionInfo.acconfig.getServerPWD();
	const char *b64nonce = m_connectionInfo.acconfig.getServerNonce();

	if (!username) username = "";
	if (!password) password = "";
	if (!b64nonce) b64nonce = "";
	
	const char *hmac_b64 = 
		NS_DM_Client::NS_Common::calculateHMAC(username, password, b64nonce, reponse, resplength);
//	GDLDEBUG("server auth data: ['%s' '%s' '%s']", username, password, b64nonce);
	GDLDEBUG("HMAC locally calculated: %s", hmac_b64);
	
	
	// x-syncml-hmac: algorithm=MD5, username="some_user",  mac=<hmac_in_b64>
	// retrieve "username" field
	const char * str = strstr(hmac, "username=");
	if (strlen(str) > strlen("username="))
		str += strlen("username=")+1;
	const char *usrbegin = str;
	while (*str != '"' && *str != '\0' && *str != '\10' && *str != '\13')
		str++;
	char *srv_hmac_username = new char[str-usrbegin+1];
	__strncpy(srv_hmac_username, usrbegin, str-usrbegin);
	srv_hmac_username[str-usrbegin] = '\0';
	
	bool go_on = !strcmp(username, srv_hmac_username);
	if (!go_on) {
		GDLDEBUG("server's HMAC calclated on username '%s', locally stored one is '%s'", srv_hmac_username, username);
	}

	SAFE_DELETE_ARR(srv_hmac_username);
	if (!go_on) {
		SAFE_DELETE_ARR(hmac_b64);
		return false;
	}
	
	// retrieve "mac" field
	str = strstr(hmac, "mac=");
	if (strlen(str) > strlen("mac="))
		str += strlen("mac=");
	const char *macbegin = str;
	while (*str != ',' && *str != ' ' && *str != '\0' && *str != '\r' && *str != '\n')
	{
		str++;
	}
	char *hmac_mac = new char[str-macbegin+1];
	__strncpy(hmac_mac, macbegin, str-macbegin);
	hmac_mac[str-macbegin] = '\0';
	
	GDLDEBUG("HMAC in response header: %s", hmac_mac);
	go_on = !strcmp(hmac_b64, hmac_mac);
	SAFE_DELETE_ARR(hmac_b64);
	SAFE_DELETE_ARR(hmac_mac);
	SAFE_DELETE_ARR(hmac);
	
	return go_on;
}


bool HMACSession::IsServerCredValid(Funambol::Cred *)
{
	return false;
}


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

		Funambol::NextNonce *pNonce = CreateNonce();
		const char *nonce64 = pNonce->getValueAsBase64();
		m_connectionInfo.acconfig.setServerNonce(nonce64);
		SAFE_DELETE_ARR(nonce64);
		SAFE_DELETE(pNonce);
		
		
		SStatusCommand *status = 
			CreateSyncHdrStatus(m_state.ServerAuthenticated ? OK : INVALID_CREDENTIALS, 
								m_connectionInfo.GetSessionURL());
		m_commandsToSend.InsertFirst(SCommandPtr(status));
		m_credentialsSent = true;
	}
}


const char * HMACSession::GetTypeName()
{
	return AUTH_TYPE_MAC;
}


void HMACSession::prepareTransport(const char *msg, uint msg_len)
{
	MD5Session::prepareTransport(msg, msg_len);

	const char *username = m_connectionInfo.acconfig.getUsername();
	const char *password = m_connectionInfo.acconfig.getPassword();
	const char *b64nonce = m_connectionInfo.acconfig.getClientNonce();
	
	if (!username) username = "";
	if (!password) password = "";
	if (!b64nonce) b64nonce = "";
	const char *hmac_b64 = 
		NS_DM_Client::NS_Common::calculateHMAC(username, password, b64nonce, msg, msg_len);

	char buffer[512] = {0};
	// x-syncml-hmac: algorithm=MD5, username="some_user",  mac=<hmac_in_b64>
	__sprintf(buffer, "algorithm=MD5, username=\"%s\", mac=%s", username, hmac_b64);
//	GDLDEBUG("client auth data: ['%s' '%s' '%s']", username, password, b64nonce);
	GDLDEBUG("calculated HMAC: %s", hmac_b64);
	SAFE_DELETE_ARR(hmac_b64);
	
	m_pTransportAgent->setProperty(PROPERTY_HMAC, buffer);
}
