File indexing completed on 2025-01-26 04:52:43
0001 /* -*- mode: c++; c-basic-offset:4 -*- 0002 command.cpp 0003 0004 This file is part of KleopatraClient, the Kleopatra interface library 0005 SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 0010 #include <config-kleopatra.h> 0011 0012 #include "command.h" 0013 #include "command_p.h" 0014 0015 #include <QtSystemDetection> // Q_OS_WIN 0016 0017 #ifdef Q_OS_WIN // HACK: AllowSetForegroundWindow needs _WIN32_WINDOWS >= 0x0490 set 0018 #ifndef _WIN32_WINDOWS 0019 #define _WIN32_WINDOWS 0x0500 0020 #endif 0021 #ifndef _WIN32_WINNT 0022 #define _WIN32_WINNT 0x0500 // good enough for Vista too 0023 #endif 0024 #include <utils/gnupg-registry.h> 0025 #include <windows.h> 0026 #endif 0027 0028 #include "libkleopatraclientcore_debug.h" 0029 #include <KLocalizedString> 0030 #include <QDir> 0031 #include <QFile> 0032 #include <QMutexLocker> 0033 #include <QProcess> 0034 0035 #include <assuan.h> 0036 #include <gpg-error.h> 0037 #include <gpgme++/error.h> 0038 0039 #include <algorithm> 0040 #include <memory> 0041 #include <sstream> 0042 #include <string> 0043 #include <type_traits> 0044 0045 using namespace KleopatraClientCopy; 0046 0047 // copied from kleopatra/utils/hex.cpp 0048 static std::string hexencode(const std::string &in) 0049 { 0050 std::string result; 0051 result.reserve(3 * in.size()); 0052 0053 static const char hex[] = "0123456789ABCDEF"; 0054 0055 for (std::string::const_iterator it = in.begin(), end = in.end(); it != end; ++it) 0056 switch (const unsigned char ch = *it) { 0057 default: 0058 if ((ch >= '!' && ch <= '~') || ch > 0xA0) { 0059 result += ch; 0060 break; 0061 } 0062 [[fallthrough]]; 0063 // else fall through 0064 case ' ': 0065 result += '+'; 0066 break; 0067 case '"': 0068 case '#': 0069 case '$': 0070 case '%': 0071 case '\'': 0072 case '+': 0073 case '=': 0074 result += '%'; 0075 result += hex[(ch & 0xF0) >> 4]; 0076 result += hex[(ch & 0x0F)]; 0077 break; 0078 } 0079 0080 return result; 0081 } 0082 0083 #ifdef UNUSED 0084 static std::string hexencode(const char *in) 0085 { 0086 if (!in) { 0087 return std::string(); 0088 } 0089 return hexencode(std::string(in)); 0090 } 0091 #endif 0092 0093 // changed from returning QByteArray to returning std::string 0094 static std::string hexencode(const QByteArray &in) 0095 { 0096 if (in.isNull()) { 0097 return std::string(); 0098 } 0099 return hexencode(std::string(in.data(), in.size())); 0100 } 0101 // end copied from kleopatra/utils/hex.cpp 0102 0103 Command::Command(QObject *p) 0104 : QObject(p) 0105 , d(new Private(this)) 0106 { 0107 d->init(); 0108 } 0109 0110 Command::Command(Private *pp, QObject *p) 0111 : QObject(p) 0112 , d(pp) 0113 { 0114 d->init(); 0115 } 0116 0117 Command::~Command() 0118 { 0119 delete d; 0120 d = nullptr; 0121 } 0122 0123 void Command::Private::init() 0124 { 0125 connect(this, &QThread::started, q, &Command::started); 0126 connect(this, &QThread::finished, q, &Command::finished); 0127 } 0128 0129 void Command::setParentWId(WId wid) 0130 { 0131 const QMutexLocker locker(&d->mutex); 0132 d->inputs.parentWId = wid; 0133 } 0134 0135 WId Command::parentWId() const 0136 { 0137 const QMutexLocker locker(&d->mutex); 0138 return d->inputs.parentWId; 0139 } 0140 0141 void Command::setServerLocation(const QString &location) 0142 { 0143 const QMutexLocker locker(&d->mutex); 0144 d->outputs.serverLocation = location; 0145 } 0146 0147 QString Command::serverLocation() const 0148 { 0149 const QMutexLocker locker(&d->mutex); 0150 return d->outputs.serverLocation; 0151 } 0152 0153 bool Command::waitForFinished() 0154 { 0155 return d->wait(); 0156 } 0157 0158 bool Command::waitForFinished(unsigned long ms) 0159 { 0160 return d->wait(ms); 0161 } 0162 0163 bool Command::error() const 0164 { 0165 const QMutexLocker locker(&d->mutex); 0166 return !d->outputs.errorString.isEmpty(); 0167 } 0168 0169 bool Command::wasCanceled() const 0170 { 0171 const QMutexLocker locker(&d->mutex); 0172 return d->outputs.canceled; 0173 } 0174 0175 QString Command::errorString() const 0176 { 0177 const QMutexLocker locker(&d->mutex); 0178 return d->outputs.errorString; 0179 } 0180 0181 qint64 Command::serverPid() const 0182 { 0183 const QMutexLocker locker(&d->mutex); 0184 return d->outputs.serverPid; 0185 } 0186 0187 void Command::start() 0188 { 0189 d->start(); 0190 } 0191 0192 void Command::cancel() 0193 { 0194 qCDebug(LIBKLEOPATRACLIENTCORE_LOG) << "Sorry, not implemented: KleopatraClient::Command::Cancel"; 0195 } 0196 0197 void Command::setOptionValue(const char *name, const QVariant &value, bool critical) 0198 { 0199 if (!name || !*name) { 0200 return; 0201 } 0202 const Private::Option opt = {value, true, critical}; 0203 const QMutexLocker locker(&d->mutex); 0204 d->inputs.options[name] = opt; 0205 } 0206 0207 QVariant Command::optionValue(const char *name) const 0208 { 0209 if (!name || !*name) { 0210 return QVariant(); 0211 } 0212 const QMutexLocker locker(&d->mutex); 0213 0214 const auto it = d->inputs.options.find(name); 0215 if (it == d->inputs.options.end()) { 0216 return QVariant(); 0217 } else { 0218 return it->second.value; 0219 } 0220 } 0221 0222 void Command::setOption(const char *name, bool critical) 0223 { 0224 if (!name || !*name) { 0225 return; 0226 } 0227 const QMutexLocker locker(&d->mutex); 0228 0229 if (isOptionSet(name)) { 0230 unsetOption(name); 0231 } 0232 0233 const Private::Option opt = {QVariant(), false, critical}; 0234 0235 d->inputs.options[name] = opt; 0236 } 0237 0238 void Command::unsetOption(const char *name) 0239 { 0240 if (!name || !*name) { 0241 return; 0242 } 0243 const QMutexLocker locker(&d->mutex); 0244 d->inputs.options.erase(name); 0245 } 0246 0247 bool Command::isOptionSet(const char *name) const 0248 { 0249 if (!name || !*name) { 0250 return false; 0251 } 0252 const QMutexLocker locker(&d->mutex); 0253 return d->inputs.options.count(name); 0254 } 0255 0256 bool Command::isOptionCritical(const char *name) const 0257 { 0258 if (!name || !*name) { 0259 return false; 0260 } 0261 const QMutexLocker locker(&d->mutex); 0262 const auto it = d->inputs.options.find(name); 0263 return it != d->inputs.options.end() && it->second.isCritical; 0264 } 0265 0266 void Command::setFilePaths(const QStringList &filePaths) 0267 { 0268 const QMutexLocker locker(&d->mutex); 0269 d->inputs.filePaths = filePaths; 0270 } 0271 0272 QStringList Command::filePaths() const 0273 { 0274 const QMutexLocker locker(&d->mutex); 0275 return d->inputs.filePaths; 0276 } 0277 0278 void Command::setRecipients(const QStringList &recipients, bool informative) 0279 { 0280 const QMutexLocker locker(&d->mutex); 0281 d->inputs.recipients = recipients; 0282 d->inputs.areRecipientsInformative = informative; 0283 } 0284 0285 QStringList Command::recipients() const 0286 { 0287 const QMutexLocker locker(&d->mutex); 0288 return d->inputs.recipients; 0289 } 0290 0291 bool Command::areRecipientsInformative() const 0292 { 0293 const QMutexLocker locker(&d->mutex); 0294 return d->inputs.areRecipientsInformative; 0295 } 0296 0297 void Command::setSenders(const QStringList &senders, bool informative) 0298 { 0299 const QMutexLocker locker(&d->mutex); 0300 d->inputs.senders = senders; 0301 d->inputs.areSendersInformative = informative; 0302 } 0303 0304 QStringList Command::senders() const 0305 { 0306 const QMutexLocker locker(&d->mutex); 0307 return d->inputs.senders; 0308 } 0309 0310 bool Command::areSendersInformative() const 0311 { 0312 const QMutexLocker locker(&d->mutex); 0313 return d->inputs.areSendersInformative; 0314 } 0315 0316 void Command::setInquireData(const char *what, const QByteArray &data) 0317 { 0318 const QMutexLocker locker(&d->mutex); 0319 d->inputs.inquireData[what] = data; 0320 } 0321 0322 void Command::unsetInquireData(const char *what) 0323 { 0324 const QMutexLocker locker(&d->mutex); 0325 d->inputs.inquireData.erase(what); 0326 } 0327 0328 QByteArray Command::inquireData(const char *what) const 0329 { 0330 const QMutexLocker locker(&d->mutex); 0331 const auto it = d->inputs.inquireData.find(what); 0332 if (it == d->inputs.inquireData.end()) { 0333 return QByteArray(); 0334 } else { 0335 return it->second; 0336 } 0337 } 0338 0339 bool Command::isInquireDataSet(const char *what) const 0340 { 0341 const QMutexLocker locker(&d->mutex); 0342 const auto it = d->inputs.inquireData.find(what); 0343 return it != d->inputs.inquireData.end(); 0344 } 0345 0346 QByteArray Command::receivedData() const 0347 { 0348 const QMutexLocker locker(&d->mutex); 0349 return d->outputs.data; 0350 } 0351 0352 void Command::setCommand(const char *command) 0353 { 0354 const QMutexLocker locker(&d->mutex); 0355 d->inputs.command = command; 0356 } 0357 0358 QByteArray Command::command() const 0359 { 0360 const QMutexLocker locker(&d->mutex); 0361 return d->inputs.command; 0362 } 0363 0364 // 0365 // here comes the ugly part 0366 // 0367 0368 static void my_assuan_release(assuan_context_t ctx) 0369 { 0370 if (ctx) { 0371 assuan_release(ctx); 0372 } 0373 } 0374 0375 using AssuanContextBase = std::shared_ptr<std::remove_pointer<assuan_context_t>::type>; 0376 namespace 0377 { 0378 struct AssuanClientContext : AssuanContextBase { 0379 AssuanClientContext() 0380 : AssuanContextBase() 0381 { 0382 } 0383 explicit AssuanClientContext(assuan_context_t ctx) 0384 : AssuanContextBase(ctx, &my_assuan_release) 0385 { 0386 } 0387 void reset(assuan_context_t ctx = nullptr) 0388 { 0389 AssuanContextBase::reset(ctx, &my_assuan_release); 0390 } 0391 }; 0392 } 0393 0394 static gpg_error_t my_assuan_transact(const AssuanClientContext &ctx, 0395 const char *command, 0396 gpg_error_t (*data_cb)(void *, const void *, size_t) = nullptr, 0397 void *data_cb_arg = nullptr, 0398 gpg_error_t (*inquire_cb)(void *, const char *) = nullptr, 0399 void *inquire_cb_arg = nullptr, 0400 gpg_error_t (*status_cb)(void *, const char *) = nullptr, 0401 void *status_cb_arg = nullptr) 0402 { 0403 return assuan_transact(ctx.get(), command, data_cb, data_cb_arg, inquire_cb, inquire_cb_arg, status_cb, status_cb_arg); 0404 } 0405 0406 static QString to_error_string(int err) 0407 { 0408 char buffer[1024]; 0409 gpg_strerror_r(static_cast<gpg_error_t>(err), buffer, sizeof buffer); 0410 buffer[sizeof buffer - 1] = '\0'; 0411 return QString::fromLocal8Bit(buffer); 0412 } 0413 0414 static QString gnupg_home_directory() 0415 { 0416 static const char *hDir = GpgME::dirInfo("homedir"); 0417 return QFile::decodeName(hDir); 0418 } 0419 0420 static QString get_default_socket_name() 0421 { 0422 const QString socketPath{QString::fromUtf8(GpgME::dirInfo("uiserver-socket"))}; 0423 if (!socketPath.isEmpty()) { 0424 // Note: The socket directory exists after GpgME::dirInfo() has been called. 0425 return socketPath; 0426 } 0427 const QString homeDir = gnupg_home_directory(); 0428 if (homeDir.isEmpty()) { 0429 return QString(); 0430 } 0431 return QDir(homeDir).absoluteFilePath(QStringLiteral("S.uiserver")); 0432 } 0433 0434 static QString default_socket_name() 0435 { 0436 static QString name = get_default_socket_name(); 0437 return name; 0438 } 0439 0440 static QString uiserver_executable() 0441 { 0442 return QStringLiteral("kleopatra"); 0443 } 0444 0445 static QString start_uiserver() 0446 { 0447 // Warning: Don't assume that the program needs to be in PATH. On Windows, it will also be found next to the calling process. 0448 if (!QProcess::startDetached(uiserver_executable(), QStringList() << QStringLiteral("--daemon"))) { 0449 return i18n("Failed to start uiserver %1", uiserver_executable()); 0450 } else { 0451 return QString(); 0452 } 0453 } 0454 0455 static gpg_error_t getinfo_pid_cb(void *opaque, const void *buffer, size_t length) 0456 { 0457 qint64 &pid = *static_cast<qint64 *>(opaque); 0458 pid = QByteArray(static_cast<const char *>(buffer), length).toLongLong(); 0459 return 0; 0460 } 0461 0462 static gpg_error_t command_data_cb(void *opaque, const void *buffer, size_t length) 0463 { 0464 QByteArray &ba = *static_cast<QByteArray *>(opaque); 0465 ba.append(QByteArray(static_cast<const char *>(buffer), length)); 0466 return 0; 0467 } 0468 0469 namespace 0470 { 0471 struct inquire_data { 0472 const std::map<std::string, QByteArray> *map; 0473 const AssuanClientContext *ctx; 0474 }; 0475 } 0476 0477 static gpg_error_t command_inquire_cb(void *opaque, const char *what) 0478 { 0479 if (!opaque) { 0480 return 0; 0481 } 0482 const inquire_data &id = *static_cast<const inquire_data *>(opaque); 0483 const auto it = id.map->find(what); 0484 if (it != id.map->end()) { 0485 const QByteArray &v = it->second; 0486 assuan_send_data(id.ctx->get(), v.data(), v.size()); 0487 } 0488 return 0; 0489 } 0490 0491 static inline std::ostream &operator<<(std::ostream &s, const QByteArray &ba) 0492 { 0493 return s << std::string(ba.data(), ba.size()); 0494 } 0495 0496 static gpg_error_t send_option(const AssuanClientContext &ctx, const char *name, const QVariant &value) 0497 { 0498 std::stringstream ss; 0499 ss << "OPTION " << name; 0500 if (value.isValid()) { 0501 ss << '=' << value.toString().toUtf8(); 0502 } 0503 return my_assuan_transact(ctx, ss.str().c_str()); 0504 } 0505 0506 static gpg_error_t send_file(const AssuanClientContext &ctx, const QString &file) 0507 { 0508 std::stringstream ss; 0509 ss << "FILE " << hexencode(QFile::encodeName(file)); 0510 return my_assuan_transact(ctx, ss.str().c_str()); 0511 } 0512 0513 static gpg_error_t send_recipient(const AssuanClientContext &ctx, const QString &recipient, bool info) 0514 { 0515 std::stringstream ss; 0516 ss << "RECIPIENT "; 0517 if (info) { 0518 ss << "--info "; 0519 } 0520 ss << "--" << hexencode(recipient.toUtf8()); 0521 return my_assuan_transact(ctx, ss.str().c_str()); 0522 } 0523 0524 static gpg_error_t send_sender(const AssuanClientContext &ctx, const QString &sender, bool info) 0525 { 0526 std::stringstream ss; 0527 ss << "SENDER "; 0528 if (info) { 0529 ss << "--info "; 0530 } 0531 ss << "--" << hexencode(sender.toUtf8()); 0532 return my_assuan_transact(ctx, ss.str().c_str()); 0533 } 0534 0535 void Command::Private::run() 0536 { 0537 // Take a snapshot of the input data, and clear the output data: 0538 Inputs in; 0539 Outputs out; 0540 { 0541 const QMutexLocker locker(&mutex); 0542 in = inputs; 0543 outputs = out; 0544 } 0545 0546 out.canceled = false; 0547 0548 if (out.serverLocation.isEmpty()) { 0549 out.serverLocation = default_socket_name(); 0550 } 0551 0552 AssuanClientContext ctx; 0553 gpg_error_t err = 0; 0554 0555 inquire_data id = {&in.inquireData, &ctx}; 0556 0557 const QString socketName = out.serverLocation; 0558 if (socketName.isEmpty()) { 0559 out.errorString = i18n("Invalid socket name!"); 0560 goto leave; 0561 } 0562 0563 { 0564 assuan_context_t naked_ctx = nullptr; 0565 err = assuan_new(&naked_ctx); 0566 if (err) { 0567 out.errorString = i18n("Could not allocate resources to connect to Kleopatra UI server at %1: %2", socketName, to_error_string(err)); 0568 goto leave; 0569 } 0570 0571 ctx.reset(naked_ctx); 0572 } 0573 0574 err = assuan_socket_connect(ctx.get(), socketName.toUtf8().constData(), -1, 0); 0575 if (err) { 0576 qDebug("UI server not running, starting it"); 0577 0578 const QString errorString = start_uiserver(); 0579 if (!errorString.isEmpty()) { 0580 out.errorString = errorString; 0581 goto leave; 0582 } 0583 0584 // give it a bit of time to start up and try a couple of times 0585 for (int i = 0; err && i < 20; ++i) { 0586 msleep(500); 0587 err = assuan_socket_connect(ctx.get(), socketName.toUtf8().constData(), -1, 0); 0588 } 0589 } 0590 0591 if (err) { 0592 out.errorString = i18n("Could not connect to Kleopatra UI server at %1: %2", socketName, to_error_string(err)); 0593 goto leave; 0594 } 0595 0596 out.serverPid = -1; 0597 err = my_assuan_transact(ctx, "GETINFO pid", &getinfo_pid_cb, &out.serverPid); 0598 if (err || out.serverPid <= 0) { 0599 out.errorString = i18n("Could not get the process-id of the Kleopatra UI server at %1: %2", socketName, to_error_string(err)); 0600 goto leave; 0601 } 0602 0603 qCDebug(LIBKLEOPATRACLIENTCORE_LOG) << "Server PID =" << out.serverPid; 0604 0605 #if defined(Q_OS_WIN) 0606 if (!AllowSetForegroundWindow((pid_t)out.serverPid)) { 0607 qCDebug(LIBKLEOPATRACLIENTCORE_LOG) << "AllowSetForegroundWindow(" << out.serverPid << ") failed: " << GetLastError(); 0608 } 0609 #endif 0610 0611 if (in.command.isEmpty()) { 0612 goto leave; 0613 } 0614 0615 if (in.parentWId) { 0616 err = send_option(ctx, "window-id", QString::number(in.parentWId, 16)); 0617 if (err) { 0618 qDebug("sending option window-id failed - ignoring"); 0619 } 0620 } 0621 0622 for (auto it = in.options.begin(), end = in.options.end(); it != end; ++it) 0623 if ((err = send_option(ctx, it->first.c_str(), it->second.hasValue ? it->second.value.toString() : QVariant()))) { 0624 if (it->second.isCritical) { 0625 out.errorString = i18n("Failed to send critical option %1: %2", QString::fromLatin1(it->first.c_str()), to_error_string(err)); 0626 goto leave; 0627 } else { 0628 qCDebug(LIBKLEOPATRACLIENTCORE_LOG) << "Failed to send non-critical option" << it->first.c_str() << ":" << to_error_string(err); 0629 } 0630 } 0631 0632 for (const QString &filePath : std::as_const(in.filePaths)) { 0633 if ((err = send_file(ctx, filePath))) { 0634 out.errorString = i18n("Failed to send file path %1: %2", filePath, to_error_string(err)); 0635 goto leave; 0636 } 0637 } 0638 0639 for (const QString &sender : std::as_const(in.senders)) { 0640 if ((err = send_sender(ctx, sender, in.areSendersInformative))) { 0641 out.errorString = i18n("Failed to send sender %1: %2", sender, to_error_string(err)); 0642 goto leave; 0643 } 0644 } 0645 0646 for (const QString &recipient : std::as_const(in.recipients)) { 0647 if ((err = send_recipient(ctx, recipient, in.areRecipientsInformative))) { 0648 out.errorString = i18n("Failed to send recipient %1: %2", recipient, to_error_string(err)); 0649 goto leave; 0650 } 0651 } 0652 0653 #if 0 0654 setup I / O; 0655 #endif 0656 0657 err = my_assuan_transact(ctx, in.command.constData(), &command_data_cb, &out.data, &command_inquire_cb, &id); 0658 if (err) { 0659 if (GpgME::Error{err}.isCanceled()) { 0660 out.canceled = true; 0661 } else { 0662 out.errorString = i18n("Command (%1) failed: %2", QString::fromLatin1(in.command.constData()), to_error_string(err)); 0663 } 0664 goto leave; 0665 } 0666 0667 leave: 0668 const QMutexLocker locker(&mutex); 0669 // copy outputs to where Command can see them: 0670 outputs = out; 0671 } 0672 0673 #include "moc_command_p.cpp" 0674 0675 #include "moc_command.cpp"