diff --git a/core/libs/prx/libScePosixForWebKit/CMakeLists.txt b/core/libs/prx/libScePosixForWebKit/CMakeLists.txt index fddddc543..036eb0577 100644 --- a/core/libs/prx/libScePosixForWebKit/CMakeLists.txt +++ b/core/libs/prx/libScePosixForWebKit/CMakeLists.txt @@ -36,4 +36,13 @@ if(BUILD_TESTING) set_tests_properties(guest_resolver PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_service_database_tests ${LIBS_INCLUDE_DIR}/tests/GuestServiceDatabase.cpp) + target_include_directories(guest_service_database_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_service_database_tests PRIVATE libScePosixForWebKit libc) + configure_windows_unwind(guest_service_database_tests) + add_test(NAME guest_service_database COMMAND guest_service_database_tests) + if(WIN32) + set_tests_properties(guest_service_database PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libScePosixForWebKit/GuestResolver.hpp b/core/libs/prx/libScePosixForWebKit/GuestResolver.hpp index 7ed54e716..5e6fc068c 100644 --- a/core/libs/prx/libScePosixForWebKit/GuestResolver.hpp +++ b/core/libs/prx/libScePosixForWebKit/GuestResolver.hpp @@ -28,4 +28,15 @@ struct AddressInfo { static_assert(sizeof(AddressInfo) == 48); static_assert(offsetof(AddressInfo, canonicalName) == 24); static_assert(offsetof(AddressInfo, address) == 32); + +struct ServiceEntry { + char* name; + char** aliases; + std::int32_t port; + char* protocol; +}; +static_assert(sizeof(ServiceEntry) == 32); +static_assert(offsetof(ServiceEntry, aliases) == 8); +static_assert(offsetof(ServiceEntry, port) == 16); +static_assert(offsetof(ServiceEntry, protocol) == 24); } diff --git a/core/libs/prx/libScePosixForWebKit/Resolver.cpp b/core/libs/prx/libScePosixForWebKit/Resolver.cpp index 6441693f4..9333e9c7e 100644 --- a/core/libs/prx/libScePosixForWebKit/Resolver.cpp +++ b/core/libs/prx/libScePosixForWebKit/Resolver.cpp @@ -31,6 +31,41 @@ struct HostLookup { std::vector addresses; }; +struct ServiceRecord { + const char* name; + std::uint16_t port; + const char* protocol; + std::array 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 aliasNames; + std::vector aliases; +}; + bool NumericAddress(const char* name, std::array& address) { std::array parts{}; std::size_t count = 0; @@ -155,6 +190,32 @@ GuestResolver::HostEntry* APS5_VABI gethostbyname_nid_postfix(const char* name) } } +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; diff --git a/core/libs/tests/GuestServiceDatabase.cpp b/core/libs/tests/GuestServiceDatabase.cpp new file mode 100644 index 000000000..30caec909 --- /dev/null +++ b/core/libs/tests/GuestServiceDatabase.cpp @@ -0,0 +1,62 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libScePosixForWebKit/GuestResolver.hpp" +#include +#include +#include +#include +#include +#include +#include + +extern "C" GuestResolver::ServiceEntry* APS5_VABI getservbyname_nid_postfix(const char*, const char*); + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (!condition) { + std::fprintf(stderr, "Service database check failed at line %u\n", location.line()); + std::abort(); + } +} + +bool NetworkPort(const GuestResolver::ServiceEntry* entry, std::uint16_t port) { + unsigned char bytes[4]; + std::memcpy(bytes, &entry->port, sizeof(bytes)); + return bytes[0] == port >> 8 && bytes[1] == (port & 0xff) && bytes[2] == 0 && bytes[3] == 0; +} + +int main() { + auto* http = getservbyname_nid_postfix("http", "tcp"); + Require(http && std::strcmp(http->name, "http") == 0 && std::strcmp(http->protocol, "tcp") == 0); + Require(NetworkPort(http, 80)); + Require(http->aliases && std::strcmp(http->aliases[0], "www") == 0); + Require(std::strcmp(http->aliases[1], "www-http") == 0 && http->aliases[2] == nullptr); + + auto* https = getservbyname_nid_postfix("https", nullptr); + Require(https && std::strcmp(https->name, "https") == 0 && std::strcmp(https->protocol, "tcp") == 0); + Require(NetworkPort(https, 443) && https->aliases && https->aliases[0] == nullptr); + + auto* domain = getservbyname_nid_postfix("domain", "udp"); + Require(domain && std::strcmp(domain->name, "domain") == 0 && std::strcmp(domain->protocol, "udp") == 0); + Require(NetworkPort(domain, 53)); + + auto* www = getservbyname_nid_postfix("www", "sctp"); + Require(www && std::strcmp(www->name, "http") == 0 && std::strcmp(www->protocol, "sctp") == 0); + Require(NetworkPort(www, 80)); + + auto* mail = getservbyname_nid_postfix("mail", nullptr); + Require(mail && std::strcmp(mail->name, "smtp") == 0 && NetworkPort(mail, 25)); + + errno = 0; + Require(getservbyname_nid_postfix("no-such-service", nullptr) == nullptr && errno == 0); + Require(getservbyname_nid_postfix("domain", "sctp") == nullptr); + Require(getservbyname_nid_postfix("ntp", "TCP") == nullptr); + Require(getservbyname_nid_postfix("HTTP", "tcp") == nullptr); + + auto* ntp = getservbyname_nid_postfix("ntp", "udp"); + Require(ntp && NetworkPort(ntp, 123)); + std::thread([] { + auto* ssh = getservbyname_nid_postfix("ssh", "tcp"); + Require(ssh && std::strcmp(ssh->name, "ssh") == 0 && NetworkPort(ssh, 22)); + }).join(); + Require(std::strcmp(ntp->name, "ntp") == 0 && std::strcmp(ntp->protocol, "udp") == 0 && NetworkPort(ntp, 123)); + return 0; +} diff --git a/docs/dev/debt/libScePosixForWebKit.md b/docs/dev/debt/libScePosixForWebKit.md index cf5516d57..c57da143e 100644 --- a/docs/dev/debt/libScePosixForWebKit.md +++ b/docs/dev/debt/libScePosixForWebKit.md @@ -9,3 +9,4 @@ - [mkstemp and isatty](../../../core/libs/prx/libScePosixForWebKit/Filesystem.cpp) (libScePosixForWebKit) follow FreeBSD's [temporary-file API](https://github.com/freebsd/freebsd-src/blob/stable/9/lib/libc/stdio/mktemp.c) and [terminal check](https://github.com/freebsd/freebsd-src/blob/stable/9/lib/libc/gen/isatty.c), without console verification. `mkstemp` replaces all trailing `X` characters, accepts a fixed name, and creates files through the guest path resolver with exclusive read/write access and mode 0600. Windows file permissions follow the existing kernel file implementation. `isatty` tests Windows console handles rather than all character devices; files, pipes and guest sockets return `ENOTTY`, closed descriptors return `EBADF`. - [arc4random](../../../core/libs/prx/libScePosixForWebKit/Export.cpp) (libScePosixForWebKit) returns four bytes of host OS entropy (`getentropy`, `rand_s` on Windows), as `arc4random_buf` does, uniform over the 32-bit range like FreeBSD 11.4's RC4 keystream in [arc4random.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/arc4random.c). The console's generator and its reseeding are unknown; `arc4random_uniform`, `arc4random_stir` and `arc4random_addrandom` are not in the module's export set and are absent - [fnmatch](../../../core/libs/prx/libScePosixForWebKit/Export.cpp) (libScePosixForWebKit) follows FreeBSD 11 [fnmatch.c](https://github.com/freebsd/freebsd-src/blob/stable/11/lib/libc/gen/fnmatch.c) in the C locale: each byte is one character, `FNM_CASEFOLD` folds ASCII letters only, and bracket ranges compare byte values. `[:class:]` names are not character classes: as in FreeBSD 11 through 14, `[[:digit:]]` is the set of `[`, `:`, `d`, `i`, `g`, `t` followed by a literal `]` (classes were added to `fnmatch.c` after 14). Multibyte characters and locale collation are not modelled. A null pattern or string throws +- [getservbyname](../../../core/libs/prx/libScePosixForWebKit/Resolver.cpp) (libScePosixForWebKit) searches a built-in copy of 37 lines of FreeBSD 11.4's [etc/services](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/etc/services) (ftp-data, ftp, ssh, telnet, smtp, domain, tftp, http, pop3, ntp, imap, https, rtsp, submission, imaps, pop3s with their aliases and protocols, in file order) the way [getservent.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/net/getservent.c) searches the file: case-sensitive name or alias, a null protocol takes the first match, the port in network byte order, the result in per-thread storage. The console's `/etc/services` is unknown, so any other service returns null without setting `errno`, as for a name the file lacks. `getaddrinfo` still resolves service names through the host's service database