The InspIRCd Project
Home | Developers | Wiki | Forums | Bug Tracker | SVN | Download | Blog | Stats
Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Namespace Members | Class Members | File Members

m_ssl_gnutls.cpp

Go to the documentation of this file.
00001 /*       +------------------------------------+
00002  *       | Inspire Internet Relay Chat Daemon |
00003  *       +------------------------------------+
00004  *
00005  *  InspIRCd: (C) 2002-2008 InspIRCd Development Team
00006  * See: http://www.inspircd.org/wiki/index.php/Credits
00007  *
00008  * This program is free but copyrighted software; see
00009  *            the file COPYING for details.
00010  *
00011  * ---------------------------------------------------
00012  */
00013 
00014 #include "inspircd.h"
00015 
00016 #include <gnutls/gnutls.h>
00017 #include <gnutls/x509.h>
00018 
00019 #include "inspircd_config.h"
00020 #include "configreader.h"
00021 #include "users.h"
00022 #include "channels.h"
00023 #include "modules.h"
00024 #include "socket.h"
00025 #include "hashcomp.h"
00026 #include "transport.h"
00027 
00028 #ifdef WINDOWS
00029 #pragma comment(lib, "libgnutls-13.lib")
00030 #undef MAX_DESCRIPTORS
00031 #define MAX_DESCRIPTORS 10000
00032 #endif
00033 
00034 /* $ModDesc: Provides SSL support for clients */
00035 /* $CompileFlags: pkgconfincludes("gnutls","/gnutls/gnutls.h","") */
00036 /* $LinkerFlags: rpath("pkg-config --libs gnutls") pkgconflibs("gnutls","/libgnutls.so","-lgnutls") */
00037 /* $ModDep: transport.h */
00038 
00039 enum issl_status { ISSL_NONE, ISSL_HANDSHAKING_READ, ISSL_HANDSHAKING_WRITE, ISSL_HANDSHAKEN, ISSL_CLOSING, ISSL_CLOSED };
00040 
00041 bool isin(int port, const std::vector<int> &portlist)
00042 {
00043         for(unsigned int i = 0; i < portlist.size(); i++)
00044                 if(portlist[i] == port)
00045                         return true;
00046 
00047         return false;
00048 }
00049 
00052 class issl_session : public classbase
00053 {
00054 public:
00055         issl_session()
00056         {
00057                 sess = NULL;
00058         }
00059         gnutls_session_t sess;
00060         issl_status status;
00061         std::string outbuf;
00062         int inbufoffset;
00063         char* inbuf;
00064         int fd;
00065 };
00066 
00067 class ModuleSSLGnuTLS : public Module
00068 {
00069 
00070         ConfigReader* Conf;
00071 
00072         char* dummy;
00073 
00074         std::vector<int> listenports;
00075 
00076         int inbufsize;
00077         issl_session sessions[MAX_DESCRIPTORS];
00078 
00079         gnutls_certificate_credentials x509_cred;
00080         gnutls_dh_params dh_params;
00081 
00082         std::string keyfile;
00083         std::string certfile;
00084         std::string cafile;
00085         std::string crlfile;
00086         std::string sslports;
00087         int dh_bits;
00088 
00089         int clientactive;
00090         bool cred_alloc;
00091 
00092  public:
00093 
00094         ModuleSSLGnuTLS(InspIRCd* Me)
00095                 : Module(Me)
00096         {
00097                 ServerInstance->PublishInterface("InspSocketHook", this);
00098 
00099                 // Not rehashable...because I cba to reduce all the sizes of existing buffers.
00100                 inbufsize = ServerInstance->Config->NetBufferSize;
00101 
00102                 gnutls_global_init(); // This must be called once in the program
00103                 
00104                 cred_alloc = false;
00105                 // Needs the flag as it ignores a plain /rehash
00106                 OnRehash(NULL,"ssl");
00107 
00108                 // Void return, guess we assume success
00109                 gnutls_certificate_set_dh_params(x509_cred, dh_params);
00110         }
00111 
00112         virtual void OnRehash(userrec* user, const std::string &param)
00113         {
00114                 Conf = new ConfigReader(ServerInstance);
00115 
00116                 for(unsigned int i = 0; i < listenports.size(); i++)
00117                 {
00118                         ServerInstance->Config->DelIOHook(listenports[i]);
00119                 }
00120 
00121                 listenports.clear();
00122                 clientactive = 0;
00123                 sslports.clear();
00124 
00125                 for(int i = 0; i < Conf->Enumerate("bind"); i++)
00126                 {
00127                         // For each <bind> tag
00128                         std::string x = Conf->ReadValue("bind", "type", i);
00129                         if(((x.empty()) || (x == "clients")) && (Conf->ReadValue("bind", "ssl", i) == "gnutls"))
00130                         {
00131                                 // Get the port we're meant to be listening on with SSL
00132                                 std::string port = Conf->ReadValue("bind", "port", i);
00133                                 irc::portparser portrange(port, false);
00134                                 long portno = -1;
00135                                 while ((portno = portrange.GetToken()))
00136                                 {
00137                                         clientactive++;
00138                                         try
00139                                         {
00140                                                 if (ServerInstance->Config->AddIOHook(portno, this))
00141                                                 {
00142                                                         listenports.push_back(portno);
00143                                                         for (size_t i = 0; i < ServerInstance->Config->ports.size(); i++)
00144                                                                 if (ServerInstance->Config->ports[i]->GetPort() == portno)
00145                                                                         ServerInstance->Config->ports[i]->SetDescription("ssl");
00146                                                         ServerInstance->Log(DEFAULT, "m_ssl_gnutls.so: Enabling SSL for port %d", portno);
00147                                                         sslports.append("*:").append(ConvToStr(portno)).append(";");
00148                                                 }
00149                                                 else
00150                                                 {
00151                                                         ServerInstance->Log(DEFAULT, "m_ssl_gnutls.so: FAILED to enable SSL on port %d, maybe you have another ssl or similar module loaded?", portno);
00152                                                 }
00153                                         }
00154                                         catch (ModuleException &e)
00155                                         {
00156                                                 ServerInstance->Log(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());
00157                                         }
00158                                 }
00159                         }
00160                 }
00161 
00162                 if(param != "ssl")
00163                 {
00164                         delete Conf;
00165                         return;
00166                 }
00167 
00168                 std::string confdir(ServerInstance->ConfigFileName);
00169                 // +1 so we the path ends with a /
00170                 confdir = confdir.substr(0, confdir.find_last_of('/') + 1);
00171 
00172                 cafile  = Conf->ReadValue("gnutls", "cafile", 0);
00173                 crlfile = Conf->ReadValue("gnutls", "crlfile", 0);
00174                 certfile        = Conf->ReadValue("gnutls", "certfile", 0);
00175                 keyfile = Conf->ReadValue("gnutls", "keyfile", 0);
00176                 dh_bits = Conf->ReadInteger("gnutls", "dhbits", 0, false);
00177 
00178                 // Set all the default values needed.
00179                 if (cafile.empty())
00180                         cafile = "ca.pem";
00181 
00182                 if (crlfile.empty())
00183                         crlfile = "crl.pem";
00184 
00185                 if (certfile.empty())
00186                         certfile = "cert.pem";
00187 
00188                 if (keyfile.empty())
00189                         keyfile = "key.pem";
00190 
00191                 if((dh_bits != 768) && (dh_bits != 1024) && (dh_bits != 2048) && (dh_bits != 3072) && (dh_bits != 4096))
00192                         dh_bits = 1024;
00193 
00194                 // Prepend relative paths with the path to the config directory.
00195                 if(cafile[0] != '/')
00196                         cafile = confdir + cafile;
00197 
00198                 if(crlfile[0] != '/')
00199                         crlfile = confdir + crlfile;
00200 
00201                 if(certfile[0] != '/')
00202                         certfile = confdir + certfile;
00203 
00204                 if(keyfile[0] != '/')
00205                         keyfile = confdir + keyfile;
00206 
00207                 int ret;
00208                 
00209                 if (cred_alloc)
00210                 {
00211                         // Deallocate the old credentials
00212                         gnutls_dh_params_deinit(dh_params);
00213                         gnutls_certificate_free_credentials(x509_cred);
00214                 }
00215                 else
00216                         cred_alloc = true;
00217                 
00218                 if((ret = gnutls_certificate_allocate_credentials(&x509_cred)) < 0)
00219                         ServerInstance->Log(DEFAULT, "m_ssl_gnutls.so: Failed to allocate certificate credentials: %s", gnutls_strerror(ret));
00220                 
00221                 if((ret = gnutls_dh_params_init(&dh_params)) < 0)
00222                         ServerInstance->Log(DEFAULT, "m_ssl_gnutls.so: Failed to initialise DH parameters: %s", gnutls_strerror(ret));
00223                 
00224                 if((ret =gnutls_certificate_set_x509_trust_file(x509_cred, cafile.c_str(), GNUTLS_X509_FMT_PEM)) < 0)
00225                         ServerInstance->Log(DEFAULT, "m_ssl_gnutls.so: Failed to set X.509 trust file '%s': %s", cafile.c_str(), gnutls_strerror(ret));
00226 
00227                 if((ret = gnutls_certificate_set_x509_crl_file (x509_cred, crlfile.c_str(), GNUTLS_X509_FMT_PEM)) < 0)
00228                         ServerInstance->Log(DEFAULT, "m_ssl_gnutls.so: Failed to set X.509 CRL file '%s': %s", crlfile.c_str(), gnutls_strerror(ret));
00229 
00230                 if((ret = gnutls_certificate_set_x509_key_file (x509_cred, certfile.c_str(), keyfile.c_str(), GNUTLS_X509_FMT_PEM)) < 0)
00231                 {
00232                         // If this fails, no SSL port will work. At all. So, do the smart thing - throw a ModuleException
00233                         throw ModuleException("Unable to load GnuTLS server certificate (" + certfile + ", key: " + keyfile + "): " + std::string(gnutls_strerror(ret)));
00234                 }
00235 
00236                 // This may be on a large (once a day or week) timer eventually.
00237                 GenerateDHParams();
00238 
00239                 DELETE(Conf);
00240         }
00241 
00242         void GenerateDHParams()
00243         {
00244                 // Generate Diffie Hellman parameters - for use with DHE
00245                 // kx algorithms. These should be discarded and regenerated
00246                 // once a day, once a week or once a month. Depending on the
00247                 // security requirements.
00248 
00249                 int ret;
00250 
00251                 if((ret = gnutls_dh_params_generate2(dh_params, dh_bits)) < 0)
00252                         ServerInstance->Log(DEFAULT, "m_ssl_gnutls.so: Failed to generate DH parameters (%d bits): %s", dh_bits, gnutls_strerror(ret));
00253         }
00254 
00255         virtual ~ModuleSSLGnuTLS()
00256         {
00257                 gnutls_dh_params_deinit(dh_params);
00258                 gnutls_certificate_free_credentials(x509_cred);
00259                 gnutls_global_deinit();
00260                 ServerInstance->UnpublishInterface("InspSocketHook", this);
00261         }
00262 
00263         virtual void OnCleanup(int target_type, void* item)
00264         {
00265                 if(target_type == TYPE_USER)
00266                 {
00267                         userrec* user = (userrec*)item;
00268 
00269                         if(user->GetExt("ssl", dummy) && isin(user->GetPort(), listenports))
00270                         {
00271                                 // User is using SSL, they're a local user, and they're using one of *our* SSL ports.
00272                                 // Potentially there could be multiple SSL modules loaded at once on different ports.
00273                                 userrec::QuitUser(ServerInstance, user, "SSL module unloading");
00274                         }
00275                         if (user->GetExt("ssl_cert", dummy) && isin(user->GetPort(), listenports))
00276                         {
00277                                 ssl_cert* tofree;
00278                                 user->GetExt("ssl_cert", tofree);
00279                                 delete tofree;
00280                                 user->Shrink("ssl_cert");
00281                         }
00282                 }
00283         }
00284 
00285         virtual void OnUnloadModule(Module* mod, const std::string &name)
00286         {
00287                 if(mod == this)
00288                 {
00289                         for(unsigned int i = 0; i < listenports.size(); i++)
00290                         {
00291                                 ServerInstance->Config->DelIOHook(listenports[i]);
00292                                 for (size_t j = 0; j < ServerInstance->Config->ports.size(); j++)
00293                                         if (ServerInstance->Config->ports[j]->GetPort() == listenports[i])
00294                                                 ServerInstance->Config->ports[j]->SetDescription("plaintext");
00295                         }
00296                 }
00297         }
00298 
00299         virtual Version GetVersion()
00300         {
00301                 return Version(1, 1, 0, 0, VF_VENDOR, API_VERSION);
00302         }
00303 
00304         void Implements(char* List)
00305         {
00306                 List[I_On005Numeric] = List[I_OnRawSocketConnect] = List[I_OnRawSocketAccept] = List[I_OnRawSocketClose] = List[I_OnRawSocketRead] = List[I_OnRawSocketWrite] = List[I_OnCleanup] = 1;
00307                 List[I_OnBufferFlushed] = List[I_OnRequest] = List[I_OnSyncUserMetaData] = List[I_OnDecodeMetaData] = List[I_OnUnloadModule] = List[I_OnRehash] = List[I_OnWhois] = List[I_OnPostConnect] = 1;
00308         }
00309 
00310         virtual void On005Numeric(std::string &output)
00311         {
00312                 output.append(" SSL=" + sslports);
00313         }
00314 
00315         virtual char* OnRequest(Request* request)
00316         {
00317                 ISHRequest* ISR = (ISHRequest*)request;
00318                 if (strcmp("IS_NAME", request->GetId()) == 0)
00319                 {
00320                         return "gnutls";
00321                 }
00322                 else if (strcmp("IS_HOOK", request->GetId()) == 0)
00323                 {
00324                         char* ret = "OK";
00325                         try
00326                         {
00327                                 ret = ServerInstance->Config->AddIOHook((Module*)this, (InspSocket*)ISR->Sock) ? (char*)"OK" : NULL;
00328                         }
00329                         catch (ModuleException &e)
00330                         {
00331                                 return NULL;
00332                         }
00333                         return ret;
00334                 }
00335                 else if (strcmp("IS_UNHOOK", request->GetId()) == 0)
00336                 {
00337                         return ServerInstance->Config->DelIOHook((InspSocket*)ISR->Sock) ? (char*)"OK" : NULL;
00338                 }
00339                 else if (strcmp("IS_HSDONE", request->GetId()) == 0)
00340                 {
00341                         if (ISR->Sock->GetFd() < 0)
00342                                 return (char*)"OK";
00343 
00344                         issl_session* session = &sessions[ISR->Sock->GetFd()];
00345                         return (session->status == ISSL_HANDSHAKING_READ || session->status == ISSL_HANDSHAKING_WRITE) ? NULL : (char*)"OK";
00346                 }
00347                 else if (strcmp("IS_ATTACH", request->GetId()) == 0)
00348                 {
00349                         if (ISR->Sock->GetFd() > -1)
00350                         {
00351                                 issl_session* session = &sessions[ISR->Sock->GetFd()];
00352                                 if (session->sess)
00353                                 {
00354                                         if ((Extensible*)ServerInstance->FindDescriptor(ISR->Sock->GetFd()) == (Extensible*)(ISR->Sock))
00355                                         {
00356                                                 VerifyCertificate(session, (InspSocket*)ISR->Sock);
00357                                                 return "OK";
00358                                         }
00359                                 }
00360                         }
00361                 }
00362                 return NULL;
00363         }
00364 
00365 
00366         virtual void OnRawSocketAccept(int fd, const std::string &ip, int localport)
00367         {
00368                 /* Are there any possibilities of an out of range fd? Hope not, but lets be paranoid */
00369                 if ((fd < 0) || (fd > MAX_DESCRIPTORS))
00370                         return;
00371 
00372                 issl_session* session = &sessions[fd];
00373 
00374                 session->fd = fd;
00375                 session->inbuf = new char[inbufsize];
00376                 session->inbufoffset = 0;
00377 
00378                 gnutls_init(&session->sess, GNUTLS_SERVER);
00379 
00380                 gnutls_set_default_priority(session->sess); // Avoid calling all the priority functions, defaults are adequate.
00381                 gnutls_credentials_set(session->sess, GNUTLS_CRD_CERTIFICATE, x509_cred);
00382                 gnutls_dh_set_prime_bits(session->sess, dh_bits);
00383 
00384                 /* This is an experimental change to avoid a warning on 64bit systems about casting between integer and pointer of different sizes
00385                  * This needs testing, but it's easy enough to rollback if need be
00386                  * Old: gnutls_transport_set_ptr(session->sess, (gnutls_transport_ptr_t) fd); // Give gnutls the fd for the socket.
00387                  * New: gnutls_transport_set_ptr(session->sess, &fd); // Give gnutls the fd for the socket.
00388                  *
00389                  * With testing this seems to...not work :/
00390                  */
00391 
00392                 gnutls_transport_set_ptr(session->sess, (gnutls_transport_ptr_t) fd); // Give gnutls the fd for the socket.
00393 
00394                 gnutls_certificate_server_set_request(session->sess, GNUTLS_CERT_REQUEST); // Request client certificate if any.
00395 
00396                 Handshake(session);
00397         }
00398 
00399         virtual void OnRawSocketConnect(int fd)
00400         {
00401                 /* Are there any possibilities of an out of range fd? Hope not, but lets be paranoid */
00402                 if ((fd < 0) || (fd > MAX_DESCRIPTORS))
00403                         return;
00404 
00405                 issl_session* session = &sessions[fd];
00406 
00407                 session->fd = fd;
00408                 session->inbuf = new char[inbufsize];
00409                 session->inbufoffset = 0;
00410 
00411                 gnutls_init(&session->sess, GNUTLS_CLIENT);
00412 
00413                 gnutls_set_default_priority(session->sess); // Avoid calling all the priority functions, defaults are adequate.
00414                 gnutls_credentials_set(session->sess, GNUTLS_CRD_CERTIFICATE, x509_cred);
00415                 gnutls_dh_set_prime_bits(session->sess, dh_bits);
00416                 gnutls_transport_set_ptr(session->sess, (gnutls_transport_ptr_t) fd); // Give gnutls the fd for the socket.
00417 
00418                 Handshake(session);
00419         }
00420 
00421         virtual void OnRawSocketClose(int fd)
00422         {
00423                 /* Are there any possibilities of an out of range fd? Hope not, but lets be paranoid */
00424                 if ((fd < 0) || (fd > MAX_DESCRIPTORS))
00425                         return;
00426 
00427                 CloseSession(&sessions[fd]);
00428 
00429                 EventHandler* user = ServerInstance->SE->GetRef(fd);
00430 
00431                 if ((user) && (user->GetExt("ssl_cert", dummy)))
00432                 {
00433                         ssl_cert* tofree;
00434                         user->GetExt("ssl_cert", tofree);
00435                         delete tofree;
00436                         user->Shrink("ssl_cert");
00437                 }
00438         }
00439 
00440         virtual int OnRawSocketRead(int fd, char* buffer, unsigned int count, int &readresult)
00441         {
00442                 /* Are there any possibilities of an out of range fd? Hope not, but lets be paranoid */
00443                 if ((fd < 0) || (fd > MAX_DESCRIPTORS))
00444                         return 0;
00445 
00446                 issl_session* session = &sessions[fd];
00447 
00448                 if (!session->sess)
00449                 {
00450                         readresult = 0;
00451                         CloseSession(session);
00452                         return 1;
00453                 }
00454 
00455                 if (session->status == ISSL_HANDSHAKING_READ)
00456                 {
00457                         // The handshake isn't finished, try to finish it.
00458 
00459                         if(!Handshake(session))
00460                         {
00461                                 // Couldn't resume handshake.
00462                                 return -1;
00463                         }
00464                 }
00465                 else if (session->status == ISSL_HANDSHAKING_WRITE)
00466                 {
00467                         errno = EAGAIN;
00468                         MakePollWrite(session);
00469                         return -1;
00470                 }
00471 
00472                 // If we resumed the handshake then session->status will be ISSL_HANDSHAKEN.
00473 
00474                 if (session->status == ISSL_HANDSHAKEN)
00475                 {
00476                         // Is this right? Not sure if the unencrypted data is garaunteed to be the same length.
00477                         // Read into the inbuffer, offset from the beginning by the amount of data we have that insp hasn't taken yet.
00478                         int ret = gnutls_record_recv(session->sess, session->inbuf + session->inbufoffset, inbufsize - session->inbufoffset);
00479 
00480                         if (ret == 0)
00481                         {
00482                                 // Client closed connection.
00483                                 readresult = 0;
00484                                 CloseSession(session);
00485                                 return 1;
00486                         }
00487                         else if (ret < 0)
00488                         {
00489                                 if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
00490                                 {
00491                                         errno = EAGAIN;
00492                                         return -1;
00493                                 }
00494                                 else
00495                                 {
00496                                         readresult = 0;
00497                                         CloseSession(session);
00498                                 }
00499                         }
00500                         else
00501                         {
00502                                 // Read successfully 'ret' bytes into inbuf + inbufoffset
00503                                 // There are 'ret' + 'inbufoffset' bytes of data in 'inbuf'
00504                                 // 'buffer' is 'count' long
00505 
00506                                 unsigned int length = ret + session->inbufoffset;
00507 
00508                                 if(count <= length)
00509                                 {
00510                                         memcpy(buffer, session->inbuf, count);
00511                                         // Move the stuff left in inbuf to the beginning of it
00512                                         memmove(session->inbuf, session->inbuf + count, (length - count));
00513                                         // Now we need to set session->inbufoffset to the amount of data still waiting to be handed to insp.
00514                                         session->inbufoffset = length - count;
00515                                         // Insp uses readresult as the count of how much data there is in buffer, so:
00516                                         readresult = count;
00517                                 }
00518                                 else
00519                                 {
00520                                         // There's not as much in the inbuf as there is space in the buffer, so just copy the whole thing.
00521                                         memcpy(buffer, session->inbuf, length);
00522                                         // Zero the offset, as there's nothing there..
00523                                         session->inbufoffset = 0;
00524                                         // As above
00525                                         readresult = length;
00526                                 }
00527                         }
00528                 }
00529                 else if(session->status == ISSL_CLOSING)
00530                         readresult = 0;
00531 
00532                 return 1;
00533         }
00534 
00535         virtual int OnRawSocketWrite(int fd, const char* buffer, int count)
00536         {
00537                 /* Are there any possibilities of an out of range fd? Hope not, but lets be paranoid */
00538                 if ((fd < 0) || (fd > MAX_DESCRIPTORS))
00539                         return 0;
00540 
00541                 issl_session* session = &sessions[fd];
00542                 const char* sendbuffer = buffer;
00543 
00544                 if (!session->sess)
00545                 {
00546                         CloseSession(session);
00547                         return 1;
00548                 }
00549 
00550                 session->outbuf.append(sendbuffer, count);
00551                 sendbuffer = session->outbuf.c_str();
00552                 count = session->outbuf.size();
00553 
00554                 if (session->status == ISSL_HANDSHAKING_WRITE || session->status == ISSL_HANDSHAKING_READ)
00555                 {
00556                         // The handshake isn't finished, try to finish it.
00557                         Handshake(session);
00558                         errno = EAGAIN;
00559                         return -1;
00560                 }
00561 
00562                 int ret = 0;
00563 
00564                 if (session->status == ISSL_HANDSHAKEN)
00565                 {
00566                         ret = gnutls_record_send(session->sess, sendbuffer, count);
00567 
00568                         if (ret == 0)
00569                         {
00570                                 CloseSession(session);
00571                         }
00572                         else if (ret < 0)
00573                         {
00574                                 if(ret != GNUTLS_E_AGAIN && ret != GNUTLS_E_INTERRUPTED)
00575                                 {
00576                                         CloseSession(session);
00577                                 }
00578                                 else
00579                                 {
00580                                         errno = EAGAIN;
00581                                 }
00582                         }
00583                         else
00584                         {
00585                                 session->outbuf = session->outbuf.substr(ret);
00586                         }
00587                 }
00588 
00589                 MakePollWrite(session);
00590 
00591                 /* Who's smart idea was it to return 1 when we havent written anything?
00592                  * This fucks the buffer up in InspSocket :p
00593                  */
00594                 return ret < 1 ? 0 : ret;
00595         }
00596 
00597         // :kenny.chatspike.net 320 Om Epy|AFK :is a Secure Connection
00598         virtual void OnWhois(userrec* source, userrec* dest)
00599         {
00600                 if (!clientactive)
00601                         return;
00602 
00603                 // Bugfix, only send this numeric for *our* SSL users
00604                 if(dest->GetExt("ssl", dummy) || (IS_LOCAL(dest) &&  isin(dest->GetPort(), listenports)))
00605                 {
00606                         ServerInstance->SendWhoisLine(source, dest, 320, "%s %s :is using a secure connection", source->nick, dest->nick);
00607                 }
00608         }
00609 
00610         virtual void OnSyncUserMetaData(userrec* user, Module* proto, void* opaque, const std::string &extname, bool displayable)
00611         {
00612                 // check if the linking module wants to know about OUR metadata
00613                 if(extname == "ssl")
00614                 {
00615                         // check if this user has an swhois field to send
00616                         if(user->GetExt(extname, dummy))
00617                         {
00618                                 // call this function in the linking module, let it format the data how it
00619                                 // sees fit, and send it on its way. We dont need or want to know how.
00620                                 proto->ProtoSendMetaData(opaque, TYPE_USER, user, extname, displayable ? "Enabled" : "ON");
00621                         }
00622                 }
00623         }
00624 
00625         virtual void OnDecodeMetaData(int target_type, void* target, const std::string &extname, const std::string &extdata)
00626         {
00627                 // check if its our metadata key, and its associated with a user
00628                 if ((target_type == TYPE_USER) && (extname == "ssl"))
00629                 {
00630                         userrec* dest = (userrec*)target;
00631                         // if they dont already have an ssl flag, accept the remote server's
00632                         if (!dest->GetExt(extname, dummy))
00633                         {
00634                                 dest->Extend(extname, "ON");
00635                         }
00636                 }
00637         }
00638 
00639         bool Handshake(issl_session* session)
00640         {
00641                 int ret = gnutls_handshake(session->sess);
00642 
00643                 if (ret < 0)
00644                 {
00645                         if(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
00646                         {
00647                                 // Handshake needs resuming later, read() or write() would have blocked.
00648 
00649                                 if(gnutls_record_get_direction(session->sess) == 0)
00650                                 {
00651                                         // gnutls_handshake() wants to read() again.
00652                                         session->status = ISSL_HANDSHAKING_READ;
00653                                 }
00654                                 else
00655                                 {
00656                                         // gnutls_handshake() wants to write() again.
00657                                         session->status = ISSL_HANDSHAKING_WRITE;
00658                                         MakePollWrite(session);
00659                                 }
00660                         }
00661                         else
00662                         {
00663                                 // Handshake failed.
00664                                 CloseSession(session);
00665                                 session->status = ISSL_CLOSING;
00666                         }
00667 
00668                         return false;
00669                 }
00670                 else
00671                 {
00672                         // Handshake complete.
00673                         // This will do for setting the ssl flag...it could be done earlier if it's needed. But this seems neater.
00674                         userrec* extendme = ServerInstance->FindDescriptor(session->fd);
00675                         if (extendme)
00676                         {
00677                                 if (!extendme->GetExt("ssl", dummy))
00678                                         extendme->Extend("ssl", "ON");
00679                         }
00680 
00681                         // Change the seesion state
00682                         session->status = ISSL_HANDSHAKEN;
00683 
00684                         // Finish writing, if any left
00685                         MakePollWrite(session);
00686 
00687                         return true;
00688                 }
00689         }
00690 
00691         virtual void OnPostConnect(userrec* user)
00692         {
00693                 // This occurs AFTER OnUserConnect so we can be sure the
00694                 // protocol module has propogated the NICK message.
00695                 if ((user->GetExt("ssl", dummy)) && (IS_LOCAL(user)))
00696                 {
00697                         // Tell whatever protocol module we're using that we need to inform other servers of this metadata NOW.
00698                         std::deque<std::string>* metadata = new std::deque<std::string>;
00699                         metadata->push_back(user->nick);
00700                         metadata->push_back("ssl");             // The metadata id
00701                         metadata->push_back("ON");              // The value to send
00702                         Event* event = new Event((char*)metadata,(Module*)this,"send_metadata");
00703                         event->Send(ServerInstance);            // Trigger the event. We don't care what module picks it up.
00704                         DELETE(event);
00705                         DELETE(metadata);
00706 
00707                         VerifyCertificate(&sessions[user->GetFd()],user);
00708                         if (sessions[user->GetFd()].sess)
00709                         {
00710                                 std::string cipher = gnutls_kx_get_name(gnutls_kx_get(sessions[user->GetFd()].sess));
00711                                 cipher.append("-").append(gnutls_cipher_get_name(gnutls_cipher_get(sessions[user->GetFd()].sess))).append("-");
00712                                 cipher.append(gnutls_mac_get_name(gnutls_mac_get(sessions[user->GetFd()].sess)));
00713                                 user->WriteServ("NOTICE %s :*** You are connected using SSL cipher \"%s\"", user->nick, cipher.c_str());
00714                         }
00715                 }
00716         }
00717 
00718         void MakePollWrite(issl_session* session)
00719         {
00720                 //OnRawSocketWrite(session->fd, NULL, 0);
00721                 EventHandler* eh = ServerInstance->FindDescriptor(session->fd);
00722                 if (eh)
00723                         ServerInstance->SE->WantWrite(eh);
00724         }
00725 
00726         virtual void OnBufferFlushed(userrec* user)
00727         {
00728                 if (user->GetExt("ssl"))
00729                 {
00730                         issl_session* session = &sessions[user->GetFd()];
00731                         if (session && session->outbuf.size())
00732                                 OnRawSocketWrite(user->GetFd(), NULL, 0);
00733                 }
00734         }
00735 
00736         void CloseSession(issl_session* session)
00737         {
00738                 if(session->sess)
00739                 {
00740                         gnutls_bye(session->sess, GNUTLS_SHUT_WR);
00741                         gnutls_deinit(session->sess);
00742                 }
00743 
00744                 if(session->inbuf)
00745                 {
00746                         delete[] session->inbuf;
00747                 }
00748 
00749                 session->outbuf.clear();
00750                 session->inbuf = NULL;
00751                 session->sess = NULL;
00752                 session->status = ISSL_NONE;
00753         }
00754 
00755         void VerifyCertificate(issl_session* session, Extensible* user)
00756         {
00757                 if (!session->sess || !user)
00758                         return;
00759 
00760                 unsigned int status;
00761                 const gnutls_datum_t* cert_list;
00762                 int ret;
00763                 unsigned int cert_list_size;
00764                 gnutls_x509_crt_t cert;
00765                 char name[MAXBUF];
00766                 unsigned char digest[MAXBUF];
00767                 size_t digest_size = sizeof(digest);
00768                 size_t name_size = sizeof(name);
00769                 ssl_cert* certinfo = new ssl_cert;
00770 
00771                 user->Extend("ssl_cert",certinfo);
00772 
00773                 /* This verification function uses the trusted CAs in the credentials
00774                  * structure. So you must have installed one or more CA certificates.
00775                  */
00776                 ret = gnutls_certificate_verify_peers2(session->sess, &status);
00777 
00778                 if (ret < 0)
00779                 {
00780                         certinfo->data.insert(std::make_pair("error",std::string(gnutls_strerror(ret))));
00781                         return;
00782                 }
00783 
00784                 if (status & GNUTLS_CERT_INVALID)
00785                 {
00786                         certinfo->data.insert(std::make_pair("invalid",ConvToStr(1)));
00787                 }
00788                 else
00789                 {
00790                         certinfo->data.insert(std::make_pair("invalid",ConvToStr(0)));
00791                 }
00792                 if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
00793                 {
00794                         certinfo->data.insert(std::make_pair("unknownsigner",ConvToStr(1)));
00795                 }
00796                 else
00797                 {
00798                         certinfo->data.insert(std::make_pair("unknownsigner",ConvToStr(0)));
00799                 }
00800                 if (status & GNUTLS_CERT_REVOKED)
00801                 {
00802                         certinfo->data.insert(std::make_pair("revoked",ConvToStr(1)));
00803                 }
00804                 else
00805                 {
00806                         certinfo->data.insert(std::make_pair("revoked",ConvToStr(0)));
00807                 }
00808                 if (status & GNUTLS_CERT_SIGNER_NOT_CA)
00809                 {
00810                         certinfo->data.insert(std::make_pair("trusted",ConvToStr(0)));
00811                 }
00812                 else
00813                 {
00814                         certinfo->data.insert(std::make_pair("trusted",ConvToStr(1)));
00815                 }
00816 
00817                 /* Up to here the process is the same for X.509 certificates and
00818                  * OpenPGP keys. From now on X.509 certificates are assumed. This can
00819                  * be easily extended to work with openpgp keys as well.
00820                  */
00821                 if (gnutls_certificate_type_get(session->sess) != GNUTLS_CRT_X509)
00822                 {
00823                         certinfo->data.insert(std::make_pair("error","No X509 keys sent"));
00824                         return;
00825                 }
00826 
00827                 ret = gnutls_x509_crt_init(&cert);
00828                 if (ret < 0)
00829                 {
00830                         certinfo->data.insert(std::make_pair("error",gnutls_strerror(ret)));
00831                         return;
00832                 }
00833 
00834                 cert_list_size = 0;
00835                 cert_list = gnutls_certificate_get_peers(session->sess, &cert_list_size);
00836                 if (cert_list == NULL)
00837                 {
00838                         certinfo->data.insert(std::make_pair("error","No certificate was found"));
00839                         return;
00840                 }
00841 
00842                 /* This is not a real world example, since we only check the first
00843                  * certificate in the given chain.
00844                  */
00845 
00846                 ret = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER);
00847                 if (ret < 0)
00848                 {
00849                         certinfo->data.insert(std::make_pair("error",gnutls_strerror(ret)));
00850                         return;
00851                 }
00852 
00853                 gnutls_x509_crt_get_dn(cert, name, &name_size);
00854 
00855                 certinfo->data.insert(std::make_pair("dn",name));
00856 
00857                 gnutls_x509_crt_get_issuer_dn(cert, name, &name_size);
00858 
00859                 certinfo->data.insert(std::make_pair("issuer",name));
00860 
00861                 if ((ret = gnutls_x509_crt_get_fingerprint(cert, GNUTLS_DIG_MD5, digest, &digest_size)) < 0)
00862                 {
00863                         certinfo->data.insert(std::make_pair("error",gnutls_strerror(ret)));
00864                 }
00865                 else
00866                 {
00867                         certinfo->data.insert(std::make_pair("fingerprint",irc::hex(digest, digest_size)));
00868                 }
00869 
00870                 /* Beware here we do not check for errors.
00871                  */
00872                 if ((gnutls_x509_crt_get_expiration_time(cert) < time(0)) || (gnutls_x509_crt_get_activation_time(cert) > time(0)))
00873                 {
00874                         certinfo->data.insert(std::make_pair("error","Not activated, or expired certificate"));
00875                 }
00876 
00877                 gnutls_x509_crt_deinit(cert);
00878 
00879                 return;
00880         }
00881 
00882 };
00883 
00884 MODULE_INIT(ModuleSSLGnuTLS);
00885