diff --git a/src/commands/setinitialpincommand.cpp b/src/commands/setinitialpincommand.cpp index 6a23b4e44..1ea9376c6 100644 --- a/src/commands/setinitialpincommand.cpp +++ b/src/commands/setinitialpincommand.cpp @@ -1,162 +1,170 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/setinitialpincommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "setinitialpincommand.h" -#include "command_p.h" +#include "cardcommand_p.h" -#include +#include "dialogs/setinitialpindialog.h" -#include +#include "smartcard/netkeycard.h" +#include "smartcard/readerstatus.h" #include using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::Dialogs; using namespace Kleo::SmartCard; using namespace GpgME; -class SetInitialPinCommand::Private : public Command::Private +class SetInitialPinCommand::Private : public CardCommand::Private { friend class ::Kleo::Commands::SetInitialPinCommand; SetInitialPinCommand *q_func() const { return static_cast(q); } public: - explicit Private(SetInitialPinCommand *qq); + explicit Private(SetInitialPinCommand *qq, const std::string &serialNumber); ~Private(); private: void init() { - } void ensureDialogCreated() const { if (dialog) { return; } SetInitialPinDialog *dlg = new SetInitialPinDialog; applyWindowID(dlg); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->setWindowTitle(i18nc("@title:window", "Set Initial Pin")); - const std::vector pinStates = ReaderStatus::instance()->pinStates(0); - - dlg->setNksPinPresent(pinStates.size() >= 1 && pinStates[0] != Card::NullPin); - dlg->setSigGPinPresent(pinStates.size() >= 3 && pinStates[2] != Card::NullPin); - connect(dlg, SIGNAL(nksPinRequested()), q_func(), SLOT(slotNksPinRequested())); connect(dlg, SIGNAL(sigGPinRequested()), q_func(), SLOT(slotSigGPinRequested())); connect(dlg, SIGNAL(rejected()), q_func(), SLOT(slotDialogRejected())); connect(dlg, SIGNAL(accepted()), q_func(), SLOT(slotDialogAccepted())); dialog = dlg; } void ensureDialogVisible() { ensureDialogCreated(); if (dialog->isVisible()) { dialog->raise(); } else { dialog->show(); } } private: void slotNksPinRequested() { ReaderStatus::mutableInstance() ->startSimpleTransaction("SCD PASSWD --nullpin PW1.CH", dialog, "setNksPinSettingResult"); } void slotSigGPinRequested() { ReaderStatus::mutableInstance() ->startSimpleTransaction("SCD PASSWD --nullpin PW1.CH.SIG", dialog, "setSigGPinSettingResult"); } void slotDialogRejected() { if (dialog->isComplete()) { slotDialogAccepted(); } else { canceled(); } } void slotDialogAccepted() { ReaderStatus::mutableInstance()->updateStatus(); finished(); } private: mutable QPointer dialog; }; SetInitialPinCommand::Private *SetInitialPinCommand::d_func() { return static_cast(d.get()); } const SetInitialPinCommand::Private *SetInitialPinCommand::d_func() const { return static_cast(d.get()); } #define q q_func() #define d d_func() -SetInitialPinCommand::Private::Private(SetInitialPinCommand *qq) - : Command::Private(qq), +SetInitialPinCommand::Private::Private(SetInitialPinCommand *qq, const std::string &serialNumber) + : CardCommand::Private(qq, serialNumber, nullptr), dialog() { - } SetInitialPinCommand::Private::~Private() {} -SetInitialPinCommand::SetInitialPinCommand() - : Command(new Private(this)) +SetInitialPinCommand::SetInitialPinCommand(const std::string &serialNumber) + : CardCommand(new Private(this, serialNumber)) { d->init(); } SetInitialPinCommand::~SetInitialPinCommand() {} QDialog *SetInitialPinCommand::dialog() const { d->ensureDialogCreated(); return d->dialog; } void SetInitialPinCommand::doStart() { + d->ensureDialogCreated(); + + const auto nksCard = ReaderStatus::instance()->getCard(d->serialNumber()); + if (!nksCard) { + d->error(i18n("Failed to find the NetKey card with the serial number: %1", QString::fromStdString(d->serialNumber()))); + d->dialog->close(); + finished(); + return; + } + + const std::vector pinStates = nksCard->pinStates(); + d->dialog->setNksPinPresent(pinStates.size() >= 1 && pinStates[0] != Card::NullPin); + d->dialog->setSigGPinPresent(pinStates.size() >= 3 && pinStates[2] != Card::NullPin); + d->ensureDialogVisible(); } void SetInitialPinCommand::doCancel() { if (d->dialog) { d->dialog->close(); } } #undef q_func #undef d_func #include "moc_setinitialpincommand.cpp" diff --git a/src/commands/setinitialpincommand.h b/src/commands/setinitialpincommand.h index 88da6e539..6c469ce1c 100644 --- a/src/commands/setinitialpincommand.h +++ b/src/commands/setinitialpincommand.h @@ -1,51 +1,51 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/setinitialpincommand.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef __KLEOPATRA_COMMANDS_SETINITIALPINCOMMAND_H__ #define __KLEOPATRA_COMMANDS_SETINITIALPINCOMMAND_H__ -#include +#include namespace Kleo { namespace Commands { -class SetInitialPinCommand : public Command +class SetInitialPinCommand : public CardCommand { Q_OBJECT public: - SetInitialPinCommand(); + SetInitialPinCommand(const std::string &serialNumber); ~SetInitialPinCommand() override; /* reimp */ static Restrictions restrictions() { return AnyCardHasNullPin; } QDialog *dialog() const; private: void doStart() override; void doCancel() override; private: class Private; inline Private *d_func(); inline const Private *d_func() const; Q_PRIVATE_SLOT(d_func(), void slotDialogRejected()) Q_PRIVATE_SLOT(d_func(), void slotDialogAccepted()) Q_PRIVATE_SLOT(d_func(), void slotNksPinRequested()) Q_PRIVATE_SLOT(d_func(), void slotSigGPinRequested()) }; } } #endif /* __KLEOPATRA_COMMANDS_SETINITIALPINCOMMAND_H__ */ diff --git a/src/smartcard/readerstatus.cpp b/src/smartcard/readerstatus.cpp index 8ccf6f3d7..fb465294f 100644 --- a/src/smartcard/readerstatus.cpp +++ b/src/smartcard/readerstatus.cpp @@ -1,848 +1,838 @@ /* -*- mode: c++; c-basic-offset:4 -*- smartcard/readerstatus.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "readerstatus.h" #include "keypairinfo.h" #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include "openpgpcard.h" #include "netkeycard.h" #include "pivcard.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utils/kdtoolsglobal.h" using namespace Kleo; using namespace Kleo::SmartCard; using namespace GpgME; static ReaderStatus *self = nullptr; #define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \ *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10)) #define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1)) static const char *flags[] = { "NOCARD", "PRESENT", "ACTIVE", "USABLE", }; static_assert(sizeof flags / sizeof * flags == Card::_NumScdStates, ""); static const char *prettyFlags[] = { "NoCard", "CardPresent", "CardActive", "CardUsable", "CardError", }; static_assert(sizeof prettyFlags / sizeof * prettyFlags == Card::NumStates, ""); #if 0 We need this once we have support for multiple readers in scdaemons interface. static unsigned int parseFileName(const QString &fileName, bool *ok) { QRegExp rx(QLatin1String("reader_(\\d+)\\.status")); if (ok) { *ok = false; } if (rx.exactMatch(QFileInfo(fileName).fileName())) { return rx.cap(1).toUInt(ok, 10); } return 0; } #endif Q_DECLARE_METATYPE(GpgME::Error) namespace { static QDebug operator<<(QDebug s, const std::vector< std::pair > &v) { typedef std::pair pair; s << '('; for (const pair &p : v) { s << "status(" << QString::fromStdString(p.first) << ") =" << QString::fromStdString(p.second) << '\n'; } return s << ')'; } static const char *app_types[] = { "_", // will hopefully never be used as an app-type :) "openpgp", "piv", "nks", "p15", "dinsig", "geldkarte", }; static_assert(sizeof app_types / sizeof * app_types == Card::NumAppTypes, ""); static Card::AppType parse_app_type(const std::string &s) { qCDebug(KLEOPATRA_LOG) << "parse_app_type(" << s.c_str() << ")"; const char **it = std::find_if(std::begin(app_types), std::end(app_types), [&s](const char *type) { return ::strcasecmp(s.c_str(), type) == 0; }); if (it == std::end(app_types)) { qCDebug(KLEOPATRA_LOG) << "App type not found"; return Card::UnknownApplication; } return static_cast(it - std::begin(app_types)); } static int parse_app_version(const std::string &s) { return std::atoi(s.c_str()); } static Card::PinState parse_pin_state(const QString &s) { bool ok; int i = s.toInt(&ok); if (!ok) { qCDebug(KLEOPATRA_LOG) << "Failed to parse pin state" << s; return Card::UnknownPinState; } switch (i) { case -4: return Card::NullPin; case -3: return Card::PinBlocked; case -2: return Card::NoPin; case -1: return Card::UnknownPinState; default: if (i < 0) { return Card::UnknownPinState; } else { return Card::PinOk; } } } template static std::unique_ptr gpgagent_transact(std::shared_ptr &gpgAgent, const char *command, std::unique_ptr transaction, Error &err) { qCDebug(KLEOPATRA_LOG) << "gpgagent_transact(" << command << ")"; err = gpgAgent->assuanTransact(command, std::move(transaction)); if (err.code()) { qCDebug(KLEOPATRA_LOG) << "gpgagent_transact(" << command << "):" << QString::fromLocal8Bit(err.asString()); if (err.code() >= GPG_ERR_ASS_GENERAL && err.code() <= GPG_ERR_ASS_UNKNOWN_INQUIRE) { qCDebug(KLEOPATRA_LOG) << "Assuan problem, killing context"; gpgAgent.reset(); } return std::unique_ptr(); } std::unique_ptr t = gpgAgent->takeLastAssuanTransaction(); return std::unique_ptr(dynamic_cast(t.release())); } static std::unique_ptr gpgagent_default_transact(std::shared_ptr &gpgAgent, const char *command, Error &err) { return gpgagent_transact(gpgAgent, command, std::unique_ptr(new DefaultAssuanTransaction), err); } static const std::string gpgagent_data(std::shared_ptr gpgAgent, const char *command, Error &err) { const std::unique_ptr t = gpgagent_default_transact(gpgAgent, command, err); if (t.get()) { qCDebug(KLEOPATRA_LOG) << "gpgagent_data(" << command << "): got" << QString::fromStdString(t->data()); return t->data(); } else { qCDebug(KLEOPATRA_LOG) << "gpgagent_data(" << command << "): t == NULL"; return std::string(); } } static const std::vector< std::pair > gpgagent_statuslines(std::shared_ptr gpgAgent, const char *what, Error &err) { const std::unique_ptr t = gpgagent_default_transact(gpgAgent, what, err); if (t.get()) { qCDebug(KLEOPATRA_LOG) << "agent_getattr_status(" << what << "): got" << t->statusLines(); return t->statusLines(); } else { qCDebug(KLEOPATRA_LOG) << "agent_getattr_status(" << what << "): t == NULL"; return std::vector >(); } } static const std::string gpgagent_status(const std::shared_ptr &gpgAgent, const char *what, Error &err) { const auto lines = gpgagent_statuslines (gpgAgent, what, err); // The status is only the last attribute // e.g. for SCD SERIALNO it would only be "SERIALNO" and for SCD GETATTR FOO // it would only be FOO const char *p = strrchr(what, ' '); const char *needle = (p + 1) ? (p + 1) : what; for (const auto &pair: lines) { if (pair.first == needle) { return pair.second; } } return std::string(); } static const std::string scd_getattr_status(std::shared_ptr &gpgAgent, const char *what, Error &err) { std::string cmd = "SCD GETATTR "; cmd += what; return gpgagent_status(gpgAgent, cmd.c_str(), err); } static const char * get_openpgp_card_manufacturer_from_serial_number(const std::string &serialno) { qCDebug(KLEOPATRA_LOG) << "get_openpgp_card_manufacturer_from_serial_number(" << serialno.c_str() << ")"; const bool isProperOpenPGPCardSerialNumber = serialno.size() == 32 && serialno.substr(0, 12) == "D27600012401"; if (isProperOpenPGPCardSerialNumber) { const char *sn = serialno.c_str(); const int manufacturerId = xtoi_2(sn + 16)*256 + xtoi_2(sn + 18); switch (manufacturerId) { case 0x0001: return "PPC Card Systems"; case 0x0002: return "Prism"; case 0x0003: return "OpenFortress"; case 0x0004: return "Wewid"; case 0x0005: return "ZeitControl"; case 0x0006: return "Yubico"; case 0x0007: return "OpenKMS"; case 0x0008: return "LogoEmail"; case 0x002A: return "Magrathea"; case 0x1337: return "Warsaw Hackerspace"; case 0xF517: return "FSIJ"; /* 0x0000 and 0xFFFF are defined as test cards per spec, 0xFF00 to 0xFFFE are assigned for use with randomly created serial numbers. */ case 0x0000: case 0xffff: return "test card"; default: return (manufacturerId & 0xff00) == 0xff00 ? "unmanaged S/N range" : "unknown"; } } else { return "unknown"; } } static const std::string get_manufacturer(std::shared_ptr &gpgAgent, Error &err) { // The result of SCD GETATTR MANUFACTURER is the manufacturer ID as unsigned number // optionally followed by the name of the manufacturer, e.g. // 6 Yubico // 65534 unmanaged S/N range const auto manufacturerIdAndName = scd_getattr_status(gpgAgent, "MANUFACTURER", err); if (err.code()) { if (err.code() == GPG_ERR_INV_NAME) { qCDebug(KLEOPATRA_LOG) << "get_manufacturer(): Querying for attribute MANUFACTURER not yet supported; needs GnuPG 2.2.21+"; } else { qCDebug(KLEOPATRA_LOG) << "get_manufacturer(): GpgME::Error(" << err.encodedError() << " (" << err.asString() << "))"; } return std::string(); } const auto startOfManufacturerName = manufacturerIdAndName.find(' '); if (startOfManufacturerName == std::string::npos) { // only ID without manufacturer name return "unknown"; } const auto manufacturerName = manufacturerIdAndName.substr(startOfManufacturerName + 1); return manufacturerName; } static void handle_openpgp_card(std::shared_ptr &ci, std::shared_ptr &gpg_agent) { Error err; auto ret = new OpenPGPCard(); ret->setSerialNumber(ci->serialNumber()); ret->setManufacturer(get_manufacturer(gpg_agent, err)); if (err.code()) { // fallback, e.g. if gpg does not yet support querying for the MANUFACTURER attribute ret->setManufacturer(get_openpgp_card_manufacturer_from_serial_number(ci->serialNumber())); } const auto info = gpgagent_statuslines(gpg_agent, "SCD LEARN --keypairinfo", err); if (err.code()) { ci->setStatus(Card::CardError); return; } ret->setKeyPairInfo(info); ci.reset(ret); } static void readKeyPairInfoFromPIVCard(const std::string &keyRef, PIVCard *pivCard, const std::shared_ptr &gpg_agent) { Error err; const std::string command = std::string("SCD READKEY --info-only -- ") + keyRef; const auto keyPairInfoLines = gpgagent_statuslines(gpg_agent, command.c_str(), err); if (err) { qCWarning(KLEOPATRA_LOG) << "readKeyPairInfoFromPIVCard(): Error on " << QString::fromStdString(command) << ":" << "GpgME::Error(" << err.encodedError() << " (" << err.asString() << "))"; return; } for (const auto &pair: keyPairInfoLines) { if (pair.first == "KEYPAIRINFO") { const KeyPairInfo info = KeyPairInfo::fromStatusLine(pair.second); if (info.grip.empty()) { qCWarning(KLEOPATRA_LOG) << "Invalid KEYPAIRINFO status line" << QString::fromStdString(pair.second); continue; } pivCard->setKeyAlgorithm(keyRef, info.algorithm); } else { qCWarning(KLEOPATRA_LOG) << "readKeyPairInfoFromPIVCard(): Unexpected status line on " << QString::fromStdString(command) << ":" << QString::fromStdString(pair.first) << QString::fromStdString(pair.second); } } } static void readCertificateFromPIVCard(const std::string &keyRef, PIVCard *pivCard, const std::shared_ptr &gpg_agent) { Error err; const std::string command = std::string("SCD READCERT ") + keyRef; const std::string certificateData = gpgagent_data(gpg_agent, command.c_str(), err); if (err && err.code() != GPG_ERR_NOT_FOUND) { qCWarning(KLEOPATRA_LOG) << "readCertificateFromPIVCard(" << QString::fromStdString(keyRef) << "): Error on " << QString::fromStdString(command) << ":" << "GpgME::Error(" << err.encodedError() << " (" << err.asString() << "))"; return; } if (certificateData.empty()) { qCDebug(KLEOPATRA_LOG) << "readCertificateFromPIVCard(" << QString::fromStdString(keyRef) << "): No certificate stored on card"; return; } qCDebug(KLEOPATRA_LOG) << "readCertificateFromPIVCard(" << QString::fromStdString(keyRef) << "): Found certificate stored on card"; pivCard->setCertificateData(keyRef, certificateData); } static void handle_piv_card(std::shared_ptr &ci, std::shared_ptr &gpg_agent) { Error err; auto pivCard = new PIVCard(); pivCard->setSerialNumber(ci->serialNumber()); const auto displaySerialnumber = scd_getattr_status(gpg_agent, "$DISPSERIALNO", err); if (err) { qCWarning(KLEOPATRA_LOG) << "handle_piv_card(): Error on GETATTR $DISPSERIALNO:" << "GpgME::Error(" << err.encodedError() << " (" << err.asString() << "))"; } pivCard->setDisplaySerialNumber(err ? ci->serialNumber() : displaySerialnumber); const auto info = gpgagent_statuslines(gpg_agent, "SCD LEARN --force", err); if (err) { ci->setStatus(Card::CardError); return; } pivCard->setCardInfo(info); for (const std::string &keyRef : PIVCard::supportedKeys()) { if (!pivCard->keyGrip(keyRef).empty()) { readKeyPairInfoFromPIVCard(keyRef, pivCard, gpg_agent); readCertificateFromPIVCard(keyRef, pivCard, gpg_agent); } } ci.reset(pivCard); } static void handle_netkey_card(std::shared_ptr &ci, std::shared_ptr &gpg_agent) { Error err; auto nkCard = new NetKeyCard(); nkCard->setSerialNumber(ci->serialNumber()); ci.reset(nkCard); ci->setAppVersion(parse_app_version(scd_getattr_status(gpg_agent, "NKS-VERSION", err))); if (err.code()) { qCDebug(KLEOPATRA_LOG) << "NKS-VERSION resulted in error" << err.asString(); ci->setErrorMsg(QStringLiteral ("NKS-VERSION failed: ") + QString::fromUtf8(err.asString())); return; } if (ci->appVersion() != 3) { qCDebug(KLEOPATRA_LOG) << "not a NetKey v3 card, giving up. Version:" << ci->appVersion(); ci->setErrorMsg(QStringLiteral("NetKey v%1 cards are not supported.").arg(ci->appVersion())); return; } // the following only works for NKS v3... const auto chvStatus = QString::fromStdString( scd_getattr_status(gpg_agent, "CHV-STATUS", err)).split(QLatin1Char(' ')); if (err.code()) { qCDebug(KLEOPATRA_LOG) << "no CHV-STATUS" << err.asString(); ci->setErrorMsg(QStringLiteral ("CHV-Status failed: ") + QString::fromUtf8(err.asString())); return; } std::vector states; states.reserve(chvStatus.count()); // CHV Status for NKS v3 is // Pin1 (Normal pin) Pin2 (Normal PUK) // SigG1 SigG PUK. int num = 0; for (const auto &state: chvStatus) { const auto parsed = parse_pin_state (state); states.push_back(parsed); if (parsed == Card::NullPin) { if (num == 0) { ci->setHasNullPin(true); } } ++num; } nkCard->setPinStates(states); // check for keys to learn: const std::unique_ptr result = gpgagent_default_transact(gpg_agent, "SCD LEARN --keypairinfo", err); if (err.code() || !result.get()) { if (err) { ci->setErrorMsg(QString::fromLatin1(err.asString())); } else { ci->setErrorMsg(QStringLiteral("Invalid internal state. No result.")); } return; } const std::vector keyPairInfos = result->statusLine("KEYPAIRINFO"); if (keyPairInfos.empty()) { return; } nkCard->setKeyPairInfo(keyPairInfos); } static std::shared_ptr get_card_status(unsigned int slot, std::shared_ptr &gpg_agent) { qCDebug(KLEOPATRA_LOG) << "get_card_status(" << slot << ',' << gpg_agent.get() << ')'; auto ci = std::shared_ptr (new Card()); if (slot != 0 || !gpg_agent) { // In the future scdaemon should support multiple slots but // not yet (2.1.18) return ci; } Error err; ci->setSerialNumber(gpgagent_status(gpg_agent, "SCD SERIALNO", err)); if (err.code() == GPG_ERR_CARD_NOT_PRESENT || err.code() == GPG_ERR_CARD_REMOVED) { ci->setStatus(Card::NoCard); return ci; } if (err.code()) { ci->setStatus(Card::CardError); return ci; } ci->setStatus(Card::CardPresent); const auto verbatimType = scd_getattr_status(gpg_agent, "APPTYPE", err); ci->setAppType(parse_app_type(verbatimType)); if (err.code()) { return ci; } // Handle different card types if (ci->appType() == Card::NksApplication) { qCDebug(KLEOPATRA_LOG) << "get_card_status: found Netkey card" << ci->serialNumber().c_str() << "end"; handle_netkey_card(ci, gpg_agent); return ci; } else if (ci->appType() == Card::OpenPGPApplication) { qCDebug(KLEOPATRA_LOG) << "get_card_status: found OpenPGP card" << ci->serialNumber().c_str() << "end"; handle_openpgp_card(ci, gpg_agent); return ci; } else if (ci->appType() == Card::PivApplication) { qCDebug(KLEOPATRA_LOG) << "get_card_status: found PIV card" << ci->serialNumber().c_str() << "end"; handle_piv_card(ci, gpg_agent); return ci; } else { qCDebug(KLEOPATRA_LOG) << "get_card_status: unhandled application:" << verbatimType.c_str(); return ci; } return ci; } static std::vector > update_cardinfo(std::shared_ptr &gpgAgent) { // Multiple smartcard readers are only supported internally by gnupg // but not by scdaemon (Status gnupg 2.1.18) // We still pretend that there can be multiple cards inserted // at once but we don't handle it yet. const auto ci = get_card_status(0, gpgAgent); return std::vector >(1, ci); } } // namespace struct Transaction { QByteArray command; QPointer receiver; const char *slot; AssuanTransaction* assuanTransaction; }; static const Transaction updateTransaction = { "__update__", nullptr, nullptr, nullptr }; static const Transaction quitTransaction = { "__quit__", nullptr, nullptr, nullptr }; namespace { class ReaderStatusThread : public QThread { Q_OBJECT public: explicit ReaderStatusThread(QObject *parent = nullptr) : QThread(parent), m_gnupgHomePath(Kleo::gnupgHomeDirectory()), m_transactions(1, updateTransaction) // force initial scan { connect(this, &ReaderStatusThread::oneTransactionFinished, this, &ReaderStatusThread::slotOneTransactionFinished); } std::vector > cardInfos() const { const QMutexLocker locker(&m_mutex); return m_cardInfos; } Card::Status cardStatus(unsigned int slot) const { const QMutexLocker locker(&m_mutex); if (slot < m_cardInfos.size()) { return m_cardInfos[slot]->status(); } else { return Card::NoCard; } } void addTransaction(const Transaction &t) { const QMutexLocker locker(&m_mutex); m_transactions.push_back(t); m_waitForTransactions.wakeOne(); } Q_SIGNALS: void firstCardWithNullPinChanged(const std::string &serialNumber); void anyCardCanLearnKeysChanged(bool); void cardChanged(unsigned int); void oneTransactionFinished(const GpgME::Error &err); public Q_SLOTS: void ping() { qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[GUI]::ping()"; addTransaction(updateTransaction); } void stop() { const QMutexLocker locker(&m_mutex); m_transactions.push_front(quitTransaction); m_waitForTransactions.wakeOne(); } private Q_SLOTS: void slotOneTransactionFinished(const GpgME::Error &err) { std::list ft; KDAB_SYNCHRONIZED(m_mutex) ft.splice(ft.begin(), m_finishedTransactions); Q_FOREACH (const Transaction &t, ft) if (t.receiver && t.slot && *t.slot) { QMetaObject::invokeMethod(t.receiver, t.slot, Qt::DirectConnection, Q_ARG(GpgME::Error, err)); } } private: void run() override { while (true) { std::shared_ptr gpgAgent; QByteArray command; bool nullSlot = false; AssuanTransaction* assuanTransaction = nullptr; std::list item; std::vector > oldCards; Error err; std::unique_ptr c = Context::createForEngine(AssuanEngine, &err); if (err.code() == GPG_ERR_NOT_SUPPORTED) { return; } gpgAgent = std::shared_ptr(c.release()); KDAB_SYNCHRONIZED(m_mutex) { while (m_transactions.empty()) { // go to sleep waiting for more work: qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[2nd]: waiting for commands"; m_waitForTransactions.wait(&m_mutex); } // splice off the first transaction without // copying, so we own it without really importing // it into this thread (the QPointer isn't // thread-safe): item.splice(item.end(), m_transactions, m_transactions.begin()); // make local copies of the interesting stuff so // we can release the mutex again: command = item.front().command; nullSlot = !item.front().slot; // we take ownership of the assuan transaction std::swap(assuanTransaction, item.front().assuanTransaction); oldCards = m_cardInfos; } qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[2nd]: new iteration command=" << command << " ; nullSlot=" << nullSlot; // now, let's see what we got: if (nullSlot && command == quitTransaction.command) { return; // quit } if ((nullSlot && command == updateTransaction.command)) { std::vector > newCards = update_cardinfo(gpgAgent); newCards.resize(std::max(newCards.size(), oldCards.size())); oldCards.resize(std::max(newCards.size(), oldCards.size())); KDAB_SYNCHRONIZED(m_mutex) m_cardInfos = newCards; std::vector >::const_iterator nit = newCards.begin(), nend = newCards.end(), oit = oldCards.begin(), oend = oldCards.end(); unsigned int idx = 0; bool anyLC = false; std::string firstCardWithNullPin; bool anyError = false; while (nit != nend && oit != oend) { const auto optr = (*oit).get(); const auto nptr = (*nit).get(); if ((optr && !nptr) || (!optr && nptr) || (optr && nptr && *optr != *nptr)) { qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[2nd]: slot" << idx << ": card Changed"; Q_EMIT cardChanged(idx); } if ((*nit)->canLearnKeys()) { anyLC = true; } if ((*nit)->hasNullPin() && firstCardWithNullPin.empty()) { firstCardWithNullPin = (*nit)->serialNumber(); } if ((*nit)->status() == Card::CardError) { anyError = true; } ++nit; ++oit; ++idx; } Q_EMIT firstCardWithNullPinChanged(firstCardWithNullPin); Q_EMIT anyCardCanLearnKeysChanged(anyLC); if (anyError) { gpgAgent.reset(); } } else { GpgME::Error err; if (assuanTransaction) { (void)gpgagent_transact(gpgAgent, command.constData(), std::unique_ptr(assuanTransaction), err); } else { (void)gpgagent_default_transact(gpgAgent, command.constData(), err); } KDAB_SYNCHRONIZED(m_mutex) // splice 'item' into m_finishedTransactions: m_finishedTransactions.splice(m_finishedTransactions.end(), item); Q_EMIT oneTransactionFinished(err); } } } private: mutable QMutex m_mutex; QWaitCondition m_waitForTransactions; const QString m_gnupgHomePath; // protected by m_mutex: std::vector > m_cardInfos; std::list m_transactions, m_finishedTransactions; }; } class ReaderStatus::Private : ReaderStatusThread { friend class Kleo::SmartCard::ReaderStatus; ReaderStatus *const q; public: explicit Private(ReaderStatus *qq) : ReaderStatusThread(qq), q(qq), watcher() { KDAB_SET_OBJECT_NAME(watcher); qRegisterMetaType("Kleo::SmartCard::Card::Status"); qRegisterMetaType("GpgME::Error"); watcher.whitelistFiles(QStringList(QStringLiteral("reader_*.status"))); watcher.addPath(Kleo::gnupgHomeDirectory()); watcher.setDelay(100); connect(this, &::ReaderStatusThread::cardChanged, q, &ReaderStatus::cardChanged); connect(this, &::ReaderStatusThread::firstCardWithNullPinChanged, q, &ReaderStatus::firstCardWithNullPinChanged); connect(this, &::ReaderStatusThread::anyCardCanLearnKeysChanged, q, &ReaderStatus::anyCardCanLearnKeysChanged); connect(&watcher, &FileSystemWatcher::triggered, this, &::ReaderStatusThread::ping); } ~Private() { stop(); if (!wait(100)) { terminate(); wait(); } } private: std::string firstCardWithNullPinImpl() const { const auto cis = cardInfos(); const auto firstWithNullPin = std::find_if(cis.cbegin(), cis.cend(), [](const std::shared_ptr &ci) { return ci->hasNullPin(); }); return firstWithNullPin != cis.cend() ? (*firstWithNullPin)->serialNumber() : std::string(); } bool anyCardCanLearnKeysImpl() const { const auto cis = cardInfos(); return std::any_of(cis.cbegin(), cis.cend(), [](const std::shared_ptr &ci) { return ci->canLearnKeys(); }); } private: FileSystemWatcher watcher; }; ReaderStatus::ReaderStatus(QObject *parent) : QObject(parent), d(new Private(this)) { self = this; qRegisterMetaType("std::string"); } ReaderStatus::~ReaderStatus() { self = nullptr; } // slot void ReaderStatus::startMonitoring() { d->start(); } // static ReaderStatus *ReaderStatus::mutableInstance() { return self; } // static const ReaderStatus *ReaderStatus::instance() { return self; } Card::Status ReaderStatus::cardStatus(unsigned int slot) const { return d->cardStatus(slot); } std::string ReaderStatus::firstCardWithNullPin() const { return d->firstCardWithNullPinImpl(); } bool ReaderStatus::anyCardCanLearnKeys() const { return d->anyCardCanLearnKeysImpl(); } -std::vector ReaderStatus::pinStates(unsigned int slot) const -{ - const auto ci = d->cardInfos(); - if (slot < ci.size()) { - return ci[slot]->pinStates(); - } else { - return std::vector(); - } -} - void ReaderStatus::startSimpleTransaction(const QByteArray &command, QObject *receiver, const char *slot) { const Transaction t = { command, receiver, slot, nullptr }; d->addTransaction(t); } void ReaderStatus::startTransaction(const QByteArray &command, QObject *receiver, const char *slot, std::unique_ptr transaction) { const Transaction t = { command, receiver, slot, transaction.release() }; d->addTransaction(t); } void ReaderStatus::updateStatus() { d->ping(); } std::vector > ReaderStatus::getCards() const { return d->cardInfos(); } #include "readerstatus.moc" diff --git a/src/smartcard/readerstatus.h b/src/smartcard/readerstatus.h index f7d3a03c1..fc18690f0 100644 --- a/src/smartcard/readerstatus.h +++ b/src/smartcard/readerstatus.h @@ -1,86 +1,84 @@ /* -*- mode: c++; c-basic-offset:4 -*- smartcard/readerstatus.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef __KLEOPATRA__SMARTCARD__READERSTATUS_H___ #define __KLEOPATRA__SMARTCARD__READERSTATUS_H___ #include #include #include "card.h" #include #include #include "kleopatra_debug.h" namespace GpgME { class AssuanTransaction; } namespace Kleo { namespace SmartCard { class ReaderStatus : public QObject { Q_OBJECT public: explicit ReaderStatus(QObject *parent = nullptr); ~ReaderStatus(); static const ReaderStatus *instance(); static ReaderStatus *mutableInstance(); void startSimpleTransaction(const QByteArray &cmd, QObject *receiver, const char *slot); void startTransaction(const QByteArray &cmd, QObject *receiver, const char *slot, std::unique_ptr transaction); Card::Status cardStatus(unsigned int slot) const; std::string firstCardWithNullPin() const; bool anyCardCanLearnKeys() const; - std::vector pinStates(unsigned int slot) const; - std::vector > getCards() const; template std::shared_ptr getCard(const std::string &serialNumber) const { for (const auto &card: getCards()) { if (card->serialNumber() == serialNumber) { qCDebug(KLEOPATRA_LOG) << "ReaderStatus::getCard() - Found card with serial number" << QString::fromStdString(serialNumber); return std::dynamic_pointer_cast(card); } } qCDebug(KLEOPATRA_LOG) << "ReaderStatus::getCard() - Did not find card with serial number" << QString::fromStdString(serialNumber); return std::shared_ptr(); } public Q_SLOTS: void updateStatus(); void startMonitoring(); Q_SIGNALS: void firstCardWithNullPinChanged(const std::string &serialNumber); void anyCardCanLearnKeysChanged(bool); void cardChanged(unsigned int slot); private: class Private; std::shared_ptr d; }; } // namespace SmartCard } // namespace Kleo Q_DECLARE_METATYPE(Kleo::SmartCard::Card::Status) #endif /* __KLEOPATRA__SMARTCARD__READERSTATUS_H___ */ diff --git a/src/systrayicon.cpp b/src/systrayicon.cpp index b1edd741b..f59bae43f 100644 --- a/src/systrayicon.cpp +++ b/src/systrayicon.cpp @@ -1,254 +1,256 @@ /* -*- mode: c++; c-basic-offset:4 -*- systemtrayicon.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "systrayicon.h" #ifndef QT_NO_SYSTEMTRAYICON #include "mainwindow.h" #include "kleopatraapplication.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::SmartCard; class SysTrayIcon::Private { friend class ::SysTrayIcon; SysTrayIcon *const q; public: explicit Private(SysTrayIcon *qq); ~Private(); private: void slotAbout() { if (!aboutDialog) { aboutDialog = new KAboutApplicationDialog(KAboutData::applicationData()); aboutDialog->setAttribute(Qt::WA_DeleteOnClose); } if (aboutDialog->isVisible()) { aboutDialog->raise(); } else { aboutDialog->show(); } } void enableDisableActions() { //work around a Qt bug (seen with Qt 4.4.0, Windows): QClipBoard->mimeData() triggers QClipboard::changed(), //triggering slotEnableDisableActions again const QSignalBlocker blocker(QApplication::clipboard()); openCertificateManagerAction.setEnabled(!q->mainWindow() || !q->mainWindow()->isVisible()); setInitialPinAction.setEnabled(!firstCardWithNullPin.empty()); learnCertificatesAction.setEnabled(anyCardCanLearnKeys); q->setAttentionWanted((!firstCardWithNullPin.empty() || anyCardCanLearnKeys) && !q->attentionWindow()); } void slotSetInitialPin() { - SetInitialPinCommand *cmd = new SetInitialPinCommand; - q->setAttentionWindow(cmd->dialog()); - startCommand(cmd); + if (!firstCardWithNullPin.empty()) { + SetInitialPinCommand *cmd = new SetInitialPinCommand(firstCardWithNullPin); + q->setAttentionWindow(cmd->dialog()); + startCommand(cmd); + } } void slotLearnCertificates() { LearnCardKeysCommand *cmd = new LearnCardKeysCommand(GpgME::CMS); q->setAttentionWindow(cmd->dialog()); startCommand(cmd); } void startCommand(Command *cmd) { Q_ASSERT(cmd); cmd->setParent(q->mainWindow()); cmd->start(); } private: std::string firstCardWithNullPin; bool anyCardCanLearnKeys = false; bool learningInProgress = false; QMenu menu; QAction openCertificateManagerAction; QAction configureAction; QAction aboutAction; QAction quitAction; ClipboardMenu clipboardMenu; QMenu cardMenu; QAction updateCardStatusAction; QAction setInitialPinAction; QAction learnCertificatesAction; QPointer aboutDialog; QEventLoopLocker eventLoopLocker; }; SysTrayIcon::Private::Private(SysTrayIcon *qq) : q(qq), menu(), openCertificateManagerAction(i18n("&Open Certificate Manager..."), q), configureAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("&Configure %1...", KAboutData::applicationData().componentName()), q), aboutAction(QIcon::fromTheme(QStringLiteral("kleopatra")), i18n("&About %1...", KAboutData::applicationData().componentName()), q), quitAction(QIcon::fromTheme(QStringLiteral("application-exit")),i18n("&Shutdown Kleopatra"), q), clipboardMenu(q), cardMenu(i18n("SmartCard")), updateCardStatusAction(i18n("Update Card Status"), q), setInitialPinAction(i18n("Set NetKey v3 Initial PIN..."), q), learnCertificatesAction(i18n("Learn NetKey v3 Card Certificates"), q), aboutDialog() { q->setNormalIcon(QIcon::fromTheme(QStringLiteral("kleopatra"))); q->setAttentionIcon(QIcon::fromTheme(QStringLiteral("secure-card"))); KDAB_SET_OBJECT_NAME(menu); KDAB_SET_OBJECT_NAME(openCertificateManagerAction); KDAB_SET_OBJECT_NAME(configureAction); KDAB_SET_OBJECT_NAME(aboutAction); KDAB_SET_OBJECT_NAME(quitAction); KDAB_SET_OBJECT_NAME(clipboardMenu); KDAB_SET_OBJECT_NAME(cardMenu); KDAB_SET_OBJECT_NAME(setInitialPinAction); KDAB_SET_OBJECT_NAME(learnCertificatesAction); connect(&openCertificateManagerAction, SIGNAL(triggered()), qApp, SLOT(openOrRaiseMainWindow())); connect(&configureAction, SIGNAL(triggered()), qApp, SLOT(openOrRaiseConfigDialog())); connect(&aboutAction, SIGNAL(triggered()), q, SLOT(slotAbout())); connect(&quitAction, &QAction::triggered, QCoreApplication::instance(), &QCoreApplication::quit); connect(&updateCardStatusAction, &QAction::triggered, ReaderStatus::instance(), &ReaderStatus::updateStatus); connect(&setInitialPinAction, SIGNAL(triggered()), q, SLOT(slotSetInitialPin())); connect(&learnCertificatesAction, SIGNAL(triggered()), q, SLOT(slotLearnCertificates())); connect(QApplication::clipboard(), SIGNAL(changed(QClipboard::Mode)), q, SLOT(slotEnableDisableActions())); menu.addAction(&openCertificateManagerAction); menu.addAction(&configureAction); menu.addAction(&aboutAction); menu.addSeparator(); menu.addMenu(clipboardMenu.clipboardMenu()->menu()); menu.addSeparator(); menu.addMenu(&cardMenu); cardMenu.addAction(&updateCardStatusAction); cardMenu.addAction(&setInitialPinAction); cardMenu.addAction(&learnCertificatesAction); menu.addSeparator(); menu.addAction(&quitAction); q->setContextMenu(&menu); clipboardMenu.setMainWindow(q->mainWindow()); } SysTrayIcon::Private::~Private() {} SysTrayIcon::SysTrayIcon(QObject *p) : SystemTrayIcon(p), d(new Private(this)) { slotEnableDisableActions(); } SysTrayIcon::~SysTrayIcon() { } MainWindow *SysTrayIcon::mainWindow() const { return static_cast(SystemTrayIcon::mainWindow()); } QDialog *SysTrayIcon::attentionWindow() const { return static_cast(SystemTrayIcon::attentionWindow()); } void SysTrayIcon::doActivated() { if (const QWidget *const aw = attentionWindow()) if (aw->isVisible()) { return; // ignore clicks while an attention window is open. } if (!d->firstCardWithNullPin.empty()) { d->slotSetInitialPin(); } else if (d->anyCardCanLearnKeys) { d->slotLearnCertificates(); } else { // Toggle visibility of MainWindow KleopatraApplication::instance()->toggleMainWindowVisibility(); } } void SysTrayIcon::setFirstCardWithNullPin(const std::string &serialNumber) { if (d->firstCardWithNullPin == serialNumber) { return; } d->firstCardWithNullPin = serialNumber; slotEnableDisableActions(); } void SysTrayIcon::setAnyCardCanLearnKeys(bool on) { if (d->anyCardCanLearnKeys == on || d->learningInProgress) { return; } d->anyCardCanLearnKeys = on; slotEnableDisableActions(); } void SysTrayIcon::slotEnableDisableActions() { d->enableDisableActions(); } /* We need this as the readerstatus might update even * while the loading is in progress. */ void SysTrayIcon::setLearningInProgress(bool value) { if (value) { setAnyCardCanLearnKeys(false); } d->learningInProgress = value; } #include "moc_systrayicon.cpp" #endif // QT_NO_SYSTEMTRAYICON