//----------------------------------------------------------------------------- // Copyright (C) Microsoft Corporation, 1997 - 1999 // // rpcisapi.cxx // // IIS ISAPI extension part of the RPC proxy over HTTP. // // Exports: // // BOOL WINAPI GetExtensionVersion( HSE_VERSION_INFO *pVer ) // // Returns the version of the spec that this server was built with. // // BOOL WINAPI HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB ) // // This function does all of the work. // //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Includes: //----------------------------------------------------------------------------- #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //----------------------------------------------------------------------------- // Globals: //----------------------------------------------------------------------------- extern SERVER_INFO *g_pServerInfo; extern BOOL g_fIsIIS6; //----------------------------------------------------------------------------- // GetExtensionVersion() // //----------------------------------------------------------------------------- BOOL WINAPI GetExtensionVersion( HSE_VERSION_INFO *pVer ) { HRESULT hr; pVer->dwExtensionVersion = MAKELONG( HSE_VERSION_MINOR, HSE_VERSION_MAJOR ); ASSERT( sizeof(EXTENSION_DESCRIPTION) <= HSE_MAX_EXT_DLL_NAME_LEN ); hr = StringCchCopyNA (pVer->lpszExtensionDesc, sizeof(pVer->lpszExtensionDesc), EXTENSION_DESCRIPTION, sizeof(EXTENSION_DESCRIPTION) ); // this cannot fail as the strings are constant ASSERT(SUCCEEDED(hr)); // if the filter data structures are not initialized yet, initialize them if (g_pServerInfo == NULL) { if (InitializeGlobalDataStructures(FALSE /* IsFromFilter */) == FALSE) { return FALSE; } } LogEventStartupSuccess (fIsIISInCompatibilityMode ? "5" : "6"); return TRUE; } //----------------------------------------------------------------------------- // ReplyToClient() // //----------------------------------------------------------------------------- BOOL ReplyToClient( EXTENSION_CONTROL_BLOCK *pECB, const char *pBuffer, DWORD *pdwSize, DWORD *pdwStatus ) { DWORD dwFlags = (HSE_IO_SYNC | HSE_IO_NODELAY); if (!pECB->WriteClient(pECB->ConnID, (char *)pBuffer, pdwSize, dwFlags)) { *pdwStatus = GetLastError(); #ifdef DBG_ERROR DbgPrint("ReplyToClient(): failed: %d\n",*pdwStatus); #endif return FALSE; } *pdwStatus = HSE_STATUS_SUCCESS; return TRUE; } DWORD ReplyToClientWithStatus ( IN EXTENSION_CONTROL_BLOCK *pECB, IN RPC_STATUS RpcStatus ) /*++ Routine Description: Sends a reply to the client with the given error code as error. Arguments: pECB - extension control block RpcStatus - error code to be returned to client Return Value: Return value appropriate for return to IIS (i.e. HSE_STATUS_*) --*/ { // size is the error string + 20 space for the error code char Buffer[sizeof(ServerErrorString) + 20]; ULONG Size; ULONG Status; BOOL Result; HRESULT hr; hr = StringCbPrintfA (Buffer, sizeof(Buffer), ServerErrorString, RpcStatus ); // this should never fail as the strings are constant ASSERT(SUCCEEDED(hr)); Size = strlen(Buffer); Result = ReplyToClient ( pECB, Buffer, &Size, &Status ); return Status; } //----------------------------------------------------------------------------- // ParseQueryString() // // The query string is in the format: // // // // Where the index is in ASCII Hex. The index is read and converted back // to a DWORD then used to locate the SERVER_OVERLAPPED. If its found, // return TRUE, else return FALSE. // // NOTE: "&" is the parameter separator if multiple parameters are passed. //----------------------------------------------------------------------------- BOOL ParseQueryString( unsigned char *pszQuery, SERVER_OVERLAPPED **ppOverlapped, DWORD *pdwStatus ) { DWORD dwIndex = 0; pszQuery = AnsiHexToDWORD(pszQuery,&dwIndex,pdwStatus); if (!pszQuery) { return FALSE; } *ppOverlapped = GetOverlapped(dwIndex); if (*ppOverlapped == NULL) { return FALSE; } return TRUE; } BOOL ParseHTTP2QueryString ( IN char *Query, IN OUT USHORT *ServerAddressBuffer, IN ULONG ServerAddressBufferLength, IN OUT USHORT *ServerPortBuffer, IN ULONG ServerPortBufferLength ) /*++ Routine Description: Parses an HTTP2 format query string into a server address and server port. Arguments: Query - the raw query string as received by the extension ServerAddressBuffer - the buffer where the server address will be stored. Undefined upon failure. Upon success it will be 0 terminated. ServerAddressBufferLengh - the length of ServerAddressBuffer in unicode chars. ServerPortBuffer - the buffer where the server port will be stored. Undefined upon failure. Upon success it will be 0 terminated. ServerPortBufferLengh - the length of ServerAddressBuffer in unicode chars. Return Value: non-zero for success and 0 for failure. --*/ { char *ColonPosition; int CharactersConverted; ColonPosition = _mbschr(Query, ServerAddressAndPortSeparator); if (ColonPosition == NULL) return FALSE; CharactersConverted = MultiByteToWideChar ( CP_ACP, MB_ERR_INVALID_CHARS, Query, ColonPosition - Query, ServerAddressBuffer, ServerAddressBufferLength ); // did we convert successfully? if (CharactersConverted == 0) return FALSE; // do we have space for the terminating null? if (CharactersConverted + 1 > ServerAddressBufferLength) return FALSE; // null terminate the server address string. Since we gave the length // explicitly to MultiByteToWideChar it will not add null for us ServerAddressBuffer[CharactersConverted] = 0; CharactersConverted = MultiByteToWideChar ( CP_ACP, MB_ERR_INVALID_CHARS, ColonPosition + 1, -1, // have MultiByteToWideChar calculate the length ServerPortBuffer, ServerPortBufferLength ); // did we convert successfully? if (CharactersConverted == 0) return FALSE; // since we had MultiByteToWideChar calculate the string length, // it has null terminated the resulting string. We're all done. return TRUE; } BOOL GetRemoteUserString ( IN EXTENSION_CONTROL_BLOCK *pECB, OUT char **FinalRemoteUser ) /*++ Routine Description: Gets the remote user name from IIS. If anonymous, an empty string will be returned. Arguments: pECB - the extension control block given to us by IIS. FinalRemoteUser - on output a pointer to the remote user name allocated by this routine. Muts be freed by caller using MemFree. Undefined on failure. Return Value: non-zero for success or 0 for failure/ --*/ { ULONG ActualServerVariableLength; char *TempRemoteUser; BOOL Result; ActualServerVariableLength = 0; TempRemoteUser = NULL; Result = pECB->GetServerVariable(pECB->ConnID, "REMOTE_USER", TempRemoteUser, &ActualServerVariableLength ); ASSERT(Result == FALSE); if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { return FALSE; } TempRemoteUser = (char *)MemAllocate(ActualServerVariableLength + 1); if (TempRemoteUser == NULL) { return FALSE; } Result = pECB->GetServerVariable(pECB->ConnID, "REMOTE_USER", TempRemoteUser, &ActualServerVariableLength ); if (Result == FALSE) { MemFree(TempRemoteUser); return FALSE; } *FinalRemoteUser = TempRemoteUser; return TRUE; } RPC_STATUS RPC_ENTRY I_RpcProxyIsValidMachine ( IN char *pszMachine, IN char *pszDotMachine, IN ULONG dwPortNumber ) { BOOL Result; Result = HttpProxyIsValidMachine(g_pServerInfo, pszMachine, pszDotMachine, dwPortNumber ); if (Result) return RPC_S_OK; else return RPC_S_ACCESS_DENIED; } RPC_STATUS RPC_ENTRY I_RpcProxyGetClientAddress ( IN void *Context, OUT char *Buffer, OUT ULONG *BufferLength ) { BOOL Result; EXTENSION_CONTROL_BLOCK *pECB = (EXTENSION_CONTROL_BLOCK *) Context; Result = pECB->GetServerVariable(pECB->ConnID, "REMOTE_ADDR", Buffer, BufferLength ); if (Result) return RPC_S_OK; else return GetLastError(); } RPC_STATUS RPC_ENTRY I_RpcProxyGetConnectionTimeout ( OUT ULONG *ConnectionTimeout ) { *ConnectionTimeout = IISConnectionTimeout; return RPC_S_OK; } const I_RpcProxyCallbackInterface ProxyCallbackInterface = { I_RpcProxyIsValidMachine, I_RpcProxyGetClientAddress, I_RpcProxyGetConnectionTimeout }; // uncomment this to see why connections are being rejected // by AllowAnonymous // #define DEBUG_ALLOW_ANONYMOUS //----------------------------------------------------------------------------- // HttpExtensionProc() // //----------------------------------------------------------------------------- DWORD WINAPI HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB ) { DWORD dwStatus; DWORD dwFlags; DWORD dwSize; SERVER_INFO *pServerInfo = g_pServerInfo; SERVER_OVERLAPPED *pOverlapped = NULL; HSE_SEND_HEADER_EX_INFO HeaderEx; CHAR *pLegacyVerb; RPC_STATUS RpcStatus; ULONG VerbLength; USHORT ServerAddress[MaxServerAddressLength]; USHORT ServerPort[MaxServerPortLength]; BOOL Result; ULONG ProxyType; BOOL ConnectionEstablishmentRequest; char EchoResponse[sizeof(EchoResponseHeader2) + 2]; // 2 is for // the size of the Echo RTS which goes instead of ContentLength int BytesWritten; ULONG BufferSize; const BYTE *EchoRTS; char ServerVariable[20]; ULONG ActualServerVariableLength; ULONG NumContentLength; BOOL AnonymousConnection; char *RemoteUser = NULL; RPC_CHAR *ActualServerName; DWORD ExtensionProcResult; DWORD HttpStatusCode; char *DestinationEnd; HRESULT hr; if (g_pServerInfo->dwEnabled == FALSE) { dwSize = sizeof(STATUS_PROXY_DISABLED) - 1; // don't want to count trailing 0. ReplyToClient(pECB,STATUS_PROXY_DISABLED, &dwSize, &dwStatus); HttpStatusCode = STATUS_SERVER_ERROR; ExtensionProcResult = HSE_STATUS_ERROR; goto AbortAndExit; } if (g_pServerInfo->AllowAnonymous == FALSE) { // if we are not allowing anonymous connections, check that a secure // channel and authentication are used // assume anonymous for now AnonymousConnection = TRUE; ActualServerVariableLength = sizeof(ServerVariable); Result = pECB->GetServerVariable(pECB->ConnID, "HTTPS", ServerVariable, &ActualServerVariableLength ); if (!Result) { HttpStatusCode = STATUS_SERVER_ERROR; ExtensionProcResult = HSE_STATUS_ERROR; goto AbortAndExit; } if (RpcpStringCompareIntA(ServerVariable, "on") == 0) { Result = GetRemoteUserString (pECB, &RemoteUser ); if (!Result) { #ifdef DEBUG_ALLOW_ANONYMOUS DbgPrint("Connection rejected getting the remote user failed\n"); #endif // #ifdef DEBUG_ALLOW_ANONYMOUS HttpStatusCode = STATUS_SERVER_ERROR; ExtensionProcResult = HSE_STATUS_ERROR; goto AbortAndExit; } // if non-empty string, it is authenticated if (RemoteUser[0] != 0) { AnonymousConnection = FALSE; #ifdef DEBUG_ALLOW_ANONYMOUS DbgPrint("Connection rejected because user could not be retrieved\n"); #endif // #ifdef DEBUG_ALLOW_ANONYMOUS } else { #ifdef DEBUG_ALLOW_ANONYMOUS DbgPrint("Connection accepted for user %s\n", RemoteUser); #endif // #ifdef DEBUG_ALLOW_ANONYMOUS } } else { #ifdef DEBUG_ALLOW_ANONYMOUS DbgPrint("Connection rejected because it is not SSL\n"); #endif // #ifdef DEBUG_ALLOW_ANONYMOUS } // if empty string, it is not authenticated if (AnonymousConnection) { dwSize = sizeof(AnonymousAccessNotAllowedString) - 1; // don't want to count trailing 0. ReplyToClient(pECB, AnonymousAccessNotAllowedString, &dwSize, &dwStatus); HttpStatusCode = STATUS_SERVER_ERROR; ExtensionProcResult = dwStatus; goto AbortAndExit; } } pECB->dwHttpStatusCode = STATUS_CONNECTION_OK; if (g_fIsIIS6) { pLegacyVerb = RPC_CONNECT; } else { pLegacyVerb = POST_STR; } VerbLength = strlen(pECB->lpszMethod); ConnectionEstablishmentRequest = FALSE; // get the verb and depending on that we will determine our course of // action if ((VerbLength == InChannelEstablishmentMethodLength) && (lstrcmpi(pECB->lpszMethod, InChannelEstablishmentMethod) == 0)) { ConnectionEstablishmentRequest = TRUE; ProxyType = RPC_PROXY_CONNECTION_TYPE_IN_PROXY; } else if ((VerbLength == OutChannelEstablishmentMethodLength) && (lstrcmpi(pECB->lpszMethod, OutChannelEstablishmentMethod) == 0)) { ConnectionEstablishmentRequest = TRUE; ProxyType = RPC_PROXY_CONNECTION_TYPE_OUT_PROXY; } if (ConnectionEstablishmentRequest) { // check if this is echo request or a true connection // establishment ActualServerVariableLength = sizeof(ServerVariable); Result = pECB->GetServerVariable(pECB->ConnID, "CONTENT_LENGTH", ServerVariable, &ActualServerVariableLength ); if (!Result) { HttpStatusCode = STATUS_SERVER_ERROR; ExtensionProcResult = HSE_STATUS_ERROR; goto AbortAndExit; } NumContentLength = atol(ServerVariable); if (NumContentLength <= MaxEchoRequestSize) { // too small for a channel. Must be an echo EchoRTS = GetEchoRTS(&BufferSize); // Echo back the RTS packet + headers hr = StringCbPrintfExA(EchoResponse, sizeof(EchoResponse), &DestinationEnd, // ppszDestEnd NULL, // pcbRemaining 0, // Flags EchoResponseHeader2, BufferSize ); if (FAILED(hr)) { HttpStatusCode = STATUS_SERVER_ERROR; ExtensionProcResult = HSE_STATUS_ERROR; goto AbortAndExit; } // calculate the number of bytes written as the // distance from the end of the written buffer to the // beginning BytesWritten = DestinationEnd - EchoResponse; // send back headers dwSize = sizeof(HeaderEx); dwFlags = 0; memset(&HeaderEx, 0, dwSize); HeaderEx.fKeepConn = TRUE; HeaderEx.pszStatus = EchoResponseHeader1; HeaderEx.cchStatus = sizeof(EchoResponseHeader1); HeaderEx.pszHeader = EchoResponse; HeaderEx.cchHeader = BytesWritten; Result = pECB->ServerSupportFunction(pECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER_EX, &HeaderEx, &dwSize, &dwFlags); if (!Result) { HttpStatusCode = STATUS_SERVER_ERROR; ExtensionProcResult = HSE_STATUS_ERROR; goto AbortAndExit; } // send back Echo RTS dwSize = BufferSize; dwFlags = 0; Result = pECB->WriteClient(pECB->ConnID, (char *)EchoRTS, &dwSize, dwFlags); if (!Result) { HttpStatusCode = STATUS_SERVER_ERROR; ExtensionProcResult = HSE_STATUS_ERROR; goto AbortAndExit; } return HSE_STATUS_SUCCESS; } else { // a channel Result = ParseHTTP2QueryString ( pECB->lpszQueryString, ServerAddress, sizeof(ServerAddress) / sizeof(ServerAddress[0]), ServerPort, sizeof(ServerPort) / sizeof(ServerPort[0]) ); if (Result == FALSE) { dwSize = sizeof(CannotParseQueryString) - 1; // don't want to count trailing 0. ReplyToClient(pECB, CannotParseQueryString, &dwSize, &dwStatus); HttpStatusCode = STATUS_SERVER_ERROR; ExtensionProcResult = dwStatus; goto AbortAndExit; } if (g_pServerInfo->RpcNewHttpProxyChannel) { // get the remote user (if not available yet) and call // the redirector dll if (RemoteUser == NULL) { Result = GetRemoteUserString (pECB, &RemoteUser ); if (Result == FALSE) { HttpStatusCode = STATUS_SERVER_ERROR; ExtensionProcResult = HSE_STATUS_ERROR; goto AbortAndExit; } } ASSERT(g_pServerInfo->RpcHttpProxyFreeString); RpcStatus = g_pServerInfo->RpcNewHttpProxyChannel ( ServerAddress, ServerPort, RemoteUser, &ActualServerName ); if (RpcStatus != RPC_S_OK) { HttpStatusCode = STATUS_SERVER_ERROR; ExtensionProcResult = HSE_STATUS_ERROR; goto AbortAndExit; } } else ActualServerName = ServerAddress; RpcStatus = I_RpcProxyNewConnection ( ProxyType, ActualServerName, ServerPort, pECB, (I_RpcProxyCallbackInterface *)&ProxyCallbackInterface ); if (g_pServerInfo->RpcNewHttpProxyChannel && (ActualServerName != ServerAddress)) { // get the remote user and call the redirector dll ASSERT(g_pServerInfo->RpcHttpProxyFreeString); g_pServerInfo->RpcHttpProxyFreeString(ActualServerName); } if (RpcStatus != RPC_S_OK) { ReplyToClientWithStatus(pECB, RpcStatus); HttpStatusCode = STATUS_CONNECTION_OK; ExtensionProcResult = HSE_STATUS_SUCCESS; goto AbortAndExit; } #if DBG DbgPrint("RPCPROXY: %d: Connection type %d to %S:%S\n", GetCurrentProcessId(), ProxyType, ServerAddress, ServerPort); #endif HttpStatusCode = STATUS_CONNECTION_OK; ExtensionProcResult = HSE_STATUS_PENDING; goto AbortAndExit; } } // // The RPC request must be a post (or RPC_CONNECT for 6.0): // if (_mbsicmp(pECB->lpszMethod,pLegacyVerb)) { dwSize = sizeof(STATUS_MUST_BE_POST_STR) - 1; // don't want to count trailing 0. ReplyToClient(pECB,STATUS_MUST_BE_POST_STR,&dwSize,&dwStatus); HttpStatusCode = STATUS_CONNECTION_OK; ExtensionProcResult = HSE_STATUS_SUCCESS; goto AbortAndExit; } // // Make sure there is no data from the initial BIND in the ECB data buffer: // // ASSERT(pECB->cbTotalBytes == 0); // // Get the connect parameters: // if (!ParseQueryString(pECB->lpszQueryString,&pOverlapped,&dwStatus)) { dwSize = sizeof(STATUS_POST_BAD_FORMAT_STR) - 1; // don't want to count trailing 0. ReplyToClient(pECB,STATUS_POST_BAD_FORMAT_STR,&dwSize,&dwStatus); HttpStatusCode = STATUS_CONNECTION_OK; ExtensionProcResult = HSE_STATUS_SUCCESS; goto AbortAndExit; } pOverlapped->pECB = pECB; // // Add the new ECB to the Active ECB List: // if (!AddToECBList(g_pServerInfo->pActiveECBList,pECB)) { #ifdef DBG_ERROR DbgPrint("HttpExtensionProc(): AddToECBList() failed\n"); #endif FreeOverlapped(pOverlapped); HttpStatusCode = STATUS_SERVER_ERROR; ExtensionProcResult = HSE_STATUS_ERROR; goto AbortAndExit; } // // Submit the first client async read: // if (!StartAsyncClientRead(pECB,pOverlapped->pConn,&dwStatus)) { #ifdef DBG_ERROR DbgPrint("HttpExtensionProc(): StartAsyncClientRead() failed %d\n",dwStatus); #endif FreeOverlapped(pOverlapped); CleanupECB(pECB); HttpStatusCode = STATUS_SERVER_ERROR; ExtensionProcResult = HSE_STATUS_ERROR; goto AbortAndExit; } // // Post the first server read on the new socket: // IncrementECBRefCount(pServerInfo->pActiveECBList,pECB); if (!SubmitNewRead(pServerInfo,pOverlapped,&dwStatus)) { #ifdef DBG_ERROR DbgPrint("HttpExtensionProc(): SubmitNewRead() failed %d\n",dwStatus); #endif FreeOverlapped(pOverlapped); CleanupECB(pECB); HttpStatusCode = STATUS_SERVER_ERROR; ExtensionProcResult = HSE_STATUS_ERROR; goto AbortAndExit; } // // Make sure the server receive thread is up and running: // if (!CheckStartReceiveThread(g_pServerInfo,&dwStatus)) { #ifdef DBG_ERROR DbgPrint("HttpExtensionProc(): CheckStartReceiveThread() failed %d\n",dwStatus); #endif FreeOverlapped(pOverlapped); CleanupECB(pECB); HttpStatusCode = STATUS_SERVER_ERROR; ExtensionProcResult = HSE_STATUS_ERROR; goto AbortAndExit; } // // Send back connection Ok to client, and also set fKeepConn to FALSE. // dwSize = sizeof(HeaderEx); dwFlags = 0; memset(&HeaderEx,0,dwSize); HeaderEx.fKeepConn = FALSE; if (!pECB->ServerSupportFunction(pECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER_EX, &HeaderEx, &dwSize, &dwFlags)) { #ifdef DBG_ERROR DbgPrint("HttpExtensionProc(): SSF(HSE_REQ_SEND_RESPONSE_HEADER_EX) failed %d\n",dwStatus); #endif FreeOverlapped(pOverlapped); CleanupECB(pECB); HttpStatusCode = STATUS_SERVER_ERROR; ExtensionProcResult = HSE_STATUS_ERROR; goto AbortAndExit; } HttpStatusCode = STATUS_SERVER_ERROR; ExtensionProcResult = HSE_STATUS_PENDING; // fall through AbortAndExit: if (RemoteUser) MemFree(RemoteUser); pECB->dwHttpStatusCode = HttpStatusCode; return ExtensionProcResult; }