]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/extra/m_ssl_gnutls.cpp
Use a set to mark client SSL ports rather than going by textual IP/port pairs
[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-2009 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 "transport.h"
18 #include "m_cap.h"
19
20 #ifdef WINDOWS
21 #pragma comment(lib, "libgnutls-13.lib")
22 #endif
23
24 /* $ModDesc: Provides SSL support for clients */
25 /* $CompileFlags: pkgconfincludes("gnutls","/gnutls/gnutls.h","") */
26 /* $LinkerFlags: rpath("pkg-config --libs gnutls") pkgconflibs("gnutls","/libgnutls.so","-lgnutls") */
27 /* $ModDep: transport.h */
28 /* $CopyInstall: conf/key.pem $(CONPATH) */
29 /* $CopyInstall: conf/cert.pem $(CONPATH) */
30
31 enum issl_status { ISSL_NONE, ISSL_HANDSHAKING_READ, ISSL_HANDSHAKING_WRITE, ISSL_HANDSHAKEN, ISSL_CLOSING, ISSL_CLOSED };
32
33 /** Represents an SSL user's extra data
34  */
35 class issl_session : public classbase
36 {
37 public:
38         issl_session()
39         {
40                 sess = NULL;
41         }
42
43         gnutls_session_t sess;
44         issl_status status;
45         std::string outbuf;
46 };
47
48 class CommandStartTLS : public Command
49 {
50         Module* Caller;
51  public:
52         CommandStartTLS (InspIRCd* Instance, Module* mod) : Command(Instance,"STARTTLS", 0, 0, true), Caller(mod)
53         {
54                 this->source = "m_ssl_gnutls.so";
55         }
56
57         CmdResult Handle (const std::vector<std::string> &parameters, User *user)
58         {
59                 /* changed from == REG_ALL to catch clients sending STARTTLS
60                  * after NICK and USER but before OnUserConnect completes and
61                  * give a proper error message (see bug #645) - dz
62                  */
63                 if (user->registered != REG_NONE)
64                 {
65                         user->WriteNumeric(691, "%s :STARTTLS is not permitted after client registration has started", user->nick.c_str());
66                 }
67                 else
68                 {
69                         if (!user->GetIOHook())
70                         {
71                                 user->WriteNumeric(670, "%s :STARTTLS successful, go ahead with TLS handshake", user->nick.c_str());
72                                 user->AddIOHook(Caller);
73                                 Caller->OnRawSocketAccept(user->GetFd(), NULL, NULL);
74                         }
75                         else
76                                 user->WriteNumeric(691, "%s :STARTTLS failure", user->nick.c_str());
77                 }
78
79                 return CMD_FAILURE;
80         }
81 };
82
83 class ModuleSSLGnuTLS : public Module
84 {
85         std::set<ListenSocketBase*> listenports;
86
87         issl_session* sessions;
88
89         gnutls_certificate_credentials x509_cred;
90         gnutls_dh_params dh_params;
91
92         std::string keyfile;
93         std::string certfile;
94         std::string cafile;
95         std::string crlfile;
96         std::string sslports;
97         int dh_bits;
98
99         bool cred_alloc;
100
101         CommandStartTLS starttls;
102
103  public:
104
105         ModuleSSLGnuTLS(InspIRCd* Me)
106                 : Module(Me), starttls(Me, this)
107         {
108                 ServerInstance->Modules->PublishInterface("BufferedSocketHook", this);
109
110                 sessions = new issl_session[ServerInstance->SE->GetMaxFds()];
111
112                 gnutls_global_init(); // This must be called once in the program
113
114                 cred_alloc = false;
115                 // Needs the flag as it ignores a plain /rehash
116                 OnModuleRehash(NULL,"ssl");
117
118                 // Void return, guess we assume success
119                 gnutls_certificate_set_dh_params(x509_cred, dh_params);
120                 Implementation eventlist[] = { I_On005Numeric, I_OnRawSocketConnect, I_OnRawSocketAccept,
121                         I_OnRawSocketClose, I_OnRawSocketRead, I_OnRawSocketWrite, I_OnCleanup,
122                         I_OnBufferFlushed, I_OnRequest, I_OnRehash, I_OnModuleRehash, I_OnPostConnect,
123                         I_OnEvent, I_OnHookIO };
124                 ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation));
125
126                 ServerInstance->AddCommand(&starttls);
127         }
128
129         virtual void OnRehash(User* user)
130         {
131                 ConfigReader Conf(ServerInstance);
132
133                 listenports.clear();
134                 sslports.clear();
135
136                 for (size_t i = 0; i < ServerInstance->ports.size(); i++)
137                 {
138                         ListenSocketBase* port = ServerInstance->ports[i];
139                         std::string desc = port->GetDescription();
140                         if (desc != "gnutls")
141                                 continue;
142
143                         listenports.insert(port);
144                         std::string portid = port->GetBindDesc();
145
146                         ServerInstance->Logs->Log("m_ssl_gnutls", DEFAULT, "m_ssl_gnutls.so: Enabling SSL for port %s", portid.c_str());
147                         if (port->GetIP() != "127.0.0.1")
148                                 sslports.append(portid).append(";");
149                 }
150
151                 if (!sslports.empty())
152                         sslports.erase(sslports.end() - 1);
153         }
154
155         virtual void OnModuleRehash(User* user, const std::string &param)
156         {
157                 if(param != "ssl")
158                         return;
159
160                 OnRehash(user);
161
162                 ConfigReader Conf(ServerInstance);
163
164                 std::string confdir(ServerInstance->ConfigFileName);
165                 // +1 so we the path ends with a /
166                 confdir = confdir.substr(0, confdir.find_last_of('/') + 1);
167
168                 cafile = Conf.ReadValue("gnutls", "cafile", 0);
169                 crlfile = Conf.ReadValue("gnutls", "crlfile", 0);
170                 certfile = Conf.ReadValue("gnutls", "certfile", 0);
171                 keyfile = Conf.ReadValue("gnutls", "keyfile", 0);
172                 dh_bits = Conf.ReadInteger("gnutls", "dhbits", 0, false);
173
174                 // Set all the default values needed.
175                 if (cafile.empty())
176                         cafile = "ca.pem";
177
178                 if (crlfile.empty())
179                         crlfile = "crl.pem";
180
181                 if (certfile.empty())
182                         certfile = "cert.pem";
183
184                 if (keyfile.empty())
185                         keyfile = "key.pem";
186
187                 if((dh_bits != 768) && (dh_bits != 1024) && (dh_bits != 2048) && (dh_bits != 3072) && (dh_bits != 4096))
188                         dh_bits = 1024;
189
190                 // Prepend relative paths with the path to the config directory.
191                 if ((cafile[0] != '/') && (!ServerInstance->Config->StartsWithWindowsDriveLetter(cafile)))
192                         cafile = confdir + cafile;
193
194                 if ((crlfile[0] != '/') && (!ServerInstance->Config->StartsWithWindowsDriveLetter(crlfile)))
195                         crlfile = confdir + crlfile;
196
197                 if ((certfile[0] != '/') && (!ServerInstance->Config->StartsWithWindowsDriveLetter(certfile)))
198                         certfile = confdir + certfile;
199
200                 if ((keyfile[0] != '/') && (!ServerInstance->Config->StartsWithWindowsDriveLetter(keyfile)))
201                         keyfile = confdir + keyfile;
202
203                 int ret;
204
205                 if (cred_alloc)
206                 {
207                         // Deallocate the old credentials
208                         gnutls_dh_params_deinit(dh_params);
209                         gnutls_certificate_free_credentials(x509_cred);
210                 }
211                 else
212                         cred_alloc = true;
213
214                 if((ret = gnutls_certificate_allocate_credentials(&x509_cred)) < 0)
215                         ServerInstance->Logs->Log("m_ssl_gnutls",DEBUG, "m_ssl_gnutls.so: Failed to allocate certificate credentials: %s", gnutls_strerror(ret));
216
217                 if((ret = gnutls_dh_params_init(&dh_params)) < 0)
218                         ServerInstance->Logs->Log("m_ssl_gnutls",DEBUG, "m_ssl_gnutls.so: Failed to initialise DH parameters: %s", gnutls_strerror(ret));
219
220                 if((ret =gnutls_certificate_set_x509_trust_file(x509_cred, cafile.c_str(), GNUTLS_X509_FMT_PEM)) < 0)
221                         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));
222
223                 if((ret = gnutls_certificate_set_x509_crl_file (x509_cred, crlfile.c_str(), GNUTLS_X509_FMT_PEM)) < 0)
224                         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));
225
226                 if((ret = gnutls_certificate_set_x509_key_file (x509_cred, certfile.c_str(), keyfile.c_str(), GNUTLS_X509_FMT_PEM)) < 0)
227                 {
228                         // If this fails, no SSL port will work. At all. So, do the smart thing - throw a ModuleException
229                         throw ModuleException("Unable to load GnuTLS server certificate (" + certfile + ", key: " + keyfile + "): " + std::string(gnutls_strerror(ret)));
230                 }
231
232                 // This may be on a large (once a day or week) timer eventually.
233                 GenerateDHParams();
234         }
235
236         void GenerateDHParams()
237         {
238                 // Generate Diffie Hellman parameters - for use with DHE
239                 // kx algorithms. These should be discarded and regenerated
240                 // once a day, once a week or once a month. Depending on the
241                 // security requirements.
242
243                 int ret;
244
245                 if((ret = gnutls_dh_params_generate2(dh_params, dh_bits)) < 0)
246                         ServerInstance->Logs->Log("m_ssl_gnutls",DEFAULT, "m_ssl_gnutls.so: Failed to generate DH parameters (%d bits): %s", dh_bits, gnutls_strerror(ret));
247         }
248
249         virtual ~ModuleSSLGnuTLS()
250         {
251                 gnutls_dh_params_deinit(dh_params);
252                 gnutls_certificate_free_credentials(x509_cred);
253                 gnutls_global_deinit();
254                 ServerInstance->Modules->UnpublishInterface("BufferedSocketHook", this);
255                 delete[] sessions;
256         }
257
258         virtual void OnCleanup(int target_type, void* item)
259         {
260                 if(target_type == TYPE_USER)
261                 {
262                         User* user = static_cast<User*>(item);
263
264                         if (user->GetIOHook() == this)
265                         {
266                                 // User is using SSL, they're a local user, and they're using one of *our* SSL ports.
267                                 // Potentially there could be multiple SSL modules loaded at once on different ports.
268                                 ServerInstance->Users->QuitUser(user, "SSL module unloading");
269                                 user->DelIOHook();
270                         }
271                         if (user->GetExt("ssl_cert"))
272                         {
273                                 ssl_cert* tofree;
274                                 user->GetExt("ssl_cert", tofree);
275                                 delete tofree;
276                                 user->Shrink("ssl_cert");
277                         }
278                 }
279         }
280
281         virtual Version GetVersion()
282         {
283                 return Version("$Id$", VF_VENDOR, API_VERSION);
284         }
285
286
287         virtual void On005Numeric(std::string &output)
288         {
289                 if (!sslports.empty())
290                         output.append(" SSL=" + sslports);
291                 output.append(" STARTTLS");
292         }
293
294         virtual void OnHookIO(EventHandler* user, ListenSocketBase* lsb)
295         {
296                 if (!user->GetIOHook() && listenports.find(lsb) != listenports.end())
297                 {
298                         /* Hook the user with our module */
299                         user->AddIOHook(this);
300                 }
301         }
302
303         virtual const char* OnRequest(Request* request)
304         {
305                 ISHRequest* ISR = static_cast<ISHRequest*>(request);
306                 if (strcmp("IS_NAME", request->GetId()) == 0)
307                 {
308                         return "gnutls";
309                 }
310                 else if (strcmp("IS_HOOK", request->GetId()) == 0)
311                 {
312                         const char* ret = "OK";
313                         try
314                         {
315                                 ret = ISR->Sock->AddIOHook(this) ? "OK" : NULL;
316                         }
317                         catch (ModuleException &e)
318                         {
319                                 return NULL;
320                         }
321                         return ret;
322                 }
323                 else if (strcmp("IS_UNHOOK", request->GetId()) == 0)
324                 {
325                         return ISR->Sock->DelIOHook() ? "OK" : NULL;
326                 }
327                 else if (strcmp("IS_HSDONE", request->GetId()) == 0)
328                 {
329                         if (ISR->Sock->GetFd() < 0)
330                                 return "OK";
331
332                         issl_session* session = &sessions[ISR->Sock->GetFd()];
333                         return (session->status == ISSL_HANDSHAKING_READ || session->status == ISSL_HANDSHAKING_WRITE) ? NULL : "OK";
334                 }
335                 else if (strcmp("IS_ATTACH", request->GetId()) == 0)
336                 {
337                         if (ISR->Sock->GetFd() > -1)
338                         {
339                                 issl_session* session = &sessions[ISR->Sock->GetFd()];
340                                 if (session->sess)
341                                 {
342                                         if (static_cast<Extensible*>(ServerInstance->SE->GetRef(ISR->Sock->GetFd())) == static_cast<Extensible*>(ISR->Sock))
343                                         {
344                                                 VerifyCertificate(session, ISR->Sock);
345                                                 return "OK";
346                                         }
347                                 }
348                         }
349                 }
350                 else if (strcmp("GET_FP", request->GetId()) == 0)
351                 {
352                         if (ISR->Sock->GetFd() > -1)
353                         {
354                                 issl_session* session = &sessions[ISR->Sock->GetFd()];
355                                 if (session->sess)
356                                 {
357                                         Extensible* ext = ISR->Sock;
358                                         ssl_cert* certinfo;
359                                         if (ext->GetExt("ssl_cert",certinfo))
360                                                 return certinfo->GetFingerprint().c_str();
361                                 }
362                         }
363                 }
364                 return NULL;
365         }
366
367
368         virtual void OnRawSocketAccept(int fd, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server)
369         {
370                 /* Are there any possibilities of an out of range fd? Hope not, but lets be paranoid */
371                 if ((fd < 0) || (fd > ServerInstance->SE->GetMaxFds() - 1))
372                         return;
373
374                 issl_session* session = &sessions[fd];
375
376                 /* For STARTTLS: Don't try and init a session on a socket that already has a session */
377                 if (session->sess)
378                         return;
379
380                 gnutls_init(&session->sess, GNUTLS_SERVER);
381
382                 gnutls_set_default_priority(session->sess); // Avoid calling all the priority functions, defaults are adequate.
383                 gnutls_credentials_set(session->sess, GNUTLS_CRD_CERTIFICATE, x509_cred);
384                 gnutls_dh_set_prime_bits(session->sess, dh_bits);
385
386                 gnutls_transport_set_ptr(session->sess, reinterpret_cast<gnutls_transport_ptr_t>(fd)); // Give gnutls the fd for the socket.
387
388                 gnutls_certificate_server_set_request(session->sess, GNUTLS_CERT_REQUEST); // Request client certificate if any.
389
390                 Handshake(session, fd);
391         }
392
393         virtual void OnRawSocketConnect(int fd)
394         {
395                 /* Are there any possibilities of an out of range fd? Hope not, but lets be paranoid */
396                 if ((fd < 0) || (fd > ServerInstance->SE->GetMaxFds() - 1))
397                         return;
398
399                 issl_session* session = &sessions[fd];
400
401                 gnutls_init(&session->sess, GNUTLS_CLIENT);
402
403                 gnutls_set_default_priority(session->sess); // Avoid calling all the priority functions, defaults are adequate.
404                 gnutls_credentials_set(session->sess, GNUTLS_CRD_CERTIFICATE, x509_cred);
405                 gnutls_dh_set_prime_bits(session->sess, dh_bits);
406                 gnutls_transport_set_ptr(session->sess, reinterpret_cast<gnutls_transport_ptr_t>(fd)); // Give gnutls the fd for the socket.
407
408                 Handshake(session, fd);
409         }
410
411         virtual void OnRawSocketClose(int fd)
412         {
413                 /* Are there any possibilities of an out of range fd? Hope not, but lets be paranoid */
414                 if ((fd < 0) || (fd > ServerInstance->SE->GetMaxFds()))
415                         return;
416
417                 CloseSession(&sessions[fd]);
418
419                 EventHandler* user = ServerInstance->SE->GetRef(fd);
420
421                 if ((user) && (user->GetExt("ssl_cert")))
422                 {
423                         ssl_cert* tofree;
424                         user->GetExt("ssl_cert", tofree);
425                         delete tofree;
426                         user->Shrink("ssl_cert");
427                 }
428         }
429
430         virtual int OnRawSocketRead(int fd, char* buffer, unsigned int count, int &readresult)
431         {
432                 /* Are there any possibilities of an out of range fd? Hope not, but lets be paranoid */
433                 if ((fd < 0) || (fd > ServerInstance->SE->GetMaxFds() - 1))
434                         return 0;
435
436                 issl_session* session = &sessions[fd];
437
438                 if (!session->sess)
439                 {
440                         readresult = 0;
441                         CloseSession(session);
442                         return 1;
443                 }
444
445                 if (session->status == ISSL_HANDSHAKING_READ)
446                 {
447                         // The handshake isn't finished, try to finish it.
448
449                         if(!Handshake(session, fd))
450                         {
451                                 errno = session->status == ISSL_CLOSING ? EIO : EAGAIN;
452                                 // Couldn't resume handshake.
453                                 return -1;
454                         }
455                 }
456                 else if (session->status == ISSL_HANDSHAKING_WRITE)
457                 {
458                         errno = EAGAIN;
459                         MakePollWrite(fd);
460                         return -1;
461                 }
462
463                 // If we resumed the handshake then session->status will be ISSL_HANDSHAKEN.
464
465                 if (session->status == ISSL_HANDSHAKEN)
466                 {
467                         unsigned int len = 0;
468                         while (len < count)
469                         {
470                                 int ret = gnutls_record_recv(session->sess, buffer + len, count - len);
471                                 if (ret > 0)
472                                 {
473                                         len += ret;
474                                 }
475                                 else if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
476                                 {
477                                         break;
478                                 }
479                                 else
480                                 {
481                                         if (ret != 0)
482                                                 ServerInstance->Logs->Log("m_ssl_gnutls", DEFAULT,
483                                                         "m_ssl_gnutls.so: Error while reading on fd %d: %s",
484                                                         fd, gnutls_strerror(ret));
485
486                                         // if ret == 0, client closed connection.
487                                         readresult = 0;
488                                         CloseSession(session);
489                                         return 1;
490                                 }
491                         }
492                         readresult = len;
493                         if (len)
494                         {
495                                 return 1;
496                         }
497                         else
498                         {
499                                 errno = EAGAIN;
500                                 return -1;
501                         }
502                 }
503                 else if (session->status == ISSL_CLOSING)
504                         readresult = 0;
505
506                 return 1;
507         }
508
509         virtual int OnRawSocketWrite(int fd, const char* buffer, int count)
510         {
511                 /* Are there any possibilities of an out of range fd? Hope not, but lets be paranoid */
512                 if ((fd < 0) || (fd > ServerInstance->SE->GetMaxFds() - 1))
513                         return 0;
514
515                 issl_session* session = &sessions[fd];
516                 const char* sendbuffer = buffer;
517
518                 if (!session->sess)
519                 {
520                         CloseSession(session);
521                         return 1;
522                 }
523
524                 session->outbuf.append(sendbuffer, count);
525                 sendbuffer = session->outbuf.c_str();
526                 count = session->outbuf.size();
527
528                 if (session->status == ISSL_HANDSHAKING_WRITE || session->status == ISSL_HANDSHAKING_READ)
529                 {
530                         // The handshake isn't finished, try to finish it.
531                         Handshake(session, fd);
532                         errno = session->status == ISSL_CLOSING ? EIO : EAGAIN;
533                         return -1;
534                 }
535
536                 int ret = 0;
537
538                 if (session->status == ISSL_HANDSHAKEN)
539                 {
540                         ret = gnutls_record_send(session->sess, sendbuffer, count);
541
542                         if (ret == 0)
543                         {
544                                 CloseSession(session);
545                         }
546                         else if (ret < 0)
547                         {
548                                 if(ret != GNUTLS_E_AGAIN && ret != GNUTLS_E_INTERRUPTED)
549                                 {
550                                         ServerInstance->Logs->Log("m_ssl_gnutls", DEFAULT,
551                                                         "m_ssl_gnutls.so: Error while writing to fd %d: %s",
552                                                         fd, gnutls_strerror(ret));
553                                         CloseSession(session);
554                                 }
555                                 else
556                                 {
557                                         errno = EAGAIN;
558                                 }
559                         }
560                         else
561                         {
562                                 session->outbuf = session->outbuf.substr(ret);
563                         }
564                 }
565
566                 if (!session->outbuf.empty())
567                         MakePollWrite(fd);
568
569                 /* Who's smart idea was it to return 1 when we havent written anything?
570                  * This fucks the buffer up in BufferedSocket :p
571                  */
572                 return ret < 1 ? 0 : ret;
573         }
574
575         bool Handshake(issl_session* session, int fd)
576         {
577                 int ret = gnutls_handshake(session->sess);
578
579                 if (ret < 0)
580                 {
581                         if(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
582                         {
583                                 // Handshake needs resuming later, read() or write() would have blocked.
584
585                                 if(gnutls_record_get_direction(session->sess) == 0)
586                                 {
587                                         // gnutls_handshake() wants to read() again.
588                                         session->status = ISSL_HANDSHAKING_READ;
589                                 }
590                                 else
591                                 {
592                                         // gnutls_handshake() wants to write() again.
593                                         session->status = ISSL_HANDSHAKING_WRITE;
594                                         MakePollWrite(fd);
595                                 }
596                         }
597                         else
598                         {
599                                 // Handshake failed.
600                                 ServerInstance->Logs->Log("m_ssl_gnutls", DEFAULT,
601                                                 "m_ssl_gnutls.so: Handshake failed on fd %d: %s",
602                                                 fd, gnutls_strerror(ret));
603                                 CloseSession(session);
604                                 session->status = ISSL_CLOSING;
605                         }
606
607                         return false;
608                 }
609                 else
610                 {
611                         // Handshake complete.
612                         // This will do for setting the ssl flag...it could be done earlier if it's needed. But this seems neater.
613                         EventHandler *extendme = ServerInstance->SE->GetRef(fd);
614                         if (extendme)
615                         {
616                                 extendme->Extend("ssl");
617                         }
618
619                         // Change the seesion state
620                         session->status = ISSL_HANDSHAKEN;
621
622                         // Finish writing, if any left
623                         MakePollWrite(fd);
624
625                         return true;
626                 }
627         }
628
629         virtual void OnPostConnect(User* user)
630         {
631                 // This occurs AFTER OnUserConnect so we can be sure the
632                 // protocol module has propagated the NICK message.
633                 if (user->GetIOHook() == this && (IS_LOCAL(user)))
634                 {
635                         ssl_cert* certdata = VerifyCertificate(&sessions[user->GetFd()],user);
636                         if (sessions[user->GetFd()].sess)
637                         {
638                                 std::string cipher = gnutls_kx_get_name(gnutls_kx_get(sessions[user->GetFd()].sess));
639                                 cipher.append("-").append(gnutls_cipher_get_name(gnutls_cipher_get(sessions[user->GetFd()].sess))).append("-");
640                                 cipher.append(gnutls_mac_get_name(gnutls_mac_get(sessions[user->GetFd()].sess)));
641                                 user->WriteServ("NOTICE %s :*** You are connected using SSL cipher \"%s\"", user->nick.c_str(), cipher.c_str());
642                         }
643
644                         ServerInstance->PI->SendMetaData(user, "ssl", "ON");
645                         if (certdata)
646                                 ServerInstance->PI->SendMetaData(user, "ssl_cert", certdata->GetMetaLine().c_str());
647                 }
648         }
649
650         void MakePollWrite(int fd)
651         {
652                 //OnRawSocketWrite(fd, NULL, 0);
653                 EventHandler* eh = ServerInstance->SE->GetRef(fd);
654                 if (eh)
655                         ServerInstance->SE->WantWrite(eh);
656         }
657
658         virtual void OnBufferFlushed(User* user)
659         {
660                 if (user->GetIOHook() == this)
661                 {
662                         issl_session* session = &sessions[user->GetFd()];
663                         if (session && session->outbuf.size())
664                                 OnRawSocketWrite(user->GetFd(), NULL, 0);
665                 }
666         }
667
668         void CloseSession(issl_session* session)
669         {
670                 if(session->sess)
671                 {
672                         gnutls_bye(session->sess, GNUTLS_SHUT_WR);
673                         gnutls_deinit(session->sess);
674                 }
675
676                 session->outbuf.clear();
677                 session->sess = NULL;
678                 session->status = ISSL_NONE;
679         }
680
681         ssl_cert* VerifyCertificate(issl_session* session, Extensible* user)
682         {
683                 if (!session->sess || !user)
684                         return NULL;
685
686                 unsigned int status;
687                 const gnutls_datum_t* cert_list;
688                 int ret;
689                 unsigned int cert_list_size;
690                 gnutls_x509_crt_t cert;
691                 char name[MAXBUF];
692                 unsigned char digest[MAXBUF];
693                 size_t digest_size = sizeof(digest);
694                 size_t name_size = sizeof(name);
695                 ssl_cert* certinfo = new ssl_cert;
696
697                 user->Extend("ssl_cert",certinfo);
698
699                 /* This verification function uses the trusted CAs in the credentials
700                  * structure. So you must have installed one or more CA certificates.
701                  */
702                 ret = gnutls_certificate_verify_peers2(session->sess, &status);
703
704                 if (ret < 0)
705                 {
706                         certinfo->error = std::string(gnutls_strerror(ret));
707                         return certinfo;
708                 }
709
710                 certinfo->invalid = (status & GNUTLS_CERT_INVALID);
711                 certinfo->unknownsigner = (status & GNUTLS_CERT_SIGNER_NOT_FOUND);
712                 certinfo->revoked = (status & GNUTLS_CERT_REVOKED);
713                 certinfo->trusted = !(status & GNUTLS_CERT_SIGNER_NOT_CA);
714
715                 /* Up to here the process is the same for X.509 certificates and
716                  * OpenPGP keys. From now on X.509 certificates are assumed. This can
717                  * be easily extended to work with openpgp keys as well.
718                  */
719                 if (gnutls_certificate_type_get(session->sess) != GNUTLS_CRT_X509)
720                 {
721                         certinfo->error = "No X509 keys sent";
722                         return certinfo;
723                 }
724
725                 ret = gnutls_x509_crt_init(&cert);
726                 if (ret < 0)
727                 {
728                         certinfo->error = gnutls_strerror(ret);
729                         return certinfo;
730                 }
731
732                 cert_list_size = 0;
733                 cert_list = gnutls_certificate_get_peers(session->sess, &cert_list_size);
734                 if (cert_list == NULL)
735                 {
736                         certinfo->error = "No certificate was found";
737                         return certinfo;
738                 }
739
740                 /* This is not a real world example, since we only check the first
741                  * certificate in the given chain.
742                  */
743
744                 ret = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER);
745                 if (ret < 0)
746                 {
747                         certinfo->error = gnutls_strerror(ret);
748                         return certinfo;
749                 }
750
751                 gnutls_x509_crt_get_dn(cert, name, &name_size);
752                 certinfo->dn = name;
753
754                 gnutls_x509_crt_get_issuer_dn(cert, name, &name_size);
755                 certinfo->issuer = name;
756
757                 if ((ret = gnutls_x509_crt_get_fingerprint(cert, GNUTLS_DIG_MD5, digest, &digest_size)) < 0)
758                 {
759                         certinfo->error = gnutls_strerror(ret);
760                 }
761                 else
762                 {
763                         certinfo->fingerprint = irc::hex(digest, digest_size);
764                 }
765
766                 /* Beware here we do not check for errors.
767                  */
768                 if ((gnutls_x509_crt_get_expiration_time(cert) < ServerInstance->Time()) || (gnutls_x509_crt_get_activation_time(cert) > ServerInstance->Time()))
769                 {
770                         certinfo->error = "Not activated, or expired certificate";
771                 }
772
773                 gnutls_x509_crt_deinit(cert);
774
775                 return certinfo;
776         }
777
778         void OnEvent(Event* ev)
779         {
780                 GenericCapHandler(ev, "tls", "tls");
781         }
782
783         void Prioritize()
784         {
785                 Module* server = ServerInstance->Modules->Find("m_spanningtree.so");
786                 ServerInstance->Modules->SetPriority(this, I_OnPostConnect, PRIORITY_AFTER, &server);
787         }
788 };
789
790 MODULE_INIT(ModuleSSLGnuTLS)