From 28998aae993fc7271d2357e36eafcd26e8b4cb10 Mon Sep 17 00:00:00 2001 From: Benjamin Zeller Date: Wed, 14 Mar 2018 18:02:21 +0100 Subject: [PATCH] Introduce keys, keyadd and keyremove commands (fixes #35) --- doc/zypper.8.txt | 24 ++ src/CMakeLists.txt | 9 + src/Command.cc | 9 + src/Command.h | 11 +- src/Zypper.cc | 28 --- src/callbacks/keyring.h | 43 +--- src/commands/conditions.cc | 22 +- src/commands/keycmds.h | 8 + src/commands/keys/addkeycmd.cc | 60 +++++ src/commands/keys/addkeycmd.h | 26 +++ src/commands/keys/keyscmd.cc | 48 ++++ src/commands/keys/keyscmd.h | 29 +++ src/commands/keys/removekeycmd.cc | 70 ++++++ src/commands/keys/removekeycmd.h | 30 +++ src/commands/utils/download.cc | 21 +- src/keys.cc | 354 ++++++++++++++++++++++++++++++ src/keys.h | 21 ++ src/output/prompt.h | 2 + src/utils/misc.cc | 63 +++++- src/utils/misc.h | 11 + 20 files changed, 794 insertions(+), 95 deletions(-) create mode 100644 src/commands/keycmds.h create mode 100644 src/commands/keys/addkeycmd.cc create mode 100644 src/commands/keys/addkeycmd.h create mode 100644 src/commands/keys/keyscmd.cc create mode 100644 src/commands/keys/keyscmd.h create mode 100644 src/commands/keys/removekeycmd.cc create mode 100644 src/commands/keys/removekeycmd.h create mode 100644 src/keys.cc create mode 100644 src/keys.h diff --git a/doc/zypper.8.txt b/doc/zypper.8.txt index 9d6be745bf..413b79a2ca 100644 --- a/doc/zypper.8.txt +++ b/doc/zypper.8.txt @@ -1573,6 +1573,30 @@ Examples: :: {nop} $ *zypper aloc --packages de_CH*::: Request *de_CH* and install language dependent packages. + +Key Management +~~~~~~~~~~~~~~ +The *keys*, *addkey*, and *removekey* commands serve for manipulating the trusted key database. A key is specified by its ID, which is the last 16 characters of its fingerprint. + +*keys* (*lk*) ['options'] ['part-of-keyid-or-name'] ['key-filename'] ...:: + List all trusted keys or show detailed information about those specified as arguments, supports also keyfiles as argument. ++ +-- + *-d*, *--detail*:: + Shows the keys in a more detailed multiline output, also shows subkey information +-- + +*addkey* (*ak*) ['options'] 'url'...:: + Imports a new key into the trusted database, the file can be specified as a URL. + +*removekey* (*rk*) ['options'] 'string' :: + Remove specified key from the trusted database. The query string can be the beginning or end of the key ID, or any part of the key name. ++ +-- + *--all-matches*:: + Continue with removing even if the query matches multiple keys. +-- + Other Commands diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 318ef3850b..9586d4e6ac 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -28,6 +28,7 @@ SET (zypper_HEADERS PackageArgs.h SolverRequester.h Summary.h + keys.h global-settings.h issue.h callbacks/keyring.h @@ -39,6 +40,10 @@ SET (zypper_HEADERS commands/commandhelpformatter.h commands/conditions.h commands/basecommand.h + commands/keycmds.h + commands/keys/keyscmd.h + commands/keys/addkeycmd.h + commands/keys/removekeycmd.h commands/locks.h commands/locks/common.h commands/locks/add.h @@ -117,6 +122,7 @@ SET( zypper_SRCS RequestFeedback.cc SolverRequester.cc Summary.cc + keys.cc global-settings.cc issue.cc callbacks/media.cc @@ -124,6 +130,9 @@ SET( zypper_SRCS commands/commandhelpformatter.cc commands/conditions.cc commands/basecommand.cc + commands/keys/keyscmd.cc + commands/keys/addkeycmd.cc + commands/keys/removekeycmd.cc commands/locks/common.cc commands/locks/add.cc commands/locks/clean.cc diff --git a/src/Command.cc b/src/Command.cc index 58c5d6fe2f..31f6bb3947 100644 --- a/src/Command.cc +++ b/src/Command.cc @@ -40,6 +40,7 @@ #include "commands/locale/localescmd.h" #include "commands/locale/addlocalecmd.h" #include "commands/locale/removelocalecmd.h" +#include "commands/keycmds.h" using namespace zypp; @@ -155,6 +156,10 @@ namespace makeCmd ( ZypperCommand::ADD_LOCALE_e , std::string(), { "addlocale", "aloc" } ), makeCmd ( ZypperCommand::REMOVE_LOCALE_e , std::string(), { "removelocale", "rloc" } ), + makeCmd ( ZypperCommand::KEYS_e, _("Key Management:"), { "keys", "lk" } ), + makeCmd ( ZypperCommand::ADDKEY_e, std::string(), { "addkey", "ak" } ), + makeCmd ( ZypperCommand::REMOVEKEY_e, std::string(), { "removekey", "rk" } ), + makeCmd ( ZypperCommand::VERSION_CMP_e , _("Other Commands:"), { "versioncmp", "vcmp" } ), makeCmd ( ZypperCommand::TARGET_OS_e , std::string(), { "targetos", "tos" } ), makeCmd ( ZypperCommand::LICENSES_e , std::string(), { "licenses" } ), @@ -262,6 +267,10 @@ DEF_ZYPPER_COMMAND( REMOVE_LOCALE ); DEF_ZYPPER_COMMAND( NEEDS_REBOOTING ); +DEF_ZYPPER_COMMAND( KEYS ); +DEF_ZYPPER_COMMAND( ADDKEY ); +DEF_ZYPPER_COMMAND( REMOVEKEY ); + #undef DEF_ZYPPER_COMMAND /////////////////////////////////////////////////////////////////// diff --git a/src/Command.h b/src/Command.h index 150314c51f..e8f96e010d 100644 --- a/src/Command.h +++ b/src/Command.h @@ -88,6 +88,11 @@ struct ZypperCommand static const ZypperCommand RUG_PATTERN_INFO; static const ZypperCommand RUG_PRODUCT_INFO; //!@} + //! + + static const ZypperCommand KEYS; + static const ZypperCommand ADDKEY; + static const ZypperCommand REMOVEKEY; static const ZypperCommand NEEDS_REBOOTING; @@ -166,7 +171,11 @@ struct ZypperCommand ADD_LOCALE_e, REMOVE_LOCALE_e, - NEEDS_REBOOTING_e + NEEDS_REBOOTING_e, + + KEYS_e, + ADDKEY_e, + REMOVEKEY_e }; using CmdFactory = std::function; diff --git a/src/Zypper.cc b/src/Zypper.cc index bcc7942dc3..deaae1fb4c 100644 --- a/src/Zypper.cc +++ b/src/Zypper.cc @@ -147,34 +147,6 @@ void Zypper::assertZYppPtrGod() } } -/////////////////////////////////////////////////////////////////// - -namespace { - - /** Whether user may create \a dir_r or has rw-access to it. */ - inline bool userMayUseDir( const Pathname & dir_r ) - { - bool mayuse = true; - if ( dir_r.empty() ) - mayuse = false; - else - { - PathInfo pi( dir_r ); - if ( pi.isExist() ) - { - if ( ! ( pi.isDir() && pi.userMayRWX() ) ) - mayuse = false; - } - else - mayuse = userMayUseDir( dir_r.dirname() ); - } - return mayuse; - } - -} //namespace - -/////////////////////////////////////////////////////////////////// - Zypper::Zypper() : _argc( 0 ) , _argv( nullptr ) diff --git a/src/callbacks/keyring.h b/src/callbacks/keyring.h index b9f66b59ac..b7157b9d3f 100644 --- a/src/callbacks/keyring.h +++ b/src/callbacks/keyring.h @@ -19,6 +19,7 @@ #include "Zypper.h" #include "Table.h" +#include "utils/misc.h" /////////////////////////////////////////////////////////////////// namespace zypp @@ -56,48 +57,6 @@ namespace zypp Zypper::instance().out().warningPar( 4, str::Format(_("This file was modified after it has been signed. This may have been a malicious change, so it might not be trustworthy anymore! You should not continue unless you know it's safe.") ) ); } - std::ostream & dumpKeyInfo( std::ostream & str, const PublicKeyData & key, const KeyContext & context = KeyContext() ) - { - Zypper & zypper = Zypper::instance(); - if ( zypper.out().type() == Out::TYPE_XML ) - { - { - xmlout::Node parent( str, "gpgkey-info", xmlout::Node::optionalContent ); - - if ( !context.empty() ) - { - dumpAsXmlOn( *parent, context.repoInfo().asUserString(), "repository" ); - } - dumpAsXmlOn( *parent, key.name(), "key-name" ); - dumpAsXmlOn( *parent, key.fingerprint(), "key-fingerprint" ); - dumpAsXmlOn( *parent, key.algoName(), "key-algorithm" ); - dumpAsXmlOn( *parent, key.created(), "key-created" ); - dumpAsXmlOn( *parent, key.expires(), "key-expires" ); - dumpAsXmlOn( *parent, key.rpmName(), "rpm-name" ); - } - return str; - } - - Table t; - t.lineStyle( none ); - if ( !context.empty() ) - { - t << ( TableRow() << "" << _("Repository:") << context.repoInfo().asUserString() ); - } - t << ( TableRow() << "" << _("Key Fingerprint:") << str::gapify( key.fingerprint(), 4 ) ) - << ( TableRow() << "" << _("Key Name:") << key.name() ) - << ( TableRow() << "" << _("Key Algorithm:") << key.algoName() ) - << ( TableRow() << "" << _("Key Created:") << key.created() ) - << ( TableRow() << "" << _("Key Expires:") << key.expiresAsString() ); - for ( const PublicSubkeyData & sub : key.subkeys() ) - t << ( TableRow() << "" << _("Subkey:") << sub.asString() ); - t << ( TableRow() << "" << _("Rpm Name:") << key.rpmName() ); - - return str << t; - } - - inline std::ostream & dumpKeyInfo( std::ostream & str, const PublicKey & key, const KeyContext & context = KeyContext() ) - { return dumpKeyInfo( str, key.keyData(), context ); } } // namespace /////////////////////////////////////////////////////////////////// diff --git a/src/commands/conditions.cc b/src/commands/conditions.cc index 56cd8b5e9e..26ace23152 100644 --- a/src/commands/conditions.cc +++ b/src/commands/conditions.cc @@ -7,17 +7,33 @@ #include "conditions.h" #include "global-settings.h" #include "Zypper.h" +#include "utils/misc.h" #include #include #include +extern ZYpp::Ptr God; + int NeedsRootCondition::check(std::string &err) { - if ( geteuid() != 0 && !Zypper::instance().config().changedRoot ) + Zypper &zypp = Zypper::instance(); + if ( geteuid() != 0 ) { - err = _("Root privileges are required to run this command."); - return ZYPPER_EXIT_ERR_PRIVILEGES; + if ( ! zypp.config().changedRoot ) + { + err = _("Root privileges are required to run this command."); + return ZYPPER_EXIT_ERR_PRIVILEGES; + } + else + { + Pathname homeDir( zypp.config().root_dir / God->homePath() ); + if ( ! userMayUseDir( homeDir ) ) + { + err = ( str::Format(_("Insufficient privileges to use directory '%s'.")) % homeDir ); + return ( ZYPPER_EXIT_ERR_PRIVILEGES ); + } + } } return ZYPPER_EXIT_OK; } diff --git a/src/commands/keycmds.h b/src/commands/keycmds.h new file mode 100644 index 0000000000..970520a9b2 --- /dev/null +++ b/src/commands/keycmds.h @@ -0,0 +1,8 @@ +#ifndef ZYPPER_COMMANDS_KEYS_H_INCLUDED +#define ZYPPER_COMMANDS_KEYS_H_INCLUDED + +#include "keys/keyscmd.h" +#include "keys/addkeycmd.h" +#include "keys/removekeycmd.h" + +#endif diff --git a/src/commands/keys/addkeycmd.cc b/src/commands/keys/addkeycmd.cc new file mode 100644 index 0000000000..5bb4c015ff --- /dev/null +++ b/src/commands/keys/addkeycmd.cc @@ -0,0 +1,60 @@ +/*---------------------------------------------------------------------------*\ + ____ _ _ __ _ __ ___ _ _ + |_ / || | '_ \ '_ \/ -_) '_| + /__|\_, | .__/ .__/\___|_| + |__/|_| |_| +\*---------------------------------------------------------------------------*/ +#include "addkeycmd.h" +#include "Zypper.h" +#include "keys.h" +#include "commands/conditions.h" +#include "utils/messages.h" + +AddKeyCmd::AddKeyCmd(std::vector &&commandAliases_r) : + ZypperBaseCommand ( + std::move( commandAliases_r ), + // translators: command synopsis; do not translate the command 'name (abbreviations)' or '-option' names + _("addkey "), + // translators: command summary + _("Add a new key to trust."), + // translators: command description + _("Imports a new key into the trusted database, the file can be specified as a URL."), + InitTarget ) +{ } + + +zypp::ZyppFlags::CommandGroup AddKeyCmd::cmdOptions() const +{ + return {}; +} + +void AddKeyCmd::doReset() +{ + return; +} + +int AddKeyCmd::execute( Zypper &zypper, const std::vector &positionalArgs ) +{ + // too many arguments + if ( positionalArgs.size() > 1 ) { + report_too_many_arguments( help() ); + return ( ZYPPER_EXIT_ERR_INVALID_ARGS ); + } + + //not enough args + if ( positionalArgs.size() < 1 ) { + report_too_few_arguments( help() ); + return ( ZYPPER_EXIT_ERR_INVALID_ARGS ); + } + + importKey( zypper, positionalArgs.front() ); + return zypper.exitCode(); +} + + +std::vector AddKeyCmd::conditions() const +{ + return { + std::make_shared() + }; +} diff --git a/src/commands/keys/addkeycmd.h b/src/commands/keys/addkeycmd.h new file mode 100644 index 0000000000..7503796312 --- /dev/null +++ b/src/commands/keys/addkeycmd.h @@ -0,0 +1,26 @@ +/*---------------------------------------------------------------------------*\ + ____ _ _ __ _ __ ___ _ _ + |_ / || | '_ \ '_ \/ -_) '_| + /__|\_, | .__/ .__/\___|_| + |__/|_| |_| +\*---------------------------------------------------------------------------*/ +#ifndef ZYPPER_COMMANDS_KEYS_ADDKEYCMD_H_INCLUDED +#define ZYPPER_COMMANDS_KEYS_ADDKEYCMD_H_INCLUDED + +#include "commands/basecommand.h" +#include "utils/flags/zyppflags.h" + +class AddKeyCmd : public ZypperBaseCommand +{ +public: + AddKeyCmd( std::vector &&commandAliases_r ); + + // ZypperBaseCommand interface +protected: + zypp::ZyppFlags::CommandGroup cmdOptions() const override; + void doReset() override; + int execute( Zypper &zypper, const std::vector &positionalArgs ) override; + std::vector conditions() const override; +}; + +#endif diff --git a/src/commands/keys/keyscmd.cc b/src/commands/keys/keyscmd.cc new file mode 100644 index 0000000000..95b4355e07 --- /dev/null +++ b/src/commands/keys/keyscmd.cc @@ -0,0 +1,48 @@ +/*---------------------------------------------------------------------------*\ + ____ _ _ __ _ __ ___ _ _ + |_ / || | '_ \ '_ \/ -_) '_| + /__|\_, | .__/ .__/\___|_| + |__/|_| |_| +\*---------------------------------------------------------------------------*/ +#include "keyscmd.h" +#include "Zypper.h" +#include "keys.h" +#include "commands/commonflags.h" + +KeysCmd::KeysCmd( std::vector &&commandAliases_r ) : + ZypperBaseCommand ( + std::move( commandAliases_r ), + // translators: command synopsis; do not translate the command 'name (abbreviations)' or '-option' names + _("keys [OPTIONS] [KEYID] [KEYFILE] ..."), + // translators: command summary + _("List all keys."), + // translators: command description + _("List all trusted keys or show detailed information about those specified as arguments, supports also keyfiles as argument."), + InitTarget ) +{ + init(); +} + +void KeysCmd::init() +{ + _showDetails = false; +} + +zypp::ZyppFlags::CommandGroup KeysCmd::cmdOptions() const +{ + auto &that = *const_cast(this); + return {{ + CommonFlags::detailsFlag( that._showDetails, '\0', _("Show more details.") ) + }}; +} + +void KeysCmd::doReset() +{ + init(); +} + +int KeysCmd::execute( Zypper &zypper, const std::vector & ) +{ + listTrustedKeys( zypper, positionalArguments(), _showDetails ); + return zypper.exitCode(); +} diff --git a/src/commands/keys/keyscmd.h b/src/commands/keys/keyscmd.h new file mode 100644 index 0000000000..75c5d1b217 --- /dev/null +++ b/src/commands/keys/keyscmd.h @@ -0,0 +1,29 @@ +/*---------------------------------------------------------------------------*\ + ____ _ _ __ _ __ ___ _ _ + |_ / || | '_ \ '_ \/ -_) '_| + /__|\_, | .__/ .__/\___|_| + |__/|_| |_| +\*---------------------------------------------------------------------------*/ +#ifndef ZYPPER_COMMANDS_KEYS_KEYSCMD_H_INCLUDED +#define ZYPPER_COMMANDS_KEYS_KEYSCMD_H_INCLUDED + +#include "commands/basecommand.h" +#include "utils/flags/zyppflags.h" + +class KeysCmd : public ZypperBaseCommand +{ +public: + KeysCmd( std::vector &&commandAliases_r ); + +private: + void init (); + bool _showDetails; + + // ZypperBaseCommand interface +protected: + zypp::ZyppFlags::CommandGroup cmdOptions() const override; + void doReset() override; + int execute(Zypper &zypper, const std::vector &) override; +}; + +#endif diff --git a/src/commands/keys/removekeycmd.cc b/src/commands/keys/removekeycmd.cc new file mode 100644 index 0000000000..0856035a2b --- /dev/null +++ b/src/commands/keys/removekeycmd.cc @@ -0,0 +1,70 @@ +/*---------------------------------------------------------------------------*\ + ____ _ _ __ _ __ ___ _ _ + |_ / || | '_ \ '_ \/ -_) '_| + /__|\_, | .__/ .__/\___|_| + |__/|_| |_| +\*---------------------------------------------------------------------------*/ +#include "removekeycmd.h" +#include "Zypper.h" +#include "keys.h" +#include "utils/messages.h" +#include "utils/flags/flagtypes.h" +#include "commands/conditions.h" + +RemoveKeyCmd::RemoveKeyCmd(std::vector &&commandAliases_r) : + ZypperBaseCommand ( + std::move( commandAliases_r ), + // translators: command synopsis; do not translate the command 'name (abbreviations)' or '-option' names + _("removekey "), + // translators: command summary + _("Remove a key from trust."), + // translators: command description + _("Remove key specified by ID from trust."), + InitTarget ) +{ + init(); +} + +void RemoveKeyCmd::init() +{ + _allMatches = false; +} + + +zypp::ZyppFlags::CommandGroup RemoveKeyCmd::cmdOptions() const +{ + auto &that = *const_cast( this ); + return {{ + { "all-matches", '\0', ZyppFlags::NoArgument, ZyppFlags::BoolCompatibleType( that._allMatches ), _("Remove all matched keys.") } + }}; +} + +void RemoveKeyCmd::doReset() +{ + init(); +} + +int RemoveKeyCmd::execute( Zypper &zypper, const std::vector &positionalArgs ) +{ + // too many arguments + if ( positionalArgs.size() > 1 ) { + report_too_many_arguments( help() ); + return ( ZYPPER_EXIT_ERR_INVALID_ARGS ); + } + + //not enough args + if ( positionalArgs.size() < 1 ) { + report_too_few_arguments( help() ); + return ( ZYPPER_EXIT_ERR_INVALID_ARGS ); + } + + removeKey( zypper, positionalArgs.front(), _allMatches ); + return zypper.exitCode(); +} + +std::vector RemoveKeyCmd::conditions() const +{ + return { + std::make_shared() + }; +} diff --git a/src/commands/keys/removekeycmd.h b/src/commands/keys/removekeycmd.h new file mode 100644 index 0000000000..f3d39690a5 --- /dev/null +++ b/src/commands/keys/removekeycmd.h @@ -0,0 +1,30 @@ +/*---------------------------------------------------------------------------*\ + ____ _ _ __ _ __ ___ _ _ + |_ / || | '_ \ '_ \/ -_) '_| + /__|\_, | .__/ .__/\___|_| + |__/|_| |_| +\*---------------------------------------------------------------------------*/ +#ifndef ZYPPER_COMMANDS_KEYS_REMOVEKEYCMD_H_INCLUDED +#define ZYPPER_COMMANDS_KEYS_REMOVEKEYCMD_H_INCLUDED + +#include "commands/basecommand.h" +#include "utils/flags/zyppflags.h" + +class RemoveKeyCmd : public ZypperBaseCommand +{ +public: + RemoveKeyCmd( std::vector &&commandAliases_r ); + +private: + void init (); + bool _allMatches; + + // ZypperBaseCommand interface +protected: + zypp::ZyppFlags::CommandGroup cmdOptions() const override; + void doReset() override; + int execute(Zypper &zypper, const std::vector &positionalArgs) override; + std::vector conditions() const override; +}; + +#endif diff --git a/src/commands/utils/download.cc b/src/commands/utils/download.cc index 93752ef9b0..4bb3c11f01 100644 --- a/src/commands/utils/download.cc +++ b/src/commands/utils/download.cc @@ -19,6 +19,7 @@ #include "commands/conditions.h" #include "utils/flags/flagtypes.h" #include "utils/messages.h" +#include "utils/misc.h" #include "Zypper.h" #include "PackageArgs.h" #include "Table.h" @@ -85,26 +86,6 @@ namespace } } - /** Whether user may create \a dir_r or has rw-access to it. */ - inline bool userMayUseDir( const Pathname & dir_r ) - { - bool mayuse = true; - if ( dir_r.empty() ) - mayuse = false; - else - { - PathInfo pi( dir_r ); - if ( pi.isExist() ) - { - if ( ! ( pi.isDir() && pi.userMayRWX() ) ) - mayuse = false; - } - else - mayuse = userMayUseDir( dir_r.dirname() ); - } - return mayuse; - } - class EnsureWriteableCacheCondition : public BaseCommandCondition { // BaseCommandCondition interface diff --git a/src/keys.cc b/src/keys.cc new file mode 100644 index 0000000000..3b7fd3ee1e --- /dev/null +++ b/src/keys.cc @@ -0,0 +1,354 @@ +/*---------------------------------------------------------------------------*\ + ____ _ _ __ _ __ ___ _ _ + |_ / || | '_ \ '_ \/ -_) '_| + /__|\_, | .__/ .__/\___|_| + |__/|_| |_| +\*---------------------------------------------------------------------------*/ + +#include "Zypper.h" +#include "utils/prompt.h" +#include "utils/misc.h" +#include "utils/text.h" +#include "utils/console.h" + +#include +#include +#include + +#include + +extern ZYpp::Ptr God; + +namespace { +/** + * Match a string argument against the key with the following approach + * - Does the key ID start or end with the string + * - Is the string part of the keys name + * - Does the subkey IDs start or end with the string + * + * \return true if the key matches + */ +bool matchKeyToArg ( const PublicKeyData &key, const std::string &arg ) + { + auto idMatches = [] ( const std::string &id, const std::string &search ) { + return str::startsWithCI( id, search ) || str::endsWithCI( id, search ); + }; + + if ( idMatches( key.id(), arg ) ) + return true; + else if ( str::containsCI( key.name(), arg ) ) + return true; + else { + for ( const auto &sKey : key.subkeys() ) { + if ( idMatches( sKey.id(), arg ) ) + return true; + } + } + return false; + } +} + +template +void dumpKeyList ( Zypper &zypper, const std::list &keysFound_r, bool details ) +{ + if ( keysFound_r.empty() ) + { + zypper.out().warning(_("No keys found.") ); + } + else + { + zypper.out().gap(); + if ( details || zypper.config().machine_readable ) + { + for(const auto &key : keysFound_r) + { + if (zypper.config().machine_readable) + dumpKeyInfo( std::cout , key ); + else + { + + // render all keys to the console in a nice way: + // +-------------------+ + // | .^l.. . | Key ID: A193FBB572174FC2 + // | ^ l.: . | Key Name: Virtualization OBS Project + // | ^ :.i ^ | Key Fingerprint: 55A0B34D 49501BB7 CA474F5A A193FBB5 72174FC2 + // | i.^ ?. | Key Created: Sat 23 Dec 2017 05:45:11 AM CET + // | ..: : .E^.| Key Expires: Mon 02 Mar 2020 05:45:11 AM CET + // | S . . .^:| Rpm Name: gpg-pubkey-72174fc2-5a3ddf57 + // | ^ ^^| + // | ^ .| + // | | + // | | + // | | + // +----[72174FC2]-----+ + + size_t artWidth = 0; + + PublicKey::AsciiArt::Options asciiArtOptions; + if (zypper.config().do_colors ) + asciiArtOptions |= PublicKey::AsciiArt::USE_COLOR; + + // render the ascii art into a buffer + std::vector art( + key.asciiArt().asLines( asciiArtOptions ) + ); + if ( art.size() ) + { + artWidth = art[0].length(); + } + + std::string keyInfo; + { + std::stringstream infoStr; + dumpKeyInfo ( infoStr, key ); + keyInfo = infoStr.str(); + } + + // render the key info into a buffer, but intend it with the width the ascii Art takes up + // MbsWriteWrapped makes sure the strings are wrapped to fit the console + std::vector formattedInfo; + { + std::stringstream infoStr; + mbs::MbsWriteWrapped writer( infoStr, artWidth, get_screen_width() ); + writer.addString( keyInfo ); + str::split( infoStr.str(), std::back_inserter(formattedInfo), "\n" ); + } + + // bring both sides together and write them to the output, + // write not data to the first nr of lines in noDataOnFirstLinesCnt + const size_t noDataOnFirstLinesCnt = art.size() > 0 ? 1 : 0; + for ( size_t i = 0; i < std::max(art.size(), formattedInfo.size() + noDataOnFirstLinesCnt); i++ ) + { + bool renderArtLine = i < art.size(); + if ( renderArtLine ) + { + std::cout << art[i]; + } + + size_t dataIdx = i - noDataOnFirstLinesCnt; + if ( i >= noDataOnFirstLinesCnt && formattedInfo.size() > dataIdx ) + { + std::cout << formattedInfo[dataIdx].substr( renderArtLine ? artWidth : 0 ); + } + + std::cout << std::endl; + } + } + zypper.out().gap(); + } + } else { + Table t; + t << ( TableHeader() + /* translators: Table column header */ << _("ID") + /* translators: Table column header */ << _("Name") + /* translators: Table column header */ << _("Expires")); + + for( const auto &key : keysFound_r ) + { + t << ( TableRow() + << key.id() + << key.name() + << key.expires().asString()); + } + t.dumpTo( std::cout ); + zypper.out().gap(); + } + } + zypper.setExitCode( ZYPPER_EXIT_OK ); +} + +void listTrustedKeys ( Zypper &zypper, const std::vector &keysToList, bool details ) +{ + if ( !God || !God->target() || !God->keyRing() ) + return; + + KeyRing_Ptr keyRing = God->keyRing(); + + if ( keysToList.size() ) { + std::list pubKeys; + for ( const std::string &arg : keysToList ) + { + for ( const PublicKeyData &key : keyRing->trustedPublicKeyData() ) { + if ( matchKeyToArg( key, arg ) ) { + //directly matched to a key, use it directly + pubKeys.push_back( key ); + } else { + // is the current arg a path or a ID + filesystem::PathInfo path (arg); + if (!path.isExist()) + { + zypper.out().warning( str::Format(_("Argument '%1%' is not a trusted ID nor a existing file.")) % arg ); + zypper.setExitCode( ZYPPER_EXIT_ERR_INVALID_ARGS ); + return; + } + + KeyManagerCtx keyMgr = KeyManagerCtx::createForOpenPGP(); + pubKeys.splice( pubKeys.end(), keyMgr.readKeyFromFile( arg )); + } + } + } + //print always in detail mode here, the user most likely wants to know more about the key + dumpKeyList( zypper, pubKeys, true ); + } + else { + dumpKeyList( zypper, keyRing->trustedPublicKeyData(), details ); + } +} + +void removeKey ( Zypper &zypper, const std::string &searchStr , bool removeAllMatches ) +{ + if ( !God || !God->keyRing() ) + return; + + KeyRing_Ptr keyRing = God->keyRing(); + + std::list foundKeys; + for ( const PublicKeyData &key : keyRing->trustedPublicKeyData() ) + { + if ( matchKeyToArg( key, searchStr )) + foundKeys.push_back( key ); + } + + if ( !foundKeys.size() ) { + zypper.out().warning( str::Format(_("Key %1% is not known")) % searchStr ); + zypper.setExitCode( ZYPPER_EXIT_ERR_INVALID_ARGS ); + return; + } + + if ( foundKeys.size() > 1 && !removeAllMatches ) { + zypper.out().warning( str::Format(_("Query %1% matches multiple keys, use the %2% argument to remove multiple matches.")) % searchStr % "--all-matches" ); + zypper.setExitCode( ZYPPER_EXIT_ERR_INVALID_ARGS ); + return; + } + + auto delKey = [&]( const PublicKeyData &key, bool askBeforeDelete ) { + + if ( askBeforeDelete ) { + std::stringstream keyInfo; + dumpKeyInfo ( keyInfo, key ); + std::string question = str::Format( + "About to delete key: \n" + "%1%\n" + "Are you sure?\n" + ) % keyInfo.str(); + + if ( ! read_bool_answer(PROMPT_YN_GPG_REMOVE_KEY_ACCEPT, question, false) ) { + return; + } + } + + try + { + std::cout << "Deleting key " << key.id() << std::endl; + keyRing->deleteKey( key.id(), true ); + } catch ( const Exception & e ) { + ZYPP_CAUGHT( e ); + zypper.out().error( e, str::Format(_("Failed to delete key: %1%")) % searchStr ); + zypper.setExitCode( ZYPPER_EXIT_ERR_ZYPP ); + return; + } + }; + + bool askBeforeDelete = true; + + if ( foundKeys.size() > 1 ) { + std::stringstream keyInfo; + dumpKeyList ( zypper, foundKeys, false ); + std::string question = _("Found multiple keys, do you want to delete them?\n"); + + PromptOptions popts; + popts.setOptions(_("y/a/c"), 2 /* default reply */); + popts.setOptionHelp(0, _("Remove all keys 'y'")); + popts.setOptionHelp(1, _("Ask for each key seperately 'a'")); + popts.setOptionHelp(2, _("Cancel 'c'")); + + zypper.out().prompt( PROMPT_YAC_GPG_REMOVE_KEYS_ACCEPT, question, popts ); + switch ( get_prompt_reply( zypper, PROMPT_YAC_GPG_REMOVE_KEYS_ACCEPT, popts) ) { + case 0: //Remove all + askBeforeDelete = false; + break; + case 1: //Seperately + askBeforeDelete = true; + break; + case 2: //Cancel + return; + } + } + + for ( const PublicKeyData &key : foundKeys ) { + delKey( key, askBeforeDelete ); + } +} + +void importKey (Zypper &zypper, const std::string &url_r ) +{ + if ( !God || !God->keyRing() ) + return; + + Url url = make_url( url_r ); + if ( !url.isValid() ) + { + zypper.setExitCode( ZYPPER_EXIT_ERR_INVALID_ARGS ); + return; + } + + //try to get a file from the URL + ManagedFile local; + try + { local = MediaSetAccess::provideFileFromUrl(url); } + catch ( const media::MediaException & e ) + { + ZYPP_CAUGHT( e ); + zypper.out().error( e, str::Format(_("Problem accessing the file at the specified URI: %1%")) % url, + _("Please check if the URI is valid and accessible.") ); + zypper.setExitCode( ZYPPER_EXIT_ERR_ZYPP ); + return; + } + catch ( const Exception & e ) + { + ZYPP_CAUGHT( e ); + zypper.out().error( e, str::Format(_("Problem encountered while trying to read the file at the specified URI %1%")) % url ); + zypper.setExitCode( ZYPPER_EXIT_ERR_ZYPP ); + return; + } + + PublicKey pKey; + try + { + pKey = PublicKey(local->asString()); + } catch ( const Exception & e ) { + ZYPP_CAUGHT( e ); + zypper.out().error( e, str::Format(_("Problem encountered while parsing the file at the specified URI %1%")) % url ); + zypper.setExitCode( ZYPPER_EXIT_ERR_ZYPP ); + return; + } + + //first import all the keys in keyfile into the general keyring, we do not trust yet + KeyRing_Ptr keyRing = God->keyRing(); + keyRing->importKey( pKey, false); + + //now lets go over all imported keys and ask explicitely if the user wants to trust them + for ( const PublicKey &key : keyRing->publicKeys() ) + { + std::stringstream keyInfo; + dumpKeyInfo ( keyInfo, key ); + std::string question = str::Format( + "About to import key: \n" + "%1%\n" + "Do you want to continue?\n" + ) % keyInfo.str(); + + if ( read_bool_answer(PROMPT_YN_GPG_UNKNOWN_KEY_ACCEPT, question, false) ) + { + try + { + keyRing->importKey( keyRing->exportPublicKey(key.keyData()), true ); + } catch ( const Exception & e ) { + ZYPP_CAUGHT( e ); + zypper.out().error( e, str::Format(_("Failed to import key from URL: %1%")) % url ); + zypper.setExitCode( ZYPPER_EXIT_ERR_ZYPP ); + return; + } + } + } +} diff --git a/src/keys.h b/src/keys.h new file mode 100644 index 0000000000..7e3319da9e --- /dev/null +++ b/src/keys.h @@ -0,0 +1,21 @@ +/*---------------------------------------------------------------------------*\ + ____ _ _ __ _ __ ___ _ _ + |_ / || | '_ \ '_ \/ -_) '_| + /__|\_, | .__/ .__/\___|_| + |__/|_| |_| +\*---------------------------------------------------------------------------*/ +#ifndef ZYPPERKEYS_H_ +#define ZYPPERKEYS_H_ + +#include +#include +#include + +class Zypper; + +void listTrustedKeys ( Zypper &zypper, const std::vector &keysToList = {} , bool details = false ); + +void importKey ( Zypper &zypper, const std::string &url); +void removeKey ( Zypper &zypper, const std::string &searchStr, bool removeAllMatches ); + +#endif diff --git a/src/output/prompt.h b/src/output/prompt.h index 7104f08431..dfffa1c28a 100644 --- a/src/output/prompt.h +++ b/src/output/prompt.h @@ -72,6 +72,8 @@ typedef enum PR_ENUM(PROMPT_PACKAGEKIT_QUIT, 23) PR_ENUM(PROMPT_YN_CONTINUE_ON_FILECONFLICT, 24) PR_ENUM(PROMPT_YN_RUN_SEARCH_PACKAGES, 25) + PR_ENUM(PROMPT_YAC_GPG_REMOVE_KEYS_ACCEPT, 26) + PR_ENUM(PROMPT_YN_GPG_REMOVE_KEY_ACCEPT, 27) #ifndef PROMPT_H_ #define PROMPT_H_ diff --git a/src/utils/misc.cc b/src/utils/misc.cc index 98b52a6a13..acff440ad8 100644 --- a/src/utils/misc.cc +++ b/src/utils/misc.cc @@ -809,7 +809,6 @@ void packagekit_suggest_quit() pkcall.close(); } - bool iType( const ui::Selectable::constPtr & sel_r ) { return sel_r->hasInstalledObj() || @@ -897,3 +896,65 @@ const char * computeStatusIndicator( const ui::Selectable & sel_r, bool tagForei return stem[1]; return stem[0]; } + +std::ostream &dumpKeyInfo(std::ostream &str, const PublicKeyData &key, const KeyContext &context) +{ + Zypper & zypper = Zypper::instance(); + if ( zypper.out().type() == Out::TYPE_XML ) + { + { + xmlout::Node parent( str, "gpgkey-info", xmlout::Node::optionalContent ); + + if ( !context.empty() ) + { + dumpAsXmlOn( *parent, context.repoInfo().asUserString(), "repository" ); + } + dumpAsXmlOn( *parent, key.id(), "key-id" ); + dumpAsXmlOn( *parent, key.name(), "key-name" ); + dumpAsXmlOn( *parent, key.fingerprint(), "key-fingerprint" ); + dumpAsXmlOn( *parent, key.algoName(), "key-algorithm" ); + dumpAsXmlOn( *parent, key.created(), "key-created" ); + dumpAsXmlOn( *parent, key.expires(), "key-expires" ); + dumpAsXmlOn( *parent, key.rpmName(), "rpm-name" ); + } + return str; + } + + Table t; + t.lineStyle( none ); + if ( !context.empty() ) + { + t << ( TableRow() << "" << _("Repository:") << context.repoInfo().asUserString() ); + } + t << ( TableRow() << "" << _("Key ID:") << key.id() ) + + << ( TableRow() << "" << _("Key Fingerprint:") << str::gapify( key.fingerprint(), 4 ) ) + << ( TableRow() << "" << _("Key Name:") << key.name() ) + << ( TableRow() << "" << _("Key Algorithm:") << key.algoName() ) + << ( TableRow() << "" << _("Key Created:") << key.created() ) + << ( TableRow() << "" << _("Key Expires:") << key.expiresAsString() ); + for ( const PublicSubkeyData & sub : key.subkeys() ) + t << ( TableRow() << "" << _("Subkey:") << sub.asString() ); + t << ( TableRow() << "" << _("Rpm Name:") << key.rpmName() ); + + return str << t; +} + +bool userMayUseDir(const Pathname &dir_r) +{ + bool mayuse = true; + if ( dir_r.empty() ) + mayuse = false; + else + { + PathInfo pi( dir_r ); + if ( pi.isExist() ) + { + if ( ! ( pi.isDir() && pi.userMayRWX() ) ) + mayuse = false; + } + else + mayuse = userMayUseDir( dir_r.dirname() ); + } + return mayuse; +} diff --git a/src/utils/misc.h b/src/utils/misc.h index 0ccf3ba6b7..db394c69cc 100644 --- a/src/utils/misc.h +++ b/src/utils/misc.h @@ -20,6 +20,8 @@ #include #include #include +#include +#include class Zypper; class Table; @@ -250,4 +252,13 @@ bool packagekit_running(); /** Send suggestion to quit to PackageKit via DBus */ void packagekit_suggest_quit(); +/** Dump information about a Key for the current output format */ +std::ostream & dumpKeyInfo( std::ostream & str, const PublicKeyData & key, const KeyContext & context = KeyContext() ); +inline std::ostream & dumpKeyInfo( std::ostream & str, const PublicKey & key, const KeyContext & context = KeyContext() ) +{ return dumpKeyInfo( str, key.keyData(), context ); } + +/** Whether user may create \a dir_r or has rw-access to it. */ +bool userMayUseDir( const Pathname & dir_r ); + + #endif /*ZYPPER_UTILS_H*/