hermes/src/Proxy.cpp

233 lines
12 KiB
C++

// Proxy.cpp
#include "Proxy.h"
#include <iostream>
#include <thread>
#include "Utils.h" // Dummy include; replace with actual Utils implementation
#include "Configfile.h" // Dummy include; replace with actual Configfile implementation
extern Configfile cfg; // External configuration
void Proxy::run(std::string& peer_address) {
// Original comments and variables retained
std::string from = "";
std::string to = "";
std::string ehlostr = "";
std::string resolvedname = "";
unsigned char last_state = SMTP_STATE_WAIT_FOR_HELO;
long unimplemented_requests = 0;
try {
bool throttled = cfg.getThrottle(); // Start with a throttled connection
bool authenticated = false; // Start with a non-authenticated connection
bool esmtp = false;
std::string strtemp;
std::string hermes_status = "unknown";
// Check whitelist
if (!cfg.getDnsWhitelistDomains().empty() &&
Utils::listed_on_dns_lists(cfg.getDnsWhitelistDomains(), cfg.getDnsWhitelistPercentage(), peer_address)) {
authenticated = true;
hermes_status = "whitelisted";
if (cfg.getWhitelistedDisablesEverything()) {
throttled = false;
}
}
if (cfg.getWhitelistedDisablesEverything() && Utils::whitelisted(cfg.getDatabaseFile(), peer_address)) {
throttled = false;
authenticated = true;
} else {
if (!cfg.getAllowDataBeforeBanner()) {
std::this_thread::sleep_for(std::chrono::seconds(cfg.getBannerDelayTime()));
if (outside->canRead(0)) { // if we have data waiting before the server gives us a 220
std::cout << "421 (data_before_banner) (ip:" << peer_address << ")\n"; // Log it
std::this_thread::sleep_for(std::chrono::seconds(20)); // Annoy spammers once more
outside->writeLine("421 Stop sending data before we show you the banner");
return;
}
}
}
// Connect to the inside server
inside.connect(cfg.getServerHost(), cfg.getServerPort());
#ifdef HAVE_SSL
if (cfg.getOutgoingSsl()) {
inside.prepareSSL(false);
inside.startSSL(false);
}
if (cfg.getIncomingSsl()) {
outside->prepareSSL(true);
outside->startSSL(true);
}
#endif // HAVE_SSL
// Main loop for communication
while (!outside->isClosed() && !inside.isClosed()) {
// Check if the client wants to send something to the server
if (outside->canRead(0.2)) {
strtemp = outside->readLine();
if (outside->isClosed()) return;
if (strtemp.length() > 10 && "mail from:" == Utils::strtolower(strtemp.substr(0, 10))) {
from = Utils::getmail(strtemp);
last_state = SMTP_STATE_WAIT_FOR_RCPTTO;
}
if ("ehlo" == Utils::strtolower(strtemp.substr(0, 4))) esmtp = true;
if (strtemp.length() > 4 && ("ehlo" == Utils::strtolower(strtemp.substr(0, 4)) ||
"helo" == Utils::strtolower(strtemp.substr(0, 4)))) {
ehlostr = Utils::trim(strtemp.substr(5));
last_state = SMTP_STATE_WAIT_FOR_MAILFROM;
}
if (strtemp.length() > 8 && "rcpt to:" == Utils::strtolower(strtemp.substr(0, 8))) {
std::string strlog = "";
std::string code = "";
std::string mechanism = "";
std::string message = "";
to = Utils::getmail(strtemp);
try {
resolvedname = Socket::resolveInverselyToString(peer_address);
} catch (Exception& e) {
resolvedname = "";
}
strlog = "from " + from + " (ip:" + peer_address + ", hostname:" + resolvedname +
", " + (esmtp ? "ehlo" : "helo") + ":" + ehlostr + ") -> to " + to;
// Check greylisting
if (cfg.getGreylist() && !authenticated && Utils::greylist(cfg.getDatabaseFile(), peer_address, from, to)) {
code = "421";
mechanism = "greylist";
message = code + " Greylisted!! Please try again in a few minutes.";
std::cout << "checking " << mechanism << "\n";
}
#ifdef HAVE_SPF
else if (cfg.getQuerySpf() && !authenticated && !spf_checker.query(peer_address, ehlostr, from)) {
hermes_status = "spf-failed";
if (cfg.getAddStatusHeader()) code = "250";
else code = cfg.getReturnTempErrorOnReject() ? "421" : "550";
mechanism = "spf";
message = code + " You do not seem to be allowed to send email for that particular domain.";
std::cout << "checking " << mechanism << "\n";
}
#endif // HAVE_SPF
// Check blacklist
else if (!authenticated && Utils::blacklisted(cfg.getDatabaseFile(), peer_address, to)) {
code = cfg.getReturnTempErrorOnReject() ? "421" : "550";
mechanism = "allowed-domain-per-ip";
message = code + " You do not seem to be allowed to send email to that particular domain from that address.";
std::cout << "checking " << mechanism << "\n";
}
// Check RBL
else if (!cfg.getDnsBlacklistDomains().empty() && !authenticated &&
Utils::listed_on_dns_lists(cfg.getDnsBlacklistDomains(), cfg.getDnsBlacklistPercentage(), peer_address)) {
hermes_status = "blacklisted";
if (cfg.getAddStatusHeader()) code = "250";
else code = cfg.getReturnTempErrorOnReject() ? "421" : "550";
mechanism = "dnsbl";
message = code + " You are listed on some DNS blacklists. Get delisted before trying to send us email.";
std::cout << "checking " << mechanism << "\n";
}
else if (cfg.getRejectNoReverseResolution() && !authenticated && "" == resolvedname) {
code = cfg.getReturnTempErrorOnReject() ? "421" : "550";
mechanism = "no reverse resolution";
message = code + " Your IP address does not resolve to a hostname.";
std::cout << "checking " << mechanism << "\n";
}
else if (cfg.getCheckHeloAgainstReverse() && !authenticated && ehlostr != resolvedname) {
code = cfg.getReturnTempErrorOnReject() ? "421" : "550";
mechanism = "helo differs from resolved name";
message = code + " Your IP hostname doesn't match your envelope hostname.";
std::cout << "checking " << mechanism << "\n";
} else {
code = "250";
}
if (!mechanism.empty()) strlog.insert(0, "(" + mechanism + ") ");
strlog.insert(0, code + " ");
std::cout << strlog << "\n"; // Log the connection
// If we didn't accept the email, punish spammers
if ("250" != code) {
inside.writeLine("QUIT");
inside.close(); // Close the socket now and leave server alone
std::this_thread::sleep_for(std::chrono::seconds(20));
outside->writeLine(message);
return;
}
last_state = SMTP_STATE_WAIT_FOR_DATA;
}
// Handle STARTTLS
if ("starttls" == Utils::strtolower(strtemp.substr(0, 8))) {
#ifdef HAVE_SSL
try {
outside->prepareSSL(true);
std::cout << "STARTTLS issued by remote, TLS enabled\n";
outside->writeLine("220 You can speak now, line is secure!!");
outside->startSSL(true);
} catch (Exception& e) {
std::cout << "STARTTLS issued by remote, but enableSSL failed!\n";
outside->writeLine("454 Tried to enable SSL but failed");
}
#else
outside->writeLine("454 TLS temporarily not available");
std::cout << "STARTTLS issued by remote, TLS was not enabled because this build lacks SSL support\n";
#endif // HAVE_SSL
strtemp = "";
}
if (strtemp.length()) inside.writeLine(strtemp);
}
// Check if the server wants to send something to the client
if (inside.canRead(0.2)) {
strtemp = inside.readLine();
if (inside.isClosed()) return;
std::string code = strtemp.substr(0, 3); // All responses by the server start with a code
if ("354" == code) { // 354 -> you can start sending data, unthrottle now
std::string endofdata = "";
ssize_t bytes_read = 0;
char buffer[4097];
outside->writeLine(strtemp);
do {
bytes_read = outside->readBytes(buffer, sizeof(buffer) - 1);
if (bytes_read < 1) throw NetworkException("Problem reading DATA contents, recv returned " + Utils::inttostr(bytes_read), __FILE__, __LINE__);
buffer[bytes_read] = '\0';
inside.writeBytes(buffer, bytes_read);
if (bytes_read < 5) endofdata += std::string(buffer);
else endofdata = std::string(buffer + bytes_read - 5);
if (endofdata.length() > 5) endofdata = endofdata.substr(endofdata.length() - 5);
} while (endofdata != "\r\n.\r\n" && endofdata.length() > 3 && endofdata.substr(2) != "\n.\n" && endofdata.substr(2) != "\r.\r");
}
if ("235" == code) { // 235 -> you are correctly authenticated
throttled = false;
authenticated = true;
hermes_status = "authenticated";
}
// Try to annoy spammers who send too many senseless commands by delaying their connection
if ("502" == code) { // 502 unimplemented
if (cfg.getNumberOfUnimplementedCommandsAllowed() != -1 && ++unimplemented_requests > cfg.getNumberOfUnimplementedCommandsAllowed()) {
inside.writeLine("QUIT");
inside.close(); // Close the socket now and leave server alone
std::this_thread::sleep_for(std::chrono::seconds(60));
outside->writeLine("502 Too many unimplemented commands, closing connection");
return;
}
}
if (strtemp.length()) outside->writeLine(strtemp);
}
if (throttled) std::this_thread::sleep_for(std::chrono::seconds(cfg.getThrottlingTime())); // Take 1 second between each command
}
} catch (Exception& e) { // Any exception will close both connections
std::cerr << "Exception occurred: " << e.what() << std::endl;
return;
}
}