/*
 * Copyright 2010 Funambol, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* $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, Funambol::TransportAgent &agent) :
    MD5Session(info, agent)
{
}


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];
    if(srv_hmac_username == NULL)
    {
        GDLWARN("new srv_hmac_username");
    }
    memset(srv_hmac_username, '\0', str-usrbegin+1);
    __strncpy(srv_hmac_username, usrbegin, str-usrbegin);

    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];
    if(hmac_mac == NULL)
    {
        GDLWARN("new hmac_mac");
    }
    memset(hmac_mac, '\0', str-macbegin+1);
    __strncpy(hmac_mac, macbegin, str-macbegin);

    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("is first message: %s", m_firstMessage ? "yes" : "no");
    if (m_firstMessage)
    {
        insertAlertReplace(code);
        m_firstMessage = false;
    }
    else
    {
        if (m_connectionInfo.state.repeatAuthentication)
        {
            insertAlertReplace(code);
//          if (m_state.InitialAlert.get())
//              m_commandsToSend.Add(m_state.InitialAlert);
        }

        Funambol::NextNonce *pNonce = CreateNonce();
        const char *nonce64 = pNonce->getValueAsBase64();
        m_connectionInfo.acconfig.setServerNonce(nonce64);
        GDLDEBUG("new server's NextNonce: '%s'", 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];
    memset(buffer, '\0', 512);
    // 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);
}
