CASA/CASA-auth-token/client/library/linux/rpc.c
Juan Carlos Luciani 215bd98dd5 Changing the RPC retry logic to be bounded by time rather than by the
number of retries to match what is being done in the Windows platform.
2008-07-09 17:51:22 +00:00

696 lines
22 KiB
C

/***********************************************************************
*
* 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 <jluciani@novell.com>
*
***********************************************************************/
//===[ Include files ]=====================================================
#include "internal.h"
//===[ Type definitions ]==================================================
#define MAX_RPC_RETRIES 3
#define MILLISECONDS_BETWEEN_RPC_RETRIES 3000
#define MAX_RPC_TIME_MILLISECONDS 60000
//===[ 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);
if (curlResult == CURLE_COULDNT_CONNECT
|| curlResult == CURLE_SSL_CONNECT_ERROR)
{
retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
CASA_FACILITY_AUTHTOKEN,
CASA_STATUS_AUTH_SERVER_UNAVAILABLE);
}
else if (curlResult == CURLE_OPERATION_TIMEOUTED
|| curlResult == CURLE_SEND_ERROR
|| curlResult == CURLE_RECV_ERROR)
{
retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
CASA_FACILITY_AUTHTOKEN,
CASA_STATUS_CONNECTION_ERROR);
}
else
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;
DWORD startTime = GetTickCount();
DWORD currentTime;
DbgTrace(1, "-Rpc- Start\n", 0);
// Retry the RPC as needed
do
{
// Check if this is a retry
if (retries != 0)
{
// This is a retry, check if we should keep retrying.
currentTime = GetTickCount();
if (currentTime > startTime)
{
if ((currentTime - startTime) > MAX_RPC_TIME_MILLISECONDS)
{
DbgTrace(1, "-Rpc- Stopping after %d retries\n", retries);
break;
}
}
else
{
// The clock must have wrapped, treat it as if no time has elapsed.
startTime = currentTime;
}
// Pause before the next retry
usleep(MILLISECONDS_BETWEEN_RPC_RETRIES * 1000);
}
// Issue the RPC
retStatus = InternalRpc(pSession,
pMethod,
flags,
pRequestData,
ppResponseData,
pResponseDataLen);
// Account for this try
retries ++;
} while (CasaStatusCode(retStatus) == CASA_STATUS_CONNECTION_ERROR);
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);
}
//++=======================================================================
//++=======================================================================
//++=======================================================================