Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
ParticleG committed Feb 5, 2023
1 parent 477e4f4 commit 6098811
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 347 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ add_executable(${PROJECT_NAME} main.cc types/Products.h)
find_package(cryptopp CONFIG REQUIRED)
find_package(Drogon CONFIG REQUIRED)
find_package(magic_enum CONFIG REQUIRED)
find_package(mailio CONFIG REQUIRED)
find_package(range-v3 CONFIG REQUIRED)

target_link_libraries(${PROJECT_NAME}
PRIVATE
cryptopp::cryptopp
Drogon::Drogon
magic_enum::magic_enum
mailio
range-v3
range-v3-meta
range-v3::meta
Expand Down
314 changes: 41 additions & 273 deletions plugins/EmailManager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,227 +4,19 @@

#include <drogon/HttpAppFramework.h>
#include <magic_enum.hpp>
#include <mailio/smtp.hpp>
#include <plugins/EmailManager.h>
#include <regex>
#include <structures/Exceptions.h>

using namespace drogon;
using namespace mailio;
using namespace magic_enum;
using namespace std;
using namespace trantor;
using namespace studio26f::helpers;
using namespace studio26f::plugins;
using namespace studio26f::structures;
using namespace studio26f::types;

void EmailManager::messageHandler(
const TcpConnectionPtr &connPtr,
MsgBuffer *msgBuffer,
const shared_ptr<Email> &email,
const function<void(bool result, const string &)> &callback
) {
std::string receivedMsg;
while (msgBuffer->readableBytes() > 0) {
string buf(msgBuffer->peek(), msgBuffer->readableBytes());
receivedMsg.append(buf);
msgBuffer->retrieveAll();
}
LOG_TRACE << "receive: " << receivedMsg;
uint32_t responseCode = stoul(receivedMsg.substr(0, 3));

if (email->state == EmailState::Close) {
callback(true, receivedMsg);
connPtr->forceClose();
return;
}
try {
switch (responseCode) {
case 220:
switch (email->state) {
case EmailState::Init: {
string outMsg("EHLO smtpclient.qw\r\n");
MsgBuffer out;
out.append(outMsg.data(), outMsg.size());

connPtr->send(std::move(out));

email->state = EmailState::HandShake;
break;
}
case EmailState::HandShake: {
string outMsg("EHLO smtpclient.qw\r\n");
MsgBuffer out;
out.append(outMsg.data(), outMsg.size());

connPtr->startClientEncryption([connPtr, out]() {
LOG_TRACE << "SSL established";
connPtr->send(out);
}, false, false);

email->state = EmailState::Auth;
break;
}
default:
throw EmailException("Unsupported state");
}
break;
case 235:
switch (email->state) {
case EmailState::Mail: {
string outMsg("MAIL FROM:<" + email->_senderEmail + ">\r\n");
MsgBuffer out;
out.append(outMsg.data(), outMsg.size());

connPtr->send(std::move(out));

email->state = EmailState::Recipient;
break;
}
default:
throw EmailException("Unsupported state");
}
break;
case 250:
switch (email->state) {
case EmailState::HandShake: {
string outMsg("STARTTLS\r\n");
MsgBuffer out;
out.append(outMsg.data(), outMsg.size());

connPtr->send(std::move(out));

email->state = EmailState::HandShake;
break;
}
case EmailState::Auth: {
string outMsg("AUTH LOGIN\r\n");
MsgBuffer out;
out.append(outMsg.data(), outMsg.size());

connPtr->send(std::move(out));

email->state = EmailState::User;
break;
}
case EmailState::Recipient: {
string outMsg("RCPT TO:<" + email->_receiverEmail + ">\r\n");
MsgBuffer out;
out.append(outMsg.data(), outMsg.size());

connPtr->send(std::move(out));

email->state = EmailState::Data;
break;
}
case EmailState::Data: {
string outMsg("DATA\r\n");
MsgBuffer out;
out.append(outMsg.data(), outMsg.size());

connPtr->send(std::move(out));

email->state = EmailState::Body;
break;
}
case EmailState::Quit: {
string outMsg("QUIT\r\n");
MsgBuffer out;
out.append(outMsg.data(), outMsg.size());

connPtr->send(std::move(out));
email->state = EmailState::Close;
break;
}
default:
throw EmailException("Unsupported state");
}
break;
case 334:
switch (email->state) {
case EmailState::User: {
string outMsg(drogon::utils::base64Encode(
reinterpret_cast<const unsigned char *>(email->_account.c_str()),
email->_account.length()
) + "\r\n");
MsgBuffer out;
out.append(outMsg.data(), outMsg.size());

connPtr->send(std::move(out));

email->state = EmailState::Pass;
break;
}
case EmailState::Pass: {
string outMsg(drogon::utils::base64Encode(
reinterpret_cast<const unsigned char *>(email->_password.c_str()),
email->_password.length()
) + "\r\n");
MsgBuffer out;
out.append(outMsg.data(), outMsg.size());

connPtr->send(std::move(out));

email->state = EmailState::Mail;
break;
}
default:
throw EmailException("Unsupported state");
}
break;
case 354:
switch (email->state) {
case EmailState::Body: {
string outMsg(
"To: " + email->_receiverEmail + "\r\n" +
"From: " + email->_senderEmail + "\r\n"
);
if (email->isHTML) {
outMsg.append("Content-Type: text/html;\r\n");
}
outMsg.append(
"Subject: " + email->_subject + "\r\n\r\n" +
email->_content + "\r\n.\r\n"
);
MsgBuffer out;
out.append(outMsg.data(), outMsg.size());

connPtr->send(std::move(out));

email->state = EmailState::Quit;
break;
}
default:
throw EmailException("Unsupported state");
}
break;
default:
throw EmailException("Unsupported state");
}
} catch (const EmailException &e) {
email->state = EmailState::Close;
callback(false, receivedMsg);
}
}

EmailManager::Email::Email(
std::string account,
std::string password,
std::string senderEmail,
std::string senderName,
string receiverEmail,
string subject,
string content,
bool isHTML,
std::shared_ptr<trantor::TcpClient> socket
) : _account(std::move(account)),
_password(std::move(password)),
_senderEmail(std::move(senderEmail)),
_senderName(std::move(senderName)),
_receiverEmail(std::move(receiverEmail)),
_subject(std::move(subject)),
_content(std::move(content)),
isHTML(isHTML),
_socket(std::move(socket)) {}

void EmailManager::initAndStart(const Json::Value &config) {
if (!(
config["server"].isString() &&
Expand Down Expand Up @@ -254,68 +46,44 @@ void EmailManager::shutdown() {
void EmailManager::smtp(
const string &receiverEmail,
const string &subject,
const string &content,
bool isHTML,
const function<void(bool, const string &)> &callback
) noexcept {
const auto resolver = app().getResolver();
resolver->resolve(_server, [=, this](const InetAddress &resolvedAddr) {
const auto threadNum = app().getThreadNum();
EventLoop *ioLoop;
for (size_t threadIndex = 0; threadIndex < threadNum; ++threadIndex) {
ioLoop = app().getIOLoop(threadIndex);
if (ioLoop == nullptr || ioLoop->isInLoopThread()) {
continue;
} else {
break;
}
}
InetAddress smtpAddress(resolvedAddr.toIp(), _port, false);
const auto emailUuid = drogon::utils::getUuid();
const auto tcpSocket = make_shared<TcpClient>(ioLoop, smtpAddress, "SMTPMail");
_emailMap.emplace(emailUuid, make_shared<Email>(
_account,
_password,
_senderEmail,
_senderName,
receiverEmail,
subject,
content,
isHTML,
tcpSocket
));
tcpSocket->setConnectionCallback([emailUuid, this](const TcpConnectionPtr &connPtr) {
if (connPtr->connected()) {
LOG_TRACE << "Connection established!";
} else {
LOG_TRACE << "Connection disconnect";
_emailMap.erase(emailUuid);
}
});
tcpSocket->setConnectionErrorCallback([emailUuid, this]() {
LOG_ERROR << "Bad Server address";
_emailMap.erase(emailUuid);
});
tcpSocket->setMessageCallback(
[emailUuid, callback, this](const TcpConnectionPtr &connPtr, MsgBuffer *msg) {
messageHandler(connPtr, msg, _emailMap[emailUuid], callback);
}
);
tcpSocket->connect();
});
}

pair<bool, string> EmailManager::smtp(
const string &receiverEmail,
const string &subject,
const string &content,
bool isHTML
const string &content
) {
promise<pair<bool, string>> resultPromise;
auto resultFuture = resultPromise.get_future();
smtp(receiverEmail, subject, content, isHTML, [&resultPromise](bool success, const string &msg) {
resultPromise.set_value({success, msg});
});
return resultFuture.get();
try {
message msg;
msg.header_codec(message::header_codec_t::BASE64);
msg.from({_senderName, _senderEmail});
msg.add_recipient({_senderName, receiverEmail});
msg.subject(subject);
msg.content_transfer_encoding(mime::content_transfer_encoding_t::BINARY);
msg.content_type(message::media_type_t::TEXT, "html", "utf-8");
msg.content(content);
smtps conn(_server, _port);
conn.authenticate(_account, _password, smtps::auth_method_t::START_TLS);
conn.submit(msg);
} catch (smtp_error &e) {
LOG_WARN << "SMTP Error: " << e.what();
throw ResponseException(
i18n("emailError"),
e,
ResultCode::EmailError,
k503ServiceUnavailable
);
} catch (dialog_error &e) {
LOG_WARN << "Dialog Error: " << e.what();
throw ResponseException(
i18n("emailError"),
e,
ResultCode::EmailError,
k503ServiceUnavailable
);
} catch (message_error &e) {
LOG_WARN << "Message Error: " << e.what();
throw ResponseException(
i18n("emailError"),
e,
ResultCode::EmailError,
k503ServiceUnavailable
);
}
}

Loading

1 comment on commit 6098811

@ParticleG
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.