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