]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/extra/m_ssl_gnutls.cpp
462209e010465256a07d90409d695bdba88d41b0
[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 # pragma GCC diagnostic ignored "-pedantic"
33 #endif
34
35 #include <gnutls/gnutls.h>
36 #include <gnutls/x509.h>
37
38 #if ((GNUTLS_VERSION_MAJOR > 2) || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR > 9) || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR == 9 && GNUTLS_VERSION_PATCH >= 8))
39 #define GNUTLS_HAS_MAC_GET_ID
40 #include <gnutls/crypto.h>
41 #endif
42
43 #if (GNUTLS_VERSION_MAJOR > 2 || GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR > 12)
44 # define GNUTLS_HAS_RND
45 #else
46 # include <gcrypt.h>
47 #endif
48
49 #ifdef _WIN32
50 # pragma comment(lib, "libgnutls.lib")
51 # pragma comment(lib, "libgcrypt.lib")
52 # pragma comment(lib, "libgpg-error.lib")
53 # pragma comment(lib, "user32.lib")
54 # pragma comment(lib, "advapi32.lib")
55 # pragma comment(lib, "libgcc.lib")
56 # pragma comment(lib, "libmingwex.lib")
57 # pragma comment(lib, "gdi32.lib")
58 #endif
59
60 /* $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'") */
61 /* $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'") */
62
63 #ifndef GNUTLS_VERSION_MAJOR
64 #define GNUTLS_VERSION_MAJOR LIBGNUTLS_VERSION_MAJOR
65 #define GNUTLS_VERSION_MINOR LIBGNUTLS_VERSION_MINOR
66 #define GNUTLS_VERSION_PATCH LIBGNUTLS_VERSION_PATCH
67 #endif
68
69 // These don't exist in older GnuTLS versions
70 #if ((GNUTLS_VERSION_MAJOR > 2) || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR > 1) || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR == 1 && GNUTLS_VERSION_PATCH >= 7))
71 #define GNUTLS_NEW_PRIO_API
72 #endif
73
74 #if(GNUTLS_VERSION_MAJOR < 2)
75 typedef gnutls_certificate_credentials_t gnutls_certificate_credentials;
76 typedef gnutls_dh_params_t gnutls_dh_params;
77 #endif
78
79 enum issl_status { ISSL_NONE, ISSL_HANDSHAKING_READ, ISSL_HANDSHAKING_WRITE, ISSL_HANDSHAKEN, ISSL_CLOSING, ISSL_CLOSED };
80
81 #if (GNUTLS_VERSION_MAJOR > 2 || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR >= 12))
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 class RandGen : public HandlerBase2<void, char*, size_t>
89 {
90  public:
91         void Call(char* buffer, size_t len)
92         {
93 #ifdef GNUTLS_HAS_RND
94                 gnutls_rnd(GNUTLS_RND_RANDOM, buffer, len);
95 #else
96                 gcry_randomize(buffer, len, GCRY_STRONG_RANDOM);
97 #endif
98         }
99 };
100
101 namespace GnuTLS
102 {
103         class Init
104         {
105          public:
106                 Init() { gnutls_global_init(); }
107                 ~Init() { gnutls_global_deinit(); }
108         };
109
110         class Exception : public ModuleException
111         {
112          public:
113                 Exception(const std::string& reason)
114                         : ModuleException(reason) { }
115         };
116
117         void ThrowOnError(int errcode, const char* msg)
118         {
119                 if (errcode < 0)
120                 {
121                         std::string reason = msg;
122                         reason.append(" :").append(gnutls_strerror(errcode));
123                         throw Exception(reason);
124                 }
125         }
126
127         /** Used to create a gnutls_datum_t* from a std::string
128          */
129         class Datum
130         {
131                 gnutls_datum_t datum;
132
133          public:
134                 Datum(const std::string& dat)
135                 {
136                         datum.data = (unsigned char*)dat.data();
137                         datum.size = static_cast<unsigned int>(dat.length());
138                 }
139
140                 const gnutls_datum_t* get() const { return &datum; }
141         };
142
143         class Hash
144         {
145                 gnutls_digest_algorithm_t hash;
146
147          public:
148                 // Nothing to deallocate, constructor may throw freely
149                 Hash(const std::string& hashname)
150                 {
151                         // As older versions of gnutls can't do this, let's disable it where needed.
152 #ifdef GNUTLS_HAS_MAC_GET_ID
153                         // As gnutls_digest_algorithm_t and gnutls_mac_algorithm_t are mapped 1:1, we can do this
154                         // There is no gnutls_dig_get_id() at the moment, but it may come later
155                         hash = (gnutls_digest_algorithm_t)gnutls_mac_get_id(hashname.c_str());
156                         if (hash == GNUTLS_DIG_UNKNOWN)
157                                 throw Exception("Unknown hash type " + hashname);
158
159                         // Check if the user is giving us something that is a valid MAC but not digest
160                         gnutls_hash_hd_t is_digest;
161                         if (gnutls_hash_init(&is_digest, hash) < 0)
162                                 throw Exception("Unknown hash type " + hashname);
163                         gnutls_hash_deinit(is_digest, NULL);
164 #else
165                         if (hashname == "md5")
166                                 hash = GNUTLS_DIG_MD5;
167                         else if (hashname == "sha1")
168                                 hash = GNUTLS_DIG_SHA1;
169                         else
170                                 throw Exception("Unknown hash type " + hashname);
171 #endif
172                 }
173
174                 gnutls_digest_algorithm_t get() const { return hash; }
175         };
176
177         class DHParams
178         {
179                 gnutls_dh_params_t dh_params;
180
181                 DHParams()
182                 {
183                         ThrowOnError(gnutls_dh_params_init(&dh_params), "gnutls_dh_params_init() failed");
184                 }
185
186          public:
187                 /** Import */
188                 static std::auto_ptr<DHParams> Import(const std::string& dhstr)
189                 {
190                         std::auto_ptr<DHParams> dh(new DHParams);
191                         int ret = gnutls_dh_params_import_pkcs3(dh->dh_params, Datum(dhstr).get(), GNUTLS_X509_FMT_PEM);
192                         ThrowOnError(ret, "Unable to import DH params");
193                         return dh;
194                 }
195
196                 /** Generate */
197                 static std::auto_ptr<DHParams> Generate(unsigned int bits)
198                 {
199                         std::auto_ptr<DHParams> dh(new DHParams);
200                         ThrowOnError(gnutls_dh_params_generate2(dh->dh_params, bits), "Unable to generate DH params");
201                         return dh;
202                 }
203
204                 ~DHParams()
205                 {
206                         gnutls_dh_params_deinit(dh_params);
207                 }
208
209                 const gnutls_dh_params_t& get() const { return dh_params; }
210         };
211
212         class X509Key
213         {
214                 /** Ensure that the key is deinited in case the constructor of X509Key throws
215                  */
216                 class RAIIKey
217                 {
218                  public:
219                         gnutls_x509_privkey_t key;
220
221                         RAIIKey()
222                         {
223                                 ThrowOnError(gnutls_x509_privkey_init(&key), "gnutls_x509_privkey_init() failed");
224                         }
225
226                         ~RAIIKey()
227                         {
228                                 gnutls_x509_privkey_deinit(key);
229                         }
230                 } key;
231
232          public:
233                 /** Import */
234                 X509Key(const std::string& keystr)
235                 {
236                         int ret = gnutls_x509_privkey_import(key.key, Datum(keystr).get(), GNUTLS_X509_FMT_PEM);
237                         ThrowOnError(ret, "Unable to import private key");
238                 }
239
240                 gnutls_x509_privkey_t& get() { return key.key; }
241         };
242
243         class X509CertList
244         {
245                 std::vector<gnutls_x509_crt_t> certs;
246
247          public:
248                 /** Import */
249                 X509CertList(const std::string& certstr)
250                 {
251                         unsigned int certcount = 3;
252                         certs.resize(certcount);
253                         Datum datum(certstr);
254
255                         int ret = gnutls_x509_crt_list_import(raw(), &certcount, datum.get(), GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED);
256                         if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER)
257                         {
258                                 // the buffer wasn't big enough to hold all certs but gnutls changed certcount to the number of available certs,
259                                 // try again with a bigger buffer
260                                 certs.resize(certcount);
261                                 ret = gnutls_x509_crt_list_import(raw(), &certcount, datum.get(), GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED);
262                         }
263
264                         ThrowOnError(ret, "Unable to load certificates");
265
266                         // Resize the vector to the actual number of certs because we rely on its size being correct
267                         // when deallocating the certs
268                         certs.resize(certcount);
269                 }
270
271                 ~X509CertList()
272                 {
273                         for (std::vector<gnutls_x509_crt_t>::iterator i = certs.begin(); i != certs.end(); ++i)
274                                 gnutls_x509_crt_deinit(*i);
275                 }
276
277                 gnutls_x509_crt_t* raw() { return &certs[0]; }
278                 unsigned int size() const { return certs.size(); }
279         };
280
281         class X509CRL : public refcountbase
282         {
283                 class RAIICRL
284                 {
285                  public:
286                         gnutls_x509_crl_t crl;
287
288                         RAIICRL()
289                         {
290                                 ThrowOnError(gnutls_x509_crl_init(&crl), "gnutls_x509_crl_init() failed");
291                         }
292
293                         ~RAIICRL()
294                         {
295                                 gnutls_x509_crl_deinit(crl);
296                         }
297                 } crl;
298
299          public:
300                 /** Import */
301                 X509CRL(const std::string& crlstr)
302                 {
303                         int ret = gnutls_x509_crl_import(get(), Datum(crlstr).get(), GNUTLS_X509_FMT_PEM);
304                         ThrowOnError(ret, "Unable to load certificate revocation list");
305                 }
306
307                 gnutls_x509_crl_t& get() { return crl.crl; }
308         };
309
310 #ifdef GNUTLS_NEW_PRIO_API
311         class Priority
312         {
313                 gnutls_priority_t priority;
314
315          public:
316                 Priority(const std::string& priorities)
317                 {
318                         // Try to set the priorities for ciphers, kex methods etc. to the user supplied string
319                         // If the user did not supply anything then the string is already set to "NORMAL"
320                         const char* priocstr = priorities.c_str();
321                         const char* prioerror;
322
323                         int ret = gnutls_priority_init(&priority, priocstr, &prioerror);
324                         if (ret < 0)
325                         {
326                                 // gnutls did not understand the user supplied string
327                                 throw Exception("Unable to initialize priorities to \"" + priorities + "\": " + gnutls_strerror(ret) + " Syntax error at position " + ConvToStr((unsigned int) (prioerror - priocstr)));
328                         }
329                 }
330
331                 ~Priority()
332                 {
333                         gnutls_priority_deinit(priority);
334                 }
335
336                 void SetupSession(gnutls_session_t sess)
337                 {
338                         gnutls_priority_set(sess, priority);
339                 }
340         };
341 #else
342         /** Dummy class, used when gnutls_priority_set() is not available
343          */
344         class Priority
345         {
346          public:
347                 Priority(const std::string& priorities)
348                 {
349                         if (priorities != "NORMAL")
350                                 throw Exception("You've set a non-default priority string, but GnuTLS lacks support for it");
351                 }
352
353                 static void SetupSession(gnutls_session_t sess)
354                 {
355                         // Always set the default priorities
356                         gnutls_set_default_priority(sess);
357                 }
358         };
359 #endif
360
361         class CertCredentials
362         {
363                 /** DH parameters associated with these credentials
364                  */
365                 std::auto_ptr<DHParams> dh;
366
367          protected:
368                 gnutls_certificate_credentials_t cred;
369
370          public:
371                 CertCredentials()
372                 {
373                         ThrowOnError(gnutls_certificate_allocate_credentials(&cred), "Cannot allocate certificate credentials");
374                 }
375
376                 ~CertCredentials()
377                 {
378                         gnutls_certificate_free_credentials(cred);
379                 }
380
381                 /** Associates these credentials with the session
382                  */
383                 void SetupSession(gnutls_session_t sess)
384                 {
385                         gnutls_credentials_set(sess, GNUTLS_CRD_CERTIFICATE, cred);
386                 }
387
388                 /** Set the given DH parameters to be used with these credentials
389                  */
390                 void SetDH(std::auto_ptr<DHParams>& DH)
391                 {
392                         dh = DH;
393                         gnutls_certificate_set_dh_params(cred, dh->get());
394                 }
395         };
396
397         class X509Credentials : public CertCredentials
398         {
399                 /** Private key
400                  */
401                 X509Key key;
402
403                 /** Certificate list, presented to the peer
404                  */
405                 X509CertList certs;
406
407                 /** Trusted CA, may be NULL
408                  */
409                 std::auto_ptr<X509CertList> trustedca;
410
411                 /** Certificate revocation list, may be NULL
412                  */
413                 std::auto_ptr<X509CRL> crl;
414
415                 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);
416
417          public:
418                 X509Credentials(const std::string& certstr, const std::string& keystr)
419                         : key(keystr)
420                         , certs(certstr)
421                 {
422                         // Throwing is ok here, the destructor of Credentials is called in that case
423                         int ret = gnutls_certificate_set_x509_key(cred, certs.raw(), certs.size(), key.get());
424                         ThrowOnError(ret, "Unable to set cert/key pair");
425
426 #ifdef GNUTLS_NEW_CERT_CALLBACK_API
427                         gnutls_certificate_set_retrieve_function(cred, cert_callback);
428 #else
429                         gnutls_certificate_client_set_retrieve_function(cred, cert_callback);
430 #endif
431                 }
432
433                 /** Sets the trusted CA and the certificate revocation list
434                  * to use when verifying certificates
435                  */
436                 void SetCA(std::auto_ptr<X509CertList>& certlist, std::auto_ptr<X509CRL>& CRL)
437                 {
438                         // Do nothing if certlist is NULL
439                         if (certlist.get())
440                         {
441                                 int ret = gnutls_certificate_set_x509_trust(cred, certlist->raw(), certlist->size());
442                                 ThrowOnError(ret, "gnutls_certificate_set_x509_trust() failed");
443
444                                 if (CRL.get())
445                                 {
446                                         ret = gnutls_certificate_set_x509_crl(cred, &CRL->get(), 1);
447                                         ThrowOnError(ret, "gnutls_certificate_set_x509_crl() failed");
448                                 }
449
450                                 trustedca = certlist;
451                                 crl = CRL;
452                         }
453                 }
454         };
455
456         class Profile : public refcountbase
457         {
458                 /** Name of this profile
459                  */
460                 const std::string name;
461
462                 /** X509 certificate(s) and key
463                  */
464                 X509Credentials x509cred;
465
466                 /** The minimum length in bits for the DH prime to be accepted as a client
467                  */
468                 unsigned int min_dh_bits;
469
470                 /** Hashing algorithm to use when generating certificate fingerprints
471                  */
472                 Hash hash;
473
474                 /** Priorities for ciphers, compression methods, etc.
475                  */
476                 Priority priority;
477
478                 Profile(const std::string& profilename, const std::string& certstr, const std::string& keystr,
479                                 std::auto_ptr<DHParams>& DH, unsigned int mindh, const std::string& hashstr,
480                                 const std::string& priostr, std::auto_ptr<X509CertList>& CA, std::auto_ptr<X509CRL>& CRL)
481                         : name(profilename)
482                         , x509cred(certstr, keystr)
483                         , min_dh_bits(mindh)
484                         , hash(hashstr)
485                         , priority(priostr)
486                 {
487                         x509cred.SetDH(DH);
488                         x509cred.SetCA(CA, CRL);
489                 }
490
491                 static std::string ReadFile(const std::string& filename)
492                 {
493                         FileReader reader(filename);
494                         std::string ret = reader.GetString();
495                         if (ret.empty())
496                                 throw Exception("Cannot read file " + filename);
497                         return ret;
498                 }
499
500          public:
501                 static reference<Profile> Create(const std::string& profilename, ConfigTag* tag)
502                 {
503                         std::string certstr = ReadFile(tag->getString("certfile", "cert.pem"));
504                         std::string keystr = ReadFile(tag->getString("keyfile", "key.pem"));
505
506                         std::auto_ptr<DHParams> dh;
507                         int gendh = tag->getInt("gendh");
508                         if (gendh)
509                         {
510                                 gendh = (gendh < 1024 ? 1024 : gendh);
511                                 dh = DHParams::Generate(gendh);
512                         }
513                         else
514                                 dh = DHParams::Import(ReadFile(tag->getString("dhfile", "dhparams.pem")));
515
516                         // Use default priority string if this tag does not specify one
517                         std::string priostr = tag->getString("priority", "NORMAL");
518                         unsigned int mindh = tag->getInt("mindhbits", 1024);
519                         std::string hashstr = tag->getString("hash", "md5");
520
521                         // Load trusted CA and revocation list, if set
522                         std::auto_ptr<X509CertList> ca;
523                         std::auto_ptr<X509CRL> crl;
524                         std::string filename = tag->getString("cafile");
525                         if (!filename.empty())
526                         {
527                                 ca.reset(new X509CertList(ReadFile(filename)));
528
529                                 filename = tag->getString("crlfile");
530                                 if (!filename.empty())
531                                         crl.reset(new X509CRL(ReadFile(filename)));
532                         }
533
534                         return new Profile(profilename, certstr, keystr, dh, mindh, hashstr, priostr, ca, crl);
535                 }
536
537                 /** Set up the given session with the settings in this profile
538                  */
539                 void SetupSession(gnutls_session_t sess)
540                 {
541                         priority.SetupSession(sess);
542                         x509cred.SetupSession(sess);
543                         gnutls_dh_set_prime_bits(sess, min_dh_bits);
544                 }
545
546                 const std::string& GetName() const { return name; }
547                 X509Credentials& GetX509Credentials() { return x509cred; }
548                 gnutls_digest_algorithm_t GetHash() const { return hash.get(); }
549         };
550 }
551
552 class GnuTLSIOHook : public SSLIOHook
553 {
554  private:
555         gnutls_session_t sess;
556         issl_status status;
557         reference<GnuTLS::Profile> profile;
558
559         void InitSession(StreamSocket* user, bool me_server)
560         {
561                 gnutls_init(&sess, me_server ? GNUTLS_SERVER : GNUTLS_CLIENT);
562
563                 profile->SetupSession(sess);
564                 gnutls_transport_set_ptr(sess, reinterpret_cast<gnutls_transport_ptr_t>(user));
565                 gnutls_transport_set_push_function(sess, gnutls_push_wrapper);
566                 gnutls_transport_set_pull_function(sess, gnutls_pull_wrapper);
567
568                 if (me_server)
569                         gnutls_certificate_server_set_request(sess, GNUTLS_CERT_REQUEST); // Request client certificate if any.
570         }
571
572         void CloseSession()
573         {
574                 if (this->sess)
575                 {
576                         gnutls_bye(this->sess, GNUTLS_SHUT_WR);
577                         gnutls_deinit(this->sess);
578                 }
579                 sess = NULL;
580                 certificate = NULL;
581                 status = ISSL_NONE;
582         }
583
584         bool Handshake(StreamSocket* user)
585         {
586                 int ret = gnutls_handshake(this->sess);
587
588                 if (ret < 0)
589                 {
590                         if(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
591                         {
592                                 // Handshake needs resuming later, read() or write() would have blocked.
593
594                                 if (gnutls_record_get_direction(this->sess) == 0)
595                                 {
596                                         // gnutls_handshake() wants to read() again.
597                                         this->status = ISSL_HANDSHAKING_READ;
598                                         SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
599                                 }
600                                 else
601                                 {
602                                         // gnutls_handshake() wants to write() again.
603                                         this->status = ISSL_HANDSHAKING_WRITE;
604                                         SocketEngine::ChangeEventMask(user, FD_WANT_NO_READ | FD_WANT_SINGLE_WRITE);
605                                 }
606                         }
607                         else
608                         {
609                                 user->SetError("Handshake Failed - " + std::string(gnutls_strerror(ret)));
610                                 CloseSession();
611                                 this->status = ISSL_CLOSING;
612                         }
613
614                         return false;
615                 }
616                 else
617                 {
618                         // Change the seesion state
619                         this->status = ISSL_HANDSHAKEN;
620
621                         VerifyCertificate();
622
623                         // Finish writing, if any left
624                         SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE | FD_ADD_TRIAL_WRITE);
625
626                         return true;
627                 }
628         }
629
630         void VerifyCertificate()
631         {
632                 unsigned int certstatus;
633                 const gnutls_datum_t* cert_list;
634                 int ret;
635                 unsigned int cert_list_size;
636                 gnutls_x509_crt_t cert;
637                 char str[512];
638                 unsigned char digest[512];
639                 size_t digest_size = sizeof(digest);
640                 size_t name_size = sizeof(str);
641                 ssl_cert* certinfo = new ssl_cert;
642                 this->certificate = certinfo;
643
644                 /* This verification function uses the trusted CAs in the credentials
645                  * structure. So you must have installed one or more CA certificates.
646                  */
647                 ret = gnutls_certificate_verify_peers2(this->sess, &certstatus);
648
649                 if (ret < 0)
650                 {
651                         certinfo->error = std::string(gnutls_strerror(ret));
652                         return;
653                 }
654
655                 certinfo->invalid = (certstatus & GNUTLS_CERT_INVALID);
656                 certinfo->unknownsigner = (certstatus & GNUTLS_CERT_SIGNER_NOT_FOUND);
657                 certinfo->revoked = (certstatus & GNUTLS_CERT_REVOKED);
658                 certinfo->trusted = !(certstatus & GNUTLS_CERT_SIGNER_NOT_CA);
659
660                 /* Up to here the process is the same for X.509 certificates and
661                  * OpenPGP keys. From now on X.509 certificates are assumed. This can
662                  * be easily extended to work with openpgp keys as well.
663                  */
664                 if (gnutls_certificate_type_get(this->sess) != GNUTLS_CRT_X509)
665                 {
666                         certinfo->error = "No X509 keys sent";
667                         return;
668                 }
669
670                 ret = gnutls_x509_crt_init(&cert);
671                 if (ret < 0)
672                 {
673                         certinfo->error = gnutls_strerror(ret);
674                         return;
675                 }
676
677                 cert_list_size = 0;
678                 cert_list = gnutls_certificate_get_peers(this->sess, &cert_list_size);
679                 if (cert_list == NULL)
680                 {
681                         certinfo->error = "No certificate was found";
682                         goto info_done_dealloc;
683                 }
684
685                 /* This is not a real world example, since we only check the first
686                  * certificate in the given chain.
687                  */
688
689                 ret = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER);
690                 if (ret < 0)
691                 {
692                         certinfo->error = gnutls_strerror(ret);
693                         goto info_done_dealloc;
694                 }
695
696                 gnutls_x509_crt_get_dn(cert, str, &name_size);
697                 certinfo->dn = str;
698
699                 gnutls_x509_crt_get_issuer_dn(cert, str, &name_size);
700                 certinfo->issuer = str;
701
702                 if ((ret = gnutls_x509_crt_get_fingerprint(cert, profile->GetHash(), digest, &digest_size)) < 0)
703                 {
704                         certinfo->error = gnutls_strerror(ret);
705                 }
706                 else
707                 {
708                         certinfo->fingerprint = BinToHex(digest, digest_size);
709                 }
710
711                 /* Beware here we do not check for errors.
712                  */
713                 if ((gnutls_x509_crt_get_expiration_time(cert) < ServerInstance->Time()) || (gnutls_x509_crt_get_activation_time(cert) > ServerInstance->Time()))
714                 {
715                         certinfo->error = "Not activated, or expired certificate";
716                 }
717
718 info_done_dealloc:
719                 gnutls_x509_crt_deinit(cert);
720         }
721
722         static const char* UnknownIfNULL(const char* str)
723         {
724                 return str ? str : "UNKNOWN";
725         }
726
727         static ssize_t gnutls_pull_wrapper(gnutls_transport_ptr_t session_wrap, void* buffer, size_t size)
728         {
729                 StreamSocket* sock = reinterpret_cast<StreamSocket*>(session_wrap);
730 #ifdef _WIN32
731                 GnuTLSIOHook* session = static_cast<GnuTLSIOHook*>(sock->GetIOHook());
732 #endif
733
734                 if (sock->GetEventMask() & FD_READ_WILL_BLOCK)
735                 {
736 #ifdef _WIN32
737                         gnutls_transport_set_errno(session->sess, EAGAIN);
738 #else
739                         errno = EAGAIN;
740 #endif
741                         return -1;
742                 }
743
744                 int rv = SocketEngine::Recv(sock, reinterpret_cast<char *>(buffer), size, 0);
745
746 #ifdef _WIN32
747                 if (rv < 0)
748                 {
749                         /* Windows doesn't use errno, but gnutls does, so check SocketEngine::IgnoreError()
750                          * and then set errno appropriately.
751                          * The gnutls library may also have a different errno variable than us, see
752                          * gnutls_transport_set_errno(3).
753                          */
754                         gnutls_transport_set_errno(session->sess, SocketEngine::IgnoreError() ? EAGAIN : errno);
755                 }
756 #endif
757
758                 if (rv < (int)size)
759                         SocketEngine::ChangeEventMask(sock, FD_READ_WILL_BLOCK);
760                 return rv;
761         }
762
763         static ssize_t gnutls_push_wrapper(gnutls_transport_ptr_t session_wrap, const void* buffer, size_t size)
764         {
765                 StreamSocket* sock = reinterpret_cast<StreamSocket*>(session_wrap);
766 #ifdef _WIN32
767                 GnuTLSIOHook* session = static_cast<GnuTLSIOHook*>(sock->GetIOHook());
768 #endif
769
770                 if (sock->GetEventMask() & FD_WRITE_WILL_BLOCK)
771                 {
772 #ifdef _WIN32
773                         gnutls_transport_set_errno(session->sess, EAGAIN);
774 #else
775                         errno = EAGAIN;
776 #endif
777                         return -1;
778                 }
779
780                 int rv = SocketEngine::Send(sock, reinterpret_cast<const char *>(buffer), size, 0);
781
782 #ifdef _WIN32
783                 if (rv < 0)
784                 {
785                         /* Windows doesn't use errno, but gnutls does, so check SocketEngine::IgnoreError()
786                          * and then set errno appropriately.
787                          * The gnutls library may also have a different errno variable than us, see
788                          * gnutls_transport_set_errno(3).
789                          */
790                         gnutls_transport_set_errno(session->sess, SocketEngine::IgnoreError() ? EAGAIN : errno);
791                 }
792 #endif
793
794                 if (rv < (int)size)
795                         SocketEngine::ChangeEventMask(sock, FD_WRITE_WILL_BLOCK);
796                 return rv;
797         }
798
799  public:
800         GnuTLSIOHook(IOHookProvider* hookprov, StreamSocket* sock, bool outbound, const reference<GnuTLS::Profile>& sslprofile)
801                 : SSLIOHook(hookprov)
802                 , sess(NULL)
803                 , status(ISSL_NONE)
804                 , profile(sslprofile)
805         {
806                 InitSession(sock, outbound);
807                 sock->AddIOHook(this);
808                 Handshake(sock);
809         }
810
811         void OnStreamSocketClose(StreamSocket* user) CXX11_OVERRIDE
812         {
813                 CloseSession();
814         }
815
816         int OnStreamSocketRead(StreamSocket* user, std::string& recvq) CXX11_OVERRIDE
817         {
818                 if (!this->sess)
819                 {
820                         CloseSession();
821                         user->SetError("No SSL session");
822                         return -1;
823                 }
824
825                 if (this->status == ISSL_HANDSHAKING_READ || this->status == ISSL_HANDSHAKING_WRITE)
826                 {
827                         // The handshake isn't finished, try to finish it.
828
829                         if (!Handshake(user))
830                         {
831                                 if (this->status != ISSL_CLOSING)
832                                         return 0;
833                                 return -1;
834                         }
835                 }
836
837                 // If we resumed the handshake then this->status will be ISSL_HANDSHAKEN.
838
839                 if (this->status == ISSL_HANDSHAKEN)
840                 {
841                         char* buffer = ServerInstance->GetReadBuffer();
842                         size_t bufsiz = ServerInstance->Config->NetBufferSize;
843                         int ret = gnutls_record_recv(this->sess, buffer, bufsiz);
844                         if (ret > 0)
845                         {
846                                 recvq.append(buffer, ret);
847                                 return 1;
848                         }
849                         else if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
850                         {
851                                 return 0;
852                         }
853                         else if (ret == 0)
854                         {
855                                 user->SetError("Connection closed");
856                                 CloseSession();
857                                 return -1;
858                         }
859                         else
860                         {
861                                 user->SetError(gnutls_strerror(ret));
862                                 CloseSession();
863                                 return -1;
864                         }
865                 }
866                 else if (this->status == ISSL_CLOSING)
867                         return -1;
868
869                 return 0;
870         }
871
872         int OnStreamSocketWrite(StreamSocket* user, std::string& sendq) CXX11_OVERRIDE
873         {
874                 if (!this->sess)
875                 {
876                         CloseSession();
877                         user->SetError("No SSL session");
878                         return -1;
879                 }
880
881                 if (this->status == ISSL_HANDSHAKING_WRITE || this->status == ISSL_HANDSHAKING_READ)
882                 {
883                         // The handshake isn't finished, try to finish it.
884                         Handshake(user);
885                         if (this->status != ISSL_CLOSING)
886                                 return 0;
887                         return -1;
888                 }
889
890                 int ret = 0;
891
892                 if (this->status == ISSL_HANDSHAKEN)
893                 {
894                         ret = gnutls_record_send(this->sess, sendq.data(), sendq.length());
895
896                         if (ret == (int)sendq.length())
897                         {
898                                 SocketEngine::ChangeEventMask(user, FD_WANT_NO_WRITE);
899                                 return 1;
900                         }
901                         else if (ret > 0)
902                         {
903                                 sendq = sendq.substr(ret);
904                                 SocketEngine::ChangeEventMask(user, FD_WANT_SINGLE_WRITE);
905                                 return 0;
906                         }
907                         else if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED || ret == 0)
908                         {
909                                 SocketEngine::ChangeEventMask(user, FD_WANT_SINGLE_WRITE);
910                                 return 0;
911                         }
912                         else // (ret < 0)
913                         {
914                                 user->SetError(gnutls_strerror(ret));
915                                 CloseSession();
916                                 return -1;
917                         }
918                 }
919
920                 return 0;
921         }
922
923         void TellCiphersAndFingerprint(LocalUser* user)
924         {
925                 if (sess)
926                 {
927                         std::string text = "*** You are connected using SSL cipher '";
928
929                         text += UnknownIfNULL(gnutls_kx_get_name(gnutls_kx_get(sess)));
930                         text.append("-").append(UnknownIfNULL(gnutls_cipher_get_name(gnutls_cipher_get(sess)))).append("-");
931                         text.append(UnknownIfNULL(gnutls_mac_get_name(gnutls_mac_get(sess)))).append("'");
932
933                         if (!certificate->fingerprint.empty())
934                                 text += " and your SSL fingerprint is " + certificate->fingerprint;
935
936                         user->WriteNotice(text);
937                 }
938         }
939
940         GnuTLS::Profile* GetProfile() { return profile; }
941 };
942
943 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)
944 {
945 #ifndef GNUTLS_NEW_CERT_CALLBACK_API
946         st->type = GNUTLS_CRT_X509;
947 #else
948         st->cert_type = GNUTLS_CRT_X509;
949         st->key_type = GNUTLS_PRIVKEY_X509;
950 #endif
951         StreamSocket* sock = reinterpret_cast<StreamSocket*>(gnutls_transport_get_ptr(sess));
952         GnuTLS::X509Credentials& cred = static_cast<GnuTLSIOHook*>(sock->GetIOHook())->GetProfile()->GetX509Credentials();
953
954         st->ncerts = cred.certs.size();
955         st->cert.x509 = cred.certs.raw();
956         st->key.x509 = cred.key.get();
957         st->deinit_all = 0;
958
959         return 0;
960 }
961
962 class GnuTLSIOHookProvider : public refcountbase, public IOHookProvider
963 {
964         reference<GnuTLS::Profile> profile;
965
966  public:
967         GnuTLSIOHookProvider(Module* mod, reference<GnuTLS::Profile>& prof)
968                 : IOHookProvider(mod, "ssl/" + prof->GetName(), IOHookProvider::IOH_SSL)
969                 , profile(prof)
970         {
971                 ServerInstance->Modules->AddService(*this);
972         }
973
974         ~GnuTLSIOHookProvider()
975         {
976                 ServerInstance->Modules->DelService(*this);
977         }
978
979         void OnAccept(StreamSocket* sock, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE
980         {
981                 new GnuTLSIOHook(this, sock, true, profile);
982         }
983
984         void OnConnect(StreamSocket* sock) CXX11_OVERRIDE
985         {
986                 new GnuTLSIOHook(this, sock, false, profile);
987         }
988 };
989
990 class ModuleSSLGnuTLS : public Module
991 {
992         typedef std::vector<reference<GnuTLSIOHookProvider> > ProfileList;
993
994         // First member of the class, gets constructed first and destructed last
995         GnuTLS::Init libinit;
996         RandGen randhandler;
997         ProfileList profiles;
998
999         void ReadProfiles()
1000         {
1001                 // First, store all profiles in a new, temporary container. If no problems occur, swap the two
1002                 // containers; this way if something goes wrong we can go back and continue using the current profiles,
1003                 // avoiding unpleasant situations where no new SSL connections are possible.
1004                 ProfileList newprofiles;
1005
1006                 ConfigTagList tags = ServerInstance->Config->ConfTags("sslprofile");
1007                 if (tags.first == tags.second)
1008                 {
1009                         // No <sslprofile> tags found, create a profile named "gnutls" from settings in the <gnutls> block
1010                         const std::string defname = "gnutls";
1011                         ConfigTag* tag = ServerInstance->Config->ConfValue(defname);
1012                         ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "No <sslprofile> tags found; using settings from the <gnutls> tag");
1013
1014                         try
1015                         {
1016                                 reference<GnuTLS::Profile> profile(GnuTLS::Profile::Create(defname, tag));
1017                                 newprofiles.push_back(new GnuTLSIOHookProvider(this, profile));
1018                         }
1019                         catch (CoreException& ex)
1020                         {
1021                                 throw ModuleException("Error while initializing the default SSL profile - " + ex.GetReason());
1022                         }
1023                 }
1024
1025                 for (ConfigIter i = tags.first; i != tags.second; ++i)
1026                 {
1027                         ConfigTag* tag = i->second;
1028                         if (tag->getString("provider") != "gnutls")
1029                                 continue;
1030
1031                         std::string name = tag->getString("name");
1032                         if (name.empty())
1033                         {
1034                                 ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Ignoring <sslprofile> tag without name at " + tag->getTagLocation());
1035                                 continue;
1036                         }
1037
1038                         reference<GnuTLS::Profile> profile;
1039                         try
1040                         {
1041                                 profile = GnuTLS::Profile::Create(name, tag);
1042                         }
1043                         catch (CoreException& ex)
1044                         {
1045                                 throw ModuleException("Error while initializing SSL profile \"" + name + "\" at " + tag->getTagLocation() + " - " + ex.GetReason());
1046                         }
1047
1048                         newprofiles.push_back(new GnuTLSIOHookProvider(this, profile));
1049                 }
1050
1051                 // New profiles are ok, begin using them
1052                 // Old profiles are deleted when their refcount drops to zero
1053                 profiles.swap(newprofiles);
1054         }
1055
1056  public:
1057         ModuleSSLGnuTLS()
1058         {
1059 #ifndef GNUTLS_HAS_RND
1060                 gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
1061 #endif
1062         }
1063
1064         void init() CXX11_OVERRIDE
1065         {
1066                 ReadProfiles();
1067                 ServerInstance->GenRandom = &randhandler;
1068         }
1069
1070         void OnModuleRehash(User* user, const std::string &param) CXX11_OVERRIDE
1071         {
1072                 if(param != "ssl")
1073                         return;
1074
1075                 try
1076                 {
1077                         ReadProfiles();
1078                 }
1079                 catch (ModuleException& ex)
1080                 {
1081                         ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, ex.GetReason() + " Not applying settings.");
1082                 }
1083         }
1084
1085         ~ModuleSSLGnuTLS()
1086         {
1087                 ServerInstance->GenRandom = &ServerInstance->HandleGenRandom;
1088         }
1089
1090         void OnCleanup(int target_type, void* item) CXX11_OVERRIDE
1091         {
1092                 if(target_type == TYPE_USER)
1093                 {
1094                         LocalUser* user = IS_LOCAL(static_cast<User*>(item));
1095
1096                         if (user && user->eh.GetIOHook() && user->eh.GetIOHook()->prov->creator == this)
1097                         {
1098                                 // User is using SSL, they're a local user, and they're using one of *our* SSL ports.
1099                                 // Potentially there could be multiple SSL modules loaded at once on different ports.
1100                                 ServerInstance->Users->QuitUser(user, "SSL module unloading");
1101                         }
1102                 }
1103         }
1104
1105         Version GetVersion() CXX11_OVERRIDE
1106         {
1107                 return Version("Provides SSL support for clients", VF_VENDOR);
1108         }
1109
1110         void OnUserConnect(LocalUser* user) CXX11_OVERRIDE
1111         {
1112                 IOHook* hook = user->eh.GetIOHook();
1113                 if (hook && hook->prov->creator == this)
1114                         static_cast<GnuTLSIOHook*>(hook)->TellCiphersAndFingerprint(user);
1115         }
1116 };
1117
1118 MODULE_INIT(ModuleSSLGnuTLS)