Compare commits
No commits in common. "a4c68baa45a117061c934734c40fd5bb8875a679" and "6b610aaa84beb06df65d8ea42305d97a8c70b87f" have entirely different histories.
a4c68baa45
...
6b610aaa84
3 changed files with 343 additions and 242 deletions
|
@ -16,7 +16,7 @@ steps:
|
||||||
image: alpine
|
image: alpine
|
||||||
commands:
|
commands:
|
||||||
- apk add -t hermes-build-deps --no-cache perl graphviz doxygen gcc make openssl-dev libspf2-dev cmake g++ sqlite-dev gettext-dev
|
- apk add -t hermes-build-deps --no-cache perl graphviz doxygen gcc make openssl-dev libspf2-dev cmake g++ sqlite-dev gettext-dev
|
||||||
- cmake -B build_dir -D BUILD_DOCS=ON
|
- cmake -B build_dir
|
||||||
- cmake --build build_dir
|
- cmake --build build_dir
|
||||||
- name: docker image build
|
- name: docker image build
|
||||||
image: plugins/docker
|
image: plugins/docker
|
||||||
|
|
|
@ -14,8 +14,6 @@ add_executable (hermes
|
||||||
src/Proxy.cpp
|
src/Proxy.cpp
|
||||||
src/Socket.cpp)
|
src/Socket.cpp)
|
||||||
|
|
||||||
option(BUILD_DOC "Build documentation")
|
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
set(SOURCES ${SOURCES}
|
set(SOURCES ${SOURCES}
|
||||||
src/FileLogger.cpp
|
src/FileLogger.cpp
|
||||||
|
@ -58,16 +56,14 @@ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Configfile.cpp
|
||||||
|
|
||||||
|
|
||||||
# doxygen
|
# doxygen
|
||||||
if (BUILD_DOCS)
|
find_package (Doxygen REQUIRED dot)
|
||||||
find_package (Doxygen)
|
if(DOXYGEN_FOUND)
|
||||||
if(DOXYGEN_FOUND)
|
|
||||||
add_custom_target(doc ALL
|
add_custom_target(doc ALL
|
||||||
doxygen
|
doxygen
|
||||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/docs)
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/docs)
|
||||||
install(
|
install(
|
||||||
DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/docs/html
|
DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/docs/html
|
||||||
TYPE DOC)
|
TYPE DOC)
|
||||||
endif()
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_link_libraries(hermes
|
target_link_libraries(hermes
|
||||||
|
|
451
src/Proxy.cpp
451
src/Proxy.cpp
|
@ -1,233 +1,338 @@
|
||||||
// Proxy.cpp
|
/**
|
||||||
|
* hermes antispam proxy
|
||||||
|
* Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; version 2 of the License
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* @author Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*/
|
||||||
#include "Proxy.h"
|
#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
|
extern LOGGER_CLASS hermes_log;
|
||||||
|
extern Configfile cfg;
|
||||||
|
|
||||||
void Proxy::run(std::string& peer_address) {
|
void Proxy::setOutside(Socket& p_outside)
|
||||||
// Original comments and variables retained
|
{
|
||||||
std::string from = "";
|
outside=p_outside;
|
||||||
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
|
* this function is the main part of the program, it just sniffs traffic
|
||||||
bool authenticated = false; // Start with a non-authenticated connection
|
* between server and client and acts acording to the following diagram:
|
||||||
bool esmtp = false;
|
*
|
||||||
std::string strtemp;
|
* TODO: fill diagram and point to website with graphical version
|
||||||
std::string hermes_status = "unknown";
|
*
|
||||||
|
*/
|
||||||
|
void Proxy::run(string &peer_address)
|
||||||
|
{
|
||||||
|
#ifdef HAVE_SPF
|
||||||
|
Spf spf_checker;
|
||||||
|
#endif //HAVE_SPF
|
||||||
|
|
||||||
// Check whitelist
|
string from="";
|
||||||
if (!cfg.getDnsWhitelistDomains().empty() &&
|
string to="";
|
||||||
Utils::listed_on_dns_lists(cfg.getDnsWhitelistDomains(), cfg.getDnsWhitelistPercentage(), peer_address)) {
|
string ehlostr="";
|
||||||
authenticated = true;
|
string resolvedname="";
|
||||||
hermes_status = "whitelisted";
|
unsigned char last_state=SMTP_STATE_WAIT_FOR_HELO;
|
||||||
if (cfg.getWhitelistedDisablesEverything()) {
|
long unimplemented_requests=0;
|
||||||
throttled = false;
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool throttled=cfg.getThrottle(); //we start with a throttled connection
|
||||||
|
bool authenticated=false; //we start with a non-authenticated connection
|
||||||
|
bool esmtp=false;
|
||||||
|
string strtemp;
|
||||||
|
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(true==cfg.getWhitelistedDisablesEverything())
|
||||||
|
throttled=false;
|
||||||
}
|
}
|
||||||
|
if(true==cfg.getWhitelistedDisablesEverything()&&Utils::whitelisted(cfg.getDatabaseFile(),peer_address))
|
||||||
|
{
|
||||||
|
throttled=false;
|
||||||
|
authenticated=true;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (cfg.getWhitelistedDisablesEverything() && Utils::whitelisted(cfg.getDatabaseFile(), peer_address)) {
|
{
|
||||||
throttled = false;
|
if(false==cfg.getAllowDataBeforeBanner())
|
||||||
authenticated = true;
|
{
|
||||||
} else {
|
sleep(cfg.getBannerDelayTime());
|
||||||
if (!cfg.getAllowDataBeforeBanner()) {
|
if(outside.canRead(0)) //if we have data waiting before the server gives us a 220 then quit, it's spam
|
||||||
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
|
LINF("421 (data_before_banner) (ip:"+peer_address+")");
|
||||||
std::cout << "421 (data_before_banner) (ip:" << peer_address << ")\n"; // Log it
|
sleep(20); // but first let's annoy spammers once more
|
||||||
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");
|
||||||
outside->writeLine("421 Stop sending data before we show you the banner");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect to the inside server
|
inside.init();
|
||||||
inside.connect(cfg.getServerHost(), cfg.getServerPort());
|
inside.connect(cfg.getServerHost(),cfg.getServerPort());
|
||||||
|
|
||||||
#ifdef HAVE_SSL
|
#ifdef HAVE_SSL
|
||||||
if (cfg.getOutgoingSsl()) {
|
if(cfg.getOutgoingSsl())
|
||||||
|
{
|
||||||
inside.prepareSSL(false);
|
inside.prepareSSL(false);
|
||||||
inside.startSSL(false);
|
inside.startSSL(false);
|
||||||
}
|
}
|
||||||
if (cfg.getIncomingSsl()) {
|
if(cfg.getIncomingSsl())
|
||||||
outside->prepareSSL(true);
|
{
|
||||||
outside->startSSL(true);
|
outside.prepareSSL(true);
|
||||||
|
outside.startSSL(true);
|
||||||
}
|
}
|
||||||
#endif // HAVE_SSL
|
#endif //HAVE_SSL
|
||||||
|
|
||||||
// Main loop for communication
|
while(!outside.isClosed()&&!inside.isClosed())
|
||||||
while (!outside->isClosed() && !inside.isClosed()) {
|
{
|
||||||
// Check if the client wants to send something to the server
|
if(outside.canRead(0.2)) //client wants to send something to server
|
||||||
if (outside->canRead(0.2)) {
|
{
|
||||||
strtemp = outside->readLine();
|
strtemp=outside.readLine();
|
||||||
if (outside->isClosed()) return;
|
if(outside.isClosed())
|
||||||
|
return;
|
||||||
if (strtemp.length() > 10 && "mail from:" == Utils::strtolower(strtemp.substr(0, 10))) {
|
if(strtemp.length()>10&&"mail from:"==Utils::strtolower(strtemp.substr(0,10)))
|
||||||
from = Utils::getmail(strtemp);
|
{
|
||||||
last_state = SMTP_STATE_WAIT_FOR_RCPTTO;
|
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 +
|
if("ehlo"==Utils::strtolower(strtemp.substr(0,4)))
|
||||||
", " + (esmtp ? "ehlo" : "helo") + ":" + ehlostr + ") -> to " + to;
|
esmtp=true;
|
||||||
|
|
||||||
// Check greylisting
|
if(strtemp.length()>4&&("ehlo"==Utils::strtolower(strtemp.substr(0,4))||"helo"==Utils::strtolower(strtemp.substr(0,4))))
|
||||||
if (cfg.getGreylist() && !authenticated && Utils::greylist(cfg.getDatabaseFile(), peer_address, from, to)) {
|
{
|
||||||
code = "421";
|
ehlostr=Utils::trim(strtemp.substr(5));
|
||||||
mechanism = "greylist";
|
last_state=SMTP_STATE_WAIT_FOR_MAILFROM;
|
||||||
message = code + " Greylisted!! Please try again in a few minutes.";
|
}
|
||||||
std::cout << "checking " << mechanism << "\n";
|
|
||||||
|
if(strtemp.length()>8&&"rcpt to:"==Utils::strtolower(strtemp.substr(0,8)))
|
||||||
|
{
|
||||||
|
string strlog="";
|
||||||
|
string code="";
|
||||||
|
string mechanism="";
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
//should we greylist¿? if we have to, quit and then sleep 20 seconds before closing the connection
|
||||||
|
code="421";
|
||||||
|
mechanism="greylist";
|
||||||
|
message=code+" Greylisted!! Please try again in a few minutes.";
|
||||||
|
LINF("checking " + mechanism);
|
||||||
}
|
}
|
||||||
#ifdef HAVE_SPF
|
#ifdef HAVE_SPF
|
||||||
else if (cfg.getQuerySpf() && !authenticated && !spf_checker.query(peer_address, ehlostr, from)) {
|
else if(cfg.getQuerySpf()&&!authenticated&&!spf_checker.query(peer_address,ehlostr,from))
|
||||||
hermes_status = "spf-failed";
|
{
|
||||||
if (cfg.getAddStatusHeader()) code = "250";
|
hermes_status="spf-failed";
|
||||||
else code = cfg.getReturnTempErrorOnReject() ? "421" : "550";
|
if(cfg.getAddStatusHeader())
|
||||||
mechanism = "spf";
|
code="250";
|
||||||
message = code + " You do not seem to be allowed to send email for that particular domain.";
|
else
|
||||||
std::cout << "checking " << mechanism << "\n";
|
code=cfg.getReturnTempErrorOnReject()?"421":"550";
|
||||||
|
mechanism="spf";
|
||||||
|
message=code+" You do not seem to be allowed to send email for that particular domain.";
|
||||||
|
LINF("checking " + mechanism);
|
||||||
}
|
}
|
||||||
#endif // HAVE_SPF
|
#endif //HAVE_SPF
|
||||||
// Check blacklist
|
//check blacklist
|
||||||
else if (!authenticated && Utils::blacklisted(cfg.getDatabaseFile(), peer_address, to)) {
|
else if(!authenticated&&Utils::blacklisted(cfg.getDatabaseFile(),peer_address,to))
|
||||||
code = cfg.getReturnTempErrorOnReject() ? "421" : "550";
|
{
|
||||||
mechanism = "allowed-domain-per-ip";
|
code=cfg.getReturnTempErrorOnReject()?"421":"550";
|
||||||
message = code + " You do not seem to be allowed to send email to that particular domain from that address.";
|
mechanism="allowed-domain-per-ip";
|
||||||
std::cout << "checking " << mechanism << "\n";
|
message=code+" You do not seem to be allowed to send email to that particular domain from that address.";
|
||||||
|
LINF("checking " + mechanism);
|
||||||
}
|
}
|
||||||
// Check RBL
|
//check rbl
|
||||||
else if (!cfg.getDnsBlacklistDomains().empty() && !authenticated &&
|
else if(!cfg.getDnsBlacklistDomains().empty()&&!authenticated&&Utils::listed_on_dns_lists(cfg.getDnsBlacklistDomains(),cfg.getDnsBlacklistPercentage(),peer_address))
|
||||||
Utils::listed_on_dns_lists(cfg.getDnsBlacklistDomains(), cfg.getDnsBlacklistPercentage(), peer_address)) {
|
{
|
||||||
hermes_status = "blacklisted";
|
hermes_status="blacklisted";
|
||||||
if (cfg.getAddStatusHeader()) code = "250";
|
if(cfg.getAddStatusHeader())
|
||||||
else code = cfg.getReturnTempErrorOnReject() ? "421" : "550";
|
code="250";
|
||||||
mechanism = "dnsbl";
|
else
|
||||||
message = code + " You are listed on some DNS blacklists. Get delisted before trying to send us email.";
|
code=cfg.getReturnTempErrorOnReject()?"421":"550";
|
||||||
std::cout << "checking " << mechanism << "\n";
|
mechanism="dnsbl";
|
||||||
|
message=code+" You are listed on some DNS blacklists. Get delisted before trying to send us email.";
|
||||||
|
LINF("checking " + mechanism);
|
||||||
}
|
}
|
||||||
else if (cfg.getRejectNoReverseResolution() && !authenticated && "" == resolvedname) {
|
else if(cfg.getRejectNoReverseResolution()&&!authenticated&&""==resolvedname)
|
||||||
code = cfg.getReturnTempErrorOnReject() ? "421" : "550";
|
{
|
||||||
mechanism = "no reverse resolution";
|
code=cfg.getReturnTempErrorOnReject()?"421":"550";
|
||||||
message = code + " Your IP address does not resolve to a hostname.";
|
mechanism="no reverse resolution";
|
||||||
std::cout << "checking " << mechanism << "\n";
|
message=code+" Your IP address does not resolve to a hostname.";
|
||||||
|
LINF("checking " + mechanism);
|
||||||
}
|
}
|
||||||
else if (cfg.getCheckHeloAgainstReverse() && !authenticated && ehlostr != resolvedname) {
|
else if(cfg.getCheckHeloAgainstReverse()&&!authenticated&&ehlostr!=resolvedname)
|
||||||
code = cfg.getReturnTempErrorOnReject() ? "421" : "550";
|
{
|
||||||
mechanism = "helo differs from resolved name";
|
code=cfg.getReturnTempErrorOnReject()?"421":"550";
|
||||||
message = code + " Your IP hostname doesn't match your envelope hostname.";
|
mechanism="helo differs from resolved name";
|
||||||
std::cout << "checking " << mechanism << "\n";
|
message=code+" Your IP hostname doesn't match your envelope hostname.";
|
||||||
} else {
|
LINF("checking " + mechanism);
|
||||||
code = "250";
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
code="250";
|
||||||
|
|
||||||
if (!mechanism.empty()) strlog.insert(0, "(" + mechanism + ") ");
|
if(""!=mechanism)
|
||||||
strlog.insert(0, code + " ");
|
strlog.insert(0,"("+mechanism+") ");
|
||||||
std::cout << strlog << "\n"; // Log the connection
|
strlog.insert(0,code+" ");
|
||||||
|
|
||||||
// If we didn't accept the email, punish spammers
|
//log the connection
|
||||||
if ("250" != code) {
|
LINF(strlog);
|
||||||
|
|
||||||
|
//if we didn't accept the email, punish spammers
|
||||||
|
if("250"!=code)
|
||||||
|
{
|
||||||
inside.writeLine("QUIT");
|
inside.writeLine("QUIT");
|
||||||
inside.close(); // Close the socket now and leave server alone
|
inside.close(); //close the socket now and leave server alone
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(20));
|
sleep(20);
|
||||||
outside->writeLine(message);
|
outside.writeLine(message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
last_state = SMTP_STATE_WAIT_FOR_DATA;
|
last_state=SMTP_STATE_WAIT_FOR_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle STARTTLS
|
if("starttls"==Utils::strtolower(strtemp.substr(0,8)))
|
||||||
if ("starttls" == Utils::strtolower(strtemp.substr(0, 8))) {
|
{
|
||||||
|
//if we have ssl then accept starttls, if not politely say fuck you
|
||||||
#ifdef HAVE_SSL
|
#ifdef HAVE_SSL
|
||||||
try {
|
try
|
||||||
outside->prepareSSL(true);
|
{
|
||||||
std::cout << "STARTTLS issued by remote, TLS enabled\n";
|
outside.prepareSSL(true);
|
||||||
outside->writeLine("220 You can speak now, line is secure!!");
|
LINF("STARTTLS issued by remote, TLS enabled");
|
||||||
outside->startSSL(true);
|
outside.writeLine("220 You can speak now, line is secure!!");
|
||||||
} catch (Exception& e) {
|
outside.startSSL(true);
|
||||||
std::cout << "STARTTLS issued by remote, but enableSSL failed!\n";
|
}
|
||||||
outside->writeLine("454 Tried to enable SSL but failed");
|
catch(Exception &e)
|
||||||
|
{
|
||||||
|
LINF("STARTTLS issued by remote, but enableSSL failed!");
|
||||||
|
LERR(e);
|
||||||
|
outside.writeLine("454 Tried to enable SSL but failed");
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
outside->writeLine("454 TLS temporarily not available");
|
outside.writeLine("454 TLS temporarily not available");
|
||||||
std::cout << "STARTTLS issued by remote, TLS was not enabled because this build lacks SSL support\n";
|
LINF("STARTTLS issued by remote, TLS was not enabled because this build lacks SSL support");
|
||||||
#endif // HAVE_SSL
|
#endif //HAVE_SSL
|
||||||
strtemp = "";
|
strtemp="";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strtemp.length()) inside.writeLine(strtemp);
|
if(strtemp.length())
|
||||||
|
inside.writeLine(strtemp);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the server wants to send something to the client
|
if(inside.canRead(0.2)) //server wants to send something to client
|
||||||
if (inside.canRead(0.2)) {
|
{
|
||||||
strtemp = inside.readLine();
|
strtemp=inside.readLine();
|
||||||
if (inside.isClosed()) return;
|
if(inside.isClosed())
|
||||||
|
return;
|
||||||
|
string code=strtemp.substr(0,3); //all responses by the server start with a code
|
||||||
|
|
||||||
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 and read binary-safe
|
||||||
if ("354" == code) { // 354 -> you can start sending data, unthrottle now
|
{
|
||||||
std::string endofdata = "";
|
string endofdata="";
|
||||||
ssize_t bytes_read = 0;
|
ssize_t bytes_read=0;
|
||||||
char buffer[4097];
|
char buffer[4097];
|
||||||
outside->writeLine(strtemp);
|
|
||||||
|
|
||||||
do {
|
outside.writeLine(strtemp);
|
||||||
bytes_read = outside->readBytes(buffer, sizeof(buffer) - 1);
|
strtemp="";
|
||||||
if (bytes_read < 1) throw NetworkException("Problem reading DATA contents, recv returned " + Utils::inttostr(bytes_read), __FILE__, __LINE__);
|
string ssltls="";
|
||||||
buffer[bytes_read] = '\0';
|
#ifdef HAVE_SSL
|
||||||
inside.writeBytes(buffer, bytes_read);
|
if (outside.is_ssl_enabled())
|
||||||
if (bytes_read < 5) endofdata += std::string(buffer);
|
ssltls=" (SSL/TLS)";
|
||||||
else endofdata = std::string(buffer + bytes_read - 5);
|
#endif //HAVE_SSL
|
||||||
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(cfg.getAddHeaders())
|
||||||
|
{
|
||||||
|
inside.writeLine("Received: from "+ehlostr+" ("+peer_address+")");
|
||||||
|
inside.writeLine(" by "+Utils::gethostname(outside.getFD())+" with "+(esmtp?"ESTMP":"SMTP")+ssltls+" via TCP; "+Utils::rfc2821_date());
|
||||||
|
inside.writeLine("X-Anti-Spam-Proxy: Proxied by Hermes [www.hermes-project.com]");
|
||||||
|
if(cfg.getAddStatusHeader())
|
||||||
|
inside.writeLine("X-Hermes-Status: "+hermes_status);
|
||||||
|
}
|
||||||
|
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+=string(buffer);
|
||||||
|
else
|
||||||
|
endofdata=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
|
if("235"==code) //235 -> you are correctly authenticated, unthrottle & authenticate
|
||||||
throttled = false;
|
{
|
||||||
authenticated = true;
|
throttled=false;
|
||||||
hermes_status = "authenticated";
|
authenticated=true;
|
||||||
|
hermes_status="authenticated";
|
||||||
}
|
}
|
||||||
|
if("250-pipelining"==Utils::strtolower(strtemp)||"250-chunking"==Utils::strtolower(strtemp)) //this solves our problems with pipelining-enabled servers
|
||||||
|
strtemp="";
|
||||||
|
|
||||||
// Try to annoy spammers who send too many senseless commands by delaying their connection
|
//this is a special case, we can't just ignore the line if it's the last line (doesn't have the dash after the code)
|
||||||
if ("502" == code) { // 502 unimplemented
|
//so we just say we support an imaginary extension (noextension).
|
||||||
if (cfg.getNumberOfUnimplementedCommandsAllowed() != -1 && ++unimplemented_requests > cfg.getNumberOfUnimplementedCommandsAllowed()) {
|
//caveat: this makes us identificable, so, if you can, configure your smtp server to either don't support pipelining
|
||||||
|
//or to not advertise it as the last capability.
|
||||||
|
if("250 pipelining"==Utils::strtolower(strtemp)||"250 chunking"==Utils::strtolower(strtemp))
|
||||||
|
strtemp="250 x-noextension";
|
||||||
|
|
||||||
|
//try to annoy spammers who send us too many senseless commands by delaying their connection a lot
|
||||||
|
if("502"==code) //502 unimplemented -> count them, if bigger than a certain number, terminate connection
|
||||||
|
{
|
||||||
|
if(cfg.getNumberOfUnimplementedCommandsAllowed()!=-1&&++unimplemented_requests>cfg.getNumberOfUnimplementedCommandsAllowed())
|
||||||
|
{
|
||||||
inside.writeLine("QUIT");
|
inside.writeLine("QUIT");
|
||||||
inside.close(); // Close the socket now and leave server alone
|
inside.close(); //close the socket now and leave server alone
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(60));
|
sleep(60);
|
||||||
outside->writeLine("502 Too many unimplemented commands, closing connection");
|
outside.writeLine("502 Too many unimplemented commands, closing connection");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strtemp.length()) outside->writeLine(strtemp);
|
if(strtemp.length())
|
||||||
|
outside.writeLine(strtemp);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (throttled) std::this_thread::sleep_for(std::chrono::seconds(cfg.getThrottlingTime())); // Take 1 second between each command
|
if(throttled)
|
||||||
|
sleep(cfg.getThrottlingTime()); //we take 1 second between each command to make spammers angry
|
||||||
}
|
}
|
||||||
} catch (Exception& e) { // Any exception will close both connections
|
}
|
||||||
std::cerr << "Exception occurred: " << e.what() << std::endl;
|
catch(Exception &e) //any exception will close both connections
|
||||||
|
{
|
||||||
|
LERR(e);
|
||||||
|
if(last_state<SMTP_STATE_WAIT_FOR_DATA)
|
||||||
|
LINF("421 (probably-throttling) from "+(""==from?"no-from":from)+" (ip:"+peer_address+", hostname:"+(""==resolvedname?"not-resolved":resolvedname)+", ehlo:"+(""==ehlostr?"no-ehlo":ehlostr)+") -> to "+(""==to?"no-to":to));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue