diff --git a/lang/cpp/src/editinteractor.cpp b/lang/cpp/src/editinteractor.cpp index 707bf8b9..373a4464 100644 --- a/lang/cpp/src/editinteractor.cpp +++ b/lang/cpp/src/editinteractor.cpp @@ -1,383 +1,386 @@ /* editinteractor.cpp - Interface for edit interactors Copyright (C) 2007 Klarälvdalens Datakonsult AB 2016 Bundesamt für Sicherheit in der Informationstechnik Software engineering by Intevation GmbH This file is part of GPGME++. GPGME++ is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. GPGME++ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with GPGME++; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "editinteractor.h" #include "callbacks.h" #include "error.h" #include "util.h" #include #ifdef _WIN32 # include #include #else # include #endif #include #include #include #ifndef GPG_ERR_ALREADY_SIGNED # define GPG_ERR_ALREADY_SIGNED GPG_ERR_USER_1 #endif using namespace GpgME; static const char *status_to_string(unsigned int status); static Error status_to_error(unsigned int status); class EditInteractor::Private { friend class ::GpgME::EditInteractor; friend class ::GpgME::CallbackHelper; EditInteractor *const q; public: explicit Private(EditInteractor *qq); ~Private(); private: unsigned int state = StartState; Error error; std::FILE *debug = nullptr; bool debugNeedsClosing = false; }; class GpgME::CallbackHelper { private: static int writeAll(int fd, const void *buf, size_t count) { size_t toWrite = count; while (toWrite > 0) { const int n = gpgme_io_write(fd, buf, toWrite); if (n < 0) { return n; } toWrite -= n; } return count; } public: static int edit_interactor_callback_impl(void *opaque, gpgme_status_code_t status, const char *args, int fd) { EditInteractor::Private *ei = (EditInteractor::Private *)opaque; Error err = status_to_error(status); if (!err) { // advance to next state based on input: const unsigned int oldState = ei->state; if (ei->q->needsNoResponse(status)) { // keep state + } else if (status == GPGME_STATUS_ERROR) { + err = ei->q->parseStatusError(args); + ei->state = EditInteractor::ErrorState; } else { ei->state = ei->q->nextState(status, args, err); } if (ei->debug) { std::fprintf(ei->debug, "EditInteractor: %u -> nextState( %s, %s ) -> %u\n", oldState, status_to_string(status), args ? args : "", ei->state); } if (err || err.isCanceled()) { ei->state = oldState; goto error; } if (ei->state != oldState && // if there was an error from before, we stop here (### this looks weird, can this happen at all?) ei->error.code() == GPG_ERR_NO_ERROR) { // successful state change -> call action if (const char *const result = ei->q->action(err)) { if (err) { goto error; } if (ei->debug) { std::fprintf(ei->debug, "EditInteractor: action result \"%s\"\n", result); } // if there's a result, write it: if (*result) { gpgme_err_set_errno(0); const ssize_t len = std::strlen(result); if (writeAll(fd, result, len) != len) { err = Error::fromSystemError(); if (ei->debug) { std::fprintf(ei->debug, "EditInteractor: Could not write to fd %d (%s)\n", fd, err.asString()); } goto error; } } gpgme_err_set_errno(0); if (writeAll(fd, "\n", 1) != 1) { err = Error::fromSystemError(); if (ei->debug) { std::fprintf(ei->debug, "EditInteractor: Could not write to fd %d (%s)\n", fd, err.asString()); } goto error; } } else { if (err) { goto error; } if (ei->debug) { std::fprintf(ei->debug, "EditInteractor: no action result\n"); } } } else { if (ei->debug) { std::fprintf(ei->debug, "EditInteractor: no action executed\n"); } } } error: if (err || err.isCanceled()) { ei->error = err; ei->state = EditInteractor::ErrorState; } if (ei->debug) { std::fprintf(ei->debug, "EditInteractor: error now %u (%s)\n", ei->error.encodedError(), gpgme_strerror(ei->error.encodedError())); } return ei->error.encodedError(); } }; static gpgme_error_t edit_interactor_callback(void *opaque, gpgme_status_code_t status, const char *args, int fd) { return CallbackHelper::edit_interactor_callback_impl(opaque, status, args, fd); } const gpgme_edit_cb_t GpgME::edit_interactor_callback = ::edit_interactor_callback; EditInteractor::Private::Private(EditInteractor *qq) : q(qq) { const char *debug_env = std::getenv("GPGMEPP_INTERACTOR_DEBUG"); if (!debug_env) { return; } if (!strcmp(debug_env, "stdout")) { debug = stdout; } else if (!strcmp(debug_env, "stderr")) { debug = stderr; } else if (debug_env) { debug = std::fopen(debug_env, "a+"); debugNeedsClosing = true; } } EditInteractor::Private::~Private() { if (debug && debugNeedsClosing) { std::fclose(debug); } } EditInteractor::EditInteractor() : d(new Private(this)) { } EditInteractor::~EditInteractor() { delete d; } unsigned int EditInteractor::state() const { return d->state; } Error EditInteractor::lastError() const { return d->error; } bool EditInteractor::needsNoResponse(unsigned int status) const { switch (status) { case GPGME_STATUS_ALREADY_SIGNED: case GPGME_STATUS_ERROR: case GPGME_STATUS_GET_BOOL: case GPGME_STATUS_GET_LINE: case GPGME_STATUS_KEY_CREATED: case GPGME_STATUS_NEED_PASSPHRASE_SYM: case GPGME_STATUS_SC_OP_FAILURE: case GPGME_STATUS_CARDCTRL: case GPGME_STATUS_BACKUP_KEY_CREATED: return false; default: return true; } } // static Error status_to_error(unsigned int status) { switch (status) { case GPGME_STATUS_MISSING_PASSPHRASE: return Error::fromCode(GPG_ERR_NO_PASSPHRASE); case GPGME_STATUS_ALREADY_SIGNED: return Error::fromCode(GPG_ERR_ALREADY_SIGNED); case GPGME_STATUS_SIGEXPIRED: return Error::fromCode(GPG_ERR_SIG_EXPIRED); } return Error(); } void EditInteractor::setDebugChannel(std::FILE *debug) { d->debug = debug; } GpgME::Error EditInteractor::parseStatusError(const char *args) { Error err; const auto fields = split(args, ' '); if (fields.size() >= 2) { err = Error{static_cast(std::stoul(fields[1]))}; } else { err = Error::fromCode(GPG_ERR_GENERAL); } return err; } static const char *const status_strings[] = { "EOF", /* mkstatus processing starts here */ "ENTER", "LEAVE", "ABORT", "GOODSIG", "BADSIG", "ERRSIG", "BADARMOR", "RSA_OR_IDEA", "KEYEXPIRED", "KEYREVOKED", "TRUST_UNDEFINED", "TRUST_NEVER", "TRUST_MARGINAL", "TRUST_FULLY", "TRUST_ULTIMATE", "SHM_INFO", "SHM_GET", "SHM_GET_BOOL", "SHM_GET_HIDDEN", "NEED_PASSPHRASE", "VALIDSIG", "SIG_ID", "ENC_TO", "NODATA", "BAD_PASSPHRASE", "NO_PUBKEY", "NO_SECKEY", "NEED_PASSPHRASE_SYM", "DECRYPTION_FAILED", "DECRYPTION_OKAY", "MISSING_PASSPHRASE", "GOOD_PASSPHRASE", "GOODMDC", "BADMDC", "ERRMDC", "IMPORTED", "IMPORT_OK", "IMPORT_PROBLEM", "IMPORT_RES", "FILE_START", "FILE_DONE", "FILE_ERROR", "BEGIN_DECRYPTION", "END_DECRYPTION", "BEGIN_ENCRYPTION", "END_ENCRYPTION", "DELETE_PROBLEM", "GET_BOOL", "GET_LINE", "GET_HIDDEN", "GOT_IT", "PROGRESS", "SIG_CREATED", "SESSION_KEY", "NOTATION_NAME", "NOTATION_DATA", "POLICY_URL", "BEGIN_STREAM", "END_STREAM", "KEY_CREATED", "USERID_HINT", "UNEXPECTED", "INV_RECP", "NO_RECP", "ALREADY_SIGNED", "SIGEXPIRED", "EXPSIG", "EXPKEYSIG", "TRUNCATED", "ERROR", "NEWSIG", "REVKEYSIG", "SIG_SUBPACKET", "NEED_PASSPHRASE_PIN", "SC_OP_FAILURE", "SC_OP_SUCCESS", "CARDCTRL", "BACKUP_KEY_CREATED", "PKA_TRUST_BAD", "PKA_TRUST_GOOD", "PLAINTEXT", }; static const unsigned int num_status_strings = sizeof status_strings / sizeof * status_strings ; const char *status_to_string(unsigned int idx) { if (idx < num_status_strings) { return status_strings[idx]; } else { return "(unknown)"; } } diff --git a/lang/cpp/src/gpgrevokekeyeditinteractor.cpp b/lang/cpp/src/gpgrevokekeyeditinteractor.cpp index fd584099..fda70f24 100644 --- a/lang/cpp/src/gpgrevokekeyeditinteractor.cpp +++ b/lang/cpp/src/gpgrevokekeyeditinteractor.cpp @@ -1,212 +1,208 @@ /* gpgrevokekeyeditinteractor.cpp - Edit Interactor to revoke own OpenPGP keys Copyright (c) 2022 g10 Code GmbH Software engineering by Ingo Klöcker This file is part of GPGME++. GPGME++ is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. GPGME++ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with GPGME++; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "gpgrevokekeyeditinteractor.h" #include "error.h" #include #include #include // avoid conflict (msvc) #ifdef ERROR # undef ERROR #endif using namespace GpgME; class GpgRevokeKeyEditInteractor::Private { enum { START = EditInteractor::StartState, COMMAND, CONFIRM_REVOKING_ENTIRE_KEY, REASON_CODE, REASON_TEXT, // all these free slots belong to REASON_TEXT, too; we increase state() // by one for each line of text, so that action() is called REASON_TEXT_DONE = REASON_TEXT + 1000, CONFIRM_REASON, QUIT, CONFIRM_SAVE, ERROR = EditInteractor::ErrorState }; GpgRevokeKeyEditInteractor *const q = nullptr; public: Private(GpgRevokeKeyEditInteractor *q) : q{q} , reasonCode{"0"} { } const char *action(Error &err) const; unsigned int nextState(unsigned int statusCode, const char *args, Error &err); std::string reasonCode; std::vector reasonLines; int nextLine = -1; }; const char *GpgRevokeKeyEditInteractor::Private::action(Error &err) const { switch (const auto state = q->state()) { case COMMAND: return "revkey"; case CONFIRM_REVOKING_ENTIRE_KEY: return "Y"; case REASON_CODE: return reasonCode.c_str(); case REASON_TEXT_DONE: return ""; case CONFIRM_REASON: return "Y"; case QUIT: return "quit"; case CONFIRM_SAVE: return "Y"; case START: return nullptr; default: if (state >= REASON_TEXT && state < REASON_TEXT_DONE) { return reasonLines[nextLine].c_str(); } // fall through case ERROR: err = Error::fromCode(GPG_ERR_GENERAL); return nullptr; } } unsigned int GpgRevokeKeyEditInteractor::Private::nextState(unsigned int status, const char *args, Error &err) { using std::strcmp; static const Error GENERAL_ERROR = Error::fromCode(GPG_ERR_GENERAL); - if (status == GPGME_STATUS_ERROR) { - err = q->parseStatusError(args); - return ERROR; - } switch (const auto state = q->state()) { case START: if (status == GPGME_STATUS_GET_LINE && strcmp(args, "keyedit.prompt") == 0) { return COMMAND; } err = GENERAL_ERROR; return ERROR; case COMMAND: if (status == GPGME_STATUS_GET_BOOL && strcmp(args, "keyedit.revoke.subkey.okay") == 0) { return CONFIRM_REVOKING_ENTIRE_KEY; } err = GENERAL_ERROR; return ERROR; case CONFIRM_REVOKING_ENTIRE_KEY: if (status == GPGME_STATUS_GET_LINE && strcmp(args, "ask_revocation_reason.code") == 0) { return REASON_CODE; } err = GENERAL_ERROR; return ERROR; case REASON_CODE: if (status == GPGME_STATUS_GET_LINE && strcmp(args, "ask_revocation_reason.text") == 0) { nextLine++; return nextLine < reasonLines.size() ? REASON_TEXT : REASON_TEXT_DONE; } err = GENERAL_ERROR; return ERROR; default: if (state >= REASON_TEXT && state < REASON_TEXT_DONE) { if (status == GPGME_STATUS_GET_LINE && strcmp(args, "ask_revocation_reason.text") == 0) { nextLine++; return nextLine < reasonLines.size() ? state + 1 : REASON_TEXT_DONE; } } err = GENERAL_ERROR; return ERROR; case REASON_TEXT_DONE: if (status == GPGME_STATUS_GET_BOOL && strcmp(args, "ask_revocation_reason.okay") == 0) { return CONFIRM_REASON; } err = GENERAL_ERROR; return ERROR; case CONFIRM_REASON: if (status == GPGME_STATUS_GET_LINE && strcmp(args, "keyedit.prompt") == 0) { return QUIT; } err = GENERAL_ERROR; return ERROR; case QUIT: if (status == GPGME_STATUS_GET_BOOL && strcmp(args, "keyedit.save.okay") == 0) { return CONFIRM_SAVE; } err = GENERAL_ERROR; return ERROR; case ERROR: if (status == GPGME_STATUS_GET_LINE && strcmp(args, "keyedit.prompt") == 0) { return QUIT; } err = q->lastError(); return ERROR; } } GpgRevokeKeyEditInteractor::GpgRevokeKeyEditInteractor() : EditInteractor{} , d{new Private{this}} { } GpgRevokeKeyEditInteractor::~GpgRevokeKeyEditInteractor() = default; void GpgRevokeKeyEditInteractor::setReason(RevocationReason reason, const std::vector &description) { d->reasonCode = std::to_string(static_cast(reason)); d->reasonLines = description; } const char *GpgRevokeKeyEditInteractor::action(Error &err) const { return d->action(err); } unsigned int GpgRevokeKeyEditInteractor::nextState(unsigned int status, const char *args, Error &err) const { return d->nextState(status, args, err); }