C, libCurl

Read email using IMAP, Gmail, C and libcurl

Requirements
* A Gmail account (Use a dedicated account! Do not use your personal one!)
* Turn on “Access for less secure apps” under the security settings of the account. less secure apps
* You may also have to enable IMAP in the account settings.

The following code snippets is from Post-recon project. This project is a work in progress.

Previous post: Send email using Gmail, C and libcurl, Part 3.

You can check Github for the full source code, here I will just point out the most interesting parts.

Collect unseen emails ids

{...}
 
int LibCurl::GetNewEmailsIDs(int **ids, const char *username, const char *password, const char *userAgent, long verbose)
{
    if (username == NULL || password == NULL || userAgent == NULL) return S_FALSE;
 
    CURL *curl;
    CURLcode res = CURLE_OK;
    struct data_size download_ctx;
    int total = -1;
 
    download_ctx.data = (char*)Common::hAlloc(1);
    download_ctx.size = 0;
 
    curl = curl_easy_init();
    if (curl) {
 
        curl_easy_setopt(curl, CURLOPT_USERNAME, username);
        curl_easy_setopt(curl, CURLOPT_PASSWORD, password);
        curl_easy_setopt(curl, CURLOPT_URL, "imaps://imap.gmail.com:993/INBOX");
        curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "SEARCH UNSEEN");
        curl_easy_setopt(curl, CURLOPT_VERBOSE, verbose); //debug, turn it off on production
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _write_function_callback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &download_ctx);
        curl_easy_setopt(curl, CURLOPT_USERAGENT, userAgent);
 
        res = curl_easy_perform(curl);
 
        if (res == CURLE_OK) {
            total = _getNewEmailsIds(ids, download_ctx.data);
        }
 
        curl_easy_cleanup(curl);
    }
 
    //Common::hFree(download_ctx.data);
 
    return total;
}
 
{...}

Read email

 
{...}
 
HRESULT LibCurl::ReceiveEmail(int uid, const char *username, const char *password, const char *userAgent, long verbose)
{
    if (username == NULL || password == NULL || userAgent == NULL) return S_FALSE;
 
    CURL *curl;
    CURLcode res = CURLE_OK;
    struct data_size download_ctx;
    char *url = 0;
    MimeMessage *mm;
 
    download_ctx.data = (char*)Common::hAlloc(1);
    download_ctx.size = 0;
 
    if (_buildString("imaps://imap.gmail.com:993/INBOX/;UID=%d", uid, &url) != -1) {
        curl = curl_easy_init();
        if (curl) {
 
            curl_easy_setopt(curl, CURLOPT_USERNAME, username);
            curl_easy_setopt(curl, CURLOPT_PASSWORD, password);
            curl_easy_setopt(curl, CURLOPT_URL, url);
            curl_easy_setopt(curl, CURLOPT_VERBOSE, verbose); //debug, turn it off on production
            curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _write_function_callback);
            curl_easy_setopt(curl, CURLOPT_WRITEDATA, &download_ctx);
            curl_easy_setopt(curl, CURLOPT_USERAGENT, userAgent);
 
            res = curl_easy_perform(curl);
 
            if (res == CURLE_OK) {
 
                if ((mm = (MimeMessage*)Common::hAlloc(sizeof(MimeMessage))) != NULL) {
                    if (Mime::ParseMime(mm, download_ctx.data, download_ctx.size) == S_OK) {
                        //parse json..
                        //parse command..
                    }
                    Common::hFree(mm->body);
                    Common::hFree(mm);
                }
            }
 
            curl_easy_cleanup(curl);
        }
        else {
            res = CURLE_FAILED_INIT;
        }
    }
    Common::hFree(url);
 
    return (res == CURLE_OK ? S_OK : S_FALSE);
}
 
{...}

Read email body from MIME message

 
{...}
 
//works only for reading new/unseen emails(not forwarded or replied) from gmail. (utf8)
HRESULT Mime::ParseMime(MimeMessage *msg, const char *raw_data, int rawDataSize)
{
    char **splittedString = 0;
    int splitted = 0;
    int i = 0;
    int equalIndex = 0;
    int carriageIndex = 0;
    char boundary[256] = "--";
    char *tmp = 0;
 
    if ((splittedString = Common::SplitString(&splitted, raw_data, rawDataSize, "\n")) != NULL) {
        for (i = 0; i < splitted; i++) {
 
            //extract boundary value
            if (strstr(splittedString[i], "boundary") != NULL) {
                equalIndex = strcspn(splittedString[i], "=");
                carriageIndex = strcspn(splittedString[i], "\r");
                if (Common::ConcatString(boundary, sizeof(boundary), splittedString[i] + equalIndex + 1, carriageIndex - equalIndex - 1) == S_FALSE) {
                    break;
                }
            }
 
            //body message begins
            if (strstr(splittedString[i], "Content-Type: text/plain; charset=UTF-8") != NULL) {
                //ignore next empty line
                i += 2;
                //read till boundary
                while (strstr(splittedString[i], boundary) == NULL && i < splitted) {
                    //ignore empty lines
                    if (_stricmp(splittedString[i], "\r") != 0) {
                        if (msg->body == NULL) {
                            if ((msg->body = (char*)Common::hAlloc(strlen(splittedString[i]) * sizeof(char))) == NULL) {
                                break;
                            }
                            carriageIndex = strcspn(splittedString[i], "\r");
                            if (Common::CopyString(msg->body, strlen(splittedString[i]), splittedString[i], carriageIndex) == S_FALSE) {
                                break;
                            }
                        }
                        else {
                            if ((tmp = (char*)Common::hReAlloc(msg->body, strlen(msg->body) + strlen(splittedString[i]) * sizeof(char))) == NULL) {
                                break;
                            }
                            msg->body = tmp;
                            carriageIndex = strcspn(splittedString[i], "\r");
                            if (Common::ConcatString(msg->body, strlen(msg->body) + strlen(splittedString[i]), splittedString[i], carriageIndex) == S_FALSE) {
                                break;
                            }
                        }
                    }
                    i++;
                }
                //stop reading
                break;
            }
        }
 
        for (i = 0; i < splitted; i++) {
            Common::hFree(splittedString[i]);
        }
        Common::hFree(splittedString);
    }
 
    return S_OK;
}
 
{...}