File indexing completed on 2024-05-19 04:46:24
0001 /* 0002 * Copyright (C) 2003-2007 Justin Karneges <justin@affinix.com> 0003 * 0004 * This library is free software; you can redistribute it and/or 0005 * modify it under the terms of the GNU Lesser General Public 0006 * License as published by the Free Software Foundation; either 0007 * version 2.1 of the License, or (at your option) any later version. 0008 * 0009 * This library is distributed in the hope that it will be useful, 0010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0012 * Lesser General Public License for more details. 0013 * 0014 * You should have received a copy of the GNU Lesser General Public 0015 * License along with this library; if not, write to the Free Software 0016 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 0017 * 0018 */ 0019 0020 #include "gpgproc_p.h" 0021 0022 #ifdef Q_OS_MAC 0023 #define QT_PIPE_HACK 0024 #endif 0025 0026 using namespace QCA; 0027 0028 namespace gpgQCAPlugin { 0029 0030 void releaseAndDeleteLater(QObject *owner, QObject *obj) 0031 { 0032 obj->disconnect(owner); 0033 obj->setParent(nullptr); 0034 obj->deleteLater(); 0035 } 0036 0037 GPGProc::Private::Private(GPGProc *_q) 0038 : QObject(_q) 0039 , q(_q) 0040 , pipeAux(this) 0041 , pipeCommand(this) 0042 , pipeStatus(this) 0043 , startTrigger(this) 0044 , doneTrigger(this) 0045 { 0046 qRegisterMetaType<gpgQCAPlugin::GPGProc::Error>("gpgQCAPlugin::GPGProc::Error"); 0047 0048 proc = nullptr; 0049 proc_relay = nullptr; 0050 startTrigger.setSingleShot(true); 0051 doneTrigger.setSingleShot(true); 0052 0053 connect(&pipeAux.writeEnd(), &QCA::QPipeEnd::bytesWritten, this, &GPGProc::Private::aux_written); 0054 connect(&pipeAux.writeEnd(), &QCA::QPipeEnd::error, this, &GPGProc::Private::aux_error); 0055 connect(&pipeCommand.writeEnd(), &QCA::QPipeEnd::bytesWritten, this, &GPGProc::Private::command_written); 0056 connect(&pipeCommand.writeEnd(), &QCA::QPipeEnd::error, this, &GPGProc::Private::command_error); 0057 connect(&pipeStatus.readEnd(), &QCA::QPipeEnd::readyRead, this, &GPGProc::Private::status_read); 0058 connect(&pipeStatus.readEnd(), &QCA::QPipeEnd::error, this, &GPGProc::Private::status_error); 0059 connect(&startTrigger, &QCA::SafeTimer::timeout, this, &GPGProc::Private::doStart); 0060 connect(&doneTrigger, &QCA::SafeTimer::timeout, this, &GPGProc::Private::doTryDone); 0061 0062 reset(ResetSessionAndData); 0063 } 0064 0065 GPGProc::Private::~Private() 0066 { 0067 reset(ResetSession); 0068 } 0069 0070 void GPGProc::Private::closePipes() 0071 { 0072 #ifdef QT_PIPE_HACK 0073 pipeAux.readEnd().reset(); 0074 pipeCommand.readEnd().reset(); 0075 pipeStatus.writeEnd().reset(); 0076 #endif 0077 0078 pipeAux.reset(); 0079 pipeCommand.reset(); 0080 pipeStatus.reset(); 0081 } 0082 0083 void GPGProc::Private::reset(ResetMode mode) 0084 { 0085 #ifndef QT_PIPE_HACK 0086 closePipes(); 0087 #endif 0088 0089 if (proc) { 0090 proc->disconnect(this); 0091 0092 if (proc->state() != QProcess::NotRunning) { 0093 // Before try to correct end proccess 0094 // Terminate if failed 0095 proc->close(); 0096 bool finished = proc->waitForFinished(5000); 0097 if (!finished) 0098 proc->terminate(); 0099 } 0100 0101 proc->setParent(nullptr); 0102 releaseAndDeleteLater(this, proc_relay); 0103 proc_relay = nullptr; 0104 delete proc; // should be safe to do thanks to relay 0105 proc = nullptr; 0106 } 0107 0108 #ifdef QT_PIPE_HACK 0109 closePipes(); 0110 #endif 0111 0112 startTrigger.stop(); 0113 doneTrigger.stop(); 0114 0115 pre_stdin.clear(); 0116 pre_aux.clear(); 0117 pre_command.clear(); 0118 pre_stdin_close = false; 0119 pre_aux_close = false; 0120 pre_command_close = false; 0121 0122 need_status = false; 0123 fin_process = false; 0124 fin_status = false; 0125 0126 if (mode >= ResetSessionAndData) { 0127 statusBuf.clear(); 0128 statusLines.clear(); 0129 leftover_stdout.clear(); 0130 leftover_stderr.clear(); 0131 error = GPGProc::FailedToStart; 0132 exitCode = -1; 0133 } 0134 } 0135 0136 bool GPGProc::Private::setupPipes(bool makeAux) 0137 { 0138 if (makeAux && !pipeAux.create()) { 0139 closePipes(); 0140 emit q->debug(QStringLiteral("Error creating pipeAux")); 0141 return false; 0142 } 0143 0144 #ifdef QPIPE_SECURE 0145 if (!pipeCommand.create(true)) // secure 0146 #else 0147 if (!pipeCommand.create()) 0148 #endif 0149 { 0150 closePipes(); 0151 emit q->debug(QStringLiteral("Error creating pipeCommand")); 0152 return false; 0153 } 0154 0155 if (!pipeStatus.create()) { 0156 closePipes(); 0157 emit q->debug(QStringLiteral("Error creating pipeStatus")); 0158 return false; 0159 } 0160 0161 return true; 0162 } 0163 0164 void GPGProc::Private::setupArguments() 0165 { 0166 QStringList fullargs; 0167 fullargs += QStringLiteral("--no-tty"); 0168 fullargs += QStringLiteral("--pinentry-mode"); 0169 fullargs += QStringLiteral("loopback"); 0170 0171 if (mode == ExtendedMode) { 0172 fullargs += QStringLiteral("--enable-special-filenames"); 0173 0174 fullargs += QStringLiteral("--status-fd"); 0175 fullargs += QString::number(pipeStatus.writeEnd().idAsInt()); 0176 0177 fullargs += QStringLiteral("--command-fd"); 0178 fullargs += QString::number(pipeCommand.readEnd().idAsInt()); 0179 } 0180 0181 for (int n = 0; n < args.count(); ++n) { 0182 QString a = args[n]; 0183 if (mode == ExtendedMode && a == QLatin1String("-&?")) 0184 fullargs += QStringLiteral("-&") + QString::number(pipeAux.readEnd().idAsInt()); 0185 else 0186 fullargs += a; 0187 } 0188 0189 QString fullcmd = fullargs.join(QStringLiteral(" ")); 0190 emit q->debug(QStringLiteral("Running: [") + bin + QLatin1Char(' ') + fullcmd + QLatin1Char(']')); 0191 0192 args = fullargs; 0193 } 0194 0195 void GPGProc::Private::doStart() 0196 { 0197 #ifdef Q_OS_WIN 0198 // Note: for unix, inheritability is set in SProcess 0199 if (pipeAux.readEnd().isValid()) 0200 pipeAux.readEnd().setInheritable(true); 0201 if (pipeCommand.readEnd().isValid()) 0202 pipeCommand.readEnd().setInheritable(true); 0203 if (pipeStatus.writeEnd().isValid()) 0204 pipeStatus.writeEnd().setInheritable(true); 0205 #endif 0206 0207 setupArguments(); 0208 0209 proc->start(bin, args); 0210 proc->waitForStarted(); 0211 0212 pipeAux.readEnd().close(); 0213 pipeCommand.readEnd().close(); 0214 pipeStatus.writeEnd().close(); 0215 } 0216 0217 void GPGProc::Private::aux_written(int x) 0218 { 0219 emit q->bytesWrittenAux(x); 0220 } 0221 0222 void GPGProc::Private::aux_error(QCA::QPipeEnd::Error) 0223 { 0224 emit q->debug(QStringLiteral("Aux: Pipe error")); 0225 reset(ResetSession); 0226 emit q->error(GPGProc::ErrorWrite); 0227 } 0228 0229 void GPGProc::Private::command_written(int x) 0230 { 0231 emit q->bytesWrittenCommand(x); 0232 } 0233 0234 void GPGProc::Private::command_error(QCA::QPipeEnd::Error) 0235 { 0236 emit q->debug(QStringLiteral("Command: Pipe error")); 0237 reset(ResetSession); 0238 emit q->error(GPGProc::ErrorWrite); 0239 } 0240 0241 void GPGProc::Private::status_read() 0242 { 0243 if (readAndProcessStatusData()) 0244 emit q->readyReadStatusLines(); 0245 } 0246 0247 void GPGProc::Private::status_error(QCA::QPipeEnd::Error e) 0248 { 0249 if (e == QPipeEnd::ErrorEOF) 0250 emit q->debug(QStringLiteral("Status: Closed (EOF)")); 0251 else 0252 emit q->debug(QStringLiteral("Status: Closed (gone)")); 0253 0254 fin_status = true; 0255 doTryDone(); 0256 } 0257 0258 void GPGProc::Private::proc_started() 0259 { 0260 emit q->debug(QStringLiteral("Process started")); 0261 0262 // Note: we don't close these here anymore. instead we 0263 // do it just after calling proc->start(). 0264 // close these, we don't need them 0265 /*pipeAux.readEnd().close(); 0266 pipeCommand.readEnd().close(); 0267 pipeStatus.writeEnd().close();*/ 0268 0269 // do the pre* stuff 0270 if (!pre_stdin.isEmpty()) { 0271 proc->write(pre_stdin); 0272 pre_stdin.clear(); 0273 } 0274 if (!pre_aux.isEmpty()) { 0275 pipeAux.writeEnd().write(pre_aux); 0276 pre_aux.clear(); 0277 } 0278 if (!pre_command.isEmpty()) { 0279 #ifdef QPIPE_SECURE 0280 pipeCommand.writeEnd().writeSecure(pre_command); 0281 #else 0282 pipeCommand.writeEnd().write(pre_command); 0283 #endif 0284 pre_command.clear(); 0285 } 0286 0287 if (pre_stdin_close) { 0288 proc->waitForBytesWritten(); 0289 proc->closeWriteChannel(); 0290 } 0291 0292 if (pre_aux_close) 0293 pipeAux.writeEnd().close(); 0294 if (pre_command_close) 0295 pipeCommand.writeEnd().close(); 0296 } 0297 0298 void GPGProc::Private::proc_readyReadStandardOutput() 0299 { 0300 emit q->readyReadStdout(); 0301 } 0302 0303 void GPGProc::Private::proc_readyReadStandardError() 0304 { 0305 emit q->readyReadStderr(); 0306 } 0307 0308 void GPGProc::Private::proc_bytesWritten(qint64 lx) 0309 { 0310 int x = (int)lx; 0311 emit q->bytesWrittenStdin(x); 0312 } 0313 0314 void GPGProc::Private::proc_finished(int x) 0315 { 0316 emit q->debug(QStringLiteral("Process finished: %1").arg(x)); 0317 exitCode = x; 0318 0319 fin_process = true; 0320 fin_process_success = true; 0321 0322 if (need_status && !fin_status) { 0323 pipeStatus.readEnd().finalize(); 0324 fin_status = true; 0325 if (readAndProcessStatusData()) { 0326 doneTrigger.start(); 0327 emit q->readyReadStatusLines(); 0328 return; 0329 } 0330 } 0331 0332 doTryDone(); 0333 } 0334 0335 void GPGProc::Private::proc_error(QProcess::ProcessError x) 0336 { 0337 QMap<int, QString> errmap; 0338 errmap[QProcess::FailedToStart] = QStringLiteral("FailedToStart"); 0339 errmap[QProcess::Crashed] = QStringLiteral("Crashed"); 0340 errmap[QProcess::Timedout] = QStringLiteral("Timedout"); 0341 errmap[QProcess::WriteError] = QStringLiteral("WriteError"); 0342 errmap[QProcess::ReadError] = QStringLiteral("ReadError"); 0343 errmap[QProcess::UnknownError] = QStringLiteral("UnknownError"); 0344 0345 emit q->debug(QStringLiteral("Process error: %1").arg(errmap[x])); 0346 0347 if (x == QProcess::FailedToStart) 0348 error = GPGProc::FailedToStart; 0349 else if (x == QProcess::WriteError) 0350 error = GPGProc::ErrorWrite; 0351 else 0352 error = GPGProc::UnexpectedExit; 0353 0354 fin_process = true; 0355 fin_process_success = false; 0356 0357 #ifdef QT_PIPE_HACK 0358 // If the process fails to start, then the ends of the pipes 0359 // intended for the child process are still open. Some Mac 0360 // users experience a lockup if we close our ends of the pipes 0361 // when the child's ends are still open. If we ensure the 0362 // child's ends are closed, we prevent this lockup. I have no 0363 // idea why the problem even happens or why this fix should 0364 // work. 0365 pipeAux.readEnd().reset(); 0366 pipeCommand.readEnd().reset(); 0367 pipeStatus.writeEnd().reset(); 0368 #endif 0369 0370 if (need_status && !fin_status) { 0371 pipeStatus.readEnd().finalize(); 0372 fin_status = true; 0373 if (readAndProcessStatusData()) { 0374 doneTrigger.start(); 0375 emit q->readyReadStatusLines(); 0376 return; 0377 } 0378 } 0379 0380 doTryDone(); 0381 } 0382 0383 void GPGProc::Private::doTryDone() 0384 { 0385 if (!fin_process) 0386 return; 0387 0388 if (need_status && !fin_status) 0389 return; 0390 0391 emit q->debug(QStringLiteral("Done")); 0392 0393 // get leftover data 0394 proc->setReadChannel(QProcess::StandardOutput); 0395 leftover_stdout = proc->readAll(); 0396 0397 proc->setReadChannel(QProcess::StandardError); 0398 leftover_stderr = proc->readAll(); 0399 0400 reset(ResetSession); 0401 if (fin_process_success) 0402 emit q->finished(exitCode); 0403 else 0404 emit q->error(error); 0405 } 0406 0407 bool GPGProc::Private::readAndProcessStatusData() 0408 { 0409 const QByteArray buf = pipeStatus.readEnd().read(); 0410 if (buf.isEmpty()) 0411 return false; 0412 0413 return processStatusData(buf); 0414 } 0415 0416 // return true if there are newly parsed lines available 0417 bool GPGProc::Private::processStatusData(const QByteArray &buf) 0418 { 0419 statusBuf.append(buf); 0420 0421 // extract all lines 0422 QStringList list; 0423 while (true) { 0424 int n = statusBuf.indexOf('\n'); 0425 if (n == -1) 0426 break; 0427 0428 // extract the string from statusbuf 0429 ++n; 0430 char *p = (char *)statusBuf.data(); 0431 QByteArray cs(p, n); 0432 const int newsize = statusBuf.size() - n; 0433 memmove(p, p + n, newsize); 0434 statusBuf.resize(newsize); 0435 0436 // convert to string without newline 0437 QString str = QString::fromUtf8(cs); 0438 str.truncate(str.length() - 1); 0439 0440 // ensure it has a proper header 0441 if (str.left(9) != QLatin1String("[GNUPG:] ")) 0442 continue; 0443 0444 // take it off 0445 str = str.mid(9); 0446 0447 // add to the list 0448 list += str; 0449 } 0450 0451 if (list.isEmpty()) 0452 return false; 0453 0454 statusLines += list; 0455 return true; 0456 } 0457 0458 GPGProc::GPGProc(QObject *parent) 0459 : QObject(parent) 0460 { 0461 d = new Private(this); 0462 } 0463 0464 GPGProc::~GPGProc() 0465 { 0466 delete d; 0467 } 0468 0469 void GPGProc::reset() 0470 { 0471 d->reset(ResetAll); 0472 } 0473 0474 bool GPGProc::isActive() const 0475 { 0476 return (d->proc ? true : false); 0477 } 0478 0479 void GPGProc::start(const QString &bin, const QStringList &args, Mode mode) 0480 { 0481 if (isActive()) 0482 d->reset(ResetSessionAndData); 0483 0484 if (mode == ExtendedMode) { 0485 if (!d->setupPipes(args.contains(QStringLiteral("-&?")))) { 0486 d->error = FailedToStart; 0487 0488 // emit later 0489 QMetaObject::invokeMethod( 0490 this, "error", Qt::QueuedConnection, Q_ARG(gpgQCAPlugin::GPGProc::Error, d->error)); 0491 return; 0492 } 0493 0494 d->need_status = true; 0495 0496 emit debug(QStringLiteral("Pipe setup complete")); 0497 } 0498 0499 d->proc = new SProcess(d); 0500 0501 #ifdef Q_OS_UNIX 0502 QList<int> plist; 0503 if (d->pipeAux.readEnd().isValid()) 0504 plist += d->pipeAux.readEnd().id(); 0505 if (d->pipeCommand.readEnd().isValid()) 0506 plist += d->pipeCommand.readEnd().id(); 0507 if (d->pipeStatus.writeEnd().isValid()) 0508 plist += d->pipeStatus.writeEnd().id(); 0509 d->proc->setInheritPipeList(plist); 0510 #endif 0511 0512 // enable the pipes we want 0513 if (d->pipeAux.writeEnd().isValid()) 0514 d->pipeAux.writeEnd().enable(); 0515 if (d->pipeCommand.writeEnd().isValid()) 0516 d->pipeCommand.writeEnd().enable(); 0517 if (d->pipeStatus.readEnd().isValid()) 0518 d->pipeStatus.readEnd().enable(); 0519 0520 d->proc_relay = new QProcessSignalRelay(d->proc, d); 0521 connect(d->proc_relay, &QProcessSignalRelay::started, d, &GPGProc::Private::proc_started); 0522 connect(d->proc_relay, 0523 &QProcessSignalRelay::readyReadStandardOutput, 0524 d, 0525 &GPGProc::Private::proc_readyReadStandardOutput); 0526 connect( 0527 d->proc_relay, &QProcessSignalRelay::readyReadStandardError, d, &GPGProc::Private::proc_readyReadStandardError); 0528 connect(d->proc_relay, &QProcessSignalRelay::bytesWritten, d, &GPGProc::Private::proc_bytesWritten); 0529 connect(d->proc_relay, &QProcessSignalRelay::finished, d, &GPGProc::Private::proc_finished); 0530 connect(d->proc_relay, &QProcessSignalRelay::error, d, &GPGProc::Private::proc_error); 0531 0532 d->bin = bin; 0533 d->args = args; 0534 d->mode = mode; 0535 d->startTrigger.start(); 0536 } 0537 0538 QByteArray GPGProc::readStdout() 0539 { 0540 if (d->proc) { 0541 d->proc->setReadChannel(QProcess::StandardOutput); 0542 return d->proc->readAll(); 0543 } else { 0544 const QByteArray a = d->leftover_stdout; 0545 d->leftover_stdout.clear(); 0546 return a; 0547 } 0548 } 0549 0550 QByteArray GPGProc::readStderr() 0551 { 0552 if (d->proc) { 0553 d->proc->setReadChannel(QProcess::StandardError); 0554 return d->proc->readAll(); 0555 } else { 0556 const QByteArray a = d->leftover_stderr; 0557 d->leftover_stderr.clear(); 0558 return a; 0559 } 0560 } 0561 0562 QStringList GPGProc::readStatusLines() 0563 { 0564 const QStringList out = d->statusLines; 0565 d->statusLines.clear(); 0566 return out; 0567 } 0568 0569 void GPGProc::writeStdin(const QByteArray &a) 0570 { 0571 if (!d->proc || a.isEmpty()) 0572 return; 0573 0574 if (d->proc->state() == QProcess::Running) 0575 d->proc->write(a); 0576 else 0577 d->pre_stdin += a; 0578 } 0579 0580 void GPGProc::writeAux(const QByteArray &a) 0581 { 0582 if (!d->proc || a.isEmpty()) 0583 return; 0584 0585 if (d->proc->state() == QProcess::Running) 0586 d->pipeAux.writeEnd().write(a); 0587 else 0588 d->pre_aux += a; 0589 } 0590 0591 #ifdef QPIPE_SECURE 0592 void GPGProc::writeCommand(const SecureArray &a) 0593 #else 0594 void GPGProc::writeCommand(const QByteArray &a) 0595 #endif 0596 { 0597 if (!d->proc || a.isEmpty()) 0598 return; 0599 0600 if (d->proc->state() == QProcess::Running) 0601 #ifdef QPIPE_SECURE 0602 d->pipeCommand.writeEnd().writeSecure(a); 0603 #else 0604 d->pipeCommand.writeEnd().write(a); 0605 #endif 0606 else 0607 d->pre_command += a; 0608 } 0609 0610 void GPGProc::closeStdin() 0611 { 0612 if (!d->proc) 0613 return; 0614 0615 if (d->proc->state() == QProcess::Running) { 0616 d->proc->waitForBytesWritten(); 0617 d->proc->closeWriteChannel(); 0618 } else { 0619 d->pre_stdin_close = true; 0620 } 0621 } 0622 0623 void GPGProc::closeAux() 0624 { 0625 if (!d->proc) 0626 return; 0627 0628 if (d->proc->state() == QProcess::Running) 0629 d->pipeAux.writeEnd().close(); 0630 else 0631 d->pre_aux_close = true; 0632 } 0633 0634 void GPGProc::closeCommand() 0635 { 0636 if (!d->proc) 0637 return; 0638 0639 if (d->proc->state() == QProcess::Running) 0640 d->pipeCommand.writeEnd().close(); 0641 else 0642 d->pre_command_close = true; 0643 } 0644 0645 }