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