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   
Generala major updatememberJohn TWC8 Aug '10 - 20:53 
A major update to the code has been posted. This update contains many fixes as mentioned in hector santos's comments.
GeneralMy vote of 5memberjaehyek17 Aug '10 - 13:07 
very useful article.
GeneralDetected memory Leaks at MFC Dialog app Using VS2008memberjaehyek17 Aug '10 - 3:07 
Hi. John TWC.
I was glad to use your source code .
So, I inserted your code into MFC Dialog App which use a button indicating "sending email".
It works fine.
But in debug mode, after executing with the app, Visual Studio 2008 dumps out the data like below.
 
Detected memory leaks!
Dumping objects ->
{4681} normal block at 0x01DAECB8, 12 bytes long.
Data: < > D0 EB DA 01 00 00 00 00 08 00 00 00
{4680} normal block at 0x01DAEC68, 16 bytes long.
Data: < > 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
{4679} normal block at 0x01DAEC18, 20 bytes long.
Data: < h > 00 00 00 00 68 EC DA 01 00 00 00 00 04 00 00 00
...
...
 
At this situation, How can I fix ?
 
In Advance, thank you.
GeneralRe: Detected memory Leaks at MFC Dialog app Using VS2008memberJohn TWC7 Aug '10 - 8:16 
Hi jaehyek1,
Thanks for pointing out this. This should be a bug of the OpenSSL library, see the OpenSSL faq. It says "In most cases the cause of an apparent memory leak is an OpenSSL internal table that is allocated when an application starts up. Since such tables do not grow in size over time they are harmless.". However the faq does give a workaround that we can use. First add #include "openssl/err.h" to the CSmtp.cpp, then in the function void CSmtp::CleanupOpenSSL(), add the following code at the end of its body:
    ERR_free_strings();
    EVP_cleanup();
    CRYPTO_cleanup_all_ex_data();
I have done some tests, and most of the leaks were removed. However, still six were reported by VS2008. Maybe the latest version of openssl has solved this, and I will try to use the latest one the next time I update the code.
Generalcongratulations [modified]memberJakub Piwowarczyk7 Aug '10 - 1:19 
It was a good idea to add SSL/TLS functionality to CSmtp class, because nowadays more and more servers use it. Who knows maybe in a few years it will be necessary to use encrypted connection with SMTP servers.
 
Be careful with sending too many mails to SMTP server, because your account could be blocked temporarily or for ever but it is well (see your account regulations). The class will be excelent to send for example notification from application or send SMS Smile | :) if your phone provider accepts it, i.e. <number>@provider.com
 
I have not tested your code yet. If I have more time I will do it for sure and send you my comments.
Jakub Piwowarczyk
modified on Saturday, August 7, 2010 7:27 AM

GeneralRe: congratulationsmemberJohn TWC7 Aug '10 - 8:50 
Thanks and thanks for your article. Sending SMS is an exciting idea Smile | :)
GeneralThe bat!memberhector santos5 Aug '10 - 5:09 
So this is where the "The Bat!" spams come from! Smile | :)
 
I have some comments.
 
1) It should use the last response line to get the reply code.
 
250-xxxxxxxx <--- its using this
250-xxxxxxxx
250 xxxxxxxx <--- should use this
 
While in theory, all reply codes SHOULD be the same for each line, there were discussions about using a continuation line as a KEEP ALIVE idea that may not be the same code as the final call.
 
Older RFC specifications where not clear on that matter and FTP which SMTP is based on does have a way to keep alive a client during a large data transfer.
 
However, Programs like this, especially old legacy SMTP clients did not allow KEEP ALIVE get the traction to officialy support it in the current RFC 5321 specification. But most modern SMTP clients do use using the last line (like OUTLOOK), so its possible to keep a client alive during a DATA "anti-smtp/virus" check which many believe was an interesting idea but legacy software prevent it from happening.
 
So while its probably ok to read the first line as the reply code, if you wanted this to support the FUTURE POSSIBILITY that a KEEP-ALIVE is reintroduced and the older clients die away, use the last line to get the reply code. Its the correct way anyway in my opinion and by doing this you won't run into trouble by a SMTP Server written to keep a client alive.
 
2) I noticed the timeouts is 60 second. Maybe I didn't read the code entirely, but if not, it should be 3-5 minutes depending on what STATE - see RFC 5322.
 
3) QUIT SHOULD also be issued regardless of completion. The Server waits for clients to issue to QUIT. The server (good ones) will never drop a client. However, if a server sees a client never issues a QUIT because something else failed, it could mark it as as a bad IP eventually. Follow the RFC - QUIT SHOULD always be issues. This is expecially true after sending the DATA as there are servers that have a NO-QUIT-CANCEL-MESSAGE policy.
 
4) The EHLO extensions are not checked.
 
It will try to use TLS even if the EHLO response does not say "STARTTLS" - that will cause a failure.
 
It will try to use AUTH even if the EHLO response does not have any AUTH extensions.
 
5) Minor, AUTH CRAM is not supported.
 
Many servers support CRAM, then again, under TLS, it is less important. Plain text is ok. So this is a minor issue.
 
Finally, testing it against our WCSMTP server, failed a number of times. I believe it is related to two things,
 
#1 above - not reading multiple lines correctly
 
#2 above - the I/O logic with timeouts and select logic is not very well written and will give you intermittent issues.
 
What I see for example is that EHLO command sees the buffer for the initial connection GREETING and/or if it gets pass the EHLO, the STARTTLS command sees the buffer for the EHLO response.
 
I suspect that MOST of the time it works for you because you are going over the wire which tends to slow things down. But if you try it against a local SMTP server on the same machine or on your LAN, then you will begin to see the I/O sychronization issues.
Hector Santos, CTO
Santronics Software, Inc.
http://www.santronics.com

GeneralRe: The bat!memberJohn TWC5 Aug '10 - 7:07 
Thank you so much for your insight and expertise! You are right, the code was not tested against a local server. I will try to modify the code according to your comments. Thanks again. Smile | :)
GeneralMy vote of 5memberprcarp5 Aug '10 - 2:24 
Exactly what I needed.
GeneralThanks!memberprcarp5 Aug '10 - 2:23 
About two weeks ago, I downloaded the same CSmtp from Jakub (based on the CFastSmtp) and was looking into wiring in OpenSSL. You beat me to the punch and saved me a bunch of work!
 
Thanks so much!
GeneralRe: Thanks!memberJohn TWC5 Aug '10 - 5:01 
Glad you liked it Smile | :)
GeneralMy vote of 5memberTomice4 Aug '10 - 4:02 
Excellent
GeneralRe: My vote of 5memberJohn TWC4 Aug '10 - 6:13 
Thanks you like it.
GeneralMy vote of 5memberAescleal4 Aug '10 - 1:13 
Scratches an itch a lot of C programmers have. Thanks for sharing.
GeneralRe: My vote of 5memberJohn TWC4 Aug '10 - 2:14 
Thanks
GeneralMy vote of 3memberAric Green1 Aug '10 - 15:49 
Work hard...Create something your own.
GeneralRe: My vote of 3memberJohn TWC2 Aug '10 - 1:26 
Hi Aric,
Thanks. Many people (like me) search the internet for c++ code that can send mails using smtp but often find that the code they found only works with unencrypted smtp. I was not able to find one that works with encrypted smtp, so I decided to write one on my own. It took me much time to study ssl/openssl, and to make the code work with all the smtp servers listed in the article. So this article and code is for people who have the same requirement but don't have enough time to study ssl/openssl (if they are not familiar with it before) and to do a lot of tests.
GeneralRe: My vote of 3memberyarp3 Aug '10 - 19:04 
Looks like that article is just about something like that since he did research about SSL and added a feature which didn't exist before.
 
It make no sense to rewrite a whole SMTP class when there is already a good one available. On the contrary improving or completing existing code is the right approach.
Yarp
http://www.senosoft.com/

GeneralRe: My vote of 3memberJohn TWC4 Aug '10 - 2:12 
Thanks Smile | :)
GeneralBlog entrymvpMd. Marufuzzaman31 Jul '10 - 15:26 
This could be a good blog entry...
Thanks
Md. Marufuzzaman


I will not say I have failed 1000 times; I will say that I have discovered 1000 ways that can cause failure – Thomas Edison.

GeneralRe: Blog entrymemberJohn TWC31 Jul '10 - 19:17 
Thanks Smile | :)
GeneralRe: Blog entrymvpMd. Marufuzzaman31 Jul '10 - 20:10 
wc
Thanks
Md. Marufuzzaman


I will not say I have failed 1000 times; I will say that I have discovered 1000 ways that can cause failure – Thomas Edison.

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

Permalink | Advertise | Privacy | Mobile
Web03 | 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