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 |