#include "network_internal.h" namespace NetworkHle { enum SslError { SSL_OK = 0, SSL_ERR_FAILED = -1, SSL_ERR_RAGAIN = -2, SSL_ERR_WAGAIN = -3, SSL_ERR_SYSCALL = -5, SSL_ERR_ZERO = -6, SSL_ERR_ID = -8, SSL_ERR_VCOMMONNAME = -9, }; enum SslIoctlv { IOCTLV_NET_SSL_NEW = 0x01, IOCTLV_NET_SSL_CONNECT = 0x02, IOCTLV_NET_SSL_DOHANDSHAKE = 0x03, IOCTLV_NET_SSL_READ = 0x04, IOCTLV_NET_SSL_WRITE = 0x05, IOCTLV_NET_SSL_SHUTDOWN = 0x06, IOCTLV_NET_SSL_SETCLIENTCERT = 0x07, IOCTLV_NET_SSL_SETCLIENTCERTDEFAULT = 0x08, IOCTLV_NET_SSL_REMOVECLIENTCERT = 0x09, IOCTLV_NET_SSL_SETROOTCA = 0x0A, IOCTLV_NET_SSL_SETROOTCADEFAULT = 0x0B, IOCTLV_NET_SSL_DOHANDSHAKEEX = 0x0C, IOCTLV_NET_SSL_SETBUILTINROOTCA = 0x0D, IOCTLV_NET_SSL_SETBUILTINCLIENTCERT = 0x0E, IOCTLV_NET_SSL_DISABLEVERIFYOPTIONFORDEBUG = 0x0F, IOCTLV_NET_SSL_DEBUGGETVERSION = 0x14, IOCTLV_NET_SSL_DEBUGGETTIME = 0x15, }; constexpr int kMaxSslSessions = 4; struct SslSession { bool active = false; bool handshaked = false; bool plaintextWfc = false; uint32_t socketFd = UINT32_MAX; NativeSocket native = kInvalidSocket; std::string hostname; std::vector nasWriteBuffer; std::vector decrypted; std::vector encryptedExtra; // Failure-report one-shots; cleared with the session by ClearSslSession. The // handshake re-runs on every read/write, so a failing one repeats forever. bool loggedHandshakeFail = false; int32_t lastLoggedReadError = 0; int32_t lastLoggedWriteError = 0; #ifdef _WIN32 bool haveCred = false; bool haveContext = false; CredHandle cred{}; CtxtHandle context{}; SecPkgContext_StreamSizes sizes{}; #endif }; static std::array g_sslSessions; // Logging wrapper around the platform handshake implementation; see below. static int32_t SslHandshake(SslSession& ssl); static bool IsRetroNasSslHost(std::string_view hostname) { if (!RetroRewindProfileActive()) { return false; } const std::string lowered = Lower(hostname); return StartsWith(lowered, "nas.") || StartsWith(lowered, "naswii."); } static bool IsRetroPlaintextSslHost(std::string_view hostname) { if (IsRetroNasSslHost(hostname)) { return true; } if (!RetroRewindProfileActive()) { return false; } const std::string lowered = Lower(hostname); return StartsWith(lowered, "sake.gs.") || lowered.find(".sake.gs.") != std::string::npos || StartsWith(lowered, "gamestats.gs.") || lowered.find(".gamestats.gs.") != std::string::npos || StartsWith(lowered, "gamestats2.gs.") || lowered.find(".gamestats2.gs.") != std::string::npos || StartsWith(lowered, "race.gs.") || lowered.find(".race.gs.") != std::string::npos; } static std::optional ParseHttpContentLength(std::string_view headers) { std::optional parsedLength; size_t lineStart = 0; while (lineStart <= headers.size()) { const size_t lineEnd = headers.find("\r\n", lineStart); const std::string_view line = headers.substr( lineStart, lineEnd == std::string_view::npos ? headers.size() - lineStart : lineEnd - lineStart); const size_t colon = line.find(':'); if (colon != std::string_view::npos && Lower(line.substr(0, colon)) == "content-length") { size_t valueStart = colon + 1; while (valueStart < line.size() && (line[valueStart] == ' ' || line[valueStart] == '\t')) { ++valueStart; } size_t valueEnd = line.size(); while (valueEnd > valueStart && (line[valueEnd - 1] == ' ' || line[valueEnd - 1] == '\t')) { --valueEnd; } size_t value = 0; const auto [end, error] = std::from_chars( line.data() + valueStart, line.data() + valueEnd, value, 10); if (error != std::errc{} || end != line.data() + valueEnd || (parsedLength && *parsedLength != value)) { return std::nullopt; } parsedLength = value; } if (lineEnd == std::string_view::npos) { break; } lineStart = lineEnd + 2; } return parsedLength; } // The Retro-WFC server rejects a NAS "POST /ac" auth body split across TCP // segments, so buffer guest chunks until Content-Length is satisfied, then // flush as one write. Flush at 16 KiB if it never terminates, and immediately // if Content-Length can't be parsed. static NasSslWriteAction AccumulateNasRequest(std::vector& buffer, const uint8_t* data, uint32_t size, std::vector& patched) { buffer.insert(buffer.end(), data, data + size); if (buffer.size() > 16 * 1024) { patched.swap(buffer); buffer.clear(); return NasSslWriteAction::Ready; } const std::string accumulated(reinterpret_cast(buffer.data()), buffer.size()); const size_t headerEnd = accumulated.find("\r\n\r\n"); if (headerEnd == std::string::npos) { return NasSslWriteAction::Buffered; } const std::optional contentLength = ParseHttpContentLength(std::string_view(accumulated).substr(0, headerEnd)); if (!contentLength) { patched.swap(buffer); buffer.clear(); return NasSslWriteAction::Ready; } const size_t requestSize = headerEnd + 4 + *contentLength; if (accumulated.size() < requestSize) { return NasSslWriteAction::Buffered; } patched.assign(buffer.begin(), buffer.begin() + requestSize); if (accumulated.size() > requestSize) { patched.insert(patched.end(), buffer.begin() + requestSize, buffer.end()); } buffer.clear(); return NasSslWriteAction::Ready; } static bool StartsNasAuthRequest(const uint8_t* data, uint32_t size) { return size >= 9 && std::memcmp(data, "POST /ac ", 9) == 0; } // SSL route: the session carries the hostname the guest asked for, so the NAS // host is identified by name. Deliberately not IsRetroNasSslHost - the SSL write // path re-assembles NAS auth regardless of which profile is active. static NasSslWriteAction PrepareNasSslWrite(SslSession& ssl, const uint8_t* data, uint32_t size, std::vector& patched) { if (!data || size == 0) { return NasSslWriteAction::PassThrough; } const std::string loweredHost = Lower(ssl.hostname); const bool isNasHost = StartsWith(loweredHost, "nas.") || StartsWith(loweredHost, "naswii."); if (!isNasHost || (!StartsNasAuthRequest(data, size) && ssl.nasWriteBuffer.empty())) { return NasSslWriteAction::PassThrough; } return AccumulateNasRequest(ssl.nasWriteBuffer, data, size, patched); } // Plain-TCP route: a rerouted 443->80 NAS connection has no hostname on the // socket, so the peer port and the stream type are what identify it. NasSslWriteAction PreparePlainNasTcpWrite(WiiSocket& socket, const uint8_t* data, uint32_t size, std::vector& patched) { if (!data || size == 0 || socket.type != SOCK_STREAM || socket.peerPort != 80) { return NasSslWriteAction::PassThrough; } if (!StartsNasAuthRequest(data, size) && socket.nasWriteBuffer.empty()) { return NasSslWriteAction::PassThrough; } return AccumulateNasRequest(socket.nasWriteBuffer, data, size, patched); } static bool WriteSslReturn(const std::vector& in, int32_t value) { if (in.empty() || !in[0].address || in[0].size < 4) { return false; } Memory::Write32(in[0].address, static_cast(value)); return true; } static int ReadSslId(const std::vector& out) { if (out.empty() || !out[0].address || out[0].size < 4) { return -1; } return static_cast(Memory::Read32(out[0].address)) - 1; } static bool IsSslIdValid(int id) { return id >= 0 && id < kMaxSslSessions && g_sslSessions[id].active; } #ifdef _WIN32 static bool IsSecuritySuccess(SECURITY_STATUS status) { return status == SEC_E_OK || status == SEC_I_CONTINUE_NEEDED || status == SEC_I_INCOMPLETE_CREDENTIALS; } static bool SendAll(NativeSocket socket, const uint8_t* data, size_t size) { size_t offset = 0; while (offset < size) { const int chunk = static_cast(std::min(size - offset, 64 * 1024)); const int ret = send(socket, reinterpret_cast(data + offset), chunk, 0); if (ret <= 0) { // Reported by the SSL_WRITE / handshake caller as SSL_ERR_SYSCALL. return false; } offset += static_cast(ret); } return true; } static int RecvBlocking(NativeSocket socket, std::vector& buffer) { std::array temp{}; const int ret = recv(socket, reinterpret_cast(temp.data()), static_cast(temp.size()), 0); if (ret > 0) { buffer.insert(buffer.end(), temp.begin(), temp.begin() + ret); } return ret; } static void KeepExtraBuffer(std::vector& dest, const SecBuffer& buffer) { dest.clear(); if (buffer.BufferType == SECBUFFER_EXTRA && buffer.pvBuffer && buffer.cbBuffer) { const auto* begin = static_cast(buffer.pvBuffer); dest.assign(begin, begin + buffer.cbBuffer); } } static void ClearSslSession(SslSession& ssl) { if (ssl.haveContext) { DeleteSecurityContext(&ssl.context); } if (ssl.haveCred) { FreeCredentialsHandle(&ssl.cred); } ssl = {}; } static int32_t EnsureSslCredentials(SslSession& ssl) { if (ssl.haveCred) { return SSL_OK; } SCHANNEL_CRED cred{}; cred.dwVersion = SCHANNEL_CRED_VERSION; // Leave certificate validation to Schannel. SCH_CRED_MANUAL_CRED_VALIDATION // suppresses that validation and requires an explicit CertGetCertificateChain // implementation, which this HLE does not provide. The target hostname passed // to InitializeSecurityContextA below is therefore checked together with the // server certificate chain. cred.dwFlags = SCH_USE_STRONG_CRYPTO | SCH_CRED_NO_DEFAULT_CREDS | SCH_CRED_AUTO_CRED_VALIDATION; TimeStamp expiry{}; const SECURITY_STATUS status = AcquireCredentialsHandleA( nullptr, const_cast(UNISP_NAME_A), SECPKG_CRED_OUTBOUND, nullptr, &cred, nullptr, nullptr, &ssl.cred, &expiry); if (status != SEC_E_OK) { return SSL_ERR_FAILED; } ssl.haveCred = true; return SSL_OK; } static int32_t SslHandshakeImpl(SslSession& ssl) { if (ssl.plaintextWfc) { ssl.handshaked = true; return SSL_OK; } // Schannel can authenticate a certificate chain without authenticating a // server identity when no target name is supplied. Refuse that ambiguous // mode rather than accepting a certificate for an unrelated endpoint. if (ssl.hostname.empty()) { return SSL_ERR_VCOMMONNAME; } const int32_t credRet = EnsureSslCredentials(ssl); if (credRet != SSL_OK) { return credRet; } if (ssl.native == kInvalidSocket) { return SSL_ERR_SYSCALL; } if (ssl.handshaked) { return SSL_OK; } DWORD attrs = 0; TimeStamp expiry{}; std::vector incoming = std::move(ssl.encryptedExtra); ssl.encryptedExtra.clear(); constexpr DWORD flags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM | ISC_REQ_EXTENDED_ERROR; for (int step = 0; step < 128; ++step) { SecBuffer outBuffer{}; outBuffer.BufferType = SECBUFFER_TOKEN; SecBufferDesc outDesc{}; outDesc.ulVersion = SECBUFFER_VERSION; outDesc.cBuffers = 1; outDesc.pBuffers = &outBuffer; SecBuffer inBuffers[2]{}; SecBufferDesc inDesc{}; SecBufferDesc* inDescPtr = nullptr; if (!incoming.empty()) { inBuffers[0].BufferType = SECBUFFER_TOKEN; inBuffers[0].pvBuffer = incoming.data(); inBuffers[0].cbBuffer = static_cast(incoming.size()); inBuffers[1].BufferType = SECBUFFER_EMPTY; inDesc.ulVersion = SECBUFFER_VERSION; inDesc.cBuffers = 2; inDesc.pBuffers = inBuffers; inDescPtr = &inDesc; } const SECURITY_STATUS status = InitializeSecurityContextA( &ssl.cred, ssl.haveContext ? &ssl.context : nullptr, const_cast(ssl.hostname.c_str()), flags, 0, SECURITY_NATIVE_DREP, inDescPtr, 0, &ssl.context, &outDesc, &attrs, &expiry); if (status != SEC_E_INVALID_HANDLE) { ssl.haveContext = true; } if (outBuffer.pvBuffer && outBuffer.cbBuffer) { const bool sent = SendAll(ssl.native, static_cast(outBuffer.pvBuffer), outBuffer.cbBuffer); FreeContextBuffer(outBuffer.pvBuffer); if (!sent) { return SSL_ERR_SYSCALL; } } if (status == SEC_E_OK) { if (inDescPtr) { KeepExtraBuffer(ssl.encryptedExtra, inBuffers[1]); } const SECURITY_STATUS sizeStatus = QueryContextAttributesA(&ssl.context, SECPKG_ATTR_STREAM_SIZES, &ssl.sizes); if (sizeStatus != SEC_E_OK) { return SSL_ERR_FAILED; } ssl.handshaked = true; return SSL_OK; } if (status == SEC_E_INCOMPLETE_MESSAGE) { const int ret = RecvBlocking(ssl.native, incoming); if (ret == 0) { return SSL_ERR_ZERO; } if (ret < 0) { return SSL_ERR_RAGAIN; } continue; } if (status == SEC_I_CONTINUE_NEEDED || status == SEC_I_INCOMPLETE_CREDENTIALS) { std::vector extra; if (inDescPtr) { KeepExtraBuffer(extra, inBuffers[1]); } incoming = std::move(extra); const int ret = RecvBlocking(ssl.native, incoming); if (ret == 0) { return SSL_ERR_ZERO; } if (ret < 0) { return SSL_ERR_RAGAIN; } continue; } return status == SEC_E_WRONG_PRINCIPAL ? SSL_ERR_VCOMMONNAME : SSL_ERR_FAILED; } return SSL_ERR_FAILED; } static int32_t SslWrite(SslSession& ssl, const uint8_t* data, uint32_t size) { if (!data || size == 0) { return SSL_ERR_ZERO; } const int32_t handshakeRet = SslHandshake(ssl); if (handshakeRet != SSL_OK) { return handshakeRet; } if (ssl.plaintextWfc) { return SendAll(ssl.native, data, size) ? static_cast(size) : SSL_ERR_SYSCALL; } uint32_t total = 0; while (total < size) { const uint32_t chunk = std::min(size - total, ssl.sizes.cbMaximumMessage); std::vector packet(ssl.sizes.cbHeader + chunk + ssl.sizes.cbTrailer); std::memcpy(packet.data() + ssl.sizes.cbHeader, data + total, chunk); SecBuffer buffers[4]{}; buffers[0].BufferType = SECBUFFER_STREAM_HEADER; buffers[0].pvBuffer = packet.data(); buffers[0].cbBuffer = ssl.sizes.cbHeader; buffers[1].BufferType = SECBUFFER_DATA; buffers[1].pvBuffer = packet.data() + ssl.sizes.cbHeader; buffers[1].cbBuffer = chunk; buffers[2].BufferType = SECBUFFER_STREAM_TRAILER; buffers[2].pvBuffer = packet.data() + ssl.sizes.cbHeader + chunk; buffers[2].cbBuffer = ssl.sizes.cbTrailer; buffers[3].BufferType = SECBUFFER_EMPTY; SecBufferDesc desc{}; desc.ulVersion = SECBUFFER_VERSION; desc.cBuffers = 4; desc.pBuffers = buffers; const SECURITY_STATUS status = EncryptMessage(&ssl.context, 0, &desc, 0); if (status != SEC_E_OK) { return SSL_ERR_FAILED; } const size_t encryptedSize = static_cast(buffers[0].cbBuffer) + buffers[1].cbBuffer + buffers[2].cbBuffer; if (!SendAll(ssl.native, packet.data(), encryptedSize)) { return SSL_ERR_SYSCALL; } total += chunk; } return static_cast(total); } static int32_t SslRead(SslSession& ssl, uint8_t* out, uint32_t size) { if (!out || size == 0) { return SSL_ERR_ZERO; } const int32_t handshakeRet = SslHandshake(ssl); if (handshakeRet != SSL_OK) { return handshakeRet; } if (ssl.plaintextWfc) { const int ret = recv(ssl.native, reinterpret_cast(out), static_cast(size), 0); if (ret == 0) { return SSL_ERR_ZERO; } if (ret < 0) { return SSL_ERR_RAGAIN; } return ret; } while (ssl.decrypted.empty()) { std::vector encrypted = std::move(ssl.encryptedExtra); ssl.encryptedExtra.clear(); if (encrypted.empty()) { const int ret = RecvBlocking(ssl.native, encrypted); if (ret == 0) { return SSL_ERR_ZERO; } if (ret < 0) { return SSL_ERR_RAGAIN; } } for (;;) { SecBuffer buffers[4]{}; buffers[0].BufferType = SECBUFFER_DATA; buffers[0].pvBuffer = encrypted.data(); buffers[0].cbBuffer = static_cast(encrypted.size()); buffers[1].BufferType = SECBUFFER_EMPTY; buffers[2].BufferType = SECBUFFER_EMPTY; buffers[3].BufferType = SECBUFFER_EMPTY; SecBufferDesc desc{}; desc.ulVersion = SECBUFFER_VERSION; desc.cBuffers = 4; desc.pBuffers = buffers; const SECURITY_STATUS status = DecryptMessage(&ssl.context, &desc, 0, nullptr); if (status == SEC_E_INCOMPLETE_MESSAGE) { const int ret = RecvBlocking(ssl.native, encrypted); if (ret == 0) { return SSL_ERR_ZERO; } if (ret < 0) { return SSL_ERR_RAGAIN; } continue; } if (status == SEC_I_CONTEXT_EXPIRED) { return SSL_ERR_ZERO; } if (!IsSecuritySuccess(status) && status != SEC_I_RENEGOTIATE) { return SSL_ERR_FAILED; } for (const SecBuffer& buffer : buffers) { if (buffer.BufferType == SECBUFFER_DATA && buffer.pvBuffer && buffer.cbBuffer) { const auto* begin = static_cast(buffer.pvBuffer); ssl.decrypted.insert(ssl.decrypted.end(), begin, begin + buffer.cbBuffer); } else if (buffer.BufferType == SECBUFFER_EXTRA && buffer.pvBuffer && buffer.cbBuffer) { const auto* begin = static_cast(buffer.pvBuffer); ssl.encryptedExtra.assign(begin, begin + buffer.cbBuffer); } } break; } } const uint32_t copied = std::min(size, static_cast(ssl.decrypted.size())); std::memcpy(out, ssl.decrypted.data(), copied); ssl.decrypted.erase(ssl.decrypted.begin(), ssl.decrypted.begin() + copied); return copied == 0 ? SSL_ERR_ZERO : static_cast(copied); } #else static void ClearSslSession(SslSession& ssl) { ssl = {}; } static int32_t SslHandshakeImpl(SslSession&) { return SSL_ERR_FAILED; } static int32_t SslWrite(SslSession&, const uint8_t*, uint32_t) { return SSL_ERR_FAILED; } static int32_t SslRead(SslSession&, uint8_t*, uint32_t) { return SSL_ERR_FAILED; } #endif // The handshake runs on every SSL read/write, so a failure repeats for as long // as the session lives; report only the first one. static int32_t SslHandshake(SslSession& ssl) { const int32_t result = SslHandshakeImpl(ssl); if (result != SSL_OK && !ssl.loggedHandshakeFail) { ssl.loggedHandshakeFail = true; NetFail("ssl handshake FAILED host=%s ssl_err=%d", ssl.hostname.empty() ? "?" : ssl.hostname.c_str(), result); } return result; } void ClearSslSessionsForSocket(uint32_t fd) { for (SslSession& ssl : g_sslSessions) { if (ssl.active && ssl.socketFd == fd) { ClearSslSession(ssl); } } } int32_t HandleSslIoctlv(uint32_t cmd, const std::vector& in, const std::vector& out) { switch (cmd) { case IOCTLV_NET_SSL_NEW: { // out[0] carries the guest verify option. Host TLS verification is // always enforced by Schannel, but still read this value so a request // pointing outside guest memory faults here as it always has. if (!out.empty() && out[0].address && out[0].size >= 4) { (void)Memory::Read32(out[0].address); } std::string hostname = out.size() > 1 ? ReadGuestString(out[1].address, out[1].size) : ""; for (int i = 0; i < kMaxSslSessions; ++i) { if (!g_sslSessions[i].active) { ClearSslSession(g_sslSessions[i]); g_sslSessions[i].active = true; g_sslSessions[i].hostname = hostname; WriteSslReturn(in, i + 1); return 0; } } WriteSslReturn(in, SSL_ERR_FAILED); NetFail("SSL_NEW host=%s FAILED: all %d session slots in use", hostname.empty() ? "?" : hostname.c_str(), kMaxSslSessions); return 0; } case IOCTLV_NET_SSL_CONNECT: { const int sslId = ReadSslId(out); if (!IsSslIdValid(sslId)) { WriteSslReturn(in, SSL_ERR_ID); return 0; } if (out.size() < 2 || !out[1].address || out[1].size < 4) { WriteSslReturn(in, SSL_ERR_FAILED); return 0; } const uint32_t socketFd = Memory::Read32(out[1].address); WiiSocket* socket = GetWiiSocket(socketFd); if (!socket) { WriteSslReturn(in, SSL_ERR_SYSCALL); return 0; } SslSession& ssl = g_sslSessions[sslId]; ssl.socketFd = socketFd; ssl.native = socket->native; ssl.plaintextWfc = false; socket->nonblocking = false; SetNonBlocking(socket->native, false); if (IsRetroPlaintextSslHost(ssl.hostname) && socket->peerPort == 443) { const int32_t reroute = ReconnectWiiSocket(*socket, 80); if (reroute != 0) { WriteSslReturn(in, SSL_ERR_SYSCALL); NetFail("SSL_CONNECT host=%s 443->80 plaintext reroute FAILED wii=%d", ssl.hostname.c_str(), reroute); return 0; } ssl.native = socket->native; ssl.plaintextWfc = true; } #ifdef _WIN32 const int timeoutMs = 15000; setsockopt(socket->native, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&timeoutMs), sizeof(timeoutMs)); setsockopt(socket->native, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast(&timeoutMs), sizeof(timeoutMs)); #endif WriteSslReturn(in, SSL_OK); return 0; } case IOCTLV_NET_SSL_DOHANDSHAKE: case IOCTLV_NET_SSL_DOHANDSHAKEEX: { const int sslId = ReadSslId(out); WriteSslReturn(in, IsSslIdValid(sslId) ? SslHandshake(g_sslSessions[sslId]) : SSL_ERR_ID); return 0; } case IOCTLV_NET_SSL_WRITE: { const int sslId = ReadSslId(out); if (!IsSslIdValid(sslId)) { WriteSslReturn(in, SSL_ERR_ID); return 0; } if (out.size() < 2 || !out[1].address) { WriteSslReturn(in, SSL_ERR_FAILED); return 0; } const auto* data = Memory::GetPointer(out[1].address, out[1].size); std::vector patched; const NasSslWriteAction nasAction = PrepareNasSslWrite(g_sslSessions[sslId], data, out[1].size, patched); if (nasAction == NasSslWriteAction::Buffered) { WriteSslReturn(in, static_cast(out[1].size)); return 0; } const bool patchedSslWrite = nasAction == NasSslWriteAction::Ready; const uint8_t* writeData = patchedSslWrite ? patched.data() : data; const uint32_t writeSize = patchedSslWrite ? static_cast(patched.size()) : out[1].size; SslSession& writeSession = g_sslSessions[sslId]; int32_t result = SslWrite(writeSession, writeData, writeSize); if (patchedSslWrite && result == static_cast(writeSize)) { result = static_cast(out[1].size); } // SSL_ERR_WAGAIN is a retry, not a failure; the SDK re-issues the write, // so only a change of error is reported. if (result < 0 && result != SSL_ERR_WAGAIN && result != writeSession.lastLoggedWriteError) { writeSession.lastLoggedWriteError = result; NetFail("SSL_WRITE host=%s size=%u FAILED ssl_err=%d", writeSession.hostname.empty() ? "?" : writeSession.hostname.c_str(), writeSize, result); } WriteSslReturn(in, result); return 0; } case IOCTLV_NET_SSL_READ: { const int sslId = ReadSslId(out); if (!IsSslIdValid(sslId)) { WriteSslReturn(in, SSL_ERR_ID); return 0; } if (in.size() < 2 || !in[1].address) { WriteSslReturn(in, SSL_ERR_FAILED); return 0; } auto* data = Memory::GetPointer(in[1].address, in[1].size); SslSession& readSession = g_sslSessions[sslId]; const int32_t result = SslRead(readSession, data, in[1].size); if (result < 0 && result != SSL_ERR_RAGAIN && result != readSession.lastLoggedReadError) { readSession.lastLoggedReadError = result; NetFail("SSL_READ host=%s FAILED ssl_err=%d%s", readSession.hostname.empty() ? "?" : readSession.hostname.c_str(), result, result == SSL_ERR_ZERO ? " (peer closed)" : ""); } WriteSslReturn(in, result); return 0; } case IOCTLV_NET_SSL_SHUTDOWN: { const int sslId = ReadSslId(out); if (!IsSslIdValid(sslId)) { WriteSslReturn(in, SSL_ERR_ID); return 0; } ClearSslSession(g_sslSessions[sslId]); WriteSslReturn(in, SSL_OK); return 0; } case IOCTLV_NET_SSL_SETCLIENTCERT: case IOCTLV_NET_SSL_SETCLIENTCERTDEFAULT: case IOCTLV_NET_SSL_REMOVECLIENTCERT: case IOCTLV_NET_SSL_SETROOTCA: case IOCTLV_NET_SSL_SETROOTCADEFAULT: case IOCTLV_NET_SSL_SETBUILTINROOTCA: case IOCTLV_NET_SSL_SETBUILTINCLIENTCERT: case IOCTLV_NET_SSL_DISABLEVERIFYOPTIONFORDEBUG: { const int sslId = ReadSslId(out); WriteSslReturn(in, IsSslIdValid(sslId) ? SSL_OK : SSL_ERR_ID); return 0; } case IOCTLV_NET_SSL_DEBUGGETVERSION: case IOCTLV_NET_SSL_DEBUGGETTIME: WriteSslReturn(in, SSL_OK); return 0; default: WriteSslReturn(in, SSL_ERR_FAILED); return 0; } } } // namespace NetworkHle