233 lines
12 KiB
C++
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;
|
|
}
|
|
}
|