]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_sslinfo.cpp
Add SSL flag to WHO response
[user/henk/code/inspircd.git] / src / modules / m_sslinfo.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
5  *
6  * This file is part of InspIRCd.  InspIRCd is free software: you can
7  * redistribute it and/or modify it under the terms of the GNU General Public
8  * License as published by the Free Software Foundation, version 2.
9  *
10  * This program is distributed in the hope that it will be useful, but WITHOUT
11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
13  * details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18
19
20 #include "inspircd.h"
21 #include "modules/ssl.h"
22 #include "modules/webirc.h"
23 #include "modules/whois.h"
24 #include "modules/who.h"
25
26 enum
27 {
28         // From oftc-hybrid.
29         RPL_WHOISCERTFP = 276,
30
31         // From UnrealIRCd.
32         RPL_WHOISSECURE = 671
33 };
34
35 class SSLCertExt : public ExtensionItem
36 {
37  public:
38         SSLCertExt(Module* parent)
39                 : ExtensionItem("ssl_cert", ExtensionItem::EXT_USER, parent)
40         {
41         }
42
43         ssl_cert* get(const Extensible* item) const
44         {
45                 return static_cast<ssl_cert*>(get_raw(item));
46         }
47
48         void set(Extensible* item, ssl_cert* value)
49         {
50                 value->refcount_inc();
51                 ssl_cert* old = static_cast<ssl_cert*>(set_raw(item, value));
52                 if (old && old->refcount_dec())
53                         delete old;
54         }
55
56         void unset(Extensible* container)
57         {
58                 free(container, unset_raw(container));
59         }
60
61         std::string serialize(SerializeFormat format, const Extensible* container, void* item) const CXX11_OVERRIDE
62         {
63                 return static_cast<ssl_cert*>(item)->GetMetaLine();
64         }
65
66         void unserialize(SerializeFormat format, Extensible* container, const std::string& value) CXX11_OVERRIDE
67         {
68                 ssl_cert* cert = new ssl_cert;
69                 set(container, cert);
70
71                 std::stringstream s(value);
72                 std::string v;
73                 getline(s,v,' ');
74
75                 cert->invalid = (v.find('v') != std::string::npos);
76                 cert->trusted = (v.find('T') != std::string::npos);
77                 cert->revoked = (v.find('R') != std::string::npos);
78                 cert->unknownsigner = (v.find('s') != std::string::npos);
79                 if (v.find('E') != std::string::npos)
80                 {
81                         getline(s,cert->error,'\n');
82                 }
83                 else
84                 {
85                         getline(s,cert->fingerprint,' ');
86                         getline(s,cert->dn,' ');
87                         getline(s,cert->issuer,'\n');
88                 }
89         }
90
91         void free(Extensible* container, void* item) CXX11_OVERRIDE
92         {
93                 ssl_cert* old = static_cast<ssl_cert*>(item);
94                 if (old && old->refcount_dec())
95                         delete old;
96         }
97 };
98
99 class UserCertificateAPIImpl : public UserCertificateAPIBase
100 {
101  public:
102         LocalIntExt nosslext;
103         SSLCertExt sslext;
104
105         UserCertificateAPIImpl(Module* mod)
106                 : UserCertificateAPIBase(mod)
107                 , nosslext("no_ssl_cert", ExtensionItem::EXT_USER, mod)
108                 , sslext(mod)
109         {
110         }
111
112         ssl_cert* GetCertificate(User* user) CXX11_OVERRIDE
113         {
114                 ssl_cert* cert = sslext.get(user);
115                 if (cert)
116                         return cert;
117
118                 LocalUser* luser = IS_LOCAL(user);
119                 if (!luser || nosslext.get(luser))
120                         return NULL;
121
122                 cert = SSLClientCert::GetCertificate(&luser->eh);
123                 if (!cert)
124                         return NULL;
125
126                 SetCertificate(user, cert);
127                 return cert;
128         }
129
130         void SetCertificate(User* user, ssl_cert* cert) CXX11_OVERRIDE
131         {
132                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Setting SSL certificate for %s: %s",
133                         user->GetFullHost().c_str(), cert->GetMetaLine().c_str());
134                 sslext.set(user, cert);
135         }
136 };
137
138 class CommandSSLInfo : public Command
139 {
140  public:
141         UserCertificateAPIImpl sslapi;
142
143         CommandSSLInfo(Module* Creator)
144                 : Command(Creator, "SSLINFO", 1)
145                 , sslapi(Creator)
146         {
147                 this->syntax = "<nick>";
148         }
149
150         CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE
151         {
152                 User* target = ServerInstance->FindNickOnly(parameters[0]);
153
154                 if ((!target) || (target->registered != REG_ALL))
155                 {
156                         user->WriteNumeric(Numerics::NoSuchNick(parameters[0]));
157                         return CMD_FAILURE;
158                 }
159                 bool operonlyfp = ServerInstance->Config->ConfValue("sslinfo")->getBool("operonly");
160                 if (operonlyfp && !user->IsOper() && target != user)
161                 {
162                         user->WriteNotice("*** You cannot view SSL certificate information for other users");
163                         return CMD_FAILURE;
164                 }
165                 ssl_cert* cert = sslapi.GetCertificate(target);
166                 if (!cert)
167                 {
168                         user->WriteNotice("*** No SSL certificate for this user");
169                 }
170                 else if (cert->GetError().length())
171                 {
172                         user->WriteNotice("*** No SSL certificate information for this user (" + cert->GetError() + ").");
173                 }
174                 else
175                 {
176                         user->WriteNotice("*** Distinguished Name: " + cert->GetDN());
177                         user->WriteNotice("*** Issuer:             " + cert->GetIssuer());
178                         user->WriteNotice("*** Key Fingerprint:    " + cert->GetFingerprint());
179                 }
180                 return CMD_SUCCESS;
181         }
182 };
183
184 class ModuleSSLInfo
185         : public Module
186         , public WebIRC::EventListener
187         , public Whois::EventListener
188         , public Who::EventListener
189 {
190  private:
191         CommandSSLInfo cmd;
192
193         bool MatchFP(ssl_cert* const cert, const std::string& fp) const
194         {
195                 return irc::spacesepstream(fp).Contains(cert->GetFingerprint());
196         }
197
198  public:
199         ModuleSSLInfo()
200                 : WebIRC::EventListener(this)
201                 , Whois::EventListener(this)
202                 , Who::EventListener(this)
203                 , cmd(this)
204         {
205         }
206
207         Version GetVersion() CXX11_OVERRIDE
208         {
209                 return Version("SSL Certificate Utilities", VF_VENDOR);
210         }
211
212         void OnWhois(Whois::Context& whois) CXX11_OVERRIDE
213         {
214                 ssl_cert* cert = cmd.sslapi.GetCertificate(whois.GetTarget());
215                 if (cert)
216                 {
217                         whois.SendLine(RPL_WHOISSECURE, "is using a secure connection");
218                         bool operonlyfp = ServerInstance->Config->ConfValue("sslinfo")->getBool("operonly");
219                         if ((!operonlyfp || whois.IsSelfWhois() || whois.GetSource()->IsOper()) && !cert->fingerprint.empty())
220                                 whois.SendLine(RPL_WHOISCERTFP, InspIRCd::Format("has client certificate fingerprint %s", cert->fingerprint.c_str()));
221                 }
222         }
223
224         ModResult OnWhoLine(const Who::Request& request, LocalUser* source, User* user, Membership* memb, Numeric::Numeric& numeric) CXX11_OVERRIDE
225         {
226                 size_t flag_index;
227                 if (!request.GetFieldIndex('f', flag_index))
228                         return MOD_RES_PASSTHRU;
229
230                 ssl_cert* cert = cmd.sslapi.GetCertificate(user);
231                 if (cert)
232                         numeric.GetParams()[flag_index].push_back('s');
233
234                 return MOD_RES_PASSTHRU;
235         }
236
237         ModResult OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated) CXX11_OVERRIDE
238         {
239                 if ((command == "OPER") && (validated))
240                 {
241                         ServerConfig::OperIndex::const_iterator i = ServerInstance->Config->oper_blocks.find(parameters[0]);
242                         if (i != ServerInstance->Config->oper_blocks.end())
243                         {
244                                 OperInfo* ifo = i->second;
245                                 ssl_cert* cert = cmd.sslapi.GetCertificate(user);
246
247                                 if (ifo->oper_block->getBool("sslonly") && !cert)
248                                 {
249                                         user->WriteNumeric(ERR_NOOPERHOST, "This oper login requires an SSL connection.");
250                                         user->CommandFloodPenalty += 10000;
251                                         return MOD_RES_DENY;
252                                 }
253
254                                 std::string fingerprint;
255                                 if (ifo->oper_block->readString("fingerprint", fingerprint) && (!cert || !MatchFP(cert, fingerprint)))
256                                 {
257                                         user->WriteNumeric(ERR_NOOPERHOST, "This oper login requires a matching SSL certificate fingerprint.");
258                                         user->CommandFloodPenalty += 10000;
259                                         return MOD_RES_DENY;
260                                 }
261                         }
262                 }
263
264                 // Let core handle it for extra stuff
265                 return MOD_RES_PASSTHRU;
266         }
267
268         void OnPostConnect(User* user) CXX11_OVERRIDE
269         {
270                 LocalUser* const localuser = IS_LOCAL(user);
271                 if (!localuser)
272                         return;
273
274                 const SSLIOHook* const ssliohook = SSLIOHook::IsSSL(&localuser->eh);
275                 if (!ssliohook || cmd.sslapi.nosslext.get(localuser))
276                         return;
277
278                 ssl_cert* const cert = ssliohook->GetCertificate();
279
280                 {
281                         std::string text = "*** You are connected to ";
282                         if (!ssliohook->GetServerName(text))
283                                 text.append(ServerInstance->Config->ServerName);
284                         text.append(" using SSL cipher '");
285                         ssliohook->GetCiphersuite(text);
286                         text.push_back('\'');
287                         if ((cert) && (!cert->GetFingerprint().empty()))
288                                 text.append(" and your SSL certificate fingerprint is ").append(cert->GetFingerprint());
289                         user->WriteNotice(text);
290                 }
291
292                 if (!cert)
293                         return;
294                 // find an auto-oper block for this user
295                 for (ServerConfig::OperIndex::const_iterator i = ServerInstance->Config->oper_blocks.begin(); i != ServerInstance->Config->oper_blocks.end(); ++i)
296                 {
297                         OperInfo* ifo = i->second;
298                         std::string fp = ifo->oper_block->getString("fingerprint");
299                         if (MatchFP(cert, fp) && ifo->oper_block->getBool("autologin"))
300                                 user->Oper(ifo);
301                 }
302         }
303
304         ModResult OnSetConnectClass(LocalUser* user, ConnectClass* myclass) CXX11_OVERRIDE
305         {
306                 ssl_cert* cert = SSLClientCert::GetCertificate(&user->eh);
307                 bool ok = true;
308                 if (myclass->config->getString("requiressl") == "trusted")
309                 {
310                         ok = (cert && cert->IsCAVerified());
311                         ServerInstance->Logs->Log("CONNECTCLASS", LOG_DEBUG, "Class requires a trusted SSL cert. Client %s one.", (ok ? "has" : "does not have"));
312                 }
313                 else if (myclass->config->getBool("requiressl"))
314                 {
315                         ok = (cert != NULL);
316                         ServerInstance->Logs->Log("CONNECTCLASS", LOG_DEBUG, "Class requires any SSL cert. Client %s one.", (ok ? "has" : "does not have"));
317                 }
318
319                 if (!ok)
320                         return MOD_RES_DENY;
321                 return MOD_RES_PASSTHRU;
322         }
323
324         void OnWebIRCAuth(LocalUser* user, const WebIRC::FlagMap* flags) CXX11_OVERRIDE
325         {
326                 // We are only interested in connection flags. If none have been
327                 // given then we have nothing to do.
328                 if (!flags)
329                         return;
330
331                 // We only care about the tls connection flag if the connection
332                 // between the gateway and the server is secure.
333                 if (!cmd.sslapi.GetCertificate(user))
334                         return;
335
336                 WebIRC::FlagMap::const_iterator iter = flags->find("secure");
337                 if (iter == flags->end())
338                 {
339                         // If this is not set then the connection between the client and
340                         // the gateway is not secure.
341                         cmd.sslapi.nosslext.set(user, 1);
342                         cmd.sslapi.sslext.unset(user);
343                         return;
344                 }
345
346                 // Create a fake ssl_cert for the user.
347                 ssl_cert* cert = new ssl_cert;
348                 cert->error = "WebIRC users can not specify valid certs yet";
349                 cert->invalid = true;
350                 cert->revoked = true;
351                 cert->trusted = false;
352                 cert->unknownsigner = true;
353                 cmd.sslapi.SetCertificate(user, cert);
354         }
355 };
356
357 MODULE_INIT(ModuleSSLInfo)