/* * Copyright (C) 2007,2008,2009,2010,2011,2012,2013 Rolf Eike Beer */ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "gpgproc.h" #include "kgpgsettings.h" #include #include #include #include #include #include class GnupgBinary { public: GnupgBinary(); const QString &binary() const; void setBinary(const QString &executable); const QStringList &standardArguments() const; unsigned int version() const; bool supportsDebugLevel() const; private: QString m_binary; QStringList m_standardArguments; unsigned int m_version; bool m_useDebugLevel; }; GnupgBinary::GnupgBinary() : m_version(0), m_useDebugLevel(false) { } const QString &GnupgBinary::binary() const { return m_binary; } /** * @brief check if GnuPG returns an error for this arguments * @param executable the GnuPG executable to call * @param arguments the arguments to pass to executable * * The arguments will be used together with "--version", so they should not * be any commands. */ static bool checkGnupgArguments(const QString &executable, const QStringList &arguments) { KProcess gpg; // We ignore the output anyway, just make sure it doesn't clutter the output of // the parent process. Simplify the handling by putting all trash in one can. gpg.setOutputChannelMode(KProcess::MergedChannels); QStringList allArguments = arguments; allArguments << QLatin1String("--version"); gpg.setProgram(executable, allArguments); return (gpg.execute() == 0); } static QString getGpgProcessHome(const QString &binary) { GPGProc process(0, binary); process << QLatin1String( "--version" ); process.start(); process.waitForFinished(-1); if (process.exitCode() == 255) { return QString(); } QString line; while (process.readln(line) != -1) { if (line.startsWith(QLatin1String("Home: "))) { line.remove(0, 6); return line.trimmed(); } } return QString(); } void GnupgBinary::setBinary(const QString &executable) { kDebug(2100) << "checking version of GnuPG executable" << executable; // must be set first as gpgVersionString() uses GPGProc to parse the output m_binary = executable; const QString verstr = GPGProc::gpgVersionString(executable); m_version = GPGProc::gpgVersion(verstr); kDebug(2100) << "version is" << verstr << m_version; m_useDebugLevel = (m_version > 0x20000); const QString gpgConfigFile = KGpgSettings::gpgConfigPath(); m_standardArguments.clear(); m_standardArguments << QLatin1String( "--no-secmem-warning" ) << QLatin1String( "--no-tty" ) << QLatin1String("--no-greeting"); if (!gpgConfigFile.isEmpty()) { m_standardArguments << QLatin1String("--options") << gpgConfigFile; // Check if the config file is in the default home directory // of the binary. If it isn't add --homedir to command line also. QString gpgdir = GPGProc::getGpgHome(executable); gpgdir.chop(1); // remove trailing '/' as QFileInfo returns string without it QFileInfo confFile(gpgConfigFile); if (confFile.absolutePath() != gpgdir) m_standardArguments << QLatin1String("--homedir") << confFile.absolutePath(); } QStringList debugLevelArguments(QLatin1String("--debug-level")); debugLevelArguments << QLatin1String("none"); if (checkGnupgArguments(executable, debugLevelArguments)) m_standardArguments << debugLevelArguments; } const QStringList& GnupgBinary::standardArguments() const { return m_standardArguments; } unsigned int GnupgBinary::version() const { return m_version; } bool GnupgBinary::supportsDebugLevel() const { return m_useDebugLevel; } K_GLOBAL_STATIC(GnupgBinary, lastBinary) GPGProc::GPGProc(QObject *parent, const QString &binary) : KLineBufferedProcess(parent) { resetProcess(binary); } GPGProc::~GPGProc() { } void GPGProc::resetProcess(const QString &binary) { GnupgBinary *bin = lastBinary; QString executable; if (binary.isEmpty()) executable = KGpgSettings::gpgBinaryPath(); else executable = binary; if (bin->binary() != executable) bin->setBinary(executable); setProgram(executable, bin->standardArguments()); setOutputChannelMode(OnlyStdoutChannel); disconnect(SIGNAL(finished(int,QProcess::ExitStatus))); disconnect(SIGNAL(lineReadyStandardOutput())); } void GPGProc::start() { // make sure there is exactly one connection from us to that signal connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(finished()), Qt::UniqueConnection); connect(this, SIGNAL(lineReadyStandardOutput()), this, SLOT(received()), Qt::UniqueConnection); KProcess::start(); } void GPGProc::received() { emit readReady(); } void GPGProc::finished() { emit processExited(); } int GPGProc::readln(QString &line, const bool colons) { QByteArray a; if (!readLineStandardOutput(&a)) return -1; line = recode(a, colons, m_codec); return line.length(); } int GPGProc::readln(QStringList &l) { QString s; int len = readln(s); if (len < 0) return len; l = s.split(QLatin1Char( ':' )); for (int i = 0; i < l.count(); ++i) { int j = 0; while ((j = l[i].indexOf(QLatin1String( "\\x3a" ), j, Qt::CaseInsensitive)) >= 0) { l[i].replace(j, 4, QLatin1Char( ':' )); j++; } } return l.count(); } QString GPGProc::recode(QByteArray a, const bool colons, const QByteArray &codec) { const char *textcodec = codec.isEmpty() ? "utf8" : codec.constData(); int pos = 0; while ((pos = a.indexOf("\\x", pos)) >= 0) { if (pos > a.length() - 4) break; const QByteArray pattern(a.mid(pos, 4)); const QByteArray hexnum(pattern.right(2)); bool ok; char n[2]; n[0] = hexnum.toUShort(&ok, 16); n[1] = '\0'; // to use n as a 0-terminated string if (!ok) { // skip this occurrence pos += 2; continue; } // QLatin1Char( ':' ) must be skipped, it is used as column delimiter // since it is pure ascii it can be replaced in QString. if (!colons && (n[0] == ':' )) { pos += 3; continue; } // it is likely to find the same byte sequence more than once int npos = pos; do { a.replace(npos, 4, n); } while ((npos = a.indexOf(pattern, npos)) >= 0); } return QTextCodec::codecForName(textcodec)->toUnicode(a); } bool GPGProc::setCodec(const QByteArray &codec) { const QList codecs = QTextCodec::availableCodecs(); if (!codecs.contains(codec)) return false; m_codec = codec; return true; } int GPGProc::gpgVersion(const QString &vstr) { if (vstr.isEmpty()) return -1; QStringList values(vstr.split(QLatin1Char( '.' ))); if (values.count() < 3) return -2; return (0x10000 * values[0].toInt() + 0x100 * values[1].toInt() + values[2].toInt()); } QString GPGProc::gpgVersionString(const QString &binary) { GPGProc process(0, binary); process << QLatin1String( "--version" ); process.start(); process.waitForFinished(-1); if (process.exitCode() == 255) return QString(); QString line; if (process.readln(line) != -1) return line.simplified().section(QLatin1Char( ' ' ), -1); else return QString(); } QString GPGProc::getGpgStartupError(const QString &binary) { GPGProc process(0, binary); process << QLatin1String( "--version" ); process.start(); process.waitForFinished(-1); QString result; while (process.hasLineStandardError()) { QByteArray tmp; process.readLineStandardError(&tmp); tmp += '\n'; result += QString::fromUtf8(tmp); } return result; } QString GPGProc::getGpgHome(const QString &binary) { // First try: if environment is set GnuPG will use that directory // We can use this directly without starting a new process QByteArray env(qgetenv("GNUPGHOME")); QString gpgHome; if (!env.isEmpty()) { gpgHome = QLatin1String( env ); } else if (!binary.isEmpty()) { // Second try: start GnuPG and ask what it is gpgHome = getGpgProcessHome(binary); } // Third try: guess what it is. if (gpgHome.isEmpty()) { #ifdef Q_OS_WIN32 //krazy:exclude=cpp gpgHome = qgetenv("APPDATA") + QLatin1String( "/gnupg/" ); gpgHome.replace(QLatin1Char( '\\' ), QLatin1Char( '/' )); #else gpgHome = QDir::homePath() + QLatin1String( "/.gnupg/" ); #endif } gpgHome.replace(QLatin1String( "//" ), QLatin1String( "/" )); if (!gpgHome.endsWith(QLatin1Char( '/' ))) gpgHome.append(QLatin1Char( '/' )); if (gpgHome.startsWith(QLatin1String("~/"))) gpgHome.replace(0, 1, QDir::homePath()); KStandardDirs::makeDir(gpgHome, 0700); return gpgHome; } #include "gpgproc.moc"