mirror of
https://github.com/anope/anope.git
synced 2026-07-01 12:46:39 +02:00
Added m_proxyscan
This commit is contained in:
@@ -211,6 +211,91 @@ mysql
|
||||
port = 3306
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user