X-Git-Url: http://andersk.mit.edu/gitweb/openssh.git/blobdiff_plain/170694d7698f04943edbd843993a978e04a01ce8..HEAD:/clientloop.c diff --git a/clientloop.c b/clientloop.c index eada5603..6ffef95a 100644 --- a/clientloop.c +++ b/clientloop.c @@ -1,3 +1,4 @@ +/* $OpenBSD: clientloop.c,v 1.218 2010/01/28 00:21:18 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -59,31 +60,56 @@ */ #include "includes.h" -RCSID("$OpenBSD: clientloop.c,v 1.124 2004/06/14 01:44:38 djm Exp $"); +#include +#include +#include +#ifdef HAVE_SYS_STAT_H +# include +#endif +#ifdef HAVE_SYS_TIME_H +# include +#endif +#include + +#include +#include +#ifdef HAVE_PATHS_H +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +#include "openbsd-compat/sys-queue.h" +#include "xmalloc.h" #include "ssh.h" #include "ssh1.h" #include "ssh2.h" -#include "xmalloc.h" #include "packet.h" #include "buffer.h" #include "compat.h" #include "channels.h" #include "dispatch.h" -#include "buffer.h" -#include "bufaux.h" #include "key.h" +#include "cipher.h" #include "kex.h" #include "log.h" #include "readconf.h" #include "clientloop.h" +#include "sshconnect.h" #include "authfd.h" #include "atomicio.h" #include "sshpty.h" #include "misc.h" -#include "monitor_fdpass.h" #include "match.h" #include "msg.h" +#include "roaming.h" /* import options */ extern Options options; @@ -95,7 +121,7 @@ extern int stdin_null_flag; extern int no_shell_flag; /* Control socket */ -extern int control_fd; +extern int muxserver_sock; /* XXX use mux_client_cleanup() instead */ /* * Name of the host we are connecting to. This is the name given on the @@ -104,6 +130,9 @@ extern int control_fd; */ extern char *host; +/* Force TTY allocation */ +extern int force_tty_flag; + /* * Flag to indicate that we have received a window change signal which has * not yet been processed. This will cause a message indicating the new @@ -113,38 +142,53 @@ extern char *host; static volatile sig_atomic_t received_window_change_signal = 0; static volatile sig_atomic_t received_signal = 0; -/* Flag indicating whether the user\'s terminal is in non-blocking mode. */ +/* Flag indicating whether the user's terminal is in non-blocking mode. */ static int in_non_blocking_mode = 0; /* Common data for the client loop code. */ -static int quit_pending; /* Set to non-zero to quit the client loop. */ -static int escape_char; /* Escape character. */ -static int escape_pending; /* Last character was the escape character */ +volatile sig_atomic_t quit_pending; /* Set non-zero to quit the loop. */ +static int escape_char1; /* Escape character. (proto1 only) */ +static int escape_pending1; /* Last character was an escape (proto1 only) */ static int last_was_cr; /* Last character was a newline. */ -static int exit_status; /* Used to store the exit status of the command. */ -static int stdin_eof; /* EOF has been encountered on standard error. */ +static int exit_status; /* Used to store the command exit status. */ +static int stdin_eof; /* EOF has been encountered on stderr. */ static Buffer stdin_buffer; /* Buffer for stdin data. */ static Buffer stdout_buffer; /* Buffer for stdout data. */ static Buffer stderr_buffer; /* Buffer for stderr data. */ -static u_long stdin_bytes, stdout_bytes, stderr_bytes; static u_int buffer_high;/* Soft max buffer size. */ static int connection_in; /* Connection to server (input). */ static int connection_out; /* Connection to server (output). */ static int need_rekeying; /* Set to non-zero if rekeying is requested. */ static int session_closed = 0; /* In SSH2: login session closed. */ -static int server_alive_timeouts = 0; static void client_init_dispatch(void); int session_ident = -1; -struct confirm_ctx { - int want_tty; - int want_subsys; - Buffer cmd; - char *term; - struct termios tio; +int session_resumed = 0; + +/* Track escape per proto2 channel */ +struct escape_filter_ctx { + int escape_pending; + int escape_char; +}; + +/* Context for channel confirmation replies */ +struct channel_reply_ctx { + const char *request_type; + int id, do_close; }; +/* Global request success/failure callbacks */ +struct global_confirm { + TAILQ_ENTRY(global_confirm) entry; + global_confirm_cb *cb; + void *ctx; + int ref_count; +}; +TAILQ_HEAD(global_confirms, global_confirm); +static struct global_confirms global_confirms = + TAILQ_HEAD_INITIALIZER(global_confirms); + /*XXX*/ extern Kex *xxx_kex; @@ -156,7 +200,7 @@ static void leave_non_blocking(void) { if (in_non_blocking_mode) { - (void) fcntl(fileno(stdin), F_SETFL, 0); + unset_nonblock(fileno(stdin)); in_non_blocking_mode = 0; } } @@ -174,7 +218,7 @@ enter_non_blocking(void) * Signal handler for the window change signal (SIGWINCH). This just sets a * flag indicating that the window has changed. */ - +/*ARGSUSED */ static void window_change_handler(int sig) { @@ -186,7 +230,7 @@ window_change_handler(int sig) * Signal handler for signals that cause the program to terminate. These * signals must be trapped to restore terminal modes. */ - +/*ARGSUSED */ static void signal_handler(int sig) { @@ -207,6 +251,119 @@ get_current_time(void) return (double) tv.tv_sec + (double) tv.tv_usec / 1000000.0; } +#define SSH_X11_PROTO "MIT-MAGIC-COOKIE-1" +void +client_x11_get_proto(const char *display, const char *xauth_path, + u_int trusted, char **_proto, char **_data) +{ + char cmd[1024]; + char line[512]; + char xdisplay[512]; + static char proto[512], data[512]; + FILE *f; + int got_data = 0, generated = 0, do_unlink = 0, i; + char *xauthdir, *xauthfile; + struct stat st; + + xauthdir = xauthfile = NULL; + *_proto = proto; + *_data = data; + proto[0] = data[0] = '\0'; + + if (xauth_path == NULL ||(stat(xauth_path, &st) == -1)) { + debug("No xauth program."); + } else { + if (display == NULL) { + debug("x11_get_proto: DISPLAY not set"); + return; + } + /* + * Handle FamilyLocal case where $DISPLAY does + * not match an authorization entry. For this we + * just try "xauth list unix:displaynum.screennum". + * XXX: "localhost" match to determine FamilyLocal + * is not perfect. + */ + if (strncmp(display, "localhost:", 10) == 0) { + snprintf(xdisplay, sizeof(xdisplay), "unix:%s", + display + 10); + display = xdisplay; + } + if (trusted == 0) { + xauthdir = xmalloc(MAXPATHLEN); + xauthfile = xmalloc(MAXPATHLEN); + strlcpy(xauthdir, "/tmp/ssh-XXXXXXXXXX", MAXPATHLEN); + if (mkdtemp(xauthdir) != NULL) { + do_unlink = 1; + snprintf(xauthfile, MAXPATHLEN, "%s/xauthfile", + xauthdir); + snprintf(cmd, sizeof(cmd), + "%s -f %s generate %s " SSH_X11_PROTO + " untrusted timeout 1200 2>" _PATH_DEVNULL, + xauth_path, xauthfile, display); + debug2("x11_get_proto: %s", cmd); + if (system(cmd) == 0) + generated = 1; + } + } + + /* + * When in untrusted mode, we read the cookie only if it was + * successfully generated as an untrusted one in the step + * above. + */ + if (trusted || generated) { + snprintf(cmd, sizeof(cmd), + "%s %s%s list %s 2>" _PATH_DEVNULL, + xauth_path, + generated ? "-f " : "" , + generated ? xauthfile : "", + display); + debug2("x11_get_proto: %s", cmd); + f = popen(cmd, "r"); + if (f && fgets(line, sizeof(line), f) && + sscanf(line, "%*s %511s %511s", proto, data) == 2) + got_data = 1; + if (f) + pclose(f); + } else + error("Warning: untrusted X11 forwarding setup failed: " + "xauth key data not generated"); + } + + if (do_unlink) { + unlink(xauthfile); + rmdir(xauthdir); + } + if (xauthdir) + xfree(xauthdir); + if (xauthfile) + xfree(xauthfile); + + /* + * If we didn't get authentication data, just make up some + * data. The forwarding code will check the validity of the + * response anyway, and substitute this data. The X11 + * server, however, will ignore this fake data and use + * whatever authentication mechanisms it was using otherwise + * for the local connection. + */ + if (!got_data) { + u_int32_t rnd = 0; + + logit("Warning: No xauth data; " + "using fake authentication data for X11 forwarding."); + strlcpy(proto, SSH_X11_PROTO, sizeof proto); + for (i = 0; i < 16; i++) { + if (i % 4 == 0) + rnd = arc4random(); + snprintf(data + 2 * i, sizeof data - 2 * i, "%02x", + rnd & 0xff); + rnd >>= 8; + } + } +} + /* * This is called when the interactive is entered. This checks if there is * an EOF coming on stdin. We must check this explicitly, as select() does @@ -239,7 +396,10 @@ client_check_initial_eof_on_stdin(void) /* Check for immediate EOF on stdin. */ len = read(fileno(stdin), buf, 1); if (len == 0) { - /* EOF. Record that we have seen it and send EOF to server. */ + /* + * EOF. Record that we have seen it and send + * EOF to server. + */ debug("Sending eof."); stdin_eof = 1; packet_start(SSH_CMSG_EOF); @@ -250,8 +410,8 @@ client_check_initial_eof_on_stdin(void) * and also process it as an escape character if * appropriate. */ - if ((u_char) buf[0] == escape_char) - escape_pending = 1; + if ((u_char) buf[0] == escape_char1) + escape_pending1 = 1; else buffer_append(&stdin_buffer, buf, 1); } @@ -281,7 +441,6 @@ client_make_packets_from_stdin_data(void) packet_put_string(buffer_ptr(&stdin_buffer), len); packet_send(); buffer_consume(&stdin_buffer, len); - stdin_bytes += len; /* If we have a pending EOF, send it now. */ if (stdin_eof && buffer_len(&stdin_buffer) == 0) { packet_start(SSH_CMSG_EOF); @@ -315,10 +474,10 @@ client_check_window_change(void) if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) < 0) return; packet_start(SSH_CMSG_WINDOW_SIZE); - packet_put_int(ws.ws_row); - packet_put_int(ws.ws_col); - packet_put_int(ws.ws_xpixel); - packet_put_int(ws.ws_ypixel); + packet_put_int((u_int)ws.ws_row); + packet_put_int((u_int)ws.ws_col); + packet_put_int((u_int)ws.ws_xpixel); + packet_put_int((u_int)ws.ws_ypixel); packet_send(); } } @@ -326,19 +485,34 @@ client_check_window_change(void) static void client_global_request_reply(int type, u_int32_t seq, void *ctxt) { - server_alive_timeouts = 0; - client_global_request_reply_fwd(type, seq, ctxt); + struct global_confirm *gc; + + if ((gc = TAILQ_FIRST(&global_confirms)) == NULL) + return; + if (gc->cb != NULL) + gc->cb(type, seq, gc->ctx); + if (--gc->ref_count <= 0) { + TAILQ_REMOVE(&global_confirms, gc, entry); + bzero(gc, sizeof(*gc)); + xfree(gc); + } + + packet_set_alive_timeouts(0); } static void server_alive_check(void) { - if (++server_alive_timeouts > options.server_alive_count_max) - packet_disconnect("Timeout, server not responding."); + if (packet_inc_alive_timeouts() > options.server_alive_count_max) { + logit("Timeout, server not responding."); + cleanup_exit(255); + } packet_start(SSH2_MSG_GLOBAL_REQUEST); packet_put_cstring("keepalive@openssh.com"); packet_put_char(1); /* boolean: want reply */ packet_send(); + /* Insert an empty placeholder to maintain ordering */ + client_register_global_confirm(NULL, NULL); } /* @@ -347,7 +521,7 @@ server_alive_check(void) */ static void client_wait_until_can_do_something(fd_set **readsetp, fd_set **writesetp, - int *maxfdp, int *nallocp, int rekeying) + int *maxfdp, u_int *nallocp, int rekeying) { struct timeval tv, *tvp; int ret; @@ -390,9 +564,6 @@ client_wait_until_can_do_something(fd_set **readsetp, fd_set **writesetp, if (packet_have_data_to_write()) FD_SET(connection_out, *writesetp); - if (control_fd != -1) - FD_SET(control_fd, *readsetp); - /* * Wait for something to happen. This will suspend the process until * some selected descriptor can be read, written, or has some other @@ -401,7 +572,7 @@ client_wait_until_can_do_something(fd_set **readsetp, fd_set **writesetp, if (options.server_alive_interval == 0 || !compat20) tvp = NULL; - else { + else { tv.tv_sec = options.server_alive_interval; tv.tv_usec = 0; tvp = &tv; @@ -431,15 +602,15 @@ client_wait_until_can_do_something(fd_set **readsetp, fd_set **writesetp, static void client_suspend_self(Buffer *bin, Buffer *bout, Buffer *berr) { - struct winsize oldws, newws; - /* Flush stdout and stderr buffers. */ if (buffer_len(bout) > 0) - atomicio(vwrite, fileno(stdout), buffer_ptr(bout), buffer_len(bout)); + atomicio(vwrite, fileno(stdout), buffer_ptr(bout), + buffer_len(bout)); if (buffer_len(berr) > 0) - atomicio(vwrite, fileno(stderr), buffer_ptr(berr), buffer_len(berr)); + atomicio(vwrite, fileno(stderr), buffer_ptr(berr), + buffer_len(berr)); - leave_raw_mode(); + leave_raw_mode(force_tty_flag); /* * Free (and clear) the buffer to reduce the amount of data that gets @@ -449,33 +620,25 @@ client_suspend_self(Buffer *bin, Buffer *bout, Buffer *berr) buffer_free(bout); buffer_free(berr); - /* Save old window size. */ - ioctl(fileno(stdin), TIOCGWINSZ, &oldws); - /* Send the suspend signal to the program itself. */ kill(getpid(), SIGTSTP); - /* Check if the window size has changed. */ - if (ioctl(fileno(stdin), TIOCGWINSZ, &newws) >= 0 && - (oldws.ws_row != newws.ws_row || - oldws.ws_col != newws.ws_col || - oldws.ws_xpixel != newws.ws_xpixel || - oldws.ws_ypixel != newws.ws_ypixel)) - received_window_change_signal = 1; + /* Reset window sizes in case they have changed */ + received_window_change_signal = 1; /* OK, we have been continued by the user. Reinitialize buffers. */ buffer_init(bin); buffer_init(bout); buffer_init(berr); - enter_raw_mode(); + enter_raw_mode(force_tty_flag); } static void -client_process_net_input(fd_set * readset) +client_process_net_input(fd_set *readset) { - int len; - char buf[8192]; + int len, cont = 0; + char buf[SSH_IOBUFSZ]; /* * Read input from the server, and add any such data to the buffer of @@ -483,11 +646,15 @@ client_process_net_input(fd_set * readset) */ if (FD_ISSET(connection_in, readset)) { /* Read as much as possible. */ - len = read(connection_in, buf, sizeof(buf)); - if (len == 0) { - /* Received EOF. The remote host has closed the connection. */ - snprintf(buf, sizeof buf, "Connection to %.300s closed by remote host.\r\n", - host); + len = roaming_read(connection_in, buf, sizeof(buf), &cont); + if (len == 0 && cont == 0) { + /* + * Received EOF. The remote host has closed the + * connection. + */ + snprintf(buf, sizeof buf, + "Connection to %.300s closed by remote host.\r\n", + host); buffer_append(&stderr_buffer, buf, strlen(buf)); quit_pending = 1; return; @@ -496,13 +663,18 @@ client_process_net_input(fd_set * readset) * There is a kernel bug on Solaris that causes select to * sometimes wake up even though there is no data available. */ - if (len < 0 && (errno == EAGAIN || errno == EINTR)) + if (len < 0 && + (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK)) len = 0; if (len < 0) { - /* An error has encountered. Perhaps there is a network problem. */ - snprintf(buf, sizeof buf, "Read from remote host %.300s: %.100s\r\n", - host, strerror(errno)); + /* + * An error has encountered. Perhaps there is a + * network problem. + */ + snprintf(buf, sizeof buf, + "Read from remote host %.300s: %.100s\r\n", + host, strerror(errno)); buffer_append(&stderr_buffer, buf, strlen(buf)); quit_pending = 1; return; @@ -512,191 +684,102 @@ client_process_net_input(fd_set * readset) } static void -client_subsystem_reply(int type, u_int32_t seq, void *ctxt) +client_status_confirm(int type, Channel *c, void *ctx) { - int id; - Channel *c; - - id = packet_get_int(); - packet_check_eom(); - - if ((c = channel_lookup(id)) == NULL) { - error("%s: no channel for id %d", __func__, id); - return; - } - - if (type == SSH2_MSG_CHANNEL_SUCCESS) - debug2("Request suceeded on channel %d", id); - else if (type == SSH2_MSG_CHANNEL_FAILURE) { - error("Request failed on channel %d", id); - channel_free(c); + struct channel_reply_ctx *cr = (struct channel_reply_ctx *)ctx; + char errmsg[256]; + int tochan; + + /* XXX supress on mux _client_ quietmode */ + tochan = options.log_level >= SYSLOG_LEVEL_ERROR && + c->ctl_chan != -1 && c->extended_usage == CHAN_EXTENDED_WRITE; + + if (type == SSH2_MSG_CHANNEL_SUCCESS) { + debug2("%s request accepted on channel %d", + cr->request_type, c->self); + } else if (type == SSH2_MSG_CHANNEL_FAILURE) { + if (tochan) { + snprintf(errmsg, sizeof(errmsg), + "%s request failed\r\n", cr->request_type); + } else { + snprintf(errmsg, sizeof(errmsg), + "%s request failed on channel %d", + cr->request_type, c->self); + } + /* If error occurred on primary session channel, then exit */ + if (cr->do_close && c->self == session_ident) + fatal("%s", errmsg); + /* If error occurred on mux client, append to their stderr */ + if (tochan) + buffer_append(&c->extended, errmsg, strlen(errmsg)); + else + error("%s", errmsg); + if (cr->do_close) { + chan_read_failed(c); + chan_write_failed(c); + } } + xfree(cr); } static void -client_extra_session2_setup(int id, void *arg) +client_abandon_status_confirm(Channel *c, void *ctx) { - struct confirm_ctx *cctx = arg; - Channel *c; - - if (cctx == NULL) - fatal("%s: cctx == NULL", __func__); - if ((c = channel_lookup(id)) == NULL) - fatal("%s: no channel for id %d", __func__, id); - - client_session2_setup(id, cctx->want_tty, cctx->want_subsys, - cctx->term, &cctx->tio, c->rfd, &cctx->cmd, - client_subsystem_reply); - - c->confirm_ctx = NULL; - buffer_free(&cctx->cmd); - free(cctx->term); - free(cctx); + xfree(ctx); } static void -client_process_control(fd_set * readset) +client_expect_confirm(int id, const char *request, int do_close) { - Buffer m; - Channel *c; - int client_fd, new_fd[3], ver; - socklen_t addrlen; - struct sockaddr_storage addr; - struct confirm_ctx *cctx; - char *cmd; - u_int len; - uid_t euid; - gid_t egid; + struct channel_reply_ctx *cr = xmalloc(sizeof(*cr)); - /* - * Accept connection on control socket - */ - if (control_fd == -1 || !FD_ISSET(control_fd, readset)) - return; + cr->request_type = request; + cr->do_close = do_close; - memset(&addr, 0, sizeof(addr)); - addrlen = sizeof(addr); - if ((client_fd = accept(control_fd, - (struct sockaddr*)&addr, &addrlen)) == -1) { - error("%s accept: %s", __func__, strerror(errno)); - return; - } - - if (getpeereid(client_fd, &euid, &egid) < 0) { - error("%s getpeereid failed: %s", __func__, strerror(errno)); - close(client_fd); - return; - } - if ((euid != 0) && (getuid() != euid)) { - error("control mode uid mismatch: peer euid %u != uid %u", - (u_int) euid, (u_int) getuid()); - close(client_fd); - return; - } - /* XXX: implement use of ssh-askpass to confirm additional channels */ - - unset_nonblock(client_fd); - - buffer_init(&m); - - buffer_put_int(&m, getpid()); - if (ssh_msg_send(client_fd, /* version */0, &m) == -1) { - error("%s: client msg_send failed", __func__); - close(client_fd); - return; - } - buffer_clear(&m); - - if (ssh_msg_recv(client_fd, &m) == -1) { - error("%s: client msg_recv failed", __func__); - close(client_fd); - return; - } - - if ((ver = buffer_get_char(&m)) != 0) { - error("%s: wrong client version %d", __func__, ver); - buffer_free(&m); - close(client_fd); - return; - } - - cctx = xmalloc(sizeof(*cctx)); - memset(cctx, 0, sizeof(*cctx)); - - cctx->want_tty = buffer_get_int(&m); - cctx->want_subsys = buffer_get_int(&m); - cctx->term = buffer_get_string(&m, &len); - - cmd = buffer_get_string(&m, &len); - buffer_init(&cctx->cmd); - buffer_append(&cctx->cmd, cmd, strlen(cmd)); - - debug2("%s: accepted tty %d, subsys %d, cmd %s", __func__, - cctx->want_tty, cctx->want_subsys, cmd); - - /* Gather fds from client */ - new_fd[0] = mm_receive_fd(client_fd); - new_fd[1] = mm_receive_fd(client_fd); - new_fd[2] = mm_receive_fd(client_fd); - - debug2("%s: got fds stdin %d, stdout %d, stderr %d", __func__, - new_fd[0], new_fd[1], new_fd[2]); - - /* Try to pick up ttymodes from client before it goes raw */ - if (cctx->want_tty && tcgetattr(new_fd[0], &cctx->tio) == -1) - error("%s: tcgetattr: %s", __func__, strerror(errno)); + channel_register_status_confirm(id, client_status_confirm, + client_abandon_status_confirm, cr); +} - buffer_clear(&m); - if (ssh_msg_send(client_fd, /* version */0, &m) == -1) { - error("%s: client msg_send failed", __func__); - close(client_fd); - close(new_fd[0]); - close(new_fd[1]); - close(new_fd[2]); +void +client_register_global_confirm(global_confirm_cb *cb, void *ctx) +{ + struct global_confirm *gc, *last_gc; + + /* Coalesce identical callbacks */ + last_gc = TAILQ_LAST(&global_confirms, global_confirms); + if (last_gc && last_gc->cb == cb && last_gc->ctx == ctx) { + if (++last_gc->ref_count >= INT_MAX) + fatal("%s: last_gc->ref_count = %d", + __func__, last_gc->ref_count); return; } - buffer_free(&m); - - /* enable nonblocking unless tty */ - if (!isatty(new_fd[0])) - set_nonblock(new_fd[0]); - if (!isatty(new_fd[1])) - set_nonblock(new_fd[1]); - if (!isatty(new_fd[2])) - set_nonblock(new_fd[2]); - - set_nonblock(client_fd); - - c = channel_new("session", SSH_CHANNEL_OPENING, - new_fd[0], new_fd[1], new_fd[2], - CHAN_SES_WINDOW_DEFAULT, CHAN_SES_PACKET_DEFAULT, - CHAN_EXTENDED_WRITE, "client-session", /*nonblock*/0); - - /* XXX */ - c->ctl_fd = client_fd; - debug3("%s: channel_new: %d", __func__, c->self); - - channel_send_open(c->self); - channel_register_confirm(c->self, client_extra_session2_setup, cctx); + gc = xmalloc(sizeof(*gc)); + gc->cb = cb; + gc->ctx = ctx; + gc->ref_count = 1; + TAILQ_INSERT_TAIL(&global_confirms, gc, entry); } static void process_cmdline(void) { void (*handler)(int); - char *s, *cmd; - u_short fwd_port, fwd_host_port; - char buf[1024], sfwd_port[6], sfwd_host_port[6]; + char *s, *cmd, *cancel_host; int delete = 0; - int local = 0; + int local = 0, remote = 0, dynamic = 0; + int cancel_port; + Forward fwd; + + bzero(&fwd, sizeof(fwd)); + fwd.listen_host = fwd.connect_host = NULL; - leave_raw_mode(); + leave_raw_mode(force_tty_flag); handler = signal(SIGINT, SIG_IGN); cmd = s = read_passphrase("\r\nssh> ", RP_ECHO); if (s == NULL) goto out; - while (*s && isspace(*s)) + while (isspace(*s)) s++; if (*s == '-') s++; /* Skip cmdline '-', if any */ @@ -705,9 +788,24 @@ process_cmdline(void) if (*s == 'h' || *s == 'H' || *s == '?') { logit("Commands:"); - logit(" -Lport:host:hostport Request local forward"); - logit(" -Rport:host:hostport Request remote forward"); - logit(" -KRhostport Cancel remote forward"); + logit(" -L[bind_address:]port:host:hostport " + "Request local forward"); + logit(" -R[bind_address:]port:host:hostport " + "Request remote forward"); + logit(" -D[bind_address:]port " + "Request dynamic forward"); + logit(" -KR[bind_address:]port " + "Cancel remote forward"); + if (!options.permit_local_command) + goto out; + logit(" !args " + "Execute local command"); + goto out; + } + + if (*s == '!' && options.permit_local_command) { + s++; + ssh_local_cmd(s); goto out; } @@ -715,70 +813,86 @@ process_cmdline(void) delete = 1; s++; } - if (*s != 'L' && *s != 'R') { + if (*s == 'L') + local = 1; + else if (*s == 'R') + remote = 1; + else if (*s == 'D') + dynamic = 1; + else { logit("Invalid command."); goto out; } - if (*s == 'L') - local = 1; - if (local && delete) { + + if ((local || dynamic) && delete) { logit("Not supported."); goto out; } - if ((!local || delete) && !compat20) { + if (remote && delete && !compat20) { logit("Not supported for SSH protocol version 1."); goto out; } - s++; - while (*s && isspace(*s)) - s++; + while (isspace(*++s)) + ; + /* XXX update list of forwards in options */ if (delete) { - if (sscanf(s, "%5[0-9]", sfwd_host_port) != 1) { - logit("Bad forwarding specification."); - goto out; + cancel_port = 0; + cancel_host = hpdelim(&s); /* may be NULL */ + if (s != NULL) { + cancel_port = a2port(s); + cancel_host = cleanhostname(cancel_host); + } else { + cancel_port = a2port(cancel_host); + cancel_host = NULL; } - if ((fwd_host_port = a2port(sfwd_host_port)) == 0) { - logit("Bad forwarding port(s)."); + if (cancel_port <= 0) { + logit("Bad forwarding close port"); goto out; } - channel_request_rforward_cancel(fwd_host_port); + channel_request_rforward_cancel(cancel_host, cancel_port); } else { - if (sscanf(s, "%5[0-9]:%255[^:]:%5[0-9]", - sfwd_port, buf, sfwd_host_port) != 3 && - sscanf(s, "%5[0-9]/%255[^/]/%5[0-9]", - sfwd_port, buf, sfwd_host_port) != 3) { + if (!parse_forward(&fwd, s, dynamic, remote)) { logit("Bad forwarding specification."); goto out; } - if ((fwd_port = a2port(sfwd_port)) == 0 || - (fwd_host_port = a2port(sfwd_host_port)) == 0) { - logit("Bad forwarding port(s)."); - goto out; - } - if (local) { - if (channel_setup_local_fwd_listener(fwd_port, buf, - fwd_host_port, options.gateway_ports) < 0) { + if (local || dynamic) { + if (channel_setup_local_fwd_listener(fwd.listen_host, + fwd.listen_port, fwd.connect_host, + fwd.connect_port, options.gateway_ports) < 0) { logit("Port forwarding failed."); goto out; } - } else - channel_request_remote_forwarding(fwd_port, buf, - fwd_host_port); + } else { + if (channel_request_remote_forwarding(fwd.listen_host, + fwd.listen_port, fwd.connect_host, + fwd.connect_port) < 0) { + logit("Port forwarding failed."); + goto out; + } + } + logit("Forwarding port."); } out: signal(SIGINT, handler); - enter_raw_mode(); + enter_raw_mode(force_tty_flag); if (cmd) xfree(cmd); + if (fwd.listen_host != NULL) + xfree(fwd.listen_host); + if (fwd.connect_host != NULL) + xfree(fwd.connect_host); } -/* process the characters one by one */ +/* + * Process the characters one by one, call with c==NULL for proto1 case. + */ static int -process_escapes(Buffer *bin, Buffer *bout, Buffer *berr, char *buf, int len) +process_escapes(Channel *c, Buffer *bin, Buffer *bout, Buffer *berr, + char *buf, int len) { char string[1024]; pid_t pid; @@ -786,30 +900,63 @@ process_escapes(Buffer *bin, Buffer *bout, Buffer *berr, char *buf, int len) u_int i; u_char ch; char *s; + int *escape_pendingp, escape_char; + struct escape_filter_ctx *efc; - for (i = 0; i < len; i++) { + if (c == NULL) { + escape_pendingp = &escape_pending1; + escape_char = escape_char1; + } else { + if (c->filter_ctx == NULL) + return 0; + efc = (struct escape_filter_ctx *)c->filter_ctx; + escape_pendingp = &efc->escape_pending; + escape_char = efc->escape_char; + } + + if (len <= 0) + return (0); + + for (i = 0; i < (u_int)len; i++) { /* Get one character at a time. */ ch = buf[i]; - if (escape_pending) { + if (*escape_pendingp) { /* We have previously seen an escape character. */ /* Clear the flag now. */ - escape_pending = 0; + *escape_pendingp = 0; /* Process the escaped character. */ switch (ch) { case '.': /* Terminate the connection. */ - snprintf(string, sizeof string, "%c.\r\n", escape_char); + snprintf(string, sizeof string, "%c.\r\n", + escape_char); buffer_append(berr, string, strlen(string)); - quit_pending = 1; + if (c && c->ctl_chan != -1) { + chan_read_failed(c); + chan_write_failed(c); + return 0; + } else + quit_pending = 1; return -1; case 'Z' - 64: - /* Suspend the program. */ - /* Print a message to that effect to the user. */ - snprintf(string, sizeof string, "%c^Z [suspend ssh]\r\n", escape_char); + /* XXX support this for mux clients */ + if (c && c->ctl_chan != -1) { + noescape: + snprintf(string, sizeof string, + "%c%c escape not available to " + "multiplexed sessions\r\n", + escape_char, ch); + buffer_append(berr, string, + strlen(string)); + continue; + } + /* Suspend the program. Inform the user */ + snprintf(string, sizeof string, + "%c^Z [suspend ssh]\r\n", escape_char); buffer_append(berr, string, strlen(string)); /* Restore terminal modes and suspend. */ @@ -834,19 +981,23 @@ process_escapes(Buffer *bin, Buffer *bout, Buffer *berr, char *buf, int len) case 'R': if (compat20) { if (datafellows & SSH_BUG_NOREKEY) - logit("Server does not support re-keying"); + logit("Server does not " + "support re-keying"); else need_rekeying = 1; } continue; case '&': + if (c && c->ctl_chan != -1) + goto noescape; /* - * Detach the program (continue to serve connections, - * but put in background and no more new connections). + * Detach the program (continue to serve + * connections, but put in background and no + * more new connections). */ /* Restore tty modes. */ - leave_raw_mode(); + leave_raw_mode(force_tty_flag); /* Stop listening for new connections. */ channel_stop_listening(); @@ -872,9 +1023,9 @@ process_escapes(Buffer *bin, Buffer *bout, Buffer *berr, char *buf, int len) return -1; } else if (!stdin_eof) { /* - * Sending SSH_CMSG_EOF alone does not always appear - * to be enough. So we try to send an EOF character - * first. + * Sending SSH_CMSG_EOF alone does not + * always appear to be enough. So we + * try to send an EOF character first. */ packet_start(SSH_CMSG_STDIN_DATA); packet_put_string("\004", 1); @@ -889,27 +1040,48 @@ process_escapes(Buffer *bin, Buffer *bout, Buffer *berr, char *buf, int len) continue; case '?': - snprintf(string, sizeof string, + if (c && c->ctl_chan != -1) { + snprintf(string, sizeof string, "%c?\r\n\ Supported escape sequences:\r\n\ -%c. - terminate connection\r\n\ -%cB - send a BREAK to the remote system\r\n\ -%cC - open a command line\r\n\ -%cR - Request rekey (SSH protocol 2 only)\r\n\ -%c^Z - suspend ssh\r\n\ -%c# - list forwarded connections\r\n\ -%c& - background ssh (when waiting for connections to terminate)\r\n\ -%c? - this message\r\n\ -%c%c - send the escape character by typing it twice\r\n\ + %c. - terminate session\r\n\ + %cB - send a BREAK to the remote system\r\n\ + %cR - Request rekey (SSH protocol 2 only)\r\n\ + %c# - list forwarded connections\r\n\ + %c? - this message\r\n\ + %c%c - send the escape character by typing it twice\r\n\ (Note that escapes are only recognized immediately after newline.)\r\n", - escape_char, escape_char, escape_char, escape_char, - escape_char, escape_char, escape_char, escape_char, - escape_char, escape_char, escape_char); + escape_char, escape_char, + escape_char, escape_char, + escape_char, escape_char, + escape_char, escape_char); + } else { + snprintf(string, sizeof string, +"%c?\r\n\ +Supported escape sequences:\r\n\ + %c. - terminate connection (and any multiplexed sessions)\r\n\ + %cB - send a BREAK to the remote system\r\n\ + %cC - open a command line\r\n\ + %cR - Request rekey (SSH protocol 2 only)\r\n\ + %c^Z - suspend ssh\r\n\ + %c# - list forwarded connections\r\n\ + %c& - background ssh (when waiting for connections to terminate)\r\n\ + %c? - this message\r\n\ + %c%c - send the escape character by typing it twice\r\n\ +(Note that escapes are only recognized immediately after newline.)\r\n", + escape_char, escape_char, + escape_char, escape_char, + escape_char, escape_char, + escape_char, escape_char, + escape_char, escape_char, + escape_char); + } buffer_append(berr, string, strlen(string)); continue; case '#': - snprintf(string, sizeof string, "%c#\r\n", escape_char); + snprintf(string, sizeof string, "%c#\r\n", + escape_char); buffer_append(berr, string, strlen(string)); s = channel_open_message(); buffer_append(berr, s, strlen(s)); @@ -917,6 +1089,8 @@ Supported escape sequences:\r\n\ continue; case 'C': + if (c && c->ctl_chan != -1) + goto noescape; process_cmdline(); continue; @@ -930,12 +1104,15 @@ Supported escape sequences:\r\n\ } } else { /* - * The previous character was not an escape char. Check if this - * is an escape. + * The previous character was not an escape char. + * Check if this is an escape. */ if (last_was_cr && ch == escape_char) { - /* It is. Set the flag and continue to next character. */ - escape_pending = 1; + /* + * It is. Set the flag and continue to + * next character. + */ + *escape_pendingp = 1; continue; } } @@ -952,16 +1129,17 @@ Supported escape sequences:\r\n\ } static void -client_process_input(fd_set * readset) +client_process_input(fd_set *readset) { int len; - char buf[8192]; + char buf[SSH_IOBUFSZ]; /* Read input from stdin. */ if (FD_ISSET(fileno(stdin), readset)) { /* Read as much as possible. */ len = read(fileno(stdin), buf, sizeof(buf)); - if (len < 0 && (errno == EAGAIN || errno == EINTR)) + if (len < 0 && + (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK)) return; /* we'll try again later */ if (len <= 0) { /* @@ -970,7 +1148,8 @@ client_process_input(fd_set * readset) * if it was an error condition. */ if (len < 0) { - snprintf(buf, sizeof buf, "read: %.100s\r\n", strerror(errno)); + snprintf(buf, sizeof buf, "read: %.100s\r\n", + strerror(errno)); buffer_append(&stderr_buffer, buf, strlen(buf)); } /* Mark that we have seen EOF. */ @@ -986,7 +1165,7 @@ client_process_input(fd_set * readset) packet_start(SSH_CMSG_EOF); packet_send(); } - } else if (escape_char == SSH_ESCAPECHAR_NONE) { + } else if (escape_char1 == SSH_ESCAPECHAR_NONE) { /* * Normal successful read, and no escape character. * Just append the data to buffer. @@ -994,18 +1173,19 @@ client_process_input(fd_set * readset) buffer_append(&stdin_buffer, buf, len); } else { /* - * Normal, successful read. But we have an escape character - * and have to process the characters one by one. + * Normal, successful read. But we have an escape + * character and have to process the characters one + * by one. */ - if (process_escapes(&stdin_buffer, &stdout_buffer, - &stderr_buffer, buf, len) == -1) + if (process_escapes(NULL, &stdin_buffer, + &stdout_buffer, &stderr_buffer, buf, len) == -1) return; } } } static void -client_process_output(fd_set * writeset) +client_process_output(fd_set *writeset) { int len; char buf[100]; @@ -1016,14 +1196,16 @@ client_process_output(fd_set * writeset) len = write(fileno(stdout), buffer_ptr(&stdout_buffer), buffer_len(&stdout_buffer)); if (len <= 0) { - if (errno == EINTR || errno == EAGAIN) + if (errno == EINTR || errno == EAGAIN || + errno == EWOULDBLOCK) len = 0; else { /* * An error or EOF was encountered. Put an * error message to stderr buffer. */ - snprintf(buf, sizeof buf, "write stdout: %.50s\r\n", strerror(errno)); + snprintf(buf, sizeof buf, + "write stdout: %.50s\r\n", strerror(errno)); buffer_append(&stderr_buffer, buf, strlen(buf)); quit_pending = 1; return; @@ -1031,7 +1213,6 @@ client_process_output(fd_set * writeset) } /* Consume printed data from the buffer. */ buffer_consume(&stdout_buffer, len); - stdout_bytes += len; } /* Write buffered output to stderr. */ if (FD_ISSET(fileno(stderr), writeset)) { @@ -1039,17 +1220,20 @@ client_process_output(fd_set * writeset) len = write(fileno(stderr), buffer_ptr(&stderr_buffer), buffer_len(&stderr_buffer)); if (len <= 0) { - if (errno == EINTR || errno == EAGAIN) + if (errno == EINTR || errno == EAGAIN || + errno == EWOULDBLOCK) len = 0; else { - /* EOF or error, but can't even print error message. */ + /* + * EOF or error, but can't even print + * error message. + */ quit_pending = 1; return; } } /* Consume printed characters from the buffer. */ buffer_consume(&stderr_buffer, len); - stderr_bytes += len; } } @@ -1068,16 +1252,39 @@ client_process_output(fd_set * writeset) static void client_process_buffered_input_packets(void) { - dispatch_run(DISPATCH_NONBLOCK, &quit_pending, compat20 ? xxx_kex : NULL); + dispatch_run(DISPATCH_NONBLOCK, &quit_pending, + compat20 ? xxx_kex : NULL); } /* scan buf[] for '~' before sending data to the peer */ -static int -simple_escape_filter(Channel *c, char *buf, int len) +/* Helper: allocate a new escape_filter_ctx and fill in its escape char */ +void * +client_new_escape_filter_ctx(int escape_char) { - /* XXX we assume c->extended is writeable */ - return process_escapes(&c->input, &c->output, &c->extended, buf, len); + struct escape_filter_ctx *ret; + + ret = xmalloc(sizeof(*ret)); + ret->escape_pending = 0; + ret->escape_char = escape_char; + return (void *)ret; +} + +/* Free the escape filter context on channel free */ +void +client_filter_cleanup(int cid, void *ctx) +{ + xfree(ctx); +} + +int +client_simple_escape_filter(Channel *c, char *buf, int len) +{ + if (c->extended_usage != CHAN_EXTENDED_WRITE) + return 0; + + return process_escapes(c, &c->input, &c->output, &c->extended, + buf, len); } static void @@ -1085,7 +1292,7 @@ client_channel_closed(int id, void *arg) { channel_cancel_cleanup(id); session_closed = 1; - leave_raw_mode(); + leave_raw_mode(force_tty_flag); } /* @@ -1100,7 +1307,9 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id) { fd_set *readset = NULL, *writeset = NULL; double start_time, total_time; - int max_fd = 0, max_fd2 = 0, len, rekeying = 0, nalloc = 0; + int max_fd = 0, max_fd2 = 0, len, rekeying = 0; + u_int64_t ibytes, obytes; + u_int nalloc = 0; char buf[100]; debug("Entering interactive session."); @@ -1108,7 +1317,7 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id) start_time = get_current_time(); /* Initialize variables. */ - escape_pending = 0; + escape_pending1 = 0; last_was_cr = 1; exit_status = -1; stdin_eof = 0; @@ -1116,8 +1325,6 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id) connection_in = packet_get_connection_in(); connection_out = packet_get_connection_out(); max_fd = MAX(connection_in, connection_out); - if (control_fd != -1) - max_fd = MAX(max_fd, control_fd); if (!compat20) { /* enable nonblocking unless tty */ @@ -1131,11 +1338,8 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id) max_fd = MAX(max_fd, fileno(stdout)); max_fd = MAX(max_fd, fileno(stderr)); } - stdin_bytes = 0; - stdout_bytes = 0; - stderr_bytes = 0; quit_pending = 0; - escape_char = escape_char_arg; + escape_char1 = escape_char_arg; /* Initialize buffers. */ buffer_init(&stdin_buffer); @@ -1148,26 +1352,29 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id) * Set signal handlers, (e.g. to restore non-blocking mode) * but don't overwrite SIG_IGN, matches behaviour from rsh(1) */ + if (signal(SIGHUP, SIG_IGN) != SIG_IGN) + signal(SIGHUP, signal_handler); if (signal(SIGINT, SIG_IGN) != SIG_IGN) signal(SIGINT, signal_handler); if (signal(SIGQUIT, SIG_IGN) != SIG_IGN) signal(SIGQUIT, signal_handler); if (signal(SIGTERM, SIG_IGN) != SIG_IGN) signal(SIGTERM, signal_handler); - if (have_pty) - signal(SIGWINCH, window_change_handler); + signal(SIGWINCH, window_change_handler); if (have_pty) - enter_raw_mode(); + enter_raw_mode(force_tty_flag); if (compat20) { session_ident = ssh2_chan_id; - if (escape_char != SSH_ESCAPECHAR_NONE) + if (escape_char_arg != SSH_ESCAPECHAR_NONE) channel_register_filter(session_ident, - simple_escape_filter); + client_simple_escape_filter, NULL, + client_filter_cleanup, + client_new_escape_filter_ctx(escape_char_arg)); if (session_ident != -1) channel_register_cleanup(session_ident, - client_channel_closed); + client_channel_closed, 0); } else { /* Check if we should immediately send eof on stdin. */ client_check_initial_eof_on_stdin(); @@ -1235,9 +1442,6 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id) /* Buffer input from the connection. */ client_process_net_input(readset); - /* Accept control connections. */ - client_process_control(readset); - if (quit_pending) break; @@ -1251,7 +1455,18 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id) client_process_output(writeset); } - /* Send as much buffered packet data as possible to the sender. */ + if (session_resumed) { + connection_in = packet_get_connection_in(); + connection_out = packet_get_connection_out(); + max_fd = MAX(max_fd, connection_out); + max_fd = MAX(max_fd, connection_in); + session_resumed = 0; + } + + /* + * Send as much buffered packet data as possible to the + * sender. + */ if (FD_ISSET(connection_out, writeset)) packet_write_poll(); } @@ -1263,13 +1478,20 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id) /* Terminate the session. */ /* Stop watching for window change. */ - if (have_pty) - signal(SIGWINCH, SIG_DFL); + signal(SIGWINCH, SIG_DFL); + + if (compat20) { + packet_start(SSH2_MSG_DISCONNECT); + packet_put_int(SSH2_DISCONNECT_BY_APPLICATION); + packet_put_cstring("disconnected by user"); + packet_send(); + packet_write_wait(); + } channel_free_all(); if (have_pty) - leave_raw_mode(); + leave_raw_mode(force_tty_flag); /* restore blocking io */ if (!isatty(fileno(stdin))) @@ -1297,7 +1519,8 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id) * that the connection has been closed. */ if (have_pty && options.log_level != SYSLOG_LEVEL_QUIET) { - snprintf(buf, sizeof buf, "Connection to %.64s closed.\r\n", host); + snprintf(buf, sizeof buf, + "Connection to %.64s closed.\r\n", host); buffer_append(&stderr_buffer, buf, strlen(buf)); } @@ -1310,7 +1533,6 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id) break; } buffer_consume(&stdout_buffer, len); - stdout_bytes += len; } /* Output any buffered data for stderr. */ @@ -1322,7 +1544,6 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id) break; } buffer_consume(&stderr_buffer, len); - stderr_bytes += len; } /* Clear and free any buffers. */ @@ -1333,13 +1554,13 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id) /* Report bytes transferred, and transfer rates. */ total_time = get_current_time() - start_time; - debug("Transferred: stdin %lu, stdout %lu, stderr %lu bytes in %.1f seconds", - stdin_bytes, stdout_bytes, stderr_bytes, total_time); + packet_get_state(MODE_IN, NULL, NULL, NULL, &ibytes); + packet_get_state(MODE_OUT, NULL, NULL, NULL, &obytes); + verbose("Transferred: sent %llu, received %llu bytes, in %.1f seconds", + obytes, ibytes, total_time); if (total_time > 0) - debug("Bytes per second: stdin %.1f, stdout %.1f, stderr %.1f", - stdin_bytes / total_time, stdout_bytes / total_time, - stderr_bytes / total_time); - + verbose("Bytes per second: sent %.1f, received %.1f", + obytes / total_time, ibytes / total_time); /* Return the exit status of the program. */ debug("Exit status %d", exit_status); return exit_status; @@ -1429,8 +1650,7 @@ client_request_forwarded_tcpip(const char *request_type, int rchan) { Channel *c = NULL; char *listen_address, *originator_address; - int listen_port, originator_port; - int sock; + u_short listen_port, originator_port; /* Get rest of the packet */ listen_address = packet_get_string(NULL); @@ -1439,19 +1659,13 @@ client_request_forwarded_tcpip(const char *request_type, int rchan) originator_port = packet_get_int(); packet_check_eom(); - debug("client_request_forwarded_tcpip: listen %s port %d, originator %s port %d", - listen_address, listen_port, originator_address, originator_port); + debug("client_request_forwarded_tcpip: listen %s port %d, " + "originator %s port %d", listen_address, listen_port, + originator_address, originator_port); + + c = channel_connect_by_listen_address(listen_port, + "forwarded-tcpip", originator_address); - sock = channel_connect_by_listen_address(listen_port); - if (sock < 0) { - xfree(originator_address); - xfree(listen_address); - return NULL; - } - c = channel_new("forwarded-tcpip", - SSH_CHANNEL_CONNECTING, sock, sock, -1, - CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_WINDOW_DEFAULT, 0, - originator_address, 1); xfree(originator_address); xfree(listen_address); return c; @@ -1462,12 +1676,13 @@ client_request_x11(const char *request_type, int rchan) { Channel *c = NULL; char *originator; - int originator_port; + u_short originator_port; int sock; if (!options.forward_x11) { error("Warning: ssh server tried X11 forwarding."); - error("Warning: this is probably a break in attempt by a malicious server."); + error("Warning: this is probably a break-in attempt by a " + "malicious server."); return NULL; } originator = packet_get_string(NULL); @@ -1500,20 +1715,65 @@ client_request_agent(const char *request_type, int rchan) if (!options.forward_agent) { error("Warning: ssh server tried agent forwarding."); - error("Warning: this is probably a break in attempt by a malicious server."); + error("Warning: this is probably a break-in attempt by a " + "malicious server."); return NULL; } - sock = ssh_get_authentication_socket(); + sock = ssh_get_authentication_socket(); if (sock < 0) return NULL; c = channel_new("authentication agent connection", SSH_CHANNEL_OPEN, sock, sock, -1, - CHAN_X11_WINDOW_DEFAULT, CHAN_TCP_WINDOW_DEFAULT, 0, + CHAN_X11_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, "authentication agent connection", 1); c->force_drain = 1; return c; } +int +client_request_tun_fwd(int tun_mode, int local_tun, int remote_tun) +{ + Channel *c; + int fd; + + if (tun_mode == SSH_TUNMODE_NO) + return 0; + + if (!compat20) { + error("Tunnel forwarding is not supported for protocol 1"); + return -1; + } + + debug("Requesting tun unit %d in mode %d", local_tun, tun_mode); + + /* Open local tunnel device */ + if ((fd = tun_open(local_tun, tun_mode)) == -1) { + error("Tunnel device open failed."); + return -1; + } + + c = channel_new("tun", SSH_CHANNEL_OPENING, fd, fd, -1, + CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, "tun", 1); + c->datagram = 1; + +#if defined(SSH_TUN_FILTER) + if (options.tun_open == SSH_TUNMODE_POINTOPOINT) + channel_register_filter(c->self, sys_tun_infilter, + sys_tun_outfilter, NULL, NULL); +#endif + + packet_start(SSH2_MSG_CHANNEL_OPEN); + packet_put_cstring("tun@openssh.com"); + packet_put_int(c->self); + packet_put_int(c->local_window_max); + packet_put_int(c->local_maxpacket); + packet_put_int(tun_mode); + packet_put_int(remote_tun); + packet_send(); + + return 0; +} + /* XXXX move to generic input handler */ static void client_input_channel_open(int type, u_int32_t seq, void *ctxt) @@ -1579,20 +1839,27 @@ client_input_channel_req(int type, u_int32_t seq, void *ctxt) debug("client_input_channel_req: channel %d rtype %s reply %d", id, rtype, reply); - c = channel_lookup(id); - if (c == NULL) { - error("client_input_channel_req: channel %d: unknown channel", id); + if (id == -1) { + error("client_input_channel_req: request for channel -1"); + } else if ((c = channel_lookup(id)) == NULL) { + error("client_input_channel_req: channel %d: " + "unknown channel", id); + } else if (strcmp(rtype, "eow@openssh.com") == 0) { + packet_check_eom(); + chan_rcvd_eow(c); } else if (strcmp(rtype, "exit-status") == 0) { exitval = packet_get_int(); - if (id == session_ident) { + if (c->ctl_chan != -1) { + mux_exit_message(c, exitval); + success = 1; + } else if (id == session_ident) { + /* Record exit value of local session */ success = 1; exit_status = exitval; - } else if (c->ctl_fd == -1) { - error("client_input_channel_req: unexpected channel %d", - session_ident); } else { - atomicio(vwrite, c->ctl_fd, &exitval, sizeof(exitval)); - success = 1; + /* Probably for a mux channel that has already closed */ + debug("%s: no sink for exit-status on channel %d", + __func__, id); } packet_check_eom(); } @@ -1625,46 +1892,50 @@ client_input_global_request(int type, u_int32_t seq, void *ctxt) } void -client_session2_setup(int id, int want_tty, int want_subsystem, - const char *term, struct termios *tiop, int in_fd, Buffer *cmd, - dispatch_fn *subsys_repl) +client_session2_setup(int id, int want_tty, int want_subsystem, + const char *term, struct termios *tiop, int in_fd, Buffer *cmd, char **env) { int len; + Channel *c = NULL; debug2("%s: id %d", __func__, id); + if ((c = channel_lookup(id)) == NULL) + fatal("client_session2_setup: channel %d: unknown channel", id); + if (want_tty) { struct winsize ws; - struct termios tio; /* Store window size in the packet. */ if (ioctl(in_fd, TIOCGWINSZ, &ws) < 0) memset(&ws, 0, sizeof(ws)); - channel_request_start(id, "pty-req", 0); + channel_request_start(id, "pty-req", 1); + client_expect_confirm(id, "PTY allocation", 0); packet_put_cstring(term != NULL ? term : ""); - packet_put_int(ws.ws_col); - packet_put_int(ws.ws_row); - packet_put_int(ws.ws_xpixel); - packet_put_int(ws.ws_ypixel); - tio = get_saved_tio(); - tty_make_modes(-1, tiop != NULL ? tiop : &tio); + packet_put_int((u_int)ws.ws_col); + packet_put_int((u_int)ws.ws_row); + packet_put_int((u_int)ws.ws_xpixel); + packet_put_int((u_int)ws.ws_ypixel); + if (tiop == NULL) + tiop = get_saved_tio(); + tty_make_modes(-1, tiop); packet_send(); /* XXX wait for reply */ + c->client_tty = 1; } /* Transfer any environment variables from client to server */ - if (options.num_send_env != 0) { + if (options.num_send_env != 0 && env != NULL) { int i, j, matched; - extern char **environ; char *name, *val; debug("Sending environment."); - for (i = 0; environ && environ[i] != NULL; i++) { + for (i = 0; env[i] != NULL; i++) { /* Split */ - name = xstrdup(environ[i]); + name = xstrdup(env[i]); if ((val = strchr(name, '=')) == NULL) { - free(name); + xfree(name); continue; } *val++ = '\0'; @@ -1678,7 +1949,7 @@ client_session2_setup(int id, int want_tty, int want_subsystem, } if (!matched) { debug3("Ignored env %s", name); - free(name); + xfree(name); continue; } @@ -1687,7 +1958,7 @@ client_session2_setup(int id, int want_tty, int want_subsystem, packet_put_cstring(name); packet_put_cstring(val); packet_send(); - free(name); + xfree(name); } } @@ -1696,22 +1967,21 @@ client_session2_setup(int id, int want_tty, int want_subsystem, if (len > 900) len = 900; if (want_subsystem) { - debug("Sending subsystem: %.*s", len, (u_char*)buffer_ptr(cmd)); - channel_request_start(id, "subsystem", subsys_repl != NULL); - if (subsys_repl != NULL) { - /* register callback for reply */ - /* XXX we assume that client_loop has already been called */ - dispatch_set(SSH2_MSG_CHANNEL_FAILURE, subsys_repl); - dispatch_set(SSH2_MSG_CHANNEL_SUCCESS, subsys_repl); - } + debug("Sending subsystem: %.*s", + len, (u_char*)buffer_ptr(cmd)); + channel_request_start(id, "subsystem", 1); + client_expect_confirm(id, "subsystem", 1); } else { - debug("Sending command: %.*s", len, (u_char*)buffer_ptr(cmd)); - channel_request_start(id, "exec", 0); + debug("Sending command: %.*s", + len, (u_char*)buffer_ptr(cmd)); + channel_request_start(id, "exec", 1); + client_expect_confirm(id, "exec", 1); } packet_put_string(buffer_ptr(cmd), buffer_len(cmd)); packet_send(); } else { - channel_request_start(id, "shell", 0); + channel_request_start(id, "shell", 1); + client_expect_confirm(id, "shell", 1); packet_send(); } } @@ -1730,6 +2000,8 @@ client_init_dispatch_20(void) dispatch_set(SSH2_MSG_CHANNEL_OPEN_FAILURE, &channel_input_open_failure); dispatch_set(SSH2_MSG_CHANNEL_REQUEST, &client_input_channel_req); dispatch_set(SSH2_MSG_CHANNEL_WINDOW_ADJUST, &channel_input_window_adjust); + dispatch_set(SSH2_MSG_CHANNEL_SUCCESS, &channel_input_status_confirm); + dispatch_set(SSH2_MSG_CHANNEL_FAILURE, &channel_input_status_confirm); dispatch_set(SSH2_MSG_GLOBAL_REQUEST, &client_input_global_request); /* rekeying */ @@ -1739,6 +2011,7 @@ client_init_dispatch_20(void) dispatch_set(SSH2_MSG_REQUEST_FAILURE, &client_global_request_reply); dispatch_set(SSH2_MSG_REQUEST_SUCCESS, &client_global_request_reply); } + static void client_init_dispatch_13(void) { @@ -1758,6 +2031,7 @@ client_init_dispatch_13(void) dispatch_set(SSH_SMSG_X11_OPEN, options.forward_x11 ? &x11_input_open : &deny_input_open); } + static void client_init_dispatch_15(void) { @@ -1765,6 +2039,7 @@ client_init_dispatch_15(void) dispatch_set(SSH_MSG_CHANNEL_CLOSE, &channel_input_ieof); dispatch_set(SSH_MSG_CHANNEL_CLOSE_CONFIRMATION, & channel_input_oclose); } + static void client_init_dispatch(void) { @@ -1780,9 +2055,9 @@ client_init_dispatch(void) void cleanup_exit(int i) { - leave_raw_mode(); + leave_raw_mode(force_tty_flag); leave_non_blocking(); - if (options.control_path != NULL && control_fd != -1) + if (options.control_path != NULL && muxserver_sock != -1) unlink(options.control_path); _exit(i); }