sessionworker.cc 10.8 KB
Newer Older
1
2
3
4
5
6
7
8

#include "rsmcore/sessionworker.hh"
#include "radixbug/bug.hh"

#include <libssh/libssh.h>

#include <cassert>
#include <sstream>
9
10
11
12

#include <QByteArray>
#include <QString>

13
14
15
16
17
18
19
20
21
22
23
24
25
26
namespace rsm
{
void assert_ssh_session(ssh_session session, const std::string& error_message)
{
  if (session == nullptr)
  {
    throw std::runtime_error(error_message.c_str());
  }
}

class SessionWorkerImpl
{
 public:
  ssh_session session = nullptr;
27
  QByteArray output_buffer;
28
29
30
31
32
33
34
35
36
37
  SessionWorkerImpl()
  {
    session = ssh_new();
    assert_ssh_session(session, "Failed to allocate new ssh session.");
  }
  ~SessionWorkerImpl()
  {
    if (session != nullptr)
    {
      ssh_free(session);
38
      ssh_finalize();
39
40
41
42
    }
  }
};

43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
void SessionWorker::processPrompts()
{
  QString instruction = ssh_userauth_kbdint_getname(p->session);
  QString name        = ssh_userauth_kbdint_getinstruction(p->session);
  int num_prompts     = ssh_userauth_kbdint_getnprompts(p->session);
  // build list of prompts
  QStringList prompts;
  char echo;
  for (int i = 0; i < num_prompts; ++i)
  {
    QString prompt = ssh_userauth_kbdint_getprompt(
        p->session, static_cast<unsigned int>(i), &echo);
    radix_tagged_line(i << ". " << prompt.toStdString());
    if (prompt.isEmpty()) break;
    prompts << prompt;
  }
  emit interactiveAuthenticationRequested(instruction, name, prompts);
}

62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
SessionWorker::SessionWorker(QObject* parent)
    : QObject(parent)
{
  p = new SessionWorkerImpl();
}

SessionWorker::SessionWorker(QString host, QObject* parent)
    : QObject(parent)
{
  p = new SessionWorkerImpl();
  setHost(host);
}

SessionWorker::~SessionWorker()
{
  if (p != nullptr) delete p;
}

80
81
QByteArray SessionWorker::readExecOutput() { return p->output_buffer; }

82
83
84
85
86
void SessionWorker::setHost(QString host)
{
  assert_ssh_session(p->session, "setHost() -- Session is not allocated.");
  ssh_options_set(p->session, SSH_OPTIONS_HOST, host.toStdString().c_str());
}
87
void SessionWorker::setLogVerbosity(SessionVerbosity level)
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
{
  assert_ssh_session(p->session,
                     "setLogVerbosity() -- Session is not allocated.");
  int verbosity = static_cast<int>(level);
  ssh_options_set(p->session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
}
void SessionWorker::setPort(int port)
{
  assert_ssh_session(p->session, "setPort() -- Session is not allocated.");
  ssh_options_set(p->session, SSH_OPTIONS_PORT, &port);
}
void SessionWorker::setUser(QString name)
{
  assert_ssh_session(p->session, "setName() -- Session is not allocated.");
  ssh_options_set(p->session, SSH_OPTIONS_USER, name.toStdString().c_str());
}
void SessionWorker::connect()
{
  assert_ssh_session(p->session, "connect() -- Session is not allocated.");
107
108
  radix_tagged_line("connect()");
  if (ssh_is_connected(p->session) == 1)
109
  {
110
    radix_tagged_line("session already connected. disconnectinng.");
111
112
    ssh_disconnect(p->session);
  }
113
  {
114
    radix_tagged_line("Attempting connection.");
115
116
117
118
119
120
121
122
    // attempt a connection
    int rc = ssh_connect(p->session);
    if (rc != SSH_OK)
    {
      char* message;
      int rv = ssh_options_get(p->session, SSH_OPTIONS_HOST, &message);

      std::ostringstream os;
123
      if (rv == SSH_OK)  // if the error message retrieval was good
124
125
126
127
128
129
130
131
      {
        os << "Error connecting to " << message << ":";
        delete message;
      }

      os << ssh_get_error(p->session);
      emit connectionFailed(os.str().c_str());
      return;
132
133
    }
  }
134
  emit connectionSuccessful();
135
136
137
138
}
void SessionWorker::disconnect()
{
  assert_ssh_session(p->session, "disconnect() -- Session is not allocated.");
139
  radix_tagged_line("disconnect()");
140
141
142
143
  if (ssh_is_connected(p->session) != 0)
  {
    radix_tagged_line("Disconnecting session.");
    ssh_disconnect(p->session);
144
    emit disconnectSuccessful();
145
146
147
148
149
150
151
152
153
154
155
156
157
158
  }
}

void SessionWorker::verifyKnownHost()
{
  assert_ssh_session(p->session,
                     "verifyKnownHost() -- Session is not allocated.");
  enum ssh_known_hosts_e state;
  unsigned char* hash       = nullptr;
  ssh_key server_public_key = nullptr;
  char* hexa;
  size_t hlen;
  QString qhexa;

159
  radix_tagged_line("verifyKnownHost()");
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
  int rc = ssh_get_server_publickey(p->session, &server_public_key);
  if (rc < 0)
  {
    emit getServerPublicKeyFailed();
    return;
  }

  rc = ssh_get_publickey_hash(server_public_key, SSH_PUBLICKEY_HASH_SHA1, &hash,
                              &hlen);
  ssh_key_free(server_public_key);
  if (rc < 0)
  {
    emit getServerPublicKeyFailed();
    return;
  }

  state = ssh_session_is_known_server(p->session);
  switch (state)
  {
    case SSH_KNOWN_HOSTS_OK:
      /* OK */

      break;
    case SSH_KNOWN_HOSTS_CHANGED:
      hexa  = ssh_get_hexa(hash, hlen);
      qhexa = hexa;
      ssh_clean_pubkey_hash(&hash);
      radix_tagged_line("SSH_KNOWN_HOSTS_CHANGED: " << qhexa.toStdString());
      emit hostPublicKeyChanged(qhexa);
      break;
    case SSH_KNOWN_HOSTS_OTHER:
      ssh_clean_pubkey_hash(&hash);
      radix_tagged_line("SSH_KNOWN_HOSTS_OTHER");
      emit hostPublicKeyUnavailable();
      break;
    case SSH_KNOWN_HOSTS_NOT_FOUND:
      /* FALL THROUGH to SSH_SERVER_NOT_KNOWN behavior */
      radix_tagged_line("SSH_KNOWN_HOSTS_NOT_FOUND");

    case SSH_KNOWN_HOSTS_UNKNOWN:
      hexa  = ssh_get_hexa(hash, hlen);
      qhexa = hexa;
      ssh_string_free_char(hexa);
      ssh_clean_pubkey_hash(&hash);
      radix_tagged_line("SSH_KNOWN_HOSTS_UNKNOWN: " << qhexa.toStdString());
      emit hostUnknown(qhexa);
      break;
    case SSH_KNOWN_HOSTS_ERROR:
      QString message = ssh_get_error(p->session);
      radix_tagged_line("SSH_KNOWN_HOSTS_ERROR: " << message.toStdString());
      ssh_clean_pubkey_hash(&hash);
      emit knownHostError(message);
      break;
  }
214
  emit verifyKnownHostSuccessful();
215
216
217
218
219
220
221
  ssh_clean_pubkey_hash(&hash);
}

void SessionWorker::acceptHostPublicKeyUpdate()
{
  assert_ssh_session(
      p->session, "acceptHostPublicKeyUpdate() -- Session is not allocated.");
222
  radix_tagged_line("authenticateHostPublicKeyUpdate()");
223
224
225
226
227
228
229
230
231
232
233
  int rc = ssh_session_update_known_hosts(p->session);
  if (rc != SSH_OK)
  {
    emit updateKnownHostsFailed();
  }
}

void SessionWorker::authenticate()
{
  assert_ssh_session(p->session, "authenticate() -- Session is not allocated.");

234
  radix_tagged_line("Authenticate.");
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
  // Try authenticating with no credentials - rarely works
  int rc = ssh_userauth_none(p->session, nullptr);
  if (rc == SSH_AUTH_ERROR)
  {
    QString message = ssh_get_error(p->session);
    emit authenticationError(message);
    return;
  }

  // get acceptable methods
  int method = ssh_userauth_list(p->session, nullptr);

  // Try to authenticate with public key first
  if (method & SSH_AUTH_METHOD_PUBLICKEY)
  {
    rc = ssh_userauth_publickey_auto(p->session, nullptr, nullptr);
    if (rc == SSH_AUTH_ERROR)
    {
      QString message = ssh_get_error(p->session);
      emit authenticationError(message);
      return;
    }
    else if (rc == SSH_AUTH_SUCCESS)
    {
      radix_tagged_line("SSH_AUTH_METHOD_PUBLICKEY succeeded.");
      emit authenticationSucceeded();
      return;
    }
    radix_tagged_line("SSH_AUTH_METHOD_PUBLICKEY didn't work.");
  }  // public key authentication
265
266
267
268
269
270
271
272
273

  // Try to authenticate with keyboard interactive";
  if (method & SSH_AUTH_METHOD_INTERACTIVE)
  {
    int err;

    err = ssh_userauth_kbdint(p->session, nullptr, nullptr);
    while (err == SSH_AUTH_INFO)
    {
274
      processPrompts();
275
276
277
278
      return;
    }
  }

279
280
281
282
283
284
285
286
287
288
289
290
291
  // Try to authenticate with password
  if (method & SSH_AUTH_METHOD_PASSWORD)
  {
    radix_tagged_line("SSH_AUTH_METHOD_PASSWORD required.");
    emit passwordRequested();
    return;
  }
}

void SessionWorker::authenticateWithPassword(QString pswd)
{
  assert_ssh_session(p->session,
                     "authenticateWithPassword() -- Session is not allocated.");
292
  radix_tagged_line("Authenticate with password.");
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
  int rc =
      ssh_userauth_password(p->session, nullptr, pswd.toStdString().c_str());
  if (rc == SSH_AUTH_ERROR)
  {
    QString message = ssh_get_error(p->session);
    emit authenticationError(message);
  }
  else if (rc == SSH_AUTH_SUCCESS)
  {
    radix_tagged_line("SSH_AUTH_METHOD_PASSWORD succeeded.");
    emit authenticationSucceeded();
    char* banner = ssh_get_issue_banner(p->session);
    if (banner)
    {
      QString qbanner = banner;
      emit loginBannerIssued(qbanner);
      delete banner;
    }
  }
}

314
315
316
void SessionWorker::authenticatePrompts(QStringList responses)
{
  int err;
317
  radix_tagged_line("authenticatePrompts()");
318
319
  for (int i = 0; i < responses.size(); ++i)
  {
320
    radix_tagged_line("Setting response " << i);
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
    const char* answer = responses.at(i).toStdString().c_str();
    err                = ssh_userauth_kbdint_setanswer(p->session,
                                        static_cast<unsigned int>(i), answer);
    if (err < 0)
    {
      emit authenticationError("Failed to authenticate prompts.");
      ssh_disconnect(p->session);
      return;
    }
  }
  // check status
  err = ssh_userauth_kbdint(p->session, nullptr, nullptr);
  if (err == SSH_AUTH_DENIED)
  {
    emit authenticationError("Authentication denied.");
    ssh_disconnect(p->session);
    return;
  }
339
340
341
342
343
344
  if (err == SSH_AUTH_INFO)
  {
    processPrompts();
    return;
  }
  if (err == SSH_AUTH_SUCCESS)
345
346
347
348
349
350
  {
    emit authenticationSucceeded();
    return;
  }
}

351
352
353
354
void SessionWorker::requestExec(QString command)
{
  assert_ssh_session(p->session, "request_exec() -- Session is not allocated.");

355
  radix_tagged_line("requestExec(" << command.toStdString() << ")");
356
357
358
359
360
361
  char buffer[256];
  // clear any previous buffer
  p->output_buffer.clear();
  ssh_channel channel = ssh_channel_new(p->session);
  if (channel == nullptr)
  {
362
363
    radix_tagged_line("Failed to create new channel");
    emit execFailed("Failed to allocate a channel on the open session.");
364
365
366
367
368
369
    return;
  }

  int rc = ssh_channel_open_session(channel);
  if (rc < 0)
  {
370
371
    radix_tagged_line("Failed to create new channel");
    emit execFailed("Failed to open a channel on the session.");
372
373
374
375
376
377
    return;
  }

  rc = ssh_channel_request_exec(channel, command.toStdString().c_str());
  if (rc < 0)
  {
378
379
    radix_tagged_line("Failed to request exec.");
    emit execFailed("Failed to execute remote command.");
380
381
382
383
384
385
386
387
388
389
    ssh_channel_close(channel);
    ssh_channel_free(channel);
    return;
  }
  int nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0);
  while (nbytes > 0)
  {
    p->output_buffer.append(buffer, nbytes);
    nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0);
  }
390
  emit execOutputReady();
391
392
  radix_tagged_line("nbytes=" << nbytes);
  radix_tagged_line("Finished reading response\n" << p->output_buffer.data());
393
394
395
396
397

  if (nbytes == 0)
  {
    ssh_channel_send_eof(channel);
  }
398
399
400
401
  if (nbytes < 0)
  {
    emit execFailed("Failed to read response from remote command.");
  }
402
403
404

  ssh_channel_close(channel);
  ssh_channel_free(channel);
405
406
  radix_tagged_line("'" << command.toStdString() << "' finished.");
  emit execFinished();
407
408
}

409
}  // namespace rsm