diff options
author | Adam <Adam@anope.org> | 2011-08-21 13:44:35 -0400 |
---|---|---|
committer | Adam <Adam@anope.org> | 2011-09-10 01:55:11 -0400 |
commit | 1e45019f8ae7de04e44dc065d2f40c2329ec2058 (patch) | |
tree | 3ce934c0188b82c957ed8495e9fd5588c89040f0 | |
parent | 2eb708e5ad8b259876d24d828f7472b77864c256 (diff) |
Added m_proxyscan
-rw-r--r-- | data/modules.example.conf | 85 | ||||
-rw-r--r-- | modules/extra/m_proxyscan.cpp | 391 |
2 files changed, 476 insertions, 0 deletions
diff --git a/data/modules.example.conf b/data/modules.example.conf index 955ab16bb..74c8bff5d 100644 --- a/data/modules.example.conf +++ b/data/modules.example.conf @@ -212,6 +212,91 @@ mysql } /* + * m_proxyscan + * + * This module allows you to scan connecting clients for open proxies. + * Note that using this will allow users to get the IP or your services. + * + * Currently the two supported proxy types are HTTP and SOCKS5. + * + * The proxy scanner works by attempting to connect to clients when they + * connect to the network, and if they have a proxy running instruct it to connect + * back to services. If services are able to connect through the proxy to itself + * then it knows it is an insecure proxy, and will ban it. + */ +#module { name = "m_proxyscan" } +m_proxyscan +{ + /* + * The target IP services tells the proxy to connect back to. This must be a publicly + * avaiable IP that remote proxies can connect to. + */ + #target_ip = "127.0.0.1" + + /* + * The port services tells the proxy to connect to. + */ + target_port = 7226 + + /* + * The listen IP services listen on for incoming connections from suspected proxies. + * This probably will be the same as target_ip, but may not be if you are behind a firewall (NAT). + */ + #listen_ip = "127.0.0.1" + + /* + * The port services should listen on for incoming connections from suspected proxies. + * This most likely will be the same as target_port. + */ + listen_port = 7226 + + /* + * An optional notice sent to clients upon connect. + */ + #connect_notice = "We will now scan your host for insecure proxies. If you do not consent to this scan please disconnect immediately" + + /* + * Who the notice should be sent from. + */ + #connect_source = "OperServ" + + /* + * If set, OperServ will add infected clients to the akill list. Without it, OperServ simply sends + * a timed G/K-line to the IRCd and forgets about it. Can be useful if your akill list is being fill up by bots. + */ + add_to_akill = yes + + /* + * How long before connections should be timed out. + */ + timeout = 5 +} + +/* + * A proxyscan block (must have m_proxyscan loaded). + * You may have multiple proxyscan blocks. + */ +proxyscan +{ + /* The type of proxy to check for. A comma separated list is allowed */ + type = "HTTP" + + /* The ports to check */ + port = "80,8080" + + /* How long to set the ban for */ + time = 4h + + /* + * The reason to ban the user for. + * %h is replaced with the type of proxy found. + * %i is replaced with the IP of proxy found. + * %p is replaced with the port. + */ + reason = "You have an open proxy running on your host (%t:%i:%p)" +} + +/* * m_ssl * * This module uses SSL to connect to the uplink server(s) diff --git a/modules/extra/m_proxyscan.cpp b/modules/extra/m_proxyscan.cpp new file mode 100644 index 000000000..1964e6bf0 --- /dev/null +++ b/modules/extra/m_proxyscan.cpp @@ -0,0 +1,391 @@ +/* + * (C) 2003-2011 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + */ + +#include "module.h" + +struct ProxyCheck +{ + std::set<Anope::string, std::less<ci::string> > types; + std::vector<unsigned short> ports; + time_t duration; + Anope::string reason; +}; + +static Anope::string ProxyCheckString; +static Anope::string target_ip; +static unsigned short target_port; +static bool add_to_akill; + +class ProxyCallbackListener : public ListenSocket +{ + class ProxyCallbackClient : public ClientSocket, public BufferedSocket + { + public: + ProxyCallbackClient(ListenSocket *l, int f, const sockaddrs &a) : Socket(f, l->IsIPv6()), ClientSocket(l, a), BufferedSocket() + { + } + + void OnAccept() + { + this->Write(ProxyCheckString); + } + + bool ProcessWrite() + { + return !BufferedSocket::ProcessWrite() || this->WriteBuffer.empty() ? false : true; + } + }; + + public: + ProxyCallbackListener(const Anope::string &b, int p) : ListenSocket(b, p, false) + { + } + + ClientSocket *OnAccept(int fd, const sockaddrs &addr) + { + return new ProxyCallbackClient(this, fd, addr); + } +}; + +class ProxyConnect : public ConnectionSocket +{ + static service_reference<XLineManager> akills; + + public: + static std::set<ProxyConnect *> proxies; + + ProxyCheck proxy; + unsigned short port; + time_t created; + + ProxyConnect(ProxyCheck &p, unsigned short po) : Socket(-1), ConnectionSocket(), proxy(p), + port(po), created(Anope::CurTime) + { + proxies.insert(this); + } + + ~ProxyConnect() + { + proxies.erase(this); + } + + virtual void OnConnect() = 0; + virtual const Anope::string GetType() const = 0; + + protected: + void Ban() + { + Anope::string reason = this->proxy.reason; + + reason = reason.replace_all_cs("%t", this->GetType()); + reason = reason.replace_all_cs("%i", this->conaddr.addr()); + reason = reason.replace_all_cs("%p", stringify(this->conaddr.port())); + + Log(findbot(Config->OperServ)) << "PROXYSCAN: Open " << this->GetType() << " proxy found on " << this->conaddr.addr() << ":" << this->conaddr.port() << " (" << reason << ")"; + if (add_to_akill && akills) + { + XLine *x = akills->Add("*@" + this->conaddr.addr(), Config->OperServ, Anope::CurTime + this->proxy.duration, reason); + /* If AkillOnAdd is disabled send it anyway, noone wants bots around... */ + if (!Config->AkillOnAdd) + akills->Send(NULL, x); + } + else + { + XLine xline("*@" + this->conaddr.addr(), Config->OperServ, Anope::CurTime + this->proxy.duration, reason); + if (ircd->szline) + ircdproto->SendSZLine(NULL, &xline); + else + ircdproto->SendAkill(NULL, &xline); + } + } +}; +service_reference<XLineManager> ProxyConnect::akills("xlinemanager/sgline"); +std::set<ProxyConnect *> ProxyConnect::proxies; + +class HTTPProxyConnect : public ProxyConnect, public BufferedSocket +{ + public: + HTTPProxyConnect(ProxyCheck &p, unsigned short po) : Socket(-1), ProxyConnect(p, po), BufferedSocket() + { + } + + void OnConnect() + { + this->Write("CONNECT %s:%d HTTP/1.0", target_ip.c_str(), target_port); + this->Write("Content-length: 0"); + this->Write("Connection: close"); + this->Write(""); + } + + const Anope::string GetType() const + { + return "HTTP"; + } + + bool Read(const Anope::string &buf) + { + if (buf == ProxyCheckString) + { + this->Ban(); + return false; + } + return true; + } +}; + +class SOCKS5ProxyConnect : public ProxyConnect, public BinarySocket +{ + public: + SOCKS5ProxyConnect(ProxyCheck &p, unsigned short po) : Socket(-1), ProxyConnect(p, po), BinarySocket() + { + } + + void OnConnect() + { + sockaddrs target_addr; + char buf[4 + sizeof(target_addr.sa4.sin_addr.s_addr) + sizeof(target_addr.sa4.sin_port)]; + int ptr = 0; + try + { + target_addr.pton(AF_INET, target_ip, target_port); + } + catch (const SocketException &) + { + return; + } + + buf[ptr++] = 5; // Version + buf[ptr++] = 1; // # of methods + buf[ptr++] = 0; // No authentication + + this->Write(buf, ptr); + + ptr = 1; + buf[ptr++] = 1; // Connect + buf[ptr++] = 0; // Reserved + buf[ptr++] = 1; // IPv4 + memcpy(buf + ptr, &target_addr.sa4.sin_addr.s_addr, sizeof(target_addr.sa4.sin_addr.s_addr)); + ptr += sizeof(target_addr.sa4.sin_addr.s_addr); + memcpy(buf + ptr, &target_addr.sa4.sin_port, sizeof(target_addr.sa4.sin_port)); + ptr += sizeof(target_addr.sa4.sin_port); + + this->Write(buf, ptr); + } + + const Anope::string GetType() const + { + return "SOCKS5"; + } + + bool Read(const char *buffer, size_t l) + { + if (l >= ProxyCheckString.length() && !strncmp(buffer, ProxyCheckString.c_str(), ProxyCheckString.length())) + { + this->Ban(); + return false; + } + return true; + } +}; + +class ModuleProxyScan : public Module +{ + Anope::string listen_ip; + unsigned short listen_port; + Anope::string con_notice, con_source; + std::vector<ProxyCheck> proxyscans; + + ProxyCallbackListener *listener; + + class ConnectionTimeout : public CallBack + { + public: + ConnectionTimeout(Module *creator, long timeout) : CallBack(creator, timeout, Anope::CurTime, true) + { + } + + void Tick(time_t) + { + for (std::set<ProxyConnect *>::iterator it = ProxyConnect::proxies.begin(), it_end = ProxyConnect::proxies.end(); it != it_end; ++it) + { + ProxyConnect *p = *it; + + if (p->created + this->GetSecs() < Anope::CurTime) + p->SetFlag(SF_DEAD); + } + } + } connectionTimeout; + + public: + ModuleProxyScan(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, SUPPORTED), + connectionTimeout(this, 5) + { + this->SetAuthor("Anope"); + + Implementation i[] = { I_OnReload, I_OnUserConnect }; + ModuleManager::Attach(i, this, sizeof(i) / sizeof(Implementation)); + + this->listener = NULL; + + try + { + OnReload(); + } + catch (const ConfigException &ex) + { + throw ModuleException(ex.GetReason()); + } + } + + ~ModuleProxyScan() + { + for (std::set<ProxyConnect *>::iterator it = ProxyConnect::proxies.begin(), it_end = ProxyConnect::proxies.end(); it != it_end;) + { + ProxyConnect *p = *it; + ++it; + delete p; + } + + for (std::map<int, Socket *>::const_iterator it = SocketEngine::Sockets.begin(), it_end = SocketEngine::Sockets.end(); it != it_end;) + { + Socket *s = it->second; + ++it; + + ClientSocket *cs = dynamic_cast<ClientSocket *>(s); + if (cs != NULL && cs->LS == this->listener) + delete s; + } + + delete this->listener; + } + + void OnReload() + { + ConfigReader config; + + Anope::string s_target_ip = config.ReadValue("m_proxyscan", "target_ip", "", 0); + if (s_target_ip.empty()) + throw ConfigException("m_proxyscan:target_ip may not be empty"); + + int s_target_port = config.ReadInteger("m_proxyscan", "target_port", "-1", 0, true); + if (s_target_port <= 0) + throw ConfigException("m_proxyscan:target_port may not be empty and must be a positive number"); + + Anope::string s_listen_ip = config.ReadValue("m_proxyscan", "listen_ip", "", 0); + if (s_listen_ip.empty()) + throw ConfigException("m_proxyscan:listen_ip may not be empty"); + + int s_listen_port = config.ReadInteger("m_proxyscan", "listen_port", "-1", 0, true); + if (s_listen_port <= 0) + throw ConfigException("m_proxyscan:listen_port may not be empty and must be a positive number"); + + target_ip = s_target_ip; + target_port = s_target_port; + this->listen_ip = s_listen_ip; + this->listen_port = s_listen_port; + this->con_notice = config.ReadValue("m_proxyscan", "connect_notice", "", 0); + this->con_source = config.ReadValue("m_proxyscan", "connect_source", "", 0); + add_to_akill = config.ReadFlag("m_proxyscan", "add_to_akill", "true", 0); + this->connectionTimeout.SetSecs(config.ReadInteger("m_proxyscan", "timeout", "5", 0, true)); + + ProxyCheckString = Config->NetworkName + " proxy check"; + delete this->listener; + this->listener = NULL; + try + { + this->listener = new ProxyCallbackListener(this->listen_ip, this->listen_port); + } + catch (const SocketException &ex) + { + throw ConfigException("m_proxyscan: " + ex.GetReason()); + } + + this->proxyscans.clear(); + for (int i = 0; i < config.Enumerate("proxyscan"); ++i) + { + ProxyCheck p; + Anope::string token; + + commasepstream sep(config.ReadValue("proxyscan", "type", "", i)); + while (sep.GetToken(token)) + { + if (!token.equals_ci("HTTP") && !token.equals_ci("SOCKS5")) + continue; + p.types.insert(token); + } + if (p.types.empty()) + continue; + + commasepstream sep2(config.ReadValue("proxyscan", "port", "", i)); + while (sep2.GetToken(token)) + { + try + { + unsigned short port = convertTo<unsigned short>(token); + p.ports.push_back(port); + } + catch (const ConvertException &) { } + } + if (p.ports.empty()) + continue; + + p.duration = dotime(config.ReadValue("proxyscan", "time", "4h", i)); + p.reason = config.ReadValue("proxyscan", "reason", "", i); + if (p.reason.empty()) + continue; + + this->proxyscans.push_back(p); + } + } + + void OnUserConnect(dynamic_reference<User> &user, bool &exempt) + { + if (exempt || !user || !user->ip() || !Me->IsSynced() || !user->server->IsSynced()) + return; + + /* At this time we only support IPv4 */ + if (user->ip.sa.sa_family != AF_INET) + return; + + if (!this->con_notice.empty() && !this->con_source.empty()) + { + BotInfo *bi = findbot(this->con_source); + if (bi) + user->SendMessage(bi, this->con_notice); + } + + for (unsigned i = this->proxyscans.size(); i > 0; --i) + { + ProxyCheck &p = this->proxyscans[i - 1]; + + for (std::set<Anope::string, std::less<ci::string> >::iterator it = p.types.begin(), it_end = p.types.end(); it != it_end; ++it) + { + for (unsigned k = 0; k < p.ports.size(); ++k) + { + try + { + ProxyConnect *con = NULL; + if (it->equals_ci("HTTP")) + con = new HTTPProxyConnect(p, p.ports[k]); + else if (it->equals_ci("SOCKS5")) + con = new SOCKS5ProxyConnect(p, p.ports[k]); + else + continue; + con->Connect(user->ip.addr(), p.ports[k]); + } + catch (const SocketException &ex) + { + Log(LOG_DEBUG) << "m_proxyscan: " << ex.GetReason(); + } + } + } + } + } +}; + +MODULE_INIT(ModuleProxyScan) + |