C++实现自动发送邮件,基于SMTP协议,支持SSL/TLS

支持发送多人,抄送多人,支持发送附件

邮件协议简单介绍

SMTP

SMTP (Simple Mail Transfer Protocol),即简单邮件传输协议,默认端口是25,通过SSL协议加密之后的默认端口是465。正如名字所暗示的那样,它其实是一个非常简单的传输协议,最初是无需身份认证,而且发件人的邮箱地址是可以由发信方任意声明的,利用这个特性可以伪造任意发件人。它是用于从源地址到目的地址传输邮件的协议,通过它来控制邮件的中转方式。SMTP 协议属于 TCP/IP 协议簇,它帮助每台计算机在发送或中转信件时找到下一个目的地。SMTP是一个 “推” 的协议,它不允许根据需要从远程服务器上 “拉” 来消息。
SMTP 认证:简单地说就是要求必须在提供了账户名和密码之后才可以登录 SMTP 服务器,这就使得那些垃圾邮件的散播者无可乘之机。增加 SMTP 认证的目的是为了使用户避免受到垃圾邮件的侵扰。

POP

POP(Post Office Protocol)邮局通讯协议,POP是互联网上的一种通讯协议,主要功能是用在接收电子邮件时。当我们寄信给另外一个人时,对方当时多半不会在线上,所以邮件服务器必须为收信者保存这封信,直到收信者来检查这封信件。当收信人收信的时候,必须通过POP通讯协定,才能取得邮件。

POP3是Post Office Protocol 3的简称,即邮局协议的第3个版本,是TCP/IP协议族中的一员,默认端口是110,通过SSL协议加密之后的默认端口是995。POP3协议主要用于支持使用客户端远程管理在服务器上的电子邮件。它规定怎样将个人计算机连接到Internet的邮件服务器和下载电子邮件的电子协议。它是因特网电子邮件的第一个离线协议标准,POP3允许用户从服务器上把邮件存储到本地主机(即自己的计算机)上,但是对邮件的操作并不会反馈到邮箱服务器上。

IMAP

IMAP (Internet Mail Access Protocol),即交互式邮件存取协议,是一个应用层协议,默认端口是143,通过SSL协议加密之后的默认端口是993。用来从本地邮件客户端,如Outlook Express、Foxmail、Mozilla Thunderbird等访问远程服务器上的邮件。它和POP3类似邮件访问标准协议类似。不同的是,开启了IMAP后,您在电子邮件客户端收取的邮件仍然保留在服务器上,同时在客户端上的操作都会反馈到服务器上,如:删除邮件,标记已读等,服务器上的邮件也会做相应的动作。同时,IMAP像POP3那样提供了方便的邮件下载服务,让用户能进行离线阅读。IMAP提供的摘要浏览功能可以让你在阅读完所有的邮件到达时间、主题、发件人、大小等信息后才作出是否下载的决定。此外,IMAP 更好地支持了从多个不同设备中随时访问新邮件。所以无论从浏览器登录邮箱或者客户端软件登录邮箱,看到的邮件以及状态都是一致的。也就是说,IMAP提供了比POP3更为强大的功能。

SMTP协议的定义:

1、SMTP 是一种TCP协议支持的提供可靠且有效电子邮件传输的应用层协议;
2、SMTP 是建立在 TCP上的一种邮件服务,主要用于传输系统之间的邮件信息并提供来信有关的通知;
3、SMTP 独立于特定的传输子系统,且只需要可靠有序的数据流信道支持;
4、SMTP 重要特性之一是其能跨越网络传输邮件,即“ SMTP 邮件中继”;
5、SMTP是一个相对简单的基于文本的协议。
 

SSL和TLS的概念与区别

什么是SSL?

SSL(Secure Socket Layer,安全套接字层),为Netscape所研发,用以保障在Internet上数据传输之安全,利用数据加密(Encryption)技术,可确保数据在网络上之传输过程中不会被截取。当前版本为3.0。它已被广泛地用于Web浏览器与服务器之间的身份认证和加密数据传输。

SSL协议位于TCP/IP协议与各种应用层协议之间,为数据通讯提供安全支持。

SSL协议可分为两层: SSL记录协议(SSL Record Protocol):它建立在可靠的传输协议(如TCP)之上,为高层协议提供数据封装、压缩、加密等基本功能的支持。

SSL握手协议(SSL Handshake Protocol):它建立在SSL记录协议之上,用于在实际的数据传输开始前,通讯双方进行身份认证、协商加密算法、交换加密密钥等。

什么是TLS?

TLS(Transport Layer Security,传输层安全协议),用于两个应用程序之间提供保密性和数据完整性。

TLS 1.0是IETF(Internet Engineering Task Force,Internet工程任务组)制定的一种新的协议,它建立在SSL 3.0协议规范之上,是SSL 3.0的后续版本,可以理解为SSL 3.1(可简单理解为同一事物不同阶段的不同称呼),它是写入了 RFC 的。

该协议由两层组成: TLS 记录协议(TLS Record)和 TLS 握手协议(TLS Handshake)。较低的层为 TLS 记录协议,位于某个可靠的传输协议(例如 TCP)上面。

SSL和TLS的主要区别?
TLS的主要目标是使SSL更安全,并使协议的规范更精确和完善。另外,TLS版本号也与SSL的不同。

邮件传送基本流程:

邮件传送主要包括3个阶段: 建立连接 、 邮件传送 和 终止连接 。

SMTP基本流程包括以下几个命令:HELO﹑EHLO、MAIL﹑RCPT﹑DATA、AUTH LOGIN和QUIT

建立连接阶段:

1、当SMTP客户端每隔一定时间对邮件缓存扫描一次,如发现有邮件,就使用SMTP的熟知端口号25与接收方的邮件服务器的SMTP服务器建立TCP连接。

2、接收方SMTP服务器发出“220 Service ready"告诉客户端它已经准备好接收邮件。若服务器未就绪,它就发送代码421(服务器不可用)。

3、客户发送HELO报文命令,并使用它的域名地址标志自己。目的是:用来把客户的域名通知服务器,值得注意的是, 在TCP的连接建立阶段,发送方和接收方都是通过它们的IP地址来告诉对方的 。(HELO报文是最初的,用户名和密码都不加密。现在改为EHLO,用户名和密码都进行base64编码发送)。

4、服务器响应代码250(请求命令完成)或根据情况的其他一些代码。

5、AUTH LOGIN–登录邮箱,这一部分一般要用base64加密。

6、服务器响应代码235(请求命令完成)或根据情况的其他一些代码。

邮件传送阶段:

在SMTP客户与服务器之间建立连接后,发件人就可以与一个或多个收件人交换单个的报文了。若收件人超过一个,则下面步骤3和步骤4将重复进行。

1、客户发送MAIL FROM报,这个命令用来开始传送邮件,它的后面跟随发件方邮件地址(返回邮件地址)。它也用来当邮件无法送达时,发送失败通知。为保证邮件的成功发送,发件方的地址应是被对方或中间转发方同意接受的。这个命令会清空有关的缓冲区,为新的邮件做准备。

2、服务器响应代码250(请求命令完成)或其他适当的代码。

3、RCPT –这个命令告诉收件方收件人的邮箱。当有多个收件人时,需要多次使用该命令RCPT
4、TO,每次只能指明一个人。如果接收方服务器不同意转发这个地址的邮件,它必须报550错误代码通知发件方。如果服务器同意转发,它要更改邮件发送路径,把最开始的目的地(该服务器)换成下一个服务器。

5、服务器响应代码250或其他适当的代码。

6、DATA–收件方把该命令之后的数据作为发送的数据。数据被加入数据缓冲区中,以单独一行是”.”的行结束数据。结束行对于接收方同时意味立即开始缓冲区内的数据传送,传送结束后清空缓冲区。DATA命令表示要开始传送邮件的内容了。

7、如果传送接受,接收方回复OK,服务器响应代码"354 Start mail input: end with <CRLF>.<CRLF>"或其他适当的报文(如421 服务器不可用,500 命令无法识别)。

8、客户用连续的行发送报文的内容。每一行的行结束时输入 <CRLF><CRLF> ,即回车换行回车换行,表示邮件内容换行,发送的附件内容也在此命令中。最后一行的行结束时输入 <CRLF>.<CRLF> ,即回车换行.回车换行,表示邮件内容结束。

9、服务器响应代码(250 请求命令完成)或其他适当的代码。

值得注意的是:虽然SMTP使用TCP连接试图使邮件的传送可靠,但它并不能保证不丢失邮件。也就是说,使用SMTP传送邮件只能说可以可靠地传送接收方的邮件服务器,在往后的情况就不知道了。接收方的邮件服务器也许会出故障,使收到的服务器全部丢失(在收件人读取信件之前)。

终止连接阶段:

在报文传送成功后,客户就终止连接。包括如下步骤:

1、QUIT–SMTP要求接收放必须回答OK,然后中断传输;在收到这个命令并回答OK前,收件方不得中断连接,即使传输出现错误。发件方在发出这个命令并收到OK答复前,也不得中断连接

2、服务器响应221(服务关闭)或其他代码。

代码实现

头文件

#pragma once
#include <string>
#include <vector>

#define WITH_OPENSSL 1

#if WITH_OPENSSL
#include <openssl/ossl_typ.h>
#endif


class EmailAccess
{
public:
    EmailAccess();
    EmailAccess(const std::string& strhost, int iport);
    ~EmailAccess();

private:
    //
    std::string m_strHost;
    int m_iPort = 0;
    //
#if WITH_OPENSSL
    SSL_CTX* m_ctx = nullptr;
    SSL* m_ssl = nullptr;
#endif
    //
    std::vector<std::string> m_vetTolist;
    std::vector<std::string> m_vetCclist;
    std::vector<std::string> m_vetAttachlist;

public:

private:
    int InitNetwork(void);
    //
    const char* GetMimeType(const char* pFileExt);
    static bool ReadFileContent(const char* pflnm, std::string& stout);
    static const std::string Base64Encode(char const* psrc, unsigned srclen);
    int RecvData(char* pbuf, int len);
    int SendData(const char* pbuf, int len);

public:
    int EmailConnect(void);
    void EmailDisconnect(void);
    void SetToList(const std::vector<std::string>& tolist);
    void SetCcList(const std::vector<std::string>& cclist);
    void SetAttachmentList(const std::vector<std::string>& attachlist);
    int SendEmail(const std::string& strfrom, const std::string& strpswd, const std::string& strto, const std::string& strsubj, const std::string& strbody);
    //
    int SendTestEmail(const std::string& strto, const std::string& strcode);
};

源文件

#include "EmailAccess.h"
#include <sstream>
#include <filesystem>
#include <WinSock2.h>
#include <WS2tcpip.h>
#if WITH_OPENSSL
#include <openssl/ssl.h>
#endif
#include <random>
#include <fstream>
#include <sstream>
#include "LogView.h"


#pragma comment(lib, "ws2_32.lib")
#if WITH_OPENSSL
#pragma comment(lib, "libcrypto.lib")
#pragma comment(lib, "libssl.lib")
#endif


SOCKET m_sckEmail = INVALID_SOCKET;
addrinfo* otAddrInfo = nullptr;

const char MimeTypes[][2][128] =
{
    { "***",    "application/octet-stream" },
    { "csv",    "text/csv" },
    { "tsv",    "text/tab-separated-values" },
    { "tab",    "text/tab-separated-values" },
    { "html",    "text/html" },
    { "htm",    "text/html" },
    { "doc",    "application/msword" },
    { "docx",    "application/vnd.openxmlformats-officedocument.wordprocessingml.document" },
    { "ods",    "application/x-vnd.oasis.opendocument.spreadsheet" },
    { "odt",    "application/vnd.oasis.opendocument.text" },
    { "rtf",    "application/rtf" },
    { "sxw",    "application/vnd.sun.xml.writer" },
    { "txt",    "text/plain" },
    { "xls",    "application/vnd.ms-excel" },
    { "xlsx",    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" },
    { "pdf",    "application/pdf" },
    { "ppt",    "application/vnd.ms-powerpoint" },
    { "pps",    "application/vnd.ms-powerpoint" },
    { "pptx",    "application/vnd.openxmlformats-officedocument.presentationml.presentation" },
    { "wmf",    "image/x-wmf" },
    { "atom",    "application/atom+xml" },
    { "xml",    "application/xml" },
    { "json",    "application/json" },
    { "js",    "application/javascript" },
    { "ogg",    "application/ogg" },
    { "ps",    "application/postscript" },
    { "woff",    "application/x-woff" },
    { "xhtml",    "application/xhtml+xml" },
    { "xht",    "application/xhtml+xml" },
    { "zip",    "application/zip" },
    { "gz",    "application/x-gzip" },
    { "rar",    "application/rar" },
    { "rm",    "application/vnd.rn-realmedia" },
    { "rmvb",    "application/vnd.rn-realmedia-vbr" },
    { "swf",    "application/x-shockwave-flash" },
    { "au",     "audio/basic" },
    { "snd",    "audio/basic" },
    { "mid",    "audio/mid" },
    { "rmi",    "audio/mid" },
    { "mp3",    "audio/mpeg" },
    { "aif",    "audio/x-aiff" },
    { "aifc",    "audio/x-aiff" },
    { "aiff",    "audio/x-aiff" },
    { "m3u",    "audio/x-mpegurl" },
    { "ra",    "audio/vnd.rn-realaudio" },
    { "ram",    "audio/vnd.rn-realaudio" },
    { "wav",    "audio/x-wave" },
    { "wma",    "audio/x-ms-wma" },
    { "m4a",    "audio/x-m4a" },
    { "bmp",    "image/bmp" },
    { "gif",    "image/gif" },
    { "jpe",    "image/jpeg" },
    { "jpeg",    "image/jpeg" },
    { "jpg",    "image/jpeg" },
    { "jfif",    "image/jpeg" },
    { "png",    "image/png" },
    { "svg",    "image/svg+xml" },
    { "tif",    "image/tiff" },
    { "tiff",    "image/tiff" },
    { "ico",    "image/vnd.microsoft.icon" },
    { "css",    "text/css" },
    { "bas",    "text/plain" },
    { "c",      "text/plain" },
    { "h",      "text/plain" },
    { "rtx",    "text/richtext" },
    { "mp2",    "video/mpeg" },
    { "mpa",    "video/mpeg" },
    { "mpe",    "video/mpeg" },
    { "mpeg",    "video/mpeg" },
    { "mpg",    "video/mpeg" },
    { "mpv2",    "video/mpeg" },
    { "mov",    "video/quicktime" },
    { "qt",    "video/quicktime" },
    { "lsf",    "video/x-la-asf" },
    { "lsx",    "video/x-la-asf" },
    { "asf",    "video/x-ms-asf" },
    { "asr",    "video/x-ms-asf" },
    { "asx",    "video/x-ms-asf" },
    { "avi",    "video/x-msvideo" },
    { "3gp",    "video/3gpp" },
    { "3gpp",    "video/3gpp" },
    { "3g2",    "video/3gpp2" },
    { "movie",    "video/x-sgi-movie" },
    { "mp4",    "video/mp4" },
    { "wmv",    "video/x-ms-wmv" },
    { "webm",    "video/webm" },
    { "m4v",    "video/x-m4v" },
    { "flv",    "video/x-flv" }
};



EmailAccess::EmailAccess()
    : m_strHost("smtp.163.com")
    , m_iPort(465)
{
    InitNetwork();
}

EmailAccess::EmailAccess(const std::string& strhost, int iport)
    : m_strHost(strhost)
    , m_iPort(iport)
{
    InitNetwork();
}

EmailAccess::~EmailAccess()
{
    WSACleanup();
}


int EmailAccess::InitNetwork(void)
{
    WSADATA wsaData;
    int ret = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (ret != 0)
    {
        LOGE("Init Netwinsock FAILED!!!");
        return ret;
    }
    return 0;
}

const char* EmailAccess::GetMimeType(const char* pFileExt)
{
    for (unsigned int i = 0, nsz = GETARRSIZE(MimeTypes); i < nsz; i++)
    {
        if (strcmp(MimeTypes[i][0], pFileExt) == 0)
        {
            return MimeTypes[i][1];
        }
    }
    return MimeTypes[0][1];
}

bool EmailAccess::ReadFileContent(const char* pflnm, std::string& stout)
{
    std::ifstream fsread(pflnm);
    if (fsread.is_open())
    {
        std::stringstream sstjson;
        sstjson << fsread.rdbuf();
        stout = sstjson.str();
        fsread.close();
        return true;
    }
    return false;
}

const std::string EmailAccess::Base64Encode(char const* psrc, unsigned srclen)
{
    static const char base64Char[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    unsigned char const* porig = (unsigned char const*)psrc; // in case any input bytes have the MSB set
    if (porig == nullptr)
        return "";

    unsigned const numOrig24BitValues = srclen / 3;
    bool havePadding = srclen > numOrig24BitValues * 3;
    bool havePadding2 = srclen == numOrig24BitValues * 3 + 2;
    unsigned const numResultBytes = 4 * (numOrig24BitValues + havePadding);
    char* result = new char[numResultBytes + 3]; // allow for trailing '/0'

    // Map each full group of 3 input bytes into 4 output base-64 characters:
    unsigned i;
    for (i = 0; i < numOrig24BitValues; ++i)
    {
        result[4 * i + 0] = base64Char[(porig[3 * i] >> 2) & 0x3F];
        result[4 * i + 1] = base64Char[(((porig[3 * i] & 0x3) << 4) | (porig[3 * i + 1] >> 4)) & 0x3F];
        result[4 * i + 2] = base64Char[((porig[3 * i + 1] << 2) | (porig[3 * i + 2] >> 6)) & 0x3F];
        result[4 * i + 3] = base64Char[porig[3 * i + 2] & 0x3F];
    }

    // Now, take padding into account.  (Note: i == numOrig24BitValues)
    if (havePadding)
    {
        result[4 * i + 0] = base64Char[(porig[3 * i] >> 2) & 0x3F];
        if (havePadding2)
        {
            result[4 * i + 1] = base64Char[(((porig[3 * i] & 0x3) << 4) | (porig[3 * i + 1] >> 4)) & 0x3F];
            result[4 * i + 2] = base64Char[(porig[3 * i + 1] << 2) & 0x3F];
        }
        else
        {
            result[4 * i + 1] = base64Char[((porig[3 * i] & 0x3) << 4) & 0x3F];
            result[4 * i + 2] = '=';
        }
        result[4 * i + 3] = '=';
    }

    result[numResultBytes] = '\0';
    std::string strret(result, numResultBytes);
    delete[] result;
    return strret;
}

int EmailAccess::RecvData(char* pbuf, int len)
{
    int ret = 0;
#if WITH_OPENSSL
    ret = SSL_read(m_ssl, pbuf, len);
#else
    ret = recv(m_sckEmail, pbuf, len, 0);
#endif
    return ret;
}

int EmailAccess::SendData(const char* pbuf, int len)
{
    int ret = 0;
#if WITH_OPENSSL
    ret = SSL_write(m_ssl, pbuf, len);
#else
    ret = send(m_sckEmail, pbuf, len, 0);
#endif
    return ret;
}

int EmailAccess::EmailConnect(void)
{
    m_sckEmail = socket(AF_INET, SOCK_STREAM, 0);
    if (m_sckEmail == INVALID_SOCKET)
    {
        LOGE("Creating EmailSocket FAILED!!!");
        return 2;
    }
    //
    addrinfo inAddrInfo = { 0 };
    inAddrInfo.ai_family = AF_INET;
    inAddrInfo.ai_socktype = SOCK_STREAM;
    //LOGD("host={}, port={}", m_strHost, m_iPort);
    int iret = getaddrinfo(m_strHost.c_str(), std::to_string(m_iPort).c_str(), &inAddrInfo, &otAddrInfo);
    if (iret != 0) // error occurs
    {
        LOGE("Calling getaddrinfo() FAILED!!!");
        return iret;
    }
    //
    iret = connect(m_sckEmail, otAddrInfo->ai_addr, otAddrInfo->ai_addrlen);
    if (iret != 0) // error occurs
    {
        LOGE("Calling connect EamilSocket FAILED!!!");
        return iret;
    }
    //
#if WITH_OPENSSL
    SSL_library_init();
    OpenSSL_add_all_algorithms();
    SSL_load_error_strings();
    m_ctx = SSL_CTX_new(SSLv23_client_method());
    //
    m_ssl = SSL_new(m_ctx);
    SSL_set_fd(m_ssl, m_sckEmail);
    SSL_connect(m_ssl);
#endif
    //
    char arbuf[64] = { 0 };
    iret = RecvData(arbuf, 63);
    LOGD("ConnectRecv={}={}", arbuf, iret);
    if (strncmp(arbuf, "220", 3) != 0) // conn failed
    {
        return 220;
    }
    //
    return 0;
}

void EmailAccess::EmailDisconnect(void)
{
#if WITH_OPENSSL
    SSL_shutdown(m_ssl);
    SSL_free(m_ssl);
    SSL_CTX_free(m_ctx);
#endif
    //
    if (m_sckEmail != INVALID_SOCKET)
    {
        freeaddrinfo(otAddrInfo);
        closesocket(m_sckEmail);
        m_sckEmail = INVALID_SOCKET;
    }
}

void EmailAccess::SetToList(const std::vector<std::string>& tolist)
{
    m_vetTolist = tolist;
}

void EmailAccess::SetCcList(const std::vector<std::string>& cclist)
{
    m_vetCclist = cclist;
}

void EmailAccess::SetAttachmentList(const std::vector<std::string>& attachlist)
{
    m_vetAttachlist = attachlist;
}

int EmailAccess::SendEmail(const std::string& strfrom, const std::string& strpswd, const std::string& strto, const std::string& strsubj, const std::string& strbody)
{
    std::string strcmd = "EHLO EmailService\r\n";
    int iret = SendData(strcmd.c_str(), strcmd.size());
    //LOGD("EhloSend={}={}", strcmd, iret);
    char arbuf[1024] = { 0 };
    const int cibufsz = sizeof(arbuf) - 1;
    iret = RecvData(arbuf, cibufsz);
    LOGD("EhloRecv={}={}", arbuf, iret);
    if (strncmp(arbuf, "250", 3) != 0) // ehlo failed
    {
        return 250;
    }
    //
#if 1
    strcmd = "AUTH PLAIN ";
    std::string strauth = '\0' + strfrom + '\0' + strpswd;
    strcmd += Base64Encode(strauth.data(), strauth.size());
    strcmd += "\r\n";
    iret = SendData(strcmd.c_str(), strcmd.size());
    //LOGD("AuthSend={}={}", strcmd, iret);
    iret = RecvData(arbuf, cibufsz);
    LOGD("AuthRecv={}={}", arbuf, iret);
    if (strncmp(arbuf, "235", 3) != 0) // auth failed
    {
        return 235;
    }
#else
#endif
    //
    strcmd = "MAIL FROM:<" + strfrom + ">\r\n";
    iret = SendData(strcmd.c_str(), strcmd.size());
    //LOGD("FromSend={}={}", strcmd, iret);
    iret = RecvData(arbuf, cibufsz);
    LOGD("FromRecv={}={}", arbuf, iret);
    if (strncmp(arbuf, "250", 3) != 0) // mail failed
    {
        return 250;
    }
    //
    strcmd = "RCPT TO:<" + strto + ">\r\n";
    iret = SendData(strcmd.c_str(), strcmd.size());
    //LOGD("ToSend={}={}", strcmd, iret);
    iret = RecvData(arbuf, cibufsz);
    LOGD("ToRecv={}={}", arbuf, iret);
    if (strncmp(arbuf, "250", 3) != 0) // rcpt failed
    {
        return 250;
    }
    //
    strcmd = "DATA\r\n";
    iret = SendData(strcmd.c_str(), strcmd.size());
    //LOGD("DataSend={}={}", strcmd, iret);
    iret = RecvData(arbuf, cibufsz);
    LOGD("DataRecv={}={}", arbuf, iret);
    if (strncmp(arbuf, "354", 3) != 0) // data failed
    {
        return 354;
    }
    std::string strcharset = "UTF-8";
    std::ostringstream strmsg;
    strmsg << "From: =?" << strcharset << "?b?" << Base64Encode(strfrom.c_str(), strfrom.length()) << "?= <" << strfrom << ">\r\n";
    strmsg << "To: " << "=?" + strcharset + "?b?" + Base64Encode(strto.c_str(), strto.length()) + "?= <" + strto + ">";
    for (const auto& ite : m_vetTolist)
    {
        strmsg << ", " << "=?" + strcharset + "?b?" + Base64Encode(ite.c_str(), ite.length()) + "?= <" + ite + ">";
    }
    strmsg << "\r\n";
    if (m_vetCclist.size() > 0)
    {
        strmsg << "Cc:";// << "=?" + strcharset + "?b?" + Base64Encode(strto.c_str(), strto.length()) + "?= <" + strto + ">";
        for (const auto& ite : m_vetCclist)
        {
            strmsg << ", " << "=?" + strcharset + "?b?" + Base64Encode(ite.c_str(), ite.length()) + "?= <" + ite + ">";
        }
        strmsg << "\r\n";
    }
    //strmsg << "Bcc:" << "=?" + charset + "?b?" + Base64Encode(strto.c_str(), strto.length()) + "?= <" + strto + ">" << "\r\n";
    strmsg << "Subject: =?" << strcharset << "?b?" << Base64Encode(strsubj.c_str(), strsubj.length()) << "?=\r\n";
    strmsg << "MIME-Version: 1.0\r\n";
    strmsg << "Content-Type:multipart/mixed; boundary=\"Separator_ztq_000\"\r\n\r\n";
    strmsg << "--Separator_ztq_000\r\n";
    strmsg << "Content-Type: multipart/alternative; boundary=\"Separator_ztq_111\"\r\n\r\n";
    strmsg << "--Separator_ztq_111\r\n";
    strmsg << "Content-Type: " << "text/plain" << "; charset=\"" << strcharset << "\"\r\n";
    strmsg << "Content-Transfer-Encoding: base64\r\n";
    strmsg << "\r\n";
    strmsg << Base64Encode(strbody.c_str(), strbody.length());
    strmsg << "\r\n\r\n";
    strmsg << "--Separator_ztq_111--\r\n";
    //attachment....
    for (const auto& ite : m_vetAttachlist)
    {
        std::filesystem::path attpath(ite);
        auto strFileName = attpath.filename().string();
        std::string strFileCont;
        bool bret = ReadFileContent(ite.c_str(), strFileCont);
        if (bret && !strFileCont.empty())
        {
            std::string fileContext = Base64Encode(strFileCont.c_str(), strFileCont.length());
            std::string extension = attpath.extension().string();
            std::string mimetype = GetMimeType(extension.c_str());
            strmsg << "--Separator_ztq_000\r\n";
            strmsg << "Content-Type: " << mimetype << "; name=\"" << strFileName << "\"\r\n";
            strmsg << "Content-Transfer-Encoding: base64\r\n";
            strmsg << "Content-Disposition: attachment; filename=\"" << strFileName << "\"\r\n\r\n";
            strmsg << fileContext + "\r\n\r\n";
        }
        else
        {
            LOGE("Invalid Email attachment!={}", ite);
        }
    }
    strmsg << "\r\n.\r\n";
    //
    strcmd = strmsg.str();
    iret = SendData(strcmd.c_str(), strcmd.size());
    //LOGD("SubjSend={}={}", strcmd, iret);
    iret = RecvData(arbuf, cibufsz);
    LOGD("SubjRecv={}={}", arbuf, iret);
    if (strncmp(arbuf, "250", 3) != 0) // subj failed
    {
        return 250;
    }
    //
    strcmd = "QUIT\r\n";
    iret = SendData(strcmd.c_str(), strcmd.size());
    //LOGD("QuitSend={}={}", strcmd, iret);
    iret = RecvData(arbuf, cibufsz);
    LOGD("QuitRecv={}={}", arbuf, iret);
    //if (strncmp(arbuf, "221", 3) != 0) // quit failed
    //{
    //    return 221;
    //}
    //
    return 0;
}

int EmailAccess::SendTestEmail(const std::string& strto, const std::string& strcode)
{
    int iret = EmailConnect();
    iret = SendEmail("fromxxx@163.com", "*****", strto, "TestEmail", strcode);
    EmailDisconnect();
    return iret;
}

测试代码:

#include "EmailAccess.h"

int main(int argc, char** argv)
{
    EmailAccess emailacc;
    int ret = emailacc.SendTestEmail("toxxx@163.com", "This is a test eamil!!!");

    return ret;
}

带SSL使用

使用官方编译好的头文件和库文件:

1、从下面地址下载已经编译好的包含 lib 和 include 文件的安装包。Win32/Win64 OpenSSL Installer for Windows - Shining Light Productionshttp://slproweb.com/products/Win32OpenSSL.html

  • 此处有Win32和Win64可选,这里的位数指的是你调用OpenSSL开发出来的软件的位数版本,而不是你计算机的位数。
  • 开发32位软件选择Win32,64位选择Win64,如果同时需要开发32位和64位的则下载两个
  • 注意,不要下载 light 版本,因为 light 版本不带 lib 和 include。如下图:

2、下载完后打开安装,选择安装位置,64位和32位不要安装在同一个目录下。

3、安装完毕后,就会有编译好的include,lib和dll文件,可以复制到项目中使用。

当然也可以自己编译OpenSSL库:

准备编译OpenSSL

编译OpenSSL库比较复杂,需要用到Perl工具和汇编器;

根据官方说明,OpenSSL编译除了Visual Studio 20XX以外,为方便编译,需要安装Strawberry Perl和NASM。

1、下载OpenSLL源代码,下载地址:

github.comhttps://github.com/openssl/openssl2、下载Perl工具,下载地址:

Strawberry Perl for Windowshttps://strawberryperl.com/3、下载NASM汇编器,下载地址:

NASMhttps://www.nasm.us/

特别注意:Perl安装后会自动添加到环境变量中,而NASM不会,一定要在环境变量Path中添加NASM.exe所在的目录,否则可能会出现意想不到的错误。

开始编译OpenSSL

进入源码所在目录后,执行以下命令:

perl Configure VC-WIN64A --prefix=D:\your\install\path
nmake
nmake install

编译过程大约需要10分钟左右,启动nmake后你可以去溜达一圈 ,回来就差不多好了。

特别注意:--prefix选项指定的安装路径必须是绝对路径,若不指定,则默认会安装在C:\Program Files (x86)\下面,如果你不是用管理员权限执行,则会出现Permission Denied 错误,切记。

可能遇到的问题:

1.nmake不能用:

启用如下的命令工具:

或者:

call "C:\Program Files (x86)\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
编译OpenSSL后

会生成include,lib和dll等文件;

结束语

小结:

其实,SMTP、POP3和IMAP协议就和HTTP、DNS等其他协议类似。只不过是为了实现某个功能而设计的一组规范而已,而装有这些协议的服务器就可以称为该协议的服务器。比如,一台服务器同时装有SMTP、POP3、HTTP协议,那么,它既可以称为SMTP服务器,也可以称为POP3服务器,同时还可以称为HTTP服务器。因为,它同时提供了这些功能。

一般情况下,小型的提供邮件服务的网站,他们的邮件服务器同时提供了SMTP、POP3和IMAP的功能。但是对于大型的提供邮件服务的网站,他们的SMTP服务器和POP3服务器以及IMAP服务器都是分开的。

当我们给其他人发送电子邮件时,我们需要写我们的邮件地址和对方的邮件地址,那么现在有下面两种情况:

当对方和我们属于同一个域时,也就是当我们是QQ邮箱,对方也是QQ邮箱。这样,我们把邮件给了我们的SMTP服务器,然后SMTP服务器只需要转给本地的POP3服务器即可。
当对方和我们不属于同一个域时,也就是当我们是QQ邮箱,而对方是163邮箱的话。我们把邮件给了我们的SMTP服务器,然后我们的SMTP服务器通过查询DNS得到对方邮箱的POP3服务器,然后将邮件通过SMTP协议传送给对方的POP3服务器或IMAP服务器。
那么,对方是如何接受我们的电子邮件的呢?也有下面两种情况:

当对方使用的是POP3协议的话,他可以使用邮件客户端对邮件进行接收以及操作。比如移动该邮件,删除该邮件,标记该邮件为已读,但是这些操作并不会反馈到邮箱服务器上。也就是说,你在本地对邮件进行的操作和邮箱服务器是不同步的。
当对方使用的是IMAP协议的话,IMAP提供邮件服务器与邮件客户端之间的双向通信,客户端的操作都会反馈到服务器上,对邮件进行的任何操作,服务器上的邮件也会做相应的动作。也就是说,你在本地对邮件进行的操作和邮箱服务器是同步的。

常用邮箱收发服务器

1.gmail

gmail服务器地址非SSLSSL
IMAPimap.gmail.com-993
POPpop.gmail.com-995
SMTPssl://smtp.gmail.com-465

2.139邮箱

139邮箱服务器地址非SSLSSL
IMAPimap.10086.cn143-
POPpop.10086.cn110995
SMTPsmtp.10086.cn25465

3.163邮箱

163邮箱服务器地址非SSLSSL
IMAPimap.163.com143993
POPpop.163.com110995
SMTPsmtp.163.com25465/994

4.qq邮箱

qq邮箱服务器地址非SSLSSL
IMAPimap.qq.com143993
POPpop.qq.com110995
SMTPsmtp.qq.com25465/587

5.sina邮箱

sina邮箱服务器地址非SSLSSL
IMAPimap.sina.com143993
POPpop.sina.com110-
SMTPsmtp.sina.com25-

6.腾讯企业邮箱

国内
腾讯企业邮箱服务器地址非SSLSSL
IMAPimap.exmail.qq.com-993
POPpop.exmail.qq.com-995
SMTPsmtp.exmail.qq.com-465

海外
腾讯企业邮箱服务器地址非SSLSSL
IMAPhwimap.exmail.qq.com-993
POPhwpop.exmail.qq.com-995
SMTPhwsmtp.exmail.qq.com-465

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值