Using libcurl To Send Email from an SBC
Sending Email from C Programs
by James
Introduction
Previously, we explored the use of SSMTP to send emails from a PhidgetSBC. This has the glaring issue of being a command line utility, which requires a system call in order to be run from a user application. This raises many red flags for any programmer, having been taught since the beginning that system calls are bad practice (which they are, more on that later).
Thus, for a cleaner approach, we will set up a system to send email alerts from an SBC using the libcurl C library on linux.
Hardware
Only a Phidget SBC is required for this project.
Software
The Pitfalls of System Calls
What are system calls?
System calls are a type of command in most operating systems that take the form system(command_text_here);
Why not to use them:
Every programmer has heard the resounding claims that system calls are bad practice and should be avoided at all costs. Why is that?
For starters, the argument that they are not compatible across all platforms generally comes up. While this is true for cross-platform applications, this doesn't seem to apply for programs designed explicitly to run on a single system, like those for the linux-based PhidgetSBC. So why else could it be?
The next issue commonly raised against system calls is that they are inefficient and slow. This might also be shrugged off by one not concerned with the efficiency of a one-off program to send simple warnings.
The major problem with using system calls arises when a program constructs a command line string to use in a system call. In this case, the inputs to said function may be manipulated in a Shell Command Injection attack, which can then be used to run malicious code in place of the expected code. This is undesirable for obvious reasons, and so we must find a better way.
libcurl
libcurl is a free, easy to use, portable library that deals with many file transfer applications. Here we will look at its ability to quickly and easily send emails.
Setting up libcurl
To use libcurl, first install it with the command
sudo apt-get install libcurl4-gnutls-dev
We recommend the gnutls
variant of libcurl for added stability when used in conjuction with
the phidget22 libraries.
Once installed, the library should be tested. We used the simple example found here as a base. Some modifications are required to get the program up and running smoothly on a PhidgetSBC:
-
For gmail setup, use
smtp://smtp.gmail.com:587
as the url for the mailserver. - The login information for the gmail account used is required for the program to sign in to the account and send the message.
curl_easy_setopt(curl, CURLOPT_USERNAME, "senderEmail@gmail.com");
curl_easy_setopt(curl, CURLOPT_PASSWORD, "senderPassword");
CURLOPT_USE_SSL
must also be set as follows to successfully connect to the gmail server used:
curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_ALL);
Message-ID
field was removed the the payload_text
string, as it is not
used in this application.
This leaves the following code, which should work simply by plugging in TO, FROM, and CC addresses.
/*
* SMTP example showing how to send e-mails
*
*/
#include <stdio.h>
#include <string.h>
#include <curl/curl.h>
/* This is a simple example showing how to send mail using libcurl's SMTP
* capabilities. For an example of using the multi interface please see
* smtp-multi.c.
*
* Note that this example requires libcurl 7.20.0 or above.
*/
#define FROM "senderEmail@gmail.com"
#define TO "toAddress@domain.com"
#define CC "ccAddress@domain.com"
static const char *payload_text[] = {
"Date: Mon, 29 Nov 2010 21:54:29 +1100\r\n",
"To: " TO "\r\n",
"From: " FROM "(Example User)\r\n",
"Cc: " CC "(Another example User)\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",
NULL
};
struct upload_status {
int lines_read;
};
static size_t payload_source(void *ptr, size_t size, size_t nmemb, void *userp)
{
struct upload_status *upload_ctx = (struct upload_status *)userp;
const char *data;
if((size == 0) || (nmemb == 0) || ((size*nmemb) < 1)) {
return 0;
}
data = payload_text[upload_ctx->lines_read];
if(data) {
size_t len = strlen(data);
memcpy(ptr, data, len);
upload_ctx->lines_read++;
return len;
}
return 0;
}
int main(void)
{
CURL *curl;
CURLcode res = CURLE_OK;
struct curl_slist *recipients = NULL;
struct upload_status upload_ctx;
upload_ctx.lines_read = 0;
curl = curl_easy_init();
if(curl) {
/* This is the URL for your mailserver */
curl_easy_setopt(curl, CURLOPT_URL, "smtp://smtp.gmail.com:587");
/* Note that this option isn't strictly required, omitting it will result
* in libcurl sending the MAIL FROM command with empty sender data. All
* autoresponses should have an empty reverse-path, and should be directed
* to the address in the reverse-path which triggered them. Otherwise,
* they could cause an endless loop. See RFC 5321 Section 4.5.5 for more
* details.
*/
curl_easy_setopt(curl, CURLOPT_MAIL_FROM, FROM);
/* Add two recipients, in this particular case they correspond to the
* To: and Cc: addressees in the header, but they could be any kind of
* recipient. */
recipients = curl_slist_append(recipients, TO);
recipients = curl_slist_append(recipients, CC);
curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients);
/* We're using a callback function to specify the payload (the headers and
* body of the message). You could just use the CURLOPT_READDATA option to
* specify a FILE pointer to read from. */
curl_easy_setopt(curl, CURLOPT_READFUNCTION, payload_source);
curl_easy_setopt(curl, CURLOPT_READDATA, &upload_ctx);
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(curl, CURLOPT_USERNAME, "senderEmail@gmail.com");
curl_easy_setopt(curl, CURLOPT_PASSWORD, "senderPassword");
curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_ALL);
/* Send the message */
res = curl_easy_perform(curl);
/* Check for errors */
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
/* Free the list of recipients */
curl_slist_free_all(recipients);
/* curl won't send the QUIT command until you call cleanup, so you should
* be able to re-use this connection for additional messages (setting
* CURLOPT_MAIL_FROM and CURLOPT_MAIL_RCPT as required, and calling
* curl_easy_perform() again. It may not be a good idea to keep the
* connection open for a very long time though (more than a few minutes
* may result in the server timing out the connection), and you do want to
* clean up in the end.
*/
curl_easy_cleanup(curl);
}
return (int)res;
}
Note that your password is stored in this file as plaintext, so appropriate precautions should be taken to keep the acount on the SBC secure.
This code can then be compiled using
gcc /path/to/curlExample.c -lcurl -ooutputFilename
The Gmail server has recently implemented new security features that will block applications it deems insecure. This includes simple applications like libcurl. As a result, you will either have to allow less secure apps on the account to be used, or set an app password if you are using 2-Step-Verificaiton.
Using libcurl with other programs
To use this functionality from other programs, the main function may be renamed from "main" to something else, and called like any other C function.
Using libcurl with Files
The basic example works well for a single, static message being sent to pre-defined email addresses, which may be enough for certain applications. However, it can be made more versatile through the use of files.
A list of email addresses can be stored and edited in a file, and later read into the program with standard
C funcitons. The addresses can then be appended to a libcurl recipient list by calling
curl_slist_append(recipients, emailAddress);
In addition a saved message can be send by setting up CURLTOPT_READDATA
with a file pointer,
and omitting call to change CURLOPT_READFUNTION
. Then a file with contents similar to
those in the payload_text
string above (or the SSMTP email files) can be sent, to allow
for more versatile messages.
Thus, to send an email to arbirtary recipients with an arbitrary message, the code can be modified to following:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <curl/curl.h>
/*
* Note that this example requires libcurl 7.20.0 or above.
*/
#define FROM "senderEmail@gmail.com"
int sendMessage(struct curl_slist *recipients, FILE *f) {
CURL *curl;
CURLcode res = CURLE_OK;
char errbuf[CURL_ERROR_SIZE];
curl = curl_easy_init();
if(curl) {
/* This is the URL for your mailserver */
curl_easy_setopt(curl, CURLOPT_URL, "smtp://smtp.gmail.com:587");
curl_easy_setopt(curl, CURLOPT_MAIL_FROM, FROM);
curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients);
curl_easy_setopt(curl, CURLOPT_USERNAME, "senderEmail@gmail.com");
curl_easy_setopt(curl, CURLOPT_PASSWORD, "senderPassword");
curl_easy_setopt(curl, CURLOPT_READDATA, f);
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_ALL);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
/* Send the message */
res = curl_easy_perform(curl);
/* Check for errors */
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %d - %s\n%s\n",
res, curl_easy_strerror(res), errbuf);
curl_easy_cleanup(curl);
}
return (int)res;
}
void parseRecipients(char* emailAddrFileName,
struct curl_slist **recipientsPtr) {
FILE *f = fopen(emailAddrFileName, "r");
char emailAddress[65];
if (f == NULL) {
fprintf(stderr, "Error opening file!\n");
exit(1);
}
//load email addresses
for (int i = 0; ; i++) {
if (fgets(emailAddress, 65, f) == NULL)
break;
emailAddress[strlen(emailAddress) - 1] = 0; //null terminate string
if (strlen(emailAddress) == 0)
break;
*recipientsPtr = curl_slist_append(*recipientsPtr, emailAddress);
}
fclose(f);
}
int sendEmail(char* emailAddrFileName, char *emailFileName) {
struct curl_slist *recipients = NULL;
int ret;
FILE *f;
parseRecipients(emailAddrFileName, &recipients);
//read in the email contents and send the message
f = fopen(emailFile, "rb");
ret = sendMessage(recipients, f);
fclose(f);
/* Free the list of recipients */
curl_slist_free_all(recipients);
return ret;
}
To send an email using this code, simply call sendEmail(emailAddrFileName, emailFileName)
with filenames
corresponding to a file with a list of recipients (aside), and the email contents file, respectively.
This example is intended as a basic starting point to get libcurl working in a small application, and is in no way intended for direct commercial use. Further error and security checks should be performed within full scale applications.
Conclusion
Sending email messages can be done quickly and easily using libcurl, in a way that is more secure than methods using system calls like SSMTP.