1 /*
2  *
3  * (C) 2003-2020 Anope Team
4  * Contact us at team@anope.org
5  *
6  * Please read COPYING and README for further details.
7  */
8 
9 #include "module.h"
10 
11 struct ProxyCheck
12 {
13 	std::set<Anope::string, ci::less> types;
14 	std::vector<unsigned short> ports;
15 	time_t duration;
16 	Anope::string reason;
17 };
18 
19 static Anope::string ProxyCheckString;
20 static Anope::string target_ip;
21 static unsigned short target_port;
22 static bool add_to_akill;
23 
24 class ProxyCallbackListener : public ListenSocket
25 {
26 	class ProxyCallbackClient : public ClientSocket, public BufferedSocket
27 	{
28 	 public:
ProxyCallbackClient(ListenSocket * l,int f,const sockaddrs & a)29 		ProxyCallbackClient(ListenSocket *l, int f, const sockaddrs &a) : Socket(f, l->IsIPv6()), ClientSocket(l, a), BufferedSocket()
30 		{
31 		}
32 
OnAccept()33 		void OnAccept() anope_override
34 		{
35 			this->Write(ProxyCheckString);
36 		}
37 
ProcessWrite()38 		bool ProcessWrite() anope_override
39 		{
40 			return !BufferedSocket::ProcessWrite() || this->write_buffer.empty() ? false : true;
41 		}
42 	};
43 
44  public:
ProxyCallbackListener(const Anope::string & b,int p)45 	ProxyCallbackListener(const Anope::string &b, int p) : Socket(-1, b.find(':') != Anope::string::npos), ListenSocket(b, p, false)
46 	{
47 	}
48 
OnAccept(int fd,const sockaddrs & addr)49 	ClientSocket *OnAccept(int fd, const sockaddrs &addr) anope_override
50 	{
51 		return new ProxyCallbackClient(this, fd, addr);
52 	}
53 };
54 
55 class ProxyConnect : public ConnectionSocket
56 {
57 	static ServiceReference<XLineManager> akills;
58 
59  public:
60 	static std::set<ProxyConnect *> proxies;
61 
62 	ProxyCheck proxy;
63 	unsigned short port;
64 	time_t created;
65 
ProxyConnect(ProxyCheck & p,unsigned short po)66 	ProxyConnect(ProxyCheck &p, unsigned short po) : Socket(-1), ConnectionSocket(), proxy(p),
67 		port(po), created(Anope::CurTime)
68 	{
69 		proxies.insert(this);
70 	}
71 
~ProxyConnect()72 	~ProxyConnect()
73 	{
74 		proxies.erase(this);
75 	}
76 
77 	virtual void OnConnect() anope_override = 0;
78 	virtual const Anope::string GetType() const = 0;
79 
80  protected:
Ban()81 	void Ban()
82 	{
83 		Anope::string reason = this->proxy.reason;
84 
85 		reason = reason.replace_all_cs("%t", this->GetType());
86 		reason = reason.replace_all_cs("%i", this->conaddr.addr());
87 		reason = reason.replace_all_cs("%p", stringify(this->conaddr.port()));
88 
89 		BotInfo *OperServ = Config->GetClient("OperServ");
90 		Log(OperServ) << "PROXYSCAN: Open " << this->GetType() << " proxy found on " << this->conaddr.addr() << ":" << this->conaddr.port() << " (" << reason << ")";
91 		XLine *x = new XLine("*@" + this->conaddr.addr(), OperServ ? OperServ->nick : "", Anope::CurTime + this->proxy.duration, reason, XLineManager::GenerateUID());
92 		if (add_to_akill && akills)
93 		{
94 			akills->AddXLine(x);
95 			akills->Send(NULL, x);
96 		}
97 		else
98 		{
99 			if (IRCD->CanSZLine)
100 				IRCD->SendSZLine(NULL, x);
101 			else
102 				IRCD->SendAkill(NULL, x);
103 			delete x;
104 		}
105 	}
106 };
107 ServiceReference<XLineManager> ProxyConnect::akills("XLineManager", "xlinemanager/sgline");
108 std::set<ProxyConnect *> ProxyConnect::proxies;
109 
110 class HTTPProxyConnect : public ProxyConnect, public BufferedSocket
111 {
112  public:
HTTPProxyConnect(ProxyCheck & p,unsigned short po)113 	HTTPProxyConnect(ProxyCheck &p, unsigned short po) : Socket(-1), ProxyConnect(p, po), BufferedSocket()
114 	{
115 	}
116 
OnConnect()117 	void OnConnect() anope_override
118 	{
119 		this->Write("CONNECT %s:%d HTTP/1.0", target_ip.c_str(), target_port);
120 		this->Write("Content-Length: 0");
121 		this->Write("Connection: close");
122 		this->Write("");
123 	}
124 
GetType() const125 	const Anope::string GetType() const anope_override
126 	{
127 		return "HTTP";
128 	}
129 
ProcessRead()130 	bool ProcessRead() anope_override
131 	{
132 		bool b = BufferedSocket::ProcessRead();
133 		if (this->GetLine() == ProxyCheckString)
134 		{
135 			this->Ban();
136 			return false;
137 		}
138 		return b;
139 	}
140 };
141 
142 class SOCKS5ProxyConnect : public ProxyConnect, public BinarySocket
143 {
144  public:
SOCKS5ProxyConnect(ProxyCheck & p,unsigned short po)145 	SOCKS5ProxyConnect(ProxyCheck &p, unsigned short po) : Socket(-1), ProxyConnect(p, po), BinarySocket()
146 	{
147 	}
148 
OnConnect()149 	void OnConnect() anope_override
150 	{
151 		sockaddrs target_addr;
152 		char buf[4 + sizeof(target_addr.sa4.sin_addr.s_addr) + sizeof(target_addr.sa4.sin_port)];
153 		int ptr = 0;
154 		target_addr.pton(AF_INET, target_ip, target_port);
155 		if (!target_addr.valid())
156 			return;
157 
158 		buf[ptr++] = 5; // Version
159 		buf[ptr++] = 1; // # of methods
160 		buf[ptr++] = 0; // No authentication
161 
162 		this->Write(buf, ptr);
163 
164 		ptr = 1;
165 		buf[ptr++] = 1; // Connect
166 		buf[ptr++] = 0; // Reserved
167 		buf[ptr++] = 1; // IPv4
168 		memcpy(buf + ptr, &target_addr.sa4.sin_addr.s_addr, sizeof(target_addr.sa4.sin_addr.s_addr));
169 		ptr += sizeof(target_addr.sa4.sin_addr.s_addr);
170 		memcpy(buf + ptr, &target_addr.sa4.sin_port, sizeof(target_addr.sa4.sin_port));
171 		ptr += sizeof(target_addr.sa4.sin_port);
172 
173 		this->Write(buf, ptr);
174 	}
175 
GetType() const176 	const Anope::string GetType() const anope_override
177 	{
178 		return "SOCKS5";
179 	}
180 
Read(const char * buffer,size_t l)181 	bool Read(const char *buffer, size_t l) anope_override
182 	{
183 		if (l >= ProxyCheckString.length() && !strncmp(buffer, ProxyCheckString.c_str(), ProxyCheckString.length()))
184 		{
185 			this->Ban();
186 			return false;
187 		}
188 		return true;
189 	}
190 };
191 
192 class ModuleProxyScan : public Module
193 {
194 	Anope::string listen_ip;
195 	unsigned short listen_port;
196 	Anope::string con_notice, con_source;
197 	std::vector<ProxyCheck> proxyscans;
198 
199 	ProxyCallbackListener *listener;
200 
201 	class ConnectionTimeout : public Timer
202 	{
203 	 public:
ConnectionTimeout(Module * c,long timeout)204 		ConnectionTimeout(Module *c, long timeout) : Timer(c, timeout, Anope::CurTime, true)
205 		{
206 		}
207 
Tick(time_t)208 		void Tick(time_t) anope_override
209 		{
210 			for (std::set<ProxyConnect *>::iterator it = ProxyConnect::proxies.begin(), it_end = ProxyConnect::proxies.end(); it != it_end;)
211 			{
212 				ProxyConnect *p = *it;
213 				++it;
214 
215 				if (p->created + this->GetSecs() < Anope::CurTime)
216 					delete p;
217 			}
218 		}
219 	} connectionTimeout;
220 
221  public:
ModuleProxyScan(const Anope::string & modname,const Anope::string & creator)222 	ModuleProxyScan(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR),
223 		connectionTimeout(this, 5)
224 	{
225 
226 
227 		this->listener = NULL;
228 	}
229 
~ModuleProxyScan()230 	~ModuleProxyScan()
231 	{
232 		for (std::set<ProxyConnect *>::iterator it = ProxyConnect::proxies.begin(), it_end = ProxyConnect::proxies.end(); it != it_end;)
233 		{
234 			ProxyConnect *p = *it;
235 			++it;
236 			delete p;
237 		}
238 
239 		for (std::map<int, Socket *>::const_iterator it = SocketEngine::Sockets.begin(), it_end = SocketEngine::Sockets.end(); it != it_end;)
240 		{
241 			Socket *s = it->second;
242 			++it;
243 
244 			ClientSocket *cs = dynamic_cast<ClientSocket *>(s);
245 			if (cs != NULL && cs->ls == this->listener)
246 				delete s;
247 		}
248 
249 		delete this->listener;
250 	}
251 
OnReload(Configuration::Conf * conf)252 	void OnReload(Configuration::Conf *conf) anope_override
253 	{
254 		Configuration::Block *config = Config->GetModule(this);
255 
256 		Anope::string s_target_ip = config->Get<const Anope::string>("target_ip");
257 		if (s_target_ip.empty())
258 			throw ConfigException(this->name + " target_ip may not be empty");
259 
260 		int s_target_port = config->Get<int>("target_port", "-1");
261 		if (s_target_port <= 0)
262 			throw ConfigException(this->name + " target_port may not be empty and must be a positive number");
263 
264 		Anope::string s_listen_ip = config->Get<const Anope::string>("listen_ip");
265 		if (s_listen_ip.empty())
266 			throw ConfigException(this->name + " listen_ip may not be empty");
267 
268 		int s_listen_port = config->Get<int>("listen_port", "-1");
269 		if (s_listen_port <= 0)
270 			throw ConfigException(this->name + " listen_port may not be empty and must be a positive number");
271 
272 		target_ip = s_target_ip;
273 		target_port = s_target_port;
274 		this->listen_ip = s_listen_ip;
275 		this->listen_port = s_listen_port;
276 		this->con_notice = config->Get<const Anope::string>("connect_notice");
277 		this->con_source = config->Get<const Anope::string>("connect_source");
278 		add_to_akill = config->Get<bool>("add_to_akill", "true");
279 		this->connectionTimeout.SetSecs(config->Get<time_t>("timeout", "5s"));
280 
281 		ProxyCheckString = Config->GetBlock("networkinfo")->Get<const Anope::string>("networkname") + " proxy check";
282 		delete this->listener;
283 		this->listener = NULL;
284 		try
285 		{
286 			this->listener = new ProxyCallbackListener(this->listen_ip, this->listen_port);
287 		}
288 		catch (const SocketException &ex)
289 		{
290 			throw ConfigException("m_proxyscan: " + ex.GetReason());
291 		}
292 
293 		this->proxyscans.clear();
294 		for (int i = 0; i < config->CountBlock("proxyscan"); ++i)
295 		{
296 			Configuration::Block *block = config->GetBlock("proxyscan", i);
297 			ProxyCheck p;
298 			Anope::string token;
299 
300 			commasepstream sep(block->Get<const Anope::string>("type"));
301 			while (sep.GetToken(token))
302 			{
303 				if (!token.equals_ci("HTTP") && !token.equals_ci("SOCKS5"))
304 					continue;
305 				p.types.insert(token);
306 			}
307 			if (p.types.empty())
308 				continue;
309 
310 			commasepstream sep2(block->Get<const Anope::string>("port"));
311 			while (sep2.GetToken(token))
312 			{
313 				try
314 				{
315 					unsigned short port = convertTo<unsigned short>(token);
316 					p.ports.push_back(port);
317 				}
318 				catch (const ConvertException &) { }
319 			}
320 			if (p.ports.empty())
321 				continue;
322 
323 			p.duration = block->Get<time_t>("time", "4h");
324 			p.reason = block->Get<const Anope::string>("reason");
325 			if (p.reason.empty())
326 				continue;
327 
328 			this->proxyscans.push_back(p);
329 		}
330 	}
331 
OnUserConnect(User * user,bool & exempt)332 	void OnUserConnect(User *user, bool &exempt) anope_override
333 	{
334 		if (exempt || user->Quitting() || !Me->IsSynced() || !user->server->IsSynced())
335 			return;
336 
337 		/* At this time we only support IPv4 */
338 		if (!user->ip.valid() || user->ip.sa.sa_family != AF_INET)
339 			/* User doesn't have a valid IPv4 IP (ipv6/spoof/etc) */
340 			return;
341 
342 		if (!this->con_notice.empty() && !this->con_source.empty())
343 		{
344 			BotInfo *bi = BotInfo::Find(this->con_source, true);
345 			if (bi)
346 				user->SendMessage(bi, this->con_notice);
347 		}
348 
349 		for (unsigned i = this->proxyscans.size(); i > 0; --i)
350 		{
351 			ProxyCheck &p = this->proxyscans[i - 1];
352 
353 			for (std::set<Anope::string, ci::less>::iterator it = p.types.begin(), it_end = p.types.end(); it != it_end; ++it)
354 			{
355 				for (unsigned k = 0; k < p.ports.size(); ++k)
356 				{
357 					try
358 					{
359 						ProxyConnect *con = NULL;
360 						if (it->equals_ci("HTTP"))
361 							con = new HTTPProxyConnect(p, p.ports[k]);
362 						else if (it->equals_ci("SOCKS5"))
363 							con = new SOCKS5ProxyConnect(p, p.ports[k]);
364 						else
365 							continue;
366 						con->Connect(user->ip.addr(), p.ports[k]);
367 					}
368 					catch (const SocketException &ex)
369 					{
370 						Log(LOG_DEBUG) << "m_proxyscan: " << ex.GetReason();
371 					}
372 				}
373 			}
374 		}
375 	}
376 };
377 
378 MODULE_INIT(ModuleProxyScan)
379