]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_sslinfo.cpp
Skip clients on ulined servers in SSLINFO <#chan>.
[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                 {
226                         if (!i->first->server->IsULine())
227                                 HandleUserInternal(source, i->first, false);
228                 }
229
230                 return CMD_SUCCESS;
231         }
232
233  public:
234         UserCertificateAPIImpl sslapi;
235         bool operonlyfp;
236
237         CommandSSLInfo(Module* Creator)
238                 : SplitCommand(Creator, "SSLINFO", 1)
239                 , sslonlymode(Creator, "sslonly")
240                 , sslapi(Creator)
241         {
242                 allow_empty_last_param = false;
243                 syntax = "<channel|nick>";
244         }
245
246         CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE
247         {
248                 if (ServerInstance->IsChannel(parameters[0]))
249                         return HandleChannel(user, parameters[0]);
250                 else
251                         return HandleUser(user, parameters[0]);
252         }
253 };
254
255 class ModuleSSLInfo
256         : public Module
257         , public WebIRC::EventListener
258         , public Whois::EventListener
259         , public Who::EventListener
260 {
261  private:
262         CommandSSLInfo cmd;
263
264         bool MatchFP(ssl_cert* const cert, const std::string& fp) const
265         {
266                 return irc::spacesepstream(fp).Contains(cert->GetFingerprint());
267         }
268
269  public:
270         ModuleSSLInfo()
271                 : WebIRC::EventListener(this)
272                 , Whois::EventListener(this)
273                 , Who::EventListener(this)
274                 , cmd(this)
275         {
276         }
277
278         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
279         {
280                 ConfigTag* tag = ServerInstance->Config->ConfValue("sslinfo");
281                 cmd.operonlyfp = tag->getBool("operonly");
282         }
283
284         Version GetVersion() CXX11_OVERRIDE
285         {
286                 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);
287         }
288
289         void OnWhois(Whois::Context& whois) CXX11_OVERRIDE
290         {
291                 ssl_cert* cert = cmd.sslapi.GetCertificate(whois.GetTarget());
292                 if (cert)
293                 {
294                         whois.SendLine(RPL_WHOISSECURE, "is using a secure connection");
295                         if ((!cmd.operonlyfp || whois.IsSelfWhois() || whois.GetSource()->IsOper()) && !cert->fingerprint.empty())
296                                 whois.SendLine(RPL_WHOISCERTFP, InspIRCd::Format("has TLS (SSL) client certificate fingerprint %s", cert->fingerprint.c_str()));
297                 }
298         }
299
300         ModResult OnWhoLine(const Who::Request& request, LocalUser* source, User* user, Membership* memb, Numeric::Numeric& numeric) CXX11_OVERRIDE
301         {
302                 size_t flag_index;
303                 if (!request.GetFieldIndex('f', flag_index))
304                         return MOD_RES_PASSTHRU;
305
306                 ssl_cert* cert = cmd.sslapi.GetCertificate(user);
307                 if (cert)
308                         numeric.GetParams()[flag_index].push_back('s');
309
310                 return MOD_RES_PASSTHRU;
311         }
312
313         ModResult OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated) CXX11_OVERRIDE
314         {
315                 if ((command == "OPER") && (validated))
316                 {
317                         ServerConfig::OperIndex::const_iterator i = ServerInstance->Config->oper_blocks.find(parameters[0]);
318                         if (i != ServerInstance->Config->oper_blocks.end())
319                         {
320                                 OperInfo* ifo = i->second;
321                                 ssl_cert* cert = cmd.sslapi.GetCertificate(user);
322
323                                 if (ifo->oper_block->getBool("sslonly") && !cert)
324                                 {
325                                         user->WriteNumeric(ERR_NOOPERHOST, "Invalid oper credentials");
326                                         user->CommandFloodPenalty += 10000;
327                                         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());
328                                         return MOD_RES_DENY;
329                                 }
330
331                                 std::string fingerprint;
332                                 if (ifo->oper_block->readString("fingerprint", fingerprint) && (!cert || !MatchFP(cert, fingerprint)))
333                                 {
334                                         user->WriteNumeric(ERR_NOOPERHOST, "Invalid oper credentials");
335                                         user->CommandFloodPenalty += 10000;
336                                         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());
337                                         return MOD_RES_DENY;
338                                 }
339                         }
340                 }
341
342                 // Let core handle it for extra stuff
343                 return MOD_RES_PASSTHRU;
344         }
345
346         void OnPostConnect(User* user) CXX11_OVERRIDE
347         {
348                 LocalUser* const localuser = IS_LOCAL(user);
349                 if (!localuser)
350                         return;
351
352                 const SSLIOHook* const ssliohook = SSLIOHook::IsSSL(&localuser->eh);
353                 if (!ssliohook || cmd.sslapi.nosslext.get(localuser))
354                         return;
355
356                 ssl_cert* const cert = ssliohook->GetCertificate();
357
358                 std::string text = "*** You are connected to ";
359                 if (!ssliohook->GetServerName(text))
360                         text.append(ServerInstance->Config->GetServerName());
361                 text.append(" using TLS (SSL) cipher '");
362                 ssliohook->GetCiphersuite(text);
363                 text.push_back('\'');
364                 if (cert && !cert->GetFingerprint().empty())
365                         text.append(" and your TLS (SSL) client certificate fingerprint is ").append(cert->GetFingerprint());
366                 user->WriteNotice(text);
367
368                 if (!cert)
369                         return;
370
371                 // Find an auto-oper block for this user
372                 for (ServerConfig::OperIndex::const_iterator i = ServerInstance->Config->oper_blocks.begin(); i != ServerInstance->Config->oper_blocks.end(); ++i)
373                 {
374                         OperInfo* ifo = i->second;
375                         std::string fp = ifo->oper_block->getString("fingerprint");
376                         if (MatchFP(cert, fp) && ifo->oper_block->getBool("autologin"))
377                                 user->Oper(ifo);
378                 }
379         }
380
381         ModResult OnSetConnectClass(LocalUser* user, ConnectClass* myclass) CXX11_OVERRIDE
382         {
383                 ssl_cert* cert = cmd.sslapi.GetCertificate(user);
384                 const char* error = NULL;
385                 const std::string requiressl = myclass->config->getString("requiressl");
386                 if (stdalgo::string::equalsci(requiressl, "trusted"))
387                 {
388                         if (!cert || !cert->IsCAVerified())
389                                 error = "a trusted TLS (SSL) client certificate";
390                 }
391                 else if (myclass->config->getBool("requiressl"))
392                 {
393                         if (!cert)
394                                 error = "a TLS (SSL) connection";
395                 }
396
397                 if (error)
398                 {
399                         ServerInstance->Logs->Log("CONNECTCLASS", LOG_DEBUG, "The %s connect class is not suitable as it requires %s",
400                                 myclass->GetName().c_str(), error);
401                         return MOD_RES_DENY;
402                 }
403
404                 return MOD_RES_PASSTHRU;
405         }
406
407         void OnWebIRCAuth(LocalUser* user, const WebIRC::FlagMap* flags) CXX11_OVERRIDE
408         {
409                 // We are only interested in connection flags. If none have been
410                 // given then we have nothing to do.
411                 if (!flags)
412                         return;
413
414                 // We only care about the tls connection flag if the connection
415                 // between the gateway and the server is secure.
416                 if (!cmd.sslapi.GetCertificate(user))
417                         return;
418
419                 WebIRC::FlagMap::const_iterator iter = flags->find("secure");
420                 if (iter == flags->end())
421                 {
422                         // If this is not set then the connection between the client and
423                         // the gateway is not secure.
424                         cmd.sslapi.nosslext.set(user, 1);
425                         cmd.sslapi.sslext.unset(user);
426                         return;
427                 }
428
429                 // Create a fake ssl_cert for the user.
430                 ssl_cert* cert = new ssl_cert;
431                 cert->error = "WebIRC users can not specify valid certs yet";
432                 cert->invalid = true;
433                 cert->revoked = true;
434                 cert->trusted = false;
435                 cert->unknownsigner = true;
436                 cmd.sslapi.SetCertificate(user, cert);
437         }
438 };
439
440 MODULE_INIT(ModuleSSLInfo)