如何使用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
相关产品推荐
相关产品推荐

