13
\$\begingroup\$

Recently I've been fiddling with the idea of sending a text message using C. Looking at a few options, I thought that using libcurl to send an SMTP email using TLS for some security would be the best and easiest option. Here's what I would like reviewed:

  • Method: Is there a better/easier way I could be sending an SMS?
  • Security: Is my current security enough? How could I add more security to make sure my authentication details aren't being snooped on?
  • SMS Format: Is there a way I could format my payload to look more like what it would if I sent it off using a normal email client? The normal email client sends something that looks like the image on the left, with my first name being blacked out; the image on the right is what it looks like sent from my program with my email being censored.

       enter image description hereenter image description here

  • Conversations: With my program, a new conversation is generated each time an SMS is sent, with the numbers at the top (950-9 in this case) being randomly chosen anew each time. Could an improvement be made to the program so that each new SMS message stays in the same conversation?

#include <stdio.h>
#include <string.h>
#include <curl/curl.h>

typedef struct
{
    int lines_read;
} UploadStatus;

size_t payload_source(void *ptr, size_t size, size_t nmemb, void *userp)
{
    if((size == 0) || (nmemb == 0) || ((size * nmemb) < 1)) return 0;

    UploadStatus *upload = (UploadStatus*) userp;
    const char *payload[] =
    {
        "Subject: SMS Test\r\n",
        "\r\n", /* empty line to divide headers from body, see RFC5322 */
        "Test",
        NULL
    };
    const char *data = payload[upload->lines_read];

    if(data)
    {
        size_t len = strlen(data);
        memcpy(ptr, data, len);
        upload->lines_read++;
        return len;
    }
    return 0;
}

int main(void)
{
    CURLcode res = CURLE_OK;
    struct curl_slist *recipients = NULL;
    UploadStatus upload_ctx = {0};
    const char *from = "test@gmail.com";
    const char *to = "UrTxtEmail"; // see http://www.emailtextmessages.com/
    CURL *curl = curl_easy_init();

    if(curl)
    {
        // set username and password
        curl_easy_setopt(curl, CURLOPT_USERNAME, from);
        curl_easy_setopt(curl, CURLOPT_PASSWORD, "password");
        curl_easy_setopt(curl, CURLOPT_URL, "smtp://smtp.gmail.com:587/");
        // start with normal connection, and upgrade to TLS using STARTTLS
        curl_easy_setopt(curl, CURLOPT_USE_SSL, (long) CURLUSESSL_ALL);
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
        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_READFUNCTION, payload_source);
        curl_easy_setopt(curl, CURLOPT_READDATA, &upload_ctx);
        curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
        // useful for debugging encryped traffic
        curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);

        // send the message
        res = curl_easy_perform(curl);
        if(res != CURLE_OK) fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
        curl_slist_free_all(recipients);
        curl_easy_cleanup(curl);
    }

    return (int) res;
}
\$\endgroup\$
4
\$\begingroup\$

A few notes:

  • First off, your UploadStatus structure only contains one variable, which is somewhat useless IMO. Instead, I would make some sort of email structure that contains all the necessary parts of an email in order for your program to send it off. Here's a structure that I came up with to use:

     typedef struct
     {
         const char* to;
         const char* from;
         const char** email;
         size_t pos;
     } EmailData;
    

    You may note that I changed your lines_read to pos and made it a size_t since I doubt you will ever read a negative number of lines.

  • The payload_source() method looks to be your CURLOPT_READFUNCTION, but it doesn't have the right format. It should look like this prototype according to the documentation:

    size_t read_callback(char *buffer, size_t size, size_t nitems, void *instream);
    

    Also, the current name of your function doesn't really convey to me that it is a callback when reading your code. To aid readability, I would change it to something such as readCallback().

  • Right now you have your email hardcoded within your code... something I would definitely look to change to be more dynamic. I would suggest creating a sort of message constructing function that would fulfill such a purpose, store it in your EmailData struct, and then pass that structure to your callback function as the instream parameter.

  • To fix your formatting I would look at the original HTML email that is sent from a Gmail client and then dissect that. Here is an example of one that I sent off:

    MIME-Version: 1.0
    Received: by 10.37.194.194 with HTTP; Tue, 11 Aug 2015 18:42:25 -0700 (PDT)
    Date: Tue, 11 Aug 2015 20:42:25 -0500
    Delivered-To: person@gmail.com
    Message-ID: <CJYf78Vnxbma4wdV1mZuBXHPeEptOzyjH1vLMzLwnhoXsPw@mail.gmail.com>
    Subject: Gmail Test
    From: syb0rg <person@gmail.com>
    To: 5555555555@tmomail.net
    Content-Type: multipart/alternative; boundary=001a11c04f6ca3e68e051d1354cc
    
    --001a11c04f6ca3e68e051d1354cc
    Content-Type: text/plain; charset=UTF-8
    
    Body content
    
    --001a11c04f6ca3e68e051d1354cc
    Content-Type: text/html; charset=UTF-8
    
    <div dir="ltr">Body content</div>
    
    --001a11c04f6ca3e68e051d1354cc--
    

    The actual dissection of each line of this email is done in my final code with comments


Final Code

Please note that this code leaks memory and doesn't contain a unique ID generation method.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <curl/curl.h>

typedef struct
{
    const char* to;
    const char* from;
    const char** email;
    size_t pos;
} EmailData;

EmailData* initEmailData(EmailData* data, const char* to, const char* from, const char* subject, const char* input)
{
    data->to = to;
    data->from = from;

    // the added numbers to the length of the string are the pre-computed
    // lengths of the strings to be concatenated + 1 (for termination char)
    size_t htmlSize = strlen(input) + 24;
    char *html = malloc(htmlSize);
    snprintf(html, htmlSize, "%s%s%s", "<div dir=\"ltr\">", input, "</div>\r\n");

    size_t headerSize = strlen(subject) + 12;
    char *header = malloc(1);
    snprintf(header, headerSize, "%s%s%s", "Subject: ", subject, "\r\n");

    size_t receiverSize = strlen(to) + 7;
    char *receiver = malloc(receiverSize);
    snprintf(receiver, receiverSize, "%s%s%s", "To: ", to, "\r\n");

    size_t senderSize = strlen(from) + 9;
    char *sender = malloc(senderSize);
    snprintf(sender, senderSize, "%s%s%s", "From: ", from, "\r\n");

    size_t timeSize = 40;
    char* timeString = malloc(timeSize);
    time_t rawtime = 0;
    time(&rawtime);
    struct tm *timeinfo = localtime(&rawtime);
    strftime (timeString, timeSize, "Date: %a, %d %b %Y %T %z\r\n", timeinfo);

    const char **payload = malloc(sizeof(char*) * 17);
    const char* setup[] =
    {
        // extends the format of email to support more data (vidoes, audio, etc)
        "MIME-Version: 1.0\r\n",
        // when the email was sent
        timeString,
        // who the email is to
        // TODO: multiple recipient support
        receiver,
        // who the email is from
        sender,
        // https://en.wikipedia.org/wiki/Message-ID
        //"Message-ID: <CJYf78Vnxbma4dV1mZuBpXPeEptOzyjHvLMBzLwnhoXsPw@mail.gmail.com>\r\n"
        // email subject, optional
        header,
        // describe the data contained in the body
        // https://tools.ietf.org/html/rfc2046#section-5.1.1
        "Content-Type: multipart/alternative; boundary=border\r\n",
        // empty line to divide headers from body, see RFC5322
        "\r\n",
        // all boundaries must start with two hyphens
        "--border\r\n",
        // let recipient interpret displaying text, send plain text & HTML
        // specify the plain text encoding
        "Content-Type: text/plain; charset=UTF-8\r\n",
        "\r\n",
        input,
        "\r\n",
        "--border\r\n",
        // specify the HTML encoding
        "Content-Type: text/html; charset=UTF-8\r\n",
        "\r\n",
        html,
        "\r\n",
        // final boundary ends with two hyphens as well
        "--border--\r\n",
        NULL
    };
    for (int i = 0; setup[i]; i++)
    {
        *(payload + i) = setup[i];
    }
    data->email = payload;
    data->pos = 0;
    return data;
}

size_t readCallback(char *ptr, size_t size, size_t nmemb, void *userp)
{
    if((size == 0) || (nmemb == 0) || ((size * nmemb) < 1)) return 0;

    EmailData *upload = (EmailData*) userp;
    const char *email = upload->email[upload->pos];

    if(email)
    {
        size_t len = strlen(email);
        if (len > size * nmemb) return CURL_READFUNC_ABORT;
        memcpy(ptr, email, len);
        upload->pos++;
        return len;
    }
    return 0;
}

int main(void)
{
    CURLcode res = CURLE_OK;
    struct curl_slist *recipients = NULL;
    const char *from = "person@gmail.com";
    const char *password = "password";
    const char *to = "5555555555@tmomail.net"; // see http://www.emailtextmessages.com/
    EmailData data = {0};
    initEmailData(&data, to, from, "Test Subject", "Test content");
    CURL *curl = curl_easy_init();

    if (curl)
    {
        // set username and password
        curl_easy_setopt(curl, CURLOPT_USERNAME, from);
        curl_easy_setopt(curl, CURLOPT_PASSWORD, password);
        curl_easy_setopt(curl, CURLOPT_URL, "smtp://smtp.gmail.com:587/");
        // start with normal connection, and upgrade to TLS using STARTTLS
        curl_easy_setopt(curl, CURLOPT_USE_SSL, (long) CURLUSESSL_ALL);
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
        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_READFUNCTION, readCallback);
        curl_easy_setopt(curl, CURLOPT_READDATA, &data);
        curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
        // useful for debugging encryped traffic
        curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);

        // send the message
        res = curl_easy_perform(curl);
        if(res != CURLE_OK) fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
        curl_slist_free_all(recipients);
        curl_easy_cleanup(curl);
    }

    return (int) res;
}
\$\endgroup\$
  • \$\begingroup\$ Is there any good reason to send a multipart/alternative rather than a straightforward text/plain? That just looks like a waste of bandwidth. You've fixed the important bug (Subject header needs to be before the blank line), though you don't really describe it. \$\endgroup\$ – Toby Speight Jul 17 '18 at 8:27
3
\$\begingroup\$

Bounds check

I can't comment on the SMS aspects of the code. But you are missing a bounds check in payload_source(). The buffer pointed at by ptr only holds size * nmemb bytes. So I think you should add a check such as:

if(data)
{
    size_t len = strlen(data);
    if (len > size * nmemb)
        return CURL_READFUNC_ABORT;
    memcpy(ptr, data, len);
    upload->lines_read++;
    return len;
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.