diff --git a/src/Proxy.cpp b/src/Proxy.cpp index 319417c..1515869 100644 --- a/src/Proxy.cpp +++ b/src/Proxy.cpp @@ -1,338 +1,233 @@ -/** - * hermes antispam proxy - * Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo - * - * 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 - */ +// Proxy.cpp #include "Proxy.h" +#include +#include +#include "Utils.h" // Dummy include; replace with actual Utils implementation +#include "Configfile.h" // Dummy include; replace with actual Configfile implementation -extern LOGGER_CLASS hermes_log; -extern Configfile cfg; +extern Configfile cfg; // External configuration -void Proxy::setOutside(Socket& p_outside) -{ - outside=p_outside; -} +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; -/** - * this function is the main part of the program, it just sniffs traffic - * between server and client and acts acording to the following diagram: - * - * TODO: fill diagram and point to website with graphical version - * - */ -void Proxy::run(string &peer_address) -{ - #ifdef HAVE_SPF - Spf spf_checker; - #endif //HAVE_SPF + 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"; - string from=""; - string to=""; - string ehlostr=""; - string resolvedname=""; - unsigned char last_state=SMTP_STATE_WAIT_FOR_HELO; - long unimplemented_requests=0; - - 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(false==cfg.getAllowDataBeforeBanner()) - { - sleep(cfg.getBannerDelayTime()); - if(outside.canRead(0)) //if we have data waiting before the server gives us a 220 then quit, it's spam - { - LINF("421 (data_before_banner) (ip:"+peer_address+")"); - sleep(20); // but first let's annoy spammers once more - outside.writeLine("421 Stop sending data before we show you the banner"); - return; - } - } - } - - inside.init(); - 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 - - while(!outside.isClosed()&&!inside.isClosed()) - { - if(outside.canRead(0.2)) //client wants to send something to server - { - 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))) - { - 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 - 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."; - LINF("checking " + mechanism); - } - #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."; - LINF("checking " + mechanism); - } - //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."; - LINF("checking " + mechanism); - } - 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."; - LINF("checking " + mechanism); - } - 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."; - LINF("checking " + mechanism); - } - else - code="250"; - - if(""!=mechanism) - strlog.insert(0,"("+mechanism+") "); - strlog.insert(0,code+" "); - - //log the connection - LINF(strlog); - - //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 - sleep(20); - outside.writeLine(message); - return; - } - last_state=SMTP_STATE_WAIT_FOR_DATA; - } - - if("starttls"==Utils::strtolower(strtemp.substr(0,8))) - { - //if we have ssl then accept starttls, if not politely say fuck you - #ifdef HAVE_SSL - try - { - outside.prepareSSL(true); - LINF("STARTTLS issued by remote, TLS enabled"); - outside.writeLine("220 You can speak now, line is secure!!"); - outside.startSSL(true); + // 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; } - catch(Exception &e) - { - LINF("STARTTLS issued by remote, but enableSSL failed!"); - LERR(e); - outside.writeLine("454 Tried to enable SSL but failed"); + } + + 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; + } } - #else - outside.writeLine("454 TLS temporarily not available"); - LINF("STARTTLS issued by remote, TLS was not enabled because this build lacks SSL support"); - #endif //HAVE_SSL - strtemp=""; } - if(strtemp.length()) - inside.writeLine(strtemp); - } + // Connect to the inside server + inside.connect(cfg.getServerHost(), cfg.getServerPort()); - if(inside.canRead(0.2)) //server wants to send something to client - { - strtemp=inside.readLine(); - if(inside.isClosed()) - return; - 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 - { - string endofdata=""; - ssize_t bytes_read=0; - char buffer[4097]; - - outside.writeLine(strtemp); - strtemp=""; - string ssltls=""; - #ifdef HAVE_SSL - if (outside.is_ssl_enabled()) - ssltls=" (SSL/TLS)"; - #endif //HAVE_SSL - - 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"*/); + #ifdef HAVE_SSL + if (cfg.getOutgoingSsl()) { + inside.prepareSSL(false); + inside.startSSL(false); } - - if("235"==code) //235 -> you are correctly authenticated, unthrottle & authenticate - { - throttled=false; - authenticated=true; - hermes_status="authenticated"; + if (cfg.getIncomingSsl()) { + outside->prepareSSL(true); + outside->startSSL(true); } - if("250-pipelining"==Utils::strtolower(strtemp)||"250-chunking"==Utils::strtolower(strtemp)) //this solves our problems with pipelining-enabled servers - strtemp=""; + #endif // HAVE_SSL - //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) - //so we just say we support an imaginary extension (noextension). - //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"; + // 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; - //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.close(); //close the socket now and leave server alone - sleep(60); - outside.writeLine("502 Too many unimplemented commands, closing connection"); - 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 } - - if(strtemp.length()) - outside.writeLine(strtemp); - } - - 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; + return; } - } - catch(Exception &e) //any exception will close both connections - { - LERR(e); - if(last_state to "+(""==to?"no-to":to)); - return; - } }