Changes to update Tomato RAF.
[tomato.git] / release / src / router / dropbear / svr-chansession.c
blob9fd49c112074bf0dac54f3bd69e221470d653f2b
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:
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
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. */
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 "random.h"
36 #include "x11fwd.h"
37 #include "agentfwd.h"
38 #include "runopts.h"
39 #include "auth.h"
41 /* Handles sessions (either shells or programs) requested by the client */
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(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(struct ChanSess *chansess);
50 static void execchild(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(struct Channel *channel);
54 static int newchansess(struct Channel *channel);
55 static void chansessionrequest(struct Channel *channel);
57 static void send_exitsignalstatus(struct Channel *channel);
58 static void send_msg_chansess_exitstatus(struct Channel * channel,
59 struct ChanSess * chansess);
60 static void send_msg_chansess_exitsignal(struct Channel * channel,
61 struct ChanSess * chansess);
62 static void get_termmodes(struct ChanSess *chansess);
65 /* required to clear environment */
66 extern char** environ;
68 static int sesscheckclose(struct Channel *channel) {
69 struct ChanSess *chansess = (struct ChanSess*)channel->typedata;
70 TRACE(("sesscheckclose, pid is %d", chansess->exit.exitpid))
71 return chansess->exit.exitpid != -1;
74 /* Handler for childs exiting, store the state for return to the client */
76 /* There's a particular race we have to watch out for: if the forked child
77 * executes, exits, and this signal-handler is called, all before the parent
78 * gets to run, then the childpids[] array won't have the pid in it. Hence we
79 * use the svr_ses.lastexit struct to hold the exit, which is then compared by
80 * the parent when it runs. This work correctly at least in the case of a
81 * single shell spawned (ie the usual case) */
82 static void sesssigchild_handler(int UNUSED(dummy)) {
84 int status;
85 pid_t pid;
86 unsigned int i;
87 struct sigaction sa_chld;
88 struct exitinfo *exit = NULL;
90 TRACE(("enter sigchld handler"))
91 while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
92 TRACE(("sigchld handler: pid %d", pid))
94 exit = NULL;
95 /* find the corresponding chansess */
96 for (i = 0; i < svr_ses.childpidsize; i++) {
97 if (svr_ses.childpids[i].pid == pid) {
98 TRACE(("found match session"));
99 exit = &svr_ses.childpids[i].chansess->exit;
100 break;
104 /* If the pid wasn't matched, then we might have hit the race mentioned
105 * above. So we just store the info for the parent to deal with */
106 if (exit == NULL) {
107 TRACE(("using lastexit"));
108 exit = &svr_ses.lastexit;
111 exit->exitpid = pid;
112 if (WIFEXITED(status)) {
113 exit->exitstatus = WEXITSTATUS(status);
115 if (WIFSIGNALED(status)) {
116 exit->exitsignal = WTERMSIG(status);
117 #if !defined(AIX) && defined(WCOREDUMP)
118 exit->exitcore = WCOREDUMP(status);
119 #else
120 exit->exitcore = 0;
121 #endif
122 } else {
123 /* we use this to determine how pid exited */
124 exit->exitsignal = -1;
127 /* Make sure that the main select() loop wakes up */
128 while (1) {
129 /* isserver is just a random byte to write. We can't do anything
130 about an error so should just ignore it */
131 if (write(ses.signal_pipe[1], &ses.isserver, 1) == 1
132 || errno != EINTR) {
133 break;
138 sa_chld.sa_handler = sesssigchild_handler;
139 sa_chld.sa_flags = SA_NOCLDSTOP;
140 sigemptyset(&sa_chld.sa_mask);
141 sigaction(SIGCHLD, &sa_chld, NULL);
142 TRACE(("leave sigchld handler"))
145 /* send the exit status or the signal causing termination for a session */
146 static void send_exitsignalstatus(struct Channel *channel) {
148 struct ChanSess *chansess = (struct ChanSess*)channel->typedata;
150 if (chansess->exit.exitpid >= 0) {
151 if (chansess->exit.exitsignal > 0) {
152 send_msg_chansess_exitsignal(channel, chansess);
153 } else {
154 send_msg_chansess_exitstatus(channel, chansess);
159 /* send the exitstatus to the client */
160 static void send_msg_chansess_exitstatus(struct Channel * channel,
161 struct ChanSess * chansess) {
163 dropbear_assert(chansess->exit.exitpid != -1);
164 dropbear_assert(chansess->exit.exitsignal == -1);
166 CHECKCLEARTOWRITE();
168 buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_REQUEST);
169 buf_putint(ses.writepayload, channel->remotechan);
170 buf_putstring(ses.writepayload, "exit-status", 11);
171 buf_putbyte(ses.writepayload, 0); /* boolean FALSE */
172 buf_putint(ses.writepayload, chansess->exit.exitstatus);
174 encrypt_packet();
178 /* send the signal causing the exit to the client */
179 static void send_msg_chansess_exitsignal(struct Channel * channel,
180 struct ChanSess * chansess) {
182 int i;
183 char* signame = NULL;
184 dropbear_assert(chansess->exit.exitpid != -1);
185 dropbear_assert(chansess->exit.exitsignal > 0);
187 TRACE(("send_msg_chansess_exitsignal %d", chansess->exit.exitsignal))
189 CHECKCLEARTOWRITE();
191 /* we check that we can match a signal name, otherwise
192 * don't send anything */
193 for (i = 0; signames[i].name != NULL; i++) {
194 if (signames[i].signal == chansess->exit.exitsignal) {
195 signame = signames[i].name;
196 break;
200 if (signame == NULL) {
201 return;
204 buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_REQUEST);
205 buf_putint(ses.writepayload, channel->remotechan);
206 buf_putstring(ses.writepayload, "exit-signal", 11);
207 buf_putbyte(ses.writepayload, 0); /* boolean FALSE */
208 buf_putstring(ses.writepayload, signame, strlen(signame));
209 buf_putbyte(ses.writepayload, chansess->exit.exitcore);
210 buf_putstring(ses.writepayload, "", 0); /* error msg */
211 buf_putstring(ses.writepayload, "", 0); /* lang */
213 encrypt_packet();
216 /* set up a session channel */
217 static int newchansess(struct Channel *channel) {
219 struct ChanSess *chansess;
221 TRACE(("new chansess %p", channel))
223 dropbear_assert(channel->typedata == NULL);
225 chansess = (struct ChanSess*)m_malloc(sizeof(struct ChanSess));
226 chansess->cmd = NULL;
227 chansess->connection_string = NULL;
228 chansess->pid = 0;
230 /* pty details */
231 chansess->master = -1;
232 chansess->slave = -1;
233 chansess->tty = NULL;
234 chansess->term = NULL;
236 chansess->exit.exitpid = -1;
238 channel->typedata = chansess;
240 #ifndef DISABLE_X11FWD
241 chansess->x11listener = NULL;
242 chansess->x11authprot = NULL;
243 chansess->x11authcookie = NULL;
244 #endif
246 #ifdef ENABLE_AGENTFWD
247 chansess->agentlistener = NULL;
248 chansess->agentfile = NULL;
249 chansess->agentdir = NULL;
250 #endif
252 return 0;
256 static struct logininfo*
257 chansess_login_alloc(struct ChanSess *chansess) {
258 struct logininfo * li;
259 li = login_alloc_entry(chansess->pid, ses.authstate.username,
260 svr_ses.remotehost, chansess->tty);
261 return li;
264 /* clean a session channel */
265 static void closechansess(struct Channel *channel) {
267 struct ChanSess *chansess;
268 unsigned int i;
269 struct logininfo *li;
271 TRACE(("enter closechansess"))
273 chansess = (struct ChanSess*)channel->typedata;
275 if (chansess == NULL) {
276 TRACE(("leave closechansess: chansess == NULL"))
277 return;
280 send_exitsignalstatus(channel);
282 m_free(chansess->cmd);
283 m_free(chansess->term);
285 #ifdef ENABLE_SVR_PUBKEY_OPTIONS
286 m_free(chansess->original_command);
287 #endif
289 if (chansess->tty) {
290 /* write the utmp/wtmp login record */
291 li = chansess_login_alloc(chansess);
292 login_logout(li);
293 login_free_entry(li);
295 pty_release(chansess->tty);
296 m_free(chansess->tty);
299 #ifndef DISABLE_X11FWD
300 x11cleanup(chansess);
301 #endif
303 #ifdef ENABLE_AGENTFWD
304 svr_agentcleanup(chansess);
305 #endif
307 /* clear child pid entries */
308 for (i = 0; i < svr_ses.childpidsize; i++) {
309 if (svr_ses.childpids[i].chansess == chansess) {
310 dropbear_assert(svr_ses.childpids[i].pid > 0);
311 TRACE(("closing pid %d", svr_ses.childpids[i].pid))
312 TRACE(("exitpid is %d", chansess->exit.exitpid))
313 svr_ses.childpids[i].pid = -1;
314 svr_ses.childpids[i].chansess = NULL;
318 m_free(chansess);
320 TRACE(("leave closechansess"))
323 /* Handle requests for a channel. These can be execution requests,
324 * or x11/authagent forwarding. These are passed to appropriate handlers */
325 static void chansessionrequest(struct Channel *channel) {
327 unsigned char * type = NULL;
328 unsigned int typelen;
329 unsigned char wantreply;
330 int ret = 1;
331 struct ChanSess *chansess;
333 TRACE(("enter chansessionrequest"))
335 type = buf_getstring(ses.payload, &typelen);
336 wantreply = buf_getbool(ses.payload);
338 if (typelen > MAX_NAME_LEN) {
339 TRACE(("leave chansessionrequest: type too long")) /* XXX send error?*/
340 goto out;
343 chansess = (struct ChanSess*)channel->typedata;
344 dropbear_assert(chansess != NULL);
345 TRACE(("type is %s", type))
347 if (strcmp(type, "window-change") == 0) {
348 ret = sessionwinchange(chansess);
349 } else if (strcmp(type, "shell") == 0) {
350 ret = sessioncommand(channel, chansess, 0, 0);
351 } else if (strcmp(type, "pty-req") == 0) {
352 ret = sessionpty(chansess);
353 } else if (strcmp(type, "exec") == 0) {
354 ret = sessioncommand(channel, chansess, 1, 0);
355 } else if (strcmp(type, "subsystem") == 0) {
356 ret = sessioncommand(channel, chansess, 1, 1);
357 #ifndef DISABLE_X11FWD
358 } else if (strcmp(type, "x11-req") == 0) {
359 ret = x11req(chansess);
360 #endif
361 #ifdef ENABLE_AGENTFWD
362 } else if (strcmp(type, "auth-agent-req@openssh.com") == 0) {
363 ret = svr_agentreq(chansess);
364 #endif
365 } else if (strcmp(type, "signal") == 0) {
366 ret = sessionsignal(chansess);
367 } else {
368 /* etc, todo "env", "subsystem" */
371 out:
373 if (wantreply) {
374 if (ret == DROPBEAR_SUCCESS) {
375 send_msg_channel_success(channel);
376 } else {
377 send_msg_channel_failure(channel);
381 m_free(type);
382 TRACE(("leave chansessionrequest"))
386 /* Send a signal to a session's process as requested by the client*/
387 static int sessionsignal(struct ChanSess *chansess) {
389 int sig = 0;
390 unsigned char* signame = NULL;
391 int i;
393 if (chansess->pid == 0) {
394 /* haven't got a process pid yet */
395 return DROPBEAR_FAILURE;
398 signame = buf_getstring(ses.payload, NULL);
400 i = 0;
401 while (signames[i].name != 0) {
402 if (strcmp(signames[i].name, signame) == 0) {
403 sig = signames[i].signal;
404 break;
406 i++;
409 m_free(signame);
411 if (sig == 0) {
412 /* failed */
413 return DROPBEAR_FAILURE;
416 if (kill(chansess->pid, sig) < 0) {
417 return DROPBEAR_FAILURE;
420 return DROPBEAR_SUCCESS;
423 /* Let the process know that the window size has changed, as notified from the
424 * client. Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
425 static int sessionwinchange(struct ChanSess *chansess) {
427 int termc, termr, termw, termh;
429 if (chansess->master < 0) {
430 /* haven't got a pty yet */
431 return DROPBEAR_FAILURE;
434 termc = buf_getint(ses.payload);
435 termr = buf_getint(ses.payload);
436 termw = buf_getint(ses.payload);
437 termh = buf_getint(ses.payload);
439 pty_change_window_size(chansess->master, termr, termc, termw, termh);
441 return DROPBEAR_SUCCESS;
444 static void get_termmodes(struct ChanSess *chansess) {
446 struct termios termio;
447 unsigned char opcode;
448 unsigned int value;
449 const struct TermCode * termcode;
450 unsigned int len;
452 TRACE(("enter get_termmodes"))
454 /* Term modes */
455 /* We'll ignore errors and continue if we can't set modes.
456 * We're ignoring baud rates since they seem evil */
457 if (tcgetattr(chansess->master, &termio) == -1) {
458 return;
461 len = buf_getint(ses.payload);
462 TRACE(("term mode str %d p->l %d p->p %d",
463 len, ses.payload->len , ses.payload->pos));
464 if (len != ses.payload->len - ses.payload->pos) {
465 dropbear_exit("Bad term mode string");
468 if (len == 0) {
469 TRACE(("leave get_termmodes: empty terminal modes string"))
470 return;
473 while (((opcode = buf_getbyte(ses.payload)) != 0x00) && opcode <= 159) {
475 /* must be before checking type, so that value is consumed even if
476 * we don't use it */
477 value = buf_getint(ses.payload);
479 /* handle types of code */
480 if (opcode > MAX_TERMCODE) {
481 continue;
483 termcode = &termcodes[(unsigned int)opcode];
486 switch (termcode->type) {
488 case TERMCODE_NONE:
489 break;
491 case TERMCODE_CONTROLCHAR:
492 termio.c_cc[termcode->mapcode] = value;
493 break;
495 case TERMCODE_INPUT:
496 if (value) {
497 termio.c_iflag |= termcode->mapcode;
498 } else {
499 termio.c_iflag &= ~(termcode->mapcode);
501 break;
503 case TERMCODE_OUTPUT:
504 if (value) {
505 termio.c_oflag |= termcode->mapcode;
506 } else {
507 termio.c_oflag &= ~(termcode->mapcode);
509 break;
511 case TERMCODE_LOCAL:
512 if (value) {
513 termio.c_lflag |= termcode->mapcode;
514 } else {
515 termio.c_lflag &= ~(termcode->mapcode);
517 break;
519 case TERMCODE_CONTROL:
520 if (value) {
521 termio.c_cflag |= termcode->mapcode;
522 } else {
523 termio.c_cflag &= ~(termcode->mapcode);
525 break;
529 if (tcsetattr(chansess->master, TCSANOW, &termio) < 0) {
530 dropbear_log(LOG_INFO, "Error setting terminal attributes");
532 TRACE(("leave get_termmodes"))
535 /* Set up a session pty which will be used to execute the shell or program.
536 * The pty is allocated now, and kept for when the shell/program executes.
537 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
538 static int sessionpty(struct ChanSess * chansess) {
540 unsigned int termlen;
541 unsigned char namebuf[65];
542 struct passwd * pw = NULL;
544 TRACE(("enter sessionpty"))
546 if (!svr_pubkey_allows_pty()) {
547 TRACE(("leave sessionpty : pty forbidden by public key option"))
548 return DROPBEAR_FAILURE;
551 chansess->term = buf_getstring(ses.payload, &termlen);
552 if (termlen > MAX_TERM_LEN) {
553 /* TODO send disconnect ? */
554 TRACE(("leave sessionpty: term len too long"))
555 return DROPBEAR_FAILURE;
558 /* allocate the pty */
559 if (chansess->master != -1) {
560 dropbear_exit("Multiple pty requests");
562 if (pty_allocate(&chansess->master, &chansess->slave, namebuf, 64) == 0) {
563 TRACE(("leave sessionpty: failed to allocate pty"))
564 return DROPBEAR_FAILURE;
567 chansess->tty = (char*)m_strdup(namebuf);
568 if (!chansess->tty) {
569 dropbear_exit("Out of memory"); /* TODO disconnect */
572 pw = getpwnam(ses.authstate.pw_name);
573 if (!pw)
574 dropbear_exit("getpwnam failed after succeeding previously");
575 pty_setowner(pw, chansess->tty);
577 /* Set up the rows/col counts */
578 sessionwinchange(chansess);
580 /* Read the terminal modes */
581 get_termmodes(chansess);
583 TRACE(("leave sessionpty"))
584 return DROPBEAR_SUCCESS;
587 static char* make_connection_string() {
588 char *local_ip, *local_port, *remote_ip, *remote_port;
589 size_t len;
590 char *ret;
591 get_socket_address(ses.sock_in, &local_ip, &local_port, &remote_ip, &remote_port, 0);
592 len = strlen(local_ip) + strlen(local_port) + strlen(remote_ip) + strlen(remote_port) + 4;
593 ret = m_malloc(len);
594 snprintf(ret, len, "%s %s %s %s", remote_ip, remote_port, local_ip, local_port);
595 m_free(local_ip);
596 m_free(local_port);
597 m_free(remote_ip);
598 m_free(remote_port);
599 return ret;
602 /* Handle a command request from the client. This is used for both shell
603 * and command-execution requests, and passes the command to
604 * noptycommand or ptycommand as appropriate.
605 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
606 static int sessioncommand(struct Channel *channel, struct ChanSess *chansess,
607 int iscmd, int issubsys) {
609 unsigned int cmdlen;
610 int ret;
612 TRACE(("enter sessioncommand"))
614 if (chansess->cmd != NULL) {
615 /* Note that only one command can _succeed_. The client might try
616 * one command (which fails), then try another. Ie fallback
617 * from sftp to scp */
618 return DROPBEAR_FAILURE;
621 if (iscmd) {
622 /* "exec" */
623 if (chansess->cmd == NULL) {
624 chansess->cmd = buf_getstring(ses.payload, &cmdlen);
626 if (cmdlen > MAX_CMD_LEN) {
627 m_free(chansess->cmd);
628 /* TODO - send error - too long ? */
629 return DROPBEAR_FAILURE;
632 if (issubsys) {
633 #ifdef SFTPSERVER_PATH
634 if ((cmdlen == 4) && strncmp(chansess->cmd, "sftp", 4) == 0) {
635 m_free(chansess->cmd);
636 chansess->cmd = m_strdup(SFTPSERVER_PATH);
637 } else
638 #endif
640 m_free(chansess->cmd);
641 return DROPBEAR_FAILURE;
646 /* take public key option 'command' into account */
647 svr_pubkey_set_forced_command(chansess);
649 #ifdef LOG_COMMANDS
650 if (chansess->cmd) {
651 dropbear_log(LOG_INFO, "User %s executing '%s'",
652 ses.authstate.pw_name, chansess->cmd);
653 } else {
654 dropbear_log(LOG_INFO, "User %s executing login shell",
655 ses.authstate.pw_name);
657 #endif
659 /* uClinux will vfork(), so there'll be a race as
660 connection_string is freed below. */
661 #ifndef __uClinux__
662 chansess->connection_string = make_connection_string();
663 #endif
665 if (chansess->term == NULL) {
666 /* no pty */
667 ret = noptycommand(channel, chansess);
668 } else {
669 /* want pty */
670 ret = ptycommand(channel, chansess);
673 #ifndef __uClinux__
674 m_free(chansess->connection_string);
675 #endif
677 if (ret == DROPBEAR_FAILURE) {
678 m_free(chansess->cmd);
680 return ret;
683 /* Execute a command and set up redirection of stdin/stdout/stderr without a
684 * pty.
685 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
686 static int noptycommand(struct Channel *channel, struct ChanSess *chansess) {
687 int ret;
689 TRACE(("enter noptycommand"))
690 ret = spawn_command(execchild, chansess,
691 &channel->writefd, &channel->readfd, &channel->errfd,
692 &chansess->pid);
694 if (ret == DROPBEAR_FAILURE) {
695 return ret;
698 ses.maxfd = MAX(ses.maxfd, channel->writefd);
699 ses.maxfd = MAX(ses.maxfd, channel->readfd);
700 ses.maxfd = MAX(ses.maxfd, channel->errfd);
702 sleep(1);
704 addchildpid(chansess, chansess->pid);
706 if (svr_ses.lastexit.exitpid != -1) {
707 unsigned int i;
708 TRACE(("parent side: lastexitpid is %d", svr_ses.lastexit.exitpid))
709 /* The child probably exited and the signal handler triggered
710 * possibly before we got around to adding the childpid. So we fill
711 * out its data manually */
712 for (i = 0; i < svr_ses.childpidsize; i++) {
713 if (svr_ses.childpids[i].pid == svr_ses.lastexit.exitpid) {
714 TRACE(("found match for lastexitpid"))
715 svr_ses.childpids[i].chansess->exit = svr_ses.lastexit;
716 svr_ses.lastexit.exitpid = -1;
717 break;
722 TRACE(("leave noptycommand"))
723 return DROPBEAR_SUCCESS;
726 /* Execute a command or shell within a pty environment, and set up
727 * redirection as appropriate.
728 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
729 static int ptycommand(struct Channel *channel, struct ChanSess *chansess) {
731 pid_t pid;
732 struct logininfo *li = NULL;
733 #ifdef DO_MOTD
734 buffer * motdbuf = NULL;
735 int len;
736 struct stat sb;
737 char *hushpath = NULL;
738 #endif
740 TRACE(("enter ptycommand"))
742 /* we need to have a pty allocated */
743 if (chansess->master == -1 || chansess->tty == NULL) {
744 dropbear_log(LOG_WARNING, "No pty was allocated, couldn't execute");
745 return DROPBEAR_FAILURE;
748 #ifdef __uClinux__
749 pid = vfork();
750 #else
751 pid = fork();
752 #endif
753 if (pid < 0)
754 return DROPBEAR_FAILURE;
756 if (pid == 0) {
757 /* child */
759 TRACE(("back to normal sigchld"))
760 /* Revert to normal sigchld handling */
761 if (signal(SIGCHLD, SIG_DFL) == SIG_ERR) {
762 dropbear_exit("signal() error");
765 /* redirect stdin/stdout/stderr */
766 close(chansess->master);
768 pty_make_controlling_tty(&chansess->slave, chansess->tty);
770 if ((dup2(chansess->slave, STDIN_FILENO) < 0) ||
771 (dup2(chansess->slave, STDERR_FILENO) < 0) ||
772 (dup2(chansess->slave, STDOUT_FILENO) < 0)) {
773 TRACE(("leave ptycommand: error redirecting filedesc"))
774 return DROPBEAR_FAILURE;
777 close(chansess->slave);
779 /* write the utmp/wtmp login record - must be after changing the
780 * terminal used for stdout with the dup2 above */
781 li = chansess_login_alloc(chansess);
782 login_login(li);
783 login_free_entry(li);
785 #ifdef DO_MOTD
786 if (svr_opts.domotd) {
787 /* don't show the motd if ~/.hushlogin exists */
789 /* 12 == strlen("/.hushlogin\0") */
790 len = strlen(ses.authstate.pw_dir) + 12;
792 hushpath = m_malloc(len);
793 snprintf(hushpath, len, "%s/.hushlogin", ses.authstate.pw_dir);
795 if (stat(hushpath, &sb) < 0) {
796 /* more than a screenful is stupid IMHO */
797 motdbuf = buf_new(80 * 25);
798 if (buf_readfile(motdbuf, MOTD_FILENAME) == DROPBEAR_SUCCESS) {
799 buf_setpos(motdbuf, 0);
800 while (motdbuf->pos != motdbuf->len) {
801 len = motdbuf->len - motdbuf->pos;
802 len = write(STDOUT_FILENO,
803 buf_getptr(motdbuf, len), len);
804 buf_incrpos(motdbuf, len);
807 buf_free(motdbuf);
809 m_free(hushpath);
811 #endif /* DO_MOTD */
813 execchild(chansess);
814 /* not reached */
816 } else {
817 /* parent */
818 TRACE(("continue ptycommand: parent"))
819 chansess->pid = pid;
821 /* add a child pid */
822 addchildpid(chansess, pid);
824 close(chansess->slave);
825 channel->writefd = chansess->master;
826 channel->readfd = chansess->master;
827 /* don't need to set stderr here */
828 ses.maxfd = MAX(ses.maxfd, chansess->master);
830 setnonblocking(chansess->master);
834 TRACE(("leave ptycommand"))
835 return DROPBEAR_SUCCESS;
838 /* Add the pid of a child to the list for exit-handling */
839 static void addchildpid(struct ChanSess *chansess, pid_t pid) {
841 unsigned int i;
842 for (i = 0; i < svr_ses.childpidsize; i++) {
843 if (svr_ses.childpids[i].pid == -1) {
844 break;
848 /* need to increase size */
849 if (i == svr_ses.childpidsize) {
850 svr_ses.childpids = (struct ChildPid*)m_realloc(svr_ses.childpids,
851 sizeof(struct ChildPid) * (svr_ses.childpidsize+1));
852 svr_ses.childpidsize++;
855 svr_ses.childpids[i].pid = pid;
856 svr_ses.childpids[i].chansess = chansess;
860 /* Clean up, drop to user privileges, set up the environment and execute
861 * the command/shell. This function does not return. */
862 static void execchild(void *user_data) {
863 struct ChanSess *chansess = user_data;
864 char *usershell = NULL;
866 /* with uClinux we'll have vfork()ed, so don't want to overwrite the
867 * hostkey. can't think of a workaround to clear it */
868 #ifndef __uClinux__
869 /* wipe the hostkey */
870 sign_key_free(svr_opts.hostkey);
871 svr_opts.hostkey = NULL;
873 /* overwrite the prng state */
874 reseedrandom();
875 #endif
877 /* clear environment */
878 /* if we're debugging using valgrind etc, we need to keep the LD_PRELOAD
879 * etc. This is hazardous, so should only be used for debugging. */
880 #ifndef DEBUG_VALGRIND
881 #ifdef HAVE_CLEARENV
882 clearenv();
883 #else /* don't HAVE_CLEARENV */
884 /* Yay for posix. */
885 if (environ) {
886 environ[0] = NULL;
888 #endif /* HAVE_CLEARENV */
889 #endif /* DEBUG_VALGRIND */
891 /* We can only change uid/gid as root ... */
892 if (getuid() == 0) {
894 if ((setgid(ses.authstate.pw_gid) < 0) ||
895 (initgroups(ses.authstate.pw_name,
896 ses.authstate.pw_gid) < 0)) {
897 dropbear_exit("Error changing user group");
899 if (setuid(ses.authstate.pw_uid) < 0) {
900 dropbear_exit("Error changing user");
902 } else {
903 /* ... but if the daemon is the same uid as the requested uid, we don't
904 * need to */
906 /* XXX - there is a minor issue here, in that if there are multiple
907 * usernames with the same uid, but differing groups, then the
908 * differing groups won't be set (as with initgroups()). The solution
909 * is for the sysadmin not to give out the UID twice */
910 if (getuid() != ses.authstate.pw_uid) {
911 dropbear_exit("Couldn't change user as non-root");
915 /* set env vars */
916 addnewvar("USER", ses.authstate.pw_name);
917 addnewvar("LOGNAME", ses.authstate.pw_name);
918 addnewvar("HOME", ses.authstate.pw_dir);
919 addnewvar("SHELL", get_user_shell());
920 addnewvar("PATH", DEFAULT_PATH);
921 if (chansess->term != NULL) {
922 addnewvar("TERM", chansess->term);
925 if (chansess->tty) {
926 addnewvar("SSH_TTY", chansess->tty);
929 if (chansess->connection_string) {
930 addnewvar("SSH_CONNECTION", chansess->connection_string);
933 #ifdef ENABLE_SVR_PUBKEY_OPTIONS
934 if (chansess->original_command) {
935 addnewvar("SSH_ORIGINAL_COMMAND", chansess->original_command);
937 #endif
939 /* change directory */
940 if (chdir(ses.authstate.pw_dir) < 0) {
941 dropbear_exit("Error changing directory");
944 #ifndef DISABLE_X11FWD
945 /* set up X11 forwarding if enabled */
946 x11setauth(chansess);
947 #endif
948 #ifdef ENABLE_AGENTFWD
949 /* set up agent env variable */
950 svr_agentset(chansess);
951 #endif
953 usershell = m_strdup(get_user_shell());
954 run_shell_command(chansess->cmd, ses.maxfd, usershell);
956 /* only reached on error */
957 dropbear_exit("Child failed");
960 const struct ChanType svrchansess = {
961 0, /* sepfds */
962 "session", /* name */
963 newchansess, /* inithandler */
964 sesscheckclose, /* checkclosehandler */
965 chansessionrequest, /* reqhandler */
966 closechansess, /* closehandler */
970 /* Set up the general chansession environment, in particular child-exit
971 * handling */
972 void svr_chansessinitialise() {
974 struct sigaction sa_chld;
976 /* single child process intially */
977 svr_ses.childpids = (struct ChildPid*)m_malloc(sizeof(struct ChildPid));
978 svr_ses.childpids[0].pid = -1; /* unused */
979 svr_ses.childpids[0].chansess = NULL;
980 svr_ses.childpidsize = 1;
981 svr_ses.lastexit.exitpid = -1; /* Nothing has exited yet */
982 sa_chld.sa_handler = sesssigchild_handler;
983 sa_chld.sa_flags = SA_NOCLDSTOP;
984 sigemptyset(&sa_chld.sa_mask);
985 if (sigaction(SIGCHLD, &sa_chld, NULL) < 0) {
986 dropbear_exit("signal() error");
991 /* add a new environment variable, allocating space for the entry */
992 void addnewvar(const char* param, const char* var) {
994 char* newvar = NULL;
995 int plen, vlen;
997 plen = strlen(param);
998 vlen = strlen(var);
1000 newvar = m_malloc(plen + vlen + 2); /* 2 is for '=' and '\0' */
1001 memcpy(newvar, param, plen);
1002 newvar[plen] = '=';
1003 memcpy(&newvar[plen+1], var, vlen);
1004 newvar[plen+vlen+1] = '\0';
1005 /* newvar is leaked here, but that's part of putenv()'s semantics */
1006 if (putenv(newvar) < 0) {
1007 dropbear_exit("environ error");