]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_proxyscan.cpp
Header update: 2007 -> 2008
[user/henk/code/inspircd.git] / src / modules / m_proxyscan.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  InspIRCd: (C) 2002-2008 InspIRCd Development Team
6  * See: http://www.inspircd.org/wiki/index.php/Credits
7  *
8  * This program is free but copyrighted software; see
9  *          the file COPYING for details.
10  *
11  * ---------------------------------------------------
12  */
13
14 #include "inspircd.h"
15 #include "users.h"
16 #include "channels.h"
17 #include "modules.h"
18
19 /* $ModDesc: Scans locally connecting clients for proxies. */
20
21 /*
22  * How this works: basic overview.
23  *  We create a socket type (derived from EventHandler -- don't feel like
24  *  belting my head against the wall ala ident). For each test in the
25  *  configuration file for each locally connecting client, we create a ProxySocket
26  *  and run the associated test.
27  *
28  * The user is allowed to connect (delaying the connect might take ages..), and they
29  * will be destroyed *if* a bad response comes back to a test.
30  */
31
32 /*
33  * A few things to note:
34  * 
35  *   O  The only place that may *delete* an active or inactive
36  *      ident socket is OnUserDisconnect in the module class.
37  *      Because this is out of scope of the socket class there is
38  *      no possibility that the socket may ever try to delete
39  *      itself.
40  *
41  *   O  Closure of the ident socket with the Close() method will
42  *      not cause removal of the socket from memory or detatchment
43  *      from its 'parent' User class. It will only flag it as an 
44  *      inactive socket in the socket engine.
45  *
46  *   O  Timeouts are handled in OnCheckReaady at the same time as
47  *      checking if the ident socket has a result. This is done
48  *      by checking if the age the of the class (its instantiation
49  *      time) plus the timeout value is greater than the current time.
50  *
51  *  O   The ident socket is able to but should not modify its
52  *      'parent' user directly. Instead the ident socket class sets
53  *      a completion flag and during the next call to OnCheckReady,
54  *      the completion flag will be checked and any result copied to
55  *      that user's class. This again ensures a single point of socket
56  *      deletion for safer, neater code.
57  *
58  *  O   The code in the constructor of the ident socket is taken from
59  *      BufferedSocket but majorly thinned down. It works for both
60  *      IPv4 and IPv6.
61  *
62  *  O   In the event that the ident socket throws a ModuleException,
63  *      nothing is done. This is counted as total and complete
64  *      failure to create a connection.
65  * --------------------------------------------------------------
66  */
67
68 class ProxySocket : public EventHandler
69 {
70  private:
71         User *user;                     /* User we are attached to */
72         InspIRCd *ServerInstance;       /* Server instance */
73         char challenge[10];             /* what is sent on connect, as bytes */
74         int clen;
75         char response[20];              /* what we kill for on recieve, as bytes */
76         int rlen;
77         bool done;
78  public:
79         ProxySocket(InspIRCd *Server, User* u, const std::string &bindip, int port, char *cstr, int clen, char *rstr, int rlen)
80         {
81                 user = u;
82                 ServerInstance = Server;
83                 this->clen = clen;
84                 this->rlen = rlen;
85
86                 int i;
87
88                 /* byte for byte copies of challenge and response. */
89                 for (i = 0; i != clen; i++)
90                 {
91                         this->challenge[i] = cstr[i];
92                 }
93
94                 for (i = 0; i != rlen; i++)
95                 {
96                         this->response[i] = rstr[i];
97                 }
98                 
99                 socklen_t size = 0;
100 #ifdef IPV6
101                 /* Does this look like a v6 ip address? */
102                 bool v6 = false;
103                 if ((bindip.empty()) || bindip.find(':') != std::string::npos)
104                 v6 = true;
105
106                 if (v6)
107                         SetFd(socket(AF_INET6, SOCK_STREAM, 0));
108                 else
109 #endif
110                         SetFd(socket(AF_INET, SOCK_STREAM, 0));
111
112                 if (GetFd() == -1)
113                         throw ModuleException("Could not create socket");
114
115                 /* We allocate two of these because sizeof(sockaddr_in6) > sizeof(sockaddr_in) */
116                 sockaddr* s = new sockaddr[2];
117                 sockaddr* addr = new sockaddr[2];
118         
119 #ifdef IPV6
120                 /* Horrid icky nasty ugly berkely socket crap. */
121                 if (v6)
122                 {
123                         in6_addr addy;
124                         in6_addr n;
125                         if (inet_pton(AF_INET6, user->GetIPString(), &addy) > 0)
126                         {
127                                 ((sockaddr_in6*)addr)->sin6_family = AF_INET6;
128                                 memcpy(&((sockaddr_in6*)addr)->sin6_addr, &addy, sizeof(addy));
129                                 ((sockaddr_in6*)addr)->sin6_port = htons(port);
130                                 size = sizeof(sockaddr_in6);
131                                 inet_pton(AF_INET6, bindip.c_str(), &n);
132                                 memcpy(&((sockaddr_in6*)s)->sin6_addr, &n, sizeof(sockaddr_in6));
133                                 ((sockaddr_in6*)s)->sin6_port = 0;
134                                 ((sockaddr_in6*)s)->sin6_family = AF_INET6;
135                         }
136                 }
137                 else
138 #endif
139                 {
140                         in_addr addy;
141                         in_addr n;
142                         if (inet_aton(user->GetIPString(), &addy) > 0)
143                         {
144                                 ((sockaddr_in*)addr)->sin_family = AF_INET;
145                                 ((sockaddr_in*)addr)->sin_addr = addy;
146                                 ((sockaddr_in*)addr)->sin_port = htons(port);
147                                 size = sizeof(sockaddr_in);
148                                 inet_aton(bindip.c_str(), &n);
149                                 ((sockaddr_in*)s)->sin_addr = n;
150                                 ((sockaddr_in*)s)->sin_port = 0;
151                                 ((sockaddr_in*)s)->sin_family = AF_INET;
152                         }
153                 }
154
155                 /* Attempt to bind (ident requests must come from the ip the query is referring to */
156                 if (ServerInstance->SE->Bind(GetFd(), s, size) < 0)
157                 {
158                         this->Close();
159                         delete[] s;
160                         delete[] addr;
161                         throw ModuleException("failed to bind()");
162                 }
163
164                 delete[] s;
165                 ServerInstance->SE->NonBlocking(GetFd());
166
167                 /* Attempt connection (nonblocking) */
168                 if (ServerInstance->SE->Connect(this, (sockaddr*)addr, size) == -1 && errno != EINPROGRESS)
169                 {
170                         this->Close();
171                         delete[] addr;
172                         throw ModuleException("connect() failed");
173                 }
174
175                 delete[] addr;
176
177                 /* Add fd to socket engine */
178                 if (!ServerInstance->SE->AddFd(this))
179                 {
180                         this->Close();
181                         throw ModuleException("out of fds");
182                 }
183
184                 /* Important: We set WantWrite immediately after connect()
185                  * because a successful connection will trigger a writability event
186                  */
187                 ServerInstance->SE->WantWrite(this);
188         }
189
190         virtual void OnConnected()
191         {
192                 ServerInstance->Log(DEBUG,"OnConnected()");
193
194                 /* Both sockaddr_in and sockaddr_in6 can be safely casted to sockaddr, especially since the
195                  * only members we use are in a part of the struct that should always be identical (at the
196                  * byte level). */
197                 #ifndef IPV6
198                 sockaddr_in laddr, raddr;
199                 #else
200                 sockaddr_in6 laddr, raddr;
201                 #endif
202
203                 socklen_t laddrsz = sizeof(laddr);
204                 socklen_t raddrsz = sizeof(raddr);
205
206                 if ((getsockname(user->GetFd(), (sockaddr*) &laddr, &laddrsz) != 0) || (getpeername(user->GetFd(), (sockaddr*) &raddr, &raddrsz) != 0))
207                 {
208                         done = true;
209                         return;
210                 }
211
212                 /* Send failed if we didnt write the whole ident request --
213                  * might as well give up if this happens!
214                  */
215                 ServerInstance->Log(DEBUG, "Sending");
216                 if (ServerInstance->SE->Send(this, this->challenge, this->clen, 0) < this->clen)
217                 {
218                         ServerInstance->Log(DEBUG, "Send incomplete");
219                         done = true;
220                 }
221         }
222
223         virtual void HandleEvent(EventType et, int errornum = 0)
224         {
225                 switch (et)
226                 {
227                         case EVENT_READ:
228                                 /* fd readable event, received ident response */
229                                 ReadResponse();
230                         break;
231                         case EVENT_WRITE:
232                                 /* fd writeable event, successfully connected! */
233                                 OnConnected();
234                         break;
235                         case EVENT_ERROR:
236                                 /* fd error event, ohshi- */
237                                 ServerInstance->Log(DEBUG,"EVENT_ERROR");
238                                 /* We *must* Close() here immediately or we get a
239                                  * huge storm of EVENT_ERROR events!
240                                  */
241                                 Close();
242                                 done = true;
243                         break;
244                 }
245         }
246
247         void Close()
248         {
249                 /* Remove ident socket from engine, and close it, but dont detatch it
250                  * from its parent user class, or attempt to delete its memory.
251                  */
252                 if (GetFd() > -1)
253                 {
254                         ServerInstance->Log(DEBUG,"Close ident socket %d", GetFd());
255                         ServerInstance->SE->DelFd(this);
256                         ServerInstance->SE->Close(GetFd());
257                         ServerInstance->SE->Shutdown(GetFd(), SHUT_WR);
258                         this->SetFd(-1);
259                 }
260         }
261
262         bool HasResult()
263         {
264                 return done;
265         }
266
267         void ReadResponse()
268         {
269                 /* We don't really need to buffer for incomplete replies here, since IDENT replies are
270                  * extremely short - there is *no* sane reason it'd be in more than one packet
271                  */
272                 char ibuf[MAXBUF];
273                 int recvresult = ServerInstance->SE->Recv(this, ibuf, MAXBUF-1, 0);
274
275                 ServerInstance->Log(DEBUG,"ReadResponse(): %s -- %d", ibuf, recvresult);
276
277                 bool match = true;
278                 int i;
279
280                 for (i = 0; i != this->rlen && i != recvresult; i++)
281                 {
282                         if (this->response[i] != ibuf[i])
283                         {
284                                 ServerInstance->Log(DEBUG, "No match at pos %d: %c ne %c", i, this->response[i], ibuf[i]);
285                                 /* no match */
286                                 match = false;
287                         }
288                 }
289
290                 if (match == true)
291                 {
292                         User::QuitUser(ServerInstance, this->user, "Open proxy detected.");
293                 }
294
295                 /* Close (but dont delete from memory) our socket
296                  * and flag as done
297                  */
298                 Close();
299                 done = true;
300                 return;
301         }
302 };
303
304 class ModuleProxy : public Module
305 {
306  private:
307         int RequestTimeout;
308  public:
309         ModuleProxy(InspIRCd *Me)
310                 : Module(Me)
311         {
312                 OnRehash(NULL, "");
313                 Implementation eventlist[] = { I_OnRehash, I_OnUserRegister, I_OnCleanup, I_OnUserDisconnect };
314                 ServerInstance->Modules->Attach(eventlist, this, 4);
315         }
316         
317         virtual Version GetVersion()
318         {
319                 return Version(1, 1, 1, 0, VF_VENDOR, API_VERSION);
320         }
321         
322         
323         virtual void OnRehash(User *user, const std::string &param)
324         {
325                 ConfigReader MyConf(ServerInstance);
326                 
327                 RequestTimeout = MyConf.ReadInteger("ident", "timeout", 0, true);
328                 if (!RequestTimeout)
329                         RequestTimeout = 5;
330         }
331         
332         virtual int OnUserRegister(User *user)
333         {
334                 user->WriteServ("NOTICE Auth :*** Checking you for proxies...");
335
336                 // Get the IP that the user is connected to, and bind to that for the outgoing connection
337                 #ifndef IPV6
338                 sockaddr_in laddr;
339                 #else
340                 sockaddr_in6 laddr;
341                 #endif
342                 socklen_t laddrsz = sizeof(laddr);
343
344                 if (getsockname(user->GetFd(), (sockaddr*) &laddr, &laddrsz) != 0)
345                 {
346                         return 0;
347                 }
348
349                 #ifndef IPV6
350                 const char *ip = inet_ntoa(laddr.sin_addr);
351                 #else
352                 char ip[INET6_ADDRSTRLEN + 1];
353                 inet_ntop(laddr.sin6_family, &laddr.sin6_addr, ip, INET6_ADDRSTRLEN);
354                 #endif
355
356                 ProxySocket *p = NULL;
357                 try
358                 {
359                         p = new ProxySocket(ServerInstance, user, ip, 80, "GET /\n", 7, "Nothing here.", 12);
360                 }
361                 catch (ModuleException &e)
362                 {
363                         ServerInstance->Log(DEBUG,"Proxy exception: %s", e.GetReason());
364                         return 0;
365                 }
366
367                 user->Extend("proxy_socket", p);
368                 return 0;
369         }
370
371         virtual void OnCleanup(int target_type, void *item)
372         {
373                 /* Module unloading, tidy up users */
374                 if (target_type == TYPE_USER)
375                         OnUserDisconnect((User*)item);
376         }
377
378         virtual void OnUserDisconnect(User *user)
379         {
380                 /* User disconnect (generic socket detatch event) */
381                 ProxySocket *p = NULL;
382                 if (user->GetExt("proxy_socket", p))
383                 {
384                         p->Close();
385                         delete p;
386                         user->Shrink("proxy_socket");
387                         ServerInstance->Log(DEBUG, "Removed proxy socket from %s", user->nick);
388                 }
389         }
390 };
391
392 MODULE_INIT(ModuleProxy)
393