Imported Upstream version 17.14.1
[platform/upstream/libzypp.git] / zypp / target / rpm / RpmDb.cc
index 2c2839d..7203d79 100644 (file)
@@ -32,6 +32,7 @@ extern "C"
 #include "zypp/base/Logger.h"
 #include "zypp/base/String.h"
 #include "zypp/base/Gettext.h"
+#include "zypp/base/LocaleGuard.h"
 
 #include "zypp/Date.h"
 #include "zypp/Pathname.h"
@@ -58,6 +59,9 @@ using namespace zypp::filesystem;
 
 #define WORKAROUNDRPMPWDBUG
 
+#undef ZYPP_BASE_LOGGER_LOGGROUP
+#define ZYPP_BASE_LOGGER_LOGGROUP "librpmDb"
+
 namespace zypp
 {
   namespace zypp_readonly_hack
@@ -123,31 +127,14 @@ struct KeyRingSignalReceiver : callback::ReceiveReport<KeyRingSignals>
 
   virtual void trustedKeyAdded( const PublicKey &key )
   {
-    MIL << "trusted key added to zypp Keyring. Importing" << endl;
-    // now import the key in rpm
-    try
-    {
-      _rpmdb.importPubkey( key );
-    }
-    catch (RpmException &e)
-    {
-      ERR << "Could not import key " << key.id() << " (" << key.name() << " from " << key.path() << " in rpm database" << endl;
-    }
+    MIL << "trusted key added to zypp Keyring. Importing..." << endl;
+    _rpmdb.importPubkey( key );
   }
 
   virtual void trustedKeyRemoved( const PublicKey &key  )
   {
     MIL << "Trusted key removed from zypp Keyring. Removing..." << endl;
-
-    // remove the key from rpm
-    try
-    {
-      _rpmdb.removePubkey( key );
-    }
-    catch (RpmException &e)
-    {
-      ERR << "Could not remove key " << key.id() << " (" << key.name() << ") from rpm database" << endl;
-    }
+    _rpmdb.removePubkey( key );
   }
 
   RpmDb &_rpmdb;
@@ -347,6 +334,14 @@ void RpmDb::initDatabase( Pathname root_r, Pathname dbPath_r, bool doRebuild_r )
     ZYPP_THROW(RpmInvalidRootException(root_r, dbPath_r));
   }
 
+  if ( dbPath_r == "/var/lib/rpm"
+    && ! PathInfo( root_r/"/var/lib/rpm" ).isExist()
+    && PathInfo( root_r/"/usr/lib/sysimage/rpm" ).isDir() )
+  {
+    WAR << "Rpm package was deleted? Injecting missing rpmdb compat symlink." << endl;
+    filesystem::symlink( "../../usr/lib/sysimage/rpm", root_r/"/var/lib/rpm" );
+  }
+
   MIL << "Calling initDatabase: " << stringPath( root_r, dbPath_r )
       << ( doRebuild_r ? " (rebuilddb)" : "" )
       << ( quickinit ? " (quickinit)" : "" ) << endl;
@@ -882,7 +877,7 @@ namespace
          _inRpmKeys  = &rpmKey_r;
          _inZyppKeys = nullptr;
          if ( !keyRelease.empty() )
-           DBG << "Old key in R: gpg-pubkey-" << rpmKey_r.version() << "-" <<  keyRelease << endl;
+           DBG << "Old key in Z: gpg-pubkey-" << rpmKey_r.version() << "-" <<  keyRelease << endl;
        }
        else if ( comp == 0 )
        {
@@ -906,7 +901,7 @@ namespace
          _inRpmKeys  = nullptr;
          _inZyppKeys = &zyppKey_r;
          if ( !keyRelease.empty() )
-           DBG << "Old key in Z: gpg-pubkey-" << zyppKey_r.gpgPubkeyVersion() << "-" << keyRelease << endl;
+           DBG << "Old key in R: gpg-pubkey-" << zyppKey_r.gpgPubkeyVersion() << "-" << keyRelease << endl;
        }
        else if ( comp == 0 )
        {
@@ -966,6 +961,34 @@ void RpmDb::syncTrustedKeys( SyncTrustedKeyBits mode_r )
   MIL << "Going to sync trusted keys..." << endl;
   std::set<Edition> rpmKeys( pubkeyEditions() );
   std::list<PublicKeyData> zyppKeys( getZYpp()->keyRing()->trustedPublicKeyData() );
+
+  if ( ! ( mode_r & SYNC_FROM_KEYRING ) )
+  {
+    // bsc#1064380: We relief PK from removing excess keys in the zypp keyring
+    // when re-acquiring the zyppp lock. For now we remove all excess keys.
+    // TODO: Once we can safely assume that all PK versions are updated we
+    // can think about re-importing newer key versions found in the zypp keyring and
+    // removing only excess ones (but case is not very likely). Unfixed PK versions
+    // however will remove the newer version found in the zypp keyring and by doing
+    // this, the key here will be removed via callback as well (keys are deleted
+    // via gpg id, regardless of the edition).
+    MIL << "Removing excess keys in zypp trusted keyring" << std::endl;
+    // Temporarily disconnect to prevent the attempt to pass back the delete request.
+    callback::TempConnect<KeyRingSignals> tempDisconnect;
+    bool dirty = false;
+    for ( const PublicKeyData & keyData : zyppKeys )
+    {
+      if ( ! rpmKeys.count( keyData.gpgPubkeyEdition() ) )
+      {
+       DBG << "Excess key in Z to delete: gpg-pubkey-" << keyData.gpgPubkeyEdition() << endl;
+       getZYpp()->keyRing()->deleteKey( keyData.id(), /*trusted*/true );
+       if ( !dirty ) dirty = true;
+      }
+    }
+    if ( dirty )
+      zyppKeys = getZYpp()->keyRing()->trustedPublicKeyData();
+  }
+
   computeKeyRingSync( rpmKeys, zyppKeys );
   MIL << (mode_r & SYNC_TO_KEYRING   ? "" : "(skip) ") << "Rpm keys to export into zypp trusted keyring: " << rpmKeys.size() << endl;
   MIL << (mode_r & SYNC_FROM_KEYRING ? "" : "(skip) ") << "Zypp trusted keys to import into rpm database: " << zyppKeys.size() << endl;
@@ -975,7 +998,7 @@ void RpmDb::syncTrustedKeys( SyncTrustedKeyBits mode_r )
   {
     // export to zypp keyring
     MIL << "Exporting rpm keyring into zypp trusted keyring" <<endl;
-    // Temporarily disconnect to prevent the attemt to re-import the exported keys.
+    // Temporarily disconnect to prevent the attempt to re-import the exported keys.
     callback::TempConnect<KeyRingSignals> tempDisconnect;
     librpmDb::db_const_iterator keepDbOpen; // just to keep a ref.
 
@@ -993,10 +1016,24 @@ void RpmDb::syncTrustedKeys( SyncTrustedKeyBits mode_r )
     try
     {
       getZYpp()->keyRing()->multiKeyImport( tmpfile.path(), true /*trusted*/);
+      // bsc#1096217: Try to spot and report legacy V3 keys found in the rpm database.
+      // Modern rpm does not import those keys, but when migrating a pre SLE12 system
+      // we may find them. rpm>4.13 even complains on sderr if sucha key is present.
+      std::set<Edition> missingKeys;
+      for ( const Edition & key : rpmKeys )
+      {
+       if ( getZYpp()->keyRing()->isKeyTrusted( key.version() ) ) // key.version is the gpgkeys short ID
+         continue;
+       ERR << "Could not import key:" << str::Format("gpg-pubkey-%s") % key << " into zypp keyring (V3 key?)" << endl;
+       missingKeys.insert( key );
+      }
+      if ( ! missingKeys.empty() )
+        callback::SendReport<KeyRingReport>()->reportNonImportedKeys(missingKeys);
     }
-    catch (Exception &e)
+    catch ( const Exception & excpt )
     {
-      ERR << "Could not import keys into in zypp keyring" << endl;
+      ZYPP_CAUGHT( excpt );
+      ERR << "Could not import keys into zypp keyring: " << endl;
     }
   }
 
@@ -1050,7 +1087,12 @@ void RpmDb::importPubkey( const PublicKey & pubkey_r )
 
   for_( it, rpmKeys.begin(), rpmKeys.end() )
   {
-    if ( keyEd == *it ) // quick test (Edition is IdStringType!)
+    // bsc#1008325: Keys using subkeys for signing don't get a higher release
+    // if new subkeys are added, because the primary key remains unchanged.
+    // For now always re-import keys with subkeys. Here we don't want to export the
+    // keys in the rpm database to check whether the subkeys are the same. The calling
+    // code should take care, we don't re-import the same kesy over and over again.
+    if ( keyEd == *it && !pubkey_r.hasSubkeys() ) // quick test (Edition is IdStringType!)
     {
       MIL << "Key " << pubkey_r << " is already in the rpm trusted keyring. (skip import)" << endl;
       return;
@@ -1113,17 +1155,25 @@ void RpmDb::importPubkey( const PublicKey & pubkey_r )
   run_rpm( opts, ExternalProgram::Stderr_To_Stdout );
 
   std::string line;
+  std::vector<std::string> excplines;
   while ( systemReadLine( line ) )
   {
-    ( str::startsWith( line, "error:" ) ? WAR : DBG ) << line << endl;
+    if ( str::startsWith( line, "error:" ) )
+    {
+      WAR << line << endl;
+      excplines.push_back( std::move(line) );
+    }
+    else
+      DBG << line << endl;
   }
 
   if ( systemStatus() != 0 )
   {
-    //TranslatorExplanation first %s is file name, second is error message
-    ZYPP_THROW(RpmSubprocessException( str::Format(_("Failed to import public key from file %s: %s"))
-                                      % pubkey_r.asString()
-                                      % error_message ));
+    // Translator: %1% is a gpg public key
+    RpmSubprocessException excp( str::Format(_("Failed to import public key %1%") ) % pubkey_r.asString() );
+    excp.moveToHistory( excplines );
+    excp.addHistory( std::move(error_message) );
+    ZYPP_THROW( std::move(excp) );
   }
   else
   {
@@ -1175,26 +1225,25 @@ void RpmDb::removePubkey( const PublicKey & pubkey_r )
   run_rpm( opts, ExternalProgram::Stderr_To_Stdout );
 
   std::string line;
+  std::vector<std::string> excplines;
   while ( systemReadLine( line ) )
   {
-    if ( line.substr( 0, 6 ) == "error:" )
+    if ( str::startsWith( line, "error:" ) )
     {
       WAR << line << endl;
+      excplines.push_back( std::move(line) );
     }
     else
-    {
       DBG << line << endl;
-    }
   }
 
-  int rpm_status = systemStatus();
-
-  if ( rpm_status != 0 )
+  if ( systemStatus() != 0 )
   {
-    //TranslatorExplanation first %s is key name, second is error message
-    ZYPP_THROW(RpmSubprocessException( str::Format(_("Failed to remove public key %s: %s"))
-                                      % pubkey_r.asString()
-                                      % error_message ));
+    // Translator: %1% is a gpg public key
+    RpmSubprocessException excp( str::Format(_("Failed to remove public key %1%") ) % pubkey_r.asString() );
+    excp.moveToHistory( excplines );
+    excp.addHistory( std::move(error_message) );
+    ZYPP_THROW( std::move(excp) );
   }
   else
   {
@@ -1472,7 +1521,7 @@ namespace
 
       int rpmLog( rpmlogRec rec_r )
       {
-       if ( _cap ) (*_cap) = rpmlogRecMessage( rec_r );
+       if ( _cap ) (*_cap) += rpmlogRecMessage( rec_r );
        return RPMLOG_DEFAULT;
       }
 
@@ -1484,96 +1533,128 @@ namespace
     { static Rpmlog _rpmlog; return _rpmlog; }
   };
 
-
-} // namespace
-///////////////////////////////////////////////////////////////////
-//
-//     METHOD NAME : RpmDb::checkPackage
-//     METHOD TYPE : RpmDb::CheckPackageResult
-//
-RpmDb::CheckPackageResult RpmDb::checkPackage( const Pathname & path_r, CheckPackageDetail & detail_r )
-{
-  PathInfo file( path_r );
-  if ( ! file.isFile() )
+  RpmDb::CheckPackageResult doCheckPackageSig( const Pathname & path_r,                        // rpm file to check
+                                              const Pathname & root_r,                 // target root
+                                              bool  requireGPGSig_r,                   // whether no gpg signature is to be reported
+                                              RpmDb::CheckPackageDetail & detail_r )   // detailed result
   {
-    ERR << "Not a file: " << file << endl;
-    return CHK_ERROR;
-  }
-
-  FD_t fd = ::Fopen( file.asString().c_str(), "r.ufdio" );
-  if ( fd == 0 || ::Ferror(fd) )
-  {
-    ERR << "Can't open file for reading: " << file << " (" << ::Fstrerror(fd) << ")" << endl;
-    if ( fd )
-      ::Fclose( fd );
-    return CHK_ERROR;
-  }
-  rpmts ts = ::rpmtsCreate();
-  ::rpmtsSetRootDir( ts, root().asString().c_str() );
-  ::rpmtsSetVSFlags( ts, RPMVSF_DEFAULT );
+    PathInfo file( path_r );
+    if ( ! file.isFile() )
+    {
+      ERR << "Not a file: " << file << endl;
+      return RpmDb::CHK_ERROR;
+    }
 
-  rpmQVKArguments_s qva;
-  memset( &qva, 0, sizeof(rpmQVKArguments_s) );
-  qva.qva_flags = (VERIFY_DIGEST|VERIFY_SIGNATURE);
+    FD_t fd = ::Fopen( file.asString().c_str(), "r.ufdio" );
+    if ( fd == 0 || ::Ferror(fd) )
+    {
+      ERR << "Can't open file for reading: " << file << " (" << ::Fstrerror(fd) << ")" << endl;
+      if ( fd )
+       ::Fclose( fd );
+      return RpmDb::CHK_ERROR;
+    }
+    rpmts ts = ::rpmtsCreate();
+    ::rpmtsSetRootDir( ts, root_r.c_str() );
+    ::rpmtsSetVSFlags( ts, RPMVSF_DEFAULT );
+
+    rpmQVKArguments_s qva;
+    memset( &qva, 0, sizeof(rpmQVKArguments_s) );
+#ifdef HAVE_NO_RPMTSSETVFYFLAGS
+    // Legacy: In rpm >= 4.15 qva_flags symbols don't exist
+    // and qva_flags is not used in signature checking at all.
+    qva.qva_flags = (VERIFY_DIGEST|VERIFY_SIGNATURE);
+#else
+    ::rpmtsSetVfyFlags( ts, RPMVSF_DEFAULT );
+#endif
+    RpmlogCapture vresult;
+    LocaleGuard guard( LC_ALL, "C" );  // bsc#1076415: rpm log output is localized, but we need to parse it :(
+    int res = ::rpmVerifySignatures( &qva, ts, fd, path_r.basename().c_str() );
+    guard.restore();
+
+    ts = rpmtsFree(ts);
+    ::Fclose( fd );
+
+    // results per line...
+    //     Header V3 RSA/SHA256 Signature, key ID 3dbdc284: OK
+    //     Header SHA1 digest: OK (a60386347863affefef484ff1f26c889373eb094)
+    //     V3 RSA/SHA256 Signature, key ID 3dbdc284: OK
+    //     MD5 digest: OK (fd5259fe677a406951dcb2e9d08c4dcc)
+    //
+    // TODO: try to get SIG info from the header rather than parsing the output
+    std::vector<std::string> lines;
+    str::split( vresult, std::back_inserter(lines), "\n" );
+    unsigned count[7] = { 0, 0, 0, 0, 0, 0, 0 };
+
+    for ( unsigned i = 1; i < lines.size(); ++i )
+    {
+      std::string & line( lines[i] );
+      RpmDb::CheckPackageResult lineres = RpmDb::CHK_ERROR;
+      if ( line.find( ": OK" ) != std::string::npos )
+      {
+       lineres = RpmDb::CHK_OK;
+       if ( line.find( "Signature, key ID" ) == std::string::npos )
+         ++count[RpmDb::CHK_NOSIG];    // Valid but no gpg signature -> CHK_NOSIG
+      }
+      else if ( line.find( ": NOKEY" ) != std::string::npos )
+      { lineres = RpmDb::CHK_NOKEY; }
+      else if ( line.find( ": BAD" ) != std::string::npos )
+      { lineres = RpmDb::CHK_FAIL; }
+      else if ( line.find( ": UNKNOWN" ) != std::string::npos )
+      { lineres = RpmDb::CHK_NOTFOUND; }
+      else if ( line.find( ": NOTRUSTED" ) != std::string::npos )
+      { lineres = RpmDb::CHK_NOTTRUSTED; }
+
+      ++count[lineres];
+      detail_r.push_back( RpmDb::CheckPackageDetail::value_type( lineres, std::move(line) ) );
+    }
 
-  RpmlogCapture vresult;
-  int res = ::rpmVerifySignatures( &qva, ts, fd, path_r.basename().c_str() );
+    RpmDb::CheckPackageResult ret = ( res ? RpmDb::CHK_ERROR : RpmDb::CHK_OK );
 
-  ts = rpmtsFree(ts);
-  ::Fclose( fd );
+    if ( count[RpmDb::CHK_FAIL] )
+      ret = RpmDb::CHK_FAIL;
 
+    else if ( count[RpmDb::CHK_NOTFOUND] )
+      ret = RpmDb::CHK_NOTFOUND;
 
-  if ( res == 0 )
-  {
-    // remove trailing NL!
-    detail_r.push_back( CheckPackageDetail::value_type( CHK_OK, str::rtrim( std::move(vresult) ) ) );
-    return CHK_OK;
-  }
+    else if ( count[RpmDb::CHK_NOKEY] )
+      ret = RpmDb::CHK_NOKEY;
 
-  // results per line...
-  WAR << vresult;
-  std::vector<std::string> lines;
-  str::split( vresult, std::back_inserter(lines), "\n" );
-  unsigned count[6] = { 0, 0, 0, 0, 0, 0 };
+    else if ( count[RpmDb::CHK_NOTTRUSTED] )
+      ret = RpmDb::CHK_NOTTRUSTED;
 
-  for ( unsigned i = 1; i < lines.size(); ++i )
-  {
-    std::string & line( lines[i] );
-    CheckPackageResult lineres = CHK_ERROR;
-    if ( line.find( ": OK" ) != std::string::npos )
-    { lineres = CHK_OK; }
-    else if ( line.find( ": NOKEY" ) != std::string::npos )
-    { lineres = CHK_NOKEY; }
-    else if ( line.find( ": BAD" ) != std::string::npos )
-    { lineres = CHK_FAIL; }
-    else if ( line.find( ": UNKNOWN" ) != std::string::npos )
-    { lineres = CHK_NOTFOUND; }
-    else if ( line.find( ": NOTRUSTED" ) != std::string::npos )
-    { lineres = CHK_NOTTRUSTED; }
+    else if ( ret == RpmDb::CHK_OK )
+    {
+      if ( count[RpmDb::CHK_OK] == count[RpmDb::CHK_NOSIG]  )
+      {
+       detail_r.push_back( RpmDb::CheckPackageDetail::value_type( RpmDb::CHK_NOSIG, std::string("    ")+_("Package is not signed!") ) );
+       if ( requireGPGSig_r )
+         ret = RpmDb::CHK_NOSIG;
+      }
+    }
 
-    ++count[lineres];
-    detail_r.push_back( CheckPackageDetail::value_type( lineres, std::move(line) ) );
+    if ( ret != RpmDb::CHK_OK )
+    {
+      WAR << path_r << " (" << requireGPGSig_r << " -> " << ret << ")" << endl;
+      WAR << vresult;
+    }
+    return ret;
   }
 
-  CheckPackageResult ret = CHK_ERROR;
-  if ( count[CHK_FAIL] )
-    ret = CHK_FAIL;
-
-  else if ( count[CHK_NOTFOUND] )
-    ret = CHK_NOTFOUND;
-
-  else if ( count[CHK_NOKEY] )
-    ret = CHK_NOKEY;
-
-  else if ( count[CHK_NOTTRUSTED] )
-    ret = CHK_NOTTRUSTED;
-
-  return ret;
-}
+} // namespace
+///////////////////////////////////////////////////////////////////
+//
+//     METHOD NAME : RpmDb::checkPackage
+//     METHOD TYPE : RpmDb::CheckPackageResult
+//
+RpmDb::CheckPackageResult RpmDb::checkPackage( const Pathname & path_r, CheckPackageDetail & detail_r )
+{ return doCheckPackageSig( path_r, root(), false/*requireGPGSig_r*/, detail_r ); }
 
 RpmDb::CheckPackageResult RpmDb::checkPackage( const Pathname & path_r )
 { CheckPackageDetail dummy; return checkPackage( path_r, dummy ); }
 
+RpmDb::CheckPackageResult RpmDb::checkPackageSignature( const Pathname & path_r, RpmDb::CheckPackageDetail & detail_r )
+{ return doCheckPackageSig( path_r, root(), true/*requireGPGSig_r*/, detail_r ); }
+
 
 // determine changed files of installed package
 bool
@@ -1985,32 +2066,31 @@ void RpmDb::doInstallPackage( const Pathname & filename, RpmInstFlags flags, cal
   run_rpm( opts, ExternalProgram::Stderr_To_Stdout );
 
   std::string line;
-  std::string rpmmsg;
-  std::vector<std::string> configwarnings;
+  std::string rpmmsg;                          // TODO: immediately forward lines via Callback::report rather than collecting
+  std::vector<std::string> configwarnings;     // TODO: immediately process lines rather than collecting
 
   unsigned linecnt = 0;
-  while (systemReadLine(line))
+  while ( systemReadLine( line ) )
   {
-    if ( linecnt < MAXRPMMESSAGELINES )
-      ++linecnt;
-    else
-      continue;
-
-    if (line.substr(0,2)=="%%")
+    if ( str::startsWith( line, "%%" ) )
     {
       int percent;
-      sscanf (line.c_str () + 2, "%d", &percent);
+      sscanf( line.c_str() + 2, "%d", &percent );
       report->progress( percent );
+      continue;
     }
-    else
-      rpmmsg += line+'\n';
 
-    if ( line.substr(0,8) == "warning:" )
-    {
+    if ( linecnt < MAXRPMMESSAGELINES )
+      ++linecnt;
+    else if ( line.find( " scriptlet failed, " ) == std::string::npos )        // always log %script errors
+      continue;
+
+    rpmmsg += line+'\n';
+
+    if ( str::startsWith( line, "warning:" ) )
       configwarnings.push_back(line);
-    }
   }
-  if ( linecnt > MAXRPMMESSAGELINES )
+  if ( linecnt >= MAXRPMMESSAGELINES )
     rpmmsg += "[truncated]\n";
 
   int rpm_status = systemStatus();
@@ -2157,7 +2237,7 @@ void RpmDb::doRemovePackage( const std::string & name_r, RpmInstFlags flags, cal
   run_rpm (opts, ExternalProgram::Stderr_To_Stdout);
 
   std::string line;
-  std::string rpmmsg;
+  std::string rpmmsg;          // TODO: immediately forward lines via Callback::report rather than collecting
 
   // got no progress from command, so we fake it:
   // 5  - command started
@@ -2169,11 +2249,11 @@ void RpmDb::doRemovePackage( const std::string & name_r, RpmInstFlags flags, cal
   {
     if ( linecnt < MAXRPMMESSAGELINES )
       ++linecnt;
-    else
+    else if ( line.find( " scriptlet failed, " ) == std::string::npos )        // always log %script errors
       continue;
     rpmmsg += line+'\n';
   }
-  if ( linecnt > MAXRPMMESSAGELINES )
+  if ( linecnt >= MAXRPMMESSAGELINES )
     rpmmsg += "[truncated]\n";
   report->progress( 50 );
   int rpm_status = systemStatus();
@@ -2373,6 +2453,8 @@ std::ostream & operator<<( std::ostream & str, RpmDb::CheckPackageResult obj )
     OUTS( CHK_NOKEY,           _("Signatures public key is not available") );
     // translators: possible rpm package signature check result [brief]
     OUTS( CHK_ERROR,           _("File does not exist or signature can't be checked") );
+    // translators: possible rpm package signature check result [brief]
+    OUTS( CHK_NOSIG,           _("File is unsigned") );
 #undef OUTS
   }
   return str << "UnknowSignatureCheckError("+str::numstring(obj)+")";