//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
//
/*++


Module Name:

    SrmpIsapi.cxx

Abstract:

    SRMP ISAPI extension
    Forwards HTTP requests to MSMQ

--*/

#include <windows.h>
#include <stdio.h>
#include <httpext.h>
#include <creg.hxx>

#include <mq.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include "scapi.h"
#include "scsrmp.hxx"


#if defined(UNDER_CE) && defined (DEBUG)
  DBGPARAM dpCurSettings = {
    TEXT("SrmpIsapi"), {
    TEXT("Error"),TEXT("Init"),TEXT("MSMQ Interface"),TEXT(""),
    TEXT(""),TEXT(""),TEXT(""),
    TEXT(""),TEXT(""),TEXT(""),TEXT(""),
    TEXT(""),TEXT(""),TEXT(""),TEXT(""),TEXT("") },
    0x0003
  }; 

#define ZONE_ERROR  DEBUGZONE(0)
#define ZONE_INIT   DEBUGZONE(1)
#define ZONE_MSMQ   DEBUGZONE(2)
#endif


BOOL GetHttpHeaders(LPEXTENSION_CONTROL_BLOCK pECB, LPSTR *ppszHeaders, BOOL *pfSSL);
BOOL GetPostData   (LPEXTENSION_CONTROL_BLOCK pECB, LPSTR *ppszPost);
void SendResponse  (LPEXTENSION_CONTROL_BLOCK pECB, DWORD dwHttpCode, DWORD *pdwStatus);
void CleanupRequest(LPEXTENSION_CONTROL_BLOCK pECB, SrmpIOCTLPacket *pSRMPPacket, HANDLE hDevice);

// Maximum buffer to receive off the wire
static DWORD g_dwMaxPOSTBuffer;

//
// ISAPI Extension Interface Functions
//
extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer) {
    DEBUGMSG(ZONE_INIT,(L"SrmpISAPI: GetExtensionVersion()\r\n"));

    CReg reg(HKEY_LOCAL_MACHINE,MSMQ_SC_SRMP_REGISTRY_KEY);
    g_dwMaxPOSTBuffer = reg.ValueDW(MSMQ_SC_SRMP_MAX_POST,MSMQ_SC_SRMP_MAX_POST_DEFAULT);

    pVer->dwExtensionVersion = HSE_VERSION;
    return TRUE;
}

static const char cszTextXML[]          = "text/xml";
static const char cszMime[]             = "multipart/related";

extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK pECB)  {
    SrmpIOCTLPacket  SRMPPacket;
    DWORD            dwHttpCode = 500;
    HANDLE           hDevice = INVALID_HANDLE_VALUE;
    DWORD            dwStatus;

    CHAR             szContentType[MAX_PATH];
    CHAR             szIpAddr[INET6_ADDRSTRLEN+1];
    DWORD            cbContentType = sizeof(szContentType);
    DWORD            cbIpAddr = sizeof(szIpAddr);

    memset(&SRMPPacket,0,sizeof(SRMPPacket));

    if (pECB->cbTotalBytes > g_dwMaxPOSTBuffer) {
        DEBUGMSG(ZONE_ERROR,(L"SrmpISAPI: Client attempts to send %d bytes, maximum allowable buffer size = %d\r\n",pECB->cbTotalBytes,g_dwMaxPOSTBuffer));
        dwHttpCode = 413;
        goto done;
    }

    if (0 != _stricmp(pECB->lpszMethod,"post")) {
        DEBUGMSG(ZONE_ERROR,(L"SrmpISAPI: Received method (%a), must be post\r\n",pECB->lpszMethod));
        dwHttpCode = 403; // access denied, we only allow posts
        goto done;
    }

    // content-type
    if (! pECB->GetServerVariable(pECB->ConnID,"CONTENT_TYPE",szContentType,&cbContentType)) {
        DEBUGMSG(ZONE_ERROR,(L"SrmpISAPI: GetServerVariable(\"CONTENT_TYPE\") failed, request aborting.  GLE=0x%08x\r\n",GetLastError()));
        goto done;
    }

    if (0 == _strnicmp(szContentType,cszTextXML,SVSUTIL_CONSTSTRLEN(cszTextXML)))
        SRMPPacket.contentType = CONTENT_TYPE_XML;
    else if (0 == _strnicmp(szContentType,cszMime,SVSUTIL_CONSTSTRLEN(cszMime)))
        SRMPPacket.contentType = CONTENT_TYPE_MIME;
    else {
        dwHttpCode = 400; // bad request
        DEBUGMSG(ZONE_ERROR,(L"SrmpISAPI: Receieved unknown content type (%s), request aborting.\r\n",szContentType));
        goto done;
    }

    // Remote IP addr
    if (! pECB->GetServerVariable(pECB->ConnID,"REMOTE_ADDR",szIpAddr,&cbIpAddr)) {
        DEBUGCHK(0); // should always be able to retrieve this.
        DEBUGMSG(ZONE_ERROR,(L"SrmpISAPI: GetServerVariable(\"REMOTE_ADDR\") failed, request aborting.  GLE=0x%08x\r\n",GetLastError()));
        goto done;
    }
    SRMPPacket.dwIP4Addr = inet_addr(szIpAddr);

    if (! GetHttpHeaders(pECB,&SRMPPacket.pszHeaders,&SRMPPacket.fSSL))
        goto done;

    if (! GetPostData(pECB,&SRMPPacket.pszPost))
        goto done;

    SRMPPacket.cbPost    = pECB->cbTotalBytes;
    SRMPPacket.cbHeaders = strlen(SRMPPacket.pszHeaders);

    if (INVALID_HANDLE_VALUE == (hDevice = CreateFile (L"MMQ1:", 0, 0, NULL, OPEN_EXISTING, 0, NULL)))
        goto done;

    DeviceIoControl(hDevice,MQAPI_Code_SRMPControl,&SRMPPacket,sizeof(SRMPPacket),&dwHttpCode,sizeof(DWORD),NULL,NULL);
done:
    SendResponse(pECB,dwHttpCode,&dwStatus);
    CleanupRequest(pECB,&SRMPPacket,hDevice);
    return dwStatus;
}

BOOL WINAPI TerminateExtension(DWORD dwFlags) {
    DEBUGMSG(ZONE_INIT,(L"SrmpISAPI: TerminateExtension()\r\n"));
    return TRUE;
}

extern "C" BOOL WINAPI DllMain( HANDLE hInstDll, DWORD fdwReason, LPVOID lpvReserved) {
    switch(fdwReason) {
        case DLL_PROCESS_ATTACH:
            DEBUGREGISTER((HINSTANCE)hInstDll);
            DEBUGMSG(ZONE_INIT,(TEXT("SrmpISAPI: DllMain attached\r\n")));
            DisableThreadLibraryCalls((HMODULE)hInstDll);
            break;

        default:
            break;
    }
    return TRUE;
}

// 
// Helper functions
// 

const char cszAllRaw[] = "ALL_RAW";
const char cszServerPortSecure[] = "SERVER_PORT_SECURE";

BOOL GetHttpHeaders(LPEXTENSION_CONTROL_BLOCK pECB, LPSTR *ppszHeaders, BOOL *pfSSL) {
    CHAR  szBuf[256];
    DWORD cbBuffer = sizeof(szBuf);

    if (!pECB->GetServerVariable(pECB->ConnID,(PSTR)cszServerPortSecure,szBuf,&cbBuffer)) {
        DEBUGCHK(0);
        return FALSE;
    }
    *pfSSL = (szBuf[0] == '1'); 

    cbBuffer = 0;
    if (pECB->GetServerVariable(pECB->ConnID,(PSTR)cszAllRaw,NULL,&cbBuffer) || GetLastError() != ERROR_INSUFFICIENT_BUFFER || cbBuffer == 0) {
        DEBUGCHK(0);  // This scenario should always succeed.  Hitting indicates bug in web server.
        return FALSE;
    }

    if (NULL == ((*ppszHeaders) = (LPSTR) LocalAlloc(LMEM_FIXED,cbBuffer))) {
        DEBUGMSG(ZONE_ERROR,(L"SrmpISAPI: LocalAlloc failed, GLE=0x%08x\r\n",GetLastError()));
        return FALSE;
    }

    if (! pECB->GetServerVariable(pECB->ConnID,(PSTR)cszAllRaw,*ppszHeaders,&cbBuffer)) {
        DEBUGCHK(0);
        return FALSE;
    }
    
    return TRUE;
}

BOOL GetPostData(LPEXTENSION_CONTROL_BLOCK pECB, LPSTR *ppszPost) {
    DWORD   cbAvailable  = pECB->cbAvailable;
    DWORD   cbTotalBytes = pECB->cbTotalBytes;

    if (cbTotalBytes > cbAvailable) {
        DWORD cbRead = cbTotalBytes - cbAvailable;

        // There's more to be read off the wire.
        if (NULL == ((*ppszPost) = (LPSTR) LocalAlloc(LMEM_FIXED,cbTotalBytes))) {
            DEBUGMSG(ZONE_ERROR,(L"SrmpISAPI: LocalAlloc failed, GLE=0x%08x\r\n",GetLastError()));
            return FALSE;
        }

        if (!pECB->ReadClient(pECB->ConnID,*ppszPost + cbAvailable,&cbRead) ||
             (cbRead != cbTotalBytes - cbAvailable) ) {
            DEBUGMSG(ZONE_ERROR,(L"SrmpISAPI: ReadClient failed to receive remainder of POST data, GLE=0x%08x\r\n",GetLastError()));
            return FALSE;
        }

        memcpy(*ppszPost,pECB->lpbData,cbAvailable);
    }
    else {
        *ppszPost = (LPSTR) pECB->lpbData;
    }
    return TRUE;
}

void SendResponse(LPEXTENSION_CONTROL_BLOCK pECB, DWORD dwHttpCode, DWORD *pdwStatus) {
    HSE_SEND_HEADER_EX_INFO  SendHeaderExInfo;
    LPCSTR szHeader = "Content-Length: 0\r\n\r\n";
    CHAR   szStatus[10];

    if (dwHttpCode == 500) {
        *pdwStatus = HSE_STATUS_ERROR;
        return;
    }
    HRESULT hr = StringCchPrintfA(szStatus,_countof(szStatus),"%d",dwHttpCode);
    ASSERT(SUCCEEDED(hr));
    
    SendHeaderExInfo.pszStatus = szStatus;
    SendHeaderExInfo.cchStatus = strlen(szStatus);
    SendHeaderExInfo.pszHeader = szHeader;
    SendHeaderExInfo.cchHeader = sizeof(szHeader) - 1;
    SendHeaderExInfo.fKeepConn = TRUE;

    if (! pECB->ServerSupportFunction(pECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&SendHeaderExInfo,NULL,NULL))
        *pdwStatus = HSE_STATUS_ERROR;
    else
        *pdwStatus = HSE_STATUS_SUCCESS_AND_KEEP_CONN;
}


void CleanupRequest(LPEXTENSION_CONTROL_BLOCK pECB, SrmpIOCTLPacket *pSRMPPacket, HANDLE hDevice) {
    if (pSRMPPacket->pszHeaders)
        LocalFree(pSRMPPacket->pszHeaders);

    if (pSRMPPacket->pszPost && (pSRMPPacket->pszPost != (PSTR) pECB->lpbData))
        LocalFree(pSRMPPacket->pszPost);

    if (hDevice != INVALID_HANDLE_VALUE)
        CloseHandle(hDevice);
}
