Priyank Chheda
Priyank Chheda

Reputation: 571

Mail format via C socket programming

I am learning socket programming in C and the best way to learn something is by implementing it. So I wrote a simple program to send mail with an attachment and some text body via C Socket Programming. The code is by far successfully with only one exception - formatting. MIME headers are showing up in mail body (screenshot attached) and mail subject is all messed up.

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iphlpapi.h>
#include <stdio.h>

#pragma comment(lib, "Ws2_32.lib")

#define SMTP_PORT "25"
#define DEFAULT_BUFLEN 1024

int sendData(SOCKET* socket, const char* data)
{
    int iResult;
    printf("%s", data);
    iResult = send(*socket, data, (int)strlen(data), 0);
    if (iResult == SOCKET_ERROR) {
        printf("send failed (msg: %s): %d\n", data, WSAGetLastError());
    }
    return iResult;
}

void recvData(SOCKET* socket)
{
    int recvbuflen = DEFAULT_BUFLEN;
    char recvbuf[DEFAULT_BUFLEN];
    int iResult;
    iResult = recv(*socket, recvbuf, recvbuflen - 1, 0);
    printf("In recvData function: %d\n", iResult);
    if (iResult > 0) {
        recvbuf[iResult] = '\0';
        printf("%s", recvbuf);
    }
    else if (iResult == 0)
        printf("Connection closed\n");
    else
        printf("recv failed: %d\n", WSAGetLastError());
}

int main(int argc, char** argv)
{
    WSADATA wsaData;

    int iResult;
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0) {
        printf("WSAStartup failed: %d\n", iResult);
        return 1;
    }

    struct addrinfo *result = NULL;
    struct addrinfo *ptr = NULL;
    struct addrinfo hints;

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    iResult = getaddrinfo("mail.sharklasers.com", SMTP_PORT, &hints, &result);
    if (iResult != 0) {
        printf("getaddrinfo failed: %d\n", iResult);
        WSACleanup();
        return 1;
    }

    SOCKET ConnectSocket = INVALID_SOCKET;
    for (ptr = result; ptr != NULL; ptr = ptr->ai_next) {
        ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
        if (ConnectSocket == INVALID_SOCKET) {
            printf("socket failed: %ld\n", WSAGetLastError());
            WSACleanup();
            return 1;
        }

        iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
        if (iResult == SOCKET_ERROR) {
            closesocket(ConnectSocket);
            ConnectSocket = INVALID_SOCKET;
            continue;
        }
        break;
    }
    freeaddrinfo(result);

    if (ConnectSocket == INVALID_SOCKET) {
        printf("Unable to connect to server!\n");
        WSACleanup();
        return 1;
    }

    sendData(&ConnectSocket, "HELO mail.sharklasers.com\r\n");
    recvData(&ConnectSocket);
    sendData(&ConnectSocket, "MAIL FROM: [email protected]\r\n");
    recvData(&ConnectSocket);
    sendData(&ConnectSocket, "RCPT TO: [email protected]\r\n");
    recvData(&ConnectSocket);
    sendData(&ConnectSocket, "DATA\r\n");
    recvData(&ConnectSocket);
    sendData(&ConnectSocket, "Subject:This is subject of my mail\r\n");
    sendData(&ConnectSocket, "And this is text\r\n");
    sendData(&ConnectSocket, "MIME-Version: 1.0\r\n");
    sendData(&ConnectSocket, "Content-Type:multipart/mixed;boundary=\"KkK170891tpbkKk__FV_KKKkkkjjwq\"\r\n");
    sendData(&ConnectSocket, "--KkK170891tpbkKk__FV_KKKkkkjjwq\r\n");
    sendData(&ConnectSocket, "Content-Transfer-Encoding: quoted-printable\r\n");
    sendData(&ConnectSocket, "Content-Type: text/plain;name=\"details.txt\"\r\n");
    sendData(&ConnectSocket, "Content-Disposition: attachment; filename=\"details.txt\"\r\n");
    sendData(&ConnectSocket, "\r\n\r\n");

    FILE* MailFilePtr = fopen("details.txt", "r");
    if (MailFilePtr == NULL)
        printf("Error opening attachment\n");

    char FileBuffer[1024];
    char buf[1024];

    memset(FileBuffer, 0, sizeof(FileBuffer));
    while (fgets(FileBuffer, sizeof(FileBuffer), MailFilePtr))
    {
        sprintf(buf, "%s", FileBuffer);
        buf[strlen(buf) - 1] = 0;
        sendData(&ConnectSocket, buf);
        memset(FileBuffer, 0, sizeof(FileBuffer));
        memset(buf, 0, sizeof(buf));
    }

    fclose(MailFilePtr);

    sendData(&ConnectSocket, "\r\n\r\n--KkK170891tpbkKk__FV_KKKkkkjjwq--\r\n\r\n");
    sendData(&ConnectSocket, ".\r\n");
    recvData(&ConnectSocket);

    sendData(&ConnectSocket, "QUIT\r\n");

    iResult = shutdown(ConnectSocket, SD_SEND);
    if (iResult == SOCKET_ERROR) {
        printf("shutdown failed: %d\n", WSAGetLastError());
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }

    closesocket(ConnectSocket);
    WSACleanup();
    return 0;
}

Question: How can I get mail body in proper format? I think I am missing some SMTP command or command sequence, but I am not sure.

NOTE

  1. I am using Guerrilla Mail for now, as Gmail needs SSL\TLS is required for communication (correct me if I am wrong). I will use openssl library for that.
  2. This is not the final code. I do have to do some code clean up. However, code review suggestions are also welcome.
  3. I am here to learn, so any links, book reference related to C Language and Network Programming are welcome.

EDIT

Edited code is based on comments and answers so far. Unfortunately the problem still exist. Instead of reading the file, i am directly passing the base64 encoded attachment (this is working). The output of this edited code is show below in screenshot

    sendData(&ConnectSocket, "MIME-Version: 1.0\r\n");
    sendData(&ConnectSocket, "Content-Type:multipart/mixed;boundary=\"977d81ff9d852ab2a0cad646f8058349\"\r\n");
    sendData(&ConnectSocket, "Subject:This is subject of my mail\r\n");
    sendData(&ConnectSocket, "--977d81ff9d852ab2a0cad646f8058349\r\n");
    sendData(&ConnectSocket, "Content-Type: text/plain; charset=\"utf-8\"\r\n");
    sendData(&ConnectSocket, "Content-Transfer-Encoding: quoted-printable\r\n\r\n");
    sendData(&ConnectSocket, "Hi Me,=0A=0AThis is an empty file.=0A=0ARegards,=0A<ME>=0A=0A---- =0ASent using Guerrillamail.com =0ABlock or report abuse : https://www.guerrillamail.com//abuse/?a=3DUVJzDA8SW6Q1mwa14nUTcwfCX9ne0dhd=0A \r\n\r\n");
    sendData(&ConnectSocket, "--977d81ff9d852ab2a0cad646f8058349\r\n");
    sendData(&ConnectSocket, "Content-Type: text/plain\r\n");
    sendData(&ConnectSocket, "Content-Transfer-Encoding: base64\r\n");
    sendData(&ConnectSocket, "Content-Disposition: attachment; filename=\"details.txt\"\r\n\r\n");
    sendData(&ConnectSocket, "U2FtcGxlIFRleHQu");
    sendData(&ConnectSocket, "\r\n\r\n--977d81ff9d852ab2a0cad646f8058349--\r\n\r\n");
    sendData(&ConnectSocket, ".\r\n");
    recvData(&ConnectSocket);
    sendData(&ConnectSocket, "QUIT\r\n");

Edited Output Mail Screenshot:

Output Mail Screenshot

Upvotes: 0

Views: 857

Answers (2)

tripleee
tripleee

Reputation: 189628

The body needs to be separated from the headers with an empty line.

sendData(&ConnectSocket, "MIME-Version: 1.0\r\n");
sendData(&ConnectSocket, "Content-Type:multipart/mixed;boundary=\"977d81ff9d852ab2a0cad646f8058349\"\r\n");
sendData(&ConnectSocket, "Subject:This is subject of my mail\r\n");
sendData(%ConnectSocket, "\r\n"); /* added */
sendData(&ConnectSocket, "--977d81ff9d852ab2a0cad646f8058349\r\n");
sendData(&ConnectSocket, "Content-Type: text/plain; charset=\"utf-8\"\r\n");
sendData(&ConnectSocket, "Content-Transfer-Encoding: quoted-printable\r\n\r\n");
sendData(&ConnectSocket, "Hi Me,=0A=0AThis is an empty file.=0A=0ARegards,=0A<ME>=0A=0A---- =0ASent using Guerrillamail.com =0ABlock or report abuse : https://www.guerrillamail.com//abuse/?a=3DUVJzDA8SW6Q1mwa14nUTcwfCX9ne0dhd=0A \r\n\r\n");
sendData(&ConnectSocket, "--977d81ff9d852ab2a0cad646f8058349\r\n");
sendData(&ConnectSocket, "Content-Type: text/plain\r\n");
sendData(&ConnectSocket, "Content-Transfer-Encoding: base64\r\n");
sendData(&ConnectSocket, "Content-Disposition: attachment; filename=\"details.txt\"\r\n\r\n");
sendData(&ConnectSocket, "U2FtcGxlIFRleHQu");
sendData(&ConnectSocket, "\r\n\r\n--977d81ff9d852ab2a0cad646f8058349--\r\n\r\n");
sendData(&ConnectSocket, ".\r\n");

When you send stuff to SMTP, you should properly check that the result code is the expected (2xx for most commands, 345 for DATA).

Using a static boundary string won't work properly over time (though I suppose you could replace or obfuscate via encoding the boundary string if you find it embedded in content).

Sending the entire message as a single long line will start breaking when the message gets longer than about 1,000 characters. One of the reasons quoted-printable is useful is that it allows you to transparently embed newlines in your content (though just sending literal newlines where the message contains newlines would already get you pretty far in this case).

A well-formed message should also have a From: and To: header.

Upvotes: 1

Emmet B
Emmet B

Reputation: 5541

name looks unnecessary in Content-Type, change it to

sendData(&ConnectSocket, "Content-Type: text/plain");

EDIT:

please change to this (move the mail message below text/plain, and do the other changes)

sendData(&ConnectSocket, "MIME-Version: 1.0\r\n");
sendData(&ConnectSocket, "Content-Type:multipart/mixed;boundary=\"KkK170891tpbkKk__FV_KKKkkkjjwq\"\r\n");
sendData(&ConnectSocket, "--KkK170891tpbkKk__FV_KKKkkkjjwq\r\n");
sendData(&ConnectSocket, "Content-Type: text/plain\r\n");
sendData(&ConnectSocket, "And this is text\r\n");
sendData(&ConnectSocket, "--KkK170891tpbkKk__FV_KKKkkkjjwq\r\n");
sendData(&ConnectSocket, "Content-Type: text/plain;name=\"details.txt\"\r\n");
sendData(&ConnectSocket, "Content-Transfer-Encoding: base64\r\n");
sendData(&ConnectSocket, "Content-Disposition: attachment; filename=\"details.txt\"\r\n");

Upvotes: 0

Related Questions