/*********************************************************************** * * 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 MAX_RPC_RETRIES 3 //===[ External prototypes ]=============================================== extern int SetupOSSLSupport(void); extern void CleanupOSSLSupport(void); //===[ Function prototypes ]=============================================== //===[ Global variables ]================================================== static bool g_rpcInitialized = false; //++======================================================================= size_t CurlWriteCallback( IN void *pData, IN size_t dataItemSz, IN size_t numDataItems, IN RpcSession *pSession) // // Arguments: // // Returns: // // Abstract: // // Notes: // // L2 //=======================================================================-- { size_t dataConsumed = numDataItems; DbgTrace(1, "-CurlWriteCallback- Start\n", 0); // Consume the data by keeping a copy of the data. Note that we may have // already consumed some data in which case we need to allocate a new // buffer big enough to hold all of it. if (pSession->pRecvData == NULL) { // We have not yet consumed receive data for the current Rpc // if the data does not exceed our maximum Rpc reply size. if ((numDataItems * dataItemSz) <= MAX_RPC_REPLY_SZ) { pSession->pRecvData = (char*) malloc(numDataItems * dataItemSz); if (pSession->pRecvData) { // Consume the data memcpy(pSession->pRecvData, pData, numDataItems * dataItemSz); pSession->recvDataLen = numDataItems * dataItemSz; } else { DbgTrace(0, "-CurlWriteCallback- Buffer allocation error\n", 0); dataConsumed = CURLE_WRITE_ERROR; // To abort RPC } } else { DbgTrace(0, "-CurlWriteCallback- Max Rpc reply size exceeded\n", 0); dataConsumed = CURLE_WRITE_ERROR; // To abort RPC } } else { // We have already consumed receive data for the current Rpc, append the new data to it // if the data does not exceed our maximum Rpc reply size. if ((pSession->recvDataLen + (numDataItems * dataItemSz)) <= MAX_RPC_REPLY_SZ) { char *pNewRecvDataBuf = (char*) malloc(pSession->recvDataLen + (numDataItems * dataItemSz)); if (pNewRecvDataBuf) { memcpy(pNewRecvDataBuf, pSession->pRecvData, pSession->recvDataLen); memcpy(pNewRecvDataBuf + pSession->recvDataLen, pData, numDataItems * dataItemSz); pSession->recvDataLen += numDataItems * dataItemSz; free(pSession->pRecvData); pSession->pRecvData = pNewRecvDataBuf; } else { DbgTrace(0, "-CurlWriteCallback- Buffer allocation error\n", 0); dataConsumed = CURLE_WRITE_ERROR; // To abort RPC // Forget about already consumed data free(pSession->pRecvData); pSession->pRecvData = NULL; } } else { DbgTrace(0, "-CurlWriteCallback- Max Rpc reply size exceeded\n", 0); dataConsumed = CURLE_WRITE_ERROR; // To abort RPC // Forget about already consumed data free(pSession->pRecvData); pSession->pRecvData = NULL; } } DbgTrace(1, "-CurlWriteCallback- End\n", 0); return dataConsumed; } //++======================================================================= RpcSession* OpenRpcSession( IN const char *pHostName, IN const uint16_t hostPort) // // Arguments: // // Returns: // // Abstract: // // Notes: // // L2 //=======================================================================-- { RpcSession *pSession = NULL; char *pPartialHttpUrl = NULL; char *pPartialHttpsUrl = NULL; int hostNameLen = strlen(pHostName); DbgTrace(1, "-OpenRpcSession- Start\n", 0); // Build the partial URL strings that may be used with this session. pPartialHttpUrl = (char*) malloc(7 /*"http://"*/ + hostNameLen + 34 /*":XXXX/CasaAuthTokenSvc/Rpc?method="*/ + 1 /*NULL Terminator*/); pPartialHttpsUrl = (char*) malloc(8 /*"https://"*/ + hostNameLen + 34 /*":XXXX/CasaAuthTokenSvc/Rpc?method="*/ + 1 /*NULL Terminator*/); if (pPartialHttpUrl && pPartialHttpsUrl) { sprintf(pPartialHttpUrl, "http://%s:%d/CasaAuthTokenSvc/Rpc?method=", pHostName, hostPort); sprintf(pPartialHttpsUrl, "https://%s:%d/CasaAuthTokenSvc/Rpc?method=", pHostName, hostPort); // Allocate space for the session pSession = (RpcSession*) malloc(sizeof(*pSession)); if (pSession) { // Zero the session structure memset(pSession, 0, sizeof(*pSession)); // Get a curl handle pSession->hCurl = curl_easy_init(); if (pSession->hCurl != NULL) { CURLcode result; bool setOptError = false; // Set necessary options on the handle if ((result = curl_easy_setopt(pSession->hCurl, CURLOPT_NOSIGNAL, 0)) != CURLE_OK) { DbgTrace(0, "-OpenRpcSession- Error setting CURLOPT_NOSIGNAL, code = %d\n", result); setOptError = true; } if ((result = curl_easy_setopt(pSession->hCurl, CURLOPT_USERAGENT, "CASA Client/1.0")) != CURLE_OK) { DbgTrace(0, "-OpenRpcSession- Error setting CURLOPT_USERAGENT, code = %d\n", result); setOptError = true; } if ((result = curl_easy_setopt(pSession->hCurl, CURLOPT_POST, 1)) != CURLE_OK) { DbgTrace(0, "-OpenRpcSession- Error setting CURLOPT_POST, code = %d\n", result); setOptError = true; } if ((result = curl_easy_setopt(pSession->hCurl, CURLOPT_CAPATH, "/etc/ssl/certs")) != CURLE_OK) { DbgTrace(0, "-OpenRpcSession- Error setting CURLOPT_CAPATH, code = %d\n", result); setOptError = true; } pSession->headers = curl_slist_append(pSession->headers, "Content-Type: text/html"); pSession->headers = curl_slist_append(pSession->headers, "Expect:"); if ((result = curl_easy_setopt(pSession->hCurl, CURLOPT_HTTPHEADER, pSession->headers)) != CURLE_OK) { DbgTrace(0, "-OpenRpcSession- Error setting CURLOPT_HTTPHEADER, code = %d\n", result); setOptError = true; } if ((result = curl_easy_setopt(pSession->hCurl, CURLOPT_WRITEFUNCTION, CurlWriteCallback)) != CURLE_OK) { DbgTrace(0, "-OpenRpcSession- Error setting CURLOPT_WRITEFUNCTION, code = %d\n", result); setOptError = true; } if ((result = curl_easy_setopt(pSession->hCurl, CURLOPT_WRITEDATA, pSession)) != CURLE_OK) { DbgTrace(0, "-OpenRpcSession- Error setting CURLOPT_WRITEDATA, code = %d\n", result); setOptError = true; } // Now check if we succeded if (setOptError == false) { // Success, finish setting up the session object. pSession->pPartialHttpUrl = pPartialHttpUrl; pSession->partialHttpUrlLen = strlen(pPartialHttpUrl); pSession->pPartialHttpsUrl = pPartialHttpsUrl; pSession->partialHttpsUrlLen = strlen(pPartialHttpsUrl); // Forget about the partial URL buffers so that they do not get deleted below pPartialHttpUrl = NULL; pPartialHttpsUrl = NULL; } else { // Failed to set a needed curl option if (pSession->headers) curl_slist_free_all(pSession->headers); curl_easy_cleanup(pSession->hCurl); free(pSession); pSession = NULL; } } else { DbgTrace(0, "-OpenRpcSession- Error creating curl handle\n", 0); free(pSession); pSession = NULL; } } else { DbgTrace(0, "-OpenRpcSession- Failed to allocate buffer for rpc session\n", 0); } } else { DbgTrace(0, "-OpenRpcSession- Failed to allocate buffer for URL\n", 0); } // Free buffers not utilized if (pPartialHttpUrl) free(pPartialHttpUrl); if (pPartialHttpsUrl) free(pPartialHttpsUrl); DbgTrace(2, "-OpenRpcSession- End, pSession = %0lX\n", (long) pSession); return pSession; } //++======================================================================= void CloseRpcSession( IN RpcSession *pSession) // // Arguments: // // Returns: // // Abstract: // // Notes: // // L2 //=======================================================================-- { DbgTrace(1, "-CloseRpcSession- Start\n", 0); // Free any HTTP headers associated with the session if (pSession->headers) curl_slist_free_all(pSession->headers); // Close the curl handle associated with this session curl_easy_cleanup(pSession->hCurl); // Free the space allocated for the session if (pSession->pRecvData) free(pSession->pRecvData); free(pSession->pPartialHttpUrl); free(pSession->pPartialHttpsUrl); free(pSession); DbgTrace(1, "-CloseRpcSession- End\n", 0); } //++======================================================================= static CasaStatus InternalRpc( IN RpcSession *pSession, IN char *pMethod, IN long flags, IN char *pRequestData, INOUT char **ppResponseData, INOUT size_t *pResponseDataLen) // // Arguments: // // Returns: // // Abstract: // // Notes: // // L2 //=======================================================================-- { #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; char *pPartialUrl; int partialUrlLen; char *pUrl; CURLcode curlResult; DbgTrace(1, "-InternalRpc- Start\n", 0); // Initialize output parameters *ppResponseData = NULL; *pResponseDataLen = 0; // Setup the URL using the input parameters if (flags & SECURE_RPC_FLAG) { pPartialUrl = pSession->pPartialHttpsUrl; partialUrlLen = pSession->partialHttpsUrlLen; // Check if we need to ignore invalid CERTS if (flags & ALLOW_INVALID_CERTS_RPC_FLAG) { if ((curlResult = curl_easy_setopt(pSession->hCurl, CURLOPT_SSL_VERIFYPEER, 0)) != CURLE_OK) { DbgTrace(0, "-InternalRpc- Error setting CURLOPT_SSL_VERIFYPEER, code = %d\n", curlResult); } if ((curlResult = curl_easy_setopt(pSession->hCurl, CURLOPT_SSL_VERIFYHOST, 0)) != CURLE_OK) { DbgTrace(0, "-InternalRpc- Error setting CURLOPT_SSL_VERIFYHOST, code = %d\n", curlResult); } } else { if ((curlResult = curl_easy_setopt(pSession->hCurl, CURLOPT_SSL_VERIFYPEER, 1)) != CURLE_OK) { DbgTrace(0, "-InternalRpc- Error setting CURLOPT_SSL_VERIFYPEER, code = %d\n", curlResult); } if ((curlResult = curl_easy_setopt(pSession->hCurl, CURLOPT_SSL_VERIFYHOST, 2)) != CURLE_OK) { DbgTrace(0, "-InternalRpc- Error setting CURLOPT_SSL_VERIFYHOST, code = %d\n", curlResult); } } } else { pPartialUrl = pSession->pPartialHttpUrl; partialUrlLen = pSession->partialHttpUrlLen; } pUrl = (char*) malloc(partialUrlLen + strlen(pMethod) + 1); if (pUrl) { strcpy(pUrl, pPartialUrl); strcat(pUrl, pMethod); // Tell curl about the URL curlResult = curl_easy_setopt(pSession->hCurl, CURLOPT_URL, pUrl); if (curlResult == CURLE_OK) { // Tell curl about our post data curlResult = curl_easy_setopt(pSession->hCurl, CURLOPT_POSTFIELDS, pRequestData); if (curlResult == CURLE_OK) { // Tell curl about our post data len curlResult = curl_easy_setopt(pSession->hCurl, CURLOPT_POSTFIELDSIZE, strlen(pRequestData)); if (curlResult == CURLE_OK) { // Now do the HTTP request curlResult = curl_easy_perform(pSession->hCurl); if (curlResult == CURLE_OK) { // Get the HTTP Response code long httpCompStatus; curlResult = curl_easy_getinfo(pSession->hCurl, CURLINFO_RESPONSE_CODE, &httpCompStatus); if (curlResult == CURLE_OK) { // Verify that the HTTP request was successfully completed by the server if (httpCompStatus == 200) { // Success, return the response data to the caller. retStatus = CASA_STATUS_SUCCESS; *ppResponseData = pSession->pRecvData; *pResponseDataLen = pSession->recvDataLen;; // Forget about the response data buffer to keep from freeing it. pSession->pRecvData = NULL; } else { DbgTrace(0, "-InternalRpc- HTTP request did not complete successfully, status = %ld\n", httpCompStatus); retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR, CASA_FACILITY_AUTHTOKEN, CASA_STATUS_UNSUCCESSFUL); } } else { DbgTrace(0, "-OpenRpcSession- Curl get info failed, code = %d\n", curlResult); retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR, CASA_FACILITY_AUTHTOKEN, CASA_STATUS_UNSUCCESSFUL); } } else { DbgTrace(0, "-OpenRpcSession- Curl perform failed, code = %d\n", curlResult); retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR, CASA_FACILITY_AUTHTOKEN, CASA_STATUS_UNSUCCESSFUL); } // Make sure that we never exit with a recv data buffer hanging off the session if (pSession->pRecvData) { free(pSession->pRecvData); pSession->pRecvData = NULL; } } else { DbgTrace(0, "-OpenRpcSession- Error setting CURLOPT_POSTFIELDSIZE, code = %d\n", curlResult); retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR, CASA_FACILITY_AUTHTOKEN, CASA_STATUS_UNSUCCESSFUL); } } else { DbgTrace(0, "-OpenRpcSession- Error setting CURLOPT_POSTFIELDS, code = %d\n", curlResult); retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR, CASA_FACILITY_AUTHTOKEN, CASA_STATUS_UNSUCCESSFUL); } } else { DbgTrace(0, "-OpenRpcSession- Error setting CURLOPT_URL, code = %d\n", curlResult); retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR, CASA_FACILITY_AUTHTOKEN, CASA_STATUS_UNSUCCESSFUL); } // Free the buffer used to hold the URL free(pUrl); } 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 = %0X\n", retStatus); return retStatus; } //++======================================================================= CasaStatus Rpc( IN RpcSession *pSession, IN char *pMethod, IN long flags, IN char *pRequestData, INOUT char **ppResponseData, INOUT size_t *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 = %0X\n", retStatus); return retStatus; } //++======================================================================= CasaStatus InitializeRpc(void) // // Arguments: // // Returns: // // Abstract: // // Notes: // // L2 //=======================================================================-- { CasaStatus retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR, CASA_FACILITY_AUTHTOKEN, CASA_STATUS_UNSUCCESSFUL); DbgTrace(1, "-InitializeRpc- Start\n", 0); // Initialize OpenSSL support if (SetupOSSLSupport() == 0) { // Perform libcurl initializatoin CURLcode curlStatus = curl_global_init(CURL_GLOBAL_SSL); if (curlStatus != 0) { DbgTrace(0, "-InitializeRpc- Error initializing libcurl, curlStatus = %0X\n", curlStatus); CleanupOSSLSupport(); } else { // Success g_rpcInitialized = true; retStatus = CASA_STATUS_SUCCESS; } } else { DbgTrace(0, "-InitializeRpc- OpenSSL support setup failure\n", 0); } DbgTrace(1, "-InitializeRpc- End, retStatus = %0X\n", retStatus); return retStatus; } //++======================================================================= void UnInitializeRpc(void) // // Arguments: // // Returns: // // Abstract: // // Notes: // // L2 //=======================================================================-- { DbgTrace(1, "-UnInitializeRpc- Start\n", 0); // Only try to cleanup if we were initialized if (g_rpcInitialized) { // Cleanup libcurl curl_global_cleanup(); // Cleanup OpenSSL support CleanupOSSLSupport(); // Forget about having been initialized g_rpcInitialized = false; } DbgTrace(1, "-UnInitializeRpc- End\n", 0); } //++======================================================================= //++======================================================================= //++=======================================================================