Coverage Report

Created: 2026-03-01 07:07

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