/*********************************************************************** * * Copyright (C) 2006 Novell, Inc. All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; version 2.1 * of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, Novell, Inc. * * To contact Novell about this file by physical or electronic mail, * you may find current contact information at www.novell.com. * * Author: Juan Carlos Luciani * ***********************************************************************/ //===[ Include files ]===================================================== #include "internal.h" //===[ Type definitions ]================================================== #define INITIAL_RESPONSE_DATA_BUF_SIZE 1028 #define INCREMENT_RESPONSE_DATA_BUF_SIZE 256 #define MAX_RPC_RETRIES 3 //===[ Function prototypes ]=============================================== //===[ Global variables ]================================================== //++======================================================================= static CasaStatus CopyMultiToWideAlloc( IN char *pMulti, IN int multiSize, INOUT LPWSTR *ppWide, INOUT int *pWideSize) // // Arguments: // // Returns: // // Abstract: // // Notes: // // L2 //=======================================================================-- { int retStatus; int size, i; DbgTrace(2, "-CopyMultiToWideAlloc- Start\n", 0); size = (multiSize + 1) * sizeof(WCHAR); if ((*ppWide = (PWCHAR) malloc(size)) != NULL) { for (i = 0; i < multiSize; i++) { *(*ppWide + i) = (unsigned char) *(pMulti + i); } *(*ppWide + i) = L'\0'; if (pWideSize) { *pWideSize = size - sizeof(WCHAR); } retStatus = CASA_STATUS_SUCCESS; } else { retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR, CASA_FACILITY_AUTHTOKEN, CASA_STATUS_INSUFFICIENT_RESOURCES); } DbgTrace(2, "-CopyMultiToWideAlloc- End, retStatus = %08X\n", retStatus); return retStatus; } //++======================================================================= static CasaStatus CopyWideToMultiAlloc( IN LPWSTR pWide, IN int wideSize, INOUT char **ppMulti, INOUT int *pMultiSize) // // Arguments: // // Returns: // // Abstract: // // Notes: // // L2 //=======================================================================-- { int retStatus; int size, i; DbgTrace(2, "-CopyWideToMultiAlloc- Start\n", 0); size = wideSize + 1; if ((*ppMulti = malloc(size)) != NULL) { for (i = 0; i < wideSize; i++) { *(*ppMulti + i) = (char) *(pWide + i); } *(*ppMulti + i) = '\0'; if (pMultiSize) { *pMultiSize = size - 1; } retStatus = CASA_STATUS_SUCCESS; } else { retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR, CASA_FACILITY_AUTHTOKEN, CASA_STATUS_INSUFFICIENT_RESOURCES); } DbgTrace(2, "-CopyWideToMultiAlloc- End, retStatus = %08X\n", retStatus); return retStatus; } //++======================================================================= RpcSession* OpenRpcSession( IN char *pHostName, IN uint16_t hostPort) // // Arguments: // // Returns: // // Abstract: // // Notes: // // L2 //=======================================================================-- { RpcSession *pSession; bool success = false; DbgTrace(1, "-OpenRpcSession- Start\n", 0); // Allocate space for the session pSession = (RpcSession*) malloc(sizeof(*pSession)); if (pSession) { // Zero the session structure memset(pSession, 0, sizeof(*pSession)); // Save copy of the hostname pSession->pHostName = malloc(strlen(pHostName) + 1); if (pSession->pHostName) { strcpy(pSession->pHostName, pHostName); // Open a Winhttp session pSession->hSession = WinHttpOpen(L"CASA Client/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); if (pSession->hSession) { LPWSTR pWideHostName; int wideHostLen; // Session opened, now convert the host name to Unicode so that // we can open a connection. if (CopyMultiToWideAlloc(pHostName, (int) strlen(pHostName), &pWideHostName, &wideHostLen) == CASA_STATUS_SUCCESS) { // Now open connection pSession->hConnection = WinHttpConnect(pSession->hSession, pWideHostName, hostPort, 0); if (pSession->hConnection == NULL) { DbgTrace(0, "-OpenRpcSession- Failed to open connection, error = %d\n", GetLastError()); } else { success = true; } // Free the host name wide string buffer free(pWideHostName); } else { DbgTrace(0, "-OpenRpcSession- Error converting host name to wide string\n", 0); } } else { DbgTrace(0, "-OpenRpcSession- Failed to open session, error = %d\n", GetLastError()); } } else { DbgTrace(0, "-OpenRpcSession- Failed to allocate buffer for host name\n", 0); } } else { DbgTrace(0, "-OpenRpcSession- Failed to allocate buffer for rpc session\n", 0); } // Clean up if we did not succeed if (!success) { if (pSession) { if (pSession->hConnection) WinHttpCloseHandle(pSession->hConnection); if (pSession->hSession) WinHttpCloseHandle(pSession->hSession); if (pSession->pHostName) free(pSession->pHostName); free(pSession); pSession = NULL; } } DbgTrace(2, "-OpenRpcSession- End, pSession = %08X\n", pSession); return pSession; } //++======================================================================= void CloseRpcSession( IN RpcSession *pSession) // // Arguments: // // Returns: // // Abstract: // // Notes: // // L2 //=======================================================================-- { DbgTrace(1, "-CloseRpcSession- Start\n", 0); // Close the connection handle WinHttpCloseHandle(pSession->hConnection); // Close the session handle WinHttpCloseHandle(pSession->hSession); // Free hostname buffer if necessary if (pSession->pHostName) free(pSession->pHostName); // Free the space allocated for the session free(pSession); DbgTrace(1, "-CloseRpcSession- End\n", 0); } //++======================================================================= static void CALLBACK SecureFailureStatusCallback( IN HINTERNET hRequest, IN DWORD *pContext, IN DWORD internetStatus, IN LPVOID pStatusInformation, IN DWORD statusInformationLength) // // Arguments: // // Returns: // // Abstract: // // Notes: // // L0 //=======================================================================-- { DbgTrace(1, "-SecureFailureStatusCallback- Start\n", 0); // Only deal with failures related to certificates if (internetStatus == WINHTTP_CALLBACK_STATUS_SECURE_FAILURE) { // Save the specific failure status *pContext = *(DWORD*) pStatusInformation; } DbgTrace(1, "-SecureFailureStatusCallback- End\n", 0); } //++======================================================================= static CasaStatus InternalRpc( IN RpcSession *pSession, IN char *pMethod, IN long flags, IN char *pRequestData, INOUT char **ppResponseData, INOUT int *pResponseDataLen) // // Arguments: // // Returns: // // Abstract: // // Notes: // // L2 //=======================================================================-- { #define RPC_TARGET_FMT_STRING "CasaAuthTokenSvc/Rpc?method=%s" #ifndef CASA_STATUS_INVALID_SERVER_CERTIFICATE #define CASA_STATUS_INVALID_SERVER_CERTIFICATE CASA_STATUS_UNSUCCESSFUL // temporary until casa_status.h is updated #endif CasaStatus retStatus = CASA_STATUS_SUCCESS; char *pRpcTarget; LPWSTR pWideRpcTarget; int wideRpcTargetLen; WCHAR sendHeaders[] = L"Content-Type: text/html"; DWORD securityFailureStatusFlags; int retriesAllowed = 1; bool attemptRetry; DbgTrace(1, "-InternalRpc- Start\n", 0); // Initialize output parameter *ppResponseData = NULL; // Create rpc target string and convert it to a wide string pRpcTarget = (char*) malloc(sizeof(RPC_TARGET_FMT_STRING) + strlen(pMethod)); if (pRpcTarget) { sprintf(pRpcTarget, RPC_TARGET_FMT_STRING, pMethod); retStatus = CopyMultiToWideAlloc(pRpcTarget, (int) strlen(pRpcTarget), &pWideRpcTarget, &wideRpcTargetLen); if (CASA_SUCCESS(retStatus)) { HINTERNET hRequest; do { // Forget about having been told to retry attemptRetry = false; // Open a request handle hRequest = WinHttpOpenRequest(pSession->hConnection, L"POST", pWideRpcTarget, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, flags & SECURE_RPC_FLAG? WINHTTP_FLAG_REFRESH | WINHTTP_FLAG_SECURE : WINHTTP_FLAG_REFRESH); if (hRequest) { int reqDataLen = (int) strlen(pRequestData); // Check if we need to set options to deal with secure connections if (flags & SECURE_RPC_FLAG) { // We are using secure connections, now proceed based on whether or not // we are configured to allow invalid certificates. if (flags & ALLOW_INVALID_CERTS_RPC_FLAG || (flags & ALLOW_INVALID_CERTS_USER_APPROVAL_RPC_FLAG && InvalidCertsFromHostAllowed(pSession->pHostName))) { DWORD secFlags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_UNKNOWN_CA; // We are configured to allow invalid certificates, inform the HTTP stack. if (WinHttpSetOption(hRequest, WINHTTP_OPTION_SECURITY_FLAGS, &secFlags, sizeof(secFlags)) == FALSE) { DbgTrace(0, "-InternalRpc- Failed setting options to ignore invalid certs, error = %d\n", GetLastError()); } } else { // We are not configured to allow invalid certificates, set a callback handler // to detect invalid certificate conditions. if (WinHttpSetStatusCallback(hRequest, SecureFailureStatusCallback, WINHTTP_CALLBACK_FLAG_SECURE_FAILURE, (DWORD_PTR) NULL) == WINHTTP_INVALID_STATUS_CALLBACK) { DbgTrace(0, "-InternalRpc- Failed setting status callback, error = %d\n", GetLastError()); } } } // Send the request securityFailureStatusFlags = 0; if (WinHttpSendRequest(hRequest, sendHeaders, -1, pRequestData, reqDataLen, reqDataLen, (DWORD_PTR) &securityFailureStatusFlags)) { // Request sent, now await for the response. if (WinHttpReceiveResponse(hRequest, NULL)) { WCHAR httpCompStatus[4] = {0}; DWORD httpCompStatusLen = sizeof(httpCompStatus); // Response received, make sure that it completed successfully. if (WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_STATUS_CODE, NULL, &httpCompStatus, &httpCompStatusLen, WINHTTP_NO_HEADER_INDEX)) { // Check that the request completed successfully if (memcmp(httpCompStatus, L"200", sizeof(httpCompStatus)) == 0) { char *pResponseData; int responseDataBufSize = INITIAL_RESPONSE_DATA_BUF_SIZE; int responseDataRead = 0; // Now read the response data, to do so we need to allocate a buffer. pResponseData = (char*) malloc(INITIAL_RESPONSE_DATA_BUF_SIZE); if (pResponseData) { char *pCurrLocation = pResponseData; DWORD bytesRead; do { bytesRead = 0; if (WinHttpReadData(hRequest, (LPVOID) pCurrLocation, responseDataBufSize - responseDataRead, &bytesRead)) { pCurrLocation += bytesRead; responseDataRead += bytesRead; // Check if we need to allocate a larger buffer if (responseDataRead == responseDataBufSize) { char *pTmpBuf; // We need to upgrade the receive buffer pTmpBuf = (char*) malloc(responseDataBufSize + INCREMENT_RESPONSE_DATA_BUF_SIZE); if (pTmpBuf) { memcpy(pTmpBuf, pResponseData, responseDataBufSize); free(pResponseData); pResponseData = pTmpBuf; pCurrLocation = pResponseData + responseDataBufSize; responseDataBufSize += INCREMENT_RESPONSE_DATA_BUF_SIZE; } else { DbgTrace(0, "-InternalRpc- Buffer allocation failure\n", 0); retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR, CASA_FACILITY_AUTHTOKEN, CASA_STATUS_INSUFFICIENT_RESOURCES); } } } else { DbgTrace(0, "-InternalRpc- Failed reading response data, error = %d\n", GetLastError()); retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR, CASA_FACILITY_AUTHTOKEN, CASA_STATUS_UNSUCCESSFUL); } } while (CASA_SUCCESS(retStatus) && bytesRead != 0); // Check if the response data was successfully received if (CASA_SUCCESS(retStatus)) { // The response data was received, return it to the caller. *ppResponseData = pResponseData; *pResponseDataLen = responseDataRead; } else { // Failed to receive the response data, free the allocated buffer. free(pResponseData); } } else { DbgTrace(0, "-InternalRpc- Buffer allocation failure\n", 0); retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR, CASA_FACILITY_AUTHTOKEN, CASA_STATUS_INSUFFICIENT_RESOURCES); } } else { DbgTrace(0, "-InternalRpc- HTTP request did not complete successfully, status = %S\n", httpCompStatus); retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR, CASA_FACILITY_AUTHTOKEN, CASA_STATUS_UNSUCCESSFUL); } } else { DbgTrace(0, "-InternalRpc- Unable to obtain http request completion status, error = %d\n", GetLastError()); retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR, CASA_FACILITY_AUTHTOKEN, CASA_STATUS_UNSUCCESSFUL); } } else { DbgTrace(0, "-InternalRpc- Unable to receive response, error = %d\n", GetLastError()); retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR, CASA_FACILITY_AUTHTOKEN, CASA_STATUS_UNSUCCESSFUL); } } else { int error = GetLastError(); if (error == ERROR_WINHTTP_CANNOT_CONNECT) { DbgTrace(0, "-InternalRpc- Unable to connect to server\n", 0); retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR, CASA_FACILITY_AUTHTOKEN, CASA_STATUS_AUTH_SERVER_UNAVAILABLE); } else if (error == ERROR_WINHTTP_SECURE_FAILURE) { DbgTrace(1, "-InternalRpc- Secure connection failure, flags = %0x\n", securityFailureStatusFlags); // Try to deal with the issue if ((securityFailureStatusFlags & ~(WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA | WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID | WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID)) == 0 && flags & ALLOW_INVALID_CERTS_USER_APPROVAL_RPC_FLAG) { WINHTTP_CERTIFICATE_INFO certInfo; DWORD certInfoLen = sizeof(certInfo); // The failure was due to an invalid CN, CA, or both. // // Obtain information about the server certificate to give user // the choice of accepting it. if (WinHttpQueryOption(hRequest, WINHTTP_OPTION_SECURITY_CERTIFICATE_STRUCT, &certInfo, &certInfoLen) && certInfo.lpszSubjectInfo != NULL && certInfo.lpszIssuerInfo != NULL) { char *pSubjectInfo; int subjectInfoLen; // Convert the subjectInfo to multi-byte retStatus = CopyWideToMultiAlloc(certInfo.lpszSubjectInfo, (int) wcslen(certInfo.lpszSubjectInfo), &pSubjectInfo, &subjectInfoLen); if (CASA_SUCCESS(retStatus)) { char *pIssuerInfo; int issuerInfoLen; // Convert the issuerInfo to multi-byte retStatus = CopyWideToMultiAlloc(certInfo.lpszIssuerInfo, (int) wcslen(certInfo.lpszIssuerInfo), &pIssuerInfo, &issuerInfoLen); if (CASA_SUCCESS(retStatus)) { long invalidCertFlags = 0; // Setup the invalid cert flags if (securityFailureStatusFlags & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA) invalidCertFlags |= INVALID_CERT_CA_FLAG; if (securityFailureStatusFlags & WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID) invalidCertFlags |= INVALID_CERT_CN_FLAG; if (securityFailureStatusFlags & WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID) invalidCertFlags |= INVALID_CERT_DATE_FLAG; // Give user the choice to accept the certificate if (UserApprovedCert(pSession->pHostName, pSubjectInfo, pIssuerInfo, invalidCertFlags)) { DbgTrace(1, "-InternalRpc- User approved invalid certificate from %s\n", pSession->pHostName); // tbd - Investigate if there is a way to set the accepted certificate in a store so that // it can be utilized by the SSL stack directly. This would be a better method for dealing with // this issue. AllowInvalidCertsFromHost(pSession->pHostName); // Try to retry the request attemptRetry = true; } else { DbgTrace(1, "-InternalRpc- User did not approve invalid certificate from %s\n", pSession->pHostName); retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR, CASA_FACILITY_AUTHTOKEN, CASA_STATUS_INVALID_SERVER_CERTIFICATE); } // Free the buffer containing the issuerInfo free(pIssuerInfo); } // Free the buffer containing the subjectInfo free(pSubjectInfo); } // Free necessary certificate information if (certInfo.lpszSubjectInfo) LocalFree(certInfo.lpszSubjectInfo); if (certInfo.lpszIssuerInfo) LocalFree(certInfo.lpszIssuerInfo); if (certInfo.lpszProtocolName) LocalFree(certInfo.lpszProtocolName); if (certInfo.lpszSignatureAlgName) LocalFree(certInfo.lpszSignatureAlgName); if (certInfo.lpszEncryptionAlgName) LocalFree(certInfo.lpszEncryptionAlgName); } else { DbgTrace(0, "-InternalRpc- Unable to obtain server certificate struct, error = %0x\n", GetLastError()); } } else { // Decided to no give the user a choice to accept invalid server certificate retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR, CASA_FACILITY_AUTHTOKEN, CASA_STATUS_INVALID_SERVER_CERTIFICATE); } } else { DbgTrace(0, "-InternalRpc- Unsuccessful send http request, error = %d\n", error); retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR, CASA_FACILITY_AUTHTOKEN, CASA_STATUS_UNSUCCESSFUL); } } // Close the request handle WinHttpCloseHandle(hRequest); } else { DbgTrace(0, "-InternalRpc- Unable to open http request, error = %d\n", GetLastError()); retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR, CASA_FACILITY_AUTHTOKEN, CASA_STATUS_UNSUCCESSFUL); } } while (attemptRetry && retriesAllowed--); // Free the rpc target wide string buffer free(pWideRpcTarget); } else { DbgTrace(0, "-InternalRpc- Error converting method name to wide string\n", 0); } // Free buffer used to hold the rpc target string free(pRpcTarget); } else { DbgTrace(0, "-InternalRpc- Buffer allocation failure\n", 0); retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR, CASA_FACILITY_AUTHTOKEN, CASA_STATUS_INSUFFICIENT_RESOURCES); } DbgTrace(1, "-InternalRpc- End, retStatus = %d\n", retStatus); return retStatus; } //++======================================================================= CasaStatus Rpc( IN RpcSession *pSession, IN char *pMethod, IN long flags, IN char *pRequestData, INOUT char **ppResponseData, INOUT int *pResponseDataLen) // // Arguments: // // Returns: // // Abstract: // // Notes: // // L2 //=======================================================================-- { CasaStatus retStatus; int retries = 0; DbgTrace(1, "-Rpc- Start\n", 0); // Retry the RPC as needed do { // Issue the RPC retStatus = InternalRpc(pSession, pMethod, flags, pRequestData, ppResponseData, pResponseDataLen); // Account for this try retries ++; } while (CasaStatusCode(retStatus) == CASA_STATUS_AUTH_SERVER_UNAVAILABLE && retries < MAX_RPC_RETRIES); DbgTrace(1, "-Rpc- End, retStatus = %d\n", retStatus); return retStatus; } //++======================================================================= CasaStatus InitializeRpc(void) // // Arguments: // // Returns: // // Abstract: // // Notes: // // L2 //=======================================================================-- { CasaStatus retStatus = CASA_STATUS_SUCCESS; DbgTrace(1, "-InitializeRpc- Start\n", 0); // Nothing to do for windows DbgTrace(1, "-InitializeRpc- End, retStatus = %08X\n", retStatus); return retStatus; } //++======================================================================= //++======================================================================= //++=======================================================================