]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_ident.cpp
Make ident handling consistant: use ChangeIdent for all ident manipulation. This...
[user/henk/code/inspircd.git] / src / modules / m_ident.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  InspIRCd: (C) 2002-2009 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  private:
77         User *user;                     /* User we are attached to */
78         InspIRCd* ServerInstance;       /* Server instance */
79         bool done;                      /* True if lookup is finished */
80         std::string result;             /* Holds the ident string if done */
81  public:
82
83         IdentRequestSocket(InspIRCd *Server, User* u, const std::string &bindip) : user(u), ServerInstance(Server), result(u->ident)
84         {
85                 socklen_t size = 0;
86 #ifdef IPV6
87                 /* Does this look like a v6 ip address? */
88                 bool v6 = false;
89                 if ((bindip.empty()) || bindip.find(':') != std::string::npos)
90                 v6 = true;
91
92                 if (v6)
93                         SetFd(socket(AF_INET6, SOCK_STREAM, 0));
94                 else
95 #endif
96                         SetFd(socket(AF_INET, SOCK_STREAM, 0));
97
98                 if (GetFd() == -1)
99                         throw ModuleException("Could not create socket");
100
101                 done = false;
102
103                 /* We allocate two of these because sizeof(sockaddr_in6) > sizeof(sockaddr_in) */
104                 irc::sockets::sockaddrs s;
105                 irc::sockets::sockaddrs addr;
106
107 #ifdef IPV6
108                 /* Horrid icky nasty ugly berkely socket crap. */
109                 if (v6)
110                 {
111                         if (inet_pton(AF_INET6, user->GetIPString(), &addr.in6.sin6_addr) > 0)
112                         {
113                                 addr.in6.sin6_family = AF_INET6;
114                                 addr.in6.sin6_port = htons(113);
115                                 size = sizeof(sockaddr_in6);
116                                 inet_pton(AF_INET6, bindip.c_str(), &s.in6.sin6_addr);
117                                 s.in6.sin6_family = AF_INET6;
118                                 s.in6.sin6_port = 0;
119                         }
120                 }
121                 else
122 #endif
123                 {
124                         if (inet_aton(user->GetIPString(), &addr.in4.sin_addr) > 0)
125                         {
126                                 addr.in4.sin_family = AF_INET;
127                                 addr.in4.sin_port = htons(113);
128                                 size = sizeof(sockaddr_in);
129                                 inet_aton(bindip.c_str(), &s.in4.sin_addr);
130                                 s.in4.sin_family = AF_INET;
131                                 s.in4.sin_port = 0;
132                         }
133                 }
134
135                 /* Attempt to bind (ident requests must come from the ip the query is referring to */
136                 if (ServerInstance->SE->Bind(GetFd(), &s.sa, size) < 0)
137                 {
138                         this->Close();
139                         throw ModuleException("failed to bind()");
140                 }
141
142                 ServerInstance->SE->NonBlocking(GetFd());
143
144                 /* Attempt connection (nonblocking) */
145                 if (ServerInstance->SE->Connect(this, &addr.sa, size) == -1 && errno != EINPROGRESS)
146                 {
147                         this->Close();
148                         throw ModuleException("connect() failed");
149                 }
150
151                 /* Add fd to socket engine */
152                 if (!ServerInstance->SE->AddFd(this))
153                 {
154                         this->Close();
155                         throw ModuleException("out of fds");
156                 }
157
158                 /* Important: We set WantWrite immediately after connect()
159                  * because a successful connection will trigger a writability event
160                  */
161                 ServerInstance->SE->WantWrite(this);
162         }
163
164         virtual void OnConnected()
165         {
166                 ServerInstance->Logs->Log("m_ident",DEBUG,"OnConnected()");
167
168                 /* Both sockaddr_in and sockaddr_in6 can be safely casted to sockaddr, especially since the
169                  * only members we use are in a part of the struct that should always be identical (at the
170                  * byte level). */
171                 irc::sockets::sockaddrs laddr, raddr;
172
173                 socklen_t laddrsz = sizeof(laddr);
174                 socklen_t raddrsz = sizeof(raddr);
175
176                 if ((getsockname(user->GetFd(), &laddr.sa, &laddrsz) != 0) || (getpeername(user->GetFd(), &raddr.sa, &raddrsz) != 0))
177                 {
178                         done = true;
179                         return;
180                 }
181
182                 char req[32];
183
184                 /* Build request in the form 'localport,remoteport\r\n' */
185                 int req_size;
186 #ifdef IPV6
187                 if (raddr.sa.sa_family == AF_INET6)
188                         req_size = snprintf(req, sizeof(req), "%d,%d\r\n", ntohs(raddr.in6.sin6_port), ntohs(laddr.in6.sin6_port));
189                 else
190 #endif
191                         req_size = snprintf(req, sizeof(req), "%d,%d\r\n", ntohs(raddr.in4.sin_port), ntohs(laddr.in4.sin_port));
192
193                 /* Send failed if we didnt write the whole ident request --
194                  * might as well give up if this happens!
195                  */
196                 if (ServerInstance->SE->Send(this, req, req_size, 0) < req_size)
197                         done = true;
198         }
199
200         virtual void HandleEvent(EventType et, int errornum = 0)
201         {
202                 switch (et)
203                 {
204                         case EVENT_READ:
205                                 /* fd readable event, received ident response */
206                                 ReadResponse();
207                         break;
208                         case EVENT_WRITE:
209                                 /* fd writeable event, successfully connected! */
210                                 OnConnected();
211                         break;
212                         case EVENT_ERROR:
213                                 /* fd error event, ohshi- */
214                                 ServerInstance->Logs->Log("m_ident",DEBUG,"EVENT_ERROR");
215                                 /* We *must* Close() here immediately or we get a
216                                  * huge storm of EVENT_ERROR events!
217                                  */
218                                 Close();
219                                 done = true;
220                         break;
221                 }
222         }
223
224         void Close()
225         {
226                 /* Remove ident socket from engine, and close it, but dont detatch it
227                  * from its parent user class, or attempt to delete its memory.
228                  */
229                 if (GetFd() > -1)
230                 {
231                         ServerInstance->Logs->Log("m_ident",DEBUG,"Close ident socket %d", GetFd());
232                         ServerInstance->SE->DelFd(this);
233                         ServerInstance->SE->Close(GetFd());
234                         ServerInstance->SE->Shutdown(GetFd(), SHUT_WR);
235                         this->SetFd(-1);
236                 }
237         }
238
239         bool HasResult()
240         {
241                 return done;
242         }
243
244         /* Note: if the lookup succeeded, will contain 'ident', otherwise
245          * will contain '~ident'. Use *GetResult() to determine lookup success.
246          */
247         const char* GetResult()
248         {
249                 return result.c_str();
250         }
251
252         void ReadResponse()
253         {
254                 /* We don't really need to buffer for incomplete replies here, since IDENT replies are
255                  * extremely short - there is *no* sane reason it'd be in more than one packet
256                  */
257                 char ibuf[MAXBUF];
258                 int recvresult = ServerInstance->SE->Recv(this, ibuf, MAXBUF-1, 0);
259
260                 /* Cant possibly be a valid response shorter than 3 chars,
261                  * because the shortest possible response would look like: '1,1'
262                  */
263                 if (recvresult < 3)
264                 {
265                         Close();
266                         done = true;
267                         return;
268                 }
269
270                 ServerInstance->Logs->Log("m_ident",DEBUG,"ReadResponse()");
271
272                 irc::sepstream sep(ibuf, ':');
273                 std::string token;
274                 for (int i = 0; sep.GetToken(token); i++)
275                 {
276                         /* We only really care about the 4th portion */
277                         if (i < 3)
278                                 continue;
279
280                         std::string ident;
281
282                         /* Truncate the ident at any characters we don't like, skip leading spaces */
283                         size_t k = 0;
284                         for (const char *j = token.c_str(); *j && (k < ServerInstance->Config->Limits.IdentMax + 1); j++)
285                         {
286                                 if (*j == ' ')
287                                         continue;
288
289                                 /* Rules taken from InspIRCd::IsIdent */
290                                 if (((*j >= 'A') && (*j <= '}')) || ((*j >= '0') && (*j <= '9')) || (*j == '-') || (*j == '.'))
291                                 {
292                                         ident += *j;
293                                         continue;
294                                 }
295
296                                 break;
297                         }
298
299                         /* Re-check with IsIdent, in case that changes and this doesn't (paranoia!) */
300                         if (!ident.empty() && ServerInstance->IsIdent(ident.c_str()))
301                         {
302                                 result = ident;
303                         }
304
305                         break;
306                 }
307
308                 /* Close (but dont delete from memory) our socket
309                  * and flag as done
310                  */
311                 Close();
312                 done = true;
313                 return;
314         }
315 };
316
317 class ModuleIdent : public Module
318 {
319  private:
320         int RequestTimeout;
321         ConfigReader *Conf;
322  public:
323         ModuleIdent(InspIRCd *Me) : Module(Me)
324         {
325                 Conf = new ConfigReader(ServerInstance);
326                 OnRehash(NULL);
327                 Implementation eventlist[] = { I_OnRehash, I_OnUserRegister, I_OnCheckReady, I_OnCleanup, I_OnUserDisconnect };
328                 ServerInstance->Modules->Attach(eventlist, this, 5);
329         }
330
331         ~ModuleIdent()
332         {
333                 delete Conf;
334         }
335
336         virtual Version GetVersion()
337         {
338                 return Version("$Id$", VF_VENDOR, API_VERSION);
339         }
340
341         virtual void OnRehash(User *user)
342         {
343                 delete Conf;
344                 Conf = new ConfigReader(ServerInstance);
345
346                 RequestTimeout = Conf->ReadInteger("ident", "timeout", 0, true);
347                 if (!RequestTimeout)
348                         RequestTimeout = 5;
349         }
350
351         virtual int OnUserRegister(User *user)
352         {
353                 for (int j = 0; j < Conf->Enumerate("connect"); j++)
354                 {
355                         std::string hostn = Conf->ReadValue("connect","allow",j);
356                         /* XXX: Fixme: does not respect port, limit, etc */
357                         if ((InspIRCd::MatchCIDR(user->GetIPString(),hostn, ascii_case_insensitive_map)) || (InspIRCd::Match(user->host,hostn, ascii_case_insensitive_map)))
358                         {
359                                 bool useident = Conf->ReadFlag("connect", "useident", "yes", j);
360
361                                 if (!useident)
362                                         return 0;
363                         }
364                 }
365
366                 /* User::ident is currently the username field from USER; with m_ident loaded, that
367                  * should be preceded by a ~. The field is actually IdentMax+2 characters wide. */
368                 if (user->ident.length() > ServerInstance->Config->Limits.IdentMax + 1)
369                         user->ident.assign(user->ident, 0, ServerInstance->Config->Limits.IdentMax);
370                 user->ident.insert(0, "~");
371
372                 user->WriteServ("NOTICE Auth :*** Looking up your ident...");
373
374                 // Get the IP that the user is connected to, and bind to that for the outgoing connection
375                 irc::sockets::sockaddrs laddr;
376                 socklen_t laddrsz = sizeof(laddr);
377
378                 if (getsockname(user->GetFd(), &laddr.sa, &laddrsz) != 0)
379                 {
380                         user->WriteServ("NOTICE Auth :*** Could not find your ident, using %s instead.", user->ident.c_str());
381                         return 0;
382                 }
383
384                 char ip[INET6_ADDRSTRLEN + 1];
385 #ifdef IPV6
386                 if (laddr.sa.sa_family == AF_INET6)
387                         inet_ntop(laddr.in6.sin6_family, &laddr.in6.sin6_addr, ip, INET6_ADDRSTRLEN);
388                 else
389 #endif
390                         inet_ntop(laddr.in4.sin_family, &laddr.in4.sin_addr, ip, INET6_ADDRSTRLEN);
391
392                 IdentRequestSocket *isock = NULL;
393                 try
394                 {
395                         isock = new IdentRequestSocket(ServerInstance, user, ip);
396                 }
397                 catch (ModuleException &e)
398                 {
399                         ServerInstance->Logs->Log("m_ident",DEBUG,"Ident exception: %s", e.GetReason());
400                         return 0;
401                 }
402
403                 user->Extend("ident_socket", isock);
404                 return 0;
405         }
406
407         /* This triggers pretty regularly, we can use it in preference to
408          * creating a Timer object and especially better than creating a
409          * Timer per ident lookup!
410          */
411         virtual bool OnCheckReady(User *user)
412         {
413                 /* Does user have an ident socket attached at all? */
414                 IdentRequestSocket *isock = NULL;
415                 if (!user->GetExt("ident_socket", isock))
416                 {
417                         ServerInstance->Logs->Log("m_ident",DEBUG, "No ident socket :(");
418                         return true;
419                 }
420
421                 ServerInstance->Logs->Log("m_ident",DEBUG, "Has ident_socket");
422
423                 time_t compare = isock->age;
424                 compare += RequestTimeout;
425
426                 /* Check for timeout of the socket */
427                 if (ServerInstance->Time() >= compare)
428                 {
429                         /* Ident timeout */
430                         user->WriteServ("NOTICE Auth :*** Ident request timed out.");
431                         ServerInstance->Logs->Log("m_ident",DEBUG, "Timeout");
432                         /* The user isnt actually disconnecting,
433                          * we call this to clean up the user
434                          */
435                         OnUserDisconnect(user);
436                         return true;
437                 }
438
439                 /* Got a result yet? */
440                 if (!isock->HasResult())
441                 {
442                         ServerInstance->Logs->Log("m_ident",DEBUG, "No result yet");
443                         return false;
444                 }
445
446                 ServerInstance->Logs->Log("m_ident",DEBUG, "Yay, result!");
447
448                 /* wooo, got a result (it will be good, or bad) */
449                 if (*(isock->GetResult()) != '~')
450                         user->WriteServ("NOTICE Auth :*** Found your ident, '%s'", isock->GetResult());
451                 else
452                         user->WriteServ("NOTICE Auth :*** Could not find your ident, using %s instead.", isock->GetResult());
453
454                 /* Copy the ident string to the user */
455                 user->ChangeIdent(isock->GetResult());
456
457                 /* The user isnt actually disconnecting, we call this to clean up the user */
458                 OnUserDisconnect(user);
459                 return true;
460         }
461
462         virtual void OnCleanup(int target_type, void *item)
463         {
464                 /* Module unloading, tidy up users */
465                 if (target_type == TYPE_USER)
466                         OnUserDisconnect((User*)item);
467         }
468
469         virtual void OnUserDisconnect(User *user)
470         {
471                 /* User disconnect (generic socket detatch event) */
472                 IdentRequestSocket *isock = NULL;
473                 if (user->GetExt("ident_socket", isock))
474                 {
475                         isock->Close();
476                         delete isock;
477                         user->Shrink("ident_socket");
478                 }
479         }
480 };
481
482 MODULE_INIT(ModuleIdent)
483