Files
my-chat_-server/common/sendemail.hpp
2025-10-13 18:34:48 +08:00

263 lines
7.5 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#pragma once
#include <iostream>
#include <string>
#include <chrono>
#include <random>
#include <ctime>
#include <cstring> // for memset
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/buffer.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
class SendEmail
{
// 请确保在链接器设置中加入 libssl.lib 与 libcrypto.lib或通过 vcpkg 自动集成)
public:
using ptr = std::shared_ptr<SendEmail>;
// 辅助函数:通过 SSL 发送数据,并检查返回值
bool sendSSL(SSL* ssl, const std::string& msg, const char* label)
{
int bytesSent = SSL_write(ssl, msg.c_str(), static_cast<int>(msg.length()));
if (bytesSent <= 0) {
std::cerr << "Failed to send " << label << " message via SSL." << std::endl;
ERR_print_errors_fp(stderr);
return false;
}
return true;
}
std::string base64_encode(const std::string& input)
{
BIO* bmem = BIO_new(BIO_s_mem());
BIO* b64 = BIO_new(BIO_f_base64());
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
b64 = BIO_push(b64, bmem);
BIO_write(b64, input.c_str(), static_cast<int>(input.length()));
BIO_flush(b64);
BUF_MEM* bptr;
BIO_get_mem_ptr(b64, &bptr);
std::string result(bptr->data, bptr->length);
BIO_free_all(b64);
return result;
}
// 辅助函数:通过 SSL 接收数据,并打印输出
bool recvSSL(SSL* ssl, char* buff, int buffSize, const char* label)
{
int bytesReceived = SSL_read(ssl, buff, buffSize);
if (bytesReceived <= 0) {
std::cerr << "Failed to receive " << label << " message via SSL." << std::endl;
ERR_print_errors_fp(stderr);
return false;
}
buff[bytesReceived] = '\0';
std::cout << label << " response: " << buff << std::endl;
return true;
}
/// <summary>
/// 发送邮件到指定邮箱
/// </summary>
/// <param name="收件人邮箱"></param>
/// <returns>成功 = true失败 = false</returns>
bool SEND_email(const std::string& email)
{
// 1. DNS解析 smtp.qq.com使用端口 465
struct addrinfo hints = {};
hints.ai_family = AF_INET; // IPv4
hints.ai_socktype = SOCK_STREAM; // TCP
struct addrinfo* addrResult = nullptr;
int ret = getaddrinfo("smtp.qq.com", "465", &hints, &addrResult);
if (ret != 0 || addrResult == nullptr) {
std::cerr << "DNS resolution failed: " << gai_strerror(ret) << std::endl;
return false;
}
// 2. 建立 TCP 连接
int sock = socket(addrResult->ai_family, addrResult->ai_socktype, addrResult->ai_protocol);
if (sock == -1) {
std::cerr << "Socket creation failed!" << std::endl;
freeaddrinfo(addrResult);
return false;
}
if (connect(sock, addrResult->ai_addr, addrResult->ai_addrlen) != 0) {
std::cerr << "Connection failed!" << std::endl;
close(sock);
freeaddrinfo(addrResult);
return false;
}
freeaddrinfo(addrResult);
// 3. 初始化 OpenSSL
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
const SSL_METHOD* method = TLS_client_method();
SSL_CTX* sslCtx = SSL_CTX_new(method);
if (!sslCtx) {
std::cerr << "Unable to create SSL context." << std::endl;
close(sock);
return false;
}
SSL* ssl = SSL_new(sslCtx);
if (!ssl) {
std::cerr << "Unable to create SSL structure." << std::endl;
SSL_CTX_free(sslCtx);
close(sock);
return false;
}
// 绑定 socket 到 SSL
SSL_set_fd(ssl, sock);
if (SSL_connect(ssl) <= 0) {
std::cerr << "SSL_connect failed." << std::endl;
ERR_print_errors_fp(stderr);
SSL_free(ssl);
SSL_CTX_free(sslCtx);
close(sock);
return false;
}
// 4. 读取服务器欢迎信息
char buff[2048] = { 0 };
if (!recvSSL(ssl, buff, sizeof(buff) - 1, "Server")) {
SSL_free(ssl);
SSL_CTX_free(sslCtx);
close(sock);
return false;
}
// 5. 发送 EHLO 命令
std::string sendMsg = "EHLO localhost\r\n";
if (!sendSSL(ssl, sendMsg, "EHLO") || !recvSSL(ssl, buff, sizeof(buff) - 1, "EHLO")) {
SSL_free(ssl);
SSL_CTX_free(sslCtx);
close(sock);
return false;
}
// 6. 认证流程AUTH LOGIN
sendMsg = "AUTH LOGIN\r\n";
if (!sendSSL(ssl, sendMsg, "AUTH") || !recvSSL(ssl, buff, sizeof(buff) - 1, "AUTH")) {
SSL_free(ssl);
SSL_CTX_free(sslCtx);
close(sock);
return false;
}
// 7. 发送用户名Base64编码后的
std::string username = base64_encode("zxiao_xin@qq.com") + "\r\n";
if (!sendSSL(ssl, username, "Username") || !recvSSL(ssl, buff, sizeof(buff) - 1, "Username")) {
SSL_free(ssl);
SSL_CTX_free(sslCtx);
close(sock);
return false;
}
// 8. 发送密码Base64编码后的
std::string password = base64_encode("ydfslvabdryvejai") + "\r\n";
if (!sendSSL(ssl, password, "Password") || !recvSSL(ssl, buff, sizeof(buff) - 1, "Password")) {
SSL_free(ssl);
SSL_CTX_free(sslCtx);
close(sock);
return false;
}
// 9. 设置发件人
sendMsg = "MAIL FROM:<zxiao_xin@qq.com>\r\n";
if (!sendSSL(ssl, sendMsg, "MAIL FROM") || !recvSSL(ssl, buff, sizeof(buff) - 1, "MAIL FROM")) {
SSL_free(ssl);
SSL_CTX_free(sslCtx);
close(sock);
return false;
}
// 10. 设置收件人
sendMsg = "RCPT TO:<" + email + ">\r\n";
if (!sendSSL(ssl, sendMsg, "RCPT TO") || !recvSSL(ssl, buff, sizeof(buff) - 1, "RCPT TO")) {
SSL_free(ssl);
SSL_CTX_free(sslCtx);
close(sock);
return false;
}
// 检查回复是否为成功代码
std::string s = buff;
s = s.substr(0, 3);
if (s != "250") {
SSL_free(ssl);
SSL_CTX_free(sslCtx);
close(sock);
return false;
}
// 11. 命令 DATA
sendMsg = "DATA\r\n";
if (!sendSSL(ssl, sendMsg, "DATA") || !recvSSL(ssl, buff, sizeof(buff) - 1, "DATA")) {
SSL_free(ssl);
SSL_CTX_free(sslCtx);
close(sock);
return false;
}
// 12. 生成验证码
std::default_random_engine engine(static_cast<unsigned int>(std::time(nullptr)));
std::uniform_int_distribution<int> distribution(10000, 99999);
std::string verificationCode = std::to_string(distribution(engine));
_verifyCode = verificationCode;
// 13. 构造邮件头和正文
sendMsg =
"From: \"Mysterious系统\" <zxiao_xin@qq.com>\r\n"
"Reply-To: \"请勿回复\" <no-reply@qq.com>\r\n"
"To: <" + email + ">\r\n"
"Subject: Mysterious验证码\r\n"
"Content-Type: text/plain; charset=UTF-8\r\n"
"\r\n"
"这是您的验证码: " + verificationCode + "\r\n"
"用于身份验证,请勿泄露。如非本人操作,请忽略此邮件。\r\n"
"\r\n.\r\n";
if (!sendSSL(ssl, sendMsg, "Email DATA") || !recvSSL(ssl, buff, sizeof(buff) - 1, "DATA send")) {
SSL_free(ssl);
SSL_CTX_free(sslCtx);
close(sock);
return false;
}
// 14. 命令 QUIT
sendMsg = "QUIT\r\n";
sendSSL(ssl, sendMsg, "QUIT");
recvSSL(ssl, buff, sizeof(buff) - 1, "QUIT");
// 15. 清理资源
SSL_shutdown(ssl);
SSL_free(ssl);
SSL_CTX_free(sslCtx);
close(sock);
return true;
}
std::string getVerifyCode() {
return _verifyCode;
}
private:
// std::string _email;
std::string _verifyCode;
};