anope- supernets anope source code & configuration |
git clone git://git.acid.vegas/anope.git |
Log | Files | Refs | Archive | README |
m_ssl_gnutls.cpp (16779B)
1 /* 2 * 3 * (C) 2014 Attila Molnar <attilamolnar@hush.com> 4 * (C) 2014-2022 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: 96 Init() { gnutls_global_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: 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 113 const gnutls_datum_t *get() const { return &datum; } 114 }; 115 116 class DHParams 117 { 118 gnutls_dh_params_t dh_params; 119 120 public: 121 DHParams() : dh_params(NULL) { } 122 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 144 ~DHParams() 145 { 146 if (dh_params) 147 gnutls_dh_params_deinit(dh_params); 148 } 149 150 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 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 169 ~RAIIKey() 170 { 171 gnutls_x509_privkey_deinit(key); 172 } 173 } key; 174 175 public: 176 /** Import */ 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 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 */ 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 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 222 gnutls_x509_crt_t* raw() { return &certs[0]; } 223 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 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 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 269 ~X509CertCredentials() 270 { 271 gnutls_certificate_free_credentials(cred); 272 } 273 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 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 287 bool HasDH() const 288 { 289 return (dh.get() != NULL); 290 } 291 292 void incrref() { refcount++; } 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 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 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 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 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 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 382 MySSLService::MySSLService(Module *o, const Anope::string &n) : SSLService(o, n) 383 { 384 } 385 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 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 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 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 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 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 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 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 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)) 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)