mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 08:33:12 -05:00
Implement getservbyname (H1bArJPagV0) in libScePosixForWebKit. Cloud Play (PPSA99600) imports it from this library only (registry index 408, call site 0x1042124); once fnmatch (#3545) is in, its predicted load stop is: FAIL: unresolved ELF import H1bArJPagV0 from libScePosixForWebKit.prx The ps5-payload-dev SDK stubs (f5d40f02, sce_stubs/) declare getservbyname in libScePosixForWebKit.c and not in libkernel.c or libSceLibcInternal.c. The call site is the service-name branch of a getaddrinfo inside the title: a non-numeric service is looked up with protocol "tcp", "udp" or "sctp" and the port is read big-endian at offset 16 of the result, which fixes the servent layout used here (name, aliases, 32-bit port, protocol at 0, 8, 16, 24). The guest has no /etc/services and the console's file is unknown, so the function searches 37 lines of FreeBSD 11.4 etc/services (8c639945): ftp-data, ftp, ssh, telnet, smtp, domain, tftp, http, pop3, ntp, imap, https, rtsp, submission, imaps and pop3s with their aliases and protocols, in file order. None of their names or aliases appears on an earlier line of the full file, so these lookups answer as the full file would. The search follows files_servent in lib/libc/net/getservent.c (8c639945): exact, case-sensitive match on the name or an alias, a null protocol takes the first match, the port in network byte order, the result in per-thread storage like getserv's NSS TLS state. Any other name returns null without touching errno, as for a name the file lacks. The repository's getaddrinfo still resolves service names through the host, so there was no table to share; this is recorded in TechnicalDebt.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
360 lines
15 KiB
C++
360 lines
15 KiB
C++
#ifdef _WIN32
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <winsock2.h>
|
|
#include <ws2tcpip.h>
|
|
#else
|
|
#include <sys/socket.h>
|
|
#include <netdb.h>
|
|
#include <netinet/in.h>
|
|
#endif
|
|
#include "prx/libc/include/general/VabiMacros.hpp"
|
|
#include "prx/libc/include/HostThreadLocal.hpp"
|
|
#include <cstdint>
|
|
#include <array>
|
|
#include <cstring>
|
|
#include <cstdlib>
|
|
#include <cerrno>
|
|
#include <new>
|
|
#include <string>
|
|
#include <vector>
|
|
#include "GuestResolver.hpp"
|
|
|
|
extern "C" int* APS5_VABI sceNetErrnoLoc();
|
|
|
|
namespace {
|
|
struct HostLookup {
|
|
GuestResolver::HostEntry entry{};
|
|
std::string name;
|
|
std::vector<std::string> aliasNames;
|
|
std::vector<char*> aliases;
|
|
std::vector<std::string> addressBytes;
|
|
std::vector<char*> addresses;
|
|
};
|
|
|
|
struct ServiceRecord {
|
|
const char* name;
|
|
std::uint16_t port;
|
|
const char* protocol;
|
|
std::array<const char*, 2> aliases;
|
|
};
|
|
|
|
constexpr ServiceRecord Services[]{
|
|
{"ftp-data", 20, "tcp", {}}, {"ftp-data", 20, "udp", {}}, {"ftp-data", 20, "sctp", {}},
|
|
{"ftp", 21, "tcp", {}}, {"ftp", 21, "udp", {}}, {"ftp", 21, "sctp", {}},
|
|
{"ssh", 22, "tcp", {}}, {"ssh", 22, "udp", {}}, {"ssh", 22, "sctp", {}},
|
|
{"telnet", 23, "tcp", {}}, {"telnet", 23, "udp", {}},
|
|
{"smtp", 25, "tcp", {"mail"}}, {"smtp", 25, "udp", {"mail"}},
|
|
{"domain", 53, "tcp", {}}, {"domain", 53, "udp", {}},
|
|
{"tftp", 69, "tcp", {}}, {"tftp", 69, "udp", {}},
|
|
{"http", 80, "tcp", {"www", "www-http"}}, {"http", 80, "udp", {"www", "www-http"}},
|
|
{"http", 80, "sctp", {"www", "www-http"}},
|
|
{"pop3", 110, "tcp", {}}, {"pop3", 110, "udp", {}},
|
|
{"ntp", 123, "tcp", {}}, {"ntp", 123, "udp", {}},
|
|
{"imap", 143, "tcp", {"imap2", "imap4"}}, {"imap", 143, "udp", {"imap2", "imap4"}},
|
|
{"https", 443, "tcp", {}}, {"https", 443, "udp", {}}, {"https", 443, "sctp", {}},
|
|
{"rtsp", 554, "tcp", {}}, {"rtsp", 554, "udp", {}},
|
|
{"submission", 587, "tcp", {}}, {"submission", 587, "udp", {}},
|
|
{"imaps", 993, "tcp", {}}, {"imaps", 993, "udp", {}},
|
|
{"pop3s", 995, "tcp", {"spop3"}}, {"pop3s", 995, "udp", {"spop3"}},
|
|
};
|
|
|
|
struct ServiceLookup {
|
|
GuestResolver::ServiceEntry entry{};
|
|
std::string name;
|
|
std::string protocol;
|
|
std::vector<std::string> aliasNames;
|
|
std::vector<char*> aliases;
|
|
};
|
|
|
|
bool NumericAddress(const char* name, std::array<unsigned char, 4>& address) {
|
|
std::array<unsigned char, 3> parts{};
|
|
std::size_t count = 0;
|
|
std::uint64_t value;
|
|
bool digit;
|
|
for (;;) {
|
|
if (*name < '0' || *name > '9') return false;
|
|
value = 0;
|
|
int base = 10;
|
|
digit = false;
|
|
if (*name == '0') {
|
|
++name;
|
|
if (*name == 'x' || *name == 'X') { base = 16; ++name; }
|
|
else { base = 8; digit = true; }
|
|
}
|
|
for (;;) {
|
|
const int next = *name >= '0' && *name <= '9' ? *name - '0' :
|
|
*name >= 'a' && *name <= 'f' ? *name - 'a' + 10 :
|
|
*name >= 'A' && *name <= 'F' ? *name - 'A' + 10 : -1;
|
|
if (next < 0 || next >= base) break;
|
|
value = value * base + next;
|
|
digit = true;
|
|
++name;
|
|
}
|
|
if (*name != '.') break;
|
|
if (count == parts.size() || value > 255) return false;
|
|
parts[count++] = static_cast<unsigned char>(value);
|
|
++name;
|
|
}
|
|
if (!digit) return false;
|
|
if (*name != '\0' && *name != ' ' && *name != '\t' && *name != '\n' &&
|
|
*name != '\r' && *name != '\f' && *name != '\v') return false;
|
|
if (count && value >= (std::uint64_t{1} << (8 * (4 - count)))) return false;
|
|
auto result = static_cast<std::uint32_t>(value);
|
|
for (std::size_t i = 0; i < count; ++i)
|
|
result |= static_cast<std::uint32_t>(parts[i]) << (24 - 8 * i);
|
|
for (std::size_t i = 0; i < address.size(); ++i)
|
|
address[i] = static_cast<unsigned char>(result >> (24 - 8 * i));
|
|
return true;
|
|
}
|
|
|
|
bool Ready() {
|
|
#ifdef _WIN32
|
|
static const int status = [] { WSADATA data{}; return WSAStartup(MAKEWORD(2, 2), &data); }();
|
|
return status == 0;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
int GuestError(int error) {
|
|
if (error == 0) return 0;
|
|
if (error == EAI_AGAIN) return 2;
|
|
if (error == EAI_BADFLAGS) return 3;
|
|
if (error == EAI_FAMILY) return 5;
|
|
if (error == EAI_MEMORY) return 6;
|
|
if (error == EAI_NONAME) return 8;
|
|
if (error == EAI_SERVICE) return 9;
|
|
if (error == EAI_SOCKTYPE) return 10;
|
|
#ifdef _WIN32
|
|
if (error == WSAEFAULT) return 14;
|
|
#else
|
|
if (error == EAI_OVERFLOW) return 14;
|
|
#endif
|
|
return 4;
|
|
}
|
|
}
|
|
|
|
extern "C" {
|
|
GuestResolver::HostEntry* APS5_VABI gethostbyname_nid_postfix(const char* name) {
|
|
if (!name || !Ready()) { *sceNetErrnoLoc() = 3; return nullptr; }
|
|
try {
|
|
std::array<unsigned char, 4> numericAddress{};
|
|
char* numericAliases[]{nullptr};
|
|
char* numericAddresses[]{reinterpret_cast<char*>(numericAddress.data()), nullptr};
|
|
hostent nativeEntry{const_cast<char*>(name), numericAliases, AF_INET, 4, numericAddresses};
|
|
hostent* native = &nativeEntry;
|
|
const bool numeric = NumericAddress(name, numericAddress);
|
|
#ifdef _WIN32
|
|
if (!numeric) native = ::gethostbyname(name);
|
|
if (!native) {
|
|
const auto error = WSAGetLastError();
|
|
*sceNetErrnoLoc() = error == WSAHOST_NOT_FOUND ? 1 : error == WSATRY_AGAIN ? 2 : error == WSANO_DATA ? 4 : 3;
|
|
return nullptr;
|
|
}
|
|
#else
|
|
std::vector<char> buffer;
|
|
int hostError = 0;
|
|
int error = 0;
|
|
if (!numeric) {
|
|
buffer.resize(1024);
|
|
while ((error = ::gethostbyname_r(name, &nativeEntry, buffer.data(), buffer.size(), &native, &hostError)) == ERANGE)
|
|
buffer.resize(buffer.size() * 2);
|
|
}
|
|
if (error != 0 || !native) {
|
|
*sceNetErrnoLoc() = hostError != 0 ? hostError : 3;
|
|
if (hostError == -1) errno = error == EAGAIN ? 35 : error;
|
|
return nullptr;
|
|
}
|
|
#endif
|
|
struct LookupTag {};
|
|
auto& lookup = HostThreadLocal<HostLookup, LookupTag>();
|
|
lookup.name = native->h_name;
|
|
lookup.aliasNames.clear();
|
|
lookup.aliases.clear();
|
|
lookup.addressBytes.clear();
|
|
lookup.addresses.clear();
|
|
for (auto** alias = native->h_aliases; *alias; ++alias) lookup.aliasNames.emplace_back(*alias);
|
|
for (auto& alias : lookup.aliasNames) lookup.aliases.push_back(alias.data());
|
|
lookup.aliases.push_back(nullptr);
|
|
for (auto** address = native->h_addr_list; *address; ++address)
|
|
lookup.addressBytes.emplace_back(*address, native->h_length);
|
|
for (auto& address : lookup.addressBytes) lookup.addresses.push_back(address.data());
|
|
lookup.addresses.push_back(nullptr);
|
|
lookup.entry = {lookup.name.data(), lookup.aliases.data(), native->h_addrtype == AF_INET6 ? 28 : 2,
|
|
native->h_length, lookup.addresses.data()};
|
|
if (numeric) *sceNetErrnoLoc() = 0;
|
|
return &lookup.entry;
|
|
} catch (const std::bad_alloc&) {
|
|
*sceNetErrnoLoc() = -1;
|
|
errno = ENOMEM;
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
GuestResolver::ServiceEntry* APS5_VABI getservbyname_nid_postfix(const char* name, const char* protocol) {
|
|
for (const auto& service : Services) {
|
|
bool named = std::strcmp(name, service.name) == 0;
|
|
for (const char* alias : service.aliases)
|
|
named = named || (alias && std::strcmp(name, alias) == 0);
|
|
if (!named || (protocol && std::strcmp(protocol, service.protocol) != 0)) continue;
|
|
try {
|
|
static thread_local ServiceLookup lookup;
|
|
lookup.name = service.name;
|
|
lookup.protocol = service.protocol;
|
|
lookup.aliasNames.clear();
|
|
lookup.aliases.clear();
|
|
for (const char* alias : service.aliases)
|
|
if (alias) lookup.aliasNames.emplace_back(alias);
|
|
for (auto& alias : lookup.aliasNames) lookup.aliases.push_back(alias.data());
|
|
lookup.aliases.push_back(nullptr);
|
|
lookup.entry = {lookup.name.data(), lookup.aliases.data(), htons(service.port), lookup.protocol.data()};
|
|
return &lookup.entry;
|
|
} catch (const std::bad_alloc&) {
|
|
errno = ENOMEM;
|
|
return nullptr;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void APS5_VABI freeaddrinfo_nid_postfix(GuestResolver::AddressInfo* first) {
|
|
while (first) {
|
|
auto* next = first->next;
|
|
std::free(first->canonicalName);
|
|
std::free(first->address);
|
|
std::free(first);
|
|
first = next;
|
|
}
|
|
}
|
|
|
|
int APS5_VABI getaddrinfo_nid_postfix(const char* node, const char* service,
|
|
const GuestResolver::AddressInfo* hints, GuestResolver::AddressInfo** output) {
|
|
if (!output) return 4;
|
|
*output = nullptr;
|
|
addrinfo nativeHints{};
|
|
if (hints) {
|
|
if (hints->addressLength || hints->address || hints->canonicalName || hints->next) return 12;
|
|
if ((hints->flags & ~0xd0f) != 0) return 3;
|
|
if (hints->family != 0 && hints->family != 2 && hints->family != 28) return 5;
|
|
if (hints->socketType != 0 && hints->socketType != 1 && hints->socketType != 2) return 10;
|
|
nativeHints.ai_family = hints->family == 28 ? AF_INET6 : hints->family == 2 ? AF_INET : AF_UNSPEC;
|
|
nativeHints.ai_socktype = hints->socketType == 1 ? SOCK_STREAM : hints->socketType == 2 ? SOCK_DGRAM : 0;
|
|
nativeHints.ai_protocol = hints->protocol;
|
|
if (hints->flags & 1) nativeHints.ai_flags |= AI_PASSIVE;
|
|
if (hints->flags & 2) nativeHints.ai_flags |= AI_CANONNAME;
|
|
if (hints->flags & 4) nativeHints.ai_flags |= AI_NUMERICHOST;
|
|
if (hints->flags & 8) nativeHints.ai_flags |= AI_NUMERICSERV;
|
|
if (hints->flags & 0x100) nativeHints.ai_flags |= AI_ALL;
|
|
if (hints->flags & 0x400) nativeHints.ai_flags |= AI_ADDRCONFIG;
|
|
if (hints->flags & 0x800) nativeHints.ai_flags |= AI_V4MAPPED;
|
|
}
|
|
if (!Ready()) return 4;
|
|
addrinfo* native = nullptr;
|
|
const auto error = ::getaddrinfo(node, service, &nativeHints, &native);
|
|
if (error) return GuestError(error);
|
|
GuestResolver::AddressInfo* first = nullptr;
|
|
auto** tail = &first;
|
|
int failure = 0;
|
|
for (auto* item = native; item; item = item->ai_next) {
|
|
if (item->ai_family != AF_INET && item->ai_family != AF_INET6) { failure = 5; break; }
|
|
auto* converted = static_cast<GuestResolver::AddressInfo*>(std::calloc(1, sizeof(GuestResolver::AddressInfo)));
|
|
if (!converted) { failure = 6; break; }
|
|
*tail = converted;
|
|
tail = &converted->next;
|
|
converted->flags = hints ? hints->flags : 0;
|
|
converted->family = item->ai_family == AF_INET ? 2 : 28;
|
|
converted->socketType = item->ai_socktype == SOCK_STREAM ? 1 : item->ai_socktype == SOCK_DGRAM ? 2 : 0;
|
|
converted->protocol = item->ai_protocol;
|
|
converted->addressLength = item->ai_family == AF_INET ? 16 : 28;
|
|
auto* bytes = static_cast<unsigned char*>(std::calloc(1, converted->addressLength));
|
|
converted->address = bytes;
|
|
if (!bytes) { failure = 6; break; }
|
|
bytes[0] = static_cast<unsigned char>(converted->addressLength);
|
|
bytes[1] = static_cast<unsigned char>(converted->family);
|
|
if (item->ai_family == AF_INET) {
|
|
const auto* v4 = reinterpret_cast<const sockaddr_in*>(item->ai_addr);
|
|
std::memcpy(bytes + 2, &v4->sin_port, 2);
|
|
std::memcpy(bytes + 4, &v4->sin_addr, 4);
|
|
} else {
|
|
const auto* v6 = reinterpret_cast<const sockaddr_in6*>(item->ai_addr);
|
|
std::memcpy(bytes + 2, &v6->sin6_port, 2);
|
|
std::memcpy(bytes + 4, &v6->sin6_flowinfo, 4);
|
|
std::memcpy(bytes + 8, &v6->sin6_addr, 16);
|
|
std::memcpy(bytes + 24, &v6->sin6_scope_id, 4);
|
|
}
|
|
if (item->ai_canonname) {
|
|
const auto size = std::strlen(item->ai_canonname) + 1;
|
|
converted->canonicalName = static_cast<char*>(std::malloc(size));
|
|
if (!converted->canonicalName) { failure = 6; break; }
|
|
std::memcpy(converted->canonicalName, item->ai_canonname, size);
|
|
}
|
|
}
|
|
::freeaddrinfo(native);
|
|
if (failure) { freeaddrinfo_nid_postfix(first); return failure; }
|
|
*output = first;
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI getnameinfo_nid_postfix(const void* address, std::uint32_t length,
|
|
char* host, std::uint32_t hostLength, char* service, std::uint32_t serviceLength, int flags) {
|
|
// PS5 sockaddr begins with byte-sized length and family. The host's does not.
|
|
if (!address || length < 2) return 5;
|
|
if ((!host && hostLength) || (!service && serviceLength)) return 4;
|
|
if ((flags & ~0x1f) != 0) return 3;
|
|
const auto* bytes = static_cast<const unsigned char*>(address);
|
|
sockaddr_storage native{};
|
|
socklen_t nativeLength;
|
|
if (bytes[1] == 2) {
|
|
if (length < 16 || bytes[0] != 16) return 5;
|
|
auto& v4 = reinterpret_cast<sockaddr_in&>(native);
|
|
v4.sin_family = AF_INET;
|
|
std::memcpy(&v4.sin_port, bytes + 2, 2);
|
|
std::memcpy(&v4.sin_addr, bytes + 4, 4);
|
|
nativeLength = sizeof(v4);
|
|
} else if (bytes[1] == 28) {
|
|
if (length < 28 || bytes[0] != 28) return 5;
|
|
auto& v6 = reinterpret_cast<sockaddr_in6&>(native);
|
|
v6.sin6_family = AF_INET6;
|
|
std::memcpy(&v6.sin6_port, bytes + 2, 2);
|
|
std::memcpy(&v6.sin6_flowinfo, bytes + 4, 4);
|
|
std::memcpy(&v6.sin6_addr, bytes + 8, 16);
|
|
std::memcpy(&v6.sin6_scope_id, bytes + 24, 4);
|
|
nativeLength = sizeof(v6);
|
|
} else return 5;
|
|
int nativeFlags = 0;
|
|
if (flags & 1) nativeFlags |= NI_NOFQDN;
|
|
if (flags & 2) nativeFlags |= NI_NUMERICHOST;
|
|
if (flags & 4) nativeFlags |= NI_NAMEREQD;
|
|
if (flags & 8) nativeFlags |= NI_NUMERICSERV;
|
|
if (flags & 16) nativeFlags |= NI_DGRAM;
|
|
if (!Ready()) return 4;
|
|
// Stage results so host-specific short-buffer errors do not leak into the
|
|
// guest ABI, and neither guest output is partially modified on overflow.
|
|
char resolvedHost[NI_MAXHOST]{};
|
|
char resolvedService[NI_MAXSERV]{};
|
|
const bool wantHost = host && hostLength;
|
|
const bool wantService = service && serviceLength;
|
|
const int error = ::getnameinfo(reinterpret_cast<const sockaddr*>(&native), nativeLength,
|
|
wantHost ? resolvedHost : nullptr, wantHost ? sizeof(resolvedHost) : 0,
|
|
wantService ? resolvedService : nullptr, wantService ? sizeof(resolvedService) : 0, nativeFlags);
|
|
if (error) return GuestError(error);
|
|
if ((wantHost && std::strlen(resolvedHost) >= hostLength) ||
|
|
(wantService && std::strlen(resolvedService) >= serviceLength)) return 14;
|
|
if (wantHost) std::memcpy(host, resolvedHost, std::strlen(resolvedHost) + 1);
|
|
if (wantService) std::memcpy(service, resolvedService, std::strlen(resolvedService) + 1);
|
|
return 0;
|
|
}
|
|
|
|
const char* APS5_VABI gai_strerror_nid_postfix(int error) {
|
|
static const char* messages[] = {
|
|
"Success", "Address family for hostname not supported", "Temporary failure in name resolution",
|
|
"Invalid value for ai_flags", "Non-recoverable failure in name resolution", "ai_family not supported",
|
|
"Memory allocation failure", "No address associated with hostname", "hostname nor servname provided, or not known",
|
|
"servname not supported for ai_socktype", "ai_socktype not supported", "System error returned in errno",
|
|
"Invalid value for hints", "Resolved protocol is unknown", "Argument buffer overflow"
|
|
};
|
|
if (error < 0 || error >= static_cast<int>(sizeof(messages) / sizeof(messages[0])))
|
|
return "Unknown error";
|
|
return messages[error];
|
|
}
|
|
}
|