c++实现smtp发送邮件,支持ssl的465端口发送,支持附件、一次发送多人、抄送等

news2024/11/27 10:13:10

前言

c++实现smtp发送邮件,支持ssl的465端口发送,支持附件、一次发送多人、抄送等。
这里只使用了openssl库(用来支持ssl的465端口),其他部分是原生c++,支持在win/linux运行。
网上很多都是原始的支持25端口,明文发送,这里介绍一下ssl思路

实现思路

使用sockect编程和ssl,使用SMTP协议。
使用了c++的多态语言特性,包括重载和虚函数。
文中给出mail.h 和mail.cpp 以及main.cpp部分测试代码
Linux安装openssl

sudo apt install libssl-dev

win下自行下载安装

SMTP协议简介

SMTP协议的定义:

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

SMTP基本命令

SMTP基本命令包括以下几个:HELO﹑EHLO、MAIL﹑RCPT﹑DATA、AUTH LOGIN和QUIT
1、HELO–发件方问候收件方,后面是发件人的服务器地址或标识。收件方回答OK时标识自己的身份。问候和确认过程表明两台机器可以进行通信,同时状态参量被复位,缓冲区被清空。
EHLO–申明身份,表示自己身份需要验证,注意这部分需要通过Telnet验证一下,是user@example.com还是user,否则会出错。
2、MAIL–这个命令用来开始传送邮件,它的后面跟随发件方邮件地址(返回邮件地址)。它也用来当邮件无法送达时,发送失败通知。为保证邮件的成功发送,发件方的地址应是被对方或中间转发方同意接受的。这个命令会清空有关的缓冲区,为新的邮件做准备。
3、RCPT –这个命令告诉收件方收件人的邮箱。当有多个收件人时,需要多次使用该命令RCPT
4、TO,每次只能指明一个人。如果接收方服务器不同意转发这个地址的邮件,它必须报550错误代码通知发件方。如果服务器同意转发,它要更改邮件发送路径,把最开始的目的地(该服务器)换成下一个服务器。
5、DATA–收件方把该命令之后的数据作为发送的数据。数据被加入数据缓冲区中,以单独一行是”.”的行结束数据。结束行对于接收方同时意味立即开始缓冲区内的数据传送,传送结束后清空缓冲区。如果传送接受,接收方回复OK。
6、QUIT–SMTP要求接收放必须回答OK,然后中断传输;在收到这个命令并回答OK前,收件方不得中断连接,即使传输出现错误。发件方在发出这个命令并收到OK答复前,也不得中断连接。
7、AUTH LOGIN–登录邮箱,这一部分一般要用base64加密。

头文件mail.h

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

#include <openssl/ossl_typ.h>

#ifndef WIN32
#include<netdb.h>
#endif

class SmtpBase
{
protected:
    struct EmailInfo
    {
        std::string smtpServer;      //the SMTP server
        std::string serverPort;      //the SMTP server port
        std::string charset;         //the email character set
        std::string sender;          //the sender's name
        std::string senderEmail;     //the sender's email
        std::string password;        //the password of sender
        std::string recipient;       //the recipient's name
        std::string recipientEmail;  //the recipient's email

        std::map<std::string, std::string> recvList; //收件人列表<email, name>

        std::string subject;         //the email message's subject  邮件主题
        std::string message;         //the email message body   邮件内容

        std::map<std::string, std::string> ccEmail;         //抄送列表
        std::vector<std::string> attachment; //附件
    };
public:

    virtual ~SmtpBase() {}
    /**
     * @brief 简单发送文本邮件
     * @param   from 发送者的帐号
     * @param   passs 发送者密码
     * @param   to 收件人
     * @param   subject 主题
     * @param   strMessage  邮件内容
     */

    virtual int SendEmail(const std::string& from, const std::string& passs, const std::string& to, const std::string& subject, const std::string& strMessage) = 0;
    /**
     * @brief 发送邮件,包括附件以及抄送人和多个收件人
     * @param   from 发送者的帐号
     * @param   passs 发送者密码
     * @param   vecTo 收件人列表
     * @param   subject 主题
     * @param   strMessage  邮件内容
     * @param   attachment  附件列表    附件可以是绝对路径,默认是可执行程序目录下
     * @param   ccList  抄送列表
     */
    virtual int SendEmail(const std::string& from, const std::string& passs, const std::vector<std::string>& vecTo,
        const std::string& subject, const std::string& strMessage, const std::vector<std::string>& attachment,const std::vector<std::string>& ccList) = 0;

    std::string GetLastError()
    {
        return m_lastErrorMsg;
    }

    virtual int Read(void* buf, int num) = 0;
    virtual int Write(const void* buf, int num) = 0;
    virtual int Connect() = 0;
    virtual int DisConnect() = 0;

protected:

    std::string m_lastErrorMsg;


};


class SmtpEmail : public SmtpBase
{

public:
    SmtpEmail(const std::string& emailHost, const std::string& port);
    ~SmtpEmail();

    int SendEmail(const std::string& from, const std::string& passs, const std::string& to, const std::string& subject, const std::string& strMessage);

    int SendEmail(const std::string& from, const std::string& passs, const std::vector<std::string>& vecTo,
        const std::string& subject, const std::string& strMessage, const std::vector<std::string>& attachment, const std::vector<std::string>& ccList);
protected:
    int Read(void* buf, int num);
    int Write(const void* buf, int num);
    int Connect();
    int DisConnect();

    virtual std::string GetEmailBody(const EmailInfo & info);
private:
    //int SMTPSSLComunicate(SSL *connection, const EmailInfo &info);
    int SMTPComunicate(const EmailInfo &info);




protected:
    addrinfo* m_addrinfo;
    int m_socketfd;

    std::string m_host;
    std::string m_port;

    bool m_isConnected;
};

class SimpleSmtpEmail : public SmtpEmail
{
public:
    using SmtpEmail::SmtpEmail;
    virtual std::string GetEmailBody(const EmailInfo & info);
};

class SslSmtpEmail : public SmtpEmail
{
public:
    using SmtpEmail::SmtpEmail;
    ~SslSmtpEmail();

    int Connect();
    int DisConnect();
protected:
    int Read(void* buf, int num);
    int Write(const void* buf, int num);
private:
    SSL_CTX *m_ctx;
    SSL *m_ssl;
};

class SimpleSslSmtpEmail : public SslSmtpEmail
{
public:
    using SslSmtpEmail::SslSmtpEmail;
    virtual std::string GetEmailBody(const EmailInfo & info);
};

实现文件mail.cpp

#ifdef WIN32
#include <WinSock2.h>
#endif
#include "mail.h"
#include <fstream>
#include <sstream>
#include <iostream>
#include <string.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#ifdef WIN32
#include <WinSock2.h>
#include <WS2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
#else 
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#define INVALID_SOCKET -1
#endif

template<typename T>
std::string join(T& vecData, const std::string& delim)
{
    if (vecData.size() <= 0)
    {
        return std::string();
    }
    std::stringstream ss;
    for (auto& item : vecData)
    {
        ss << delim  << item ;
    }

    return ss.str().substr(delim.length());
}

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" }
};


std::string fileBasename(const std::string path) 
{
    std::string filename = path.substr(path.find_last_of("/\\") + 1);//解析出文件名字
    return filename;
}

std::string getFileContents(const char *filename)
{
    std::ifstream in(filename, std::ios::in | std::ios::binary);
    if (in)
    {
        std::string contents;
        in.seekg(0, std::ios::end);
        contents.resize(in.tellg());
        in.seekg(0, std::ios::beg);
        in.read(&contents[0], contents.size());
        in.close();
        return(contents);
    }
    else {
        printf("文件读取失败:%s\n",filename);
        return "";
    }
}
//获取文件的后缀名 如xxx.jpg 获取的是jpg
std::string GetFileExtension(const std::string& FileName)
{
    if (FileName.find_last_of(".") != std::string::npos)        //find_last_of逆向查找在原字符串中最后一个与指定字符串(或字符)中的某个字符匹配的字符,返回它的位置。若查找失败,则返回npos。
        return FileName.substr(FileName.find_last_of(".") + 1);
    return "";
}

const char* GetMimeTypeFromFileName(char* szFileExt)
{
    for (unsigned int i = 0; i < sizeof(MimeTypes) / sizeof(MimeTypes[0]); i++)
    {
        if (strcmp(MimeTypes[i][0], szFileExt) == 0)
        {
            return MimeTypes[i][1];
        }
    }
    return MimeTypes[0][1];   //if does not match any,  "application/octet-stream" is returned
}

char* base64Encode(char const* origSigned, unsigned origLength)
{
    static const char base64Char[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    unsigned char const* orig = (unsigned char const*)origSigned; // in case any input bytes have the MSB set
    if (orig == NULL) return NULL;

    unsigned const numOrig24BitValues = origLength / 3;
    bool havePadding = origLength > numOrig24BitValues * 3;
    bool havePadding2 = origLength == 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[(orig[3 * i] >> 2) & 0x3F];
        result[4 * i + 1] = base64Char[(((orig[3 * i] & 0x3) << 4) | (orig[3 * i + 1] >> 4)) & 0x3F];
        result[4 * i + 2] = base64Char[((orig[3 * i + 1] << 2) | (orig[3 * i + 2] >> 6)) & 0x3F];
        result[4 * i + 3] = base64Char[orig[3 * i + 2] & 0x3F];
    }

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

    result[numResultBytes] = '\0';
    return result;
}

int SmtpEmail::SMTPComunicate(const EmailInfo &info)
{

    if (Connect() != 0)
    {
        return -1;
    }
    char * buffer = new char[1000];
    memset(buffer, 0, 1000);

    Read(buffer, 999);
    if (strncmp(buffer, "220", 3) != 0) // not equal to 220
    {
        m_lastErrorMsg = buffer;
        return 220;
    }

    //向服务器发送ehlo
    std::string command = "ehlo EmailService\r\n";
    Write(command.c_str(), command.length());

    memset(buffer, 0, 1000);
    Read(buffer, 999);
    if (strncmp(buffer, "250", 3) != 0) // ehlo failed
    {
        m_lastErrorMsg = buffer;
        return 250;
    }

    //进行登录验证
    command = "AUTH PLAIN ";
    std::string auth = '\0' + info.senderEmail + '\0' + info.password;
    command += base64Encode(auth.data(), auth.size());
    command += "\r\n";
    Write(command.c_str(), command.length());

    memset(buffer, 0, 1000);
    Read(buffer, 999);
    if (strncmp(buffer, "235", 3) != 0) // login failed
    {
        m_lastErrorMsg = buffer;
        return 250;
    }

    //设置邮件发送者的邮箱地址
    command = "mail FROM:<" + info.senderEmail + ">\r\n";
    Write( command.c_str(), command.length());

    memset(buffer, 0, 1000);
    Read(buffer, 999);
    if (strncmp(buffer, "250", 3) != 0) // not ok
    {
        m_lastErrorMsg = buffer;
        return 250;
    }

    //设置邮件接收者的邮箱地址
    command = "RCPT TO:<" + info.recipientEmail + ">\r\n";
    Write( command.c_str(), command.length());

    memset(buffer, 0, 1000);
    Read( buffer, 999);
    if (strncmp(buffer, "250", 3) != 0) // not ok
    {
        m_lastErrorMsg = buffer;
        return 250;
    }



    //准备发送邮件
    command = "data\r\n";
    Write( command.c_str(), command.length());

    memset(buffer, 0, 1000);
    Read( buffer, 999);
    if (strncmp(buffer, "354", 3) != 0) // not ready to receive message
    {
        m_lastErrorMsg = buffer;
        return 354;
    }

    command = std::move(GetEmailBody(info));
    Write( command.c_str(), command.length());

    memset(buffer, 0, 1000);
    Read(buffer, 999);
    if (strncmp(buffer, "250", 3) != 0) // not ok
    {
        m_lastErrorMsg = buffer;
        return 250;
    }

    //结束发送过程
    delete buffer;
    Write( "quit\r\n", 6);

    DisConnect();
    return 0;
}

std::string SmtpEmail::GetEmailBody(const EmailInfo &info)
{
    //设定邮件的发送者名称、接收者名称、邮件主题,邮件内容。
    std::ostringstream message;
    message << "From: =?" << info.charset << "?b?" << base64Encode(info.sender.c_str(), info.sender.length()) << "?= <" << info.senderEmail << ">\r\n";

    std::vector<std::string> vecToList;
    for (auto item : info.recvList)
    {
        std::string to = "=?" + info.charset + "?b?" + base64Encode(item.second.c_str(), item.second.length()) + "?= <" + item.first + ">";
        vecToList.push_back(to);
    }

    message << "To: " << join(vecToList, ",") << "\r\n";
    message << "Subject: =?" << info.charset << "?b?" << base64Encode(info.subject.c_str(), info.subject.length()) << "?=\r\n";
    message << "MIME-Version: 1.0\r\n";

    if (info.ccEmail.size() > 0)
    {
        std::vector<std::string> vecCcList;
        for (auto item : info.ccEmail)
        {
            std::string cc = "=?" + info.charset + "?b?" + base64Encode(item.first.c_str(), item.first.length()) + "?= <" + item.second + ">";
            vecCcList.push_back(cc);
        }
        message << "Cc:" << join(vecCcList, ",") << "\r\n";
    }

    message << "Content-Type:multipart/mixed; boundary=\"Separator_ztq_000\"\r\n\r\n";
    message << "--Separator_ztq_000\r\n";
    message << "Content-Type: multipart/alternative; boundary=\"Separator_ztq_111\"\r\n\r\n";
    message << "--Separator_ztq_111\r\n";
    message << "Content-Type: " << "text/plain" << "; charset=\"" << info.charset << "\"\r\n";
    message << "Content-Transfer-Encoding: base64\r\n";
    message << "\r\n";                  //此处要加,不然邮件正文无内容
    message << base64Encode(info.message.c_str(), info.message.length());
    message << "\r\n\r\n";
    message << "--Separator_ztq_111--\r\n";
    //---------------------文件部分处理--------------------------------------

    for (auto item : info.attachment)
    {
        std::string filename = fileBasename(item);
        std::string strContext = getFileContents(item.c_str());
        if(strContext.empty())
        {
            std::cerr << "请检查传入的文件路径是否正确,此路径文件添加到附件失败,不发送此文件:" << std::endl;
            std::cerr << item << std::endl;
        }
        else
        {
            std::string fileContext = base64Encode(strContext.c_str(), strContext.length());
            std::string extension = GetFileExtension(filename);
            std::string mimetype = GetMimeTypeFromFileName((char*)extension.c_str());
            message << "--Separator_ztq_000\r\n";
            message << "Content-Type: " << mimetype << "; name=\"" << filename << "\"\r\n";
            message << "Content-Transfer-Encoding: base64\r\n";
            message << "Content-Disposition: attachment; filename=\"" << filename << "\"\r\n\r\n";
            message << fileContext + "\r\n\r\n";        //把读取到的文件内容以二进制形式发送
        }
    }
    //-----------------------------------------------------------
    message << "\r\n.\r\n";
    return message.str();
}

SmtpEmail::SmtpEmail(const std::string& emailHost, const std::string& port) :m_host(emailHost), m_port(port)
{

}

SmtpEmail::~SmtpEmail()
{

}

int SmtpEmail::Read(void* buf, int num)
{
    return recv(m_socketfd, (char*)buf, num, 0);
}
int SmtpEmail::Write(const void* buf, int num)
{
    return send(m_socketfd, (char*)buf, num, 0);
}

int SmtpEmail::Connect()
{
#ifdef WIN32
    //start socket connection
    WSADATA wsadata;
    WSAStartup(MAKEWORD(2, 2), &wsadata);
#endif

    m_socketfd = socket(AF_INET, SOCK_STREAM, 0);
    if (m_socketfd == INVALID_SOCKET)
    {

        m_lastErrorMsg = "Error on creating socket fd.";
        return -1;
    }

    addrinfo inAddrInfo = { 0 };
    inAddrInfo.ai_family = AF_INET;
    inAddrInfo.ai_socktype = SOCK_STREAM;

    printf("host:%s port:%s \n",m_host.c_str(), m_port.c_str());
    if (getaddrinfo(m_host.c_str(), m_port.c_str(), &inAddrInfo, &m_addrinfo) != 0) // error occurs
    {

        m_lastErrorMsg = "Error on calling getadrrinfo().";
        return -2;
    }


    if (connect(m_socketfd, m_addrinfo->ai_addr, m_addrinfo->ai_addrlen))
    {

        m_lastErrorMsg = "Error on calling connect().";
        return -3;
    }
    return 0;
}

int SmtpEmail::DisConnect()
{
    freeaddrinfo(m_addrinfo);
#ifdef WIN32
    closesocket(m_socketfd);
#else
    close(m_socketfd);
#endif
    return 0;
}

/*********************************************************************************/


std::string SimpleSmtpEmail::GetEmailBody(const EmailInfo &info)
{
    //设定邮件的发送者名称、接收者名称、邮件主题,邮件内容。
    std::ostringstream message;
    message << "From: =?" << info.charset << "?b?" << base64Encode(info.sender.c_str(), info.sender.length()) << "?= <" << info.senderEmail << ">\r\n";

    std::vector<std::string> vecToList;
    for (auto item : info.recvList)
    {
        std::string to = "=?" + info.charset + "?b?" + base64Encode(item.second.c_str(), item.second.length()) + "?= <" + item.first + ">";
        vecToList.push_back(to);
    }

    message << "To: " << join(vecToList, ",") << "\r\n";
    message << "Subject: =?" << info.charset << "?b?" << base64Encode(info.subject.c_str(), info.subject.length()) << "?=\r\n";
    message << "MIME-Version: 1.0\r\n";

    if (info.ccEmail.size() > 0)
    {
        std::vector<std::string> vecCcList;
        for (auto item : info.ccEmail)
        {
            std::string cc = "=?" + info.charset + "?b?" + base64Encode(item.first.c_str(), item.first.length()) + "?= <" + item.second + ">";
            vecCcList.push_back(cc);
        }
        message << "Cc:" << join(vecCcList, ",") << "\r\n";
    }

    message << "Content-Type:multipart/mixed; boundary=\"Separator_ztq_000\"\r\n\r\n";
    message << "--Separator_ztq_000\r\n";
    message << "Content-Type: multipart/alternative; boundary=\"Separator_ztq_111\"\r\n\r\n";
    message << "--Separator_ztq_111\r\n";
    message << "Content-Type: " << "text/plain" << "; charset=\"" << info.charset << "\"\r\n";
    message << "Content-Transfer-Encoding: base64\r\n";
    message << "\r\n";                  //此处要加,不然邮件正文无内容
    message << base64Encode(info.message.c_str(), info.message.length());
    message << "\r\n\r\n";
    message << "--Separator_ztq_111--\r\n";
    //---------------------文件部分处理--------------------------------------

    for (auto item : info.attachment)
    {
        std::string filename = fileBasename(item);
        std::string strContext = getFileContents(item.c_str());
        if(strContext.empty())
        {
            std::cerr << "请检查传入的文件路径是否正确,此路径文件添加到附件失败,不发送此文件:" << std::endl;
            std::cerr << item << std::endl;
        }
        else
        {
            std::string fileContext = base64Encode(strContext.c_str(), strContext.length());
            std::string extension = GetFileExtension(filename);
            std::string mimetype = GetMimeTypeFromFileName((char*)extension.c_str());
            message << "--Separator_ztq_000\r\n";
            message << "Content-Type: " << mimetype << "; name=\"" << filename << "\"\r\n";
            message << "Content-Transfer-Encoding: base64\r\n";
            message << "Content-Disposition: attachment; filename=\"" << filename << "\"\r\n\r\n";
            message << fileContext + "\r\n\r\n";        //把读取到的文件内容以二进制形式发送
        }
    }
    //-----------------------------------------------------------
    message << "\r\n.\r\n";
    return message.str();
}

/***************************************************************************************************/

SslSmtpEmail::~SslSmtpEmail()
{

}

int SslSmtpEmail::Connect()
{
    if (SmtpEmail::Connect() == 0)
    {
        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_socketfd);
        SSL_connect(m_ssl);
    }
    return 0;
}

int SslSmtpEmail::DisConnect()
{
    SSL_shutdown(m_ssl);
    SSL_free(m_ssl);
    SSL_CTX_free(m_ctx);

    SmtpEmail::DisConnect();
    return 0;
}



int SmtpEmail::SendEmail(const std::string& from, const std::string& passs, const std::string& to, const std::string& subject, const std::string& strMessage)
{

    EmailInfo info;
    info.charset = "UTF-8";
    info.sender = from;
    info.password = passs;
    info.senderEmail = from;
    info.recipientEmail = to;

    info.recvList[to] = "";

    info.subject = subject;
    info.message = strMessage;

    return SMTPComunicate(info);
}



int SmtpEmail::SendEmail(const std::string& from, const std::string& passs, const std::vector<std::string>& vecTo,
                         const std::string& subject, const std::string& strMessage, const std::vector<std::string>& attachment, const std::vector<std::string>& ccList)
{
    std::vector<std::string> recvList;
    recvList.insert(recvList.end(), vecTo.begin(), vecTo.end());
    recvList.insert(recvList.end(), ccList.begin(), ccList.end());

    for (auto& item : recvList)
    {
        EmailInfo info;
        info.charset = "UTF-8";
        info.sender = from;
        info.password = passs;
        info.senderEmail = from;;
        info.recipientEmail = item;

        for (auto item : vecTo)
        {
            info.recvList[item] = "";
        }

        info.subject = subject;
        info.message = strMessage;

        for (auto& item : ccList)
        {
            info.ccEmail[item] = item;
        }

        info.attachment = attachment;
        if (SMTPComunicate(info) != 0)
        {
            return -1;
        }
    }
    return 0;
}



int SslSmtpEmail::Read(void * buf, int num)
{

    int ret = SSL_read(m_ssl, buf, num);

    return ret;
}

int SslSmtpEmail::Write(const void * buf, int num)
{
    return SSL_write(m_ssl, buf, num);
}


std::string SimpleSslSmtpEmail::GetEmailBody(const EmailInfo &info)
{
    //设定邮件的发送者名称、接收者名称、邮件主题,邮件内容。
    std::ostringstream message;
    message << "From: =?" << info.charset << "?b?" << base64Encode(info.sender.c_str(), info.sender.length()) << "?= <" << info.senderEmail << ">\r\n";

    std::vector<std::string> vecToList;
    for (auto item : info.recvList)
    {
        std::string to = "=?" + info.charset + "?b?" + base64Encode(item.second.c_str(), item.second.length()) + "?= <" + item.first + ">";
        vecToList.push_back(to);
    }

    message << "To: " << join(vecToList, ",") << "\r\n";
    message << "Subject: =?" << info.charset << "?b?" << base64Encode(info.subject.c_str(), info.subject.length()) << "?=\r\n";
    message << "MIME-Version: 1.0\r\n";

    if (info.ccEmail.size() > 0)
    {
        std::vector<std::string> vecCcList;
        for (auto item : info.ccEmail)
        {
            std::string cc = "=?" + info.charset + "?b?" + base64Encode(item.first.c_str(), item.first.length()) + "?= <" + item.second + ">";
            vecCcList.push_back(cc);
        }
        message << "Cc:" << join(vecCcList, ",") << "\r\n";
    }
    message << "Content-Type:multipart/mixed; boundary=\"Separator_ztq_000\"\r\n\r\n";
    message << "--Separator_ztq_000\r\n";
    message << "Content-Type: multipart/alternative; boundary=\"Separator_ztq_111\"\r\n\r\n";
    message << "--Separator_ztq_111\r\n";
    message << "Content-Type: " << "text/plain" << "; charset=\"" << info.charset << "\"\r\n";
    message << "Content-Transfer-Encoding: base64\r\n";
    message << "\r\n";                  //此处要加,不然邮件正文无内容
    message << base64Encode(info.message.c_str(), info.message.length());
    message << "\r\n\r\n";
    message << "--Separator_ztq_111--\r\n";
    //---------------------文件部分处理--------------------------------------

    for (auto item : info.attachment)
    {
        std::string filename = fileBasename(item);
        std::string strContext = getFileContents(item.c_str());
        if(strContext.empty())
        {
            std::cerr << "请检查传入的文件路径是否正确,此路径文件添加到附件失败,不发送此文件:" << std::endl;
            std::cerr << item << std::endl;
        }
        else
        {
            std::string fileContext = base64Encode(strContext.c_str(), strContext.length());
            std::string extension = GetFileExtension(filename);
            std::string mimetype = GetMimeTypeFromFileName((char*)extension.c_str());
            message << "--Separator_ztq_000\r\n";
            message << "Content-Type: " << mimetype << "; name=\"" << filename << "\"\r\n";
            message << "Content-Transfer-Encoding: base64\r\n";
            message << "Content-Disposition: attachment; filename=\"" << filename << "\"\r\n\r\n";
            message << fileContext + "\r\n\r\n";        //把读取到的文件内容以二进制形式发送
        }
    }
    //-----------------------------------------------------------
    message << "\r\n.\r\n";
    return message.str();
}

测试代码 main.cpp

SendEmail函数重载,支持基本的简单发送文本邮件,和支持附件、抄送、以及发给多人的发送方式,亲测能够发送成功,默认发送的编码是utf-8格式

#include <iostream>
#include <stdio.h>
#include "mail.h"

int main()
{

    std::string from = "xxxx@163.com";
    std::string passs ="xxxxx";//这里替换成自己的授权码
    std::string to = "xxx@126.com";
    std::string subject = "hello";
    std::string strMessage = "test";


    std::vector<std::string> vecTo; //发送列表
    vecTo.push_back("xxx@126.com");

    std::vector<std::string> ccList;
    // ccList.push_back("xxx@xxx.com.cn");//抄送列表

    std::vector<std::string> attachment;
    attachment.push_back("mail.h");
    attachment.push_back("/home/soft/Desktop/openwrt-sdk-imx6ul/1/mybuild.sh");


    SmtpBase *base;
    SimpleSmtpEmail m_mail("smtp.163.com","25");
    base = &m_mail;
    //base->SendEmail(from, passs, to, subject, strMessage);//普通的文本发送,明文发送

    SimpleSslSmtpEmail m_ssl_mail("smtp.163.com","465");
    base=&m_ssl_mail;
    //base->SendEmail(from, passs, to, subject, strMessage);
    base->SendEmail(from, passs,vecTo,subject,strMessage,attachment,ccList);//加密的发送,支持抄送、附件等

    return 0;
}


邮箱收件箱
查看邮件头
邮件头
收到的邮件
收到的邮件截图

参考链接:

C++基于Smtp协议发送邮件
SMTP基本原理与C++实现向多人发送邮件

后记

如果您觉得有帮助的话,请点个赞吧!有问题请留言评论,欢迎指出意见,谢谢。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/620643.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Fiddler抓包工具之高级工具栏中的重定向AutoResponder的用法

重定向AutoResponder的用法 关于Fiddler的AutoResponder重定向功能&#xff0c;主要是时进行会话的拦截&#xff0c;然后替换原始资源的功能。 它与手动修该reponse是一样的&#xff0c;只是更加方便了&#xff0c;可以创建相应的rules&#xff0c;适合批处理的重定向功能。 …

[SQL Server]数据库入门之多表查询

&#x1f3ac; 博客主页&#xff1a;博主链接 &#x1f3a5; 本文由 M malloc 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f384; 学习专栏推荐&#xff1a;LeetCode刷题集&#xff01; &#x1f3c5; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指…

使用vant,实现密码输入框右边提供可视按钮(最简单)

在实际项目开发中&#xff0c;要实现密码输入框带密码可见切换按钮&#xff08;右侧的眼睛&#xff09;&#xff0c;点眼睛可以显示或隐藏密码。 实现原理&#xff1a;动态绑定输入框类型 1.绑定密码框的type属性&#xff0c;在密码框使用插槽 ps&#xff1a;由于icon标签不…

美股怎么交易?有哪些美股交易基础知识?

美股市场相对成熟&#xff0c;投资回报率也更高一些&#xff0c;受到投资者喜爱。美股怎么交易&#xff1f;首先就需要了解美股交易基础知识。 美股交易基础知识一、美股交易市场 美股主要交易市场有NYSE纽约证券交易所、NASDAQ纳斯达克证券市场、AMEX美国证券交易所。 美股交…

GitOps 最佳实践(上)| 基于 Amazon EKS 构建 CI/CD 流水线

GitOps 是目前比较理想的方法来实现基于 Kuberentes 集群的持续部署。 了解了 GitOps 的概念以及 CI/CD 流水线的架构&#xff0c;接下来我们将通过以下四个模块逐步完成构建 CI/CD 流水线的最佳实践&#xff1a; 通过 IaC 部署云基础架构&#xff1b;在 Amazon EKS 集群上部…

2023年新课标I卷作文,5位人工智能考生(ChatGPT,文心一言,GPT4, ChatGLM-6b, ChatT5)来写作,看谁写得最好

大家好&#xff0c;我是微学AI&#xff0c;今天是2023年6月7日&#xff0c;一年一度的高考又来了&#xff0c;今年的高考作文题也新鲜出炉。今年是特殊的一年&#xff0c;有人说2023是AI的元年&#xff0c;这一年里有大语言模型的爆发&#xff0c;每天都有大模型的公布&#xf…

23年测试岗,测试工程师从初级到中高级进阶,测试晋升之路...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 调查显示&#xf…

【旋转摆正验证码】移动积分兑换影视会员活动旋转摆正验证码识别——识别解绝方法

移动积分兑换影视会员活动旋转验证码的0~200ms级小模型识别思路 具体讲解识别思路 移动积分兑换影视会员活动拖动旋转验证码被破解&#xff1f;当代流行的人机验证到底安不安全&#xff1f; 提示&#xff1a;以下是皆为学习交流之&#xff0c;如有侵权 &#xff0c;望通知删帖…

年内BEV落地之战:华为遥遥领先,还是蔚小理登上王座?

作者 | 张祥威 编辑 | 德新 落地城市NOA&#xff0c;是今年最重磅的自动驾驶大战。而BEV感知&#xff0c;目前看来是 通往城市NOA的必经之路。 年内落地BEV&#xff0c;已经是国内自动驾驶头部玩家的共识。 其实&#xff0c;BEV是很早就提出的算法&#xff0c;又称鸟瞰图或上帝…

R730调整风扇转速

整整一个月没有写文章了&#xff0c;一是因为最近太忙&#xff0c;有点休息的时间就想躺着&#xff1b;二是买了Tesla P40显卡&#xff0c;想写个安装教程&#xff0c;结果快一个月了&#xff0c;安装还是失败。 大家如果谁懂在R730的ESXi上&#xff0c;用直通方式安装Tesla&am…

MMPretrain代码课

安装注意事项 训练时需要基于算法库源码进行开发&#xff0c;所以需要git clone mmpretrain仓库。如果只调用&#xff0c;则pip install 即可。 from mmpretrain import get_model, list_models,inference_model分别用于模型的获取、例举、推理 此时还没加载预训练权重 tor…

Redis-Cluster集群架构

Redis-Cluster 1.哨兵模式和redis-cluster模式的区别 哨兵模式的问题&#xff1a;1.只有一个master节点可以提供写的操作&#xff0c;qps 最多10w&#xff0c;对于高并发特别高的大型互联网系统 ​ 2.单节点不会内存太大&#xff0c;内存很大会给主节点造成压力&#xff0c;…

如何用数据资产管理,解锁数据新价值

数字经济和数字化转型的发展有什么共通点吗&#xff1f;这个问题的答案也很明显&#xff0c;数据就是数字经济数字化转型的基础&#xff0c;也是推动两者快速发展的核心要素。数字化时代&#xff0c;数据已经成为了个人、机构、企业乃至国家的重要战略资产&#xff0c;所以如何…

CnOpenData数字经济专利及引用被引用数据

一、数据简介 自人类社会进入信息时代以来&#xff0c;数字技术的快速发展和广泛应用衍生出数字经济。与农耕时代的农业经济、工业时代的工业经济大有不同&#xff0c;数字经济是一种新的经济、新的动能、新的业态&#xff0c;并引发了社会和经济的整体性深刻变革。现阶段&…

Nginx网络服务——页面优化与安全

Nginx网络服务——优化与防盗链 一、Nginx的网页优化1.Nginx的网页压缩2.Nginx的图片缓存3.Nginx的连接超时设置4.Nginx的并发设置 二、Nginx的页面安全1.查看Nginx版本的方式2.隐藏版本号 三、Nginx的日志分割1.编写日志分割脚本2. 执行脚本进行测试3. 将日志脚本添加至计划性…

InnoDB - 行格式

文章目录 InnoDB - 行格式1. 什么是行格式2. 四种行格式3. Compact行格式 InnoDB - 行格式 1. 什么是行格式 我们平时是以行记录为单位向表中插入数据的&#xff0c;这些数据在磁盘上的存放方式被称为行格式或者记录格式。 InnoDB引擎中支持四种行格式&#xff1a;Compact、…

Java8 Stream详解及中间操作方法使用示例(一)

Java 8 引入了 Stream API&#xff0c;提供了一种新的处理集合和数组的方式。Stream API 可以让我们更加便捷、灵活地处理数据&#xff0c;尤其是大规模数据。在这里&#xff0c;我将详细介绍 Java 8 中的 Stream API。 什么是 Stream Stream 是 Java 8 中引入的一个新的概念&…

vs2022配置pcl1.13.1

下载 下载PCL预编译安装程序PCL-1.13.1-AllInOne-msvc2022-win64.exe 和要安装的PCL组件&#xff08;例如pcl-1.13.1-pdb-msvc2022-win64.zip&#xff09; 安装 双击 PCL-1.13.1-AllInOne-msvc2022-win64.exe进行安装。到图1的步骤时&#xff0c;选择第二项。 图1 下一步&am…

串口助手(布局,图标,串口号,隐藏界面,显示实时时间)

文章目录 前言一、串口助手布局二、设置软件的标题&#xff0c;图标三、显示可用串口号四、隐藏&#xff0c;显示面板五、显示实时时间总结 前言 从这篇文章开始 教大家自己制作一个串口助手软件&#xff0c;并实现基本的功能。学做一个 串口助手可以一边回顾复习 QT 的相关知…

《面试1v1》G1垃圾回收器

我是 javapub&#xff0c;一名 Markdown 程序员从&#x1f468;‍&#x1f4bb;&#xff0c;八股文种子选手。 《面试1v1》 连载中… 面试官&#xff1a; G1垃圾收集器?听说很牛逼的样子! 候选人&#xff1a; 是的,G1是JDK9默认的垃圾收集器,代替了CMS收集器。它的目标是达到…