#include"gpgop.h" #include #include #include #include #include"gpgproc/gpgproc.h" static QByteArray stringToArray(const QString &str) { QCString cs = str.utf8(); QByteArray a(cs.length()); memcpy(a.data(), cs.data(), a.size()); return a; } class GpgOp::Private { public: Private() {} QString bin, home; int op; GPGProc *proc; bool tryAgent; QByteArray outbuf, errbuf; bool didPassphrase; QString sigKeyID; QDateTime sigTS; int verType; OpenPGP::KeyList keys; QString keyring; bool badpp; QString enc; QByteArray dec; }; GpgOp::GpgOp(const QString &bin, const QString &home, QObject *parent) :QObject(parent) { d = new Private; d->bin = bin; d->home = home; d->proc = 0; d->op = -1; d->tryAgent = true; } GpgOp::~GpgOp() { reset(); delete d; } void GpgOp::reset() { if(d->proc) { d->proc->disconnect(this); d->proc->deleteLater(); d->proc = 0; } d->sigKeyID = ""; d->sigTS = QDateTime(); d->didPassphrase = false; d->outbuf.resize(0); d->errbuf.resize(0); d->keys.clear(); d->keyring = ""; d->verType = OpenPGP::VerifyError; d->badpp = false; d->enc = ""; d->dec.resize(0); } void GpgOp::stop() { reset(); } bool GpgOp::isActive() const { return (d->proc ? true: false); } int GpgOp::op() const { return d->op; } const OpenPGP::KeyList & GpgOp::keys() const { return d->keys; } const QString & GpgOp::keyringFile() const { return d->keyring; } const QString & GpgOp::keyID() const { return d->sigKeyID; } const QDateTime & GpgOp::timestamp() const { return d->sigTS; } int GpgOp::verifyResult() const { return d->verType; } bool GpgOp::badPassphrase() const { return d->badpp; } const QString & GpgOp::encrypted() const { return d->enc; } const QByteArray & GpgOp::decrypted() const { return d->dec; } const QString & GpgOp::signature() const { return d->enc; } void GpgOp::setTryAgent(bool b) { d->tryAgent = b; } void GpgOp::doCheck() { reset(); d->op = Check; QStringList args; args += "--version"; if(!launchGPG(args, false)) { QTimer::singleShot(0, this, SLOT(doFail())); return; } } void GpgOp::doSecretKeyringFile() { reset(); d->op = SecretKeyringFile; QStringList args; args += "--list-secret-keys"; if(!launchGPG(args, false)) { QTimer::singleShot(0, this, SLOT(doFail())); return; } } void GpgOp::doPublicKeyringFile() { reset(); d->op = PublicKeyringFile; QStringList args; args += "--list-public-keys"; if(!launchGPG(args, false)) { QTimer::singleShot(0, this, SLOT(doFail())); return; } } void GpgOp::doSecretKeys() { reset(); d->op = SecretKeys; QStringList args; args += "--fixed-list-mode"; args += "--with-colons"; args += "--list-secret-keys"; if(!launchGPG(args, false)) { QTimer::singleShot(0, this, SLOT(doFail())); return; } } void GpgOp::doPublicKeys() { reset(); d->op = PublicKeys; QStringList args; args += "--fixed-list-mode"; args += "--with-colons"; args += "--list-public-keys"; if(!launchGPG(args, false)) { QTimer::singleShot(0, this, SLOT(doFail())); return; } } void GpgOp::doEncrypt(const QByteArray &in, const QStringList &keys) { reset(); d->op = Encrypt; QStringList args; args += "--armor"; args += "--always-trust"; args += "--encrypt"; // recipients for(QStringList::ConstIterator it = keys.begin(); it != keys.end(); ++it) { args += "--recipient"; args += QString("0x") + *it; } if(!launchGPG(args)) { QTimer::singleShot(0, this, SLOT(doFail())); return; } if(!in.isEmpty()) d->proc->writeToStdin(in); else d->proc->closeStdin(); } void GpgOp::doDecrypt(const QString &in) { reset(); d->op = Decrypt; QStringList args; if(!d->tryAgent) args += "--no-use-agent"; args += "--armor"; args += "--decrypt"; if(!launchGPG(args)) { QTimer::singleShot(0, this, SLOT(doFail())); return; } if(!in.isEmpty()) d->proc->writeToStdin(stringToArray(fixOutgoingLines(in))); else d->proc->closeStdin(); } void GpgOp::doSign(const QByteArray &in, const QString &keyID) { reset(); d->op = Sign; QStringList args; if(!d->tryAgent) args += "--no-use-agent"; args += "--armor"; args += "--default-key"; args += QString("0x") + keyID; args += "--detach-sign"; if(!launchGPG(args)) { QTimer::singleShot(0, this, SLOT(doFail())); return; } if(!in.isEmpty()) d->proc->writeToStdin(in); else d->proc->closeStdin(); } void GpgOp::doVerify(const QByteArray &in, const QString &sig) { reset(); d->op = Verify; d->verType = OpenPGP::VerifyError; QStringList args; args += "--armor"; args += "--verify"; args += "-"; args += "-&?"; if(!launchGPG(args)) { QTimer::singleShot(0, this, SLOT(doFail())); return; } // sig goes into stdin if(!sig.isEmpty()) d->proc->writeToStdin(stringToArray(fixOutgoingLines(sig))); else d->proc->closeStdin(); // data goes into aux d->proc->writeToAux(in); d->proc->closeAux(); } void GpgOp::submitPassphrase(const QString &pp) { QCString cs = pp.local8Bit() + '\n'; QByteArray a(cs.length()); memcpy(a.data(), cs.data(), a.size()); d->proc->writeToCommand(a); } void GpgOp::doFail() { finished(false); } bool GpgOp::launchGPG(const QStringList &args, bool useExtra) { QStringList localargs(args); // can't add extra strings in a QStringList&; this solution works if (!(d->home.isEmpty())) { //GnuPG homedir specified localargs += "--homedir"; localargs += d->home; } d->proc = new GPGProc; connect(d->proc, SIGNAL(readyReadStdout()), SLOT(proc_readyReadStdout())); connect(d->proc, SIGNAL(readyReadStderr()), SLOT(proc_readyReadStderr())); connect(d->proc, SIGNAL(processExited()), SLOT(proc_processExited())); connect(d->proc, SIGNAL(wroteToStdin()), SLOT(proc_wroteToStdin())); connect(d->proc, SIGNAL(statusLine(const QString &)), SLOT(proc_statusLine(const QString &))); if(!d->proc->start(d->bin, localargs, useExtra)) { d->proc->disconnect(this); d->proc->deleteLater(); d->proc = 0; return false; } return true; } void GpgOp::proc_readyReadStdout() { QByteArray block = d->proc->readStdout(); int oldsize = d->outbuf.size(); d->outbuf.resize(oldsize + block.size()); memcpy(d->outbuf.data() + oldsize, block.data(), block.size()); } void GpgOp::proc_readyReadStderr() { QByteArray block = d->proc->readStderr(); int oldsize = d->errbuf.size(); d->errbuf.resize(oldsize + block.size()); memcpy(d->errbuf.data() + oldsize, block.data(), block.size()); } void GpgOp::proc_wroteToStdin() { d->proc->closeStdin(); } void GpgOp::proc_statusLine(const QString &str) { #ifdef GPG_DEBUG printf("{%s}\n", str.latin1()); #endif QString s, rest; int n = str.find(' '); if(n == -1) { s = str; rest = ""; } else { s = str.mid(0, n); rest = str.mid(n+1); } if(s == "NEED_PASSPHRASE") { if(!(d->tryAgent && getenv("GPG_AGENT_INFO"))) { if(!d->didPassphrase) { d->didPassphrase = true; needPassphrase(); } else { QByteArray a(1); a[0] = '\n'; d->proc->writeToCommand(a); } } } else if(s == "BAD_PASSPHRASE") { d->badpp = true; } else if(s == "GOOD_PASSPHRASE") { d->badpp = false; } else if(s == "GOODSIG") { QString keyID; int n = rest.find(' '); if(n == -1) keyID = rest; else keyID = rest.mid(0, n); d->sigKeyID = keyID; d->verType = OpenPGP::VerifyGood; } else if(s == "BADSIG") { QString keyID; int n = rest.find(' '); if(n == -1) keyID = rest; else keyID = rest.mid(0, n); d->sigKeyID = keyID; d->verType = OpenPGP::VerifyBad; } else if(s == "ERRSIG") { QStringList list = QStringList::split(' ', rest, false); d->sigKeyID = list[0]; d->sigTS.setTime_t(list[4].toInt()); d->verType = OpenPGP::VerifyNoKey; } else if(s == "VALIDSIG") { QStringList list = QStringList::split(' ', rest, false); d->sigTS.setTime_t(list[2].toInt()); } } void GpgOp::proc_processExited() { if(!d->proc) return; #ifdef GPG_DEBUG printf("GPG Finished: "); #endif bool clean = true; int exitStatus = -1; if(!d->proc->normalExit()) { clean = false; #ifdef GPG_DEBUG printf("bad exit.. crash?\n"); #endif } else { exitStatus = d->proc->exitStatus(); #ifdef GPG_DEBUG printf("exitStatus=%d\n", exitStatus); #endif } d->proc->disconnect(this); d->proc->deleteLater(); d->proc = 0; processResult(clean, exitStatus, d->outbuf, d->errbuf); } void GpgOp::processResult(bool clean, int code, const QByteArray &out, const QByteArray &err) { // put stdout and stderr into QStrings QCString cs; cs.resize(out.size()+1); memcpy(cs.data(), out.data(), out.size()); QString outstr = QString::fromLatin1(cs); cs.resize(err.size()+1); memcpy(cs.data(), err.data(), err.size()); QString errstr = QString::fromLatin1(cs); #ifdef GPG_DEBUG printf("stdout: [%s]\n", outstr.latin1()); printf("stderr: [%s]\n", errstr.latin1()); #endif if(d->op == Check) { if(!clean || code != 0) { doFail(); return; } finished(true); } else if(d->op == SecretKeyringFile || d->op == PublicKeyringFile) { if(!clean || code != 0) { doFail(); return; } if(!findKeyringFilename(fixIncomingLines(outstr), &d->keyring)) { doFail(); return; } finished(true); } else if(d->op == SecretKeys || d->op == PublicKeys) { if(!clean || code != 0) { doFail(); return; } if(!stringToKeyList(fixIncomingLines(outstr), &d->keys, &d->keyring)) { doFail(); return; } finished(true); } else if(d->op == Encrypt) { if(!clean || code != 0) { doFail(); return; } d->enc = fixIncomingLines(outstr); finished(true); } else if(d->op == Decrypt) { if(!clean || code != 0) { doFail(); return; } d->dec = out; finished(true); } else if(d->op == Sign) { if(!clean || code != 0) { doFail(); return; } d->enc = fixIncomingLines(outstr); finished(true); } else if(d->op == Verify) { if(!clean || code != 0) { doFail(); return; } finished(true); } } QString GpgOp::fixIncomingLines(const QString &str) { #ifdef Q_WS_WIN QString out; for(int n = 0; n < (int)str.length(); ++n) { if(str.at(n) == '\r' && (n + 1) < (int)str.length() && str.at(n+1) == '\n') { out += '\n'; ++n; } else out += str.at(n); } return out; #else return str; #endif } QString GpgOp::fixOutgoingLines(const QString &str) { #ifdef Q_WS_WIN QString out; for(int n = 0; n < (int)str.length(); ++n) { if(str.at(n) == '\n' && (n == 0 || str.at(n-1) != '\r')) out += "\r\n"; else out += str.at(n); } return out; #else return str; #endif } bool GpgOp::stringToKeyList(const QString &outstr, OpenPGP::KeyList *_keylist, QString *_keyring) { OpenPGP::KeyList keyList; QStringList lines = QStringList::split('\n', outstr, true); if(lines.count() < 1) return false; QStringList::ConstIterator it = lines.begin(); // first line is keyring file QString keyring = *(it++); // if the second line isn't a divider, we are dealing // with a new version of gnupg that doesn't give us // the keyring file on gpg --list-keys --with-colons if(it == lines.end() || (*it).at(0) != '-') { // first line wasn't the keyring name... keyring = ""; // ...so read the first line again it--; } else { // this was the divider line - skip it it++; } OpenPGP::Key *k = 0; for(; it != lines.end(); ++it) { QStringList f = QStringList::split(':', *it, true); QString type = f[0]; if(type == "pub") { if(k) { keyList += (*k); delete k; k = 0; } k = new OpenPGP::Key; k->setKeyID(f[4]); } else if(type == "sec") { if(k) { keyList += (*k); delete k; k = 0; } k = new OpenPGP::Key; k->setKeyID(f[4]); } else if(type == "uid") { if(!k) continue; // ignore a uid if we already have one if(!k->userID().isEmpty()) continue; QString s = f[9]; QCString uid; // convert the "backslash" C-string syntax for(int n = 0; n < (int)s.length(); ++n) { if(s.at(n) == '\\' && n + 1 < (int)s.length()) { ++n; unsigned char c = (unsigned char)s.at(n).latin1(); if(c == '\\') uid += '\\'; else if(c == 'x' && n + 2 < (int)s.length()) { ++n; QString hex = s.mid(n, 2); bool ok; uint val = hex.toInt(&ok, 16); uid += (unsigned char)val; ++n; // only skip one, the for-loop will skip the next } } else { uid += (unsigned char)s.at(n).latin1(); } } k->setUserID(QString::fromUtf8(uid)); } } if(k) { keyList += (*k); delete k; k = 0; } if(_keylist) *_keylist = keyList; if(_keyring) *_keyring = keyring; return true; } bool GpgOp::findKeyringFilename(const QString &outstr, QString *_keyring) { QStringList lines = QStringList::split('\n', outstr, true); if(lines.count() < 1) return false; *_keyring = lines[0]; return true; }