File indexing completed on 2024-05-05 05:44:47

0001 /***************************************************************************
0002  *   Copyright (C) 2005-2009 by Rajko Albrecht  ral@alwins-world.de        *
0003  *   https://kde.org/applications/development/org.kde.kdesvn               *
0004  *                                                                         *
0005  *   This program is free software; you can redistribute it and/or modify  *
0006  *   it under the terms of the GNU General Public License as published by  *
0007  *   the Free Software Foundation; either version 2 of the License, or     *
0008  *   (at your option) any later version.                                   *
0009  *                                                                         *
0010  *   This program is distributed in the hope that it will be useful,       *
0011  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0012  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0013  *   GNU General Public License for more details.                          *
0014  *                                                                         *
0015  *   You should have received a copy of the GNU General Public License     *
0016  *   along with this program; if not, write to the                         *
0017  *   Free Software Foundation, Inc.,                                       *
0018  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
0019  ***************************************************************************/
0020 /*
0021  * Copyright (c) 2003 Christian Loose <christian.loose@hamburg.de>
0022  */
0023 
0024 #include "sshagent.h"
0025 #include "kdesvn-config.h"
0026 
0027 #include "kdesvn_debug.h"
0028 #include <KProcess>
0029 #include <QCoreApplication>
0030 #include <QRegExp>
0031 #include <QStandardPaths>
0032 
0033 // initialize static member variables
0034 bool SshAgent::m_isRunning = false;
0035 bool SshAgent::m_isOurAgent = false;
0036 bool SshAgent::m_addIdentitiesDone = false;
0037 QString SshAgent::m_authSock;
0038 QString SshAgent::m_pid;
0039 
0040 class SshClean
0041 {
0042 public:
0043     SshClean()
0044     {
0045     }
0046 
0047     ~SshClean()
0048     {
0049         SshAgent ssh;
0050         ssh.killSshAgent();
0051     }
0052 };
0053 
0054 SshAgent::SshAgent(QObject *parent)
0055     : QObject(parent)
0056     , sshAgent(nullptr)
0057 {
0058     static SshClean st;
0059 }
0060 
0061 SshAgent::~SshAgent()
0062 {
0063 }
0064 
0065 bool SshAgent::querySshAgent()
0066 {
0067     if (m_isRunning) {
0068         return true;
0069     }
0070 
0071     // Did the user already start a ssh-agent process?
0072     const QByteArray pid = qgetenv("SSH_AGENT_PID");
0073     if (!pid.isEmpty()) {
0074         m_pid = QString::fromLocal8Bit(pid);
0075 
0076         const QByteArray sock = qgetenv("SSH_AUTH_SOCK");
0077         if (!sock.isEmpty()) {
0078             m_authSock = QString::fromLocal8Bit(sock);
0079         }
0080         /* make sure that we have a askpass program.
0081          * on some systems something like that isn't installed.*/
0082         m_isOurAgent = false;
0083         m_isRunning = true;
0084     }
0085     // We have to start a new ssh-agent process
0086     else {
0087         m_isOurAgent = true;
0088         m_isRunning = startSshAgent();
0089     }
0090     askPassEnv();
0091     return m_isRunning;
0092 }
0093 
0094 void SshAgent::askPassEnv()
0095 {
0096 #ifdef FORCE_ASKPASS
0097     qCDebug(KDESVN_LOG) << "Using test askpass" << Qt::endl;
0098     qputenv("SSH_ASKPASS", FORCE_ASKPASS);
0099 #else
0100     const QString kdesvnAskPass(QStringLiteral("kdesvnaskpass"));
0101     // first search nearby us
0102     QString askPassPath = QStandardPaths::findExecutable(kdesvnAskPass, {QCoreApplication::applicationDirPath()});
0103     if (askPassPath.isEmpty()) {
0104         // now search in PATH
0105         askPassPath = QStandardPaths::findExecutable(kdesvnAskPass);
0106     }
0107     if (askPassPath.isEmpty()) {
0108         // ok, not found, but maybe ssh-agent does ...
0109         askPassPath = kdesvnAskPass;
0110     }
0111     qputenv("SSH_ASKPASS", askPassPath.toLocal8Bit());
0112 #endif
0113 }
0114 
0115 bool SshAgent::addSshIdentities(bool force)
0116 {
0117     if (m_addIdentitiesDone && !force) {
0118         return true;
0119     }
0120 
0121     if (!m_isRunning) {
0122         qWarning() << "No ssh-agent is running, can not execute ssh-add";
0123         return false;
0124     }
0125 
0126     // add identities to ssh-agent
0127     KProcess proc;
0128 
0129     proc.setEnv(QStringLiteral("SSH_AGENT_PID"), m_pid);
0130     proc.setEnv(QStringLiteral("SSH_AUTH_SOCK"), m_authSock);
0131 
0132 #ifdef FORCE_ASKPASS
0133     qCDebug(KDESVN_LOG) << "Using test askpass" << Qt::endl;
0134     proc.setEnv("SSH_ASKPASS", FORCE_ASKPASS);
0135 #else
0136     qCDebug(KDESVN_LOG) << "Using kdesvnaskpass" << Qt::endl;
0137     proc.setEnv(QStringLiteral("SSH_ASKPASS"), QStringLiteral("kdesvnaskpass"));
0138 #endif
0139 
0140     proc << QStringLiteral("ssh-add");
0141     proc.start();
0142     // endless
0143     proc.waitForFinished(-1);
0144 
0145     m_addIdentitiesDone = proc.exitStatus() == QProcess::NormalExit && proc.exitStatus() == 0;
0146     askPassEnv();
0147     return m_addIdentitiesDone;
0148 }
0149 
0150 void SshAgent::killSshAgent()
0151 {
0152     if (!m_isRunning || !m_isOurAgent) {
0153         return;
0154     }
0155 
0156     QProcess proc;
0157     proc.start(QStringLiteral("kill"), {m_pid});
0158     proc.waitForFinished();
0159 }
0160 
0161 void SshAgent::slotProcessExited(int exitCode, QProcess::ExitStatus exitStatus)
0162 {
0163     if (exitStatus != QProcess::NormalExit || exitCode != 0) {
0164         return;
0165     }
0166     const QRegExp cshPidRx(QStringLiteral("setenv SSH_AGENT_PID (\\d*);"));
0167     const QRegExp cshSockRx(QStringLiteral("setenv SSH_AUTH_SOCK (.*);"));
0168 
0169     const QRegExp bashPidRx(QStringLiteral("SSH_AGENT_PID=(\\d*).*"));
0170     const QRegExp bashSockRx(QStringLiteral("SSH_AUTH_SOCK=(.*\\.\\d*);.*"));
0171     const QStringList m_outputLines = m_Output.split(QLatin1Char('\n'), QString::SkipEmptyParts);
0172 
0173     for (const auto &outputLine : m_outputLines) {
0174         if (m_pid.isEmpty()) {
0175             int pos = cshPidRx.indexIn(outputLine);
0176             if (pos > -1) {
0177                 m_pid = cshPidRx.cap(1);
0178                 continue;
0179             }
0180 
0181             pos = bashPidRx.indexIn(outputLine);
0182             if (pos > -1) {
0183                 m_pid = bashPidRx.cap(1);
0184                 continue;
0185             }
0186         }
0187 
0188         if (m_authSock.isEmpty()) {
0189             int pos = cshSockRx.indexIn(outputLine);
0190             if (pos > -1) {
0191                 m_authSock = cshSockRx.cap(1);
0192                 continue;
0193             }
0194 
0195             pos = bashSockRx.indexIn(outputLine);
0196             if (pos > -1) {
0197                 m_authSock = bashSockRx.cap(1);
0198                 continue;
0199             }
0200         }
0201     }
0202 }
0203 
0204 void SshAgent::slotReceivedStdout()
0205 {
0206     if (!sshAgent) {
0207         return;
0208     }
0209     m_Output += QString::fromLocal8Bit(sshAgent->readAllStandardOutput());
0210 }
0211 
0212 bool SshAgent::startSshAgent()
0213 {
0214     if (sshAgent) {
0215         return false;
0216     }
0217     sshAgent = new KProcess();
0218     *sshAgent << QStringLiteral("ssh-agent");
0219 
0220     sshAgent->setOutputChannelMode(KProcess::MergedChannels);
0221 
0222     connect(sshAgent, QOverload<int, QProcess::ExitStatus>::of(&KProcess::finished), this, &SshAgent::slotProcessExited);
0223     connect(sshAgent, &KProcess::readyReadStandardOutput, this, &SshAgent::slotReceivedStdout);
0224     sshAgent->start();
0225     // wait for process to finish eg. backgrounding
0226     sshAgent->waitForFinished(-1);
0227     bool ok = (sshAgent->exitStatus() == QProcess::NormalExit && sshAgent->exitStatus() == 0);
0228     delete sshAgent;
0229     sshAgent = nullptr;
0230 
0231     return ok;
0232 }
0233 
0234 #include "moc_sshagent.cpp"