X-Git-Url: http://andersk.mit.edu/gitweb/openssh.git/blobdiff_plain/72becb621a33e6d4cf3455196989bf5487e4dc65..e657a401ea0cc079caa678761b02701dd5f8b8c1:/clientloop.c diff --git a/clientloop.c b/clientloop.c index 37cecf5a..eca87777 100644 --- a/clientloop.c +++ b/clientloop.c @@ -1,4 +1,4 @@ -/* $OpenBSD: clientloop.c,v 1.196 2008/06/12 04:06:00 djm Exp $ */ +/* $OpenBSD: clientloop.c,v 1.215 2009/11/17 05:31:44 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -107,9 +107,9 @@ #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; @@ -147,12 +147,11 @@ static 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). */ @@ -162,6 +161,8 @@ static int session_closed = 0; /* In SSH2: login session closed. */ static void client_init_dispatch(void); int session_ident = -1; +int session_resumed = 0; + /* Track escape per proto2 channel */ struct escape_filter_ctx { int escape_pending; @@ -392,7 +393,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); @@ -434,7 +438,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); @@ -491,13 +494,13 @@ client_global_request_reply(int type, u_int32_t seq, void *ctxt) xfree(gc); } - keep_alive_timeouts = 0; + packet_set_alive_timeouts(0); } static void server_alive_check(void) { - if (++keep_alive_timeouts > options.server_alive_count_max) { + if (packet_inc_alive_timeouts() > options.server_alive_count_max) { logit("Timeout, server not responding."); cleanup_exit(255); } @@ -601,9 +604,11 @@ client_suspend_self(Buffer *bin, Buffer *bout, Buffer *berr) { /* 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(); @@ -632,8 +637,8 @@ client_suspend_self(Buffer *bin, Buffer *bout, Buffer *berr) static void 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 @@ -641,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; @@ -654,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; @@ -729,14 +743,14 @@ client_expect_confirm(int id, const char *request, int do_close) void client_register_global_confirm(global_confirm_cb *cb, void *ctx) { - struct global_confirm *gc, *first_gc; + struct global_confirm *gc, *last_gc; /* Coalesce identical callbacks */ - first_gc = TAILQ_FIRST(&global_confirms); - if (first_gc && first_gc->cb == cb && first_gc->ctx == ctx) { - if (++first_gc->ref_count >= INT_MAX) - fatal("%s: first_gc->ref_count = %d", - __func__, first_gc->ref_count); + 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; } @@ -753,8 +767,8 @@ process_cmdline(void) void (*handler)(int); char *s, *cmd, *cancel_host; int delete = 0; - int local = 0; - u_short cancel_port; + int local = 0, remote = 0, dynamic = 0; + int cancel_port; Forward fwd; bzero(&fwd, sizeof(fwd)); @@ -778,6 +792,8 @@ process_cmdline(void) "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) @@ -797,17 +813,22 @@ 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; } @@ -825,17 +846,17 @@ process_cmdline(void) cancel_port = a2port(cancel_host); cancel_host = NULL; } - if (cancel_port == 0) { + if (cancel_port <= 0) { logit("Bad forwarding close port"); goto out; } channel_request_rforward_cancel(cancel_host, cancel_port); } else { - if (!parse_forward(&fwd, s)) { + if (!parse_forward(&fwd, s, dynamic, remote)) { logit("Bad forwarding specification."); goto out; } - if (local) { + 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) { @@ -932,8 +953,7 @@ process_escapes(Channel *c, Buffer *bin, Buffer *bout, Buffer *berr, strlen(string)); continue; } - /* Suspend the program. */ - /* Print a message to that effect to the user. */ + /* Suspend the program. Inform the user */ snprintf(string, sizeof string, "%c^Z [suspend ssh]\r\n", escape_char); buffer_append(berr, string, strlen(string)); @@ -960,7 +980,8 @@ process_escapes(Channel *c, Buffer *bin, Buffer *bout, Buffer *berr, 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; } @@ -970,8 +991,9 @@ process_escapes(Channel *c, Buffer *bin, Buffer *bout, Buffer *berr, if (c && c->ctl_fd != -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(); @@ -1000,9 +1022,9 @@ process_escapes(Channel *c, Buffer *bin, Buffer *bout, Buffer *berr, 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); @@ -1023,7 +1045,6 @@ process_escapes(Channel *c, Buffer *bin, Buffer *bout, Buffer *berr, Supported escape sequences:\r\n\ %c. - terminate session\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# - list forwarded connections\r\n\ %c? - this message\r\n\ @@ -1032,8 +1053,7 @@ Supported escape sequences:\r\n\ 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\ @@ -1068,6 +1088,8 @@ Supported escape sequences:\r\n\ continue; case 'C': + if (c && c->ctl_fd != -1) + goto noescape; process_cmdline(); continue; @@ -1081,11 +1103,14 @@ 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. */ + /* + * It is. Set the flag and continue to + * next character. + */ *escape_pendingp = 1; continue; } @@ -1106,13 +1131,14 @@ static void 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) { /* @@ -1121,7 +1147,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. */ @@ -1145,8 +1172,9 @@ 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(NULL, &stdin_buffer, &stdout_buffer, &stderr_buffer, buf, len) == -1) @@ -1167,14 +1195,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; @@ -1182,7 +1212,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)) { @@ -1190,17 +1219,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; } } @@ -1219,7 +1251,8 @@ 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 */ @@ -1236,6 +1269,13 @@ client_new_escape_filter_ctx(int 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) { @@ -1267,6 +1307,7 @@ 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; + u_int64_t ibytes, obytes; u_int nalloc = 0; char buf[100]; @@ -1298,9 +1339,6 @@ 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_char1 = escape_char_arg; @@ -1333,6 +1371,7 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id) if (escape_char_arg != SSH_ESCAPECHAR_NONE) channel_register_filter(session_ident, 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, @@ -1423,7 +1462,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(); } @@ -1437,6 +1487,14 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id) /* Stop watching for window change. */ 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) @@ -1468,7 +1526,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)); } @@ -1481,7 +1540,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. */ @@ -1493,7 +1551,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. */ @@ -1504,13 +1561,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; @@ -1600,7 +1657,7 @@ client_request_forwarded_tcpip(const char *request_type, int rchan) { Channel *c = NULL; char *listen_address, *originator_address; - int listen_port, originator_port; + u_short listen_port, originator_port; /* Get rest of the packet */ listen_address = packet_get_string(NULL); @@ -1626,12 +1683,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); @@ -1664,7 +1722,8 @@ 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(); @@ -1688,7 +1747,7 @@ client_request_tun_fwd(int tun_mode, int local_tun, int remote_tun) return 0; if (!compat20) { - error("Tunnel forwarding is not support for protocol 1"); + error("Tunnel forwarding is not supported for protocol 1"); return -1; } @@ -1707,7 +1766,7 @@ client_request_tun_fwd(int tun_mode, int local_tun, int remote_tun) #if defined(SSH_TUN_FILTER) if (options.tun_open == SSH_TUNMODE_POINTOPOINT) channel_register_filter(c->self, sys_tun_infilter, - sys_tun_outfilter); + sys_tun_outfilter, NULL, NULL); #endif packet_start(SSH2_MSG_CHANNEL_OPEN); @@ -1790,28 +1849,31 @@ client_input_channel_req(int type, u_int32_t seq, void *ctxt) 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); + 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_fd != -1) { + /* Dispatch to mux client */ + atomicio(vwrite, c->ctl_fd, &exitval, sizeof(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) { + } else { error("client_input_channel_req: unexpected channel %d", session_ident); - } else { - atomicio(vwrite, c->ctl_fd, &exitval, sizeof(exitval)); - success = 1; } packet_check_eom(); } if (reply) { packet_start(success ? SSH2_MSG_CHANNEL_SUCCESS : SSH2_MSG_CHANNEL_FAILURE); - packet_put_int(id); + packet_put_int(c->remote_id); packet_send(); } xfree(rtype); @@ -1956,6 +2018,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) { @@ -1975,6 +2038,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) { @@ -1982,6 +2046,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) {