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 | |
20 | extern int kdesuDebugArea(); |
21 | |
22 | namespace KDESu |
23 | { |
24 | using namespace KDESuPrivate; |
25 | |
26 | class SshProcessPrivate : public StubProcessPrivate |
27 | { |
28 | public: |
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 | |
40 | SshProcess::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 | |
48 | SshProcess::~SshProcess() = default; |
49 | |
50 | void SshProcess::setHost(const QByteArray &host) |
51 | { |
52 | Q_D(SshProcess); |
53 | |
54 | d->host = host; |
55 | } |
56 | |
57 | void SshProcess::setStub(const QByteArray &stub) |
58 | { |
59 | Q_D(SshProcess); |
60 | |
61 | d->stub = stub; |
62 | } |
63 | |
64 | int SshProcess::checkInstall(const char *password) |
65 | { |
66 | return exec(password, check: 1); |
67 | } |
68 | |
69 | int SshProcess::checkNeedPassword() |
70 | { |
71 | return exec(password: nullptr, check: 2); |
72 | } |
73 | |
74 | int 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 | |
137 | QByteArray SshProcess::prompt() const |
138 | { |
139 | Q_D(const SshProcess); |
140 | |
141 | return d->prompt; |
142 | } |
143 | |
144 | QByteArray 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 | */ |
163 | int 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. |
234 | QByteArray SshProcess::display() |
235 | { |
236 | return "no" ; |
237 | } |
238 | |
239 | QByteArray SshProcess::displayAuth() |
240 | { |
241 | return "no" ; |
242 | } |
243 | |
244 | void SshProcess::virtual_hook(int id, void *data) |
245 | { |
246 | StubProcess::virtual_hook(id, data); |
247 | } |
248 | |
249 | } // namespace KDESu |
250 | |