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