]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_ident.cpp
Add RAWIO log level which is more verbose than DEBUG
[user/henk/code/inspircd.git] / src / modules / m_ident.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  InspIRCd: (C) 2002-2010 InspIRCd Development Team
6  * See: http://wiki.inspircd.org/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
16 /* $ModDesc: Provides support for RFC1413 ident lookups */
17
18 /* --------------------------------------------------------------
19  * Note that this is the third incarnation of m_ident. The first
20  * two attempts were pretty crashy, mainly due to the fact we tried
21  * to use InspSocket/BufferedSocket to make them work. This class
22  * is ok for more heavyweight tasks, it does a lot of things behind
23  * the scenes that are not good for ident sockets and it has a huge
24  * memory footprint!
25  *
26  * To fix all the issues that we had in the old ident modules (many
27  * nasty race conditions that would cause segfaults etc) we have
28  * rewritten this module to use a simplified socket object based
29  * directly off EventHandler. As EventHandler only has low level
30  * readability, writeability and error events tied directly to the
31  * socket engine, this makes our lives easier as nothing happens to
32  * our ident lookup class that is outside of this module, or out-
33  * side of the control of the class. There are no timers, internal
34  * events, or such, which will cause the socket to be deleted,
35  * queued for deletion, etc. In fact, theres not even any queueing!
36  *
37  * Using this framework we have a much more stable module.
38  *
39  * A few things to note:
40  *
41  *   O  The only place that may *delete* an active or inactive
42  *      ident socket is OnUserDisconnect in the module class.
43  *      Because this is out of scope of the socket class there is
44  *      no possibility that the socket may ever try to delete
45  *      itself.
46  *
47  *   O  Closure of the ident socket with the Close() method will
48  *      not cause removal of the socket from memory or detatchment
49  *      from its 'parent' User class. It will only flag it as an
50  *      inactive socket in the socket engine.
51  *
52  *   O  Timeouts are handled in OnCheckReaady at the same time as
53  *      checking if the ident socket has a result. This is done
54  *      by checking if the age the of the class (its instantiation
55  *      time) plus the timeout value is greater than the current time.
56  *
57  *  O   The ident socket is able to but should not modify its
58  *      'parent' user directly. Instead the ident socket class sets
59  *      a completion flag and during the next call to OnCheckReady,
60  *      the completion flag will be checked and any result copied to
61  *      that user's class. This again ensures a single point of socket
62  *      deletion for safer, neater code.
63  *
64  *  O   The code in the constructor of the ident socket is taken from
65  *      BufferedSocket but majorly thinned down. It works for both
66  *      IPv4 and IPv6.
67  *
68  *  O   In the event that the ident socket throws a ModuleException,
69  *      nothing is done. This is counted as total and complete
70  *      failure to create a connection.
71  * --------------------------------------------------------------
72  */
73
74 class IdentRequestSocket : public EventHandler
75 {
76  public:
77         LocalUser *user;                        /* User we are attached to */
78         std::string result;             /* Holds the ident string if done */
79         time_t age;
80         bool done;                      /* True if lookup is finished */
81
82         IdentRequestSocket(LocalUser* u) : user(u)
83         {
84                 age = ServerInstance->Time();
85
86                 SetFd(socket(user->server_sa.sa.sa_family, SOCK_STREAM, 0));
87
88                 if (GetFd() == -1)
89                         throw ModuleException("Could not create socket");
90
91                 done = false;
92
93                 irc::sockets::sockaddrs bindaddr;
94                 irc::sockets::sockaddrs connaddr;
95
96                 memcpy(&bindaddr, &user->server_sa, sizeof(bindaddr));
97                 memcpy(&connaddr, &user->client_sa, sizeof(connaddr));
98
99                 if (connaddr.sa.sa_family == AF_INET6)
100                 {
101                         bindaddr.in6.sin6_port = 0;
102                         connaddr.in6.sin6_port = htons(113);
103                 }
104                 else
105                 {
106                         bindaddr.in4.sin_port = 0;
107                         connaddr.in4.sin_port = htons(113);
108                 }
109
110                 /* Attempt to bind (ident requests must come from the ip the query is referring to */
111                 if (ServerInstance->SE->Bind(GetFd(), bindaddr) < 0)
112                 {
113                         this->Close();
114                         throw ModuleException("failed to bind()");
115                 }
116
117                 ServerInstance->SE->NonBlocking(GetFd());
118
119                 /* Attempt connection (nonblocking) */
120                 if (ServerInstance->SE->Connect(this, &connaddr.sa, connaddr.sa_size()) == -1 && errno != EINPROGRESS)
121                 {
122                         this->Close();
123                         throw ModuleException("connect() failed");
124                 }
125
126                 /* Add fd to socket engine */
127                 if (!ServerInstance->SE->AddFd(this, FD_WANT_NO_READ | FD_WANT_POLL_WRITE))
128                 {
129                         this->Close();
130                         throw ModuleException("out of fds");
131                 }
132         }
133
134         virtual void OnConnected()
135         {
136                 ServerInstance->Logs->Log("m_ident",DEBUG,"OnConnected()");
137                 ServerInstance->SE->ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
138
139                 char req[32];
140
141                 /* Build request in the form 'localport,remoteport\r\n' */
142                 int req_size;
143                 if (user->client_sa.sa.sa_family == AF_INET6)
144                         req_size = snprintf(req, sizeof(req), "%d,%d\r\n",
145                                 ntohs(user->client_sa.in6.sin6_port), ntohs(user->server_sa.in6.sin6_port));
146                 else
147                         req_size = snprintf(req, sizeof(req), "%d,%d\r\n",
148                                 ntohs(user->client_sa.in4.sin_port), ntohs(user->server_sa.in4.sin_port));
149
150                 /* Send failed if we didnt write the whole ident request --
151                  * might as well give up if this happens!
152                  */
153                 if (ServerInstance->SE->Send(this, req, req_size, 0) < req_size)
154                         done = true;
155         }
156
157         virtual void HandleEvent(EventType et, int errornum = 0)
158         {
159                 switch (et)
160                 {
161                         case EVENT_READ:
162                                 /* fd readable event, received ident response */
163                                 ReadResponse();
164                         break;
165                         case EVENT_WRITE:
166                                 /* fd writeable event, successfully connected! */
167                                 OnConnected();
168                         break;
169                         case EVENT_ERROR:
170                                 /* fd error event, ohshi- */
171                                 ServerInstance->Logs->Log("m_ident",DEBUG,"EVENT_ERROR");
172                                 /* We *must* Close() here immediately or we get a
173                                  * huge storm of EVENT_ERROR events!
174                                  */
175                                 Close();
176                                 done = true;
177                         break;
178                 }
179         }
180
181         void Close()
182         {
183                 /* Remove ident socket from engine, and close it, but dont detatch it
184                  * from its parent user class, or attempt to delete its memory.
185                  */
186                 if (GetFd() > -1)
187                 {
188                         ServerInstance->Logs->Log("m_ident",DEBUG,"Close ident socket %d", GetFd());
189                         ServerInstance->SE->DelFd(this);
190                         ServerInstance->SE->Close(GetFd());
191                         this->SetFd(-1);
192                 }
193         }
194
195         bool HasResult()
196         {
197                 return done;
198         }
199
200         void ReadResponse()
201         {
202                 /* We don't really need to buffer for incomplete replies here, since IDENT replies are
203                  * extremely short - there is *no* sane reason it'd be in more than one packet
204                  */
205                 char ibuf[MAXBUF];
206                 int recvresult = ServerInstance->SE->Recv(this, ibuf, MAXBUF-1, 0);
207
208                 /* Cant possibly be a valid response shorter than 3 chars,
209                  * because the shortest possible response would look like: '1,1'
210                  */
211                 if (recvresult < 3)
212                 {
213                         Close();
214                         done = true;
215                         return;
216                 }
217
218                 ServerInstance->Logs->Log("m_ident",DEBUG,"ReadResponse()");
219
220                 irc::sepstream sep(ibuf, ':');
221                 std::string token;
222                 for (int i = 0; sep.GetToken(token); i++)
223                 {
224                         /* We only really care about the 4th portion */
225                         if (i < 3)
226                                 continue;
227
228                         std::string ident;
229
230                         /* Truncate the ident at any characters we don't like, skip leading spaces */
231                         size_t k = 0;
232                         for (const char *j = token.c_str(); *j && (k < ServerInstance->Config->Limits.IdentMax + 1); j++)
233                         {
234                                 if (*j == ' ')
235                                         continue;
236
237                                 /* Rules taken from InspIRCd::IsIdent */
238                                 if (((*j >= 'A') && (*j <= '}')) || ((*j >= '0') && (*j <= '9')) || (*j == '-') || (*j == '.'))
239                                 {
240                                         ident += *j;
241                                         continue;
242                                 }
243
244                                 break;
245                         }
246
247                         /* Re-check with IsIdent, in case that changes and this doesn't (paranoia!) */
248                         if (!ident.empty() && ServerInstance->IsIdent(ident.c_str()))
249                         {
250                                 result = ident;
251                         }
252
253                         break;
254                 }
255
256                 /* Close (but dont delete from memory) our socket
257                  * and flag as done
258                  */
259                 Close();
260                 done = true;
261                 return;
262         }
263 };
264
265 class ModuleIdent : public Module
266 {
267         int RequestTimeout;
268         SimpleExtItem<IdentRequestSocket> ext;
269  public:
270         ModuleIdent() : ext("ident_socket", this)
271         {
272                 OnRehash(NULL);
273                 Implementation eventlist[] = {
274                         I_OnRehash, I_OnUserInit, I_OnCheckReady,
275                         I_OnUserDisconnect, I_OnSetConnectClass
276                 };
277                 ServerInstance->Modules->Attach(eventlist, this, 5);
278         }
279
280         ~ModuleIdent()
281         {
282         }
283
284         virtual Version GetVersion()
285         {
286                 return Version("Provides support for RFC1413 ident lookups", VF_VENDOR);
287         }
288
289         virtual void OnRehash(User *user)
290         {
291                 ConfigReader Conf;
292
293                 RequestTimeout = Conf.ReadInteger("ident", "timeout", 0, true);
294                 if (!RequestTimeout)
295                         RequestTimeout = 5;
296         }
297
298         void OnUserInit(LocalUser *user)
299         {
300                 ConfigTag* tag = user->MyClass->config;
301                 if (!tag->getBool("useident", true))
302                         return;
303
304                 user->WriteServ("NOTICE Auth :*** Looking up your ident...");
305
306                 try
307                 {
308                         IdentRequestSocket *isock = new IdentRequestSocket(IS_LOCAL(user));
309                         ext.set(user, isock);
310                 }
311                 catch (ModuleException &e)
312                 {
313                         ServerInstance->Logs->Log("m_ident",DEBUG,"Ident exception: %s", e.GetReason());
314                 }
315         }
316
317         /* This triggers pretty regularly, we can use it in preference to
318          * creating a Timer object and especially better than creating a
319          * Timer per ident lookup!
320          */
321         virtual ModResult OnCheckReady(LocalUser *user)
322         {
323                 /* Does user have an ident socket attached at all? */
324                 IdentRequestSocket *isock = ext.get(user);
325                 if (!isock)
326                 {
327                         ServerInstance->Logs->Log("m_ident",DEBUG, "No ident socket :(");
328                         return MOD_RES_PASSTHRU;
329                 }
330
331                 ServerInstance->Logs->Log("m_ident",DEBUG, "Has ident_socket");
332
333                 time_t compare = isock->age;
334                 compare += RequestTimeout;
335
336                 /* Check for timeout of the socket */
337                 if (ServerInstance->Time() >= compare)
338                 {
339                         /* Ident timeout */
340                         user->WriteServ("NOTICE Auth :*** Ident request timed out.");
341                         ServerInstance->Logs->Log("m_ident",DEBUG, "Timeout");
342                 }
343                 else if (!isock->HasResult())
344                 {
345                         // time still good, no result yet... hold the registration
346                         ServerInstance->Logs->Log("m_ident",DEBUG, "No result yet");
347                         return MOD_RES_DENY;
348                 }
349
350                 ServerInstance->Logs->Log("m_ident",DEBUG, "Yay, result!");
351
352                 /* wooo, got a result (it will be good, or bad) */
353                 if (isock->result.empty())
354                 {
355                         user->ident.insert(0, 1, '~');
356                         user->WriteServ("NOTICE Auth :*** Could not find your ident, using %s instead.", user->ident.c_str());
357                 }
358                 else
359                 {
360                         user->ident = isock->result;
361                         user->WriteServ("NOTICE Auth :*** Found your ident, '%s'", user->ident.c_str());
362                 }
363
364                 isock->Close();
365                 ext.unset(user);
366                 return MOD_RES_PASSTHRU;
367         }
368
369         ModResult OnSetConnectClass(LocalUser* user, ConnectClass* myclass)
370         {
371                 if (myclass->config->getBool("requireident") && user->ident[0] == '~')
372                         return MOD_RES_DENY;
373                 return MOD_RES_PASSTHRU;
374         }
375
376         virtual void OnCleanup(int target_type, void *item)
377         {
378                 /* Module unloading, tidy up users */
379                 if (target_type == TYPE_USER)
380                         OnUserDisconnect((LocalUser*)item);
381         }
382
383         virtual void OnUserDisconnect(LocalUser *user)
384         {
385                 /* User disconnect (generic socket detatch event) */
386                 IdentRequestSocket *isock = ext.get(user);
387                 if (isock)
388                 {
389                         isock->Close();
390                         ext.unset(user);
391                 }
392         }
393 };
394
395 MODULE_INIT(ModuleIdent)
396