merge of '48fdaa8706d1acda35e9d564adc9a1fbc96c18c8'
[dropbear.git] / svr-chansession.c
blob5ecc57fc6078f32b95a732a0a085cd340a2cfe90
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 "utmp.h"
37 #include "x11fwd.h"
38 #include "agentfwd.h"
39 #include "runopts.h"
40 #include "auth.h"
42 /* Handles sessions (either shells or programs) requested by the client */
44 static int sessioncommand(struct Channel *channel, struct ChanSess *chansess,
45 int iscmd, int issubsys);
46 static int sessionpty(struct ChanSess * chansess);
47 static int sessionsignal(struct ChanSess *chansess);
48 static int noptycommand(struct Channel *channel, struct ChanSess *chansess);
49 static int ptycommand(struct Channel *channel, struct ChanSess *chansess);
50 static int sessionwinchange(struct ChanSess *chansess);
51 static void execchild(void *user_data_chansess);
52 static void addchildpid(struct ChanSess *chansess, pid_t pid);
53 static void sesssigchild_handler(int val);
54 static void closechansess(struct Channel *channel);
55 static int newchansess(struct Channel *channel);
56 static void chansessionrequest(struct Channel *channel);
58 static void send_exitsignalstatus(struct Channel *channel);
59 static void send_msg_chansess_exitstatus(struct Channel * channel,
60 struct ChanSess * chansess);
61 static void send_msg_chansess_exitsignal(struct Channel * channel,
62 struct ChanSess * chansess);
63 static void get_termmodes(struct ChanSess *chansess);
66 /* required to clear environment */
67 extern char** environ;
69 static int sesscheckclose(struct Channel *channel) {
70 struct ChanSess *chansess = (struct ChanSess*)channel->typedata;
71 TRACE(("sesscheckclose, pid is %d", chansess->exit.exitpid))
72 return chansess->exit.exitpid != -1;
75 /* Handler for childs exiting, store the state for return to the client */
77 /* There's a particular race we have to watch out for: if the forked child
78 * executes, exits, and this signal-handler is called, all before the parent
79 * gets to run, then the childpids[] array won't have the pid in it. Hence we
80 * use the svr_ses.lastexit struct to hold the exit, which is then compared by
81 * the parent when it runs. This work correctly at least in the case of a
82 * single shell spawned (ie the usual case) */
83 static void sesssigchild_handler(int UNUSED(dummy)) {
85 int status;
86 pid_t pid;
87 unsigned int i;
88 struct sigaction sa_chld;
89 struct exitinfo *exit = NULL;
91 TRACE(("enter sigchld handler"))
92 while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
93 TRACE(("sigchld handler: pid %d", pid))
95 exit = NULL;
96 /* find the corresponding chansess */
97 for (i = 0; i < svr_ses.childpidsize; i++) {
98 if (svr_ses.childpids[i].pid == pid) {
99 TRACE(("found match session"));
100 exit = &svr_ses.childpids[i].chansess->exit;
101 break;
105 /* If the pid wasn't matched, then we might have hit the race mentioned
106 * above. So we just store the info for the parent to deal with */
107 if (exit == NULL) {
108 TRACE(("using lastexit"));
109 exit = &svr_ses.lastexit;
112 exit->exitpid = pid;
113 if (WIFEXITED(status)) {
114 exit->exitstatus = WEXITSTATUS(status);
116 if (WIFSIGNALED(status)) {
117 exit->exitsignal = WTERMSIG(status);
118 #if !defined(AIX) && defined(WCOREDUMP)
119 exit->exitcore = WCOREDUMP(status);
120 #else
121 exit->exitcore = 0;
122 #endif
123 } else {
124 /* we use this to determine how pid exited */
125 exit->exitsignal = -1;
128 /* Make sure that the main select() loop wakes up */
129 while (1) {
130 /* isserver is just a random byte to write. We can't do anything
131 about an error so should just ignore it */
132 if (write(ses.signal_pipe[1], &ses.isserver, 1) == 1
133 || errno != EINTR) {
134 break;
139 sa_chld.sa_handler = sesssigchild_handler;
140 sa_chld.sa_flags = SA_NOCLDSTOP;
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 dropbear_assert(channel->typedata == NULL);
223 chansess = (struct ChanSess*)m_malloc(sizeof(struct ChanSess));
224 chansess->cmd = NULL;
225 chansess->connection_string = NULL;
226 chansess->pid = 0;
228 /* pty details */
229 chansess->master = -1;
230 chansess->slave = -1;
231 chansess->tty = NULL;
232 chansess->term = NULL;
234 chansess->exit.exitpid = -1;
236 channel->typedata = chansess;
238 #ifndef DISABLE_X11FWD
239 chansess->x11listener = NULL;
240 chansess->x11authprot = NULL;
241 chansess->x11authcookie = NULL;
242 #endif
244 #ifndef DISABLE_AGENTFWD
245 chansess->agentlistener = NULL;
246 chansess->agentfile = NULL;
247 chansess->agentdir = NULL;
248 #endif
250 return 0;
254 static struct logininfo*
255 chansess_login_alloc(struct ChanSess *chansess) {
256 struct logininfo * li;
257 li = login_alloc_entry(chansess->pid, ses.authstate.username,
258 svr_ses.remotehost, chansess->tty);
259 return li;
262 /* clean a session channel */
263 static void closechansess(struct Channel *channel) {
265 struct ChanSess *chansess;
266 unsigned int i;
267 struct logininfo *li;
269 TRACE(("enter closechansess"))
271 chansess = (struct ChanSess*)channel->typedata;
273 if (chansess == NULL) {
274 TRACE(("leave closechansess: chansess == NULL"))
275 return;
278 send_exitsignalstatus(channel);
280 m_free(chansess->cmd);
281 m_free(chansess->term);
283 if (chansess->tty) {
284 /* write the utmp/wtmp login record */
285 li = chansess_login_alloc(chansess);
286 login_logout(li);
287 login_free_entry(li);
289 pty_release(chansess->tty);
290 m_free(chansess->tty);
293 #ifndef DISABLE_X11FWD
294 x11cleanup(chansess);
295 #endif
297 #ifndef DISABLE_AGENTFWD
298 svr_agentcleanup(chansess);
299 #endif
301 /* clear child pid entries */
302 for (i = 0; i < svr_ses.childpidsize; i++) {
303 if (svr_ses.childpids[i].chansess == chansess) {
304 dropbear_assert(svr_ses.childpids[i].pid > 0);
305 TRACE(("closing pid %d", svr_ses.childpids[i].pid))
306 TRACE(("exitpid is %d", chansess->exit.exitpid))
307 svr_ses.childpids[i].pid = -1;
308 svr_ses.childpids[i].chansess = NULL;
312 m_free(chansess);
314 TRACE(("leave closechansess"))
317 /* Handle requests for a channel. These can be execution requests,
318 * or x11/authagent forwarding. These are passed to appropriate handlers */
319 static void chansessionrequest(struct Channel *channel) {
321 unsigned char * type = NULL;
322 unsigned int typelen;
323 unsigned char wantreply;
324 int ret = 1;
325 struct ChanSess *chansess;
327 TRACE(("enter chansessionrequest"))
329 type = buf_getstring(ses.payload, &typelen);
330 wantreply = buf_getbool(ses.payload);
332 if (typelen > MAX_NAME_LEN) {
333 TRACE(("leave chansessionrequest: type too long")) /* XXX send error?*/
334 goto out;
337 chansess = (struct ChanSess*)channel->typedata;
338 dropbear_assert(chansess != NULL);
339 TRACE(("type is %s", type))
341 if (strcmp(type, "window-change") == 0) {
342 ret = sessionwinchange(chansess);
343 } else if (strcmp(type, "shell") == 0) {
344 ret = sessioncommand(channel, chansess, 0, 0);
345 } else if (strcmp(type, "pty-req") == 0) {
346 ret = sessionpty(chansess);
347 } else if (strcmp(type, "exec") == 0) {
348 ret = sessioncommand(channel, chansess, 1, 0);
349 } else if (strcmp(type, "subsystem") == 0) {
350 ret = sessioncommand(channel, chansess, 1, 1);
351 #ifndef DISABLE_X11FWD
352 } else if (strcmp(type, "x11-req") == 0) {
353 ret = x11req(chansess);
354 #endif
355 #ifndef DISABLE_AGENTFWD
356 } else if (strcmp(type, "auth-agent-req@openssh.com") == 0) {
357 ret = svr_agentreq(chansess);
358 #endif
359 } else if (strcmp(type, "signal") == 0) {
360 ret = sessionsignal(chansess);
361 } else {
362 /* etc, todo "env", "subsystem" */
365 out:
367 if (wantreply) {
368 if (ret == DROPBEAR_SUCCESS) {
369 send_msg_channel_success(channel);
370 } else {
371 send_msg_channel_failure(channel);
375 m_free(type);
376 TRACE(("leave chansessionrequest"))
380 /* Send a signal to a session's process as requested by the client*/
381 static int sessionsignal(struct ChanSess *chansess) {
383 int sig = 0;
384 unsigned char* signame = NULL;
385 int i;
387 if (chansess->pid == 0) {
388 /* haven't got a process pid yet */
389 return DROPBEAR_FAILURE;
392 signame = buf_getstring(ses.payload, NULL);
394 i = 0;
395 while (signames[i].name != 0) {
396 if (strcmp(signames[i].name, signame) == 0) {
397 sig = signames[i].signal;
398 break;
400 i++;
403 m_free(signame);
405 if (sig == 0) {
406 /* failed */
407 return DROPBEAR_FAILURE;
410 if (kill(chansess->pid, sig) < 0) {
411 return DROPBEAR_FAILURE;
414 return DROPBEAR_SUCCESS;
417 /* Let the process know that the window size has changed, as notified from the
418 * client. Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
419 static int sessionwinchange(struct ChanSess *chansess) {
421 int termc, termr, termw, termh;
423 if (chansess->master < 0) {
424 /* haven't got a pty yet */
425 return DROPBEAR_FAILURE;
428 termc = buf_getint(ses.payload);
429 termr = buf_getint(ses.payload);
430 termw = buf_getint(ses.payload);
431 termh = buf_getint(ses.payload);
433 pty_change_window_size(chansess->master, termr, termc, termw, termh);
435 return DROPBEAR_SUCCESS;
438 static void get_termmodes(struct ChanSess *chansess) {
440 struct termios termio;
441 unsigned char opcode;
442 unsigned int value;
443 const struct TermCode * termcode;
444 unsigned int len;
446 TRACE(("enter get_termmodes"))
448 /* Term modes */
449 /* We'll ignore errors and continue if we can't set modes.
450 * We're ignoring baud rates since they seem evil */
451 if (tcgetattr(chansess->master, &termio) == -1) {
452 return;
455 len = buf_getint(ses.payload);
456 TRACE(("term mode str %d p->l %d p->p %d",
457 len, ses.payload->len , ses.payload->pos));
458 if (len != ses.payload->len - ses.payload->pos) {
459 dropbear_exit("bad term mode string");
462 if (len == 0) {
463 TRACE(("leave get_termmodes: empty terminal modes string"))
464 return;
467 while (((opcode = buf_getbyte(ses.payload)) != 0x00) && opcode <= 159) {
469 /* must be before checking type, so that value is consumed even if
470 * we don't use it */
471 value = buf_getint(ses.payload);
473 /* handle types of code */
474 if (opcode > MAX_TERMCODE) {
475 continue;
477 termcode = &termcodes[(unsigned int)opcode];
480 switch (termcode->type) {
482 case TERMCODE_NONE:
483 break;
485 case TERMCODE_CONTROLCHAR:
486 termio.c_cc[termcode->mapcode] = value;
487 break;
489 case TERMCODE_INPUT:
490 if (value) {
491 termio.c_iflag |= termcode->mapcode;
492 } else {
493 termio.c_iflag &= ~(termcode->mapcode);
495 break;
497 case TERMCODE_OUTPUT:
498 if (value) {
499 termio.c_oflag |= termcode->mapcode;
500 } else {
501 termio.c_oflag &= ~(termcode->mapcode);
503 break;
505 case TERMCODE_LOCAL:
506 if (value) {
507 termio.c_lflag |= termcode->mapcode;
508 } else {
509 termio.c_lflag &= ~(termcode->mapcode);
511 break;
513 case TERMCODE_CONTROL:
514 if (value) {
515 termio.c_cflag |= termcode->mapcode;
516 } else {
517 termio.c_cflag &= ~(termcode->mapcode);
519 break;
523 if (tcsetattr(chansess->master, TCSANOW, &termio) < 0) {
524 dropbear_log(LOG_INFO, "error setting terminal attributes");
526 TRACE(("leave get_termmodes"))
529 /* Set up a session pty which will be used to execute the shell or program.
530 * The pty is allocated now, and kept for when the shell/program executes.
531 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
532 static int sessionpty(struct ChanSess * chansess) {
534 unsigned int termlen;
535 unsigned char namebuf[65];
536 struct passwd * pw = NULL;
538 TRACE(("enter sessionpty"))
540 if (!svr_pubkey_allows_pty()) {
541 TRACE(("leave sessionpty : pty forbidden by public key option"))
542 return DROPBEAR_FAILURE;
545 chansess->term = buf_getstring(ses.payload, &termlen);
546 if (termlen > MAX_TERM_LEN) {
547 /* TODO send disconnect ? */
548 TRACE(("leave sessionpty: term len too long"))
549 return DROPBEAR_FAILURE;
552 /* allocate the pty */
553 if (chansess->master != -1) {
554 dropbear_exit("multiple pty requests");
556 if (pty_allocate(&chansess->master, &chansess->slave, namebuf, 64) == 0) {
557 TRACE(("leave sessionpty: failed to allocate pty"))
558 return DROPBEAR_FAILURE;
561 chansess->tty = (char*)m_strdup(namebuf);
562 if (!chansess->tty) {
563 dropbear_exit("out of memory"); /* TODO disconnect */
566 pw = getpwnam(ses.authstate.pw_name);
567 if (!pw)
568 dropbear_exit("getpwnam failed after succeeding previously");
569 pty_setowner(pw, chansess->tty);
571 /* Set up the rows/col counts */
572 sessionwinchange(chansess);
574 /* Read the terminal modes */
575 get_termmodes(chansess);
577 TRACE(("leave sessionpty"))
578 return DROPBEAR_SUCCESS;
581 static char* make_connection_string() {
582 char *local_ip, *local_port, *remote_ip, *remote_port;
583 size_t len;
584 char *ret;
585 get_socket_address(ses.sock_in, &local_ip, &local_port, &remote_ip, &remote_port, 0);
586 len = strlen(local_ip) + strlen(local_port) + strlen(remote_ip) + strlen(remote_port) + 4;
587 ret = m_malloc(len);
588 snprintf(ret, len, "%s %s %s %s", remote_ip, remote_port, local_ip, local_port);
589 m_free(local_ip);
590 m_free(local_port);
591 m_free(remote_ip);
592 m_free(remote_port);
593 return ret;
596 /* Handle a command request from the client. This is used for both shell
597 * and command-execution requests, and passes the command to
598 * noptycommand or ptycommand as appropriate.
599 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
600 static int sessioncommand(struct Channel *channel, struct ChanSess *chansess,
601 int iscmd, int issubsys) {
603 unsigned int cmdlen;
604 int ret;
606 TRACE(("enter sessioncommand"))
608 if (chansess->cmd != NULL) {
609 /* Note that only one command can _succeed_. The client might try
610 * one command (which fails), then try another. Ie fallback
611 * from sftp to scp */
612 return DROPBEAR_FAILURE;
615 if (iscmd) {
616 /* "exec" */
617 if (chansess->cmd == NULL) {
618 chansess->cmd = buf_getstring(ses.payload, &cmdlen);
620 if (cmdlen > MAX_CMD_LEN) {
621 m_free(chansess->cmd);
622 /* TODO - send error - too long ? */
623 return DROPBEAR_FAILURE;
626 if (issubsys) {
627 #ifdef SFTPSERVER_PATH
628 if ((cmdlen == 4) && strncmp(chansess->cmd, "sftp", 4) == 0) {
629 m_free(chansess->cmd);
630 chansess->cmd = m_strdup(SFTPSERVER_PATH);
631 } else
632 #endif
634 m_free(chansess->cmd);
635 return DROPBEAR_FAILURE;
640 /* take public key option 'command' into account */
641 svr_pubkey_set_forced_command(chansess);
643 #ifdef LOG_COMMANDS
644 if (chansess->cmd) {
645 dropbear_log(LOG_INFO, "user %s executing '%s'",
646 ses.authstate.pw_name, chansess->cmd);
647 } else {
648 dropbear_log(LOG_INFO, "user %s executing login shell",
649 ses.authstate.pw_name);
651 #endif
653 /* uClinux will vfork(), so there'll be a race as
654 connection_string is freed below. */
655 #ifndef __uClinux__
656 chansess->connection_string = make_connection_string();
657 #endif
659 if (chansess->term == NULL) {
660 /* no pty */
661 ret = noptycommand(channel, chansess);
662 } else {
663 /* want pty */
664 ret = ptycommand(channel, chansess);
667 #ifndef __uClinux__
668 m_free(chansess->connection_string);
669 #endif
671 if (ret == DROPBEAR_FAILURE) {
672 m_free(chansess->cmd);
674 return ret;
677 /* Execute a command and set up redirection of stdin/stdout/stderr without a
678 * pty.
679 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
680 static int noptycommand(struct Channel *channel, struct ChanSess *chansess) {
681 int ret;
683 TRACE(("enter noptycommand"))
684 ret = spawn_command(execchild, chansess,
685 &channel->writefd, &channel->readfd, &channel->errfd,
686 &chansess->pid);
688 if (ret == DROPBEAR_FAILURE) {
689 return ret;
692 ses.maxfd = MAX(ses.maxfd, channel->writefd);
693 ses.maxfd = MAX(ses.maxfd, channel->readfd);
694 ses.maxfd = MAX(ses.maxfd, channel->errfd);
696 addchildpid(chansess, chansess->pid);
698 if (svr_ses.lastexit.exitpid != -1) {
699 unsigned int i;
700 TRACE(("parent side: lastexitpid is %d", svr_ses.lastexit.exitpid))
701 /* The child probably exited and the signal handler triggered
702 * possibly before we got around to adding the childpid. So we fill
703 * out its data manually */
704 for (i = 0; i < svr_ses.childpidsize; i++) {
705 if (svr_ses.childpids[i].pid == svr_ses.lastexit.exitpid) {
706 TRACE(("found match for lastexitpid"))
707 svr_ses.childpids[i].chansess->exit = svr_ses.lastexit;
708 svr_ses.lastexit.exitpid = -1;
713 TRACE(("leave noptycommand"))
714 return DROPBEAR_SUCCESS;
717 /* Execute a command or shell within a pty environment, and set up
718 * redirection as appropriate.
719 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
720 static int ptycommand(struct Channel *channel, struct ChanSess *chansess) {
722 pid_t pid;
723 struct logininfo *li = NULL;
724 #ifdef DO_MOTD
725 buffer * motdbuf = NULL;
726 int len;
727 struct stat sb;
728 char *hushpath = NULL;
729 #endif
731 TRACE(("enter ptycommand"))
733 /* we need to have a pty allocated */
734 if (chansess->master == -1 || chansess->tty == NULL) {
735 dropbear_log(LOG_WARNING, "no pty was allocated, couldn't execute");
736 return DROPBEAR_FAILURE;
739 #ifdef __uClinux__
740 pid = vfork();
741 #else
742 pid = fork();
743 #endif
744 if (pid < 0)
745 return DROPBEAR_FAILURE;
747 if (pid == 0) {
748 /* child */
750 TRACE(("back to normal sigchld"))
751 /* Revert to normal sigchld handling */
752 if (signal(SIGCHLD, SIG_DFL) == SIG_ERR) {
753 dropbear_exit("signal() error");
756 /* redirect stdin/stdout/stderr */
757 close(chansess->master);
759 pty_make_controlling_tty(&chansess->slave, chansess->tty);
761 if ((dup2(chansess->slave, STDIN_FILENO) < 0) ||
762 (dup2(chansess->slave, STDERR_FILENO) < 0) ||
763 (dup2(chansess->slave, STDOUT_FILENO) < 0)) {
764 TRACE(("leave ptycommand: error redirecting filedesc"))
765 return DROPBEAR_FAILURE;
768 close(chansess->slave);
770 /* write the utmp/wtmp login record - must be after changing the
771 * terminal used for stdout with the dup2 above */
772 li = chansess_login_alloc(chansess);
773 login_login(li);
774 login_free_entry(li);
776 #ifdef DO_MOTD
777 if (svr_opts.domotd) {
778 /* don't show the motd if ~/.hushlogin exists */
780 /* 12 == strlen("/.hushlogin\0") */
781 len = strlen(ses.authstate.pw_dir) + 12;
783 hushpath = m_malloc(len);
784 snprintf(hushpath, len, "%s/.hushlogin", ses.authstate.pw_dir);
786 if (stat(hushpath, &sb) < 0) {
787 /* more than a screenful is stupid IMHO */
788 motdbuf = buf_new(80 * 25);
789 if (buf_readfile(motdbuf, MOTD_FILENAME) == DROPBEAR_SUCCESS) {
790 buf_setpos(motdbuf, 0);
791 while (motdbuf->pos != motdbuf->len) {
792 len = motdbuf->len - motdbuf->pos;
793 len = write(STDOUT_FILENO,
794 buf_getptr(motdbuf, len), len);
795 buf_incrpos(motdbuf, len);
798 buf_free(motdbuf);
800 m_free(hushpath);
802 #endif /* DO_MOTD */
804 execchild(chansess);
805 /* not reached */
807 } else {
808 /* parent */
809 TRACE(("continue ptycommand: parent"))
810 chansess->pid = pid;
812 /* add a child pid */
813 addchildpid(chansess, pid);
815 close(chansess->slave);
816 channel->writefd = chansess->master;
817 channel->readfd = chansess->master;
818 /* don't need to set stderr here */
819 ses.maxfd = MAX(ses.maxfd, chansess->master);
821 setnonblocking(chansess->master);
825 TRACE(("leave ptycommand"))
826 return DROPBEAR_SUCCESS;
829 /* Add the pid of a child to the list for exit-handling */
830 static void addchildpid(struct ChanSess *chansess, pid_t pid) {
832 unsigned int i;
833 for (i = 0; i < svr_ses.childpidsize; i++) {
834 if (svr_ses.childpids[i].pid == -1) {
835 break;
839 /* need to increase size */
840 if (i == svr_ses.childpidsize) {
841 svr_ses.childpids = (struct ChildPid*)m_realloc(svr_ses.childpids,
842 sizeof(struct ChildPid) * (svr_ses.childpidsize+1));
843 svr_ses.childpidsize++;
846 svr_ses.childpids[i].pid = pid;
847 svr_ses.childpids[i].chansess = chansess;
851 /* Clean up, drop to user privileges, set up the environment and execute
852 * the command/shell. This function does not return. */
853 static void execchild(void *user_data) {
854 struct ChanSess *chansess = user_data;
855 char *usershell = NULL;
857 /* with uClinux we'll have vfork()ed, so don't want to overwrite the
858 * hostkey. can't think of a workaround to clear it */
859 #ifndef __uClinux__
860 /* wipe the hostkey */
861 sign_key_free(svr_opts.hostkey);
862 svr_opts.hostkey = NULL;
864 /* overwrite the prng state */
865 reseedrandom();
866 #endif
868 /* clear environment */
869 /* if we're debugging using valgrind etc, we need to keep the LD_PRELOAD
870 * etc. This is hazardous, so should only be used for debugging. */
871 #ifndef DEBUG_VALGRIND
872 #ifdef HAVE_CLEARENV
873 clearenv();
874 #else /* don't HAVE_CLEARENV */
875 /* Yay for posix. */
876 if (environ) {
877 environ[0] = NULL;
879 #endif /* HAVE_CLEARENV */
880 #endif /* DEBUG_VALGRIND */
882 /* We can only change uid/gid as root ... */
883 if (getuid() == 0) {
885 if ((setgid(ses.authstate.pw_gid) < 0) ||
886 (initgroups(ses.authstate.pw_name,
887 ses.authstate.pw_gid) < 0)) {
888 dropbear_exit("error changing user group");
890 if (setuid(ses.authstate.pw_uid) < 0) {
891 dropbear_exit("error changing user");
893 } else {
894 /* ... but if the daemon is the same uid as the requested uid, we don't
895 * need to */
897 /* XXX - there is a minor issue here, in that if there are multiple
898 * usernames with the same uid, but differing groups, then the
899 * differing groups won't be set (as with initgroups()). The solution
900 * is for the sysadmin not to give out the UID twice */
901 if (getuid() != ses.authstate.pw_uid) {
902 dropbear_exit("couldn't change user as non-root");
906 /* set env vars */
907 addnewvar("USER", ses.authstate.pw_name);
908 addnewvar("LOGNAME", ses.authstate.pw_name);
909 addnewvar("HOME", ses.authstate.pw_dir);
910 addnewvar("SHELL", get_user_shell());
911 addnewvar("PATH", DEFAULT_PATH);
912 if (chansess->term != NULL) {
913 addnewvar("TERM", chansess->term);
916 if (chansess->tty) {
917 addnewvar("SSH_TTY", chansess->tty);
920 if (chansess->connection_string) {
921 addnewvar("SSH_CONNECTION", chansess->connection_string);
924 #ifdef ENABLE_SVR_PUBKEY_OPTIONS
925 if (ses.authstate.pubkey_options &&
926 ses.authstate.pubkey_options->original_command) {
927 addnewvar("SSH_ORIGINAL_COMMAND",
928 ses.authstate.pubkey_options->original_command);
930 #endif
932 /* change directory */
933 if (chdir(ses.authstate.pw_dir) < 0) {
934 dropbear_exit("error changing directory");
937 #ifndef DISABLE_X11FWD
938 /* set up X11 forwarding if enabled */
939 x11setauth(chansess);
940 #endif
941 #ifndef DISABLE_AGENTFWD
942 /* set up agent env variable */
943 svr_agentset(chansess);
944 #endif
946 usershell = m_strdup(get_user_shell());
947 run_shell_command(chansess->cmd, ses.maxfd, usershell);
949 /* only reached on error */
950 dropbear_exit("child failed");
953 const struct ChanType svrchansess = {
954 0, /* sepfds */
955 "session", /* name */
956 newchansess, /* inithandler */
957 sesscheckclose, /* checkclosehandler */
958 chansessionrequest, /* reqhandler */
959 closechansess, /* closehandler */
963 /* Set up the general chansession environment, in particular child-exit
964 * handling */
965 void svr_chansessinitialise() {
967 struct sigaction sa_chld;
969 /* single child process intially */
970 svr_ses.childpids = (struct ChildPid*)m_malloc(sizeof(struct ChildPid));
971 svr_ses.childpids[0].pid = -1; /* unused */
972 svr_ses.childpids[0].chansess = NULL;
973 svr_ses.childpidsize = 1;
974 svr_ses.lastexit.exitpid = -1; /* Nothing has exited yet */
975 sa_chld.sa_handler = sesssigchild_handler;
976 sa_chld.sa_flags = SA_NOCLDSTOP;
977 if (sigaction(SIGCHLD, &sa_chld, NULL) < 0) {
978 dropbear_exit("signal() error");
983 /* add a new environment variable, allocating space for the entry */
984 void addnewvar(const char* param, const char* var) {
986 char* newvar = NULL;
987 int plen, vlen;
989 plen = strlen(param);
990 vlen = strlen(var);
992 newvar = m_malloc(plen + vlen + 2); /* 2 is for '=' and '\0' */
993 memcpy(newvar, param, plen);
994 newvar[plen] = '=';
995 memcpy(&newvar[plen+1], var, vlen);
996 newvar[plen+vlen+1] = '\0';
997 /* newvar is leaked here, but that's part of putenv()'s semantics */
998 if (putenv(newvar) < 0) {
999 dropbear_exit("environ error");