Coverage Report

Created: 2025-10-13 07:06

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