00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020 #include "gpgproc.h"
00021
00022 #include "sprocess.h"
00023
00024 #ifdef Q_OS_MAC
00025 #define QT_PIPE_HACK
00026 #endif
00027
00028 #define QPROC_SIGNAL_RELAY
00029
00030 using namespace QCA;
00031
00032 namespace gpgQCAPlugin {
00033
00034 void releaseAndDeleteLater(QObject *owner, QObject *obj)
00035 {
00036 obj->disconnect(owner);
00037 obj->setParent(0);
00038 obj->deleteLater();
00039 }
00040
00041
00042
00043
00044 SafeTimer::SafeTimer(QObject *parent) :
00045 QObject(parent)
00046 {
00047 timer = new QTimer(this);
00048 connect(timer, SIGNAL(timeout()), SIGNAL(timeout()));
00049 }
00050
00051 SafeTimer::~SafeTimer()
00052 {
00053 releaseAndDeleteLater(this, timer);
00054 }
00055
00056 int SafeTimer::interval() const
00057 {
00058 return timer->interval();
00059 }
00060
00061 bool SafeTimer::isActive() const
00062 {
00063 return timer->isActive();
00064 }
00065
00066 bool SafeTimer::isSingleShot() const
00067 {
00068 return timer->isSingleShot();
00069 }
00070
00071 void SafeTimer::setInterval(int msec)
00072 {
00073 timer->setInterval(msec);
00074 }
00075
00076 void SafeTimer::setSingleShot(bool singleShot)
00077 {
00078 timer->setSingleShot(singleShot);
00079 }
00080
00081 int SafeTimer::timerId() const
00082 {
00083 return timer->timerId();
00084 }
00085
00086 void SafeTimer::start(int msec)
00087 {
00088 timer->start(msec);
00089 }
00090
00091 void SafeTimer::start()
00092 {
00093 timer->start();
00094 }
00095
00096 void SafeTimer::stop()
00097 {
00098 timer->stop();
00099 }
00100
00101
00102
00103
00104 class QProcessSignalRelay : public QObject
00105 {
00106 Q_OBJECT
00107 public:
00108 QProcessSignalRelay(QProcess *proc, QObject *parent = 0)
00109 :QObject(parent)
00110 {
00111 qRegisterMetaType<QProcess::ProcessError>("QProcess::ProcessError");
00112 connect(proc, SIGNAL(started()), SLOT(proc_started()), Qt::QueuedConnection);
00113 connect(proc, SIGNAL(readyReadStandardOutput()), SLOT(proc_readyReadStandardOutput()), Qt::QueuedConnection);
00114 connect(proc, SIGNAL(readyReadStandardError()), SLOT(proc_readyReadStandardError()), Qt::QueuedConnection);
00115 connect(proc, SIGNAL(bytesWritten(qint64)), SLOT(proc_bytesWritten(qint64)), Qt::QueuedConnection);
00116 connect(proc, SIGNAL(finished(int)), SLOT(proc_finished(int)), Qt::QueuedConnection);
00117 connect(proc, SIGNAL(error(QProcess::ProcessError)), SLOT(proc_error(QProcess::ProcessError)), Qt::QueuedConnection);
00118 }
00119
00120 signals:
00121 void started();
00122 void readyReadStandardOutput();
00123 void readyReadStandardError();
00124 void bytesWritten(qint64);
00125 void finished(int);
00126 void error(QProcess::ProcessError);
00127
00128 public slots:
00129 void proc_started()
00130 {
00131 emit started();
00132 }
00133
00134 void proc_readyReadStandardOutput()
00135 {
00136 emit readyReadStandardOutput();
00137 }
00138
00139 void proc_readyReadStandardError()
00140 {
00141 emit readyReadStandardError();
00142 }
00143
00144 void proc_bytesWritten(qint64 x)
00145 {
00146 emit bytesWritten(x);
00147 }
00148
00149 void proc_finished(int x)
00150 {
00151 emit finished(x);
00152 }
00153
00154 void proc_error(QProcess::ProcessError x)
00155 {
00156 emit error(x);
00157 }
00158 };
00159
00160
00161
00162
00163 enum ResetMode
00164 {
00165 ResetSession = 0,
00166 ResetSessionAndData = 1,
00167 ResetAll = 2
00168 };
00169
00170 class GPGProc::Private : public QObject
00171 {
00172 Q_OBJECT
00173 public:
00174 GPGProc *q;
00175 QString bin;
00176 QStringList args;
00177 GPGProc::Mode mode;
00178 SProcess *proc;
00179 #ifdef QPROC_SIGNAL_RELAY
00180 QProcessSignalRelay *proc_relay;
00181 #endif
00182 QPipe pipeAux, pipeCommand, pipeStatus;
00183 QByteArray statusBuf;
00184 QStringList statusLines;
00185 GPGProc::Error error;
00186 int exitCode;
00187 SafeTimer startTrigger, doneTrigger;
00188
00189 QByteArray pre_stdin, pre_aux;
00190 #ifdef QPIPE_SECURE
00191 SecureArray pre_command;
00192 #else
00193 QByteArray pre_command;
00194 #endif
00195 bool pre_stdin_close, pre_aux_close, pre_command_close;
00196
00197 bool need_status, fin_process, fin_process_success, fin_status;
00198 QByteArray leftover_stdout;
00199 QByteArray leftover_stderr;
00200
00201 Private(GPGProc *_q) : QObject(_q), q(_q), pipeAux(this), pipeCommand(this), pipeStatus(this), startTrigger(this), doneTrigger(this)
00202 {
00203 qRegisterMetaType<gpgQCAPlugin::GPGProc::Error>("gpgQCAPlugin::GPGProc::Error");
00204
00205 proc = 0;
00206 #ifdef QPROC_SIGNAL_RELAY
00207 proc_relay = 0;
00208 #endif
00209 startTrigger.setSingleShot(true);
00210 doneTrigger.setSingleShot(true);
00211
00212 connect(&pipeAux.writeEnd(), SIGNAL(bytesWritten(int)), SLOT(aux_written(int)));
00213 connect(&pipeAux.writeEnd(), SIGNAL(error(QCA::QPipeEnd::Error)), SLOT(aux_error(QCA::QPipeEnd::Error)));
00214 connect(&pipeCommand.writeEnd(), SIGNAL(bytesWritten(int)), SLOT(command_written(int)));
00215 connect(&pipeCommand.writeEnd(), SIGNAL(error(QCA::QPipeEnd::Error)), SLOT(command_error(QCA::QPipeEnd::Error)));
00216 connect(&pipeStatus.readEnd(), SIGNAL(readyRead()), SLOT(status_read()));
00217 connect(&pipeStatus.readEnd(), SIGNAL(error(QCA::QPipeEnd::Error)), SLOT(status_error(QCA::QPipeEnd::Error)));
00218 connect(&startTrigger, SIGNAL(timeout()), SLOT(doStart()));
00219 connect(&doneTrigger, SIGNAL(timeout()), SLOT(doTryDone()));
00220
00221 reset(ResetSessionAndData);
00222 }
00223
00224 ~Private()
00225 {
00226 reset(ResetSession);
00227 }
00228
00229 void closePipes()
00230 {
00231 #ifdef QT_PIPE_HACK
00232 pipeAux.readEnd().reset();
00233 pipeCommand.readEnd().reset();
00234 pipeStatus.writeEnd().reset();
00235 #endif
00236
00237 pipeAux.reset();
00238 pipeCommand.reset();
00239 pipeStatus.reset();
00240 }
00241
00242 void reset(ResetMode mode)
00243 {
00244 #ifndef QT_PIPE_HACK
00245 closePipes();
00246 #endif
00247
00248 if(proc)
00249 {
00250 proc->disconnect(this);
00251 if(proc->state() != QProcess::NotRunning)
00252 proc->terminate();
00253 proc->setParent(0);
00254 #ifdef QPROC_SIGNAL_RELAY
00255 releaseAndDeleteLater(this, proc_relay);
00256 proc_relay = 0;
00257 delete proc;
00258 #else
00259 proc->deleteLater();
00260 #endif
00261 proc = 0;
00262 }
00263
00264 #ifdef QT_PIPE_HACK
00265 closePipes();
00266 #endif
00267
00268 startTrigger.stop();
00269 doneTrigger.stop();
00270
00271 pre_stdin.clear();
00272 pre_aux.clear();
00273 pre_command.clear();
00274 pre_stdin_close = false;
00275 pre_aux_close = false;
00276 pre_command_close = false;
00277
00278 need_status = false;
00279 fin_process = false;
00280 fin_status = false;
00281
00282 if(mode >= ResetSessionAndData)
00283 {
00284 statusBuf.clear();
00285 statusLines.clear();
00286 leftover_stdout.clear();
00287 leftover_stderr.clear();
00288 error = GPGProc::FailedToStart;
00289 exitCode = -1;
00290 }
00291 }
00292
00293 bool setupPipes(bool makeAux)
00294 {
00295 if(makeAux && !pipeAux.create())
00296 {
00297 closePipes();
00298 emit q->debug("Error creating pipeAux");
00299 return false;
00300 }
00301
00302 #ifdef QPIPE_SECURE
00303 if(!pipeCommand.create(true))
00304 #else
00305 if(!pipeCommand.create())
00306 #endif
00307 {
00308 closePipes();
00309 emit q->debug("Error creating pipeCommand");
00310 return false;
00311 }
00312
00313 if(!pipeStatus.create())
00314 {
00315 closePipes();
00316 emit q->debug("Error creating pipeStatus");
00317 return false;
00318 }
00319
00320 return true;
00321 }
00322
00323 void setupArguments()
00324 {
00325 QStringList fullargs;
00326 fullargs += "--no-tty";
00327
00328 if(mode == ExtendedMode)
00329 {
00330 fullargs += "--enable-special-filenames";
00331
00332 fullargs += "--status-fd";
00333 fullargs += QString::number(pipeStatus.writeEnd().idAsInt());
00334
00335 fullargs += "--command-fd";
00336 fullargs += QString::number(pipeCommand.readEnd().idAsInt());
00337 }
00338
00339 for(int n = 0; n < args.count(); ++n)
00340 {
00341 QString a = args[n];
00342 if(mode == ExtendedMode && a == "-&?")
00343 fullargs += QString("-&") + QString::number(pipeAux.readEnd().idAsInt());
00344 else
00345 fullargs += a;
00346 }
00347
00348 QString fullcmd = fullargs.join(" ");
00349 emit q->debug(QString("Running: [") + bin + ' ' + fullcmd + ']');
00350
00351 args = fullargs;
00352 }
00353
00354 public slots:
00355 void doStart()
00356 {
00357 #ifdef Q_OS_WIN
00358
00359 if(pipeAux.readEnd().isValid())
00360 pipeAux.readEnd().setInheritable(true);
00361 if(pipeCommand.readEnd().isValid())
00362 pipeCommand.readEnd().setInheritable(true);
00363 if(pipeStatus.writeEnd().isValid())
00364 pipeStatus.writeEnd().setInheritable(true);
00365 #endif
00366
00367 setupArguments();
00368
00369 proc->start(bin, args);
00370
00371
00372
00373
00374
00375
00376
00377
00378 pipeAux.readEnd().close();
00379 pipeCommand.readEnd().close();
00380 pipeStatus.writeEnd().close();
00381 }
00382
00383 void aux_written(int x)
00384 {
00385 emit q->bytesWrittenAux(x);
00386 }
00387
00388 void aux_error(QCA::QPipeEnd::Error)
00389 {
00390 emit q->debug("Aux: Pipe error");
00391 reset(ResetSession);
00392 emit q->error(GPGProc::ErrorWrite);
00393 }
00394
00395 void command_written(int x)
00396 {
00397 emit q->bytesWrittenCommand(x);
00398 }
00399
00400 void command_error(QCA::QPipeEnd::Error)
00401 {
00402 emit q->debug("Command: Pipe error");
00403 reset(ResetSession);
00404 emit q->error(GPGProc::ErrorWrite);
00405 }
00406
00407 void status_read()
00408 {
00409 if(readAndProcessStatusData())
00410 emit q->readyReadStatusLines();
00411 }
00412
00413 void status_error(QCA::QPipeEnd::Error e)
00414 {
00415 if(e == QPipeEnd::ErrorEOF)
00416 emit q->debug("Status: Closed (EOF)");
00417 else
00418 emit q->debug("Status: Closed (gone)");
00419
00420 fin_status = true;
00421 doTryDone();
00422 }
00423
00424 void proc_started()
00425 {
00426 emit q->debug("Process started");
00427
00428
00429
00430
00431
00432
00433
00434
00435
00436 if(!pre_stdin.isEmpty())
00437 {
00438 proc->write(pre_stdin);
00439 pre_stdin.clear();
00440 }
00441 if(!pre_aux.isEmpty())
00442 {
00443 pipeAux.writeEnd().write(pre_aux);
00444 pre_aux.clear();
00445 }
00446 if(!pre_command.isEmpty())
00447 {
00448 #ifdef QPIPE_SECURE
00449 pipeCommand.writeEnd().writeSecure(pre_command);
00450 #else
00451 pipeCommand.writeEnd().write(pre_command);
00452 #endif
00453 pre_command.clear();
00454 }
00455
00456 if(pre_stdin_close)
00457 proc->closeWriteChannel();
00458 if(pre_aux_close)
00459 pipeAux.writeEnd().close();
00460 if(pre_command_close)
00461 pipeCommand.writeEnd().close();
00462 }
00463
00464 void proc_readyReadStandardOutput()
00465 {
00466 emit q->readyReadStdout();
00467 }
00468
00469 void proc_readyReadStandardError()
00470 {
00471 emit q->readyReadStderr();
00472 }
00473
00474 void proc_bytesWritten(qint64 lx)
00475 {
00476 int x = (int)lx;
00477 emit q->bytesWrittenStdin(x);
00478 }
00479
00480 void proc_finished(int x)
00481 {
00482 emit q->debug(QString("Process finished: %1").arg(x));
00483 exitCode = x;
00484
00485 fin_process = true;
00486 fin_process_success = true;
00487
00488 if(need_status && !fin_status)
00489 {
00490 pipeStatus.readEnd().finalize();
00491 fin_status = true;
00492 if(readAndProcessStatusData())
00493 {
00494 doneTrigger.start();
00495 emit q->readyReadStatusLines();
00496 return;
00497 }
00498 }
00499
00500 doTryDone();
00501 }
00502
00503 void proc_error(QProcess::ProcessError x)
00504 {
00505 QMap<int, QString> errmap;
00506 errmap[QProcess::FailedToStart] = "FailedToStart";
00507 errmap[QProcess::Crashed] = "Crashed";
00508 errmap[QProcess::Timedout] = "Timedout";
00509 errmap[QProcess::WriteError] = "WriteError";
00510 errmap[QProcess::ReadError] = "ReadError";
00511 errmap[QProcess::UnknownError] = "UnknownError";
00512
00513 emit q->debug(QString("Process error: %1").arg(errmap[x]));
00514
00515 if(x == QProcess::FailedToStart)
00516 error = GPGProc::FailedToStart;
00517 else if(x == QProcess::WriteError)
00518 error = GPGProc::ErrorWrite;
00519 else
00520 error = GPGProc::UnexpectedExit;
00521
00522 fin_process = true;
00523 fin_process_success = false;
00524
00525 #ifdef QT_PIPE_HACK
00526
00527
00528
00529
00530
00531
00532
00533 pipeAux.readEnd().reset();
00534 pipeCommand.readEnd().reset();
00535 pipeStatus.writeEnd().reset();
00536 #endif
00537
00538 if(need_status && !fin_status)
00539 {
00540 pipeStatus.readEnd().finalize();
00541 fin_status = true;
00542 if(readAndProcessStatusData())
00543 {
00544 doneTrigger.start();
00545 emit q->readyReadStatusLines();
00546 return;
00547 }
00548 }
00549
00550 doTryDone();
00551 }
00552
00553 void doTryDone()
00554 {
00555 if(!fin_process)
00556 return;
00557
00558 if(need_status && !fin_status)
00559 return;
00560
00561 emit q->debug("Done");
00562
00563
00564 proc->setReadChannel(QProcess::StandardOutput);
00565 leftover_stdout = proc->readAll();
00566
00567 proc->setReadChannel(QProcess::StandardError);
00568 leftover_stderr = proc->readAll();
00569
00570 reset(ResetSession);
00571 if(fin_process_success)
00572 emit q->finished(exitCode);
00573 else
00574 emit q->error(error);
00575 }
00576
00577 private:
00578 bool readAndProcessStatusData()
00579 {
00580 QByteArray buf = pipeStatus.readEnd().read();
00581 if(buf.isEmpty())
00582 return false;
00583
00584 return processStatusData(buf);
00585 }
00586
00587
00588 bool processStatusData(const QByteArray &buf)
00589 {
00590 statusBuf.append(buf);
00591
00592
00593 QStringList list;
00594 while(1)
00595 {
00596 int n = statusBuf.indexOf('\n');
00597 if(n == -1)
00598 break;
00599
00600
00601 ++n;
00602 char *p = (char *)statusBuf.data();
00603 QByteArray cs(p, n);
00604 int newsize = statusBuf.size() - n;
00605 memmove(p, p + n, newsize);
00606 statusBuf.resize(newsize);
00607
00608
00609 QString str = QString::fromUtf8(cs);
00610 str.truncate(str.length() - 1);
00611
00612
00613 if(str.left(9) != "[GNUPG:] ")
00614 continue;
00615
00616
00617 str = str.mid(9);
00618
00619
00620 list += str;
00621 }
00622
00623 if(list.isEmpty())
00624 return false;
00625
00626 statusLines += list;
00627 return true;
00628 }
00629 };
00630
00631 GPGProc::GPGProc(QObject *parent)
00632 :QObject(parent)
00633 {
00634 d = new Private(this);
00635 }
00636
00637 GPGProc::~GPGProc()
00638 {
00639 delete d;
00640 }
00641
00642 void GPGProc::reset()
00643 {
00644 d->reset(ResetAll);
00645 }
00646
00647 bool GPGProc::isActive() const
00648 {
00649 return (d->proc ? true : false);
00650 }
00651
00652 void GPGProc::start(const QString &bin, const QStringList &args, Mode mode)
00653 {
00654 if(isActive())
00655 d->reset(ResetSessionAndData);
00656
00657 if(mode == ExtendedMode)
00658 {
00659 if(!d->setupPipes(args.contains("-&?")))
00660 {
00661 d->error = FailedToStart;
00662
00663
00664 QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, Q_ARG(gpgQCAPlugin::GPGProc::Error, d->error));
00665 return;
00666 }
00667
00668 d->need_status = true;
00669
00670 emit debug("Pipe setup complete");
00671 }
00672
00673 d->proc = new SProcess(d);
00674
00675 #ifdef Q_OS_UNIX
00676 QList<int> plist;
00677 if(d->pipeAux.readEnd().isValid())
00678 plist += d->pipeAux.readEnd().id();
00679 if(d->pipeCommand.readEnd().isValid())
00680 plist += d->pipeCommand.readEnd().id();
00681 if(d->pipeStatus.writeEnd().isValid())
00682 plist += d->pipeStatus.writeEnd().id();
00683 d->proc->setInheritPipeList(plist);
00684 #endif
00685
00686
00687 if(d->pipeAux.writeEnd().isValid())
00688 d->pipeAux.writeEnd().enable();
00689 if(d->pipeCommand.writeEnd().isValid())
00690 d->pipeCommand.writeEnd().enable();
00691 if(d->pipeStatus.readEnd().isValid())
00692 d->pipeStatus.readEnd().enable();
00693
00694 #ifdef QPROC_SIGNAL_RELAY
00695 d->proc_relay = new QProcessSignalRelay(d->proc, d);
00696 connect(d->proc_relay, SIGNAL(started()), d, SLOT(proc_started()));
00697 connect(d->proc_relay, SIGNAL(readyReadStandardOutput()), d, SLOT(proc_readyReadStandardOutput()));
00698 connect(d->proc_relay, SIGNAL(readyReadStandardError()), d, SLOT(proc_readyReadStandardError()));
00699 connect(d->proc_relay, SIGNAL(bytesWritten(qint64)), d, SLOT(proc_bytesWritten(qint64)));
00700 connect(d->proc_relay, SIGNAL(finished(int)), d, SLOT(proc_finished(int)));
00701 connect(d->proc_relay, SIGNAL(error(QProcess::ProcessError)), d, SLOT(proc_error(QProcess::ProcessError)));
00702 #else
00703 connect(d->proc, SIGNAL(started()), d, SLOT(proc_started()));
00704 connect(d->proc, SIGNAL(readyReadStandardOutput()), d, SLOT(proc_readyReadStandardOutput()));
00705 connect(d->proc, SIGNAL(readyReadStandardError()), d, SLOT(proc_readyReadStandardError()));
00706 connect(d->proc, SIGNAL(bytesWritten(qint64)), d, SLOT(proc_bytesWritten(qint64)));
00707 connect(d->proc, SIGNAL(finished(int)), d, SLOT(proc_finished(int)));
00708 connect(d->proc, SIGNAL(error(QProcess::ProcessError)), d, SLOT(proc_error(QProcess::ProcessError)));
00709 #endif
00710
00711 d->bin = bin;
00712 d->args = args;
00713 d->mode = mode;
00714 d->startTrigger.start();
00715 }
00716
00717 QByteArray GPGProc::readStdout()
00718 {
00719 if(d->proc)
00720 {
00721 d->proc->setReadChannel(QProcess::StandardOutput);
00722 return d->proc->readAll();
00723 }
00724 else
00725 {
00726 QByteArray a = d->leftover_stdout;
00727 d->leftover_stdout.clear();
00728 return a;
00729 }
00730 }
00731
00732 QByteArray GPGProc::readStderr()
00733 {
00734 if(d->proc)
00735 {
00736 d->proc->setReadChannel(QProcess::StandardError);
00737 return d->proc->readAll();
00738 }
00739 else
00740 {
00741 QByteArray a = d->leftover_stderr;
00742 d->leftover_stderr.clear();
00743 return a;
00744 }
00745 }
00746
00747 QStringList GPGProc::readStatusLines()
00748 {
00749 QStringList out = d->statusLines;
00750 d->statusLines.clear();
00751 return out;
00752 }
00753
00754 void GPGProc::writeStdin(const QByteArray &a)
00755 {
00756 if(!d->proc || a.isEmpty())
00757 return;
00758
00759 if(d->proc->state() == QProcess::Running)
00760 d->proc->write(a);
00761 else
00762 d->pre_stdin += a;
00763 }
00764
00765 void GPGProc::writeAux(const QByteArray &a)
00766 {
00767 if(!d->proc || a.isEmpty())
00768 return;
00769
00770 if(d->proc->state() == QProcess::Running)
00771 d->pipeAux.writeEnd().write(a);
00772 else
00773 d->pre_aux += a;
00774 }
00775
00776 #ifdef QPIPE_SECURE
00777 void GPGProc::writeCommand(const SecureArray &a)
00778 #else
00779 void GPGProc::writeCommand(const QByteArray &a)
00780 #endif
00781 {
00782 if(!d->proc || a.isEmpty())
00783 return;
00784
00785 if(d->proc->state() == QProcess::Running)
00786 #ifdef QPIPE_SECURE
00787 d->pipeCommand.writeEnd().writeSecure(a);
00788 #else
00789 d->pipeCommand.writeEnd().write(a);
00790 #endif
00791 else
00792 d->pre_command += a;
00793 }
00794
00795 void GPGProc::closeStdin()
00796 {
00797 if(!d->proc)
00798 return;
00799
00800 if(d->proc->state() == QProcess::Running)
00801 d->proc->closeWriteChannel();
00802 else
00803 d->pre_stdin_close = true;
00804 }
00805
00806 void GPGProc::closeAux()
00807 {
00808 if(!d->proc)
00809 return;
00810
00811 if(d->proc->state() == QProcess::Running)
00812 d->pipeAux.writeEnd().close();
00813 else
00814 d->pre_aux_close = true;
00815 }
00816
00817 void GPGProc::closeCommand()
00818 {
00819 if(!d->proc)
00820 return;
00821
00822 if(d->proc->state() == QProcess::Running)
00823 d->pipeCommand.writeEnd().close();
00824 else
00825 d->pre_command_close = true;
00826 }
00827
00828 }
00829
00830 #include "gpgproc.moc"