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