#include "LuprexSockets.hpp" #include "enginewrapper.hpp" #include "drvutil.hpp" #include "Sockets.h" #include "SocketTypes.h" #include "SocketSubsystem.h" #include "AddressInfoTypes.h" #define UI UI_ST THIRD_PARTY_INCLUDES_START #include #include #include #include #include #include #include #include THIRD_PARTY_INCLUDES_END #undef UI #define WIN32_LEAN_AND_MEAN #include #include #include #include #include #define MAX_BIO_BUFFER (128 * 1024) enum EChanState { CHAN_INACTIVE, CHAN_PLAINTEXT, CHAN_SSL_CONNECTING, CHAN_SSL_ACCEPTING, CHAN_SSL_READWRITE, }; class FLpxSocketsI; // A port-listening socket. class FLpxListener { public: FLpxSocketsI *LSI; int BoundPort; FSocket* Socket; FLpxListener(FLpxSocketsI *lsi, int bp, FSocket* sock); ~FLpxListener(); }; // A communication socket. class FLpxChannel { public: FLpxSocketsI* LSI; int ChannelID; FSocket* Socket; SSL* SSLState; BIO* RecvBIO; BIO* SendBIO; // If recent_error is set, that means that a recent IO operation generated // an error. As a special case, EOF on read is considered an error, we use // the string "EOF" for this case. std::string RecentError; // OpenSSL has a rule: if you try to SSL_write and it returns // SSL_ERROR_WANT_READ, then you have to retry the write with the same // number of bytes. In this event, we record how many bytes we // attempted to write, which will enable us to retry. int RetryWriteNBytes; // True if the channel needs to be advanced. bool NeedAdvance; EChanState State; uint32_t NBytes; const char* Bytes; void Close(std::string_view error); // Copy data from the socket into the recv bio. // // If it detects an error or EOF, sets the RecentError flag. // void TransferSocketToRecvBIO(); // Copy data from the send bio to the socket. // // If it detects an error or EOF, sets the RecentError flag. // void TransferSendBIOToSocket(); // Check if an SSL error is serious. If so, close the channel. // // The 'retval' is the return value of the SSL function that returned an // error. // // All errors are considered serious except for SSL_ERROR_WANT_READ, which // is not serious because it is transient. However, if you get an // SSL_ERROR_WANT_READ when there's tons of data available in the read // buffer, that's inexplicable and therefore serious. // void CloseChannelIfSSLErrorIsSerious(int retval); FLpxChannel(FLpxSocketsI *lsi, FSocket* sock, int chid, SSL_CTX* ctx, EChanState state); ~FLpxChannel() { Close(""); } }; class FLpxSocketsI : public FLpxSockets { public: // Fatal error status. std::string FatalError; // We don't own the wrapper, we just have a pointer to it. // We require a guarantee that it outlives us. EngineWrapper* Luprex; // A general-purpose character buffer. char ChBuf[DRV_SHORTSTRING_SIZE]; TArray Channels; TArray Listeners; // Pointer to the socket subsystem. ISocketSubsystem* Subsys; SSL_CTX* ServerCTX; SSL_CTX* ClientSecureCTX; SSL_CTX* ClientInsecureCTX; FLpxSocketsI(EngineWrapper* w); virtual ~FLpxSocketsI() override; // Error handling. void SetError(const std::string& s); virtual std::string GetError() override { return FatalError; } bool AnyError() { return !FatalError.empty(); } // Return true if we're listening on port P. bool ListeningOnPort(int p); // Open a connection and return a socket. FSocket* OpenConnection(const std::string& host, const std::string& port, std::string& err); // Handle various phases of the operation. void HandleListenPorts(); void HandleNewOutgoingSockets(); void HandleSocketInputOutput(); // Main update routine. virtual void Update() override; }; static std::string strerror_str(int errnum) { char buf[256]; int status = strerror_s(buf, 256, errnum); if (status != 0) { snprintf(buf, 256, "unknown errno %d", errnum); } return buf; } static const char* dummy_cert = "-----BEGIN CERTIFICATE-----\n" "MIIDezCCAmOgAwIBAgIUajKmxrLMr9zBMlphrTJU5qKG8FgwDQYJKoZIhvcNAQEL\n" "BQAwTDELMAkGA1UEBhMCVVMxFTATBgNVBAgMDFBlbm5zeWx2YW5pYTESMBAGA1UE\n" "CgwJbG9jYWxob3N0MRIwEAYDVQQDDAlsb2NhbGhvc3QwIBcNMjIwMzIyMTczMzA4\n" "WhgPMjEyMjAyMjYxNzMzMDhaMEwxCzAJBgNVBAYTAlVTMRUwEwYDVQQIDAxQZW5u\n" "c3lsdmFuaWExEjAQBgNVBAoMCWxvY2FsaG9zdDESMBAGA1UEAwwJbG9jYWxob3N0\n" "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5OWIaKqYae4nPxvu5EP3\n" "VilcjApYcMT4+2ypfQoB6PEep5lwguA929rNsTKnhGsEiQAZ0eZPEZN7VhUwf/hz\n" "26jIyTT43ELkt6k97wwSZSXuT65RpSiemwEs6g2mMwzpgP6nv+yam4HjE9AKiHGN\n" "YeTV72Nw1EN70t6IjIf4jsJRXqDJkUx5sSSD6j0WBTOhzozIDgZHTDwiLhatE66m\n" "SNoD8oWC0PscbUgOJkFpbaCAS8RJmpsdgkTFae2rzL9cOFLGw6OgV/BV1J1s0ks8\n" "+veoMMtIO6fese+OZ+DyQbuGaoaltZUXzY6QjD5l34m2mGplelT7BrpcqJTBHwmh\n" "CwIDAQABo1MwUTAdBgNVHQ4EFgQUXQM5TVfJ9gpUXg8fZ8yfuUVcBP8wHwYDVR0j\n" "BBgwFoAUXQM5TVfJ9gpUXg8fZ8yfuUVcBP8wDwYDVR0TAQH/BAUwAwEB/zANBgkq\n" "hkiG9w0BAQsFAAOCAQEAqYX/ZGv0Qh/xdXppjnqojm8mH0giDW4tvwMqHcW3YRa3\n" "9J2yYot+rHjU5g4n6HEmWDBE0eqLz9n3Y3fkFzT8RWZwBaST965CgsfGofyuA2hC\n" "Ddn4Am3B5tTPmi8WWRZg8amhpGVD/mwkoVFIK0M337b1aZUJYPE+Kc9WetSL2KqB\n" "EhqSQpkAWhVadzP85dq2T9EDjAvhlFTFlDEBx1GDUcc8M0KQ9NEvLT7LgoUcbMiT\n" "PerlSZQTB0crchXTRSERgiwu80r7D6STn/RcPL9Fg5PkA94/d87jGbmV4sxSRsvM\n" "z+DnJGjHrV1J/jHPrnVvVLpigBlGno3C5O/sRw3gcQ==\n" "-----END CERTIFICATE-----\n"; static const char* dummy_key = "-----BEGIN PRIVATE KEY-----\n" "MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDk5Yhoqphp7ic/\n" "G+7kQ/dWKVyMClhwxPj7bKl9CgHo8R6nmXCC4D3b2s2xMqeEawSJABnR5k8Rk3tW\n" "FTB/+HPbqMjJNPjcQuS3qT3vDBJlJe5PrlGlKJ6bASzqDaYzDOmA/qe/7JqbgeMT\n" "0AqIcY1h5NXvY3DUQ3vS3oiMh/iOwlFeoMmRTHmxJIPqPRYFM6HOjMgOBkdMPCIu\n" "Fq0TrqZI2gPyhYLQ+xxtSA4mQWltoIBLxEmamx2CRMVp7avMv1w4UsbDo6BX8FXU\n" "nWzSSzz696gwy0g7p96x745n4PJBu4ZqhqW1lRfNjpCMPmXfibaYamV6VPsGulyo\n" "lMEfCaELAgMBAAECggEBAJa1AiFX4U4tva1xqNKmZV1XklWqIhzts7lnDBkF08gZ\n" "qcNT5Z5mIpR09eVropwvEidZ56Yp63l5D0XYYbyAS1gfQ0QnGot7h7fdOKgB3MK4\n" "PLY94gfKPNN17KqWHg2SvNNv1+cn04v78xUCb0zy5tHDp5Acexdm70ohtupARElJ\n" "LSHdS7ebsqZUFXbbM3BpPEsQLi3PrzNs1DrKkZ3rR6eMGrsDqExXx8/foi9aZKsd\n" "BGM2/kcTJ5aY6NhSv5iqO1oK46sbMrjVW/bYNsOyl0eFjwTRahn+Zhp/JMewZYeu\n" "715g6kzbZNwEzBLgrhNPF6E2ycEr/C6z5bE78g5QCkECgYEA8s07UUY25bjYiWWy\n" "W38pT7d/OXBSyKnq16N6MjVahl29r7nezFiDeLhLC0QiwXu/+qyxVZkB95MMGZXS\n" "AsaKFNis3AJ6eR4SYyhpSScYKNvlKIiW37TtR4FDcy7y5LL6tFpiDDIGH3LuyWNo\n" "d76142MBpv5aStnLGYU3pcZj43sCgYEA8VbNM4nqgSCQcbnHYjvsgphEMNSaoVie\n" "xob2uigXdV6Te0ayoUFBnVNKVsRhk+sswuTV4k1pK/On+USVl2tQ16tcaVMjTfSD\n" "HLYTJLmt6s4DcywWj5dfkbDoe5PulGXNZE960qXmOC62Lf0VMRwJ5x4FBRvGTjKC\n" "zvekI2/kO7ECgYEAhBGeclb/BXXGUvY+TgadMf9d9KBkZ0IFu8Xwcd8TnoLe6vbv\n" "ebery75zE228egIWKwREcYsIxuH1cvVLhrb35N73J7UxaTAyUD1rB598RL1XqPSj\n" "HIwNhReK2NxwwnWYaQHA02FiczjRKjooWPojdcwk2fEArDZLg1YzLrj7HIECgYEA\n" "htdx1Y8ESFtyeShMv5UtoxYCW6oeL3H9XH0CE6bc3IYYLvOkULbOO2HTEkGtJ2Fp\n" "5AbJfiS0U4tS2dI5Jp4eUDH9cxexjRfFvd/5ODbKdnver5X9kQMJsbQ/YPSZg66R\n" "oK9Lt7Bbvh5TScSy93psCgba1SzckspkDdGNkwMsaTECgYEAnFWaxormLUpXQRLs\n" "tKzMMHgVnHlsHiqXH432zmT2fpGZHYoWbsGuQjjrHGnSiu3QbDhnzM6y/T2GRs6z\n" "zHteIo/tzIyxg4MvJGJ9qANA7HoiKBdQ7G/I/NLJIyWAjj+e7/hgzKFcf+dpjpDq\n" "HcKc9a4WXhC7yu79e5BnKWltHXY=\n" "-----END PRIVATE KEY-----\n"; std::string SSLErrorString() { // Get the last code. int code = 0; while (true) { int icode = ERR_get_error(); if (icode == 0) break; code = icode; } // Fetch and clear errno. int terrno = errno; errno = 0; if (code != 0) { const char* rc = ERR_reason_error_string(code); if (rc != nullptr) { return rc; } else { return strerror_str(ERR_GET_REASON(code)); } } else if (terrno != 0) { return strerror_str(terrno); } else { return ""; } } static SSL_CTX* SSLNewContext(int verify) { SSL_CTX* ctx = SSL_CTX_new(TLS_method()); SSL_CTX_set_mode(ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); SSL_CTX_set_verify(ctx, verify, nullptr); return ctx; } static std::string SSLLoadCertificateAuthorities(SSL_CTX* ctx) { HCERTSTORE hStore = CertOpenSystemStoreW(0, L"ROOT"); if (!hStore) { return "Could not open system cert store."; } PCCERT_CONTEXT pContext = NULL; X509* x509; X509_STORE* store = SSL_CTX_get_cert_store(ctx); while (true) { pContext = CertEnumCertificatesInStore(hStore, pContext); if (pContext == nullptr) break; const unsigned char* encoded_cert = pContext->pbCertEncoded; x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded); if (x509) { X509_STORE_add_cert(store, x509); X509_free(x509); } } CertCloseStore(hStore, 0); return ""; } static std::string SSLUseCertificateString(SSL_CTX* ctx, const char* str) { ERR_clear_error(); BIO* bio = BIO_new(BIO_s_mem()); BIO_puts(bio, str); X509* certificate = PEM_read_bio_X509(bio, NULL, NULL, NULL); std::string result; if (SSL_CTX_use_certificate(ctx, certificate) <= 0) { result = SSLErrorString(); } X509_free(certificate); BIO_free(bio); return result; } std::string SSLLoadDummyCert(SSL_CTX* ctx) { std::string err1 = SSLUseCertificateString(ctx, dummy_cert); std::string err2 = SSLUseCertificateString(ctx, dummy_key); if (!err1.empty()) return err1; if (!err1.empty()) return err2; return ""; } // Return the amount of 'space left' in a BIO. This is a fiction, // because MEM BIOs technically have unlimited capacity. We're // artificially limiting them to a certain size because there's no // reason to buffer huge amounts of data. int BIOSpace(BIO* bio) { int space = (MAX_BIO_BUFFER)-BIO_pending(bio); if (space < 0) space = 0; return space; } // Discard the first nbytes in buffer. // This is a terribly inefficient way to discard data that has // already been processed. There has to be something better. void BIODiscard(BIO* b, int nbytes, char* chbuf) { while (nbytes > 0) { int nread = nbytes; if (nread > DRV_SHORTSTRING_SIZE) nread = DRV_SHORTSTRING_SIZE; int ndropped = BIO_read(b, chbuf, nread); check(ndropped == nread); nbytes -= ndropped; } } FLpxChannel::FLpxChannel(FLpxSocketsI* lsi, FSocket* sock, int chid, SSL_CTX* ctx, EChanState st) { LSI = lsi; ChannelID = chid; Socket = sock; RecvBIO = BIO_new(BIO_s_mem()); SendBIO = BIO_new(BIO_s_mem()); RecentError.clear(); RetryWriteNBytes = 0; NeedAdvance = true; if (st == CHAN_PLAINTEXT) { SSLState = nullptr; } else { SSLState = SSL_new(ctx); SSL_set_bio(SSLState, RecvBIO, SendBIO); } State = st; NBytes = 0; Bytes = 0; } void FLpxChannel::Close(std::string_view err) { // Close and release the SSL channel. // This frees the BIO objects as well. if (SSLState != nullptr) { SSL_free(SSLState); SSLState = nullptr; } RecvBIO = nullptr; SendBIO = nullptr; RecentError.clear(); RetryWriteNBytes = 0; NeedAdvance = false; // Close and release the socket. if (Socket != nullptr) { Socket->Close(); LSI->Subsys->DestroySocket(Socket); Socket = nullptr; } // Notify luprex that the channel has been closed. if (ChannelID > 0) { LSI->Luprex->play_notify_close(LSI->Luprex, ChannelID, err.size(), err.data()); } // Close everything else. State = CHAN_INACTIVE; ChannelID = -1; NBytes = 0; Bytes = nullptr; } void FLpxChannel::TransferSocketToRecvBIO() { if ((State == CHAN_INACTIVE) || (!RecentError.empty())) { return; } int32 bytesread; bool ok = Socket->Recv((uint8*)LSI->ChBuf, DRV_SHORTSTRING_SIZE, bytesread); if (!ok) { RecentError = "EOF"; return; } if (bytesread > 0) { int nstored = BIO_write(RecvBIO, LSI->ChBuf, bytesread); check(nstored == bytesread); NeedAdvance = true; } } void FLpxChannel::TransferSendBIOToSocket() { if ((State == CHAN_INACTIVE) || (!RecentError.empty())) { return; } char* data; int ndata = BIO_get_mem_data(SendBIO, &data); if (ndata > DRV_SHORTSTRING_SIZE) ndata = DRV_SHORTSTRING_SIZE; // It is an error to call this function when there is nothing in the send BIO. check(ndata > 0); int32 bytessent; bool ok = Socket->Send((const uint8*)data, ndata, bytessent); if (!ok) { RecentError = "Send failure on socket"; return; } if (bytessent > 0) { BIODiscard(SendBIO, bytessent, LSI->ChBuf); NeedAdvance = true; } } // Close the channel if there's a serious OpenSSL error. // void FLpxChannel::CloseChannelIfSSLErrorIsSerious(int retval) { int error = SSL_get_error(SSLState, retval); // Should never have write errors, because we're // using a memory BIO with unlimited capacity. check(error != SSL_ERROR_WANT_WRITE); // If we get a read error, make sure it's plausible: // if the recv bio is full, that makes no sense. if (error == SSL_ERROR_WANT_READ) { if (BIOSpace(RecvBIO) == 0) { Close("ssl waiting for data, but there's tons of data"); } return; } // Any other error is an actual error. Close // the channel. std::string errstr = SSLErrorString(); if (errstr == "") errstr = "unknown error"; Close(errstr); } FLpxListener::FLpxListener(FLpxSocketsI *lsi, int bp, FSocket *sock) { LSI = lsi; BoundPort = bp; Socket = sock; } FLpxListener::~FLpxListener() { if (Socket != nullptr) { Socket->Close(); LSI->Subsys->DestroySocket(Socket); Socket = nullptr; } } void FLpxSocketsI::SetError(const std::string& s) { if (FatalError.empty()) { FatalError = s; } } FLpxSocketsI::FLpxSocketsI(EngineWrapper *w) { Luprex = w; ServerCTX = nullptr; ClientSecureCTX = nullptr; ClientInsecureCTX = nullptr; Subsys = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM); if (Subsys == nullptr) { SetError("Cannot obtain the socket subsystem"); } ServerCTX = SSLNewContext(SSL_VERIFY_NONE); ClientSecureCTX = SSLNewContext(SSL_VERIFY_PEER); ClientInsecureCTX = SSLNewContext(SSL_VERIFY_NONE); SetError(SSLLoadCertificateAuthorities(ClientSecureCTX)); SetError(SSLLoadDummyCert(ServerCTX)); HandleListenPorts(); } FLpxSocketsI::~FLpxSocketsI() { if (ServerCTX != nullptr) { SSL_CTX_free(ServerCTX); } if (ClientSecureCTX != nullptr) { SSL_CTX_free(ClientSecureCTX); } if (ClientInsecureCTX != nullptr) { SSL_CTX_free(ClientInsecureCTX); } // Cleanup //for (ChanInfo& chan : chans_) { // close_channel(chan, ""); //} } bool FLpxSocketsI::ListeningOnPort(int p) { for (const FLpxListener& l : Listeners) { if (l.BoundPort == p) return true; } return false; } void FLpxSocketsI::HandleListenPorts() { uint32_t nports; const uint32_t* ports; Luprex->get_listen_ports(Luprex, &nports, &ports); for (uint32_t i = 0; i < nports; i++) { int port = ports[i]; if (!ListeningOnPort(port)) { // TODO: Open a listening socket. // Push the new port and socket onto Listeners. } } } FSocket* FLpxSocketsI::OpenConnection(const std::string& host, const std::string& port, std::string& err) { std::string hostport = host + ":" + port; FString fshost(host.size(), (const UTF8CHAR *)host.c_str()); FString fsport(port.size(), (const UTF8CHAR *)port.c_str()); FAddressInfoResult air = Subsys->GetAddressInfo(*fshost, *fsport, EAddressInfoFlags::Default, NAME_None, ESocketType::SOCKTYPE_Streaming); if (air.Results.Num() == 0) { err = std::string("DNS Lookup failed for: ") + hostport; return nullptr; } const FAddressInfoResultData& dns = air.Results[0]; const FInternetAddr& inetaddr = *dns.Address; std::string sdescription = host + ":" + port; FString description(sdescription.c_str()); FSocket* Socket = Subsys->CreateSocket(NAME_Stream, description, inetaddr.GetProtocolType()); if (Socket == nullptr) { err = std::string("Could not create socket for ") + hostport; return nullptr; } bool connected = Socket->Connect(inetaddr); if (!connected) { Subsys->DestroySocket(Socket); err = std::string("Could not connect to ") + hostport; return nullptr; } Socket->SetNonBlocking(true); err = ""; return Socket; } void FLpxSocketsI::HandleNewOutgoingSockets() { uint32_t nchids; const uint32_t* chids; Luprex->get_new_outgoing(Luprex, &nchids, &chids); for (uint32_t i = 0; i < nchids; i++) { uint32_t chid = chids[i]; std::string err, cert, host, port; const char* target = Luprex->get_target(Luprex, chid); drvutil::split_target(target, cert, host, port); if (cert.empty() || host.empty() || port.empty()) { std::string message = "invalid target: "; message += target; Luprex->play_notify_close(Luprex, chid, message.size(), message.c_str()); continue; } SSL_CTX* ctx = nullptr; if (cert == "cert") { ctx = ClientSecureCTX; } else if (cert == "nocert") { ctx = ClientInsecureCTX; } else { std::string message = "invalid cert rule: "; message += target; Luprex->play_notify_close(Luprex, chid, message.size(), message.c_str()); continue; } FSocket *sock = OpenConnection(host, port, err); if (sock == nullptr) { Luprex->play_notify_close(Luprex, chid, err.size(), err.c_str()); continue; } Channels.Emplace(this, sock, chid, ctx, CHAN_SSL_CONNECTING); } Luprex->play_clear_new_outgoing(Luprex); } void FLpxSocketsI::HandleSocketInputOutput() { } void FLpxSocketsI::Update() { HandleNewOutgoingSockets(); HandleSocketInputOutput(); } FLpxSockets* FLpxSockets::Create(EngineWrapper* w) { return new FLpxSocketsI(w); }