clientloop.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
* All rights reserved
* The main loop for the interactive session (client side).
*
* As far as I am concerned, the code I have written for this software
* can be used freely for any purpose. Any derived versions of this
* software must be clearly marked as such, and if the derived work is
* incompatible with the protocol description in the RFC file, it must be
* called by a name other than "ssh" or "Secure Shell".
*
*
* Copyright (c) 1999 Theo de Raadt. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*
* SSH2 support added by Markus Friedl.
* Copyright (c) 1999, 2000, 2001 Markus Friedl. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "includes.h"
RCSID("$OpenBSD: clientloop.c,v 1.104 2002/08/22 19:38:42 stevesk Exp $");
#pragma ident "%Z%%M% %I% %E% SMI"
#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 "kex.h"
#include "log.h"
#include "readconf.h"
#include "clientloop.h"
#include "authfd.h"
#include "atomicio.h"
#include "sshtty.h"
#include "misc.h"
#include "readpass.h"
/* import options */
extern int stdin_null_flag;
/*
* Name of the host we are connecting to. This is the name given on the
* command line, or the HostName specified for the user-supplied name in a
* configuration file.
*/
extern char *host;
/*
* 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
* window size to be sent to the server a little later. This is volatile
* because this is updated in a signal handler.
*/
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. */
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 */
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 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 void client_init_dispatch(void);
int session_ident = -1;
/*XXX*/
/* Restores stdin to blocking mode. */
static void
leave_non_blocking(void)
{
if (in_non_blocking_mode) {
in_non_blocking_mode = 0;
}
}
/* Puts stdin terminal in non-blocking mode. */
static void
enter_non_blocking(void)
{
in_non_blocking_mode = 1;
}
/*
* Signal handler for the window change signal (SIGWINCH). This just sets a
* flag indicating that the window has changed.
*/
static void
window_change_handler(int sig)
{
}
/*
* Signal handler for signals that cause the program to terminate. These
* signals must be trapped to restore terminal modes.
*/
static void
signal_handler(int sig)
{
quit_pending = 1;
}
/*
* Returns current time in seconds from Jan 1, 1970 with the maximum
* available resolution.
*/
static double
get_current_time(void)
{
}
/*
* 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
*/
static void
{
int len;
char buf[1];
/*
* mark that we have seen an EOF and send an EOF message to the
* server. Otherwise, we try to read a single character; it appears
* read for this descriptor, which means that we never get EOF. This
*/
if (stdin_null_flag) {
/* Fake EOF on stdin. */
debug("Sending eof.");
stdin_eof = 1;
packet_send();
} else {
/* Check for immediate EOF on stdin. */
if (len == 0) {
/* EOF. Record that we have seen it and send EOF to server. */
debug("Sending eof.");
stdin_eof = 1;
packet_send();
} else if (len > 0) {
/*
* Got data. We must store the data in the buffer,
* and also process it as an escape character if
* appropriate.
*/
escape_pending = 1;
else
}
}
}
/*
* Make packets from buffered stdin data, and buffer them for sending to the
* connection.
*/
static void
{
/* Send buffered stdin data to the server. */
while (buffer_len(&stdin_buffer) > 0 &&
/* Keep the packets at reasonable size. */
if (len > packet_get_maxsize())
len = packet_get_maxsize();
packet_send();
stdin_bytes += len;
/* If we have a pending EOF, send it now. */
packet_send();
}
}
}
/*
* Checks if the client window has changed, and sends a packet about it to
* the server if so. The actual change is detected elsewhere (by a software
* interrupt on Unix); this just checks the flag and sends a message if
* appropriate.
*/
static void
{
return;
/** XXX race */
return;
debug2("client_check_window_change: changed");
if (compat20) {
packet_send();
} else {
packet_send();
}
}
/*
* Waits until the client can do something (some data becomes available on
* one of the file descriptors).
*/
static void
{
/* Add any selections by the channel mechanism. */
if (!compat20) {
/* Read from the connection, unless our buffers are full. */
/*
* Read from stdin, unless we have seen EOF or have very much
* buffered data to send to the server.
*/
if (!stdin_eof && packet_not_very_much_data_to_write())
if (buffer_len(&stdout_buffer) > 0)
if (buffer_len(&stderr_buffer) > 0)
} else {
/* channel_prepare_select could have closed the last channel */
if (session_closed && !channel_still_open() &&
/* clear mask since we did not call select() */
return;
} else {
}
}
/* Select server connection if have data to write to the server. */
if (packet_have_data_to_write())
/*
* Wait for something to happen. This will suspend the process until
* some selected descriptor can be read, written, or has some other
* event pending. Note: if you want to implement SSH_MSG_IGNORE
* messages to fool traffic analysis, this might be the place to do
* it: just have a random timeout for the select, and send a random
* SSH_MSG_IGNORE packet when the timeout expires.
*/
char buf[100];
/*
* We have to clear the select masks, because we return.
* We have to return, because the mainloop checks for the flags
* set by the signal handlers.
*/
return;
/* Note: we might still have data in the buffers. */
quit_pending = 1;
}
}
static void
{
/* Flush stdout and stderr buffers. */
if (buffer_len(bout) > 0)
if (buffer_len(berr) > 0)
/*
* Free (and clear) the buffer to reduce the amount of data that gets
* written to swap.
*/
/* Save old window size. */
/* Send the suspend signal to the program itself. */
/* Check if the window size has changed. */
/* OK, we have been continued by the user. Reinitialize buffers. */
}
static void
{
int len;
char buf[8192];
/*
* Read input from the server, and add any such data to the buffer of
* the packet subsystem.
*/
/* Read as much as possible. */
if (len == 0) {
/* Received EOF. The remote host has closed the connection. */
gettext("Connection to %.300s closed "
"by remote host.\r\n"),
host);
quit_pending = 1;
return;
}
/*
* There is a kernel bug on Solaris that causes select to
* sometimes wake up even though there is no data available.
*/
len = 0;
if (len < 0) {
/* An error has encountered. Perhaps there is a network problem. */
gettext("Read from remote host "
"%.300s: %.100s\r\n"),
quit_pending = 1;
return;
}
}
}
static void
process_cmdline(void)
{
void (*handler)(int);
char *s, *cmd;
int local = 0;
if (s == NULL)
goto out;
while (*s && isspace(*s))
s++;
if (*s == 0)
goto out;
log("Invalid command.");
goto out;
}
if (s[1] == 'L')
local = 1;
log("Not supported for SSH protocol version 1.");
goto out;
}
s += 2;
while (*s && isspace(*s))
s++;
if (sscanf(s, "%5[0-9]:%255[^:]:%5[0-9]",
sscanf(s, "%5[0-9]/%255[^/]/%5[0-9]",
log("Bad forwarding specification.");
goto out;
}
log("Bad forwarding port(s).");
goto out;
}
if (local) {
log("Port forwarding failed.");
goto out;
}
} else
log("Forwarding port.");
out:
if (cmd)
}
/* process the characters one by one */
static int
{
char string[1536];
int bytes = 0;
u_int i;
char *s;
for (i = 0; i < len; i++) {
/* Get one character at a time. */
if (escape_pending) {
/* We have previously seen an escape character. */
/* Clear the flag now. */
escape_pending = 0;
/* Process the escaped character. */
switch (ch) {
case '.':
/* Terminate the connection. */
quit_pending = 1;
return -1;
case 'Z' - 64:
/* Suspend the program. */
/* Print a message to that effect to the user. */
gettext("%c^Z [suspend ssh]\r\n"),
/* Restore terminal modes and suspend. */
/* We have been continued. */
continue;
case 'B':
if (compat20) {
gettext("%cB [sent break]\r\n"),
"break", 0);
packet_put_int(1000);
packet_send();
}
continue;
case 'R':
if (compat20) {
if (datafellows & SSH_BUG_NOREKEY)
log("Server does not support re-keying");
else
need_rekeying = 1;
}
continue;
case '&':
/*
* Detach the program (continue to serve connections,
* but put in background and no more new connections).
*/
/* Restore tty modes. */
/* Stop listening for new connections. */
gettext("%c& [backgrounded]\n"),
/* Fork into background. */
if (pid < 0) {
continue;
}
if (pid != 0) { /* This is the parent. */
/* The parent just exits. */
exit(0);
}
/* The child continues serving connections. */
if (compat20) {
/* fake EOF on stdin */
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.
*/
packet_send();
/* Close stdin. */
stdin_eof = 1;
if (buffer_len(bin) == 0) {
packet_send();
}
}
continue;
case '?':
"%c?\r\n\
Supported escape sequences:\r\n\
%c. - terminate connection\r\n\
%cB - send break\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"),
continue;
case '#':
s = channel_open_message();
xfree(s);
continue;
case 'C':
continue;
default:
if (ch != escape_char) {
bytes++;
}
/* Escaped characters fall through here */
break;
}
} else {
/*
* The previous character was not an escape char. Check if this
* is an escape.
*/
/* It is. Set the flag and continue to next character. */
escape_pending = 1;
continue;
}
}
/*
* Normal character. Record whether it was a newline,
* and append it to the buffer.
*/
bytes++;
}
return bytes;
}
static void
{
int len;
char buf[8192];
/* Read input from stdin. */
/* Read as much as possible. */
return; /* we'll try again later */
if (len <= 0) {
/*
* Received EOF or error. They are treated
* similarly, except that an error message is printed
* if it was an error condition.
*/
if (len < 0) {
}
/* Mark that we have seen EOF. */
stdin_eof = 1;
/*
* Send an EOF message to the server unless there is
* data in the buffer. If there is data in the
* buffer, no message will be sent now. Code
* elsewhere will send the EOF when the buffer
* becomes empty if stdin_eof is set.
*/
if (buffer_len(&stdin_buffer) == 0) {
packet_send();
}
} else if (escape_char == SSH_ESCAPECHAR_NONE) {
/*
* Normal successful read, and no escape character.
* Just append the data to buffer.
*/
} else {
/*
* Normal, successful read. But we have an escape character
* and have to process the characters one by one.
*/
return;
}
}
}
static void
{
int len;
char buf[100];
/* Write buffered output to stdout. */
/* Write as much data as possible. */
if (len <= 0) {
len = 0;
else {
/*
* An error or EOF was encountered. Put an
* error message to stderr buffer.
*/
quit_pending = 1;
return;
}
}
/* Consume printed data from the buffer. */
stdout_bytes += len;
}
/* Write buffered output to stderr. */
/* Write as much data as possible. */
if (len <= 0) {
len = 0;
else {
/* EOF or error, but can't even print error message. */
quit_pending = 1;
return;
}
}
/* Consume printed characters from the buffer. */
stderr_bytes += len;
}
}
/*
* Get packets from the connection input buffer, and process them as long as
* there are packets available.
*
* Any unknown packets received during the actual
* session cause the session to terminate. This is
* intended to make debugging easier since no
* confirmations are sent. Any compatible protocol
* extensions must be negotiated during the
* preparatory phase.
*/
static void
{
}
/* scan buf[] for '~' before sending data to the peer */
static int
{
/* XXX we assume c->extended is writeable */
}
static void
{
if (id != session_ident)
error("client_channel_closed: id %d != session_ident %d",
id, session_ident);
session_closed = 1;
if (in_raw_mode())
}
/*
* Implements the interactive session with the server. This is called after
* the user has been authenticated, and a command has been started on the
* remote host. If escape_char != SSH_ESCAPECHAR_NONE, it is the character
* used as an escape character for terminating or suspending the session.
*/
int
{
double start_time, total_time;
char buf[100];
debug("Entering interactive session.");
/* Initialize variables. */
escape_pending = 0;
last_was_cr = 1;
exit_status = -1;
exit_status = 0;
stdin_eof = 0;
if (!compat20) {
/* enable nonblocking unless tty */
}
stdin_bytes = 0;
stdout_bytes = 0;
stderr_bytes = 0;
quit_pending = 0;
/* Initialize buffers. */
/*
* Set signal handlers to restore non-blocking mode, but
* don't overwrite SIG_IGN - matches behavious from rsh(1).
*/
if (have_pty)
if (have_pty)
if (compat20) {
if (escape_char != SSH_ESCAPECHAR_NONE)
if (session_ident != -1)
} else {
/* Check if we should immediately send eof on stdin. */
}
/* Main loop of the client for the interactive session mode. */
while (!quit_pending) {
/* Process buffered packets sent by the server. */
break;
if (rekeying) {
debug("rekeying in progress");
} else {
/*
* Make packets of buffered stdin data, and buffer
* them for sending to the server.
*/
if (!compat20)
/*
* Make packets from buffered channel data, and
* enqueue them for sending to the server.
*/
/*
* Check if the window size has changed, and buffer a
* message about it to the server if so.
*/
if (quit_pending)
break;
}
/*
* Wait until we have something to do (something becomes
* available on one of the descriptors).
*/
if (quit_pending)
break;
/* Do channel operations unless rekeying in progress. */
if (!rekeying) {
if (need_rekeying) {
debug("user requests rekeying");
need_rekeying = 0;
}
}
/* Buffer input from the connection. */
if (quit_pending)
break;
if (!compat20) {
/* Buffer data from stdin */
/*
* Process output to stdout and stderr. Output to
* the connection is processed elsewhere (above).
*/
}
/* Send as much buffered packet data as possible to the sender. */
}
if (readset)
if (writeset)
/* Terminate the session. */
/* Stop watching for window change. */
if (have_pty)
if (have_pty)
/* restore blocking io */
if (received_signal) {
if (in_non_blocking_mode) /* XXX */
}
/*
* In interactive mode (with pseudo tty) display a message indicating
* that the connection has been closed.
*/
gettext("Connection to %.64s closed.\r\n"),
host);
}
/* Output any buffered data for stdout. */
while (buffer_len(&stdout_buffer) > 0) {
if (len <= 0) {
error("Write failed flushing stdout buffer.");
break;
}
stdout_bytes += len;
}
/* Output any buffered data for stderr. */
while (buffer_len(&stderr_buffer) > 0) {
if (len <= 0) {
error("Write failed flushing stderr buffer.");
break;
}
stderr_bytes += len;
}
/* Clear and free any buffers. */
/* Report bytes transferred, and transfer rates. */
debug("Transferred: stdin %lu, stdout %lu, stderr %lu bytes in %.1f seconds",
if (total_time > 0)
debug("Bytes per second: stdin %.1f, stdout %.1f, stderr %.1f",
/* Return the exit status of the program. */
return exit_status;
}
/*********/
static void
{
}
static void
{
}
static void
{
/* Acknowledge the exit. */
packet_send();
/*
* Must wait for packet to be sent since we are
* exiting the loop.
*/
/* Flag that we want to exit. */
quit_pending = 1;
}
static Channel *
{
char *listen_address, *originator_address;
int listen_port, originator_port;
int sock;
/* Get rest of the packet */
debug("client_request_forwarded_tcpip: listen %s port %d, originator %s port %d",
if (sock < 0) {
return NULL;
}
c = channel_new("forwarded-tcpip",
return c;
}
static Channel *
{
char *originator;
int 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.");
return NULL;
}
if (datafellows & SSH_BUG_X11FWD) {
debug2("buggy server: x11 request w/o originator_port");
originator_port = 0;
} else {
}
/* XXX check permission */
sock = x11_connect_display();
if (sock < 0)
return NULL;
c = channel_new("x11",
c->force_drain = 1;
return c;
}
static Channel *
{
int sock;
if (!options.forward_agent) {
error("Warning: ssh server tried agent forwarding.");
error("Warning: this is probably a break in attempt by a malicious server.");
return NULL;
}
if (sock < 0)
return NULL;
c = channel_new("authentication agent connection",
c->force_drain = 1;
return c;
}
/* XXXX move to generic input handler */
static void
{
char *ctype;
int rchan;
rchan = packet_get_int();
rwindow = packet_get_int();
rmaxpack = packet_get_int();
debug("client_input_channel_open: ctype %s rchan %d win %d max %d",
}
/* XXX duplicate : */
if (c != NULL) {
c->remote_window = rwindow;
c->remote_maxpacket = rmaxpack;
if (c->type != SSH_CHANNEL_CONNECTING) {
packet_put_int(c->remote_id);
packet_put_int(c->self);
packet_send();
}
} else {
if (!(datafellows & SSH_BUG_OPENFAILURE)) {
packet_put_cstring("open failed");
packet_put_cstring("");
}
packet_send();
}
}
static void
{
char *rtype;
id = packet_get_int();
reply = packet_get_char();
debug("client_input_channel_req: channel %d rtype %s reply %d",
if (session_ident == -1) {
} else if (id != session_ident) {
error("client_input_channel_req: channel %d: wrong channel: %d",
session_ident, id);
}
c = channel_lookup(id);
if (c == NULL) {
success = 1;
}
if (reply) {
packet_put_int(c->remote_id);
packet_send();
}
}
static void
{
char *rtype;
int want_reply;
int success = 0;
if (want_reply) {
packet_send();
}
}
static void
client_init_dispatch_20(void)
{
/* rekeying */
/* global request reply messages */
}
static void
client_init_dispatch_13(void)
{
}
static void
client_init_dispatch_15(void)
{
}
static void
client_init_dispatch(void)
{
if (compat20)
else if (compat13)
else
}