CASA/CASA-auth-token/client/library/linux/rpc.c

696 lines
22 KiB
C
Raw Normal View History

/***********************************************************************
*
* 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);
}
//++=======================================================================
//++=======================================================================
//++=======================================================================