Initial revision
diff --git a/clientloop.c b/clientloop.c
new file mode 100644
index 0000000..43373b7
--- /dev/null
+++ b/clientloop.c
@@ -0,0 +1,924 @@
+/*
+
+clientloop.c
+
+Author: Tatu Ylonen <ylo@cs.hut.fi>
+
+Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
+                   All rights reserved
+
+
+Created: Sat Sep 23 12:23:57 1995 ylo
+
+The main loop for the interactive session (client side).
+
+*/
+
+#include "includes.h"
+RCSID("$Id: clientloop.c,v 1.1 1999/10/27 03:42:44 damien Exp $");
+
+#include "xmalloc.h"
+#include "ssh.h"
+#include "packet.h"
+#include "buffer.h"
+#include "authfd.h"
+
+/* Flag indicating whether quiet mode is on. */
+extern int quiet_flag;
+
+/* Flag indicating that stdin should be redirected from /dev/null. */
+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 int received_window_change_signal = 0;
+
+/* Terminal modes, as saved by enter_raw_mode. */
+static struct termios saved_tio;
+
+/* Flag indicating whether we are in raw mode.  This is used by enter_raw_mode
+   and leave_raw_mode. */
+static int in_raw_mode = 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 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 Buffer stdin_buffer;  /* Buffer for stdin data. */
+static Buffer stdout_buffer; /* Buffer for stdout data. */
+static Buffer stderr_buffer; /* Buffer for stderr data. */
+static unsigned int buffer_high; /* Soft max buffer size. */
+static int max_fd; /* Maximum file descriptor number in select(). */
+static int connection_in; /* Connection to server (input). */
+static int connection_out; /* Connection to server (output). */
+static unsigned long stdin_bytes, stdout_bytes, stderr_bytes;
+static int quit_pending; /* Set to non-zero to quit the client loop. */
+static int escape_char; /* Escape character. */
+
+/* Returns the user\'s terminal to normal mode if it had been put in raw 
+   mode. */
+
+void leave_raw_mode()
+{
+  if (!in_raw_mode)
+    return;
+  in_raw_mode = 0;
+  if (tcsetattr(fileno(stdin), TCSADRAIN, &saved_tio) < 0)
+    perror("tcsetattr");
+
+  fatal_remove_cleanup((void (*)(void *))leave_raw_mode, NULL);
+}
+
+/* Puts the user\'s terminal in raw mode. */
+
+void enter_raw_mode()
+{
+  struct termios tio;
+
+  if (tcgetattr(fileno(stdin), &tio) < 0)
+    perror("tcgetattr");
+  saved_tio = tio;
+  tio.c_iflag |= IGNPAR;
+  tio.c_iflag &= ~(ISTRIP|INLCR|IGNCR|ICRNL|IXON|IXANY|IXOFF);
+  tio.c_lflag &= ~(ISIG|ICANON|ECHO|ECHOE|ECHOK|ECHONL);
+#ifdef IEXTEN
+  tio.c_lflag &= ~IEXTEN;
+#endif /* IEXTEN */
+  tio.c_oflag &= ~OPOST;
+  tio.c_cc[VMIN] = 1;
+  tio.c_cc[VTIME] = 0;
+  if (tcsetattr(fileno(stdin), TCSADRAIN, &tio) < 0)
+    perror("tcsetattr");
+  in_raw_mode = 1;
+
+  fatal_add_cleanup((void (*)(void *))leave_raw_mode, NULL);
+}  
+
+/* Puts stdin terminal in non-blocking mode. */
+
+/* Restores stdin to blocking mode. */
+
+void leave_non_blocking()
+{
+  if (in_non_blocking_mode)
+    {
+      (void)fcntl(fileno(stdin), F_SETFL, 0);
+      in_non_blocking_mode = 0;
+      fatal_remove_cleanup((void (*)(void *))leave_non_blocking, NULL);
+    }
+}
+
+void enter_non_blocking()
+{
+  in_non_blocking_mode = 1;
+  (void)fcntl(fileno(stdin), F_SETFL, O_NONBLOCK);
+  fatal_add_cleanup((void (*)(void *))leave_non_blocking, NULL);
+}
+
+/* Signal handler for the window change signal (SIGWINCH).  This just
+   sets a flag indicating that the window has changed. */
+
+void window_change_handler(int sig)
+{
+  received_window_change_signal = 1;
+  signal(SIGWINCH, window_change_handler);
+}
+
+/* Signal handler for signals that cause the program to terminate.  These
+   signals must be trapped to restore terminal modes. */
+
+void signal_handler(int sig)
+{
+  if (in_raw_mode)
+    leave_raw_mode();
+  if (in_non_blocking_mode)
+    leave_non_blocking();
+  channel_stop_listening();
+  packet_close();
+  fatal("Killed by signal %d.", sig);
+}
+
+/* Returns current time in seconds from Jan 1, 1970 with the maximum available
+   resolution. */
+
+double get_current_time()
+{
+  struct timeval tv;
+  gettimeofday(&tv, NULL);
+  return (double)tv.tv_sec + (double)tv.tv_usec / 1000000.0;
+}
+
+/* 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 not appear to wake up when redirecting from /dev/null. */
+
+void client_check_initial_eof_on_stdin()
+{
+  int len;
+  char buf[1];
+
+  /* If standard input is to be "redirected from /dev/null", we simply
+     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 that for some
+     files, such /dev/null, select() never wakes up for read for this
+     descriptor, which means that we never get EOF.  This way we will get
+     the EOF if stdin comes from /dev/null or similar. */
+  if (stdin_null_flag)
+    {
+      /* Fake EOF on stdin. */
+      debug("Sending eof.");
+      stdin_eof = 1;
+      packet_start(SSH_CMSG_EOF);
+      packet_send();
+    }
+  else
+    {
+      /* Enter non-blocking mode for stdin. */
+      enter_non_blocking();
+
+      /* 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. */
+	  debug("Sending eof.");
+	  stdin_eof = 1;
+	  packet_start(SSH_CMSG_EOF);
+	  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. */
+	    if ((unsigned char)buf[0] == escape_char)
+	      escape_pending = 1;
+	    else
+	      {
+		buffer_append(&stdin_buffer, buf, 1);
+		stdin_bytes += 1;
+	      }
+	  }
+      
+      /* Leave non-blocking mode. */
+      leave_non_blocking();
+    }
+}
+
+/* Get packets from the connection input buffer, and process them as long
+   as there are packets available. */
+
+void client_process_buffered_input_packets()
+{
+  int type;
+  char *data;
+  unsigned int data_len;
+  int payload_len;
+
+  /* Process any buffered packets from the server. */
+  while (!quit_pending && (type = packet_read_poll(&payload_len)) != SSH_MSG_NONE)
+    {
+      switch (type)
+	{
+	  
+	case SSH_SMSG_STDOUT_DATA:
+	  data = packet_get_string(&data_len);
+	  packet_integrity_check(payload_len, 4 + data_len, type);
+	  buffer_append(&stdout_buffer, data, data_len);
+	  stdout_bytes += data_len;
+	  memset(data, 0, data_len);
+	  xfree(data);
+	  break;
+
+	case SSH_SMSG_STDERR_DATA:
+	  data = packet_get_string(&data_len);
+	  packet_integrity_check(payload_len, 4 + data_len, type);
+	  buffer_append(&stderr_buffer, data, data_len);
+	  stdout_bytes += data_len;
+	  memset(data, 0, data_len);
+	  xfree(data);
+	  break;
+
+	case SSH_SMSG_EXITSTATUS:
+	  packet_integrity_check(payload_len, 4, type);
+	  exit_status = packet_get_int();
+	  /* Acknowledge the exit. */
+	  packet_start(SSH_CMSG_EXIT_CONFIRMATION);
+	  packet_send();
+	  /* Must wait for packet to be sent since we are exiting the
+	     loop. */
+	  packet_write_wait();
+	  /* Flag that we want to exit. */
+	  quit_pending = 1;
+	  break;
+
+	case SSH_SMSG_X11_OPEN:
+	  x11_input_open(payload_len);
+	  break;
+
+	case SSH_MSG_PORT_OPEN:
+	  channel_input_port_open(payload_len);
+	  break;
+
+	case SSH_SMSG_AGENT_OPEN:
+	  packet_integrity_check(payload_len, 4, type);
+	  auth_input_open_request();
+	  break;
+
+	case SSH_MSG_CHANNEL_OPEN_CONFIRMATION:
+	  packet_integrity_check(payload_len, 4 + 4, type);
+	  channel_input_open_confirmation();
+	  break;
+
+	case SSH_MSG_CHANNEL_OPEN_FAILURE:
+	  packet_integrity_check(payload_len, 4, type);
+	  channel_input_open_failure();
+	  break;
+
+	case SSH_MSG_CHANNEL_DATA:
+	  channel_input_data(payload_len);
+	  break;
+
+	case SSH_MSG_CHANNEL_CLOSE:
+	  packet_integrity_check(payload_len, 4, type);
+	  channel_input_close();
+	  break;
+
+	case SSH_MSG_CHANNEL_CLOSE_CONFIRMATION:
+	  packet_integrity_check(payload_len, 4, type);
+	  channel_input_close_confirmation();
+	  break;
+
+	default:
+	  /* 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. */
+	  packet_disconnect("Protocol error during session: type %d",
+			    type);
+	}
+    }
+}
+
+/* Make packets from buffered stdin data, and buffer them for sending to
+   the connection. */
+
+void client_make_packets_from_stdin_data()
+{
+  unsigned int len;
+
+  /* Send buffered stdin data to the server. */
+  while (buffer_len(&stdin_buffer) > 0 && 
+	 packet_not_very_much_data_to_write())
+    {
+      len = buffer_len(&stdin_buffer);
+      if (len > 32768)
+	len = 32768;  /* Keep the packets at reasonable size. */
+      packet_start(SSH_CMSG_STDIN_DATA);
+      packet_put_string(buffer_ptr(&stdin_buffer), len);
+      packet_send();
+      buffer_consume(&stdin_buffer, len);
+      /* If we have a pending EOF, send it now. */
+      if (stdin_eof && buffer_len(&stdin_buffer) == 0)
+	{
+	  packet_start(SSH_CMSG_EOF);
+	  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. */
+
+void client_check_window_change()
+{
+  /* Send possible window change message to the server. */
+  if (received_window_change_signal)
+    {
+      struct winsize ws;
+
+      /* Clear the window change indicator. */
+      received_window_change_signal = 0;
+
+      /* Read new window size. */
+      if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) >= 0)
+	{
+	  /* Successful, send the packet now. */
+	  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_send();
+	}
+    }
+}
+
+/* Waits until the client can do something (some data becomes available on
+   one of the file descriptors). */
+
+void client_wait_until_can_do_something(fd_set *readset, fd_set *writeset)
+{
+  /* Initialize select masks. */
+  FD_ZERO(readset);
+  
+  /* Read from the connection, unless our buffers are full. */
+  if (buffer_len(&stdout_buffer) < buffer_high &&
+      buffer_len(&stderr_buffer) < buffer_high &&
+      channel_not_very_much_buffered_data())
+    FD_SET(connection_in, readset);
+
+  /* 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())
+    FD_SET(fileno(stdin), readset);
+  
+  FD_ZERO(writeset);
+  
+  /* Add any selections by the channel mechanism. */
+  channel_prepare_select(readset, writeset);
+  
+  /* Select server connection if have data to write to the server. */
+  if (packet_have_data_to_write())
+    FD_SET(connection_out, writeset);
+
+  /* Select stdout if have data in buffer. */
+  if (buffer_len(&stdout_buffer) > 0)
+    FD_SET(fileno(stdout), writeset);
+
+  /* Select stderr if have data in buffer. */
+  if (buffer_len(&stderr_buffer) > 0)
+    FD_SET(fileno(stderr), writeset);
+
+  /* Update maximum file descriptor number, if appropriate. */
+  if (channel_max_fd() > max_fd)
+    max_fd = channel_max_fd();
+
+  /* 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. */
+  if (select(max_fd + 1, readset, writeset, NULL, NULL) < 0)
+    {
+      char buf[100];
+      /* Some systems fail to clear these automatically. */
+      FD_ZERO(readset);
+      FD_ZERO(writeset);
+      if (errno == EINTR)
+	return;
+      /* Note: we might still have data in the buffers. */
+      snprintf(buf, sizeof buf, "select: %s\r\n", strerror(errno));
+      buffer_append(&stderr_buffer, buf, strlen(buf));
+      stderr_bytes += strlen(buf);
+      quit_pending = 1;
+    }
+}
+
+void client_suspend_self()
+{
+  struct winsize oldws, newws;
+
+  /* Flush stdout and stderr buffers. */
+  if (buffer_len(&stdout_buffer) > 0)
+    write(fileno(stdout), 
+	  buffer_ptr(&stdout_buffer), 
+	  buffer_len(&stdout_buffer));
+  if (buffer_len(&stderr_buffer) > 0)
+    write(fileno(stderr), 
+	  buffer_ptr(&stderr_buffer), 
+	  buffer_len(&stderr_buffer));
+
+  /* Leave raw mode. */
+  leave_raw_mode();
+
+  /* Free (and clear) the buffer to reduce the
+     amount of data that gets written to swap. */
+  buffer_free(&stdin_buffer);
+  buffer_free(&stdout_buffer);
+  buffer_free(&stderr_buffer);
+
+  /* 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;
+
+  /* OK, we have been continued by the user. 
+     Reinitialize buffers. */
+  buffer_init(&stdin_buffer);
+  buffer_init(&stdout_buffer);
+  buffer_init(&stderr_buffer);
+
+  /* Re-enter raw mode. */
+  enter_raw_mode();
+}
+
+void client_process_input(fd_set *readset)
+{
+  int len, pid;
+  char buf[8192], *s;
+
+  /* Read input from the server, and add any such data to the buffer of the
+     packet subsystem. */
+  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);
+	  buffer_append(&stderr_buffer, buf, strlen(buf));
+	  stderr_bytes += strlen(buf);
+	  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. */
+      if (len < 0 && errno == EAGAIN)
+	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));
+	  buffer_append(&stderr_buffer, buf, strlen(buf));
+	  stderr_bytes += strlen(buf);
+	  quit_pending = 1;
+	  return;
+	}
+      packet_process_incoming(buf, len);
+    }
+
+  /* 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)
+	{
+	  /* Received EOF or error.  They are treated similarly,
+	     except that an error message is printed if it was
+	     an error condition. */
+	  if (len < 0)
+	    {
+	      snprintf(buf, sizeof buf, "read: %.100s\r\n", strerror(errno));
+	      buffer_append(&stderr_buffer, buf, strlen(buf));
+	      stderr_bytes += strlen(buf);
+	    }
+	  /* 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_start(SSH_CMSG_EOF);
+	      packet_send();
+	    }
+	}
+      else
+	if (escape_char == -1)
+	  {
+	    /* Normal successful read, and no escape character.  Just 
+	       append the data to buffer. */
+	    buffer_append(&stdin_buffer, buf, len);
+	    stdin_bytes += len;
+	  }
+	else
+	  {
+	    /* Normal, successful read.  But we have an escape character
+	       and have to process the characters one by one. */
+	    unsigned int i;
+	    for (i = 0; i < len; i++)
+	      {
+		unsigned char ch;
+		/* Get one character at a time. */
+		ch = buf[i];
+		
+		/* Check if we have a pending escape character. */
+		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. */
+			snprintf(buf, sizeof buf, "%c.\r\n", escape_char);
+			buffer_append(&stderr_buffer, buf, strlen(buf));
+			stderr_bytes += strlen(buf);
+			quit_pending = 1;
+			return;
+
+		      case 'Z' - 64:
+			  /* Suspend the program. */
+			  /* Print a message to that effect to the user. */
+			  snprintf(buf, sizeof buf, "%c^Z\r\n", escape_char);
+			  buffer_append(&stderr_buffer, buf, strlen(buf));
+			  stderr_bytes += strlen(buf);
+
+			  /* Restore terminal modes and suspend. */
+			  client_suspend_self();
+
+			  /* We have been continued. */
+			  continue;
+			
+		      case '&':
+			/* Detach the program (continue to serve connections,
+			   but put in background and no more new 
+			   connections). */
+			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_start(SSH_CMSG_STDIN_DATA);
+			    packet_put_string("\004", 1);
+			    packet_send();
+			    /* Close stdin. */
+			    stdin_eof = 1;
+			    if (buffer_len(&stdin_buffer) == 0)
+			      {
+				packet_start(SSH_CMSG_EOF);
+				packet_send();
+			      }
+			  }
+			/* Restore tty modes. */
+			leave_raw_mode();
+
+			/* Stop listening for new connections. */
+			channel_stop_listening();
+
+			printf("%c& [backgrounded]\n", escape_char);
+			
+			/* Fork into background. */
+			pid = fork();
+			if (pid < 0)
+			  {
+			    error("fork: %.100s", strerror(errno));
+			    continue;
+			  }
+			if (pid != 0)
+			  { /* This is the parent. */
+			    /* The parent just exits. */
+			    exit(0);
+			  }
+
+			/* The child continues serving connections. */
+			continue;
+
+		      case '?':
+			snprintf(buf, sizeof buf, "%c?\r\n\
+Supported escape sequences:\r\n\
+~.  - terminate connection\r\n\
+~^Z - suspend ssh\r\n\
+~#  - list forwarded connections\r\n\
+~&  - background ssh (when waiting for connections to terminate)\r\n\
+~?  - this message\r\n\
+~~  - send the escape character by typing it twice\r\n\
+(Note that escapes are only recognized immediately after newline.)\r\n",
+				escape_char);
+			buffer_append(&stderr_buffer, buf, strlen(buf));
+			continue;
+
+		      case '#':
+			snprintf(buf, sizeof buf, "%c#\r\n", escape_char);
+			buffer_append(&stderr_buffer, buf, strlen(buf));
+			s = channel_open_message();
+			buffer_append(&stderr_buffer, s, strlen(s));
+			xfree(s);
+			continue;
+
+		      default:
+			if (ch != escape_char)
+			  {
+			    /* Escape character followed by non-special
+			       character.  Append both to the input
+			       buffer. */
+			    buf[0] = escape_char;
+			    buf[1] = ch;
+			    buffer_append(&stdin_buffer, buf, 2);
+			    stdin_bytes += 2;
+			    continue;
+			  }
+			/* Note that escape character typed twice falls through
+			   here; the latter gets processed as a normal
+			   character below. */
+			break;
+		      }
+		  }
+		else
+		  {
+		    /* 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;
+			continue;
+		      }
+		  }
+
+		/* Normal character.  Record whether it was a newline,
+		   and append it to the buffer. */
+		last_was_cr = (ch == '\r' || ch == '\n');
+		buf[0] = ch;
+		buffer_append(&stdin_buffer, buf, 1);
+		stdin_bytes += 1;
+		continue;
+	      }
+	  }
+    }
+}
+
+void client_process_output(fd_set *writeset)
+{
+  int len;
+  char buf[100];
+
+  /* Write buffered output to stdout. */
+  if (FD_ISSET(fileno(stdout), writeset))
+    {
+      /* Write as much data as possible. */
+      len = write(fileno(stdout), buffer_ptr(&stdout_buffer),
+		  buffer_len(&stdout_buffer));
+      if (len <= 0)
+	{
+	  if (errno == EAGAIN)
+	    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));
+	      buffer_append(&stderr_buffer, buf, strlen(buf));
+	      stderr_bytes += strlen(buf);
+	      quit_pending = 1;
+	      return;
+	    }
+	}
+      /* Consume printed data from the buffer. */
+      buffer_consume(&stdout_buffer, len);
+    }
+
+  /* Write buffered output to stderr. */
+  if (FD_ISSET(fileno(stderr), writeset))
+    {
+      /* Write as much data as possible. */
+      len = write(fileno(stderr), buffer_ptr(&stderr_buffer),
+		  buffer_len(&stderr_buffer));
+      if (len <= 0) {
+	if (errno == EAGAIN)
+	  len = 0;
+	else
+	  {
+	    /* 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);
+    }
+}
+
+/* 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 != -1, it is the character
+   used as an escape character for terminating or suspending the
+   session. */
+
+int client_loop(int have_pty, int escape_char_arg)
+{
+  double start_time, total_time;
+  int len;
+  char buf[100];
+
+  debug("Entering interactive session.");
+
+  start_time = get_current_time();
+
+  /* Initialize variables. */
+  escape_pending = 0;
+  last_was_cr = 1;
+  exit_status = -1;
+  stdin_eof = 0;
+  buffer_high = 64 * 1024;
+  connection_in = packet_get_connection_in();
+  connection_out = packet_get_connection_out();
+  max_fd = connection_in;
+  if (connection_out > max_fd)
+    max_fd = connection_out;
+  stdin_bytes = 0;
+  stdout_bytes = 0;
+  stderr_bytes = 0;
+  quit_pending = 0;
+  escape_char = escape_char_arg;
+
+  /* Initialize buffers. */
+  buffer_init(&stdin_buffer);
+  buffer_init(&stdout_buffer);
+  buffer_init(&stderr_buffer);
+
+  /* Set signal handlers to restore non-blocking mode.  */
+  signal(SIGINT, signal_handler);
+  signal(SIGQUIT, signal_handler);
+  signal(SIGTERM, signal_handler);
+  signal(SIGPIPE, SIG_IGN);
+  if (have_pty)
+    signal(SIGWINCH, window_change_handler);
+
+  /* Enter raw mode if have a pseudo terminal. */
+  if (have_pty)
+    enter_raw_mode();
+
+  /* Check if we should immediately send of on stdin. */
+  client_check_initial_eof_on_stdin();
+
+  /* Main loop of the client for the interactive session mode. */
+  while (!quit_pending)
+    {
+      fd_set readset, writeset;
+
+      /* Precess buffered packets sent by the server. */
+      client_process_buffered_input_packets();
+
+      /* Make packets of buffered stdin data, and buffer them for sending
+	 to the server. */
+      client_make_packets_from_stdin_data();
+
+      /* Make packets from buffered channel data, and buffer them for sending
+	 to the server. */
+      if (packet_not_very_much_data_to_write())
+	channel_output_poll();
+
+      /* Check if the window size has changed, and buffer a message about
+	 it to the server if so. */
+      client_check_window_change();
+
+      if (quit_pending)
+	break;
+
+      /* Wait until we have something to do (something becomes available
+	 on one of the descriptors). */
+      client_wait_until_can_do_something(&readset, &writeset);
+
+      if (quit_pending)
+	break;
+
+      /* Do channel operations. */
+      channel_after_select(&readset, &writeset);
+
+      /* Process input from the connection and from stdin.  Buffer any data
+         that is available. */
+      client_process_input(&readset);
+
+      /* Process output to stdout and stderr.   Output to the connection
+         is processed elsewhere (above). */
+      client_process_output(&writeset);
+
+      /* Send as much buffered packet data as possible to the sender. */
+      if (FD_ISSET(connection_out, &writeset))
+	packet_write_poll();
+    }
+
+  /* Terminate the session. */
+
+  /* Stop watching for window change. */
+  if (have_pty)
+    signal(SIGWINCH, SIG_DFL);
+
+  /* Stop listening for connections. */
+  channel_stop_listening();
+
+  /* In interactive mode (with pseudo tty) display a message indicating that
+     the connection has been closed. */
+  if (have_pty && !quiet_flag)
+    {
+      snprintf(buf, sizeof buf, "Connection to %.64s closed.\r\n", host);
+      buffer_append(&stderr_buffer, buf, strlen(buf));
+      stderr_bytes += strlen(buf);
+    }
+
+  /* Output any buffered data for stdout. */
+  while (buffer_len(&stdout_buffer) > 0)
+    {
+      len = write(fileno(stdout), buffer_ptr(&stdout_buffer), 
+		  buffer_len(&stdout_buffer));
+      if (len <= 0)
+	{
+	  error("Write failed flushing stdout buffer.");
+	  break;
+	}
+      buffer_consume(&stdout_buffer, len);
+    }
+
+  /* Output any buffered data for stderr. */
+  while (buffer_len(&stderr_buffer) > 0)
+    {
+      len = write(fileno(stderr), buffer_ptr(&stderr_buffer), 
+		  buffer_len(&stderr_buffer));
+      if (len <= 0)
+	{
+	  error("Write failed flushing stderr buffer.");
+	  break;
+	}
+      buffer_consume(&stderr_buffer, len);
+    }
+
+  /* Leave raw mode. */
+  if (have_pty)
+    leave_raw_mode();
+
+  /* Clear and free any buffers. */
+  memset(buf, 0, sizeof(buf));
+  buffer_free(&stdin_buffer);
+  buffer_free(&stdout_buffer);
+  buffer_free(&stderr_buffer);
+
+  /* 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);
+  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);
+
+  /* Return the exit status of the program. */
+  debug("Exit status %d", exit_status);
+  return exit_status;
+}