Simple TLS/SSL SMTP client for Qt5

For one of my projects I wanted to develop a crash reporting dialog where user can enter the steps he did to crash the application and then by pressing send button the app automatically will send me user report and the log to my e-mail address. Unfortunatelly Qt doesn't have a Smtp library, and I don't want anything heavy just to send a simple string of characters through gmail smtp server.


Update

Both implementations are now available on github

 

 

There are some examples and codes on github like:
https://github.com/bluetiger9/SmtpClient-for-Qt

The above one actually looks like very decent peace of code, however again, it is a little bit too heavy for my taste and it didn't compile on Qt5 immidiatly (even the example) and I didn't want to go through to try and fix it.

So I found an example code for SMTP from Qt3. Which was almost exactly what I was looking for, it was simple for sure. However, it lacked two major features, it didn't support TLS/SSL communication and authentification I need it to use gmail as my smtp server. Then I thought how hard would it be to improve that code?

 

SMTP protocol for sending email

First of all every communication with the server will result a response from server. It is important to extract first 3 characters from the message which are response codes. Those codes obviously will be used for error checking whether communication is going smoothly.

Basic algorithm for sending email through smtp:

  1. Connecting to the server and waiting for greeting message
  2. If response code = 220 then send a greeting back to server "EHLO yourdomain.com\r\n"
  3. If response code = 250 then identify from whom the mail is coming from by sending "MAIL FROM:<youremail@mail.com>\r\n"
  4. If response code = 250 then send whom the email is addressed to "RCPT TO:<send_to@mail.com>\r\n"
  5. If response code = 250 then now it is time to inititate sending the contents of the email by sending "DATA\r\n"
  6. If response code = 354 then send your contents for example "This is an email\r\n"
  7. If response code = 250 then your email has been sent and now you can close the connection by sending "QUIT\r\n"

 

Basically the above algorithm was used in the example code I found from Qt3. As you can see the above algorithm lacks authentification and SSL/TLS handshake. To do add those things to the code it turned out to be actually simpler than I first anticipated. 

Basic algorithm for TLS/SSL smtp email sending:

  1. Connecting to the server and waiting for greeting message
  2. If response code = 220 then send a greeting back to server "EHLO yourdomain.com\r\n"
  3. If response code = 250 then initiate TLS communication by making a SSL/TLS handshake, for this I use the methods provided in QSslSocket class, more precisely QSslSocket::startClientEncryption(). (make communictaion encrypted)
  4. If response code = 250 then you have successfully initiated a encrypted communication and now we can start authentification by sending "AUTH LOGIN\r\n"
  5. If response code = 334 then the server is waiting for username, since my aim was to use GMAIL as smtp server their API states that the username and password must be sent in base64 coding, it would look something like this "bXlfdXNlcm5hbWU=\r\n"  (my_username)
  6. If response code = 334 then send the server password, and again in base64 coding "bXlfcGFzc3dvcmQ=\r\n" (my_password)
  7. If response code = 235 then you have successfully logged in and now you can identify from whom the mail is coming from by sending "MAIL FROM:<youremail@mail.com>\r\n"
  8. If response code = 250 then send whom the email is addressed to "RCPT TO:<send_to@mail.com>\r\n"
  9. If response code = 250 then now it is time to inititate sending the contents of the email by sending "DATA\r\n"
  10. If response code = 354 then send your contents for example "This is an email\r\n"
  11. If response code = 250 then your email has been sent and now you can close the connection by sending "QUIT\r\n"

 

That's it, by following the above pseudo code, you should be able to write a simple TLS/SSL SMTP client for sending email using gmail as smtp server. You can use the above approach to send email through telnet aswell.

Important: Every command to server has to be finished with carriege return and new line symbol in other words you have to finish the line with "\r\n"

 

So by following the steps I wrote above, I slightly rewrote the example code which you can see below. However I would advise just trying to write the code yourself, belive it's not that hard, if you follow the steps.


First of all, for the code to run you will need Qt with ssl support compiled within and OpenSSL library . If you have ssl compiled in the Qt library you don't need to add anything to the pro file, except QT+= network.

 

Smtp.h

/*
Copyright (c) 2013 Raivis Strogonovs
 
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/
 
#ifndef SMTP_H
#define SMTP_H
 
 
#include <QtNetwork/QAbstractSocket>
#include <QtNetwork/QSslSocket>
#include < QString >
#include < QTextStream >
#include < QDebug >
#include <QtWidgets/QMessageBox>
#include < QByteArray >
 
 
 
class Smtp : public QObject
{
    Q_OBJECT
 
 
public:
    Smtp( const QString &user, const QString &pass,
          const QString &host, int port = 465, int timeout = 30000 );
    ~Smtp();
 
    void sendMail( const QString &from, const QString &to,
                   const QString &subject, const QString &body );
 
signals:
    void status( const QString &);
 
private slots:
    void stateChanged(QAbstractSocket::SocketState socketState);
    void errorReceived(QAbstractSocket::SocketError socketError);
    void disconnected();
    void connected();
    void readyRead();
 
private:
    int timeout;
    QString message;
    QTextStream *t;
    QSslSocket *socket;
    QString from;
    QString rcpt;
    QString response;
    QString user;
    QString pass;
    QString host;
    int port;
    enum states{Tls, HandShake ,Auth,User,Pass,Rcpt,Mail,Data,Init,Body,Quit,Close};
    int state;
 
};
#endif

 

Smtp.cpp

/*
Copyright (c) 2013 Raivis Strogonovs
 
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/
 
 
 
#include "smtp.h"
 
Smtp::Smtp( const QString &user, const QString &pass, const QString &host, int port, int timeout )
{    
    socket = new QSslSocket(this);
 
    connect(socket, SIGNAL(readyRead()), this, SLOT(readyRead()));
    connect(socket, SIGNAL(connected()), this, SLOT(connected() ) );
    connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this,SLOT(errorReceived(QAbstractSocket::SocketError)));   
    connect(socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(stateChanged(QAbstractSocket::SocketState)));
    connect(socket, SIGNAL(disconnected()), this,SLOT(disconnected()));
 
 
    this->user = user;
    this->pass = pass;
 
    this->host = host;
    this->port = port;
    this->timeout = timeout;
 
 
}
 
void Smtp::sendMail(const QString &from, const QString &to, const QString &subject, const QString &body)
{
    message = "To: " + to + "\n";
    message.append("From: " + from + "\n");
    message.append("Subject: " + subject + "\n");
    message.append(body);
    message.replace( QString::fromLatin1( "\n" ), QString::fromLatin1( "\r\n" ) );
    message.replace( QString::fromLatin1( "\r\n.\r\n" ),
    QString::fromLatin1( "\r\n..\r\n" ) );
    this->from = from;
    rcpt = to;
    state = Init;
    socket->connectToHostEncrypted(host, port); //"smtp.gmail.com" and 465 for gmail TLS
    if (!socket->waitForConnected(timeout)) {
         qDebug() << socket->errorString();
     }
 
    t = new QTextStream( socket );
 
 
 
}
 
Smtp::~Smtp()
{
    delete t;
    delete socket;
}
void Smtp::stateChanged(QAbstractSocket::SocketState socketState)
{
 
    qDebug() <<"stateChanged " << socketState;
}
 
void Smtp::errorReceived(QAbstractSocket::SocketError socketError)
{
    qDebug() << "error " <<socketError;
}
 
void Smtp::disconnected()
{
 
    qDebug() <<"disconneted";
    qDebug() << "error "  << socket->errorString();
}
 
void Smtp::connected()
{    
    qDebug() << "Connected ";
}
 
void Smtp::readyRead()
{
 
     qDebug() <<"readyRead";
    // SMTP is line-oriented
 
    QString responseLine;
    do
    {
        responseLine = socket->readLine();
        response += responseLine;
    }
    while ( socket->canReadLine() && responseLine[3] != ' ' );
 
    responseLine.truncate( 3 );
 
    qDebug() << "Server response code:" <<  responseLine;
    qDebug() << "Server response: " << response;
 
    if ( state == Init && responseLine == "220" )
    {
        // banner was okay, let's go on
        *t << "EHLO localhost" <<"\r\n";
        t->flush();
 
        state = HandShake;
    }
    //No need, because I'm using socket->startClienEncryption() which makes the SSL handshake for you
    /*else if (state == Tls && responseLine == "250")
    {
        // Trying AUTH
        qDebug() << "STarting Tls";
        *t << "STARTTLS" << "\r\n";
        t->flush();
        state = HandShake;
    }*/
    else if (state == HandShake && responseLine == "250")
    {
        socket->startClientEncryption();
        if(!socket->waitForEncrypted(timeout))
        {
            qDebug() << socket->errorString();
            state = Close;
        }
 
 
        //Send EHLO once again but now encrypted
 
        *t << "EHLO localhost" << "\r\n";
        t->flush();
        state = Auth;
    }
    else if (state == Auth && responseLine == "250")
    {
        // Trying AUTH
        qDebug() << "Auth";
        *t << "AUTH LOGIN" << "\r\n";
        t->flush();
        state = User;
    }
    else if (state == User && responseLine == "334")
    {
        //Trying User        
        qDebug() << "Username";
        //GMAIL is using XOAUTH2 protocol, which basically means that password and username has to be sent in base64 coding
        //https://developers.google.com/gmail/xoauth2_protocol
        *t << QByteArray().append(user).toBase64()  << "\r\n";
        t->flush();
 
        state = Pass;
    }
    else if (state == Pass && responseLine == "334")
    {
        //Trying pass
        qDebug() << "Pass";
        *t << QByteArray().append(pass).toBase64() << "\r\n";
        t->flush();
 
        state = Mail;
    }
    else if ( state == Mail && responseLine == "235" )
    {
        // HELO response was okay (well, it has to be)
 
        //Apperantly for Google it is mandatory to have MAIL FROM and RCPT email formated the following way -> <email@gmail.com>
        qDebug() << "MAIL FROM:<" << from << ">";
        *t << "MAIL FROM:<" << from << ">\r\n";
        t->flush();
        state = Rcpt;
    }
    else if ( state == Rcpt && responseLine == "250" )
    {
        //Apperantly for Google it is mandatory to have MAIL FROM and RCPT email formated the following way -> <email@gmail.com>
        *t << "RCPT TO:<" << rcpt << ">\r\n"; //r
        t->flush();
        state = Data;
    }
    else if ( state == Data && responseLine == "250" )
    {
 
        *t << "DATA\r\n";
        t->flush();
        state = Body;
    }
    else if ( state == Body && responseLine == "354" )
    {
 
        *t << message << "\r\n.\r\n";
        t->flush();
        state = Quit;
    }
    else if ( state == Quit && responseLine == "250" )
    {
 
        *t << "QUIT\r\n";
        t->flush();
        // here, we just close.
        state = Close;
        emit status( tr( "Message sent" ) );
    }
    else if ( state == Close )
    {
        deleteLater();
        return;
    }
    else
    {
        // something broke.
        QMessageBox::warning( 0, tr( "Qt Simple SMTP client" ), tr( "Unexpected reply from SMTP server:\n\n" ) + response );
        state = Close;
        emit status( tr( "Failed to send message" ) );
    }
    response = "";
}

 

 

Example usage:

smtp = new Smtp("your_username@gmail.com", "your_password", "smtp.gmail.com");
connect(smtp, SIGNAL(status(QString)), this, SLOT(mailSent(QString))); 

smtp->sendMail("sent_from@gmail.com", "send_it_to@gmail.com" , "This is a subject","This is a body");

 

That's all you have to do, to send a simple text based email through Qt5 and most likely Qt4 as well. ENJOY!

 

The result:

 

 

Download: smtp.zip (32.82K)

To run the exectutable you have to install Qt5, Microsoft Visual C++ redistributables and OpenSSL on your computer.
To compile you just need OpenSSL and Qt5!

If you want file attachemnt support as well refer to my other article, which improves this piece of code for attachment support.

 

References:

Google. (2013). Gmail APIs. Available: https://developers.google.com/gmail/xoauth2_protocol. Last accessed 9th Jul 2013.

Network Sorcery Inc.. (Not Available). SMTP, Simple Mail Transfer Protocol. Available: http://www.networksorcery.com/enp/protocol/smtp.htm. Last accessed 9th Jul 2013.

Microsoft. (2003). What is TLS/SSL?. Available: http://technet.microsoft.com/en-us/library/cc784450%28v=ws.10%29.aspx. Last accessed 9th Jul 2013.



Related Articles



ADVERTISEMENT

  • Chris Sparks

    Chris Sparks in Simple TLS/SSL SMTP client for Qt5

    qt.network.ssl: QSslSocket::connectToHostEncrypted: TLS initialization failed

    Edward Martin

    Edward Martin in Introduction to data encryption

    The introduction to the data encryption is very much helpful as you will get to know about the data encryption procedure which might help you to protect the files and folders. You can check the 

    bean

    bean in DIY 3D printer, AKA RepRap

    The 3D printer creates the dimensional object and with the features of the 3D printer, it executes the process if need further query then visit fix error code 0xc004f074...

    preter jack

    preter jack in Single Cycle MIPS CPU in VHDL

    Single-cycle CPU is more improved than the normal and usual CPUs and it has a lot of advantages and all you can get from fix

    henrydevid

    henrydevid in nRF51 Makefile with Qt Creator

    It is great for all, I have some different tricks if you want to know it then for that I have some different tricks, for that, you want to learn to code. For that, if you want to know it then for that you just visit some tutorial. And from there it is great for all. If you faced any type...

    Steve Rogger

    Steve Rogger in OpenCV on Raspberry Pi

    I have read the post and it is very much helpful because I have got to know about the open cv on the raspberry pi. The images and the screenshots provided here will guide you for your questions. The code you will get here is very unique and can be only used to run OpenCV. If you face any...

    preter jack

    preter jack in Programming micro-controller (Arduino) with cheap HC-06 Bluetooth

    This type of IC Bluetooth offers a lot of advantages and this type of Bluetooth are really very user-friendly and easy to use. It provides many unique features that are all mentioned over the