Coverage Report

Created: 2023-09-25 06:08

/src/dropbear/src/cli-chansession.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 "packet.h"
28
#include "buffer.h"
29
#include "session.h"
30
#include "dbutil.h"
31
#include "channel.h"
32
#include "ssh.h"
33
#include "runopts.h"
34
#include "termcodes.h"
35
#include "chansession.h"
36
#include "agentfwd.h"
37
38
static void cli_closechansess(const struct Channel *channel);
39
static int cli_initchansess(struct Channel *channel);
40
static void cli_chansessreq(struct Channel *channel);
41
static void send_chansess_pty_req(const struct Channel *channel);
42
static void send_chansess_shell_req(const struct Channel *channel);
43
static void cli_escape_handler(const struct Channel *channel, const unsigned char* buf, int *len);
44
static int cli_init_netcat(struct Channel *channel);
45
46
static void cli_tty_setup(void);
47
48
const struct ChanType clichansess = {
49
  "session", /* name */
50
  cli_initchansess, /* inithandler */
51
  NULL, /* checkclosehandler */
52
  cli_chansessreq, /* reqhandler */
53
  cli_closechansess, /* closehandler */
54
  NULL, /* cleanup */
55
};
56
57
0
static void cli_chansessreq(struct Channel *channel) {
58
59
0
  char* type = NULL;
60
0
  int wantreply;
61
62
0
  TRACE(("enter cli_chansessreq"))
63
64
0
  type = buf_getstring(ses.payload, NULL);
65
0
  wantreply = buf_getbool(ses.payload);
66
67
0
  if (strcmp(type, "exit-status") == 0) {
68
0
    cli_ses.retval = buf_getint(ses.payload);
69
0
    TRACE(("got exit-status of '%d'", cli_ses.retval))
70
0
  } else if (strcmp(type, "exit-signal") == 0) {
71
0
    TRACE(("got exit-signal, ignoring it"))
72
0
  } else {
73
0
    TRACE(("unknown request '%s'", type))
74
0
    if (wantreply) {
75
0
      send_msg_channel_failure(channel);
76
0
    }
77
0
    goto out;
78
0
  }
79
    
80
0
out:
81
0
  m_free(type);
82
0
}
83
  
84
85
/* If the main session goes, we close it up */
86
0
static void cli_closechansess(const struct Channel *UNUSED(channel)) {
87
0
  cli_tty_cleanup(); /* Restore tty modes etc */
88
89
  /* This channel hasn't gone yet, so we have > 1 */
90
0
  if (ses.chancount > 1) {
91
0
    dropbear_log(LOG_INFO, "Waiting for other channels to close...");
92
0
  }
93
0
}
94
95
/* Taken from OpenSSH's sshtty.c:
96
 * RCSID("OpenBSD: sshtty.c,v 1.5 2003/09/19 17:43:35 markus Exp "); */
97
0
static void cli_tty_setup() {
98
99
0
  struct termios tio;
100
101
0
  TRACE(("enter cli_pty_setup"))
102
103
0
  if (cli_ses.tty_raw_mode == 1) {
104
0
    TRACE(("leave cli_tty_setup: already in raw mode!"))
105
0
    return;
106
0
  }
107
108
0
  if (tcgetattr(STDIN_FILENO, &tio) == -1) {
109
0
    dropbear_exit("Failed to set raw TTY mode");
110
0
  }
111
112
  /* make a copy */
113
0
  cli_ses.saved_tio = tio;
114
115
0
  tio.c_iflag |= IGNPAR;
116
0
  tio.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF);
117
0
#ifdef IUCLC
118
0
  tio.c_iflag &= ~IUCLC;
119
0
#endif
120
0
  tio.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL);
121
0
#ifdef IEXTEN
122
0
  tio.c_lflag &= ~IEXTEN;
123
0
#endif
124
0
  tio.c_oflag &= ~OPOST;
125
0
  tio.c_cc[VMIN] = 1;
126
0
  tio.c_cc[VTIME] = 0;
127
0
  if (tcsetattr(STDIN_FILENO, TCSADRAIN, &tio) == -1) {
128
0
    dropbear_exit("Failed to set raw TTY mode");
129
0
  }
130
131
0
  cli_ses.tty_raw_mode = 1;
132
0
  TRACE(("leave cli_tty_setup"))
133
0
}
134
135
2.93k
void cli_tty_cleanup() {
136
137
2.93k
  TRACE(("enter cli_tty_cleanup"))
138
139
2.93k
  if (cli_ses.tty_raw_mode == 0) {
140
2.93k
    TRACE(("leave cli_tty_cleanup: not in raw mode"))
141
2.93k
    return;
142
2.93k
  }
143
144
0
  if (tcsetattr(STDIN_FILENO, TCSADRAIN, &cli_ses.saved_tio) == -1) {
145
0
    dropbear_log(LOG_WARNING, "Failed restoring TTY");
146
0
  } else {
147
0
    cli_ses.tty_raw_mode = 0; 
148
0
  }
149
150
0
  TRACE(("leave cli_tty_cleanup"))
151
0
}
152
153
0
static void put_termcodes() {
154
155
0
  struct termios tio;
156
0
  unsigned int sshcode;
157
0
  const struct TermCode *termcode;
158
0
  unsigned int value;
159
0
  unsigned int mapcode;
160
161
0
  unsigned int bufpos1, bufpos2;
162
163
0
  TRACE(("enter put_termcodes"))
164
165
0
  if (tcgetattr(STDIN_FILENO, &tio) == -1) {
166
0
    dropbear_log(LOG_WARNING, "Failed reading termmodes");
167
0
    buf_putint(ses.writepayload, 1); /* Just the terminator */
168
0
    buf_putbyte(ses.writepayload, 0); /* TTY_OP_END */
169
0
    return;
170
0
  }
171
172
0
  bufpos1 = ses.writepayload->pos;
173
0
  buf_putint(ses.writepayload, 0); /* A placeholder for the final length */
174
175
  /* As with Dropbear server, we ignore baud rates for now */
176
0
  for (sshcode = 1; sshcode < MAX_TERMCODE; sshcode++) {
177
178
0
    termcode = &termcodes[sshcode];
179
0
    mapcode = termcode->mapcode;
180
181
0
    switch (termcode->type) {
182
183
0
      case TERMCODE_NONE:
184
0
        continue;
185
186
0
      case TERMCODE_CONTROLCHAR:
187
0
        value = tio.c_cc[mapcode];
188
0
        break;
189
190
0
      case TERMCODE_INPUT:
191
0
        value = tio.c_iflag & mapcode;
192
0
        break;
193
194
0
      case TERMCODE_OUTPUT:
195
0
        value = tio.c_oflag & mapcode;
196
0
        break;
197
198
0
      case TERMCODE_LOCAL:
199
0
        value = tio.c_lflag & mapcode;
200
0
        break;
201
202
0
      case TERMCODE_CONTROL:
203
0
        value = tio.c_cflag & mapcode;
204
0
        break;
205
206
0
      default:
207
0
        continue;
208
209
0
    }
210
211
    /* If we reach here, we have something to say */
212
0
    buf_putbyte(ses.writepayload, sshcode);
213
0
    buf_putint(ses.writepayload, value);
214
0
  }
215
216
0
  buf_putbyte(ses.writepayload, 0); /* THE END, aka TTY_OP_END */
217
218
  /* Put the string length at the start of the buffer */
219
0
  bufpos2 = ses.writepayload->pos;
220
221
0
  buf_setpos(ses.writepayload, bufpos1); /* Jump back */
222
0
  buf_putint(ses.writepayload, bufpos2 - bufpos1 - 4); /* len(termcodes) */
223
0
  buf_setpos(ses.writepayload, bufpos2); /* Back where we were */
224
225
0
  TRACE(("leave put_termcodes"))
226
0
}
227
228
0
static void put_winsize() {
229
230
0
  struct winsize ws;
231
232
0
  if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0) {
233
    /* Some sane defaults */
234
0
    ws.ws_row = 25;
235
0
    ws.ws_col = 80;
236
0
    ws.ws_xpixel = 0;
237
0
    ws.ws_ypixel = 0;
238
0
  }
239
240
0
  buf_putint(ses.writepayload, ws.ws_col); /* Cols */
241
0
  buf_putint(ses.writepayload, ws.ws_row); /* Rows */
242
0
  buf_putint(ses.writepayload, ws.ws_xpixel); /* Width */
243
0
  buf_putint(ses.writepayload, ws.ws_ypixel); /* Height */
244
245
0
}
246
247
0
static void sigwinch_handler(int UNUSED(unused)) {
248
249
0
  cli_ses.winchange = 1;
250
251
0
}
252
253
0
void cli_chansess_winchange() {
254
255
0
  unsigned int i;
256
0
  struct Channel *channel = NULL;
257
258
0
  for (i = 0; i < ses.chansize; i++) {
259
0
    channel = ses.channels[i];
260
0
    if (channel != NULL && channel->type == &clichansess) {
261
0
      CHECKCLEARTOWRITE();
262
0
      buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_REQUEST);
263
0
      buf_putint(ses.writepayload, channel->remotechan);
264
0
      buf_putstring(ses.writepayload, "window-change", 13);
265
0
      buf_putbyte(ses.writepayload, 0); /* FALSE says the spec */
266
0
      put_winsize();
267
0
      encrypt_packet();
268
0
    }
269
0
  }
270
0
  cli_ses.winchange = 0;
271
0
}
272
273
0
static void send_chansess_pty_req(const struct Channel *channel) {
274
275
0
  char* term = NULL;
276
277
0
  TRACE(("enter send_chansess_pty_req"))
278
279
0
  start_send_channel_request(channel, "pty-req");
280
281
  /* Don't want replies */
282
0
  buf_putbyte(ses.writepayload, 0);
283
284
  /* Get the terminal */
285
0
  term = getenv("TERM");
286
0
  if (term == NULL) {
287
0
    term = "vt100"; /* Seems a safe default */
288
0
  }
289
0
  buf_putstring(ses.writepayload, term, strlen(term));
290
291
  /* Window size */
292
0
  put_winsize();
293
294
  /* Terminal mode encoding */
295
0
  put_termcodes();
296
297
0
  encrypt_packet();
298
299
  /* Set up a window-change handler */
300
0
  if (signal(SIGWINCH, sigwinch_handler) == SIG_ERR) {
301
0
    dropbear_exit("Signal error");
302
0
  }
303
0
  TRACE(("leave send_chansess_pty_req"))
304
0
}
305
306
0
static void send_chansess_shell_req(const struct Channel *channel) {
307
308
0
  char* reqtype = NULL;
309
310
0
  TRACE(("enter send_chansess_shell_req"))
311
312
0
  if (cli_opts.cmd) {
313
0
    if (cli_opts.is_subsystem) {
314
0
      reqtype = "subsystem";
315
0
    } else {
316
0
      reqtype = "exec";
317
0
    }
318
0
  } else {
319
0
    reqtype = "shell";
320
0
  }
321
322
0
  start_send_channel_request(channel, reqtype);
323
324
  /* XXX TODO */
325
0
  buf_putbyte(ses.writepayload, 0); /* Don't want replies */
326
0
  if (cli_opts.cmd) {
327
0
    buf_putstring(ses.writepayload, cli_opts.cmd, strlen(cli_opts.cmd));
328
0
  }
329
330
0
  encrypt_packet();
331
0
  TRACE(("leave send_chansess_shell_req"))
332
0
}
333
334
/* Shared for normal client channel and netcat-alike */
335
0
static int cli_init_stdpipe_sess(struct Channel *channel) {
336
0
  channel->writefd = STDOUT_FILENO;
337
0
  setnonblocking(STDOUT_FILENO);
338
339
0
  channel->readfd = STDIN_FILENO;
340
0
  setnonblocking(STDIN_FILENO);
341
342
0
  channel->errfd = STDERR_FILENO;
343
0
  setnonblocking(STDERR_FILENO);
344
345
0
  channel->extrabuf = cbuf_new(opts.recv_window);
346
0
  channel->bidir_fd = 0;
347
0
  return 0;
348
0
}
349
350
0
static int cli_init_netcat(struct Channel *channel) {
351
0
  return cli_init_stdpipe_sess(channel);
352
0
}
353
354
0
static int cli_initchansess(struct Channel *channel) {
355
356
0
  cli_init_stdpipe_sess(channel);
357
358
0
#if DROPBEAR_CLI_AGENTFWD
359
0
  if (cli_opts.agent_fwd) {
360
0
    cli_setup_agent(channel);
361
0
  }
362
0
#endif
363
0
  if (cli_opts.wantpty) {
364
0
    send_chansess_pty_req(channel);
365
0
    channel->prio = DROPBEAR_PRIO_LOWDELAY;
366
0
  }
367
368
0
  send_chansess_shell_req(channel);
369
370
0
  if (cli_opts.wantpty) {
371
0
    cli_tty_setup();
372
0
    channel->read_mangler = cli_escape_handler;
373
0
    cli_ses.last_char = '\r';
374
0
  }
375
376
0
  return 0; /* Success */
377
0
}
378
379
#if DROPBEAR_CLI_NETCAT
380
381
static const struct ChanType cli_chan_netcat = {
382
  "direct-tcpip",
383
  cli_init_netcat, /* inithandler */
384
  NULL,
385
  NULL,
386
  cli_closechansess,
387
  NULL,
388
};
389
390
0
void cli_send_netcat_request() {
391
392
0
  const char* source_host = "127.0.0.1";
393
0
  const int source_port = 22;
394
395
0
  TRACE(("enter cli_send_netcat_request"))
396
0
  cli_opts.wantpty = 0;
397
398
0
  if (send_msg_channel_open_init(STDIN_FILENO, &cli_chan_netcat) 
399
0
      == DROPBEAR_FAILURE) {
400
0
    dropbear_exit("Couldn't open initial channel");
401
0
  }
402
403
0
  buf_putstring(ses.writepayload, cli_opts.netcat_host,
404
0
      strlen(cli_opts.netcat_host));
405
0
  buf_putint(ses.writepayload, cli_opts.netcat_port);
406
407
  /* originator ip - localhost is accurate enough */
408
0
  buf_putstring(ses.writepayload, source_host, strlen(source_host));
409
0
  buf_putint(ses.writepayload, source_port);
410
411
0
  encrypt_packet();
412
0
  TRACE(("leave cli_send_netcat_request"))
413
0
}
414
#endif
415
416
0
void cli_send_chansess_request() {
417
418
0
  TRACE(("enter cli_send_chansess_request"))
419
420
0
  if (send_msg_channel_open_init(STDIN_FILENO, &clichansess) 
421
0
      == DROPBEAR_FAILURE) {
422
0
    dropbear_exit("Couldn't open initial channel");
423
0
  }
424
425
  /* No special channel request data */
426
0
  encrypt_packet();
427
0
  TRACE(("leave cli_send_chansess_request"))
428
429
0
}
430
431
/* returns 1 if the character should be consumed, 0 to pass through */
432
static int
433
0
do_escape(unsigned char c) {
434
0
  switch (c) {
435
0
    case '.':
436
0
      dropbear_exit("Terminated");
437
0
      return 1;
438
0
    case 0x1a:
439
      /* ctrl-z */
440
0
      cli_tty_cleanup();
441
0
      kill(getpid(), SIGTSTP);
442
      /* after continuation */
443
0
      cli_tty_setup();
444
0
      cli_ses.winchange = 1;
445
0
      return 1;
446
0
    default:
447
0
      return 0;
448
0
  }
449
0
}
450
451
static
452
0
void cli_escape_handler(const struct Channel* UNUSED(channel), const unsigned char* buf, int *len) {
453
0
  char c;
454
0
  int skip_char = 0;
455
456
  /* only handle escape characters if they are read one at a time. simplifies 
457
     the code and avoids nasty people putting ~. at the start of a line to paste  */
458
0
  if (*len != 1) {
459
0
    cli_ses.last_char = 0x0;
460
0
    return;
461
0
  }
462
463
0
  c = buf[0];
464
465
0
  if (cli_ses.last_char == DROPBEAR_ESCAPE_CHAR) {
466
0
    skip_char = do_escape(c);
467
0
    cli_ses.last_char = 0x0;
468
0
  } else {
469
0
    if (c == DROPBEAR_ESCAPE_CHAR) {
470
0
      if (cli_ses.last_char == '\r') {
471
0
        cli_ses.last_char = DROPBEAR_ESCAPE_CHAR;
472
0
        skip_char = 1;
473
0
      } else {
474
0
        cli_ses.last_char = 0x0;
475
0
      }
476
0
    } else {
477
0
      cli_ses.last_char = c;
478
0
    }
479
0
  }
480
481
0
  if (skip_char) {
482
0
    *len = 0;
483
0
  }
484
0
}