]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/extra/m_ssl_openssl.cpp
c0f02f322cb40b8845d468e5969e06aca46c11e9
[user/henk/code/inspircd.git] / src / modules / extra / m_ssl_openssl.cpp
1 #include <string>
2 #include <vector>
3
4 #include <openssl/ssl.h>
5 #include <openssl/err.h>
6
7 #include "inspircd_config.h"
8 #include "configreader.h"
9 #include "users.h"
10 #include "channels.h"
11 #include "modules.h"
12 #include "helperfuncs.h"
13 #include "socket.h"
14 #include "hashcomp.h"
15 #include "inspircd.h"
16
17 /* $ModDesc: Provides SSL support for clients */
18 /* $CompileFlags: -I/usr/include -I/usr/local/include */
19 /* $LinkerFlags: -L/usr/local/lib -Wl,--rpath -Wl,/usr/local/lib -L/usr/lib -Wl,--rpath -Wl,/usr/lib -lssl */
20
21 extern InspIRCd* ServerInstance;
22
23 enum issl_status { ISSL_NONE, ISSL_HANDSHAKING, ISSL_OPEN };
24 enum issl_io_status { ISSL_WRITE, ISSL_READ };
25
26 bool isin(int port, const std::vector<int> &portlist)
27 {
28         for(unsigned int i = 0; i < portlist.size(); i++)
29                 if(portlist[i] == port)
30                         return true;
31                         
32         return false;
33 }
34
35 char* get_error()
36 {
37         return ERR_error_string(ERR_get_error(), NULL);
38 }
39
40 class issl_session : public classbase
41 {
42 public:
43         SSL* sess;
44         issl_status status;
45         issl_io_status rstat;
46         issl_io_status wstat;
47
48         unsigned int inbufoffset;
49         char* inbuf;                    // Buffer OpenSSL reads into.
50         std::string outbuf;     // Buffer for outgoing data that OpenSSL will not take.
51         int fd;
52         
53         issl_session()
54         {
55                 rstat = ISSL_READ;
56                 wstat = ISSL_WRITE;
57         }
58 };
59
60 class ModuleSSLOpenSSL : public Module
61 {
62         Server* Srv;
63         ConfigReader* Conf;
64         
65         CullList culllist;
66         
67         std::vector<int> listenports;
68         
69         int inbufsize;
70         issl_session sessions[MAX_DESCRIPTORS];
71         
72         SSL_CTX* ctx;
73         
74         char* dummy;
75         
76         std::string keyfile;
77         std::string certfile;
78         std::string cafile;
79         // std::string crlfile;
80         std::string dhfile;
81         
82  public:
83         
84         ModuleSSLOpenSSL(Server* Me)
85                 : Module::Module(Me)
86         {
87                 Srv = Me;
88                 
89                 // Not rehashable...because I cba to reduce all the sizes of existing buffers.
90                 inbufsize = ServerInstance->Config->NetBufferSize;
91                 
92                 /* Global SSL library initialization*/
93                 SSL_library_init();
94                 SSL_load_error_strings();
95                 
96                 /* Build our SSL context*/
97                 ctx = SSL_CTX_new( SSLv23_server_method() );
98
99                 // Needs the flag as it ignores a plain /rehash
100                 OnRehash("ssl");
101         }
102         
103         virtual void OnRehash(const std::string &param)
104         {
105                 if(param != "ssl")
106                         return;
107         
108                 Conf = new ConfigReader;
109                         
110                 for(unsigned int i = 0; i < listenports.size(); i++)
111                 {
112                         ServerInstance->Config->DelIOHook(listenports[i]);
113                 }
114                 
115                 listenports.clear();
116                 
117                 for(int i = 0; i < Conf->Enumerate("bind"); i++)
118                 {
119                         // For each <bind> tag
120                         if(((Conf->ReadValue("bind", "type", i) == "") || (Conf->ReadValue("bind", "type", i) == "clients")) && (Conf->ReadValue("bind", "ssl", i) == "openssl"))
121                         {
122                                 // Get the port we're meant to be listening on with SSL
123                                 unsigned int port = Conf->ReadInteger("bind", "port", i, true);
124                                 if (ServerInstance->Config->AddIOHook(port, this))
125                                 {
126                                         // We keep a record of which ports we're listening on with SSL
127                                         listenports.push_back(port);
128                                 
129                                         log(DEFAULT, "m_ssl_openssl.so: Enabling SSL for port %d", port);
130                                 }
131                                 else
132                                 {
133                                         log(DEFAULT, "m_ssl_openssl.so: FAILED to enable SSL on port %d, maybe you have another ssl or similar module loaded?", port);
134                                 }
135                         }
136                 }
137                 
138                 std::string confdir(CONFIG_FILE);
139                 // +1 so we the path ends with a /
140                 confdir = confdir.substr(0, confdir.find_last_of('/') + 1);
141                 
142                 cafile  = Conf->ReadValue("openssl", "cafile", 0);
143                 // crlfile      = Conf->ReadValue("openssl", "crlfile", 0);
144                 certfile        = Conf->ReadValue("openssl", "certfile", 0);
145                 keyfile = Conf->ReadValue("openssl", "keyfile", 0);
146                 dhfile  = Conf->ReadValue("openssl", "dhfile", 0);
147                 
148                 // Set all the default values needed.
149                 if(cafile == "")
150                         cafile = "ca.pem";
151                         
152                 //if(crlfile == "")
153                 //      crlfile = "crl.pem";
154                         
155                 if(certfile == "")
156                         certfile = "cert.pem";
157                         
158                 if(keyfile == "")
159                         keyfile = "key.pem";
160                         
161                 if(dhfile == "")
162                         dhfile = "dhparams.pem";
163                         
164                 // Prepend relative paths with the path to the config directory.        
165                 if(cafile[0] != '/')
166                         cafile = confdir + cafile;
167                 
168                 //if(crlfile[0] != '/')
169                 //      crlfile = confdir + crlfile;
170                         
171                 if(certfile[0] != '/')
172                         certfile = confdir + certfile;
173                         
174                 if(keyfile[0] != '/')
175                         keyfile = confdir + keyfile;
176                         
177                 if(dhfile[0] != '/')
178                         dhfile = confdir + dhfile;
179
180                 /* Load our keys and certificates*/
181                 if(!SSL_CTX_use_certificate_chain_file(ctx, certfile.c_str()))
182                 {
183                         log(DEFAULT, "m_ssl_openssl.so: Can't read certificate file %s", certfile.c_str());
184                 }
185
186                 if(!SSL_CTX_use_PrivateKey_file(ctx, keyfile.c_str(), SSL_FILETYPE_PEM))
187                 {
188                         log(DEFAULT, "m_ssl_openssl.so: Can't read key file %s", keyfile.c_str());
189                 }
190
191                 /* Load the CAs we trust*/
192                 if(!SSL_CTX_load_verify_locations(ctx, cafile.c_str(), 0))
193                 {
194                         log(DEFAULT, "m_ssl_openssl.so: Can't read CA list from ", cafile.c_str());
195                 }
196
197                 FILE* dhpfile = fopen(dhfile.c_str(), "r");
198                 DH* ret;
199
200                 if(dhpfile == NULL)
201                 {
202                         log(DEFAULT, "m_ssl_openssl.so Couldn't open DH file %s: %s", dhfile.c_str(), strerror(errno));
203                         throw ModuleException();
204                 }
205                 else
206                 {
207                         ret = PEM_read_DHparams(dhpfile, NULL, NULL, NULL);
208                 
209                         if(SSL_CTX_set_tmp_dh(ctx, ret) < 0)
210                         {
211                                 log(DEFAULT, "m_ssl_openssl.so: Couldn't set DH parameters");
212                         }
213                 }
214                 
215                 fclose(dhpfile);
216
217                 DELETE(Conf);
218         }
219
220         virtual ~ModuleSSLOpenSSL()
221         {
222                 SSL_CTX_free(ctx);
223         }
224         
225         virtual void OnCleanup(int target_type, void* item)
226         {
227                 if(target_type == TYPE_USER)
228                 {
229                         userrec* user = (userrec*)item;
230                         
231                         if(user->GetExt("ssl", dummy) && IS_LOCAL(user) && isin(user->GetPort(), listenports))
232                         {
233                                 // User is using SSL, they're a local user, and they're using one of *our* SSL ports.
234                                 // Potentially there could be multiple SSL modules loaded at once on different ports.
235                                 log(DEBUG, "m_ssl_openssl.so: Adding user %s to cull list", user->nick);
236                                 culllist.AddItem(user, "SSL module unloading");
237                         }
238                 }
239         }
240         
241         virtual void OnUnloadModule(Module* mod, const std::string &name)
242         {
243                 if(mod == this)
244                 {
245                         // We're being unloaded, kill all the users added to the cull list in OnCleanup
246                         int numusers = culllist.Apply();
247                         log(DEBUG, "m_ssl_openssl.so: Killed %d users for unload of OpenSSL SSL module", numusers);
248                         
249                         for(unsigned int i = 0; i < listenports.size(); i++)
250                                 ServerInstance->Config->DelIOHook(listenports[i]);
251                 }
252         }
253         
254         virtual Version GetVersion()
255         {
256                 return Version(1, 0, 0, 0, VF_VENDOR);
257         }
258
259         void Implements(char* List)
260         {
261                 List[I_OnRawSocketAccept] = List[I_OnRawSocketClose] = List[I_OnRawSocketRead] = List[I_OnRawSocketWrite] = List[I_OnCleanup] = 1;
262                 List[I_OnSyncUserMetaData] = List[I_OnDecodeMetaData] = List[I_OnUnloadModule] = List[I_OnRehash] = List[I_OnWhois] = List[I_OnGlobalConnect] = 1;
263         }
264
265         virtual void OnRawSocketAccept(int fd, const std::string &ip, int localport)
266         {
267                 issl_session* session = &sessions[fd];
268         
269                 session->fd = fd;
270                 session->inbuf = new char[inbufsize];
271                 session->inbufoffset = 0;               
272                 session->sess = SSL_new(ctx);
273                 session->status = ISSL_NONE;
274         
275                 if(session->sess == NULL)
276                 {
277                         log(DEBUG, "m_ssl.so: Couldn't create SSL object: %s", get_error());
278                         return;
279                 }
280                 
281                 if(SSL_set_fd(session->sess, fd) == 0)
282                 {
283                         log(DEBUG, "m_ssl.so: Couldn't set fd for SSL object: %s", get_error());
284                         return;
285                 }
286
287                 Handshake(session);
288         }
289
290         virtual void OnRawSocketClose(int fd)
291         {
292                 log(DEBUG, "m_ssl_openssl.so: OnRawSocketClose: %d", fd);
293                 CloseSession(&sessions[fd]);
294         }
295         
296         virtual int OnRawSocketRead(int fd, char* buffer, unsigned int count, int &readresult)
297         {
298                 issl_session* session = &sessions[fd];
299                 
300                 if(!session->sess)
301                 {
302                         log(DEBUG, "m_ssl_openssl.so: OnRawSocketRead: No session to read from");
303                         readresult = 0;
304                         CloseSession(session);
305                         return 1;
306                 }
307                 
308                 log(DEBUG, "m_ssl_openssl.so: OnRawSocketRead(%d, buffer, %u, %d)", fd, count, readresult);
309                 
310                 if(session->status == ISSL_HANDSHAKING)
311                 {
312                         if(session->rstat == ISSL_READ || session->wstat == ISSL_READ)
313                         {
314                                 // The handshake isn't finished and it wants to read, try to finish it.
315                                 if(Handshake(session))
316                                 {
317                                         // Handshake successfully resumed.
318                                         log(DEBUG, "m_ssl_openssl.so: OnRawSocketRead: successfully resumed handshake");
319                                 }
320                                 else
321                                 {
322                                         // Couldn't resume handshake.   
323                                         log(DEBUG, "m_ssl_openssl.so: OnRawSocketRead: failed to resume handshake");
324                                         return -1;
325                                 }
326                         }
327                         else
328                         {
329                                 log(DEBUG, "m_ssl_openssl.so: OnRawSocketRead: handshake wants to write data but we are currently reading");
330                                 return -1;                      
331                         }
332                 }
333                 
334                 // If we resumed the handshake then session->status will be ISSL_OPEN
335                                 
336                 if(session->status == ISSL_OPEN)
337                 {
338                         if(session->wstat == ISSL_READ)
339                         {
340                                 if(DoWrite(session) == 0)
341                                         return 0;
342                         }
343                         
344                         if(session->rstat == ISSL_READ)
345                         {
346                                 int ret = DoRead(session);
347                         
348                                 if(ret > 0)
349                                 {
350                                         if(count <= session->inbufoffset)
351                                         {
352                                                 memcpy(buffer, session->inbuf, count);
353                                                 // Move the stuff left in inbuf to the beginning of it
354                                                 memcpy(session->inbuf, session->inbuf + count, (session->inbufoffset - count));
355                                                 // Now we need to set session->inbufoffset to the amount of data still waiting to be handed to insp.
356                                                 session->inbufoffset -= count;
357                                                 // Insp uses readresult as the count of how much data there is in buffer, so:
358                                                 readresult = count;
359                                         }
360                                         else
361                                         {
362                                                 // There's not as much in the inbuf as there is space in the buffer, so just copy the whole thing.
363                                                 memcpy(buffer, session->inbuf, session->inbufoffset);
364                                                 
365                                                 readresult = session->inbufoffset;
366                                                 // Zero the offset, as there's nothing there..
367                                                 session->inbufoffset = 0;
368                                         }
369                                 
370                                         log(DEBUG, "m_ssl_openssl.so: OnRawSocketRead: Passing %d bytes up to insp:", count);
371                                         Srv->Log(DEBUG, std::string(buffer, readresult));
372                                 
373                                         return 1;
374                                 }
375                                 else
376                                 {
377                                         return ret;
378                                 }
379                         }
380                 }
381                 
382                 return -1;
383         }
384         
385         virtual int OnRawSocketWrite(int fd, const char* buffer, int count)
386         {               
387                 issl_session* session = &sessions[fd];
388
389                 if(!session->sess)
390                 {
391                         log(DEBUG, "m_ssl_openssl.so: OnRawSocketWrite: No session to write to");
392                         CloseSession(session);
393                         return 1;
394                 }
395                 
396                 log(DEBUG, "m_ssl_openssl.so: OnRawSocketWrite: Adding %d bytes to the outgoing buffer", count);                
397                 session->outbuf.append(buffer, count);
398                 
399                 if(session->status == ISSL_HANDSHAKING)
400                 {
401                         // The handshake isn't finished, try to finish it.
402                         if(session->rstat == ISSL_WRITE || session->wstat == ISSL_WRITE)
403                         {
404                                 if(Handshake(session))
405                                 {
406                                         // Handshake successfully resumed.
407                                         log(DEBUG, "m_ssl_openssl.so: OnRawSocketWrite: successfully resumed handshake");
408                                 }
409                                 else
410                                 {
411                                         // Couldn't resume handshake.   
412                                         log(DEBUG, "m_ssl_openssl.so: OnRawSocketWrite: failed to resume handshake"); 
413                                 }
414                         }
415                         else
416                         {
417                                 log(DEBUG, "m_ssl_openssl.so: OnRawSocketWrite: handshake wants to read data but we are currently writing");                    
418                         }
419                 }
420                 
421                 if(session->status == ISSL_OPEN)
422                 {
423                         if(session->rstat == ISSL_WRITE)
424                         {
425                                 DoRead(session);
426                         }
427                         
428                         if(session->wstat == ISSL_WRITE)
429                         {
430                                 return DoWrite(session);
431                         }
432                 }
433                 
434                 return 1;
435         }
436         
437         int DoWrite(issl_session* session)
438         {
439                 log(DEBUG, "m_ssl_openssl.so: DoWrite: Trying to write %d bytes:", session->outbuf.size());
440                 Srv->Log(DEBUG, session->outbuf);
441                         
442                 int ret = SSL_write(session->sess, session->outbuf.data(), session->outbuf.size());
443                 
444                 if(ret == 0)
445                 {
446                         log(DEBUG, "m_ssl_openssl.so: DoWrite: Client closed the connection");
447                         CloseSession(session);
448                         return 0;
449                 }
450                 else if(ret < 0)
451                 {
452                         int err = SSL_get_error(session->sess, ret);
453                         
454                         if(err == SSL_ERROR_WANT_WRITE)
455                         {
456                                 log(DEBUG, "m_ssl_openssl.so: DoWrite: Not all SSL data written, need to retry: %s", get_error());
457                                 session->wstat = ISSL_WRITE;
458                                 return -1;
459                         }
460                         else if(err == SSL_ERROR_WANT_READ)
461                         {
462                                 log(DEBUG, "m_ssl_openssl.so: DoWrite: Not all SSL data written but the damn thing wants to read instead: %s", get_error());
463                                 session->wstat = ISSL_READ;
464                                 return -1;
465                         }
466                         else
467                         {
468                                 log(DEBUG, "m_ssl_openssl.so: DoWrite: Error writing SSL data: %s", get_error());
469                                 CloseSession(session);
470                                 return 0;
471                         }
472                 }
473                 else
474                 {
475                         log(DEBUG, "m_ssl_openssl.so: DoWrite: Successfully wrote %d bytes", ret);
476                         session->outbuf = session->outbuf.substr(ret);
477                         return ret;
478                 }
479         }
480         
481         int DoRead(issl_session* session)
482         {
483                 // Is this right? Not sure if the unencrypted data is garaunteed to be the same length.
484                 // Read into the inbuffer, offset from the beginning by the amount of data we have that insp hasn't taken yet.
485                 log(DEBUG, "m_ssl_openssl.so: DoRead: SSL_read(sess, inbuf+%d, %d-%d)", session->inbufoffset, inbufsize, session->inbufoffset);
486                         
487                 int ret = SSL_read(session->sess, session->inbuf + session->inbufoffset, inbufsize - session->inbufoffset);
488
489                 if(ret == 0)
490                 {
491                         // Client closed connection.
492                         log(DEBUG, "m_ssl_openssl.so: DoRead: Client closed the connection");
493                         CloseSession(session);
494                         return 0;
495                 }
496                 else if(ret < 0)
497                 {
498                         int err = SSL_get_error(session->sess, ret);
499                                 
500                         if(err == SSL_ERROR_WANT_READ)
501                         {
502                                 log(DEBUG, "m_ssl_openssl.so: DoRead: Not all SSL data read, need to retry: %s", get_error());
503                                 session->rstat = ISSL_READ;
504                                 return -1;
505                         }
506                         else if(err == SSL_ERROR_WANT_WRITE)
507                         {
508                                 log(DEBUG, "m_ssl_openssl.so: DoRead: Not all SSL data read but the damn thing wants to write instead: %s", get_error());
509                                 session->rstat = ISSL_WRITE;
510                                 return -1;
511                         }
512                         else
513                         {
514                                 log(DEBUG, "m_ssl_openssl.so: DoRead: Error reading SSL data: %s", get_error());
515                                 CloseSession(session);
516                                 return 0;
517                         }
518                 }
519                 else
520                 {
521                         // Read successfully 'ret' bytes into inbuf + inbufoffset
522                         // There are 'ret' + 'inbufoffset' bytes of data in 'inbuf'
523                         // 'buffer' is 'count' long
524                         
525                         log(DEBUG, "m_ssl_openssl.so: DoRead: Read %d bytes, now have %d waiting to be passed up", ret, ret + session->inbufoffset);
526
527                         session->inbufoffset += ret;
528
529                         return ret;
530                 }
531         }
532         
533         // :kenny.chatspike.net 320 Om Epy|AFK :is a Secure Connection
534         virtual void OnWhois(userrec* source, userrec* dest)
535         {
536                 // Bugfix, only send this numeric for *our* SSL users
537                 if(dest->GetExt("ssl", dummy) || (IS_LOCAL(dest) &&  isin(dest->GetPort(), listenports)))
538                 {
539                         source->WriteServ("320 %s %s :is using a secure connection", source->nick, dest->nick);
540                 }
541         }
542         
543         virtual void OnSyncUserMetaData(userrec* user, Module* proto, void* opaque, const std::string &extname)
544         {
545                 // check if the linking module wants to know about OUR metadata
546                 if(extname == "ssl")
547                 {
548                         // check if this user has an swhois field to send
549                         if(user->GetExt(extname, dummy))
550                         {
551                                 // call this function in the linking module, let it format the data how it
552                                 // sees fit, and send it on its way. We dont need or want to know how.
553                                 proto->ProtoSendMetaData(opaque, TYPE_USER, user, extname, "ON");
554                         }
555                 }
556         }
557         
558         virtual void OnDecodeMetaData(int target_type, void* target, const std::string &extname, const std::string &extdata)
559         {
560                 // check if its our metadata key, and its associated with a user
561                 if ((target_type == TYPE_USER) && (extname == "ssl"))
562                 {
563                         userrec* dest = (userrec*)target;
564                         // if they dont already have an ssl flag, accept the remote server's
565                         if (!dest->GetExt(extname, dummy))
566                         {
567                                 dest->Extend(extname, "ON");
568                         }
569                 }
570         }
571         
572         bool Handshake(issl_session* session)
573         {               
574                 int ret = SSL_accept(session->sess);
575       
576                 if(ret < 0)
577                 {
578                         int err = SSL_get_error(session->sess, ret);
579                                 
580                         if(err == SSL_ERROR_WANT_READ)
581                         {
582                                 log(DEBUG, "m_ssl_openssl.so: Handshake: Not completed, need to read again: %s", get_error());
583                                 session->rstat = ISSL_READ;
584                                 session->status = ISSL_HANDSHAKING;
585                         }
586                         else if(err == SSL_ERROR_WANT_WRITE)
587                         {
588                                 log(DEBUG, "m_ssl_openssl.so: Handshake: Not completed, need to write more data: %s", get_error());
589                                 session->wstat = ISSL_WRITE;
590                                 session->status = ISSL_HANDSHAKING;
591                                 MakePollWrite(session);
592                         }
593                         else
594                         {
595                                 log(DEBUG, "m_ssl_openssl.so: Handshake: Failed, bailing: %s", get_error());
596                                 CloseSession(session);
597                         }
598
599                         return false;
600                 }
601                 else
602                 {
603                         // Handshake complete.
604                         log(DEBUG, "m_ssl_openssl.so: Handshake completed");
605                         
606                         // This will do for setting the ssl flag...it could be done earlier if it's needed. But this seems neater.
607                         userrec* u = Srv->FindDescriptor(session->fd);
608                         if (u)
609                         {
610                                 if (!u->GetExt("ssl", dummy))
611                                         u->Extend("ssl", "ON");
612                         }
613                         
614                         session->status = ISSL_OPEN;
615                         
616                         MakePollWrite(session);
617                         
618                         return true;
619                 }
620         }
621         
622         virtual void OnGlobalConnect(userrec* user)
623         {
624                 // This occurs AFTER OnUserConnect so we can be sure the
625                 // protocol module has propogated the NICK message.
626                 if ((user->GetExt("ssl", dummy)) && (IS_LOCAL(user)))
627                 {
628                         // Tell whatever protocol module we're using that we need to inform other servers of this metadata NOW.
629                         std::deque<std::string>* metadata = new std::deque<std::string>;
630                         metadata->push_back(user->nick);
631                         metadata->push_back("ssl");             // The metadata id
632                         metadata->push_back("ON");              // The value to send
633                         Event* event = new Event((char*)metadata,(Module*)this,"send_metadata");
634                         event->Send();                          // Trigger the event. We don't care what module picks it up.
635                         DELETE(event);
636                         DELETE(metadata);
637                 }
638         }
639         
640         void MakePollWrite(issl_session* session)
641         {
642                 OnRawSocketWrite(session->fd, NULL, 0);
643         }
644         
645         void CloseSession(issl_session* session)
646         {
647                 if(session->sess)
648                 {
649                         SSL_shutdown(session->sess);
650                         SSL_free(session->sess);
651                 }
652                 
653                 if(session->inbuf)
654                 {
655                         delete[] session->inbuf;
656                 }
657                 
658                 session->outbuf.clear();
659                 session->inbuf = NULL;
660                 session->sess = NULL;
661                 session->status = ISSL_NONE;
662         }
663 };
664
665 class ModuleSSLOpenSSLFactory : public ModuleFactory
666 {
667  public:
668         ModuleSSLOpenSSLFactory()
669         {
670         }
671         
672         ~ModuleSSLOpenSSLFactory()
673         {
674         }
675         
676         virtual Module * CreateModule(Server* Me)
677         {
678                 return new ModuleSSLOpenSSL(Me);
679         }
680 };
681
682
683 extern "C" void * init_module( void )
684 {
685         return new ModuleSSLOpenSSLFactory;
686 }