summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam <Adam@anope.org>2011-08-21 13:44:35 -0400
committerAdam <Adam@anope.org>2011-09-10 01:55:11 -0400
commit1e45019f8ae7de04e44dc065d2f40c2329ec2058 (patch)
tree3ce934c0188b82c957ed8495e9fd5588c89040f0
parent2eb708e5ad8b259876d24d828f7472b77864c256 (diff)
Added m_proxyscan
-rw-r--r--data/modules.example.conf85
-rw-r--r--modules/extra/m_proxyscan.cpp391
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)
+