#include "StdAfx.h"
#include "DigestAuthenticate.h"
#include "TextTools.h"
#include "Base64.h"
#include "CryptTools.h"

/////////////////////////////////////////////////////////////////////////////////////////
//	チェック
/////////////////////////////////////////////////////////////////////////////////////////
/*!
	認証解析

	\return AUTH_PARSE_STATUS_OK:OK, AUTH_PARSE_STATUS_NONE:認証無し、AUTH_PARSE_STATUS_ERROR:異常な要求
*/
AUTH_PARSE_STATUS CDigestAuthenticate::ParseAuthMethod(CAuthenticateManager *manager, CAlias &alias, CRequest &request)
{
	//	認証オプションを取得
	CString authOptionLine = request.GetOptionAccess().GetConfig(_T("Authorization"), _T(""));
	authOptionLine.Trim();
	TRACE("Authorization: %s\n", authOptionLine);

	//	認証無し
	if(authOptionLine.IsEmpty())
		return(AUTH_PARSE_STATUS_NONE);

	//	認証取得
	CString	authMethod;
	CTextTools::GetToken(authOptionLine, authMethod, _T(" "), TRUE);

	//	認証タイプ
	authMethod.MakeLower();
	request.GetAuthDetailAccess().SetKeyStr(_T("AuthMethod"), authMethod);

	//	認証タイプ
	if(authMethod == _T("basic"))
	{
		//	分析
		if(ParseBasicAuth(authOptionLine, request))
			return(AUTH_PARSE_STATUS_ERROR);
	}
	else if(authMethod == _T("digest"))
	{
		//	分析
		if(ParseAuthOptionLine(authOptionLine, request))
			return(AUTH_PARSE_STATUS_ERROR);

		//	要求をチェック
		if(CheckDigestAuthRequest(manager, alias, request))
			return(AUTH_PARSE_STATUS_ERROR);
	}
	else
		return(AUTH_PARSE_STATUS_ERROR);

	return(AUTH_PARSE_STATUS_OK);
}

/*!
	認証チェック

	\return FALSE:認証不可, TRUE:認証OK
*/
int CDigestAuthenticate::CheckAuthUser(CAuthenticateManager *manager, CAlias &alias, CRequest &request)
{
	if(request.GetAuthDetailAccess().GetKeyStr(_T("AuthMethod"), _T("")) == _T("basic"))
	{
		CString	passwd;
		if(GetUserPasswd(manager, alias, request.GetUser(), passwd))
		{
			if(request.GetPasswd() == passwd)
				return(TRUE);
		}
		return(FALSE);
	}
	else if(request.GetAuthDetailAccess().GetKeyStr(_T("AuthMethod"), _T("")) == _T("digest"))
	{
		//	ダイジェスト認証詳細
		CWorkspaceAccess digestAuthOption = request.GetAuthDetailAccess().GetAccess(_T("DigestAuth"));

		//	nonceをチェック
		if(!manager->CheckNonce(digestAuthOption.GetKeyStr(_T("nonce"), _T(""))))
			return(FALSE);

		CString	passwd;
		if(GetUserPasswd(manager, alias, request.GetUser(), passwd))
		{
			//	ハッシュチェック
			if(CheckDigestAuthResponse(alias, request, passwd))
				return(TRUE);
		}
		return(FALSE);
	}
	else
		return(FALSE);
}


/////////////////////////////////////////////////////////////////////////////////////////
//	生成
/////////////////////////////////////////////////////////////////////////////////////////
/*!
	認証ラインを取得(Basic)
*/
CString CDigestAuthenticate::GetBasicAuthOptionLine(CAlias &alias)
{
	CString	line = CTextTools::GetFormat(_T("Basic realm=\"%s\""), alias.GetPathInfo().GetKeyStr(_T("Info"), _T("")));
	TRACE("%s\n", line);
	return(line);
}


/*!
	認証ラインを取得(Digest)
*/
CString CDigestAuthenticate::GetDigestAuthOptionLine(CAuthenticateManager *manager, CAlias &alias)
{
	CString realm = GetRealm(alias.GetPathInfo().GetKeyStr(_T("Info"), _T("")));
	
	CString line = CTextTools::GetFormat(_T("Digest realm=\"%s\", nonce=\"%s\", algorithm=MD5, qop=\"auth\""), realm, manager->CreateNonce());
	TRACE("%s\n", line);
	return(line);
}



/////////////////////////////////////////////////////////////////////////////////////////
//	内部処理
/////////////////////////////////////////////////////////////////////////////////////////
/*!
	基本認証の処理
*/
int CDigestAuthenticate::ParseBasicAuth(CString authLine, CRequest &request)
{
	//	Base64を解析
	CString	userpass = CBase64::Decode64(authLine);

	//	分解
	int	index = userpass.Find(":");
	if(index == -1)
		return(-1);

	//	ユーザ名
	request.SetUser(userpass.Left(index));

	//	パスワード
	request.SetPasswd(userpass.Mid(index + 1));

	return(0);
}

/*!
	ダイジェスト認証の詳細チェック
*/
int CDigestAuthenticate::CheckDigestAuthRequest(CAuthenticateManager *manager, CAlias &alias, CRequest &request)
{
	//	ダイジェスト認証詳細
	CWorkspaceAccess digestAuthOption = request.GetAuthDetailAccess().GetAccess(_T("DigestAuth"));

	//	realm確認
	if(digestAuthOption.GetKeyStr(_T("realm"), _T("")) != GetRealm(alias.GetPathInfo().GetKeyStr(_T("Info"), _T(""))))
		return(-1);

	//	uri確認(後方一致で一致すればよい)
	if(!CheckURIMatch(request.GetURI(), digestAuthOption.GetKeyStr(_T("uri"), _T(""))))
		return(-1);

	return(0);
}

/*!
	ハッシュ取得
*/
int CDigestAuthenticate::CheckDigestAuthResponse(CAlias &alias, CRequest &request, CString passwd, CString tag)
{
	//	ダイジェスト認証詳細
	CWorkspaceAccess digestAuthOption = request.GetAuthDetailAccess().GetAccess(_T("DigestAuth"));

	CString responseClient	= digestAuthOption.GetKeyStr(_T("response"), _T(""));
	CString nonce	= digestAuthOption.GetKeyStr(_T("nonce"), _T(""));
	CString user	= digestAuthOption.GetKeyStr(_T("username"), _T(""));
	CString realm	= digestAuthOption.GetKeyStr(_T("realm"), _T(""));
	CString nc		= digestAuthOption.GetKeyStr(_T("nc"), _T(""));
	CString cnonce	= digestAuthOption.GetKeyStr(_T("cnonce"), _T(""));
	CString qop		= digestAuthOption.GetKeyStr(_T("qop"), _T(""));
	CString uri		= digestAuthOption.GetKeyStr(_T("uri"), _T(""));
	CString method	= request.GetMethod();

	//	A1文字列(MD5-HEX)
	CString	a1 = CCryptTools::GetMD5HexFromStr(user + _T(":") + realm + _T(":") + passwd);

	//	A2文字列(MD5-HEX)
	CString	a2 = CCryptTools::GetMD5HexFromStr(method + _T(":") + uri);

	//	Response文字列(MD5-HEX)
	CString	responseServer = CCryptTools::GetMD5HexFromStr(a1 + _T(":") + nonce + _T(":") + nc +  _T(":") + cnonce +  _T(":") + qop + _T(":") + a2);

	if(responseServer == responseClient)
		return(TRUE);
	return(FALSE);
}


/*!
	Authorization オプション文字列を分解
*/
int CDigestAuthenticate::ParseAuthOptionLine(CString optionLine, CRequest &request)
{
	//	ダイジェスト認証詳細
	CWorkspaceAccess digestAuthOption = request.GetAuthDetailAccess().GetAccess(_T("DigestAuth"));

	//	詳細を取得
	CStringArray	optionArray;
	CTextTools::GetAllTokenDoubleQuotation(optionLine, optionArray, _T(","), TRUE);

	//	詳細を分解
	for(int i=0; i<optionArray.GetSize(); i++)
	{
		CString	pair = optionArray[i];

		//	= で分解
		CString	key;
		if(CTextTools::GetToken(pair, key, _T("="), TRUE))
		{
			pair.Trim();

			//	追加
			if(!key.IsEmpty() && !pair.IsEmpty())
			{
				//	ダブルコーテーションを外す
				pair = CTextTools::RemoveDoubleQuotation(pair);

				//	ダブルコーテーションが無ければ追加
				if(key.Find(_T("\"")) == -1 &&  pair.Find(_T("\"")) == -1)
					digestAuthOption.SetKeyStr(key.MakeLower(), pair);
			}
		}
	}

	//	チェック
	if(digestAuthOption.GetKeyStr(_T("username"), _T("")).IsEmpty())
		return(-1);
	if(!digestAuthOption.KeyIsExist(_T("realm")))
		return(-1);
	if(digestAuthOption.GetKeyStr(_T("nonce"), _T("")).IsEmpty())
		return(-1);
	if(digestAuthOption.GetKeyStr(_T("uri"), _T("")).IsEmpty())
		return(-1);
	if(digestAuthOption.GetKeyStr(_T("algorithm"), _T("")).CompareNoCase(_T("MD5")) != 0)
		return(-1);
	if(digestAuthOption.GetKeyStr(_T("qop"), _T("")).CompareNoCase(_T("auth")) != 0)
		return(-1);
	if(digestAuthOption.GetKeyStr(_T("nc"), _T("")).IsEmpty())
		return(-1);
	if(digestAuthOption.GetKeyStr(_T("cnonce"), _T("")).IsEmpty())
		return(-1);
	if(digestAuthOption.GetKeyStr(_T("response"), _T("")).IsEmpty())
		return(-1);

	//	ユーザ名
	request.SetUser(digestAuthOption.GetKeyStr(_T("username"), _T("")));
	return(0);
}


/*!
	ユーザ名・パスワードチェック

	\return FALSE:認証不可, TRUE:認証OK
*/
int CDigestAuthenticate::GetUserPasswd(CAuthenticateManager *manager, CAlias &alias, CString userName, CString &passwd)
{
	CWorkspaceAccess	pathUser = alias.GetPathInfo().GetAccess("Users");

	//	パスに設定されてるユーザか確認
	CStringArray	userList;
	pathUser.GetAllKey(userList);
	for(int i=0;i<userList.GetSize();i++)
	{
		if(pathUser.GetConfig(userList[i], _T("")) == userName)
			return(manager->GetUserPasswd(userName, passwd));	//	ユーザ・パスワードが有効か確認
	}

	//	WindowsXP 対策
	int index = userName.Find("\\");
	if(index == -1)
		return(FALSE);
	userName = userName.Mid(index+1);

	//	もう一度確認
	for(int i=0;i<userList.GetSize();i++)
	{
		if(pathUser.GetConfig(userList[i], _T("")) == userName)
			return(manager->GetUserPasswd(userName, passwd));	//	ユーザ・パスワードが有効か確認
	}

	return(FALSE);
}


/*!
	URIの一致を確認
*/
int CDigestAuthenticate::CheckURIMatch(CString serverURI, CString clientURI)
{
	if(serverURI.GetLength() <= clientURI.GetLength())
		if(serverURI == clientURI.Right(serverURI.GetLength()))
			return(TRUE);

	//	IE6対策
	CString	uriIE6;
	if(CTextTools::GetToken(serverURI, uriIE6, _T("?")))
	{
		if(uriIE6.GetLength() <= clientURI.GetLength())
			if(uriIE6 == clientURI.Right(serverURI.GetLength()))
				return(TRUE);
	}

	return(FALSE);
}


/*!
	realm取得
*/
CString CDigestAuthenticate::GetRealm(CString realmSrc)
{
	CStringW	str(realmSrc), ret;
	wchar_t		*ptr = str.GetBuffer(0);
	int			len = str.GetLength();

	//	置き換え
	for(int i=0; i<len; i++)
	{
		if(*ptr >= 0x0080)
			ret += L"_";
		else
			ret += *ptr;
		*ptr++;
	}
	return(CStringA(ret));
}

