]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/extra/m_ssl_gnutls.cpp
m_ssl_gnutls: Log ssl errors
[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-2008 InspIRCd Development Team
6  * See: http://www.inspircd.org/wiki/index.php/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: exec("libgnutls-config --cflags") */
26 /* $LinkerFlags: rpath("libgnutls-config --libs") exec("libgnutls-config --libs") */
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 bool isin(const std::string &host, int port, const std::vector<std::string> &portlist)
34 {
35         if (std::find(portlist.begin(), portlist.end(), "*:" + ConvToStr(port)) != portlist.end())
36                 return true;
37
38         if (std::find(portlist.begin(), portlist.end(), ":" + ConvToStr(port)) != portlist.end())
39                 return true;
40
41         return std::find(portlist.begin(), portlist.end(), host + ":" + ConvToStr(port)) != portlist.end();
42 }
43
44 /** Represents an SSL user's extra data
45  */
46 class issl_session : public classbase
47 {
48 public:
49         issl_session()
50         {
51                 sess = NULL;
52         }
53
54         gnutls_session_t sess;
55         issl_status status;
56         std::string outbuf;
57         int inbufoffset;
58         char* inbuf;
59         int fd;
60 };
61
62 class CommandStartTLS : public Command
63 {
64         Module* Caller;
65  public:
66         /* Command 'dalinfo', takes no parameters and needs no special modes */
67         CommandStartTLS (InspIRCd* Instance, Module* mod) : Command(Instance,"STARTTLS", 0, 0, true), Caller(mod)
68         {
69                 this->source = "m_ssl_gnutls.so";
70         }
71
72         CmdResult Handle (const std::vector<std::string> &parameters, User *user)
73         {
74                 if (user->registered == REG_ALL)
75                 {
76                         ServerInstance->Users->QuitUser(user, "STARTTLS not allowed after client registration");
77                 }
78                 else
79                 {
80                         if (!user->GetIOHook())
81                         {
82                                 user->WriteNumeric(670, "%s :STARTTLS successful, go ahead with TLS handshake", user->nick.c_str());
83                                 user->AddIOHook(Caller);
84                                 Caller->OnRawSocketAccept(user->GetFd(), user->GetIPString(), user->GetPort());
85                         }
86                         else
87                                 user->WriteNumeric(671, "%s :STARTTLS failure", user->nick.c_str());
88                 }
89
90                 return CMD_FAILURE;
91         }
92 };
93
94 class ModuleSSLGnuTLS : public Module
95 {
96
97         ConfigReader* Conf;
98
99         char* dummy;
100
101         std::vector<std::string> listenports;
102
103         int inbufsize;
104         issl_session* sessions;
105
106         gnutls_certificate_credentials x509_cred;
107         gnutls_dh_params dh_params;
108
109         std::string keyfile;
110         std::string certfile;
111         std::string cafile;
112         std::string crlfile;
113         std::string sslports;
114         int dh_bits;
115
116         int clientactive;
117         bool cred_alloc;
118
119         CommandStartTLS* starttls;
120
121  public:
122
123         ModuleSSLGnuTLS(InspIRCd* Me)
124                 : Module(Me)
125         {
126                 ServerInstance->Modules->PublishInterface("BufferedSocketHook", this);
127
128                 sessions = new issl_session[ServerInstance->SE->GetMaxFds()];
129
130                 // Not rehashable...because I cba to reduce all the sizes of existing buffers.
131                 inbufsize = ServerInstance->Config->NetBufferSize;
132
133                 gnutls_global_init(); // This must be called once in the program
134
135                 cred_alloc = false;
136                 // Needs the flag as it ignores a plain /rehash
137                 OnRehash(NULL,"ssl");
138
139                 // Void return, guess we assume success
140                 gnutls_certificate_set_dh_params(x509_cred, dh_params);
141                 Implementation eventlist[] = { I_On005Numeric, I_OnRawSocketConnect, I_OnRawSocketAccept, I_OnRawSocketClose, I_OnRawSocketRead, I_OnRawSocketWrite, I_OnCleanup,
142                         I_OnBufferFlushed, I_OnRequest, I_OnSyncUserMetaData, I_OnDecodeMetaData, I_OnUnloadModule, I_OnRehash, I_OnWhois, I_OnPostConnect, I_OnEvent, I_OnHookUserIO };
143                 ServerInstance->Modules->Attach(eventlist, this, 17);
144
145                 starttls = new CommandStartTLS(ServerInstance, this);
146                 ServerInstance->AddCommand(starttls);
147         }
148
149         virtual void OnRehash(User* user, const std::string &param)
150         {
151                 Conf = new ConfigReader(ServerInstance);
152
153                 listenports.clear();
154                 clientactive = 0;
155                 sslports.clear();
156
157                 for(int index = 0; index < Conf->Enumerate("bind"); index++)
158                 {
159                         // For each <bind> tag
160                         std::string x = Conf->ReadValue("bind", "type", index);
161                         if(((x.empty()) || (x == "clients")) && (Conf->ReadValue("bind", "ssl", index) == "gnutls"))
162                         {
163                                 // Get the port we're meant to be listening on with SSL
164                                 std::string port = Conf->ReadValue("bind", "port", index);
165                                 std::string addr = Conf->ReadValue("bind", "address", index);
166
167                                 irc::portparser portrange(port, false);
168                                 long portno = -1;
169                                 while ((portno = portrange.GetToken()))
170                                 {
171                                         clientactive++;
172                                         try
173                                         {
174                                                 listenports.push_back(addr + ":" + ConvToStr(portno));
175
176                                                 for (size_t i = 0; i < ServerInstance->Config->ports.size(); i++)
177                                                         if ((ServerInstance->Config->ports[i]->GetPort() == portno) && (ServerInstance->Config->ports[i]->GetIP() == addr))
178                                                                 ServerInstance->Config->ports[i]->SetDescription("ssl");
179                                                 ServerInstance->Logs->Log("m_ssl_gnutls",DEFAULT, "m_ssl_gnutls.so: Enabling SSL for port %ld", portno);
180
181                                                 sslports.append((addr.empty() ? "*" : addr)).append(":").append(ConvToStr(portno)).append(";");
182                                         }
183                                         catch (ModuleException &e)
184                                         {
185                                                 ServerInstance->Logs->Log("m_ssl_gnutls",DEFAULT, "m_ssl_gnutls.so: FAILED to enable SSL on port %ld: %s. Maybe it's already hooked by the same port on a different IP, or you have an other SSL or similar module loaded?", portno, e.GetReason());
186                                         }
187                                 }
188                         }
189                 }
190
191                 if (!sslports.empty())
192                         sslports.erase(sslports.end() - 1);
193
194                 if(param != "ssl")
195                 {
196                         delete Conf;
197                         return;
198                 }
199
200                 std::string confdir(ServerInstance->ConfigFileName);
201                 // +1 so we the path ends with a /
202                 confdir = confdir.substr(0, confdir.find_last_of('/') + 1);
203
204                 cafile  = Conf->ReadValue("gnutls", "cafile", 0);
205                 crlfile = Conf->ReadValue("gnutls", "crlfile", 0);
206                 certfile        = Conf->ReadValue("gnutls", "certfile", 0);
207                 keyfile = Conf->ReadValue("gnutls", "keyfile", 0);
208                 dh_bits = Conf->ReadInteger("gnutls", "dhbits", 0, false);
209
210                 // Set all the default values needed.
211                 if (cafile.empty())
212                         cafile = "ca.pem";
213
214                 if (crlfile.empty())
215                         crlfile = "crl.pem";
216
217                 if (certfile.empty())
218                         certfile = "cert.pem";
219
220                 if (keyfile.empty())
221                         keyfile = "key.pem";
222
223                 if((dh_bits != 768) && (dh_bits != 1024) && (dh_bits != 2048) && (dh_bits != 3072) && (dh_bits != 4096))
224                         dh_bits = 1024;
225
226                 // Prepend relative paths with the path to the config directory.
227                 if ((cafile[0] != '/') && (!ServerInstance->Config->StartsWithWindowsDriveLetter(cafile)))
228                         cafile = confdir + cafile;
229
230                 if ((crlfile[0] != '/') && (!ServerInstance->Config->StartsWithWindowsDriveLetter(crlfile)))
231                         crlfile = confdir + crlfile;
232
233                 if ((certfile[0] != '/') && (!ServerInstance->Config->StartsWithWindowsDriveLetter(certfile)))
234                         certfile = confdir + certfile;
235
236                 if ((keyfile[0] != '/') && (!ServerInstance->Config->StartsWithWindowsDriveLetter(keyfile)))
237                         keyfile = confdir + keyfile;
238
239                 int ret;
240                 
241                 if (cred_alloc)
242                 {
243                         // Deallocate the old credentials
244                         gnutls_dh_params_deinit(dh_params);
245                         gnutls_certificate_free_credentials(x509_cred);
246                 }
247                 else
248                         cred_alloc = true;
249                 
250                 if((ret = gnutls_certificate_allocate_credentials(&x509_cred)) < 0)
251                         ServerInstance->Logs->Log("m_ssl_gnutls",DEFAULT, "m_ssl_gnutls.so: Failed to allocate certificate credentials: %s", gnutls_strerror(ret));
252                 
253                 if((ret = gnutls_dh_params_init(&dh_params)) < 0)
254                         ServerInstance->Logs->Log("m_ssl_gnutls",DEFAULT, "m_ssl_gnutls.so: Failed to initialise DH parameters: %s", gnutls_strerror(ret));
255                 
256                 if((ret =gnutls_certificate_set_x509_trust_file(x509_cred, cafile.c_str(), GNUTLS_X509_FMT_PEM)) < 0)
257                         ServerInstance->Logs->Log("m_ssl_gnutls",DEFAULT, "m_ssl_gnutls.so: Failed to set X.509 trust file '%s': %s", cafile.c_str(), gnutls_strerror(ret));
258
259                 if((ret = gnutls_certificate_set_x509_crl_file (x509_cred, crlfile.c_str(), GNUTLS_X509_FMT_PEM)) < 0)
260                         ServerInstance->Logs->Log("m_ssl_gnutls",DEFAULT, "m_ssl_gnutls.so: Failed to set X.509 CRL file '%s': %s", crlfile.c_str(), gnutls_strerror(ret));
261
262                 if((ret = gnutls_certificate_set_x509_key_file (x509_cred, certfile.c_str(), keyfile.c_str(), GNUTLS_X509_FMT_PEM)) < 0)
263                 {
264                         // If this fails, no SSL port will work. At all. So, do the smart thing - throw a ModuleException
265                         throw ModuleException("Unable to load GnuTLS server certificate (" + std::string(certfile) + ", key: " + keyfile + "): " + std::string(gnutls_strerror(ret)));
266                 }
267
268                 // This may be on a large (once a day or week) timer eventually.
269                 GenerateDHParams();
270
271                 delete Conf;
272         }
273
274         void GenerateDHParams()
275         {
276                 // Generate Diffie Hellman parameters - for use with DHE
277                 // kx algorithms. These should be discarded and regenerated
278                 // once a day, once a week or once a month. Depending on the
279                 // security requirements.
280
281                 int ret;
282
283                 if((ret = gnutls_dh_params_generate2(dh_params, dh_bits)) < 0)
284                         ServerInstance->Logs->Log("m_ssl_gnutls",DEFAULT, "m_ssl_gnutls.so: Failed to generate DH parameters (%d bits): %s", dh_bits, gnutls_strerror(ret));
285         }
286
287         virtual ~ModuleSSLGnuTLS()
288         {
289                 gnutls_dh_params_deinit(dh_params);
290                 gnutls_certificate_free_credentials(x509_cred);
291                 gnutls_global_deinit();
292                 ServerInstance->Modules->UnpublishInterface("BufferedSocketHook", this);
293                 delete[] sessions;
294         }
295
296         virtual void OnCleanup(int target_type, void* item)
297         {
298                 if(target_type == TYPE_USER)
299                 {
300                         User* user = (User*)item;
301
302                         if (user->GetIOHook() == this)
303                         {
304                                 // User is using SSL, they're a local user, and they're using one of *our* SSL ports.
305                                 // Potentially there could be multiple SSL modules loaded at once on different ports.
306                                 ServerInstance->Users->QuitUser(user, "SSL module unloading");
307                                 user->DelIOHook();
308                         }
309                         if (user->GetExt("ssl_cert", dummy))
310                         {
311                                 ssl_cert* tofree;
312                                 user->GetExt("ssl_cert", tofree);
313                                 delete tofree;
314                                 user->Shrink("ssl_cert");
315                         }
316                 }
317         }
318
319         virtual void OnUnloadModule(Module* mod, const std::string &name)
320         {
321                 if(mod == this)
322                 {
323                         for(unsigned int i = 0; i < listenports.size(); i++)
324                         {
325                                 for (size_t j = 0; j < ServerInstance->Config->ports.size(); j++)
326                                         if (listenports[i] == (ServerInstance->Config->ports[j]->GetIP()+":"+ConvToStr(ServerInstance->Config->ports[j]->GetPort())))
327                                                 ServerInstance->Config->ports[j]->SetDescription("plaintext");
328                         }
329                 }
330         }
331
332         virtual Version GetVersion()
333         {
334                 return Version("$Id$", VF_VENDOR, API_VERSION);
335         }
336
337
338         virtual void On005Numeric(std::string &output)
339         {
340                 output.append(" SSL=" + sslports);
341         }
342
343         virtual void OnHookUserIO(User* user, const std::string &targetip)
344         {
345                 if (!user->GetIOHook() && isin(targetip,user->GetPort(),listenports))
346                 {
347                         /* Hook the user with our module */
348                         user->AddIOHook(this);
349                 }
350         }
351
352         virtual const char* OnRequest(Request* request)
353         {
354                 ISHRequest* ISR = (ISHRequest*)request;
355                 if (strcmp("IS_NAME", request->GetId()) == 0)
356                 {
357                         return "gnutls";
358                 }
359                 else if (strcmp("IS_HOOK", request->GetId()) == 0)
360                 {
361                         const char* ret = "OK";
362                         try
363                         {
364                                 ret = ISR->Sock->AddIOHook((Module*)this) ? "OK" : NULL;
365                         }
366                         catch (ModuleException &e)
367                         {
368                                 return NULL;
369                         }
370                         return ret;
371                 }
372                 else if (strcmp("IS_UNHOOK", request->GetId()) == 0)
373                 {
374                         return ISR->Sock->DelIOHook() ? "OK" : NULL;
375                 }
376                 else if (strcmp("IS_HSDONE", request->GetId()) == 0)
377                 {
378                         if (ISR->Sock->GetFd() < 0)
379                                 return "OK";
380
381                         issl_session* session = &sessions[ISR->Sock->GetFd()];
382                         return (session->status == ISSL_HANDSHAKING_READ || session->status == ISSL_HANDSHAKING_WRITE) ? NULL : "OK";
383                 }
384                 else if (strcmp("IS_ATTACH", request->GetId()) == 0)
385                 {
386                         if (ISR->Sock->GetFd() > -1)
387                         {
388                                 issl_session* session = &sessions[ISR->Sock->GetFd()];
389                                 if (session->sess)
390                                 {
391                                         if ((Extensible*)ServerInstance->FindDescriptor(ISR->Sock->GetFd()) == (Extensible*)(ISR->Sock))
392                                         {
393                                                 VerifyCertificate(session, (BufferedSocket*)ISR->Sock);
394                                                 return "OK";
395                                         }
396                                 }
397                         }
398                 }
399                 return NULL;
400         }
401
402
403         virtual void OnRawSocketAccept(int fd, const std::string &ip, int localport)
404         {
405                 /* Are there any possibilities of an out of range fd? Hope not, but lets be paranoid */
406                 if ((fd < 0) || (fd > ServerInstance->SE->GetMaxFds() - 1))
407                         return;
408
409                 issl_session* session = &sessions[fd];
410
411                 /* For STARTTLS: Don't try and init a session on a socket that already has a session */
412                 if (session->sess)
413                         return;
414
415                 session->fd = fd;
416                 session->inbuf = new char[inbufsize];
417                 session->inbufoffset = 0;
418
419                 gnutls_init(&session->sess, GNUTLS_SERVER);
420
421                 gnutls_set_default_priority(session->sess); // Avoid calling all the priority functions, defaults are adequate.
422                 gnutls_credentials_set(session->sess, GNUTLS_CRD_CERTIFICATE, x509_cred);
423                 gnutls_dh_set_prime_bits(session->sess, dh_bits);
424
425                 /* This is an experimental change to avoid a warning on 64bit systems about casting between integer and pointer of different sizes
426                  * This needs testing, but it's easy enough to rollback if need be
427                  * Old: gnutls_transport_set_ptr(session->sess, (gnutls_transport_ptr_t) fd); // Give gnutls the fd for the socket.
428                  * New: gnutls_transport_set_ptr(session->sess, &fd); // Give gnutls the fd for the socket.
429                  *
430                  * With testing this seems to...not work :/
431                  */
432
433                 gnutls_transport_set_ptr(session->sess, (gnutls_transport_ptr_t) fd); // Give gnutls the fd for the socket.
434
435                 gnutls_certificate_server_set_request(session->sess, GNUTLS_CERT_REQUEST); // Request client certificate if any.
436
437                 Handshake(session);
438         }
439
440         virtual void OnRawSocketConnect(int fd)
441         {
442                 /* Are there any possibilities of an out of range fd? Hope not, but lets be paranoid */
443                 if ((fd < 0) || (fd > ServerInstance->SE->GetMaxFds() - 1))
444                         return;
445
446                 issl_session* session = &sessions[fd];
447
448                 session->fd = fd;
449                 session->inbuf = new char[inbufsize];
450                 session->inbufoffset = 0;
451
452                 gnutls_init(&session->sess, GNUTLS_CLIENT);
453
454                 gnutls_set_default_priority(session->sess); // Avoid calling all the priority functions, defaults are adequate.
455                 gnutls_credentials_set(session->sess, GNUTLS_CRD_CERTIFICATE, x509_cred);
456                 gnutls_dh_set_prime_bits(session->sess, dh_bits);
457                 gnutls_transport_set_ptr(session->sess, (gnutls_transport_ptr_t) fd); // Give gnutls the fd for the socket.
458
459                 Handshake(session);
460         }
461
462         virtual void OnRawSocketClose(int fd)
463         {
464                 /* Are there any possibilities of an out of range fd? Hope not, but lets be paranoid */
465                 if ((fd < 0) || (fd > ServerInstance->SE->GetMaxFds()))
466                         return;
467
468                 CloseSession(&sessions[fd]);
469
470                 EventHandler* user = ServerInstance->SE->GetRef(fd);
471
472                 if ((user) && (user->GetExt("ssl_cert", dummy)))
473                 {
474                         ssl_cert* tofree;
475                         user->GetExt("ssl_cert", tofree);
476                         delete tofree;
477                         user->Shrink("ssl_cert");
478                 }
479         }
480
481         virtual int OnRawSocketRead(int fd, char* buffer, unsigned int count, int &readresult)
482         {
483                 /* Are there any possibilities of an out of range fd? Hope not, but lets be paranoid */
484                 if ((fd < 0) || (fd > ServerInstance->SE->GetMaxFds() - 1))
485                         return 0;
486
487                 issl_session* session = &sessions[fd];
488
489                 if (!session->sess)
490                 {
491                         readresult = 0;
492                         CloseSession(session);
493                         return 1;
494                 }
495
496                 if (session->status == ISSL_HANDSHAKING_READ)
497                 {
498                         // The handshake isn't finished, try to finish it.
499
500                         if(!Handshake(session))
501                         {
502                                 // Couldn't resume handshake.
503                                 return -1;
504                         }
505                 }
506                 else if (session->status == ISSL_HANDSHAKING_WRITE)
507                 {
508                         errno = EAGAIN;
509                         MakePollWrite(session);
510                         return -1;
511                 }
512
513                 // If we resumed the handshake then session->status will be ISSL_HANDSHAKEN.
514
515                 if (session->status == ISSL_HANDSHAKEN)
516                 {
517                         // Is this right? Not sure if the unencrypted data is garaunteed to be the same length.
518                         // Read into the inbuffer, offset from the beginning by the amount of data we have that insp hasn't taken yet.
519                         int ret = gnutls_record_recv(session->sess, session->inbuf + session->inbufoffset, inbufsize - session->inbufoffset);
520
521                         if (ret == 0)
522                         {
523                                 // Client closed connection.
524                                 readresult = 0;
525                                 CloseSession(session);
526                                 return 1;
527                         }
528                         else if (ret < 0)
529                         {
530                                 if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
531                                 {
532                                         errno = EAGAIN;
533                                         return -1;
534                                 }
535                                 else
536                                 {
537                                         ServerInstance->Logs->Log("m_ssl_gnutls", DEFAULT,
538                                                         "m_ssl_gnutls.so: Error while reading on fd %d: %s",
539                                                         session->fd, gnutls_strerror(ret));
540                                         readresult = 0;
541                                         CloseSession(session);
542                                 }
543                         }
544                         else
545                         {
546                                 // Read successfully 'ret' bytes into inbuf + inbufoffset
547                                 // There are 'ret' + 'inbufoffset' bytes of data in 'inbuf'
548                                 // 'buffer' is 'count' long
549
550                                 unsigned int length = ret + session->inbufoffset;
551
552                                 if(count <= length)
553                                 {
554                                         memcpy(buffer, session->inbuf, count);
555                                         // Move the stuff left in inbuf to the beginning of it
556                                         memmove(session->inbuf, session->inbuf + count, (length - count));
557                                         // Now we need to set session->inbufoffset to the amount of data still waiting to be handed to insp.
558                                         session->inbufoffset = length - count;
559                                         // Insp uses readresult as the count of how much data there is in buffer, so:
560                                         readresult = count;
561                                 }
562                                 else
563                                 {
564                                         // There's not as much in the inbuf as there is space in the buffer, so just copy the whole thing.
565                                         memcpy(buffer, session->inbuf, length);
566                                         // Zero the offset, as there's nothing there..
567                                         session->inbufoffset = 0;
568                                         // As above
569                                         readresult = length;
570                                 }
571                         }
572                 }
573                 else if(session->status == ISSL_CLOSING)
574                         readresult = 0;
575
576                 return 1;
577         }
578
579         virtual int OnRawSocketWrite(int fd, const char* buffer, int count)
580         {
581                 /* Are there any possibilities of an out of range fd? Hope not, but lets be paranoid */
582                 if ((fd < 0) || (fd > ServerInstance->SE->GetMaxFds() - 1))
583                         return 0;
584
585                 issl_session* session = &sessions[fd];
586                 const char* sendbuffer = buffer;
587
588                 if (!session->sess)
589                 {
590                         CloseSession(session);
591                         return 1;
592                 }
593
594                 session->outbuf.append(sendbuffer, count);
595                 sendbuffer = session->outbuf.c_str();
596                 count = session->outbuf.size();
597
598                 if (session->status == ISSL_HANDSHAKING_WRITE)
599                 {
600                         // The handshake isn't finished, try to finish it.
601                         Handshake(session);
602                         errno = EAGAIN;
603                         return -1;
604                 }
605
606                 int ret = 0;
607
608                 if (session->status == ISSL_HANDSHAKEN)
609                 {
610                         ret = gnutls_record_send(session->sess, sendbuffer, count);
611
612                         if (ret == 0)
613                         {
614                                 CloseSession(session);
615                         }
616                         else if (ret < 0)
617                         {
618                                 if(ret != GNUTLS_E_AGAIN && ret != GNUTLS_E_INTERRUPTED)
619                                 {
620                                         ServerInstance->Logs->Log("m_ssl_gnutls", DEFAULT,
621                                                         "m_ssl_gnutls.so: Error while writing to fd %d: %s",
622                                                         session->fd, gnutls_strerror(ret));
623                                         CloseSession(session);
624                                 }
625                                 else
626                                 {
627                                         errno = EAGAIN;
628                                 }
629                         }
630                         else
631                         {
632                                 session->outbuf = session->outbuf.substr(ret);
633                         }
634                 }
635
636                 MakePollWrite(session);
637
638                 /* Who's smart idea was it to return 1 when we havent written anything?
639                  * This fucks the buffer up in BufferedSocket :p
640                  */
641                 return ret < 1 ? 0 : ret;
642         }
643
644         // :kenny.chatspike.net 320 Om Epy|AFK :is a Secure Connection
645         virtual void OnWhois(User* source, User* dest)
646         {
647                 if (!clientactive)
648                         return;
649
650                 // Bugfix, only send this numeric for *our* SSL users
651                 if (dest->GetExt("ssl", dummy) || ((IS_LOCAL(dest) && (dest->GetIOHook() == this))))
652                 {
653                         ServerInstance->SendWhoisLine(source, dest, 320, "%s %s :is using a secure connection", source->nick.c_str(), dest->nick.c_str());
654                 }
655         }
656
657         virtual void OnSyncUserMetaData(User* user, Module* proto, void* opaque, const std::string &extname, bool displayable)
658         {
659                 // check if the linking module wants to know about OUR metadata
660                 if(extname == "ssl")
661                 {
662                         // check if this user has an swhois field to send
663                         if(user->GetExt(extname, dummy))
664                         {
665                                 // call this function in the linking module, let it format the data how it
666                                 // sees fit, and send it on its way. We dont need or want to know how.
667                                 proto->ProtoSendMetaData(opaque, TYPE_USER, user, extname, displayable ? "Enabled" : "ON");
668                         }
669                 }
670         }
671
672         virtual void OnDecodeMetaData(int target_type, void* target, const std::string &extname, const std::string &extdata)
673         {
674                 // check if its our metadata key, and its associated with a user
675                 if ((target_type == TYPE_USER) && (extname == "ssl"))
676                 {
677                         User* dest = (User*)target;
678                         // if they dont already have an ssl flag, accept the remote server's
679                         if (!dest->GetExt(extname, dummy))
680                         {
681                                 dest->Extend(extname, "ON");
682                         }
683                 }
684         }
685
686         bool Handshake(issl_session* session)
687         {
688                 int ret = gnutls_handshake(session->sess);
689
690                 if (ret < 0)
691                 {
692                         if(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
693                         {
694                                 // Handshake needs resuming later, read() or write() would have blocked.
695
696                                 if(gnutls_record_get_direction(session->sess) == 0)
697                                 {
698                                         // gnutls_handshake() wants to read() again.
699                                         session->status = ISSL_HANDSHAKING_READ;
700                                 }
701                                 else
702                                 {
703                                         // gnutls_handshake() wants to write() again.
704                                         session->status = ISSL_HANDSHAKING_WRITE;
705                                         MakePollWrite(session);
706                                 }
707                         }
708                         else
709                         {
710                                 // Handshake failed.
711                                 ServerInstance->Logs->Log("m_ssl_gnutls", DEFAULT,
712                                                 "m_ssl_gnutls.so: Handshake failed on fd %d: %s",
713                                                 session->fd, gnutls_strerror(ret));
714                                 CloseSession(session);
715                                 session->status = ISSL_CLOSING;
716                         }
717
718                         return false;
719                 }
720                 else
721                 {
722                         // Handshake complete.
723                         // This will do for setting the ssl flag...it could be done earlier if it's needed. But this seems neater.
724                         User* extendme = ServerInstance->FindDescriptor(session->fd);
725                         if (extendme)
726                         {
727                                 if (!extendme->GetExt("ssl", dummy))
728                                         extendme->Extend("ssl", "ON");
729                         }
730
731                         // Change the seesion state
732                         session->status = ISSL_HANDSHAKEN;
733
734                         // Finish writing, if any left
735                         MakePollWrite(session);
736
737                         return true;
738                 }
739         }
740
741         virtual void OnPostConnect(User* user)
742         {
743                 // This occurs AFTER OnUserConnect so we can be sure the
744                 // protocol module has propagated the NICK message.
745                 if ((user->GetExt("ssl", dummy)) && (IS_LOCAL(user)))
746                 {
747                         // Tell whatever protocol module we're using that we need to inform other servers of this metadata NOW.
748                         ServerInstance->PI->SendMetaData(user, TYPE_USER, "SSL", "on");
749
750                         VerifyCertificate(&sessions[user->GetFd()],user);
751                         if (sessions[user->GetFd()].sess)
752                         {
753                                 std::string cipher = gnutls_kx_get_name(gnutls_kx_get(sessions[user->GetFd()].sess));
754                                 cipher.append("-").append(gnutls_cipher_get_name(gnutls_cipher_get(sessions[user->GetFd()].sess))).append("-");
755                                 cipher.append(gnutls_mac_get_name(gnutls_mac_get(sessions[user->GetFd()].sess)));
756                                 user->WriteServ("NOTICE %s :*** You are connected using SSL cipher \"%s\"", user->nick.c_str(), cipher.c_str());
757                         }
758                 }
759         }
760
761         void MakePollWrite(issl_session* session)
762         {
763                 //OnRawSocketWrite(session->fd, NULL, 0);
764                 EventHandler* eh = ServerInstance->FindDescriptor(session->fd);
765                 if (eh)
766                         ServerInstance->SE->WantWrite(eh);
767         }
768
769         virtual void OnBufferFlushed(User* user)
770         {
771                 if (user->GetExt("ssl"))
772                 {
773                         issl_session* session = &sessions[user->GetFd()];
774                         if (session && session->outbuf.size())
775                                 OnRawSocketWrite(user->GetFd(), NULL, 0);
776                 }
777         }
778
779         void CloseSession(issl_session* session)
780         {
781                 if(session->sess)
782                 {
783                         gnutls_bye(session->sess, GNUTLS_SHUT_WR);
784                         gnutls_deinit(session->sess);
785                 }
786
787                 if(session->inbuf)
788                 {
789                         delete[] session->inbuf;
790                 }
791
792                 session->outbuf.clear();
793                 session->inbuf = NULL;
794                 session->sess = NULL;
795                 session->status = ISSL_NONE;
796         }
797
798         void VerifyCertificate(issl_session* session, Extensible* user)
799         {
800                 if (!session->sess || !user)
801                         return;
802
803                 unsigned int status;
804                 const gnutls_datum_t* cert_list;
805                 int ret;
806                 unsigned int cert_list_size;
807                 gnutls_x509_crt_t cert;
808                 char name[MAXBUF];
809                 unsigned char digest[MAXBUF];
810                 size_t digest_size = sizeof(digest);
811                 size_t name_size = sizeof(name);
812                 ssl_cert* certinfo = new ssl_cert;
813
814                 user->Extend("ssl_cert",certinfo);
815
816                 /* This verification function uses the trusted CAs in the credentials
817                  * structure. So you must have installed one or more CA certificates.
818                  */
819                 ret = gnutls_certificate_verify_peers2(session->sess, &status);
820
821                 if (ret < 0)
822                 {
823                         certinfo->data.insert(std::make_pair("error",std::string(gnutls_strerror(ret))));
824                         return;
825                 }
826
827                 if (status & GNUTLS_CERT_INVALID)
828                 {
829                         certinfo->data.insert(std::make_pair("invalid",ConvToStr(1)));
830                 }
831                 else
832                 {
833                         certinfo->data.insert(std::make_pair("invalid",ConvToStr(0)));
834                 }
835                 if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
836                 {
837                         certinfo->data.insert(std::make_pair("unknownsigner",ConvToStr(1)));
838                 }
839                 else
840                 {
841                         certinfo->data.insert(std::make_pair("unknownsigner",ConvToStr(0)));
842                 }
843                 if (status & GNUTLS_CERT_REVOKED)
844                 {
845                         certinfo->data.insert(std::make_pair("revoked",ConvToStr(1)));
846                 }
847                 else
848                 {
849                         certinfo->data.insert(std::make_pair("revoked",ConvToStr(0)));
850                 }
851                 if (status & GNUTLS_CERT_SIGNER_NOT_CA)
852                 {
853                         certinfo->data.insert(std::make_pair("trusted",ConvToStr(0)));
854                 }
855                 else
856                 {
857                         certinfo->data.insert(std::make_pair("trusted",ConvToStr(1)));
858                 }
859
860                 /* Up to here the process is the same for X.509 certificates and
861                  * OpenPGP keys. From now on X.509 certificates are assumed. This can
862                  * be easily extended to work with openpgp keys as well.
863                  */
864                 if (gnutls_certificate_type_get(session->sess) != GNUTLS_CRT_X509)
865                 {
866                         certinfo->data.insert(std::make_pair("error","No X509 keys sent"));
867                         return;
868                 }
869
870                 ret = gnutls_x509_crt_init(&cert);
871                 if (ret < 0)
872                 {
873                         certinfo->data.insert(std::make_pair("error",gnutls_strerror(ret)));
874                         return;
875                 }
876
877                 cert_list_size = 0;
878                 cert_list = gnutls_certificate_get_peers(session->sess, &cert_list_size);
879                 if (cert_list == NULL)
880                 {
881                         certinfo->data.insert(std::make_pair("error","No certificate was found"));
882                         return;
883                 }
884
885                 /* This is not a real world example, since we only check the first
886                  * certificate in the given chain.
887                  */
888
889                 ret = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER);
890                 if (ret < 0)
891                 {
892                         certinfo->data.insert(std::make_pair("error",gnutls_strerror(ret)));
893                         return;
894                 }
895
896                 gnutls_x509_crt_get_dn(cert, name, &name_size);
897
898                 certinfo->data.insert(std::make_pair("dn",name));
899
900                 gnutls_x509_crt_get_issuer_dn(cert, name, &name_size);
901
902                 certinfo->data.insert(std::make_pair("issuer",name));
903
904                 if ((ret = gnutls_x509_crt_get_fingerprint(cert, GNUTLS_DIG_MD5, digest, &digest_size)) < 0)
905                 {
906                         certinfo->data.insert(std::make_pair("error",gnutls_strerror(ret)));
907                 }
908                 else
909                 {
910                         certinfo->data.insert(std::make_pair("fingerprint",irc::hex(digest, digest_size)));
911                 }
912
913                 /* Beware here we do not check for errors.
914                  */
915                 if ((gnutls_x509_crt_get_expiration_time(cert) < ServerInstance->Time()) || (gnutls_x509_crt_get_activation_time(cert) > ServerInstance->Time()))
916                 {
917                         certinfo->data.insert(std::make_pair("error","Not activated, or expired certificate"));
918                 }
919
920                 gnutls_x509_crt_deinit(cert);
921
922                 return;
923         }
924
925         void OnEvent(Event* ev)
926         {
927                 GenericCapHandler(ev, "tls", "tls");
928         }
929
930         void Prioritize()
931         {
932                 Module* server = ServerInstance->Modules->Find("m_spanningtree.so");
933                 ServerInstance->Modules->SetPriority(this, I_OnPostConnect, PRIO_AFTER, &server);
934         }
935 };
936
937 MODULE_INIT(ModuleSSLGnuTLS)