/** * Open a socket connection to the proxy server * @param serverName Name of the server, i.e. "localhost" * @param port Port number to connect with, i.e. DEFAULT_PROXY_PORT */ error_t clientsocket_open(const char *serverName, int port) { struct sockaddr_in serverAddress; struct hostent *server; assert(serverName); SYSLOG_INFO("Attempting to open socket to %s on port %d", serverName, port); gTerminate = false; socketFd = socket(AF_INET, SOCK_STREAM, 0); if ((socketFd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { SYSLOG_ERR("ERROR opening socket"); return -1; } if ((server = gethostbyname(serverName)) == NULL) { SYSLOG_ERR("ERROR, no such host\n"); return -1; } bzero((char *) &serverAddress, sizeof(serverAddress)); serverAddress.sin_family = AF_INET; memcpy((char *) server->h_addr, (char *) &serverAddress.sin_addr.s_addr, server->h_length); serverAddress.sin_port = htons(port); SYSLOG_INFO("Connecting..."); if (connect(socketFd, (struct sockaddr *) &serverAddress, sizeof(serverAddress)) < 0) { SYSLOG_ERR("ERROR connecting"); return FAIL; } SYSLOG_INFO("Connection established on fd %d", socketFd); // Initialize the thread pthread_attr_init(&sThreadAttr); // Will run detached from the main dispatcher thread pthread_attr_setdetachstate(&sThreadAttr, PTHREAD_CREATE_DETACHED); // Round robin schedule is fine when processing is extremely low pthread_attr_setschedpolicy(&sThreadAttr, SCHED_RR); // Create the thread if (pthread_create(&sThreadId, &sThreadAttr, &_clientCommThread, NULL)) { SYSLOG_ERR("Creating proxy thread failed: %s", strerror(errno)); clientsocket_close(); return FAIL; } return SUCCESS; }
/** * @brief Called when creating a curl data structure that will be shared across different * http connections. DNS caching data is such data structure. * * @param curlShHandle: pointer to a CURLSH structure that will be instantiated by curl_share_init * * @return none **/ void libhttpcomm_curlShareInit(CURLSH *curlShHandle) { curlShHandle = curl_share_init(); if (curlShHandle != NULL) { if(curl_share_setopt(curlShHandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS) != CURLSHE_OK) { SYSLOG_ERR("curl_easy_setopt CURLSHOPT_SHARE"); } }else { SYSLOG_ERR("curl_share_init"); } }
/** * Parse the characters found as the value of an XML tag */ static void _login_xml_charactersHandler(void *ctx, const xmlChar *ch, int len) { int i; char output[API_KEY_LENGTH]; login_info_t *loginInfo = (login_info_t *) ctx; if (len > 0 && len < API_KEY_LENGTH) { for (i = 0; i < len; i++) { //if not equal a LF, CR store the character if ((ch[i] != 10) && (ch[i] != 13)) { output[i] = ch[i]; } } output[i] = '\0'; if (strcmp(loginInfo->xmlTag, "keyExpire") == 0) { // Tag is ok, but do nothing } else if (strcmp(loginInfo->xmlTag, "resultCode") == 0) { loginInfo->resultCode = atoi(output); } else if (strcmp(loginInfo->xmlTag, "key") == 0) { snprintf(apiKey, sizeof(apiKey), "%s", output); libconfigio_write(proxycli_getConfigFilename(), CONFIGIO_API_KEY, apiKey); } else { SYSLOG_DEBUG("Login does not support XML tag %s", loginInfo->xmlTag); } } else { SYSLOG_ERR("Received an XML value that is too long to parse"); } }
/**********************************************************************************************//** * @brief Called when a message has to be received from the server. this is a standard streamer * if the size of the data to read, equal to size*nmemb, the function can return * what was read and the function will be called again by libcurl. * * @param ptr: where the received message resides * @param size: size*nmemb == number of bytes to read * @param nmemb: size*nmemb == number of bytes to read * @param userp: ptr to where the message will be written -> inputted by CURLOPT_WRITEDATA call below * * @return number of bytes that were written ***************************************************************************************************/ static size_t writer(void *ptr, size_t size, size_t nmemb, void *userp) { struct HttpIoInfo *dataToRead = (struct HttpIoInfo *) userp; char *data = (char *)ptr; if (dataToRead == NULL || dataToRead->buffer == NULL) { SYSLOG_ERR ("dataToRead == NULL"); return 0; } // keeping one byte for the null byte if((strlen(dataToRead->buffer)+(size * nmemb)) > (dataToRead->size - 1)) { SYSLOG_WARNING ("buffer overflow would result -> strlen(writeData): %u, (size * nmemb): %u, max size: %u", strlen(dataToRead->buffer), (size * nmemb), dataToRead->size); return 0; } else { } strncat(dataToRead->buffer, data, (size * nmemb)); return (size * nmemb); }
/** * Parse the characters found as the value of an XML tag */ static void _getactivationinfo_xml_charactersHandler(void *ctx, const xmlChar *ch, int len) { int i; char output[ACTIVATION_KEY_LENGTH]; getactivationinfo_info_t *getActivationInfo = (getactivationinfo_info_t *) ctx; if (len > 0 && len < ACTIVATION_KEY_LENGTH) { for (i = 0; i < len; i++) { //if not equal a LF, CR store the character if ((ch[i] != 10) && (ch[i] != 13)) { output[i] = ch[i]; } } output[i] = '\0'; if (strcmp(getActivationInfo->xmlTag, "resultCode") == 0) { getActivationInfo->resultCode = atoi(output); } else if (strcmp(getActivationInfo->xmlTag, "deviceActivationKey") == 0) { snprintf(activationKey, sizeof(activationKey), "%s", output); } else { SYSLOG_DEBUG("Activation does not support XML tag %s", getActivationInfo->xmlTag); } } else { SYSLOG_ERR("Received an XML value that is too long to parse"); } }
/** * Obtain the 48-bit MAC dest and convert to an EUI-64 value from the * hardware NIC * * @param dest Buffer of at least 8 bytes * @param destLen Length of the buffer * @return SUCCESS if we are able to capture the EUI64 */ error_t eui64_toBytes(uint8_t *dest, int destLen) { struct ifreq *ifr; struct ifconf ifc; char buf[1024]; int sock, i; int ok = 0; assert(dest); if(destLen < EUI64_BYTES_SIZE) { return FAIL; } memset(dest, 0x0, destLen); sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock == -1) { return -1; } ifc.ifc_len = sizeof(buf); ifc.ifc_buf = buf; ioctl(sock, SIOCGIFCONF, &ifc); ifr = ifc.ifc_req; for (i = 0; i < ifc.ifc_len / sizeof(struct ifreq); ifr++) { if (strcmp(ifr->ifr_name, "eth0") == 0 || strcmp(ifr->ifr_name, "eth1") == 0 || strcmp(ifr->ifr_name, "wlan0") == 0 || strcmp(ifr->ifr_name, "br0") == 0) { if (ioctl(sock, SIOCGIFFLAGS, ifr) == 0) { if (!(ifr->ifr_flags & IFF_LOOPBACK)) { if (ioctl(sock, SIOCGIFHWADDR, ifr) == 0) { ok = 1; break; } } } } } close(sock); if (ok) { /* Convert 48 bit MAC dest to EUI-64 */ memcpy(dest, ifr->ifr_hwaddr.sa_data, 6); /* Insert the converting bits in the middle */ /* dest[3] = 0xFF; dest[4] = 0xFE; memcpy(&dest[5], &(ifr->ifr_hwaddr.sa_data[3]), 3); */ } else { SYSLOG_ERR("Couldn't read MAC dest to seed EUI64"); return FAIL; } return SUCCESS; }
/** * Send a message to the proxy * @param message Message to send * @param len Length of the message to send */ error_t clientsocket_send(const char *message, int len) { assert(message); if (write(socketFd, message, len) < 0) { SYSLOG_ERR("ERROR writing to socket"); return FAIL; } return SUCCESS; }
/** * @brief Called when a message has to be sent to the server. this is a standard streamer * if the size of the data to write is larger than size*nmemb, this function * will be called several times by libcurl. * * @param ptr: where data has to be written * @param size: size*nmemb == maximum number of bytes that can be written each time * @param nmemb: size*nmemb == maximum number of bytes that can be written each time * @param userp: ptr to message to write -> inputted by CURLOPT_READDATA call below * * @return number of bytes that were written **/ static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userp) { struct HttpIoInfo *dataToWrite = (struct HttpIoInfo *) userp; int dataWritten = 0; if (dataToWrite == NULL || dataToWrite->buffer == NULL) { SYSLOG_ERR ("dataToWrite == NULL"); return 0; } if (size * nmemb < 1) { SYSLOG_ERR("size * nmemb < 1"); return 0; } if (dataToWrite->size > 0) { if (dataToWrite->size > (size * nmemb)) { dataWritten = size * nmemb; SYSLOG_DEBUG("dataToWrite->size = %u is larger than size * nmemb = %u", dataToWrite->size, size * nmemb); } else { dataWritten = dataToWrite->size; } memcpy (ptr, dataToWrite->buffer, dataWritten); dataToWrite->buffer += dataWritten; dataToWrite->size -= dataWritten; return dataWritten; /* we return 1 byte at a time! */ } else { return 0; } return 0; }
/** * Parse the characters found as the value of an XML tag */ static void _registrationinfo_xml_charactersHandler(void *ctx, const xmlChar *ch, int len) { int i; char output[ACTIVATION_KEY_LENGTH]; registrationinfo_info_t *registrationInfo = (registrationinfo_info_t *) ctx; if (len > 0 && len < ACTIVATION_KEY_LENGTH) { for (i = 0; i < len; i++) { //if not equal a LF, CR store the character if ((ch[i] != 10) && (ch[i] != 13)) { output[i] = ch[i]; } } output[i] = '\0'; if (strcmp(registrationInfo->xmlTag, "resultCode") == 0) { registrationInfo->resultCode = atoi(output); } else if (strcmp(registrationInfo->xmlTag, "host") == 0) { libconfigio_write(proxycli_getConfigFilename(), CONFIGIO_CLOUD_HOST, output); } else if (strcmp(registrationInfo->xmlTag, "port") == 0) { libconfigio_write(proxycli_getConfigFilename(), CONFIGIO_CLOUD_PORT, output); } else if (strcmp(registrationInfo->xmlTag, "uri") == 0) { libconfigio_write(proxycli_getConfigFilename(), CONFIGIO_CLOUD_URI, output); } else if (strcmp(registrationInfo->xmlTag, "useSSL") == 0) { libconfigio_write(proxycli_getConfigFilename(), CONFIGIO_CLOUD_USE_SSL, output); } else { SYSLOG_DEBUG("Registration does not support XML tag %s", registrationInfo->xmlTag); } } else { SYSLOG_ERR("Received an XML value that is too long to parse"); } }
/** * @brief Generic function to write token into a file in the form (token=value) * * @param fileName: file name for which token will be updated * @param token: Token in file to update: example (ESP_HOST_NAME) * @param value: value of token to update (see definition of max buffer sizes in libconfigio.h) * * @return SUCCESS or FAIL */ error_t libconfigio_write(const char* fileName, const char* token, const char* value) { error_t retVal = SUCCESS; FILE *configFd = NULL; FILE *tmpConfigFd = NULL; // of back up file char line[LINE_MAX]; long filePos = -1; ///where the current value is located long eofPos = -1; ///where the end of the file is char currentValue[LINE_MAX]; char tmpFileName[256]; ///back up file name when copying the file assert(fileName); assert(token); assert(value); memset(line, 0, sizeof(line)); umask(022); // setting permissions to be able to write, open files // note that the config file or a link to it must be in /opt/etc, the source file is in // hub/etc/ if (access(fileName, F_OK) != 0) { SYSLOG_DEBUG("file %s does not exist -> will be created", fileName); }else if (access(fileName, W_OK) != 0) { SYSLOG_DEBUG("no write permission for file %s", fileName); return FAIL; }else { // see if the token already exists -> if so read it and gets its line position in the file. filePos = libconfigio_read (fileName, token, currentValue, sizeof(currentValue)); if(strcmp (value, currentValue) == 0) { // found value and it is the same -> no need to write the flash -> we're done retVal = SUCCESS; goto out; } } if (filePos != -1) //if found the value { //used for backing up of data when rewriting values -> temporary file snprintf(tmpFileName, sizeof(tmpFileName), "%s.tmp", fileName); // TODO: use temp file tmpnam() from Linux tmpConfigFd = fopen(tmpFileName, "w+"); if (tmpConfigFd == NULL) { SYSLOG_ERR("%s -> could not open %s for reading and writing", strerror(errno), tmpFileName); retVal = FAIL; goto out; } // file that we will update configFd = fopen(fileName, "r+"); if (configFd == NULL) { SYSLOG_ERR("%s -> could not open %s for reading and writing", strerror(errno), fileName); retVal = FAIL; goto out; } //find which that existing value is fseek(configFd, filePos, SEEK_SET); //get the first line and then dump it. if (fgets(line, sizeof(line), configFd) == NULL) { SYSLOG_ERR("fgets of first line failed"); } // now we need to cpy the following lines in the temporary file while (!feof (configFd)) { if(fgets(line, sizeof(line), configFd) == NULL) { break; } else if(fputs(line, tmpConfigFd) == EOF) { SYSLOG_ERR("writing tmp file %s, %s", tmpFileName, strerror(errno)); } } //go back to where that line is fseek(configFd, filePos, SEEK_SET); // write the new value fprintf(configFd, "%s=%s\n", token,value); // then recopy the tmp file where we are now //go back to where that line is fseek(tmpConfigFd, 0, SEEK_SET); // now we need to cpy the following lines in the temporary file while (!feof (tmpConfigFd)) { if(fgets(line, sizeof(line), tmpConfigFd) == NULL) { break; } else { if(fputs(line, configFd) == EOF) { SYSLOG_ERR("writing cur file %s, %s", fileName, strerror(errno)); } } } //update so that we know the file size eofPos = ftell(configFd); } else { //create - write token + value in file if either value or file does not exist //find where that existing value is if(configFd != NULL) { fclose(configFd); } configFd = fopen(fileName, "a+"); if (configFd == NULL) { SYSLOG_ERR("%s -> could not open %s for appending", strerror(errno), fileName); retVal = FAIL; goto out; } fprintf(configFd, "%s=%s\n", token,value); } out: if (configFd != NULL) { if (eofPos > 0) { //eliminate junk at the end. if(ftruncate(fileno(configFd), eofPos) < 0) { SYSLOG_ERR("ftruncate failed: %s", strerror(errno)); } } fflush(configFd); fclose(configFd); } if (tmpConfigFd != NULL) { fclose(tmpConfigFd); remove(tmpFileName); } return retVal; }
/** * @brief Generic function to read token from a file in the form (token=value) * * @param fileName: file name for which token will be updated * @param token: Token in file to read: example (ESP_HOST_NAME) * @param value: ptr to which token will be written (see definition of max buffer sizes in libconfigio.h) * @param valueSize: size of buffer pointed by value * * @return offset in file of the line where the token reside in the file. -1 if not present or * file does not exist **/ long libconfigio_read(const char* fileName, const char* token, char* value, int valueSize) { long retVal = -1; FILE *configFd = NULL; char line[LINE_MAX]; char *eofStatus; char *tmpString; int index = 0; assert(fileName); assert(token); assert(value); memset(line,0, sizeof(line)); umask(022); // setting permissions to be able to read, write, open files if (access(fileName, F_OK) != 0) { SYSLOG_ERR("file %s does not exist", fileName); retVal = -1; goto out; }else if (access(fileName, R_OK) != 0) { SYSLOG_ERR("no read permission for file %s", fileName); retVal = -1; goto out; }else { //file exists } configFd = fopen(fileName, "r"); if (configFd == NULL) { SYSLOG_ERR("%s -> could not open %s for reading", strerror(errno), fileName); retVal = -1; goto out; } // read file and see if token is already there while (feof(configFd) == 0) { eofStatus = fgets(line, sizeof(line), configFd); if( eofStatus == NULL ) //another indication of EOF { break; } tmpString = strstr(line, token); if(tmpString != NULL) { tmpString = strstr(tmpString, "="); if (tmpString == NULL) { retVal = -1; goto out; } tmpString++; while (isspace(*tmpString)) // skip leading spaces and tabs { tmpString++; } //copy value in string // stop when you get a control character.. new line, new feed index = 0; while (iscntrl(*tmpString) == 0 && index < valueSize) { value[index] = *tmpString; tmpString++; index++; } value[index] = '\0'; retVal = ftell(configFd) - strlen(line); //get position in the file break; } } out: if (configFd != NULL) { fclose(configFd); } return retVal; }
/** * @brief Sends a file through HTTP to a remote computer * * @param url: url of the server (hostname + uri) * @param sslCertPath: location of where the certificate is * @param authToken: authentication token to be added in the header * @param fileName: ptr to message to send. NULL if none * @param rxBuffer: ptr for storing message received by the server -> must exist * @param maxRxBufferSize: max size of rxBuffer in bytes. * @param timeouts: specifies connect and transfer timeouts for the connection * * @return true for success, false for failure */ int libhttpcomm_sendFile(const char *url, const char *sslCertPath, const char *authToken, char *fileName, char *rxBuffer, int maxRxBufferSize, http_timeout_t timeouts) { CURL * curlHandle; CURLcode curlResult; FILE *file = NULL; char tempString[PATH_MAX]; char errorBuffer[CURL_ERROR_SIZE]; int fileSize = 0; struct HttpIoInfo inBoundCommInfo; struct curl_slist *slist = NULL; struct stat fileStats; double connectDuration = 0.0; double transferDuration = 0.0; double nameResolvingDuration = 0.0; long httpResponseCode = 0; long httpConnectCode = 0; long curlErrno = 0; bool_t retVal = true; assert (url); assert (fileName); curlHandle = curl_easy_init(); if (curlHandle) { //creating the curl object // TODO: not sure that setting a pointer to a pointer that is scoped elsewhere is correct. file = fopen(fileName, "r"); if (file == NULL) { SYSLOG_ERR("%s opening %s", strerror(errno), fileName); retVal = false; goto out; } if (stat (fileName, &fileStats) >= 0) { fileSize = (int)fileStats.st_size; }else { SYSLOG_ERR("stats returned %s", strerror(errno)); } slist = curl_slist_append(slist, "Content-Type: application/octet-stream"); snprintf(tempString, sizeof(tempString), "Content-Length: %d", fileSize); slist = curl_slist_append(slist, tempString); SYSLOG_DEBUG("fileName: %s, url: %s, fileSize = %d", fileName, url, fileSize); if (_libhttpcomm_configureHttp(curlHandle, NULL, slist, CURLOPT_POST, url, sslCertPath, authToken, timeouts, NULL) == false) { retVal = false; goto out; } curlResult = curl_easy_setopt(curlHandle, CURLOPT_ERRORBUFFER, errorBuffer); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_ERRORBUFFER", curl_easy_strerror(curlResult)); retVal = false; goto out; } /* pointer to pass to our read function */ // here you must put the file info curlResult = curl_easy_setopt(curlHandle, CURLOPT_READDATA, file); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_READDATA", curl_easy_strerror(curlResult)); retVal = false; goto out; } // sets maximum size of our internal buffer curlResult = curl_easy_setopt(curlHandle, CURLOPT_BUFFERSIZE, maxRxBufferSize); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_BUFFERSIZE", curl_easy_strerror(curlResult)); retVal = false; goto out; } // CURLOPT_WRITEFUNCTION and CURLOPT_WRITEDATA in this context refers to // data received from the server... so curl will write data to us. curlResult = curl_easy_setopt(curlHandle, CURLOPT_WRITEFUNCTION, writer); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_WRITEFUNCTION", curl_easy_strerror(curlResult)); retVal = false; goto out; } inBoundCommInfo.buffer = rxBuffer; inBoundCommInfo.size = maxRxBufferSize; curlResult = curl_easy_setopt(curlHandle, CURLOPT_WRITEDATA, &inBoundCommInfo); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_WRITEDATA", curl_easy_strerror(curlResult)); retVal = false; goto out; } curlResult = curl_easy_perform(curlHandle); curl_easy_getinfo(curlHandle, CURLINFO_APPCONNECT_TIME, &connectDuration ); curl_easy_getinfo(curlHandle, CURLINFO_NAMELOOKUP_TIME, &nameResolvingDuration ); curl_easy_getinfo(curlHandle, CURLINFO_TOTAL_TIME, &transferDuration ); curl_easy_getinfo(curlHandle, CURLINFO_RESPONSE_CODE, &httpResponseCode ); curl_easy_getinfo(curlHandle, CURLINFO_HTTP_CONNECTCODE, &httpConnectCode ); if (nameResolvingDuration >= 2.0) { SYSLOG_WARNING("connectDuration=%.2lf, nameResolvingDuration=%.2lf, transferDuration=%.2lf, " "httpConnectCode=%ld", connectDuration, nameResolvingDuration, transferDuration, httpConnectCode); } else { SYSLOG_DEBUG("connectDuration=%.2lf, nameResolvingDuration=%.2lf, transferDuration=%.2lf, " "httpConnectCode=%ld", connectDuration, nameResolvingDuration, transferDuration, httpConnectCode); } if (httpResponseCode >= 300 || httpConnectCode >= 300) { SYSLOG_ERR("HTTP error response code: %ld, connect code: %ld", httpResponseCode, httpConnectCode); retVal = false; goto out; } if (curlResult != CURLE_OK) { if (curlResult != CURLE_ABORTED_BY_CALLBACK) { if (curl_easy_getinfo(curlHandle, CURLINFO_OS_ERRNO, &curlErrno) != CURLE_OK) { curlErrno = 0; SYSLOG_ERR("curl_easy_getinfo"); } SYSLOG_ERR("curl_easy_perform: %s, %s for url %s", curl_easy_strerror(curlResult), strerror((int)curlErrno), url); }else { SYSLOG_DEBUG("quitting curl transfer"); } retVal = false; goto out; } // the following is a special case - a time-out from the server is going to return a // string with 1 character in it ... if (strlen(rxBuffer) > 1) { /* put the result into the main buffer and return */ SYSLOG_DEBUG("received msg length %d", strlen(rxBuffer)); }else { if (strlen(rxBuffer) == 1) { SYSLOG_DEBUG("received time-out message from the server"); } rxBuffer[0] = '\0'; goto out; } } else { SYSLOG_ERR("curl_easy_init failed"); retVal = false; } out: fclose(file); _libhttpcomm_closeHttp(curlHandle, slist); return retVal; }
/** * @brief Get a file through HTTP from PPC servers * * @param shareCurlHandle: Curl handle shared across connections * @param url: url of the server (hostname + uri) * @param sslCertPath: location of where the certificate is * @param authToken: authentication token to be added in the header * @param rxFile: FILE ptr to the incoming file * @param maxRxFileSize: max size of the file to receive * @param timeouts: specifies connect and transfer timeouts for the connection * @param ProgressCallback: function pointer that will be called every second during the connection * * @return true for success, false for failure */ int libhttpcomm_getFile(CURLSH * shareCurlHandle, const char *url, const char *sslCertPath, const char *authToken, FILE *rxFile, int maxRxFileSize, http_timeout_t timeouts, int (*ProgressCallback) (void *clientp, double dltotal, double dlnow, double ultotal, double ulnow)) { CURL * curlHandle; CURLcode curlResult; char errorBuffer[CURL_ERROR_SIZE]; struct curl_slist *slist = NULL; double connectDuration = 0.0; double transferDuration = 0.0; double nameResolvingDuration = 0.0; long curlErrno = 0; long httpResponseCode = 0; long httpConnectCode = 0; bool_t retVal = true; assert (rxFile); assert (url); SYSLOG_DEBUG("url: %s", url); curlHandle = curl_easy_init(); if (curlHandle) { if (_libhttpcomm_configureHttp(curlHandle, shareCurlHandle, slist, CURLOPT_HTTPGET, url, sslCertPath, authToken, timeouts, ProgressCallback) == false) { retVal = false; goto out; } curlResult = curl_easy_setopt(curlHandle, CURLOPT_ERRORBUFFER, errorBuffer); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_ERRORBUFFER", curl_easy_strerror(curlResult)); retVal = false; goto out; } // sets maximum size of our internal buffer curlResult = curl_easy_setopt(curlHandle, CURLOPT_BUFFERSIZE, maxRxFileSize); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_BUFFERSIZE", curl_easy_strerror(curlResult)); retVal = false; goto out; } curlResult = curl_easy_setopt(curlHandle, CURLOPT_WRITEDATA, rxFile); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_WRITEDATA", curl_easy_strerror(curlResult)); retVal = false; goto out; } curlResult = curl_easy_perform(curlHandle); curl_easy_getinfo(curlHandle, CURLINFO_APPCONNECT_TIME, &connectDuration ); curl_easy_getinfo(curlHandle, CURLINFO_NAMELOOKUP_TIME, &nameResolvingDuration ); curl_easy_getinfo(curlHandle, CURLINFO_TOTAL_TIME, &transferDuration ); curl_easy_getinfo(curlHandle, CURLINFO_RESPONSE_CODE, &httpResponseCode ); curl_easy_getinfo(curlHandle, CURLINFO_HTTP_CONNECTCODE, &httpConnectCode ); if (httpResponseCode >= 300 || httpConnectCode >= 300) { retVal = false; goto out; } if (nameResolvingDuration >= 2.0) { SYSLOG_WARNING("connectDuration=%.2lf, nameResolvingDuration=%.2lf, transferDuration=%.2lf", connectDuration, nameResolvingDuration, transferDuration); } else { SYSLOG_DEBUG("connectDuration=%.2lf, nameResolvingDuration=%.2lf, transferDuration=%.2lf", connectDuration, nameResolvingDuration, transferDuration); } if (curlResult != CURLE_OK) { if (curlResult != CURLE_ABORTED_BY_CALLBACK) { if (curl_easy_getinfo(curlHandle, CURLINFO_OS_ERRNO, &curlErrno) != CURLE_OK) { curlErrno = 0; SYSLOG_ERR("curl_easy_getinfo"); } SYSLOG_ERR("curl_easy_perform: %s, %s for url %s", curl_easy_strerror(curlResult), strerror((int)curlErrno), url); }else { SYSLOG_DEBUG("quitting curl transfer"); } retVal = false; goto out; } } else { SYSLOG_ERR("curl_easy_init failed"); retVal = false; } out: _libhttpcomm_closeHttp(curlHandle, slist); return retVal; }
/** * @brief Sends a message through HTTP to PPC servers * * @param shareCurlHandle: Curl handle shared across connections * @param httpMethod: CURLOPT_POST or CURLOPT_HTTPGET * @param url: url of the server (hostname + uri) * @param sslCertPath: location of where the certificate is * @param authToken: authentication token to be added in the header * @param msgToSendPtr: ptr to message to send. NULL if none * @param msgToSendSize: send of msgToSendPtr * @param rxBuffer: ptr for storing message received by the server -> must exist * @param maxRxBufferSize: max size of rxBuffer in bytes -> if 0 it is assumed that rxBuffer is of type FILE* * @param timeouts: specifies connect and transfer timeouts for the connection * @param ProgressCallback: function pointer that will be called every second during the connection * * @return true for success, false for failure */ int libhttpcomm_postMsg(CURLSH * shareCurlHandle, CURLoption httpMethod, const char *url, const char *sslCertPath, const char *authToken, char *msgToSendPtr, int msgToSendSize, char *rxBuffer, int maxRxBufferSize, http_param_t params, int (*ProgressCallback) (void *clientp, double dltotal, double dlnow, double ultotal, double ulnow)) { CURL * curlHandle = NULL; CURLcode curlResult; char tempString[PATH_MAX]; char errorBuffer[CURL_ERROR_SIZE]; struct HttpIoInfo outBoundCommInfo; struct HttpIoInfo inBoundCommInfo; struct curl_slist *slist = NULL; double connectDuration = 0.0; double transferDuration = 0.0; double nameResolvingDuration = 0.0; long httpResponseCode = 0; long httpConnectCode = 0; long curlErrno = 0; assert (rxBuffer); assert(url); if (params.verbose == true) { if (msgToSendPtr != NULL) { SYSLOG_DEBUG("httpMethod: 0x%x, url: %s, size: %d, outgoing msg: %s", httpMethod, url, msgToSendSize, msgToSendPtr); }else { SYSLOG_DEBUG("httpMethod: 0x%x, url: %s", httpMethod, url); } } rxBuffer[0] = 0; curlHandle = curl_easy_init(); if ( params.key != NULL ) { slist = curl_slist_append(slist, params.key); slist = curl_slist_append(slist, "Content-Type:"); } if (curlHandle) { if ( httpMethod == CURLOPT_POST ) { if ( msgToSendSize > 0 ) { slist = curl_slist_append(slist, "Content-Type: text/xml"); snprintf(tempString, sizeof(tempString), "Content-Length: %d", msgToSendSize); slist = curl_slist_append(slist, tempString); } else if ( msgToSendSize == 0 ) { snprintf(tempString, sizeof(tempString), "Content-Length: %d", msgToSendSize); slist = curl_slist_append(slist, tempString); } } else if ( httpMethod == CURLOPT_HTTPGET ) { if ( params.password != NULL ) { SYSLOG_ERR("password: %s", params.password); slist = curl_slist_append(slist, params.password); } if ( params.key != NULL ) { SYSLOG_ERR("key: %s", params.key); slist = curl_slist_append(slist, params.key); } } if (_libhttpcomm_configureHttp(curlHandle, shareCurlHandle, slist, httpMethod, url, sslCertPath, authToken, params.timeouts, ProgressCallback) == false) { curlErrno = ENOEXEC; goto out; } curlResult = curl_easy_setopt(curlHandle, CURLOPT_ERRORBUFFER, errorBuffer); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_ERRORBUFFER", curl_easy_strerror(curlResult)); curlErrno = ENOEXEC; goto out; } // CURLOPT_READFUNCTION and CURLOPT_READDATA in this context refers to // data to be sent to the server... so curl will read data from us. if(msgToSendPtr != NULL) { if ( msgToSendSize > 0 ) { curlResult = curl_easy_setopt(curlHandle, CURLOPT_READFUNCTION, read_callback); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_READFUNCTION", curl_easy_strerror(curlResult)); curlErrno = ENOEXEC; goto out; } //creating the curl object // TODO: not sure that setting a pointer to a pointer that is scoped elsewhere is correct. outBoundCommInfo.buffer = msgToSendPtr; outBoundCommInfo.size = msgToSendSize; /* pointer to pass to our read function */ // here you must put the file info curlResult = curl_easy_setopt(curlHandle, CURLOPT_READDATA, &outBoundCommInfo); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_READDATA", curl_easy_strerror(curlResult)); curlErrno = ENOEXEC; goto out; } } } // sets maximum size of our internal buffer curlResult = curl_easy_setopt(curlHandle, CURLOPT_BUFFERSIZE, maxRxBufferSize); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_BUFFERSIZE", curl_easy_strerror(curlResult)); curlErrno = ENOEXEC; goto out; } // CURLOPT_WRITEFUNCTION and CURLOPT_WRITEDATA in this context refers to // data received from the server... so curl will write data to us. curlResult = curl_easy_setopt(curlHandle, CURLOPT_WRITEFUNCTION, writer); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_WRITEFUNCTION", curl_easy_strerror(curlResult)); curlErrno = ENOEXEC; goto out; } inBoundCommInfo.buffer = rxBuffer; inBoundCommInfo.size = maxRxBufferSize; curlResult = curl_easy_setopt(curlHandle, CURLOPT_WRITEDATA, &inBoundCommInfo); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_WRITEDATA", curl_easy_strerror(curlResult)); curlErrno = ENOEXEC; goto out; } curlResult = curl_easy_setopt(curlHandle, CURLOPT_POSTFIELDS, NULL); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_POSTFIELDS", curl_easy_strerror(curlResult)); curlErrno = ENOEXEC; goto out; } curlResult = curl_easy_perform(curlHandle); curl_easy_getinfo(curlHandle, CURLINFO_APPCONNECT_TIME, &connectDuration ); curl_easy_getinfo(curlHandle, CURLINFO_NAMELOOKUP_TIME, &nameResolvingDuration ); curl_easy_getinfo(curlHandle, CURLINFO_TOTAL_TIME, &transferDuration ); curl_easy_getinfo(curlHandle, CURLINFO_RESPONSE_CODE, &httpResponseCode ); curl_easy_getinfo(curlHandle, CURLINFO_HTTP_CONNECTCODE, &httpConnectCode ); if (httpResponseCode >= 300 || httpConnectCode >= 300) { if (params.verbose == true) SYSLOG_ERR("HTTP error response code:%ld, connect code:%ld", httpResponseCode, httpConnectCode); curlErrno = EHOSTUNREACH; goto out; } if (curlResult != CURLE_OK) { if (curlResult != CURLE_ABORTED_BY_CALLBACK) { if (curl_easy_getinfo(curlHandle, CURLINFO_OS_ERRNO, &curlErrno) != CURLE_OK) { curlErrno = ENOEXEC; SYSLOG_ERR("curl_easy_getinfo"); } if (curlResult == CURLE_OPERATION_TIMEDOUT) curlErrno = ETIMEDOUT; /// time out error must be distinctive else if (curlErrno == 0) curlErrno = ENOEXEC; /// can't be equalt to 0 if curlResult != CURLE_OK if (params.verbose == true) SYSLOG_WARNING("%s, %s for url %s", curl_easy_strerror(curlResult), strerror((int)curlErrno), url); }else { curlErrno = EAGAIN; if (params.verbose == true) SYSLOG_DEBUG("quitting curl transfer"); } goto out; }else if (params.verbose == true) { if (nameResolvingDuration >= 2.0) { SYSLOG_WARNING("connectDuration=%.2lf, nameResolvingDuration=%.2lf, transferDuration=%.2lf, " "httpConnectCode=%ld", connectDuration, nameResolvingDuration, transferDuration, httpConnectCode); } else { SYSLOG_DEBUG("connectDuration=%.2lf, nameResolvingDuration=%.2lf, transferDuration=%.2lf, " "httpConnectCode=%ld", connectDuration, nameResolvingDuration, transferDuration, httpConnectCode); } } // the following is a special case - a time-out from the server is going to return a // string with 1 character in it ... if (strlen(rxBuffer) > 1) { /* put the result into the main buffer and return */ if (params.verbose == true) SYSLOG_DEBUG("received msg length %d", strlen(rxBuffer)); if(msgToSendPtr != NULL && msgToSendSize > 0 ) { msgToSendPtr[0] = 0; } }else { SYSLOG_DEBUG("received time-out message from the server"); rxBuffer[0] = '\0'; curlErrno = EAGAIN; goto out; } } else { SYSLOG_ERR("curl_easy_init failed"); curlErrno = ENOEXEC; } out: _libhttpcomm_closeHttp(curlHandle, slist); return (int)curlErrno; }
/** * @brief This function configures a HTTP connection with PPC standard parameters * * @param curlHandle: curl handle to configure * @param shareCurlHandle: curl handle shared across connections, used for DNS caching. * @param slist: linked list that stores the HTTP Header * @param httpMethod: HTTP RESTFUL method to use (GET, POST, DELETE PUT) * @param url: url of the server (hostname + uri) * @param sslCertPath: location of where the certificate is * @param authToken: authentication token to be added in the header * @param fileName: ptr to message to send. NULL if none * @param rxBuffer: ptr for storing message received by the server -> must exist * @param maxRxBufferSize: max size of rxBuffer in bytes. * @param timeouts: specifies connect and transfer timeouts for the connection * @param ProgressCallback: Function to be called periodically by curl every second * while the transfer is underway * * @return true for success, false for failure */ int _libhttpcomm_configureHttp(CURL * curlHandle, CURLSH * shareCurlHandle, struct curl_slist *slist, CURLoption httpMethod, const char *url, const char *sslCertPath, const char *authToken, http_timeout_t timeouts, int (*ProgressCallback) (void *clientp, double dltotal, double dlnow, double ultotal, double ulnow)) { int retVal = true; CURLcode curlResult; char tempString[256]; if (shareCurlHandle != NULL) { curlResult = curl_easy_setopt(curlHandle, CURLOPT_SHARE, shareCurlHandle); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_SHARE", curl_easy_strerror(curlResult)); retVal = false; goto out; } } curlResult = curl_easy_setopt(curlHandle, CURLOPT_DNS_CACHE_TIMEOUT, HTTPCOMM_DEFAULT_DNS_CACHING_TIMEOUT_SEC); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_FORBID_REUSE", curl_easy_strerror(curlResult)); retVal = false; goto out; } // no signals when using c-ares curlResult = curl_easy_setopt(curlHandle, CURLOPT_NOSIGNAL, 1L); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_NOSIGNAL", curl_easy_strerror(curlResult)); retVal = false; goto out; } //making sure that this is a new connection curlResult = curl_easy_setopt(curlHandle, CURLOPT_FORBID_REUSE, false); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_FORBID_REUSE", curl_easy_strerror(curlResult)); retVal = false; goto out; } #if 0 //TODO: curlResult = curl_easy_setopt(curlHandle, CURLOPT_VERBOSE, true); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_VERBOSE", curl_easy_strerror(curlResult)); retVal = false; goto out; } #endif curlResult = curl_easy_setopt(curlHandle, CURLOPT_FRESH_CONNECT, false); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_FRESH_CONNECT", curl_easy_strerror(curlResult)); retVal = false; goto out; } // for safe multi-threaded operation // now only used in one thread. to be able to disable signal -> c-ares library has be used // for name resolving curlResult = curl_easy_setopt(curlHandle, CURLOPT_NOSIGNAL, 1); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_NOSIGNAL", curl_easy_strerror(curlResult)); retVal = false; goto out; } // less than 30 seconds is not reliable if (timeouts.connectTimeout != 0) { curlResult = curl_easy_setopt(curlHandle, CURLOPT_CONNECTTIMEOUT, timeouts.connectTimeout); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_CONNECTTIMEOUT", curl_easy_strerror(curlResult)); retVal = false; goto out; } } if (timeouts.transferTimeout != 0) { curlResult = curl_easy_setopt(curlHandle, CURLOPT_TIMEOUT, timeouts.transferTimeout); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_TIMEOUT", curl_easy_strerror(curlResult)); retVal = false; goto out; } } // set progress meter so that we can read if the push pipe is getting full if(ProgressCallback != NULL) { curlResult = curl_easy_setopt(curlHandle, CURLOPT_NOPROGRESS, 0); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_NOPROGRESS", curl_easy_strerror(curlResult)); retVal = false; goto out; } curlResult = curl_easy_setopt(curlHandle, CURLOPT_PROGRESSFUNCTION, ProgressCallback); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_PROGRESSFUNCTION", curl_easy_strerror(curlResult)); retVal = false; goto out; } } if (sslCertPath != NULL) { curlResult = curl_easy_setopt(curlHandle, CURLOPT_SSL_VERIFYPEER, 0L); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_SSL_VERIFYPEER", curl_easy_strerror(curlResult)); retVal = false; goto out; } curlResult = curl_easy_setopt(curlHandle, CURLOPT_CAPATH, sslCertPath); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_CAPATH", curl_easy_strerror(curlResult)); retVal = false; goto out; } } curlResult = curl_easy_setopt(curlHandle, CURLOPT_URL, url); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_URL", curl_easy_strerror(curlResult)); retVal = false; goto out; } curlResult = curl_easy_setopt(curlHandle, httpMethod, 1L); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_POST", curl_easy_strerror(curlResult)); retVal = false; goto out; } // all is ready, so do it -> set the http header //generic http header slist = curl_slist_append(slist, "User-Agent: IOT Proxy"); if (authToken != NULL) { snprintf(tempString, sizeof(tempString), "PPCAuthorization: esp token=%s", authToken); slist = curl_slist_append(slist, tempString); } //TODO: may want to add a paramter for an optional header line curlResult = curl_easy_setopt(curlHandle, CURLOPT_HTTPHEADER, slist); if (curlResult != CURLE_OK) { SYSLOG_ERR("%s CURLOPT_HTTPHEADER", curl_easy_strerror(curlResult)); retVal = false; goto out; } out: return retVal; }