65.9K
CodeProject 正在变化。 阅读更多。
Home

带 SSL/TLS 的 SMTP 客户端

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (131投票s)

2010 年 8 月 3 日

CPOL

7分钟阅读

viewsIcon

2085331

downloadIcon

41172

C++ SMTP 客户端,支持 SSL 和 TLS 加密连接到 SMTP 服务器

引言

我需要在用 C++ 编写的产品中发送电子邮件,所以我搜索了互联网,并找到了一篇很棒的文章:《SMTP 客户端》,由 Jakub Piwowarczyk 撰写。然而,我的许多客户使用的 SMTP 服务器需要安全连接(TLS 或 SSL),而 SMTP 客户端不支持。因此,我必须为《SMTP 客户端》中的 CSmtp 类添加 SSL/TLS 支持,然后才能在我的产品中使用它。由于我对 SSL/OpenSSL 是新手,花了我不少时间才学会如何正确使用它,并使代码能够与几个流行的 SMTP 服务器协同工作。我还看到人们在互联网上搜索 C++ 实现的 SMTP/SSL/TLS,但就是找不到。所以我决定分享我写的这个,希望它能为不熟悉 SSL 的人们节省一些时间。

请注意,本文不涉及 SMTP 的详细信息。如果您需要了解更多关于 SMTP 的信息,请参阅原文《SMTP 客户端》。

背景

SMTP 有两种安全连接方式,一种是 SSL,另一种是 TLS。有些 SMTP 服务器只支持一种,有些则两种都支持。总的来说,SSL 的端口通常是 465,TLS 的端口通常是 587,但这并非总是如此。除了端口不同之外,SMTP/SSL 与 SMTP/TLS 的区别在于,SMTP/SSL 在底层 TCP 连接建立后立即协商加密连接,而 SMTP/TLS 要求客户端在协商加密连接之前向服务器发送 STARTLS 命令。

SMTP/SSL 所涉及的步骤如下:

  1. 客户端通过 TCP 连接到服务器。
  2. 客户端与服务器协商加密连接。
  3. 服务器通过加密连接向客户端发送欢迎消息。
  4. 客户端通过加密连接向服务器发送 EHLO 命令。
  5. 服务器通过加密连接响应 EHLO 命令。

SMTP/TLS 所涉及的步骤如下:

  1. 客户端通过 TCP 连接到服务器。
  2. 服务器通过未加密的连接向客户端发送欢迎消息。
  3. 客户端通过未加密的连接向服务器发送 EHLO 命令。
  4. 服务器通过未加密的连接响应 EHLO 命令。
  5. 客户端通过未加密的连接向服务器发送 STARTTLS 命令。
  6. 服务器通过未加密的连接响应 STARTTLS 命令。
  7. 客户端与服务器协商加密连接。
  8. 客户端通过加密连接向服务器发送 EHLO 命令。
  9. 服务器通过加密连接响应 EHLO 命令。

Using the Code

我在示例代码中使用了 openssl(http://www.openssl.org)。示例代码中的 "openssl-0.9.8l" 目录包含所有必需的头文件和两个预编译的 静态 openssl 库。如果您也想在您的代码中使用此版本的 openssl,请务必将整个 "openssl-0.9.8l" 目录复制到您的项目根目录,并将 "openssl-0.9.8l\inc32" 添加到 "其他包含目录",还将 "openssl-0.9.8l\out32" 添加到 "其他库目录"。

如果您想自己构建 openssl,请参考 http://www.openssl.org 获取详细说明。

#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();

如果您使用非特权用户帐户测试 Yahoo,邮件将发送失败。Yahoo SMTP 服务器返回的错误消息是 "530 Access denied: Free users cannot access this server"(530 访问被拒绝:免费用户无法访问此服务器)。

注释

  • 代码不验证服务器身份,也就是说,它不检查服务器证书。如果我们确保向程序提供正确的服务器地址,这通常不是一个大问题。然而,如果我们在不检查证书的情况下,仍然有可能与冒充者通信,这一点值得一提。
  • 不允许使用本文中的代码进行垃圾邮件发送。

参考文献

历史

  • 版本 2.4,2015/10/22

感谢大家有效的众包!请继续改进我们的库!

  • 正如 这里讨论的,修复了文件被遗留打开以及在发生错误时缓冲区未被删除的问题。感谢 Josep Solà!
  • 修复了打开附件时出现的问题,如 这里讨论的。感谢 Graham!
  • 修复了潜在的内存泄漏问题,如 这里讨论的。感谢 LahPo!
  • 这里推荐的,扩大了总消息大小限制。感谢 Stanislav!
  • 修复了附件文件路径不完整的问题,如 这里讨论的。感谢 Member 11508846 和 Member 11887128!

感谢大家有效的众包!请继续改进我们的库!

  • 根据 GKarRacer 贡献的修复,修复了在处理消息正文的某一行之前,对 MsgBody.size() 的范围检查不正确的问题。
  • 将内存分配和检查所有附件是否可以打开的操作移到了 MAIL 命令发出之前,这样如果其中一个附件有问题,就可以优雅地中止发送邮件,而不会在没有附件的情况下发送邮件。
  • 将所有 sprintf 命令更改为 snprintf 以增加安全性。为 MS Visual C 上的 snprintf 宏定义为 sprintf_s。由于 MS Visual C 版的 strcpy_s 函数参数顺序不同,无法在不影响移植性的情况下使用它,因此还更改了大多数 strcpy 调用为 snprintf
  • 根据 jcyangzh 贡献的修复,修复了 SayQuit 函数中可能出现的无限循环问题。
  • 根据 sbrytskyy 贡献的修复,添加了使 AUTH PLAIN 登录正常工作所需的修复。
  • 版本 2.1,2012/11/06
  • 版本 2.0,2011/06/23
    • 添加了 m_bAuthenticate 成员变量,以便能够禁用身份验证,即使服务器可能支持它。它默认为 true,因此如果未设置,库将按以前的方式工作。
    • 将安全类型 m_type、新的 m_Authenticate 标志、登录名和密码添加到 ConnectRemoteServer 函数中。如果调用中不包含这些新参数,函数将按以前的方式工作。
    • 将新的 m_Authenticate 标志添加到 SetSMTPServer 函数中。如果未提供,函数将按以前的方式工作。
    • 根据 Martin Kjallman 贡献的修复
    • 根据 Karpov Andrey 贡献的修复
    • 根据 Jakub Piwowarczyk 贡献的修复
  • 版本 1.9,2010/08/19
    • 添加了 PLAIN、CRAM-MD5 和 DIGESTMD5 授权。
    • 添加了 DisconnectRemoteServer() 函数,并重新配置了 Send() 函数,使得如果您已经调用了 ConnectRemoveServer() 函数,它将使用现有连接并保持连接打开。这允许您调用 ConnectRemoteServer(),然后在同一连接上发送多条消息。如果使用此方法,您必须调用 DisconnectRemoteServer() 来关闭连接。如果您在未调用 ConnectRemoteServer() 的情况下调用 Send(),它将在发送后关闭连接。此更改应完全向后兼容。
  • 版本 1.8,2010/08/09
  • 2010/08/03
    • 修改了引言。
  • 2010/08/02
    • 添加了注释。
  • 2010/08/01
    • 初次发布
© . All rights reserved.