]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/extra/m_ssl_gnutls.cpp
Fix initialization of SSL certificate field on connect
[user/henk/code/inspircd.git] / src / modules / extra / m_ssl_gnutls.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  InspIRCd: (C) 2002-2010 InspIRCd Development Team
6  * See: http://wiki.inspircd.org/Credits
7  *
8  * This program is free but copyrighted software; see
9  *          the file COPYING for details.
10  *
11  * ---------------------------------------------------
12  */
13
14 #include "inspircd.h"
15 #include <gnutls/gnutls.h>
16 #include <gnutls/x509.h>
17 #include <gcrypt.h>
18 #include "ssl.h"
19 #include "m_cap.h"
20
21 /* $ModDesc: Provides SSL support for clients */
22 /* $CompileFlags: pkgconfincludes("gnutls","/gnutls/gnutls.h","") */
23 /* $LinkerFlags: rpath("pkg-config --libs gnutls") pkgconflibs("gnutls","/libgnutls.so","-lgnutls") */
24
25 enum issl_status { ISSL_NONE, ISSL_HANDSHAKING_READ, ISSL_HANDSHAKING_WRITE, ISSL_HANDSHAKEN, ISSL_CLOSING, ISSL_CLOSED };
26
27 static std::vector<gnutls_x509_crt_t> x509_certs;
28 static gnutls_x509_privkey_t x509_key;
29 static int cert_callback (gnutls_session_t session, const gnutls_datum_t * req_ca_rdn, int nreqs,
30         const gnutls_pk_algorithm_t * sign_algos, int sign_algos_length, gnutls_retr_st * st) {
31
32         st->type = GNUTLS_CRT_X509;
33         st->ncerts = x509_certs.size();
34         st->cert.x509 = &x509_certs[0];
35         st->key.x509 = x509_key;
36         st->deinit_all = 0;
37
38         return 0;
39 }
40
41 static ssize_t gnutls_pull_wrapper(gnutls_transport_ptr_t user_wrap, void* buffer, size_t size)
42 {
43         StreamSocket* user = reinterpret_cast<StreamSocket*>(user_wrap);
44         if (user->GetEventMask() & FD_READ_WILL_BLOCK)
45         {
46                 errno = EAGAIN;
47                 return -1;
48         }
49         int rv = recv(user->GetFd(), reinterpret_cast<char *>(buffer), size, 0);
50         if (rv < (int)size)
51                 ServerInstance->SE->ChangeEventMask(user, FD_READ_WILL_BLOCK);
52         return rv;
53 }
54
55 static ssize_t gnutls_push_wrapper(gnutls_transport_ptr_t user_wrap, const void* buffer, size_t size)
56 {
57         StreamSocket* user = reinterpret_cast<StreamSocket*>(user_wrap);
58         if (user->GetEventMask() & FD_WRITE_WILL_BLOCK)
59         {
60                 errno = EAGAIN;
61                 return -1;
62         }
63         int rv = send(user->GetFd(), reinterpret_cast<const char *>(buffer), size, 0);
64         if (rv < (int)size)
65                 ServerInstance->SE->ChangeEventMask(user, FD_WRITE_WILL_BLOCK);
66         return rv;
67 }
68
69 class RandGen : public HandlerBase2<void, char*, size_t>
70 {
71  public:
72         RandGen() {}
73         void Call(char* buffer, size_t len)
74         {
75                 gcry_randomize(buffer, len, GCRY_STRONG_RANDOM);
76         }
77 };
78
79 /** Represents an SSL user's extra data
80  */
81 class issl_session
82 {
83 public:
84         gnutls_session_t sess;
85         issl_status status;
86         reference<ssl_cert> cert;
87         issl_session() : sess(NULL) {}
88 };
89
90 class CommandStartTLS : public SplitCommand
91 {
92  public:
93         bool enabled;
94         CommandStartTLS (Module* mod) : SplitCommand(mod, "STARTTLS")
95         {
96                 enabled = true;
97                 works_before_reg = true;
98         }
99
100         CmdResult HandleLocal(const std::vector<std::string> &parameters, LocalUser *user)
101         {
102                 if (!enabled)
103                 {
104                         user->WriteNumeric(691, "%s :STARTTLS is not enabled", user->nick.c_str());
105                         return CMD_FAILURE;
106                 }
107
108                 if (user->registered == REG_ALL)
109                 {
110                         user->WriteNumeric(691, "%s :STARTTLS is not permitted after client registration is complete", user->nick.c_str());
111                 }
112                 else
113                 {
114                         if (!user->eh.GetIOHook())
115                         {
116                                 user->WriteNumeric(670, "%s :STARTTLS successful, go ahead with TLS handshake", user->nick.c_str());
117                                 /* We need to flush the write buffer prior to adding the IOHook,
118                                  * otherwise we'll be sending this line inside the SSL session - which
119                                  * won't start its handshake until the client gets this line. Currently,
120                                  * we assume the write will not block here; this is usually safe, as
121                                  * STARTTLS is sent very early on in the registration phase, where the
122                                  * user hasn't built up much sendq. Handling a blocked write here would
123                                  * be very annoying.
124                                  */
125                                 user->eh.DoWrite();
126                                 user->eh.AddIOHook(creator);
127                                 creator->OnStreamSocketAccept(&user->eh, NULL, NULL);
128                         }
129                         else
130                                 user->WriteNumeric(691, "%s :STARTTLS failure", user->nick.c_str());
131                 }
132
133                 return CMD_FAILURE;
134         }
135 };
136
137 class ModuleSSLGnuTLS : public Module
138 {
139         issl_session* sessions;
140
141         gnutls_certificate_credentials x509_cred;
142         gnutls_dh_params dh_params;
143         gnutls_digest_algorithm_t hash;
144
145         std::string sslports;
146         int dh_bits;
147
148         bool cred_alloc;
149
150         RandGen randhandler;
151         CommandStartTLS starttls;
152
153         GenericCap capHandler;
154         ServiceProvider iohook;
155  public:
156
157         ModuleSSLGnuTLS()
158                 : starttls(this), capHandler(this, "tls"), iohook(this, "ssl/gnutls", SERVICE_IOHOOK)
159         {
160                 sessions = new issl_session[ServerInstance->SE->GetMaxFds()];
161
162                 gnutls_global_init(); // This must be called once in the program
163                 gnutls_x509_privkey_init(&x509_key);
164
165                 cred_alloc = false;
166         }
167
168         void init()
169         {
170                 // Needs the flag as it ignores a plain /rehash
171                 OnModuleRehash(NULL,"ssl");
172
173                 ServerInstance->GenRandom = &randhandler;
174
175                 // Void return, guess we assume success
176                 gnutls_certificate_set_dh_params(x509_cred, dh_params);
177                 Implementation eventlist[] = { I_On005Numeric, I_OnRehash, I_OnModuleRehash, I_OnUserConnect,
178                         I_OnEvent, I_OnHookIO };
179                 ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation));
180
181                 ServerInstance->Modules->AddService(iohook);
182                 ServerInstance->AddCommand(&starttls);
183         }
184
185         void OnRehash(User* user)
186         {
187                 sslports.clear();
188
189                 ConfigTag* Conf = ServerInstance->Config->ConfValue("gnutls");
190                 starttls.enabled = Conf->getBool("starttls", true);
191
192                 if (Conf->getBool("showports", true))
193                 {
194                         for (size_t i = 0; i < ServerInstance->ports.size(); i++)
195                         {
196                                 ListenSocket* port = ServerInstance->ports[i];
197                                 if (port->bind_tag->getString("ssl") != "gnutls")
198                                         continue;
199
200                                 const std::string& portid = port->bind_desc;
201                                 ServerInstance->Logs->Log("m_ssl_gnutls", DEFAULT, "m_ssl_gnutls.so: Enabling SSL for port %s", portid.c_str());
202
203                                 if (port->bind_tag->getString("type", "clients") == "clients" && port->bind_addr != "127.0.0.1")
204                                         sslports.append(portid).append(";");
205                         }
206
207                         if (!sslports.empty())
208                                 sslports.erase(sslports.end() - 1);
209                 }
210         }
211
212         void OnModuleRehash(User* user, const std::string &param)
213         {
214                 if(param != "ssl")
215                         return;
216
217                 std::string keyfile;
218                 std::string certfile;
219                 std::string cafile;
220                 std::string crlfile;
221                 OnRehash(user);
222
223                 ConfigTag* Conf = ServerInstance->Config->ConfValue("gnutls");
224
225                 cafile = Conf->getString("cafile", "conf/ca.pem");
226                 crlfile = Conf->getString("crlfile", "conf/crl.pem");
227                 certfile = Conf->getString("certfile", "conf/cert.pem");
228                 keyfile = Conf->getString("keyfile", "conf/key.pem");
229                 dh_bits = Conf->getInt("dhbits");
230                 std::string hashname = Conf->getString("hash", "md5");
231
232                 if((dh_bits != 768) && (dh_bits != 1024) && (dh_bits != 2048) && (dh_bits != 3072) && (dh_bits != 4096))
233                         dh_bits = 1024;
234
235                 if (hashname == "md5")
236                         hash = GNUTLS_DIG_MD5;
237                 else if (hashname == "sha1")
238                         hash = GNUTLS_DIG_SHA1;
239                 else
240                         throw ModuleException("Unknown hash type " + hashname);
241
242
243                 int ret;
244
245                 if (cred_alloc)
246                 {
247                         // Deallocate the old credentials
248                         gnutls_dh_params_deinit(dh_params);
249                         gnutls_certificate_free_credentials(x509_cred);
250
251                         for(unsigned int i=0; i < x509_certs.size(); i++)
252                                 gnutls_x509_crt_deinit(x509_certs[i]);
253                         x509_certs.clear();
254                 }
255                 else
256                         cred_alloc = true;
257
258                 if((ret = gnutls_certificate_allocate_credentials(&x509_cred)) < 0)
259                         ServerInstance->Logs->Log("m_ssl_gnutls",DEBUG, "m_ssl_gnutls.so: Failed to allocate certificate credentials: %s", gnutls_strerror(ret));
260
261                 if((ret =gnutls_certificate_set_x509_trust_file(x509_cred, cafile.c_str(), GNUTLS_X509_FMT_PEM)) < 0)
262                         ServerInstance->Logs->Log("m_ssl_gnutls",DEBUG, "m_ssl_gnutls.so: Failed to set X.509 trust file '%s': %s", cafile.c_str(), gnutls_strerror(ret));
263
264                 if((ret = gnutls_certificate_set_x509_crl_file (x509_cred, crlfile.c_str(), GNUTLS_X509_FMT_PEM)) < 0)
265                         ServerInstance->Logs->Log("m_ssl_gnutls",DEBUG, "m_ssl_gnutls.so: Failed to set X.509 CRL file '%s': %s", crlfile.c_str(), gnutls_strerror(ret));
266
267                 FileReader reader;
268
269                 reader.LoadFile(certfile);
270                 std::string cert_string = reader.Contents();
271                 gnutls_datum_t cert_datum = { (unsigned char*)cert_string.data(), cert_string.length() };
272
273                 reader.LoadFile(keyfile);
274                 std::string key_string = reader.Contents();
275                 gnutls_datum_t key_datum = { (unsigned char*)key_string.data(), key_string.length() };
276
277                 // If this fails, no SSL port will work. At all. So, do the smart thing - throw a ModuleException
278                 unsigned int certcount = Conf->getInt("certcount", 3);
279                 x509_certs.resize(certcount);
280                 ret = gnutls_x509_crt_list_import(&x509_certs[0], &certcount, &cert_datum, GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED);
281                 if (ret < 0)
282                         throw ModuleException("Unable to load GnuTLS server certificate (" + certfile + "): " + std::string(gnutls_strerror(ret)));
283                 x509_certs.resize(certcount);
284
285                 if((ret = gnutls_x509_privkey_import(x509_key, &key_datum, GNUTLS_X509_FMT_PEM)) < 0)
286                         throw ModuleException("Unable to load GnuTLS server private key (" + keyfile + "): " + std::string(gnutls_strerror(ret)));
287
288                 if((ret = gnutls_certificate_set_x509_key(x509_cred, &x509_certs[0], certcount, x509_key)) < 0)
289                         throw ModuleException("Unable to set GnuTLS cert/key pair: " + std::string(gnutls_strerror(ret)));
290
291                 gnutls_certificate_client_set_retrieve_function (x509_cred, cert_callback);
292
293                 if((ret = gnutls_dh_params_init(&dh_params)) < 0)
294                         ServerInstance->Logs->Log("m_ssl_gnutls",DEFAULT, "m_ssl_gnutls.so: Failed to initialise DH parameters: %s", gnutls_strerror(ret));
295
296                 // This may be on a large (once a day or week) timer eventually.
297                 GenerateDHParams();
298         }
299
300         void GenerateDHParams()
301         {
302                 // Generate Diffie Hellman parameters - for use with DHE
303                 // kx algorithms. These should be discarded and regenerated
304                 // once a day, once a week or once a month. Depending on the
305                 // security requirements.
306
307                 int ret;
308
309                 if((ret = gnutls_dh_params_generate2(dh_params, dh_bits)) < 0)
310                         ServerInstance->Logs->Log("m_ssl_gnutls",DEFAULT, "m_ssl_gnutls.so: Failed to generate DH parameters (%d bits): %s", dh_bits, gnutls_strerror(ret));
311         }
312
313         ~ModuleSSLGnuTLS()
314         {
315                 for(unsigned int i=0; i < x509_certs.size(); i++)
316                         gnutls_x509_crt_deinit(x509_certs[i]);
317                 x509_certs.clear();
318                 gnutls_x509_privkey_deinit(x509_key);
319                 if (cred_alloc)
320                 {
321                         gnutls_dh_params_deinit(dh_params);
322                         gnutls_certificate_free_credentials(x509_cred);
323                 }
324                 gnutls_global_deinit();
325                 delete[] sessions;
326                 ServerInstance->GenRandom = &ServerInstance->HandleGenRandom;
327         }
328
329         void OnCleanup(int target_type, void* item)
330         {
331                 if(target_type == TYPE_USER)
332                 {
333                         LocalUser* user = IS_LOCAL(static_cast<User*>(item));
334
335                         if (user && user->eh.GetIOHook() == this)
336                         {
337                                 // User is using SSL, they're a local user, and they're using one of *our* SSL ports.
338                                 // Potentially there could be multiple SSL modules loaded at once on different ports.
339                                 ServerInstance->Users->QuitUser(user, "SSL module unloading");
340                         }
341                 }
342         }
343
344         Version GetVersion()
345         {
346                 return Version("Provides SSL support for clients", VF_VENDOR);
347         }
348
349
350         void On005Numeric(std::string &output)
351         {
352                 if (!sslports.empty())
353                         output.append(" SSL=" + sslports);
354                 if (starttls.enabled)
355                         output.append(" STARTTLS");
356         }
357
358         void OnHookIO(StreamSocket* user, ListenSocket* lsb)
359         {
360                 if (!user->GetIOHook() && lsb->bind_tag->getString("ssl") == "gnutls")
361                 {
362                         /* Hook the user with our module */
363                         user->AddIOHook(this);
364                 }
365         }
366
367         void OnRequest(Request& request)
368         {
369                 if (strcmp("GET_SSL_CERT", request.id) == 0)
370                 {
371                         SocketCertificateRequest& req = static_cast<SocketCertificateRequest&>(request);
372                         int fd = req.sock->GetFd();
373                         issl_session* session = &sessions[fd];
374
375                         req.cert = session->cert;
376                 }
377         }
378
379         void OnStreamSocketAccept(StreamSocket* user, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server)
380         {
381                 int fd = user->GetFd();
382                 issl_session* session = &sessions[fd];
383
384                 /* For STARTTLS: Don't try and init a session on a socket that already has a session */
385                 if (session->sess)
386                         return;
387
388                 gnutls_init(&session->sess, GNUTLS_SERVER);
389
390                 gnutls_set_default_priority(session->sess); // Avoid calling all the priority functions, defaults are adequate.
391                 gnutls_credentials_set(session->sess, GNUTLS_CRD_CERTIFICATE, x509_cred);
392                 gnutls_dh_set_prime_bits(session->sess, dh_bits);
393
394                 gnutls_transport_set_ptr(session->sess, reinterpret_cast<gnutls_transport_ptr_t>(user));
395                 gnutls_transport_set_push_function(session->sess, gnutls_push_wrapper);
396                 gnutls_transport_set_pull_function(session->sess, gnutls_pull_wrapper);
397
398                 gnutls_certificate_server_set_request(session->sess, GNUTLS_CERT_REQUEST); // Request client certificate if any.
399
400                 Handshake(session, user);
401         }
402
403         void OnStreamSocketConnect(StreamSocket* user)
404         {
405                 issl_session* session = &sessions[user->GetFd()];
406
407                 gnutls_init(&session->sess, GNUTLS_CLIENT);
408
409                 gnutls_set_default_priority(session->sess); // Avoid calling all the priority functions, defaults are adequate.
410                 gnutls_credentials_set(session->sess, GNUTLS_CRD_CERTIFICATE, x509_cred);
411                 gnutls_dh_set_prime_bits(session->sess, dh_bits);
412                 gnutls_transport_set_ptr(session->sess, reinterpret_cast<gnutls_transport_ptr_t>(user));
413                 gnutls_transport_set_push_function(session->sess, gnutls_push_wrapper);
414                 gnutls_transport_set_pull_function(session->sess, gnutls_pull_wrapper);
415
416                 Handshake(session, user);
417         }
418
419         void OnStreamSocketClose(StreamSocket* user)
420         {
421                 CloseSession(&sessions[user->GetFd()]);
422         }
423
424         int OnStreamSocketRead(StreamSocket* user, std::string& recvq)
425         {
426                 issl_session* session = &sessions[user->GetFd()];
427
428                 if (!session->sess)
429                 {
430                         CloseSession(session);
431                         user->SetError("No SSL session");
432                         return -1;
433                 }
434
435                 if (session->status == ISSL_HANDSHAKING_READ || session->status == ISSL_HANDSHAKING_WRITE)
436                 {
437                         // The handshake isn't finished, try to finish it.
438
439                         if(!Handshake(session, user))
440                         {
441                                 if (session->status != ISSL_CLOSING)
442                                         return 0;
443                                 return -1;
444                         }
445                 }
446
447                 // If we resumed the handshake then session->status will be ISSL_HANDSHAKEN.
448
449                 if (session->status == ISSL_HANDSHAKEN)
450                 {
451                         char* buffer = ServerInstance->GetReadBuffer();
452                         size_t bufsiz = ServerInstance->Config->NetBufferSize;
453                         int ret = gnutls_record_recv(session->sess, buffer, bufsiz);
454                         if (ret > 0)
455                         {
456                                 recvq.append(buffer, ret);
457                                 return 1;
458                         }
459                         else if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
460                         {
461                                 return 0;
462                         }
463                         else if (ret == 0)
464                         {
465                                 user->SetError("SSL Connection closed");
466                                 CloseSession(session);
467                                 return -1;
468                         }
469                         else
470                         {
471                                 user->SetError(gnutls_strerror(ret));
472                                 CloseSession(session);
473                                 return -1;
474                         }
475                 }
476                 else if (session->status == ISSL_CLOSING)
477                         return -1;
478
479                 return 0;
480         }
481
482         int OnStreamSocketWrite(StreamSocket* user, std::string& sendq)
483         {
484                 issl_session* session = &sessions[user->GetFd()];
485
486                 if (!session->sess)
487                 {
488                         CloseSession(session);
489                         user->SetError("No SSL session");
490                         return -1;
491                 }
492
493                 if (session->status == ISSL_HANDSHAKING_WRITE || session->status == ISSL_HANDSHAKING_READ)
494                 {
495                         // The handshake isn't finished, try to finish it.
496                         Handshake(session, user);
497                         if (session->status != ISSL_CLOSING)
498                                 return 0;
499                         return -1;
500                 }
501
502                 int ret = 0;
503
504                 if (session->status == ISSL_HANDSHAKEN)
505                 {
506                         ret = gnutls_record_send(session->sess, sendq.data(), sendq.length());
507
508                         if (ret == (int)sendq.length())
509                         {
510                                 ServerInstance->SE->ChangeEventMask(user, FD_WANT_NO_WRITE);
511                                 return 1;
512                         }
513                         else if (ret > 0)
514                         {
515                                 sendq = sendq.substr(ret);
516                                 ServerInstance->SE->ChangeEventMask(user, FD_WANT_SINGLE_WRITE);
517                                 return 0;
518                         }
519                         else if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED || ret == 0)
520                         {
521                                 ServerInstance->SE->ChangeEventMask(user, FD_WANT_SINGLE_WRITE);
522                                 return 0;
523                         }
524                         else // (ret < 0)
525                         {
526                                 user->SetError(gnutls_strerror(ret));
527                                 CloseSession(session);
528                                 return -1;
529                         }
530                 }
531
532                 return 0;
533         }
534
535         bool Handshake(issl_session* session, StreamSocket* user)
536         {
537                 int ret = gnutls_handshake(session->sess);
538
539                 if (ret < 0)
540                 {
541                         if(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
542                         {
543                                 // Handshake needs resuming later, read() or write() would have blocked.
544
545                                 if(gnutls_record_get_direction(session->sess) == 0)
546                                 {
547                                         // gnutls_handshake() wants to read() again.
548                                         session->status = ISSL_HANDSHAKING_READ;
549                                         ServerInstance->SE->ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
550                                 }
551                                 else
552                                 {
553                                         // gnutls_handshake() wants to write() again.
554                                         session->status = ISSL_HANDSHAKING_WRITE;
555                                         ServerInstance->SE->ChangeEventMask(user, FD_WANT_NO_READ | FD_WANT_SINGLE_WRITE);
556                                 }
557                         }
558                         else
559                         {
560                                 user->SetError(std::string("Handshake Failed - ") + gnutls_strerror(ret));
561                                 CloseSession(session);
562                                 session->status = ISSL_CLOSING;
563                         }
564
565                         return false;
566                 }
567                 else
568                 {
569                         // Change the seesion state
570                         session->status = ISSL_HANDSHAKEN;
571
572                         VerifyCertificate(session,user);
573
574                         // Finish writing, if any left
575                         ServerInstance->SE->ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE | FD_ADD_TRIAL_WRITE);
576
577                         return true;
578                 }
579         }
580
581         void OnUserConnect(LocalUser* user)
582         {
583                 if (user->eh.GetIOHook() == this)
584                 {
585                         if (sessions[user->eh.GetFd()].sess)
586                         {
587                                 ssl_cert* cert = sessions[user->eh.GetFd()].cert;
588                                 std::string cipher = gnutls_kx_get_name(gnutls_kx_get(sessions[user->eh.GetFd()].sess));
589                                 cipher.append("-").append(gnutls_cipher_get_name(gnutls_cipher_get(sessions[user->eh.GetFd()].sess))).append("-");
590                                 cipher.append(gnutls_mac_get_name(gnutls_mac_get(sessions[user->eh.GetFd()].sess)));
591                                 if (cert->fingerprint.empty())
592                                         user->WriteServ("NOTICE %s :*** You are connected using SSL cipher \"%s\"", user->nick.c_str(), cipher.c_str());
593                                 else
594                                         user->WriteServ("NOTICE %s :*** You are connected using SSL cipher \"%s\""
595                                                 " and your SSL fingerprint is %s", user->nick.c_str(), cipher.c_str(), cert->fingerprint.c_str());
596                         }
597                 }
598         }
599
600         void CloseSession(issl_session* session)
601         {
602                 if (session->sess)
603                 {
604                         gnutls_bye(session->sess, GNUTLS_SHUT_WR);
605                         gnutls_deinit(session->sess);
606                 }
607                 session->sess = NULL;
608                 session->cert = NULL;
609                 session->status = ISSL_NONE;
610         }
611
612         void VerifyCertificate(issl_session* session, StreamSocket* user)
613         {
614                 if (!session->sess || !user || session->cert)
615                         return;
616
617                 unsigned int status;
618                 const gnutls_datum_t* cert_list;
619                 int ret;
620                 unsigned int cert_list_size;
621                 gnutls_x509_crt_t cert;
622                 char name[MAXBUF];
623                 unsigned char digest[MAXBUF];
624                 size_t digest_size = sizeof(digest);
625                 size_t name_size = sizeof(name);
626                 ssl_cert* certinfo = new ssl_cert;
627                 session->cert = certinfo;
628
629                 /* This verification function uses the trusted CAs in the credentials
630                  * structure. So you must have installed one or more CA certificates.
631                  */
632                 ret = gnutls_certificate_verify_peers2(session->sess, &status);
633
634                 if (ret < 0)
635                 {
636                         certinfo->error = std::string(gnutls_strerror(ret));
637                         return;
638                 }
639
640                 certinfo->invalid = (status & GNUTLS_CERT_INVALID);
641                 certinfo->unknownsigner = (status & GNUTLS_CERT_SIGNER_NOT_FOUND);
642                 certinfo->revoked = (status & GNUTLS_CERT_REVOKED);
643                 certinfo->trusted = !(status & GNUTLS_CERT_SIGNER_NOT_CA);
644
645                 /* Up to here the process is the same for X.509 certificates and
646                  * OpenPGP keys. From now on X.509 certificates are assumed. This can
647                  * be easily extended to work with openpgp keys as well.
648                  */
649                 if (gnutls_certificate_type_get(session->sess) != GNUTLS_CRT_X509)
650                 {
651                         certinfo->error = "No X509 keys sent";
652                         return;
653                 }
654
655                 ret = gnutls_x509_crt_init(&cert);
656                 if (ret < 0)
657                 {
658                         certinfo->error = gnutls_strerror(ret);
659                         return;
660                 }
661
662                 cert_list_size = 0;
663                 cert_list = gnutls_certificate_get_peers(session->sess, &cert_list_size);
664                 if (cert_list == NULL)
665                 {
666                         certinfo->error = "No certificate was found";
667                         goto info_done_dealloc;
668                 }
669
670                 /* This is not a real world example, since we only check the first
671                  * certificate in the given chain.
672                  */
673
674                 ret = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER);
675                 if (ret < 0)
676                 {
677                         certinfo->error = gnutls_strerror(ret);
678                         goto info_done_dealloc;
679                 }
680
681                 gnutls_x509_crt_get_dn(cert, name, &name_size);
682                 certinfo->dn = name;
683
684                 gnutls_x509_crt_get_issuer_dn(cert, name, &name_size);
685                 certinfo->issuer = name;
686
687                 if ((ret = gnutls_x509_crt_get_fingerprint(cert, hash, digest, &digest_size)) < 0)
688                 {
689                         certinfo->error = gnutls_strerror(ret);
690                 }
691                 else
692                 {
693                         certinfo->fingerprint = irc::hex(digest, digest_size);
694                 }
695
696                 /* Beware here we do not check for errors.
697                  */
698                 if ((gnutls_x509_crt_get_expiration_time(cert) < ServerInstance->Time()) || (gnutls_x509_crt_get_activation_time(cert) > ServerInstance->Time()))
699                 {
700                         certinfo->error = "Not activated, or expired certificate";
701                 }
702
703 info_done_dealloc:
704                 gnutls_x509_crt_deinit(cert);
705         }
706
707         void OnEvent(Event& ev)
708         {
709                 if (starttls.enabled)
710                         capHandler.HandleEvent(ev);
711         }
712 };
713
714 MODULE_INIT(ModuleSSLGnuTLS)