]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_sslinfo.cpp
d3514eac6acc4ccf1e254b517fbce2ad839e1578
[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                 free(container, unset_raw(container));
58         }
59
60         std::string serialize(SerializeFormat format, const Extensible* container, void* item) const CXX11_OVERRIDE
61         {
62                 return static_cast<ssl_cert*>(item)->GetMetaLine();
63         }
64
65         void unserialize(SerializeFormat format, Extensible* container, const std::string& value) CXX11_OVERRIDE
66         {
67                 ssl_cert* cert = new ssl_cert;
68                 set(container, cert);
69
70                 std::stringstream s(value);
71                 std::string v;
72                 getline(s,v,' ');
73
74                 cert->invalid = (v.find('v') != std::string::npos);
75                 cert->trusted = (v.find('T') != std::string::npos);
76                 cert->revoked = (v.find('R') != std::string::npos);
77                 cert->unknownsigner = (v.find('s') != std::string::npos);
78                 if (v.find('E') != std::string::npos)
79                 {
80                         getline(s,cert->error,'\n');
81                 }
82                 else
83                 {
84                         getline(s,cert->fingerprint,' ');
85                         getline(s,cert->dn,' ');
86                         getline(s,cert->issuer,'\n');
87                 }
88         }
89
90         void free(Extensible* container, void* item) CXX11_OVERRIDE
91         {
92                 ssl_cert* old = static_cast<ssl_cert*>(item);
93                 if (old && old->refcount_dec())
94                         delete old;
95         }
96 };
97
98 class UserCertificateAPIImpl : public UserCertificateAPIBase
99 {
100  public:
101         LocalIntExt nosslext;
102         SSLCertExt sslext;
103
104         UserCertificateAPIImpl(Module* mod)
105                 : UserCertificateAPIBase(mod)
106                 , nosslext("no_ssl_cert", ExtensionItem::EXT_USER, mod)
107                 , sslext(mod)
108         {
109         }
110
111         ssl_cert* GetCertificate(User* user) CXX11_OVERRIDE
112         {
113                 ssl_cert* cert = sslext.get(user);
114                 if (cert)
115                         return cert;
116
117                 LocalUser* luser = IS_LOCAL(user);
118                 if (!luser || nosslext.get(luser))
119                         return NULL;
120
121                 cert = SSLClientCert::GetCertificate(&luser->eh);
122                 if (!cert)
123                         return NULL;
124
125                 SetCertificate(user, cert);
126                 return cert;
127         }
128
129         void SetCertificate(User* user, ssl_cert* cert) CXX11_OVERRIDE
130         {
131                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Setting SSL certificate for %s: %s",
132                         user->GetFullHost().c_str(), cert->GetMetaLine().c_str());
133                 sslext.set(user, cert);
134         }
135 };
136
137 class CommandSSLInfo : public Command
138 {
139  public:
140         UserCertificateAPIImpl sslapi;
141
142         CommandSSLInfo(Module* Creator)
143                 : Command(Creator, "SSLINFO", 1)
144                 , sslapi(Creator)
145         {
146                 this->syntax = "<nick>";
147         }
148
149         CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE
150         {
151                 User* target = ServerInstance->FindNickOnly(parameters[0]);
152
153                 if ((!target) || (target->registered != REG_ALL))
154                 {
155                         user->WriteNumeric(Numerics::NoSuchNick(parameters[0]));
156                         return CMD_FAILURE;
157                 }
158                 bool operonlyfp = ServerInstance->Config->ConfValue("sslinfo")->getBool("operonly");
159                 if (operonlyfp && !user->IsOper() && target != user)
160                 {
161                         user->WriteNotice("*** You cannot view SSL certificate information for other users");
162                         return CMD_FAILURE;
163                 }
164                 ssl_cert* cert = sslapi.GetCertificate(target);
165                 if (!cert)
166                 {
167                         user->WriteNotice("*** No SSL certificate for this user");
168                 }
169                 else if (cert->GetError().length())
170                 {
171                         user->WriteNotice("*** No SSL certificate information for this user (" + cert->GetError() + ").");
172                 }
173                 else
174                 {
175                         user->WriteNotice("*** Distinguished Name: " + cert->GetDN());
176                         user->WriteNotice("*** Issuer:             " + cert->GetIssuer());
177                         user->WriteNotice("*** Key Fingerprint:    " + cert->GetFingerprint());
178                 }
179                 return CMD_SUCCESS;
180         }
181 };
182
183 class ModuleSSLInfo
184         : public Module
185         , public WebIRC::EventListener
186         , public Whois::EventListener
187 {
188  private:
189         CommandSSLInfo cmd;
190
191  public:
192         ModuleSSLInfo()
193                 : WebIRC::EventListener(this)
194                 , Whois::EventListener(this)
195                 , cmd(this)
196         {
197         }
198
199         Version GetVersion() CXX11_OVERRIDE
200         {
201                 return Version("SSL Certificate Utilities", VF_VENDOR);
202         }
203
204         void OnWhois(Whois::Context& whois) CXX11_OVERRIDE
205         {
206                 ssl_cert* cert = cmd.sslapi.GetCertificate(whois.GetTarget());
207                 if (cert)
208                 {
209                         whois.SendLine(RPL_WHOISSECURE, "is using a secure connection");
210                         bool operonlyfp = ServerInstance->Config->ConfValue("sslinfo")->getBool("operonly");
211                         if ((!operonlyfp || whois.IsSelfWhois() || whois.GetSource()->IsOper()) && !cert->fingerprint.empty())
212                                 whois.SendLine(RPL_WHOISCERTFP, InspIRCd::Format("has client certificate fingerprint %s", cert->fingerprint.c_str()));
213                 }
214         }
215
216         ModResult OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated) CXX11_OVERRIDE
217         {
218                 if ((command == "OPER") && (validated))
219                 {
220                         ServerConfig::OperIndex::const_iterator i = ServerInstance->Config->oper_blocks.find(parameters[0]);
221                         if (i != ServerInstance->Config->oper_blocks.end())
222                         {
223                                 OperInfo* ifo = i->second;
224                                 ssl_cert* cert = cmd.sslapi.GetCertificate(user);
225
226                                 if (ifo->oper_block->getBool("sslonly") && !cert)
227                                 {
228                                         user->WriteNumeric(ERR_NOOPERHOST, "This oper login requires an SSL connection.");
229                                         user->CommandFloodPenalty += 10000;
230                                         return MOD_RES_DENY;
231                                 }
232
233                                 std::string fingerprint;
234                                 if (ifo->oper_block->readString("fingerprint", fingerprint) && (!cert || cert->GetFingerprint() != fingerprint))
235                                 {
236                                         user->WriteNumeric(ERR_NOOPERHOST, "This oper login requires a matching SSL certificate fingerprint.");
237                                         user->CommandFloodPenalty += 10000;
238                                         return MOD_RES_DENY;
239                                 }
240                         }
241                 }
242
243                 // Let core handle it for extra stuff
244                 return MOD_RES_PASSTHRU;
245         }
246
247         void OnPostConnect(User* user) CXX11_OVERRIDE
248         {
249                 LocalUser* const localuser = IS_LOCAL(user);
250                 if (!localuser)
251                         return;
252
253                 const SSLIOHook* const ssliohook = SSLIOHook::IsSSL(&localuser->eh);
254                 if (!ssliohook || cmd.sslapi.nosslext.get(localuser))
255                         return;
256
257                 ssl_cert* const cert = ssliohook->GetCertificate();
258
259                 {
260                         std::string text = "*** You are connected to ";
261                         if (!ssliohook->GetServerName(text))
262                                 text.append(ServerInstance->Config->ServerName);
263                         text.append(" using SSL cipher '");
264                         ssliohook->GetCiphersuite(text);
265                         text.push_back('\'');
266                         if ((cert) && (!cert->GetFingerprint().empty()))
267                                 text.append(" and your SSL certificate fingerprint is ").append(cert->GetFingerprint());
268                         user->WriteNotice(text);
269                 }
270
271                 if (!cert)
272                         return;
273                 // find an auto-oper block for this user
274                 for (ServerConfig::OperIndex::const_iterator i = ServerInstance->Config->oper_blocks.begin(); i != ServerInstance->Config->oper_blocks.end(); ++i)
275                 {
276                         OperInfo* ifo = i->second;
277                         std::string fp = ifo->oper_block->getString("fingerprint");
278                         if (fp == cert->fingerprint && ifo->oper_block->getBool("autologin"))
279                                 user->Oper(ifo);
280                 }
281         }
282
283         ModResult OnSetConnectClass(LocalUser* user, ConnectClass* myclass) CXX11_OVERRIDE
284         {
285                 ssl_cert* cert = SSLClientCert::GetCertificate(&user->eh);
286                 bool ok = true;
287                 if (myclass->config->getString("requiressl") == "trusted")
288                 {
289                         ok = (cert && cert->IsCAVerified());
290                         ServerInstance->Logs->Log("CONNECTCLASS", LOG_DEBUG, "Class requires a trusted SSL cert. Client %s one.", (ok ? "has" : "does not have"));
291                 }
292                 else if (myclass->config->getBool("requiressl"))
293                 {
294                         ok = (cert != NULL);
295                         ServerInstance->Logs->Log("CONNECTCLASS", LOG_DEBUG, "Class requires any SSL cert. Client %s one.", (ok ? "has" : "does not have"));
296                 }
297
298                 if (!ok)
299                         return MOD_RES_DENY;
300                 return MOD_RES_PASSTHRU;
301         }
302
303         void OnWebIRCAuth(LocalUser* user, const WebIRC::FlagMap* flags) CXX11_OVERRIDE
304         {
305                 // We are only interested in connection flags. If none have been
306                 // given then we have nothing to do.
307                 if (!flags)
308                         return;
309
310                 // We only care about the tls connection flag if the connection
311                 // between the gateway and the server is secure.
312                 if (!cmd.sslapi.GetCertificate(user))
313                         return;
314
315                 WebIRC::FlagMap::const_iterator iter = flags->find("secure");
316                 if (iter == flags->end())
317                 {
318                         // If this is not set then the connection between the client and
319                         // the gateway is not secure.
320                         cmd.sslapi.nosslext.set(user, 1);
321                         cmd.sslapi.sslext.unset(user);
322                         return;
323                 }
324
325                 // Create a fake ssl_cert for the user.
326                 ssl_cert* cert = new ssl_cert;
327                 cert->error = "WebIRC users can not specify valid certs yet";
328                 cert->invalid = true;
329                 cert->revoked = true;
330                 cert->trusted = false;
331                 cert->unknownsigner = true;
332                 cmd.sslapi.SetCertificate(user, cert);
333         }
334 };
335
336 MODULE_INIT(ModuleSSLInfo)