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.
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 withnitems
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.
Short answer: I think you need to add content to the recipients variable.
You didn't supply enough information to examine your c++ implementation, so there's not much one can say about it. I translated your code back to C a bit and confirmed it is working as intended as in the cURL smtp-mail.c example.
soquestsmtp-mail.cpp
#include "pch.h"
enum optionuses : uint32_t
{
useVerboseOutput = 1 << 1, // 0x02
useTLS = 1 << 2, // 0x04
useAuthentication = 1 << 3, // 0x08
};
struct upload_status {
int lines_read;
};
typedef struct upload_status* pupload_status;
static size_t __cdecl invoke_write_data(void* buffer, size_t size, size_t nmemb, void* pInstance);
static size_t __cdecl payload_source(void* buffer, size_t size, size_t nmemb);
static size_t __cdecl read_callback(void* buffer, size_t size, size_t nitems, void* instream);
static const char* payload_text[] = {
"Date: Mon, 29 Nov 2010 21:54:29 +1100\r\n",
"To: <addressee@example.net>\r\n",
"From: <sender@example.org>\r\n",
"Cc: <info@example.org>\r\n",
"Message-ID: <dcd7cb36-11db-487a-9f3a-e652a9458efd@"
"rfcpedant.example.org>\r\n",
"Subject: SMTP example message\r\n",
"\r\n", /* empty line to divide headers from body, see RFC5322 */
"The body of the message starts here.\r\n",
"\r\n",
"It could be a lot of lines, could be MIME encoded, whatever.\r\n",
"Check RFC5322.\r\n",
nullptr
};
extern "C" int __cdecl
wmain(_In_ int argc,_In_reads_(argc) _Pre_z_ wchar_t** argv,_In_z_ wchar_t** envp)
{
argc;argv;envp;
CURL* curl = nullptr;
int res = CURLE_OK;
uint32_t options = useVerboseOutput|useTLS|useAuthentication;
std::string smtpAddress = "smtp://mail.example.com";
std::string fromAddress = "<sender@example.org>";
std::string toAddress = "<addressee@example.net>";
std::string ccAddress = "<info@example.org>";
std::string username = "sockerconny";
std::string password = "love_mom";
struct curl_slist* recipients = nullptr;
struct upload_status upload_ctx;
upload_ctx.lines_read = 0;
curl = curl_easy_init();
//curl_easy_setopt(curl, CURLOPT_FORBID_REUSE, 1);
if (curl)
{
if (options & useVerboseOutput)
{
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
}
curl_easy_setopt(curl, CURLOPT_URL, smtpAddress.c_str());
if (options & useTLS)
{
curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_ALL);
}
if (options & useAuthentication)
{
if (username.empty() || password.empty())
{
// throw std::logic_error("SMTP username or password has not been set but authentication is enabled");
}
curl_easy_setopt(curl, CURLOPT_USERNAME, username.c_str());
curl_easy_setopt(curl, CURLOPT_PASSWORD, password.c_str());
}
curl_easy_setopt(curl, CURLOPT_MAIL_FROM, fromAddress.c_str());
recipients = curl_slist_append(recipients, toAddress.c_str());
recipients = curl_slist_append(recipients, ccAddress.c_str());
curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients);
curl_easy_setopt(curl, CURLOPT_READDATA, &upload_ctx);
curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback);
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
//Send the message
res = curl_easy_perform(curl);
curl_slist_free_all(recipients);
curl_easy_cleanup(curl);
}
}
size_t __cdecl read_callback(void* buffer, size_t size, size_t nitems, void *instream)
{
pupload_status pupload_ctx = (pupload_status)instream;
const char* data = nullptr;
if((size == 0) || (nitems == 0) || ((size*nitems) < 1)) {
return 0;
}
data = payload_text[pupload_ctx->lines_read];
if(data) {
size_t len = strlen(data);
memcpy(buffer, data, len);
pupload_ctx->lines_read++;
return len;
}
return 0;
}
pch.h
// pch.h - precompiled header with standard includes and definitions
#pragma once
#define STRICT
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#define DISABLE_WARNING_PUSH(x) \
__pragma(warning(push)); __pragma(warning(disable: x))
#define DISABLE_WARNING_POP __pragma(warning(pop))
#define _WIN32_WINNT 0x0601 // minimum Windows 7
#include <winsdkver.h>
#include <sdkddkver.h>
#ifndef WINAPI_FAMILY
#define WINAPI_FAMILY WINAPI_FAMILY_DESKTOP_APP
#endif
// disable useless MSVC warnings when compiling with -Wall
#pragma warning(disable: 4514 4710 4711)
// comment out for diagnostic messages, usually safe to ignore
#pragma warning(disable: 4625 4626 4820)
// temporary disable warnings when compiling with -Wall
DISABLE_WARNING_PUSH(4191 4350 4365 4774 4571 4640 5026 5027 5039)
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#include <stdexcept>
#include <windows.h>
#include <ws2tcpip.h>
DISABLE_WARNING_POP
#define CURL_STATICLIB
#define USE_LIBSSH2
#define HAVE_LIBSSH2_H
#define USE_SCHANNEL
#define USE_WINDOWS_SSPI
#define USE_WIN32_IDN
#define WANT_IDN_PROTOTYPES
#include <curl/curl.h>
User contributions licensed under CC BY-SA 3.0