Code View

plugin / plugin-2.2.0.0 / src / dso / DSOLoader.cpp
// SPDX-License-Identifier: MIT
#include "DSOLoader.hh"
#include "DSOExceptions.hh"

#if defined(DSOLD_WITH_SIGN)
#  include <sign/Verifier.hh>
#endif

#include <dlfcn.h>

#include <cstdlib>
#include <filesystem>
#include <set>
#include <sstream>
#include <string>
#include <vector>

#ifdef __CYGWIN__
#define PATH_SEPARATOR ';'
#else
#define PATH_SEPARATOR ':'
#endif

#if defined( hpux ) || defined( __hpux ) || defined( __hpux__ )
#define LIBPATH "SHLIB"
#elif defined( _AIX )
#define LIBPATH "LIBPATH"
#elif defined( __CYGWIN__ )
#define LIBPATH "PATH"
#else
#define LIBPATH "LD_LIBRARY_PATH"
#endif

// ---------------------------------------------------------------------------
// DSO handle RAII guard (anonymous namespace, file-local)
// ---------------------------------------------------------------------------
namespace
{
  class HandleGuard final
  {
    public:
      explicit HandleGuard( void* h ) noexcept : handle( h ) {}
      HandleGuard( HandleGuard&& o ) noexcept : handle( o.handle ) { o.handle = nullptr; }
      ~HandleGuard( ) noexcept { if( handle ) dlclose( handle ); }
      HandleGuard( HandleGuard const& )            = delete;
      HandleGuard& operator=( HandleGuard const& ) = delete;
      HandleGuard& operator=( HandleGuard&& )      = delete;
      bool operator<( HandleGuard const& o ) const noexcept { return handle < o.handle; }
      void* handle;
  };

  std::set< HandleGuard > dsoHandles;
}  // namespace

// ---------------------------------------------------------------------------
namespace fedem::dso
{
  std::string DSOLoader::filenamePrefix;
  std::mutex  DSOLoader::mutex_;

  // =========================================================================
  // Private helpers — caller must hold mutex_
  // =========================================================================

  // Returns the resolved path that was successfully loaded.
  std::string DSOLoader::loadSingle( std::string const& soName )
  {
    void* dlib = dlopen( soName.c_str( ), RTLD_GLOBAL | RTLD_LAZY );
    if( dlib == nullptr )
    {
      char const* err = dlerror( );
      throw exception::FileNotFound( soName, err ? err : "" );
    }
    dsoHandles.emplace( dlib );
    return soName;
  }

  void DSOLoader::scanDirectory( std::string const& ext,
                                 std::string const& folder,
                                 std::string& missingFiles )
  {
    namespace fs = std::filesystem;
    fs::path dirPath = folder.empty( ) ? fs::current_path( ) : fs::path( folder );
    std::error_code ec;
    if( ! fs::is_directory( dirPath, ec ) ) return;

    for( auto const& entry : fs::directory_iterator( dirPath, ec ) )
    {
      if( ec ) break;
      if( ! entry.is_regular_file( ec ) && ! entry.is_symlink( ec ) ) continue;

      std::string const fname = entry.path( ).filename( ).string( );
      bool match = false;
      if( filenamePrefix.empty( ) )
        match = fname.size() >= ext.size()
             && fname.compare( fname.size()-ext.size(), ext.size(), ext ) == 0;
      else
        match = fname.size() >= (filenamePrefix.size()+ext.size())
             && fname.compare(0, filenamePrefix.size(), filenamePrefix) == 0
             && fname.compare(fname.size()-ext.size(), ext.size(), ext) == 0;

      if( match )
      {
        try { loadSingle( entry.path().string() ); }
        catch( exception::FileNotFound& ex )
        {
          if( !ex.missingFilename().empty() )
          {
            if( !missingFiles.empty() ) missingFiles += ", ";
            missingFiles += ex.missingFilename() + "\n    -- " + ex.whatStr();
          }
        }
      }
    }
  }

  // =========================================================================
  // Public API — original (no signature check)
  // =========================================================================

  void DSOLoader::load( std::string const& soName )
  {
    std::lock_guard<std::mutex> lk( mutex_ );
    loadSingle( soName );
  }

  void DSOLoader::load( std::string const& soName,
                        std::vector<std::string> const& list )
  {
    std::lock_guard<std::mutex> lk( mutex_ );
    std::ostringstream errLog( std::ios_base::ate );
    bool found = false;

    for( auto const& dir : list )
    {
      namespace fs = std::filesystem;
      fs::path soPath = dir.empty() ? fs::path(soName) : fs::path(dir) / soName;
      try { loadSingle( soPath.string() ); found = true; break; }
      catch( exception::FileNotFound& ex ) { errLog << ex.whatStr(); }
    }
    if( !found ) throw exception::FileNotFound( soName, errLog.str() );
  }

  void DSOLoader::loadAll( std::string const& ext, std::string const& folder )
  {
    std::lock_guard<std::mutex> lk( mutex_ );
    std::string miss;
    scanDirectory( ext, folder, miss );
    if( !miss.empty() ) throw exception::FileNotFound( miss, "DSO load error" );
  }

  void DSOLoader::loadAll( std::string const& ext )
  {
    std::lock_guard<std::mutex> lk( mutex_ );
    std::string miss;
    scanDirectory( ext, "", miss );
    if( !miss.empty() ) throw exception::FileNotFound( miss, "DSO load error" );
  }

  void DSOLoader::loadAll( std::string const& ext,
                           std::vector<std::string> const& list )
  {
    std::lock_guard<std::mutex> lk( mutex_ );
    std::string miss;
    for( auto const& dir : list ) scanDirectory( ext, dir, miss );
    if( !miss.empty() ) throw exception::FileNotFound( miss, "DSO load error" );
  }

  void DSOLoader::loadAllByEnvironment( std::string const& ext,
                                        std::string const& env )
  {
    std::lock_guard<std::mutex> lk( mutex_ );
    char const* envVal = getenv( env.c_str() );
    std::string miss;
    if( envVal )
    {
      std::istringstream stream( envVal );
      std::string token;
      while( std::getline( stream, token, PATH_SEPARATOR ) )
        scanDirectory( ext, token, miss );
    }
    else scanDirectory( ext, "", miss );
    if( !miss.empty() ) throw exception::FileNotFound( miss, "DSO load error" );
  }

  void DSOLoader::loadAllByEnvironment( std::string const& ext )
  {
    loadAllByEnvironment( ext, LIBPATH );
  }

  void DSOLoader::prefix( std::string const& pre )
  {
    std::lock_guard<std::mutex> lk( mutex_ );
    filenamePrefix = pre;
  }

  std::string DSOLoader::prefix( )
  {
    std::lock_guard<std::mutex> lk( mutex_ );
    return filenamePrefix;
  }

#if defined(DSOLD_WITH_SIGN)
  // =========================================================================
  // Signed loading API — compiled only when plugin-sign + OpenSSL available
  // =========================================================================

  SignedLoadResult DSOLoader::loadSigned(
      std::string const&              soName,
      std::vector<std::string> const& searchDirs,
      std::string const&              trustedKeysDir )
  {
    SignedLoadResult result;
    namespace fs = std::filesystem;

    // Resolve path and load
    std::string resolvedPath;
    {
        std::lock_guard<std::mutex> lk( mutex_ );
        std::ostringstream errLog;
        for( auto const& dir : searchDirs )
        {
            fs::path soPath = dir.empty() ? fs::path(soName) : fs::path(dir) / soName;
            if( !fs::exists( soPath ) ) continue;
            try
            {
                resolvedPath = loadSingle( soPath.string() );
                result.loaded  = true;
                result.soPath  = resolvedPath;
                break;
            }
            catch( exception::FileNotFound& ex ) { errLog << ex.whatStr(); }
        }
        if( !result.loaded )
        {
            result.detail = errLog.str();
            result.trust  = sign::TrustLevel::UNSIGNED;
            return result;
        }
    }

    // Verify (outside mutex — may take time, uses its own cache)
    sign::VerifyResult vr = sign::Verifier::verify( resolvedPath, trustedKeysDir );
    result.trust  = vr.trust;
    result.detail = vr.detail;
    return result;
  }

  void DSOLoader::loadVerified(
      std::string const&              soName,
      std::vector<std::string> const& searchDirs,
      std::string const&              trustedKeysDir,
      sign::TrustLevel                minTrust )
  {
    SignedLoadResult r = loadSigned( soName, searchDirs, trustedKeysDir );

    if( !r.loaded )
        throw exception::FileNotFound( soName, r.detail );

    // Check trust level meets minimum
    // Ordering: TRUSTED > UNKNOWN > UNSIGNED > REJECTED
    auto rank = []( sign::TrustLevel t ) -> int {
        switch( t ) {
            case sign::TrustLevel::TRUSTED:  return 3;
            case sign::TrustLevel::UNKNOWN:  return 2;
            case sign::TrustLevel::UNSIGNED: return 1;
            case sign::TrustLevel::REJECTED: return 0;
        }
        return 0;
    };

    if( rank( r.trust ) < rank( minTrust ) )
        throw exception::SignatureRejected( r.soPath, r.detail );
  }

#endif // DSOLD_WITH_SIGN

}  // namespace fedem::dso