]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/extra/m_ssl_gnutls.cpp
Add StreamSocket::GetModHook() for obtaining the IOHook belonging to a given module
[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                 Profile(const std::string& profilename, const std::string& certstr, const std::string& keystr,
587                                 std::auto_ptr<DHParams>& DH, unsigned int mindh, const std::string& hashstr,
588                                 const std::string& priostr, std::auto_ptr<X509CertList>& CA, std::auto_ptr<X509CRL>& CRL,
589                                 unsigned int recsize)
590                         : name(profilename)
591                         , x509cred(certstr, keystr)
592                         , min_dh_bits(mindh)
593                         , hash(hashstr)
594                         , priority(priostr)
595                         , outrecsize(recsize)
596                 {
597                         x509cred.SetDH(DH);
598                         x509cred.SetCA(CA, CRL);
599                 }
600
601                 static std::string ReadFile(const std::string& filename)
602                 {
603                         FileReader reader(filename);
604                         std::string ret = reader.GetString();
605                         if (ret.empty())
606                                 throw Exception("Cannot read file " + filename);
607                         return ret;
608                 }
609
610                 static std::string GetPrioStr(const std::string& profilename, ConfigTag* tag)
611                 {
612                         // Use default priority string if this tag does not specify one
613                         std::string priostr = GnuTLS::Priority::GetDefault();
614                         bool found = tag->readString("priority", priostr);
615                         // 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
616                         if (!tag->getBool("strictpriority", found))
617                         {
618                                 std::string stripped = GnuTLS::Priority::RemoveUnknownTokens(priostr);
619                                 if (stripped.empty())
620                                 {
621                                         // Stripping failed, act as if a prio string wasn't set
622                                         stripped = GnuTLS::Priority::RemoveUnknownTokens(GnuTLS::Priority::GetDefault());
623                                         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());
624                                 }
625                                 else if ((found) && (stripped != priostr))
626                                 {
627                                         // Prio string was set in the config and we ended up with something that works but different
628                                         ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Priority string for profile \"%s\" contains unknown tokens, stripped to \"%s\"", profilename.c_str(), stripped.c_str());
629                                 }
630                                 priostr.swap(stripped);
631                         }
632                         return priostr;
633                 }
634
635          public:
636                 static reference<Profile> Create(const std::string& profilename, ConfigTag* tag)
637                 {
638                         std::string certstr = ReadFile(tag->getString("certfile", "cert.pem"));
639                         std::string keystr = ReadFile(tag->getString("keyfile", "key.pem"));
640
641                         std::auto_ptr<DHParams> dh = DHParams::Import(ReadFile(tag->getString("dhfile", "dhparams.pem")));
642
643                         std::string priostr = GetPrioStr(profilename, tag);
644                         unsigned int mindh = tag->getInt("mindhbits", 1024);
645                         std::string hashstr = tag->getString("hash", "md5");
646
647                         // Load trusted CA and revocation list, if set
648                         std::auto_ptr<X509CertList> ca;
649                         std::auto_ptr<X509CRL> crl;
650                         std::string filename = tag->getString("cafile");
651                         if (!filename.empty())
652                         {
653                                 ca.reset(new X509CertList(ReadFile(filename)));
654
655                                 filename = tag->getString("crlfile");
656                                 if (!filename.empty())
657                                         crl.reset(new X509CRL(ReadFile(filename)));
658                         }
659
660 #ifdef INSPIRCD_GNUTLS_HAS_CORK
661                         // If cork support is available outrecsize represents the (rough) max amount of data we give GnuTLS while corked
662                         unsigned int outrecsize = tag->getInt("outrecsize", 2048, 512);
663 #else
664                         unsigned int outrecsize = tag->getInt("outrecsize", 2048, 512, 16384);
665 #endif
666                         return new Profile(profilename, certstr, keystr, dh, mindh, hashstr, priostr, ca, crl, outrecsize);
667                 }
668
669                 /** Set up the given session with the settings in this profile
670                  */
671                 void SetupSession(gnutls_session_t sess)
672                 {
673                         priority.SetupSession(sess);
674                         x509cred.SetupSession(sess);
675                         gnutls_dh_set_prime_bits(sess, min_dh_bits);
676
677                         // Request client certificate if we are a server, no-op if we're a client
678                         gnutls_certificate_server_set_request(sess, GNUTLS_CERT_REQUEST);
679                 }
680
681                 const std::string& GetName() const { return name; }
682                 X509Credentials& GetX509Credentials() { return x509cred; }
683                 gnutls_digest_algorithm_t GetHash() const { return hash.get(); }
684                 unsigned int GetOutgoingRecordSize() const { return outrecsize; }
685         };
686 }
687
688 class GnuTLSIOHook : public SSLIOHook
689 {
690  private:
691         gnutls_session_t sess;
692         issl_status status;
693         reference<GnuTLS::Profile> profile;
694 #ifdef INSPIRCD_GNUTLS_HAS_CORK
695         size_t gbuffersize;
696 #endif
697
698         void CloseSession()
699         {
700                 if (this->sess)
701                 {
702                         gnutls_bye(this->sess, GNUTLS_SHUT_WR);
703                         gnutls_deinit(this->sess);
704                 }
705                 sess = NULL;
706                 certificate = NULL;
707                 status = ISSL_NONE;
708         }
709
710         // Returns 1 if handshake succeeded, 0 if it is still in progress, -1 if it failed
711         int Handshake(StreamSocket* user)
712         {
713                 int ret = gnutls_handshake(this->sess);
714
715                 if (ret < 0)
716                 {
717                         if(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
718                         {
719                                 // Handshake needs resuming later, read() or write() would have blocked.
720                                 this->status = ISSL_HANDSHAKING;
721
722                                 if (gnutls_record_get_direction(this->sess) == 0)
723                                 {
724                                         // gnutls_handshake() wants to read() again.
725                                         SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
726                                 }
727                                 else
728                                 {
729                                         // gnutls_handshake() wants to write() again.
730                                         SocketEngine::ChangeEventMask(user, FD_WANT_NO_READ | FD_WANT_SINGLE_WRITE);
731                                 }
732
733                                 return 0;
734                         }
735                         else
736                         {
737                                 user->SetError("Handshake Failed - " + std::string(gnutls_strerror(ret)));
738                                 CloseSession();
739                                 return -1;
740                         }
741                 }
742                 else
743                 {
744                         // Change the seesion state
745                         this->status = ISSL_HANDSHAKEN;
746
747                         VerifyCertificate();
748
749                         // Finish writing, if any left
750                         SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE | FD_ADD_TRIAL_WRITE);
751
752                         return 1;
753                 }
754         }
755
756         void VerifyCertificate()
757         {
758                 unsigned int certstatus;
759                 const gnutls_datum_t* cert_list;
760                 int ret;
761                 unsigned int cert_list_size;
762                 gnutls_x509_crt_t cert;
763                 char str[512];
764                 unsigned char digest[512];
765                 size_t digest_size = sizeof(digest);
766                 size_t name_size = sizeof(str);
767                 ssl_cert* certinfo = new ssl_cert;
768                 this->certificate = certinfo;
769
770                 /* This verification function uses the trusted CAs in the credentials
771                  * structure. So you must have installed one or more CA certificates.
772                  */
773                 ret = gnutls_certificate_verify_peers2(this->sess, &certstatus);
774
775                 if (ret < 0)
776                 {
777                         certinfo->error = std::string(gnutls_strerror(ret));
778                         return;
779                 }
780
781                 certinfo->invalid = (certstatus & GNUTLS_CERT_INVALID);
782                 certinfo->unknownsigner = (certstatus & GNUTLS_CERT_SIGNER_NOT_FOUND);
783                 certinfo->revoked = (certstatus & GNUTLS_CERT_REVOKED);
784                 certinfo->trusted = !(certstatus & GNUTLS_CERT_SIGNER_NOT_CA);
785
786                 /* Up to here the process is the same for X.509 certificates and
787                  * OpenPGP keys. From now on X.509 certificates are assumed. This can
788                  * be easily extended to work with openpgp keys as well.
789                  */
790                 if (gnutls_certificate_type_get(this->sess) != GNUTLS_CRT_X509)
791                 {
792                         certinfo->error = "No X509 keys sent";
793                         return;
794                 }
795
796                 ret = gnutls_x509_crt_init(&cert);
797                 if (ret < 0)
798                 {
799                         certinfo->error = gnutls_strerror(ret);
800                         return;
801                 }
802
803                 cert_list_size = 0;
804                 cert_list = gnutls_certificate_get_peers(this->sess, &cert_list_size);
805                 if (cert_list == NULL)
806                 {
807                         certinfo->error = "No certificate was found";
808                         goto info_done_dealloc;
809                 }
810
811                 /* This is not a real world example, since we only check the first
812                  * certificate in the given chain.
813                  */
814
815                 ret = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER);
816                 if (ret < 0)
817                 {
818                         certinfo->error = gnutls_strerror(ret);
819                         goto info_done_dealloc;
820                 }
821
822                 if (gnutls_x509_crt_get_dn(cert, str, &name_size) == 0)
823                 {
824                         std::string& dn = certinfo->dn;
825                         dn = str;
826                         // Make sure there are no chars in the string that we consider invalid
827                         if (dn.find_first_of("\r\n") != std::string::npos)
828                                 dn.clear();
829                 }
830
831                 name_size = sizeof(str);
832                 if (gnutls_x509_crt_get_issuer_dn(cert, str, &name_size) == 0)
833                 {
834                         std::string& issuer = certinfo->issuer;
835                         issuer = str;
836                         if (issuer.find_first_of("\r\n") != std::string::npos)
837                                 issuer.clear();
838                 }
839
840                 if ((ret = gnutls_x509_crt_get_fingerprint(cert, profile->GetHash(), digest, &digest_size)) < 0)
841                 {
842                         certinfo->error = gnutls_strerror(ret);
843                 }
844                 else
845                 {
846                         certinfo->fingerprint = BinToHex(digest, digest_size);
847                 }
848
849                 /* Beware here we do not check for errors.
850                  */
851                 if ((gnutls_x509_crt_get_expiration_time(cert) < ServerInstance->Time()) || (gnutls_x509_crt_get_activation_time(cert) > ServerInstance->Time()))
852                 {
853                         certinfo->error = "Not activated, or expired certificate";
854                 }
855
856 info_done_dealloc:
857                 gnutls_x509_crt_deinit(cert);
858         }
859
860         // Returns 1 if application I/O should proceed, 0 if it must wait for the underlying protocol to progress, -1 on fatal error
861         int PrepareIO(StreamSocket* sock)
862         {
863                 if (status == ISSL_HANDSHAKEN)
864                         return 1;
865                 else if (status == ISSL_HANDSHAKING)
866                 {
867                         // The handshake isn't finished, try to finish it
868                         return Handshake(sock);
869                 }
870
871                 CloseSession();
872                 sock->SetError("No SSL session");
873                 return -1;
874         }
875
876 #ifdef INSPIRCD_GNUTLS_HAS_CORK
877         int FlushBuffer(StreamSocket* sock)
878         {
879                 // If GnuTLS has some data buffered, write it
880                 if (gbuffersize)
881                         return HandleWriteRet(sock, gnutls_record_uncork(this->sess, 0));
882                 return 1;
883         }
884 #endif
885
886         int HandleWriteRet(StreamSocket* sock, int ret)
887         {
888                 if (ret > 0)
889                 {
890 #ifdef INSPIRCD_GNUTLS_HAS_CORK
891                         gbuffersize -= ret;
892                         if (gbuffersize)
893                         {
894                                 SocketEngine::ChangeEventMask(sock, FD_WANT_SINGLE_WRITE);
895                                 return 0;
896                         }
897 #endif
898                         return ret;
899                 }
900                 else if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED || ret == 0)
901                 {
902                         SocketEngine::ChangeEventMask(sock, FD_WANT_SINGLE_WRITE);
903                         return 0;
904                 }
905                 else // (ret < 0)
906                 {
907                         sock->SetError(gnutls_strerror(ret));
908                         CloseSession();
909                         return -1;
910                 }
911         }
912
913         static const char* UnknownIfNULL(const char* str)
914         {
915                 return str ? str : "UNKNOWN";
916         }
917
918         static ssize_t gnutls_pull_wrapper(gnutls_transport_ptr_t session_wrap, void* buffer, size_t size)
919         {
920                 StreamSocket* sock = reinterpret_cast<StreamSocket*>(session_wrap);
921 #ifdef _WIN32
922                 GnuTLSIOHook* session = static_cast<GnuTLSIOHook*>(sock->GetModHook(thismod));
923 #endif
924
925                 if (sock->GetEventMask() & FD_READ_WILL_BLOCK)
926                 {
927 #ifdef _WIN32
928                         gnutls_transport_set_errno(session->sess, EAGAIN);
929 #else
930                         errno = EAGAIN;
931 #endif
932                         return -1;
933                 }
934
935                 int rv = SocketEngine::Recv(sock, reinterpret_cast<char *>(buffer), size, 0);
936
937 #ifdef _WIN32
938                 if (rv < 0)
939                 {
940                         /* Windows doesn't use errno, but gnutls does, so check SocketEngine::IgnoreError()
941                          * and then set errno appropriately.
942                          * The gnutls library may also have a different errno variable than us, see
943                          * gnutls_transport_set_errno(3).
944                          */
945                         gnutls_transport_set_errno(session->sess, SocketEngine::IgnoreError() ? EAGAIN : errno);
946                 }
947 #endif
948
949                 if (rv < (int)size)
950                         SocketEngine::ChangeEventMask(sock, FD_READ_WILL_BLOCK);
951                 return rv;
952         }
953
954 #ifdef INSPIRCD_GNUTLS_HAS_VECTOR_PUSH
955         static ssize_t VectorPush(gnutls_transport_ptr_t transportptr, const giovec_t* iov, int iovcnt)
956         {
957                 StreamSocket* sock = reinterpret_cast<StreamSocket*>(transportptr);
958 #ifdef _WIN32
959                 GnuTLSIOHook* session = static_cast<GnuTLSIOHook*>(sock->GetModHook(thismod));
960 #endif
961
962                 if (sock->GetEventMask() & FD_WRITE_WILL_BLOCK)
963                 {
964 #ifdef _WIN32
965                         gnutls_transport_set_errno(session->sess, EAGAIN);
966 #else
967                         errno = EAGAIN;
968 #endif
969                         return -1;
970                 }
971
972                 // Cast the giovec_t to iovec not to IOVector so the correct function is called on Windows
973                 int ret = SocketEngine::WriteV(sock, reinterpret_cast<const iovec*>(iov), iovcnt);
974 #ifdef _WIN32
975                 // See the function above for more info about the usage of gnutls_transport_set_errno() on Windows
976                 if (ret < 0)
977                         gnutls_transport_set_errno(session->sess, SocketEngine::IgnoreError() ? EAGAIN : errno);
978 #endif
979
980                 int size = 0;
981                 for (int i = 0; i < iovcnt; i++)
982                         size += iov[i].iov_len;
983
984                 if (ret < size)
985                         SocketEngine::ChangeEventMask(sock, FD_WRITE_WILL_BLOCK);
986                 return ret;
987         }
988
989 #else // INSPIRCD_GNUTLS_HAS_VECTOR_PUSH
990         static ssize_t gnutls_push_wrapper(gnutls_transport_ptr_t session_wrap, const void* buffer, size_t size)
991         {
992                 StreamSocket* sock = reinterpret_cast<StreamSocket*>(session_wrap);
993 #ifdef _WIN32
994                 GnuTLSIOHook* session = static_cast<GnuTLSIOHook*>(sock->GetModHook(thismod));
995 #endif
996
997                 if (sock->GetEventMask() & FD_WRITE_WILL_BLOCK)
998                 {
999 #ifdef _WIN32
1000                         gnutls_transport_set_errno(session->sess, EAGAIN);
1001 #else
1002                         errno = EAGAIN;
1003 #endif
1004                         return -1;
1005                 }
1006
1007                 int rv = SocketEngine::Send(sock, reinterpret_cast<const char *>(buffer), size, 0);
1008
1009 #ifdef _WIN32
1010                 if (rv < 0)
1011                 {
1012                         /* Windows doesn't use errno, but gnutls does, so check SocketEngine::IgnoreError()
1013                          * and then set errno appropriately.
1014                          * The gnutls library may also have a different errno variable than us, see
1015                          * gnutls_transport_set_errno(3).
1016                          */
1017                         gnutls_transport_set_errno(session->sess, SocketEngine::IgnoreError() ? EAGAIN : errno);
1018                 }
1019 #endif
1020
1021                 if (rv < (int)size)
1022                         SocketEngine::ChangeEventMask(sock, FD_WRITE_WILL_BLOCK);
1023                 return rv;
1024         }
1025 #endif // INSPIRCD_GNUTLS_HAS_VECTOR_PUSH
1026
1027  public:
1028         GnuTLSIOHook(IOHookProvider* hookprov, StreamSocket* sock, inspircd_gnutls_session_init_flags_t flags, const reference<GnuTLS::Profile>& sslprofile)
1029                 : SSLIOHook(hookprov)
1030                 , sess(NULL)
1031                 , status(ISSL_NONE)
1032                 , profile(sslprofile)
1033 #ifdef INSPIRCD_GNUTLS_HAS_CORK
1034                 , gbuffersize(0)
1035 #endif
1036         {
1037                 gnutls_init(&sess, flags);
1038                 gnutls_transport_set_ptr(sess, reinterpret_cast<gnutls_transport_ptr_t>(sock));
1039 #ifdef INSPIRCD_GNUTLS_HAS_VECTOR_PUSH
1040                 gnutls_transport_set_vec_push_function(sess, VectorPush);
1041 #else
1042                 gnutls_transport_set_push_function(sess, gnutls_push_wrapper);
1043 #endif
1044                 gnutls_transport_set_pull_function(sess, gnutls_pull_wrapper);
1045                 profile->SetupSession(sess);
1046
1047                 sock->AddIOHook(this);
1048                 Handshake(sock);
1049         }
1050
1051         void OnStreamSocketClose(StreamSocket* user) CXX11_OVERRIDE
1052         {
1053                 CloseSession();
1054         }
1055
1056         int OnStreamSocketRead(StreamSocket* user, std::string& recvq) CXX11_OVERRIDE
1057         {
1058                 // Finish handshake if needed
1059                 int prepret = PrepareIO(user);
1060                 if (prepret <= 0)
1061                         return prepret;
1062
1063                 // If we resumed the handshake then this->status will be ISSL_HANDSHAKEN.
1064                 {
1065                         GnuTLS::DataReader reader(sess);
1066                         int ret = reader.ret();
1067                         if (ret > 0)
1068                         {
1069                                 reader.appendto(recvq);
1070                                 return 1;
1071                         }
1072                         else if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
1073                         {
1074                                 return 0;
1075                         }
1076                         else if (ret == 0)
1077                         {
1078                                 user->SetError("Connection closed");
1079                                 CloseSession();
1080                                 return -1;
1081                         }
1082                         else
1083                         {
1084                                 user->SetError(gnutls_strerror(ret));
1085                                 CloseSession();
1086                                 return -1;
1087                         }
1088                 }
1089         }
1090
1091         int OnStreamSocketWrite(StreamSocket* user, StreamSocket::SendQueue& sendq) CXX11_OVERRIDE
1092         {
1093                 // Finish handshake if needed
1094                 int prepret = PrepareIO(user);
1095                 if (prepret <= 0)
1096                         return prepret;
1097
1098                 // Session is ready for transferring application data
1099
1100 #ifdef INSPIRCD_GNUTLS_HAS_CORK
1101                 while (true)
1102                 {
1103                         // If there is something in the GnuTLS buffer try to send() it
1104                         int ret = FlushBuffer(user);
1105                         if (ret <= 0)
1106                                 return ret; // Couldn't flush entire buffer, retry later (or close on error)
1107
1108                         // GnuTLS buffer is empty, if the sendq is empty as well then break to set FD_WANT_NO_WRITE
1109                         if (sendq.empty())
1110                                 break;
1111
1112                         // GnuTLS buffer is empty but sendq is not, begin sending data from the sendq
1113                         gnutls_record_cork(this->sess);
1114                         while ((!sendq.empty()) && (gbuffersize < profile->GetOutgoingRecordSize()))
1115                         {
1116                                 const StreamSocket::SendQueue::Element& elem = sendq.front();
1117                                 gbuffersize += elem.length();
1118                                 ret = gnutls_record_send(this->sess, elem.data(), elem.length());
1119                                 if (ret < 0)
1120                                 {
1121                                         CloseSession();
1122                                         return -1;
1123                                 }
1124                                 sendq.pop_front();
1125                         }
1126                 }
1127 #else
1128                 int ret = 0;
1129
1130                 while (!sendq.empty())
1131                 {
1132                         FlattenSendQueue(sendq, profile->GetOutgoingRecordSize());
1133                         const StreamSocket::SendQueue::Element& buffer = sendq.front();
1134                         ret = HandleWriteRet(user, gnutls_record_send(this->sess, buffer.data(), buffer.length()));
1135
1136                         if (ret <= 0)
1137                                 return ret;
1138                         else if (ret < (int)buffer.length())
1139                         {
1140                                 sendq.erase_front(ret);
1141                                 SocketEngine::ChangeEventMask(user, FD_WANT_SINGLE_WRITE);
1142                                 return 0;
1143                         }
1144
1145                         // Wrote entire record, continue sending
1146                         sendq.pop_front();
1147                 }
1148 #endif
1149
1150                 SocketEngine::ChangeEventMask(user, FD_WANT_NO_WRITE);
1151                 return 1;
1152         }
1153
1154         void GetCiphersuite(std::string& out) const CXX11_OVERRIDE
1155         {
1156                 if (!IsHandshakeDone())
1157                         return;
1158                 out.append(UnknownIfNULL(gnutls_protocol_get_name(gnutls_protocol_get_version(sess)))).push_back('-');
1159                 out.append(UnknownIfNULL(gnutls_kx_get_name(gnutls_kx_get(sess)))).push_back('-');
1160                 out.append(UnknownIfNULL(gnutls_cipher_get_name(gnutls_cipher_get(sess)))).push_back('-');
1161                 out.append(UnknownIfNULL(gnutls_mac_get_name(gnutls_mac_get(sess))));
1162         }
1163
1164         GnuTLS::Profile* GetProfile() { return profile; }
1165         bool IsHandshakeDone() const { return (status == ISSL_HANDSHAKEN); }
1166 };
1167
1168 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)
1169 {
1170 #ifndef GNUTLS_NEW_CERT_CALLBACK_API
1171         st->type = GNUTLS_CRT_X509;
1172 #else
1173         st->cert_type = GNUTLS_CRT_X509;
1174         st->key_type = GNUTLS_PRIVKEY_X509;
1175 #endif
1176         StreamSocket* sock = reinterpret_cast<StreamSocket*>(gnutls_transport_get_ptr(sess));
1177         GnuTLS::X509Credentials& cred = static_cast<GnuTLSIOHook*>(sock->GetModHook(thismod))->GetProfile()->GetX509Credentials();
1178
1179         st->ncerts = cred.certs.size();
1180         st->cert.x509 = cred.certs.raw();
1181         st->key.x509 = cred.key.get();
1182         st->deinit_all = 0;
1183
1184         return 0;
1185 }
1186
1187 class GnuTLSIOHookProvider : public refcountbase, public IOHookProvider
1188 {
1189         reference<GnuTLS::Profile> profile;
1190
1191  public:
1192         GnuTLSIOHookProvider(Module* mod, reference<GnuTLS::Profile>& prof)
1193                 : IOHookProvider(mod, "ssl/" + prof->GetName(), IOHookProvider::IOH_SSL)
1194                 , profile(prof)
1195         {
1196                 ServerInstance->Modules->AddService(*this);
1197         }
1198
1199         ~GnuTLSIOHookProvider()
1200         {
1201                 ServerInstance->Modules->DelService(*this);
1202         }
1203
1204         void OnAccept(StreamSocket* sock, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE
1205         {
1206                 new GnuTLSIOHook(this, sock, GNUTLS_SERVER, profile);
1207         }
1208
1209         void OnConnect(StreamSocket* sock) CXX11_OVERRIDE
1210         {
1211                 new GnuTLSIOHook(this, sock, GNUTLS_CLIENT, profile);
1212         }
1213 };
1214
1215 class ModuleSSLGnuTLS : public Module
1216 {
1217         typedef std::vector<reference<GnuTLSIOHookProvider> > ProfileList;
1218
1219         // First member of the class, gets constructed first and destructed last
1220         GnuTLS::Init libinit;
1221         RandGen randhandler;
1222         ProfileList profiles;
1223
1224         void ReadProfiles()
1225         {
1226                 // First, store all profiles in a new, temporary container. If no problems occur, swap the two
1227                 // containers; this way if something goes wrong we can go back and continue using the current profiles,
1228                 // avoiding unpleasant situations where no new SSL connections are possible.
1229                 ProfileList newprofiles;
1230
1231                 ConfigTagList tags = ServerInstance->Config->ConfTags("sslprofile");
1232                 if (tags.first == tags.second)
1233                 {
1234                         // No <sslprofile> tags found, create a profile named "gnutls" from settings in the <gnutls> block
1235                         const std::string defname = "gnutls";
1236                         ConfigTag* tag = ServerInstance->Config->ConfValue(defname);
1237                         ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "No <sslprofile> tags found; using settings from the <gnutls> tag");
1238
1239                         try
1240                         {
1241                                 reference<GnuTLS::Profile> profile(GnuTLS::Profile::Create(defname, tag));
1242                                 newprofiles.push_back(new GnuTLSIOHookProvider(this, profile));
1243                         }
1244                         catch (CoreException& ex)
1245                         {
1246                                 throw ModuleException("Error while initializing the default SSL profile - " + ex.GetReason());
1247                         }
1248                 }
1249
1250                 for (ConfigIter i = tags.first; i != tags.second; ++i)
1251                 {
1252                         ConfigTag* tag = i->second;
1253                         if (tag->getString("provider") != "gnutls")
1254                                 continue;
1255
1256                         std::string name = tag->getString("name");
1257                         if (name.empty())
1258                         {
1259                                 ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Ignoring <sslprofile> tag without name at " + tag->getTagLocation());
1260                                 continue;
1261                         }
1262
1263                         reference<GnuTLS::Profile> profile;
1264                         try
1265                         {
1266                                 profile = GnuTLS::Profile::Create(name, tag);
1267                         }
1268                         catch (CoreException& ex)
1269                         {
1270                                 throw ModuleException("Error while initializing SSL profile \"" + name + "\" at " + tag->getTagLocation() + " - " + ex.GetReason());
1271                         }
1272
1273                         newprofiles.push_back(new GnuTLSIOHookProvider(this, profile));
1274                 }
1275
1276                 // New profiles are ok, begin using them
1277                 // Old profiles are deleted when their refcount drops to zero
1278                 profiles.swap(newprofiles);
1279         }
1280
1281  public:
1282         ModuleSSLGnuTLS()
1283         {
1284 #ifndef GNUTLS_HAS_RND
1285                 gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
1286 #endif
1287                 thismod = this;
1288         }
1289
1290         void init() CXX11_OVERRIDE
1291         {
1292                 ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "GnuTLS lib version %s module was compiled for " GNUTLS_VERSION, gnutls_check_version(NULL));
1293                 ReadProfiles();
1294                 ServerInstance->GenRandom = &randhandler;
1295         }
1296
1297         void OnModuleRehash(User* user, const std::string &param) CXX11_OVERRIDE
1298         {
1299                 if(param != "ssl")
1300                         return;
1301
1302                 try
1303                 {
1304                         ReadProfiles();
1305                 }
1306                 catch (ModuleException& ex)
1307                 {
1308                         ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, ex.GetReason() + " Not applying settings.");
1309                 }
1310         }
1311
1312         ~ModuleSSLGnuTLS()
1313         {
1314                 ServerInstance->GenRandom = &ServerInstance->HandleGenRandom;
1315         }
1316
1317         void OnCleanup(int target_type, void* item) CXX11_OVERRIDE
1318         {
1319                 if(target_type == TYPE_USER)
1320                 {
1321                         LocalUser* user = IS_LOCAL(static_cast<User*>(item));
1322
1323                         if ((user) && (user->eh.GetModHook(this)))
1324                         {
1325                                 // User is using SSL, they're a local user, and they're using one of *our* SSL ports.
1326                                 // Potentially there could be multiple SSL modules loaded at once on different ports.
1327                                 ServerInstance->Users->QuitUser(user, "SSL module unloading");
1328                         }
1329                 }
1330         }
1331
1332         Version GetVersion() CXX11_OVERRIDE
1333         {
1334                 return Version("Provides SSL support for clients", VF_VENDOR);
1335         }
1336
1337         ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE
1338         {
1339                 const GnuTLSIOHook* const iohook = static_cast<GnuTLSIOHook*>(user->eh.GetModHook(this));
1340                 if ((iohook) && (!iohook->IsHandshakeDone()))
1341                         return MOD_RES_DENY;
1342                 return MOD_RES_PASSTHRU;
1343         }
1344 };
1345
1346 MODULE_INIT(ModuleSSLGnuTLS)