Coverage Report

Created: 2023-06-07 06:44

/src/dropbear/src/cli-session.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Dropbear SSH
3
 * 
4
 * Copyright (c) 2002,2003 Matt Johnston
5
 * Copyright (c) 2004 by Mihnea Stoenescu
6
 * All rights reserved.
7
 * 
8
 * Permission is hereby granted, free of charge, to any person obtaining a copy
9
 * of this software and associated documentation files (the "Software"), to deal
10
 * in the Software without restriction, including without limitation the rights
11
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
 * copies of the Software, and to permit persons to whom the Software is
13
 * furnished to do so, subject to the following conditions:
14
 * 
15
 * The above copyright notice and this permission notice shall be included in
16
 * all copies or substantial portions of the Software.
17
 * 
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
 * SOFTWARE. */
25
26
#include "includes.h"
27
#include "session.h"
28
#include "dbutil.h"
29
#include "kex.h"
30
#include "ssh.h"
31
#include "packet.h"
32
#include "tcpfwd.h"
33
#include "channel.h"
34
#include "dbrandom.h"
35
#include "service.h"
36
#include "runopts.h"
37
#include "chansession.h"
38
#include "agentfwd.h"
39
#include "crypto_desc.h"
40
#include "netio.h"
41
42
static void cli_remoteclosed(void) ATTRIB_NORETURN;
43
static void cli_sessionloop(void);
44
static void cli_session_init(pid_t proxy_cmd_pid);
45
static void cli_finished(void) ATTRIB_NORETURN;
46
static void recv_msg_service_accept(void);
47
static void cli_session_cleanup(void);
48
static void recv_msg_global_request_cli(void);
49
50
struct clientsession cli_ses; /* GLOBAL */
51
52
/* Sorted in decreasing frequency will be more efficient - data and window
53
 * should be first */
54
static const packettype cli_packettypes[] = {
55
  /* TYPE, FUNCTION */
56
  {SSH_MSG_CHANNEL_DATA, recv_msg_channel_data},
57
  {SSH_MSG_CHANNEL_EXTENDED_DATA, recv_msg_channel_extended_data},
58
  {SSH_MSG_CHANNEL_WINDOW_ADJUST, recv_msg_channel_window_adjust},
59
  {SSH_MSG_USERAUTH_FAILURE, recv_msg_userauth_failure}, /* client */
60
  {SSH_MSG_USERAUTH_SUCCESS, recv_msg_userauth_success}, /* client */
61
  {SSH_MSG_KEXINIT, recv_msg_kexinit},
62
  {SSH_MSG_KEXDH_REPLY, recv_msg_kexdh_reply}, /* client */
63
  {SSH_MSG_NEWKEYS, recv_msg_newkeys},
64
  {SSH_MSG_SERVICE_ACCEPT, recv_msg_service_accept}, /* client */
65
  {SSH_MSG_CHANNEL_REQUEST, recv_msg_channel_request},
66
  {SSH_MSG_CHANNEL_OPEN, recv_msg_channel_open},
67
  {SSH_MSG_CHANNEL_EOF, recv_msg_channel_eof},
68
  {SSH_MSG_CHANNEL_CLOSE, recv_msg_channel_close},
69
  {SSH_MSG_CHANNEL_OPEN_CONFIRMATION, recv_msg_channel_open_confirmation},
70
  {SSH_MSG_CHANNEL_OPEN_FAILURE, recv_msg_channel_open_failure},
71
  {SSH_MSG_USERAUTH_BANNER, recv_msg_userauth_banner}, /* client */
72
  {SSH_MSG_USERAUTH_SPECIFIC_60, recv_msg_userauth_specific_60}, /* client */
73
  {SSH_MSG_GLOBAL_REQUEST, recv_msg_global_request_cli},
74
  {SSH_MSG_CHANNEL_SUCCESS, ignore_recv_response},
75
  {SSH_MSG_CHANNEL_FAILURE, ignore_recv_response},
76
#if DROPBEAR_CLI_REMOTETCPFWD
77
  {SSH_MSG_REQUEST_SUCCESS, cli_recv_msg_request_success}, /* client */
78
  {SSH_MSG_REQUEST_FAILURE, cli_recv_msg_request_failure}, /* client */
79
#else
80
  /* For keepalive */
81
  {SSH_MSG_REQUEST_SUCCESS, ignore_recv_response},
82
  {SSH_MSG_REQUEST_FAILURE, ignore_recv_response},
83
#endif
84
  {SSH_MSG_EXT_INFO, recv_msg_ext_info},
85
  {0, NULL} /* End */
86
};
87
88
static const struct ChanType *cli_chantypes[] = {
89
#if DROPBEAR_CLI_REMOTETCPFWD
90
  &cli_chan_tcpremote,
91
#endif
92
#if DROPBEAR_CLI_AGENTFWD
93
  &cli_chan_agent,
94
#endif
95
  NULL /* Null termination */
96
};
97
98
void cli_connected(int result, int sock, void* userdata, const char *errstring)
99
0
{
100
0
  struct sshsession *myses = userdata;
101
0
  if (result == DROPBEAR_FAILURE) {
102
0
    dropbear_exit("Connect failed: %s", errstring);
103
0
  }
104
0
  myses->sock_in = myses->sock_out = sock;
105
0
  DEBUG1(("cli_connected"))
106
0
  ses.socket_prio = DROPBEAR_PRIO_NORMAL;
107
  /* switches to lowdelay */
108
0
  update_channel_prio();
109
0
}
110
111
0
void cli_session(int sock_in, int sock_out, struct dropbear_progress_connection *progress, pid_t proxy_cmd_pid) {
112
113
0
  common_session_init(sock_in, sock_out);
114
115
0
  if (progress) {
116
0
    connect_set_writequeue(progress, &ses.writequeue);
117
0
  }
118
119
0
  chaninitialise(cli_chantypes);
120
121
  /* Set up cli_ses vars */
122
0
  cli_session_init(proxy_cmd_pid);
123
124
  /* Ready to go */
125
0
  ses.init_done = 1;
126
127
  /* Exchange identification */
128
0
  send_session_identification();
129
130
0
  kexfirstinitialise(); /* initialise the kex state */
131
132
0
  send_msg_kexinit();
133
134
0
  session_loop(cli_sessionloop);
135
136
  /* Not reached */
137
138
0
}
139
140
#if DROPBEAR_KEX_FIRST_FOLLOWS
141
0
static void cli_send_kex_first_guess() {
142
0
  send_msg_kexdh_init();
143
0
}
144
#endif
145
146
0
static void cli_session_init(pid_t proxy_cmd_pid) {
147
148
0
  cli_ses.state = STATE_NOTHING;
149
0
  cli_ses.kex_state = KEX_NOTHING;
150
151
0
  cli_ses.tty_raw_mode = 0;
152
0
  cli_ses.winchange = 0;
153
154
  /* We store std{in,out,err}'s flags, so we can set them back on exit
155
   * (otherwise busybox's ash isn't happy */
156
0
  cli_ses.stdincopy = dup(STDIN_FILENO);
157
0
  cli_ses.stdinflags = fcntl(STDIN_FILENO, F_GETFL, 0);
158
0
  cli_ses.stdoutcopy = dup(STDOUT_FILENO);
159
0
  cli_ses.stdoutflags = fcntl(STDOUT_FILENO, F_GETFL, 0);
160
0
  cli_ses.stderrcopy = dup(STDERR_FILENO);
161
0
  cli_ses.stderrflags = fcntl(STDERR_FILENO, F_GETFL, 0);
162
163
0
  cli_ses.retval = EXIT_SUCCESS; /* Assume it's clean if we don't get a
164
                    specific exit status */
165
0
  cli_ses.proxy_cmd_pid = proxy_cmd_pid;
166
0
  TRACE(("proxy command PID='%d'", proxy_cmd_pid));
167
168
  /* Auth */
169
0
  cli_ses.lastprivkey = NULL;
170
0
  cli_ses.lastauthtype = 0;
171
0
  cli_ses.is_trivial_auth = 1;
172
173
  /* For printing "remote host closed" for the user */
174
0
  ses.remoteclosed = cli_remoteclosed;
175
176
0
  ses.extra_session_cleanup = cli_session_cleanup;
177
178
  /* packet handlers */
179
0
  ses.packettypes = cli_packettypes;
180
181
0
  ses.isserver = 0;
182
183
0
#if DROPBEAR_KEX_FIRST_FOLLOWS
184
0
  ses.send_kex_first_guess = cli_send_kex_first_guess;
185
0
#endif
186
187
0
}
188
189
0
static void send_msg_service_request(const char* servicename) {
190
191
0
  TRACE(("enter send_msg_service_request: servicename='%s'", servicename))
192
193
0
  CHECKCLEARTOWRITE();
194
195
0
  buf_putbyte(ses.writepayload, SSH_MSG_SERVICE_REQUEST);
196
0
  buf_putstring(ses.writepayload, servicename, strlen(servicename));
197
198
0
  encrypt_packet();
199
0
  TRACE(("leave send_msg_service_request"))
200
0
}
201
202
0
static void recv_msg_service_accept(void) {
203
  /* do nothing, if it failed then the server MUST have disconnected */
204
0
}
205
206
/* This function drives the progress of the session - it initiates KEX,
207
 * service, userauth and channel requests */
208
0
static void cli_sessionloop() {
209
210
0
  TRACE2(("enter cli_sessionloop"))
211
212
0
  if (ses.lastpacket == 0) {
213
0
    TRACE2(("exit cli_sessionloop: no real packets yet"))
214
0
    return;
215
0
  }
216
217
0
  if (ses.lastpacket == SSH_MSG_KEXINIT && cli_ses.kex_state == KEX_NOTHING) {
218
    /* We initiate the KEXDH. If DH wasn't the correct type, the KEXINIT
219
     * negotiation would have failed. */
220
0
    if (!ses.kexstate.our_first_follows_matches) {
221
0
      send_msg_kexdh_init();
222
0
    }
223
0
    cli_ses.kex_state = KEXDH_INIT_SENT;      
224
0
    TRACE(("leave cli_sessionloop: done with KEXINIT_RCVD"))
225
0
    return;
226
0
  }
227
228
  /* A KEX has finished, so we should go back to our KEX_NOTHING state */
229
0
  if (cli_ses.kex_state != KEX_NOTHING && ses.kexstate.sentnewkeys) {
230
0
    cli_ses.kex_state = KEX_NOTHING;
231
0
  }
232
233
  /* We shouldn't do anything else if a KEX is in progress */
234
0
  if (cli_ses.kex_state != KEX_NOTHING) {
235
0
    TRACE(("leave cli_sessionloop: kex_state != KEX_NOTHING"))
236
0
    return;
237
0
  }
238
239
0
  if (ses.kexstate.donefirstkex == 0) {
240
    /* We might reach here if we have partial packet reads or have
241
     * received SSG_MSG_IGNORE etc. Just skip it */
242
0
    TRACE2(("donefirstkex false\n"))
243
0
    return;
244
0
  }
245
246
0
  switch (cli_ses.state) {
247
248
0
    case STATE_NOTHING:
249
      /* We've got the transport layer sorted, we now need to request
250
       * userauth */
251
0
      send_msg_service_request(SSH_SERVICE_USERAUTH);
252
      /* We aren't using any "implicit server authentication" methods,
253
      so don't need to wait for a response for SSH_SERVICE_USERAUTH
254
      before sending the auth messages (rfc4253 10) */
255
0
      cli_auth_getmethods();
256
0
      cli_ses.state = USERAUTH_REQ_SENT;
257
0
      TRACE(("leave cli_sessionloop: sent userauth methods req"))
258
0
      return;
259
260
0
    case USERAUTH_REQ_SENT:
261
0
      TRACE(("leave cli_sessionloop: waiting, req_sent"))
262
0
      return;
263
      
264
0
    case USERAUTH_FAIL_RCVD:
265
0
      if (cli_auth_try() == DROPBEAR_FAILURE) {
266
0
        dropbear_exit("No auth methods could be used.");
267
0
      }
268
0
      cli_ses.state = USERAUTH_REQ_SENT;
269
0
      TRACE(("leave cli_sessionloop: cli_auth_try"))
270
0
      return;
271
272
0
    case USERAUTH_SUCCESS_RCVD:
273
0
#ifndef DISABLE_SYSLOG
274
0
      if (opts.usingsyslog) {
275
0
        dropbear_log(LOG_INFO, "Authentication succeeded.");
276
0
      }
277
0
#endif
278
279
0
      if (cli_opts.backgrounded) {
280
0
        int devnull;
281
        /* keeping stdin open steals input from the terminal and
282
           is confusing, though stdout/stderr could be useful. */
283
0
        devnull = open(DROPBEAR_PATH_DEVNULL, O_RDONLY);
284
0
        if (devnull < 0) {
285
0
          dropbear_exit("Opening /dev/null: %d %s",
286
0
              errno, strerror(errno));
287
0
        }
288
0
        dup2(devnull, STDIN_FILENO);
289
0
        if (daemon(0, 1) < 0) {
290
0
          dropbear_exit("Backgrounding failed: %d %s", 
291
0
              errno, strerror(errno));
292
0
        }
293
0
      }
294
      
295
0
#if DROPBEAR_CLI_NETCAT
296
0
      if (cli_opts.netcat_host) {
297
0
        cli_send_netcat_request();
298
0
      } else 
299
0
#endif
300
0
      if (!cli_opts.no_cmd) {
301
0
        cli_send_chansess_request();
302
0
      }
303
304
0
#if DROPBEAR_CLI_LOCALTCPFWD
305
0
      setup_localtcp();
306
0
#endif
307
0
#if DROPBEAR_CLI_REMOTETCPFWD
308
0
      setup_remotetcp();
309
0
#endif
310
311
0
      TRACE(("leave cli_sessionloop: running"))
312
0
      cli_ses.state = SESSION_RUNNING;
313
0
      return;
314
315
0
    case SESSION_RUNNING:
316
0
      if (ses.chancount < 1 && !cli_opts.no_cmd) {
317
0
        cli_finished();
318
0
      }
319
320
0
      if (cli_ses.winchange) {
321
0
        cli_chansess_winchange();
322
0
      }
323
0
      return;
324
325
    /* XXX more here needed */
326
327
328
0
  default:
329
0
    break;
330
0
  }
331
332
0
  TRACE2(("leave cli_sessionloop: fell out"))
333
334
0
}
335
336
0
void kill_proxy_command(void) {
337
  /*
338
   * Send SIGHUP to proxy command if used. We don't wait() in
339
   * case it hangs and instead rely on init to reap the child
340
   */
341
0
  if (cli_ses.proxy_cmd_pid > 1) {
342
0
    TRACE(("killing proxy command with PID='%d'", cli_ses.proxy_cmd_pid));
343
0
    kill(cli_ses.proxy_cmd_pid, SIGHUP);
344
0
  }
345
0
}
346
347
0
static void cli_session_cleanup(void) {
348
349
0
  if (!ses.init_done) {
350
0
    return;
351
0
  }
352
353
0
  kill_proxy_command();
354
355
  /* Set std{in,out,err} back to non-blocking - busybox ash dies nastily if
356
   * we don't revert the flags */
357
  /* Ignore return value since there's nothing we can do */
358
0
  (void)fcntl(cli_ses.stdincopy, F_SETFL, cli_ses.stdinflags);
359
0
  (void)fcntl(cli_ses.stdoutcopy, F_SETFL, cli_ses.stdoutflags);
360
0
  (void)fcntl(cli_ses.stderrcopy, F_SETFL, cli_ses.stderrflags);
361
362
  /* Don't leak */
363
0
  m_close(cli_ses.stdincopy);
364
0
  m_close(cli_ses.stdoutcopy);
365
0
  m_close(cli_ses.stderrcopy);
366
367
0
  cli_tty_cleanup();
368
0
  if (cli_ses.server_sig_algs) {
369
0
    buf_free(cli_ses.server_sig_algs);
370
0
  }
371
0
}
372
373
0
static void cli_finished() {
374
0
  TRACE(("cli_finished()"))
375
376
0
  session_cleanup();
377
0
  fprintf(stderr, "Connection to %s@%s:%s closed.\n", cli_opts.username,
378
0
      cli_opts.remotehost, cli_opts.remoteport);
379
0
  exit(cli_ses.retval);
380
0
}
381
382
383
/* called when the remote side closes the connection */
384
0
static void cli_remoteclosed() {
385
386
  /* XXX TODO perhaps print a friendlier message if we get this but have
387
   * already sent/received disconnect message(s) ??? */
388
0
  m_close(ses.sock_in);
389
0
  m_close(ses.sock_out);
390
0
  ses.sock_in = -1;
391
0
  ses.sock_out = -1;
392
0
  dropbear_exit("Remote closed the connection");
393
0
}
394
395
/* Operates in-place turning dirty (untrusted potentially containing control
396
 * characters) text into clean text. 
397
 * Note: this is safe only with ascii - other charsets could have problems. */
398
0
void cleantext(char* dirtytext) {
399
400
0
  unsigned int i, j;
401
0
  char c;
402
403
0
  j = 0;
404
0
  for (i = 0; dirtytext[i] != '\0'; i++) {
405
406
0
    c = dirtytext[i];
407
    /* We can ignore '\r's */
408
0
    if ( (c >= ' ' && c <= '~') || c == '\n' || c == '\t') {
409
0
      dirtytext[j] = c;
410
0
      j++;
411
0
    }
412
0
  }
413
  /* Null terminate */
414
0
  dirtytext[j] = '\0';
415
0
}
416
417
0
static void recv_msg_global_request_cli(void) {
418
0
  unsigned int wantreply = 0;
419
420
0
  buf_eatstring(ses.payload);
421
0
  wantreply = buf_getbool(ses.payload);
422
423
0
  TRACE(("recv_msg_global_request_cli: want_reply: %u", wantreply));
424
425
0
  if (wantreply) {
426
    /* Send a proper rejection */
427
0
    send_msg_request_failure();
428
0
  }
429
0
}
430
431
0
void cli_dropbear_exit(int exitcode, const char* format, va_list param) {
432
0
  char exitmsg[150];
433
0
  char fullmsg[300];
434
435
  /* Note that exit message must be rendered before session cleanup */
436
437
  /* Render the formatted exit message */
438
0
  vsnprintf(exitmsg, sizeof(exitmsg), format, param);
439
0
  TRACE(("Exited, cleaning up: %s", exitmsg))
440
441
  /* Add the prefix depending on session/auth state */
442
0
  if (!ses.init_done) {
443
0
    snprintf(fullmsg, sizeof(fullmsg), "Exited: %s", exitmsg);
444
0
  } else {
445
0
    snprintf(fullmsg, sizeof(fullmsg), 
446
0
        "Connection to %s@%s:%s exited: %s", 
447
0
        cli_opts.username, cli_opts.remotehost, 
448
0
        cli_opts.remoteport, exitmsg);
449
0
  }
450
451
  /* Do the cleanup first, since then the terminal will be reset */
452
0
  session_cleanup();
453
  
454
0
#if DROPBEAR_FUZZ
455
0
    if (fuzz.do_jmp) {
456
0
        longjmp(fuzz.jmp, 1);
457
0
    }
458
0
#endif
459
460
  /* Avoid printing onwards from terminal cruft */
461
0
  fprintf(stderr, "\n");
462
463
0
  dropbear_log(LOG_INFO, "%s", fullmsg);
464
465
0
  exit(exitcode);
466
0
}
467
468
0
void cli_dropbear_log(int priority, const char* format, va_list param) {
469
470
0
  char printbuf[1024];
471
0
  const char *name;
472
473
0
  name = cli_opts.progname;
474
0
  if (!name) {
475
0
    name = "dbclient";
476
0
  }
477
478
0
  vsnprintf(printbuf, sizeof(printbuf), format, param);
479
480
0
#ifndef DISABLE_SYSLOG
481
0
  if (opts.usingsyslog) {
482
0
    syslog(priority, "%s", printbuf);
483
0
  }
484
0
#endif
485
486
0
  fprintf(stderr, "%s: %s\n", name, printbuf);
487
0
  fflush(stderr);
488
0
}
489