Coverage Report

Created: 2025-08-29 06:35

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