]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_sslinfo.cpp
Rename OnClientProtocolProcessTag to OnProcessTag.
[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
25 enum
26 {
27         // From oftc-hybrid.
28         RPL_WHOISCERTFP = 276,
29
30         // From UnrealIRCd.
31         RPL_WHOISSECURE = 671
32 };
33
34 class SSLCertExt : public ExtensionItem
35 {
36  public:
37         SSLCertExt(Module* parent)
38                 : ExtensionItem("ssl_cert", ExtensionItem::EXT_USER, parent)
39         {
40         }
41
42         ssl_cert* get(const Extensible* item) const
43         {
44                 return static_cast<ssl_cert*>(get_raw(item));
45         }
46
47         void set(Extensible* item, ssl_cert* value)
48         {
49                 value->refcount_inc();
50                 ssl_cert* old = static_cast<ssl_cert*>(set_raw(item, value));
51                 if (old && old->refcount_dec())
52                         delete old;
53         }
54
55         void unset(Extensible* container)
56         {
57                 void* old = unset_raw(container);
58                 delete static_cast<std::string*>(old);
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 {
189  private:
190         CommandSSLInfo cmd;
191
192  public:
193         ModuleSSLInfo()
194                 : WebIRC::EventListener(this)
195                 , Whois::EventListener(this)
196                 , cmd(this)
197         {
198         }
199
200         Version GetVersion() CXX11_OVERRIDE
201         {
202                 return Version("SSL Certificate Utilities", VF_VENDOR);
203         }
204
205         void OnWhois(Whois::Context& whois) CXX11_OVERRIDE
206         {
207                 ssl_cert* cert = cmd.sslapi.GetCertificate(whois.GetTarget());
208                 if (cert)
209                 {
210                         whois.SendLine(RPL_WHOISSECURE, "is using a secure connection");
211                         bool operonlyfp = ServerInstance->Config->ConfValue("sslinfo")->getBool("operonly");
212                         if ((!operonlyfp || whois.IsSelfWhois() || whois.GetSource()->IsOper()) && !cert->fingerprint.empty())
213                                 whois.SendLine(RPL_WHOISCERTFP, InspIRCd::Format("has client certificate fingerprint %s", cert->fingerprint.c_str()));
214                 }
215         }
216
217         ModResult OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated) CXX11_OVERRIDE
218         {
219                 if ((command == "OPER") && (validated))
220                 {
221                         ServerConfig::OperIndex::const_iterator i = ServerInstance->Config->oper_blocks.find(parameters[0]);
222                         if (i != ServerInstance->Config->oper_blocks.end())
223                         {
224                                 OperInfo* ifo = i->second;
225                                 ssl_cert* cert = cmd.sslapi.GetCertificate(user);
226
227                                 if (ifo->oper_block->getBool("sslonly") && !cert)
228                                 {
229                                         user->WriteNumeric(ERR_NOOPERHOST, "This oper login requires an SSL connection.");
230                                         user->CommandFloodPenalty += 10000;
231                                         return MOD_RES_DENY;
232                                 }
233
234                                 std::string fingerprint;
235                                 if (ifo->oper_block->readString("fingerprint", fingerprint) && (!cert || cert->GetFingerprint() != fingerprint))
236                                 {
237                                         user->WriteNumeric(ERR_NOOPERHOST, "This oper login requires a matching SSL certificate fingerprint.");
238                                         user->CommandFloodPenalty += 10000;
239                                         return MOD_RES_DENY;
240                                 }
241                         }
242                 }
243
244                 // Let core handle it for extra stuff
245                 return MOD_RES_PASSTHRU;
246         }
247
248         void OnPostConnect(User* user) CXX11_OVERRIDE
249         {
250                 LocalUser* const localuser = IS_LOCAL(user);
251                 if (!localuser)
252                         return;
253
254                 const SSLIOHook* const ssliohook = SSLIOHook::IsSSL(&localuser->eh);
255                 if (!ssliohook)
256                         return;
257
258                 ssl_cert* const cert = ssliohook->GetCertificate();
259
260                 {
261                         std::string text = "*** You are connected to ";
262                         if (!ssliohook->GetServerName(text))
263                                 text.append(ServerInstance->Config->ServerName);
264                         text.append(" using SSL cipher '");
265                         ssliohook->GetCiphersuite(text);
266                         text.push_back('\'');
267                         if ((cert) && (!cert->GetFingerprint().empty()))
268                                 text.append(" and your SSL certificate fingerprint is ").append(cert->GetFingerprint());
269                         user->WriteNotice(text);
270                 }
271
272                 if (!cert)
273                         return;
274                 // find an auto-oper block for this user
275                 for (ServerConfig::OperIndex::const_iterator i = ServerInstance->Config->oper_blocks.begin(); i != ServerInstance->Config->oper_blocks.end(); ++i)
276                 {
277                         OperInfo* ifo = i->second;
278                         std::string fp = ifo->oper_block->getString("fingerprint");
279                         if (fp == cert->fingerprint && ifo->oper_block->getBool("autologin"))
280                                 user->Oper(ifo);
281                 }
282         }
283
284         ModResult OnSetConnectClass(LocalUser* user, ConnectClass* myclass) CXX11_OVERRIDE
285         {
286                 ssl_cert* cert = SSLClientCert::GetCertificate(&user->eh);
287                 bool ok = true;
288                 if (myclass->config->getString("requiressl") == "trusted")
289                 {
290                         ok = (cert && cert->IsCAVerified());
291                         ServerInstance->Logs->Log("CONNECTCLASS", LOG_DEBUG, "Class requires a trusted SSL cert. Client %s one.", (ok ? "has" : "does not have"));
292                 }
293                 else if (myclass->config->getBool("requiressl"))
294                 {
295                         ok = (cert != NULL);
296                         ServerInstance->Logs->Log("CONNECTCLASS", LOG_DEBUG, "Class requires any SSL cert. Client %s one.", (ok ? "has" : "does not have"));
297                 }
298
299                 if (!ok)
300                         return MOD_RES_DENY;
301                 return MOD_RES_PASSTHRU;
302         }
303
304         void OnWebIRCAuth(LocalUser* user, const WebIRC::FlagMap* flags) CXX11_OVERRIDE
305         {
306                 // We are only interested in connection flags. If none have been
307                 // given then we have nothing to do.
308                 if (!flags)
309                         return;
310
311                 // We only care about the tls connection flag if the connection
312                 // between the gateway and the server is secure.
313                 if (!cmd.sslapi.GetCertificate(user))
314                         return;
315
316                 WebIRC::FlagMap::const_iterator iter = flags->find("secure");
317                 if (iter == flags->end())
318                 {
319                         // If this is not set then the connection between the client and
320                         // the gateway is not secure.
321                         cmd.sslapi.nosslext.set(user, 1);
322                         cmd.sslapi.sslext.unset(user);
323                         return;
324                 }
325
326                 // Create a fake ssl_cert for the user.
327                 ssl_cert* cert = new ssl_cert;
328                 cert->error = "WebIRC users can not specify valid certs yet";
329                 cert->invalid = true;
330                 cert->revoked = true;
331                 cert->trusted = false;
332                 cert->unknownsigner = true;
333                 cmd.sslapi.SetCertificate(user, cert);
334         }
335 };
336
337 MODULE_INIT(ModuleSSLInfo)