1/*
2 This file is part of the KDE project, module kdesu.
3 SPDX-FileCopyrightText: 2000 Geert Jansen <jansen@kde.org>
4
5 SPDX-License-Identifier: GPL-2.0-only
6
7 ssh.cpp: Execute a program on a remote machine using ssh.
8*/
9
10#include "sshprocess.h"
11
12#include "kcookie_p.h"
13#include "stubprocess_p.h"
14#include <ksu_debug.h>
15
16#include <signal.h>
17#include <time.h>
18#include <unistd.h>
19
20extern int kdesuDebugArea();
21
22namespace KDESu
23{
24using namespace KDESuPrivate;
25
26class SshProcessPrivate : public StubProcessPrivate
27{
28public:
29 SshProcessPrivate(const QByteArray &host)
30 : host(host)
31 , stub("kdesu_stub")
32 {
33 }
34 QByteArray prompt;
35 QByteArray host;
36 QByteArray error;
37 QByteArray stub;
38};
39
40SshProcess::SshProcess(const QByteArray &host, const QByteArray &user, const QByteArray &command)
41 : StubProcess(*new SshProcessPrivate(host))
42{
43 m_user = user;
44 m_command = command;
45 srand(seed: time(timer: nullptr));
46}
47
48SshProcess::~SshProcess() = default;
49
50void SshProcess::setHost(const QByteArray &host)
51{
52 Q_D(SshProcess);
53
54 d->host = host;
55}
56
57void SshProcess::setStub(const QByteArray &stub)
58{
59 Q_D(SshProcess);
60
61 d->stub = stub;
62}
63
64int SshProcess::checkInstall(const char *password)
65{
66 return exec(password, check: 1);
67}
68
69int SshProcess::checkNeedPassword()
70{
71 return exec(password: nullptr, check: 2);
72}
73
74int SshProcess::exec(const char *password, int check)
75{
76 Q_D(SshProcess);
77
78 if (check) {
79 setTerminal(true);
80 }
81
82 QList<QByteArray> args;
83 args += "-l";
84 args += m_user;
85 args += "-o";
86 args += "StrictHostKeyChecking=no";
87 args += d->host;
88 args += d->stub;
89
90 if (StubProcess::exec(command: "ssh", args) < 0) {
91 return check ? SshNotFound : -1;
92 }
93
94 int ret = converseSsh(password, check);
95 if (ret < 0) {
96 if (!check) {
97 qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] "
98 << "Conversation with ssh failed.";
99 }
100 return ret;
101 }
102 if (check == 2) {
103 if (ret == 1) {
104 kill(pid: m_pid, SIGTERM);
105 waitForChild();
106 }
107 return ret;
108 }
109
110 if (m_erase && password) {
111 memset(s: const_cast<char *>(password), c: 0, n: qstrlen(str: password));
112 }
113
114 ret = converseStub(check);
115 if (ret < 0) {
116 if (!check) {
117 qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] "
118 << "Conversation with kdesu_stub failed.";
119 }
120 return ret;
121 } else if (ret == 1) {
122 kill(pid: m_pid, SIGTERM);
123 waitForChild();
124 ret = SshIncorrectPassword;
125 }
126
127 if (check == 1) {
128 waitForChild();
129 return 0;
130 }
131
132 setExitString("Waiting for forwarded connections to terminate");
133 ret = waitForChild();
134 return ret;
135}
136
137QByteArray SshProcess::prompt() const
138{
139 Q_D(const SshProcess);
140
141 return d->prompt;
142}
143
144QByteArray SshProcess::error() const
145{
146 Q_D(const SshProcess);
147
148 return d->error;
149}
150
151/*
152 * Conversation with ssh.
153 * If check is 0, this waits for either a "Password: " prompt,
154 * or the header of the stub. If a prompt is received, the password is
155 * written back. Used for running a command.
156 * If check is 1, operation is the same as 0 except that if a stub header is
157 * received, the stub is stopped with the "stop" command. This is used for
158 * checking a password.
159 * If check is 2, operation is the same as 1, except that no password is
160 * written. The prompt is saved to prompt. Used for checking the need for
161 * a password.
162 */
163int SshProcess::converseSsh(const char *password, int check)
164{
165 Q_D(SshProcess);
166
167 unsigned i;
168 unsigned j;
169 unsigned colon;
170
171 QByteArray line;
172 int state = 0;
173
174 while (state < 2) {
175 line = readLine();
176 const uint len = line.length();
177 if (line.isNull()) {
178 return -1;
179 }
180
181 switch (state) {
182 case 0:
183 // Check for "kdesu_stub" header.
184 if (line == "kdesu_stub") {
185 unreadLine(line);
186 return 0;
187 }
188
189 // Match "Password: " with the regex ^[^:]+:[\w]*$.
190 for (i = 0, j = 0, colon = 0; i < len; ++i) {
191 if (line[i] == ':') {
192 j = i;
193 colon++;
194 continue;
195 }
196 if (!isspace(line[i])) {
197 j++;
198 }
199 }
200 if ((colon == 1) && (line[j] == ':')) {
201 if (check == 2) {
202 d->prompt = line;
203 return SshNeedsPassword;
204 }
205 if (waitSlave()) {
206 return -1;
207 }
208 write(fd: fd(), buf: password, n: strlen(s: password));
209 write(fd: fd(), buf: "\n", n: 1);
210 state++;
211 break;
212 }
213
214 // Warning/error message.
215 d->error += line;
216 d->error += '\n';
217 if (m_terminal) {
218 fprintf(stderr, format: "ssh: %s\n", line.constData());
219 }
220 break;
221
222 case 1:
223 if (line.isEmpty()) {
224 state++;
225 break;
226 }
227 return -1;
228 }
229 }
230 return 0;
231}
232
233// Display redirection is handled by ssh natively.
234QByteArray SshProcess::display()
235{
236 return "no";
237}
238
239QByteArray SshProcess::displayAuth()
240{
241 return "no";
242}
243
244void SshProcess::virtual_hook(int id, void *data)
245{
246 StubProcess::virtual_hook(id, data);
247}
248
249} // namespace KDESu
250

source code of kdesu/src/sshprocess.cpp