File indexing completed on 2024-04-21 03:53:58

0001 /*
0002     This file is part of the KDE project, module kdesu.
0003     SPDX-FileCopyrightText: 2000 Geert Jansen <jansen@kde.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-only
0006 
0007     ssh.cpp: Execute a program on a remote machine using ssh.
0008 */
0009 
0010 #include "sshprocess.h"
0011 
0012 #include "kcookie_p.h"
0013 #include "stubprocess_p.h"
0014 #include <ksu_debug.h>
0015 
0016 #include <signal.h>
0017 #include <time.h>
0018 #include <unistd.h>
0019 
0020 extern int kdesuDebugArea();
0021 
0022 namespace KDESu
0023 {
0024 using namespace KDESuPrivate;
0025 
0026 class SshProcessPrivate : public StubProcessPrivate
0027 {
0028 public:
0029     SshProcessPrivate(const QByteArray &host)
0030         : host(host)
0031         , stub("kdesu_stub")
0032     {
0033     }
0034     QByteArray prompt;
0035     QByteArray host;
0036     QByteArray error;
0037     QByteArray stub;
0038 };
0039 
0040 SshProcess::SshProcess(const QByteArray &host, const QByteArray &user, const QByteArray &command)
0041     : StubProcess(*new SshProcessPrivate(host))
0042 {
0043     m_user = user;
0044     m_command = command;
0045     srand(time(nullptr));
0046 }
0047 
0048 SshProcess::~SshProcess() = default;
0049 
0050 void SshProcess::setHost(const QByteArray &host)
0051 {
0052     Q_D(SshProcess);
0053 
0054     d->host = host;
0055 }
0056 
0057 void SshProcess::setStub(const QByteArray &stub)
0058 {
0059     Q_D(SshProcess);
0060 
0061     d->stub = stub;
0062 }
0063 
0064 int SshProcess::checkInstall(const char *password)
0065 {
0066     return exec(password, 1);
0067 }
0068 
0069 int SshProcess::checkNeedPassword()
0070 {
0071     return exec(nullptr, 2);
0072 }
0073 
0074 int SshProcess::exec(const char *password, int check)
0075 {
0076     Q_D(SshProcess);
0077 
0078     if (check) {
0079         setTerminal(true);
0080     }
0081 
0082     QList<QByteArray> args;
0083     args += "-l";
0084     args += m_user;
0085     args += "-o";
0086     args += "StrictHostKeyChecking=no";
0087     args += d->host;
0088     args += d->stub;
0089 
0090     if (StubProcess::exec("ssh", args) < 0) {
0091         return check ? SshNotFound : -1;
0092     }
0093 
0094     int ret = converseSsh(password, check);
0095     if (ret < 0) {
0096         if (!check) {
0097             qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] "
0098                                 << "Conversation with ssh failed.";
0099         }
0100         return ret;
0101     }
0102     if (check == 2) {
0103         if (ret == 1) {
0104             kill(m_pid, SIGTERM);
0105             waitForChild();
0106         }
0107         return ret;
0108     }
0109 
0110     if (m_erase && password) {
0111         memset(const_cast<char *>(password), 0, qstrlen(password));
0112     }
0113 
0114     ret = converseStub(check);
0115     if (ret < 0) {
0116         if (!check) {
0117             qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] "
0118                                 << "Conversation with kdesu_stub failed.";
0119         }
0120         return ret;
0121     } else if (ret == 1) {
0122         kill(m_pid, SIGTERM);
0123         waitForChild();
0124         ret = SshIncorrectPassword;
0125     }
0126 
0127     if (check == 1) {
0128         waitForChild();
0129         return 0;
0130     }
0131 
0132     setExitString("Waiting for forwarded connections to terminate");
0133     ret = waitForChild();
0134     return ret;
0135 }
0136 
0137 QByteArray SshProcess::prompt() const
0138 {
0139     Q_D(const SshProcess);
0140 
0141     return d->prompt;
0142 }
0143 
0144 QByteArray SshProcess::error() const
0145 {
0146     Q_D(const SshProcess);
0147 
0148     return d->error;
0149 }
0150 
0151 /*
0152  * Conversation with ssh.
0153  * If check is 0, this waits for either a "Password: " prompt,
0154  * or the header of the stub. If a prompt is received, the password is
0155  * written back. Used for running a command.
0156  * If check is 1, operation is the same as 0 except that if a stub header is
0157  * received, the stub is stopped with the "stop" command. This is used for
0158  * checking a password.
0159  * If check is 2, operation is the same as 1, except that no password is
0160  * written. The prompt is saved to prompt. Used for checking the need for
0161  * a password.
0162  */
0163 int SshProcess::converseSsh(const char *password, int check)
0164 {
0165     Q_D(SshProcess);
0166 
0167     unsigned i;
0168     unsigned j;
0169     unsigned colon;
0170 
0171     QByteArray line;
0172     int state = 0;
0173 
0174     while (state < 2) {
0175         line = readLine();
0176         const uint len = line.length();
0177         if (line.isNull()) {
0178             return -1;
0179         }
0180 
0181         switch (state) {
0182         case 0:
0183             // Check for "kdesu_stub" header.
0184             if (line == "kdesu_stub") {
0185                 unreadLine(line);
0186                 return 0;
0187             }
0188 
0189             // Match "Password: " with the regex ^[^:]+:[\w]*$.
0190             for (i = 0, j = 0, colon = 0; i < len; ++i) {
0191                 if (line[i] == ':') {
0192                     j = i;
0193                     colon++;
0194                     continue;
0195                 }
0196                 if (!isspace(line[i])) {
0197                     j++;
0198                 }
0199             }
0200             if ((colon == 1) && (line[j] == ':')) {
0201                 if (check == 2) {
0202                     d->prompt = line;
0203                     return SshNeedsPassword;
0204                 }
0205                 if (waitSlave()) {
0206                     return -1;
0207                 }
0208                 write(fd(), password, strlen(password));
0209                 write(fd(), "\n", 1);
0210                 state++;
0211                 break;
0212             }
0213 
0214             // Warning/error message.
0215             d->error += line;
0216             d->error += '\n';
0217             if (m_terminal) {
0218                 fprintf(stderr, "ssh: %s\n", line.constData());
0219             }
0220             break;
0221 
0222         case 1:
0223             if (line.isEmpty()) {
0224                 state++;
0225                 break;
0226             }
0227             return -1;
0228         }
0229     }
0230     return 0;
0231 }
0232 
0233 // Display redirection is handled by ssh natively.
0234 QByteArray SshProcess::display()
0235 {
0236     return "no";
0237 }
0238 
0239 QByteArray SshProcess::displayAuth()
0240 {
0241     return "no";
0242 }
0243 
0244 void SshProcess::virtual_hook(int id, void *data)
0245 {
0246     StubProcess::virtual_hook(id, data);
0247 }
0248 
0249 } // namespace KDESu