/* * * (C) 2003-2024 Anope Team * Contact us at team@anope.org * * Please read COPYING and README for further details. */ #include "module.h" struct ProxyCheck final { std::set types; std::vector 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 final : public ListenSocket { class ProxyCallbackClient final : public ClientSocket , public BufferedSocket { public: ProxyCallbackClient(ListenSocket *l, int f, const sockaddrs &a) : Socket(f, l->GetFamily()), ClientSocket(l, a), BufferedSocket() { } void OnAccept() override { this->Write(ProxyCheckString); } bool ProcessWrite() override { return !(!BufferedSocket::ProcessWrite() || this->write_buffer.empty()); } }; public: ProxyCallbackListener(const Anope::string &b, int p) : Socket(-1, b.find(':') == Anope::string::npos ? AF_INET : AF_INET6), ListenSocket(b, p, false) { } ClientSocket *OnAccept(int fd, const sockaddrs &addr) override { return new ProxyCallbackClient(this, fd, addr); } }; class ProxyConnect : public ConnectionSocket { static ServiceReference akills; public: static std::set 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() override { proxies.erase(this); } void OnConnect() override = 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", Anope::ToString(this->conaddr.port())); BotInfo *OperServ = Config->GetClient("OperServ"); Log(OperServ) << "PROXYSCAN: Open " << this->GetType() << " proxy found on " << this->conaddr.str() << " (" << reason << ")"; auto *x = new XLine("*@" + this->conaddr.addr(), OperServ ? OperServ->nick : "", Anope::CurTime + this->proxy.duration, reason, XLineManager::GenerateUID()); if (add_to_akill && akills) { akills->AddXLine(x); akills->Send(NULL, x); } else { if (IRCD->CanSZLine) IRCD->SendSZLine(NULL, x); else IRCD->SendAkill(NULL, x); delete x; } } }; ServiceReference ProxyConnect::akills("XLineManager", "xlinemanager/sgline"); std::set ProxyConnect::proxies; class HTTPProxyConnect final : public ProxyConnect , public BufferedSocket { public: HTTPProxyConnect(ProxyCheck &p, unsigned short po) : Socket(-1), ProxyConnect(p, po), BufferedSocket() { } void OnConnect() override { 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 override { return "HTTP"; } bool ProcessRead() override { bool b = BufferedSocket::ProcessRead(); if (this->GetLine() == ProxyCheckString) { this->Ban(); return false; } return b; } }; class SOCKS5ProxyConnect final : public ProxyConnect , public BinarySocket { public: SOCKS5ProxyConnect(ProxyCheck &p, unsigned short po) : Socket(-1), ProxyConnect(p, po), BinarySocket() { } void OnConnect() override { sockaddrs target_addr; char buf[4 + sizeof(target_addr.sa4.sin_addr.s_addr) + sizeof(target_addr.sa4.sin_port)]; int ptr = 0; target_addr.pton(AF_INET, target_ip, target_port); if (!target_addr.valid()) 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 override { return "SOCKS5"; } bool Read(const char *buffer, size_t l) override { if (l >= ProxyCheckString.length() && !strncmp(buffer, ProxyCheckString.c_str(), ProxyCheckString.length())) { this->Ban(); return false; } return true; } }; class ModuleProxyScan final : public Module { Anope::string listen_ip; unsigned short listen_port; Anope::string con_notice, con_source; std::vector proxyscans; ProxyCallbackListener *listener; class ConnectionTimeout final : public Timer { public: ConnectionTimeout(Module *c, time_t timeout) : Timer(c, timeout, true) { } void Tick() override { for (std::set::iterator it = ProxyConnect::proxies.begin(), it_end = ProxyConnect::proxies.end(); it != it_end;) { ProxyConnect *p = *it; ++it; if (p->created + this->GetSecs() < Anope::CurTime) delete p; } } } connectionTimeout; public: ModuleProxyScan(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR), connectionTimeout(this, 5) { this->listener = NULL; } ~ModuleProxyScan() override { for (std::set::iterator it = ProxyConnect::proxies.begin(), it_end = ProxyConnect::proxies.end(); it != it_end;) { ProxyConnect *p = *it; ++it; delete p; } for (std::map::const_iterator it = SocketEngine::Sockets.begin(), it_end = SocketEngine::Sockets.end(); it != it_end;) { Socket *s = it->second; ++it; ClientSocket *cs = dynamic_cast(s); if (cs != NULL && cs->ls == this->listener) delete s; } delete this->listener; } void OnReload(Configuration::Conf *conf) override { Configuration::Block *config = Config->GetModule(this); Anope::string s_target_ip = config->Get("target_ip"); if (s_target_ip.empty()) throw ConfigException(this->name + " target_ip may not be empty"); int s_target_port = config->Get("target_port", "-1"); if (s_target_port <= 0) throw ConfigException(this->name + " target_port may not be empty and must be a positive number"); Anope::string s_listen_ip = config->Get("listen_ip"); if (s_listen_ip.empty()) throw ConfigException(this->name + " listen_ip may not be empty"); int s_listen_port = config->Get("listen_port", "-1"); if (s_listen_port <= 0) throw ConfigException(this->name + " 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->Get("connect_notice"); this->con_source = config->Get("connect_source"); add_to_akill = config->Get("add_to_akill", "true"); this->connectionTimeout.SetSecs(config->Get("timeout", "5s")); ProxyCheckString = Config->GetBlock("networkinfo")->Get("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("proxyscan: " + ex.GetReason()); } this->proxyscans.clear(); for (int i = 0; i < config->CountBlock("proxyscan"); ++i) { Configuration::Block *block = config->GetBlock("proxyscan", i); ProxyCheck p; Anope::string token; commasepstream sep(block->Get("type")); 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(block->Get("port")); while (sep2.GetToken(token)) { if (auto port = Anope::TryConvert(token)) p.ports.push_back(port.value()); } if (p.ports.empty()) continue; p.duration = block->Get("time", "4h"); p.reason = block->Get("reason"); if (p.reason.empty()) continue; this->proxyscans.push_back(p); } } void OnUserConnect(User *user, bool &exempt) override { if (exempt || user->Quitting() || !Me->IsSynced() || !user->server->IsSynced()) return; /* At this time we only support IPv4 */ if (!user->ip.valid() || user->ip.sa.sa_family != AF_INET) /* User doesn't have a valid IPv4 IP (ipv6/spoof/etc) */ return; if (!this->con_notice.empty() && !this->con_source.empty()) { BotInfo *bi = BotInfo::Find(this->con_source, true); if (bi) user->SendMessage(bi, this->con_notice); } for (unsigned i = this->proxyscans.size(); i > 0; --i) { ProxyCheck &p = this->proxyscans[i - 1]; for (const auto &type : p.types) { for (const auto port : p.ports) { try { ProxyConnect *con = NULL; if (type.equals_ci("HTTP")) con = new HTTPProxyConnect(p, port); else if (type.equals_ci("SOCKS5")) con = new SOCKS5ProxyConnect(p, port); else continue; con->Connect(user->ip.addr(), port); } catch (const SocketException &ex) { Log(LOG_DEBUG) << "proxyscan: " << ex.GetReason(); } } } } } }; MODULE_INIT(ModuleProxyScan)