///////////////////////////////////////////////////////////////////////////////
//
//  File Name 
//      RASHOST.CPP
//
//  Description
//      This file contains the initialization functions for the DLL library
//      and for the Security Host protocol. The NT RAS manager will call into
//      our DLL when a dialing user needs to be validated.
//
//  Author
//      Irving De la Cruz
//
//  Revision: 1.1
//
// Written for Microsoft Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#include "RASHOST.H"

HINSTANCE                       ghLibHandle = NULL;
RASSECURITYDIALOGSENDPROC       gpfnSendProc = NULL;
RASSECURITYDIALOGRECEIVEPROC    gpfnReceiveProc = NULL;

///////////////////////////////////////////////////////////////////////////////
//    LibMain()
//
//    Parameters
//      { Refer to the Win32 SDK documentation for DLL entry points }
//
//    Purpose
//      Entry point of this DLL
//
//    Return Value
//      TRUE if the DLL should proceed with the dwReason case, FALSE otherwise.
//
BOOL APIENTRY LibMain (HINSTANCE hDll, DWORD dwReason, LPVOID lpReserved)
{
    if (DLL_PROCESS_DETACH == dwReason && ghLibHandle)
    {
        FreeLibrary (ghLibHandle);
    }
    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
//    RasSecurityDialogBegin()
//
//    Parameters
//      hPort       Handle to the COM port where the user is logging in
//                  (A RAS handle, not a standard handle)
//      pSendBuf    Pointer to a buffer where we can copy stuff to be sent
//                  over the wire
//      SendBufSize Size of the outgoing buffer
//      pRecvBuf    Pointer a buffer where stuff is received from the
//                  remote client machine
//      RecvBufSize Size of the incoming buffer
//      RasSecurityDialogComplete   Pointer to a callback function we must
//                  call after we have completed our validation, indicating
//                  the RAS manager weather or not we granted access to the
//                  caller.
//
//    Purpose
//      This is the entry point for our security DLL. When the RAS manager
//      receives a call from a user, after it does its user validation, it
//      will call into us at this entry point to allow us to do extra
//      validation.
//      NOTE: It is very important that this function does not BLOCK. If
//            it does, the RAS manager will be block and no other calls
//            will be accepted. That is why we copy the incoming
//            parameters into a private structure and then spawn a thread
//            to do the actual validation work.
//            The passed parameters will be valid until we call the
//            ending function.
//
//    Return Value
//      ERROR_SUCCESS is we were able to start the validation. Otherwise
//      return and error from WINERROR.H or RASERROR.H
//
DWORD WINAPI RasSecurityDialogBegin (HPORT hPort,
                                     PBYTE pSendBuf,
                                     DWORD SendBufSize,
                                     PBYTE pRecvBuf,
                                     DWORD RecvBufSize,
                                     VOID  (WINAPI *RasSecurityDialogComplete)(SECURITY_MESSAGE*))
{
    // Initialize the global pointer to the entry points in the RAS manager
    // DLL. I use these entry points to send and receive data to the remote
    // client machine. These functions will send and receive RAW buffers of
    // data to the other machine just before PPP-framing starts.
    // These functions have not been exported in any publish import library
    // so we must do a dynamic binding to them.
    if (NULL == ghLibHandle)
    {
        ghLibHandle = LoadLibrary ("RASMAN.DLL");
        if (NULL == ghLibHandle)
        {
            return ERROR_DLL_NOT_FOUND;
        }
    }
    // Obtain the send and received entry points from the RAS manager DLL.
    if (NULL == gpfnSendProc)
    {
        gpfnSendProc = (RASSECURITYDIALOGSENDPROC)GetProcAddress (ghLibHandle, TEXT("RasSecurityDialogSend"));
        if (NULL == gpfnSendProc)
        {
            return ERROR_PROC_NOT_FOUND;
        }
    }
    if (NULL == gpfnReceiveProc)
    {
        gpfnReceiveProc = (RASSECURITYDIALOGRECEIVEPROC)GetProcAddress (ghLibHandle, TEXT("RasSecurityDialogReceive"));
        if (NULL == gpfnReceiveProc)
        {
            return ERROR_PROC_NOT_FOUND;
        }
    }
    
    // Save the parameters passed and give them to the working thread
    // that will handle the authentication of the caller. The thread
    // should release this memory.
    // The parameters passed to use should be valid until we call the
    // dialog end function or until we get call in the termination
    // dialog entry point by the RAS system supervisor (the RAS manager)
    PWORK_THREAD_DATA pCallData = (PWORK_THREAD_DATA)HeapAlloc (GetProcessHeap(),
                                                                HEAP_ZERO_MEMORY,
                                                                sizeof(WORK_THREAD_DATA));
    if (NULL == pCallData)
    {
        return ERROR_OUTOFMEMORY;
    }
    // Initialize the members.
    pCallData->hPort = hPort;
    pCallData->pSendBuf = pSendBuf;
    pCallData->SendBufSize = SendBufSize;
    pCallData->pRecvBuf = pRecvBuf;
    pCallData->RecvBufSize = RecvBufSize;
    pCallData->pfnSecurityDialogEnd = RasSecurityDialogComplete;

    // For each call, spawn a working thread that will make the neccesary
    // authentication of the dial-up caller. We should never block the call
    // to RasSecurityDialogBegin(). If we do so, RAS will also block and no
    // more call would be accepted.
    DWORD dwThreadID;
    HANDLE hThread = CreateThread (NULL,
                                   0,
                                   (LPTHREAD_START_ROUTINE)CallWorkerThread,
                                   (LPVOID)pCallData,
                                   0,
                                   &dwThreadID);
    if (NULL == hThread)
    {
        // Call GetLastError() before calling HeapFree().
        DWORD dwError = GetLastError();
        HeapFree (GetProcessHeap(), 0, pCallData);
        return dwError;
    }
    return ERROR_SUCCESS;
}

///////////////////////////////////////////////////////////////////////////////
//    RasSecurityDialogEnd()
//
//    Parameters
//      hPort   Handle to the COM port (A RAS handle, not a standard handle)
//
//    Purpose
//      Stub function. Need to export this by the security DLL even if
//      not needed. We need to return non-zero to indicate RAS we are no
//      longer responsible for the port connection. It will take care
//      of the rest.
//
//    Return Value
//      ERROR_PORT_DISCONNECTED always
//
DWORD WINAPI RasSecurityDialogEnd (HPORT hPort)
{
    return ERROR_PORT_DISCONNECTED;
}

///////////////////////////////////////////////////////////////////////////////
//    CallWorkerThread()
//
//    Parameters
//      pCallData   Pointer to the data structure with the data for the
//                  instance we are handling.
//
//    Purpose
//      This is the function for the thread that interacts with a dialing RAS
//      user. Here we query the user for its identity and based on it we
//      compute a challenge to for it. Once the response from the client
//      is received, we validate it and decide wether or not we allow access.
//
//    Return Value
//      None
//
void WINAPI CallWorkerThread (PWORK_THREAD_DATA pCallData)
{
    // Initialize the appropiate structures
    SECURITY_MESSAGE smResult = { 0 };
    smResult.hPort = pCallData->hPort;
    lstrcpy (smResult.UserName, TEXT("(RASHOST - Unknown)"));

    DWORD dwBytes = pCallData->RecvBufSize;
    ZeroMemory (pCallData->pRecvBuf, dwBytes);
    dwBytes = sizeof(RASHOST_DATA);

    // Initialize the structure that we'll send to the caller
    RASHOST_DATA HostData = { 0 };
    HostData.dwSize = sizeof(RASHOST_DATA);
    
    // Create an event to wait for client responses
    HANDLE hEvent = CreateEvent (NULL, FALSE, FALSE, NULL);

    // We must copy the data from the structure to the buffer RAS
    // manager uses to transmit the information. This buffer
    // cannot be larger than 1500 bytes
    CopyMemory (pCallData->pSendBuf, &HostData, sizeof(RASHOST_DATA));
    DWORD dwError = 0;

    gpfnSendProc (pCallData->hPort,
                  pCallData->pSendBuf,
                  (WORD)sizeof(RASHOST_DATA));

    // Wait for an answer from the client responder
    gpfnReceiveProc (pCallData->hPort,
                     pCallData->pRecvBuf,
                     (WORD *)&dwBytes,
                     0,
                     hEvent);
    DWORD dwWait;
    dwWait = WaitForSingleObject (hEvent, CLIENT_RESPONSE_TIMEOUT);
    if (WAIT_OBJECT_0 != dwWait)
    {
        dwError = ERROR_SMM_TIMEOUT;
        goto ErrorExit;
    }

    // Copy the client data from the incoming buffer to the data structure
    CopyMemory (&HostData, pCallData->pRecvBuf, sizeof(RASHOST_DATA));
    
    // Save this information for RAS authentication with the NT RAS manager.
    // The user name must be the same as the one in the NT account database
    // on the domain where the user is granted access.
    lstrcpy (smResult.UserName, HostData.szUserName);
    lstrcpy (smResult.Domain, HostData.szUserDomain);

    //lstrcpy (smResult.UserName, "IrvingD");

    BOOL fAccessGranted; // Weather or not access was granted
    // With the received client response, validate the access for the client
    dwError = ValidateCallingUser (&HostData, &fAccessGranted);
    if (dwError)
    {
        goto ErrorExit;
    }
    
    if (fAccessGranted)
    {
        // Access has been granted
        smResult.dwMsgId = SECURITYMSG_SUCCESS;
    }
    else
    {
        // The caller did not respond correctly
        smResult.dwMsgId = SECURITYMSG_FAILURE;
    }

ErrorExit:
    if (dwError)
    {
        // There was an error, deny access.
        if (ERROR_SMM_TIMEOUT == dwError)
        {
            smResult.dwMsgId = SECURITYMSG_FAILURE;
        }
        else
        {
            smResult.dwMsgId = SECURITYMSG_ERROR;
            smResult.dwError = dwError;
        }
    }
    // Release the synchronization objects
    CloseHandle (hEvent);
    // Tell the RAS manager we are finished by calling the END dialog funcition.
    pCallData->pfnSecurityDialogEnd (&smResult);
    // Free the memory of this call instance before exiting the thread
    HeapFree (GetProcessHeap(), 0, pCallData);
}

///////////////////////////////////////////////////////////////////////////////
//    ValidateCallingUser()
//
//    Parameters
//      pData               Pointer to the host data structure
//      pfAccessGranted     We return TRUE here is access is granted.
//                          FALSE otherwise.
//
//    Purpose
//      Here we validate the answer to the challenge sent to the client.
//
//    Return Value
//      ERROR_SUCCESS if successful. Otherwiser an error code from WINERROR.H
//      or RASERROR.H
//
DWORD WINAPI ValidateCallingUser (PRASHOST_DATA       pData,
                                  BOOL *              pfAccessGranted)
{
    DWORD dwResult = ERROR_SUCCESS;
    *pfAccessGranted = FALSE;
    
    // Do stuff here

    return dwResult;
}

// End of file for RASHOST.CPP
