]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_cloaking.cpp
Merge pull request #1337 from SaberUK/master+merge
[user/henk/code/inspircd.git] / src / modules / m_cloaking.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
5  *   Copyright (C) 2006-2008 Robin Burchell <robin+git@viroteck.net>
6  *   Copyright (C) 2008 Pippijn van Steenhoven <pip88nl@gmail.com>
7  *   Copyright (C) 2003-2008 Craig Edwards <craigedwards@brainbox.cc>
8  *   Copyright (C) 2007 John Brooks <john.brooks@dereferenced.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 #include "modules/hash.h"
28
29 enum CloakMode
30 {
31         /** 2.0 cloak of "half" of the hostname plus the full IP hash */
32         MODE_HALF_CLOAK,
33         /** 2.0 cloak of IP hash, split at 2 common CIDR range points */
34         MODE_OPAQUE
35 };
36
37 // lowercase-only encoding similar to base64, used for hash output
38 static const char base32[] = "0123456789abcdefghijklmnopqrstuv";
39
40 /** Handles user mode +x
41  */
42 class CloakUser : public ModeHandler
43 {
44  public:
45         LocalStringExt ext;
46         std::string debounce_uid;
47         time_t debounce_ts;
48         int debounce_count;
49
50         CloakUser(Module* source)
51                 : ModeHandler(source, "cloak", 'x', PARAM_NONE, MODETYPE_USER),
52                 ext("cloaked_host", ExtensionItem::EXT_USER, source), debounce_ts(0), debounce_count(0)
53         {
54         }
55
56         ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string &parameter, bool adding)
57         {
58                 LocalUser* user = IS_LOCAL(dest);
59
60                 /* For remote clients, we don't take any action, we just allow it.
61                  * The local server where they are will set their cloak instead.
62                  * This is fine, as we will receive it later.
63                  */
64                 if (!user)
65                 {
66                         dest->SetMode(this, adding);
67                         return MODEACTION_ALLOW;
68                 }
69
70                 if (user->uuid == debounce_uid && debounce_ts == ServerInstance->Time())
71                 {
72                         // prevent spamming using /mode user +x-x+x-x+x-x
73                         if (++debounce_count > 2)
74                                 return MODEACTION_DENY;
75                 }
76                 else
77                 {
78                         debounce_uid = user->uuid;
79                         debounce_count = 1;
80                         debounce_ts = ServerInstance->Time();
81                 }
82
83                 if (adding == user->IsModeSet(this))
84                         return MODEACTION_DENY;
85
86                 /* don't allow this user to spam modechanges */
87                 if (source == dest)
88                         user->CommandFloodPenalty += 5000;
89
90                 if (adding)
91                 {
92                         // assume this is more correct
93                         if (user->registered != REG_ALL && user->host != user->dhost)
94                                 return MODEACTION_DENY;
95
96                         std::string* cloak = ext.get(user);
97
98                         if (!cloak)
99                         {
100                                 /* Force creation of missing cloak */
101                                 creator->OnUserConnect(user);
102                                 cloak = ext.get(user);
103                         }
104                         if (cloak)
105                         {
106                                 user->ChangeDisplayedHost(*cloak);
107                                 user->SetMode(this, true);
108                                 return MODEACTION_ALLOW;
109                         }
110                         else
111                                 return MODEACTION_DENY;
112                 }
113                 else
114                 {
115                         /* User is removing the mode, so restore their real host
116                          * and make it match the displayed one.
117                          */
118                         user->SetMode(this, false);
119                         user->ChangeDisplayedHost(user->host.c_str());
120                         return MODEACTION_ALLOW;
121                 }
122         }
123 };
124
125 class CommandCloak : public Command
126 {
127  public:
128         CommandCloak(Module* Creator) : Command(Creator, "CLOAK", 1)
129         {
130                 flags_needed = 'o';
131                 syntax = "<host>";
132         }
133
134         CmdResult Handle(const std::vector<std::string> &parameters, User *user);
135 };
136
137 class ModuleCloaking : public Module
138 {
139  public:
140         CloakUser cu;
141         CloakMode mode;
142         CommandCloak ck;
143         std::string prefix;
144         std::string suffix;
145         std::string key;
146         const char* xtab[4];
147         dynamic_reference<HashProvider> Hash;
148
149         ModuleCloaking() : cu(this), mode(MODE_OPAQUE), ck(this), Hash(this, "hash/md5")
150         {
151         }
152
153         /** This function takes a domain name string and returns just the last two domain parts,
154          * or the last domain part if only two are available. Failing that it just returns what it was given.
155          *
156          * For example, if it is passed "svn.inspircd.org" it will return ".inspircd.org".
157          * If it is passed "brainbox.winbot.co.uk" it will return ".co.uk",
158          * and if it is passed "localhost.localdomain" it will return ".localdomain".
159          *
160          * This is used to ensure a significant part of the host is always cloaked (see Bug #216)
161          */
162         std::string LastTwoDomainParts(const std::string &host)
163         {
164                 int dots = 0;
165                 std::string::size_type splitdot = host.length();
166
167                 for (std::string::size_type x = host.length() - 1; x; --x)
168                 {
169                         if (host[x] == '.')
170                         {
171                                 splitdot = x;
172                                 dots++;
173                         }
174                         if (dots >= 3)
175                                 break;
176                 }
177
178                 if (splitdot == host.length())
179                         return "";
180                 else
181                         return host.substr(splitdot);
182         }
183
184         /**
185          * 2.0-style cloaking function
186          * @param item The item to cloak (part of an IP or hostname)
187          * @param id A unique ID for this type of item (to make it unique if the item matches)
188          * @param len The length of the output. Maximum for MD5 is 16 characters.
189          */
190         std::string SegmentCloak(const std::string& item, char id, int len)
191         {
192                 std::string input;
193                 input.reserve(key.length() + 3 + item.length());
194                 input.append(1, id);
195                 input.append(key);
196                 input.append(1, '\0'); // null does not terminate a C++ string
197                 input.append(item);
198
199                 std::string rv = Hash->GenerateRaw(input).substr(0,len);
200                 for(int i=0; i < len; i++)
201                 {
202                         // this discards 3 bits per byte. We have an
203                         // overabundance of bits in the hash output, doesn't
204                         // matter which ones we are discarding.
205                         rv[i] = base32[rv[i] & 0x1F];
206                 }
207                 return rv;
208         }
209
210         std::string SegmentIP(const irc::sockets::sockaddrs& ip, bool full)
211         {
212                 std::string bindata;
213                 int hop1, hop2, hop3;
214                 int len1, len2;
215                 std::string rv;
216                 if (ip.sa.sa_family == AF_INET6)
217                 {
218                         bindata = std::string((const char*)ip.in6.sin6_addr.s6_addr, 16);
219                         hop1 = 8;
220                         hop2 = 6;
221                         hop3 = 4;
222                         len1 = 6;
223                         len2 = 4;
224                         // pfx s1.s2.s3. (xxxx.xxxx or s4) sfx
225                         //     6  4  4    9/6
226                         rv.reserve(prefix.length() + 26 + suffix.length());
227                 }
228                 else
229                 {
230                         bindata = std::string((const char*)&ip.in4.sin_addr, 4);
231                         hop1 = 3;
232                         hop2 = 0;
233                         hop3 = 2;
234                         len1 = len2 = 3;
235                         // pfx s1.s2. (xxx.xxx or s3) sfx
236                         rv.reserve(prefix.length() + 15 + suffix.length());
237                 }
238
239                 rv.append(prefix);
240                 rv.append(SegmentCloak(bindata, 10, len1));
241                 rv.append(1, '.');
242                 bindata.erase(hop1);
243                 rv.append(SegmentCloak(bindata, 11, len2));
244                 if (hop2)
245                 {
246                         rv.append(1, '.');
247                         bindata.erase(hop2);
248                         rv.append(SegmentCloak(bindata, 12, len2));
249                 }
250
251                 if (full)
252                 {
253                         rv.append(1, '.');
254                         bindata.erase(hop3);
255                         rv.append(SegmentCloak(bindata, 13, 6));
256                         rv.append(suffix);
257                 }
258                 else
259                 {
260                         if (ip.sa.sa_family == AF_INET6)
261                         {
262                                 rv.append(InspIRCd::Format(".%02x%02x.%02x%02x%s",
263                                         ip.in6.sin6_addr.s6_addr[2], ip.in6.sin6_addr.s6_addr[3],
264                                         ip.in6.sin6_addr.s6_addr[0], ip.in6.sin6_addr.s6_addr[1], suffix.c_str()));
265                         }
266                         else
267                         {
268                                 const unsigned char* ip4 = (const unsigned char*)&ip.in4.sin_addr;
269                                 rv.append(InspIRCd::Format(".%d.%d%s", ip4[1], ip4[0], suffix.c_str()));
270                         }
271                 }
272                 return rv;
273         }
274
275         ModResult OnCheckBan(User* user, Channel* chan, const std::string& mask) CXX11_OVERRIDE
276         {
277                 LocalUser* lu = IS_LOCAL(user);
278                 if (!lu)
279                         return MOD_RES_PASSTHRU;
280
281                 OnUserConnect(lu);
282                 std::string* cloak = cu.ext.get(user);
283                 /* Check if they have a cloaked host, but are not using it */
284                 if (cloak && *cloak != user->dhost)
285                 {
286                         const std::string cloakMask = user->nick + "!" + user->ident + "@" + *cloak;
287                         if (InspIRCd::Match(cloakMask, mask))
288                                 return MOD_RES_DENY;
289                 }
290                 return MOD_RES_PASSTHRU;
291         }
292
293         void Prioritize()
294         {
295                 /* Needs to be after m_banexception etc. */
296                 ServerInstance->Modules->SetPriority(this, I_OnCheckBan, PRIORITY_LAST);
297         }
298
299         // this unsets umode +x on every host change. If we are actually doing a +x
300         // mode change, we will call SetMode back to true AFTER the host change is done.
301         void OnChangeHost(User* u, const std::string& host) CXX11_OVERRIDE
302         {
303                 if (u->IsModeSet(cu))
304                 {
305                         u->SetMode(cu, false);
306                         u->WriteCommand("MODE", "-" + ConvToStr(cu.GetModeChar()));
307                 }
308         }
309
310         Version GetVersion() CXX11_OVERRIDE
311         {
312                 std::string testcloak = "broken";
313                 if (Hash)
314                 {
315                         switch (mode)
316                         {
317                                 case MODE_HALF_CLOAK:
318                                         testcloak = prefix + SegmentCloak("*", 3, 8) + suffix;
319                                         break;
320                                 case MODE_OPAQUE:
321                                         testcloak = prefix + SegmentCloak("*", 4, 8) + suffix;
322                         }
323                 }
324                 return Version("Provides masking of user hostnames", VF_COMMON|VF_VENDOR, testcloak);
325         }
326
327         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
328         {
329                 ConfigTag* tag = ServerInstance->Config->ConfValue("cloak");
330                 prefix = tag->getString("prefix");
331                 suffix = tag->getString("suffix", ".IP");
332
333                 std::string modestr = tag->getString("mode");
334                 if (modestr == "half")
335                         mode = MODE_HALF_CLOAK;
336                 else if (modestr == "full")
337                         mode = MODE_OPAQUE;
338                 else
339                         throw ModuleException("Bad value for <cloak:mode>; must be half or full");
340
341                 key = tag->getString("key");
342                 if (key.empty() || key == "secret")
343                         throw ModuleException("You have not defined cloak keys for m_cloaking. Define <cloak:key> as a network-wide secret.");
344         }
345
346         std::string GenCloak(const irc::sockets::sockaddrs& ip, const std::string& ipstr, const std::string& host)
347         {
348                 std::string chost;
349
350                 irc::sockets::sockaddrs hostip;
351                 bool host_is_ip = irc::sockets::aptosa(host, ip.port(), hostip) && hostip == ip;
352
353                 switch (mode)
354                 {
355                         case MODE_HALF_CLOAK:
356                         {
357                                 if (!host_is_ip)
358                                         chost = prefix + SegmentCloak(host, 1, 6) + LastTwoDomainParts(host);
359                                 if (chost.empty() || chost.length() > 50)
360                                         chost = SegmentIP(ip, false);
361                                 break;
362                         }
363                         case MODE_OPAQUE:
364                         default:
365                                 chost = SegmentIP(ip, true);
366                 }
367                 return chost;
368         }
369
370         void OnUserConnect(LocalUser* dest) CXX11_OVERRIDE
371         {
372                 std::string* cloak = cu.ext.get(dest);
373                 if (cloak)
374                         return;
375
376                 cu.ext.set(dest, GenCloak(dest->client_sa, dest->GetIPString(), dest->host));
377         }
378 };
379
380 CmdResult CommandCloak::Handle(const std::vector<std::string> &parameters, User *user)
381 {
382         ModuleCloaking* mod = (ModuleCloaking*)(Module*)creator;
383         irc::sockets::sockaddrs sa;
384         std::string cloak;
385
386         if (irc::sockets::aptosa(parameters[0], 0, sa))
387                 cloak = mod->GenCloak(sa, parameters[0], parameters[0]);
388         else
389                 cloak = mod->GenCloak(sa, "", parameters[0]);
390
391         user->WriteNotice("*** Cloak for " + parameters[0] + " is " + cloak);
392
393         return CMD_SUCCESS;
394 }
395
396 MODULE_INIT(ModuleCloaking)