http challenge response POST with curl - c++

I have written a small c++ program to connect to my router (avm Fritzbox 7590) using a challenge response method.
For this I use openssl (SHA256), curl(GET & POST) and tinyXML (parsing the response).
I have a member function "query_challenge()" to query the FritzBox for a challenge.
This I receive as .xml, this I receive:
<?xml version="1.0" encoding="utf-8"?>
<SessionInfo>
<SID>0000000000000000</SID>
<Challenge>2$60000$*******************$6000$15a84421a7dfc4d9ac8026f776de48f3</Challenge>
<BlockTime>0</BlockTime>
<Rights/>
<Users>
<User last="1">adminxxx</User>
</Users>
</SessionInfo>
With the member function "std::string calculate_pbkdf2_response()" I evaluate the received challenge and send this "query_sessionID()" with a POST to the FritzBox to log in and get a valid SessionID.
When calling my query_sessionID() method, if everything is correct, I should get another XML via the POST in which a valid SID(SessionID) is contained.
However, I get the following response:
<?xml version="1.0" encoding="utf-8"?>
<SessionInfo>
<SID>0000000000000000</SID>
<Challenge>2$60000$diffent*******************$6000$1268d1df004ffdb2fd8074b557467a01</Challenge>
<BlockTime>128</BlockTime>
<Rights/>
<Users>
<User last="1">adminxxx</User>
</Users>
</SessionInfo>
which shows that either the calculated resopnse is incorrect or the POST may not have worked correctly.
I tested my calculate_pbkdf2_response() with the example below.
The example challenge “2$10000$5A1711$2000$5A1722” and the password
“1example!“ (utf8-encoded) results in: hash1 =
pbdkf2_hmac_sha256(“1example!”, 5A1711, 10000)
=> 0x23428e9dec39d95ac7a40514062df0f9e94f996e17c398c79898d0403b332d3b (hex) response = 5A1722$ + pbdkf2_hmac_sha256(hash1, 5A1722,
2000).hex()
=> 5A1722$1798a1672bca7c6463d6b245f82b53703b0f50813401b03e4045a5861e689adb
https://avm.de/fileadmin/user_upload/Global/Service/Schnittstellen/AVM_Technical_Note_-_Session_ID_english_2021-05-03.pdf
This "5A1722$1798a1672bca7c6463d6b245f82b53703b0f50813401b03e4045a5861e689adb" response gets my calculate_pbkdf2_response() method also out with the respective PW and challenge.
So I suspect that my CURL POST request may not be correct.
Maybe someone has a tip who has more experience with this stuff, this is my first http attempt with CURL as well as the first use of openSSL or challenge response method.
main.cpp:
#include <iostream>
#include "../public/FritzBox_Session.hpp"
int main(int argc, char const *argv[])
{
std::string user = "adminxxx";
std::string pw = "passwort";
homeAutomat::FritzBox::FritzBox_Session TEST(user, pw);
TEST.establish_connection();
return 0;
}
FritzBox_Session.hpp:
#include <iostream>
#include <string>
#include <sstream>
#include <curl/curl.h>
#include <nlohmann/json.hpp>
#include <tinyxml2.h>
#include <openssl/evp.h>
#include <openssl/sha.h>
namespace homeAutomat {
namespace FritzBox {
class FritzBox_Session
{
private:
CURL *curl;
const std::string fritzbox_addr = "http://fritz.box/login_sid.lua?version=2";
std::string mUsername;
std::string mPassword;
std::stringstream mChallenge;
std::string mSID;
tinyxml2::XMLDocument XML_response;
bool query_challenge();
bool query_sessionID();
std::string calculate_pbkdf2_response();
public:
FritzBox_Session(std::string &username, std::string &password);
~FritzBox_Session();
bool establish_connection();
};
} // FritzBox
} // homeAutomat
FritzBox_Session.cpp:
#include "FritzBox_Session.hpp"
using namespace homeAutomat;
using namespace FritzBox;
using namespace nlohmann;
namespace {
// CURL - ReadReceivedData Callback-Fuction
size_t writeFunction(void *ptr, size_t size, size_t nmemb, void *userdata)
{
((std::string*)userdata)->append((char*)ptr, size * nmemb);
return size * nmemb;
}
}
FritzBox_Session::FritzBox_Session(std::string &username, std::string &password)
:
mUsername(username),
mPassword(password)
{
curl = curl_easy_init();
if(!curl){
std::cerr << "curl isn't initialized! " << std::endl;
}
}
FritzBox_Session::~FritzBox_Session()
{
curl_easy_cleanup(curl);
}
bool FritzBox_Session::establish_connection()
{
if(query_challenge()){
if(query_sessionID()){
}
else{
std::cerr << "error at query_sessionID() occurred" << std::endl;
return false;
}
}
else{
std::cerr << "error at query_challenge() occurred" << std::endl;
return false;
}
return true;
}
bool FritzBox_Session::query_challenge()
{
std::string readBuffer;
CURLcode retVal;
if(!curl){
std::cerr << "curl isn't initialized!'" << std::endl;
return 1;
}
curl_easy_setopt(curl, CURLOPT_URL, fritzbox_addr.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunction);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
retVal = curl_easy_perform(curl);
if(retVal != CURLE_OK){
std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(retVal) << std::endl;
return false;
}
int error_id = XML_response.Parse(&readBuffer[0]);
if(error_id != tinyxml2::XML_SUCCESS){
std::cout << "xml parse error" << XML_response.ErrorName() << std::endl;
XML_response.Clear();
return false;
}
auto save_xml_to_file = XML_response.SaveFile("response_query_challenge.xml");
if(save_xml_to_file != tinyxml2::XML_SUCCESS){
std::cout << "xml save to file error: " << XML_response.ErrorName() << std::endl;
}
// search for <SessionInfo>
tinyxml2::XMLElement* sessionInfo = XML_response.FirstChildElement("SessionInfo");
if(sessionInfo == nullptr){
std::cerr << "XML response has no <SessionInfo>-Variable!" << std::endl;
XML_response.Clear();
return false;
}
// search for <SessionInfo> <Challenge> </SessionInfo>
tinyxml2::XMLElement* challenge = sessionInfo->FirstChildElement("Challenge");
if(challenge == nullptr){
std::cerr << "XML response has no <Challenge>-Variable!" << std::endl;
XML_response.Clear();
return false;
}
// get String Value of <SessionInfo> <Challenge> </SessionInfo>
mChallenge << challenge->GetText();
if(mChallenge.str().empty()){
std::cerr << "XML response <Challenge> is empty!" << std::endl;
XML_response.Clear();
return false;
}
XML_response.Clear();
return true;
}
bool FritzBox_Session::query_sessionID()
{
CURLcode retVal;
std::string readBuffer;
std::string response = calculate_pbkdf2_response();
if (!curl) {
std::cerr << "Fehler beim Initialisieren von CURL" << std::endl;
return 1;
}
// HTTP Method 'POST'
curl_easy_setopt(curl, CURLOPT_POST, 1L);
// Set POST Data
std::string post_data = "username=" + mUsername + "&response=" + response;
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, post_data.length());
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, &post_data[0]);
// Set Content-Type-Header
struct curl_slist* headers = NULL;
headers = curl_slist_append(headers, "Content-Type: application/x-www-form-urlencoded");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
// Set Callback to writeFunction() received data
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunction);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
// Send POST data and received answer
retVal = curl_easy_perform(curl);
if(retVal != CURLE_OK){
std::cerr << "Error while sending the request: " << curl_easy_strerror(retVal) << std::endl;
return false;
}
else{
int error_id = XML_response.Parse(&readBuffer[0]);
if(error_id != tinyxml2::XML_SUCCESS){
std::cout << "xml parse error" << XML_response.ErrorName() << std::endl;
}
auto save_xml_to_file = XML_response.SaveFile("response_query_sessionID.xml");
if(save_xml_to_file != tinyxml2::XML_SUCCESS){
std::cout << "xml save to file error: " << XML_response.ErrorName() << std::endl;
}
tinyxml2::XMLElement* sessionInfo = XML_response.FirstChildElement("SessionInfo");
if(sessionInfo == nullptr){
std::cerr << "XML response has no <SessionInfo>-Variable!" << std::endl;
XML_response.Clear();
return false;
}
tinyxml2::XMLElement* SID = sessionInfo->FirstChildElement("SID");
if(SID == nullptr){
std::cerr << "XML response has no <SID>-Variable!" << std::endl;
XML_response.Clear();
return false;
}
mSID = SID->GetText();
if(mSID.empty()){
std::cerr << "XML response <SID> is empty!" << std::endl;
XML_response.Clear();
return false;
}
if(mSID.compare("0000000000000000") == 0){
std::cerr << "username or password wrong!" << std::endl;
}
else{
std::cout << "login was successful SID is: " << mSID << std::endl;
}
return true;
}
}
std::string FritzBox_Session::calculate_pbkdf2_response()
{
// Split the challenge into parts
std::vector<std::string> challenge_parts;
std::string item;
while (std::getline(mChallenge, item, '$'))
{
challenge_parts.push_back(item);
}
// Extract all necessary values encoded into the challenge
int iter1 = std::stoi(challenge_parts[1]);
int iter2 = std::stoi(challenge_parts[3]);
// Convert the salt strings to unsigned char arrays
unsigned char salt1[challenge_parts[2].size() / 2];
unsigned char salt2[challenge_parts[4].size() / 2];
for (int i = 0; i < challenge_parts[2].size(); i += 2) {
sscanf(challenge_parts[2].substr(i, 2).c_str(), "%02x", &salt1[i / 2]);
}
for (int i = 0; i < challenge_parts[4].size(); i += 2) {
sscanf(challenge_parts[4].substr(i, 2).c_str(), "%02x", &salt2[i / 2]);
}
// Hash twice, once with static salt...
unsigned char hash1[SHA256_DIGEST_LENGTH];
PKCS5_PBKDF2_HMAC(mPassword.c_str(), mPassword.size(), salt1, sizeof(salt1), iter1, EVP_sha256(), SHA256_DIGEST_LENGTH, hash1);
// Once with dynamic salt.
unsigned char hash2[SHA256_DIGEST_LENGTH];
PKCS5_PBKDF2_HMAC((const char*)hash1, sizeof(hash1), salt2, sizeof(salt2), iter2, EVP_sha256(), SHA256_DIGEST_LENGTH, hash2);
// Convert the hash2 array to a hexadecimal string
char hash2_hex[SHA256_DIGEST_LENGTH * 2 + 1];
for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
sprintf(hash2_hex + i * 2, "%02x", hash2[i]);
}
return challenge_parts[4] + '$' + hash2_hex;
}

Related

Writing Data Fails With CURLOPT_WRITEFUNCTION After Many Writeback Calls

Essentially, I have a write callback that I set for CURLOPT_WRITEFUNCTION. I also set my pointer for writing the data to in CURLOPT_WRITEDATA.
I run an infinite while loop for around 5 seconds, then CURLOPT fails to right data to the void* up pointer defined in CURLOPT_WRITEDATA, then after that one failure it starts working again. I got tons of successful writes sprinkled with failures. Is this a memory issue, and if so, is there a way to circumvent reaching this issue altogether?
// these locations will likely be different on your local.
#include "../cygwin64/usr/include/curl/curl.h"
#include "../cygwin64/usr/include/json/json.h"
size_t write_callback(char *buf, size_t size, size_t nmemb, void* up) {
size_t num_bytes = size*nmemb;
std::string* data = (std::string*) up;
for(int i = 0; i < num_bytes; i++) {
data->push_back(buf[i]);
}
return num_bytes;
}
CURL* init_curl(struct curl_slist *headers, std::string* chunk) {
CURL *curl;
CURLcode res;
curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) chunk);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
return curl;
}
bool curl_get(struct curl_slist *headers, const std::string& url,
Json::Value* json_res) {
// this is where my data should be written to
std::string data;
CURL* curl = init_curl(headers, &data);
CURLcode res;
bool success = true;
if (curl) {
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
res = curl_easy_perform(curl);
if(res != CURLE_OK) {
std::cout << "Could not perform get for " << url << std::endl;
std::cout << curl_easy_strerror(res) << std::endl;
success = false;
} else {
Json::Value json_data;
Json::CharReaderBuilder json_reader;
std::istringstream stream_data(data);
std::string errs;
if(Json::parseFromStream(json_reader, stream_data, &json_data, &errs)) {
std::cout << "successfully parsed JSON data for: " << url << std::endl;
*json_res = json_data;
} else {
std::cout << "failed to parse JSON data for: " << url << std::endl;
std::cout << errs << std::endl;
std::cout << json_data << std::endl;
std::cout << "finished failing" << std::endl;
success = false;
}
}
} else {
success = false;
}
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
return success;
}
int main(int argc, char** argv) {
curl_global_init(CURL_GLOBAL_ALL);
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Accept: application/json");
while (true) {
Json::Value response;
if (curl_get(headers, "https://api.robinhood.com/quotes/?symbols=AMZN", &response)) {
std::cout << response << std::endl;
} else {
// reaches here sometimes because response (my data) is null
std::cout << "failed to get last trade price" << std::endl;
}
}
curl_global_cleanup();
return 0;
}
Expect no failures, but I get intermittent failures to right the data to my WRITEDATA chunk.

How to get OpenSSL BIO_do_connect() failure reason?

I'm using Ubuntu 18.04/gcc 7.3/OpenSSL 1.1.0g to make C++ app performing TLS/SSL connection with non-blocking BIO API.
When BIO_do_connect() fails connecting, e.g. if using wrong host name or port, there is no errors reported by OpenSSL. ERR_get_error() returns zero and ERR_print_errors_xx() doesn't print anything.
So the question is - how to get actual connection failure reason, e.g. 'Connection refused' or 'Host not resolved' etc?
Used code snippet below:
#include <cstdio>
#include <cstring>
#include <iostream>
#include "openssl/bio.h"
#include "openssl/err.h"
#include "openssl/ssl.h"
int main(int argc, char *argv[])
{
OPENSSL_init_ssl(OPENSSL_INIT_SSL_DEFAULT, nullptr);
std::cout << OpenSSL_version(OPENSSL_VERSION) << std::endl;
SSL_CTX* ctx = SSL_CTX_new(TLS_client_method());
if (!ctx)
{
std::cerr << "Error creating SSL context:" << std::endl;
ERR_print_errors_fp(stderr);
return 1;
}
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2);
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3);
if(!SSL_CTX_load_verify_locations(ctx,
"/etc/ssl/certs/ca-certificates.crt",
nullptr))
{
std::cerr << "Error loading trust store into SSL context" << std::endl;
ERR_print_errors_fp(stderr);
return 1;
}
BIO* cbio = BIO_new_ssl_connect(ctx);
SSL* ssl = nullptr;
BIO_get_ssl(cbio, &ssl);
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
BIO_set_conn_hostname(cbio, "not_actually_existing_host.com:https");
BIO_set_nbio(cbio, 1);
std::cout << "Start connecting" << std::endl;
next:
if (BIO_do_connect(cbio) <= 0)
{
if (!BIO_should_retry(cbio))
{
std::cerr << "Error attempting to connect:" << std::endl;
ERR_print_errors_fp(stderr); // <---- PRINTS NOTHING!!!
BIO_free_all(cbio);
SSL_CTX_free(ctx);
return 1;
}
else goto next;
}
std::cout << "Connected OK" << std::endl;
BIO_free_all(cbio);
SSL_CTX_free(ctx);
return 0;
}
This approach finally works for me:
const auto sysErrorCode = errno;
const auto sslErrorCode = ERR_get_error();
std::string errorDescription;
if (sslErrorCode != 0) errorDescription = ERR_error_string(sslErrorCode, nullptr);
if (sysErrorCode != 0)
{
if (!errorDescription.empty()) errorDescription += '\n';
errorDescription += "System error, code=" + std::to_string(sysErrorCode);
errorDescription += ", ";
errorDescription += strerror(sysErrorCode);
}

TCP server message extra characters c++

I think I am missing something and doesn't make sense.
I am writing pretty simple TCP server, everything works pretty much as expected, but when a message saying "500 LOGIN FAILED" gets send over network, it get interpreted as "$500 LOGIN FAILED".
I am testing my server using telnet on localhost
here is simplified version of my code
recv(c_sockfd, buf, BUFFSIZE, 0))
inBuffer.push_back(buf);
auto messageToSend = checkResponse(parseBuffer(inBuffer.back()));
//get the second thing in the tuple
outBuffer.push_back(std::get<1>(messageToSend));
bzero(buf, sizeof(buf));
send(c_sockfd, &outBuffer.back(), sizeof( outBuffer.back() ), 0)
in the checkResponse func, I am implementing logic to decide what message to send, and somehow when I send ERROR message the extra character is added at the beginning of the message.
EXAMPLE 1:
Connected to localhost.
Escape character is '^]'.
200 LOGIN
Robot345\r\n
201 PASSWORD
674\r\n
202 OK
INFO iasdijasdjiajsdiajdijasidjiansdjsdvhdf dfvsdfsdf\r\n
&501 SYNTAX ERROR
Notice the "&" character
EXAMPLE 2:
Connected to localhost.
Escape character is '^]'.
200 LOGIN
Robot345\r\n
201 PASSWORD
456\r\n
$500 LOGIN FAILED
Notice the "$" character
Does anyone have any idea where the extra characters could be added to the string?
I didn't want to include full code, because the requirement was to have all in one file, which makes it difficult to read. Here it goes tho.
FULL CODE:
#include <iostream>
#include <regex>
#include <iterator>
#include <vector>
#include <sstream>
#include <string>
#include <stdio.h>
#include <string.h>
#include <string.h>
#include <netdb.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <tuple>
#define MIN_PORT 3000
#define MAX_PORT 3999
#define BUFFSIZE 1000
/**
0 - LOGIN SUCCESSFUL, USERNAME IS IN THE BUFFER
1 - PASSWORD CHECK
2 - PASSWORD OK, COMMUNICATING
*/
int state = 0;
std::string username, password;
/**
CHECKS ENTERED PASSWORD BASES ON THE SUM OF ASCII VALUES OF USERNAME
#return: true on success, false otherwise
*/
bool checkPassword(std::string password){
std::istringstream sst;
sst.str(username);
unsigned char byte = '\0';
int value = 0;
// std::cout << "byte poprve: " << byte << std::endl;
// std::cout << "byte poprve INT: " << (int) byte << std::endl;
while (sst >> byte) {
std::cout << "podruhe: " << byte << std::endl;
std::cout << "podruhe INT: " << (int) byte << std::endl;
std::cout << "Prubezna SUMA: " << (int) value << std::endl;
value += byte;
}
std::cout << "suma: " << value << std::endl;
// Check the entered password
if (password == std::to_string(value)) {
return true;
}
return false;
}
/**
CHECKS MESSAGE SYNTAX BASED ON THE STATE WE ARE IN
CHECKS PASSWORD
CHECKS CHECK SUM
#param response <string type (if available), string message to parse>
#return TRUE on success, FALSE otherwise
*/
bool checkMessage(std::tuple<std::string,std::string> response){
auto messageToParse = std::get<1>(response);
std::string delimeter = "\r\n";
std::string::size_type pos = messageToParse.find(delimeter);
//INITIAL CHECK
if (pos < 1){
return false;
}
//somehow you have to multiply the length by 2
auto parsedMessage = messageToParse.substr(0,pos - 2*delimeter.length());
std::cout << parsedMessage << " : THIS IS YOUR PARSED MESSAGE";
//USERNAME
if (state == 0) {
username = parsedMessage;
return true;
}
//PASSWORD CHECK
if (state == 1 && checkPassword(parsedMessage)) {
password = parsedMessage;
return true;
}
if (state == 2) {
std::string type = std::get<0>(response);
//INFO
if( type == "I" ){
return true;
}
//PHOTO
if ( type == "F") {
return true;
}
}
return false;
}
/**
THIS FUNC WILL CHECK RESPONSE FROM THE ROBOT, AND DECIDE WHAT TO DO BASED ON THE STATE
#return tuple<bool TRUE if everything is right,std::string MESSAGE to send to the robot>
*/
std::tuple<bool,std::string> checkResponse(std::tuple<std::string, std::string> response){
if (state == 0) {
if (checkMessage(response)) {
std::cout << state << " / / state" << std::endl;
return std::make_tuple(true, "201 PASSWORD\r\n");
}
}
if (state == 1) {
// TADY BUDE JESTE PODMINKA, ZE HESLO JE SPRAVNE
if(checkMessage(response)){
std::cout << state << " / / / state" << std::endl;
return std::make_tuple(true, "202 OK\r\n");
}else{
std::cout << state << " / / / / state" << std::endl;
return std::make_tuple(false, "500 LOGIN FAILED\r\n");
}
}
if (state == 2) {
if (checkMessage(response)) {
std::cout << state << " / / / / / state" << std::endl;
return std::make_tuple(true, "202 OK\r\n");
}else{
std::cout << state << " / / / / / / state" << std::endl;
return std::make_tuple(false, "501 SYNTAX ERROR \r\n");
}
}
std::cout << state << " / / / / / / / / state" << std::endl;
return std::make_tuple(false, "unexpected result");
}
/**
This func will parse the incoming buffer
#param buffer incoming buffer
#return tuple <String type of message (U,I,P,F), String actual message>
*/
std::tuple<std::string, std::string> parseBuffer(std::string buffer){
if (state == 0) {
return std::make_tuple("U", buffer);
}
if (state == 1) {
return std::make_tuple("P", buffer);
}else{
std::string delimeter = " ";
std::string::size_type pos = buffer.find(delimeter);
std::string type = buffer.substr(0, pos );
std::string message = buffer.erase(0, pos + delimeter.length());
return std::make_tuple(type, message);
}
}
int main(int argc, char *argv[])
{
char buf[BUFFSIZE];
std::vector<std::string> outBuffer;
std::vector<std::string> inBuffer;
int sockfd, c_sockfd;
sockaddr_in my_addr, rem_addr;
socklen_t rem_addr_length;
int mlen;
const int PORT_NUM = atoi(argv[1]);
if( (PORT_NUM > MAX_PORT) || (PORT_NUM < MIN_PORT)){
perror("Port number is not acceptable");
exit(-1);
}
if ((sockfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
{
perror("Socket nelze otevrit");
exit(-1);
}
bzero(&my_addr, sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(PORT_NUM);
std::cout << PORT_NUM << " PORT NUM" << std::endl;
if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr)) == -1)
{
perror("Chyba v bind");
close(sockfd); exit(1);
}
if (listen(sockfd, SOMAXCONN) == -1)
{
perror("Nelze provest listen");
close(sockfd); exit(1);
}
while (1)
{
rem_addr_length=sizeof(rem_addr);
c_sockfd = accept(sockfd, (struct sockaddr*) &rem_addr, &rem_addr_length);
if ( c_sockfd == -1)
{
perror("Nelze accept");
close(sockfd); exit(1);
}
///FIRST MESSAGE
std::string ok = "200 LOGIN\r\n";
send(c_sockfd, &ok, sizeof(std::string), 0);
if ((mlen = recv(c_sockfd, buf, BUFFSIZE, 0)) == -1)
perror("Chyba pri cteni");
else{
while (mlen)
{
///---------- MAIN PART--------------
//This is where comunication is happening
inBuffer.push_back(buf);
//Parse the buffer, check the message and
auto messageToSend = checkResponse(parseBuffer(inBuffer.back()));
//get the second thing in the tuple
outBuffer.push_back(std::get<1>(messageToSend));
bzero(buf, sizeof(buf));
///---------- MAIN PART--------------
state++;
std::cout << state << " state num" << std::endl;
if (send(c_sockfd, &outBuffer.back(), sizeof( outBuffer.back() ), 0) == -1)
{
perror("Chyba pri zapisu");
break;
}else{
}
std::cout << inBuffer.back() << std::endl;
if ((mlen = recv(c_sockfd, buf, BUFFSIZE, 0)) == -1)
{
perror("Chyba pri cteni");
break;
}
}
close(c_sockfd);
}
}
}
The problem is with this:
std::vector<std::string> outBuffer;
and this:
send(c_sockfd, &outBuffer.back(), sizeof( outBuffer.back() ), 0)
You can't send std::string objects over the network. You must send the string it contains. Those are two very different things.
For a simple fix, do e.g.
send(c_sockfd, outBuffer.back().c_str(), outBuffer.back().length(), 0)
If you want to send the terminating null then add one to the length to send.
For more details, while implementations of std::string is allowed to optimize small strings to be contained inside the actual object, otherwise a std::string object is really nothing more than a size and a pointer to the actual string (implementations might have other members as well).
A pointer is unique to the currently running process on the host system. You can't transfer a pointer over the network. You can't even save a pointer to a file and then load it again and have it working in a new process (even if it's a process from the same program).
By sending the std::string object, all you're really sending is this pointer. So on the receiving side it have no idea what you're really sending and how it should treat that.

Using Boost asio to receive commands and execute them

I'm trying to make a boost server, which will receive commands and do certain things. Now I would like to create a function, that will receive a file and save it to a specific location. The problem is with serialization. I don't know how can I recognize a command in a stream in an efficint way. I tried with boost::asio::read_until. And actually my code works. First file is being sent and received perfectly. But I am getting an error (The file handle supplied is not valid) when client sends second file. I would be very grateful for every advice. Thanks in an advance!
bool Sync::start_server() {
boost::asio::streambuf request_buf;
std::istream request_stream(&request_buf);
boost::system::error_code error;
try {
tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), conf->def_port));
acceptor.accept(socket); //socket is a member of class Sync
while (true)
{
error.clear();
size_t siz = boost::asio::read_until(socket, request_buf, "\n\n");
std::cout << "request size:" << request_buf.size() << "\n";
string command;
string parameter;
size_t data_size = 0;
request_stream >> command;
request_stream >> parameter;
request_stream >> data_size;
request_buf.consume(siz);//And also this
//cut filename from path below
size_t pos = parameter.find_last_of('\\');
if (pos != std::string::npos)
parameter = parameter.substr(pos + 1);
//cut filename from path above
//command = "save";// constant until I make up other functions
//execute(command, parameter, data_size);
save(parameter,data_size);//parameter is filename
}
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
And function to save file to a hard drive:
bool Sync::save(string filename, size_t filesize) {
boost::array<char, 1024> buf;
cout << "filesize is" << filesize;
size_t data_size = 0;
boost::system::error_code error;
std::ofstream output_file(filename.c_str(), std::ios_base::binary);
if (!output_file)
{
std::cout << "failed to open " << filename << std::endl;
return __LINE__;
}
while (true) {
size_t len = socket.read_some(boost::asio::buffer(buf), error);
if (len>0)
output_file.write(buf.c_array(), (std::streamsize)len);
if (output_file.tellp() == (std::fstream::pos_type)(std::streamsize)filesize)
{
output_file.close();
buf.empty();
break; // file was received
}
if (error)
{
socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, error);
socket.close(error);
output_file.close();
buf.empty();
break;//an error occured
}
}
}
read_until might read beyond the delimiter (therefore request_buf.size() can be more than siz). This is a conceptual problem when you implement save because you read data_size bytes from the socket, which ignores any data already in request_buf
These things are code smells:
if (output_file.tellp() == (std::fstream::pos_type)(std::streamsize)filesize) {
(never use C-style casts). And
return __LINE__; // huh? just `true` then
And
buf.empty();
(That has no effect whatsoever).
I present here three versions:
First Cleanup
Simplify (using tcp::iostream)
Simplify! (assuming more things about the request format)
First Cleanup
Here's a reasonable cleanup:
Live On Coliru
#include <boost/asio.hpp>
#include <boost/array.hpp>
#include <iostream>
#include <fstream>
namespace ba = boost::asio;
using ba::ip::tcp;
struct Conf {
int def_port = 6767;
} s_config;
struct Request {
std::string command;
std::string parameter;
std::size_t data_size = 0;
std::string get_filename() const {
// cut filename from path - TODO use boost::filesystem::path instead
return parameter.substr(parameter.find_last_of('\\') + 1);
}
friend std::istream& operator>>(std::istream& is, Request& req) {
return is >> req.command >> req.parameter >> req.data_size;
}
};
struct Sync {
bool start_server();
bool save(Request const& req, boost::asio::streambuf& request_buf);
ba::io_service& io_service;
tcp::socket socket{ io_service };
Conf const *conf = &s_config;
};
bool Sync::start_server() {
boost::asio::streambuf request_buf;
boost::system::error_code error;
try {
tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), conf->def_port));
acceptor.accept(socket); // socket is a member of class Sync
while (true) {
error.clear();
std::string req_txt;
{
char const* delim = "\n\n";
size_t siz = boost::asio::read_until(socket, request_buf, delim, error);
// correct for actual request siz
auto b = buffers_begin(request_buf.data()),
e = buffers_end(request_buf.data());
auto where = std::search(b, e, delim, delim+strlen(delim));
siz = where==e
? std::distance(b,e)
: std::distance(b,where)+strlen(delim);
std::copy_n(b, siz, back_inserter(req_txt));
request_buf.consume(siz); // consume only the request text bits from the buffer
}
std::cout << "request size:" << req_txt.size() << "\n";
std::cout << "Request text: '" << req_txt << "'\n";
Request req;
{
std::istringstream request_stream(req_txt);
request_stream.exceptions(std::ios::failbit);
request_stream >> req;
}
save(req, request_buf); // parameter is filename
}
} catch (std::exception &e) {
std::cerr << "Error parsing request: " << e.what() << std::endl;
}
return false;
}
bool Sync::save(Request const& req, boost::asio::streambuf& request_buf) {
auto filesize = req.data_size;
std::cout << "filesize is: " << filesize << "\n";
{
std::ofstream output_file(req.get_filename(), std::ios::binary);
if (!output_file) {
std::cout << "failed to open " << req.get_filename() << std::endl;
return true;
}
// deplete request_buf
if (request_buf.size()) {
if (request_buf.size() < filesize)
{
filesize -= request_buf.size();
output_file << &request_buf;
}
else {
// copy only filesize already available bytes
std::copy_n(std::istreambuf_iterator<char>(&request_buf), filesize,
std::ostreambuf_iterator<char>(output_file));
filesize = 0;
}
}
while (filesize) {
boost::array<char, 1024> buf;
boost::system::error_code error;
std::streamsize len = socket.read_some(boost::asio::buffer(buf), error);
if (len > 0)
{
output_file.write(buf.c_array(), len);
filesize -= len;
}
if (error) {
socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, error); // ignore error
socket.close(error);
break; // an error occured
}
}
} // closes output_file
return false;
}
int main() {
ba::io_service svc;
Sync s{svc};
s.start_server();
svc.run();
}
Prints with a client like echo -ne "save test.txt 12\n\nHello world\n" | netcat 127.0.0.1 6767:
request size:18
Request text: 'save test.txt 12
'
filesize is: 12
request size:1
Request text: '
'
Error parsing request: basic_ios::clear: iostream error
SIMPLIFY
However, since everything is synchronous, why not just use tcp::iostream socket;. That would make start_server look like this:
tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), conf->def_port));
acceptor.accept(*socket.rdbuf());
while (socket) {
std::string req_txt, line;
while (getline(socket, line) && !line.empty()) {
req_txt += line + "\n";
}
std::cout << "request size:" << req_txt.size() << "\n";
std::cout << "Request text: '" << req_txt << "'\n";
Request req;
if (std::istringstream(req_txt) >> req)
save(req);
}
And save even simpler:
void Sync::save(Request const& req) {
char buf[1024];
size_t remain = req.data_size, n = 0;
for (std::ofstream of(req.get_filename(), std::ios::binary);
socket.read(buf, std::min(sizeof(buf), remain)), (n = socket.gcount());
remain -= n)
{
if (!of.write(buf, n))
break;
}
}
See it Live On Coliru
When tested with
for f in test{a..z}.txt; do (echo -ne "save $f 12\n\nHello world\n"); done | netcat 127.0.0.1 6767
that prints:
request size:18
Request text: 'save testa.txt 12
'
request size:18
Request text: 'save testb.txt 12
'
[... snip ...]
request size:18
Request text: 'save testz.txt 12
'
request size:0
Request text: ''
Even Simpler
If you know that the request is a single line, or whitespace is not significant:
struct Sync {
void run_server();
void save(Request const& req);
private:
Conf const *conf = &s_config;
tcp::iostream socket;
};
void Sync::run_server() {
ba::io_service io_service;
tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), conf->def_port));
acceptor.accept(*socket.rdbuf());
for (Request req; socket >> std::noskipws >> req; std::cout << req << " handled\n")
save(req);
}
void Sync::save(Request const& req) {
char buf[1024];
size_t remain = req.data_size, n = 0;
for (std::ofstream of(req.get_filename(), std::ios::binary);
socket.read(buf, std::min(sizeof(buf), remain)), (n = socket.gcount());
remain -= n)
{
if (!of.write(buf, n)) break;
}
}
int main() {
Sync().run_server();
}
That's the entire program in ~33 lines of code. See it Live On Coliru, printing:
Request {"save" "testa.txt"} handled
Request {"save" "testb.txt"} handled
Request {"save" "testc.txt"} handled
[... snip ...]
Request {"save" "testy.txt"} handled
Request {"save" "testz.txt"} handled

C++ libcurl seg fault in write callback function

I'm trying to get something done quick and dirty. I saw another SO question and tried to reuse the code. I'm hitting a couple rest services (not multithreaded) that return json and when the CURLOPT_WRITEFUNCTION is called it throws a seg fault. I'm still trying to grasp all the c++ concepts so it's been pretty difficult diagnosing.
Here's what I see
static std::string *DownloadedResponse;
static size_t writer(char *data, size_t size, size_t nmemb, std::string *buffer_in)
{
cout << "In writer callback" << endl;
// Is there anything in the buffer?
if (buffer_in != NULL)
{
cout << "Buffer not null" << endl;
// Append the data to the buffer
buffer_in->append(data, size * nmemb);
cout <<" Buffer appended, seting response" << endl;
DownloadedResponse = buffer_in;
cout << "Set downloadedResponse" << endl;
return size * nmemb;
}
return 0;
}
std::string downloadJSON(std::string URL)
{
CURL *curl;
CURLcode res;
struct curl_slist *headers=NULL; // init to NULL is important
std::ostringstream oss;
curl_slist_append(headers, "Accept: application/json");
curl_slist_append( headers, "Content-Type: application/json");
curl_slist_append( headers, "charsets: utf-8");
curl = curl_easy_init();
if (curl)
{
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_URL, URL.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPGET,1);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,writer); // I comment this to display response in stdout.
cout << "calling easy_perform" << endl;
res = curl_easy_perform(curl);
cout << "call made.." << endl;
if (CURLE_OK == res)
{
char *ct;
res = curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &ct);
if((CURLE_OK == res) && ct)
{
cout << "returning downloaded resposne" << endl;
return *DownloadedResponse;
}
}
else
{
cout << "CURLCode: " << res << endl;
}
}
cout << "Returning null" << endl;
return NULL;
}
Output
$ ./test-rest
calling easy_perform
In writer callback
Buffer not nullSegmentation fault (core dumped)
How am I improperly using the string in the writer callback function?
You forgot to pass in a string pointer/reference with CURLOPT_WRITEDATA.