]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_cgiirc.cpp
fc55cc47fa268652e2672ef90aeb276b08664c71
[user/henk/code/inspircd.git] / src / modules / m_cgiirc.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
5  *   Copyright (C) 2007-2008 John Brooks <john.brooks@dereferenced.net>
6  *   Copyright (C) 2008 Pippijn van Steenhoven <pip88nl@gmail.com>
7  *   Copyright (C) 2006-2008 Craig Edwards <craigedwards@brainbox.cc>
8  *   Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
9  *   Copyright (C) 2007 Dennis Friis <peavey@inspircd.org>
10  *   Copyright (C) 2006 Oliver Lupton <oliverlupton@gmail.com>
11  *
12  * This file is part of InspIRCd.  InspIRCd is free software: you can
13  * redistribute it and/or modify it under the terms of the GNU General Public
14  * License as published by the Free Software Foundation, version 2.
15  *
16  * This program is distributed in the hope that it will be useful, but WITHOUT
17  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
19  * details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23  */
24
25
26 #include "inspircd.h"
27
28 /* $ModDesc: Change user's hosts connecting from known CGI:IRC hosts */
29
30 enum CGItype { INVALID, PASS, IDENT, PASSFIRST, IDENTFIRST, WEBIRC };
31
32
33 /** Holds a CGI site's details
34  */
35 class CGIhost
36 {
37 public:
38         std::string hostmask;
39         CGItype type;
40         std::string password;
41
42         CGIhost(const std::string &mask = "", CGItype t = IDENTFIRST, const std::string &spassword ="")
43         : hostmask(mask), type(t), password(spassword)
44         {
45         }
46 };
47 typedef std::vector<CGIhost> CGIHostlist;
48
49 /*
50  * WEBIRC
51  *  This is used for the webirc method of CGIIRC auth, and is (really) the best way to do these things.
52  *  Syntax: WEBIRC password client hostname ip
53  *  Where password is a shared key, client is the name of the "client" and version (e.g. cgiirc), hostname
54  *  is the resolved host of the client issuing the command and IP is the real IP of the client.
55  *
56  * How it works:
57  *  To tie in with the rest of cgiirc module, and to avoid race conditions, /webirc is only processed locally
58  *  and simply sets metadata on the user, which is later decoded on full connect to give something meaningful.
59  */
60 class CommandWebirc : public Command
61 {
62  public:
63         bool notify;
64         StringExtItem realhost;
65         StringExtItem realip;
66         LocalStringExt webirc_hostname;
67         LocalStringExt webirc_ip;
68
69         CGIHostlist Hosts;
70         CommandWebirc(Module* Creator)
71                 : Command(Creator, "WEBIRC", 4),
72                   realhost("cgiirc_realhost", Creator), realip("cgiirc_realip", Creator),
73                   webirc_hostname("cgiirc_webirc_hostname", Creator), webirc_ip("cgiirc_webirc_ip", Creator)
74                 {
75                         works_before_reg = true;
76                         this->syntax = "password client hostname ip";
77                 }
78                 CmdResult Handle(const std::vector<std::string> &parameters, User *user)
79                 {
80                         if(user->registered == REG_ALL)
81                                 return CMD_FAILURE;
82
83                         for(CGIHostlist::iterator iter = Hosts.begin(); iter != Hosts.end(); iter++)
84                         {
85                                 if(InspIRCd::Match(user->host, iter->hostmask, ascii_case_insensitive_map) || InspIRCd::MatchCIDR(user->GetIPString(), iter->hostmask, ascii_case_insensitive_map))
86                                 {
87                                         if(iter->type == WEBIRC && parameters[0] == iter->password)
88                                         {
89                                                 realhost.set(user, user->host);
90                                                 realip.set(user, user->GetIPString());
91                                                 if (notify)
92                                                         ServerInstance->SNO->WriteGlobalSno('a', "Connecting user %s detected as using CGI:IRC (%s), changing real host to %s from %s", user->nick.c_str(), user->host.c_str(), parameters[2].c_str(), user->host.c_str());
93                                                 webirc_hostname.set(user, parameters[2]);
94                                                 webirc_ip.set(user, parameters[3]);
95                                                 return CMD_SUCCESS;
96                                         }
97                                 }
98                         }
99
100                         ServerInstance->SNO->WriteGlobalSno('a', "Connecting user %s tried to use WEBIRC, but didn't match any configured webirc blocks.", user->GetFullRealHost().c_str());
101                         return CMD_FAILURE;
102                 }
103 };
104
105
106 /** Resolver for CGI:IRC hostnames encoded in ident/GECOS
107  */
108 class CGIResolver : public Resolver
109 {
110         std::string typ;
111         std::string theiruid;
112         LocalIntExt& waiting;
113         bool notify;
114  public:
115         CGIResolver(Module* me, bool NotifyOpers, const std::string &source, bool forward, LocalUser* u,
116                         const std::string &type, bool &cached, LocalIntExt& ext)
117                 : Resolver(source, forward ? DNS_QUERY_A : DNS_QUERY_PTR4, cached, me), typ(type), theiruid(u->uuid),
118                 waiting(ext), notify(NotifyOpers)
119         {
120         }
121
122         virtual void OnLookupComplete(const std::string &result, unsigned int ttl, bool cached)
123         {
124                 /* Check the user still exists */
125                 User* them = ServerInstance->FindUUID(theiruid);
126                 if (them)
127                 {
128                         if (notify)
129                                 ServerInstance->SNO->WriteGlobalSno('a', "Connecting user %s detected as using CGI:IRC (%s), changing real host to %s from %s", them->nick.c_str(), them->host.c_str(), result.c_str(), typ.c_str());
130
131                         if (result.length() > 64)
132                                 return;
133                         them->host = result;
134                         them->dhost = result;
135                         them->InvalidateCache();
136                         them->CheckLines(true);
137                 }
138         }
139
140         virtual void OnError(ResolverError e, const std::string &errormessage)
141         {
142                 User* them = ServerInstance->FindUUID(theiruid);
143                 if (them)
144                 {
145                         if (notify)
146                                 ServerInstance->SNO->WriteToSnoMask('a', "Connecting user %s detected as using CGI:IRC (%s), but their host can't be resolved from their %s!", them->nick.c_str(), them->host.c_str(), typ.c_str());
147                 }
148         }
149
150         virtual ~CGIResolver()
151         {
152                 User* them = ServerInstance->FindUUID(theiruid);
153                 if (!them)
154                         return;
155                 int count = waiting.get(them);
156                 if (count)
157                         waiting.set(them, count - 1);
158         }
159 };
160
161 class ModuleCgiIRC : public Module
162 {
163         CommandWebirc cmd;
164         LocalIntExt waiting;
165 public:
166         ModuleCgiIRC() : cmd(this), waiting("cgiirc-delay", this)
167         {
168         }
169
170         void init()
171         {
172                 OnRehash(NULL);
173                 ServerInstance->AddCommand(&cmd);
174                 ServerInstance->Extensions.Register(&cmd.realhost);
175                 ServerInstance->Extensions.Register(&cmd.realip);
176                 ServerInstance->Extensions.Register(&cmd.webirc_hostname);
177                 ServerInstance->Extensions.Register(&cmd.webirc_ip);
178                 ServerInstance->Extensions.Register(&waiting);
179
180                 Implementation eventlist[] = { I_OnRehash, I_OnUserRegister, I_OnCheckReady };
181                 ServerInstance->Modules->Attach(eventlist, this, 3);
182         }
183
184         void OnRehash(User* user)
185         {
186                 cmd.Hosts.clear();
187
188                 // Do we send an oper notice when a CGI:IRC has their host changed?
189                 cmd.notify = ServerInstance->Config->ConfValue("cgiirc")->getBool("opernotice", true);
190
191                 ConfigTagList tags = ServerInstance->Config->ConfTags("cgihost");
192                 for (ConfigIter i = tags.first; i != tags.second; ++i)
193                 {
194                         ConfigTag* tag = i->second;
195                         std::string hostmask = tag->getString("mask"); // An allowed CGI:IRC host
196                         std::string type = tag->getString("type"); // What type of user-munging we do on this host.
197                         std::string password = tag->getString("password");
198
199                         if(hostmask.length())
200                         {
201                                 if (type == "webirc" && !password.length()) {
202                                                 ServerInstance->Logs->Log("CONFIG",DEFAULT, "m_cgiirc: Missing password in config: %s", hostmask.c_str());
203                                 }
204                                 else
205                                 {
206                                         CGItype cgitype = INVALID;
207                                         if (type == "pass")
208                                                 cgitype = PASS;
209                                         else if (type == "ident")
210                                                 cgitype = IDENT;
211                                         else if (type == "passfirst")
212                                                 cgitype = PASSFIRST;
213                                         else if (type == "webirc")
214                                         {
215                                                 cgitype = WEBIRC;
216                                         }
217
218                                         if (cgitype == INVALID)
219                                                 cgitype = PASS;
220
221                                         cmd.Hosts.push_back(CGIhost(hostmask,cgitype, password.length() ? password : "" ));
222                                 }
223                         }
224                         else
225                         {
226                                 ServerInstance->Logs->Log("CONFIG",DEFAULT, "m_cgiirc.so: Invalid <cgihost:mask> value in config: %s", hostmask.c_str());
227                                 continue;
228                         }
229                 }
230         }
231
232         ModResult OnCheckReady(LocalUser *user)
233         {
234                 if (waiting.get(user))
235                         return MOD_RES_DENY;
236
237                 std::string *webirc_ip = cmd.webirc_ip.get(user);
238                 if (!webirc_ip)
239                         return MOD_RES_PASSTHRU;
240
241                 ServerInstance->Users->RemoveCloneCounts(user);
242                 user->SetClientIP(webirc_ip->c_str());
243                 cmd.webirc_ip.unset(user);
244
245                 std::string* webirc_hostname = cmd.webirc_hostname.get(user);
246                 if (webirc_hostname && webirc_hostname->length() < 64)
247                         user->host = user->dhost = *webirc_hostname;
248                 else
249                         user->host = user->dhost = user->GetIPString();
250
251                 user->InvalidateCache();
252                 cmd.webirc_hostname.unset(user);
253
254                 ServerInstance->Users->AddLocalClone(user);
255                 ServerInstance->Users->AddGlobalClone(user);
256                 user->SetClass();
257                 user->CheckClass();
258                 user->CheckLines(true);
259                 if (user->quitting)
260                         return MOD_RES_DENY;
261
262                 return MOD_RES_PASSTHRU;
263         }
264
265         ModResult OnUserRegister(LocalUser* user)
266         {
267                 for(CGIHostlist::iterator iter = cmd.Hosts.begin(); iter != cmd.Hosts.end(); iter++)
268                 {
269                         if(InspIRCd::Match(user->host, iter->hostmask, ascii_case_insensitive_map) || InspIRCd::MatchCIDR(user->GetIPString(), iter->hostmask, ascii_case_insensitive_map))
270                         {
271                                 // Deal with it...
272                                 if(iter->type == PASS)
273                                 {
274                                         CheckPass(user); // We do nothing if it fails so...
275                                         user->CheckLines(true);
276                                 }
277                                 else if(iter->type == PASSFIRST && !CheckPass(user))
278                                 {
279                                         // If the password lookup failed, try the ident
280                                         CheckIdent(user);       // If this fails too, do nothing
281                                         user->CheckLines(true);
282                                 }
283                                 else if(iter->type == IDENT)
284                                 {
285                                         CheckIdent(user); // Nothing on failure.
286                                         user->CheckLines(true);
287                                 }
288                                 else if(iter->type == IDENTFIRST && !CheckIdent(user))
289                                 {
290                                         // If the ident lookup fails, try the password.
291                                         CheckPass(user);
292                                         user->CheckLines(true);
293                                 }
294                                 else if(iter->type == WEBIRC)
295                                 {
296                                         // We don't need to do anything here
297                                 }
298                                 return MOD_RES_PASSTHRU;
299                         }
300                 }
301                 return MOD_RES_PASSTHRU;
302         }
303
304         bool CheckPass(LocalUser* user)
305         {
306                 if(IsValidHost(user->password))
307                 {
308                         cmd.realhost.set(user, user->host);
309                         cmd.realip.set(user, user->GetIPString());
310                         user->host = user->password;
311                         user->dhost = user->password;
312                         user->InvalidateCache();
313
314                         ServerInstance->Users->RemoveCloneCounts(user);
315                         user->SetClientIP(user->password.c_str());
316                         ServerInstance->Users->AddLocalClone(user);
317                         ServerInstance->Users->AddGlobalClone(user);
318                         user->SetClass();
319                         user->CheckClass();
320
321                         try
322                         {
323                                 bool cached;
324                                 CGIResolver* r = new CGIResolver(this, cmd.notify, user->password, false, user, "PASS", cached, waiting);
325                                 ServerInstance->AddResolver(r, cached);
326                                 waiting.set(user, waiting.get(user) + 1);
327                         }
328                         catch (...)
329                         {
330                                 if (cmd.notify)
331                                         ServerInstance->SNO->WriteToSnoMask('a', "Connecting user %s detected as using CGI:IRC (%s), but I could not resolve their hostname!", user->nick.c_str(), user->host.c_str());
332                         }
333
334                         user->password.clear();
335                         return true;
336                 }
337
338                 return false;
339         }
340
341         bool CheckIdent(LocalUser* user)
342         {
343                 const char* ident;
344                 int len = user->ident.length();
345                 in_addr newip;
346
347                 if(len == 8)
348                         ident = user->ident.c_str();
349                 else if(len == 9 && user->ident[0] == '~')
350                         ident = user->ident.c_str() + 1;
351                 else
352                         return false;
353
354                 errno = 0;
355                 unsigned long ipaddr = strtoul(ident, NULL, 16);
356                 if (errno)
357                         return false;
358                 newip.s_addr = htonl(ipaddr);
359                 char* newipstr = inet_ntoa(newip);
360
361                 cmd.realhost.set(user, user->host);
362                 cmd.realip.set(user, user->GetIPString());
363                 ServerInstance->Users->RemoveCloneCounts(user);
364                 user->SetClientIP(newipstr);
365                 ServerInstance->Users->AddLocalClone(user);
366                 ServerInstance->Users->AddGlobalClone(user);
367                 user->SetClass();
368                 user->CheckClass();
369                 user->host = newipstr;
370                 user->dhost = newipstr;
371                 user->ident.assign("~cgiirc", 0, 8);
372                 try
373                 {
374
375                         bool cached;
376                         CGIResolver* r = new CGIResolver(this, cmd.notify, newipstr, false, user, "IDENT", cached, waiting);
377                         ServerInstance->AddResolver(r, cached);
378                         waiting.set(user, waiting.get(user) + 1);
379                 }
380                 catch (...)
381                 {
382                         user->InvalidateCache();
383
384                         if(cmd.notify)
385                                  ServerInstance->SNO->WriteToSnoMask('a', "Connecting user %s detected as using CGI:IRC (%s), but I could not resolve their hostname!", user->nick.c_str(), user->host.c_str());
386                 }
387
388                 return true;
389         }
390
391         bool IsValidHost(const std::string &host)
392         {
393                 if(!host.size() || host.size() > 64)
394                         return false;
395
396                 for(unsigned int i = 0; i < host.size(); i++)
397                 {
398                         if(     ((host[i] >= '0') && (host[i] <= '9')) ||
399                                         ((host[i] >= 'A') && (host[i] <= 'Z')) ||
400                                         ((host[i] >= 'a') && (host[i] <= 'z')) ||
401                                         ((host[i] == '-') && (i > 0) && (i+1 < host.size()) && (host[i-1] != '.') && (host[i+1] != '.')) ||
402                                         ((host[i] == '.') && (i > 0) && (i+1 < host.size())) )
403
404                                 continue;
405                         else
406                                 return false;
407                 }
408
409                 return true;
410         }
411
412         bool IsValidIP(const std::string &ip)
413         {
414                 if(ip.size() < 7 || ip.size() > 15)
415                         return false;
416
417                 short sincedot = 0;
418                 short dots = 0;
419
420                 for(unsigned int i = 0; i < ip.size(); i++)
421                 {
422                         if((dots <= 3) && (sincedot <= 3))
423                         {
424                                 if((ip[i] >= '0') && (ip[i] <= '9'))
425                                 {
426                                         sincedot++;
427                                 }
428                                 else if(ip[i] == '.')
429                                 {
430                                         sincedot = 0;
431                                         dots++;
432                                 }
433                         }
434                         else
435                         {
436                                 return false;
437
438                         }
439                 }
440
441                 if(dots != 3)
442                         return false;
443
444                 return true;
445         }
446
447         virtual ~ModuleCgiIRC()
448         {
449         }
450
451         virtual Version GetVersion()
452         {
453                 return Version("Change user's hosts connecting from known CGI:IRC hosts",VF_VENDOR);
454         }
455
456 };
457
458 MODULE_INIT(ModuleCgiIRC)