Programminghobby
Programminghobby

Reputation: 115

SMTP server dropped before STARTTLS completes

When communicating with the "smtp.gmail.com" server I used the EHLO command to get the greet from gmail "at your service" response, then STARTTLS command and got "2.0.0 Ready to start TLS". After the "STARTTLS" command I start sending the email data like "MAIL FROM: %s\n", from" but when I do the server connection closes. I now realize that the server was waiting for the TLS negotiation, what TLS negotiation commands do I send to establish a handshake? I referenced Chapter 5. from the RFC document: [https://www.rfc-editor.org/rfc/rfc2487] for information about TLS Negotiation.

The C++ code I am using is from: [http://www.cplusplus.com/forum/windows/35333/]

#include <windows.h> 
#include <cstdio>
#include <iostream>
using namespace std;
#pragma comment(lib, "ws2_32.lib") //I added because of LNK2019 error
//#include <winsock2.h>  //on MSVC2008 - windows.h already includes winsock stuff    /* WSAGetLastError, WSAStartUp  */
#define snprintf _snprintf

static void sendmail_write(const int  sock,const char *str,const char *arg)
 {
    char buf[4096];

    if (arg != NULL)
        snprintf(buf, sizeof(buf), str, arg);
    else
        snprintf(buf, sizeof(buf), str);

    send(sock, buf, strlen(buf), 0);

// read a reply from server
    char outbuf[1024];
    int len=recv(sock,outbuf,1024,0);
    outbuf[len]='\0';
    cout <<outbuf;

}

static int sendmail(
                const char *from,
                const char *to,
                const char *subject,
                const char *body,
                const char *hostname,
        const char *user,
        const char *pass,
                const int   port
            ) {

    struct hostent *host;
    struct sockaddr_in saddr_in;
    int sock = 0;


    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        return -1;
    }


    sock = socket(AF_INET, SOCK_STREAM, 0);
    host = gethostbyname(hostname);

    saddr_in.sin_family      = AF_INET;
    saddr_in.sin_port        = htons((u_short)port);
    saddr_in.sin_addr.s_addr = 0;

    memcpy((char*)&(saddr_in.sin_addr), host->h_addr, host->h_length);

    if (connect(sock, (struct sockaddr*)&saddr_in, sizeof(saddr_in)) == -1) {
        return -2;
    }



    sendmail_write(sock, "EHLO %s\n",       "MyMailDomain");  // Should I use HELO or EHLO?

    sendmail_write(sock, "STARTTLS\n",""); // <----- starting TLS?

    sendmail_write(sock, "MAIL FROM: %s\n", from);    // from
    sendmail_write(sock, "RCPT TO: %s\n",   to);      // to
    sendmail_write(sock, "DATA\n",          NULL);    // begin data
    sendmail_write(sock, "From: %s\n",      from);
    sendmail_write(sock, "To: %s\n",        to);
    sendmail_write(sock, "Subject: %s\n",   subject);
    sendmail_write(sock, "\n",              NULL);
    sendmail_write(sock, "%s\n",            body);    // data
    sendmail_write(sock, ".\n",             NULL);    // end data
    sendmail_write(sock, "QUIT\n",          NULL);    // terminate

    closesocket(sock);

    return 0;
}


int main(int argc, char *argv[]) {
    int ret = sendmail(

        "[email protected]",  // from - put an email address here
        "[email protected]", // to - put an email address here
        "subject",
        "body",
        "smtp.gmail.com",
    "[email protected]",
    "password",
        587
    );

    if (ret != 0)
        fprintf(stderr, "Failed to send mail (code: %i).\n", ret);
    else
        fprintf(stdout, "\nMail successfully sent.\n");

    return ret;
}

Upvotes: 1

Views: 941

Answers (1)

Andrew Henle
Andrew Henle

Reputation: 1

This is a quick, abbreviated "STARTTLS" example using OpenSSL, without any error checking (the proper header files are left as an exercise - that will help learn OpenSSL):

SSL_CTX *ctx = SSL_CTX_new( SSLv23_client_method() );
SSL *ssl = SSL_new( ctx );

connect( sock, ( struct sockaddr * ) &saddr_in, sizeof( saddr_in ) );

send( sock, "EHLO X\r\n", strlen( "EHLO X\r\n" );

// drain any reply - you can examine this - it can have useful data in it
recv( sock, buf, sizeof( buf ) );

// switch this plaintext connection over to SSL/TLS
send( sock, ". STARTTLS\r\n", strlen( ". STARTTLS\r\n" );
recv( sock, buf, sizeof( buf ) );

// now start the SSL/TLS negotiation
SSL_set_fd( ssl, sock );
int rc = SSL_connect( ssl );

...

After this, assuming the SSL_connect() call succeeds, you'd use SSL_write( ssl, void *buf, int num ) instead of send( sock, void *buf, size_t num ), and SSL_read() instead of recv() to read and write data such as user names and passwords. Note that you really need to pay attention to incomplete read and write operations, and also failures that return SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE from SSL_get_error(). For example, if SSL_read() fails, then SSL_get_error() returns SSL_ERROR_WANT_READ, you have to retry the SSL_read().

And you will want to add a lot of error checking - check everything, and actually do calls such as SSL_write() and SSL_read() from helper functions that handle short writes and errors, and properly retry.

Getting it working will take some trial-and-error on your part.

Upvotes: 1

Related Questions