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