// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2, or (at your option)
// any later version.<P>
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.<P>
// 
// To obtain a copy of the GNU General Public License write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. <P>


package rob.servlets ;

import java.io.* ;
import java.util.* ;

import javax.servlet.* ;
import javax.servlet.http.* ;

import mindbright.ssh.SSHPropertyHandler ;
import mindbright.ssh.SSHInteractiveClient ;

import rob.util.RStringBufferInputStream ;


/**
 * <code>SSHServlet</code> allows the user to connect to the local host via a web
 * browser.  It could easily be extended to allow connection to any host.  This is
 * done entirely by the servlet, and demonstrates the ability that servlets have
 * of keeping a process running in between browser requests.  State is kept 
 * between sessions using cookies or URL re-writing. <P>
 *
 * The general outline is as follows.  The user enters his id and password.  An
 * SSH session is then started with these values, and a cookie sent to the browser.
 * The session is saved with the cookie, so that it can be recovered with the next
 * request.   <P>
 * 
 * The user enters a command and presses execute.  When the servlet receives the
 * request it retrieves the user's session from the cookie and sends the command
 * to the SSH session.  The output from the command is read into a string and
 * returned in the new HTML page. <P>
 *
 * The session will terminate when the users clicks on logout or when the cookie
 * expires (after a fixed time with no requests, usually around 30 minutes). <P>
 *
 * The SSH connection was implemented in Java and is copyright by:
 * Copyright (c) 1998,99 by Mindbright Technology AB, Stockholm, Sweden.
 *                 www.mindbright.se, info@mindbright.se
 * The code is kindly released under the GNU Public license.  The implementation is
 * very good, but it was not written to be extendedoutside of its own package.  In
 * order to make it usable I have patched it somewhat; the patch is included at the
 * end of this file.
 *
 * Created: Wed Aug 16 14:36:00 2000
 *
 * @author <a href="mailto:rob@corwin.the-judds.org">Robert Judd</a>
 * @version 1.0
 * @since 1.0
 * @see HttpServlet
 */
public class SSHServlet extends HttpServlet {

    /**
     * Maintain our own list of sessions.  One can get the same list using an 
     * <code>HttpServletRequest</code> object, but we use this list when the 
     * servlet is destroyed to close any open connections, and there is no such
     * object available in that method. <P>
     *
     * Add a session to the list with <code>sessions.put(session)</code>,
     * remove a session with <code>sessions.remove(session)</code> and enumerate
     * the sessions using <code>sessions.iterator()</code> (see below).
     *
     * @see destroy()
     */
    private HashSet sessions ;

    /**
     * Initialize the servlet.  All we do is instantiate the list for the sessions.
     *
     * @param config a <code>ServletConfig</code> value
     * @exception ServletException if an error occurs
     */
    public void init(ServletConfig config) throws ServletException {
	super.init(config);
	sessions = new HashSet() ;
    }
    
    
    /**
     * Process a <code>GET</code> request.  This expects a parameter to be passed
     * in with name "action".  The value will determine what to do next.  Allowed
     * values are:
     * <DL>
     * <DT>Log In</DT>
     * <DD>Expect parameters "userid" and "password"; try to start an SSH session
     *     and return the response from the server. </DD>
     *
     * <DT>Log Out</DT>
     * <DD>Exit the shell and close the current session.  The login page is 
     *     returned.</DD>
     *
     * <DT>Execute</DT>
     * <DD>Expect parameter "command" containing a command to send to the shell.
     *     The output from the server will be returned to the user. </DD>
     * </DL><P>
     *
     * If no action parameter is given, then the login page is presented.<P>
     *
     * @param req the <code>HttpServletRequest</code> object passed in by the
     *            server
     * @param res the <code>HttpServletResponse</code> object passed in by the
     *            server
     *
     * @exception <code>ServletException</code> if anything goes wrong with the
     *            servlet/server communication
     * @exception <code>IOException</code> if anything goes wrong with the io
     *            streams.
     */
    public void doGet(HttpServletRequest req, HttpServletResponse res)
    	throws ServletException, IOException {
	
	// Let the browser know we are returning an html page
	res.setContentType("text/html") ;

	// Get the writer to write our output on
	PrintWriter out = res.getWriter() ;
	
	// Find out what action to take
	String action = req.getParameter("action") ;

	// If none specified, then return the login page
	if (action == null || action.equals("")) {
	    
	    // Nothing to do yet.  Just return the page
	    writeLoginPage(out) ;

	    // PrintWriters do not throw exceptions; the only way to detect them
	    // is to call their checkError() method.
	    if (out.checkError()) 
		writeErrorPage(out, "There was an error returning the login page.");

	} else if (action.equals("Log In"))
	    login(out, req, res) ;
	else if (action.equals("Log Out"))
	    logout(out, req) ;
	else if (action.equals("Execute"))
	    execute(out, req, res) ;
	else 
	    writeErrorPage(out, "Your command: "+action+" was not understood.") ;
	

    }  

    /**  Just send the request on to <code>doGet</code>. */
    public void doPost(HttpServletRequest req, HttpServletResponse res)
    	throws ServletException, IOException {
	
	doGet(req, res) ;
    }

    /**
     * This is called by the server when it is about to dispose of the servlet.  
     * Any cleanup necessary should be done here.  In particular any open sessions 
     * should be closed and any worker threads stopped.  The <code>destory</code>
     * method of the superclass should also be called for any cleaning up that it
     * requires.
     */
    public void destroy() {

	// To loop through the sessions in the HashSet call its iterator and then
	// call next() until there are no more
	for (Iterator i = sessions.iterator(); i.hasNext();) {
	    HttpSession session = (HttpSession) i.next() ;
	    if (session != null) {

		// Get the input stream so we can logout from the shell if the
		// connection is still open.
		RStringBufferInputStream stdin = 
		    (RStringBufferInputStream) session.getValue("STDIN") ;
		// Snd the exit command
		if (stdin != null) {
		    stdin.append("exit\n") ;
		    try { Thread.sleep(500) ; } catch (Exception ignored) {}
		}

		// Stop the worker thread that keeps the SSH session running
		Thread t = (Thread) session.getValue("SSHThread") ;
		t.stop() ;

		// Invalidate the cookie.
		session.invalidate() ;
	    }
	}
	// Also stop getting random data we no longer need it.
	SSHInteractiveClient.secureRandom.updater.stop() ;
	super.destroy() ;
    }
	    
    

    // ----------- The methods to handle the different requests -------------

    /**
     * This is called when the user tries to login; it tries to start an ssh 
     * session, and returns the output from this.  The first step is to get the 
     * userid and password.  Next an <code>HttpSession</code> object is created.
     * This is used to save state (and the SSH connection in particular) between
     * connections.  The session object is stored in the response to the user as
     * a cookie if the user's browser supports and accepts then, or else as a 
     * session id in the URLs in the return page.  <P>
     *
     * Notice how easy it is to use cookies: a session is obtained from the request
     * with <code>HttpSession session = req.getSession(true)</code> (and created
     * if it hasn't been created already).  Objects may be stored in it with
     * <code>session.putValue(String key, Object value)</code> and later
     * retrieved with <code>Object session.getValue(String key)</code>.  That's it!
     * In case the user's browser doesn't support cookies, or has them disabled,
     * any URLs back to the servlet in the return page should be encoded with
     * <code>String res.encodeUrl(String url)</code>.
     *
     * The main work is done in creating an SSH connection.  Once it is created 
     * the output is read and sent on to <code>writeProcessPage</code> which
     * actually returns the page to the browser.  In between the various parts of
     * the SSH connection are saved to the session object so that they may be 
     * used in the next request.  <P>
     *
     * @param out the <code>PrintWriter</code> to return the response to the user
     * @param req The <code>HttpServletRequest</code> represnting the user's 
     *            request.  This is used to get the parameters and the session.
     * @param res The <code>HttpServletResponse</code> representing the response
     *            to the user.  This is used in case the URLs need to be re-written
     *            to include the session id, although here it is passed straight on
     *            to <code>writeProcessPage</code>.
     */
    public void login(PrintWriter out, HttpServletRequest req, 
		      HttpServletResponse res) {

	// Get the parameters
	String userid = req.getParameter("userid") ;
	String password = req.getParameter("password") ;

	// Create a session for the user (the argument 'true' means create it)
	HttpSession session = req.getSession(true);

	// Properties to pass in to SSH - the first three are obvious.  The last
	// 'idhost' tells SSH not to look for the host key in a known hosts file.
	Properties p = new Properties() ;
	p.setProperty("usrname",  userid) ;
	p.setProperty("password", password) ;
	p.setProperty("server", "localhost") ;
	p.setProperty("idhost", "false") ;
	SSHPropertyHandler sshph = new SSHPropertyHandler(p) ;

	// We write commands from the user to stdin, where SSH will read them
	RStringBufferInputStream stdin = new RStringBufferInputStream() ;

	// SSH writes its output to stdout, where we read it and return it to 
	// the user
	StringWriter stdout = new StringWriter() ;

	// This creates the client, but in order to do anything it has to be started
	SSHInteractiveClient client = new SSHInteractiveClient
	    (true, true, sshph, stdin, new PrintWriter(stdout)) ;


	// Start the SSH process
	Thread thread = new Thread(client) ;
	thread.start() ;
	thread.setPriority(Thread.currentThread().getPriority()+2) ;

	// Allow time for SSH to connect	    
	try { Thread.sleep(1000) ; } catch (Exception ignored) {}

	// Read the response
	StringBuffer buf = stdout.getBuffer() ;
	// Save it
	String text = buf.toString() ;
	// Wipe it from the stream
	buf.setLength(0) ;

	// Save the important parts and add the session to our list.
	session.putValue("SSHClient", client) ;
	session.putValue("SSHThread", thread) ;
	session.putValue("STDIN", stdin) ;
	session.putValue("STDOUT", stdout) ;
	sessions.add(session) ;

	// Finally return the response.
	writeProcessPage(out, text, res) ;

    }

    /**
     * This is called when the user enters a command and clicks on "Execute".
     * The command is read, written to the input of the SSH session, and the output
     * from SSH returned in an HTML page. <P>
     * 
     * We get the SSH session from the cookie and retrieve the <code>stdin</code>
     * and <code>stdout</code> streams.  Then we get the command parameter and write
     * it to <code>stdin</code>.  Wait for a second to allow the SSH process to 
     * run, read the response and return it to the user via
     * <code>writeProcessPage</code>.  <P>
     *
     * @param out a <code>PrintWriter</code> value
     * @param req a <code>HttpServletRequest</code> value
     * @param res a <code>HttpServletResponse</code> value
     */
    public void execute(PrintWriter out, HttpServletRequest req, 
			HttpServletResponse res) {

	// Get the session - we send the 'false' parameter so that if there is no
	// session one will not be created and the user is redirected to the login
	// page
	HttpSession session = req.getSession(false);
	if (session == null)
	    writeLoginPage(out) ;
	else {

	    // Retrieve stdin and stdout
	    RStringBufferInputStream stdin = 
		(RStringBufferInputStream) session.getValue("STDIN") ;
	    StringWriter stdout = (StringWriter) session.getValue("STDOUT") ;

	    if (stdin == null || stdout == null) 
		logout(out, req) ;
	    else {
		// Get the command
		String cmd = req.getParameter("command") ;
		if (cmd == null) cmd = "" ;

		// Write the command to the SSH process's input, together with a 
		// newline.  Then wait, read the output and return it.
		stdin.append(cmd+"\n") ;
		try { Thread.sleep(1000) ; } catch (Exception ignored) {} 
		StringBuffer buf = stdout.getBuffer() ;
		String text = buf.toString() ;
		buf.setLength(0) ;
		writeProcessPage(out, text, res) ;
	    }
	}
    }

    /**
     * Called when the user wants to log out.  This stops the SSH process and 
     * deletes the session.  The login page is returned.
     *
     * @param out a <code>PrintWriter</code> value
     * @param req a <code>HttpServletRequest</code> value
     */
    public void logout(PrintWriter out, HttpServletRequest req) {

	HttpSession session = req.getSession(false);
	if (session != null) {

	    // Stop the worker thread that keeps the session running
	    Thread t = (Thread) session.getValue("SSHThread") ;
	    t.stop() ;

	    // Invalidating a session tells the servlet engine to delete it.
	    session.invalidate() ;
	    // Remove it from our list
	    sessions.remove(session) ;
	}

	// Some bookkeeping for the SSH process.
	if (sessions.size() == 0) SSHInteractiveClient.secureRandom.updater.stop() ;
	writeLoginPage(out) ;
    }

    /**
     * Returns the login page to the user using the <code>PrintWriter</code>
     * from the servlet response.  All the <code>writeXXXXPage</code> methods 
     * follow the same pattern: their data is stored in a String array which is 
     * written straight to the output, except for certain lines that a re-written.
     * In this case one of te last lines is changed to say,
     * "Generated by SSHServlet Mon 1 Jan 2000 ...."
     * with the current date and time.  The only interesting method is
     * <code>writeProcessPage</code>.
     *
     * @param out a <code>PrintWriter</code> value
     */
    public static void writeLoginPage(PrintWriter out) {

	for (int i=0; i<LOGIN_MESSAGE_LINE; ++i)
	    out.println(loginData[i]) ;

	out.println("Generated by SSHServlet "+new Date().toString());

	for (int i=LOGIN_MESSAGE_LINE+1, max=loginData.length; i<max; ++i)
	    out.println(loginData[i]) ;

	out.flush() ;
	

    }

    public static final int LOGIN_MESSAGE_LINE = 67 ;
    public static final String[] loginData = new String[] {

	"<html>",
	"<head>",
	"",
	"<script language=\"JavaScript\">",
	"",
	"<!--- Hide me",
	"// Check input fields and submit form.",
	"function checkLogin() {",
	"    // Make sure the userid field is not blank",
	"    if(document.login.userid.value.length == 0){",
	"        alert(\"The user id field should not be blank.\");",
	"        document.login.userid.focus();",
	"        return;",
	"    }",
	"    document.login.submit();",
	"    document.login.reset();",
	"}",
	"//End hiding-->",
	"",
	"",
	"</script>",
	"",
	"<title>SSHServlet Login</title>",
	"",
	"</head>",
	"<body bgcolor=\"#FFFFFF\" onLoad=\"document.login.userid.focus()\">",
	" ",
	"<table border=0 cellspacing=0 cellpadding=10 width=100% bgcolor=\"#767E9E\">",
	"  <tr><td align=center valign=center height=60><font color=\"#C2D7EA\" size=7>",
	"    <b>SSHServlet Login</b></font></td></tr>",
	"</table>",
	"",
	"<br>",
	"",
	"<form method=\"POST\" name=\"login\" action=\"rob.servlets.SSHServlet\">",
	"",
	"<table>",
	"  <tr>",
	"    <td>User id: </td> ",
	"    <td><input maxlength=\"40\" type=\"text\" name=\"userid\" size=\"20\" value=\"\"></td>",
	"  </tr>",
	"  <tr>",
	"    <td>Password: </td>",
	"    <td><input maxlength=\"40\" type=\"password\" name=\"password\" size=\"20\" value=\"\"></td>",
	"  </tr>",
	"  <tr>",
	"    <td><input type=\"button\" onClick=\"checkLogin()\" value=\"Log In\">",
	"	<input type=\"hidden\" name=\"action\" value=\"Log In\"></td>",
	"    <td><input type=\"button\" onClick=\"reset()\" value=\"Reset\"></td>",
	"  </tr>",
	"</table>",
	"",
	"<noscript>",
	"If your browser does not support JavaScript use this button:",
	"<input type=\"submit\" value=\"Log In\">",
	"</noscript>",
	"",
	"</form>",
	"",
	"<br>",
	"<table border=0 cellspacing=0 cellpadding=10 width=100% bgcolor=\"#767E9E\">",
	"  <tr><td align=right> &nbsp;",
	"  </td></tr>",
	"</table>",
	"<br>",
	"<address>",
	"<font color=\"#767E9E\">",
	"Generated by SSHServlet Fri Mar  9 16:05:14 2001",
	"</font>",
	"</address>",
	"",
	"</body>",
	"</html>",
    } ;
    

    /**
     * Return the response from the SSH process in an HTML page.  The output is
     * passed in as the String <code>text</code>.  We need the servlet response
     * here to encode the URL in case the user's browser does not support or
     * accept cookies. <P>
     *
     * Encoding the URLs is easy.  Anywhere you would normally have a URL in the
     * output that refers to this servlet, say: <code>
     * <A HREF="/servlet/rob.servlets.SSHServlet?action=Log+Out">
     * </code>
     * you replace the string with the value: <code>
     * res.encodeUrl("/servlet/rob.servlets.SSHServlet?action=Log+Out")
     * </code>.
     *
     * @param out a <code>PrintWriter</code> value
     * @param text a <code>String</code> value
     * @param res a <code>HttpServletResponse</code> value
     */
    public static void writeProcessPage(PrintWriter out, String text, 
					HttpServletResponse res) {

	for (int i=0; i<ACTION_LINE; ++i)
	    out.println(processData[i]) ;

	out.print("<form method=\"POST\" name=\"form\" action=\"");
	if (res != null) 
	    out.print(res.encodeUrl("/servlet/rob.servlets.SSHServlet")) ;
	else
	    out.print("/servlet/rob.servlets.SSHServlet") ;
	out.println("\">") ;

	for (int i=ACTION_LINE+1; i<OUTPUT_LINE; ++i)
	    out.println(processData[i]) ;

	out.println(text);

	for (int i=OUTPUT_LINE+1; i<LOGOUT_LINE; ++i)
	    out.println(processData[i]) ;

	out.print("    <a href=\"");
	if (res != null) 
	    out.print
		(res.encodeUrl("/servlet/rob.servlets.SSHServlet?action=Log+Out"));
	else
	    out.print("/servlet/rob.servlets.SSHServlet?action=Log+Out") ;
	out.println("\">");

	for (int i=LOGOUT_LINE+1; i<PROCESS_MESSAGE_LINE; ++i)
	    out.println(processData[i]) ;

	out.println("Generated by SSHServlet "+new Date().toString());

	for (int i=PROCESS_MESSAGE_LINE+1, max=processData.length; i<max; ++i)
	    out.println(processData[i]);

	out.flush() ;
	


    }

    public static final int ACTION_LINE = 24 ;
    public static final int OUTPUT_LINE = 47 ;
    public static final int LOGOUT_LINE = 57 ;
    public static final int PROCESS_MESSAGE_LINE = 64 ;
    public static final String[] processData = new String[] {



	"<html>",
	"<head>",
	"",
	"<title>SSHServlet</title>",
	"",
	"<style type=\"text/css\">",
	"<!--",
	"a:link {  text-decoration: none}",
	"a:visited {  text-decoration: none}",
	"a:hover {  text-decoration: underline}",
	"-->",
	"</style>",
	"",
	"",
	"</head>",
	"<body bgcolor=\"#FFFFFF\" onLoad=\"document.form.command.focus()\">",
	" ",
	"<table border=0 cellspacing=0 cellpadding=10 width=100% bgcolor=\"#767E9E\">",
	"  <tr><td align=center valign=center height=60><font color=\"#C2D7EA\" size=7>",
	"    <b>SSHServlet</b></font></td></tr>",
	"</table>",
	"",
	"<br>",
	"",
	"<form method=\"POST\" name=\"form\" action=\"rob.servlets.SSHServlet\">",
	"",
	"<input maxlength=\"1600\" type=\"text\" name=\"command\" size=\"80\" value=\"\">",
	"",
	"<table><tr><td>",
	"<input type=\"button\" onClick=\"submit()\" value=\"Execute\">",
	"<input type=\"hidden\" name=\"action\" value=\"Execute\">",
	"&nbsp;&nbsp;",
	"<input type=\"button\" onClick=\"reset()\" value=\"Reset\">",
	"&nbsp;&nbsp;",
	"<noscript>",
	"If your browser does not support JavaScript use this button:",
	"<input type=\"submit\" value=\"Execute\">",
	"</noscript>",
	"</td></tr></table>",
	"    ",
	"</form>",
	"",
	"<table bgcolor=\"#000000\" cellpadding=1><tr><td>",
	"<table bgcolor=\"#FFFFFF\" cellpadding=10><tr><td>",
	"<pre>",
	"<textarea cols=80>",
	"",
	"YOUR COMMAND OUTPUT GOES HERE",
	"",
	"</textarea>",
	"</pre>",
	"</td></tr></table>",
	"</td></tr></table>",
	"",
	"<br>",
	"<table border=0 cellspacing=0 cellpadding=10 width=100% bgcolor=\"#767E9E\">",
	"  <tr><td align=right>",
	"    <a href=\"/servlet/rob.servlets.SSHServlet?action=Log+Out\">",
	"    <font color=\"#C2D7EA\"><b>Log Out</b></font></a>",
	"  </td></tr>",
	"</table>",
	"<br>",
	"<address>",
	"<font color=\"#767E9E\">",
	"Generated by LoginPage Fri Apr 13 16:54:57 CDT 2001",
	"</font>",
	"</address>",
	"",
	"</body>",
	"</html>"
    } ;


    /**
     * Return a page describing the error.
     *
     * @param out a <code>PrintWriter</code> value
     * @param msg a <code>String</code> value
     */
    public static void writeErrorPage(PrintWriter out, String msg) {

	for (int i=0; i<ERROR_LINE; ++i)
	    out.println(errorData[i]) ;

	out.println(msg);

	for (int i=ERROR_LINE+1; i<ERROR_MESSAGE_LINE; ++i)
	    out.println(errorData[i]) ;

	out.println("Generated by SSHServlet "+new Date().toString());

	for (int i=ERROR_MESSAGE_LINE+1, max=errorData.length; i<max; ++i)
	    out.println(errorData[i]);

	out.flush() ;
	


    }


 
    public static final int ERROR_LINE = 16 ;
    public static final int ERROR_MESSAGE_LINE = 26 ;
    public static final String[] errorData = new String[] {

	"<html>",
	"<head>",
	"",
	"<title>SSHServlet Error</title>",
	"",
	"</head>",
	"<body bgcolor=\"#FFFFFF\">",
	" ",
	"<table border=0 cellspacing=0 cellpadding=10 width=100% bgcolor=\"#767E9E\">",
	"  <tr><td align=center valign=center height=60><font color=\"#C2D7EA\" size=7>",
	"    <b>SSHServlet Error</b></font></td></tr>",
	"</table>",
	"",
	"<br>",
	"Your request could not be processed for the following reason:",
	"<ul>",
	"Doh!",
	"</ul>",
	"<br>",
	"<table border=0 cellspacing=0 cellpadding=10 width=100% bgcolor=\"#767E9E\">",
	"  <tr><td align=right> &nbsp;",
	"  </td></tr>",
	"</table>",
	"<br>",
	"<address>",
	"<font color=\"#767E9E\">",
	"Generated by SSHServlet Fri Apr 13 17:40:06 CDT 2001",
	"</font>",
	"</address>",
	"",
	"</body>",
	"</html>"
    } ;

    /**
     * This allows the developer to see how the HTML pages produced by the servlet
     * will look.  For example, to see the login page use 
     * <code>java rob.servlets.SSHServlet login > loginpage.html</code> and
     * view the page with your browser.
     *
     * @param args a <code>String[]</code> value of command line arguments
     */
    public static void main(String[] args) {

	PrintWriter out = new PrintWriter(System.out) ;
	if (args == null || args.length == 0) 
	    out.println
		("Usage: java rob.servlets.SSHServlet (login|error message|process text)");
	else if(args[0].toLowerCase().equals("login"))
	    writeLoginPage(out) ;
	else if(args[0].toLowerCase().equals("error") && args.length > 1)
	    writeErrorPage(out, args[1]) ;
	else if(args[0].toLowerCase().equals("process") && args.length > 1)
	    writeProcessPage(out, args[1], null) ;
	else
	    out.println
		("Usage: java SSHServlet (login|error message|process text)");

	out.flush() ;

    }

}

/*

Apply patches with cd mindbright/ssh ; patch < patchfile

------- patch for SSHInteractiveClient.java ---- 8>< cut here ----------
--- Orig.SSHInteractiveClient.java	Mon Apr 16 18:20:24 2001
+++ SSHInteractiveClient.java	Fri Apr 13 19:07:07 2001
@@ -21,6 +21,8 @@
 package mindbright.ssh;
 
 import java.util.Properties;
+import java.io.InputStream ;
+import java.io.PrintWriter ;
 import java.io.IOException;
 import java.io.FileNotFoundException;
 import java.net.UnknownHostException;
@@ -79,7 +81,10 @@
       } catch (SSHStdIO.CtrlDPressedException e) {
 	controller.sendDisconnect("exit");
       } catch (Exception e) {
-	controller.alert("Error in console-thread: " + e.toString());
+	  java.io.StringWriter w = new java.io.StringWriter() ;
+	  java.io.PrintWriter pw = new java.io.PrintWriter(w) ;
+	  e.printStackTrace(pw) ;
+	  controller.alert("Error in console-thread: " + e.toString() + "\n" + w.toString());
       }
     }
   }
@@ -89,6 +94,10 @@
   }
 
   public SSHInteractiveClient(boolean quiet, boolean cmdsh, SSHPropertyHandler propsHandler) {
+      this(quiet, cmdsh, propsHandler, System.in, new PrintWriter(System.out)) ;
+  }
+
+  public SSHInteractiveClient(boolean quiet, boolean cmdsh, SSHPropertyHandler propsHandler, InputStream input, PrintWriter output) {
     super(propsHandler, propsHandler);
 
     this.propsHandler = propsHandler;
@@ -100,7 +109,7 @@
     this.quiet     = quiet;
     this.initQuiet = quiet;
 
-    setConsole(new SSHStdIO());
+    setConsole(new SSHStdIO(input, output));
     sshStdIO = (SSHStdIO)console;
     sshStdIO.setClient(this);
     sshStdIO.enableCommandShell(cmdsh);
------- End patch for SSHInteractiveClient.java ---- 8>< cut here ----------

------- patch for SSHStdIO.java ---- 8>< cut here ----------
--- Orig.SSHStdIO.java	Mon Apr 16 18:19:43 2001
+++ SSHStdIO.java	Thu Apr 12 18:35:09 2001
@@ -70,12 +70,23 @@
   //
   static Clipboard localClipboard = null;
 
-  public SSHStdIO() {
+    InputStream input ;
+    PrintWriter output ;
+
+
+  public SSHStdIO(InputStream input, PrintWriter output) {
     this.readLineLock   = new Boolean(false);
     this.controller     = null;
     this.sndCipher      = null;
     this.isConnected    = false;
     this.commandShell   = null;
+    this.input = input ;
+    this.output = output ;
+  }
+
+
+  public SSHStdIO() {
+      this(System.in, new PrintWriter(System.out)) ;
   }
 
   public boolean isConnected() {
@@ -168,10 +179,11 @@
       line = readLine(defaultVal);
       this.echoStar = false;
     } else {
-      BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
-      System.out.print(prompt);
+      BufferedReader br = new BufferedReader(new InputStreamReader(input));
+      output.print(prompt);
+      output.flush() ;
       line = br.readLine();
-
+    
       // !!! kludge, winlost doesn't give empty input with ^D, this is a kludgy thing
       //  anyway since we're not sure that ^D is EOF in the shell...
       //
@@ -187,6 +199,7 @@
       pressedCtrlD = false;
       throw new CtrlDPressedException();
     }
+    
 
     return line;
   }
@@ -241,14 +254,16 @@
     if(term != null) {
       term.write(str);
     } else {
-      System.out.print(str);
+      output.print(str);
+      output.flush() ;
     }
   }
   public void println(String str) {
     if(term != null) {
       term.write(str + "\n\r");
     } else {
-      System.out.println(str);
+      output.println(str);
+      output.flush() ;
     }
   }
   public void serverConnect(SSHChannelController controller, Cipher sndCipher) {
------- End patch for SSHStdIO.java ---- 8>< cut here ----------

*/
