diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index afc7c1e..1330a40 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -293,6 +293,31 @@ if (WIN32) endif() # OpenSSL for TLS (libevent bufferevent_openssl, libmysqlclient). +# +# Linked statically on purpose: the released shared library must not carry a runtime +# dependency on a particular OpenSSL installation. With the shared libs, the macOS +# dylib gets an absolute path baked in (/opt/homebrew/opt/openssl@N/lib/libssl.N.dylib) +# and fails to load on any machine that does not have that exact Homebrew prefix; the +# Windows DLL likewise dragged in libssl-3-x64.dll / libcrypto-3-x64.dll. +option(FAKELUA_PREFER_STATIC_DEPS "Prefer static third-party libraries where possible" ON) +if (FAKELUA_PREFER_STATIC_DEPS AND NOT MINGW) + set(OPENSSL_USE_STATIC_LIBS TRUE) + if (WIN32) + # Match the static CRT choice when consuming MSVC-built OpenSSL. + set(OPENSSL_MSVC_STATIC_RT ON) + endif () +endif () +# MinGW keeps the *shared* OpenSSL on purpose (libcrypto-3-x64.dll / libssl-3-x64.dll). +# +# RC4 / Blowfish / DES live in OpenSSL 3's "legacy" provider, which is a loadable module +# (ossl-modules/legacy.dll), not part of libcrypto itself. A static libcrypto can only find +# that module through the MODULESDIR compiled into it, and MSYS2 builds OpenSSL with a MSYS +# path there ("/mingw64/lib/ossl-modules") that Win32 LoadLibrary cannot resolve. Statically +# linking OpenSSL on MinGW therefore makes OSSL_PROVIDER_load("legacy") fail silently and +# breaks crypto.rc4 / blowfish / des (verified: BuildWithWindows, 8 test_crypto failures). +# The shared libcrypto derives its module directory from the DLL location at runtime, so it +# finds legacy.dll. Ship those two DLLs next to libfakelua.dll in the release archive. + if (WIN32 AND NOT OPENSSL_ROOT_DIR) get_filename_component(_fakelua_mingw_bin "${CMAKE_CXX_COMPILER}" DIRECTORY) get_filename_component(_fakelua_mingw_root "${_fakelua_mingw_bin}" DIRECTORY) @@ -305,6 +330,24 @@ endif() find_package(OpenSSL REQUIRED) find_package(Threads REQUIRED) +if (OPENSSL_USE_STATIC_LIBS) + # Guard against silently resolving to the shared libs (e.g. a Homebrew keg installed + # without the .a files), which would reintroduce the hardcoded runtime path. + # ".dll.a" covers the MinGW/MSVC import libraries of the OpenSSL DLLs. + set(_fl_openssl_shared "") + foreach (_fl_ssl_lib OpenSSL::SSL OpenSSL::Crypto) + get_target_property(_fl_lib_loc ${_fl_ssl_lib} IMPORTED_LOCATION) + if (_fl_lib_loc MATCHES "\\.(so|dylib|dll\\.a)(\\..*)?$") + list(APPEND _fl_openssl_shared "${_fl_ssl_lib}=${_fl_lib_loc}") + endif () + endforeach () + if (_fl_openssl_shared) + message(WARNING + "FAKELUA_PREFER_STATIC_DEPS is ON but OpenSSL resolved to non-static libs: " + "${_fl_openssl_shared}. The shipped library will depend on OpenSSL at runtime.") + endif () +endif () + # libevent: per-State event_base, driven by runtime.tick() via EVLOOP_NONBLOCK. CPMAddPackage( NAME libevent @@ -499,6 +542,20 @@ endif() target_link_libraries(fakelua PRIVATE ${TARGET_LIB}) +# MinGW runtime: drop libgcc_s_seh-1.dll and libwinpthread-1.dll from the shipped DLL. +# +# libstdc++ is deliberately NOT statically linked. include/fakelua.h exposes STL types +# across the ABI boundary (std::string returned by value, std::vector>), +# and ViToString() is implemented inline in that header — so host and library must share +# one copy of libstdc++. Statically linking it would give each side its own copy and +# corrupt the heap whenever a std::string crosses the boundary. +# libgcc / winpthread are pure C-level runtimes with no such ABI surface, so they are safe. +if (FAKELUA_PREFER_STATIC_DEPS AND MINGW) + target_link_options(fakelua PRIVATE + -static-libgcc + -Wl,-Bstatic -lwinpthread -Wl,-Bdynamic) +endif () + target_include_directories(fakelua PUBLIC $ $ diff --git a/src/native/crypto/native_crypto.cpp b/src/native/crypto/native_crypto.cpp index 277baa6..9794f1b 100644 --- a/src/native/crypto/native_crypto.cpp +++ b/src/native/crypto/native_crypto.cpp @@ -532,13 +532,20 @@ void RegisterCryptoLibraryApi(State *s) { // On OpenSSL 3.x, RC4 / Blowfish / DES are in the "legacy" provider which // is not loaded by default. Load it once here so callers don't have to - // configure openssl.cnf manually. Failure is silently ignored: if the - // legacy provider is already loaded (via config), or if this OpenSSL build - // doesn't have it, the rest of the API still works. + // configure openssl.cnf manually. Failure only degrades the legacy ciphers: + // the rest of the API still works, so warn instead of throwing. static std::once_flag legacy_provider_init; - std::call_once(legacy_provider_init, []() { + std::call_once(legacy_provider_init, [s]() { #if OPENSSL_VERSION_MAJOR >= 3 - OSSL_PROVIDER_load(nullptr, "legacy"); + if (OSSL_PROVIDER_load(nullptr, "legacy") == nullptr) { + // Most common cause: libcrypto cannot find its provider modules + // (ossl-modules/legacy), e.g. a static OpenSSL whose MODULESDIR is wrong. + LOG_WARN(s, + "crypto", + "failed to load the OpenSSL \"legacy\" provider: crypto.rc4 / blowfish / des " + "and the matching crypto.encrypt/decrypt algorithms will fail. " + "check OPENSSL_MODULES or the OpenSSL modules directory"); + } OSSL_PROVIDER_load(nullptr, "default"); #endif });