]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_sslinfo.cpp
Fix the cloaking module on C++98 compilers.
[user/henk/code/inspircd.git] / src / modules / m_sslinfo.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2020 Matt Schatz <genius3000@g3k.solutions>
5  *   Copyright (C) 2019 linuxdaemon <linuxdaemon.irc@gmail.com>
6  *   Copyright (C) 2013, 2017-2021 Sadie Powell <sadie@witchery.services>
7  *   Copyright (C) 2012-2016 Attila Molnar <attilamolnar@hush.com>
8  *   Copyright (C) 2012 Robby <robby@chatbelgie.be>
9  *   Copyright (C) 2010 Adam <Adam@anope.org>
10  *   Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
11  *   Copyright (C) 2007 Dennis Friis <peavey@inspircd.org>
12  *   Copyright (C) 2006-2007, 2009-2010 Craig Edwards <brain@inspircd.org>
13  *
14  * This file is part of InspIRCd.  InspIRCd is free software: you can
15  * redistribute it and/or modify it under the terms of the GNU General Public
16  * License as published by the Free Software Foundation, version 2.
17  *
18  * This program is distributed in the hope that it will be useful, but WITHOUT
19  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
20  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
21  * details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
25  */
26
27
28 #include "inspircd.h"
29 #include "modules/ssl.h"
30 #include "modules/webirc.h"
31 #include "modules/whois.h"
32 #include "modules/who.h"
33
34 enum
35 {
36         // From oftc-hybrid.
37         RPL_WHOISCERTFP = 276,
38
39         // From UnrealIRCd.
40         RPL_WHOISSECURE = 671
41 };
42
43 class SSLCertExt : public ExtensionItem
44 {
45  public:
46         SSLCertExt(Module* parent)
47                 : ExtensionItem("ssl_cert", ExtensionItem::EXT_USER, parent)
48         {
49         }
50
51         ssl_cert* get(const Extensible* item) const
52         {
53                 return static_cast<ssl_cert*>(get_raw(item));
54         }
55
56         void set(Extensible* item, ssl_cert* value)
57         {
58                 value->refcount_inc();
59                 ssl_cert* old = static_cast<ssl_cert*>(set_raw(item, value));
60                 if (old && old->refcount_dec())
61                         delete old;
62         }
63
64         void unset(Extensible* container)
65         {
66                 free(container, unset_raw(container));
67         }
68
69         std::string ToNetwork(const Extensible* container, void* item) const CXX11_OVERRIDE
70         {
71                 return static_cast<ssl_cert*>(item)->GetMetaLine();
72         }
73
74         void FromNetwork(Extensible* container, const std::string& value) CXX11_OVERRIDE
75         {
76                 ssl_cert* cert = new ssl_cert;
77                 set(container, cert);
78
79                 std::stringstream s(value);
80                 std::string v;
81                 getline(s,v,' ');
82
83                 cert->invalid = (v.find('v') != std::string::npos);
84                 cert->trusted = (v.find('T') != std::string::npos);
85                 cert->revoked = (v.find('R') != std::string::npos);
86                 cert->unknownsigner = (v.find('s') != std::string::npos);
87                 if (v.find('E') != std::string::npos)
88                 {
89                         getline(s,cert->error,'\n');
90                 }
91                 else
92                 {
93                         getline(s,cert->fingerprint,' ');
94                         getline(s,cert->dn,' ');
95                         getline(s,cert->issuer,'\n');
96                 }
97         }
98
99         void free(Extensible* container, void* item) CXX11_OVERRIDE
100         {
101                 ssl_cert* old = static_cast<ssl_cert*>(item);
102                 if (old && old->refcount_dec())
103                         delete old;
104         }
105 };
106
107 class UserCertificateAPIImpl : public UserCertificateAPIBase
108 {
109  public:
110         LocalIntExt nosslext;
111         SSLCertExt sslext;
112
113         UserCertificateAPIImpl(Module* mod)
114                 : UserCertificateAPIBase(mod)
115                 , nosslext("no_ssl_cert", ExtensionItem::EXT_USER, mod)
116                 , sslext(mod)
117         {
118         }
119
120         ssl_cert* GetCertificate(User* user) CXX11_OVERRIDE
121         {
122                 ssl_cert* cert = sslext.get(user);
123                 if (cert)
124                         return cert;
125
126                 LocalUser* luser = IS_LOCAL(user);
127                 if (!luser || nosslext.get(luser))
128                         return NULL;
129
130                 cert = SSLClientCert::GetCertificate(&luser->eh);
131                 if (!cert)
132                         return NULL;
133
134                 SetCertificate(user, cert);
135                 return cert;
136         }
137
138         void SetCertificate(User* user, ssl_cert* cert) CXX11_OVERRIDE
139         {
140                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Setting TLS (SSL) client certificate for %s: %s",
141                         user->GetFullHost().c_str(), cert->GetMetaLine().c_str());
142                 sslext.set(user, cert);
143         }
144 };
145
146 class CommandSSLInfo : public SplitCommand
147 {
148  private:
149         ChanModeReference sslonlymode;
150
151         void HandleUserInternal(LocalUser* source, User* target, bool verbose)
152         {
153                 ssl_cert* cert = sslapi.GetCertificate(target);
154                 if (!cert)
155                 {
156                         source->WriteNotice(InspIRCd::Format("*** %s is not connected using TLS (SSL).", target->nick.c_str()));
157                 }
158                 else if (cert->GetError().length())
159                 {
160                         source->WriteNotice(InspIRCd::Format("*** %s is connected using TLS (SSL) but has not specified a valid client certificate (%s).",
161                                 target->nick.c_str(), cert->GetError().c_str()));
162                 }
163                 else if (!verbose)
164                 {
165                         source->WriteNotice(InspIRCd::Format("*** %s is connected using TLS (SSL) with a valid client certificate (%s).",
166                                 target->nick.c_str(), cert->GetFingerprint().c_str()));
167                 }
168                 else
169                 {
170                         source->WriteNotice("*** Distinguished Name: " + cert->GetDN());
171                         source->WriteNotice("*** Issuer:             " + cert->GetIssuer());
172                         source->WriteNotice("*** Key Fingerprint:    " + cert->GetFingerprint());
173                 }
174         }
175
176         CmdResult HandleUser(LocalUser* source, const std::string& nick)
177         {
178                 User* target = ServerInstance->FindNickOnly(nick);
179                 if (!target || target->registered != REG_ALL)
180                 {
181                         source->WriteNumeric(Numerics::NoSuchNick(nick));
182                         return CMD_FAILURE;
183                 }
184
185                 if (operonlyfp && !source->IsOper() && source != target)
186                 {
187                         source->WriteNumeric(ERR_NOPRIVILEGES, "You must be a server operator to view TLS (SSL) client certificate information for other users.");
188                         return CMD_FAILURE;
189                 }
190
191                 HandleUserInternal(source, target, true);
192                 return CMD_SUCCESS;
193         }
194
195         CmdResult HandleChannel(LocalUser* source, const std::string& channel)
196         {
197                 Channel* chan = ServerInstance->FindChan(channel);
198                 if (!chan)
199                 {
200                         source->WriteNumeric(Numerics::NoSuchChannel(channel));
201                         return CMD_FAILURE;
202                 }
203
204                 if (operonlyfp && !source->IsOper())
205                 {
206                         source->WriteNumeric(ERR_NOPRIVILEGES, "You must be a server operator to view TLS (SSL) client certificate information for channels.");
207                         return CMD_FAILURE;
208                 }
209
210                 if (!source->IsOper() && chan->GetPrefixValue(source) < OP_VALUE)
211                 {
212                         source->WriteNumeric(ERR_CHANOPRIVSNEEDED, chan->name, "You must be a channel operator.");
213                         return CMD_FAILURE;
214                 }
215
216                 if (sslonlymode)
217                 {
218                         source->WriteNotice(InspIRCd::Format("*** %s %s have channel mode +%c (%s) set.",
219                                 chan->name.c_str(), chan->IsModeSet(sslonlymode) ? "does" : "does not",
220                                 sslonlymode->GetModeChar(), sslonlymode->name.c_str()));
221                 }
222
223                 const Channel::MemberMap& userlist = chan->GetUsers();
224                 for (Channel::MemberMap::const_iterator i = userlist.begin(); i != userlist.end(); ++i)
225                         HandleUserInternal(source, i->first, false);
226
227                 return CMD_SUCCESS;
228         }
229
230  public:
231         UserCertificateAPIImpl sslapi;
232         bool operonlyfp;
233
234         CommandSSLInfo(Module* Creator)
235                 : SplitCommand(Creator, "SSLINFO", 1)
236                 , sslonlymode(Creator, "sslonly")
237                 , sslapi(Creator)
238         {
239                 allow_empty_last_param = false;
240                 syntax = "<channel|nick>";
241         }
242
243         CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE
244         {
245                 if (ServerInstance->IsChannel(parameters[0]))
246                         return HandleChannel(user, parameters[0]);
247                 else
248                         return HandleUser(user, parameters[0]);
249         }
250 };
251
252 class ModuleSSLInfo
253         : public Module
254         , public WebIRC::EventListener
255         , public Whois::EventListener
256         , public Who::EventListener
257 {
258  private:
259         CommandSSLInfo cmd;
260
261         bool MatchFP(ssl_cert* const cert, const std::string& fp) const
262         {
263                 return irc::spacesepstream(fp).Contains(cert->GetFingerprint());
264         }
265
266  public:
267         ModuleSSLInfo()
268                 : WebIRC::EventListener(this)
269                 , Whois::EventListener(this)
270                 , Who::EventListener(this)
271                 , cmd(this)
272         {
273         }
274
275         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
276         {
277                 ConfigTag* tag = ServerInstance->Config->ConfValue("sslinfo");
278                 cmd.operonlyfp = tag->getBool("operonly");
279         }
280
281         Version GetVersion() CXX11_OVERRIDE
282         {
283                 return Version("Adds user facing TLS (SSL) information, various TLS (SSL) configuration options, and the /SSLINFO command to look up TLS (SSL) certificate information for other users.", VF_VENDOR);
284         }
285
286         void OnWhois(Whois::Context& whois) CXX11_OVERRIDE
287         {
288                 ssl_cert* cert = cmd.sslapi.GetCertificate(whois.GetTarget());
289                 if (cert)
290                 {
291                         whois.SendLine(RPL_WHOISSECURE, "is using a secure connection");
292                         if ((!cmd.operonlyfp || whois.IsSelfWhois() || whois.GetSource()->IsOper()) && !cert->fingerprint.empty())
293                                 whois.SendLine(RPL_WHOISCERTFP, InspIRCd::Format("has TLS (SSL) client certificate fingerprint %s", cert->fingerprint.c_str()));
294                 }
295         }
296
297         ModResult OnWhoLine(const Who::Request& request, LocalUser* source, User* user, Membership* memb, Numeric::Numeric& numeric) CXX11_OVERRIDE
298         {
299                 size_t flag_index;
300                 if (!request.GetFieldIndex('f', flag_index))
301                         return MOD_RES_PASSTHRU;
302
303                 ssl_cert* cert = cmd.sslapi.GetCertificate(user);
304                 if (cert)
305                         numeric.GetParams()[flag_index].push_back('s');
306
307                 return MOD_RES_PASSTHRU;
308         }
309
310         ModResult OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated) CXX11_OVERRIDE
311         {
312                 if ((command == "OPER") && (validated))
313                 {
314                         ServerConfig::OperIndex::const_iterator i = ServerInstance->Config->oper_blocks.find(parameters[0]);
315                         if (i != ServerInstance->Config->oper_blocks.end())
316                         {
317                                 OperInfo* ifo = i->second;
318                                 ssl_cert* cert = cmd.sslapi.GetCertificate(user);
319
320                                 if (ifo->oper_block->getBool("sslonly") && !cert)
321                                 {
322                                         user->WriteNumeric(ERR_NOOPERHOST, "Invalid oper credentials");
323                                         user->CommandFloodPenalty += 10000;
324                                         ServerInstance->SNO->WriteGlobalSno('o', "WARNING! Failed oper attempt by %s using login '%s': a secure connection is required.", user->GetFullRealHost().c_str(), parameters[0].c_str());
325                                         return MOD_RES_DENY;
326                                 }
327
328                                 std::string fingerprint;
329                                 if (ifo->oper_block->readString("fingerprint", fingerprint) && (!cert || !MatchFP(cert, fingerprint)))
330                                 {
331                                         user->WriteNumeric(ERR_NOOPERHOST, "Invalid oper credentials");
332                                         user->CommandFloodPenalty += 10000;
333                                         ServerInstance->SNO->WriteGlobalSno('o', "WARNING! Failed oper attempt by %s using login '%s': their TLS (SSL) client certificate fingerprint does not match.", user->GetFullRealHost().c_str(), parameters[0].c_str());
334                                         return MOD_RES_DENY;
335                                 }
336                         }
337                 }
338
339                 // Let core handle it for extra stuff
340                 return MOD_RES_PASSTHRU;
341         }
342
343         void OnPostConnect(User* user) CXX11_OVERRIDE
344         {
345                 LocalUser* const localuser = IS_LOCAL(user);
346                 if (!localuser)
347                         return;
348
349                 const SSLIOHook* const ssliohook = SSLIOHook::IsSSL(&localuser->eh);
350                 if (!ssliohook || cmd.sslapi.nosslext.get(localuser))
351                         return;
352
353                 ssl_cert* const cert = ssliohook->GetCertificate();
354
355                 std::string text = "*** You are connected to ";
356                 if (!ssliohook->GetServerName(text))
357                         text.append(ServerInstance->Config->GetServerName());
358                 text.append(" using TLS (SSL) cipher '");
359                 ssliohook->GetCiphersuite(text);
360                 text.push_back('\'');
361                 if (cert && !cert->GetFingerprint().empty())
362                         text.append(" and your TLS (SSL) client certificate fingerprint is ").append(cert->GetFingerprint());
363                 user->WriteNotice(text);
364
365                 if (!cert)
366                         return;
367
368                 // Find an auto-oper block for this user
369                 for (ServerConfig::OperIndex::const_iterator i = ServerInstance->Config->oper_blocks.begin(); i != ServerInstance->Config->oper_blocks.end(); ++i)
370                 {
371                         OperInfo* ifo = i->second;
372                         std::string fp = ifo->oper_block->getString("fingerprint");
373                         if (MatchFP(cert, fp) && ifo->oper_block->getBool("autologin"))
374                                 user->Oper(ifo);
375                 }
376         }
377
378         ModResult OnSetConnectClass(LocalUser* user, ConnectClass* myclass) CXX11_OVERRIDE
379         {
380                 ssl_cert* cert = cmd.sslapi.GetCertificate(user);
381                 const char* error = NULL;
382                 const std::string requiressl = myclass->config->getString("requiressl");
383                 if (stdalgo::string::equalsci(requiressl, "trusted"))
384                 {
385                         if (!cert || !cert->IsCAVerified())
386                                 error = "a trusted TLS (SSL) client certificate";
387                 }
388                 else if (myclass->config->getBool("requiressl"))
389                 {
390                         if (!cert)
391                                 error = "a TLS (SSL) connection";
392                 }
393
394                 if (error)
395                 {
396                         ServerInstance->Logs->Log("CONNECTCLASS", LOG_DEBUG, "The %s connect class is not suitable as it requires %s",
397                                 myclass->GetName().c_str(), error);
398                         return MOD_RES_DENY;
399                 }
400
401                 return MOD_RES_PASSTHRU;
402         }
403
404         void OnWebIRCAuth(LocalUser* user, const WebIRC::FlagMap* flags) CXX11_OVERRIDE
405         {
406                 // We are only interested in connection flags. If none have been
407                 // given then we have nothing to do.
408                 if (!flags)
409                         return;
410
411                 // We only care about the tls connection flag if the connection
412                 // between the gateway and the server is secure.
413                 if (!cmd.sslapi.GetCertificate(user))
414                         return;
415
416                 WebIRC::FlagMap::const_iterator iter = flags->find("secure");
417                 if (iter == flags->end())
418                 {
419                         // If this is not set then the connection between the client and
420                         // the gateway is not secure.
421                         cmd.sslapi.nosslext.set(user, 1);
422                         cmd.sslapi.sslext.unset(user);
423                         return;
424                 }
425
426                 // Create a fake ssl_cert for the user.
427                 ssl_cert* cert = new ssl_cert;
428                 cert->error = "WebIRC users can not specify valid certs yet";
429                 cert->invalid = true;
430                 cert->revoked = true;
431                 cert->trusted = false;
432                 cert->unknownsigner = true;
433                 cmd.sslapi.SetCertificate(user, cert);
434         }
435 };
436
437 MODULE_INIT(ModuleSSLInfo)