Click here to Skip to main content
Click here to Skip to main content

SMTP Client with SSL/TLS

By , , 6 May 2013
 

Introduction

I needed to send emails in a product written in C++, so I searched the Internet and found a great article: SMTP Client written by Jakub Piwowarczyk. However, many of my clients use SMTP servers that require secure connection (TLS or SSL), and the SMTP Client does not support it. So I had to add SSL/TLS support to the CSmtp class from SMTP Client before I could use it in my product. As I was new to SSL/OpenSSL, it did take me quite some time to learn how to use it properly, and to make the code work with several popular SMTP servers. I have also seen people searching the internet looking for a C++ implementation of SMTP/SSL/TLS, but just could not find one. So I decided to share the one I wrote, in the hope that it will save people who are not familiar with SSL some time.

Note that this article does not cover the details of SMTP. Please go to the original article SMTP Client if you need to know more about SMTP.

Background

There are 2 kinds of secure connections for SMTP, one is SSL and the other is TLS. Some SMTP servers support only one kind and some support both. Generally speaking, the port for SSL is 465, and the port for TLS is 587, but this is not always the case. In addition to the ports being different, SMTP/SSL is different than SMTP/TLS in that, SMTP/SSL negotiates an encrypted connection directly after the underlying TCP connection has been established, while SMTP/TLS requires that the client send a STARTLS command to the server before they negotiate an encrypted connection.

The steps involved in SMTP/SSL are as follows:

  1. The client connects to the server using TCP.
  2. The client negotiates an encrypted connection with the server.
  3. The server sends a welcome message using the encrypted connection to the client.
  4. The client sends a EHLO command using the encrypted connection to the server.
  5. The server responds to the EHLO command using the encrypted connection.

The steps involved in SMTP/TLS are as follows:

  1. The client connects to the server using TCP.
  2. The server sends a welcome message using the un-encrypted connection to the client.
  3. The client sends a EHLO command using the un-encrypted connection to the server.
  4. The server responds to the EHLO command using the un-encrypted connection.
  5. The client sends a STARTTLS command using the un-encrypted connection to the server.
  6. The server responds to the STARTTLS command using the un-encrypted connection.
  7. The client negotiates an encrypted connection with the server.
  8. The client sends a EHLO command using the encrypted connection to the server.
  9. The server responds to the EHLO command using the encrypted connection.

Using the Code

I have used openssl (http://www.openssl.org) in the sample code. The directory "openssl-0.9.8l" in the sample code contains all the necessary header files and the two pre-built static openssl libraries. If you would also like to use this version of openssl in your code, be sure to copy the entire "openssl-0.9.8l" directory to the root directory of your project and add "openssl-0.9.8l\inc32" to "Additional Include Directories", and also add "openssl-0.9.8l\out32" to "Additional Library Directories".

If you would like to build your own openssl, please refer to http://www.openssl.org for detailed instructions.

#define test_gmail_tls
    CSmtp mail;
#if defined(test_gmail_tls)
    mail.SetSMTPServer("smtp.gmail.com",587);
    mail.SetSecurityType(USE_TLS);
#elif defined(test_gmail_ssl)
    mail.SetSMTPServer("smtp.gmail.com",465);
    mail.SetSecurityType(USE_SSL);
#elif defined(test_hotmail_TLS)
    mail.SetSMTPServer("smtp.live.com",25);
    mail.SetSecurityType(USE_TLS);
#elif defined(test_aol_tls)
    mail.SetSMTPServer("smtp.aol.com",587);
    mail.SetSecurityType(USE_TLS);
#elif defined(test_yahoo_ssl)
    mail.SetSMTPServer("plus.smtp.mail.yahoo.com",465);
    mail.SetSecurityType(USE_SSL);
#endif
    mail.SetLogin("***");
    mail.SetPassword("***");
    mail.SetSenderName("User");
    // ......
    mail.Send();

If you use a non-privileged user account to test Yahoo, the mail will fail to send. And the error message returned by the Yahoo SMTP server is "530 Access denied: Free users cannot access this server".

Notes

  • The code does not verify the server's identity, that is, it does not check the server's certificate. This is usually not a big problem if we make sure we feed the program with correct server addresses. However, it is still worth mentioning that there is the chance that we are talking to an impersonator if we don't check the certificate.
  • You are not allowed to use the code in this article for spamming.

References

History

  • Rev 2.2, 2013/05/06

Thanks to everyone for the effective crowdsourcing!  Please keep up the improvements to our library!

  • Added fix contributed GKarRacer for the improper checking of the MsgBody.size() before working with a line of the message body.
  • Moved memory allocation and checking if all attachments can be opened to before the MAIL command is issued so that if there is an issue with one of them, sending the email can be gracefully aborted without the email being sent without the attachments.
  • Changed all the sprintf commands to snprintf to add greater security.  #define'd snprintf to sprintf_s for MS Visual C.  Also changed most strcpy calls to snprintf since the MS Visual C version of that function strcpy_s has the arguments re-ordered so it wouldn't be possible to use it without affecting portability.
  • Added fix contributed by jcyangzh about a possible infinite loop in the SayQuit function.
  • Added fixes contributed by sbrytskyy required to make the AUTH PLAIN login work properly.
  • Rev 2.1, 2012/11/06
  • Rev 2.0, 2011/06/23
    • Added the m_bAuthenticate member variable to be able to disable authentication even though it may be supported by the server. It defaults to true so if it is not set, the library will act as it would have before the addition.
    • Added the ability to pass the security type, m_type, the new m_Authenticate flag, the login and password into the ConnectRemoteServer function. If these new arguments are not included in the call, the function will work as it did before.
    • Added the ability to pass the new m_Authenticate flag into the SetSMTPServer function. If not provided, the function will act as it would before the addition.
    • Added fixes contributed by Martin Kjallman
    • Added fixes contributed by Karpov Andrey
    • Added fixes contributed by Jakub Piwowarczyk
  • Rev 1.9, 2010/08/19
    • Added PLAIN, CRAM-MD5 and DIGESTMD5 authorization.
    • Added a DisconnectRemoteServer() function and reconfigured the Send() function such that if you have already called the ConnectRemoveServer() function, it will use the existing connection and leave the connection open. This allows you to call ConnectRemoteServer(), then send multiple messages on the same connection. If this approach is used, you have to call DisconnectRemoteServer() to close the connection. If you call Send() without calling ConnectRemoteServer(), it will close the connection after sending. This change should be fully backward compatible.
  • Rev 1.8, 2010/08/09
  • 2010/08/03
    • Modified Introduction
  • 2010/08/02
    • Added note
  • 2010/08/01
    • Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Authors

David Johns
Web Developer
United States United States
Member
No Biography provided

John_Tang
Software Developer
China China
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralRe: fixed CRAM-MD5memberludohavil14 Feb '13 - 5:28 
there are changes that are specific to what I'm using it for. But the original code did not work for me.
the same for the other post I made.
BugFixed AUTH PLAINmemberludohavil8 Jan '13 - 10:26 
this will fix the AUTH PLAIN process
also the MD5-DIGEST is broken but you should not used it anymore
 

else if(IsKeywordSupported(RecvBuf, "PLAIN") == true)
{
pEntry = FindCommandEntry(command_AUTHPLAIN);
unsigned char ustrLogin[200];
ZeroMemory( ustrLogin, 200);
 
memcpy(ustrLogin, m_sLogin.c_str(), m_sLogin.length());
memcpy(ustrLogin + m_sLogin.length() + 1, m_sLogin.c_str(), m_sLogin.length());
memcpy(ustrLogin + 2 * m_sLogin.length() + 2, m_sPassword.c_str(), m_sPassword.length());
 
std::string encoded_login = base64_encode(ustrLogin, m_sLogin.length() * 2 + m_sPassword.length() + 2);
 
pEntry = FindCommandEntry(command_PASSWORD);
 
sprintf(SendBuf, "AUTH PLAIN %s\r\n", encoded_login.c_str());
SendData(pEntry);
ReceiveResponse(pEntry);
}
GeneralRe: Fixed AUTH PLAINmemberDavid Johns13 Feb '13 - 14:56 
Doesn't this get the exact same result as the current code? Is there some portability issue with how it is currently written?
GeneralRe: Fixed AUTH PLAINmemberludohavil14 Feb '13 - 5:27 
I have to check, but the fixes were made testing with the actual email services.
Bugexception in SayQuit could lead to infinite loop.memberjcyangzh13 Dec '12 - 17:05 
If there is a exception in SayQuit.RecieveResponse, the outer most exception handler would be called, which would then unwind the CSmtp object. Then the desstructor would be called, but m_bConnected was not set to false(it was setted after RecieveResponse), so the destrutor would call DisconnectRemoteServer...
 
Finally, the application will crash.
GeneralRe: exception in SayQuit could lead to infinite loop.memberDavid Johns16 Dec '12 - 10:24 
Thanks for finding and contributing this. Can you propose a solution and we will incorporate it into the next release?
 
Thanks,
David
GeneralRe: exception in SayQuit could lead to infinite loop.memberjcyangzh16 Dec '12 - 16:22 
	strcpy(SendBuf, "QUIT\r\n");
	m_bConnected=false;
	SendData(pEntry);
	ReceiveResponse(pEntry);
just put m_bConnected = false before SendData() in SayQuit.
Questionhow to add a socket proxy class on it? so that I can set socket4/5 proxy when I send email?memberASERERTA@#@s25 Nov '12 - 2:53 
how to add a socket proxy class on it? so that I can set socket4/5 proxy when I send email?
 
like http://www.codeproject.com/Articles/1652/CAsyncProxySocket-CAsyncSocket-derived-class-to-co
or other socket proxy class. where should i add the code?
Questionlooks like a bug in plain auth [modified]membersbrytskyy21 Nov '12 - 0:55 
			else if(IsKeywordSupported(RecvBuf, "PLAIN") == true)
			{
				pEntry = FindCommandEntry(command_AUTHPLAIN);
				sprintf(SendBuf, "^%s^%s", m_sLogin.c_str(), m_sPassword.c_str());
				for(unsigned int i=0; i<strlen(SendBuf); i++)
				{
					if(SendBuf[i]=='^') SendBuf[i]='\0';
				}
				const unsigned char *ustrLogin = CharToUnsignedChar(SendBuf);
				std::string encoded_login = base64_encode(ustrLogin, strlen(SendBuf));
				delete[] ustrLogin;
				sprintf(SendBuf, "AUTH PLAIN %s", encoded_login.c_str());
				SendData(pEntry);
				ReceiveResponse(pEntry);
			}
 
Check, please. You are printing to "^%s^%s", then replace ^ with 0, then use strlen.
Strlen will return 0 in this case all the time.
Am i right about bug?

modified 21 Nov '12 - 8:25.

AnswerRe: looks like a bug in plain authmemberDavid Johns3 Dec '12 - 15:18 
You are right. Here are the changes that are necessary to make it work properly:
 
CSmtp.cpp line 78 needs to be changed to:
	{command_AUTHPLAIN,     5*60,  5*60,  235, ECSmtp::COMMAND_AUTH_PLAIN},
 
CSmtp.cpp starting at line 863 should be changed to:
			else if(IsKeywordSupported(RecvBuf, "PLAIN") == true)
			{
				pEntry = FindCommandEntry(command_AUTHPLAIN);
				sprintf(SendBuf, "%s^%s^%s", m_sLogin.c_str(), m_sLogin.c_str(), m_sPassword.c_str());
				unsigned int length = strlen(SendBuf);
				unsigned char *ustrLogin = CharToUnsignedChar(SendBuf);
				for(unsigned int i=0; i<length; i++)
				{
					if(ustrLogin[i]==94) ustrLogin[i]=0;
				}
				std::string encoded_login = base64_encode(ustrLogin, length);
				delete[] ustrLogin;
				sprintf(SendBuf, "AUTH PLAIN %s\r\n", encoded_login.c_str());
				SendData(pEntry);
				ReceiveResponse(pEntry);
			}
 
I'll post these changes in the next release, but at least for now you can easily change version 2.1 to include this fix.
GeneralI have some changes for OS X use.memberDavembg19 Nov '12 - 8:26 
Works great on OS X with some minor changes.
Please let me know where to submit them for inclusion.
Nice project, works fine for me.
thanks!
GeneralRe: I have some changes for OS X use.memberDavid Johns3 Dec '12 - 0:56 
Davembg,
 
Great! Thanks for your contribution. Hopefully the changes are over version 2.1, which will make them easy to incorporate. You can zip them and send them to davidandrebecca.johns_at_gmail_dot_com.
GeneralMy vote of 5memberMichael Haephrati מיכאל האפרתי6 Nov '12 - 7:21 
Thanks!
QuestionPerfect for my needs. One issuememberMember 95677173 Nov '12 - 7:11 
It didn't compile out of the box - the TLS setting was scoped as if it was part of the Csmtp class, but it's separate. Once main() was corrected for that, it ran and worked fine.
QuestionProgram crashes sometimesmemberMember 953694724 Oct '12 - 6:43 
Hello my friend, thanks for this AWESOME lib.
 
I have a problem. I wrote this program that loops around and sends a message at a fixed interval. My problem usually occurs when I send a file around 3.5MB. Sometimes after 2-10 iterations, the program hangs within the mail.Send() routine. I dont get any error, just an appcrash. The error occurs irregularly.
 
Any ideas on to what might be causing this?
AnswerRe: Program crashes sometimesmemberMember 953694724 Oct '12 - 7:21 
I also get an SSL error rarely but it causes the application to crash. Any ideas on how to acknowledge the error but not end the program?
GeneralRe: Program crashes sometimesmemberDavid Johns26 Oct '12 - 1:09 
You could put a try{}catch() around where the error occurs. Have you trapped what line in the code can lead to the crash?
GeneralRe: Program crashes sometimesmemberMember 953694726 Oct '12 - 3:09 
Hi David,
Here's what I tried. There is already a try/catch statement around the mail.send() call. Do I need to add something more? I added more exception definitions to pinpoint exactly which function and line causes the SSL error. I just run it now and I got an error exception from the SendData_SSL function. The thing is that I dont mind if it fails some times, the problem is that the   whole program crashes with the exception. Am I doing something wrong? How can I avoid the program crashing completely on any exception? Any ideas?
GeneralRe: further infomemberMember 953694726 Oct '12 - 5:16 
Hi David,
Im using visual studio c++ 2012 btw. It seems that if I put some fake throw commands in functions the catching works ok. BUT if I add fake throw's in the functions such as readdata_ssl or writedata_ssl (that originally were creating problems) it seems that the exception propagates and re-fires until it reaches my main and then the program crashes with an unhandled exception. It should only propagate once isn't it? since you have one "throw" in the nested catch?
 
Anyways what I did was to add another nested try/catch statement in my main and it seems to work. Shouldn't the exception be cleared or deleted when caught. Weird. Im more of a pure C programmer at heart so please forgive me if I missed anything.
 
Great library anyways!
GeneralRe: further infomemberDavid Johns3 Nov '12 - 9:30 
The only try{}catch() blocks are catching const ECSmtp&. It must be that another object type is being thrown. You could put a catch(...) after the catch(const ECSmtp&) to catch any other object types that are thrown. This would isolate the issue that is crashing your program. If you find a good solution, please post back with the exact changes you propose.
 
Thanks,
David
QuestionDoes not work when GMAIL is setup with 2 factor authenticationmemberRichmond Umagat20 Oct '12 - 17:28 
Has anyone tried testing this with a GMAIL account configured to use 2-factor authentication?
GeneralWorking well with Visual C++ 2010memberTiago Mielczarski Germano16 Oct '12 - 11:06 
Very nice code!
Thank you very much!
QuestionAwesome codememberaldur_disciple25 Sep '12 - 21:51 
Your sample code saved me much time understanding the OpenSSL library.
Thanks a lot !
QuestionCompiling code in VC++ 6.0membersantu9 Sep '12 - 23:27 
Hi All,
 
Does anyone has the workspace working in VC++ 6.0? If yes please send. Otherwise, please send the source code of openssl used in the project so that i can build in VC++ 6.0 to eliminate linking errors.
 
It is urgent.
 
Thanks in advance,
 
Santosh
With regards
A.Santosh

AnswerRe: Compiling code in VC++ 6.0membervaibhavbvp10 Sep '12 - 22:40 
yeah i have used this in vc6.0 without any problem.if you are facing any such error then let me know,would try to resolve them.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130516.1 | Last Updated 6 May 2013
Article Copyright 2010 by David Johns, John_Tang
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid