/* * 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 (ses.authstate.pubkey_info != NULL) { addnewvar("SSH_PUBKEYINFO", ses.authstate.pubkey_info); } /* 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"); } }