diff --git a/src/uiserver/uiserver.cpp b/src/uiserver/uiserver.cpp index d9746f0be..ab4d2ca71 100644 --- a/src/uiserver/uiserver.cpp +++ b/src/uiserver/uiserver.cpp @@ -1,282 +1,297 @@ /* -*- mode: c++; c-basic-offset:4 -*- uiserver/uiserver.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 "uiserver.h" #include "uiserver_p.h" #include "sessiondata.h" #include #include #include #include #include "kleopatra_debug.h" #include +#include + #include #include #include #include #include #include #include using namespace Kleo; // static void UiServer::setLogStream(FILE *stream) { assuan_set_assuan_log_stream(stream); } UiServer::Private::Private(UiServer *qq) : QTcpServer(), q(qq), file(), factories(), connections(), suggestedSocketName(), actualSocketName(), cryptoCommandsEnabled(false) { #ifndef HAVE_ASSUAN2 assuan_set_assuan_err_source(GPG_ERR_SOURCE_DEFAULT); #else assuan_set_gpg_err_source(GPG_ERR_SOURCE_DEFAULT); assuan_sock_init(); #endif } bool UiServer::Private::isStaleAssuanSocket(const QString &fileName) { assuan_context_t ctx = nullptr; #ifndef HAVE_ASSUAN2 const bool error = assuan_socket_connect_ext(&ctx, QFile::encodeName(fileName).constData(), -1, 0); #else const bool error = assuan_new(&ctx) || assuan_socket_connect(ctx, QFile::encodeName(fileName).constData(), ASSUAN_INVALID_PID, 0); #endif if (!error) #ifndef HAVE_ASSUAN2 assuan_disconnect(ctx); #else assuan_release(ctx); #endif return error; } UiServer::UiServer(const QString &socket, QObject *p) : QObject(p), d(new Private(this)) { d->suggestedSocketName = d->makeFileName(socket); } UiServer::~UiServer() { if (QFile::exists(d->actualSocketName)) { QFile::remove(d->actualSocketName); } } namespace { using Iterator = std::vector>::iterator; static bool empty(std::pair iters) { return iters.first == iters.second; } } bool UiServer::registerCommandFactory(const std::shared_ptr &cf) { if (cf && empty(std::equal_range(d->factories.begin(), d->factories.end(), cf, _detail::ByName()))) { d->factories.push_back(cf); std::inplace_merge(d->factories.begin(), d->factories.end() - 1, d->factories.end(), _detail::ByName()); return true; } else { if (!cf) { qCWarning(KLEOPATRA_LOG) << "NULL factory"; } else { qCWarning(KLEOPATRA_LOG) << (void *)cf.get() << " factory already registered"; } return false; } } void UiServer::start() { d->makeListeningSocket(); } void UiServer::stop() { d->close(); if (d->file.exists()) { d->file.remove(); } if (isStopped()) { SessionDataHandler::instance()->clear(); Q_EMIT stopped(); } } void UiServer::enableCryptoCommands(bool on) { if (on == d->cryptoCommandsEnabled) { return; } d->cryptoCommandsEnabled = on; std::for_each(d->connections.cbegin(), d->connections.cend(), [on](std::shared_ptr conn) { conn->enableCryptoCommands(on); }); } QString UiServer::socketName() const { return d->actualSocketName; } bool UiServer::waitForStopped(unsigned int ms) { if (isStopped()) { return true; } QEventLoop loop; QTimer timer; timer.setInterval(ms); timer.setSingleShot(true); connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); connect(this, &UiServer::stopped, &loop, &QEventLoop::quit); loop.exec(); return !timer.isActive(); } bool UiServer::isStopped() const { return d->connections.empty() && !d->isListening(); } bool UiServer::isStopping() const { return !d->connections.empty() && !d->isListening(); } void UiServer::Private::slotConnectionClosed(Kleo::AssuanServerConnection *conn) { qCDebug(KLEOPATRA_LOG) << "UiServer: connection " << (void *)conn << " closed"; connections.erase(std::remove_if(connections.begin(), connections.end(), [conn](const std::shared_ptr &other) { return conn == other.get(); }), connections.end()); if (q->isStopped()) { SessionDataHandler::instance()->clear(); Q_EMIT q->stopped(); } } void UiServer::Private::incomingConnection(qintptr fd) { try { qCDebug(KLEOPATRA_LOG) << "UiServer: client connect on fd " << fd; #if defined(HAVE_ASSUAN_SOCK_GET_NONCE) || defined(HAVE_ASSUAN2) if (assuan_sock_check_nonce((assuan_fd_t)fd, &nonce)) { qCDebug(KLEOPATRA_LOG) << "UiServer: nonce check failed"; assuan_sock_close((assuan_fd_t)fd); return; } #endif const std::shared_ptr c(new AssuanServerConnection((assuan_fd_t)fd, factories)); connect(c.get(), &AssuanServerConnection::closed, this, &Private::slotConnectionClosed); connect(c.get(), &AssuanServerConnection::startKeyManagerRequested, q, &UiServer::startKeyManagerRequested, Qt::QueuedConnection); connect(c.get(), &AssuanServerConnection::startConfigDialogRequested, q, &UiServer::startConfigDialogRequested, Qt::QueuedConnection); c->enableCryptoCommands(cryptoCommandsEnabled); connections.push_back(c); qCDebug(KLEOPATRA_LOG) << "UiServer: client connection " << (void *)c.get() << " established successfully"; } catch (const Exception &e) { qCDebug(KLEOPATRA_LOG) << "UiServer: client connection failed: " << e.what(); QTcpSocket s; s.setSocketDescriptor(fd); QTextStream(&s) << "ERR " << e.error_code() << " " << e.what() << "\r\n"; s.waitForBytesWritten(); s.close(); } catch (...) { qCDebug(KLEOPATRA_LOG) << "UiServer: client connection failed: unknown exception caught"; // this should never happen... QTcpSocket s; s.setSocketDescriptor(fd); QTextStream(&s) << "ERR 63 unknown exception caught\r\n"; s.waitForBytesWritten(); s.close(); } } QString UiServer::Private::makeFileName(const QString &socket) const { if (!socket.isEmpty()) { return socket; } + const QString socketPath{QString::fromUtf8(GpgME::dirInfo("uiserver-socket"))}; + if (!socketPath.isEmpty()) { + // Note: The socket directory exists after GpgME::dirInfo() has been called. + return socketPath; + } + // GPGME (or GnuPG) is too old to return the socket path. + // In this case we fallback to assume that the socket directory is + // the home directory as we did in the past. This is not correct but + // probably the safest fallback we can do despite that it is a + // bug to assume the socket directory in the home directory. See + // https://dev.gnupg.org/T5613 const QString gnupgHome = gnupgHomeDirectory(); if (gnupgHome.isEmpty()) { throw_(i18n("Could not determine the GnuPG home directory. Consider setting the GNUPGHOME environment variable.")); } + // We should not create the home directory, but this only happens for very + // old and long unsupported versions of gnupg. ensureDirectoryExists(gnupgHome); const QDir dir(gnupgHome); Q_ASSERT(dir.exists()); return dir.absoluteFilePath(QStringLiteral("S.uiserver")); } void UiServer::Private::ensureDirectoryExists(const QString &path) const { const QFileInfo info(path); if (info.exists() && !info.isDir()) { throw_(i18n("Cannot determine the GnuPG home directory: %1 exists but is not a directory.", path)); } if (info.exists()) { return; } const QDir dummy; //there is no static QDir::mkpath()... errno = 0; if (!dummy.mkpath(path)) { throw_(i18n("Could not create GnuPG home directory %1: %2", path, systemErrorString())); } } void UiServer::Private::makeListeningSocket() { // First, create a file (we do this only for the name, gmpfh) const QString fileName = suggestedSocketName; if (QFile::exists(fileName)) { if (isStaleAssuanSocket(fileName)) { QFile::remove(fileName); } else { throw_(i18n("Detected another running gnupg UI server listening at %1.", fileName)); } } doMakeListeningSocket(fileName.toUtf8()); actualSocketName = suggestedSocketName; } #include "moc_uiserver_p.cpp"