You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用STARTTLS通过boost::ssl连接Office365 SMTP服务?

修复方案

核心问题说明

你的代码运行异常是因为STARTTLS流程逻辑错误、配置错误和语法错误共同导致的,具体问题如下:

  • 587端口为SMTP明文起始端口,在STARTTLS握手完成前,所有读写操作必须直接调用ssl_socket的底层TCP套接字接口,你直接用未完成握手的ssl_socket读写,会触发SSL层未初始化的异常
  • Office365已不再支持TLS 1.0协议,你的SSL上下文初始化时指定的tlsv1参数会导致握手被服务端拒绝
  • 发送STARTTLS命令后没有读取服务端的220响应就直接执行握手,不符合协议规范
  • 代码存在语法错误:writeLine("STARTTLS")语句末尾缺少分号
  • AUTH PLAIN命令需要携带Base64编码的身份凭证,仅发送命令头会被服务端拒绝

修正后的完整代码

#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind/bind.hpp>
#include <boost/lexical_cast.hpp>
#include <openssl/x509.h>
#include <openssl/err.h>
#include <iostream>
#include <iterator>
#include <sstream>
#include <cstring>

// 这里保留你原有Base64编解码的依赖实现,假设base64_text/base64_dec可用
extern const char* base64_text(const char* src);
extern const char* base64_dec(const char* src);

#define LOG(x) std::cout << x << std::endl

using namespace boost::asio;
using ip::tcp;

class SMTPClient
{
    typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket;

    public:
        SMTPClient(
            const std::string& pServer,
            unsigned int pPort,
            const std::string& pUser,
            const std::string& pPassword )
            : mServer(pServer)
            , mPort(pPort)
            , mUserName(pUser)
            , mPassword(pPassword)
            // 改用TLS 1.2版本上下文,适配Office365要求
            , mContext(mIOService, boost::asio::ssl::context_base::tlsv12)
            , mSocket(mIOService, mContext)
            , mResolver(mIOService)
            , mHasError(false)
        {
            mContext.set_verify_mode(boost::asio::ssl::context::verify_peer);
            mSocket.set_verify_callback(
            boost::bind(&SMTPClient::verify_certificate, this, boost::placeholders::_1, boost::placeholders::_2));
            // 加载系统根证书,避免Office365证书验证失败
            mContext.set_default_verify_paths();

            tcp::resolver::query qry(mServer, boost::lexical_cast<std::string>(mPort));
            mResolver.async_resolve( qry, boost::bind( &SMTPClient::handleResolve, this, boost::placeholders::_1,boost::placeholders::_2));
        }

        bool verify_certificate(bool preverified,
            boost::asio::ssl::verify_context& ctx)
        {
            char subject_name[256];
            X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
            X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);

            LOG("Verifying " << subject_name << " preverified: " << preverified);

            return preverified; // 建议保留预校验结果,不要强行返回true避免安全风险
        } 

        bool send(
            const std::string& from,
            const std::string& to,
            const std::string& subject,
            const std::string& message,
            std::string& error)
        {
            mFrom=from;
            mTo=to;
            mSubject=subject;
            mMessage=message;
            mIOService.run();
            error = mErrorMsg;
            return mHasError;
        }

    private:
        std::string encodeBase64(const std::string& data)
        {
            LOG(data);
            std::stringstream os;
            std::copy(
                base64_text(
                    data.c_str()),
                    base64_text(data.c_str() + data.size()),
                    std::ostream_iterator<char>(os)
                );
            LOG(os.str());
            return os.str();
        }

        std::string dencodeBase64(const std::string& data)
        {
            LOG(data);
            std::stringstream os;
            std::copy(
                base64_dec(
                    data.c_str()),
                    base64_dec(data.c_str() + data.size()),
                    std::ostream_iterator<char>(os)
                );
            LOG(os.str());
            return os.str();
        }

        void handleResolve(const boost::system::error_code& error, tcp::resolver::results_type endpoints)
        {
            if(!error)
            {
                boost::asio::async_connect(mSocket.lowest_layer(), endpoints,
                    boost::bind(&SMTPClient::handleConnect, this, boost::placeholders::_1));
            }
            else
            {
                mHasError=true;
                mErrorMsg= error.message();
            }
        }

        // 明文阶段写接口,操作底层TCP套接字
        void writePlainLine(const std::string& data)
        {
            std::string buf = data + "\r\n";
            boost::asio::write(mSocket.lowest_layer(), boost::asio::buffer(buf));
        }

        // 明文阶段读接口,操作底层TCP套接字
        std::string readPlainLine()
        {
            char data[4096];
            boost::system::error_code ec;
            size_t len = mSocket.lowest_layer().read_some(boost::asio::buffer(data), ec);
            if(ec) {
                mHasError = true;
                mErrorMsg = "Read plain error: " + ec.message();
                return "";
            }
            std::string res(data, len);
            LOG("Plain recv: " << res);
            return res;
        }

        // 加密阶段写接口,操作ssl_socket
        void writeSslLine(const std::string& data)
        {
            std::string buf = data + "\r\n";
            boost::asio::write(mSocket, boost::asio::buffer(buf));
        }

        // 加密阶段读接口,操作ssl_socket
        std::string readSslLine()
        {
            char data[4096];
            boost::system::error_code ec;
            size_t len = mSocket.read_some(boost::asio::buffer(data), ec);
            if(ec) {
                mHasError = true;
                mErrorMsg = "Read ssl error: " + ec.message();
                return "";
            }
            std::string res(data, len);
            LOG("Ssl recv: " << res);
            return res;
        }

        void log_ssl()
        {
            int err;
            while (err = ERR_get_error()) {
                char *str = ERR_error_string(err, 0);
                if (!str)
                    return;
                LOG(str);
            }
        }

        void handleConnect(const boost::system::error_code& error)
        {
            if (!error)
            {
                // 初始明文交互
                readPlainLine();
                if(mHasError) return;

                writePlainLine("EHLO " + mServer);
                readPlainLine();
                if(mHasError) return;

                writePlainLine("STARTTLS");
                std::string starttlsRes = readPlainLine();
                if(mHasError || starttlsRes.substr(0,3) != "220") {
                    mHasError = true;
                    mErrorMsg = "STARTTLS failed: " + starttlsRes;
                    return;
                }

                // 收到220响应后执行SSL握手
                boost::system::error_code handshakeEc;
                mSocket.handshake(ssl_socket::client, handshakeEc);
                if(handshakeEc) {
                    mHasError = true;
                    mErrorMsg = "Handshake error: " + handshakeEc.message();
                    log_ssl();
                    return;
                }

                // 握手完成后进入加密交互
                writeSslLine("EHLO " + mServer);
                readSslLine();
                if(mHasError) return;

                // 构造AUTH PLAIN凭证,格式为\0用户名\0密码
                std::string authStr = std::string("\0",1) + mUserName + std::string("\0",1) + mPassword;
                writeSslLine("AUTH PLAIN " + encodeBase64(authStr));
                readSslLine();
                if(mHasError) return;

                // 后续发件流程按SMTP协议补充
                writeSslLine("MAIL FROM:<" + mFrom + ">");
                readSslLine();
                writeSslLine("RCPT TO:<" + mTo + ">");
                readSslLine();
                writeSslLine("DATA");
                readSslLine();
                writeSslLine("Subject: " + mSubject + "\r\n\r\n" + mMessage + "\r\n.");
                readSslLine();
                writeSslLine("QUIT");
                readSslLine();
            }
            else
            {
                mHasError=true;
                mErrorMsg= error.message();
            }
        }

        std::string mServer;
        std::string mUserName;
        std::string mPassword;
        std::string mFrom;
        std::string mTo;
        std::string mSubject;
        std::string mMessage;
        unsigned int mPort;
        boost::asio::io_service mIOService;
        tcp::resolver mResolver;
        boost::asio::ssl::context mContext;
        ssl_socket mSocket;
        bool mHasError;
        std::string mErrorMsg;
};

bool send_mail(std::string text, std::string &error)
{
    std::string 
        subject = "Subject",
        me_mail = "你的完整邮箱地址",
        me_mail_server = "smtp.office365.com", 
        me_user = "你的完整邮箱地址",
        me_pass = "你的邮箱密码/应用密码",
        target_mail = "收件人邮箱地址";

    int server_port = 587;

    SMTPClient mailc(me_mail_server, server_port, me_user, me_pass);
    return mailc.send(me_mail, target_mail, subject, text, error);
}

额外注意事项

  • Office365现在要求启用多因素认证的账号必须使用应用密码登录,普通密码会被拒绝
  • 发件人地址必须和登录的邮箱地址完全一致,否则会被服务端拦截

内容的提问来源于stack exchange,提问作者i chik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 11:15:04