C++ SMTPS

This code opens a TLS connection with a gmail server and conducts a SMTP dialog (no PIPELINING) to send an email.
The most time consuming functions are the send(Tx) and Receive(Rx) - called Push and Pop in cryptlib. These are designed to test the SMTP return from the gmail server and return when a complete, correctly formatted response is received, or generate an error as described in my article about SMTP.

There are also examples of PIPELINING and Multi-Part emails (with HTML) in BASIC



// Compiles with Visual Studio 2008 for Windows

// SMTPS Application for sending email via gmail using cryptlib for TLS encryption
// (example debug output below)

#include <iostream> // input/output functionality
#include <sstream>  // manipulate strings (integer conversion)
#include <string>   // work with strings in a more intuitive way
#include <fstream>  // file output
using namespace std;

// Header file for cryptlib
#include "cryptlib.h" // includes <windows.h>

// Link the required .lib spec file for the dll
#pragma comment(lib,"cl32.lib")

// debug file
#define DEBUG_FILE "CryptLib_dbg.txt"

#define MailHost "smtp.gmail.com"
#define MailTo     "you@yourdomain.com"
#define MailFrom "me@mydomain.com"
#define UserName "yZJlb3LmNvvbayZXF3W3ccmb3m9xvc3b3m5==" // MIME encoded
#define Password "XpGwXJbnvbd=" // MIME encoded
#define TCP_PORT 465 // TLS
#define SMTP_RESPONSE_TIMEOUT 5000 // 5 sec response timeout

static ofstream debug; // debug file


//================================================================================
string Err2Str(int result) // Return an error code description
{
    if (result == CRYPT_OK) return "";

    switch(result)
    {
        case CRYPT_ERROR_PARAM1:    return "Bad argument, parameter 1"; break;
        case CRYPT_ERROR_PARAM2:    return "Bad argument, parameter 2"; break;
        case CRYPT_ERROR_PARAM3:    return "Bad argument, parameter 3"; break;
        case CRYPT_ERROR_PARAM4:    return "Bad argument, parameter 4"; break;
        case CRYPT_ERROR_PARAM5:    return "Bad argument, parameter 5"; break;
        case CRYPT_ERROR_PARAM6:    return "Bad argument, parameter 6"; break;
        case CRYPT_ERROR_PARAM7:    return "Bad argument, parameter 7"; break;
        case CRYPT_ERROR_MEMORY:    return "Out of memory"; break;
        case CRYPT_ERROR_NOTINITED: return "Data has not been initialised"; break;
        case CRYPT_ERROR_INITED:    return "Data has already been init'd"; break;
        case CRYPT_ERROR_NOSECURE:  return "Opn.not avail.at requested sec.level"; break;
        case CRYPT_ERROR_RANDOM:    return "No reliable random data available"; break;
        case CRYPT_ERROR_FAILED:    return "Operation failed"; break;
        case CRYPT_ERROR_INTERNAL:  return "Internal consistency check failed"; break;
        case CRYPT_ERROR_NOTAVAIL:  return "This type of opn.not available"; break;
        case CRYPT_ERROR_PERMISSION:return "No permiss.to perform this operation"; break;
        case CRYPT_ERROR_WRONGKEY:  return "Incorrect key used to decrypt data"; break;
        case CRYPT_ERROR_INCOMPLETE:return "Operation incomplete/still in progress"; break;
        case CRYPT_ERROR_COMPLETE:  return "Operation complete/can't continue"; break;
        case CRYPT_ERROR_TIMEOUT:   return "Operation timed out before completion"; break;
        case CRYPT_ERROR_INVALID:   return "Invalid/inconsistent information"; break;
        case CRYPT_ERROR_SIGNALLED: return "Resource destroyed by extnl.event"; break;
        case CRYPT_ERROR_OVERFLOW:  return "Resources/space exhausted"; break;
        case CRYPT_ERROR_UNDERFLOW: return "Not enough data available"; break;
        case CRYPT_ERROR_BADDATA:   return "Bad/unrecognised data format"; break;
        case CRYPT_ERROR_SIGNATURE: return "Signature/integrity check failed"; break;
        case CRYPT_ERROR_OPEN:      return "Cannot open object"; break;
        case CRYPT_ERROR_READ:      return "Cannot read item from object"; break;
        case CRYPT_ERROR_WRITE:     return "Cannot write item to object"; break;
        case CRYPT_ERROR_NOTFOUND:  return "Requested item not found in object"; break;
        case CRYPT_ERROR_DUPLICATE: return "Item already present in object"; break;
        case CRYPT_ENVELOPE_RESOURCE:return "Need resource to proceed"; break;
        default: return "Unknown return code!";
    }
};
            
//¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
string ErrExStr( int hCrypt ) // Return full error text
{                     
  int MsgLen;
    char* cBuff;
    cBuff = new char[512];

        cryptGetAttributeString( hCrypt, CRYPT_ATTRIBUTE_INT_ERRORMESSAGE, &cBuff, &MsgLen );  
        string sRet(cBuff);

    return sRet;
}
     
//¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
string ParseStr( string sIn, string sDelim, int nField )

    int match, LenStr, LenDelim, ePos, sPos(0), count(0);
    string sRet;

        LenDelim = sDelim.length();
        LenStr   = sIn.length();
        if( LenStr < 1 || LenDelim < 1 ) return ""; // Empty String
        if( nField < 1 ) return "";
        //=========== cout << "LenDelim=" << LenDelim << ", sIn.length=" << sIn.length() << endl;


        for( ePos=0; ePos < LenStr; ePos++ ) // iterate through the string
        { // cout << "sPos=" << sPos << ", LenStr=" << LenStr << ", ePos=" << ePos << ", sIn[ePos]=" << sIn[ePos] << endl;
            match = 1; // default = match found
            for( int k=0; k < LenDelim; k++ ) // Byte value
            { 
                if( ePos+k > LenStr ) // end of the string
                    break;
                else if( sIn[ePos+k] != sDelim[k] ){ // match failed
                    match = 0; break; }
            }
            //===========

            if( match || (ePos == LenStr-1) )  // process line
            {
                if( !match ) ePos = LenStr + LenDelim; // (ePos == LenStr-1)
                count++; // cout << "sPos=" << sPos << ", ePos=" << ePos << " >" << sIn.substr(sPos, ePos-sPos) << endl;
                if( count == nField ){ sRet = sIn.substr(sPos, ePos-sPos); break; }
                ePos = ePos+LenDelim-1; // jump over Delim
                sPos = ePos+1; // Begin after Delim
            } // cout << "Final ePos=" << ePos << ", count=" << count << ", LenStr=" << LenStr << endl;
        }// next

    return sRet;     
}

//¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
int SMTPLnCode( string sLine ) // return the 3 digit code for specified line
{
    int Code;
    stringstream ss; // typesafe string to int conversion

        if( sLine.length() < 3 ) return 0; // Empty String

        ss.str( sLine.substr(0, 3) ); // 3 Digit code
        ss >> Code;    // after last digit is extracted, eof state is set

    return Code; 
}   

//¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
int TLSPop( int hCrypt, string& sErr, string& sReply, int nRetLn )
// Handles responses that are delayed, lines that are delayed, missing end of response   
// Each line of the SMTP response must be terminated by <CRLF>
// In Multi-Line returns, each SMTP code will be followed by a hyphen, except the last line  
{                     
  int k, RetVal, BytesReply, Last, Totms, count, BufLen;
    char* cBuff;
    string sRet, sBuff;


        BufLen = 255;
        cBuff  = new char[BufLen];
   
        // Recover unexpected data
        if( nRetLn < 1 ) {
            RetVal = cryptPopData( hCrypt, cBuff, BufLen, &BytesReply );
            if( RetVal != CRYPT_OK )
            {
                sErr = "cryptPopData ERROR: "+Err2Str(RetVal)+" - "+ErrExStr(hCrypt);
                return -22;
            } 
            else if( BytesReply > 0 )
            {
                sErr = cBuff; // debug << "TLSPop sErr=" << sErr << endl;
                return BytesReply;
            }
            return 0;

        // Recover & Process Response
        }else { // debug << "SMTP_RESPONSE_TIMEOUT=" << SMTP_RESPONSE_TIMEOUT << endl;
            sReply = "";
            count  = 0; // debug << "nRetLn=" << nRetLn << endl;
            while( count < nRetLn )
            {
                Totms  = 0;
                while(true) //
                {
                    Sleep(20); // Wait for a response 
                    Totms += 20;
                    if( Totms > SMTP_RESPONSE_TIMEOUT )
                    { // debug << "Totms > SMTP_RESPONSE_TIMEOUT" << endl;
                        sErr = "Response timeout exceeded";
                        return -28;
                    } // debug << Totms << "m/s" << endl;

                    RetVal = cryptPopData( hCrypt, cBuff, BufLen, &BytesReply ); 
                    if( RetVal != CRYPT_OK )
                    {
                        sErr = "cryptPopData ERROR: "+Err2Str(RetVal)+" - "+ErrExStr(hCrypt);
                        return -29;
                    } 
                    else if( BytesReply > 0 ) // Received some data
                    { // debug << "Received " << BytesReply << " Bytes" << endl;
                        sBuff = cBuff; // debug << "sBuff=" << sBuff << endl;
                        sReply += sBuff.substr(0,BytesReply); 
                        Last = sReply.length(); // debug << "[Last-1]=" << (int)sReply[Last-1] << ", [Last-2]=" << (int)sReply[Last-2] << endl;

                        if( Last > 5 && sReply[Last-1] == 10 && sReply[Last-2] == 13 )
                        {
                            for( k=3; k<=Last; k++ ) // debug << "CharNum=" << Last-k+1) + ", " + CHR$(@pByte[Last-k]) << endl;
                            { //

                                if( sReply[Last-k] == 10 && sReply[Last-k+4] == 32 ){ ++count; break; } // Space not a hyphen "-", Response complete
                                if( k == Last            && sReply[Last-k+3] == 32 ){ ++count; break; } // Space not a hyphen "-", Response complete
                            } // for
                        } // if(RetVal != CRYPT_OK)
                    } // BytesReply
                    break;
                } // while(true) // debug <<  BytesToHexPtr( STRPTR(sBuff), BytesReply ) << endl;
            } // while(count < nRetLn)

        } // end if

    return SMTPLnCode(  ParseStr(sReply, "\r\n", count) );
}   


//¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
int TLSPushPop( int hCrypt, string& sErr, string& sReply, int nRetLn, string sSend )
// Handles responses that are delayed, lines that are delayed, missing end of response   
// Each line of the SMTP response must be terminated by <CRLF>
// In Multi-Line returns, each SMTP code will be followed by a hyphen, except the last line  
{                     
  int RetVal, BytesSent;

   
    // Trap unexpected returns
    RetVal = TLSPop( hCrypt, sErr, sReply, 0 );
    if( RetVal < 0 ) return RetVal; // debug "initial response:" << sErr << endl;


    // Push data  debug << "Sent:" << sSend.length() << " Bytes, " << sSend.substr(0, sSend.find("\r\n") ) << endl;
    if( !(sSend.length() > 0 && nRetLn > 0) ){ sErr = "No data to send"; return -24; }
    RetVal = cryptPushData( hCrypt, sSend.data(), sSend.length(), &BytesSent );
    if( RetVal != CRYPT_OK )
        {
          sErr = "CryptPushData ERROR: "+Err2Str(RetVal)+" - "+ErrExStr(hCrypt);
          return -25;
        } 
        else if( sSend.length() != BytesSent )
        {
            stringstream ss;
            ss << "CryptPushData ERROR: ToSend=" << sSend.length() << ", Sent=" << BytesSent;
          sErr = ss.str();
          return -26;
    }


    // Flush outgoing data 
    RetVal = cryptFlushData(hCrypt);
    if( RetVal != CRYPT_OK )
     {
      sErr = "CryptFlushData ERROR: "+Err2Str(RetVal)+" - "+ErrExStr(hCrypt);
      return -27;
     }


    // Recover response
    RetVal = TLSPop( hCrypt, sErr, sReply, nRetLn );
    if( RetVal < 0 ) return RetVal; // debug << "TLSPushPop Rx SMTPLnCode=" << RetVal << endl;

    return RetVal;
}   


//¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
int SMTPTLS(   string sSrvr,
             string sUser,
             string sPass,
             string sFrom,
             string sTo,
             string sBody, 
             string sErr     )

{
    int RetVal, FuncRet;
    CRYPT_SESSION hSess;
    string sSend, sReply;

        if( cryptInit() != CRYPT_OK ) return -2; // Initialize Library

        FuncRet = 1; // Default function return
        while(true) // Main Request processing block
        {
      // Create the session 
      RetVal = cryptCreateSession( &hSess, CRYPT_UNUSED, CRYPT_SESSION_SSL );
      if(RetVal != CRYPT_OK){ sErr = "CryptCreateSession ERROR: "+Err2Str(RetVal); FuncRet = -4; break; }  

      // Add the server name "smtp.gmail.com" 
      RetVal = cryptSetAttributeString( hSess, CRYPT_SESSINFO_SERVER_NAME, sSrvr.data(), sSrvr.length() );
      if(RetVal != CRYPT_OK){ sErr = "SERVER_NAME ERROR: "+Err2Str(RetVal)+" "+ErrExStr(hSess); FuncRet = -6; break; }   
              
      // Specify the Port
      RetVal = cryptSetAttribute( hSess, CRYPT_SESSINFO_SERVER_PORT, TCP_PORT );
      if(RetVal != CRYPT_OK){ sErr = "SERVER_PORT ERROR: "+Err2Str(RetVal)+" "+ErrExStr(hSess); FuncRet = -8; break; }  

      // Activate the session
      RetVal = cryptSetAttribute( hSess, CRYPT_SESSINFO_ACTIVE, 1 );
      if(RetVal != CRYPT_OK){ sErr = "SESSINFO_ACTIVE ERROR: "+Err2Str(RetVal)+" "+ErrExStr(hSess); FuncRet = -10; break; } 

      // Discard initial response created by connecting 
      RetVal = TLSPop( hSess, sErr, sReply, 0 ); // debug << "Discarded initial Response=" << sErr << endl;
      if( RetVal < 0 ){ FuncRet = -12;  break; } // debug "initial response:" << sErr


      // MIME dialog
      sSend  = "EHLO \r\n"; // ESMTP version of HELO
      RetVal = TLSPushPop( hSess, sErr, sReply, 1, sSend );
      if( RetVal < 0 ){ return RetVal; break; } 
      if( RetVal != 250 ){ sErr = "EHLO Failed: "+sErr; FuncRet = -14; break; }

      RetVal = TLSPushPop(hSess, sErr, sReply, 1, "AUTH LOGIN \r\n" ); //' Login
      if( RetVal != 334 ){ sErr = "AUTH Failed: "+sErr; FuncRet = -16; break; }

      RetVal = TLSPushPop(hSess, sErr, sReply, 1, sUser + "\r\n"); //' MimeEncode Username
      if( RetVal != 334 ){ sErr = "user Failed: "+sErr; FuncRet = -18; break; }

      RetVal = TLSPushPop(hSess, sErr, sReply, 1, sPass + "\r\n"); //' MimeEncode Password
      if( RetVal != 235 ){ sErr = "pass Failed: "+sErr; FuncRet = -20; break; } 

      RetVal = TLSPushPop(hSess, sErr, sReply, 1, "MAIL FROM: <" + sFrom + ">\r\n"); //' Sender
      if( RetVal != 250 ){ sErr = "MAIL FROM Failed: "+sErr; FuncRet = -22; break; } 

      RetVal = TLSPushPop(hSess, sErr, sReply, 1, "RCPT TO: <" + sTo + ">\r\n"); //' Recipient
      if( RetVal != 250 ){ sErr = "RCPT TO Failed: "+sErr; FuncRet = -24; break; }  

      RetVal = TLSPushPop(hSess, sErr, sReply, 1, "DATA \r\n"); //' Body begins
      if( RetVal != 354 ){ sErr = "DATA Failed: "+sErr; FuncRet = -26; break; }  
   
      RetVal = TLSPushPop(hSess, sErr, sReply, 1, sBody + "\r\n.\r\n" ); //' Body
      if( RetVal != 250 ){ sErr = "body Failed: "+sErr; FuncRet = -28; break; }  
      sErr = "Email Sent OK"; return 1; // 250 2.0.0 OK - Message sent
 

      RetVal = TLSPushPop(hSess, sErr, sReply, 1, "QUIT \r\n"); //' Terminate MIME  
      if( RetVal < 0 ) return RetVal;
      if( RetVal != 221 ){ sErr = "QUIT Failed: "+sErr; FuncRet = -39; break; }  


            break;
        }
    if (hSess) cryptDestroySession(hSess); // Close the session
    cryptEnd();

    return FuncRet;
}


// ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
int main( )
{
  int RetVal;
  string sBody, sErr;

debug.open(DEBUG_FILE);


    sBody = sBody + "From: me@mydomain.com\r\n"; // $MailFrom
    sBody = sBody + "To: you@yourdomain.com\r\n";
    sBody = sBody + "Subject: Gmail Test using TLS Encryption\r\n\r\n";
    sBody = sBody + "C++ version of the code created this email"; // etc etc

    RetVal = SMTPTLS( MailHost, // $MailHost
                                            UserName, // $UserName
                                            Password, // $Password
                                            MailFrom, // $MailFrom
                                            MailTo,   // $MailTo
                                            sBody,
                                            sErr );

    if(RetVal < 0) debug << "ERROR: " << sErr;
    //============

debug.close( );

    return 0;
}

// ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

// sample SMTPS sesion debug output:

    //Sent:7 Bytes, EHLO
    //sBuff=250-mx.google.com at your service, [76.201.141.16]
    //250-SIZE 35651584
    //250-8BITMIME
    //250-AUTH LOGIN PLAIN
    //250-ENHANCEDSTATUSCODES
    //250 PIPELINING

    //Sent:13 Bytes, AUTH LOGIN
    //sBuff=334 VXcm5hNlbWU6

    //Sent:46 Bytes, yZJlb3LmNvvbayZXF3W3ccmb3m9xvc3b3m5==
    //sBuff=334 UGF3dvczcmQ6

    //Sent:14 Bytes, XpGwXJbnvbd=
    //sBuff=235 2.7.0 Accepted

    //Sent:46 Bytes, MAIL FROM: <me@mydomain.com>
    //sBuff=250 2.1.0 OK 35sm523021yxh.69

    //Sent:29 Bytes, RCPT TO: <you@yourdomain.com>
    //sBuff=250 2.1.5 OK 35sm523021yxh.69

    //Sent:7 Bytes, DATA
    //sBuff=354  Go ahead 35sm523021yxh.69

    //Sent:152 Bytes, From: noreply@mydomain.com
    //sBuff=250 2.0.0 OK 1259274271 35sm523021yxh.69


Comments