1 /*
2 *
3 * (C) 2014 Attila Molnar <attilamolnar@hush.com>
4 * (C) 2014-2020 Anope Team
5 * Contact us at team@anope.org
6 *
7 * Please read COPYING and README for further details.
8 */
9
10 /* RequiredLibraries: gnutls */
11 /* RequiredWindowsLibraries: libgnutls-30 */
12
13 #include "module.h"
14 #include "modules/ssl.h"
15
16 #include <errno.h>
17 #include <gnutls/gnutls.h>
18 #include <gnutls/x509.h>
19
20 class GnuTLSModule;
21 static GnuTLSModule *me;
22
23 namespace GnuTLS { class X509CertCredentials; }
24
25 class MySSLService : public SSLService
26 {
27 public:
28 MySSLService(Module *o, const Anope::string &n);
29
30 /** Initialize a socket to use SSL
31 * @param s The socket
32 */
33 void Init(Socket *s) anope_override;
34 };
35
36 class SSLSocketIO : public SocketIO
37 {
38 public:
39 gnutls_session_t sess;
40 GnuTLS::X509CertCredentials* mycreds;
41
42 /** Constructor
43 */
44 SSLSocketIO();
45
46 /** Really receive something from the buffer
47 * @param s The socket
48 * @param buf The buf to read to
49 * @param sz How much to read
50 * @return Number of bytes received
51 */
52 int Recv(Socket *s, char *buf, size_t sz) anope_override;
53
54 /** Write something to the socket
55 * @param s The socket
56 * @param buf The data to write
57 * @param size The length of the data
58 */
59 int Send(Socket *s, const char *buf, size_t sz) anope_override;
60
61 /** Accept a connection from a socket
62 * @param s The socket
63 * @return The new socket
64 */
65 ClientSocket *Accept(ListenSocket *s) anope_override;
66
67 /** Finished accepting a connection from a socket
68 * @param s The socket
69 * @return SF_ACCEPTED if accepted, SF_ACCEPTING if still in process, SF_DEAD on error
70 */
71 SocketFlag FinishAccept(ClientSocket *cs) anope_override;
72
73 /** Connect the socket
74 * @param s THe socket
75 * @param target IP to connect to
76 * @param port to connect to
77 */
78 void Connect(ConnectionSocket *s, const Anope::string &target, int port) anope_override;
79
80 /** Called to potentially finish a pending connection
81 * @param s The socket
82 * @return SF_CONNECTED on success, SF_CONNECTING if still pending, and SF_DEAD on error.
83 */
84 SocketFlag FinishConnect(ConnectionSocket *s) anope_override;
85
86 /** Called when the socket is destructing
87 */
88 void Destroy() anope_override;
89 };
90
91 namespace GnuTLS
92 {
93 class Init
94 {
95 public:
Init()96 Init() { gnutls_global_init(); }
~Init()97 ~Init() { gnutls_global_deinit(); }
98 };
99
100 /** Used to create a gnutls_datum_t* from an Anope::string
101 */
102 class Datum
103 {
104 gnutls_datum_t datum;
105
106 public:
Datum(const Anope::string & dat)107 Datum(const Anope::string &dat)
108 {
109 datum.data = reinterpret_cast<unsigned char *>(const_cast<char *>(dat.data()));
110 datum.size = static_cast<unsigned int>(dat.length());
111 }
112
get() const113 const gnutls_datum_t *get() const { return &datum; }
114 };
115
116 class DHParams
117 {
118 gnutls_dh_params_t dh_params;
119
120 public:
DHParams()121 DHParams() : dh_params(NULL) { }
122
Import(const Anope::string & dhstr)123 void Import(const Anope::string &dhstr)
124 {
125 if (dh_params != NULL)
126 {
127 gnutls_dh_params_deinit(dh_params);
128 dh_params = NULL;
129 }
130
131 int ret = gnutls_dh_params_init(&dh_params);
132 if (ret < 0)
133 throw ConfigException("Unable to initialize DH parameters");
134
135 ret = gnutls_dh_params_import_pkcs3(dh_params, Datum(dhstr).get(), GNUTLS_X509_FMT_PEM);
136 if (ret < 0)
137 {
138 gnutls_dh_params_deinit(dh_params);
139 dh_params = NULL;
140 throw ConfigException("Unable to import DH parameters");
141 }
142 }
143
~DHParams()144 ~DHParams()
145 {
146 if (dh_params)
147 gnutls_dh_params_deinit(dh_params);
148 }
149
get() const150 gnutls_dh_params_t get() const { return dh_params; }
151 };
152
153 class X509Key
154 {
155 /** Ensure that the key is deinited in case the constructor of X509Key throws
156 */
157 class RAIIKey
158 {
159 public:
160 gnutls_x509_privkey_t key;
161
RAIIKey()162 RAIIKey()
163 {
164 int ret = gnutls_x509_privkey_init(&key);
165 if (ret < 0)
166 throw ConfigException("gnutls_x509_privkey_init() failed");
167 }
168
~RAIIKey()169 ~RAIIKey()
170 {
171 gnutls_x509_privkey_deinit(key);
172 }
173 } key;
174
175 public:
176 /** Import */
X509Key(const Anope::string & keystr)177 X509Key(const Anope::string &keystr)
178 {
179 int ret = gnutls_x509_privkey_import(key.key, Datum(keystr).get(), GNUTLS_X509_FMT_PEM);
180 if (ret < 0)
181 throw ConfigException("Error loading private key: " + Anope::string(gnutls_strerror(ret)));
182 }
183
get()184 gnutls_x509_privkey_t& get() { return key.key; }
185 };
186
187 class X509CertList
188 {
189 std::vector<gnutls_x509_crt_t> certs;
190
191 public:
192 /** Import */
X509CertList(const Anope::string & certstr)193 X509CertList(const Anope::string &certstr)
194 {
195 unsigned int certcount = 3;
196 certs.resize(certcount);
197 Datum datum(certstr);
198
199 int ret = gnutls_x509_crt_list_import(raw(), &certcount, datum.get(), GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED);
200 if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER)
201 {
202 // the buffer wasn't big enough to hold all certs but gnutls changed certcount to the number of available certs,
203 // try again with a bigger buffer
204 certs.resize(certcount);
205 ret = gnutls_x509_crt_list_import(raw(), &certcount, datum.get(), GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED);
206 }
207
208 if (ret < 0)
209 throw ConfigException("Unable to load certificates" + Anope::string(gnutls_strerror(ret)));
210
211 // Resize the vector to the actual number of certs because we rely on its size being correct
212 // when deallocating the certs
213 certs.resize(certcount);
214 }
215
~X509CertList()216 ~X509CertList()
217 {
218 for (std::vector<gnutls_x509_crt_t>::iterator i = certs.begin(); i != certs.end(); ++i)
219 gnutls_x509_crt_deinit(*i);
220 }
221
raw()222 gnutls_x509_crt_t* raw() { return &certs[0]; }
size() const223 unsigned int size() const { return certs.size(); }
224 };
225
226 class X509CertCredentials
227 {
228 unsigned int refcount;
229 gnutls_certificate_credentials_t cred;
230 DHParams dh;
231
LoadFile(const Anope::string & filename)232 static Anope::string LoadFile(const Anope::string &filename)
233 {
234 std::ifstream ifs(filename.c_str());
235 const Anope::string ret((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
236 return ret;
237 }
238
239 #if (GNUTLS_VERSION_MAJOR < 2 || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR < 12))
240 static int cert_callback(gnutls_session_t sess, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, gnutls_retr_st* st);
241 #else
242 static int cert_callback(gnutls_session_t sess, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, gnutls_retr2_st* st);
243 #endif
244
245 public:
246 X509CertList certs;
247 X509Key key;
248
X509CertCredentials(const Anope::string & certfile,const Anope::string & keyfile)249 X509CertCredentials(const Anope::string &certfile, const Anope::string &keyfile)
250 : refcount(0), certs(LoadFile(certfile)), key(LoadFile(keyfile))
251 {
252 if (gnutls_certificate_allocate_credentials(&cred) < 0)
253 throw ConfigException("Cannot allocate certificate credentials");
254
255 int ret = gnutls_certificate_set_x509_key(cred, certs.raw(), certs.size(), key.get());
256 if (ret < 0)
257 {
258 gnutls_certificate_free_credentials(cred);
259 throw ConfigException("Unable to set cert/key pair");
260 }
261
262 #if (GNUTLS_VERSION_MAJOR < 2 || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR < 12))
263 gnutls_certificate_client_set_retrieve_function(cred, cert_callback);
264 #else
265 gnutls_certificate_set_retrieve_function(cred, cert_callback);
266 #endif
267 }
268
~X509CertCredentials()269 ~X509CertCredentials()
270 {
271 gnutls_certificate_free_credentials(cred);
272 }
273
SetupSession(gnutls_session_t sess)274 void SetupSession(gnutls_session_t sess)
275 {
276 gnutls_credentials_set(sess, GNUTLS_CRD_CERTIFICATE, cred);
277 gnutls_set_default_priority(sess);
278 }
279
SetDH(const Anope::string & dhfile)280 void SetDH(const Anope::string &dhfile)
281 {
282 const Anope::string dhdata = LoadFile(dhfile);
283 dh.Import(dhdata);
284 gnutls_certificate_set_dh_params(cred, dh.get());
285 }
286
HasDH() const287 bool HasDH() const
288 {
289 return (dh.get() != NULL);
290 }
291
incrref()292 void incrref() { refcount++; }
decrref()293 void decrref() { if (!--refcount) delete this; }
294 };
295 }
296
297 class GnuTLSModule : public Module
298 {
299 GnuTLS::Init libinit;
300
301 public:
302 GnuTLS::X509CertCredentials *cred;
303 MySSLService service;
304
GnuTLSModule(const Anope::string & modname,const Anope::string & creator)305 GnuTLSModule(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR), cred(NULL), service(this, "ssl")
306 {
307 me = this;
308 this->SetPermanent(true);
309 }
310
~GnuTLSModule()311 ~GnuTLSModule()
312 {
313 for (std::map<int, Socket *>::const_iterator it = SocketEngine::Sockets.begin(), it_end = SocketEngine::Sockets.end(); it != it_end;)
314 {
315 Socket *s = it->second;
316 ++it;
317
318 if (dynamic_cast<SSLSocketIO *>(s->io))
319 delete s;
320 }
321
322 if (cred)
323 cred->decrref();
324 }
325
CheckFile(const Anope::string & filename)326 static void CheckFile(const Anope::string &filename)
327 {
328 if (!Anope::IsFile(filename.c_str()))
329 {
330 Log() << "File does not exist: " << filename;
331 throw ConfigException("Error loading certificate/private key");
332 }
333 }
334
OnReload(Configuration::Conf * conf)335 void OnReload(Configuration::Conf *conf) anope_override
336 {
337 Configuration::Block *config = conf->GetModule(this);
338
339 const Anope::string certfile = config->Get<const Anope::string>("cert", "data/anope.crt");
340 const Anope::string keyfile = config->Get<const Anope::string>("key", "data/anope.key");
341 const Anope::string dhfile = config->Get<const Anope::string>("dh", "data/dhparams.pem");
342
343 CheckFile(certfile);
344 CheckFile(keyfile);
345
346 GnuTLS::X509CertCredentials *newcred = new GnuTLS::X509CertCredentials(certfile, keyfile);
347
348 // DH params is not mandatory
349 if (Anope::IsFile(dhfile.c_str()))
350 {
351 try
352 {
353 newcred->SetDH(dhfile);
354 }
355 catch (...)
356 {
357 delete newcred;
358 throw;
359 }
360 Log(LOG_DEBUG) << "m_ssl_gnutls: Successfully loaded DH parameters from " << dhfile;
361 }
362
363 if (cred)
364 cred->decrref();
365 cred = newcred;
366 cred->incrref();
367
368 Log(LOG_DEBUG) << "m_ssl_gnutls: Successfully loaded certificate " << certfile << " and private key " << keyfile;
369 }
370
OnPreServerConnect()371 void OnPreServerConnect() anope_override
372 {
373 Configuration::Block *config = Config->GetBlock("uplink", Anope::CurrentUplink);
374
375 if (config->Get<bool>("ssl"))
376 {
377 this->service.Init(UplinkSock);
378 }
379 }
380 };
381
MySSLService(Module * o,const Anope::string & n)382 MySSLService::MySSLService(Module *o, const Anope::string &n) : SSLService(o, n)
383 {
384 }
385
Init(Socket * s)386 void MySSLService::Init(Socket *s)
387 {
388 if (s->io != &NormalSocketIO)
389 throw CoreException("Socket initializing SSL twice");
390
391 s->io = new SSLSocketIO();
392 }
393
Recv(Socket * s,char * buf,size_t sz)394 int SSLSocketIO::Recv(Socket *s, char *buf, size_t sz)
395 {
396 int ret = gnutls_record_recv(this->sess, buf, sz);
397
398 if (ret > 0)
399 TotalRead += ret;
400 else if (ret < 0)
401 {
402 switch (ret)
403 {
404 case GNUTLS_E_AGAIN:
405 case GNUTLS_E_INTERRUPTED:
406 SocketEngine::SetLastError(EAGAIN);
407 break;
408 default:
409 if (s == UplinkSock)
410 {
411 // Log and fake an errno because this is a fatal error on the uplink socket
412 Log() << "SSL error: " << gnutls_strerror(ret);
413 }
414 SocketEngine::SetLastError(ECONNRESET);
415 }
416 }
417
418 return ret;
419 }
420
Send(Socket * s,const char * buf,size_t sz)421 int SSLSocketIO::Send(Socket *s, const char *buf, size_t sz)
422 {
423 int ret = gnutls_record_send(this->sess, buf, sz);
424
425 if (ret > 0)
426 TotalWritten += ret;
427 else
428 {
429 switch (ret)
430 {
431 case 0:
432 case GNUTLS_E_AGAIN:
433 case GNUTLS_E_INTERRUPTED:
434 SocketEngine::SetLastError(EAGAIN);
435 break;
436 default:
437 if (s == UplinkSock)
438 {
439 // Log and fake an errno because this is a fatal error on the uplink socket
440 Log() << "SSL error: " << gnutls_strerror(ret);
441 }
442 SocketEngine::SetLastError(ECONNRESET);
443 }
444 }
445
446 return ret;
447 }
448
Accept(ListenSocket * s)449 ClientSocket *SSLSocketIO::Accept(ListenSocket *s)
450 {
451 if (s->io == &NormalSocketIO)
452 throw SocketException("Attempting to accept on uninitialized socket with SSL");
453
454 sockaddrs conaddr;
455
456 socklen_t size = sizeof(conaddr);
457 int newsock = accept(s->GetFD(), &conaddr.sa, &size);
458
459 #ifndef INVALID_SOCKET
460 const int INVALID_SOCKET = -1;
461 #endif
462
463 if (newsock < 0 || newsock == INVALID_SOCKET)
464 throw SocketException("Unable to accept connection: " + Anope::LastError());
465
466 ClientSocket *newsocket = s->OnAccept(newsock, conaddr);
467 me->service.Init(newsocket);
468 SSLSocketIO *io = anope_dynamic_static_cast<SSLSocketIO *>(newsocket->io);
469
470 if (gnutls_init(&io->sess, GNUTLS_SERVER) != GNUTLS_E_SUCCESS)
471 throw SocketException("Unable to initialize SSL socket");
472
473 me->cred->SetupSession(io->sess);
474 gnutls_transport_set_ptr(io->sess, reinterpret_cast<gnutls_transport_ptr_t>(newsock));
475
476 newsocket->flags[SF_ACCEPTING] = true;
477 this->FinishAccept(newsocket);
478
479 return newsocket;
480 }
481
FinishAccept(ClientSocket * cs)482 SocketFlag SSLSocketIO::FinishAccept(ClientSocket *cs)
483 {
484 if (cs->io == &NormalSocketIO)
485 throw SocketException("Attempting to finish connect uninitialized socket with SSL");
486 else if (cs->flags[SF_ACCEPTED])
487 return SF_ACCEPTED;
488 else if (!cs->flags[SF_ACCEPTING])
489 throw SocketException("SSLSocketIO::FinishAccept called for a socket not accepted nor accepting?");
490
491 SSLSocketIO *io = anope_dynamic_static_cast<SSLSocketIO *>(cs->io);
492
493 int ret = gnutls_handshake(io->sess);
494 if (ret < 0)
495 {
496 if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
497 {
498 // gnutls_handshake() wants to read or write again;
499 // if gnutls_record_get_direction() returns 0 it wants to read, otherwise it wants to write.
500 if (gnutls_record_get_direction(io->sess) == 0)
501 {
502 SocketEngine::Change(cs, false, SF_WRITABLE);
503 SocketEngine::Change(cs, true, SF_READABLE);
504 }
505 else
506 {
507 SocketEngine::Change(cs, true, SF_WRITABLE);
508 SocketEngine::Change(cs, false, SF_READABLE);
509 }
510 return SF_ACCEPTING;
511 }
512 else
513 {
514 cs->OnError(Anope::string(gnutls_strerror(ret)));
515 cs->flags[SF_DEAD] = true;
516 cs->flags[SF_ACCEPTING] = false;
517 return SF_DEAD;
518 }
519 }
520 else
521 {
522 cs->flags[SF_ACCEPTED] = true;
523 cs->flags[SF_ACCEPTING] = false;
524 SocketEngine::Change(cs, false, SF_WRITABLE);
525 SocketEngine::Change(cs, true, SF_READABLE);
526 cs->OnAccept();
527 return SF_ACCEPTED;
528 }
529 }
530
Connect(ConnectionSocket * s,const Anope::string & target,int port)531 void SSLSocketIO::Connect(ConnectionSocket *s, const Anope::string &target, int port)
532 {
533 if (s->io == &NormalSocketIO)
534 throw SocketException("Attempting to connect uninitialized socket with SSL");
535
536 s->flags[SF_CONNECTING] = s->flags[SF_CONNECTED] = false;
537
538 s->conaddr.pton(s->IsIPv6() ? AF_INET6 : AF_INET, target, port);
539 int c = connect(s->GetFD(), &s->conaddr.sa, s->conaddr.size());
540 if (c == -1)
541 {
542 if (Anope::LastErrorCode() != EINPROGRESS)
543 {
544 s->OnError(Anope::LastError());
545 s->flags[SF_DEAD] = true;
546 return;
547 }
548 else
549 {
550 SocketEngine::Change(s, true, SF_WRITABLE);
551 s->flags[SF_CONNECTING] = true;
552 return;
553 }
554 }
555 else
556 {
557 s->flags[SF_CONNECTING] = true;
558 this->FinishConnect(s);
559 }
560 }
561
FinishConnect(ConnectionSocket * s)562 SocketFlag SSLSocketIO::FinishConnect(ConnectionSocket *s)
563 {
564 if (s->io == &NormalSocketIO)
565 throw SocketException("Attempting to finish connect uninitialized socket with SSL");
566 else if (s->flags[SF_CONNECTED])
567 return SF_CONNECTED;
568 else if (!s->flags[SF_CONNECTING])
569 throw SocketException("SSLSocketIO::FinishConnect called for a socket not connected nor connecting?");
570
571 SSLSocketIO *io = anope_dynamic_static_cast<SSLSocketIO *>(s->io);
572
573 if (io->sess == NULL)
574 {
575 if (gnutls_init(&io->sess, GNUTLS_CLIENT) != GNUTLS_E_SUCCESS)
576 throw SocketException("Unable to initialize SSL socket");
577 me->cred->SetupSession(io->sess);
578 gnutls_transport_set_ptr(io->sess, reinterpret_cast<gnutls_transport_ptr_t>(s->GetFD()));
579 }
580
581 int ret = gnutls_handshake(io->sess);
582 if (ret < 0)
583 {
584 if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
585 {
586 // gnutls_handshake() wants to read or write again;
587 // if gnutls_record_get_direction() returns 0 it wants to read, otherwise it wants to write.
588 if (gnutls_record_get_direction(io->sess) == 0)
589 {
590 SocketEngine::Change(s, false, SF_WRITABLE);
591 SocketEngine::Change(s, true, SF_READABLE);
592 }
593 else
594 {
595 SocketEngine::Change(s, true, SF_WRITABLE);
596 SocketEngine::Change(s, false, SF_READABLE);
597 }
598
599 return SF_CONNECTING;
600 }
601 else
602 {
603 s->OnError(Anope::string(gnutls_strerror(ret)));
604 s->flags[SF_CONNECTING] = false;
605 s->flags[SF_DEAD] = true;
606 return SF_DEAD;
607 }
608 }
609 else
610 {
611 s->flags[SF_CONNECTING] = false;
612 s->flags[SF_CONNECTED] = true;
613 SocketEngine::Change(s, false, SF_WRITABLE);
614 SocketEngine::Change(s, true, SF_READABLE);
615 s->OnConnect();
616 return SF_CONNECTED;
617 }
618 }
619
Destroy()620 void SSLSocketIO::Destroy()
621 {
622 if (this->sess)
623 {
624 gnutls_bye(this->sess, GNUTLS_SHUT_WR);
625 gnutls_deinit(this->sess);
626 }
627
628 mycreds->decrref();
629
630 delete this;
631 }
632
SSLSocketIO()633 SSLSocketIO::SSLSocketIO() : sess(NULL), mycreds(me->cred)
634 {
635 mycreds->incrref();
636 }
637
638 #if (GNUTLS_VERSION_MAJOR < 2 || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR < 12))
cert_callback(gnutls_session_t sess,const gnutls_datum_t * req_ca_rdn,int nreqs,const gnutls_pk_algorithm_t * sign_algos,int sign_algos_length,gnutls_retr_st * st)639 int GnuTLS::X509CertCredentials::cert_callback(gnutls_session_t sess, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, gnutls_retr_st* st)
640 {
641 st->type = GNUTLS_CRT_X509;
642 #else
643 int GnuTLS::X509CertCredentials::cert_callback(gnutls_session_t sess, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, gnutls_retr2_st* st)
644 {
645 st->cert_type = GNUTLS_CRT_X509;
646 st->key_type = GNUTLS_PRIVKEY_X509;
647 #endif
648 st->ncerts = me->cred->certs.size();
649 st->cert.x509 = me->cred->certs.raw();
650 st->key.x509 = me->cred->key.get();
651 st->deinit_all = 0;
652
653 return 0;
654 }
655
656 MODULE_INIT(GnuTLSModule)
657