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