/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: clbascauth.cpp,v 1.1.2.1 2004/07/09 02:08:33 hubbe Exp $
 * 
 * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved.
 * 
 * The contents of this file, and the files included with this file,
 * are subject to the current version of the RealNetworks Public
 * Source License (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (the "RCSL") available at
 * http://www.helixcommunity.org/content/rcsl, in which case the RCSL
 * will apply. You may also obtain the license terms directly from
 * RealNetworks.  You may not use this file except in compliance with
 * the RPSL or, if you have a valid RCSL with RealNetworks applicable
 * to this file, the RCSL.  Please see the applicable RPSL or RCSL for
 * the rights, obligations and limitations governing use of the
 * contents of the file.
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. If you do not delete the provisions above, a recipient may
 * use your version of this file under the terms of any one of the
 * RPSL, the RCSL or the GPL.
 * 
 * This file is part of the Helix DNA Technology. RealNetworks is the
 * developer of the Original Code and owns the copyrights in the
 * portions it created.
 * 
 * This file, and the files included with this file, is distributed
 * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
 * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
 * ENJOYMENT OR NON-INFRINGEMENT.
 * 
 * Technology Compatibility Kit Test Suite(s) Location:
 *    http://www.helixcommunity.org/content/tck
 * 
 * Contributor(s):
 * 
 * ***** END LICENSE BLOCK ***** */

#include <stdio.h>
#include <ctype.h>

#include "hxtypes.h"

#include "clbascauth.ver"

#define INITGUID

#include "hxstrutl.h"
#include "hxcom.h"
#include "hxplugn.h"
#include "hxprefs.h"
#include "hxfiles.h"
#include "ihxpckts.h"
#include "hxauthn.h"
#include "hxplgns.h"
#include "hxcomm.h"
#include "hxdb.h"

#undef INITGUID

#include "hxbuffer.h"
#include "chxpckts.h"
#include "unkimp.h"
#include "clbascauth.h"
#include "rtsputil.h"

#include "hxver.h"

#include "hxheap.h"
#ifdef _DEBUG
#undef HX_THIS_FILE
static char HX_THIS_FILE[] = __FILE__;
#endif

#ifdef _AIX
#include "hxtbuf.h"
#include "dllpath.h"
ENABLE_MULTILOAD_DLLACCESS_PATHS(ClBascauth);
#endif

const char* CClientBasicAuthenticator::zm_pDescription = "RealNetworks Basic Authenticator";
const char* CClientBasicAuthenticator::zm_pCopyright	 = HXVER_COPYRIGHT;
const char* CClientBasicAuthenticator::zm_pMoreInfoURL = HXVER_MOREINFO;

/****************************************************************************
 *
 *  Function:
 *
 *	HXCreateInstance()
 *
 *  Purpose:
 *
 *	Function implemented by all plugin DLL's to create an instance of
 *	any of the objects supported by the DLL. This method is similar to
 *	Window's CoCreateInstance() in its purpose, except that it only
 *	creates objects from this plugin DLL.
 *
 *	NOTE: Aggregation is never used. Therefore an outer unknown is
 *	not passed to this function, and you do not need to code for this
 *	situation.
 *
 */
STDAPI ENTRYPOINT(HXCreateInstance)
(
    IUnknown**  /*OUT*/	ppIUnknown
)
{
    HX_RESULT HX_RESULTRet = CClientBasicAuthenticator::CreateInstance(ppIUnknown);
    return HX_RESULTRet;
}

/****************************************************************************
 *
 *  Function:
 *
 *	HXShutdown()
 *
 *  Purpose:
 *
 *	Function implemented by all plugin DLL's to free any *global*
 *	resources. This method is called just before the DLL is unloaded.
 *
 */
STDAPI ENTRYPOINT(HXShutdown)(void)
{
    return HXR_OK;
}

// XXXSSH - This is obnoxious, but at least it's concise.  
BEGIN_INTERFACE_LIST(CClientBasicAuthenticator)
    INTERFACE_LIST_ENTRY(IID_IHXPlugin, IHXPlugin)
    INTERFACE_LIST_ENTRY
    (
	IID_IHXObjectConfiguration, 
	IHXObjectConfiguration
    )
    INTERFACE_LIST_ENTRY
    (
	IID_IHXClientAuthConversation, 
	IHXClientAuthConversation
    )
    INTERFACE_LIST_ENTRY
    (
	IID_IHXCredRequestResponse, 
	IHXCredRequestResponse
    )
END_INTERFACE_LIST

CClientBasicAuthenticator::CClientBasicAuthenticator()
    : m_bFinished(FALSE)
    , m_pClientRequest(NULL)
    , m_pClientRespondee(NULL)
    , m_pRequestContext(NULL)
    , m_pContext(NULL)
    , m_bIsProxyAuthentication(FALSE)
{
}

CClientBasicAuthenticator::~CClientBasicAuthenticator()
{
    HX_RELEASE(m_pClientRequest);
    HX_RELEASE(m_pClientRespondee);
    HX_RELEASE(m_pRequestContext);
    HX_RELEASE(m_pContext);
}

// IHXObjectConfiguration
STDMETHODIMP
CClientBasicAuthenticator::SetContext(IUnknown* pContext)
{
    m_pContext = pContext;

    if (m_pContext)
    {
	m_pContext->AddRef();
    }

    return HXR_OK;
}

STDMETHODIMP 
CClientBasicAuthenticator::SetConfiguration(IHXValues* pConfig)
{
    return HXR_NOTIMPL;
}

/************************************************************************
 *  Method:
 *    IHXPlugin::InitPlugin
 *  Purpose:
 *    Initializes the plugin for use. This interface must always be
 *    called before any other method is called. This is primarily needed
 *    so that the plugin can have access to the context for creation of
 *    IHXBuffers and IMalloc.
 */
STDMETHODIMP 
CClientBasicAuthenticator::InitPlugin(IUnknown* /*IN*/ pContext)
{
    m_pContext = pContext;

    if (m_pContext)
    {
	m_pContext->AddRef();
    }


    return HXR_OK;
}

/************************************************************************
 *  Method:
 *    IHXPlugin::GetPluginInfo
 *  Purpose:
 *    Returns the basic information about this plugin. Including:
 *
 *    bLoadMultiple	whether or not this plugin DLL can be loaded
 *			multiple times. All File Formats must set
 *			this value to TRUE.
 *    pDescription	which is used in about UIs (can be NULL)
 *    pCopyright	which is used in about UIs (can be NULL)
 *    pMoreInfoURL	which is used in about UIs (can be NULL)
 */
STDMETHODIMP 
CClientBasicAuthenticator::GetPluginInfo
(
    REF(BOOL)        /*OUT*/ bLoadMultiple,
    REF(const char*) /*OUT*/ pDescription,
    REF(const char*) /*OUT*/ pCopyright,
    REF(const char*) /*OUT*/ pMoreInfoURL,
    REF(ULONG32)     /*OUT*/ ulVersionNumber
)
{
    bLoadMultiple = TRUE;   // Must be true for file formats.

    pDescription    = zm_pDescription;
    pCopyright	    = zm_pCopyright;
    pMoreInfoURL    = zm_pMoreInfoURL;
    ulVersionNumber = TARVER_ULONG32_VERSION;

    return HXR_OK;
}

// IHXCredRequestResponse
STDMETHODIMP
CClientBasicAuthenticator::CredentialsReady
(
    HX_RESULT	Status,
    IHXValues* pCredentials
)
{
    HX_RESULT Ret = HXR_FAIL;

    if(!m_pClientRespondee)
    {
	return HXR_UNEXPECTED;
    }

    if(!m_pRequestContext)
    {
	m_pClientRespondee->ResponseReady(HXR_UNEXPECTED, NULL);
	HX_RELEASE(m_pClientRespondee);
	return HXR_UNEXPECTED;
    }

    if(FAILED(Status))
    {
	m_pClientRespondee->ResponseReady(Status, m_pClientRequest);
	HX_RELEASE(m_pClientRespondee);
	return Status;
    }

    if(pCredentials)
    {
	IHXBuffer* pPassword = NULL;
	IHXValues* pResponseHeaders = NULL;

	Ret = pCredentials->GetPropertyCString("Password", pPassword);

	if(SUCCEEDED(Ret))
	{
	    Ret = _StorageToHeader(pCredentials, pPassword, &pResponseHeaders);
	}

	m_pClientRequest->SetRequestHeaders(pResponseHeaders);
	m_bFinished = TRUE;
	m_pClientRespondee->ResponseReady(HXR_OK, m_pClientRequest);

	HX_RELEASE(pPassword);
	HX_RELEASE(pResponseHeaders);
    }
    else
    {
	m_pClientRespondee->ResponseReady(HXR_FAIL, m_pClientRequest);
    }

    HX_RELEASE(m_pClientRespondee);

    return Ret;
}

// IHXClientAuthConversation
STDMETHODIMP 
CClientBasicAuthenticator::MakeResponse
(
    IHXClientAuthResponse* pClientRespondee,
    IHXRequest*            pClientRequest
)
{
    if(!pClientRespondee || !pClientRequest)
    {
	return HXR_UNEXPECTED;
    }
    
    m_pClientRequest = pClientRequest;
    m_pClientRequest->AddRef();
    m_pClientRequest->QueryInterface(IID_IHXRequestContext,
	(void **)&m_pRequestContext);

    m_pClientRespondee = pClientRespondee;
    m_pClientRespondee->AddRef();

    IHXValues* pChallengeHeaders = NULL;

    m_pClientRequest->GetResponseHeaders(pChallengeHeaders);

    if(!pChallengeHeaders)
    {
	m_pClientRespondee->ResponseReady(HXR_UNEXPECTED, pClientRequest);
	HX_RELEASE(m_pClientRespondee);
	return HXR_UNEXPECTED;
    }

    HX_RESULT Ret = HXR_FAIL;
    IHXBuffer* pChallengeBuf = NULL;

    pChallengeHeaders->GetPropertyCString("WWW-Authenticate", pChallengeBuf);

    m_bIsProxyAuthentication = FALSE;
    
    if (!pChallengeBuf)
    {
	pChallengeHeaders->GetPropertyCString("Proxy-Authenticate", pChallengeBuf);
	m_bIsProxyAuthentication = TRUE;
    }

    if (pChallengeBuf)
    {
	const char* sChallenge = (const char*) pChallengeBuf->GetBuffer();

	if(strncasecmp(sChallenge, "Basic", 5) == 0)
	{
	    IHXCredRequest* pCredRequest = NULL;
	    IHXValues*      pCredentials = NULL;

	    // Set up Prompt, User, Password fields for UI
	    _DescribeCredentials(pChallengeHeaders, &pCredentials);

	    m_pClientRespondee->QueryInterface(IID_IHXCredRequest,
		(void **)&pCredRequest);
	   
	    Ret = pCredRequest->GetCredentials(this, pCredentials);
	    // Flow continues in CredentialsReady()
	    HX_RELEASE(pCredRequest);
	    HX_RELEASE(pCredentials);
	}
	else
	{
	    m_pClientRespondee->ResponseReady(HXR_FAIL, NULL);
	    HX_RELEASE(m_pClientRespondee);
	}
    }
    else
    {
	m_pClientRespondee->ResponseReady(HXR_FAIL, NULL);
	HX_RELEASE(m_pClientRespondee);
    }

    HX_RELEASE(pChallengeHeaders);
    HX_RELEASE(pChallengeBuf);
    return Ret;
}

BOOL 
CClientBasicAuthenticator::IsDone()
{
    return m_bFinished;
}

STDMETHODIMP
CClientBasicAuthenticator::Authenticated(BOOL bAuthenticated)
{
    return HXR_OK;
}

void 
CClientBasicAuthenticator::_SetPropertyFromCharArray(IHXValues* pOptions, 
			  const char* sName, const char* sValue)
{
    IHXBuffer* pVal = NULL;
    CHXBuffer::FromCharArray(sValue, &pVal);
    pOptions->SetPropertyCString(sName, pVal);
    HX_RELEASE(pVal);
}

HX_RESULT 
CClientBasicAuthenticator::_DescribeCredentials
(
    IHXValues*  pChallengeHeaders,
    IHXValues** ppParms   
)
{
    HX_RESULT Ret = _ChallengeToCredentials(pChallengeHeaders, ppParms);

    if (SUCCEEDED(Ret) && (*ppParms))
    {
	// XXXSSH - does this msg ever actually shown anywhere??
	_SetPropertyFromCharArray(*ppParms, "Prompt",
	    "The Realm %Realm% has indicated that %URI% is secure \
	    content. Please fill out the credentials requested below \
	    to gain access. (WARNING: This type of authentication sends\
	    your password to the server)");
	_SetPropertyFromCharArray(*ppParms, "User", "?");
	_SetPropertyFromCharArray(*ppParms, "Password", "?*");
	
	// Now fill in the rest of pChallengeHeaders' stuff, so some
	// IHXAuthenticationManager2 implementor has the luxury of
	// picking and choosing what's appropriate. Like a future
	// "psuedonym" header, perhaps?
	
	IHXBuffer* pBuffer = NULL;
	const char * pName;
	
	HX_RESULT res = pChallengeHeaders->GetFirstPropertyCString(pName, pBuffer);
	while (res == HXR_OK)
	{
	    (*ppParms)->SetPropertyCString(pName, pBuffer);
	    
	    pBuffer->Release();
	    res = pChallengeHeaders->GetNextPropertyCString(pName, pBuffer);
	}
    }

    return Ret;
}

HX_RESULT
CClientBasicAuthenticator::_StorageToHeader
(
    IHXValues*  pCredentials,
    IHXBuffer*  pStoredPassword,
    IHXValues** ppResponseHeaders
)
{
    if (!pStoredPassword)
    {
	HX_ASSERT(0);
	return HXR_UNEXPECTED;
    }

    IHXBuffer* pUserName = NULL;
    HX_RESULT Ret = HXR_UNEXPECTED;

    if (FAILED(pCredentials->GetPropertyCString("UserName", pUserName)))
    {
	return HXR_UNEXPECTED;
    }

    char prebuf[1024]; /* Flawfinder: ignore */
    sprintf(prebuf, "%-.200s:%-.200s", pUserName->GetBuffer(), /* Flawfinder: ignore */
	pStoredPassword->GetBuffer());

    HX_RELEASE(pUserName);

    char encodedbuf[1024]; /* Flawfinder: ignore */
    INT32 encodedlen;
    encodedlen = BinTo64((const BYTE*)prebuf, strlen(prebuf), encodedbuf);
    if (encodedlen >=1024)
    {
	encodedlen = 1023;
    }
    encodedbuf[encodedlen] = '\0';

    char authbuf[1024]; /* Flawfinder: ignore */
    SafeSprintf(authbuf, 1024, "Basic %s", encodedbuf);
    
    (*ppResponseHeaders) = new CHXHeader;
    (*ppResponseHeaders)->AddRef();

    if (m_bIsProxyAuthentication)
    {
	_SetPropertyFromCharArray((*ppResponseHeaders), "Proxy-Authorization", authbuf);
    }
    else
    {
	_SetPropertyFromCharArray((*ppResponseHeaders), "Authorization", authbuf);
    }

    return Ret;
}

BOOL
CClientBasicAuthenticator::_GetQuotedValue
(
    const char*& instr, 
    char* valname, 
    char* valbuf
)
{
    char* equals = (char*)strchr(instr, '=');
    if(!equals)
	return FALSE;

    while(isspace(*(equals - 1)) && equals > instr) equals--;
    if(equals <= instr || (equals - instr > 200))
	return FALSE;

    strncpy(valname, instr, equals - instr); /* Flawfinder: ignore */
    valname[equals -instr] = 0;

    char* firstquote = strchr(equals, '"');
    if(!firstquote)
	return FALSE;

    char* secondquote = strchr(firstquote + 1, '"');
    if(!secondquote || (secondquote - firstquote > 200))
	return FALSE;

    strncpy(valbuf, firstquote + 1, secondquote - firstquote - 1); /* Flawfinder: ignore */
    valbuf[secondquote - firstquote - 1] = 0;
    instr = secondquote + 1;
    return TRUE;
}

HX_RESULT
CClientBasicAuthenticator::_ChallengeToCredentials
(
    IHXValues* pChallengeHeaders, 
    IHXValues** ppCredentials
)
{
    IHXBuffer* pChallengeBuf = NULL;

    if (m_bIsProxyAuthentication)
    {
	pChallengeHeaders->GetPropertyCString("Proxy-Authenticate", pChallengeBuf);
    }
    else
    {
	pChallengeHeaders->GetPropertyCString("WWW-Authenticate", pChallengeBuf);
    }

    if (!pChallengeBuf)
    {
	return HXR_FAIL;
    }

    const char* sChallenge = (const char*) pChallengeBuf->GetBuffer();

    if (strncasecmp(sChallenge, "Basic", 5) == 0)
    {
	BOOL done = FALSE;

	(*ppCredentials) = new CHXHeader;
	(*ppCredentials)->AddRef();

	sChallenge += 5;

	char valbuf[256]; /* Flawfinder: ignore */
	char valname[256]; /* Flawfinder: ignore */

	while(!done)
	{
	    while ((isspace(*sChallenge) || *sChallenge == ',') && *sChallenge)
	    {
		++sChallenge;
	    }

	    if(!(*sChallenge))
		break;

	    if(_GetQuotedValue(sChallenge, valname, valbuf))
	    {
		_SetPropertyFromCharArray((*ppCredentials), valname, valbuf);
	    }
	    else
	    {
		done = TRUE;
	    }
	}

	HX_RELEASE(pChallengeBuf);
	return HXR_OK;
    }

    HX_RELEASE(pChallengeBuf);
    return HXR_FAIL;
}
