]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/extra/m_ssl_gnutls.cpp
core_hostname_lookup: find answer record of the correct type instead of assuming...
[user/henk/code/inspircd.git] / src / modules / extra / m_ssl_gnutls.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
5  *   Copyright (C) 2008 John Brooks <john.brooks@dereferenced.net>
6  *   Copyright (C) 2006-2008 Craig Edwards <craigedwards@brainbox.cc>
7  *   Copyright (C) 2007 Dennis Friis <peavey@inspircd.org>
8  *   Copyright (C) 2006 Oliver Lupton <oliverlupton@gmail.com>
9  *
10  * This file is part of InspIRCd.  InspIRCd is free software: you can
11  * redistribute it and/or modify it under the terms of the GNU General Public
12  * License as published by the Free Software Foundation, version 2.
13  *
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
17  * details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  */
22
23
24 #include "inspircd.h"
25 #include "modules/ssl.h"
26 #include <memory>
27
28 // Fix warnings about the use of commas at end of enumerator lists on C++03.
29 #if defined __clang__
30 # pragma clang diagnostic ignored "-Wc++11-extensions"
31 #elif defined __GNUC__
32 # if __GNUC__ < 6
33 #  pragma GCC diagnostic ignored "-pedantic"
34 # else
35 #  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
36 # endif
37 #endif
38
39 #include <gnutls/gnutls.h>
40 #include <gnutls/x509.h>
41
42 #ifndef GNUTLS_VERSION_NUMBER
43 #define GNUTLS_VERSION_NUMBER LIBGNUTLS_VERSION_NUMBER
44 #define GNUTLS_VERSION LIBGNUTLS_VERSION
45 #endif
46
47 // Check if the GnuTLS library is at least version major.minor.patch
48 #define INSPIRCD_GNUTLS_HAS_VERSION(major, minor, patch) (GNUTLS_VERSION_NUMBER >= ((major << 16) | (minor << 8) | patch))
49
50 #if INSPIRCD_GNUTLS_HAS_VERSION(2, 9, 8)
51 #define GNUTLS_HAS_MAC_GET_ID
52 #include <gnutls/crypto.h>
53 #endif
54
55 #if INSPIRCD_GNUTLS_HAS_VERSION(2, 12, 0)
56 # define GNUTLS_HAS_RND
57 #else
58 # include <gcrypt.h>
59 #endif
60
61 #ifdef _WIN32
62 # pragma comment(lib, "libgnutls-30.lib")
63 #endif
64
65 /* $CompileFlags: pkgconfincludes("gnutls","/gnutls/gnutls.h","") eval("print `libgcrypt-config --cflags | tr -d \r` if `pkg-config --modversion gnutls 2>/dev/null | tr -d \r` lt '2.12'") */
66 /* $LinkerFlags: rpath("pkg-config --libs gnutls") pkgconflibs("gnutls","/libgnutls.so","-lgnutls") eval("print `libgcrypt-config --libs | tr -d \r` if `pkg-config --modversion gnutls 2>/dev/null | tr -d \r` lt '2.12'") */
67
68 // These don't exist in older GnuTLS versions
69 #if INSPIRCD_GNUTLS_HAS_VERSION(2, 1, 7)
70 #define GNUTLS_NEW_PRIO_API
71 #endif
72
73 #if (!INSPIRCD_GNUTLS_HAS_VERSION(2, 0, 0))
74 typedef gnutls_certificate_credentials_t gnutls_certificate_credentials;
75 typedef gnutls_dh_params_t gnutls_dh_params;
76 #endif
77
78 enum issl_status { ISSL_NONE, ISSL_HANDSHAKING, ISSL_HANDSHAKEN };
79
80 #if INSPIRCD_GNUTLS_HAS_VERSION(2, 12, 0)
81 #define INSPIRCD_GNUTLS_HAS_VECTOR_PUSH
82 #define GNUTLS_NEW_CERT_CALLBACK_API
83 typedef gnutls_retr2_st cert_cb_last_param_type;
84 #else
85 typedef gnutls_retr_st cert_cb_last_param_type;
86 #endif
87
88 #if INSPIRCD_GNUTLS_HAS_VERSION(3, 3, 5)
89 #define INSPIRCD_GNUTLS_HAS_RECV_PACKET
90 #endif
91
92 #if INSPIRCD_GNUTLS_HAS_VERSION(2, 99, 0)
93 // The second parameter of gnutls_init() has changed in 2.99.0 from gnutls_connection_end_t to unsigned int
94 // (it became a general flags parameter) and the enum has been deprecated and generates a warning on use.
95 typedef unsigned int inspircd_gnutls_session_init_flags_t;
96 #else
97 typedef gnutls_connection_end_t inspircd_gnutls_session_init_flags_t;
98 #endif
99
100 #if INSPIRCD_GNUTLS_HAS_VERSION(3, 1, 9)
101 #define INSPIRCD_GNUTLS_HAS_CORK
102 #endif
103
104 static Module* thismod;
105
106 class RandGen : public HandlerBase2<void, char*, size_t>
107 {
108  public:
109         void Call(char* buffer, size_t len)
110         {
111 #ifdef GNUTLS_HAS_RND
112                 gnutls_rnd(GNUTLS_RND_RANDOM, buffer, len);
113 #else
114                 gcry_randomize(buffer, len, GCRY_STRONG_RANDOM);
115 #endif
116         }
117 };
118
119 namespace GnuTLS
120 {
121         class Init
122         {
123          public:
124                 Init() { gnutls_global_init(); }
125                 ~Init() { gnutls_global_deinit(); }
126         };
127
128         class Exception : public ModuleException
129         {
130          public:
131                 Exception(const std::string& reason)
132                         : ModuleException(reason) { }
133         };
134
135         void ThrowOnError(int errcode, const char* msg)
136         {
137                 if (errcode < 0)
138                 {
139                         std::string reason = msg;
140                         reason.append(" :").append(gnutls_strerror(errcode));
141                         throw Exception(reason);
142                 }
143         }
144
145         /** Used to create a gnutls_datum_t* from a std::string
146          */
147         class Datum
148         {
149                 gnutls_datum_t datum;
150
151          public:
152                 Datum(const std::string& dat)
153                 {
154                         datum.data = (unsigned char*)dat.data();
155                         datum.size = static_cast<unsigned int>(dat.length());
156                 }
157
158                 const gnutls_datum_t* get() const { return &datum; }
159         };
160
161         class Hash
162         {
163                 gnutls_digest_algorithm_t hash;
164
165          public:
166                 // Nothing to deallocate, constructor may throw freely
167                 Hash(const std::string& hashname)
168                 {
169                         // As older versions of gnutls can't do this, let's disable it where needed.
170 #ifdef GNUTLS_HAS_MAC_GET_ID
171                         // As gnutls_digest_algorithm_t and gnutls_mac_algorithm_t are mapped 1:1, we can do this
172                         // There is no gnutls_dig_get_id() at the moment, but it may come later
173                         hash = (gnutls_digest_algorithm_t)gnutls_mac_get_id(hashname.c_str());
174                         if (hash == GNUTLS_DIG_UNKNOWN)
175                                 throw Exception("Unknown hash type " + hashname);
176
177                         // Check if the user is giving us something that is a valid MAC but not digest
178                         gnutls_hash_hd_t is_digest;
179                         if (gnutls_hash_init(&is_digest, hash) < 0)
180                                 throw Exception("Unknown hash type " + hashname);
181                         gnutls_hash_deinit(is_digest, NULL);
182 #else
183                         if (hashname == "md5")
184                                 hash = GNUTLS_DIG_MD5;
185                         else if (hashname == "sha1")
186                                 hash = GNUTLS_DIG_SHA1;
187 #ifdef INSPIRCD_GNUTLS_ENABLE_SHA256_FINGERPRINT
188                         else if (hashname == "sha256")
189                                 hash = GNUTLS_DIG_SHA256;
190 #endif
191                         else
192                                 throw Exception("Unknown hash type " + hashname);
193 #endif
194                 }
195
196                 gnutls_digest_algorithm_t get() const { return hash; }
197         };
198
199         class DHParams
200         {
201                 gnutls_dh_params_t dh_params;
202
203                 DHParams()
204                 {
205                         ThrowOnError(gnutls_dh_params_init(&dh_params), "gnutls_dh_params_init() failed");
206                 }
207
208          public:
209                 /** Import */
210                 static std::auto_ptr<DHParams> Import(const std::string& dhstr)
211                 {
212                         std::auto_ptr<DHParams> dh(new DHParams);
213                         int ret = gnutls_dh_params_import_pkcs3(dh->dh_params, Datum(dhstr).get(), GNUTLS_X509_FMT_PEM);
214                         ThrowOnError(ret, "Unable to import DH params");
215                         return dh;
216                 }
217
218                 ~DHParams()
219                 {
220                         gnutls_dh_params_deinit(dh_params);
221                 }
222
223                 const gnutls_dh_params_t& get() const { return dh_params; }
224         };
225
226         class X509Key
227         {
228                 /** Ensure that the key is deinited in case the constructor of X509Key throws
229                  */
230                 class RAIIKey
231                 {
232                  public:
233                         gnutls_x509_privkey_t key;
234
235                         RAIIKey()
236                         {
237                                 ThrowOnError(gnutls_x509_privkey_init(&key), "gnutls_x509_privkey_init() failed");
238                         }
239
240                         ~RAIIKey()
241                         {
242                                 gnutls_x509_privkey_deinit(key);
243                         }
244                 } key;
245
246          public:
247                 /** Import */
248                 X509Key(const std::string& keystr)
249                 {
250                         int ret = gnutls_x509_privkey_import(key.key, Datum(keystr).get(), GNUTLS_X509_FMT_PEM);
251                         ThrowOnError(ret, "Unable to import private key");
252                 }
253
254                 gnutls_x509_privkey_t& get() { return key.key; }
255         };
256
257         class X509CertList
258         {
259                 std::vector<gnutls_x509_crt_t> certs;
260
261          public:
262                 /** Import */
263                 X509CertList(const std::string& certstr)
264                 {
265                         unsigned int certcount = 3;
266                         certs.resize(certcount);
267                         Datum datum(certstr);
268
269                         int ret = gnutls_x509_crt_list_import(raw(), &certcount, datum.get(), GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED);
270                         if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER)
271                         {
272                                 // the buffer wasn't big enough to hold all certs but gnutls changed certcount to the number of available certs,
273                                 // try again with a bigger buffer
274                                 certs.resize(certcount);
275                                 ret = gnutls_x509_crt_list_import(raw(), &certcount, datum.get(), GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED);
276                         }
277
278                         ThrowOnError(ret, "Unable to load certificates");
279
280                         // Resize the vector to the actual number of certs because we rely on its size being correct
281                         // when deallocating the certs
282                         certs.resize(certcount);
283                 }
284
285                 ~X509CertList()
286                 {
287                         for (std::vector<gnutls_x509_crt_t>::iterator i = certs.begin(); i != certs.end(); ++i)
288                                 gnutls_x509_crt_deinit(*i);
289                 }
290
291                 gnutls_x509_crt_t* raw() { return &certs[0]; }
292                 unsigned int size() const { return certs.size(); }
293         };
294
295         class X509CRL : public refcountbase
296         {
297                 class RAIICRL
298                 {
299                  public:
300                         gnutls_x509_crl_t crl;
301
302                         RAIICRL()
303                         {
304                                 ThrowOnError(gnutls_x509_crl_init(&crl), "gnutls_x509_crl_init() failed");
305                         }
306
307                         ~RAIICRL()
308                         {
309                                 gnutls_x509_crl_deinit(crl);
310                         }
311                 } crl;
312
313          public:
314                 /** Import */
315                 X509CRL(const std::string& crlstr)
316                 {
317                         int ret = gnutls_x509_crl_import(get(), Datum(crlstr).get(), GNUTLS_X509_FMT_PEM);
318                         ThrowOnError(ret, "Unable to load certificate revocation list");
319                 }
320
321                 gnutls_x509_crl_t& get() { return crl.crl; }
322         };
323
324 #ifdef GNUTLS_NEW_PRIO_API
325         class Priority
326         {
327                 gnutls_priority_t priority;
328
329          public:
330                 Priority(const std::string& priorities)
331                 {
332                         // Try to set the priorities for ciphers, kex methods etc. to the user supplied string
333                         // If the user did not supply anything then the string is already set to "NORMAL"
334                         const char* priocstr = priorities.c_str();
335                         const char* prioerror;
336
337                         int ret = gnutls_priority_init(&priority, priocstr, &prioerror);
338                         if (ret < 0)
339                         {
340                                 // gnutls did not understand the user supplied string
341                                 throw Exception("Unable to initialize priorities to \"" + priorities + "\": " + gnutls_strerror(ret) + " Syntax error at position " + ConvToStr((unsigned int) (prioerror - priocstr)));
342                         }
343                 }
344
345                 ~Priority()
346                 {
347                         gnutls_priority_deinit(priority);
348                 }
349
350                 void SetupSession(gnutls_session_t sess)
351                 {
352                         gnutls_priority_set(sess, priority);
353                 }
354
355                 static const char* GetDefault()
356                 {
357                         return "NORMAL:%SERVER_PRECEDENCE:-VERS-SSL3.0";
358                 }
359
360                 static std::string RemoveUnknownTokens(const std::string& prio)
361                 {
362                         std::string ret;
363                         irc::sepstream ss(prio, ':');
364                         for (std::string token; ss.GetToken(token); )
365                         {
366                                 // Save current position so we can revert later if needed
367                                 const std::string::size_type prevpos = ret.length();
368                                 // Append next token
369                                 if (!ret.empty())
370                                         ret.push_back(':');
371                                 ret.append(token);
372
373                                 gnutls_priority_t test;
374                                 if (gnutls_priority_init(&test, ret.c_str(), NULL) < 0)
375                                 {
376                                         // The new token broke the priority string, revert to the previously working one
377                                         ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Priority string token not recognized: \"%s\"", token.c_str());
378                                         ret.erase(prevpos);
379                                 }
380                                 else
381                                 {
382                                         // Worked
383                                         gnutls_priority_deinit(test);
384                                 }
385                         }
386                         return ret;
387                 }
388         };
389 #else
390         /** Dummy class, used when gnutls_priority_set() is not available
391          */
392         class Priority
393         {
394          public:
395                 Priority(const std::string& priorities)
396                 {
397                         if (priorities != GetDefault())
398                                 throw Exception("You've set a non-default priority string, but GnuTLS lacks support for it");
399                 }
400
401                 static void SetupSession(gnutls_session_t sess)
402                 {
403                         // Always set the default priorities
404                         gnutls_set_default_priority(sess);
405                 }
406
407                 static const char* GetDefault()
408                 {
409                         return "NORMAL";
410                 }
411
412                 static std::string RemoveUnknownTokens(const std::string& prio)
413                 {
414                         // We don't do anything here because only NORMAL is accepted
415                         return prio;
416                 }
417         };
418 #endif
419
420         class CertCredentials
421         {
422                 /** DH parameters associated with these credentials
423                  */
424                 std::auto_ptr<DHParams> dh;
425
426          protected:
427                 gnutls_certificate_credentials_t cred;
428
429          public:
430                 CertCredentials()
431                 {
432                         ThrowOnError(gnutls_certificate_allocate_credentials(&cred), "Cannot allocate certificate credentials");
433                 }
434
435                 ~CertCredentials()
436                 {
437                         gnutls_certificate_free_credentials(cred);
438                 }
439
440                 /** Associates these credentials with the session
441                  */
442                 void SetupSession(gnutls_session_t sess)
443                 {
444                         gnutls_credentials_set(sess, GNUTLS_CRD_CERTIFICATE, cred);
445                 }
446
447                 /** Set the given DH parameters to be used with these credentials
448                  */
449                 void SetDH(std::auto_ptr<DHParams>& DH)
450                 {
451                         dh = DH;
452                         gnutls_certificate_set_dh_params(cred, dh->get());
453                 }
454         };
455
456         class X509Credentials : public CertCredentials
457         {
458                 /** Private key
459                  */
460                 X509Key key;
461
462                 /** Certificate list, presented to the peer
463                  */
464                 X509CertList certs;
465
466                 /** Trusted CA, may be NULL
467                  */
468                 std::auto_ptr<X509CertList> trustedca;
469
470                 /** Certificate revocation list, may be NULL
471                  */
472                 std::auto_ptr<X509CRL> crl;
473
474                 static int cert_callback(gnutls_session_t session, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, cert_cb_last_param_type* st);
475
476          public:
477                 X509Credentials(const std::string& certstr, const std::string& keystr)
478                         : key(keystr)
479                         , certs(certstr)
480                 {
481                         // Throwing is ok here, the destructor of Credentials is called in that case
482                         int ret = gnutls_certificate_set_x509_key(cred, certs.raw(), certs.size(), key.get());
483                         ThrowOnError(ret, "Unable to set cert/key pair");
484
485 #ifdef GNUTLS_NEW_CERT_CALLBACK_API
486                         gnutls_certificate_set_retrieve_function(cred, cert_callback);
487 #else
488                         gnutls_certificate_client_set_retrieve_function(cred, cert_callback);
489 #endif
490                 }
491
492                 /** Sets the trusted CA and the certificate revocation list
493                  * to use when verifying certificates
494                  */
495                 void SetCA(std::auto_ptr<X509CertList>& certlist, std::auto_ptr<X509CRL>& CRL)
496                 {
497                         // Do nothing if certlist is NULL
498                         if (certlist.get())
499                         {
500                                 int ret = gnutls_certificate_set_x509_trust(cred, certlist->raw(), certlist->size());
501                                 ThrowOnError(ret, "gnutls_certificate_set_x509_trust() failed");
502
503                                 if (CRL.get())
504                                 {
505                                         ret = gnutls_certificate_set_x509_crl(cred, &CRL->get(), 1);
506                                         ThrowOnError(ret, "gnutls_certificate_set_x509_crl() failed");
507                                 }
508
509                                 trustedca = certlist;
510                                 crl = CRL;
511                         }
512                 }
513         };
514
515         class DataReader
516         {
517                 int retval;
518 #ifdef INSPIRCD_GNUTLS_HAS_RECV_PACKET
519                 gnutls_packet_t packet;
520
521          public:
522                 DataReader(gnutls_session_t sess)
523                 {
524                         // Using the packet API avoids the final copy of the data which GnuTLS does if we supply
525                         // our own buffer. Instead, we get the buffer containing the data from GnuTLS and copy it
526                         // to the recvq directly from there in appendto().
527                         retval = gnutls_record_recv_packet(sess, &packet);
528                 }
529
530                 void appendto(std::string& recvq)
531                 {
532                         // Copy data from GnuTLS buffers to recvq
533                         gnutls_datum_t datum;
534                         gnutls_packet_get(packet, &datum, NULL);
535                         recvq.append(reinterpret_cast<const char*>(datum.data), datum.size);
536
537                         gnutls_packet_deinit(packet);
538                 }
539 #else
540                 char* const buffer;
541
542          public:
543                 DataReader(gnutls_session_t sess)
544                         : buffer(ServerInstance->GetReadBuffer())
545                 {
546                         // Read data from GnuTLS buffers into ReadBuffer
547                         retval = gnutls_record_recv(sess, buffer, ServerInstance->Config->NetBufferSize);
548                 }
549
550                 void appendto(std::string& recvq)
551                 {
552                         // Copy data from ReadBuffer to recvq
553                         recvq.append(buffer, retval);
554                 }
555 #endif
556
557                 int ret() const { return retval; }
558         };
559
560         class Profile : public refcountbase
561         {
562                 /** Name of this profile
563                  */
564                 const std::string name;
565
566                 /** X509 certificate(s) and key
567                  */
568                 X509Credentials x509cred;
569
570                 /** The minimum length in bits for the DH prime to be accepted as a client
571                  */
572                 unsigned int min_dh_bits;
573
574                 /** Hashing algorithm to use when generating certificate fingerprints
575                  */
576                 Hash hash;
577
578                 /** Priorities for ciphers, compression methods, etc.
579                  */
580                 Priority priority;
581
582                 /** Rough max size of records to send
583                  */
584                 const unsigned int outrecsize;
585
586                 /** True to request a client certificate as a server
587                  */
588                 const bool requestclientcert;
589
590                 Profile(const std::string& profilename, const std::string& certstr, const std::string& keystr,
591                                 std::auto_ptr<DHParams>& DH, unsigned int mindh, const std::string& hashstr,
592                                 const std::string& priostr, std::auto_ptr<X509CertList>& CA, std::auto_ptr<X509CRL>& CRL,
593                                 unsigned int recsize, bool Requestclientcert)
594                         : name(profilename)
595                         , x509cred(certstr, keystr)
596                         , min_dh_bits(mindh)
597                         , hash(hashstr)
598                         , priority(priostr)
599                         , outrecsize(recsize)
600                         , requestclientcert(Requestclientcert)
601                 {
602                         x509cred.SetDH(DH);
603                         x509cred.SetCA(CA, CRL);
604                 }
605
606                 static std::string ReadFile(const std::string& filename)
607                 {
608                         FileReader reader(filename);
609                         std::string ret = reader.GetString();
610                         if (ret.empty())
611                                 throw Exception("Cannot read file " + filename);
612                         return ret;
613                 }
614
615                 static std::string GetPrioStr(const std::string& profilename, ConfigTag* tag)
616                 {
617                         // Use default priority string if this tag does not specify one
618                         std::string priostr = GnuTLS::Priority::GetDefault();
619                         bool found = tag->readString("priority", priostr);
620                         // If the prio string isn't set in the config don't be strict about the default one because it doesn't work on all versions of GnuTLS
621                         if (!tag->getBool("strictpriority", found))
622                         {
623                                 std::string stripped = GnuTLS::Priority::RemoveUnknownTokens(priostr);
624                                 if (stripped.empty())
625                                 {
626                                         // Stripping failed, act as if a prio string wasn't set
627                                         stripped = GnuTLS::Priority::RemoveUnknownTokens(GnuTLS::Priority::GetDefault());
628                                         ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Priority string for profile \"%s\" contains unknown tokens and stripping it didn't yield a working one either, falling back to \"%s\"", profilename.c_str(), stripped.c_str());
629                                 }
630                                 else if ((found) && (stripped != priostr))
631                                 {
632                                         // Prio string was set in the config and we ended up with something that works but different
633                                         ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Priority string for profile \"%s\" contains unknown tokens, stripped to \"%s\"", profilename.c_str(), stripped.c_str());
634                                 }
635                                 priostr.swap(stripped);
636                         }
637                         return priostr;
638                 }
639
640          public:
641                 static reference<Profile> Create(const std::string& profilename, ConfigTag* tag)
642                 {
643                         std::string certstr = ReadFile(tag->getString("certfile", "cert.pem"));
644                         std::string keystr = ReadFile(tag->getString("keyfile", "key.pem"));
645
646                         std::auto_ptr<DHParams> dh = DHParams::Import(ReadFile(tag->getString("dhfile", "dhparams.pem")));
647
648                         std::string priostr = GetPrioStr(profilename, tag);
649                         unsigned int mindh = tag->getInt("mindhbits", 1024);
650                         std::string hashstr = tag->getString("hash", "md5");
651
652                         // Load trusted CA and revocation list, if set
653                         std::auto_ptr<X509CertList> ca;
654                         std::auto_ptr<X509CRL> crl;
655                         std::string filename = tag->getString("cafile");
656                         if (!filename.empty())
657                         {
658                                 ca.reset(new X509CertList(ReadFile(filename)));
659
660                                 filename = tag->getString("crlfile");
661                                 if (!filename.empty())
662                                         crl.reset(new X509CRL(ReadFile(filename)));
663                         }
664
665 #ifdef INSPIRCD_GNUTLS_HAS_CORK
666                         // If cork support is available outrecsize represents the (rough) max amount of data we give GnuTLS while corked
667                         unsigned int outrecsize = tag->getInt("outrecsize", 2048, 512);
668 #else
669                         unsigned int outrecsize = tag->getInt("outrecsize", 2048, 512, 16384);
670 #endif
671
672                         const bool requestclientcert = tag->getBool("requestclientcert", true);
673
674                         return new Profile(profilename, certstr, keystr, dh, mindh, hashstr, priostr, ca, crl, outrecsize, requestclientcert);
675                 }
676
677                 /** Set up the given session with the settings in this profile
678                  */
679                 void SetupSession(gnutls_session_t sess)
680                 {
681                         priority.SetupSession(sess);
682                         x509cred.SetupSession(sess);
683                         gnutls_dh_set_prime_bits(sess, min_dh_bits);
684
685                         // Request client certificate if enabled and we are a server, no-op if we're a client
686                         if (requestclientcert)
687                                 gnutls_certificate_server_set_request(sess, GNUTLS_CERT_REQUEST);
688                 }
689
690                 const std::string& GetName() const { return name; }
691                 X509Credentials& GetX509Credentials() { return x509cred; }
692                 gnutls_digest_algorithm_t GetHash() const { return hash.get(); }
693                 unsigned int GetOutgoingRecordSize() const { return outrecsize; }
694         };
695 }
696
697 class GnuTLSIOHook : public SSLIOHook
698 {
699  private:
700         gnutls_session_t sess;
701         issl_status status;
702         reference<GnuTLS::Profile> profile;
703 #ifdef INSPIRCD_GNUTLS_HAS_CORK
704         size_t gbuffersize;
705 #endif
706
707         void CloseSession()
708         {
709                 if (this->sess)
710                 {
711                         gnutls_bye(this->sess, GNUTLS_SHUT_WR);
712                         gnutls_deinit(this->sess);
713                 }
714                 sess = NULL;
715                 certificate = NULL;
716                 status = ISSL_NONE;
717         }
718
719         // Returns 1 if handshake succeeded, 0 if it is still in progress, -1 if it failed
720         int Handshake(StreamSocket* user)
721         {
722                 int ret = gnutls_handshake(this->sess);
723
724                 if (ret < 0)
725                 {
726                         if(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
727                         {
728                                 // Handshake needs resuming later, read() or write() would have blocked.
729                                 this->status = ISSL_HANDSHAKING;
730
731                                 if (gnutls_record_get_direction(this->sess) == 0)
732                                 {
733                                         // gnutls_handshake() wants to read() again.
734                                         SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
735                                 }
736                                 else
737                                 {
738                                         // gnutls_handshake() wants to write() again.
739                                         SocketEngine::ChangeEventMask(user, FD_WANT_NO_READ | FD_WANT_SINGLE_WRITE);
740                                 }
741
742                                 return 0;
743                         }
744                         else
745                         {
746                                 user->SetError("Handshake Failed - " + std::string(gnutls_strerror(ret)));
747                                 CloseSession();
748                                 return -1;
749                         }
750                 }
751                 else
752                 {
753                         // Change the seesion state
754                         this->status = ISSL_HANDSHAKEN;
755
756                         VerifyCertificate();
757
758                         // Finish writing, if any left
759                         SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE | FD_ADD_TRIAL_WRITE);
760
761                         return 1;
762                 }
763         }
764
765         void VerifyCertificate()
766         {
767                 unsigned int certstatus;
768                 const gnutls_datum_t* cert_list;
769                 int ret;
770                 unsigned int cert_list_size;
771                 gnutls_x509_crt_t cert;
772                 char str[512];
773                 unsigned char digest[512];
774                 size_t digest_size = sizeof(digest);
775                 size_t name_size = sizeof(str);
776                 ssl_cert* certinfo = new ssl_cert;
777                 this->certificate = certinfo;
778
779                 /* This verification function uses the trusted CAs in the credentials
780                  * structure. So you must have installed one or more CA certificates.
781                  */
782                 ret = gnutls_certificate_verify_peers2(this->sess, &certstatus);
783
784                 if (ret < 0)
785                 {
786                         certinfo->error = std::string(gnutls_strerror(ret));
787                         return;
788                 }
789
790                 certinfo->invalid = (certstatus & GNUTLS_CERT_INVALID);
791                 certinfo->unknownsigner = (certstatus & GNUTLS_CERT_SIGNER_NOT_FOUND);
792                 certinfo->revoked = (certstatus & GNUTLS_CERT_REVOKED);
793                 certinfo->trusted = !(certstatus & GNUTLS_CERT_SIGNER_NOT_CA);
794
795                 /* Up to here the process is the same for X.509 certificates and
796                  * OpenPGP keys. From now on X.509 certificates are assumed. This can
797                  * be easily extended to work with openpgp keys as well.
798                  */
799                 if (gnutls_certificate_type_get(this->sess) != GNUTLS_CRT_X509)
800                 {
801                         certinfo->error = "No X509 keys sent";
802                         return;
803                 }
804
805                 ret = gnutls_x509_crt_init(&cert);
806                 if (ret < 0)
807                 {
808                         certinfo->error = gnutls_strerror(ret);
809                         return;
810                 }
811
812                 cert_list_size = 0;
813                 cert_list = gnutls_certificate_get_peers(this->sess, &cert_list_size);
814                 if (cert_list == NULL)
815                 {
816                         certinfo->error = "No certificate was found";
817                         goto info_done_dealloc;
818                 }
819
820                 /* This is not a real world example, since we only check the first
821                  * certificate in the given chain.
822                  */
823
824                 ret = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER);
825                 if (ret < 0)
826                 {
827                         certinfo->error = gnutls_strerror(ret);
828                         goto info_done_dealloc;
829                 }
830
831                 if (gnutls_x509_crt_get_dn(cert, str, &name_size) == 0)
832                 {
833                         std::string& dn = certinfo->dn;
834                         dn = str;
835                         // Make sure there are no chars in the string that we consider invalid
836                         if (dn.find_first_of("\r\n") != std::string::npos)
837                                 dn.clear();
838                 }
839
840                 name_size = sizeof(str);
841                 if (gnutls_x509_crt_get_issuer_dn(cert, str, &name_size) == 0)
842                 {
843                         std::string& issuer = certinfo->issuer;
844                         issuer = str;
845                         if (issuer.find_first_of("\r\n") != std::string::npos)
846                                 issuer.clear();
847                 }
848
849                 if ((ret = gnutls_x509_crt_get_fingerprint(cert, profile->GetHash(), digest, &digest_size)) < 0)
850                 {
851                         certinfo->error = gnutls_strerror(ret);
852                 }
853                 else
854                 {
855                         certinfo->fingerprint = BinToHex(digest, digest_size);
856                 }
857
858                 /* Beware here we do not check for errors.
859                  */
860                 if ((gnutls_x509_crt_get_expiration_time(cert) < ServerInstance->Time()) || (gnutls_x509_crt_get_activation_time(cert) > ServerInstance->Time()))
861                 {
862                         certinfo->error = "Not activated, or expired certificate";
863                 }
864
865 info_done_dealloc:
866                 gnutls_x509_crt_deinit(cert);
867         }
868
869         // Returns 1 if application I/O should proceed, 0 if it must wait for the underlying protocol to progress, -1 on fatal error
870         int PrepareIO(StreamSocket* sock)
871         {
872                 if (status == ISSL_HANDSHAKEN)
873                         return 1;
874                 else if (status == ISSL_HANDSHAKING)
875                 {
876                         // The handshake isn't finished, try to finish it
877                         return Handshake(sock);
878                 }
879
880                 CloseSession();
881                 sock->SetError("No SSL session");
882                 return -1;
883         }
884
885 #ifdef INSPIRCD_GNUTLS_HAS_CORK
886         int FlushBuffer(StreamSocket* sock)
887         {
888                 // If GnuTLS has some data buffered, write it
889                 if (gbuffersize)
890                         return HandleWriteRet(sock, gnutls_record_uncork(this->sess, 0));
891                 return 1;
892         }
893 #endif
894
895         int HandleWriteRet(StreamSocket* sock, int ret)
896         {
897                 if (ret > 0)
898                 {
899 #ifdef INSPIRCD_GNUTLS_HAS_CORK
900                         gbuffersize -= ret;
901                         if (gbuffersize)
902                         {
903                                 SocketEngine::ChangeEventMask(sock, FD_WANT_SINGLE_WRITE);
904                                 return 0;
905                         }
906 #endif
907                         return ret;
908                 }
909                 else if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED || ret == 0)
910                 {
911                         SocketEngine::ChangeEventMask(sock, FD_WANT_SINGLE_WRITE);
912                         return 0;
913                 }
914                 else // (ret < 0)
915                 {
916                         sock->SetError(gnutls_strerror(ret));
917                         CloseSession();
918                         return -1;
919                 }
920         }
921
922         static const char* UnknownIfNULL(const char* str)
923         {
924                 return str ? str : "UNKNOWN";
925         }
926
927         static ssize_t gnutls_pull_wrapper(gnutls_transport_ptr_t session_wrap, void* buffer, size_t size)
928         {
929                 StreamSocket* sock = reinterpret_cast<StreamSocket*>(session_wrap);
930 #ifdef _WIN32
931                 GnuTLSIOHook* session = static_cast<GnuTLSIOHook*>(sock->GetModHook(thismod));
932 #endif
933
934                 if (sock->GetEventMask() & FD_READ_WILL_BLOCK)
935                 {
936 #ifdef _WIN32
937                         gnutls_transport_set_errno(session->sess, EAGAIN);
938 #else
939                         errno = EAGAIN;
940 #endif
941                         return -1;
942                 }
943
944                 int rv = SocketEngine::Recv(sock, reinterpret_cast<char *>(buffer), size, 0);
945
946 #ifdef _WIN32
947                 if (rv < 0)
948                 {
949                         /* Windows doesn't use errno, but gnutls does, so check SocketEngine::IgnoreError()
950                          * and then set errno appropriately.
951                          * The gnutls library may also have a different errno variable than us, see
952                          * gnutls_transport_set_errno(3).
953                          */
954                         gnutls_transport_set_errno(session->sess, SocketEngine::IgnoreError() ? EAGAIN : errno);
955                 }
956 #endif
957
958                 if (rv < (int)size)
959                         SocketEngine::ChangeEventMask(sock, FD_READ_WILL_BLOCK);
960                 return rv;
961         }
962
963 #ifdef INSPIRCD_GNUTLS_HAS_VECTOR_PUSH
964         static ssize_t VectorPush(gnutls_transport_ptr_t transportptr, const giovec_t* iov, int iovcnt)
965         {
966                 StreamSocket* sock = reinterpret_cast<StreamSocket*>(transportptr);
967 #ifdef _WIN32
968                 GnuTLSIOHook* session = static_cast<GnuTLSIOHook*>(sock->GetModHook(thismod));
969 #endif
970
971                 if (sock->GetEventMask() & FD_WRITE_WILL_BLOCK)
972                 {
973 #ifdef _WIN32
974                         gnutls_transport_set_errno(session->sess, EAGAIN);
975 #else
976                         errno = EAGAIN;
977 #endif
978                         return -1;
979                 }
980
981                 // Cast the giovec_t to iovec not to IOVector so the correct function is called on Windows
982                 int ret = SocketEngine::WriteV(sock, reinterpret_cast<const iovec*>(iov), iovcnt);
983 #ifdef _WIN32
984                 // See the function above for more info about the usage of gnutls_transport_set_errno() on Windows
985                 if (ret < 0)
986                         gnutls_transport_set_errno(session->sess, SocketEngine::IgnoreError() ? EAGAIN : errno);
987 #endif
988
989                 int size = 0;
990                 for (int i = 0; i < iovcnt; i++)
991                         size += iov[i].iov_len;
992
993                 if (ret < size)
994                         SocketEngine::ChangeEventMask(sock, FD_WRITE_WILL_BLOCK);
995                 return ret;
996         }
997
998 #else // INSPIRCD_GNUTLS_HAS_VECTOR_PUSH
999         static ssize_t gnutls_push_wrapper(gnutls_transport_ptr_t session_wrap, const void* buffer, size_t size)
1000         {
1001                 StreamSocket* sock = reinterpret_cast<StreamSocket*>(session_wrap);
1002 #ifdef _WIN32
1003                 GnuTLSIOHook* session = static_cast<GnuTLSIOHook*>(sock->GetModHook(thismod));
1004 #endif
1005
1006                 if (sock->GetEventMask() & FD_WRITE_WILL_BLOCK)
1007                 {
1008 #ifdef _WIN32
1009                         gnutls_transport_set_errno(session->sess, EAGAIN);
1010 #else
1011                         errno = EAGAIN;
1012 #endif
1013                         return -1;
1014                 }
1015
1016                 int rv = SocketEngine::Send(sock, reinterpret_cast<const char *>(buffer), size, 0);
1017
1018 #ifdef _WIN32
1019                 if (rv < 0)
1020                 {
1021                         /* Windows doesn't use errno, but gnutls does, so check SocketEngine::IgnoreError()
1022                          * and then set errno appropriately.
1023                          * The gnutls library may also have a different errno variable than us, see
1024                          * gnutls_transport_set_errno(3).
1025                          */
1026                         gnutls_transport_set_errno(session->sess, SocketEngine::IgnoreError() ? EAGAIN : errno);
1027                 }
1028 #endif
1029
1030                 if (rv < (int)size)
1031                         SocketEngine::ChangeEventMask(sock, FD_WRITE_WILL_BLOCK);
1032                 return rv;
1033         }
1034 #endif // INSPIRCD_GNUTLS_HAS_VECTOR_PUSH
1035
1036  public:
1037         GnuTLSIOHook(IOHookProvider* hookprov, StreamSocket* sock, inspircd_gnutls_session_init_flags_t flags, const reference<GnuTLS::Profile>& sslprofile)
1038                 : SSLIOHook(hookprov)
1039                 , sess(NULL)
1040                 , status(ISSL_NONE)
1041                 , profile(sslprofile)
1042 #ifdef INSPIRCD_GNUTLS_HAS_CORK
1043                 , gbuffersize(0)
1044 #endif
1045         {
1046                 gnutls_init(&sess, flags);
1047                 gnutls_transport_set_ptr(sess, reinterpret_cast<gnutls_transport_ptr_t>(sock));
1048 #ifdef INSPIRCD_GNUTLS_HAS_VECTOR_PUSH
1049                 gnutls_transport_set_vec_push_function(sess, VectorPush);
1050 #else
1051                 gnutls_transport_set_push_function(sess, gnutls_push_wrapper);
1052 #endif
1053                 gnutls_transport_set_pull_function(sess, gnutls_pull_wrapper);
1054                 profile->SetupSession(sess);
1055
1056                 sock->AddIOHook(this);
1057                 Handshake(sock);
1058         }
1059
1060         void OnStreamSocketClose(StreamSocket* user) CXX11_OVERRIDE
1061         {
1062                 CloseSession();
1063         }
1064
1065         int OnStreamSocketRead(StreamSocket* user, std::string& recvq) CXX11_OVERRIDE
1066         {
1067                 // Finish handshake if needed
1068                 int prepret = PrepareIO(user);
1069                 if (prepret <= 0)
1070                         return prepret;
1071
1072                 // If we resumed the handshake then this->status will be ISSL_HANDSHAKEN.
1073                 {
1074                         GnuTLS::DataReader reader(sess);
1075                         int ret = reader.ret();
1076                         if (ret > 0)
1077                         {
1078                                 reader.appendto(recvq);
1079                                 return 1;
1080                         }
1081                         else if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
1082                         {
1083                                 return 0;
1084                         }
1085                         else if (ret == 0)
1086                         {
1087                                 user->SetError("Connection closed");
1088                                 CloseSession();
1089                                 return -1;
1090                         }
1091                         else
1092                         {
1093                                 user->SetError(gnutls_strerror(ret));
1094                                 CloseSession();
1095                                 return -1;
1096                         }
1097                 }
1098         }
1099
1100         int OnStreamSocketWrite(StreamSocket* user, StreamSocket::SendQueue& sendq) CXX11_OVERRIDE
1101         {
1102                 // Finish handshake if needed
1103                 int prepret = PrepareIO(user);
1104                 if (prepret <= 0)
1105                         return prepret;
1106
1107                 // Session is ready for transferring application data
1108
1109 #ifdef INSPIRCD_GNUTLS_HAS_CORK
1110                 while (true)
1111                 {
1112                         // If there is something in the GnuTLS buffer try to send() it
1113                         int ret = FlushBuffer(user);
1114                         if (ret <= 0)
1115                                 return ret; // Couldn't flush entire buffer, retry later (or close on error)
1116
1117                         // GnuTLS buffer is empty, if the sendq is empty as well then break to set FD_WANT_NO_WRITE
1118                         if (sendq.empty())
1119                                 break;
1120
1121                         // GnuTLS buffer is empty but sendq is not, begin sending data from the sendq
1122                         gnutls_record_cork(this->sess);
1123                         while ((!sendq.empty()) && (gbuffersize < profile->GetOutgoingRecordSize()))
1124                         {
1125                                 const StreamSocket::SendQueue::Element& elem = sendq.front();
1126                                 gbuffersize += elem.length();
1127                                 ret = gnutls_record_send(this->sess, elem.data(), elem.length());
1128                                 if (ret < 0)
1129                                 {
1130                                         CloseSession();
1131                                         return -1;
1132                                 }
1133                                 sendq.pop_front();
1134                         }
1135                 }
1136 #else
1137                 int ret = 0;
1138
1139                 while (!sendq.empty())
1140                 {
1141                         FlattenSendQueue(sendq, profile->GetOutgoingRecordSize());
1142                         const StreamSocket::SendQueue::Element& buffer = sendq.front();
1143                         ret = HandleWriteRet(user, gnutls_record_send(this->sess, buffer.data(), buffer.length()));
1144
1145                         if (ret <= 0)
1146                                 return ret;
1147                         else if (ret < (int)buffer.length())
1148                         {
1149                                 sendq.erase_front(ret);
1150                                 SocketEngine::ChangeEventMask(user, FD_WANT_SINGLE_WRITE);
1151                                 return 0;
1152                         }
1153
1154                         // Wrote entire record, continue sending
1155                         sendq.pop_front();
1156                 }
1157 #endif
1158
1159                 SocketEngine::ChangeEventMask(user, FD_WANT_NO_WRITE);
1160                 return 1;
1161         }
1162
1163         void GetCiphersuite(std::string& out) const CXX11_OVERRIDE
1164         {
1165                 if (!IsHandshakeDone())
1166                         return;
1167                 out.append(UnknownIfNULL(gnutls_protocol_get_name(gnutls_protocol_get_version(sess)))).push_back('-');
1168                 out.append(UnknownIfNULL(gnutls_kx_get_name(gnutls_kx_get(sess)))).push_back('-');
1169                 out.append(UnknownIfNULL(gnutls_cipher_get_name(gnutls_cipher_get(sess)))).push_back('-');
1170                 out.append(UnknownIfNULL(gnutls_mac_get_name(gnutls_mac_get(sess))));
1171         }
1172
1173         GnuTLS::Profile* GetProfile() { return profile; }
1174         bool IsHandshakeDone() const { return (status == ISSL_HANDSHAKEN); }
1175 };
1176
1177 int GnuTLS::X509Credentials::cert_callback(gnutls_session_t sess, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, cert_cb_last_param_type* st)
1178 {
1179 #ifndef GNUTLS_NEW_CERT_CALLBACK_API
1180         st->type = GNUTLS_CRT_X509;
1181 #else
1182         st->cert_type = GNUTLS_CRT_X509;
1183         st->key_type = GNUTLS_PRIVKEY_X509;
1184 #endif
1185         StreamSocket* sock = reinterpret_cast<StreamSocket*>(gnutls_transport_get_ptr(sess));
1186         GnuTLS::X509Credentials& cred = static_cast<GnuTLSIOHook*>(sock->GetModHook(thismod))->GetProfile()->GetX509Credentials();
1187
1188         st->ncerts = cred.certs.size();
1189         st->cert.x509 = cred.certs.raw();
1190         st->key.x509 = cred.key.get();
1191         st->deinit_all = 0;
1192
1193         return 0;
1194 }
1195
1196 class GnuTLSIOHookProvider : public refcountbase, public IOHookProvider
1197 {
1198         reference<GnuTLS::Profile> profile;
1199
1200  public:
1201         GnuTLSIOHookProvider(Module* mod, reference<GnuTLS::Profile>& prof)
1202                 : IOHookProvider(mod, "ssl/" + prof->GetName(), IOHookProvider::IOH_SSL)
1203                 , profile(prof)
1204         {
1205                 ServerInstance->Modules->AddService(*this);
1206         }
1207
1208         ~GnuTLSIOHookProvider()
1209         {
1210                 ServerInstance->Modules->DelService(*this);
1211         }
1212
1213         void OnAccept(StreamSocket* sock, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE
1214         {
1215                 new GnuTLSIOHook(this, sock, GNUTLS_SERVER, profile);
1216         }
1217
1218         void OnConnect(StreamSocket* sock) CXX11_OVERRIDE
1219         {
1220                 new GnuTLSIOHook(this, sock, GNUTLS_CLIENT, profile);
1221         }
1222 };
1223
1224 class ModuleSSLGnuTLS : public Module
1225 {
1226         typedef std::vector<reference<GnuTLSIOHookProvider> > ProfileList;
1227
1228         // First member of the class, gets constructed first and destructed last
1229         GnuTLS::Init libinit;
1230         RandGen randhandler;
1231         ProfileList profiles;
1232
1233         void ReadProfiles()
1234         {
1235                 // First, store all profiles in a new, temporary container. If no problems occur, swap the two
1236                 // containers; this way if something goes wrong we can go back and continue using the current profiles,
1237                 // avoiding unpleasant situations where no new SSL connections are possible.
1238                 ProfileList newprofiles;
1239
1240                 ConfigTagList tags = ServerInstance->Config->ConfTags("sslprofile");
1241                 if (tags.first == tags.second)
1242                 {
1243                         // No <sslprofile> tags found, create a profile named "gnutls" from settings in the <gnutls> block
1244                         const std::string defname = "gnutls";
1245                         ConfigTag* tag = ServerInstance->Config->ConfValue(defname);
1246                         ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "No <sslprofile> tags found; using settings from the <gnutls> tag");
1247
1248                         try
1249                         {
1250                                 reference<GnuTLS::Profile> profile(GnuTLS::Profile::Create(defname, tag));
1251                                 newprofiles.push_back(new GnuTLSIOHookProvider(this, profile));
1252                         }
1253                         catch (CoreException& ex)
1254                         {
1255                                 throw ModuleException("Error while initializing the default SSL profile - " + ex.GetReason());
1256                         }
1257                 }
1258
1259                 for (ConfigIter i = tags.first; i != tags.second; ++i)
1260                 {
1261                         ConfigTag* tag = i->second;
1262                         if (tag->getString("provider") != "gnutls")
1263                                 continue;
1264
1265                         std::string name = tag->getString("name");
1266                         if (name.empty())
1267                         {
1268                                 ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Ignoring <sslprofile> tag without name at " + tag->getTagLocation());
1269                                 continue;
1270                         }
1271
1272                         reference<GnuTLS::Profile> profile;
1273                         try
1274                         {
1275                                 profile = GnuTLS::Profile::Create(name, tag);
1276                         }
1277                         catch (CoreException& ex)
1278                         {
1279                                 throw ModuleException("Error while initializing SSL profile \"" + name + "\" at " + tag->getTagLocation() + " - " + ex.GetReason());
1280                         }
1281
1282                         newprofiles.push_back(new GnuTLSIOHookProvider(this, profile));
1283                 }
1284
1285                 // New profiles are ok, begin using them
1286                 // Old profiles are deleted when their refcount drops to zero
1287                 profiles.swap(newprofiles);
1288         }
1289
1290  public:
1291         ModuleSSLGnuTLS()
1292         {
1293 #ifndef GNUTLS_HAS_RND
1294                 gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
1295 #endif
1296                 thismod = this;
1297         }
1298
1299         void init() CXX11_OVERRIDE
1300         {
1301                 ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "GnuTLS lib version %s module was compiled for " GNUTLS_VERSION, gnutls_check_version(NULL));
1302                 ReadProfiles();
1303                 ServerInstance->GenRandom = &randhandler;
1304         }
1305
1306         void OnModuleRehash(User* user, const std::string &param) CXX11_OVERRIDE
1307         {
1308                 if(param != "ssl")
1309                         return;
1310
1311                 try
1312                 {
1313                         ReadProfiles();
1314                 }
1315                 catch (ModuleException& ex)
1316                 {
1317                         ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, ex.GetReason() + " Not applying settings.");
1318                 }
1319         }
1320
1321         ~ModuleSSLGnuTLS()
1322         {
1323                 ServerInstance->GenRandom = &ServerInstance->HandleGenRandom;
1324         }
1325
1326         void OnCleanup(int target_type, void* item) CXX11_OVERRIDE
1327         {
1328                 if(target_type == TYPE_USER)
1329                 {
1330                         LocalUser* user = IS_LOCAL(static_cast<User*>(item));
1331
1332                         if ((user) && (user->eh.GetModHook(this)))
1333                         {
1334                                 // User is using SSL, they're a local user, and they're using one of *our* SSL ports.
1335                                 // Potentially there could be multiple SSL modules loaded at once on different ports.
1336                                 ServerInstance->Users->QuitUser(user, "SSL module unloading");
1337                         }
1338                 }
1339         }
1340
1341         Version GetVersion() CXX11_OVERRIDE
1342         {
1343                 return Version("Provides SSL support for clients", VF_VENDOR);
1344         }
1345
1346         ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE
1347         {
1348                 const GnuTLSIOHook* const iohook = static_cast<GnuTLSIOHook*>(user->eh.GetModHook(this));
1349                 if ((iohook) && (!iohook->IsHandshakeDone()))
1350                         return MOD_RES_DENY;
1351                 return MOD_RES_PASSTHRU;
1352         }
1353 };
1354
1355 MODULE_INIT(ModuleSSLGnuTLS)