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