// aplKerberos-Windows.cpp
// Переделанн из https://vm-svn-pss/svn/pss/trunk/SRC/__QT/_StepData/aplSocketTransportQt/aplKerberos-Windows.cpp
// изменения отмечены комментариями //VS
//VC #ifndef __linux__

#include "stdafx.h"
#include <WinError.h>
#include "aplSocketTransport.h"
#include "aplKerberos.h"
#include "commands.h"

#pragma comment (lib,"Secur32.lib")

typedef unsigned short ushort;

CaplKerberos::CaplKerberos():
m_csError(_T("")),m_pOutBuffer(0),m_iOutBufferSize(0),
m_csInitUser(_T("")), m_csInitDomain(_T("")), m_csTargetName(_T(""))
{
    m_bIsServer=false;

    m_secStatus=SEC_E_OK;
    memset(&m_hCred, 0, sizeof(m_hCred));

    m_cbMaxToken=0;

    memset(&m_hCtxt, 0, sizeof(m_hCtxt));
    m_ctxtAttr=0;
    m_reqCtxtAttrs = 0;

    m_fHaveCtxtHandle = false;

    m_pOutBuffer=0;
    m_iOutBufferSize=0;
}
//***********************************************************************

CaplKerberos::~CaplKerberos()
{
    Clear();
}

//***********************************************************************

void CaplKerberos::Clear()
{
    m_secStatus=SEC_E_OK;

    if(!(0==m_hCtxt.dwLower && 0==m_hCtxt.dwUpper)) {::DeleteSecurityContext(&m_hCtxt); memset(&m_hCtxt, 0, sizeof(m_hCtxt));}

    if(0!=m_hCred.dwLower || 0!=m_hCred.dwLower) ::FreeCredentialsHandle(&m_hCred);
    memset(&m_hCred, 0, sizeof(m_hCred));

    m_fHaveCtxtHandle=false;

    if(0!=m_pOutBuffer) {delete m_pOutBuffer; m_pOutBuffer=0;}
    m_iOutBufferSize=0;

    m_ctxtAttr=0;
    m_cbMaxToken=0;

	m_csInitUser.Empty(); m_csInitDomain.Empty();
    m_csTargetName.Empty();
}


//***********************************************************************
bool CaplKerberos::Init(CString csTargetName, CString csUser, CString csPassword, CString csDomain, CString /*qsKeytab*/)
{
    Clear();

    if(!m_bIsServer)
    {
        if(csTargetName.IsEmpty()) { m_csError=APL_T("Некорректный SPN"); return false; }
    }

    // Читаем размер буфера для Kerberos  (так было в примере). Заодно проверяем поддержку Kerberos
    PSecPkgInfoW pkgInfo=0;
    m_secStatus = QuerySecurityPackageInfoW ((WCHAR*)L"Kerberos" ,&pkgInfo);

    if(0!=pkgInfo)
    {
        m_cbMaxToken = pkgInfo->cbMaxToken;
        FreeContextBuffer (pkgInfo);
    }

    if ( (SEC_E_OK != m_secStatus ) || (0==m_cbMaxToken))
    {
        m_csError=APL_T("Kerberos не поддерживается.");
        return false;
    }

    TimeStamp credLifetime;  // memset(&credLifetime, 0, sizeof(credLifetime));
    _SEC_WINNT_AUTH_IDENTITY_W  auth, *pAuth=0;

    if(!csUser.IsEmpty())  // Если задан конкретный пользователь
    {
		m_csInitUser=csUser; m_csInitDomain=csDomain;

        // 	//  пользователь
        memset(&auth, 0, sizeof(auth));
        pAuth=&auth;

        //auth.Flags=SEC_WINNT_AUTH_IDENTITY_ANSI;
        auth.Flags= SEC_WINNT_AUTH_IDENTITY_UNICODE;

		CaplStringAdapter ad;
		CStringW csInitUserW = m_csInitUser;
		CStringW csPasswordW = csPassword;
		CStringW csInitDomainW = m_csInitDomain;

        auth.User=(ushort *)(const wchar_t*)csInitUserW; auth.UserLength=m_csInitUser.GetLength();

		if(!csPassword.IsEmpty()) {auth.Password=(ushort *)(const wchar_t*)csPasswordW;	auth.PasswordLength=csPassword.GetLength(); }

        // Внимание!  Для соединения в текущем домене auth.Domain должна быть равно 0. Пустая строка вызовет ошибку
        if(!m_csInitDomain.IsEmpty()) {auth.Domain=(ushort *)(const wchar_t*)csInitDomainW;	auth.DomainLength=m_csInitDomain.GetLength(); }
    }


#ifdef __GNUC__
    unsigned __LONG32 fCredentialUse=SECPKG_CRED_OUTBOUND;
#else
    unsigned long fCredentialUse=SECPKG_CRED_OUTBOUND;
#endif

    if(m_bIsServer) fCredentialUse=SECPKG_CRED_INBOUND;

    // AcquireCredentialsHandle - ничего не выполняет.  Просто формирует внутр. структуры для послед. использования.
    // Ошибку может вернуть только если не поддерживается Kerberos, что нереально.

    m_secStatus = ::AcquireCredentialsHandleW(NULL, (WCHAR*)L"Kerberos", fCredentialUse, NULL, pAuth, NULL, NULL, &m_hCred, &credLifetime);


    if (SEC_E_OK == m_secStatus )
    {
        m_csTargetName=csTargetName;
        return true;
    }

    SECURITY_STATUS secStatus=m_secStatus;
    Clear();
    m_secStatus=secStatus;
    m_csError=APL_T("Ошибка инициализации контекста. Возможно, не поддерживается Kerberos");
    return false;
}

//***********************************************************************
//***********************************************************************
//***********************************************************************

CaplKerberosClient::CaplKerberosClient()
{
}
//***********************************************************************

int CaplKerberosClient::ProcessingBuffer(unsigned char *pInBuffer, INT32 iInBufferSize, unsigned char *&pOutBuffer, INT32 &OutBufferSize)
{
	OutBufferSize=0;
	m_class_info.Format(_T("CaplKerberosClient " POINTER_FORMAT),this);

	if ( 0==m_cbMaxToken) { m_csError=APL_T("Kerberos не поддерживается."); return -1; }

    SecBuffer        inSecBuf;
    SecBufferDesc    inSecBufDesc;

    if(0==pInBuffer)
    {
        m_fHaveCtxtHandle=false;
       if(!(0==m_hCtxt.dwLower && 0==m_hCtxt.dwUpper)) {::DeleteSecurityContext(&m_hCtxt); memset(&m_hCtxt, 0, sizeof(m_hCtxt));}
        m_ctxtAttr=0;
    }
    else
    {
        inSecBufDesc.ulVersion = 0;
        inSecBufDesc.cBuffers = 1;
        inSecBufDesc.pBuffers = &inSecBuf;
        inSecBuf.BufferType = SECBUFFER_TOKEN;
        inSecBuf.cbBuffer = iInBufferSize;
        inSecBuf.pvBuffer = pInBuffer;
    }

    if(0==m_pOutBuffer) m_pOutBuffer= new BYTE [m_cbMaxToken];

    SecBuffer        outSecBuf;
    outSecBuf.BufferType = SECBUFFER_TOKEN;
    outSecBuf.cbBuffer = m_cbMaxToken;
    outSecBuf.pvBuffer = m_pOutBuffer;

    SecBufferDesc    outSecBufDesc;
    outSecBufDesc.ulVersion = 0;
    outSecBufDesc.cBuffers = 1;
    outSecBufDesc.pBuffers = &outSecBuf;

    //ULONG fContextAttr=0;
    ULONG reqCtxtAttrs=ISC_REQ_USE_SESSION_KEY;

    TimeStamp credLifetime;
	CStringW csTargetNameW = m_csTargetName;

    m_secStatus = ::InitializeSecurityContextW ( &m_hCred, m_fHaveCtxtHandle ? &m_hCtxt : NULL,
                      (WCHAR*)(LPCWSTR)csTargetNameW,  reqCtxtAttrs, 0, SECURITY_NATIVE_DREP,
                       m_fHaveCtxtHandle ? &inSecBufDesc : NULL, 0,
                       &m_hCtxt,  &outSecBufDesc, &m_ctxtAttr, &credLifetime );


    if(SEC_E_LOGON_DENIED==m_secStatus)
    {
		// Неверные имя пользователя или пароль.  Появляется при первом вызове (до передачи данных на сервер).
        m_csError=APL_T("Неверные имя пользователя или пароль.");
        return -1;
    }
    else if(SEC_E_NO_AUTHENTICATING_AUTHORITY==m_secStatus)
    {
        // Неверный домен пользователя. Появляется при первом вызове (до передачи данных на сервис).
         m_csError=APL_T("Неверный домен пользователя");
        return -1;
    }
    else if(SEC_E_NO_CREDENTIALS==m_secStatus)
    {
		// Неверный домен пользователя. Появляется при первом вызове (до передачи данных на сервер).
         m_csError=APL_T("Нет учетных данных (возможно, аутентификация выполнена не в домене).");
        return -1;
    }
    else if(SEC_E_TARGET_UNKNOWN  == m_secStatus) // 0x80090303
    {
		// Некорректное имя в SPN  (<service/computer>). Появляется при первом вызове (до передачи данных на сервер).
        m_csError=APL_T("Некорректное имя в SPN  (<service/computer>)");
        return -1;
    }
    else if(SEC_E_WRONG_PRINCIPAL  == m_secStatus) // 0x80090322
    {
		// Некорректное имя service в SPN  (<service/computer>). Появляется после ответа сервера
		m_csError=APL_T("Некорректный принципал. Возможно некорректное имя service в SPN  (<service/computer>) или сервер запущен от имени пользователя отличного от указанного при регистрации SPN.");
        return -1;
    }

    if( SEC_I_COMPLETE_NEEDED == m_secStatus )
    {
        // Такого быть не должно
		m_csError=APL_T("Недостаточно данных для продолжения (SEC_I_COMPLETE_NEEDED).");
        return -1;
    }

    if(SEC_E_OK == m_secStatus ||  SEC_I_CONTINUE_NEEDED==m_secStatus || SEC_I_COMPLETE_AND_CONTINUE == m_secStatus)
    {
        m_fHaveCtxtHandle = true;

        // Buffer to the server if we have anything to send
        if (0!=outSecBuf.cbBuffer && 0!=outSecBuf.cbBuffer) // Надо посылать даже если (SEC_E_OK == m_secStatus)
        {
            pOutBuffer=(unsigned char *)outSecBuf.pvBuffer;
            OutBufferSize=outSecBuf.cbBuffer;
        }

        if(SEC_I_CONTINUE_NEEDED == m_secStatus)
        {
            // Продолжение обмена
            return 1;
        }

        // Всё закончено
        return 0;
    }


    m_csError.Format(APL_T("Системная ошибка 0x%X"),(ULONG)m_secStatus);
    return -1;
}


CaplKerberosServer::CaplKerberosServer()
{
    m_bIsServer=true;
}

//***********************************************************************
void CaplKerberosServer::Clear()
{
    CaplKerberos::Clear();

    m_csUser.Empty(); m_csClientName.Empty(); m_csServerName.Empty();
}


//***********************************************************************

int CaplKerberosServer::ProcessingBuffer(unsigned char *pInBuffer, INT32 iInBufferSize, unsigned char *&pOutBuffer, INT32 &OutBufferSize)
{
	m_class_info.Format(_T("CaplKerberosServer " POINTER_FORMAT),this);
    if ( 0==m_cbMaxToken) { m_csError=APL_T("Kerberos не поддерживается."); return -1; }

    if(0==pInBuffer)  { m_csError=APL_T("На сервере получен пустой входной буфер!"); return -1;   }

    SecBuffer        inSecBuf;
    SecBufferDesc    inSecBufDesc;

    inSecBufDesc.ulVersion = 0;
    inSecBufDesc.cBuffers = 1;
    inSecBufDesc.pBuffers = &inSecBuf;
    inSecBuf.BufferType = SECBUFFER_TOKEN;
    inSecBuf.cbBuffer = iInBufferSize;
    inSecBuf.pvBuffer = pInBuffer;

    if(0==m_pOutBuffer) m_pOutBuffer= new BYTE [m_cbMaxToken];

    SecBuffer        outSecBuf;
    outSecBuf.BufferType = SECBUFFER_TOKEN;
    outSecBuf.cbBuffer = m_cbMaxToken;
    outSecBuf.pvBuffer = m_pOutBuffer;

    SecBufferDesc    outSecBufDesc;
    outSecBufDesc.ulVersion = 0;
    outSecBufDesc.cBuffers = 1;
    outSecBufDesc.pBuffers = &outSecBuf;

    TimeStamp credLifetime;

    m_secStatus = ::AcceptSecurityContext (	&m_hCred, m_fHaveCtxtHandle ? &m_hCtxt : NULL,
        &inSecBufDesc, 	m_reqCtxtAttrs, SECURITY_NATIVE_DREP, &m_hCtxt, &outSecBufDesc,
        &m_ctxtAttr, &credLifetime);

    if (0!=outSecBuf.cbBuffer && 0!=outSecBuf.cbBuffer) // Надо посылать даже если (SEC_E_OK == m_secStatus)
    {
        pOutBuffer=(unsigned char *)outSecBuf.pvBuffer;
        OutBufferSize=outSecBuf.cbBuffer;
    }

 /*   if(SEC_E_LOGON_DENIED==m_secStatus)
    {
		// Неверные имя пользователя или пароль.  Появляется при первом вызове (до передачи данных на сервер).
        m_csError=APL_T("Неверные имя пользователя или пароль.");
        return -1;
    }
    else if(SEC_E_NO_AUTHENTICATING_AUTHORITY==m_secStatus)
    {
		// Неверный домен пользователя. Появляется при первом вызове (до передачи данных на сервер).
         m_csError=APL_T("Неверный домен пользователя");
        return -1;
    }
    else if(SEC_E_NO_CREDENTIALS==m_secStatus)
    {
		// Неверный домен пользователя. Появляется при первом вызове (до передачи данных на сервер).
         m_csError=APL_T("Нет учетных данных (возможно, аутентификация выполнена не в домене).");
        return -1;
    }
    else if(SEC_E_TARGET_UNKNOWN  == m_secStatus) // 0x80090303
    {
		// Некорректное имя computer в SPN  (<service/computer>). Появляется при первом вызове (до передачи данных на сервер).
        m_csError=APL_T("Некорректное имя computer в SPN  (<service/computer>)");
        return -1;
    }
    else if(SEC_E_WRONG_PRINCIPAL  == m_secStatus) // 0x80090322
    {
		// Некорректное имя service в SPN  (<service/computer>). Появляется после ответа сервер
        m_csError=APL_T("Некорректное имя computer в SPN  (<service/computer>).");
        return -1;
    }*/

    if( SEC_I_COMPLETE_NEEDED == m_secStatus )
    {
        // Такого быть не должно
		m_csError=APL_T("Недостаточно данных для продолжения (SEC_I_COMPLETE_NEEDED).");
        return -1;
    }

    if(SEC_E_OK == m_secStatus || SEC_I_CONTINUE_NEEDED == m_secStatus|| SEC_I_COMPLETE_AND_CONTINUE == m_secStatus)
    {
        m_fHaveCtxtHandle = true;

        if(SEC_I_CONTINUE_NEEDED == m_secStatus)
        {
            // Продолжение обмена
            return 1;
        }

        // Всё закончено  - читаем информацию о пользователе

        _SecPkgContext_NamesW  userName; memset(&userName,0,sizeof(userName));
        QueryContextAttributesW(&m_hCtxt,SECPKG_ATTR_NAMES,&userName);

		if(0!=userName.sUserName){m_csUser=(WCHAR*)userName.sUserName; /*m_csUser.SetLength(wcslen((WCHAR*)userName.sUserName));*/}

        _SecPkgContext_NativeNamesW NativeNames;  memset(&NativeNames,0,sizeof(NativeNames));
        QueryContextAttributesW(&m_hCtxt,SECPKG_ATTR_NATIVE_NAMES,&NativeNames);

		if(0!=NativeNames.sClientName){ m_csClientName = (WCHAR*)NativeNames.sClientName; /*m_csClientName.SetLength(wcslen((WCHAR*)NativeNames.sClientName));*/}
		if(0!=NativeNames.sServerName){ m_csServerName = (WCHAR*)NativeNames.sServerName; /*m_csServerName.SetLength(wcslen((WCHAR*)NativeNames.sServerName));*/}

		FreeContextBuffer(userName.sUserName);
		FreeContextBuffer(NativeNames.sClientName);
		FreeContextBuffer(NativeNames.sServerName);

		LOG_WARN_C(APL_T("m_csUser: %s, m_csClientName: %s, m_csServerName: %s "), (LPCTSTR)m_csUser, (LPCTSTR)m_csClientName, (LPCTSTR)m_csServerName);
        return 0;
    }

    m_csError.FormatMessage(APL_T("Системная ошибка 0x%X"),(ULONG)m_secStatus);

    return -1;
}

//VC #endif

