blob: 0cb6600b75e92d9c531762e61c816ee620ce348f [file] [log] [blame]
/*
Copyright © Trustonic Limited 2013
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the Trustonic Limited nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>
#include <curl/curl.h>
#include "logging.h"
#include "rootpaErrors.h"
#include "seclient.h"
#include "cacerts.h"
#define HTTP_CODE_MOVED 301
#define HTTP_CODE_BAD_REQUEST 400
#define HTTP_CODE_NOT_FOUND 404
#define HTTP_CODE_METHOD_NOT_ALLOWED 405
#define HTTP_CODE_NOT_ACCEPTABLE 406
#define HTTP_CODE_REQUEST_TIMEOUT 408
#define HTTP_CODE_CONFLICT 409
#define HTTP_CODE_LENGTH_REQUIRED 411
#define HTTP_CODE_TOO_LONG 414
#define HTTP_CODE_UNSUPPORTED_MEDIA 415
#define HTTP_CODE_INVALID_DATA 422
#define HTTP_CODE_FAILED_DEPENDENCY 424
#define HTTP_CODE_INTERNAL_ERROR 500
#define HTTP_CODE_CMP_VERSION 501
#define HTTP_CODE_SERVICE_UNAVAILABLE 503
#define HTTP_CODE_HTTP_VERSION 505
#ifdef __DEBUG
#define NONEXISTENT_TEST_URL "http://10.255.255.8:9/"
#endif
#define CERT_PATH_MAX_LEN 256
#define CECERT_FILENAME "cacert.pem"
static char certificatePath_[CERT_PATH_MAX_LEN];
static char certificateFilePath_[CERT_PATH_MAX_LEN];
static int MAX_ATTEMPTS=30;
static const struct timespec SLEEPTIME={0,300*1000*1000}; // 0.3 seconds --> 30x0.3 = 9 seconds
rootpaerror_t httpCommunicate(const char* const inputP, const char** linkP, const char** relP, const char** commandP, httpMethod_t method);
rootpaerror_t httpPostAndReceiveCommand(const char* const inputP, const char** linkP, const char** relP, const char** commandP)
{
LOGD("httpPostAndReceiveCommand %ld", (long int) inputP);
return httpCommunicate(inputP, linkP, relP, commandP, httpMethod_POST);
}
rootpaerror_t httpPutAndReceiveCommand(const char* const inputP, const char** linkP, const char** relP, const char** commandP)
{
LOGD("httpPutAndReceiveCommand %ld", (long int) inputP);
if(NULL==inputP)
{
return ROOTPA_ERROR_ILLEGAL_ARGUMENT;
}
LOGD("%s", inputP);
return httpCommunicate(inputP, linkP, relP, commandP, httpMethod_PUT);
}
rootpaerror_t httpGetAndReceiveCommand(const char** linkP, const char** relP, const char** commandP)
{
LOGD("httpGetAndReceiveCommand");
return httpCommunicate(NULL, linkP, relP, commandP, false);
}
rootpaerror_t httpDeleteAndReceiveCommand(const char** linkP, const char** relP, const char** commandP)
{
LOGD("httpDeleteAndReceiveCommand");
return httpCommunicate(NULL, linkP, relP, commandP, httpMethod_DELETE);
}
typedef struct
{
char* memoryP;
size_t size;
} MemoryStruct;
typedef struct
{
char* linkP;
size_t linkSize;
char* relP;
size_t relSize;
} HeaderStruct;
typedef struct
{
const char* responseP;
size_t size;
uint32_t offset;
} ResponseStruct;
static size_t readResponseCallback(void *ptr, size_t size, size_t nmemb, void *userp)
{
size_t totalSize=nmemb*size;
size_t readSize;
ResponseStruct* rspP=(ResponseStruct*) userp;
LOGD(">>readResponseCallback %d %d %d\n", (int) totalSize, (int) rspP->size, rspP->offset);
if(rspP->offset>=rspP->size) return 0;
if(totalSize<((rspP->size)))
{
readSize=totalSize;
}
else
{
readSize=rspP->size;
}
memcpy(ptr, (rspP->responseP+rspP->offset), readSize);
rspP->offset+=readSize;
LOGD("<<readResponseCallback %d %d %d\n", (int) readSize, (int) rspP->size, rspP->offset);
return readSize;
}
static size_t writeMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
size_t realsize = size * nmemb;
MemoryStruct* mem = (MemoryStruct *)userp;
mem->memoryP = realloc(mem->memoryP, mem->size + realsize + 1);
if (mem->memoryP == NULL) {
/* out of memory! */
LOGE("not enough memory (realloc returned NULL)\n");
return 0; // returning anything different from what was passed to this function indicates an error
}
memcpy(&(mem->memoryP[mem->size]), contents, realsize);
mem->size += realsize;
mem->memoryP[mem->size] = 0;
return realsize;
}
#ifdef __DEBUG
int debug_function (CURL * curl_handle, curl_infotype info, char* debugMessageP, size_t debugMessageSize, void * extrabufferP)
{
if(debugMessageP!=NULL && debugMessageSize!=0)
{
char* msgP=malloc(debugMessageSize+1);
memcpy(msgP, debugMessageP, debugMessageSize);
msgP[debugMessageSize]=0;
LOGD("curl: %d %s",info, msgP);
free(msgP);
}
else
{
LOGD("curl: no debug msg %d %d", info, (int) debugMessageSize);
}
return 0;
}
#endif
bool copyHeader(void *contents, size_t length, char** headerP)
{
*headerP = malloc(length + 1);
if (*headerP == NULL) {
/* out of memory! */
LOGE("not enough memory (malloc returned NULL)\n");
return false;
}
memcpy(*headerP , contents, length);
(*headerP)[length] = 0;
return true;
}
//
// The header format is as follow
// Link <https://se.cgbe.trustonic.com:8443/activity/00000000-4455-6677-8899-aabbccddeeff>;rel="http://10.0.2.2/relation/system_info"
// parse out uri's specified in Link and rel
//
bool updateLinkAndRel(HeaderStruct* memP, void* ptr)
{
char* startP=NULL;
char* endP=NULL;
// first update link
startP=strcasestr((char*) ptr, "Link");
if(NULL==startP) return false;
startP=strstr(startP,"<");
if(NULL==startP) return false;
startP++;
endP=strstr(startP,">");
if(NULL==endP) return false;
memP->linkSize=endP-startP;
if(copyHeader(startP, memP->linkSize, &(memP->linkP))==false)
{
return false;
}
// then update rel, we will be successful even if it is not found
startP=strcasestr(endP, "rel=");
if(NULL==startP)
{
return true;
}
startP+=5; // sizeof "rel="
endP=strstr(startP,"\"");
if(NULL==endP)
{
return true;
}
memP->relSize=endP-startP;
if(copyHeader(startP, memP->relSize, &(memP->relP))==false)
{
LOGE("could not copy rel, but since we are this far, continuing anyway");
}
return true;
}
static size_t writeHeaderCallback( void *ptr, size_t size, size_t nmemb, void *userp)
{
size_t realSize = size * nmemb;
HeaderStruct* memP = (HeaderStruct *)userp;
if(realSize>=sizeof("Link:") && memcmp(ptr, "Link:", sizeof("Link:")-1)==0)
{
if(updateLinkAndRel(memP, ptr)==false)
{
LOGE("Problems in updating Link and rel");
}
}
return realSize;
}
uint32_t shorter(uint32_t first, uint32_t second)
{
return (first>second?second:first);
}
void setCertPath(const char* localPathP, const char* certPathP)
{
memset(certificatePath_, 0, CERT_PATH_MAX_LEN);
memset(certificateFilePath_, 0, CERT_PATH_MAX_LEN);
if (certPathP!=NULL && (strlen(certPathP)+1)<CERT_PATH_MAX_LEN)
{
strcpy(certificatePath_, certPathP);
}
if (localPathP!=NULL && (strlen(localPathP)+1+sizeof(CECERT_FILENAME))<CERT_PATH_MAX_LEN)
{
strcpy(certificateFilePath_, localPathP);
strcat(certificateFilePath_, "/");
}
strcat(certificateFilePath_, CECERT_FILENAME);
}
//
// TODO-refactor: saveCertFile is duplicate from saveFile in xmlMessageHandler.c, move these to common place
//
void saveCertFile(char* filePath, char* fileContent)
{
LOGD(">>saveCertFile %s", filePath);
FILE* fh;
if ((fh = fopen(filePath, "w")) != NULL) // recreating the file every time, this is not the most efficient way, but ensures
{ // the file is updated in case rootpa and the required content is updated
fprintf(fh, "%s", fileContent);
fclose(fh);
}
else
{
LOGE("saveCertFile no handle %s", filePath);
}
LOGD("<<saveCertFile");
}
bool setBasicOpt(CURL* curl_handle, MemoryStruct* chunkP, HeaderStruct* headerChunkP, const char* linkP, struct curl_slist* headerListP)
{
if(curl_easy_setopt(curl_handle, CURLOPT_URL, linkP)!=CURLE_OK)
{
LOGE("curl_easy_setopt CURLOPT_URL failed");
return false;
}
/* reading response to memory instead of file */
if(curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, writeMemoryCallback)!=CURLE_OK)
{
LOGE("curl_easy_setopt CURLOPT_WRITEFUNCTION failed");
return false;
}
if(curl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, writeHeaderCallback)!=CURLE_OK)
{
LOGE("curl_easy_setopt CURLOPT_HEADERFUNCTION failed");
return false;
}
if(curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *) chunkP)!=CURLE_OK)
{
LOGE("curl_easy_setopt CURLOPT_WRITEDATA failed");
return false;
}
if(curl_easy_setopt(curl_handle, CURLOPT_WRITEHEADER, (void *) headerChunkP)!=CURLE_OK)
{
LOGE("curl_easy_setopt CURLOPT_WRITEHEADER failed");
return false;
}
if(curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headerListP)!=CURLE_OK)
{
LOGE("curl_easy_setopt CURLOPT_HTTPHEADER failed");
return false;
}
/* some servers don't like requests that are made without a user-agent
field, so we provide one */
if(curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "rpa/1.0")!=CURLE_OK)
{
LOGE("curl_easy_setopt CURLOPT_USERAGENT failed");
return false;
}
if(curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1L)!=CURLE_OK)
{
LOGE("curl_easy_setopt CURLOPT_USERAGENT failed");
return false;
}
saveCertFile(certificateFilePath_, CA_CERTIFICATES);
LOGD("curl_easy_setopt CURLOPT_CAINFO %s", certificateFilePath_);
if(curl_easy_setopt(curl_handle, CURLOPT_CAINFO, certificateFilePath_)!=CURLE_OK)
{
LOGE("curl_easy_setopt CURLOPT_CAINFO failed");
return false;
}
LOGD("curl_easy_setopt CURLOPT_CAPATH %s", certificatePath_);
if(curl_easy_setopt(curl_handle, CURLOPT_CAPATH, certificatePath_)!=CURLE_OK)
{
LOGE("curl_easy_setopt CURLOPT_CAPATH failed");
return false;
}
long int se_connection_timeout=120L; // timeout after 120 seconds
#ifdef __DEBUG
curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L);
curl_easy_setopt(curl_handle, CURLOPT_DEBUGFUNCTION, debug_function);
if(strncmp(linkP, NONEXISTENT_TEST_URL, shorter(strlen(NONEXISTENT_TEST_URL), strlen(linkP)))==0)
{
se_connection_timeout=3L; // reducing the connection timeout for testing purposes
MAX_ATTEMPTS=1; // this is for testint code, we are using nonexitent url here so no unncessary attempts
LOGD("setBasicOpt timeout set to %ld", se_connection_timeout);
}
#endif
if(curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT, se_connection_timeout)!=CURLE_OK)
{
LOGE("curl_easy_setopt CURLOPT_TIMEOUT failed");
return false;
}
/** libcurl uses the http_proxy and https_proxy environment variables for proxy settings.
That variable is set in the OS specific wrapper. These are left here in order to make
this comment earier to be found in searches.
curl_easy_setopt(curl_handle,CURLOPT_PROXY, "http://proxyaddress");
curl_easy_setopt(curl_handle,CURLOPT_PROXYPORT, "read_proxy_port");
curl_easy_setopt(curl_handle,CURLOPT_PROXYUSERNAME, "read_proxy_username");
curl_easy_setopt(curl_handle,CURLOPT_PROXYPASSWORD, "read_proxy_password");
*/
return true;
}
bool setPutOpt(CURL* curl_handle, ResponseStruct* responseChunk)
{
LOGD(">>setPutOpt");
if (curl_easy_setopt(curl_handle, CURLOPT_READFUNCTION, readResponseCallback)!=CURLE_OK)
{
LOGE("curl_easy_setopt CURLOPT_READFUNCTION failed");
return false;
}
if (curl_easy_setopt(curl_handle, CURLOPT_UPLOAD, 1L)!=CURLE_OK)
{
LOGE("curl_easy_setopt CURLOPT_UPLOAD failed");
return false;
}
if (curl_easy_setopt(curl_handle, CURLOPT_PUT, 1L)!=CURLE_OK)
{
LOGE("curl_easy_setopt CURLOPT_PUT failed");
return false;
}
if (curl_easy_setopt(curl_handle, CURLOPT_READDATA, responseChunk)!=CURLE_OK)
{
LOGE("curl_easy_setopt CURLOPT_READDATA failed");
return false;
}
long s=responseChunk->size;
if (curl_easy_setopt(curl_handle, CURLOPT_INFILESIZE, s)!=CURLE_OK)
{
LOGE("curl_easy_setopt CURLOPT_INFILESIZE_LARGE failed");
return false;
}
LOGD("<<setPutOpt");
return true;
}
bool setPostOpt(CURL* curl_handle, const char* inputP)
{
LOGD(">>setPostOpt %ld %d", (long int) inputP, inputP?(int)strlen(inputP):0);
if (curl_easy_setopt(curl_handle, CURLOPT_POST, 1L)!=CURLE_OK)
{
LOGE("curl_easy_setopt CURLOPT_POST failed");
return false;
}
if(NULL==inputP)
{
if (curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDSIZE, 0L)!=CURLE_OK)
{
LOGE("curl_easy_setopt CURLOPT_POSTFIELDSIZE failed");
return false;
}
}
if (curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, (void*) inputP)!=CURLE_OK)
{
LOGE("curl_easy_setopt CURLOPT_POSTFIELDS failed");
return false;
}
LOGD("<<setPostOpt");
return true;
}
bool setDeleteOpt(CURL* curl_handle, const char* inputP)
{
LOGD(">>setDeleteOpt %s", inputP);
if (curl_easy_setopt(curl_handle, CURLOPT_CUSTOMREQUEST, "DELETE")!=CURLE_OK)
{
LOGE("curl_easy_setopt CURLOPT_CUSTOMREQUEST failed");
return false;
}
LOGD("<<setDeleteOpt");
return true;
}
CURL* curl_handle_=NULL;
rootpaerror_t openSeClientAndInit()
{
if(curl_global_init(CURL_GLOBAL_ALL)!=CURLE_OK)
{
LOGE("curl_gloabal_init failed");
return ROOTPA_ERROR_NETWORK;
}
curl_handle_=curl_easy_init();
if(NULL==curl_handle_)
{
LOGE("initialize failed");
return ROOTPA_ERROR_NETWORK;
}
return ROOTPA_OK;
}
void closeSeClientAndCleanup()
{
if(curl_handle_)
{
curl_easy_cleanup(curl_handle_);
curl_handle_=NULL;
}
curl_global_cleanup();
}
rootpaerror_t httpCommunicate(const char * const inputP, const char** linkP, const char** relP, const char** commandP, httpMethod_t method)
{
rootpaerror_t ret=ROOTPA_OK;
long int curlRet=CURLE_COULDNT_CONNECT;
long int http_code = 0;
int attempts=0;
struct curl_slist* httpHeaderP = NULL;
LOGD(">>httpCommunicate");
if(NULL==linkP || NULL==relP || NULL==commandP || NULL==*linkP)
{
return ROOTPA_ERROR_ILLEGAL_ARGUMENT;
}
LOGD("url %s", *linkP);
*commandP=NULL;
*relP=NULL;
ResponseStruct responseChunk;
HeaderStruct headerChunk;
headerChunk.linkSize = 0;
headerChunk.relSize = 0;
headerChunk.linkP = NULL;
headerChunk.relP = NULL;
MemoryStruct chunk;
chunk.size = 0; /* no data at this point */
chunk.memoryP = malloc(1); /* will be grown as needed by the realloc above */
if(NULL==chunk.memoryP)
{
return ROOTPA_ERROR_OUT_OF_MEMORY;
}
chunk.memoryP[0]=0;
LOGD("HTTP method %d", method);
//Process HTTP methods
if(method == httpMethod_PUT)
{
responseChunk.responseP=inputP;
responseChunk.size=strlen(responseChunk.responseP);
responseChunk.offset=0;
if(setPutOpt(curl_handle_, &responseChunk)==false)
{
LOGE("setPutOpt failed");
free(chunk.memoryP);
return ROOTPA_ERROR_NETWORK;
}
}
else if(method == httpMethod_POST)
{
if (setPostOpt(curl_handle_, inputP)==false)
{
LOGE("setPostOpt failed");
free(chunk.memoryP);
return ROOTPA_ERROR_NETWORK;
}
}
else if(method == httpMethod_DELETE)
{
LOGD("DELETE method. Calling setDeleteOpt..");
if (setDeleteOpt(curl_handle_, inputP)==false)
{
LOGE("setDeleteOpt failed");
free(chunk.memoryP);
return ROOTPA_ERROR_NETWORK;
}
}
else
{
if(method != httpMethod_GET)
{
LOGE("Unsupported HTTP method");
free(chunk.memoryP);
return ROOTPA_ERROR_NETWORK;
}
}
/* disable Expect: 100-continue since it creates problems with some proxies, it is only related to post but we do it here for simplicity */
httpHeaderP = curl_slist_append(httpHeaderP, "Expect:");
httpHeaderP = curl_slist_append(httpHeaderP, "Content-Type: application/vnd.mcorecm+xml;v=1.0");
httpHeaderP = curl_slist_append(httpHeaderP, "Accept: application/vnd.mcorecm+xml;v=1.0");
if(setBasicOpt(curl_handle_, &chunk, &headerChunk, *linkP, httpHeaderP)==false)
{
LOGE("setBasicOpt failed");
free(chunk.memoryP);
return ROOTPA_ERROR_NETWORK;
}
while(curlRet!=CURLE_OK && attempts++ < MAX_ATTEMPTS)
{
curlRet=curl_easy_perform(curl_handle_);
LOGD("curl_easy_perform %ld %d", curlRet, attempts);
if(CURLE_OK==curlRet) break;
nanosleep(&SLEEPTIME,NULL);
}
curl_easy_getinfo (curl_handle_, CURLINFO_RESPONSE_CODE, &http_code);
if(curlRet!=CURLE_OK)
{
LOGE("curl_easy_perform failed %ld", curlRet);
free(chunk.memoryP);
free(headerChunk.linkP);
free(headerChunk.relP);
curl_easy_reset(curl_handle_);
return ROOTPA_ERROR_NETWORK;
}
LOGD("http return code from SE %ld", (long int) http_code);
if ((200 <= http_code && http_code < 300))
{
ret=ROOTPA_OK;
}
else if (HTTP_CODE_BAD_REQUEST == http_code ||
HTTP_CODE_METHOD_NOT_ALLOWED == http_code ||
HTTP_CODE_NOT_ACCEPTABLE == http_code ||
HTTP_CODE_CONFLICT == http_code ||
HTTP_CODE_LENGTH_REQUIRED == http_code ||
HTTP_CODE_TOO_LONG == http_code ||
HTTP_CODE_UNSUPPORTED_MEDIA == http_code ||
HTTP_CODE_INVALID_DATA == http_code ||
HTTP_CODE_INTERNAL_ERROR == http_code ||
HTTP_CODE_HTTP_VERSION == http_code)
{
ret=ROOTPA_ERROR_INTERNAL;
}
else if(HTTP_CODE_MOVED == http_code || // new URL would be in Location: header but RootPA does not support in currently (unless libcurl supports it transparently)
HTTP_CODE_REQUEST_TIMEOUT == http_code ||
HTTP_CODE_SERVICE_UNAVAILABLE == http_code)
{
ret=ROOTPA_ERROR_NETWORK;
}
else if (HTTP_CODE_CMP_VERSION == http_code)
{
ret=ROOTPA_ERROR_SE_CMP_VERSION;
}
else if (HTTP_CODE_FAILED_DEPENDENCY == http_code)
{
ret=ROOTPA_ERROR_SE_PRECONDITION_NOT_MET;
}
else if (HTTP_CODE_NOT_FOUND == http_code)
{
ret=ROOTPA_ERROR_ILLEGAL_ARGUMENT; // since the arguments (spid, in some cases uuid) for the URL are received from the client,
// this can be returned. It is also possible that suid is wrong (corrupted in device or info
// from device binding missing from SE, but we can not detect that easily.
}
else
{
LOGE("unexpected http return code from SE %ld", (long int)http_code);
ret=ROOTPA_ERROR_NETWORK;
}
/* cleanup curl stuff */
*commandP=chunk.memoryP; // this needs to be freed by client
*linkP=headerChunk.linkP; // this needs to be freed by client
*relP=headerChunk.relP; // this needs to be freed by client
if (httpHeaderP) curl_slist_free_all(httpHeaderP); // since we disabled some headers
curl_easy_reset(curl_handle_);
LOGD("%lu bytes retrieved\n", (long)chunk.size);
LOGD("<<httpCommunicate %d %ld %ld", (int) ret, (long int) http_code, (long int) curlRet);
return ret;
}