remove unused variable
[dropbear.git] / svr-chansession.c
blob24cdc686aec803a2a1f08f768d74c6e4f027f683
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 sigaction(SIGCHLD, &sa_chld, NULL);
141 TRACE(("leave sigchld handler"))
144 /* send the exit status or the signal causing termination for a session */
145 static void send_exitsignalstatus(struct Channel *channel) {
147 struct ChanSess *chansess = (struct ChanSess*)channel->typedata;
149 if (chansess->exit.exitpid >= 0) {
150 if (chansess->exit.exitsignal > 0) {
151 send_msg_chansess_exitsignal(channel, chansess);
152 } else {
153 send_msg_chansess_exitstatus(channel, chansess);
158 /* send the exitstatus to the client */
159 static void send_msg_chansess_exitstatus(struct Channel * channel,
160 struct ChanSess * chansess) {
162 dropbear_assert(chansess->exit.exitpid != -1);
163 dropbear_assert(chansess->exit.exitsignal == -1);
165 CHECKCLEARTOWRITE();
167 buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_REQUEST);
168 buf_putint(ses.writepayload, channel->remotechan);
169 buf_putstring(ses.writepayload, "exit-status", 11);
170 buf_putbyte(ses.writepayload, 0); /* boolean FALSE */
171 buf_putint(ses.writepayload, chansess->exit.exitstatus);
173 encrypt_packet();
177 /* send the signal causing the exit to the client */
178 static void send_msg_chansess_exitsignal(struct Channel * channel,
179 struct ChanSess * chansess) {
181 int i;
182 char* signame = NULL;
183 dropbear_assert(chansess->exit.exitpid != -1);
184 dropbear_assert(chansess->exit.exitsignal > 0);
186 TRACE(("send_msg_chansess_exitsignal %d", chansess->exit.exitsignal))
188 CHECKCLEARTOWRITE();
190 /* we check that we can match a signal name, otherwise
191 * don't send anything */
192 for (i = 0; signames[i].name != NULL; i++) {
193 if (signames[i].signal == chansess->exit.exitsignal) {
194 signame = signames[i].name;
195 break;
199 if (signame == NULL) {
200 return;
203 buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_REQUEST);
204 buf_putint(ses.writepayload, channel->remotechan);
205 buf_putstring(ses.writepayload, "exit-signal", 11);
206 buf_putbyte(ses.writepayload, 0); /* boolean FALSE */
207 buf_putstring(ses.writepayload, signame, strlen(signame));
208 buf_putbyte(ses.writepayload, chansess->exit.exitcore);
209 buf_putstring(ses.writepayload, "", 0); /* error msg */
210 buf_putstring(ses.writepayload, "", 0); /* lang */
212 encrypt_packet();
215 /* set up a session channel */
216 static int newchansess(struct Channel *channel) {
218 struct ChanSess *chansess;
220 dropbear_assert(channel->typedata == NULL);
222 chansess = (struct ChanSess*)m_malloc(sizeof(struct ChanSess));
223 chansess->cmd = NULL;
224 chansess->connection_string = NULL;
225 chansess->pid = 0;
227 /* pty details */
228 chansess->master = -1;
229 chansess->slave = -1;
230 chansess->tty = NULL;
231 chansess->term = NULL;
233 chansess->exit.exitpid = -1;
235 channel->typedata = chansess;
237 #ifndef DISABLE_X11FWD
238 chansess->x11listener = NULL;
239 chansess->x11authprot = NULL;
240 chansess->x11authcookie = NULL;
241 #endif
243 #ifndef DISABLE_AGENTFWD
244 chansess->agentlistener = NULL;
245 chansess->agentfile = NULL;
246 chansess->agentdir = NULL;
247 #endif
249 return 0;
253 static struct logininfo*
254 chansess_login_alloc(struct ChanSess *chansess) {
255 struct logininfo * li;
256 li = login_alloc_entry(chansess->pid, ses.authstate.username,
257 svr_ses.remotehost, chansess->tty);
258 return li;
261 /* clean a session channel */
262 static void closechansess(struct Channel *channel) {
264 struct ChanSess *chansess;
265 unsigned int i;
266 struct logininfo *li;
268 TRACE(("enter closechansess"))
270 chansess = (struct ChanSess*)channel->typedata;
272 if (chansess == NULL) {
273 TRACE(("leave closechansess: chansess == NULL"))
274 return;
277 send_exitsignalstatus(channel);
279 m_free(chansess->cmd);
280 m_free(chansess->term);
282 if (chansess->tty) {
283 /* write the utmp/wtmp login record */
284 li = chansess_login_alloc(chansess);
285 login_logout(li);
286 login_free_entry(li);
288 pty_release(chansess->tty);
289 m_free(chansess->tty);
292 #ifndef DISABLE_X11FWD
293 x11cleanup(chansess);
294 #endif
296 #ifndef DISABLE_AGENTFWD
297 svr_agentcleanup(chansess);
298 #endif
300 /* clear child pid entries */
301 for (i = 0; i < svr_ses.childpidsize; i++) {
302 if (svr_ses.childpids[i].chansess == chansess) {
303 dropbear_assert(svr_ses.childpids[i].pid > 0);
304 TRACE(("closing pid %d", svr_ses.childpids[i].pid))
305 TRACE(("exitpid is %d", chansess->exit.exitpid))
306 svr_ses.childpids[i].pid = -1;
307 svr_ses.childpids[i].chansess = NULL;
311 m_free(chansess);
313 TRACE(("leave closechansess"))
316 /* Handle requests for a channel. These can be execution requests,
317 * or x11/authagent forwarding. These are passed to appropriate handlers */
318 static void chansessionrequest(struct Channel *channel) {
320 unsigned char * type = NULL;
321 unsigned int typelen;
322 unsigned char wantreply;
323 int ret = 1;
324 struct ChanSess *chansess;
326 TRACE(("enter chansessionrequest"))
328 type = buf_getstring(ses.payload, &typelen);
329 wantreply = buf_getbool(ses.payload);
331 if (typelen > MAX_NAME_LEN) {
332 TRACE(("leave chansessionrequest: type too long")) /* XXX send error?*/
333 goto out;
336 chansess = (struct ChanSess*)channel->typedata;
337 dropbear_assert(chansess != NULL);
338 TRACE(("type is %s", type))
340 if (strcmp(type, "window-change") == 0) {
341 ret = sessionwinchange(chansess);
342 } else if (strcmp(type, "shell") == 0) {
343 ret = sessioncommand(channel, chansess, 0, 0);
344 } else if (strcmp(type, "pty-req") == 0) {
345 ret = sessionpty(chansess);
346 } else if (strcmp(type, "exec") == 0) {
347 ret = sessioncommand(channel, chansess, 1, 0);
348 } else if (strcmp(type, "subsystem") == 0) {
349 ret = sessioncommand(channel, chansess, 1, 1);
350 #ifndef DISABLE_X11FWD
351 } else if (strcmp(type, "x11-req") == 0) {
352 ret = x11req(chansess);
353 #endif
354 #ifndef DISABLE_AGENTFWD
355 } else if (strcmp(type, "auth-agent-req@openssh.com") == 0) {
356 ret = svr_agentreq(chansess);
357 #endif
358 } else if (strcmp(type, "signal") == 0) {
359 ret = sessionsignal(chansess);
360 } else {
361 /* etc, todo "env", "subsystem" */
364 out:
366 if (wantreply) {
367 if (ret == DROPBEAR_SUCCESS) {
368 send_msg_channel_success(channel);
369 } else {
370 send_msg_channel_failure(channel);
374 m_free(type);
375 TRACE(("leave chansessionrequest"))
379 /* Send a signal to a session's process as requested by the client*/
380 static int sessionsignal(struct ChanSess *chansess) {
382 int sig = 0;
383 unsigned char* signame = NULL;
384 int i;
386 if (chansess->pid == 0) {
387 /* haven't got a process pid yet */
388 return DROPBEAR_FAILURE;
391 signame = buf_getstring(ses.payload, NULL);
393 i = 0;
394 while (signames[i].name != 0) {
395 if (strcmp(signames[i].name, signame) == 0) {
396 sig = signames[i].signal;
397 break;
399 i++;
402 m_free(signame);
404 if (sig == 0) {
405 /* failed */
406 return DROPBEAR_FAILURE;
409 if (kill(chansess->pid, sig) < 0) {
410 return DROPBEAR_FAILURE;
413 return DROPBEAR_SUCCESS;
416 /* Let the process know that the window size has changed, as notified from the
417 * client. Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
418 static int sessionwinchange(struct ChanSess *chansess) {
420 int termc, termr, termw, termh;
422 if (chansess->master < 0) {
423 /* haven't got a pty yet */
424 return DROPBEAR_FAILURE;
427 termc = buf_getint(ses.payload);
428 termr = buf_getint(ses.payload);
429 termw = buf_getint(ses.payload);
430 termh = buf_getint(ses.payload);
432 pty_change_window_size(chansess->master, termr, termc, termw, termh);
434 return DROPBEAR_SUCCESS;
437 static void get_termmodes(struct ChanSess *chansess) {
439 struct termios termio;
440 unsigned char opcode;
441 unsigned int value;
442 const struct TermCode * termcode;
443 unsigned int len;
445 TRACE(("enter get_termmodes"))
447 /* Term modes */
448 /* We'll ignore errors and continue if we can't set modes.
449 * We're ignoring baud rates since they seem evil */
450 if (tcgetattr(chansess->master, &termio) == -1) {
451 return;
454 len = buf_getint(ses.payload);
455 TRACE(("term mode str %d p->l %d p->p %d",
456 len, ses.payload->len , ses.payload->pos));
457 if (len != ses.payload->len - ses.payload->pos) {
458 dropbear_exit("bad term mode string");
461 if (len == 0) {
462 TRACE(("leave get_termmodes: empty terminal modes string"))
463 return;
466 while (((opcode = buf_getbyte(ses.payload)) != 0x00) && opcode <= 159) {
468 /* must be before checking type, so that value is consumed even if
469 * we don't use it */
470 value = buf_getint(ses.payload);
472 /* handle types of code */
473 if (opcode > MAX_TERMCODE) {
474 continue;
476 termcode = &termcodes[(unsigned int)opcode];
479 switch (termcode->type) {
481 case TERMCODE_NONE:
482 break;
484 case TERMCODE_CONTROLCHAR:
485 termio.c_cc[termcode->mapcode] = value;
486 break;
488 case TERMCODE_INPUT:
489 if (value) {
490 termio.c_iflag |= termcode->mapcode;
491 } else {
492 termio.c_iflag &= ~(termcode->mapcode);
494 break;
496 case TERMCODE_OUTPUT:
497 if (value) {
498 termio.c_oflag |= termcode->mapcode;
499 } else {
500 termio.c_oflag &= ~(termcode->mapcode);
502 break;
504 case TERMCODE_LOCAL:
505 if (value) {
506 termio.c_lflag |= termcode->mapcode;
507 } else {
508 termio.c_lflag &= ~(termcode->mapcode);
510 break;
512 case TERMCODE_CONTROL:
513 if (value) {
514 termio.c_cflag |= termcode->mapcode;
515 } else {
516 termio.c_cflag &= ~(termcode->mapcode);
518 break;
522 if (tcsetattr(chansess->master, TCSANOW, &termio) < 0) {
523 dropbear_log(LOG_INFO, "error setting terminal attributes");
525 TRACE(("leave get_termmodes"))
528 /* Set up a session pty which will be used to execute the shell or program.
529 * The pty is allocated now, and kept for when the shell/program executes.
530 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
531 static int sessionpty(struct ChanSess * chansess) {
533 unsigned int termlen;
534 unsigned char namebuf[65];
535 struct passwd * pw = NULL;
537 TRACE(("enter sessionpty"))
539 if (!svr_pubkey_allows_pty()) {
540 TRACE(("leave sessionpty : pty forbidden by public key option"))
541 return DROPBEAR_FAILURE;
544 chansess->term = buf_getstring(ses.payload, &termlen);
545 if (termlen > MAX_TERM_LEN) {
546 /* TODO send disconnect ? */
547 TRACE(("leave sessionpty: term len too long"))
548 return DROPBEAR_FAILURE;
551 /* allocate the pty */
552 if (chansess->master != -1) {
553 dropbear_exit("multiple pty requests");
555 if (pty_allocate(&chansess->master, &chansess->slave, namebuf, 64) == 0) {
556 TRACE(("leave sessionpty: failed to allocate pty"))
557 return DROPBEAR_FAILURE;
560 chansess->tty = (char*)m_strdup(namebuf);
561 if (!chansess->tty) {
562 dropbear_exit("out of memory"); /* TODO disconnect */
565 pw = getpwnam(ses.authstate.pw_name);
566 if (!pw)
567 dropbear_exit("getpwnam failed after succeeding previously");
568 pty_setowner(pw, chansess->tty);
570 /* Set up the rows/col counts */
571 sessionwinchange(chansess);
573 /* Read the terminal modes */
574 get_termmodes(chansess);
576 TRACE(("leave sessionpty"))
577 return DROPBEAR_SUCCESS;
580 static char* make_connection_string() {
581 char *local_ip, *local_port, *remote_ip, *remote_port;
582 size_t len;
583 char *ret;
584 get_socket_address(ses.sock_in, &local_ip, &local_port, &remote_ip, &remote_port, 0);
585 len = strlen(local_ip) + strlen(local_port) + strlen(remote_ip) + strlen(remote_port) + 4;
586 ret = m_malloc(len);
587 snprintf(ret, len, "%s %s %s %s", remote_ip, remote_port, local_ip, local_port);
588 m_free(local_ip);
589 m_free(local_port);
590 m_free(remote_ip);
591 m_free(remote_port);
592 return ret;
595 /* Handle a command request from the client. This is used for both shell
596 * and command-execution requests, and passes the command to
597 * noptycommand or ptycommand as appropriate.
598 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
599 static int sessioncommand(struct Channel *channel, struct ChanSess *chansess,
600 int iscmd, int issubsys) {
602 unsigned int cmdlen;
603 int ret;
605 TRACE(("enter sessioncommand"))
607 if (chansess->cmd != NULL) {
608 /* Note that only one command can _succeed_. The client might try
609 * one command (which fails), then try another. Ie fallback
610 * from sftp to scp */
611 return DROPBEAR_FAILURE;
614 if (iscmd) {
615 /* "exec" */
616 if (chansess->cmd == NULL) {
617 chansess->cmd = buf_getstring(ses.payload, &cmdlen);
619 if (cmdlen > MAX_CMD_LEN) {
620 m_free(chansess->cmd);
621 /* TODO - send error - too long ? */
622 return DROPBEAR_FAILURE;
625 if (issubsys) {
626 #ifdef SFTPSERVER_PATH
627 if ((cmdlen == 4) && strncmp(chansess->cmd, "sftp", 4) == 0) {
628 m_free(chansess->cmd);
629 chansess->cmd = m_strdup(SFTPSERVER_PATH);
630 } else
631 #endif
633 m_free(chansess->cmd);
634 return DROPBEAR_FAILURE;
639 /* take public key option 'command' into account */
640 svr_pubkey_set_forced_command(chansess);
642 #ifdef LOG_COMMANDS
643 if (chansess->cmd) {
644 dropbear_log(LOG_INFO, "user %s executing '%s'",
645 ses.authstate.pw_name, chansess->cmd);
646 } else {
647 dropbear_log(LOG_INFO, "user %s executing login shell",
648 ses.authstate.pw_name);
650 #endif
652 /* uClinux will vfork(), so there'll be a race as
653 connection_string is freed below. */
654 #ifndef __uClinux__
655 chansess->connection_string = make_connection_string();
656 #endif
658 if (chansess->term == NULL) {
659 /* no pty */
660 ret = noptycommand(channel, chansess);
661 } else {
662 /* want pty */
663 ret = ptycommand(channel, chansess);
666 #ifndef __uClinux__
667 m_free(chansess->connection_string);
668 #endif
670 if (ret == DROPBEAR_FAILURE) {
671 m_free(chansess->cmd);
673 return ret;
676 /* Execute a command and set up redirection of stdin/stdout/stderr without a
677 * pty.
678 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
679 static int noptycommand(struct Channel *channel, struct ChanSess *chansess) {
680 int ret;
682 TRACE(("enter noptycommand"))
683 ret = spawn_command(execchild, chansess,
684 &channel->writefd, &channel->readfd, &channel->errfd,
685 &chansess->pid);
687 if (ret == DROPBEAR_FAILURE) {
688 return ret;
691 ses.maxfd = MAX(ses.maxfd, channel->writefd);
692 ses.maxfd = MAX(ses.maxfd, channel->readfd);
693 ses.maxfd = MAX(ses.maxfd, channel->errfd);
695 addchildpid(chansess, chansess->pid);
697 if (svr_ses.lastexit.exitpid != -1) {
698 unsigned int i;
699 TRACE(("parent side: lastexitpid is %d", svr_ses.lastexit.exitpid))
700 /* The child probably exited and the signal handler triggered
701 * possibly before we got around to adding the childpid. So we fill
702 * out its data manually */
703 for (i = 0; i < svr_ses.childpidsize; i++) {
704 if (svr_ses.childpids[i].pid == svr_ses.lastexit.exitpid) {
705 TRACE(("found match for lastexitpid"))
706 svr_ses.childpids[i].chansess->exit = svr_ses.lastexit;
707 svr_ses.lastexit.exitpid = -1;
712 TRACE(("leave noptycommand"))
713 return DROPBEAR_SUCCESS;
716 /* Execute a command or shell within a pty environment, and set up
717 * redirection as appropriate.
718 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
719 static int ptycommand(struct Channel *channel, struct ChanSess *chansess) {
721 pid_t pid;
722 struct logininfo *li = NULL;
723 #ifdef DO_MOTD
724 buffer * motdbuf = NULL;
725 int len;
726 struct stat sb;
727 char *hushpath = NULL;
728 #endif
730 TRACE(("enter ptycommand"))
732 /* we need to have a pty allocated */
733 if (chansess->master == -1 || chansess->tty == NULL) {
734 dropbear_log(LOG_WARNING, "no pty was allocated, couldn't execute");
735 return DROPBEAR_FAILURE;
738 #ifdef __uClinux__
739 pid = vfork();
740 #else
741 pid = fork();
742 #endif
743 if (pid < 0)
744 return DROPBEAR_FAILURE;
746 if (pid == 0) {
747 /* child */
749 TRACE(("back to normal sigchld"))
750 /* Revert to normal sigchld handling */
751 if (signal(SIGCHLD, SIG_DFL) == SIG_ERR) {
752 dropbear_exit("signal() error");
755 /* redirect stdin/stdout/stderr */
756 close(chansess->master);
758 pty_make_controlling_tty(&chansess->slave, chansess->tty);
760 if ((dup2(chansess->slave, STDIN_FILENO) < 0) ||
761 (dup2(chansess->slave, STDERR_FILENO) < 0) ||
762 (dup2(chansess->slave, STDOUT_FILENO) < 0)) {
763 TRACE(("leave ptycommand: error redirecting filedesc"))
764 return DROPBEAR_FAILURE;
767 close(chansess->slave);
769 /* write the utmp/wtmp login record - must be after changing the
770 * terminal used for stdout with the dup2 above */
771 li = chansess_login_alloc(chansess);
772 login_login(li);
773 login_free_entry(li);
775 #ifdef DO_MOTD
776 if (svr_opts.domotd) {
777 /* don't show the motd if ~/.hushlogin exists */
779 /* 12 == strlen("/.hushlogin\0") */
780 len = strlen(ses.authstate.pw_dir) + 12;
782 hushpath = m_malloc(len);
783 snprintf(hushpath, len, "%s/.hushlogin", ses.authstate.pw_dir);
785 if (stat(hushpath, &sb) < 0) {
786 /* more than a screenful is stupid IMHO */
787 motdbuf = buf_new(80 * 25);
788 if (buf_readfile(motdbuf, MOTD_FILENAME) == DROPBEAR_SUCCESS) {
789 buf_setpos(motdbuf, 0);
790 while (motdbuf->pos != motdbuf->len) {
791 len = motdbuf->len - motdbuf->pos;
792 len = write(STDOUT_FILENO,
793 buf_getptr(motdbuf, len), len);
794 buf_incrpos(motdbuf, len);
797 buf_free(motdbuf);
799 m_free(hushpath);
801 #endif /* DO_MOTD */
803 execchild(chansess);
804 /* not reached */
806 } else {
807 /* parent */
808 TRACE(("continue ptycommand: parent"))
809 chansess->pid = pid;
811 /* add a child pid */
812 addchildpid(chansess, pid);
814 close(chansess->slave);
815 channel->writefd = chansess->master;
816 channel->readfd = chansess->master;
817 /* don't need to set stderr here */
818 ses.maxfd = MAX(ses.maxfd, chansess->master);
820 setnonblocking(chansess->master);
824 TRACE(("leave ptycommand"))
825 return DROPBEAR_SUCCESS;
828 /* Add the pid of a child to the list for exit-handling */
829 static void addchildpid(struct ChanSess *chansess, pid_t pid) {
831 unsigned int i;
832 for (i = 0; i < svr_ses.childpidsize; i++) {
833 if (svr_ses.childpids[i].pid == -1) {
834 break;
838 /* need to increase size */
839 if (i == svr_ses.childpidsize) {
840 svr_ses.childpids = (struct ChildPid*)m_realloc(svr_ses.childpids,
841 sizeof(struct ChildPid) * (svr_ses.childpidsize+1));
842 svr_ses.childpidsize++;
845 svr_ses.childpids[i].pid = pid;
846 svr_ses.childpids[i].chansess = chansess;
850 /* Clean up, drop to user privileges, set up the environment and execute
851 * the command/shell. This function does not return. */
852 static void execchild(void *user_data) {
853 struct ChanSess *chansess = user_data;
854 char *usershell = NULL;
856 /* with uClinux we'll have vfork()ed, so don't want to overwrite the
857 * hostkey. can't think of a workaround to clear it */
858 #ifndef __uClinux__
859 /* wipe the hostkey */
860 sign_key_free(svr_opts.hostkey);
861 svr_opts.hostkey = NULL;
863 /* overwrite the prng state */
864 reseedrandom();
865 #endif
867 /* clear environment */
868 /* if we're debugging using valgrind etc, we need to keep the LD_PRELOAD
869 * etc. This is hazardous, so should only be used for debugging. */
870 #ifndef DEBUG_VALGRIND
871 #ifdef HAVE_CLEARENV
872 clearenv();
873 #else /* don't HAVE_CLEARENV */
874 /* Yay for posix. */
875 if (environ) {
876 environ[0] = NULL;
878 #endif /* HAVE_CLEARENV */
879 #endif /* DEBUG_VALGRIND */
881 /* We can only change uid/gid as root ... */
882 if (getuid() == 0) {
884 if ((setgid(ses.authstate.pw_gid) < 0) ||
885 (initgroups(ses.authstate.pw_name,
886 ses.authstate.pw_gid) < 0)) {
887 dropbear_exit("error changing user group");
889 if (setuid(ses.authstate.pw_uid) < 0) {
890 dropbear_exit("error changing user");
892 } else {
893 /* ... but if the daemon is the same uid as the requested uid, we don't
894 * need to */
896 /* XXX - there is a minor issue here, in that if there are multiple
897 * usernames with the same uid, but differing groups, then the
898 * differing groups won't be set (as with initgroups()). The solution
899 * is for the sysadmin not to give out the UID twice */
900 if (getuid() != ses.authstate.pw_uid) {
901 dropbear_exit("couldn't change user as non-root");
905 /* set env vars */
906 addnewvar("USER", ses.authstate.pw_name);
907 addnewvar("LOGNAME", ses.authstate.pw_name);
908 addnewvar("HOME", ses.authstate.pw_dir);
909 addnewvar("SHELL", get_user_shell());
910 addnewvar("PATH", DEFAULT_PATH);
911 if (chansess->term != NULL) {
912 addnewvar("TERM", chansess->term);
915 if (chansess->tty) {
916 addnewvar("SSH_TTY", chansess->tty);
919 if (chansess->connection_string) {
920 addnewvar("SSH_CONNECTION", chansess->connection_string);
923 #ifdef ENABLE_SVR_PUBKEY_OPTIONS
924 if (ses.authstate.pubkey_options &&
925 ses.authstate.pubkey_options->original_command) {
926 addnewvar("SSH_ORIGINAL_COMMAND",
927 ses.authstate.pubkey_options->original_command);
929 #endif
931 /* change directory */
932 if (chdir(ses.authstate.pw_dir) < 0) {
933 dropbear_exit("error changing directory");
936 #ifndef DISABLE_X11FWD
937 /* set up X11 forwarding if enabled */
938 x11setauth(chansess);
939 #endif
940 #ifndef DISABLE_AGENTFWD
941 /* set up agent env variable */
942 svr_agentset(chansess);
943 #endif
945 usershell = m_strdup(get_user_shell());
946 run_shell_command(chansess->cmd, ses.maxfd, usershell);
948 /* only reached on error */
949 dropbear_exit("child failed");
952 const struct ChanType svrchansess = {
953 0, /* sepfds */
954 "session", /* name */
955 newchansess, /* inithandler */
956 sesscheckclose, /* checkclosehandler */
957 chansessionrequest, /* reqhandler */
958 closechansess, /* closehandler */
962 /* Set up the general chansession environment, in particular child-exit
963 * handling */
964 void svr_chansessinitialise() {
966 struct sigaction sa_chld;
968 /* single child process intially */
969 svr_ses.childpids = (struct ChildPid*)m_malloc(sizeof(struct ChildPid));
970 svr_ses.childpids[0].pid = -1; /* unused */
971 svr_ses.childpids[0].chansess = NULL;
972 svr_ses.childpidsize = 1;
973 svr_ses.lastexit.exitpid = -1; /* Nothing has exited yet */
974 sa_chld.sa_handler = sesssigchild_handler;
975 sa_chld.sa_flags = SA_NOCLDSTOP;
976 if (sigaction(SIGCHLD, &sa_chld, NULL) < 0) {
977 dropbear_exit("signal() error");
982 /* add a new environment variable, allocating space for the entry */
983 void addnewvar(const char* param, const char* var) {
985 char* newvar = NULL;
986 int plen, vlen;
988 plen = strlen(param);
989 vlen = strlen(var);
991 newvar = m_malloc(plen + vlen + 2); /* 2 is for '=' and '\0' */
992 memcpy(newvar, param, plen);
993 newvar[plen] = '=';
994 memcpy(&newvar[plen+1], var, vlen);
995 newvar[plen+vlen+1] = '\0';
996 /* newvar is leaked here, but that's part of putenv()'s semantics */
997 if (putenv(newvar) < 0) {
998 dropbear_exit("environ error");