Coverage Report

Created: 2023-03-06 09:28

/src/dropbear/svr-chansession.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Dropbear - a SSH2 server
3
 * 
4
 * Copyright (c) 2002,2003 Matt Johnston
5
 * All rights reserved.
6
 * 
7
 * Permission is hereby granted, free of charge, to any person obtaining a copy
8
 * of this software and associated documentation files (the "Software"), to deal
9
 * in the Software without restriction, including without limitation the rights
10
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
 * copies of the Software, and to permit persons to whom the Software is
12
 * furnished to do so, subject to the following conditions:
13
 * 
14
 * The above copyright notice and this permission notice shall be included in
15
 * all copies or substantial portions of the Software.
16
 * 
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
 * SOFTWARE. */
24
25
#include "includes.h"
26
#include "packet.h"
27
#include "buffer.h"
28
#include "session.h"
29
#include "dbutil.h"
30
#include "channel.h"
31
#include "chansession.h"
32
#include "sshpty.h"
33
#include "termcodes.h"
34
#include "ssh.h"
35
#include "dbrandom.h"
36
#include "x11fwd.h"
37
#include "agentfwd.h"
38
#include "runopts.h"
39
#include "auth.h"
40
41
/* Handles sessions (either shells or programs) requested by the client */
42
43
static int sessioncommand(struct Channel *channel, struct ChanSess *chansess,
44
    int iscmd, int issubsys);
45
static int sessionpty(struct ChanSess * chansess);
46
static int sessionsignal(const struct ChanSess *chansess);
47
static int noptycommand(struct Channel *channel, struct ChanSess *chansess);
48
static int ptycommand(struct Channel *channel, struct ChanSess *chansess);
49
static int sessionwinchange(const struct ChanSess *chansess);
50
static void execchild(const void *user_data_chansess);
51
static void addchildpid(struct ChanSess *chansess, pid_t pid);
52
static void sesssigchild_handler(int val);
53
static void closechansess(const struct Channel *channel);
54
static void cleanupchansess(const struct Channel *channel);
55
static int newchansess(struct Channel *channel);
56
static void chansessionrequest(struct Channel *channel);
57
static int sesscheckclose(struct Channel *channel);
58
59
static void send_exitsignalstatus(const struct Channel *channel);
60
static void send_msg_chansess_exitstatus(const struct Channel * channel,
61
    const struct ChanSess * chansess);
62
static void send_msg_chansess_exitsignal(const struct Channel * channel,
63
    const struct ChanSess * chansess);
64
static void get_termmodes(const struct ChanSess *chansess);
65
66
const struct ChanType svrchansess = {
67
  "session", /* name */
68
  newchansess, /* inithandler */
69
  sesscheckclose, /* checkclosehandler */
70
  chansessionrequest, /* reqhandler */
71
  closechansess, /* closehandler */
72
  cleanupchansess /* cleanup */
73
};
74
75
/* Returns whether the channel is ready to close. The child process
76
   must not be running (has never started, or has exited) */
77
0
static int sesscheckclose(struct Channel *channel) {
78
0
  struct ChanSess *chansess = (struct ChanSess*)channel->typedata;
79
0
  TRACE(("sesscheckclose, pid %d, exitpid %d", chansess->pid, chansess->exit.exitpid))
80
81
0
  if (chansess->exit.exitpid != -1) {
82
0
    channel->flushing = 1;
83
0
  }
84
0
  return chansess->pid == 0 || chansess->exit.exitpid != -1;
85
0
}
86
87
/* Handler for childs exiting, store the state for return to the client */
88
89
/* There's a particular race we have to watch out for: if the forked child
90
 * executes, exits, and this signal-handler is called, all before the parent
91
 * gets to run, then the childpids[] array won't have the pid in it. Hence we
92
 * use the svr_ses.lastexit struct to hold the exit, which is then compared by
93
 * the parent when it runs. This work correctly at least in the case of a
94
 * single shell spawned (ie the usual case) */
95
74.8k
void svr_chansess_checksignal(void) {
96
74.8k
  int status;
97
74.8k
  pid_t pid;
98
99
74.8k
  if (!ses.channel_signal_pending) {
100
74.8k
    return;
101
74.8k
  }
102
103
0
  while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
104
0
    unsigned int i;
105
0
    struct exitinfo *ex = NULL;
106
0
    TRACE(("svr_chansess_checksignal : pid %d", pid))
107
108
0
    ex = NULL;
109
    /* find the corresponding chansess */
110
0
    for (i = 0; i < svr_ses.childpidsize; i++) {
111
0
      if (svr_ses.childpids[i].pid == pid) {
112
0
        TRACE(("found match session"));
113
0
        ex = &svr_ses.childpids[i].chansess->exit;
114
0
        break;
115
0
      }
116
0
    }
117
118
    /* If the pid wasn't matched, then we might have hit the race mentioned
119
     * above. So we just store the info for the parent to deal with */
120
0
    if (ex == NULL) {
121
0
      TRACE(("using lastexit"));
122
0
      ex = &svr_ses.lastexit;
123
0
    }
124
125
0
    ex->exitpid = pid;
126
0
    if (WIFEXITED(status)) {
127
0
      ex->exitstatus = WEXITSTATUS(status);
128
0
    }
129
0
    if (WIFSIGNALED(status)) {
130
0
      ex->exitsignal = WTERMSIG(status);
131
0
#if !defined(AIX) && defined(WCOREDUMP)
132
0
      ex->exitcore = WCOREDUMP(status);
133
#else
134
      ex->exitcore = 0;
135
#endif
136
0
    } else {
137
      /* we use this to determine how pid exited */
138
0
      ex->exitsignal = -1;
139
0
    }
140
0
  }
141
0
}
142
143
0
static void sesssigchild_handler(int UNUSED(dummy)) {
144
0
  struct sigaction sa_chld;
145
146
0
  const int saved_errno = errno;
147
148
0
  TRACE(("enter sigchld handler"))
149
150
  /* Make sure that the main select() loop wakes up */
151
0
  while (1) {
152
    /* isserver is just a random byte to write. We can't do anything
153
    about an error so should just ignore it */
154
0
    if (write(ses.signal_pipe[1], &ses.isserver, 1) == 1
155
0
        || errno != EINTR) {
156
0
      break;
157
0
    }
158
0
  }
159
160
0
  sa_chld.sa_handler = sesssigchild_handler;
161
0
  sa_chld.sa_flags = SA_NOCLDSTOP;
162
0
  sigemptyset(&sa_chld.sa_mask);
163
0
  sigaction(SIGCHLD, &sa_chld, NULL);
164
0
  TRACE(("leave sigchld handler"))
165
166
0
  errno = saved_errno;
167
0
}
168
169
/* send the exit status or the signal causing termination for a session */
170
0
static void send_exitsignalstatus(const struct Channel *channel) {
171
172
0
  struct ChanSess *chansess = (struct ChanSess*)channel->typedata;
173
174
0
  if (chansess->exit.exitpid >= 0) {
175
0
    if (chansess->exit.exitsignal > 0) {
176
0
      send_msg_chansess_exitsignal(channel, chansess);
177
0
    } else {
178
0
      send_msg_chansess_exitstatus(channel, chansess);
179
0
    }
180
0
  }
181
0
}
182
183
/* send the exitstatus to the client */
184
static void send_msg_chansess_exitstatus(const struct Channel * channel,
185
0
    const struct ChanSess * chansess) {
186
187
0
  dropbear_assert(chansess->exit.exitpid != -1);
188
0
  dropbear_assert(chansess->exit.exitsignal == -1);
189
190
0
  CHECKCLEARTOWRITE();
191
192
0
  buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_REQUEST);
193
0
  buf_putint(ses.writepayload, channel->remotechan);
194
0
  buf_putstring(ses.writepayload, "exit-status", 11);
195
0
  buf_putbyte(ses.writepayload, 0); /* boolean FALSE */
196
0
  buf_putint(ses.writepayload, chansess->exit.exitstatus);
197
198
0
  encrypt_packet();
199
200
0
}
201
202
/* send the signal causing the exit to the client */
203
static void send_msg_chansess_exitsignal(const struct Channel * channel,
204
0
    const struct ChanSess * chansess) {
205
206
0
  int i;
207
0
  char* signame = NULL;
208
0
  dropbear_assert(chansess->exit.exitpid != -1);
209
0
  dropbear_assert(chansess->exit.exitsignal > 0);
210
211
0
  TRACE(("send_msg_chansess_exitsignal %d", chansess->exit.exitsignal))
212
213
0
  CHECKCLEARTOWRITE();
214
215
  /* we check that we can match a signal name, otherwise
216
   * don't send anything */
217
0
  for (i = 0; signames[i].name != NULL; i++) {
218
0
    if (signames[i].signal == chansess->exit.exitsignal) {
219
0
      signame = signames[i].name;
220
0
      break;
221
0
    }
222
0
  }
223
224
0
  if (signame == NULL) {
225
0
    return;
226
0
  }
227
228
0
  buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_REQUEST);
229
0
  buf_putint(ses.writepayload, channel->remotechan);
230
0
  buf_putstring(ses.writepayload, "exit-signal", 11);
231
0
  buf_putbyte(ses.writepayload, 0); /* boolean FALSE */
232
0
  buf_putstring(ses.writepayload, signame, strlen(signame));
233
0
  buf_putbyte(ses.writepayload, chansess->exit.exitcore);
234
0
  buf_putstring(ses.writepayload, "", 0); /* error msg */
235
0
  buf_putstring(ses.writepayload, "", 0); /* lang */
236
237
0
  encrypt_packet();
238
0
}
239
240
/* set up a session channel */
241
0
static int newchansess(struct Channel *channel) {
242
243
0
  struct ChanSess *chansess;
244
245
0
  TRACE(("new chansess %p", (void*)channel))
246
247
0
  dropbear_assert(channel->typedata == NULL);
248
249
0
  chansess = (struct ChanSess*)m_malloc(sizeof(struct ChanSess));
250
0
  chansess->cmd = NULL;
251
0
  chansess->connection_string = NULL;
252
0
  chansess->client_string = NULL;
253
0
  chansess->pid = 0;
254
255
  /* pty details */
256
0
  chansess->master = -1;
257
0
  chansess->slave = -1;
258
0
  chansess->tty = NULL;
259
0
  chansess->term = NULL;
260
261
0
  chansess->exit.exitpid = -1;
262
263
0
  channel->typedata = chansess;
264
265
#if DROPBEAR_X11FWD
266
  chansess->x11listener = NULL;
267
  chansess->x11authprot = NULL;
268
  chansess->x11authcookie = NULL;
269
#endif
270
271
0
#if DROPBEAR_SVR_AGENTFWD
272
0
  chansess->agentlistener = NULL;
273
0
  chansess->agentfile = NULL;
274
0
  chansess->agentdir = NULL;
275
0
#endif
276
277
  /* Will drop to DROPBEAR_PRIO_NORMAL if a non-tty command starts */
278
0
  channel->prio = DROPBEAR_PRIO_LOWDELAY;
279
280
0
  return 0;
281
282
0
}
283
284
static struct logininfo* 
285
0
chansess_login_alloc(const struct ChanSess *chansess) {
286
0
  struct logininfo * li;
287
0
  li = login_alloc_entry(chansess->pid, ses.authstate.username,
288
0
      svr_ses.remotehost, chansess->tty);
289
0
  return li;
290
0
}
291
292
/* send exit status message before the channel is closed */
293
0
static void closechansess(const struct Channel *channel) {
294
0
  struct ChanSess *chansess;
295
296
0
  TRACE(("enter closechansess"))
297
298
0
  chansess = (struct ChanSess*)channel->typedata;
299
300
0
  if (chansess == NULL) {
301
0
    TRACE(("leave closechansess: chansess == NULL"))
302
0
    return;
303
0
  }
304
305
0
  send_exitsignalstatus(channel);
306
0
  TRACE(("leave closechansess"))
307
0
}
308
309
/* clean a session channel */
310
0
static void cleanupchansess(const struct Channel *channel) {
311
312
0
  struct ChanSess *chansess;
313
0
  unsigned int i;
314
0
  struct logininfo *li;
315
316
0
  TRACE(("enter closechansess"))
317
318
0
  chansess = (struct ChanSess*)channel->typedata;
319
320
0
  if (chansess == NULL) {
321
0
    TRACE(("leave closechansess: chansess == NULL"))
322
0
    return;
323
0
  }
324
325
0
  m_free(chansess->cmd);
326
0
  m_free(chansess->term);
327
0
  m_free(chansess->original_command);
328
329
0
  if (chansess->tty) {
330
    /* write the utmp/wtmp login record */
331
0
    li = chansess_login_alloc(chansess);
332
0
    login_logout(li);
333
0
    login_free_entry(li);
334
335
0
    pty_release(chansess->tty);
336
0
    m_free(chansess->tty);
337
0
  }
338
339
#if DROPBEAR_X11FWD
340
  x11cleanup(chansess);
341
#endif
342
343
0
#if DROPBEAR_SVR_AGENTFWD
344
0
  svr_agentcleanup(chansess);
345
0
#endif
346
347
  /* clear child pid entries */
348
0
  for (i = 0; i < svr_ses.childpidsize; i++) {
349
0
    if (svr_ses.childpids[i].chansess == chansess) {
350
0
      dropbear_assert(svr_ses.childpids[i].pid > 0);
351
0
      TRACE(("closing pid %d", svr_ses.childpids[i].pid))
352
0
      TRACE(("exitpid is %d", chansess->exit.exitpid))
353
0
      svr_ses.childpids[i].pid = -1;
354
0
      svr_ses.childpids[i].chansess = NULL;
355
0
    }
356
0
  }
357
        
358
0
  m_free(chansess);
359
360
0
  TRACE(("leave closechansess"))
361
0
}
362
363
/* Handle requests for a channel. These can be execution requests,
364
 * or x11/authagent forwarding. These are passed to appropriate handlers */
365
0
static void chansessionrequest(struct Channel *channel) {
366
367
0
  char * type = NULL;
368
0
  unsigned int typelen;
369
0
  unsigned char wantreply;
370
0
  int ret = 1;
371
0
  struct ChanSess *chansess;
372
373
0
  TRACE(("enter chansessionrequest"))
374
375
0
  type = buf_getstring(ses.payload, &typelen);
376
0
  wantreply = buf_getbool(ses.payload);
377
378
0
  if (typelen > MAX_NAME_LEN) {
379
0
    TRACE(("leave chansessionrequest: type too long")) /* XXX send error?*/
380
0
    goto out;
381
0
  }
382
383
0
  chansess = (struct ChanSess*)channel->typedata;
384
0
  dropbear_assert(chansess != NULL);
385
0
  TRACE(("type is %s", type))
386
387
0
  if (strcmp(type, "window-change") == 0) {
388
0
    ret = sessionwinchange(chansess);
389
0
  } else if (strcmp(type, "shell") == 0) {
390
0
    ret = sessioncommand(channel, chansess, 0, 0);
391
0
  } else if (strcmp(type, "pty-req") == 0) {
392
0
    ret = sessionpty(chansess);
393
0
  } else if (strcmp(type, "exec") == 0) {
394
0
    ret = sessioncommand(channel, chansess, 1, 0);
395
0
  } else if (strcmp(type, "subsystem") == 0) {
396
0
    ret = sessioncommand(channel, chansess, 1, 1);
397
#if DROPBEAR_X11FWD
398
  } else if (strcmp(type, "x11-req") == 0) {
399
    ret = x11req(chansess);
400
#endif
401
0
#if DROPBEAR_SVR_AGENTFWD
402
0
  } else if (strcmp(type, "auth-agent-req@openssh.com") == 0) {
403
0
    ret = svr_agentreq(chansess);
404
0
#endif
405
0
  } else if (strcmp(type, "signal") == 0) {
406
0
    ret = sessionsignal(chansess);
407
0
  } else {
408
    /* etc, todo "env", "subsystem" */
409
0
  }
410
411
0
out:
412
413
0
  if (wantreply) {
414
0
    if (ret == DROPBEAR_SUCCESS) {
415
0
      send_msg_channel_success(channel);
416
0
    } else {
417
0
      send_msg_channel_failure(channel);
418
0
    }
419
0
  }
420
421
0
  m_free(type);
422
0
  TRACE(("leave chansessionrequest"))
423
0
}
424
425
426
/* Send a signal to a session's process as requested by the client*/
427
0
static int sessionsignal(const struct ChanSess *chansess) {
428
0
  TRACE(("sessionsignal"))
429
430
0
  int sig = 0;
431
0
  char* signame = NULL;
432
0
  int i;
433
434
0
  if (chansess->pid == 0) {
435
0
    TRACE(("sessionsignal: done no pid"))
436
    /* haven't got a process pid yet */
437
0
    return DROPBEAR_FAILURE;
438
0
  }
439
440
0
  signame = buf_getstring(ses.payload, NULL);
441
442
0
  for (i = 0; signames[i].name != NULL; i++) {
443
0
    if (strcmp(signames[i].name, signame) == 0) {
444
0
      sig = signames[i].signal;
445
0
      break;
446
0
    }
447
0
  }
448
449
0
  m_free(signame);
450
451
0
  TRACE(("sessionsignal: pid %d signal %d", (int)chansess->pid, sig))
452
0
  if (sig == 0) {
453
    /* failed */
454
0
    return DROPBEAR_FAILURE;
455
0
  }
456
      
457
0
  if (kill(chansess->pid, sig) < 0) {
458
0
    TRACE(("sessionsignal: kill() errored"))
459
0
    return DROPBEAR_FAILURE;
460
0
  } 
461
462
0
  return DROPBEAR_SUCCESS;
463
0
}
464
465
/* Let the process know that the window size has changed, as notified from the
466
 * client. Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
467
0
static int sessionwinchange(const struct ChanSess *chansess) {
468
469
0
  int termc, termr, termw, termh;
470
471
0
  if (chansess->master < 0) {
472
    /* haven't got a pty yet */
473
0
    return DROPBEAR_FAILURE;
474
0
  }
475
      
476
0
  termc = buf_getint(ses.payload);
477
0
  termr = buf_getint(ses.payload);
478
0
  termw = buf_getint(ses.payload);
479
0
  termh = buf_getint(ses.payload);
480
  
481
0
  pty_change_window_size(chansess->master, termr, termc, termw, termh);
482
483
0
  return DROPBEAR_SUCCESS;
484
0
}
485
486
0
static void get_termmodes(const struct ChanSess *chansess) {
487
488
0
  struct termios termio;
489
0
  unsigned char opcode;
490
0
  unsigned int value;
491
0
  const struct TermCode * termcode;
492
0
  unsigned int len;
493
494
0
  TRACE(("enter get_termmodes"))
495
496
  /* Term modes */
497
  /* We'll ignore errors and continue if we can't set modes.
498
   * We're ignoring baud rates since they seem evil */
499
0
  if (tcgetattr(chansess->master, &termio) == -1) {
500
0
    return;
501
0
  }
502
503
0
  len = buf_getint(ses.payload);
504
0
  TRACE(("term mode str %d p->l %d p->p %d", 
505
0
        len, ses.payload->len , ses.payload->pos));
506
0
  if (len != ses.payload->len - ses.payload->pos) {
507
0
    dropbear_exit("Bad term mode string");
508
0
  }
509
510
0
  if (len == 0) {
511
0
    TRACE(("leave get_termmodes: empty terminal modes string"))
512
0
    return;
513
0
  }
514
515
0
  while (((opcode = buf_getbyte(ses.payload)) != 0x00) && opcode <= 159) {
516
517
    /* must be before checking type, so that value is consumed even if
518
     * we don't use it */
519
0
    value = buf_getint(ses.payload);
520
521
    /* handle types of code */
522
0
    if (opcode > MAX_TERMCODE) {
523
0
      continue;
524
0
    }
525
0
    termcode = &termcodes[(unsigned int)opcode];
526
    
527
528
0
    switch (termcode->type) {
529
530
0
      case TERMCODE_NONE:
531
0
        break;
532
533
0
      case TERMCODE_CONTROLCHAR:
534
0
        termio.c_cc[termcode->mapcode] = value;
535
0
        break;
536
537
0
      case TERMCODE_INPUT:
538
0
        if (value) {
539
0
          termio.c_iflag |= termcode->mapcode;
540
0
        } else {
541
0
          termio.c_iflag &= ~(termcode->mapcode);
542
0
        }
543
0
        break;
544
545
0
      case TERMCODE_OUTPUT:
546
0
        if (value) {
547
0
          termio.c_oflag |= termcode->mapcode;
548
0
        } else {
549
0
          termio.c_oflag &= ~(termcode->mapcode);
550
0
        }
551
0
        break;
552
553
0
      case TERMCODE_LOCAL:
554
0
        if (value) {
555
0
          termio.c_lflag |= termcode->mapcode;
556
0
        } else {
557
0
          termio.c_lflag &= ~(termcode->mapcode);
558
0
        }
559
0
        break;
560
561
0
      case TERMCODE_CONTROL:
562
0
        if (value) {
563
0
          termio.c_cflag |= termcode->mapcode;
564
0
        } else {
565
0
          termio.c_cflag &= ~(termcode->mapcode);
566
0
        }
567
0
        break;
568
        
569
0
    }
570
0
  }
571
0
  if (tcsetattr(chansess->master, TCSANOW, &termio) < 0) {
572
0
    dropbear_log(LOG_INFO, "Error setting terminal attributes");
573
0
  }
574
0
  TRACE(("leave get_termmodes"))
575
0
}
576
577
/* Set up a session pty which will be used to execute the shell or program.
578
 * The pty is allocated now, and kept for when the shell/program executes.
579
 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
580
0
static int sessionpty(struct ChanSess * chansess) {
581
582
0
  unsigned int termlen;
583
0
  char namebuf[65];
584
0
  struct passwd * pw = NULL;
585
586
0
  TRACE(("enter sessionpty"))
587
588
0
  if (!svr_pubkey_allows_pty()) {
589
0
    TRACE(("leave sessionpty : pty forbidden by public key option"))
590
0
    return DROPBEAR_FAILURE;
591
0
  }
592
593
0
  chansess->term = buf_getstring(ses.payload, &termlen);
594
0
  if (termlen > MAX_TERM_LEN) {
595
    /* TODO send disconnect ? */
596
0
    TRACE(("leave sessionpty: term len too long"))
597
0
    return DROPBEAR_FAILURE;
598
0
  }
599
600
  /* allocate the pty */
601
0
  if (chansess->master != -1) {
602
0
    dropbear_exit("Multiple pty requests");
603
0
  }
604
0
  if (pty_allocate(&chansess->master, &chansess->slave, namebuf, 64) == 0) {
605
0
    TRACE(("leave sessionpty: failed to allocate pty"))
606
0
    return DROPBEAR_FAILURE;
607
0
  }
608
  
609
0
  chansess->tty = m_strdup(namebuf);
610
0
  if (!chansess->tty) {
611
0
    dropbear_exit("Out of memory"); /* TODO disconnect */
612
0
  }
613
614
0
  pw = getpwnam(ses.authstate.pw_name);
615
0
  if (!pw)
616
0
    dropbear_exit("getpwnam failed after succeeding previously");
617
0
  pty_setowner(pw, chansess->tty);
618
619
  /* Set up the rows/col counts */
620
0
  sessionwinchange(chansess);
621
622
  /* Read the terminal modes */
623
0
  get_termmodes(chansess);
624
625
0
  TRACE(("leave sessionpty"))
626
0
  return DROPBEAR_SUCCESS;
627
0
}
628
629
#if !DROPBEAR_VFORK
630
0
static void make_connection_string(struct ChanSess *chansess) {
631
0
  char *local_ip, *local_port, *remote_ip, *remote_port;
632
0
  size_t len;
633
0
  get_socket_address(ses.sock_in, &local_ip, &local_port, &remote_ip, &remote_port, 0);
634
635
  /* "remoteip remoteport localip localport" */
636
0
  len = strlen(local_ip) + strlen(remote_ip) + 20;
637
0
  chansess->connection_string = m_malloc(len);
638
0
  snprintf(chansess->connection_string, len, "%s %s %s %s", remote_ip, remote_port, local_ip, local_port);
639
640
  /* deprecated but bash only loads .bashrc if SSH_CLIENT is set */ 
641
  /* "remoteip remoteport localport" */
642
0
  len = strlen(remote_ip) + 20;
643
0
  chansess->client_string = m_malloc(len);
644
0
  snprintf(chansess->client_string, len, "%s %s %s", remote_ip, remote_port, local_port);
645
646
0
  m_free(local_ip);
647
0
  m_free(local_port);
648
0
  m_free(remote_ip);
649
0
  m_free(remote_port);
650
0
}
651
#endif
652
653
/* Handle a command request from the client. This is used for both shell
654
 * and command-execution requests, and passes the command to
655
 * noptycommand or ptycommand as appropriate.
656
 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
657
static int sessioncommand(struct Channel *channel, struct ChanSess *chansess,
658
0
    int iscmd, int issubsys) {
659
660
0
  unsigned int cmdlen = 0;
661
0
  int ret;
662
663
0
  TRACE(("enter sessioncommand %d", channel->index))
664
665
0
  if (chansess->pid != 0) {
666
    /* Note that only one command can _succeed_. The client might try
667
     * one command (which fails), then try another. Ie fallback
668
     * from sftp to scp */
669
0
    TRACE(("leave sessioncommand, already have a command"))
670
0
    return DROPBEAR_FAILURE;
671
0
  }
672
673
0
  if (iscmd) {
674
    /* "exec" */
675
0
    if (chansess->cmd == NULL) {
676
0
      chansess->cmd = buf_getstring(ses.payload, &cmdlen);
677
678
0
      if (cmdlen > MAX_CMD_LEN) {
679
0
        m_free(chansess->cmd);
680
        /* TODO - send error - too long ? */
681
0
        TRACE(("leave sessioncommand, command too long %d", cmdlen))
682
0
        return DROPBEAR_FAILURE;
683
0
      }
684
0
    }
685
0
    if (issubsys) {
686
0
#if DROPBEAR_SFTPSERVER
687
0
      if ((cmdlen == 4) && strncmp(chansess->cmd, "sftp", 4) == 0) {
688
0
        char *expand_path = expand_homedir_path(SFTPSERVER_PATH);
689
0
        m_free(chansess->cmd);
690
0
        chansess->cmd = m_strdup(expand_path);
691
0
        m_free(expand_path);
692
0
      } else 
693
0
#endif
694
0
      {
695
0
        m_free(chansess->cmd);
696
0
        TRACE(("leave sessioncommand, unknown subsystem"))
697
0
        return DROPBEAR_FAILURE;
698
0
      }
699
0
    }
700
0
  }
701
  
702
703
  /* take global command into account */
704
0
  if (svr_opts.forced_command) {
705
0
    if (chansess->cmd) {
706
0
      chansess->original_command = chansess->cmd;
707
0
    } else {
708
0
      chansess->original_command = m_strdup("");
709
0
    }
710
0
    chansess->cmd = m_strdup(svr_opts.forced_command);
711
0
  } else {
712
    /* take public key option 'command' into account */
713
0
    svr_pubkey_set_forced_command(chansess);
714
0
  }
715
716
717
#if LOG_COMMANDS
718
  if (chansess->cmd) {
719
    dropbear_log(LOG_INFO, "User %s executing '%s'", 
720
            ses.authstate.pw_name, chansess->cmd);
721
  } else {
722
    dropbear_log(LOG_INFO, "User %s executing login shell", 
723
            ses.authstate.pw_name);
724
  }
725
#endif
726
727
  /* uClinux will vfork(), so there'll be a race as 
728
  connection_string is freed below. */
729
0
#if !DROPBEAR_VFORK
730
0
  make_connection_string(chansess);
731
0
#endif
732
733
0
  if (chansess->term == NULL) {
734
    /* no pty */
735
0
    ret = noptycommand(channel, chansess);
736
0
    if (ret == DROPBEAR_SUCCESS) {
737
0
      channel->prio = DROPBEAR_PRIO_NORMAL;
738
0
      update_channel_prio();
739
0
    }
740
0
  } else {
741
    /* want pty */
742
0
    ret = ptycommand(channel, chansess);
743
0
  }
744
745
0
#if !DROPBEAR_VFORK
746
0
  m_free(chansess->connection_string);
747
0
  m_free(chansess->client_string);
748
0
#endif
749
750
0
  if (ret == DROPBEAR_FAILURE) {
751
0
    m_free(chansess->cmd);
752
0
  }
753
0
  TRACE(("leave sessioncommand, ret %d", ret))
754
0
  return ret;
755
0
}
756
757
/* Execute a command and set up redirection of stdin/stdout/stderr without a
758
 * pty.
759
 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
760
0
static int noptycommand(struct Channel *channel, struct ChanSess *chansess) {
761
0
  int ret;
762
763
0
  TRACE(("enter noptycommand"))
764
0
  ret = spawn_command(execchild, chansess, 
765
0
      &channel->writefd, &channel->readfd, &channel->errfd,
766
0
      &chansess->pid);
767
768
0
  if (ret == DROPBEAR_FAILURE) {
769
0
    return ret;
770
0
  }
771
772
0
  ses.maxfd = MAX(ses.maxfd, channel->writefd);
773
0
  ses.maxfd = MAX(ses.maxfd, channel->readfd);
774
0
  ses.maxfd = MAX(ses.maxfd, channel->errfd);
775
0
  channel->bidir_fd = 0;
776
777
0
  addchildpid(chansess, chansess->pid);
778
779
0
  if (svr_ses.lastexit.exitpid != -1) {
780
0
    unsigned int i;
781
0
    TRACE(("parent side: lastexitpid is %d", svr_ses.lastexit.exitpid))
782
    /* The child probably exited and the signal handler triggered
783
     * possibly before we got around to adding the childpid. So we fill
784
     * out its data manually */
785
0
    for (i = 0; i < svr_ses.childpidsize; i++) {
786
0
      if (svr_ses.childpids[i].pid == svr_ses.lastexit.exitpid) {
787
0
        TRACE(("found match for lastexitpid"))
788
0
        svr_ses.childpids[i].chansess->exit = svr_ses.lastexit;
789
0
        svr_ses.lastexit.exitpid = -1;
790
0
        break;
791
0
      }
792
0
    }
793
0
  }
794
795
0
  TRACE(("leave noptycommand"))
796
0
  return DROPBEAR_SUCCESS;
797
0
}
798
799
/* Execute a command or shell within a pty environment, and set up
800
 * redirection as appropriate.
801
 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
802
0
static int ptycommand(struct Channel *channel, struct ChanSess *chansess) {
803
804
0
  pid_t pid;
805
0
  struct logininfo *li = NULL;
806
0
#if DO_MOTD
807
0
  buffer * motdbuf = NULL;
808
0
  int len;
809
0
  struct stat sb;
810
0
  char *hushpath = NULL;
811
0
#endif
812
813
0
  TRACE(("enter ptycommand"))
814
815
  /* we need to have a pty allocated */
816
0
  if (chansess->master == -1 || chansess->tty == NULL) {
817
0
    dropbear_log(LOG_WARNING, "No pty was allocated, couldn't execute");
818
0
    return DROPBEAR_FAILURE;
819
0
  }
820
  
821
#if DROPBEAR_VFORK
822
  pid = vfork();
823
#else
824
0
  pid = fork();
825
0
#endif
826
0
  if (pid < 0)
827
0
    return DROPBEAR_FAILURE;
828
829
0
  if (pid == 0) {
830
    /* child */
831
    
832
0
    TRACE(("back to normal sigchld"))
833
    /* Revert to normal sigchld handling */
834
0
    if (signal(SIGCHLD, SIG_DFL) == SIG_ERR) {
835
0
      dropbear_exit("signal() error");
836
0
    }
837
    
838
    /* redirect stdin/stdout/stderr */
839
0
    close(chansess->master);
840
841
0
    pty_make_controlling_tty(&chansess->slave, chansess->tty);
842
    
843
0
    if ((dup2(chansess->slave, STDIN_FILENO) < 0) ||
844
0
      (dup2(chansess->slave, STDOUT_FILENO) < 0)) {
845
0
      TRACE(("leave ptycommand: error redirecting filedesc"))
846
0
      return DROPBEAR_FAILURE;
847
0
      }
848
849
    /* write the utmp/wtmp login record - must be after changing the
850
     * terminal used for stdout with the dup2 above, otherwise
851
     * the wtmp login will not be recorded */
852
0
    li = chansess_login_alloc(chansess);
853
0
    login_login(li);
854
0
    login_free_entry(li);
855
856
    /* Can now dup2 stderr. Messages from login_login() have gone
857
    to the parent stderr */
858
0
    if (dup2(chansess->slave, STDERR_FILENO) < 0) {
859
0
      TRACE(("leave ptycommand: error redirecting filedesc"))
860
0
      return DROPBEAR_FAILURE;
861
0
    }
862
863
0
    close(chansess->slave);
864
865
0
#if DO_MOTD
866
0
    if (svr_opts.domotd && !chansess->cmd) {
867
      /* don't show the motd if ~/.hushlogin exists */
868
869
      /* 12 == strlen("/.hushlogin\0") */
870
0
      len = strlen(ses.authstate.pw_dir) + 12; 
871
872
0
      hushpath = m_malloc(len);
873
0
      snprintf(hushpath, len, "%s/.hushlogin", ses.authstate.pw_dir);
874
875
0
      if (stat(hushpath, &sb) < 0) {
876
0
        char *expand_path = NULL;
877
        /* more than a screenful is stupid IMHO */
878
0
        motdbuf = buf_new(80 * 25);
879
0
        expand_path = expand_homedir_path(MOTD_FILENAME);
880
0
        if (buf_readfile(motdbuf, expand_path) == DROPBEAR_SUCCESS) {
881
0
          buf_setpos(motdbuf, 0);
882
0
          while (motdbuf->pos != motdbuf->len) {
883
0
            len = motdbuf->len - motdbuf->pos;
884
0
            len = write(STDOUT_FILENO, 
885
0
                buf_getptr(motdbuf, len), len);
886
0
            buf_incrpos(motdbuf, len);
887
0
          }
888
0
        }
889
0
        m_free(expand_path);
890
0
        buf_free(motdbuf);
891
892
0
      }
893
0
      m_free(hushpath);
894
0
    }
895
0
#endif /* DO_MOTD */
896
897
0
    execchild(chansess);
898
    /* not reached */
899
900
0
  } else {
901
    /* parent */
902
0
    TRACE(("continue ptycommand: parent"))
903
0
    chansess->pid = pid;
904
905
    /* add a child pid */
906
0
    addchildpid(chansess, pid);
907
908
0
    close(chansess->slave);
909
0
    channel->writefd = chansess->master;
910
0
    channel->readfd = chansess->master;
911
    /* don't need to set stderr here */
912
0
    ses.maxfd = MAX(ses.maxfd, chansess->master);
913
0
    channel->bidir_fd = 1;
914
915
0
    setnonblocking(chansess->master);
916
917
0
  }
918
919
0
  TRACE(("leave ptycommand"))
920
0
  return DROPBEAR_SUCCESS;
921
0
}
922
923
/* Add the pid of a child to the list for exit-handling */
924
0
static void addchildpid(struct ChanSess *chansess, pid_t pid) {
925
926
0
  unsigned int i;
927
0
  for (i = 0; i < svr_ses.childpidsize; i++) {
928
0
    if (svr_ses.childpids[i].pid == -1) {
929
0
      break;
930
0
    }
931
0
  }
932
933
  /* need to increase size */
934
0
  if (i == svr_ses.childpidsize) {
935
0
    svr_ses.childpids = (struct ChildPid*)m_realloc(svr_ses.childpids,
936
0
        sizeof(struct ChildPid) * (svr_ses.childpidsize+1));
937
0
    svr_ses.childpidsize++;
938
0
  }
939
  
940
0
  TRACE(("addchildpid %d pid %d for chansess %p", i, pid, chansess))
941
0
  svr_ses.childpids[i].pid = pid;
942
0
  svr_ses.childpids[i].chansess = chansess;
943
944
0
}
945
946
/* Clean up, drop to user privileges, set up the environment and execute
947
 * the command/shell. This function does not return. */
948
0
static void execchild(const void *user_data) {
949
0
  const struct ChanSess *chansess = user_data;
950
0
  char *usershell = NULL;
951
0
  char *cp = NULL;
952
0
  char *envcp = getenv("LANG");
953
0
  if (envcp != NULL) {
954
0
    cp = m_strdup(envcp);
955
0
  }
956
957
  /* with uClinux we'll have vfork()ed, so don't want to overwrite the
958
   * hostkey. can't think of a workaround to clear it */
959
0
#if !DROPBEAR_VFORK
960
  /* wipe the hostkey */
961
0
  sign_key_free(svr_opts.hostkey);
962
0
  svr_opts.hostkey = NULL;
963
964
  /* overwrite the prng state */
965
0
  seedrandom();
966
0
#endif
967
968
  /* clear environment if -e was not set */
969
  /* if we're debugging using valgrind etc, we need to keep the LD_PRELOAD
970
   * etc. This is hazardous, so should only be used for debugging. */
971
0
  if ( !svr_opts.pass_on_env) {
972
0
#ifndef DEBUG_VALGRIND
973
0
#ifdef HAVE_CLEARENV
974
0
    clearenv();
975
#else /* don't HAVE_CLEARENV */
976
    /* Yay for posix. */
977
    if (environ) {
978
      environ[0] = NULL;
979
    }
980
#endif /* HAVE_CLEARENV */
981
0
#endif /* DEBUG_VALGRIND */
982
0
  }
983
984
0
#if DROPBEAR_SVR_MULTIUSER
985
  /* We can only change uid/gid as root ... */
986
0
  if (getuid() == 0) {
987
988
0
    if ((setgid(ses.authstate.pw_gid) < 0) ||
989
0
      (initgroups(ses.authstate.pw_name, 
990
0
            ses.authstate.pw_gid) < 0)) {
991
0
      dropbear_exit("Error changing user group");
992
0
    }
993
0
    if (setuid(ses.authstate.pw_uid) < 0) {
994
0
      dropbear_exit("Error changing user");
995
0
    }
996
0
  } else {
997
    /* ... but if the daemon is the same uid as the requested uid, we don't
998
     * need to */
999
1000
    /* XXX - there is a minor issue here, in that if there are multiple
1001
     * usernames with the same uid, but differing groups, then the
1002
     * differing groups won't be set (as with initgroups()). The solution
1003
     * is for the sysadmin not to give out the UID twice */
1004
0
    if (getuid() != ses.authstate.pw_uid) {
1005
0
      dropbear_exit("Couldn't change user as non-root");
1006
0
    }
1007
0
  }
1008
0
#endif
1009
1010
  /* set env vars */
1011
0
  addnewvar("USER", ses.authstate.pw_name);
1012
0
  addnewvar("LOGNAME", ses.authstate.pw_name);
1013
0
  addnewvar("HOME", ses.authstate.pw_dir);
1014
0
  addnewvar("SHELL", get_user_shell());
1015
0
  if (getuid() == 0) {
1016
0
    addnewvar("PATH", DEFAULT_ROOT_PATH);
1017
0
  } else {
1018
0
    addnewvar("PATH", DEFAULT_PATH);
1019
0
  }
1020
0
  if (cp != NULL) {
1021
0
    addnewvar("LANG", cp);
1022
0
    m_free(cp);
1023
0
  } 
1024
0
  if (chansess->term != NULL) {
1025
0
    addnewvar("TERM", chansess->term);
1026
0
  }
1027
1028
0
  if (chansess->tty) {
1029
0
    addnewvar("SSH_TTY", chansess->tty);
1030
0
  }
1031
  
1032
0
  if (chansess->connection_string) {
1033
0
    addnewvar("SSH_CONNECTION", chansess->connection_string);
1034
0
  }
1035
1036
0
  if (chansess->client_string) {
1037
0
    addnewvar("SSH_CLIENT", chansess->client_string);
1038
0
  }
1039
  
1040
0
  if (chansess->original_command) {
1041
0
    addnewvar("SSH_ORIGINAL_COMMAND", chansess->original_command);
1042
0
  }
1043
0
#if DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT
1044
0
  if (ses.authstate.pubkey_info != NULL) {
1045
0
    addnewvar("SSH_PUBKEYINFO", ses.authstate.pubkey_info);
1046
0
  }
1047
0
#endif
1048
1049
  /* change directory */
1050
0
  if (chdir(ses.authstate.pw_dir) < 0) {
1051
0
    int e = errno;
1052
0
    if (chdir("/") < 0) {
1053
0
      dropbear_exit("chdir(\"/\") failed");
1054
0
    }
1055
0
    fprintf(stderr, "Failed chdir '%s': %s\n", ses.authstate.pw_dir, strerror(e));
1056
0
  }
1057
1058
1059
#if DROPBEAR_X11FWD
1060
  /* set up X11 forwarding if enabled */
1061
  x11setauth(chansess);
1062
#endif
1063
0
#if DROPBEAR_SVR_AGENTFWD
1064
  /* set up agent env variable */
1065
0
  svr_agentset(chansess);
1066
0
#endif
1067
1068
0
  usershell = m_strdup(get_user_shell());
1069
0
  run_shell_command(chansess->cmd, ses.maxfd, usershell);
1070
1071
  /* only reached on error */
1072
0
  dropbear_exit("Child failed");
1073
0
}
1074
1075
/* Set up the general chansession environment, in particular child-exit
1076
 * handling */
1077
2.69k
void svr_chansessinitialise() {
1078
1079
2.69k
  struct sigaction sa_chld;
1080
1081
  /* single child process intially */
1082
2.69k
  svr_ses.childpids = (struct ChildPid*)m_malloc(sizeof(struct ChildPid));
1083
2.69k
  svr_ses.childpids[0].pid = -1; /* unused */
1084
2.69k
  svr_ses.childpids[0].chansess = NULL;
1085
2.69k
  svr_ses.childpidsize = 1;
1086
2.69k
  svr_ses.lastexit.exitpid = -1; /* Nothing has exited yet */
1087
2.69k
  sa_chld.sa_handler = sesssigchild_handler;
1088
2.69k
  sa_chld.sa_flags = SA_NOCLDSTOP;
1089
2.69k
  sigemptyset(&sa_chld.sa_mask);
1090
2.69k
  if (sigaction(SIGCHLD, &sa_chld, NULL) < 0) {
1091
0
    dropbear_exit("signal() error");
1092
0
  }
1093
  
1094
2.69k
}
1095
1096
/* add a new environment variable, allocating space for the entry */
1097
0
void addnewvar(const char* param, const char* var) {
1098
1099
0
  char* newvar = NULL;
1100
0
  int plen, vlen;
1101
1102
0
  plen = strlen(param);
1103
0
  vlen = strlen(var);
1104
1105
0
  newvar = m_malloc(plen + vlen + 2); /* 2 is for '=' and '\0' */
1106
0
  memcpy(newvar, param, plen);
1107
0
  newvar[plen] = '=';
1108
0
  memcpy(&newvar[plen+1], var, vlen);
1109
0
  newvar[plen+vlen+1] = '\0';
1110
  /* newvar is leaked here, but that's part of putenv()'s semantics */
1111
0
  if (putenv(newvar) < 0) {
1112
0
    dropbear_exit("environ error");
1113
0
  }
1114
0
}