Showing posts with label libcurl. Show all posts
Showing posts with label libcurl. Show all posts

Friday, July 13, 2018

C++ Libcurl causing write access violation in readfunction callback on larger content

Leave a Comment

I am working on a C++ project where I am using libcurl to send an email over SMTP. The code is pretty much working for small content, however, on larger emails, its throwing a write access violation and I can't see any reason why.

Below is how I am using the curl function to send mail:

curl = curl_easy_init();         //curl_easy_setopt(curl, CURLOPT_FORBID_REUSE, 1);         if (curl)         {             if (this->useVerboseOutput)             {                 curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);             }             curl_easy_setopt(curl, CURLOPT_URL, smtpAddress.c_str());               if (this->useTLS)             {                 curl_easy_setopt(curl, CURLOPT_USE_SSL, (long)CURLUSESSL_ALL);             }             if (this->useAuthentication)             {                 if (this->username.empty() || this->password.empty())                 {                     throw logic_error("SMTP username or password has not been set but authentication is enabled");                 }                 curl_easy_setopt(curl, CURLOPT_USERNAME, this->username.c_str());                 curl_easy_setopt(curl, CURLOPT_PASSWORD, this->password.c_str());             }              curl_easy_setopt(curl, CURLOPT_MAIL_FROM, this->fromAddress.c_str());             curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients);             curl_easy_setopt(curl, CURLOPT_READDATA, this);             curl_easy_setopt(curl, CURLOPT_READFUNCTION, &EmailSender::invoke_write_data);             curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);              //Send the message             res = curl_easy_perform(curl); 

Below is the read function call back

size_t EmailSender::invoke_write_data(void *data, size_t size, size_t nmemb, void* pInstance) {     return ((EmailSender*)pInstance)->payload_source(data, size, nmemb); }  size_t EmailSender::payload_source(void *ptr, size_t size, size_t nmemb) {     //struct upload_status *upload_ctx = (struct upload_status*)userp;     const char *data;      if ((size == 0) || (nmemb == 0) || ((size*nmemb) < 1)) {         return 0;     }      if (this->upload_ctx.lines_read < this->lineArray.size())     {         data = this->lineArray.at(this->upload_ctx.lines_read).c_str();     }     else     {         return 0;     }      if (data) {         size_t len = strlen(data);         memcpy(ptr, data, len);         this->upload_ctx.lines_read++;          return len;     }      return 0; } 

Its crashing on the line this->upload_ctx.lines_read++; after the 5th call (there are 6 lines in the vector lineArray and upload_ctx->lines_read is 5.

The full error message is:

Exception thrown at 0x00007FFF4E8F16D7 (vcruntime140d.dll) in myapp.exe: 0xC0000005: Access violation writing location 0x00000205CC8AC000. 

1 Answers

Answers 1

According to the documentation of CURLOPT_READFUNCTION:

SYNOPSIS

#include <curl/curl.h> size_t read_callback(char *buffer, size_t size, size_t nitems, void *instream); CURLcode curl_easy_setopt(CURL *handle, CURLOPT_READFUNCTION, read_callback); 

DESCRIPTION

Pass a pointer to your callback function, as the prototype shows above.

This callback function gets called by libcurl as soon as it needs to read data in order to send it to the peer - like if you ask it to upload or post data to the server. The data area pointed at by the pointer buffer should be filled up with at most size multiplied with nitems number of bytes by your function.

You wrote:

size_t len = strlen(data); memcpy(ptr, data, len); 

Since len only depends on your data to send, and since you do not check it is less than size*nitems (nmemb for you), you might write out of the buffer allocated by libcurl, hence invoke undefined behavior.

Since you work by line but libcurl works by byte, you will need to rework your application to keep track of partially written lines, or drop the notion of line altogether.

Read More

Saturday, June 25, 2016

certificate problems trying to send email with libcurl

Leave a Comment

This is my libcurl code. I am trying to send email to my own email domain in linux.

This is my sample libcurl code.

curl_easy_setopt(curl, CURLOPT_USERNAME, "username@mydomain.com");     curl_easy_setopt(curl, CURLOPT_PASSWORD, "mypassword");     curl_easy_setopt(curl, CURLOPT_URL, "smtp://mail.mydomain.com:25");     curl_easy_setopt(curl, CURLOPT_USE_SSL, (long)CURLUSESSL_ALL);     curl_easy_setopt(curl, CURLOPT_MAIL_FROM, FROM);     recipients = curl_slist_append(recipients, TO);     curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients);     curl_easy_setopt(curl, CURLOPT_INFILESIZE, file_size);     curl_easy_setopt(curl, CURLOPT_READFUNCTION, fileBuf_source);     curl_easy_setopt(curl, CURLOPT_READDATA, &file_upload_ctx);     curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);     curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); //Dont display Curl Connection data Change 1L to 0      res = curl_easy_perform(curl); 

When I run this code, I am getting the below error.

* Rebuilt URL to: smtp://mail.mydomain.com:25/ * Hostname was NOT found in DNS cache *   Trying <My mail domain Ip address>... * Connected to mail.mydomain.com (<My mail domain Ip address>) port 25 (#0) < 220 mail.mydomain.com ESMTP > EHLO client6 < 250-mail.mydomain.com < 250-PIPELINING < 250-SIZE 20480000 < 250-VRFY < 250-ETRN < 250-STARTTLS < 250-AUTH PLAIN LOGIN < 250-ENHANCEDSTATUSCODES < 250-8BITMIME < 250 DSN > STARTTLS < 220 2.0.0 Ready to start TLS * successfully set certificate verify locations: *   CAfile: none   CApath: /etc/ssl/certs * SSL certificate problem: self signed certificate * Closing connection 0 curl_easy_perform() failed: Peer certificate cannot be authenticated with given CA certificates 

1 Answers

Answers 1

Your issue is that your server is providing a self-signed certificate so curl is not able to verify its provenance. You have several options:

  • The best option is to get a server certificate that is signed by a well-known certificate authority. Some CAs will issue a certificate you can use for free; search for "free ssl certificate". You will need to be able to provide some proof that you control the domain.

  • You can install your self-signed certificate to the list of trusted CAs on the computer(s) that run your libcurl code. The procedure to do this depends on your OS (even different distributions of Linux may do this differently). This link is a decent starting point for Linux.

  • Your program can tell libcurl to verify with the self-signed certificate. See Adding self-signed SSL certificate for libcurl.

  • You can create your own certificate authority and use either of the previous two approaches. The advantage of this over self-signing is it decouples the signing and the signed certificates. If you want to change the server certificate (e.g. if it expires or the host name changes) you don't necessarily need to reconfigure all the clients.

  • For completeness, you could disable verification by setting CURLOPT_SSL_VERIFYPEER to 0. This is highly discouraged, however, as it makes the access insecure. You should only do this for testing purposes, or in the rare case that the network between client and server is guaranteed to be secure.

Read More

Wednesday, April 27, 2016

Curl command line works, C++ curl library does not

Leave a Comment

I'm trying to do some request via curl library of C++. I can successfully do my request and get the correct response via command line, but I cannot get the correct response via C++ code. My command line command looks like this

curl -X POST -H 'Accept: application/json' -H 'Content-Type: application/json' -H 'Authorization: <some_hash_value>' -k <my_full_url> -data '<my_json_string>' 

That works fine. Now I try to do the same request in C++ code. My code looks like this

void performRequest(const std::string& json, const void* userData, CallbackFunction callback) {     struct curl_slist* headers = NULL;      headers = curl_slist_append(headers, "Accept: application/json");     headers = curl_slist_append(headers, "Content-Type: application/json");     headers = curl_slist_append(headers, (std::string("Authorization: ") + m_authorization).c_str());      CURL* curlHandle = curl_easy_init();     if (!curlHandle)     {         std::cerr << "Curl handler initialization failed";     }      curl_easy_setopt(curlHandle, CURLOPT_NOSIGNAL, 1);     curl_easy_setopt(curlHandle, CURLOPT_HTTPHEADER, headers);      // specify target URL, and note that this URL should include a file name, not only a directory      curl_easy_setopt(curlHandle, CURLOPT_URL, m_url.c_str());      // enable uploading     curl_easy_setopt(curlHandle, CURLOPT_UPLOAD, 1L);      // set HTTP method to POST     curl_easy_setopt(curlHandle, CURLOPT_CUSTOMREQUEST, "POST");      // set json data; I use EXACTLY the same string as in command line     curl_easy_setopt(curlHandle, CURLOPT_COPYPOSTFIELDS, json.c_str());      // set data size     curl_easy_setopt(curlHandle, CURLOPT_POSTFIELDSIZE_LARGE, json.size());      // set user data for getting it in response     curl_easy_setopt(curlHandle, CURLOPT_WRITEDATA, userData);    // pointer to a custom struct      // set callback function for getting response     curl_easy_setopt(curlHandle, CURLOPT_WRITEFUNCTION, callback);    // some callback      // send request     curl_easy_perform(curlHandle);      curl_easy_cleanup(curlHandle);     curl_slist_free_all(headers); } 

However, for some reason I get an error in the response from the server, from which I can assume that my code's request is not equivalent to command line's command. It seems that body is not sent. I cannot see my request Json body when I use CURLOPT_DEBUGFUNCTION for dumping debug info.

What is the problem here? What am I doing wrong? Any ideas?

3 Answers

Answers 1

Here is sample code that should work for you.

Notice that I:

  • Removed CURLOPT_UPLOAD as it does not seem as you are actually uploading something but rather just doing a simple POST.

  • Changed CURLOPT_CUSTOMREQUEST to CURLOPT_POST (not that it should matter), but I find it cleaner.

  • Reordered CURLOPT_POSTFIELDSIZE_LARGE and CURLOPT_COPYPOSTFIELDS

  • Removed the CURLOPT_WRITEDATA line for the sake of this sample code.

I have tested the following only by connecting to an instance of nc -l localhost 80

 static size_t callback(char *ptr, size_t size, size_t nmemb, void *userdata)  {    string s(ptr);    cout << s << endl;    return size * nmemb;  }    int main(int argc, char** argv)  {    string m_authorization("PWNED");    string m_url("http://localhost");    string m_json("{}");     curl_global_init(CURL_GLOBAL_ALL);     CURL* curlHandle = curl_easy_init();     struct curl_slist* headers = nullptr;    headers = curl_slist_append(headers, "Accept: application/json");    headers = curl_slist_append(headers, "Content-Type: application/json");    headers = curl_slist_append(headers, (std::string("Authorization: ") + m_authorization).c_str());     curl_easy_setopt(curlHandle, CURLOPT_NOSIGNAL, 1);    curl_easy_setopt(curlHandle, CURLOPT_HTTPHEADER, headers);     // specify target URL, and note that this URL should include a file name, not only a directory    curl_easy_setopt(curlHandle, CURLOPT_URL, m_url.c_str());     // <= You are not uploading anything actually, this is a simple POST with payload    // enable uploading    // curl_easy_setopt(curlHandle, CURLOPT_UPLOAD, 1L);     // set HTTP method to POST    curl_easy_setopt(curlHandle, CURLOPT_POST, 1L);     // set data size before copy    curl_easy_setopt(curlHandle, CURLOPT_POSTFIELDSIZE_LARGE, m_json.size());     // set json data; I use EXACTLY the same string as in command line    curl_easy_setopt(curlHandle, CURLOPT_COPYPOSTFIELDS, m_json.c_str());     // set user data for getting it in response    // curl_easy_setopt(curlHandle, CURLOPT_WRITEDATA, userData);    // pointer to a custom struct     // set callback function for getting response    curl_easy_setopt(curlHandle, CURLOPT_WRITEFUNCTION, callback);    // some callback     // send request    curl_easy_perform(curlHandle);     curl_slist_free_all(headers);     curl_easy_cleanup(curlHandle);    curl_global_cleanup();    return 0;  } 

Answers 2

In windows, you must init the winsock stuff with below function

curl_global_init(CURL_GLOBAL_ALL); 

Answers 3

The problem was solved tanks to Rocki's and drew010's comments.

  1. I have removed CURLOPT_CUSTOMREQUEST, CURLOPT_UPLOAD and CURLOPT_NOSIGNAL setting statements as there is no need of them.
  2. I have also removed the line for setting CURLOPT_POSTFIELDSIZE_LARGE, although it works fine if it is set before setting CURLOPT_COPYPOSTFIELDS. If the size has not been set prior to CURLOPT_COPYPOSTFIELDS, the data is assumed to be a zero terminated string; else the stored size informs the library about the byte count to copy. In any case, the size must not be changed after CURLOPT_COPYPOSTFIELDS, unless another CURLOPT_POSTFIELDS or CURLOPT_COPYPOSTFIELDS option is issued. (See: curl.haxx.se/libcurl/c/CURLOPT_COPYPOSTFIELDS.html)
Read More