summaryrefslogtreecommitdiff
path: root/src/svr-chansession.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/svr-chansession.c')
-rw-r--r--src/svr-chansession.c1114
1 files changed, 1114 insertions, 0 deletions
diff --git a/src/svr-chansession.c b/src/svr-chansession.c
new file mode 100644
index 0000000..656a968
--- /dev/null
+++ b/src/svr-chansession.c
@@ -0,0 +1,1114 @@
+/*
+ * Dropbear - a SSH2 server
+ *
+ * Copyright (c) 2002,2003 Matt Johnston
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE. */
+
+#include "includes.h"
+#include "packet.h"
+#include "buffer.h"
+#include "session.h"
+#include "dbutil.h"
+#include "channel.h"
+#include "chansession.h"
+#include "sshpty.h"
+#include "termcodes.h"
+#include "ssh.h"
+#include "dbrandom.h"
+#include "x11fwd.h"
+#include "agentfwd.h"
+#include "runopts.h"
+#include "auth.h"
+
+/* Handles sessions (either shells or programs) requested by the client */
+
+static int sessioncommand(struct Channel *channel, struct ChanSess *chansess,
+ int iscmd, int issubsys);
+static int sessionpty(struct ChanSess * chansess);
+static int sessionsignal(const struct ChanSess *chansess);
+static int noptycommand(struct Channel *channel, struct ChanSess *chansess);
+static int ptycommand(struct Channel *channel, struct ChanSess *chansess);
+static int sessionwinchange(const struct ChanSess *chansess);
+static void execchild(const void *user_data_chansess);
+static void addchildpid(struct ChanSess *chansess, pid_t pid);
+static void sesssigchild_handler(int val);
+static void closechansess(const struct Channel *channel);
+static void cleanupchansess(const struct Channel *channel);
+static int newchansess(struct Channel *channel);
+static void chansessionrequest(struct Channel *channel);
+static int sesscheckclose(struct Channel *channel);
+
+static void send_exitsignalstatus(const struct Channel *channel);
+static void send_msg_chansess_exitstatus(const struct Channel * channel,
+ const struct ChanSess * chansess);
+static void send_msg_chansess_exitsignal(const struct Channel * channel,
+ const struct ChanSess * chansess);
+static void get_termmodes(const struct ChanSess *chansess);
+
+const struct ChanType svrchansess = {
+ "session", /* name */
+ newchansess, /* inithandler */
+ sesscheckclose, /* checkclosehandler */
+ chansessionrequest, /* reqhandler */
+ closechansess, /* closehandler */
+ cleanupchansess /* cleanup */
+};
+
+/* Returns whether the channel is ready to close. The child process
+ must not be running (has never started, or has exited) */
+static int sesscheckclose(struct Channel *channel) {
+ struct ChanSess *chansess = (struct ChanSess*)channel->typedata;
+ TRACE(("sesscheckclose, pid %d, exitpid %d", chansess->pid, chansess->exit.exitpid))
+
+ if (chansess->exit.exitpid != -1) {
+ channel->flushing = 1;
+ }
+ return chansess->pid == 0 || chansess->exit.exitpid != -1;
+}
+
+/* Handler for childs exiting, store the state for return to the client */
+
+/* There's a particular race we have to watch out for: if the forked child
+ * executes, exits, and this signal-handler is called, all before the parent
+ * gets to run, then the childpids[] array won't have the pid in it. Hence we
+ * use the svr_ses.lastexit struct to hold the exit, which is then compared by
+ * the parent when it runs. This work correctly at least in the case of a
+ * single shell spawned (ie the usual case) */
+void svr_chansess_checksignal(void) {
+ int status;
+ pid_t pid;
+
+ if (!ses.channel_signal_pending) {
+ return;
+ }
+
+ while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
+ unsigned int i;
+ struct exitinfo *ex = NULL;
+ TRACE(("svr_chansess_checksignal : pid %d", pid))
+
+ ex = NULL;
+ /* find the corresponding chansess */
+ for (i = 0; i < svr_ses.childpidsize; i++) {
+ if (svr_ses.childpids[i].pid == pid) {
+ TRACE(("found match session"));
+ ex = &svr_ses.childpids[i].chansess->exit;
+ break;
+ }
+ }
+
+ /* If the pid wasn't matched, then we might have hit the race mentioned
+ * above. So we just store the info for the parent to deal with */
+ if (ex == NULL) {
+ TRACE(("using lastexit"));
+ ex = &svr_ses.lastexit;
+ }
+
+ ex->exitpid = pid;
+ if (WIFEXITED(status)) {
+ ex->exitstatus = WEXITSTATUS(status);
+ }
+ if (WIFSIGNALED(status)) {
+ ex->exitsignal = WTERMSIG(status);
+#if !defined(AIX) && defined(WCOREDUMP)
+ ex->exitcore = WCOREDUMP(status);
+#else
+ ex->exitcore = 0;
+#endif
+ } else {
+ /* we use this to determine how pid exited */
+ ex->exitsignal = -1;
+ }
+ }
+}
+
+static void sesssigchild_handler(int UNUSED(dummy)) {
+ struct sigaction sa_chld;
+
+ const int saved_errno = errno;
+
+ TRACE(("enter sigchld handler"))
+
+ /* Make sure that the main select() loop wakes up */
+ while (1) {
+ /* isserver is just a random byte to write. We can't do anything
+ about an error so should just ignore it */
+ if (write(ses.signal_pipe[1], &ses.isserver, 1) == 1
+ || errno != EINTR) {
+ break;
+ }
+ }
+
+ sa_chld.sa_handler = sesssigchild_handler;
+ sa_chld.sa_flags = SA_NOCLDSTOP;
+ sigemptyset(&sa_chld.sa_mask);
+ sigaction(SIGCHLD, &sa_chld, NULL);
+ TRACE(("leave sigchld handler"))
+
+ errno = saved_errno;
+}
+
+/* send the exit status or the signal causing termination for a session */
+static void send_exitsignalstatus(const struct Channel *channel) {
+
+ struct ChanSess *chansess = (struct ChanSess*)channel->typedata;
+
+ if (chansess->exit.exitpid >= 0) {
+ if (chansess->exit.exitsignal > 0) {
+ send_msg_chansess_exitsignal(channel, chansess);
+ } else {
+ send_msg_chansess_exitstatus(channel, chansess);
+ }
+ }
+}
+
+/* send the exitstatus to the client */
+static void send_msg_chansess_exitstatus(const struct Channel * channel,
+ const struct ChanSess * chansess) {
+
+ dropbear_assert(chansess->exit.exitpid != -1);
+ dropbear_assert(chansess->exit.exitsignal == -1);
+
+ CHECKCLEARTOWRITE();
+
+ buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_REQUEST);
+ buf_putint(ses.writepayload, channel->remotechan);
+ buf_putstring(ses.writepayload, "exit-status", 11);
+ buf_putbyte(ses.writepayload, 0); /* boolean FALSE */
+ buf_putint(ses.writepayload, chansess->exit.exitstatus);
+
+ encrypt_packet();
+
+}
+
+/* send the signal causing the exit to the client */
+static void send_msg_chansess_exitsignal(const struct Channel * channel,
+ const struct ChanSess * chansess) {
+
+ int i;
+ char* signame = NULL;
+ dropbear_assert(chansess->exit.exitpid != -1);
+ dropbear_assert(chansess->exit.exitsignal > 0);
+
+ TRACE(("send_msg_chansess_exitsignal %d", chansess->exit.exitsignal))
+
+ CHECKCLEARTOWRITE();
+
+ /* we check that we can match a signal name, otherwise
+ * don't send anything */
+ for (i = 0; signames[i].name != NULL; i++) {
+ if (signames[i].signal == chansess->exit.exitsignal) {
+ signame = signames[i].name;
+ break;
+ }
+ }
+
+ if (signame == NULL) {
+ return;
+ }
+
+ buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_REQUEST);
+ buf_putint(ses.writepayload, channel->remotechan);
+ buf_putstring(ses.writepayload, "exit-signal", 11);
+ buf_putbyte(ses.writepayload, 0); /* boolean FALSE */
+ buf_putstring(ses.writepayload, signame, strlen(signame));
+ buf_putbyte(ses.writepayload, chansess->exit.exitcore);
+ buf_putstring(ses.writepayload, "", 0); /* error msg */
+ buf_putstring(ses.writepayload, "", 0); /* lang */
+
+ encrypt_packet();
+}
+
+/* set up a session channel */
+static int newchansess(struct Channel *channel) {
+
+ struct ChanSess *chansess;
+
+ TRACE(("new chansess %p", (void*)channel))
+
+ dropbear_assert(channel->typedata == NULL);
+
+ chansess = (struct ChanSess*)m_malloc(sizeof(struct ChanSess));
+ chansess->cmd = NULL;
+ chansess->connection_string = NULL;
+ chansess->client_string = NULL;
+ chansess->pid = 0;
+
+ /* pty details */
+ chansess->master = -1;
+ chansess->slave = -1;
+ chansess->tty = NULL;
+ chansess->term = NULL;
+
+ chansess->exit.exitpid = -1;
+
+ channel->typedata = chansess;
+
+#if DROPBEAR_X11FWD
+ chansess->x11listener = NULL;
+ chansess->x11authprot = NULL;
+ chansess->x11authcookie = NULL;
+#endif
+
+#if DROPBEAR_SVR_AGENTFWD
+ chansess->agentlistener = NULL;
+ chansess->agentfile = NULL;
+ chansess->agentdir = NULL;
+#endif
+
+ /* Will drop to DROPBEAR_PRIO_NORMAL if a non-tty command starts */
+ channel->prio = DROPBEAR_PRIO_LOWDELAY;
+
+ return 0;
+
+}
+
+static struct logininfo*
+chansess_login_alloc(const struct ChanSess *chansess) {
+ struct logininfo * li;
+ li = login_alloc_entry(chansess->pid, ses.authstate.username,
+ svr_ses.remotehost, chansess->tty);
+ return li;
+}
+
+/* send exit status message before the channel is closed */
+static void closechansess(const struct Channel *channel) {
+ struct ChanSess *chansess;
+
+ TRACE(("enter closechansess"))
+
+ chansess = (struct ChanSess*)channel->typedata;
+
+ if (chansess == NULL) {
+ TRACE(("leave closechansess: chansess == NULL"))
+ return;
+ }
+
+ send_exitsignalstatus(channel);
+ TRACE(("leave closechansess"))
+}
+
+/* clean a session channel */
+static void cleanupchansess(const struct Channel *channel) {
+
+ struct ChanSess *chansess;
+ unsigned int i;
+ struct logininfo *li;
+
+ TRACE(("enter closechansess"))
+
+ chansess = (struct ChanSess*)channel->typedata;
+
+ if (chansess == NULL) {
+ TRACE(("leave closechansess: chansess == NULL"))
+ return;
+ }
+
+ m_free(chansess->cmd);
+ m_free(chansess->term);
+ m_free(chansess->original_command);
+
+ if (chansess->tty) {
+ /* write the utmp/wtmp login record */
+ li = chansess_login_alloc(chansess);
+ login_logout(li);
+ login_free_entry(li);
+
+ pty_release(chansess->tty);
+ m_free(chansess->tty);
+ }
+
+#if DROPBEAR_X11FWD
+ x11cleanup(chansess);
+#endif
+
+#if DROPBEAR_SVR_AGENTFWD
+ svr_agentcleanup(chansess);
+#endif
+
+ /* clear child pid entries */
+ for (i = 0; i < svr_ses.childpidsize; i++) {
+ if (svr_ses.childpids[i].chansess == chansess) {
+ dropbear_assert(svr_ses.childpids[i].pid > 0);
+ TRACE(("closing pid %d", svr_ses.childpids[i].pid))
+ TRACE(("exitpid is %d", chansess->exit.exitpid))
+ svr_ses.childpids[i].pid = -1;
+ svr_ses.childpids[i].chansess = NULL;
+ }
+ }
+
+ m_free(chansess);
+
+ TRACE(("leave closechansess"))
+}
+
+/* Handle requests for a channel. These can be execution requests,
+ * or x11/authagent forwarding. These are passed to appropriate handlers */
+static void chansessionrequest(struct Channel *channel) {
+
+ char * type = NULL;
+ unsigned int typelen;
+ unsigned char wantreply;
+ int ret = 1;
+ struct ChanSess *chansess;
+
+ TRACE(("enter chansessionrequest"))
+
+ type = buf_getstring(ses.payload, &typelen);
+ wantreply = buf_getbool(ses.payload);
+
+ if (typelen > MAX_NAME_LEN) {
+ TRACE(("leave chansessionrequest: type too long")) /* XXX send error?*/
+ goto out;
+ }
+
+ chansess = (struct ChanSess*)channel->typedata;
+ dropbear_assert(chansess != NULL);
+ TRACE(("type is %s", type))
+
+ if (strcmp(type, "window-change") == 0) {
+ ret = sessionwinchange(chansess);
+ } else if (strcmp(type, "shell") == 0) {
+ ret = sessioncommand(channel, chansess, 0, 0);
+ } else if (strcmp(type, "pty-req") == 0) {
+ ret = sessionpty(chansess);
+ } else if (strcmp(type, "exec") == 0) {
+ ret = sessioncommand(channel, chansess, 1, 0);
+ } else if (strcmp(type, "subsystem") == 0) {
+ ret = sessioncommand(channel, chansess, 1, 1);
+#if DROPBEAR_X11FWD
+ } else if (strcmp(type, "x11-req") == 0) {
+ ret = x11req(chansess);
+#endif
+#if DROPBEAR_SVR_AGENTFWD
+ } else if (strcmp(type, "auth-agent-req@openssh.com") == 0) {
+ ret = svr_agentreq(chansess);
+#endif
+ } else if (strcmp(type, "signal") == 0) {
+ ret = sessionsignal(chansess);
+ } else {
+ /* etc, todo "env", "subsystem" */
+ }
+
+out:
+
+ if (wantreply) {
+ if (ret == DROPBEAR_SUCCESS) {
+ send_msg_channel_success(channel);
+ } else {
+ send_msg_channel_failure(channel);
+ }
+ }
+
+ m_free(type);
+ TRACE(("leave chansessionrequest"))
+}
+
+
+/* Send a signal to a session's process as requested by the client*/
+static int sessionsignal(const struct ChanSess *chansess) {
+ TRACE(("sessionsignal"))
+
+ int sig = 0;
+ char* signame = NULL;
+ int i;
+
+ if (chansess->pid == 0) {
+ TRACE(("sessionsignal: done no pid"))
+ /* haven't got a process pid yet */
+ return DROPBEAR_FAILURE;
+ }
+
+ signame = buf_getstring(ses.payload, NULL);
+
+ for (i = 0; signames[i].name != NULL; i++) {
+ if (strcmp(signames[i].name, signame) == 0) {
+ sig = signames[i].signal;
+ break;
+ }
+ }
+
+ m_free(signame);
+
+ TRACE(("sessionsignal: pid %d signal %d", (int)chansess->pid, sig))
+ if (sig == 0) {
+ /* failed */
+ return DROPBEAR_FAILURE;
+ }
+
+ if (kill(chansess->pid, sig) < 0) {
+ TRACE(("sessionsignal: kill() errored"))
+ return DROPBEAR_FAILURE;
+ }
+
+ return DROPBEAR_SUCCESS;
+}
+
+/* Let the process know that the window size has changed, as notified from the
+ * client. Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
+static int sessionwinchange(const struct ChanSess *chansess) {
+
+ int termc, termr, termw, termh;
+
+ if (chansess->master < 0) {
+ /* haven't got a pty yet */
+ return DROPBEAR_FAILURE;
+ }
+
+ termc = buf_getint(ses.payload);
+ termr = buf_getint(ses.payload);
+ termw = buf_getint(ses.payload);
+ termh = buf_getint(ses.payload);
+
+ pty_change_window_size(chansess->master, termr, termc, termw, termh);
+
+ return DROPBEAR_SUCCESS;
+}
+
+static void get_termmodes(const struct ChanSess *chansess) {
+
+ struct termios termio;
+ unsigned char opcode;
+ unsigned int value;
+ const struct TermCode * termcode;
+ unsigned int len;
+
+ TRACE(("enter get_termmodes"))
+
+ /* Term modes */
+ /* We'll ignore errors and continue if we can't set modes.
+ * We're ignoring baud rates since they seem evil */
+ if (tcgetattr(chansess->master, &termio) == -1) {
+ return;
+ }
+
+ len = buf_getint(ses.payload);
+ TRACE(("term mode str %d p->l %d p->p %d",
+ len, ses.payload->len , ses.payload->pos));
+ if (len != ses.payload->len - ses.payload->pos) {
+ dropbear_exit("Bad term mode string");
+ }
+
+ if (len == 0) {
+ TRACE(("leave get_termmodes: empty terminal modes string"))
+ return;
+ }
+
+ while (((opcode = buf_getbyte(ses.payload)) != 0x00) && opcode <= 159) {
+
+ /* must be before checking type, so that value is consumed even if
+ * we don't use it */
+ value = buf_getint(ses.payload);
+
+ /* handle types of code */
+ if (opcode > MAX_TERMCODE) {
+ continue;
+ }
+ termcode = &termcodes[(unsigned int)opcode];
+
+
+ switch (termcode->type) {
+
+ case TERMCODE_NONE:
+ break;
+
+ case TERMCODE_CONTROLCHAR:
+ termio.c_cc[termcode->mapcode] = value;
+ break;
+
+ case TERMCODE_INPUT:
+ if (value) {
+ termio.c_iflag |= termcode->mapcode;
+ } else {
+ termio.c_iflag &= ~(termcode->mapcode);
+ }
+ break;
+
+ case TERMCODE_OUTPUT:
+ if (value) {
+ termio.c_oflag |= termcode->mapcode;
+ } else {
+ termio.c_oflag &= ~(termcode->mapcode);
+ }
+ break;
+
+ case TERMCODE_LOCAL:
+ if (value) {
+ termio.c_lflag |= termcode->mapcode;
+ } else {
+ termio.c_lflag &= ~(termcode->mapcode);
+ }
+ break;
+
+ case TERMCODE_CONTROL:
+ if (value) {
+ termio.c_cflag |= termcode->mapcode;
+ } else {
+ termio.c_cflag &= ~(termcode->mapcode);
+ }
+ break;
+
+ }
+ }
+ if (tcsetattr(chansess->master, TCSANOW, &termio) < 0) {
+ dropbear_log(LOG_INFO, "Error setting terminal attributes");
+ }
+ TRACE(("leave get_termmodes"))
+}
+
+/* Set up a session pty which will be used to execute the shell or program.
+ * The pty is allocated now, and kept for when the shell/program executes.
+ * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
+static int sessionpty(struct ChanSess * chansess) {
+
+ unsigned int termlen;
+ char namebuf[65];
+ struct passwd * pw = NULL;
+
+ TRACE(("enter sessionpty"))
+
+ if (!svr_pubkey_allows_pty()) {
+ TRACE(("leave sessionpty : pty forbidden by public key option"))
+ return DROPBEAR_FAILURE;
+ }
+
+ chansess->term = buf_getstring(ses.payload, &termlen);
+ if (termlen > MAX_TERM_LEN) {
+ /* TODO send disconnect ? */
+ TRACE(("leave sessionpty: term len too long"))
+ return DROPBEAR_FAILURE;
+ }
+
+ /* allocate the pty */
+ if (chansess->master != -1) {
+ dropbear_exit("Multiple pty requests");
+ }
+ if (pty_allocate(&chansess->master, &chansess->slave, namebuf, 64) == 0) {
+ TRACE(("leave sessionpty: failed to allocate pty"))
+ return DROPBEAR_FAILURE;
+ }
+
+ chansess->tty = m_strdup(namebuf);
+ if (!chansess->tty) {
+ dropbear_exit("Out of memory"); /* TODO disconnect */
+ }
+
+ pw = getpwnam(ses.authstate.pw_name);
+ if (!pw)
+ dropbear_exit("getpwnam failed after succeeding previously");
+ pty_setowner(pw, chansess->tty);
+
+ /* Set up the rows/col counts */
+ sessionwinchange(chansess);
+
+ /* Read the terminal modes */
+ get_termmodes(chansess);
+
+ TRACE(("leave sessionpty"))
+ return DROPBEAR_SUCCESS;
+}
+
+#if !DROPBEAR_VFORK
+static void make_connection_string(struct ChanSess *chansess) {
+ char *local_ip, *local_port, *remote_ip, *remote_port;
+ size_t len;
+ get_socket_address(ses.sock_in, &local_ip, &local_port, &remote_ip, &remote_port, 0);
+
+ /* "remoteip remoteport localip localport" */
+ len = strlen(local_ip) + strlen(remote_ip) + 20;
+ chansess->connection_string = m_malloc(len);
+ snprintf(chansess->connection_string, len, "%s %s %s %s", remote_ip, remote_port, local_ip, local_port);
+
+ /* deprecated but bash only loads .bashrc if SSH_CLIENT is set */
+ /* "remoteip remoteport localport" */
+ len = strlen(remote_ip) + 20;
+ chansess->client_string = m_malloc(len);
+ snprintf(chansess->client_string, len, "%s %s %s", remote_ip, remote_port, local_port);
+
+ m_free(local_ip);
+ m_free(local_port);
+ m_free(remote_ip);
+ m_free(remote_port);
+}
+#endif
+
+/* Handle a command request from the client. This is used for both shell
+ * and command-execution requests, and passes the command to
+ * noptycommand or ptycommand as appropriate.
+ * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
+static int sessioncommand(struct Channel *channel, struct ChanSess *chansess,
+ int iscmd, int issubsys) {
+
+ unsigned int cmdlen = 0;
+ int ret;
+
+ TRACE(("enter sessioncommand %d", channel->index))
+
+ if (chansess->pid != 0) {
+ /* Note that only one command can _succeed_. The client might try
+ * one command (which fails), then try another. Ie fallback
+ * from sftp to scp */
+ TRACE(("leave sessioncommand, already have a command"))
+ return DROPBEAR_FAILURE;
+ }
+
+ if (iscmd) {
+ /* "exec" */
+ if (chansess->cmd == NULL) {
+ chansess->cmd = buf_getstring(ses.payload, &cmdlen);
+
+ if (cmdlen > MAX_CMD_LEN) {
+ m_free(chansess->cmd);
+ /* TODO - send error - too long ? */
+ TRACE(("leave sessioncommand, command too long %d", cmdlen))
+ return DROPBEAR_FAILURE;
+ }
+ }
+ if (issubsys) {
+#if DROPBEAR_SFTPSERVER
+ if ((cmdlen == 4) && strncmp(chansess->cmd, "sftp", 4) == 0) {
+ char *expand_path = expand_homedir_path(SFTPSERVER_PATH);
+ m_free(chansess->cmd);
+ chansess->cmd = m_strdup(expand_path);
+ m_free(expand_path);
+ } else
+#endif
+ {
+ m_free(chansess->cmd);
+ TRACE(("leave sessioncommand, unknown subsystem"))
+ return DROPBEAR_FAILURE;
+ }
+ }
+ }
+
+
+ /* take global command into account */
+ if (svr_opts.forced_command) {
+ if (chansess->cmd) {
+ chansess->original_command = chansess->cmd;
+ } else {
+ chansess->original_command = m_strdup("");
+ }
+ chansess->cmd = m_strdup(svr_opts.forced_command);
+ } else {
+ /* take public key option 'command' into account */
+ svr_pubkey_set_forced_command(chansess);
+ }
+
+
+#if LOG_COMMANDS
+ if (chansess->cmd) {
+ dropbear_log(LOG_INFO, "User %s executing '%s'",
+ ses.authstate.pw_name, chansess->cmd);
+ } else {
+ dropbear_log(LOG_INFO, "User %s executing login shell",
+ ses.authstate.pw_name);
+ }
+#endif
+
+ /* uClinux will vfork(), so there'll be a race as
+ connection_string is freed below. */
+#if !DROPBEAR_VFORK
+ make_connection_string(chansess);
+#endif
+
+ if (chansess->term == NULL) {
+ /* no pty */
+ ret = noptycommand(channel, chansess);
+ if (ret == DROPBEAR_SUCCESS) {
+ channel->prio = DROPBEAR_PRIO_NORMAL;
+ update_channel_prio();
+ }
+ } else {
+ /* want pty */
+ ret = ptycommand(channel, chansess);
+ }
+
+#if !DROPBEAR_VFORK
+ m_free(chansess->connection_string);
+ m_free(chansess->client_string);
+#endif
+
+ if (ret == DROPBEAR_FAILURE) {
+ m_free(chansess->cmd);
+ }
+ TRACE(("leave sessioncommand, ret %d", ret))
+ return ret;
+}
+
+/* Execute a command and set up redirection of stdin/stdout/stderr without a
+ * pty.
+ * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
+static int noptycommand(struct Channel *channel, struct ChanSess *chansess) {
+ int ret;
+
+ TRACE(("enter noptycommand"))
+ ret = spawn_command(execchild, chansess,
+ &channel->writefd, &channel->readfd, &channel->errfd,
+ &chansess->pid);
+
+ if (ret == DROPBEAR_FAILURE) {
+ return ret;
+ }
+
+ ses.maxfd = MAX(ses.maxfd, channel->writefd);
+ ses.maxfd = MAX(ses.maxfd, channel->readfd);
+ ses.maxfd = MAX(ses.maxfd, channel->errfd);
+ channel->bidir_fd = 0;
+
+ addchildpid(chansess, chansess->pid);
+
+ if (svr_ses.lastexit.exitpid != -1) {
+ unsigned int i;
+ TRACE(("parent side: lastexitpid is %d", svr_ses.lastexit.exitpid))
+ /* The child probably exited and the signal handler triggered
+ * possibly before we got around to adding the childpid. So we fill
+ * out its data manually */
+ for (i = 0; i < svr_ses.childpidsize; i++) {
+ if (svr_ses.childpids[i].pid == svr_ses.lastexit.exitpid) {
+ TRACE(("found match for lastexitpid"))
+ svr_ses.childpids[i].chansess->exit = svr_ses.lastexit;
+ svr_ses.lastexit.exitpid = -1;
+ break;
+ }
+ }
+ }
+
+ TRACE(("leave noptycommand"))
+ return DROPBEAR_SUCCESS;
+}
+
+/* Execute a command or shell within a pty environment, and set up
+ * redirection as appropriate.
+ * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
+static int ptycommand(struct Channel *channel, struct ChanSess *chansess) {
+
+ pid_t pid;
+ struct logininfo *li = NULL;
+#if DO_MOTD
+ buffer * motdbuf = NULL;
+ int len;
+ struct stat sb;
+ char *hushpath = NULL;
+#endif
+
+ TRACE(("enter ptycommand"))
+
+ /* we need to have a pty allocated */
+ if (chansess->master == -1 || chansess->tty == NULL) {
+ dropbear_log(LOG_WARNING, "No pty was allocated, couldn't execute");
+ return DROPBEAR_FAILURE;
+ }
+
+#if DROPBEAR_VFORK
+ pid = vfork();
+#else
+ pid = fork();
+#endif
+ if (pid < 0)
+ return DROPBEAR_FAILURE;
+
+ if (pid == 0) {
+ /* child */
+
+ TRACE(("back to normal sigchld"))
+ /* Revert to normal sigchld handling */
+ if (signal(SIGCHLD, SIG_DFL) == SIG_ERR) {
+ dropbear_exit("signal() error");
+ }
+
+ /* redirect stdin/stdout/stderr */
+ close(chansess->master);
+
+ pty_make_controlling_tty(&chansess->slave, chansess->tty);
+
+ if ((dup2(chansess->slave, STDIN_FILENO) < 0) ||
+ (dup2(chansess->slave, STDOUT_FILENO) < 0)) {
+ TRACE(("leave ptycommand: error redirecting filedesc"))
+ return DROPBEAR_FAILURE;
+ }
+
+ /* write the utmp/wtmp login record - must be after changing the
+ * terminal used for stdout with the dup2 above, otherwise
+ * the wtmp login will not be recorded */
+ li = chansess_login_alloc(chansess);
+ login_login(li);
+ login_free_entry(li);
+
+ /* Can now dup2 stderr. Messages from login_login() have gone
+ to the parent stderr */
+ if (dup2(chansess->slave, STDERR_FILENO) < 0) {
+ TRACE(("leave ptycommand: error redirecting filedesc"))
+ return DROPBEAR_FAILURE;
+ }
+
+ close(chansess->slave);
+
+#if DO_MOTD
+ if (svr_opts.domotd && !chansess->cmd) {
+ /* don't show the motd if ~/.hushlogin exists */
+
+ /* 12 == strlen("/.hushlogin\0") */
+ len = strlen(ses.authstate.pw_dir) + 12;
+
+ hushpath = m_malloc(len);
+ snprintf(hushpath, len, "%s/.hushlogin", ses.authstate.pw_dir);
+
+ if (stat(hushpath, &sb) < 0) {
+ char *expand_path = NULL;
+ /* more than a screenful is stupid IMHO */
+ motdbuf = buf_new(80 * 25);
+ expand_path = expand_homedir_path(MOTD_FILENAME);
+ if (buf_readfile(motdbuf, expand_path) == DROPBEAR_SUCCESS) {
+ buf_setpos(motdbuf, 0);
+ while (motdbuf->pos != motdbuf->len) {
+ len = motdbuf->len - motdbuf->pos;
+ len = write(STDOUT_FILENO,
+ buf_getptr(motdbuf, len), len);
+ buf_incrpos(motdbuf, len);
+ }
+ }
+ m_free(expand_path);
+ buf_free(motdbuf);
+
+ }
+ m_free(hushpath);
+ }
+#endif /* DO_MOTD */
+
+ execchild(chansess);
+ /* not reached */
+
+ } else {
+ /* parent */
+ TRACE(("continue ptycommand: parent"))
+ chansess->pid = pid;
+
+ /* add a child pid */
+ addchildpid(chansess, pid);
+
+ close(chansess->slave);
+ channel->writefd = chansess->master;
+ channel->readfd = chansess->master;
+ /* don't need to set stderr here */
+ ses.maxfd = MAX(ses.maxfd, chansess->master);
+ channel->bidir_fd = 1;
+
+ setnonblocking(chansess->master);
+
+ }
+
+ TRACE(("leave ptycommand"))
+ return DROPBEAR_SUCCESS;
+}
+
+/* Add the pid of a child to the list for exit-handling */
+static void addchildpid(struct ChanSess *chansess, pid_t pid) {
+
+ unsigned int i;
+ for (i = 0; i < svr_ses.childpidsize; i++) {
+ if (svr_ses.childpids[i].pid == -1) {
+ break;
+ }
+ }
+
+ /* need to increase size */
+ if (i == svr_ses.childpidsize) {
+ svr_ses.childpids = (struct ChildPid*)m_realloc(svr_ses.childpids,
+ sizeof(struct ChildPid) * (svr_ses.childpidsize+1));
+ svr_ses.childpidsize++;
+ }
+
+ TRACE(("addchildpid %d pid %d for chansess %p", i, pid, chansess))
+ svr_ses.childpids[i].pid = pid;
+ svr_ses.childpids[i].chansess = chansess;
+
+}
+
+/* Clean up, drop to user privileges, set up the environment and execute
+ * the command/shell. This function does not return. */
+static void execchild(const void *user_data) {
+ const struct ChanSess *chansess = user_data;
+ char *usershell = NULL;
+ char *cp = NULL;
+ char *envcp = getenv("LANG");
+ if (envcp != NULL) {
+ cp = m_strdup(envcp);
+ }
+
+ /* with uClinux we'll have vfork()ed, so don't want to overwrite the
+ * hostkey. can't think of a workaround to clear it */
+#if !DROPBEAR_VFORK
+ /* wipe the hostkey */
+ sign_key_free(svr_opts.hostkey);
+ svr_opts.hostkey = NULL;
+
+ /* overwrite the prng state */
+ seedrandom();
+#endif
+
+ /* clear environment if -e was not set */
+ /* if we're debugging using valgrind etc, we need to keep the LD_PRELOAD
+ * etc. This is hazardous, so should only be used for debugging. */
+ if ( !svr_opts.pass_on_env) {
+#ifndef DEBUG_VALGRIND
+#ifdef HAVE_CLEARENV
+ clearenv();
+#else /* don't HAVE_CLEARENV */
+ /* Yay for posix. */
+ if (environ) {
+ environ[0] = NULL;
+ }
+#endif /* HAVE_CLEARENV */
+#endif /* DEBUG_VALGRIND */
+ }
+
+#if DROPBEAR_SVR_MULTIUSER
+ /* We can only change uid/gid as root ... */
+ if (getuid() == 0) {
+
+ if ((setgid(ses.authstate.pw_gid) < 0) ||
+ (initgroups(ses.authstate.pw_name,
+ ses.authstate.pw_gid) < 0)) {
+ dropbear_exit("Error changing user group");
+ }
+ if (setuid(ses.authstate.pw_uid) < 0) {
+ dropbear_exit("Error changing user");
+ }
+ } else {
+ /* ... but if the daemon is the same uid as the requested uid, we don't
+ * need to */
+
+ /* XXX - there is a minor issue here, in that if there are multiple
+ * usernames with the same uid, but differing groups, then the
+ * differing groups won't be set (as with initgroups()). The solution
+ * is for the sysadmin not to give out the UID twice */
+ if (getuid() != ses.authstate.pw_uid) {
+ dropbear_exit("Couldn't change user as non-root");
+ }
+ }
+#endif
+
+ /* set env vars */
+ addnewvar("USER", ses.authstate.pw_name);
+ addnewvar("LOGNAME", ses.authstate.pw_name);
+ addnewvar("HOME", ses.authstate.pw_dir);
+ addnewvar("SHELL", get_user_shell());
+ if (getuid() == 0) {
+ addnewvar("PATH", DEFAULT_ROOT_PATH);
+ } else {
+ addnewvar("PATH", DEFAULT_PATH);
+ }
+ if (cp != NULL) {
+ addnewvar("LANG", cp);
+ m_free(cp);
+ }
+ if (chansess->term != NULL) {
+ addnewvar("TERM", chansess->term);
+ }
+
+ if (chansess->tty) {
+ addnewvar("SSH_TTY", chansess->tty);
+ }
+
+ if (chansess->connection_string) {
+ addnewvar("SSH_CONNECTION", chansess->connection_string);
+ }
+
+ if (chansess->client_string) {
+ addnewvar("SSH_CLIENT", chansess->client_string);
+ }
+
+ if (chansess->original_command) {
+ addnewvar("SSH_ORIGINAL_COMMAND", chansess->original_command);
+ }
+#if DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT
+ if (ses.authstate.pubkey_info != NULL) {
+ addnewvar("SSH_PUBKEYINFO", ses.authstate.pubkey_info);
+ }
+#endif
+
+ /* change directory */
+ if (chdir(ses.authstate.pw_dir) < 0) {
+ int e = errno;
+ if (chdir("/") < 0) {
+ dropbear_exit("chdir(\"/\") failed");
+ }
+ fprintf(stderr, "Failed chdir '%s': %s\n", ses.authstate.pw_dir, strerror(e));
+ }
+
+
+#if DROPBEAR_X11FWD
+ /* set up X11 forwarding if enabled */
+ x11setauth(chansess);
+#endif
+#if DROPBEAR_SVR_AGENTFWD
+ /* set up agent env variable */
+ svr_agentset(chansess);
+#endif
+
+ usershell = m_strdup(get_user_shell());
+ run_shell_command(chansess->cmd, ses.maxfd, usershell);
+
+ /* only reached on error */
+ dropbear_exit("Child failed");
+}
+
+/* Set up the general chansession environment, in particular child-exit
+ * handling */
+void svr_chansessinitialise() {
+
+ struct sigaction sa_chld;
+
+ /* single child process intially */
+ svr_ses.childpids = (struct ChildPid*)m_malloc(sizeof(struct ChildPid));
+ svr_ses.childpids[0].pid = -1; /* unused */
+ svr_ses.childpids[0].chansess = NULL;
+ svr_ses.childpidsize = 1;
+ svr_ses.lastexit.exitpid = -1; /* Nothing has exited yet */
+ sa_chld.sa_handler = sesssigchild_handler;
+ sa_chld.sa_flags = SA_NOCLDSTOP;
+ sigemptyset(&sa_chld.sa_mask);
+ if (sigaction(SIGCHLD, &sa_chld, NULL) < 0) {
+ dropbear_exit("signal() error");
+ }
+
+}
+
+/* add a new environment variable, allocating space for the entry */
+void addnewvar(const char* param, const char* var) {
+
+ char* newvar = NULL;
+ int plen, vlen;
+
+ plen = strlen(param);
+ vlen = strlen(var);
+
+ newvar = m_malloc(plen + vlen + 2); /* 2 is for '=' and '\0' */
+ memcpy(newvar, param, plen);
+ newvar[plen] = '=';
+ memcpy(&newvar[plen+1], var, vlen);
+ newvar[plen+vlen+1] = '\0';
+ /* newvar is leaked here, but that's part of putenv()'s semantics */
+ if (putenv(newvar) < 0) {
+ dropbear_exit("environ error");
+ }
+}