/* **************************************************************************** * * jsonTreat - */ std::string jsonTreat(const char* content, ConnectionInfo* ciP, ParseData* parseDataP, RequestType request, std::string payloadWord, JsonRequest** reqPP) { std::string res = "OK"; JsonRequest* reqP = jsonRequestGet(request, ciP->method); LM_T(LmtParse, ("Treating a JSON request: '%s'", content)); ciP->parseDataP = parseDataP; if (reqP == NULL) { std::string errorReply = restErrorReplyGet(ciP, ciP->outFormat, "", requestType(request), SccBadRequest, std::string("Sorry, no request treating object found for RequestType '") + requestType(request) + "'"); LM_RE(errorReply, ("Sorry, no request treating object found for RequestType %d (%s)", request, requestType(request))); } if (reqPP != NULL) *reqPP = reqP; LM_T(LmtParse, ("Treating '%s' request", reqP->keyword.c_str())); reqP->init(parseDataP); try { res = jsonParse(ciP, content, reqP->keyword, reqP->parseVector, parseDataP); if (ciP->inCompoundValue == true) orion::compoundValueEnd(ciP, parseDataP); if ((lmTraceIsSet(LmtCompoundValueShow)) && (ciP->compoundValueP != NULL)) ciP->compoundValueP->shortShow("after parse: "); } catch (std::exception &e) { std::string errorReply = restErrorReplyGet(ciP, ciP->outFormat, "", reqP->keyword, SccBadRequest, std::string("JSON Parse Error: ") + e.what()); LM_E(("JSON Parse Error: '%s'", e.what())); LM_RE(errorReply, (res.c_str())); } if (res != "OK") { LM_E(("JSON parse error: %s", res.c_str())); ciP->httpStatusCode = SccBadRequest; std::string answer = restErrorReplyGet(ciP, ciP->outFormat, "", payloadWord, ciP->httpStatusCode, res); return answer; } reqP->present(parseDataP); LM_T(LmtParseCheck, ("Calling check for JSON parsed tree (%s)", ciP->payloadWord)); res = reqP->check(parseDataP, ciP); reqP->present(parseDataP); return res; }
/* **************************************************************************** * * socketHttpConnect - */ int socketHttpConnect(const std::string& host, unsigned short port) { int fd; struct addrinfo hints; struct addrinfo* peer; char port_str[10]; LM_VVV(("Generic Connect to: '%s' port: '%d'", host.c_str(), port)); memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; if (ipVersionUsed == IPV4) { hints.ai_family = AF_INET; LM_VVV(("Allow IPv4 only")); } else if (ipVersionUsed == IPV6) { hints.ai_family = AF_INET6; LM_VVV(("Allow IPv6 only")); } else { hints.ai_family = AF_UNSPEC; LM_VVV(("Allow IPv4 or IPv6")); } snprintf(port_str, sizeof(port_str), "%d" , (int) port); if (getaddrinfo(host.c_str(), port_str, &hints, &peer) != 0) { LM_RE(-1, ("getaddrinfo('%s'): %s", host.c_str(), strerror(errno))); } if ((fd = socket(peer->ai_family, peer->ai_socktype, peer->ai_protocol)) == -1) { LM_RE(-1, ("socket: %s", strerror(errno))); } if (connect(fd, peer->ai_addr, peer->ai_addrlen) == -1) { freeaddrinfo(peer); close(fd); LM_E(("connect(%s, %d): %s", host.c_str(), port, strerror(errno))); return -1; } freeaddrinfo(peer); return fd; }
/* **************************************************************************** * * mapGetIndividualContextEntityAttributes - */ HttpStatusCode mapGetIndividualContextEntityAttributes(const std::string& entityId, ContextElementResponse* response, ConnectionInfo* ciP) { HttpStatusCode ms; QueryContextRequest qcRequest; QueryContextResponse qcResponse; EntityId entity(entityId, "", "false"); // Here I fill in the 'query' entityId for the response response->contextElement.entityId.fill(entityId, "", "false"); qcRequest.entityIdVector.push_back(&entity); ms = mongoQueryContext(&qcRequest, &qcResponse, ciP->tenant, ciP->servicePathV, ciP->uriParam); if ((ms != SccOk) || (qcResponse.contextElementResponseVector.size() == 0)) { // Here I fill in statusCode for the response response->statusCode.fill(SccContextElementNotFound, std::string("Entity id: '") + entityId + "'"); LM_RE(ms, ("entityId '%s' not found", entityId.c_str())); } std::vector<ContextAttribute*> attrV = qcResponse.contextElementResponseVector.get(0)->contextElement.contextAttributeVector.vec; for (unsigned int ix = 0; ix < attrV.size() ; ++ix) { ContextAttribute* ca = new ContextAttribute(attrV[ix]); response->contextElement.contextAttributeVector.push_back(ca); } response->statusCode.fill(&qcResponse.contextElementResponseVector.get(0)->statusCode); return ms; }
/* **************************************************************************** * * attributeExpression - */ static std::string attributeExpression(std::string path, std::string value, ParseData* reqDataP) { LM_T(LmtParse, ("Got an attributeExpression: '%s'", value.c_str())); reqDataP->dcar.res.restriction.attributeExpression.set(value); if (value == "") LM_RE("Empty attribute expression", ("Empty attribute expression")); return "OK"; }
/* **************************************************************************** * * entityIdParse - */ std::string entityIdParse(RequestType requestType, xml_node<>* node, EntityId* entityIdP) { for (xml_attribute<> *attr = node->first_attribute(); attr; attr = attr->next_attribute()) { if (attr->name() == std::string("type")) { entityIdP->type = attr->value(); LM_T(LmtEntityId, ("Got a type for an entity: '%s'", entityIdP->type.c_str())); } else if (attr->name() == std::string("isPattern")) { entityIdP->isPattern = attr->value(); LM_T(LmtEntityId, ("Got an isPattern for an entity: '%s'", entityIdP->isPattern.c_str())); } else LM_RE("unsupported attribute for EntityId", ("Warning: unsupported attribute '%s' for EntityId", attr->name())); } return "OK"; }
/* **************************************************************************** * * paRcFileParse - parse startup file */ int paRcFileParse(void) { char dir[1024]; char path[1024]; char line[512]; int lineNo = 0; FILE* fP; char w[512]; LM_ENTRY(); if ((paRcFileName == NULL) || (paRcFileName[0] == 0)) { LM_EXIT(); return 0; } if (dirFind(dir, sizeof(dir)) == 0) { LM_T(LmtPaRcFile, ("RC file '%s' found in directory '%s'", paRcFileName, dir)); } else { return 0; } snprintf(path, sizeof(path), "%s/%s", dir, paRcFileName); if ((fP = fopen(path, "r")) == NULL) { LM_RE(-1, ("error opening RC file '%s': %s", path, strerror(errno))); } LM_T(LmtPaRcFile, ("parsing RC file %s", path)); while (fgets(line, sizeof(line), fP) != NULL) { char* delim; char* var; char* val; PaiArgument* aP; bool varFound; char envVarName[128]; ++lineNo; LM_T(LmtPaRcFile, ("got line %d", lineNo)); newlineStrip(line); LM_T(LmtPaRcFile, ("got line %d", lineNo)); commentStrip(line, '#'); LM_T(LmtPaRcFile, ("got line %d", lineNo)); baWsStrip(line); LM_T(LmtPaRcFile, ("got line %d", lineNo)); if (line[0] == 0) { continue; } LM_T(LmtPaRcFile, ("got line %d", lineNo)); delim = strchr(line, '='); if (delim == NULL) { char w[512]; snprintf(w, sizeof(w), "%s[%d]: no delimiter found", path, lineNo); PA_WARNING(PasParseError, w); continue; } *delim = 0; var = line; val = &delim[1]; baWsStrip(var); baWsStrip(val); if (var[0] == 0) { fclose(fP); LM_RE(-1, ("%s[%d]: no variable ...", path, lineNo)); } if (val[0] == 0) { fclose(fP); LM_RE(-1, ("%s[%d]: no value for variable %s", path, lineNo, var)); } varFound = false; paIterateInit(); while ((aP = paIterateNext(paiList)) != NULL) { paEnvName(aP, envVarName, sizeof(envVarName)); if (strcmp(var, envVarName) == 0) { aP->from = PafRcFile; LM_T(LmtPaRcFileVal, ("got value '%s' for %s", val, envVarName)); varFound = true; break; } } if (varFound == false) { char w[512]; snprintf(w, sizeof(w), "%s[%d]: variable '%s' not recognized", path, lineNo, var); PA_WARNING(PasNoSuchVariable, w); continue; } switch (aP->type) { case PaString: strcpy((char*) aP->varP, val); break; case PaBoolean: if ((strcmp(val, "TRUE") == 0) || (strcmp(val, "ON") == 0) || (strcmp(val, "yes") == 0) || (strcmp(val, "1") == 0)) *((bool*) (int64_t) aP->varP) = true; else if ((strcmp(val, "FALSE") == 0) || (strcmp(val, "OFF") == 0) || (strcmp(val, "no") == 0) || (strcmp(val, "0") == 0)) *((bool*) (int64_t) aP->varP) = false; else { snprintf(w, sizeof(w), "bad value '%s' for boolean variable %s", val, envVarName); PA_WARNING(PasNoSuchBooleanValue, w); } break; case PaSList: case PaIList: LM_TODO(("lists ...")); break; case PaInt: case PaIntU: *((int64_t*) aP->varP) = baStoi(val); break; case PaShort: case PaShortU: *((int16_t*) (int64_t) aP->varP) = baStoi(val); break; case PaFloat: *((float*) (int64_t) aP->varP) = baStof(val); break; case PaDouble: *((double*) (int64_t) aP->varP) = baStod(val); break; case PaChar: case PaCharU: *((char*) (int64_t) aP->varP) = baStoi(val); break; default: snprintf(w, sizeof(w), "bad type %d for variable %s", aP->type, envVarName); PA_WARNING(PasNoSuchType, w); } } fclose(fP); return 0; }
/* **************************************************************************** * * restStart - */ static int restStart(IpVersion ipVersion, const char* httpsKey = NULL, const char* httpsCertificate = NULL) { if (port == 0) LM_RE(1, ("Please call restInit before starting the REST service")); if ((ipVersion == IPV4) || (ipVersion == IPDUAL)) { memset(&sad, 0, sizeof(sad)); if (inet_pton(AF_INET, bindIp, &(sad.sin_addr.s_addr)) != 1) LM_RE(2, ("V4 inet_pton fail for %s", bindIp)); sad.sin_family = AF_INET; sad.sin_port = htons(port); if ((httpsKey != NULL) && (httpsCertificate != NULL)) { mhdDaemon = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION | MHD_USE_SSL, // MHD_USE_SELECT_INTERNALLY htons(port), NULL, NULL, connectionTreat, NULL, MHD_OPTION_HTTPS_MEM_KEY, httpsKey, MHD_OPTION_HTTPS_MEM_CERT, httpsCertificate, MHD_OPTION_NOTIFY_COMPLETED, requestCompleted, NULL, MHD_OPTION_CONNECTION_MEMORY_LIMIT, 2 * PAYLOAD_SIZE, MHD_OPTION_SOCK_ADDR, (struct sockaddr*) &sad, MHD_OPTION_END); } else { LM_V(("Starting HTTP daemon on IPv4 %s port %d", bindIp, port)); mhdDaemon = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, // MHD_USE_SELECT_INTERNALLY htons(port), NULL, NULL, connectionTreat, NULL, MHD_OPTION_NOTIFY_COMPLETED, requestCompleted, NULL, MHD_OPTION_CONNECTION_MEMORY_LIMIT, 2 * PAYLOAD_SIZE, MHD_OPTION_SOCK_ADDR, (struct sockaddr*) &sad, MHD_OPTION_END); } if (mhdDaemon == NULL) LM_RE(3, ("MHD_start_daemon failed")); } if ((ipVersion == IPV6) || (ipVersion == IPDUAL)) { memset(&sad_v6, 0, sizeof(sad_v6)); if (inet_pton(AF_INET6, bindIPv6, &(sad_v6.sin6_addr.s6_addr)) != 1) LM_RE(1, ("V6 inet_pton fail for %s", bindIPv6)); sad_v6.sin6_family = AF_INET6; sad_v6.sin6_port = htons(port); if ((httpsKey != NULL) && (httpsCertificate != NULL)) { LM_V(("Starting HTTPS daemon on IPv6 %s port %d", bindIPv6, port)); mhdDaemon_v6 = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION | MHD_USE_IPv6 | MHD_USE_SSL, htons(port), NULL, NULL, connectionTreat, NULL, MHD_OPTION_HTTPS_MEM_KEY, httpsKey, MHD_OPTION_HTTPS_MEM_CERT, httpsCertificate, MHD_OPTION_NOTIFY_COMPLETED, requestCompleted, NULL, MHD_OPTION_CONNECTION_MEMORY_LIMIT, 2 * PAYLOAD_SIZE, MHD_OPTION_SOCK_ADDR, (struct sockaddr*) &sad_v6, MHD_OPTION_END); } else { LM_V(("Starting HTTP daemon on IPv6 %s port %d", bindIPv6, port)); mhdDaemon_v6 = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION | MHD_USE_IPv6, htons(port), NULL, NULL, connectionTreat, NULL, MHD_OPTION_NOTIFY_COMPLETED, requestCompleted, NULL, MHD_OPTION_CONNECTION_MEMORY_LIMIT, 2 * PAYLOAD_SIZE, MHD_OPTION_SOCK_ADDR, (struct sockaddr*) &sad_v6, MHD_OPTION_END); } if (mhdDaemon_v6 == NULL) LM_RE(1, ("MHD_start_daemon_v6 failed")); } return 0; }
/* **************************************************************************** * * connectionTreat - * * This is the MHD_AccessHandlerCallback function for MHD_start_daemon * This function returns: * o MHD_YES if the connection was handled successfully * o MHD_NO if the socket must be closed due to a serious error * * - This function is called once when the headers are read and the ciP is created. * - Then it is called for data payload and once all the payload an acknowledgement * must be done, setting *upload_data_size to ZERO. * - The last call is made with *upload_data_size == 0 and now is when the connection * is open to send responses. * * Call 1: *con_cls == NULL * Call 2: *con_cls != NULL AND *upload_data_size != 0 * Call 3: *con_cls != NULL AND *upload_data_size == 0 */ static int connectionTreat ( void* cls, MHD_Connection* connection, const char* url, const char* method, const char* version, const char* upload_data, size_t* upload_data_size, void** con_cls ) { ConnectionInfo* ciP = (ConnectionInfo*) *con_cls; size_t dataLen = *upload_data_size; // 1. First call - setup ConnectionInfo and get/check HTTP headers if (ciP == NULL) { if ((ciP = new ConnectionInfo(url, method, version, connection)) == NULL) LM_RE(MHD_NO, ("Error allocating ConnectionInfo")); *con_cls = (void*) ciP; // Pointer to ConnectionInfo for subsequent calls MHD_get_connection_values(connection, MHD_HEADER_KIND, httpHeaderGet, &ciP->httpHeaders); ciP->outFormat = wantedOutputSupported(ciP->httpHeaders.accept, &ciP->charset); if (ciP->outFormat == NOFORMAT) ciP->outFormat = XML; // XML is default output format if (contentTypeCheck(ciP) != 0) { LM_W(("Error in Content-Type")); restReply(ciP, ciP->answer); } else if (outFormatCheck(ciP) != 0) { LM_W(("Bad Accepted Out-Format (in Accept header)")); restReply(ciP, ciP->answer); } else ciP->inFormat = formatParse(ciP->httpHeaders.contentType, NULL); return MHD_YES; } // // 2. Data gathering calls // // 2-1. Data gathering calls, just wait // 2-2. Last data gathering call, acknowledge the receipt of data // if (dataLen != 0) { if (dataLen == ciP->httpHeaders.contentLength) { if (ciP->httpHeaders.contentLength <= PAYLOAD_MAX_SIZE) { if (ciP->httpHeaders.contentLength > STATIC_BUFFER_SIZE) ciP->payload = (char*) malloc(ciP->httpHeaders.contentLength); else ciP->payload = static_buffer; ciP->payloadSize = dataLen; memcpy(ciP->payload, upload_data, dataLen); ciP->payload[dataLen] = 0; } else { char details[256]; snprintf(details, sizeof(details), "payload size: %d, max size supported: %d", ciP->httpHeaders.contentLength, PAYLOAD_MAX_SIZE); ciP->answer = restErrorReplyGet(ciP, ciP->outFormat, "", ciP->url, SccRequestEntityTooLarge, details); ciP->httpStatusCode = SccRequestEntityTooLarge; } // All payload received, acknowledge! *upload_data_size = 0; } else LM_T(LmtPartialPayload, ("Got %d of payload of %d bytes", dataLen, ciP->httpHeaders.contentLength)); return MHD_YES; } // 3. Finally, serve the request (unless an error has occurred) if (((ciP->verb == POST) || (ciP->verb == PUT)) && (ciP->httpHeaders.contentLength == 0)) { std::string errorMsg = restErrorReplyGet(ciP, ciP->outFormat, "", url, SccLengthRequired, "Zero/No Content-Length in PUT/POST request"); ciP->httpStatusCode = SccLengthRequired; restReply(ciP, errorMsg); } else if (ciP->answer != "") restReply(ciP, ciP->answer); else serveFunction(ciP); return MHD_YES; }
/* **************************************************************************** * * wantedOutputSupported - */ static Format wantedOutputSupported(std::string acceptList, std::string* charsetP) { std::vector<std::string> vec; char* copy; if (acceptList.length() == 0) { /* HTTP RFC states that a missing Accept header must be interpreted as if the client is * accepting any type */ copy = strdup("*/*"); } else { copy = strdup((char*) acceptList.c_str()); } char* cP = copy; do { char* comma; comma = strstr(cP, ","); if (comma != NULL) { *comma = 0; cP = wsStrip(cP); vec.push_back(cP); cP = comma; ++cP; } else { cP = wsStrip(cP); if (*cP != 0) { vec.push_back(cP); } *cP = 0; } } while (*cP != 0); free(copy); bool xml = false; bool json = false; for (unsigned int ix = 0; ix < vec.size(); ++ix) { char* s; // // charset embedded in 'Accept' header? // We read it but we don't do anything with it ... // if ((s = strstr((char*) vec[ix].c_str(), ";")) != NULL) { *s = 0; ++s; s = wsStrip(s); if (strncmp(s, "charset=", 8) == 0) { s = &s[8]; s = wsStrip(s); if (charsetP != NULL) *charsetP = s; } } std::string format = vec[ix].c_str(); if (format == "*/*") xml = true; if (format == "*/xml") xml = true; if (format == "application/*") xml = true; if (format == "application/xml") xml = true; if (format == "application/json") json = true; if (format == "*/json") json = true; if ((acceptTextXml == true) && (format == "text/xml")) xml = true; // // Resetting charset // if (charsetP != NULL) *charsetP = ""; } if (xml == true) return XML; else if (json == true) return JSON; LM_RE(NOFORMAT, ("No valid 'Accept-format' found")); }
/* **************************************************************************** * * sendHttpSocket - * * The waitForResponse arguments specifies if the method has to wait for response * before return. If this argument is false, the return string is "" * * FIXME: I don't like too much "reusing" natural output to return "error" in the * case of error. I think it would be smarter to use "std::string* error" in the * arguments or (even better) and exception. To be solved in the future in a hardening * period. * * Note, we are using an hybrid approach, consisting in an static thread-local buffer of * small size that cope with the most of notifications to avoid expensive * calloc/free syscalls if the notification payload is not very large. * */ std::string sendHttpSocket ( const std::string& _ip, unsigned short port, const std::string& protocol, const std::string& verb, const std::string& tenant, const std::string& resource, const std::string& content_type, const std::string& content, bool useRush, bool waitForResponse ) { char buffer[TAM_BUF]; char response[TAM_BUF]; char preContent[TAM_BUF]; char msgStatic[MAX_STA_MSG_SIZE]; char* what = (char*) "static"; char* msgDynamic = NULL; char* msg = msgStatic; // by default, use the static buffer std::string rushHeaderIP = ""; unsigned short rushHeaderPort = 0; std::string rushHttpHeaders = ""; static unsigned long long callNo = 0; std::string result; std::string ip = _ip; ++callNo; // Preconditions check if (port == 0) LM_RE("error", ("port is ZERO")); if (ip.empty()) LM_RE("error", ("ip is empty")); if (verb.empty()) LM_RE("error", ("verb is empty")); if (resource.empty()) LM_RE("error", ("resource is empty")); if ((content_type.empty()) && (!content.empty())) LM_RE("error", ("Content-Type is empty but there is actual content")); if ((!content_type.empty()) && (content.empty())) LM_RE("error", ("there is actual content but Content-Type is empty")); // // Rush // Every call to sendHttpSocket specifies whether RUSH should be used or not. // But, this depends also on how the broker was started, so here the 'useRush' // parameter is cancelled in case the broker was started without rush. // // If rush is to be used, the IP/port is stored in rushHeaderIP/rushHeaderPort and // after that, the host and port of rush is set as ip/port for the message, that is // now sent to rush instead of to its final destination. // Also, a few HTTP headers for rush nust be setup. // if (useRush) { if ((rushPort == 0) || (rushHost == "")) useRush = false; else { char portAsString[16]; rushHeaderIP = ip; rushHeaderPort = port; ip = rushHost; port = rushPort; sprintf(portAsString, "%d", (int) rushHeaderPort); rushHttpHeaders = "X-relayer-host: " + rushHeaderIP + ":" + portAsString + "\n"; if (protocol == "https:") rushHttpHeaders += "X-relayer-protocol: https\n"; } } // Buffers clear memset(buffer, 0, TAM_BUF); memset(response, 0, TAM_BUF); memset(msg, 0, MAX_STA_MSG_SIZE); snprintf(preContent, sizeof(preContent), "%s %s HTTP/1.1\n" "User-Agent: orion/%s\n" "Host: %s:%d\n" "Accept: application/xml, application/json\n%s", verb.c_str(), resource.c_str(), versionGet(), ip.c_str(), (int) port, rushHttpHeaders.c_str()); LM_T(LmtRush, ("'PRE' HTTP headers:\n--------------\n%s\n-------------", preContent)); if (tenant != "") { char tenantHeader[128]; snprintf(tenantHeader, sizeof(tenantHeader), "fiware-service: %s\n", tenant.c_str()); strncat(preContent, tenantHeader, sizeof(preContent) - strlen(preContent)); } if (!content.empty()) { char headers[512]; sprintf(headers, "Content-Type: %s\n" "Content-Length: %zu\n", content_type.c_str(), content.length() + 1); strncat(preContent, headers, sizeof(preContent) - strlen(preContent)); /* Choose the right buffer (static or dynamic) to use. Note we are using +3 due to: * + 1, for the \n between preContent and content * + 1, for the \n at the end of the message * + 1, for the \0 by the trailing character in C strings */ int neededSize = content.length() + strlen(preContent) + 3; if (neededSize > MAX_DYN_MSG_SIZE) { LM_RE("error", ("HTTP request to send is too large: %d bytes", content.length() + strlen(preContent))); } else if (neededSize > MAX_STA_MSG_SIZE) { msgDynamic = (char*) calloc(sizeof(char), neededSize); if (msgDynamic == NULL) { LM_RE("error", ("dynamic memory allocation failure")); } msg = msgDynamic; what = (char*) "dynamic"; } /* The above checking should ensure that the three parts fit, so we are using * sprint() instead of snprintf() */ sprintf(msg, "%s\n%s", preContent, content.c_str()); } else { /* In the case of no-content we assume that MAX_STA_MSG_SIZE is enough to send the message */ LM_T(LmtClientOutputPayload, ("Using static buffer to send HTTP request (empty content)")); sprintf(msg, "%s\n", preContent); } /* We add a final newline (I guess that HTTP protocol needs it) */ strcat(msg, "\n"); int fd = socketHttpConnect(ip, port); // Connecting to HTTP server if (fd == -1) LM_RE("error", ("Unable to connect to HTTP server at %s:%d", ip.c_str(), port)); int nb; int sz = strlen(msg); LM_T(LmtClientOutputPayload, ("Sending message %lu to HTTP server: sending %s message of %d bytes to HTTP server", callNo, what, sz)); LM_T(LmtClientOutputPayloadDump, ("Sending to HTTP server payload:\n%s", msg)); nb = send(fd, msg, sz, 0); if (msgDynamic != NULL) { free (msgDynamic); } if (nb == -1) LM_RE("error", ("error sending to HTTP server: %s", strerror(errno))); else if (nb != sz) LM_E(("error sending to HTTP server. Sent %d bytes of %d", nb, sz)); if (waitForResponse) { nb = recv(fd,&buffer,TAM_BUF-1,0); if (nb == -1) LM_RE("error", ("error recv from HTTP server: %s", strerror(errno))); else if ( nb >= TAM_BUF) LM_RE("error", ("recv from HTTP server too long")); else { memcpy(response, buffer, nb); LM_T(LmtClientInputPayload, ("Received from HTTP server:\n%s", response)); } if (strlen(response) > 0) result = response; } else { LM_T(LmtClientInputPayload, ("not waiting for response")); result = ""; } close(fd); return result; }