diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000000000000000000000000000000000000..844d5e34ca86241eeb9d1866a7d08be55ac5a743
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,14 @@
+
+Original author
+---------------
+
+Lee David Painter
+
+Contributors
+------------
+
+Brett Smith
+Richard Pernavas
+Erwin Bolwidt
+Sascha Hunold <hunoldinho@users.sourceforge.net>
+
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000000000000000000000000000000000000..e3c66fbc074922bd57843980b2e437bb84666762
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,33 @@
+
+Version 0.2.9
+-------------
+
+* com/sshtools/j2ssh/io/ByteArrayWriter.java (sahu)
+  - UFT-8 patch
+
+* applied patch number [977590] 
+  - possible deadlock in transport lock handling
+
+* made source compilable with Java 5 (sahu)
+  - enum -> en
+  - changed getState() -> getStartStopState() [SessionChannelServer.java]
+
+* cosmetic refactoring (sahu)
+  - removed calls to deprecated methods
+  - example: hide() replaced by setVisible()
+  
+* com/sshtools/j2ssh/sftp/FileAttributes.java (sahu)
+  - fixed test for symbolic links
+
+*  com/sshtools/j2ssh/sftp/SftpFile.java (sahu)
+  - changed permission handling
+  
+* rekey patch (sahu)
+  - rekey bug when more than 1 GB is transferred
+
+
+Version 0.2.8
+-------------
+
+- changed license to GPL only
+- changed ant build.xml file to compile j2ssh with Java > 1.4
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000000000000000000000000000000000000..1240b6950befd0e82801c3c31adde95ba948ef07
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,53 @@
+
+====================
+Using Sshtools J2SSH
+====================
+	
+1) First unzip the distribution file into the PATH_OF_YOUR_CHOICE which we refer to as $INSTALL_DIR.
+2) Build the JAR files using the ant script $INSTALL_DIR/build.xml
+3) Add the JAR files to your class path.
+   
+=========
+JAR files
+=========
+The Sshtools distribution comes a number jar files, they are
+
+j2ssh-core-VERSION.jar 
+The core jar file contains SSH client components, the J2SSH core now implements
+most of the basic ssh specifications and implements a number of clients including ssh, sftp and scp.
+The core also includes the ssh agent implementation, port forwarding, proxy components and a framework
+for extending channels, subsystems and all ssh protocol algorithms.
+
+j2ssh-ant-VERSION.jar
+This file contains the J2SSH ant tasks
+
+j2ssh-common-VERSION.jar
+The common jar file contains a number of reusable components such as Swing authentication 
+prompts, an xml configuration context, remote identification and configuration automation utilites.
+
+j2ssh-daemon-VERSION.jar
+The daemon file implements the server side components in conjunction with the j2ssh core.
+
+=====================
+J2SSH dependencies
+=====================
+   
+The dependencies vary for J2SSH depending upon the JDK and individual J2SSH jar files required
+by your implementation.
+
+If you are using JDK 1.4 or greater, the only dependency the j2ssh jar files require 
+is commons-logging.jar with the exception of the J2SSH ant tasks which also require ant.jar
+
+For JDK 1.3.1 the core requires a JCE provider. We have tested the core using the bouncycastle JCE which
+is provided with the JDK 1.3.1 distribution file. If you are using the common or daemon jar files with 
+JDK 1.3.1 you will also need to include xerces for xml parsing which is also included.
+
+=================
+Building Sshtools
+=================
+    
+If you downloaded the source distribution you need to build the binaries before
+using sshtools. Like most java appilications today, Sshtools relies on ANT as 
+its buildtool. ANT is availale from "http://jakarta.apache.org/ant/".  ANT
+requires a build file called build.xml which can be found in the $INSTALL_DIR
+directory. 
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..d511905c1647a1e311e8b20d5930a37a9c2531cd
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,339 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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 of the License, or
+    (at your option) any later version.
+
+    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.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2c867eec4bfb3e1597d2f331fb3c0bc455c47ee0
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="sshtools" default="build" basedir=".">
+    <!-- Set global properties for this build -->
+    <property name="build.examples" value="./examples"/>
+    <property name="build.dist" value="./dist"/>
+    <property name="build.conf" value="./conf"/>
+    <property name="build.dist.classes" value="${build.dist}/classes"/>
+    <property name="build.dist.lib" value="${build.dist}/lib"/>
+    <property name="build.dependency" value="./lib"/>
+    <property name="build.src" value="./src"/>
+    <property name="build.docs" value="./docs"/>
+
+    <!-- Global build parameters -->
+    <property file="j2ssh.properties"/>
+
+    <!-- Set this to 'yes' if you wish the classes
+        to be compiled with debug information -->
+    <property name="build.debugInformation" value="off"/>
+
+    <!-- Build the project classpath -->
+    <path id="project.class.path">
+      <fileset dir="${build.dependency}">
+        <include name="*.jar"/>
+      </fileset>
+      <pathelement path="${build.dist.classes}/"/>
+    </path>
+
+   <target name="compile" depends="clean">
+       <echo message="Creating directories"/>
+       <!-- Create the output directory -->
+       <mkdir dir="${build.dist}"/>
+       <mkdir dir="${build.dist.classes}"/>
+       <!-- Copy the projects resources to the classpath -->
+       <echo message="Copying resource to classpath"/>
+       <copy todir="${build.dist.classes}" >
+          <fileset dir="${build.src}" >
+             <include name="**/*.png"/>
+             <include name="**/*.gif"/>
+             <include name="**/*.xpm"/>
+             <include name="**/*.ico"/>
+          </fileset>
+       </copy>
+       <!-- Compile the source -->
+       <javac srcdir="${build.src}" debug="${build.debugInformation}"
+              destdir="${build.dist.classes}" includes="**/*.java"
+	      source="1.4" target="1.4">
+	       <classpath refid="project.class.path"/>
+       </javac>
+
+    </target>
+    <target name="build" depends="compile">
+         <mkdir dir="${build.dist.lib}"/>
+
+         <!-- Build the J2SSH library files -->
+ 	         <jar jarfile="${build.dist.lib}/j2ssh-core-${j2ssh.version.major}.${j2ssh.version.minor}.${j2ssh.version.build}.jar" basedir="${build.dist.classes}">
+                <include name="com/sshtools/j2ssh/**/*.class"/>
+                <manifest>
+                   <attribute name="Product-Version"
+                         value="${j2ssh.version.major}.${j2ssh.version.minor}.${j2ssh.version.build}"/>
+                 </manifest>
+             </jar>
+
+             <jar jarfile="${build.dist.lib}/j2ssh-ant-${j2ssh.version.major}.${j2ssh.version.minor}.${j2ssh.version.build}.jar" basedir="${build.dist.classes}">
+                <include name="com/sshtools/ant/**/*.class"/>
+                <manifest>
+                   <attribute name="Product-Version"
+                         value="${j2ssh.version.major}.${j2ssh.version.minor}.${j2ssh.version.build}"/>
+                 </manifest>
+             </jar>
+
+             <jar jarfile="${build.dist.lib}/j2ssh-dameon-${j2ssh.version.major}.${j2ssh.version.minor}.${j2ssh.version.build}.jar" basedir="${build.dist.classes}">
+                <include name="com/sshtools/daemon/**/*.class"/>
+                <exclude name="com/sshtools/daemon/windows/**/*.*"/>
+                <exclude name="com/sshtools/daemon/linux/**/*.*"/>
+                <manifest>
+                   <attribute name="Product-Version" 
+                         value="${j2ssh.version.major}.${j2ssh.version.minor}.${j2ssh.version.build}"/>
+                 </manifest>
+             </jar>
+             
+   	         <jar jarfile="${build.dist.lib}/j2ssh-common-${j2ssh.version.major}.${j2ssh.version.minor}.${j2ssh.version.build}.jar" basedir="${build.dist.classes}">
+                <include name="com/sshtools/common/**/*.class"/>
+                <include name="com/sshtools/common/**/*.png"/>
+                <include name="com/sshtools/common/**/*.gif"/>
+                <manifest>
+                   <attribute name="Product-Version"
+                         value="${j2ssh.version.major}.${j2ssh.version.minor}.${j2ssh.version.build}"/>
+                 </manifest>
+             </jar>
+    </target>
+
+        <!-- JAVADOCS TARGET -->
+    <target name="javadoc" depends="build">
+
+     <mkdir dir="${build.docs}"/>
+
+     <javadoc packagenames="com.sshtools.*"
+           sourcepath="${build.src}"
+           defaultexcludes="yes"
+           destdir="${build.docs}"
+           author="true"
+           version="true"
+           use="true"
+           windowtitle="J2SSH Javadocs"
+	       notree="true">
+    <doctitle><![CDATA[<h1>SSHTools J2SSH</h1><br>
+    <p>SSH (Secure Shell) is a program to log into another computer over a network, to execute commands in a
+remote machine and to move files from one machine to another. It provides strong authentication and secure
+communication over insecure networks.</p>]]></doctitle>
+<bottom><![CDATA[<i>Copyright &#169; 2002-2003 Lee David Painter & Contributors. All Rights Reserved.</i>]]></bottom>
+</javadoc>
+
+ </target>
+
+  <!-- Clean up all the generated files -->
+  <target name="clean" >
+    <!-- delete the classpath -->
+    <delete dir="${build.dist.classes}"/>
+    <delete dir="${build.dist.lib}"/>
+    <delete dir="${build.dist}"/>
+  </target>
+
+
+  <target name="release">
+
+   <delete dir="./release"/>
+   <mkdir dir="./release"/>
+   <zip zipfile="./release/j2ssh-${j2ssh.version.major}.${j2ssh.version.minor}.${j2ssh.version.build}-src.zip">
+     <zipfileset dir="${build.src}" includes="**/*.java" prefix="j2ssh/src"/>
+     <zipfileset dir="${build.src}" includes="**/*.png" prefix="j2ssh/src"/>
+     <zipfileset dir="${build.src}" includes="**/*.gif" prefix="j2ssh/src"/>
+     <zipfileset dir="${build.src}" includes="**/*.ico" prefix="j2ssh/src"/>
+     <zipfileset dir="${build.src}" includes="**/*.xpm" prefix="j2ssh/src"/>
+     <zipfileset dir="${build.conf}" includes="*.xml" prefix="j2ssh/conf"/>
+     <zipfileset dir="${build.dependency}" includes="*.jar" prefix="j2ssh/lib"/>
+     <zipfileset dir="${build.dependency}" includes="*.LICENSE" prefix="j2ssh/lib"/>
+     <zipfileset dir="${build.examples}" includes="*.java" prefix="j2ssh/examples"/>
+     <zipfileset dir="." includes="build.xml,j2ssh.properties,INSTALL,LICENSE,ChangeLog,AUTHORS" prefix="j2ssh"/>
+   </zip>
+
+   <unzip src="./release/j2ssh-${j2ssh.version.major}.${j2ssh.version.minor}.${j2ssh.version.build}-src.zip"
+         dest="./release/j2ssh"/>
+   <tar tarfile="./release/j2ssh-${j2ssh.version.major}.${j2ssh.version.minor}.${j2ssh.version.build}-src.tar"
+        basedir="./release/j2ssh"/>
+    <gzip zipfile="./release/j2ssh-${j2ssh.version.major}.${j2ssh.version.minor}.${j2ssh.version.build}-src.tar.gz"
+       src="./release/j2ssh-${j2ssh.version.major}.${j2ssh.version.minor}.${j2ssh.version.build}-src.tar"/>
+
+  <delete file="./release/j2ssh-${j2ssh.version.major}.${j2ssh.version.minor}.${j2ssh.version.build}-src.tar"/>
+  <delete dir="./release/j2ssh"/>
+  </target>
+</project>
diff --git a/conf/authorization.xml b/conf/authorization.xml
new file mode 100644
index 0000000000000000000000000000000000000000..b78294933a83ee6dfada0542c0f1958ab597e96a
--- /dev/null
+++ b/conf/authorization.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- Sshtools User Authorization File -->
+<AuthorizedKeys>
+  <!-- Enter authorized public key elements here -->
+  <!--<Key>dsa.pub</Key> -->
+
+</AuthorizedKeys>
diff --git a/conf/automation.xml b/conf/automation.xml
new file mode 100644
index 0000000000000000000000000000000000000000..fddba1b588daca2d772c3a6257be1bc01a12bc78
--- /dev/null
+++ b/conf/automation.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- SSHTools automation file, defines mappings for automated configuration -->
+<Automation>
+    <RemoteIdentification defaultName="OpenSSH">
+       <Rule startsWith="OpenSSH"/>
+       <Rule startsWith="OpenSSH" contains="3.4" name="OpenSSH 3.4" priority="1"/>
+       <AuthorizedKeysFormat implementationClass="com.sshtools.common.automate.OpenSSHAuthorizedKeysFormat"
+                             defaultPath=".ssh/authorized_keys"/>
+    </RemoteIdentification>
+    <RemoteIdentification defaultName="SSHTools">
+       <Rule startsWith="http://www.sshtools.com"/>
+       <AuthorizedKeysFormat implementationClass="com.sshtools.common.automate.SshtoolsAuthorizedKeysFormat"
+                             defaultPath=".ssh2/authorization.xml"/>
+    </RemoteIdentification>
+    <RemoteIdentification defaultName="SSH2">
+       <Rule contains="SSH Secure Shell"/>
+       <AuthorizedKeysFormat implementationClass="com.sshtools.common.automate.SSH2AuthorizedKeysFormat"
+                             defaultPath=".ssh2/authorization"/>
+    </RemoteIdentification>
+
+</Automation>
\ No newline at end of file
diff --git a/conf/platform.xml b/conf/platform.xml
new file mode 100644
index 0000000000000000000000000000000000000000..aa4516b574e38fd43ead91d321743cc46bf2e49f
--- /dev/null
+++ b/conf/platform.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Platform configuration file - Determines the behaviour of platform specific services
+-->
+<PlatformConfiguration>
+   <!-- The process provider for executing and redirecting a process -->
+   <NativeProcessProvider></NativeProcessProvider>
+   <!-- The authentication provider for authenticating users and obtaining user information -->
+   <NativeAuthenticationProvider></NativeAuthenticationProvider>
+   <!-- The file system provider for SFTP -->
+   <NativeFileSystemProvider></NativeFileSystemProvider>
+   <!-- Native settings which may be used by the process or authentication provider -->
+   <!-- Add native settings here -->
+   <!-- <NativeSetting Name="AuthenticateOnDomain" Value="."/> -->
+</PlatformConfiguration>
diff --git a/conf/sshtools.xml b/conf/sshtools.xml
new file mode 100644
index 0000000000000000000000000000000000000000..30e49245a9b41085b20d1a0f78e29f6e68540c5e
--- /dev/null
+++ b/conf/sshtools.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- The Java SSH API Configuration file -->
+<SshAPIConfiguration>
+  
+  <!-- The Cipher configuration, add or overide default cipher implementations -->
+  <CipherConfiguration>
+    <DefaultAlgorithm>blowfish-cbc</DefaultAlgorithm>
+  </CipherConfiguration>
+  <!-- The Message Authentication Code configuration, add or overide default mac implementations -->
+  <MacConfiguration>
+    <DefaultAlgorithm>hmac-md5</DefaultAlgorithm>
+  </MacConfiguration>
+  <!-- The Compression configuration, add or overide default compression implementations -->
+  <CompressionConfiguration>
+    <DefaultAlgorithm>none</DefaultAlgorithm>
+  </CompressionConfiguration>
+  <!-- The Public Key configuration, add or overide default public key implementations -->
+  <PublicKeyConfiguration>
+   <DefaultAlgorithm>ssh-dss</DefaultAlgorithm>
+   
+   <DefaultPublicFormat>SECSH-PublicKey-Base64Encoded</DefaultPublicFormat>
+   <DefaultPrivateFormat>SSHTools-PrivateKey-Base64Encoded</DefaultPrivateFormat>
+  </PublicKeyConfiguration>
+  <!-- The Authentication configuration, add or overide default authentication implementations  -->
+  <AuthenticationConfiguration>
+     
+  </AuthenticationConfiguration>
+  <!-- The Key Exchange configuration, add or overide default Key Exchange implementations -->
+  <KeyExchangeConfiguration>
+    <DefaultAlgorithm>diffie-hellman-group1-sha1</DefaultAlgorithm>
+  </KeyExchangeConfiguration>
+</SshAPIConfiguration>
diff --git a/examples/KBIConnect.java b/examples/KBIConnect.java
new file mode 100644
index 0000000000000000000000000000000000000000..6cfd3c59dcb6e2caa4aa8eef44931cca3edc4d66
--- /dev/null
+++ b/examples/KBIConnect.java
@@ -0,0 +1,133 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import com.sshtools.j2ssh.SshClient;
+import com.sshtools.j2ssh.authentication.AuthenticationProtocolState;
+import com.sshtools.j2ssh.authentication.KBIAuthenticationClient;
+import com.sshtools.j2ssh.authentication.KBIPrompt;
+import com.sshtools.j2ssh.authentication.KBIRequestHandler;
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+import com.sshtools.j2ssh.configuration.SshConnectionProperties;
+import com.sshtools.j2ssh.io.IOStreamConnector;
+import com.sshtools.j2ssh.io.IOStreamConnectorState;
+import com.sshtools.j2ssh.session.SessionChannelClient;
+/*import java.util.logging.FileHandler;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.logging.SimpleFormatter;*/
+/**
+ * Demonstrates a simple password connection to an SSH server.
+ *
+ * @author <A HREF="mailto:lee@sshtools.com">Lee David Painter</A>
+ * @version $Id: KBIConnect.java,v 1.8 2003/07/16 10:42:07 t_magicthize Exp $
+ *
+ * @created 20 December 2002
+ */
+public class KBIConnect {
+  private static BufferedReader reader =
+      new BufferedReader(new InputStreamReader(System.in));
+  /**
+   * The main program for the PasswordConnect class
+   *
+   * @param args The command line arguments
+   */
+  public static void main(String args[]) {
+    try {
+      // Setup a logfile
+      /*Handler fh = new FileHandler("example.log");
+      fh.setFormatter(new SimpleFormatter());
+      Logger.getLogger("com.sshtools").setUseParentHandlers(false);
+      Logger.getLogger("com.sshtools").addHandler(fh);
+      Logger.getLogger("com.sshtools").setLevel(Level.ALL);*/
+      // Configure J2SSH (This will attempt to install the bouncycastle provider
+      // under jdk 1.3.1)
+      ConfigurationLoader.initialize(false);
+      System.out.print("Connect to host? ");
+      System.out.print("Connect to host? ");
+      String hostname = reader.readLine();
+      // Make a client connection
+      SshClient ssh = new SshClient();
+      SshConnectionProperties properties = new SshConnectionProperties();
+      properties.setHost(hostname);
+      // Connect to the host
+      ssh.connect(properties);
+      // Create a password authentication instance
+      KBIAuthenticationClient kbi = new KBIAuthenticationClient();
+      // Get the users name
+      System.out.print("Username? ");
+      // Read the password
+      String username = reader.readLine();
+      kbi.setUsername(username);
+      kbi.setKBIRequestHandler(new KBIRequestHandler() {
+        public void showPrompts(String name, String instructions,
+                                KBIPrompt[] prompts) {
+          System.out.println(name);
+          System.out.println(instructions);
+          String response;
+          if (prompts != null) {
+            for (int i = 0; i < prompts.length; i++) {
+              System.out.print(prompts[i].getPrompt() + ": ");
+              try {
+                response = reader.readLine();
+                prompts[i].setResponse(response);
+              }
+              catch (IOException ex) {
+                prompts[i].setResponse("");
+                ex.printStackTrace();
+              }
+            }
+          }
+        }
+      });
+      // Try the authentication
+      int result = ssh.authenticate(kbi);
+      // Evaluate the result
+      if (result == AuthenticationProtocolState.COMPLETE) {
+        // The connection is authenticated we can now do some real work!
+        SessionChannelClient session = ssh.openSessionChannel();
+        if(!session.requestPseudoTerminal("vt100", 80, 24, 0, 0, ""))
+          System.out.println("Failed to allocate a pseudo terminal");
+        if(session.startShell()) {
+          IOStreamConnector input =
+              new IOStreamConnector(System.in, session.getOutputStream());
+          IOStreamConnector output =
+              new IOStreamConnector(session.getInputStream(), System.out);
+          output.getState().waitForState(IOStreamConnectorState.CLOSED);
+        }else
+          System.out.println("Failed to start the users shell");
+        ssh.disconnect();
+      }
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+}
diff --git a/examples/PasswordConnect.java b/examples/PasswordConnect.java
new file mode 100644
index 0000000000000000000000000000000000000000..fbbdcaf1b0ca0ae5dc98f44b42d8de5745bb8b73
--- /dev/null
+++ b/examples/PasswordConnect.java
@@ -0,0 +1,122 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import com.sshtools.j2ssh.SshClient;
+import com.sshtools.j2ssh.authentication.AuthenticationProtocolState;
+import com.sshtools.j2ssh.authentication.PasswordAuthenticationClient;
+import com.sshtools.j2ssh.configuration.SshConnectionProperties;
+import com.sshtools.j2ssh.connection.ChannelState;
+import com.sshtools.j2ssh.io.IOStreamConnector;
+import com.sshtools.j2ssh.session.SessionChannelClient;
+import com.sshtools.j2ssh.transport.TransportProtocolEventHandler;
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+// JDK > 1.4 ONLY
+/*import java.util.logging.FileHandler;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.logging.SimpleFormatter;*/
+/**
+ * Demonstrates a simple password connection to an SSH server.
+ *
+ * @author <A HREF="mailto:lee@sshtools.com">Lee David Painter</A>
+ * @version $Id: PasswordConnect.java,v 1.12 2003/07/16 10:42:07 t_magicthize Exp $
+ *
+ * @created 20 December 2002
+ */
+public class PasswordConnect {
+  /**
+   * The main program for the PasswordConnect class
+   *
+   * @param args The command line arguments
+   */
+  public static void main(String args[]) {
+    try {
+      // JDK > 1.4 ONLY
+      /*Handler fh = new FileHandler("example.log");
+      fh.setFormatter(new SimpleFormatter());
+      Logger.getLogger("com.sshtools").setUseParentHandlers(false);
+      Logger.getLogger("com.sshtools").addHandler(fh);
+      Logger.getLogger("com.sshtools").setLevel(Level.ALL);*/
+      // Configure J2SSH (This will attempt to install the bouncycastle provider
+      // under jdk 1.3.1)
+      ConfigurationLoader.initialize(false);
+      BufferedReader reader =
+          new BufferedReader(new InputStreamReader(System.in));
+      System.out.print("Connect to host? ");
+      String hostname = reader.readLine();
+      // Make a client connection
+      SshClient ssh = new SshClient();
+      ssh.setSocketTimeout(30000);
+      SshConnectionProperties properties = new SshConnectionProperties();
+      properties.setHost(hostname);
+      properties.setPrefPublicKey("ssh-dss");
+      // Connect to the host
+      ssh.connect(properties);
+      // Create a password authentication instance
+      PasswordAuthenticationClient pwd = new PasswordAuthenticationClient();
+      // Get the users name
+      System.out.print("Username? ");
+      // Read the password
+      String username = reader.readLine();
+      pwd.setUsername(username);
+      // Get the password
+      System.out.print("Password? ");
+      String password = reader.readLine();
+      pwd.setPassword(password);
+      // Try the authentication
+      int result = ssh.authenticate(pwd);
+      // Evaluate the result
+      if (result == AuthenticationProtocolState.COMPLETE) {
+        // The connection is authenticated we can now do some real work!
+        SessionChannelClient session = ssh.openSessionChannel();
+        if(!session.requestPseudoTerminal("vt100", 80, 24, 0, 0, ""))
+          System.out.println("Failed to allocate a pseudo terminal");
+        if (session.startShell()) {
+          IOStreamConnector input =
+              new IOStreamConnector();
+          IOStreamConnector output =
+              new IOStreamConnector();
+          IOStreamConnector error =
+              new IOStreamConnector();
+          output.setCloseOutput(false);
+          input.setCloseInput(false);
+          error.setCloseOutput(false);
+          input.connect(System.in, session.getOutputStream());
+          output.connect(session.getInputStream(), System.out);
+          error.connect(session.getStderrInputStream(), System.out);
+          session.getState().waitForState(ChannelState.CHANNEL_CLOSED);
+        }else
+          System.out.println("Failed to start the users shell");
+        ssh.disconnect();
+      }
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+}
diff --git a/examples/PortForwarding.java b/examples/PortForwarding.java
new file mode 100644
index 0000000000000000000000000000000000000000..af653f2df8bd4daf0db53554961a8bba11be830e
--- /dev/null
+++ b/examples/PortForwarding.java
@@ -0,0 +1,100 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import com.sshtools.j2ssh.SshClient;
+import com.sshtools.j2ssh.authentication.AuthenticationProtocolState;
+import com.sshtools.j2ssh.authentication.PasswordAuthenticationClient;
+import com.sshtools.j2ssh.forwarding.ForwardingClient;
+import com.sshtools.j2ssh.transport.TransportProtocolState;
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+/*import java.util.logging.FileHandler;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.logging.SimpleFormatter;*/
+/**
+ * Demonstrates a starting both a local and remote forwarding configuration.
+ *
+ * @author <A HREF="mailto:lee@sshtools.com">Lee David Painter</A>
+ * @version $Id: PortForwarding.java,v 1.9 2003/07/16 10:42:07 t_magicthize Exp $
+ *
+ * @created 20 December 2002
+ */
+public class PortForwarding {
+  /**
+   * The main program for the PortForwarding class
+   *
+   * @param args The command line arguments
+   */
+  public static void main(String args[]) {
+    try {
+      // Setup a logfile
+      /*Handler fh = new FileHandler("example.log");
+      fh.setFormatter(new SimpleFormatter());
+      Logger.getLogger("com.sshtools").setUseParentHandlers(false);
+      Logger.getLogger("com.sshtools").addHandler(fh);
+      Logger.getLogger("com.sshtools").setLevel(Level.ALL);*/
+      // Configure J2SSH (This will attempt to install the bouncycastle provider
+      // under jdk 1.3.1)
+      ConfigurationLoader.initialize(false);
+      BufferedReader reader =
+          new BufferedReader(new InputStreamReader(System.in));
+      System.out.print("Connect to host? ");
+      String hostname = reader.readLine();
+      // Make a client connection
+      SshClient ssh = new SshClient();
+      // Connect to the hos
+      ssh.connect(hostname);
+      // Create a password authentication instance
+      PasswordAuthenticationClient pwd = new PasswordAuthenticationClient();
+      // Get the users name
+      System.out.print("Username? ");
+      String username = reader.readLine();
+      pwd.setUsername(username);
+      // Get the password
+      System.out.print("Password? ");
+      String password = reader.readLine();
+      pwd.setPassword(password);
+      // Try the authentication
+      int result = ssh.authenticate(pwd);
+      // Evaluate the result
+      if (result == AuthenticationProtocolState.COMPLETE) {
+        ForwardingClient forwarding = ssh.getForwardingClient();
+        forwarding.addLocalForwarding("Test Local", "0.0.0.0", 8081,
+                                      "127.0.0.1", 80);
+        forwarding.startLocalForwarding("Test Local");
+        forwarding.addRemoteForwarding("Test Remote", "0.0.0.0", 8081,
+                                       "127.0.0.1", 8080);
+        forwarding.startRemoteForwarding("Test Remote");
+      }
+      ssh.getConnectionState().waitForState(TransportProtocolState.DISCONNECTED);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+}
diff --git a/examples/PublicKeyConnect.java b/examples/PublicKeyConnect.java
new file mode 100644
index 0000000000000000000000000000000000000000..dc1f9d51b7eca71759ae295ef8c764b530e130ea
--- /dev/null
+++ b/examples/PublicKeyConnect.java
@@ -0,0 +1,129 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import com.sshtools.j2ssh.SshClient;
+import com.sshtools.j2ssh.authentication.AuthenticationProtocolState;
+import com.sshtools.j2ssh.authentication.PublicKeyAuthenticationClient;
+import com.sshtools.j2ssh.io.IOStreamConnector;
+import com.sshtools.j2ssh.io.IOStreamConnectorState;
+import com.sshtools.j2ssh.session.SessionChannelClient;
+import com.sshtools.j2ssh.transport.publickey.SshPrivateKey;
+import com.sshtools.j2ssh.transport.publickey.SshPrivateKeyFile;
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+/*import java.util.logging.FileHandler;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.logging.SimpleFormatter;*/
+/**
+ * Demonstrates a public key authentication connection to an SSH server.
+ *
+ * @author <A HREF="mailto:lee@sshtools.com">Lee David Painter</A>
+ * @version $Id: PublicKeyConnect.java,v 1.8 2003/07/16 10:42:08 t_magicthize Exp $
+ *
+ * @created 20 December 2002
+ */
+public class PublicKeyConnect {
+  /**
+   * The main program for the PublicKeyConnect class
+   *
+   * @param args The command line arguments
+   */
+  public static void main(String args[]) {
+    try {
+      // Setup a logfile
+      /*Handler fh = new FileHandler("example.log");
+      fh.setFormatter(new SimpleFormatter());
+      Logger.getLogger("com.sshtools").setUseParentHandlers(false);
+      Logger.getLogger("com.sshtools").addHandler(fh);
+      Logger.getLogger("com.sshtools").setLevel(Level.ALL);*/
+      // Configure J2SSH (This will attempt to install the bouncycastle provider
+      // under jdk 1.3.1)
+      ConfigurationLoader.initialize(false);
+      BufferedReader reader =
+          new BufferedReader(new InputStreamReader(System.in));
+      System.out.print("Connect to host? ");
+      String hostname = reader.readLine();
+      // Make a client connection
+      SshClient ssh = new SshClient();
+      // Connect to the host
+      ssh.connect(hostname);
+      PublicKeyAuthenticationClient pk = new PublicKeyAuthenticationClient();
+      // Get the users name
+      System.out.print("Username? ");
+      String username = reader.readLine();
+      pk.setUsername(username);
+      // Get the private key file
+      System.out.print("Path to private key file? ");
+      String filename = reader.readLine();
+      // Open up the private key file
+      SshPrivateKeyFile file =
+          SshPrivateKeyFile.parse(new File(filename));
+      // If the private key is passphrase protected then ask for the passphrase
+      String passphrase = null;
+      if (file.isPassphraseProtected()) {
+        System.out.print("Enter passphrase? ");
+        passphrase = reader.readLine();
+      }
+      // Get the key
+      SshPrivateKey key = file.toPrivateKey(passphrase);
+      pk.setKey(key);
+      // Try the authentication
+      int result = ssh.authenticate(pk);
+      // Evaluate the result
+      if (result == AuthenticationProtocolState.COMPLETE) {
+        // The connection is authenticated we can now do some real work!
+        SessionChannelClient session = ssh.openSessionChannel();
+        if(!session.requestPseudoTerminal("vt100", 80, 24, 0, 0, ""))
+          System.out.println("Failed to allocate a pseudo terminal");
+        if(session.startShell()) {
+          InputStream in = session.getInputStream();
+          OutputStream out = session.getOutputStream();
+          IOStreamConnector input =
+              new IOStreamConnector(System.in, session.getOutputStream());
+          IOStreamConnector output =
+              new IOStreamConnector(session.getInputStream(), System.out);
+          output.getState().waitForState(IOStreamConnectorState.CLOSED);
+        } else
+          System.out.println("Failed to start the users shell");
+        ssh.disconnect();
+      }
+      if (result == AuthenticationProtocolState.PARTIAL) {
+        System.out.println("Further authentication requried!");
+      }
+      if (result == AuthenticationProtocolState.FAILED) {
+        System.out.println("Authentication failed!");
+      }
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+}
diff --git a/examples/SftpConnect.java b/examples/SftpConnect.java
new file mode 100644
index 0000000000000000000000000000000000000000..5c68dcc4f68ca9b09d8ad4cabb2ef5003dd52652
--- /dev/null
+++ b/examples/SftpConnect.java
@@ -0,0 +1,125 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import com.sshtools.j2ssh.SshClient;
+import com.sshtools.j2ssh.authentication.AuthenticationProtocolState;
+import com.sshtools.j2ssh.authentication.PasswordAuthenticationClient;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.session.SessionChannelClient;
+import com.sshtools.j2ssh.sftp.FileAttributes;
+import com.sshtools.j2ssh.sftp.SftpFile;
+import com.sshtools.j2ssh.sftp.SftpFileOutputStream;
+import com.sshtools.j2ssh.SftpClient;
+import java.io.*;
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+/*import java.util.logging.FileHandler;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.logging.SimpleFormatter;*/
+/**
+ * Demonstrates a simple password connection to an SSH server.
+ *
+ * @author <A HREF="mailto:lee@sshtools.com">Lee David Painter</A>
+ * @version $Id: SftpConnect.java,v 1.8 2003/07/16 10:42:08 t_magicthize Exp $
+ *
+ * @created 20 December 2002
+ */
+public class SftpConnect {
+  /**
+   * The main program for the PasswordConnect class
+   *
+   * @param args The command line arguments
+   */
+  public static void main(String args[]) {
+    try {
+      // Setup a logfile
+      /*Handler fh = new FileHandler("example.log");
+      fh.setFormatter(new SimpleFormatter());
+      Logger.getLogger("com.sshtools").setUseParentHandlers(false);
+      Logger.getLogger("com.sshtools").addHandler(fh);
+      Logger.getLogger("com.sshtools").setLevel(Level.ALL);*/
+      // Configure J2SSH (This will attempt to install the bouncycastle provider
+      // under jdk 1.3.1)
+      ConfigurationLoader.initialize(false);
+      BufferedReader reader =
+          new BufferedReader(new InputStreamReader(System.in));
+      System.out.print("Connect to host? ");
+      String hostname = reader.readLine();
+      // Make a client connection
+      SshClient ssh = new SshClient();
+      // Connect to the host
+      ssh.connect(hostname);
+      // Create a password authentication instance
+      PasswordAuthenticationClient pwd = new PasswordAuthenticationClient();
+      // Get the users name
+      System.out.print("Username? ");
+      String username = reader.readLine();
+      pwd.setUsername(username);
+      // Get the password
+      System.out.print("Password? ");
+      String password = reader.readLine();
+      pwd.setPassword(password);
+      // Try the authentication
+      int result = ssh.authenticate(pwd);
+      // Evaluate the result
+      if (result == AuthenticationProtocolState.COMPLETE) {
+        // The connection is authenticated we can now do some real work!
+        SftpClient sftp = ssh.openSftpClient();
+        // Make a directory
+        try {
+          sftp.mkdir("j2ssh");
+        }
+        catch (IOException ex) {
+        }
+        // Change directory
+        sftp.cd("j2ssh");
+        System.out.println(sftp.pwd());
+        // Change the mode
+        sftp.chmod(0777, "j2ssh");
+        sftp.lcd("c:/");
+        // Upload a file
+        sftp.put("system.gif");
+        // Change the local directory
+        sftp.lcd("localdir");
+        // Download a file
+        sftp.get("somefile.txt", "anotherfile.txt");
+        // Remove a directory or file
+        sftp.rm("j2ssh");
+        // Quit
+        sftp.quit();
+        ssh.disconnect();
+      }
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+    finally {
+      System.exit(0);
+    }
+  }
+}
diff --git a/j2ssh.properties b/j2ssh.properties
new file mode 100644
index 0000000000000000000000000000000000000000..a55b44acc7ed4eb9043264ace46494decf642ed1
--- /dev/null
+++ b/j2ssh.properties
@@ -0,0 +1,10 @@
+# J2SSH Project version
+j2ssh.project.name=j2ssh
+j2ssh.project.type=
+j2ssh.version.major=0
+j2ssh.version.minor=2
+j2ssh.version.build=9
+core.project.name=core
+common.project.name=common
+sshant.project.name=ant
+daemon.project.name=daemon
diff --git a/src/com/sshtools/ant/ConditionalTasks.java b/src/com/sshtools/ant/ConditionalTasks.java
new file mode 100644
index 0000000000000000000000000000000000000000..33d9f2405325e075cc63c288de72ee15aaf8a239
--- /dev/null
+++ b/src/com/sshtools/ant/ConditionalTasks.java
@@ -0,0 +1,152 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.ant;
+
+import org.apache.tools.ant.*;
+
+import java.io.*;
+
+import java.util.*;
+
+
+public class ConditionalTasks extends Task implements TaskContainer {
+    private ArrayList tasks = new ArrayList();
+    private String dirs;
+    private String files;
+    private String name;
+    private String family;
+
+    public ConditionalTasks() {
+    }
+
+    public void setFamily(String family) {
+        this.family = family;
+    }
+
+    public void setDirs(String dirs) {
+        this.dirs = dirs;
+    }
+
+    public void setFiles(String files) {
+        this.files = files;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public void addTask(Task task) {
+        tasks.add(tasks.size(), task);
+    }
+
+    public void execute() {
+        if ((dirs == null) && (files == null)) {
+            throw new BuildException(
+                "ConditionalTasks: You must supply at least one of either the files or dirs properties");
+        }
+
+        if (name == null) {
+            throw new BuildException(
+                "ConditionalTasks: You must supply a name for these conditional tasks!");
+        }
+
+        log("Verifying conditions for " + name);
+
+        if (family != null) {
+            StringTokenizer tokenizer = new StringTokenizer(dirs, ",");
+            boolean familyMatch = false;
+
+            while (tokenizer.hasMoreElements() && !familyMatch) {
+                String condition = (String) tokenizer.nextElement();
+
+                if (condition.equals(family)) {
+                    familyMatch = true;
+                }
+            }
+
+            if (!familyMatch) {
+                log("ConditionalTasks: OS Family '" + family +
+                    "' does not match; " + name + " will not be performed");
+
+                return;
+            }
+        }
+
+        File basedir = getProject().getBaseDir();
+
+        if (dirs != null) {
+            StringTokenizer tokenizer = new StringTokenizer(dirs, ",");
+            File f;
+
+            while (tokenizer.hasMoreElements()) {
+                String condition = (String) tokenizer.nextElement();
+                f = new File(basedir, condition);
+
+                if (!f.exists()) {
+                    f = new File(condition);
+
+                    if (!f.exists()) {
+                        log("ConditionalTasks: Directory '" + condition +
+                            "' does not exist; " + name +
+                            " will not be performed");
+
+                        return;
+                    }
+                }
+            }
+        }
+
+        if (files != null) {
+            StringTokenizer tokenizer = new StringTokenizer(files, ",");
+            File f;
+
+            while (tokenizer.hasMoreElements()) {
+                String condition = (String) tokenizer.nextElement();
+                f = new File(basedir, condition);
+
+                if (!f.exists()) {
+                    log("ConditionalTasks: File '" + condition +
+                        "' does not exist; " + name + " will not be performed");
+
+                    return;
+                }
+            }
+        }
+
+        System.out.println("Executing Conditional Tasks");
+
+        Iterator it = tasks.iterator();
+        Task task;
+
+        while (it.hasNext()) {
+            task = (Task) it.next();
+            task.setProject(getProject());
+            task.setOwningTarget(getOwningTarget());
+            task.setLocation(getLocation());
+            task.perform();
+        }
+    }
+}
diff --git a/src/com/sshtools/ant/Sftp.java b/src/com/sshtools/ant/Sftp.java
new file mode 100644
index 0000000000000000000000000000000000000000..bb98c6e18fcccb57ece6bf0f2bcf32bc86f6370d
--- /dev/null
+++ b/src/com/sshtools/ant/Sftp.java
@@ -0,0 +1,728 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.ant;
+
+import com.sshtools.j2ssh.*;
+import com.sshtools.j2ssh.sftp.*;
+
+import org.apache.tools.ant.*;
+import org.apache.tools.ant.types.*;
+import org.apache.tools.ant.util.*;
+
+import java.io.*;
+
+import java.util.*;
+
+
+/**
+ * Basic SFTP client. Performs the following actions:
+ * <ul>
+ *   <li> <strong>put</strong> - send files to a remote server. This is the
+ *   default action.</li>
+ *   <li> <strong>get</strong> - retreive files from a remote server.</li>
+ *   <li> <strong>del</strong> - delete files from a remote server.</li>
+ *   <li> <strong>chmod</strong> - changes the mode of files on a remote server.</li>
+ * </ul>
+ *
+ */
+public class Sftp extends SshSubTask {
+    protected static final int SEND_FILES = 0;
+    protected static final int GET_FILES = 1;
+    protected static final int DEL_FILES = 2;
+    protected static final int MK_DIR = 4;
+    protected static final int CHMOD = 5;
+
+    //private Ssh parent = null;
+    protected static final String[] ACTION_STRS = {
+        "Sending", "Getting", "Deleting", "Listing", "Making directory", "chmod"
+    };
+    protected static final String[] COMPLETED_ACTION_STRS = {
+        "Sent", "Retrieved", "Deleted", "Listed", "Created directory",
+        "Mode changed"
+    };
+    private String remotedir = ".";
+
+    //  private File listing;
+    private boolean verbose = false;
+    private boolean newerOnly = false;
+    private int action = SEND_FILES;
+    private Vector filesets = new Vector();
+    private Vector dirCache = new Vector();
+    private int transferred = 0;
+    private String remoteFileSep = "/";
+    private boolean skipFailedTransfers = false;
+    private int skipped = 0;
+    private boolean ignoreNoncriticalErrors = false;
+    private String chmod = "777";
+    private FileUtils fileUtils = FileUtils.newFileUtils();
+
+    /**
+     * Set to true to receive notification about each file as it is
+     * transferred.
+     */
+    public void setVerbose(boolean verbose) {
+        this.verbose = verbose;
+    }
+
+    /**
+     * Sets the remote working directory
+     * */
+    public void setRemotedir(String remotedir) {
+        this.remotedir = remotedir;
+    }
+
+    /**
+         * A synonym for <tt>depends</tt>. Set to true to transmit only new or changed
+     * files.
+     */
+    public void setNewer(boolean newer) {
+        this.newerOnly = newer;
+    }
+
+    /**
+     * Set to true to transmit only files that are new or changed from their
+     * remote counterparts. The default is to transmit all files.
+     */
+    public void setDepends(boolean depends) {
+        this.newerOnly = depends;
+    }
+
+    /**
+     * Sets the file permission mode (Unix only) for files sent to the server.
+     */
+    public void setChmod(String theMode) {
+        this.chmod = theMode;
+    }
+
+    /**
+     *  A set of files to upload or download
+     */
+    public void addFileset(FileSet set) {
+        filesets.addElement(set);
+    }
+
+    /**
+     * Sets the FTP action to be taken. Currently accepts "put", "get", "del",
+     * "mkdir" and "list".
+     *
+     * @deprecated setAction(String) is deprecated and is replaced with
+     *      setAction(FTP.Action) to make Ant's Introspection mechanism do the
+     *      work and also to encapsulate operations on the type in its own
+     *      class.
+     * @ant.attribute ignore="true"
+     */
+
+    /* public void setAction(String action) throws BuildException {
+       log("DEPRECATED - The setAction(String) method has been deprecated."
+           + " Use setAction(FTP.Action) instead.");
+       Action a = new Action();
+       a.setValue(action);
+       this.action = a.getAction();
+     }*/
+
+    /**
+     * Sets the FTP action to be taken. Currently accepts "put", "get", "del",
+     * "mkdir", "chmod" and "list".
+     */
+    public void setAction(Action action) throws BuildException {
+        this.action = action.getAction();
+    }
+
+    /**
+     * If true, enables unsuccessful file put, delete and get
+     * operations to be skipped with a warning and the remainder
+     * of the files still transferred.
+     */
+    public void setSkipFailedTransfers(boolean skipFailedTransfers) {
+        this.skipFailedTransfers = skipFailedTransfers;
+    }
+
+    /**
+     * set the flag to skip errors on directory creation.
+     * (and maybe later other server specific errors)
+     */
+    public void setIgnoreNoncriticalErrors(boolean ignoreNoncriticalErrors) {
+        this.ignoreNoncriticalErrors = ignoreNoncriticalErrors;
+    }
+
+    /** Checks to see that all required parameters are set.  */
+    protected void checkConfiguration() throws BuildException {
+        /* if ( (action == LIST_FILES) && (listing == null)) {
+           throw new BuildException("listing attribute must be set for list "
+                                    + "action!");
+         }*/
+        if ((action == MK_DIR) && (remotedir == null)) {
+            throw new BuildException("remotedir attribute must be set for " +
+                "mkdir action!");
+        }
+
+        if ((action == CHMOD) && (chmod == null)) {
+            throw new BuildException("chmod attribute must be set for chmod " +
+                "action!");
+        }
+    }
+
+    /**
+     * For each file in the fileset, do the appropriate action: send, get,
+     * delete, or list.
+     */
+    protected int transferFiles(SftpClient sftp, FileSet fs)
+        throws IOException, BuildException {
+        FileScanner ds;
+
+        if (action == SEND_FILES) {
+            ds = fs.getDirectoryScanner(parent.getProject());
+        } else {
+            ds = new SftpDirectoryScanner(sftp);
+            fs.setupDirectoryScanner(ds, parent.getProject());
+            ds.scan();
+        }
+
+        String[] dsfiles = ds.getIncludedFiles();
+        String dir = null;
+
+        if ((ds.getBasedir() == null) &&
+                ((action == SEND_FILES) || (action == GET_FILES))) {
+            throw new BuildException("the dir attribute must be set for send " +
+                "and get actions");
+        } else {
+            if ((action == SEND_FILES) || (action == GET_FILES)) {
+                dir = ds.getBasedir().getAbsolutePath();
+            }
+        }
+
+        // If we are doing a listing, we need the output stream created now.
+        BufferedWriter bw = null;
+
+        try {
+            /*if (action == LIST_FILES) {
+              File pd = fileUtils.getParentFile(listing);
+              if (!pd.exists()) {
+                pd.mkdirs();
+              }
+              bw = new BufferedWriter(new FileWriter(listing));
+                   }*/
+            for (int i = 0; i < dsfiles.length; i++) {
+                switch (action) {
+                case SEND_FILES: {
+                    sendFile(sftp, dir, dsfiles[i]);
+
+                    break;
+                }
+
+                case GET_FILES: {
+                    getFile(sftp, dir, dsfiles[i]);
+
+                    break;
+                }
+
+                case DEL_FILES: {
+                    delFile(sftp, dsfiles[i]);
+
+                    break;
+                }
+
+                case CHMOD: {
+                    chmod(sftp, dsfiles[i]);
+                    transferred++;
+
+                    break;
+                }
+
+                default:throw new BuildException("unknown ftp action " +
+                        action);
+                }
+            }
+        } finally {
+            if (bw != null) {
+                bw.close();
+            }
+        }
+
+        return dsfiles.length;
+    }
+
+    /**
+     * Sends all files specified by the configured filesets to the remote
+     * server.
+     */
+    protected void transferFiles(SftpClient sftp)
+        throws IOException, BuildException {
+        transferred = 0;
+        skipped = 0;
+
+        if (filesets.size() == 0) {
+            throw new BuildException("at least one fileset must be specified.");
+        } else {
+            // get files from filesets
+            for (int i = 0; i < filesets.size(); i++) {
+                FileSet fs = (FileSet) filesets.elementAt(i);
+
+                if (fs != null) {
+                    transferFiles(sftp, fs);
+                }
+            }
+        }
+
+        log(transferred + " files " + COMPLETED_ACTION_STRS[action]);
+
+        if (skipped != 0) {
+            log(skipped + " files were not successfully " +
+                COMPLETED_ACTION_STRS[action]);
+        }
+    }
+
+    /**
+     * Correct a file path to correspond to the remote host requirements. This
+     * implementation currently assumes that the remote end can handle
+     * Unix-style paths with forward-slash separators. This can be overridden
+     * with the <code>separator</code> task parameter. No attempt is made to
+     * determine what syntax is appropriate for the remote host.
+     */
+    protected String resolveFile(String file) {
+        return file.replace(System.getProperty("file.separator").charAt(0),
+            remoteFileSep.charAt(0));
+    }
+
+    /**
+     * Creates all parent directories specified in a complete relative
+     * pathname. Attempts to create existing directories will not cause
+     * errors.
+     */
+    protected void createParents(SftpClient sftp, String filename)
+        throws IOException, BuildException {
+        Vector parents = new Vector();
+        File dir = new File(filename);
+        String dirname;
+
+        while ((dirname = dir.getParent()) != null) {
+            dir = new File(dirname);
+            parents.addElement(dir);
+        }
+
+        for (int i = parents.size() - 1; i >= 0; i--) {
+            dir = (File) parents.elementAt(i);
+
+            if (!dirCache.contains(dir)) {
+                log("creating remote directory " + resolveFile(dir.getPath()),
+                    Project.MSG_VERBOSE);
+
+                try {
+                    sftp.mkdir(resolveFile(dir.getPath()));
+                } catch (IOException ex) {
+                }
+
+                dirCache.addElement(dir);
+            }
+        }
+    }
+
+    /**
+     * Checks to see if the remote file is current as compared with the local
+     * file. Returns true if the remote file is up to date.
+     */
+    protected boolean isUpToDate(SftpClient sftp, File localFile,
+        String remoteFile) throws IOException, BuildException {
+        try {
+            log("Checking date for " + remoteFile, Project.MSG_VERBOSE);
+
+            FileAttributes attrs = sftp.stat(remoteFile);
+
+            // SFTP uses seconds since Jan 1 1970 UTC
+            long remoteTimestamp = attrs.getModifiedTime().longValue() * 1000; //files[0].getTimestamp().getTime().getTime();
+
+            // Java uses milliseconds since Jan 1 1970 UTC
+            long localTimestamp = localFile.lastModified();
+
+            if (this.action == SEND_FILES) {
+                return remoteTimestamp > localTimestamp;
+            } else {
+                return localTimestamp > remoteTimestamp;
+            }
+        } catch (IOException ex) {
+            return false;
+        }
+    }
+
+    /**
+     * Sends a single file to the remote host. <code>filename</code> may
+     * contain a relative path specification. When this is the case, <code>sendFile</code>
+     * will attempt to create any necessary parent directories before sending
+     * the file. The file will then be sent using the entire relative path
+     * spec - no attempt is made to change directories. It is anticipated that
+     * this may eventually cause problems with some FTP servers, but it
+     * simplifies the coding.
+     */
+    protected void sendFile(SftpClient sftp, String dir, String filename)
+        throws IOException, BuildException {
+        InputStream instream = null;
+        SftpFileOutputStream out = null;
+
+        try {
+            File file = parent.getProject().resolveFile(new File(dir, filename).getPath());
+            String remotefile = resolveFile(filename);
+
+            if (newerOnly && isUpToDate(sftp, file, remotefile)) {
+                return;
+            }
+
+            if (verbose) {
+                log("transferring " + file.getAbsolutePath() + " to " +
+                    remotedir + remotefile);
+            }
+
+            instream = new BufferedInputStream(new FileInputStream(file));
+            createParents(sftp, filename);
+            sftp.put(file.getAbsolutePath(), remotefile);
+
+            // Set the umask
+            sftp.chmod(Integer.parseInt(chmod, 8), remotefile);
+            log("File " + file.getAbsolutePath() + " copied to " + parent.host,
+                Project.MSG_VERBOSE);
+            transferred++;
+        } catch (IOException ex1) {
+            String s = "Could not put file: " + ex1.getMessage();
+
+            if (skipFailedTransfers == true) {
+                log(s, Project.MSG_WARN);
+                skipped++;
+            } else {
+                throw new BuildException(s);
+            }
+        } finally {
+            try {
+                if (instream != null) {
+                    instream.close();
+                }
+            } catch (IOException ex) {
+                // ignore it
+            }
+
+            try {
+                if (out != null) {
+                    out.close();
+                }
+            } catch (IOException ex) {
+            }
+        }
+    }
+
+    /** Delete a file from the remote host.  */
+    protected void delFile(SftpClient sftp, String filename)
+        throws IOException, BuildException {
+        if (verbose) {
+            log("deleting " + filename);
+        }
+
+        try {
+            String remotefile = resolveFile(filename);
+            sftp.rm(remotefile);
+            log("File " + filename + " deleted from " + parent.host,
+                Project.MSG_VERBOSE);
+            transferred++;
+        } catch (IOException ex) {
+            String s = "could not delete file: " + ex.getMessage();
+
+            if (skipFailedTransfers == true) {
+                log(s, Project.MSG_WARN);
+                skipped++;
+            } else {
+                throw new BuildException(s);
+            }
+        }
+    }
+
+    protected void chmod(SftpClient sftp, String filename)
+        throws IOException, BuildException {
+        sftp.chmod(Integer.parseInt(chmod, 8), resolveFile(filename));
+    }
+
+    /**
+     * Retrieve a single file to the remote host. <code>filename</code> may
+     * contain a relative path specification. <p>
+     *
+     * The file will then be retreived using the entire relative path spec -
+     * no attempt is made to change directories. It is anticipated that this
+     * may eventually cause problems with some FTP servers, but it simplifies
+     * the coding.</p>
+     */
+    protected void getFile(SftpClient sftp, String dir, String filename)
+        throws IOException, BuildException {
+        try {
+            String localfile = filename;
+
+            if (localfile.indexOf("/") >= 0) {
+                localfile = localfile.substring(filename.lastIndexOf("/"));
+            }
+
+            File file = parent.getProject().resolveFile(new File(dir, localfile).getAbsolutePath());
+            log(dir);
+            log(filename);
+            log(file.getAbsolutePath());
+
+            if (newerOnly && isUpToDate(sftp, file, resolveFile(filename))) {
+                return;
+            }
+
+            if (verbose) {
+                log("transferring " + filename + " to " +
+                    file.getAbsolutePath());
+            }
+
+            File pdir = fileUtils.getParentFile(file);
+
+            if (!pdir.exists()) {
+                pdir.mkdirs();
+            }
+
+            //sftp.lcd(dir);
+            // Get the file
+            sftp.get(filename, file.getAbsolutePath());
+
+            if (verbose) {
+                log("File " + file.getAbsolutePath() + " copied from " +
+                    parent.host);
+            }
+
+            FileAttributes attrs = sftp.stat(filename);
+            file.setLastModified(attrs.getModifiedTime().longValue() * 1000);
+            transferred++;
+        } catch (IOException ioe) {
+            String s = "could not get file: " + ioe.getMessage();
+
+            if (skipFailedTransfers == true) {
+                log(s, Project.MSG_WARN);
+                skipped++;
+            } else {
+                throw new BuildException(s);
+            }
+        }
+    }
+
+    /**
+     * Create the specified directory on the remote host.
+     *
+     * @param sftp The SFTP client connection
+     * @param dir The directory to create
+     */
+    protected void makeRemoteDir(SftpClient sftp, String dir)
+        throws BuildException {
+        if (verbose) {
+            log("creating directory: " + dir);
+        }
+
+        try {
+            sftp.mkdir(dir);
+        } catch (IOException ex) {
+            log(ex.getMessage());
+        }
+    }
+
+    /** Runs the task.  */
+    public void execute(SshClient ssh) throws BuildException {
+        try {
+            Integer.parseInt(chmod, 8);
+        } catch (NumberFormatException ex) {
+            throw new BuildException(
+                "chmod attribute format is incorrect, use octal number format i.e 0777");
+        }
+
+        executeSFTPTask(ssh);
+    }
+
+    protected void executeSFTPTask(SshClient ssh) throws BuildException {
+        SftpClient sftp = null;
+
+        try {
+            sftp = ssh.openSftpClient();
+
+            if (action == MK_DIR) {
+                makeRemoteDir(sftp, remotedir);
+            } else {
+                if (remotedir.trim().length() > 0) {
+                    log("Setting the remote directory "); //, Project.MSG_VERBOSE);
+                    sftp.cd(remotedir);
+                }
+
+                // Get the absolute path of the remote directory
+                remotedir = sftp.pwd();
+                log("Remote directory is " + remotedir);
+
+                if (!remotedir.endsWith("/")) {
+                    remotedir += "/";
+                }
+
+                log(ACTION_STRS[action] + " files");
+                transferFiles(sftp);
+            }
+        } catch (IOException ex) {
+            throw new BuildException("error during SFTP transfer: " + ex);
+        } finally {
+            if ((sftp != null) && !sftp.isClosed()) {
+                try {
+                    log("Quiting SFTP", Project.MSG_VERBOSE);
+                    sftp.quit();
+                } catch (IOException ex) {
+                    // ignore it
+                }
+            }
+        }
+    }
+
+    protected class SftpDirectoryScanner extends DirectoryScanner {
+        protected SftpClient sftp = null;
+
+        public SftpDirectoryScanner(SftpClient sftp) {
+            super();
+            this.sftp = sftp;
+        }
+
+        public void scan() {
+            if (includes == null) {
+                // No includes supplied, so set it to 'matches all'
+                includes = new String[1];
+                includes[0] = "**";
+            }
+
+            if (excludes == null) {
+                excludes = new String[0];
+            }
+
+            filesIncluded = new Vector();
+            filesNotIncluded = new Vector();
+            filesExcluded = new Vector();
+            dirsIncluded = new Vector();
+            dirsNotIncluded = new Vector();
+            dirsExcluded = new Vector();
+            scandir(remotedir, true);
+        }
+
+        protected void scandir(String dir, boolean fast) {
+            try {
+                List children = sftp.ls(dir);
+
+                if (!dir.endsWith("/")) {
+                    dir += "/";
+                }
+
+                Iterator it = children.iterator();
+
+                while (it.hasNext()) {
+                    SftpFile file = (SftpFile) it.next();
+
+                    if (!file.getFilename().equals(".") &&
+                            !file.getFilename().equals("..")) {
+                        if (file.isDirectory()) {
+                            String name = dir + file.getFilename();
+
+                            if (isIncluded(name)) {
+                                if (!isExcluded(name)) {
+                                    dirsIncluded.addElement(name);
+
+                                    if (fast) {
+                                        scandir(dir + file.getFilename(), fast);
+                                    }
+                                } else {
+                                    dirsExcluded.addElement(name);
+
+                                    if (fast && couldHoldIncluded(name)) {
+                                        scandir(dir + file.getFilename(), fast);
+                                    }
+                                }
+                            } else {
+                                dirsNotIncluded.addElement(name);
+
+                                if (fast && couldHoldIncluded(name)) {
+                                    scandir(dir + file.getFilename(), fast);
+                                }
+                            }
+
+                            if (!fast) {
+                                scandir(dir + file.getFilename(), fast);
+                            }
+                        } else {
+                            if (file.isFile()) {
+                                String name = dir + file.getFilename();
+
+                                if (isIncluded(name)) {
+                                    if (!isExcluded(name)) {
+                                        filesIncluded.addElement(name);
+                                    } else {
+                                        filesExcluded.addElement(name);
+                                    }
+                                } else {
+                                    filesNotIncluded.addElement(name);
+                                }
+                            }
+                        }
+                    }
+                }
+
+                //ftp.changeToParentDirectory();
+            } catch (IOException e) {
+                throw new BuildException("Error while communicating with SFTP ",
+                    e);
+            }
+        }
+    }
+
+    /**
+     * an action to perform, one of
+     * "send", "put", "recv", "get", "del", "delete", "list", "mkdir", "chmod"
+     */
+    public static class Action extends EnumeratedAttribute {
+        private static final String[] validActions = {
+            "send", "put", "recv", "get", "del", "delete", "list", "mkdir",
+            "chmod"
+        };
+
+        public String[] getValues() {
+            return validActions;
+        }
+
+        public int getAction() {
+            String actionL = getValue().toLowerCase(Locale.US);
+
+            if (actionL.equals("send") || actionL.equals("put")) {
+                return SEND_FILES;
+            } else if (actionL.equals("recv") || actionL.equals("get")) {
+                return GET_FILES;
+            } else if (actionL.equals("del") || actionL.equals("delete")) {
+                return DEL_FILES;
+            }
+            /*else if (actionL.equals("list")) {
+              return LIST_FILES;
+                   }*/
+            else if (actionL.equals("chmod")) {
+                return CHMOD;
+            } else if (actionL.equals("mkdir")) {
+                return MK_DIR;
+            }
+
+            return SEND_FILES;
+        }
+    }
+}
diff --git a/src/com/sshtools/ant/Ssh.java b/src/com/sshtools/ant/Ssh.java
new file mode 100644
index 0000000000000000000000000000000000000000..6991d09524ed02a6c0f924302915ba708206f9bf
--- /dev/null
+++ b/src/com/sshtools/ant/Ssh.java
@@ -0,0 +1,610 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.ant;
+
+import com.sshtools.j2ssh.*;
+import com.sshtools.j2ssh.authentication.*;
+import com.sshtools.j2ssh.configuration.*;
+import com.sshtools.j2ssh.session.*;
+import com.sshtools.j2ssh.transport.*;
+import com.sshtools.j2ssh.transport.cipher.*;
+import com.sshtools.j2ssh.transport.hmac.*;
+import com.sshtools.j2ssh.transport.publickey.*;
+
+import org.apache.tools.ant.*;
+
+import java.io.*;
+
+import java.util.*;
+
+
+public class Ssh extends Task {
+    protected String host;
+    protected int port = 22;
+    protected String username;
+    protected String password;
+    protected String keyfile;
+    protected String passphrase;
+    protected String cipher;
+    protected String mac;
+    protected String fingerprint;
+    protected String logfile = null;
+    protected boolean verifyhost = true;
+    protected boolean always = false;
+    protected SshClient ssh;
+    protected Vector tasks = new Vector();
+    protected String sshtoolsHome;
+    protected String newline = "\n";
+
+    public Ssh() {
+        super();
+    }
+
+    protected void validate() throws BuildException {
+        if (host == null) {
+            throw new BuildException("You must provide a host to connect to!");
+        }
+
+        if (username == null) {
+            throw new BuildException(
+                "You must supply a username for authentication!");
+        }
+
+        if ((password == null) && (keyfile == null)) {
+            throw new BuildException(
+                "You must supply either a password or keyfile/passphrase to authenticate!");
+        }
+
+        if (verifyhost && (fingerprint == null)) {
+            throw new BuildException(
+                "Public key fingerprint required to verify the host");
+        }
+    }
+
+    protected void connectAndAuthenticate() throws BuildException {
+        if (sshtoolsHome != null) {
+            System.setProperty("sshtools.home", sshtoolsHome);
+        }
+
+        log("Initializing J2SSH");
+
+        try {
+            ConfigurationLoader.initialize(false);
+            log("Creating connection to " + host + ":" + String.valueOf(port));
+
+            if (ssh == null) {
+                ssh = new SshClient();
+
+                SshConnectionProperties properties = new SshConnectionProperties();
+                properties.setHost(host);
+                properties.setPort(port);
+                properties.setUsername(username);
+
+                if (cipher != null) {
+                    if (SshCipherFactory.getSupportedCiphers().contains(cipher)) {
+                        properties.setPrefSCEncryption(cipher);
+                        properties.setPrefCSEncryption(cipher);
+                    } else {
+                        this.log(cipher +
+                            " is not a supported cipher, using default " +
+                            SshCipherFactory.getDefaultCipher());
+                    }
+                }
+
+                if (mac != null) {
+                    if (SshHmacFactory.getSupportedMacs().contains(mac)) {
+                        properties.setPrefCSMac(mac);
+                        properties.setPrefSCMac(mac);
+                    } else {
+                        this.log(mac +
+                            " is not a supported mac, using default " +
+                            SshHmacFactory.getDefaultHmac());
+                    }
+                }
+
+                log("Connecting....");
+                ssh.connect(properties,
+                    new AbstractKnownHostsKeyVerification(
+                        new File(System.getProperty("user.home"),
+                            ".ssh" + File.separator + "known_hosts").getAbsolutePath()) {
+                        public void onUnknownHost(String hostname,
+                            SshPublicKey key) throws InvalidHostFileException {
+                            if (Ssh.this.verifyhost) {
+                                if (key.getFingerprint().equalsIgnoreCase(Ssh.this.fingerprint)) {
+                                    allowHost(hostname, key, always);
+                                }
+                            } else {
+                                allowHost(hostname, key, always);
+                            }
+                        }
+
+                        public void onHostKeyMismatch(String hostname,
+                            SshPublicKey allowed, SshPublicKey supplied)
+                            throws InvalidHostFileException {
+                            if (Ssh.this.verifyhost) {
+                                if (supplied.getFingerprint().equalsIgnoreCase(Ssh.this.fingerprint)) {
+                                    allowHost(hostname, supplied, always);
+                                }
+                            } else {
+                                allowHost(hostname, supplied, always);
+                            }
+                        }
+
+                        public void onDeniedHost(String host) {
+                            log("The server host key is denied!");
+                        }
+                    });
+
+                int result;
+                boolean authenticated = false;
+                log("Authenticating " + username);
+
+                if (keyfile != null) {
+                    log("Performing public key authentication");
+
+                    PublicKeyAuthenticationClient pk = new PublicKeyAuthenticationClient();
+
+                    // Open up the private key file
+                    SshPrivateKeyFile file = SshPrivateKeyFile.parse(new File(
+                                keyfile));
+
+                    // If the private key is passphrase protected then ask for the passphrase
+                    if (file.isPassphraseProtected() && (passphrase == null)) {
+                        throw new BuildException(
+                            "Private key file is passphrase protected, please supply a valid passphrase!");
+                    }
+
+                    // Get the key
+                    SshPrivateKey key = file.toPrivateKey(passphrase);
+                    pk.setUsername(username);
+                    pk.setKey(key);
+
+                    // Try the authentication
+                    result = ssh.authenticate(pk);
+
+                    if (result == AuthenticationProtocolState.COMPLETE) {
+                        authenticated = true;
+                    } else if (result == AuthenticationProtocolState.PARTIAL) {
+                        log(
+                            "Public key authentication completed, attempting password authentication");
+                    } else {
+                        throw new BuildException(
+                            "Public Key Authentication failed!");
+                    }
+                }
+
+                if ((password != null) && (authenticated == false)) {
+                    log("Performing password authentication");
+
+                    PasswordAuthenticationClient pwd = new PasswordAuthenticationClient();
+                    pwd.setUsername(username);
+                    pwd.setPassword(password);
+                    result = ssh.authenticate(pwd);
+
+                    if (result == AuthenticationProtocolState.COMPLETE) {
+                        log("Authentication complete");
+                    } else if (result == AuthenticationProtocolState.PARTIAL) {
+                        throw new BuildException(
+                            "Password Authentication succeeded but further authentication required!");
+                    } else {
+                        throw new BuildException(
+                            "Password Authentication failed!");
+                    }
+                }
+            }
+        } catch (IOException ex) {
+            throw new BuildException(ex);
+        }
+    }
+
+    protected void disconnect() throws BuildException {
+        try {
+            log("Disconnecting from " + host);
+            ssh.disconnect();
+        } catch (Exception ex) {
+            throw new BuildException(ex);
+        }
+    }
+
+    public void execute() throws org.apache.tools.ant.BuildException {
+        validate();
+        connectAndAuthenticate();
+        executeSubTasks();
+        disconnect();
+    }
+
+    protected void executeSubTasks() throws BuildException {
+        Iterator it = tasks.iterator();
+        SshSubTask task;
+
+        while (it.hasNext()) {
+            task = (SshSubTask) it.next();
+            task.setParent(this);
+            task.execute(ssh);
+        }
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public void setPort(int port) {
+        this.port = port;
+    }
+
+    public void setNewline(String newline) {
+        this.newline = newline;
+    }
+
+    public void setHost(String host) {
+        this.host = host;
+    }
+
+    public void setKeyfile(String keyfile) {
+        this.keyfile = keyfile;
+    }
+
+    public void setPassphrase(String passphrase) {
+        this.passphrase = passphrase;
+    }
+
+    public void setCipher(String cipher) {
+        this.cipher = cipher;
+    }
+
+    public void setMac(String mac) {
+        this.mac = mac;
+    }
+
+    public void setLogfile(String logfile) {
+        this.logfile = logfile;
+    }
+
+    public void setFingerprint(String fingerprint) {
+        this.fingerprint = fingerprint;
+    }
+
+    public void setVerifyhost(boolean verifyhost) {
+        this.verifyhost = verifyhost;
+    }
+
+    public void setAlways(boolean always) {
+        this.always = always;
+    }
+
+    public void setSshtoolshome(String sshtoolsHome) {
+        this.sshtoolsHome = sshtoolsHome;
+    }
+
+    protected boolean hasMoreSftpTasks() {
+        Iterator it = tasks.iterator();
+
+        while (it.hasNext()) {
+            if (it.next().getClass().equals(Sftp.class)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    public SshSubTask createShell() {
+        SshSubTask task = new Shell();
+        tasks.addElement(task);
+
+        return task;
+    }
+
+    public SshSubTask createExec() {
+        SshSubTask task = new Exec();
+        tasks.addElement(task);
+
+        return task;
+    }
+
+    public SshSubTask createSftp() {
+        SshSubTask task = new Sftp();
+        tasks.addElement(task);
+
+        return task;
+    }
+
+    public class Exec extends Shell {
+        private String cmd;
+
+        public void execute(SshClient ssh) throws BuildException {
+            this.validate();
+
+            try {
+                log("Executing command " + cmd);
+
+                // Create the session channel
+                SessionChannelClient session = ssh.openSessionChannel();
+                output = new SessionOutputReader(session);
+
+                // Allocate a pseudo terminal is one has been requested
+                allocatePseudoTerminal(session);
+
+                // Execute the command
+                if (session.executeCommand(cmd)) {
+                    performTasks(session);
+                } else {
+                    throw new BuildException("The command failed to start");
+                }
+            } catch (IOException ex) {
+            }
+        }
+
+        public void setCmd(String cmd) {
+            this.cmd = cmd;
+        }
+    }
+
+    public class Shell extends SshSubTask implements PseudoTerminal {
+        private String term = null;
+        private int cols = 80;
+        private int rows = 34;
+        private int width = 0;
+        private int height = 0;
+        private String terminalModes = "";
+        private Vector commands = new Vector();
+        private SessionChannelClient session;
+        protected SessionOutputReader output;
+
+        public void execute(SshClient ssh) throws BuildException {
+            this.validate();
+
+            try {
+                // Create the session channel
+                session = ssh.openSessionChannel();
+
+                // Add an event listener so we can filter the output for our read commands
+                // This is much easier than reading from an InputStream that could potentailly block
+                output = new SessionOutputReader(session);
+
+                // Allocate a pseudo terminal is one has been requested
+                allocatePseudoTerminal(session);
+
+                // Start the shell
+                if (session.startShell()) {
+                    performTasks(session);
+                } else {
+                    throw new BuildException("The session failed to start");
+                }
+            } catch (IOException ex) {
+            }
+        }
+
+        protected void validate() throws BuildException {
+            if (ssh == null) {
+                throw new BuildException("Invalid SSH session");
+            }
+
+            if (!ssh.isConnected()) {
+                throw new BuildException("The SSH session is not connected");
+            }
+        }
+
+        protected void allocatePseudoTerminal(SessionChannelClient session)
+            throws BuildException {
+            try {
+                if (term != null) {
+                    if (!session.requestPseudoTerminal(this)) {
+                        throw new BuildException(
+                            "The server failed to allocate a pseudo terminal");
+                    }
+                }
+            } catch (IOException ex) {
+                throw new BuildException(ex);
+            }
+        }
+
+        protected void performTasks(SessionChannelClient session)
+            throws BuildException {
+            if (commands.size() > 0) {
+                Iterator it = commands.iterator();
+                Object obj;
+
+                while (it.hasNext()) {
+                    obj = it.next();
+
+                    if (obj instanceof Write) {
+                        ((Write) obj).execute();
+                    } else if (obj instanceof Read) {
+                        ((Read) obj).execute();
+                    } else {
+                        throw new BuildException("Unexpected shell operation " +
+                            obj.toString());
+                    }
+                }
+            } else {
+                try {
+                    output.echoLineByLineToClose(new SessionOutputEcho() {
+                            public void echo(String echo) {
+                                log(echo);
+                            }
+                        });
+                } catch (InterruptedException ex) {
+                    throw new BuildException(ex);
+                }
+            }
+        }
+
+        public void setTerm(String term) {
+            this.term = term;
+        }
+
+        public void setCols(int cols) {
+            this.cols = cols;
+        }
+
+        public void setRows(int rows) {
+            this.rows = rows;
+        }
+
+        /**
+         * PseduoTermainal interface
+         */
+        public String getTerm() {
+            return term;
+        }
+
+        public int getColumns() {
+            return cols;
+        }
+
+        public int getRows() {
+            return rows;
+        }
+
+        public int getWidth() {
+            return width;
+        }
+
+        public int getHeight() {
+            return height;
+        }
+
+        public String getEncodedTerminalModes() {
+            return terminalModes;
+        }
+
+        /**
+         * Reading/Writing to the session/command
+         */
+        public Write createWrite() {
+            Write write = new Write();
+            commands.add(write);
+
+            return write;
+        }
+
+        public Read createRead() {
+            Read read = new Read();
+            commands.add(read);
+
+            return read;
+        }
+
+        public class Read {
+            protected String taskString = "";
+            private int timeout = 0;
+            private boolean echo = true;
+
+            public void execute() throws BuildException {
+                try {
+                    output.markCurrentPosition();
+
+                    if (output.waitForString(taskString, timeout,
+                                new SessionOutputEcho() {
+                                public void echo(String msg) {
+                                    if (echo) {
+                                    log(msg);
+                                }
+                            }
+                        })
+                    ) {
+                    } else {
+                        throw new BuildException("Timeout waiting for string " +
+                            taskString);
+                    }
+                } catch (InterruptedException ex) {
+                    throw new BuildException(ex);
+                }
+            }
+
+            /**
+             *  the message as nested text
+             */
+            public void addText(String s) {
+                setString(Ssh.this.getProject().replaceProperties(s));
+            }
+
+            public void setTimeout(int timeout) {
+                this.timeout = timeout;
+            }
+
+            public void setEcho(boolean echo) {
+                this.echo = echo;
+            }
+
+            /**
+             * the message as an attribute
+             */
+            public void setString(String s) {
+                taskString += s;
+            }
+        }
+
+        public class Write {
+            protected boolean echo = true;
+            protected String taskString = "";
+            protected boolean newline = true;
+
+            public void execute() throws BuildException {
+                try {
+                    if (echo) {
+                        log(taskString);
+                    }
+
+                    session.getOutputStream().write(taskString.getBytes());
+
+                    if (newline) {
+                        session.getOutputStream().write(Ssh.this.newline.getBytes());
+                    }
+                } catch (IOException ex) {
+                    throw new BuildException(ex);
+                }
+            }
+
+            /**
+             *  the message as nested text
+             */
+            public void addText(String s) {
+                setString(Ssh.this.getProject().replaceProperties(s));
+            }
+
+            /**
+             * the message as an attribute
+             */
+            public void setString(String s) {
+                taskString += s;
+            }
+
+            public void setEcho(boolean echo) {
+                this.echo = echo;
+            }
+
+            public void setNewline(boolean newline) {
+                this.newline = newline;
+            }
+        }
+    }
+}
diff --git a/src/com/sshtools/ant/SshSubTask.java b/src/com/sshtools/ant/SshSubTask.java
new file mode 100644
index 0000000000000000000000000000000000000000..e5e299f9b6fdd5a99c87e1b0092198f219f3f746
--- /dev/null
+++ b/src/com/sshtools/ant/SshSubTask.java
@@ -0,0 +1,53 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.ant;
+
+import com.sshtools.j2ssh.*;
+
+import org.apache.tools.ant.*;
+
+
+public class SshSubTask {
+    protected String taskString = "";
+    protected Ssh parent;
+
+    protected void setParent(Ssh parent) {
+        this.parent = parent;
+    }
+
+    protected void log(String msg) {
+        parent.log(msg);
+    }
+
+    protected void log(String msg, int i) {
+        parent.log(msg, i);
+    }
+
+    public void execute(SshClient ssh) throws BuildException {
+        throw new BuildException(
+            "Shouldn't be able instantiate an SshSubTask directly");
+    }
+}
diff --git a/src/com/sshtools/common/authentication/AuthenticationDialog.java b/src/com/sshtools/common/authentication/AuthenticationDialog.java
new file mode 100644
index 0000000000000000000000000000000000000000..3dd96b19fd5f3766d9507163aad5d3820d6c284e
--- /dev/null
+++ b/src/com/sshtools/common/authentication/AuthenticationDialog.java
@@ -0,0 +1,267 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.authentication;
+
+import com.sshtools.common.ui.IconWrapperPanel;
+import com.sshtools.common.ui.ResourceIcon;
+import com.sshtools.common.ui.SshToolsConnectionHostTab;
+import com.sshtools.common.ui.UIUtil;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dialog;
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.GridLayout;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import java.util.ArrayList;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.SwingConstants;
+import javax.swing.SwingUtilities;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class AuthenticationDialog extends JDialog {
+    JList jListAuths = new JList();
+    JLabel messageLabel = new JLabel();
+    boolean cancelled = false;
+
+    /**
+* Creates a new AuthenticationDialog object.
+*/
+    public AuthenticationDialog() {
+        super((Frame) null, "Select Authentication Method(s)", true);
+        init();
+    }
+
+    /**
+* Creates a new AuthenticationDialog object.
+*
+* @param frame
+*/
+    public AuthenticationDialog(Frame frame) {
+        super(frame, "Select Authentication Method(s)", true);
+        init();
+    }
+
+    /**
+* Creates a new AuthenticationDialog object.
+*
+* @param dialog
+*/
+    public AuthenticationDialog(Dialog dialog) {
+        super(dialog, "Select Authentication Method(s)", true);
+        init();
+    }
+
+    void init() {
+        try {
+            jbInit();
+            pack();
+        } catch (Exception ex) {
+            ex.printStackTrace();
+        }
+    }
+
+    private void setMethodList(java.util.List methods) {
+        jListAuths.setListData(methods.toArray());
+
+        if (methods.size() > 0) {
+            jListAuths.setSelectedIndex(0);
+        }
+    }
+
+    void jbInit() throws Exception {
+        //
+        setDefaultCloseOperation(JDialog.HIDE_ON_CLOSE);
+
+        //
+        messageLabel.setForeground(Color.red);
+        messageLabel.setHorizontalAlignment(JLabel.CENTER);
+
+        //  Create the list of available methods and put in in a scroll panel
+        jListAuths = new JList();
+        jListAuths.setVisibleRowCount(5);
+
+        JPanel listPanel = new JPanel(new GridLayout(1, 1));
+        listPanel.setBorder(BorderFactory.createEmptyBorder(4, 0, 0, 0));
+        listPanel.add(new JScrollPane(jListAuths));
+
+        //  Main panel
+        JPanel centerPanel = new JPanel(new BorderLayout());
+        centerPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+        centerPanel.add(new JLabel(
+                "Please select an authentication method(s) to continue."),
+            BorderLayout.NORTH);
+        centerPanel.add(listPanel, BorderLayout.CENTER);
+
+        //  Create the bottom button panel
+        JButton proceed = new JButton("Proceed");
+        proceed.setMnemonic('p');
+        proceed.setDefaultCapable(true);
+        proceed.addActionListener(new ActionListener() {
+                public void actionPerformed(ActionEvent evt) {
+                    //  I presume this component **is** reused?
+                    setVisible(false);
+                }
+            });
+        getRootPane().setDefaultButton(proceed);
+
+        JButton cancel = new JButton("Cancel");
+        cancel.setMnemonic('c');
+        cancel.addActionListener(new ActionListener() {
+                public void actionPerformed(ActionEvent evt) {
+                    cancelled = true;
+                    setVisible(false);
+                }
+            });
+
+        JPanel southPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 0, 0));
+        southPanel.setBorder(BorderFactory.createEmptyBorder(4, 0, 0, 0));
+        southPanel.add(cancel);
+        southPanel.add(proceed);
+
+        //  Create the center banner panel
+        IconWrapperPanel iconPanel = new IconWrapperPanel(new ResourceIcon(
+                    SshToolsConnectionHostTab.AUTH_ICON), centerPanel);
+        iconPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+
+        //  The main panel contains everything and is surrounded by a border
+        JPanel mainPanel = new JPanel(new BorderLayout());
+        mainPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+        mainPanel.add(messageLabel, BorderLayout.NORTH);
+        mainPanel.add(iconPanel, BorderLayout.CENTER);
+        mainPanel.add(southPanel, BorderLayout.SOUTH);
+
+        //  Build the main panel
+        getContentPane().setLayout(new GridLayout(1, 1));
+        getContentPane().add(mainPanel);
+    }
+
+    /**
+*
+*
+* @param parent
+* @param support
+*
+* @return
+*/
+    public static java.util.List showAuthenticationDialog(Component parent,
+        java.util.List support) {
+        return showAuthenticationDialog(parent, support, null);
+    }
+
+    /**
+*
+*
+* @param parent
+* @param support
+* @param message
+*
+* @return
+*/
+    public static java.util.List showAuthenticationDialog(Component parent,
+        java.util.List support, String message) {
+        Window w = (Window) SwingUtilities.getAncestorOfClass(Window.class,
+                parent);
+        AuthenticationDialog dialog = null;
+
+        if (w instanceof Frame) {
+            dialog = new AuthenticationDialog((Frame) w);
+        } else if (w instanceof Dialog) {
+            dialog = new AuthenticationDialog((Dialog) w);
+        } else {
+            dialog = new AuthenticationDialog();
+        }
+
+        UIUtil.positionComponent(SwingConstants.CENTER, dialog);
+
+        return dialog.showAuthenticationMethods(support, message);
+    }
+
+    /**
+*
+*
+* @param supported
+* @param message
+*
+* @return
+*/
+    public java.util.List showAuthenticationMethods(java.util.List supported,
+        String message) {
+        // Set the list
+        this.setMethodList(supported);
+
+        // Show the dialog
+        UIUtil.positionComponent(SwingConstants.CENTER, this);
+
+        if (message != null) {
+            messageLabel.setVisible(true);
+            messageLabel.setText(message);
+        } else {
+            messageLabel.setVisible(false);
+        }
+
+        pack();
+        toFront();
+        setVisible(true);
+
+        // Put the selected values into a new list and return
+        java.util.List list = new ArrayList();
+
+        if (!cancelled) {
+            Object[] methods = jListAuths.getSelectedValues();
+
+            if (methods != null) {
+                for (int i = 0; i < methods.length; i++) {
+                    list.add(methods[i]);
+                }
+            }
+        }
+
+        return list;
+    }
+
+    void jButtonProceed_actionPerformed(ActionEvent e) {
+      setVisible(false);
+    }
+}
diff --git a/src/com/sshtools/common/authentication/BannerDialog.java b/src/com/sshtools/common/authentication/BannerDialog.java
new file mode 100644
index 0000000000000000000000000000000000000000..37d802960d432f01fc5611852738693fea85288a
--- /dev/null
+++ b/src/com/sshtools/common/authentication/BannerDialog.java
@@ -0,0 +1,187 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.authentication;
+
+import com.sshtools.common.ui.IconWrapperPanel;
+import com.sshtools.common.ui.ResourceIcon;
+import com.sshtools.common.ui.UIUtil;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dialog;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.Frame;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.SwingConstants;
+import javax.swing.SwingUtilities;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class BannerDialog extends JDialog {
+    //  Statics
+    final static String BANNER_ICON = "largebanner.png";
+
+    //  Private instance variables
+    private JTextArea text;
+
+    /**
+* Creates a new BannerDialog object.
+*
+* @param bannerText
+*/
+    public BannerDialog(String bannerText) {
+        super((Frame) null, "SSH Authentication - Banner Message", true);
+        init(bannerText);
+    }
+
+    /**
+* Creates a new BannerDialog object.
+*
+* @param parent
+* @param bannerText
+*/
+    public BannerDialog(Frame parent, String bannerText) {
+        super(parent, "SSH Authentication - Banner Message", true);
+        init(bannerText);
+    }
+
+    /**
+* Creates a new BannerDialog object.
+*
+* @param parent
+* @param bannerText
+*/
+    public BannerDialog(Dialog parent, String bannerText) {
+        super(parent, "SSH Authentication - Banner Message", true);
+        init(bannerText);
+    }
+
+    void init(String bannerText) {
+        try {
+            jbInit();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        //
+        setText(bannerText);
+    }
+
+    /**
+*
+*
+* @param parent
+* @param bannerText
+*/
+    public static void showBannerDialog(Component parent, String bannerText) {
+        Window w = (Window) SwingUtilities.getAncestorOfClass(Window.class,
+                parent);
+        BannerDialog dialog = null;
+
+        if (w instanceof Frame) {
+            dialog = new BannerDialog((Frame) w, bannerText);
+        } else if (w instanceof Dialog) {
+            dialog = new BannerDialog((Dialog) w, bannerText);
+        } else {
+            dialog = new BannerDialog(bannerText);
+        }
+
+        UIUtil.positionComponent(SwingConstants.CENTER, dialog);
+        dialog.toFront();
+        dialog.setVisible(true);
+    }
+
+    /**
+*
+*
+* @param text
+*/
+    public void setText(String text) {
+        this.text.setText(text);
+        this.repaint();
+    }
+
+    void jbInit() throws Exception {
+        setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+
+        //  Create the component to display the banner text
+        text = new JTextArea();
+        text.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+
+        //        text.setLineWrap(true);
+        text.setEditable(false);
+
+        Font f = new Font("MonoSpaced", text.getFont().getStyle(),
+                text.getFont().getSize());
+        text.setFont(f);
+
+        JScrollPane textScroller = new JScrollPane(text);
+
+        //  Create the center banner panel
+        IconWrapperPanel centerPanel = new IconWrapperPanel(new ResourceIcon(
+                    BannerDialog.class, BANNER_ICON), textScroller);
+        centerPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+
+        //  Create the south button panel
+        JButton ok = new JButton("Ok");
+        ok.setMnemonic('o');
+        ok.setDefaultCapable(true);
+        ok.addActionListener(new ActionListener() {
+                public void actionPerformed(ActionEvent evt) {
+                    //  I presume this component is not reused?
+                    dispose();
+                }
+            });
+
+        JPanel southPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+        southPanel.add(ok);
+        getRootPane().setDefaultButton(ok);
+
+        //  Build the main panel
+        getContentPane().setLayout(new BorderLayout());
+        getContentPane().add(centerPanel, BorderLayout.CENTER);
+        getContentPane().add(southPanel, BorderLayout.SOUTH);
+
+        //
+        setSize(500, 245);
+        setResizable(false);
+    }
+}
diff --git a/src/com/sshtools/common/authentication/KBIRequestHandlerDialog.java b/src/com/sshtools/common/authentication/KBIRequestHandlerDialog.java
new file mode 100644
index 0000000000000000000000000000000000000000..3e01954f7cefb8a4699d69c5218b2996ccf7682a
--- /dev/null
+++ b/src/com/sshtools/common/authentication/KBIRequestHandlerDialog.java
@@ -0,0 +1,199 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.authentication;
+
+import com.sshtools.common.ui.IconWrapperPanel;
+import com.sshtools.common.ui.ResourceIcon;
+import com.sshtools.common.ui.UIUtil;
+import com.sshtools.common.ui.XTextField;
+
+import com.sshtools.j2ssh.authentication.KBIPrompt;
+import com.sshtools.j2ssh.authentication.KBIRequestHandler;
+
+import java.awt.BorderLayout;
+import java.awt.Dialog;
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JPasswordField;
+import javax.swing.SwingConstants;
+import javax.swing.text.JTextComponent;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class KBIRequestHandlerDialog extends JDialog
+    implements KBIRequestHandler {
+    /**  */
+    public final static String KBI_ICON = "largekbi.png";
+    boolean cancelled;
+    JLabel instructionLabel = new JLabel();
+    JPanel buttonsPanel = new JPanel();
+    JTextComponent[] promptReply;
+
+    /**
+* Creates a new KBIRequestHandlerDialog object.
+*/
+    public KBIRequestHandlerDialog() {
+        super((Frame) null, "", true);
+        init();
+    }
+
+    /**
+* Creates a new KBIRequestHandlerDialog object.
+*
+* @param frame
+*/
+    public KBIRequestHandlerDialog(Frame frame) {
+        super(frame, "", true);
+        init();
+    }
+
+    /**
+* Creates a new KBIRequestHandlerDialog object.
+*
+* @param dialog
+*/
+    public KBIRequestHandlerDialog(Dialog dialog) {
+        super(dialog, "", true);
+        init();
+    }
+
+    void init() {
+        setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+        instructionLabel.setHorizontalAlignment(JLabel.CENTER);
+        instructionLabel.setBorder(BorderFactory.createEmptyBorder(4, 4, 8, 4));
+
+        JButton ok = new JButton("Ok");
+        ok.setMnemonic('o');
+        ok.setDefaultCapable(true);
+        ok.addActionListener(new ActionListener() {
+                public void actionPerformed(ActionEvent evt) {
+                    setVisible(false);
+                }
+            });
+        getRootPane().setDefaultButton(ok);
+
+        JButton cancel = new JButton("Cancel");
+        cancel.setMnemonic('c');
+        cancel.addActionListener(new ActionListener() {
+                public void actionPerformed(ActionEvent evt) {
+                    cancelled = true;
+                    setVisible(false);
+                }
+            });
+        buttonsPanel.setLayout(new FlowLayout(FlowLayout.RIGHT, 0, 0));
+        buttonsPanel.setBorder(BorderFactory.createEmptyBorder(4, 0, 0, 0));
+        buttonsPanel.add(cancel);
+        buttonsPanel.add(ok);
+    }
+
+    /**
+*
+*
+* @param name
+* @param instruction
+* @param prompts
+*/
+    public void showPrompts(String name, String instruction, KBIPrompt[] prompts) {
+        setTitle(name);
+        getContentPane().invalidate();
+        getContentPane().removeAll();
+        instructionLabel.setText(instruction);
+
+        JPanel promptPanel = new JPanel(new GridBagLayout());
+        GridBagConstraints gbc = new GridBagConstraints();
+        gbc.insets = new Insets(0, 0, 4, 4);
+        gbc.fill = GridBagConstraints.CENTER;
+        gbc.anchor = GridBagConstraints.WEST;
+        promptReply = new JTextComponent[prompts.length];
+
+        for (int i = 0; i < prompts.length; i++) {
+            if (prompts[i].echo()) {
+                promptReply[i] = new XTextField(prompts[i].getResponse(), 15);
+            } else {
+                promptReply[i] = new JPasswordField(prompts[i].getResponse(), 15);
+            }
+
+            System.out.println("Creating prompt " + prompts[i].getPrompt() +
+                " and setting to " + prompts[i].getResponse());
+            gbc.weightx = 0.0;
+            UIUtil.jGridBagAdd(promptPanel,
+                new JLabel(prompts[i].getPrompt() + " "), gbc,
+                GridBagConstraints.RELATIVE);
+            gbc.weightx = 1.0;
+            UIUtil.jGridBagAdd(promptPanel, promptReply[i], gbc,
+                GridBagConstraints.REMAINDER);
+        }
+
+        JPanel centerPanel = new JPanel(new BorderLayout());
+        centerPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+        centerPanel.add(instructionLabel, BorderLayout.NORTH);
+        centerPanel.add(promptPanel, BorderLayout.CENTER);
+
+        //  Create the center banner panel
+        IconWrapperPanel iconPanel = new IconWrapperPanel(new ResourceIcon(
+                    KBIRequestHandlerDialog.class, KBI_ICON), centerPanel);
+        iconPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+
+        //  The main panel contains everything and is surrounded by a border
+        JPanel mainPanel = new JPanel(new BorderLayout());
+        mainPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+        mainPanel.add(iconPanel, BorderLayout.CENTER);
+        mainPanel.add(buttonsPanel, BorderLayout.SOUTH);
+
+        //  Build the main panel
+        getContentPane().setLayout(new GridLayout(1, 1));
+        getContentPane().add(mainPanel);
+        getContentPane().validate();
+        pack();
+        UIUtil.positionComponent(SwingConstants.CENTER, this);
+        setVisible(true);
+
+        if (!cancelled) {
+            for (int i = 0; i < promptReply.length; i++) {
+                System.out.println("Setting reply " + i + " to " +
+                    promptReply[i].getText());
+                prompts[i].setResponse(promptReply[i].getText());
+            }
+        }
+    }
+}
diff --git a/src/com/sshtools/common/authentication/PassphraseDialog.java b/src/com/sshtools/common/authentication/PassphraseDialog.java
new file mode 100644
index 0000000000000000000000000000000000000000..ebf65c7e2551456ee12d6e453157306c062aa615
--- /dev/null
+++ b/src/com/sshtools/common/authentication/PassphraseDialog.java
@@ -0,0 +1,249 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.authentication;
+
+import com.sshtools.common.ui.IconWrapperPanel;
+import com.sshtools.common.ui.ResourceIcon;
+import com.sshtools.common.ui.UIUtil;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dialog;
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JPasswordField;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class PassphraseDialog extends JDialog {
+    //  Statics
+    final static String PASSPHRASE_ICON = "/com/sshtools/common/authentication/largepassphrase.png";
+    JButton jButtonCancel = new JButton();
+    JButton jButtonOK = new JButton();
+    JLabel message = new JLabel("Enter passphrase");
+    JPasswordField jPasswordField = new JPasswordField(20);
+    boolean userCancelled = false;
+
+    /**
+* Creates a new PassphraseDialog object.
+*/
+    public PassphraseDialog() {
+        super((Frame) null, "Passphrase", true);
+        init(null);
+    }
+
+    /**
+* Creates a new PassphraseDialog object.
+*
+* @param parent
+*/
+    public PassphraseDialog(Frame parent) {
+        super(parent, "Passphrase", true);
+        init(parent);
+    }
+
+    /**
+* Creates a new PassphraseDialog object.
+*
+* @param parent
+* @param identity
+*/
+    public PassphraseDialog(Frame parent, String identity) {
+        super(parent, "Passphrase", true);
+        init(parent);
+        setTitle(identity + " - Identity");
+    }
+
+    /**
+* Creates a new PassphraseDialog object.
+*
+* @param parent
+*/
+    public PassphraseDialog(Dialog parent) {
+        super(parent, "Passphrase", true);
+        init(parent);
+    }
+
+    /*public void setVisible(boolean visible) {
+if (visible) {
+UIUtil.positionComponent(UIUtil.CENTER, PassphraseDialog.this);
+}
+ }*/
+
+    /**
+*
+*
+* @return
+*/
+    public boolean isCancelled() {
+        return userCancelled;
+    }
+
+    /**
+*
+*
+* @param message
+*/
+    public void setMessage(String message) {
+        this.message.setText(message);
+    }
+
+    /**
+*
+*
+* @param color
+*/
+    public void setMessageForeground(Color color) {
+        message.setForeground(color);
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public char[] getPassphrase() {
+        return jPasswordField.getPassword();
+    }
+
+    void init(Window parent) {
+        getContentPane().setLayout(new GridLayout(1, 1));
+
+        if (parent != null) {
+            this.setLocationRelativeTo(parent);
+        }
+
+        try {
+            jbInit();
+            pack();
+            UIUtil.positionComponent(UIUtil.CENTER, PassphraseDialog.this);
+        } catch (Exception ex) {
+            ex.printStackTrace();
+        }
+    }
+
+    void jButtonCancel_actionPerformed(ActionEvent e) {
+        userCancelled = true;
+        setVisible(false);
+    }
+
+    void jButtonOK_actionPerformed(ActionEvent e) {
+        userCancelled = false;
+        setVisible(false);
+    }
+
+    void jbInit() throws Exception {
+        // Add a window listener to see when the window closes without
+        // selecting OK
+        addWindowListener(new WindowAdapter() {
+                public void windowClosing(WindowEvent evt) {
+                    userCancelled = true;
+                }
+            });
+
+        //  Ok button
+        jButtonOK.addActionListener(new java.awt.event.ActionListener() {
+                public void actionPerformed(ActionEvent e) {
+                    jButtonOK_actionPerformed(e);
+                }
+            });
+        jButtonOK.setText("OK");
+        jButtonOK.setMnemonic('o');
+        getRootPane().setDefaultButton(jButtonOK);
+
+        //  Cancel button
+        jButtonCancel.setText("Cancel");
+        jButtonCancel.setMnemonic('c');
+        jButtonCancel.addActionListener(new java.awt.event.ActionListener() {
+                public void actionPerformed(ActionEvent e) {
+                    jButtonCancel_actionPerformed(e);
+                }
+            });
+
+        //  Passphrase panel
+        JPanel passphrasePanel = new JPanel(new GridBagLayout());
+        GridBagConstraints gbc = new GridBagConstraints();
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        gbc.anchor = GridBagConstraints.WEST;
+        gbc.insets = new Insets(0, 2, 2, 2);
+        gbc.weightx = 1.0;
+        UIUtil.jGridBagAdd(passphrasePanel, message, gbc,
+            GridBagConstraints.REMAINDER);
+        UIUtil.jGridBagAdd(passphrasePanel, jPasswordField, gbc,
+            GridBagConstraints.REMAINDER);
+
+        //  Create the center banner panel
+        IconWrapperPanel centerPanel = new IconWrapperPanel(new ResourceIcon(
+                    PASSPHRASE_ICON), passphrasePanel);
+        centerPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+
+        //
+        JPanel buttonPanel = new JPanel(new GridBagLayout());
+        gbc = new GridBagConstraints();
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        gbc.anchor = GridBagConstraints.CENTER;
+        gbc.insets = new Insets(6, 6, 0, 0);
+        gbc.weighty = 1.0;
+        UIUtil.jGridBagAdd(buttonPanel, jButtonOK, gbc,
+            GridBagConstraints.RELATIVE);
+        UIUtil.jGridBagAdd(buttonPanel, jButtonCancel, gbc,
+            GridBagConstraints.REMAINDER);
+
+        JPanel southPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 0, 0));
+        southPanel.add(buttonPanel);
+
+        //  Wrap the whole thing in an empty border
+        JPanel mainPanel = new JPanel(new BorderLayout());
+        mainPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+        mainPanel.add(centerPanel, BorderLayout.CENTER);
+        mainPanel.add(southPanel, BorderLayout.SOUTH);
+
+        //  Build the main panel
+        getContentPane().add(mainPanel);
+
+        //
+        jPasswordField.grabFocus();
+    }
+}
diff --git a/src/com/sshtools/common/authentication/PasswordAuthenticationDialog.java b/src/com/sshtools/common/authentication/PasswordAuthenticationDialog.java
new file mode 100644
index 0000000000000000000000000000000000000000..dc6cb06faa8babb6b1952ba2d6622ec10f90dc16
--- /dev/null
+++ b/src/com/sshtools/common/authentication/PasswordAuthenticationDialog.java
@@ -0,0 +1,313 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.authentication;
+
+import com.sshtools.common.ui.IconWrapperPanel;
+import com.sshtools.common.ui.ResourceIcon;
+import com.sshtools.common.ui.UIUtil;
+import com.sshtools.common.ui.XTextField;
+
+import com.sshtools.j2ssh.authentication.AuthenticationProtocolException;
+import com.sshtools.j2ssh.authentication.PasswordAuthenticationClient;
+import com.sshtools.j2ssh.authentication.SshAuthenticationClient;
+import com.sshtools.j2ssh.authentication.SshAuthenticationPrompt;
+
+import java.awt.BorderLayout;
+import java.awt.Dialog;
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import java.lang.reflect.Method;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPasswordField;
+import javax.swing.SwingConstants;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public class PasswordAuthenticationDialog extends JDialog
+    implements SshAuthenticationPrompt {
+    //  Statics
+    final static String KEY_ICON = "largecard.png";
+    PasswordAuthenticationClient instance;
+    JButton jButtonCancel = new JButton();
+    JButton jButtonOK = new JButton();
+    JPasswordField jPasswordField = new JPasswordField(20);
+    XTextField jTextUsername = new XTextField(20);
+    boolean userCancelled = false;
+
+    /**
+* Creates a new PasswordAuthenticationDialog object.
+*/
+    public PasswordAuthenticationDialog() {
+        super((Frame) null, "Password Authentication", true);
+        init(null);
+    }
+
+    /**
+* Creates a new PasswordAuthenticationDialog object.
+*
+* @param parent
+*/
+    public PasswordAuthenticationDialog(Frame parent) {
+        super(parent, "Password Authentication", true);
+        init(parent);
+    }
+
+    /**
+* Creates a new PasswordAuthenticationDialog object.
+*
+* @param parent
+*/
+    public PasswordAuthenticationDialog(Dialog parent) {
+        super(parent, "Password Authentication", true);
+        init(parent);
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getPassword() {
+        return String.valueOf(jPasswordField.getPassword());
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getUsername() {
+        return jTextUsername.getText();
+    }
+
+    /**
+*
+*
+* @param username
+*/
+    public void setUsername(String username) {
+        if (username != null) {
+            jTextUsername.setText(username);
+        }
+    }
+
+    /**
+*
+*
+* @param instance
+*
+* @throws AuthenticationProtocolException
+*/
+    public void setInstance(SshAuthenticationClient instance)
+        throws AuthenticationProtocolException {
+        if (instance instanceof PasswordAuthenticationClient) {
+            this.instance = (PasswordAuthenticationClient) instance;
+        } else {
+            throw new AuthenticationProtocolException(
+                "PasswordAuthenticationClient instance required");
+        }
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public boolean showPrompt(SshAuthenticationClient inst)
+        throws AuthenticationProtocolException {
+        if (inst instanceof PasswordAuthenticationClient) {
+            instance = (PasswordAuthenticationClient) inst;
+
+            if (instance.getUsername() != null) {
+                jTextUsername.setText(instance.getUsername());
+            }
+
+            if (!jTextUsername.getText().equals("")) {
+                jPasswordField.grabFocus();
+            }
+
+            UIUtil.positionComponent(SwingConstants.CENTER, this);
+            setVisible(true);
+
+            if (!userCancelled) {
+                instance.setUsername(getUsername());
+                instance.setPassword(getPassword());
+
+                return true;
+            } else {
+                return false;
+            }
+        } else {
+            throw new AuthenticationProtocolException(
+                "PasswordAuthenticationClient instance required");
+        }
+    }
+
+    void init(Window parent) {
+        setModal(true);
+
+        getContentPane().setLayout(new GridLayout(1, 1));
+
+        if (parent != null) {
+            try {
+                Method m = getClass().getMethod("setLocationRelativeTo",
+                        new Class[] { parent.getClass() });
+                m.invoke(this, new Object[] { parent });
+            } catch (Throwable t) {
+                //
+            }
+        }
+
+        try {
+            jbInit();
+            pack();
+        } catch (Exception ex) {
+            ex.printStackTrace();
+        }
+    }
+
+    void jButtonCancel_actionPerformed(ActionEvent e) {
+        userCancelled = true;
+        setVisible(false);
+    }
+
+    void jButtonOK_actionPerformed(ActionEvent e) {
+        if (jTextUsername.getText().trim().equals("")) {
+            JOptionPane.showMessageDialog(this, "You must enter a username!",
+                "Password Authentication", JOptionPane.OK_OPTION);
+
+            return;
+        }
+
+        setVisible(false);
+    }
+
+    void jbInit() throws Exception {
+        // Add a window listener to see when the window closes without
+        // selecting OK
+        addWindowListener(new WindowAdapter() {
+                public void windowClosing(WindowEvent evt) {
+                    userCancelled = true;
+                }
+            });
+
+        //  Ok button
+        jButtonOK.addActionListener(new java.awt.event.ActionListener() {
+                public void actionPerformed(ActionEvent e) {
+                    jButtonOK_actionPerformed(e);
+                }
+            });
+        jButtonOK.setText("OK");
+        jButtonOK.setMnemonic('o');
+        getRootPane().setDefaultButton(jButtonOK);
+
+        //  Cancel button
+        jButtonCancel.setText("Cancel");
+        jButtonCancel.setMnemonic('c');
+        jButtonCancel.addActionListener(new java.awt.event.ActionListener() {
+                public void actionPerformed(ActionEvent e) {
+                    jButtonCancel_actionPerformed(e);
+                }
+            });
+
+        //  User / password panel
+        JPanel userPasswordPanel = new JPanel(new GridBagLayout());
+        GridBagConstraints gbc = new GridBagConstraints();
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        gbc.anchor = GridBagConstraints.WEST;
+        gbc.insets = new Insets(0, 2, 2, 2);
+        gbc.weightx = 1.0;
+
+        //  Username
+        UIUtil.jGridBagAdd(userPasswordPanel, new JLabel("User"), gbc,
+            GridBagConstraints.REMAINDER);
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        UIUtil.jGridBagAdd(userPasswordPanel, jTextUsername, gbc,
+            GridBagConstraints.REMAINDER);
+        gbc.fill = GridBagConstraints.NONE;
+
+        //  Username
+        UIUtil.jGridBagAdd(userPasswordPanel, new JLabel("Password"), gbc,
+            GridBagConstraints.REMAINDER);
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        UIUtil.jGridBagAdd(userPasswordPanel, jPasswordField, gbc,
+            GridBagConstraints.REMAINDER);
+        gbc.fill = GridBagConstraints.NONE;
+
+        //  Create the center banner panel
+        IconWrapperPanel centerPanel = new IconWrapperPanel(new ResourceIcon(
+                    PasswordAuthenticationDialog.class, KEY_ICON),
+                userPasswordPanel);
+        centerPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+
+        //
+        JPanel buttonPanel = new JPanel(new GridBagLayout());
+        gbc = new GridBagConstraints();
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        gbc.anchor = GridBagConstraints.CENTER;
+        gbc.insets = new Insets(6, 6, 0, 0);
+        gbc.weighty = 1.0;
+        UIUtil.jGridBagAdd(buttonPanel, jButtonOK, gbc,
+            GridBagConstraints.RELATIVE);
+        UIUtil.jGridBagAdd(buttonPanel, jButtonCancel, gbc,
+            GridBagConstraints.REMAINDER);
+
+        JPanel southPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 0, 0));
+        southPanel.add(buttonPanel);
+
+        //  Wrap the whole thing in an empty border
+        JPanel mainPanel = new JPanel(new BorderLayout());
+        mainPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+        mainPanel.add(centerPanel, BorderLayout.CENTER);
+        mainPanel.add(southPanel, BorderLayout.SOUTH);
+
+        //  Build the main panel
+        getContentPane().add(mainPanel);
+
+        //
+        jPasswordField.grabFocus();
+    }
+}
diff --git a/src/com/sshtools/common/authentication/PasswordChange.java b/src/com/sshtools/common/authentication/PasswordChange.java
new file mode 100644
index 0000000000000000000000000000000000000000..cfb73dbface116f92669e0706f3ff904ea4ae8e9
--- /dev/null
+++ b/src/com/sshtools/common/authentication/PasswordChange.java
@@ -0,0 +1,234 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.authentication;
+
+import com.sshtools.common.ui.IconWrapperPanel;
+import com.sshtools.common.ui.ResourceIcon;
+import com.sshtools.common.ui.SshToolsConnectionHostTab;
+import com.sshtools.common.ui.UIUtil;
+
+import com.sshtools.j2ssh.authentication.PasswordChangePrompt;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dialog;
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPasswordField;
+import javax.swing.SwingConstants;
+import javax.swing.SwingUtilities;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class PasswordChange implements PasswordChangePrompt {
+    //
+
+    /**  */
+    public final static String PASSWORD_ICON = "/com/sshtools/common/authentication/largepassword.png";
+
+    //
+    private static PasswordChange instance;
+
+    //
+    private Component parent;
+
+    private PasswordChange() {
+    }
+
+    /**
+*
+*
+* @param parent
+*/
+    public void setParentComponent(Component parent) {
+        this.parent = parent;
+    }
+
+    /**
+*
+*
+* @param prompt
+*
+* @return
+*/
+    public String changePassword(String prompt) {
+        Window w = (parent == null) ? null
+                                    : (Window) SwingUtilities.getAncestorOfClass(Window.class,
+                parent);
+        PasswordChangeDialog dialog = null;
+
+        if (w instanceof Frame) {
+            dialog = new PasswordChangeDialog((Frame) w, prompt);
+        } else if (w instanceof Dialog) {
+            dialog = new PasswordChangeDialog((Dialog) w, prompt);
+        } else {
+            dialog = new PasswordChangeDialog(prompt);
+        }
+
+        char[] p = dialog.getPassword();
+
+        return (p == null) ? null : new String(p);
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public static PasswordChange getInstance() {
+        if (instance == null) {
+            instance = new PasswordChange();
+        }
+
+        return instance;
+    }
+
+    class PasswordChangeDialog extends JDialog {
+        JLabel promptLabel = new JLabel();
+        JPasswordField password = new JPasswordField(15);
+        JPasswordField confirm = new JPasswordField(15);
+        boolean cancelled;
+
+        PasswordChangeDialog(String prompt) {
+            super((Frame) null, "Password Change", true);
+            init(prompt);
+        }
+
+        PasswordChangeDialog(Frame frame, String prompt) {
+            super(frame, "Password Change", true);
+            init(prompt);
+        }
+
+        PasswordChangeDialog(Dialog dialog, String prompt) {
+            super(dialog, "Password Change", true);
+            init(prompt);
+        }
+
+        char[] getPassword() {
+            return (cancelled == true) ? null : password.getPassword();
+        }
+
+        void init(String prompt) {
+            setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+
+            JPanel g = new JPanel(new GridBagLayout());
+            GridBagConstraints gbc = new GridBagConstraints();
+            gbc.insets = new Insets(0, 0, 2, 2);
+            gbc.anchor = GridBagConstraints.WEST;
+            gbc.fill = GridBagConstraints.HORIZONTAL;
+            gbc.weightx = 0.0;
+            UIUtil.jGridBagAdd(g, new JLabel("Password: "), gbc,
+                GridBagConstraints.RELATIVE);
+            gbc.weightx = 1.0;
+            UIUtil.jGridBagAdd(g, password, gbc, GridBagConstraints.REMAINDER);
+            gbc.weightx = 0.0;
+            UIUtil.jGridBagAdd(g, new JLabel("Confirm: "), gbc,
+                GridBagConstraints.RELATIVE);
+            gbc.weightx = 1.0;
+            UIUtil.jGridBagAdd(g, confirm, gbc, GridBagConstraints.REMAINDER);
+
+            //
+            promptLabel.setHorizontalAlignment(JLabel.CENTER);
+
+            //  Main panel
+            JPanel centerPanel = new JPanel(new BorderLayout());
+            centerPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+            centerPanel.add(promptLabel, BorderLayout.NORTH);
+            centerPanel.add(g, BorderLayout.CENTER);
+
+            //  Create the bottom button panel
+            JButton ok = new JButton("Ok");
+            ok.setMnemonic('o');
+            ok.setDefaultCapable(true);
+            ok.addActionListener(new ActionListener() {
+                    public void actionPerformed(ActionEvent evt) {
+                        if (!new String(password.getPassword()).equals(
+                                    new String(confirm.getPassword()))) {
+                            JOptionPane.showMessageDialog(PasswordChangeDialog.this,
+                                "Passwords do not match. Please try again.",
+                                "Passwords do not match",
+                                JOptionPane.ERROR_MESSAGE);
+                        } else {
+                          setVisible(false);
+                        }
+                    }
+                });
+            getRootPane().setDefaultButton(ok);
+
+            JButton cancel = new JButton("Cancel");
+            cancel.setMnemonic('c');
+            cancel.addActionListener(new ActionListener() {
+                    public void actionPerformed(ActionEvent evt) {
+                        cancelled = true;
+                        setVisible(false);
+                    }
+                });
+
+            JPanel southPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 0, 0));
+            southPanel.setBorder(BorderFactory.createEmptyBorder(4, 0, 0, 0));
+            southPanel.add(cancel);
+            southPanel.add(ok);
+
+            //  Create the center banner panel
+            IconWrapperPanel iconPanel = new IconWrapperPanel(new ResourceIcon(
+                        SshToolsConnectionHostTab.AUTH_ICON), centerPanel);
+            iconPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+
+            //  The main panel contains everything and is surrounded by a border
+            JPanel mainPanel = new JPanel(new BorderLayout());
+            mainPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+            mainPanel.add(iconPanel, BorderLayout.CENTER);
+            mainPanel.add(southPanel, BorderLayout.SOUTH);
+
+            //  Build the main panel
+            getContentPane().setLayout(new GridLayout(1, 1));
+            getContentPane().add(mainPanel);
+            pack();
+            toFront();
+            UIUtil.positionComponent(SwingConstants.CENTER, this);
+            setVisible(true);
+        }
+    }
+}
diff --git a/src/com/sshtools/common/authentication/PublicKeyAuthenticationPrompt.java b/src/com/sshtools/common/authentication/PublicKeyAuthenticationPrompt.java
new file mode 100644
index 0000000000000000000000000000000000000000..cfacff81cd58f089d369bcf7a28e8ba38bb1271c
--- /dev/null
+++ b/src/com/sshtools/common/authentication/PublicKeyAuthenticationPrompt.java
@@ -0,0 +1,177 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.authentication;
+
+import com.sshtools.j2ssh.authentication.AuthenticationProtocolException;
+import com.sshtools.j2ssh.authentication.PublicKeyAuthenticationClient;
+import com.sshtools.j2ssh.authentication.SshAuthenticationClient;
+import com.sshtools.j2ssh.authentication.SshAuthenticationPrompt;
+import com.sshtools.j2ssh.transport.publickey.InvalidSshKeyException;
+import com.sshtools.j2ssh.transport.publickey.SshPrivateKey;
+import com.sshtools.j2ssh.transport.publickey.SshPrivateKeyFile;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dialog;
+import java.awt.Frame;
+import java.awt.Window;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import javax.swing.JFileChooser;
+import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class PublicKeyAuthenticationPrompt implements SshAuthenticationPrompt {
+    private Component parent;
+    private PublicKeyAuthenticationClient instance;
+
+    /**
+* Creates a new PublicKeyAuthenticationPrompt object.
+*
+* @param parent
+*/
+    public PublicKeyAuthenticationPrompt(Component parent) {
+        this.parent = parent;
+    }
+
+    /**
+*
+*
+* @param instance
+*
+* @throws AuthenticationProtocolException
+*/
+    public void setInstance(SshAuthenticationClient instance)
+        throws AuthenticationProtocolException {
+        if (instance instanceof PublicKeyAuthenticationClient) {
+            this.instance = (PublicKeyAuthenticationClient) instance;
+        } else {
+            throw new AuthenticationProtocolException(
+                "PublicKeyAuthenticationClient instance required");
+        }
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public boolean showPrompt(SshAuthenticationClient inst)
+        throws AuthenticationProtocolException {
+        if (inst instanceof PublicKeyAuthenticationClient) {
+            instance = (PublicKeyAuthenticationClient) inst;
+        } else {
+            throw new AuthenticationProtocolException(
+                "PublicKeyAuthenticationClient instance required");
+        }
+
+        File keyfile = (instance.getKeyfile() == null) ? null
+                                                       : new File(instance.getKeyfile());
+        String passphrase = null;
+        SshPrivateKeyFile pkf = null;
+        SshPrivateKey key;
+
+        if ((keyfile == null) || !keyfile.exists()) {
+            JFileChooser chooser = new JFileChooser();
+            chooser.setFileHidingEnabled(false);
+            chooser.setDialogTitle("Select Private Key File For Authentication");
+
+            if (chooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) {
+                keyfile = chooser.getSelectedFile();
+            } else {
+                return false;
+            }
+        }
+
+        FileInputStream in = null;
+
+        try {
+            pkf = SshPrivateKeyFile.parse(keyfile);
+        } catch (InvalidSshKeyException iske) {
+            JOptionPane.showMessageDialog(parent, iske.getMessage());
+
+            return false;
+        } catch (IOException ioe) {
+            JOptionPane.showMessageDialog(parent, ioe.getMessage());
+        }
+
+        // Now see if its passphrase protected
+        if (pkf.isPassphraseProtected()) {
+            // Show the passphrase dialog
+            Window w = (Window) SwingUtilities.getAncestorOfClass(Window.class,
+                    parent);
+            PassphraseDialog dialog = null;
+
+            if (w instanceof Frame) {
+                dialog = new PassphraseDialog((Frame) w);
+            } else if (w instanceof Dialog) {
+                dialog = new PassphraseDialog((Dialog) w);
+            } else {
+                dialog = new PassphraseDialog();
+            }
+
+            do {
+                dialog.setVisible(true);
+
+                if (dialog.isCancelled()) {
+                    return false;
+                }
+
+                passphrase = new String(dialog.getPassphrase());
+
+                try {
+                    key = pkf.toPrivateKey(passphrase);
+
+                    break;
+                } catch (InvalidSshKeyException ihke) {
+                    dialog.setMessage("Passphrase Invalid! Try again");
+                    dialog.setMessageForeground(Color.red);
+                }
+            } while (true);
+        } else {
+            try {
+                key = pkf.toPrivateKey(passphrase);
+            } catch (InvalidSshKeyException ihke) {
+                return false;
+            }
+        }
+
+        instance.setKey(key);
+        instance.setKeyfile(keyfile.getAbsolutePath());
+
+        return true;
+    }
+}
diff --git a/src/com/sshtools/common/authentication/largebanner.png b/src/com/sshtools/common/authentication/largebanner.png
new file mode 100644
index 0000000000000000000000000000000000000000..5842dca2ccf6adfd4100a29cb1622f1e44d451fd
Binary files /dev/null and b/src/com/sshtools/common/authentication/largebanner.png differ
diff --git a/src/com/sshtools/common/authentication/largecard.png b/src/com/sshtools/common/authentication/largecard.png
new file mode 100644
index 0000000000000000000000000000000000000000..145059180d03429672479b24622ea4302b7df528
Binary files /dev/null and b/src/com/sshtools/common/authentication/largecard.png differ
diff --git a/src/com/sshtools/common/authentication/largekbi.png b/src/com/sshtools/common/authentication/largekbi.png
new file mode 100644
index 0000000000000000000000000000000000000000..877e309e62fdf10b90b4e141826722b93a26db4f
Binary files /dev/null and b/src/com/sshtools/common/authentication/largekbi.png differ
diff --git a/src/com/sshtools/common/authentication/largepassphrase.png b/src/com/sshtools/common/authentication/largepassphrase.png
new file mode 100644
index 0000000000000000000000000000000000000000..d4d2bb01b67f37b6d66b99e0c575d50cd4e152e3
Binary files /dev/null and b/src/com/sshtools/common/authentication/largepassphrase.png differ
diff --git a/src/com/sshtools/common/authentication/largepassword.png b/src/com/sshtools/common/authentication/largepassword.png
new file mode 100644
index 0000000000000000000000000000000000000000..811f0159842630389e19e156628421b1eb97c48c
Binary files /dev/null and b/src/com/sshtools/common/authentication/largepassword.png differ
diff --git a/src/com/sshtools/common/automate/AuthorizedKeys.java b/src/com/sshtools/common/automate/AuthorizedKeys.java
new file mode 100644
index 0000000000000000000000000000000000000000..7329df129c9aea3b1ada7bcfa9fba604fa6ae830
--- /dev/null
+++ b/src/com/sshtools/common/automate/AuthorizedKeys.java
@@ -0,0 +1,140 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.automate;
+
+import com.sshtools.j2ssh.transport.publickey.InvalidSshKeyException;
+import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
+
+import java.io.IOException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class AuthorizedKeys {
+    private HashMap keys = new HashMap();
+
+    /**
+*
+*
+* @return
+*/
+    public Map getAuthorizedKeys() {
+        return keys;
+    }
+
+    /**
+*
+*
+* @param username
+* @param key
+*/
+    public void addKey(String username, SshPublicKey key) {
+        if (!containsKey(key)) {
+            keys.put(key, username);
+        }
+    }
+
+    /**
+*
+*
+* @param key
+*/
+    public void removeKey(SshPublicKey key) {
+        keys.remove(key);
+    }
+
+    /**
+*
+*
+* @param key
+*
+* @return
+*/
+    public boolean containsKey(SshPublicKey key) {
+        return keys.containsValue(key);
+    }
+
+    /**
+*
+*
+* @param formatted
+* @param serverId
+* @param loader
+*
+* @return
+*
+* @throws RemoteIdentificationException
+* @throws IOException
+* @throws InvalidSshKeyException
+*/
+    public static AuthorizedKeys parse(byte[] formatted, String serverId,
+        String hostname, AuthorizedKeysFileLoader loader)
+        throws RemoteIdentificationException, IOException, 
+            InvalidSshKeyException {
+        AuthorizedKeysFormat format = RemoteIdentificationFactory.getInstance(serverId,
+                hostname).getAuthorizedKeysFormat();
+
+        if (format.requiresKeyFiles()) {
+            return format.unformat(formatted, loader);
+        } else {
+            return format.unformat(formatted);
+        }
+    }
+
+    /**
+*
+*
+* @param keys
+* @param serverId
+* @param saver
+*
+* @return
+*
+* @throws RemoteIdentificationException
+* @throws IOException
+* @throws InvalidSshKeyException
+*/
+    public static byte[] create(AuthorizedKeys keys, String serverId,
+        String hostname, AuthorizedKeysFileSaver saver)
+        throws RemoteIdentificationException, IOException, 
+            InvalidSshKeyException {
+        AuthorizedKeysFormat format = RemoteIdentificationFactory.getInstance(serverId,
+                hostname).getAuthorizedKeysFormat();
+
+        if (format.requiresKeyFiles()) {
+            return format.format(keys, saver);
+        } else {
+            return format.format(keys);
+        }
+    }
+}
diff --git a/src/com/sshtools/common/automate/AuthorizedKeysFileLoader.java b/src/com/sshtools/common/automate/AuthorizedKeysFileLoader.java
new file mode 100644
index 0000000000000000000000000000000000000000..e01eb44ce31aea0d0f794c836171996454ed71a8
--- /dev/null
+++ b/src/com/sshtools/common/automate/AuthorizedKeysFileLoader.java
@@ -0,0 +1,47 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.automate;
+
+import java.io.*;
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public interface AuthorizedKeysFileLoader {
+  /**
+   *
+   *
+   * @param filename
+   *
+   * @return
+   *
+   * @throws IOException
+   */
+  public byte[] loadFile(String filename) throws IOException;
+}
diff --git a/src/com/sshtools/common/automate/AuthorizedKeysFileSaver.java b/src/com/sshtools/common/automate/AuthorizedKeysFileSaver.java
new file mode 100644
index 0000000000000000000000000000000000000000..ce58576d435aec552f03aaa6e72782468737328a
--- /dev/null
+++ b/src/com/sshtools/common/automate/AuthorizedKeysFileSaver.java
@@ -0,0 +1,48 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.automate;
+
+import java.io.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public interface AuthorizedKeysFileSaver {
+    /**
+*
+*
+* @param filename
+* @param filedata
+*
+* @throws IOException
+*/
+    public void saveFile(String filename, byte[] filedata)
+        throws IOException;
+}
diff --git a/src/com/sshtools/common/automate/AuthorizedKeysFormat.java b/src/com/sshtools/common/automate/AuthorizedKeysFormat.java
new file mode 100644
index 0000000000000000000000000000000000000000..206f02693149c5def8f5f50ef4035a4b3c946b07
--- /dev/null
+++ b/src/com/sshtools/common/automate/AuthorizedKeysFormat.java
@@ -0,0 +1,101 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.automate;
+
+import com.sshtools.j2ssh.transport.publickey.InvalidSshKeyException;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public interface AuthorizedKeysFormat {
+    /**
+*
+*
+* @param keys
+*
+* @return
+*
+* @throws IOException
+* @throws InvalidSshKeyException
+*/
+    public byte[] format(AuthorizedKeys keys)
+        throws IOException, InvalidSshKeyException;
+
+    /**
+*
+*
+* @param keys
+* @param saver
+*
+* @return
+*
+* @throws IOException
+* @throws InvalidSshKeyException
+*/
+    public byte[] format(AuthorizedKeys keys, AuthorizedKeysFileSaver saver)
+        throws IOException, InvalidSshKeyException;
+
+    /**
+*
+*
+* @param formatted
+*
+* @return
+*
+* @throws IOException
+* @throws InvalidSshKeyException
+*/
+    public AuthorizedKeys unformat(byte[] formatted)
+        throws IOException, InvalidSshKeyException;
+
+    /**
+*
+*
+* @param formatted
+* @param loader
+*
+* @return
+*
+* @throws IOException
+* @throws InvalidSshKeyException
+*/
+    public AuthorizedKeys unformat(byte[] formatted,
+        AuthorizedKeysFileLoader loader)
+        throws IOException, InvalidSshKeyException;
+
+    /**
+*
+*
+* @return
+*/
+    public boolean requiresKeyFiles();
+}
diff --git a/src/com/sshtools/common/automate/AutomationConfiguration.java b/src/com/sshtools/common/automate/AutomationConfiguration.java
new file mode 100644
index 0000000000000000000000000000000000000000..594cc0b85a975c3793a80f8314dc1d75f24c2bf8
--- /dev/null
+++ b/src/com/sshtools/common/automate/AutomationConfiguration.java
@@ -0,0 +1,224 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.automate;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class AutomationConfiguration {
+    private HashMap remoteIdentifications = new HashMap();
+
+    /**
+* Creates a new AutomationConfiguration object.
+*
+* @param in
+*
+* @throws IOException
+* @throws SAXException
+* @throws ParserConfigurationException
+*/
+    public AutomationConfiguration(InputStream in)
+        throws IOException, SAXException, ParserConfigurationException {
+        SAXParserFactory saxFactory = SAXParserFactory.newInstance();
+        SAXParser saxParser = saxFactory.newSAXParser();
+        saxParser.parse(in, new AutomationConfigurationSAXHandler());
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public Map getRemoteIdentifications() {
+        return remoteIdentifications;
+    }
+
+    /**
+*
+*
+* @param args
+*/
+    public static void main(String[] args) {
+        try {
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    private class AutomationConfigurationSAXHandler extends DefaultHandler {
+        private String AUTOMATION_ELEMENT = "Automation";
+        private String REMOTEID_ELEMENT = "RemoteIdentification";
+        private String AUTHORIZEDKEYSFORMAT_ELEMENT = "AuthorizedKeysFormat";
+        private String RULE_ELEMENT = "Rule";
+        private String STARTSWITH_ATTRIBUTE = "startsWith";
+        private String CONTAINS_ATTRIBUTE = "contains";
+        private String DEFAULTNAME_ATTRIBUTE = "defaultName";
+        private String NAME_ATTRIBUTE = "name";
+        private String IMPLEMENTATION_ATTRIBUTE = "implementationClass";
+        private String PRIORITY_ATTRIBUTE = "priority";
+        private String DEFAULTPATH_ATTRIBUTE = "defaultPath";
+        private String currentElement = null;
+        private RemoteIdentification currentRID = null;
+
+        public void startElement(String uri, String localName, String qname,
+            Attributes attrs) throws SAXException {
+            if (currentElement == null) {
+                if (!qname.equals(AUTOMATION_ELEMENT)) {
+                    throw new SAXException("Unexpected root element <" + qname +
+                        ">");
+                }
+            } else {
+                if (currentElement.equals(AUTOMATION_ELEMENT)) {
+                    if (qname.equals(REMOTEID_ELEMENT)) {
+                        String defaultName = attrs.getValue(DEFAULTNAME_ATTRIBUTE);
+
+                        if (defaultName == null) {
+                            throw new SAXException(DEFAULTNAME_ATTRIBUTE +
+                                " attribute must be specified");
+                        }
+
+                        currentRID = new RemoteIdentification(defaultName);
+                    } else {
+                        throw new SAXException("Unexpected element <" + qname +
+                            ">");
+                    }
+                } else if (currentElement.equals(REMOTEID_ELEMENT)) {
+                    if (qname.equals(RULE_ELEMENT)) {
+                        String startsWith = attrs.getValue(STARTSWITH_ATTRIBUTE);
+                        String contains = attrs.getValue(CONTAINS_ATTRIBUTE);
+                        String name = attrs.getValue(NAME_ATTRIBUTE);
+                        String priority = attrs.getValue(PRIORITY_ATTRIBUTE);
+
+                        try {
+                            RemoteIdentificationRule rule = new RemoteIdentificationRule();
+
+                            if (startsWith != null) {
+                                rule.addExpression(STARTSWITH_ATTRIBUTE,
+                                    startsWith);
+                            }
+
+                            if (contains != null) {
+                                rule.addExpression(CONTAINS_ATTRIBUTE, contains);
+                            }
+
+                            if (name != null) {
+                                rule.setName(name);
+                            }
+
+                            try {
+                                if (priority != null) {
+                                    rule.setPriority(Integer.parseInt(priority));
+                                }
+                            } catch (NumberFormatException ex1) {
+                                throw new SAXException(
+                                    "Failed to parse priority value! value=" +
+                                    priority);
+                            }
+
+                            currentRID.addRule(rule);
+                        } catch (UnsupportedRuleException ure) {
+                            throw new SAXException(ure.getMessage());
+                        }
+                    } else if (qname.equals(AUTHORIZEDKEYSFORMAT_ELEMENT)) {
+                        String implementationClass = attrs.getValue(IMPLEMENTATION_ATTRIBUTE);
+                        String defaultPath = attrs.getValue(DEFAULTPATH_ATTRIBUTE);
+
+                        if (implementationClass == null) {
+                            throw new SAXException(IMPLEMENTATION_ATTRIBUTE +
+                                " attribute is required");
+                        }
+
+                        try {
+                            currentRID.setAuthorizedKeysFormat(Class.forName(
+                                    implementationClass));
+                            currentRID.setAuthorizedKeysDefaultPath(defaultPath);
+                        } catch (ClassNotFoundException ex) {
+                            throw new SAXException(ex.getMessage());
+                        }
+                    } else {
+                        throw new SAXException("Unexpected element <" + qname +
+                            ">");
+                    }
+                } else {
+                    throw new SAXException("Unexpected element <" + qname +
+                        ">");
+                }
+            }
+
+            currentElement = qname;
+        }
+
+        public void endElement(String uri, String localName, String qname)
+            throws SAXException {
+            if (currentElement != null) {
+                if (!currentElement.equals(qname)) {
+                    throw new SAXException("Unexpected end element found <" +
+                        qname + ">");
+                }
+
+                if (currentElement.equals(REMOTEID_ELEMENT)) {
+                    if (currentRID.getRules().size() > 0) {
+                        remoteIdentifications.put(currentRID.getDefaultName(),
+                            currentRID);
+                    } else {
+                        throw new SAXException("<" + REMOTEID_ELEMENT + "> " +
+                            " requires at least one child <" + RULE_ELEMENT +
+                            "> element!");
+                    }
+
+                    currentElement = AUTOMATION_ELEMENT;
+                } else if (currentElement.equals(RULE_ELEMENT)) {
+                    currentElement = REMOTEID_ELEMENT;
+                } else if (currentElement.equals(AUTHORIZEDKEYSFORMAT_ELEMENT)) {
+                    currentElement = REMOTEID_ELEMENT;
+                } else if (currentElement.equals(AUTOMATION_ELEMENT)) {
+                    currentElement = null;
+                } else {
+                    throw new SAXException("Unexpected end element <" + qname +
+                        ">");
+                }
+            }
+        }
+    }
+}
diff --git a/src/com/sshtools/common/automate/OpenSSHAuthorizedKeysFormat.java b/src/com/sshtools/common/automate/OpenSSHAuthorizedKeysFormat.java
new file mode 100644
index 0000000000000000000000000000000000000000..19997f78f15521ad77a6d3ebfa2fecbcc72505b9
--- /dev/null
+++ b/src/com/sshtools/common/automate/OpenSSHAuthorizedKeysFormat.java
@@ -0,0 +1,151 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.automate;
+
+import com.sshtools.j2ssh.transport.publickey.InvalidSshKeyException;
+import com.sshtools.j2ssh.transport.publickey.OpenSSHPublicKeyFormat;
+import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
+import com.sshtools.j2ssh.transport.publickey.SshPublicKeyFile;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+import java.util.Iterator;
+import java.util.Map;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class OpenSSHAuthorizedKeysFormat implements AuthorizedKeysFormat {
+    /**
+*
+*
+* @param keys
+* @param saver
+*
+* @return
+*
+* @throws IOException
+* @throws InvalidSshKeyException
+* @throws UnsupportedOperationException
+*/
+    public byte[] format(AuthorizedKeys keys, AuthorizedKeysFileSaver saver)
+        throws IOException, InvalidSshKeyException {
+        throw new UnsupportedOperationException(
+            "The OpenSSH authorized key file does not support additional key files!");
+    }
+
+    /**
+*
+*
+* @param formatted
+* @param loader
+*
+* @return
+*
+* @throws IOException
+* @throws InvalidSshKeyException
+* @throws UnsupportedOperationException
+*/
+    public AuthorizedKeys unformat(byte[] formatted,
+        AuthorizedKeysFileLoader loader)
+        throws IOException, InvalidSshKeyException {
+        throw new UnsupportedOperationException(
+            "The OpenSSH authorized key file does not support additional key files!");
+    }
+
+    /**
+*
+*
+* @param keys
+*
+* @return
+*
+* @throws IOException
+* @throws InvalidSshKeyException
+*/
+    public byte[] format(AuthorizedKeys keys)
+        throws IOException, InvalidSshKeyException {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        SshPublicKeyFile pubfile;
+        OpenSSHPublicKeyFormat openssh = new OpenSSHPublicKeyFormat();
+        Map.Entry entry;
+
+        for (Iterator it = keys.getAuthorizedKeys().entrySet().iterator();
+                (it != null) && it.hasNext();) {
+            entry = (Map.Entry) it.next();
+            openssh.setComment((String) entry.getValue());
+            pubfile = SshPublicKeyFile.create((SshPublicKey) entry.getKey(),
+                    openssh);
+            out.write(pubfile.toString().getBytes("US-ASCII"));
+            out.write('\n');
+        }
+
+        return out.toByteArray();
+    }
+
+    /**
+*
+*
+* @param formatted
+*
+* @return
+*
+* @throws IOException
+* @throws InvalidSshKeyException
+*/
+    public AuthorizedKeys unformat(byte[] formatted)
+        throws IOException, InvalidSshKeyException {
+        AuthorizedKeys keys = new AuthorizedKeys();
+        BufferedReader reader = new BufferedReader(new InputStreamReader(
+                    new ByteArrayInputStream(formatted)));
+        String line;
+        SshPublicKeyFile pubfile;
+
+        while ((line = reader.readLine()) != null) {
+            pubfile = SshPublicKeyFile.parse(line.getBytes("US-ASCII"));
+            keys.addKey(pubfile.getComment(), pubfile.toPublicKey());
+        }
+
+        return keys;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public boolean requiresKeyFiles() {
+        return false;
+    }
+}
diff --git a/src/com/sshtools/common/automate/RemoteIdentification.java b/src/com/sshtools/common/automate/RemoteIdentification.java
new file mode 100644
index 0000000000000000000000000000000000000000..0f3e1a8ce5631ee3bfe23a40fe47d02fcb9e2d93
--- /dev/null
+++ b/src/com/sshtools/common/automate/RemoteIdentification.java
@@ -0,0 +1,400 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.automate;
+
+import com.sshtools.j2ssh.SftpClient;
+import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.19 $
+ */
+public class RemoteIdentification {
+    /**  */
+    public static final int ADD_AUTHORIZEDKEY = 1;
+
+    /**  */
+    public static final int REMOVE_AUTHORIZEDKEY = 2;
+    private String defaultName;
+    private Vector rules = new Vector();
+    private Class authorizedKeysFormat;
+    private String defaultPath;
+
+    /**  */
+    protected Log log = LogFactory.getLog(RemoteIdentification.class);
+
+    /**
+* Creates a new RemoteIdentification object.
+*
+* @param defaultName
+*/
+    public RemoteIdentification(String defaultName) {
+        this.defaultName = defaultName;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    protected List getRules() {
+        return rules;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getDefaultName() {
+        return defaultName;
+    }
+
+    /**
+*
+*
+* @param ident
+*
+* @return
+*
+* @throws RemoteIdentificationException
+*/
+    public String getName(String ident) throws RemoteIdentificationException {
+        boolean pass = false;
+        Iterator it = rules.iterator();
+        Vector passed = new Vector();
+        RemoteIdentificationRule rule;
+        String rulename = null;
+
+        // Check all the rules
+        while (it.hasNext()) {
+            rule = (RemoteIdentificationRule) it.next();
+
+            if (rule.testRule(ident)) {
+                passed.add(rule);
+            }
+        }
+
+        if (passed.size() > 0) {
+            // Select the highest priority rule where 0=highest 10=lowest
+            it = passed.iterator();
+
+            RemoteIdentificationRule ret = null;
+
+            while (it.hasNext()) {
+                rule = (RemoteIdentificationRule) it.next();
+
+                if (ret == null) {
+                    ret = rule;
+                } else {
+                    if (rule.getPriority() < ret.getPriority()) {
+                        ret = rule;
+                    }
+                }
+            }
+
+            if (ret.getName() != null) {
+                return ret.getName();
+            } else {
+                return defaultName;
+            }
+        } else {
+            throw new RemoteIdentificationException(
+                "No rules exist to identify the remote host with ident string " +
+                ident);
+        }
+    }
+
+    /**
+*
+*
+* @param rule
+*/
+    protected void addRule(RemoteIdentificationRule rule) {
+        rules.add(rule);
+    }
+
+    /**
+*
+*
+* @param ident
+*
+* @return
+*/
+    protected boolean testRules(String ident) {
+        boolean pass = false;
+        Iterator it = rules.iterator();
+        RemoteIdentificationRule rule;
+
+        while (it.hasNext() && !pass) {
+            rule = (RemoteIdentificationRule) it.next();
+            pass = rule.testRule(ident);
+        }
+
+        return pass;
+    }
+
+    /**
+*
+*
+* @param implementationClass
+*/
+    protected void setAuthorizedKeysFormat(Class implementationClass) {
+        authorizedKeysFormat = implementationClass;
+    }
+
+    /**
+*
+*
+* @param defaultPath
+*/
+    protected void setAuthorizedKeysDefaultPath(String defaultPath) {
+        this.defaultPath = defaultPath;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getAuthorizedKeysDefaultPath() {
+        return defaultPath;
+    }
+
+    /**
+*
+*
+* @return
+*
+* @throws RemoteIdentificationException
+*/
+    public AuthorizedKeysFormat getAuthorizedKeysFormat()
+        throws RemoteIdentificationException {
+        try {
+            if (authorizedKeysFormat != null) {
+                return (AuthorizedKeysFormat) authorizedKeysFormat.newInstance();
+            } else {
+                throw new RemoteIdentificationException(
+                    "There is no authorized keys format set for this remote id");
+            }
+        } catch (Exception ex) {
+            throw new RemoteIdentificationException("Failed to instansiate " +
+                authorizedKeysFormat.getName());
+        }
+    }
+
+    /**
+*
+*
+* @param sftp
+* @param serverId
+* @param system
+* @param username
+* @param pk
+* @param authorizationFile
+* @param mode
+*
+* @return
+*
+* @throws RemoteIdentificationException
+*/
+    public boolean configureUserAccess(SftpClient sftp, String serverId,
+        String system, String username, SshPublicKey pk,
+        String authorizationFile, int mode)
+        throws RemoteIdentificationException {
+        Vector keys = new Vector();
+        keys.add(pk);
+
+        return configureUserAccess(sftp, serverId, system, username, keys,
+            authorizationFile, mode);
+    }
+
+    /**
+*
+*
+* @param sftp
+* @param serverId
+* @param system
+* @param username
+* @param keys
+* @param authorizationFile
+* @param mode
+*
+* @return
+*
+* @throws RemoteIdentificationException
+*/
+    public boolean configureUserAccess(final SftpClient sftp,
+        final String serverId, String system, String username, List keys,
+        String authorizationFile, int mode)
+        throws RemoteIdentificationException {
+        try {
+            if (sftp.isClosed()) {
+                throw new RemoteIdentificationException(
+                    "SFTP connection must be open");
+            }
+
+            if (authorizationFile == null) {
+                throw new RemoteIdentificationException(
+                    "authorization file cannot be null");
+            }
+
+            if ((mode != ADD_AUTHORIZEDKEY) && (mode != REMOVE_AUTHORIZEDKEY)) {
+                throw new RemoteIdentificationException(
+                    "Invalid configuration mode specifed in call to configureUserAccess");
+            }
+
+            AuthorizedKeys authorizedKeys;
+            authorizationFile.replace('\\', '/');
+
+            final String directory = ((authorizationFile.lastIndexOf("/") > 0)
+                ? authorizationFile.substring(0,
+                    authorizationFile.lastIndexOf("/") + 1) : "");
+
+            try {
+                // Remove the old backup - ignore the error
+                try {
+                    sftp.rm(authorizationFile + ".bak");
+                } catch (IOException ex) {
+                }
+
+                // Change the current authorization file to the backup
+                sftp.rename(authorizationFile, authorizationFile + ".bak");
+                log.info("Opening existing authorized keys file from " +
+                    authorizationFile + ".bak");
+
+                ByteArrayOutputStream out = new ByteArrayOutputStream();
+                sftp.get(authorizationFile + ".bak", out);
+
+                byte[] backup = out.toByteArray();
+                out.close();
+
+                // Obtain the current authoized keys settings
+                log.info("Parsing authorized keys file");
+                authorizedKeys = AuthorizedKeys.parse(backup, serverId, system,
+                        new AuthorizedKeysFileLoader() {
+                            public byte[] loadFile(String filename)
+                                throws IOException {
+                                ByteArrayOutputStream out = new ByteArrayOutputStream();
+                                sftp.get(directory + filename, out);
+                                out.close();
+
+                                return out.toByteArray();
+                            }
+                        });
+            } catch (IOException ioe) {
+                // Could not open so create a new file
+                authorizedKeys = new AuthorizedKeys();
+            } catch (RemoteIdentificationException rie) {
+                throw new RemoteIdentificationException(
+                    "Open3SP cannot identify the remote host.\n" +
+                    "Please email support@open3sp.org with specifying 'remote identification' in the subject and supplying the server type and the follwing data '" +
+                    serverId + "'");
+            }
+
+            log.info("Updating authorized keys file");
+
+            // Check the existing keys and add any that are not present
+            SshPublicKey pk;
+
+            for (Iterator x = keys.iterator(); x.hasNext();) {
+                pk = (SshPublicKey) x.next();
+
+                if (!authorizedKeys.containsKey(pk) &&
+                        (mode == ADD_AUTHORIZEDKEY)) {
+                    authorizedKeys.addKey(username, pk);
+                } else if (authorizedKeys.containsKey(pk) &&
+                        (mode == REMOVE_AUTHORIZEDKEY)) {
+                    authorizedKeys.removeKey(pk);
+                }
+            }
+
+            // Verfiy that the directory exists?
+            log.info("Verifying directory " + directory);
+
+            int umask = sftp.umask(0022);
+            sftp.mkdirs(directory);
+
+            // Output the new file
+            log.info("Writing new authorized keys file to " +
+                authorizationFile);
+
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+            // Output the authorization file to a ByteArrayOutputStream
+            out.write(AuthorizedKeys.create(authorizedKeys, serverId, system,
+                    new AuthorizedKeysFileSaver() {
+                    public void saveFile(String filename, byte[] filedata)
+                        throws IOException {
+                        //SftpFile file = null;
+                        ByteArrayInputStream in = null;
+
+                        try {
+                            in = new ByteArrayInputStream(filedata);
+                            sftp.put(in, directory + filename);
+                        } catch (IOException ex) {
+                            log.info("Error writing public key file to server" +
+                                    filename, ex);
+                        } finally {
+                            if (in != null) {
+                                in.close();
+                            }
+                        }
+                    }
+                }));
+            out.close();
+
+            // Copy the new authorisation file to the server
+            ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+            sftp.umask(0133);
+            sftp.put(in, authorizationFile);
+            sftp.umask(umask);
+
+            return true;
+        } catch (IOException ioe) {
+            throw new RemoteIdentificationException(ioe.getMessage());
+        } catch (RemoteIdentificationException rie) {
+            throw new RemoteIdentificationException(
+                "SSHTools cannot identify the remote host.\n" +
+                "Please email support@sshtools.com specifying 'remote identification' in the subject, supplying the server type and the following data: '" +
+                serverId + "'");
+        }
+    }
+}
diff --git a/src/com/sshtools/common/automate/RemoteIdentificationException.java b/src/com/sshtools/common/automate/RemoteIdentificationException.java
new file mode 100644
index 0000000000000000000000000000000000000000..ab599054b4edba7c92f6f0061bd3fa1beca66c3a
--- /dev/null
+++ b/src/com/sshtools/common/automate/RemoteIdentificationException.java
@@ -0,0 +1,44 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.automate;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class RemoteIdentificationException extends Exception {
+    /**
+* Creates a new RemoteIdentificationException object.
+*
+* @param msg
+*/
+    public RemoteIdentificationException(String msg) {
+        super(msg);
+    }
+}
diff --git a/src/com/sshtools/common/automate/RemoteIdentificationFactory.java b/src/com/sshtools/common/automate/RemoteIdentificationFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..ae02fa4ca2b70dc5ed8de8d369d30bd59ca73ab5
--- /dev/null
+++ b/src/com/sshtools/common/automate/RemoteIdentificationFactory.java
@@ -0,0 +1,122 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.automate;
+
+import com.sshtools.j2ssh.configuration.ConfigurationException;
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+
+import java.util.Iterator;
+import java.util.Map;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class RemoteIdentificationFactory {
+    private static Map remoteIdentifications = null;
+
+    static {
+        try {
+            if (ConfigurationLoader.isConfigurationAvailable(
+                        AutomationConfiguration.class)) {
+                remoteIdentifications = ((AutomationConfiguration) ConfigurationLoader.getConfiguration(AutomationConfiguration.class)).getRemoteIdentifications();
+            }
+        } catch (ConfigurationException ex) {
+        }
+    }
+
+    /**
+*
+*
+* @param serverId
+*
+* @return
+*
+* @throws RemoteIdentificationException
+*/
+    public static synchronized RemoteIdentification getInstance(
+        String serverId, String hostname) throws RemoteIdentificationException {
+        if (remoteIdentifications == null) {
+            throw new RemoteIdentificationException(
+                "There are no remote identification rules!");
+        }
+
+        Iterator it = remoteIdentifications.entrySet().iterator();
+        Map.Entry entry;
+        RemoteIdentification rid;
+
+        // Check the hostname first
+        while (it.hasNext()) {
+            entry = (Map.Entry) it.next();
+            rid = (RemoteIdentification) entry.getValue();
+
+            if (hostname != null) {
+                if (rid.getDefaultName().equals(hostname)) {
+                    return rid;
+                }
+            }
+        }
+
+        it = remoteIdentifications.entrySet().iterator();
+
+        // Now check against the rules
+        while (it.hasNext()) {
+            entry = (Map.Entry) it.next();
+            rid = (RemoteIdentification) entry.getValue();
+
+            if (rid.testRules(serverId)) {
+                return rid;
+            }
+        }
+
+        throw new RemoteIdentificationException(
+            "Failed to find a remote identification rule");
+    }
+
+    /**
+*
+*
+* @param args
+*/
+    public static void main(String[] args) {
+        try {
+            RemoteIdentification rid;
+            String serverId = "http://www.sshtools.com J2SSH 0.1.1 beta [CLIENT]";
+            rid = getInstance(serverId, null);
+            System.out.println("Remote Identification: " +
+                rid.getName(serverId));
+            serverId = "OpenSSH_3.4p1";
+            rid = getInstance(serverId, null);
+            System.out.println("Remote Identification: " +
+                rid.getName(serverId));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/src/com/sshtools/common/automate/RemoteIdentificationRule.java b/src/com/sshtools/common/automate/RemoteIdentificationRule.java
new file mode 100644
index 0000000000000000000000000000000000000000..542053a31646065b7fc5cd9ea019a5955a92e73b
--- /dev/null
+++ b/src/com/sshtools/common/automate/RemoteIdentificationRule.java
@@ -0,0 +1,137 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.automate;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class RemoteIdentificationRule {
+    private static HashSet allowedOperations = new HashSet();
+
+    static {
+        allowedOperations.add("startsWith");
+        allowedOperations.add("contains");
+    }
+
+    private HashMap expressions = new HashMap();
+    private int priority = 10;
+    private String name;
+
+    /**
+*
+*
+* @param identification
+*
+* @return
+*/
+    public boolean testRule(String identification) {
+        // Get the software version portion of the id string
+        String svc = identification.substring(identification.lastIndexOf("-") +
+                1);
+        Iterator it = expressions.entrySet().iterator();
+        Map.Entry entry;
+        boolean pass = false;
+        String operation;
+
+        while (it.hasNext()) {
+            entry = (Map.Entry) it.next();
+            operation = (String) entry.getKey();
+
+            if (operation.equals("startsWith")) {
+                pass = svc.startsWith((String) entry.getValue());
+            }
+
+            if (operation.equals("contains")) {
+                pass = (svc.indexOf((String) entry.getValue()) >= 0);
+            }
+        }
+
+        return pass;
+    }
+
+    /**
+*
+*
+* @param priority
+*/
+    protected void setPriority(int priority) {
+        this.priority = priority;
+    }
+
+    /**
+*
+*
+* @param operation
+* @param value
+*
+* @throws UnsupportedRuleException
+*/
+    protected void addExpression(String operation, String value)
+        throws UnsupportedRuleException {
+        if (allowedOperations.contains(operation)) {
+            expressions.put(operation, value);
+        } else {
+            throw new UnsupportedRuleException("The rule '" + operation +
+                "' is not supported");
+        }
+    }
+
+    /**
+*
+*
+* @param name
+*/
+    protected void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getName() {
+        return name;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public int getPriority() {
+        return priority;
+    }
+}
diff --git a/src/com/sshtools/common/automate/SSH2AuthorizedKeysFormat.java b/src/com/sshtools/common/automate/SSH2AuthorizedKeysFormat.java
new file mode 100644
index 0000000000000000000000000000000000000000..30d0047865e12ebb35d3998f52694c418bc9e97a
--- /dev/null
+++ b/src/com/sshtools/common/automate/SSH2AuthorizedKeysFormat.java
@@ -0,0 +1,174 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.automate;
+
+import com.sshtools.j2ssh.transport.publickey.InvalidSshKeyException;
+import com.sshtools.j2ssh.transport.publickey.SECSHPublicKeyFormat;
+import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
+import com.sshtools.j2ssh.transport.publickey.SshPublicKeyFile;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+import java.util.Iterator;
+import java.util.Map;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class SSH2AuthorizedKeysFormat implements AuthorizedKeysFormat {
+    private static final String header = "# Open3SP auto-generated authorization file\n";
+    private static final String key = "key ";
+
+    /**
+*
+*
+* @param keys
+*
+* @return
+*
+* @throws IOException
+* @throws InvalidSshKeyException
+* @throws java.lang.UnsupportedOperationException
+*/
+    public byte[] format(AuthorizedKeys keys)
+        throws IOException, InvalidSshKeyException {
+        throw new java.lang.UnsupportedOperationException(
+            "SSH2 authorized keys format requries seperate key files!");
+    }
+
+    /**
+*
+*
+* @param formatted
+*
+* @return
+*
+* @throws IOException
+* @throws InvalidSshKeyException
+* @throws java.lang.UnsupportedOperationException
+*/
+    public AuthorizedKeys unformat(byte[] formatted)
+        throws IOException, InvalidSshKeyException {
+        throw new java.lang.UnsupportedOperationException(
+            "SSH2 authorized keys format requries seperate key files!");
+    }
+
+    /**
+*
+*
+* @param keys
+* @param saver
+*
+* @return
+*
+* @throws IOException
+* @throws InvalidSshKeyException
+*/
+    public byte[] format(AuthorizedKeys keys, AuthorizedKeysFileSaver saver)
+        throws IOException, InvalidSshKeyException {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        out.write(header.getBytes("US-ASCII"));
+
+        SshPublicKeyFile pubfile;
+        SECSHPublicKeyFormat secsh = new SECSHPublicKeyFormat();
+        Map.Entry entry;
+
+        for (Iterator it = keys.getAuthorizedKeys().entrySet().iterator();
+                (it != null) && it.hasNext();) {
+            entry = (Map.Entry) it.next();
+
+            // Write out the public key file
+            String username = (String) entry.getValue();
+            String filename = username + ".pub";
+            secsh.setComment(username);
+            pubfile = SshPublicKeyFile.create((SshPublicKey) entry.getKey(),
+                    secsh);
+            saver.saveFile(filename, pubfile.toString().getBytes("US-ASCII"));
+
+            // Write out the key entry
+            out.write(key.getBytes("US-ASCII"));
+            out.write(filename.getBytes("US-ASCII"));
+            out.write('\n');
+        }
+
+        return out.toByteArray();
+    }
+
+    /**
+*
+*
+* @param formatted
+* @param loader
+*
+* @return
+*
+* @throws IOException
+* @throws InvalidSshKeyException
+*/
+    public AuthorizedKeys unformat(byte[] formatted,
+        AuthorizedKeysFileLoader loader)
+        throws IOException, InvalidSshKeyException {
+        AuthorizedKeys keys = new AuthorizedKeys();
+        BufferedReader reader = new BufferedReader(new InputStreamReader(
+                    new ByteArrayInputStream(formatted)));
+        String line;
+        SshPublicKeyFile pubfile;
+        String filename;
+        String username;
+
+        while ((line = reader.readLine()) != null) {
+            if (line.trim().startsWith("key")) {
+                // Get the filename and load
+                filename = line.substring(line.trim().lastIndexOf(" ") + 1)
+                               .trim();
+                pubfile = SshPublicKeyFile.parse(loader.loadFile(filename));
+
+                // Get the username from the filename - .pub
+                username = filename.substring(0, filename.length() - 4);
+                keys.addKey(username, pubfile.toPublicKey());
+            }
+        }
+
+        return keys;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public boolean requiresKeyFiles() {
+        return true;
+    }
+}
diff --git a/src/com/sshtools/common/automate/SshtoolsAuthorizedKeysFormat.java b/src/com/sshtools/common/automate/SshtoolsAuthorizedKeysFormat.java
new file mode 100644
index 0000000000000000000000000000000000000000..88e93d06f0d075b0f00e3eef019e858360134347
--- /dev/null
+++ b/src/com/sshtools/common/automate/SshtoolsAuthorizedKeysFormat.java
@@ -0,0 +1,168 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.automate;
+
+import com.sshtools.common.configuration.Authorization;
+
+import com.sshtools.j2ssh.transport.publickey.InvalidSshKeyException;
+import com.sshtools.j2ssh.transport.publickey.SECSHPublicKeyFormat;
+import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
+import com.sshtools.j2ssh.transport.publickey.SshPublicKeyFile;
+
+import org.xml.sax.SAXException;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class SshtoolsAuthorizedKeysFormat implements AuthorizedKeysFormat {
+    /**
+*
+*
+* @param keys
+*
+* @return
+*
+* @throws java.lang.UnsupportedOperationException
+*/
+    public byte[] format(AuthorizedKeys keys) {
+        throw new java.lang.UnsupportedOperationException(
+            "SSHTools authorized keys format requries seperate key files!");
+    }
+
+    /**
+*
+*
+* @param formatted
+*
+* @return
+*
+* @throws java.lang.UnsupportedOperationException
+*/
+    public AuthorizedKeys unformat(byte[] formatted) {
+        throw new java.lang.UnsupportedOperationException(
+            "SSHTools authorized keys format requries seperate key files!");
+    }
+
+    /**
+*
+*
+* @param keys
+* @param saver
+*
+* @return
+*
+* @throws IOException
+* @throws InvalidSshKeyException
+*/
+    public byte[] format(AuthorizedKeys keys, AuthorizedKeysFileSaver saver)
+        throws IOException, InvalidSshKeyException {
+        Authorization authorization = new Authorization();
+        SshPublicKeyFile pubfile;
+        SECSHPublicKeyFormat secsh = new SECSHPublicKeyFormat();
+        Map.Entry entry;
+
+        for (Iterator it = keys.getAuthorizedKeys().entrySet().iterator();
+                (it != null) && it.hasNext();) {
+            entry = (Map.Entry) it.next();
+
+            // Write out the public key file
+            String username = (String) entry.getValue();
+            String filename = username + ".pub";
+            secsh.setComment(username);
+            pubfile = SshPublicKeyFile.create((SshPublicKey) entry.getKey(),
+                    secsh);
+            saver.saveFile(filename, pubfile.toString().getBytes("US-ASCII"));
+
+            // Write out the key entry
+            authorization.addKey(filename);
+        }
+
+        return authorization.toString().getBytes("US-ASCII");
+    }
+
+    /**
+*
+*
+* @param formatted
+* @param loader
+*
+* @return
+*
+* @throws IOException
+* @throws InvalidSshKeyException
+*/
+    public AuthorizedKeys unformat(byte[] formatted,
+        AuthorizedKeysFileLoader loader)
+        throws IOException, InvalidSshKeyException {
+        try {
+            AuthorizedKeys keys = new AuthorizedKeys();
+            Authorization authorization = new Authorization(new ByteArrayInputStream(
+                        formatted));
+            List keyfiles = authorization.getAuthorizedKeys();
+            Iterator it = keyfiles.iterator();
+            String filename;
+            SshPublicKeyFile pubfile;
+            String username;
+
+            while (it.hasNext()) {
+                filename = (String) it.next();
+                pubfile = SshPublicKeyFile.parse(loader.loadFile(filename));
+                username = filename.substring(0, filename.length() - 4);
+                keys.addKey(username, pubfile.toPublicKey());
+            }
+
+            return keys;
+        } catch (ParserConfigurationException ex) {
+            throw new IOException("Failed to read authorization file: " +
+                ex.getMessage());
+        } catch (SAXException ex) {
+            throw new IOException("Failed to read authorization file: " +
+                ex.getMessage());
+        }
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public boolean requiresKeyFiles() {
+        return true;
+    }
+}
diff --git a/src/com/sshtools/common/automate/SystemVerification.java b/src/com/sshtools/common/automate/SystemVerification.java
new file mode 100644
index 0000000000000000000000000000000000000000..dda8feca3140ca3edccdb562a0eba967cec6e8d2
--- /dev/null
+++ b/src/com/sshtools/common/automate/SystemVerification.java
@@ -0,0 +1,73 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.automate;
+
+import com.sshtools.j2ssh.transport.HostKeyVerification;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.11 $
+ */
+public interface SystemVerification extends HostKeyVerification {
+    /**
+*
+*
+* @param defaultPath
+*
+* @return
+*
+* @throws RemoteIdentificationException
+*/
+    public String inputAuthorizationFile(String defaultPath)
+        throws RemoteIdentificationException;
+
+    /**
+*
+*
+* @param prompt
+*
+* @return
+*
+* @throws RemoteIdentificationException
+*/
+    public String inputUsername(String prompt)
+        throws RemoteIdentificationException;
+
+    /**
+*
+*
+* @param prompt
+*
+* @return
+*
+* @throws RemoteIdentificationException
+*/
+    public String inputPassword(String prompt)
+        throws RemoteIdentificationException;
+}
diff --git a/src/com/sshtools/common/automate/UnsupportedRuleException.java b/src/com/sshtools/common/automate/UnsupportedRuleException.java
new file mode 100644
index 0000000000000000000000000000000000000000..b916fb24a9900e8ae2e73fcf5137bde5d4465961
--- /dev/null
+++ b/src/com/sshtools/common/automate/UnsupportedRuleException.java
@@ -0,0 +1,44 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.automate;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class UnsupportedRuleException extends Exception {
+    /**
+* Creates a new UnsupportedRuleException object.
+*
+* @param msg
+*/
+    public UnsupportedRuleException(String msg) {
+        super(msg);
+    }
+}
diff --git a/src/com/sshtools/common/configuration/Authorization.java b/src/com/sshtools/common/configuration/Authorization.java
new file mode 100644
index 0000000000000000000000000000000000000000..600407773ff92523bfebb24ca84bbddf2565b2b3
--- /dev/null
+++ b/src/com/sshtools/common/configuration/Authorization.java
@@ -0,0 +1,164 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.configuration;
+
+import org.xml.sax.*;
+import org.xml.sax.helpers.*;
+
+import java.io.*;
+
+import java.util.*;
+
+import javax.xml.parsers.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class Authorization extends DefaultHandler {
+    private static final String AUTHORIZEDKEYS_ELEMENT = "AuthorizedKeys";
+    private static final String KEY_ELEMENT = "Key";
+    private ArrayList authorizedKeys = new ArrayList();
+
+    /**
+* Creates a new Authorization object.
+*
+* @param in
+*
+* @throws SAXException
+* @throws ParserConfigurationException
+* @throws IOException
+*/
+    public Authorization(InputStream in)
+        throws SAXException, ParserConfigurationException, IOException {
+        SAXParserFactory saxFactory = SAXParserFactory.newInstance();
+        SAXParser saxParser = saxFactory.newSAXParser();
+        authorizedKeys.clear();
+        saxParser.parse(in, new AuthorizedKeysSAXHandler());
+    }
+
+    /**
+* Creates a new Authorization object.
+*/
+    public Authorization() {
+        // Creates an empty authorization file
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public List getAuthorizedKeys() {
+        return (List) authorizedKeys.clone();
+    }
+
+    /**
+*
+*
+* @param keyfile
+*/
+    public void addKey(String keyfile) {
+        authorizedKeys.add(keyfile);
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String toString() {
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+        xml += "<!-- SSHTools Public Key Authorization File -->\n";
+        xml += ("<" + AUTHORIZEDKEYS_ELEMENT + ">\n");
+        xml += "<!-- Enter authorized public key elements here -->\n";
+
+        Iterator it = authorizedKeys.iterator();
+
+        while (it.hasNext()) {
+            xml += ("   <" + KEY_ELEMENT + ">" + it.next().toString() + "</" +
+            KEY_ELEMENT + ">\n");
+        }
+
+        xml += ("</" + AUTHORIZEDKEYS_ELEMENT + ">");
+
+        return xml;
+    }
+
+    class AuthorizedKeysSAXHandler extends DefaultHandler {
+        private String currentElement = null;
+
+        public void startElement(String uri, String localName, String qname,
+            Attributes attrs) throws SAXException {
+            if (currentElement == null) {
+                if (!qname.equals(AUTHORIZEDKEYS_ELEMENT)) {
+                    throw new SAXException("Unexpected root element " + qname);
+                }
+            } else {
+                if (currentElement.equals(AUTHORIZEDKEYS_ELEMENT)) {
+                    if (!qname.equals(KEY_ELEMENT)) {
+                        throw new SAXException("Unexpected element " + qname);
+                    }
+                } else {
+                    throw new SAXException("Unexpected element " + qname);
+                }
+            }
+
+            currentElement = qname;
+        }
+
+        public void characters(char[] ch, int start, int length)
+            throws SAXException {
+            if (currentElement != null) {
+                if (currentElement.equals(KEY_ELEMENT)) {
+                    String key = new String(ch, start, length);
+                    authorizedKeys.add(key);
+                }
+            }
+        }
+
+        public void endElement(String uri, String localName, String qname)
+            throws SAXException {
+            if (currentElement != null) {
+                if (!currentElement.equals(qname)) {
+                    throw new SAXException("Unexpected end element found " +
+                        qname);
+                }
+
+                if (currentElement.equals(KEY_ELEMENT)) {
+                    currentElement = AUTHORIZEDKEYS_ELEMENT;
+                } else if (currentElement.equals(AUTHORIZEDKEYS_ELEMENT)) {
+                    currentElement = null;
+                } else {
+                    throw new SAXException("Unexpected end element " + qname);
+                }
+            }
+        }
+    }
+}
diff --git a/src/com/sshtools/common/configuration/InvalidProfileFileException.java b/src/com/sshtools/common/configuration/InvalidProfileFileException.java
new file mode 100644
index 0000000000000000000000000000000000000000..7742ff406c092c12a6b4c15b5d2a475f4f66b113
--- /dev/null
+++ b/src/com/sshtools/common/configuration/InvalidProfileFileException.java
@@ -0,0 +1,46 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.configuration;
+
+import com.sshtools.j2ssh.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class InvalidProfileFileException extends SshException {
+    /**
+* Creates a new InvalidProfileFileException object.
+*
+* @param msg
+*/
+    public InvalidProfileFileException(String msg) {
+        super(msg);
+    }
+}
diff --git a/src/com/sshtools/common/configuration/SshAPIConfiguration.java b/src/com/sshtools/common/configuration/SshAPIConfiguration.java
new file mode 100644
index 0000000000000000000000000000000000000000..3f1c2819a57526014ab5d5d6ecf550af792bd9ac
--- /dev/null
+++ b/src/com/sshtools/common/configuration/SshAPIConfiguration.java
@@ -0,0 +1,628 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.configuration;
+
+import com.sshtools.j2ssh.configuration.*;
+
+import org.xml.sax.*;
+import org.xml.sax.helpers.*;
+
+import java.io.*;
+
+import java.util.*;
+
+import javax.xml.parsers.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class SshAPIConfiguration extends DefaultHandler
+    implements com.sshtools.j2ssh.configuration.SshAPIConfiguration {
+    private String defaultCipher = null;
+    private String defaultMac = null;
+    private String defaultCompression = null;
+    private String defaultPublicKey = null;
+    private String defaultKeyExchange = null;
+    private List cipherExtensions = new ArrayList();
+    private List macExtensions = new ArrayList();
+    private List compressionExtensions = new ArrayList();
+    private List pkExtensions = new ArrayList();
+    private List kexExtensions = new ArrayList();
+    private List authExtensions = new ArrayList();
+    private List pkFormats = new ArrayList();
+    private List prvFormats = new ArrayList();
+    private String defaultPublicFormat = null;
+    private String defaultPrivateFormat = null;
+    private String currentElement = null;
+    private String parentElement = null;
+    private List currentList = null;
+    private ExtensionAlgorithm currentExt = null;
+
+    /**
+* Creates a new SshAPIConfiguration object.
+*
+* @param in
+*
+* @throws SAXException
+* @throws ParserConfigurationException
+* @throws IOException
+*/
+    public SshAPIConfiguration(InputStream in)
+        throws SAXException, ParserConfigurationException, IOException {
+        reload(in);
+    }
+
+    /**
+*
+*
+* @param in
+*
+* @throws SAXException
+* @throws ParserConfigurationException
+* @throws IOException
+*/
+    public void reload(InputStream in)
+        throws SAXException, ParserConfigurationException, IOException {
+        defaultCipher = null;
+        defaultMac = null;
+        defaultCompression = null;
+        defaultKeyExchange = null;
+        defaultPublicKey = null;
+        defaultPublicFormat = null;
+        defaultPrivateFormat = null;
+        cipherExtensions.clear();
+        macExtensions.clear();
+        compressionExtensions.clear();
+        pkExtensions.clear();
+        kexExtensions.clear();
+        authExtensions.clear();
+        pkFormats.clear();
+        prvFormats.clear();
+
+        SAXParserFactory saxFactory = SAXParserFactory.newInstance();
+        SAXParser saxParser = saxFactory.newSAXParser();
+        saxParser.parse(in, this);
+    }
+
+    /**
+*
+*
+* @param ch
+* @param start
+* @param length
+*
+* @throws SAXException
+*/
+    public void characters(char[] ch, int start, int length)
+        throws SAXException {
+        String value = new String(ch, start, length);
+
+        if (currentElement != null) {
+            if (currentElement.equals("AlgorithmName")) {
+                if (currentExt != null) {
+                    currentExt.setAlgorithmName(value);
+                } else {
+                    throw new SAXException("Unexpected AlgorithmName element!");
+                }
+
+                return;
+            }
+
+            if (currentElement.equals("ImplementationClass")) {
+                if (currentExt != null) {
+                    currentExt.setImplementationClass(value);
+                } else {
+                    throw new SAXException(
+                        "Unexpected ImplementationClass element!");
+                }
+
+                return;
+            }
+
+            if (currentElement.equals("DefaultAlgorithm")) {
+                if (parentElement.equals("CipherConfiguration")) {
+                    defaultCipher = value;
+                } else if (parentElement.equals("MacConfiguration")) {
+                    defaultMac = value;
+                } else if (parentElement.equals("CompressionConfiguration")) {
+                    defaultCompression = value;
+                } else if (parentElement.equals("PublicKeyConfiguration")) {
+                    defaultPublicKey = value;
+                } else if (parentElement.equals("KeyExchangeConfiguration")) {
+                    defaultKeyExchange = value;
+                } else {
+                    throw new SAXException(
+                        "Unexpected parent elemenet for DefaultAlgorithm element");
+                }
+            }
+
+            if (currentElement.equals("DefaultPublicFormat")) {
+                defaultPublicFormat = value;
+            }
+
+            if (currentElement.equals("DefaultPrivateFormat")) {
+                defaultPrivateFormat = value;
+            }
+        }
+    }
+
+    /**
+*
+*
+* @param uri
+* @param localName
+* @param qname
+*
+* @throws SAXException
+*/
+    public void endElement(String uri, String localName, String qname)
+        throws SAXException {
+        if (currentElement != null) {
+            if (!currentElement.equals(qname)) {
+                throw new SAXException("Unexpected end element found " + qname);
+            } else if (currentElement.equals("SshAPIConfiguration")) {
+                currentElement = null;
+            } else if (currentElement.equals("CipherConfiguration") ||
+                    currentElement.equals("MacConfiguration") ||
+                    currentElement.equals("PublicKeyConfiguration") ||
+                    currentElement.equals("CompressionConfiguration") ||
+                    currentElement.equals("KeyExchangeConfiguration") ||
+                    currentElement.equals("AuthenticationConfiguration")) {
+                currentList = null;
+                currentElement = "SshAPIConfiguration";
+            } else if (currentElement.equals("ExtensionAlgorithm")) {
+                if (currentExt == null) {
+                    throw new SAXException(
+                        "Critical error, null extension algortihm");
+                }
+
+                if ((currentExt.getAlgorithmName() == null) ||
+                        (currentExt.getImplementationClass() == null)) {
+                    throw new SAXException(
+                        "Unexpected end of ExtensionAlgorithm Element");
+                }
+
+                currentList.add(currentExt);
+                currentExt = null;
+                currentElement = parentElement;
+            } else if (currentElement.equals("DefaultAlgorithm") ||
+                    currentElement.equals("DefaultPublicFormat") ||
+                    currentElement.equals("DefaultPrivateFormat") ||
+                    currentElement.equals("PublicKeyFormat") ||
+                    currentElement.equals("PrivateKeyFormat")) {
+                currentElement = parentElement;
+            } else if (currentElement.equals("AlgorithmName")) {
+                currentElement = "ExtensionAlgorithm";
+            } else if (currentElement.equals("ImplementationClass")) {
+                currentElement = "ExtensionAlgorithm";
+            } else {
+                throw new SAXException("Unexpected end element " + qname);
+            }
+        }
+    }
+
+    /**
+*
+*
+* @param uri
+* @param localName
+* @param qname
+* @param attrs
+*
+* @throws SAXException
+*/
+    public void startElement(String uri, String localName, String qname,
+        Attributes attrs) throws SAXException {
+        if (currentElement == null) {
+            if (!qname.equals("SshAPIConfiguration")) {
+                throw new SAXException("Unexpected root element " + qname);
+            }
+        } else {
+            if (currentElement.equals("SshAPIConfiguration")) {
+                if (!qname.equals("CipherConfiguration") &&
+                        !qname.equals("MacConfiguration") &&
+                        !qname.equals("CompressionConfiguration") &&
+                        !qname.equals("PublicKeyConfiguration") &&
+                        !qname.equals("AuthenticationConfiguration") &&
+                        !qname.equals("KeyExchangeConfiguration")) {
+                    throw new SAXException("Unexpected <" + qname +
+                        "> element after SshAPIConfiguration");
+                }
+            } else if (currentElement.equals("CipherConfiguration")) {
+                if (qname.equals("ExtensionAlgorithm")) {
+                    currentList = cipherExtensions;
+                    parentElement = currentElement;
+                    currentExt = new ExtensionAlgorithm();
+                } else if (qname.equals("DefaultAlgorithm")) {
+                    parentElement = currentElement;
+                } else {
+                    throw new SAXException("Unexpected element <" + qname +
+                        "> found after CipherConfiguration");
+                }
+            } else if (currentElement.equals("MacConfiguration")) {
+                if (qname.equals("ExtensionAlgorithm")) {
+                    currentList = macExtensions;
+                    parentElement = currentElement;
+                    currentExt = new ExtensionAlgorithm();
+                } else if (qname.equals("DefaultAlgorithm")) {
+                    parentElement = currentElement;
+                } else {
+                    throw new SAXException("Unexpected element <" + qname +
+                        "> found after CipherConfiguration");
+                }
+            } else if (currentElement.equals("CompressionConfiguration")) {
+                if (qname.equals("ExtensionAlgorithm")) {
+                    currentList = compressionExtensions;
+                    parentElement = currentElement;
+                    currentExt = new ExtensionAlgorithm();
+                } else if (qname.equals("DefaultAlgorithm")) {
+                    parentElement = currentElement;
+                } else {
+                    throw new SAXException("Unexpected element <" + qname +
+                        "> found after CompressionConfiguration");
+                }
+            } else if (currentElement.equals("PublicKeyConfiguration")) {
+                if (qname.equals("ExtensionAlgorithm")) {
+                    currentList = pkExtensions;
+                    parentElement = currentElement;
+                    currentExt = new ExtensionAlgorithm();
+                } else if (qname.equals("DefaultAlgorithm")) {
+                    parentElement = currentElement;
+                } else if (qname.equals("PublicKeyFormat")) {
+                    String cls = attrs.getValue("ImplementationClass");
+
+                    if (cls == null) {
+                        throw new SAXException(
+                            "<PublicKeyFormat> element requries the ImplementationClass attribute");
+                    }
+
+                    pkFormats.add(cls);
+                } else if (qname.equals("PrivateKeyFormat")) {
+                    String cls = attrs.getValue("ImplementationClass");
+
+                    if (cls == null) {
+                        throw new SAXException(
+                            "<PrivateKeyFormat> element requries the ImplementationClass attribute");
+                    }
+
+                    prvFormats.add(cls);
+                } else if (qname.equals("DefaultPublicFormat")) {
+                    parentElement = currentElement;
+                } else if (qname.equals("DefaultPrivateFormat")) {
+                    parentElement = currentElement;
+                } else {
+                    throw new SAXException("Unexpected element <" + qname +
+                        "> found after PublicKeyConfiguration");
+                }
+            } else if (currentElement.equals("AuthenticationConfiguration")) {
+                if (qname.equals("ExtensionAlgorithm")) {
+                    currentList = authExtensions;
+                    parentElement = currentElement;
+                    currentExt = new ExtensionAlgorithm();
+                } else {
+                    throw new SAXException("Unexpected element <" + qname +
+                        "> found after AuthenticationConfiguration");
+                }
+            } else if (currentElement.equals("KeyExchangeConfiguration")) {
+                if (qname.equals("ExtensionAlgorithm")) {
+                    currentList = kexExtensions;
+                    parentElement = currentElement;
+                    currentExt = new ExtensionAlgorithm();
+                } else if (qname.equals("DefaultAlgorithm")) {
+                    parentElement = currentElement;
+                } else {
+                    throw new SAXException("Unexpected element <" + qname +
+                        "> found after KeyExchangeConfiguration");
+                }
+            } else if ((currentElement.equals("ExtensionAlgorithm") &&
+                    qname.equals("AlgorithmName")) ||
+                    (currentElement.equals("ExtensionAlgorithm") &&
+                    qname.equals("ImplementationClass"))) {
+            } else {
+                throw new SAXException("Unexpected element " + qname);
+            }
+        }
+
+        currentElement = qname;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public List getCompressionExtensions() {
+        return compressionExtensions;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public List getCipherExtensions() {
+        return cipherExtensions;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public List getMacExtensions() {
+        return macExtensions;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public List getAuthenticationExtensions() {
+        return authExtensions;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public List getPublicKeyExtensions() {
+        return pkExtensions;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public List getKeyExchangeExtensions() {
+        return kexExtensions;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getDefaultCipher() {
+        return defaultCipher;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getDefaultMac() {
+        return defaultMac;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getDefaultCompression() {
+        return defaultCompression;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getDefaultPublicKey() {
+        return defaultPublicKey;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getDefaultKeyExchange() {
+        return defaultKeyExchange;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getDefaultPublicKeyFormat() {
+        return defaultPublicFormat;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getDefaultPrivateKeyFormat() {
+        return defaultPrivateFormat;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public List getPublicKeyFormats() {
+        return pkFormats;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public List getPrivateKeyFormats() {
+        return prvFormats;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String toString() {
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+        xml += "<!-- Sshtools J2SSH Configuration file -->\n";
+        xml += "<SshAPIConfiguration>\n";
+        xml += "   <!-- The Cipher configuration, add or overide default cipher implementations -->\n";
+        xml += "   <CipherConfiguration>\n";
+
+        Iterator it = cipherExtensions.iterator();
+        ExtensionAlgorithm ext;
+
+        while (it.hasNext()) {
+            ext = (ExtensionAlgorithm) it.next();
+            xml += "      <ExtensionAlgorithm>\n";
+            xml += ("         <AlgorithmName>" + ext.getAlgorithmName() +
+            "</AlgorithmName>\n");
+            xml += ("         <ImplementationClass>" +
+            ext.getImplementationClass() + "</ImplementationClass>\n");
+            xml += "      </ExtensionAlgorithm>\n";
+        }
+
+        xml += ("      <DefaultAlgorithm>" + defaultCipher +
+        "</DefaultAlgorithm>\n");
+        xml += "   </CipherConfiguration>\n";
+        xml += "   <!-- The Mac configuration, add or overide default mac implementations -->\n";
+        xml += "   <MacConfiguration>\n";
+        it = macExtensions.iterator();
+
+        while (it.hasNext()) {
+            ext = (ExtensionAlgorithm) it.next();
+            xml += "      <ExtensionAlgorithm>\n";
+            xml += ("         <AlgorithmName>" + ext.getAlgorithmName() +
+            "</AlgorithmName>\n");
+            xml += ("         <ImplementationClass>" +
+            ext.getImplementationClass() + "</ImplementationClass>\n");
+            xml += "      </ExtensionAlgorithm>\n";
+        }
+
+        xml += ("      <DefaultAlgorithm>" + defaultMac +
+        "</DefaultAlgorithm>\n");
+        xml += "   </MacConfiguration>\n";
+        xml += "   <!-- The Compression configuration, add or overide default compression implementations -->\n";
+        xml += "   <CompressionConfiguration>\n";
+        it = compressionExtensions.iterator();
+
+        while (it.hasNext()) {
+            ext = (ExtensionAlgorithm) it.next();
+            xml += "      <ExtensionAlgorithm>\n";
+            xml += ("         <AlgorithmName>" + ext.getAlgorithmName() +
+            "</AlgorithmName>\n");
+            xml += ("         <ImplementationClass>" +
+            ext.getImplementationClass() + "</ImplementationClass>\n");
+            xml += "      </ExtensionAlgorithm>\n";
+        }
+
+        xml += ("      <DefaultAlgorithm>" + defaultCompression +
+        "</DefaultAlgorithm>\n");
+        xml += "   </CompressionConfiguration>\n";
+        xml += "   <!-- The Public Key configuration, add or overide default public key implementations -->\n";
+        xml += "   <PublicKeyConfiguration>\n";
+        it = pkExtensions.iterator();
+
+        while (it.hasNext()) {
+            ext = (ExtensionAlgorithm) it.next();
+            xml += "      <ExtensionAlgorithm>\n";
+            xml += ("         <AlgorithmName>" + ext.getAlgorithmName() +
+            "</AlgorithmName>\n");
+            xml += ("         <ImplementationClass>" +
+            ext.getImplementationClass() + "</ImplementationClass>\n");
+            xml += "      </ExtensionAlgorithm>\n";
+        }
+
+        xml += ("      <DefaultAlgorithm>" + defaultPublicKey +
+        "</DefaultAlgorithm>\n");
+        it = pkFormats.iterator();
+
+        String cls;
+
+        while (it.hasNext()) {
+            cls = (String) it.next();
+            xml += ("      <PublicKeyFormat ImplementationClass=\"" + cls +
+            "\"/>\n");
+        }
+
+        it = prvFormats.iterator();
+
+        while (it.hasNext()) {
+            cls = (String) it.next();
+            xml += ("      <PrivateKeyFormat ImplementationClass=\"" + cls +
+            "\"/>\n");
+        }
+
+        xml += ("      <DefaultPublicFormat>" + defaultPublicFormat +
+        "</DefaultPublicFormat>\n");
+        xml += ("      <DefaultPrivateFormat>" + defaultPrivateFormat +
+        "</DefaultPrivateFormat>\n");
+        xml += "   </PublicKeyConfiguration>\n";
+        xml += "   <!-- The Key Exchange configuration, add or overide default key exchange implementations -->\n";
+        xml += "   <KeyExchangeConfiguration>\n";
+        it = kexExtensions.iterator();
+
+        while (it.hasNext()) {
+            ext = (ExtensionAlgorithm) it.next();
+            xml += "      <ExtensionAlgorithm>\n";
+            xml += ("         <AlgorithmName>" + ext.getAlgorithmName() +
+            "</AlgorithmName>\n");
+            xml += ("         <ImplementationClass>" +
+            ext.getImplementationClass() + "</ImplementationClass>\n");
+            xml += "      </ExtensionAlgorithm>\n";
+        }
+
+        xml += ("      <DefaultAlgorithm>" + defaultKeyExchange +
+        "</DefaultAlgorithm>\n");
+        xml += "   </KeyExchangeConfiguration>\n";
+        xml += "   <!-- The Authentication configuration, add or overide default authentication implementations -->\n";
+        xml += "   <AuthenticationConfiguration>\n";
+        it = authExtensions.iterator();
+
+        while (it.hasNext()) {
+            ext = (ExtensionAlgorithm) it.next();
+            xml += "      <ExtensionAlgorithm>\n";
+            xml += ("         <AlgorithmName>" + ext.getAlgorithmName() +
+            "</AlgorithmName>\n");
+            xml += ("         <ImplementationClass>" +
+            ext.getImplementationClass() + "</ImplementationClass>\n");
+            xml += "      </ExtensionAlgorithm>\n";
+        }
+
+        xml += "   </AuthenticationConfiguration>\n";
+        xml += "</SshAPIConfiguration>";
+
+        return xml;
+    }
+}
diff --git a/src/com/sshtools/common/configuration/SshToolsConnectionProfile.java b/src/com/sshtools/common/configuration/SshToolsConnectionProfile.java
new file mode 100644
index 0000000000000000000000000000000000000000..ad34f678767fe6e77fc9220a6409ebf8a26234c4
--- /dev/null
+++ b/src/com/sshtools/common/configuration/SshToolsConnectionProfile.java
@@ -0,0 +1,826 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.configuration;
+
+import com.sshtools.common.util.PropertyUtil;
+
+import com.sshtools.j2ssh.authentication.SshAuthenticationClient;
+import com.sshtools.j2ssh.authentication.SshAuthenticationClientFactory;
+import com.sshtools.j2ssh.configuration.SshConnectionProperties;
+import com.sshtools.j2ssh.forwarding.ForwardingConfiguration;
+import com.sshtools.j2ssh.io.IOUtil;
+import com.sshtools.j2ssh.transport.AlgorithmNotSupportedException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.awt.Color;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.22 $
+ */
+public class SshToolsConnectionProfile extends SshConnectionProperties {
+    private static Log log = LogFactory.getLog(SshToolsConnectionProfile.class);
+
+    /**  */
+    public static final int DO_NOTHING = 1;
+
+    /**  */
+    public static final int START_SHELL = 2;
+
+    /**  */
+    public static final int EXECUTE_COMMANDS = 3;
+    private Map applicationProperties = new HashMap();
+    private Map sftpFavorites = new HashMap();
+    private Map authMethods = new HashMap();
+    private boolean requestPseudoTerminal = true;
+    private boolean disconnectOnSessionClose = true;
+    private int onceAuthenticated = START_SHELL;
+    private String executeCommands = "";
+    private boolean allowAgentForwarding = false;
+
+    // SAX Processing variables
+    private String currentElement = null;
+    private String currentAuthentication = null;
+    private Properties currentProperties = null;
+    private String connectionFile;
+
+    /**
+* Creates a new SshToolsConnectionProfile object.
+*/
+    public SshToolsConnectionProfile() {
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public Map getAuthenticationMethods() {
+        return authMethods;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public boolean requiresPseudoTerminal() {
+        return requestPseudoTerminal;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public boolean disconnectOnSessionClose() {
+        return disconnectOnSessionClose;
+    }
+
+    /**
+*
+*
+* @param requiresPseudoTerminal
+*/
+    public void setRequiresPseudoTerminal(boolean requiresPseudoTerminal) {
+        this.requestPseudoTerminal = requiresPseudoTerminal;
+    }
+
+    /**
+*
+*
+* @param disconnectOnSessionClose
+*/
+    public void setDisconnectOnSessionClose(boolean disconnectOnSessionClose) {
+        this.disconnectOnSessionClose = disconnectOnSessionClose;
+    }
+
+    /**
+*
+*/
+    public void clearAuthenticationCache() {
+        SshAuthenticationClient auth;
+        Properties properties;
+
+        for (Iterator it = authMethods.values().iterator(); it.hasNext();) {
+            auth = (SshAuthenticationClient) it.next();
+            properties = auth.getPersistableProperties();
+            auth.reset();
+            auth.setPersistableProperties(properties);
+        }
+    }
+
+    /**
+*
+*
+* @param onceAuthenticated
+*/
+    public void setOnceAuthenticatedCommand(int onceAuthenticated) {
+        this.onceAuthenticated = onceAuthenticated;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public int getOnceAuthenticatedCommand() {
+        return onceAuthenticated;
+    }
+
+    /**
+*
+*
+* @param executeCommands
+*/
+    public void setCommandsToExecute(String executeCommands) {
+        this.executeCommands = executeCommands;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getCommandsToExecute() {
+        return executeCommands;
+    }
+
+    /**
+*
+*
+* @param name
+* @param defaultValue
+*
+* @return
+*/
+    public String getApplicationProperty(String name, String defaultValue) {
+        String value = (String) applicationProperties.get(name);
+
+        if (value == null) {
+            return defaultValue;
+        } else {
+            return value;
+        }
+    }
+
+    /**
+*
+*
+* @param name
+* @param defaultValue
+*
+* @return
+*/
+    public Map getSftpFavorites() {
+        return sftpFavorites;
+    }
+
+    /**
+*
+*
+* @param name
+* @param defaultValue
+*
+* @return
+*/
+    public void setSftpFavorite(String name, String value) {
+        sftpFavorites.put(name, value);
+    }
+
+    /**
+*
+*
+* @param name
+* @param defaultValue
+*
+* @return
+*/
+    public int getApplicationPropertyInt(String name, int defaultValue) {
+        try {
+            return Integer.parseInt(getApplicationProperty(name,
+                    String.valueOf(defaultValue)));
+        } catch (NumberFormatException nfe) {
+            return defaultValue;
+        }
+    }
+
+    /**
+*
+*
+* @param name
+* @param defaultValue
+*
+* @return
+*/
+    public boolean getApplicationPropertyBoolean(String name,
+        boolean defaultValue) {
+        try {
+            return new Boolean(getApplicationProperty(name,
+                    String.valueOf(defaultValue))).booleanValue();
+        } catch (NumberFormatException nfe) {
+            return defaultValue;
+        }
+    }
+
+    /**
+*
+*
+* @param name
+* @param defaultColor
+*
+* @return
+*/
+    public Color getApplicationPropertyColor(String name, Color defaultColor) {
+        return PropertyUtil.stringToColor(getApplicationProperty(name,
+                PropertyUtil.colorToString(defaultColor)));
+    }
+
+    /**
+*
+*
+* @param name
+* @param value
+*/
+    public void setApplicationProperty(String name, String value) {
+        applicationProperties.put(name, value);
+    }
+
+    /**
+*
+*
+* @param name
+* @param value
+*/
+    public void setApplicationProperty(String name, int value) {
+        applicationProperties.put(name, String.valueOf(value));
+    }
+
+    /**
+*
+*
+* @param name
+* @param value
+*/
+    public void setApplicationProperty(String name, boolean value) {
+        applicationProperties.put(name, String.valueOf(value));
+    }
+
+    /**
+*
+*
+* @param name
+* @param value
+*/
+    public void setApplicationProperty(String name, Color value) {
+        applicationProperties.put(name, PropertyUtil.colorToString(value));
+    }
+
+    /**
+*
+*
+* @param method
+*/
+    public void addAuthenticationMethod(SshAuthenticationClient method) {
+        if (method != null) {
+            if (!authMethods.containsKey(method.getMethodName())) {
+                authMethods.put(method.getMethodName(), method);
+            }
+        }
+    }
+
+    /**
+*
+*
+* @param config
+*/
+    public void addLocalForwarding(ForwardingConfiguration config) {
+        if (config != null) {
+            localForwardings.put(config.getName(), config);
+        }
+    }
+
+    /**
+*
+*
+* @param config
+*/
+    public void addRemoteForwarding(ForwardingConfiguration config) {
+        if (config != null) {
+            remoteForwardings.put(config.getName(), config);
+        }
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public boolean getAllowAgentForwarding() {
+        return allowAgentForwarding;
+    }
+
+    /**
+*
+*
+* @param allowAgentForwarding
+*/
+    public void setAllowAgentForwarding(boolean allowAgentForwarding) {
+        this.allowAgentForwarding = allowAgentForwarding;
+    }
+
+    /**
+*
+*
+* @param name
+*/
+    public void removeLocalForwarding(String name) {
+        localForwardings.remove(name);
+    }
+
+    /**
+*
+*
+* @param name
+*/
+    public void removeRemoteForwarding(String name) {
+        remoteForwardings.remove(name);
+    }
+
+    /**
+*
+*
+* @param file
+*
+* @throws InvalidProfileFileException
+*/
+    public void open(String file) throws InvalidProfileFileException {
+        connectionFile = file;
+        open(new File(file));
+    }
+
+    /**
+*
+*
+* @param file
+*
+* @throws InvalidProfileFileException
+*/
+    public void open(File file) throws InvalidProfileFileException {
+        InputStream in = null;
+
+        try {
+            in = new FileInputStream(file);
+            open(in);
+        } catch (FileNotFoundException fnfe) {
+            throw new InvalidProfileFileException(file + " was not found!");
+        } finally {
+            IOUtil.closeStream(in);
+        }
+    }
+
+    /**
+*
+*
+* @param in
+*
+* @throws InvalidProfileFileException
+*/
+    public void open(InputStream in) throws InvalidProfileFileException {
+        try {
+            SAXParserFactory saxFactory = SAXParserFactory.newInstance();
+            SAXParser saxParser = saxFactory.newSAXParser();
+            XMLHandler handler = new XMLHandler();
+            saxParser.parse(in, handler);
+            handler = null;
+
+            //            in.close();
+        } catch (IOException ioe) {
+            throw new InvalidProfileFileException("IO error. " +
+                ioe.getMessage());
+        } catch (SAXException sax) {
+            throw new InvalidProfileFileException("SAX Error: " +
+                sax.getMessage());
+        } catch (ParserConfigurationException pce) {
+            throw new InvalidProfileFileException("SAX Parser Error: " +
+                pce.getMessage());
+        } finally {
+        }
+    }
+
+    /**
+*
+*
+* @param method
+*/
+    public void removeAuthenticaitonMethod(String method) {
+        authMethods.remove(method);
+    }
+
+    public void removeAuthenticationMethods() {
+        authMethods.clear();
+    }
+
+    /**
+*
+*
+* @param file
+*
+* @throws InvalidProfileFileException
+*/
+    public void save(String file) throws InvalidProfileFileException {
+        try {
+            File f = new File(file);
+            FileOutputStream out = new FileOutputStream(f);
+            out.write(toString().getBytes());
+            out.close();
+        } catch (FileNotFoundException fnfe) {
+            throw new InvalidProfileFileException(file + " was not found!");
+        } catch (IOException ioe) {
+            throw new InvalidProfileFileException("io error on " + file);
+        } finally {
+        }
+    }
+
+    public void save() throws InvalidProfileFileException {
+        save(connectionFile);
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String toString() {
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+        xml += ("<SshToolsConnectionProfile Hostname=\"" + host + "\" Port=\"" +
+        String.valueOf(port) + "\" Username=\"" + username + "\"" +
+        " Provider=\"" + getTransportProviderString() + "\">");
+        xml += ("   <PreferedCipher Client2Server=\"" + prefEncryption +
+        "\" Server2Client=\"" + prefDecryption + "\"/>\n");
+        xml += ("   <PreferedMac Client2Server=\"" + prefRecvMac +
+        "\" Server2Client=\"" + prefSendMac + "\"/>\n");
+        xml += ("   <PreferedCompression Client2Server=\"" + prefRecvComp +
+        "\" Server2Client=\"" + prefSendComp + "\"/>\n");
+        xml += ("   <PreferedPublicKey Name=\"" + prefPK + "\"/>\n");
+        xml += ("   <PreferedKeyExchange Name=\"" + prefKex + "\"/>\n");
+        xml += ("   <OnceAuthenticated value=\"" +
+        String.valueOf(onceAuthenticated) + "\"/>\n");
+
+        if (onceAuthenticated == EXECUTE_COMMANDS) {
+            xml += ("    <ExecuteCommands>" + executeCommands +
+            "</ExecuteCommands>\n");
+        }
+
+        Iterator it = authMethods.entrySet().iterator();
+        Map.Entry entry;
+        Properties properties;
+
+        while (it.hasNext()) {
+            entry = (Map.Entry) it.next();
+            xml += ("   <AuthenticationMethod Name=\"" + entry.getKey() +
+            "\">\n");
+
+            SshAuthenticationClient auth = (SshAuthenticationClient) entry.getValue();
+            properties = auth.getPersistableProperties();
+
+            Iterator it2 = properties.entrySet().iterator();
+
+            while (it2.hasNext()) {
+                entry = (Map.Entry) it2.next();
+                xml += ("      <AuthenticationProperty Name=\"" +
+                entry.getKey() + "\" Value=\"" + entry.getValue() + "\"/>\n");
+            }
+
+            xml += "   </AuthenticationMethod>\n";
+        }
+
+        it = applicationProperties.entrySet().iterator();
+
+        while (it.hasNext()) {
+            entry = (Map.Entry) it.next();
+            xml += ("   <ApplicationProperty Name=\"" + entry.getKey() +
+            "\" Value=\"" + entry.getValue() + "\"/>\n");
+        }
+
+        // Write the SFTP Favorite entries to file
+        it = sftpFavorites.entrySet().iterator();
+
+        while (it.hasNext()) {
+            entry = (Map.Entry) it.next();
+            xml += ("   <SftpFavorite Name=\"" + entry.getKey() +
+            "\" Value=\"" + entry.getValue() + "\"/>\n");
+        }
+
+        it = localForwardings.values().iterator();
+        xml += ("   <ForwardingAutoStart value=\"" +
+        String.valueOf(forwardingAutoStart) + "\"/>\n");
+
+        while (it.hasNext()) {
+            ForwardingConfiguration config = (ForwardingConfiguration) it.next();
+            xml += ("   <LocalPortForwarding Name=\"" + config.getName() +
+            "\" AddressToBind=\"" + config.getAddressToBind() +
+            "\" PortToBind=\"" + String.valueOf(config.getPortToBind()) +
+            "\" AddressToConnect=\"" + config.getHostToConnect() +
+            "\" PortToConnect=\"" + String.valueOf(config.getPortToConnect()) +
+            "\"/>\n");
+        }
+
+        it = remoteForwardings.values().iterator();
+
+        while (it.hasNext()) {
+            ForwardingConfiguration config = (ForwardingConfiguration) it.next();
+            xml += ("   <RemotePortForwarding Name=\"" + config.getName() +
+            "\" AddressToBind=\"" + config.getAddressToBind() +
+            "\" PortToBind=\"" + String.valueOf(config.getPortToBind()) +
+            "\" AddressToConnect=\"" + config.getHostToConnect() +
+            "\" PortToConnect=\"" + String.valueOf(config.getPortToConnect()) +
+            "\"/>\n");
+        }
+
+        xml += "</SshToolsConnectionProfile>";
+
+        return xml;
+    }
+
+    private class XMLHandler extends DefaultHandler {
+        boolean commandsToExecute = false;
+
+        public void startElement(String uri, String localName, String qname,
+            Attributes attrs) throws SAXException {
+            if (currentElement == null) {
+                if (!qname.equals("SshToolsConnectionProfile")) {
+                    throw new SAXException("Unexpected root element " + qname);
+                }
+
+                host = attrs.getValue("Hostname");
+                username = attrs.getValue("Username");
+
+                String p = attrs.getValue("Port");
+
+                if (p == null) {
+                    port = 22;
+                } else {
+                    port = Integer.parseInt(p);
+                }
+
+                setTransportProviderString(attrs.getValue("Provider"));
+
+                if ((host == null) || (username == null)) {
+                    throw new SAXException(
+                        "Required attribute for element <SshToolsConnectionProfile> missing");
+                }
+            } else {
+                String c2s;
+                String s2c;
+
+                if (currentElement.equals("SshToolsConnectionProfile")) {
+                    if (qname.equals("PreferedCipher")) {
+                        c2s = attrs.getValue("Client2Server");
+                        s2c = attrs.getValue("Server2Client");
+
+                        if ((c2s == null) || (s2c == null)) {
+                            throw new SAXException(
+                                "Required attribute missing for <PreferedCipher> element");
+                        }
+
+                        prefEncryption = c2s;
+                        prefDecryption = s2c;
+                    } else if (qname.equals("OnceAuthenticated")) {
+                        if (attrs.getValue("value") != null) {
+                            try {
+                                onceAuthenticated = Integer.parseInt(attrs.getValue(
+                                            "value"));
+                            } catch (NumberFormatException ex) {
+                                onceAuthenticated = START_SHELL;
+                            }
+                        }
+                    } else if (qname.equals("ExecuteCommands")) {
+                        commandsToExecute = true;
+                        executeCommands = "";
+                    } else if (qname.equals("PreferedCompression")) {
+                        c2s = attrs.getValue("Client2Server");
+                        s2c = attrs.getValue("Server2Client");
+
+                        if ((c2s == null) || (s2c == null)) {
+                            throw new SAXException(
+                                "Required attribute missing for <PreferedCompression> element");
+                        }
+
+                        prefRecvComp = c2s;
+                        prefSendComp = s2c;
+                    } else if (qname.equals("PreferedMac")) {
+                        c2s = attrs.getValue("Client2Server");
+                        s2c = attrs.getValue("Server2Client");
+
+                        if ((c2s == null) || (s2c == null)) {
+                            throw new SAXException(
+                                "Required attribute missing for <PreferedMac> element");
+                        }
+
+                        prefRecvMac = c2s;
+                        prefSendMac = s2c;
+                    } else if (qname.equals("PreferedPublicKey")) {
+                        String name = attrs.getValue("Name");
+
+                        if (name == null) {
+                            throw new SAXException(
+                                "Required attribute missing for <PreferedPublickey> element");
+                        }
+
+                        prefPK = name;
+                    } else if (qname.equals("PreferedKeyExchange")) {
+                        String name = attrs.getValue("Name");
+
+                        if (name == null) {
+                            throw new SAXException(
+                                "Required attribute missing for <PreferedKeyExchange> element");
+                        }
+
+                        prefPK = name;
+                    } else if (qname.equals("ApplicationProperty")) {
+                        String name = attrs.getValue("Name");
+                        String value = attrs.getValue("Value");
+
+                        if ((name == null) || (value == null)) {
+                            throw new SAXException(
+                                "Required attributes missing for <ApplicationProperty> element");
+                        }
+
+                        applicationProperties.put(name, value);
+                    } else if (qname.equals("SftpFavorite")) {
+                        String name = attrs.getValue("Name");
+                        String value = attrs.getValue("Value");
+
+                        if ((name == null) || (value == null)) {
+                            throw new SAXException(
+                                "Required attributes missing for <SftpFavorite> element");
+                        }
+
+                        sftpFavorites.put(name, value);
+                    } else if (qname.equals("AuthenticationMethod")) {
+                        currentAuthentication = attrs.getValue("Name");
+                        currentProperties = new Properties();
+
+                        if (currentAuthentication == null) {
+                            throw new SAXException(
+                                "Required attribute missing for <AuthenticationMethod> element");
+                        }
+                    } else if (qname.equals("LocalPortForwarding") ||
+                            qname.equals("RemotePortForwarding")) {
+                        String name = attrs.getValue("Name");
+                        String addressToBind = attrs.getValue("AddressToBind");
+                        String portToBind = attrs.getValue("PortToBind");
+                        String addressToConnect = attrs.getValue(
+                                "AddressToConnect");
+                        String portToConnect = attrs.getValue("PortToConnect");
+
+                        if ((name == null) || (addressToBind == null) ||
+                                (portToBind == null) ||
+                                (addressToConnect == null) ||
+                                (portToConnect == null)) {
+                            throw new SAXException(
+                                "Required attribute missing for <" + qname +
+                                "> element");
+                        }
+
+                        ForwardingConfiguration config = new ForwardingConfiguration(name,
+                                addressToBind, Integer.parseInt(portToBind),
+                                addressToConnect,
+                                Integer.parseInt(portToConnect));
+
+                        if (qname.equals("LocalPortForwarding")) {
+                            localForwardings.put(name, config);
+                        } else {
+                            remoteForwardings.put(name, config);
+                        }
+                    } else if (qname.equals("ForwardingAutoStart")) {
+                        try {
+                            forwardingAutoStart = Boolean.valueOf(attrs.getValue(
+                                        "value")).booleanValue();
+                        } catch (Throwable ex1) {
+                        }
+                    } else {
+                        throw new SAXException("Unexpected element <" + qname +
+                            "> after SshToolsConnectionProfile");
+                    }
+                } else if (currentElement.equals("AuthenticationMethod")) {
+                    if (qname.equals("AuthenticationProperty")) {
+                        String name = attrs.getValue("Name");
+                        String value = attrs.getValue("Value");
+
+                        if ((name == null) || (value == null)) {
+                            throw new SAXException(
+                                "Required attribute missing for <AuthenticationProperty> element");
+                        }
+
+                        currentProperties.setProperty(name, value);
+                    } else {
+                        throw new SAXException("Unexpected element <" + qname +
+                            "> found after AuthenticationMethod");
+                    }
+                }
+            }
+
+            currentElement = qname;
+        }
+
+        public void characters(char[] ch, int pos, int len) {
+            executeCommands += new String(ch, pos, len);
+        }
+
+        public void endElement(String uri, String localName, String qname)
+            throws SAXException {
+            if (currentElement != null) {
+                if (!currentElement.equals(qname)) {
+                    throw new SAXException("Unexpected end element found " +
+                        qname);
+                } else if (qname.equals("SshToolsConnectionProfile")) {
+                    currentElement = null;
+                } else if (qname.startsWith("Prefered")) {
+                    currentElement = "SshToolsConnectionProfile";
+                } else if (qname.equals("OnceAuthenticated")) {
+                    currentElement = "SshToolsConnectionProfile";
+                } else if (qname.equals("ExecuteCommands")) {
+                    currentElement = "SshToolsConnectionProfile";
+                } else if (qname.equals("ApplicationProperty")) {
+                    currentElement = "SshToolsConnectionProfile";
+                } else if (qname.equals("SftpFavorite")) {
+                    currentElement = "SshToolsConnectionProfile";
+                } else if (qname.equals("AuthenticationProperty")) {
+                    currentElement = "AuthenticationMethod";
+                } else if (qname.equals("LocalPortForwarding") ||
+                        qname.equals("RemotePortForwarding") ||
+                        qname.equals("ForwardingAutoStart")) {
+                    currentElement = "SshToolsConnectionProfile";
+                } else if (qname.equals("AuthenticationMethod")) {
+                    currentElement = "SshToolsConnectionProfile";
+
+                    try {
+                        SshAuthenticationClient auth = SshAuthenticationClientFactory.newInstance(currentAuthentication);
+                        auth.setPersistableProperties(currentProperties);
+                        authMethods.put(currentAuthentication, auth);
+                    } catch (AlgorithmNotSupportedException anse) {
+                        log.warn(
+                            "AuthenticationMethod element ignored because '" +
+                            currentAuthentication +
+                            "' authentication is not supported");
+                    } finally {
+                        currentAuthentication = null;
+                    }
+                } else {
+                    throw new SAXException("Unexpected end element <" + qname +
+                        "> found");
+                }
+            }
+        }
+    }
+}
diff --git a/src/com/sshtools/common/configuration/XmlConfigurationContext.java b/src/com/sshtools/common/configuration/XmlConfigurationContext.java
new file mode 100644
index 0000000000000000000000000000000000000000..a5f101a926c3ec2a4f75d4535e7dff33cc6bf441
--- /dev/null
+++ b/src/com/sshtools/common/configuration/XmlConfigurationContext.java
@@ -0,0 +1,150 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.configuration;
+
+import com.sshtools.common.automate.*;
+
+import com.sshtools.j2ssh.configuration.*;
+
+import org.apache.commons.logging.*;
+
+import java.util.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public class XmlConfigurationContext implements ConfigurationContext {
+    private static Log log = LogFactory.getLog(XmlConfigurationContext.class);
+    HashMap configurations = new HashMap();
+    String apiResource = "sshtools.xml";
+    String automationResource = "automation.xml";
+    private boolean failOnError = false;
+
+    /**
+* Creates a new XmlConfigurationContext object.
+*/
+    public XmlConfigurationContext() {
+    }
+
+    /**
+*
+*
+* @param apiResource
+*/
+    public void setAPIConfigurationResource(String apiResource) {
+        this.apiResource = apiResource;
+    }
+
+    /**
+*
+*
+* @param automationResource
+*/
+    public void setAutomationConfigurationResource(String automationResource) {
+        this.automationResource = automationResource;
+    }
+
+    /**
+*
+*
+* @param failOnError
+*/
+    public void setFailOnError(boolean failOnError) {
+        this.failOnError = failOnError;
+    }
+
+    /**
+*
+*
+* @throws ConfigurationException
+*/
+    public void initialize() throws ConfigurationException {
+        if (apiResource != null) {
+            try {
+                SshAPIConfiguration x = new SshAPIConfiguration(ConfigurationLoader.loadFile(
+                            apiResource));
+                configurations.put(com.sshtools.j2ssh.configuration.SshAPIConfiguration.class,
+                    x);
+            } catch (Exception ex) {
+                if (failOnError) {
+                    throw new ConfigurationException(ex.getMessage());
+                } else {
+                    log.info(apiResource + " could not be found: " +
+                        ex.getMessage());
+                }
+            }
+        }
+
+        if (automationResource != null) {
+            try {
+                AutomationConfiguration y = new AutomationConfiguration(ConfigurationLoader.loadFile(
+                            automationResource));
+                configurations.put(com.sshtools.common.automate.AutomationConfiguration.class,
+                    y);
+            } catch (Exception ex) {
+                if (failOnError) {
+                    throw new ConfigurationException(ex.getMessage());
+                } else {
+                    log.info(automationResource + " could not be found: " +
+                        ex.getMessage());
+                }
+            }
+        }
+    }
+
+    /**
+*
+*
+* @param cls
+*
+* @return
+*/
+    public boolean isConfigurationAvailable(Class cls) {
+        return configurations.containsKey(cls);
+    }
+
+    /**
+*
+*
+* @param cls
+*
+* @return
+*
+* @throws ConfigurationException
+*/
+    public Object getConfiguration(Class cls) throws ConfigurationException {
+        if (configurations.containsKey(cls)) {
+            return configurations.get(cls);
+        } else {
+            throw new ConfigurationException(cls.getName() +
+                " configuration not available");
+        }
+    }
+}
diff --git a/src/com/sshtools/common/hosts/AbstractHostKeyVerification.java b/src/com/sshtools/common/hosts/AbstractHostKeyVerification.java
new file mode 100644
index 0000000000000000000000000000000000000000..f75a3dceabe0e4965c13f64deb358a13fa11e4c1
--- /dev/null
+++ b/src/com/sshtools/common/hosts/AbstractHostKeyVerification.java
@@ -0,0 +1,524 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.hosts;
+
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+import com.sshtools.j2ssh.transport.HostKeyVerification;
+import com.sshtools.j2ssh.transport.InvalidHostFileException;
+import com.sshtools.j2ssh.transport.TransportProtocolException;
+import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FilePermission;
+import java.io.IOException;
+import java.io.InputStream;
+
+import java.security.AccessControlException;
+import java.security.AccessController;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public abstract class AbstractHostKeyVerification extends DefaultHandler
+    implements HostKeyVerification {
+    private static String defaultHostFile;
+    private static Log log = LogFactory.getLog(HostKeyVerification.class);
+
+    static {
+        log.info("Determining default host file");
+
+        // Get the sshtools.home system property
+        defaultHostFile = ConfigurationLoader.getConfigurationDirectory();
+
+        if (defaultHostFile == null) {
+            log.info(
+                "No configuration location, persistence of host keys will be disabled.");
+        } else {
+            // Set the default host file name to our hosts.xml
+            defaultHostFile += "hosts.xml";
+            log.info("Defaulting host file to " + defaultHostFile);
+        }
+    }
+
+    private List deniedHosts = new ArrayList();
+    private Map allowedHosts = new HashMap();
+    private String hostFile;
+    private boolean hostFileWriteable;
+    private boolean expectEndElement = false;
+    private String currentElement = null;
+
+    /**
+* Creates a new AbstractHostKeyVerification object.
+*
+* @throws InvalidHostFileException
+*/
+    public AbstractHostKeyVerification() throws InvalidHostFileException {
+        this(defaultHostFile);
+        hostFile = defaultHostFile;
+    }
+
+    /**
+* Creates a new AbstractHostKeyVerification object.
+*
+* @param hostFileName
+*
+* @throws InvalidHostFileException
+*/
+    public AbstractHostKeyVerification(String hostFileName)
+        throws InvalidHostFileException {
+        InputStream in = null;
+
+        try {
+            //  If no host file is supplied, or there is not enough permission to load
+            //  the file, then just create an empty list.
+            if (hostFileName != null) {
+                if (System.getSecurityManager() != null) {
+                    AccessController.checkPermission(new FilePermission(
+                            hostFileName, "read"));
+                }
+
+                //  Load the hosts file. Do not worry if fle doesnt exist, just disable
+                //  save of
+                File f = new File(hostFileName);
+
+                if (f.exists()) {
+                    in = new FileInputStream(f);
+                    hostFile = hostFileName;
+
+                    SAXParserFactory saxFactory = SAXParserFactory.newInstance();
+                    SAXParser saxParser = saxFactory.newSAXParser();
+                    saxParser.parse(in, this);
+                    hostFileWriteable = f.canWrite();
+                } else {
+                    // Try to create the file
+                    if (f.createNewFile()) {
+                        FileOutputStream out = new FileOutputStream(f);
+                        out.write(toString().getBytes());
+                        out.close();
+                        hostFileWriteable = true;
+                    } else {
+                        hostFileWriteable = false;
+                    }
+                }
+
+                if (!hostFileWriteable) {
+                    log.warn("Host file is not writeable.");
+                }
+            }
+        } catch (AccessControlException ace) {
+            log.warn(
+                "Not enough permission to load a hosts file, so just creating an empty list");
+        } catch (IOException ioe) {
+            throw new InvalidHostFileException("Could not open or read " +
+                hostFileName);
+        } catch (SAXException sax) {
+            throw new InvalidHostFileException("Failed XML parsing: " +
+                sax.getMessage());
+        } catch (ParserConfigurationException pce) {
+            throw new InvalidHostFileException(
+                "Failed to initialize xml parser: " + pce.getMessage());
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException ioe) {
+                }
+            }
+        }
+    }
+
+    /**
+*
+*
+* @param uri
+* @param localName
+* @param qname
+* @param attrs
+*
+* @throws SAXException
+*/
+    public void startElement(String uri, String localName, String qname,
+        Attributes attrs) throws SAXException {
+        if (currentElement == null) {
+            if (qname.equals("HostAuthorizations")) {
+                allowedHosts.clear();
+                deniedHosts.clear();
+                currentElement = qname;
+            } else {
+                throw new SAXException("Unexpected document element!");
+            }
+        } else {
+            if (!currentElement.equals("HostAuthorizations")) {
+                throw new SAXException("Unexpected parent element found!");
+            }
+
+            if (qname.equals("AllowHost")) {
+                String hostname = attrs.getValue("HostName");
+                String fingerprint = attrs.getValue("Fingerprint");
+
+                if ((hostname != null) && (fingerprint != null)) {
+                    if (log.isDebugEnabled()) {
+                        log.debug("AllowHost element for host '" + hostname +
+                            "' with fingerprint '" + fingerprint + "'");
+                    }
+
+                    allowedHosts.put(hostname, fingerprint);
+                    currentElement = qname;
+                } else {
+                    throw new SAXException("Requried attribute(s) missing!");
+                }
+            } else if (qname.equals("DenyHost")) {
+                String hostname = attrs.getValue("HostName");
+
+                if (hostname != null) {
+                    if (log.isDebugEnabled()) {
+                        log.debug("DenyHost element for host " + hostname);
+                    }
+
+                    deniedHosts.add(hostname);
+                    currentElement = qname;
+                } else {
+                    throw new SAXException(
+                        "Required attribute hostname missing");
+                }
+            } else {
+                log.warn("Unexpected " + qname +
+                    " element found in allowed hosts file");
+            }
+        }
+    }
+
+    /**
+*
+*
+* @param uri
+* @param localName
+* @param qname
+*
+* @throws SAXException
+*/
+    public void endElement(String uri, String localName, String qname)
+        throws SAXException {
+        if (currentElement == null) {
+            throw new SAXException("Unexpected end element found!");
+        }
+
+        if (currentElement.equals("HostAuthorizations")) {
+            currentElement = null;
+
+            return;
+        }
+
+        if (currentElement.equals("AllowHost")) {
+            currentElement = "HostAuthorizations";
+
+            return;
+        }
+
+        if (currentElement.equals("DenyHost")) {
+            currentElement = "HostAuthorizations";
+
+            return;
+        }
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public boolean isHostFileWriteable() {
+        return hostFileWriteable;
+    }
+
+    /**
+*
+*
+* @param host
+*
+* @throws TransportProtocolException
+*/
+    public abstract void onDeniedHost(String host)
+        throws TransportProtocolException;
+
+    /**
+*
+*
+* @param host
+* @param allowedHostKey
+* @param actualHostKey
+*
+* @throws TransportProtocolException
+*/
+    public abstract void onHostKeyMismatch(String host, String allowedHostKey,
+        String actualHostKey) throws TransportProtocolException;
+
+    /**
+*
+*
+* @param host
+* @param hostKeyFingerprint
+*
+* @throws TransportProtocolException
+*/
+    public abstract void onUnknownHost(String host, String hostKeyFingerprint)
+        throws TransportProtocolException;
+
+    /**
+*
+*
+* @param host
+* @param hostKeyFingerprint
+* @param always
+*
+* @throws InvalidHostFileException
+*/
+    public void allowHost(String host, String hostKeyFingerprint, boolean always)
+        throws InvalidHostFileException {
+        if (log.isDebugEnabled()) {
+            log.debug("Allowing " + host + " with fingerprint " +
+                hostKeyFingerprint);
+        }
+
+        // Put the host into the allowed hosts list, overiding any previous
+        // entry
+        allowedHosts.put(host, hostKeyFingerprint);
+
+        // If we always want to allow then save the host file with the
+        // new details
+        if (always) {
+            saveHostFile();
+        }
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public Map allowedHosts() {
+        return allowedHosts;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public java.util.List deniedHosts() {
+        return deniedHosts;
+    }
+
+    /**
+*
+*
+* @param host
+*/
+    public void removeAllowedHost(String host) {
+        allowedHosts.remove(host);
+    }
+
+    /**
+*
+*
+* @param host
+*/
+    public void removeDeniedHost(String host) {
+        for (int i = deniedHosts.size() - 1; i >= 0; i--) {
+            String h = (String) deniedHosts.get(i);
+
+            if (h.equals(host)) {
+                deniedHosts.remove(i);
+            }
+        }
+    }
+
+    /**
+*
+*
+* @param host
+* @param always
+*
+* @throws InvalidHostFileException
+*/
+    public void denyHost(String host, boolean always)
+        throws InvalidHostFileException {
+        if (log.isDebugEnabled()) {
+            log.debug(host + " is denied access");
+        }
+
+        // Get the denied host from the list
+        if (!deniedHosts.contains(host)) {
+            deniedHosts.add(host);
+        }
+
+        // Save it if need be
+        if (always) {
+            saveHostFile();
+        }
+    }
+
+    /**
+*
+*
+* @param host
+* @param pk
+*
+* @return
+*
+* @throws TransportProtocolException
+*/
+    public boolean verifyHost(String host, SshPublicKey pk)
+        throws TransportProtocolException {
+        String fingerprint = pk.getFingerprint();
+        log.info("Verifying " + host + " host key");
+
+        if (log.isDebugEnabled()) {
+            log.debug("Fingerprint: " + fingerprint);
+        }
+
+        // See if the host is denied by looking at the denied hosts list
+        if (deniedHosts.contains(host)) {
+            onDeniedHost(host);
+
+            return false;
+        }
+
+        // Try the allowed hosts by looking at the allowed hosts map
+        if (allowedHosts.containsKey(host)) {
+            // The host is allowed so check the fingerprint
+            String currentFingerprint = (String) allowedHosts.get(host);
+
+            if (currentFingerprint.compareToIgnoreCase(fingerprint) == 0) {
+                return true;
+            }
+
+            // The host key does not match the recorded so call the abstract
+            // method so that the user can decide
+            onHostKeyMismatch(host, currentFingerprint, fingerprint);
+
+            // Recheck the after the users input
+            return checkFingerprint(host, fingerprint);
+        } else {
+            // The host is unknown os ask the user
+            onUnknownHost(host, fingerprint);
+
+            // Recheck ans return the result
+            return checkFingerprint(host, fingerprint);
+        }
+    }
+
+    private boolean checkFingerprint(String host, String fingerprint) {
+        String currentFingerprint = (String) allowedHosts.get(host);
+
+        if (currentFingerprint != null) {
+            if (currentFingerprint.compareToIgnoreCase(fingerprint) == 0) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+*
+*
+* @throws InvalidHostFileException
+*/
+    public void saveHostFile() throws InvalidHostFileException {
+        if (!hostFileWriteable) {
+            throw new InvalidHostFileException("Host file is not writeable.");
+        }
+
+        log.info("Saving " + defaultHostFile);
+
+        try {
+            File f = new File(hostFile);
+            FileOutputStream out = new FileOutputStream(f);
+            out.write(toString().getBytes());
+            out.close();
+        } catch (IOException e) {
+            throw new InvalidHostFileException("Could not write to " +
+                hostFile);
+        }
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String toString() {
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<HostAuthorizations>\n";
+        xml += "<!-- Host Authorizations file, used by the abstract class HostKeyVerification to verify the servers host key -->";
+        xml += "   <!-- Allow the following hosts access if they provide the correct public key -->\n";
+
+        Map.Entry entry;
+        Iterator it = allowedHosts.entrySet().iterator();
+
+        while (it.hasNext()) {
+            entry = (Map.Entry) it.next();
+            xml += ("   " + "<AllowHost HostName=\"" +
+            entry.getKey().toString() + "\" Fingerprint=\"" +
+            entry.getValue().toString() + "\"/>\n");
+        }
+
+        xml += "   <!-- Deny the following hosts access -->\n";
+        it = deniedHosts.iterator();
+
+        while (it.hasNext()) {
+            xml += ("   <DenyHost HostName=\"" + it.next().toString() +
+            "\"/>\n");
+        }
+
+        xml += "</HostAuthorizations>";
+
+        return xml;
+    }
+}
diff --git a/src/com/sshtools/common/hosts/ConsoleHostKeyVerification.java b/src/com/sshtools/common/hosts/ConsoleHostKeyVerification.java
new file mode 100644
index 0000000000000000000000000000000000000000..9531c80b1336c85517054471b0e816d77074bc29
--- /dev/null
+++ b/src/com/sshtools/common/hosts/ConsoleHostKeyVerification.java
@@ -0,0 +1,140 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.hosts;
+
+import com.sshtools.j2ssh.transport.InvalidHostFileException;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class ConsoleHostKeyVerification extends AbstractHostKeyVerification {
+    /**
+* Creates a new ConsoleHostKeyVerification object.
+*
+* @throws InvalidHostFileException
+*/
+    public ConsoleHostKeyVerification() throws InvalidHostFileException {
+        super();
+    }
+
+    /**
+* Creates a new ConsoleHostKeyVerification object.
+*
+* @param hostFile
+*
+* @throws InvalidHostFileException
+*/
+    public ConsoleHostKeyVerification(String hostFile)
+        throws InvalidHostFileException {
+        super(hostFile);
+    }
+
+    /**
+*
+*
+* @param hostname
+*/
+    public void onDeniedHost(String hostname) {
+        System.out.println("Access to the host " + hostname +
+            " is denied from this system");
+    }
+
+    /**
+*
+*
+* @param host
+* @param fingerprint
+* @param actual
+*/
+    public void onHostKeyMismatch(String host, String fingerprint, String actual) {
+        try {
+            System.out.println("The host key supplied by " + host + " is: " +
+                actual);
+            System.out.println("The current allowed key for " + host + " is: " +
+                fingerprint);
+            getResponse(host, actual);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+*
+*
+* @param host
+* @param fingerprint
+*/
+    public void onUnknownHost(String host, String fingerprint) {
+        try {
+            System.out.println("The host " + host +
+                " is currently unknown to the system");
+            System.out.println("The host key fingerprint is: " + fingerprint);
+            getResponse(host, fingerprint);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void getResponse(String host, String fingerprint)
+        throws InvalidHostFileException, IOException {
+        String response = "";
+        BufferedReader reader = new BufferedReader(new InputStreamReader(
+                    System.in));
+
+        while (!(response.equalsIgnoreCase("YES") ||
+                response.equalsIgnoreCase("NO") ||
+                (response.equalsIgnoreCase("ALWAYS") && isHostFileWriteable()))) {
+            String options = (isHostFileWriteable() ? "Yes|No|Always" : "Yes|No");
+
+            if (!isHostFileWriteable()) {
+                System.out.println(
+                    "Always option disabled, host file is not writeable");
+            }
+
+            System.out.print("Do you want to allow this host key? [" + options +
+                "]: ");
+            response = reader.readLine();
+        }
+
+        if (response.equalsIgnoreCase("YES")) {
+            allowHost(host, fingerprint, false);
+        }
+
+        if (response.equalsIgnoreCase("ALWAYS") && isHostFileWriteable()) {
+            allowHost(host, fingerprint, true);
+        }
+
+        // Do nothing on NO
+    }
+}
diff --git a/src/com/sshtools/common/hosts/DialogHostKeyVerification.java b/src/com/sshtools/common/hosts/DialogHostKeyVerification.java
new file mode 100644
index 0000000000000000000000000000000000000000..74151722931fc5c58c6a898130879a42f4ea88fa
--- /dev/null
+++ b/src/com/sshtools/common/hosts/DialogHostKeyVerification.java
@@ -0,0 +1,235 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.hosts;
+
+import com.sshtools.j2ssh.transport.InvalidHostFileException;
+import com.sshtools.j2ssh.transport.TransportProtocolException;
+
+import java.awt.Component;
+
+import java.lang.reflect.InvocationTargetException;
+
+import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class DialogHostKeyVerification extends AbstractHostKeyVerification {
+    Component parent;
+    private boolean verificationEnabled = true;
+
+    /**
+* Creates a new DialogHostKeyVerification object.
+*
+* @param parent
+*
+* @throws InvalidHostFileException
+*/
+    public DialogHostKeyVerification(Component parent)
+        throws InvalidHostFileException {
+        this.parent = parent;
+    }
+
+    /**
+* Creates a new DialogHostKeyVerification object.
+*
+* @param parent
+* @param hostFileName
+*
+* @throws InvalidHostFileException
+*/
+    public DialogHostKeyVerification(Component parent, String hostFileName)
+        throws InvalidHostFileException {
+        super(hostFileName);
+        this.parent = parent;
+    }
+
+    /**
+*
+*
+* @param enabled
+*/
+    public void setVerificationEnabled(boolean enabled) {
+        this.verificationEnabled = verificationEnabled;
+    }
+
+    /**
+*
+*
+* @param host
+*
+* @throws TransportProtocolException
+*/
+    public void onDeniedHost(final String host)
+        throws TransportProtocolException {
+        // Show a message to the user to inform them that the host
+        // is denied
+        try {
+            if (verificationEnabled) {
+                SwingUtilities.invokeAndWait(new Runnable() {
+                        public void run() {
+                            JOptionPane.showMessageDialog(parent,
+                                "Access to '" + host + "' is denied.\n" +
+                                "Verify the access granted/denied in the allowed hosts file.",
+                                "Remote Host Authentication",
+                                JOptionPane.ERROR_MESSAGE);
+                        }
+                    });
+            }
+        } catch (InvocationTargetException ite) {
+            throw new TransportProtocolException("Invocation Exception: " +
+                ite.getMessage());
+        } catch (InterruptedException ie) {
+            throw new TransportProtocolException(
+                "SwingUtilities thread interrupted!");
+        }
+    }
+
+    /**
+*
+*
+* @param host
+* @param recordedFingerprint
+* @param actualFingerprint
+*
+* @throws TransportProtocolException
+*/
+    public void onHostKeyMismatch(final String host,
+        final String recordedFingerprint, final String actualFingerprint)
+        throws TransportProtocolException {
+        try {
+            if (verificationEnabled) {
+                SwingUtilities.invokeAndWait(new Runnable() {
+                        public void run() {
+                            Object[] options = getOptions();
+                            int res = JOptionPane.showOptionDialog(parent,
+                                    "The host '" + host +
+                                    "' has provided a different host key.\nThe host key" +
+                                    " fingerprint provided is '" +
+                                    actualFingerprint + "'.\n" +
+                                    "The allowed host key fingerprint is " +
+                                    recordedFingerprint +
+                                    ".\nDo you want to allow this host?",
+                                    "Remote host authentication",
+                                    JOptionPane.YES_NO_CANCEL_OPTION,
+                                    JOptionPane.QUESTION_MESSAGE, null,
+                                    options, options[0]);
+
+                            try {
+                                // Handle the reply
+                                if ((options.length == 3) && (res == 0)) {
+                                    // Always allow the host with the new fingerprint
+                                    allowHost(host, actualFingerprint, true);
+                                } else if (((options.length == 2) &&
+                                        (res == 0)) ||
+                                        ((options.length == 3) && (res == 1))) {
+                                    // Only allow the host this once
+                                    allowHost(host, actualFingerprint, false);
+                                }
+                            } catch (InvalidHostFileException e) {
+                                showExceptionMessage(e);
+                            }
+                        }
+                    });
+            }
+        } catch (InvocationTargetException ite) {
+            throw new TransportProtocolException("Invocation Exception: " +
+                ite.getMessage());
+        } catch (InterruptedException ie) {
+            throw new TransportProtocolException(
+                "SwingUtilities thread interrupted!");
+        }
+    }
+
+    /**
+*
+*
+* @param host
+* @param fingerprint
+*
+* @throws TransportProtocolException
+*/
+    public void onUnknownHost(final String host, final String fingerprint)
+        throws TransportProtocolException {
+        // Set up the users options. Only allow always if we can
+        // write to the hosts file
+        try {
+            if (verificationEnabled) {
+                SwingUtilities.invokeAndWait(new Runnable() {
+                        public void run() {
+                            Object[] options = getOptions();
+                            int res = JOptionPane.showOptionDialog(parent,
+                                    "The host '" + host +
+                                    "' is unknown. The host key" +
+                                    " fingerprint is\n'" + fingerprint +
+                                    "'.\nDo you want to allow this host?",
+                                    "Remote host authentication",
+                                    JOptionPane.YES_NO_CANCEL_OPTION,
+                                    JOptionPane.QUESTION_MESSAGE, null,
+                                    options, options[0]);
+
+                            try {
+                                // Handle the reply
+                                if ((options.length == 3) && (res == 0)) {
+                                    // Always allow the host with the new fingerprint
+                                    allowHost(host, fingerprint, true);
+                                } else if (((options.length == 2) &&
+                                        (res == 0)) ||
+                                        ((options.length == 3) && (res == 1))) {
+                                    // Only allow the host this once
+                                    allowHost(host, fingerprint, false);
+                                }
+                            } catch (InvalidHostFileException e) {
+                                showExceptionMessage(e);
+                            }
+                        }
+                    });
+            }
+        } catch (InvocationTargetException ite) {
+            throw new TransportProtocolException("Invocation Exception: " +
+                ite.getMessage());
+        } catch (InterruptedException ie) {
+            throw new TransportProtocolException(
+                "SwingUtilities thread interrupted!");
+        }
+    }
+
+    private String[] getOptions() {
+        return isHostFileWriteable() ? new String[] { "Always", "Yes", "No" }
+                                     : new String[] { "Yes", "No" };
+    }
+
+    private void showExceptionMessage(Exception e) {
+        JOptionPane.showMessageDialog(parent,
+            "An unexpected error occured!\n\n" + e.getMessage(),
+            "Host Verification", JOptionPane.ERROR_MESSAGE);
+    }
+}
diff --git a/src/com/sshtools/common/hosts/DialogKnownHostsKeyVerification.java b/src/com/sshtools/common/hosts/DialogKnownHostsKeyVerification.java
new file mode 100644
index 0000000000000000000000000000000000000000..fa0fa54d3122673a5c6348acfe4035928b367a15
--- /dev/null
+++ b/src/com/sshtools/common/hosts/DialogKnownHostsKeyVerification.java
@@ -0,0 +1,243 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.hosts;
+
+import com.sshtools.j2ssh.transport.AbstractKnownHostsKeyVerification;
+import com.sshtools.j2ssh.transport.InvalidHostFileException;
+import com.sshtools.j2ssh.transport.TransportProtocolException;
+import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
+
+import java.awt.Component;
+
+import java.io.File;
+
+import java.lang.reflect.InvocationTargetException;
+
+import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class DialogKnownHostsKeyVerification
+    extends AbstractKnownHostsKeyVerification {
+    Component parent;
+    private boolean verificationEnabled = true;
+
+    /**
+* Creates a new DialogKnownHostsKeyVerification object.
+*
+* @param parent
+*
+* @throws InvalidHostFileException
+*/
+    public DialogKnownHostsKeyVerification(Component parent)
+        throws InvalidHostFileException {
+        super(new File(System.getProperty("user.home"),
+                ".ssh" + File.separator + "known_hosts").getAbsolutePath());
+        this.parent = parent;
+    }
+
+    /**
+* Creates a new DialogKnownHostsKeyVerification object.
+*
+* @param parent
+* @param hostFileName
+*
+* @throws InvalidHostFileException
+*/
+    public DialogKnownHostsKeyVerification(Component parent, String hostFileName)
+        throws InvalidHostFileException {
+        super(hostFileName);
+        this.parent = parent;
+    }
+
+    /**
+*
+*
+* @param enabled
+*/
+    public void setVerificationEnabled(boolean enabled) {
+        this.verificationEnabled = verificationEnabled;
+    }
+
+    /**
+*
+*
+* @param host
+*
+* @throws TransportProtocolException
+*/
+    public void onDeniedHost(final String host)
+        throws TransportProtocolException {
+        // Show a message to the user to inform them that the host
+        // is denied
+        try {
+            if (verificationEnabled) {
+                SwingUtilities.invokeAndWait(new Runnable() {
+                        public void run() {
+                            JOptionPane.showMessageDialog(parent,
+                                "Access to '" + host + "' is denied.\n" +
+                                "Verify the access granted/denied in the allowed hosts file.",
+                                "Remote Host Authentication",
+                                JOptionPane.ERROR_MESSAGE);
+                        }
+                    });
+            }
+        } catch (InvocationTargetException ite) {
+            throw new TransportProtocolException("Invocation Exception: " +
+                ite.getMessage());
+        } catch (InterruptedException ie) {
+            throw new TransportProtocolException(
+                "SwingUtilities thread interrupted!");
+        }
+    }
+
+    /**
+*
+*
+* @param host
+* @param recorded
+* @param actual
+*
+* @throws TransportProtocolException
+*/
+    public void onHostKeyMismatch(final String host,
+        final SshPublicKey recorded, final SshPublicKey actual)
+        throws TransportProtocolException {
+        try {
+            if (verificationEnabled) {
+                SwingUtilities.invokeAndWait(new Runnable() {
+                        public void run() {
+                            Object[] options = getOptions();
+                            int res = JOptionPane.showOptionDialog(parent,
+                                    "The host '" + host +
+                                    "' has provided a different host key.\nThe host key" +
+                                    " fingerprint provided is '" +
+                                    actual.getFingerprint() + "'.\n" +
+                                    "The allowed host key fingerprint is " +
+                                    recorded.getFingerprint() +
+                                    ".\nDo you want to allow this host?",
+                                    "Remote host authentication",
+                                    JOptionPane.YES_NO_CANCEL_OPTION,
+                                    JOptionPane.QUESTION_MESSAGE, null,
+                                    options, options[0]);
+
+                            try {
+                                // Handle the reply
+                                if ((options.length == 3) && (res == 0)) {
+                                    // Always allow the host with the new fingerprint
+                                    allowHost(host, actual, true);
+                                } else if (((options.length == 2) &&
+                                        (res == 0)) ||
+                                        ((options.length == 3) && (res == 1))) {
+                                    // Only allow the host this once
+                                    allowHost(host, actual, false);
+                                }
+                            } catch (InvalidHostFileException e) {
+                                showExceptionMessage(e);
+                            }
+                        }
+                    });
+            }
+        } catch (InvocationTargetException ite) {
+            throw new TransportProtocolException("Invocation Exception: " +
+                ite.getMessage());
+        } catch (InterruptedException ie) {
+            throw new TransportProtocolException(
+                "SwingUtilities thread interrupted!");
+        }
+    }
+
+    /**
+*
+*
+* @param host
+* @param key
+*
+* @throws TransportProtocolException
+*/
+    public void onUnknownHost(final String host, final SshPublicKey key)
+        throws TransportProtocolException {
+        // Set up the users options. Only allow always if we can
+        // write to the hosts file
+        try {
+            if (verificationEnabled) {
+                SwingUtilities.invokeAndWait(new Runnable() {
+                        public void run() {
+                            Object[] options = getOptions();
+                            int res = JOptionPane.showOptionDialog(parent,
+                                    "The host '" + host +
+                                    "' is unknown. The host key" +
+                                    " fingerprint is\n'" +
+                                    key.getFingerprint() +
+                                    "'.\nDo you want to allow this host?",
+                                    "Remote host authentication",
+                                    JOptionPane.YES_NO_CANCEL_OPTION,
+                                    JOptionPane.QUESTION_MESSAGE, null,
+                                    options, options[0]);
+
+                            try {
+                                // Handle the reply
+                                if ((options.length == 3) && (res == 0)) {
+                                    // Always allow the host with the new fingerprint
+                                    allowHost(host, key, true);
+                                } else if (((options.length == 2) &&
+                                        (res == 0)) ||
+                                        ((options.length == 3) && (res == 1))) {
+                                    // Only allow the host this once
+                                    allowHost(host, key, false);
+                                }
+                            } catch (InvalidHostFileException e) {
+                                showExceptionMessage(e);
+                            }
+                        }
+                    });
+            }
+        } catch (InvocationTargetException ite) {
+            throw new TransportProtocolException("Invocation Exception: " +
+                ite.getMessage());
+        } catch (InterruptedException ie) {
+            throw new TransportProtocolException(
+                "SwingUtilities thread interrupted!");
+        }
+    }
+
+    private String[] getOptions() {
+        return isHostFileWriteable() ? new String[] { "Always", "Yes", "No" }
+                                     : new String[] { "Yes", "No" };
+    }
+
+    private void showExceptionMessage(Exception e) {
+        JOptionPane.showMessageDialog(parent,
+            "An unexpected error occured!\n\n" + e.getMessage(),
+            "Host Verification", JOptionPane.ERROR_MESSAGE);
+    }
+}
diff --git a/src/com/sshtools/common/keygen/KeygenPanel.java b/src/com/sshtools/common/keygen/KeygenPanel.java
new file mode 100644
index 0000000000000000000000000000000000000000..7d37085de3154e79e01dd368223973109ce2e9c3
--- /dev/null
+++ b/src/com/sshtools/common/keygen/KeygenPanel.java
@@ -0,0 +1,418 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.keygen;
+
+import com.sshtools.common.ui.NumericTextField;
+import com.sshtools.common.ui.UIUtil;
+import com.sshtools.common.ui.XTextField;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import java.io.File;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JPasswordField;
+import javax.swing.JProgressBar;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class KeygenPanel extends JPanel implements DocumentListener,
+    ActionListener {
+    /**  */
+    public final static int GENERATE_KEY_PAIR = 0;
+
+    /**  */
+    public final static int CONVERT_IETF_SECSH_TO_OPENSSH = 1;
+
+    /**  */
+    public final static int CONVERT_OPENSSH_TO_IETF_SECSH = 2;
+
+    /**  */
+    public final static int CHANGE_PASSPHRASE = 3;
+
+    //  Private instance variables
+    private JButton browseInput;
+
+    //  Private instance variables
+    private JButton browseOutput;
+    private JComboBox action;
+    private JComboBox type;
+    private JLabel bitsLabel;
+    private JLabel inputFileLabel;
+    private JLabel newPassphraseLabel;
+    private JLabel oldPassphraseLabel;
+    private JLabel outputFileLabel;
+    private JLabel typeLabel;
+    private JPasswordField newPassphrase;
+    private JPasswordField oldPassphrase;
+    private JProgressBar strength;
+    private XTextField inputFile;
+    private XTextField outputFile;
+    private NumericTextField bits;
+
+    /**
+* Creates a new KeygenPanel object.
+*/
+    public KeygenPanel() {
+        super();
+
+        JPanel keyPanel = new JPanel(new GridBagLayout());
+        keyPanel.setBorder(BorderFactory.createTitledBorder("Key"));
+
+        //  Create the main panel
+        GridBagConstraints gbc = new GridBagConstraints();
+        gbc.fill = GridBagConstraints.NONE;
+        gbc.anchor = GridBagConstraints.WEST;
+
+        Insets normalInsets = new Insets(0, 2, 4, 2);
+        Insets indentedInsets = new Insets(0, 26, 4, 2);
+        gbc.insets = normalInsets;
+        gbc.weightx = 0.0;
+
+        //  Action
+        UIUtil.jGridBagAdd(keyPanel, new JLabel("Action"), gbc, 1);
+        gbc.weightx = 1.0;
+        action = new JComboBox(new String[] {
+                    "Generate key pair", "Convert IETF SECSH to OpenSSH",
+                    "Convert OpenSSH to IETF SECSH", "Change passphrase"
+                });
+        action.addActionListener(this);
+        gbc.weightx = 2.0;
+        UIUtil.jGridBagAdd(keyPanel, action, gbc, GridBagConstraints.RELATIVE);
+        gbc.weightx = 0.0;
+        UIUtil.jGridBagAdd(keyPanel, new JLabel(), gbc,
+            GridBagConstraints.REMAINDER);
+        gbc.insets = indentedInsets;
+
+        //  File
+        inputFileLabel = new JLabel("Input File");
+        UIUtil.jGridBagAdd(keyPanel, inputFileLabel, gbc, 1);
+        gbc.insets = normalInsets;
+        gbc.weightx = 1.0;
+        inputFile = new XTextField(20);
+        UIUtil.jGridBagAdd(keyPanel, inputFile, gbc, GridBagConstraints.RELATIVE);
+        inputFileLabel.setLabelFor(inputFile);
+        gbc.weightx = 0.0;
+        browseInput = new JButton("Browse");
+        browseInput.setMnemonic('b');
+        browseInput.addActionListener(this);
+        UIUtil.jGridBagAdd(keyPanel, browseInput, gbc,
+            GridBagConstraints.REMAINDER);
+
+        //  File
+        gbc.insets = indentedInsets;
+        outputFileLabel = new JLabel("Output File");
+        UIUtil.jGridBagAdd(keyPanel, outputFileLabel, gbc, 1);
+        gbc.insets = normalInsets;
+        gbc.weightx = 1.0;
+        outputFile = new XTextField(20);
+        UIUtil.jGridBagAdd(keyPanel, outputFile, gbc,
+            GridBagConstraints.RELATIVE);
+        gbc.weightx = 0.0;
+        outputFileLabel.setLabelFor(outputFile);
+        browseOutput = new JButton("Browse");
+        browseOutput.setMnemonic('r');
+        browseOutput.addActionListener(this);
+        UIUtil.jGridBagAdd(keyPanel, browseOutput, gbc,
+            GridBagConstraints.REMAINDER);
+
+        //  Old Passphrase
+        gbc.insets = indentedInsets;
+        oldPassphraseLabel = new JLabel("Old Passphrase");
+        UIUtil.jGridBagAdd(keyPanel, oldPassphraseLabel, gbc, 1);
+        gbc.insets = normalInsets;
+        gbc.weightx = 2.0;
+        oldPassphrase = new JPasswordField(20);
+        oldPassphrase.setBackground(Color.white);
+        oldPassphrase.getDocument().addDocumentListener(this);
+        oldPassphraseLabel.setLabelFor(oldPassphrase);
+        UIUtil.jGridBagAdd(keyPanel, oldPassphrase, gbc,
+            GridBagConstraints.RELATIVE);
+        UIUtil.jGridBagAdd(keyPanel, new JLabel(), gbc,
+            GridBagConstraints.REMAINDER);
+
+        //  Passphrase
+        gbc.insets = indentedInsets;
+        newPassphraseLabel = new JLabel("New Passphrase");
+        UIUtil.jGridBagAdd(keyPanel, newPassphraseLabel, gbc, 1);
+        gbc.insets = normalInsets;
+        gbc.weightx = 2.0;
+        newPassphrase = new JPasswordField(20);
+        newPassphrase.setBackground(Color.white);
+        newPassphrase.getDocument().addDocumentListener(this);
+        newPassphraseLabel.setLabelFor(newPassphrase);
+        UIUtil.jGridBagAdd(keyPanel, newPassphrase, gbc,
+            GridBagConstraints.RELATIVE);
+        UIUtil.jGridBagAdd(keyPanel, new JLabel(), gbc,
+            GridBagConstraints.REMAINDER);
+
+        //  Bits
+        gbc.insets = indentedInsets;
+        bitsLabel = new JLabel("Bits");
+        UIUtil.jGridBagAdd(keyPanel, bitsLabel, gbc, 1);
+        gbc.weightx = 2.0;
+        gbc.insets = normalInsets;
+        bits = new NumericTextField(new Integer(512), new Integer(1024),
+                new Integer(1024));
+        bitsLabel.setLabelFor(bits);
+        UIUtil.jGridBagAdd(keyPanel, bits, gbc, GridBagConstraints.RELATIVE);
+        UIUtil.jGridBagAdd(keyPanel, new JLabel(), gbc,
+            GridBagConstraints.REMAINDER);
+
+        //  Type
+        gbc.insets = indentedInsets;
+        typeLabel = new JLabel("Type");
+        UIUtil.jGridBagAdd(keyPanel, typeLabel, gbc, 1);
+        gbc.insets = normalInsets;
+        gbc.weightx = 2.0;
+        type = new JComboBox(new String[] { "DSA", "RSA" });
+        type.setFont(inputFile.getFont());
+
+        //  Combo boxes look crap in metal
+        UIUtil.jGridBagAdd(keyPanel, type, gbc, GridBagConstraints.RELATIVE);
+        UIUtil.jGridBagAdd(keyPanel, new JLabel(), gbc,
+            GridBagConstraints.REMAINDER);
+        strength = new JProgressBar(0, 40);
+        strength.setStringPainted(true);
+
+        JPanel strengthPanel = new JPanel(new GridLayout(1, 1));
+        strengthPanel.setBorder(BorderFactory.createCompoundBorder(
+                BorderFactory.createTitledBorder("Passphrase strength"),
+                BorderFactory.createEmptyBorder(4, 4, 4, 4)));
+        strengthPanel.add(strength);
+
+        //  Build this panel
+        setLayout(new BorderLayout());
+        add(keyPanel, BorderLayout.CENTER);
+        add(strengthPanel, BorderLayout.SOUTH);
+        calculateStrength();
+        setAvailableActions();
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public int getAction() {
+        return action.getSelectedIndex();
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public int getBits() {
+        return ((Integer) bits.getValue()).intValue();
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getInputFilename() {
+        return inputFile.getText();
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public char[] getNewPassphrase() {
+        return newPassphrase.getPassword();
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public char[] getOldPassphrase() {
+        return oldPassphrase.getPassword();
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getOutputFilename() {
+        return outputFile.getText();
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getType() {
+        return (String) type.getSelectedItem();
+    }
+
+    /**
+*
+*
+* @param evt
+*/
+    public void actionPerformed(ActionEvent evt) {
+        if (evt.getSource() == browseOutput) {
+            File f = new File(outputFile.getText());
+            JFileChooser chooser = new JFileChooser(f);
+            chooser.setSelectedFile(f);
+            chooser.setDialogTitle("Choose output file ..");
+            chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+
+            if (chooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
+                outputFile.setText(chooser.getSelectedFile().getPath());
+            }
+        } else if (evt.getSource() == browseInput) {
+            File f = new File(inputFile.getText());
+            JFileChooser chooser = new JFileChooser(f);
+            chooser.setSelectedFile(f);
+            chooser.setDialogTitle("Choose input file ..");
+            chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+
+            if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
+                inputFile.setText(chooser.getSelectedFile().getPath());
+            }
+        } else {
+            setAvailableActions();
+        }
+    }
+
+    /**
+*
+*
+* @param e
+*/
+    public void changedUpdate(DocumentEvent e) {
+        calculateStrength();
+    }
+
+    /**
+*
+*
+* @param e
+*/
+    public void insertUpdate(DocumentEvent e) {
+        calculateStrength();
+    }
+
+    /**
+*
+*
+* @param e
+*/
+    public void removeUpdate(DocumentEvent e) {
+        calculateStrength();
+    }
+
+    private void setAvailableActions() {
+        inputFile.setEnabled((getAction() == CONVERT_IETF_SECSH_TO_OPENSSH) ||
+            (getAction() == CONVERT_OPENSSH_TO_IETF_SECSH) ||
+            (getAction() == CHANGE_PASSPHRASE));
+        inputFileLabel.setEnabled(inputFile.isEnabled());
+        browseInput.setEnabled(inputFile.isEnabled());
+        bits.setEnabled(getAction() == GENERATE_KEY_PAIR);
+        bitsLabel.setEnabled(bits.isEnabled());
+        outputFile.setEnabled((getAction() == CONVERT_IETF_SECSH_TO_OPENSSH) ||
+            (getAction() == CONVERT_OPENSSH_TO_IETF_SECSH) ||
+            (getAction() == GENERATE_KEY_PAIR));
+        outputFileLabel.setEnabled(outputFile.isEnabled());
+        browseOutput.setEnabled(outputFile.isEnabled());
+        newPassphrase.setEnabled((getAction() == GENERATE_KEY_PAIR) ||
+            (getAction() == CHANGE_PASSPHRASE));
+        newPassphraseLabel.setEnabled(newPassphrase.isEnabled());
+        oldPassphrase.setEnabled(getAction() == CHANGE_PASSPHRASE);
+        oldPassphraseLabel.setEnabled(oldPassphrase.isEnabled());
+        type.setEnabled(getAction() == GENERATE_KEY_PAIR);
+        typeLabel.setEnabled(type.isEnabled());
+
+        if (inputFile.isEnabled()) {
+            inputFile.requestFocus();
+        } else {
+            outputFile.requestFocus();
+        }
+    }
+
+    private void calculateStrength() {
+        char[] pw = newPassphrase.getPassword();
+        strength.setValue((pw.length < 40) ? pw.length : 40);
+
+        Color f;
+        String t;
+
+        if (pw.length == 0) {
+            f = Color.red;
+            t = "Empty!!";
+        } else {
+            StringBuffer buf = new StringBuffer();
+            buf.append(pw.length);
+            buf.append(" characters - ");
+
+            if (pw.length < 10) {
+                f = Color.red;
+                buf.append("Weak!");
+            } else if (pw.length < 20) {
+                f = Color.orange;
+                buf.append("Ok");
+            } else if (pw.length < 30) {
+                f = Color.green.darker();
+                buf.append("Strong");
+            } else {
+                f = Color.green;
+                buf.append("Very strong!");
+            }
+
+            t = buf.toString();
+        }
+
+        strength.setString(t);
+        strength.setForeground(f);
+    }
+}
diff --git a/src/com/sshtools/common/keygen/KeygenPanel2.java b/src/com/sshtools/common/keygen/KeygenPanel2.java
new file mode 100644
index 0000000000000000000000000000000000000000..211c446b8798f007c508364c2a7ec315f497c40e
--- /dev/null
+++ b/src/com/sshtools/common/keygen/KeygenPanel2.java
@@ -0,0 +1,126 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.keygen;
+
+import java.awt.*;
+
+import javax.swing.*;
+import javax.swing.border.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class KeygenPanel2 extends JPanel {
+    //  Actions
+
+    /**  */
+    public final static int GENERATE_KEY_PAIR = 0;
+
+    /**  */
+    public final static int CONVERT_IETF_SECSH_TO_OPENSSH = 1;
+
+    /**  */
+    public final static int CONVERT_OPENSSH_TO_IETF_SECSH = 2;
+
+    /**  */
+    public final static int CHANGE_PASSPHRASE = 3;
+
+    //  Private instance variables
+    private JButton browseInput;
+    BorderLayout borderLayout1 = new BorderLayout();
+    JPanel jPanel1 = new JPanel();
+    JTabbedPane key1Panel = new JTabbedPane();
+    JPanel key1GeneratePanel = new JPanel();
+    JPanel file = new JPanel();
+    BorderLayout borderLayout2 = new BorderLayout();
+    JPanel key1DataPanel = new JPanel();
+    JScrollPane jScrollPane1 = new JScrollPane();
+    JTextArea jTextArea1 = new JTextArea();
+    GridLayout gridLayout1 = new GridLayout();
+    TitledBorder titledBorder1;
+    TitledBorder titledBorder2;
+    GridBagLayout gridBagLayout1 = new GridBagLayout();
+    JLabel jLabel1 = new JLabel();
+    JTextField jTextField1 = new JTextField();
+    JButton jButton1 = new JButton();
+    JButton jButton2 = new JButton();
+
+    /**
+* Creates a new KeygenPanel2 object.
+*/
+    public KeygenPanel2() {
+        super();
+
+        try {
+            jbInit();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void jbInit() throws Exception {
+        titledBorder1 = new TitledBorder("Data");
+        titledBorder2 = new TitledBorder("Key 1");
+        this.setLayout(borderLayout1);
+        jPanel1.setLayout(borderLayout2);
+        jTextArea1.setText("jTextArea1");
+        key1DataPanel.setLayout(gridLayout1);
+        key1DataPanel.setBorder(titledBorder1);
+        jPanel1.setBorder(titledBorder2);
+        file.setLayout(gridBagLayout1);
+        jLabel1.setText("File:");
+        jTextField1.setText("jTextField1");
+        jButton1.setText("Open");
+        jButton2.setText("Browse");
+        this.add(jPanel1, BorderLayout.CENTER);
+        jPanel1.add(key1Panel, BorderLayout.NORTH);
+        key1Panel.add(key1GeneratePanel, "Generate");
+        key1Panel.add(file, "File");
+        jPanel1.add(key1DataPanel, BorderLayout.CENTER);
+        key1DataPanel.add(jScrollPane1, null);
+        jScrollPane1.getViewport().add(jTextArea1, null);
+        file.add(jLabel1,
+            new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
+                GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,
+                new Insets(2, 2, 0, 3), 0, 0));
+        file.add(jTextField1,
+            new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0,
+                GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,
+                new Insets(2, 2, 0, 0), 0, 0));
+        file.add(jButton1,
+            new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0,
+                GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,
+                new Insets(2, 2, 0, 2), 0, 0));
+        file.add(jButton2,
+            new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0,
+                GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,
+                new Insets(2, 0, 0, 2), 0, 0));
+    }
+}
diff --git a/src/com/sshtools/common/keygen/Main.java b/src/com/sshtools/common/keygen/Main.java
new file mode 100644
index 0000000000000000000000000000000000000000..d887128d1f0266ecec5bef7e98dff88227c024d8
--- /dev/null
+++ b/src/com/sshtools/common/keygen/Main.java
@@ -0,0 +1,321 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.keygen;
+
+import com.sshtools.common.ui.IconWrapperPanel;
+import com.sshtools.common.ui.ResourceIcon;
+import com.sshtools.common.ui.UIUtil;
+
+import com.sshtools.j2ssh.configuration.ConfigurationException;
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+import com.sshtools.j2ssh.transport.publickey.OpenSSHPublicKeyFormat;
+import com.sshtools.j2ssh.transport.publickey.SECSHPublicKeyFormat;
+import com.sshtools.j2ssh.transport.publickey.SshKeyGenerator;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.ProgressMonitor;
+import javax.swing.SwingConstants;
+import javax.swing.UIManager;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class Main extends JFrame implements ActionListener {
+    //  Statics
+    final static String ICON = "/com/sshtools/common/authentication/largepassphrase.png";
+    JButton close;
+    JButton generate;
+    KeygenPanel keygen;
+
+    /**
+* Creates a new Main object.
+*/
+    public Main() {
+        super("ssh-keygen");
+
+        /* try {
+ConfigurationLoader.setLogfile(ConfigurationLoader.getHomeDirectory()
+ + "logs/ssh-keygen.log");
+} catch (IOException ex) {
+}*/
+        try {
+            ConfigurationLoader.initialize(false);
+        } catch (ConfigurationException ex) {
+        }
+
+        //  Set the frame icon
+        setIconImage(new ResourceIcon(ICON).getImage());
+
+        //
+        keygen = new KeygenPanel();
+
+        //  Create the center banner panel
+        IconWrapperPanel centerPanel = new IconWrapperPanel(new ResourceIcon(
+                    ICON), keygen);
+        centerPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+
+        //  Button panel
+        JPanel buttonPanel = new JPanel(new GridBagLayout());
+        GridBagConstraints gbc = new GridBagConstraints();
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        gbc.anchor = GridBagConstraints.CENTER;
+        gbc.insets = new Insets(6, 6, 0, 0);
+        gbc.weighty = 1.0;
+        generate = new JButton("Generate");
+        generate.addActionListener(this);
+        generate.setMnemonic('g');
+        this.getRootPane().setDefaultButton(generate);
+        UIUtil.jGridBagAdd(buttonPanel, generate, gbc,
+            GridBagConstraints.RELATIVE);
+        close = new JButton("Close");
+        close.addActionListener(this);
+        close.setMnemonic('c');
+        UIUtil.jGridBagAdd(buttonPanel, close, gbc, GridBagConstraints.REMAINDER);
+
+        JPanel southPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 0, 0));
+        southPanel.add(buttonPanel);
+
+        //  Wrap the whole thing in an empty border
+        JPanel mainPanel = new JPanel(new BorderLayout());
+        mainPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+        mainPanel.add(centerPanel, BorderLayout.CENTER);
+        mainPanel.add(southPanel, BorderLayout.SOUTH);
+
+        //  Build the main panel
+        getContentPane().setLayout(new GridLayout(1, 1));
+        getContentPane().add(mainPanel);
+    }
+
+    /**
+*
+*
+* @param evt
+*/
+    public void actionPerformed(ActionEvent evt) {
+        //  Close
+        if (evt.getSource() == close) {
+            dispose();
+
+            return;
+        }
+
+        final String newPassphrase = new String(keygen.getNewPassphrase()).trim();
+        final String oldPassphrase = new String(keygen.getOldPassphrase()).trim();
+
+        if ((keygen.getAction() == KeygenPanel.GENERATE_KEY_PAIR) ||
+                (keygen.getAction() == KeygenPanel.CHANGE_PASSPHRASE)) {
+            if (newPassphrase.length() == 0) {
+                if (JOptionPane.showConfirmDialog(this,
+                            "Passphrase is empty. Are you sure?",
+                            "Empty Passphrase", JOptionPane.YES_NO_OPTION) == JOptionPane.NO_OPTION) {
+                    return;
+                }
+            }
+        }
+
+        final File inputFile = new File(keygen.getInputFilename());
+        final File outputFile = new File(keygen.getOutputFilename());
+        final File publicFile = new File(keygen.getOutputFilename() + ".pub");
+
+        //  Check if the output file was supplied
+        if ((keygen.getAction() == KeygenPanel.CONVERT_IETF_SECSH_TO_OPENSSH) ||
+                (keygen.getAction() == KeygenPanel.CONVERT_OPENSSH_TO_IETF_SECSH) ||
+                (keygen.getAction() == KeygenPanel.GENERATE_KEY_PAIR)) {
+            if (keygen.getOutputFilename().length() == 0) {
+                JOptionPane.showMessageDialog(this, "No Output file supplied.",
+                    "Error", JOptionPane.ERROR_MESSAGE);
+
+                return;
+            }
+
+            //  Check if the output file exists, and confirm overwrit if it does
+            if (outputFile.exists()) {
+                if (JOptionPane.showConfirmDialog(this,
+                            "Output file " + outputFile.getName() +
+                            " exists. Are you sure?", "File exists",
+                            JOptionPane.YES_NO_OPTION) == JOptionPane.NO_OPTION) {
+                    return;
+                }
+            }
+
+            //  Make sure the output file is writeable
+            if (outputFile.exists() && !outputFile.canWrite()) {
+                JOptionPane.showMessageDialog(this,
+                    "Output file " + outputFile.getName() +
+                    " can not be written.", "Unwriteable file",
+                    JOptionPane.ERROR_MESSAGE);
+
+                return;
+            }
+        }
+
+        //  If this is a conversion, check the input file is provided
+        if ((keygen.getAction() == KeygenPanel.CONVERT_IETF_SECSH_TO_OPENSSH) ||
+                (keygen.getAction() == KeygenPanel.CONVERT_OPENSSH_TO_IETF_SECSH)) {
+            if (keygen.getInputFilename().length() == 0) {
+                JOptionPane.showMessageDialog(this, "No Input file supplied.",
+                    "Error", JOptionPane.ERROR_MESSAGE);
+
+                return;
+            }
+        } else if (keygen.getAction() == KeygenPanel.GENERATE_KEY_PAIR) {
+            //  Check if the public key file is writeable. We should test if it exists
+            //  as thats just too many questions for the user
+            if (publicFile.exists() && !publicFile.canWrite()) {
+                JOptionPane.showMessageDialog(this,
+                    "Public key file " + publicFile.getName() +
+                    " can not be written.", "Unwriteable file",
+                    JOptionPane.ERROR_MESSAGE);
+
+                return;
+            }
+        }
+
+        //  Now generate the key
+        final ProgressMonitor monitor = new ProgressMonitor(this,
+                "Generating keys", "Generating", 0, 100);
+        monitor.setMillisToDecideToPopup(0);
+        monitor.setMillisToPopup(0);
+
+        Runnable r = new Runnable() {
+                public void run() {
+                    try {
+                        if (keygen.getAction() == KeygenPanel.CHANGE_PASSPHRASE) {
+                            monitor.setNote("Changing passphrase");
+                            SshKeyGenerator.changePassphrase(inputFile,
+                                oldPassphrase, newPassphrase);
+                            monitor.setNote("Complete");
+                            JOptionPane.showMessageDialog(Main.this,
+                                "Passphrase changed", "Passphrase changed",
+                                JOptionPane.INFORMATION_MESSAGE);
+                        } else if (keygen.getAction() == KeygenPanel.CONVERT_IETF_SECSH_TO_OPENSSH) {
+                            monitor.setNote("Converting key file");
+                            writeString(outputFile,
+                                SshKeyGenerator.convertPublicKeyFile(
+                                    inputFile, new OpenSSHPublicKeyFormat()));
+                            monitor.setNote("Complete");
+                            JOptionPane.showMessageDialog(Main.this,
+                                "Key converted", "Key converted",
+                                JOptionPane.INFORMATION_MESSAGE);
+                        } else if (keygen.getAction() == KeygenPanel.CONVERT_OPENSSH_TO_IETF_SECSH) {
+                            monitor.setNote("Converting key file");
+                            writeString(outputFile,
+                                SshKeyGenerator.convertPublicKeyFile(
+                                    inputFile, new SECSHPublicKeyFormat()));
+                            monitor.setNote("Complete");
+                            JOptionPane.showMessageDialog(Main.this,
+                                "Key converted", "Key converted",
+                                JOptionPane.INFORMATION_MESSAGE);
+                        } else {
+                            monitor.setNote("Creating generator");
+
+                            SshKeyGenerator generator = new SshKeyGenerator();
+                            monitor.setNote("Generating");
+
+                            String username = System.getProperty("user.name");
+                            generator.generateKeyPair(keygen.getType(),
+                                keygen.getBits(), outputFile.getAbsolutePath(),
+                                username, newPassphrase);
+                            monitor.setNote("Complete");
+                            JOptionPane.showMessageDialog(Main.this,
+                                "Key generated to " + outputFile.getName(),
+                                "Complete", JOptionPane.INFORMATION_MESSAGE);
+                        }
+                    } catch (Exception e) {
+                        JOptionPane.showMessageDialog(Main.this,
+                            e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
+                    } finally {
+                        monitor.close();
+                    }
+                }
+            };
+
+        Thread t = new Thread(r);
+        t.start();
+    }
+
+    /**
+*
+*
+* @param args
+*/
+    public static void main(String[] args) {
+        try {
+            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+        } catch (Exception e) {
+        }
+
+        Main main = new Main();
+        main.addWindowListener(new WindowAdapter() {
+                public void windowClosing(WindowEvent evt) {
+                    System.exit(0);
+                }
+            });
+        main.pack();
+        UIUtil.positionComponent(SwingConstants.CENTER, main);
+        main.setVisible(true);
+    }
+
+    private void writeString(File file, String string)
+        throws IOException {
+        FileOutputStream out = null;
+
+        try {
+            out = new FileOutputStream(file);
+
+            PrintWriter w = new PrintWriter(out, true);
+            w.println(string);
+        } finally {
+            if (out != null) {
+                out.close();
+            }
+        }
+    }
+}
diff --git a/src/com/sshtools/common/keygen/keygen.png b/src/com/sshtools/common/keygen/keygen.png
new file mode 100644
index 0000000000000000000000000000000000000000..6dd431445c70e0d3c0e907fb8997df79e0cb1fdc
Binary files /dev/null and b/src/com/sshtools/common/keygen/keygen.png differ
diff --git a/src/com/sshtools/common/keygen/largekeygen.png b/src/com/sshtools/common/keygen/largekeygen.png
new file mode 100644
index 0000000000000000000000000000000000000000..e1a6f844ba2c530136d9167b5e594e3112d56ec2
Binary files /dev/null and b/src/com/sshtools/common/keygen/largekeygen.png differ
diff --git a/src/com/sshtools/common/mru/MRUAction.java b/src/com/sshtools/common/mru/MRUAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..bb325a421dbffe37a1e24796b385749a3a4fb9d5
--- /dev/null
+++ b/src/com/sshtools/common/mru/MRUAction.java
@@ -0,0 +1,63 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.mru;
+
+import com.sshtools.common.ui.EmptyIcon;
+import com.sshtools.common.ui.MenuAction;
+import com.sshtools.common.ui.StandardAction;
+
+import javax.swing.Action;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public abstract class MRUAction extends MenuAction {
+    /**
+* Creates a new MRUAction object.
+*
+* @param model
+*/
+    public MRUAction(MRUListModel model) {
+        putValue(Action.NAME, "Recent");
+        putValue(Action.SMALL_ICON, new EmptyIcon(16, 16));
+        putValue(Action.SHORT_DESCRIPTION, "Recent connections");
+        putValue(Action.LONG_DESCRIPTION, "Recent connection files");
+        putValue(Action.MNEMONIC_KEY, new Integer('r'));
+        putValue(Action.ACTION_COMMAND_KEY, "recent");
+        putValue(StandardAction.ON_MENUBAR, new Boolean(true));
+        putValue(StandardAction.MENU_NAME, "File");
+        putValue(StandardAction.MENU_ITEM_GROUP, new Integer(0));
+        putValue(StandardAction.MENU_ITEM_WEIGHT, new Integer(99));
+
+        MRUMenu menu = new MRUMenu(this, model);
+        menu.addActionListener(this);
+        putValue(MenuAction.MENU, new MRUMenu(this, model));
+    }
+}
diff --git a/src/com/sshtools/common/mru/MRUList.java b/src/com/sshtools/common/mru/MRUList.java
new file mode 100644
index 0000000000000000000000000000000000000000..7a20a12f6059c637383eb61102ec210b2fc261d4
--- /dev/null
+++ b/src/com/sshtools/common/mru/MRUList.java
@@ -0,0 +1,196 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.mru;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+import java.util.Iterator;
+import java.util.Stack;
+import java.util.Vector;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class MRUList extends java.util.Vector {
+    private static Log log = LogFactory.getLog(MRUList.class);
+    private static final String MRU_LIST_ELEMENT = "MRUList";
+    private static final String FILE_ELEMENT = "File";
+    private String currentElement = null;
+
+    /**
+* Creates a new MRUList object.
+*/
+    public MRUList() {
+        super();
+    }
+
+    /**
+* Creates a new MRUList object.
+*
+* @param in
+*
+* @throws SAXException
+* @throws ParserConfigurationException
+* @throws IOException
+*/
+    public MRUList(InputStream in)
+        throws SAXException, ParserConfigurationException, IOException {
+        this();
+        reload(in);
+    }
+
+    /**
+*
+*
+* @param in
+*
+* @throws SAXException
+* @throws ParserConfigurationException
+* @throws IOException
+*/
+    public void reload(InputStream in)
+        throws SAXException, ParserConfigurationException, IOException {
+        SAXParserFactory saxFactory = SAXParserFactory.newInstance();
+        SAXParser saxParser = saxFactory.newSAXParser();
+        saxParser.parse(in, new MRUSAXHandler());
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String toString() {
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+        xml += ("<!-- Most recently used -->\n<" + MRU_LIST_ELEMENT + ">\n");
+
+        Iterator it = iterator();
+        File file = null;
+
+        while (it.hasNext()) {
+            file = (File) it.next();
+            xml += ("   " + "<" + FILE_ELEMENT + ">" + file.getAbsolutePath() +
+            "</" + FILE_ELEMENT + ">\n");
+        }
+
+        xml += ("</" + MRU_LIST_ELEMENT + ">");
+
+        return xml;
+    }
+
+    private class MRUSAXHandler extends DefaultHandler {
+        private String MRU_LIST_ELEMENT = "MRUList";
+        private String FILE_ELEMENT = "File";
+        private File currentFile = null;
+        private Stack tags = new Stack();
+
+        public void startElement(String uri, String localName, String qname,
+            Attributes attrs) throws SAXException {
+            ElementWrapper currentElement = (tags.size() == 0) ? null
+                                                               : (ElementWrapper) tags.peek();
+
+            if (currentElement == null) {
+                if (!qname.equals(MRU_LIST_ELEMENT)) {
+                    throw new SAXException("Unexpected root element <" + qname +
+                        ">");
+                }
+            } else {
+                if (currentElement.element.equals(MRU_LIST_ELEMENT)) {
+                    if (qname.equals(FILE_ELEMENT)) {
+                    } else {
+                        throw new SAXException("Unexpected element <" + qname +
+                            ">");
+                    }
+                } else {
+                    throw new SAXException("Unexpected element <" + qname +
+                        ">");
+                }
+            }
+
+            ElementWrapper w = new ElementWrapper(qname);
+            tags.push(w);
+        }
+
+        public void characters(char[] ch, int start, int len)
+            throws SAXException {
+            ElementWrapper currentElement = (tags.size() == 0) ? null
+                                                               : (ElementWrapper) tags.peek();
+
+            if (currentElement != null) {
+                currentElement.text.append(new String(ch, start, len));
+            } else {
+                throw new SAXException("Unexpected text at " + start + " for " +
+                    len);
+            }
+        }
+
+        public void endElement(String uri, String localName, String qname)
+            throws SAXException {
+            ElementWrapper currentElement = (tags.size() == 0) ? null
+                                                               : (ElementWrapper) tags.peek();
+
+            if (currentElement != null) {
+                if (!currentElement.element.equals(qname)) {
+                    throw new SAXException("Unexpected end element found <" +
+                        qname + ">");
+                }
+
+                if (currentElement.element.equals(FILE_ELEMENT)) {
+                    MRUList.this.add(new File(currentElement.text.toString()));
+                }
+
+                tags.pop();
+            }
+        }
+    }
+
+    public class ElementWrapper {
+        String element;
+        StringBuffer text;
+
+        ElementWrapper(String element) {
+            this.element = element;
+            text = new StringBuffer();
+        }
+    }
+}
diff --git a/src/com/sshtools/common/mru/MRUListModel.java b/src/com/sshtools/common/mru/MRUListModel.java
new file mode 100644
index 0000000000000000000000000000000000000000..ae2656bdf2aa38109f4a54b8c3a87fd595e4fd17
--- /dev/null
+++ b/src/com/sshtools/common/mru/MRUListModel.java
@@ -0,0 +1,115 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.mru;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.File;
+
+import javax.swing.AbstractListModel;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class MRUListModel extends AbstractListModel {
+    private static Log log = LogFactory.getLog(MRUListModel.class);
+    private MRUList mru;
+
+    /**
+* Creates a new MRUListModel object.
+*/
+    public MRUListModel() {
+        super();
+        setMRUList(new MRUList());
+    }
+
+    /**
+*
+*
+* @param f
+*/
+    public void add(File f) {
+        mru.insertElementAt(f, 0);
+
+        for (int i = mru.size() - 1; i >= 1; i--) {
+            if (((File) mru.elementAt(i)).equals(f)) {
+                mru.removeElementAt(i);
+            }
+        }
+
+        if (mru.size() > 15) {
+            for (int i = mru.size() - 1; i >= 15; i--) {
+                mru.removeElementAt(i);
+            }
+        }
+
+        fireContentsChanged(this, 0, getSize() - 1);
+    }
+
+    /**
+*
+*
+* @param i
+*
+* @return
+*/
+    public Object getElementAt(int i) {
+        return mru.get(i);
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public int getSize() {
+        return (mru == null) ? 0 : mru.size();
+    }
+
+    /**
+*
+*
+* @param mru
+*/
+    public void setMRUList(MRUList mru) {
+        this.mru = mru;
+        fireContentsChanged(this, 0, getSize());
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public MRUList getMRUList() {
+        return mru;
+    }
+}
diff --git a/src/com/sshtools/common/mru/MRUMenu.java b/src/com/sshtools/common/mru/MRUMenu.java
new file mode 100644
index 0000000000000000000000000000000000000000..addfc68c1dcb03b867921f1fcec77d823c3b9496
--- /dev/null
+++ b/src/com/sshtools/common/mru/MRUMenu.java
@@ -0,0 +1,137 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.mru;
+
+import java.awt.*;
+import java.awt.event.*;
+
+import java.io.*;
+
+import javax.swing.*;
+import javax.swing.event.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class MRUMenu extends JMenu implements ListDataListener, ActionListener {
+    private MRUListModel model;
+
+    /**
+* Creates a new MRUMenu object.
+*
+* @param action
+* @param model
+*/
+    protected MRUMenu(Action action, MRUListModel model) {
+        super(action);
+        init(model);
+    }
+
+    /**
+* Creates a new MRUMenu object.
+*
+* @param text
+* @param model
+*/
+    protected MRUMenu(String text, MRUListModel model) {
+        super(text);
+        init(model);
+    }
+
+    private void init(MRUListModel model) {
+        this.model = model;
+        rebuildMenu();
+        model.addListDataListener(this);
+    }
+
+    /**
+*
+*/
+    public void cleanUp() {
+        model.removeListDataListener(this);
+    }
+
+    /**
+*
+*
+* @param e
+*/
+    public void intervalAdded(ListDataEvent e) {
+        rebuildMenu();
+    }
+
+    /**
+*
+*
+* @param e
+*/
+    public void intervalRemoved(ListDataEvent e) {
+        rebuildMenu();
+    }
+
+    /**
+*
+*
+* @param e
+*/
+    public void contentsChanged(ListDataEvent e) {
+        rebuildMenu();
+    }
+
+    /**
+*
+*
+* @param evt
+*/
+    public void actionPerformed(ActionEvent evt) {
+        fireActionPerformed(evt);
+    }
+
+    private void rebuildMenu() {
+        Component[] c = getMenuComponents();
+
+        for (int i = 0; (c != null) && (i < c.length); i++) {
+            ((JMenuItem) c[i]).removeActionListener(this);
+            remove(c[i]);
+        }
+
+        for (int i = 0; i < model.getSize(); i++) {
+            File f = (File) model.getElementAt(i);
+            JMenuItem m = new JMenuItem(f.getName());
+            m.setActionCommand(f.getAbsolutePath());
+            m.setToolTipText(f.getAbsolutePath());
+            m.addActionListener(this);
+            add(m);
+        }
+
+        setEnabled(model.getSize() > 0);
+        validate();
+    }
+}
diff --git a/src/com/sshtools/common/ui/AboutAction.java b/src/com/sshtools/common/ui/AboutAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..3205c5128e70b59a737655aece8fc5ee61eb9b76
--- /dev/null
+++ b/src/com/sshtools/common/ui/AboutAction.java
@@ -0,0 +1,84 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+
+import javax.swing.Action;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class AboutAction extends StandardAction {
+    private final static String ACTION_COMMAND_KEY_ABOUT = "about-command";
+    private final static String NAME_ABOUT = "About";
+    private final static String SMALL_ICON_ABOUT = "/com/sshtools/common/ui/about.png";
+    private final static String LARGE_ICON_ABOUT = "";
+    private final static int MNEMONIC_KEY_ABOUT = 'A';
+    private SshToolsApplication application;
+    private Component parent;
+
+    /**
+* Creates a new AboutAction object.
+*
+* @param parent
+* @param application
+*/
+    public AboutAction(Component parent, SshToolsApplication application) {
+        this.application = application;
+        this.parent = parent;
+        putValue(Action.NAME, NAME_ABOUT);
+        putValue(Action.SMALL_ICON, getIcon(SMALL_ICON_ABOUT));
+        putValue(LARGE_ICON, getIcon(LARGE_ICON_ABOUT));
+        putValue(Action.SHORT_DESCRIPTION,
+            "About " + application.getApplicationName());
+        putValue(Action.LONG_DESCRIPTION,
+            "Show information about " + application.getApplicationName());
+        putValue(Action.MNEMONIC_KEY, new Integer(MNEMONIC_KEY_ABOUT));
+        putValue(Action.ACTION_COMMAND_KEY, ACTION_COMMAND_KEY_ABOUT);
+        putValue(StandardAction.ON_MENUBAR, new Boolean(true));
+        putValue(StandardAction.MENU_NAME, "Help");
+        putValue(StandardAction.MENU_ITEM_GROUP, new Integer(90));
+        putValue(StandardAction.MENU_ITEM_WEIGHT, new Integer(90));
+        putValue(StandardAction.ON_TOOLBAR, new Boolean(true));
+        putValue(StandardAction.TOOLBAR_GROUP, new Integer(90));
+        putValue(StandardAction.TOOLBAR_WEIGHT, new Integer(10));
+    }
+
+    /**
+*
+*
+* @param evt
+*/
+    public void actionPerformed(ActionEvent evt) {
+        application.showAbout(parent);
+    }
+}
diff --git a/src/com/sshtools/common/ui/BooleanIconRenderer.java b/src/com/sshtools/common/ui/BooleanIconRenderer.java
new file mode 100644
index 0000000000000000000000000000000000000000..bcf8c76818b96286a0d5db0174c11d001d49dcf3
--- /dev/null
+++ b/src/com/sshtools/common/ui/BooleanIconRenderer.java
@@ -0,0 +1,60 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.*;
+
+import javax.swing.*;
+import javax.swing.table.*;
+
+
+public class BooleanIconRenderer extends DefaultTableCellRenderer {
+    //  Private instance variables
+    private Icon trueIcon;
+
+    //  Private instance variables
+    private Icon falseIcon;
+
+    public BooleanIconRenderer(Icon trueIcon, Icon falseIcon) {
+        this.trueIcon = trueIcon;
+        this.falseIcon = falseIcon;
+        setHorizontalAlignment(JLabel.CENTER);
+    }
+
+    public Component getTableCellRendererComponent(JTable table, Object value,
+        boolean isSelected, boolean hasFocus, int row, int column) {
+        super.getTableCellRendererComponent(table, value, isSelected, hasFocus,
+            row, column);
+        setText(null);
+        setIcon(((Boolean) value).booleanValue() ? trueIcon : falseIcon);
+
+        return this;
+    }
+
+    public String getText() {
+        return null;
+    }
+}
diff --git a/src/com/sshtools/common/ui/CloseAction.java b/src/com/sshtools/common/ui/CloseAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..cca95ec10fead275e87bd352e43d6b1b41faf6e9
--- /dev/null
+++ b/src/com/sshtools/common/ui/CloseAction.java
@@ -0,0 +1,70 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.event.KeyEvent;
+
+import javax.swing.Action;
+import javax.swing.KeyStroke;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class CloseAction extends StandardAction {
+    private final static String ACTION_COMMAND_KEY_CLOSE = "close-command";
+    private final static String NAME_CLOSE = "Close";
+    private final static String SMALL_ICON_CLOSE = "/com/sshtools/common/ui/close.png";
+    private final static String LARGE_ICON_CLOSE = "";
+    private final static String SHORT_DESCRIPTION_CLOSE = "Close the current connection";
+    private final static String LONG_DESCRIPTION_CLOSE = "Close the current connection";
+    private final static int MNEMONIC_KEY_CLOSE = 'C';
+
+    /**
+* Creates a new CloseAction object.
+*/
+    public CloseAction() {
+        putValue(Action.NAME, NAME_CLOSE);
+        putValue(Action.SMALL_ICON, getIcon(SMALL_ICON_CLOSE));
+        putValue(LARGE_ICON, getIcon(LARGE_ICON_CLOSE));
+        putValue(Action.ACCELERATOR_KEY,
+            KeyStroke.getKeyStroke(KeyEvent.VK_Q, KeyEvent.ALT_MASK));
+        putValue(Action.SHORT_DESCRIPTION, SHORT_DESCRIPTION_CLOSE);
+        putValue(Action.LONG_DESCRIPTION, LONG_DESCRIPTION_CLOSE);
+        putValue(Action.MNEMONIC_KEY, new Integer(MNEMONIC_KEY_CLOSE));
+        putValue(Action.ACTION_COMMAND_KEY, ACTION_COMMAND_KEY_CLOSE);
+        putValue(StandardAction.ON_MENUBAR, new Boolean(true));
+        putValue(StandardAction.MENU_NAME, "File");
+        putValue(StandardAction.MENU_ITEM_GROUP, new Integer(0));
+        putValue(StandardAction.MENU_ITEM_WEIGHT, new Integer(60));
+        putValue(StandardAction.ON_TOOLBAR, new Boolean(true));
+        putValue(StandardAction.TOOLBAR_GROUP, new Integer(0));
+        putValue(StandardAction.TOOLBAR_WEIGHT, new Integer(10));
+    }
+}
diff --git a/src/com/sshtools/common/ui/ColorComboBox.java b/src/com/sshtools/common/ui/ColorComboBox.java
new file mode 100644
index 0000000000000000000000000000000000000000..d93f0f7acdc18b4ca106909d54c72fac7c0be083
--- /dev/null
+++ b/src/com/sshtools/common/ui/ColorComboBox.java
@@ -0,0 +1,278 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import java.util.Vector;
+
+import javax.swing.AbstractListModel;
+import javax.swing.BorderFactory;
+import javax.swing.ComboBoxModel;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.JColorChooser;
+import javax.swing.JComboBox;
+import javax.swing.JList;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class ColorComboBox extends JComboBox {
+    /**
+* Creates a new ColorComboBox object.
+*/
+    public ColorComboBox() {
+        this(null);
+    }
+
+    /**
+* Creates a new ColorComboBox object.
+*
+* @param color
+*/
+    public ColorComboBox(Color color) {
+        super(new ColorComboModel());
+        setColor(color);
+        setRenderer(new ColorRenderer());
+        addActionListener(new ActionListener() {
+                public void actionPerformed(ActionEvent evt) {
+                    if (getSelectedItem() == null) {
+                        chooseCustomColor();
+                    } else {
+                        fireChangeEvent();
+                    }
+                }
+            });
+    }
+
+    /**
+*
+*/
+    protected void fireChangeEvent() {
+        ChangeEvent evt = new ChangeEvent(this);
+        ChangeListener[] l = (ChangeListener[]) listenerList.getListeners(ChangeListener.class);
+
+        for (int i = (l.length - 1); i >= 0; i--) {
+            l[i].stateChanged(evt);
+        }
+    }
+
+    /**
+*
+*
+* @param l
+*/
+    public void addChangeListener(ChangeListener l) {
+        listenerList.add(ChangeListener.class, l);
+    }
+
+    /**
+*
+*
+* @param l
+*/
+    public void removeChangeListener(ChangeListener l) {
+        listenerList.remove(ChangeListener.class, l);
+    }
+
+    private void chooseCustomColor() {
+        Color c = JColorChooser.showDialog(this, "Custom Color", Color.black);
+
+        if (c != null) {
+            setColor(c);
+            fireChangeEvent();
+        }
+    }
+
+    /**
+*
+*
+* @param c
+*/
+    public void setColor(Color c) {
+        for (int i = 0; i < (getModel().getSize() - 1); i++) {
+            Color z = (Color) getModel().getElementAt(i);
+
+            if (z.equals(c)) {
+                setSelectedIndex(i);
+
+                return;
+            }
+        }
+
+        if (c != null) {
+            ((ColorComboModel) getModel()).addColor(c);
+        }
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public Color getColor() {
+        return (Color) getSelectedItem();
+    }
+
+    //  Supporting classes
+    static class ColorComboModel extends AbstractListModel
+        implements ComboBoxModel {
+        private Vector colors = new Vector();
+        private Object selected;
+
+        ColorComboModel() {
+            colors = new Vector();
+
+            //  Add the initial colors
+            colors.addElement(Color.black);
+            colors.addElement(Color.white);
+            colors.addElement(Color.red.darker());
+            colors.addElement(Color.red);
+            colors.addElement(Color.orange.darker());
+            colors.addElement(Color.orange);
+            colors.addElement(Color.yellow.darker());
+            colors.addElement(Color.yellow);
+            colors.addElement(Color.green.darker());
+            colors.addElement(Color.green);
+            colors.addElement(Color.blue.darker());
+            colors.addElement(Color.blue);
+            colors.addElement(Color.cyan.darker());
+            colors.addElement(Color.cyan);
+            colors.addElement(Color.magenta.darker());
+            colors.addElement(Color.magenta);
+            colors.addElement(Color.pink.darker());
+            colors.addElement(Color.pink);
+            colors.addElement(Color.lightGray);
+            colors.addElement(Color.gray);
+            colors.addElement(Color.darkGray);
+
+            //  Black is initialy selected
+            selected = colors.elementAt(0);
+        }
+
+        public int getSize() {
+            return colors.size() + 1;
+        }
+
+        public Object getElementAt(int i) {
+            if (i == colors.size()) {
+                return null;
+            } else {
+                return colors.elementAt(i);
+            }
+        }
+
+        public void setSelectedItem(Object sel) {
+            selected = sel;
+        }
+
+        public Object getSelectedItem() {
+            return selected;
+        }
+
+        public void addColor(Color c) {
+            int idx = colors.size();
+            colors.addElement(c);
+            selected = c;
+            fireIntervalAdded(this, idx, idx);
+        }
+    }
+
+    class ColorRenderer extends DefaultListCellRenderer {
+        private ColorIcon icon;
+
+        ColorRenderer() {
+            icon = new ColorIcon(Color.black, new Dimension(10, 10), Color.black);
+            setBorder(BorderFactory.createEmptyBorder(0, 16, 0, 0));
+        }
+
+        public Component getListCellRendererComponent(JList list, Object value,
+            int index, boolean isSelected, boolean cellHasFocus) {
+            super.getListCellRendererComponent(list, value, index, isSelected,
+                cellHasFocus);
+
+            Color c = (Color) value;
+
+            //  If the value is null. Then this signifies custom color
+            if (c == null) {
+                setIcon(javax.swing.plaf.basic.BasicIconFactory.getCheckBoxIcon());
+                setText("Custom ....");
+            } else {
+                //  Set up the icon
+                icon.setColor(c);
+                setIcon(icon);
+
+                //  Set the text. If the color is a well known one with a name, render
+                //  the name. Otherwise use the RGB values
+                String s = "#" + c.getRed() + "," + c.getGreen() + "," +
+                    c.getBlue();
+
+                if (c.equals(Color.black)) {
+                    s = "Black";
+                } else if (c.equals(Color.white)) {
+                    s = "White";
+                } else if (c.equals(Color.red)) {
+                    s = "Red";
+                } else if (c.equals(Color.orange)) {
+                    s = "Orange";
+                } else if (c.equals(Color.yellow)) {
+                    s = "Yellow";
+                } else if (c.equals(Color.green)) {
+                    s = "Green";
+                } else if (c.equals(Color.blue)) {
+                    s = "Blue";
+                } else if (c.equals(Color.cyan)) {
+                    s = "Cyan";
+                } else if (c.equals(Color.magenta)) {
+                    s = "Magenta";
+                } else if (c.equals(Color.pink)) {
+                    s = "Pink";
+                } else if (c.equals(Color.lightGray)) {
+                    s = "Light Gray";
+                } else if (c.equals(Color.gray)) {
+                    s = "Gray";
+                } else if (c.equals(Color.darkGray)) {
+                    s = "Dark Gray";
+                }
+
+                setText(s);
+            }
+
+            //
+            return this;
+        }
+    }
+}
diff --git a/src/com/sshtools/common/ui/ColorIcon.java b/src/com/sshtools/common/ui/ColorIcon.java
new file mode 100644
index 0000000000000000000000000000000000000000..d8c5d726032b23062784ca92ba194c301873be4d
--- /dev/null
+++ b/src/com/sshtools/common/ui/ColorIcon.java
@@ -0,0 +1,146 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.*;
+
+import javax.swing.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class ColorIcon implements Icon {
+    //  Private instance variables
+    private Dimension size;
+    private Color color;
+    private Color borderColor;
+
+    /**
+* Creates a new ColorIcon object.
+*/
+    public ColorIcon() {
+        this(null);
+    }
+
+    /**
+* Creates a new ColorIcon object.
+*
+* @param color
+*/
+    public ColorIcon(Color color) {
+        this(color, null);
+    }
+
+    /**
+* Creates a new ColorIcon object.
+*
+* @param color
+* @param borderColor
+*/
+    public ColorIcon(Color color, Color borderColor) {
+        this(color, null, borderColor);
+    }
+
+    /**
+* Creates a new ColorIcon object.
+*
+* @param color
+* @param size
+* @param borderColor
+*/
+    public ColorIcon(Color color, Dimension size, Color borderColor) {
+        setColor(color);
+        setSize(size);
+        setBorderColor(borderColor);
+    }
+
+    /**
+*
+*
+* @param c
+* @param g
+* @param x
+* @param y
+*/
+    public void paintIcon(Component c, Graphics g, int x, int y) {
+        g.setColor((color == null) ? Color.black : color);
+        g.fillRect(x, y, getIconWidth(), getIconHeight());
+
+        if (borderColor != null) {
+            g.setColor(borderColor);
+            g.drawRect(x, y, getIconWidth(), getIconHeight());
+        }
+    }
+
+    /**
+*
+*
+* @param size
+*/
+    public void setSize(Dimension size) {
+        this.size = size;
+    }
+
+    /**
+*
+*
+* @param color
+*/
+    public void setColor(Color color) {
+        this.color = color;
+    }
+
+    /**
+*
+*
+* @param borderColor
+*/
+    public void setBorderColor(Color borderColor) {
+        this.borderColor = borderColor;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public int getIconWidth() {
+        return (size == null) ? 16 : size.width;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public int getIconHeight() {
+        return (size == null) ? 16 : size.height;
+    }
+}
diff --git a/src/com/sshtools/common/ui/ConnectionPropertiesAction.java b/src/com/sshtools/common/ui/ConnectionPropertiesAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..cf6099167b91371f33e6c8980719f5fe0c07e468
--- /dev/null
+++ b/src/com/sshtools/common/ui/ConnectionPropertiesAction.java
@@ -0,0 +1,63 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.event.KeyEvent;
+
+import javax.swing.Action;
+import javax.swing.KeyStroke;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public abstract class ConnectionPropertiesAction extends StandardAction {
+    /**
+* Creates a new ConnectionPropertiesAction object.
+*/
+    public ConnectionPropertiesAction() {
+        putValue(Action.NAME, "Connection Settings");
+        putValue(Action.SMALL_ICON,
+            getIcon("/com/sshtools/common/ui/properties.png"));
+        putValue(Action.SHORT_DESCRIPTION, "Connection settings");
+        putValue(Action.LONG_DESCRIPTION,
+            "Change the current connecting settings");
+        putValue(Action.MNEMONIC_KEY, new Integer('t'));
+        putValue(Action.ACCELERATOR_KEY,
+            KeyStroke.getKeyStroke(KeyEvent.VK_T, KeyEvent.ALT_MASK));
+        putValue(Action.ACTION_COMMAND_KEY, "connect-properties-command");
+        putValue(StandardAction.ON_MENUBAR, new Boolean(true));
+        putValue(StandardAction.MENU_NAME, "Edit");
+        putValue(StandardAction.MENU_ITEM_GROUP, new Integer(80));
+        putValue(StandardAction.MENU_ITEM_WEIGHT, new Integer(10));
+        putValue(StandardAction.ON_TOOLBAR, new Boolean(true));
+        putValue(StandardAction.TOOLBAR_GROUP, new Integer(15));
+        putValue(StandardAction.TOOLBAR_WEIGHT, new Integer(10));
+    }
+}
diff --git a/src/com/sshtools/common/ui/DataNotificationListener.java b/src/com/sshtools/common/ui/DataNotificationListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..ebd0ad7093c411302ac09850932c4fc6207062d7
--- /dev/null
+++ b/src/com/sshtools/common/ui/DataNotificationListener.java
@@ -0,0 +1,80 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import com.sshtools.j2ssh.connection.Channel;
+import com.sshtools.j2ssh.connection.ChannelEventAdapter;
+import com.sshtools.j2ssh.connection.ChannelEventListener;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.Timer;
+
+
+public class DataNotificationListener extends ChannelEventAdapter {
+    Timer timerReceiving;
+    Timer timerSending;
+    StatusBar bar;
+
+    public DataNotificationListener(StatusBar bar) {
+        this.bar = bar;
+        timerReceiving = new Timer(500, new ReceivingActionListener());
+        timerReceiving.setRepeats(false);
+        timerSending = new Timer(500, new SendingActionListener());
+        timerSending.setRepeats(false);
+    }
+
+    public void actionPerformed(ActionEvent evt) {
+        bar.setReceiving(false);
+    }
+
+    public void onDataReceived(Channel channel, byte[] data) {
+        if (!timerReceiving.isRunning()) {
+            bar.setReceiving(true);
+            timerReceiving.start();
+        }
+    }
+
+    public void onDataSent(Channel channel, byte[] data) {
+        if (!timerSending.isRunning()) {
+            bar.setSending(true);
+            timerSending.start();
+        }
+    }
+
+    class SendingActionListener implements ActionListener {
+        public void actionPerformed(ActionEvent evt) {
+            bar.setSending(false);
+        }
+    }
+
+    class ReceivingActionListener implements ActionListener {
+        public void actionPerformed(ActionEvent evt) {
+            bar.setReceiving(false);
+        }
+    }
+}
diff --git a/src/com/sshtools/common/ui/EditAction.java b/src/com/sshtools/common/ui/EditAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..63686ece7e545988728b55f55f1288fe97216bf9
--- /dev/null
+++ b/src/com/sshtools/common/ui/EditAction.java
@@ -0,0 +1,55 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import javax.swing.Action;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class EditAction extends StandardAction {
+    /**
+* Creates a new EditAction object.
+*/
+    public EditAction() {
+        putValue(Action.NAME, "Edit");
+        putValue(Action.SMALL_ICON,
+            getIcon("/com/sshtools/common/ui/fileedit.png"));
+        putValue(Action.SHORT_DESCRIPTION, "Edit connection file");
+        putValue(Action.LONG_DESCRIPTION, "Edit connection file");
+        putValue(Action.MNEMONIC_KEY, new Integer('e'));
+        putValue(Action.ACTION_COMMAND_KEY, "edit-command");
+        putValue(StandardAction.ON_MENUBAR, new Boolean(true));
+        putValue(StandardAction.MENU_NAME, "File");
+        putValue(StandardAction.MENU_ITEM_GROUP, new Integer(0));
+        putValue(StandardAction.MENU_ITEM_WEIGHT, new Integer(6));
+        putValue(StandardAction.ON_TOOLBAR, new Boolean(false));
+    }
+}
diff --git a/src/com/sshtools/common/ui/EmptyIcon.java b/src/com/sshtools/common/ui/EmptyIcon.java
new file mode 100644
index 0000000000000000000000000000000000000000..59bbc62494c3c737241dd785bebdaa5c0d32c1c1
--- /dev/null
+++ b/src/com/sshtools/common/ui/EmptyIcon.java
@@ -0,0 +1,82 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.*;
+
+import javax.swing.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class EmptyIcon implements Icon {
+    private int w;
+    private int h;
+
+    /**
+* Creates a new EmptyIcon object.
+*
+* @param w
+* @param h
+*/
+    public EmptyIcon(int w, int h) {
+        this.w = w;
+        this.h = h;
+    }
+
+    /**
+*
+*
+* @param c
+* @param g
+* @param x
+* @param y
+*/
+    public void paintIcon(Component c, Graphics g, int x, int y) {
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public int getIconWidth() {
+        return w;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public int getIconHeight() {
+        return h;
+    }
+}
diff --git a/src/com/sshtools/common/ui/ExitAction.java b/src/com/sshtools/common/ui/ExitAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..846880697ebcfed99b91cc815b2a667ac909fd4b
--- /dev/null
+++ b/src/com/sshtools/common/ui/ExitAction.java
@@ -0,0 +1,78 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+
+import javax.swing.Action;
+import javax.swing.KeyStroke;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class ExitAction extends StandardAction {
+    //
+    private SshToolsApplication application;
+    private SshToolsApplicationContainer container;
+
+    /**
+* Creates a new ExitAction object.
+*
+* @param application
+* @param container
+*/
+    public ExitAction(SshToolsApplication application,
+        SshToolsApplicationContainer container) {
+        this.application = application;
+        this.container = container;
+        putValue(Action.NAME, "Exit");
+        putValue(Action.SMALL_ICON, getIcon("exit.png"));
+        putValue(Action.SHORT_DESCRIPTION, "Exit");
+        putValue(Action.ACCELERATOR_KEY,
+            KeyStroke.getKeyStroke(KeyEvent.VK_X, KeyEvent.ALT_MASK));
+        putValue(Action.LONG_DESCRIPTION, "Exit this window");
+        putValue(Action.MNEMONIC_KEY, new Integer('x'));
+        putValue(StandardAction.ON_MENUBAR, new Boolean(true));
+        putValue(StandardAction.MENU_NAME, "File");
+        putValue(StandardAction.MENU_ITEM_GROUP, new Integer(90));
+        putValue(StandardAction.MENU_ITEM_WEIGHT, new Integer(90));
+        putValue(StandardAction.ON_TOOLBAR, new Boolean(false));
+    }
+
+    /**
+*
+*
+* @param evt
+*/
+    public void actionPerformed(ActionEvent evt) {
+        application.closeContainer(container);
+    }
+}
diff --git a/src/com/sshtools/common/ui/FolderBar.java b/src/com/sshtools/common/ui/FolderBar.java
new file mode 100644
index 0000000000000000000000000000000000000000..510b0d6c28dbc1d883c9a7dd6c1c50b17decfe49
--- /dev/null
+++ b/src/com/sshtools/common/ui/FolderBar.java
@@ -0,0 +1,81 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.*;
+
+import javax.swing.*;
+
+
+public class FolderBar extends JPanel {
+    //  Private instance variables
+    private JLabel textLabel;
+
+    //  Private instance variables
+    private JLabel iconLabel;
+    private Action action;
+
+    public FolderBar() {
+        this(null, null);
+    }
+
+    public FolderBar(String text) {
+        this(text, null);
+    }
+
+    public FolderBar(String text, Icon icon) {
+        super(new BorderLayout());
+        setOpaque(true);
+        setBackground(getBackground().darker());
+        add(textLabel = new JLabel(), BorderLayout.CENTER);
+        add(iconLabel = new JLabel(), BorderLayout.WEST);
+        iconLabel.setFont(iconLabel.getFont().deriveFont(Font.BOLD));
+        textLabel.setVerticalAlignment(JLabel.CENTER);
+        textLabel.setVerticalTextPosition(JLabel.BOTTOM);
+        textLabel.setForeground(Color.lightGray);
+        iconLabel.setVerticalAlignment(JLabel.CENTER);
+        setIcon(icon);
+        setText(text);
+    }
+
+    public Action getAction() {
+        return action;
+    }
+
+    public void setAction(Action action) {
+        this.action = action;
+        setIcon((Icon) action.getValue(Action.SMALL_ICON));
+        setText((String) action.getValue(Action.NAME));
+    }
+
+    public void setText(String text) {
+        textLabel.setText(text);
+    }
+
+    public void setIcon(Icon icon) {
+        iconLabel.setIcon(icon);
+    }
+}
diff --git a/src/com/sshtools/common/ui/GlobalOptionsTab.java b/src/com/sshtools/common/ui/GlobalOptionsTab.java
new file mode 100644
index 0000000000000000000000000000000000000000..3e48d87a841b5d0eb623a5261ffb4cf76e1713ba
--- /dev/null
+++ b/src/com/sshtools/common/ui/GlobalOptionsTab.java
@@ -0,0 +1,194 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+
+import javax.swing.BorderFactory;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.Icon;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.UIManager;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class GlobalOptionsTab extends JPanel implements OptionsTab {
+    /**  */
+    public final static String GLOBAL_ICON = "largeglobal.png";
+
+    //
+    private JComboBox lafChooser;
+
+    /**
+* Creates a new GlobalOptionsTab object.
+*/
+    public GlobalOptionsTab() {
+        super();
+
+        Insets ins = new Insets(2, 2, 2, 2);
+        JPanel s = new JPanel(new GridBagLayout());
+        GridBagConstraints gbc1 = new GridBagConstraints();
+        gbc1.weighty = 1.0;
+        gbc1.insets = ins;
+        gbc1.anchor = GridBagConstraints.NORTHWEST;
+        gbc1.fill = GridBagConstraints.HORIZONTAL;
+        gbc1.weightx = 0.0;
+        UIUtil.jGridBagAdd(s, new JLabel("Look and feel"), gbc1,
+            GridBagConstraints.RELATIVE);
+        gbc1.weightx = 1.0;
+        UIUtil.jGridBagAdd(s,
+            lafChooser = new JComboBox(
+                    SshToolsApplication.getAllLookAndFeelInfo()), gbc1,
+            GridBagConstraints.REMAINDER);
+        lafChooser.setRenderer(new LAFRenderer());
+
+        IconWrapperPanel w = new IconWrapperPanel(new ResourceIcon(
+                    GlobalOptionsTab.class, GLOBAL_ICON), s);
+
+        //  This tab
+        setLayout(new BorderLayout());
+        add(w, BorderLayout.CENTER);
+        setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+        reset();
+    }
+
+    /**
+*
+*/
+    public void reset() {
+        String sel = PreferencesStore.get(SshToolsApplication.PREF_LAF,
+                UIManager.getLookAndFeel().getClass().getName());
+
+        for (int i = 0; i < lafChooser.getModel().getSize(); i++) {
+            if (((UIManager.LookAndFeelInfo) lafChooser.getModel().getElementAt(i)).getClassName()
+                     .equals(sel)) {
+                lafChooser.setSelectedIndex(i);
+
+                break;
+            }
+        }
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getTabContext() {
+        return "Options";
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public Icon getTabIcon() {
+        return null;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getTabTitle() {
+        return "Global";
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getTabToolTipText() {
+        return "Global options.";
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public int getTabMnemonic() {
+        return 'g';
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public Component getTabComponent() {
+        return this;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public boolean validateTab() {
+        return true;
+    }
+
+    /**
+*
+*/
+    public void applyTab() {
+        PreferencesStore.put(SshToolsApplication.PREF_LAF,
+            ((UIManager.LookAndFeelInfo) lafChooser.getSelectedItem()).getClassName());
+    }
+
+    /**
+*
+*/
+    public void tabSelected() {
+    }
+
+    class LAFRenderer extends DefaultListCellRenderer {
+        public Component getListCellRendererComponent(JList list, Object value,
+            int index, boolean isSelected, boolean cellHasFocus) {
+            super.getListCellRendererComponent(list, value, index, isSelected,
+                cellHasFocus);
+            setText(((UIManager.LookAndFeelInfo) value).getName());
+
+            return this;
+        }
+    }
+}
diff --git a/src/com/sshtools/common/ui/HostsTab.java b/src/com/sshtools/common/ui/HostsTab.java
new file mode 100644
index 0000000000000000000000000000000000000000..016cc613125752d7a36b2800255d9b94f69627d4
--- /dev/null
+++ b/src/com/sshtools/common/ui/HostsTab.java
@@ -0,0 +1,394 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import com.sshtools.j2ssh.transport.AbstractKnownHostsKeyVerification;
+import com.sshtools.j2ssh.transport.InvalidHostFileException;
+import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.swing.AbstractListModel;
+import javax.swing.BorderFactory;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.Icon;
+import javax.swing.JButton;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class HostsTab extends JPanel implements OptionsTab, ActionListener {
+    /**  */
+    public final static String GLOBAL_ICON = "/com/sshtools/common/ui/largeserveridentity.png";
+
+    /**  */
+    public final static String ALLOW_ICON = "/com/sshtools/common/ui/ok.png";
+
+    /**  */
+    public final static String DENY_ICON = "/com/sshtools/common/ui/cancel.png";
+
+    /**  */
+    public final static String REMOVE_ICON = "/com/sshtools/common/ui/remove.png";
+    private static Log log = LogFactory.getLog(HostsTab.class);
+
+    //
+    private JList hosts;
+    private AbstractKnownHostsKeyVerification hostKeyVerifier;
+    private JButton remove;
+
+    //private JButton deny;
+    private HostsListModel model;
+
+    /**
+* Creates a new HostsTab object.
+*
+* @param hostKeyVerifier
+*/
+    public HostsTab(AbstractKnownHostsKeyVerification hostKeyVerifier) {
+        super();
+        this.hostKeyVerifier = hostKeyVerifier;
+        hosts = new JList(model = new HostsListModel());
+        hosts.setVisibleRowCount(10);
+        hosts.setCellRenderer(new HostRenderer());
+        hosts.addListSelectionListener(new ListSelectionListener() {
+                public void valueChanged(ListSelectionEvent evt) {
+                    setAvailableActions();
+                }
+            });
+        remove = new JButton("Remove", new ResourceIcon(REMOVE_ICON));
+        remove.addActionListener(this);
+
+        //deny = new JButton("Deny", new ResourceIcon(DENY_ICON));
+        //deny.addActionListener(this);
+        JPanel b = new JPanel(new GridBagLayout());
+        b.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0));
+
+        GridBagConstraints gbc = new GridBagConstraints();
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        gbc.insets = new Insets(0, 0, 4, 0);
+        gbc.anchor = GridBagConstraints.NORTH;
+        gbc.weightx = 1.0;
+        UIUtil.jGridBagAdd(b, remove, gbc, GridBagConstraints.REMAINDER);
+        gbc.weighty = 1.0;
+
+        //UIUtil.jGridBagAdd(b, deny, gbc, GridBagConstraints.REMAINDER);
+        JPanel s = new JPanel(new BorderLayout());
+        s.add(new JScrollPane(hosts), BorderLayout.CENTER);
+        s.add(b, BorderLayout.EAST);
+
+        IconWrapperPanel w = new IconWrapperPanel(new ResourceIcon(GLOBAL_ICON),
+                s);
+
+        //  This tab
+        setLayout(new BorderLayout());
+        add(w, BorderLayout.CENTER);
+        setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+        reset();
+    }
+
+    /**
+*
+*
+* @param evt
+*/
+    public void actionPerformed(ActionEvent evt) {
+        if (evt.getSource() == remove) {
+            model.removeHostAt(hosts.getSelectedIndex());
+        }
+
+        /*else if (evt.getSource() == deny) {
+int i = hosts.getSelectedIndex();
+HostWrapper w = model.getHostAt(i);
+w.allow = false;
+model.updateHostAt(i);
+ }*/
+        setAvailableActions();
+    }
+
+    private void setAvailableActions() {
+        HostWrapper w = ((model.getSize() > 0) &&
+            (hosts.getSelectedValues().length == 1))
+            ? model.getHostAt(hosts.getSelectedIndex()) : null;
+        remove.setEnabled(w != null);
+
+        //deny.setEnabled( (w != null) && w.allow);
+    }
+
+    /**
+*
+*/
+    public void reset() {
+        ((HostsListModel) hosts.getModel()).refresh();
+        setAvailableActions();
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getTabContext() {
+        return "Options";
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public Icon getTabIcon() {
+        return null;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getTabTitle() {
+        return "Hosts";
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getTabToolTipText() {
+        return "Allowed and denied hosts.";
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public int getTabMnemonic() {
+        return 'h';
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public Component getTabComponent() {
+        return this;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public boolean validateTab() {
+        return true;
+    }
+
+    /**
+*
+*/
+    public void applyTab() {
+        try {
+            Map map = hostKeyVerifier.allowedHosts();
+            String[] hosts = new String[map.keySet().size()];
+            map.keySet().toArray(hosts);
+            log.debug("Checking if any allowed hosts need to be removed");
+
+            for (int i = hosts.length - 1; i >= 0; i--) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Looking for host " + hosts[i]);
+                }
+
+                HostWrapper w = model.getHost(hosts[i]);
+
+                if (w != null) {
+                    if (log.isDebugEnabled()) {
+                        log.debug("Found host " + hosts[i]);
+                    }
+
+                    if (!w.allow) {
+                        if (log.isDebugEnabled()) {
+                            log.debug("Denying host " + hosts[i]);
+                        }
+
+                        hostKeyVerifier.removeAllowedHost(hosts[i]);
+
+                        //hostKeyVerifier.denyHost(hosts[i], true);
+                    }
+                } else {
+                    if (log.isDebugEnabled()) {
+                        log.debug("Host removed " + hosts[i]);
+                    }
+
+                    hostKeyVerifier.removeAllowedHost(hosts[i]);
+                }
+            }
+
+            /*java.util.List list = hostKeyVerifier.deniedHosts();
+log.debug("Checking if any denied hosts need to be removed");
+ for (int i = list.size() - 1; i >= 0; i--) {
+String h = (String) list.get(i);
+if (log.isDebugEnabled()) {
+log.debug("Looking for host " + h);
+}
+HostWrapper w = model.getHost(h);
+if (w == null) {
+if (log.isDebugEnabled()) {
+log.debug("Removing host " + h);
+}
+hostKeyVerifier.removeDeniedHost(h);
+}
+ }*/
+            hostKeyVerifier.saveHostFile();
+        } catch (InvalidHostFileException ihfe) {
+            log.error("Failed to store hosts file.", ihfe);
+        }
+    }
+
+    /**
+*
+*/
+    public void tabSelected() {
+    }
+
+    class HostRenderer extends DefaultListCellRenderer {
+        Icon allowIcon;
+        Icon denyIcon;
+
+        public HostRenderer() {
+            allowIcon = new ResourceIcon(ALLOW_ICON);
+            denyIcon = new ResourceIcon(DENY_ICON);
+        }
+
+        public Component getListCellRendererComponent(JList list, Object value,
+            int index, boolean isSelected, boolean cellHasFocus) {
+            super.getListCellRendererComponent(list, value, index, isSelected,
+                cellHasFocus);
+
+            HostWrapper w = (HostWrapper) value;
+            setIcon(w.allow ? allowIcon : denyIcon);
+            setText(w.host);
+
+            return this;
+        }
+    }
+
+    class HostWrapper {
+        boolean allow;
+        String host;
+        SshPublicKey key;
+        Map keys;
+
+        HostWrapper(boolean allow, String host, Map keys) {
+            this.allow = allow;
+            this.host = host;
+            this.keys = keys;
+        }
+    }
+
+    class HostsListModel extends AbstractListModel {
+        java.util.List hosts;
+
+        public HostsListModel() {
+            hosts = new java.util.ArrayList();
+            refresh();
+        }
+
+        public void refresh() {
+            hosts.clear();
+
+            Map map = hostKeyVerifier.allowedHosts();
+
+            for (Iterator i = map.keySet().iterator(); i.hasNext();) {
+                String k = (String) i.next();
+                Map keys = (Map) map.get(k);
+                hosts.add(new HostWrapper(true, k, keys));
+            }
+
+            /* java.util.List list = hostKeyVerifier.deniedHosts();
+for (Iterator i = list.iterator(); i.hasNext(); ) {
+String h = (String) i.next();
+hosts.add(new HostWrapper(false, h, null));
+}*/
+            fireContentsChanged(this, 0, getSize() - 1);
+        }
+
+        public HostWrapper getHost(String name) {
+            for (Iterator i = hosts.iterator(); i.hasNext();) {
+                HostWrapper w = (HostWrapper) i.next();
+
+                if (w.host.equals(name)) {
+                    return w;
+                }
+            }
+
+            return null;
+        }
+
+        public int getSize() {
+            return hosts.size();
+        }
+
+        public Object getElementAt(int index) {
+            return hosts.get(index);
+        }
+
+        public HostWrapper getHostAt(int index) {
+            return (HostWrapper) hosts.get(index);
+        }
+
+        public void removeHostAt(int index) {
+            hosts.remove(index);
+            fireIntervalRemoved(this, index, index);
+        }
+
+        public void updateHostAt(int index) {
+            fireContentsChanged(this, index, index);
+        }
+    }
+}
diff --git a/src/com/sshtools/common/ui/IconWrapperPanel.java b/src/com/sshtools/common/ui/IconWrapperPanel.java
new file mode 100644
index 0000000000000000000000000000000000000000..833e281f2f6f1d07e9e179572d74762c76c4c5a7
--- /dev/null
+++ b/src/com/sshtools/common/ui/IconWrapperPanel.java
@@ -0,0 +1,58 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.*;
+
+import javax.swing.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class IconWrapperPanel extends JPanel {
+    /**
+* Creates a new IconWrapperPanel object.
+*
+* @param icon
+* @param component
+*/
+    public IconWrapperPanel(Icon icon, Component component) {
+        super(new BorderLayout());
+
+        //  Create the west panel with the icon in it
+        JPanel westPanel = new JPanel(new BorderLayout());
+        westPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 4));
+        westPanel.add(new JLabel(icon), BorderLayout.NORTH);
+
+        //  Build this panel
+        add(westPanel, BorderLayout.WEST);
+        add(component, BorderLayout.CENTER);
+    }
+}
diff --git a/src/com/sshtools/common/ui/ImagePanel.java b/src/com/sshtools/common/ui/ImagePanel.java
new file mode 100644
index 0000000000000000000000000000000000000000..b1bb574106687c8f6e941c75dc6a72aa1f49a4ae
--- /dev/null
+++ b/src/com/sshtools/common/ui/ImagePanel.java
@@ -0,0 +1,126 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Insets;
+
+import javax.swing.JPanel;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.17 $
+ */
+public class ImagePanel extends JPanel {
+    private ResourceIcon icon;
+    private boolean alignBottomRight = false;
+
+    /**
+* Creates a new ImagePanel object.
+*
+* @param imageName
+*/
+    public ImagePanel(String imageName) {
+        icon = new ResourceIcon(imageName);
+    }
+
+    /**
+* Creates a new ImagePanel object.
+*
+* @param icon
+* @param bottom
+*/
+    public ImagePanel(ResourceIcon icon, int bottom) {
+        this.icon = icon;
+        alignBottomRight = true;
+    }
+
+    /**
+* Creates a new ImagePanel object.
+*
+* @param imageName
+* @param bottom
+*/
+    public ImagePanel(String imageName, int bottom) {
+        icon = new ResourceIcon(imageName);
+        alignBottomRight = true;
+    }
+
+    /**
+* Creates a new ImagePanel object.
+*/
+    public ImagePanel() {
+        try {
+            jbInit();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public Dimension getPreferedSize() {
+        Insets insets = getInsets();
+
+        return new Dimension(icon.getIconWidth() + insets.left + insets.right,
+            icon.getIconHeight() + insets.top + insets.bottom);
+    }
+
+    /**
+*
+*
+* @param g
+*/
+    public void paintComponent(Graphics g) {
+        super.paintComponent(g);
+
+        if (icon != null) {
+            Insets insets = getInsets();
+
+            if (!alignBottomRight) {
+                // Paint the image at the top left hand side of the panel
+                icon.paintIcon(this, g, insets.left, insets.top);
+            } else {
+                // Paint the image at the bottom right hand side of the panel
+                icon.paintIcon(this, g,
+                    (this.getWidth() - icon.getIconWidth()),
+                    (this.getHeight() - icon.getIconHeight()));
+            }
+        }
+    }
+
+    private void jbInit() throws Exception {
+        this.setBackground(Color.white);
+    }
+}
diff --git a/src/com/sshtools/common/ui/MenuAction.java b/src/com/sshtools/common/ui/MenuAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..47e6cf29a80828079762cd3edd62cb3281aaecf0
--- /dev/null
+++ b/src/com/sshtools/common/ui/MenuAction.java
@@ -0,0 +1,38 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public abstract class MenuAction extends StandardAction {
+    /**  */
+    public final static String MENU = "menu";
+}
diff --git a/src/com/sshtools/common/ui/MultilineLabel.java b/src/com/sshtools/common/ui/MultilineLabel.java
new file mode 100644
index 0000000000000000000000000000000000000000..44d1bca560828d84f65dfac35bf823f22c114471
--- /dev/null
+++ b/src/com/sshtools/common/ui/MultilineLabel.java
@@ -0,0 +1,118 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+
+import java.util.StringTokenizer;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class MultilineLabel extends JPanel {
+    //  Private instance variables
+    private GridBagConstraints constraints;
+    private String text;
+
+    /**
+* Creates a new MultilineLabel object.
+*/
+    public MultilineLabel() {
+        this("");
+    }
+
+    /**
+* Creates a new MultilineLabel object.
+*
+* @param text
+*/
+    public MultilineLabel(String text) {
+        super(new GridBagLayout());
+        constraints = new GridBagConstraints();
+        constraints.anchor = GridBagConstraints.NORTHWEST;
+        constraints.fill = GridBagConstraints.NONE;
+        setText(text);
+    }
+
+    /**
+*
+*
+* @param f
+*/
+    public void setFont(Font f) {
+        super.setFont(f);
+
+        for (int i = 0; i < getComponentCount(); i++) {
+            getComponent(i).setFont(f);
+        }
+    }
+
+    /**
+*
+*
+* @param text
+*/
+    public void setText(String text) {
+        this.text = text;
+        removeAll();
+
+        StringTokenizer tok = new StringTokenizer(text, "\n");
+        constraints.weighty = 0.0;
+        constraints.weightx = 1.0;
+
+        while (tok.hasMoreTokens()) {
+            String t = tok.nextToken();
+
+            if (!tok.hasMoreTokens()) {
+                constraints.weighty = 1.0;
+            }
+
+            UIUtil.jGridBagAdd(this, new JLabel(t), constraints,
+                GridBagConstraints.REMAINDER);
+        }
+
+        revalidate();
+        repaint();
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getText() {
+        return text;
+    }
+}
diff --git a/src/com/sshtools/common/ui/NewAction.java b/src/com/sshtools/common/ui/NewAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..a656afe600dd131008715fd36c4a07976e462835
--- /dev/null
+++ b/src/com/sshtools/common/ui/NewAction.java
@@ -0,0 +1,70 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.event.KeyEvent;
+
+import javax.swing.Action;
+import javax.swing.KeyStroke;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class NewAction extends StandardAction {
+    private final static String ACTION_COMMAND_KEY_NEW = "new-command";
+    private final static String NAME_NEW = "New Connection";
+    private final static String SMALL_ICON_NEW = "/com/sshtools/common/ui/newconnect.png";
+    private final static String LARGE_ICON_NEW = "";
+    private final static String SHORT_DESCRIPTION_NEW = "Create a new connection";
+    private final static String LONG_DESCRIPTION_NEW = "Create a new SSH connection";
+    private final static int MNEMONIC_KEY_NEW = 'C';
+
+    /**
+* Creates a new NewAction object.
+*/
+    public NewAction() {
+        putValue(Action.NAME, NAME_NEW);
+        putValue(Action.SMALL_ICON, getIcon(SMALL_ICON_NEW));
+        putValue(LARGE_ICON, getIcon(LARGE_ICON_NEW));
+        putValue(Action.SHORT_DESCRIPTION, SHORT_DESCRIPTION_NEW);
+        putValue(Action.LONG_DESCRIPTION, LONG_DESCRIPTION_NEW);
+        putValue(Action.ACCELERATOR_KEY,
+            KeyStroke.getKeyStroke(KeyEvent.VK_N, KeyEvent.ALT_MASK));
+        putValue(Action.MNEMONIC_KEY, new Integer(MNEMONIC_KEY_NEW));
+        putValue(Action.ACTION_COMMAND_KEY, ACTION_COMMAND_KEY_NEW);
+        putValue(StandardAction.ON_MENUBAR, new Boolean(true));
+        putValue(StandardAction.MENU_NAME, "File");
+        putValue(StandardAction.MENU_ITEM_GROUP, new Integer(0));
+        putValue(StandardAction.MENU_ITEM_WEIGHT, new Integer(1));
+        putValue(StandardAction.ON_TOOLBAR, new Boolean(true));
+        putValue(StandardAction.TOOLBAR_GROUP, new Integer(0));
+        putValue(StandardAction.TOOLBAR_WEIGHT, new Integer(0));
+    }
+}
diff --git a/src/com/sshtools/common/ui/NewWindowAction.java b/src/com/sshtools/common/ui/NewWindowAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..b36d9c8c9c55e7bef3a6e63bd08f00d03b0cd87d
--- /dev/null
+++ b/src/com/sshtools/common/ui/NewWindowAction.java
@@ -0,0 +1,90 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+
+import javax.swing.Action;
+import javax.swing.KeyStroke;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class NewWindowAction extends StandardAction {
+    /**  */
+    protected Log log = LogFactory.getLog(NewWindowAction.class);
+
+    //
+    private SshToolsApplication application;
+
+    /**
+* Creates a new NewWindowAction object.
+*
+* @param application
+*/
+    public NewWindowAction(SshToolsApplication application) {
+        this.application = application;
+        putValue(Action.NAME, "New Window");
+        putValue(Action.SMALL_ICON,
+            getIcon("/com/sshtools/common/ui/newwindow.png"));
+        putValue(LARGE_ICON,
+            getIcon("/com/sshtools/common/ui/largenewwindow.png"));
+        putValue(Action.SHORT_DESCRIPTION, "Create new window");
+        putValue(Action.ACCELERATOR_KEY,
+            KeyStroke.getKeyStroke(KeyEvent.VK_W, KeyEvent.ALT_MASK));
+        putValue(Action.LONG_DESCRIPTION, "Create a new SSHTerm window");
+        putValue(Action.MNEMONIC_KEY, new Integer('w'));
+        putValue(Action.ACTION_COMMAND_KEY, "new-window");
+        putValue(StandardAction.ON_MENUBAR, new Boolean(true));
+        putValue(StandardAction.MENU_NAME, "File");
+        putValue(StandardAction.MENU_ITEM_GROUP, new Integer(0));
+        putValue(StandardAction.MENU_ITEM_WEIGHT, new Integer(90));
+        putValue(StandardAction.ON_TOOLBAR, new Boolean(true));
+        putValue(StandardAction.TOOLBAR_GROUP, new Integer(0));
+        putValue(StandardAction.TOOLBAR_WEIGHT, new Integer(90));
+    }
+
+    /**
+*
+*
+* @param evt
+*/
+    public void actionPerformed(ActionEvent evt) {
+        try {
+            application.newContainer();
+        } catch (SshToolsApplicationException stae) {
+            log.error(stae);
+        }
+    }
+}
diff --git a/src/com/sshtools/common/ui/NumericTextField.java b/src/com/sshtools/common/ui/NumericTextField.java
new file mode 100644
index 0000000000000000000000000000000000000000..39d403cd870f54221d97a7c37e1c46d6a8c5edb2
--- /dev/null
+++ b/src/com/sshtools/common/ui/NumericTextField.java
@@ -0,0 +1,501 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.Color;
+import java.awt.event.FocusEvent;
+
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.NumberFormat;
+import java.text.ParseException;
+
+import javax.swing.JTextField;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.PlainDocument;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class NumericTextField extends XTextField {
+    private Color positiveBackground;
+    private DecimalFormatSymbols symbols;
+
+    //  Private instance variables
+    private NumberFormat numberFormat;
+    private boolean selectAllOnFocusGain;
+    private int wColumnWidth;
+
+    /**
+* Creates a new NumericTextField object.
+*
+* @param min
+* @param max
+*/
+    public NumericTextField(Number min, Number max) {
+        this(min, max, min);
+    }
+
+    /**
+* Creates a new NumericTextField object.
+*
+* @param min
+* @param max
+* @param initial
+* @param rightJustify
+*/
+    public NumericTextField(Number min, Number max, Number initial,
+        boolean rightJustify) {
+        this(min, max, initial, rightJustify, null);
+    }
+
+    /**
+* Creates a new NumericTextField object.
+*
+* @param min
+* @param max
+* @param initial
+*/
+    public NumericTextField(Number min, Number max, Number initial) {
+        this(min, max, initial, true);
+    }
+
+    /**
+* Creates a new NumericTextField object.
+*
+* @param min
+* @param max
+* @param initial
+* @param rightJustify
+* @param numberFormat
+*
+* @throws IllegalArgumentException
+*/
+    public NumericTextField(Number min, Number max, Number initial,
+        boolean rightJustify, NumberFormat numberFormat) {
+        super(Math.max(min.toString().length(), max.toString().length()));
+        setNumberFormat(numberFormat);
+
+        if (min.getClass().equals(max.getClass()) &&
+                max.getClass().equals(initial.getClass())) {
+            setDocument(new ADocument(min, max));
+            setValue(initial);
+        } else {
+            throw new IllegalArgumentException(
+                "All arguments must be of the same class");
+        }
+
+        setRightJustify(rightJustify);
+    }
+
+    /*
+*  Overides <code>JTextFields</codes> calculation of the width of a single
+*  character (M space)
+*
+*  @return column width based on '9'
+*  public int getColumnWidth() {
+*  if (wColumnWidth==0) {
+*  FontMetrics metrics = getFontMetrics(getFont());
+*  wColumnWidth = metrics.charWidth('W');
+*  }
+*  return wColumnWidth;
+*  }
+*/
+    protected void processFocusEvent(FocusEvent e) {
+        super.processFocusEvent(e);
+
+        if (!e.isTemporary()) {
+            switch (e.getID()) {
+            case FocusEvent.FOCUS_LOST:
+
+                if (getNumberFormat() != null) {
+                    String s = getNumberFormat().format(getValue()).toString();
+
+                    if (!getText().equals(s)) {
+                        setText(s);
+                    }
+                }
+
+                break;
+
+            case FocusEvent.FOCUS_GAINED:
+
+                if (isSelectAllOnFocusGain()) {
+                    selectAll();
+                }
+
+                break;
+            }
+        }
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public boolean isSelectAllOnFocusGain() {
+        return selectAllOnFocusGain;
+    }
+
+    /**
+*
+*
+* @param selectAllOnFocusGain
+*/
+    public void setSelectAllOnFocusGain(boolean selectAllOnFocusGain) {
+        this.selectAllOnFocusGain = selectAllOnFocusGain;
+    }
+
+    /**
+*
+*
+* @param max
+*/
+    public void setMaximum(Number max) {
+        ((ADocument) getDocument()).setMaximum(max);
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public Number getMaximum() {
+        return ((ADocument) getDocument()).max;
+    }
+
+    /**
+*
+*
+* @param min
+*/
+    public void setMinimum(Number min) {
+        ((ADocument) getDocument()).setMinimum(min);
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public Number getMinimum() {
+        return ((ADocument) getDocument()).min;
+    }
+
+    /**
+*
+*
+* @param numberFormat
+*/
+    public void setNumberFormat(NumberFormat numberFormat) {
+        this.numberFormat = numberFormat;
+
+        if (numberFormat instanceof DecimalFormat) {
+            symbols = ((DecimalFormat) numberFormat).getDecimalFormatSymbols();
+        } else {
+            symbols = new DecimalFormatSymbols();
+        }
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public NumberFormat getNumberFormat() {
+        return numberFormat;
+    }
+
+    /**
+*
+*
+* @param rightJustify
+*/
+    public void setRightJustify(boolean rightJustify) {
+        setHorizontalAlignment(rightJustify ? JTextField.RIGHT : JTextField.LEFT);
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public boolean isRightJustify() {
+        return getHorizontalAlignment() == JTextField.RIGHT;
+    }
+
+    /**
+*
+*
+* @param s
+*/
+    public void setText(String s) {
+        ADocument doc = (ADocument) getDocument();
+        Number oldValue = doc.currentVal;
+
+        try {
+            doc.currentVal = doc.parse(s);
+        } catch (Exception e) {
+            e.printStackTrace();
+
+            return;
+        }
+
+        if (oldValue != doc.currentVal) {
+            doc.checkingEnabled = false;
+            super.setText(s);
+            doc.checkingEnabled = true;
+        }
+    }
+
+    /**
+*
+*
+* @param i
+*/
+    public void setValue(Number i) {
+        setText(i.toString());
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public Number getValue() {
+        return ((ADocument) getDocument()).getValue();
+    }
+
+    //  Supporting classes
+    class ADocument extends PlainDocument {
+        Number currentVal;
+        Number max;
+        Number min;
+        boolean checkingEnabled = true;
+        boolean rightJustify = true;
+
+        public ADocument(Number min, Number max) {
+            this.min = min;
+            this.max = max;
+
+            if (min.getClass().equals(Byte.class)) {
+                currentVal = new Byte((byte) 0);
+            } else {
+                if (min.getClass().equals(Short.class)) {
+                    currentVal = new Short((short) 0);
+                } else {
+                    if (min.getClass().equals(Integer.class)) {
+                        currentVal = new Integer(0);
+                    } else {
+                        if (min.getClass().equals(Long.class)) {
+                            currentVal = new Long(0L);
+                        } else {
+                            if (min.getClass().equals(Float.class)) {
+                                currentVal = new Float(0f);
+                            } else {
+                                currentVal = new Double(0d);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        public void setMaximum(Number max) {
+            this.max = max;
+        }
+
+        public void setMinimum(Number min) {
+            this.min = min;
+        }
+
+        public void setRightJustify(boolean rightJustify) {
+            this.rightJustify = rightJustify;
+        }
+
+        public boolean isRightJustify() {
+            return rightJustify;
+        }
+
+        public Number getValue() {
+            return currentVal;
+        }
+
+        public void insertString(int offs, String str, AttributeSet a)
+            throws BadLocationException {
+            if (str == null) {
+                return;
+            }
+
+            if (!checkingEnabled) {
+                super.insertString(offs, str, a);
+
+                return;
+            }
+
+            String proposedResult = null;
+
+            if (getLength() == 0) {
+                proposedResult = str;
+            } else {
+                StringBuffer currentBuffer = new StringBuffer(getText(0,
+                            getLength()));
+                currentBuffer.insert(offs, str);
+                proposedResult = currentBuffer.toString();
+            }
+
+            try {
+                currentVal = parse(proposedResult);
+                super.insertString(offs, str, a);
+            } catch (Exception e) {
+            }
+        }
+
+        public Number parse(String proposedResult) throws NumberFormatException {
+            Double d = new Double(0d);
+
+            //   See if the proposed result matches the number format (if any)
+            if (!proposedResult.equals(String.valueOf(symbols.getMinusSign())) &&
+                    (proposedResult.length() != 0)) {
+                if (getNumberFormat() != null) {
+                    //   Strip out everything from the proposed result other than the
+                    //   numbers and and decimal separators
+                    StringBuffer sB = new StringBuffer();
+
+                    for (int i = 0; i < proposedResult.length(); i++) {
+                        char ch = proposedResult.charAt(i);
+
+                        if ((ch == symbols.getDecimalSeparator()) ||
+                                ((ch >= '0') && (ch <= '9'))) {
+                            sB.append(ch);
+                        }
+                    }
+
+                    String s = sB.toString();
+
+                    //   Find out how many digits there are before the decimal place
+                    int i = 0;
+
+                    for (;
+                            (i < s.length()) &&
+                            (s.charAt(i) != symbols.getDecimalSeparator());
+                            i++) {
+                        ;
+                    }
+
+                    int before = i;
+                    int after = 0;
+
+                    if (before < s.length()) {
+                        after = s.length() - i - 1;
+                    }
+
+                    if (before > getNumberFormat().getMaximumIntegerDigits()) {
+                        throw new NumberFormatException(
+                            "More digits BEFORE the decimal separator than allowed:" +
+                            proposedResult);
+                    }
+
+                    if (after > getNumberFormat().getMaximumFractionDigits()) {
+                        throw new NumberFormatException(
+                            "More digits AFTER the decimal separator than allowed:" +
+                            proposedResult);
+                    }
+
+                    //   Now try to parse the field against the number format
+                    try {
+                        d = new Double(getNumberFormat().parse(proposedResult)
+                                           .doubleValue());
+                    } catch (ParseException pE) {
+                        throw new NumberFormatException("Failed to parse. " +
+                            proposedResult + pE.getMessage());
+                    }
+                }
+                //   Just use the default parse
+                else {
+                    d = new Double(proposedResult);
+                }
+            }
+
+            //   Now determine if the number if within range
+            if ((d.doubleValue() >= min.doubleValue()) &&
+                    (d.doubleValue() <= max.doubleValue())) {
+                //   Now create the real type
+                if (min.getClass().equals(Byte.class)) {
+                    return new Byte(d.byteValue());
+                } else {
+                    if (min.getClass().equals(Short.class)) {
+                        return new Short(d.shortValue());
+                    } else {
+                        if (min.getClass().equals(Integer.class)) {
+                            return new Integer(d.intValue());
+                        } else {
+                            if (min.getClass().equals(Long.class)) {
+                                return new Long(d.longValue());
+                            } else {
+                                if (min.getClass().equals(Float.class)) {
+                                    return new Float(d.floatValue());
+                                } else {
+                                    return d;
+                                }
+                            }
+                        }
+                    }
+                }
+            } else {
+                throw new NumberFormatException(d +
+                    " Is out of range. Minimum is " + min.doubleValue() +
+                    ", Maximum is " + max.doubleValue());
+            }
+        }
+
+        public void remove(int offs, int len) throws BadLocationException {
+            if (!checkingEnabled) {
+                super.remove(offs, len);
+
+                return;
+            }
+
+            String currentText = getText(0, getLength());
+            String beforeOffset = currentText.substring(0, offs);
+            String afterOffset = currentText.substring(len + offs,
+                    currentText.length());
+            String proposedResult = beforeOffset + afterOffset;
+
+            try {
+                currentVal = parse(proposedResult);
+                super.remove(offs, len);
+            } catch (Exception e) {
+            }
+        }
+    }
+}
diff --git a/src/com/sshtools/common/ui/OpenAction.java b/src/com/sshtools/common/ui/OpenAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..bec42959b92849087abb5a02dfaac146255e59a7
--- /dev/null
+++ b/src/com/sshtools/common/ui/OpenAction.java
@@ -0,0 +1,70 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.event.KeyEvent;
+
+import javax.swing.Action;
+import javax.swing.KeyStroke;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class OpenAction extends StandardAction {
+    private final static String ACTION_COMMAND_KEY_OPEN = "open-command";
+    private final static String NAME_OPEN = "Open";
+    private final static String SMALL_ICON_OPEN = "/com/sshtools/common/ui/fileopen.png";
+    private final static String LARGE_ICON_OPEN = "";
+    private final static String SHORT_DESCRIPTION_OPEN = "Open an existing connection";
+    private final static String LONG_DESCRIPTION_OPEN = "Opens an existing connection from file";
+    private final static int MNEMONIC_KEY_OPEN = 'O';
+
+    /**
+* Creates a new OpenAction object.
+*/
+    public OpenAction() {
+        putValue(Action.NAME, NAME_OPEN);
+        putValue(Action.SMALL_ICON, getIcon(SMALL_ICON_OPEN));
+        putValue(LARGE_ICON, getIcon(LARGE_ICON_OPEN));
+        putValue(Action.SHORT_DESCRIPTION, SHORT_DESCRIPTION_OPEN);
+        putValue(Action.LONG_DESCRIPTION, LONG_DESCRIPTION_OPEN);
+        putValue(Action.ACCELERATOR_KEY,
+            KeyStroke.getKeyStroke(KeyEvent.VK_O, KeyEvent.ALT_MASK));
+        putValue(Action.MNEMONIC_KEY, new Integer(MNEMONIC_KEY_OPEN));
+        putValue(Action.ACTION_COMMAND_KEY, ACTION_COMMAND_KEY_OPEN);
+        putValue(StandardAction.ON_MENUBAR, new Boolean(true));
+        putValue(StandardAction.MENU_NAME, "File");
+        putValue(StandardAction.MENU_ITEM_GROUP, new Integer(0));
+        putValue(StandardAction.MENU_ITEM_WEIGHT, new Integer(5));
+        putValue(StandardAction.ON_TOOLBAR, new Boolean(true));
+        putValue(StandardAction.TOOLBAR_GROUP, new Integer(0));
+        putValue(StandardAction.TOOLBAR_WEIGHT, new Integer(5));
+    }
+}
diff --git a/src/com/sshtools/common/ui/Option.java b/src/com/sshtools/common/ui/Option.java
new file mode 100644
index 0000000000000000000000000000000000000000..9019c88f7c86870299d7886b55be76937e1f25fc
--- /dev/null
+++ b/src/com/sshtools/common/ui/Option.java
@@ -0,0 +1,79 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class Option {
+    private String text;
+    private String toolTipText;
+    private int mnemonic;
+
+    /**
+* Creates a new Option object.
+*
+* @param text
+* @param toolTipText
+* @param mnemonic
+*/
+    public Option(String text, String toolTipText, int mnemonic) {
+        this.text = text;
+        this.toolTipText = toolTipText;
+        this.mnemonic = mnemonic;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getText() {
+        return text;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public int getMnemonic() {
+        return mnemonic;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getToolTipText() {
+        return toolTipText;
+    }
+}
diff --git a/src/com/sshtools/common/ui/OptionCallback.java b/src/com/sshtools/common/ui/OptionCallback.java
new file mode 100644
index 0000000000000000000000000000000000000000..af7a5271f97a6456b399511fd51229f0e2c83b11
--- /dev/null
+++ b/src/com/sshtools/common/ui/OptionCallback.java
@@ -0,0 +1,45 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public interface OptionCallback {
+    /**
+*
+*
+* @param dialog
+* @param option
+*
+* @return
+*/
+    public boolean canClose(OptionsDialog dialog, Option option);
+}
diff --git a/src/com/sshtools/common/ui/OptionsAction.java b/src/com/sshtools/common/ui/OptionsAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..8b6c9b16ec92e67bbb9f6617d80cd16f0681d3e1
--- /dev/null
+++ b/src/com/sshtools/common/ui/OptionsAction.java
@@ -0,0 +1,57 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import javax.swing.Action;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class OptionsAction extends StandardAction {
+    /**
+* Creates a new OptionsAction object.
+*/
+    public OptionsAction() {
+        putValue(Action.NAME, "Options");
+        putValue(Action.SMALL_ICON,
+            getIcon("/com/sshtools/common/ui/options.png"));
+        putValue(Action.SHORT_DESCRIPTION, "Application options");
+        putValue(Action.LONG_DESCRIPTION, "Edit the application options");
+        putValue(Action.MNEMONIC_KEY, new Integer('o'));
+        putValue(Action.ACTION_COMMAND_KEY, "options-command");
+        putValue(StandardAction.ON_MENUBAR, new Boolean(true));
+        putValue(StandardAction.MENU_NAME, "Tools");
+        putValue(StandardAction.MENU_ITEM_GROUP, new Integer(90));
+        putValue(StandardAction.MENU_ITEM_WEIGHT, new Integer(99));
+        putValue(StandardAction.ON_TOOLBAR, new Boolean(true));
+        putValue(StandardAction.TOOLBAR_GROUP, new Integer(90));
+        putValue(StandardAction.TOOLBAR_WEIGHT, new Integer(0));
+    }
+}
diff --git a/src/com/sshtools/common/ui/OptionsDialog.java b/src/com/sshtools/common/ui/OptionsDialog.java
new file mode 100644
index 0000000000000000000000000000000000000000..b9131bc7cc794b92adf9fa5c321aa6156a2f327d
--- /dev/null
+++ b/src/com/sshtools/common/ui/OptionsDialog.java
@@ -0,0 +1,229 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.Icon;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSeparator;
+import javax.swing.SwingUtilities;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class OptionsDialog extends JDialog implements ActionListener {
+    private Option selectedOption;
+    private OptionCallback callback;
+    private JButton defaultButton;
+
+    /**
+* Creates a new OptionsDialog object.
+*
+* @param parent
+* @param options
+* @param message
+* @param title
+* @param defaultOption
+* @param callback
+* @param modal
+* @param icon
+*/
+    public OptionsDialog(JDialog parent, Option[] options, Object message,
+        String title, Option defaultOption, OptionCallback callback,
+        boolean modal, Icon icon) {
+        super(parent, title, modal);
+        init(options, message, defaultOption, callback, icon);
+    }
+
+    /**
+* Creates a new OptionsDialog object.
+*
+* @param parent
+* @param options
+* @param message
+* @param title
+* @param defaultOption
+* @param callback
+* @param modal
+* @param icon
+*/
+    public OptionsDialog(JFrame parent, Option[] options, Object message,
+        String title, Option defaultOption, OptionCallback callback,
+        boolean modal, Icon icon) {
+        super(parent, title, modal);
+        init(options, message, defaultOption, callback, icon);
+    }
+
+    private void init(Option[] options, Object message, Option defaultOption,
+        OptionCallback callback, Icon icon) {
+        //
+        this.callback = callback;
+
+        JPanel b = new JPanel(new FlowLayout(FlowLayout.RIGHT, 2, 2));
+        b.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+
+        for (int i = 0; i < options.length; i++) {
+            JButton button = new JButton(options[i].getText());
+
+            if (options[i] == defaultOption) {
+                button.setDefaultCapable(options[i] == defaultOption);
+                defaultButton = button;
+            }
+
+            button.setMnemonic(options[i].getMnemonic());
+            button.setToolTipText(options[i].getToolTipText());
+            button.putClientProperty("option", options[i]);
+            button.addActionListener(this);
+            b.add(button);
+        }
+
+        //
+        JPanel s = new JPanel(new BorderLayout());
+        s.setBorder(BorderFactory.createEmptyBorder(4, 0, 0, 0));
+        s.add(new JSeparator(JSeparator.HORIZONTAL), BorderLayout.NORTH);
+        s.add(b, BorderLayout.SOUTH);
+
+        //
+        JPanel z = new JPanel(new BorderLayout());
+        z.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+
+        //
+        if (message instanceof JComponent) {
+            z.add((JComponent) message, BorderLayout.CENTER);
+        } else {
+            z.add(new MultilineLabel(String.valueOf(message)),
+                BorderLayout.CENTER);
+        }
+
+        //  Icon panel
+        JLabel i = null;
+
+        if (icon != null) {
+            i = new JLabel(icon);
+            i.setVerticalAlignment(JLabel.NORTH);
+            i.setBorder(BorderFactory.createEmptyBorder(4, 4, 0, 4));
+        }
+
+        //  Build this panel
+        getContentPane().setLayout(new BorderLayout());
+        getContentPane().add(z, BorderLayout.CENTER);
+
+        if (i != null) {
+            getContentPane().add(i, BorderLayout.WEST);
+        }
+
+        getContentPane().add(s, BorderLayout.SOUTH);
+
+        //
+        pack();
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public JButton getDefaultButton() {
+        return defaultButton;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public Option getSelectedOption() {
+        return selectedOption;
+    }
+
+    /**
+*
+*
+* @param evt
+*/
+    public void actionPerformed(ActionEvent evt) {
+        selectedOption = (Option) ((JButton) evt.getSource()).getClientProperty(
+                "option");
+
+        if ((callback == null) || callback.canClose(this, selectedOption)) {
+            setVisible(false);
+        }
+    }
+
+    /**
+*
+*
+* @param parent
+* @param options
+* @param message
+* @param title
+* @param defaultOption
+* @param callback
+* @param icon
+*
+* @return
+*/
+    public static OptionsDialog createOptionDialog(JComponent parent,
+        Option[] options, Object message, String title, Option defaultOption,
+        OptionCallback callback, Icon icon) {
+        //
+        OptionsDialog dialog = null;
+        Window w = (Window) SwingUtilities.getAncestorOfClass(Window.class,
+                parent);
+
+        if (w instanceof JFrame) {
+            dialog = new OptionsDialog((JFrame) w, options, message, title,
+                    defaultOption, callback, true, icon);
+        } else if (w instanceof JDialog) {
+            dialog = new OptionsDialog((JDialog) w, options, message, title,
+                    defaultOption, callback, true, icon);
+        } else {
+            dialog = new OptionsDialog((JFrame) null, options, message, title,
+                    defaultOption, callback, true, icon);
+        }
+
+        if (dialog.getDefaultButton() != null) {
+            dialog.getRootPane().setDefaultButton(dialog.getDefaultButton());
+        }
+
+        return dialog;
+    }
+}
diff --git a/src/com/sshtools/common/ui/OptionsPanel.java b/src/com/sshtools/common/ui/OptionsPanel.java
new file mode 100644
index 0000000000000000000000000000000000000000..a1959ba9a77bb465443eb747135cacd036ee3280
--- /dev/null
+++ b/src/com/sshtools/common/ui/OptionsPanel.java
@@ -0,0 +1,206 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.SwingConstants;
+import javax.swing.SwingUtilities;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class OptionsPanel extends JPanel {
+    //
+
+    /**  */
+    protected Log log = LogFactory.getLog(OptionsPanel.class);
+
+    //
+    private Tabber tabber;
+    private boolean cancelled;
+
+    /**
+* Creates a new OptionsPanel object.
+*
+* @param optionalTabs
+*/
+    public OptionsPanel(OptionsTab[] optionalTabs) {
+        super();
+        tabber = new Tabber();
+
+        if (optionalTabs != null) {
+            for (int i = 0; i < optionalTabs.length; i++) {
+                optionalTabs[i].reset();
+                addTab(optionalTabs[i]);
+            }
+        }
+
+        //  Add the common tabs
+        addTab(new GlobalOptionsTab());
+
+        //  Build this panel
+        setLayout(new GridLayout(1, 1));
+        add(tabber);
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public boolean validateTabs() {
+        return tabber.validateTabs();
+    }
+
+    /**
+*
+*/
+    public void applyTabs() {
+        tabber.applyTabs();
+    }
+
+    /**
+*
+*
+* @param tab
+*/
+    public void addTab(OptionsTab tab) {
+        tabber.addTab(tab);
+    }
+
+    /**
+*
+*/
+    public void reset() {
+        for (int i = 0; i < tabber.getTabCount(); i++) {
+            ((OptionsTab) tabber.getTabAt(i)).reset();
+        }
+    }
+
+    /**
+*
+*
+* @param parent
+* @param optionalTabs
+*
+* @return
+*/
+    public static boolean showOptionsDialog(Component parent,
+        OptionsTab[] optionalTabs) {
+        final OptionsPanel opts = new OptionsPanel(optionalTabs);
+        opts.reset();
+
+        JDialog d = null;
+        Window w = (Window) SwingUtilities.getAncestorOfClass(Window.class,
+                parent);
+
+        if (w instanceof JDialog) {
+            d = new JDialog((JDialog) w, "Options", true);
+        } else if (w instanceof JFrame) {
+            d = new JDialog((JFrame) w, "Options", true);
+        } else {
+            d = new JDialog((JFrame) null, "Options", true);
+        }
+
+        final JDialog dialog = d;
+
+        //  Create the bottom button panel
+        final JButton cancel = new JButton("Cancel");
+        cancel.setMnemonic('c');
+        cancel.addActionListener(new ActionListener() {
+                public void actionPerformed(ActionEvent evt) {
+                    opts.cancelled = true;
+                    dialog.setVisible(false);
+                }
+            });
+
+        final JButton ok = new JButton("Ok");
+        ok.setMnemonic('o');
+        ok.addActionListener(new ActionListener() {
+                public void actionPerformed(ActionEvent evt) {
+                    if (opts.validateTabs()) {
+                        dialog.setVisible(false);
+                    }
+                }
+            });
+        dialog.getRootPane().setDefaultButton(ok);
+
+        JPanel buttonPanel = new JPanel(new GridBagLayout());
+        GridBagConstraints gbc = new GridBagConstraints();
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        gbc.anchor = GridBagConstraints.CENTER;
+        gbc.insets = new Insets(6, 6, 0, 0);
+        gbc.weighty = 1.0;
+        UIUtil.jGridBagAdd(buttonPanel, ok, gbc, GridBagConstraints.RELATIVE);
+        UIUtil.jGridBagAdd(buttonPanel, cancel, gbc,
+            GridBagConstraints.REMAINDER);
+
+        JPanel southPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 0, 0));
+        southPanel.add(buttonPanel);
+
+        //
+        JPanel mainPanel = new JPanel(new BorderLayout());
+        mainPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+        mainPanel.add(opts, BorderLayout.CENTER);
+        mainPanel.add(southPanel, BorderLayout.SOUTH);
+
+        // Show the dialog
+        dialog.getContentPane().setLayout(new GridLayout(1, 1));
+        dialog.getContentPane().add(mainPanel);
+        dialog.pack();
+        dialog.setResizable(true);
+        UIUtil.positionComponent(SwingConstants.CENTER, dialog);
+        dialog.setVisible(true);
+
+        if (!opts.cancelled) {
+            opts.applyTabs();
+        }
+
+        return !opts.cancelled;
+    }
+}
diff --git a/src/com/sshtools/common/ui/OptionsTab.java b/src/com/sshtools/common/ui/OptionsTab.java
new file mode 100644
index 0000000000000000000000000000000000000000..c25cc320394520a335be16b61b6c8a4aace5350d
--- /dev/null
+++ b/src/com/sshtools/common/ui/OptionsTab.java
@@ -0,0 +1,40 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public interface OptionsTab extends Tab {
+    /**
+*
+*/
+    public void reset();
+}
diff --git a/src/com/sshtools/common/ui/PreferencesStore.java b/src/com/sshtools/common/ui/PreferencesStore.java
new file mode 100644
index 0000000000000000000000000000000000000000..5f79163e010799c039608fc1d27b39f707161aae
--- /dev/null
+++ b/src/com/sshtools/common/ui/PreferencesStore.java
@@ -0,0 +1,378 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.awt.Rectangle;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+import javax.swing.JTable;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public class PreferencesStore {
+    /**  */
+    protected static Log log = LogFactory.getLog(PreferencesStore.class);
+
+    //
+    private static File file;
+    private static boolean storeAvailable;
+    private static Properties preferences;
+
+    //  Intialise the preferences
+    static {
+        preferences = new Properties();
+    }
+
+    /**
+*
+*
+* @param table
+* @param pref
+*/
+    public static void saveTableMetrics(JTable table, String pref) {
+        for (int i = 0; i < table.getColumnModel().getColumnCount(); i++) {
+            int w = table.getColumnModel().getColumn(i).getWidth();
+            put(pref + ".column." + i + ".width", String.valueOf(w));
+            put(pref + ".column." + i + ".position",
+                String.valueOf(table.convertColumnIndexToModel(i)));
+        }
+    }
+
+    /**
+*
+*
+* @param table
+* @param pref
+* @param defaultWidths
+*
+* @throws IllegalArgumentException
+*/
+    public static void restoreTableMetrics(JTable table, String pref,
+        int[] defaultWidths) {
+        //  Check the table columns may be resized correctly
+        if (table.getAutoResizeMode() != JTable.AUTO_RESIZE_OFF) {
+            throw new IllegalArgumentException(
+                "Table AutoResizeMode must be JTable.AUTO_RESIZE_OFF");
+        }
+
+        //  Restore the table column widths and positions
+        for (int i = 0; i < table.getColumnModel().getColumnCount(); i++) {
+            try {
+                table.moveColumn(table.convertColumnIndexToView(getInt(pref +
+                            ".column." + i + ".position", i)), i);
+                table.getColumnModel().getColumn(i).setPreferredWidth(getInt(pref +
+                        ".column." + i + ".width",
+                        (defaultWidths == null)
+                        ? table.getColumnModel().getColumn(i).getPreferredWidth()
+                        : defaultWidths[i]));
+            } catch (NumberFormatException nfe) {
+            }
+        }
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public static boolean isStoreAvailable() {
+        return storeAvailable;
+    }
+
+    /**
+*
+*
+* @param file
+*/
+    public static void init(File file) {
+        PreferencesStore.file = file;
+
+        //  Make sure the preferences directory exists, creating it if it doesn't
+        File dir = file.getParentFile();
+
+        if (!dir.exists()) {
+            log.info("Creating SSHTerm preferences directory " +
+                dir.getAbsolutePath());
+
+            if (!dir.mkdirs()) {
+                log.error("Preferences directory " + dir.getAbsolutePath() +
+                    " could not be created. " +
+                    "Preferences will not be stored");
+            }
+        }
+
+        storeAvailable = dir.exists();
+
+        //  If the preferences file exists, then load it
+        if (storeAvailable) {
+            if (file.exists()) {
+                InputStream in = null;
+
+                try {
+                    in = new FileInputStream(file);
+                    preferences.load(in);
+                    storeAvailable = true;
+                } catch (IOException ioe) {
+                    log.error(ioe);
+                } finally {
+                    if (in != null) {
+                        try {
+                            in.close();
+                        } catch (IOException ioe) {
+                        }
+                    }
+                }
+            }
+            //  Otherwise create it
+            else {
+                savePreferences();
+            }
+        } else {
+            log.warn("Preferences store not available.");
+        }
+    }
+
+    /**
+*
+*/
+    public static void savePreferences() {
+        if (file == null) {
+            log.error(
+                "Preferences not saved as PreferencesStore has not been initialise.");
+        } else {
+            OutputStream out = null;
+
+            try {
+                out = new FileOutputStream(file);
+                preferences.store(out, "SSHTerm preferences");
+                log.info("Preferences written to " + file.getAbsolutePath());
+                storeAvailable = true;
+            } catch (IOException ioe) {
+                log.error(ioe);
+            } finally {
+                if (out != null) {
+                    try {
+                        out.close();
+                    } catch (IOException ioe) {
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+*
+*
+* @param name
+* @param def
+*
+* @return
+*/
+    public static String get(String name, String def) {
+        return preferences.getProperty(name, def);
+    }
+
+    /**
+*
+*
+* @param name
+* @param val
+*/
+    public static void put(String name, String val) {
+        preferences.put(name, val);
+    }
+
+    /**
+*
+*
+* @param name
+* @param def
+*
+* @return
+*/
+    public static Rectangle getRectangle(String name, Rectangle def) {
+        String s = preferences.getProperty(name);
+
+        if ((s == null) || s.equals("")) {
+            return def;
+        } else {
+            StringTokenizer st = new StringTokenizer(s, ",");
+            Rectangle r = new Rectangle();
+
+            try {
+                r.x = Integer.parseInt(st.nextToken());
+                r.y = Integer.parseInt(st.nextToken());
+                r.width = Integer.parseInt(st.nextToken());
+                r.height = Integer.parseInt(st.nextToken());
+            } catch (NumberFormatException nfe) {
+                log.warn("Preference is " + name + " is badly formatted", nfe);
+            }
+
+            return r;
+        }
+    }
+
+    /**
+*
+*
+* @param name
+* @param val
+*/
+    public static void putRectangle(String name, Rectangle val) {
+        preferences.put(name,
+            (val == null) ? ""
+                          : (val.x + "," + val.y + "," + val.width + "," +
+            val.height));
+    }
+
+    /**
+*
+*
+* @param name
+* @param def
+*
+* @return
+*/
+    public static int getInt(String name, int def) {
+        String s = preferences.getProperty(name);
+
+        if ((s != null) && !s.equals("")) {
+            try {
+                return Integer.parseInt(s);
+            } catch (NumberFormatException nfe) {
+                log.warn("Preference is " + name + " is badly formatted", nfe);
+            }
+        }
+
+        return def;
+    }
+
+    /**
+*
+*
+* @param name
+* @param def
+*
+* @return
+*/
+    public static double getDouble(String name, double def) {
+        String s = preferences.getProperty(name);
+
+        if ((s != null) && !s.equals("")) {
+            try {
+                return Double.parseDouble(s);
+            } catch (NumberFormatException nfe) {
+                log.warn("Preference is " + name + " is badly formatted", nfe);
+            }
+        }
+
+        return def;
+    }
+
+    /**
+*
+*
+* @param name
+* @param val
+*/
+    public static void putInt(String name, int val) {
+        preferences.put(name, String.valueOf(val));
+    }
+
+    /**
+*
+*
+* @param name
+* @param val
+*/
+    public static void putDouble(String name, double val) {
+        preferences.put(name, String.valueOf(val));
+    }
+
+    /**
+*
+*
+* @param name
+* @param def
+*
+* @return
+*/
+    public static boolean getBoolean(String name, boolean def) {
+        return get(name, String.valueOf(def)).equals("true");
+    }
+
+    /**
+*
+*
+* @param name
+* @param val
+*/
+    public static void putBoolean(String name, boolean val) {
+        preferences.put(name, String.valueOf(val));
+    }
+
+    /**
+*
+*
+* @param name
+*
+* @return
+*/
+    public static boolean preferenceExists(String name) {
+        return preferences.containsKey(name);
+    }
+
+    /**
+*
+*
+* @param name
+*
+* @return
+*/
+    public static boolean removePreference(String name) {
+        boolean exists = preferenceExists(name);
+        preferences.remove(name);
+
+        return exists;
+    }
+}
diff --git a/src/com/sshtools/common/ui/PrintAction.java b/src/com/sshtools/common/ui/PrintAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..eec6bfb2722a6a37bff8b65a35686bdb56d60869
--- /dev/null
+++ b/src/com/sshtools/common/ui/PrintAction.java
@@ -0,0 +1,52 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.event.KeyEvent;
+
+import javax.swing.Action;
+import javax.swing.KeyStroke;
+
+
+public abstract class PrintAction extends StandardAction {
+    public PrintAction() {
+        putValue(Action.NAME, "Print");
+        putValue(Action.SMALL_ICON, getIcon("/com/sshtools/common/ui/print.png"));
+        putValue(Action.SHORT_DESCRIPTION, "Print");
+        putValue(Action.LONG_DESCRIPTION, "Print the terminal screen");
+        putValue(Action.MNEMONIC_KEY, new Integer('p'));
+        putValue(Action.ACTION_COMMAND_KEY, "print-command");
+        putValue(Action.ACCELERATOR_KEY,
+            KeyStroke.getKeyStroke(KeyEvent.VK_P, KeyEvent.ALT_MASK));
+        putValue(StandardAction.ON_MENUBAR, new Boolean(true));
+        putValue(StandardAction.MENU_NAME, "File");
+        putValue(StandardAction.MENU_ITEM_GROUP, new Integer(80));
+        putValue(StandardAction.MENU_ITEM_WEIGHT, new Integer(0));
+        putValue(StandardAction.ON_TOOLBAR, new Boolean(true));
+        putValue(StandardAction.TOOLBAR_GROUP, new Integer(00));
+        putValue(StandardAction.TOOLBAR_WEIGHT, new Integer(80));
+    }
+}
diff --git a/src/com/sshtools/common/ui/PrintPreview.java b/src/com/sshtools/common/ui/PrintPreview.java
new file mode 100644
index 0000000000000000000000000000000000000000..f62c73850ed4f0359b7549fb32d73d4f16f9878f
--- /dev/null
+++ b/src/com/sshtools/common/ui/PrintPreview.java
@@ -0,0 +1,336 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.image.*;
+import java.awt.print.*;
+
+import javax.swing.*;
+import javax.swing.border.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class PrintPreview extends JPanel implements ActionListener, Runnable {
+    /**  */
+    protected PrinterJob job;
+
+    /**  */
+    protected JButton setup;
+
+    /**  */
+    protected JComboBox scale;
+
+    /**  */
+    protected int pageWidth;
+
+    /**  */
+    protected Printable printable;
+
+    /**  */
+    protected PreviewContainer previewer;
+
+    /**  */
+    protected PageFormat pageFormat;
+
+    /**  */
+    protected int pageHeight;
+
+    /**  */
+    protected JScrollPane scrollpane;
+
+    /**
+* Creates a new PrintPreview object.
+*
+* @param printable
+* @param pageFormat
+*/
+    public PrintPreview(Printable printable, PageFormat pageFormat) {
+        super(new BorderLayout());
+        this.printable = printable;
+        job = PrinterJob.getPrinterJob();
+        this.pageFormat = pageFormat;
+
+        JPanel jpanel = new JPanel(new FlowLayout(0, 2, 2));
+        setup = new JButton("Setup");
+        setup.addActionListener(this);
+        setup.setMnemonic('p');
+        jpanel.add(setup);
+
+        String[] as = { "10 %", "25 %", "50 %", "75 %", "100 %", "200 %" };
+        scale = new JComboBox(as);
+        scale.setSelectedItem(as[2]);
+        scale.addActionListener(this);
+        scale.setEditable(true);
+        jpanel.add(scale);
+        add(jpanel, BorderLayout.NORTH);
+        previewer = new PreviewContainer();
+
+        if ((pageFormat.getHeight() == 0.0D) ||
+                (pageFormat.getWidth() == 0.0D)) {
+            System.err.println("Unable to determine default page size");
+
+            return;
+        }
+
+        pageWidth = (int) pageFormat.getWidth();
+        pageHeight = (int) pageFormat.getHeight();
+
+        byte byte0 = 50;
+        int i = (pageWidth * byte0) / 100;
+        int j = (pageHeight * byte0) / 100;
+        int k = 0;
+
+        try {
+            do {
+                BufferedImage bufferedimage = new BufferedImage(pageWidth,
+                        pageHeight, 1);
+                Graphics g = bufferedimage.getGraphics();
+                g.setColor(Color.white);
+                g.fillRect(0, 0, pageWidth, pageHeight);
+
+                if (printable.print(g, pageFormat, k) != 0) {
+                    break;
+                }
+
+                PagePreview pagepreview = new PagePreview(i, j, bufferedimage);
+                previewer.add(pagepreview);
+                k++;
+            } while (true);
+        } catch (PrinterException printerexception) {
+            printerexception.printStackTrace();
+            System.err.println("Printing error: " +
+                printerexception.toString());
+        }
+
+        scrollpane = new JScrollPane(previewer);
+        add(scrollpane, BorderLayout.CENTER);
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public PageFormat getPageFormat() {
+        return pageFormat;
+    }
+
+    private void setup() {
+        pageFormat = job.pageDialog(pageFormat);
+        pageWidth = (int) pageFormat.getWidth();
+        pageHeight = (int) pageFormat.getHeight();
+
+        Thread thread = new Thread(this);
+        thread.start();
+    }
+
+    /**
+*
+*/
+    public void run() {
+        String s = scale.getSelectedItem().toString();
+        scrollpane.setViewportView(new JLabel("Resizing"));
+
+        if (s.endsWith("%")) {
+            s = s.substring(0, s.length() - 1);
+        }
+
+        s = s.trim();
+
+        int i = 0;
+
+        try {
+            i = Integer.parseInt(s);
+        } catch (NumberFormatException numberformatexception) {
+            i = 100;
+        }
+
+        int j = (pageWidth * i) / 100;
+        int k = (pageHeight * i) / 100;
+        Component[] acomponent = previewer.getComponents();
+
+        for (int l = 0; l < acomponent.length; l++) {
+            if (acomponent[l] instanceof PagePreview) {
+                PagePreview pagepreview = (PagePreview) acomponent[l];
+                pagepreview.setScaledSize(j, k);
+            }
+        }
+
+        scrollpane.setViewportView(previewer);
+    }
+
+    /**
+*
+*
+* @param actionevent
+*/
+    public void actionPerformed(ActionEvent actionevent) {
+        if (actionevent.getSource() == setup) {
+            setup();
+        } else if (actionevent.getSource() == scale) {
+            Thread thread = new Thread(this);
+            thread.start();
+        }
+    }
+
+    class PagePreview extends JPanel {
+        protected int m_h;
+        protected int m_w;
+        protected Image m_img;
+        protected Image m_source;
+
+        public PagePreview(int i, int j, Image image) {
+            m_w = i;
+            m_h = j;
+            m_source = image;
+            m_img = m_source.getScaledInstance(m_w, m_h, 4);
+            m_img.flush();
+            setBackground(Color.white);
+            setBorder(new MatteBorder(1, 1, 2, 2, Color.black));
+        }
+
+        public Dimension getMaximumSize() {
+            return getPreferredSize();
+        }
+
+        public Dimension getPreferredSize() {
+            Insets insets = getInsets();
+
+            return new Dimension(m_w + insets.left + insets.right,
+                m_h + insets.top + insets.bottom);
+        }
+
+        public void paint(Graphics g) {
+            g.setColor(getBackground());
+            g.fillRect(0, 0, getWidth(), getHeight());
+            g.drawImage(m_img, 0, 0, this);
+            paintBorder(g);
+        }
+
+        public Dimension getMinimumSize() {
+            return getPreferredSize();
+        }
+
+        public void setScaledSize(int i, int j) {
+            m_w = i;
+            m_h = j;
+            m_img = m_source.getScaledInstance(m_w, m_h, 4);
+            repaint();
+        }
+    }
+
+    class PreviewContainer extends JPanel {
+        protected int V_GAP;
+        protected int H_GAP;
+
+        PreviewContainer() {
+            V_GAP = 10;
+            H_GAP = 16;
+        }
+
+        public Dimension getMaximumSize() {
+            return getPreferredSize();
+        }
+
+        public void doLayout() {
+            Insets insets = getInsets();
+            int i = insets.left + H_GAP;
+            int j = insets.top + V_GAP;
+            int k = getComponentCount();
+
+            if (k == 0) {
+                return;
+            }
+
+            Component component = getComponent(0);
+            Dimension dimension = component.getPreferredSize();
+            int l = dimension.width;
+            int i1 = dimension.height;
+            Dimension dimension1 = getParent().getSize();
+            int j1 = Math.max((dimension1.width - H_GAP) / (l + H_GAP), 1);
+            int k1 = k / j1;
+
+            if ((k1 * j1) < k) {
+                k1++;
+            }
+
+            int l1 = 0;
+
+            for (int i2 = 0; i2 < k1; i2++) {
+                for (int j2 = 0; j2 < j1; j2++) {
+                    if (l1 >= k) {
+                        return;
+                    }
+
+                    Component component1 = getComponent(l1++);
+                    component1.setBounds(i, j, l, i1);
+                    i += (l + H_GAP);
+                }
+
+                j += (i1 + V_GAP);
+                i = insets.left + H_GAP;
+            }
+        }
+
+        public Dimension getMinimumSize() {
+            return getPreferredSize();
+        }
+
+        public Dimension getPreferredSize() {
+            int i = getComponentCount();
+
+            if (i == 0) {
+                return new Dimension(H_GAP, V_GAP);
+            }
+
+            Component component = getComponent(0);
+            Dimension dimension = component.getPreferredSize();
+            int j = dimension.width;
+            int k = dimension.height;
+            Dimension dimension1 = getParent().getSize();
+            int l = Math.max((dimension1.width - H_GAP) / (j + H_GAP), 1);
+            int i1 = i / l;
+
+            if ((i1 * l) < i) {
+                i1++;
+            }
+
+            int j1 = (l * (j + H_GAP)) + H_GAP;
+            int k1 = (i1 * (k + V_GAP)) + V_GAP;
+            Insets insets = getInsets();
+
+            return new Dimension(j1 + insets.left + insets.right,
+                k1 + insets.top + insets.bottom);
+        }
+    }
+}
diff --git a/src/com/sshtools/common/ui/PrintPreviewAction.java b/src/com/sshtools/common/ui/PrintPreviewAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..b65a44b672a064c77794b755ed411214902a4cf0
--- /dev/null
+++ b/src/com/sshtools/common/ui/PrintPreviewAction.java
@@ -0,0 +1,51 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.event.KeyEvent;
+
+import javax.swing.Action;
+import javax.swing.KeyStroke;
+
+
+public abstract class PrintPreviewAction extends StandardAction {
+    public PrintPreviewAction() {
+        putValue(Action.NAME, "Print Preview");
+        putValue(Action.SMALL_ICON,
+            getIcon("/com/sshtools/common/ui/printpreview.png"));
+        putValue(Action.SHORT_DESCRIPTION, "Print Preview");
+        putValue(Action.LONG_DESCRIPTION, "Preview what would be printed");
+        putValue(Action.MNEMONIC_KEY, new Integer('r'));
+        putValue(Action.ACCELERATOR_KEY,
+            KeyStroke.getKeyStroke(KeyEvent.VK_E, KeyEvent.ALT_MASK));
+        putValue(Action.ACTION_COMMAND_KEY, "print-preview-command");
+        putValue(StandardAction.ON_MENUBAR, new Boolean(true));
+        putValue(StandardAction.MENU_NAME, "File");
+        putValue(StandardAction.MENU_ITEM_GROUP, new Integer(80));
+        putValue(StandardAction.MENU_ITEM_WEIGHT, new Integer(10));
+        putValue(StandardAction.ON_TOOLBAR, new Boolean(false));
+    }
+}
diff --git a/src/com/sshtools/common/ui/RecordAction.java b/src/com/sshtools/common/ui/RecordAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..f9dbfbb3eeaa47d16c4a39b3a905a4fdaac0a415
--- /dev/null
+++ b/src/com/sshtools/common/ui/RecordAction.java
@@ -0,0 +1,70 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.event.KeyEvent;
+
+import javax.swing.Action;
+import javax.swing.KeyStroke;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class RecordAction extends StandardAction {
+    private final static String ACTION_COMMAND_KEY_RECORD = "record-command";
+    private final static String NAME_RECORD = "Record";
+    private final static String SMALL_ICON_RECORD = "/com/sshtools/common/ui/record.png";
+    private final static String LARGE_ICON_RECORD = "";
+    private final static String SHORT_DESCRIPTION_RECORD = "Record output";
+    private final static String LONG_DESCRIPTION_RECORD = "Record all output to a file";
+    private final static int MNEMONIC_KEY_RECORD = 'R';
+
+    /**
+* Creates a new RecordAction object.
+*/
+    public RecordAction() {
+        putValue(Action.NAME, NAME_RECORD);
+        putValue(Action.SMALL_ICON, getIcon(SMALL_ICON_RECORD));
+        putValue(LARGE_ICON, getIcon(LARGE_ICON_RECORD));
+        putValue(Action.SHORT_DESCRIPTION, SHORT_DESCRIPTION_RECORD);
+        putValue(Action.LONG_DESCRIPTION, LONG_DESCRIPTION_RECORD);
+        putValue(Action.ACCELERATOR_KEY,
+            KeyStroke.getKeyStroke(KeyEvent.VK_R, KeyEvent.ALT_MASK));
+        putValue(Action.MNEMONIC_KEY, new Integer(MNEMONIC_KEY_RECORD));
+        putValue(Action.ACTION_COMMAND_KEY, ACTION_COMMAND_KEY_RECORD);
+        putValue(StandardAction.ON_MENUBAR, new Boolean(true));
+        putValue(StandardAction.MENU_NAME, "File");
+        putValue(StandardAction.MENU_ITEM_GROUP, new Integer(60));
+        putValue(StandardAction.MENU_ITEM_WEIGHT, new Integer(20));
+        putValue(StandardAction.ON_TOOLBAR, new Boolean(true));
+        putValue(StandardAction.TOOLBAR_GROUP, new Integer(60));
+        putValue(StandardAction.TOOLBAR_WEIGHT, new Integer(20));
+    }
+}
diff --git a/src/com/sshtools/common/ui/RefreshAction.java b/src/com/sshtools/common/ui/RefreshAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..f7567ee879d5e485ccca5452b393ac1762f0ff33
--- /dev/null
+++ b/src/com/sshtools/common/ui/RefreshAction.java
@@ -0,0 +1,70 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.event.KeyEvent;
+
+import javax.swing.Action;
+import javax.swing.KeyStroke;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class RefreshAction extends StandardAction {
+    private final static String ACTION_COMMAND_KEY_REFRESH = "refresh-command";
+    private final static String NAME_REFRESH = "Refresh";
+    private final static String SMALL_ICON_REFRESH = "/com/sshtools/common/ui/refresh.png";
+    private final static String LARGE_ICON_REFRESH = "";
+    private final static String SHORT_DESCRIPTION_REFRESH = "Refresh terminal";
+    private final static String LONG_DESCRIPTION_REFRESH = "Refresh the terminal screen";
+    private final static int MNEMONIC_KEY_REFRESH = 'R';
+
+    /**
+* Creates a new RefreshAction object.
+*/
+    public RefreshAction() {
+        putValue(Action.NAME, NAME_REFRESH);
+        putValue(Action.SMALL_ICON, getIcon(SMALL_ICON_REFRESH));
+        putValue(LARGE_ICON, getIcon(LARGE_ICON_REFRESH));
+        putValue(Action.SHORT_DESCRIPTION, SHORT_DESCRIPTION_REFRESH);
+        putValue(Action.LONG_DESCRIPTION, LONG_DESCRIPTION_REFRESH);
+        putValue(Action.MNEMONIC_KEY, new Integer(MNEMONIC_KEY_REFRESH));
+        putValue(Action.ACTION_COMMAND_KEY, ACTION_COMMAND_KEY_REFRESH);
+        putValue(Action.ACCELERATOR_KEY,
+            KeyStroke.getKeyStroke(KeyEvent.VK_F5, KeyEvent.ALT_MASK));
+        putValue(StandardAction.MENU_NAME, "View");
+        putValue(StandardAction.ON_MENUBAR, new Boolean(true));
+        putValue(StandardAction.MENU_ITEM_GROUP, new Integer(20));
+        putValue(StandardAction.MENU_ITEM_WEIGHT, new Integer(10));
+        putValue(StandardAction.ON_TOOLBAR, new Boolean(true));
+        putValue(StandardAction.TOOLBAR_GROUP, new Integer(5));
+        putValue(StandardAction.TOOLBAR_WEIGHT, new Integer(20));
+    }
+}
diff --git a/src/com/sshtools/common/ui/ResourceIcon.java b/src/com/sshtools/common/ui/ResourceIcon.java
new file mode 100644
index 0000000000000000000000000000000000000000..79fdca667a0d2482e70732c22e564326c6b4fc00
--- /dev/null
+++ b/src/com/sshtools/common/ui/ResourceIcon.java
@@ -0,0 +1,136 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.awt.Image;
+import java.awt.Toolkit;
+
+import java.io.FilePermission;
+
+import java.net.URL;
+
+import java.security.AccessControlException;
+import java.security.AccessController;
+
+import javax.swing.ImageIcon;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.20 $
+ */
+public class ResourceIcon extends ImageIcon {
+    private static Log log = LogFactory.getLog(ResourceIcon.class.getName());
+    Class cls;
+
+    /**
+* Creates a new ResourceIcon object.
+*
+* @param cls
+* @param image
+*/
+    public ResourceIcon(Class cls, String image) {
+        super();
+        this.cls = cls;
+
+        if (image.startsWith("/")) {
+            loadImage(image);
+        } else {
+            String path = "/" + cls.getPackage().getName();
+            path = path.replace('.', '/');
+            path += ("/" + image);
+            loadImage(path);
+        }
+    }
+
+    /**
+* Creates a new ResourceIcon object.
+*
+* @param url
+*/
+    public ResourceIcon(URL url) {
+        super(url);
+    }
+
+    /**
+* Creates a new ResourceIcon object.
+*
+* @param imageName
+* @deprecated Having this available is now bad practice since most of our
+* software is plugable; each class requesting a resource should do so from
+* the class loader that loaded the class, to keep track of images a class
+* should also not be requesting a resource that is outside its own package.
+*
+* For resources outside of a package, we should think about creating static
+* helper class to store them.
+*
+ * Use the ResourceIcon(Class cls, String image) constructor instead providing
+* the class instance of the class using the image.
+*
+*/
+    public ResourceIcon(String imageName) {
+        super();
+        this.cls = getClass();
+        loadImage(imageName);
+    }
+
+    /**
+*
+*
+* @param imageName
+*/
+    protected void loadImage(String imageName) {
+        Image image = null;
+        URL url = cls.getResource(imageName);
+
+        if (url != null) {
+            log.debug(url.toString());
+            image = Toolkit.getDefaultToolkit().getImage(url);
+        } else {
+            try {
+                if (System.getSecurityManager() != null) {
+                    AccessController.checkPermission(new FilePermission(
+                            imageName, "read"));
+                }
+
+                image = Toolkit.getDefaultToolkit().getImage(imageName);
+            } catch (AccessControlException ace) {
+                log.error("Icon " + imageName + " could not be located as a " +
+                    "resource, and the current security manager will not " +
+                    "allow checking for a local file.");
+            }
+        }
+
+        if (image != null) {
+            this.setImage(image);
+        }
+    }
+}
diff --git a/src/com/sshtools/common/ui/SaveAction.java b/src/com/sshtools/common/ui/SaveAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..bf25852affa70031cb15b3cab89e5970c2b4728b
--- /dev/null
+++ b/src/com/sshtools/common/ui/SaveAction.java
@@ -0,0 +1,70 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.event.KeyEvent;
+
+import javax.swing.Action;
+import javax.swing.KeyStroke;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class SaveAction extends StandardAction {
+    private final static String ACTION_COMMAND_KEY_SAVE = "save-command";
+    private final static String NAME_SAVE = "Save";
+    private final static String SMALL_ICON_SAVE = "/com/sshtools/common/ui/save.png";
+    private final static String LARGE_ICON_SAVE = "";
+    private final static String SHORT_DESCRIPTION_SAVE = "Save the current connection";
+    private final static String LONG_DESCRIPTION_SAVE = "Save the current connection properties to file";
+    private final static int MNEMONIC_KEY_SAVE = 'S';
+
+    /**
+* Creates a new SaveAction object.
+*/
+    public SaveAction() {
+        putValue(Action.NAME, NAME_SAVE);
+        putValue(Action.SMALL_ICON, getIcon(SMALL_ICON_SAVE));
+        putValue(LARGE_ICON, getIcon(LARGE_ICON_SAVE));
+        putValue(Action.SHORT_DESCRIPTION, SHORT_DESCRIPTION_SAVE);
+        putValue(Action.ACCELERATOR_KEY,
+            KeyStroke.getKeyStroke(KeyEvent.VK_S, KeyEvent.ALT_MASK));
+        putValue(Action.LONG_DESCRIPTION, LONG_DESCRIPTION_SAVE);
+        putValue(Action.MNEMONIC_KEY, new Integer(MNEMONIC_KEY_SAVE));
+        putValue(Action.ACTION_COMMAND_KEY, ACTION_COMMAND_KEY_SAVE);
+        putValue(StandardAction.MENU_NAME, "File");
+        putValue(StandardAction.MENU_ITEM_GROUP, new Integer(0));
+        putValue(StandardAction.ON_MENUBAR, new Boolean(true));
+        putValue(StandardAction.MENU_ITEM_WEIGHT, new Integer(50));
+        putValue(StandardAction.ON_TOOLBAR, new Boolean(true));
+        putValue(StandardAction.TOOLBAR_GROUP, new Integer(0));
+        putValue(StandardAction.TOOLBAR_WEIGHT, new Integer(20));
+    }
+}
diff --git a/src/com/sshtools/common/ui/SaveAsAction.java b/src/com/sshtools/common/ui/SaveAsAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..0917910d5801645a4860587f1965b098b690b7f4
--- /dev/null
+++ b/src/com/sshtools/common/ui/SaveAsAction.java
@@ -0,0 +1,55 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import javax.swing.Action;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class SaveAsAction extends StandardAction {
+    /**
+* Creates a new SaveAsAction object.
+*/
+    public SaveAsAction() {
+        putValue(Action.NAME, "Save As");
+        putValue(Action.SMALL_ICON, new EmptyIcon(16, 16));
+        putValue(Action.SHORT_DESCRIPTION, "Save the connection");
+        putValue(Action.LONG_DESCRIPTION,
+            "Save the connection as a different file");
+        putValue(Action.MNEMONIC_KEY, new Integer('v'));
+        putValue(Action.ACTION_COMMAND_KEY, "saveas-command");
+        putValue(StandardAction.MENU_NAME, "File");
+        putValue(StandardAction.MENU_ITEM_GROUP, new Integer(0));
+        putValue(StandardAction.ON_MENUBAR, new Boolean(true));
+        putValue(StandardAction.MENU_ITEM_WEIGHT, new Integer(51));
+        putValue(StandardAction.ON_TOOLBAR, new Boolean(false));
+    }
+}
diff --git a/src/com/sshtools/common/ui/SessionManager.java b/src/com/sshtools/common/ui/SessionManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..c8e8dc7007f59a657fe5365e983083fceccf48b2
--- /dev/null
+++ b/src/com/sshtools/common/ui/SessionManager.java
@@ -0,0 +1,171 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import com.sshtools.common.configuration.SshToolsConnectionProfile;
+
+import com.sshtools.j2ssh.SftpClient;
+import com.sshtools.j2ssh.SshEventAdapter;
+import com.sshtools.j2ssh.connection.Channel;
+import com.sshtools.j2ssh.connection.ChannelFactory;
+import com.sshtools.j2ssh.forwarding.ForwardingClient;
+import com.sshtools.j2ssh.session.SessionChannelClient;
+
+import java.io.IOException;
+
+
+/**
+ * <p>This interface is used by the Session Provider framework to abstract
+ * the SshClient connection away from the session provider. This restricts
+ * the session to performing operations that are allowed by the controlling
+ * application. For instance, the provider cannot simply diconnect the
+ * connection, since the SshClient's disconnect method is not exposed, instead
+ * a <code>requestDisconnect</code> method is provided allowing the controlling
+ * application to simply ignore a disconnect since it may have other sessions
+ * open.
+ * </p>
+ *
+ * <p>
+ * Most of the methods of this interface will simply be required to call the
+ * identical method on SshClient.
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Revision: 1.11 $
+ */
+public interface SessionManager {
+    /**
+* Opens a session on the managed connection.
+*
+* @return
+*
+* @throws IOException
+*/
+    public SessionChannelClient openSession() throws IOException;
+
+    /**
+* The session can call this method to apply any changes to the profile it
+* may have made.
+*
+* @param profile
+*/
+    public void applyProfileChanges(SshToolsConnectionProfile profile);
+
+    /**
+* Opens an SftpClient on the managed connection.
+*
+* @return
+*
+* @throws IOException
+*/
+    public SftpClient openSftpClient() throws IOException;
+
+    /**
+* Opens a channel on the managed connection.
+*
+* @param channel
+*
+* @return
+*
+* @throws IOException
+*/
+    public boolean openChannel(Channel channel) throws IOException;
+
+    /**
+* Determine if the managed connection is still connected.
+*
+* @return
+*/
+    public boolean isConnected();
+
+    /**
+* Called when a session wants to disconnect the connection. The manager
+* implementation should ideally not diconnect unless no other sessions
+* are open.
+*
+* @return
+*/
+    public boolean requestDisconnect();
+
+    /**
+* Gets the managed connections port forwarding client.
+*
+* @return
+*/
+    public ForwardingClient getForwardingClient();
+
+    /**
+* Send a global request
+*
+* @param requestname
+* @param wantreply
+* @param requestdata
+*
+* @return
+*
+* @throws IOException
+*/
+    public byte[] sendGlobalRequest(String requestname, boolean wantreply,
+        byte[] requestdata) throws IOException;
+
+    /**
+* Add an event handler to the managed connection
+*
+* @param eventHandler
+*/
+    public void addEventHandler(SshEventAdapter eventHandler);
+
+    /**
+* Gets the identification string supplied by the server.
+*
+* @return
+*/
+    public String getServerId();
+
+    /**
+* Returns the guessed EOL setting of the remote computer
+* @return
+*/
+    public int getRemoteEOL();
+
+    /**
+* Adds a channel factory to the managed connection.
+*
+* @param channelType
+* @param cf
+*
+* @throws IOException
+*/
+    public void allowChannelOpen(String channelType, ChannelFactory cf)
+        throws IOException;
+
+    /**
+* Get the current profile attached to the session.
+*
+* @return
+*/
+    public SshToolsConnectionProfile getProfile();
+}
diff --git a/src/com/sshtools/common/ui/SessionProvider.java b/src/com/sshtools/common/ui/SessionProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..b37df095378e4420e911e5ab8a750cbf3c16198e
--- /dev/null
+++ b/src/com/sshtools/common/ui/SessionProvider.java
@@ -0,0 +1,172 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+
+/**
+ * <p>Instances of this class are created by the <code>SessionProviderFactory</code>
+ * for each installed session provider. Instances of this class can be supplied
+ * to the <code>SessionProviderFrame</code> to create windows contains the
+ * session providers service</p>
+ *
+ * @author Lee David Painter
+ * @version $Id: SessionProvider.java,v 1.12 2003/09/22 15:57:57 martianx Exp $
+ */
+public class SessionProvider {
+    String id;
+    String name;
+    Class cls;
+    String description;
+    char mnemonic;
+    ResourceIcon smallicon;
+    ResourceIcon largeicon;
+    Class[] propertypages;
+    int weight;
+    Class optionsClass;
+
+    SessionProvider(String id, String name, Class cls, String description,
+        String mnemonic, String smallicon, String largeicon,
+        Class optionsClass, Class[] propertypages, int weight) {
+        this.id = id;
+        this.name = name;
+        this.cls = cls;
+        this.description = description;
+        this.mnemonic = mnemonic.charAt(0);
+        this.propertypages = propertypages;
+        this.optionsClass = optionsClass;
+        this.smallicon = new ResourceIcon(cls, smallicon);
+        this.largeicon = new ResourceIcon(cls, largeicon);
+        this.weight = weight;
+    }
+
+    /**
+* Get the name of the provider e.g. 'Terminal Session'.
+* @return
+*/
+    public String getName() {
+        return name;
+    }
+
+    /**
+* Get the class instance for the session providers implementation.
+* @return
+*/
+    public Class getProviderClass() {
+        return cls;
+    }
+
+    /**
+* Get an array of class instances for the providers property pages.
+* @return
+*/
+    public Class[] getPropertyPages() {
+        return propertypages;
+    }
+
+    /**
+* Get the description of the provider e.g. 'Opens a terminal session'
+* @return
+*/
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+* Get the mnemonic character for key access
+* @return
+*/
+    public char getMnemonic() {
+        return mnemonic;
+    }
+
+    /**
+* Get the weight of the provider.
+* @return
+*/
+    public int getWeight() {
+        return weight;
+    }
+
+    /**
+* Get the id of the provider e.g. 'sshterm'.
+* @return
+*/
+    public String getId() {
+        return id;
+    }
+
+    /**
+* Get the small icon of the provider.
+* @return
+*/
+    public ResourceIcon getSmallIcon() {
+        return smallicon;
+    }
+
+    /**
+* Get the large icon of the provider.
+* @return
+*/
+    public ResourceIcon getLargeIcon() {
+        return largeicon;
+    }
+
+    /**
+* Get the options class implementation
+* @return
+*/
+    public Class getOptionsClass() {
+        return optionsClass;
+    }
+
+    /**
+* Compares this session provider against another object. This method
+* will only return true if the object provided is an instance of
+* <code>SessionProvider</code> and that the provider id and implementation
+* class are equal.
+*
+* @param obj
+* @return
+*/
+    public boolean equals(Object obj) {
+        if ((obj != null) && obj instanceof SessionProvider) {
+            SessionProvider provider = (SessionProvider) obj;
+
+            return provider.id.equals(id) &&
+            provider.getProviderClass().equals(cls);
+        }
+
+        return false;
+    }
+
+    /**
+* Returns the name of the provider.
+* @return
+*/
+    public String toString() {
+        return name;
+    }
+}
diff --git a/src/com/sshtools/common/ui/SessionProviderAction.java b/src/com/sshtools/common/ui/SessionProviderAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..bc9d777daddb124bc0dd077270774150071e970a
--- /dev/null
+++ b/src/com/sshtools/common/ui/SessionProviderAction.java
@@ -0,0 +1,55 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import javax.swing.Action;
+
+
+public class SessionProviderAction extends StandardAction {
+    SessionProvider provider;
+
+    public SessionProviderAction(SessionProvider provider) {
+        this.provider = provider;
+        putValue(Action.NAME, provider.getName());
+        putValue(Action.SMALL_ICON, provider.getSmallIcon());
+        putValue(LARGE_ICON, provider.getLargeIcon());
+        putValue(Action.SHORT_DESCRIPTION, provider.getDescription());
+        putValue(Action.LONG_DESCRIPTION, provider.getDescription());
+        putValue(Action.MNEMONIC_KEY, new Integer(provider.getMnemonic()));
+        putValue(Action.ACTION_COMMAND_KEY, provider.getName());
+        putValue(StandardAction.ON_MENUBAR, new Boolean(true));
+        putValue(StandardAction.MENU_NAME, "Tools");
+        putValue(StandardAction.MENU_ITEM_GROUP, new Integer(10));
+        putValue(StandardAction.MENU_ITEM_WEIGHT, new Integer(70));
+        putValue(StandardAction.ON_TOOLBAR, new Boolean(true));
+        putValue(StandardAction.TOOLBAR_GROUP, new Integer(40));
+        putValue(StandardAction.TOOLBAR_WEIGHT, new Integer(20));
+    }
+
+    public SessionProvider getProvider() {
+        return provider;
+    }
+}
diff --git a/src/com/sshtools/common/ui/SessionProviderFactory.java b/src/com/sshtools/common/ui/SessionProviderFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..f21766857de4d08d4eecb48ab23f94898f321a20
--- /dev/null
+++ b/src/com/sshtools/common/ui/SessionProviderFactory.java
@@ -0,0 +1,193 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+import com.sshtools.j2ssh.io.IOUtil;
+import com.sshtools.j2ssh.util.ExtensionClassLoader;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import java.net.URL;
+
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Properties;
+import java.util.Vector;
+
+
+/**
+ *
+ * <p>This class is responsible for dynamically loading all the installed
+ * session providers. A session provider can be used with
+ * <code>SessionProviderFrame</code> to integrate an ssh service such as
+ * a terminal window or sftp window within another application.</p>
+ *
+ * <p>To install a session provider you should provide a special properties
+ * file resource at the root of your source tree called 'session.provider'.</p>
+ *
+ * <blockquote><pre>
+ * This properties file should have the following properties defined:
+ *
+ * provider.id= [The unique name of the provider e.g 'sshterm']
+     * provider.name= [The descriptive name of the provider e.g. 'Terminal Session']
+ * provider.class= [Fully qualified classname of the provider implementation]
+ * provider.shortdesc= [A short description of the provider]
+ * provider.smallicon= [The providers small icon, must be filename only and be
+ * placed in the same package as the provider class implementation]
+ * provider.largeicon= [The providers large icon, must be filename only and be
+ * placed in the same package as the provider class implementation]
+ * provider.mnemonic= [The mnemonic character]
+ * provider.options= [The options panel implementation, must implement
+ * com.sshtools.common.ui.OptionsTab]
+ * property.page.1= [An number of property page panels, must implement
+ * com.sshtools.common.ui.SshToolsConnectionTab]
+ * property.page.2= [More property pages added like this]
+ * provider.weight= [Weight setting, used to order providers]
+ * </pre></blockquote>
+ *
+ * </p>
+ * @author Lee David Painter
+ * @version $Id: SessionProviderFactory.java,v 1.12 2003/09/22 15:57:57 martianx Exp $
+ */
+public class SessionProviderFactory {
+    private static Log log = LogFactory.getLog(SessionProviderFactory.class);
+    private static SessionProviderFactory instance;
+    HashMap providers = new HashMap();
+
+    SessionProviderFactory() {
+        ExtensionClassLoader classloader = ConfigurationLoader.getExtensionClassLoader();
+
+        try {
+            Enumeration en = classloader.getResources("session.provider");
+            URL url = null;
+            Properties properties;
+            InputStream in;
+            SessionProvider provider;
+            String name;
+            String id;
+
+            while (en.hasMoreElements()) {
+                try {
+                    url = (URL) en.nextElement();
+                    in = url.openStream();
+                    properties = new Properties();
+                    properties.load(in);
+                    IOUtil.closeStream(in);
+
+                    if (properties.containsKey("provider.class") &&
+                            properties.containsKey("provider.name")) {
+                        Class cls = classloader.loadClass(properties.getProperty(
+                                    "provider.class"));
+                        String optionsClassName = properties.getProperty(
+                                "provider.options");
+                        Class optionsClass = ((optionsClassName == null) ||
+                            optionsClassName.equals("")) ? null
+                                                         : classloader.loadClass(optionsClassName);
+                        String pageclass;
+                        int num = 1;
+                        Vector pages = new Vector();
+
+                        do {
+                            pageclass = properties.getProperty("property.page." +
+                                    String.valueOf(num), null);
+
+                            if (pageclass != null) {
+                                pages.add(classloader.loadClass(pageclass));
+                                num++;
+                            }
+                        } while (pageclass != null);
+
+                        Class[] propertypages = new Class[pages.size()];
+                        pages.toArray(propertypages);
+                        name = properties.getProperty("provider.name");
+
+                        int weight = Integer.parseInt(properties.getProperty(
+                                    "provider.weight"));
+                        id = properties.getProperty("provider.id", name);
+                        provider = new SessionProvider(id, name, cls,
+                                properties.getProperty("provider.shortdesc"),
+                                properties.getProperty("provider.mnemonic"),
+                                properties.getProperty("provider.smallicon"),
+                                properties.getProperty("provider.largeicon"),
+                                optionsClass, propertypages, weight);
+                        providers.put(id, provider);
+                        log.info("Installed " + provider.getName() +
+                            " session provider");
+                    }
+                } catch (ClassNotFoundException ex) {
+                    log.warn("Session provider class not found", ex);
+                } catch (IOException ex) {
+                    log.warn("Failed to read " + url.toExternalForm(), ex);
+                }
+            }
+        } catch (IOException ex) {
+        }
+    }
+
+    /**
+* Get all the installed SessionProvider's.
+*
+* @return A list containing instances of <code>SessionProvider</code>
+*/
+    public List getSessionProviders() {
+        return new Vector(providers.values());
+    }
+
+    /**
+* <p>Get a <code>SessionProvider</code> by its id. The id is defined by the
+ * provider.id property in the providers 'session.provider' resource file.</p>
+*
+* <p>Session providers that are currently defined within the SSHTools source
+* tree are:<br>
+* <blockquote><pre>
+* sshterm   - Terminal session provider
+* shift     - SFTP session provider
+* tunneling - Secure port forwarding provider
+* sshvnc    - VNC session provider
+* </pre></blockquote>
+*
+* @param id the id of the SessionProvider.
+* @return
+*/
+    public SessionProvider getProvider(String id) {
+        return (SessionProvider) providers.get(id);
+    }
+
+    /**
+* Get the one time instance of the factory.
+* @return
+*/
+    public static SessionProviderFactory getInstance() {
+        return (instance == null) ? (instance = new SessionProviderFactory())
+                                  : instance;
+    }
+}
diff --git a/src/com/sshtools/common/ui/SessionProviderFrame.java b/src/com/sshtools/common/ui/SessionProviderFrame.java
new file mode 100644
index 0000000000000000000000000000000000000000..228d361a4479ae57fcdf85972442d82acff51ad5
--- /dev/null
+++ b/src/com/sshtools/common/ui/SessionProviderFrame.java
@@ -0,0 +1,287 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import com.sshtools.common.configuration.SshToolsConnectionProfile;
+
+import com.sshtools.j2ssh.SftpClient;
+import com.sshtools.j2ssh.SshClient;
+import com.sshtools.j2ssh.SshEventAdapter;
+import com.sshtools.j2ssh.connection.Channel;
+import com.sshtools.j2ssh.connection.ChannelFactory;
+import com.sshtools.j2ssh.forwarding.ForwardingClient;
+import com.sshtools.j2ssh.session.SessionChannelClient;
+
+import java.awt.BorderLayout;
+
+import java.io.IOException;
+
+
+/**
+ * <p>This frame class embeds a SessionProvider and manages the connection
+ * on behalf of the caller. To invoke a session provider from an external
+ * application is a straight forward procedure. Assuming that the connection
+ * has already been established [see SshClient] you can invoke a frame using
+ * the following code:</p>
+ *
+ * <blockquote><pre>
+ * // Create an SshClient connection
+ * SshClient ssh = new SshClient();
+ *
+ * // Connection code goes here - see SshClient for more details
+ *
+ * SessionProviderFrame frame = new SessionProviderFrame(null,
+ *                            new SshToolsConnectionProfile(),
+ *                                                        ssh,
+ *          SessionProviderFactory.getInstance().getProvider("sshterm"));
+ * frame.pack();
+ * frame.show();
+ * </pre></blockquote>
+ *
+ * @author Lee David Painter
+ * @version $Id: SessionProviderFrame.java,v 1.9 2003/11/16 19:30:08 rpernavas Exp $
+ */
+public class SessionProviderFrame extends SshToolsApplicationFrame
+    implements SessionManager {
+    //  Private instance variables
+    private SshToolsApplicationSessionPanel panel;
+    private SessionProvider provider;
+    private SshToolsConnectionProfile profile;
+    private SshClient ssh;
+    private boolean disconnectOnClose = false;
+
+    /**
+* Construct a new Session Provider frame.
+*
+* @param app The SshToolsApplication instance, can be null
+* @param profile The profile of the connection
+* @param ssh the client connection
+* @param provider the provider instance
+* @throws IOException
+* @throws SshToolsApplicationException
+*/
+    public SessionProviderFrame(SshToolsConnectionProfile profile,
+        SshClient ssh, SessionProvider provider)
+        throws IOException, SshToolsApplicationException {
+        try {
+            this.provider = provider;
+            this.ssh = ssh;
+            this.profile = profile;
+            setIconImage(provider.getSmallIcon().getImage());
+            setTitle(provider.getName() + " - " +
+                ssh.getConnectionProperties().getHost());
+            getContentPane().setLayout(new BorderLayout());
+            getContentPane().add(panel = (SshToolsApplicationSessionPanel) provider.getProviderClass()
+                                                                                   .newInstance(),
+                BorderLayout.CENTER);
+
+            return;
+        } catch (IllegalAccessException ex) {
+        } catch (InstantiationException ex) {
+        }
+
+        throw new SshToolsApplicationException("Failed to create instance of " +
+            provider.getProviderClass().getName());
+    }
+
+    /**
+* Initialize the frame and open the remote session
+* @param app the application object, can be null
+* @return
+* @throws IOException
+* @throws SshToolsApplicationException
+*/
+    public boolean initFrame(SshToolsApplication app)
+        throws IOException, SshToolsApplicationException {
+        panel.setCurrentConnectionProfile(profile);
+        panel.init(app);
+        init(app, panel);
+        pack();
+
+        return panel.openSession(this, profile);
+    }
+
+    /**
+* Get the attached session provider panel.
+* @return
+*/
+    public SshToolsApplicationSessionPanel getSessionPanel() {
+        return panel;
+    }
+
+    /**
+* Returns the guessed EOL setting of the remote computer
+* @return
+*/
+    public int getRemoteEOL() {
+        return ssh.getRemoteEOL();
+    }
+
+    /**
+* Called by the application framework when testing exit state
+* @return
+*/
+    public boolean canExit() {
+        return panel.canClose();
+    }
+
+    /**
+ * Called by the framework when exiting. Can also be called to close the session.
+*/
+    public void exit() {
+        panel.close();
+        dispose();
+    }
+
+    /**
+* Implementation of the SessionManager method, simply calls the SshClient
+* openSession method.
+* @return
+* @throws IOException
+*/
+    public SessionChannelClient openSession() throws IOException {
+        return ssh.openSessionChannel();
+    }
+
+    /**
+ * Implementation of the SessionManager method, this does nothing. Overide this
+ * method to provide additional functionality to save changes made by the session
+* to the profile.
+*
+* @param profile
+*/
+    public void applyProfileChanges(SshToolsConnectionProfile profile) {
+    }
+
+    /**
+* When the session closes, should the connection be disconnected?
+* @param disconnectOnClose
+*/
+    public void setDisconnectOnClose(boolean disconnectOnClose) {
+        this.disconnectOnClose = disconnectOnClose;
+    }
+
+    /**
+ * Implementation of the SessionManager method, this simply calls the SshClient
+* method openSftpClient.
+* @return
+* @throws IOException
+*/
+    public SftpClient openSftpClient() throws IOException {
+        return ssh.openSftpClient();
+    }
+
+    /**
+ * Implementation of the SessionManager method, this simply calls the SshClient
+* method openChannel.
+* @param channel
+* @return
+* @throws IOException
+*/
+    public boolean openChannel(Channel channel) throws IOException {
+        return ssh.openChannel(channel);
+    }
+
+    /**
+ * Implementation of the SessionManager method, this simply calls the SshClient
+* method isConnected.
+* @return
+*/
+    public boolean isConnected() {
+        return ssh.isConnected();
+    }
+
+    /**
+* Implementation of the SessionManager method, this simply returns false.
+* Overide to change this behaviour
+*
+* @return
+*/
+    public boolean requestDisconnect() {
+        return disconnectOnClose;
+    }
+
+    /**
+* Implementation of the SessionManager method, simply calls the SshClient
+* method getForwardingClient.
+* @return
+*/
+    public ForwardingClient getForwardingClient() {
+        return ssh.getForwardingClient();
+    }
+
+    /**
+* Implementation of the SessionManager method, simply calls the SshClient
+* method sendGlobalRequest.
+* @param requestname
+* @param wantreply
+* @param requestdata
+* @return
+* @throws IOException
+*/
+    public byte[] sendGlobalRequest(String requestname, boolean wantreply,
+        byte[] requestdata) throws IOException {
+        return ssh.sendGlobalRequest(requestname, wantreply, requestdata);
+    }
+
+    /**
+* Implementation of the SessionManager method, simply calls the SshClient
+* method addEventHandler.
+* @param eventHandler
+*/
+    public void addEventHandler(SshEventAdapter eventHandler) {
+        ssh.addEventHandler(eventHandler);
+    }
+
+    /**
+* Implemenation of the SessionManager method, simply calls the SshClient
+* method getServerId.
+* @return
+*/
+    public String getServerId() {
+        return ssh.getServerId();
+    }
+
+    /**
+* Implemenation of the SessionManager method, simply calls the SshClient
+* method allowChannelOpen.
+* @param channelType
+* @param cf
+* @throws IOException
+*/
+    public void allowChannelOpen(String channelType, ChannelFactory cf)
+        throws IOException {
+        ssh.allowChannelOpen(channelType, cf);
+    }
+
+    /**
+* Gets the profile currently attached to the frame.
+* @return
+*/
+    public SshToolsConnectionProfile getProfile() {
+        return profile;
+    }
+}
diff --git a/src/com/sshtools/common/ui/SessionProviderInternalFrame.java b/src/com/sshtools/common/ui/SessionProviderInternalFrame.java
new file mode 100644
index 0000000000000000000000000000000000000000..6913d757701edfb0c5b17405858e45a04d30aece
--- /dev/null
+++ b/src/com/sshtools/common/ui/SessionProviderInternalFrame.java
@@ -0,0 +1,283 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.io.IOException;
+import java.awt.BorderLayout;
+import com.sshtools.common.configuration.SshToolsConnectionProfile;
+import com.sshtools.j2ssh.SftpClient;
+import com.sshtools.j2ssh.SshClient;
+import com.sshtools.j2ssh.SshEventAdapter;
+import com.sshtools.j2ssh.connection.Channel;
+import com.sshtools.j2ssh.connection.ChannelFactory;
+import com.sshtools.j2ssh.forwarding.ForwardingClient;
+import com.sshtools.j2ssh.session.SessionChannelClient;
+
+/**
+ * <p>This frame class embeds a SessionProvider and manages the connection
+ * on behalf of the caller. To invoke a session provider from an external
+ * application is a straight forward procedure. Assuming that the connection
+ * has already been established [see SshClient] you can invoke a frame using
+ * the following code:</p>
+ *
+ * <blockquote><pre>
+ * // Create an SshClient connection
+ * SshClient ssh = new SshClient();
+ *
+ * // Connection code goes here - see SshClient for more details
+ *
+ * SessionProviderFrame frame = new SessionProviderFrame(null,
+ *                            new SshToolsConnectionProfile(),
+ *                                                        ssh,
+ *          SessionProviderFactory.getInstance().getProvider("sshterm"));
+ * frame.pack();
+ * frame.show();
+ * </pre></blockquote>
+ *
+ * @author Lee David Painter
+ * @version $Id: SessionProviderInternalFrame.java,v 1.3 2003/09/24 11:26:32 martianx Exp $
+ */
+public class SessionProviderInternalFrame
+    extends SshToolsApplicationInternalFrame
+    implements SessionManager {
+  //  Private instance variables
+  private SshToolsApplicationSessionPanel panel;
+  private SessionProvider provider;
+  private SshToolsConnectionProfile profile;
+  private SshClient ssh;
+  private boolean disconnectOnClose = false;
+  /**
+   * Construct a new Session Provider frame.
+   *
+   * @param app The SshToolsApplication instance, can be null
+   * @param profile The profile of the connection
+   * @param ssh the client connection
+   * @param provider the provider instance
+   * @throws IOException
+   * @throws SshToolsApplicationException
+   */
+  public SessionProviderInternalFrame(SshToolsConnectionProfile profile,
+                                      SshClient ssh,
+                                      SessionProvider provider) throws
+      IOException, SshToolsApplicationException {
+    try {
+      this.provider = provider;
+      this.ssh = ssh;
+      this.profile = profile;
+      //setIconImage(provider.getSmallIcon().getImage());
+      setTitle(provider.getName() + " - " +
+               ssh.getConnectionProperties().getHost());
+      getContentPane().setLayout(new BorderLayout());
+      getContentPane().add(panel = (SshToolsApplicationSessionPanel)
+                           provider.getProviderClass().
+                           newInstance(), BorderLayout.CENTER);
+      return;
+    }
+    catch (IllegalAccessException ex) {
+    }
+    catch (InstantiationException ex) {
+    }
+    throw new SshToolsApplicationException("Failed to create instance of "
+                                           +
+                                           provider.getProviderClass().getName());
+  }
+
+  /**
+   * Initialize the frame and open the remote session
+   * @param app the application object, can be null
+   * @return
+   * @throws IOException
+   * @throws SshToolsApplicationException
+   */
+  public boolean initFrame(SshToolsApplication app) throws IOException,
+      SshToolsApplicationException {
+    panel.init(app);
+    init(app, panel);
+    pack();
+    return panel.openSession(this, profile);
+  }
+
+  /**
+   * Get the attached session provider panel.
+   * @return
+   */
+  public SshToolsApplicationSessionPanel getSessionPanel() {
+    return panel;
+  }
+
+  /**
+   * Called by the application framework when testing exit state
+   * @return
+   */
+  public boolean canExit() {
+    return panel.canClose();
+  }
+
+  /**
+       * Called by the framework when exiting. Can also be called to close the session.
+   */
+  public void exit() {
+    panel.close();
+    dispose();
+  }
+
+  /**
+   * Implementation of the SessionManager method, simply calls the SshClient
+   * openSession method.
+   * @return
+   * @throws IOException
+   */
+  public SessionChannelClient openSession() throws IOException {
+    return ssh.openSessionChannel();
+  }
+
+  /**
+   * Returns the guessed EOL setting of the remote computer
+   * @return
+   */
+  public int getRemoteEOL() {
+    return ssh.getRemoteEOL();
+  }
+
+  /**
+       * Implementation of the SessionManager method, this does nothing. Overide this
+       * method to provide additional functionality to save changes made by the session
+   * to the profile.
+   *
+   * @param profile
+   */
+  public void applyProfileChanges(SshToolsConnectionProfile profile) {
+  }
+
+  /**
+       * Implementation of the SessionManager method, this simply calls the SshClient
+   * method openSftpClient.
+   * @return
+   * @throws IOException
+   */
+  public SftpClient openSftpClient() throws IOException {
+    return ssh.openSftpClient();
+  }
+
+  /**
+       * Implementation of the SessionManager method, this simply calls the SshClient
+   * method openChannel.
+   * @param channel
+   * @return
+   * @throws IOException
+   */
+  public boolean openChannel(Channel channel) throws IOException {
+    return ssh.openChannel(channel);
+  }
+
+  /**
+       * Implementation of the SessionManager method, this simply calls the SshClient
+   * method isConnected.
+   * @return
+   */
+  public boolean isConnected() {
+    return ssh.isConnected();
+  }
+
+  /**
+   * When the session closes, should the connection be disconnected?
+   * @param disconnectOnClose
+   */
+  public void setDisconnectOnClose(boolean disconnectOnClose) {
+    this.disconnectOnClose = disconnectOnClose;
+  }
+
+  /**
+   * Implementation of the SessionManager method, this simply returns false.
+   * Overide to change this behaviour
+   *
+   * @return
+   */
+  public boolean requestDisconnect() {
+    return disconnectOnClose;
+  }
+
+  /**
+   * Implementation of the SessionManager method, simply calls the SshClient
+   * method getForwardingClient.
+   * @return
+   */
+  public ForwardingClient getForwardingClient() {
+    return ssh.getForwardingClient();
+  }
+
+  /**
+   * Implementation of the SessionManager method, simply calls the SshClient
+   * method sendGlobalRequest.
+   * @param requestname
+   * @param wantreply
+   * @param requestdata
+   * @return
+   * @throws IOException
+   */
+  public byte[] sendGlobalRequest(String requestname, boolean wantreply,
+                                  byte[] requestdata) throws IOException {
+    return ssh.sendGlobalRequest(requestname, wantreply, requestdata);
+  }
+
+  /**
+   * Implementation of the SessionManager method, simply calls the SshClient
+   * method addEventHandler.
+   * @param eventHandler
+   */
+  public void addEventHandler(SshEventAdapter eventHandler) {
+    ssh.addEventHandler(eventHandler);
+  }
+
+  /**
+   * Implemenation of the SessionManager method, simply calls the SshClient
+   * method getServerId.
+   * @return
+   */
+  public String getServerId() {
+    return ssh.getServerId();
+  }
+
+  /**
+   * Implemenation of the SessionManager method, simply calls the SshClient
+   * method allowChannelOpen.
+   * @param channelType
+   * @param cf
+   * @throws IOException
+   */
+  public void allowChannelOpen(String channelType, ChannelFactory cf) throws
+      IOException {
+    ssh.allowChannelOpen(channelType, cf);
+  }
+
+  /**
+   * Gets the profile currently attached to the frame.
+   * @return
+   */
+  public SshToolsConnectionProfile getProfile() {
+    return profile;
+  }
+}
diff --git a/src/com/sshtools/common/ui/SshToolsApplication.java b/src/com/sshtools/common/ui/SshToolsApplication.java
new file mode 100644
index 0000000000000000000000000000000000000000..61fd4e3922f7f7dd0c5d8f82aca39eaac310a2e6
--- /dev/null
+++ b/src/com/sshtools/common/ui/SshToolsApplication.java
@@ -0,0 +1,657 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import com.sshtools.common.mru.MRUList;
+import com.sshtools.common.mru.MRUListModel;
+import com.sshtools.common.util.BrowserLauncher;
+
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+import com.sshtools.j2ssh.io.IOUtil;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Cursor;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FilePermission;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+
+import java.security.AccessControlException;
+import java.security.AccessController;
+
+import java.util.Iterator;
+import java.util.Vector;
+
+import javax.swing.BorderFactory;
+import javax.swing.Icon;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.LookAndFeel;
+import javax.swing.UIManager;
+
+
+/**
+ * An abstract application class that provides container management, look
+ * and feel configuration and most recently used menus.
+ *
+ * @author Brett Smith
+ * @version $Revision: 1.19 $
+ */
+public abstract class SshToolsApplication {
+    /**  */
+    public final static String PREF_CONNECTION_LAST_HOST = "apps.connection.lastHost";
+
+    /**  */
+    public final static String PREF_CONNECTION_LAST_USER = "apps.connection.lastUser";
+
+    /**  */
+    public final static String PREF_CONNECTION_LAST_PORT = "apps.connection.lastPort";
+
+    /**  */
+    public final static String PREF_CONNECTION_LAST_KEY = "apps.connection.lastKey";
+
+    /**  */
+    public final static String PREF_LAF = "apps.laf";
+
+    /**  */
+    public final static String CROSS_PLATFORM_LAF = "CROSS_PLATFORM";
+
+    /**  */
+    public final static String DEFAULT_LAF = "DEFAULT";
+
+    /**  */
+    public final static String SYSTEM_LAF = "SYSTEM";
+
+    /**  */
+    protected static Vector containers = new Vector();
+
+    /**  */
+    protected static Log log = LogFactory.getLog(SshToolsApplication.class);
+
+    /**  */
+    protected static MRUListModel mruModel;
+    private static UIManager.LookAndFeelInfo[] allLookAndFeelInfo;
+
+    static {
+        UIManager.LookAndFeelInfo[] i;
+
+        try {
+            i = UIManager.getInstalledLookAndFeels();
+        } catch (Throwable t) {
+            i = new UIManager.LookAndFeelInfo[0];
+        }
+
+        allLookAndFeelInfo = new UIManager.LookAndFeelInfo[i.length + 3];
+        System.arraycopy(i, 0, allLookAndFeelInfo, 0, i.length);
+        allLookAndFeelInfo[i.length] = new UIManager.LookAndFeelInfo("Default",
+                DEFAULT_LAF);
+
+        allLookAndFeelInfo[i.length + 1] = new UIManager.LookAndFeelInfo("Cross Platform",
+                CROSS_PLATFORM_LAF);
+        allLookAndFeelInfo[i.length + 2] = new UIManager.LookAndFeelInfo("System",
+                SYSTEM_LAF);
+    }
+
+    /**  */
+    protected Class panelClass;
+
+    /**  */
+    protected Class defaultContainerClass;
+
+    /**  */
+    protected java.util.List additionalOptionsTabs;
+
+    /**
+* Creates a new SshToolsApplication object.
+*
+* @param panelClass
+* @param defaultContainerClass
+*/
+    public SshToolsApplication(Class panelClass, Class defaultContainerClass) {
+        this.panelClass = panelClass;
+        this.defaultContainerClass = defaultContainerClass;
+        additionalOptionsTabs = new java.util.ArrayList();
+
+        try {
+            if (System.getSecurityManager() != null) {
+                AccessController.checkPermission(new FilePermission(
+                        "<<ALL FILES>>", "write"));
+            }
+
+            File a = getApplicationPreferencesDirectory();
+
+            if (a == null) {
+                throw new AccessControlException(
+                    "Application preferences directory not specified.");
+            }
+
+            InputStream in = null;
+            MRUList mru = new MRUList();
+
+            try {
+                File f = new File(a, getApplicationName() + ".mru");
+
+                if (f.exists()) {
+                    if (log.isDebugEnabled()) {
+                        log.debug("Loading MRU from " + f.getAbsolutePath());
+                    }
+
+                    in = new FileInputStream(f);
+                    mru.reload(in);
+                } else {
+                    if (log.isDebugEnabled()) {
+                        log.debug("MRU file " + f.getAbsolutePath() +
+                            " doesn't exist, creating empty list");
+                    }
+                }
+            } catch (Exception e) {
+                log.error("Could not load MRU list.", e);
+            } finally {
+                IOUtil.closeStream(in);
+            }
+
+            mruModel = new MRUListModel();
+            mruModel.setMRUList(mru);
+        } catch (AccessControlException ace) {
+            log.error("Could not load MRU.", ace);
+        }
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public static UIManager.LookAndFeelInfo[] getAllLookAndFeelInfo() {
+        return allLookAndFeelInfo;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public MRUListModel getMRUModel() {
+        return mruModel;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public abstract String getApplicationName();
+
+    /**
+*
+*
+* @return
+*/
+    public abstract String getApplicationVersion();
+
+    /**
+*
+*
+* @return
+*/
+    public abstract Icon getApplicationLargeIcon();
+
+    /**
+*
+*
+* @return
+*/
+    public abstract String getAboutLicenseDetails();
+
+    /**
+*
+*
+* @return
+*/
+    public abstract String getAboutURL();
+
+    /**
+*
+*
+* @return
+*/
+    public abstract String getAboutAuthors();
+
+    /**
+*
+*
+* @return
+*/
+    public abstract File getApplicationPreferencesDirectory();
+
+    /**
+*
+*
+* @return
+*/
+    public OptionsTab[] getAdditionalOptionsTabs() {
+        OptionsTab[] t = new OptionsTab[additionalOptionsTabs.size()];
+        additionalOptionsTabs.toArray(t);
+
+        return t;
+    }
+
+    /**
+*
+*
+* @param tab
+*/
+    public void addAdditionalOptionsTab(OptionsTab tab) {
+        if (!additionalOptionsTabs.contains(tab)) {
+            additionalOptionsTabs.add(tab);
+        }
+    }
+
+    /**
+*
+*
+* @param tab
+*/
+    public void removeAdditionalOptionsTab(OptionsTab tab) {
+        additionalOptionsTabs.remove(tab);
+    }
+
+    /**
+*
+*
+* @param title
+*/
+    public void removeAdditionalOptionsTab(String title) {
+        OptionsTab t = getOptionsTab(title);
+
+        if (t != null) {
+            removeAdditionalOptionsTab(t);
+        }
+    }
+
+    /**
+*
+*
+* @param title
+*
+* @return
+*/
+    public OptionsTab getOptionsTab(String title) {
+        for (Iterator i = additionalOptionsTabs.iterator(); i.hasNext();) {
+            OptionsTab t = (OptionsTab) i.next();
+
+            if (t.getTabTitle().equals(title)) {
+                return t;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+*
+*/
+    public void exit() {
+        log.debug("Exiting application");
+        PreferencesStore.savePreferences();
+
+        FileOutputStream out = null;
+        File a = getApplicationPreferencesDirectory();
+
+        if (a != null) {
+            try {
+                File f = new File(getApplicationPreferencesDirectory(),
+                        getApplicationName() + ".mru");
+                ;
+
+                if (log.isDebugEnabled()) {
+                    log.debug("Saving MRU to " + f.getAbsolutePath());
+                }
+
+                out = new FileOutputStream(f);
+
+                PrintWriter w = new PrintWriter(out, true);
+                w.println(mruModel.getMRUList().toString());
+            } catch (IOException ioe) {
+                log.error("Could not save MRU. ", ioe);
+            } finally {
+                IOUtil.closeStream(out);
+            }
+        } else {
+            log.debug(
+                "Not saving preferences because no preferences directory is available.");
+        }
+
+        System.exit(0);
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public int getContainerCount() {
+        return containers.size();
+    }
+
+    /**
+*
+*
+* @param idx
+*
+* @return
+*/
+    public SshToolsApplicationContainer getContainerAt(int idx) {
+        return (SshToolsApplicationContainer) containers.elementAt(idx);
+    }
+
+    /**
+*
+*
+* @param panel
+*
+* @return
+*/
+    public SshToolsApplicationContainer getContainerForPanel(
+        SshToolsApplicationPanel panel) {
+        for (Iterator i = containers.iterator(); i.hasNext();) {
+            SshToolsApplicationContainer c = (SshToolsApplicationContainer) i.next();
+
+            if (c.getApplicationPanel() == panel) {
+                return c;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+*
+*
+* @param container
+*/
+    public void closeContainer(SshToolsApplicationContainer container) {
+        if (log.isDebugEnabled()) {
+            log.debug("Asking " + container + " if it can close");
+        }
+
+        if (container.getApplicationPanel().canClose()) {
+            if (log.isDebugEnabled()) {
+                log.debug("Closing");
+
+                for (Iterator i = containers.iterator(); i.hasNext();) {
+                    log.debug(i.next() + " is currently open");
+                }
+            }
+
+            container.getApplicationPanel().close();
+            container.closeContainer();
+            containers.removeElement(container);
+
+            if (containers.size() == 0) {
+                exit();
+            } else {
+                log.debug(
+                    "Not closing completely because there are containers still open");
+
+                for (Iterator i = containers.iterator(); i.hasNext();) {
+                    log.debug(i.next() + " is still open");
+                }
+            }
+        }
+    }
+
+    /**
+* Show an 'About' dialog
+*
+*
+*/
+    public void showAbout(Component parent) {
+        JPanel p = new JPanel(new GridBagLayout());
+        p.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+
+        GridBagConstraints gBC = new GridBagConstraints();
+        gBC.anchor = GridBagConstraints.CENTER;
+        gBC.fill = GridBagConstraints.HORIZONTAL;
+        gBC.insets = new Insets(1, 1, 1, 1);
+
+        JLabel a = new JLabel(getApplicationName());
+        a.setFont(a.getFont().deriveFont(24f));
+        UIUtil.jGridBagAdd(p, a, gBC, GridBagConstraints.REMAINDER);
+
+        JLabel v = new JLabel(ConfigurationLoader.getVersionString(
+                    getApplicationName(), getApplicationVersion()));
+        v.setFont(v.getFont().deriveFont(10f));
+        UIUtil.jGridBagAdd(p, v, gBC, GridBagConstraints.REMAINDER);
+
+        MultilineLabel x = new MultilineLabel(getAboutAuthors());
+        x.setBorder(BorderFactory.createEmptyBorder(8, 0, 8, 0));
+        x.setFont(x.getFont().deriveFont(12f));
+        UIUtil.jGridBagAdd(p, x, gBC, GridBagConstraints.REMAINDER);
+
+        MultilineLabel c = new MultilineLabel(getAboutLicenseDetails());
+        c.setFont(c.getFont().deriveFont(10f));
+        UIUtil.jGridBagAdd(p, c, gBC, GridBagConstraints.REMAINDER);
+
+        final JLabel h = new JLabel(getAboutURL());
+        h.setForeground(Color.blue);
+        h.setFont(new Font(h.getFont().getName(), Font.BOLD, 10));
+        h.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+        h.addMouseListener(new MouseAdapter() {
+                public void mouseClicked(MouseEvent evt) {
+                    try {
+                        BrowserLauncher.openURL(getAboutURL());
+                    } catch (IOException ioe) {
+                        ioe.printStackTrace();
+                    }
+                }
+            });
+        UIUtil.jGridBagAdd(p, h, gBC, GridBagConstraints.REMAINDER);
+        JOptionPane.showMessageDialog(parent, p, "About",
+            JOptionPane.PLAIN_MESSAGE, getApplicationLargeIcon());
+    }
+
+    /**
+*
+*
+* @return
+*
+* @throws SshToolsApplicationException
+*/
+    public SshToolsApplicationContainer newContainer()
+        throws SshToolsApplicationException {
+        SshToolsApplicationContainer container = null;
+
+        try {
+            container = (SshToolsApplicationContainer) defaultContainerClass.newInstance();
+            newContainer(container);
+
+            return container;
+        } catch (Throwable t) {
+            throw new SshToolsApplicationException(t);
+        }
+    }
+
+    /**
+*
+*
+* @param container
+*
+* @throws SshToolsApplicationException
+*/
+    public void newContainer(SshToolsApplicationContainer container)
+        throws SshToolsApplicationException {
+        try {
+            SshToolsApplicationPanel panel = (SshToolsApplicationPanel) panelClass.newInstance();
+            panel.init(this);
+            panel.rebuildActionComponents();
+            panel.setAvailableActions();
+            container.init(this, panel);
+            panel.setContainer(container);
+
+            if (!container.isContainerVisible()) {
+                container.setContainerVisible(true);
+            }
+
+            containers.addElement(container);
+        } catch (Throwable t) {
+            throw new SshToolsApplicationException(t);
+        }
+    }
+
+    /**
+*
+*
+* @param container
+* @param newContainerClass
+*
+* @return
+*
+* @throws SshToolsApplicationException
+*/
+    public SshToolsApplicationContainer convertContainer(
+        SshToolsApplicationContainer container, Class newContainerClass)
+        throws SshToolsApplicationException {
+        log.info("Converting container of class " +
+            container.getClass().getName() + " to " +
+            newContainerClass.getName());
+
+        int idx = containers.indexOf(container);
+
+        if (idx == -1) {
+            throw new SshToolsApplicationException(
+                "Container is not being manager by the application.");
+        }
+
+        SshToolsApplicationContainer newContainer = null;
+
+        try {
+            container.closeContainer();
+
+            SshToolsApplicationPanel panel = container.getApplicationPanel();
+            newContainer = (SshToolsApplicationContainer) newContainerClass.newInstance();
+            newContainer.init(this, panel);
+            panel.setContainer(newContainer);
+
+            if (!newContainer.isContainerVisible()) {
+                newContainer.setContainerVisible(true);
+            }
+
+            containers.setElementAt(newContainer, idx);
+
+            return newContainer;
+        } catch (Throwable t) {
+            throw new SshToolsApplicationException(t);
+        }
+    }
+
+    /**
+*
+*
+* @param args
+*
+* @throws SshToolsApplicationException
+*/
+    public void init(String[] args) throws SshToolsApplicationException {
+        File f = getApplicationPreferencesDirectory();
+
+        if (f != null) {
+            //
+            PreferencesStore.init(new File(f,
+                    getApplicationName() + ".properties"));
+            log.info("Preferences will be saved to " + f.getAbsolutePath());
+        } else {
+            log.warn("No preferences can be saved.");
+        }
+
+        try {
+            setLookAndFeel(PreferencesStore.get(PREF_LAF, SYSTEM_LAF));
+            UIManager.put("OptionPane.errorIcon",
+                new ResourceIcon(SshToolsApplication.class, "dialog-error4.png"));
+            UIManager.put("OptionPane.informationIcon",
+                new ResourceIcon(SshToolsApplication.class,
+                    "dialog-information.png"));
+            UIManager.put("OptionPane.warningIcon",
+                new ResourceIcon(SshToolsApplication.class,
+                    "dialog-warning2.png"));
+            UIManager.put("OptionPane.questionIcon",
+                new ResourceIcon(SshToolsApplication.class,
+                    "dialog-question3.png"));
+        } catch (Throwable t) {
+            log.error(t);
+        }
+    }
+
+    /**
+*
+*
+* @param className
+*
+* @throws Exception
+*/
+    public static void setLookAndFeel(String className)
+        throws Exception {
+        LookAndFeel laf = null;
+
+        if (!className.equals(DEFAULT_LAF)) {
+            if (className.equals(SYSTEM_LAF)) {
+                String systemLaf = UIManager.getSystemLookAndFeelClassName();
+                log.debug("System Look And Feel is " + systemLaf);
+                laf = (LookAndFeel) Class.forName(systemLaf).newInstance();
+            } else if (className.equals(CROSS_PLATFORM_LAF)) {
+                String crossPlatformLaf = UIManager.getCrossPlatformLookAndFeelClassName();
+                log.debug("Cross Platform Look And Feel is " +
+                    crossPlatformLaf);
+                laf = (LookAndFeel) Class.forName(crossPlatformLaf).newInstance();
+            } else {
+                laf = (LookAndFeel) Class.forName(className).newInstance();
+            }
+        }
+
+        //  Now actually set the look and feel
+        if (laf != null) {
+            log.info("Setting look and feel " + laf.getName() + " (" +
+                laf.getClass().getName() + ")");
+            UIManager.setLookAndFeel(laf);
+            UIManager.put("EditorPane.font", UIManager.getFont("TextArea.font"));
+        }
+    }
+}
diff --git a/src/com/sshtools/common/ui/SshToolsApplicationApplet.java b/src/com/sshtools/common/ui/SshToolsApplicationApplet.java
new file mode 100644
index 0000000000000000000000000000000000000000..4e9416e6d79a900828798c58aad358963c8663a3
--- /dev/null
+++ b/src/com/sshtools/common/ui/SshToolsApplicationApplet.java
@@ -0,0 +1,470 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import com.sshtools.common.util.PropertyUtil;
+
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import java.util.StringTokenizer;
+
+import javax.swing.BorderFactory;
+import javax.swing.JApplet;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JProgressBar;
+import javax.swing.JSeparator;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public abstract class SshToolsApplicationApplet extends JApplet {
+    //     eurgghh!
+
+    /**  */
+    public final static String[][] PARAMETER_INFO = {
+        {
+            "sshapps.log.file", "string",
+            "Logging output destination. Defaults to @console@"
+        },
+        {
+            "sshapps.log.level", "string",
+            "Logging level. DEBUG,FATAL,ERROR,WARN,INFO,DEBUG or OFF. Defaults to OFF"
+        },
+        {
+            "sshapps.ui.informationPanel.background", "hex color",
+            "Set the background color of the 'information panel'"
+        },
+        {
+            "sshapps.ui.informationPanel.foreground", "boolean",
+            "Set the foreground color of the 'information panel'"
+        },
+        {
+            "sshapps.ui.informationPanel.borderColor", "boolean",
+            "Set the border color of the 'information panel'"
+        },
+        {
+            "sshapps.ui.informationPanel.borderThickness", "integer",
+            "Set the border thickness of the 'information panel'"
+        },
+        { "sshapps.ui.toolBar", "boolean", "Enable / Disable the tool bar" },
+        { "sshapps.ui.menuBar", "boolean", "Enable / Disable the menu bar" },
+        {
+            "sshapps.ui.disabledActions", "string",
+            "Comma (,) separated list of disable actions"
+        },
+        { "sshapps.ui.statusBar", "boolean", "Enable / Disable the menu bar" }
+    };
+
+    /**  */
+    protected Log log = LogFactory.getLog(SshToolsApplicationApplet.class);
+
+    //  Private instance variables
+
+    /**  */
+    protected LoadingPanel loadingPanel;
+
+    /**  */
+    protected JSeparator toolSeparator;
+
+    /**  */
+    protected SshToolsApplicationPanel applicationPanel;
+
+    /**  */
+    protected Color infoForeground;
+
+    /**  */
+    protected int infoBorderThickness;
+
+    /**  */
+    protected boolean toolBar;
+
+    /**  */
+    protected boolean menuBar;
+
+    /**  */
+    protected boolean statusBar;
+
+    /**  */
+    protected Color infoBackground;
+
+    /**  */
+    protected Color infoBorderColor;
+
+    /**  */
+    protected String disabledActions;
+
+    /**
+*
+*
+* @param key
+* @param def
+*
+* @return
+*/
+    public String getParameter(String key, String def) {
+        String v = getParameter(key);
+
+        return (v != null) ? v : def;
+    }
+
+    /**
+*
+*/
+    public void init() {
+        try {
+            Runnable r = new Runnable() {
+                    public void run() {
+                        try {
+                            getContentPane().setLayout(new BorderLayout());
+                            setAppletComponent(loadingPanel = new LoadingPanel());
+                            initApplet();
+
+                            JComponent p = buildAppletComponent();
+                            startApplet();
+                            setAppletComponent(p);
+                        } catch (Throwable t) {
+                            seriousAppletError(t);
+                        }
+                    }
+                };
+
+            Thread t = new Thread(r);
+            t.start();
+        } catch (Throwable t) {
+            seriousAppletError(t);
+        }
+    }
+
+    /**
+*
+*
+* @throws IOException
+*/
+    public void initApplet() throws IOException {
+        /*ConfigurationLoader.setLogfile(getParameter("sshapps.log.file",
+"@console@"));
+ log.getRootLogger().setLevel(org.apache.log4j.Level.toLevel(
+getParameter("sshapps.log.level", "DEBUG")));*/
+        ConfigurationLoader.initialize(false);
+        infoBackground = PropertyUtil.stringToColor(getParameter(
+                    "sshapps.ui.informationPanel.background",
+                    PropertyUtil.colorToString(new Color(255, 255, 204))));
+        infoForeground = PropertyUtil.stringToColor(getParameter(
+                    "sshapps.ui.informationPanel.foreground",
+                    PropertyUtil.colorToString(Color.black)));
+        infoBorderColor = PropertyUtil.stringToColor(getParameter(
+                    "sshapps.ui.informationPanel.borderColor",
+                    PropertyUtil.colorToString(Color.black)));
+        infoBorderThickness = PropertyUtil.stringToInt(getParameter(
+                    "sshapps.ui.informationPanel.borderThickness", "1"), 1);
+        toolBar = getParameter("sshapps.ui.toolBar", "true").equalsIgnoreCase("true");
+        menuBar = getParameter("sshapps.ui.menuBar", "true").equalsIgnoreCase("true");
+        statusBar = getParameter("sshapps.ui.statusBar", "true")
+                        .equalsIgnoreCase("true");
+        disabledActions = getParameter("sshapps.ui.disabledActions", "");
+    }
+
+    /**
+*
+*/
+    public void startApplet() {
+    }
+
+    /**
+*
+*
+* @return
+*
+* @throws IOException
+* @throws SshToolsApplicationException
+*/
+    public JComponent buildAppletComponent()
+        throws IOException, SshToolsApplicationException {
+        loadingPanel.setStatus("Creating application");
+        applicationPanel = createApplicationPanel();
+        loadingPanel.setStatus("Building action components");
+        applicationPanel.rebuildActionComponents();
+        log.debug("Disabled actions list = " + disabledActions);
+
+        StringTokenizer tk = new StringTokenizer((disabledActions == null) ? ""
+                                                                           : disabledActions,
+                ",");
+
+        while (tk.hasMoreTokens()) {
+            String n = tk.nextToken();
+            log.debug("Disable " + n);
+            applicationPanel.setActionVisible(n, false);
+        }
+
+        JPanel p = new JPanel(new BorderLayout());
+        JPanel n = new JPanel(new BorderLayout());
+
+        if (applicationPanel.getJMenuBar() != null) {
+            n.add(applicationPanel.getJMenuBar(), BorderLayout.NORTH);
+            log.debug("Setting menu bar visibility to " + menuBar);
+            applicationPanel.setMenuBarVisible(menuBar);
+        }
+
+        if (applicationPanel.getToolBar() != null) {
+            JPanel t = new JPanel(new BorderLayout());
+            t.add(applicationPanel.getToolBar(), BorderLayout.NORTH);
+            applicationPanel.setToolBarVisible(toolBar);
+            t.add(toolSeparator = new JSeparator(JSeparator.HORIZONTAL),
+                BorderLayout.SOUTH);
+            toolSeparator.setVisible(applicationPanel.getToolBar().isVisible());
+
+            final SshToolsApplicationPanel pnl = applicationPanel;
+            applicationPanel.getToolBar().addComponentListener(new ComponentAdapter() {
+                    public void componentHidden(ComponentEvent evt) {
+                        toolSeparator.setVisible(pnl.getToolBar().isVisible());
+                    }
+                });
+            n.add(t, BorderLayout.SOUTH);
+        }
+
+        p.add(n, BorderLayout.NORTH);
+        p.add(applicationPanel, BorderLayout.CENTER);
+
+        if (applicationPanel.getStatusBar() != null) {
+            p.add(applicationPanel.getStatusBar(), BorderLayout.SOUTH);
+            applicationPanel.setStatusBarVisible(statusBar);
+        }
+
+        return p;
+    }
+
+    /**
+*
+*
+* @param name
+*/
+    public void doAction(String name) {
+        StandardAction a = applicationPanel.getAction(name);
+
+        if (a != null) {
+            if (a.isEnabled()) {
+                log.debug("Performing action " + a.getName());
+                a.actionPerformed(new ActionEvent(this,
+                        ActionEvent.ACTION_PERFORMED, a.getActionCommand()));
+            } else {
+                log.warn("No performing action '" + a.getName() +
+                    "' because it is disabled.");
+            }
+        } else {
+            log.error("No action named " + name);
+        }
+    }
+
+    /**
+*
+*
+* @return
+*
+* @throws SshToolsApplicationException
+*/
+    public abstract SshToolsApplicationPanel createApplicationPanel()
+        throws SshToolsApplicationException;
+
+    /**
+*
+*
+* @param component
+*/
+    protected void setAppletComponent(JComponent component) {
+        if (getContentPane().getComponentCount() > 0) {
+            getContentPane().invalidate();
+            getContentPane().removeAll();
+        }
+
+        getContentPane().add(component, BorderLayout.CENTER);
+        getContentPane().validate();
+        getContentPane().repaint();
+    }
+
+    /**
+*
+*
+* @param t
+*/
+    protected void seriousAppletError(Throwable t) {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<html><p>A serious error has occured ...</p><br>");
+        buf.append("<p><font size=\"-1\" color=\"#ff0000\"><b>");
+
+        StringWriter writer = new StringWriter();
+        t.printStackTrace(new PrintWriter(writer, true));
+
+        StringTokenizer tk = new StringTokenizer(writer.toString(), "\n");
+
+        while (tk.hasMoreTokens()) {
+            String msg = tk.nextToken();
+            buf.append(msg);
+
+            if (tk.hasMoreTokens()) {
+                buf.append("<br>");
+            }
+        }
+
+        buf.append("</b></font></p><html>");
+
+        SshToolsApplicationAppletPanel p = new SshToolsApplicationAppletPanel();
+        p.setLayout(new GridBagLayout());
+
+        GridBagConstraints gbc = new GridBagConstraints();
+        gbc.anchor = GridBagConstraints.CENTER;
+        gbc.insets = new Insets(0, 0, 8, 0);
+        gbc.fill = GridBagConstraints.NONE;
+        UIUtil.jGridBagAdd(p, new JLabel(buf.toString()), gbc,
+            GridBagConstraints.REMAINDER);
+        setAppletComponent(p);
+    }
+
+    /**
+*
+*/
+    public void start() {
+    }
+
+    /**
+*
+*/
+    public void stop() {
+    }
+
+    /**
+*
+*/
+    public void destroy() {
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String[][] getParameterInfo() {
+        return PARAMETER_INFO;
+    }
+
+    public class SshToolsApplicationAppletContainer extends JPanel
+        implements SshToolsApplicationContainer {
+        //  Private instance variables
+        private SshToolsApplicationPanel panel;
+        private SshToolsApplication application;
+
+        //Construct the applet
+        public SshToolsApplicationAppletContainer() {
+        }
+
+        public void init(SshToolsApplication application,
+            SshToolsApplicationPanel panel) throws SshToolsApplicationException {
+            this.application = application;
+            this.panel = panel;
+            panel.registerActionMenu(new SshToolsApplicationPanel.ActionMenu(
+                    "Help", "Help", 'h', 99));
+            panel.registerAction(new AboutAction(this, application));
+            getApplicationPanel().rebuildActionComponents();
+        }
+
+        public void setContainerTitle(String title) {
+            getAppletContext().showStatus(title);
+        }
+
+        public SshToolsApplicationPanel getApplicationPanel() {
+            return panel;
+        }
+
+        public void closeContainer() {
+            //  We dont do anything here
+        }
+
+        public void setContainerVisible(boolean visible) {
+            setVisible(visible);
+        }
+
+        public boolean isContainerVisible() {
+            return isVisible();
+        }
+    }
+
+    class SshToolsApplicationAppletPanel extends JPanel {
+        SshToolsApplicationAppletPanel() {
+            super();
+            setOpaque(true);
+            setBackground(infoBackground);
+            setForeground(infoForeground);
+            setBorder(BorderFactory.createLineBorder(infoBorderColor,
+                    infoBorderThickness));
+        }
+    }
+
+    class LoadingPanel extends SshToolsApplicationAppletPanel {
+        private JProgressBar bar;
+
+        LoadingPanel() {
+            super();
+            setLayout(new GridBagLayout());
+
+            GridBagConstraints gbc = new GridBagConstraints();
+            gbc.anchor = GridBagConstraints.CENTER;
+            gbc.insets = new Insets(0, 0, 8, 0);
+            gbc.fill = GridBagConstraints.NONE;
+            UIUtil.jGridBagAdd(this, new JLabel("Loading " + getAppletInfo()),
+                gbc, GridBagConstraints.REMAINDER);
+            bar = new JProgressBar(0, 100);
+
+            //bar.setIndeterminate(true);
+            bar.setStringPainted(true);
+            UIUtil.jGridBagAdd(this, bar, gbc, GridBagConstraints.REMAINDER);
+        }
+
+        public void setStatus(String status) {
+            bar.setString(status);
+        }
+    }
+}
diff --git a/src/com/sshtools/common/ui/SshToolsApplicationClientApplet.java b/src/com/sshtools/common/ui/SshToolsApplicationClientApplet.java
new file mode 100644
index 0000000000000000000000000000000000000000..d59bfc0a0c76cc04416cefa6f16d4e8eefa64ca7
--- /dev/null
+++ b/src/com/sshtools/common/ui/SshToolsApplicationClientApplet.java
@@ -0,0 +1,248 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import com.sshtools.common.configuration.SshToolsConnectionProfile;
+import com.sshtools.common.util.PropertyUtil;
+
+import com.sshtools.j2ssh.authentication.SshAuthenticationClient;
+import com.sshtools.j2ssh.authentication.SshAuthenticationClientFactory;
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+import com.sshtools.j2ssh.io.IOUtil;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public abstract class SshToolsApplicationClientApplet
+    extends SshToolsApplicationApplet {
+    /**  */
+    public final static String[][] CLIENT_PARAMETER_INFO = {
+        {
+            "sshapps.connection.url", "string",
+            "The URL of a connection profile to open"
+        },
+        { "sshapps.connection.host", "string", "The host to connect to" },
+        { "sshapps.connection.userName", "string", "The user to connect as" },
+        {
+            "sshapps.connection.authenticationMethod", "string",
+            "Authentication method. password,publickey etc."
+        },
+        {
+            "sshapps.connection.connectImmediately", "boolean",
+            "Connect immediately."
+        },
+        {
+            "sshapps.connection.showConnectionDialog", "boolean",
+            "Show connection dialog."
+        },
+        {
+            "sshapps.connection.disableHostKeyVerification", "boolean",
+            "Disable the host key verification dialog."
+        }
+    };
+
+    /**  */
+    protected Log log = LogFactory.getLog(SshToolsApplicationClientApplet.class);
+
+    //  Private instance variables
+    private String connectionProfileLocation;
+
+    /**  */
+    protected String authenticationMethod;
+
+    /**  */
+    protected String host;
+
+    /**  */
+    protected int port;
+
+    /**  */
+    protected String user;
+
+    /**  */
+    protected boolean connectImmediately;
+
+    /**  */
+    protected boolean showConnectionDialog;
+
+    /**  */
+    protected boolean disableHostKeyVerification;
+
+    /**  */
+    protected SshToolsConnectionProfile profile;
+
+    /**
+*
+*
+* @throws IOException
+*/
+    public void initApplet() throws IOException {
+        super.initApplet();
+        connectionProfileLocation = getParameter("sshapps.connectionProfile.url",
+                "");
+
+        //   Get the connection parameters
+        host = getParameter("sshapps.connection.host", "");
+        port = PropertyUtil.stringToInt(getParameter(
+                    "sshapps.connection.port", ""), 22);
+        user = getParameter("sshapps.connection.userName",
+                ConfigurationLoader.checkAndGetProperty("user.home", ""));
+        authenticationMethod = getParameter("sshapps.connection.authenticationMethod",
+                "");
+        connectImmediately = getParameter("sshapps.connection.connectImmediately",
+                "false").equalsIgnoreCase("true");
+        showConnectionDialog = getParameter("sshapps.connection.showConnectionDialog",
+                "false").equalsIgnoreCase("true");
+        disableHostKeyVerification = getParameter("sshapps.connection.disableHostKeyVerification",
+                "false").equalsIgnoreCase("true");
+        buildProfile();
+    }
+
+    /**
+*
+*/
+    public void startApplet() {
+        // Disable the host key verification if requested
+        if (disableHostKeyVerification) {
+            ((SshToolsApplicationClientPanel) applicationPanel).setHostHostVerification(null);
+            ((SshToolsApplicationClientPanel) applicationPanel).application.removeAdditionalOptionsTab(
+                "Hosts");
+            log.debug("Host key verification disabled");
+        } else {
+            log.debug("Host key verification enabled");
+        }
+
+        if (connectImmediately) {
+            loadingPanel.setStatus("Connecting");
+
+            if (showConnectionDialog) {
+                SshToolsConnectionProfile newProfile = ((SshToolsApplicationClientPanel) applicationPanel).newConnectionProfile(profile);
+
+                if (newProfile != null) {
+                    profile = newProfile;
+                    ((SshToolsApplicationClientPanel) applicationPanel).connect(profile,
+                        true);
+                }
+            } else {
+                ((SshToolsApplicationClientPanel) applicationPanel).connect(profile,
+                    false);
+            }
+        }
+    }
+
+    /**
+*
+*
+* @throws IOException
+*/
+    protected void buildProfile() throws IOException {
+        profile = new SshToolsConnectionProfile();
+
+        //  Load the connection profile if specified
+        if (!connectionProfileLocation.equals("")) {
+            log.info("Loading connection profile " + connectionProfileLocation);
+            loadingPanel.setStatus("Loading connection profile");
+
+            InputStream in = null;
+
+            try {
+                URL u = null;
+
+                try {
+                    u = new URL(connectionProfileLocation);
+                } catch (MalformedURLException murle) {
+                    u = new URL(getCodeBase() + "/" +
+                            connectionProfileLocation);
+                }
+
+                log.info("Full URL of connection profile is " + u);
+                in = u.openStream();
+                profile.open(in);
+            } finally {
+                IOUtil.closeStream(in);
+            }
+        }
+
+        if (!host.equals("")) {
+            log.info("Building connection profile from parameters ");
+            log.debug("Setting host to " + host);
+            profile.setHost(host);
+            log.debug("Setting port to " + port);
+            profile.setPort(port);
+            log.debug("Setting username to " + user);
+            profile.setUsername(user);
+
+            if (!authenticationMethod.equals("")) {
+                try {
+                    log.debug("Adding authentication method " +
+                        authenticationMethod);
+
+                    SshAuthenticationClient authClient = SshAuthenticationClientFactory.newInstance(authenticationMethod);
+                    profile.addAuthenticationMethod(authClient);
+                } catch (Exception e) {
+                    log.error("Could not add authentication method.", e);
+                }
+            }
+        }
+    }
+
+    /**
+*
+*/
+    public void destroy() {
+        if (applicationPanel.isConnected()) {
+            ((SshToolsApplicationClientPanel) applicationPanel).closeConnection(true);
+        }
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String[][] getParameterInfo() {
+        String[][] s = super.getParameterInfo();
+        String[][] p = new String[s.length + CLIENT_PARAMETER_INFO.length][];
+        System.arraycopy(s, 0, p, 0, s.length);
+        System.arraycopy(CLIENT_PARAMETER_INFO, 0, p, s.length,
+            CLIENT_PARAMETER_INFO.length);
+
+        return p;
+    }
+}
diff --git a/src/com/sshtools/common/ui/SshToolsApplicationClientPanel.java b/src/com/sshtools/common/ui/SshToolsApplicationClientPanel.java
new file mode 100644
index 0000000000000000000000000000000000000000..ed69eb4cac9712afd5b8f3ea59346b45bea9f1cc
--- /dev/null
+++ b/src/com/sshtools/common/ui/SshToolsApplicationClientPanel.java
@@ -0,0 +1,1012 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import com.sshtools.common.authentication.AuthenticationDialog;
+import com.sshtools.common.authentication.BannerDialog;
+import com.sshtools.common.authentication.KBIRequestHandlerDialog;
+import com.sshtools.common.authentication.PasswordAuthenticationDialog;
+import com.sshtools.common.authentication.PasswordChange;
+import com.sshtools.common.authentication.PublicKeyAuthenticationPrompt;
+import com.sshtools.common.automate.RemoteIdentification;
+import com.sshtools.common.automate.RemoteIdentificationException;
+import com.sshtools.common.automate.RemoteIdentificationFactory;
+import com.sshtools.common.configuration.InvalidProfileFileException;
+import com.sshtools.common.configuration.SshToolsConnectionProfile;
+import com.sshtools.common.hosts.DialogKnownHostsKeyVerification;
+
+import com.sshtools.j2ssh.SshClient;
+import com.sshtools.j2ssh.SshException;
+import com.sshtools.j2ssh.SshThread;
+import com.sshtools.j2ssh.agent.AgentAuthenticationClient;
+import com.sshtools.j2ssh.agent.AgentNotAvailableException;
+import com.sshtools.j2ssh.agent.SshAgentClient;
+import com.sshtools.j2ssh.authentication.AuthenticationProtocolState;
+import com.sshtools.j2ssh.authentication.KBIAuthenticationClient;
+import com.sshtools.j2ssh.authentication.PasswordAuthenticationClient;
+import com.sshtools.j2ssh.authentication.PublicKeyAuthenticationClient;
+import com.sshtools.j2ssh.authentication.SshAuthenticationClient;
+import com.sshtools.j2ssh.authentication.SshAuthenticationClientFactory;
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+import com.sshtools.j2ssh.transport.AbstractKnownHostsKeyVerification;
+import com.sshtools.j2ssh.transport.HostKeyVerification;
+import com.sshtools.j2ssh.transport.InvalidHostFileException;
+import com.sshtools.j2ssh.transport.TransportProtocolException;
+import com.sshtools.j2ssh.transport.TransportProtocolState;
+import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.awt.Frame;
+import java.awt.LayoutManager;
+
+import java.io.File;
+import java.io.FilePermission;
+import java.io.IOException;
+
+import java.security.AccessControlException;
+import java.security.AccessController;
+
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.swing.JFileChooser;
+import javax.swing.JOptionPane;
+import javax.swing.SwingConstants;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+import javax.swing.filechooser.FileFilter;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.24 $
+ */
+public abstract class SshToolsApplicationClientPanel
+    extends SshToolsApplicationPanel {
+    /**  */
+    public final static String PREF_CONNECTION_FILE_DIRECTORY = "sshapps.connectionFile.directory";
+
+    //
+
+    /**  */
+    public final static int BANNER_TIMEOUT = 2000;
+
+    /**  */
+    protected static AbstractKnownHostsKeyVerification ver;
+
+    //
+
+    /**  */
+    protected Log log = LogFactory.getLog(SshToolsApplicationClientPanel.class);
+
+    /**  */
+    protected HostKeyVerification hostKeyVerification;
+
+    /**  */
+    protected File currentConnectionFile;
+
+    /**  */
+    protected boolean needSave;
+
+    /**  */
+    protected SshToolsConnectionProfile currentConnectionProfile;
+
+    /**  */
+    protected javax.swing.filechooser.FileFilter connectionFileFilter = new ConnectionFileFilter();
+
+    /**  */
+    protected SshClient ssh;
+
+    /**
+* Creates a new SshToolsApplicationClientPanel object.
+*/
+    public SshToolsApplicationClientPanel() {
+        super();
+    }
+
+    /**
+* Creates a new SshToolsApplicationClientPanel object.
+*
+* @param mgr
+*/
+    public SshToolsApplicationClientPanel(LayoutManager mgr) {
+        super(mgr);
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public abstract SshToolsConnectionTab[] getAdditionalConnectionTabs();
+
+    /**
+*
+*
+* @return
+*/
+    public HostKeyVerification getHostKeyVerification() {
+        return hostKeyVerification;
+    }
+
+    /**
+*
+*
+* @param hostKeyVerification
+*/
+    public void setHostHostVerification(HostKeyVerification hostKeyVerification) {
+        this.hostKeyVerification = hostKeyVerification;
+    }
+
+    /**
+*
+*
+* @param application
+*
+* @throws SshToolsApplicationException
+*/
+    public void init(SshToolsApplication application)
+        throws SshToolsApplicationException {
+        super.init(application);
+
+        try {
+            //if (ver == null) {
+            ver = new DialogKnownHostsKeyVerification(this);
+
+            //}
+            setHostHostVerification(ver);
+
+            if (ver.isHostFileWriteable()) {
+                application.addAdditionalOptionsTab(new HostsTab(ver));
+            }
+        } catch (InvalidHostFileException uhfe) {
+            log.warn("Host key verification will be DISABLED.", uhfe);
+        }
+    }
+
+    /**
+*
+*/
+    public void editConnection() {
+        // Create a file chooser with the current directory set to the
+        // application home
+        JFileChooser fileDialog = new JFileChooser(PreferencesStore.get(
+                    PREF_CONNECTION_FILE_DIRECTORY,
+                    System.getProperty("sshtools.home",
+                        System.getProperty("user.home"))));
+        fileDialog.setFileFilter(connectionFileFilter);
+
+        // Show it
+        int ret = fileDialog.showOpenDialog(this);
+
+        // If we've approved the selection then process
+        if (ret == fileDialog.APPROVE_OPTION) {
+            PreferencesStore.put(PREF_CONNECTION_FILE_DIRECTORY,
+                fileDialog.getCurrentDirectory().getAbsolutePath());
+
+            // Get the file
+            File f = fileDialog.getSelectedFile();
+
+            // Load the profile
+            SshToolsConnectionProfile p = new SshToolsConnectionProfile();
+
+            try {
+                p.open(f);
+
+                if (editConnection(p)) {
+                    saveConnection(false, f, p);
+                }
+            } catch (IOException ioe) {
+                showErrorMessage(this, "Failed to load connection profile.",
+                    "Error", ioe);
+            }
+        }
+    }
+
+    /**
+*
+*
+* @param profile
+*
+* @return
+*/
+    public SshToolsConnectionProfile newConnectionProfile(
+        SshToolsConnectionProfile profile) {
+        return SshToolsConnectionPanel.showConnectionDialog(SshToolsApplicationClientPanel.this,
+            profile, getAdditionalConnectionTabs());
+    }
+
+    /**
+*
+*/
+    public void open() {
+        // Create a file chooser with the current directory set to the
+        // application home
+        String prefsDir = super.getApplication()
+                               .getApplicationPreferencesDirectory()
+                               .getAbsolutePath();
+        JFileChooser fileDialog = new JFileChooser(prefsDir);
+        fileDialog.setFileFilter(connectionFileFilter);
+
+        // Show it
+        int ret = fileDialog.showOpenDialog(this);
+
+        // If we've approved the selection then process
+        if (ret == fileDialog.APPROVE_OPTION) {
+            PreferencesStore.put(PREF_CONNECTION_FILE_DIRECTORY,
+                fileDialog.getCurrentDirectory().getAbsolutePath());
+
+            // Get the file
+            File f = fileDialog.getSelectedFile();
+            open(f);
+        }
+    }
+
+    /**
+*
+*
+* @param f
+*/
+    public void open(File f) {
+        log.debug("Opening connection file " + f);
+
+        // Make sure a connection is not already open
+        if (isConnected()) {
+            Option optNew = new Option("New", "New create a new window", 'n');
+            Option optClose = new Option("Close", "Close current connection",
+                    'l');
+            Option optCancel = new Option("Cancel",
+                    "Cancel the opening of this connection", 'c');
+            OptionsDialog dialog = OptionsDialog.createOptionDialog(this,
+                    new Option[] { optNew, optClose, optCancel },
+                    "You already have a connection open. Select\n" +
+                    "Close to close the current connection, New\n" +
+                    "to create a new terminal or Cancel to abort.",
+                    "Existing connection", optNew, null,
+                    UIManager.getIcon("OptionPane.warningIcon"));
+            UIUtil.positionComponent(SwingConstants.CENTER, dialog);
+            dialog.setVisible(true);
+
+            Option opt = dialog.getSelectedOption();
+
+            if ((opt == null) || (opt == optCancel)) {
+                return;
+            } else if (opt == optNew) {
+                try {
+                    SshToolsApplicationContainer c = (SshToolsApplicationContainer) application.newContainer();
+                    ((SshToolsApplicationClientPanel) c.getApplicationPanel()).open(f);
+
+                    return;
+                } catch (SshToolsApplicationException stae) {
+                    log.error(stae);
+                }
+            } else {
+                closeConnection(true);
+            }
+        }
+
+        // Save to MRU
+        if (getApplication().getMRUModel() != null) {
+            getApplication().getMRUModel().add(f);
+        }
+
+        // Make sure its not invalid
+        if (f != null) {
+            // Create a new connection properties object
+            SshToolsConnectionProfile profile = new SshToolsConnectionProfile();
+
+            try {
+                // Open the file
+                profile.open(f.getAbsolutePath());
+                setNeedSave(false);
+                currentConnectionFile = f;
+                setContainerTitle(f);
+
+                // Connect with the new details.
+                connect(profile, false);
+            } catch (InvalidProfileFileException fnfe) {
+                showExceptionMessage(fnfe.getMessage(), "Open Connection");
+            } catch (SshException e) {
+                e.printStackTrace();
+                showExceptionMessage("An unexpected error occured!",
+                    "Open Connection");
+            }
+        } else {
+            showExceptionMessage("Invalid file specified", "Open Connection");
+        }
+    }
+
+    /**
+*
+*
+* @param profile
+* @param newProfile
+*/
+    public void connect(final SshToolsConnectionProfile profile,
+        final boolean newProfile) {
+        // We need to connect
+        ssh = new SshClient();
+
+        // Set the current connection properties
+        setCurrentConnectionProfile(profile);
+
+        // We'll do the threading rather than j2ssh as we want to get errors
+        Runnable r = new Runnable() {
+                public void run() {
+                    // Update the status bar
+                    getStatusBar().setStatusText("Connecting");
+                    getStatusBar().setHost(getCurrentConnectionProfile()
+                                               .getHost(),
+                        getCurrentConnectionProfile().getPort());
+                    getStatusBar().setUser(getCurrentConnectionProfile()
+                                               .getUsername());
+
+                    //
+                    try {
+                        log.info("Connecting to " +
+                            getCurrentConnectionProfile().getHost() + " as " +
+                            getCurrentConnectionProfile().getUsername());
+                        ssh.connect(getCurrentConnectionProfile(),
+                            (getHostKeyVerification() == null)
+                            ? new SinkHostKeyVerification()
+                            : getHostKeyVerification());
+
+                        // Set the remote id if we can find on
+                        try {
+                            RemoteIdentification rid = RemoteIdentificationFactory.getInstance(ssh.getServerId(),
+                                    ssh.getConnectionProperties().getHost());
+                            getStatusBar().setRemoteId(rid.getName(
+                                    ssh.getServerId()));
+                        } catch (RemoteIdentificationException ex) {
+                            getStatusBar().setRemoteId("Unknown");
+                        }
+
+                        if (postConnection()) {
+                            if (!authenticateUser(newProfile)) {
+                                closeConnection(false);
+                            } else {
+                                setAvailableActions();
+                            }
+                        }
+                    } catch (IOException sshe) {
+                        ssh = null;
+                        showExceptionMessage("Connection Error",
+                            "Could not establish a connection to host: \n\n " +
+                            sshe.getMessage());
+                        SshToolsApplicationClientPanel.this.closeConnection(false);
+                    } catch (SecurityException se) {
+                        ssh = null;
+                        showErrorMessage(SshToolsApplicationClientPanel.this,
+                            "Error", se);
+                        SshToolsApplicationClientPanel.this.closeConnection(false);
+                    }
+                }
+            };
+
+        Thread thread = new SshThread(r,
+                application.getApplicationName() + " connection", true);
+        thread.start();
+    }
+
+    /**
+*
+*
+* @param ssh
+* @param profile
+*
+* @throws IOException
+*/
+    public void connect(SshClient ssh, SshToolsConnectionProfile profile)
+        throws IOException {
+        this.ssh = ssh;
+
+        if (!ssh.isAuthenticated()) {
+            authenticateUser(false);
+        }
+
+        // Set the current connection properties
+        setCurrentConnectionProfile(profile);
+        authenticationComplete(false);
+    }
+
+    /**
+*
+*
+* @param newProfile
+*
+* @return
+*
+* @throws IOException
+*/
+    protected boolean authenticateUser(boolean newProfile)
+        throws IOException {
+        // We should now authenticate
+        int result = AuthenticationProtocolState.READY;
+
+        // Our authenticated flag
+        boolean authenticated = false;
+
+        // Get the supported authentication methods
+        java.util.List supported = SshAuthenticationClientFactory.getSupportedMethods();
+
+        // If the server supports public key lets look for an agent and try
+        // some of his keys
+        if (supported.contains("publickey")) {
+            if (System.getProperty("sshtools.agent") != null) {
+                try {
+                    SshAgentClient agent = SshAgentClient.connectLocalAgent("SSHTerm",
+                            System.getProperty("sshtools.agent") /*, 5*/);
+                    AgentAuthenticationClient aac = new AgentAuthenticationClient();
+                    aac.setAgent(agent);
+                    aac.setUsername(getCurrentConnectionProfile().getUsername());
+                    result = ssh.authenticate(aac);
+                    agent.close();
+                } catch (AgentNotAvailableException ex) {
+                    log.info("No agent was available for authentication");
+
+                    // Just continue
+                }
+
+                if (result == AuthenticationProtocolState.COMPLETE) {
+                    authenticationComplete(newProfile);
+
+                    return true;
+                }
+            }
+        }
+
+        // Create a list for display that will contain only the
+        // supported and available methods
+        java.util.List display = new java.util.ArrayList();
+
+        // Get the available methods
+        java.util.List auths = null;
+        auths = ssh.getAvailableAuthMethods(getCurrentConnectionProfile()
+                                                .getUsername());
+
+        // Did we receive a banner from the remote computer
+        final String banner = ssh.getAuthenticationBanner(BANNER_TIMEOUT);
+
+        if (banner != null) {
+            if (!banner.trim().equals("")) {
+                try {
+                    SwingUtilities.invokeAndWait(new Runnable() {
+                            public void run() {
+                                BannerDialog.showBannerDialog(SshToolsApplicationClientPanel.this,
+                                    banner);
+                            }
+                        });
+                } catch (Exception e) {
+                    log.error("Failed to invoke and wait on BannerDialog", e);
+                }
+            }
+        }
+
+        // Are there any authentication methods within the properties file?
+        // Iterate through selecting only the supported and available
+        Iterator it = supported.iterator();
+
+        while (it.hasNext() && !authenticated) {
+            Object obj = it.next();
+
+            if (auths.contains(obj)) {
+                display.add(obj);
+            }
+        }
+
+        // First look to see if we have any authenticaiton methods available
+        // in the profile properties object as this will overide a manual selection
+        java.util.Map authMethods = (Map) ((HashMap) getCurrentConnectionProfile()
+                                                         .getAuthenticationMethods()).clone();
+        it = authMethods.entrySet().iterator();
+
+        //Iterator it2 = null;
+        java.util.List selected;
+
+        // Loop until the user either cancels or completes
+        boolean completed = false;
+        SshAuthenticationClient auth;
+        Map.Entry entry;
+        String msg = null;
+
+        while (!completed &&
+                (ssh.getConnectionState().getValue() != TransportProtocolState.DISCONNECTED)) {
+            auth = null;
+
+            // Select an authentication method from the properties file or
+            // prompt the user to choose
+            if (it.hasNext()) {
+                Object obj = it.next();
+
+                if (obj instanceof Map.Entry) {
+                    entry = (Map.Entry) obj;
+                    auth = (SshAuthenticationClient) entry.getValue();
+                } else if (obj instanceof String) {
+                    auth = SshAuthenticationClientFactory.newInstance((String) obj);
+                    auth.setUsername(getCurrentConnectionProfile().getUsername());
+                } else {
+                    closeConnection(true);
+                    throw new IOException(
+                        "Iterator of Map or List of String expected");
+                }
+            } else {
+                selected = AuthenticationDialog.showAuthenticationDialog(this,
+                        display, ((msg == null) ? "" : msg));
+
+                if (selected.size() > 0) {
+                    it = selected.iterator();
+                } else {
+                    closeConnection(true);
+
+                    return false;
+                }
+            }
+
+            if (auth != null) {
+                // The password authentication client can act upon requests to change the password
+
+                /* if (auth instanceof PasswordAuthenticationClient) {
+PasswordAuthenticationDialog dialog = new PasswordAuthenticationDialog(SshTerminalPanel.this);
+((PasswordAuthenticationClient) auth).setAuthenticationPrompt(dialog);
+( (PasswordAuthenticationClient) auth)
+.setPasswordChangePrompt(PasswordChange.getInstance());
+PasswordChange.getInstance().setParentComponent(
+SshTerminalPanel.this);
+}*/
+
+                // Show the implementations dialog
+                // if(auth.showAuthenticationDialog()) {
+                // Authentication with the details supplied
+                result = showAuthenticationPrompt(auth); //ssh.authenticate(auth);
+
+                if (result == AuthenticationProtocolState.FAILED) {
+                    msg = auth.getMethodName() +
+                        " authentication failed, try again?";
+                }
+
+                // If the result returned partial success then continue
+                if (result == AuthenticationProtocolState.PARTIAL) {
+                    // We succeeded so add to the connections authenticaiton
+                    // list and continue on to the next one
+                    getCurrentConnectionProfile().addAuthenticationMethod(auth);
+                    msg = auth.getMethodName() +
+                        " authentication succeeded but another is required";
+                }
+
+                if (result == AuthenticationProtocolState.COMPLETE) {
+                    authenticated = true;
+
+                    //If successfull add to the connections list so we can save later
+                    getCurrentConnectionProfile().addAuthenticationMethod(auth);
+
+                    // Set the completed flag
+                    completed = true;
+                    authenticationComplete(newProfile);
+                }
+
+                if (result == AuthenticationProtocolState.CANCELLED) {
+                    ssh.disconnect();
+
+                    return false;
+                }
+
+                //   }
+                //  else {
+                // User has cancelled the authenticaiton
+                //       closeConnection(true);
+                //       return false;
+                //  }
+            }
+
+            // end of if
+        }
+
+        // end of while
+        return authenticated;
+    }
+
+    /**
+*
+*
+* @param instance
+*
+* @return
+*
+* @throws IOException
+*/
+    protected int showAuthenticationPrompt(SshAuthenticationClient instance)
+        throws IOException {
+        instance.setUsername(getCurrentConnectionProfile().getUsername());
+
+        if (instance instanceof PasswordAuthenticationClient) {
+            PasswordAuthenticationDialog dialog = new PasswordAuthenticationDialog((Frame) SwingUtilities.getAncestorOfClass(
+                        Frame.class, SshToolsApplicationClientPanel.this));
+            instance.setAuthenticationPrompt(dialog);
+            ((PasswordAuthenticationClient) instance).setPasswordChangePrompt(PasswordChange.getInstance());
+            PasswordChange.getInstance().setParentComponent(SshToolsApplicationClientPanel.this);
+        } else if (instance instanceof PublicKeyAuthenticationClient) {
+            PublicKeyAuthenticationPrompt prompt = new PublicKeyAuthenticationPrompt(SshToolsApplicationClientPanel.this);
+            instance.setAuthenticationPrompt(prompt);
+        } else if (instance instanceof KBIAuthenticationClient) {
+            KBIAuthenticationClient kbi = new KBIAuthenticationClient();
+            ((KBIAuthenticationClient) instance).setKBIRequestHandler(new KBIRequestHandlerDialog(
+                    (Frame) SwingUtilities.getAncestorOfClass(Frame.class,
+                        SshToolsApplicationClientPanel.this)));
+        }
+
+        return ssh.authenticate(instance);
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public abstract boolean postConnection();
+
+    /**
+*
+*
+* @param newProfile
+*
+* @throws SshException
+* @throws IOException
+*/
+    public abstract void authenticationComplete(boolean newProfile)
+        throws SshException, IOException;
+
+    /**
+*
+*
+* @param file
+*/
+    public void setContainerTitle(File file) {
+        String verString = ConfigurationLoader.getVersionString(application.getApplicationName(),
+                application.getApplicationVersion());
+
+        if (container != null) {
+            container.setContainerTitle((file == null) ? verString
+                                                       : (verString + " [" +
+                file.getName() + "]"));
+        }
+    }
+
+    /**
+*
+*
+* @param needSave
+*/
+    public void setNeedSave(boolean needSave) {
+        if (needSave != this.needSave) {
+            this.needSave = needSave;
+            setAvailableActions();
+        }
+    }
+
+    /**
+*
+*
+* @param file
+*/
+    public void setCurrentConnectionFile(File file) {
+        currentConnectionFile = file;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public File getCurrentConnectionFile() {
+        return currentConnectionFile;
+    }
+
+    /**
+*
+*
+* @param profile
+*/
+    public void setCurrentConnectionProfile(SshToolsConnectionProfile profile) {
+        currentConnectionProfile = profile;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public SshToolsConnectionProfile getCurrentConnectionProfile() {
+        return currentConnectionProfile;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public boolean isNeedSave() {
+        return needSave;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public boolean isConnected() {
+        return (ssh != null) && ssh.isConnected();
+    }
+
+    /**
+*
+*
+* @throws SshException
+*/
+    public void connect() throws SshException {
+        if (getCurrentConnectionProfile() == null) {
+            throw new SshException(
+                "Can't connect, no connection profile have been set.");
+        }
+
+        //  There isn't anywhere to store this setting yet
+        connect(getCurrentConnectionProfile(), false);
+    }
+
+    /**
+*
+*
+* @param disconnect
+*/
+    public void closeConnection(boolean disconnect) {
+        //
+        if (isNeedSave()) {
+            //  Only allow saving of files if allowed by the security manager
+            try {
+                if (System.getSecurityManager() != null) {
+                    AccessController.checkPermission(new FilePermission(
+                            "<<ALL FILES>>", "write"));
+
+                    if (JOptionPane.showConfirmDialog(this,
+                                "You have unsaved changes to the connection " +
+                                ((currentConnectionFile == null) ? "<Untitled>"
+                                                                     : currentConnectionFile.getName()) +
+                                ".\nDo you want to save the changes now?",
+                                "Unsaved changes", JOptionPane.YES_NO_OPTION,
+                                JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION) {
+                        saveConnection(false, getCurrentConnectionFile(),
+                            getCurrentConnectionProfile());
+                        setNeedSave(false);
+                    }
+                }
+            } catch (AccessControlException ace) {
+                log.warn(
+                    "Changes made to connection, but security manager won't allow saving of files.");
+            }
+        }
+
+        setCurrentConnectionFile(null);
+    }
+
+    /**
+*
+*
+* @return
+*/
+    protected boolean allowConnectionSettingsEditing() {
+        return true;
+    }
+
+    /**
+*
+*
+* @param profile
+*
+* @return
+*/
+    public boolean editConnection(SshToolsConnectionProfile profile) {
+        final SshToolsConnectionPanel panel = new SshToolsConnectionPanel(allowConnectionSettingsEditing());
+        SshToolsConnectionTab[] tabs = getAdditionalConnectionTabs();
+
+        for (int i = 0; (tabs != null) && (i < tabs.length); i++) {
+            tabs[i].setConnectionProfile(profile);
+            panel.addTab(tabs[i]);
+        }
+
+        panel.setConnectionProfile(profile);
+
+        final Option ok = new Option("Ok",
+                "Apply the settings and close this dialog", 'o');
+        final Option cancel = new Option("Cancel",
+                "Close this dialog without applying the settings", 'c');
+        OptionCallback callback = new OptionCallback() {
+                public boolean canClose(OptionsDialog dialog, Option option) {
+                    if (option == ok) {
+                        return panel.validateTabs();
+                    }
+
+                    return true;
+                }
+            };
+
+        OptionsDialog od = OptionsDialog.createOptionDialog(SshToolsApplicationClientPanel.this,
+                new Option[] { ok, cancel }, panel, "Connection Settings", ok,
+                callback, null);
+        od.pack();
+        UIUtil.positionComponent(SwingConstants.CENTER, od);
+        od.setVisible(true);
+
+        if (od.getSelectedOption() == ok) {
+            panel.applyTabs();
+
+            if (profile == getCurrentConnectionProfile()) {
+                setNeedSave(true);
+            }
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+*
+*
+* @param saveAs
+* @param file
+* @param profile
+*
+* @return
+*/
+    public File saveConnection(boolean saveAs, File file,
+        SshToolsConnectionProfile profile) {
+        if (profile != null) {
+            if ((file == null) || saveAs) {
+                String prefsDir = super.getApplication()
+                                       .getApplicationPreferencesDirectory()
+                                       .getAbsolutePath();
+                JFileChooser fileDialog = new JFileChooser(prefsDir);
+                fileDialog.setFileFilter(connectionFileFilter);
+
+                int ret = fileDialog.showSaveDialog(this);
+
+                if (ret == fileDialog.CANCEL_OPTION) {
+                    return null;
+                }
+
+                file = fileDialog.getSelectedFile();
+
+                if (!file.getName().toLowerCase().endsWith(".xml")) {
+                    file = new File(file.getAbsolutePath() + ".xml");
+                }
+            }
+
+            try {
+                if (saveAs && file.exists()) {
+                    if (JOptionPane.showConfirmDialog(this,
+                                "File already exists. Are you sure?",
+                                "File exists", JOptionPane.YES_NO_OPTION,
+                                JOptionPane.WARNING_MESSAGE) == JOptionPane.NO_OPTION) {
+                        return null;
+                    }
+                }
+
+                // Check to make sure its valid
+                if (file != null) {
+                    // Save the connection details to file
+                    log.debug("Saving connection to " + file.getAbsolutePath());
+                    profile.save(file.getAbsolutePath());
+
+                    if (profile == getCurrentConnectionProfile()) {
+                        log.debug(
+                            "Current connection saved, disabling save action.");
+                        setNeedSave(false);
+                    }
+
+                    return file;
+                } else {
+                    showExceptionMessage("The file specified is invalid!",
+                        "Save Connection");
+                }
+            } catch (InvalidProfileFileException e) {
+                showExceptionMessage(e.getMessage(), "Save Connection");
+            }
+        }
+
+        return null;
+    }
+
+    public static class ActionMenu implements Comparable {
+        int weight;
+        int mnemonic;
+        String name;
+        String displayName;
+
+        public ActionMenu(String name, String displayName, int mnemonic,
+            int weight) {
+            this.name = name;
+            this.displayName = displayName;
+            this.mnemonic = mnemonic;
+            this.weight = weight;
+        }
+
+        public int compareTo(Object o) {
+            int i = new Integer(weight).compareTo(new Integer(
+                        ((ActionMenu) o).weight));
+
+            return (i == 0)
+            ? displayName.compareTo(((ActionMenu) o).displayName) : i;
+        }
+    }
+
+    class ToolBarActionComparator implements Comparator {
+        public int compare(Object o1, Object o2) {
+            int i = ((Integer) ((StandardAction) o1).getValue(StandardAction.TOOLBAR_GROUP)).compareTo((Integer) ((StandardAction) o2).getValue(
+                        StandardAction.TOOLBAR_GROUP));
+
+            return (i == 0)
+            ? ((Integer) ((StandardAction) o1).getValue(StandardAction.TOOLBAR_WEIGHT)).compareTo((Integer) ((StandardAction) o2).getValue(
+                    StandardAction.TOOLBAR_WEIGHT)) : i;
+        }
+    }
+
+    class MenuItemActionComparator implements Comparator {
+        public int compare(Object o1, Object o2) {
+            int i = ((Integer) ((StandardAction) o1).getValue(StandardAction.MENU_ITEM_GROUP)).compareTo((Integer) ((StandardAction) o2).getValue(
+                        StandardAction.MENU_ITEM_GROUP));
+
+            return (i == 0)
+            ? ((Integer) ((StandardAction) o1).getValue(StandardAction.MENU_ITEM_WEIGHT)).compareTo((Integer) ((StandardAction) o2).getValue(
+                    StandardAction.MENU_ITEM_WEIGHT)) : i;
+        }
+    }
+
+    class ConnectionFileFilter extends javax.swing.filechooser.FileFilter {
+        public boolean accept(File f) {
+            return f.isDirectory() ||
+            f.getName().toLowerCase().endsWith(".xml");
+        }
+
+        public String getDescription() {
+            return "Connection files (*.xml)";
+        }
+    }
+
+    class SinkHostKeyVerification implements HostKeyVerification {
+        public boolean verifyHost(String host, SshPublicKey pk)
+            throws TransportProtocolException {
+            log.warn("Accepting host " + host +
+                " as host key verification is disabled.");
+
+            return true;
+        }
+    }
+}
diff --git a/src/com/sshtools/common/ui/SshToolsApplicationContainer.java b/src/com/sshtools/common/ui/SshToolsApplicationContainer.java
new file mode 100644
index 0000000000000000000000000000000000000000..22c50d577d1e0af7e025c2bad185164818817306
--- /dev/null
+++ b/src/com/sshtools/common/ui/SshToolsApplicationContainer.java
@@ -0,0 +1,79 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public interface SshToolsApplicationContainer {
+    /**
+*
+*
+* @param application
+* @param panel
+*
+* @throws SshToolsApplicationException
+*/
+    public void init(SshToolsApplication application,
+        SshToolsApplicationPanel panel) throws SshToolsApplicationException;
+
+    /**
+*
+*
+* @return
+*/
+    public SshToolsApplicationPanel getApplicationPanel();
+
+    /**
+*
+*/
+    public void closeContainer();
+
+    /**
+*
+*
+* @param visible
+*/
+    public void setContainerVisible(boolean visible);
+
+    /**
+*
+*
+* @param title
+*/
+    public void setContainerTitle(String title);
+
+    /**
+*
+*
+* @return
+*/
+    public boolean isContainerVisible();
+}
diff --git a/src/com/sshtools/common/ui/SshToolsApplicationException.java b/src/com/sshtools/common/ui/SshToolsApplicationException.java
new file mode 100644
index 0000000000000000000000000000000000000000..247d7ab6edea228ef22e908bf06490cf480b2ae3
--- /dev/null
+++ b/src/com/sshtools/common/ui/SshToolsApplicationException.java
@@ -0,0 +1,81 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.lang.reflect.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class SshToolsApplicationException extends Exception {
+    /**
+* Creates a new SshToolsApplicationException object.
+*/
+    public SshToolsApplicationException() {
+        this(null, null);
+    }
+
+    /**
+* Creates a new SshToolsApplicationException object.
+*
+* @param msg
+*/
+    public SshToolsApplicationException(String msg) {
+        this(msg, null);
+    }
+
+    /**
+* Creates a new SshToolsApplicationException object.
+*
+* @param cause
+*/
+    public SshToolsApplicationException(Throwable cause) {
+        this(null, cause);
+    }
+
+    /**
+* Creates a new SshToolsApplicationException object.
+*
+* @param msg
+* @param cause
+*/
+    public SshToolsApplicationException(String msg, Throwable cause) {
+        super(msg);
+
+        if (cause != null) {
+            try {
+                Method m = getClass().getMethod("initCause",
+                        new Class[] { Throwable.class });
+                m.invoke(this, new Object[] { cause });
+            } catch (Exception e) {
+            }
+        }
+    }
+}
diff --git a/src/com/sshtools/common/ui/SshToolsApplicationFrame.java b/src/com/sshtools/common/ui/SshToolsApplicationFrame.java
new file mode 100644
index 0000000000000000000000000000000000000000..0bd4491f20f23f1e8bb54351b2ee2bd6e187b1da
--- /dev/null
+++ b/src/com/sshtools/common/ui/SshToolsApplicationFrame.java
@@ -0,0 +1,303 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.Point;
+import java.awt.Toolkit;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JSeparator;
+import javax.swing.SwingConstants;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.19 $
+ */
+public class SshToolsApplicationFrame extends JFrame
+    implements SshToolsApplicationContainer {
+    //  Preference names
+
+    /**  */
+    public final static String PREF_LAST_FRAME_GEOMETRY = "application.lastFrameGeometry";
+
+    /**  */
+    protected Log log = LogFactory.getLog(SshToolsApplicationFrame.class);
+
+    /**  */
+    protected StandardAction exitAction;
+
+    /**  */
+    protected StandardAction aboutAction;
+
+    /**  */
+    protected StandardAction newWindowAction;
+
+    /**  */
+    protected JSeparator toolSeparator;
+
+    //
+    private SshToolsApplicationPanel panel;
+    private SshToolsApplication application;
+    private boolean showAboutBox = true;
+    private boolean showExitAction = true;
+    private boolean showNewWindowAction = true;
+    private boolean showMenu = true;
+
+    public void showAboutBox(boolean showAboutBox) {
+        this.showAboutBox = showAboutBox;
+    }
+
+    public void showExitAction(boolean showExitAction) {
+        this.showExitAction = showExitAction;
+    }
+
+    public void showNewWindowAction(boolean showNewWindowAction) {
+        this.showNewWindowAction = showNewWindowAction;
+    }
+
+    /**
+*
+*
+* @param application
+* @param panel
+*
+* @throws SshToolsApplicationException
+*/
+    public void init(final SshToolsApplication application,
+        SshToolsApplicationPanel panel) throws SshToolsApplicationException {
+        this.panel = panel;
+        this.application = application;
+
+        if (application != null) {
+            setTitle(ConfigurationLoader.getVersionString(
+                    application.getApplicationName(),
+                    application.getApplicationVersion())); // + " " + application.getApplicationVersion());
+        }
+
+        setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+
+        // Register the File menu
+        panel.registerActionMenu(new SshToolsApplicationPanel.ActionMenu(
+                "File", "File", 'f', 0));
+
+        // Register the Exit action
+        if (showExitAction && (application != null)) {
+            panel.registerAction(exitAction = new ExitAction(application, this));
+
+            // Register the New Window Action
+        }
+
+        if (showNewWindowAction && (application != null)) {
+            panel.registerAction(newWindowAction = new NewWindowAction(
+                        application));
+
+            // Register the Help menu
+        }
+
+        panel.registerActionMenu(new SshToolsApplicationPanel.ActionMenu(
+                "Help", "Help", 'h', 99));
+
+        // Register the About box action
+        if (showAboutBox && (application != null)) {
+            panel.registerAction(aboutAction = new AboutAction(this, application));
+        }
+
+        getApplicationPanel().rebuildActionComponents();
+
+        JPanel p = new JPanel(new BorderLayout());
+
+        if (panel.getJMenuBar() != null) {
+            setJMenuBar(panel.getJMenuBar());
+        }
+
+        if (panel.getToolBar() != null) {
+            JPanel t = new JPanel(new BorderLayout());
+            t.add(panel.getToolBar(), BorderLayout.NORTH);
+            t.add(toolSeparator = new JSeparator(JSeparator.HORIZONTAL),
+                BorderLayout.SOUTH);
+            toolSeparator.setVisible(panel.getToolBar().isVisible());
+
+            final SshToolsApplicationPanel pnl = panel;
+            panel.getToolBar().addComponentListener(new ComponentAdapter() {
+                    public void componentHidden(ComponentEvent evt) {
+                        log.debug("Tool separator is now " +
+                            pnl.getToolBar().isVisible());
+                        toolSeparator.setVisible(pnl.getToolBar().isVisible());
+                    }
+                });
+            p.add(t, BorderLayout.NORTH);
+        }
+
+        p.add(panel, BorderLayout.CENTER);
+
+        if (panel.getStatusBar() != null) {
+            p.add(panel.getStatusBar(), BorderLayout.SOUTH);
+        }
+
+        getContentPane().setLayout(new GridLayout(1, 1));
+        getContentPane().add(p);
+
+        // Watch for the frame closing
+        setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+        addWindowListener(new WindowAdapter() {
+                public void windowClosing(WindowEvent evt) {
+                    if (application != null) {
+                        application.closeContainer(SshToolsApplicationFrame.this);
+                    } else {
+                        int confirm = JOptionPane.showOptionDialog(SshToolsApplicationFrame.this,
+                                "Close " + getTitle() + "?", "Close Operation",
+                                JOptionPane.YES_NO_OPTION,
+                                JOptionPane.QUESTION_MESSAGE, null, null, null);
+
+                        if (confirm == 0) {
+                            hide();
+                        }
+                    }
+                }
+            });
+
+        // If this is the first frame, center the window on the screen
+        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+        boolean found = false;
+
+        if ((application != null) && (application.getContainerCount() != 0)) {
+            for (int i = 0; (i < application.getContainerCount()) && !found;
+                    i++) {
+                SshToolsApplicationContainer c = application.getContainerAt(i);
+
+                if (c instanceof SshToolsApplicationFrame) {
+                    SshToolsApplicationFrame f = (SshToolsApplicationFrame) c;
+                    setSize(f.getSize());
+
+                    Point newLocation = new Point(f.getX(), f.getY());
+                    newLocation.x += 48;
+                    newLocation.y += 48;
+
+                    if (newLocation.x > (screenSize.getWidth() - 64)) {
+                        newLocation.x = 0;
+                    }
+
+                    if (newLocation.y > (screenSize.getHeight() - 64)) {
+                        newLocation.y = 0;
+                    }
+
+                    setLocation(newLocation);
+                    found = true;
+                }
+            }
+        }
+
+        if (!found) {
+            // Is there a previous stored geometry we can use?
+            if (PreferencesStore.preferenceExists(PREF_LAST_FRAME_GEOMETRY)) {
+                setBounds(PreferencesStore.getRectangle(
+                        PREF_LAST_FRAME_GEOMETRY, getBounds()));
+            } else {
+                pack();
+                UIUtil.positionComponent(SwingConstants.CENTER, this);
+            }
+        }
+    }
+
+    /**
+*
+*
+* @param title
+*/
+    public void setContainerTitle(String title) {
+        setTitle(title);
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public SshToolsApplication getApplication() {
+        return application;
+    }
+
+    /**
+*
+*
+* @param visible
+*/
+    public void setContainerVisible(boolean visible) {
+        setVisible(visible);
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public boolean isContainerVisible() {
+        return isVisible();
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public SshToolsApplicationPanel getApplicationPanel() {
+        return panel;
+    }
+
+    /**
+*
+*/
+    public void closeContainer() {
+        /*  If this is the last frame to close, then store its geometry for use
+when the next frame opens */
+        if ((application != null) && (application.getContainerCount() == 1)) {
+            PreferencesStore.putRectangle(PREF_LAST_FRAME_GEOMETRY, getBounds());
+        }
+
+        dispose();
+        getApplicationPanel().deregisterAction(newWindowAction);
+        getApplicationPanel().deregisterAction(exitAction);
+        getApplicationPanel().deregisterAction(aboutAction);
+        getApplicationPanel().rebuildActionComponents();
+    }
+}
diff --git a/src/com/sshtools/common/ui/SshToolsApplicationInternalFrame.java b/src/com/sshtools/common/ui/SshToolsApplicationInternalFrame.java
new file mode 100644
index 0000000000000000000000000000000000000000..a27966a07031c0ba3b5a81a413890fbfc94bbd23
--- /dev/null
+++ b/src/com/sshtools/common/ui/SshToolsApplicationInternalFrame.java
@@ -0,0 +1,313 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.awt.BorderLayout;
+import java.awt.GridLayout;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyVetoException;
+import java.beans.VetoableChangeListener;
+
+import javax.swing.JFrame;
+import javax.swing.JInternalFrame;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JSeparator;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.4 $
+ */
+public class SshToolsApplicationInternalFrame extends JInternalFrame
+    implements SshToolsApplicationContainer {
+    //  Preference names
+
+    /**  */
+    public final static String PREF_LAST_FRAME_GEOMETRY = "application.lastFrameGeometry";
+
+    /**  */
+    protected Log log = LogFactory.getLog(SshToolsApplicationInternalFrame.class);
+
+    /**  */
+    protected StandardAction exitAction;
+
+    /**  */
+    protected StandardAction aboutAction;
+
+    /**  */
+    protected StandardAction newWindowAction;
+
+    /**  */
+    protected JSeparator toolSeparator;
+
+    //
+    private SshToolsApplicationPanel panel;
+    private SshToolsApplication application;
+    private boolean showAboutBox = true;
+    private boolean showExitAction = true;
+    private boolean showNewWindowAction = true;
+    private boolean showMenu = true;
+
+    public void showAboutBox(boolean showAboutBox) {
+        this.showAboutBox = showAboutBox;
+    }
+
+    public void showExitAction(boolean showExitAction) {
+        this.showExitAction = showExitAction;
+    }
+
+    public void showNewWindowAction(boolean showNewWindowAction) {
+        this.showNewWindowAction = showNewWindowAction;
+    }
+
+    /**
+*
+*
+* @param application
+* @param panel
+*
+* @throws SshToolsApplicationException
+*/
+    public void init(final SshToolsApplication application,
+        SshToolsApplicationPanel panel) throws SshToolsApplicationException {
+        this.panel = panel;
+        this.application = application;
+
+        if (application != null) {
+            setTitle(ConfigurationLoader.getVersionString(
+                    application.getApplicationName(),
+                    application.getApplicationVersion())); // + " " + application.getApplicationVersion());
+        }
+
+        // Register the File menu
+        panel.registerActionMenu(new SshToolsApplicationPanel.ActionMenu(
+                "File", "File", 'f', 0));
+
+        // Register the Exit action
+        if (showExitAction && (application != null)) {
+            panel.registerAction(exitAction = new ExitAction(application, this));
+
+            // Register the New Window Action
+        }
+
+        if (showNewWindowAction && (application != null)) {
+            panel.registerAction(newWindowAction = new NewWindowAction(
+                        application));
+
+            // Register the Help menu
+        }
+
+        panel.registerActionMenu(new SshToolsApplicationPanel.ActionMenu(
+                "Help", "Help", 'h', 99));
+
+        // Register the About box action
+        if (showAboutBox && (application != null)) {
+            panel.registerAction(aboutAction = new AboutAction(this, application));
+        }
+
+        getApplicationPanel().rebuildActionComponents();
+
+        JPanel p = new JPanel(new BorderLayout());
+
+        if (panel.getJMenuBar() != null) {
+            setJMenuBar(panel.getJMenuBar());
+        }
+
+        if (panel.getToolBar() != null) {
+            JPanel t = new JPanel(new BorderLayout());
+            t.add(panel.getToolBar(), BorderLayout.NORTH);
+            t.add(toolSeparator = new JSeparator(JSeparator.HORIZONTAL),
+                BorderLayout.SOUTH);
+            toolSeparator.setVisible(panel.getToolBar().isVisible());
+
+            final SshToolsApplicationPanel pnl = panel;
+            panel.getToolBar().addComponentListener(new ComponentAdapter() {
+                    public void componentHidden(ComponentEvent evt) {
+                        log.debug("Tool separator is now " +
+                            pnl.getToolBar().isVisible());
+                        toolSeparator.setVisible(pnl.getToolBar().isVisible());
+                    }
+                });
+            p.add(t, BorderLayout.NORTH);
+        }
+
+        p.add(panel, BorderLayout.CENTER);
+
+        if (panel.getStatusBar() != null) {
+            p.add(panel.getStatusBar(), BorderLayout.SOUTH);
+        }
+
+        getContentPane().setLayout(new GridLayout(1, 1));
+        getContentPane().add(p);
+
+        // Watch for the frame closing
+        //setDefaultCloseOperation(JInternalFrame.DO_NOTHING_ON_CLOSE);
+        addVetoableChangeListener(new VetoableChangeListener() {
+                public void vetoableChange(PropertyChangeEvent evt)
+                    throws PropertyVetoException {
+                    if (application != null) {
+                        application.closeContainer(SshToolsApplicationInternalFrame.this);
+                    } else {
+                        if (evt.getPropertyName().equals(IS_CLOSED_PROPERTY)) {
+                            boolean changed = ((Boolean) evt.getNewValue()).booleanValue();
+
+                            if (changed) {
+                                int confirm = JOptionPane.showOptionDialog(SshToolsApplicationInternalFrame.this,
+                                        "Close " + getTitle() + "?",
+                                        "Close Operation",
+                                        JOptionPane.YES_NO_OPTION,
+                                        JOptionPane.QUESTION_MESSAGE, null,
+                                        null, null);
+
+                                if (confirm == 0) {
+                                    SshToolsApplicationInternalFrame.this.getDesktopPane()
+                                                                         .remove(SshToolsApplicationInternalFrame.this);
+                                }
+                            }
+                        }
+                    }
+                }
+            });
+
+        /*this.addWindowListener(new WindowAdapter() {
+public void windowClosing(WindowEvent evt) {
+if(application!=null)
+application.closeContainer(SshToolsApplicationFrame.this);
+else
+hide();
+}
+});
+// If this is the first frame, center the window on the screen
+/*Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+boolean found = false;
+if (application!=null && application.getContainerCount() != 0) {
+for (int i = 0; (i < application.getContainerCount()) && !found;
+i++) {
+SshToolsApplicationContainer c = application.getContainerAt(i);
+if (c instanceof SshToolsApplicationFrame) {
+SshToolsApplicationFrame f = (SshToolsApplicationFrame) c;
+setSize(f.getSize());
+Point newLocation = new Point(f.getX(), f.getY());
+newLocation.x += 48;
+newLocation.y += 48;
+if (newLocation.x > (screenSize.getWidth() - 64)) {
+ newLocation.x = 0;
+}
+if (newLocation.y > (screenSize.getHeight() - 64)) {
+ newLocation.y = 0;
+}
+setLocation(newLocation);
+found = true;
+}
+}
+}
+if (!found) {
+// Is there a previous stored geometry we can use?
+if (PreferencesStore.preferenceExists(PREF_LAST_FRAME_GEOMETRY)) {
+setBounds(PreferencesStore.getRectangle(
+ PREF_LAST_FRAME_GEOMETRY, getBounds()));
+}
+else {
+pack();
+UIUtil.positionComponent(SwingConstants.CENTER, this);
+}
+}*/
+        pack();
+    }
+
+    /**
+*
+*
+* @param title
+*/
+    public void setContainerTitle(String title) {
+        setTitle(title);
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public SshToolsApplication getApplication() {
+        return application;
+    }
+
+    /**
+*
+*
+* @param visible
+*/
+    public void setContainerVisible(boolean visible) {
+        setVisible(visible);
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public boolean isContainerVisible() {
+        return isVisible();
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public SshToolsApplicationPanel getApplicationPanel() {
+        return panel;
+    }
+
+    /**
+*
+*/
+    public void closeContainer() {
+        /*  If this is the last frame to close, then store its geometry for use
+when the next frame opens */
+        if ((application != null) && (application.getContainerCount() == 1)) {
+            PreferencesStore.putRectangle(PREF_LAST_FRAME_GEOMETRY, getBounds());
+        }
+
+        dispose();
+        getApplicationPanel().deregisterAction(newWindowAction);
+        getApplicationPanel().deregisterAction(exitAction);
+        getApplicationPanel().deregisterAction(aboutAction);
+        getApplicationPanel().rebuildActionComponents();
+    }
+}
diff --git a/src/com/sshtools/common/ui/SshToolsApplicationPanel.java b/src/com/sshtools/common/ui/SshToolsApplicationPanel.java
new file mode 100644
index 0000000000000000000000000000000000000000..a16b1938eaa0eefadf4d5b5a21404c28bf4e20d0
--- /dev/null
+++ b/src/com/sshtools/common/ui/SshToolsApplicationPanel.java
@@ -0,0 +1,794 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.awt.Component;
+import java.awt.LayoutManager;
+import java.awt.event.ActionEvent;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Vector;
+
+import javax.swing.Action;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JToolBar;
+import javax.swing.filechooser.FileFilter;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.23 $
+ */
+public abstract class SshToolsApplicationPanel extends JPanel {
+    //
+
+    /**  */
+    protected Log log = LogFactory.getLog(SshToolsApplicationPanel.class);
+
+    /**  */
+    protected SshToolsApplication application;
+
+    /**  */
+    protected JMenuBar menuBar;
+
+    /**  */
+    protected JToolBar toolBar;
+
+    /**  */
+    protected JPopupMenu contextMenu;
+
+    /**  */
+    protected SshToolsApplicationContainer container;
+
+    /**  */
+    protected Vector actions = new Vector();
+
+    /**  */
+    protected HashMap actionsVisible = new HashMap();
+
+    /**  */
+    protected boolean toolsVisible;
+
+    /**  */
+    protected Vector actionMenus = new Vector();
+
+    /**
+* Creates a new SshToolsApplicationPanel object.
+*/
+    public SshToolsApplicationPanel() {
+        super();
+    }
+
+    /**
+* Creates a new SshToolsApplicationPanel object.
+*
+* @param mgr
+*/
+    public SshToolsApplicationPanel(LayoutManager mgr) {
+        super(mgr);
+    }
+
+    /**
+* Called by the application framework to test the closing state
+*
+* @return
+*/
+    public abstract boolean canClose();
+
+    /**
+* Called by the application framework to close the panel
+*/
+    public abstract void close();
+
+    /**
+* Called by the application framework when a change in connection state
+* has occured. The available actions should be enabled/disabled in this
+* methods implementation
+*/
+    public abstract void setAvailableActions();
+
+    /**
+* Set an actions visible state
+*
+* @param name
+* @param visible
+*/
+    public void setActionVisible(String name, boolean visible) {
+        log.debug("Setting action '" + name + "' to visibility " + visible);
+        actionsVisible.put(name, new Boolean(visible));
+    }
+
+    /**
+* Gets the container for this panel.
+*
+* @return
+*/
+    public SshToolsApplicationContainer getContainer() {
+        return container;
+    }
+
+    /**
+* Sets the container for this panel
+*
+* @param container
+*/
+    public void setContainer(SshToolsApplicationContainer container) {
+        this.container = container;
+    }
+
+    /**
+* Register a new menu
+*
+* @param actionMenu
+*/
+    public void registerActionMenu(ActionMenu actionMenu) {
+        ActionMenu current = getActionMenu(actionMenu.name);
+
+        if (current == null) {
+            actionMenus.addElement(actionMenu);
+        }
+    }
+
+    /**
+* Gets a menu by name
+*
+* @param actionMenuName
+*
+* @return
+*/
+    public ActionMenu getActionMenu(String actionMenuName) {
+        return getActionMenu(actionMenus.iterator(), actionMenuName);
+    }
+
+    private ActionMenu getActionMenu(Iterator actions, String actionMenuName) {
+        while (actions.hasNext()) {
+            ActionMenu a = (ActionMenu) actions.next();
+
+            if (a.name.equals(actionMenuName)) {
+                return a;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+* Get an action by name
+*
+* @param name
+*
+* @return
+*/
+    public StandardAction getAction(String name) {
+        for (Iterator i = actions.iterator(); i.hasNext();) {
+            StandardAction a = (StandardAction) i.next();
+
+            if (a.getName().equals(name)) {
+                return a;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+* Deregister an action
+*
+* @param action
+*/
+    public void deregisterAction(StandardAction action) {
+        actions.removeElement(action);
+    }
+
+    /**
+* Register a new action
+*
+* @param action
+*/
+    public void registerAction(StandardAction action) {
+        actions.addElement(action);
+    }
+
+    /**
+* Initialize the panel
+*
+* @param application
+*
+* @throws SshToolsApplicationException
+*/
+    public void init(SshToolsApplication application)
+        throws SshToolsApplicationException {
+        this.application = application;
+        menuBar = new JMenuBar();
+
+        // Creat the tool bar
+        toolBar = new JToolBar();
+        toolBar.setFloatable(false);
+        toolBar.setBorderPainted(false);
+        toolBar.putClientProperty("JToolBar.isRollover", Boolean.TRUE);
+
+        // Create the context menu
+        contextMenu = new JPopupMenu();
+        registerActionMenu(new ActionMenu("Tools", "Tools", 't', 30));
+
+        if (PreferencesStore.isStoreAvailable()) {
+            log.debug("Preferences store is available, adding options action");
+            registerAction(new OptionsAction() {
+                    public void actionPerformed(ActionEvent evt) {
+                        showOptions();
+                    }
+                });
+        }
+    }
+
+    /**
+* Show the options dialog
+*/
+    public void showOptions() {
+        OptionsTab[] tabs = getApplication().getAdditionalOptionsTabs();
+        OptionsPanel.showOptionsDialog(this, tabs);
+    }
+
+    /**
+* Rebuild all the action components such as toobar, context menu
+*/
+    public void rebuildActionComponents() {
+        //  Clear the current state of the component
+        log.debug("Rebuild action components");
+        toolBar.removeAll();
+
+        //
+        Vector enabledActions = new Vector();
+
+        for (Iterator i = actions.iterator(); i.hasNext();) {
+            StandardAction a = (StandardAction) i.next();
+            String n = (String) a.getValue(Action.NAME);
+            Boolean s = (Boolean) actionsVisible.get(n);
+
+            if (s == null) {
+                s = Boolean.TRUE;
+            }
+
+            if (Boolean.TRUE.equals(s)) {
+                log.debug("Action " + n + " is enabled.");
+                enabledActions.add(a);
+            } else {
+                log.debug("Action " + n + " not enabled.");
+            }
+        }
+
+        //  Build the tool bar, grouping the actions
+        Vector v = new Vector();
+
+        for (Iterator i = enabledActions.iterator(); i.hasNext();) {
+            StandardAction a = (StandardAction) i.next();
+
+            if (Boolean.TRUE.equals(
+                        (Boolean) a.getValue(StandardAction.ON_TOOLBAR))) {
+                v.addElement(a);
+            }
+        }
+
+        Collections.sort(v, new ToolBarActionComparator());
+
+        Integer grp = null;
+
+        for (Iterator i = v.iterator(); i.hasNext();) {
+            StandardAction z = (StandardAction) i.next();
+
+            if ((grp != null) &&
+                    !grp.equals(
+                        (Integer) z.getValue(StandardAction.TOOLBAR_GROUP))) {
+                toolBar.add(new ToolBarSeparator());
+            }
+
+            if (Boolean.TRUE.equals(
+                        (Boolean) z.getValue(StandardAction.IS_TOGGLE_BUTTON))) {
+                ToolToggleButton tBtn = new ToolToggleButton(z);
+                toolBar.add(tBtn);
+            } else {
+                ToolButton btn = new ToolButton(z);
+                toolBar.add(btn);
+            }
+
+            grp = (Integer) z.getValue(StandardAction.TOOLBAR_GROUP);
+        }
+
+        toolBar.revalidate();
+        toolBar.repaint();
+
+        //  Build the context menu, grouping the actions
+        Vector c = new Vector();
+        contextMenu.removeAll();
+
+        for (Iterator i = enabledActions.iterator(); i.hasNext();) {
+            StandardAction a = (StandardAction) i.next();
+
+            if (Boolean.TRUE.equals(
+                        (Boolean) a.getValue(StandardAction.ON_CONTEXT_MENU))) {
+                c.addElement(a);
+            }
+        }
+
+        Collections.sort(c, new ContextActionComparator());
+        grp = null;
+
+        for (Iterator i = c.iterator(); i.hasNext();) {
+            StandardAction z = (StandardAction) i.next();
+
+            if ((grp != null) &&
+                    !grp.equals(
+                        (Integer) z.getValue(StandardAction.CONTEXT_MENU_GROUP))) {
+                contextMenu.addSeparator();
+            }
+
+            contextMenu.add(z);
+            grp = (Integer) z.getValue(StandardAction.CONTEXT_MENU_GROUP);
+        }
+
+        contextMenu.revalidate();
+
+        //  Build the menu bar
+        menuBar.removeAll();
+        v.removeAllElements();
+
+        for (Enumeration e = enabledActions.elements(); e.hasMoreElements();) {
+            StandardAction a = (StandardAction) e.nextElement();
+
+            if (Boolean.TRUE.equals(
+                        (Boolean) a.getValue(StandardAction.ON_MENUBAR))) {
+                v.addElement(a);
+            }
+        }
+
+        Vector menus = (Vector) actionMenus.clone();
+        Collections.sort(menus);
+
+        HashMap map = new HashMap();
+
+        for (Iterator i = v.iterator(); i.hasNext();) {
+            StandardAction z = (StandardAction) i.next();
+            String menuName = (String) z.getValue(StandardAction.MENU_NAME);
+
+            if (menuName == null) {
+                log.error("Action " + z.getName() +
+                    " doesnt specify a value for " + StandardAction.MENU_NAME);
+            } else {
+                String m = (String) z.getValue(StandardAction.MENU_NAME);
+                ActionMenu menu = getActionMenu(menus.iterator(), m);
+
+                if (menu == null) {
+                    log.error("Action menu " + z.getName() + " does not exist");
+                } else {
+                    Vector x = (Vector) map.get(menu.name);
+
+                    if (x == null) {
+                        x = new Vector();
+                        map.put(menu.name, x);
+                    }
+
+                    x.addElement(z);
+                }
+            }
+        }
+
+        for (Iterator i = menus.iterator(); i.hasNext();) {
+            ActionMenu m = (ActionMenu) i.next();
+            Vector x = (Vector) map.get(m.name);
+
+            if (x != null) {
+                Collections.sort(x, new MenuItemActionComparator());
+
+                JMenu menu = new JMenu(m.displayName);
+                menu.setMnemonic(m.mnemonic);
+                grp = null;
+
+                for (Iterator j = x.iterator(); j.hasNext();) {
+                    StandardAction a = (StandardAction) j.next();
+                    Integer g = (Integer) a.getValue(StandardAction.MENU_ITEM_GROUP);
+
+                    if ((grp != null) && !g.equals(grp)) {
+                        menu.addSeparator();
+                    }
+
+                    grp = g;
+
+                    if (a instanceof MenuAction) {
+                        JMenu mnu = (JMenu) a.getValue(MenuAction.MENU);
+                        menu.add(mnu);
+                    } else {
+                        JMenuItem item = new JMenuItem(a);
+                        menu.add(item);
+                    }
+                }
+
+                menuBar.add(menu);
+            } else {
+                log.error("Can't find menu " + m.name);
+            }
+        }
+
+        menuBar.validate();
+        menuBar.repaint();
+    }
+
+    /**
+* Determine if the toolbar, menu and statusbar are visible
+*
+* @return
+*/
+    public boolean isToolsVisible() {
+        return toolsVisible;
+    }
+
+    // Adds the new favorite to the appropriate favorite menu
+    public void addFavorite(StandardAction action) {
+        for (int i = 0; i < menuBar.getMenuCount(); i++) {
+            JMenu menu = menuBar.getMenu(i);
+
+            if ((menu.getText() != null) && menu.getText().equals("Favorites")) {
+                menu.add(action);
+            }
+        }
+    }
+
+    /**
+* Set the visible state of the menu bar
+*
+* @param visible
+*/
+    public void setMenuBarVisible(boolean visible) {
+        if ((getJMenuBar() != null) && (getJMenuBar().isVisible() != visible)) {
+            getJMenuBar().setVisible(visible);
+            revalidate();
+        }
+    }
+
+    /**
+* Set the visible state of the toolbar
+*
+* @param visible
+*/
+    public void setToolBarVisible(boolean visible) {
+        if ((getToolBar() != null) && (getToolBar().isVisible() != visible)) {
+            getToolBar().setVisible(visible);
+            revalidate();
+        }
+    }
+
+    /**
+* Set the visible state of the statusbar
+*
+* @param visible
+*/
+    public void setStatusBarVisible(boolean visible) {
+        if ((getStatusBar() != null) &&
+                (getStatusBar().isVisible() != visible)) {
+            getStatusBar().setVisible(visible);
+            revalidate();
+        }
+    }
+
+    /**
+* Set the visible state of all tools. This will set the toolbar, menu and
+* status bar visible states to the value provided.
+*
+* @param visible
+*/
+    public void setToolsVisible(boolean visible) {
+        synchronized (getTreeLock()) {
+            if ((getToolBar() != null) &&
+                    (getToolBar().isVisible() != visible)) {
+                getToolBar().setVisible(visible);
+            }
+
+            if ((getJMenuBar() != null) &&
+                    (getJMenuBar().isVisible() != visible)) {
+                getJMenuBar().setVisible(visible);
+            }
+
+            if ((getStatusBar() != null) &&
+                    (getStatusBar().isVisible() != visible)) {
+                getStatusBar().setVisible(visible);
+            }
+
+            toolsVisible = visible;
+            revalidate();
+        }
+    }
+
+    /**
+* Show an exception message
+*
+* @param title
+* @param message
+*/
+    public void showExceptionMessage(String title, String message) {
+        JOptionPane.showMessageDialog(this, message, title,
+            JOptionPane.ERROR_MESSAGE);
+    }
+
+    /**
+* Show an error message with detail
+*
+* @param parent
+* @param title
+* @param exception
+*/
+    public static void showErrorMessage(Component parent, String title,
+        Throwable exception) {
+        showErrorMessage(parent, null, title, exception);
+    }
+
+    /**
+* Show an error message with toggable detail
+*
+* @param parent
+* @param mesg
+* @param title
+* @param exception
+*/
+    public static void showErrorMessage(Component parent, String mesg,
+        String title, Throwable exception) {
+        boolean details = false;
+
+        while (true) {
+            String[] opts = new String[] {
+                    details ? "Hide Details" : "Details", "Ok"
+                };
+            StringBuffer buf = new StringBuffer();
+
+            if (mesg != null) {
+                buf.append(mesg);
+            }
+
+            appendException(exception, 0, buf, details);
+
+            MultilineLabel message = new MultilineLabel(buf.toString());
+            int opt = JOptionPane.showOptionDialog(parent, message, title,
+                    JOptionPane.OK_CANCEL_OPTION, JOptionPane.ERROR_MESSAGE,
+                    null, opts, opts[1]);
+
+            if (opt == 0) {
+                details = !details;
+            } else {
+                break;
+            }
+        }
+    }
+
+    private static void appendException(Throwable exception, int level,
+        StringBuffer buf, boolean details) {
+        try {
+            if (((exception != null) && (exception.getMessage() != null)) &&
+                    (exception.getMessage().length() > 0)) {
+                if (details && (level > 0)) {
+                    buf.append("\n \nCaused by ...\n");
+                }
+
+                buf.append(exception.getMessage());
+            }
+
+            if (details) {
+                if (exception != null) {
+                    if ((exception.getMessage() != null) &&
+                            (exception.getMessage().length() == 0)) {
+                        buf.append("\n \nCaused by ...");
+                    } else {
+                        buf.append("\n \n");
+                    }
+                }
+
+                StringWriter sw = new StringWriter();
+
+                if (exception != null) {
+                    exception.printStackTrace(new PrintWriter(sw));
+                }
+
+                buf.append(sw.toString());
+            }
+
+            try {
+                java.lang.reflect.Method method = exception.getClass()
+                                                           .getMethod("getCause",
+                        new Class[] {  });
+                Throwable cause = (Throwable) method.invoke(exception, null);
+
+                if (cause != null) {
+                    appendException(cause, level + 1, buf, details);
+                }
+            } catch (Exception e) {
+            }
+        } catch (Throwable ex) {
+        }
+    }
+
+    /**
+* Returns the connected state of the panel
+*
+* @return
+*/
+    public abstract boolean isConnected();
+
+    /**
+* Set the title of the container
+*
+* @param file
+*/
+    public void setContainerTitle(File file) {
+        String verString = "";
+
+        if (application != null) {
+            verString = ConfigurationLoader.getVersionString(application.getApplicationName(),
+                    application.getApplicationVersion());
+        }
+
+        if (container != null) {
+            container.setContainerTitle((file == null) ? verString
+                                                       : (verString + " [" +
+                file.getName() + "]"));
+        }
+    }
+
+    /**
+* Gets the toolbar
+*
+* @return
+*/
+    public JToolBar getToolBar() {
+        return toolBar;
+    }
+
+    /**
+* Get the context menu
+*
+* @return
+*/
+    public JPopupMenu getContextMenu() {
+        return contextMenu;
+    }
+
+    /**
+* Get the main menu
+*
+* @return
+*/
+    public JMenuBar getJMenuBar() {
+        return menuBar;
+    }
+
+    /**
+* Get the status bar
+*
+* @return
+*/
+    public StatusBar getStatusBar() {
+        return null;
+    }
+
+    /**
+* Get the application attached to the panel
+*
+* @return
+*/
+    public SshToolsApplication getApplication() {
+        return application;
+    }
+
+    /**
+* Get the icon for the panel
+*
+* @return
+*/
+    public abstract ResourceIcon getIcon();
+
+    public static class ActionMenu implements Comparable {
+        int weight;
+        int mnemonic;
+        String name;
+        String displayName;
+
+        public ActionMenu(String name, String displayName, int mnemonic,
+            int weight) {
+            this.name = name;
+            this.displayName = displayName;
+            this.mnemonic = mnemonic;
+            this.weight = weight;
+        }
+
+        public int compareTo(Object o) {
+            int i = new Integer(weight).compareTo(new Integer(
+                        ((ActionMenu) o).weight));
+
+            return (i == 0)
+            ? displayName.compareTo(((ActionMenu) o).displayName) : i;
+        }
+    }
+
+    class ToolBarActionComparator implements Comparator {
+        public int compare(Object o1, Object o2) {
+            int i = ((Integer) ((StandardAction) o1).getValue(StandardAction.TOOLBAR_GROUP)).compareTo((Integer) ((StandardAction) o2).getValue(
+                        StandardAction.TOOLBAR_GROUP));
+
+            return (i == 0)
+            ? ((Integer) ((StandardAction) o1).getValue(StandardAction.TOOLBAR_WEIGHT)).compareTo((Integer) ((StandardAction) o2).getValue(
+                    StandardAction.TOOLBAR_WEIGHT)) : i;
+        }
+    }
+
+    class ContextActionComparator implements Comparator {
+        public int compare(Object o1, Object o2) {
+            int i = ((Integer) ((StandardAction) o1).getValue(StandardAction.CONTEXT_MENU_GROUP)).compareTo((Integer) ((StandardAction) o2).getValue(
+                        StandardAction.CONTEXT_MENU_GROUP));
+
+            return (i == 0)
+            ? ((Integer) ((StandardAction) o1).getValue(StandardAction.CONTEXT_MENU_WEIGHT)).compareTo((Integer) ((StandardAction) o2).getValue(
+                    StandardAction.CONTEXT_MENU_WEIGHT)) : i;
+        }
+    }
+
+    class MenuItemActionComparator implements Comparator {
+        public int compare(Object o1, Object o2) {
+            int i = ((Integer) ((StandardAction) o1).getValue(StandardAction.MENU_ITEM_GROUP)).compareTo((Integer) ((StandardAction) o2).getValue(
+                        StandardAction.MENU_ITEM_GROUP));
+
+            return (i == 0)
+            ? ((Integer) ((StandardAction) o1).getValue(StandardAction.MENU_ITEM_WEIGHT)).compareTo((Integer) ((StandardAction) o2).getValue(
+                    StandardAction.MENU_ITEM_WEIGHT)) : i;
+        }
+    }
+
+    class ConnectionFileFilter extends javax.swing.filechooser.FileFilter {
+        public boolean accept(File f) {
+            return f.isDirectory() ||
+            f.getName().toLowerCase().endsWith(".xml");
+        }
+
+        public String getDescription() {
+            return "Connection files (*.xml)";
+        }
+    }
+}
diff --git a/src/com/sshtools/common/ui/SshToolsApplicationSessionPanel.java b/src/com/sshtools/common/ui/SshToolsApplicationSessionPanel.java
new file mode 100644
index 0000000000000000000000000000000000000000..2b670bb1b8ec45cdbffcb0e23b12fb8714e4cf7b
--- /dev/null
+++ b/src/com/sshtools/common/ui/SshToolsApplicationSessionPanel.java
@@ -0,0 +1,268 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import com.sshtools.common.configuration.SshToolsConnectionProfile;
+
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+import com.sshtools.j2ssh.connection.ChannelEventListener;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.awt.LayoutManager;
+
+import java.io.File;
+import java.io.IOException;
+
+import java.util.Comparator;
+
+import javax.swing.SwingConstants;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public abstract class SshToolsApplicationSessionPanel
+    extends SshToolsApplicationPanel {
+    /**  */
+    public final static String PREF_CONNECTION_FILE_DIRECTORY = "sshapps.connectionFile.directory";
+
+    /**  */
+    protected Log log = LogFactory.getLog(SshToolsApplicationSessionPanel.class);
+
+    /**  */
+    protected SshToolsConnectionProfile currentConnectionProfile;
+
+    /**  */
+    protected SessionManager manager;
+
+    /**
+* Creates a new SshToolsApplicationClientPanel object.
+*/
+    public SshToolsApplicationSessionPanel() {
+        super();
+    }
+
+    /**
+* Creates a new SshToolsApplicationClientPanel object.
+*
+* @param mgr
+*/
+    public SshToolsApplicationSessionPanel(LayoutManager mgr) {
+        super(mgr);
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public abstract SshToolsConnectionTab[] getAdditionalConnectionTabs();
+
+    public abstract void addEventListener(ChannelEventListener eventListener);
+
+    public abstract boolean requiresConfiguration();
+
+    public abstract String getId();
+
+    /**
+*
+*
+* @param manager
+* @param profile
+*
+* @throws IOException
+*/
+    public final boolean openSession(SessionManager manager,
+        SshToolsConnectionProfile profile) throws IOException {
+        this.manager = manager;
+
+        // Set the current connection properties
+        setCurrentConnectionProfile(profile);
+
+        if (requiresConfiguration() &&
+                !profile.getApplicationPropertyBoolean(getId() + ".configured",
+                    false)) {
+            if (!editSettings(profile)) {
+                return false;
+            }
+        }
+
+        return onOpenSession();
+    }
+
+    /**
+*
+*
+* @throws IOException
+*/
+    public abstract boolean onOpenSession() throws IOException;
+
+    /**
+*
+*
+* @return
+*/
+    public boolean isConnected() {
+        return (manager != null) && manager.isConnected();
+    }
+
+    /**
+*
+*
+* @param file
+*/
+    public void setContainerTitle(File file) {
+        String verString = "";
+
+        if (application != null) {
+            verString = ConfigurationLoader.getVersionString(application.getApplicationName(),
+                    application.getApplicationVersion());
+        }
+
+        if (container != null) {
+            container.setContainerTitle((file == null) ? verString
+                                                       : (verString + " [" +
+                file.getName() + "]"));
+        }
+    }
+
+    /**
+*
+*
+* @param profile
+*/
+    public void setCurrentConnectionProfile(SshToolsConnectionProfile profile) {
+        currentConnectionProfile = profile;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public SshToolsConnectionProfile getCurrentConnectionProfile() {
+        return currentConnectionProfile;
+    }
+
+    /**
+*
+*
+* @param profile
+*
+* @return
+*/
+    public boolean editSettings(SshToolsConnectionProfile profile) {
+        final SshToolsConnectionPanel panel = new SshToolsConnectionPanel(false);
+        SshToolsConnectionTab[] tabs = getAdditionalConnectionTabs();
+
+        for (int i = 0; (tabs != null) && (i < tabs.length); i++) {
+            tabs[i].setConnectionProfile(profile);
+            panel.addTab(tabs[i]);
+        }
+
+        panel.setConnectionProfile(profile);
+
+        final Option ok = new Option("Ok",
+                "Apply the settings and close this dialog", 'o');
+        final Option cancel = new Option("Cancel",
+                "Close this dialog without applying the settings", 'c');
+        OptionCallback callback = new OptionCallback() {
+                public boolean canClose(OptionsDialog dialog, Option option) {
+                    if (option == ok) {
+                        return panel.validateTabs();
+                    }
+
+                    return true;
+                }
+            };
+
+        OptionsDialog od = OptionsDialog.createOptionDialog(SshToolsApplicationSessionPanel.this,
+                new Option[] { ok, cancel }, panel, "Connection Settings", ok,
+                callback, null);
+        od.pack();
+        UIUtil.positionComponent(SwingConstants.CENTER, od);
+        od.setVisible(true);
+
+        if (od.getSelectedOption() == ok) {
+            // Apply the changes to the profile
+            panel.applyTabs();
+
+            // Ask the session manager to apply them to persistence
+            manager.applyProfileChanges(profile);
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /*public static class ActionMenu
+implements Comparable {
+int weight;
+int mnemonic;
+String name;
+String displayName;
+public ActionMenu(String name, String displayName, int mnemonic,
+                int weight) {
+this.name = name;
+this.displayName = displayName;
+this.mnemonic = mnemonic;
+this.weight = weight;
+}
+public int compareTo(Object o) {
+int i = new Integer(weight).compareTo(new Integer(
+    ( (ActionMenu) o).weight));
+return (i == 0)
+    ? displayName.compareTo( ( (ActionMenu) o).displayName) : i;
+}
+}*/
+    class ToolBarActionComparator implements Comparator {
+        public int compare(Object o1, Object o2) {
+            int i = ((Integer) ((StandardAction) o1).getValue(StandardAction.TOOLBAR_GROUP)).compareTo((Integer) ((StandardAction) o2).getValue(
+                        StandardAction.TOOLBAR_GROUP));
+
+            return (i == 0)
+            ? ((Integer) ((StandardAction) o1).getValue(StandardAction.TOOLBAR_WEIGHT)).compareTo((Integer) ((StandardAction) o2).getValue(
+                    StandardAction.TOOLBAR_WEIGHT)) : i;
+        }
+    }
+
+    class MenuItemActionComparator implements Comparator {
+        public int compare(Object o1, Object o2) {
+            int i = ((Integer) ((StandardAction) o1).getValue(StandardAction.MENU_ITEM_GROUP)).compareTo((Integer) ((StandardAction) o2).getValue(
+                        StandardAction.MENU_ITEM_GROUP));
+
+            return (i == 0)
+            ? ((Integer) ((StandardAction) o1).getValue(StandardAction.MENU_ITEM_WEIGHT)).compareTo((Integer) ((StandardAction) o2).getValue(
+                    StandardAction.MENU_ITEM_WEIGHT)) : i;
+        }
+    }
+}
diff --git a/src/com/sshtools/common/ui/SshToolsConnectionHostTab.java b/src/com/sshtools/common/ui/SshToolsConnectionHostTab.java
new file mode 100644
index 0000000000000000000000000000000000000000..8ebd2a2eb0e2af83f326da832cce986f9d46c03a
--- /dev/null
+++ b/src/com/sshtools/common/ui/SshToolsConnectionHostTab.java
@@ -0,0 +1,419 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import com.sshtools.common.configuration.SshToolsConnectionProfile;
+
+import com.sshtools.j2ssh.authentication.SshAuthenticationClient;
+import com.sshtools.j2ssh.authentication.SshAuthenticationClientFactory;
+import com.sshtools.j2ssh.transport.AlgorithmNotSupportedException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.awt.Component;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.swing.BorderFactory;
+import javax.swing.Icon;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.ListModel;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.19 $
+ */
+public class SshToolsConnectionHostTab extends JPanel
+    implements SshToolsConnectionTab {
+    //
+
+    /**  */
+    public final static int DEFAULT_PORT = 22;
+
+    //
+
+    /**  */
+    public final static String CONNECT_ICON = "largeserveridentity.png";
+
+    /**  */
+    public final static String AUTH_ICON = "largelock.png";
+
+    /**  */
+    public final static String SHOW_AVAILABLE = "<Show available methods>";
+
+    //
+
+    /**  */
+    protected XTextField jTextHostname = new XTextField();
+
+    /**  */
+    protected NumericTextField jTextPort = new NumericTextField(new Integer(0),
+            new Integer(65535), new Integer(DEFAULT_PORT));
+
+    /**  */
+    protected XTextField jTextUsername = new XTextField();
+
+    /**  */
+    protected JList jListAuths = new JList();
+
+    /**  */
+    protected java.util.List methods = new ArrayList();
+
+    /**  */
+    protected SshToolsConnectionProfile profile;
+
+    /**  */
+    protected JCheckBox allowAgentForwarding;
+
+    /**  */
+    protected Log log = LogFactory.getLog(SshToolsConnectionHostTab.class);
+
+    /**
+* Creates a new SshToolsConnectionHostTab object.
+*/
+    public SshToolsConnectionHostTab() {
+        super();
+
+        //  Create the main connection details panel
+        JPanel mainConnectionDetailsPanel = new JPanel(new GridBagLayout());
+        GridBagConstraints gbc = new GridBagConstraints();
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        gbc.anchor = GridBagConstraints.NORTHWEST;
+        gbc.insets = new Insets(0, 2, 2, 2);
+        gbc.weightx = 1.0;
+
+        //  Host name
+        UIUtil.jGridBagAdd(mainConnectionDetailsPanel, new JLabel("Hostname"),
+            gbc, GridBagConstraints.REMAINDER);
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        UIUtil.jGridBagAdd(mainConnectionDetailsPanel, jTextHostname, gbc,
+            GridBagConstraints.REMAINDER);
+        gbc.fill = GridBagConstraints.NONE;
+
+        //  Port
+        UIUtil.jGridBagAdd(mainConnectionDetailsPanel, new JLabel("Port"), gbc,
+            GridBagConstraints.REMAINDER);
+        UIUtil.jGridBagAdd(mainConnectionDetailsPanel, jTextPort, gbc,
+            GridBagConstraints.REMAINDER);
+
+        //  Username
+        UIUtil.jGridBagAdd(mainConnectionDetailsPanel, new JLabel("Username"),
+            gbc, GridBagConstraints.REMAINDER);
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        gbc.weighty = 1.0;
+        UIUtil.jGridBagAdd(mainConnectionDetailsPanel, jTextUsername, gbc,
+            GridBagConstraints.REMAINDER);
+        gbc.fill = GridBagConstraints.NONE;
+
+        //
+        IconWrapperPanel iconMainConnectionDetailsPanel = new IconWrapperPanel(new ResourceIcon(
+                    SshToolsConnectionHostTab.class, CONNECT_ICON),
+                mainConnectionDetailsPanel);
+
+        //  Authentication methods panel
+        JPanel authMethodsPanel = new JPanel(new GridBagLayout());
+        authMethodsPanel.setBorder(BorderFactory.createEmptyBorder(4, 0, 0, 0));
+        gbc = new GridBagConstraints();
+        gbc.fill = GridBagConstraints.NONE;
+        gbc.anchor = GridBagConstraints.NORTHWEST;
+        gbc.insets = new Insets(2, 2, 2, 2);
+        gbc.weightx = 1.0;
+
+        //  Authentication methods
+        UIUtil.jGridBagAdd(authMethodsPanel,
+            new JLabel("Authentication Methods"), gbc,
+            GridBagConstraints.REMAINDER);
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        gbc.weighty = 1.0;
+        jListAuths.setVisibleRowCount(5);
+        UIUtil.jGridBagAdd(authMethodsPanel, new JScrollPane(jListAuths), gbc,
+            GridBagConstraints.REMAINDER);
+        allowAgentForwarding = new JCheckBox("Allow agent forwarding");
+        UIUtil.jGridBagAdd(authMethodsPanel, allowAgentForwarding, gbc,
+            GridBagConstraints.REMAINDER);
+
+        //
+        IconWrapperPanel iconAuthMethodsPanel = new IconWrapperPanel(new ResourceIcon(
+                    SshToolsConnectionHostTab.class, AUTH_ICON),
+                authMethodsPanel);
+
+        //  This panel
+        setLayout(new GridBagLayout());
+        setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+        gbc = new GridBagConstraints();
+        gbc.fill = GridBagConstraints.BOTH;
+        gbc.anchor = GridBagConstraints.WEST;
+        gbc.insets = new Insets(2, 2, 2, 2);
+        gbc.weightx = 1.0;
+        UIUtil.jGridBagAdd(this, iconMainConnectionDetailsPanel, gbc,
+            GridBagConstraints.REMAINDER);
+        gbc.weighty = 1.0;
+        UIUtil.jGridBagAdd(this, iconAuthMethodsPanel, gbc,
+            GridBagConstraints.REMAINDER);
+
+        //  Set up the values in the various components
+        addAuthenticationMethods();
+    }
+
+    /**
+*
+*
+* @param profile
+*/
+    public void setConnectionProfile(SshToolsConnectionProfile profile) {
+        this.profile = profile;
+        jTextHostname.setText((profile == null) ? "" : profile.getHost());
+        jTextUsername.setText((profile == null) ? "" : profile.getUsername());
+        jTextPort.setValue(new Integer((profile == null) ? 22 : profile.getPort()));
+
+        if (System.getProperty("sshtools.agent") == null) {
+            allowAgentForwarding.setSelected(false);
+            allowAgentForwarding.setEnabled(false);
+        } else {
+            allowAgentForwarding.setEnabled(true);
+            allowAgentForwarding.setSelected((profile != null) &&
+                profile.getAllowAgentForwarding());
+        }
+
+        // Match the authentication methods
+        Map auths = (profile == null) ? new HashMap()
+                                      : profile.getAuthenticationMethods();
+        Iterator it = auths.entrySet().iterator();
+        Map.Entry entry;
+        String authmethod;
+        int[] selectionarray = new int[auths.values().size()];
+        int count = 0;
+        ListModel model = jListAuths.getModel();
+
+        while (it.hasNext()) {
+            entry = (Map.Entry) it.next();
+            authmethod = (String) entry.getKey();
+
+            for (int i = 0; i < model.getSize(); i++) {
+                if (model.getElementAt(i).equals(authmethod)) {
+                    selectionarray[count++] = i;
+
+                    break;
+                }
+            }
+
+            /*if (jListAuths.getNextMatch(authmethod, 0, Position.Bias.Forward) > -1) {
+selectionarray[count] = jListAuths.getNextMatch(authmethod, 0,
+Position.Bias.Forward);
+count++;
+}*/
+            jListAuths.clearSelection();
+            jListAuths.setSelectedIndices(selectionarray);
+        }
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public SshToolsConnectionProfile getConnectionProfile() {
+        return profile;
+    }
+
+    private void addAuthenticationMethods() {
+        java.util.List methods = new ArrayList();
+        methods.add(SHOW_AVAILABLE);
+        methods.addAll(SshAuthenticationClientFactory.getSupportedMethods());
+        jListAuths.setListData(methods.toArray());
+        jListAuths.setSelectedIndex(0);
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getTabContext() {
+        return "Connection";
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public Icon getTabIcon() {
+        return null;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getTabTitle() {
+        return "Host";
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getTabToolTipText() {
+        return "The main host connection details.";
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public int getTabMnemonic() {
+        return 'h';
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public Component getTabComponent() {
+        return this;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public boolean validateTab() {
+        // Validate that we have enough information
+        if (jTextHostname.getText().equals("") ||
+                jTextPort.getText().equals("") ||
+                jTextUsername.getText().equals("")) {
+            JOptionPane.showMessageDialog(this, "Please enter all details!",
+                "Connect", JOptionPane.OK_OPTION);
+
+            return false;
+        }
+
+        // Setup the authentications selected
+        java.util.List chosen = getChosenAuth();
+
+        if (chosen != null) {
+            Iterator it = chosen.iterator();
+
+            while (it.hasNext()) {
+                String method = (String) it.next();
+
+                try {
+                    SshAuthenticationClient auth = SshAuthenticationClientFactory.newInstance(method);
+                } catch (AlgorithmNotSupportedException anse) {
+                    JOptionPane.showMessageDialog(this,
+                        method + " is not supported!");
+
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    private java.util.List getChosenAuth() {
+        // Determine whether any authenticaiton methods we selected
+        Object[] auths = jListAuths.getSelectedValues();
+        String a;
+        java.util.List l = new java.util.ArrayList();
+
+        if (auths != null) {
+            for (int i = 0; i < auths.length; i++) {
+                a = (String) auths[i];
+
+                if (a.equals(SHOW_AVAILABLE)) {
+                    return null;
+                } else {
+                    l.add(a);
+                }
+            }
+        } else {
+            return null;
+        }
+
+        return l;
+    }
+
+    /**
+*
+*/
+    public void applyTab() {
+        profile.setHost(jTextHostname.getText());
+        profile.setPort(Integer.valueOf(jTextPort.getText()).intValue());
+        profile.setUsername(jTextUsername.getText());
+        profile.setAllowAgentForwarding(allowAgentForwarding.getModel()
+                                                            .isSelected());
+
+        java.util.List chosen = getChosenAuth();
+
+        // Remove the authentication methods and re-apply them
+        profile.removeAuthenticationMethods();
+
+        if (chosen != null) {
+            Iterator it = chosen.iterator();
+
+            while (it.hasNext()) {
+                String method = (String) it.next();
+
+                try {
+                    SshAuthenticationClient auth = SshAuthenticationClientFactory.newInstance(method);
+                    auth.setUsername(jTextUsername.getText());
+                    profile.addAuthenticationMethod(auth);
+                } catch (AlgorithmNotSupportedException anse) {
+                    log.error("This should have been caught by validateTab()?",
+                        anse);
+                }
+            }
+        }
+    }
+
+    /**
+*
+*/
+    public void tabSelected() {
+    }
+}
diff --git a/src/com/sshtools/common/ui/SshToolsConnectionPanel.java b/src/com/sshtools/common/ui/SshToolsConnectionPanel.java
new file mode 100644
index 0000000000000000000000000000000000000000..4277030d4bf335cf2767de3581f038dbdf7fe9cd
--- /dev/null
+++ b/src/com/sshtools/common/ui/SshToolsConnectionPanel.java
@@ -0,0 +1,264 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import com.sshtools.common.configuration.SshToolsConnectionProfile;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.SwingConstants;
+import javax.swing.SwingUtilities;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class SshToolsConnectionPanel extends JPanel {
+    //  Strings
+    final static String DEFAULT = "<Default>";
+    final static int DEFAULT_PORT = 22;
+
+    //
+
+    /**  */
+    protected Log log = LogFactory.getLog(SshToolsConnectionPanel.class);
+
+    //
+    private Tabber tabber;
+    private SshToolsConnectionProfile profile;
+
+    /**
+* Creates a new SshToolsConnectionPanel object.
+*
+* @param showConnectionTabs
+*/
+    public SshToolsConnectionPanel(boolean showConnectionTabs) {
+        super();
+        tabber = new Tabber();
+
+        if (showConnectionTabs) {
+            //  Add the common tabs
+            addTab(new SshToolsConnectionHostTab());
+            addTab(new SshToolsConnectionProtocolTab());
+            addTab(new SshToolsConnectionProxyTab());
+        }
+
+        //  Build this panel
+        setLayout(new GridLayout(1, 1));
+        add(tabber);
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public boolean validateTabs() {
+        return tabber.validateTabs();
+    }
+
+    /**
+*
+*/
+    public void applyTabs() {
+        tabber.applyTabs();
+    }
+
+    /**
+*
+*
+* @param tab
+*/
+    public void addTab(SshToolsConnectionTab tab) {
+        tabber.addTab(tab);
+    }
+
+    /**
+*
+*
+* @param profile
+*/
+    public void setConnectionProfile(SshToolsConnectionProfile profile) {
+        this.profile = profile;
+
+        for (int i = 0; i < tabber.getTabCount(); i++) {
+            ((SshToolsConnectionTab) tabber.getTabAt(i)).setConnectionProfile(profile);
+        }
+    }
+
+    /**
+*
+*
+* @param parent
+* @param optionalTabs
+*
+* @return
+*/
+    public static SshToolsConnectionProfile showConnectionDialog(
+        Component parent, SshToolsConnectionTab[] optionalTabs) {
+        return showConnectionDialog(parent, null, optionalTabs);
+    }
+
+    /**
+*
+*
+* @param parent
+* @param profile
+* @param optionalTabs
+*
+* @return
+*/
+    public static SshToolsConnectionProfile showConnectionDialog(
+        Component parent, SshToolsConnectionProfile profile,
+        SshToolsConnectionTab[] optionalTabs) {
+        //  If no properties are provided, then use the default
+        if (profile == null) {
+            profile = new SshToolsConnectionProfile();
+            profile.setHost(PreferencesStore.get(
+                    SshToolsApplication.PREF_CONNECTION_LAST_HOST, ""));
+            profile.setPort(PreferencesStore.getInt(
+                    SshToolsApplication.PREF_CONNECTION_LAST_PORT, DEFAULT_PORT));
+            profile.setUsername(PreferencesStore.get(
+                    SshToolsApplication.PREF_CONNECTION_LAST_USER, ""));
+        }
+
+        final SshToolsConnectionPanel conx = new SshToolsConnectionPanel(true);
+
+        if (optionalTabs != null) {
+            for (int i = 0; i < optionalTabs.length; i++) {
+                conx.addTab(optionalTabs[i]);
+            }
+        }
+
+        conx.setConnectionProfile(profile);
+
+        JDialog d = null;
+        Window w = (Window) SwingUtilities.getAncestorOfClass(Window.class,
+                parent);
+
+        if (w instanceof JDialog) {
+            d = new JDialog((JDialog) w, "Connection Profile", true);
+        } else if (w instanceof JFrame) {
+            d = new JDialog((JFrame) w, "Connection Profile", true);
+        } else {
+            d = new JDialog((JFrame) null, "Connection Profile", true);
+        }
+
+        final JDialog dialog = d;
+
+        class UserAction {
+            boolean connect;
+        }
+
+        final UserAction userAction = new UserAction();
+
+        //  Create the bottom button panel
+        final JButton cancel = new JButton("Cancel");
+        cancel.setMnemonic('c');
+        cancel.addActionListener(new ActionListener() {
+                public void actionPerformed(ActionEvent evt) {
+                    dialog.setVisible(false);
+                }
+            });
+
+        final JButton connect = new JButton("Connect");
+        connect.setMnemonic('t');
+        connect.addActionListener(new ActionListener() {
+                public void actionPerformed(ActionEvent evt) {
+                    if (conx.validateTabs()) {
+                        userAction.connect = true;
+                        dialog.setVisible(false);
+                    }
+                }
+            });
+        dialog.getRootPane().setDefaultButton(connect);
+
+        JPanel buttonPanel = new JPanel(new GridBagLayout());
+        GridBagConstraints gbc = new GridBagConstraints();
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        gbc.anchor = GridBagConstraints.CENTER;
+        gbc.insets = new Insets(6, 6, 0, 0);
+        gbc.weighty = 1.0;
+        UIUtil.jGridBagAdd(buttonPanel, connect, gbc,
+            GridBagConstraints.RELATIVE);
+        UIUtil.jGridBagAdd(buttonPanel, cancel, gbc,
+            GridBagConstraints.REMAINDER);
+
+        JPanel southPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 0, 0));
+        southPanel.add(buttonPanel);
+
+        //
+        JPanel mainPanel = new JPanel(new BorderLayout());
+        mainPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+        mainPanel.add(conx, BorderLayout.CENTER);
+        mainPanel.add(southPanel, BorderLayout.SOUTH);
+
+        // Show the dialog
+        dialog.getContentPane().setLayout(new GridLayout(1, 1));
+        dialog.getContentPane().add(mainPanel);
+        dialog.pack();
+        dialog.setResizable(false);
+        UIUtil.positionComponent(SwingConstants.CENTER, dialog);
+        dialog.setVisible(true);
+
+        if (!userAction.connect) {
+            return null;
+        }
+
+        conx.applyTabs();
+
+        // Make sure we didn't cancel
+        PreferencesStore.put(SshToolsApplication.PREF_CONNECTION_LAST_HOST,
+            profile.getHost());
+        PreferencesStore.put(SshToolsApplication.PREF_CONNECTION_LAST_USER,
+            profile.getUsername());
+        PreferencesStore.putInt(SshToolsApplication.PREF_CONNECTION_LAST_PORT,
+            profile.getPort());
+
+        // Return the connection properties
+        return profile;
+    }
+}
diff --git a/src/com/sshtools/common/ui/SshToolsConnectionProtocolTab.java b/src/com/sshtools/common/ui/SshToolsConnectionProtocolTab.java
new file mode 100644
index 0000000000000000000000000000000000000000..efa86901b3853ea0a8838d82fc88da1ee51c0b1f
--- /dev/null
+++ b/src/com/sshtools/common/ui/SshToolsConnectionProtocolTab.java
@@ -0,0 +1,354 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import com.sshtools.common.configuration.SshToolsConnectionProfile;
+
+import com.sshtools.j2ssh.transport.cipher.SshCipherFactory;
+import com.sshtools.j2ssh.transport.compression.SshCompressionFactory;
+import com.sshtools.j2ssh.transport.hmac.SshHmacFactory;
+import com.sshtools.j2ssh.transport.kex.SshKeyExchangeFactory;
+import com.sshtools.j2ssh.transport.publickey.SshKeyPairFactory;
+
+import java.awt.Component;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+
+import java.util.Iterator;
+
+import javax.swing.BorderFactory;
+import javax.swing.Icon;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSeparator;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class SshToolsConnectionProtocolTab extends JPanel
+    implements SshToolsConnectionTab {
+    final static String KEYS_ICON = "/com/sshtools/common/ui/largekeys.png";
+    final static String PROTOCOL_ICON = "/com/sshtools/common/ui/largeprotocol.png";
+    final static String DEFAULT = "<Default>";
+
+    //
+
+    /**  */
+    protected JComboBox jComboCipherCS = new JComboBox();
+
+    /**  */
+    protected JComboBox jComboCipherSC = new JComboBox();
+
+    /**  */
+    protected JComboBox jComboMacCS = new JComboBox();
+
+    /**  */
+    protected JComboBox jComboMacSC = new JComboBox();
+
+    /**  */
+    protected JComboBox jComboCompCS = new JComboBox();
+
+    /**  */
+    protected JComboBox jComboCompSC = new JComboBox();
+
+    /**  */
+    protected JComboBox jComboKex = new JComboBox();
+
+    /**  */
+    protected JComboBox jComboPK = new JComboBox();
+
+    /**  */
+    protected SshToolsConnectionProfile profile;
+
+    /**
+* Creates a new SshToolsConnectionProtocolTab object.
+*/
+    public SshToolsConnectionProtocolTab() {
+        super();
+
+        //  Keys
+        JPanel keysPanel = new JPanel(new GridBagLayout());
+        GridBagConstraints gbc = new GridBagConstraints();
+        gbc.fill = GridBagConstraints.NONE;
+        gbc.anchor = GridBagConstraints.WEST;
+        gbc.insets = new Insets(2, 2, 2, 2);
+        gbc.weightx = 1.0;
+
+        //  Public key
+        UIUtil.jGridBagAdd(keysPanel, new JLabel("Public key"), gbc,
+            GridBagConstraints.REMAINDER);
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        UIUtil.jGridBagAdd(keysPanel, jComboPK, gbc,
+            GridBagConstraints.REMAINDER);
+        gbc.fill = GridBagConstraints.NONE;
+
+        //  Public key
+        UIUtil.jGridBagAdd(keysPanel, new JLabel("Key exchange"), gbc,
+            GridBagConstraints.REMAINDER);
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        UIUtil.jGridBagAdd(keysPanel, jComboKex, gbc,
+            GridBagConstraints.REMAINDER);
+        gbc.fill = GridBagConstraints.NONE;
+
+        //
+        IconWrapperPanel iconKeysPanel = new IconWrapperPanel(new ResourceIcon(
+                    KEYS_ICON), keysPanel);
+
+        //  Preferences
+        JPanel prefPanel = new JPanel(new GridBagLayout());
+        prefPanel.setBorder(BorderFactory.createEmptyBorder(4, 0, 0, 0));
+        gbc = new GridBagConstraints();
+        gbc.fill = GridBagConstraints.NONE;
+        gbc.anchor = GridBagConstraints.WEST;
+        gbc.insets = new Insets(2, 2, 2, 2);
+        gbc.weightx = 1.0;
+
+        //  Public key
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        UIUtil.jGridBagAdd(prefPanel, new JLabel("Client - > Server"), gbc,
+            GridBagConstraints.RELATIVE);
+        UIUtil.jGridBagAdd(prefPanel, new JLabel("Server - > Client"), gbc,
+            GridBagConstraints.REMAINDER);
+
+        //  Separator
+        gbc.weightx = 2.0;
+        UIUtil.jGridBagAdd(prefPanel, new JSeparator(JSeparator.HORIZONTAL),
+            gbc, GridBagConstraints.REMAINDER);
+
+        //  Cipher
+        UIUtil.jGridBagAdd(prefPanel, new JLabel("Cipher"), gbc,
+            GridBagConstraints.RELATIVE);
+        UIUtil.jGridBagAdd(prefPanel, new JLabel("Cipher"), gbc,
+            GridBagConstraints.REMAINDER);
+        UIUtil.jGridBagAdd(prefPanel, jComboCipherCS, gbc,
+            GridBagConstraints.RELATIVE);
+        UIUtil.jGridBagAdd(prefPanel, jComboCipherSC, gbc,
+            GridBagConstraints.REMAINDER);
+
+        //  Mac
+        UIUtil.jGridBagAdd(prefPanel, new JLabel("Mac"), gbc,
+            GridBagConstraints.RELATIVE);
+        UIUtil.jGridBagAdd(prefPanel, new JLabel("Mac"), gbc,
+            GridBagConstraints.REMAINDER);
+        UIUtil.jGridBagAdd(prefPanel, jComboMacCS, gbc,
+            GridBagConstraints.RELATIVE);
+        UIUtil.jGridBagAdd(prefPanel, jComboMacSC, gbc,
+            GridBagConstraints.REMAINDER);
+
+        //  Compression
+        UIUtil.jGridBagAdd(prefPanel, new JLabel("Compression"), gbc,
+            GridBagConstraints.RELATIVE);
+        UIUtil.jGridBagAdd(prefPanel, new JLabel("Compression"), gbc,
+            GridBagConstraints.REMAINDER);
+        UIUtil.jGridBagAdd(prefPanel, jComboCompCS, gbc,
+            GridBagConstraints.RELATIVE);
+        UIUtil.jGridBagAdd(prefPanel, jComboCompSC, gbc,
+            GridBagConstraints.REMAINDER);
+
+        //
+        IconWrapperPanel iconPrefPanel = new IconWrapperPanel(new ResourceIcon(
+                    PROTOCOL_ICON), prefPanel);
+
+        //  This tab
+        setLayout(new GridBagLayout());
+        setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+        gbc = new GridBagConstraints();
+        gbc.fill = GridBagConstraints.NONE;
+        gbc.anchor = GridBagConstraints.WEST;
+        gbc.insets = new Insets(2, 2, 2, 2);
+        gbc.weightx = 1.0;
+        UIUtil.jGridBagAdd(this, iconKeysPanel, gbc,
+            GridBagConstraints.REMAINDER);
+        gbc.weighty = 1.0;
+        UIUtil.jGridBagAdd(this, iconPrefPanel, gbc,
+            GridBagConstraints.REMAINDER);
+
+        //
+        loadList(SshCipherFactory.getSupportedCiphers(), jComboCipherCS, true);
+        loadList(SshCipherFactory.getSupportedCiphers(), jComboCipherSC, true);
+        loadList(SshHmacFactory.getSupportedMacs(), jComboMacCS, true);
+        loadList(SshHmacFactory.getSupportedMacs(), jComboMacSC, true);
+        loadList(SshCompressionFactory.getSupportedCompression(), jComboCompCS,
+            true);
+        loadList(SshCompressionFactory.getSupportedCompression(), jComboCompSC,
+            true);
+        loadList(SshKeyExchangeFactory.getSupportedKeyExchanges(), jComboKex,
+            true);
+        loadList(SshKeyPairFactory.getSupportedKeys(), jComboPK, true);
+    }
+
+    /**
+*
+*
+* @param profile
+*/
+    public void setConnectionProfile(SshToolsConnectionProfile profile) {
+        this.profile = profile;
+        jComboCipherCS.setSelectedItem(profile.getPrefCSEncryption());
+        jComboCipherSC.setSelectedItem(profile.getPrefSCEncryption());
+        jComboMacCS.setSelectedItem(profile.getPrefCSMac());
+        jComboMacSC.setSelectedItem(profile.getPrefSCMac());
+        jComboCompCS.setSelectedItem(profile.getPrefCSComp());
+        jComboCompSC.setSelectedItem(profile.getPrefSCComp());
+        jComboKex.setSelectedItem(profile.getPrefKex());
+        jComboPK.setSelectedItem(profile.getPrefPublicKey());
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public SshToolsConnectionProfile getConnectionProfile() {
+        return profile;
+    }
+
+    private void loadList(java.util.List list, JComboBox combo,
+        boolean addDefault) {
+        Iterator it = list.iterator();
+
+        if (addDefault) {
+            combo.addItem(DEFAULT);
+        }
+
+        while (it.hasNext()) {
+            combo.addItem(it.next());
+        }
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getTabContext() {
+        return "Connection";
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public Icon getTabIcon() {
+        return null;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getTabTitle() {
+        return "Protocol";
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getTabToolTipText() {
+        return "Protocol related properties.";
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public int getTabMnemonic() {
+        return 'p';
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public Component getTabComponent() {
+        return this;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public boolean validateTab() {
+        return true;
+    }
+
+    /**
+*
+*/
+    public void applyTab() {
+        // Get the algorithm preferences
+        if (!jComboCipherCS.getSelectedItem().equals(DEFAULT)) {
+            profile.setPrefCSEncryption((String) jComboCipherCS.getSelectedItem());
+        }
+
+        if (!jComboCipherSC.getSelectedItem().equals(DEFAULT)) {
+            profile.setPrefSCEncryption((String) jComboCipherSC.getSelectedItem());
+        }
+
+        if (!jComboMacCS.getSelectedItem().equals(DEFAULT)) {
+            profile.setPrefCSMac((String) jComboMacCS.getSelectedItem());
+        }
+
+        if (!jComboMacSC.getSelectedItem().equals(DEFAULT)) {
+            profile.setPrefSCMac((String) jComboMacSC.getSelectedItem());
+        }
+
+        if (!jComboCompCS.getSelectedItem().equals(DEFAULT)) {
+            profile.setPrefCSComp((String) jComboCompCS.getSelectedItem());
+        }
+
+        if (!jComboCompSC.getSelectedItem().equals(DEFAULT)) {
+            profile.setPrefSCComp((String) jComboCompSC.getSelectedItem());
+        }
+
+        if (!jComboKex.getSelectedItem().equals(DEFAULT)) {
+            profile.setPrefKex((String) jComboKex.getSelectedItem());
+        }
+
+        if (!jComboPK.getSelectedItem().equals(DEFAULT)) {
+            profile.setPrefPublicKey((String) jComboPK.getSelectedItem());
+        }
+    }
+
+    /**
+*
+*/
+    public void tabSelected() {
+    }
+}
diff --git a/src/com/sshtools/common/ui/SshToolsConnectionProxyTab.java b/src/com/sshtools/common/ui/SshToolsConnectionProxyTab.java
new file mode 100644
index 0000000000000000000000000000000000000000..96ad39504e52935b33050845f4606c55754136dc
--- /dev/null
+++ b/src/com/sshtools/common/ui/SshToolsConnectionProxyTab.java
@@ -0,0 +1,333 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import com.sshtools.common.configuration.SshToolsConnectionProfile;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.Icon;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JPasswordField;
+import javax.swing.JRadioButton;
+import javax.swing.JTextField;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class SshToolsConnectionProxyTab extends JPanel
+    implements SshToolsConnectionTab {
+    /**  */
+    public final static String PROXY_ICON = "/com/sshtools/common/ui/proxy.png";
+
+    /**  */
+    protected JRadioButton noProxy = new JRadioButton("No proxy");
+
+    /**  */
+    protected JRadioButton httpProxy = new JRadioButton("HTTP");
+
+    /**  */
+    protected JRadioButton socks4Proxy = new JRadioButton("SOCKS 4");
+
+    /**  */
+    protected JRadioButton socks5Proxy = new JRadioButton("SOCKS 5");
+
+    /**  */
+    protected ButtonGroup group = new ButtonGroup();
+
+    /**  */
+    protected JPanel proxyframe = new JPanel(new GridBagLayout());
+
+    /**  */
+    protected JTextField username = new JTextField();
+
+    /**  */
+    protected JPasswordField password = new JPasswordField();
+
+    /**  */
+    protected JTextField proxy = new JTextField();
+
+    /**  */
+    protected NumericTextField port = new NumericTextField(new Integer(1),
+            new Integer(65535));
+
+    /**  */
+    protected SshToolsConnectionProfile profile;
+
+    /**  */
+    protected Log log = LogFactory.getLog(SshToolsConnectionProxyTab.class);
+
+    /**
+* Creates a new SshToolsConnectionProxyTab object.
+*/
+    public SshToolsConnectionProxyTab() {
+        super();
+        group.add(noProxy);
+        group.add(httpProxy);
+        group.add(socks4Proxy);
+        group.add(socks5Proxy);
+
+        ChangeListener listener = new ChangeListener() {
+                public void stateChanged(ChangeEvent e) {
+                    if (noProxy.isSelected()) {
+                        username.setEnabled(false);
+                        password.setEnabled(false);
+                        proxy.setEnabled(false);
+
+                        //port.setEnabled(false);
+                        port.setForeground(Color.white);
+                    } else {
+                        username.setEnabled(true);
+                        password.setEnabled(true);
+                        proxy.setEnabled(true);
+
+                        //port.setEnabled(true);
+                        port.setForeground(Color.black);
+
+                        if (httpProxy.isSelected()) {
+                            port.setText("80");
+                        } else {
+                            port.setText("1080");
+                        }
+                    }
+                }
+            };
+
+        noProxy.getModel().addChangeListener(listener);
+        httpProxy.getModel().addChangeListener(listener);
+        socks4Proxy.getModel().addChangeListener(listener);
+        socks5Proxy.getModel().addChangeListener(listener);
+
+        //  Create the main connection details panel
+        GridBagConstraints gbc = new GridBagConstraints();
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        gbc.anchor = GridBagConstraints.NORTH;
+        gbc.insets = new Insets(0, 2, 2, 2);
+        gbc.weightx = 1.0;
+        proxyframe.setBorder(BorderFactory.createTitledBorder(
+                "Connect using the following proxy"));
+
+        //  No proxy label
+        gbc.insets = new Insets(2, 10, 2, 2);
+        UIUtil.jGridBagAdd(proxyframe, noProxy, gbc, GridBagConstraints.RELATIVE);
+
+        // Socks 4 label
+        gbc.insets = new Insets(2, 15, 2, 2);
+        UIUtil.jGridBagAdd(proxyframe, socks4Proxy, gbc,
+            GridBagConstraints.REMAINDER);
+
+        //gbc.fill = GridBagConstraints.HORIZONTAL;
+        // Http Proxy
+        gbc.insets = new Insets(2, 10, 2, 2);
+        UIUtil.jGridBagAdd(proxyframe, httpProxy, gbc,
+            GridBagConstraints.RELATIVE);
+
+        // Socks 5 label
+        gbc.insets = new Insets(2, 15, 2, 2);
+        UIUtil.jGridBagAdd(proxyframe, socks5Proxy, gbc,
+            GridBagConstraints.REMAINDER);
+        gbc.insets = new Insets(2, 10, 2, 10);
+
+        JPanel connect = new JPanel(new GridBagLayout());
+        connect.setBorder(BorderFactory.createTitledBorder("Proxy Details"));
+        UIUtil.jGridBagAdd(connect, new JLabel("Host"), gbc,
+            GridBagConstraints.REMAINDER);
+        UIUtil.jGridBagAdd(connect, proxy, gbc, GridBagConstraints.REMAINDER);
+        UIUtil.jGridBagAdd(connect, new JLabel("Port"), gbc,
+            GridBagConstraints.REMAINDER);
+        gbc.anchor = GridBagConstraints.WEST;
+        gbc.fill = GridBagConstraints.NONE;
+        UIUtil.jGridBagAdd(connect, port, gbc, GridBagConstraints.REMAINDER);
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        UIUtil.jGridBagAdd(connect, new JLabel("Username"), gbc,
+            GridBagConstraints.REMAINDER);
+        UIUtil.jGridBagAdd(connect, username, gbc, GridBagConstraints.REMAINDER);
+        UIUtil.jGridBagAdd(connect, new JLabel("Password"), gbc,
+            GridBagConstraints.REMAINDER);
+        gbc.insets = new Insets(2, 10, 10, 10);
+        UIUtil.jGridBagAdd(connect, password, gbc, GridBagConstraints.REMAINDER);
+
+        JPanel main = new JPanel(new GridBagLayout());
+        gbc.insets = new Insets(2, 2, 2, 2);
+        UIUtil.jGridBagAdd(main, proxyframe, gbc, GridBagConstraints.REMAINDER);
+        UIUtil.jGridBagAdd(main, connect, gbc, GridBagConstraints.REMAINDER);
+
+        IconWrapperPanel iconProxyDetailsPanel = new IconWrapperPanel(new ResourceIcon(
+                    PROXY_ICON), main);
+        noProxy.setSelected(true);
+
+        //  This panel
+        setLayout(new BorderLayout());
+        setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+        gbc = new GridBagConstraints();
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        gbc.anchor = GridBagConstraints.NORTH;
+        gbc.insets = new Insets(2, 2, 2, 2);
+        gbc.weightx = 1.0;
+        add(iconProxyDetailsPanel, BorderLayout.NORTH);
+    }
+
+    /**
+*
+*
+* @param profile
+*/
+    public void setConnectionProfile(SshToolsConnectionProfile profile) {
+        this.profile = profile;
+
+        if (profile.getTransportProvider() == SshToolsConnectionProfile.USE_HTTP_PROXY) {
+            httpProxy.setSelected(true);
+        } else if (profile.getTransportProvider() == SshToolsConnectionProfile.USE_SOCKS4_PROXY) {
+            socks4Proxy.setSelected(true);
+        } else if (profile.getTransportProvider() == SshToolsConnectionProfile.USE_SOCKS5_PROXY) {
+            socks5Proxy.setSelected(true);
+        }
+
+        proxy.setText(profile.getProxyHost());
+
+        if (profile.getProxyPort() > 0) {
+            port.setValue(new Integer(profile.getProxyPort()));
+        }
+
+        username.setText(profile.getProxyUsername());
+        password.setText(profile.getProxyPassword());
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public SshToolsConnectionProfile getConnectionProfile() {
+        return profile;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getTabContext() {
+        return "Connection";
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public Icon getTabIcon() {
+        return null;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getTabTitle() {
+        return "Proxy";
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getTabToolTipText() {
+        return "Configure the proxy connection.";
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public int getTabMnemonic() {
+        return 'p';
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public Component getTabComponent() {
+        return this;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public boolean validateTab() {
+        return true;
+    }
+
+    /**
+*
+*/
+    public void applyTab() {
+        if (httpProxy.isSelected()) {
+            profile.setTransportProvider(SshToolsConnectionProfile.USE_HTTP_PROXY);
+        } else if (socks4Proxy.isSelected()) {
+            profile.setTransportProvider(SshToolsConnectionProfile.USE_SOCKS4_PROXY);
+        } else if (socks5Proxy.isSelected()) {
+            profile.setTransportProvider(SshToolsConnectionProfile.USE_SOCKS5_PROXY);
+        } else {
+            profile.setTransportProvider(SshToolsConnectionProfile.USE_STANDARD_SOCKET);
+        }
+
+        profile.setProxyHost(proxy.getText());
+        profile.setProxyPort(port.getValue().intValue());
+        profile.setProxyUsername(username.getText());
+        profile.setProxyPassword(new String(password.getPassword()));
+    }
+
+    /**
+*
+*/
+    public void tabSelected() {
+    }
+}
diff --git a/src/com/sshtools/common/ui/SshToolsConnectionTab.java b/src/com/sshtools/common/ui/SshToolsConnectionTab.java
new file mode 100644
index 0000000000000000000000000000000000000000..f5c64926e52665417edddb6b4f69d3b387863c56
--- /dev/null
+++ b/src/com/sshtools/common/ui/SshToolsConnectionTab.java
@@ -0,0 +1,51 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import com.sshtools.common.configuration.SshToolsConnectionProfile;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public interface SshToolsConnectionTab extends Tab {
+    /**
+*
+*
+* @param profile
+*/
+    public void setConnectionProfile(SshToolsConnectionProfile profile);
+
+    /**
+*
+*
+* @return
+*/
+    public SshToolsConnectionProfile getConnectionProfile();
+}
diff --git a/src/com/sshtools/common/ui/StandardAction.java b/src/com/sshtools/common/ui/StandardAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..3df31dd0f29cc1cd3d670ade797c74c762f8ff84
--- /dev/null
+++ b/src/com/sshtools/common/ui/StandardAction.java
@@ -0,0 +1,198 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.event.*;
+
+import java.net.*;
+
+import javax.swing.*;
+import javax.swing.event.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public abstract class StandardAction extends AbstractAction {
+    /**  */
+    public final static String ON_TOOLBAR = "onToolBar";
+
+    /**  */
+    public final static String TOOLBAR_GROUP = "toolBarGroup";
+
+    /**  */
+    public final static String TOOLBAR_WEIGHT = "toolBarWeight";
+
+    /**  */
+    public final static String ON_MENUBAR = "onMenuBar";
+
+    /**  */
+    public final static String MENU_NAME = "menuName";
+
+    /**  */
+    public final static String MENU_ITEM_GROUP = "menuItemGroup";
+
+    /**  */
+    public final static String MENU_ITEM_WEIGHT = "menuItemWeight";
+
+    /**  */
+    public final static String IMAGE_DIR = "/com/sshtools/sshterm/";
+
+    /**  */
+    public final static String HIDE_TOOLBAR_TEXT = "hideToolbarText";
+
+    /**  */
+    public final static String IS_TOGGLE_BUTTON = "isToggleButton";
+
+    /**  */
+    public final static String LARGE_ICON = "LargeIcon";
+
+    /**  */
+    public final static String ON_CONTEXT_MENU = "onContextMenu";
+
+    /**  */
+    public final static String CONTEXT_MENU_GROUP = "contextMenuGroup";
+
+    /**  */
+    public final static String CONTEXT_MENU_WEIGHT = "contextMenuWeight";
+
+    /**  */
+    public final static String MENU_ICON = "menuIcon";
+
+    // The listener to action events (usually the main UI)
+    private EventListenerList listeners;
+
+    /**
+*
+*
+* @return
+*/
+    public String getActionCommand() {
+        return (String) getValue(Action.ACTION_COMMAND_KEY);
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getShortDescription() {
+        return (String) getValue(Action.SHORT_DESCRIPTION);
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getLongDescription() {
+        return (String) getValue(Action.LONG_DESCRIPTION);
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getName() {
+        return (String) getValue(Action.NAME);
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getSmallIcon() {
+        return (String) getValue(Action.SMALL_ICON);
+    }
+
+    /**
+*
+*
+* @param evt
+*/
+    public void actionPerformed(ActionEvent evt) {
+        if (listeners != null) {
+            Object[] listenerList = listeners.getListenerList();
+
+            // Recreate the ActionEvent and stuff the value of the ACTION_COMMAND_KEY
+            ActionEvent e = new ActionEvent(evt.getSource(), evt.getID(),
+                    (String) getValue(Action.ACTION_COMMAND_KEY));
+
+            for (int i = 0; i <= (listenerList.length - 2); i += 2) {
+                ((ActionListener) listenerList[i + 1]).actionPerformed(e);
+            }
+        }
+    }
+
+    /**
+*
+*
+* @param l
+*/
+    public void addActionListener(ActionListener l) {
+        if (listeners == null) {
+            listeners = new EventListenerList();
+        }
+
+        listeners.add(ActionListener.class, l);
+    }
+
+    /**
+*
+*
+* @param l
+*/
+    public void removeActionListener(ActionListener l) {
+        if (listeners == null) {
+            return;
+        }
+
+        listeners.remove(ActionListener.class, l);
+    }
+
+    /**
+*
+*
+* @param name
+*
+* @return
+*/
+    public ImageIcon getIcon(String name) {
+        String imagePath = name.startsWith("/") ? name : (IMAGE_DIR + name);
+        URL url = this.getClass().getResource(imagePath);
+
+        if (url != null) {
+            return new ImageIcon(url);
+        }
+
+        return null;
+    }
+}
diff --git a/src/com/sshtools/common/ui/StartStopStateRenderer.java b/src/com/sshtools/common/ui/StartStopStateRenderer.java
new file mode 100644
index 0000000000000000000000000000000000000000..731224b9ae0f588dbd13dd3a96d03ae4eb3932b2
--- /dev/null
+++ b/src/com/sshtools/common/ui/StartStopStateRenderer.java
@@ -0,0 +1,88 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import com.sshtools.j2ssh.util.StartStopState;
+
+import java.awt.Component;
+
+import javax.swing.Icon;
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.table.DefaultTableCellRenderer;
+
+
+public class StartStopStateRenderer extends DefaultTableCellRenderer {
+    //  Private instance variables
+    private Icon startedIcon;
+
+    //  Private instance variables
+    private Icon stoppedIcon;
+
+    //  Private instance variables
+    private Icon failedIcon;
+
+    //  Private instance variables
+    private String errorMsg;
+
+    public StartStopStateRenderer(Icon startedIcon, Icon stoppedIcon) {
+        this.startedIcon = startedIcon;
+        this.stoppedIcon = stoppedIcon;
+        setHorizontalAlignment(JLabel.CENTER);
+    }
+
+    public StartStopStateRenderer(Icon startedIcon, Icon stoppedIcon,
+        Icon failedIcon, String errorMsg) {
+        this.startedIcon = startedIcon;
+        this.stoppedIcon = stoppedIcon;
+        this.failedIcon = failedIcon;
+        this.errorMsg = errorMsg;
+        setHorizontalAlignment(JLabel.CENTER);
+    }
+
+    public Component getTableCellRendererComponent(JTable table, Object value,
+        boolean isSelected, boolean hasFocus, int row, int column) {
+        super.getTableCellRendererComponent(table, value, isSelected, hasFocus,
+            row, column);
+
+        StartStopState state = (StartStopState) value;
+
+        if (state.getValue() == StartStopState.FAILED) {
+            setIcon(failedIcon);
+            setToolTipText(errorMsg);
+        } else {
+            setIcon((state.getValue() == StartStopState.STOPPED) ? stoppedIcon
+                                                                 : startedIcon);
+            setToolTipText(null);
+        }
+
+        return this;
+    }
+
+    public String getText() {
+        return null;
+    }
+}
diff --git a/src/com/sshtools/common/ui/StatusBar.java b/src/com/sshtools/common/ui/StatusBar.java
new file mode 100644
index 0000000000000000000000000000000000000000..1e632c47bb9aa12664984d76da730a6a31e84a66
--- /dev/null
+++ b/src/com/sshtools/common/ui/StatusBar.java
@@ -0,0 +1,211 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+
+import javax.swing.BorderFactory;
+import javax.swing.Icon;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class StatusBar extends JPanel {
+    /**  */
+    public final static Icon GREEN_LED_ON = new ResourceIcon(StatusBar.class,
+            "greenledon.png");
+
+    /**  */
+    public final static Icon GREEN_LED_OFF = new ResourceIcon(StatusBar.class,
+            "greenledoff.png");
+
+    /**  */
+    public final static Icon RED_LED_ON = new ResourceIcon(StatusBar.class,
+            "redledon.png");
+
+    /**  */
+    public final static Icon RED_LED_OFF = new ResourceIcon(StatusBar.class,
+            "redledoff.png");
+
+    //
+    private StatusLabel connected;
+
+    //
+    private StatusLabel statusText;
+
+    //
+    private StatusLabel host;
+
+    //
+    private StatusLabel user;
+
+    //
+    private StatusLabel rid;
+    private JLabel sending;
+    private JLabel receiving;
+    private javax.swing.Timer sendingTimer;
+    private javax.swing.Timer receivingTimer;
+
+    /**
+* Creates a new StatusBar object.
+*/
+    public StatusBar() {
+        super(new GridBagLayout());
+
+        GridBagConstraints gbc = new GridBagConstraints();
+        gbc.anchor = GridBagConstraints.WEST;
+        gbc.fill = GridBagConstraints.BOTH;
+        gbc.weightx = 0.0;
+        connected = new StatusLabel(RED_LED_OFF);
+        connected.setHorizontalAlignment(JLabel.CENTER);
+        UIUtil.jGridBagAdd(this, connected, gbc, 1);
+
+        JPanel lights = new JPanel(new GridLayout(1, 2));
+        sending = new JLabel(GREEN_LED_OFF);
+        sending.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 2));
+        sending.setHorizontalAlignment(JLabel.CENTER);
+        receiving = new JLabel(GREEN_LED_OFF);
+        receiving.setHorizontalAlignment(JLabel.CENTER);
+        lights.add(sending);
+        lights.add(receiving);
+        gbc.weightx = 0.0;
+        lights.setBorder(BorderFactory.createCompoundBorder(
+                BorderFactory.createLoweredBevelBorder(),
+                BorderFactory.createEmptyBorder(1, 1, 1, 1)));
+        gbc.weightx = 1.5;
+        host = new StatusLabel();
+        UIUtil.jGridBagAdd(this, host, gbc, 1);
+        user = new StatusLabel();
+        UIUtil.jGridBagAdd(this, user, gbc, 1);
+        rid = new StatusLabel();
+        UIUtil.jGridBagAdd(this, rid, gbc, 1);
+        statusText = new StatusLabel();
+        gbc.weightx = 4.0;
+        UIUtil.jGridBagAdd(this, statusText, gbc, GridBagConstraints.RELATIVE);
+        gbc.weightx = 0.0;
+        UIUtil.jGridBagAdd(this, lights, gbc, GridBagConstraints.REMAINDER);
+    }
+
+    /**
+*
+*
+* @param receiving
+*/
+    public void setReceiving(boolean receiving) {
+        this.receiving.setIcon(receiving ? GREEN_LED_ON : GREEN_LED_OFF);
+    }
+
+    /**
+*
+*
+* @param sending
+*/
+    public void setSending(boolean sending) {
+        this.sending.setIcon(sending ? GREEN_LED_ON : GREEN_LED_OFF);
+    }
+
+    /**
+*
+*
+* @param connected
+*/
+    public void setConnected(boolean connected) {
+        this.connected.setIcon(connected ? RED_LED_ON : RED_LED_OFF);
+    }
+
+    /**
+*
+*
+* @param text
+*/
+    public void setStatusText(String text) {
+        statusText.setText(text);
+    }
+
+    /**
+*
+*
+* @param text
+*/
+    public void setHost(String text) {
+        host.setText(text);
+    }
+
+    /**
+*
+*
+* @param text
+* @param port
+*/
+    public void setHost(String text, int port) {
+        host.setText(text + ":" + String.valueOf(port));
+    }
+
+    /**
+*
+*
+* @param remoteId
+*/
+    public void setRemoteId(String remoteId) {
+        rid.setText(remoteId);
+    }
+
+    /**
+*
+*
+* @param text
+*/
+    public void setUser(String text) {
+        user.setText(text);
+    }
+
+    class StatusLabel extends JLabel {
+        StatusLabel(Icon icon) {
+            super(icon);
+            init();
+        }
+
+        StatusLabel() {
+            super();
+            init();
+        }
+
+        void init() {
+            setFont(getFont().deriveFont(10f));
+            setBorder(BorderFactory.createCompoundBorder(
+                    BorderFactory.createLoweredBevelBorder(),
+                    BorderFactory.createEmptyBorder(1, 1, 1, 1)));
+        }
+    }
+}
diff --git a/src/com/sshtools/common/ui/StopAction.java b/src/com/sshtools/common/ui/StopAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..fe93bdd5a30408ecec501997a009e9d9231352df
--- /dev/null
+++ b/src/com/sshtools/common/ui/StopAction.java
@@ -0,0 +1,69 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.event.*;
+
+import javax.swing.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class StopAction extends StandardAction {
+    private final static String ACTION_COMMAND_KEY_STOP = "stop-command";
+    private final static String NAME_STOP = "Stop";
+    private final static String SMALL_ICON_STOP = "/com/sshtools/common/ui/stop.png";
+    private final static String LARGE_ICON_STOP = "";
+    private final static String SHORT_DESCRIPTION_STOP = "Stop recording";
+    private final static String LONG_DESCRIPTION_STOP = "Stop recording output to file";
+    private final static int MNEMONIC_KEY_STOP = 's';
+
+    /**
+* Creates a new StopAction object.
+*/
+    public StopAction() {
+        putValue(Action.NAME, NAME_STOP);
+        putValue(Action.SMALL_ICON, getIcon(SMALL_ICON_STOP));
+        putValue(LARGE_ICON, getIcon(LARGE_ICON_STOP));
+        putValue(Action.SHORT_DESCRIPTION, SHORT_DESCRIPTION_STOP);
+        putValue(Action.LONG_DESCRIPTION, LONG_DESCRIPTION_STOP);
+        putValue(Action.ACCELERATOR_KEY,
+            KeyStroke.getKeyStroke(KeyEvent.VK_J, KeyEvent.ALT_MASK));
+        putValue(Action.MNEMONIC_KEY, new Integer(MNEMONIC_KEY_STOP));
+        putValue(Action.ACTION_COMMAND_KEY, ACTION_COMMAND_KEY_STOP);
+        putValue(StandardAction.ON_MENUBAR, new Boolean(true));
+        putValue(StandardAction.MENU_NAME, "File");
+        putValue(StandardAction.MENU_ITEM_GROUP, new Integer(60));
+        putValue(StandardAction.MENU_ITEM_WEIGHT, new Integer(10));
+        putValue(StandardAction.ON_TOOLBAR, new Boolean(true));
+        putValue(StandardAction.TOOLBAR_GROUP, new Integer(60));
+        putValue(StandardAction.TOOLBAR_WEIGHT, new Integer(10));
+    }
+}
diff --git a/src/com/sshtools/common/ui/Tab.java b/src/com/sshtools/common/ui/Tab.java
new file mode 100644
index 0000000000000000000000000000000000000000..383df6515aaf4512712886b035a05b907dcf08fe
--- /dev/null
+++ b/src/com/sshtools/common/ui/Tab.java
@@ -0,0 +1,98 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.*;
+
+import javax.swing.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public interface Tab {
+    /**
+*
+*
+* @return
+*/
+    public String getTabContext();
+
+    /**
+*
+*
+* @return
+*/
+    public Icon getTabIcon();
+
+    /**
+*
+*
+* @return
+*/
+    public String getTabTitle();
+
+    /**
+*
+*
+* @return
+*/
+    public String getTabToolTipText();
+
+    /**
+*
+*
+* @return
+*/
+    public int getTabMnemonic();
+
+    /**
+*
+*
+* @return
+*/
+    public Component getTabComponent();
+
+    /**
+*
+*
+* @return
+*/
+    public boolean validateTab();
+
+    /**
+*
+*/
+    public void applyTab();
+
+    /**
+*
+*/
+    public void tabSelected();
+}
diff --git a/src/com/sshtools/common/ui/Tabber.java b/src/com/sshtools/common/ui/Tabber.java
new file mode 100644
index 0000000000000000000000000000000000000000..9ff7b99a3953cf3315c631d8e310de9cedf3d8e0
--- /dev/null
+++ b/src/com/sshtools/common/ui/Tabber.java
@@ -0,0 +1,128 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.*;
+
+import javax.swing.*;
+import javax.swing.event.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class Tabber extends JTabbedPane {
+    /**
+* Creates a new Tabber object.
+*/
+    public Tabber() {
+        this(TOP);
+    }
+
+    /**
+* Creates a new Tabber object.
+*
+* @param tabPlacement
+*/
+    public Tabber(int tabPlacement) {
+        super(tabPlacement);
+        addChangeListener(new ChangeListener() {
+                public void stateChanged(ChangeEvent e) {
+                    if (getSelectedIndex() != -1) {
+                        getTabAt(getSelectedIndex()).tabSelected();
+                    }
+                }
+            });
+    }
+
+    /**
+*
+*
+* @param i
+*
+* @return
+*/
+    public Tab getTabAt(int i) {
+        return ((TabPanel) getComponentAt(i)).getTab();
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public boolean validateTabs() {
+        for (int i = 0; i < getTabCount(); i++) {
+            Tab tab = ((TabPanel) getComponentAt(i)).getTab();
+
+            if (!tab.validateTab()) {
+                setSelectedIndex(i);
+
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+*
+*/
+    public void applyTabs() {
+        for (int i = 0; i < getTabCount(); i++) {
+            Tab tab = ((TabPanel) getComponentAt(i)).getTab();
+            tab.applyTab();
+        }
+    }
+
+    /**
+*
+*
+* @param tab
+*/
+    public void addTab(Tab tab) {
+        addTab(tab.getTabTitle(), tab.getTabIcon(), new TabPanel(tab),
+            tab.getTabToolTipText());
+    }
+
+    class TabPanel extends JPanel {
+        private Tab tab;
+
+        TabPanel(Tab tab) {
+            super(new BorderLayout());
+            this.tab = tab;
+            setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+            add(tab.getTabComponent(), BorderLayout.CENTER);
+        }
+
+        public Tab getTab() {
+            return tab;
+        }
+    }
+}
diff --git a/src/com/sshtools/common/ui/TextBox.java b/src/com/sshtools/common/ui/TextBox.java
new file mode 100644
index 0000000000000000000000000000000000000000..1751085e58f8618927cc69d3591f6579d7c40e6a
--- /dev/null
+++ b/src/com/sshtools/common/ui/TextBox.java
@@ -0,0 +1,60 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import javax.swing.*;
+
+
+/**
+ *  Description of the Class
+ *
+ *@author     magicthize
+ *@created    26 May 2002
+ */
+public class TextBox extends JTextArea {
+    private String text;
+
+    public TextBox() {
+        this("");
+    }
+
+    public TextBox(String text) {
+        this(text, 0, 0);
+    }
+
+    public TextBox(String text, int rows, int columns) {
+        super(rows, columns);
+        setBackground(UIManager.getColor("Label.background"));
+        setForeground(UIManager.getColor("Label.foreground"));
+        setBorder(UIManager.getBorder("Label.border"));
+        setFont(UIManager.getFont("TextField.font"));
+        setOpaque(false);
+        setWrapStyleWord(true);
+        setLineWrap(true);
+        setEditable(false);
+        setText(text);
+    }
+}
diff --git a/src/com/sshtools/common/ui/ToolBarSeparator.java b/src/com/sshtools/common/ui/ToolBarSeparator.java
new file mode 100644
index 0000000000000000000000000000000000000000..dd54f81c257ab5976782007b54eb8166f9340827
--- /dev/null
+++ b/src/com/sshtools/common/ui/ToolBarSeparator.java
@@ -0,0 +1,65 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.*;
+
+import javax.swing.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class ToolBarSeparator extends JSeparator {
+    /**
+* Creates a new ToolBarSeparator object.
+*/
+    public ToolBarSeparator() {
+        super(JSeparator.VERTICAL);
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public Dimension getMaximumSize() {
+        return (((JToolBar) getParent()).getOrientation() == JToolBar.HORIZONTAL)
+        ? new Dimension(4, super.getMaximumSize().height)
+        : new Dimension(super.getMaximumSize().width, 4);
+    }
+
+    /**
+*
+*/
+    public void doLayout() {
+        setOrientation((((JToolBar) getParent()).getOrientation() == JToolBar.HORIZONTAL)
+            ? JSeparator.VERTICAL : JSeparator.HORIZONTAL);
+    }
+}
diff --git a/src/com/sshtools/common/ui/ToolButton.java b/src/com/sshtools/common/ui/ToolButton.java
new file mode 100644
index 0000000000000000000000000000000000000000..20de156cd323f494a29d8edd1d15e24eee0a3d2c
--- /dev/null
+++ b/src/com/sshtools/common/ui/ToolButton.java
@@ -0,0 +1,95 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.*;
+
+import javax.swing.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class ToolButton extends JButton {
+    //
+    private final static Insets INSETS = new Insets(0, 0, 0, 0);
+    private boolean hideText;
+
+    /**
+* Creates a new ToolButton object.
+*
+* @param action
+*/
+    public ToolButton(Action action) {
+        super(action);
+        setMargin(INSETS);
+        setRequestFocusEnabled(false);
+        setFocusPainted(false);
+
+        if (action.getValue(StandardAction.HIDE_TOOLBAR_TEXT) != null) {
+            setHideText(Boolean.TRUE.equals(
+                    (Boolean) action.getValue(StandardAction.HIDE_TOOLBAR_TEXT)));
+        } else {
+            setHideText(true);
+        }
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public boolean isFocusable() {
+        return false;
+    }
+
+    /**
+*
+*
+* @param hideText
+*/
+    public void setHideText(boolean hideText) {
+        if (this.hideText != hideText) {
+            firePropertyChange("hideText", this.hideText, hideText);
+        }
+
+        this.hideText = hideText;
+        this.setHorizontalTextPosition(ToolButton.RIGHT);
+        repaint();
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getText() {
+        return hideText ? null : super.getText();
+    }
+}
diff --git a/src/com/sshtools/common/ui/ToolToggleButton.java b/src/com/sshtools/common/ui/ToolToggleButton.java
new file mode 100644
index 0000000000000000000000000000000000000000..e5299c4b5d29f4172ca27de8d921aa7165762078
--- /dev/null
+++ b/src/com/sshtools/common/ui/ToolToggleButton.java
@@ -0,0 +1,98 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.*;
+
+import javax.swing.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.2 $
+ */
+public class ToolToggleButton extends JToggleButton {
+    //
+    private final static Insets INSETS = new Insets(0, 0, 0, 0);
+    private boolean hideText;
+
+    /**
+* Creates a new ToolButton object.
+*
+* @param action
+*/
+    public ToolToggleButton(Action action) {
+        super(action);
+        setMargin(INSETS);
+        setRequestFocusEnabled(false);
+        setFocusPainted(false);
+
+        if (action.getValue(StandardAction.HIDE_TOOLBAR_TEXT) != null) {
+            setHideText(Boolean.TRUE.equals(
+                    (Boolean) action.getValue(StandardAction.HIDE_TOOLBAR_TEXT)));
+        } else {
+            setHideText(true);
+        }
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public boolean isFocusable() {
+        return false;
+    }
+
+    /**
+*
+*
+* @param hideText
+*/
+    public void setHideText(boolean hideText) {
+        if (this.hideText != hideText) {
+            firePropertyChange("hideText", this.hideText, hideText);
+        }
+
+        if (hideText) {
+            this.hideText = hideText;
+            this.setHorizontalTextPosition(ToolButton.RIGHT);
+        }
+
+        repaint();
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String getText() {
+        return hideText ? null : super.getText();
+    }
+}
diff --git a/src/com/sshtools/common/ui/UIUtil.java b/src/com/sshtools/common/ui/UIUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..2d24ae1f807589e72216cb2ec55cebde4e0d0f66
--- /dev/null
+++ b/src/com/sshtools/common/ui/UIUtil.java
@@ -0,0 +1,183 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.*;
+import java.awt.event.*;
+
+import java.util.*;
+
+import javax.swing.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public class UIUtil implements SwingConstants {
+    /**
+* Parse a string in the format of <code>[character]</code> to create an
+* Integer that may be used for an action.
+*
+* @param character mnemonic string
+* @return mnemonic
+*/
+    public static Integer parseMnemonicString(String string) {
+        try {
+            return new Integer(string);
+        } catch (Throwable t) {
+            return new Integer(-1);
+        }
+    }
+
+    /**
+* Parse a string in the format of [ALT+|CTRL+|SHIFT+]<keycode> to
+* create a keystroke. This can be used to define accelerators from
+* resource bundles
+*
+* @param string accelerator string
+* @return keystroke
+*/
+    public static KeyStroke parseAcceleratorString(String string) {
+        if ((string == null) || string.equals("")) {
+            return null;
+        }
+
+        StringTokenizer t = new StringTokenizer(string, "+");
+        int mod = 0;
+        int key = -1;
+
+        while (t.hasMoreTokens()) {
+            String x = t.nextToken();
+
+            if (x.equalsIgnoreCase("ctrl")) {
+                mod += KeyEvent.CTRL_MASK;
+            } else if (x.equalsIgnoreCase("shift")) {
+                mod += KeyEvent.SHIFT_MASK;
+            } else if (x.equalsIgnoreCase("alt")) {
+                mod += KeyEvent.ALT_MASK;
+            } else {
+                try {
+                    java.lang.reflect.Field f = KeyEvent.class.getField(x);
+                    key = f.getInt(null);
+                } catch (Throwable ex) {
+                    ex.printStackTrace();
+                }
+            }
+        }
+
+        if (key != -1) {
+            KeyStroke ks = KeyStroke.getKeyStroke(key, mod);
+
+            return ks;
+        }
+
+        return null;
+    }
+
+    /**
+*
+*
+* @param parent
+* @param componentToAdd
+* @param constraints
+* @param pos
+*
+* @throws IllegalArgumentException
+*/
+    public static void jGridBagAdd(JComponent parent, Component componentToAdd,
+        GridBagConstraints constraints, int pos) {
+        if (!(parent.getLayout() instanceof GridBagLayout)) {
+            throw new IllegalArgumentException(
+                "parent must have a GridBagLayout");
+        }
+
+        //
+        GridBagLayout layout = (GridBagLayout) parent.getLayout();
+
+        //
+        constraints.gridwidth = pos;
+        layout.setConstraints(componentToAdd, constraints);
+        parent.add(componentToAdd);
+    }
+
+    /**
+*
+*
+* @param p
+* @param c
+*/
+    public static void positionComponent(int p, Component c) {
+        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
+
+        switch (p) {
+        case NORTH_WEST:
+            c.setLocation(0, 0);
+
+            break;
+
+        case NORTH:
+            c.setLocation((d.width - c.getSize().width) / 2, 0);
+
+            break;
+
+        case NORTH_EAST:
+            c.setLocation((d.width - c.getSize().width), 0);
+
+            break;
+
+        case WEST:
+            c.setLocation(0, (d.height - c.getSize().height) / 2);
+
+            break;
+
+        case SOUTH_WEST:
+            c.setLocation(0, (d.height - c.getSize().height));
+
+            break;
+
+        case EAST:
+            c.setLocation(d.width - c.getSize().width,
+                (d.height - c.getSize().height) / 2);
+
+            break;
+
+        case SOUTH_EAST:
+            c.setLocation((d.width - c.getSize().width),
+                (d.height - c.getSize().height) - 30);
+
+            break;
+
+        case CENTER:
+            c.setLocation((d.width - c.getSize().width) / 2,
+                (d.height - c.getSize().height) / 2);
+
+            break;
+        }
+    }
+}
diff --git a/src/com/sshtools/common/ui/XTextField.java b/src/com/sshtools/common/ui/XTextField.java
new file mode 100644
index 0000000000000000000000000000000000000000..b889a0534db68b808ddef77fd095b59863b04046
--- /dev/null
+++ b/src/com/sshtools/common/ui/XTextField.java
@@ -0,0 +1,254 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.ui;
+
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.event.*;
+
+import javax.swing.*;
+import javax.swing.text.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class XTextField extends JTextField implements ClipboardOwner {
+    private JPopupMenu popup;
+    private Action cutAction;
+    private Action copyAction;
+    private Action pasteAction;
+    private Action deleteAction;
+    private Action selectAllAction;
+
+    /**
+* Creates a new XTextField object.
+*/
+    public XTextField() {
+        this(null, null, 0);
+    }
+
+    /**
+* Creates a new XTextField object.
+*
+* @param text
+*/
+    public XTextField(String text) {
+        this(null, text, 0);
+    }
+
+    /**
+* Creates a new XTextField object.
+*
+* @param columns
+*/
+    public XTextField(int columns) {
+        this(null, null, columns);
+    }
+
+    /**
+* Creates a new XTextField object.
+*
+* @param text
+* @param columns
+*/
+    public XTextField(String text, int columns) {
+        this(null, text, columns);
+    }
+
+    /**
+* Creates a new XTextField object.
+*
+* @param doc
+* @param text
+* @param columns
+*/
+    public XTextField(Document doc, String text, int columns) {
+        super(doc, text, columns);
+        initXtensions();
+    }
+
+    /**
+*
+*
+* @param clipboard
+* @param contents
+*/
+    public void lostOwnership(Clipboard clipboard, Transferable contents) {
+    }
+
+    private void showPopup(int x, int y) {
+        //  Grab the focus, this should deselect any other selected fields.
+        requestFocus();
+
+        //  If the popup has never been show before - then build it
+        if (popup == null) {
+            popup = new JPopupMenu("Clipboard");
+            popup.add(cutAction = new CutAction());
+            popup.add(copyAction = new CopyAction());
+            popup.add(pasteAction = new PasteAction());
+            popup.add(deleteAction = new DeleteAction());
+            popup.addSeparator();
+            popup.add(selectAllAction = new SelectAllAction());
+        }
+
+        //  Enabled the actions based on the field contents
+        cutAction.setEnabled(isEnabled() && (getSelectedText() != null));
+        copyAction.setEnabled(isEnabled() && (getSelectedText() != null));
+        deleteAction.setEnabled(isEnabled() && (getSelectedText() != null));
+        pasteAction.setEnabled(isEnabled() &&
+            Toolkit.getDefaultToolkit().getSystemClipboard().getContents(this)
+                   .isDataFlavorSupported(DataFlavor.stringFlavor));
+        selectAllAction.setEnabled(isEnabled());
+
+        //  Make the popup visible
+        popup.show(this, x, y);
+    }
+
+    private void initXtensions() {
+        addMouseListener(new MouseAdapter() {
+                public void mouseClicked(MouseEvent evt) {
+                    if (SwingUtilities.isRightMouseButton(evt)) {
+                        showPopup(evt.getX(), evt.getY());
+                    }
+                }
+            });
+        addFocusListener(new FocusListener() {
+                public void focusGained(FocusEvent evt) {
+                    XTextField.this.selectAll();
+                }
+
+                public void focusLost(FocusEvent evt) {
+                    //                if(popup.isVisible())
+                    //                    popup.setVisible(false);
+                }
+            });
+    }
+
+    //  Supporting actions
+    class CopyAction extends AbstractAction {
+        public CopyAction() {
+            putValue(Action.NAME, "Copy");
+            putValue(Action.SMALL_ICON,
+                new ResourceIcon(XTextField.class, "copy.png"));
+            putValue(Action.SHORT_DESCRIPTION, "Copy");
+            putValue(Action.LONG_DESCRIPTION,
+                "Copy the selection from the text and place it in the clipboard");
+            putValue(Action.MNEMONIC_KEY, new Integer('c'));
+            putValue(Action.ACCELERATOR_KEY,
+                KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.CTRL_MASK));
+        }
+
+        public void actionPerformed(ActionEvent evt) {
+            Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(
+                    getText()), XTextField.this);
+        }
+    }
+
+    class CutAction extends AbstractAction {
+        public CutAction() {
+            putValue(Action.NAME, "Cut");
+            putValue(Action.SMALL_ICON,
+                new ResourceIcon(XTextField.class, "cut.png"));
+            putValue(Action.SHORT_DESCRIPTION, "Cut selection");
+            putValue(Action.LONG_DESCRIPTION,
+                "Cut the selection from the text and place it in the clipboard");
+            putValue(Action.MNEMONIC_KEY, new Integer('u'));
+            putValue(Action.ACCELERATOR_KEY,
+                KeyStroke.getKeyStroke(KeyEvent.VK_X, KeyEvent.CTRL_MASK));
+        }
+
+        public void actionPerformed(ActionEvent evt) {
+            Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(
+                    getText()), XTextField.this);
+            setText("");
+        }
+    }
+
+    class PasteAction extends AbstractAction {
+        public PasteAction() {
+            putValue(Action.NAME, "Paste");
+            putValue(Action.SMALL_ICON,
+                new ResourceIcon(XTextField.class, "paste.png"));
+            putValue(Action.SHORT_DESCRIPTION, "Paste clipboard content");
+            putValue(Action.LONG_DESCRIPTION,
+                "Paste the clipboard contents to the current care position or replace the selection");
+            putValue(Action.MNEMONIC_KEY, new Integer('p'));
+            putValue(Action.ACCELERATOR_KEY,
+                KeyStroke.getKeyStroke(KeyEvent.VK_X, KeyEvent.CTRL_MASK));
+        }
+
+        public void actionPerformed(ActionEvent evt) {
+            Transferable t = Toolkit.getDefaultToolkit().getSystemClipboard()
+                                    .getContents(this);
+
+            if (t.isDataFlavorSupported(DataFlavor.stringFlavor)) {
+                try {
+                    setText(t.getTransferData(DataFlavor.stringFlavor).toString());
+                } catch (Exception e) {
+                    //  Dont care
+                }
+            }
+        }
+    }
+
+    class DeleteAction extends AbstractAction {
+        public DeleteAction() {
+            putValue(Action.NAME, "Delete");
+            putValue(Action.SMALL_ICON,
+                new ResourceIcon(XTextField.class, "delete.png"));
+            putValue(Action.SHORT_DESCRIPTION, "Delete selection");
+            putValue(Action.LONG_DESCRIPTION,
+                "Delete the selection from the text");
+            putValue(Action.MNEMONIC_KEY, new Integer('d'));
+            putValue(Action.ACCELERATOR_KEY,
+                KeyStroke.getKeyStroke(KeyEvent.VK_X, KeyEvent.CTRL_MASK));
+        }
+
+        public void actionPerformed(ActionEvent evt) {
+            setText("");
+        }
+    }
+
+    class SelectAllAction extends AbstractAction {
+        SelectAllAction() {
+            putValue(Action.SMALL_ICON, new EmptyIcon(16, 16));
+            putValue(Action.NAME, "Select All");
+            putValue(Action.SHORT_DESCRIPTION, "Select All");
+            putValue(Action.LONG_DESCRIPTION, "Select all items in the context");
+            putValue(Action.MNEMONIC_KEY, new Integer('a'));
+            putValue(Action.ACCELERATOR_KEY,
+                KeyStroke.getKeyStroke(KeyEvent.VK_A, KeyEvent.CTRL_MASK));
+        }
+
+        public void actionPerformed(ActionEvent evt) {
+            selectAll();
+        }
+    }
+}
diff --git a/src/com/sshtools/common/ui/about.png b/src/com/sshtools/common/ui/about.png
new file mode 100644
index 0000000000000000000000000000000000000000..f971c8aca8831c5f7cf031de4e1a78e73f52d795
Binary files /dev/null and b/src/com/sshtools/common/ui/about.png differ
diff --git a/src/com/sshtools/common/ui/add.png b/src/com/sshtools/common/ui/add.png
new file mode 100644
index 0000000000000000000000000000000000000000..1d903dac0a94d56ea38d663ffe4742900bee6c30
Binary files /dev/null and b/src/com/sshtools/common/ui/add.png differ
diff --git a/src/com/sshtools/common/ui/cancel.png b/src/com/sshtools/common/ui/cancel.png
new file mode 100644
index 0000000000000000000000000000000000000000..10a64c18e12565199d513ea2f78e37d23d41aa2e
Binary files /dev/null and b/src/com/sshtools/common/ui/cancel.png differ
diff --git a/src/com/sshtools/common/ui/close.png b/src/com/sshtools/common/ui/close.png
new file mode 100644
index 0000000000000000000000000000000000000000..972bfa369a59676478742c1258a283da4c52ce39
Binary files /dev/null and b/src/com/sshtools/common/ui/close.png differ
diff --git a/src/com/sshtools/common/ui/commands.png b/src/com/sshtools/common/ui/commands.png
new file mode 100644
index 0000000000000000000000000000000000000000..af26d0649687084d682a51fdd9477fec464eb2cb
Binary files /dev/null and b/src/com/sshtools/common/ui/commands.png differ
diff --git a/src/com/sshtools/common/ui/copy.png b/src/com/sshtools/common/ui/copy.png
new file mode 100644
index 0000000000000000000000000000000000000000..ee3793110461b9d3fe58358a4b4cfe87252d2de4
Binary files /dev/null and b/src/com/sshtools/common/ui/copy.png differ
diff --git a/src/com/sshtools/common/ui/cut.png b/src/com/sshtools/common/ui/cut.png
new file mode 100644
index 0000000000000000000000000000000000000000..4ea39c4bf9757314b6effb39d85aa06ee7f4aed9
Binary files /dev/null and b/src/com/sshtools/common/ui/cut.png differ
diff --git a/src/com/sshtools/common/ui/delete.png b/src/com/sshtools/common/ui/delete.png
new file mode 100644
index 0000000000000000000000000000000000000000..173da46973c680420d57120cc29a99670759a8c5
Binary files /dev/null and b/src/com/sshtools/common/ui/delete.png differ
diff --git a/src/com/sshtools/common/ui/dialog-error4.png b/src/com/sshtools/common/ui/dialog-error4.png
new file mode 100644
index 0000000000000000000000000000000000000000..f9a6717216fba6bbc0ed901dea2810bd94d791ee
Binary files /dev/null and b/src/com/sshtools/common/ui/dialog-error4.png differ
diff --git a/src/com/sshtools/common/ui/dialog-information.png b/src/com/sshtools/common/ui/dialog-information.png
new file mode 100644
index 0000000000000000000000000000000000000000..8afa062fbcb7eb8076bcee6174f28f2692246105
Binary files /dev/null and b/src/com/sshtools/common/ui/dialog-information.png differ
diff --git a/src/com/sshtools/common/ui/dialog-question3.png b/src/com/sshtools/common/ui/dialog-question3.png
new file mode 100644
index 0000000000000000000000000000000000000000..dfa69846f1ac23710acb0acb35b24c95f75a4dca
Binary files /dev/null and b/src/com/sshtools/common/ui/dialog-question3.png differ
diff --git a/src/com/sshtools/common/ui/dialog-warning2.png b/src/com/sshtools/common/ui/dialog-warning2.png
new file mode 100644
index 0000000000000000000000000000000000000000..3c8a37df51861ef31171987ef06c59fffa044f61
Binary files /dev/null and b/src/com/sshtools/common/ui/dialog-warning2.png differ
diff --git a/src/com/sshtools/common/ui/fileedit.png b/src/com/sshtools/common/ui/fileedit.png
new file mode 100644
index 0000000000000000000000000000000000000000..61c0cc4a4725059b604755ecf39d437d84253fb9
Binary files /dev/null and b/src/com/sshtools/common/ui/fileedit.png differ
diff --git a/src/com/sshtools/common/ui/fileopen.png b/src/com/sshtools/common/ui/fileopen.png
new file mode 100644
index 0000000000000000000000000000000000000000..97f9988f016c4b9cd75dd758d4f58125491df9b7
Binary files /dev/null and b/src/com/sshtools/common/ui/fileopen.png differ
diff --git a/src/com/sshtools/common/ui/global.png b/src/com/sshtools/common/ui/global.png
new file mode 100644
index 0000000000000000000000000000000000000000..430969cc3036610948ca6f9a01f3a1ac59830377
Binary files /dev/null and b/src/com/sshtools/common/ui/global.png differ
diff --git a/src/com/sshtools/common/ui/greenledoff.png b/src/com/sshtools/common/ui/greenledoff.png
new file mode 100644
index 0000000000000000000000000000000000000000..86518102cbfc5cdaffb75dd00e94456800f7fe31
Binary files /dev/null and b/src/com/sshtools/common/ui/greenledoff.png differ
diff --git a/src/com/sshtools/common/ui/greenledon.png b/src/com/sshtools/common/ui/greenledon.png
new file mode 100644
index 0000000000000000000000000000000000000000..36569e7191160c2c52603ac0ad763dc40f5e7a28
Binary files /dev/null and b/src/com/sshtools/common/ui/greenledon.png differ
diff --git a/src/com/sshtools/common/ui/im.gif b/src/com/sshtools/common/ui/im.gif
new file mode 100644
index 0000000000000000000000000000000000000000..aff60374d1dd86541e833f51b6b4604c47ef1485
Binary files /dev/null and b/src/com/sshtools/common/ui/im.gif differ
diff --git a/src/com/sshtools/common/ui/im.png b/src/com/sshtools/common/ui/im.png
new file mode 100644
index 0000000000000000000000000000000000000000..b957e5897e8d1ef2e95d3bccc514602d13bc741a
Binary files /dev/null and b/src/com/sshtools/common/ui/im.png differ
diff --git a/src/com/sshtools/common/ui/largecard.png b/src/com/sshtools/common/ui/largecard.png
new file mode 100644
index 0000000000000000000000000000000000000000..37f4d74500c39edb282c578e73bd67d3911d72de
Binary files /dev/null and b/src/com/sshtools/common/ui/largecard.png differ
diff --git a/src/com/sshtools/common/ui/largeglobal.png b/src/com/sshtools/common/ui/largeglobal.png
new file mode 100644
index 0000000000000000000000000000000000000000..d1c25d06a333dbe950e5c123c4ff13201f5bb1c8
Binary files /dev/null and b/src/com/sshtools/common/ui/largeglobal.png differ
diff --git a/src/com/sshtools/common/ui/largekeys.png b/src/com/sshtools/common/ui/largekeys.png
new file mode 100644
index 0000000000000000000000000000000000000000..e474d703237c955cad6ccb84b743badc0a35e41f
Binary files /dev/null and b/src/com/sshtools/common/ui/largekeys.png differ
diff --git a/src/com/sshtools/common/ui/largelock.png b/src/com/sshtools/common/ui/largelock.png
new file mode 100644
index 0000000000000000000000000000000000000000..8b1540ff064d0c5f3fcd2e385ba31ebee200f2c2
Binary files /dev/null and b/src/com/sshtools/common/ui/largelock.png differ
diff --git a/src/com/sshtools/common/ui/largenewwindow.png b/src/com/sshtools/common/ui/largenewwindow.png
new file mode 100644
index 0000000000000000000000000000000000000000..dbcff4eca57f2b2ebcbb36b9e181939db84f269a
Binary files /dev/null and b/src/com/sshtools/common/ui/largenewwindow.png differ
diff --git a/src/com/sshtools/common/ui/largeoptions.png b/src/com/sshtools/common/ui/largeoptions.png
new file mode 100644
index 0000000000000000000000000000000000000000..89bc37e094683e2f33ea5ec87c59160b2875e1ec
Binary files /dev/null and b/src/com/sshtools/common/ui/largeoptions.png differ
diff --git a/src/com/sshtools/common/ui/largeprotocol.png b/src/com/sshtools/common/ui/largeprotocol.png
new file mode 100644
index 0000000000000000000000000000000000000000..384d3f48ec5764dcd7e5e73c93704060e6aa39a7
Binary files /dev/null and b/src/com/sshtools/common/ui/largeprotocol.png differ
diff --git a/src/com/sshtools/common/ui/largeserveridentity.png b/src/com/sshtools/common/ui/largeserveridentity.png
new file mode 100644
index 0000000000000000000000000000000000000000..e8e474f635fcc8299e37bf121acc90b5a2146848
Binary files /dev/null and b/src/com/sshtools/common/ui/largeserveridentity.png differ
diff --git a/src/com/sshtools/common/ui/newconnect.png b/src/com/sshtools/common/ui/newconnect.png
new file mode 100644
index 0000000000000000000000000000000000000000..e6e1a8ef60f8f159e3ba4161885863b4a94ce2c5
Binary files /dev/null and b/src/com/sshtools/common/ui/newconnect.png differ
diff --git a/src/com/sshtools/common/ui/newwindow.png b/src/com/sshtools/common/ui/newwindow.png
new file mode 100644
index 0000000000000000000000000000000000000000..7c8b5e04689f69bcbd2f8fb42832c7fd928aa5c7
Binary files /dev/null and b/src/com/sshtools/common/ui/newwindow.png differ
diff --git a/src/com/sshtools/common/ui/ok.png b/src/com/sshtools/common/ui/ok.png
new file mode 100644
index 0000000000000000000000000000000000000000..5381fa2c49272fefd139b8a3076aae7be8fa77dd
Binary files /dev/null and b/src/com/sshtools/common/ui/ok.png differ
diff --git a/src/com/sshtools/common/ui/options.png b/src/com/sshtools/common/ui/options.png
new file mode 100644
index 0000000000000000000000000000000000000000..db4b86f2e7ddf4a21bf3c7ffc68df2b091eba172
Binary files /dev/null and b/src/com/sshtools/common/ui/options.png differ
diff --git a/src/com/sshtools/common/ui/padlock.png b/src/com/sshtools/common/ui/padlock.png
new file mode 100644
index 0000000000000000000000000000000000000000..5f3b000f9dabec1ed7416dc0c2641a6b5aa50adc
Binary files /dev/null and b/src/com/sshtools/common/ui/padlock.png differ
diff --git a/src/com/sshtools/common/ui/password.png b/src/com/sshtools/common/ui/password.png
new file mode 100644
index 0000000000000000000000000000000000000000..4d531929b20bf9dc2802baa0e96968b303354539
Binary files /dev/null and b/src/com/sshtools/common/ui/password.png differ
diff --git a/src/com/sshtools/common/ui/paste.png b/src/com/sshtools/common/ui/paste.png
new file mode 100644
index 0000000000000000000000000000000000000000..f9e7d9df56c47421de2aeba8f4b19139333f81d8
Binary files /dev/null and b/src/com/sshtools/common/ui/paste.png differ
diff --git a/src/com/sshtools/common/ui/print.png b/src/com/sshtools/common/ui/print.png
new file mode 100644
index 0000000000000000000000000000000000000000..961657146a4169e66c15290b76336f098972b2b1
Binary files /dev/null and b/src/com/sshtools/common/ui/print.png differ
diff --git a/src/com/sshtools/common/ui/printpreview.png b/src/com/sshtools/common/ui/printpreview.png
new file mode 100644
index 0000000000000000000000000000000000000000..459883f278a8e3cf4423ef6656bd727bc2077b46
Binary files /dev/null and b/src/com/sshtools/common/ui/printpreview.png differ
diff --git a/src/com/sshtools/common/ui/properties.png b/src/com/sshtools/common/ui/properties.png
new file mode 100644
index 0000000000000000000000000000000000000000..5fb9c1fb2b8eef79b5b54907ab9014b4c3953e3d
Binary files /dev/null and b/src/com/sshtools/common/ui/properties.png differ
diff --git a/src/com/sshtools/common/ui/proxy.png b/src/com/sshtools/common/ui/proxy.png
new file mode 100644
index 0000000000000000000000000000000000000000..ee8837a7352406be540b41e0f385986f3258b6ff
Binary files /dev/null and b/src/com/sshtools/common/ui/proxy.png differ
diff --git a/src/com/sshtools/common/ui/record.png b/src/com/sshtools/common/ui/record.png
new file mode 100644
index 0000000000000000000000000000000000000000..ec4906c502e5b79926125e64ceeb1a525ceff1e6
Binary files /dev/null and b/src/com/sshtools/common/ui/record.png differ
diff --git a/src/com/sshtools/common/ui/redledoff.png b/src/com/sshtools/common/ui/redledoff.png
new file mode 100644
index 0000000000000000000000000000000000000000..545c1ceea7c0823ab3ccb91719452db5b7e93b78
Binary files /dev/null and b/src/com/sshtools/common/ui/redledoff.png differ
diff --git a/src/com/sshtools/common/ui/redledon.png b/src/com/sshtools/common/ui/redledon.png
new file mode 100644
index 0000000000000000000000000000000000000000..453c7b4d9c170be1422840e4867148726d0d3b5e
Binary files /dev/null and b/src/com/sshtools/common/ui/redledon.png differ
diff --git a/src/com/sshtools/common/ui/refresh.png b/src/com/sshtools/common/ui/refresh.png
new file mode 100644
index 0000000000000000000000000000000000000000..e1ce850ca4d0890b541936d3a96b7c30691e8d45
Binary files /dev/null and b/src/com/sshtools/common/ui/refresh.png differ
diff --git a/src/com/sshtools/common/ui/remove.png b/src/com/sshtools/common/ui/remove.png
new file mode 100644
index 0000000000000000000000000000000000000000..4492040612bd933eec37eb1e0668b5b9d2f9a49f
Binary files /dev/null and b/src/com/sshtools/common/ui/remove.png differ
diff --git a/src/com/sshtools/common/ui/save.png b/src/com/sshtools/common/ui/save.png
new file mode 100644
index 0000000000000000000000000000000000000000..8dc9a7a872cf391d7459b7b39e1bf9d823cacc8d
Binary files /dev/null and b/src/com/sshtools/common/ui/save.png differ
diff --git a/src/com/sshtools/common/ui/stop.png b/src/com/sshtools/common/ui/stop.png
new file mode 100644
index 0000000000000000000000000000000000000000..06614ee7e6d1a68e380ca126fb675fd5ba84c81b
Binary files /dev/null and b/src/com/sshtools/common/ui/stop.png differ
diff --git a/src/com/sshtools/common/util/BrowserLauncher.java b/src/com/sshtools/common/util/BrowserLauncher.java
new file mode 100644
index 0000000000000000000000000000000000000000..82049d5b6dd25ebd4e9aff1c568e5173963eeeaa
--- /dev/null
+++ b/src/com/sshtools/common/util/BrowserLauncher.java
@@ -0,0 +1,442 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.util;
+
+import java.io.*;
+
+import java.lang.reflect.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class BrowserLauncher {
+    private static int jvm;
+    private static Object browser;
+    private static boolean loadedWithoutErrors;
+    private static Class mrjFileUtilsClass;
+    private static Class mrjOSTypeClass;
+    private static Class macOSErrorClass;
+    private static Class aeDescClass;
+    private static Constructor aeTargetConstructor;
+    private static Constructor appleEventConstructor;
+    private static Constructor aeDescConstructor;
+    private static Method findFolder;
+    private static Method getFileType;
+    private static Method makeOSType;
+    private static Method putParameter;
+    private static Method sendNoReply;
+    private static Object kSystemFolderType;
+    private static Integer keyDirectObject;
+    private static Integer kAutoGenerateReturnID;
+    private static Integer kAnyTransactionID;
+    private static final int MRJ_2_0 = 0;
+    private static final int MRJ_2_1 = 1;
+    private static final int WINDOWS_NT = 2;
+    private static final int WINDOWS_9x = 3;
+    private static final int OTHER = -1;
+    private static final String FINDER_TYPE = "FNDR";
+    private static final String FINDER_CREATOR = "MACS";
+    private static final String GURL_EVENT = "GURL";
+    private static final String FIRST_WINDOWS_PARAMETER = "/c";
+    private static final String SECOND_WINDOWS_PARAMETER = "start";
+    private static final String NETSCAPE_OPEN_PARAMETER_START = " -remote 'openURL(";
+    private static final String NETSCAPE_OPEN_PARAMETER_END = ")'";
+    private static String errorMessage;
+
+    static {
+        loadedWithoutErrors = true;
+
+        String osName = System.getProperty("os.name");
+
+        if ("Mac OS".equals(osName)) {
+            String mrjVersion = System.getProperty("mrj.version");
+            String majorMRJVersion = mrjVersion.substring(0, 3);
+
+            try {
+                double version = Double.valueOf(majorMRJVersion).doubleValue();
+
+                if (version == 2) {
+                    jvm = MRJ_2_0;
+                } else if (version >= 2.1) {
+                    // For the time being, assume that all post-2.0 versions of MRJ work the same
+                    jvm = MRJ_2_1;
+                } else {
+                    loadedWithoutErrors = false;
+                    errorMessage = "Unsupported MRJ version: " + version;
+                }
+            } catch (NumberFormatException nfe) {
+                loadedWithoutErrors = false;
+                errorMessage = "Invalid MRJ version: " + mrjVersion;
+            }
+        } else if (osName.startsWith("Windows")) {
+            if (osName.indexOf("9") != -1) {
+                jvm = WINDOWS_9x;
+            } else {
+                jvm = WINDOWS_NT;
+            }
+        } else {
+            jvm = OTHER;
+        }
+
+        if (loadedWithoutErrors) { // if we haven't hit any errors yet
+            loadedWithoutErrors = loadClasses();
+        }
+    }
+
+    private BrowserLauncher() {
+    }
+
+    private static boolean loadClasses() {
+        switch (jvm) {
+        case MRJ_2_0:
+
+            try {
+                Class aeTargetClass = Class.forName("com.apple.MacOS.AETarget");
+                macOSErrorClass = Class.forName("com.apple.MacOS.MacOSError");
+
+                Class osUtilsClass = Class.forName("com.apple.MacOS.OSUtils");
+                Class appleEventClass = Class.forName(
+                        "com.apple.MacOS.AppleEvent");
+                Class aeClass = Class.forName("com.apple.MacOS.ae");
+                aeDescClass = Class.forName("com.apple.MacOS.AEDesc");
+                aeTargetConstructor = aeTargetClass.getDeclaredConstructor(new Class[] {
+                            int.class
+                        });
+                appleEventConstructor = appleEventClass.getDeclaredConstructor(new Class[] {
+                            int.class, int.class, aeTargetClass, int.class,
+                            int.class
+                        });
+                aeDescConstructor = aeDescClass.getDeclaredConstructor(new Class[] {
+                            String.class
+                        });
+                makeOSType = osUtilsClass.getDeclaredMethod("makeOSType",
+                        new Class[] { String.class });
+                putParameter = appleEventClass.getDeclaredMethod("putParameter",
+                        new Class[] { int.class, aeDescClass });
+                sendNoReply = appleEventClass.getDeclaredMethod("sendNoReply",
+                        new Class[] {  });
+
+                Field keyDirectObjectField = aeClass.getDeclaredField(
+                        "keyDirectObject");
+                keyDirectObject = (Integer) keyDirectObjectField.get(null);
+
+                Field autoGenerateReturnIDField = appleEventClass.getDeclaredField(
+                        "kAutoGenerateReturnID");
+                kAutoGenerateReturnID = (Integer) autoGenerateReturnIDField.get(null);
+
+                Field anyTransactionIDField = appleEventClass.getDeclaredField(
+                        "kAnyTransactionID");
+                kAnyTransactionID = (Integer) anyTransactionIDField.get(null);
+            } catch (ClassNotFoundException cnfe) {
+                errorMessage = cnfe.getMessage();
+
+                return false;
+            } catch (NoSuchMethodException nsme) {
+                errorMessage = nsme.getMessage();
+
+                return false;
+            } catch (NoSuchFieldException nsfe) {
+                errorMessage = nsfe.getMessage();
+
+                return false;
+            } catch (IllegalAccessException iae) {
+                errorMessage = iae.getMessage();
+
+                return false;
+            }
+
+            break;
+
+        case MRJ_2_1:
+
+            try {
+                mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils");
+                mrjOSTypeClass = Class.forName("com.apple.mrj.MRJOSType");
+
+                Field systemFolderField = mrjFileUtilsClass.getDeclaredField(
+                        "kSystemFolderType");
+                kSystemFolderType = systemFolderField.get(null);
+                findFolder = mrjFileUtilsClass.getDeclaredMethod("findFolder",
+                        new Class[] { mrjOSTypeClass });
+                getFileType = mrjFileUtilsClass.getDeclaredMethod("getFileType",
+                        new Class[] { File.class });
+            } catch (ClassNotFoundException cnfe) {
+                errorMessage = cnfe.getMessage();
+
+                return false;
+            } catch (NoSuchFieldException nsfe) {
+                errorMessage = nsfe.getMessage();
+
+                return false;
+            } catch (NoSuchMethodException nsme) {
+                errorMessage = nsme.getMessage();
+
+                return false;
+            } catch (SecurityException se) {
+                errorMessage = se.getMessage();
+
+                return false;
+            } catch (IllegalAccessException iae) {
+                errorMessage = iae.getMessage();
+
+                return false;
+            }
+
+            break;
+        }
+
+        return true;
+    }
+
+    private static Object locateBrowser() {
+        if (browser != null) {
+            return browser;
+        }
+
+        switch (jvm) {
+        case MRJ_2_0:
+
+            try {
+                Integer finderCreatorCode = (Integer) makeOSType.invoke(null,
+                        new Object[] { FINDER_CREATOR });
+                Object aeTarget = aeTargetConstructor.newInstance(new Object[] {
+                            finderCreatorCode
+                        });
+                Integer gurlType = (Integer) makeOSType.invoke(null,
+                        new Object[] { GURL_EVENT });
+                Object appleEvent = appleEventConstructor.newInstance(new Object[] {
+                            gurlType, gurlType, aeTarget, kAutoGenerateReturnID,
+                            kAnyTransactionID
+                        });
+
+                // Don't set browser = appleEvent because then the next time we call
+                // locateBrowser(), we'll get the same AppleEvent, to which we'll already have
+                // added the relevant parameter. Instead, regenerate the AppleEvent every time.
+                // There's probably a way to do this better; if any has any ideas, please let
+                // me know.
+                return appleEvent;
+            } catch (IllegalAccessException iae) {
+                browser = null;
+                errorMessage = iae.getMessage();
+
+                return browser;
+            } catch (InstantiationException ie) {
+                browser = null;
+                errorMessage = ie.getMessage();
+
+                return browser;
+            } catch (InvocationTargetException ite) {
+                browser = null;
+                errorMessage = ite.getMessage();
+
+                return browser;
+            }
+
+        case MRJ_2_1:
+
+            File systemFolder;
+
+            try {
+                systemFolder = (File) findFolder.invoke(null,
+                        new Object[] { kSystemFolderType });
+            } catch (IllegalArgumentException iare) {
+                browser = null;
+                errorMessage = iare.getMessage();
+
+                return browser;
+            } catch (IllegalAccessException iae) {
+                browser = null;
+                errorMessage = iae.getMessage();
+
+                return browser;
+            } catch (InvocationTargetException ite) {
+                browser = null;
+                errorMessage = ite.getTargetException().getClass() + ": " +
+                    ite.getTargetException().getMessage();
+
+                return browser;
+            }
+
+            String[] systemFolderFiles = systemFolder.list();
+
+            // Avoid a FilenameFilter because that can't be stopped mid-list
+            for (int i = 0; i < systemFolderFiles.length; i++) {
+                try {
+                    File file = new File(systemFolder, systemFolderFiles[i]);
+
+                    if (!file.isFile()) {
+                        continue;
+                    }
+
+                    Object fileType = getFileType.invoke(null,
+                            new Object[] { file });
+
+                    if (FINDER_TYPE.equals(fileType.toString())) {
+                        browser = file.toString(); // Actually the Finder, but that's OK
+
+                        return browser;
+                    }
+                } catch (IllegalArgumentException iare) {
+                    browser = browser;
+                    errorMessage = iare.getMessage();
+
+                    return null;
+                } catch (IllegalAccessException iae) {
+                    browser = null;
+                    errorMessage = iae.getMessage();
+
+                    return browser;
+                } catch (InvocationTargetException ite) {
+                    browser = null;
+                    errorMessage = ite.getTargetException().getClass() + ": " +
+                        ite.getTargetException().getMessage();
+
+                    return browser;
+                }
+            }
+
+            browser = null;
+
+            break;
+
+        case WINDOWS_NT:
+            browser = "cmd.exe";
+
+            break;
+
+        case WINDOWS_9x:
+            browser = "command.com";
+
+            break;
+
+        case OTHER:default:
+
+            //browser = "netscape"; surely mozilla is the thing these days
+            browser = "mozilla";
+
+            break;
+        }
+
+        return browser;
+    }
+
+    /**
+*
+*
+* @param url
+*
+* @throws IOException
+*/
+    public static void openURL(String url) throws IOException {
+        if (!loadedWithoutErrors) {
+            throw new IOException("Exception in finding browser: " +
+                errorMessage);
+        }
+
+        Object browser = locateBrowser();
+
+        if (browser == null) {
+            throw new IOException("Unable to locate browser: " + errorMessage);
+        }
+
+        switch (jvm) {
+        case MRJ_2_0:
+
+            Object aeDesc = null;
+
+            try {
+                aeDesc = aeDescConstructor.newInstance(new Object[] { url });
+                putParameter.invoke(browser,
+                    new Object[] { keyDirectObject, aeDesc });
+                sendNoReply.invoke(browser, new Object[] {  });
+            } catch (InvocationTargetException ite) {
+                throw new IOException(
+                    "InvocationTargetException while creating AEDesc: " +
+                    ite.getMessage());
+            } catch (IllegalAccessException iae) {
+                throw new IOException(
+                    "IllegalAccessException while building AppleEvent: " +
+                    iae.getMessage());
+            } catch (InstantiationException ie) {
+                throw new IOException(
+                    "InstantiationException while creating AEDesc: " +
+                    ie.getMessage());
+            } finally {
+                aeDesc = null; // Encourage it to get disposed if it was created
+                browser = null; // Ditto
+            }
+
+            break;
+
+        case MRJ_2_1:
+            Runtime.getRuntime().exec(new String[] { (String) browser, url });
+
+            break;
+
+        case WINDOWS_NT:
+        case WINDOWS_9x:
+            Runtime.getRuntime().exec(new String[] {
+                    (String) browser, FIRST_WINDOWS_PARAMETER,
+                    SECOND_WINDOWS_PARAMETER, url
+                });
+
+            break;
+
+        case OTHER:
+
+            // Assume that we're on Unix and that Netscape is installed
+            // First, attempt to open the URL in a currently running session of Netscape
+            Process process = Runtime.getRuntime().exec((String) browser +
+                    NETSCAPE_OPEN_PARAMETER_START + url +
+                    NETSCAPE_OPEN_PARAMETER_END);
+
+            try {
+                int exitCode = process.waitFor();
+
+                if (exitCode != 0) { // if Netscape was not open
+                    Runtime.getRuntime().exec(new String[] { (String) browser, url });
+                }
+            } catch (InterruptedException ie) {
+                throw new IOException(
+                    "InterruptedException while launching browser: " +
+                    ie.getMessage());
+            }
+
+            break;
+
+        default:
+
+            // This should never occur, but if it does, we'll try the simplest thing possible
+            Runtime.getRuntime().exec(new String[] { (String) browser, url });
+
+            break;
+        }
+    }
+}
diff --git a/src/com/sshtools/common/util/GeneralUtil.java b/src/com/sshtools/common/util/GeneralUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..b7d4f3e71b9afd9386a09f3654180be5485e66f2
--- /dev/null
+++ b/src/com/sshtools/common/util/GeneralUtil.java
@@ -0,0 +1,148 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.util;
+
+import java.awt.*;
+
+
+/**
+ * Other utilities not worth their own class
+ */
+public final class GeneralUtil {
+    /**
+* This method will replace '&' with "&amp;", '"' with "&quot;", '<' with "&lt;" and '>' with "&gt;".
+*
+* @param html html to encode
+* @return encoded html
+*/
+    public static String encodeHTML(String html) {
+        // Does java have a method of doing this?
+        StringBuffer buf = new StringBuffer();
+        char ch;
+
+        for (int i = 0; i < html.length(); i++) {
+            ch = html.charAt(i);
+
+            switch (ch) {
+            case '&':
+
+                //	May be already encoded
+                if (((i + 5) < html.length()) &&
+                        html.substring(i + 1, i + 5).equals("amp;")) {
+                    buf.append(ch);
+                } else {
+                    buf.append("&amp;");
+                }
+
+                break;
+
+            case '"':
+                buf.append("&quot;");
+
+                break;
+
+            case '<':
+                buf.append("&lt;");
+
+                break;
+
+            case '>':
+                buf.append("&gt;");
+
+                break;
+
+            default:
+                buf.append(ch);
+            }
+        }
+
+        return buf.toString();
+    }
+
+    /**
+* Return a <code>Color</code> object given a string representation of it
+*
+* @param color
+* @return string representation
+* @throws IllegalArgumentException if string in bad format
+*/
+    public static Color stringToColor(String s) {
+        try {
+            return new Color(Integer.decode("0x" + s.substring(1, 3)).intValue(),
+                Integer.decode("0x" + s.substring(3, 5)).intValue(),
+                Integer.decode("0x" + s.substring(5, 7)).intValue());
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+* Return a string representation of a color
+*
+* @param color
+* @return string representation
+*/
+    public static String colorToString(Color color) {
+        if (color == null) {
+            return "";
+        }
+
+        StringBuffer buf = new StringBuffer();
+        buf.append('#');
+        buf.append(numberToPaddedHexString(color.getRed(), 2));
+        buf.append(numberToPaddedHexString(color.getGreen(), 2));
+        buf.append(numberToPaddedHexString(color.getBlue(), 2));
+
+        return buf.toString();
+    }
+
+    /**
+* Convert a number to a zero padded hex string
+*
+* @param int number
+* @return zero padded hex string
+* @throws IllegalArgumentException if number takes up more characters than
+*                                  <code>size</code>
+*/
+    public static String numberToPaddedHexString(int number, int size) {
+        String s = Integer.toHexString(number);
+
+        if (s.length() > size) {
+            throw new IllegalArgumentException(
+                "Number too big for padded hex string");
+        }
+
+        StringBuffer buf = new StringBuffer();
+
+        for (int i = 0; i < (size - s.length()); i++) {
+            buf.append('0');
+        }
+
+        buf.append(s);
+
+        return buf.toString();
+    }
+}
diff --git a/src/com/sshtools/common/util/JVMUtil.java b/src/com/sshtools/common/util/JVMUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..14cedf294dedce1e23d3fe7548408e47cdad9986
--- /dev/null
+++ b/src/com/sshtools/common/util/JVMUtil.java
@@ -0,0 +1,67 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.util;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class JVMUtil {
+    /**
+*
+*
+* @return
+*/
+    public static int getMajorVersion() {
+        return 1;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public static int getMinorVersion() {
+        return 4;
+    }
+
+    /**
+*
+*
+* @param args
+*/
+    public static void main(String[] args) {
+        System.getProperties().list(System.out);
+        System.out.println("Major=" + getMajorVersion());
+        System.out.println("Minor=" + getMinorVersion());
+    }
+}
+
+
+// end class Base64
diff --git a/src/com/sshtools/common/util/PropertyUtil.java b/src/com/sshtools/common/util/PropertyUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..c0c4ee935738555a10edaa7e5d93cffac5b8f0cb
--- /dev/null
+++ b/src/com/sshtools/common/util/PropertyUtil.java
@@ -0,0 +1,156 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.util;
+
+import java.awt.*;
+
+import java.util.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class PropertyUtil {
+    /**
+*
+*
+* @param number
+* @param defaultValue
+*
+* @return
+*/
+    public static int stringToInt(String number, int defaultValue) {
+        try {
+            return (number == null) ? defaultValue : Integer.parseInt(number);
+        } catch (NumberFormatException nfe) {
+            return defaultValue;
+        }
+    }
+
+    /**
+*
+*
+* @param color
+*
+* @return
+*/
+    public static String colorToString(Color color) {
+        StringBuffer buf = new StringBuffer();
+        buf.append('#');
+        buf.append(numberToPaddedHexString(color.getRed(), 2));
+        buf.append(numberToPaddedHexString(color.getGreen(), 2));
+        buf.append(numberToPaddedHexString(color.getBlue(), 2));
+
+        return buf.toString();
+    }
+
+    /**
+*
+*
+* @param font
+*
+* @return
+*/
+    public static String fontToString(Font font) {
+        StringBuffer b = new StringBuffer(font.getName());
+        b.append(",");
+        b.append(font.getStyle());
+        b.append(",");
+        b.append(font.getSize());
+
+        return b.toString();
+    }
+
+    /**
+*
+*
+* @param fontString
+*
+* @return
+*/
+    public static Font stringToFont(String fontString) {
+        StringTokenizer st = new StringTokenizer(fontString, ",");
+
+        try {
+            return new Font(st.nextToken(), Integer.parseInt(st.nextToken()),
+                Integer.parseInt(st.nextToken()));
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+*
+*
+* @param s
+*
+* @return
+*
+* @throws IllegalArgumentException
+*/
+    public static Color stringToColor(String s) {
+        try {
+            return new Color(Integer.decode("0x" + s.substring(1, 3)).intValue(),
+                Integer.decode("0x" + s.substring(3, 5)).intValue(),
+                Integer.decode("0x" + s.substring(5, 7)).intValue());
+        } catch (Exception e) {
+            throw new IllegalArgumentException(
+                "Bad color string format. Should be #rrggbb ");
+        }
+    }
+
+    /**
+*
+*
+* @param number
+* @param size
+*
+* @return
+*
+* @throws IllegalArgumentException
+*/
+    public static String numberToPaddedHexString(int number, int size) {
+        String s = Integer.toHexString(number);
+
+        if (s.length() > size) {
+            throw new IllegalArgumentException(
+                "Number too big for padded hex string");
+        }
+
+        StringBuffer buf = new StringBuffer();
+
+        for (int i = 0; i < (size - s.length()); i++) {
+            buf.append('0');
+        }
+
+        buf.append(s);
+
+        return buf.toString();
+    }
+}
diff --git a/src/com/sshtools/common/util/Search.java b/src/com/sshtools/common/util/Search.java
new file mode 100644
index 0000000000000000000000000000000000000000..a29737a8774fa316c7830c7b2ceb59ca5c13a4a1
--- /dev/null
+++ b/src/com/sshtools/common/util/Search.java
@@ -0,0 +1,70 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.util;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.8 $
+ */
+public class Search {
+    /**
+*
+*
+* @param str
+* @param query
+*
+* @return
+*
+* @throws IllegalArgumentException
+*/
+    public static boolean matchesWildcardQuery(String str, String query)
+        throws IllegalArgumentException {
+        int idx = query.indexOf("*");
+
+        if (idx > -1) {
+            // We have a wildcard search
+            if ((idx > 0) && (idx < (query.length() - 1))) {
+                throw new IllegalArgumentException(
+                    "Wildcards not supported in middle of query string; use either 'searchtext*' or '*searchtext'");
+            }
+
+            if (idx == (query.length() - 1)) {
+                return str.startsWith(query.substring(0, idx));
+            } else {
+                return str.endsWith(query.substring(idx + 1));
+            }
+        } else {
+            if (str.equalsIgnoreCase(query)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}
diff --git a/src/com/sshtools/common/util/UID.java b/src/com/sshtools/common/util/UID.java
new file mode 100644
index 0000000000000000000000000000000000000000..16c6bcb333e161472fb891fa04b901613f87ef66
--- /dev/null
+++ b/src/com/sshtools/common/util/UID.java
@@ -0,0 +1,172 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.util;
+
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+
+import java.io.ByteArrayInputStream;
+
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
+
+import java.util.Arrays;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.8 $
+ */
+public class UID {
+    byte[] uid;
+
+    private UID(String str) throws UIDException {
+        if (str == null) {
+            throw new UIDException("UID cannot be NULL");
+        }
+
+        try {
+            uid = new byte[str.length() / 2];
+
+            String tmp;
+            int pos = 0;
+
+            for (int i = 0; i < str.length(); i += 2) {
+                tmp = str.substring(i, i + 2);
+                uid[pos++] = (byte) Integer.parseInt(tmp, 16);
+            }
+        } catch (NumberFormatException ex) {
+            throw new UIDException("Failed to parse UID String: " +
+                ex.getMessage());
+        }
+    }
+
+    private UID(byte[] uid) {
+        this.uid = uid;
+    }
+
+    /**
+*
+*
+* @param content
+*
+* @return
+*
+* @throws UIDException
+*/
+    public static UID generateUniqueId(byte[] content)
+        throws UIDException {
+        try {
+            // Create a uniqiue identifier from the content and some random data
+            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
+
+            if (content != null) {
+                ByteArrayInputStream in = new ByteArrayInputStream(content);
+                DigestInputStream dis = new DigestInputStream(in, messageDigest);
+
+                while (dis.read() != -1) {
+                    ;
+                }
+
+                dis.close();
+                in.close();
+            }
+
+            // Add some random noise so the id is not generated soley by the
+            // file content
+            byte[] noise = new byte[1024];
+            ConfigurationLoader.getRND().nextBytes(noise);
+            messageDigest.update(noise);
+
+            // Generate the id
+            UID uid = new UID(messageDigest.digest());
+
+            return uid;
+        } catch (Exception ex) {
+            throw new UIDException("Failed to generate a unique identifier: " +
+                ex.getMessage());
+        }
+    }
+
+    /**
+*
+*
+* @param uid
+*
+* @return
+*
+* @throws UIDException
+*/
+    public static UID fromString(String uid) throws UIDException {
+        return new UID(uid);
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public String toString() {
+        StringBuffer checksumSb = new StringBuffer();
+
+        for (int i = 0; i < uid.length; i++) {
+            String hexStr = Integer.toHexString(0x00ff & uid[i]);
+
+            if (hexStr.length() < 2) {
+                checksumSb.append("0");
+            }
+
+            checksumSb.append(hexStr);
+        }
+
+        return checksumSb.toString();
+    }
+
+    /**
+*
+*
+* @param obj
+*
+* @return
+*/
+    public boolean equals(Object obj) {
+        if ((obj != null) && obj instanceof UID) {
+            return Arrays.equals(uid, ((UID) obj).uid);
+        }
+
+        return false;
+    }
+
+    /**
+*
+*
+* @return
+*/
+    public int hashCode() {
+        return toString().hashCode();
+    }
+}
diff --git a/src/com/sshtools/common/util/UIDException.java b/src/com/sshtools/common/util/UIDException.java
new file mode 100644
index 0000000000000000000000000000000000000000..d6abf23a0719287d57fa28c3019e2d9f80dab5ba
--- /dev/null
+++ b/src/com/sshtools/common/util/UIDException.java
@@ -0,0 +1,44 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.util;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.8 $
+ */
+public class UIDException extends Exception {
+    /**
+* Creates a new UIDException object.
+*
+* @param msg
+*/
+    public UIDException(String msg) {
+        super(msg);
+    }
+}
diff --git a/src/com/sshtools/common/util/X11Util.java b/src/com/sshtools/common/util/X11Util.java
new file mode 100644
index 0000000000000000000000000000000000000000..e475e0d8625f7eac6db89da22bc2336332ca8d16
--- /dev/null
+++ b/src/com/sshtools/common/util/X11Util.java
@@ -0,0 +1,146 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.common.util;
+
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class X11Util {
+    // Logger
+
+    /**  */
+    protected static Log log = LogFactory.getLog(X11Util.class);
+    static byte[] table = {
+        0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62,
+        0x63, 0x64, 0x65, 0x66
+    };
+
+    /**
+*
+*
+* @param displayNumber
+*
+* @return
+*
+* @throws IOException
+*/
+    public static String getCookie(int displayNumber) throws IOException {
+        log.debug("Getting cookie for " + displayNumber + " using xauth");
+
+        Process process = null;
+        InputStream in = null;
+        InputStream err = null;
+        OutputStream out = null;
+
+        //  try {
+        byte[] foo = new byte[16];
+        ConfigurationLoader.getRND().nextBytes(foo);
+
+        byte[] bar = new byte[32];
+
+        for (int i = 0; i < 16; i++) {
+            bar[2 * i] = table[(foo[i] >>> 4) & 0xf];
+            bar[(2 * i) + 1] = table[(foo[i]) & 0xf];
+        }
+
+        return new String(bar);
+
+        /*    String cmd = "xauth list :" + displayNumber;
+log.debug("Executing " + cmd);
+process = Runtime.getRuntime().exec(cmd);
+IOStreamConnector connect = new IOStreamConnector(
+err = process.getErrorStream(), System.out);
+BufferedReader reader = new BufferedReader(
+new InputStreamReader(in = process.getInputStream()));
+out = process.getOutputStream();
+String line = null;
+String cookie = null;
+while( ( line = reader.readLine() ) != null) {
+log.debug(line);
+StringTokenizer t = new StringTokenizer(line);
+try {
+    String host = t.nextToken();
+    String type = t.nextToken();
+    String value = t.nextToken();
+    if(cookie == null) {
+        cookie = value;
+        log.debug("Using cookie " + cookie);
+    }
+}
+catch(Exception e) {
+    log.error("Unexpected response from xauth.", e);
+}
+}
+return cookie;
+ }
+ finally {
+IOUtil.closeStream(in);
+IOUtil.closeStream(err);
+IOUtil.closeStream(out);
+ }*/
+    }
+
+    /**
+*
+*
+* @param displayNumber
+*
+* @return
+*/
+    public static String createCookie(String displayNumber) {
+        log.error("Creating fake cookie");
+
+        StringBuffer b = new StringBuffer();
+
+        for (int i = 0; i < 16; i++) {
+            int r = (int) (Math.random() * 256);
+            String h = Integer.toHexString(r);
+
+            if (h.length() == 1) {
+                b.append(0);
+            }
+
+            b.append(h);
+        }
+
+        log.error("Fake cookie is " + b.toString());
+
+        return b.toString();
+    }
+}
diff --git a/src/com/sshtools/daemon/SshDaemon.java b/src/com/sshtools/daemon/SshDaemon.java
new file mode 100644
index 0000000000000000000000000000000000000000..4295832bd769d8d3d66d0255f0a59644f5df09ac
--- /dev/null
+++ b/src/com/sshtools/daemon/SshDaemon.java
@@ -0,0 +1,156 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon;
+
+import com.sshtools.common.configuration.*;
+
+import com.sshtools.daemon.configuration.*;
+import com.sshtools.daemon.forwarding.*;
+import com.sshtools.daemon.session.*;
+
+import com.sshtools.j2ssh.configuration.*;
+import com.sshtools.j2ssh.connection.*;
+
+import org.apache.commons.logging.*;
+
+import java.io.*;
+
+import java.net.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.17 $
+ */
+public class SshDaemon {
+    private static Log log = LogFactory.getLog(SshDaemon.class);
+
+    /**
+ *
+ *
+ * @param args
+ */
+    public static void main(String[] args) {
+        try {
+            XmlServerConfigurationContext context = new XmlServerConfigurationContext();
+            context.setServerConfigurationResource(ConfigurationLoader.checkAndGetProperty(
+                    "sshtools.server", "server.xml"));
+            context.setPlatformConfigurationResource(System.getProperty(
+                    "sshtools.platform", "platform.xml"));
+            ConfigurationLoader.initialize(false, context);
+
+            XmlConfigurationContext context2 = new XmlConfigurationContext();
+            context2.setFailOnError(false);
+            context2.setAPIConfigurationResource(ConfigurationLoader.checkAndGetProperty(
+                    "sshtools.config", "sshtools.xml"));
+            context2.setAutomationConfigurationResource(ConfigurationLoader.checkAndGetProperty(
+                    "sshtools.automate", "automation.xml"));
+            ConfigurationLoader.initialize(false, context2);
+
+            if (args.length > 0) {
+                if (args[0].equals("-start")) {
+                    start();
+                } else if (args[0].equals("-stop")) {
+                    if (args.length > 1) {
+                        stop(args[1]);
+                    } else {
+                        stop("The framework daemon is shutting down");
+                    }
+                } else {
+                    System.out.println("Usage: SshDaemon [-start|-stop]");
+                }
+            } else {
+                System.out.println("Usage: SshDaemon [-start|-stop]");
+            }
+        } catch (Exception e) {
+            log.error("The server failed to process the " +
+                ((args.length > 0) ? args[0] : "") + " command", e);
+        }
+    }
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    public static void start() throws IOException {
+        // We need at least one host key
+        SshServer server = new SshServer() {
+                public void configureServices(ConnectionProtocol connection)
+                    throws IOException {
+                    connection.addChannelFactory(SessionChannelFactory.SESSION_CHANNEL,
+                        new SessionChannelFactory());
+
+                    if (ConfigurationLoader.isConfigurationAvailable(
+                                ServerConfiguration.class)) {
+                        if (((ServerConfiguration) ConfigurationLoader.getConfiguration(
+                                    ServerConfiguration.class)).getAllowTcpForwarding()) {
+                            ForwardingServer forwarding = new ForwardingServer(connection);
+                        }
+                    }
+                }
+
+                public void shutdown(String msg) {
+                    // Disconnect all sessions
+                }
+            };
+
+        server.startServer();
+    }
+
+    /**
+ *
+ *
+ * @param msg
+ *
+ * @throws IOException
+ */
+    public static void stop(String msg) throws IOException {
+        try {
+            Socket socket = new Socket(InetAddress.getLocalHost(),
+                    ((ServerConfiguration) ConfigurationLoader.getConfiguration(
+                        ServerConfiguration.class)).getCommandPort());
+
+            // Write the command id
+            socket.getOutputStream().write(0x3a);
+
+            // Write the length of the message (max 255)
+            int len = (msg.length() <= 255) ? msg.length() : 255;
+            socket.getOutputStream().write(len);
+
+            // Write the message
+            if (len > 0) {
+                socket.getOutputStream().write(msg.substring(0, len).getBytes());
+            }
+
+            socket.close();
+        } catch (IOException ioe) {
+            ioe.printStackTrace();
+        }
+    }
+}
diff --git a/src/com/sshtools/daemon/SshServer.java b/src/com/sshtools/daemon/SshServer.java
new file mode 100644
index 0000000000000000000000000000000000000000..70ad6e538427ae637bd7df3c530d825695c17b05
--- /dev/null
+++ b/src/com/sshtools/daemon/SshServer.java
@@ -0,0 +1,394 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon;
+
+import com.sshtools.daemon.authentication.*;
+import com.sshtools.daemon.configuration.*;
+import com.sshtools.daemon.transport.*;
+
+import com.sshtools.j2ssh.*;
+import com.sshtools.j2ssh.configuration.*;
+import com.sshtools.j2ssh.connection.*;
+import com.sshtools.j2ssh.net.*;
+import com.sshtools.j2ssh.transport.*;
+import com.sshtools.j2ssh.util.*;
+
+import org.apache.commons.logging.*;
+
+import java.io.*;
+
+import java.net.*;
+
+import java.util.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.18 $
+ */
+public abstract class SshServer {
+    private static Log log = LogFactory.getLog(SshServer.class);
+    private ConnectionListener listener = null;
+    private ServerSocket server = null;
+    private boolean shutdown = false;
+    private ServerSocket commandServerSocket;
+
+    /**  */
+    protected List activeConnections = new Vector();
+    Thread thread;
+
+    /**
+ * Creates a new SshServer object.
+ *
+ * @throws IOException
+ * @throws SshException
+ */
+    public SshServer() throws IOException {
+        String serverId = System.getProperty("sshtools.serverid");
+
+        if (serverId != null) {
+            TransportProtocolServer.SOFTWARE_VERSION_COMMENTS = serverId;
+        }
+
+        if (!ConfigurationLoader.isConfigurationAvailable(
+                    ServerConfiguration.class)) {
+            throw new SshException("Server configuration not available!");
+        }
+
+        if (!ConfigurationLoader.isConfigurationAvailable(
+                    PlatformConfiguration.class)) {
+            throw new SshException("Platform configuration not available");
+        }
+
+        if (((ServerConfiguration) ConfigurationLoader.getConfiguration(
+                    ServerConfiguration.class)).getServerHostKeys().size() <= 0) {
+            throw new SshException(
+                "Server cannot start because there are no server host keys available");
+        }
+    }
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    public void startServer() throws IOException {
+        log.info("Starting server");
+        shutdown = false;
+        startServerSocket();
+        thread = new Thread(new Runnable() {
+                    public void run() {
+                        try {
+                            startCommandSocket();
+                        } catch (IOException ex) {
+                            log.info("Failed to start command socket", ex);
+
+                            try {
+                                stopServer("The command socket failed to start");
+                            } catch (IOException ex1) {
+                            }
+                        }
+                    }
+                });
+        thread.start();
+
+        try {
+            thread.join();
+        } catch (InterruptedException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+
+    /**
+ *
+ *
+ * @param command
+ * @param client
+ *
+ * @throws IOException
+ */
+    protected void processCommand(int command, Socket client)
+        throws IOException {
+        if (command == 0x3a) {
+            int len = client.getInputStream().read();
+            byte[] msg = new byte[len];
+            client.getInputStream().read(msg);
+            stopServer(new String(msg));
+        }
+    }
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    protected void startCommandSocket() throws IOException {
+        try {
+            commandServerSocket = new ServerSocket(((ServerConfiguration) ConfigurationLoader.getConfiguration(
+                        ServerConfiguration.class)).getCommandPort(), 50,
+                    InetAddress.getLocalHost());
+
+            Socket client;
+
+            while ((client = commandServerSocket.accept()) != null) {
+                log.info("Command request received");
+
+                // Read and process the command
+                processCommand(client.getInputStream().read(), client);
+                client.close();
+
+                if (shutdown) {
+                    break;
+                }
+            }
+
+            commandServerSocket.close();
+        } catch (Exception e) {
+            if (!shutdown) {
+                log.fatal("The command socket failed", e);
+            }
+        }
+    }
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    protected void startServerSocket() throws IOException {
+        listener = new ConnectionListener(((ServerConfiguration) ConfigurationLoader.getConfiguration(
+                    ServerConfiguration.class)).getListenAddress(),
+                ((ServerConfiguration) ConfigurationLoader.getConfiguration(
+                    ServerConfiguration.class)).getPort());
+        listener.start();
+    }
+
+    /**
+ *
+ *
+ * @param msg
+ *
+ * @throws IOException
+ */
+    public void stopServer(String msg) throws IOException {
+        log.info("Shutting down server");
+        shutdown = true;
+        log.debug(msg);
+        shutdown(msg);
+        listener.stop();
+        log.debug("Stopping command server");
+
+        try {
+            if (commandServerSocket != null) {
+                commandServerSocket.close();
+            }
+        } catch (IOException ioe) {
+            log.error(ioe);
+        }
+    }
+
+    /**
+ *
+ *
+ * @param msg
+ */
+    protected abstract void shutdown(String msg);
+
+    /**
+ *
+ *
+ * @param connection
+ *
+ * @throws IOException
+ */
+    protected abstract void configureServices(ConnectionProtocol connection)
+        throws IOException;
+
+    /**
+ *
+ *
+ * @param socket
+ *
+ * @throws IOException
+ */
+    protected void refuseSession(Socket socket) throws IOException {
+        TransportProtocolServer transport = new TransportProtocolServer(true);
+        transport.startTransportProtocol(new ConnectedSocketTransportProvider(
+                socket), new SshConnectionProperties());
+    }
+
+    /**
+ *
+ *
+ * @param socket
+ *
+ * @return
+ *
+ * @throws IOException
+ */
+    protected TransportProtocolServer createSession(Socket socket)
+        throws IOException {
+        log.debug("Initializing connection");
+
+        InetAddress address = socket.getInetAddress();
+
+        /*( (InetSocketAddress) socket
+     .getRemoteSocketAddress()).getAddress();*/
+        log.debug("Remote Hostname: " + address.getHostName());
+        log.debug("Remote IP: " + address.getHostAddress());
+
+        TransportProtocolServer transport = new TransportProtocolServer();
+
+        // Create the Authentication Protocol
+        AuthenticationProtocolServer authentication = new AuthenticationProtocolServer();
+
+        // Create the Connection Protocol
+        ConnectionProtocol connection = new ConnectionProtocol();
+
+        // Configure the connections services
+        configureServices(connection);
+
+        // Allow the Connection Protocol to be accepted by the Authentication Protocol
+        authentication.acceptService(connection);
+
+        // Allow the Authentication Protocol to be accepted by the Transport Protocol
+        transport.acceptService(authentication);
+        transport.startTransportProtocol(new ConnectedSocketTransportProvider(
+                socket), new SshConnectionProperties());
+
+        return transport;
+    }
+
+    class ConnectionListener implements Runnable {
+        private Log log = LogFactory.getLog(ConnectionListener.class);
+        private ServerSocket server;
+        private String listenAddress;
+        private Thread thread;
+        private int maxConnections;
+        private int port;
+        private StartStopState state = new StartStopState(StartStopState.STOPPED);
+
+        public ConnectionListener(String listenAddress, int port) {
+            this.port = port;
+            this.listenAddress = listenAddress;
+        }
+
+        public void run() {
+            try {
+                log.debug("Starting connection listener thread");
+                state.setValue(StartStopState.STARTED);
+                server = new ServerSocket(port);
+
+                Socket socket;
+                maxConnections = ((ServerConfiguration) ConfigurationLoader.getConfiguration(ServerConfiguration.class)).getMaxConnections();
+
+                boolean refuse = false;
+                TransportProtocolEventHandler eventHandler = new TransportProtocolEventAdapter() {
+                        public void onDisconnect(TransportProtocol transport) {
+                            // Remove from our active channels list only if
+                            // were still connected (the thread cleans up
+                            // when were exiting so this is to avoid any concurrent
+                            // modification problems
+                            if (state.getValue() != StartStopState.STOPPED) {
+                                synchronized (activeConnections) {
+                                    log.info(transport.getUnderlyingProviderDetail() +
+                                        " connection closed");
+                                    activeConnections.remove(transport);
+                                }
+                            }
+                        }
+                    };
+
+                try {
+                    while (((socket = server.accept()) != null) &&
+                            (state.getValue() == StartStopState.STARTED)) {
+                        log.debug("New connection requested");
+
+                        if (maxConnections < activeConnections.size()) {
+                            refuseSession(socket);
+                        } else {
+                            TransportProtocolServer transport = createSession(socket);
+                            log.info("Monitoring active session from " +
+                                socket.getInetAddress().getHostName());
+
+                            synchronized (activeConnections) {
+                                activeConnections.add(transport);
+                            }
+
+                            transport.addEventHandler(eventHandler);
+                        }
+                    }
+                } catch (IOException ex) {
+                    if (state.getValue() != StartStopState.STOPPED) {
+                        log.info("The server was shutdown unexpectedly", ex);
+                    }
+                }
+
+                state.setValue(StartStopState.STOPPED);
+
+                // Closing all connections
+                log.info("Disconnecting active sessions");
+
+                for (Iterator it = activeConnections.iterator(); it.hasNext();) {
+                    ((TransportProtocolServer) it.next()).disconnect(
+                        "The server is shuting down");
+                }
+
+                listener = null;
+                log.info("Exiting connection listener thread");
+            } catch (IOException ex) {
+                log.info("The server thread failed", ex);
+            } finally {
+                thread = null;
+            }
+
+            // brett
+            //      System.exit(0);
+        }
+
+        public void start() {
+            thread = new SshThread(this, "Connection listener", true);
+            thread.start();
+        }
+
+        public void stop() {
+            try {
+                state.setValue(StartStopState.STOPPED);
+                server.close();
+
+                if (thread != null) {
+                    thread.interrupt();
+                }
+            } catch (IOException ioe) {
+                log.warn("The listening socket reported an error during shutdown",
+                    ioe);
+            }
+        }
+    }
+}
diff --git a/src/com/sshtools/daemon/authentication/AuthenticationProtocolServer.java b/src/com/sshtools/daemon/authentication/AuthenticationProtocolServer.java
new file mode 100644
index 0000000000000000000000000000000000000000..69bc125619d478a08584e3f6be4d1deb09351c3d
--- /dev/null
+++ b/src/com/sshtools/daemon/authentication/AuthenticationProtocolServer.java
@@ -0,0 +1,346 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.authentication;
+
+import com.sshtools.daemon.configuration.*;
+import com.sshtools.daemon.platform.*;
+
+import com.sshtools.j2ssh.*;
+import com.sshtools.j2ssh.authentication.*;
+import com.sshtools.j2ssh.configuration.*;
+import com.sshtools.j2ssh.transport.*;
+
+import org.apache.commons.logging.*;
+
+import java.io.*;
+
+import java.util.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.11 $
+ */
+public class AuthenticationProtocolServer extends AsyncService {
+    private static Log log = LogFactory.getLog(AuthenticationProtocolServer.class);
+    private List completedAuthentications = new ArrayList();
+    private Map acceptServices = new HashMap();
+    private List availableAuths;
+    private String serviceToStart;
+    private int[] messageFilter = new int[1];
+    private SshMessageStore methodMessages = new SshMessageStore();
+    private int attempts = 0;
+    private boolean completed = false;
+
+    /**
+ * Creates a new AuthenticationProtocolServer object.
+ */
+    public AuthenticationProtocolServer() {
+        super("ssh-userauth");
+        messageFilter[0] = SshMsgUserAuthRequest.SSH_MSG_USERAUTH_REQUEST;
+    }
+
+    /**
+ *
+ *
+ * @throws java.io.IOException
+ */
+    protected void onServiceAccept() throws java.io.IOException {
+    }
+
+    /**
+ *
+ *
+ * @param startMode
+ *
+ * @throws java.io.IOException
+ */
+    protected void onServiceInit(int startMode) throws java.io.IOException {
+        // Register the required messages
+        messageStore.registerMessage(SshMsgUserAuthRequest.SSH_MSG_USERAUTH_REQUEST,
+            SshMsgUserAuthRequest.class);
+        transport.addMessageStore(methodMessages);
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public byte[] getSessionIdentifier() {
+        return transport.getSessionIdentifier();
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public TransportProtocolState getConnectionState() {
+        return transport.getState();
+    }
+
+    /**
+ *
+ *
+ * @param msg
+ *
+ * @throws IOException
+ */
+    public void sendMessage(SshMessage msg) throws IOException {
+        transport.sendMessage(msg, this);
+    }
+
+    /**
+ *
+ *
+ * @return
+ *
+ * @throws IOException
+ * @throws SshException
+ */
+    public SshMessage readMessage() throws IOException {
+        try {
+            return methodMessages.nextMessage();
+        } catch (InterruptedException ex) {
+            throw new SshException("The thread was interrupted");
+        }
+    }
+
+    /**
+ *
+ *
+ * @param messageId
+ * @param cls
+ */
+    public void registerMessage(int messageId, Class cls) {
+        methodMessages.registerMessage(messageId, cls);
+    }
+
+    /**
+ *
+ *
+ * @throws java.io.IOException
+ * @throws AuthenticationProtocolException
+ */
+    protected void onServiceRequest() throws java.io.IOException {
+        // Send a user auth banner if configured
+        ServerConfiguration server = (ServerConfiguration) ConfigurationLoader.getConfiguration(ServerConfiguration.class);
+
+        if (server == null) {
+            throw new AuthenticationProtocolException(
+                "Server configuration unavailable");
+        }
+
+        availableAuths = new ArrayList();
+
+        Iterator it = SshAuthenticationServerFactory.getSupportedMethods()
+                                                    .iterator();
+        String method;
+        List allowed = server.getAllowedAuthentications();
+
+        while (it.hasNext()) {
+            method = (String) it.next();
+
+            if (allowed.contains(method)) {
+                availableAuths.add(method);
+            }
+        }
+
+        if (availableAuths.size() <= 0) {
+            throw new AuthenticationProtocolException(
+                "No valid authentication methods have been specified");
+        }
+
+        // Accept the service request
+        sendServiceAccept();
+
+        String bannerFile = server.getAuthenticationBanner();
+
+        if (bannerFile != null) {
+            if (bannerFile.length() > 0) {
+                InputStream in = ConfigurationLoader.loadFile(bannerFile);
+
+                if (in != null) {
+                    byte[] data = new byte[in.available()];
+                    in.read(data);
+                    in.close();
+
+                    SshMsgUserAuthBanner bannerMsg = new SshMsgUserAuthBanner(new String(
+                                data));
+                    transport.sendMessage(bannerMsg, this);
+                } else {
+                    log.info("The banner file '" + bannerFile +
+                        "' was not found");
+                }
+            }
+        }
+    }
+
+    /**
+ *
+ *
+ * @param msg
+ *
+ * @throws java.io.IOException
+ * @throws AuthenticationProtocolException
+ */
+    protected void onMessageReceived(SshMessage msg) throws java.io.IOException {
+        switch (msg.getMessageId()) {
+        case SshMsgUserAuthRequest.SSH_MSG_USERAUTH_REQUEST: {
+            onMsgUserAuthRequest((SshMsgUserAuthRequest) msg);
+
+            break;
+        }
+
+        default:
+            throw new AuthenticationProtocolException(
+                "Unregistered message received!");
+        }
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    protected int[] getAsyncMessageFilter() {
+        return messageFilter;
+    }
+
+    /**
+ *
+ *
+ * @param service
+ */
+    public void acceptService(Service service) {
+        acceptServices.put(service.getServiceName(), service);
+    }
+
+    private void sendUserAuthFailure(boolean success) throws IOException {
+        Iterator it = availableAuths.iterator();
+        String auths = null;
+
+        while (it.hasNext()) {
+            auths = ((auths == null) ? "" : (auths + ",")) +
+                (String) it.next();
+        }
+
+        SshMsgUserAuthFailure reply = new SshMsgUserAuthFailure(auths, success);
+        transport.sendMessage(reply, this);
+    }
+
+    /**
+ *
+ */
+    protected void onStop() {
+        try {
+            // If authentication succeeded then wait for the
+            // disconnect and logoff the user
+            if (completed) {
+                try {
+                    transport.getState().waitForState(TransportProtocolState.DISCONNECTED);
+                } catch (InterruptedException ex) {
+                    log.warn("The authentication service was interrupted");
+                }
+
+                NativeAuthenticationProvider nap = NativeAuthenticationProvider.getInstance();
+                nap.logoffUser();
+            }
+        } catch (IOException ex) {
+            log.warn("Failed to logoff " + SshThread.getCurrentThreadUser());
+        }
+    }
+
+    private void sendUserAuthSuccess() throws IOException {
+        SshMsgUserAuthSuccess msg = new SshMsgUserAuthSuccess();
+        Service service = (Service) acceptServices.get(serviceToStart);
+        service.init(Service.ACCEPTING_SERVICE, transport); //, nativeSettings);
+        service.start();
+        transport.sendMessage(msg, this);
+        completed = true;
+        stop();
+    }
+
+    private void onMsgUserAuthRequest(SshMsgUserAuthRequest msg)
+        throws IOException {
+        if (msg.getMethodName().equals("none")) {
+            sendUserAuthFailure(false);
+        } else {
+            if (attempts >= ((ServerConfiguration) ConfigurationLoader.getConfiguration(
+                        ServerConfiguration.class)).getMaxAuthentications()) {
+                // Too many authentication attempts
+                transport.disconnect("Too many failed authentication attempts");
+            } else {
+                // If the service is supported then perfrom the authentication
+                if (acceptServices.containsKey(msg.getServiceName())) {
+                    String method = msg.getMethodName();
+
+                    if (availableAuths.contains(method)) {
+                        SshAuthenticationServer auth = SshAuthenticationServerFactory.newInstance(method);
+                        serviceToStart = msg.getServiceName();
+
+                        //auth.setUsername(msg.getUsername());
+                        int result = auth.authenticate(this, msg); //, nativeSettings);
+
+                        if (result == AuthenticationProtocolState.FAILED) {
+                            sendUserAuthFailure(false);
+                        } else if (result == AuthenticationProtocolState.COMPLETE) {
+                            completedAuthentications.add(auth.getMethodName());
+
+                            ServerConfiguration sc = (ServerConfiguration) ConfigurationLoader.getConfiguration(ServerConfiguration.class);
+                            Iterator it = sc.getRequiredAuthentications()
+                                            .iterator();
+
+                            while (it.hasNext()) {
+                                if (!completedAuthentications.contains(
+                                            it.next())) {
+                                    sendUserAuthFailure(true);
+
+                                    return;
+                                }
+                            }
+
+                            thread.setUsername(msg.getUsername());
+                            sendUserAuthSuccess();
+                        } else {
+                            // Authentication probably returned READY as no completion
+                            // evaluation was needed
+                        }
+                    } else {
+                        sendUserAuthFailure(false);
+                    }
+                } else {
+                    sendUserAuthFailure(false);
+                }
+
+                attempts++;
+            }
+        }
+    }
+}
diff --git a/src/com/sshtools/daemon/authentication/AuthorizationFileVerification.java b/src/com/sshtools/daemon/authentication/AuthorizationFileVerification.java
new file mode 100644
index 0000000000000000000000000000000000000000..97458709b23d32ec94aae3312c323902301553dc
--- /dev/null
+++ b/src/com/sshtools/daemon/authentication/AuthorizationFileVerification.java
@@ -0,0 +1,224 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.authentication;
+
+import com.sshtools.common.configuration.*;
+
+import com.sshtools.daemon.configuration.*;
+import com.sshtools.daemon.platform.*;
+
+import com.sshtools.j2ssh.authentication.*;
+import com.sshtools.j2ssh.configuration.*;
+import com.sshtools.j2ssh.io.*;
+import com.sshtools.j2ssh.transport.publickey.*;
+
+import org.apache.commons.logging.*;
+
+import java.io.*;
+
+import java.util.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class AuthorizationFileVerification implements PublicKeyVerification {
+    private static Log log = LogFactory.getLog(AuthorizationFileVerification.class);
+
+    /**
+ *
+ *
+ * @param username
+ * @param algorithm
+ * @param encoded
+ * @param service
+ * @param sessionId
+ * @param signature
+ *
+ * @return
+ *
+ * @throws IOException
+ */
+    public boolean verifyKeySignature(String username, String algorithm,
+        byte[] encoded, String service, byte[] sessionId, byte[] signature)
+        throws IOException {
+        try {
+            SshPublicKey key = getAuthorizedKey(username, algorithm, encoded);
+            ByteArrayWriter data = new ByteArrayWriter();
+            data.writeBinaryString(sessionId);
+            data.write(SshMsgUserAuthRequest.SSH_MSG_USERAUTH_REQUEST);
+            data.writeString(username);
+            data.writeString(service);
+            data.writeString("publickey");
+            data.write(1);
+            data.writeString(key.getAlgorithmName());
+            data.writeBinaryString(key.getEncoded());
+
+            if (key.verifySignature(signature, data.toByteArray())) {
+                return true;
+            }
+        } catch (IOException ex) {
+        }
+
+        return false;
+    }
+
+    private SshPublicKey getAuthorizedKey(String username, String algorithm,
+        byte[] encoded) throws IOException {
+        NativeAuthenticationProvider provider = NativeAuthenticationProvider.getInstance();
+        String userHome = provider.getHomeDirectory(username); //, nativeSettings);
+
+        if (userHome == null) {
+            log.warn("There is no home directory for " + username +
+                " is available");
+        }
+
+        // Replace '\' with '/' because when we use it in String.replaceAll
+        // for some reason it removes them?
+        if (userHome != null) {
+            userHome = userHome.replace('\\', '/');
+        }
+
+        ServerConfiguration config = (ServerConfiguration) ConfigurationLoader.getConfiguration(ServerConfiguration.class);
+        String authorizationFile;
+        String userConfigDir = config.getUserConfigDirectory();
+
+        // First replace any '\' with '/' (Becasue replaceAll removes them!)
+        userConfigDir = userConfigDir.replace('\\', '/');
+
+        // Replace any home directory tokens
+        if ((userConfigDir.indexOf("%D") > -1) && (userHome == null)) {
+            throw new IOException(
+                "<UserConfigDirectory> requires home directory, but none available for " +
+                username);
+        }
+
+        int idx = 0;
+
+        while ((idx = userConfigDir.indexOf("%D", idx + 1)) > -1) {
+            StringBuffer buf = new StringBuffer(userConfigDir);
+            buf = buf.replace(idx, idx + 1, userHome);
+            userConfigDir = buf.toString();
+        }
+
+        idx = 0;
+
+        while ((idx = userConfigDir.indexOf("%U", idx + 1)) > -1) {
+            StringBuffer buf = new StringBuffer(userConfigDir);
+            buf = buf.replace(idx, idx + 1, username);
+            userConfigDir = buf.toString();
+        }
+
+        // Replace the '/' with File.seperator and trim
+        userConfigDir = userConfigDir.replace('/', File.separatorChar).trim();
+
+        if (!userConfigDir.endsWith(File.separator)) {
+            userConfigDir += File.separator;
+        }
+
+        authorizationFile = userConfigDir + config.getAuthorizationFile();
+
+        // Load the authorization file
+        File file = new File(authorizationFile);
+
+        if (!file.exists()) {
+            log.info("authorizationFile: " + authorizationFile +
+                " does not exist.");
+            throw new IOException("authorizationFile: " + authorizationFile +
+                " does not exist.");
+        }
+
+        FileInputStream in = new FileInputStream(file);
+        Authorization keys;
+
+        try {
+            keys = new Authorization(in);
+        } catch (Exception e) {
+            throw new AuthenticationProtocolException(
+                "Failed to load authorized keys file " + authorizationFile);
+        }
+
+        //      SshPublicKey key = SshPublicKeyFile.parse(encoded);
+        Iterator it = keys.getAuthorizedKeys().iterator();
+        SshKeyPair pair = SshKeyPairFactory.newInstance(algorithm);
+        SshPublicKey authorizedKey = null;
+        SshPublicKey key = pair.decodePublicKey(encoded);
+        boolean valid = false;
+        String keyfile;
+
+        while (it.hasNext()) {
+            keyfile = (String) it.next();
+
+            // Look for the file in the user config dir first
+            file = new File(userConfigDir + keyfile);
+
+            // If it does not exist then look absolute
+            if (!file.exists()) {
+                file = new File(keyfile);
+            }
+
+            if (file.exists()) {
+                // Try to open the public key in the default file format
+                // otherwise attempt the supported key formats
+                SshPublicKeyFile pkf = SshPublicKeyFile.parse(file);
+                authorizedKey = pkf.toPublicKey();
+
+                if (authorizedKey.equals(key)) {
+                    return authorizedKey;
+                }
+            } else {
+                log.info("Failed attempt to load key file " + keyfile);
+            }
+        }
+
+        throw new IOException("");
+    }
+
+    /**
+ *
+ *
+ * @param username
+ * @param algorithm
+ * @param encoded
+ *
+ * @return
+ *
+ * @throws IOException
+ */
+    public boolean acceptKey(String username, String algorithm, byte[] encoded)
+        throws IOException {
+        try {
+            getAuthorizedKey(username, algorithm, encoded);
+
+            return true;
+        } catch (Exception ex) {
+            return false;
+        }
+    }
+}
diff --git a/src/com/sshtools/daemon/authentication/KBIPasswordAuthenticationServer.java b/src/com/sshtools/daemon/authentication/KBIPasswordAuthenticationServer.java
new file mode 100644
index 0000000000000000000000000000000000000000..6dfff398f78725d82e57ad3fa456096516c9207f
--- /dev/null
+++ b/src/com/sshtools/daemon/authentication/KBIPasswordAuthenticationServer.java
@@ -0,0 +1,163 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.authentication;
+
+import com.sshtools.daemon.platform.*;
+
+import com.sshtools.j2ssh.authentication.*;
+import com.sshtools.j2ssh.transport.*;
+
+import org.apache.commons.logging.*;
+
+import java.io.*;
+
+import java.util.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.10 $
+ */
+public class KBIPasswordAuthenticationServer extends SshAuthenticationServer {
+    private static Log log = LogFactory.getLog(KBIPasswordAuthenticationServer.class);
+
+    /**
+ *
+ *
+ * @return
+ */
+    public final String getMethodName() {
+        return "keyboard-interactive";
+    }
+
+    /**
+ *
+ *
+ * @param tokens
+ */
+    public void setAuthenticatedTokens(Map tokens) {
+    }
+
+    /**
+ *
+ *
+ * @param authentication
+ * @param msg
+ *
+ * @return
+ *
+ * @throws IOException
+ */
+    public int authenticate(AuthenticationProtocolServer authentication,
+        SshMsgUserAuthRequest msg) throws IOException { //, Map nativeSettings)
+
+        NativeAuthenticationProvider authImpl = NativeAuthenticationProvider.getInstance();
+
+        if (authImpl == null) {
+            log.error(
+                "Cannot perfrom authentication witout native authentication provider");
+
+            return AuthenticationProtocolState.FAILED;
+        }
+
+        authentication.registerMessage(SshMsgUserAuthInfoResponse.SSH_MSG_USERAUTH_INFO_RESPONSE,
+            SshMsgUserAuthInfoResponse.class);
+
+        SshMsgUserAuthInfoRequest info = new SshMsgUserAuthInfoRequest("Password authentication",
+                "", "");
+        info.addPrompt(msg.getUsername() + "'s password", false);
+        authentication.sendMessage(info);
+
+        SshMessage response = authentication.readMessage();
+
+        if (response instanceof SshMsgUserAuthInfoResponse) {
+            String[] responses = ((SshMsgUserAuthInfoResponse) response).getResponses();
+
+            if (responses.length == 1) {
+                String password = responses[0];
+
+                try {
+                    if (authImpl.logonUser(msg.getUsername(), password)) { //, nativeSettings)) {
+                        log.info(msg.getUsername() +
+                            " has passed password authentication");
+
+                        return AuthenticationProtocolState.COMPLETE;
+                    } else {
+                        log.info(msg.getUsername() +
+                            " has failed password authentication");
+
+                        return AuthenticationProtocolState.FAILED;
+                    }
+                } catch (PasswordChangeException ex) {
+                    info = new SshMsgUserAuthInfoRequest("Password change required",
+                            "", "");
+                    info.addPrompt("New password", false);
+                    info.addPrompt("Confirm password", false);
+                    authentication.sendMessage(info);
+                    response = authentication.readMessage();
+
+                    if (response instanceof SshMsgUserAuthInfoResponse) {
+                        responses = ((SshMsgUserAuthInfoResponse) response).getResponses();
+
+                        if (responses.length == 2) {
+                            if (responses[0].equals(responses[1])) {
+                                if (authImpl.changePassword(msg.getUsername(),
+                                            password, responses[0])) {
+                                    return AuthenticationProtocolState.COMPLETE;
+                                } else {
+                                    return AuthenticationProtocolState.FAILED;
+                                }
+                            } else {
+                                return AuthenticationProtocolState.FAILED;
+                            }
+                        } else {
+                            log.error("Client replied with an invalid message " +
+                                response.getMessageName());
+
+                            return AuthenticationProtocolState.FAILED;
+                        }
+                    } else {
+                        log.error("Client replied with an invalid message " +
+                            response.getMessageName());
+
+                        return AuthenticationProtocolState.FAILED;
+                    }
+                }
+            } else {
+                log.error("Client responded with too many values!");
+
+                return AuthenticationProtocolState.FAILED;
+            }
+        } else {
+            log.error("Client replied with an invalid message " +
+                response.getMessageName());
+
+            return AuthenticationProtocolState.FAILED;
+        }
+    }
+}
diff --git a/src/com/sshtools/daemon/authentication/PasswordAuthenticationServer.java b/src/com/sshtools/daemon/authentication/PasswordAuthenticationServer.java
new file mode 100644
index 0000000000000000000000000000000000000000..214579523c7d26002e4d97ffe865f8fa5b7dbb0b
--- /dev/null
+++ b/src/com/sshtools/daemon/authentication/PasswordAuthenticationServer.java
@@ -0,0 +1,131 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.authentication;
+
+import com.sshtools.daemon.platform.*;
+
+import com.sshtools.j2ssh.authentication.*;
+import com.sshtools.j2ssh.io.*;
+
+import org.apache.commons.logging.*;
+
+import java.io.*;
+
+import java.util.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.10 $
+ */
+public class PasswordAuthenticationServer extends SshAuthenticationServer {
+    private static Log log = LogFactory.getLog(PasswordAuthenticationServer.class);
+
+    /**
+ *
+ *
+ * @return
+ */
+    public final String getMethodName() {
+        return "password";
+    }
+
+    /**
+ *
+ *
+ * @param tokens
+ */
+    public void setAuthenticatedTokens(Map tokens) {
+    }
+
+    /**
+ *
+ *
+ * @param authentication
+ * @param msg
+ *
+ * @return
+ *
+ * @throws IOException
+ */
+    public int authenticate(AuthenticationProtocolServer authentication,
+        SshMsgUserAuthRequest msg) throws IOException {
+        NativeAuthenticationProvider authImpl = NativeAuthenticationProvider.getInstance();
+
+        if (authImpl == null) {
+            log.error(
+                "Cannot perfrom authentication witout native authentication provider");
+
+            return AuthenticationProtocolState.FAILED;
+        }
+
+        ByteArrayReader bar = new ByteArrayReader(msg.getRequestData());
+        boolean changepwd = ((bar.read() == 0) ? false : true);
+        String password = bar.readString();
+        String newpassword = null;
+
+        if (changepwd) {
+            newpassword = bar.readString();
+
+            try {
+                if (!authImpl.changePassword(msg.getUsername(), password,
+                            newpassword)) {
+                    return AuthenticationProtocolState.FAILED;
+                }
+
+                if (authImpl.logonUser(msg.getUsername(), newpassword)) {
+                    return AuthenticationProtocolState.COMPLETE;
+                } else {
+                    return AuthenticationProtocolState.FAILED;
+                }
+            } catch (PasswordChangeException ex1) {
+                return AuthenticationProtocolState.FAILED;
+            }
+        } else {
+            try {
+                if (authImpl.logonUser(msg.getUsername(), password)) {
+                    log.info(msg.getUsername() +
+                        " has passed password authentication");
+
+                    return AuthenticationProtocolState.COMPLETE;
+                } else {
+                    log.info(msg.getUsername() +
+                        " has failed password authentication");
+
+                    return AuthenticationProtocolState.FAILED;
+                }
+            } catch (PasswordChangeException ex) {
+                SshMsgUserAuthPwdChangeReq reply = new SshMsgUserAuthPwdChangeReq(msg.getUsername() +
+                        " is required to change password", "");
+                authentication.sendMessage(reply);
+
+                return AuthenticationProtocolState.READY;
+            }
+        }
+    }
+}
diff --git a/src/com/sshtools/daemon/authentication/PublicKeyAuthenticationServer.java b/src/com/sshtools/daemon/authentication/PublicKeyAuthenticationServer.java
new file mode 100644
index 0000000000000000000000000000000000000000..e2f16ce6e66a4b5f0f90f5c6943efdf20c69a1d6
--- /dev/null
+++ b/src/com/sshtools/daemon/authentication/PublicKeyAuthenticationServer.java
@@ -0,0 +1,145 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.authentication;
+
+import com.sshtools.daemon.platform.*;
+
+import com.sshtools.j2ssh.authentication.*;
+import com.sshtools.j2ssh.io.*;
+
+import org.apache.commons.logging.*;
+
+import java.io.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class PublicKeyAuthenticationServer extends SshAuthenticationServer {
+    private static Class pkv = AuthorizationFileVerification.class;
+    private Log log = LogFactory.getLog(PublicKeyAuthenticationServer.class);
+
+    /**
+ * Creates a new PublicKeyAuthenticationServer object.
+ */
+    public PublicKeyAuthenticationServer() {
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getMethodName() {
+        return "publickey";
+    }
+
+    /**
+ *
+ *
+ * @param pkv
+ */
+    public static void setVerificationImpl(Class pkv) {
+        PublicKeyAuthenticationServer.pkv = pkv;
+    }
+
+    /**
+ *
+ *
+ * @param authentication
+ * @param msg
+ *
+ * @return
+ *
+ * @throws IOException
+ */
+    public int authenticate(AuthenticationProtocolServer authentication,
+        SshMsgUserAuthRequest msg) throws IOException { //, Map nativeSettings)
+
+        ByteArrayReader bar = new ByteArrayReader(msg.getRequestData());
+
+        // If check == 0 then authenticate, otherwise just inform that
+        // the authentication can continue with the key supplied
+        int check = bar.read();
+        String algorithm = bar.readString();
+        byte[] encoded = bar.readBinaryString();
+        byte[] signature = null;
+
+        try {
+            PublicKeyVerification verify = (PublicKeyVerification) pkv.newInstance();
+
+            if (check == 0) {
+                // Verify that the public key can be used for authenticaiton
+                //boolean ok = SshKeyPairFactory.supportsKey(algorithm);
+                // Send the reply
+                if (verify.acceptKey(msg.getUsername(), algorithm, encoded)) {
+                    SshMsgUserAuthPKOK reply = new SshMsgUserAuthPKOK(algorithm,
+                            encoded);
+                    authentication.sendMessage(reply);
+
+                    return AuthenticationProtocolState.READY;
+                } else {
+                    return AuthenticationProtocolState.FAILED;
+                }
+            } else {
+                signature = bar.readBinaryString();
+
+                NativeAuthenticationProvider authProv = NativeAuthenticationProvider.getInstance();
+
+                if (authProv == null) {
+                    log.error(
+                        "Authentication failed because no native authentication provider is available");
+
+                    return AuthenticationProtocolState.FAILED;
+                }
+
+                if (!authProv.logonUser(msg.getUsername())) { //, nativeSettings)) {
+                    log.info("Authentication failed because " +
+                        msg.getUsername() + " is not a valid username");
+
+                    return AuthenticationProtocolState.FAILED;
+                }
+
+                try {
+                    if (verify.verifyKeySignature(msg.getUsername(), algorithm,
+                                encoded, msg.getServiceName(),
+                                authentication.getSessionIdentifier(), signature)) {
+                        return AuthenticationProtocolState.COMPLETE;
+                    }
+                } catch (Exception ex) {
+                    log.error("Failed to create an instance of the verification implementation",
+                        ex);
+                }
+            }
+        } catch (Exception e) {
+        }
+
+        return AuthenticationProtocolState.FAILED;
+    }
+}
diff --git a/src/com/sshtools/daemon/authentication/PublicKeyVerification.java b/src/com/sshtools/daemon/authentication/PublicKeyVerification.java
new file mode 100644
index 0000000000000000000000000000000000000000..d25ba5c60c3a5b69eec7a6a51e7785815aec75ef
--- /dev/null
+++ b/src/com/sshtools/daemon/authentication/PublicKeyVerification.java
@@ -0,0 +1,69 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.authentication;
+
+import java.io.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public interface PublicKeyVerification {
+    /**
+ *
+ *
+ * @param username
+ * @param algorithm
+ * @param encoded
+ * @param service
+ * @param sessionId
+ * @param signature
+ *
+ * @return
+ *
+ * @throws IOException
+ */
+    public boolean verifyKeySignature(String username, String algorithm,
+        byte[] encoded, String service, byte[] sessionId, byte[] signature)
+        throws IOException;
+
+    /**
+ *
+ *
+ * @param username
+ * @param algorithm
+ * @param encoded
+ *
+ * @return
+ *
+ * @throws IOException
+ */
+    public boolean acceptKey(String username, String algorithm, byte[] encoded)
+        throws IOException;
+}
diff --git a/src/com/sshtools/daemon/authentication/SshAuthenticationServer.java b/src/com/sshtools/daemon/authentication/SshAuthenticationServer.java
new file mode 100644
index 0000000000000000000000000000000000000000..3d97ea8e7d9fe9b35b65f079746fa2e07c213395
--- /dev/null
+++ b/src/com/sshtools/daemon/authentication/SshAuthenticationServer.java
@@ -0,0 +1,69 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.authentication;
+
+import com.sshtools.j2ssh.authentication.SshMsgUserAuthRequest;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.10 $
+ */
+public abstract class SshAuthenticationServer {
+    private String username;
+
+    /**
+ *
+ *
+ * @return
+ */
+    public abstract String getMethodName();
+
+    /**
+ *
+ *
+ * @param authentication
+ * @param msg
+ *
+ * @return
+ *
+ * @throws IOException
+ */
+    public abstract int authenticate(
+        AuthenticationProtocolServer authentication, SshMsgUserAuthRequest msg)
+        throws IOException;
+
+    //  public void setUsername(String username) {
+    //      this.username = username;
+    //  }
+    // public String getUsername() {
+    //     return username;
+    // }
+}
diff --git a/src/com/sshtools/daemon/authentication/SshAuthenticationServerFactory.java b/src/com/sshtools/daemon/authentication/SshAuthenticationServerFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..75658f66afc75b3b21f303e21c13187ec57c1698
--- /dev/null
+++ b/src/com/sshtools/daemon/authentication/SshAuthenticationServerFactory.java
@@ -0,0 +1,152 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.authentication;
+
+import com.sshtools.j2ssh.configuration.ConfigurationException;
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+import com.sshtools.j2ssh.configuration.ExtensionAlgorithm;
+import com.sshtools.j2ssh.configuration.SshAPIConfiguration;
+import com.sshtools.j2ssh.transport.AlgorithmNotSupportedException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.10 $
+ */
+public class SshAuthenticationServerFactory {
+    private static Map auths;
+    private static Log log = LogFactory.getLog(SshAuthenticationServerFactory.class);
+
+    /**  */
+    public final static String AUTH_PASSWORD = "password";
+
+    /**  */
+    public final static String AUTH_PK = "publickey";
+
+    /**  */
+    public final static String AUTH_KBI = "keyboard-interactive";
+
+    static {
+        auths = new HashMap();
+        log.info("Loading supported authentication methods");
+        auths.put(AUTH_PASSWORD, PasswordAuthenticationServer.class);
+        auths.put(AUTH_PK, PublicKeyAuthenticationServer.class);
+        auths.put(AUTH_KBI, KBIPasswordAuthenticationServer.class);
+
+        try {
+            if (ConfigurationLoader.isConfigurationAvailable(
+                        SshAPIConfiguration.class)) {
+                SshAPIConfiguration config = (SshAPIConfiguration) ConfigurationLoader.getConfiguration(SshAPIConfiguration.class);
+                List addons = config.getAuthenticationExtensions();
+                Iterator it = addons.iterator();
+
+                // Add the methods to our supported list
+                while (it.hasNext()) {
+                    ExtensionAlgorithm method = (ExtensionAlgorithm) it.next();
+                    String name = method.getAlgorithmName();
+
+                    if (auths.containsKey(name)) {
+                        log.debug("Standard authentication implementation for " +
+                            name + " is being overidden by " +
+                            method.getImplementationClass());
+                    } else {
+                        log.debug(name + " authentication is implemented by " +
+                            method.getImplementationClass());
+                    }
+
+                    try {
+                        Class cls = ConfigurationLoader.getExtensionClass(method.getImplementationClass());
+                        Object obj = cls.newInstance();
+
+                        if (obj instanceof SshAuthenticationServer) {
+                            auths.put(name, cls);
+                        }
+                    } catch (Exception e) {
+                        log.warn(
+                            "Failed to load extension authentication implementation " +
+                            method.getImplementationClass(), e);
+                    }
+                }
+            }
+        } catch (ConfigurationException ex) {
+        }
+    }
+
+    /**
+ * Creates a new SshAuthenticationServerFactory object.
+ */
+    protected SshAuthenticationServerFactory() {
+    }
+
+    /**
+ *
+ */
+    public static void initialize() {
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public static List getSupportedMethods() {
+        // Get the list of ciphers
+        ArrayList list = new ArrayList(auths.keySet());
+
+        // Return the list
+        return list;
+    }
+
+    /**
+ *
+ *
+ * @param methodName
+ *
+ * @return
+ *
+ * @throws AlgorithmNotSupportedException
+ */
+    public static SshAuthenticationServer newInstance(String methodName)
+        throws AlgorithmNotSupportedException {
+        try {
+            return (SshAuthenticationServer) ((Class) auths.get(methodName)).newInstance();
+        } catch (Exception e) {
+            throw new AlgorithmNotSupportedException(methodName +
+                " is not supported!");
+        }
+    }
+}
diff --git a/src/com/sshtools/daemon/configuration/AllowedSubsystem.java b/src/com/sshtools/daemon/configuration/AllowedSubsystem.java
new file mode 100644
index 0000000000000000000000000000000000000000..8e81b4e7506f807d366c01204a50eeeaa685cb3a
--- /dev/null
+++ b/src/com/sshtools/daemon/configuration/AllowedSubsystem.java
@@ -0,0 +1,79 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.configuration;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class AllowedSubsystem {
+    private String type;
+    private String name;
+    private String provider;
+
+    /**
+ * Creates a new AllowedSubsystem object.
+ *
+ * @param type
+ * @param name
+ * @param provider
+ */
+    public AllowedSubsystem(String type, String name, String provider) {
+        this.type = type;
+        this.name = name;
+        this.provider = provider;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getType() {
+        return type;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getName() {
+        return name;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getProvider() {
+        return provider;
+    }
+}
diff --git a/src/com/sshtools/daemon/configuration/PlatformConfiguration.java b/src/com/sshtools/daemon/configuration/PlatformConfiguration.java
new file mode 100644
index 0000000000000000000000000000000000000000..19b7aa5470e90bd38b850577e9a82c8f74292779
--- /dev/null
+++ b/src/com/sshtools/daemon/configuration/PlatformConfiguration.java
@@ -0,0 +1,422 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.configuration;
+
+import com.sshtools.daemon.vfs.*;
+
+import org.apache.commons.logging.*;
+
+import org.xml.sax.*;
+import org.xml.sax.helpers.*;
+
+import java.io.*;
+
+import java.util.*;
+
+import javax.xml.parsers.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class PlatformConfiguration extends DefaultHandler {
+    private static Log log = LogFactory.getLog(PlatformConfiguration.class);
+    private static final String PLATFORM_ELEMENT = "PlatformConfiguration";
+    private static final String NATIVE_PROCESS_ELEMENT = "NativeProcessProvider";
+    private static final String NATIVE_AUTH_ELEMENT = "NativeAuthenticationProvider";
+    private static final String NFS_ELEMENT = "NativeFileSystemProvider";
+    private static final String NATIVE_SETTING_ELEMENT = "NativeSetting";
+    private static final String VFSMOUNT_ELEMENT = "VFSMount";
+    private static final String VFSROOT_ELEMENT = "VFSRoot";
+    private static final String VFSPERMISSION_ELEMENT = "VFSPermission";
+    private static final String PATH_ATTRIBUTE = "path";
+    private static final String MOUNT_ATTRIBUTE = "mount";
+    private static final String NAME_ATTRIBUTE = "name";
+    private static final String VALUE_ATTRIBUTE = "value";
+    private static final String PERMISSIONS_ATTRIBUTE = "permissions";
+    private String currentElement = null;
+    private Map nativeSettings = new HashMap();
+    private String nativeProcessProvider = null;
+    private String nativeAuthenticationProvider = null;
+    private String nativeFileSystemProvider = null;
+    private Map vfsMounts = new HashMap();
+    private VFSMount vfsRoot = null;
+
+    /**
+ * Creates a new PlatformConfiguration object.
+ *
+ * @param in
+ *
+ * @throws SAXException
+ * @throws ParserConfigurationException
+ * @throws IOException
+ */
+    protected PlatformConfiguration(InputStream in)
+        throws SAXException, ParserConfigurationException, IOException {
+        reload(in);
+    }
+
+    /**
+ *
+ *
+ * @param in
+ *
+ * @throws SAXException
+ * @throws ParserConfigurationException
+ * @throws IOException
+ */
+    public void reload(InputStream in)
+        throws SAXException, ParserConfigurationException, IOException {
+        SAXParserFactory saxFactory = SAXParserFactory.newInstance();
+        SAXParser saxParser = saxFactory.newSAXParser();
+        saxParser.parse(in, new PlatformConfigurationSAXHandler());
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public Map getVFSMounts() {
+        return vfsMounts;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getNativeAuthenticationProvider() {
+        return nativeAuthenticationProvider;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getNativeFileSystemProvider() {
+        return nativeFileSystemProvider;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getNativeProcessProvider() {
+        return nativeProcessProvider;
+    }
+
+    /**
+ *
+ *
+ * @param name
+ *
+ * @return
+ */
+    public String getSetting(String name) {
+        return (String) nativeSettings.get(name);
+    }
+
+    /**
+ *
+ *
+ * @param name
+ * @param defaultValue
+ *
+ * @return
+ */
+    public String getSetting(String name, String defaultValue) {
+        if (nativeSettings.containsKey(name)) {
+            return (String) nativeSettings.get(name);
+        } else {
+            return defaultValue;
+        }
+    }
+
+    /**
+ *
+ *
+ * @param name
+ *
+ * @return
+ */
+    public boolean containsSetting(String name) {
+        return nativeSettings.containsKey(name);
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public VFSMount getVFSRoot() {
+        return vfsRoot;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String toString() {
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+        xml += ("<!-- Platform Configuration file, Determines the behaviour of platform specific services -->\n<" +
+        PLATFORM_ELEMENT + ">\n");
+        xml += "   <!-- The process provider for executing and redirecting a process -->\n";
+        xml += ("   <" + NATIVE_PROCESS_ELEMENT + ">" + nativeProcessProvider +
+        "</" + NATIVE_PROCESS_ELEMENT + ">\n");
+        xml += "   <!-- The authentication provider for authenticating users and obtaining user information -->\n";
+        xml += ("   <" + NATIVE_AUTH_ELEMENT + ">" +
+        nativeAuthenticationProvider + "</" + NATIVE_AUTH_ELEMENT + ">\n");
+        xml += "   <!-- Native settings which may be used by the process or authentication provider -->\n";
+
+        Map.Entry entry;
+        Iterator it = nativeSettings.entrySet().iterator();
+
+        while (it.hasNext()) {
+            entry = (Map.Entry) it.next();
+            xml += ("   " + "<" + NATIVE_SETTING_ELEMENT + " " +
+            NAME_ATTRIBUTE + "=\"" + entry.getKey().toString() + "\" " +
+            VALUE_ATTRIBUTE + "=\"" + entry.getValue().toString() + "\"/>\n");
+        }
+
+        if (vfsRoot != null) {
+            xml += ("   " + "<" + VFSROOT_ELEMENT + " path=\"" + vfsRoot +
+            "\"/>\n");
+        }
+
+        it = vfsMounts.entrySet().iterator();
+
+        String path;
+        String mount;
+
+        while (it.hasNext()) {
+            entry = (Map.Entry) it.next();
+            path = (String) entry.getValue();
+            mount = (String) entry.getKey();
+            xml += ("   " + "<" + VFSMOUNT_ELEMENT + " " +
+            (mount.equals(path) ? ""
+                                : (MOUNT_ATTRIBUTE + "=\"" +
+            entry.getKey().toString() + "\" ")) + PATH_ATTRIBUTE + "=\"" +
+            entry.getValue().toString() + "\"/>\n");
+        }
+
+        xml += ("</" + PLATFORM_ELEMENT + ">");
+
+        return xml;
+    }
+
+    class PlatformConfigurationSAXHandler extends DefaultHandler {
+        private VFSMount currentMount = null;
+
+        public void startElement(String uri, String localName, String qname,
+            Attributes attrs) throws SAXException {
+            if (currentElement == null) {
+                if (qname.equals(PLATFORM_ELEMENT)) {
+                    currentElement = qname;
+                }
+
+                nativeProcessProvider = null;
+                nativeAuthenticationProvider = null;
+                nativeSettings.clear();
+            } else {
+                if (currentElement.equals(PLATFORM_ELEMENT)) {
+                    if (!qname.equals(NATIVE_PROCESS_ELEMENT) &&
+                            !qname.equals(NATIVE_AUTH_ELEMENT) &&
+                            !qname.equals(NATIVE_SETTING_ELEMENT) &&
+                            !qname.equals(VFSMOUNT_ELEMENT) &&
+                            !qname.equals(VFSROOT_ELEMENT) &&
+                            !qname.equals(NFS_ELEMENT)) {
+                        throw new SAXException("Unexpected element " + qname);
+                    }
+                } else if (currentElement.equals(VFSMOUNT_ELEMENT)) {
+                    if (!qname.equals(VFSPERMISSION_ELEMENT)) {
+                        throw new SAXException("Unexpected element " + qname);
+                    }
+                } else {
+                    throw new SAXException("Unexpected element " + qname);
+                }
+
+                currentElement = qname;
+
+                if (qname.equals(NATIVE_SETTING_ELEMENT)) {
+                    String name = attrs.getValue(NAME_ATTRIBUTE);
+                    String value = attrs.getValue(VALUE_ATTRIBUTE);
+
+                    if ((name == null) || (value == null)) {
+                        throw new SAXException(
+                            "Required attributes missing for NativeSetting element");
+                    }
+
+                    log.debug("NativeSetting " + name + "=" + value);
+                    nativeSettings.put(name, value);
+                }
+
+                if (qname.equals(VFSPERMISSION_ELEMENT)) {
+                    String name = attrs.getValue(NAME_ATTRIBUTE);
+                    String permissions = attrs.getValue(PERMISSIONS_ATTRIBUTE);
+                    currentMount.setPermissions(new VFSPermission(name,
+                            permissions));
+                }
+
+                if (qname.equals(VFSMOUNT_ELEMENT)) {
+                    String path = attrs.getValue(PATH_ATTRIBUTE);
+                    String mount = attrs.getValue(MOUNT_ATTRIBUTE);
+                    String permissions = attrs.getValue(PERMISSIONS_ATTRIBUTE);
+
+                    if ((path != null) && (mount != null)) {
+                        // verify the mount - must start with / and be unique
+                        if (!mount.trim().equals("/")) {
+                            try {
+                                currentMount = new VFSMount(mount, path);
+
+                                if (permissions == null) {
+                                    currentMount.setPermissions(new VFSPermission(
+                                            "default"));
+                                } else {
+                                    currentMount.setPermissions(new VFSPermission(
+                                            "default", permissions));
+                                }
+
+                                if (!vfsMounts.containsKey(
+                                            currentMount.getMount())) {
+                                    vfsMounts.put(currentMount.getMount(),
+                                        currentMount);
+                                } else {
+                                    throw new SAXException("The mount " +
+                                        mount + " is already defined");
+                                }
+                            } catch (IOException ex1) {
+                                throw new SAXException(
+                                    "VFSMount element is invalid mount=" +
+                                    mount + " path=" + path);
+                            }
+                        } else {
+                            throw new SAXException(
+                                "The root mount / cannot be configured, use <VFSRoot path=\"" +
+                                path + "\"/> instead");
+                        }
+                    } else {
+                        throw new SAXException("Required " + PATH_ATTRIBUTE +
+                            " attribute for element <" + VFSMOUNT_ELEMENT +
+                            "> is missing");
+                    }
+                }
+
+                if (qname.equals(VFSROOT_ELEMENT)) {
+                    if (vfsRoot != null) {
+                        throw new SAXException(
+                            "Only one VFSRoot can be defined");
+                    }
+
+                    String path = attrs.getValue(PATH_ATTRIBUTE);
+                    String permissions = attrs.getValue(PERMISSIONS_ATTRIBUTE);
+
+                    try {
+                        vfsRoot = new VFSMount("/", path);
+
+                        if (permissions != null) {
+                            vfsRoot.setPermissions(new VFSPermission(
+                                    "default", permissions));
+                        } else {
+                            vfsRoot.setPermissions(new VFSPermission("default"));
+                        }
+
+                        vfsRoot.setRoot(true);
+                    } catch (IOException ex) {
+                        throw new SAXException(
+                            "VFSRoot element is invalid path=" + path);
+                    }
+                }
+            }
+        }
+
+        public void characters(char[] ch, int start, int length)
+            throws SAXException {
+            if (currentElement == null) {
+                throw new SAXException("Unexpected characters found");
+            }
+
+            if (currentElement.equals(NATIVE_AUTH_ELEMENT)) {
+                nativeAuthenticationProvider = new String(ch, start, length).trim();
+                log.debug("NativeAuthenticationProvider=" +
+                    nativeAuthenticationProvider);
+
+                return;
+            }
+
+            if (currentElement.equals(NATIVE_PROCESS_ELEMENT)) {
+                nativeProcessProvider = new String(ch, start, length).trim();
+                log.debug("NativeProcessProvider=" + nativeProcessProvider);
+
+                return;
+            }
+
+            if (currentElement.equals(NFS_ELEMENT)) {
+                nativeFileSystemProvider = new String(ch, start, length).trim();
+                log.debug("NativeFileSystemProvider=" +
+                    nativeFileSystemProvider);
+
+                return;
+            }
+        }
+
+        public void endElement(String uri, String localName, String qname)
+            throws SAXException {
+            if (currentElement == null) {
+                throw new SAXException("Unexpected end element for " + qname);
+            }
+
+            if (!currentElement.equals(qname)) {
+                throw new SAXException("Unexpected end element found");
+            }
+
+            if (currentElement.equals(PLATFORM_ELEMENT)) {
+                currentElement = null;
+
+                return;
+            }
+
+            if (currentElement.equals(VFSPERMISSION_ELEMENT)) {
+                currentElement = VFSMOUNT_ELEMENT;
+            } else if (!currentElement.equals(NATIVE_SETTING_ELEMENT) &&
+                    !currentElement.equals(NATIVE_AUTH_ELEMENT) &&
+                    !currentElement.equals(NATIVE_PROCESS_ELEMENT) &&
+                    !currentElement.equals(NFS_ELEMENT) &&
+                    !currentElement.equals(VFSMOUNT_ELEMENT) &&
+                    !currentElement.equals(VFSROOT_ELEMENT)) {
+                throw new SAXException("Unexpected end element for " + qname);
+            } else {
+                currentElement = PLATFORM_ELEMENT;
+            }
+        }
+    }
+}
diff --git a/src/com/sshtools/daemon/configuration/ServerConfiguration.java b/src/com/sshtools/daemon/configuration/ServerConfiguration.java
new file mode 100644
index 0000000000000000000000000000000000000000..d5bb6925288ce3db94f9a3f5354759761de9a124
--- /dev/null
+++ b/src/com/sshtools/daemon/configuration/ServerConfiguration.java
@@ -0,0 +1,507 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.configuration;
+
+import com.sshtools.daemon.session.*;
+
+import com.sshtools.j2ssh.configuration.*;
+import com.sshtools.j2ssh.transport.publickey.*;
+
+import org.apache.commons.logging.*;
+
+import org.xml.sax.*;
+import org.xml.sax.helpers.*;
+
+import java.io.*;
+
+import java.util.*;
+
+import javax.xml.parsers.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class ServerConfiguration extends DefaultHandler {
+    private static Log log = LogFactory.getLog(ServerConfiguration.class);
+    private Map allowedSubsystems = new HashMap();
+    private Map serverHostKeys = new HashMap();
+    private List allowedAuthentications = new ArrayList();
+    private List requiredAuthentications = new ArrayList();
+    private int commandPort = 12222;
+    private int port = 22;
+    private String listenAddress = "0.0.0.0";
+    private int maxConnections = 10;
+    private int maxAuthentications = 5;
+    private String terminalProvider = "";
+    private String authorizationFile = "authorization.xml";
+    private String userConfigDirectory = "%D/.ssh2";
+    private String authenticationBanner = "";
+    private boolean allowTcpForwarding = true;
+    private String currentElement = null;
+    private Class sessionChannelImpl = SessionChannelServer.class;
+
+    /**
+ * Creates a new ServerConfiguration object.
+ *
+ * @param in
+ *
+ * @throws SAXException
+ * @throws ParserConfigurationException
+ * @throws IOException
+ */
+    public ServerConfiguration(InputStream in)
+        throws SAXException, ParserConfigurationException, IOException {
+        reload(in);
+    }
+
+    /**
+ *
+ *
+ * @param in
+ *
+ * @throws SAXException
+ * @throws ParserConfigurationException
+ * @throws IOException
+ */
+    public void reload(InputStream in)
+        throws SAXException, ParserConfigurationException, IOException {
+        allowedSubsystems.clear();
+        serverHostKeys.clear();
+        allowedAuthentications.clear();
+        requiredAuthentications.clear();
+        commandPort = 12222;
+        port = 22;
+        listenAddress = "0.0.0.0";
+        maxConnections = 10;
+        maxAuthentications = 5;
+        terminalProvider = "";
+        authorizationFile = "authorization.xml";
+        userConfigDirectory = "%D/.ssh2";
+        authenticationBanner = "";
+        allowTcpForwarding = true;
+        currentElement = null;
+
+        SAXParserFactory saxFactory = SAXParserFactory.newInstance();
+        SAXParser saxParser = saxFactory.newSAXParser();
+        saxParser.parse(in, this);
+    }
+
+    /**
+ *
+ *
+ * @param uri
+ * @param localName
+ * @param qname
+ * @param attrs
+ *
+ * @throws SAXException
+ */
+    public void startElement(String uri, String localName, String qname,
+        Attributes attrs) throws SAXException {
+        if (currentElement == null) {
+            if (!qname.equals("ServerConfiguration")) {
+                throw new SAXException("Unexpected root element " + qname);
+            }
+        } else {
+            if (currentElement.equals("ServerConfiguration")) {
+                if (qname.equals("ServerHostKey")) {
+                    //String algorithm = attrs.getValue("AlgorithmName");
+                    String privateKey = attrs.getValue("PrivateKeyFile");
+
+                    if (privateKey == null) {
+                        throw new SAXException(
+                            "Required attributes missing from <ServerHostKey> element");
+                    }
+
+                    log.debug("ServerHostKey PrivateKeyFile=" + privateKey);
+
+                    File f = new File(privateKey);
+
+                    if (!f.exists()) {
+                        privateKey = ConfigurationLoader.getConfigurationDirectory() +
+                            privateKey;
+                        f = new File(privateKey);
+                    }
+
+                    try {
+                        if (f.exists()) {
+                            SshPrivateKeyFile pkf = SshPrivateKeyFile.parse(f);
+                            SshPrivateKey key = pkf.toPrivateKey(null);
+                            serverHostKeys.put(key.getAlgorithmName(), key);
+                        } else {
+                            log.warn("Private key file '" + privateKey +
+                                "' could not be found");
+                        }
+                    } catch (InvalidSshKeyException ex) {
+                        log.warn("Failed to load private key '" + privateKey, ex);
+                    } catch (IOException ioe) {
+                        log.warn("Failed to load private key '" + privateKey,
+                            ioe);
+                    }
+                } else if (qname.equals("Subsystem")) {
+                    String type = attrs.getValue("Type");
+                    String name = attrs.getValue("Name");
+                    String provider = attrs.getValue("Provider");
+
+                    if ((type == null) || (name == null) || (provider == null)) {
+                        throw new SAXException(
+                            "Required attributes missing from <Subsystem> element");
+                    }
+
+                    log.debug("Subsystem Type=" + type + " Name=" + name +
+                        " Provider=" + provider);
+                    allowedSubsystems.put(name,
+                        new AllowedSubsystem(type, name, provider));
+                } else if (!qname.equals("AuthenticationBanner") &&
+                        !qname.equals("MaxConnections") &&
+                        !qname.equals("MaxAuthentications") &&
+                        !qname.equals("ListenAddress") &&
+                        !qname.equals("Port") && !qname.equals("CommandPort") &&
+                        !qname.equals("TerminalProvider") &&
+                        !qname.equals("AllowedAuthentication") &&
+                        !qname.equals("RequiredAuthentication") &&
+                        !qname.equals("AuthorizationFile") &&
+                        !qname.equals("UserConfigDirectory") &&
+                        !qname.equals("AllowTcpForwarding")) {
+                    throw new SAXException("Unexpected <" + qname +
+                        "> element after SshAPIConfiguration");
+                }
+            }
+        }
+
+        currentElement = qname;
+    }
+
+    /**
+ *
+ *
+ * @param ch
+ * @param start
+ * @param length
+ *
+ * @throws SAXException
+ */
+    public void characters(char[] ch, int start, int length)
+        throws SAXException {
+        String value = new String(ch, start, length);
+
+        if (currentElement != null) {
+            if (currentElement.equals("AuthenticationBanner")) {
+                authenticationBanner = value;
+                log.debug("AuthenticationBanner=" + authenticationBanner);
+            } else if (currentElement.equals("MaxConnections")) {
+                maxConnections = Integer.parseInt(value);
+                log.debug("MaxConnections=" + value);
+            } else if (currentElement.equals("ListenAddress")) {
+                listenAddress = value;
+                log.debug("ListenAddress=" + listenAddress);
+            } else if (currentElement.equals("Port")) {
+                port = Integer.parseInt(value);
+                log.debug("Port=" + value);
+            } else if (currentElement.equals("CommandPort")) {
+                commandPort = Integer.parseInt(value);
+                log.debug("CommandPort=" + value);
+            } else if (currentElement.equals("TerminalProvider")) {
+                terminalProvider = value;
+                log.debug("TerminalProvider=" + terminalProvider);
+            } else if (currentElement.equals("AllowedAuthentication")) {
+                if (!allowedAuthentications.contains(value)) {
+                    allowedAuthentications.add(value);
+                    log.debug("AllowedAuthentication=" + value);
+                }
+            } else if (currentElement.equals("RequiredAuthentication")) {
+                if (!requiredAuthentications.contains(value)) {
+                    requiredAuthentications.add(value);
+                    log.debug("RequiredAuthentication=" + value);
+                }
+            } else if (currentElement.equals("AuthorizationFile")) {
+                authorizationFile = value;
+                log.debug("AuthorizationFile=" + authorizationFile);
+            } else if (currentElement.equals("UserConfigDirectory")) {
+                userConfigDirectory = value;
+                log.debug("UserConfigDirectory=" + userConfigDirectory);
+            } else if (currentElement.equals("SessionChannelImpl")) {
+                try {
+                    sessionChannelImpl = ConfigurationLoader.getExtensionClass(value);
+                } catch (Exception e) {
+                    log.error("Failed to load SessionChannelImpl " + value, e);
+                }
+            } else if (currentElement.equals("MaxAuthentications")) {
+                maxAuthentications = Integer.parseInt(value);
+                log.debug("MaxAuthentications=" + value);
+            } else if (currentElement.equals("AllowTcpForwarding")) {
+                allowTcpForwarding = Boolean.valueOf(value).booleanValue();
+            }
+        }
+    }
+
+    /**
+ *
+ *
+ * @param uri
+ * @param localName
+ * @param qname
+ *
+ * @throws SAXException
+ */
+    public void endElement(String uri, String localName, String qname)
+        throws SAXException {
+        if (currentElement != null) {
+            if (!currentElement.equals(qname)) {
+                throw new SAXException("Unexpected end element found <" +
+                    qname + ">");
+            } else if (currentElement.equals("ServerConfiguration")) {
+                currentElement = null;
+            } else if (currentElement.equals("AuthenticationBanner") ||
+                    currentElement.equals("ServerHostKey") ||
+                    currentElement.equals("Subsystem") ||
+                    currentElement.equals("MaxConnections") ||
+                    currentElement.equals("MaxAuthentications") ||
+                    currentElement.equals("ListenAddress") ||
+                    currentElement.equals("Port") ||
+                    currentElement.equals("CommandPort") ||
+                    currentElement.equals("TerminalProvider") ||
+                    currentElement.equals("AllowedAuthentication") ||
+                    currentElement.equals("RequiredAuthentication") ||
+                    currentElement.equals("AuthorizationFile") ||
+                    currentElement.equals("UserConfigDirectory") ||
+                    currentElement.equals("AllowTcpForwarding")) {
+                currentElement = "ServerConfiguration";
+            }
+        } else {
+            throw new SAXException("Unexpected end element <" + qname +
+                "> found");
+        }
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public List getRequiredAuthentications() {
+        return requiredAuthentications;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public List getAllowedAuthentications() {
+        return allowedAuthentications;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public boolean getAllowTcpForwarding() {
+        return allowTcpForwarding;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getAuthenticationBanner() {
+        return authenticationBanner;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public int getCommandPort() {
+        return commandPort;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getUserConfigDirectory() {
+        return userConfigDirectory;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getAuthorizationFile() {
+        return authorizationFile;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getListenAddress() {
+        return listenAddress;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public int getMaxConnections() {
+        return maxConnections;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public int getMaxAuthentications() {
+        return maxAuthentications;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public int getPort() {
+        return port;
+    }
+
+    /*public Class getSessionChannelImpl() {
+ return sessionChannelImpl;
+  }*/
+    public Map getServerHostKeys() {
+        return serverHostKeys;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public Map getSubsystems() {
+        return allowedSubsystems;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getTerminalProvider() {
+        return terminalProvider;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String toString() {
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+        xml += "<!-- Server configuration file - If filenames are not absolute they are assummed to be in the same directory as this configuration file. -->\n";
+        xml += "<ServerConfiguration>\n";
+        xml += "   <!-- The available host keys for server authentication -->\n";
+
+        Map.Entry entry;
+        Iterator it = serverHostKeys.entrySet().iterator();
+
+        while (it.hasNext()) {
+            entry = (Map.Entry) it.next();
+            xml += ("   <ServerHostKey PrivateKeyFile=\"" + entry.getValue() +
+            "\"/>\n");
+        }
+
+        xml += "   <!-- Add any number of subsystem elements here -->\n";
+
+        AllowedSubsystem subsystem;
+        it = allowedSubsystems.entrySet().iterator();
+
+        while (it.hasNext()) {
+            subsystem = (AllowedSubsystem) ((Map.Entry) it.next()).getValue();
+            xml += ("   <Subsystem Name=\"" + subsystem.getName() +
+            "\" Type=\"" + subsystem.getType() + "\" Provider=\"" +
+            subsystem.getProvider() + "\"/>\n");
+        }
+
+        xml += "   <!-- Display the following authentication banner before authentication -->\n";
+        xml += ("   <AuthenticationBanner>" + authenticationBanner +
+        "</AuthenticationBanner>\n");
+        xml += "   <!-- The maximum number of connected sessions available -->\n";
+        xml += ("   <MaxConnections>" + String.valueOf(maxConnections) +
+        "</MaxConnections>\n");
+        xml += "   <!-- The maximum number of authentication attemtps for each connection -->\n";
+        xml += ("   <MaxAuthentications>" + String.valueOf(maxAuthentications) +
+        "</MaxAuthentications>\n");
+        xml += "   <!-- Bind to the following address to listen for connections -->\n";
+        xml += ("   <ListenAddress>" + listenAddress + "</ListenAddress>\n");
+        xml += "   <!-- The port to listen to -->\n";
+        xml += ("   <Port>" + String.valueOf(port) + "</Port>\n");
+        xml += "   <!-- Listen on the following port (on localhost) for server commands such as stop -->\n";
+        xml += ("   <CommandPort>" + String.valueOf(commandPort) +
+        "</CommandPort>\n");
+        xml += "   <!-- Specify the executable that provides the default shell -->\n";
+        xml += ("   <TerminalProvider>" + terminalProvider +
+        "</TerminalProvider>\n");
+        xml += "   <!-- Specify any number of allowed authentications -->\n";
+        it = allowedAuthentications.iterator();
+
+        while (it.hasNext()) {
+            xml += ("   <AllowedAuthentication>" + it.next().toString() +
+            "</AllowedAuthentication>\n");
+        }
+
+        xml += "   <!-- Specify any number of required authentications -->\n";
+        it = requiredAuthentications.iterator();
+
+        while (it.hasNext()) {
+            xml += ("   <RequiredAuthentication>" + it.next().toString() +
+            "</RequiredAuthentication>\n");
+        }
+
+        xml += "   <!-- The users authorizations file -->\n";
+        xml += ("   <AuthorizationFile>" + authorizationFile +
+        "</AuthorizationFile>\n");
+        xml += "   <!-- The users configuration directory where files such as AuthorizationFile are found. For users home directory specify %D For users name specify %U  -->\n";
+        xml += ("   <UserConfigDirectory>" + userConfigDirectory +
+        "</UserConfigDirectory>\n");
+        xml += ("<AllowTcpForwarding>" + String.valueOf(allowTcpForwarding) +
+        "</AllowTcpForwarding>\n");
+        xml += "</ServerConfiguration>\n";
+
+        return xml;
+    }
+}
diff --git a/src/com/sshtools/daemon/configuration/SshAPIConfiguration.java b/src/com/sshtools/daemon/configuration/SshAPIConfiguration.java
new file mode 100644
index 0000000000000000000000000000000000000000..9f9c1808d795f67be9c1376265371a7df5d693e4
--- /dev/null
+++ b/src/com/sshtools/daemon/configuration/SshAPIConfiguration.java
@@ -0,0 +1,634 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.configuration;
+
+import com.sshtools.j2ssh.configuration.ExtensionAlgorithm;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class SshAPIConfiguration extends DefaultHandler
+    implements com.sshtools.j2ssh.configuration.SshAPIConfiguration {
+    private String defaultCipher = null;
+    private String defaultMac = null;
+    private String defaultCompression = null;
+    private String defaultPublicKey = null;
+    private String defaultKeyExchange = null;
+    private List cipherExtensions = new ArrayList();
+    private List macExtensions = new ArrayList();
+    private List compressionExtensions = new ArrayList();
+    private List pkExtensions = new ArrayList();
+    private List kexExtensions = new ArrayList();
+    private List authExtensions = new ArrayList();
+    private List pkFormats = new ArrayList();
+    private List prvFormats = new ArrayList();
+    private String defaultPublicFormat = null;
+    private String defaultPrivateFormat = null;
+    private String currentElement = null;
+    private String parentElement = null;
+    private List currentList = null;
+    private ExtensionAlgorithm currentExt = null;
+
+    /**
+ * Creates a new SshAPIConfiguration object.
+ *
+ * @param in
+ *
+ * @throws SAXException
+ * @throws ParserConfigurationException
+ * @throws IOException
+ */
+    public SshAPIConfiguration(InputStream in)
+        throws SAXException, ParserConfigurationException, IOException {
+        reload(in);
+    }
+
+    /**
+ *
+ *
+ * @param in
+ *
+ * @throws SAXException
+ * @throws ParserConfigurationException
+ * @throws IOException
+ */
+    public void reload(InputStream in)
+        throws SAXException, ParserConfigurationException, IOException {
+        defaultCipher = null;
+        defaultMac = null;
+        defaultCompression = null;
+        defaultKeyExchange = null;
+        defaultPublicKey = null;
+        defaultPublicFormat = null;
+        defaultPrivateFormat = null;
+        cipherExtensions.clear();
+        macExtensions.clear();
+        compressionExtensions.clear();
+        pkExtensions.clear();
+        kexExtensions.clear();
+        authExtensions.clear();
+        pkFormats.clear();
+        prvFormats.clear();
+
+        SAXParserFactory saxFactory = SAXParserFactory.newInstance();
+        SAXParser saxParser = saxFactory.newSAXParser();
+        saxParser.parse(in, this);
+    }
+
+    /**
+ *
+ *
+ * @param ch
+ * @param start
+ * @param length
+ *
+ * @throws SAXException
+ */
+    public void characters(char[] ch, int start, int length)
+        throws SAXException {
+        String value = new String(ch, start, length);
+
+        if (currentElement != null) {
+            if (currentElement.equals("AlgorithmName")) {
+                if (currentExt != null) {
+                    currentExt.setAlgorithmName(value);
+                } else {
+                    throw new SAXException("Unexpected AlgorithmName element!");
+                }
+
+                return;
+            }
+
+            if (currentElement.equals("ImplementationClass")) {
+                if (currentExt != null) {
+                    currentExt.setImplementationClass(value);
+                } else {
+                    throw new SAXException(
+                        "Unexpected ImplementationClass element!");
+                }
+
+                return;
+            }
+
+            if (currentElement.equals("DefaultAlgorithm")) {
+                if (parentElement.equals("CipherConfiguration")) {
+                    defaultCipher = value;
+                } else if (parentElement.equals("MacConfiguration")) {
+                    defaultMac = value;
+                } else if (parentElement.equals("CompressionConfiguration")) {
+                    defaultCompression = value;
+                } else if (parentElement.equals("PublicKeyConfiguration")) {
+                    defaultPublicKey = value;
+                } else if (parentElement.equals("KeyExchangeConfiguration")) {
+                    defaultKeyExchange = value;
+                } else {
+                    throw new SAXException(
+                        "Unexpected parent elemenet for DefaultAlgorithm element");
+                }
+            }
+
+            if (currentElement.equals("DefaultPublicFormat")) {
+                defaultPublicFormat = value;
+            }
+
+            if (currentElement.equals("DefaultPrivateFormat")) {
+                defaultPrivateFormat = value;
+            }
+        }
+    }
+
+    /**
+ *
+ *
+ * @param uri
+ * @param localName
+ * @param qname
+ *
+ * @throws SAXException
+ */
+    public void endElement(String uri, String localName, String qname)
+        throws SAXException {
+        if (currentElement != null) {
+            if (!currentElement.equals(qname)) {
+                throw new SAXException("Unexpected end element found " + qname);
+            } else if (currentElement.equals("SshAPIConfiguration")) {
+                currentElement = null;
+            } else if (currentElement.equals("CipherConfiguration") ||
+                    currentElement.equals("MacConfiguration") ||
+                    currentElement.equals("PublicKeyConfiguration") ||
+                    currentElement.equals("CompressionConfiguration") ||
+                    currentElement.equals("KeyExchangeConfiguration") ||
+                    currentElement.equals("AuthenticationConfiguration")) {
+                currentList = null;
+                currentElement = "SshAPIConfiguration";
+            } else if (currentElement.equals("ExtensionAlgorithm")) {
+                if (currentExt == null) {
+                    throw new SAXException(
+                        "Critical error, null extension algortihm");
+                }
+
+                if ((currentExt.getAlgorithmName() == null) ||
+                        (currentExt.getImplementationClass() == null)) {
+                    throw new SAXException(
+                        "Unexpected end of ExtensionAlgorithm Element");
+                }
+
+                currentList.add(currentExt);
+                currentExt = null;
+                currentElement = parentElement;
+            } else if (currentElement.equals("DefaultAlgorithm") ||
+                    currentElement.equals("DefaultPublicFormat") ||
+                    currentElement.equals("DefaultPrivateFormat") ||
+                    currentElement.equals("PublicKeyFormat") ||
+                    currentElement.equals("PrivateKeyFormat")) {
+                currentElement = parentElement;
+            } else if (currentElement.equals("AlgorithmName")) {
+                currentElement = "ExtensionAlgorithm";
+            } else if (currentElement.equals("ImplementationClass")) {
+                currentElement = "ExtensionAlgorithm";
+            } else {
+                throw new SAXException("Unexpected end element " + qname);
+            }
+        }
+    }
+
+    /**
+ *
+ *
+ * @param uri
+ * @param localName
+ * @param qname
+ * @param attrs
+ *
+ * @throws SAXException
+ */
+    public void startElement(String uri, String localName, String qname,
+        Attributes attrs) throws SAXException {
+        if (currentElement == null) {
+            if (!qname.equals("SshAPIConfiguration")) {
+                throw new SAXException("Unexpected root element " + qname);
+            }
+        } else {
+            if (currentElement.equals("SshAPIConfiguration")) {
+                if (!qname.equals("CipherConfiguration") &&
+                        !qname.equals("MacConfiguration") &&
+                        !qname.equals("CompressionConfiguration") &&
+                        !qname.equals("PublicKeyConfiguration") &&
+                        !qname.equals("AuthenticationConfiguration") &&
+                        !qname.equals("KeyExchangeConfiguration")) {
+                    throw new SAXException("Unexpected <" + qname +
+                        "> element after SshAPIConfiguration");
+                }
+            } else if (currentElement.equals("CipherConfiguration")) {
+                if (qname.equals("ExtensionAlgorithm")) {
+                    currentList = cipherExtensions;
+                    parentElement = currentElement;
+                    currentExt = new ExtensionAlgorithm();
+                } else if (qname.equals("DefaultAlgorithm")) {
+                    parentElement = currentElement;
+                } else {
+                    throw new SAXException("Unexpected element <" + qname +
+                        "> found after CipherConfiguration");
+                }
+            } else if (currentElement.equals("MacConfiguration")) {
+                if (qname.equals("ExtensionAlgorithm")) {
+                    currentList = macExtensions;
+                    parentElement = currentElement;
+                    currentExt = new ExtensionAlgorithm();
+                } else if (qname.equals("DefaultAlgorithm")) {
+                    parentElement = currentElement;
+                } else {
+                    throw new SAXException("Unexpected element <" + qname +
+                        "> found after CipherConfiguration");
+                }
+            } else if (currentElement.equals("CompressionConfiguration")) {
+                if (qname.equals("ExtensionAlgorithm")) {
+                    currentList = compressionExtensions;
+                    parentElement = currentElement;
+                    currentExt = new ExtensionAlgorithm();
+                } else if (qname.equals("DefaultAlgorithm")) {
+                    parentElement = currentElement;
+                } else {
+                    throw new SAXException("Unexpected element <" + qname +
+                        "> found after CompressionConfiguration");
+                }
+            } else if (currentElement.equals("PublicKeyConfiguration")) {
+                if (qname.equals("ExtensionAlgorithm")) {
+                    currentList = pkExtensions;
+                    parentElement = currentElement;
+                    currentExt = new ExtensionAlgorithm();
+                } else if (qname.equals("DefaultAlgorithm")) {
+                    parentElement = currentElement;
+                } else if (qname.equals("PublicKeyFormat")) {
+                    String cls = attrs.getValue("ImplementationClass");
+
+                    if (cls == null) {
+                        throw new SAXException(
+                            "<PublicKeyFormat> element requries the ImplementationClass attribute");
+                    }
+
+                    pkFormats.add(cls);
+                } else if (qname.equals("PrivateKeyFormat")) {
+                    String cls = attrs.getValue("ImplementationClass");
+
+                    if (cls == null) {
+                        throw new SAXException(
+                            "<PrivateKeyFormat> element requries the ImplementationClass attribute");
+                    }
+
+                    prvFormats.add(cls);
+                } else if (qname.equals("DefaultPublicFormat")) {
+                    parentElement = currentElement;
+                } else if (qname.equals("DefaultPrivateFormat")) {
+                    parentElement = currentElement;
+                } else {
+                    throw new SAXException("Unexpected element <" + qname +
+                        "> found after PublicKeyConfiguration");
+                }
+            } else if (currentElement.equals("AuthenticationConfiguration")) {
+                if (qname.equals("ExtensionAlgorithm")) {
+                    currentList = authExtensions;
+                    parentElement = currentElement;
+                    currentExt = new ExtensionAlgorithm();
+                } else {
+                    throw new SAXException("Unexpected element <" + qname +
+                        "> found after AuthenticationConfiguration");
+                }
+            } else if (currentElement.equals("KeyExchangeConfiguration")) {
+                if (qname.equals("ExtensionAlgorithm")) {
+                    currentList = kexExtensions;
+                    parentElement = currentElement;
+                    currentExt = new ExtensionAlgorithm();
+                } else if (qname.equals("DefaultAlgorithm")) {
+                    parentElement = currentElement;
+                } else {
+                    throw new SAXException("Unexpected element <" + qname +
+                        "> found after KeyExchangeConfiguration");
+                }
+            } else if ((currentElement.equals("ExtensionAlgorithm") &&
+                    qname.equals("AlgorithmName")) ||
+                    (currentElement.equals("ExtensionAlgorithm") &&
+                    qname.equals("ImplementationClass"))) {
+            } else {
+                throw new SAXException("Unexpected element " + qname);
+            }
+        }
+
+        currentElement = qname;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public List getCompressionExtensions() {
+        return compressionExtensions;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public List getCipherExtensions() {
+        return cipherExtensions;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public List getMacExtensions() {
+        return macExtensions;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public List getAuthenticationExtensions() {
+        return authExtensions;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public List getPublicKeyExtensions() {
+        return pkExtensions;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public List getKeyExchangeExtensions() {
+        return kexExtensions;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getDefaultCipher() {
+        return defaultCipher;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getDefaultMac() {
+        return defaultMac;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getDefaultCompression() {
+        return defaultCompression;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getDefaultPublicKey() {
+        return defaultPublicKey;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getDefaultKeyExchange() {
+        return defaultKeyExchange;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getDefaultPublicKeyFormat() {
+        return defaultPublicFormat;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getDefaultPrivateKeyFormat() {
+        return defaultPrivateFormat;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public List getPublicKeyFormats() {
+        return pkFormats;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public List getPrivateKeyFormats() {
+        return prvFormats;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String toString() {
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+        xml += "<!-- Sshtools J2SSH Configuration file -->\n";
+        xml += "<SshAPIConfiguration>\n";
+        xml += "   <!-- The Cipher configuration, add or overide default cipher implementations -->\n";
+        xml += "   <CipherConfiguration>\n";
+
+        Iterator it = cipherExtensions.iterator();
+        ExtensionAlgorithm ext;
+
+        while (it.hasNext()) {
+            ext = (ExtensionAlgorithm) it.next();
+            xml += "      <ExtensionAlgorithm>\n";
+            xml += ("         <AlgorithmName>" + ext.getAlgorithmName() +
+            "</AlgorithmName>\n");
+            xml += ("         <ImplementationClass>" +
+            ext.getImplementationClass() + "</ImplementationClass>\n");
+            xml += "      </ExtensionAlgorithm>\n";
+        }
+
+        xml += ("      <DefaultAlgorithm>" + defaultCipher +
+        "</DefaultAlgorithm>\n");
+        xml += "   </CipherConfiguration>\n";
+        xml += "   <!-- The Mac configuration, add or overide default mac implementations -->\n";
+        xml += "   <MacConfiguration>\n";
+        it = macExtensions.iterator();
+
+        while (it.hasNext()) {
+            ext = (ExtensionAlgorithm) it.next();
+            xml += "      <ExtensionAlgorithm>\n";
+            xml += ("         <AlgorithmName>" + ext.getAlgorithmName() +
+            "</AlgorithmName>\n");
+            xml += ("         <ImplementationClass>" +
+            ext.getImplementationClass() + "</ImplementationClass>\n");
+            xml += "      </ExtensionAlgorithm>\n";
+        }
+
+        xml += ("      <DefaultAlgorithm>" + defaultMac +
+        "</DefaultAlgorithm>\n");
+        xml += "   </MacConfiguration>\n";
+        xml += "   <!-- The Compression configuration, add or overide default compression implementations -->\n";
+        xml += "   <CompressionConfiguration>\n";
+        it = compressionExtensions.iterator();
+
+        while (it.hasNext()) {
+            ext = (ExtensionAlgorithm) it.next();
+            xml += "      <ExtensionAlgorithm>\n";
+            xml += ("         <AlgorithmName>" + ext.getAlgorithmName() +
+            "</AlgorithmName>\n");
+            xml += ("         <ImplementationClass>" +
+            ext.getImplementationClass() + "</ImplementationClass>\n");
+            xml += "      </ExtensionAlgorithm>\n";
+        }
+
+        xml += ("      <DefaultAlgorithm>" + defaultCompression +
+        "</DefaultAlgorithm>\n");
+        xml += "   </CompressionConfiguration>\n";
+        xml += "   <!-- The Public Key configuration, add or overide default public key implementations -->\n";
+        xml += "   <PublicKeyConfiguration>\n";
+        it = pkExtensions.iterator();
+
+        while (it.hasNext()) {
+            ext = (ExtensionAlgorithm) it.next();
+            xml += "      <ExtensionAlgorithm>\n";
+            xml += ("         <AlgorithmName>" + ext.getAlgorithmName() +
+            "</AlgorithmName>\n");
+            xml += ("         <ImplementationClass>" +
+            ext.getImplementationClass() + "</ImplementationClass>\n");
+            xml += "      </ExtensionAlgorithm>\n";
+        }
+
+        xml += ("      <DefaultAlgorithm>" + defaultPublicKey +
+        "</DefaultAlgorithm>\n");
+        it = pkFormats.iterator();
+
+        String cls;
+
+        while (it.hasNext()) {
+            cls = (String) it.next();
+            xml += ("      <PublicKeyFormat ImplementationClass=\"" + cls +
+            "\"/>\n");
+        }
+
+        it = prvFormats.iterator();
+
+        while (it.hasNext()) {
+            cls = (String) it.next();
+            xml += ("      <PrivateKeyFormat ImplementationClass=\"" + cls +
+            "\"/>\n");
+        }
+
+        xml += ("      <DefaultPublicFormat>" + defaultPublicFormat +
+        "</DefaultPublicFormat>\n");
+        xml += ("      <DefaultPrivateFormat>" + defaultPrivateFormat +
+        "</DefaultPrivateFormat>\n");
+        xml += "   </PublicKeyConfiguration>\n";
+        xml += "   <!-- The Key Exchange configuration, add or overide default key exchange implementations -->\n";
+        xml += "   <KeyExchangeConfiguration>\n";
+        it = kexExtensions.iterator();
+
+        while (it.hasNext()) {
+            ext = (ExtensionAlgorithm) it.next();
+            xml += "      <ExtensionAlgorithm>\n";
+            xml += ("         <AlgorithmName>" + ext.getAlgorithmName() +
+            "</AlgorithmName>\n");
+            xml += ("         <ImplementationClass>" +
+            ext.getImplementationClass() + "</ImplementationClass>\n");
+            xml += "      </ExtensionAlgorithm>\n";
+        }
+
+        xml += ("      <DefaultAlgorithm>" + defaultKeyExchange +
+        "</DefaultAlgorithm>\n");
+        xml += "   </KeyExchangeConfiguration>\n";
+        xml += "   <!-- The Authentication configuration, add or overide default authentication implementations -->\n";
+        xml += "   <AuthenticationConfiguration>\n";
+        it = authExtensions.iterator();
+
+        while (it.hasNext()) {
+            ext = (ExtensionAlgorithm) it.next();
+            xml += "      <ExtensionAlgorithm>\n";
+            xml += ("         <AlgorithmName>" + ext.getAlgorithmName() +
+            "</AlgorithmName>\n");
+            xml += ("         <ImplementationClass>" +
+            ext.getImplementationClass() + "</ImplementationClass>\n");
+            xml += "      </ExtensionAlgorithm>\n";
+        }
+
+        xml += "   </AuthenticationConfiguration>\n";
+        xml += "</SshAPIConfiguration>";
+
+        return xml;
+    }
+}
diff --git a/src/com/sshtools/daemon/configuration/XmlServerConfigurationContext.java b/src/com/sshtools/daemon/configuration/XmlServerConfigurationContext.java
new file mode 100644
index 0000000000000000000000000000000000000000..cbd5848a5cfc49f753acdd46316ae6b4c34bf4b2
--- /dev/null
+++ b/src/com/sshtools/daemon/configuration/XmlServerConfigurationContext.java
@@ -0,0 +1,139 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.configuration;
+
+import com.sshtools.j2ssh.configuration.ConfigurationContext;
+import com.sshtools.j2ssh.configuration.ConfigurationException;
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+
+import java.util.HashMap;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class XmlServerConfigurationContext implements ConfigurationContext {
+    HashMap configurations = new HashMap();
+    String serverResource = null;
+    String platformResource = null;
+    boolean failOnError = true;
+
+    /**
+ * Creates a new XmlServerConfigurationContext object.
+ */
+    public XmlServerConfigurationContext() {
+    }
+
+    /**
+ *
+ *
+ * @param serverResource
+ */
+    public void setServerConfigurationResource(String serverResource) {
+        this.serverResource = serverResource;
+    }
+
+    /**
+ *
+ *
+ * @param platformResource
+ */
+    public void setPlatformConfigurationResource(String platformResource) {
+        this.platformResource = platformResource;
+    }
+
+    /**
+ *
+ *
+ * @param failOnError
+ */
+    public void setFailOnError(boolean failOnError) {
+        this.failOnError = failOnError;
+    }
+
+    /**
+ *
+ *
+ * @throws ConfigurationException
+ */
+    public void initialize() throws ConfigurationException {
+        if (serverResource != null) {
+            try {
+                ServerConfiguration y = new ServerConfiguration(ConfigurationLoader.loadFile(
+                            serverResource));
+                configurations.put(ServerConfiguration.class, y);
+            } catch (Exception ex) {
+                if (failOnError) {
+                    throw new ConfigurationException(ex.getMessage());
+                }
+            }
+        }
+
+        if (platformResource != null) {
+            try {
+                PlatformConfiguration z = new PlatformConfiguration(ConfigurationLoader.loadFile(
+                            platformResource));
+                configurations.put(PlatformConfiguration.class, z);
+            } catch (Exception ex) {
+                if (failOnError) {
+                    throw new ConfigurationException(ex.getMessage());
+                }
+            }
+        }
+    }
+
+    /**
+ *
+ *
+ * @param cls
+ *
+ * @return
+ */
+    public boolean isConfigurationAvailable(Class cls) {
+        return configurations.containsKey(cls);
+    }
+
+    /**
+ *
+ *
+ * @param cls
+ *
+ * @return
+ *
+ * @throws ConfigurationException
+ */
+    public Object getConfiguration(Class cls) throws ConfigurationException {
+        if (configurations.containsKey(cls)) {
+            return configurations.get(cls);
+        } else {
+            throw new ConfigurationException(cls.getName() +
+                " configuration not available");
+        }
+    }
+}
diff --git a/src/com/sshtools/daemon/forwarding/ForwardingServer.java b/src/com/sshtools/daemon/forwarding/ForwardingServer.java
new file mode 100644
index 0000000000000000000000000000000000000000..f90a2477b9413b54276496bfab2203e36222b2ef
--- /dev/null
+++ b/src/com/sshtools/daemon/forwarding/ForwardingServer.java
@@ -0,0 +1,329 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.forwarding;
+
+import com.sshtools.j2ssh.connection.Channel;
+import com.sshtools.j2ssh.connection.ChannelFactory;
+import com.sshtools.j2ssh.connection.ConnectionProtocol;
+import com.sshtools.j2ssh.connection.GlobalRequestHandler;
+import com.sshtools.j2ssh.connection.GlobalRequestResponse;
+import com.sshtools.j2ssh.connection.InvalidChannelException;
+import com.sshtools.j2ssh.forwarding.ForwardingClient;
+import com.sshtools.j2ssh.forwarding.ForwardingConfiguration;
+import com.sshtools.j2ssh.forwarding.ForwardingConfigurationException;
+import com.sshtools.j2ssh.forwarding.ForwardingListener;
+import com.sshtools.j2ssh.forwarding.ForwardingSocketChannel;
+import com.sshtools.j2ssh.io.ByteArrayReader;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+
+import java.net.Socket;
+import java.net.SocketPermission;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class ForwardingServer implements ChannelFactory, GlobalRequestHandler {
+    private static Log log = LogFactory.getLog(ForwardingServer.class);
+    private ConnectionProtocol connection;
+    private List channelTypes = new Vector();
+    private List localForwardings = new Vector();
+    private List remoteForwardings = new Vector();
+
+    /**
+ * Creates a new ForwardingServer object.
+ *
+ * @param connection
+ *
+ * @throws IOException
+ */
+    public ForwardingServer(ConnectionProtocol connection)
+        throws IOException {
+        this.connection = connection;
+        channelTypes.add(ForwardingSocketChannel.LOCAL_FORWARDING_CHANNEL);
+        connection.addChannelFactory(ForwardingSocketChannel.LOCAL_FORWARDING_CHANNEL,
+            this);
+        connection.allowGlobalRequest(ForwardingClient.REMOTE_FORWARD_REQUEST,
+            this);
+        connection.allowGlobalRequest(ForwardingClient.REMOTE_FORWARD_CANCEL_REQUEST,
+            this);
+    }
+
+    /* public List getChannelType() {
+ return channelTypes;
+  }*/
+    public Channel createChannel(String channelType, byte[] requestData)
+        throws InvalidChannelException {
+        if (!channelType.equals(
+                    ForwardingSocketChannel.LOCAL_FORWARDING_CHANNEL)) {
+            throw new InvalidChannelException(
+                "The client can only request the " +
+                "opening of a local forwarding channel");
+        }
+
+        try {
+            ByteArrayReader bar = new ByteArrayReader(requestData);
+            String hostToConnect = bar.readString();
+            int portToConnect = (int) bar.readInt();
+            String originatingHost = bar.readString();
+            int originatingPort = (int) bar.readInt();
+
+            // Get a configuration item for the forwarding
+            ForwardingConfiguration config = getLocalForwardingByAddress(originatingHost,
+                    originatingPort);
+            Socket socket = new Socket(hostToConnect, portToConnect);
+
+            // Create the channel adding it to the active channels
+            ForwardingSocketChannel channel = config.createForwardingSocketChannel(channelType,
+                    hostToConnect, portToConnect, originatingHost,
+                    originatingPort);
+            channel.bindSocket(socket);
+
+            return channel;
+        } catch (ForwardingConfigurationException fce) {
+            throw new InvalidChannelException(
+                "No valid forwarding configuration was available for the request");
+        } catch (IOException ioe) {
+            throw new InvalidChannelException("The channel request data is " +
+                "invalid/or corrupt for channel type " + channelType);
+        }
+    }
+
+    /**
+ *
+ *
+ * @param requestName
+ * @param requestData
+ *
+ * @return
+ */
+    public GlobalRequestResponse processGlobalRequest(String requestName,
+        byte[] requestData) {
+        GlobalRequestResponse response = new GlobalRequestResponse(false);
+        String addressToBind = null;
+        int portToBind = -1;
+        log.debug("Processing " + requestName + " global request");
+
+        try {
+            ByteArrayReader bar = new ByteArrayReader(requestData);
+            addressToBind = bar.readString();
+            portToBind = (int) bar.readInt();
+
+            if (requestName.equals(ForwardingClient.REMOTE_FORWARD_REQUEST)) {
+                addRemoteForwardingConfiguration(addressToBind, portToBind);
+                response = new GlobalRequestResponse(true);
+            }
+
+            if (requestName.equals(
+                        ForwardingClient.REMOTE_FORWARD_CANCEL_REQUEST)) {
+                removeRemoteForwarding(addressToBind, portToBind);
+                response = new GlobalRequestResponse(true);
+            }
+        } catch (IOException ioe) {
+            log.warn("The client failed to request " + requestName + " for " +
+                addressToBind + ":" + String.valueOf(portToBind), ioe);
+        }
+
+        return response;
+    }
+
+    /**
+ *
+ *
+ * @param orginatingAddress
+ * @param originatingPort
+ *
+ * @return
+ *
+ * @throws ForwardingConfigurationException
+ */
+    protected ForwardingConfiguration getLocalForwardingByAddress(
+        String orginatingAddress, int originatingPort)
+        throws ForwardingConfigurationException {
+        try {
+            Iterator it = localForwardings.iterator();
+            ForwardingConfiguration config;
+
+            while (it.hasNext()) {
+                config = (ForwardingConfiguration) it.next();
+
+                if (config.getAddressToBind().equals(orginatingAddress) &&
+                        (config.getPortToBind() == originatingPort)) {
+                    return config;
+                }
+            }
+
+            // No configuration is available so create one
+            config = new ForwardingConfiguration(orginatingAddress,
+                    originatingPort);
+
+            // We must start this configuration in order to use it
+            config.start();
+            localForwardings.add(config);
+
+            return config;
+        } catch (IOException ex) {
+            throw new ForwardingConfigurationException(ex.getMessage());
+        }
+    }
+
+    /**
+ *
+ *
+ * @param addressToBind
+ * @param portToBind
+ *
+ * @return
+ *
+ * @throws ForwardingConfigurationException
+ */
+    protected ForwardingConfiguration getRemoteForwardingByAddress(
+        String addressToBind, int portToBind)
+        throws ForwardingConfigurationException {
+        Iterator it = remoteForwardings.iterator();
+        ForwardingConfiguration config;
+
+        while (it.hasNext()) {
+            config = (ForwardingConfiguration) it.next();
+
+            if (config.getAddressToBind().equals(addressToBind) &&
+                    (config.getPortToBind() == portToBind)) {
+                return config;
+            }
+        }
+
+        throw new ForwardingConfigurationException(
+            "The remote forwarding does not exist!");
+    }
+
+    /**
+ *
+ *
+ * @param addressToBind
+ * @param portToBind
+ *
+ * @throws ForwardingConfigurationException
+ */
+    protected void addRemoteForwardingConfiguration(String addressToBind,
+        int portToBind) throws ForwardingConfigurationException {
+        // Is the server already listening
+        Iterator it = remoteForwardings.iterator();
+        ForwardingConfiguration config;
+
+        while (it.hasNext()) {
+            config = (ForwardingConfiguration) it.next();
+
+            if (config.getAddressToBind().equals(addressToBind) &&
+                    (config.getPortToBind() == portToBind)) {
+                throw new ForwardingConfigurationException(
+                    "The address and port are already in use!");
+            }
+        }
+
+        config = new ForwardingConfiguration(addressToBind, portToBind);
+
+        // Check the security mananger
+        SecurityManager manager = System.getSecurityManager();
+
+        if (manager != null) {
+            try {
+                manager.checkPermission(new SocketPermission(addressToBind +
+                        ":" + String.valueOf(portToBind), "accept,listen"));
+            } catch (SecurityException e) {
+                throw new ForwardingConfigurationException(
+                    "The security manager has denied listen permision on " +
+                    addressToBind + ":" + String.valueOf(portToBind));
+            }
+        }
+
+        try {
+            ForwardingListener listener = new ServerForwardingListener(connection,
+                    addressToBind, portToBind);
+            remoteForwardings.add(listener);
+            listener.start();
+        } catch (IOException ex) {
+            throw new ForwardingConfigurationException(ex.getMessage());
+        }
+    }
+
+    /**
+ *
+ *
+ * @param addressToBind
+ * @param portToBind
+ *
+ * @throws ForwardingConfigurationException
+ */
+    protected void removeRemoteForwarding(String addressToBind, int portToBind)
+        throws ForwardingConfigurationException {
+        ForwardingConfiguration config = getRemoteForwardingByAddress(addressToBind,
+                portToBind);
+
+        // Stop the forwarding
+        config.stop();
+
+        // Remove from the remote forwardings list
+        remoteForwardings.remove(config);
+    }
+
+    class ServerForwardingListener extends ForwardingListener {
+        public ServerForwardingListener(ConnectionProtocol connection,
+            String addressToBind, int portToBind) {
+            super(connection, addressToBind, portToBind);
+        }
+
+        public ForwardingSocketChannel createChannel(String hostToConnect,
+            int portToConnect, Socket socket)
+            throws ForwardingConfigurationException {
+            try {
+                ForwardingSocketChannel channel = createForwardingSocketChannel(ForwardingSocketChannel.REMOTE_FORWARDING_CHANNEL,
+                        hostToConnect, portToConnect,
+                        
+                    /*( (InetSocketAddress) socket.getRemoteSocketAddress()).getAddress()
+          .getHostAddress(),
+          ( (InetSocketAddress) socket.getRemoteSocketAddress())
+          .getPort()*/
+                    socket.getInetAddress().getHostAddress(), socket.getPort());
+                channel.bindSocket(socket);
+
+                return channel;
+            } catch (IOException ex) {
+                throw new ForwardingConfigurationException(ex.getMessage());
+            }
+        }
+    }
+}
diff --git a/src/com/sshtools/daemon/platform/InvalidHandleException.java b/src/com/sshtools/daemon/platform/InvalidHandleException.java
new file mode 100644
index 0000000000000000000000000000000000000000..f18f19db701e82c453e8d3b8b6acaa06a79e58db
--- /dev/null
+++ b/src/com/sshtools/daemon/platform/InvalidHandleException.java
@@ -0,0 +1,44 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.platform;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class InvalidHandleException extends Exception {
+    /**
+ * Creates a new InvalidHandleException object.
+ *
+ * @param msg
+ */
+    public InvalidHandleException(String msg) {
+        super(msg);
+    }
+}
diff --git a/src/com/sshtools/daemon/platform/NativeAuthenticationProvider.java b/src/com/sshtools/daemon/platform/NativeAuthenticationProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..83ea6da13db2322a5ebe93a4ee72f252f08f8971
--- /dev/null
+++ b/src/com/sshtools/daemon/platform/NativeAuthenticationProvider.java
@@ -0,0 +1,155 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.platform;
+
+import com.sshtools.daemon.configuration.*;
+
+import com.sshtools.j2ssh.configuration.*;
+
+import org.apache.commons.logging.*;
+
+import java.io.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public abstract class NativeAuthenticationProvider {
+    private static Log log = LogFactory.getLog(NativeAuthenticationProvider.class);
+    private static Class cls;
+    private static NativeAuthenticationProvider instance;
+
+    static {
+        try {
+            if (ConfigurationLoader.isConfigurationAvailable(
+                        PlatformConfiguration.class)) {
+                cls = ConfigurationLoader.getExtensionClass(((PlatformConfiguration) ConfigurationLoader.getConfiguration(
+                            PlatformConfiguration.class)).getNativeAuthenticationProvider());
+
+                //
+            }
+        } catch (Exception e) {
+            log.error("Failed to load native authentication provider", e);
+            instance = null;
+        }
+    }
+
+    /**
+ *
+ *
+ * @param cls
+ */
+    public static void setProvider(Class cls) {
+        NativeAuthenticationProvider.cls = cls;
+    }
+
+    /**
+ *
+ *
+ * @param username
+ *
+ * @return
+ *
+ * @throws IOException
+ */
+    public abstract String getHomeDirectory(String username)
+        throws IOException;
+
+    /**
+ *
+ *
+ * @param username
+ * @param password
+ *
+ * @return
+ *
+ * @throws PasswordChangeException
+ * @throws IOException
+ */
+    public abstract boolean logonUser(String username, String password)
+        throws PasswordChangeException, IOException;
+
+    /**
+ *
+ *
+ * @param username
+ *
+ * @return
+ *
+ * @throws IOException
+ */
+    public abstract boolean logonUser(String username)
+        throws IOException;
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    public abstract void logoffUser() throws IOException;
+
+    /**
+ *
+ *
+ * @param username
+ * @param oldpassword
+ * @param newpassword
+ *
+ * @return
+ */
+    public abstract boolean changePassword(String username, String oldpassword,
+        String newpassword);
+
+    /**
+ *
+ *
+ * @return
+ *
+ * @throws IOException
+ */
+    public static NativeAuthenticationProvider getInstance()
+        throws IOException {
+        if (instance == null) {
+            try {
+                if (cls == null) {
+                    throw new IOException(
+                        "There is no authentication provider configured");
+                }
+
+                instance = (NativeAuthenticationProvider) cls.newInstance();
+            } catch (Exception e) {
+                throw new IOException(
+                    "The authentication provider failed to initialize: " +
+                    e.getMessage());
+            }
+        }
+
+        return instance;
+    }
+}
diff --git a/src/com/sshtools/daemon/platform/NativeFileSystemProvider.java b/src/com/sshtools/daemon/platform/NativeFileSystemProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..7ff06b024aafe388f9646f949de18494dbc29b7d
--- /dev/null
+++ b/src/com/sshtools/daemon/platform/NativeFileSystemProvider.java
@@ -0,0 +1,368 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.platform;
+
+import com.sshtools.daemon.configuration.*;
+
+import com.sshtools.j2ssh.configuration.*;
+import com.sshtools.j2ssh.io.*;
+import com.sshtools.j2ssh.sftp.*;
+
+import org.apache.commons.logging.*;
+
+import java.io.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public abstract class NativeFileSystemProvider {
+    private static Log log = LogFactory.getLog(NativeAuthenticationProvider.class);
+    private static NativeFileSystemProvider instance;
+
+    /**  */
+    public static final int OPEN_READ = SftpSubsystemClient.OPEN_READ;
+
+    /**  */
+    public static final int OPEN_WRITE = SftpSubsystemClient.OPEN_WRITE;
+
+    /**  */
+    public static final int OPEN_APPEND = SftpSubsystemClient.OPEN_APPEND;
+
+    /**  */
+    public static final int OPEN_CREATE = SftpSubsystemClient.OPEN_CREATE;
+
+    /**  */
+    public static final int OPEN_TRUNCATE = SftpSubsystemClient.OPEN_TRUNCATE;
+
+    /**  */
+    public static final int OPEN_EXCLUSIVE = SftpSubsystemClient.OPEN_EXCLUSIVE;
+
+    static {
+        try {
+            if (ConfigurationLoader.isConfigurationAvailable(
+                        PlatformConfiguration.class)) {
+                Class cls = ConfigurationLoader.getExtensionClass(((PlatformConfiguration) ConfigurationLoader.getConfiguration(
+                            PlatformConfiguration.class)).getNativeFileSystemProvider());
+                instance = (NativeFileSystemProvider) cls.newInstance();
+            }
+        } catch (Exception e) {
+            log.error("Failed to load native file system provider", e);
+            instance = null;
+        }
+    }
+
+    /**
+ *
+ *
+ * @param path
+ *
+ * @return
+ *
+ * @throws PermissionDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+    public abstract boolean fileExists(String path) throws IOException;
+
+    /**
+ *
+ *
+ * @param path
+ *
+ * @return
+ *
+ * @throws PermissionDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+    public abstract String getCanonicalPath(String path)
+        throws IOException, FileNotFoundException;
+
+    /**
+ *
+ *
+ * @param path
+ *
+ * @return
+ *
+ * @throws FileNotFoundException
+ */
+    public abstract String getRealPath(String path)
+        throws FileNotFoundException;
+
+    /**
+ *
+ *
+ * @param path
+ *
+ * @return
+ *
+ * @throws PermissionDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+    public abstract boolean makeDirectory(String path)
+        throws PermissionDeniedException, FileNotFoundException, IOException;
+
+    /**
+ *
+ *
+ * @param path
+ *
+ * @return
+ *
+ * @throws IOException
+ * @throws FileNotFoundException
+ */
+    public abstract FileAttributes getFileAttributes(String path)
+        throws IOException, FileNotFoundException;
+
+    /**
+ *
+ *
+ * @param handle
+ *
+ * @return
+ *
+ * @throws IOException
+ * @throws InvalidHandleException
+ */
+    public abstract FileAttributes getFileAttributes(byte[] handle)
+        throws IOException, InvalidHandleException;
+
+    /**
+ *
+ *
+ * @param path
+ *
+ * @return
+ *
+ * @throws PermissionDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+    public abstract byte[] openDirectory(String path)
+        throws PermissionDeniedException, FileNotFoundException, IOException;
+
+    /**
+ *
+ *
+ * @param handle
+ *
+ * @return
+ *
+ * @throws InvalidHandleException
+ * @throws EOFException
+ */
+    public abstract SftpFile[] readDirectory(byte[] handle)
+        throws InvalidHandleException, EOFException, IOException;
+
+    /**
+ *
+ *
+ * @param path
+ * @param flags
+ * @param attrs
+ *
+ * @return
+ *
+ * @throws PermissionDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+    public abstract byte[] openFile(String path, UnsignedInteger32 flags,
+        FileAttributes attrs)
+        throws PermissionDeniedException, FileNotFoundException, IOException;
+
+    /**
+ *
+ *
+ * @param handle
+ * @param offset
+ * @param len
+ *
+ * @return
+ *
+ * @throws InvalidHandleException
+ * @throws EOFException
+ * @throws IOException
+ */
+    public abstract byte[] readFile(byte[] handle, UnsignedInteger64 offset,
+        UnsignedInteger32 len)
+        throws InvalidHandleException, EOFException, IOException;
+
+    /**
+ *
+ *
+ * @param handle
+ * @param offset
+ * @param data
+ * @param off
+ * @param len
+ *
+ * @throws InvalidHandleException
+ * @throws IOException
+ */
+    public abstract void writeFile(byte[] handle, UnsignedInteger64 offset,
+        byte[] data, int off, int len)
+        throws InvalidHandleException, IOException;
+
+    /**
+ *
+ *
+ * @param handle
+ *
+ * @throws InvalidHandleException
+ * @throws IOException
+ */
+    public abstract void closeFile(byte[] handle)
+        throws InvalidHandleException, IOException;
+
+    /**
+ *
+ *
+ * @param path
+ *
+ * @throws PermissionDeniedException
+ * @throws IOException
+ * @throws FileNotFoundException
+ */
+    public abstract void removeFile(String path)
+        throws PermissionDeniedException, IOException, FileNotFoundException;
+
+    /**
+ *
+ *
+ * @param oldpath
+ * @param newpath
+ *
+ * @throws PermissionDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+    public abstract void renameFile(String oldpath, String newpath)
+        throws PermissionDeniedException, FileNotFoundException, IOException;
+
+    /**
+ *
+ *
+ * @param path
+ *
+ * @throws PermissionDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+    public abstract void removeDirectory(String path)
+        throws PermissionDeniedException, FileNotFoundException, IOException;
+
+    /**
+ *
+ *
+ * @param path
+ * @param attrs
+ *
+ * @throws PermissionDeniedException
+ * @throws IOException
+ * @throws FileNotFoundException
+ */
+    public abstract void setFileAttributes(String path, FileAttributes attrs)
+        throws PermissionDeniedException, IOException, FileNotFoundException;
+
+    /**
+ *
+ *
+ * @param handle
+ * @param attrs
+ *
+ * @throws PermissionDeniedException
+ * @throws IOException
+ * @throws InvalidHandleException
+ */
+    public abstract void setFileAttributes(byte[] handle, FileAttributes attrs)
+        throws PermissionDeniedException, IOException, InvalidHandleException;
+
+    /**
+ *
+ *
+ * @param path
+ *
+ * @return
+ *
+ * @throws UnsupportedFileOperationException
+ * @throws FileNotFoundException
+ * @throws IOException
+ * @throws PermissionDeniedException
+ */
+    public abstract SftpFile readSymbolicLink(String path)
+        throws UnsupportedFileOperationException, FileNotFoundException, 
+            IOException, PermissionDeniedException;
+
+    /**
+ *
+ *
+ * @param link
+ * @param target
+ *
+ * @throws UnsupportedFileOperationException
+ * @throws FileNotFoundException
+ * @throws IOException
+ * @throws PermissionDeniedException
+ */
+    public abstract void createSymbolicLink(String link, String target)
+        throws UnsupportedFileOperationException, FileNotFoundException, 
+            IOException, PermissionDeniedException;
+
+    public abstract String getDefaultPath(String username)
+        throws FileNotFoundException;
+
+    /**
+ *
+ *
+ * @param username
+ * @param path
+ * @param permissions
+ *
+ * @throws PermissionDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+    public abstract void verifyPermissions(String username, String path,
+        String permissions)
+        throws PermissionDeniedException, FileNotFoundException, IOException;
+
+    /**
+ *
+ *
+ * @return
+ */
+    public static NativeFileSystemProvider getInstance() {
+        return instance;
+    }
+}
diff --git a/src/com/sshtools/daemon/platform/NativeProcessProvider.java b/src/com/sshtools/daemon/platform/NativeProcessProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..638fb9624ce59d26613f78d356b3660e4cfa3f76
--- /dev/null
+++ b/src/com/sshtools/daemon/platform/NativeProcessProvider.java
@@ -0,0 +1,187 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.platform;
+
+import com.sshtools.daemon.configuration.PlatformConfiguration;
+
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import java.util.Map;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public abstract class NativeProcessProvider { //implements SessionDataProvider {
+
+    private static Log log = LogFactory.getLog(NativeProcessProvider.class);
+    private static Class provider;
+
+    static {
+        try {
+            if (ConfigurationLoader.isConfigurationAvailable(
+                        PlatformConfiguration.class)) {
+                provider = ConfigurationLoader.getExtensionClass(((PlatformConfiguration) ConfigurationLoader.getConfiguration(
+                            PlatformConfiguration.class)).getNativeProcessProvider());
+            }
+        } catch (Exception e) {
+            log.error("Failed to load native process provider", e);
+            provider = null;
+        }
+    }
+
+    /**
+ *
+ *
+ * @return
+ *
+ * @throws IOException
+ */
+    public static NativeProcessProvider newInstance() throws IOException {
+        try {
+            return (NativeProcessProvider) provider.newInstance();
+        } catch (Exception e) {
+            throw new IOException(
+                "The process provider failed to create a new instance: " +
+                e.getMessage());
+        }
+    }
+
+    /**
+ *
+ *
+ * @param provider
+ */
+    public static void setProvider(Class provider) {
+        NativeProcessProvider.provider = provider;
+    }
+
+    /**
+ *
+ *
+ * @return
+ *
+ * @throws IOException
+ */
+    public abstract InputStream getInputStream() throws IOException;
+
+    /**
+ *
+ *
+ * @return
+ *
+ * @throws IOException
+ */
+    public abstract OutputStream getOutputStream() throws IOException;
+
+    /**
+ *
+ *
+ * @return
+ */
+    public abstract InputStream getStderrInputStream()
+        throws IOException;
+
+    /**
+ *
+ */
+    public abstract void kill();
+
+    /**
+ *
+ *
+ * @return
+ */
+    public abstract boolean stillActive();
+
+    /**
+ *
+ *
+ * @return
+ */
+    public abstract int waitForExitCode();
+
+    /**
+ *
+ *
+ * @return
+ */
+    public abstract String getDefaultTerminalProvider();
+
+    /**
+ *
+ *
+ * @param command
+ * @param environment
+ *
+ * @return
+ *
+ * @throws IOException
+ */
+    public abstract boolean createProcess(String command, Map environment)
+        throws IOException;
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    public abstract void start() throws IOException;
+
+    /**
+ *
+ *
+ * @param term
+ *
+ * @return
+ */
+    public abstract boolean supportsPseudoTerminal(String term);
+
+    /**
+ *
+ *
+ * @param term
+ * @param cols
+ * @param rows
+ * @param width
+ * @param height
+ * @param modes
+ *
+ * @return
+ */
+    public abstract boolean allocatePseudoTerminal(String term, int cols,
+        int rows, int width, int height, String modes);
+}
diff --git a/src/com/sshtools/daemon/platform/PasswordChangeException.java b/src/com/sshtools/daemon/platform/PasswordChangeException.java
new file mode 100644
index 0000000000000000000000000000000000000000..57a9e7373ea7ca997806f953dea90b67debf2473
--- /dev/null
+++ b/src/com/sshtools/daemon/platform/PasswordChangeException.java
@@ -0,0 +1,41 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.platform;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class PasswordChangeException extends Exception {
+    /**
+ * Creates a new PasswordChangeException object.
+ */
+    public PasswordChangeException() {
+    }
+}
diff --git a/src/com/sshtools/daemon/platform/PermissionDeniedException.java b/src/com/sshtools/daemon/platform/PermissionDeniedException.java
new file mode 100644
index 0000000000000000000000000000000000000000..12748c2395fec11f981140b5d651f11307c28d09
--- /dev/null
+++ b/src/com/sshtools/daemon/platform/PermissionDeniedException.java
@@ -0,0 +1,44 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.platform;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class PermissionDeniedException extends Exception {
+    /**
+ * Creates a new PermissionDeniedException object.
+ *
+ * @param msg
+ */
+    public PermissionDeniedException(String msg) {
+        super(msg);
+    }
+}
diff --git a/src/com/sshtools/daemon/platform/UnsupportedFileOperationException.java b/src/com/sshtools/daemon/platform/UnsupportedFileOperationException.java
new file mode 100644
index 0000000000000000000000000000000000000000..02b1af36a9498bfd50978c0bc1eae34fd0f2be4d
--- /dev/null
+++ b/src/com/sshtools/daemon/platform/UnsupportedFileOperationException.java
@@ -0,0 +1,44 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.platform;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class UnsupportedFileOperationException extends Exception {
+    /**
+ * Creates a new UnsupportedFileOperationException object.
+ *
+ * @param msg
+ */
+    public UnsupportedFileOperationException(String msg) {
+        super(msg);
+    }
+}
diff --git a/src/com/sshtools/daemon/scp/ScpServer.java b/src/com/sshtools/daemon/scp/ScpServer.java
new file mode 100644
index 0000000000000000000000000000000000000000..6e7fb5db5e09c1436da78146e370138939ab865e
--- /dev/null
+++ b/src/com/sshtools/daemon/scp/ScpServer.java
@@ -0,0 +1,837 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.scp;
+
+import com.sshtools.daemon.platform.*;
+import com.sshtools.daemon.util.*;
+
+import com.sshtools.j2ssh.*;
+import com.sshtools.j2ssh.io.*;
+import com.sshtools.j2ssh.sftp.*;
+
+import org.apache.commons.logging.*;
+
+import java.io.*;
+
+import java.util.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.8 $
+ */
+public class ScpServer extends NativeProcessProvider implements Runnable {
+    private static Log log = LogFactory.getLog(ScpServer.class);
+    private static int BUFFER_SIZE = 16384;
+
+    //	Private instance variables
+    private InputStream in;
+
+    //	Private instance variables
+    private InputStream err;
+    private OutputStream out;
+    private String destination;
+    private PipedOutputStream pipeIn;
+    private PipedOutputStream pipeErr;
+    private PipedInputStream pipeOut;
+    private SshThread scpServerThread;
+    private int verbosity = 0;
+    private int exitCode;
+    private boolean directory;
+    private boolean recursive;
+    private boolean from;
+    private boolean to;
+    private NativeFileSystemProvider nfs;
+    private byte[] buffer = new byte[BUFFER_SIZE];
+    private String currentDirectory;
+    private boolean preserveAttributes;
+
+    /**
+ * Creates a new ScpServer object.
+ */
+    public ScpServer() {
+        nfs = NativeFileSystemProvider.getInstance();
+    }
+
+    /* (non-Javadoc)
+ * @see com.sshtools.daemon.platform.NativeProcessProvider#allocatePseudoTerminal(java.lang.String, int, int, int, int, java.lang.String)
+ */
+    public boolean allocatePseudoTerminal(String term, int cols, int rows,
+        int width, int height, String modes) {
+        return false;
+    }
+
+    /* (non-Javadoc)
+ * @see com.sshtools.daemon.platform.NativeProcessProvider#createProcess(java.lang.String, java.util.Map)
+ */
+    public boolean createProcess(String command, Map environment)
+        throws IOException {
+        log.info("Creating ScpServer");
+
+        if (nfs == null) {
+            throw new IOException(
+                "NativeFileSystem was not instantiated. Please check logs");
+        }
+
+        scp(command.substring(4));
+
+        return true;
+    }
+
+    /* (non-Javadoc)
+ * @see com.sshtools.daemon.platform.NativeProcessProvider#getDefaultTerminalProvider()
+ */
+    public String getDefaultTerminalProvider() {
+        return null;
+    }
+
+    /* (non-Javadoc)
+ * @see com.sshtools.daemon.platform.NativeProcessProvider#getInputStream()
+ */
+    public InputStream getInputStream() throws IOException {
+        return in;
+    }
+
+    /* (non-Javadoc)
+     * @see com.sshtools.daemon.platform.NativeProcessProvider#getStderrInputStream()
+ */
+    public InputStream getStderrInputStream() {
+        return err;
+    }
+
+    /* (non-Javadoc)
+ * @see com.sshtools.daemon.platform.NativeProcessProvider#getOutputStream()
+ */
+    public OutputStream getOutputStream() throws IOException {
+        return out;
+    }
+
+    /* (non-Javadoc)
+ * @see com.sshtools.daemon.platform.NativeProcessProvider#kill()
+ */
+    public void kill() {
+        log.info("Killing ScpServer");
+
+        try {
+            if (pipeIn != null) {
+                pipeIn.close();
+            }
+        } catch (IOException ioe) {
+        }
+
+        try {
+            if (pipeOut != null) {
+                pipeOut.close();
+            }
+        } catch (IOException ioe) {
+        }
+
+        try {
+            if (pipeErr != null) {
+                pipeErr.close();
+            }
+        } catch (IOException ioe) {
+        }
+    }
+
+    /* (non-Javadoc)
+ * @see com.sshtools.daemon.platform.NativeProcessProvider#start()
+ */
+    public void start() throws IOException {
+        log.debug("Starting ScpServer thread");
+        scpServerThread = SshThread.getCurrentThread().cloneThread(this,
+                "ScpServer");
+        scpServerThread.start();
+    }
+
+    /* (non-Javadoc)
+ * @see com.sshtools.daemon.platform.NativeProcessProvider#stillActive()
+ */
+    public boolean stillActive() {
+        return false;
+    }
+
+    /* (non-Javadoc)
+ * @see com.sshtools.daemon.platform.NativeProcessProvider#supportsPseudoTerminal(java.lang.String)
+ */
+    public boolean supportsPseudoTerminal(String term) {
+        return false;
+    }
+
+    /* (non-Javadoc)
+ * @see com.sshtools.daemon.platform.NativeProcessProvider#waitForExitCode()
+ */
+    public int waitForExitCode() {
+        try {
+            synchronized (this) {
+                wait();
+            }
+        } catch (InterruptedException ie) {
+        }
+
+        log.debug("Returning exit code of " + exitCode);
+
+        return exitCode;
+    }
+
+    private void scp(String args) throws IOException {
+        log.debug("Parsing ScpServer options " + args);
+
+        //	Parse the command line for supported options
+        String[] a = StringUtil.current().allParts(args, " ");
+        destination = null;
+        directory = false;
+        from = false;
+        to = false;
+        recursive = false;
+        verbosity = 0;
+
+        boolean remote = false;
+
+        for (int i = 0; i < a.length; i++) {
+            if (a[i].startsWith("-")) {
+                String s = a[i].substring(1);
+
+                for (int j = 0; j < s.length(); j++) {
+                    char ch = s.charAt(j);
+
+                    switch (ch) {
+                    case 't':
+                        to = true;
+
+                        break;
+
+                    case 'd':
+                        directory = true;
+
+                        break;
+
+                    case 'f':
+                        from = true;
+
+                        break;
+
+                    case 'r':
+                        recursive = true;
+
+                        break;
+
+                    case 'v':
+                        verbosity++;
+
+                        break;
+
+                    case 'p':
+                        preserveAttributes = true;
+
+                        break;
+
+                    default:
+                        log.warn("Unsupported argument, allowing to continue.");
+                    }
+                }
+            } else {
+                if (destination == null) {
+                    destination = a[i];
+                } else {
+                    throw new IOException("More than one destination supplied " +
+                        a[i]);
+                }
+            }
+        }
+
+        if (!to && !from) {
+            throw new IOException("Must supply either -t or -f.");
+        }
+
+        if (destination == null) {
+            throw new IOException("Destination not supplied.");
+        }
+
+        log.debug("Destination is " + destination);
+        log.debug("Recursive is " + recursive);
+        log.debug("Directory is " + directory);
+        log.debug("Verbosity is " + verbosity);
+        log.debug("From is " + from);
+        log.debug("To is " + to);
+        log.debug("Preserve Attributes " + preserveAttributes);
+
+        //	Start the SCP server
+        log.debug("Creating pipes");
+        pipeIn = new PipedOutputStream();
+        pipeErr = new PipedOutputStream();
+        pipeOut = new PipedInputStream();
+        in = new PipedInputStream(pipeIn);
+        err = new PipedInputStream(pipeErr);
+        out = new PipedOutputStream(pipeOut);
+    }
+
+    /**
+ * Send ok command to client
+ *
+ * @throws IOException on any error
+ */
+    private void writeOk() throws IOException {
+        log.debug("Sending client ok command");
+        pipeIn.write(0);
+        pipeIn.flush();
+    }
+
+    /**
+ * Send command to client
+ *
+ * @param cmd command
+ *
+ * @throws IOException on any error
+ */
+    private void writeCommand(String cmd) throws IOException {
+        log.debug("Sending command '" + cmd + "'");
+        pipeIn.write(cmd.getBytes());
+
+        if (!cmd.endsWith("\n")) {
+            pipeIn.write("\n".getBytes());
+        }
+
+        pipeIn.flush();
+    }
+
+    /**
+ * Send error message to client
+ *
+ * @param msg error message
+ *
+ * @throws IOException on any error
+ */
+    private void writeError(String msg) throws IOException {
+        writeError(msg, false);
+    }
+
+    /**
+ * Send error message to client
+ *
+ * @param msg error message
+ * @param serious serious error
+ *
+ * @throws IOException on any error
+ */
+    private void writeError(String msg, boolean serious)
+        throws IOException {
+        log.debug("Sending error message '" + msg + "' to client (serious=" +
+            serious + ")");
+        pipeIn.write(serious ? 2 : 1);
+        pipeIn.write(msg.getBytes());
+
+        if (!msg.endsWith("\n")) {
+            pipeIn.write('\n');
+        }
+
+        pipeIn.flush();
+    }
+
+    /* (non-Javadoc)
+ * @see java.lang.Runnable#run()
+ */
+    public void run() {
+        log.debug("Running ScpServer thread");
+
+        try {
+            if (from) {
+                log.info("From mode");
+
+                try {
+                    waitForResponse();
+
+                    //	Build a string pattern that may be used to match wildcards
+                    StringPattern sp = new StringPattern(destination);
+
+                    /*If this looks like a wildcard, then attempt a simple expansion.
+ * This only work for the base part of the file name at the moment
+ */
+                    if (sp.hasWildcard()) {
+                        log.debug("Path contains wildcard");
+
+                        String base = destination;
+                        String dir = ".";
+                        int idx = base.lastIndexOf('/');
+
+                        if (idx != -1) {
+                            if (idx > 0) {
+                                dir = base.substring(0, idx);
+                            }
+
+                            base = base.substring(idx + 1);
+                        }
+
+                        log.debug("Looking for matches in " + dir + " for " +
+                            base);
+                        sp = new StringPattern(base);
+
+                        byte[] handle = null;
+
+                        try {
+                            handle = nfs.openDirectory(dir);
+
+                            SftpFile[] files = nfs.readDirectory(handle);
+
+                            for (int i = 0; i < files.length; i++) {
+                                log.debug("Testing for match against " +
+                                    files[i].getFilename());
+
+                                if (sp.matches(files[i].getFilename())) {
+                                    log.debug("Matched");
+                                    writeFileToRemote(dir + "/" +
+                                        files[i].getFilename());
+                                } else {
+                                    log.debug("No match");
+                                }
+                            }
+                        } finally {
+                            if (handle != null) {
+                                try {
+                                    nfs.closeFile(handle);
+                                } catch (Exception e) {
+                                }
+                            }
+                        }
+                    } else {
+                        log.debug("No wildcards");
+                        writeFileToRemote(destination);
+                    }
+
+                    log.debug("File transfers complete");
+                } catch (FileNotFoundException fnfe) {
+                    log.error(fnfe);
+                    writeError(fnfe.getMessage(), true);
+                    throw new IOException(fnfe.getMessage());
+                } catch (PermissionDeniedException pde) {
+                    log.error(pde);
+                    writeError(pde.getMessage(), true);
+                    throw new IOException(pde.getMessage());
+                } catch (InvalidHandleException ihe) {
+                    log.error(ihe);
+                    writeError(ihe.getMessage(), true);
+                    throw new IOException(ihe.getMessage());
+                } catch (IOException ioe) {
+                    log.error(ioe);
+                    writeError(ioe.getMessage(), true);
+                    throw new IOException(ioe.getMessage());
+                }
+            } else {
+                log.info("To mode");
+                readFromRemote(destination);
+            }
+        } catch (Throwable t) {
+            t.printStackTrace();
+            log.error(t);
+            exitCode = 1;
+        }
+
+        //
+        log.debug("ScpServer stopped, notify block on waitForExitCode().");
+
+        synchronized (this) {
+            notify();
+        }
+    }
+
+    private boolean writeDirToRemote(String path) throws IOException {
+        FileAttributes attr = nfs.getFileAttributes(path);
+
+        if (attr.isDirectory() && !recursive) {
+            writeError("File " + path + " is a directory, use recursive mode");
+
+            return false;
+        }
+
+        String basename = path;
+        int idx = path.lastIndexOf('/');
+
+        if (idx != -1) {
+            basename = path.substring(idx + 1);
+        }
+
+        writeCommand("D" + attr.getMaskString() + " 0 " + basename + "\n");
+        waitForResponse();
+
+        byte[] handle = null;
+
+        try {
+            handle = nfs.openDirectory(path);
+
+            SftpFile[] list = nfs.readDirectory(handle);
+
+            for (int i = 0; i < list.length; i++) {
+                writeFileToRemote(path + "/" + list[i].getFilename());
+            }
+
+            writeCommand("E");
+        } catch (InvalidHandleException ihe) {
+            throw new IOException(ihe.getMessage());
+        } catch (PermissionDeniedException e) {
+            throw new IOException(e.getMessage());
+        } finally {
+            if (handle != null) {
+                try {
+                    nfs.closeFile(handle);
+                } catch (Exception e) {
+                    log.error(e);
+                }
+            }
+        }
+
+        return true;
+    }
+
+    private void writeFileToRemote(String path)
+        throws IOException, PermissionDeniedException, InvalidHandleException {
+        FileAttributes attr = nfs.getFileAttributes(path);
+
+        if (attr.isDirectory()) {
+            if (!writeDirToRemote(path)) {
+                return;
+            }
+        } else if (attr.isFile()) {
+            String basename = path;
+            int idx = basename.lastIndexOf('/');
+
+            if (idx != -1) {
+                basename = path.substring(idx + 1);
+            }
+
+            // TODO: Deal with permissions properly
+            writeCommand("C" + attr.getMaskString() + " " + attr.getSize() +
+                " " + basename + "\n");
+            waitForResponse();
+            log.debug("Opening file " + path);
+
+            byte[] handle = null;
+
+            try {
+                handle = nfs.openFile(path,
+                        new UnsignedInteger32(
+                            NativeFileSystemProvider.OPEN_READ), attr);
+
+                int count = 0;
+                log.debug("Sending file");
+
+                while (count < attr.getSize().intValue()) {
+                    try {
+                        byte[] buf = nfs.readFile(handle,
+                                new UnsignedInteger64(String.valueOf(count)),
+                                new UnsignedInteger32(BUFFER_SIZE));
+                        count += buf.length;
+                        log.debug("Writing block of " + buf.length + " bytes");
+                        pipeIn.write(buf);
+                    } catch (EOFException eofe) {
+                        log.debug("End of file - finishing transfer");
+
+                        break;
+                    }
+                }
+
+                pipeIn.flush();
+
+                if (count < attr.getSize().intValue()) {
+                    throw new IOException(
+                        "File transfer terminated abnormally.");
+                } else {
+                    log.info("File transfer complete.");
+                }
+
+                writeOk();
+            } finally {
+                if (handle != null) {
+                    try {
+                        nfs.closeFile(handle);
+                    } catch (Exception e) {
+                        log.error(e);
+                    }
+                }
+            }
+        } else {
+            throw new IOException(path + " not valid for SCP.");
+        }
+
+        waitForResponse();
+    }
+
+    private void waitForResponse() throws IOException {
+        log.debug("Waiting for response");
+
+        int r = pipeOut.read();
+
+        if (r == 0) {
+            log.debug("Got Ok");
+
+            // All is well, no error
+            return;
+        }
+
+        if (r == -1) {
+            throw new EOFException("SCP returned unexpected EOF");
+        }
+
+        String msg = readString();
+        log.debug("Got error '" + msg + "'");
+
+        if (r == (byte) '\02') {
+            log.debug("This is a serious error");
+            throw new IOException(msg);
+        }
+
+        throw new IOException("SCP returned an unexpected error: " + msg);
+    }
+
+    private void readFromRemote(String path) throws IOException {
+        String cmd;
+        String[] cmdParts = new String[3];
+        writeOk();
+
+        while (true) {
+            log.debug("Waiting for command");
+
+            try {
+                cmd = readString();
+            } catch (EOFException e) {
+                return;
+            }
+
+            log.debug("Got command '" + cmd + "'");
+
+            char cmdChar = cmd.charAt(0);
+
+            switch (cmdChar) {
+            case 'E':
+                writeOk();
+
+                return;
+
+            case 'T':
+                log.error("SCP time not currently supported");
+                writeError(
+                    "WARNING: This server does not currently support the SCP time command");
+
+                break;
+
+            case 'C':
+            case 'D':
+                parseCommand(cmd, cmdParts);
+
+                FileAttributes attr = null;
+
+                try {
+                    log.debug("Getting attributes for current destination (" +
+                        path + ")");
+                    attr = nfs.getFileAttributes(path);
+                } catch (FileNotFoundException fnfe) {
+                    log.debug("Current destination not found");
+                }
+
+                String targetPath = path;
+                String name = cmdParts[2];
+
+                if ((attr != null) && attr.isDirectory()) {
+                    log.debug("Target is a directory");
+                    targetPath += ('/' + name);
+                }
+
+                FileAttributes targetAttr = null;
+
+                try {
+                    log.debug("Getting attributes for target destination (" +
+                        targetPath + ")");
+                    targetAttr = nfs.getFileAttributes(targetPath);
+                } catch (FileNotFoundException fnfe) {
+                    log.debug("Target destination not found");
+                }
+
+                if (cmdChar == 'D') {
+                    log.debug("Got directory request");
+
+                    if (targetAttr != null) {
+                        if (!targetAttr.isDirectory()) {
+                            String msg = "Invalid target " + name +
+                                ", must be a directory";
+                            writeError(msg);
+                            throw new IOException(msg);
+                        }
+                    } else {
+                        try {
+                            log.debug("Creating directory " + targetPath);
+
+                            if (!nfs.makeDirectory(targetPath)) {
+                                String msg = "Could not create directory: " +
+                                    name;
+                                writeError(msg);
+                                throw new IOException(msg);
+                            } else {
+                                log.debug("Setting permissions on directory");
+                                attr.setPermissionsFromMaskString(cmdParts[0]);
+                            }
+                        } catch (FileNotFoundException e1) {
+                            writeError("File not found");
+                            throw new IOException("File not found");
+                        } catch (PermissionDeniedException e1) {
+                            writeError("Permission denied");
+                            throw new IOException("Permission denied");
+                        }
+                    }
+
+                    readFromRemote(targetPath);
+
+                    continue;
+                }
+
+                log.debug("Opening file for writing");
+
+                byte[] handle = null;
+
+                try {
+                    // Open the file
+                    handle = nfs.openFile(targetPath,
+                            new UnsignedInteger32(NativeFileSystemProvider.OPEN_CREATE |
+                                NativeFileSystemProvider.OPEN_WRITE |
+                                NativeFileSystemProvider.OPEN_TRUNCATE), attr);
+                    log.debug("NFS file opened");
+                    writeOk();
+                    log.debug("Reading from client");
+
+                    int count = 0;
+                    int read;
+                    long length = Long.parseLong(cmdParts[1]);
+
+                    while (count < length) {
+                        read = pipeOut.read(buffer, 0,
+                                (int) (((length - count) < buffer.length)
+                                ? (length - count) : buffer.length));
+
+                        if (read == -1) {
+                            throw new EOFException(
+                                "ScpServer received an unexpected EOF during file transfer");
+                        }
+
+                        log.debug("Got block of " + read);
+                        nfs.writeFile(handle,
+                            new UnsignedInteger64(String.valueOf(count)),
+                            buffer, 0, read);
+                        count += read;
+                    }
+
+                    log.debug("File transfer complete");
+                } catch (InvalidHandleException ihe) {
+                    writeError("Invalid handle.");
+                    throw new IOException("Invalid handle.");
+                } catch (FileNotFoundException e) {
+                    writeError("File not found");
+                    throw new IOException("File not found");
+                } catch (PermissionDeniedException e) {
+                    writeError("Permission denied");
+                    throw new IOException("Permission denied");
+                } finally {
+                    if (handle != null) {
+                        try {
+                            log.debug("Closing handle");
+                            nfs.closeFile(handle);
+                        } catch (Exception e) {
+                        }
+                    }
+                }
+
+                waitForResponse();
+
+                if (preserveAttributes) {
+                    attr.setPermissionsFromMaskString(cmdParts[0]);
+                    log.debug("Setting permissions on directory to " +
+                        attr.getPermissionsString());
+
+                    try {
+                        nfs.setFileAttributes(targetPath, attr);
+                    } catch (Exception e) {
+                        writeError("Failed to set file permissions.");
+
+                        break;
+                    }
+                }
+
+                writeOk();
+
+                break;
+
+            default:
+                writeError("Unexpected cmd: " + cmd);
+                throw new IOException("SCP unexpected cmd: " + cmd);
+            }
+        }
+    }
+
+    private void parseCommand(String cmd, String[] cmdParts)
+        throws IOException {
+        int l;
+        int r;
+        l = cmd.indexOf(' ');
+        r = cmd.indexOf(' ', l + 1);
+
+        if ((l == -1) || (r == -1)) {
+            writeError("Syntax error in cmd");
+            throw new IOException("Syntax error in cmd");
+        }
+
+        cmdParts[0] = cmd.substring(1, l);
+        cmdParts[1] = cmd.substring(l + 1, r);
+        cmdParts[2] = cmd.substring(r + 1);
+    }
+
+    private String readString() throws IOException {
+        int ch;
+        int i = 0;
+
+        while (((ch = pipeOut.read()) != ((int) '\n')) && (ch >= 0)) {
+            buffer[i++] = (byte) ch;
+        }
+
+        if (ch == -1) {
+            throw new EOFException("SCP returned unexpected EOF");
+        }
+
+        if (buffer[0] == (byte) '\n') {
+            throw new IOException("Unexpected <NL>");
+        }
+
+        if ((buffer[0] == (byte) '\02') || (buffer[0] == (byte) '\01')) {
+            String msg = new String(buffer, 1, i - 1);
+
+            if (buffer[0] == (byte) '\02') {
+                throw new IOException(msg);
+            }
+
+            throw new IOException("SCP returned an unexpected error: " + msg);
+        }
+
+        return new String(buffer, 0, i);
+    }
+}
diff --git a/src/com/sshtools/daemon/session/PseudoTerminalWrapper.java b/src/com/sshtools/daemon/session/PseudoTerminalWrapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..d78f4d33fad9e0ea88ae2448c8e87e7200e47f2c
--- /dev/null
+++ b/src/com/sshtools/daemon/session/PseudoTerminalWrapper.java
@@ -0,0 +1,128 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.session;
+
+import com.sshtools.daemon.terminal.*;
+
+import java.io.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class PseudoTerminalWrapper {
+    private InputStream masterIn;
+    private OutputStream masterOut;
+    private InputStream slaveIn;
+    private OutputStream slaveOut;
+    private String term;
+    private int cols;
+    private int rows;
+    private int width;
+    private int height;
+    private String modes;
+    private TerminalIO terminal;
+    private UserInput ui;
+
+    /**
+ * Creates a new PseudoTerminalWrapper object.
+ *
+ * @param term
+ * @param cols
+ * @param rows
+ * @param width
+ * @param height
+ * @param modes
+ */
+    public PseudoTerminalWrapper(String term, int cols, int rows, int width,
+        int height, String modes) {
+        this.term = term;
+        this.cols = cols;
+        this.rows = rows;
+        this.height = height;
+        this.width = width;
+    }
+
+    /**
+ *
+ *
+ * @param masterIn
+ */
+    public void bindMasterInputStream(InputStream masterIn) {
+        this.masterIn = masterIn;
+    }
+
+    /**
+ *
+ *
+ * @param masterOut
+ */
+    public void bindMasterOutputStream(OutputStream masterOut) {
+        this.masterOut = masterOut;
+    }
+
+    /**
+ *
+ *
+ * @param slaveOut
+ */
+    public void bindSlaveOutputStream(OutputStream slaveOut) {
+        this.slaveOut = slaveOut;
+    }
+
+    /**
+ *
+ *
+ * @param slaveIn
+ */
+    public void bindSlaveInputStream(InputStream slaveIn) {
+        this.slaveIn = slaveIn;
+    }
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    public void initialize() throws IOException {
+        this.terminal = new TerminalIO(masterIn, masterOut, term, cols, rows);
+        terminal.bindSlaveInputStream(slaveIn);
+        terminal.bindSlaveOutputStream(slaveOut);
+        ui = new UserInput(terminal, slaveOut);
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public InputStream getMasterInputStream() {
+        return terminal.getMasterInputStream();
+    }
+}
diff --git a/src/com/sshtools/daemon/session/SessionChannelFactory.java b/src/com/sshtools/daemon/session/SessionChannelFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..d772f6170994d8c445c411fb9e98e41abbfff6af
--- /dev/null
+++ b/src/com/sshtools/daemon/session/SessionChannelFactory.java
@@ -0,0 +1,89 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.session;
+
+import com.sshtools.j2ssh.configuration.*;
+import com.sshtools.j2ssh.connection.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class SessionChannelFactory implements ChannelFactory {
+    /**  */
+    public final static String SESSION_CHANNEL = "session";
+    Class sessionChannelImpl;
+
+    /**
+ * Creates a new SessionChannelFactory object.
+ *
+ * @throws ConfigurationException
+ */
+    public SessionChannelFactory() throws ConfigurationException {
+        sessionChannelImpl = SessionChannelServer.class;
+
+        /*ServerConfiguration server = ConfigurationLoader.getServerConfiguration();
+sessionChannelImpl = ConfigurationLoader.getServerConfiguration().getSessionChannelImpl();*/
+    }
+
+    /*public List getChannelType() {
+ List list = new ArrayList();
+ list.add(SessionChannelServer.SESSION_CHANNEL_TYPE);
+ return list;
+  }*/
+    public Channel createChannel(String channelType, byte[] requestData)
+        throws InvalidChannelException {
+        try {
+            if (channelType.equals("session")) {
+                return (Channel) sessionChannelImpl.newInstance();
+            } else {
+                throw new InvalidChannelException(
+                    "Only session channels can be opened by this factory");
+            }
+        } catch (Exception e) {
+            throw new InvalidChannelException(
+                "Failed to create session channel implemented by " +
+                sessionChannelImpl.getName());
+        }
+    }
+
+    /*public void setSessionChannelImpl(Class sessionChannelImpl)
+ throws InvalidChannelException {
+ try {
+     Channel channel = (Channel) sessionChannelImpl.newInstance();
+     if (!(channel instanceof AbstractSessionChannelServer)) {
+    throw new InvalidChannelException(
+        "Class does not extend AbstractSessionChannelServer");
+     }
+ } catch (Exception e) {
+     throw new InvalidChannelException(
+    "Cannot set session channel implementation");
+ }
+  }*/
+}
diff --git a/src/com/sshtools/daemon/session/SessionChannelServer.java b/src/com/sshtools/daemon/session/SessionChannelServer.java
new file mode 100644
index 0000000000000000000000000000000000000000..eee0bf1c8e286fbd63ef2b3f1ddbfadf3773b17a
--- /dev/null
+++ b/src/com/sshtools/daemon/session/SessionChannelServer.java
@@ -0,0 +1,603 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.session;
+
+import com.sshtools.daemon.configuration.*;
+import com.sshtools.daemon.platform.*;
+import com.sshtools.daemon.scp.*;
+import com.sshtools.daemon.subsystem.*;
+
+import com.sshtools.j2ssh.*;
+import com.sshtools.j2ssh.agent.*;
+import com.sshtools.j2ssh.configuration.*;
+import com.sshtools.j2ssh.connection.*;
+import com.sshtools.j2ssh.io.*;
+import com.sshtools.j2ssh.util.*;
+
+import org.apache.commons.logging.*;
+
+import java.io.*;
+
+import java.util.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public class SessionChannelServer extends IOChannel {
+    private static Log log = LogFactory.getLog(SessionChannelServer.class);
+
+    /**  */
+    public final static String SESSION_CHANNEL_TYPE = "session";
+    private static Map allowedSubsystems = new HashMap();
+    private Map environment = new HashMap();
+    private NativeProcessProvider processInstance;
+    private SubsystemServer subsystemInstance;
+    private Thread thread;
+    private IOStreamConnector ios;
+    private ChannelOutputStream stderrOut;
+    private InputStream stderrIn;
+    private ProcessMonitorThread processMonitor;
+    private PseudoTerminalWrapper pty;
+    private SshAgentForwardingListener agent;
+    private ServerConfiguration config;
+
+    /**
+ * Creates a new SessionChannelServer object.
+ *
+ * @throws ConfigurationException
+ */
+    public SessionChannelServer() throws ConfigurationException {
+        super();
+
+        // Load the allowed subsystems from the server configuration
+        config = (ServerConfiguration) ConfigurationLoader.getConfiguration(ServerConfiguration.class);
+        allowedSubsystems.putAll(config.getSubsystems());
+    }
+
+    private void bindStderrInputStream(InputStream stderrIn) {
+        this.stderrIn = stderrIn;
+        ios = new IOStreamConnector(stderrIn, stderrOut);
+    }
+
+    /**
+ *
+ *
+ * @param cols
+ * @param rows
+ * @param width
+ * @param height
+ */
+    protected void onChangeTerminalDimensions(int cols, int rows, int width,
+        int height) {
+    }
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    protected void onChannelClose() throws IOException {
+        // Remove our reference to the agent
+        if (agent != null) {
+            agent.removeReference(this);
+        }
+
+        if (processInstance != null) {
+            if (processInstance.stillActive()) {
+                processInstance.kill();
+            }
+        }
+
+        if (subsystemInstance != null) {
+            subsystemInstance.stop();
+        }
+
+        // If we have a process monitor then get the exit code
+        // and send before we close the channel
+        if (processMonitor != null) {
+            StartStopState state = processMonitor.getStartStopState();
+
+            try {
+                state.waitForState(StartStopState.STOPPED);
+            } catch (InterruptedException ex) {
+                throw new IOException("The process monitor was interrupted");
+            }
+        }
+    }
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    protected void onChannelEOF() throws IOException {
+    }
+
+    /**
+ *
+ *
+ * @param data
+ *
+ * @throws IOException
+ */
+    protected void onChannelExtData(byte[] data) throws IOException {
+        // Do something with the data
+    }
+
+    /**
+ *
+ *
+ * @throws InvalidChannelException
+ */
+    protected void onChannelOpen() throws InvalidChannelException {
+        stderrOut = new ChannelOutputStream(this,
+                new Integer(SshMsgChannelExtendedData.SSH_EXTENDED_DATA_STDERR));
+    }
+
+    /**
+ *
+ *
+ * @param command
+ *
+ * @return
+ *
+ * @throws IOException
+ */
+    protected boolean onExecuteCommand(String command)
+        throws IOException {
+        log.debug("Executing command " + command);
+
+        // Hack for now
+        if (command.startsWith("scp ")) {
+            if (processInstance == null) {
+                processInstance = new ScpServer();
+            }
+        }
+
+        // Create an instance of the native process provider if we n
+        if (processInstance == null) {
+            processInstance = NativeProcessProvider.newInstance();
+        }
+
+        if (processInstance == null) {
+            log.debug("Failed to create process");
+
+            return false;
+        }
+
+        boolean result = processInstance.createProcess(command, environment);
+
+        if (result) {
+            if (pty != null) {
+                // Bind the streams to the pseudo terminal wrapper
+                pty.bindMasterOutputStream(getOutputStream());
+                pty.bindMasterInputStream(getInputStream());
+                pty.bindSlaveInputStream(processInstance.getInputStream());
+                pty.bindSlaveOutputStream(processInstance.getOutputStream());
+
+                // Initialize the terminal
+                pty.initialize();
+
+                // Bind the master output stream of the pty to the session
+                bindInputStream(pty.getMasterInputStream());
+
+                // Bind the processes stderr
+                bindStderrInputStream(processInstance.getStderrInputStream());
+            } else {
+                // Just bind the process streams to the session
+                bindInputStream(processInstance.getInputStream());
+                bindOutputStream(processInstance.getOutputStream());
+                bindStderrInputStream(processInstance.getStderrInputStream());
+            }
+        }
+
+        return result;
+    }
+
+    /**
+ *
+ *
+ * @param term
+ * @param cols
+ * @param rows
+ * @param width
+ * @param height
+ * @param modes
+ *
+ * @return
+ */
+    protected boolean onRequestPseudoTerminal(String term, int cols, int rows,
+        int width, int height, String modes) {
+        try {
+            // Create an instance of the native process provider
+            processInstance = NativeProcessProvider.newInstance();
+
+            if (processInstance.supportsPseudoTerminal(term)) {
+                return processInstance.allocatePseudoTerminal(term, cols, rows,
+                    width, height, modes);
+            } else {
+                pty = new PseudoTerminalWrapper(term, cols, rows, width,
+                        height, modes);
+
+                return true;
+            }
+        } catch (IOException ioe) {
+            log.warn("Failed to allocate pseudo terminal " + term, ioe);
+
+            return false;
+        }
+    }
+
+    /**
+ *
+ *
+ * @param name
+ * @param value
+ */
+    protected void onSetEnvironmentVariable(String name, String value) {
+        environment.put(name, value);
+    }
+
+    /**
+ *
+ *
+ * @return
+ *
+ * @throws IOException
+ */
+    protected boolean onStartShell() throws IOException {
+        String shell = config.getTerminalProvider();
+
+        if (processInstance == null) {
+            processInstance = NativeProcessProvider.newInstance();
+        }
+
+        if ((shell != null) && !shell.trim().equals("")) {
+            int idx = shell.indexOf("%DEFAULT_TERMINAL%");
+
+            if (idx > -1) {
+                shell = ((idx > 0) ? shell.substring(0, idx) : "") +
+                    processInstance.getDefaultTerminalProvider() +
+                    (((idx + 18) < shell.length()) ? shell.substring(idx + 18)
+                                                   : "");
+            }
+        } else {
+            shell = processInstance.getDefaultTerminalProvider();
+        }
+
+        return onExecuteCommand(shell);
+    }
+
+    /**
+ *
+ *
+ * @param subsystem
+ *
+ * @return
+ */
+    protected boolean onStartSubsystem(String subsystem) {
+        boolean result = false;
+
+        try {
+            if (!allowedSubsystems.containsKey(subsystem)) {
+                log.error(subsystem + " Subsystem is not available");
+
+                return false;
+            }
+
+            AllowedSubsystem obj = (AllowedSubsystem) allowedSubsystems.get(subsystem);
+
+            if (obj.getType().equals("class")) {
+                // Create the class implementation and start the subsystem
+                Class cls = Class.forName(obj.getProvider());
+                subsystemInstance = (SubsystemServer) cls.newInstance();
+                subsystemInstance.setSession(this);
+                bindInputStream(subsystemInstance.getInputStream());
+                bindOutputStream(subsystemInstance.getOutputStream());
+
+                return true;
+            } else {
+                // Determine the subsystem provider
+                String provider = obj.getProvider();
+                File f = new File(provider);
+
+                if (!f.exists()) {
+                    provider = ConfigurationLoader.getHomeDirectory() + "bin" +
+                        File.separator + provider;
+                    f = new File(provider);
+
+                    if (!f.exists()) {
+                        log.error("Failed to locate subsystem provider " +
+                            obj.getProvider());
+
+                        return false;
+                    }
+                }
+
+                return onExecuteCommand(provider);
+            }
+        } catch (Exception e) {
+            log.error("Failed to start subsystem " + subsystem, e);
+        }
+
+        return false;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public byte[] getChannelOpenData() {
+        return null;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public byte[] getChannelConfirmationData() {
+        return null;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    protected int getMinimumWindowSpace() {
+        return 1024;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    protected int getMaximumWindowSpace() {
+        return 32648;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    protected int getMaximumPacketSize() {
+        return 32648;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getChannelType() {
+        return SESSION_CHANNEL_TYPE;
+    }
+
+    /**
+ *
+ *
+ * @param requestType
+ * @param wantReply
+ * @param requestData
+ *
+ * @throws IOException
+ */
+    protected void onChannelRequest(String requestType, boolean wantReply,
+        byte[] requestData) throws IOException {
+        log.debug("Channel Request received: " + requestType);
+
+        boolean success = false;
+
+        if (requestType.equals("shell")) {
+            success = onStartShell();
+
+            if (success) {
+                if (wantReply) {
+                    connection.sendChannelRequestSuccess(this);
+                }
+
+                processInstance.start();
+                processMonitor = new ProcessMonitorThread(processInstance);
+            } else if (wantReply) {
+                connection.sendChannelRequestFailure(this);
+            }
+        }
+
+        if (requestType.equals("env")) {
+            ByteArrayReader bar = new ByteArrayReader(requestData);
+            String name = bar.readString();
+            String value = bar.readString();
+            onSetEnvironmentVariable(name, value);
+
+            if (wantReply) {
+                connection.sendChannelRequestSuccess(this);
+            }
+        }
+
+        if (requestType.equals("exec")) {
+            ByteArrayReader bar = new ByteArrayReader(requestData);
+            String command = bar.readString();
+            success = onExecuteCommand(command);
+
+            if (success) {
+                if (wantReply) {
+                    connection.sendChannelRequestSuccess(this);
+                }
+
+                processInstance.start();
+                processMonitor = new ProcessMonitorThread(processInstance);
+            } else if (wantReply) {
+                connection.sendChannelRequestFailure(this);
+            }
+        }
+
+        if (requestType.equals("subsystem")) {
+            ByteArrayReader bar = new ByteArrayReader(requestData);
+            String subsystem = bar.readString();
+            success = onStartSubsystem(subsystem);
+
+            if (success) {
+                if (wantReply) {
+                    connection.sendChannelRequestSuccess(this);
+                }
+
+                if (processInstance != null) {
+                    processInstance.start();
+                    processMonitor = new ProcessMonitorThread(processInstance);
+                } else if (subsystemInstance != null) {
+                    subsystemInstance.start();
+                    processMonitor = new ProcessMonitorThread(subsystemInstance);
+                }
+            } else if (wantReply) {
+                connection.sendChannelRequestFailure(this);
+            }
+        }
+
+        if (requestType.equals("pty-req")) {
+            ByteArrayReader bar = new ByteArrayReader(requestData);
+            String term = bar.readString();
+            int cols = (int) bar.readInt();
+            int rows = (int) bar.readInt();
+            int width = (int) bar.readInt();
+            int height = (int) bar.readInt();
+            String modes = bar.readString();
+            success = onRequestPseudoTerminal(term, cols, rows, width, height,
+                    modes);
+
+            if (wantReply && success) {
+                connection.sendChannelRequestSuccess(this);
+            } else if (wantReply) {
+                connection.sendChannelRequestFailure(this);
+            }
+        }
+
+        if (requestType.equals("window-change")) {
+            ByteArrayReader bar = new ByteArrayReader(requestData);
+            int cols = (int) bar.readInt();
+            int rows = (int) bar.readInt();
+            int width = (int) bar.readInt();
+            int height = (int) bar.readInt();
+            onChangeTerminalDimensions(cols, rows, width, height);
+
+            if (wantReply && success) {
+                connection.sendChannelRequestSuccess(this);
+            } else if (wantReply) {
+                connection.sendChannelRequestFailure(this);
+            }
+        }
+
+        if (requestType.equals("auth-agent-req")) {
+            try {
+                SshThread thread = SshThread.getCurrentThread();
+
+                // Get an agent instance
+                agent = SshAgentForwardingListener.getInstance(thread.getSessionIdString(),
+                        connection);
+
+                // Inform the agent we want to track this reference
+                agent.addReference(this);
+
+                // Set the environment so processes can find the agent
+                environment.put("SSH_AGENT_AUTH", agent.getConfiguration());
+
+                // Set a thread property so other services within this server can find it
+                thread.setProperty("sshtools.agent", agent.getConfiguration());
+
+                if (wantReply) {
+                    connection.sendChannelRequestSuccess(this);
+                }
+            } catch (Exception ex) {
+                if (wantReply) {
+                    connection.sendChannelRequestFailure(this);
+                }
+            }
+        }
+    }
+
+    class ProcessMonitorThread extends Thread {
+        private NativeProcessProvider process;
+        private SubsystemServer subsystem;
+        private StartStopState state;
+
+        public ProcessMonitorThread(NativeProcessProvider process) {
+            this.process = process;
+            state = new StartStopState(StartStopState.STARTED);
+            start();
+        }
+
+        public ProcessMonitorThread(SubsystemServer subsystem) {
+            state = subsystem.getState();
+        }
+
+        public StartStopState getStartStopState() {
+            return state;
+        }
+
+        public void run() {
+            try {
+                log.info("Monitor waiting for process exit code");
+
+                int exitcode = process.waitForExitCode();
+
+                if (exitcode == 9999999) {
+                    log.error("Process monitor failed to retrieve exit code");
+                } else {
+                    log.debug("Process exit code is " +
+                        String.valueOf(exitcode));
+                    process.getInputStream().close();
+                    process.getOutputStream().close();
+                    process.getStderrInputStream().close();
+
+                    ByteArrayWriter baw = new ByteArrayWriter();
+                    baw.writeInt(exitcode);
+
+                    // Send the exit request
+                    if (connection.isConnected() &&
+                            SessionChannelServer.this.isOpen()) {
+                        connection.sendChannelRequest(SessionChannelServer.this,
+                            "exit-status", false, baw.toByteArray());
+                    }
+
+                    // Stop the monitor
+                    state.setValue(StartStopState.STOPPED);
+
+                    // Close the session
+                    SessionChannelServer.this.close();
+                }
+            } catch (IOException ioe) {
+                log.error("Failed to kill process", ioe);
+            }
+        }
+    }
+}
diff --git a/src/com/sshtools/daemon/sftp/SftpSubsystemServer.java b/src/com/sshtools/daemon/sftp/SftpSubsystemServer.java
new file mode 100644
index 0000000000000000000000000000000000000000..644e5848b40cad066baafa0d9af07620c9f4485f
--- /dev/null
+++ b/src/com/sshtools/daemon/sftp/SftpSubsystemServer.java
@@ -0,0 +1,777 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.sftp;
+
+import com.sshtools.daemon.platform.*;
+import com.sshtools.daemon.session.*;
+import com.sshtools.daemon.subsystem.*;
+
+import com.sshtools.j2ssh.*;
+import com.sshtools.j2ssh.connection.*;
+import com.sshtools.j2ssh.io.*;
+import com.sshtools.j2ssh.sftp.*;
+import com.sshtools.j2ssh.subsystem.*;
+
+import org.apache.commons.logging.*;
+
+import java.io.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class SftpSubsystemServer extends SubsystemServer {
+    /**  */
+    public static final int VERSION_1 = 1;
+
+    /**  */
+    public static final int VERSION_2 = 2;
+
+    /**  */
+    public static final int VERSION_3 = 3;
+
+    /**  */
+    public static final int VERSION_4 = 4;
+    private static Log log = LogFactory.getLog(SftpSubsystemServer.class);
+    private NativeFileSystemProvider nfs;
+
+    /**
+ * Creates a new SftpSubsystemServer object.
+ */
+    public SftpSubsystemServer() {
+        registerMessage(SshFxpInit.SSH_FXP_INIT, SshFxpInit.class);
+        registerMessage(SshFxpMkdir.SSH_FXP_MKDIR, SshFxpMkdir.class);
+        registerMessage(SshFxpRealPath.SSH_FXP_REALPATH, SshFxpRealPath.class);
+        registerMessage(SshFxpOpenDir.SSH_FXP_OPENDIR, SshFxpOpenDir.class);
+        registerMessage(SshFxpOpen.SSH_FXP_OPEN, SshFxpOpen.class);
+        registerMessage(SshFxpRead.SSH_FXP_READ, SshFxpRead.class);
+        registerMessage(SshFxpWrite.SSH_FXP_WRITE, SshFxpWrite.class);
+        registerMessage(SshFxpReadDir.SSH_FXP_READDIR, SshFxpReadDir.class);
+        registerMessage(SshFxpClose.SSH_FXP_CLOSE, SshFxpClose.class);
+        registerMessage(SshFxpLStat.SSH_FXP_LSTAT, SshFxpLStat.class);
+        registerMessage(SshFxpStat.SSH_FXP_STAT, SshFxpStat.class);
+        registerMessage(SshFxpRemove.SSH_FXP_REMOVE, SshFxpRemove.class);
+        registerMessage(SshFxpRename.SSH_FXP_RENAME, SshFxpRename.class);
+        registerMessage(SshFxpRmdir.SSH_FXP_RMDIR, SshFxpRmdir.class);
+        registerMessage(SshFxpSetStat.SSH_FXP_SETSTAT, SshFxpSetStat.class);
+        registerMessage(SshFxpFStat.SSH_FXP_FSTAT, SshFxpFStat.class);
+        registerMessage(SshFxpFSetStat.SSH_FXP_FSETSTAT, SshFxpFSetStat.class);
+        registerMessage(SshFxpReadlink.SSH_FXP_READLINK, SshFxpReadlink.class);
+        registerMessage(SshFxpSymlink.SSH_FXP_SYMLINK, SshFxpSymlink.class);
+    }
+
+    /**
+ *
+ *
+ * @param session
+ */
+    public void setSession(SessionChannelServer session) {
+        session.addEventListener(new ChannelEventListener() {
+                public void onChannelOpen(Channel channel) {
+                }
+
+                public void onChannelEOF(Channel channel) {
+                    try {
+                        SftpSubsystemServer.this.session.close();
+                    } catch (IOException ex) {
+                    }
+                }
+
+                public void onChannelClose(Channel channel) {
+                }
+
+                public void onDataReceived(Channel channel, byte[] data) {
+                }
+
+                public void onDataSent(Channel channel, byte[] data) {
+                }
+            });
+        super.setSession(session);
+    }
+
+    /**
+ *
+ *
+ * @param msg
+ */
+    protected void onMessageReceived(SubsystemMessage msg) {
+        switch (msg.getMessageType()) {
+        case SshFxpInit.SSH_FXP_INIT: {
+            onInitialize((SshFxpInit) msg);
+
+            break;
+        }
+
+        case SshFxpMkdir.SSH_FXP_MKDIR: {
+            onMakeDirectory((SshFxpMkdir) msg);
+
+            break;
+        }
+
+        case SshFxpRealPath.SSH_FXP_REALPATH: {
+            onRealPath((SshFxpRealPath) msg);
+
+            break;
+        }
+
+        case SshFxpOpenDir.SSH_FXP_OPENDIR: {
+            onOpenDirectory((SshFxpOpenDir) msg);
+
+            break;
+        }
+
+        case SshFxpOpen.SSH_FXP_OPEN: {
+            onOpenFile((SshFxpOpen) msg);
+
+            break;
+        }
+
+        case SshFxpRead.SSH_FXP_READ: {
+            onReadFile((SshFxpRead) msg);
+
+            break;
+        }
+
+        case SshFxpWrite.SSH_FXP_WRITE: {
+            onWriteFile((SshFxpWrite) msg);
+
+            break;
+        }
+
+        case SshFxpReadDir.SSH_FXP_READDIR: {
+            onReadDirectory((SshFxpReadDir) msg);
+
+            break;
+        }
+
+        case SshFxpLStat.SSH_FXP_LSTAT: {
+            onLStat((SshFxpLStat) msg);
+
+            break;
+        }
+
+        case SshFxpStat.SSH_FXP_STAT: {
+            onStat((SshFxpStat) msg);
+
+            break;
+        }
+
+        case SshFxpFStat.SSH_FXP_FSTAT: {
+            onFStat((SshFxpFStat) msg);
+
+            break;
+        }
+
+        case SshFxpClose.SSH_FXP_CLOSE: {
+            onCloseFile((SshFxpClose) msg);
+
+            break;
+        }
+
+        case SshFxpRemove.SSH_FXP_REMOVE: {
+            onRemoveFile((SshFxpRemove) msg);
+
+            break;
+        }
+
+        case SshFxpRename.SSH_FXP_RENAME: {
+            onRenameFile((SshFxpRename) msg);
+
+            break;
+        }
+
+        case SshFxpRmdir.SSH_FXP_RMDIR: {
+            onRemoveDirectory((SshFxpRmdir) msg);
+
+            break;
+        }
+
+        case SshFxpSetStat.SSH_FXP_SETSTAT: {
+            onSetAttributes((SshFxpSetStat) msg);
+
+            break;
+        }
+
+        case SshFxpFSetStat.SSH_FXP_FSETSTAT: {
+            onSetAttributes((SshFxpFSetStat) msg);
+
+            break;
+        }
+
+        case SshFxpReadlink.SSH_FXP_READLINK: {
+            onReadlink((SshFxpReadlink) msg);
+
+            break;
+        }
+
+        case SshFxpSymlink.SSH_FXP_SYMLINK: {
+            onSymlink((SshFxpSymlink) msg);
+
+            break;
+        }
+
+        default: {
+        }
+        }
+    }
+
+    private void onSetAttributes(SshFxpSetStat msg) {
+        SubsystemMessage reply;
+
+        try {
+            nfs.setFileAttributes(checkDefaultPath(msg.getPath()),
+                msg.getAttributes());
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_OK),
+                    "The attributes were set", "");
+        } catch (FileNotFoundException fnfe) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_NO_SUCH_FILE),
+                    fnfe.getMessage(), "");
+        } catch (PermissionDeniedException pde) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_PERMISSION_DENIED),
+                    pde.getMessage(), "");
+        } catch (IOException ioe) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_FAILURE),
+                    ioe.getMessage(), "");
+        }
+
+        sendMessage(reply);
+    }
+
+    private void onSetAttributes(SshFxpFSetStat msg) {
+        SubsystemMessage reply;
+
+        try {
+            nfs.setFileAttributes(msg.getHandle(), msg.getAttributes());
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_OK),
+                    "The attributes were set", "");
+        } catch (InvalidHandleException ihe) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_FAILURE),
+                    ihe.getMessage(), "");
+        } catch (PermissionDeniedException pde) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_PERMISSION_DENIED),
+                    pde.getMessage(), "");
+        } catch (IOException ioe) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_FAILURE),
+                    ioe.getMessage(), "");
+        }
+
+        sendMessage(reply);
+    }
+
+    private void onReadlink(SshFxpReadlink msg) {
+        SubsystemMessage reply;
+
+        try {
+            /*
+     File f = nfs.readSymbolicLink(VirtualFileSystem.translateVFSPath(
+        msg.getPath()));
+             SftpFile[] files = new SftpFile[1];
+             files[0] = new SftpFile(VirtualFileSystem.translateNFSPath(
+        f.getCanonicalPath()),
+    nfs.getFileAttributes(f.getCanonicalPath()));
+             reply = new SshFxpName(msg.getId(), files);
+ */
+            reply = new SshFxpName(msg.getId(),
+                    new SftpFile[] {
+                        nfs.readSymbolicLink(checkDefaultPath(msg.getPath()))
+                    });
+        } catch (FileNotFoundException ioe) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_NO_SUCH_FILE),
+                    ioe.getMessage(), "");
+        } catch (PermissionDeniedException pde) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_PERMISSION_DENIED),
+                    pde.getMessage(), "");
+        } catch (UnsupportedFileOperationException uso) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_OP_UNSUPPORTED),
+                    uso.getMessage(), "");
+        } catch (IOException ioe2) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_FAILURE),
+                    ioe2.getMessage(), "");
+        }
+
+        sendMessage(reply);
+    }
+
+    private void onSymlink(SshFxpSymlink msg) {
+        SubsystemMessage reply;
+
+        try {
+            /*
+             nfs.createSymbolicLink(VirtualFileSystem.translateVFSPath(
+    msg.getLinkPath()),
+             VirtualFileSystem.translateVFSPath(msg.getTargetPath()));
+ */
+            nfs.createSymbolicLink(checkDefaultPath(msg.getLinkPath()),
+                checkDefaultPath(msg.getTargetPath()));
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_OK),
+                    "The symbolic link was created", "");
+        } catch (FileNotFoundException ioe) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_NO_SUCH_FILE),
+                    ioe.getMessage(), "");
+        } catch (PermissionDeniedException pde) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_PERMISSION_DENIED),
+                    pde.getMessage(), "");
+        } catch (IOException ioe2) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_FAILURE),
+                    ioe2.getMessage(), "");
+        } catch (UnsupportedFileOperationException uso) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_OP_UNSUPPORTED),
+                    uso.getMessage(), "");
+        }
+
+        sendMessage(reply);
+    }
+
+    private void onRemoveDirectory(SshFxpRmdir msg) {
+        SubsystemMessage reply;
+
+        try {
+            /*
+             nfs.removeDirectory(VirtualFileSystem.translateVFSPath(
+    msg.getPath()));
+ */
+            nfs.removeDirectory(checkDefaultPath(msg.getPath()));
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_OK),
+                    "The directory was removed", "");
+        } catch (FileNotFoundException ioe) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_NO_SUCH_FILE),
+                    ioe.getMessage(), "");
+        } catch (IOException ioe2) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_FAILURE),
+                    ioe2.getMessage(), "");
+        } catch (PermissionDeniedException pde) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_PERMISSION_DENIED),
+                    pde.getMessage(), "");
+        }
+
+        sendMessage(reply);
+    }
+
+    private void onRenameFile(SshFxpRename msg) {
+        SubsystemMessage reply;
+
+        try {
+            /*
+     nfs.renameFile(VirtualFileSystem.translateVFSPath(msg.getOldPath()),
+             VirtualFileSystem.translateVFSPath(msg.getNewPath()));
+ */
+            nfs.renameFile(checkDefaultPath(msg.getOldPath()),
+                checkDefaultPath(msg.getNewPath()));
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_OK),
+                    "The file was removed", "");
+        } catch (FileNotFoundException ioe) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_NO_SUCH_FILE),
+                    ioe.getMessage(), "");
+        } catch (IOException ioe2) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_FAILURE),
+                    ioe2.getMessage(), "");
+        } catch (PermissionDeniedException pde) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_PERMISSION_DENIED),
+                    pde.getMessage(), "");
+        }
+
+        sendMessage(reply);
+    }
+
+    private void onRemoveFile(SshFxpRemove msg) {
+        SubsystemMessage reply;
+
+        try {
+            /*
+     nfs.removeFile(VirtualFileSystem.translateVFSPath(msg.getFilename()));
+ */
+            nfs.removeFile(checkDefaultPath(msg.getFilename()));
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_OK),
+                    "The file was removed", "");
+        } catch (FileNotFoundException ioe) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_NO_SUCH_FILE),
+                    ioe.getMessage(), "");
+        } catch (IOException ioe2) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_FAILURE),
+                    ioe2.getMessage(), "");
+        } catch (PermissionDeniedException pde) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_PERMISSION_DENIED),
+                    pde.getMessage(), "");
+        }
+
+        sendMessage(reply);
+    }
+
+    private void onOpenFile(SshFxpOpen msg) {
+        SubsystemMessage reply;
+
+        try {
+            reply = new SshFxpHandle(msg.getId(),
+                    nfs.openFile(checkDefaultPath(msg.getFilename()),
+                        msg.getPflags(), msg.getAttributes()));
+        } catch (FileNotFoundException ioe) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_NO_SUCH_FILE),
+                    ioe.getMessage(), "");
+        } catch (IOException ioe2) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_FAILURE),
+                    ioe2.getMessage(), "");
+        } catch (PermissionDeniedException pde) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_PERMISSION_DENIED),
+                    pde.getMessage(), "");
+        }
+
+        sendMessage(reply);
+    }
+
+    private void onReadFile(SshFxpRead msg) {
+        SubsystemMessage reply;
+
+        try {
+            reply = new SshFxpData(msg.getId(),
+                    nfs.readFile(msg.getHandle(), msg.getOffset(),
+                        msg.getLength()));
+        } catch (EOFException eof) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_EOF),
+                    eof.getMessage(), "");
+        } catch (InvalidHandleException ihe) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_FAILURE),
+                    ihe.getMessage(), "");
+        } catch (IOException ioe2) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_FAILURE),
+                    ioe2.getMessage(), "");
+        }
+
+        sendMessage(reply);
+    }
+
+    private void onWriteFile(SshFxpWrite msg) {
+        SubsystemMessage reply;
+
+        try {
+            nfs.writeFile(msg.getHandle(), msg.getOffset(), msg.getData(), 0,
+                msg.getData().length);
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_OK),
+                    "The write completed successfully", "");
+        } catch (InvalidHandleException ihe) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_FAILURE),
+                    ihe.getMessage(), "");
+        } catch (IOException ioe2) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_FAILURE),
+                    ioe2.getMessage(), "");
+        }
+
+        sendMessage(reply);
+    }
+
+    private void onCloseFile(SshFxpClose msg) {
+        SubsystemMessage reply;
+
+        try {
+            nfs.closeFile(msg.getHandle());
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_OK),
+                    "The operation completed", "");
+        } catch (InvalidHandleException ihe) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_FAILURE),
+                    ihe.getMessage(), "");
+        } catch (IOException ioe2) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_FAILURE),
+                    ioe2.getMessage(), "");
+        }
+
+        sendMessage(reply);
+    }
+
+    private void onFStat(SshFxpFStat msg) {
+        SubsystemMessage reply;
+
+        try {
+            reply = new SshFxpAttrs(msg.getId(),
+                    nfs.getFileAttributes(msg.getHandle()));
+        } catch (InvalidHandleException ihe) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_FAILURE),
+                    ihe.getMessage(), "");
+        } catch (IOException ioe2) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_FAILURE),
+                    ioe2.getMessage(), "");
+        }
+
+        sendMessage(reply);
+    }
+
+    private void onStat(SshFxpStat msg) {
+        SubsystemMessage reply;
+
+        try {
+            String path = checkDefaultPath(msg.getPath());
+
+            if (nfs.fileExists(path)) {
+                SftpFile[] files = new SftpFile[1];
+                reply = new SshFxpAttrs(msg.getId(),
+                        nfs.getFileAttributes(
+                    /*nfs.getCanonicalPath(*/
+                    msg.getPath() /*)*/));
+            } else {
+                reply = new SshFxpStatus(msg.getId(),
+                        new UnsignedInteger32(SshFxpStatus.STATUS_FX_NO_SUCH_FILE),
+                        path + " is not a valid file path", "");
+            }
+        } catch (FileNotFoundException ioe) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_NO_SUCH_FILE),
+                    ioe.getMessage(), "");
+        } catch (IOException ioe2) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_FAILURE),
+                    ioe2.getMessage(), "");
+        }
+
+        sendMessage(reply);
+    }
+
+    private void onLStat(SshFxpLStat msg) {
+        SubsystemMessage reply;
+
+        try {
+            String path = checkDefaultPath(msg.getPath());
+
+            if (nfs.fileExists(path)) {
+                SftpFile[] files = new SftpFile[1];
+                reply = new SshFxpAttrs(msg.getId(),
+                        nfs.getFileAttributes(nfs.getCanonicalPath(
+                                msg.getPath())));
+            } else {
+                reply = new SshFxpStatus(msg.getId(),
+                        new UnsignedInteger32(SshFxpStatus.STATUS_FX_NO_SUCH_FILE),
+                        path + " is not a valid file path", "");
+            }
+        } catch (FileNotFoundException ioe) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_NO_SUCH_FILE),
+                    ioe.getMessage(), "");
+        } catch (IOException ioe2) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_FAILURE),
+                    ioe2.getMessage(), "");
+        }
+
+        sendMessage(reply);
+    }
+
+    private void onReadDirectory(SshFxpReadDir msg) {
+        SubsystemMessage reply;
+
+        try {
+            /*
+             File[] files = nfs.readDirectory(msg.getHandle());
+             SftpFile[] sftpfiles = new SftpFile[files.length];
+             for (int i = 0; i < files.length; i++) {
+             sftpfiles[i] = new SftpFile(files[i].getName(),
+        nfs.getFileAttributes(files[i].getCanonicalPath()));
+             }
+ */
+            SftpFile[] sftpfiles = nfs.readDirectory(msg.getHandle());
+            reply = new SshFxpName(msg.getId(), sftpfiles);
+        } catch (FileNotFoundException ioe) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_NO_SUCH_FILE),
+                    ioe.getMessage(), "");
+        } catch (InvalidHandleException ihe) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_FAILURE),
+                    ihe.getMessage(), "");
+        } catch (EOFException eof) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_EOF),
+                    eof.getMessage(), "");
+        } catch (IOException ioe2) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_FAILURE),
+                    ioe2.getMessage(), "");
+        }
+
+        sendMessage(reply);
+    }
+
+    private void onOpenDirectory(SshFxpOpenDir msg) {
+        SubsystemMessage reply;
+
+        try {
+            /*
+     String path = VirtualFileSystem.translateVFSPath(msg.getPath());
+ */
+            String path = checkDefaultPath(msg.getPath());
+            reply = new SshFxpHandle(msg.getId(), nfs.openDirectory(path));
+        } catch (FileNotFoundException ioe) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_NO_SUCH_FILE),
+                    ioe.getMessage(), "");
+        } catch (IOException ioe2) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_FAILURE),
+                    ioe2.getMessage(), "");
+        } catch (PermissionDeniedException pde) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_PERMISSION_DENIED),
+                    pde.getMessage(), "");
+        }
+
+        sendMessage(reply);
+    }
+
+    private void onRealPath(SshFxpRealPath msg) {
+        SubsystemMessage reply;
+
+        try {
+            /*
+     String path = VirtualFileSystem.translateVFSPath(msg.getPath());
+             path = VirtualFileSystem.translateNFSPath(path);
+ */
+            String path = nfs.getRealPath(checkDefaultPath(msg.getPath()));
+
+            if (path != null) {
+                SftpFile[] files = new SftpFile[1];
+                files[0] = new SftpFile(path);
+                reply = new SshFxpName(msg.getId(), files);
+            } else {
+                reply = new SshFxpStatus(msg.getId(),
+                        new UnsignedInteger32(SshFxpStatus.STATUS_FX_FAILURE),
+                        msg.getPath() +
+                        " could not be translated into a system dependent path",
+                        "");
+            }
+        } catch (FileNotFoundException ioe) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_NO_SUCH_FILE),
+                    ioe.getMessage(), "");
+        } catch (IOException ioe) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_FAILURE),
+                    ioe.getMessage(), "");
+        }
+
+        sendMessage(reply);
+    }
+
+    private void onMakeDirectory(SshFxpMkdir msg) {
+        SubsystemMessage reply;
+
+        try {
+            /*
+     String path = VirtualFileSystem.translateVFSPath(msg.getPath());
+ */
+            String path = checkDefaultPath(msg.getPath());
+
+            if (nfs.makeDirectory(path)) {
+                reply = new SshFxpStatus(msg.getId(),
+                        new UnsignedInteger32(SshFxpStatus.STATUS_FX_OK),
+                        "The operation completed sucessfully", "");
+            } else {
+                // Send an error back to the client
+                reply = new SshFxpStatus(msg.getId(),
+                        new UnsignedInteger32(SshFxpStatus.STATUS_FX_FAILURE),
+                        "The operation failed", "");
+            }
+        } catch (FileNotFoundException ioe) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_NO_SUCH_FILE),
+                    ioe.getMessage(), "");
+        } catch (PermissionDeniedException pde) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_PERMISSION_DENIED),
+                    pde.getMessage(), "");
+        } catch (IOException ioe) {
+            reply = new SshFxpStatus(msg.getId(),
+                    new UnsignedInteger32(SshFxpStatus.STATUS_FX_FAILURE),
+                    ioe.getMessage(), "");
+        }
+
+        sendMessage(reply);
+    }
+
+    private String checkDefaultPath(String path) throws IOException {
+        // Use the users home directory if no path is supplied
+        if (path.equals("")) {
+            return nfs.getDefaultPath(SshThread.getCurrentThreadUser());
+        } else {
+            return path;
+        }
+    }
+
+    private void onInitialize(SshFxpInit msg) {
+        // Get the native file system
+        nfs = NativeFileSystemProvider.getInstance();
+
+        // Determine the users home directory
+        if (msg.getVersion().intValue() == VERSION_3) {
+            SshFxpVersion reply = new SshFxpVersion(new UnsignedInteger32(
+                        VERSION_3), null);
+            sendMessage(reply);
+        } else {
+            // Wrong version
+        }
+    }
+}
diff --git a/src/com/sshtools/daemon/subsystem/SubsystemServer.java b/src/com/sshtools/daemon/subsystem/SubsystemServer.java
new file mode 100644
index 0000000000000000000000000000000000000000..04ba208a2457e35b3242f3667752f472a9c405a9
--- /dev/null
+++ b/src/com/sshtools/daemon/subsystem/SubsystemServer.java
@@ -0,0 +1,173 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.subsystem;
+
+import com.sshtools.daemon.session.*;
+
+import com.sshtools.j2ssh.*;
+import com.sshtools.j2ssh.subsystem.*;
+import com.sshtools.j2ssh.transport.*;
+import com.sshtools.j2ssh.util.*;
+
+import org.apache.commons.logging.*;
+
+import java.io.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public abstract class SubsystemServer implements Runnable {
+    private static Log log = LogFactory.getLog(SubsystemServer.class);
+    private SubsystemMessageStore incoming = new SubsystemMessageStore();
+    private SubsystemMessageStore outgoing = new SubsystemMessageStore();
+    private SubsystemInputStream in = new SubsystemInputStream(outgoing);
+    private SubsystemOutputStream out = new SubsystemOutputStream(incoming);
+    private SshThread thread;
+    private StartStopState state = new StartStopState(StartStopState.STOPPED);
+
+    /**  */
+    protected SessionChannelServer session;
+
+    /**
+ * Creates a new SubsystemServer object.
+ */
+    public SubsystemServer() {
+    }
+
+    /**
+ *
+ *
+ * @param session
+ */
+    public void setSession(SessionChannelServer session) {
+        this.session = session;
+    }
+
+    /**
+ *
+ *
+ * @return
+ *
+ * @throws IOException
+ */
+    public InputStream getInputStream() throws IOException {
+        return in;
+    }
+
+    /**
+ *
+ *
+ * @return
+ *
+ * @throws IOException
+ */
+    public OutputStream getOutputStream() throws IOException {
+        return out;
+    }
+
+    /**
+ *
+ */
+    public void run() {
+        state.setValue(StartStopState.STARTED);
+
+        try {
+            while (state.getValue() == StartStopState.STARTED) {
+                SubsystemMessage msg = incoming.nextMessage();
+
+                if (msg != null) {
+                    onMessageReceived(msg);
+                }
+            }
+        } catch (MessageStoreEOFException meof) {
+        }
+
+        thread = null;
+    }
+
+    /**
+ *
+ */
+    public void start() {
+        if (Thread.currentThread() instanceof SshThread) {
+            thread = ((SshThread) Thread.currentThread()).cloneThread(this,
+                    "SubsystemServer");
+            thread.start();
+        } else {
+            log.error(
+                "Subsystem Server must be called from within an SshThread context");
+            stop();
+        }
+    }
+
+    /**
+ *
+ */
+    public void stop() {
+        state.setValue(StartStopState.STOPPED);
+        incoming.close();
+        outgoing.close();
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public StartStopState getState() {
+        return state;
+    }
+
+    /**
+ *
+ *
+ * @param msg
+ */
+    protected abstract void onMessageReceived(SubsystemMessage msg);
+
+    /**
+ *
+ *
+ * @param messageId
+ * @param implementor
+ */
+    protected void registerMessage(int messageId, Class implementor) {
+        incoming.registerMessage(messageId, implementor);
+    }
+
+    /**
+ *
+ *
+ * @param msg
+ */
+    protected void sendMessage(SubsystemMessage msg) {
+        outgoing.addMessage(msg);
+    }
+}
diff --git a/src/com/sshtools/daemon/terminal/BasicTerminal.java b/src/com/sshtools/daemon/terminal/BasicTerminal.java
new file mode 100644
index 0000000000000000000000000000000000000000..b726fcb9033a8273efa42649515394b6b94d1633
--- /dev/null
+++ b/src/com/sshtools/daemon/terminal/BasicTerminal.java
@@ -0,0 +1,399 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+/**
+ * SSHTools - Java SSH API The contents of this package has been derived from
+ * the TelnetD library available from http://sourceforge.net/projects/telnetd
+ * The original license of the source code is as follows: TelnetD library
+ * (embeddable telnet daemon) Copyright (C) 2000 Dieter Wimberger This library
+ * is free software; you can either redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License version 2.1,1999 as
+ * published by the Free Software Foundation (see copy received along with the
+ * library), or under the terms of the BSD-style license received along with
+ * this library.
+ */
+package com.sshtools.daemon.terminal;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.11 $
+ */
+public abstract class BasicTerminal implements Terminal {
+    //Associations
+
+    /**  */
+    protected Colorizer myColorizer;
+
+    /**
+ * Creates a new BasicTerminal object.
+ */
+    public BasicTerminal() {
+        myColorizer = Colorizer.getReference();
+    }
+
+    //constructor
+    public int translateControlCharacter(int c) {
+        switch (c) {
+        case DEL:
+            return TerminalIO.DELETE;
+
+        case BS:
+            return TerminalIO.BACKSPACE;
+
+        case HT:
+            return TerminalIO.TABULATOR;
+
+        case ESC:
+            return TerminalIO.ESCAPE;
+
+        case SGR:
+            return TerminalIO.COLORINIT;
+
+        case EOT:
+            return TerminalIO.LOGOUTREQUEST;
+
+        default:
+            return c;
+        }
+    }
+
+    //translateControlCharacter
+    public int translateEscapeSequence(int[] buffer) {
+        try {
+            if (buffer[0] == LSB) {
+                switch (buffer[1]) {
+                case A:
+                    return TerminalIO.UP;
+
+                case B:
+                    return TerminalIO.DOWN;
+
+                case C:
+                    return TerminalIO.RIGHT;
+
+                case D:
+                    return TerminalIO.LEFT;
+
+                default:
+                    break;
+                }
+            }
+        } catch (ArrayIndexOutOfBoundsException e) {
+            return TerminalIO.BYTEMISSING;
+        }
+
+        return TerminalIO.UNRECOGNIZED;
+    }
+
+    //translateEscapeSequence
+    public byte[] getCursorMoveSequence(int direction, int times) {
+        byte[] sequence = null;
+
+        if (times == 1) {
+            sequence = new byte[3];
+        } else {
+            sequence = new byte[times * 3];
+        }
+
+        for (int g = 0; g < (times * 3); g++) {
+            sequence[g] = ESC;
+            sequence[g + 1] = LSB;
+
+            switch (direction) {
+            case TerminalIO.UP:
+                sequence[g + 2] = A;
+
+                break;
+
+            case TerminalIO.DOWN:
+                sequence[g + 2] = B;
+
+                break;
+
+            case TerminalIO.RIGHT:
+                sequence[g + 2] = C;
+
+                break;
+
+            case TerminalIO.LEFT:
+                sequence[g + 2] = D;
+
+                break;
+
+            default:
+                break;
+            }
+
+            g = g + 2;
+        }
+
+        return sequence;
+    }
+
+    // getCursorMoveSequence
+    public byte[] getCursorPositioningSequence(int[] pos) {
+        byte[] sequence = null;
+
+        if (pos == TerminalIO.HOME) {
+            sequence = new byte[3];
+            sequence[0] = ESC;
+            sequence[1] = LSB;
+            sequence[2] = H;
+        } else {
+            //first translate integer coords into digits
+            byte[] rowdigits = translateIntToDigitCodes(pos[0]);
+            byte[] columndigits = translateIntToDigitCodes(pos[1]);
+            int offset = 0;
+
+            //now build up the sequence:
+            sequence = new byte[4 + rowdigits.length + columndigits.length];
+            sequence[0] = ESC;
+            sequence[1] = LSB;
+
+            //now copy the digit bytes
+            System.arraycopy(rowdigits, 0, sequence, 2, rowdigits.length);
+
+            //offset is now 2+rowdigits.length
+            offset = 2 + rowdigits.length;
+            sequence[offset] = SEMICOLON;
+            offset++;
+            System.arraycopy(columndigits, 0, sequence, offset,
+                columndigits.length);
+            offset = offset + columndigits.length;
+            sequence[offset] = H;
+        }
+
+        return sequence;
+    }
+
+    //getCursorPositioningSequence
+    public byte[] getEraseSequence(int eraseFunc) {
+        byte[] sequence = null;
+
+        switch (eraseFunc) {
+        case TerminalIO.EEOL:
+            sequence = new byte[3];
+            sequence[0] = ESC;
+            sequence[1] = LSB;
+            sequence[2] = LE;
+
+            break;
+
+        case TerminalIO.EBOL:
+            sequence = new byte[4];
+            sequence[0] = ESC;
+            sequence[1] = LSB;
+            sequence[2] = 49; //Ascii Code of 1
+            sequence[3] = LE;
+
+            break;
+
+        case TerminalIO.EEL:
+            sequence = new byte[4];
+            sequence[0] = ESC;
+            sequence[1] = LSB;
+            sequence[2] = 50; //Ascii Code 2
+            sequence[3] = LE;
+
+            break;
+
+        case TerminalIO.EEOS:
+            sequence = new byte[3];
+            sequence[0] = ESC;
+            sequence[1] = LSB;
+            sequence[2] = SE;
+
+            break;
+
+        case TerminalIO.EBOS:
+            sequence = new byte[4];
+            sequence[0] = ESC;
+            sequence[1] = LSB;
+            sequence[2] = 49; //Ascii Code of 1
+            sequence[3] = SE;
+
+            break;
+
+        case TerminalIO.EES:
+            sequence = new byte[4];
+            sequence[0] = ESC;
+            sequence[1] = LSB;
+            sequence[2] = 50; //Ascii Code of 2
+            sequence[3] = SE;
+
+            break;
+
+        default:
+            break;
+        }
+
+        return sequence;
+    }
+
+    //getEraseSequence
+    public byte[] getSpecialSequence(int function) {
+        byte[] sequence = null;
+
+        switch (function) {
+        case TerminalIO.STORECURSOR:
+            sequence = new byte[2];
+            sequence[0] = ESC;
+            sequence[1] = 55; //Ascii Code of 7
+
+            break;
+
+        case TerminalIO.RESTORECURSOR:
+            sequence = new byte[2];
+            sequence[0] = ESC;
+            sequence[1] = 56; //Ascii Code of 8
+
+            break;
+        }
+
+        return sequence;
+    }
+
+    //getSpecialSequence
+    public byte[] getGRSequence(int type, int param) {
+        byte[] sequence = new byte[0];
+        int offset = 0;
+
+        switch (type) {
+        case TerminalIO.FCOLOR:
+        case TerminalIO.BCOLOR:
+
+            byte[] color = translateIntToDigitCodes(param);
+            sequence = new byte[3 + color.length];
+            sequence[0] = ESC;
+            sequence[1] = LSB;
+
+            //now copy the digit bytes
+            System.arraycopy(color, 0, sequence, 2, color.length);
+
+            //offset is now 2+color.length
+            offset = 2 + color.length;
+            sequence[offset] = 109; //ASCII Code of m
+
+            break;
+
+        case TerminalIO.STYLE:
+
+            byte[] style = translateIntToDigitCodes(param);
+            sequence = new byte[3 + style.length];
+            sequence[0] = ESC;
+            sequence[1] = LSB;
+
+            //now copy the digit bytes
+            System.arraycopy(style, 0, sequence, 2, style.length);
+
+            //offset is now 2+style.length
+            offset = 2 + style.length;
+            sequence[offset] = 109; //ASCII Code of m
+
+            break;
+
+        case TerminalIO.RESET:
+            sequence = new byte[5];
+            sequence[0] = ESC;
+            sequence[1] = LSB;
+            sequence[2] = 52; //ASCII Code of 4
+            sequence[3] = 56; //ASCII Code of 8
+            sequence[4] = 109; //ASCII Code of m
+
+            break;
+        }
+
+        return sequence;
+    }
+
+    //getGRsequence
+    public byte[] getScrollMarginsSequence(int topmargin, int bottommargin) {
+        byte[] sequence = new byte[0];
+
+        if (supportsScrolling()) {
+            //first translate integer coords into digits
+            byte[] topdigits = translateIntToDigitCodes(topmargin);
+            byte[] bottomdigits = translateIntToDigitCodes(bottommargin);
+            int offset = 0;
+
+            //now build up the sequence:
+            sequence = new byte[4 + topdigits.length + bottomdigits.length];
+            sequence[0] = ESC;
+            sequence[1] = LSB;
+
+            //now copy the digit bytes
+            System.arraycopy(topdigits, 0, sequence, 2, topdigits.length);
+
+            //offset is now 2+topdigits.length
+            offset = 2 + topdigits.length;
+            sequence[offset] = SEMICOLON;
+            offset++;
+            System.arraycopy(bottomdigits, 0, sequence, offset,
+                bottomdigits.length);
+            offset = offset + bottomdigits.length;
+            sequence[offset] = r;
+        }
+
+        return sequence;
+    }
+
+    //getScrollMarginsSequence
+    public String format(String str) {
+        return myColorizer.colorize(str, supportsSGR());
+    }
+
+    //format
+    public byte[] getInitSequence() {
+        byte[] sequence = new byte[0];
+
+        return sequence;
+    }
+
+    //getInitSequence
+    public int getAtomicSequenceLength() {
+        return 2;
+    }
+
+    //getAtomicSequenceLength
+    public byte[] translateIntToDigitCodes(int in) {
+        return Integer.toString(in).getBytes();
+    }
+
+    //translateIntToDigitCodes
+    public abstract boolean supportsSGR();
+
+    /**
+ *
+ *
+ * @return
+ */
+    public abstract boolean supportsScrolling();
+}
+
+
+//class BasicTerminal
diff --git a/src/com/sshtools/daemon/terminal/BufferOverflowException.java b/src/com/sshtools/daemon/terminal/BufferOverflowException.java
new file mode 100644
index 0000000000000000000000000000000000000000..fac4a58f9433ab39620407ea8c3d342e93d2d440
--- /dev/null
+++ b/src/com/sshtools/daemon/terminal/BufferOverflowException.java
@@ -0,0 +1,50 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+/**
+ * SSHTools - Java SSH API The contents of this package has been derived from
+ * the TelnetD library available from http://sourceforge.net/projects/telnetd
+ * The original license of the source code is as follows: TelnetD library
+ * (embeddable telnet daemon) Copyright (C) 2000 Dieter Wimberger This library
+ * is free software; you can either redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License version 2.1,1999 as
+ * published by the Free Software Foundation (see copy received along with the
+ * library), or under the terms of the BSD-style license received along with
+ * this library.
+ */
+package com.sshtools.daemon.terminal;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.11 $
+ */
+public class BufferOverflowException extends Exception {
+}
+
+
+//class BufferOverFlowException
diff --git a/src/com/sshtools/daemon/terminal/CharBuffer.java b/src/com/sshtools/daemon/terminal/CharBuffer.java
new file mode 100644
index 0000000000000000000000000000000000000000..ca71df6e5b81bca84f646d0d6a4f8c2c9457bea1
--- /dev/null
+++ b/src/com/sshtools/daemon/terminal/CharBuffer.java
@@ -0,0 +1,115 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+/**
+ * SSHTools - Java SSH API The contents of this package has been derived from
+ * the TelnetD library available from http://sourceforge.net/projects/telnetd
+ * The original license of the source code is as follows: TelnetD library
+ * (embeddable telnet daemon) Copyright (C) 2000 Dieter Wimberger This library
+ * is free software; you can either redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License version 2.1,1999 as
+ * published by the Free Software Foundation (see copy received along with the
+ * library), or under the terms of the BSD-style license received along with
+ * this library.
+ */
+package com.sshtools.daemon.terminal;
+
+import java.util.*;
+
+
+class CharBuffer {
+    //Members
+    private Vector myBuffer;
+    private int mySize;
+
+    /**
+ * Creates a new CharBuffer object.
+ *
+ * @param size
+ */
+    public CharBuffer(int size) {
+        myBuffer = new Vector(size);
+        mySize = size;
+    }
+
+    //constructor
+    public char getCharAt(int pos) throws IndexOutOfBoundsException {
+        return ((Character) myBuffer.elementAt(pos)).charValue();
+    }
+
+    //getCharAt
+    public void setCharAt(int pos, char ch) throws IndexOutOfBoundsException {
+        myBuffer.setElementAt(new Character(ch), pos);
+    }
+
+    //setCharAt
+    public void insertCharAt(int pos, char ch)
+        throws BufferOverflowException, IndexOutOfBoundsException {
+        myBuffer.insertElementAt(new Character(ch), pos);
+    }
+
+    //insertCharAt
+    public void append(char aChar) throws BufferOverflowException {
+        myBuffer.addElement(new Character(aChar));
+    }
+
+    //append
+    public void removeCharAt(int pos) throws IndexOutOfBoundsException {
+        myBuffer.removeElementAt(pos);
+    }
+
+    //removeCharAt
+    public void clear() {
+        myBuffer.removeAllElements();
+    }
+
+    //clear
+    public int size() {
+        return myBuffer.size();
+    }
+
+    //size
+    public String toString() {
+        StringBuffer sbuf = new StringBuffer();
+
+        for (int i = 0; i < myBuffer.size(); i++) {
+            sbuf.append(((Character) myBuffer.elementAt(i)).charValue());
+        }
+
+        return sbuf.toString();
+    }
+
+    //toString
+    public void ensureSpace(int chars) throws BufferOverflowException {
+        if (chars > (mySize - myBuffer.size())) {
+            throw new BufferOverflowException();
+        }
+    }
+
+    //ensureSpace
+}
+
+
+//class CharBuffer
diff --git a/src/com/sshtools/daemon/terminal/ColorHelper.java b/src/com/sshtools/daemon/terminal/ColorHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..13684d1ab61ca06139c19fae84cae04e2036a231
--- /dev/null
+++ b/src/com/sshtools/daemon/terminal/ColorHelper.java
@@ -0,0 +1,190 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+/**
+ * SSHTools - Java SSH API The contents of this package has been derived from
+ * the TelnetD library available from http://sourceforge.net/projects/telnetd
+ * The original license of the source code is as follows: TelnetD library
+ * (embeddable telnet daemon) Copyright (C) 2000 Dieter Wimberger This library
+ * is free software; you can either redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License version 2.1,1999 as
+ * published by the Free Software Foundation (see copy received along with the
+ * library), or under the terms of the BSD-style license received along with
+ * this library.
+ */
+package com.sshtools.daemon.terminal;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class ColorHelper {
+    /**  */
+    public static final String INTERNAL_MARKER = "\001";
+
+    /**  */
+    public static final int MARKER_CODE = 1;
+
+    /**  */
+    public static final String BLACK = "S";
+
+    /**  */
+    public static final String RED = "R";
+
+    /**  */
+    public static final String GREEN = "G";
+
+    /**  */
+    public static final String YELLOW = "Y";
+
+    /**  */
+    public static final String BLUE = "B";
+
+    /**  */
+    public static final String MAGENTA = "M";
+
+    /**  */
+    public static final String CYAN = "C";
+
+    /**  */
+    public static final String white = "W";
+
+    /**  */
+    public static final String BOLD = "f";
+
+    /**  */
+    public static final String BOLD_OFF = "d"; //normal color or normal intensity
+
+    /**  */
+    public static final String ITALIC = "i";
+
+    /**  */
+    public static final String ITALIC_OFF = "j";
+
+    /**  */
+    public static final String UNDERLINED = "u";
+
+    /**  */
+    public static final String UNDERLINED_OFF = "v";
+
+    /**  */
+    public static final String BLINK = "e";
+
+    /**  */
+    public static final String BLINK_OFF = "n";
+
+    /**  */
+    public static final String RESET_ALL = "a";
+
+    /**
+ *
+ *
+ * @param str
+ * @param color
+ *
+ * @return
+ */
+    public static String colorizeText(String str, String color) {
+        return INTERNAL_MARKER + color + str + INTERNAL_MARKER + RESET_ALL;
+    }
+
+    //colorizeText
+    public static String colorizeBackground(String str, String color) {
+        return INTERNAL_MARKER + color.toLowerCase() + str + INTERNAL_MARKER +
+        RESET_ALL;
+    }
+
+    //colorizeBackground
+    public static String colorizeText(String str, String fgc, String bgc) {
+        return INTERNAL_MARKER + fgc + INTERNAL_MARKER + bgc.toLowerCase() +
+        str + INTERNAL_MARKER + RESET_ALL;
+    }
+
+    //colorizeText
+    public static String boldcolorizeText(String str, String color) {
+        return INTERNAL_MARKER + BOLD + INTERNAL_MARKER + color + str +
+        INTERNAL_MARKER + RESET_ALL;
+    }
+
+    //colorizeBoldText
+    public static String boldcolorizeText(String str, String fgc, String bgc) {
+        return INTERNAL_MARKER + BOLD + INTERNAL_MARKER + fgc +
+        INTERNAL_MARKER + bgc.toLowerCase() + str + INTERNAL_MARKER +
+        RESET_ALL;
+    }
+
+    //colorizeBoldText
+    public static String boldText(String str) {
+        return INTERNAL_MARKER + BOLD + str + INTERNAL_MARKER + BOLD_OFF;
+    }
+
+    //boldText
+    public static String italicText(String str) {
+        return INTERNAL_MARKER + ITALIC + str + INTERNAL_MARKER + ITALIC_OFF;
+    }
+
+    //italicText
+    public static String underlinedText(String str) {
+        return INTERNAL_MARKER + UNDERLINED + str + INTERNAL_MARKER +
+        UNDERLINED_OFF;
+    }
+
+    //underlinedText
+    public static String blinkingText(String str) {
+        return INTERNAL_MARKER + BLINK + str + INTERNAL_MARKER + BLINK_OFF;
+    }
+
+    //blinkingText
+    public static long getVisibleLength(String str) {
+        int counter = 0;
+        int parsecursor = 0;
+        int foundcursor = 0;
+        boolean done = false;
+
+        while (!done) {
+            foundcursor = str.indexOf(MARKER_CODE, parsecursor);
+
+            if (foundcursor != -1) {
+                //increment counter
+                counter++;
+
+                //parseon from the next char
+                parsecursor = foundcursor + 1;
+            } else {
+                done = true;
+            }
+        }
+
+        return (str.length() - (counter * 2));
+    }
+
+    //getVisibleLength
+}
+
+
+//class ColorHelper
diff --git a/src/com/sshtools/daemon/terminal/Colorizer.java b/src/com/sshtools/daemon/terminal/Colorizer.java
new file mode 100644
index 0000000000000000000000000000000000000000..832f0bd3d8cbd113a55acf8983649451eaafcee9
--- /dev/null
+++ b/src/com/sshtools/daemon/terminal/Colorizer.java
@@ -0,0 +1,372 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+/**
+ * SSHTools - Java SSH API The contents of this package has been derived from
+ * the TelnetD library available from http://sourceforge.net/projects/telnetd
+ * The original license of the source code is as follows: TelnetD library
+ * (embeddable telnet daemon) Copyright (C) 2000 Dieter Wimberger This library
+ * is free software; you can either redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License version 2.1,1999 as
+ * published by the Free Software Foundation (see copy received along with the
+ * library), or under the terms of the BSD-style license received along with
+ * this library.
+ */
+package com.sshtools.daemon.terminal;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public final class Colorizer {
+    private static Object Self; //Singleton instance reference
+    private static int testcount = 0;
+    private static Colorizer myColorizer;
+
+    //Constants
+    private static final int 
+    /*black*/ S = 30;
+
+    //Constants
+    private static final int s = 40;
+
+    //Constants
+    private static final int 
+    /*red*/ R = 31;
+
+    //Constants
+    private static final int r = 41;
+
+    //Constants
+    private static final int 
+    /*green*/ G = 32;
+
+    //Constants
+    private static final int g = 42;
+
+    //Constants
+    private static final int 
+    /*yellow*/ Y = 33;
+
+    //Constants
+    private static final int y = 43;
+
+    //Constants
+    private static final int 
+    /*blue*/ B = 34;
+
+    //Constants
+    private static final int b = 44;
+
+    //Constants
+    private static final int 
+    /*magenta*/ M = 35;
+
+    //Constants
+    private static final int m = 45;
+
+    //Constants
+    private static final int 
+    /*cyan*/ C = 36;
+
+    //Constants
+    private static final int c = 46;
+
+    //Constants
+    private static final int 
+    /*white*/ W = 37;
+
+    //Constants
+    private static final int w = 47;
+
+    //Constants
+    private static final int 
+    /*bold*/ f = 1;
+
+    //Constants
+    private static final int 
+    /*!bold*/ d = 22;
+
+    //Constants
+    private static final int 
+    /*italic*/ i = 3;
+
+    //Constants
+    private static final int 
+    /*!italic*/ j = 23;
+
+    //Constants
+    private static final int 
+    /*underlined*/ u = 4;
+
+    //Constants
+    private static final int 
+    /*!underlined*/ v = 24;
+
+    //Constants
+    private static final int 
+    /*blink*/ e = 5;
+
+    //Constants
+    private static final int 
+    /*steady*/ n = 25;
+
+    //Constants
+    private static final int 
+    /*hide*/ h = 8;
+
+    //Constants
+    private static final int 
+    /*all out*/ a = 0;
+    private int[] colortranslation; //translation table
+    private int leng;
+
+    private Colorizer() {
+        colortranslation = new int[128];
+        colortranslation[83] = S;
+        colortranslation[82] = R;
+        colortranslation[71] = G;
+        colortranslation[89] = Y;
+        colortranslation[66] = B;
+        colortranslation[77] = M;
+        colortranslation[67] = C;
+        colortranslation[87] = W;
+        colortranslation[115] = s;
+        colortranslation[114] = r;
+        colortranslation[103] = g;
+        colortranslation[121] = y;
+        colortranslation[98] = b;
+        colortranslation[109] = m;
+        colortranslation[99] = c;
+        colortranslation[119] = w;
+        colortranslation[102] = f;
+        colortranslation[100] = d;
+        colortranslation[105] = i;
+        colortranslation[106] = j;
+        colortranslation[117] = u;
+        colortranslation[118] = v;
+        colortranslation[101] = e;
+        colortranslation[110] = n;
+        colortranslation[104] = h;
+        colortranslation[97] = a;
+        Self = this;
+    }
+
+    //constructor
+    public String colorize(String str, boolean support) {
+        StringBuffer out = new StringBuffer(str.length() + 20);
+        int parsecursor = 0;
+        int foundcursor = 0;
+        boolean done = false;
+
+        while (!done) {
+            foundcursor = str.indexOf(ColorHelper.MARKER_CODE, parsecursor);
+
+            if (foundcursor != -1) {
+                out.append(str.substring(parsecursor, foundcursor));
+
+                if (support) {
+                    out.append(addEscapeSequence(str.substring(foundcursor + 1,
+                                foundcursor + 2)));
+                }
+
+                parsecursor = foundcursor + 2;
+            } else {
+                out.append(str.substring(parsecursor, str.length()));
+                done = true;
+            }
+        }
+
+        /*
+ * This will always add a "reset all" escape sequence
+ * behind the input string.
+ * Basically this is a good idea, because developers tend to
+ * forget writing colored strings properly.
+ */
+        if (support) {
+            out.append(addEscapeSequence("a"));
+        }
+
+        return out.toString();
+    }
+
+    //colorize
+    private String addEscapeSequence(String attribute) {
+        StringBuffer tmpbuf = new StringBuffer(10);
+        byte[] tmpbytes = attribute.getBytes();
+        int key = (int) tmpbytes[0];
+        tmpbuf.append((char) 27);
+        tmpbuf.append((char) 91);
+        tmpbuf.append((new Integer(colortranslation[key])).toString());
+        tmpbuf.append((char) 109);
+
+        return tmpbuf.toString();
+    }
+
+    //addEscapeSequence
+    public static Colorizer getReference() {
+        if (Self != null) {
+            return (Colorizer) Self;
+        } else {
+            return new Colorizer();
+        }
+    }
+
+    //getReference
+    private static void announceResult(boolean res) {
+        if (res) {
+            System.out.println("[#" + testcount + "] ok.");
+        } else {
+            System.out.println("[#" + testcount +
+                "] failed (see possible StackTrace).");
+        }
+    }
+
+    //announceResult
+    private static void announceTest(String what) {
+        testcount++;
+        System.out.println("Test #" + testcount + " [" + what + "]:");
+    }
+
+    //announceTest
+    private static void bfcolorTest(String color) {
+        System.out.println("->" +
+            myColorizer.colorize(ColorHelper.boldcolorizeText("COLOR", color),
+                true) + "<-");
+    }
+
+    //bfcolorTest
+    private static void fcolorTest(String color) {
+        System.out.println("->" +
+            myColorizer.colorize(ColorHelper.colorizeText("COLOR", color), true) +
+            "<-");
+    }
+
+    //fcolorTest
+    private static void bcolorTest(String color) {
+        System.out.println("->" +
+            myColorizer.colorize(ColorHelper.colorizeBackground("     ", color),
+                true) + "<-");
+    }
+
+    //bcolorTest
+    public static void main(String[] args) {
+        try {
+            announceTest("Instantiation");
+            myColorizer = Colorizer.getReference();
+            announceResult(true);
+            announceTest("Textcolor Tests");
+            fcolorTest(ColorHelper.BLACK);
+            fcolorTest(ColorHelper.RED);
+            fcolorTest(ColorHelper.GREEN);
+            fcolorTest(ColorHelper.YELLOW);
+            fcolorTest(ColorHelper.BLUE);
+            fcolorTest(ColorHelper.MAGENTA);
+            fcolorTest(ColorHelper.CYAN);
+            fcolorTest(ColorHelper.white);
+            announceResult(true);
+            announceTest("Bold textcolor Tests");
+            bfcolorTest(ColorHelper.BLACK);
+            bfcolorTest(ColorHelper.RED);
+            bfcolorTest(ColorHelper.GREEN);
+            bfcolorTest(ColorHelper.YELLOW);
+            bfcolorTest(ColorHelper.BLUE);
+            bfcolorTest(ColorHelper.MAGENTA);
+            bfcolorTest(ColorHelper.CYAN);
+            bfcolorTest(ColorHelper.white);
+            announceResult(true);
+            announceTest("Background Tests");
+            bcolorTest(ColorHelper.BLACK);
+            bcolorTest(ColorHelper.RED);
+            bcolorTest(ColorHelper.GREEN);
+            bcolorTest(ColorHelper.YELLOW);
+            bcolorTest(ColorHelper.BLUE);
+            bcolorTest(ColorHelper.MAGENTA);
+            bcolorTest(ColorHelper.CYAN);
+            bcolorTest(ColorHelper.white);
+            announceResult(true);
+            announceTest("Mixed Color Tests");
+            System.out.println("->" +
+                myColorizer.colorize(ColorHelper.colorizeText("COLOR",
+                        ColorHelper.white, ColorHelper.BLUE), true) + "<-");
+            System.out.println("->" +
+                myColorizer.colorize(ColorHelper.colorizeText("COLOR",
+                        ColorHelper.YELLOW, ColorHelper.GREEN), true) + "<-");
+            System.out.println("->" +
+                myColorizer.colorize(ColorHelper.boldcolorizeText("COLOR",
+                        ColorHelper.white, ColorHelper.BLUE), true) + "<-");
+            System.out.println("->" +
+                myColorizer.colorize(ColorHelper.boldcolorizeText("COLOR",
+                        ColorHelper.YELLOW, ColorHelper.GREEN), true) + "<-");
+            announceResult(true);
+            announceTest("Style Tests");
+            System.out.println("->" +
+                myColorizer.colorize(ColorHelper.boldText("Bold"), true) +
+                "<-");
+            System.out.println("->" +
+                myColorizer.colorize(ColorHelper.italicText("Italic"), true) +
+                "<-");
+            System.out.println("->" +
+                myColorizer.colorize(ColorHelper.underlinedText("Underlined"),
+                    true) + "<-");
+            System.out.println("->" +
+                myColorizer.colorize(ColorHelper.blinkingText("Blinking"), true) +
+                "<-");
+            announceResult(true);
+            announceTest("Visible length test");
+
+            String colorized = ColorHelper.boldcolorizeText("STRING",
+                    ColorHelper.YELLOW);
+            System.out.println("->" + myColorizer.colorize(colorized, true) +
+                "<-");
+            System.out.println("Visible length=" +
+                ColorHelper.getVisibleLength(colorized));
+            colorized = ColorHelper.boldcolorizeText("BANNER",
+                    ColorHelper.white, ColorHelper.BLUE) +
+                ColorHelper.colorizeText("COLOR", ColorHelper.white,
+                    ColorHelper.BLUE) + ColorHelper.underlinedText("UNDER");
+            System.out.println("->" + myColorizer.colorize(colorized, true) +
+                "<-");
+            System.out.println("Visible length=" +
+                ColorHelper.getVisibleLength(colorized));
+            announceResult(true);
+
+            if (false) {
+                throw new Exception(); //this will shut up jikes
+            }
+        } catch (Exception e) {
+            announceResult(false);
+            e.printStackTrace();
+        }
+    }
+
+    //main (test routine)
+}
+
+
+//class Colorizer
diff --git a/src/com/sshtools/daemon/terminal/Editline.java b/src/com/sshtools/daemon/terminal/Editline.java
new file mode 100644
index 0000000000000000000000000000000000000000..5f17dcd4ca31d70eb2c338f02fb39283ad1afd14
--- /dev/null
+++ b/src/com/sshtools/daemon/terminal/Editline.java
@@ -0,0 +1,503 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+/**
+ * SSHTools - Java SSH API The contents of this package has been derived from
+ * the TelnetD library available from http://sourceforge.net/projects/telnetd
+ * The original license of the source code is as follows: TelnetD library
+ * (embeddable telnet daemon) Copyright (C) 2000 Dieter Wimberger This library
+ * is free software; you can either redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License version 2.1,1999 as
+ * published by the Free Software Foundation (see copy received along with the
+ * library), or under the terms of the BSD-style license received along with
+ * this library.
+ */
+package com.sshtools.daemon.terminal;
+
+import java.io.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class Editline {
+    //Aggregations (inner class!)
+    private Buffer buf;
+
+    //Members
+    private TerminalIO myIO;
+    private int Cursor = 0;
+    private boolean InsertMode = true;
+    private int lastSize = 0;
+    private boolean hardwrapped = false;
+    private char lastread;
+    private int lastcurspos = 0;
+    private boolean maskInput = false;
+    private char mask = '*';
+
+    /**
+ * Creates a new Editline object.
+ *
+ * @param io
+ */
+    public Editline(TerminalIO io) {
+        myIO = io;
+
+        //allways full length
+        buf = new Buffer(myIO.getColumns() - 1);
+        Cursor = 0;
+        InsertMode = true;
+    }
+
+    //constructor
+    public int size() {
+        return buf.size();
+    }
+
+    //getSize
+    public String getValue() {
+        return buf.toString();
+    }
+
+    //getValue
+    public void setValue(String str)
+        throws BufferOverflowException, IOException {
+        storeSize();
+
+        //buffer
+        buf.clear();
+
+        //cursor
+        Cursor = 0;
+
+        //screen
+        myIO.moveLeft(lastSize);
+        myIO.eraseToEndOfLine();
+        append(str);
+    }
+
+    //setValue
+    public void maskInput(boolean maskInput) {
+        this.maskInput = maskInput;
+    }
+
+    /**
+ *
+ *
+ * @param mask
+ */
+    public void setMask(char mask) {
+        this.mask = mask;
+    }
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    public void clear() throws IOException {
+        storeSize();
+
+        //Buffer
+        buf.clear();
+
+        //Cursor
+        Cursor = 0;
+
+        //Screen
+        draw();
+    }
+
+    //clear
+    public String getSoftwrap() throws IndexOutOfBoundsException, IOException {
+        //Wrap from Buffer
+        String content = buf.toString();
+        int idx = content.lastIndexOf(" ");
+
+        if (idx == -1) {
+            content = "";
+        } else {
+            //System.out.println("Line:softwrap:lastspace:"+idx);
+            content = content.substring(idx + 1, content.length());
+
+            //System.out.println("Line:softwrap:wraplength:"+content.length());
+            //Cursor
+            //remeber relative cursor pos
+            Cursor = size();
+            Cursor = Cursor - content.length();
+
+            //buffer
+            for (int i = 0; i < content.length(); i++) {
+                buf.removeCharAt(Cursor);
+            }
+
+            //screen
+            myIO.moveLeft(content.length());
+            myIO.eraseToEndOfLine();
+
+            //System.out.println("Line:softwrap:buffercontent:"+buf.toString());
+        }
+
+        return content + getLastRead();
+    }
+
+    //getSoftWrap
+    public String getHardwrap() throws IndexOutOfBoundsException, IOException {
+        //Buffer
+        String content = buf.toString();
+        content = content.substring(Cursor, content.length());
+
+        //System.out.println("buffer:tostring:"+buf.toString()+":");
+        //System.out.println("buffer:size:"+buf.size());
+        int lastsize = buf.size();
+
+        for (int i = Cursor; i < lastsize; i++) {
+            buf.removeCharAt(Cursor);
+
+            //System.out.println("buffer:removing char #"+i);
+        }
+
+        //System.out.println("buffer:tostring:"+buf.toString()+":");
+        //cursor stays
+        //screen
+        myIO.eraseToEndOfLine();
+
+        return content;
+    }
+
+    //getHardWrap
+    private void setCharAt(int pos, char ch)
+        throws IndexOutOfBoundsException, IOException {
+        //buffer
+        buf.setCharAt(pos, ch);
+
+        //cursor
+        //implements overwrite mode no change
+        //screen
+        draw();
+    }
+
+    //setCharAt
+    private void insertCharAt(int pos, char ch)
+        throws BufferOverflowException, IndexOutOfBoundsException, IOException {
+        storeSize();
+
+        //buffer
+        buf.ensureSpace(1);
+        buf.insertCharAt(pos, ch);
+
+        //cursor adjustment (so that it stays in "same" pos)
+        if (Cursor >= pos) {
+            Cursor++;
+        }
+
+        //screen
+        draw();
+    }
+
+    //insertCharAt
+    private void removeCharAt(int pos)
+        throws IndexOutOfBoundsException, IOException {
+        storeSize();
+
+        //buffer
+        buf.removeCharAt(pos);
+
+        //cursor
+        if (Cursor > pos) {
+            Cursor--;
+        }
+
+        //screen
+        draw();
+    }
+
+    //removeChatAt
+    private void insertStringAt(int pos, String str)
+        throws BufferOverflowException, IndexOutOfBoundsException, IOException {
+        storeSize();
+
+        //buffer
+        buf.ensureSpace(str.length());
+
+        for (int i = 0; i < str.length(); i++) {
+            buf.insertCharAt(pos, str.charAt(i));
+
+            //Cursor
+            Cursor++;
+        }
+
+        //screen
+        draw();
+    }
+
+    //insertStringAt
+    public void append(char ch) throws BufferOverflowException, IOException {
+        storeSize();
+
+        //buffer
+        buf.ensureSpace(1);
+        buf.append(ch);
+
+        //cursor
+        Cursor++;
+
+        //screen
+        if (!maskInput) {
+            myIO.write(ch);
+        } else {
+            myIO.write(mask);
+        }
+    }
+
+    //append(char)
+    public void append(String str) throws BufferOverflowException, IOException {
+        storeSize();
+
+        //buffer
+        buf.ensureSpace(str.length());
+
+        for (int i = 0; i < str.length(); i++) {
+            buf.append(str.charAt(i));
+
+            //Cursor
+            Cursor++;
+        }
+
+        //screen
+        if (!maskInput) {
+            myIO.write(str);
+        } else {
+            for (int i = 0; i < str.length(); i++) {
+                myIO.write(mask);
+            }
+        }
+    }
+
+    //append(String)
+    public int getCursorPosition() {
+        return Cursor;
+    }
+
+    //getCursorPosition
+    public void setCursorPosition(int pos) {
+        if (buf.size() < pos) {
+            Cursor = buf.size();
+        } else {
+            Cursor = pos;
+        }
+
+        //System.out.println("Editline:cursor:"+Cursor);
+    }
+
+    //setCursorPosition
+    private char getLastRead() {
+        return lastread;
+    }
+
+    //getLastRead
+    private void setLastRead(char ch) {
+        lastread = ch;
+    }
+
+    //setLastRead
+    public boolean isInInsertMode() {
+        return InsertMode;
+    }
+
+    //isInInsertMode
+    public void setInsertMode(boolean b) {
+        InsertMode = b;
+    }
+
+    //setInsertMode
+    public boolean isHardwrapped() {
+        return hardwrapped;
+    }
+
+    //isHardwrapped
+    public void setHardwrapped(boolean b) {
+        hardwrapped = b;
+    }
+
+    //setHardwrapped
+    public int run() {
+        try {
+            int in = 0;
+
+            do {
+                //get next key
+                in = myIO.read();
+
+                //store cursorpos
+                lastcurspos = Cursor;
+
+                switch (in) {
+                case TerminalIO.LEFT:
+
+                    if (!moveLeft()) {
+                        return in;
+                    }
+
+                    break;
+
+                case TerminalIO.RIGHT:
+
+                    if (!moveRight()) {
+                        return in;
+                    }
+
+                    break;
+
+                case TerminalIO.BACKSPACE:
+
+                    try {
+                        if (Cursor == 0) {
+                            return in;
+                        } else {
+                            removeCharAt(Cursor - 1);
+                        }
+                    } catch (IndexOutOfBoundsException ioobex) {
+                        myIO.bell();
+                    }
+
+                    break;
+
+                case TerminalIO.DELETE:
+
+                    try {
+                        removeCharAt(Cursor);
+                    } catch (IndexOutOfBoundsException ioobex) {
+                        myIO.bell();
+                    }
+
+                    break;
+
+                case TerminalIO.ENTER:
+                case TerminalIO.UP:
+                case TerminalIO.DOWN:
+                case TerminalIO.TABULATOR:
+                    return in;
+
+                default:
+
+                    try {
+                        handleCharInput(in);
+                    } catch (BufferOverflowException boex) {
+                        setLastRead((char) in);
+
+                        return in;
+                    }
+                }
+
+                myIO.flush();
+            } while (true);
+        } catch (IOException ioe) {
+            return TerminalIO.IOERROR;
+        }
+    }
+
+    //run
+    public void draw() throws IOException {
+        myIO.moveLeft(lastcurspos);
+        myIO.eraseToEndOfLine();
+
+        if (!maskInput) {
+            myIO.write(buf.toString());
+        } else {
+            for (int i = 0; i < buf.size(); i++) {
+                myIO.write(mask);
+            }
+        }
+
+        //adjust screen cursor hmm
+        if (Cursor < buf.size()) {
+            myIO.moveLeft(buf.size() - Cursor);
+        }
+    }
+
+    private boolean moveRight() throws IOException {
+        //cursor
+        if (Cursor < buf.size()) {
+            Cursor++;
+
+            //screen
+            myIO.moveRight(1);
+
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private boolean moveLeft() throws IOException {
+        //cursor
+        if (Cursor > 0) {
+            Cursor--;
+
+            //screen
+            myIO.moveLeft(1);
+
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private boolean isCursorAtEnd() {
+        return (Cursor == buf.size());
+    }
+
+    private void handleCharInput(int ch)
+        throws BufferOverflowException, IOException {
+        if (isCursorAtEnd()) {
+            append((char) ch);
+        } else {
+            if (isInInsertMode()) {
+                try {
+                    insertCharAt(Cursor, (char) ch);
+                } catch (BufferOverflowException ex) {
+                    //ignore buffer overflow on insert
+                    myIO.bell();
+                }
+            } else {
+                setCharAt(Cursor, (char) ch);
+            }
+        }
+    }
+
+    private void storeSize() {
+        lastSize = buf.size();
+    }
+
+    class Buffer extends CharBuffer {
+        public Buffer(int size) {
+            super(size);
+        }
+    }
+}
diff --git a/src/com/sshtools/daemon/terminal/Terminal.java b/src/com/sshtools/daemon/terminal/Terminal.java
new file mode 100644
index 0000000000000000000000000000000000000000..6314e2adf6d0a7eb57abe2e696ac1ef0c1b226b5
--- /dev/null
+++ b/src/com/sshtools/daemon/terminal/Terminal.java
@@ -0,0 +1,230 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+/**
+ * SSHTools - Java SSH API The contents of this package has been derived from
+ * the TelnetD library available from http://sourceforge.net/projects/telnetd
+ * The original license of the source code is as follows: TelnetD library
+ * (embeddable telnet daemon) Copyright (C) 2000 Dieter Wimberger This library
+ * is free software; you can either redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License version 2.1,1999 as
+ * published by the Free Software Foundation (see copy received along with the
+ * library), or under the terms of the BSD-style license received along with
+ * this library.
+ */
+package com.sshtools.daemon.terminal;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.11 $
+ */
+public interface Terminal {
+    //Constants
+
+    /**  */
+    public static final byte EOT = 4;
+
+    /**  */
+    public static final byte BS = 8;
+
+    /**  */
+    public static final byte DEL = 127;
+
+    /**  */
+    public static final byte HT = 9;
+
+    /**  */
+    public static final byte FF = 12;
+
+    /**  */
+    public static final byte SGR = 1;
+
+    /**  */
+    public static final byte CAN = 24;
+
+    /**  */
+    public static final byte ESC = 27;
+
+    /**  */
+    public static final byte LSB = 91;
+
+    /**  */
+    public static final byte SEMICOLON = 59;
+
+    /**  */
+    public static final byte A = 65;
+
+    /**  */
+    public static final byte B = 66;
+
+    /**  */
+    public static final byte C = 67;
+
+    /**  */
+    public static final byte D = 68;
+
+    /**  */
+    public static final byte E = 69; // for next Line (like CR/LF)
+
+    /**  */
+    public static final byte H = 72; // for Home and Positionsetting or f
+
+    /**  */
+    public static final byte f = 102;
+
+    /**  */
+    public static final byte r = 114;
+
+    /**  */
+    public static final byte LE = 75; // K...line erase actions related
+
+    /**  */
+    public static final byte SE = 74; // J...screen erase actions related
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getName();
+
+    /**
+ *
+ *
+ * @param byteread
+ *
+ * @return
+ */
+    public int translateControlCharacter(int byteread);
+
+    /**
+ *
+ *
+ * @param buffer
+ *
+ * @return
+ */
+    public int translateEscapeSequence(int[] buffer);
+
+    /**
+ *
+ *
+ * @param eraseFunc
+ *
+ * @return
+ */
+    public byte[] getEraseSequence(int eraseFunc);
+
+    /**
+ *
+ *
+ * @param dir
+ * @param times
+ *
+ * @return
+ */
+    public byte[] getCursorMoveSequence(int dir, int times);
+
+    /**
+ *
+ *
+ * @param pos
+ *
+ * @return
+ */
+    public byte[] getCursorPositioningSequence(int[] pos);
+
+    /**
+ *
+ *
+ * @param sequence
+ *
+ * @return
+ */
+    public byte[] getSpecialSequence(int sequence);
+
+    /**
+ *
+ *
+ * @param topmargin
+ * @param bottommargin
+ *
+ * @return
+ */
+    public byte[] getScrollMarginsSequence(int topmargin, int bottommargin);
+
+    /**
+ *
+ *
+ * @param type
+ * @param param
+ *
+ * @return
+ */
+    public byte[] getGRSequence(int type, int param);
+
+    /**
+ *
+ *
+ * @param str
+ *
+ * @return
+ */
+    public String format(String str);
+
+    /**
+ *
+ *
+ * @return
+ */
+    public byte[] getInitSequence();
+
+    /**
+ *
+ *
+ * @return
+ */
+    public boolean supportsSGR();
+
+    /**
+ *
+ *
+ * @return
+ */
+    public boolean supportsScrolling();
+
+    /**
+ *
+ *
+ * @return
+ */
+    public int getAtomicSequenceLength();
+}
+
+
+//interface Terminal
diff --git a/src/com/sshtools/daemon/terminal/TerminalFactory.java b/src/com/sshtools/daemon/terminal/TerminalFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..1688959f3337996e53f637715c7e4baf7361b7b1
--- /dev/null
+++ b/src/com/sshtools/daemon/terminal/TerminalFactory.java
@@ -0,0 +1,69 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+/**
+ * SSHTools - Java SSH API The contents of this package has been derived from
+ * the TelnetD library available from http://sourceforge.net/projects/telnetd
+ * The original license of the source code is as follows: TelnetD library
+ * (embeddable telnet daemon) Copyright (C) 2000 Dieter Wimberger This library
+ * is free software; you can either redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License version 2.1,1999 as
+ * published by the Free Software Foundation (see copy received along with the
+ * library), or under the terms of the BSD-style license received along with
+ * this library.
+ */
+package com.sshtools.daemon.terminal;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.11 $
+ */
+public class TerminalFactory {
+    /**
+ * Creates a new TerminalFactory object.
+ */
+    public TerminalFactory() {
+    }
+
+    /**
+ *
+ *
+ * @param term
+ *
+ * @return
+ */
+    public static Terminal newInstance(String term) {
+        if (term.equalsIgnoreCase("ANSI")) {
+            return new ansi();
+        } else if (term.equalsIgnoreCase("xterm")) {
+            return new xterm();
+        } else {
+            return new vt100();
+        }
+    }
+}
diff --git a/src/com/sshtools/daemon/terminal/TerminalIO.java b/src/com/sshtools/daemon/terminal/TerminalIO.java
new file mode 100644
index 0000000000000000000000000000000000000000..7f3746e5b157f26dbc98b1e057dbf85f6d5d3bf5
--- /dev/null
+++ b/src/com/sshtools/daemon/terminal/TerminalIO.java
@@ -0,0 +1,1133 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+/**
+ * SSHTools - Java SSH API The contents of this package has been derived from
+ * the TelnetD library available from http://sourceforge.net/projects/telnetd
+ * The original license of the source code is as follows: TelnetD library
+ * (embeddable telnet daemon) Copyright (C) 2000 Dieter Wimberger This library
+ * is free software; you can either redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License version 2.1,1999 as
+ * published by the Free Software Foundation (see copy received along with the
+ * library), or under the terms of the BSD-style license received along with
+ * this library.
+ */
+package com.sshtools.daemon.terminal;
+
+import com.sshtools.j2ssh.io.*;
+import com.sshtools.j2ssh.session.*;
+
+import java.io.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class TerminalIO implements PseudoTerminal {
+    // implements BasicTerminalIO {
+
+    /**  */
+    public static final int EOL_CRLF = 1;
+
+    /**  */
+    public static final int EOL_CR = 2;
+
+    /**  */
+    public static final int[] HOME = { 0, 0 };
+
+    /**  */
+    public static final int IOERROR = -1; //CTRL-D beim login
+
+    /**  */
+    public static final int 
+    // Positioning 10xx
+    UP = 1001; //CTRL-D beim login
+
+    /**  */
+    public static final int DOWN = 1002; //CTRL-D beim login
+
+    /**  */
+    public static final int RIGHT = 1003; //CTRL-D beim login
+
+    /**  */
+    public static final int LEFT = 1004; //CTRL-D beim login
+
+    /**  */
+    public static final int STORECURSOR = 1051; //CTRL-D beim login
+
+    /**  */
+    public static final int RESTORECURSOR = 1052; //CTRL-D beim login
+
+    /**  */
+    public static final int 
+    // Erasing 11xx
+    EEOL = 1100; //CTRL-D beim login
+
+    /**  */
+    public static final int EBOL = 1101; //CTRL-D beim login
+
+    /**  */
+    public static final int EEL = 1103; //CTRL-D beim login
+
+    /**  */
+    public static final int EEOS = 1104; //CTRL-D beim login
+
+    /**  */
+    public static final int EBOS = 1105; //CTRL-D beim login
+
+    /**  */
+    public static final int EES = 1106; //CTRL-D beim login
+
+    /**  */
+    public static final int 
+    // Escape Sequence-ing 12xx
+    ESCAPE = 1200; //CTRL-D beim login
+
+    /**  */
+    public static final int BYTEMISSING = 1201; //CTRL-D beim login
+
+    /**  */
+    public static final int UNRECOGNIZED = 1202; //CTRL-D beim login
+
+    /**  */
+    public static final int 
+    // Control Characters 13xx
+    ENTER = 10; //CTRL-D beim login
+
+    /**  */
+    public static final int 
+    //ENTER = 1300, //LF is ENTER at the moment
+    TABULATOR = 1301; //CTRL-D beim login
+
+    /**  */
+    public static final int DELETE = 1302; //CTRL-D beim login
+
+    /**  */
+    public static final int BACKSPACE = 1303; //CTRL-D beim login
+
+    /**  */
+    public static final int COLORINIT = 1304; //CTRL-D beim login
+
+    /**  */
+    public static final int HANDLED = 1305; //CTRL-D beim login
+
+    /**  */
+    public static final int LOGOUTREQUEST = 1306; //CTRL-D beim login
+
+    /**  */
+    public static final int LineUpdate = 475;
+
+    /**  */
+    public static final int CharacterUpdate = 476;
+
+    /**  */
+    public static final int ScreenpartUpdate = 477;
+
+    /**  */
+    public static final int EditBuffer = 575;
+
+    /**  */
+    public static final int LineEditBuffer = 576;
+
+    /**  */
+    public static final int BEL = 7;
+
+    /**  */
+    public static final int BS = 8;
+
+    /**  */
+    public static final int DEL = 127;
+
+    /**  */
+    public static final int CR = 13;
+
+    /**  */
+    public static final int LF = 10;
+
+    /**  */
+    public static final int FCOLOR = 10001;
+
+    /**  */
+    public static final int BCOLOR = 10002;
+
+    /**  */
+    public static final int STYLE = 10003;
+
+    /**  */
+    public static final int RESET = 10004;
+
+    /**  */
+    public static final int BOLD = 1;
+
+    /**  */
+    public static final int BOLD_OFF = 22;
+
+    /**  */
+    public static final int ITALIC = 3;
+
+    /**  */
+    public static final int ITALIC_OFF = 23;
+
+    /**  */
+    public static final int BLINK = 5;
+
+    /**  */
+    public static final int BLINK_OFF = 25;
+
+    /**  */
+    public static final int UNDERLINED = 4;
+
+    /**  */
+    public static final int UNDERLINED_OFF = 24;
+
+    //Constants
+
+    /**  */
+    public static final int BLACK = 30;
+
+    /**  */
+    public static final int RED = 31;
+
+    /**  */
+    public static final int GREEN = 32;
+
+    /**  */
+    public static final int YELLOW = 33;
+
+    /**  */
+    public static final int BLUE = 34;
+
+    /**  */
+    public static final int MAGENTA = 35;
+
+    /**  */
+    public static final int CYAN = 36;
+
+    /**  */
+    public static final int white = 37;
+
+    /**  */
+    public static final String CRLF = "\r\n";
+    private Terminal terminal;
+    private DataInputStream in;
+    private DataOutputStream out;
+    private boolean closing;
+    private boolean cr;
+    private boolean nl;
+    private boolean acousticSignalling; //flag for accoustic signalling
+    private boolean autoflush; //flag for autoflushing mode
+    private int eol = EOL_CRLF;
+    private int lastByte;
+    private boolean uselast = false;
+    private Colorizer color = Colorizer.getReference();
+    private String term;
+    private int cols;
+    private int rows;
+    private PipedInputStream masterIn;
+    private PipedOutputStream masterOut;
+    private InputStream slaveIn;
+    private OutputStream slaveOut;
+    private IOStreamConnector ios;
+
+    //private OutputStream masterOut;
+    // private OutputStream slaveOut = new SlaveOutputStream();
+    public TerminalIO(InputStream in, OutputStream out, String term, int cols,
+        int rows) throws IOException {
+        attachStreams(in, out);
+        this.term = term;
+        this.rows = rows;
+        this.cols = cols;
+        acousticSignalling = true;
+        masterOut = new PipedOutputStream();
+        masterIn = new PipedInputStream(masterOut);
+        autoflush = true;
+        closing = false;
+        cr = false;
+
+        //set default terminal
+        setDefaultTerminal();
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public InputStream getMasterInputStream() {
+        return masterIn;
+    }
+
+    /**
+ *
+ *
+ * @param slaveIn
+ */
+    public void bindSlaveInputStream(InputStream slaveIn) {
+        this.slaveIn = slaveIn;
+        this.ios = new IOStreamConnector(slaveIn, masterOut);
+    }
+
+    /**
+ *
+ *
+ * @param slaveOut
+ */
+    public void bindSlaveOutputStream(OutputStream slaveOut) {
+        this.slaveOut = slaveOut;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public OutputStream getSlaveOutputStream() {
+        return slaveOut;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public int getWidth() {
+        return 0;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public int getHeight() {
+        return 0;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getTerm() {
+        return terminal.getName();
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getEncodedTerminalModes() {
+        return "";
+    }
+
+    /* public void setMasterOutputStream(OutputStream masterOut) {
+     this.masterOut = masterOut;
+   }*/
+    public InputStream getAttachedInputStream() throws IOException {
+        if (in == null) {
+            throw new IOException(
+                "The teminal is not attached to an InputStream");
+        }
+
+        return in;
+    }
+
+    /**
+ *
+ *
+ * @return
+ *
+ * @throws IOException
+ */
+    public OutputStream getAttachedOutputStream() throws IOException {
+        if (out == null) {
+            throw new IOException(
+                "The terminal is not attached to an OutputStream");
+        }
+
+        return out;
+    }
+
+    /**
+ *
+ */
+    public void detachStreams() {
+        this.in = null;
+        this.out = null;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public int getEOL() {
+        return eol;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getEOLString() {
+        return ((eol == EOL_CR) ? "\r" : "\r\n");
+    }
+
+    /**
+ *
+ *
+ * @param eol
+ */
+    public void setEOL(int eol) {
+        this.eol = eol;
+    }
+
+    /**
+ *
+ *
+ * @param in
+ * @param out
+ */
+    public void attachStreams(InputStream in, OutputStream out) {
+        this.in = new DataInputStream(new BufferedInputStream(in));
+        this.out = new DataOutputStream(new BufferedOutputStream(out));
+    }
+
+    /**
+ *
+ *
+ * @return
+ *
+ * @throws IOException
+ */
+    public int read() throws IOException {
+        int i = stripCRSeq(rawread());
+
+        //translate possible control sequences
+        i = terminal.translateControlCharacter(i);
+
+        if ((i > 256) && (i == ESCAPE)) {
+            i = handleEscapeSequence(i);
+        }
+
+        return i;
+    }
+
+    /**
+ *
+ *
+ * @param ch
+ *
+ * @throws IOException
+ */
+    public void write(char ch) throws IOException {
+        write((byte) ch);
+
+        if (autoflush) {
+            flush();
+        }
+    }
+
+    /**
+ *
+ *
+ * @param str
+ *
+ * @throws IOException
+ */
+    public void write(String str) throws IOException {
+        write((color.colorize(str, terminal.supportsSGR())).getBytes());
+
+        if (autoflush) {
+            flush();
+        }
+    }
+
+    /**
+ *
+ *
+ * @param str
+ *
+ * @throws IOException
+ */
+    public void println(String str) throws IOException {
+        write(str);
+        write(getEOLString().getBytes());
+
+        if (autoflush) {
+            flush();
+        }
+    }
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    public void println() throws IOException {
+        write(getEOLString().getBytes());
+
+        if (autoflush) {
+            flush();
+        }
+    }
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    public void eraseToEndOfLine() throws IOException {
+        doErase(EEOL);
+    }
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    public void eraseToBeginOfLine() throws IOException {
+        doErase(EBOL);
+    }
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    public void eraseLine() throws IOException {
+        doErase(EEL);
+    }
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    public void eraseToEndOfScreen() throws IOException {
+        doErase(EEOS);
+    }
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    public void eraseToBeginOfScreen() throws IOException {
+        doErase(EBOS);
+    }
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    public void eraseScreen() throws IOException {
+        doErase(EES);
+    }
+
+    private void doErase(int funcConst) throws IOException {
+        write(terminal.getEraseSequence(funcConst));
+
+        if (autoflush) {
+            flush();
+        }
+    }
+
+    /**
+ *
+ *
+ * @param direction
+ * @param times
+ *
+ * @throws IOException
+ */
+    public void moveCursor(int direction, int times) throws IOException {
+        write(terminal.getCursorMoveSequence(direction, times));
+
+        if (autoflush) {
+            flush();
+        }
+    }
+
+    /**
+ *
+ *
+ * @param times
+ *
+ * @throws IOException
+ */
+    public void moveLeft(int times) throws IOException {
+        moveCursor(LEFT, times);
+    }
+
+    /**
+ *
+ *
+ * @param times
+ *
+ * @throws IOException
+ */
+    public void moveRight(int times) throws IOException {
+        moveCursor(RIGHT, times);
+    }
+
+    /**
+ *
+ *
+ * @param times
+ *
+ * @throws IOException
+ */
+    public void moveUp(int times) throws IOException {
+        moveCursor(UP, times);
+    }
+
+    /**
+ *
+ *
+ * @param times
+ *
+ * @throws IOException
+ */
+    public void moveDown(int times) throws IOException {
+        moveCursor(DOWN, times);
+    }
+
+    /**
+ *
+ *
+ * @param row
+ * @param col
+ *
+ * @throws IOException
+ */
+    public void setCursor(int row, int col) throws IOException {
+        int[] pos = new int[2];
+        pos[0] = row;
+        pos[1] = col;
+        write(terminal.getCursorPositioningSequence(pos));
+
+        if (autoflush) {
+            flush();
+        }
+    }
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    public void homeCursor() throws IOException {
+        write(terminal.getCursorPositioningSequence(HOME));
+
+        if (autoflush) {
+            flush();
+        }
+    }
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    public void storeCursor() throws IOException {
+        write(terminal.getSpecialSequence(STORECURSOR));
+    }
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    public void restoreCursor() throws IOException {
+        write(terminal.getSpecialSequence(RESTORECURSOR));
+    }
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    public void closeInput() throws IOException {
+        if (in == null) {
+            throw new IOException(
+                "The terminal is not attached to an inputstream");
+        }
+
+        in.close();
+    }
+
+    private int read16int() throws IOException {
+        if (in == null) {
+            throw new IOException(
+                "The terminal is not attached to an inputstream");
+        }
+
+        return in.readUnsignedShort();
+    }
+
+    private int rawread() throws IOException {
+        if (in == null) {
+            throw new IOException(
+                "The terminal is not attached to an inputstream");
+        }
+
+        return in.readUnsignedByte();
+    }
+
+    private int stripCRSeq(int input) throws IOException {
+        if (in == null) {
+            throw new IOException(
+                "The terminal is not attached to an inputstream");
+        }
+
+        // Check for CR
+        if (input == CR) {
+            // Mark the current position and test for LF
+            in.mark(1);
+
+            // If there is more data to read, check for LF
+            if (in.available() > 0) {
+                int next = in.readUnsignedByte();
+
+                // If we don't have LF then reset back to the mark position
+                if (next != LF) {
+                    in.reset();
+                }
+            }
+
+            return TerminalIO.ENTER;
+        }
+
+        return input;
+    }
+
+    /**
+ *
+ *
+ * @param b
+ *
+ * @throws IOException
+ */
+    public void write(byte b) throws IOException {
+        if (out == null) {
+            throw new IOException(
+                "The terminal is not attached to an outputstream");
+        }
+
+        if (eol == EOL_CRLF) {
+            if (!cr && (b == 10)) {
+                out.write(13);
+
+                //if(masterOut!=null)
+                //  masterOut.write(13);
+            }
+
+            //ensure CRLF(\r\n) is written for CR(\r) to adhere
+            //to the telnet protocol.
+            if (cr && (b != 10)) {
+                out.write(10);
+
+                // if(masterOut!=null)
+                //   masterOut.write(10);
+            }
+
+            out.write(b);
+
+            // if(masterOut!=null)
+            //   masterOut.write(b);
+            if (b == 13) {
+                cr = true;
+            } else {
+                cr = false;
+            }
+        } else {
+            out.write(b);
+
+            // if(masterOut!=null)
+            //   masterOut.write(b);
+        }
+    }
+
+    /**
+ *
+ *
+ * @param i
+ *
+ * @throws IOException
+ */
+    public void write(int i) throws IOException {
+        write((byte) i);
+    }
+
+    /**
+ *
+ *
+ * @param sequence
+ *
+ * @throws IOException
+ */
+    public void write(byte[] sequence) throws IOException {
+        for (int z = 0; z < sequence.length; z++) {
+            write(sequence[z]);
+        }
+    }
+
+    /**
+ *
+ *
+ * @param sequence
+ *
+ * @throws IOException
+ */
+    public void write(int[] sequence) throws IOException {
+        for (int j = 0; j < sequence.length; j++) {
+            write((byte) sequence[j]);
+        }
+    }
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    public void flush() throws IOException {
+        if (out == null) {
+            throw new IOException(
+                "The terminal is not attached to an outputstream");
+        }
+
+        // If were attached then flush, else ignore
+        out.flush();
+    }
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    public void closeOutput() throws IOException {
+        if (out == null) {
+            throw new IOException(
+                "The terminal is not attached to an outputstream");
+        }
+
+        closing = true;
+        out.close();
+    }
+
+    /**
+ *
+ *
+ * @param bool
+ */
+    public void setSignalling(boolean bool) {
+        acousticSignalling = bool;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public boolean isSignalling() {
+        return acousticSignalling;
+    }
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    public void bell() throws IOException {
+        if (acousticSignalling) {
+            write(BEL);
+        }
+
+        if (autoflush) {
+            flush();
+        }
+    }
+
+    /**
+ *
+ *
+ * @param topmargin
+ * @param bottommargin
+ *
+ * @return
+ *
+ * @throws IOException
+ */
+    public boolean defineScrollRegion(int topmargin, int bottommargin)
+        throws IOException {
+        if (terminal.supportsScrolling()) {
+            write(terminal.getScrollMarginsSequence(topmargin, bottommargin));
+            flush();
+
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+ *
+ *
+ * @param color
+ *
+ * @throws IOException
+ */
+    public void setForegroundColor(int color) throws IOException {
+        if (terminal.supportsSGR()) {
+            write(terminal.getGRSequence(FCOLOR, color));
+
+            if (autoflush) {
+                flush();
+            }
+        }
+    }
+
+    /**
+ *
+ *
+ * @param color
+ *
+ * @throws IOException
+ */
+    public void setBackgroundColor(int color) throws IOException {
+        if (terminal.supportsSGR()) {
+            //this method adds the offset to the fg color by itself
+            write(terminal.getGRSequence(BCOLOR, color + 10));
+
+            if (autoflush) {
+                flush();
+            }
+        }
+    }
+
+    /**
+ *
+ *
+ * @param b
+ *
+ * @throws IOException
+ */
+    public void setBold(boolean b) throws IOException {
+        if (terminal.supportsSGR()) {
+            if (b) {
+                write(terminal.getGRSequence(STYLE, BOLD));
+            } else {
+                write(terminal.getGRSequence(STYLE, BOLD_OFF));
+            }
+
+            if (autoflush) {
+                flush();
+            }
+        }
+    }
+
+    /**
+ *
+ *
+ * @param b
+ *
+ * @throws IOException
+ */
+    public void setUnderlined(boolean b) throws IOException {
+        if (terminal.supportsSGR()) {
+            if (b) {
+                write(terminal.getGRSequence(STYLE, UNDERLINED));
+            } else {
+                write(terminal.getGRSequence(STYLE, UNDERLINED_OFF));
+            }
+
+            if (autoflush) {
+                flush();
+            }
+        }
+    }
+
+    /**
+ *
+ *
+ * @param b
+ *
+ * @throws IOException
+ */
+    public void setItalic(boolean b) throws IOException {
+        if (terminal.supportsSGR()) {
+            if (b) {
+                write(terminal.getGRSequence(STYLE, ITALIC));
+            } else {
+                write(terminal.getGRSequence(STYLE, ITALIC_OFF));
+            }
+
+            if (autoflush) {
+                flush();
+            }
+        }
+    }
+
+    /**
+ *
+ *
+ * @param b
+ *
+ * @throws IOException
+ */
+    public void setBlink(boolean b) throws IOException {
+        if (terminal.supportsSGR()) {
+            if (b) {
+                write(terminal.getGRSequence(STYLE, BLINK));
+            } else {
+                write(terminal.getGRSequence(STYLE, BLINK_OFF));
+            }
+
+            if (autoflush) {
+                flush();
+            }
+        }
+    }
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    public void resetAttributes() throws IOException {
+        if (terminal.supportsSGR()) {
+            write(terminal.getGRSequence(RESET, 0));
+        }
+    }
+
+    private int handleEscapeSequence(int i) throws IOException {
+        if (i == ESCAPE) {
+            int[] bytebuf = new int[terminal.getAtomicSequenceLength()];
+
+            //fill atomic length
+            //FIXME: ensure CAN, broken Escapes etc.
+            for (int m = 0; m < bytebuf.length; m++) {
+                bytebuf[m] = read();
+            }
+
+            return terminal.translateEscapeSequence(bytebuf);
+        }
+
+        if (i == BYTEMISSING) {
+            //FIXME:longer escapes etc...
+        }
+
+        return HANDLED;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public boolean isAutoflushing() {
+        return autoflush;
+    }
+
+    /**
+ *
+ *
+ * @param b
+ */
+    public void setAutoflushing(boolean b) {
+        autoflush = b;
+    }
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    public void close() throws IOException {
+        closeOutput();
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public Terminal getTerminal() {
+        return terminal;
+    }
+
+    //getTerminal
+    public void setDefaultTerminal() throws IOException {
+        //set the terminal passing the negotiated string
+        setTerminal(term);
+    }
+
+    /**
+ *
+ *
+ * @param terminalName
+ *
+ * @throws IOException
+ */
+    public void setTerminal(String terminalName) throws IOException {
+        terminal = TerminalFactory.newInstance(terminalName);
+
+        //Terminal is set we init it....
+        initTerminal();
+    }
+
+    private void initTerminal() throws IOException {
+        write(terminal.getInitSequence());
+        flush();
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public int getRows() {
+        return rows;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public int getColumns() {
+        return cols;
+    }
+}
+
+
+//class TerminalIO
diff --git a/src/com/sshtools/daemon/terminal/UserInput.java b/src/com/sshtools/daemon/terminal/UserInput.java
new file mode 100644
index 0000000000000000000000000000000000000000..6f446a5b716f1d776d3b1ab23a9cbead37cd121e
--- /dev/null
+++ b/src/com/sshtools/daemon/terminal/UserInput.java
@@ -0,0 +1,510 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+/**
+ * SSHTools - Java SSH API The contents of this package has been derived from
+ * the TelnetD library available from http://sourceforge.net/projects/telnetd
+ * The original license of the source code is as follows: TelnetD library
+ * (embeddable telnet daemon) Copyright (C) 2000 Dieter Wimberger This library
+ * is free software; you can either redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License version 2.1,1999 as
+ * published by the Free Software Foundation (see copy received along with the
+ * library), or under the terms of the BSD-style license received along with
+ * this library.
+ */
+package com.sshtools.daemon.terminal;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class UserInput implements Runnable {
+    //Aggregations (inner class!)
+    private Buffer buf;
+
+    //Members
+    private TerminalIO myIO;
+    private int Cursor = 0;
+    private boolean InsertMode = true;
+    private int lastSize = 0;
+    private boolean hardwrapped = false;
+    private char lastread;
+    private int lastcurspos = 0;
+    private boolean maskInput = false;
+    private char mask = '*';
+    private OutputStream pout;
+
+    /**
+ * Creates a new UserInput object.
+ *
+ * @param io
+ * @param pout
+ */
+    public UserInput(TerminalIO io, OutputStream pout) {
+        myIO = io;
+        this.pout = pout;
+
+        //allways full length
+        buf = new Buffer(myIO.getColumns() - 1);
+        Cursor = 0;
+        InsertMode = true;
+
+        Thread thread = new Thread(this);
+        thread.setDaemon(true);
+        thread.start();
+    }
+
+    //constructor
+    public int size() {
+        return buf.size();
+    }
+
+    //getSize
+    public String getValue() {
+        return buf.toString();
+    }
+
+    //getValue
+    public void setValue(String str)
+        throws BufferOverflowException, IOException {
+        storeSize();
+
+        //buffer
+        buf.clear();
+
+        //cursor
+        Cursor = 0;
+
+        //screen
+        myIO.moveLeft(lastSize);
+        myIO.eraseToEndOfLine();
+        append(str);
+    }
+
+    //setValue
+    public void maskInput(boolean maskInput) {
+        this.maskInput = maskInput;
+    }
+
+    /**
+ *
+ *
+ * @param mask
+ */
+    public void setMask(char mask) {
+        this.mask = mask;
+    }
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    public void clear() throws IOException {
+        storeSize();
+
+        //Buffer
+        buf.clear();
+
+        //Cursor
+        Cursor = 0;
+
+        //Screen
+        draw();
+    }
+
+    //clear
+    public String getSoftwrap() throws IndexOutOfBoundsException, IOException {
+        //Wrap from Buffer
+        String content = buf.toString();
+        int idx = content.lastIndexOf(" ");
+
+        if (idx == -1) {
+            content = "";
+        } else {
+            //System.out.println("Line:softwrap:lastspace:"+idx);
+            content = content.substring(idx + 1, content.length());
+
+            //System.out.println("Line:softwrap:wraplength:"+content.length());
+            //Cursor
+            //remeber relative cursor pos
+            Cursor = size();
+            Cursor = Cursor - content.length();
+
+            //buffer
+            for (int i = 0; i < content.length(); i++) {
+                buf.removeCharAt(Cursor);
+            }
+
+            //screen
+            myIO.moveLeft(content.length());
+            myIO.eraseToEndOfLine();
+
+            //System.out.println("Line:softwrap:buffercontent:"+buf.toString());
+        }
+
+        return content + getLastRead();
+    }
+
+    //getSoftWrap
+    public String getHardwrap() throws IndexOutOfBoundsException, IOException {
+        //Buffer
+        String content = buf.toString();
+        content = content.substring(Cursor, content.length());
+
+        //System.out.println("buffer:tostring:"+buf.toString()+":");
+        //System.out.println("buffer:size:"+buf.size());
+        int lastsize = buf.size();
+
+        for (int i = Cursor; i < lastsize; i++) {
+            buf.removeCharAt(Cursor);
+
+            //System.out.println("buffer:removing char #"+i);
+        }
+
+        //System.out.println("buffer:tostring:"+buf.toString()+":");
+        //cursor stays
+        //screen
+        myIO.eraseToEndOfLine();
+
+        return content;
+    }
+
+    //getHardWrap
+    private void setCharAt(int pos, char ch)
+        throws IndexOutOfBoundsException, IOException {
+        //buffer
+        buf.setCharAt(pos, ch);
+
+        //cursor
+        //implements overwrite mode no change
+        //screen
+        draw();
+    }
+
+    //setCharAt
+    private void insertCharAt(int pos, char ch)
+        throws BufferOverflowException, IndexOutOfBoundsException, IOException {
+        storeSize();
+
+        //buffer
+        buf.ensureSpace(1);
+        buf.insertCharAt(pos, ch);
+
+        //cursor adjustment (so that it stays in "same" pos)
+        if (Cursor >= pos) {
+            Cursor++;
+        }
+
+        //screen
+        draw();
+    }
+
+    //insertCharAt
+    private void removeCharAt(int pos)
+        throws IndexOutOfBoundsException, IOException {
+        storeSize();
+
+        //buffer
+        buf.removeCharAt(pos);
+
+        //cursor
+        if (Cursor > pos) {
+            Cursor--;
+        }
+
+        //screen
+        draw();
+    }
+
+    //removeChatAt
+    private void insertStringAt(int pos, String str)
+        throws BufferOverflowException, IndexOutOfBoundsException, IOException {
+        storeSize();
+
+        //buffer
+        buf.ensureSpace(str.length());
+
+        for (int i = 0; i < str.length(); i++) {
+            buf.insertCharAt(pos, str.charAt(i));
+
+            //Cursor
+            Cursor++;
+        }
+
+        //screen
+        draw();
+    }
+
+    //insertStringAt
+    public void append(char ch) throws BufferOverflowException, IOException {
+        storeSize();
+
+        //buffer
+        buf.ensureSpace(1);
+        buf.append(ch);
+
+        //cursor
+        Cursor++;
+
+        //screen
+        if (!maskInput) {
+            myIO.write(ch);
+        } else {
+            myIO.write(mask);
+        }
+    }
+
+    //append(char)
+    public void append(String str) throws BufferOverflowException, IOException {
+        storeSize();
+
+        //buffer
+        buf.ensureSpace(str.length());
+
+        for (int i = 0; i < str.length(); i++) {
+            buf.append(str.charAt(i));
+
+            //Cursor
+            Cursor++;
+        }
+
+        //screen
+        if (!maskInput) {
+            myIO.write(str);
+        } else {
+            for (int i = 0; i < str.length(); i++) {
+                myIO.write(mask);
+            }
+        }
+    }
+
+    //append(String)
+    public int getCursorPosition() {
+        return Cursor;
+    }
+
+    //getCursorPosition
+    public void setCursorPosition(int pos) {
+        if (buf.size() < pos) {
+            Cursor = buf.size();
+        } else {
+            Cursor = pos;
+        }
+
+        //System.out.println("Editline:cursor:"+Cursor);
+    }
+
+    //setCursorPosition
+    private char getLastRead() {
+        return lastread;
+    }
+
+    //getLastRead
+    private void setLastRead(char ch) {
+        lastread = ch;
+    }
+
+    //setLastRead
+    public boolean isInInsertMode() {
+        return InsertMode;
+    }
+
+    //isInInsertMode
+    public void setInsertMode(boolean b) {
+        InsertMode = b;
+    }
+
+    //setInsertMode
+    public boolean isHardwrapped() {
+        return hardwrapped;
+    }
+
+    //isHardwrapped
+    public void setHardwrapped(boolean b) {
+        hardwrapped = b;
+    }
+
+    //setHardwrapped
+    public void run() {
+        try {
+            int in = 0;
+
+            do {
+                //get next key
+                in = myIO.read();
+
+                //store cursorpos
+                lastcurspos = Cursor;
+
+                switch (in) {
+                case TerminalIO.LEFT:
+                    moveLeft();
+
+                    break;
+
+                case TerminalIO.RIGHT:
+                    moveRight();
+
+                    break;
+
+                case TerminalIO.BACKSPACE:
+
+                    try {
+                        if (Cursor != 0) {
+                            removeCharAt(Cursor - 1);
+                        }
+                    } catch (IndexOutOfBoundsException ioobex) {
+                        myIO.bell();
+                    }
+
+                    break;
+
+                case TerminalIO.DELETE:
+
+                    try {
+                        removeCharAt(Cursor);
+                    } catch (IndexOutOfBoundsException ioobex) {
+                        myIO.bell();
+                    }
+
+                    break;
+
+                case TerminalIO.ENTER:
+                    myIO.write(myIO.getEOLString());
+
+                    if (buf.size() > 0) {
+                        pout.write(buf.toString().getBytes());
+                    }
+
+                    buf.clear();
+                    Cursor = 0;
+                    pout.write(TerminalIO.CRLF.getBytes());
+
+                    break;
+
+                case TerminalIO.UP:
+                case TerminalIO.DOWN:
+                case TerminalIO.TABULATOR:default:
+
+                    try {
+                        handleCharInput(in);
+                    } catch (BufferOverflowException boex) {
+                        setLastRead((char) in);
+                    }
+                }
+
+                myIO.flush();
+            } while (true);
+        } catch (IOException ioe) {
+            return;
+        }
+    }
+
+    //run
+    public void draw() throws IOException {
+        myIO.moveLeft(lastcurspos);
+        myIO.eraseToEndOfLine();
+
+        if (!maskInput) {
+            myIO.write(buf.toString());
+        } else {
+            for (int i = 0; i < buf.size(); i++) {
+                myIO.write(mask);
+            }
+        }
+
+        //adjust screen cursor hmm
+        if (Cursor < buf.size()) {
+            myIO.moveLeft(buf.size() - Cursor);
+        }
+    }
+
+    private boolean moveRight() throws IOException {
+        //cursor
+        if (Cursor < buf.size()) {
+            Cursor++;
+
+            //screen
+            myIO.moveRight(1);
+
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private boolean moveLeft() throws IOException {
+        //cursor
+        if (Cursor > 0) {
+            Cursor--;
+
+            //screen
+            myIO.moveLeft(1);
+
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private boolean isCursorAtEnd() {
+        return (Cursor == buf.size());
+    }
+
+    private void handleCharInput(int ch)
+        throws BufferOverflowException, IOException {
+        if (isCursorAtEnd()) {
+            append((char) ch);
+        } else {
+            if (isInInsertMode()) {
+                try {
+                    insertCharAt(Cursor, (char) ch);
+                } catch (BufferOverflowException ex) {
+                    //ignore buffer overflow on insert
+                    myIO.bell();
+                }
+            } else {
+                setCharAt(Cursor, (char) ch);
+            }
+        }
+    }
+
+    private void storeSize() {
+        lastSize = buf.size();
+    }
+
+    class Buffer extends CharBuffer {
+        public Buffer(int size) {
+            super(size);
+        }
+    }
+}
diff --git a/src/com/sshtools/daemon/terminal/ansi.java b/src/com/sshtools/daemon/terminal/ansi.java
new file mode 100644
index 0000000000000000000000000000000000000000..22183b664010f975c7a7e3ca36451c0a5480aeda
--- /dev/null
+++ b/src/com/sshtools/daemon/terminal/ansi.java
@@ -0,0 +1,68 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+/**
+ * SSHTools - Java SSH API The contents of this package has been derived from
+ * the TelnetD library available from http://sourceforge.net/projects/telnetd
+ * The original license of the source code is as follows: TelnetD library
+ * (embeddable telnet daemon) Copyright (C) 2000 Dieter Wimberger This library
+ * is free software; you can either redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License version 2.1,1999 as
+ * published by the Free Software Foundation (see copy received along with the
+ * library), or under the terms of the BSD-style license received along with
+ * this library.
+ */
+package com.sshtools.daemon.terminal;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.11 $
+ */
+public class ansi extends BasicTerminal {
+    /**
+ *
+ *
+ * @return
+ */
+    public boolean supportsSGR() {
+        return true;
+    }
+
+    //supportsSGR
+    public boolean supportsScrolling() {
+        return true;
+    }
+
+    //supportsSoftScroll
+    public String getName() {
+        return "ansi";
+    }
+}
+
+
+//class ansi
diff --git a/src/com/sshtools/daemon/terminal/vt100.java b/src/com/sshtools/daemon/terminal/vt100.java
new file mode 100644
index 0000000000000000000000000000000000000000..4900acbfd121965939372d287ae497873b77a7af
--- /dev/null
+++ b/src/com/sshtools/daemon/terminal/vt100.java
@@ -0,0 +1,68 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+/**
+ * SSHTools - Java SSH API The contents of this package has been derived from
+ * the TelnetD library available from http://sourceforge.net/projects/telnetd
+ * The original license of the source code is as follows: TelnetD library
+ * (embeddable telnet daemon) Copyright (C) 2000 Dieter Wimberger This library
+ * is free software; you can either redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License version 2.1,1999 as
+ * published by the Free Software Foundation (see copy received along with the
+ * library), or under the terms of the BSD-style license received along with
+ * this library.
+ */
+package com.sshtools.daemon.terminal;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.11 $
+ */
+public class vt100 extends BasicTerminal {
+    /**
+ *
+ *
+ * @return
+ */
+    public boolean supportsSGR() {
+        return false;
+    }
+
+    //supportsSGR
+    public boolean supportsScrolling() {
+        return true;
+    }
+
+    //supportsSoftScroll
+    public String getName() {
+        return "vt100";
+    }
+}
+
+
+//class vt100
diff --git a/src/com/sshtools/daemon/terminal/xterm.java b/src/com/sshtools/daemon/terminal/xterm.java
new file mode 100644
index 0000000000000000000000000000000000000000..adee18c5fe16879c2f5bf135f8159cc0e0584bc6
--- /dev/null
+++ b/src/com/sshtools/daemon/terminal/xterm.java
@@ -0,0 +1,68 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+/**
+ * SSHTools - Java SSH API The contents of this package has been derived from
+ * the TelnetD library available from http://sourceforge.net/projects/telnetd
+ * The original license of the source code is as follows: TelnetD library
+ * (embeddable telnet daemon) Copyright (C) 2000 Dieter Wimberger This library
+ * is free software; you can either redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License version 2.1,1999 as
+ * published by the Free Software Foundation (see copy received along with the
+ * library), or under the terms of the BSD-style license received along with
+ * this library.
+ */
+package com.sshtools.daemon.terminal;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.11 $
+ */
+public class xterm extends BasicTerminal {
+    /**
+ *
+ *
+ * @return
+ */
+    public boolean supportsSGR() {
+        return true;
+    }
+
+    //supportsSGR
+    public boolean supportsScrolling() {
+        return true;
+    }
+
+    //supportsScrolling
+    public String getName() {
+        return "xterm";
+    }
+}
+
+
+//class xterm
diff --git a/src/com/sshtools/daemon/transport/TransportProtocolServer.java b/src/com/sshtools/daemon/transport/TransportProtocolServer.java
new file mode 100644
index 0000000000000000000000000000000000000000..0c1fdb254799be73ec2d365adc2d93a6231455c0
--- /dev/null
+++ b/src/com/sshtools/daemon/transport/TransportProtocolServer.java
@@ -0,0 +1,448 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.transport;
+
+import com.sshtools.daemon.configuration.ServerConfiguration;
+
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+import com.sshtools.j2ssh.transport.*;
+import com.sshtools.j2ssh.transport.cipher.SshCipher;
+import com.sshtools.j2ssh.transport.cipher.SshCipherFactory;
+import com.sshtools.j2ssh.transport.hmac.SshHmac;
+import com.sshtools.j2ssh.transport.hmac.SshHmacFactory;
+import com.sshtools.j2ssh.transport.kex.KeyExchangeException;
+import com.sshtools.j2ssh.transport.kex.SshKeyExchange;
+import com.sshtools.j2ssh.transport.publickey.SshKeyPairFactory;
+import com.sshtools.j2ssh.transport.publickey.SshPrivateKey;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class TransportProtocolServer extends TransportProtocolCommon {
+    private static Log log = LogFactory.getLog(TransportProtocolServer.class);
+    private Map acceptServices = new HashMap();
+    private ServerConfiguration config;
+    private boolean refuse = false;
+
+    /**
+ * Creates a new TransportProtocolServer object.
+ *
+ * @throws IOException
+ */
+    public TransportProtocolServer() throws IOException {
+        config = (ServerConfiguration) ConfigurationLoader.getConfiguration(ServerConfiguration.class);
+    }
+
+    /**
+ * Creates a new TransportProtocolServer object.
+ *
+ * @param refuse
+ *
+ * @throws IOException
+ */
+    public TransportProtocolServer(boolean refuse) throws IOException {
+        this();
+        this.refuse = refuse;
+    }
+
+    /**
+ *
+ */
+    protected void onDisconnect() {
+        acceptServices.clear();
+    }
+
+    /**
+ *
+ *
+ * @param service
+ *
+ * @throws IOException
+ */
+    public void acceptService(Service service) throws IOException {
+        acceptServices.put(service.getServiceName(), service);
+    }
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    public void refuseConnection() throws IOException {
+        log.info("Refusing connection");
+
+        // disconnect with max_connections reason
+        sendDisconnect(SshMsgDisconnect.TOO_MANY_CONNECTIONS,
+            "Too many connections");
+    }
+
+    /**
+ *
+ *
+ * @throws MessageAlreadyRegisteredException
+ */
+    public void registerTransportMessages()
+        throws MessageAlreadyRegisteredException {
+        messageStore.registerMessage(SshMsgServiceRequest.SSH_MSG_SERVICE_REQUEST,
+            SshMsgServiceRequest.class);
+    }
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    protected void startBinaryPacketProtocol() throws IOException {
+        if (refuse) {
+            sendKeyExchangeInit();
+
+            //sshIn.open();
+            refuseConnection();
+        } else {
+            super.startBinaryPacketProtocol();
+        }
+    }
+
+    /**
+ *
+ *
+ * @return
+ *
+ * @throws AlgorithmNotAgreedException
+ */
+    protected String getDecryptionAlgorithm()
+        throws AlgorithmNotAgreedException {
+        return determineAlgorithm(clientKexInit.getSupportedCSEncryption(),
+            serverKexInit.getSupportedCSEncryption());
+    }
+
+    /**
+ *
+ *
+ * @return
+ *
+ * @throws AlgorithmNotAgreedException
+ */
+    protected String getEncryptionAlgorithm()
+        throws AlgorithmNotAgreedException {
+        return determineAlgorithm(clientKexInit.getSupportedSCEncryption(),
+            serverKexInit.getSupportedSCEncryption());
+    }
+
+    /**
+ *
+ *
+ * @return
+ *
+ * @throws AlgorithmNotAgreedException
+ */
+    protected String getInputStreamCompAlgortihm()
+        throws AlgorithmNotAgreedException {
+        return determineAlgorithm(clientKexInit.getSupportedCSComp(),
+            serverKexInit.getSupportedCSComp());
+    }
+
+    /**
+ *
+ *
+ * @return
+ *
+ * @throws AlgorithmNotAgreedException
+ */
+    protected String getInputStreamMacAlgorithm()
+        throws AlgorithmNotAgreedException {
+        return determineAlgorithm(clientKexInit.getSupportedCSMac(),
+            serverKexInit.getSupportedCSMac());
+    }
+
+    /**
+ *
+ */
+    protected void setLocalIdent() {
+        serverIdent = "SSH-" + PROTOCOL_VERSION + "-" +
+            SOFTWARE_VERSION_COMMENTS + " [SERVER]";
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getLocalId() {
+        return serverIdent;
+    }
+
+    /**
+ *
+ *
+ * @param msg
+ */
+    protected void setLocalKexInit(SshMsgKexInit msg) {
+        log.debug(msg.toString());
+        serverKexInit = msg;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    protected SshMsgKexInit getLocalKexInit() {
+        return serverKexInit;
+    }
+
+    /**
+ *
+ *
+ * @return
+ *
+ * @throws AlgorithmNotAgreedException
+ */
+    protected String getOutputStreamCompAlgorithm()
+        throws AlgorithmNotAgreedException {
+        return determineAlgorithm(clientKexInit.getSupportedSCComp(),
+            serverKexInit.getSupportedSCComp());
+    }
+
+    /**
+ *
+ *
+ * @return
+ *
+ * @throws AlgorithmNotAgreedException
+ */
+    protected String getOutputStreamMacAlgorithm()
+        throws AlgorithmNotAgreedException {
+        return determineAlgorithm(clientKexInit.getSupportedSCMac(),
+            serverKexInit.getSupportedSCMac());
+    }
+
+    /**
+ *
+ *
+ * @param ident
+ */
+    protected void setRemoteIdent(String ident) {
+        clientIdent = ident;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getRemoteId() {
+        return clientIdent;
+    }
+
+    /**
+ *
+ *
+ * @param msg
+ */
+    protected void setRemoteKexInit(SshMsgKexInit msg) {
+        log.debug(msg.toString());
+        clientKexInit = msg;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    protected SshMsgKexInit getRemoteKexInit() {
+        return clientKexInit;
+    }
+
+    /**
+ *
+ *
+ * @return
+ *
+ * @throws IOException
+ * @throws TransportProtocolException
+ */
+    protected SshMsgKexInit createLocalKexInit() throws IOException {
+        SshMsgKexInit msg = new SshMsgKexInit(properties);
+        Map keys = config.getServerHostKeys();
+
+        if (keys.size() > 0) {
+            Iterator it = keys.entrySet().iterator();
+            List available = new ArrayList();
+
+            while (it.hasNext()) {
+                Map.Entry entry = (Map.Entry) it.next();
+
+                if (SshKeyPairFactory.supportsKey(entry.getKey().toString())) {
+                    available.add(entry.getKey());
+                } else {
+                    log.warn("Server host key algorithm '" +
+                        entry.getKey().toString() + "' not supported");
+                }
+            }
+
+            if (available.size() > 0) {
+                msg.setSupportedPK(available);
+            } else {
+                throw new TransportProtocolException(
+                    "No server host keys available");
+            }
+        } else {
+            throw new TransportProtocolException(
+                "There are no server host keys available");
+        }
+
+        return msg;
+    }
+
+    /**
+ *
+ *
+ * @throws IOException
+ */
+    protected void onStartTransportProtocol() throws IOException {
+    }
+
+    /**
+ *
+ *
+ * @param kex
+ *
+ * @throws IOException
+ * @throws KeyExchangeException
+ */
+    protected void performKeyExchange(SshKeyExchange kex)
+        throws IOException {
+        // Determine the public key algorithm and obtain an instance
+        String keyType = determineAlgorithm(clientKexInit.getSupportedPublicKeys(),
+                serverKexInit.getSupportedPublicKeys());
+
+        // Create an instance of the public key from the factory
+        //SshKeyPair pair = SshKeyPairFactory.newInstance(keyType);
+        // Get the configuration and get the relevant host key
+        Map keys = config.getServerHostKeys();
+        Iterator it = keys.entrySet().iterator();
+        SshPrivateKey pk; //privateKeyFile = null;
+
+        while (it.hasNext()) {
+            Map.Entry entry = (Map.Entry) it.next();
+
+            if (entry.getKey().equals(keyType)) {
+                pk = (SshPrivateKey) entry.getValue();
+                kex.performServerExchange(clientIdent, serverIdent,
+                    clientKexInit.toByteArray(), serverKexInit.toByteArray(), pk);
+
+                return;
+            }
+        }
+
+        throw new KeyExchangeException(
+            "No host key available for the determined public key algorithm");
+    }
+
+    /**
+ *
+ *
+ * @param msg
+ *
+ * @throws IOException
+ */
+    protected void onMessageReceived(SshMessage msg) throws IOException {
+        switch (msg.getMessageId()) {
+        case SshMsgServiceRequest.SSH_MSG_SERVICE_REQUEST: {
+            onMsgServiceRequest((SshMsgServiceRequest) msg);
+
+            break;
+        }
+        }
+    }
+
+    /**
+ *
+ *
+ * @param encryptCSKey
+ * @param encryptCSIV
+ * @param encryptSCKey
+ * @param encryptSCIV
+ * @param macCSKey
+ * @param macSCKey
+ *
+ * @throws AlgorithmNotAgreedException
+ * @throws AlgorithmOperationException
+ * @throws AlgorithmNotSupportedException
+ * @throws AlgorithmInitializationException
+ */
+    protected void setupNewKeys(byte[] encryptCSKey, byte[] encryptCSIV,
+        byte[] encryptSCKey, byte[] encryptSCIV, byte[] macCSKey,
+        byte[] macSCKey)
+        throws AlgorithmNotAgreedException, AlgorithmOperationException, 
+            AlgorithmNotSupportedException, AlgorithmInitializationException {
+        // Setup the encryption cipher
+        SshCipher sshCipher = SshCipherFactory.newInstance(getEncryptionAlgorithm());
+        sshCipher.init(SshCipher.ENCRYPT_MODE, encryptSCIV, encryptSCKey);
+        algorithmsOut.setCipher(sshCipher);
+
+        // Setup the decryption cipher
+        sshCipher = SshCipherFactory.newInstance(getDecryptionAlgorithm());
+        sshCipher.init(SshCipher.DECRYPT_MODE, encryptCSIV, encryptCSKey);
+        algorithmsIn.setCipher(sshCipher);
+
+        // Create and put our macs into operation
+        SshHmac hmac = SshHmacFactory.newInstance(getOutputStreamMacAlgorithm());
+        hmac.init(macSCKey);
+        algorithmsOut.setHmac(hmac);
+        hmac = SshHmacFactory.newInstance(getInputStreamMacAlgorithm());
+        hmac.init(macCSKey);
+        algorithmsIn.setHmac(hmac);
+    }
+
+    private void onMsgServiceRequest(SshMsgServiceRequest msg)
+        throws IOException {
+        if (acceptServices.containsKey(msg.getServiceName())) {
+            Service service = (Service) acceptServices.get(msg.getServiceName());
+            service.init(Service.ACCEPTING_SERVICE, this);
+            service.start();
+        } else {
+            this.sendDisconnect(SshMsgDisconnect.SERVICE_NOT_AVAILABLE,
+                msg.getServiceName() + " is not available");
+        }
+    }
+}
diff --git a/src/com/sshtools/daemon/util/StringExaminer.java b/src/com/sshtools/daemon/util/StringExaminer.java
new file mode 100644
index 0000000000000000000000000000000000000000..353b4a5db9c4282632934aa701fa9bd2ce9f641e
--- /dev/null
+++ b/src/com/sshtools/daemon/util/StringExaminer.java
@@ -0,0 +1,277 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+// ===========================================================================
+// CONTENT  : CLASS StringExaminer
+// AUTHOR   : Manfred Duchrow
+// VERSION  : 1.0 - 29/09/2002
+// HISTORY  :
+//  29/09/2002  duma  CREATED
+//
+// Copyright (c) 2002, by Manfred Duchrow. All rights reserved.
+// ===========================================================================
+package com.sshtools.daemon.util;
+
+
+// ===========================================================================
+// IMPORTS
+// ===========================================================================
+
+/**
+ * As a subclass of StringScanner this class allows more advanced navigation
+ * over the underlying string.    <br>
+ * That includes moving to positions of specific substrings etc.
+ *
+ * @author Manfred Duchrow
+ * @version $Id: StringExaminer.java,v 1.7 2003/09/11 15:37:07 martianx Exp $
+ */
+public class StringExaminer extends StringScanner {
+    // =========================================================================
+    // CONSTANTS
+    // =========================================================================
+    // =========================================================================
+    // INSTANCE VARIABLES
+    // =========================================================================
+    private boolean ignoreCase = false;
+
+    // =========================================================================
+    // CLASS METHODS
+    // =========================================================================
+    // =========================================================================
+    // CONSTRUCTORS
+    // =========================================================================
+
+    /**
+ * Initialize the new instance with the string to examine.   <br>
+ * The string will be treated case-sensitive.
+ *
+ * @param stringToExamine The string that should be examined
+ */
+    public StringExaminer(String stringToExamine) {
+        this(stringToExamine, false);
+    }
+
+    // StringExaminer()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Initialize the new instance with the string to examine.
+ *
+ * @param stringToExamine The string that should be examined
+ * @param ignoreCase Specified whether or not treating the string case
+ *        insensitive
+ */
+    public StringExaminer(String stringToExamine, boolean ignoreCase) {
+        super(stringToExamine);
+        this.ignoreCase(ignoreCase);
+    }
+
+    // StringExaminer()
+
+    /**
+ *
+ *
+ * @return
+ */
+    protected boolean ignoreCase() {
+        return ignoreCase;
+    }
+
+    /**
+ *
+ *
+ * @param newValue
+ */
+    protected void ignoreCase(boolean newValue) {
+        ignoreCase = newValue;
+    }
+
+    // -------------------------------------------------------------------------
+    // =========================================================================
+    // PUBLIC INSTANCE METHODS
+    // =========================================================================
+
+    /**
+ * Increments the position pointer up to the last character that matched
+ * the character sequence in the given matchString. Returns true, if the
+ * matchString was found, otherwise false.
+ *
+ * <p>
+ * If the matchString was found, the next invocation of method nextChar()
+ * returns the first character after that matchString.
+ * </p>
+ *
+ * @param matchString The string to look up
+ *
+ * @return
+ */
+    public boolean skipAfter(String matchString) {
+        char ch = '-';
+        char matchChar = ' ';
+        boolean found = false;
+        int index = 0;
+
+        if ((matchString == null) || (matchString.length() == 0)) {
+            return false;
+        }
+
+        ch = this.nextChar();
+
+        while ((endNotReached(ch)) && (!found)) {
+            matchChar = matchString.charAt(index);
+
+            if (this.charsAreEqual(ch, matchChar)) {
+                index++;
+
+                if (index >= matchString.length()) { // whole matchString checked ?
+                    found = true;
+                } else {
+                    ch = this.nextChar();
+                }
+            } else {
+                if (index == 0) {
+                    ch = this.nextChar();
+                } else {
+                    index = 0;
+                }
+            }
+        }
+
+        return found;
+    }
+
+    // skipAfter()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Increments the position pointer up to the first character before the
+ * character sequence in the given matchString. Returns true, if the
+ * matchString was found, otherwise false.
+ *
+ * <p>
+ * If the matchString was found, the next invocation of method nextChar()
+ * returns the first character of that matchString from the position where
+ * it was found inside the examined string.
+ * </p>
+ *
+ * @param matchString The string to look up
+ *
+ * @return
+ */
+    public boolean skipBefore(String matchString) {
+        boolean found;
+        found = this.skipAfter(matchString);
+
+        if (found) {
+            this.skip(0 - matchString.length());
+        }
+
+        return found;
+    }
+
+    // skipBefore()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the a string containing all characters from the current position
+ * up to the end of the examined string.   <br>
+ * The character position of the examiner is not changed by this method.
+ *
+ * @return
+ */
+    public String peekUpToEnd() {
+        return this.upToEnd(true);
+    }
+
+    // peekUpToEnd()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the a string containing all characters from the current position
+ * up to the end of the examined string.   <br>
+ * The character position is put to the end by this method. That means the
+ * next invocation of nextChar() returns END_REACHED.
+ *
+ * @return
+ */
+    public String upToEnd() {
+        return this.upToEnd(false);
+    }
+
+    // upToEnd()
+
+    /**
+ *
+ *
+ * @param char1
+ * @param char2
+ *
+ * @return
+ */
+    protected boolean charsAreEqual(char char1, char char2) {
+        return (this.ignoreCase())
+        ? (Character.toUpperCase(char1) == Character.toUpperCase(char2))
+        : (char1 == char2);
+    }
+
+    // charsAreEqual()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the a string containing all characters from the current position
+ * up to the end of the examined string.   <br>
+ * Depending on the peek flag the character position of the examiner  is
+ * unchanged (true) after calling this method or points behind the strings
+ * last character.
+ *
+ * @param peek
+ *
+ * @return
+ */
+    protected String upToEnd(boolean peek) {
+        char result = '-';
+        int lastPosition = 0;
+        StringBuffer buffer = new StringBuffer(100);
+        lastPosition = this.getPosition();
+        result = this.nextChar();
+
+        while (endNotReached(result)) {
+            buffer.append(result);
+            result = this.nextChar();
+        }
+
+        if (peek) {
+            this.setPosition(lastPosition);
+        }
+
+        return buffer.toString();
+    }
+
+    // upToEnd()
+    // -------------------------------------------------------------------------
+}
+
+
+// class StringExaminer
diff --git a/src/com/sshtools/daemon/util/StringPattern.java b/src/com/sshtools/daemon/util/StringPattern.java
new file mode 100644
index 0000000000000000000000000000000000000000..44b41c4bd68da149c299bce49610d6ff9d70f4d1
--- /dev/null
+++ b/src/com/sshtools/daemon/util/StringPattern.java
@@ -0,0 +1,567 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+// ===========================================================================
+// CONTENT  : CLASS StringPattern
+// AUTHOR   : Manfred Duchrow
+// VERSION  : 1.7 - 13/02/2003
+// HISTORY  :
+//  24/01/2000  duma  CREATED
+//  08/01/2002  duma  bugfix  -> Handle *xxx (equal characters after star) correctly
+//  16/01/2002  duma  changed -> Implements Serializable
+//	06/07/2002	duma	bugfix	-> Couldn't match "London" on "L*n"
+//	19/09/2002	duma	bugfix	-> Couldn't match "MA_DR_HRBLUB" on "*_HR*"
+//	19/09/2002	duma	changed	-> Using now StringExaminer instead of CharacterIterator
+//	29/09/2002	duma	changed	-> Refactored: Using StringExaminer instead of StringScanner
+//	26/12/2002	duma	changed	-> Comment of matches() was wrong / new hasWildcard()
+//	13/02/2003	duma	added		-> setDigitWildcardChar()
+//
+// Copyright (c) 2000-2003, by Manfred Duchrow. All rights reserved.
+// ===========================================================================
+package com.sshtools.daemon.util;
+
+
+// ===========================================================================
+// IMPORTS
+// ===========================================================================
+import java.io.*;
+
+
+/**
+ * This class provides services for checking strings against string-patterns.
+ * Currently it supports the wildcards<br>
+ * '' for any number of any character and <br>
+ * '?' for any one character. The API is very simple:<br>
+ * <br>
+ * There are only the two class methods <i>match()</i> and
+ * <i>matchIgnoreCase()</i>. <br>
+ * Example: <br>
+ * StringPattern.match( 'Hello World", "H W" ) ;  --> evaluates to true  <br>
+ * StringPattern.matchIgnoreCase( 'StringPattern", "str???pat" ) ;  -->
+ * evaluates to true  <br>
+ *
+ * @author Manfred Duchrow
+ * @version 1.7
+ */
+public class StringPattern implements Serializable {
+    // =========================================================================
+    // CONSTANTS
+    // =========================================================================
+
+    /**  */
+    protected final static String MULTI_WILDCARD = "*";
+
+    /**  */
+    protected final static char MULTICHAR_WILDCARD = '*';
+
+    /**  */
+    protected final static char SINGLECHAR_WILDCARD = '?';
+
+    // =========================================================================
+    // INSTANCE VARIABLES
+    // =========================================================================
+    private boolean ignoreCase = false;
+    private String pattern = null;
+
+    // -------------------------------------------------------------------------
+    private Character digitWildcard = null;
+
+    // -------------------------------------------------------------------------
+    // =========================================================================
+    // CONSTRUCTORS
+    // =========================================================================
+
+    /**
+ * Initializes the new instance with the string pattern and the selecteion,
+ * if case should be ignored when comparing characters.
+ *
+ * @param pattern The pattern to check against ( May contain '' and '?'
+ *        wildcards )
+ * @param ignoreCase Definition, if case sensitive character comparison or
+ *        not.
+ */
+    public StringPattern(String pattern, boolean ignoreCase) {
+        this.setPattern(pattern);
+        this.setIgnoreCase(ignoreCase);
+    }
+
+    // StringPattern()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Initializes the new instance with the string pattern. The default is
+ * case sensitive checking.
+ *
+ * @param pattern The pattern to check against ( May contain '' and '?'
+ *        wildcards )
+ */
+    public StringPattern(String pattern) {
+        this(pattern, false);
+    }
+
+    // StringPattern()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Initializes the new instance with the string pattern and a digit
+ * wildcard  character. The default is case sensitive checking.
+ *
+ * @param pattern The pattern to check against ( May contain '', '?'
+ *        wildcards and the digit wildcard )
+ * @param digitWildcard A wildcard character that stands as placeholder for
+ *        digits
+ */
+    public StringPattern(String pattern, char digitWildcard) {
+        this(pattern, false, digitWildcard);
+    }
+
+    // StringPattern()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Initializes the new instance with the string pattern and the selecteion,
+ * if case should be ignored when comparing characters plus a wildcard
+ * character for digits.
+ *
+ * @param pattern The pattern to check against ( May contain '' and '?'
+ *        wildcards )
+ * @param ignoreCase Definition, if case sensitive character comparison or
+ *        not.
+ * @param digitWildcard A wildcard character that stands as placeholder for
+ *        digits
+ */
+    public StringPattern(String pattern, boolean ignoreCase, char digitWildcard) {
+        this.setPattern(pattern);
+        this.setIgnoreCase(ignoreCase);
+        this.setDigitWildcardChar(digitWildcard);
+    }
+
+    // StringPattern()
+
+    /**
+ * Returns whether or not the pattern matching ignores upper and lower case
+ *
+ * @return
+ */
+    public boolean getIgnoreCase() {
+        return ignoreCase;
+    }
+
+    /**
+ * Sets whether the pattern matching should ignore case or not
+ *
+ * @param newValue
+ */
+    public void setIgnoreCase(boolean newValue) {
+        ignoreCase = newValue;
+    }
+
+    /**
+ * Returns the pattern as string.
+ *
+ * @return
+ */
+    public String getPattern() {
+        return pattern;
+    }
+
+    /**
+ * Sets the pattern to a new value
+ *
+ * @param newValue
+ */
+    public void setPattern(String newValue) {
+        pattern = newValue;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    protected Character digitWildcard() {
+        return digitWildcard;
+    }
+
+    /**
+ *
+ *
+ * @param newValue
+ */
+    protected void digitWildcard(Character newValue) {
+        digitWildcard = newValue;
+    }
+
+    // =========================================================================
+    // CLASS METHODS
+    // =========================================================================
+
+    /**
+ * Returns true, if the given probe string matches the given pattern.  <br>
+ * The character comparison is done case sensitive.
+ *
+ * @param probe The string to check against the pattern.
+ * @param pattern The patter, that probably contains wildcards ( '' or '?'
+ *        )
+ *
+ * @return
+ */
+    public static boolean match(String probe, String pattern) {
+        StringPattern stringPattern = new StringPattern(pattern, false);
+
+        return (stringPattern.matches(probe));
+    }
+
+    // match()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns true, if the given probe string matches the given pattern.  <br>
+ * The character comparison is done ignoring upper/lower-case.
+ *
+ * @param probe The string to check against the pattern.
+ * @param pattern The patter, that probably contains wildcards ( '' or '?'
+ *        )
+ *
+ * @return
+ */
+    public static boolean matchIgnoreCase(String probe, String pattern) {
+        StringPattern stringPattern = new StringPattern(pattern, true);
+
+        return (stringPattern.matches(probe));
+    }
+
+    // matchIgnoreCase()
+    // -------------------------------------------------------------------------
+    // =========================================================================
+    // PUBLIC INSTANCE METHODS
+    // =========================================================================
+
+    /**
+ * Tests if a specified string matches the pattern.
+ *
+ * @param probe The string to compare to the pattern
+ *
+ * @return true if and only if the probe matches the pattern, false
+ *         otherwise.
+ */
+    public boolean matches(String probe) {
+        StringExaminer patternIterator = null;
+        StringExaminer probeIterator = null;
+        char patternCh = '-';
+        char probeCh = '-';
+        String newPattern = null;
+        String subPattern = null;
+        int charIndex = 0;
+
+        if (probe == null) {
+            return false;
+        }
+
+        if (probe.length() == 0) {
+            return false;
+        }
+
+        patternIterator = this.newExaminer(this.getPattern());
+        probeIterator = this.newExaminer(probe);
+        probeCh = probeIterator.nextChar();
+        patternCh = this.getPatternChar(patternIterator, probeCh);
+
+        while ((this.endNotReached(patternCh)) &&
+                (this.endNotReached(probeCh))) {
+            if (patternCh == MULTICHAR_WILDCARD) {
+                patternCh = this.skipWildcards(patternIterator);
+
+                if (this.endReached(patternCh)) {
+                    return true; // No more characters after multi wildcard - So everything matches
+                } else {
+                    patternIterator.skip(-1);
+                    newPattern = this.upToEnd(patternIterator);
+                    charIndex = newPattern.indexOf(MULTICHAR_WILDCARD);
+
+                    if (charIndex >= 0) {
+                        subPattern = newPattern.substring(0, charIndex);
+
+                        if (this.skipAfter(probeIterator, subPattern)) {
+                            patternIterator = this.newExaminer(newPattern.substring(
+                                        charIndex));
+                            patternCh = probeCh;
+                        } else {
+                            return false;
+                        }
+                    } else {
+                        probeIterator.skip(-1);
+
+                        return this.matchReverse(newPattern, probeIterator);
+                    }
+                }
+            }
+
+            if (this.charsAreEqual(probeCh, patternCh)) {
+                if (this.endNotReached(patternCh)) {
+                    probeCh = probeIterator.nextChar();
+                    patternCh = this.getPatternChar(patternIterator, probeCh);
+                }
+            } else {
+                if (patternCh != MULTICHAR_WILDCARD) {
+                    return false; // character is not matching - return immediately
+                }
+            }
+        }
+
+        // while()
+        return ((this.endReached(patternCh)) && (this.endReached(probeCh)));
+    }
+
+    // matches()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the pattern string.
+ *
+ * @see java.lang.Object#toString()
+ */
+    public String toString() {
+        if (this.getPattern() == null) {
+            return super.toString();
+        } else {
+            return this.getPattern();
+        }
+    }
+
+    // toString()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns true if the pattern contains any '' or '?' wildcard character.
+ *
+ * @return
+ */
+    public boolean hasWildcard() {
+        if (this.getPattern() == null) {
+            return false;
+        }
+
+        if (this.hasDigitWildcard()) {
+            if (this.getPattern().indexOf(this.digitWildcardChar()) >= 0) {
+                return true;
+            }
+        }
+
+        return (this.getPattern().indexOf(MULTI_WILDCARD) >= 0) ||
+        (this.getPattern().indexOf(SINGLECHAR_WILDCARD) >= 0);
+    }
+
+    // hasWildcard()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Sets the given character as a wildcard character in this pattern to
+ * match only digits ('0'-'9').   <br>
+ *
+ * @param digitWildcard The placeholder character for digits
+ */
+    public void setDigitWildcardChar(char digitWildcard) {
+        if (digitWildcard <= 0) {
+            this.digitWildcard(null);
+        } else {
+            this.digitWildcard(new Character(digitWildcard));
+        }
+    }
+
+    // setDigitWildcardChar()
+
+    /**
+ *
+ *
+ * @return
+ */
+    protected boolean hasDigitWildcard() {
+        return this.digitWildcard() != null;
+    }
+
+    // hasDigitWildcard()
+    // -------------------------------------------------------------------------
+    protected char digitWildcardChar() {
+        if (this.hasDigitWildcard()) {
+            return this.digitWildcard().charValue();
+        } else {
+            return '\0';
+        }
+    }
+
+    // digitWildcardChar()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Moves the iterator position to the next character that is no wildcard.
+ * Doesn't skip digit wildcards !
+ *
+ * @param iterator
+ *
+ * @return
+ */
+    protected char skipWildcards(StringExaminer iterator) {
+        char result = '-';
+
+        do {
+            result = iterator.nextChar();
+        } while ((result == MULTICHAR_WILDCARD) ||
+                (result == SINGLECHAR_WILDCARD));
+
+        return result;
+    }
+
+    // skipWildcards()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Increments the given iterator up to the last character that matched the
+ * character sequence in the given matchString. Returns true, if the
+ * matchString was found, otherwise false.
+ *
+ * @param examiner
+ * @param matchString The string to be found (must not contain )
+ *
+ * @return
+ */
+    protected boolean skipAfter(StringExaminer examiner, String matchString) {
+        // Do not use the method of StringExaminer anymore, because digit wildcard
+        // support is in the charsAreEqual() method which is unknown to the examiner.
+        // return examiner.skipAfter( matchString ) ;
+        char ch = '-';
+        char matchChar = ' ';
+        boolean found = false;
+        int index = 0;
+
+        if ((matchString == null) || (matchString.length() == 0)) {
+            return false;
+        }
+
+        ch = examiner.nextChar();
+
+        while ((examiner.endNotReached(ch)) && (!found)) {
+            matchChar = matchString.charAt(index);
+
+            if (this.charsAreEqual(ch, matchChar)) {
+                index++;
+
+                if (index >= matchString.length()) { // whole matchString checked ?
+                    found = true;
+                } else {
+                    ch = examiner.nextChar();
+                }
+            } else {
+                if (index == 0) {
+                    ch = examiner.nextChar();
+                } else {
+                    index = 0;
+                }
+            }
+        }
+
+        return found;
+    }
+
+    // skipAfter()
+    // -------------------------------------------------------------------------
+    protected String upToEnd(StringExaminer iterator) {
+        return iterator.upToEnd();
+    }
+
+    // upToEnd()
+    // -------------------------------------------------------------------------
+    protected boolean matchReverse(String pattern, StringExaminer probeIterator) {
+        String newPattern;
+        String newProbe;
+        StringPattern newMatcher;
+        newPattern = MULTI_WILDCARD + pattern;
+        newProbe = this.upToEnd(probeIterator);
+        newPattern = this.strUtil().reverse(newPattern);
+        newProbe = this.strUtil().reverse(newProbe);
+        newMatcher = new StringPattern(newPattern, this.getIgnoreCase());
+
+        if (this.hasDigitWildcard()) {
+            newMatcher.setDigitWildcardChar(this.digitWildcardChar());
+        }
+
+        return newMatcher.matches(newProbe);
+    }
+
+    // matchReverse()
+    // -------------------------------------------------------------------------
+    protected boolean charsAreEqual(char probeChar, char patternChar) {
+        if (this.hasDigitWildcard()) {
+            if (patternChar == this.digitWildcardChar()) {
+                return Character.isDigit(probeChar);
+            }
+        }
+
+        if (this.getIgnoreCase()) {
+            return (Character.toUpperCase(probeChar) == Character.toUpperCase(patternChar));
+        } else {
+            return (probeChar == patternChar);
+        }
+    }
+
+    // charsAreEqual()
+    // -------------------------------------------------------------------------
+    protected boolean endReached(char character) {
+        return (character == StringExaminer.END_REACHED);
+    }
+
+    // endReached()
+    // -------------------------------------------------------------------------
+    protected boolean endNotReached(char character) {
+        return (!endReached(character));
+    }
+
+    // endNotReached()
+    // -------------------------------------------------------------------------
+    protected char getPatternChar(StringExaminer patternIterator, char probeCh) {
+        char patternCh;
+        patternCh = patternIterator.nextChar();
+
+        return ((patternCh == SINGLECHAR_WILDCARD) ? probeCh : patternCh);
+    }
+
+    // getPatternChar()
+    // -------------------------------------------------------------------------
+    protected StringExaminer newExaminer(String str) {
+        return new StringExaminer(str, this.getIgnoreCase());
+    }
+
+    // newExaminer()
+    // -------------------------------------------------------------------------
+    protected StringUtil strUtil() {
+        return StringUtil.current();
+    }
+
+    // strUtil()
+    // -------------------------------------------------------------------------
+}
+
+
+// class StringPattern
diff --git a/src/com/sshtools/daemon/util/StringScanner.java b/src/com/sshtools/daemon/util/StringScanner.java
new file mode 100644
index 0000000000000000000000000000000000000000..c9e94774bb49fca50898bbf7f0a1980e7b15ce84
--- /dev/null
+++ b/src/com/sshtools/daemon/util/StringScanner.java
@@ -0,0 +1,286 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+// ===========================================================================
+// CONTENT  : CLASS StringScanner
+// AUTHOR   : Manfred Duchrow
+// VERSION  : 1.1 - 29/09/2002
+// HISTORY  :
+//  11/07/2001  duma  CREATED
+//	29/09/2002	duma	added		-> endReached(), endNotReached()
+//
+// Copyright (c) 2001-2002, by Manfred Duchrow. All rights reserved.
+// ===========================================================================
+package com.sshtools.daemon.util;
+
+
+// ===========================================================================
+// IMPORTS
+// ===========================================================================
+
+/**
+ * Simple scanner that allows to navigate over the characters of a string.
+ *
+ * @author Manfred Duchrow
+ * @version 1.1
+ */
+public class StringScanner {
+    // =========================================================================
+    // CONSTANTS
+    // =========================================================================
+
+    /**  */
+    public static final char END_REACHED = (char) -1;
+
+    // =========================================================================
+    // INSTANCE VARIABLES
+    // =========================================================================
+
+    /**  */
+    protected int length = 0;
+
+    /**  */
+    protected int position = 0;
+
+    /**  */
+    protected int pos_marker = 0;
+
+    /**  */
+    protected char[] buffer = null;
+
+    // -------------------------------------------------------------------------
+    // =========================================================================
+    // CONSTRUCTORS
+    // =========================================================================
+
+    /**
+ * Initialize the new instance with the string that should be scanned.
+ *
+ * @param stringToScan
+ */
+    public StringScanner(String stringToScan) {
+        super();
+        length = stringToScan.length();
+        buffer = new char[length];
+        stringToScan.getChars(0, length, buffer, 0);
+    }
+
+    // StringScanner()
+    // =========================================================================
+    // PUBLIC CLASS METHODS
+    // =========================================================================
+
+    /**
+ * Returns true, if the given character indicates that the end of the
+ * scanned string is reached.
+ *
+ * @param character
+ *
+ * @return
+ */
+    public boolean endReached(char character) {
+        return (character == END_REACHED);
+    }
+
+    // endReached()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns true, if the given character does <b>not</b> indicate that the
+ * end of the scanned string si reached.
+ *
+ * @param character
+ *
+ * @return
+ */
+    public boolean endNotReached(char character) {
+        return (!endReached(character));
+    }
+
+    // endNotReached()
+    // =========================================================================
+    // PUBLIC INSTANCE METHODS
+    // =========================================================================
+
+    /**
+ * Returns the string the scanner was initialized with
+ *
+ * @return
+ */
+    public String toString() {
+        return new String(buffer);
+    }
+
+    // toString()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Moves the position pointer count characters. positive values move
+ * forwards, negative backwards. The position never becomes negative !
+ *
+ * @param count
+ */
+    public void skip(int count) {
+        position += count;
+
+        if (position < 0) {
+            position = 0;
+        }
+    }
+
+    // skip()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the character at the current position without changing the
+ * position, that is subsequent calls to this method return always the
+ * same character.
+ *
+ * @return
+ */
+    public char peek() {
+        return ((position < length()) ? buffer[position] : END_REACHED);
+    }
+
+    // skip()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the character at the current position and increments the
+ * position afterwards by 1.
+ *
+ * @return
+ */
+    public char nextChar() {
+        char next = this.peek();
+
+        if (endNotReached(next)) {
+            this.skip(1);
+        }
+
+        return next;
+    }
+
+    // nextChar()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns true, if the scanner has reached the end and a further
+ * invocation  of nextChar() would return the END_REACHED character.
+ *
+ * @return
+ */
+    public boolean atEnd() {
+        return (endReached(this.peek()));
+    }
+
+    // atEnd()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns true, if the scanner has not yet reached the end.
+ *
+ * @return
+ */
+    public boolean hasNext() {
+        return !this.atEnd();
+    }
+
+    // hasNext()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the next character that is no whitespace and leaves the position
+ * pointer one character after the returned one.
+ *
+ * @return
+ */
+    public char nextNoneWhitespaceChar() {
+        char next = this.nextChar();
+
+        while ((endNotReached(next)) && (Character.isWhitespace(next))) {
+            next = this.nextChar();
+        }
+
+        return next;
+    }
+
+    // nextNoneWhitespaceChar()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the current position in the string
+ *
+ * @return
+ */
+    public int getPosition() {
+        return position;
+    }
+
+    // getPosition()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Remembers the current position for later use with restorePosition()
+ */
+    public void markPosition() {
+        pos_marker = position;
+    }
+
+    // markPosition()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Restores the position to the value of the latest markPosition() call
+ */
+    public void restorePosition() {
+        this.setPosition(pos_marker);
+    }
+
+    // restorePosition()
+
+    /**
+ *
+ *
+ * @return
+ */
+    protected int length() {
+        return length;
+    }
+
+    // length()
+    // -------------------------------------------------------------------------
+    protected void setPosition(int pos) {
+        if ((pos >= 0) && (pos <= this.length())) {
+            position = pos;
+        }
+    }
+
+    // setPosition()
+    // -------------------------------------------------------------------------
+}
+
+
+// class StringScanner
diff --git a/src/com/sshtools/daemon/util/StringUtil.java b/src/com/sshtools/daemon/util/StringUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..235601d9089fed5e346f34a1aadb92b98f742d49
--- /dev/null
+++ b/src/com/sshtools/daemon/util/StringUtil.java
@@ -0,0 +1,1871 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+// ===========================================================================
+// CONTENT  : CLASS StringUtil
+// AUTHOR   : Manfred Duchrow
+// VERSION  : 2.0 - 21/03/2003
+// HISTORY  :
+//  10/07/1999 	duma  CREATED
+//	09/12/1999	duma	added		->	SPACE, repeat()
+//										moved		->	from package com.mdcs.util
+//	25/01/2000	duma	moved		->	from package com.mdcs.text
+//  09/02/2000  duma  changed ->  renamed SPACE to CH_SPACE
+//                    added   ->  CH_CR, CH_TAB, ..., STR_SPACE, STR_NEWLINE, ...
+//  11/01/2002  duma  added   ->	indexOf(), indexOfIgnoreCase(), contains(), containsIgnoreCase()
+//	17/05/2002	duma	added		->	copyFrom()
+//	03/07/2002	duma	added		->	cutHead(), prefix(), suffix()
+//	06/07/2002	duma	added		->	indexOf() and contains() for StringPattern and reverse()
+//	15/08/2002	duma	added		->	upTo(), startingFrom(), asMap()
+//	29/09/2002	duma	added		->	allParts() and allSubstrings() that don't skip empty elements
+//	06/03/2003	duma	changed	->	append() now uses System.arraycopy()
+//										added		->	remove( String[], String[] ), remove( String[], String )
+//																removeNull( String[] )
+//	21/03/2003	duma	added		->	leftPad(), leftPadCh(), rightPad(), rightPadCh() for int values
+//
+// Copyright (c) 1999-2003, by Manfred Duchrow. All rights reserved.
+// ===========================================================================
+package com.sshtools.daemon.util;
+
+import java.io.*;
+
+import java.text.*;
+
+import java.util.*;
+
+
+/**
+ * The sole instance of this class provides several convienience methods for
+ * string manipulation such as substring replacement or character repetition.
+ *
+ * @author Manfred Duchrow
+ * @version 2.0
+ */
+public class StringUtil {
+    // =========================================================================
+    // CONSTANTS
+    // =========================================================================
+
+    /** Constant for the space character */
+    public static final char CH_SPACE = '\u0020';
+
+    /** Constant for the new line character */
+    public static final char CH_NEWLINE = '\n';
+
+    /** Constant for the carriage return character */
+    public static final char CH_CR = '\r';
+
+    /** Constant for the tabulator character */
+    public static final char CH_TAB = '\t';
+
+    /** Constant for the String representation of the space character */
+    public static final String STR_SPACE = "\u0020";
+
+    /** Constant for the String representation of the new line character */
+    public static final String STR_NEWLINE = "\n";
+
+    /**
+ * Constant for the String representation of the carriage return character
+ */
+    public static final String STR_CR = "\r";
+
+    /** Constant for the String representation of the tabulator character */
+    public static final String STR_TAB = "\t";
+    private static final String WORD_DELIM = STR_SPACE + STR_TAB + STR_NEWLINE +
+        STR_CR;
+
+    // =========================================================================
+    // CLASS VARIABLES
+    // =========================================================================
+    private static StringUtil singleton = null;
+
+    private static StringUtil getSingleton() {
+        return singleton;
+    }
+
+    private static void setSingleton(StringUtil inst) {
+        singleton = inst;
+    }
+
+    // =========================================================================
+    // PUBLIC CLASS METHODS
+    // =========================================================================
+
+    /**
+ * Returns the one and only instance of this class.
+ *
+ * @return
+ */
+    public static StringUtil current() {
+        if (getSingleton() == null) {
+            setSingleton(new StringUtil());
+        }
+
+        return getSingleton();
+    }
+
+    // current()
+    // =========================================================================
+    // PUBLIC INSTANCE METHODS
+    // =========================================================================
+
+    /**
+     * Returns the given string with all found oldSubStr replaced by newSubStr. <br>
+ * Example: StringUtil.current().replaceAll( "Seven of ten", "even", "ix"
+ * ) ;<br>
+ * results in: "Six of ten"
+ *
+ * @param sourceStr The string that should be checked for occurrences of
+ *        oldSubStr
+ * @param oldSubStr The string that is searched for in sourceStr
+ * @param newSubStr The new string that is placed everywhere the oldSubStr
+ *        was found
+ *
+ * @return The original string with all found substrings replaced by new
+ *         strings
+ */
+    public String replaceAll(String sourceStr, String oldSubStr,
+        String newSubStr) {
+        String part = null;
+        String result = "";
+        int index = -1;
+        int subLen = 0;
+        subLen = oldSubStr.length();
+        part = sourceStr;
+
+        while ((part.length() > 0) && (subLen > 0)) {
+            index = part.indexOf(oldSubStr);
+
+            if (index >= 0) {
+                result = result + part.substring(0, index) + newSubStr;
+                part = part.substring(index + subLen);
+            } else {
+                result = result + part;
+                part = "";
+            }
+        }
+
+        // while
+        return result;
+    }
+
+    // replaceAll()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns a string with size of count and all characters initialized with
+ * ch.   <br>
+ *
+ * @param ch the character to be repeated in the result string.
+ * @param count the number of times the given character should occur in the
+ *        result string.
+ *
+ * @return A string containing <i>count</i> characters <i>ch</i>.
+ */
+    public String repeat(char ch, int count) {
+        StringBuffer buffer = null;
+        buffer = new StringBuffer(count);
+
+        for (int i = 1; i <= count; i++) {
+            buffer.append(ch);
+        }
+
+        return (buffer.toString());
+    }
+
+    // repeat()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns an array of substrings of the given text.    <br>
+ * The delimiters between the substrings are the whitespace characters
+ * SPACE, NEWLINE, CR and TAB.
+ *
+ * @param text The string that should be splitted into whitespace separated
+ *        words
+ *
+ * @return An array of substrings of the given text
+ *
+ * @see #parts(String, String)
+ */
+    public String[] words(String text) {
+        return this.parts(text, WORD_DELIM);
+    }
+
+    // words()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns an array of substrings of the given text.    <br>
+ * The separators between the substrings are the given delimiters. Each
+ * character in the delimiter string is treated as a separator. <br>
+ * All consecutive delimiters are treated as one delimiter, that is there
+ * will be no empty strings in the result.
+ *
+ * @param text The string that should be splitted into substrings
+ * @param delimiters All characters that should be recognized as a
+ *        separator or substrings
+ *
+ * @return An array of substrings of the given text
+ *
+ * @see #allParts(String, String)
+ * @see #substrings(String, String)
+ * @see #allSubstrings(String, String)
+ */
+    public String[] parts(String text, String delimiters) {
+        return this.parts(text, delimiters, false);
+    }
+
+    // parts()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns an array of substrings of the given text.    <br>
+ * The separators between the substrings are the given delimiters. Each
+ * character in the delimiter string is treated as a separator. <br>
+ * For each delimiter that is followed immediately by another delimiter an
+ * empty string will be added to the result. There are no empty strings
+ * added to the result for a delimiter at the very beginning of at the
+ * very end.
+ *
+ * <p>
+ * Examples:
+ * </p>
+ *
+ * <p>
+ * allParts( "/A/B//", "/" )  --> { "A", "B", "" }<br>
+ * allParts( "/A,B/C;D", ",;/" )  --> { "A", "B", "C", "D" }<br>
+ * allParts( "A/B,C/D", "," )  --> { "A/B", "C/D" }<br>
+ * </p>
+ *
+ * @param text The string that should be splitted into substrings
+ * @param delimiters All characters that should be recognized as a
+ *        separator or substrings
+ *
+ * @return An array of substrings of the given text
+ *
+ * @see #parts(String, String)
+ * @see #substrings(String, String)
+ * @see #allSubstrings(String, String)
+ */
+    public String[] allParts(String text, String delimiters) {
+        return this.parts(text, delimiters, true);
+    }
+
+    // allParts()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the given text split up into an array of strings, at the
+ * occurrances of the separator string.  In contrary to method parts() the
+ * separator is a one or many character sequence delimiter. That is, only
+ * the exact sequence  of the characters in separator identifies the end
+ * of a substring. Subsequent occurences of separator will be skipped.
+ * Therefore no empty strings ("") will be in the result array.
+ *
+ * @param text The text to be split up
+ * @param separator The string that separates the substrings
+ *
+ * @return An array of substrings not containing any separator anymore
+ *
+ * @see #allSubstrings(String, String)
+ * @see #parts(String, String)
+ * @see #allParts(String, String)
+ */
+    public String[] substrings(String text, String separator) {
+        return this.substrings(text, separator, false);
+    }
+
+    // substrings()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the given text split up into an array of strings, at the
+ * occurrances of the separator string.  In contrary to method allParts()
+ * the separator is a one or many character sequence delimiter. That is,
+ * only the exact sequence  of the characters in separator identifies the
+ * end of a substring. Subsequent occurences of separator are not skipped.
+ * They are added as empty strings to the result.
+ *
+ * @param text The text to be split up
+ * @param separator The string that separates the substrings
+ *
+ * @return An array of substrings not containing any separator anymore
+ *
+ * @see #substrings(String, String)
+ * @see #parts(String, String)
+ * @see #allParts(String, String)
+ */
+    public String[] allSubstrings(String text, String separator) {
+        return this.substrings(text, separator, true);
+    }
+
+    // allSubstrings()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the first substring that is enclosed by the specified
+ * delimiters.   <br>
+ * The delimiters are not included in the return string.
+ *
+ * <p>
+ * Example:<br> getDelimitedSubstring( "This {placeholder} belongs to me",
+ * "{", "}" ) --> returns "placeholder"
+ * </p>
+ *
+ * @param text The input string that contains the delimited part
+ * @param startDelimiter The start delimiter of the substring
+ * @param endDelimiter The end delimiter of the substring
+ *
+ * @return The substring or an empty string, if no delimiters are found.
+ */
+    public String getDelimitedSubstring(String text, String startDelimiter,
+        String endDelimiter) {
+        int start;
+        int stop;
+        String subStr = "";
+
+        if ((text != null) && (startDelimiter != null) &&
+                (endDelimiter != null)) {
+            start = text.indexOf(startDelimiter);
+
+            if (start >= 0) {
+                stop = text.indexOf(endDelimiter, start + 1);
+
+                if (stop > start) {
+                    subStr = text.substring(start + 1, stop);
+                }
+            }
+        }
+
+        return subStr;
+    }
+
+    // getDelimitedSubstring()
+    // -------------------------------------------------------------------------
+
+    /**
+     * Returns the first substring that is enclosed by the specified delimiter. <br>
+ * The delimiters are not included in the return string.
+ *
+ * <p>
+ * Example:<br> getDelimitedSubstring( "File 'text.txt' not found.", "'",
+ * "'" ) --> returns "text.txt"
+ * </p>
+ *
+ * @param text The input string that contains the delimited part
+ * @param delimiter The start and end delimiter of the substring
+ *
+ * @return The substring or an empty string, if no delimiters are found.
+ */
+    public String getDelimitedSubstring(String text, String delimiter) {
+        return this.getDelimitedSubstring(text, delimiter, delimiter);
+    }
+
+    // getDelimitedSubstring()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Prints the stack trace of the specified throwable to a string and
+ * returns it.
+ *
+ * @param throwable
+ *
+ * @return
+ */
+    public String stackTrace(Throwable throwable) {
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        throwable.printStackTrace(pw);
+        pw.close();
+
+        return sw.toString();
+    }
+
+    // stackTrace()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the given string filled (on the left) up to the specified length
+ * with the given character.     <br>
+ * Example: leftPadCh( "12", 6, '0' ) --> "000012"
+ *
+ * @param str
+ * @param len
+ * @param ch
+ *
+ * @return
+ */
+    public String leftPadCh(String str, int len, char ch) {
+        return this.padCh(str, len, ch, true);
+    }
+
+    // leftPadCh()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the given string filled (on the left) up to the specified length
+ * with spaces.     <br>
+ * Example: leftPad( "XX", 4 ) --> "  XX"
+ *
+ * @param str The string that has to be filled up to the specified length
+ * @param len The length of the result string
+ *
+ * @return
+ */
+    public String leftPad(String str, int len) {
+        return this.leftPadCh(str, len, CH_SPACE);
+    }
+
+    // leftPad()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the given integer as string filled (on the left) up to the
+ * specified length with the given fill character.     <br>
+ * Example: leftPad( 24, 5, '' ) --> "24"
+ *
+ * @param value
+ * @param len
+ * @param fillChar
+ *
+ * @return
+ */
+    public String leftPadCh(int value, int len, char fillChar) {
+        return this.leftPadCh(Integer.toString(value), len, fillChar);
+    }
+
+    // leftPadCh()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the given integer as string filled (on the left) up to the
+ * specified length with zeroes.     <br>
+ * Example: leftPad( 12, 4 ) --> "0012"
+ *
+ * @param value
+ * @param len
+ *
+ * @return
+ */
+    public String leftPad(int value, int len) {
+        return this.leftPadCh(value, len, '0');
+    }
+
+    // leftPad()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the given string filled (on the right) up to the specified
+ * length with the given character.     <br>
+ * Example: rightPadCh( "34", 5, 'X' ) --> "34XXX"
+ *
+ * @param str
+ * @param len
+ * @param ch
+ *
+ * @return
+ */
+    public String rightPadCh(String str, int len, char ch) {
+        return this.padCh(str, len, ch, false);
+    }
+
+    // rightPadCh()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the given string filled (on the right) up to the specified
+ * length with spaces.     <br>
+ * Example: rightPad( "88", 6 ) --> "88    "
+ *
+ * @param str
+ * @param len
+ *
+ * @return
+ */
+    public String rightPad(String str, int len) {
+        return this.rightPadCh(str, len, CH_SPACE);
+    }
+
+    // rightPad()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the given integer as string filled (on the right) up to the
+ * specified length with the given character.     <br>
+ * Example: rightPad( "32", 4, '#' ) --> "32##"
+ *
+ * @param value
+ * @param len
+ * @param fillChar
+ *
+ * @return
+ */
+    public String rightPadCh(int value, int len, char fillChar) {
+        return this.rightPadCh(Integer.toString(value), len, fillChar);
+    }
+
+    // rightPad()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the given integer as string filled (on the right) up to the
+ * specified length with spaces.     <br>
+ * Example: rightPad( "17", 5 ) --> "17   "
+ *
+ * @param value
+ * @param len
+ *
+ * @return
+ */
+    public String rightPad(int value, int len) {
+        return this.rightPadCh(value, len, CH_SPACE);
+    }
+
+    // rightPad()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the given string filled equally left and right up to the
+ * specified length with the given character.     <br>
+ * Example: centerCh( "A", 5, '_' ) --> "__A__"  <br>
+ * Example: centerCh( "XX", 7, '+' ) --> "++XX+++"
+ *
+ * @param str
+ * @param len
+ * @param ch
+ *
+ * @return
+ */
+    public String centerCh(String str, int len, char ch) {
+        String buffer = null;
+        int missing = len - str.length();
+        int half = 0;
+
+        if (missing <= 0) {
+            return str;
+        }
+
+        half = missing / 2;
+        buffer = this.rightPadCh(str, len - half, ch);
+
+        return this.leftPadCh(buffer, len, ch);
+    }
+
+    // centerCh()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the given string filled (on the right and right) up to the
+ * specified length with spaces.     <br>
+ * Example: center( "Mike", 10 ) --> "   Mike   "
+ *
+ * @param str
+ * @param len
+ *
+ * @return
+ */
+    public String center(String str, int len) {
+        return this.centerCh(str, len, CH_SPACE);
+    }
+
+    // center()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the given string array extended by one element that hold the
+ * specified string.
+ *
+ * @param strings
+ * @param string
+ *
+ * @return
+ */
+    public String[] append(String[] strings, String string) {
+        String[] appStr = { string };
+
+        return this.append(strings, appStr);
+    }
+
+    // append()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns an array of strings that contains all strings given by the first
+ * and second string array. The strings from the  second array will be
+ * added at the end of the first array.
+ *
+ * @param strings The array of string to which to append
+ * @param appendStrings The string to be appended to the first array
+ *
+ * @return
+ */
+    public String[] append(String[] strings, String[] appendStrings) {
+        String[] newStrings = null;
+
+        if (strings == null) {
+            return appendStrings;
+        }
+
+        if (appendStrings == null) {
+            return strings;
+        }
+
+        newStrings = new String[strings.length + appendStrings.length];
+        System.arraycopy(strings, 0, newStrings, 0, strings.length);
+        System.arraycopy(appendStrings, 0, newStrings, strings.length,
+            appendStrings.length);
+
+        return newStrings;
+    }
+
+    // append()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns an array of strings that contains all strings given in the first
+ * plus the specified string to append, if it is not already in the given
+ * array.
+ *
+ * @param strings
+ * @param appendString
+ *
+ * @return
+ */
+    public String[] appendIfNotThere(String[] strings, String appendString) {
+        if (this.contains(strings, appendString)) {
+            return strings;
+        } else {
+            return this.append(strings, appendString);
+        }
+    }
+
+    // appendIfNotThere()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns an array of strings that contains all strings given in the first
+ * plus all strings of the second array that are not already in the first
+ * array.
+ *
+ * @param strings
+ * @param appendStrings
+ *
+ * @return
+ */
+    public String[] appendIfNotThere(String[] strings, String[] appendStrings) {
+        String[] newStrings = strings;
+
+        if (appendStrings == null) {
+            return newStrings;
+        }
+
+        for (int i = 0; i < appendStrings.length; i++) {
+            newStrings = this.appendIfNotThere(newStrings, appendStrings[i]);
+        }
+
+        return newStrings;
+    }
+
+    // appendIfNotThere()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Removes all string of the second array from the first array. Returns a
+ * new array of string that contains all remaining strings of the original
+ * strings array.
+ *
+ * @param strings The array from which to remove the strings
+ * @param removeStrings The strings to be removed
+ *
+ * @return
+ */
+    public String[] remove(String[] strings, String[] removeStrings) {
+        if ((strings == null) || (removeStrings == null) ||
+                (strings.length == 0) || (removeStrings.length == 0)) {
+            return strings;
+        }
+
+        return this.removeFromStringArray(strings, removeStrings);
+    }
+
+    // remove()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Removes the given string from the specified string array. Returns a new
+ * array of string that contains all remaining strings of the original
+ * strings array.
+ *
+ * @param strings The array from which to remove the string
+ * @param removeString The string to be removed
+ *
+ * @return
+ */
+    public String[] remove(String[] strings, String removeString) {
+        String[] removeStrings = { removeString };
+
+        return this.remove(strings, removeStrings);
+    }
+
+    // remove()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Removes all null values from the given string array. Returns a new
+ * string array that contains all none null values of the  input array.
+ *
+ * @param strings The array to be cleared of null values
+ *
+ * @return
+ */
+    public String[] removeNull(String[] strings) {
+        if (strings == null) {
+            return strings;
+        }
+
+        return this.removeFromStringArray(strings, null);
+    }
+
+    // removeNull()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns a string that contains all given strings concatenated and
+ * separated by the specified separator.
+ *
+ * @param strings The array of strings that should be concatenated
+ * @param separator The separator between the strings
+ *
+ * @return One string containing the concatenated strings separated by
+ *         separator
+ */
+    public String asString(String[] strings, String separator) {
+        StringBuffer buffer = null;
+        buffer = new StringBuffer(strings.length * 20);
+
+        if (strings.length > 0) {
+            buffer.append(strings[0].toString());
+
+            for (int i = 1; i < strings.length; i++) {
+                buffer.append(separator);
+
+                if (strings[i] != null) {
+                    buffer.append(strings[i]);
+                }
+            }
+        }
+
+        return buffer.toString();
+    }
+
+    // asString()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns a string that contains all given strings concatenated and
+ * separated by comma.
+ *
+ * @param strings The array of strings that should be concatenated
+ *
+ * @return One string containing the concatenated strings separated by
+ *         comma (",")
+ */
+    public String asString(String[] strings) {
+        return this.asString(strings, ",");
+    }
+
+    // asString()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the index of the first string in the given string array that
+ * matches the specified string pattern. If no string is found in the
+ * array the result is -1.
+ *
+ * @param strArray An array of string (may contain null elements)
+ * @param pattern The pattern the searched string must match
+ *
+ * @return The index of the matching string in the array or -1 if not found
+ */
+    public int indexOf(String[] strArray, StringPattern pattern) {
+        if ((strArray == null) || (strArray.length == 0)) {
+            return -1;
+        }
+
+        boolean found = false;
+
+        for (int i = 0; i < strArray.length; i++) {
+            if (strArray[i] == null) {
+                if (pattern == null) {
+                    found = true;
+                }
+            } else {
+                if (pattern != null) {
+                    found = pattern.matches(strArray[i]);
+                }
+            }
+
+            if (found) {
+                return i;
+            }
+        }
+
+        return -1;
+    }
+
+    // indexOf()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the index of the specified string in the given string array. It
+ * returns the index of the first occurrence of the string. If the string
+ * is not found in the array the result is -1. The comparison of the
+ * strings is case-sensitive!
+ *
+ * @param strArray An array of string (may contain null elements)
+ * @param searchStr The string to be looked up in the array (null allowed)
+ *
+ * @return The index of the string in the array or -1 if not found
+ */
+    public int indexOf(String[] strArray, String searchStr) {
+        return this.indexOfString(strArray, searchStr, false);
+    }
+
+    // indexOf()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the index of the specified string in the given string array. It
+ * returns the index of the first occurrence of the string. If the string
+ * is not found in the array the result is -1. The comparison of the
+ * strings is case-insensitive!
+ *
+ * @param strArray An array of string (may contain null elements)
+ * @param searchStr The string to be looked up in the array (null allowed)
+ *
+ * @return The index of the string in the array or -1 if not found
+ */
+    public int indexOfIgnoreCase(String[] strArray, String searchStr) {
+        return this.indexOfString(strArray, searchStr, true);
+    }
+
+    // indexOfIgnoreCase()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns whether or not the specified string can be found in the given
+ * string array.
+ *
+ * @param strArray An array of string (may contain null elements)
+ * @param searchStr The string to be looked up in the array (null allowed)
+ * @param ignoreCase Defines whether or not the comparison is
+ *        case-sensitive.
+ *
+ * @return true, if the specified array contains the given string
+ */
+    public boolean contains(String[] strArray, String searchStr,
+        boolean ignoreCase) {
+        if (ignoreCase) {
+            return this.containsIgnoreCase(strArray, searchStr);
+        } else {
+            return this.contains(strArray, searchStr);
+        }
+    }
+
+    // contains()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns whether or not a string can be found in the given string array
+ * that matches the specified string pattern.
+ *
+ * @param strArray An array of string (may contain null elements)
+ * @param pattern The string pattern to match against in the array (null
+ *        allowed)
+ *
+ * @return true, if the specified array contains a string matching the
+ *         pattern
+ */
+    public boolean contains(String[] strArray, StringPattern pattern) {
+        return (this.indexOf(strArray, pattern) >= 0);
+    }
+
+    // contains()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns whether or not the specified string can be found in the given
+ * string array. The comparison of the strings is case-sensitive!
+ *
+ * @param strArray An array of string (may contain null elements)
+ * @param searchStr The string to be looked up in the array (null allowed)
+ *
+ * @return true, if the specified array contains the given string
+ */
+    public boolean contains(String[] strArray, String searchStr) {
+        return (this.indexOf(strArray, searchStr) >= 0);
+    }
+
+    // contains()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns whether or not the specified string can be found in the given
+ * string array. The comparison of the strings is case-insensitive!
+ *
+ * @param strArray An array of string (may contain null elements)
+ * @param searchStr The string to be looked up in the array (null allowed)
+ *
+ * @return true, if the specified array contains the given string
+ */
+    public boolean containsIgnoreCase(String[] strArray, String searchStr) {
+        return (this.indexOfIgnoreCase(strArray, searchStr) >= 0);
+    }
+
+    // containsIgnoreCase()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns all elements of string array <i>from</i> in a new array from
+ * index start up to the end. If start index is larger than the array's
+ * length, an empty array will be returned.
+ *
+ * @param from The string array the elements should be copied from
+ * @param start Index of the first element to copy
+ *
+ * @return
+ */
+    public String[] copyFrom(String[] from, int start) {
+        if (from == null) {
+            return null;
+        }
+
+        return this.copyFrom(from, start, from.length - 1);
+    }
+
+    // copyFrom()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns all elements of string array <i>from</i> in a new array from
+ * index start up to index end (inclusive). If end is larger than the last
+ * valid index, it will be reduced to the last index. If end index is less
+ * than start index, an empty array will be returned.
+ *
+ * @param from The string array the elements should be copied from
+ * @param start Index of the first element to copy
+ * @param end Index of last element to be copied
+ *
+ * @return
+ */
+    public String[] copyFrom(String[] from, int start, int end) {
+        String[] result;
+        int count;
+        int stop = end;
+
+        if (from == null) {
+            return null;
+        }
+
+        if (stop > (from.length - 1)) {
+            stop = from.length - 1;
+        }
+
+        count = stop - start + 1;
+
+        if (count < 1) {
+            return new String[0];
+        }
+
+        result = new String[count];
+        System.arraycopy(from, start, result, 0, count);
+
+        return result;
+    }
+
+    // copyFrom()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the portion of the given string that comes before the last
+ * occurance of the specified separator.    <br>
+ * If the separator could not be found in the given string, then the
+ * string is returned unchanged.
+ *
+ * <p>
+ * Examples:
+ * </p>
+ *
+ * <p>
+ * cutTail( "A/B/C", "/" ) ;   // returns "A/B" <br>
+ * cutTail( "A/B/C", "," ) ;   // returns "A/B/C"
+ * </p>
+ *
+ * <p></p>
+ *
+ * @param text The string from which to cut off the tail
+ * @param separator The separator from where to cut off
+ *
+ * @return the string without the separator and without the characters
+ *         after the separator
+ *
+ * @see #prefix( String, String )
+ * @see #suffix( String, String )
+ * @see #cutHead( String, String )
+ * @see #startingFrom( String, String )
+ * @see #upTo( String, String )
+ */
+    public String cutTail(String text, String separator) {
+        int index;
+
+        if ((text == null) || (separator == null)) {
+            return text;
+        }
+
+        index = text.lastIndexOf(separator);
+
+        if (index < 0) {
+            return text;
+        }
+
+        return text.substring(0, index);
+    }
+
+    // cutTail()
+    // ------------------------------------------------------------------------
+
+    /**
+ * Returns the portion of the given string that stands after the last
+ * occurance of the specified separator.    <br>
+ * If the separator could not be found in the given string, then the
+ * string is returned unchanged.
+ *
+ * <p>
+ * Examples:
+ * </p>
+ *
+ * <p>
+ * cutHead( "A/B/C", "/" ) ;   // returns "C" <br>
+ * cutHead( "A/B/C", "," ) ;   // returns "A/B/C"
+ * </p>
+ *
+ * <p></p>
+ *
+ * @param text The string from which to cut off the head
+ * @param separator The separator up to which to cut off
+ *
+ * @return the string without the separator and without the characters
+ *         before the separator
+ *
+ * @see #prefix( String, String )
+ * @see #cutTail( String, String )
+ * @see #suffix( String, String )
+ * @see #startingFrom( String, String )
+ * @see #upTo( String, String )
+ */
+    public String cutHead(String text, String separator) {
+        int index;
+
+        if ((text == null) || (separator == null)) {
+            return text;
+        }
+
+        index = text.lastIndexOf(separator);
+
+        if (index < 0) {
+            return text;
+        }
+
+        return text.substring(index + 1);
+    }
+
+    // cutHead()
+    // ------------------------------------------------------------------------
+
+    /**
+ * Returns a string array with two elements where the first is the
+ * attribute name and the second is the attribute value. Splits the given
+ * string at the first occurance of separator and returns the piece before
+ * the separator in element 0 and the piece after the  separator in the
+ * returned array. If the separator is not found, the first element
+ * contains the full string and the second an empty string.
+ *
+ * @param str The string that contains the name-value pair
+ * @param separator The separator between name and value
+ *
+ * @return
+ */
+    public String[] splitNameValue(String str, String separator) {
+        String[] result = { "", "" };
+        int index;
+
+        if (str != null) {
+            index = str.indexOf(separator);
+
+            if (index > 0) {
+                result[0] = str.substring(0, index);
+                result[1] = str.substring(index + separator.length());
+            } else {
+                result[0] = str;
+            }
+        }
+
+        return result;
+    }
+
+    // splitNameValue()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the substring of the given string that comes before the first
+ * occurance of the specified separator. If the string starts with a
+ * separator, the result will be an empty string. If the string doesn't
+ * contain the separator the method returns null.
+ *
+ * <p>
+ * Examples:
+ * </p>
+ *
+ * <p>
+ * prefix( "A/B/C", "/" ) ;   // returns "A" <br>
+ * prefix( "A/B/C", "," ) ;   // returns null
+ * </p>
+ *
+ * <p></p>
+ *
+ * @param str The string of which the prefix is desired
+ * @param separator Separates the prefix from the rest of the string
+ *
+ * @return
+ *
+ * @see #suffix( String, String )
+ * @see #cutTail( String, String )
+ * @see #cutHead( String, String )
+ * @see #startingFrom( String, String )
+ * @see #upTo( String, String )
+ */
+    public String prefix(String str, String separator) {
+        return this.prefix(str, separator, true);
+    }
+
+    // prefix()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the substring of the given string that comes after the first
+ * occurance of the specified separator. If the string ends with a
+ * separator, the result will be an empty string. If the string doesn't
+ * contain the separator the method returns null.
+ *
+ * <p>
+ * Examples:
+ * </p>
+ *
+ * <p>
+ * suffix( "A/B/C", "/" ) ;   // returns "B/C" <br>
+ * suffix( "A/B/C", "," ) ;   // returns null
+ * </p>
+ *
+ * <p></p>
+ *
+ * @param str The string of which the suffix is desired
+ * @param separator Separates the suffix from the rest of the string
+ *
+ * @return
+ *
+ * @see #prefix( String, String )
+ * @see #cutTail( String, String )
+ * @see #cutHead( String, String )
+ * @see #startingFrom( String, String )
+ * @see #upTo( String, String )
+ */
+    public String suffix(String str, String separator) {
+        return this.suffix(str, separator, true);
+    }
+
+    // suffix()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the substring of the given string that comes before the first
+ * occurance of the specified separator. If the string starts with a
+ * separator, the result will be an empty string. If the string doesn't
+ * contain the separator the method returns the whole string unchanged.
+ *
+ * <p>
+ * Examples:
+ * </p>
+ *
+ * <p>
+ * upTo( "A/B/C", "/" ) ;   // returns "A" <br>
+ * upTo( "A/B/C", "," ) ;   // returns "A/B/C" <br>
+ * upTo( "/A/B/C", "/" ) ;   // returns ""
+ * </p>
+ *
+ * <p></p>
+ *
+ * @param str The string of which the prefix is desired
+ * @param separator Separates the prefix from the rest of the string
+ *
+ * @return
+ *
+ * @see #prefix( String, String )
+ * @see #cutTail( String, String )
+ * @see #cutHead( String, String )
+ * @see #startingFrom( String, String )
+ * @see #suffix( String, String )
+ */
+    public String upTo(String str, String separator) {
+        return this.prefix(str, separator, false);
+    }
+
+    // upTo()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the substring of the given string that comes after the first
+ * occurance of the specified separator. If the string doesn't contain the
+ * separator the method returns the whole string unchanged.
+ *
+ * <p>
+ * Examples:
+ * </p>
+ *
+ * <p>
+ * startingFrom( "A/B/C", "/" ) ;   // returns "B/C" <br>
+ * startingFrom( "A/B/C", "," ) ;   // returns "A/B/C"
+ * </p>
+ *
+ * <p></p>
+ *
+ * @param str The string of which the suffix is desired
+ * @param separator Separates the suffix from the rest of the string
+ *
+ * @return
+ *
+ * @see #prefix( String, String )
+ * @see #cutTail( String, String )
+ * @see #cutHead( String, String )
+ * @see #suffix( String, String )
+ * @see #upTo( String, String )
+ */
+    public String startingFrom(String str, String separator) {
+        return this.suffix(str, separator, false);
+    }
+
+    // startingFrom()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns a string that contains all characters of the given string in
+ * reverse order.
+ *
+ * @param str
+ *
+ * @return
+ */
+    public String reverse(String str) {
+        if (str == null) {
+            return null;
+        }
+
+        char[] newStr = new char[str.length()];
+        StringCharacterIterator iterator = new StringCharacterIterator(str);
+        int i = 0;
+
+        for (char ch = iterator.last(); ch != CharacterIterator.DONE;
+                ch = iterator.previous()) {
+            newStr[i] = ch;
+            i++;
+        }
+
+        return new String(newStr);
+    }
+
+    // reverse()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the given map with new entries from the specified String. If the
+     * specified map is null a new empty java.util.Hashtable will be  created. <br>
+ * The string is split up into elements separated by the elementSeparator
+     * parameter. If this parameter is null the default separator "," is used. <br>
+ * After that each part is split up to a key-value pair separated by the
+ * keyValueSeparator parameter. If this parameter is null the default "="
+ * is used. <br>
+ * Then the key-value pairs are added to the map and the map is returned.
+ *
+ * <p>
+ * <b>Be aware that all leading and trailing whitespaces of keys and values
+ * will be removed!</b>
+ * </p>
+ *
+ * @param str The string that contains the list of key-value pairs
+ * @param elementSeparator The separator between the elements of the list
+ * @param keyValueSeparator The separator between the keys and values
+ * @param map The map to which the key-value pairs are added
+ *
+ * @return
+ */
+    public Map toMap(String str, String elementSeparator,
+        String keyValueSeparator, Map map) {
+        Map result;
+        String elemSep;
+        String kvSep;
+        String[] assignments;
+        String[] nameValue;
+
+        if (str == null) {
+            return map;
+        }
+
+        result = ((map == null) ? new Hashtable() : map);
+        elemSep = (elementSeparator == null) ? "," : elementSeparator;
+        kvSep = (keyValueSeparator == null) ? "=" : keyValueSeparator;
+        assignments = this.parts(str, elemSep);
+
+        for (int i = 0; i < assignments.length; i++) {
+            nameValue = this.splitNameValue(assignments[i], kvSep);
+            nameValue[0] = nameValue[0].trim();
+            nameValue[1] = nameValue[1].trim();
+
+            if (nameValue[0].length() > 0) {
+                result.put(nameValue[0], nameValue[1]);
+            }
+        }
+
+        return result;
+    }
+
+    // asMap()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns a new map object that contains all key-value pairs of the
+ * specified string.  <br>
+ * The separator between the elements is assumed to be "," and "=" between
+ * key and value.
+ *
+ * <p>
+ * Example:<br> "main=Fred,support1=John,support2=Stella,manager=Oscar"
+ * </p>
+ *
+ * <p>
+ * <b>Be aware that all leading and trailing whitespaces of keys and values
+ * will be removed!</b>
+ * </p>
+ *
+ * @param str The string with the list of key-value pairs
+ *
+ * @return
+ */
+    public Map asMap(String str) {
+        return this.toMap(str, null, null, null);
+    }
+
+    // asMap()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns a new map object that contains all key-value pairs of the
+ * specified string.  <br>
+ * The separator between the keys and values is assumed to be "=".
+ *
+ * <p>
+ * <b>Be aware that all leading and trailing whitespaces of keys and values
+ * will be removed!</b>
+ * </p>
+ *
+ * @param str The string that contains the list of key-value pairs
+ * @param elementSeparator The separator between the elements of the list
+ *
+ * @return
+ */
+    public Map asMap(String str, String elementSeparator) {
+        return this.toMap(str, elementSeparator, null, null);
+    }
+
+    // asMap()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns a new map object that contains all key-value pairs of the
+ * specified string.
+ *
+ * <p>
+ * <b>Be aware that all leading and trailing whitespaces of keys and values
+ * will be removed!</b>
+ * </p>
+ *
+ * @param str The string that contains the list of key-value pairs
+ * @param elementSeparator The separator between the elements of the list
+ * @param keyValueSeparator The separator between the keys and values
+ *
+ * @return
+ */
+    public Map asMap(String str, String elementSeparator,
+        String keyValueSeparator) {
+        return this.toMap(str, elementSeparator, keyValueSeparator, null);
+    }
+
+    // asMap()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the given map object with all key-value pairs of the specified
+ * string added to it. <br>
+ * The separator between the keys and values is assumed to be "=".
+ *
+ * <p>
+ * <b>Be aware that all leading and trailing whitespaces of keys and values
+ * will be removed!</b>
+ * </p>
+ *
+ * @param str The string that contains the list of key-value pairs
+ * @param elementSeparator The separator between the elements of the list
+ * @param map The map to which the key-value pairs are added
+ *
+ * @return
+ */
+    public Map toMap(String str, String elementSeparator, Map map) {
+        return this.toMap(str, elementSeparator, null, map);
+    }
+
+    // toMap()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Adds all key-value pairs of the given string to the specified map. <br>
+ * The separator between the elements is assumed to be "," and "=" between
+ * key and value.
+ *
+ * <p>
+ * <b>Be aware that all leading and trailing whitespaces of keys and values
+ * will be removed!</b>
+ * </p>
+ *
+ * @param str The string that contains the list of key-value pairs
+ * @param map The map to which the key-value pairs are added
+ *
+ * @return
+ */
+    public Map toMap(String str, Map map) {
+        return this.toMap(str, null, null, map);
+    }
+
+    // toMap()
+    // -------------------------------------------------------------------------
+
+    /**
+     * Adds all key-value pairs of the given string to a new properties object. <br>
+ * The separator between the elements is assumed to be "," and "=" between
+ * key and value.
+ *
+ * <p>
+ * <b>Be aware that all leading and trailing whitespaces of keys and values
+ * will be removed!</b>
+ * </p>
+ *
+ * @param str The string that contains the list of key-value pairs
+ *
+ * @return
+ */
+    public Properties asProperties(String str) {
+        return this.toProperties(str, null);
+    }
+
+    // asProperties()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Adds all key-value pairs of the given string to the specified
+ * properties. <br>
+ * The separator between the elements is assumed to be "," and "=" between
+ * key and value.
+ *
+ * <p>
+ * <b>Be aware that all leading and trailing whitespaces of keys and values
+ * will be removed!</b>
+ * </p>
+ *
+ * @param str The string that contains the list of key-value pairs
+ * @param properties The properties where the key-value pairs should be
+ *        added
+ *
+ * @return
+ */
+    public Properties toProperties(String str, Properties properties) {
+        Properties props = (properties == null) ? new Properties() : properties;
+
+        return (Properties) this.toMap(str, null, null, props);
+    }
+
+    // toProperties()
+    // -------------------------------------------------------------------------
+    // =========================================================================
+    // PROTECTED INSTANCE METHODS
+    // =========================================================================
+
+    /**
+ * Cuts off all leading and trailing occurences of separator in text.
+ *
+ * @param text
+ * @param separator
+ *
+ * @return
+ */
+    protected String trimSeparator(String text, String separator) {
+        int sepLen = separator.length();
+
+        while (text.startsWith(separator)) {
+            text = text.substring(separator.length());
+        }
+
+        while (text.endsWith(separator)) {
+            text = text.substring(0, text.length() - sepLen);
+        }
+
+        return text;
+    }
+
+    // trimSeparator()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns an array of substrings of the given text.    <br>
+ * The separators between the substrings are the given delimiters. Each
+ * character in the delimiter string is treated as a separator.
+ *
+ * @param text The string that should be splitted into substrings
+ * @param delimiters All characters that should be recognized as a
+ *        separator or substrings
+ * @param all If true, empty elements will be returned, otherwise thye are
+ *        skipped
+ *
+ * @return An array of substrings of the given text
+ */
+    protected String[] parts(String text, String delimiters, boolean all) {
+        ArrayList result = null;
+        StringTokenizer tokenizer = null;
+
+        if (text == null) {
+            return null;
+        }
+
+        if ((delimiters == null) || (delimiters.length() == 0)) {
+            String[] resultArray = { text };
+
+            return resultArray;
+        }
+
+        if (text.length() == 0) {
+            return new String[0];
+        } else {
+            result = new ArrayList();
+            tokenizer = new StringTokenizer(text, delimiters, all);
+
+            if (all) {
+                this.collectParts(result, tokenizer, delimiters);
+            } else {
+                this.collectParts(result, tokenizer);
+            }
+        }
+
+        return (String[]) result.toArray(new String[0]);
+    }
+
+    // parts()
+    // -------------------------------------------------------------------------
+    protected void collectParts(List list, StringTokenizer tokenizer) {
+        while (tokenizer.hasMoreTokens()) {
+            list.add(tokenizer.nextToken());
+        }
+    }
+
+    // collectParts()
+    // -------------------------------------------------------------------------
+    protected void collectParts(List list, StringTokenizer tokenizer,
+        String delimiter) {
+        String token;
+        boolean lastWasDelimiter = false;
+
+        while (tokenizer.hasMoreTokens()) {
+            token = tokenizer.nextToken();
+
+            if (delimiter.indexOf(token) >= 0) {
+                if (lastWasDelimiter) {
+                    list.add("");
+                }
+
+                lastWasDelimiter = true;
+            } else {
+                list.add(token);
+                lastWasDelimiter = false;
+            }
+        }
+    }
+
+    // collectParts()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the given text split up into an array of strings, at the
+ * occurrances of the separator string.  In contrary to method parts() the
+ * separator is a one or many character sequence delimiter. That is, only
+ * the exact sequence  of the characters in separator identifies the end
+ * of a substring. Parameter all defines whether empty strings between
+ * consecutive separators are added to the result or not.
+ *
+ * @param text The text to be split up
+ * @param separator The string that separates the substrings
+ * @param all If true, empty strings are added, otherwise skipped
+ *
+ * @return An array of substrings not containing any separator anymore
+ *
+ * @see #parts(String, String, boolean)
+ */
+    protected String[] substrings(String text, String separator, boolean all) {
+        int index = 0;
+        int start = 0;
+        int sepLen = 0;
+        int strLen = 0;
+        String str = text;
+        ArrayList strings = new ArrayList();
+
+        if (text == null) {
+            return new String[0];
+        }
+
+        if ((separator == null) || (separator.length() == 0)) {
+            if (text.length() == 0) {
+                return new String[0];
+            }
+
+            String[] resultArray = { text };
+
+            return resultArray;
+        }
+
+        if (!all) {
+            str = this.trimSeparator(text, separator);
+        }
+
+        strLen = str.length();
+
+        if (strLen > 0) {
+            sepLen = separator.length();
+            index = str.indexOf(separator, start);
+
+            while (index >= 0) {
+                if (all) {
+                    if (index > 0) {
+                        strings.add(str.substring(start, index));
+                    }
+                } else {
+                    if (index > (start + sepLen)) {
+                        strings.add(str.substring(start, index));
+                    }
+                }
+
+                start = index + sepLen;
+                index = str.indexOf(separator, start);
+            }
+
+            if (start < strLen) {
+                strings.add(str.substring(start));
+            }
+        }
+
+        return (String[]) strings.toArray(new String[0]);
+    }
+
+    // substrings()
+    // -------------------------------------------------------------------------
+    protected String padCh(String str, int len, char ch, boolean left) {
+        StringBuffer buffer = null;
+        int missing = len - str.length();
+
+        if (missing <= 0) {
+            return str;
+        }
+
+        buffer = new StringBuffer(len);
+
+        if (!left) {
+            buffer.append(str);
+        }
+
+        for (int i = 1; i <= missing; i++) {
+            buffer.append(ch);
+        }
+
+        if (left) {
+            buffer.append(str);
+        }
+
+        return buffer.toString();
+    }
+
+    // padCh()
+    // -------------------------------------------------------------------------
+    protected int indexOfString(String[] strArray, String searchStr,
+        boolean ignoreCase) {
+        if ((strArray == null) || (strArray.length == 0)) {
+            return -1;
+        }
+
+        boolean found = false;
+
+        for (int i = 0; i < strArray.length; i++) {
+            if (strArray[i] == null) {
+                if (searchStr == null) {
+                    found = true;
+                }
+            } else {
+                if (ignoreCase) {
+                    found = strArray[i].equalsIgnoreCase(searchStr);
+                } else {
+                    found = strArray[i].equals(searchStr);
+                }
+            }
+
+            if (found) {
+                return i;
+            }
+        }
+
+        return -1;
+    }
+
+    // indexOfString()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the substring of the given string that comes before the first
+ * occurance of the specified separator. If the string starts with a
+ * separator, the result will be an empty string. If the string doesn't
+ * contain the separator the method returns null or the whole string,
+ * depending on the returnNull flag.
+ *
+ * @param str The string of which the prefix is desired
+ * @param separator Separates the prefix from the rest of the string
+ * @param returnNull Specifies if null will be returned if no separator is
+ *        found
+ *
+ * @return
+ */
+    protected String prefix(String str, String separator, boolean returnNull) {
+        if (str == null) {
+            return null;
+        }
+
+        if (separator == null) {
+            return (returnNull ? null : str);
+        }
+
+        int index = str.indexOf(separator);
+
+        if (index >= 0) {
+            return str.substring(0, index);
+        } else {
+            return (returnNull ? null : str);
+        }
+    }
+
+    // prefix()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Returns the substring of the given string that comes after the first
+ * occurance of the specified separator. If the string ends with a
+ * separator, the result will be an empty string. If the string doesn't
+ * contain the separator the method returns null or the whole string,
+ * depending on the returnNull flag.
+ *
+ * @param str The string of which the suffix is desired
+ * @param separator Separates the suffix from the rest of the string
+ * @param returnNull Specifies if null will be returned if no separator is
+ *        found
+ *
+ * @return
+ */
+    protected String suffix(String str, String separator, boolean returnNull) {
+        if (str == null) {
+            return null;
+        }
+
+        if (separator == null) {
+            return (returnNull ? null : str);
+        }
+
+        int index = str.indexOf(separator);
+
+        if (index >= 0) {
+            return str.substring(index + separator.length());
+        } else {
+            return (returnNull ? null : str);
+        }
+    }
+
+    // suffix()
+    // -------------------------------------------------------------------------
+
+    /**
+ * Removes the given strings from the array. If removeStrings is null it
+ * means that all null values are removed from the first array.
+ *
+ * @param strings
+ * @param removeStrings
+ *
+ * @return
+ */
+    protected String[] removeFromStringArray(String[] strings,
+        String[] removeStrings) {
+        List list;
+        boolean remains;
+        list = new ArrayList(strings.length);
+
+        for (int i = 0; i < strings.length; i++) {
+            if (removeStrings == null) {
+                remains = strings[i] != null;
+            } else {
+                remains = !this.contains(removeStrings, strings[i]);
+            }
+
+            if (remains) {
+                list.add(strings[i]);
+            }
+        }
+
+        return (String[]) list.toArray(new String[list.size()]);
+    }
+
+    // removeFromStringArray()
+    // -------------------------------------------------------------------------
+}
+
+
+// class StringUtil
diff --git a/src/com/sshtools/daemon/vfs/VFSMount.java b/src/com/sshtools/daemon/vfs/VFSMount.java
new file mode 100644
index 0000000000000000000000000000000000000000..ffcad6bf9c8444acf934d34df8084e0fc1659e80
--- /dev/null
+++ b/src/com/sshtools/daemon/vfs/VFSMount.java
@@ -0,0 +1,137 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.vfs;
+
+import com.sshtools.j2ssh.configuration.*;
+
+import java.io.*;
+
+import java.util.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class VFSMount {
+    private String mount;
+    private String path;
+    private HashMap acl = new HashMap();
+    private boolean isroot = false;
+
+    /**
+ * Creates a new VFSMount object.
+ *
+ * @param mount
+ * @param path
+ *
+ * @throws IOException
+ */
+    public VFSMount(String mount, String path) throws IOException {
+        path.replace('\\', '/');
+
+        // Replace any tokens
+        int index = path.indexOf("%HOME%");
+
+        if (index >= 0) {
+            path = ((index > 0) ? path.substring(0, index) : "") +
+                ConfigurationLoader.getHomeDirectory() +
+                (((index + 6) < (path.length() - 1))
+                ? path.substring(index +
+                    ((path.charAt(index + 6) == '/') ? 7 : 6)) : "");
+        }
+
+        File f = new File(path);
+
+        if (!f.exists()) {
+            f.mkdirs();
+        }
+
+        if (!mount.trim().startsWith("/")) {
+            this.mount = "/" + mount.trim();
+        } else {
+            this.mount = mount.trim();
+        }
+
+        this.path = f.getCanonicalPath();
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public boolean isRoot() {
+        return isroot;
+    }
+
+    /**
+ *
+ *
+ * @param isroot
+ */
+    public void setRoot(boolean isroot) {
+        this.isroot = isroot;
+    }
+
+    /**
+ *
+ *
+ * @param permissions
+ */
+    public void setPermissions(VFSPermission permissions) {
+        acl.put(permissions.getName(), permissions);
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getPath() {
+        return path.replace('\\', '/');
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getMount() {
+        return mount;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public Map getPermissions() {
+        return acl;
+    }
+}
diff --git a/src/com/sshtools/daemon/vfs/VFSPermission.java b/src/com/sshtools/daemon/vfs/VFSPermission.java
new file mode 100644
index 0000000000000000000000000000000000000000..04a99c57c4e63efa7c8af866bbf0dfa28c911f48
--- /dev/null
+++ b/src/com/sshtools/daemon/vfs/VFSPermission.java
@@ -0,0 +1,162 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.vfs;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class VFSPermission {
+    private boolean canRead;
+    private boolean canWrite;
+    private boolean canExecute;
+    private String name;
+
+    /**
+ * Creates a new VFSPermission object.
+ *
+ * @param name
+ * @param permissions
+ */
+    public VFSPermission(String name, String permissions) {
+        this.name = name;
+        setPermissions(permissions);
+    }
+
+    /**
+ * Creates a new VFSPermission object.
+ *
+ * @param name
+ */
+    public VFSPermission(String name) {
+        this.name = name;
+        setPermissions("rwx");
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getName() {
+        return name;
+    }
+
+    /**
+ *
+ *
+ * @param permissions
+ */
+    public void setPermissions(String permissions) {
+        canRead = false;
+        canWrite = false;
+        canExecute = false;
+
+        for (int i = 0; i < permissions.length(); i++) {
+            switch (permissions.charAt(i)) {
+            case 'r': {
+                canRead = true;
+
+                break;
+            }
+
+            case 'w': {
+                canWrite = true;
+
+                break;
+            }
+
+            case 'x': {
+                canExecute = true;
+
+                break;
+            }
+            }
+        }
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public String getPermissions() {
+        return (canRead ? "r" : "") + (canWrite ? "w" : "") +
+        (canExecute ? "x" : "");
+    }
+
+    /**
+ *
+ *
+ * @param permissions
+ *
+ * @return
+ */
+    public boolean verifyPermissions(String permissions) {
+        String tmp = getPermissions();
+        String ch;
+
+        for (int i = 0; i < permissions.length(); i++) {
+            ch = permissions.substring(i, 1);
+
+            if (tmp.indexOf(ch) == -1) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public boolean canRead() {
+        return canRead;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public boolean canWrite() {
+        return canWrite;
+    }
+
+    /**
+ *
+ *
+ * @return
+ */
+    public boolean canExecute() {
+        return canExecute;
+    }
+}
diff --git a/src/com/sshtools/daemon/vfs/VFSPermissionHandler.java b/src/com/sshtools/daemon/vfs/VFSPermissionHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..be5e4f0ce51f838282644fcbab6fab1e01966226
--- /dev/null
+++ b/src/com/sshtools/daemon/vfs/VFSPermissionHandler.java
@@ -0,0 +1,48 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.vfs;
+
+import com.sshtools.daemon.platform.*;
+
+/**
+ * <p>Title: </p>
+ * <p>Description: </p>
+ * <p>Copyright: Copyright (c) 2003</p>
+ * <p>Company: </p>
+ * @author Lee David Painter
+ * @version $Id: VFSPermissionHandler.java,v 1.6 2003/09/11 15:37:07 martianx Exp $
+ */
+import java.io.*;
+
+
+public interface VFSPermissionHandler {
+    public void verifyPermissions(String username, String path,
+        String permissions)
+        throws PermissionDeniedException, FileNotFoundException, IOException;
+
+    public String getVFSHomeDirectory(String username)
+        throws FileNotFoundException;
+}
diff --git a/src/com/sshtools/daemon/vfs/VirtualFileSystem.java b/src/com/sshtools/daemon/vfs/VirtualFileSystem.java
new file mode 100644
index 0000000000000000000000000000000000000000..a152e7871002624d52a6f4763d5a6aa5eaa00fcc
--- /dev/null
+++ b/src/com/sshtools/daemon/vfs/VirtualFileSystem.java
@@ -0,0 +1,1168 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.daemon.vfs;
+
+import com.sshtools.daemon.configuration.PlatformConfiguration;
+import com.sshtools.daemon.platform.InvalidHandleException;
+import com.sshtools.daemon.platform.NativeAuthenticationProvider;
+import com.sshtools.daemon.platform.NativeFileSystemProvider;
+import com.sshtools.daemon.platform.PermissionDeniedException;
+import com.sshtools.daemon.platform.UnsupportedFileOperationException;
+
+import com.sshtools.j2ssh.SshThread;
+import com.sshtools.j2ssh.configuration.ConfigurationException;
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.io.UnsignedInteger64;
+import com.sshtools.j2ssh.sftp.FileAttributes;
+import com.sshtools.j2ssh.sftp.SftpFile;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.32 $
+ */
+public class VirtualFileSystem extends NativeFileSystemProvider {
+    private static String USER_HOME = "/home/";
+    private static Map vfsmounts;
+    private static VFSMount vfsroot;
+    private static Log log = LogFactory.getLog(VirtualFileSystem.class);
+    private static VFSPermissionHandler permissionHandler = null;
+
+    static {
+        try {
+            vfsmounts = ((PlatformConfiguration) ConfigurationLoader.getConfiguration(PlatformConfiguration.class)).getVFSMounts();
+            vfsroot = ((PlatformConfiguration) ConfigurationLoader.getConfiguration(PlatformConfiguration.class)).getVFSRoot();
+        } catch (ConfigurationException ex) {
+            log.error("Failed to initialize the Virtual File System", ex);
+        }
+    }
+
+    private Map openFiles = new HashMap();
+
+    /**
+ * Creates a new VirtualFileSystem object.
+ *
+ * @throws IOException
+ */
+    public VirtualFileSystem() throws IOException {
+        if (!ConfigurationLoader.isConfigurationAvailable(
+                    PlatformConfiguration.class)) {
+            throw new IOException("No valid platform configuration available");
+        }
+    }
+
+    public static void setPermissionHandler(
+        VFSPermissionHandler permissionHandler) {
+        VirtualFileSystem.permissionHandler = permissionHandler;
+    }
+
+    private static String getVFSHomeDirectory(String username)
+        throws FileNotFoundException {
+        if (permissionHandler != null) {
+            return permissionHandler.getVFSHomeDirectory(username);
+        } else {
+            return USER_HOME + username;
+        }
+    }
+
+    private static String getNFSHomeDirectory() throws FileNotFoundException {
+        try {
+            if (Thread.currentThread() instanceof SshThread &&
+                    SshThread.hasUserContext()) {
+                NativeAuthenticationProvider nap = NativeAuthenticationProvider.getInstance();
+
+                return nap.getHomeDirectory(SshThread.getCurrentThreadUser());
+            } else {
+                throw new FileNotFoundException("There is no user logged in");
+            }
+        } catch (IOException e) {
+            throw new FileNotFoundException(e.getMessage());
+        }
+    }
+
+    /**
+ *
+ *
+ * @param str
+ * @param with
+ *
+ * @return
+ */
+    public static boolean startsWithIgnoreCase(String str, String with) {
+        return str.substring(0,
+            (with.length() > str.length()) ? str.length() : with.length())
+                  .equalsIgnoreCase(with);
+    }
+
+    /**
+ *
+ *
+ * @param nfspath
+ *
+ * @return
+ *
+ * @throws FileNotFoundException
+ */
+    public static String translateNFSPath(String nfspath)
+        throws FileNotFoundException {
+        nfspath = nfspath.replace('\\', '/');
+
+        // ./ means home
+        if (nfspath.startsWith("./")) {
+            nfspath = nfspath.substring(2);
+        }
+
+        //if (startsWithIgnoreCase(nfspath, nfshome)) {
+        try {
+            String nfshome = getNFSHomeDirectory().replace('\\', '/');
+            nfshome = translateCanonicalPath(nfshome, nfshome);
+
+            String vfshome = getVFSHomeDirectory(SshThread.getCurrentThreadUser());
+
+            // First check for the userhome
+            log.debug("NFSPath=" + nfspath);
+            log.debug("NFSHome=" + nfshome);
+            nfspath = translateCanonicalPath(nfspath, nfshome);
+
+            int idx = nfspath.indexOf(nfshome);
+
+            return vfshome + nfspath.substring(nfshome.length());
+
+            //            StringBuffer buf = new StringBuffer(nfspath);
+            //            buf = buf.replace(idx, idx + nfshome.length(), vfshome);
+            //            return buf.toString(); /*nfspath.replaceFirst(nfshome, vfshome);*/
+            //}
+        } catch (FileNotFoundException ex) { /* Ignore as we will try other mounts */
+        }
+
+        // Now lets translate from the available mounts
+        Iterator it = vfsmounts.entrySet().iterator();
+        Map.Entry entry;
+        String mount;
+        String path;
+        VFSMount m;
+
+        while (it.hasNext()) {
+            entry = (Map.Entry) it.next();
+            mount = (String) entry.getKey();
+            m = (VFSMount) entry.getValue();
+            path = m.getPath();
+            log.debug(m.getMount() + "=" + m.getPath());
+
+            // if (startsWithIgnoreCase(nfspath, path)) {
+            try {
+                nfspath = translateCanonicalPath(nfspath, path);
+
+                int idx = nfspath.indexOf(path);
+                StringBuffer buf = new StringBuffer(nfspath);
+                buf = buf.replace(idx, idx + path.length(), mount);
+
+                return buf.toString();
+            } catch (FileNotFoundException ex) {
+                /* Ingore as we will try other mounts */
+            }
+
+            // }
+        }
+
+        // if (startsWithIgnoreCase(nfspath, vfsroot.getPath())) {
+        log.debug("VFSRoot=" + vfsroot.getPath());
+        nfspath = translateCanonicalPath(nfspath, vfsroot.getPath());
+        path = nfspath.substring(vfsroot.getPath().length()); //replaceFirst(vfsroot.getPath(), "");
+
+        return (path.startsWith("/") ? path : ("/" + path));
+
+        //  } else {
+        //      throw new FileNotFoundException(nfspath + " could not be found");
+        //  }
+    }
+
+    private static VFSMount getMount(String vfspath)
+        throws FileNotFoundException, IOException {
+        String vfshome = getVFSHomeDirectory(SshThread.getCurrentThreadUser());
+        VFSMount m;
+
+        if (vfspath.startsWith("/")) {
+            if (vfspath.startsWith(vfshome)) {
+                m = new VFSMount(vfshome, getNFSHomeDirectory());
+                m.setPermissions(new VFSPermission(
+                        SshThread.getCurrentThreadUser(), "rwx"));
+
+                return m;
+            } else {
+                Iterator it = vfsmounts.entrySet().iterator();
+                Map.Entry entry;
+                String mount;
+
+                while (it.hasNext()) {
+                    entry = (Map.Entry) it.next();
+                    mount = (String) entry.getKey();
+
+                    if (vfspath.startsWith(mount)) {
+                        return (VFSMount) entry.getValue();
+                    }
+                }
+
+                if (vfsroot != null) {
+                    return vfsroot;
+                } else {
+                    throw new FileNotFoundException("The path was not found");
+                }
+            }
+        } else {
+            m = new VFSMount(vfshome, getNFSHomeDirectory());
+            m.setPermissions(new VFSPermission(vfshome.substring(
+                        vfshome.lastIndexOf("/")), "rwx"));
+
+            return m;
+        }
+    }
+
+    /**
+ *
+ *
+ * @param vfspath
+ *
+ * @return
+ *
+ * @throws FileNotFoundException
+ */
+    public static String translateVFSPath(String vfspath)
+        throws FileNotFoundException {
+        return translateVFSPath(vfspath, null);
+    }
+
+    public static String translateVFSPath(String vfspath, String vfscwd)
+        throws FileNotFoundException {
+        // Translate any backslashes for sanity
+        vfspath = vfspath.replace('\\', '/').trim();
+
+        try {
+            if (!vfspath.startsWith("/")) {
+                // Work out the path using the current directory
+                String path = (((vfscwd == null) || vfscwd.trim().equals(""))
+                    ? getVFSHomeDirectory(SshThread.getCurrentThreadUser())
+                    : vfscwd);
+                vfspath = path + (path.endsWith("/") ? "" : "/") + vfspath;
+            }
+
+            String nfshome = getNFSHomeDirectory().replace('\\', '/');
+            String vfshome = getVFSHomeDirectory(SshThread.getCurrentThreadUser());
+
+            if (vfspath.startsWith(vfshome)) {
+                // Return the canonicalized system dependent path
+                if (vfspath.length() > vfshome.length()) {
+                    return translateCanonicalPath(nfshome +
+                        (nfshome.endsWith("/") ? "" : "/") +
+                        vfspath.substring(vfshome.length() + 1), nfshome);
+                } else {
+                    return translateCanonicalPath(nfshome, nfshome);
+                }
+            }
+        } catch (FileNotFoundException ex) {
+            // Ignore since we dont always need to be running as a user
+        }
+
+        // The path does not refer to the absolute USER_HOME
+        // so we will look up using the platform.xml VFS mounts
+        Iterator it = vfsmounts.entrySet().iterator();
+        Map.Entry entry;
+        String mount;
+        String path;
+        VFSMount m;
+
+        while (it.hasNext()) {
+            entry = (Map.Entry) it.next();
+            mount = (String) entry.getKey();
+            m = (VFSMount) entry.getValue();
+            path = m.getPath();
+
+            if (vfspath.startsWith(mount)) {
+                // Lets translate the path, making sure we do not move outside
+                // vfs with ..
+                String str = path + vfspath.substring(mount.length());
+
+                // vfspath.replaceFirst(mount,
+                //        path)
+                return translateCanonicalPath(str, path);
+            }
+        }
+
+        // If we reached here then the VFS path did not refer to an optional mount
+        // or the users home directory, so lets attempt to use the VFS root is there
+        // is one defined
+        if (vfsroot != null) {
+            path = vfsroot.getPath() +
+                (vfsroot.getPath().endsWith("/") ? vfspath.substring(1) : vfspath);
+
+            return translateCanonicalPath(path, vfsroot.getPath());
+        } else {
+            throw new FileNotFoundException("The file could not be found");
+        }
+    }
+
+    /*else {
+  try {
+    String nfshome = (nfscwd == null || nfscwd.trim().equals("") ? getNFSHomeDirectory() : nfscwd);
+    String path = nfshome + (nfshome.endsWith("/") ? "" : "/") + vfspath;
+    return translateCanonicalPath(path, nfshome);
+  }
+  catch (FileNotFoundException ex1) {
+    throw new FileNotFoundException(
+     "Only fully qualified VFS paths can be translated outside of a user context");
+  }
+  /*  String path = nfshome + (nfshome.endsWith("/") ? "" : "/")
+         + vfspath;
+     return translateCanonicalPath(path, nfshome);*/
+
+    //}
+
+    /**
+ *
+ *
+ * @param path
+ * @param securemount
+ *
+ * @return
+ *
+ * @throws FileNotFoundException
+ */
+    public static String translateCanonicalPath(String path, String securemount)
+        throws FileNotFoundException {
+        try {
+            log.debug("Translating for canonical path " + path +
+                " against secure mount " + securemount);
+
+            File f = new File(path);
+            String canonical = f.getCanonicalPath().replace('\\', '/');
+            File f2 = new File(securemount);
+            String canonical2 = f2.getCanonicalPath().replace('\\', '/');
+
+            // Verify that the canonical path does not exit out of the mount
+            if (canonical.startsWith(canonical2)) {
+                return canonical;
+            } else {
+                throw new FileNotFoundException(path + " could not be found");
+            }
+        } catch (IOException ex) {
+            throw new FileNotFoundException(path + " could not be found");
+        }
+    }
+
+    /**
+ *
+ *
+ * @param path
+ *
+ * @return
+ *
+ * @throws PermissionDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+    public boolean makeDirectory(String path)
+        throws PermissionDeniedException, FileNotFoundException, IOException {
+        //    String realPath = path;
+        path = VirtualFileSystem.translateVFSPath(path);
+
+        File f = new File(path);
+        verifyPermissions(SshThread.getCurrentThreadUser(), path, "rw");
+        log.debug("Creating directory " + f.getAbsolutePath());
+
+        return f.mkdir();
+    }
+
+    /**
+ *
+ *
+ * @param path
+ *
+ * @return
+ *
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+    public VFSPermission getVFSPermission(String path)
+        throws FileNotFoundException, IOException {
+        VFSMount mount = getMount(translateNFSPath(path));
+
+        if (mount.getPermissions().containsKey(SshThread.getCurrentThreadUser())) {
+            return (VFSPermission) mount.getPermissions().get(SshThread.getCurrentThreadUser());
+        } else {
+            return (VFSPermission) mount.getPermissions().get("default");
+        }
+    }
+
+    /**
+ *
+ *
+ * @param handle
+ *
+ * @return
+ *
+ * @throws IOException
+ * @throws InvalidHandleException
+ */
+    public FileAttributes getFileAttributes(byte[] handle)
+        throws IOException, InvalidHandleException {
+        String shandle = new String(handle);
+
+        if (openFiles.containsKey(shandle)) {
+            Object obj = openFiles.get(shandle);
+            File f;
+
+            if (obj instanceof OpenFile) {
+                f = ((OpenFile) obj).getFile();
+            } else if (obj instanceof OpenDirectory) {
+                f = ((OpenDirectory) obj).getFile();
+            } else {
+                throw new IOException("Unexpected open file handle");
+            }
+
+            VFSPermission permissions = getVFSPermission(f.getAbsolutePath());
+
+            if (permissions == null) {
+                throw new IOException("No default permissions set");
+            }
+
+            FileAttributes attrs = new FileAttributes();
+            attrs.setSize(new UnsignedInteger64(String.valueOf(f.length())));
+            attrs.setTimes(new UnsignedInteger32(f.lastModified() / 1000),
+                new UnsignedInteger32(f.lastModified() / 1000));
+
+            boolean canExec = true;
+
+            try {
+                if (System.getSecurityManager() != null) {
+                    System.getSecurityManager().checkExec(f.getCanonicalPath());
+                }
+            } catch (SecurityException ex1) {
+                canExec = false;
+            }
+
+            attrs.setPermissions((((f.canRead() && permissions.canRead()) ? "r"
+                                                                          : "-") +
+                ((f.canWrite() && permissions.canWrite()) ? "w" : "-") +
+                ((canExec && permissions.canExecute()) ? "x" : "-")));
+            attrs.setPermissions(new UnsignedInteger32(attrs.getPermissions()
+                                                            .longValue() |
+                    (f.isDirectory() ? FileAttributes.S_IFDIR
+                                     : FileAttributes.S_IFREG)));
+
+            return attrs;
+        } else {
+            throw new InvalidHandleException("The handle is invalid");
+        }
+    }
+
+    /**
+ *
+ *
+ * @param path
+ *
+ * @return
+ *
+ * @throws IOException
+ * @throws FileNotFoundException
+ */
+    public FileAttributes getFileAttributes(String path)
+        throws IOException, FileNotFoundException {
+        log.debug("Getting file attributes for " + path);
+        path = translateVFSPath(path);
+
+        // Look up the VFS mount attributes
+        File f = new File(path);
+        path = f.getCanonicalPath();
+
+        if (!f.exists()) {
+            throw new FileNotFoundException(path + " doesn't exist");
+        }
+
+        VFSPermission permissions = getVFSPermission(path);
+
+        if (permissions == null) {
+            throw new IOException("No default permissions set");
+        }
+
+        FileAttributes attrs = new FileAttributes();
+        attrs.setSize(new UnsignedInteger64(String.valueOf(f.length())));
+        attrs.setTimes(new UnsignedInteger32(f.lastModified() / 1000),
+            new UnsignedInteger32(f.lastModified() / 1000));
+
+        boolean canExec = true;
+
+        try {
+            if (System.getSecurityManager() != null) {
+                System.getSecurityManager().checkExec(f.getCanonicalPath());
+            }
+        } catch (SecurityException ex1) {
+            canExec = false;
+        }
+
+        attrs.setPermissions((((f.canRead() && permissions.canRead()) ? "r" : "-") +
+            ((f.canWrite() && permissions.canWrite()) ? "w" : "-") +
+            ((canExec && permissions.canExecute()) ? "x" : "-")));
+        attrs.setPermissions(new UnsignedInteger32(attrs.getPermissions()
+                                                        .longValue() |
+                (f.isDirectory() ? FileAttributes.S_IFDIR : FileAttributes.S_IFREG)));
+
+        return attrs;
+    }
+
+    /**
+ *
+ *
+ * @param path
+ *
+ * @return
+ *
+ * @throws PermissionDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+    public byte[] openDirectory(String path)
+        throws PermissionDeniedException, FileNotFoundException, IOException {
+        String realPath = path;
+        path = VirtualFileSystem.translateVFSPath(path);
+
+        File f = new File(path);
+        verifyPermissions(SshThread.getCurrentThreadUser(), path, "r");
+
+        if (f.exists()) {
+            if (f.isDirectory()) {
+                openFiles.put(f.toString(), new OpenDirectory(realPath, path, f));
+
+                return f.toString().getBytes("US-ASCII");
+            } else {
+                throw new IOException(translateNFSPath(path) +
+                    " is not a directory");
+            }
+        } else {
+            throw new FileNotFoundException(translateNFSPath(path) +
+                " does not exist");
+        }
+    }
+
+    /**
+ *
+ *
+ * @param handle
+ *
+ * @return
+ *
+ * @throws InvalidHandleException
+ * @throws EOFException
+ * @throws IOException
+ */
+    public SftpFile[] readDirectory(byte[] handle)
+        throws InvalidHandleException, EOFException, IOException {
+        String shandle = new String(handle);
+
+        if (openFiles.containsKey(shandle)) {
+            Object obj = openFiles.get(shandle);
+
+            if (obj instanceof OpenDirectory) {
+                OpenDirectory dir = (OpenDirectory) obj;
+                int pos = dir.getPosition();
+                File[] children = dir.getChildren();
+
+                if (children == null) {
+                    throw new IOException("Permission denined.");
+                }
+
+                int count = ((children.length - pos) < 100)
+                    ? (children.length - pos) : 100;
+
+                if (count > 0) {
+                    SftpFile[] files = new SftpFile[count];
+
+                    for (int i = 0; i < files.length; i++) {
+                        File f = children[pos + i];
+                        String absolutePath = dir.realPath + "/" + f.getName();
+                        SftpFile sftpfile = new SftpFile(f.getName(),
+                                getFileAttributes(absolutePath));
+                        files[i] = sftpfile;
+                    }
+
+                    dir.readpos = pos + files.length;
+
+                    return files;
+                } else {
+                    throw new EOFException("There are no more files");
+                }
+            } else {
+                throw new InvalidHandleException(
+                    "Handle is not an open directory");
+            }
+        } else {
+            throw new InvalidHandleException("The handle is invalid");
+        }
+    }
+
+    /**
+ *
+ *
+ * @param path
+ * @param flags
+ * @param attrs
+ *
+ * @return
+ *
+ * @throws PermissionDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+    public byte[] openFile(String path, UnsignedInteger32 flags,
+        FileAttributes attrs)
+        throws PermissionDeniedException, FileNotFoundException, IOException {
+        path = VirtualFileSystem.translateVFSPath(path);
+
+        File f = new File(path);
+        verifyPermissions(SshThread.getCurrentThreadUser(), path, "r");
+
+        // Check if the file does not exist and process according to flags
+        if (!f.exists()) {
+            if ((flags.intValue() & NativeFileSystemProvider.OPEN_CREATE) == NativeFileSystemProvider.OPEN_CREATE) {
+                // The file does not exist and the create flag is present so lets create it
+                if (!f.createNewFile()) {
+                    throw new IOException(translateNFSPath(path) +
+                        " could not be created");
+                }
+            } else {
+                // The file does not exist and no create flag present
+                throw new FileNotFoundException(translateNFSPath(path) +
+                    " does not exist");
+            }
+        } else {
+            if (((flags.intValue() & NativeFileSystemProvider.OPEN_CREATE) == NativeFileSystemProvider.OPEN_CREATE) &&
+                    ((flags.intValue() &
+                    NativeFileSystemProvider.OPEN_EXCLUSIVE) == NativeFileSystemProvider.OPEN_EXCLUSIVE)) {
+                // The file exists but the EXCL flag is set which requires that the
+                // file should not exist prior to creation, so throw a status exception
+                throw new IOException(translateNFSPath(path) +
+                    " already exists");
+            }
+        }
+
+        // The file now exists so open the file according to the flags yb building the relevant
+        // flags for the RandomAccessFile class
+        String mode = "r" +
+            (((flags.intValue() & NativeFileSystemProvider.OPEN_WRITE) == NativeFileSystemProvider.OPEN_WRITE)
+            ? "ws" : "");
+        RandomAccessFile raf = new RandomAccessFile(f, mode);
+
+        // Determine whether we need to truncate the file
+        if (((flags.intValue() & NativeFileSystemProvider.OPEN_CREATE) == NativeFileSystemProvider.OPEN_CREATE) &&
+                ((flags.intValue() & NativeFileSystemProvider.OPEN_TRUNCATE) == NativeFileSystemProvider.OPEN_TRUNCATE)) {
+            // Set the length to zero
+            raf.setLength(0);
+        }
+
+        // Record the open file
+        openFiles.put(raf.toString(), new OpenFile(f, raf, flags));
+
+        // Return the handle
+        return raf.toString().getBytes("US-ASCII");
+    }
+
+    /**
+ *
+ *
+ * @param handle
+ * @param offset
+ * @param len
+ *
+ * @return
+ *
+ * @throws InvalidHandleException
+ * @throws EOFException
+ * @throws IOException
+ */
+    public byte[] readFile(byte[] handle, UnsignedInteger64 offset,
+        UnsignedInteger32 len)
+        throws InvalidHandleException, EOFException, IOException {
+        String shandle = new String(handle);
+
+        if (openFiles.containsKey(shandle)) {
+            Object obj = openFiles.get(shandle);
+
+            if (obj instanceof OpenFile) {
+                OpenFile file = (OpenFile) obj;
+
+                if ((file.getFlags().intValue() &
+                        NativeFileSystemProvider.OPEN_READ) == NativeFileSystemProvider.OPEN_READ) {
+                    byte[] buf = new byte[len.intValue()];
+
+                    if (file.getRandomAccessFile().getFilePointer() != offset.longValue()) {
+                        file.getRandomAccessFile().seek(offset.longValue());
+                    }
+
+                    int read = file.getRandomAccessFile().read(buf);
+
+                    if (read >= 0) {
+                        if (read == buf.length) {
+                            return buf;
+                        } else {
+                            byte[] tmp = new byte[read];
+                            System.arraycopy(buf, 0, tmp, 0, read);
+
+                            return tmp;
+                        }
+                    } else {
+                        throw new EOFException("The file is EOF");
+                    }
+                } else {
+                    throw new InvalidHandleException(
+                        "The file handle was not opened for reading");
+                }
+            } else {
+                throw new InvalidHandleException("Handle is not an open file");
+            }
+        } else {
+            throw new InvalidHandleException("The handle is invalid");
+        }
+    }
+
+    /**
+ *
+ *
+ * @param handle
+ * @param offset
+ * @param data
+ * @param off
+ * @param len
+ *
+ * @throws InvalidHandleException
+ * @throws IOException
+ */
+    public void writeFile(byte[] handle, UnsignedInteger64 offset, byte[] data,
+        int off, int len) throws InvalidHandleException, IOException {
+        String shandle = new String(handle);
+
+        if (openFiles.containsKey(shandle)) {
+            Object obj = openFiles.get(shandle);
+
+            if (obj instanceof OpenFile) {
+                OpenFile file = (OpenFile) obj;
+
+                if ((file.getFlags().intValue() &
+                        NativeFileSystemProvider.OPEN_WRITE) == NativeFileSystemProvider.OPEN_WRITE) {
+                    if ((file.getFlags().intValue() &
+                            NativeFileSystemProvider.OPEN_APPEND) == NativeFileSystemProvider.OPEN_APPEND) {
+                        // Force the data to be written to the end of the file by seeking to the end
+                        file.getRandomAccessFile().seek(file.getRandomAccessFile()
+                                                            .length());
+                    } else if (file.getRandomAccessFile().getFilePointer() != offset.longValue()) {
+                        // Move the file pointer if its not in the write place
+                        file.getRandomAccessFile().seek(offset.longValue());
+                    }
+
+                    file.getRandomAccessFile().write(data, off, len);
+                } else {
+                    throw new InvalidHandleException(
+                        "The file was not opened for writing");
+                }
+            } else {
+                throw new InvalidHandleException("Handle is not an open file");
+            }
+        } else {
+            throw new InvalidHandleException("The handle is invalid");
+        }
+    }
+
+    /**
+ *
+ *
+ * @param handle
+ *
+ * @throws InvalidHandleException
+ * @throws IOException
+ */
+    public void closeFile(byte[] handle)
+        throws InvalidHandleException, IOException {
+        String shandle = new String(handle);
+
+        if (openFiles.containsKey(shandle)) {
+            Object obj = openFiles.get(shandle);
+
+            if (obj instanceof OpenDirectory) {
+                openFiles.remove(shandle);
+            } else if (obj instanceof OpenFile) {
+                ((OpenFile) obj).getRandomAccessFile().close();
+                openFiles.remove(shandle);
+            } else {
+                throw new InvalidHandleException("Internal server error");
+            }
+        } else {
+            throw new InvalidHandleException("The handle is invalid");
+        }
+    }
+
+    /**
+ *
+ *
+ * @param path
+ *
+ * @throws PermissionDeniedException
+ * @throws IOException
+ * @throws FileNotFoundException
+ */
+    public void removeFile(String path)
+        throws PermissionDeniedException, IOException, FileNotFoundException {
+        path = VirtualFileSystem.translateVFSPath(path);
+
+        File f = new File(path);
+
+        if (f.exists()) {
+            try {
+                if (f.isFile()) {
+                    if (!f.delete()) {
+                        throw new IOException("Failed to delete " +
+                            translateNFSPath(path));
+                    }
+                } else {
+                    throw new IOException(translateNFSPath(path) +
+                        " is a directory, use remove directory command to remove");
+                }
+            } catch (SecurityException se) {
+                throw new PermissionDeniedException("Permission denied");
+            }
+        } else {
+            throw new FileNotFoundException(translateNFSPath(path) +
+                " does not exist");
+        }
+    }
+
+    /**
+ *
+ *
+ * @param oldpath
+ * @param newpath
+ *
+ * @throws PermissionDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+    public void renameFile(String oldpath, String newpath)
+        throws PermissionDeniedException, FileNotFoundException, IOException {
+        oldpath = VirtualFileSystem.translateVFSPath(oldpath);
+        newpath = VirtualFileSystem.translateVFSPath(newpath);
+
+        File f = new File(oldpath);
+        verifyPermissions(SshThread.getCurrentThreadUser(), oldpath, "rw");
+        verifyPermissions(SshThread.getCurrentThreadUser(), newpath, "rw");
+
+        if (f.exists()) {
+            File f2 = new File(newpath);
+
+            if (!f2.exists()) {
+                if (!f.renameTo(f2)) {
+                    throw new IOException("Failed to rename file " +
+                        translateNFSPath(oldpath));
+                }
+            } else {
+                throw new IOException(translateNFSPath(newpath) +
+                    " already exists");
+            }
+        } else {
+            throw new FileNotFoundException(translateNFSPath(oldpath) +
+                " does not exist");
+        }
+    }
+
+    /**
+ *
+ *
+ * @param path
+ *
+ * @throws PermissionDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+    public void removeDirectory(String path)
+        throws PermissionDeniedException, FileNotFoundException, IOException {
+        path = VirtualFileSystem.translateVFSPath(path);
+
+        File f = new File(path);
+        verifyPermissions(SshThread.getCurrentThreadUser(), path, "rw");
+
+        if (f.isDirectory()) {
+            if (f.exists()) {
+                if (f.listFiles().length == 0) {
+                    if (!f.delete()) {
+                        throw new IOException("Failed to remove directory " +
+                            translateNFSPath(path));
+                    }
+                } else {
+                    throw new IOException(translateNFSPath(path) +
+                        " is not an empty directory");
+                }
+            } else {
+                throw new FileNotFoundException(translateNFSPath(path) +
+                    " does not exist");
+            }
+        } else {
+            throw new IOException(translateNFSPath(path) +
+                " is not a directory");
+        }
+    }
+
+    /**
+ *
+ *
+ * @param path
+ * @param attrs
+ *
+ * @throws PermissionDeniedException
+ * @throws IOException
+ * @throws FileNotFoundException
+ */
+    public void setFileAttributes(String path, FileAttributes attrs)
+        throws PermissionDeniedException, IOException, FileNotFoundException {
+        // Since we cannot really set permissions, this should be ignored as we
+        // do not want applications to fail.
+
+        /*String realPath = VirtualFileSystem.translateVFSPath(path);
+     throw new PermissionDeniedException(
+    "Cannot set file attributes using virtual file system for file "
+    + realPath);*/
+    }
+
+    /**
+ *
+ *
+ * @param handle
+ * @param attrs
+ *
+ * @throws PermissionDeniedException
+ * @throws IOException
+ * @throws InvalidHandleException
+ */
+    public void setFileAttributes(byte[] handle, FileAttributes attrs)
+        throws PermissionDeniedException, IOException, InvalidHandleException {
+    }
+
+    /**
+ *
+ *
+ * @param path
+ *
+ * @return
+ *
+ * @throws UnsupportedFileOperationException
+ * @throws FileNotFoundException
+ * @throws IOException
+ * @throws PermissionDeniedException
+ */
+    public SftpFile readSymbolicLink(String path)
+        throws UnsupportedFileOperationException, FileNotFoundException, 
+            IOException, PermissionDeniedException {
+        throw new UnsupportedFileOperationException(
+            "Symbolic links are not supported by the Virtual File System");
+    }
+
+    /**
+ *
+ *
+ * @param link
+ * @param target
+ *
+ * @throws UnsupportedFileOperationException
+ * @throws FileNotFoundException
+ * @throws IOException
+ * @throws PermissionDeniedException
+ */
+    public void createSymbolicLink(String link, String target)
+        throws UnsupportedFileOperationException, FileNotFoundException, 
+            IOException, PermissionDeniedException {
+        throw new UnsupportedFileOperationException(
+            "Symbolic links are not supported by the Virtual File System");
+    }
+
+    /**
+ *
+ *
+ * @param path
+ *
+ * @return
+ *
+ * @throws IOException
+ */
+    public boolean fileExists(String path) throws IOException {
+        File f = new File(VirtualFileSystem.translateVFSPath(path));
+
+        return f.exists();
+    }
+
+    public String getDefaultPath(String username) throws FileNotFoundException {
+        return getVFSHomeDirectory(username);
+    }
+
+    /**
+ *
+ *
+ * @param path
+ *
+ * @return
+ *
+ * @throws IOException
+ * @throws FileNotFoundException
+ */
+    public String getCanonicalPath(String path)
+        throws IOException, FileNotFoundException {
+        File f = new File(VirtualFileSystem.translateVFSPath(path));
+
+        return f.getCanonicalPath();
+    }
+
+    /**
+ *
+ *
+ * @param path
+ *
+ * @return
+ *
+ * @throws FileNotFoundException
+ */
+    public String getRealPath(String path) throws FileNotFoundException {
+        log.debug("Get real path for '" + path + "'");
+        path = VirtualFileSystem.translateVFSPath(path);
+        log.debug("Translated VFS is '" + path + "'");
+        path = VirtualFileSystem.translateNFSPath(path);
+        log.debug("Translated NFS is '" + path + "'");
+
+        return path;
+    }
+
+    /**
+ *
+ *
+ * @param username
+ * @param path
+ * @param permissions
+ *
+ * @throws PermissionDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+    public void verifyPermissions(String username, String path,
+        String permissions)
+        throws PermissionDeniedException, FileNotFoundException, IOException {
+        String vfspath = translateNFSPath(path);
+
+        if (permissionHandler == null) {
+            VFSMount mount = getMount(vfspath);
+            VFSPermission perm;
+
+            if (mount.getPermissions().containsKey(SshThread.getCurrentThreadUser())) {
+                perm = (VFSPermission) mount.getPermissions().get(SshThread.getCurrentThreadUser());
+            } else if (mount.getPermissions().containsKey("default")) {
+                perm = (VFSPermission) mount.getPermissions().get("default");
+            } else {
+                throw new PermissionDeniedException(
+                    "No permissions set for mount");
+            }
+
+            if (!perm.verifyPermissions(permissions)) {
+                throw new PermissionDeniedException("Permission denied for " +
+                    translateNFSPath(path));
+            }
+        } else {
+            permissionHandler.verifyPermissions(username, path, permissions);
+        }
+    }
+
+    class OpenFile {
+        File f;
+        RandomAccessFile raf;
+        UnsignedInteger32 flags;
+
+        public OpenFile(File f, RandomAccessFile raf, UnsignedInteger32 flags) {
+            this.f = f;
+            this.raf = raf;
+            this.flags = flags;
+        }
+
+        public File getFile() {
+            return f;
+        }
+
+        public RandomAccessFile getRandomAccessFile() {
+            return raf;
+        }
+
+        public UnsignedInteger32 getFlags() {
+            return flags;
+        }
+    }
+
+    class OpenDirectory {
+        File f;
+        File[] children;
+        int readpos = 0;
+        String path;
+        String realPath;
+
+        public OpenDirectory(String realPath, String path, File f) {
+            this.path = path;
+            this.realPath = realPath;
+            this.f = f;
+            this.children = f.listFiles();
+        }
+
+        public File getFile() {
+            return f;
+        }
+
+        public File[] getChildren() {
+            return children;
+        }
+
+        public int getPosition() {
+            return readpos;
+        }
+
+        public void setPosition(int readpos) {
+            this.readpos = readpos;
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/DirectoryOperation.java b/src/com/sshtools/j2ssh/DirectoryOperation.java
new file mode 100644
index 0000000000000000000000000000000000000000..c20d3a818b76710f2a36183a4b4bb6e7f9d6d288
--- /dev/null
+++ b/src/com/sshtools/j2ssh/DirectoryOperation.java
@@ -0,0 +1,170 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh;
+
+import com.sshtools.j2ssh.sftp.SftpFile;
+
+import java.io.*;
+
+import java.util.*;
+
+
+public class DirectoryOperation {
+    Vector unchangedFiles = new Vector();
+    Vector newFiles = new Vector();
+    Vector updatedFiles = new Vector();
+    Vector deletedFiles = new Vector();
+    Vector recursedDirectories = new Vector();
+
+    public DirectoryOperation() {
+    }
+
+    protected void addNewFile(File f) {
+        newFiles.add(f);
+    }
+
+    protected void addUpdatedFile(File f) {
+        updatedFiles.add(f);
+    }
+
+    protected void addDeletedFile(File f) {
+        deletedFiles.add(f);
+    }
+
+    protected void addUnchangedFile(File f) {
+        unchangedFiles.add(f);
+    }
+
+    protected void addNewFile(SftpFile f) {
+        newFiles.add(f);
+    }
+
+    protected void addUpdatedFile(SftpFile f) {
+        updatedFiles.add(f);
+    }
+
+    protected void addDeletedFile(SftpFile f) {
+        deletedFiles.add(f);
+    }
+
+    protected void addUnchangedFile(SftpFile f) {
+        unchangedFiles.add(f);
+    }
+
+    public List getNewFiles() {
+        return newFiles;
+    }
+
+    public List getUpdatedFiles() {
+        return updatedFiles;
+    }
+
+    public List getUnchangedFiles() {
+        return unchangedFiles;
+    }
+
+    public List getDeletedFiles() {
+        return deletedFiles;
+    }
+
+    public boolean containsFile(File f) {
+        return unchangedFiles.contains(f) || newFiles.contains(f) ||
+        updatedFiles.contains(f) || deletedFiles.contains(f) ||
+        recursedDirectories.contains(f);
+    }
+
+    public boolean containsFile(SftpFile f) {
+        return unchangedFiles.contains(f) || newFiles.contains(f) ||
+        updatedFiles.contains(f) || deletedFiles.contains(f) ||
+        recursedDirectories.contains(f.getAbsolutePath());
+    }
+
+    public void addDirectoryOperation(DirectoryOperation op, File f) {
+        updatedFiles.addAll(op.getUpdatedFiles());
+        newFiles.addAll(op.getNewFiles());
+        unchangedFiles.addAll(op.getUnchangedFiles());
+        deletedFiles.addAll(op.getDeletedFiles());
+        recursedDirectories.add(f);
+    }
+
+    public int getFileCount() {
+        return newFiles.size() + updatedFiles.size();
+    }
+
+    public void addDirectoryOperation(DirectoryOperation op, String file) {
+        updatedFiles.addAll(op.getUpdatedFiles());
+        newFiles.addAll(op.getNewFiles());
+        unchangedFiles.addAll(op.getUnchangedFiles());
+        deletedFiles.addAll(op.getDeletedFiles());
+        recursedDirectories.add(file);
+    }
+
+    public long getTransferSize() {
+        Object obj;
+        long size = 0;
+        SftpFile sftpfile;
+        File file;
+
+        for (Iterator i = newFiles.iterator(); i.hasNext();) {
+            obj = i.next();
+
+            if (obj instanceof File) {
+                file = (File) obj;
+
+                if (file.isFile()) {
+                    size += file.length();
+                }
+            } else if (obj instanceof SftpFile) {
+                sftpfile = (SftpFile) obj;
+
+                if (sftpfile.isFile()) {
+                    size += sftpfile.getAttributes().getSize().longValue();
+                }
+            }
+        }
+
+        for (Iterator i = updatedFiles.iterator(); i.hasNext();) {
+            obj = i.next();
+
+            if (obj instanceof File) {
+                file = (File) obj;
+
+                if (file.isFile()) {
+                    size += file.length();
+                }
+            } else if (obj instanceof SftpFile) {
+                sftpfile = (SftpFile) obj;
+
+                if (sftpfile.isFile()) {
+                    size += sftpfile.getAttributes().getSize().longValue();
+                }
+            }
+        }
+
+        // Add a value for deleted files??
+        return size;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/FileTransferProgress.java b/src/com/sshtools/j2ssh/FileTransferProgress.java
new file mode 100644
index 0000000000000000000000000000000000000000..7e87c89435b8ba61db0348d9a279795c04fb91df
--- /dev/null
+++ b/src/com/sshtools/j2ssh/FileTransferProgress.java
@@ -0,0 +1,76 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh;
+
+
+/**
+ * <p>
+ * Title:
+ * </p>
+ *
+ * <p>
+ * Description:
+ * </p>
+ *
+ * <p>
+ * Copyright: Copyright (c) 2003
+ * </p>
+ *
+ * <p>
+ * Company:
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Id: FileTransferProgress.java,v 1.10 2003/09/11 15:35:00 martianx Exp $
+ */
+public interface FileTransferProgress {
+    /**
+     *
+     *
+     * @param bytesTotal
+     * @param remoteFile
+     */
+    public void started(long bytesTotal, String remoteFile);
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isCancelled();
+
+    /**
+     *
+     *
+     * @param bytesSoFar
+     */
+    public void progressed(long bytesSoFar);
+
+    /**
+     *
+     */
+    public void completed();
+}
diff --git a/src/com/sshtools/j2ssh/ScpClient.java b/src/com/sshtools/j2ssh/ScpClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..482947b5f40265487070ce3cb5376f3edb474f71
--- /dev/null
+++ b/src/com/sshtools/j2ssh/ScpClient.java
@@ -0,0 +1,668 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh;
+
+import java.io.BufferedInputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+import com.sshtools.j2ssh.connection.ChannelEventListener;
+import com.sshtools.j2ssh.session.SessionChannelClient;
+
+/**
+ * <p>
+ * Implements a Secure Copy (SCP) client. This may be useful when the server
+ * does not support SFTP.
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Revision: 1.18 $
+ *
+ * @since 0.2.0
+ */
+public final class ScpClient {
+  private SshClient ssh;
+  private File cwd;
+  private boolean verbose;
+  private ChannelEventListener eventListener;
+  /**
+   * <p>
+   * Creates an SCP client. CWD (Current working directory) will be the CWD
+   * of the process that started this JVM.
+   * </p>
+   *
+   * @param ssh A connected SshClient
+   * @param verbose Output verbose detail
+   * @param eventListener
+   *
+   * @since 0.2.0
+   */
+  public ScpClient(SshClient ssh, boolean verbose,
+                   ChannelEventListener eventListener) {
+    this(new File(ConfigurationLoader.checkAndGetProperty("user.dir", ".")),
+         ssh, verbose, eventListener);
+  }
+
+  /**
+   * <p>
+   * Creates an SCP client.
+   * </p>
+   *
+   * @param cwd The current local directory
+   * @param ssh A connected SshClient
+   * @param verbose Output verbose detail
+   * @param eventListener
+   *
+   * @since 0.2.0
+   */
+  public ScpClient(File cwd, SshClient ssh, boolean verbose,
+                   ChannelEventListener eventListener) {
+    this.ssh = ssh;
+    this.cwd = cwd;
+    this.verbose = verbose;
+    this.eventListener = eventListener;
+  }
+
+  /**
+   * <p>
+   * Uploads a <code>java.io.InputStream</code> to a remove server as file.
+   * You <strong>must</strong> supply the correct number of bytes that will
+   * be written.
+   * </p>
+   *
+   * @param in stream providing file
+   * @param length number of bytes that will be written
+   * @param localFile local file name
+   * @param remoteFile remote file name
+   *
+   * @throws IOException on any error
+   */
+  public void put(InputStream in, long length, String localFile,
+                  String remoteFile) throws IOException {
+    ScpChannel scp = new ScpChannel("scp -t " + (verbose ? "-v " : "")
+                                    + remoteFile);
+    scp.addEventListener(eventListener);
+    if (!ssh.openChannel(scp)) {
+      throw new IOException("Failed to open SCP channel");
+    }
+    scp.waitForResponse();
+    scp.writeStreamToRemote(in, length, localFile);
+    scp.close();
+  }
+
+  /**
+   * <p>
+   * Gets a remote file as an <code>java.io.InputStream</code>.
+   * </p>
+   *
+   * @param remoteFile remote file name
+   *
+   * @return stream
+   *
+   * @throws IOException on any error
+   */
+  public InputStream get(String remoteFile) throws IOException {
+    ScpChannel scp = new ScpChannel("scp " + "-f " + (verbose ? "-v " : "")
+                                    + remoteFile);
+    scp.addEventListener(eventListener);
+    if (!ssh.openChannel(scp)) {
+      throw new IOException("Failed to open SCP Channel");
+    }
+    return scp.readStreamFromRemote();
+  }
+
+  /**
+   * <p>
+   * Uploads a local file onto the remote server.
+   * </p>
+   *
+   * @param localFile The path to the local file relative to the local
+   *        current directory; may be a file or directory
+   * @param remoteFile The path on the remote server, may be a file or
+   *        directory
+   * @param recursive Copy the contents of a directory recursivly
+   *
+   * @throws IOException if an IO error occurs during the operation
+   *
+   * @since 0.2.0
+   */
+  public void put(String localFile, String remoteFile, boolean recursive) throws
+      IOException {
+    File lf = new File(localFile);
+    if (!lf.isAbsolute()) {
+      lf = new File(cwd, localFile);
+    }
+    if (!lf.exists()) {
+      throw new IOException(localFile + " does not exist");
+    }
+    if (!lf.isFile() && !lf.isDirectory()) {
+      throw new IOException(localFile
+                            + " is not a regular file or directory");
+    }
+    if (lf.isDirectory() && !recursive) {
+      throw new IOException(localFile
+                            + " is a directory, use recursive mode");
+    }
+    if ( (remoteFile == null) || remoteFile.equals("")) {
+      remoteFile = ".";
+    }
+    ScpChannel scp = new ScpChannel("scp "
+                                    + (lf.isDirectory() ? "-d " : "") + "-t "
+                                    + (recursive ? "-r " : "") +
+                                    (verbose ? "-v " : "")
+                                    + remoteFile);
+    scp.addEventListener(eventListener);
+    if (!ssh.openChannel(scp)) {
+      throw new IOException("Failed to open SCP channel");
+    }
+    scp.waitForResponse();
+    scp.writeFileToRemote(lf, recursive);
+    scp.close();
+  }
+
+  /**
+   * <p>
+   * Uploads an array of local files onto the remote server.
+   * </p>
+   *
+   * @param localFiles an array of local files; may be files or directories
+   * @param remoteFile the path on the remote server, may be a file or
+   *        directory1
+   * @param recursive Copy the contents of directorys recursivly
+   *
+   * @throws IOException if an IO error occurs during the operation
+   *
+   * @since 0.2.0
+   */
+  public void put(String[] localFiles, String remoteFile, boolean recursive) throws
+      IOException {
+    if ( (remoteFile == null) || remoteFile.equals("")) {
+      remoteFile = ".";
+    }
+    if (localFiles.length == 1) {
+      put(localFiles[0], remoteFile, recursive);
+    }
+    else {
+      ScpChannel scp = new ScpChannel("scp " + "-d -t "
+                                      + (recursive ? "-r " : "") +
+                                      (verbose ? "-v " : "")
+                                      + remoteFile);
+      scp.addEventListener(eventListener);
+      if (!ssh.openChannel(scp)) {
+        throw new IOException("Failed to open SCP channel");
+      }
+      scp.waitForResponse();
+      for (int i = 0; i < localFiles.length; i++) {
+        File lf = new File(localFiles[i]);
+        if (!lf.isAbsolute()) {
+          lf = new File(cwd, localFiles[i]);
+        }
+        if (!lf.isFile() && !lf.isDirectory()) {
+          throw new IOException(lf.getName()
+                                + " is not a regular file or directory");
+        }
+        scp.writeFileToRemote(lf, recursive);
+      }
+      scp.close();
+    }
+  }
+
+  /**
+   * <p>
+   * Downloads an array of remote files to the local computer.
+   * </p>
+   *
+   * @param localFile The local path to place the files
+   * @param remoteFiles The path of the remote files
+   * @param recursive recursivly copy the contents of a directory
+   *
+   * @throws IOException if an IO error occurs during the operation
+   *
+   * @since 0.2.0
+   */
+  public void get(String localFile, String[] remoteFiles, boolean recursive) throws
+      IOException {
+    StringBuffer buf = new StringBuffer();
+    for (int i = 0; i < remoteFiles.length; i++) {
+      buf.append("\"");
+      buf.append(remoteFiles[i]);
+      buf.append("\" ");
+    }
+    String remoteFile = buf.toString();
+    remoteFile = remoteFile.trim();
+    get(localFile, remoteFile, recursive);
+  }
+
+  /**
+   * <p>
+   * Downloads a remote file onto the local computer.
+   * </p>
+   *
+   * @param localFile The path to place the file
+   * @param remoteFile The path of the file on the remote server
+   * @param recursive recursivly copy the contents of a directory
+   *
+   * @throws IOException if an IO error occurs during the operation
+   *
+   * @since 0.2.0
+   */
+  public void get(String localFile, String remoteFile, boolean recursive) throws
+      IOException {
+    if ( (localFile == null) || localFile.equals("")) {
+      localFile = ".";
+    }
+    File lf = new File(localFile);
+    if (!lf.isAbsolute()) {
+      lf = new File(cwd, localFile);
+    }
+    if (lf.exists() && !lf.isFile() && !lf.isDirectory()) {
+      throw new IOException(localFile
+                            + " is not a regular file or directory");
+    }
+    ScpChannel scp = new ScpChannel("scp " + "-f "
+                                    + (recursive ? "-r " : "") +
+                                    (verbose ? "-v " : "")
+                                    + remoteFile);
+    scp.addEventListener(eventListener);
+    if (!ssh.openChannel(scp)) {
+      throw new IOException("Failed to open SCP Channel");
+    }
+    scp.readFromRemote(lf);
+    scp.close();
+  }
+
+  /**
+   * <p>
+   * Implements an SCP channel by extending the
+   * <code>SessionChannelClient</code>.
+   * </p>
+   *
+   * @since 0.2.0
+   */
+  class ScpChannel
+      extends SessionChannelClient {
+    byte[] buffer = new byte[16384];
+    String cmd;
+    /**
+     * <p>
+     * Contruct the channel with the specified scp command.
+     * </p>
+     *
+     * @param cmd The scp command
+     *
+     * @since 0.2.0
+     */
+    ScpChannel(String cmd) {
+      this.cmd = cmd;
+      setName("scp");
+    }
+
+    /**
+     * <p>
+     * This implementation executes the scp command when the channel is
+     * opened.
+     * </p>
+     *
+     * @throws IOException
+     *
+     * @since 0.2.0
+     */
+    protected void onChannelOpen() throws IOException {
+      if (!executeCommand(cmd)) {
+        throw new IOException("Failed to execute the command " + cmd);
+      }
+    }
+
+    /**
+     * <p>
+     * Writes a directory to the remote server.
+     * </p>
+     *
+     * @param dir The source directory
+     * @param recursive Add the contents of the directory recursivley
+     *
+     * @return true if the file was written, otherwise false
+     *
+     * @throws IOException if an IO error occurs
+     *
+     * @since 0.2.0
+     */
+    private boolean writeDirToRemote(File dir, boolean recursive) throws
+        IOException {
+      if (!recursive) {
+        writeError("File " + dir.getName()
+                   + " is a directory, use recursive mode");
+        return false;
+      }
+      String cmd = "D0755 0 " + dir.getName() + "\n";
+      out.write(cmd.getBytes());
+      waitForResponse();
+      String[] list = dir.list();
+      for (int i = 0; i < list.length; i++) {
+        File f = new File(dir, list[i]);
+        writeFileToRemote(f, recursive);
+      }
+      out.write("E\n".getBytes());
+      return true;
+    }
+
+    /**
+     * <p>
+     * Write a stream as a file to the remote server. You
+     * <strong>must</strong> supply the correct number of bytes that will
+     * be written.
+     * </p>
+     *
+     * @param in stream
+     * @param length number of bytes to write
+     * @param localName local file name
+     *
+     * @throws IOException if an IO error occurs
+     *
+     * @since 0.2.0
+     */
+    private void writeStreamToRemote(InputStream in, long length,
+                                     String localName) throws IOException {
+      String cmd = "C0644 " + length + " " + localName + "\n";
+      out.write(cmd.getBytes());
+      waitForResponse();
+      writeCompleteFile(in, length);
+      writeOk();
+      waitForResponse();
+    }
+
+    /**
+     * <p>
+     * Write a file to the remote server.
+     * </p>
+     *
+     * @param file The source file
+     * @param recursive Add the contents of the directory recursivley
+     *
+     * @throws IOException if an IO error occurs
+     *
+     * @since 0.2.0
+     */
+    private void writeFileToRemote(File file, boolean recursive) throws
+        IOException {
+      if (file.isDirectory()) {
+        if (!writeDirToRemote(file, recursive)) {
+          return;
+        }
+      }
+      else if (file.isFile()) {
+        String cmd = "C0644 " + file.length() + " " + file.getName()
+            + "\n";
+        out.write(cmd.getBytes());
+        waitForResponse();
+        FileInputStream fi = new FileInputStream(file);
+        writeCompleteFile(fi, file.length());
+        writeOk();
+      }
+      else {
+        throw new IOException(file.getName() + " not valid for SCP");
+      }
+      waitForResponse();
+    }
+
+    private void readFromRemote(File file) throws IOException {
+      String cmd;
+      String[] cmdParts = new String[3];
+      writeOk();
+      while (true) {
+        try {
+          cmd = readString();
+        }
+        catch (EOFException e) {
+          return;
+        }
+        char cmdChar = cmd.charAt(0);
+        switch (cmdChar) {
+          case 'E':
+            writeOk();
+            return;
+          case 'T':
+            throw new IOException("SCP time not supported: " + cmd);
+          case 'C':
+          case 'D':
+            String targetName = file.getAbsolutePath();
+            parseCommand(cmd, cmdParts);
+            if (file.isDirectory()) {
+              targetName += (File.separator + cmdParts[2]);
+            }
+            File targetFile = new File(targetName);
+            if (cmdChar == 'D') {
+              if (targetFile.exists()) {
+                if (!targetFile.isDirectory()) {
+                  String msg = "Invalid target "
+                      + targetFile.getName()
+                      + ", must be a directory";
+                  writeError(msg);
+                  throw new IOException(msg);
+                }
+              }
+              else {
+                if (!targetFile.mkdir()) {
+                  String msg = "Could not create directory: "
+                      + targetFile.getName();
+                  writeError(msg);
+                  throw new IOException(msg);
+                }
+              }
+              readFromRemote(targetFile);
+              continue;
+            }
+            FileOutputStream fo = new FileOutputStream(targetFile);
+            writeOk();
+            long len = Long.parseLong(cmdParts[1]);
+            readCompleteFile(fo, len);
+            waitForResponse();
+            writeOk();
+            break;
+          default:
+            writeError("Unexpected cmd: " + cmd);
+            throw new IOException("SCP unexpected cmd: " + cmd);
+        }
+      }
+    }
+
+    private InputStream readStreamFromRemote() throws IOException {
+      String cmd;
+      String[] cmdParts = new String[3];
+      writeOk();
+      try {
+        cmd = readString();
+      }
+      catch (EOFException e) {
+        return null;
+      }
+      char cmdChar = cmd.charAt(0);
+      switch (cmdChar) {
+        case 'E':
+          writeOk();
+          return null;
+        case 'T':
+          throw new IOException("SCP time not supported: " + cmd);
+        case 'D':
+          throw new IOException(
+              "Directories cannot be copied to a stream");
+        case 'C':
+          parseCommand(cmd, cmdParts);
+          writeOk();
+          long len = Long.parseLong(cmdParts[1]);
+          return new BufferedInputStream(new ScpInputStream(len, in, this),
+                                         16 * 1024);
+        default:
+          writeError("Unexpected cmd: " + cmd);
+          throw new IOException("SCP unexpected cmd: " + cmd);
+      }
+    }
+
+    private void parseCommand(String cmd, String[] cmdParts) throws IOException {
+      int l;
+      int r;
+      l = cmd.indexOf(' ');
+      r = cmd.indexOf(' ', l + 1);
+      if ( (l == -1) || (r == -1)) {
+        writeError("Syntax error in cmd");
+        throw new IOException("Syntax error in cmd");
+      }
+      cmdParts[0] = cmd.substring(1, l);
+      cmdParts[1] = cmd.substring(l + 1, r);
+      cmdParts[2] = cmd.substring(r + 1);
+    }
+
+    private String readString() throws IOException {
+      int ch;
+      int i = 0;
+      while ( ( (ch = in.read()) != ( (int) '\n')) && (ch >= 0)) {
+        buffer[i++] = (byte) ch;
+      }
+      if (ch == -1) {
+        throw new EOFException("SCP returned unexpected EOF");
+      }
+      if (buffer[0] == (byte) '\n') {
+        throw new IOException("Unexpected <NL>");
+      }
+      if ( (buffer[0] == (byte) '\02') || (buffer[0] == (byte) '\01')) {
+        String msg = new String(buffer, 1, i - 1);
+        if (buffer[0] == (byte) '\02') {
+          throw new IOException(msg);
+        }
+        throw new IOException("SCP returned an unexpected error: "
+                              + msg);
+      }
+      return new String(buffer, 0, i);
+    }
+
+    private void waitForResponse() throws IOException {
+      int r = in.read();
+      if (r == 0) {
+        // All is well, no error
+        return;
+      }
+      if (r == -1) {
+        throw new EOFException("SCP returned unexpected EOF");
+      }
+      String msg = readString();
+      if (r == (byte) '\02') {
+        throw new IOException(msg);
+      }
+      throw new IOException("SCP returned an unexpected error: " + msg);
+    }
+
+    private void writeOk() throws IOException {
+      out.write(0);
+    }
+
+    private void writeError(String reason) throws IOException {
+      out.write(1);
+      out.write(reason.getBytes());
+    }
+
+    private void writeCompleteFile(InputStream file, long size) throws
+        IOException {
+      int count = 0;
+      int read;
+      try {
+        while (count < size) {
+          read = file.read(buffer, 0,
+                           (int) ( ( (size - count) < buffer.length)
+                                  ? (size - count) : buffer.length));
+          if (read == -1) {
+            throw new EOFException("SCP received an unexpected EOF");
+          }
+          count += read;
+          out.write(buffer, 0, read);
+        }
+      }
+      finally {
+        file.close();
+      }
+    }
+
+    private void readCompleteFile(FileOutputStream file, long size) throws
+        IOException {
+      int count = 0;
+      int read;
+      try {
+        while (count < size) {
+          read = in.read(buffer, 0,
+                         (int) ( ( (size - count) < buffer.length)
+                                ? (size - count) : buffer.length));
+          if (read == -1) {
+            throw new EOFException("SCP received an unexpected EOF");
+          }
+          count += read;
+          file.write(buffer, 0, read);
+        }
+      }
+      finally {
+        file.close();
+      }
+    }
+  }
+
+  class ScpInputStream
+      extends InputStream {
+    long length;
+    InputStream in;
+    long count;
+    ScpChannel channel;
+    ScpInputStream(long length, InputStream in, ScpChannel channel) {
+      this.length = length;
+      this.in = in;
+      this.channel = channel;
+    }
+
+    public int read() throws IOException {
+      if (count == length) {
+        return -1;
+      }
+      if (count >= length) {
+        throw new EOFException("End of file.");
+      }
+      int r = in.read();
+      if (r == -1) {
+        throw new EOFException("Unexpected EOF.");
+      }
+      count++;
+      if (count == length) {
+        channel.waitForResponse();
+        channel.writeOk();
+      }
+      return r;
+    }
+
+    public void close() throws IOException {
+      channel.close();
+    }
+  }
+}
diff --git a/src/com/sshtools/j2ssh/SftpClient.java b/src/com/sshtools/j2ssh/SftpClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..7791629f67202d267fa4d63ac51b95d9403091c9
--- /dev/null
+++ b/src/com/sshtools/j2ssh/SftpClient.java
@@ -0,0 +1,1240 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh;
+
+import com.sshtools.j2ssh.connection.*;
+import com.sshtools.j2ssh.io.*;
+import com.sshtools.j2ssh.sftp.*;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+
+/**
+ * <p>
+ * Implements a Secure File Transfer (SFTP) client.
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Revision: 1.44 $
+ *
+ * @since 0.2.0
+ */
+public class SftpClient {
+    SftpSubsystemClient sftp;
+    String cwd;
+    String lcwd;
+    private int BLOCKSIZE = 65535;
+
+    // Default permissions is determined by default_permissions ^ umask
+    int umask = 0022;
+    int default_permissions = 0777;
+
+    /**
+     * <p>
+     * Constructs the SFTP client.
+     * </p>
+     *
+     * @param ssh the <code>SshClient</code> instance
+     *
+     * @throws IOException if an IO error occurs
+     */
+    SftpClient(SshClient ssh) throws IOException {
+        this(ssh, null);
+    }
+
+    /**
+     * <p>
+     * Constructs the SFTP client with a given channel event listener.
+     * </p>
+     *
+     * @param ssh the <code>SshClient</code> instance
+     * @param eventListener an event listener implementation
+     *
+     * @throws IOException if an IO error occurs
+     */
+    SftpClient(SshClient ssh, ChannelEventListener eventListener)
+        throws IOException {
+        if (!ssh.isConnected()) {
+            throw new IOException("SshClient is not connected");
+        }
+
+        this.sftp = ssh.openSftpChannel(eventListener);
+
+        // Get the users default directory
+        cwd = sftp.getDefaultDirectory();
+        lcwd = System.getProperty("user.home");
+    }
+
+    /**
+     * Sets the umask used by this client.
+     * @param umask
+     * @return the previous umask value
+     */
+    public int umask(int umask) {
+        int old = umask;
+        this.umask = umask;
+
+        return old;
+    }
+
+    /**
+     * <p>
+     * Changes the working directory on the remote server.
+     * </p>
+     *
+     * @param dir the new working directory
+     *
+     * @throws IOException if an IO error occurs or the file does not exist
+     * @throws FileNotFoundException
+     *
+     * @since 0.2.0
+     */
+    public void cd(String dir) throws IOException {
+        try {
+            String actual;
+
+            if (dir.equals("")) {
+                actual = sftp.getDefaultDirectory();
+            } else {
+                actual = resolveRemotePath(dir);
+                actual = sftp.getAbsolutePath(actual);
+            }
+
+            FileAttributes attr = sftp.getAttributes(actual);
+
+            if (!attr.isDirectory()) {
+                throw new IOException(dir + " is not a directory");
+            }
+
+            cwd = actual;
+        } catch (IOException ex) {
+            throw new FileNotFoundException(dir + " could not be found");
+        }
+    }
+
+    private File resolveLocalPath(String path) throws IOException {
+        File f = new File(path);
+
+        if (!f.isAbsolute()) {
+            f = new File(lcwd, path);
+        }
+
+        return f;
+    }
+
+    private String resolveRemotePath(String path) throws IOException {
+        verifyConnection();
+
+        String actual;
+
+        if (!path.startsWith("/")) {
+            actual = cwd + (cwd.endsWith("/") ? "" : "/") + path;
+        } else {
+            actual = path;
+        }
+
+        return actual;
+    }
+
+    private void verifyConnection() throws SshException {
+        if (sftp.isClosed()) {
+            throw new SshException("The SFTP connection has been closed");
+        }
+    }
+
+    /**
+     * <p>
+     * Creates a new directory on the remote server. This method will throw an
+     * exception if the directory already exists. To create directories and
+     * disregard any errors use the <code>mkdirs</code> method.
+     * </p>
+     *
+     * @param dir the name of the new directory
+     *
+     * @throws IOException if an IO error occurs or if the directory already
+     *         exists
+     *
+     * @since 0.2.0
+     */
+    public void mkdir(String dir) throws IOException {
+        String actual = resolveRemotePath(dir);
+
+        try {
+            FileAttributes attrs = stat(actual);
+
+            if (!attrs.isDirectory()) {
+                throw new IOException("File already exists named " + dir);
+            }
+        } catch (IOException ex) {
+            sftp.makeDirectory(actual);
+            chmod(default_permissions ^ umask, actual);
+        }
+    }
+
+    /**
+     * <p>
+     * Create a directory or set of directories. This method will not fail even
+     * if the directories exist. It is advisable to test whether the directory
+     * exists before attempting an operation by using the <code>stat</code>
+     * method to return the directories attributes.
+     * </p>
+     *
+     * @param dir the path of directories to create.
+     */
+    public void mkdirs(String dir) {
+        StringTokenizer tokens = new StringTokenizer(dir, "/");
+        String path = dir.startsWith("/") ? "/" : "";
+
+        while (tokens.hasMoreElements()) {
+            path += (String) tokens.nextElement();
+
+            try {
+                stat(path);
+            } catch (IOException ex) {
+                try {
+                    mkdir(path);
+                } catch (IOException ex2) {
+                }
+            }
+
+            path += "/";
+        }
+    }
+
+    /**
+     * <p>
+     * Returns the absolute path name of the current remote working directory.
+     * </p>
+     *
+     * @return the absolute path of the remote working directory.
+     *
+     * @since 0.2.0
+     */
+    public String pwd() {
+        return cwd;
+    }
+
+    /**
+     * <p>
+     * List the contents of the current remote working directory.
+     * </p>
+     *
+     * <p>
+     * Returns a list of <code>SftpFile</code> instances for the current
+     * working directory.
+     * </p>
+     *
+     * @return a list of SftpFile for the current working directory
+     *
+     * @throws IOException if an IO error occurs
+     *
+     * @see com.sshtools.j2ssh.sftp.SftpFile
+     * @since 0.2.0
+     */
+    public List ls() throws IOException {
+        return ls(cwd);
+    }
+
+    /**
+     * <p>
+     * List the contents remote directory.
+     * </p>
+     *
+     * <p>
+     * Returns a list of <code>SftpFile</code> instances for the remote
+     * directory.
+     * </p>
+     *
+     * @param path the path on the remote server to list
+     *
+     * @return a list of SftpFile for the remote directory
+     *
+     * @throws IOException if an IO error occurs
+     *
+     * @see com.sshtools.j2ssh.sftp.SftpFile
+     * @since 0.2.0
+     */
+    public List ls(String path) throws IOException {
+        String actual = resolveRemotePath(path);
+        FileAttributes attrs = sftp.getAttributes(actual);
+
+        if (!attrs.isDirectory()) {
+            throw new IOException(path + " is not a directory");
+        }
+
+        SftpFile file = sftp.openDirectory(actual);
+        Vector children = new Vector();
+
+        while (sftp.listChildren(file, children) > -1) {
+            ;
+        }
+
+        file.close();
+
+        return children;
+    }
+
+    /**
+     * <p>
+     * Changes the local working directory.
+     * </p>
+     *
+     * @param path the path to the new working directory
+     *
+     * @throws IOException if an IO error occurs
+     *
+     * @since 0.2.0
+     */
+    public void lcd(String path) throws IOException {
+        File actual;
+
+        if (!isLocalAbsolutePath(path)) {
+            actual = new File(lcwd, path);
+        } else {
+            actual = new File(path);
+        }
+
+        if (!actual.isDirectory()) {
+            throw new IOException(path + " is not a directory");
+        }
+
+        lcwd = actual.getCanonicalPath();
+    }
+
+    private static boolean isLocalAbsolutePath(String path) {
+        return (new File(path)).isAbsolute();
+    }
+
+    /**
+     * <p>
+     * Returns the absolute path to the local working directory.
+     * </p>
+     *
+     * @return the absolute path of the local working directory.
+     *
+     * @since 0.2.0
+     */
+    public String lpwd() {
+        return lcwd;
+    }
+
+    /**
+     * <p>
+     * Download the remote file to the local computer.
+     * </p>
+     *
+     * @param path the path to the remote file
+     * @param progress
+     *
+     * @return
+     *
+     * @throws IOException if an IO error occurs of the file does not exist
+     * @throws TransferCancelledException
+     *
+     * @since 0.2.0
+     */
+    public FileAttributes get(String path, FileTransferProgress progress)
+        throws IOException, TransferCancelledException {
+        String localfile;
+
+        if (path.lastIndexOf("/") > -1) {
+            localfile = path.substring(path.lastIndexOf("/") + 1);
+        } else {
+            localfile = path;
+        }
+
+        return get(path, localfile, progress);
+    }
+
+    /**
+     *
+     *
+     * @param path
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public FileAttributes get(String path) throws IOException {
+        return get(path, (FileTransferProgress) null);
+    }
+
+    private void transferFile(InputStream in, OutputStream out)
+        throws IOException, TransferCancelledException {
+        transferFile(in, out, null);
+    }
+
+    private void transferFile(
+        InputStream in, 
+        OutputStream out,
+        FileTransferProgress progress)
+      throws IOException, TransferCancelledException 
+    {
+      try {
+        long bytesSoFar = 0;
+        byte[] buffer = new byte[BLOCKSIZE];
+        int read;
+        
+        while ((read = in.read(buffer)) > -1) {
+          if ((progress != null) && progress.isCancelled()) {
+            throw new TransferCancelledException();
+          }
+          
+          if (read > 0) {
+            out.write(buffer, 0, read);
+            
+            //out.flush();
+            bytesSoFar += read;
+            
+            if (progress != null) {
+              progress.progressed(bytesSoFar);
+            }
+          }
+        }
+      } 
+      finally {
+        try {
+          in.close();
+          out.close();
+        } 
+        catch (IOException ex) {
+        }
+      }
+    }
+
+    /**
+     * <p>
+     * Download the remote file to the local computer. If the paths provided
+     * are not absolute the current working directory is used.
+     * </p>
+     *
+     * @param remote the path/name of the remote file
+     * @param local the path/name to place the file on the local computer
+     * @param progress
+     *
+     * @return
+     *
+     * @throws IOException if an IO error occurs or the file does not exist
+     * @throws TransferCancelledException
+     *
+     * @since 0.2.0
+     */
+    public FileAttributes get(String remote, String local,
+        FileTransferProgress progress)
+        throws IOException, TransferCancelledException {
+        File localPath = resolveLocalPath(local);
+
+        if (!localPath.exists()) {
+            localPath.getParentFile().mkdirs();
+            localPath.createNewFile();
+        }
+
+        FileOutputStream out = new FileOutputStream(localPath);
+
+        return get(remote, out, progress);
+    }
+
+    /**
+     *
+     *
+     * @param remote
+     * @param local
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public FileAttributes get(String remote, String local)
+        throws IOException {
+        return get(remote, local, null);
+    }
+
+    /**
+     * <p>
+     * Download the remote file writing it to the specified
+     * <code>OutputStream</code>. The OutputStream is closed by this mehtod
+     * even if the operation fails.
+     * </p>
+     *
+     * @param remote the path/name of the remote file
+     * @param local the OutputStream to write
+     * @param progress
+     *
+     * @return
+     *
+     * @throws IOException if an IO error occurs or the file does not exist
+     * @throws TransferCancelledException
+     *
+     * @since 0.2.0
+     */
+    public FileAttributes get(String remote, OutputStream local,
+        FileTransferProgress progress)
+        throws IOException, TransferCancelledException {
+        String remotePath = resolveRemotePath(remote);
+        FileAttributes attrs = stat(remotePath);
+
+        if (progress != null) {
+            progress.started(attrs.getSize().longValue(), remotePath);
+        }
+
+        SftpFileInputStream in = new SftpFileInputStream(sftp.openFile(
+                    remotePath, SftpSubsystemClient.OPEN_READ));
+        transferFile(in, local, progress);
+
+        if (progress != null) {
+            progress.completed();
+        }
+
+        return attrs;
+    }
+
+    /**
+     *
+     *
+     * @param remote
+     * @param local
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public FileAttributes get(String remote, OutputStream local)
+        throws IOException {
+        return get(remote, local, null);
+    }
+
+    /**
+     * <p>
+     * Returns the state of the SFTP client. The client is closed if the
+     * underlying session channel is closed. Invoking the <code>quit</code>
+     * method of this object will close the underlying session channel.
+     * </p>
+     *
+     * @return true if the client is still connected, otherwise false
+     *
+     * @since 0.2.0
+     */
+    public boolean isClosed() {
+        return sftp.isClosed();
+    }
+
+    /**
+     * <p>
+     * Upload a file to the remote computer.
+     * </p>
+     *
+     * @param local the path/name of the local file
+     * @param progress
+     *
+     * @return
+     *
+     * @throws IOException if an IO error occurs or the file does not exist
+     * @throws TransferCancelledException
+     *
+     * @since 0.2.0
+     */
+    public void put(String local, FileTransferProgress progress)
+        throws IOException, TransferCancelledException {
+        File f = new File(local);
+        put(local, f.getName(), progress);
+    }
+
+    /**
+     *
+     *
+     * @param local
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public void put(String local) throws IOException {
+        put(local, (FileTransferProgress) null);
+    }
+
+    /**
+     * <p>
+     * Upload a file to the remote computer. If the paths provided are not
+     * absolute the current working directory is used.
+     * </p>
+     *
+     * @param local the path/name of the local file
+     * @param remote the path/name of the destination file
+     * @param progress
+     *
+     * @return
+     *
+     * @throws IOException if an IO error occurs or the file does not exist
+     * @throws TransferCancelledException
+     *
+     * @since 0.2.0
+     */
+    public void put(String local, String remote, FileTransferProgress progress)
+        throws IOException, TransferCancelledException {
+        File localPath = resolveLocalPath(local);
+        FileInputStream in = new FileInputStream(localPath);
+
+        try {
+            FileAttributes attrs = stat(remote);
+
+            if (attrs.isDirectory()) {
+                File f = new File(local);
+                remote += ((remote.endsWith("/") ? "" : "/") + f.getName());
+            }
+        } catch (IOException ex) {
+        }
+
+        put(in, remote, progress);
+    }
+
+    /**
+     *
+     *
+     * @param local
+     * @param remote
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public void put(String local, String remote) throws IOException {
+        put(local, remote, null);
+    }
+
+    /**
+     * <p>
+     * Upload a file to the remote computer reading from the specified <code>
+     * InputStream</code>. The InputStream is closed, even if the operation
+     * fails.
+     * </p>
+     *
+     * @param in the InputStream being read
+     * @param remote the path/name of the destination file
+     * @param progress
+     *
+     * @return
+     *
+     * @throws IOException if an IO error occurs
+     * @throws TransferCancelledException
+     *
+     * @since 0.2.0
+     */
+    public void put(InputStream in, String remote, FileTransferProgress progress)
+        throws IOException, TransferCancelledException {
+        String remotePath = resolveRemotePath(remote);
+        SftpFileOutputStream out;
+        FileAttributes attrs;
+        boolean newfile = false;
+
+        try {
+            attrs = stat(remotePath);
+            out = new SftpFileOutputStream(sftp.openFile(remotePath,
+                        SftpSubsystemClient.OPEN_CREATE |
+                        SftpSubsystemClient.OPEN_TRUNCATE |
+                        SftpSubsystemClient.OPEN_WRITE));
+        } catch (IOException ex) {
+            attrs = new FileAttributes();
+            newfile = true;
+            attrs.setPermissions(new UnsignedInteger32(default_permissions ^
+                    umask));
+            out = new SftpFileOutputStream(sftp.openFile(remotePath,
+                        SftpSubsystemClient.OPEN_CREATE |
+                        SftpSubsystemClient.OPEN_WRITE, attrs));
+        }
+
+        if (progress != null) {
+            progress.started(in.available(), remotePath);
+        }
+
+        transferFile(in, out, progress);
+
+        if (progress != null) {
+            progress.completed();
+        }
+
+        // Set the permissions here since at creation they dont always work
+        if (newfile) {
+            chmod(default_permissions ^ umask, remotePath);
+        }
+    }
+
+    /**
+     *
+     *
+     * @param in
+     * @param remote
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public void put(InputStream in, String remote) throws IOException {
+        put(in, remote, null);
+    }
+
+    /**
+     * <p>
+     * Sets the user ID to owner for the file or directory.
+     * </p>
+     *
+     * @param uid numeric user id of the new owner
+     * @param path the path to the remote file/directory
+     *
+     * @throws IOException if an IO error occurs or the file does not exist
+     *
+     * @since 0.2.0
+     */
+    public void chown(int uid, String path) throws IOException {
+        String actual = resolveRemotePath(path);
+        FileAttributes attrs = sftp.getAttributes(actual);
+        attrs.setUID(new UnsignedInteger32(uid));
+        sftp.setAttributes(actual, attrs);
+    }
+
+    /**
+     * <p>
+     * Sets the group ID for the file or directory.
+     * </p>
+     *
+     * @param gid the numeric group id for the new group
+     * @param path the path to the remote file/directory
+     *
+     * @throws IOException if an IO error occurs or the file does not exist
+     *
+     * @since 0.2.0
+     */
+    public void chgrp(int gid, String path) throws IOException {
+        String actual = resolveRemotePath(path);
+        FileAttributes attrs = sftp.getAttributes(actual);
+        attrs.setGID(new UnsignedInteger32(gid));
+        sftp.setAttributes(actual, attrs);
+    }
+
+    /**
+     * <p>
+     * Changes the access permissions or modes of the specified file or
+     * directory.
+     * </p>
+     *
+     * <p>
+     * Modes determine who can read, change or execute a file.
+     * </p>
+     * <blockquote><pre>Absolute modes are octal numbers specifying the complete list of
+     * attributes for the files; you specify attributes by OR'ing together
+     * these bits.
+     *
+     * 0400       Individual read
+     * 0200       Individual write
+     * 0100       Individual execute (or list directory)
+     * 0040       Group read
+     * 0020       Group write
+     * 0010       Group execute
+     * 0004       Other read
+     * 0002       Other write
+     * 0001       Other execute </pre></blockquote>
+     *
+     * @param permissions the absolute mode of the file/directory
+     * @param path the path to the file/directory on the remote server
+     *
+     * @throws IOException if an IO error occurs or the file if not found
+     *
+     * @since 0.2.0
+     */
+    public void chmod(int permissions, String path) throws IOException {
+        String actual = resolveRemotePath(path);
+        sftp.changePermissions(actual, permissions);
+    }
+
+    public void umask(String umask) throws IOException {
+        try {
+            this.umask = Integer.parseInt(umask, 8);
+        } catch (NumberFormatException ex) {
+            throw new IOException(
+                "umask must be 4 digit octal number e.g. 0022");
+        }
+    }
+
+    /**
+     * <p>
+     * Rename a file on the remote computer.
+     * </p>
+     *
+     * @param oldpath the old path
+     * @param newpath the new path
+     *
+     * @throws IOException if an IO error occurs
+     *
+     * @since 0.2.0
+     */
+    public void rename(String oldpath, String newpath)
+        throws IOException {
+        String from = resolveRemotePath(oldpath);
+        String to = resolveRemotePath(newpath);
+        sftp.renameFile(from, to);
+    }
+
+    /**
+     * <p>
+     * Remove a file or directory from the remote computer.
+     * </p>
+     *
+     * @param path the path of the remote file/directory
+     *
+     * @throws IOException if an IO error occurs
+     *
+     * @since 0.2.0
+     */
+    public void rm(String path) throws IOException {
+        String actual = resolveRemotePath(path);
+        FileAttributes attrs = sftp.getAttributes(actual);
+
+        if (attrs.isDirectory()) {
+            sftp.removeDirectory(actual);
+        } else {
+            sftp.removeFile(actual);
+        }
+    }
+
+    /**
+     *
+     *
+     * @param path
+     * @param force
+     * @param recurse
+     *
+     * @throws IOException
+     */
+    public void rm(String path, boolean force, boolean recurse)
+        throws IOException {
+        String actual = resolveRemotePath(path);
+        FileAttributes attrs = sftp.getAttributes(actual);
+        SftpFile file;
+
+        if (attrs.isDirectory()) {
+            List list = ls(path);
+
+            if (!force && (list.size() > 0)) {
+                throw new IOException(
+                    "You cannot delete non-empty directory, use force=true to overide");
+            } else {
+                for (Iterator it = list.iterator(); it.hasNext();) {
+                    file = (SftpFile) it.next();
+
+                    if (file.isDirectory() && !file.getFilename().equals(".") &&
+                            !file.getFilename().equals("..")) {
+                        if (recurse) {
+                            rm(file.getAbsolutePath(), force, recurse);
+                        } else {
+                            throw new IOException(
+                                "Directory has contents, cannot delete without recurse=true");
+                        }
+                    } else if (file.isFile()) {
+                        sftp.removeFile(file.getAbsolutePath());
+                    }
+                }
+            }
+
+            sftp.removeDirectory(actual);
+        } else {
+            sftp.removeFile(actual);
+        }
+    }
+
+    /**
+     * <p>
+     * Create a symbolic link on the remote computer.
+     * </p>
+     *
+     * @param path the path to the existing file
+     * @param link the new link
+     *
+     * @throws IOException if an IO error occurs or the operation is not
+     *         supported on the remote platform
+     *
+     * @since 0.2.0
+     */
+    public void symlink(String path, String link) throws IOException {
+        String actualPath = resolveRemotePath(path);
+        String actualLink = resolveRemotePath(link);
+        sftp.createSymbolicLink(actualPath, actualLink);
+    }
+
+    /**
+     * <p>
+     * Returns the attributes of the file from the remote computer.
+     * </p>
+     *
+     * @param path the path of the file on the remote computer
+     *
+     * @return the attributes
+     *
+     * @throws IOException if an IO error occurs or the file does not exist
+     *
+     * @see com.sshtools.j2ssh.sftp.FileAttributes
+     * @since 0.2.0
+     */
+    public FileAttributes stat(String path) throws IOException {
+        String actual = resolveRemotePath(path);
+
+        return sftp.getAttributes(actual);
+    }
+
+    /**
+     *
+     *
+     * @param path
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public String getAbsolutePath(String path) throws IOException {
+        String actual = resolveRemotePath(path);
+
+        return sftp.getAbsolutePath(path);
+    }
+
+    /**
+     * <p>
+     * Close the SFTP client.
+     * </p>
+     *
+     * @throws IOException
+     *
+     * @since 0.2.0
+     */
+    public void quit() throws IOException {
+        sftp.close();
+    }
+
+    /**
+     *
+     *
+     * @param localdir
+     * @param remotedir
+     * @param recurse
+     * @param sync
+     * @param commit
+     * @param progress
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public DirectoryOperation copyLocalDirectory(String localdir,
+        String remotedir, boolean recurse, boolean sync, boolean commit,
+        FileTransferProgress progress) throws IOException {
+        DirectoryOperation op = new DirectoryOperation();
+
+        // Record the previous
+        String pwd = pwd();
+        String lpwd = lpwd();
+        File local = resolveLocalPath(localdir);
+        remotedir = resolveRemotePath(remotedir);
+        remotedir += (remotedir.endsWith("/") ? "" : "/");
+        remotedir += local.getName();
+        remotedir += (remotedir.endsWith("/") ? "" : "/");
+
+        // Setup the remote directory if were committing
+        if (commit) {
+            try {
+                FileAttributes attrs = stat(remotedir);
+            } catch (IOException ex) {
+                mkdir(remotedir);
+            }
+        }
+
+        // List the local files and verify against the remote server
+        File[] ls = local.listFiles();
+
+        if (ls != null) {
+            for (int i = 0; i < ls.length; i++) {
+                if (ls[i].isDirectory() && !ls[i].getName().equals(".") &&
+                        !ls[i].getName().equals("..")) {
+                    if (recurse) {
+                        File f = new File(local, ls[i].getName());
+                        op.addDirectoryOperation(copyLocalDirectory(
+                                f.getAbsolutePath(), remotedir, recurse, sync,
+                                commit, progress), f);
+                    }
+                } else if (ls[i].isFile()) {
+                    try {
+                        FileAttributes attrs = stat(remotedir +
+                                ls[i].getName());
+
+                        if ((ls[i].length() == attrs.getSize().longValue()) &&
+                                ((ls[i].lastModified() / 1000) == attrs.getModifiedTime()
+                                                                           .longValue())) {
+                            op.addUnchangedFile(ls[i]);
+                        } else {
+                            op.addUpdatedFile(ls[i]);
+                        }
+                    } catch (IOException ex1) {
+                        op.addNewFile(ls[i]);
+                    }
+
+                    if (commit) {
+                        put(ls[i].getAbsolutePath(),
+                            remotedir + ls[i].getName(), progress);
+
+                        FileAttributes attrs = stat(remotedir +
+                                ls[i].getName());
+                        attrs.setTimes(new UnsignedInteger32(
+                                ls[i].lastModified() / 1000),
+                            new UnsignedInteger32(ls[i].lastModified() / 1000));
+                        sftp.setAttributes(remotedir + ls[i].getName(), attrs);
+                    }
+                }
+            }
+        }
+
+        if (sync) {
+            // List the contents of the new local directory and remove any
+            // files/directories that were not updated
+            try {
+                List files = ls(remotedir);
+                SftpFile file;
+                File f;
+
+                for (Iterator it = files.iterator(); it.hasNext();) {
+                    file = (SftpFile) it.next();
+
+                    // Create a local file object to test for its existence
+                    f = new File(local, file.getFilename());
+
+                    if (!op.containsFile(file) &&
+                            !file.getFilename().equals(".") &&
+                            !file.getFilename().equals("..")) {
+                        op.addDeletedFile(file);
+
+                        if (commit) {
+                            if (file.isDirectory()) {
+                                // Recurse through the directory, deleting stuff
+                                recurseMarkForDeletion(file, op);
+
+                                if (commit) {
+                                    rm(file.getAbsolutePath(), true, true);
+                                }
+                            } else if (file.isFile()) {
+                                rm(file.getAbsolutePath());
+                            }
+                        }
+                    }
+                }
+            } catch (IOException ex2) {
+                // Ignorew since if it does not exist we cant delete it
+            }
+        }
+
+        // Return the operation details
+        return op;
+    }
+
+    /**
+     *
+     *
+     * @param eventListener
+     */
+    public void addEventListener(ChannelEventListener eventListener) {
+        sftp.addEventListener(eventListener);
+    }
+
+    private void recurseMarkForDeletion(SftpFile file, DirectoryOperation op)
+        throws IOException {
+        List list = ls(file.getAbsolutePath());
+        op.addDeletedFile(file);
+
+        for (Iterator it = list.iterator(); it.hasNext();) {
+            file = (SftpFile) it.next();
+
+            if (file.isDirectory() && !file.getFilename().equals(".") &&
+                    !file.getFilename().equals("..")) {
+                recurseMarkForDeletion(file, op);
+            } else if (file.isFile()) {
+                op.addDeletedFile(file);
+            }
+        }
+    }
+
+    private void recurseMarkForDeletion(File file, DirectoryOperation op)
+        throws IOException {
+        File[] list = file.listFiles();
+        op.addDeletedFile(file);
+
+        if (list != null) {
+            for (int i = 0; i < list.length; i++) {
+                file = list[i];
+
+                if (file.isDirectory() && !file.getName().equals(".") &&
+                        !file.getName().equals("..")) {
+                    recurseMarkForDeletion(file, op);
+                } else if (file.isFile()) {
+                    op.addDeletedFile(file);
+                }
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @param remotedir
+     * @param localdir
+     * @param recurse
+     * @param sync
+     * @param commit
+     * @param progress
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public DirectoryOperation copyRemoteDirectory(String remotedir,
+        String localdir, boolean recurse, boolean sync, boolean commit,
+        FileTransferProgress progress) throws IOException {
+        // Create an operation object to hold the information
+        DirectoryOperation op = new DirectoryOperation();
+
+        // Record the previous working directoies
+        String pwd = pwd();
+        String lpwd = lpwd();
+        cd(remotedir);
+
+        // Setup the local cwd
+        String base = remotedir;
+        int idx = base.lastIndexOf('/');
+
+        if (idx != -1) {
+            base = base.substring(idx + 1);
+        }
+
+        File local = new File(localdir, base);
+
+        //				File local = new File(localdir, remotedir);
+        if (!local.isAbsolute()) {
+            local = new File(lpwd(), localdir);
+        }
+
+        if (!local.exists() && commit) {
+            local.mkdir();
+        }
+
+        List files = ls();
+        SftpFile file;
+        File f;
+
+        for (Iterator it = files.iterator(); it.hasNext();) {
+            file = (SftpFile) it.next();
+
+            if (file.isDirectory() && !file.getFilename().equals(".") &&
+                    !file.getFilename().equals("..")) {
+                if (recurse) {
+                    f = new File(local, file.getFilename());
+                    op.addDirectoryOperation(copyRemoteDirectory(
+                            file.getFilename(), local.getAbsolutePath(),
+                            recurse, sync, commit, progress), f);
+                }
+            } else if (file.isFile()) {
+                f = new File(local, file.getFilename());
+
+                if (f.exists() &&
+                        (f.length() == file.getAttributes().getSize().longValue()) &&
+                        ((f.lastModified() / 1000) == file.getAttributes()
+                                                              .getModifiedTime()
+                                                              .longValue())) {
+                    if (commit) {
+                        op.addUnchangedFile(f);
+                    } else {
+                        op.addUnchangedFile(file);
+                    }
+
+                    continue;
+                }
+
+                if (f.exists()) {
+                    if (commit) {
+                        op.addUpdatedFile(f);
+                    } else {
+                        op.addUpdatedFile(file);
+                    }
+                } else {
+                    if (commit) {
+                        op.addNewFile(f);
+                    } else {
+                        op.addNewFile(file);
+                    }
+                }
+
+                if (commit) {
+                    FileAttributes attrs = get(file.getFilename(),
+                            f.getAbsolutePath(), progress);
+                    f.setLastModified(attrs.getModifiedTime().longValue() * 1000);
+                }
+            }
+        }
+
+        if (sync) {
+            // List the contents of the new local directory and remove any
+            // files/directories that were not updated
+            File[] contents = local.listFiles();
+
+            if (contents != null) {
+                for (int i = 0; i < contents.length; i++) {
+                    if (!op.containsFile(contents[i])) {
+                        op.addDeletedFile(contents[i]);
+
+                        if (contents[i].isDirectory() &&
+                                !contents[i].getName().equals(".") &&
+                                !contents[i].getName().equals("..")) {
+                            recurseMarkForDeletion(contents[i], op);
+
+                            if (commit) {
+                                IOUtil.recurseDeleteDirectory(contents[i]);
+                            }
+                        } else if (commit) {
+                            contents[i].delete();
+                        }
+                    }
+                }
+            }
+        }
+
+        cd(pwd);
+
+        return op;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/SshClient.java b/src/com/sshtools/j2ssh/SshClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..9cdde0bf10045b1de23f9aa3140ea2b46845e909
--- /dev/null
+++ b/src/com/sshtools/j2ssh/SshClient.java
@@ -0,0 +1,1406 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh;
+
+import com.sshtools.j2ssh.authentication.AuthenticationProtocolClient;
+import com.sshtools.j2ssh.authentication.AuthenticationProtocolState;
+import com.sshtools.j2ssh.authentication.PublicKeyAuthenticationClient;
+import com.sshtools.j2ssh.authentication.SshAuthenticationClient;
+import com.sshtools.j2ssh.configuration.SshConnectionProperties;
+import com.sshtools.j2ssh.connection.Channel;
+import com.sshtools.j2ssh.connection.ChannelEventAdapter;
+import com.sshtools.j2ssh.connection.ChannelEventListener;
+import com.sshtools.j2ssh.connection.ChannelFactory;
+import com.sshtools.j2ssh.connection.ConnectionProtocol;
+import com.sshtools.j2ssh.forwarding.ForwardingClient;
+import com.sshtools.j2ssh.net.TransportProvider;
+import com.sshtools.j2ssh.net.TransportProviderFactory;
+import com.sshtools.j2ssh.session.SessionChannelClient;
+import com.sshtools.j2ssh.sftp.SftpSubsystemClient;
+import com.sshtools.j2ssh.transport.ConsoleKnownHostsKeyVerification;
+import com.sshtools.j2ssh.transport.HostKeyVerification;
+import com.sshtools.j2ssh.transport.TransportProtocolClient;
+import com.sshtools.j2ssh.transport.TransportProtocolState;
+import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
+import com.sshtools.j2ssh.util.State;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.File;
+import java.io.IOException;
+
+import java.net.UnknownHostException;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+
+/**
+ * <p>
+ * Implements an SSH client with methods to connect to a remote server and
+ * perform all necersary SSH functions such as SCP, SFTP, executing commands,
+ * starting the users shell and perform port forwarding.
+ * </p>
+ *
+ * <p>
+ * There are several steps to perform prior to performing the desired task.
+ * This involves the making the initial connection, authenticating the user
+ * and creating a session to execute a command, shell or subsystem and/or
+ * configuring the port forwarding manager.
+ * </p>
+ *
+ * <p>
+ * To create a connection use the following code:<br>
+ * <blockquote><pre>
+ * // Create a instance and connect SshClient
+ * ssh = new SshClient();
+ * ssh.connect("hostname");
+ * </pre></blockquote>
+ * Once this code has executed and returned
+ * the connection is ready for authentication:<br>
+ * <blockquote><pre>
+ * PasswordAuthenticationClient pwd = new PasswordAuthenticationClient();
+ * pwd.setUsername("foo");
+ * pwd.setPassword("xxxxx");
+ * // Authenticate the user
+ * int result = ssh.authenticate(pwd);
+ * if(result==AuthenticationProtocolState.COMPLETED) {
+ *    // Authentication complete
+ * }
+ * </pre></blockquote>
+ * Once authenticated the user's shell can be started:<br>
+ * <blockquote><pre>
+ * // Open a session channel
+ * SessionChannelClient session =
+ *                      ssh.openSessionChannel();
+ *
+ * // Request a pseudo terminal, if you do not you may not see the prompt
+ * if(session.requestPseudoTerminal("ansi", 80, 24, 0, 0, "") {
+ *      // Start the users shell
+ *      if(session.startShell()) {
+ *         // Do something with the session output
+ *         session.getOutputStream().write("echo message\n");
+ *         ....
+ *       }
+ * }
+ * </pre></blockquote>
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Revision: 1.75 $
+ *
+ * @since 0.2.0
+ */
+public class SshClient {
+    private static Log log = LogFactory.getLog(SshClient.class);
+
+    /**
+     * The SSH Authentication protocol implementation for this SSH client. The
+     * SSH Authentication protocol runs over the SSH Transport protocol as a
+     * transport protocol service.
+     */
+    protected AuthenticationProtocolClient authentication;
+
+    /**
+     * The SSH Connection protocol implementation for this SSH client. The
+     * connection protocol runs over the SSH Transport protocol as a transport
+     * protocol service and is started by the authentication protocol after a
+     * successful authentication.
+     */
+    protected ConnectionProtocol connection;
+
+    /** Provides a high level management interface for SSH port forwarding. */
+    protected ForwardingClient forwarding;
+
+    /** The SSH Transport protocol implementation for this SSH Client. */
+    protected TransportProtocolClient transport;
+
+    /** The current state of the authentication for the current connection. */
+    protected int authenticationState = AuthenticationProtocolState.READY;
+
+    /**
+     * The timeout in milliseconds for the underlying transport provider
+     * (typically a Socket).
+     */
+    protected int socketTimeout = 0;
+
+    /**
+     * A Transport protocol event handler instance that receives notifications
+     * of transport layer events such as Socket timeouts and disconnection.
+     */
+    protected SshEventAdapter eventHandler = null;
+
+    /** The currently active channels for this SSH Client connection. */
+    protected Vector activeChannels = new Vector();
+
+    /**
+     * An channel event listener implemention to maintain the active channel
+     * list.
+     */
+    protected ActiveChannelEventListener activeChannelListener = new ActiveChannelEventListener();
+
+    /**
+     * Flag indicating whether the forwarding instance is created when the
+     * connection is made.
+     */
+    protected boolean useDefaultForwarding = true;
+
+    /** The currently active Sftp clients */
+    private Vector activeSftpClients = new Vector();
+
+    /**
+     * <p>
+     * Contructs an unitilialized SshClient ready for connecting.
+     * </p>
+     */
+    public SshClient() {
+    }
+
+    /**
+     * <p>
+     * Returns the server's authentication banner.
+     * </p>
+     *
+     * <p>
+     * In some jurisdictions, sending a warning message before authentication
+     * may be relevant for getting legal protection.  Many UNIX machines, for
+     * example, normally display text from `/etc/issue', or use "tcp wrappers"
+     * or similar software to display a banner before issuing a login prompt.
+     * </p>
+     *
+     * <p>
+     * The server may or may not send this message. Call this method to
+     * retrieve the message, specifying a timeout limit to wait for the
+     * message.
+     * </p>
+     *
+     * @param timeout The number of milliseconds to wait for the banner message
+     *        before returning
+     *
+     * @return The server's banner message
+     *
+     * @exception IOException If an IO error occurs reading the message
+     *
+     * @since 0.2.0
+     */
+    public String getAuthenticationBanner(int timeout)
+        throws IOException {
+        if (authentication == null) {
+            return "";
+        } else {
+            return authentication.getBannerMessage(timeout);
+        }
+    }
+
+    /**
+     * <p>
+     * Returns the list of available authentication methods for a given user.
+     * </p>
+     *
+     * <p>
+     * A client may request a list of authentication methods that may continue
+     * by using the "none" authentication method.This method calls the "none"
+     * method and returns the available authentication methods.
+     * </p>
+     *
+     * @param username The name of the account for which you require the
+     *        available authentication methods
+     *
+     * @return A list of Strings, for example "password", "publickey" &
+     *         "keyboard-interactive"
+     *
+     * @exception IOException If an IO error occurs during the operation
+     *
+     * @since 0.2.0
+     */
+    public List getAvailableAuthMethods(String username)
+        throws IOException {
+        if (authentication != null) {
+            return authentication.getAvailableAuths(username,
+                connection.getServiceName());
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * <p>
+     * Returns the connection state of the client.
+     * </p>
+     *
+     * @return true if the client is connected, false otherwise
+     *
+     * @since 0.2.0
+     */
+    public boolean isConnected() {
+        State state = (transport == null) ? null : transport.getState();
+        int value = (state == null) ? TransportProtocolState.DISCONNECTED
+                                    : state.getValue();
+
+        return ((value == TransportProtocolState.CONNECTED) ||
+        (value == TransportProtocolState.PERFORMING_KEYEXCHANGE));
+    }
+
+    /**
+     * <p>
+     * Evaluate whether the client has successfully authenticated.
+     * </p>
+     *
+     * @return true if the client is authenticated, otherwise false
+     */
+    public boolean isAuthenticated() {
+        return authenticationState == AuthenticationProtocolState.COMPLETE;
+    }
+
+    /**
+     * <p>
+     * Returns the identification string sent by the server during protocol
+     * negotiation. For example "SSH-2.0-OpenSSH_p3.4".
+     * </p>
+     *
+     * @return The server's identification string.
+     *
+     * @since 0.2.0
+     */
+    public String getServerId() {
+        return transport.getRemoteId();
+    }
+
+    /**
+     * <p>
+     * Returns the server's public key supplied during key exchange.
+     * </p>
+     *
+     * @return the server's public key
+     *
+     * @since 0.2.0
+     */
+    public SshPublicKey getServerHostKey() {
+        return transport.getServerHostKey();
+    }
+
+    /**
+     * <p>
+     * Returns the transport protocol's connection state.
+     * </p>
+     *
+     * @return The transport protocol's state
+     *
+     * @since 0.2.0
+     */
+    public TransportProtocolState getConnectionState() {
+        return transport.getState();
+    }
+
+    /**
+     * <p>
+     * Returns the default port forwarding manager.
+     * </p>
+     *
+     * @return This connection's forwarding client
+     *
+     * @since 0.2.0
+     */
+    public ForwardingClient getForwardingClient() {
+        return forwarding;
+    }
+
+    /**
+     * <p>
+     * Return's a rough guess at the server's EOL setting. This is simply
+     * derived from the identification string and should not be used as a cast
+     * iron proof on the EOL setting.
+     * </p>
+     *
+     * @return The transport protocol's EOL constant
+     *
+     * @since 0.2.0
+     */
+    public int getRemoteEOL() {
+        return transport.getRemoteEOL();
+    }
+
+    /**
+     * <p>
+     * Set the event handler for the underlying transport protocol.
+     * </p>
+     * <blockquote>
+     * <pre>
+     * ssh.setEventHandler(new TransportProtocolEventHandler() {
+     *
+     *   public void onSocketTimeout(TransportProtocol transport) {<br>
+     *     // Do something to handle the socket timeout<br>
+     *   }
+     *
+     *   public void onDisconnect(TransportProtocol transport) {
+     *     // Perhaps some clean up?
+     *   }
+     * });
+     * </pre>
+     * </blockquote>
+     *
+     * @param eventHandler The event handler instance to receive transport
+     *        protocol events
+     *
+     * @see com.sshtools.j2ssh.transport.TransportProtocolEventHandler
+     * @since 0.2.0
+     */
+    public void addEventHandler(SshEventAdapter eventHandler) {
+        // If were connected then add, otherwise store for later connection
+        if (transport != null) {
+            transport.addEventHandler(eventHandler);
+            authentication.addEventListener(eventHandler);
+        } else {
+            this.eventHandler = eventHandler;
+        }
+    }
+
+    /**
+     * <p>
+     * Set's the socket timeout (in milliseconds) for the underlying transport
+     * provider. This MUST be called prior to connect.
+     * </p>
+     * <blockquote>
+     * SshClient ssh = new SshClient();
+     * ssh.setSocketTimeout(30000);
+     * ssh.connect("hostname");
+     * </blockquote>
+     *
+     * @param milliseconds The number of milliseconds without activity before
+     *        the timeout event occurs
+     *
+     * @since 0.2.0
+     */
+    public void setSocketTimeout(int milliseconds) {
+        this.socketTimeout = milliseconds;
+    }
+
+    /**
+     * <p>
+     * Return's a rough guess at the server's EOL setting. This is simply
+     * derived from the identification string and should not be used as a cast
+     * iron proof on the EOL setting.
+     * </p>
+     *
+     * @return The EOL string
+     *
+     * @since 0.2.0
+     */
+    public String getRemoteEOLString() {
+        return ((transport.getRemoteEOL() == TransportProtocolClient.EOL_CRLF)
+        ? "\r\n" : "\n");
+    }
+
+    /**
+     * Get the connection properties for this connection.
+     *
+     * @return
+     */
+    public SshConnectionProperties getConnectionProperties() {
+        return transport.getProperties();
+    }
+
+    /**
+     * <p>
+     * Authenticate the user on the remote host.
+     * </p>
+     *
+     * <p>
+     * To authenticate the user, create an <code>SshAuthenticationClient</code>
+     * instance and configure it with the authentication details.
+     * </p>
+     * <code> PasswordAuthenticationClient pwd = new
+     * PasswordAuthenticationClient(); pwd.setUsername("root");
+     * pwd.setPassword("xxxxxxxxx"); int result = ssh.authenticate(pwd);
+     * </code>
+     *
+     * <p>
+     * The method returns a result value will one of the public static values
+     * defined in <code>AuthenticationProtocolState</code>. These are<br>
+     * <br>
+     * COMPLETED - The authentication succeeded.<br>
+     * PARTIAL   - The authentication succeeded but a further authentication
+     * method is required.<br>
+     * FAILED    - The authentication failed.<br>
+     * CANCELLED - The user cancelled authentication (can only be returned
+     * when the user is prompted for information.<br>
+     * </p>
+     *
+     * @param auth A configured SshAuthenticationClient instance ready for
+     *        authentication
+     *
+     * @return The authentication result
+     *
+     * @exception IOException If an IO error occurs during authentication
+     *
+     * @since 0.2.0
+     */
+    public int authenticate(SshAuthenticationClient auth)
+        throws IOException {
+        // Do the authentication
+        authenticationState = authentication.authenticate(auth, connection);
+
+        if ((authenticationState == AuthenticationProtocolState.COMPLETE) &&
+                useDefaultForwarding) {
+            // Use some method to synchronize forwardings on the ForwardingClient
+            forwarding.synchronizeConfiguration(transport.getProperties());
+        }
+
+        return authenticationState;
+    }
+
+    /**
+     * <p>
+     * Determine whether a private/public key pair will be accepted for public
+     * key authentication.
+     * </p>
+     *
+     * <p>
+     * When using public key authentication, the signing of data could take
+     * some time depending upon the available machine resources. By calling
+     * this method, you can determine whether the server will accept a key for
+     * authentication by providing the public key. The server will verify the
+     * key against the user's authorized keys and return true should the
+     * public key be authorized. The caller can then proceed with the private
+     * key operation.
+     * </p>
+     *
+     * @param username The username for authentication
+     * @param key The public key for which authentication will be attempted
+     *
+     * @return true if the server will accept the key, otherwise false
+     *
+     * @exception IOException If an IO error occurs during the operation
+     * @throws SshException
+     *
+     * @since 0.2.0
+     */
+    public boolean acceptsKey(String username, SshPublicKey key)
+        throws IOException {
+        if (authenticationState != AuthenticationProtocolState.COMPLETE) {
+            PublicKeyAuthenticationClient pk = new PublicKeyAuthenticationClient();
+
+            return pk.acceptsKey(authentication, username,
+                connection.getServiceName(), key);
+        } else {
+            throw new SshException("Authentication has been completed!");
+        }
+    }
+
+    /**
+     * <p>
+     * Connect the client to the server using default connection properties.
+     * </p>
+     *
+     * <p>
+     * This call attempts to connect to the hostname specified on the standard
+     * SSH port of 22 and uses all the default connection properties. This
+     * call is the equivilent of calling:
+     * </p>
+     * <blockquote><pre>
+     * SshConnectionProperties properties = new
+     *                           SshConnectionProperties();
+     * properties.setHostname("hostname");
+     * ssh.connect(properties);
+     * </pre></blockquote>
+     *
+     * @param hostname The hostname of the server to connect
+     *
+     * @exception IOException If an IO error occurs during the connect
+     *            operation
+     *
+     * @see #connect(com.sshtools.j2ssh.configuration.SshConnectionProperties)
+     * @since 0.2.0
+     */
+    public void connect(String hostname) throws IOException {
+        connect(hostname, 22, new ConsoleKnownHostsKeyVerification());
+    }
+
+    /**
+     * <p>
+     * Connect the client to the server using the default connection
+     * properties.
+     * </p>
+     *
+     * <p>
+     * This call attempts to connect to the hostname specified on the standard
+     * SSH port of 22 and uses all the default connection properties. When
+     * this method returns the connection has been established, the server's
+     * identity been verified and the connection is ready for user
+     * authentication. Host key verification will be performed using the host
+     * key verification instance provided:
+     * </p>
+     * <blockquote><pre>
+     * // Connect and consult $HOME/.ssh/known_hosts
+     * ssh.connect("hostname", new ConsoleKnownHostsKeyVerification());
+     * // Connect and allow any host
+     * ssh.connect("hostname", new
+     *                 IgnoreHostKeyVerification());
+     * </pre></blockquote>
+     *
+     * <p>
+     * You can provide your own host key verification process by implementing
+     * the <code>HostKeyVerification</code> interface.
+     * </p>
+     *
+     * @param hostname The hostname of the server to connect
+     * @param hosts The host key verification instance to consult for host key
+     *        validation
+     *
+     * @exception IOException If an IO error occurs during the connect
+     *            operation
+     *
+     * @see #connect(com.sshtools.j2ssh.configuration.SshConnectionProperties,
+     *      com.sshtools.j2ssh.transport.HostKeyVerification)
+     * @since 0.2.0
+     */
+    public void connect(String hostname, HostKeyVerification hosts)
+        throws IOException {
+        connect(hostname, 22, hosts);
+    }
+
+    /**
+     * <p>
+     * Connect the client to the server on a specified port with default
+     * connection properties.
+     * </p>
+     *
+     * <p>
+     * This call attempts to connect to the hostname and port specified. This
+     * call is the equivilent of calling:
+     * </p>
+     * <blockquote></pre>
+     * SshConnectionProperties properties = new
+     *                               SshConnectionProperties();
+     * properties.setHostname("hostname");
+     * properties.setPort(10022);
+     * ssh.connect(properties);
+     * </pre></blockquote>
+     *
+     * @param hostname The hostname of the server to connect
+     * @param port The port to connect
+     *
+     * @exception IOException If an IO error occurs during the connect
+     *            operation
+     *
+     * @see #connect(com.sshtools.j2ssh.configuration.SshConnectionProperties)
+     * @since 0.2.0
+     */
+    public void connect(String hostname, int port) throws IOException {
+        connect(hostname, port, new ConsoleKnownHostsKeyVerification());
+    }
+
+    /**
+     * <p>
+     * Connect the client to the server on a specified port with default
+     * connection properties.
+     * </p>
+     *
+     * <p>
+     * This call attempts to connect to the hostname and port specified. When
+     * this method returns the connection has been established, the server's
+     * identity been verified and the connection is ready for user
+     * authentication. Host key verification will be performed using the host
+     * key verification instance provided:
+     * </p>
+     * <blockquote><pre>
+     * // Connect and consult $HOME/.ssh/known_hosts
+     * ssh.connect("hostname", new ConsoleKnownHostsKeyVerification());
+     * // Connect and allow any host
+     * ssh.connect("hostname", new
+     *                 IgnoreHostKeyVerification());
+     * </pre></blockquote>
+     *
+     * <p>
+     * You can provide your own host key verification process by implementing
+     * the <code>HostKeyVerification</code> interface.
+     * </p>
+     *
+     * @param hostname The hostname of the server to connect
+     * @param port The port to connect
+     * @param hosts The host key verification instance to consult for host key
+     *        validation
+     *
+     * @exception IOException If an IO error occurs during the connect
+     *            operation
+     *
+     * @see #connect(com.sshtools.j2ssh.configuration.SshConnectionProperties,
+     *      com.sshtools.j2ssh.transport.HostKeyVerification)
+     * @since 0.2.0
+     */
+    public void connect(String hostname, int port, HostKeyVerification hosts)
+        throws IOException {
+        SshConnectionProperties properties = new SshConnectionProperties();
+        properties.setHost(hostname);
+        properties.setPort(port);
+        connect(properties, hosts);
+    }
+
+    /**
+     * <p>
+     * Connect the client to the server with the specified properties.
+     * </p>
+     *
+     * <p>
+     * This call attempts to connect to using the connection properties
+     * specified. When this method returns the connection has been
+     * established, the server's identity been verified and the connection is
+     * ready for user authentication. To use this method first create a
+     * properties instance and set the required fields.
+     * </p>
+     * <blockquote><pre>
+     * SshConnectionProperties properties = new
+     *                         SshConnectionProperties();
+     * properties.setHostname("hostname");
+     * properties.setPort(10022);
+     * properties.setPrefCSEncryption("blowfish-cbc");
+     * ssh.connect(properties);
+     * </pre></blockquote>
+     *
+     * <p>
+     * Host key verification will be performed using
+     * <code>ConsoleKnownHostsKeyVerification</code> and so this call is the
+     * equivilent of calling:
+     * </p>
+     * <blockquote><pre>
+     * ssh.connect("hostname", new ConsoleKnownHostsKeyVerification());
+     * </pre></blockquote>
+     *
+     * <p>
+     * If the key is not matched to any keys already in the
+     * $HOME/.ssh/known_hosts file, the user will be prompted via the console
+     * to confirm the identity of the remote server. The user will receive the
+     * following prompt.
+     * </p>
+     * <code> The host shell.sourceforge.net is currently unknown to the system
+     * The host key fingerprint is: 1024: 4c 68 3 d4 5c 58 a6 1d 9d 17 13 24
+     * 14 48 ba 99 Do you want to allow this host key? [Yes|No|Always]:
+     * </code>
+     *
+     * <p>
+     * Selecting the "always" option will write the key to the known_hosts
+     * file.
+     * </p>
+     *
+     * @param properties The connection properties
+     *
+     * @exception IOException If an IO error occurs during the connect
+     *            operation
+     *
+     * @since 0.2.0
+     */
+    public void connect(SshConnectionProperties properties)
+        throws IOException {
+        connect(properties, new ConsoleKnownHostsKeyVerification());
+    }
+
+    /**
+     * <p>
+     * Connect the client to the server with the specified properties.
+     * </p>
+     *
+     * <p>
+     * This call attempts to connect to using the connection properties
+     * specified. When this method returns the connection has been
+     * established, the server's identity been verified and the connection is
+     * ready for user authentication. To use this method first create a
+     * properties instance and set the required fields.
+     * </p>
+     * <blockquote><pre>
+     * SshConnectionProperties properties = new
+     *                             SshConnectionProperties();
+     * properties.setHostname("hostname");
+     * properties.setPort(22);             // Defaults to 22
+     * // Set the prefered client->server encryption
+     * ssh.setPrefCSEncryption("blowfish-cbc");
+     * // Set the prefered server->client encrpytion
+     * ssh.setPrefSCEncrpyion("3des-cbc");
+     * ssh.connect(properties);
+     * </pre></blockquote>
+     *
+     * <p>
+     * Host key verification will be performed using the host key verification
+     * instance provided:<br>
+     * <blockquote><pre>
+     * // Connect and consult $HOME/.ssh/known_hosts
+     * ssh.connect("hostname", new ConsoleKnownHostsKeyVerification());
+     * // Connect and allow any host
+     * ssh.connect("hostname", new
+     *                 IgnoreHostKeyVerification());
+     * </pre></blockquote>
+     * You can provide your own host key verification process by implementing the
+     * <code>HostKeyVerification</code> interface.
+     * </p>
+     *
+     * @param properties The connection properties
+     * @param hostVerification The host key verification instance to consult
+     *        for host  key validation
+     *
+     * @exception UnknownHostException If the host is unknown
+     * @exception IOException If an IO error occurs during the connect
+     *            operation
+     *
+     * @since 0.2.0
+     */
+    public void connect(SshConnectionProperties properties,
+        HostKeyVerification hostVerification)
+        throws UnknownHostException, IOException {
+        TransportProvider provider = TransportProviderFactory.connectTransportProvider(properties /*, connectTimeout*/,
+                socketTimeout);
+
+        // Start the transport protocol
+        transport = new TransportProtocolClient(hostVerification);
+        transport.addEventHandler(eventHandler);
+        transport.startTransportProtocol(provider, properties);
+
+        // Start the authentication protocol
+        authentication = new AuthenticationProtocolClient();
+        authentication.addEventListener(eventHandler);
+        transport.requestService(authentication);
+        connection = new ConnectionProtocol();
+
+        if (useDefaultForwarding) {
+            forwarding = new ForwardingClient(connection);
+        }
+    }
+
+    /**
+     * <p>
+     * Sets the timeout value for the key exchange.
+     * </p>
+     *
+     * <p>
+     * When this time limit is reached the transport protocol will initiate a
+     * key re-exchange. The default value is one hour with the minumin timeout
+     * being 60 seconds.
+     * </p>
+     *
+     * @param seconds The number of seconds beofre key re-exchange
+     *
+     * @exception IOException If the timeout value is invalid
+     *
+     * @since 0.2.0
+     */
+    public void setKexTimeout(long seconds) throws IOException {
+        transport.setKexTimeout(seconds);
+    }
+
+    /**
+     * <p>
+     * Sets the key exchance transfer limit in kilobytes.
+     * </p>
+     *
+     * <p>
+     * Once this amount of data has been transfered the transport protocol will
+     * initiate a key re-exchange. The default value is one gigabyte of data
+     * with the mimimun value of 10 kilobytes.
+     * </p>
+     *
+     * @param kilobytes The data transfer limit in kilobytes
+     *
+     * @exception IOException If the data transfer limit is invalid
+     */
+    public void setKexTransferLimit(long kilobytes) throws IOException {
+        transport.setKexTransferLimit(kilobytes);
+    }
+
+    /**
+     * <p>
+     * Set's the send ignore flag to send random data packets.
+     * </p>
+     *
+     * <p>
+     * If this flag is set to true, then the transport protocol will send
+     * additional SSH_MSG_IGNORE packets with random data.
+     * </p>
+     *
+     * @param sendIgnore true if you want to turn on random packet data,
+     *        otherwise false
+     *
+     * @since 0.2.0
+     */
+    public void setSendIgnore(boolean sendIgnore) {
+        transport.setSendIgnore(sendIgnore);
+    }
+
+    /**
+     * <p>
+     * Turn the default forwarding manager on/off.
+     * </p>
+     *
+     * <p>
+     * If this flag is set to false before connection, the client will not
+     * create a port forwarding manager. Use this to provide you own
+     * forwarding implementation.
+     * </p>
+     *
+     * @param useDefaultForwarding Set to false if you not wish to use the
+     *        default forwarding manager.
+     *
+     * @since 0.2.0
+     */
+    public void setUseDefaultForwarding(boolean useDefaultForwarding) {
+        this.useDefaultForwarding = useDefaultForwarding;
+    }
+
+    /**
+     * <p>
+     * Disconnect the client.
+     * </p>
+     *
+     * @since 0.2.0
+     */
+    public void disconnect() {
+        if (connection != null) {
+            connection.stop();
+        }
+
+        if (transport != null) {
+            transport.disconnect("Terminating connection");
+        }
+    }
+
+    /**
+     * <p>
+     * Returns the number of bytes transmitted to the remote server.
+     * </p>
+     *
+     * @return The number of bytes transmitted
+     *
+     * @since 0.2.0
+     */
+    public long getOutgoingByteCount() {
+        return transport.getOutgoingByteCount();
+    }
+
+    /**
+     * <p>
+     * Returns the number of bytes received from the remote server.
+     * </p>
+     *
+     * @return The number of bytes received
+     *
+     * @since 0.2.0
+     */
+    public long getIncomingByteCount() {
+        return transport.getIncomingByteCount();
+    }
+
+    /**
+     * <p>
+     * Returns the number of active channels for this client.
+     * </p>
+     *
+     * <p>
+     * This is the total count of sessions, port forwarding, sftp, scp and
+     * custom channels currently open.
+     * </p>
+     *
+     * @return The number of active channels
+     *
+     * @since 0.2.0
+     */
+    public int getActiveChannelCount() {
+        synchronized (activeChannels) {
+            return activeChannels.size();
+        }
+    }
+
+    /**
+     * <p>
+     * Returns the list of active channels.
+     * </p>
+     *
+     * @return The list of active channels
+     *
+     * @since 0.2.0
+     */
+    public List getActiveChannels() {
+        synchronized (activeChannels) {
+            return (List) activeChannels.clone();
+        }
+    }
+
+    /**
+     * <p>
+     * Returns true if there is an active session channel of the specified
+     * type.
+     * </p>
+     *
+     * <p>
+     * When a session is created, it is assigned a default type. For instance,
+     * when a session is created it as a type of "uninitialized"; however when
+     * a shell is started on the session, the type is set to "shell". This
+     * also occurs for commands where the type is set to the command which is
+     * executed and subsystems where the type is set to the subsystem name.
+     * This allows each session to be saved in the active session channel's
+     * list and recalled later. It is also possible to set the session
+     * channel's type using the setSessionType method of the
+     * <code>SessionChannelClient</code> class.
+     * </p>
+     * <blockquote><pre>
+     * if(ssh.hasActiveSession("shell")) {
+     *      SessionChannelClient session =
+     *           ssh.getActiveSession("shell");
+     * }
+     * </pre></blockquote>
+     *
+     * @param type The string specifying the channel type
+     *
+     * @return true if an active session channel exists, otherwise false
+     *
+     * @since 0.2.0
+     */
+    public boolean hasActiveSession(String type) {
+        Iterator it = activeChannels.iterator();
+        Object obj;
+
+        while (it.hasNext()) {
+            obj = it.next();
+
+            if (obj instanceof SessionChannelClient) {
+                if (((SessionChannelClient) obj).getSessionType().equals(type)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * <p>
+     * Returns the active session channel of the given type.
+     * </p>
+     *
+     * @param type The type fo session channel
+     *
+     * @return The session channel instance
+     *
+     * @exception IOException If the session type does not exist
+     *
+     * @since 0.2.0
+     */
+    public SessionChannelClient getActiveSession(String type)
+        throws IOException {
+        Iterator it = activeChannels.iterator();
+        Object obj;
+
+        while (it.hasNext()) {
+            obj = it.next();
+
+            if (obj instanceof SessionChannelClient) {
+                if (((SessionChannelClient) obj).getSessionType().equals(type)) {
+                    return (SessionChannelClient) obj;
+                }
+            }
+        }
+
+        throw new IOException("There are no active " + type + " sessions");
+    }
+
+    /**
+     * Determine whether the channel supplied is an active channel
+     *
+     * @param channel
+     *
+     * @return
+     */
+    public boolean isActiveChannel(Channel channel) {
+        return activeChannels.contains(channel);
+    }
+
+    /**
+     * <p>
+     * Open's a session channel on the remote server.
+     * </p>
+     *
+     * <p>
+     * A session channel may be used to start the user's shell, execute a
+     * command or start a subsystem such as SFTP.
+     * </p>
+     *
+     * @return An new session channel
+     *
+     * @exception IOException If authentication has not been completed, the
+     *            server refuses to open the channel or a general IO error
+     *            occurs
+     *
+     * @see com.sshtools.j2ssh.session.SessionChannelClient
+     * @since 0.2.0
+     */
+    public SessionChannelClient openSessionChannel() throws IOException {
+        return openSessionChannel(null);
+    }
+
+    /**
+     *
+     * <p>
+     * Open's a session channel on the remote server.
+     * </p>
+     *
+     * <p>
+     * A session channel may be used to start the user's shell, execute a
+     * command or start a subsystem such as SFTP.
+     * </p>
+     *
+     * @param eventListener an event listner interface to add to the channel
+     *
+     * @return
+     *
+     * @throws IOException
+     * @throws SshException
+     */
+    public SessionChannelClient openSessionChannel(
+        ChannelEventListener eventListener) throws IOException {
+        if (authenticationState != AuthenticationProtocolState.COMPLETE) {
+            throw new SshException("Authentication has not been completed!");
+        }
+
+        SessionChannelClient session = new SessionChannelClient();
+        session.addEventListener(activeChannelListener);
+
+        if (!connection.openChannel(session, eventListener)) {
+            throw new SshException("The server refused to open a session");
+        }
+
+        return session;
+    }
+
+    /**
+     * <p>
+     * Open an SFTP client for file transfer operations.
+     * </p>
+     * <blockquote><pre>
+     * SftpClient sftp = ssh.openSftpClient();
+     * sftp.cd("foo");
+     * sftp.put("somefile.txt");
+     * sftp.quit();
+     * </pre></blockquote>
+     *
+     * @return Returns an initialized SFTP client
+     *
+     * @exception IOException If an IO error occurs during the operation
+     *
+     * @see SftpClient
+     * @since 0.2.0
+     */
+    public SftpClient openSftpClient() throws IOException {
+        return openSftpClient(null);
+    }
+
+    /**
+     * <p>
+     * Open an SFTP client for file transfer operations. Adds the supplied
+     * event listener to the underlying channel.
+     * </p>
+     *
+     * @param eventListener
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public SftpClient openSftpClient(ChannelEventListener eventListener)
+        throws IOException {
+        SftpClient sftp = new SftpClient(this, eventListener);
+        activeSftpClients.add(sftp);
+
+        return sftp;
+    }
+
+    /**
+     * Determine if there are existing sftp clients open
+     *
+     * @return
+     */
+    public boolean hasActiveSftpClient() {
+        synchronized (activeSftpClients) {
+            return activeSftpClients.size() > 0;
+        }
+    }
+
+    /**
+     * Get an active sftp client
+     *
+     * @return
+     *
+     * @throws IOException
+     * @throws SshException
+     */
+    public SftpClient getActiveSftpClient() throws IOException {
+        synchronized (activeSftpClients) {
+            if (activeSftpClients.size() > 0) {
+                return (SftpClient) activeSftpClients.get(0);
+            } else {
+                throw new SshException("There are no active SFTP clients");
+            }
+        }
+    }
+
+    /**
+     * <p>
+     * Open an SCP client for file transfer operations where SFTP is not
+     * supported.
+     * </p>
+     *
+     * <p>
+     * Sets the local working directory to the user's home directory
+     * </p>
+     * <blockquote><pre>
+     * ScpClient scp = ssh.openScpClient();
+     * scp.put("somefile.txt");
+     * </pre></blockquote>
+     *
+     * @return An initialized SCP client
+     *
+     * @exception IOException If an IO error occurs during the operation
+     *
+     * @see ScpClient
+     * @since 0.2.0
+     */
+    public ScpClient openScpClient() throws IOException {
+        return new ScpClient(new File(System.getProperty("user.home")), this,
+            false, activeChannelListener);
+    }
+
+    /**
+     * <p>
+     * Open an SCP client for file transfer operations where SFTP is not
+     * supported.
+     * </p>
+     *
+     * <p>
+     * This method sets a local current working directory.
+     * </p>
+     * <blockquote><pre>
+     * ScpClient scp = ssh.openScpClient("foo");
+     * scp.put("somefile.txt");
+     * </pre></blockquote>
+     *
+     * @param cwd The local directory as the base for all local files
+     *
+     * @return An intialized SCP client
+     *
+     * @exception IOException If an IO error occurs during the operation
+     *
+     * @see SftpClient
+     * @since 0.2.0
+     */
+    public ScpClient openScpClient(File cwd) throws IOException {
+        return new ScpClient(cwd, this, false, activeChannelListener);
+    }
+
+    /**
+     * <p>
+     * Open's an Sftp channel.
+     * </p>
+     *
+     * <p>
+     * Use this sftp channel if you require a lower level api into the SFTP
+     * protocol.
+     * </p>
+     *
+     * @return an initialized sftp subsystem instance
+     *
+     * @throws IOException if an IO error occurs or the channel cannot be
+     *         opened
+     *
+     * @since 0.2.0
+     */
+    public SftpSubsystemClient openSftpChannel() throws IOException {
+        return openSftpChannel(null);
+    }
+
+    /**
+     * Open an SftpSubsystemChannel. For advanced use only
+     *
+     * @param eventListener
+     *
+     * @return
+     *
+     * @throws IOException
+     * @throws SshException
+     */
+    public SftpSubsystemClient openSftpChannel(
+        ChannelEventListener eventListener) throws IOException {
+        SessionChannelClient session = openSessionChannel(eventListener);
+        SftpSubsystemClient sftp = new SftpSubsystemClient();
+
+        if (!openChannel(sftp)) {
+            throw new SshException("The SFTP subsystem failed to start");
+        }
+
+        // Initialize SFTP
+        if (!sftp.initialize()) {
+            throw new SshException(
+                "The SFTP Subsystem could not be initialized");
+        }
+
+        return sftp;
+    }
+
+    /**
+     * <p>
+     * Open's a channel.
+     * </p>
+     *
+     * <p>
+     * Call this method to open a custom channel. This method is used by all
+     * other channel opening methods. For example the openSessionChannel
+     * method could be implemented as:<br>
+     * <blockquote><pre>
+     * SessionChannelClient session =
+     *                 new SessionChannelClient();
+     * if(ssh.openChannel(session)) {
+     *    // Channel is now open
+     * }
+     * </pre></blockquote>
+     * </p>
+     *
+     * @param channel
+     *
+     * @return true if the channel was opened, otherwise false
+     *
+     * @exception IOException if an IO error occurs
+     * @throws SshException
+     *
+     * @since 0.2.0
+     */
+    public boolean openChannel(Channel channel) throws IOException {
+        if (authenticationState != AuthenticationProtocolState.COMPLETE) {
+            throw new SshException("Authentication has not been completed!");
+        }
+
+        // Open the channel providing our channel listener so we can track
+        return connection.openChannel(channel, activeChannelListener);
+    }
+
+    /**
+     * <p>
+     * Instructs the underlying connection protocol to allow channels of the
+     * given type to be opened by the server.
+     * </p>
+     *
+     * <p>
+     * The client does not allow channels to be opened by default. Call this
+     * method to allow the server to open channels by providing a
+     * <code>ChannelFactory</code> implementation to create instances upon
+     * request.
+     * </p>
+     *
+     * @param channelName The channel type name
+     * @param cf The factory implementation that will create instances of the
+     *        channel when a channel open request is recieved.
+     *
+     * @exception IOException if an IO error occurs
+     *
+     * @since 0.2.0
+     */
+    public void allowChannelOpen(String channelName, ChannelFactory cf)
+        throws IOException {
+        connection.addChannelFactory(channelName, cf);
+    }
+
+    /**
+     * <p>
+     * Stops the specified channel type from being opended.
+     * </p>
+     *
+     * @param channelName The channel type name
+     *
+     * @throws IOException if an IO error occurs
+     *
+     * @since 0.2.1
+     */
+    public void denyChannelOpen(String channelName) throws IOException {
+        connection.removeChannelFactory(channelName);
+    }
+
+    /**
+     * <p>
+     * Send a global request to the server.
+     * </p>
+     *
+     * <p>
+     * The SSH specification provides a global request mechanism which is used
+     * for starting/stopping remote forwarding. This is a general mechanism
+     * which can be used for other purposes if the server supports the global
+     * requests.
+     * </p>
+     *
+     * @param requestName The name of the global request
+     * @param wantReply true if the server should send an explict reply
+     * @param requestData the global request data
+     *
+     * @return true if the global request succeeded or wantReply==false,
+     *         otherwise false
+     *
+     * @throws IOException if an IO error occurs
+     *
+     * @since 0.2.0
+     */
+    public byte[] sendGlobalRequest(String requestName, boolean wantReply,
+        byte[] requestData) throws IOException {
+        return connection.sendGlobalRequest(requestName, wantReply, requestData);
+    }
+
+    /**
+     * <p>
+     * Implements the <code>ChannelEventListener</code> interface to provide
+     * real time tracking of active channels.
+     * </p>
+     */
+    class ActiveChannelEventListener extends ChannelEventAdapter {
+        /**
+         * <p>
+         * Adds the channel to the active channel list.
+         * </p>
+         *
+         * @param channel The channel being opened
+         */
+        public void onChannelOpen(Channel channel) {
+            synchronized (activeChannels) {
+                activeChannels.add(channel);
+            }
+        }
+
+        /**
+         * <p>
+         * Removes the closed channel from the clients active channels list.
+         * </p>
+         *
+         * @param channel The channle being closed
+         */
+        public void onChannelClose(Channel channel) {
+            synchronized (activeChannels) {
+                activeChannels.remove(channel);
+            }
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/SshEventAdapter.java b/src/com/sshtools/j2ssh/SshEventAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..4add19d7d81f2c2e38380bff88a073b4976f89ab
--- /dev/null
+++ b/src/com/sshtools/j2ssh/SshEventAdapter.java
@@ -0,0 +1,76 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh;
+
+import com.sshtools.j2ssh.authentication.AuthenticationProtocolListener;
+import com.sshtools.j2ssh.transport.TransportProtocol;
+import com.sshtools.j2ssh.transport.TransportProtocolEventHandler;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.8 $
+ */
+public class SshEventAdapter implements TransportProtocolEventHandler,
+    AuthenticationProtocolListener {
+    /**
+     * Creates a new SshEventAdapter object.
+     */
+    public SshEventAdapter() {
+    }
+
+    /**
+     *
+     *
+     * @param transport
+     */
+    public void onSocketTimeout(TransportProtocol transport) {
+    }
+
+    /**
+     *
+     *
+     * @param transport
+     */
+    public void onDisconnect(TransportProtocol transport) {
+    }
+
+    /**
+     *
+     *
+     * @param transport
+     */
+    public void onConnected(TransportProtocol transport) {
+    }
+
+    /**
+     *
+     */
+    public void onAuthenticationComplete() {
+    }
+}
diff --git a/src/com/sshtools/j2ssh/SshException.java b/src/com/sshtools/j2ssh/SshException.java
new file mode 100644
index 0000000000000000000000000000000000000000..90ab152672044ebb34845b34100431e9e7a7153d
--- /dev/null
+++ b/src/com/sshtools/j2ssh/SshException.java
@@ -0,0 +1,68 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh;
+
+import java.io.*;
+
+
+/**
+ * <p>
+ * The base exception for all exceptions thrown within the J2SSH application
+ * framework.
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Revision: 1.20 $
+ *
+ * @since 0.2.0
+ */
+public class SshException extends IOException {
+    /**
+     * <p>
+     * Constructs an exception.
+     * </p>
+     *
+     * @param msg The error message
+     *
+     * @since 0.2.0
+     */
+    public SshException(String msg) {
+        super(msg);
+    }
+
+    /**
+     * <p>
+     * Constructs an exception.
+     * </p>
+     *
+     * @param cause The cause of the exception
+     *
+     * @since 0.2.1
+     */
+    public SshException(Throwable cause) {
+        super(cause.getMessage());
+    }
+}
diff --git a/src/com/sshtools/j2ssh/SshRuntimeException.java b/src/com/sshtools/j2ssh/SshRuntimeException.java
new file mode 100644
index 0000000000000000000000000000000000000000..ad2143bf0e8210cd4b7534340cea889173f9adb7
--- /dev/null
+++ b/src/com/sshtools/j2ssh/SshRuntimeException.java
@@ -0,0 +1,52 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh;
+
+
+/**
+ * <p>
+ * Runtime exception's thrown by the J2SSH application framework.
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Revision: 1.14 $
+ *
+ * @since 0.2.0
+ */
+public class SshRuntimeException extends RuntimeException {
+    /**
+     * <p>
+     * Constructs a runtime exception.
+     * </p>
+     *
+     * @param msg the error message
+     *
+     * @since 0.2.0
+     */
+    public SshRuntimeException(String msg) {
+        super(msg);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/SshThread.java b/src/com/sshtools/j2ssh/SshThread.java
new file mode 100644
index 0000000000000000000000000000000000000000..c0e55c402d8c3d7b1fdf8c893226fff5959a23d3
--- /dev/null
+++ b/src/com/sshtools/j2ssh/SshThread.java
@@ -0,0 +1,312 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh;
+
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+
+import java.util.HashMap;
+
+
+/**
+ * <p>
+ * Enables the J2SSH application framework to execute threads in the context of
+ * a given session.
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Revision: 1.25 $
+ *
+ * @since 0.2.0
+ */
+public class SshThread extends Thread {
+    private static HashMap names = new HashMap();
+
+    /** The raw session id generating during the first key exchange. */
+    protected byte[] sessionId;
+
+    /** A string representation of the session id. */
+    protected String sessionIdString = null;
+
+    /** The thread owner */
+    protected String username;
+
+    /** The thread properties */
+    private HashMap settings = new HashMap();
+
+    /**
+     * <p>
+     * Constructs an SshThread.
+     * </p>
+     *
+     * @param target The target to execute
+     * @param name The name of the thread
+     * @param daemon run as a daemon thread?
+     *
+     * @since 0.2.0
+     */
+    public SshThread(Runnable target, String name, boolean daemon) {
+        super(target);
+        setProperties(name, daemon);
+    }
+
+    public SshThread(String name, boolean daemon) {
+        setProperties(name, daemon);
+    }
+
+    private void setProperties(String name, boolean daemon) {
+        Integer i;
+
+        if (names.containsKey(name)) {
+            i = new Integer(((Integer) names.get(name)).intValue() + 1);
+        } else {
+            i = new Integer(1);
+        }
+
+        names.put(name, i);
+        setName(name + " " + Integer.toHexString(i.intValue() & 0xFF));
+        setDaemon(daemon);
+
+        if (ConfigurationLoader.isContextClassLoader()) {
+            setContextClassLoader(ConfigurationLoader.getContextClassLoader());
+        }
+    }
+
+    /**
+     * <p>
+     * Sets the session id for this thread.
+     * </p>
+     *
+     * @param sessionId the session id created during the first key exchange.
+     *
+     * @since 0.2.0
+     */
+    public void setSessionId(byte[] sessionId) {
+        if (sessionId != null) {
+            this.sessionId = new byte[sessionId.length];
+            System.arraycopy(sessionId, 0, this.sessionId, 0, sessionId.length);
+            sessionIdString = String.valueOf(new String(sessionId).hashCode() &
+                    0xFFFFFFFFL);
+        }
+    }
+
+    /**
+     * <p>
+     * Returns the session id string for this thread.
+     * </p>
+     *
+     * @return a string representation of the session id
+     *
+     * @since 0.2.0
+     */
+    public String getSessionIdString() {
+        return sessionIdString;
+    }
+
+    /**
+     * <p>
+     * Set the username for this thread.
+     * </p>
+     *
+     * @param username the thread owner
+     *
+     * @since 0.2.0
+     */
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    /**
+     * <p>
+     * Gets the username for this thread.
+     * </p>
+     *
+     * @return the thread owner
+     *
+     * @since 0.2.0
+     */
+    public String getUsername() {
+        return username;
+    }
+
+    /**
+     * <p>
+     * Create's a cloned copy of this thread with the given target and name.
+     * </p>
+     *
+     * @param target the target to execute
+     * @param name the thread name
+     *
+     * @return the cloned thread
+     *
+     * @since 0.2.0
+     */
+    public SshThread cloneThread(Runnable target, String name) {
+        SshThread thread = new SshThread(target, name, isDaemon());
+        thread.setSessionId(sessionId);
+        thread.setUsername(username);
+        thread.settings.putAll(settings);
+
+        return thread;
+    }
+
+    /**
+     * <p>
+     * Sets a property in the thread.
+     * </p>
+     *
+     * @param name the name of the property
+     * @param value the property value
+     *
+     * @since 0.2.0
+     */
+    public void setProperty(String name, Object value) {
+        settings.put(name, value);
+    }
+
+    /**
+     * <p>
+     * Gets a property from this thread.
+     * </p>
+     *
+     * @param name the name of the property
+     *
+     * @return the property value
+     *
+     * @since 0.2.0
+     */
+    public Object getProperty(String name) {
+        return settings.get(name);
+    }
+
+    /**
+     * <p>
+     * Determine if this thread contains the given property.
+     * </p>
+     *
+     * @param name the name of the property
+     *
+     * @return true if the property exists, otherwise false
+     *
+     * @since 0.2.0
+     */
+    public boolean containsProperty(String name) {
+        return settings.containsKey(name);
+    }
+
+    /**
+     * <p>
+     * Call to determine the username of the current thread context.
+     * </p>
+     *
+     * <p>
+     * This should be called when the caller is certain that the current thread
+     * is running in an <code>SshThread</code> context. If not a runtime
+     * exception is thrown.
+     * </p>
+     *
+     * @return the owner of the current thread
+     *
+     * @throws SshRuntimeException if the current thread is not an
+     *         <code>SshThread</code>
+     *
+     * @since 0.2.0
+     */
+    public static String getCurrentThreadUser() throws SshRuntimeException {
+        String username;
+
+        if (Thread.currentThread() instanceof SshThread) {
+            return ((SshThread) Thread.currentThread()).getUsername();
+        } else {
+            throw new SshRuntimeException(
+                "The current thread is not running within an SshThread context");
+        }
+    }
+
+    public static boolean hasUserContext() {
+        if (Thread.currentThread() instanceof SshThread) {
+            return ((SshThread) Thread.currentThread()).getUsername() != null;
+        } else {
+            throw new SshRuntimeException(
+                "The current thread is not running within an SshThread context");
+        }
+    }
+
+    /**
+     * <p>
+     * Returns the session id of the current thread context.
+     * </p>
+     *
+     * <p>
+     * This should be called when the caller is certain that the current thread
+     * is running in an <code>SshThread</code> context. If not a Runtime
+     * exception is thrown.
+     * </p>
+     *
+     * @return the session id of the current thread
+     *
+     * @throws SshRuntimeException if the current thread is not an
+     *         <code>SshThread</code>
+     *
+     * @since 0.2.0
+     */
+    public static String getCurrentSessionId() throws SshRuntimeException {
+        String username;
+
+        if (Thread.currentThread() instanceof SshThread) {
+            return ((SshThread) Thread.currentThread()).getSessionIdString();
+        } else {
+            throw new SshRuntimeException(
+                "The current thread is not running within an SshThread context");
+        }
+    }
+
+    /**
+     * <p>
+     * Returns the current <code>SshThread</code>.
+     * </p>
+     *
+     * <p>
+     * This should be called when the caller is certain that the current thread
+     * is running in an <code>SshThread</code> context. If not a Runtime
+     * exception is thrown.
+     * </p>
+     *
+     * @return the current <code>SshThread</code>
+     *
+     * @throws SshRuntimeException if the current thread is not an
+     *         <code>SshThread</code>
+     *
+     * @since 0.2.0
+     */
+    public static SshThread getCurrentThread() throws SshRuntimeException {
+        if (Thread.currentThread() instanceof SshThread) {
+            return (SshThread) Thread.currentThread();
+        } else {
+            throw new SshRuntimeException(
+                "The current thread is not an SshThread");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/TransferCancelledException.java b/src/com/sshtools/j2ssh/TransferCancelledException.java
new file mode 100644
index 0000000000000000000000000000000000000000..064111b3f4cf2b3b8bb0cf179d96bbdc4d87f121
--- /dev/null
+++ b/src/com/sshtools/j2ssh/TransferCancelledException.java
@@ -0,0 +1,58 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh;
+
+import java.io.*;
+
+
+/**
+ * <p>
+ * Title:
+ * </p>
+ *
+ * <p>
+ * Description:
+ * </p>
+ *
+ * <p>
+ * Copyright: Copyright (c) 2003
+ * </p>
+ *
+ * <p>
+ * Company:
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Id: TransferCancelledException.java,v 1.9 2003/09/11 15:35:00 martianx Exp $
+ */
+public class TransferCancelledException extends IOException {
+    /**
+     * Creates a new TransferCancelledException object.
+     */
+    public TransferCancelledException() {
+        super();
+    }
+}
diff --git a/src/com/sshtools/j2ssh/agent/AgentAuthenticationClient.java b/src/com/sshtools/j2ssh/agent/AgentAuthenticationClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..848bca0099c40272c1000d30de778d4afc6922b7
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/AgentAuthenticationClient.java
@@ -0,0 +1,282 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+import com.sshtools.j2ssh.SshClient;
+import com.sshtools.j2ssh.authentication.AuthenticationProtocolClient;
+import com.sshtools.j2ssh.authentication.AuthenticationProtocolException;
+import com.sshtools.j2ssh.authentication.AuthenticationProtocolState;
+import com.sshtools.j2ssh.authentication.PublicKeyAuthenticationClient;
+import com.sshtools.j2ssh.authentication.SshAuthenticationClient;
+import com.sshtools.j2ssh.authentication.SshMsgUserAuthPKOK;
+import com.sshtools.j2ssh.authentication.SshMsgUserAuthRequest;
+import com.sshtools.j2ssh.authentication.TerminatedStateException;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.SshMessage;
+import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.awt.Component;
+
+import java.io.IOException;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+
+
+/**
+ * <p>
+ * Provides an application with an authentication mechanism that links to the
+ * sshtools agent; the agent stores private keys and can hash and sign data
+ * for the public key authentication request.
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Revision: 1.19 $
+ */
+public class AgentAuthenticationClient extends SshAuthenticationClient {
+    private static Log log = LogFactory.getLog(PublicKeyAuthenticationClient.class);
+
+    /**  */
+    protected SshAgentClient agent;
+
+    /**
+     * Creates a new AgentAuthenticationClient object.
+     */
+    public AgentAuthenticationClient() {
+    }
+
+    /*public void setKey(SshPublicKey key) {
+        this.key = key;
+         }*/
+    public void setAgent(SshAgentClient agent) {
+        this.agent = agent;
+    }
+
+    /**
+     *
+     */
+    public void reset() {
+        agent = null;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMethodName() {
+        return "publickey";
+    }
+
+    /**
+     *
+     *
+     * @param authentication
+     * @param username
+     * @param serviceToStart
+     * @param key
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public boolean acceptsKey(AuthenticationProtocolClient authentication,
+        String username, String serviceToStart, SshPublicKey key)
+        throws IOException {
+        authentication.registerMessage(SshMsgUserAuthPKOK.class,
+            SshMsgUserAuthPKOK.SSH_MSG_USERAUTH_PK_OK);
+        log.info(
+            "Determining if server can accept public key for authentication");
+
+        ByteArrayWriter baw = new ByteArrayWriter();
+
+        // Now prepare and send the message
+        baw.write(0);
+        baw.writeString(key.getAlgorithmName());
+        baw.writeBinaryString(key.getEncoded());
+
+        SshMessage msg = new SshMsgUserAuthRequest(username, serviceToStart,
+                getMethodName(), baw.toByteArray());
+        authentication.sendMessage(msg);
+
+        try {
+            msg = authentication.readMessage(SshMsgUserAuthPKOK.SSH_MSG_USERAUTH_PK_OK);
+
+            if (msg instanceof SshMsgUserAuthPKOK) {
+                return true;
+            } else {
+                throw new IOException(
+                    "Unexpected message returned from readMessage");
+            }
+        } catch (TerminatedStateException ex) {
+            return false;
+        }
+    }
+
+    /**
+     *
+     *
+     * @param authentication
+     * @param serviceToStart
+     *
+     * @throws IOException
+     * @throws TerminatedStateException
+     * @throws AuthenticationProtocolException
+     */
+    public void authenticate(AuthenticationProtocolClient authentication,
+        String serviceToStart) throws IOException, TerminatedStateException {
+        if ((getUsername() == null) || (agent == null)) {
+            throw new AuthenticationProtocolException(
+                "You must supply a username and agent");
+        }
+
+        // Iterate the agents keys, find an acceptable key and authenticate
+        Map keys = agent.listKeys();
+        Iterator it = keys.entrySet().iterator();
+        boolean acceptable = false;
+        SshPublicKey key = null;
+        String description;
+        Map.Entry entry;
+
+        while (it.hasNext() && !acceptable) {
+            entry = (Map.Entry) it.next();
+            key = (SshPublicKey) entry.getKey();
+            description = (String) entry.getValue();
+            acceptable = acceptsKey(authentication, getUsername(),
+                    serviceToStart, key);
+            log.info("Agent authentication with key " + key.getFingerprint() +
+                " [" + description + "] is " +
+                (acceptable ? " acceptable" : " not acceptable"));
+
+            if (acceptable) {
+                ByteArrayWriter baw = new ByteArrayWriter();
+                log.info("Generating data to sign");
+                log.info("Preparing public key authentication request");
+
+                // Now prepare and send the message
+                baw.write(1);
+                baw.writeString(key.getAlgorithmName());
+                baw.writeBinaryString(key.getEncoded());
+
+                // Create the signature data
+                ByteArrayWriter data = new ByteArrayWriter();
+                data.writeBinaryString(authentication.getSessionIdentifier());
+                data.write(SshMsgUserAuthRequest.SSH_MSG_USERAUTH_REQUEST);
+                data.writeString(getUsername());
+                data.writeString(serviceToStart);
+                data.writeString(getMethodName());
+                data.write(1);
+                data.writeString(key.getAlgorithmName());
+                data.writeBinaryString(key.getEncoded());
+
+                // Generate the signature
+                baw.writeBinaryString(agent.hashAndSign(key, data.toByteArray()));
+
+                SshMsgUserAuthRequest msg = new SshMsgUserAuthRequest(getUsername(),
+                        serviceToStart, getMethodName(), baw.toByteArray());
+                authentication.sendMessage(msg);
+
+                try {
+                    authentication.readAuthenticationState();
+                } catch (TerminatedStateException ex) {
+                    if (ex.getState() == AuthenticationProtocolState.COMPLETE) {
+                        throw ex;
+                    }
+                }
+            }
+        }
+
+        throw new TerminatedStateException(AuthenticationProtocolState.FAILED);
+    }
+
+    /**
+     *
+     *
+     * @param parent
+     *
+     * @return
+     */
+    public boolean showAuthenticationDialog(Component parent) {
+        return false;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public Properties getPersistableProperties() {
+        Properties properties = new Properties();
+
+        return properties;
+    }
+
+    /**
+     *
+     *
+     * @param properties
+     */
+    public void setPersistableProperties(Properties properties) {
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean canAuthenticate() {
+        return ((agent != null) && (getUsername() != null));
+    }
+
+    /**
+     *
+     *
+     * @param ssh
+     *
+     * @return
+     */
+    public boolean hasAcceptableKey(SshClient ssh) {
+        try {
+            Map keys = agent.listKeys();
+            SshPublicKey key;
+
+            for (Iterator x = keys.keySet().iterator(); x.hasNext();) {
+                key = (SshPublicKey) x.next();
+
+                if (ssh.acceptsKey(getUsername(), key)) {
+                    return true;
+                }
+            }
+        } catch (IOException ex) {
+        }
+
+        return false;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/agent/AgentNotAvailableException.java b/src/com/sshtools/j2ssh/agent/AgentNotAvailableException.java
new file mode 100644
index 0000000000000000000000000000000000000000..34b674c0b9ba04823a5a387da348b07beeb23a4a
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/AgentNotAvailableException.java
@@ -0,0 +1,42 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class AgentNotAvailableException extends Exception {
+    /**
+     * Creates a new AgentNotAvailableException object.
+     */
+    public AgentNotAvailableException() {
+        super("An agent could not be found");
+    }
+}
diff --git a/src/com/sshtools/j2ssh/agent/AgentSocketChannel.java b/src/com/sshtools/j2ssh/agent/AgentSocketChannel.java
new file mode 100644
index 0000000000000000000000000000000000000000..c72dc106efb7a6f6b2b5eb5f1e3f4abd77d382c3
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/AgentSocketChannel.java
@@ -0,0 +1,158 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+import com.sshtools.j2ssh.connection.InvalidChannelException;
+import com.sshtools.j2ssh.connection.SocketChannel;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+
+import java.io.IOException;
+
+import java.net.InetAddress;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.11 $
+ */
+public class AgentSocketChannel extends SocketChannel {
+    /**  */
+    public static final String AGENT_FORWARDING_CHANNEL = "auth-agent";
+
+    //protected Socket socket = null;
+    private boolean isForwarding;
+
+    /**
+     * Creates a new AgentSocketChannel object.
+     *
+     * @param isForwarding
+     */
+    public AgentSocketChannel(boolean isForwarding) {
+        this.isForwarding = isForwarding;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getChannelType() {
+        return AGENT_FORWARDING_CHANNEL;
+    }
+
+    /*public void bindSocket(Socket socket) throws IOException {
+       this.socket = socket;
+       if (state.getValue() == ChannelState.CHANNEL_OPEN) {
+         bindInputStream(socket.getInputStream());
+         bindOutputStream(socket.getOutputStream());
+       }
+     }*/
+    protected void onChannelRequest(String requestType, boolean wantReply,
+        byte[] requestData) throws java.io.IOException {
+        if (wantReply) {
+            connection.sendChannelRequestFailure(this);
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected int getMaximumPacketSize() {
+        return 32678;
+    }
+
+    /*protected void onChannelClose() throws java.io.IOException {
+     }
+     protected void onChannelEOF() throws IOException {
+     }*/
+    public byte[] getChannelOpenData() {
+        return null;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected int getMinimumWindowSpace() {
+        return 1024;
+    }
+
+    /**
+     *
+     *
+     * @throws com.sshtools.j2ssh.connection.InvalidChannelException DOCUMENT
+     *         ME!
+     * @throws InvalidChannelException
+     */
+    protected void onChannelOpen()
+        throws com.sshtools.j2ssh.connection.InvalidChannelException {
+        try {
+            //if (socket != null) {
+            if (isForwarding) {
+                // Were forwarding so insert the forwarding notice before any other data
+                SshAgentForwardingNotice msg = new SshAgentForwardingNotice(InetAddress.getLocalHost()
+                                                                                       .getHostName(),
+                        InetAddress.getLocalHost().getHostAddress(),
+                        socket.getPort());
+                ByteArrayWriter baw = new ByteArrayWriter();
+                baw.writeBinaryString(msg.toByteArray());
+                sendChannelData(baw.toByteArray());
+            }
+
+            super.onChannelOpen();
+
+            // Now bind the socket to the channel
+            //  bindInputStream(socket.getInputStream());
+            //  bindOutputStream(socket.getOutputStream());
+            //}
+        } catch (IOException ex) {
+            throw new InvalidChannelException(ex.getMessage());
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected int getMaximumWindowSpace() {
+        return 32768;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getChannelConfirmationData() {
+        return null;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/agent/ForwardingNotice.java b/src/com/sshtools/j2ssh/agent/ForwardingNotice.java
new file mode 100644
index 0000000000000000000000000000000000000000..92dcefc40d76470c6d1da0cd90f5ff8bfbc7f423
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/ForwardingNotice.java
@@ -0,0 +1,73 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+class ForwardingNotice {
+    String remoteHostname;
+    String remoteIPAddress;
+    int remotePort;
+
+    /**
+     * Creates a new ForwardingNotice object.
+     *
+     * @param remoteHostname
+     * @param remoteIPAddress
+     * @param remotePort
+     */
+    public ForwardingNotice(String remoteHostname, String remoteIPAddress,
+        int remotePort) {
+        this.remoteHostname = remoteHostname;
+        this.remoteIPAddress = remoteIPAddress;
+        this.remotePort = remotePort;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getRemoteHostname() {
+        return remoteHostname;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getRemoteIPAddress() {
+        return remoteIPAddress;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getRemotePort() {
+        return remotePort;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/agent/KeyConstraints.java b/src/com/sshtools/j2ssh/agent/KeyConstraints.java
new file mode 100644
index 0000000000000000000000000000000000000000..1e218869daf3d53115c5b5635e2b3672c7a6408f
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/KeyConstraints.java
@@ -0,0 +1,292 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class KeyConstraints {
+    /**  */
+    public static final long NO_TIMEOUT = 0;
+
+    /**  */
+    public static final long NO_LIMIT = 0xffffffffL;
+
+    /**  */
+    protected static final int SSH_AGENT_CONSTRAINT_TIMEOUT = 50;
+
+    /**  */
+    protected static final int SSH_AGENT_CONSTRAINT_USE_LIMIT = 51;
+
+    /**  */
+    protected static final int SSH_AGENT_CONSTRAINT_FORWARDING_STEPS = 52;
+
+    /**  */
+    protected static final int SSH_AGENT_CONSTRAINT_FORWARDING_PATH = 100;
+
+    /**  */
+    protected static final int SSH_AGENT_CONSTRAINT_SSH1_COMPAT = 150;
+
+    /**  */
+    protected static final int SSH_AGENT_CONSTRAINT_NEED_USER_VERIFICATION = 151;
+    private UnsignedInteger32 timeout = new UnsignedInteger32(NO_TIMEOUT);
+    private UnsignedInteger32 uselimit = new UnsignedInteger32(NO_LIMIT);
+    private UnsignedInteger32 maxsteps = new UnsignedInteger32(NO_LIMIT);
+    private String forwardingpath = "";
+    private boolean userverify = false;
+    private boolean compat = false;
+    private long keyadded = System.currentTimeMillis();
+    private long usedcount = 0;
+
+    /**
+     * Creates a new KeyConstraints object.
+     */
+    public KeyConstraints() {
+    }
+
+    /**
+     * Creates a new KeyConstraints object.
+     *
+     * @param bar
+     *
+     * @throws IOException
+     */
+    public KeyConstraints(ByteArrayReader bar) throws IOException {
+        while (bar.available() > 0) {
+            switch (bar.read() & 0xFF) {
+            case SSH_AGENT_CONSTRAINT_TIMEOUT:
+                timeout = bar.readUINT32();
+
+                break;
+
+            case SSH_AGENT_CONSTRAINT_USE_LIMIT:
+                uselimit = bar.readUINT32();
+
+                break;
+
+            case SSH_AGENT_CONSTRAINT_FORWARDING_STEPS:
+                maxsteps = bar.readUINT32();
+
+                break;
+
+            case SSH_AGENT_CONSTRAINT_FORWARDING_PATH:
+                forwardingpath = bar.readString();
+
+                break;
+
+            case SSH_AGENT_CONSTRAINT_SSH1_COMPAT:
+                compat = (bar.read() != 0);
+
+                break;
+
+            case SSH_AGENT_CONSTRAINT_NEED_USER_VERIFICATION:
+                userverify = (bar.read() != 0);
+
+                break;
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @param timeout
+     */
+    public void setKeyTimeout(UnsignedInteger32 timeout) {
+        this.timeout = timeout;
+    }
+
+    /**
+     *
+     *
+     * @param uselimit
+     */
+    public void setKeyUseLimit(int uselimit) {
+        this.uselimit = new UnsignedInteger32(uselimit);
+    }
+
+    /**
+     *
+     *
+     * @param maxsteps
+     */
+    public void setMaximumForwardingSteps(int maxsteps) {
+        this.maxsteps = new UnsignedInteger32(maxsteps);
+    }
+
+    /**
+     *
+     *
+     * @param forwardingpath
+     */
+    public void setForwardingPath(String forwardingpath) {
+        this.forwardingpath = forwardingpath;
+    }
+
+    /**
+     *
+     *
+     * @param userverify
+     */
+    public void setRequiresUserVerification(boolean userverify) {
+        this.userverify = userverify;
+    }
+
+    /**
+     *
+     *
+     * @param compat
+     */
+    public void setSSH1Compatible(boolean compat) {
+        this.compat = compat;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getKeyTimeout() {
+        return timeout.longValue();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getKeyUseLimit() {
+        return uselimit.longValue();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getMaximumForwardingSteps() {
+        return maxsteps.longValue();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getUsedCount() {
+        return usedcount;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean hasTimedOut() {
+        return (timeout.longValue() != 0)
+        ? (((System.currentTimeMillis() - keyadded) / 1000) > timeout.longValue())
+        : false;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean canUse() {
+        return (uselimit.longValue() != 0) ? (usedcount < uselimit.longValue())
+                                           : true;
+    }
+
+    /**
+     *
+     */
+    public void use() {
+        usedcount++;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getForwardingPath() {
+        return forwardingpath;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean requiresUserVerification() {
+        return userverify;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isSSH1Compatible() {
+        return compat;
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public byte[] toByteArray() throws IOException {
+        ByteArrayWriter baw = new ByteArrayWriter();
+        baw.write(SSH_AGENT_CONSTRAINT_TIMEOUT);
+        baw.writeUINT32(timeout);
+        baw.write(SSH_AGENT_CONSTRAINT_USE_LIMIT);
+        baw.writeUINT32(uselimit);
+        baw.write(SSH_AGENT_CONSTRAINT_FORWARDING_STEPS);
+        baw.writeUINT32(maxsteps);
+        baw.write(SSH_AGENT_CONSTRAINT_FORWARDING_PATH);
+        baw.writeString(forwardingpath);
+        baw.write(SSH_AGENT_CONSTRAINT_SSH1_COMPAT);
+        baw.write(compat ? 0 : 1);
+        baw.write(SSH_AGENT_CONSTRAINT_NEED_USER_VERIFICATION);
+        baw.write(userverify ? 0 : 1);
+
+        return baw.toByteArray();
+    }
+}
diff --git a/src/com/sshtools/j2ssh/agent/KeyStore.java b/src/com/sshtools/j2ssh/agent/KeyStore.java
new file mode 100644
index 0000000000000000000000000000000000000000..470531b7425e1229d72bc873d9d34590266d1361
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/KeyStore.java
@@ -0,0 +1,339 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+import com.sshtools.j2ssh.transport.publickey.*;
+
+import org.apache.commons.logging.*;
+
+import java.io.*;
+
+import java.util.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class KeyStore {
+    private static Log log = LogFactory.getLog(KeyStore.class);
+    HashMap publickeys = new HashMap();
+    HashMap privatekeys = new HashMap();
+    HashMap constraints = new HashMap();
+    Vector index = new Vector();
+    Vector listeners = new Vector();
+    String lockedPassword = null;
+
+    /**
+     * Creates a new KeyStore object.
+     */
+    public KeyStore() {
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public Map getPublicKeys() {
+        return (Map) publickeys.clone();
+    }
+
+    /**
+     *
+     *
+     * @param key
+     *
+     * @return
+     */
+    public int indexOf(SshPublicKey key) {
+        return index.indexOf(key);
+    }
+
+    /**
+     *
+     *
+     * @param i
+     *
+     * @return
+     */
+    public SshPublicKey elementAt(int i) {
+        return (SshPublicKey) index.elementAt(i);
+    }
+
+    /**
+     *
+     *
+     * @param key
+     *
+     * @return
+     */
+    public String getDescription(SshPublicKey key) {
+        return (String) publickeys.get(key);
+    }
+
+    /**
+     *
+     *
+     * @param key
+     *
+     * @return
+     */
+    public KeyConstraints getKeyConstraints(SshPublicKey key) {
+        return (KeyConstraints) constraints.get(key);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int size() {
+        return index.size();
+    }
+
+    /**
+     *
+     *
+     * @param listener
+     */
+    public void addKeyStoreListener(KeyStoreListener listener) {
+        listeners.add(listener);
+    }
+
+    /**
+     *
+     *
+     * @param listener
+     */
+    public void removeKeyStoreListener(KeyStoreListener listener) {
+        listeners.remove(listener);
+    }
+
+    /**
+     *
+     *
+     * @param prvkey
+     * @param pubkey
+     * @param description
+     * @param cs
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public boolean addKey(SshPrivateKey prvkey, SshPublicKey pubkey,
+        String description, KeyConstraints cs) throws IOException {
+        synchronized (publickeys) {
+            if (!publickeys.containsKey(pubkey)) {
+                publickeys.put(pubkey, description);
+                privatekeys.put(pubkey, prvkey);
+                constraints.put(pubkey, cs);
+                index.add(pubkey);
+
+                Iterator it = listeners.iterator();
+                KeyStoreListener listener;
+
+                while (it.hasNext()) {
+                    listener = (KeyStoreListener) it.next();
+                    listener.onAddKey(this);
+                }
+
+                return true;
+            } else {
+                return false;
+            }
+        }
+    }
+
+    /**
+     *
+     */
+    public void deleteAllKeys() {
+        synchronized (publickeys) {
+            publickeys.clear();
+            privatekeys.clear();
+            constraints.clear();
+            index.clear();
+
+            Iterator it = listeners.iterator();
+            KeyStoreListener listener;
+
+            while (it.hasNext()) {
+                listener = (KeyStoreListener) it.next();
+                listener.onDeleteAllKeys(this);
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @param pubkey
+     * @param forwardingNodes
+     * @param data
+     *
+     * @return
+     *
+     * @throws KeyTimeoutException
+     * @throws InvalidSshKeyException
+     * @throws InvalidSshKeySignatureException
+     */
+    public byte[] performHashAndSign(SshPublicKey pubkey, List forwardingNodes,
+        byte[] data)
+        throws KeyTimeoutException, InvalidSshKeyException, 
+            InvalidSshKeySignatureException {
+        synchronized (publickeys) {
+            if (privatekeys.containsKey(pubkey)) {
+                SshPrivateKey key = (SshPrivateKey) privatekeys.get(pubkey);
+                KeyConstraints cs = (KeyConstraints) constraints.get(pubkey);
+
+                if (cs.canUse()) {
+                    if (!cs.hasTimedOut()) {
+                        cs.use();
+
+                        byte[] sig = key.generateSignature(data);
+                        Iterator it = listeners.iterator();
+                        KeyStoreListener listener;
+
+                        while (it.hasNext()) {
+                            listener = (KeyStoreListener) it.next();
+                            listener.onKeyOperation(this, "hash-and-sign");
+                        }
+
+                        return sig;
+                    } else {
+                        throw new KeyTimeoutException();
+                    }
+                } else {
+                    throw new KeyTimeoutException();
+                }
+            } else {
+                throw new InvalidSshKeyException("The key does not exist");
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @param pubkey
+     * @param description
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public boolean deleteKey(SshPublicKey pubkey, String description)
+        throws IOException {
+        synchronized (publickeys) {
+            if (publickeys.containsKey(pubkey)) {
+                String desc = (String) publickeys.get(pubkey);
+
+                if (description.equals(desc)) {
+                    publickeys.remove(pubkey);
+                    privatekeys.remove(pubkey);
+                    constraints.remove(pubkey);
+                    index.remove(pubkey);
+
+                    Iterator it = listeners.iterator();
+                    KeyStoreListener listener;
+
+                    while (it.hasNext()) {
+                        listener = (KeyStoreListener) it.next();
+                        listener.onDeleteKey(this);
+                    }
+
+                    return true;
+                }
+            }
+
+            return false;
+        }
+    }
+
+    /**
+     *
+     *
+     * @param password
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public boolean lock(String password) throws IOException {
+        synchronized (publickeys) {
+            if (lockedPassword == null) {
+                lockedPassword = password;
+
+                Iterator it = listeners.iterator();
+                KeyStoreListener listener;
+
+                while (it.hasNext()) {
+                    listener = (KeyStoreListener) it.next();
+                    listener.onLock(this);
+                }
+
+                return true;
+            } else {
+                return false;
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @param password
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public boolean unlock(String password) throws IOException {
+        synchronized (publickeys) {
+            if (lockedPassword != null) {
+                if (password.equals(lockedPassword)) {
+                    lockedPassword = null;
+
+                    Iterator it = listeners.iterator();
+                    KeyStoreListener listener;
+
+                    while (it.hasNext()) {
+                        listener = (KeyStoreListener) it.next();
+                        listener.onUnlock(this);
+                    }
+
+                    return true;
+                }
+            }
+
+            return false;
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/agent/KeyStoreListener.java b/src/com/sshtools/j2ssh/agent/KeyStoreListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..ad72c27311252a1e413ec090989b74343220d0b1
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/KeyStoreListener.java
@@ -0,0 +1,78 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public interface KeyStoreListener {
+    /**
+     *
+     *
+     * @param keystore
+     */
+    public void onDeleteKey(KeyStore keystore);
+
+    /**
+     *
+     *
+     * @param keystore
+     */
+    public void onLock(KeyStore keystore);
+
+    /**
+     *
+     *
+     * @param keystore
+     */
+    public void onUnlock(KeyStore keystore);
+
+    /**
+     *
+     *
+     * @param keystore
+     */
+    public void onAddKey(KeyStore keystore);
+
+    /**
+     *
+     *
+     * @param keystore
+     */
+    public void onDeleteAllKeys(KeyStore keystore);
+
+    /**
+     *
+     *
+     * @param keystore
+     * @param operation
+     */
+    public void onKeyOperation(KeyStore keystore, String operation);
+}
diff --git a/src/com/sshtools/j2ssh/agent/KeyTimeoutException.java b/src/com/sshtools/j2ssh/agent/KeyTimeoutException.java
new file mode 100644
index 0000000000000000000000000000000000000000..907ccc2eab7a18fd418c017e8d6c192e21af9a77
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/KeyTimeoutException.java
@@ -0,0 +1,42 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class KeyTimeoutException extends Exception {
+    /**
+     * Creates a new KeyTimeoutException object.
+     */
+    public KeyTimeoutException() {
+        super("The key has timed out");
+    }
+}
diff --git a/src/com/sshtools/j2ssh/agent/SshAgentAddKey.java b/src/com/sshtools/j2ssh/agent/SshAgentAddKey.java
new file mode 100644
index 0000000000000000000000000000000000000000..cd83642eec485b115e2cebc4be4a374aae5a641e
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/SshAgentAddKey.java
@@ -0,0 +1,161 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.publickey.SshKeyPairFactory;
+import com.sshtools.j2ssh.transport.publickey.SshPrivateKey;
+import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
+
+import java.io.IOException;
+
+
+class SshAgentAddKey extends SubsystemMessage {
+    /**  */
+    public static final int SSH_AGENT_ADD_KEY = 202;
+    SshPrivateKey prvkey;
+    SshPublicKey pubkey;
+    String description;
+    KeyConstraints constraints;
+
+    /**
+     * Creates a new SshAgentAddKey object.
+     */
+    public SshAgentAddKey() {
+        super(SSH_AGENT_ADD_KEY);
+    }
+
+    /**
+     * Creates a new SshAgentAddKey object.
+     *
+     * @param prvkey
+     * @param pubkey
+     * @param description
+     * @param constraints
+     */
+    public SshAgentAddKey(SshPrivateKey prvkey, SshPublicKey pubkey,
+        String description, KeyConstraints constraints) {
+        super(SSH_AGENT_ADD_KEY);
+        this.prvkey = prvkey;
+        this.pubkey = pubkey;
+        this.description = description;
+        this.constraints = constraints;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public SshPrivateKey getPrivateKey() {
+        return prvkey;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public SshPublicKey getPublicKey() {
+        return pubkey;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public KeyConstraints getKeyConstraints() {
+        return constraints;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_AGENT_ADD_KEY";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            baw.writeBinaryString(prvkey.getEncoded());
+            baw.writeBinaryString(pubkey.getEncoded());
+            baw.writeString(description);
+            baw.write(constraints.toByteArray());
+        } catch (IOException ex) {
+            throw new InvalidMessageException(ex.getMessage());
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            prvkey = SshKeyPairFactory.decodePrivateKey(bar.readBinaryString());
+            pubkey = SshKeyPairFactory.decodePublicKey(bar.readBinaryString());
+            description = bar.readString();
+            constraints = new KeyConstraints(bar);
+        } catch (IOException ex) {
+            throw new InvalidMessageException(ex.getMessage());
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/agent/SshAgentAlive.java b/src/com/sshtools/j2ssh/agent/SshAgentAlive.java
new file mode 100644
index 0000000000000000000000000000000000000000..583f690a8024acd3ae19a806a5160ffe2b54cf2e
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/SshAgentAlive.java
@@ -0,0 +1,116 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+
+import java.io.IOException;
+
+
+class SshAgentAlive extends SubsystemMessage {
+    /**  */
+    public static final int SSH_AGENT_ALIVE = 150;
+    private byte[] padding;
+
+    /**
+     * Creates a new SshAgentAlive object.
+     */
+    public SshAgentAlive() {
+        super(SSH_AGENT_ALIVE);
+    }
+
+    /**
+     * Creates a new SshAgentAlive object.
+     *
+     * @param padding
+     */
+    public SshAgentAlive(byte[] padding) {
+        super(SSH_AGENT_ALIVE);
+        this.padding = padding;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getPadding() {
+        return padding;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_AGENT_ALIVE";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            baw.write(padding);
+        } catch (IOException ioe) {
+            throw new InvalidMessageException(ioe.getMessage());
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            padding = new byte[bar.available()];
+            bar.read(padding);
+        } catch (IOException ioe) {
+            throw new InvalidMessageException(ioe.getMessage());
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/agent/SshAgentClient.java b/src/com/sshtools/j2ssh/agent/SshAgentClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..611aafba34537a4b34ff7068894fe082bb5e30c5
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/SshAgentClient.java
@@ -0,0 +1,479 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.publickey.SshPrivateKey;
+import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import java.net.InetAddress;
+import java.net.Socket;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * Provides a client connection to the ssh agent.
+ *
+ * @author $author$
+ * @version $Revision: 1.17 $
+ */
+public class SshAgentClient {
+    private static Log log = LogFactory.getLog(SshAgentClient.class);
+
+    /** The hash and sign private key operation */
+    public static final String HASH_AND_SIGN = "hash-and-sign";
+    InputStream in;
+    OutputStream out;
+    boolean isForwarded = false;
+    HashMap messages = new HashMap();
+    Socket socket;
+
+    SshAgentClient(boolean isForwarded, String application, InputStream in,
+        OutputStream out) throws IOException {
+        log.info("New SshAgentClient instance created");
+        this.in = in;
+        this.out = out;
+        this.isForwarded = isForwarded;
+        registerMessages();
+
+        if (isForwarded) {
+            sendForwardingNotice();
+        } else {
+            sendVersionRequest(application);
+        }
+    }
+
+    SshAgentClient(boolean isForwarded, String application, Socket socket)
+        throws IOException {
+        log.info("New SshAgentClient instance created");
+        this.socket = socket;
+        this.in = socket.getInputStream();
+        this.out = socket.getOutputStream();
+        this.isForwarded = isForwarded;
+        registerMessages();
+
+        if (isForwarded) {
+            sendForwardingNotice();
+        } else {
+            sendVersionRequest(application);
+        }
+    }
+
+    /**
+     * Connect to the local agent.
+     *
+     * @param application the application connecting
+     * @param location the location of the agent, in the form "localhost:port"
+     *
+     * @return a connected agent client
+     *
+     * @throws AgentNotAvailableException if the agent is not available at the
+     *         location specified
+     * @throws IOException if an IO error occurs
+     */
+    public static SshAgentClient connectLocalAgent(String application,
+        String location) throws AgentNotAvailableException, IOException {
+        try {
+            Socket socket = connectAgentSocket(location);
+
+            return new SshAgentClient(false, application, socket);
+        } catch (IOException ex) {
+            throw new AgentNotAvailableException();
+        }
+    }
+
+    /**
+     * Connect a socket to the agent at the location specified.
+     *
+     * @param location the location of the agent, in the form "localhost:port"
+     *
+     * @return the connected socket
+     *
+     * @throws AgentNotAvailableException if an agent is not available at the
+     *         location specified
+     * @throws IOException if an IO error occurs
+     */
+    public static Socket connectAgentSocket(String location)
+        throws AgentNotAvailableException, IOException {
+        try {
+            if (location == null) {
+                throw new AgentNotAvailableException();
+            }
+
+            int idx = location.indexOf(":");
+
+            if (idx == -1) {
+                throw new AgentNotAvailableException();
+            }
+
+            String host = location.substring(0, idx);
+            int port = Integer.parseInt(location.substring(idx + 1));
+            Socket socket = new Socket(host, port);
+
+            return socket;
+        } catch (IOException ex) {
+            throw new AgentNotAvailableException();
+        }
+    }
+
+    /**
+     * Close the agent
+     */
+    public void close() {
+        log.info("Closing agent client");
+
+        try {
+            in.close();
+        } catch (IOException ex) {
+        }
+
+        try {
+            out.close();
+        } catch (IOException ex1) {
+        }
+
+        try {
+            if (socket != null) {
+                socket.close();
+            }
+        } catch (IOException ex2) {
+        }
+    }
+
+    /**
+     * Register the subsystem messages
+     */
+    protected void registerMessages() {
+        messages.put(new Integer(
+                SshAgentVersionResponse.SSH_AGENT_VERSION_RESPONSE),
+            SshAgentVersionResponse.class);
+        messages.put(new Integer(SshAgentSuccess.SSH_AGENT_SUCCESS),
+            SshAgentSuccess.class);
+        messages.put(new Integer(SshAgentFailure.SSH_AGENT_FAILURE),
+            SshAgentFailure.class);
+        messages.put(new Integer(SshAgentKeyList.SSH_AGENT_KEY_LIST),
+            SshAgentKeyList.class);
+        messages.put(new Integer(SshAgentRandomData.SSH_AGENT_RANDOM_DATA),
+            SshAgentRandomData.class);
+        messages.put(new Integer(SshAgentAlive.SSH_AGENT_ALIVE),
+            SshAgentAlive.class);
+        messages.put(new Integer(
+                SshAgentOperationComplete.SSH_AGENT_OPERATION_COMPLETE),
+            SshAgentOperationComplete.class);
+    }
+
+    /**
+     * Request the agent version.
+     *
+     * @param application the application connecting
+     *
+     * @throws IOException if an IO error occurs
+     */
+    protected void sendVersionRequest(String application)
+        throws IOException {
+        SubsystemMessage msg = new SshAgentRequestVersion(application);
+        sendMessage(msg);
+        msg = readMessage();
+
+        if (msg instanceof SshAgentVersionResponse) {
+            SshAgentVersionResponse reply = (SshAgentVersionResponse) msg;
+
+            if (reply.getVersion() != 2) {
+                throw new IOException(
+                    "The agent verison is not compatible with verison 2");
+            }
+        } else {
+            throw new IOException(
+                "The agent did not respond with the appropriate version");
+        }
+    }
+
+    /**
+     * Add a key to the agent
+     *
+     * @param prvkey the private key to add
+     * @param pubkey the private keys public key
+     * @param description a description of the key
+     * @param constraints a set of contraints for key use
+     *
+     * @throws IOException if an IO error occurs
+     */
+    public void addKey(SshPrivateKey prvkey, SshPublicKey pubkey,
+        String description, KeyConstraints constraints)
+        throws IOException {
+        SubsystemMessage msg = new SshAgentAddKey(prvkey, pubkey, description,
+                constraints);
+        sendMessage(msg);
+        msg = readMessage();
+
+        if (!(msg instanceof SshAgentSuccess)) {
+            throw new IOException("The key could not be added");
+        }
+    }
+
+    /**
+     * Request a hash and sign operation be performed for a given public key.
+     *
+     * @param key the public key of the required private key
+     * @param data the data to has and sign
+     *
+     * @return the hashed and signed data
+     *
+     * @throws IOException if an IO error occurs
+     */
+    public byte[] hashAndSign(SshPublicKey key, byte[] data)
+        throws IOException {
+        SubsystemMessage msg = new SshAgentPrivateKeyOp(key, HASH_AND_SIGN, data);
+        sendMessage(msg);
+        msg = readMessage();
+
+        if (msg instanceof SshAgentOperationComplete) {
+            return ((SshAgentOperationComplete) msg).getData();
+        } else {
+            throw new IOException("The operation failed");
+        }
+    }
+
+    /**
+     * List all the keys on the agent.
+     *
+     * @return a map of public keys and descriptions
+     *
+     * @throws IOException if an IO error occurs
+     */
+    public Map listKeys() throws IOException {
+        SubsystemMessage msg = new SshAgentListKeys();
+        sendMessage(msg);
+        msg = readMessage();
+
+        if (msg instanceof SshAgentKeyList) {
+            return ((SshAgentKeyList) msg).getKeys();
+        } else {
+            throw new IOException("The agent responsed with an invalid message");
+        }
+    }
+
+    /**
+     * Lock the agent
+     *
+     * @param password password that will be required to unlock
+     *
+     * @return true if the agent was locked, otherwise false
+     *
+     * @throws IOException if an IO error occurs
+     */
+    public boolean lockAgent(String password) throws IOException {
+        SubsystemMessage msg = new SshAgentLock(password);
+        sendMessage(msg);
+        msg = readMessage();
+
+        return (msg instanceof SshAgentSuccess);
+    }
+
+    /**
+     * Unlock the agent
+     *
+     * @param password the password to unlock
+     *
+     * @return true if the agent was unlocked, otherwise false
+     *
+     * @throws IOException if an IO error occurs
+     */
+    public boolean unlockAgent(String password) throws IOException {
+        SubsystemMessage msg = new SshAgentUnlock(password);
+        sendMessage(msg);
+        msg = readMessage();
+
+        return (msg instanceof SshAgentSuccess);
+    }
+
+    /**
+     * Request some random data from the remote side
+     *
+     * @param count the number of bytes needed
+     *
+     * @return the random data received
+     *
+     * @throws IOException if an IO error occurs
+     */
+    public byte[] getRandomData(int count) throws IOException {
+        SubsystemMessage msg = new SshAgentRandom(count);
+        sendMessage(msg);
+        msg = readMessage();
+
+        if (msg instanceof SshAgentRandomData) {
+            return ((SshAgentRandomData) msg).getRandomData();
+        } else {
+            throw new IOException(
+                "Agent failed to provide the request random data");
+        }
+    }
+
+    /**
+     * Ping the remote side with some random padding data
+     *
+     * @param padding the padding data
+     *
+     * @throws IOException if an IO error occurs
+     */
+    public void ping(byte[] padding) throws IOException {
+        SubsystemMessage msg = new SshAgentPing(padding);
+        sendMessage(msg);
+        msg = readMessage();
+
+        if (msg instanceof SshAgentAlive) {
+            if (!Arrays.equals(padding, ((SshAgentAlive) msg).getPadding())) {
+                throw new IOException(
+                    "Agent failed to reply with expected data");
+            }
+        } else {
+            throw new IOException(
+                "Agent failed to provide the request random data");
+        }
+    }
+
+    /**
+     * Delete a key held by the agent
+     *
+     * @param key the public key of the private key to delete
+     * @param description the description of the key
+     *
+     * @throws IOException if an IO error occurs
+     */
+    public void deleteKey(SshPublicKey key, String description)
+        throws IOException {
+        SubsystemMessage msg = new SshAgentDeleteKey(key, description);
+        sendMessage(msg);
+        msg = readMessage();
+
+        if (!(msg instanceof SshAgentSuccess)) {
+            throw new IOException("The agent failed to delete the key");
+        }
+    }
+
+    /**
+     * Delete all the keys held by the agent.
+     *
+     * @throws IOException if an IO error occurs
+     */
+    public void deleteAllKeys() throws IOException {
+        SubsystemMessage msg = new SshAgentDeleteAllKeys();
+        sendMessage(msg);
+        msg = readMessage();
+
+        if (!(msg instanceof SshAgentSuccess)) {
+            throw new IOException("The agent failed to delete all keys");
+        }
+    }
+
+    /**
+     * Send a forwarding notice.
+     *
+     * @throws IOException if an IO error occurs
+     */
+    protected void sendForwardingNotice() throws IOException {
+        InetAddress addr = InetAddress.getLocalHost();
+        SshAgentForwardingNotice msg = new SshAgentForwardingNotice(addr.getHostName(),
+                addr.getHostAddress(), 22);
+        sendMessage(msg);
+    }
+
+    /**
+     * Send a subsystem message
+     *
+     * @param msg the message to send
+     *
+     * @throws IOException if an IO error occurs
+     */
+    protected void sendMessage(SubsystemMessage msg) throws IOException {
+        log.info("Sending message " + msg.getMessageName());
+
+        byte[] msgdata = msg.toByteArray();
+        out.write(ByteArrayWriter.encodeInt(msgdata.length));
+        out.write(msgdata);
+        out.flush();
+    }
+
+    /**
+     * Read a single message from the inputstream and convert into a valid
+     * subsystem message
+     *
+     * @return the next available subsystem message
+     *
+     * @throws InvalidMessageException if the message received is invalid
+     */
+    protected SubsystemMessage readMessage() throws InvalidMessageException {
+        try {
+            byte[] lendata = new byte[4];
+            byte[] msgdata;
+            int len;
+
+            // Read the first 4 bytes to determine the length of the message
+            len = 0;
+
+            while (len < 3) {
+                len += in.read(lendata, len, lendata.length - len);
+            }
+
+            len = (int) ByteArrayReader.readInt(lendata, 0);
+            msgdata = new byte[len];
+            len = 0;
+
+            while (len < msgdata.length) {
+                len += in.read(msgdata, len, msgdata.length - len);
+            }
+
+            Integer id = new Integer((int) msgdata[0] & 0xFF);
+
+            if (messages.containsKey(id)) {
+                Class cls = (Class) messages.get(id);
+                SubsystemMessage msg = (SubsystemMessage) cls.newInstance();
+                msg.fromByteArray(msgdata);
+                log.info("Received message " + msg.getMessageName());
+
+                return msg;
+            } else {
+                throw new InvalidMessageException("Unrecognised message id " +
+                    id.toString());
+            }
+        } catch (Exception ex) {
+            throw new InvalidMessageException(ex.getMessage());
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/agent/SshAgentConnection.java b/src/com/sshtools/j2ssh/agent/SshAgentConnection.java
new file mode 100644
index 0000000000000000000000000000000000000000..e49958dbef7d1a409e72706212a07588ae31cbcb
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/SshAgentConnection.java
@@ -0,0 +1,527 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+import com.sshtools.j2ssh.configuration.*;
+import com.sshtools.j2ssh.io.*;
+import com.sshtools.j2ssh.subsystem.*;
+import com.sshtools.j2ssh.transport.publickey.*;
+
+import org.apache.commons.logging.*;
+
+import java.io.*;
+
+import java.net.*;
+
+import java.util.*;
+
+
+/**
+ * This class provides a connection using the SSH agent protocol.
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class SshAgentConnection implements Runnable {
+    private static Log log = LogFactory.getLog(SshAgentConnection.class);
+    InputStream in;
+    OutputStream out;
+    KeyStore keystore;
+    Thread thread;
+    Vector forwardingNodes = new Vector();
+    Socket socket;
+
+    SshAgentConnection(KeyStore keystore, InputStream in, OutputStream out) {
+        this.keystore = keystore;
+        this.in = in;
+        this.out = out;
+        thread = new Thread(this);
+        thread.start();
+    }
+
+    SshAgentConnection(KeyStore keystore, Socket socket)
+        throws IOException {
+        this.keystore = keystore;
+        this.in = socket.getInputStream();
+        this.out = socket.getOutputStream();
+        this.socket = socket;
+        socket.setSoTimeout(5000);
+        thread = new Thread(this);
+        thread.start();
+    }
+
+    /**
+     * Send a success message.
+     *
+     * @throws IOException if an IO error occurs
+     */
+    protected void sendAgentSuccess() throws IOException {
+        SshAgentSuccess msg = new SshAgentSuccess();
+        sendMessage(msg);
+    }
+
+    /**
+     * Send a failure message
+     *
+     * @param errorcode the error code of the failure
+     *
+     * @throws IOException if an IO error occurs
+     */
+    protected void sendAgentFailure(int errorcode) throws IOException {
+        SshAgentFailure msg = new SshAgentFailure(errorcode);
+        sendMessage(msg);
+    }
+
+    /**
+     * Send the version response; this class currently implements version 2
+     *
+     * @throws IOException if an IO error occurs
+     */
+    protected void sendVersionResponse() throws IOException {
+        SshAgentVersionResponse msg = new SshAgentVersionResponse(2);
+        sendMessage(msg);
+    }
+
+    /**
+     * Send the agents key list to the remote side. This supplies all the
+     * public keys.
+     *
+     * @throws IOException if an IO error occurs
+     */
+    protected void sendAgentKeyList() throws IOException {
+        SshAgentKeyList msg = new SshAgentKeyList(keystore.getPublicKeys());
+        sendMessage(msg);
+    }
+
+    /**
+     * Send the completed signing operation data.
+     *
+     * @param data the data generating from the signing operation
+     *
+     * @throws IOException if an IO error occurs
+     */
+    protected void sendOperationComplete(byte[] data) throws IOException {
+        SshAgentOperationComplete msg = new SshAgentOperationComplete(data);
+        sendMessage(msg);
+    }
+
+    /**
+     * Send some random data to the remote side.
+     *
+     * @param data some random data
+     *
+     * @throws IOException if an IO error occurs
+     */
+    protected void sendRandomData(byte[] data) throws IOException {
+        SshAgentRandomData msg = new SshAgentRandomData(data);
+        sendMessage(msg);
+    }
+
+    /**
+     * Send the agent alive message. This is sent to test whether the agent is
+     * still active
+     *
+     * @param padding some random padding for the message
+     *
+     * @throws IOException if an IO error occurs
+     */
+    protected void sendAgentAlive(byte[] padding) throws IOException {
+        SshAgentAlive msg = new SshAgentAlive(padding);
+        sendMessage(msg);
+    }
+
+    /**
+     * Sends a subsystem message.
+     *
+     * @param msg the subsystem message to send
+     *
+     * @throws IOException if an IO error occurs
+     */
+    protected void sendMessage(SubsystemMessage msg) throws IOException {
+        log.info("Sending message " + msg.getMessageName());
+
+        byte[] msgdata = msg.toByteArray();
+        out.write(ByteArrayWriter.encodeInt(msgdata.length));
+        out.write(msgdata);
+        out.flush();
+    }
+
+    /**
+     * Called when a forwarding notice is recceived from the remote side.
+     *
+     * @param msg the forwarding notice
+     */
+    protected void onForwardingNotice(SshAgentForwardingNotice msg) {
+        forwardingNodes.add(new ForwardingNotice(msg.getRemoteHostname(),
+                msg.getRemoteIPAddress(), msg.getRemotePort()));
+    }
+
+    /**
+     * Called when the remote side requests the version number of this
+     * protocol.
+     *
+     * @param msg the version request message
+     *
+     * @throws IOException if an IO error occurs
+     */
+    protected void onRequestVersion(SshAgentRequestVersion msg)
+        throws IOException {
+        sendVersionResponse();
+    }
+
+    /**
+     * Called when the remote side adds a key the agent.
+     *
+     * @param msg the message containing the key
+     *
+     * @throws IOException if an IO error occurs
+     */
+    protected void onAddKey(SshAgentAddKey msg) throws IOException {
+        if (keystore.addKey(msg.getPrivateKey(), msg.getPublicKey(),
+                    msg.getDescription(), msg.getKeyConstraints())) {
+            sendAgentSuccess();
+        } else {
+            sendAgentFailure(SshAgentFailure.SSH_AGENT_ERROR_FAILURE);
+        }
+    }
+
+    /**
+     * Called when the remote side requests that all keys be removed from the
+     * agent.
+     *
+     * @param msg the delete all keys message
+     *
+     * @throws IOException if an IO error occurs
+     */
+    protected void onDeleteAllKeys(SshAgentDeleteAllKeys msg)
+        throws IOException {
+        keystore.deleteAllKeys();
+        sendAgentSuccess();
+    }
+
+    /**
+     * Called by the remote side when a list of the agents keys is required
+     *
+     * @param msg the list all keys message
+     *
+     * @throws IOException if an IO error occurs
+     */
+    protected void onListKeys(SshAgentListKeys msg) throws IOException {
+        sendAgentKeyList();
+    }
+
+    /**
+     * Called by the remote side to initiate a private key operation.
+     *
+     * @param msg the private key operation message
+     *
+     * @throws IOException if an IO error occurs
+     */
+    protected void onPrivateKeyOp(SshAgentPrivateKeyOp msg)
+        throws IOException {
+        try {
+            if (msg.getOperation().equals("sign")) {
+                sendAgentFailure(SshAgentFailure.SSH_AGENT_ERROR_KEY_NOT_SUITABLE);
+            } else if (msg.getOperation().equals("hash-and-sign")) {
+                byte[] sig = keystore.performHashAndSign(msg.getPublicKey(),
+                        forwardingNodes, msg.getOperationData());
+                sendOperationComplete(sig);
+            } else if (msg.getOperation().equals("decrypt")) {
+                sendAgentFailure(SshAgentFailure.SSH_AGENT_ERROR_KEY_NOT_SUITABLE);
+            } else if (msg.getOperation().equals("ssh1-challenge-response")) {
+                sendAgentFailure(SshAgentFailure.SSH_AGENT_ERROR_KEY_NOT_SUITABLE);
+            } else {
+                sendAgentFailure(SshAgentFailure.SSH_AGENT_ERROR_UNSUPPORTED_OP);
+            }
+        } catch (KeyTimeoutException ex) {
+            sendAgentFailure(SshAgentFailure.SSH_AGENT_ERROR_TIMEOUT);
+        } catch (InvalidSshKeyException ex) {
+            sendAgentFailure(SshAgentFailure.SSH_AGENT_ERROR_KEY_NOT_FOUND);
+        }
+    }
+
+    /**
+     * Called by the remote side to delete a key from the agent
+     *
+     * @param msg the message containin the key to delete
+     *
+     * @throws IOException if an IO error occurs
+     */
+    protected void onDeleteKey(SshAgentDeleteKey msg) throws IOException {
+        if (keystore.deleteKey(msg.getPublicKey(), msg.getDescription())) {
+            sendAgentSuccess();
+        } else {
+            sendAgentFailure(SshAgentFailure.SSH_AGENT_ERROR_KEY_NOT_FOUND);
+        }
+    }
+
+    /**
+     * Called by the remote side when the agent is to be locked
+     *
+     * @param msg the message containing a password
+     *
+     * @throws IOException if an IO error occurs
+     */
+    protected void onLock(SshAgentLock msg) throws IOException {
+        if (keystore.lock(msg.getPassword())) {
+            sendAgentSuccess();
+        } else {
+            sendAgentFailure(SshAgentFailure.SSH_AGENT_ERROR_DENIED);
+        }
+    }
+
+    /**
+     * Called by the remote side when the agent is to be unlocked
+     *
+     * @param msg the message containin the password
+     *
+     * @throws IOException if an IO error occurs
+     */
+    protected void onUnlock(SshAgentUnlock msg) throws IOException {
+        if (keystore.unlock(msg.getPassword())) {
+            sendAgentSuccess();
+        } else {
+            sendAgentFailure(SshAgentFailure.SSH_AGENT_ERROR_DENIED);
+        }
+    }
+
+    /**
+     * Called when a ping message is received
+     *
+     * @param msg the ping message containing some padding
+     *
+     * @throws IOException if an IO error occurs
+     */
+    protected void onPing(SshAgentPing msg) throws IOException {
+        sendAgentAlive(msg.getPadding());
+    }
+
+    /**
+     * Called when the remote side sends a random message
+     *
+     * @param msg the random message
+     *
+     * @throws IOException if an IO error occurs
+     */
+    protected void onRandom(SshAgentRandom msg) throws IOException {
+        if (msg.getLength() > 0) {
+            byte[] random = new byte[msg.getLength()];
+            ConfigurationLoader.getRND().nextBytes(random);
+            sendRandomData(random);
+        } else {
+            sendAgentFailure(SshAgentFailure.SSH_AGENT_ERROR_FAILURE);
+        }
+    }
+
+    /**
+     * The connection thread
+     */
+    public void run() {
+        try {
+            log.info("Starting agent connection thread");
+
+            byte[] lendata = new byte[4];
+            byte[] msgdata;
+            int len;
+            int read;
+            boolean alive = true;
+
+            while (alive) {
+                // Read the first 4 bytes to determine the length of the message
+                len = 0;
+
+                while (len < lendata.length) {
+                    try {
+                        read = 0;
+                        read = in.read(lendata, len, lendata.length - len);
+
+                        if (read >= 0) {
+                            len += read;
+                        } else {
+                            alive = false;
+
+                            break;
+                        }
+                    } catch (InterruptedIOException ex) {
+                        if (ex.bytesTransferred > 0) {
+                            len += ex.bytesTransferred;
+                        }
+                    }
+                }
+
+                if (alive) {
+                    len = (int) ByteArrayReader.readInt(lendata, 0);
+                    msgdata = new byte[len];
+                    len = 0;
+
+                    while (len < msgdata.length) {
+                        try {
+                            len += in.read(msgdata, len, msgdata.length - len);
+                        } catch (InterruptedIOException ex1) {
+                            len += ex1.bytesTransferred;
+                        }
+                    }
+
+                    onMessageReceived(msgdata);
+                }
+            }
+        } catch (IOException ex) {
+            log.info("The agent connection terminated");
+        } finally {
+            try {
+                socket.close();
+            } catch (Exception ex) {
+            }
+        }
+
+        log.info("Exiting agent connection thread");
+    }
+
+    /**
+     * Process a message and route to the handler method
+     *
+     * @param msgdata the raw message received
+     *
+     * @throws IOException if an IO error occurs
+     */
+    protected void onMessageReceived(byte[] msgdata) throws IOException {
+        switch ((int) (msgdata[0] & 0xFF)) {
+        case SshAgentForwardingNotice.SSH_AGENT_FORWARDING_NOTICE: {
+            log.info("Agent forwarding notice received");
+
+            SshAgentForwardingNotice msg = new SshAgentForwardingNotice();
+            msg.fromByteArray(msgdata);
+            onForwardingNotice(msg);
+
+            break;
+        }
+
+        case SshAgentRequestVersion.SSH_AGENT_REQUEST_VERSION: {
+            log.info("Agent version request received");
+
+            SshAgentRequestVersion msg = new SshAgentRequestVersion();
+            msg.fromByteArray(msgdata);
+            onRequestVersion(msg);
+
+            break;
+        }
+
+        case SshAgentAddKey.SSH_AGENT_ADD_KEY: {
+            log.info("Adding key to agent");
+
+            SshAgentAddKey msg = new SshAgentAddKey();
+            msg.fromByteArray(msgdata);
+            onAddKey(msg);
+
+            break;
+        }
+
+        case SshAgentDeleteAllKeys.SSH_AGENT_DELETE_ALL_KEYS: {
+            log.info("Deleting all keys from agent");
+
+            SshAgentDeleteAllKeys msg = new SshAgentDeleteAllKeys();
+            msg.fromByteArray(msgdata);
+            onDeleteAllKeys(msg);
+
+            break;
+        }
+
+        case SshAgentListKeys.SSH_AGENT_LIST_KEYS: {
+            log.info("Listing agent keys");
+
+            SshAgentListKeys msg = new SshAgentListKeys();
+            msg.fromByteArray(msgdata);
+            onListKeys(msg);
+
+            break;
+        }
+
+        case SshAgentPrivateKeyOp.SSH_AGENT_PRIVATE_KEY_OP: {
+            log.info("Performing agent private key operation");
+
+            SshAgentPrivateKeyOp msg = new SshAgentPrivateKeyOp();
+            msg.fromByteArray(msgdata);
+            onPrivateKeyOp(msg);
+
+            break;
+        }
+
+        case SshAgentDeleteKey.SSH_AGENT_DELETE_KEY: {
+            log.info("Deleting key from agent");
+
+            SshAgentDeleteKey msg = new SshAgentDeleteKey();
+            msg.fromByteArray(msgdata);
+            onDeleteKey(msg);
+
+            break;
+        }
+
+        case SshAgentLock.SSH_AGENT_LOCK: {
+            log.info("Locking agent");
+
+            SshAgentLock msg = new SshAgentLock();
+            msg.fromByteArray(msgdata);
+            onLock(msg);
+
+            break;
+        }
+
+        case SshAgentUnlock.SSH_AGENT_UNLOCK: {
+            log.info("Unlocking agent");
+
+            SshAgentUnlock msg = new SshAgentUnlock();
+            msg.fromByteArray(msgdata);
+            onUnlock(msg);
+
+            break;
+        }
+
+        case SshAgentPing.SSH_AGENT_PING: {
+            log.info("Ping Ping Ping Ping Ping");
+
+            SshAgentPing msg = new SshAgentPing();
+            msg.fromByteArray(msgdata);
+            onPing(msg);
+
+            break;
+        }
+
+        case SshAgentRandom.SSH_AGENT_RANDOM: {
+            log.info("Random message received");
+
+            SshAgentRandom msg = new SshAgentRandom();
+            msg.fromByteArray(msgdata);
+            onRandom(msg);
+
+            break;
+        }
+
+        default:
+            throw new IOException("Unrecognized message type " +
+                String.valueOf(msgdata[0]) + " received");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/agent/SshAgentDeleteAllKeys.java b/src/com/sshtools/j2ssh/agent/SshAgentDeleteAllKeys.java
new file mode 100644
index 0000000000000000000000000000000000000000..74ede1b8aabe647167799d0257ae35e54489d078
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/SshAgentDeleteAllKeys.java
@@ -0,0 +1,80 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+
+
+class SshAgentDeleteAllKeys extends SubsystemMessage {
+    /**  */
+    public static final int SSH_AGENT_DELETE_ALL_KEYS = 203;
+
+    /**
+     * Creates a new SshAgentDeleteAllKeys object.
+     */
+    public SshAgentDeleteAllKeys() {
+        super(SSH_AGENT_DELETE_ALL_KEYS);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_AGENT_DELETE_ALL_KEYS";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+    }
+}
diff --git a/src/com/sshtools/j2ssh/agent/SshAgentDeleteKey.java b/src/com/sshtools/j2ssh/agent/SshAgentDeleteKey.java
new file mode 100644
index 0000000000000000000000000000000000000000..8fbf8dea16ad93c279537865d665ac85027070ef
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/SshAgentDeleteKey.java
@@ -0,0 +1,131 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.publickey.SshKeyPairFactory;
+import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
+
+import java.io.IOException;
+
+
+class SshAgentDeleteKey extends SubsystemMessage {
+    /**  */
+    public static final int SSH_AGENT_DELETE_KEY = 207;
+    SshPublicKey pubkey;
+    String description;
+
+    /**
+     * Creates a new SshAgentDeleteKey object.
+     */
+    public SshAgentDeleteKey() {
+        super(SSH_AGENT_DELETE_KEY);
+    }
+
+    /**
+     * Creates a new SshAgentDeleteKey object.
+     *
+     * @param pubkey
+     * @param description
+     */
+    public SshAgentDeleteKey(SshPublicKey pubkey, String description) {
+        super(SSH_AGENT_DELETE_KEY);
+        this.pubkey = pubkey;
+        this.description = description;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public SshPublicKey getPublicKey() {
+        return pubkey;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_AGENT_DELETE_KEY";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            baw.writeBinaryString(pubkey.getEncoded());
+            baw.writeString(description);
+        } catch (IOException ex) {
+            throw new InvalidMessageException(ex.getMessage());
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            pubkey = SshKeyPairFactory.decodePublicKey(bar.readBinaryString());
+            description = bar.readString();
+        } catch (IOException ex) {
+            throw new InvalidMessageException(ex.getMessage());
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/agent/SshAgentFailure.java b/src/com/sshtools/j2ssh/agent/SshAgentFailure.java
new file mode 100644
index 0000000000000000000000000000000000000000..0515cf8613bb79da7e057bb16d57b53b3e7a6443
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/SshAgentFailure.java
@@ -0,0 +1,139 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+
+import java.io.IOException;
+
+
+class SshAgentFailure extends SubsystemMessage {
+    /**  */
+    public static final int SSH_AGENT_FAILURE = 102;
+
+    /**  */
+    public static final int SSH_AGENT_ERROR_TIMEOUT = 1;
+
+    /**  */
+    public static final int SSH_AGENT_ERROR_KEY_NOT_FOUND = 2;
+
+    /**  */
+    public static final int SSH_AGENT_ERROR_DECRYPT_FAILED = 3;
+
+    /**  */
+    public static final int SSH_AGENT_ERROR_SIZE_ERROR = 4;
+
+    /**  */
+    public static final int SSH_AGENT_ERROR_KEY_NOT_SUITABLE = 5;
+
+    /**  */
+    public static final int SSH_AGENT_ERROR_DENIED = 6;
+
+    /**  */
+    public static final int SSH_AGENT_ERROR_FAILURE = 7;
+
+    /**  */
+    public static final int SSH_AGENT_ERROR_UNSUPPORTED_OP = 8;
+    private int errorcode;
+
+    /**
+     * Creates a new SshAgentFailure object.
+     */
+    public SshAgentFailure() {
+        super(SSH_AGENT_FAILURE);
+    }
+
+    /**
+     * Creates a new SshAgentFailure object.
+     *
+     * @param errorcode
+     */
+    public SshAgentFailure(int errorcode) {
+        super(SSH_AGENT_FAILURE);
+        this.errorcode = errorcode;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_AGENT_FAILURE";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getErrorCode() {
+        return errorcode;
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            baw.writeInt(errorcode);
+        } catch (IOException ioe) {
+            throw new InvalidMessageException(ioe.getMessage());
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            errorcode = (int) bar.readInt();
+        } catch (IOException ioe) {
+            throw new InvalidMessageException(ioe.getMessage());
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/agent/SshAgentForwardingListener.java b/src/com/sshtools/j2ssh/agent/SshAgentForwardingListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..d36755b2daf4bcbfb2fd340213526f2cedd2ea3d
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/SshAgentForwardingListener.java
@@ -0,0 +1,202 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+import com.sshtools.j2ssh.connection.ConnectionProtocol;
+import com.sshtools.j2ssh.util.StartStopState;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+import java.util.HashMap;
+import java.util.Vector;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public class SshAgentForwardingListener {
+    private static Log log = LogFactory.getLog(SshAgentForwardingListener.class);
+    private static HashMap agents = new HashMap();
+    ServerSocket server;
+    int port;
+    String location;
+    StartStopState state = new StartStopState(StartStopState.STOPPED);
+    Thread thread;
+    ConnectionProtocol connection;
+    Vector references = new Vector();
+    String sessionId;
+
+    SshAgentForwardingListener(String sessionId, ConnectionProtocol connection) {
+        log.info("Forwarding agent started");
+        this.sessionId = sessionId;
+        this.connection = connection;
+        port = selectPort();
+        location = "localhost:" + String.valueOf(port);
+        thread = new Thread(new Runnable() {
+                    public void run() {
+                        state.setValue(StartStopState.STARTED);
+
+                        try {
+                            server = new ServerSocket(port, 5,
+                                    InetAddress.getByName("localhost"));
+
+                            //server.bind(new InetSocketAddress("localhost", port));
+                            Socket socket;
+
+                            while ((state.getValue() == StartStopState.STARTED) &&
+                                    ((socket = server.accept()) != null)) {
+                                AgentSocketChannel channel = new AgentSocketChannel(true);
+                                channel.bindSocket(socket);
+
+                                if (!SshAgentForwardingListener.this.connection.openChannel(
+                                            channel)) {
+                                    log.warn(
+                                        "Failed to open agent forwarding channel");
+                                }
+                            }
+                        } catch (Exception e) {
+                            if (state.getValue() == StartStopState.STARTED) {
+                                log.warn("Forwarding agent socket failed", e);
+                            }
+                        }
+
+                        state.setValue(StartStopState.STOPPED);
+                    }
+                });
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getConfiguration() {
+        return location;
+    }
+
+    /**
+     *
+     *
+     * @param obj
+     */
+    public void addReference(Object obj) {
+        if (!references.contains(obj)) {
+            references.add(obj);
+        }
+    }
+
+    /**
+     *
+     *
+     * @param obj
+     */
+    public void removeReference(Object obj) {
+        if (references.contains(obj)) {
+            references.remove(obj);
+
+            if (references.size() == 0) {
+                stop();
+                agents.remove(sessionId);
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    public void start() throws IOException {
+        thread.start();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getPort() {
+        return port;
+    }
+
+    /**
+     *
+     */
+    public void stop() {
+        try {
+            state.setValue(StartStopState.STOPPED);
+            server.close();
+        } catch (IOException ex) {
+        }
+    }
+
+    private int selectPort() {
+        return 49152 +
+        (int) Math.round(((float) 16383 * ConfigurationLoader.getRND()
+                                                             .nextFloat()));
+    }
+
+    /**
+     *
+     *
+     * @param sessionId
+     * @param connection
+     *
+     * @return
+     *
+     * @throws AgentNotAvailableException
+     */
+    public static SshAgentForwardingListener getInstance(String sessionId,
+        ConnectionProtocol connection) throws AgentNotAvailableException {
+        if (agents.containsKey(sessionId)) {
+            SshAgentForwardingListener agent = (SshAgentForwardingListener) agents.get(sessionId);
+
+            return agent;
+        } else {
+            try {
+                SshAgentForwardingListener agent = new SshAgentForwardingListener(sessionId,
+                        connection);
+                agent.start();
+                agents.put(sessionId, agent);
+
+                return agent;
+            } catch (IOException ex) {
+                throw new AgentNotAvailableException();
+            }
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/agent/SshAgentForwardingNotice.java b/src/com/sshtools/j2ssh/agent/SshAgentForwardingNotice.java
new file mode 100644
index 0000000000000000000000000000000000000000..f64ba48aefcd84528e1810b88f623e3bca68c300
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/SshAgentForwardingNotice.java
@@ -0,0 +1,145 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+
+import java.io.IOException;
+
+
+class SshAgentForwardingNotice extends SubsystemMessage {
+    /**  */
+    public static final int SSH_AGENT_FORWARDING_NOTICE = 206;
+    String remoteHostname;
+    String remoteIPAddress;
+    UnsignedInteger32 remotePort;
+
+    /**
+     * Creates a new SshAgentForwardingNotice object.
+     */
+    public SshAgentForwardingNotice() {
+        super(SSH_AGENT_FORWARDING_NOTICE);
+    }
+
+    /**
+     * Creates a new SshAgentForwardingNotice object.
+     *
+     * @param remoteHostname
+     * @param remoteIPAddress
+     * @param remotePort
+     */
+    public SshAgentForwardingNotice(String remoteHostname,
+        String remoteIPAddress, int remotePort) {
+        super(SSH_AGENT_FORWARDING_NOTICE);
+        this.remoteHostname = remoteHostname;
+        this.remoteIPAddress = remoteIPAddress;
+        this.remotePort = new UnsignedInteger32(remotePort);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getRemoteHostname() {
+        return remoteHostname;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getRemoteIPAddress() {
+        return remoteIPAddress;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getRemotePort() {
+        return remotePort.intValue();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_AGENT_FORWARDING_NOTICE";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            baw.writeString(remoteHostname);
+            baw.writeString(remoteIPAddress);
+            baw.writeUINT32(remotePort);
+        } catch (IOException ex) {
+            throw new InvalidMessageException(ex.getMessage());
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            remoteHostname = bar.readString();
+            remoteIPAddress = bar.readString();
+            remotePort = bar.readUINT32();
+        } catch (IOException ex) {
+            throw new InvalidMessageException(ex.getMessage());
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/agent/SshAgentKeyList.java b/src/com/sshtools/j2ssh/agent/SshAgentKeyList.java
new file mode 100644
index 0000000000000000000000000000000000000000..c1e167d3b0ef49bb3932c4c259752f03985e0b21
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/SshAgentKeyList.java
@@ -0,0 +1,145 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.publickey.SshKeyPairFactory;
+import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
+
+import java.io.IOException;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+
+class SshAgentKeyList extends SubsystemMessage {
+    /**  */
+    public static final int SSH_AGENT_KEY_LIST = 104;
+    private Map keys;
+
+    /**
+     * Creates a new SshAgentKeyList object.
+     *
+     * @param keys
+     */
+    public SshAgentKeyList(Map keys) {
+        super(SSH_AGENT_KEY_LIST);
+        this.keys = keys;
+    }
+
+    /**
+     * Creates a new SshAgentKeyList object.
+     */
+    public SshAgentKeyList() {
+        super(SSH_AGENT_KEY_LIST);
+        this.keys = new HashMap();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public Map getKeys() {
+        return keys;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_AGENT_KEY_LIST";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            baw.writeInt(keys.size());
+
+            Map.Entry entry;
+            Iterator it = keys.entrySet().iterator();
+            SshPublicKey key;
+            String description;
+
+            while (it.hasNext()) {
+                entry = (Map.Entry) it.next();
+                key = (SshPublicKey) entry.getKey();
+                description = (String) entry.getValue();
+                baw.writeBinaryString(key.getEncoded());
+                baw.writeString(description);
+            }
+        } catch (IOException ex) {
+            throw new InvalidMessageException("Failed to write message data");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            int num = (int) bar.readInt();
+            SshPublicKey key;
+            String description;
+            byte[] buf;
+
+            for (int i = 0; i < num; i++) {
+                buf = bar.readBinaryString();
+                key = SshKeyPairFactory.decodePublicKey(buf);
+                description = bar.readString();
+                keys.put(key, description);
+            }
+        } catch (IOException ex) {
+            throw new InvalidMessageException("Failed to read message data");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/agent/SshAgentListKeys.java b/src/com/sshtools/j2ssh/agent/SshAgentListKeys.java
new file mode 100644
index 0000000000000000000000000000000000000000..e8c777196ef82c940fee10b12fcb57911b295558
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/SshAgentListKeys.java
@@ -0,0 +1,80 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+
+
+class SshAgentListKeys extends SubsystemMessage {
+    /**  */
+    public static final int SSH_AGENT_LIST_KEYS = 204;
+
+    /**
+     * Creates a new SshAgentListKeys object.
+     */
+    public SshAgentListKeys() {
+        super(SSH_AGENT_LIST_KEYS);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_AGENT_LIST_KEYS";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+    }
+}
diff --git a/src/com/sshtools/j2ssh/agent/SshAgentLock.java b/src/com/sshtools/j2ssh/agent/SshAgentLock.java
new file mode 100644
index 0000000000000000000000000000000000000000..08935903eaea19d401acc0a6fc91db4674ce20d7
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/SshAgentLock.java
@@ -0,0 +1,115 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+
+import java.io.IOException;
+
+
+class SshAgentLock extends SubsystemMessage {
+    /**  */
+    public static final int SSH_AGENT_LOCK = 208;
+    String password;
+
+    /**
+     * Creates a new SshAgentLock object.
+     */
+    public SshAgentLock() {
+        super(SSH_AGENT_LOCK);
+    }
+
+    /**
+     * Creates a new SshAgentLock object.
+     *
+     * @param password
+     */
+    public SshAgentLock(String password) {
+        super(SSH_AGENT_LOCK);
+        this.password = password;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getPassword() {
+        return password;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_AGENT_LOCK";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            baw.writeString(password);
+        } catch (IOException ioe) {
+            throw new InvalidMessageException(ioe.getMessage());
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            password = bar.readString();
+        } catch (IOException ioe) {
+            throw new InvalidMessageException(ioe.getMessage());
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/agent/SshAgentOperationComplete.java b/src/com/sshtools/j2ssh/agent/SshAgentOperationComplete.java
new file mode 100644
index 0000000000000000000000000000000000000000..81d576947780b330f16bf6eb27b455e12a60ffa6
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/SshAgentOperationComplete.java
@@ -0,0 +1,110 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+
+import java.io.IOException;
+
+
+class SshAgentOperationComplete extends SubsystemMessage {
+    /**  */
+    public static final int SSH_AGENT_OPERATION_COMPLETE = 105;
+    private byte[] data;
+
+    /**
+     * Creates a new SshAgentOperationComplete object.
+     */
+    public SshAgentOperationComplete() {
+        super(SSH_AGENT_OPERATION_COMPLETE);
+    }
+
+    /**
+     * Creates a new SshAgentOperationComplete object.
+     *
+     * @param data
+     */
+    public SshAgentOperationComplete(byte[] data) {
+        super(SSH_AGENT_OPERATION_COMPLETE);
+        this.data = data;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getData() {
+        return data;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_AGENT_OPERATION_COMPLETE";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            baw.writeBinaryString(data);
+        } catch (IOException ioe) {
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            data = bar.readBinaryString();
+        } catch (IOException ioe) {
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/agent/SshAgentPing.java b/src/com/sshtools/j2ssh/agent/SshAgentPing.java
new file mode 100644
index 0000000000000000000000000000000000000000..c4d0add0e17392e9d8172452b696b3af5c72101b
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/SshAgentPing.java
@@ -0,0 +1,116 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+
+import java.io.IOException;
+
+
+class SshAgentPing extends SubsystemMessage {
+    /**  */
+    public static final int SSH_AGENT_PING = 212;
+    private byte[] padding;
+
+    /**
+     * Creates a new SshAgentPing object.
+     */
+    public SshAgentPing() {
+        super(SSH_AGENT_PING);
+    }
+
+    /**
+     * Creates a new SshAgentPing object.
+     *
+     * @param padding
+     */
+    public SshAgentPing(byte[] padding) {
+        super(SSH_AGENT_PING);
+        this.padding = padding;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getPadding() {
+        return padding;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_AGENT_PING";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            baw.write(padding);
+        } catch (IOException ioe) {
+            throw new InvalidMessageException(ioe.getMessage());
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            padding = new byte[bar.available()];
+            bar.read(padding);
+        } catch (IOException ioe) {
+            throw new InvalidMessageException(ioe.getMessage());
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/agent/SshAgentPrivateKeyOp.java b/src/com/sshtools/j2ssh/agent/SshAgentPrivateKeyOp.java
new file mode 100644
index 0000000000000000000000000000000000000000..bada4d3209d2a0849f923fe3ea148ffbf9ed5a14
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/SshAgentPrivateKeyOp.java
@@ -0,0 +1,147 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.publickey.SshKeyPairFactory;
+import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
+
+import java.io.IOException;
+
+
+class SshAgentPrivateKeyOp extends SubsystemMessage {
+    /**  */
+    public static final int SSH_AGENT_PRIVATE_KEY_OP = 205;
+    SshPublicKey pubkey;
+    String operation;
+    byte[] data;
+
+    /**
+     * Creates a new SshAgentPrivateKeyOp object.
+     */
+    public SshAgentPrivateKeyOp() {
+        super(SSH_AGENT_PRIVATE_KEY_OP);
+    }
+
+    /**
+     * Creates a new SshAgentPrivateKeyOp object.
+     *
+     * @param pubkey
+     * @param operation
+     * @param data
+     */
+    public SshAgentPrivateKeyOp(SshPublicKey pubkey, String operation,
+        byte[] data) {
+        super(SSH_AGENT_PRIVATE_KEY_OP);
+        this.pubkey = pubkey;
+        this.operation = operation;
+        this.data = data;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public SshPublicKey getPublicKey() {
+        return pubkey;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getOperation() {
+        return operation;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getOperationData() {
+        return data;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_AGENT_PRIVATE_KEY_OP";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            baw.writeString(operation);
+            baw.writeBinaryString(pubkey.getEncoded());
+            baw.write(data);
+        } catch (IOException ex) {
+            throw new InvalidMessageException(ex.getMessage());
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            operation = bar.readString();
+            pubkey = SshKeyPairFactory.decodePublicKey(bar.readBinaryString());
+            data = new byte[bar.available()];
+            bar.read(data);
+        } catch (IOException ex) {
+            throw new InvalidMessageException(ex.getMessage());
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/agent/SshAgentRandom.java b/src/com/sshtools/j2ssh/agent/SshAgentRandom.java
new file mode 100644
index 0000000000000000000000000000000000000000..4780f52a0cee58a1d3b16685975b1106dd11142e
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/SshAgentRandom.java
@@ -0,0 +1,115 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+
+import java.io.IOException;
+
+
+class SshAgentRandom extends SubsystemMessage {
+    /**  */
+    public static final int SSH_AGENT_RANDOM = 213;
+    private int length;
+
+    /**
+     * Creates a new SshAgentRandom object.
+     */
+    public SshAgentRandom() {
+        super(SSH_AGENT_RANDOM);
+    }
+
+    /**
+     * Creates a new SshAgentRandom object.
+     *
+     * @param length
+     */
+    public SshAgentRandom(int length) {
+        super(SSH_AGENT_RANDOM);
+        this.length = length;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getLength() {
+        return length;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_AGENT_RANDOM";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            baw.writeInt(length);
+        } catch (IOException ioe) {
+            throw new InvalidMessageException(ioe.getMessage());
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            length = (int) bar.readInt();
+        } catch (IOException ioe) {
+            throw new InvalidMessageException(ioe.getMessage());
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/agent/SshAgentRandomData.java b/src/com/sshtools/j2ssh/agent/SshAgentRandomData.java
new file mode 100644
index 0000000000000000000000000000000000000000..3af1976b95f23dbdb7e78b9107313229bc6eb3b6
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/SshAgentRandomData.java
@@ -0,0 +1,115 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+
+import java.io.IOException;
+
+
+class SshAgentRandomData extends SubsystemMessage {
+    /**  */
+    public static final int SSH_AGENT_RANDOM_DATA = 106;
+    private byte[] data;
+
+    /**
+     * Creates a new SshAgentRandomData object.
+     */
+    public SshAgentRandomData() {
+        super(SSH_AGENT_RANDOM_DATA);
+    }
+
+    /**
+     * Creates a new SshAgentRandomData object.
+     *
+     * @param data
+     */
+    public SshAgentRandomData(byte[] data) {
+        super(SSH_AGENT_RANDOM_DATA);
+        this.data = data;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getRandomData() {
+        return data;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_AGENT_RANDOM_DATA";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            baw.writeBinaryString(data);
+        } catch (IOException ioe) {
+            throw new InvalidMessageException(ioe.getMessage());
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            data = bar.readBinaryString();
+        } catch (IOException ioe) {
+            throw new InvalidMessageException(ioe.getMessage());
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/agent/SshAgentRequestVersion.java b/src/com/sshtools/j2ssh/agent/SshAgentRequestVersion.java
new file mode 100644
index 0000000000000000000000000000000000000000..2aa2e6e1cd5ccfce56c2e263408dd748431d78fd
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/SshAgentRequestVersion.java
@@ -0,0 +1,115 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+
+import java.io.IOException;
+
+
+class SshAgentRequestVersion extends SubsystemMessage {
+    /**  */
+    public static final int SSH_AGENT_REQUEST_VERSION = 1;
+    String version;
+
+    /**
+     * Creates a new SshAgentRequestVersion object.
+     */
+    public SshAgentRequestVersion() {
+        super(SSH_AGENT_REQUEST_VERSION);
+    }
+
+    /**
+     * Creates a new SshAgentRequestVersion object.
+     *
+     * @param version
+     */
+    public SshAgentRequestVersion(String version) {
+        super(SSH_AGENT_REQUEST_VERSION);
+        this.version = version;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getVersion() {
+        return version;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_AGENT_REQUEST_VERSION";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            baw.writeString(version);
+        } catch (IOException ex) {
+            throw new InvalidMessageException(ex.getMessage());
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            version = bar.readString();
+        } catch (IOException ex) {
+            throw new InvalidMessageException(ex.getMessage());
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/agent/SshAgentSocketListener.java b/src/com/sshtools/j2ssh/agent/SshAgentSocketListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..eac31e6ca3e07a1a49d9f69718a2962c71be8cd3
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/SshAgentSocketListener.java
@@ -0,0 +1,202 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+import com.sshtools.j2ssh.util.StartStopState;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.20 $
+ */
+public class SshAgentSocketListener {
+    private static Log log = LogFactory.getLog(SshAgentSocketListener.class);
+    StartStopState state = new StartStopState(StartStopState.STOPPED);
+    KeyStore keystore;
+    ServerSocket server;
+    int port;
+    Thread thread;
+    String location;
+
+    /**
+     * Creates a new SshAgentSocketListener object.
+     *
+     * @param location the location of the listening agent. This should be a
+     *        random port on the localhost such as localhost:15342
+     * @param keystore the keystore for agent operation
+     *
+     * @throws AgentNotAvailableException if the location specifies an invalid
+     *         location
+     */
+    public SshAgentSocketListener(String location, KeyStore keystore)
+        throws AgentNotAvailableException {
+        log.info("New SshAgent instance created");
+
+        // Verify the agent location
+        this.location = location;
+
+        if (location == null) {
+            throw new AgentNotAvailableException();
+        }
+
+        this.location = location;
+
+        int idx = location.indexOf(":");
+
+        if (idx == -1) {
+            throw new AgentNotAvailableException();
+        }
+
+        String host = location.substring(0, idx);
+        port = Integer.parseInt(location.substring(idx + 1));
+        this.keystore = keystore;
+
+        try {
+            server = new ServerSocket(port, 5, InetAddress.getByName(host));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Get the agent listeners state
+     *
+     * @return the current state of the listener
+     */
+    public StartStopState getState() {
+        return state;
+    }
+
+    /**
+     * Starts the agent listener thread
+     */
+    public void start() {
+        thread = new Thread(new Runnable() {
+                    public void run() {
+                        try {
+                            Socket socket;
+                            System.setProperty("sshtools.agent", location);
+                            state.setValue(StartStopState.STARTED);
+
+                            while ((socket = server.accept()) != null) {
+                                SshAgentConnection agentClient = new SshAgentConnection(keystore,
+                                        socket.getInputStream(),
+                                        socket.getOutputStream());
+                            }
+
+                            thread = null;
+                        } catch (IOException ex) {
+                            log.info("The agent listener closed: " +
+                                ex.getMessage());
+                        } finally {
+                            state.setValue(StartStopState.STOPPED);
+                        }
+                    }
+                });
+        thread.start();
+    }
+
+    /**
+     * The current port of the agent listener
+     *
+     * @return the integer port
+     */
+    public int getPort() {
+        return port;
+    }
+
+    /**
+     * Stops the agent listener
+     */
+    public void stop() {
+        try {
+            server.close();
+        } catch (IOException ex) {
+        }
+    }
+
+    /**
+     * Gets the underlying keystore for this agent listener.
+     *
+     * @return the keystore
+     */
+    protected KeyStore getKeystore() {
+        return keystore;
+    }
+
+    /**
+     * Configure a new random port for the agent listener.
+     *
+     * @return the random port for this agent.
+     */
+    public static int configureNewLocation() {
+        return 49152 + (int) Math.round(((float) 16383 * Math.random()));
+    }
+
+    /**
+     * The main entry point for the application. This method currently accepts
+     * the -start parameter which will look for the sshtools.agent system
+     * property. To configure the agent and to get a valid location call with
+     * -configure, set the system sshtools.home system property and start.
+     *
+     * @param args the programs arguments
+     */
+    public static void main(String[] args) {
+        if (args.length > 0) {
+            if (args[0].equals("-start")) {
+                Thread thread = new Thread(new Runnable() {
+                            public void run() {
+                                try {
+                                    SshAgentSocketListener agent = new SshAgentSocketListener(System.getProperty(
+                                                "sshtools.agent"),
+                                            new KeyStore());
+                                    agent.start();
+                                } catch (Exception ex) {
+                                    ex.printStackTrace();
+                                }
+                            }
+                        });
+                thread.start();
+            }
+
+            if (args[0].equals("-configure")) {
+                System.out.println("SET SSHTOOLS_AGENT=localhost:" +
+                    String.valueOf(configureNewLocation()));
+            }
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/agent/SshAgentSuccess.java b/src/com/sshtools/j2ssh/agent/SshAgentSuccess.java
new file mode 100644
index 0000000000000000000000000000000000000000..3520f035916779db6a458ea9599746cf68f532c8
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/SshAgentSuccess.java
@@ -0,0 +1,80 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+
+
+class SshAgentSuccess extends SubsystemMessage {
+    /**  */
+    public static final int SSH_AGENT_SUCCESS = 101;
+
+    /**
+     * Creates a new SshAgentSuccess object.
+     */
+    public SshAgentSuccess() {
+        super(SSH_AGENT_SUCCESS);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_AGENT_SUCCESS";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+    }
+}
diff --git a/src/com/sshtools/j2ssh/agent/SshAgentUnlock.java b/src/com/sshtools/j2ssh/agent/SshAgentUnlock.java
new file mode 100644
index 0000000000000000000000000000000000000000..0709741470e03501bd08b331b3291ed81550a213
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/SshAgentUnlock.java
@@ -0,0 +1,115 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+
+import java.io.IOException;
+
+
+class SshAgentUnlock extends SubsystemMessage {
+    /**  */
+    public static final int SSH_AGENT_UNLOCK = 209;
+    String password;
+
+    /**
+     * Creates a new SshAgentUnlock object.
+     */
+    public SshAgentUnlock() {
+        super(SSH_AGENT_UNLOCK);
+    }
+
+    /**
+     * Creates a new SshAgentUnlock object.
+     *
+     * @param password
+     */
+    public SshAgentUnlock(String password) {
+        super(SSH_AGENT_UNLOCK);
+        this.password = password;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getPassword() {
+        return password;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_AGENT_UNLOCK";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            baw.writeString(password);
+        } catch (IOException ioe) {
+            throw new InvalidMessageException(ioe.getMessage());
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            password = bar.readString();
+        } catch (IOException ioe) {
+            throw new InvalidMessageException(ioe.getMessage());
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/agent/SshAgentVersionResponse.java b/src/com/sshtools/j2ssh/agent/SshAgentVersionResponse.java
new file mode 100644
index 0000000000000000000000000000000000000000..6effc344e86eb914c99a8c9057d71fbc91c70e38
--- /dev/null
+++ b/src/com/sshtools/j2ssh/agent/SshAgentVersionResponse.java
@@ -0,0 +1,116 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.agent;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+
+import java.io.IOException;
+
+
+class SshAgentVersionResponse extends SubsystemMessage {
+    /**  */
+    public static final int SSH_AGENT_VERSION_RESPONSE = 103;
+    UnsignedInteger32 version;
+
+    /**
+     * Creates a new SshAgentVersionResponse object.
+     */
+    public SshAgentVersionResponse() {
+        super(SSH_AGENT_VERSION_RESPONSE);
+    }
+
+    /**
+     * Creates a new SshAgentVersionResponse object.
+     *
+     * @param version
+     */
+    public SshAgentVersionResponse(int version) {
+        super(SSH_AGENT_VERSION_RESPONSE);
+        this.version = new UnsignedInteger32(version);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getVersion() {
+        return version.intValue();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_AGENT_VERSION_RESPONSE";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            baw.writeUINT32(version);
+        } catch (IOException ex) {
+            throw new InvalidMessageException(ex.getMessage());
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            version = bar.readUINT32();
+        } catch (IOException ex) {
+            throw new InvalidMessageException(ex.getMessage());
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/authentication/AuthenticationProtocolClient.java b/src/com/sshtools/j2ssh/authentication/AuthenticationProtocolClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..0c59e47c56ef80495e32d0edf6111f07b8787984
--- /dev/null
+++ b/src/com/sshtools/j2ssh/authentication/AuthenticationProtocolClient.java
@@ -0,0 +1,350 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.authentication;
+
+import com.sshtools.j2ssh.SshException;
+import com.sshtools.j2ssh.transport.MessageNotAvailableException;
+import com.sshtools.j2ssh.transport.MessageStoreEOFException;
+import com.sshtools.j2ssh.transport.Service;
+import com.sshtools.j2ssh.transport.SshMessage;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.27 $
+ */
+public class AuthenticationProtocolClient extends Service {
+    private static Log log = LogFactory.getLog(AuthenticationProtocolClient.class);
+    private int[] resultFilter = new int[2];
+    private int[] singleIdFilter = new int[3];
+    private Vector listeners = new Vector();
+
+    /**
+     * Creates a new AuthenticationProtocolClient object.
+     */
+    public AuthenticationProtocolClient() {
+        super("ssh-userauth");
+        resultFilter[0] = SshMsgUserAuthSuccess.SSH_MSG_USERAUTH_SUCCESS;
+        resultFilter[1] = SshMsgUserAuthFailure.SSH_MSG_USERAUTH_FAILURE;
+        singleIdFilter[0] = SshMsgUserAuthSuccess.SSH_MSG_USERAUTH_SUCCESS;
+        singleIdFilter[1] = SshMsgUserAuthFailure.SSH_MSG_USERAUTH_FAILURE;
+    }
+
+    /**
+     *
+     *
+     * @throws java.io.IOException
+     */
+    protected void onServiceAccept() throws java.io.IOException {
+    }
+
+    /**
+     *
+     */
+    protected void onStart() {
+    }
+
+    /**
+     *
+     *
+     * @param startMode
+     *
+     * @throws java.io.IOException
+     * @throws IOException
+     */
+    protected void onServiceInit(int startMode) throws java.io.IOException {
+        if (startMode == Service.ACCEPTING_SERVICE) {
+            throw new IOException(
+                "The Authentication Protocol client cannot be accepted");
+        }
+
+        messageStore.registerMessage(SshMsgUserAuthFailure.SSH_MSG_USERAUTH_FAILURE,
+            SshMsgUserAuthFailure.class);
+        messageStore.registerMessage(SshMsgUserAuthSuccess.SSH_MSG_USERAUTH_SUCCESS,
+            SshMsgUserAuthSuccess.class);
+        messageStore.registerMessage(SshMsgUserAuthBanner.SSH_MSG_USERAUTH_BANNER,
+            SshMsgUserAuthBanner.class);
+
+        //messageStore.registerMessage(SshMsgUserAuthPwdChangeReq.SSH_MSG_USERAUTH_PWD_CHANGEREQ,
+        //    SshMsgUserAuthPwdChangeReq.class);
+    }
+
+    /**
+     *
+     *
+     * @throws java.io.IOException
+     * @throws IOException
+     */
+    protected void onServiceRequest() throws java.io.IOException {
+        throw new IOException("This class implements the client protocol only!");
+    }
+
+    /**
+     *
+     *
+     * @param listener
+     */
+    public void addEventListener(AuthenticationProtocolListener listener) {
+        if (listener != null) {
+            listeners.add(listener);
+        }
+    }
+
+    /**
+     *
+     *
+     * @param username
+     * @param serviceName
+     *
+     * @return
+     *
+     * @throws IOException
+     * @throws SshException
+     */
+    public List getAvailableAuths(String username, String serviceName)
+        throws IOException {
+        log.info("Requesting authentication methods");
+
+        SshMessage msg = new SshMsgUserAuthRequest(username, serviceName,
+                "none", null);
+        transport.sendMessage(msg, this);
+
+        try {
+            msg = messageStore.getMessage(resultFilter);
+        } catch (InterruptedException ex) {
+            throw new SshException(
+                "The thread was interrupted whilst waiting for an authentication message");
+        }
+
+        if (msg instanceof SshMsgUserAuthFailure) {
+            return ((SshMsgUserAuthFailure) msg).getAvailableAuthentications();
+        } else {
+            throw new IOException(
+                "None request returned success! Insecure feature not supported");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param auth
+     * @param serviceToStart
+     *
+     * @return
+     *
+     * @throws IOException
+     * @throws SshException
+     */
+    public int authenticate(SshAuthenticationClient auth, Service serviceToStart)
+        throws IOException {
+        try {
+            if (!auth.canAuthenticate() && auth.canPrompt()) {
+                SshAuthenticationPrompt prompt = auth.getAuthenticationPrompt();
+
+                if (!prompt.showPrompt(auth)) {
+                    return AuthenticationProtocolState.CANCELLED;
+                }
+            }
+
+            auth.authenticate(this, serviceToStart.getServiceName());
+
+            SshMessage msg = parseMessage(messageStore.getMessage(resultFilter));
+
+            // We should not get this far
+            throw new AuthenticationProtocolException(
+                "Unexpected authentication message " + msg.getMessageName());
+        } catch (TerminatedStateException tse) {
+            if (tse.getState() == AuthenticationProtocolState.COMPLETE) {
+                serviceToStart.init(Service.ACCEPTING_SERVICE, transport); //, nativeSettings);
+                serviceToStart.start();
+
+                for (Iterator it = listeners.iterator(); it.hasNext();) {
+                    AuthenticationProtocolListener listener = (AuthenticationProtocolListener) it.next();
+
+                    if (listener != null) {
+                        listener.onAuthenticationComplete();
+                    }
+                }
+            }
+
+            return tse.getState();
+        } catch (InterruptedException ex) {
+            throw new SshException(
+                "The thread was interrupted whilst waiting for an authentication message");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param msg
+     *
+     * @throws IOException
+     */
+    public void sendMessage(SshMessage msg) throws IOException {
+        transport.sendMessage(msg, this);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getSessionIdentifier() {
+        return transport.getSessionIdentifier();
+    }
+
+    /**
+     *
+     *
+     * @param cls
+     * @param messageId
+     */
+    public void registerMessage(Class cls, int messageId) {
+        messageStore.registerMessage(messageId, cls);
+    }
+
+    /**
+     *
+     *
+     * @param messageId
+     *
+     * @return
+     *
+     * @throws TerminatedStateException
+     * @throws IOException
+     */
+    public SshMessage readMessage(int messageId)
+        throws TerminatedStateException, IOException {
+        singleIdFilter[2] = messageId;
+
+        return internalReadMessage(singleIdFilter);
+    }
+
+    private SshMessage internalReadMessage(int[] messageIdFilter)
+        throws TerminatedStateException, IOException {
+        try {
+            SshMessage msg = messageStore.getMessage(messageIdFilter);
+
+            return parseMessage(msg);
+        } catch (MessageStoreEOFException meof) {
+            throw new AuthenticationProtocolException("Failed to read messages");
+        } catch (InterruptedException ex) {
+            throw new SshException(
+                "The thread was interrupted whilst waiting for an authentication message");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param messageId
+     *
+     * @return
+     *
+     * @throws TerminatedStateException
+     * @throws IOException
+     */
+    public SshMessage readMessage(int[] messageId)
+        throws TerminatedStateException, IOException {
+        int[] messageIdFilter = new int[messageId.length + resultFilter.length];
+        System.arraycopy(resultFilter, 0, messageIdFilter, 0,
+            resultFilter.length);
+        System.arraycopy(messageId, 0, messageIdFilter, resultFilter.length,
+            messageId.length);
+
+        return internalReadMessage(messageIdFilter);
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     * @throws TerminatedStateException
+     */
+    public void readAuthenticationState()
+        throws IOException, TerminatedStateException {
+        internalReadMessage(resultFilter);
+    }
+
+    private SshMessage parseMessage(SshMessage msg)
+        throws TerminatedStateException {
+        if (msg instanceof SshMsgUserAuthFailure) {
+            if (((SshMsgUserAuthFailure) msg).getPartialSuccess()) {
+                throw new TerminatedStateException(AuthenticationProtocolState.PARTIAL);
+            } else {
+                throw new TerminatedStateException(AuthenticationProtocolState.FAILED);
+            }
+        } else if (msg instanceof SshMsgUserAuthSuccess) {
+            throw new TerminatedStateException(AuthenticationProtocolState.COMPLETE);
+        } else {
+            return msg;
+        }
+    }
+
+    /**
+     *
+     *
+     * @param timeout
+     *
+     * @return
+     *
+     * @throws IOException
+     * @throws SshException
+     */
+    public String getBannerMessage(int timeout) throws IOException {
+        try {
+            log.debug(
+                "getBannerMessage is attempting to read the authentication banner");
+
+            SshMessage msg = messageStore.peekMessage(SshMsgUserAuthBanner.SSH_MSG_USERAUTH_BANNER,
+                    timeout);
+
+            return ((SshMsgUserAuthBanner) msg).getBanner();
+        } catch (MessageNotAvailableException e) {
+            return "";
+        } catch (MessageStoreEOFException eof) {
+            log.error(
+                "Failed to retreive banner becasue the message store is EOF");
+
+            return "";
+        } catch (InterruptedException ex) {
+            throw new SshException(
+                "The thread was interrupted whilst waiting for an authentication message");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/authentication/AuthenticationProtocolException.java b/src/com/sshtools/j2ssh/authentication/AuthenticationProtocolException.java
new file mode 100644
index 0000000000000000000000000000000000000000..a8757b6c6c83fb4de8bf6939241be1750eb0ec72
--- /dev/null
+++ b/src/com/sshtools/j2ssh/authentication/AuthenticationProtocolException.java
@@ -0,0 +1,46 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.authentication;
+
+import java.io.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.21 $
+ */
+public class AuthenticationProtocolException extends IOException {
+    /**
+     * Creates a new AuthenticationProtocolException object.
+     *
+     * @param msg
+     */
+    public AuthenticationProtocolException(String msg) {
+        super(msg);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/authentication/AuthenticationProtocolListener.java b/src/com/sshtools/j2ssh/authentication/AuthenticationProtocolListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..23f9783e89659d6452a9380b558fcc5ceb52fa92
--- /dev/null
+++ b/src/com/sshtools/j2ssh/authentication/AuthenticationProtocolListener.java
@@ -0,0 +1,54 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.authentication;
+
+
+/**
+ * <p>
+ * Title:
+ * </p>
+ *
+ * <p>
+ * Description:
+ * </p>
+ *
+ * <p>
+ * Copyright: Copyright (c) 2003
+ * </p>
+ *
+ * <p>
+ * Company:
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Id: AuthenticationProtocolListener.java,v 1.8 2003/09/11 15:35:01 martianx Exp $
+ */
+public interface AuthenticationProtocolListener {
+    /**
+     *
+     */
+    public void onAuthenticationComplete();
+}
diff --git a/src/com/sshtools/j2ssh/authentication/AuthenticationProtocolState.java b/src/com/sshtools/j2ssh/authentication/AuthenticationProtocolState.java
new file mode 100644
index 0000000000000000000000000000000000000000..d5509bff6993ba27ca8f4d2a2fbffa382fa2ef45
--- /dev/null
+++ b/src/com/sshtools/j2ssh/authentication/AuthenticationProtocolState.java
@@ -0,0 +1,50 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.authentication;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.22 $
+ */
+public class AuthenticationProtocolState {
+    /**  */
+    public final static int READY = 1;
+
+    /**  */
+    public final static int FAILED = 2;
+
+    /**  */
+    public final static int PARTIAL = 3;
+
+    /**  */
+    public final static int COMPLETE = 4;
+
+    /**  */
+    public final static int CANCELLED = 5;
+}
diff --git a/src/com/sshtools/j2ssh/authentication/HostbasedAuthenticationClient.java b/src/com/sshtools/j2ssh/authentication/HostbasedAuthenticationClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..efc133d8f26095bb3b6da23f2a90d6a4bb9e16ed
--- /dev/null
+++ b/src/com/sshtools/j2ssh/authentication/HostbasedAuthenticationClient.java
@@ -0,0 +1,276 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.authentication;
+
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.publickey.SshPrivateKey;
+import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+
+import java.net.InetAddress;
+
+import java.util.Properties;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.20 $
+ */
+public class HostbasedAuthenticationClient extends SshAuthenticationClient {
+    private static Log log = LogFactory.getLog(HostbasedAuthenticationClient.class);
+
+    /**  */
+    protected SshPrivateKey key;
+    private String privateKeyFile = null;
+    private String passphrase = null;
+    private String clientUser = null;
+
+    /**
+     * Creates a new HostbasedAuthenticationClient object.
+     */
+    public HostbasedAuthenticationClient() {
+    }
+
+    /**
+     *
+     *
+     * @param key
+     */
+    public void setKey(SshPrivateKey key) {
+        this.key = key;
+    }
+
+    /**
+     *
+     */
+    public void reset() {
+        privateKeyFile = null;
+        passphrase = null;
+        clientUser = null;
+    }
+
+    /**
+     *
+     *
+     * @param clientUser
+     */
+    public void setClientUsername(String clientUser) {
+        this.clientUser = clientUser;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMethodName() {
+        return "hostbased";
+    }
+
+    /**
+     *
+     *
+     * @param authentication
+     * @param serviceToStart
+     *
+     * @throws IOException
+     * @throws TerminatedStateException
+     * @throws AuthenticationProtocolException
+     */
+    public void authenticate(AuthenticationProtocolClient authentication,
+        String serviceToStart) throws IOException, TerminatedStateException {
+        if ((getUsername() == null) || (key == null)) {
+            throw new AuthenticationProtocolException(
+                "You must supply a username and a key");
+        }
+
+        ByteArrayWriter baw = new ByteArrayWriter();
+        log.info("Generating data to sign");
+
+        SshPublicKey pub = key.getPublicKey();
+        InetAddress addr = InetAddress.getLocalHost();
+        String hostname = addr.getHostName();
+        log.info("Preparing hostbased authentication request for " + hostname);
+
+        // Now prepare and send the message
+        baw.writeString(pub.getAlgorithmName());
+        baw.writeBinaryString(pub.getEncoded());
+        baw.writeString(hostname);
+
+        if (clientUser != null) {
+            baw.writeString(clientUser);
+        } else {
+            baw.writeString(getUsername());
+        }
+
+        // Create the signature data
+        ByteArrayWriter data = new ByteArrayWriter();
+        data.writeBinaryString(authentication.getSessionIdentifier());
+        data.write(SshMsgUserAuthRequest.SSH_MSG_USERAUTH_REQUEST);
+        data.writeString(getUsername());
+        data.writeString(serviceToStart);
+        data.writeString(getMethodName());
+        data.writeString(pub.getAlgorithmName());
+        data.writeBinaryString(pub.getEncoded());
+        data.writeString(hostname);
+
+        if (clientUser != null) {
+            data.writeString(clientUser);
+        } else {
+            data.writeString(getUsername());
+        }
+
+        // Generate the signature
+        baw.writeBinaryString(key.generateSignature(data.toByteArray()));
+
+        SshMsgUserAuthRequest msg = new SshMsgUserAuthRequest(getUsername(),
+                serviceToStart, getMethodName(), baw.toByteArray());
+        authentication.sendMessage(msg);
+    }
+
+    /* public boolean showAuthenticationDialog(Component parent) {
+        SshPrivateKeyFile pkf = null;
+        if (privateKeyFile == null) {
+     JFileChooser chooser = new JFileChooser();
+     chooser.setFileHidingEnabled(false);
+     chooser.setDialogTitle("Select Private Key File For Authentication");
+     if (chooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) {
+       privateKeyFile = chooser.getSelectedFile().getAbsolutePath();
+     }
+     else {
+       return false;
+     }
+        }
+        FileInputStream in = null;
+        try {
+     pkf = SshPrivateKeyFile.parse(new File(privateKeyFile));
+        }
+        catch (InvalidSshKeyException iske) {
+     JOptionPane.showMessageDialog(parent, iske.getMessage());
+     return false;
+        }
+        catch (IOException ioe) {
+     JOptionPane.showMessageDialog(parent, ioe.getMessage());
+        }
+        // Now see if its passphrase protected
+        if (pkf.isPassphraseProtected()) {
+     if (passphrase == null) {
+       // Show the passphrase dialog
+       Window w = (Window) SwingUtilities.getAncestorOfClass(Window.class,
+           parent);
+       PassphraseDialog dialog = null;
+       if (w instanceof Frame) {
+         dialog = new PassphraseDialog( (Frame) w);
+       }
+       else if (w instanceof Dialog) {
+         dialog = new PassphraseDialog( (Dialog) w);
+       }
+       else {
+         dialog = new PassphraseDialog();
+       }
+       do {
+         dialog.setVisible(true);
+         if (dialog.isCancelled()) {
+           return false;
+         }
+         passphrase = new String(dialog.getPassphrase());
+         try {
+           key = pkf.toPrivateKey(passphrase);
+           break;
+         }
+         catch (InvalidSshKeyException ihke) {
+           dialog.setMessage("Passphrase Invalid! Try again");
+           dialog.setMessageForeground(Color.red);
+         }
+       }
+       while (true);
+     }
+     else {
+       try {
+         key = pkf.toPrivateKey(passphrase);
+       }
+       catch (InvalidSshKeyException ihke) {
+         return false;
+       }
+     }
+        }
+        else {
+     try {
+       key = pkf.toPrivateKey(null);
+     }
+     catch (InvalidSshKeyException ihke) {
+       JOptionPane.showMessageDialog(parent, ihke.getMessage());
+       return false;
+     }
+        }
+        return true;
+      }*/
+    public Properties getPersistableProperties() {
+        Properties properties = new Properties();
+
+        if (getUsername() != null) {
+            properties.setProperty("Username", getUsername());
+        }
+
+        if (privateKeyFile != null) {
+            properties.setProperty("PrivateKey", privateKeyFile);
+        }
+
+        return properties;
+    }
+
+    /**
+     *
+     *
+     * @param properties
+     */
+    public void setPersistableProperties(Properties properties) {
+        setUsername(properties.getProperty("Username"));
+
+        if (properties.getProperty("PrivateKey") != null) {
+            privateKeyFile = properties.getProperty("PrivateKey");
+        }
+
+        if (properties.getProperty("Passphrase") != null) {
+            passphrase = properties.getProperty("Passphrase");
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean canAuthenticate() {
+        return ((getUsername() != null) && (key != null));
+    }
+}
diff --git a/src/com/sshtools/j2ssh/authentication/KBIAuthenticationClient.java b/src/com/sshtools/j2ssh/authentication/KBIAuthenticationClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..15b4ae191598738a818cf03a9370ce86c2fa5ad0
--- /dev/null
+++ b/src/com/sshtools/j2ssh/authentication/KBIAuthenticationClient.java
@@ -0,0 +1,166 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.authentication;
+
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.SshMessage;
+
+import java.util.Properties;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.19 $
+ */
+public class KBIAuthenticationClient extends SshAuthenticationClient {
+    KBIRequestHandler handler;
+
+    /**
+     *
+     *
+     * @return
+     */
+    public Properties getPersistableProperties() {
+        return new Properties();
+    }
+
+    /**
+     *
+     *
+     * @param handler
+     */
+    public void setKBIRequestHandler(KBIRequestHandler handler) {
+        this.handler = handler;
+    }
+
+    /**
+     *
+     */
+    public void reset() {
+    }
+
+    /**
+     *
+     *
+     * @param authentication
+     * @param serviceToStart
+     *
+     * @throws com.sshtools.j2ssh.authentication.TerminatedStateException
+     *
+     * @throws java.io.IOException
+     * @throws AuthenticationProtocolException
+     */
+    public void authenticate(AuthenticationProtocolClient authentication,
+        String serviceToStart)
+        throws com.sshtools.j2ssh.authentication.TerminatedStateException, 
+            java.io.IOException {
+        if (handler == null) {
+            throw new AuthenticationProtocolException(
+                "A request handler must be set!");
+        }
+
+        authentication.registerMessage(SshMsgUserAuthInfoRequest.class,
+            SshMsgUserAuthInfoRequest.SSH_MSG_USERAUTH_INFO_REQUEST);
+
+        // Send the authentication request
+        ByteArrayWriter baw = new ByteArrayWriter();
+        baw.writeString("");
+        baw.writeString("");
+
+        SshMessage msg = new SshMsgUserAuthRequest(getUsername(),
+                serviceToStart, getMethodName(), baw.toByteArray());
+        authentication.sendMessage(msg);
+
+        // Read a message
+        while (true) {
+            msg = authentication.readMessage(SshMsgUserAuthInfoRequest.SSH_MSG_USERAUTH_INFO_REQUEST);
+
+            if (msg instanceof SshMsgUserAuthInfoRequest) {
+                SshMsgUserAuthInfoRequest request = (SshMsgUserAuthInfoRequest) msg;
+                KBIPrompt[] prompts = request.getPrompts();
+                handler.showPrompts(request.getName(),
+                    request.getInstruction(), prompts);
+
+                // Now send the response message
+                msg = new SshMsgUserAuthInfoResponse(prompts);
+                authentication.sendMessage(msg);
+            } else {
+                throw new AuthenticationProtocolException(
+                    "Unexpected authentication message " +
+                    msg.getMessageName());
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean canAuthenticate() {
+        return true;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMethodName() {
+        return "keyboard-interactive";
+    }
+
+    /**
+     *
+     *
+     * @param properties
+     */
+    public void setPersistableProperties(Properties properties) {
+    }
+
+    /* public boolean showAuthenticationDialog(Component parent)
+      throws java.io.IOException {
+      final Component myparent = parent;
+      this.handler = new KBIRequestHandlerDialog();
+      //        this.handler = new KBIRequestHandler() {
+      //                    public void showPrompts(String name, String instructions,
+      //                        KBIPrompt[] prompts) {
+      //                        if (prompts != null) {
+      //                            for (int i = 0; i < prompts.length; i++) {
+      //                                // We can echo the response back to the client
+      //                                prompts[i].setResponse((JOptionPane
+      //                                    .showInputDialog(myparent,
+      //                                        prompts[i].getPrompt(), name,
+      //                                        JOptionPane.QUESTION_MESSAGE)));
+      //                            }
+      //                        }
+      //                    }
+      //                };
+      return true;
+       }*/
+}
diff --git a/src/com/sshtools/j2ssh/authentication/KBIPrompt.java b/src/com/sshtools/j2ssh/authentication/KBIPrompt.java
new file mode 100644
index 0000000000000000000000000000000000000000..68c976f60c78d439f2fe1e95ca6b42dabb199468
--- /dev/null
+++ b/src/com/sshtools/j2ssh/authentication/KBIPrompt.java
@@ -0,0 +1,86 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.authentication;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class KBIPrompt {
+    private String prompt;
+    private String response;
+    private boolean echo;
+
+    /**
+     * Creates a new KBIPrompt object.
+     *
+     * @param prompt
+     * @param echo
+     */
+    protected KBIPrompt(String prompt, boolean echo) {
+        this.prompt = prompt;
+        this.echo = echo;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getPrompt() {
+        return prompt;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean echo() {
+        return echo;
+    }
+
+    /**
+     *
+     *
+     * @param response
+     */
+    public void setResponse(String response) {
+        this.response = response;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getResponse() {
+        return response;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/authentication/KBIRequestHandler.java b/src/com/sshtools/j2ssh/authentication/KBIRequestHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..6780f722b55ca156babe0d30f6178293748472cc
--- /dev/null
+++ b/src/com/sshtools/j2ssh/authentication/KBIRequestHandler.java
@@ -0,0 +1,44 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.authentication;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public interface KBIRequestHandler {
+    /**
+     *
+     *
+     * @param name
+     * @param instruction
+     * @param prompts
+     */
+    public void showPrompts(String name, String instruction, KBIPrompt[] prompts);
+}
diff --git a/src/com/sshtools/j2ssh/authentication/PasswordAuthenticationClient.java b/src/com/sshtools/j2ssh/authentication/PasswordAuthenticationClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..875294669987ed6391c17b4a21da16c7b798202e
--- /dev/null
+++ b/src/com/sshtools/j2ssh/authentication/PasswordAuthenticationClient.java
@@ -0,0 +1,188 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.authentication;
+
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+
+import java.util.Properties;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.22 $
+ */
+public class PasswordAuthenticationClient extends SshAuthenticationClient {
+    private static Log log = LogFactory.getLog(PasswordAuthenticationClient.class);
+    private PasswordChangePrompt changePrompt = null;
+
+    /**  */
+    protected String password = null;
+
+    /**
+     *
+     *
+     * @return
+     */
+    public final String getMethodName() {
+        return "password";
+    }
+
+    /**
+     *
+     *
+     * @param password
+     */
+    public final void setPassword(String password) {
+        this.password = password;
+    }
+
+    /**
+     *
+     */
+    public void reset() {
+        password = null;
+    }
+
+    /**
+     *
+     *
+     * @param changePrompt
+     */
+    public void setPasswordChangePrompt(PasswordChangePrompt changePrompt) {
+        this.changePrompt = changePrompt;
+    }
+
+    /*public boolean showAuthenticationDialog(Component parent)
+     throws AuthenticationProtocolException {
+     if (password != null) {
+         return true;
+     }
+     // Create the password authentication dialog
+     Window w = (Window) SwingUtilities.getAncestorOfClass(Window.class,
+        parent);
+     PasswordAuthenticationDialog dialog = null;
+     if (w instanceof Frame) {
+         dialog = new PasswordAuthenticationDialog((Frame) w);
+     } else if (w instanceof Dialog) {
+         dialog = new PasswordAuthenticationDialog((Dialog) w);
+     } else {
+         dialog = new PasswordAuthenticationDialog();
+     }
+     // Show the dialog
+     if (dialog.showPromptForPassword(getUsername())) {
+         setUsername(dialog.getUsername());
+         setPassword(dialog.getPassword());
+         return true;
+     }
+     return false;
+      }*/
+    /*public void setAuthenticatedTokens(Map tokens) {
+      }*/
+    public void authenticate(AuthenticationProtocolClient authentication,
+        String serviceToStart) throws IOException, TerminatedStateException {
+        if ((getUsername() == null) || (password == null)) {
+            throw new AuthenticationProtocolException(
+                "Username and password cannot be null!");
+        }
+
+        authentication.registerMessage(SshMsgUserAuthPwdChangeReq.class,
+            SshMsgUserAuthPwdChangeReq.SSH_MSG_USERAUTH_PWD_CHANGEREQ);
+
+        // Send a password authentication request
+        ByteArrayWriter baw = new ByteArrayWriter();
+        baw.write(0);
+        baw.writeString(password);
+
+        SshMsgUserAuthRequest msg = new SshMsgUserAuthRequest(getUsername(),
+                serviceToStart, "password", baw.toByteArray());
+        authentication.sendMessage(msg);
+
+        SshMsgUserAuthPwdChangeReq pwd = (SshMsgUserAuthPwdChangeReq) authentication.readMessage(SshMsgUserAuthPwdChangeReq.SSH_MSG_USERAUTH_PWD_CHANGEREQ);
+
+        if (changePrompt != null) {
+            String newpassword = changePrompt.changePassword(pwd.getPrompt());
+
+            if (newpassword != null) {
+                log.debug("Setting new password");
+                baw = new ByteArrayWriter();
+                baw.write(1);
+                baw.writeString(password);
+                baw.writeString(newpassword);
+                msg = new SshMsgUserAuthRequest(getUsername(), serviceToStart,
+                        "password", baw.toByteArray());
+                authentication.sendMessage(msg);
+            } else {
+                throw new TerminatedStateException(AuthenticationProtocolState.FAILED);
+            }
+        } else {
+            throw new TerminatedStateException(AuthenticationProtocolState.FAILED);
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public Properties getPersistableProperties() {
+        Properties properties = new Properties();
+
+        if (getUsername() != null) {
+            properties.setProperty("Username", getUsername());
+        }
+
+        return properties;
+    }
+
+    /**
+     *
+     *
+     * @param properties
+     */
+    public void setPersistableProperties(Properties properties) {
+        setUsername(properties.getProperty("Username"));
+
+        if (properties.getProperty("Password") != null) {
+            setPassword(properties.getProperty("Password"));
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean canAuthenticate() {
+        return ((getUsername() != null) && (password != null));
+    }
+}
diff --git a/src/com/sshtools/j2ssh/authentication/PasswordChangePrompt.java b/src/com/sshtools/j2ssh/authentication/PasswordChangePrompt.java
new file mode 100644
index 0000000000000000000000000000000000000000..5fd9eb47242b08471a39e54ec75b7c678e9e6d94
--- /dev/null
+++ b/src/com/sshtools/j2ssh/authentication/PasswordChangePrompt.java
@@ -0,0 +1,44 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.authentication;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public interface PasswordChangePrompt {
+    /**
+     *
+     *
+     * @param prompt
+     *
+     * @return
+     */
+    public String changePassword(String prompt);
+}
diff --git a/src/com/sshtools/j2ssh/authentication/PublicKeyAuthenticationClient.java b/src/com/sshtools/j2ssh/authentication/PublicKeyAuthenticationClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..11712d0ff08d9982b468b71da295911d02f717a8
--- /dev/null
+++ b/src/com/sshtools/j2ssh/authentication/PublicKeyAuthenticationClient.java
@@ -0,0 +1,292 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.authentication;
+
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.SshMessage;
+import com.sshtools.j2ssh.transport.publickey.SshPrivateKey;
+import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+
+import java.util.Properties;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.21 $
+ */
+public class PublicKeyAuthenticationClient extends SshAuthenticationClient {
+    private static Log log = LogFactory.getLog(PublicKeyAuthenticationClient.class);
+
+    /**  */
+    protected SshPrivateKey key;
+    private String privateKeyFile = null;
+    private String passphrase = null;
+
+    /**
+     * Creates a new PublicKeyAuthenticationClient object.
+     */
+    public PublicKeyAuthenticationClient() {
+    }
+
+    /**
+     *
+     *
+     * @param key
+     */
+    public void setKey(SshPrivateKey key) {
+        this.key = key;
+    }
+
+    public void setKeyfile(String privateKeyFile) {
+        this.privateKeyFile = privateKeyFile;
+    }
+
+    public String getKeyfile() {
+        return privateKeyFile;
+    }
+
+    /**
+     *
+     */
+    public void reset() {
+        privateKeyFile = null;
+        passphrase = null;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMethodName() {
+        return "publickey";
+    }
+
+    /**
+     *
+     *
+     * @param authentication
+     * @param username
+     * @param serviceToStart
+     * @param key
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public boolean acceptsKey(AuthenticationProtocolClient authentication,
+        String username, String serviceToStart, SshPublicKey key)
+        throws IOException {
+        authentication.registerMessage(SshMsgUserAuthPKOK.class,
+            SshMsgUserAuthPKOK.SSH_MSG_USERAUTH_PK_OK);
+        log.info(
+            "Determining if server can accept public key for authentication");
+
+        ByteArrayWriter baw = new ByteArrayWriter();
+
+        // Now prepare and send the message
+        baw.write(0);
+        baw.writeString(key.getAlgorithmName());
+        baw.writeBinaryString(key.getEncoded());
+
+        SshMessage msg = new SshMsgUserAuthRequest(username, serviceToStart,
+                getMethodName(), baw.toByteArray());
+        authentication.sendMessage(msg);
+
+        try {
+            msg = authentication.readMessage(SshMsgUserAuthPKOK.SSH_MSG_USERAUTH_PK_OK);
+
+            if (msg instanceof SshMsgUserAuthPKOK) {
+                return true;
+            } else {
+                throw new IOException(
+                    "Unexpected message returned from readMessage");
+            }
+        } catch (TerminatedStateException ex) {
+            return false;
+        }
+    }
+
+    /**
+     *
+     *
+     * @param authentication
+     * @param serviceToStart
+     *
+     * @throws IOException
+     * @throws TerminatedStateException
+     * @throws AuthenticationProtocolException
+     */
+    public void authenticate(AuthenticationProtocolClient authentication,
+        String serviceToStart) throws IOException, TerminatedStateException {
+        if ((getUsername() == null) || (key == null)) {
+            throw new AuthenticationProtocolException(
+                "You must supply a username and a key");
+        }
+
+        ByteArrayWriter baw = new ByteArrayWriter();
+        log.info("Generating data to sign");
+
+        SshPublicKey pub = key.getPublicKey();
+        log.info("Preparing public key authentication request");
+
+        // Now prepare and send the message
+        baw.write(1);
+        baw.writeString(pub.getAlgorithmName());
+        baw.writeBinaryString(pub.getEncoded());
+
+        // Create the signature data
+        ByteArrayWriter data = new ByteArrayWriter();
+        data.writeBinaryString(authentication.getSessionIdentifier());
+        data.write(SshMsgUserAuthRequest.SSH_MSG_USERAUTH_REQUEST);
+        data.writeString(getUsername());
+        data.writeString(serviceToStart);
+        data.writeString(getMethodName());
+        data.write(1);
+        data.writeString(pub.getAlgorithmName());
+        data.writeBinaryString(pub.getEncoded());
+
+        // Generate the signature
+        baw.writeBinaryString(key.generateSignature(data.toByteArray()));
+
+        SshMsgUserAuthRequest msg = new SshMsgUserAuthRequest(getUsername(),
+                serviceToStart, getMethodName(), baw.toByteArray());
+        authentication.sendMessage(msg);
+    }
+
+    /*public boolean showAuthenticationDialog(Component parent) {
+         SshPrivateKeyFile pkf = null;
+         if (privateKeyFile == null) {
+        JFileChooser chooser = new JFileChooser();
+        chooser.setFileHidingEnabled(false);
+        chooser.setDialogTitle("Select Private Key File For Authentication");
+        if (chooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) {
+            privateKeyFile = chooser.getSelectedFile().getAbsolutePath();
+        } else {
+            return false;
+        }
+         }
+         FileInputStream in = null;
+         try {
+        pkf = SshPrivateKeyFile.parse(new File(privateKeyFile));
+         } catch (InvalidSshKeyException iske) {
+        JOptionPane.showMessageDialog(parent, iske.getMessage());
+        return false;
+         } catch (IOException ioe) {
+        JOptionPane.showMessageDialog(parent, ioe.getMessage());
+         }
+         // Now see if its passphrase protected
+         if (pkf.isPassphraseProtected()) {
+        if (passphrase == null) {
+            // Show the passphrase dialog
+     Window w = (Window) SwingUtilities.getAncestorOfClass(Window.class,
+                    parent);
+            PassphraseDialog dialog = null;
+            if (w instanceof Frame) {
+                dialog = new PassphraseDialog((Frame) w);
+            } else if (w instanceof Dialog) {
+                dialog = new PassphraseDialog((Dialog) w);
+            } else {
+                dialog = new PassphraseDialog();
+            }
+            do {
+                dialog.setVisible(true);
+                if (dialog.isCancelled()) {
+                    return false;
+                }
+                passphrase = new String(dialog.getPassphrase());
+                try {
+                    key = pkf.toPrivateKey(passphrase);
+                    break;
+                } catch (InvalidSshKeyException ihke) {
+                    dialog.setMessage("Passphrase Invalid! Try again");
+                    dialog.setMessageForeground(Color.red);
+                }
+            } while (true);
+        } else {
+            try {
+                key = pkf.toPrivateKey(passphrase);
+            } catch (InvalidSshKeyException ihke) {
+                return false;
+            }
+        }
+         } else {
+        try {
+            key = pkf.toPrivateKey(null);
+        } catch (InvalidSshKeyException ihke) {
+            JOptionPane.showMessageDialog(parent, ihke.getMessage());
+            return false;
+        }
+         }
+         return true;
+     }*/
+    public Properties getPersistableProperties() {
+        Properties properties = new Properties();
+
+        if (getUsername() != null) {
+            properties.setProperty("Username", getUsername());
+        }
+
+        if (privateKeyFile != null) {
+            properties.setProperty("PrivateKey", privateKeyFile);
+        }
+
+        return properties;
+    }
+
+    /**
+     *
+     *
+     * @param properties
+     */
+    public void setPersistableProperties(Properties properties) {
+        setUsername(properties.getProperty("Username"));
+
+        if (properties.getProperty("PrivateKey") != null) {
+            privateKeyFile = properties.getProperty("PrivateKey");
+        }
+
+        if (properties.getProperty("Passphrase") != null) {
+            passphrase = properties.getProperty("Passphrase");
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean canAuthenticate() {
+        return ((getUsername() != null) && (key != null));
+    }
+}
diff --git a/src/com/sshtools/j2ssh/authentication/SshAuthenticationClient.java b/src/com/sshtools/j2ssh/authentication/SshAuthenticationClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..1fd8c6e1afed2444388cf6f653589b345700bcba
--- /dev/null
+++ b/src/com/sshtools/j2ssh/authentication/SshAuthenticationClient.java
@@ -0,0 +1,137 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.authentication;
+
+import java.io.IOException;
+
+import java.util.Properties;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public abstract class SshAuthenticationClient {
+    private String username;
+    private SshAuthenticationPrompt prompt;
+
+    /**
+     *
+     *
+     * @return
+     */
+    public abstract String getMethodName();
+
+    /**
+     *
+     *
+     * @param authentication
+     * @param serviceToStart
+     *
+     * @throws IOException
+     * @throws TerminatedStateException
+     */
+    public abstract void authenticate(
+        AuthenticationProtocolClient authentication, String serviceToStart)
+        throws IOException, TerminatedStateException;
+
+    /**
+     *
+     *
+     * @param prompt
+     *
+     * @throws AuthenticationProtocolException
+     */
+    public void setAuthenticationPrompt(SshAuthenticationPrompt prompt)
+        throws AuthenticationProtocolException {
+        //prompt.setInstance(this);
+        this.prompt = prompt;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public SshAuthenticationPrompt getAuthenticationPrompt() {
+        return prompt;
+    }
+
+    /**
+     *
+     *
+     * @param username
+     */
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getUsername() {
+        return username;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public abstract Properties getPersistableProperties();
+
+    /**
+     *
+     */
+    public abstract void reset();
+
+    /**
+     *
+     *
+     * @param properties
+     */
+    public abstract void setPersistableProperties(Properties properties);
+
+    /**
+     *
+     *
+     * @return
+     */
+    public abstract boolean canAuthenticate();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean canPrompt() {
+        return prompt != null;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/authentication/SshAuthenticationClientFactory.java b/src/com/sshtools/j2ssh/authentication/SshAuthenticationClientFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..d00c762f86dcc22c044e52f1b1200d26e3201ff5
--- /dev/null
+++ b/src/com/sshtools/j2ssh/authentication/SshAuthenticationClientFactory.java
@@ -0,0 +1,175 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.authentication;
+
+import com.sshtools.j2ssh.configuration.ConfigurationException;
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+import com.sshtools.j2ssh.configuration.ExtensionAlgorithm;
+import com.sshtools.j2ssh.configuration.SshAPIConfiguration;
+import com.sshtools.j2ssh.transport.AlgorithmNotSupportedException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.FilePermission;
+
+import java.security.AccessControlException;
+import java.security.AccessController;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.20 $
+ */
+public class SshAuthenticationClientFactory {
+    private static Map auths;
+    private static Log log = LogFactory.getLog(SshAuthenticationClientFactory.class);
+
+    /**  */
+    public final static String AUTH_PASSWORD = "password";
+
+    /**  */
+    public final static String AUTH_PK = "publickey";
+
+    /**  */
+    public final static String AUTH_KBI = "keyboard-interactive";
+
+    /**  */
+    public final static String AUTH_HOSTBASED = "hostbased";
+
+    static {
+        auths = new HashMap();
+        log.info("Loading supported authentication methods");
+        auths.put(AUTH_PASSWORD, PasswordAuthenticationClient.class);
+
+        //  Only allow key authentication if we are able to open local files
+        try {
+            if (System.getSecurityManager() != null) {
+                AccessController.checkPermission(new FilePermission(
+                        "<<ALL FILES>>", "read"));
+            }
+
+            auths.put(AUTH_PK, PublicKeyAuthenticationClient.class);
+        } catch (AccessControlException ace) {
+            log.info(
+                "The security manager prevents use of Public Key Authentication on the client");
+        }
+
+        auths.put(AUTH_KBI, KBIAuthenticationClient.class);
+
+        //auths.put(AUTH_HOSTBASED, HostbasedAuthenticationClient.class);
+        try {
+            // Load external methods from configuration file
+            if (ConfigurationLoader.isConfigurationAvailable(
+                        SshAPIConfiguration.class)) {
+                SshAPIConfiguration config = (SshAPIConfiguration) ConfigurationLoader.getConfiguration(SshAPIConfiguration.class);
+                List addons = config.getAuthenticationExtensions();
+                Iterator it = addons.iterator();
+
+                // Add the methods to our supported list
+                while (it.hasNext()) {
+                    ExtensionAlgorithm method = (ExtensionAlgorithm) it.next();
+                    String name = method.getAlgorithmName();
+
+                    if (auths.containsKey(name)) {
+                        log.debug("Standard authentication implementation for " +
+                            name + " is being overidden by " +
+                            method.getImplementationClass());
+                    } else {
+                        log.debug(name + " authentication is implemented by " +
+                            method.getImplementationClass());
+                    }
+
+                    try {
+                        Class cls = ConfigurationLoader.getExtensionClass(method.getImplementationClass());
+                        Object obj = cls.newInstance();
+
+                        if (obj instanceof SshAuthenticationClient) {
+                            auths.put(name, cls);
+                        }
+                    } catch (Exception e) {
+                        log.warn(
+                            "Failed to load extension authentication implementation" +
+                            method.getImplementationClass(), e);
+                    }
+                }
+            }
+        } catch (ConfigurationException ex) {
+        }
+    }
+
+    /**
+     * Creates a new SshAuthenticationClientFactory object.
+     */
+    protected SshAuthenticationClientFactory() {
+    }
+
+    /**
+     *
+     */
+    public static void initialize() {
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public static List getSupportedMethods() {
+        // Get the list of ciphers
+        ArrayList list = new ArrayList(auths.keySet());
+
+        // Return the list
+        return list;
+    }
+
+    /**
+     *
+     *
+     * @param methodName
+     *
+     * @return
+     *
+     * @throws AlgorithmNotSupportedException
+     */
+    public static SshAuthenticationClient newInstance(String methodName)
+        throws AlgorithmNotSupportedException {
+        try {
+            return (SshAuthenticationClient) ((Class) auths.get(methodName)).newInstance();
+        } catch (Exception e) {
+            throw new AlgorithmNotSupportedException(methodName +
+                " is not supported!");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/authentication/SshAuthenticationPrompt.java b/src/com/sshtools/j2ssh/authentication/SshAuthenticationPrompt.java
new file mode 100644
index 0000000000000000000000000000000000000000..e51e76db3b26868e01c7e69a0d7790b82b0ab865
--- /dev/null
+++ b/src/com/sshtools/j2ssh/authentication/SshAuthenticationPrompt.java
@@ -0,0 +1,54 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.authentication;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public interface SshAuthenticationPrompt {
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean showPrompt(SshAuthenticationClient instance)
+        throws AuthenticationProtocolException;
+
+    /**
+     *
+     *
+     * @param instance
+     *
+     * @throws AuthenticationProtocolException
+     */
+
+    /* public void setInstance(SshAuthenticationClient instance)
+         throws AuthenticationProtocolException;*/
+}
diff --git a/src/com/sshtools/j2ssh/authentication/SshMsgUserAuthBanner.java b/src/com/sshtools/j2ssh/authentication/SshMsgUserAuthBanner.java
new file mode 100644
index 0000000000000000000000000000000000000000..f9f65f2746449f6435750cb336d408381befb764
--- /dev/null
+++ b/src/com/sshtools/j2ssh/authentication/SshMsgUserAuthBanner.java
@@ -0,0 +1,126 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.authentication;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.SshMessage;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.25 $
+ */
+public class SshMsgUserAuthBanner extends SshMessage {
+    /**  */
+    public final static int SSH_MSG_USERAUTH_BANNER = 53;
+    private String banner;
+    private String languageTag;
+
+    /**
+     * Creates a new SshMsgUserAuthBanner object.
+     */
+    public SshMsgUserAuthBanner() {
+        super(SSH_MSG_USERAUTH_BANNER);
+    }
+
+    /**
+     * Creates a new SshMsgUserAuthBanner object.
+     *
+     * @param banner
+     */
+    public SshMsgUserAuthBanner(String banner) {
+        super(SSH_MSG_USERAUTH_BANNER);
+        this.banner = banner;
+        this.languageTag = "";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getBanner() {
+        return banner;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getLanguageTag() {
+        return languageTag;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_USERAUTH_BANNER";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+        try {
+            baw.writeString(banner);
+            baw.writeString(languageTag);
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Error writing the message data");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+        try {
+            banner = bar.readString();
+            languageTag = bar.readString();
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Error reading the message data");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/authentication/SshMsgUserAuthFailure.java b/src/com/sshtools/j2ssh/authentication/SshMsgUserAuthFailure.java
new file mode 100644
index 0000000000000000000000000000000000000000..47bd751497b504cfc72aa40ea4d19a576859987d
--- /dev/null
+++ b/src/com/sshtools/j2ssh/authentication/SshMsgUserAuthFailure.java
@@ -0,0 +1,153 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.authentication;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.SshMessage;
+
+import java.io.IOException;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.StringTokenizer;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.23 $
+ */
+public class SshMsgUserAuthFailure extends SshMessage {
+    /**  */
+    protected final static int SSH_MSG_USERAUTH_FAILURE = 51;
+    private List auths;
+    private boolean partialSuccess;
+
+    /**
+     * Creates a new SshMsgUserAuthFailure object.
+     */
+    public SshMsgUserAuthFailure() {
+        super(SSH_MSG_USERAUTH_FAILURE);
+    }
+
+    /**
+     * Creates a new SshMsgUserAuthFailure object.
+     *
+     * @param auths
+     * @param partialSuccess
+     *
+     * @throws InvalidMessageException
+     */
+    public SshMsgUserAuthFailure(String auths, boolean partialSuccess)
+        throws InvalidMessageException {
+        super(SSH_MSG_USERAUTH_FAILURE);
+        loadListFromDelimString(auths);
+        this.partialSuccess = partialSuccess;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public List getAvailableAuthentications() {
+        return auths;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_USERAUTH_FAILURE";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean getPartialSuccess() {
+        return partialSuccess;
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+        try {
+            String authMethods = null;
+            Iterator it = auths.iterator();
+
+            while (it.hasNext()) {
+                authMethods = ((authMethods == null) ? "" : (authMethods + ",")) +
+                    (String) it.next();
+            }
+
+            baw.writeString(authMethods);
+            baw.write((partialSuccess ? 1 : 0));
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Invalid message data");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+        try {
+            String auths = bar.readString();
+            partialSuccess = ((bar.read() != 0) ? true : false);
+            loadListFromDelimString(auths);
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Invalid message data");
+        }
+    }
+
+    private void loadListFromDelimString(String list) {
+        StringTokenizer tok = new StringTokenizer(list, ",");
+        auths = new ArrayList();
+
+        while (tok.hasMoreElements()) {
+            auths.add(tok.nextElement());
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/authentication/SshMsgUserAuthInfoRequest.java b/src/com/sshtools/j2ssh/authentication/SshMsgUserAuthInfoRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..dccde08a6f821cdaaf9f177bff32e77b702e3dc4
--- /dev/null
+++ b/src/com/sshtools/j2ssh/authentication/SshMsgUserAuthInfoRequest.java
@@ -0,0 +1,209 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.authentication;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.SshMessage;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public class SshMsgUserAuthInfoRequest extends SshMessage {
+    /**  */
+    public static final int SSH_MSG_USERAUTH_INFO_REQUEST = 60;
+    private String name;
+    private String instruction;
+    private String langtag;
+    private KBIPrompt[] prompts;
+
+    /**
+     * Creates a new SshMsgUserAuthInfoRequest object.
+     */
+    public SshMsgUserAuthInfoRequest() {
+        super(SSH_MSG_USERAUTH_INFO_REQUEST);
+    }
+
+    /**
+     * Creates a new SshMsgUserAuthInfoRequest object.
+     *
+     * @param name
+     * @param instruction
+     * @param langtag
+     */
+    public SshMsgUserAuthInfoRequest(String name, String instruction,
+        String langtag) {
+        super(SSH_MSG_USERAUTH_INFO_REQUEST);
+        this.name = name;
+        this.instruction = instruction;
+        this.langtag = langtag;
+    }
+
+    /**
+     *
+     *
+     * @param prompt
+     * @param echo
+     */
+    public void addPrompt(String prompt, boolean echo) {
+        if (prompts == null) {
+            prompts = new KBIPrompt[1];
+            prompts[0] = new KBIPrompt(prompt, echo);
+        } else {
+            KBIPrompt[] temp = new KBIPrompt[prompts.length + 1];
+            System.arraycopy(prompts, 0, temp, 0, prompts.length);
+            prompts = temp;
+            prompts[prompts.length - 1] = new KBIPrompt(prompt, echo);
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public KBIPrompt[] getPrompts() {
+        return prompts;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getInstruction() {
+        return instruction;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getLanguageTag() {
+        return langtag;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_USERAUTH_INFO_REQUEST";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            if (name != null) {
+                baw.writeString(name);
+            } else {
+                baw.writeString("");
+            }
+
+            if (instruction != null) {
+                baw.writeString(instruction);
+            } else {
+                baw.writeString("");
+            }
+
+            if (langtag != null) {
+                baw.writeString(langtag);
+            } else {
+                baw.writeString("");
+            }
+
+            if (prompts == null) {
+                baw.writeInt(0);
+            } else {
+                baw.writeInt(prompts.length);
+
+                for (int i = 0; i < prompts.length; i++) {
+                    baw.writeString(prompts[i].getPrompt());
+                    baw.write(prompts[i].echo() ? 1 : 0);
+                }
+            }
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Failed to write message data");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            name = bar.readString();
+            instruction = bar.readString();
+            langtag = bar.readString();
+
+            long num = bar.readInt();
+            String prompt;
+            boolean echo;
+
+            for (int i = 0; i < num; i++) {
+                prompt = bar.readString();
+                echo = (bar.read() == 1);
+                addPrompt(prompt, echo);
+            }
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Failed to read message data");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/authentication/SshMsgUserAuthInfoResponse.java b/src/com/sshtools/j2ssh/authentication/SshMsgUserAuthInfoResponse.java
new file mode 100644
index 0000000000000000000000000000000000000000..46167411c8427fd3ec757775f61ffa7f864e5089
--- /dev/null
+++ b/src/com/sshtools/j2ssh/authentication/SshMsgUserAuthInfoResponse.java
@@ -0,0 +1,141 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.authentication;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.SshMessage;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public class SshMsgUserAuthInfoResponse extends SshMessage {
+    /**  */
+    public static final int SSH_MSG_USERAUTH_INFO_RESPONSE = 61;
+    private KBIPrompt[] prompts;
+    String[] responses;
+
+    /**
+     * Creates a new SshMsgUserAuthInfoResponse object.
+     */
+    public SshMsgUserAuthInfoResponse() {
+        super(SSH_MSG_USERAUTH_INFO_RESPONSE);
+    }
+
+    /**
+     * Creates a new SshMsgUserAuthInfoResponse object.
+     *
+     * @param prompts
+     */
+    public SshMsgUserAuthInfoResponse(KBIPrompt[] prompts) {
+        super(SSH_MSG_USERAUTH_INFO_RESPONSE);
+
+        if (prompts != null) {
+            responses = new String[prompts.length];
+
+            for (int i = 0; i < responses.length; i++) {
+                responses[i] = prompts[i].getResponse();
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_USERAUTH_INFO_RESPONSE";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String[] getResponses() {
+        return responses;
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            if (responses == null) {
+                baw.writeInt(0);
+            } else {
+                baw.writeInt(responses.length);
+
+                for (int i = 0; i < responses.length; i++) {
+                    baw.writeString(responses[i]);
+                }
+            }
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Failed to write message data");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws com.sshtools.j2ssh.transport.InvalidMessageException {
+        try {
+            int num = (int) bar.readInt();
+
+            if (num > 0) {
+                responses = new String[num];
+
+                for (int i = 0; i < responses.length; i++) {
+                    responses[i] = bar.readString();
+                }
+            }
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Failed to read message data");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/authentication/SshMsgUserAuthPKOK.java b/src/com/sshtools/j2ssh/authentication/SshMsgUserAuthPKOK.java
new file mode 100644
index 0000000000000000000000000000000000000000..768085c9d3c9915daca2f8876f11486203ff91cb
--- /dev/null
+++ b/src/com/sshtools/j2ssh/authentication/SshMsgUserAuthPKOK.java
@@ -0,0 +1,127 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.authentication;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.SshMessage;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.22 $
+ */
+public class SshMsgUserAuthPKOK extends SshMessage {
+    /**  */
+    public final static int SSH_MSG_USERAUTH_PK_OK = 60;
+    private String algorithm;
+    private byte[] key;
+
+    //private boolean ok;
+
+    /**
+     * Creates a new SshMsgUserAuthPKOK object.
+     */
+    public SshMsgUserAuthPKOK() {
+        super(SSH_MSG_USERAUTH_PK_OK);
+    }
+
+    /**
+     * Creates a new SshMsgUserAuthPKOK object.
+     *
+     * @param ok
+     * @param algorithm
+     * @param key
+     */
+    public SshMsgUserAuthPKOK( /*boolean ok,*/
+        String algorithm, byte[] key) {
+        super(SSH_MSG_USERAUTH_PK_OK);
+
+        //this.ok = ok;
+        this.algorithm = algorithm;
+        this.key = key;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+
+    /*public boolean isOk() {
+      return ok;
+       }*/
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_USERAUTH_PK_OK";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+        try {
+            //baw.write(ok ? 1 : 0);
+            baw.writeString(algorithm);
+            baw.writeBinaryString(key);
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Failed to write message data!");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+        try {
+            //ok = ((bar.read() == 1) ? true : false);
+            algorithm = bar.readString();
+            key = bar.readBinaryString();
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Failed to read message data!");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/authentication/SshMsgUserAuthPwdChangeReq.java b/src/com/sshtools/j2ssh/authentication/SshMsgUserAuthPwdChangeReq.java
new file mode 100644
index 0000000000000000000000000000000000000000..aeb7c23d0587e59409bf9c240c7399686df5ecb5
--- /dev/null
+++ b/src/com/sshtools/j2ssh/authentication/SshMsgUserAuthPwdChangeReq.java
@@ -0,0 +1,125 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.authentication;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.SshMessage;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public class SshMsgUserAuthPwdChangeReq extends SshMessage {
+    /**  */
+    public static final int SSH_MSG_USERAUTH_PWD_CHANGEREQ = 60;
+    private String prompt;
+    private String language;
+
+    /**
+     * Creates a new SshMsgUserAuthPwdChangeReq object.
+     */
+    public SshMsgUserAuthPwdChangeReq() {
+        super(SSH_MSG_USERAUTH_PWD_CHANGEREQ);
+    }
+
+    /**
+     * Creates a new SshMsgUserAuthPwdChangeReq object.
+     *
+     * @param prompt
+     * @param language
+     */
+    public SshMsgUserAuthPwdChangeReq(String prompt, String language) {
+        super(SSH_MSG_USERAUTH_PWD_CHANGEREQ);
+        this.prompt = prompt;
+        this.language = language;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getPrompt() {
+        return prompt;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getLanguage() {
+        return language;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_USERAUTH_PWD_CHANGEREQ";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+        try {
+            baw.writeString(prompt);
+            baw.writeString(language);
+        } catch (Exception ex) {
+            throw new InvalidMessageException(ex.getMessage());
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+        try {
+            prompt = bar.readString();
+            language = bar.readString();
+        } catch (Exception ex) {
+            throw new InvalidMessageException(ex.getMessage());
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/authentication/SshMsgUserAuthRequest.java b/src/com/sshtools/j2ssh/authentication/SshMsgUserAuthRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..ce94c5b849970587f0433881abb13182beb5c919
--- /dev/null
+++ b/src/com/sshtools/j2ssh/authentication/SshMsgUserAuthRequest.java
@@ -0,0 +1,163 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.authentication;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.SshMessage;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.24 $
+ */
+public class SshMsgUserAuthRequest extends SshMessage {
+    /**  */
+    public final static int SSH_MSG_USERAUTH_REQUEST = 50;
+    private String methodName;
+    private String serviceName;
+    private String username;
+    private byte[] requestData;
+
+    /**
+     * Creates a new SshMsgUserAuthRequest object.
+     */
+    public SshMsgUserAuthRequest() {
+        super(SSH_MSG_USERAUTH_REQUEST);
+    }
+
+    /**
+     * Creates a new SshMsgUserAuthRequest object.
+     *
+     * @param username
+     * @param serviceName
+     * @param methodName
+     * @param requestData
+     */
+    public SshMsgUserAuthRequest(String username, String serviceName,
+        String methodName, byte[] requestData) {
+        super(SSH_MSG_USERAUTH_REQUEST);
+        this.username = username;
+        this.serviceName = serviceName;
+        this.methodName = methodName;
+        this.requestData = requestData;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_USERAUTH_REQUEST";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMethodName() {
+        return methodName;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getRequestData() {
+        return requestData;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getServiceName() {
+        return serviceName;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getUsername() {
+        return username;
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+        try {
+            baw.writeString(username);
+            baw.writeString(serviceName);
+            baw.writeString(methodName);
+
+            if (requestData != null) {
+                baw.write(requestData);
+            }
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Invalid message data");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+        try {
+            username = bar.readString();
+            serviceName = bar.readString();
+            methodName = bar.readString();
+
+            if (bar.available() > 0) {
+                requestData = new byte[bar.available()];
+                bar.read(requestData);
+            }
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Invalid message data");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/authentication/SshMsgUserAuthSuccess.java b/src/com/sshtools/j2ssh/authentication/SshMsgUserAuthSuccess.java
new file mode 100644
index 0000000000000000000000000000000000000000..3b889570d0a7b3d4bdf1208e136e41e56fff3cc2
--- /dev/null
+++ b/src/com/sshtools/j2ssh/authentication/SshMsgUserAuthSuccess.java
@@ -0,0 +1,81 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.authentication;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.SshMessage;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.23 $
+ */
+public class SshMsgUserAuthSuccess extends SshMessage {
+    /**  */
+    protected final static int SSH_MSG_USERAUTH_SUCCESS = 52;
+
+    /**
+     * Creates a new SshMsgUserAuthSuccess object.
+     */
+    public SshMsgUserAuthSuccess() {
+        super(SSH_MSG_USERAUTH_SUCCESS);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_USERAUTH_SUCCESS";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+    }
+}
diff --git a/src/com/sshtools/j2ssh/authentication/TerminatedStateException.java b/src/com/sshtools/j2ssh/authentication/TerminatedStateException.java
new file mode 100644
index 0000000000000000000000000000000000000000..e43cb155f498dfbf232846aaf02aefe35dd6850e
--- /dev/null
+++ b/src/com/sshtools/j2ssh/authentication/TerminatedStateException.java
@@ -0,0 +1,55 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.authentication;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class TerminatedStateException extends Exception {
+    private int state;
+
+    /**
+     * Creates a new TerminatedStateException object.
+     *
+     * @param state
+     */
+    public TerminatedStateException(int state) {
+        this.state = state;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getState() {
+        return state;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/configuration/ConfigurationContext.java b/src/com/sshtools/j2ssh/configuration/ConfigurationContext.java
new file mode 100644
index 0000000000000000000000000000000000000000..b42ba4ae624581155e17887102265e3c64e692c1
--- /dev/null
+++ b/src/com/sshtools/j2ssh/configuration/ConfigurationContext.java
@@ -0,0 +1,62 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.configuration;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public interface ConfigurationContext {
+    /**
+     *
+     *
+     * @param cls
+     *
+     * @return
+     */
+    public boolean isConfigurationAvailable(Class cls);
+
+    /**
+     *
+     *
+     * @param cls
+     *
+     * @return
+     *
+     * @throws ConfigurationException
+     */
+    public Object getConfiguration(Class cls) throws ConfigurationException;
+
+    /**
+     *
+     *
+     * @throws ConfigurationException
+     */
+    public void initialize() throws ConfigurationException;
+}
diff --git a/src/com/sshtools/j2ssh/configuration/ConfigurationException.java b/src/com/sshtools/j2ssh/configuration/ConfigurationException.java
new file mode 100644
index 0000000000000000000000000000000000000000..db075b8a1d6f1dab3ed82a4a730713bad8c4e42f
--- /dev/null
+++ b/src/com/sshtools/j2ssh/configuration/ConfigurationException.java
@@ -0,0 +1,46 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.configuration;
+
+import com.sshtools.j2ssh.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class ConfigurationException extends SshException {
+    /**
+     * Creates a new ConfigurationException object.
+     *
+     * @param msg
+     */
+    public ConfigurationException(String msg) {
+        super(msg);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/configuration/ConfigurationLoader.java b/src/com/sshtools/j2ssh/configuration/ConfigurationLoader.java
new file mode 100644
index 0000000000000000000000000000000000000000..c52c6431d9e9913975dccea44eb0a02172d2f8c0
--- /dev/null
+++ b/src/com/sshtools/j2ssh/configuration/ConfigurationLoader.java
@@ -0,0 +1,491 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.configuration;
+
+import com.sshtools.j2ssh.util.ExtensionClassLoader;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FilenameFilter;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import java.security.AccessControlException;
+import java.security.AccessController;
+import java.security.SecureRandom;
+import java.security.Security;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+import java.util.PropertyPermission;
+import java.util.Vector;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.67 $
+ */
+public class ConfigurationLoader {
+    private static Vector contexts = new Vector();
+    private static SecureRandom rnd;
+    private static ExtensionClassLoader ext = null;
+    private static ClassLoader clsLoader = null;
+    private static Log log = LogFactory.getLog(ConfigurationLoader.class);
+    private static String homedir;
+    private static boolean initialized = false;
+    private static Object initializationLock = new Object();
+
+    static {
+        // Get the sshtools.home system property. If system properties can not be
+        // read then we are running in a sandbox
+        //     try {
+        homedir = checkAndGetProperty("sshtools.home",
+                System.getProperty("java.home"));
+
+        if ((homedir != null) && !homedir.endsWith(File.separator)) {
+            homedir += File.separator;
+        }
+
+        rnd = new SecureRandom();
+        rnd.nextInt();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public static SecureRandom getRND() {
+        return rnd;
+    }
+
+    /**
+     *
+     *
+     * @param projectname
+     * @param versionFile
+     *
+     * @return
+     */
+    public static String getVersionString(String projectname, String versionFile) {
+        Properties properties = new Properties();
+        String version = projectname;
+
+        try {
+            properties.load(loadFile(versionFile));
+
+            String project = projectname.toLowerCase();
+            String major = properties.getProperty(project + ".version.major");
+            String minor = properties.getProperty(project + ".version.minor");
+            String build = properties.getProperty(project + ".version.build");
+            String type = properties.getProperty(project + ".project.type");
+
+            if ((major != null) && (minor != null) && (build != null)) {
+                version += (" " + major + "." + minor + "." + build);
+            }
+
+            if (type != null) {
+                version += (" " + type);
+            }
+        } catch (Exception e) {
+        }
+
+        return version;
+    }
+
+    /**
+     *
+     *
+     * @param property
+     * @param defaultValue
+     *
+     * @return
+     */
+    public static String checkAndGetProperty(String property,
+        String defaultValue) {
+        //  Check for access to sshtools.platform
+        try {
+            if (System.getSecurityManager() != null) {
+                AccessController.checkPermission(new PropertyPermission(
+                        property, "read"));
+            }
+
+            return System.getProperty(property, defaultValue);
+        } catch (AccessControlException ace) {
+            return defaultValue;
+        }
+    }
+
+    /**
+     *
+     *
+     * @param force
+     *
+     * @throws ConfigurationException
+     */
+    public static void initialize(boolean force) throws ConfigurationException {
+        initialize(force, new DefaultConfigurationContext());
+    }
+
+    /**
+     * <p>
+     * Initializes the J2SSH api with a specified configuration context. This
+     * method will attempt to load the Bouncycastle JCE if it detects the java
+     * version is 1.3.1.
+     * </p>
+     *
+     * @param force force the configuration to load even if a configuration
+     *        already exists
+     * @param context the configuration context to load
+     *
+     * @throws ConfigurationException if the configuration is invalid or if a
+     *         security provider is not available
+     */
+    public static void initialize(boolean force, ConfigurationContext context)
+        throws ConfigurationException {
+        // }
+        try {
+            String javaversion = System.getProperty("java.version");
+            log.info("JAVA version is " + javaversion);
+
+            if (javaversion.startsWith("1.3")) {
+                boolean provider = false;
+
+                for (int i = 0; i < Security.getProviders().length; i++) {
+                    log.info(Security.getProviders()[i].getName() +
+                        " security provider found");
+
+                    if (Security.getProviders()[i].getClass().getName().equals("org.bouncycastle.jce.provider.BouncyCastleProvider")) {
+                        provider = true;
+                    }
+                }
+
+                if (provider == false) {
+                    log.info("Attempting to load the bouncycastle jce provider");
+
+                    // Attempt to load a JCE Provider - replace or remove these statements
+                    // depending upon how you want to initialize your JCE provider
+                    Class cls;
+                    cls = Class.forName(
+                            "org.bouncycastle.jce.provider.BouncyCastleProvider");
+                    java.security.Security.addProvider((java.security.Provider) cls.newInstance());
+                }
+            }
+        } catch (Exception ex) {
+            log.info("Failed to load the bouncycastle jce provider", ex);
+
+            if (java.security.Security.getProviders().length <= 0) {
+                throw new ConfigurationException(
+                    "There are no security providers available; install jce-jdk13-119.jar available from http://www.bouncycastle.org");
+            } else {
+                log.info("An existing provider has been detected");
+            }
+        }
+
+        synchronized (initializationLock) {
+            if (initialized && !force) {
+                return;
+            }
+
+            //   }
+            context.initialize();
+            contexts.add(context);
+
+            if (ext == null) {
+                // We need to setup the dynamic class loading with the extension jars
+                ext = new ExtensionClassLoader(ConfigurationLoader.class.getClassLoader());
+
+                try {
+                    //  Jar files to add to the classpath
+                    File dir = new File(homedir + "lib" + File.separator +
+                            "ext");
+
+                    // Filter for .jar files
+                    FilenameFilter filter = new FilenameFilter() {
+                            public boolean accept(File dir, String name) {
+                                return name.endsWith(".jar");
+                            }
+                        };
+
+                    // Get the list
+                    File[] children = dir.listFiles(filter);
+                    List classpath = new Vector();
+
+                    if (children != null) {
+                        for (int i = 0; i < children.length; i++) {
+                            // Get filename of file or directory
+                            log.info("Extension " +
+                                children[i].getAbsolutePath() +
+                                " being added to classpath");
+                            ext.add(children[i]);
+                        }
+                    }
+                } catch (AccessControlException ex) {
+                    log.info(
+                        "Cannot access lib/ext directory, extension classes will not be loaded");
+                }
+            }
+
+            initialized = true;
+        }
+    }
+
+    /**
+     *
+     *
+     * @param cls
+     *
+     * @return
+     *
+     * @throws ConfigurationException
+     */
+    public static boolean isConfigurationAvailable(Class cls)
+        throws ConfigurationException {
+        if (!initialized) {
+            initialize(false);
+        }
+
+        if (contexts.size() > 0) {
+            Iterator it = contexts.iterator();
+
+            while (it.hasNext()) {
+                ConfigurationContext context = (ConfigurationContext) it.next();
+
+                if (context.isConfigurationAvailable(cls)) {
+                    return true;
+                }
+            }
+
+            return false;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     *
+     *
+     * @param cls
+     *
+     * @return
+     *
+     * @throws ConfigurationException
+     */
+    public static Object getConfiguration(Class cls)
+        throws ConfigurationException {
+        if (contexts.size() > 0) {
+            Iterator it = contexts.iterator();
+
+            while (it.hasNext()) {
+                ConfigurationContext context = (ConfigurationContext) it.next();
+
+                if (context.isConfigurationAvailable(cls)) {
+                    return context.getConfiguration(cls);
+                }
+            }
+        }
+
+        throw new ConfigurationException("No " + cls.getName() +
+            " configuration is available in this context");
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public static String getConfigurationDirectory() {
+        return homedir + "conf/";
+    }
+
+    /**
+     *
+     *
+     * @param name
+     *
+     * @return
+     *
+     * @throws ClassNotFoundException
+     * @throws ConfigurationException
+     */
+    public static Class getExtensionClass(String name)
+        throws ClassNotFoundException, ConfigurationException {
+        if (!initialized) {
+            initialize(false);
+        }
+
+        if (ext == null) {
+            throw new ClassNotFoundException("Configuration not initialized");
+        }
+
+        return ext.loadClass(name);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public static String getHomeDirectory() {
+        return homedir;
+    }
+
+    /**
+     *
+     *
+     * @param clsLoader
+     */
+    public static void setContextClassLoader(ClassLoader clsLoader) {
+        ConfigurationLoader.clsLoader = clsLoader;
+    }
+
+    public static ExtensionClassLoader getExtensionClassLoader() {
+        return ext;
+    }
+
+    public static String getExtensionPath() {
+        return homedir + "/lib/ext";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public static ClassLoader getContextClassLoader() {
+        return ConfigurationLoader.clsLoader;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public static boolean isContextClassLoader() {
+        return (clsLoader != null);
+    }
+
+    /**
+     *
+     *
+     * @param homedir
+     */
+    public static void setHomeDirectory(String homedir) {
+        ConfigurationLoader.homedir = homedir.replace('\\', '/');
+
+        if (!ConfigurationLoader.homedir.endsWith("/")) {
+            ConfigurationLoader.homedir += "/";
+        }
+    }
+
+    /**
+     *
+     *
+     * @param filename
+     *
+     * @return
+     *
+     * @throws FileNotFoundException
+     */
+    public static InputStream loadFile(String filename)
+        throws FileNotFoundException {
+        FileInputStream in;
+
+        try {
+            in = new FileInputStream(getConfigurationDirectory() + filename);
+
+            return in;
+        } catch (FileNotFoundException fnfe) {
+        }
+
+        try {
+            in = new FileInputStream(homedir + filename);
+
+            return in;
+        } catch (FileNotFoundException fnfe) {
+        }
+
+        in = new FileInputStream(filename);
+
+        return in;
+    }
+
+    /**
+     *
+     *
+     * @param filename
+     *
+     * @return
+     *
+     * @throws FileNotFoundException
+     */
+    public static OutputStream saveFile(String filename)
+        throws FileNotFoundException {
+        // Look for the file in the config directory
+        File f = new File(getConfigurationDirectory() + filename);
+
+        if (f.exists()) {
+            // Yes its there so create an outputstream to it
+            return new FileOutputStream(f);
+        } else {
+            // Look for it absolute
+            f = new File(filename);
+
+            if (f.exists()) {
+                return new FileOutputStream(filename); // yes so do absolute
+            } else {
+                // Determine whether the filename is absolute or not with a primitive check
+                return new FileOutputStream((filename.indexOf(
+                        File.pathSeparator) >= 0) ? filename
+                                                  : (getConfigurationDirectory() +
+                    filename));
+            }
+        }
+    }
+
+    static class DefaultConfigurationContext implements ConfigurationContext {
+        public void initialize() throws ConfigurationException {
+            // Do nothing
+        }
+
+        public boolean isConfigurationAvailable(Class cls) {
+            return false;
+        }
+
+        public Object getConfiguration(Class cls) throws ConfigurationException {
+            throw new ConfigurationException(
+                "Default configuration does not contain " + cls.getName());
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/configuration/ExtensionAlgorithm.java b/src/com/sshtools/j2ssh/configuration/ExtensionAlgorithm.java
new file mode 100644
index 0000000000000000000000000000000000000000..303fdc0af408dbccf182e05533fd79ef271ca283
--- /dev/null
+++ b/src/com/sshtools/j2ssh/configuration/ExtensionAlgorithm.java
@@ -0,0 +1,80 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.configuration;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class ExtensionAlgorithm {
+    private String name;
+    private String implClass;
+
+    /**
+     * Creates a new ExtensionAlgorithm object.
+     */
+    public ExtensionAlgorithm() {
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getAlgorithmName() {
+        return name;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getImplementationClass() {
+        return implClass;
+    }
+
+    /**
+     *
+     *
+     * @param name
+     */
+    public void setAlgorithmName(String name) {
+        this.name = name;
+    }
+
+    /**
+     *
+     *
+     * @param implClass
+     */
+    public void setImplementationClass(String implClass) {
+        this.implClass = implClass;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/configuration/SshAPIConfiguration.java b/src/com/sshtools/j2ssh/configuration/SshAPIConfiguration.java
new file mode 100644
index 0000000000000000000000000000000000000000..31160b412c832f8e64d96bda7d632795261186cb
--- /dev/null
+++ b/src/com/sshtools/j2ssh/configuration/SshAPIConfiguration.java
@@ -0,0 +1,142 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.configuration;
+
+import java.util.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.27 $
+ */
+public interface SshAPIConfiguration {
+    /**
+     *
+     *
+     * @return
+     */
+    public List getCompressionExtensions();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public List getCipherExtensions();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public List getMacExtensions();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public List getAuthenticationExtensions();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public List getPublicKeyExtensions();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public List getKeyExchangeExtensions();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getDefaultCipher();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getDefaultMac();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getDefaultCompression();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getDefaultPublicKey();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getDefaultKeyExchange();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getDefaultPublicKeyFormat();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getDefaultPrivateKeyFormat();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public List getPublicKeyFormats();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public List getPrivateKeyFormats();
+}
diff --git a/src/com/sshtools/j2ssh/configuration/SshConnectionProperties.java b/src/com/sshtools/j2ssh/configuration/SshConnectionProperties.java
new file mode 100644
index 0000000000000000000000000000000000000000..5f7590a8dc9129c14c6951e3f5f32dc8004cff73
--- /dev/null
+++ b/src/com/sshtools/j2ssh/configuration/SshConnectionProperties.java
@@ -0,0 +1,474 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.configuration;
+
+import com.sshtools.j2ssh.forwarding.ForwardingConfiguration;
+import com.sshtools.j2ssh.transport.cipher.SshCipherFactory;
+import com.sshtools.j2ssh.transport.compression.SshCompressionFactory;
+import com.sshtools.j2ssh.transport.hmac.SshHmacFactory;
+import com.sshtools.j2ssh.transport.kex.SshKeyExchangeFactory;
+import com.sshtools.j2ssh.transport.publickey.SshKeyPairFactory;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.27 $
+ */
+public class SshConnectionProperties {
+    private static Log log = LogFactory.getLog(SshConnectionProperties.class);
+
+    /**  */
+    public static final int USE_STANDARD_SOCKET = 1;
+
+    /**  */
+    public static final int USE_HTTP_PROXY = 2;
+
+    /**  */
+    public static final int USE_SOCKS4_PROXY = 3;
+
+    /**  */
+    public static final int USE_SOCKS5_PROXY = 4;
+
+    /**  */
+    protected int transportProvider = USE_STANDARD_SOCKET;
+
+    /**  */
+    protected String proxyHostname;
+
+    /**  */
+    protected int proxyPort;
+
+    /**  */
+    protected String proxyUsername;
+
+    /**  */
+    protected String proxyPassword;
+
+    /**  */
+    protected String host;
+
+    /**  */
+    protected String prefDecryption = SshCipherFactory.getDefaultCipher();
+
+    /**  */
+    protected String prefEncryption = SshCipherFactory.getDefaultCipher();
+
+    /**  */
+    protected String prefKex = SshKeyExchangeFactory.getDefaultKeyExchange();
+
+    /**  */
+    protected String prefPK = SshKeyPairFactory.getDefaultPublicKey();
+
+    /**  */
+    protected String prefRecvComp = SshCompressionFactory.getDefaultCompression();
+
+    /**  */
+    protected String prefRecvMac = SshHmacFactory.getDefaultHmac();
+
+    /**  */
+    protected String prefSendComp = SshCompressionFactory.getDefaultCompression();
+
+    /**  */
+    protected String prefSendMac = SshHmacFactory.getDefaultHmac();
+
+    /**  */
+    protected String username;
+
+    /**  */
+    protected int port = 22;
+    protected Map localForwardings = new HashMap();
+    protected Map remoteForwardings = new HashMap();
+    protected boolean forwardingAutoStart = false;
+
+    /**
+     * Creates a new SshConnectionProperties object.
+     */
+    public SshConnectionProperties() {
+    }
+
+    /**
+     *
+     *
+     * @param host
+     */
+    public void setHost(String host) {
+        this.host = host;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getHost() {
+        return host;
+    }
+
+    /**
+     *
+     *
+     * @param port
+     */
+    public void setPort(int port) {
+        this.port = port;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getPort() {
+        return port;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getTransportProvider() {
+        return transportProvider;
+    }
+
+    /**
+     *
+     *
+     * @param name
+     */
+    public void setTransportProviderString(String name) {
+        if (name != null) {
+            if (name.equalsIgnoreCase("http")) {
+                transportProvider = USE_HTTP_PROXY;
+            } else if (name.equalsIgnoreCase("socks4")) {
+                transportProvider = USE_SOCKS4_PROXY;
+            } else if (name.equalsIgnoreCase("socks5")) {
+                transportProvider = USE_SOCKS5_PROXY;
+            } else {
+                transportProvider = USE_STANDARD_SOCKET;
+            }
+        } else {
+            transportProvider = USE_STANDARD_SOCKET;
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getTransportProviderString() {
+        if (transportProvider == USE_HTTP_PROXY) {
+            return "http";
+        } else if (transportProvider == USE_SOCKS4_PROXY) {
+            return "socks4";
+        } else if (transportProvider == USE_SOCKS5_PROXY) {
+            return "socks5";
+        } else {
+            return "socket";
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getProxyHost() {
+        return proxyHostname;
+    }
+
+    public void removeAllForwardings() {
+        localForwardings.clear();
+        remoteForwardings.clear();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getProxyPort() {
+        return proxyPort;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getProxyUsername() {
+        return proxyUsername;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getProxyPassword() {
+        return proxyPassword;
+    }
+
+    /**
+     *
+     *
+     * @param transportProvider
+     */
+    public void setTransportProvider(int transportProvider) {
+        this.transportProvider = transportProvider;
+    }
+
+    /**
+     *
+     *
+     * @param proxyHostname
+     */
+    public void setProxyHost(String proxyHostname) {
+        this.proxyHostname = proxyHostname;
+    }
+
+    /**
+     *
+     *
+     * @param proxyPort
+     */
+    public void setProxyPort(int proxyPort) {
+        this.proxyPort = proxyPort;
+    }
+
+    /**
+     *
+     *
+     * @param proxyUsername
+     */
+    public void setProxyUsername(String proxyUsername) {
+        this.proxyUsername = proxyUsername;
+    }
+
+    /**
+     *
+     *
+     * @param proxyPassword
+     */
+    public void setProxyPassword(String proxyPassword) {
+        this.proxyPassword = proxyPassword;
+    }
+
+    /**
+     *
+     *
+     * @param pref
+     */
+    public void setPrefCSComp(String pref) {
+        prefSendComp = pref;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getPrefCSComp() {
+        return prefSendComp;
+    }
+
+    /**
+     *
+     *
+     * @param pref
+     */
+    public void setPrefCSEncryption(String pref) {
+        prefEncryption = pref;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getPrefCSEncryption() {
+        return prefEncryption;
+    }
+
+    /**
+     *
+     *
+     * @param pref
+     */
+    public void setPrefCSMac(String pref) {
+        prefSendMac = pref;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getPrefCSMac() {
+        return prefSendMac;
+    }
+
+    /**
+     *
+     *
+     * @param pref
+     */
+    public void setPrefKex(String pref) {
+        prefKex = pref;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getPrefKex() {
+        return prefKex;
+    }
+
+    /**
+     *
+     *
+     * @param pref
+     */
+    public void setPrefPublicKey(String pref) {
+        prefPK = pref;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getPrefPublicKey() {
+        return prefPK;
+    }
+
+    /**
+     *
+     *
+     * @param pref
+     */
+    public void setPrefSCComp(String pref) {
+        prefRecvComp = pref;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getPrefSCComp() {
+        return prefRecvComp;
+    }
+
+    /**
+     *
+     *
+     * @param pref
+     */
+    public void setPrefSCEncryption(String pref) {
+        prefDecryption = pref;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getPrefSCEncryption() {
+        return prefDecryption;
+    }
+
+    public Map getLocalForwardings() {
+        return localForwardings;
+    }
+
+    public Map getRemoteForwardings() {
+        return remoteForwardings;
+    }
+
+    public void addLocalForwarding(ForwardingConfiguration cf) {
+        localForwardings.put(cf.getName(), cf);
+    }
+
+    public void addRemoteForwarding(ForwardingConfiguration cf) {
+        remoteForwardings.put(cf.getName(), cf);
+    }
+
+    public boolean getForwardingAutoStartMode() {
+        return forwardingAutoStart;
+    }
+
+    public void setForwardingAutoStartMode(boolean forwardingAutoStart) {
+        this.forwardingAutoStart = forwardingAutoStart;
+    }
+
+    /**
+     *
+     *
+     * @param pref
+     */
+    public void setPrefSCMac(String pref) {
+        prefRecvMac = pref;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getPrefSCMac() {
+        return prefRecvMac;
+    }
+
+    /**
+     *
+     *
+     * @param username
+     */
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getUsername() {
+        return username;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/connection/BindingChannel.java b/src/com/sshtools/j2ssh/connection/BindingChannel.java
new file mode 100644
index 0000000000000000000000000000000000000000..e7ac4ec150fd5278f98e3fae0dfb992ebcf197a6
--- /dev/null
+++ b/src/com/sshtools/j2ssh/connection/BindingChannel.java
@@ -0,0 +1,228 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.connection;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+
+import java.util.Iterator;
+import java.util.Vector;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public abstract class BindingChannel extends Channel {
+    private Log log = LogFactory.getLog(BindingChannel.class);
+
+    /**  */
+    protected BindingChannel boundChannel;
+
+    /**  */
+    protected Vector messages = new Vector();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isBound() {
+        return boundChannel != null;
+    }
+
+    /**
+     *
+     *
+     * @param boundChannel
+     *
+     * @throws IOException
+     */
+    public void bindChannel(BindingChannel boundChannel)
+        throws IOException {
+        if (boundChannel == null) {
+            throw new IOException("[" + getName() +
+                "] Bound channel cannot be null");
+        }
+
+        if (isBound()) {
+            throw new IOException("[" + getName() +
+                "] This channel is already bound to another channel [" +
+                boundChannel.getName() + "]");
+        }
+
+        this.boundChannel = boundChannel;
+
+        if (!boundChannel.isBound()) {
+            boundChannel.bindChannel(this);
+
+            synchronized (messages) {
+                if (boundChannel.isOpen() && (messages.size() > 0)) {
+                    sendOutstandingMessages();
+                }
+            }
+        } else {
+            if (!boundChannel.boundChannel.equals(this)) {
+                throw new IOException("[" + getName() +
+                    "] Channel is already bound to an another channel [" +
+                    boundChannel.boundChannel.getName() + "]");
+            }
+        }
+    }
+
+    private void sendOutstandingMessages() throws IOException {
+        // Send the outstanding messages
+        if (boundChannel == null) {
+            return;
+        }
+
+        synchronized (messages) {
+            Iterator it = messages.iterator();
+
+            while (it.hasNext()) {
+                Object obj = it.next();
+
+                if (obj instanceof SshMsgChannelData) {
+                    boundChannel.sendChannelData(((SshMsgChannelData) obj).getChannelData());
+                } else if (obj instanceof SshMsgChannelExtendedData) {
+                    boundChannel.sendChannelExtData(((SshMsgChannelExtendedData) obj).getDataTypeCode(),
+                        ((SshMsgChannelExtendedData) obj).getChannelData());
+                } else {
+                    throw new IOException("[" + getName() +
+                        "] Invalid message type in pre bound message list!");
+                }
+            }
+
+            messages.clear();
+        }
+    }
+
+    /**
+     *
+     *
+     * @param msg
+     *
+     * @throws java.io.IOException
+     */
+    protected void onChannelExtData(SshMsgChannelExtendedData msg)
+        throws java.io.IOException {
+        synchronized (messages) {
+            if (boundChannel != null) {
+                if (boundChannel.isOpen()) {
+                    boundChannel.sendChannelExtData(msg.getDataTypeCode(),
+                        msg.getChannelData());
+                } else {
+                    messages.add(msg);
+                }
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @param msg
+     *
+     * @throws java.io.IOException
+     */
+    protected void onChannelData(SshMsgChannelData msg)
+        throws java.io.IOException {
+        synchronized (messages) {
+            if (boundChannel != null) {
+                if (boundChannel.isOpen()) {
+                    boundChannel.sendChannelData(msg.getChannelData());
+                } else {
+                    messages.add(msg);
+                }
+            }
+        }
+    }
+
+    /*public void setLocalEOF() throws IOException {
+       synchronized(state) {
+         super.setLocalEOF();
+         if (!boundChannel.isRemoteEOF()) {
+      log.info("onLocalEOF [" + getName() + "] is setting " + boundChannel.getName() + " to EOF");
+      boundChannel.setRemoteEOF();
+      //boundChannel.setLocalEOF();
+         }
+       }
+     }*/
+    protected void setRemoteEOF() throws IOException {
+        synchronized (state) {
+            super.setRemoteEOF();
+
+            if (!boundChannel.isLocalEOF()) {
+                log.info("onRemoteEOF [" + getName() + "] is setting " +
+                    boundChannel.getName() + " to EOF");
+                boundChannel.setLocalEOF();
+
+                //boundChannel.setRemoteEOF();
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @throws java.io.IOException
+     */
+    protected void onChannelEOF() throws java.io.IOException {
+    }
+
+    /**
+     *
+     *
+     * @throws java.io.IOException
+     */
+    protected void onChannelClose() throws java.io.IOException {
+        /*synchronized(state) {
+             if (boundChannel != null) {
+               if (boundChannel.isOpen())
+                 boundChannel.close();
+             }
+         }*/
+    }
+
+    /**
+     *
+     *
+     * @throws java.io.IOException
+     */
+    protected void onChannelOpen() throws java.io.IOException {
+        synchronized (messages) {
+            if (boundChannel != null) {
+                if (boundChannel.isOpen() && (messages.size() > 0)) {
+                    sendOutstandingMessages();
+                }
+            }
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/connection/Channel.java b/src/com/sshtools/j2ssh/connection/Channel.java
new file mode 100644
index 0000000000000000000000000000000000000000..e2cc2f123eea92bd0e5526e43e7b4bb64fa40c01
--- /dev/null
+++ b/src/com/sshtools/j2ssh/connection/Channel.java
@@ -0,0 +1,647 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.connection;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+
+import java.util.Iterator;
+import java.util.Vector;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.74 $
+ */
+public abstract class Channel {
+    private static Log log = LogFactory.getLog(Channel.class);
+
+    /**  */
+    protected ChannelDataWindow localWindow = new ChannelDataWindow();
+
+    /**  */
+    protected ChannelDataWindow remoteWindow = new ChannelDataWindow();
+
+    /**  */
+    protected ConnectionProtocol connection;
+
+    /**  */
+    protected long localChannelId;
+
+    /**  */
+    protected long localPacketSize;
+
+    /**  */
+    protected long remoteChannelId;
+
+    /**  */
+    protected long remotePacketSize;
+
+    /**  */
+    protected ChannelState state = new ChannelState();
+    private boolean isClosed = false;
+    private boolean isLocalEOF = false;
+    private boolean isRemoteEOF = false;
+    private boolean localHasClosed = false;
+    private boolean remoteHasClosed = false;
+    private String name = "Unnamed Channel";
+    private Vector eventListeners = new Vector();
+
+    /**
+     * Creates a new Channel object.
+     */
+    public Channel() {
+        this.localPacketSize = getMaximumPacketSize();
+        this.localWindow.increaseWindowSpace(getMaximumWindowSpace());
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public abstract byte[] getChannelOpenData();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public abstract byte[] getChannelConfirmationData();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public abstract String getChannelType();
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected abstract int getMinimumWindowSpace();
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected abstract int getMaximumWindowSpace();
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected abstract int getMaximumPacketSize();
+
+    /**
+     *
+     *
+     * @param msg
+     *
+     * @throws IOException
+     */
+    protected abstract void onChannelData(SshMsgChannelData msg)
+        throws IOException;
+
+    /**
+     *
+     *
+     * @param msg
+     *
+     * @throws IOException
+     */
+    protected void processChannelData(SshMsgChannelData msg)
+        throws IOException {
+        synchronized (state) {
+            if (!isClosed()) {
+                if (msg.getChannelDataLength() > localWindow.getWindowSpace()) {
+                    throw new IOException(
+                        "More data recieved than is allowed by the channel data window [" +
+                        name + "]");
+                }
+
+                long windowSpace = localWindow.consumeWindowSpace(msg.getChannelData().length);
+
+                if (windowSpace < getMinimumWindowSpace()) {
+                    if (log.isDebugEnabled()) {
+                        log.debug("Channel " + String.valueOf(localChannelId) +
+                            " requires more window space [" + name + "]");
+                    }
+
+                    windowSpace = getMaximumWindowSpace() - windowSpace;
+                    log.debug("Requesting connection protocol increase window");
+                    connection.sendChannelWindowAdjust(this, windowSpace);
+                    localWindow.increaseWindowSpace(windowSpace);
+                }
+
+                onChannelData(msg);
+
+                Iterator it = eventListeners.iterator();
+                ChannelEventListener eventListener;
+
+                while (it.hasNext()) {
+                    eventListener = (ChannelEventListener) it.next();
+
+                    if (eventListener != null) {
+                        eventListener.onDataReceived(this, msg.getChannelData());
+                    }
+                }
+            } else {
+                throw new IOException(
+                    "Channel data received but channel is closed [" + name +
+                    "]");
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isClosed() {
+        synchronized (state) {
+            return state.getValue() == ChannelState.CHANNEL_CLOSED;
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isOpen() {
+        synchronized (state) {
+            return state.getValue() == ChannelState.CHANNEL_OPEN;
+        }
+    }
+
+    /**
+     *
+     *
+     * @param data
+     *
+     * @throws IOException
+     */
+    protected 
+    /*synchronized*/ void sendChannelData(byte[] data) throws IOException {
+        if (!connection.isConnected()) {
+            throw new IOException("The connection has been closed [" + name +
+                "]");
+        }
+
+        if (!isClosed()) {
+            connection.sendChannelData(this, data);
+
+            Iterator it = eventListeners.iterator();
+            ChannelEventListener eventListener;
+
+            while (it.hasNext()) {
+                eventListener = (ChannelEventListener) it.next();
+
+                if (eventListener != null) {
+                    eventListener.onDataSent(this, data);
+                }
+            }
+        } else {
+            throw new IOException("The channel is closed [" + name + "]");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param type
+     * @param data
+     *
+     * @throws IOException
+     */
+    protected 
+    /*synchronized*/ void sendChannelExtData(int type, byte[] data) throws IOException {
+        if (!connection.isConnected()) {
+            throw new IOException("The connection has been closed [" + name +
+                "]");
+        }
+
+        if (!isClosed()) {
+            connection.sendChannelExtData(this, type, data);
+
+            Iterator it = eventListeners.iterator();
+            ChannelEventListener eventListener;
+
+            while (it.hasNext()) {
+                eventListener = (ChannelEventListener) it.next();
+
+                if (eventListener != null) {
+                    eventListener.onDataSent(this, data);
+                }
+            }
+        } else {
+            throw new IOException("The channel is closed [" + name + "]");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param msg
+     *
+     * @throws IOException
+     */
+    protected abstract void onChannelExtData(SshMsgChannelExtendedData msg)
+        throws IOException;
+
+    /**
+     *
+     *
+     * @param msg
+     *
+     * @throws IOException
+     */
+    protected void processChannelData(SshMsgChannelExtendedData msg)
+        throws IOException {
+        synchronized (state) {
+            if (msg.getChannelData().length > localWindow.getWindowSpace()) {
+                throw new IOException(
+                    "More data recieved than is allowed by the channel data window [" +
+                    name + "]");
+            }
+
+            long windowSpace = localWindow.consumeWindowSpace(msg.getChannelData().length);
+
+            if (windowSpace < getMinimumWindowSpace()) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Channel " + String.valueOf(localChannelId) +
+                        " requires more window space [" + name + "]");
+                }
+
+                windowSpace = getMaximumWindowSpace() - windowSpace;
+                connection.sendChannelWindowAdjust(this, windowSpace);
+                localWindow.increaseWindowSpace(windowSpace);
+            }
+
+            onChannelExtData(msg);
+
+            Iterator it = eventListeners.iterator();
+            ChannelEventListener eventListener;
+
+            while (it.hasNext()) {
+                eventListener = (ChannelEventListener) it.next();
+
+                if (eventListener != null) {
+                    eventListener.onDataReceived(this, msg.getChannelData());
+                }
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getLocalChannelId() {
+        return localChannelId;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getLocalPacketSize() {
+        return localPacketSize;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public ChannelDataWindow getLocalWindow() {
+        return localWindow;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getRemoteChannelId() {
+        return remoteChannelId;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getRemotePacketSize() {
+        return remotePacketSize;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public ChannelDataWindow getRemoteWindow() {
+        return remoteWindow;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public ChannelState getState() {
+        return state;
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    public void close() throws IOException {
+        synchronized (state) {
+            if (isOpen()) {
+                if ((connection != null) && !localHasClosed &&
+                        connection.isConnected()) {
+                    connection.closeChannel(this);
+                }
+
+                localHasClosed = true;
+
+                if (log.isDebugEnabled()) {
+                    log.debug("Connection is " +
+                        ((connection == null) ? "null"
+                                              : (connection.isConnected()
+                        ? "connected" : "not connected")));
+                }
+
+                if (remoteHasClosed ||
+                        ((connection == null) || !connection.isConnected())) {
+                    log.info("Finializing channel close");
+                    finalizeClose();
+                }
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    protected void remoteClose() throws IOException {
+        log.info("Remote side is closing channel");
+
+        synchronized (state) {
+            remoteHasClosed = true;
+            close();
+        }
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    protected void finalizeClose() throws IOException {
+        synchronized (state) {
+            state.setValue(ChannelState.CHANNEL_CLOSED);
+            onChannelClose();
+
+            Iterator it = eventListeners.iterator();
+            ChannelEventListener eventListener;
+
+            while (it.hasNext()) {
+                eventListener = (ChannelEventListener) it.next();
+
+                if (eventListener != null) {
+                    eventListener.onChannelClose(this);
+                }
+            }
+
+            if (connection != null) {
+                connection.freeChannel(this);
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    public void setLocalEOF() throws IOException {
+        synchronized (state) {
+            isLocalEOF = true;
+            connection.sendChannelEOF(this);
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isLocalEOF() {
+        return isLocalEOF;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isRemoteEOF() {
+        return isRemoteEOF;
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    protected void setRemoteEOF() throws IOException {
+        synchronized (state) {
+            isRemoteEOF = true;
+            onChannelEOF();
+
+            Iterator it = eventListeners.iterator();
+            ChannelEventListener eventListener;
+
+            while (it.hasNext()) {
+                eventListener = (ChannelEventListener) it.next();
+
+                if (eventListener != null) {
+                    eventListener.onChannelEOF(this);
+                }
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @param eventListener
+     */
+    public void addEventListener(ChannelEventListener eventListener) {
+        eventListeners.add(eventListener);
+    }
+
+    /**
+     *
+     *
+     * @param connection
+     * @param localChannelId
+     * @param senderChannelId
+     * @param initialWindowSize
+     * @param maximumPacketSize
+     *
+     * @throws IOException
+     */
+    protected void init(ConnectionProtocol connection, long localChannelId,
+        long senderChannelId, long initialWindowSize, long maximumPacketSize)
+        throws IOException {
+        this.localChannelId = localChannelId;
+        this.remoteChannelId = senderChannelId;
+        this.remotePacketSize = maximumPacketSize;
+        this.remoteWindow.increaseWindowSpace(initialWindowSize);
+        this.connection = connection;
+
+        synchronized (state) {
+            state.setValue(ChannelState.CHANNEL_OPEN);
+        }
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    protected void open() throws IOException {
+        synchronized (state) {
+            state.setValue(ChannelState.CHANNEL_OPEN);
+            onChannelOpen();
+
+            Iterator it = eventListeners.iterator();
+            ChannelEventListener eventListener;
+
+            while (it.hasNext()) {
+                eventListener = (ChannelEventListener) it.next();
+
+                if (eventListener != null) {
+                    eventListener.onChannelOpen(this);
+                }
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @param connection
+     * @param localChannelId
+     * @param senderChannelId
+     * @param initialWindowSize
+     * @param maximumPacketSize
+     * @param eventListener
+     *
+     * @throws IOException
+     */
+    protected void init(ConnectionProtocol connection, long localChannelId,
+        long senderChannelId, long initialWindowSize, long maximumPacketSize,
+        ChannelEventListener eventListener) throws IOException {
+        if (eventListener != null) {
+            addEventListener(eventListener);
+        }
+
+        init(connection, localChannelId, senderChannelId, initialWindowSize,
+            maximumPacketSize);
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    protected abstract void onChannelClose() throws IOException;
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    protected abstract void onChannelEOF() throws IOException;
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    protected abstract void onChannelOpen() throws IOException;
+
+    /**
+     *
+     *
+     * @param requestType
+     * @param wantReply
+     * @param requestData
+     *
+     * @throws IOException
+     */
+    protected abstract void onChannelRequest(String requestType,
+        boolean wantReply, byte[] requestData) throws IOException;
+
+    /**
+     *
+     *
+     * @param name
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getName() {
+        return name;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/connection/ChannelDataWindow.java b/src/com/sshtools/j2ssh/connection/ChannelDataWindow.java
new file mode 100644
index 0000000000000000000000000000000000000000..7c1c156f80a2e23cf61197a8ff60b45750d83bc3
--- /dev/null
+++ b/src/com/sshtools/j2ssh/connection/ChannelDataWindow.java
@@ -0,0 +1,106 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.connection;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.19 $
+ */
+public class ChannelDataWindow {
+    private static Log log = LogFactory.getLog(ChannelDataWindow.class);
+    long windowSpace = 0;
+
+    /**
+     * Creates a new ChannelDataWindow object.
+     */
+    public ChannelDataWindow() {
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public synchronized long getWindowSpace() {
+        return windowSpace;
+    }
+
+    /**
+     *
+     *
+     * @param count
+     *
+     * @return
+     */
+    public synchronized long consumeWindowSpace(int count) {
+        if (windowSpace < count) {
+            waitForWindowSpace(count);
+        }
+
+        windowSpace -= count;
+
+        return windowSpace;
+    }
+
+    /**
+     *
+     *
+     * @param count
+     */
+    public synchronized void increaseWindowSpace(long count) {
+        if (log.isDebugEnabled()) {
+            log.debug("Increasing window space by " + String.valueOf(count));
+        }
+
+        windowSpace += count;
+        notifyAll();
+    }
+
+    /**
+     *
+     *
+     * @param minimum
+     */
+    public synchronized void waitForWindowSpace(int minimum) {
+        if (log.isDebugEnabled()) {
+            log.debug("Waiting for " + String.valueOf(minimum) +
+                " bytes of window space");
+        }
+
+        while (windowSpace < minimum) {
+            try {
+                wait(50);
+            } catch (InterruptedException e) {
+            }
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/connection/ChannelEventAdapter.java b/src/com/sshtools/j2ssh/connection/ChannelEventAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..b5c6c1bff111199e53dc3dae98a718a5cb17db24
--- /dev/null
+++ b/src/com/sshtools/j2ssh/connection/ChannelEventAdapter.java
@@ -0,0 +1,97 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.connection;
+
+
+/**
+ * <p>
+ * Title:
+ * </p>
+ *
+ * <p>
+ * Description:
+ * </p>
+ *
+ * <p>
+ * Copyright: Copyright (c) 2003
+ * </p>
+ *
+ * <p>
+ * Company:
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Id: ChannelEventAdapter.java,v 1.8 2003/09/11 15:35:06 martianx Exp $
+ */
+public abstract class ChannelEventAdapter implements ChannelEventListener {
+    /**
+     * Creates a new ChannelEventAdapter object.
+     */
+    public ChannelEventAdapter() {
+    }
+
+    /**
+     *
+     *
+     * @param channel
+     */
+    public void onChannelOpen(Channel channel) {
+    }
+
+    /**
+     *
+     *
+     * @param channel
+     */
+    public void onChannelEOF(Channel channel) {
+    }
+
+    /**
+     *
+     *
+     * @param channel
+     */
+    public void onChannelClose(Channel channel) {
+    }
+
+    /**
+     *
+     *
+     * @param channel
+     * @param data
+     */
+    public void onDataReceived(Channel channel, byte[] data) {
+    }
+
+    /**
+     *
+     *
+     * @param channel
+     * @param data
+     */
+    public void onDataSent(Channel channel, byte[] data) {
+    }
+}
diff --git a/src/com/sshtools/j2ssh/connection/ChannelEventListener.java b/src/com/sshtools/j2ssh/connection/ChannelEventListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..76e25487c39eac457b6e52db8d5ed69ccb88cd61
--- /dev/null
+++ b/src/com/sshtools/j2ssh/connection/ChannelEventListener.java
@@ -0,0 +1,72 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.connection;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public interface ChannelEventListener {
+    /**
+     *
+     *
+     * @param channel
+     */
+    public void onChannelOpen(Channel channel);
+
+    /**
+     *
+     *
+     * @param channel
+     */
+    public void onChannelEOF(Channel channel);
+
+    /**
+     *
+     *
+     * @param channel
+     */
+    public void onChannelClose(Channel channel);
+
+    /**
+     *
+     *
+     * @param channel
+     * @param data
+     */
+    public void onDataReceived(Channel channel, byte[] data);
+
+    /**
+     *
+     *
+     * @param channel
+     * @param data
+     */
+    public void onDataSent(Channel channel, byte[] data);
+}
diff --git a/src/com/sshtools/j2ssh/connection/ChannelFactory.java b/src/com/sshtools/j2ssh/connection/ChannelFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..cd6af8394a80178256758c0b68d497e2bc8b5ba4
--- /dev/null
+++ b/src/com/sshtools/j2ssh/connection/ChannelFactory.java
@@ -0,0 +1,39 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.connection;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.18 $
+ */
+public interface ChannelFactory {
+    //public List getChannelType();
+    public Channel createChannel(String channelType, byte[] requestData)
+        throws InvalidChannelException;
+}
diff --git a/src/com/sshtools/j2ssh/connection/ChannelInputStream.java b/src/com/sshtools/j2ssh/connection/ChannelInputStream.java
new file mode 100644
index 0000000000000000000000000000000000000000..d1e3cd857aa57b3239477d048c29f811aa77da43
--- /dev/null
+++ b/src/com/sshtools/j2ssh/connection/ChannelInputStream.java
@@ -0,0 +1,309 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.connection;
+
+import com.sshtools.j2ssh.transport.MessageNotAvailableException;
+import com.sshtools.j2ssh.transport.MessageStoreEOFException;
+import com.sshtools.j2ssh.transport.SshMessageStore;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.35 $
+ */
+public class ChannelInputStream extends InputStream {
+    private static Log log = LogFactory.getLog(ChannelInputStream.class);
+    int[] filter;
+    byte[] msgdata;
+    int currentPos = 0;
+    private SshMessageStore messageStore;
+    private Integer type = null;
+    private int interrupt = 5000;
+    private boolean isBlocking = false;
+    private Object lock = new Object();
+    private Thread blockingThread = null;
+
+    /**
+     * Creates a new ChannelInputStream object.
+     *
+     * @param messageStore
+     * @param type
+     */
+    public ChannelInputStream(SshMessageStore messageStore, Integer type) {
+        this.messageStore = messageStore;
+        filter = new int[1];
+        this.type = type;
+
+        if (type != null) {
+            filter[0] = SshMsgChannelExtendedData.SSH_MSG_CHANNEL_EXTENDED_DATA;
+        } else {
+            filter[0] = SshMsgChannelData.SSH_MSG_CHANNEL_DATA;
+        }
+    }
+
+    /**
+     * Creates a new ChannelInputStream object.
+     *
+     * @param messageStore
+     */
+    public ChannelInputStream(SshMessageStore messageStore) {
+        this(messageStore, null);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int available() {
+        int available = 0;
+
+        if (msgdata != null) {
+            available = msgdata.length - currentPos;
+
+            if (log.isDebugEnabled() && (available > 0)) {
+                log.debug(String.valueOf(available) +
+                    " bytes of channel data available");
+            }
+
+            available = (available >= 0) ? available : 0;
+        }
+
+        if (available == 0) {
+            try {
+                if (type != null) {
+                    SshMsgChannelExtendedData msg = (SshMsgChannelExtendedData) messageStore.peekMessage(filter);
+                    available = msg.getChannelData().length;
+                } else {
+                    SshMsgChannelData msg = (SshMsgChannelData) messageStore.peekMessage(filter);
+                    available = msg.getChannelData().length;
+                }
+
+                if (log.isDebugEnabled()) {
+                    log.debug(String.valueOf(available) +
+                        " bytes of channel data available");
+                }
+            } catch (MessageStoreEOFException mse) {
+                log.debug("No bytes available since the MessageStore is EOF");
+                available = -1;
+            } catch (MessageNotAvailableException mna) {
+                available = 0;
+            } catch (InterruptedException ex) {
+                log.info("peekMessage was interrupted, no data available!");
+                available = 0;
+            }
+        }
+
+        return available;
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    public void close() throws IOException {
+        log.info("Closing ChannelInputStream");
+        messageStore.close();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isClosed() {
+        return messageStore.isClosed();
+    }
+
+    /**
+     *
+     *
+     * @param interrupt
+     */
+    public void setBlockInterrupt(int interrupt) {
+        this.interrupt = (interrupt < 1000) ? 1000 : interrupt;
+    }
+
+    /**
+     *
+     */
+    public void interrupt() {
+        messageStore.breakWaiting();
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws java.io.IOException
+     * @throws InterruptedIOException
+     */
+    public int read() throws java.io.IOException {
+        try {
+            block();
+
+            return msgdata[currentPos++] & 0xFF;
+        } catch (MessageStoreEOFException mse) {
+            return -1;
+        } catch (InterruptedException ex) {
+            throw new InterruptedIOException(
+                "The thread was interrupted whilst waiting for channel data");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param b
+     * @param off
+     * @param len
+     *
+     * @return
+     *
+     * @throws IOException
+     * @throws IOException
+     */
+    public int read(byte[] b, int off, int len) throws IOException {
+        try {
+            block();
+
+            int actual = available();
+
+            if (actual > len) {
+                actual = len;
+            }
+
+            if (actual > 0) {
+                System.arraycopy(msgdata, currentPos, b, off, actual);
+                currentPos += actual;
+            }
+
+            return actual;
+        } catch (MessageStoreEOFException mse) {
+            return -1;
+        } catch (InterruptedException ex) {
+            throw new InterruptedIOException(
+                "The thread was interrupted whilst waiting for channel data");
+        }
+    }
+
+    private void block()
+        throws MessageStoreEOFException, InterruptedException, IOException {
+        if (msgdata == null) {
+            collectNextMessage();
+        }
+
+        if (currentPos >= msgdata.length) {
+            collectNextMessage();
+        }
+    }
+
+    private void startBlockingOperation() throws IOException {
+        synchronized (lock) {
+            if (isBlocking) {
+                throw new IOException((("Cannot read from InputStream! " +
+                    blockingThread) == null) ? "**NULL THREAD**"
+                                             : (blockingThread.getName() +
+                    " is currently performing a blocking operation"));
+            }
+
+            log.debug("Starting blocking operation");
+            blockingThread = Thread.currentThread();
+            isBlocking = true;
+        }
+    }
+
+    private void stopBlockingOperation() throws IOException {
+        synchronized (lock) {
+            log.debug("Completed blocking operation");
+            blockingThread = null;
+            isBlocking = false;
+        }
+    }
+
+    private void collectNextMessage()
+        throws MessageStoreEOFException, InterruptedException, IOException {
+        // Collect the next message
+        startBlockingOperation();
+
+        try {
+            if (type != null) {
+                SshMsgChannelExtendedData msg = null;
+
+                while ((msg == null) && !isClosed()) {
+                    try {
+                        log.debug("Waiting for extended channel data");
+                        msg = (SshMsgChannelExtendedData) messageStore.getMessage(filter,
+                                interrupt);
+                    } catch (MessageNotAvailableException ex) {
+                        // Ignore the timeout but this allows us to review the
+                        // InputStreams state once in a while
+                    }
+                }
+
+                if (msg != null) {
+                    msgdata = msg.getChannelData();
+                    currentPos = 0;
+                } else {
+                    throw new MessageStoreEOFException();
+                }
+            } else {
+                SshMsgChannelData msg = null;
+
+                while ((msg == null) && !isClosed()) {
+                    try {
+                        log.debug("Waiting for channel data");
+                        msg = (SshMsgChannelData) messageStore.getMessage(filter,
+                                interrupt);
+                    } catch (MessageNotAvailableException ex1) {
+                        // Ignore the timeout but this allows us to review the
+                        // InputStreams state once in a while
+                    }
+                }
+
+                if (msg != null) {
+                    msgdata = msg.getChannelData();
+                    currentPos = 0;
+                } else {
+                    throw new MessageStoreEOFException();
+                }
+            }
+        } finally {
+            stopBlockingOperation();
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/connection/ChannelOutputStream.java b/src/com/sshtools/j2ssh/connection/ChannelOutputStream.java
new file mode 100644
index 0000000000000000000000000000000000000000..42f3132a9704d7431d8f3535807f08ea1d07f88e
--- /dev/null
+++ b/src/com/sshtools/j2ssh/connection/ChannelOutputStream.java
@@ -0,0 +1,160 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.connection;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.27 $
+ */
+public class ChannelOutputStream extends OutputStream {
+    private static Log log = LogFactory.getLog(ChannelOutputStream.class);
+    private Channel channel;
+    private boolean isClosed = false;
+    private Integer type = null;
+
+    /**
+     * Creates a new ChannelOutputStream object.
+     *
+     * @param channel
+     * @param type
+     */
+    public ChannelOutputStream(Channel channel, Integer type) {
+        this.channel = channel;
+        this.type = type;
+    }
+
+    /**
+     * Creates a new ChannelOutputStream object.
+     *
+     * @param channel
+     */
+    public ChannelOutputStream(Channel channel) {
+        this(channel, null);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isClosed() {
+        return isClosed;
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    public void close() throws IOException {
+        log.info("Closing ChannelOutputStream");
+        isClosed = true;
+
+        // Send an EOF if the channel is not closed
+        if (!channel.isClosed()) {
+            channel.connection.sendChannelEOF(channel);
+        }
+    }
+
+    /**
+     *
+     *
+     * @param b
+     * @param off
+     * @param len
+     *
+     * @throws IOException
+     */
+    public void write(byte[] b, int off, int len) throws IOException {
+        if (isClosed) {
+            throw new IOException("The ChannelOutputStream is closed!");
+        }
+
+        byte[] data = null;
+
+        if ((off > 0) || (len < b.length)) {
+            data = new byte[len];
+            System.arraycopy(b, off, data, 0, len);
+        } else {
+            data = b;
+        }
+
+        sendChannelData(data);
+    }
+
+    /**
+     *
+     *
+     * @param b
+     *
+     * @throws IOException
+     */
+    public void write(int b) throws IOException {
+        if (isClosed) {
+            throw new IOException("The ChannelOutputStream is closed!");
+        }
+
+        byte[] data = new byte[1];
+        data[0] = (byte) b;
+        sendChannelData(data);
+    }
+
+    private void sendChannelData(byte[] data) throws IOException {
+        channel.sendChannelData(data);
+
+        /* int sent = 0;
+            int block;
+            int remaining;
+            long max;
+            byte[] buffer;
+            ChannelDataWindow window = channel.getRemoteWindow();
+            while (sent < data.length) {
+                remaining = data.length - sent;
+                max = ((window.getWindowSpace() < channel.getRemotePacketSize())
+           && window.getWindowSpace() > 0)
+           ? window.getWindowSpace() : channel.getRemotePacketSize();
+                block = (max < remaining) ? (int) max : remaining;
+                channel.remoteWindow.consumeWindowSpace(block);
+                buffer = new byte[block];
+                System.arraycopy(data, sent, buffer, 0, block);
+                if (type != null) {
+           channel.sendChannelExtData(type.intValue(), buffer);
+                } else {
+           channel.sendChannelData(buffer);
+                }
+                sent += block;
+            }*/
+    }
+}
diff --git a/src/com/sshtools/j2ssh/connection/ChannelState.java b/src/com/sshtools/j2ssh/connection/ChannelState.java
new file mode 100644
index 0000000000000000000000000000000000000000..1145ebefc4ecd3e6ac38b3290aff7fcbab2ac725
--- /dev/null
+++ b/src/com/sshtools/j2ssh/connection/ChannelState.java
@@ -0,0 +1,65 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.connection;
+
+import com.sshtools.j2ssh.util.State;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.17 $
+ */
+public class ChannelState extends State {
+    /**  */
+    public final static int CHANNEL_UNINITIALIZED = 1;
+
+    /**  */
+    public final static int CHANNEL_OPEN = 2;
+
+    /**  */
+    public final static int CHANNEL_CLOSED = 3;
+
+    /**
+     * Creates a new ChannelState object.
+     */
+    public ChannelState() {
+        super(CHANNEL_UNINITIALIZED);
+    }
+
+    /**
+     *
+     *
+     * @param state
+     *
+     * @return
+     */
+    public boolean isValidState(int state) {
+        return ((state == CHANNEL_UNINITIALIZED) || (state == CHANNEL_OPEN) ||
+        (state == CHANNEL_CLOSED));
+    }
+}
diff --git a/src/com/sshtools/j2ssh/connection/ConnectionProtocol.java b/src/com/sshtools/j2ssh/connection/ConnectionProtocol.java
new file mode 100644
index 0000000000000000000000000000000000000000..e7741687124530fbf3b7e86315ae344027d53d06
--- /dev/null
+++ b/src/com/sshtools/j2ssh/connection/ConnectionProtocol.java
@@ -0,0 +1,940 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.connection;
+
+import com.sshtools.j2ssh.SshException;
+import com.sshtools.j2ssh.transport.AsyncService;
+import com.sshtools.j2ssh.transport.MessageStoreEOFException;
+import com.sshtools.j2ssh.transport.ServiceState;
+import com.sshtools.j2ssh.transport.SshMessage;
+import com.sshtools.j2ssh.transport.TransportProtocolState;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+
+import java.util.HashMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.68 $
+ */
+public class ConnectionProtocol extends AsyncService {
+    private static Log log = LogFactory.getLog(ConnectionProtocol.class);
+    private HashSet reusableChannels = new HashSet();
+    private Map activeChannels = new ConcurrentHashMap();
+    private Map allowedChannels = new HashMap();
+    private Map globalRequests = new HashMap();
+    private long nextChannelId = 0;
+
+    /**
+     * Creates a new ConnectionProtocol object.
+     */
+    public ConnectionProtocol() {
+        super("ssh-connection");
+    }
+
+    /**
+     *
+     *
+     * @param channelName
+     * @param cf
+     *
+     * @throws IOException
+     */
+    public void addChannelFactory(String channelName, ChannelFactory cf)
+        throws IOException {
+        allowedChannels.put(channelName, cf);
+    }
+
+    /**
+     *
+     *
+     * @param channelName
+     */
+    public void removeChannelFactory(String channelName) {
+        allowedChannels.remove(channelName);
+    }
+
+    /**
+     *
+     *
+     * @param channelName
+     *
+     * @return
+     */
+    public boolean containsChannelFactory(String channelName) {
+        return allowedChannels.containsKey(channelName);
+    }
+
+    /**
+     *
+     *
+     * @param requestName
+     * @param handler
+     */
+    public void allowGlobalRequest(String requestName,
+        GlobalRequestHandler handler) {
+        globalRequests.put(requestName, handler);
+    }
+
+    /**
+     *
+     *
+     * @param channel
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public synchronized boolean openChannel(Channel channel)
+        throws IOException {
+        return openChannel(channel, null);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isConnected() {
+        return ((transport.getState().getValue() == TransportProtocolState.CONNECTED) ||
+        (transport.getState().getValue() == TransportProtocolState.PERFORMING_KEYEXCHANGE)) &&
+        (getState().getValue() == ServiceState.SERVICE_STARTED);
+    }
+
+    private Long getChannelId() {
+       // synchronized (activeChannels) { 
+            if (reusableChannels.size() <= 0) {
+                return new Long(nextChannelId++);
+            } else {
+                return (Long) reusableChannels.iterator().next();
+            }
+        //}
+    }
+
+    /**
+     *
+     *
+     * @param channel
+     * @param eventListener
+     *
+     * @return
+     *
+     * @throws IOException
+     * @throws SshException
+     */
+    public synchronized boolean openChannel(Channel channel,
+        ChannelEventListener eventListener) throws IOException {
+        //synchronized (activeChannels) {
+            Long channelId = getChannelId();
+
+            // Create the message
+            SshMsgChannelOpen msg = new SshMsgChannelOpen(channel.getChannelType(),
+                    channelId.longValue(),
+                    channel.getLocalWindow().getWindowSpace(),
+                    channel.getLocalPacketSize(), channel.getChannelOpenData());
+
+            // Send the message
+            transport.sendMessage(msg, this);
+
+            // Wait for the next message to confirm the open channel (or not)
+            int[] messageIdFilter = new int[2];
+            messageIdFilter[0] = SshMsgChannelOpenConfirmation.SSH_MSG_CHANNEL_OPEN_CONFIRMATION;
+            messageIdFilter[1] = SshMsgChannelOpenFailure.SSH_MSG_CHANNEL_OPEN_FAILURE;
+
+            try {
+                SshMessage result = messageStore.getMessage(messageIdFilter);
+
+                if (result.getMessageId() == SshMsgChannelOpenConfirmation.SSH_MSG_CHANNEL_OPEN_CONFIRMATION) {
+                    SshMsgChannelOpenConfirmation conf = (SshMsgChannelOpenConfirmation) result;
+                    activeChannels.put(channelId, channel);
+                    log.debug("Initiating channel");
+                    channel.init(this, channelId.longValue(),
+                        conf.getSenderChannel(), conf.getInitialWindowSize(),
+                        conf.getMaximumPacketSize(), eventListener);
+                    channel.open();
+                    log.info("Channel " +
+                        String.valueOf(channel.getLocalChannelId()) +
+                        " is open [" + channel.getName() + "]");
+
+                    return true;
+                } else {
+                    // Make sure the channels state is closed
+                    channel.getState().setValue(ChannelState.CHANNEL_CLOSED);
+
+                    return false;
+                }
+            } catch (MessageStoreEOFException mse) {
+                throw new IOException(mse.getMessage());
+            } catch (InterruptedException ex) {
+                throw new SshException(
+                    "The thread was interrupted whilst waiting for a connection protocol message");
+            }
+        //}
+    }
+
+    /**
+     *
+     */
+    protected synchronized void onStop() {
+        log.info("Closing all active channels");
+	//	synchronized (activeChannels) {
+		log.info("thread has "+activeChannels.values().size()+" active channels to stop");
+        try {
+            Channel channel;
+
+            for (Iterator x = activeChannels.values().iterator(); x.hasNext();) {
+                channel = (Channel) x.next();
+
+                if (channel != null) {
+                    if (log.isDebugEnabled()) {
+                        log.debug("Closing " + channel.getName() + " id=" +
+                            String.valueOf(channel.getLocalChannelId()));
+                    }
+
+                    channel.close();
+                }
+            }
+        } catch (Throwable t) {
+			log.error("Unable to close all channels: "+t.getMessage(),t);
+        }
+
+        activeChannels.clear();
+	//	}
+    }
+
+    /**
+     *
+     *
+     * @param channel
+     * @param data
+     *
+     * @throws IOException
+     */
+    public synchronized void sendChannelData(Channel channel, byte[] data)
+        throws IOException {
+        synchronized (channel.getState()) {
+            if (log.isDebugEnabled()) {
+                log.debug("Sending " + String.valueOf(data.length) +
+                    " bytes for channel id " +
+                    String.valueOf(channel.getLocalChannelId()));
+            }
+
+            int sent = 0;
+            int block;
+            int remaining;
+            long max;
+            byte[] buffer;
+            ChannelDataWindow window = channel.getRemoteWindow();
+
+            while (sent < data.length) {
+                remaining = data.length - sent;
+                max = ((window.getWindowSpace() < channel.getRemotePacketSize()) &&
+                    (window.getWindowSpace() > 0)) ? window.getWindowSpace()
+                                                   : channel.getRemotePacketSize();
+                block = (max < remaining) ? (int) max : remaining;
+                channel.remoteWindow.consumeWindowSpace(block);
+                buffer = new byte[block];
+                System.arraycopy(data, sent, buffer, 0, block);
+
+                SshMsgChannelData msg = new SshMsgChannelData(channel.getRemoteChannelId(),
+                        buffer);
+                transport.sendMessage(msg, this);
+
+                /*                if (type != null) {
+                     channel.sendChannelExtData(type.intValue(), buffer);
+                                } else {
+                                    channel.sendChannelData(buffer);
+                                }*/
+                sent += block;
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @param channel
+     *
+     * @throws IOException
+     */
+    public void sendChannelEOF(Channel channel) throws IOException {
+        //synchronized (activeChannels) {
+            if (!activeChannels.containsValue(channel)) {
+                throw new IOException(
+                    "Attempt to send EOF for a non existent channel " +
+                    String.valueOf(channel.getLocalChannelId()));
+            }
+
+            log.info("Local computer has set channel " +
+                String.valueOf(channel.getLocalChannelId()) + " to EOF [" +
+                channel.getName() + "]");
+
+            SshMsgChannelEOF msg = new SshMsgChannelEOF(channel.getRemoteChannelId());
+            transport.sendMessage(msg, this);
+       // }
+    }
+
+    /**
+     *
+     *
+     * @param channel
+     * @param extendedType
+     * @param data
+     *
+     * @throws IOException
+     */
+    public synchronized void sendChannelExtData(Channel channel,
+        int extendedType, byte[] data) throws IOException {
+        channel.getRemoteWindow().consumeWindowSpace(data.length);
+
+        int sent = 0;
+        int block;
+        int remaining;
+        long max;
+        byte[] buffer;
+        ChannelDataWindow window = channel.getRemoteWindow();
+
+        while (sent < data.length) {
+            remaining = data.length - sent;
+            max = ((window.getWindowSpace() < channel.getRemotePacketSize()) &&
+                (window.getWindowSpace() > 0)) ? window.getWindowSpace()
+                                               : channel.getRemotePacketSize();
+            block = (max < remaining) ? (int) max : remaining;
+            channel.remoteWindow.consumeWindowSpace(block);
+            buffer = new byte[block];
+            System.arraycopy(data, sent, buffer, 0, block);
+
+            SshMsgChannelExtendedData msg = new SshMsgChannelExtendedData(channel.getRemoteChannelId(),
+                    extendedType, buffer);
+            transport.sendMessage(msg, this);
+
+            /*                if (type != null) {
+                            channel.sendChannelExtData(type.intValue(), buffer);
+                        } else {
+                            channel.sendChannelData(buffer);
+                        }*/
+            sent += block;
+        }
+    }
+
+    /**
+     *
+     *
+     * @param channel
+     * @param requestType
+     * @param wantReply
+     * @param requestData
+     *
+     * @return
+     *
+     * @throws IOException
+     * @throws SshException
+     */
+    public synchronized boolean sendChannelRequest(Channel channel,
+        String requestType, boolean wantReply, byte[] requestData)
+        throws IOException {
+        boolean success = true;
+        log.info("Sending " + requestType + " request for the " +
+            channel.getChannelType() + " channel");
+
+        SshMsgChannelRequest msg = new SshMsgChannelRequest(channel.getRemoteChannelId(),
+                requestType, wantReply, requestData);
+        transport.sendMessage(msg, this);
+
+        // If the user requests a reply then wait for the message and return result
+        if (wantReply) {
+            // Set up our message filter
+            int[] messageIdFilter = new int[2];
+            
+            messageIdFilter[0] = SshMsgChannelSuccess.SSH_MSG_CHANNEL_SUCCESS;
+            messageIdFilter[1] = SshMsgChannelFailure.SSH_MSG_CHANNEL_FAILURE;
+            
+            log.info("Waiting for channel request reply");
+
+            try {
+              // Wait for either success or failure
+              SshMessage reply = messageStore.getMessage(messageIdFilter);
+              
+              switch (reply.getMessageId()) {
+              case SshMsgChannelSuccess.SSH_MSG_CHANNEL_SUCCESS: {
+                log.info("Channel request succeeded");
+                success = true;
+                
+                break;
+              }
+              
+              case SshMsgChannelFailure.SSH_MSG_CHANNEL_FAILURE: {
+                log.info("Channel request failed");
+                success = false;
+                
+                break;
+              }
+              } 
+            } catch (InterruptedException ex) {
+                throw new SshException(
+                    "The thread was interrupted whilst waiting for a connection protocol message");
+            }
+        }
+
+        return success;
+    }
+
+    /**
+     *
+     *
+     * @param channel
+     *
+     * @throws IOException
+     */
+    public void sendChannelRequestFailure(Channel channel)
+        throws IOException {
+        SshMsgChannelFailure msg = new SshMsgChannelFailure(channel.getRemoteChannelId());
+        transport.sendMessage(msg, this);
+    }
+
+    /**
+     *
+     *
+     * @param channel
+     *
+     * @throws IOException
+     */
+    public void sendChannelRequestSuccess(Channel channel)
+        throws IOException {
+        SshMsgChannelSuccess msg = new SshMsgChannelSuccess(channel.getRemoteChannelId());
+        transport.sendMessage(msg, this);
+    }
+
+    /**
+     *
+     *
+     * @param channel
+     * @param bytesToAdd
+     *
+     * @throws IOException
+     */
+    public void sendChannelWindowAdjust(Channel channel, long bytesToAdd)
+        throws IOException {
+        log.debug("Increasing window size by " + String.valueOf(bytesToAdd) +
+            " bytes");
+
+        SshMsgChannelWindowAdjust msg = new SshMsgChannelWindowAdjust(channel.getRemoteChannelId(),
+                bytesToAdd);
+        transport.sendMessage(msg, this);
+    }
+
+    /**
+     *
+     *
+     * @param requestName
+     * @param wantReply
+     * @param requestData
+     *
+     * @return
+     *
+     * @throws IOException
+     * @throws SshException
+     */
+    public synchronized byte[] sendGlobalRequest(String requestName,
+        boolean wantReply, byte[] requestData) throws IOException {
+        boolean success = true;
+        SshMsgGlobalRequest msg = new SshMsgGlobalRequest(requestName, true,
+                requestData);
+        transport.sendMessage(msg, this);
+
+        if (wantReply) {
+            // Set up our message filter
+            int[] messageIdFilter = new int[2];
+            messageIdFilter[0] = SshMsgRequestSuccess.SSH_MSG_REQUEST_SUCCESS;
+            messageIdFilter[1] = SshMsgRequestFailure.SSH_MSG_REQUEST_FAILURE;
+            log.debug("Waiting for global request reply");
+
+            try {
+                // Wait for either success or failure
+                SshMessage reply = messageStore.getMessage(messageIdFilter);
+
+                switch (reply.getMessageId()) {
+                case SshMsgRequestSuccess.SSH_MSG_REQUEST_SUCCESS: {
+                    log.debug("Global request succeeded");
+
+                    return ((SshMsgRequestSuccess) reply).getRequestData();
+                }
+
+                case SshMsgRequestFailure.SSH_MSG_REQUEST_FAILURE: {
+                    log.debug("Global request failed");
+                    throw new SshException("The request failed");
+                }
+                }
+            } catch (InterruptedException ex) {
+                throw new SshException(
+                    "The thread was interrupted whilst waiting for a connection protocol message");
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected int[] getAsyncMessageFilter() {
+        int[] messageFilter = new int[10];
+        messageFilter[0] = SshMsgGlobalRequest.SSH_MSG_GLOBAL_REQUEST;
+        messageFilter[3] = SshMsgChannelOpen.SSH_MSG_CHANNEL_OPEN;
+        messageFilter[4] = SshMsgChannelClose.SSH_MSG_CHANNEL_CLOSE;
+        messageFilter[5] = SshMsgChannelEOF.SSH_MSG_CHANNEL_EOF;
+        messageFilter[6] = SshMsgChannelExtendedData.SSH_MSG_CHANNEL_EXTENDED_DATA;
+        messageFilter[7] = SshMsgChannelData.SSH_MSG_CHANNEL_DATA;
+        messageFilter[8] = SshMsgChannelRequest.SSH_MSG_CHANNEL_REQUEST;
+        messageFilter[9] = SshMsgChannelWindowAdjust.SSH_MSG_CHANNEL_WINDOW_ADJUST;
+
+        return messageFilter;
+    }
+
+    /**
+     *
+     *
+     * @param channel
+     *
+     * @throws IOException
+     */
+    protected void closeChannel(Channel channel) throws IOException {
+        SshMsgChannelClose msg = new SshMsgChannelClose(channel.getRemoteChannelId());
+        log.info("Local computer has closed channel " +
+            String.valueOf(channel.getLocalChannelId()) + "[" +
+            channel.getName() + "]");
+        transport.sendMessage(msg, this);
+    }
+
+    /**
+     *
+     *
+     * @param requestName
+     * @param wantReply
+     * @param requestData
+     *
+     * @throws IOException
+     */
+    protected void onGlobalRequest(String requestName, boolean wantReply,
+        byte[] requestData) throws IOException {
+        log.debug("Processing " + requestName + " global request");
+
+        if (!globalRequests.containsKey(requestName)) {
+            sendGlobalRequestFailure();
+        } else {
+            GlobalRequestHandler handler = (GlobalRequestHandler) globalRequests.get(requestName);
+            GlobalRequestResponse response = handler.processGlobalRequest(requestName,
+                    requestData);
+
+            if (wantReply) {
+                if (response.hasSucceeded()) {
+                    sendGlobalRequestSuccess(response.getResponseData());
+                } else {
+                    sendGlobalRequestFailure();
+                }
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @param msg
+     *
+     * @throws IOException
+     */
+    protected void onMessageReceived(SshMessage msg) throws IOException {
+        // Route the message to the correct handling function
+        switch (msg.getMessageId()) {
+        case SshMsgGlobalRequest.SSH_MSG_GLOBAL_REQUEST: {
+            onMsgGlobalRequest((SshMsgGlobalRequest) msg);
+
+            break;
+        }
+
+        case SshMsgChannelOpen.SSH_MSG_CHANNEL_OPEN: {
+            onMsgChannelOpen((SshMsgChannelOpen) msg);
+
+            break;
+        }
+
+        case SshMsgChannelClose.SSH_MSG_CHANNEL_CLOSE: {
+            onMsgChannelClose((SshMsgChannelClose) msg);
+
+            break;
+        }
+
+        case SshMsgChannelEOF.SSH_MSG_CHANNEL_EOF: {
+            onMsgChannelEOF((SshMsgChannelEOF) msg);
+
+            break;
+        }
+
+        case SshMsgChannelData.SSH_MSG_CHANNEL_DATA: {
+            onMsgChannelData((SshMsgChannelData) msg);
+
+            break;
+        }
+
+        case SshMsgChannelExtendedData.SSH_MSG_CHANNEL_EXTENDED_DATA: {
+            onMsgChannelExtendedData((SshMsgChannelExtendedData) msg);
+
+            break;
+        }
+
+        case SshMsgChannelRequest.SSH_MSG_CHANNEL_REQUEST: {
+            onMsgChannelRequest((SshMsgChannelRequest) msg);
+
+            break;
+        }
+
+        case SshMsgChannelWindowAdjust.SSH_MSG_CHANNEL_WINDOW_ADJUST: {
+            onMsgChannelWindowAdjust((SshMsgChannelWindowAdjust) msg);
+
+            break;
+        }
+
+        default: {
+            // If we never registered it why are we getting it?
+            log.debug("Message not handled");
+            throw new IOException("Unregistered message received!");
+        }
+        }
+    }
+
+    /**
+     *
+     */
+    protected void onServiceAccept() {
+    }
+
+    /**
+     *
+     *
+     * @param startMode
+     *
+     * @throws IOException
+     */
+    protected void onServiceInit(int startMode) throws IOException {
+        log.info("Registering connection protocol messages");
+        messageStore.registerMessage(SshMsgChannelOpenConfirmation.SSH_MSG_CHANNEL_OPEN_CONFIRMATION,
+            SshMsgChannelOpenConfirmation.class);
+        messageStore.registerMessage(SshMsgChannelOpenFailure.SSH_MSG_CHANNEL_OPEN_FAILURE,
+            SshMsgChannelOpenFailure.class);
+        messageStore.registerMessage(SshMsgChannelOpen.SSH_MSG_CHANNEL_OPEN,
+            SshMsgChannelOpen.class);
+        messageStore.registerMessage(SshMsgChannelClose.SSH_MSG_CHANNEL_CLOSE,
+            SshMsgChannelClose.class);
+        messageStore.registerMessage(SshMsgChannelEOF.SSH_MSG_CHANNEL_EOF,
+            SshMsgChannelEOF.class);
+        messageStore.registerMessage(SshMsgChannelData.SSH_MSG_CHANNEL_DATA,
+            SshMsgChannelData.class);
+        messageStore.registerMessage(SshMsgChannelExtendedData.SSH_MSG_CHANNEL_EXTENDED_DATA,
+            SshMsgChannelExtendedData.class);
+        messageStore.registerMessage(SshMsgChannelFailure.SSH_MSG_CHANNEL_FAILURE,
+            SshMsgChannelFailure.class);
+        messageStore.registerMessage(SshMsgChannelRequest.SSH_MSG_CHANNEL_REQUEST,
+            SshMsgChannelRequest.class);
+        messageStore.registerMessage(SshMsgChannelSuccess.SSH_MSG_CHANNEL_SUCCESS,
+            SshMsgChannelSuccess.class);
+        messageStore.registerMessage(SshMsgChannelWindowAdjust.SSH_MSG_CHANNEL_WINDOW_ADJUST,
+            SshMsgChannelWindowAdjust.class);
+        messageStore.registerMessage(SshMsgGlobalRequest.SSH_MSG_GLOBAL_REQUEST,
+            SshMsgGlobalRequest.class);
+        messageStore.registerMessage(SshMsgRequestFailure.SSH_MSG_REQUEST_FAILURE,
+            SshMsgRequestFailure.class);
+        messageStore.registerMessage(SshMsgRequestSuccess.SSH_MSG_REQUEST_SUCCESS,
+            SshMsgRequestSuccess.class);
+    }
+
+    /**
+     *
+     */
+    protected void onServiceRequest() {
+    }
+
+    /**
+     *
+     *
+     * @param channel
+     *
+     * @throws IOException
+     */
+    protected void sendChannelFailure(Channel channel)
+        throws IOException {
+        SshMsgChannelFailure msg = new SshMsgChannelFailure(channel.getRemoteChannelId());
+        transport.sendMessage(msg, this);
+    }
+
+    /**
+     *
+     *
+     * @param channel
+     *
+     * @throws IOException
+     */
+    protected void sendChannelOpenConfirmation(Channel channel)
+        throws IOException {
+        SshMsgChannelOpenConfirmation msg = new SshMsgChannelOpenConfirmation(channel.getRemoteChannelId(),
+                channel.getLocalChannelId(),
+                channel.getLocalWindow().getWindowSpace(),
+                channel.getLocalPacketSize(),
+                channel.getChannelConfirmationData());
+        transport.sendMessage(msg, this);
+    }
+
+    /**
+     *
+     *
+     * @param remoteChannelId
+     * @param reasonCode
+     * @param additionalInfo
+     * @param languageTag
+     *
+     * @throws IOException
+     */
+    protected void sendChannelOpenFailure(long remoteChannelId,
+        long reasonCode, String additionalInfo, String languageTag)
+        throws IOException {
+        SshMsgChannelOpenFailure msg = new SshMsgChannelOpenFailure(remoteChannelId,
+                reasonCode, additionalInfo, languageTag);
+        transport.sendMessage(msg, this);
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    protected void sendGlobalRequestFailure() throws IOException {
+        SshMsgRequestFailure msg = new SshMsgRequestFailure();
+        transport.sendMessage(msg, this);
+    }
+
+    /**
+     *
+     *
+     * @param requestData
+     *
+     * @throws IOException
+     */
+    protected void sendGlobalRequestSuccess(byte[] requestData)
+        throws IOException {
+        SshMsgRequestSuccess msg = new SshMsgRequestSuccess(requestData);
+        transport.sendMessage(msg, this);
+    }
+
+    private Channel getChannel(long channelId) throws IOException {
+        //synchronized (activeChannels) {
+            Long l = new Long(channelId);
+
+            if (!activeChannels.containsKey(l)) {
+                throw new IOException("Non existent channel " + l.toString() +
+                    " requested");
+            }
+			return (Channel) activeChannels.get(l);
+        //}
+    }
+
+    private void onMsgChannelClose(SshMsgChannelClose msg)
+        throws IOException {
+        Channel channel = getChannel(msg.getRecipientChannel());
+
+        // If we have not already closed it then inform the subclasses
+        if (channel == null) {
+            throw new IOException("Remote computer tried to close a " +
+                "non existent channel " +
+                String.valueOf(msg.getRecipientChannel()));
+        }
+
+        log.info("Remote computer has closed channel " +
+            String.valueOf(channel.getLocalChannelId()) + "[" +
+            channel.getName() + "]");
+
+        // If the channel is not already closed then close it
+        if (channel.getState().getValue() != ChannelState.CHANNEL_CLOSED) {
+            channel.remoteClose();
+        }
+    }
+
+    private void onMsgChannelData(SshMsgChannelData msg)
+        throws IOException {
+        if (log.isDebugEnabled()) {
+            log.debug("Received " +
+                String.valueOf(msg.getChannelData().length) +
+                " bytes of data for channel id " +
+                String.valueOf(msg.getRecipientChannel()));
+        }
+
+        // Get the data's channel
+        Channel channel = getChannel(msg.getRecipientChannel());
+        channel.processChannelData(msg);
+    }
+
+    private void onMsgChannelEOF(SshMsgChannelEOF msg)
+        throws IOException {
+        Channel channel = getChannel(msg.getRecipientChannel());
+
+        try {
+            log.info("Remote computer has set channel " +
+                String.valueOf(msg.getRecipientChannel()) + " to EOF [" +
+                channel.getName() + "]");
+            channel.setRemoteEOF();
+        } catch (IOException ioe) {
+            log.info("Failed to close the ChannelInputStream after EOF event");
+        }
+    }
+
+    private void onMsgChannelExtendedData(SshMsgChannelExtendedData msg)
+        throws IOException {
+        Channel channel = getChannel(msg.getRecipientChannel());
+
+        if (channel == null) {
+            throw new IOException(
+                "Remote computer sent data for non existent channel");
+        }
+
+        channel.getLocalWindow().consumeWindowSpace(msg.getChannelData().length);
+        channel.processChannelData(msg);
+    }
+
+    private void onMsgChannelOpen(SshMsgChannelOpen msg)
+        throws IOException {
+        //synchronized (activeChannels) {
+            log.info("Request for " + msg.getChannelType() +
+                " channel recieved");
+
+            // Try to get the channel implementation from the allowed channels
+            ChannelFactory cf = (ChannelFactory) allowedChannels.get(msg.getChannelType());
+
+            if (cf == null) {
+                sendChannelOpenFailure(msg.getSenderChannelId(),
+                    SshMsgChannelOpenFailure.SSH_OPEN_CONNECT_FAILED,
+                    "The channel type is not supported", "");
+                log.info("Request for channel type " + msg.getChannelType() +
+                    " refused");
+
+                return;
+            }
+
+            try {
+                log.info("Creating channel " + msg.getChannelType());
+
+                Channel channel = cf.createChannel(msg.getChannelType(),
+                        msg.getChannelData());
+
+                // Initialize the channel
+                log.info("Initiating channel");
+
+                Long channelId = getChannelId();
+                channel.init(this, channelId.longValue(),
+                    msg.getSenderChannelId(), msg.getInitialWindowSize(),
+                    msg.getMaximumPacketSize());
+                activeChannels.put(channelId, channel);
+                log.info("Sending channel open confirmation");
+
+                // Send the confirmation message
+                sendChannelOpenConfirmation(channel);
+
+                // Open the channel for real
+                channel.open();
+            } catch (InvalidChannelException ice) {
+                sendChannelOpenFailure(msg.getSenderChannelId(),
+                    SshMsgChannelOpenFailure.SSH_OPEN_CONNECT_FAILED,
+                    ice.getMessage(), "");
+            }
+        //}
+    }
+
+    private void onMsgChannelRequest(SshMsgChannelRequest msg)
+        throws IOException {
+        Channel channel = getChannel(msg.getRecipientChannel());
+
+        if (channel == null) {
+            log.warn("Remote computer tried to make a request for " +
+                "a non existence channel!");
+        }
+
+        channel.onChannelRequest(msg.getRequestType(), msg.getWantReply(),
+            msg.getChannelData());
+    }
+
+    private void onMsgChannelWindowAdjust(SshMsgChannelWindowAdjust msg)
+        throws IOException {
+        Channel channel = getChannel(msg.getRecipientChannel());
+
+        if (channel == null) {
+            throw new IOException("Remote computer tried to increase " +
+                "window space for non existent channel " +
+                String.valueOf(msg.getRecipientChannel()));
+        }
+
+        channel.getRemoteWindow().increaseWindowSpace(msg.getBytesToAdd());
+
+        if (log.isDebugEnabled()) {
+            log.debug(String.valueOf(msg.getBytesToAdd()) +
+                " bytes added to remote window");
+            log.debug("Remote window space is " +
+                String.valueOf(channel.getRemoteWindow().getWindowSpace()));
+        }
+    }
+
+    private void onMsgGlobalRequest(SshMsgGlobalRequest msg)
+        throws IOException {
+        onGlobalRequest(msg.getRequestName(), msg.getWantReply(),
+            msg.getRequestData());
+    }
+
+    /**
+     *
+     *
+     * @param channel
+     */
+    protected void freeChannel(Channel channel) {
+        //synchronized (activeChannels) {
+            log.info("Freeing channel " +
+                String.valueOf(channel.getLocalChannelId()) + " [" +
+                channel.getName() + "]");
+
+            Long channelId = new Long(channel.getLocalChannelId());
+            activeChannels.remove(channelId);
+
+            //reusableChannels.add(channelId);
+        //}
+    }
+}
diff --git a/src/com/sshtools/j2ssh/connection/GlobalRequestHandler.java b/src/com/sshtools/j2ssh/connection/GlobalRequestHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..d4c5afdb43efe537d2a25894d8d1dea167b9f183
--- /dev/null
+++ b/src/com/sshtools/j2ssh/connection/GlobalRequestHandler.java
@@ -0,0 +1,46 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.connection;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public interface GlobalRequestHandler {
+    /**
+     *
+     *
+     * @param requestName
+     * @param requestData
+     *
+     * @return
+     */
+    public GlobalRequestResponse processGlobalRequest(String requestName,
+        byte[] requestData);
+}
diff --git a/src/com/sshtools/j2ssh/connection/GlobalRequestResponse.java b/src/com/sshtools/j2ssh/connection/GlobalRequestResponse.java
new file mode 100644
index 0000000000000000000000000000000000000000..26c50c512a1874fea19c7670f66e361f6d0608d2
--- /dev/null
+++ b/src/com/sshtools/j2ssh/connection/GlobalRequestResponse.java
@@ -0,0 +1,76 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.connection;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class GlobalRequestResponse {
+    //public static final GlobalRequestResponse REQUEST_FAILED = new GlobalRequestResponse(false);
+    //public static final GlobalRequestResponse REQUEST_SUCCEEDED = new GlobalRequestResponse(true);
+    private byte[] responseData = null;
+    private boolean succeeded;
+
+    /**
+     * Creates a new GlobalRequestResponse object.
+     *
+     * @param succeeded
+     */
+    public GlobalRequestResponse(boolean succeeded) {
+        this.succeeded = succeeded;
+    }
+
+    /**
+     *
+     *
+     * @param responseData
+     */
+    public void setResponseData(byte[] responseData) {
+        this.responseData = responseData;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getResponseData() {
+        return responseData;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean hasSucceeded() {
+        return succeeded;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/connection/IOChannel.java b/src/com/sshtools/j2ssh/connection/IOChannel.java
new file mode 100644
index 0000000000000000000000000000000000000000..8275b3e4ef03fa16aed8c61e228153097c44b951
--- /dev/null
+++ b/src/com/sshtools/j2ssh/connection/IOChannel.java
@@ -0,0 +1,321 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.connection;
+
+import com.sshtools.j2ssh.io.IOStreamConnector;
+import com.sshtools.j2ssh.transport.MessageNotAvailableException;
+import com.sshtools.j2ssh.transport.MessageStoreEOFException;
+import com.sshtools.j2ssh.transport.SshMessageStore;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public abstract class IOChannel extends Channel {
+    private static Log log = LogFactory.getLog(IOChannel.class);
+
+    /**  */
+    private SshMessageStore incoming = new SshMessageStore();
+
+    /**  */
+    protected ChannelInputStream in;
+
+    /**  */
+    protected ChannelOutputStream out;
+
+    /**  */
+    protected InputStream boundInputStream = null;
+
+    /**  */
+    protected OutputStream boundOutputStream = null;
+
+    //protected IOChannel boundIOChannel = null;
+
+    /**  */
+    protected IOStreamConnector ios = null;
+
+    /**
+     *
+     *
+     * @param connection
+     * @param localChannelId
+     * @param senderChannelId
+     * @param initialWindowSize
+     * @param maximumPacketSize
+     *
+     * @throws IOException
+     */
+    protected void init(ConnectionProtocol connection, long localChannelId,
+        long senderChannelId, long initialWindowSize, long maximumPacketSize)
+        throws IOException {
+        this.in = new ChannelInputStream(incoming); //ChannelInputStream.createStandard(incoming);
+        this.out = new ChannelOutputStream(this);
+        super.init(connection, localChannelId, senderChannelId,
+            initialWindowSize, maximumPacketSize);
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    protected void open() throws IOException {
+        super.open();
+
+        // If were bound send any outstanding messages sitting around
+        if (boundOutputStream != null) {
+            sendOutstandingMessages();
+        }
+
+        // Start the bound inputstream
+        if ((boundInputStream != null) && (ios == null)) {
+            ios.setCloseInput(false);
+            ios.setCloseOutput(false);
+            ios.connect(boundInputStream, out);
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public ChannelInputStream getInputStream() {
+        return in;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public ChannelOutputStream getOutputStream() {
+        return out;
+    }
+
+    /**
+     *
+     *
+     * @param msg
+     *
+     * @throws IOException
+     */
+    protected void onChannelData(SshMsgChannelData msg)
+        throws IOException {
+        // Synchronize on the message store to ensure that another thread
+        // does not try to read its data. This will make sure that the incoming
+        // messages are not being flushed to an outputstream after a bind
+        synchronized (incoming) {
+            if (boundOutputStream != null) {
+                try {
+                    boundOutputStream.write(msg.getChannelData());
+                } catch (IOException ex) {
+                    log.info(
+                        "Could not route data to the bound OutputStream; Closing channel.");
+                    log.info(ex.getMessage());
+                    close();
+                }
+            } else {
+                incoming.addMessage(msg);
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    public void setLocalEOF() throws IOException {
+        super.setLocalEOF();
+
+        if (!out.isClosed()) {
+            out.close();
+        }
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    protected void onChannelEOF() throws IOException {
+        if (!in.isClosed()) {
+            in.close();
+        }
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    protected void onChannelClose() throws IOException {
+        // Close the input/output streams
+        if (!in.isClosed()) {
+            in.close();
+        }
+
+        if (!out.isClosed()) {
+            out.close();
+        }
+
+        // Close the bound channel
+
+        /* if(boundIOChannel!=null && !boundIOChannel.isClosed())
+             boundIOChannel.close();*/
+
+        // Close the IOStream connector if were bound
+        if (ios != null) {
+            ios.close();
+        }
+    }
+
+    /**
+     *
+     *
+     * @param msg
+     *
+     * @throws IOException
+     */
+    protected void onChannelExtData(SshMsgChannelExtendedData msg)
+        throws IOException {
+        // This class will not deal with extended data
+        // incoming.addMessage(msg);
+    }
+
+    /*public void bindIOChannel(IOChannel boundIOChannel) throws IOException {
+       this.boundIOChannel = boundIOChannel;
+        // If the bound channel is open then bind the outputstreams
+        if (boundIOChannel.getState().getValue() == ChannelState.CHANNEL_OPEN) {
+     throw new IOException("You cannot bind to an open channel");
+        }
+        // Create an event listener so we can listen
+        boundIOChannel.addEventListener(new ChannelEventListener() {
+     public void onChannelOpen(Channel channel) {
+         try {
+           bindOutputStream(IOChannel.this.boundIOChannel.getOutputStream());
+           IOChannel.this.boundIOChannel.bindOutputStream(getOutputStream());
+         }
+         catch (IOException ex) {
+           log.info("Failed to bind the channel");
+         }
+     }
+     public void onChannelEOF(Channel channel) {
+       try {
+           //setLocalEOF();
+           close();
+       }
+       catch (IOException ex) {
+         log.info("Failed to set the channel to EOF");
+       }
+     }
+     public void onChannelClose(Channel channel)  {
+       try {
+         if(!isClosed())
+           close();
+       }
+       catch (IOException ex) {
+         log.info("Failed to close the channel");
+       }
+     }
+     public void onDataReceived(Channel channel, byte[] data) {
+     }
+     public void onDataSent(Channel channel, byte[] data) {
+     }
+        });
+     }*/
+    public void bindOutputStream(OutputStream boundOutputStream)
+        throws IOException {
+        // Synchronize on the incoming message store to ensure that no other
+        // messages are added whilst we transfer to a bound state
+        synchronized (incoming) {
+            this.boundOutputStream = boundOutputStream;
+
+            if (state.getValue() == ChannelState.CHANNEL_OPEN) {
+                sendOutstandingMessages();
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @param boundInputStream
+     *
+     * @throws IOException
+     */
+    public void bindInputStream(InputStream boundInputStream)
+        throws IOException {
+        this.boundInputStream = boundInputStream;
+        this.ios = new IOStreamConnector();
+
+        if (state.getValue() == ChannelState.CHANNEL_OPEN) {
+            ios.setCloseInput(false);
+            ios.setCloseOutput(false);
+            ios.connect(boundInputStream, out);
+        }
+    }
+
+    private void sendOutstandingMessages() throws IOException {
+        if ((boundInputStream != null) && (boundOutputStream != null) &&
+                incoming.hasMessages()) {
+            while (true) {
+                try {
+                    // Peek into the message store and look for the next message
+                    SshMsgChannelData msg = (SshMsgChannelData) incoming.peekMessage(SshMsgChannelData.SSH_MSG_CHANNEL_DATA);
+
+                    // Remove the message so we dont process again
+                    incoming.removeMessage(msg);
+
+                    // Write the message out to the bound OutputStream
+                    try {
+                        boundOutputStream.write(msg.getChannelData());
+                    } catch (IOException ex1) {
+                        //log.info("Could not write outstanding messages to the bound OutputStream: "  +ex1.getMessage());
+                        close();
+                    }
+                } catch (MessageStoreEOFException ex) {
+                    break;
+                } catch (MessageNotAvailableException ex) {
+                    break;
+                } catch (InterruptedException ex) {
+                    throw new IOException("The thread was interrupted");
+                }
+            }
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/connection/InvalidChannelException.java b/src/com/sshtools/j2ssh/connection/InvalidChannelException.java
new file mode 100644
index 0000000000000000000000000000000000000000..9158d4ec702ed8c426dd6e40598f479da9e09d2a
--- /dev/null
+++ b/src/com/sshtools/j2ssh/connection/InvalidChannelException.java
@@ -0,0 +1,46 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.connection;
+
+import java.io.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class InvalidChannelException extends IOException {
+    /**
+     * Creates a new InvalidChannelException object.
+     *
+     * @param msg
+     */
+    public InvalidChannelException(String msg) {
+        super(msg);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/connection/SocketChannel.java b/src/com/sshtools/j2ssh/connection/SocketChannel.java
new file mode 100644
index 0000000000000000000000000000000000000000..478c7bdbccc4a3cc8b1a82dec2f8613f7fe7ccb2
--- /dev/null
+++ b/src/com/sshtools/j2ssh/connection/SocketChannel.java
@@ -0,0 +1,199 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.connection;
+
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+
+import java.net.Socket;
+import java.net.SocketException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public abstract class SocketChannel extends Channel {
+    private static Log log = LogFactory.getLog(SocketChannel.class);
+
+    /**  */
+    protected Socket socket = null;
+    Thread thread;
+
+    /**
+     *
+     *
+     * @param socket
+     *
+     * @throws IOException
+     */
+    public void bindSocket(Socket socket) throws IOException {
+        if (state.getValue() == ChannelState.CHANNEL_UNINITIALIZED) {
+            this.socket = socket;
+        } else {
+            throw new IOException(
+                "The socket can only be bound to an unitialized channel");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param msg
+     *
+     * @throws IOException
+     */
+    protected void onChannelData(SshMsgChannelData msg)
+        throws IOException {
+        try {
+            socket.getOutputStream().write(msg.getChannelData());
+        } catch (IOException ex) {
+        }
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    protected void onChannelEOF() throws IOException {
+        try {
+            //synchronized(state) {
+            //if (isOpen())
+            //  close();
+            socket.shutdownOutput();
+
+            // }
+        } catch (IOException ex) {
+            log.info(
+                "Failed to shutdown Socket OutputStream in response to EOF event: " +
+                ex.getMessage());
+        }
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    protected void onChannelClose() throws IOException {
+        try {
+            socket.close();
+        } catch (IOException ex) {
+            log.info("Failed to close socket on channel close event: " +
+                ex.getMessage());
+        }
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    protected void onChannelOpen() throws IOException {
+        if (socket == null) {
+            throw new IOException(
+                "The socket must be bound to the channel before opening");
+        }
+
+        thread = new Thread(new SocketReader());
+        thread.start();
+    }
+
+    /**
+     *
+     *
+     * @param msg
+     *
+     * @throws IOException
+     */
+    protected void onChannelExtData(SshMsgChannelExtendedData msg)
+        throws IOException {
+        // We do not have an extended data channel for the socket so ignore
+    }
+
+    class SocketReader implements Runnable {
+        public void run() {
+            byte[] buffer = new byte[getMaximumPacketSize()];
+            ByteArrayWriter baw = new ByteArrayWriter();
+
+            try {
+                socket.setSoTimeout(2000);
+            } catch (SocketException ex2) {
+            }
+
+            try {
+                int read = 0;
+
+                while ((read >= 0) && !isClosed()) {
+                    try {
+                        read = socket.getInputStream().read(buffer);
+                    } catch (InterruptedIOException ex1) {
+                        read = ex1.bytesTransferred;
+                    }
+
+                    synchronized (state) {
+                        if (isClosed() || isLocalEOF()) {
+                            break;
+                        }
+
+                        if (read > 0) {
+                            baw.write(buffer, 0, read);
+                            sendChannelData(baw.toByteArray());
+                            baw.reset();
+                        }
+                    }
+                }
+            } catch (IOException ex) {
+                // Break out of the while loop
+            }
+
+            try {
+                synchronized (state) {
+                    if (!isLocalEOF()) {
+                        setLocalEOF();
+                    }
+
+                    if (isOpen()) {
+                        close();
+                    }
+                }
+            } catch (Exception ex) {
+                log.info("Failed to send channel EOF message: " +
+                    ex.getMessage());
+            }
+
+            thread = null;
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/connection/SshMsgChannelClose.java b/src/com/sshtools/j2ssh/connection/SshMsgChannelClose.java
new file mode 100644
index 0000000000000000000000000000000000000000..b31204e40b21ba0592d9dc5da286d0bafc9cf246
--- /dev/null
+++ b/src/com/sshtools/j2ssh/connection/SshMsgChannelClose.java
@@ -0,0 +1,115 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.connection;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.SshMessage;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.20 $
+ */
+public class SshMsgChannelClose extends SshMessage {
+    /**  */
+    protected final static int SSH_MSG_CHANNEL_CLOSE = 97;
+
+    // The recipient channel id
+    private long recipientChannel;
+
+    /**
+     * Creates a new SshMsgChannelClose object.
+     *
+     * @param recipientChannel
+     */
+    public SshMsgChannelClose(long recipientChannel) {
+        super(SSH_MSG_CHANNEL_CLOSE);
+        this.recipientChannel = recipientChannel;
+    }
+
+    /**
+     * Creates a new SshMsgChannelClose object.
+     */
+    public SshMsgChannelClose() {
+        super(SSH_MSG_CHANNEL_CLOSE);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_CHANNEL_CLOSE";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getRecipientChannel() {
+        return recipientChannel;
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+        try {
+            baw.writeInt(recipientChannel);
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Invalid message data");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+        try {
+            recipientChannel = bar.readInt();
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Invalid message data");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/connection/SshMsgChannelData.java b/src/com/sshtools/j2ssh/connection/SshMsgChannelData.java
new file mode 100644
index 0000000000000000000000000000000000000000..4b773d50bcfe6127afcaaa7020c64e253a2f555e
--- /dev/null
+++ b/src/com/sshtools/j2ssh/connection/SshMsgChannelData.java
@@ -0,0 +1,148 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.connection;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.SshMessage;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.22 $
+ */
+public class SshMsgChannelData extends SshMessage {
+    /**  */
+    public final static int SSH_MSG_CHANNEL_DATA = 94;
+
+    // The channel data
+    private byte[] channelData;
+
+    // The recipient channel id
+    private long recipientChannel;
+
+    /**
+     * Creates a new SshMsgChannelData object.
+     *
+     * @param recipientChannel
+     * @param channelData
+     */
+    public SshMsgChannelData(long recipientChannel, byte[] channelData) {
+        super(SSH_MSG_CHANNEL_DATA);
+        this.recipientChannel = recipientChannel;
+        this.channelData = channelData;
+    }
+
+    /**
+     * Creates a new SshMsgChannelData object.
+     */
+    public SshMsgChannelData() {
+        super(SSH_MSG_CHANNEL_DATA);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getChannelData() {
+        return channelData;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getChannelDataLength() {
+        return channelData.length;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_CHANNEL_DATA";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getRecipientChannel() {
+        return recipientChannel;
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+        try {
+            baw.writeInt(recipientChannel);
+
+            if (channelData != null) {
+                baw.writeBinaryString(channelData);
+            } else {
+                baw.writeInt(0);
+            }
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Invalid message data");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+        try {
+            recipientChannel = bar.readInt();
+
+            if (bar.available() > 0) {
+                channelData = bar.readBinaryString();
+            }
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Invalid message data");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/connection/SshMsgChannelEOF.java b/src/com/sshtools/j2ssh/connection/SshMsgChannelEOF.java
new file mode 100644
index 0000000000000000000000000000000000000000..af7dc33688ef78e9a44f0db4806a7e5734816b65
--- /dev/null
+++ b/src/com/sshtools/j2ssh/connection/SshMsgChannelEOF.java
@@ -0,0 +1,113 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.connection;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.SshMessage;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.20 $
+ */
+public class SshMsgChannelEOF extends SshMessage {
+    /**  */
+    protected final static int SSH_MSG_CHANNEL_EOF = 96;
+    private long recipientChannel;
+
+    /**
+     * Creates a new SshMsgChannelEOF object.
+     *
+     * @param recipientChannel
+     */
+    public SshMsgChannelEOF(long recipientChannel) {
+        super(SSH_MSG_CHANNEL_EOF);
+        this.recipientChannel = recipientChannel;
+    }
+
+    /**
+     * Creates a new SshMsgChannelEOF object.
+     */
+    public SshMsgChannelEOF() {
+        super(SSH_MSG_CHANNEL_EOF);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_CHANNEL_EOF";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getRecipientChannel() {
+        return recipientChannel;
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+        try {
+            baw.writeInt(recipientChannel);
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Invalid message data");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+        try {
+            recipientChannel = bar.readInt();
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Invalid message data");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/connection/SshMsgChannelExtendedData.java b/src/com/sshtools/j2ssh/connection/SshMsgChannelExtendedData.java
new file mode 100644
index 0000000000000000000000000000000000000000..7937aa5ee28aff12ea1f69e7f595d117631c137e
--- /dev/null
+++ b/src/com/sshtools/j2ssh/connection/SshMsgChannelExtendedData.java
@@ -0,0 +1,153 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.connection;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.SshMessage;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.21 $
+ */
+public class SshMsgChannelExtendedData extends SshMessage {
+    /**  */
+    public final static int SSH_MSG_CHANNEL_EXTENDED_DATA = 95;
+
+    /**  */
+    public final static int SSH_EXTENDED_DATA_STDERR = 1;
+    private byte[] channelData;
+    private int dataTypeCode;
+    private long recipientChannel;
+
+    /**
+     * Creates a new SshMsgChannelExtendedData object.
+     *
+     * @param recipientChannel
+     * @param dataTypeCode
+     * @param channelData
+     */
+    public SshMsgChannelExtendedData(long recipientChannel, int dataTypeCode,
+        byte[] channelData) {
+        super(SSH_MSG_CHANNEL_EXTENDED_DATA);
+        this.recipientChannel = recipientChannel;
+        this.dataTypeCode = dataTypeCode;
+        this.channelData = channelData;
+    }
+
+    /**
+     * Creates a new SshMsgChannelExtendedData object.
+     */
+    public SshMsgChannelExtendedData() {
+        super(SSH_MSG_CHANNEL_EXTENDED_DATA);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getChannelData() {
+        return channelData;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getDataTypeCode() {
+        return dataTypeCode;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_CHANNEL_EXTENDED_DATA";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getRecipientChannel() {
+        return recipientChannel;
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+        try {
+            baw.writeInt(recipientChannel);
+            baw.writeInt(dataTypeCode);
+
+            if (channelData != null) {
+                baw.writeBinaryString(channelData);
+            } else {
+                baw.writeString("");
+            }
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Invalid message data");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+        try {
+            recipientChannel = bar.readInt();
+            dataTypeCode = (int) bar.readInt();
+
+            if (bar.available() > 0) {
+                channelData = bar.readBinaryString();
+            }
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Invalid message data");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/connection/SshMsgChannelFailure.java b/src/com/sshtools/j2ssh/connection/SshMsgChannelFailure.java
new file mode 100644
index 0000000000000000000000000000000000000000..6be63a69dc287584509d935d9506e348e595eb72
--- /dev/null
+++ b/src/com/sshtools/j2ssh/connection/SshMsgChannelFailure.java
@@ -0,0 +1,113 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.connection;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.SshMessage;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.20 $
+ */
+public class SshMsgChannelFailure extends SshMessage {
+    /**  */
+    protected final static int SSH_MSG_CHANNEL_FAILURE = 100;
+    private long channelId;
+
+    /**
+     * Creates a new SshMsgChannelFailure object.
+     *
+     * @param recipientChannelId
+     */
+    public SshMsgChannelFailure(long recipientChannelId) {
+        super(SSH_MSG_CHANNEL_FAILURE);
+        channelId = recipientChannelId;
+    }
+
+    /**
+     * Creates a new SshMsgChannelFailure object.
+     */
+    public SshMsgChannelFailure() {
+        super(SSH_MSG_CHANNEL_FAILURE);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_CHANNEL_FAILURE";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getRecipientChannelId() {
+        return channelId;
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+        try {
+            baw.writeInt(channelId);
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Invalid message data");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+        try {
+            channelId = bar.readInt();
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Invalid message data");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/connection/SshMsgChannelOpen.java b/src/com/sshtools/j2ssh/connection/SshMsgChannelOpen.java
new file mode 100644
index 0000000000000000000000000000000000000000..46d2e8b5212bf2bc5f42aa9a96a5d110197fc3f7
--- /dev/null
+++ b/src/com/sshtools/j2ssh/connection/SshMsgChannelOpen.java
@@ -0,0 +1,177 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.connection;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.SshMessage;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.20 $
+ */
+public class SshMsgChannelOpen extends SshMessage {
+    /**  */
+    protected final static int SSH_MSG_CHANNEL_OPEN = 90;
+    private String channelType;
+    private byte[] channelData;
+    private long initialWindowSize;
+    private long maximumPacketSize;
+    private long senderChannelId;
+
+    /**
+     * Creates a new SshMsgChannelOpen object.
+     *
+     * @param channelType
+     * @param senderChannelId
+     * @param initialWindowSize
+     * @param maximumPacketSize
+     * @param channelData
+     */
+    public SshMsgChannelOpen(String channelType, long senderChannelId,
+        long initialWindowSize, long maximumPacketSize, byte[] channelData) {
+        super(SSH_MSG_CHANNEL_OPEN);
+        this.channelType = channelType;
+        this.senderChannelId = senderChannelId;
+        this.initialWindowSize = initialWindowSize;
+        this.maximumPacketSize = maximumPacketSize;
+        this.channelData = channelData;
+    }
+
+    /**
+     * Creates a new SshMsgChannelOpen object.
+     */
+    public SshMsgChannelOpen() {
+        super(SSH_MSG_CHANNEL_OPEN);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getChannelData() {
+        return channelData;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getChannelType() {
+        return channelType;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getInitialWindowSize() {
+        return initialWindowSize;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getMaximumPacketSize() {
+        return maximumPacketSize;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_CHANNEL_OPEN";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getSenderChannelId() {
+        return senderChannelId;
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+        try {
+            baw.writeString(channelType);
+            baw.writeInt(senderChannelId);
+            baw.writeInt(initialWindowSize);
+            baw.writeInt(maximumPacketSize);
+
+            if (channelData != null) {
+                baw.write(channelData);
+            }
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Could not write message data");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+        try {
+            channelType = bar.readString();
+            senderChannelId = bar.readInt();
+            initialWindowSize = bar.readInt();
+            maximumPacketSize = bar.readInt();
+
+            if (bar.available() > 0) {
+                channelData = new byte[bar.available()];
+                bar.read(channelData);
+            }
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Invalid message data");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/connection/SshMsgChannelOpenConfirmation.java b/src/com/sshtools/j2ssh/connection/SshMsgChannelOpenConfirmation.java
new file mode 100644
index 0000000000000000000000000000000000000000..690d386d45a6af53a71ba37430c9a2926037a084
--- /dev/null
+++ b/src/com/sshtools/j2ssh/connection/SshMsgChannelOpenConfirmation.java
@@ -0,0 +1,178 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.connection;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.SshMessage;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.20 $
+ */
+public class SshMsgChannelOpenConfirmation extends SshMessage {
+    /**  */
+    protected final static int SSH_MSG_CHANNEL_OPEN_CONFIRMATION = 91;
+    private byte[] channelData;
+    private long initialWindowSize;
+    private long maximumPacketSize;
+    private long recipientChannel;
+    private long senderChannel;
+
+    /**
+     * Creates a new SshMsgChannelOpenConfirmation object.
+     *
+     * @param recipientChannel
+     * @param senderChannel
+     * @param initialWindowSize
+     * @param maximumPacketSize
+     * @param channelData
+     */
+    public SshMsgChannelOpenConfirmation(long recipientChannel,
+        long senderChannel, long initialWindowSize, long maximumPacketSize,
+        byte[] channelData) {
+        super(SSH_MSG_CHANNEL_OPEN_CONFIRMATION);
+        this.recipientChannel = recipientChannel;
+        this.senderChannel = senderChannel;
+        this.initialWindowSize = initialWindowSize;
+        this.maximumPacketSize = maximumPacketSize;
+        this.channelData = channelData;
+    }
+
+    /**
+     * Creates a new SshMsgChannelOpenConfirmation object.
+     */
+    public SshMsgChannelOpenConfirmation() {
+        super(SSH_MSG_CHANNEL_OPEN_CONFIRMATION);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getChannelData() {
+        return channelData;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getInitialWindowSize() {
+        return initialWindowSize;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getMaximumPacketSize() {
+        return maximumPacketSize;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_CHANNEL_OPEN_CONFIRMATION";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getRecipientChannel() {
+        return recipientChannel;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getSenderChannel() {
+        return senderChannel;
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+        try {
+            baw.writeInt(recipientChannel);
+            baw.writeInt(senderChannel);
+            baw.writeInt(initialWindowSize);
+            baw.writeInt(maximumPacketSize);
+
+            if (channelData != null) {
+                baw.write(channelData);
+            }
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Invalid message data");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+        try {
+            recipientChannel = bar.readInt();
+            senderChannel = bar.readInt();
+            initialWindowSize = bar.readInt();
+            maximumPacketSize = bar.readInt();
+
+            if (bar.available() > 0) {
+                channelData = new byte[bar.available()];
+                bar.read(channelData);
+            }
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Invalid message data");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/connection/SshMsgChannelOpenFailure.java b/src/com/sshtools/j2ssh/connection/SshMsgChannelOpenFailure.java
new file mode 100644
index 0000000000000000000000000000000000000000..e613c46863aa79777b7bb2eb4d5a72b0223f2acd
--- /dev/null
+++ b/src/com/sshtools/j2ssh/connection/SshMsgChannelOpenFailure.java
@@ -0,0 +1,168 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.connection;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.SshMessage;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.20 $
+ */
+public class SshMsgChannelOpenFailure extends SshMessage {
+    /**  */
+    protected final static int SSH_MSG_CHANNEL_OPEN_FAILURE = 92;
+
+    /**  */
+    protected final static long SSH_OPEN_ADMINISTRATIVELY_PROHIBITED = 1;
+
+    /**  */
+    protected final static long SSH_OPEN_CONNECT_FAILED = 2;
+
+    /**  */
+    protected final static long SSH_OPEN_UNKNOWN_CHANNEL_TYPE = 3;
+
+    /**  */
+    protected final static long SSH_OPEN_RESOURCE_SHORTAGE = 4;
+    private String additional;
+    private String languageTag;
+    private long reasonCode;
+    private long recipientChannel;
+
+    /**
+     * Creates a new SshMsgChannelOpenFailure object.
+     *
+     * @param recipientChannel
+     * @param reasonCode
+     * @param additional
+     * @param languageTag
+     */
+    public SshMsgChannelOpenFailure(long recipientChannel, long reasonCode,
+        String additional, String languageTag) {
+        super(SSH_MSG_CHANNEL_OPEN_FAILURE);
+        this.recipientChannel = recipientChannel;
+        this.reasonCode = reasonCode;
+        this.additional = additional;
+        this.languageTag = languageTag;
+    }
+
+    /**
+     * Creates a new SshMsgChannelOpenFailure object.
+     */
+    public SshMsgChannelOpenFailure() {
+        super(SSH_MSG_CHANNEL_OPEN_FAILURE);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getAdditionalText() {
+        return additional;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getLanguageTag() {
+        return languageTag;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_CHANNEL_OPEN_FAILURE";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getReasonCode() {
+        return reasonCode;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getRecipientChannel() {
+        return recipientChannel;
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+        try {
+            baw.writeInt(recipientChannel);
+            baw.writeInt(reasonCode);
+            baw.writeString(additional);
+            baw.writeString(languageTag);
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Invalid message data");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+        try {
+            recipientChannel = bar.readInt();
+            reasonCode = bar.readInt();
+            additional = bar.readString();
+            languageTag = bar.readString();
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Invalid message data");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/connection/SshMsgChannelRequest.java b/src/com/sshtools/j2ssh/connection/SshMsgChannelRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..6587944d8fba40f62ae200c20624f0c7741a9a26
--- /dev/null
+++ b/src/com/sshtools/j2ssh/connection/SshMsgChannelRequest.java
@@ -0,0 +1,163 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.connection;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.SshMessage;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.20 $
+ */
+public class SshMsgChannelRequest extends SshMessage {
+    /**  */
+    protected final static int SSH_MSG_CHANNEL_REQUEST = 98;
+    private String requestType;
+    private byte[] channelData;
+    private boolean wantReply;
+    private long recipientChannel;
+
+    /**
+     * Creates a new SshMsgChannelRequest object.
+     *
+     * @param recipientChannel
+     * @param requestType
+     * @param wantReply
+     * @param channelData
+     */
+    public SshMsgChannelRequest(long recipientChannel, String requestType,
+        boolean wantReply, byte[] channelData) {
+        super(SSH_MSG_CHANNEL_REQUEST);
+        this.recipientChannel = recipientChannel;
+        this.requestType = requestType;
+        this.wantReply = wantReply;
+        this.channelData = channelData;
+    }
+
+    /**
+     * Creates a new SshMsgChannelRequest object.
+     */
+    public SshMsgChannelRequest() {
+        super(SSH_MSG_CHANNEL_REQUEST);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getChannelData() {
+        return channelData;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_CHANNEL_REQUEST";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getRecipientChannel() {
+        return recipientChannel;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getRequestType() {
+        return requestType;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean getWantReply() {
+        return wantReply;
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+        try {
+            baw.writeInt(recipientChannel);
+            baw.writeString(requestType);
+            baw.write((wantReply ? 1 : 0));
+
+            if (channelData != null) {
+                baw.write(channelData);
+            }
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Invalid message data");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+        try {
+            recipientChannel = bar.readInt();
+            requestType = bar.readString();
+            wantReply = ((bar.read() == 0) ? false : true);
+
+            if (bar.available() > 0) {
+                channelData = new byte[bar.available()];
+                bar.read(channelData);
+            }
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Invalid message data");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/connection/SshMsgChannelSuccess.java b/src/com/sshtools/j2ssh/connection/SshMsgChannelSuccess.java
new file mode 100644
index 0000000000000000000000000000000000000000..98d9408c543048959b588011390e9ef804377499
--- /dev/null
+++ b/src/com/sshtools/j2ssh/connection/SshMsgChannelSuccess.java
@@ -0,0 +1,113 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.connection;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.SshMessage;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.20 $
+ */
+public class SshMsgChannelSuccess extends SshMessage {
+    /**  */
+    protected final static int SSH_MSG_CHANNEL_SUCCESS = 99;
+    private long channelId;
+
+    /**
+     * Creates a new SshMsgChannelSuccess object.
+     *
+     * @param recipientChannelId
+     */
+    public SshMsgChannelSuccess(long recipientChannelId) {
+        super(SSH_MSG_CHANNEL_SUCCESS);
+        channelId = recipientChannelId;
+    }
+
+    /**
+     * Creates a new SshMsgChannelSuccess object.
+     */
+    public SshMsgChannelSuccess() {
+        super(SSH_MSG_CHANNEL_SUCCESS);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getChannelId() {
+        return channelId;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_CHANNEL_SUCCESS";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+        try {
+            baw.writeInt(channelId);
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Invalid message data");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+        try {
+            channelId = bar.readInt();
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Invalid message data");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/connection/SshMsgChannelWindowAdjust.java b/src/com/sshtools/j2ssh/connection/SshMsgChannelWindowAdjust.java
new file mode 100644
index 0000000000000000000000000000000000000000..0b308a67ccda3076f5233e9c4d2047a4cce97ffb
--- /dev/null
+++ b/src/com/sshtools/j2ssh/connection/SshMsgChannelWindowAdjust.java
@@ -0,0 +1,127 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.connection;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.SshMessage;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.20 $
+ */
+public class SshMsgChannelWindowAdjust extends SshMessage {
+    /**  */
+    protected final static int SSH_MSG_CHANNEL_WINDOW_ADJUST = 93;
+    private long bytesToAdd;
+    private long recipientChannel;
+
+    /**
+     * Creates a new SshMsgChannelWindowAdjust object.
+     *
+     * @param recipientChannel
+     * @param bytesToAdd
+     */
+    public SshMsgChannelWindowAdjust(long recipientChannel, long bytesToAdd) {
+        super(SSH_MSG_CHANNEL_WINDOW_ADJUST);
+        this.recipientChannel = recipientChannel;
+        this.bytesToAdd = bytesToAdd;
+    }
+
+    /**
+     * Creates a new SshMsgChannelWindowAdjust object.
+     */
+    public SshMsgChannelWindowAdjust() {
+        super(SSH_MSG_CHANNEL_WINDOW_ADJUST);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getBytesToAdd() {
+        return bytesToAdd;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_CHANNEL_WINDOW_ADJUST";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getRecipientChannel() {
+        return recipientChannel;
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+        try {
+            baw.writeInt(recipientChannel);
+            baw.writeInt(bytesToAdd);
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Invalid message data");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+        try {
+            recipientChannel = bar.readInt();
+            bytesToAdd = bar.readInt();
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Invalid message data");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/connection/SshMsgGlobalRequest.java b/src/com/sshtools/j2ssh/connection/SshMsgGlobalRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a60a71d593712108710d5ebd49e9680544f8b22f
--- /dev/null
+++ b/src/com/sshtools/j2ssh/connection/SshMsgGlobalRequest.java
@@ -0,0 +1,149 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.connection;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.SshMessage;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.20 $
+ */
+public class SshMsgGlobalRequest extends SshMessage {
+    /**  */
+    protected final static int SSH_MSG_GLOBAL_REQUEST = 80;
+    private String requestName;
+    private byte[] requestData;
+    private boolean wantReply;
+
+    /**
+     * Creates a new SshMsgGlobalRequest object.
+     *
+     * @param requestName
+     * @param wantReply
+     * @param requestData
+     */
+    public SshMsgGlobalRequest(String requestName, boolean wantReply,
+        byte[] requestData) {
+        super(SSH_MSG_GLOBAL_REQUEST);
+        this.requestName = requestName;
+        this.wantReply = wantReply;
+        this.requestData = requestData;
+    }
+
+    /**
+     * Creates a new SshMsgGlobalRequest object.
+     */
+    public SshMsgGlobalRequest() {
+        super(SSH_MSG_GLOBAL_REQUEST);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_GLOBAL_REQUEST";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getRequestData() {
+        return requestData;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getRequestName() {
+        return requestName;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean getWantReply() {
+        return wantReply;
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+        try {
+            baw.writeString(requestName);
+            baw.write((wantReply ? 1 : 0));
+
+            if (requestData != null) {
+                baw.write(requestData);
+            }
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Invalid message data");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+        try {
+            requestName = bar.readString();
+            wantReply = ((bar.read() == 0) ? false : true);
+
+            if (bar.available() > 0) {
+                requestData = new byte[bar.available()];
+                bar.read(requestData);
+            }
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Invalid message data");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/connection/SshMsgRequestFailure.java b/src/com/sshtools/j2ssh/connection/SshMsgRequestFailure.java
new file mode 100644
index 0000000000000000000000000000000000000000..6991df315a7e869f089590229d09f30ad9dee61f
--- /dev/null
+++ b/src/com/sshtools/j2ssh/connection/SshMsgRequestFailure.java
@@ -0,0 +1,81 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.connection;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.SshMessage;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.20 $
+ */
+public class SshMsgRequestFailure extends SshMessage {
+    /**  */
+    public final static int SSH_MSG_REQUEST_FAILURE = 82;
+
+    /**
+     * Creates a new SshMsgRequestFailure object.
+     */
+    public SshMsgRequestFailure() {
+        super(SSH_MSG_REQUEST_FAILURE);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_REQUEST_FAILURE";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+    }
+}
diff --git a/src/com/sshtools/j2ssh/connection/SshMsgRequestSuccess.java b/src/com/sshtools/j2ssh/connection/SshMsgRequestSuccess.java
new file mode 100644
index 0000000000000000000000000000000000000000..f49eef169e487ab8d1c868b1dca6c06f64b7bb38
--- /dev/null
+++ b/src/com/sshtools/j2ssh/connection/SshMsgRequestSuccess.java
@@ -0,0 +1,118 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.connection;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.SshMessage;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.20 $
+ */
+public class SshMsgRequestSuccess extends SshMessage {
+    /**  */
+    protected final static int SSH_MSG_REQUEST_SUCCESS = 81;
+    private byte[] requestData;
+
+    /**
+     * Creates a new SshMsgRequestSuccess object.
+     *
+     * @param requestData
+     */
+    public SshMsgRequestSuccess(byte[] requestData) {
+        super(SSH_MSG_REQUEST_SUCCESS);
+        this.requestData = requestData;
+    }
+
+    /**
+     * Creates a new SshMsgRequestSuccess object.
+     */
+    public SshMsgRequestSuccess() {
+        super(SSH_MSG_REQUEST_SUCCESS);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_REQUEST_SUCCESS";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getRequestData() {
+        return requestData;
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+        try {
+            if (requestData != null) {
+                baw.write(requestData);
+            }
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Invalid message data");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+        try {
+            if (bar.available() > 0) {
+                requestData = new byte[bar.available()];
+                bar.read(requestData);
+            }
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Invalid message data");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/forwarding/ForwardingBindingChannel.java b/src/com/sshtools/j2ssh/forwarding/ForwardingBindingChannel.java
new file mode 100644
index 0000000000000000000000000000000000000000..170f385dd2aaf6ed3630eae49f66d1f800285f5c
--- /dev/null
+++ b/src/com/sshtools/j2ssh/forwarding/ForwardingBindingChannel.java
@@ -0,0 +1,184 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.forwarding;
+
+import com.sshtools.j2ssh.connection.BindingChannel;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+//import java.net.InetSocketAddress;
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class ForwardingBindingChannel extends BindingChannel
+    implements ForwardingChannel {
+    private static Log log = LogFactory.getLog(ForwardingSocketChannel.class);
+    private ForwardingChannelImpl channel;
+
+    /**
+     * Creates a new ForwardingBindingChannel object.
+     *
+     * @param forwardType
+     * @param hostToConnectOrBind
+     * @param portToConnectOrBind
+     * @param originatingHost
+     * @param originatingPort
+     *
+     * @throws ForwardingConfigurationException
+     */
+    public ForwardingBindingChannel(String forwardType, String name,
+        
+    /*ForwardingConfiguration config,*/
+    String hostToConnectOrBind, int portToConnectOrBind,
+        String originatingHost, int originatingPort)
+        throws ForwardingConfigurationException {
+        if (!forwardType.equals(LOCAL_FORWARDING_CHANNEL) &&
+                !forwardType.equals(REMOTE_FORWARDING_CHANNEL) &&
+                !forwardType.equals(X11_FORWARDING_CHANNEL)) {
+            throw new ForwardingConfigurationException(
+                "The forwarding type is invalid");
+        }
+
+        channel = new ForwardingChannelImpl(forwardType, name,
+                hostToConnectOrBind, portToConnectOrBind, originatingHost,
+                originatingPort);
+    }
+
+    public String getName() {
+        return channel.getName();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getChannelOpenData() {
+        return channel.getChannelOpenData();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getChannelConfirmationData() {
+        return channel.getChannelConfirmationData();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getChannelType() {
+        return channel.getChannelType();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected int getMinimumWindowSpace() {
+        return 32768;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected int getMaximumWindowSpace() {
+        return 131072;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected int getMaximumPacketSize() {
+        return 32768;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getOriginatingHost() {
+        return channel.getOriginatingHost();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getOriginatingPort() {
+        return channel.getOriginatingPort();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getHostToConnectOrBind() {
+        return channel.getHostToConnectOrBind();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getPortToConnectOrBind() {
+        return channel.getPortToConnectOrBind();
+    }
+
+    /**
+     *
+     *
+     * @param request
+     * @param wantReply
+     * @param requestData
+     *
+     * @throws IOException
+     */
+    protected void onChannelRequest(String request, boolean wantReply,
+        byte[] requestData) throws IOException {
+        connection.sendChannelRequestFailure(this);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/forwarding/ForwardingChannel.java b/src/com/sshtools/j2ssh/forwarding/ForwardingChannel.java
new file mode 100644
index 0000000000000000000000000000000000000000..29b98a9ec12f98a91bf5b47cdf3669e9007af56d
--- /dev/null
+++ b/src/com/sshtools/j2ssh/forwarding/ForwardingChannel.java
@@ -0,0 +1,81 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.forwarding;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.42 $
+ */
+public interface ForwardingChannel {
+    /**  */
+    public final static String X11_FORWARDING_CHANNEL = "x11";
+
+    /**  */
+    public final static String LOCAL_FORWARDING_CHANNEL = "direct-tcpip";
+
+    /**  */
+    public final static String REMOTE_FORWARDING_CHANNEL = "forwarded-tcpip";
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getChannelType();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getOriginatingHost();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getOriginatingPort();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getHostToConnectOrBind();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getPortToConnectOrBind();
+
+    public String getName();
+}
diff --git a/src/com/sshtools/j2ssh/forwarding/ForwardingChannelImpl.java b/src/com/sshtools/j2ssh/forwarding/ForwardingChannelImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..cb390ce9f1bfd7ea3430893422ccf65766746695
--- /dev/null
+++ b/src/com/sshtools/j2ssh/forwarding/ForwardingChannelImpl.java
@@ -0,0 +1,158 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.forwarding;
+
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.11 $
+ */
+public class ForwardingChannelImpl implements ForwardingChannel {
+    private static Log log = LogFactory.getLog(ForwardingChannelImpl.class);
+    private String forwardType;
+    private String originatingHost;
+    private int originatingPort;
+    private String hostToConnectOrBind;
+    private int portToConnectOrBind;
+    private String name;
+
+    /**
+     * Creates a new ForwardingChannelImpl object.
+     *
+     * @param forwardType
+     * @param hostToConnectOrBind
+     * @param portToConnectOrBind
+     * @param originatingHost
+     * @param originatingPort
+     *
+     * @throws ForwardingConfigurationException
+     */
+    public ForwardingChannelImpl(String forwardType, String name, /*ForwardingConfiguration config,*/
+        String hostToConnectOrBind, int portToConnectOrBind,
+        String originatingHost, int originatingPort)
+        throws ForwardingConfigurationException {
+        if (!forwardType.equals(LOCAL_FORWARDING_CHANNEL) &&
+                !forwardType.equals(REMOTE_FORWARDING_CHANNEL) &&
+                !forwardType.equals(X11_FORWARDING_CHANNEL)) {
+            throw new ForwardingConfigurationException(
+                "The forwarding type is invalid");
+        }
+
+        //this.config = config;
+        this.forwardType = forwardType;
+        this.hostToConnectOrBind = hostToConnectOrBind;
+        this.portToConnectOrBind = portToConnectOrBind;
+        this.originatingHost = originatingHost;
+        this.originatingPort = originatingPort;
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getHostToConnectOrBind() {
+        return hostToConnectOrBind;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getPortToConnectOrBind() {
+        return portToConnectOrBind;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getChannelOpenData() {
+        try {
+            ByteArrayWriter baw = new ByteArrayWriter();
+            baw.writeString(hostToConnectOrBind);
+            baw.writeInt(portToConnectOrBind);
+            baw.writeString(originatingHost);
+            baw.writeInt(originatingPort);
+
+            return baw.toByteArray();
+        } catch (IOException ioe) {
+            return null;
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getChannelConfirmationData() {
+        return null;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getChannelType() {
+        return forwardType;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getOriginatingHost() {
+        return originatingHost;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getOriginatingPort() {
+        return originatingPort;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/forwarding/ForwardingClient.java b/src/com/sshtools/j2ssh/forwarding/ForwardingClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..e396477b077d97949df3fcca12ce515ac704ab92
--- /dev/null
+++ b/src/com/sshtools/j2ssh/forwarding/ForwardingClient.java
@@ -0,0 +1,830 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.forwarding;
+
+import com.sshtools.j2ssh.configuration.SshConnectionProperties;
+import com.sshtools.j2ssh.connection.Channel;
+import com.sshtools.j2ssh.connection.ChannelFactory;
+import com.sshtools.j2ssh.connection.ConnectionProtocol;
+import com.sshtools.j2ssh.connection.InvalidChannelException;
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.util.StartStopState;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+
+import java.net.Socket;
+import java.net.SocketPermission;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.37 $
+ */
+public class ForwardingClient implements ChannelFactory {
+    private static Log log = LogFactory.getLog(ForwardingClient.class);
+
+    /**  */
+    public final static String REMOTE_FORWARD_REQUEST = "tcpip-forward";
+
+    /**  */
+    public final static String REMOTE_FORWARD_CANCEL_REQUEST = "cancel-tcpip-forward";
+    private ConnectionProtocol connection;
+    private List channelTypes = new Vector();
+    private Map localForwardings = new HashMap();
+    private Map remoteForwardings = new HashMap();
+    private XDisplay xDisplay;
+    private ForwardingConfiguration x11ForwardingConfiguration;
+
+    /**
+     * Creates a new ForwardingClient object.
+     *
+     * @param connection
+     *
+     * @throws IOException
+     */
+    public ForwardingClient(ConnectionProtocol connection)
+        throws IOException {
+        this.connection = connection;
+
+        //channelTypes.add(ForwardingSocketChannel.REMOTE_FORWARDING_CHANNEL);
+        connection.addChannelFactory(ForwardingSocketChannel.REMOTE_FORWARDING_CHANNEL,
+            this);
+        connection.addChannelFactory(ForwardingSocketChannel.X11_FORWARDING_CHANNEL,
+            this);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public List getChannelType() {
+        return channelTypes;
+    }
+
+    /**
+     *
+     *
+     * @param localDisplay
+     */
+    public void enableX11Forwarding(XDisplay localDisplay) {
+        xDisplay = localDisplay;
+        x11ForwardingConfiguration = new ForwardingConfiguration("x11", "", 0,
+                xDisplay.getHost(), xDisplay.getPort());
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public ForwardingConfiguration getX11ForwardingConfiguration() {
+        return x11ForwardingConfiguration;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean hasActiveConfigurations() {
+        // First check the size
+        if ((localForwardings.size() == 0) && (remoteForwardings.size() == 0)) {
+            return false;
+        }
+
+        Iterator it = localForwardings.values().iterator();
+
+        while (it.hasNext()) {
+            if (((ForwardingConfiguration) it.next()).getState().getValue() == StartStopState.STARTED) {
+                return true;
+            }
+        }
+
+        it = remoteForwardings.values().iterator();
+
+        while (it.hasNext()) {
+            if (((ForwardingConfiguration) it.next()).getState().getValue() == StartStopState.STARTED) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    public void synchronizeConfiguration(SshConnectionProperties properties) {
+        ForwardingConfiguration fwd = null;
+
+        if (properties.getLocalForwardings().size() > 0) {
+            for (Iterator it = properties.getLocalForwardings().values()
+                                         .iterator(); it.hasNext();) {
+                try {
+                    fwd = (ForwardingConfiguration) it.next();
+                    fwd = addLocalForwarding(fwd);
+
+                    if (properties.getForwardingAutoStartMode()) {
+                        startLocalForwarding(fwd.getName());
+                    }
+                } catch (Throwable ex) {
+                    log.warn((("Failed to start local forwarding " + fwd) != null)
+                        ? fwd.getName() : "", ex);
+                }
+            }
+        }
+
+        if (properties.getRemoteForwardings().size() > 0) {
+            for (Iterator it = properties.getRemoteForwardings().values()
+                                         .iterator(); it.hasNext();) {
+                try {
+                    fwd = (ForwardingConfiguration) it.next();
+                    addRemoteForwarding(fwd);
+
+                    if (properties.getForwardingAutoStartMode()) {
+                        startRemoteForwarding(fwd.getName());
+                    }
+                } catch (Throwable ex) {
+                    log.warn((("Failed to start remote forwarding " + fwd) != null)
+                        ? fwd.getName() : "", ex);
+                }
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean hasActiveForwardings() {
+        // First check the size
+        if ((localForwardings.size() == 0) && (remoteForwardings.size() == 0)) {
+            return false;
+        }
+
+        Iterator it = localForwardings.values().iterator();
+
+        while (it.hasNext()) {
+            if (((ForwardingConfiguration) it.next()).getActiveForwardingSocketChannels()
+                     .size() > 0) {
+                return true;
+            }
+        }
+
+        it = remoteForwardings.values().iterator();
+
+        while (it.hasNext()) {
+            if (((ForwardingConfiguration) it.next()).getActiveForwardingSocketChannels()
+                     .size() > 0) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     *
+     *
+     * @param addressToBind
+     * @param portToBind
+     *
+     * @return
+     *
+     * @throws ForwardingConfigurationException
+     */
+    public ForwardingConfiguration getLocalForwardingByAddress(
+        String addressToBind, int portToBind)
+        throws ForwardingConfigurationException {
+        Iterator it = localForwardings.values().iterator();
+        ForwardingConfiguration config;
+
+        while (it.hasNext()) {
+            config = (ForwardingConfiguration) it.next();
+
+            if (config.getAddressToBind().equals(addressToBind) &&
+                    (config.getPortToBind() == portToBind)) {
+                return config;
+            }
+        }
+
+        throw new ForwardingConfigurationException(
+            "The configuration does not exist");
+    }
+
+    /**
+     *
+     *
+     * @param name
+     *
+     * @return
+     *
+     * @throws ForwardingConfigurationException
+     */
+    public ForwardingConfiguration getLocalForwardingByName(String name)
+        throws ForwardingConfigurationException {
+        if (!localForwardings.containsKey(name)) {
+            throw new ForwardingConfigurationException(
+                "The configuraiton does not exist!");
+        }
+
+        return (ForwardingConfiguration) localForwardings.get(name);
+    }
+
+    /**
+     *
+     *
+     * @param name
+     *
+     * @return
+     *
+     * @throws ForwardingConfigurationException
+     */
+    public ForwardingConfiguration getRemoteForwardingByName(String name)
+        throws ForwardingConfigurationException {
+        if (!remoteForwardings.containsKey(name)) {
+            throw new ForwardingConfigurationException(
+                "The configuraiton does not exist!");
+        }
+
+        return (ForwardingConfiguration) remoteForwardings.get(name);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public Map getLocalForwardings() {
+        return localForwardings;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public Map getRemoteForwardings() {
+        return remoteForwardings;
+    }
+
+    /**
+     *
+     *
+     * @param addressToBind
+     * @param portToBind
+     *
+     * @return
+     *
+     * @throws ForwardingConfigurationException
+     */
+    public ForwardingConfiguration getRemoteForwardingByAddress(
+        String addressToBind, int portToBind)
+        throws ForwardingConfigurationException {
+        Iterator it = remoteForwardings.values().iterator();
+        ForwardingConfiguration config;
+
+        while (it.hasNext()) {
+            config = (ForwardingConfiguration) it.next();
+
+            if (config.getAddressToBind().equals(addressToBind) &&
+                    (config.getPortToBind() == portToBind)) {
+                return config;
+            }
+        }
+
+        throw new ForwardingConfigurationException(
+            "The configuration does not exist");
+    }
+
+    /**
+     *
+     *
+     * @param name
+     *
+     * @throws ForwardingConfigurationException
+     */
+    public void removeLocalForwarding(String name)
+        throws ForwardingConfigurationException {
+        if (!localForwardings.containsKey(name)) {
+            throw new ForwardingConfigurationException(
+                "The name is not a valid forwarding configuration");
+        }
+
+        ForwardingListener listener = (ForwardingListener) localForwardings.get(name);
+
+        if (listener.isRunning()) {
+            stopLocalForwarding(name);
+        }
+
+        localForwardings.remove(name);
+    }
+
+    /**
+     *
+     *
+     * @param name
+     *
+     * @throws IOException
+     * @throws ForwardingConfigurationException
+     */
+    public void removeRemoteForwarding(String name)
+        throws IOException, ForwardingConfigurationException {
+        if (!remoteForwardings.containsKey(name)) {
+            throw new ForwardingConfigurationException(
+                "The name is not a valid forwarding configuration");
+        }
+
+        ForwardingListener listener = (ForwardingListener) remoteForwardings.get(name);
+
+        if (listener.isRunning()) {
+            stopRemoteForwarding(name);
+        }
+
+        remoteForwardings.remove(name);
+    }
+
+    /**
+     *
+     *
+     * @param uniqueName
+     * @param addressToBind
+     * @param portToBind
+     * @param hostToConnect
+     * @param portToConnect
+     *
+     * @return
+     *
+     * @throws ForwardingConfigurationException
+     */
+    public ForwardingConfiguration addLocalForwarding(String uniqueName,
+        String addressToBind, int portToBind, String hostToConnect,
+        int portToConnect) throws ForwardingConfigurationException {
+        // Check that the name does not exist
+        if (localForwardings.containsKey(uniqueName)) {
+            throw new ForwardingConfigurationException(
+                "The configuration name already exists!");
+        }
+
+        // Check that the address to bind and port are not already being used
+        Iterator it = localForwardings.values().iterator();
+        ForwardingConfiguration config;
+
+        while (it.hasNext()) {
+            config = (ForwardingConfiguration) it.next();
+
+            if (config.getAddressToBind().equals(addressToBind) &&
+                    (config.getPortToBind() == portToBind)) {
+                throw new ForwardingConfigurationException(
+                    "The address and port are already in use");
+            }
+        }
+
+        // Check the security mananger
+        SecurityManager manager = System.getSecurityManager();
+
+        if (manager != null) {
+            try {
+                manager.checkPermission(new SocketPermission(addressToBind +
+                        ":" + String.valueOf(portToBind), "accept,listen"));
+            } catch (SecurityException e) {
+                throw new ForwardingConfigurationException(
+                    "The security manager has denied listen permision on " +
+                    addressToBind + ":" + String.valueOf(portToBind));
+            }
+        }
+
+        // Create the configuration object
+        ForwardingConfiguration cf = new ClientForwardingListener(uniqueName,
+                connection, addressToBind, portToBind, hostToConnect,
+                portToConnect);
+        localForwardings.put(uniqueName, cf);
+
+        return cf;
+    }
+
+    /**
+     *
+     *
+     * @param fwd
+     *
+     * @return
+     *
+     * @throws ForwardingConfigurationException
+     */
+    public ForwardingConfiguration addLocalForwarding(
+        ForwardingConfiguration fwd) throws ForwardingConfigurationException {
+        return addLocalForwarding(fwd.getName(), fwd.getAddressToBind(),
+            fwd.getPortToBind(), fwd.getHostToConnect(), fwd.getPortToConnect());
+
+        /*     // Check that the name does not exist
+              if (localForwardings.containsKey(fwd.getName())) {
+         throw new ForwardingConfigurationException(
+             "The configuration name already exists!");
+              }
+              // Check that the address to bind and port are not already being used
+              Iterator it = localForwardings.values().iterator();
+              ForwardingConfiguration config;
+              while (it.hasNext()) {
+         config = (ForwardingConfiguration) it.next();
+         if (config.getAddressToBind().equals(fwd.getAddressToBind())
+                 && (config.getPortToBind() == fwd.getPortToBind())) {
+             throw new ForwardingConfigurationException(
+                 "The address and port are already in use");
+         }
+              }
+              // Check the security mananger
+              SecurityManager manager = System.getSecurityManager();
+              if (manager != null) {
+         try {
+             manager.checkPermission(new SocketPermission(fwd
+                     .getAddressToBind() + ":"
+                     + String.valueOf(fwd.getPortToBind()), "accept,listen"));
+         } catch (SecurityException e) {
+             throw new ForwardingConfigurationException(
+                 "The security manager has denied listen permision on "
+                 + fwd.getAddressToBind() + ":"
+                 + String.valueOf(fwd.getPortToBind()));
+         }
+              }
+              // Create the configuration object
+              localForwardings.put(fwd.getName(),
+         new ClientForwardingListener(fwd.getName(), connection,
+             fwd.getAddressToBind(), fwd.getPortToBind(),
+             fwd.getHostToConnect(), fwd.getPortToConnect()));*/
+    }
+
+    /**
+     *
+     *
+     * @param uniqueName
+     * @param addressToBind
+     * @param portToBind
+     * @param hostToConnect
+     * @param portToConnect
+     *
+     * @throws ForwardingConfigurationException
+     */
+    public void addRemoteForwarding(String uniqueName, String addressToBind,
+        int portToBind, String hostToConnect, int portToConnect)
+        throws ForwardingConfigurationException {
+        // Check that the name does not exist
+        if (remoteForwardings.containsKey(uniqueName)) {
+            throw new ForwardingConfigurationException(
+                "The remote forwaring configuration name already exists!");
+        }
+
+        // Check that the address to bind and port are not already being used
+        Iterator it = remoteForwardings.values().iterator();
+        ForwardingConfiguration config;
+
+        while (it.hasNext()) {
+            config = (ForwardingConfiguration) it.next();
+
+            if (config.getAddressToBind().equals(addressToBind) &&
+                    (config.getPortToBind() == portToBind)) {
+                throw new ForwardingConfigurationException(
+                    "The remote forwarding address and port are already in use");
+            }
+        }
+
+        // Check the security mananger
+        SecurityManager manager = System.getSecurityManager();
+
+        if (manager != null) {
+            try {
+                manager.checkPermission(new SocketPermission(hostToConnect +
+                        ":" + String.valueOf(portToConnect), "connect"));
+            } catch (SecurityException e) {
+                throw new ForwardingConfigurationException(
+                    "The security manager has denied connect permision on " +
+                    hostToConnect + ":" + String.valueOf(portToConnect));
+            }
+        }
+
+        // Create the configuration object
+        remoteForwardings.put(uniqueName,
+            new ForwardingConfiguration(uniqueName, addressToBind, portToBind,
+                hostToConnect, portToConnect));
+    }
+
+    /**
+     *
+     *
+     * @param fwd
+     *
+     * @throws ForwardingConfigurationException
+     */
+    public void addRemoteForwarding(ForwardingConfiguration fwd)
+        throws ForwardingConfigurationException {
+        // Check that the name does not exist
+        if (remoteForwardings.containsKey(fwd.getName())) {
+            throw new ForwardingConfigurationException(
+                "The remote forwaring configuration name already exists!");
+        }
+
+        // Check that the address to bind and port are not already being used
+        Iterator it = remoteForwardings.values().iterator();
+        ForwardingConfiguration config;
+
+        while (it.hasNext()) {
+            config = (ForwardingConfiguration) it.next();
+
+            if (config.getAddressToBind().equals(fwd.getAddressToBind()) &&
+                    (config.getPortToBind() == fwd.getPortToBind())) {
+                throw new ForwardingConfigurationException(
+                    "The remote forwarding address and port are already in use");
+            }
+        }
+
+        // Check the security mananger
+        SecurityManager manager = System.getSecurityManager();
+
+        if (manager != null) {
+            try {
+                manager.checkPermission(new SocketPermission(fwd.getHostToConnect() +
+                        ":" + String.valueOf(fwd.getPortToConnect()), "connect"));
+            } catch (SecurityException e) {
+                throw new ForwardingConfigurationException(
+                    "The security manager has denied connect permision on " +
+                    fwd.getHostToConnect() + ":" +
+                    String.valueOf(fwd.getPortToConnect()));
+            }
+        }
+
+        // Create the configuration object
+        remoteForwardings.put(fwd.getName(), fwd);
+    }
+
+    /**
+     *
+     *
+     * @param channelType
+     * @param requestData
+     *
+     * @return
+     *
+     * @throws InvalidChannelException
+     */
+    public Channel createChannel(String channelType, byte[] requestData)
+        throws InvalidChannelException {
+        if (channelType.equals(ForwardingSocketChannel.X11_FORWARDING_CHANNEL)) {
+            if (xDisplay == null) {
+                throw new InvalidChannelException(
+                    "Local display has not been set for X11 forwarding.");
+            }
+
+            try {
+                ByteArrayReader bar = new ByteArrayReader(requestData);
+                String originatingHost = bar.readString();
+                int originatingPort = (int) bar.readInt();
+                log.debug("Creating socket to " +
+                    x11ForwardingConfiguration.getHostToConnect() + "/" +
+                    x11ForwardingConfiguration.getPortToConnect());
+
+                Socket socket = new Socket(x11ForwardingConfiguration.getHostToConnect(),
+                        x11ForwardingConfiguration.getPortToConnect());
+
+                // Create the channel adding it to the active channels
+                ForwardingSocketChannel channel = x11ForwardingConfiguration.createForwardingSocketChannel(channelType,
+                        x11ForwardingConfiguration.getHostToConnect(),
+                        x11ForwardingConfiguration.getPortToConnect(),
+                        originatingHost, originatingPort);
+                channel.bindSocket(socket);
+                channel.addEventListener(x11ForwardingConfiguration.monitor);
+
+                return channel;
+            } catch (IOException ioe) {
+                throw new InvalidChannelException(ioe.getMessage());
+            }
+        }
+
+        if (channelType.equals(
+                    ForwardingSocketChannel.REMOTE_FORWARDING_CHANNEL)) {
+            try {
+                ByteArrayReader bar = new ByteArrayReader(requestData);
+                String addressBound = bar.readString();
+                int portBound = (int) bar.readInt();
+                String originatingHost = bar.readString();
+                int originatingPort = (int) bar.readInt();
+                ForwardingConfiguration config = getRemoteForwardingByAddress(addressBound,
+                        portBound);
+                Socket socket = new Socket(config.getHostToConnect(),
+                        config.getPortToConnect());
+
+                /*Socket socket = new Socket();
+                 socket.connect(new InetSocketAddress(
+                     config.getHostToConnect(), config.getPortToConnect()));*/
+
+                // Create the channel adding it to the active channels
+                ForwardingSocketChannel channel = config.createForwardingSocketChannel(channelType,
+                        config.getHostToConnect(), config.getPortToConnect(),
+                        originatingHost, originatingPort);
+                channel.bindSocket(socket);
+                channel.addEventListener(config.monitor);
+
+                return channel;
+            } catch (ForwardingConfigurationException fce) {
+                throw new InvalidChannelException(
+                    "No valid forwarding configuration was available for the request address");
+            } catch (IOException ioe) {
+                throw new InvalidChannelException(ioe.getMessage());
+            }
+        }
+
+        throw new InvalidChannelException(
+            "The server can only request a remote forwarding channel or an" +
+            "X11 forwarding channel");
+    }
+
+    /**
+     *
+     *
+     * @param uniqueName
+     *
+     * @throws ForwardingConfigurationException
+     */
+    public void startLocalForwarding(String uniqueName)
+        throws ForwardingConfigurationException {
+        if (!localForwardings.containsKey(uniqueName)) {
+            throw new ForwardingConfigurationException(
+                "The name is not a valid forwarding configuration");
+        }
+
+        try {
+            ForwardingListener listener = (ForwardingListener) localForwardings.get(uniqueName);
+            listener.start();
+        } catch (IOException ex) {
+            throw new ForwardingConfigurationException(ex.getMessage());
+        }
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     * @throws ForwardingConfigurationException
+     */
+    public void startX11Forwarding()
+        throws IOException, ForwardingConfigurationException {
+        if (x11ForwardingConfiguration == null) {
+            throw new ForwardingConfigurationException(
+                "X11 forwarding hasn't been enabled.");
+        }
+
+        ByteArrayWriter baw = new ByteArrayWriter();
+        baw.writeString(x11ForwardingConfiguration.getAddressToBind());
+        baw.writeInt(x11ForwardingConfiguration.getPortToBind());
+        x11ForwardingConfiguration.getState().setValue(StartStopState.STARTED);
+
+        if (log.isDebugEnabled()) {
+            log.info("X11 forwarding started");
+            log.debug("Address to bind: " +
+                x11ForwardingConfiguration.getAddressToBind());
+            log.debug("Port to bind: " +
+                String.valueOf(x11ForwardingConfiguration.getPortToBind()));
+            log.debug("Host to connect: " +
+                x11ForwardingConfiguration.hostToConnect);
+            log.debug("Port to connect: " +
+                x11ForwardingConfiguration.portToConnect);
+        } else {
+            log.info("Request for X11 rejected.");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param name
+     *
+     * @throws IOException
+     * @throws ForwardingConfigurationException
+     */
+    public void startRemoteForwarding(String name)
+        throws IOException, ForwardingConfigurationException {
+        if (!remoteForwardings.containsKey(name)) {
+            throw new ForwardingConfigurationException(
+                "The name is not a valid forwarding configuration");
+        }
+
+        ForwardingConfiguration config = (ForwardingConfiguration) remoteForwardings.get(name);
+        ByteArrayWriter baw = new ByteArrayWriter();
+        baw.writeString(config.getAddressToBind());
+        baw.writeInt(config.getPortToBind());
+        connection.sendGlobalRequest(REMOTE_FORWARD_REQUEST, true,
+            baw.toByteArray());
+        remoteForwardings.put(name, config);
+        config.getState().setValue(StartStopState.STARTED);
+        log.info("Remote forwarding configuration '" + name + "' started");
+
+        if (log.isDebugEnabled()) {
+            log.debug("Address to bind: " + config.getAddressToBind());
+            log.debug("Port to bind: " +
+                String.valueOf(config.getPortToBind()));
+            log.debug("Host to connect: " + config.hostToConnect);
+            log.debug("Port to connect: " + config.portToConnect);
+        }
+    }
+
+    /**
+     *
+     *
+     * @param uniqueName
+     *
+     * @throws ForwardingConfigurationException
+     */
+    public void stopLocalForwarding(String uniqueName)
+        throws ForwardingConfigurationException {
+        if (!localForwardings.containsKey(uniqueName)) {
+            throw new ForwardingConfigurationException(
+                "The name is not a valid forwarding configuration");
+        }
+
+        ForwardingListener listener = (ForwardingListener) localForwardings.get(uniqueName);
+        listener.stop();
+        log.info("Local forwarding configuration " + uniqueName + "' stopped");
+    }
+
+    /**
+     *
+     *
+     * @param name
+     *
+     * @throws IOException
+     * @throws ForwardingConfigurationException
+     */
+    public void stopRemoteForwarding(String name)
+        throws IOException, ForwardingConfigurationException {
+        if (!remoteForwardings.containsKey(name)) {
+            throw new ForwardingConfigurationException(
+                "The remote forwarding configuration does not exist");
+        }
+
+        ForwardingConfiguration config = (ForwardingConfiguration) remoteForwardings.get(name);
+        ByteArrayWriter baw = new ByteArrayWriter();
+        baw.writeString(config.getAddressToBind());
+        baw.writeInt(config.getPortToBind());
+        connection.sendGlobalRequest(REMOTE_FORWARD_CANCEL_REQUEST, true,
+            baw.toByteArray());
+        config.getState().setValue(StartStopState.STOPPED);
+        log.info("Remote forwarding configuration '" + name + "' stopped");
+    }
+
+    public class ClientForwardingListener extends ForwardingListener {
+        public ClientForwardingListener(String name,
+            ConnectionProtocol connection, String addressToBind,
+            int portToBind, String hostToConnect, int portToConnect) {
+            super(name, connection, addressToBind, portToBind, hostToConnect,
+                portToConnect);
+        }
+
+        public ForwardingSocketChannel createChannel(String hostToConnect,
+            int portToConnect, Socket socket)
+            throws ForwardingConfigurationException {
+            return createForwardingSocketChannel(ForwardingSocketChannel.LOCAL_FORWARDING_CHANNEL,
+                hostToConnect, portToConnect,
+                
+            /*( (InetSocketAddress) socket.
+            getRemoteSocketAddress()).getAddress()
+            .getHostAddress()*/
+            socket.getInetAddress().getHostAddress(), 
+            /*( (InetSocketAddress) socket.
+            getRemoteSocketAddress()).getPort()*/
+            socket.getPort());
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/forwarding/ForwardingConfiguration.java b/src/com/sshtools/j2ssh/forwarding/ForwardingConfiguration.java
new file mode 100644
index 0000000000000000000000000000000000000000..9bc79f132e85e5bf7c35392e50d488e1cddea50d
--- /dev/null
+++ b/src/com/sshtools/j2ssh/forwarding/ForwardingConfiguration.java
@@ -0,0 +1,417 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.forwarding;
+
+import com.sshtools.j2ssh.connection.Channel;
+import com.sshtools.j2ssh.connection.ChannelEventListener;
+import com.sshtools.j2ssh.util.StartStopState;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+
+import java.util.List;
+import java.util.Vector;
+
+import javax.swing.event.EventListenerList;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.41 $
+ */
+public class ForwardingConfiguration {
+    private static Log log = LogFactory.getLog(ForwardingConfiguration.class);
+
+    /**  */
+    protected StartStopState state = new StartStopState(StartStopState.STOPPED);
+
+    /**  */
+    protected String addressToBind;
+
+    /**  */
+    protected String hostToConnect;
+
+    /**  */
+    protected String name;
+
+    /**  */
+    protected int portToBind;
+
+    /**  */
+    protected int portToConnect;
+
+    /**  */
+    protected ForwardingConfigurationMonitor monitor = new ForwardingConfigurationMonitor();
+
+    /**  */
+    protected EventListenerList listenerList = new EventListenerList();
+    private List activeForwardings = new Vector();
+
+    /**
+     * Creates a new ForwardingConfiguration object.
+     *
+     * @param name
+     * @param addressToBind
+     * @param portToBind
+     * @param hostToConnect
+     * @param portToConnect
+     */
+    public ForwardingConfiguration(String name, String addressToBind,
+        int portToBind, String hostToConnect, int portToConnect) {
+        this.addressToBind = addressToBind;
+        this.portToBind = portToBind;
+        this.name = name;
+        this.hostToConnect = hostToConnect;
+        this.portToConnect = portToConnect;
+    }
+
+    /**
+     * Creates a new ForwardingConfiguration object.
+     *
+     * @param addressToBind
+     * @param portToBind
+     */
+    public ForwardingConfiguration(String addressToBind, int portToBind) {
+        this(addressToBind + ":" + String.valueOf(portToBind), addressToBind,
+            portToBind, "[Specified by connecting computer]", -1);
+    }
+
+    /**
+     *
+     *
+     * @param l
+     */
+    public void addForwardingConfigurationListener(
+        ForwardingConfigurationListener l) {
+        listenerList.add(ForwardingConfigurationListener.class, l);
+    }
+
+    /**
+     *
+     *
+     * @param l
+     */
+    public void removeForwardingConfigurationListener(
+        ForwardingConfigurationListener l) {
+        listenerList.remove(ForwardingConfigurationListener.class, l);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public List getActiveForwardingSocketChannels() {
+        return activeForwardings;
+    }
+
+    public boolean isForwarding() {
+        return state.getValue() == StartStopState.STARTED;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getAddressToBind() {
+        return addressToBind;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getHostToConnect() {
+        return hostToConnect;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getPortToBind() {
+        return portToBind;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getPortToConnect() {
+        return portToConnect;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public StartStopState getState() {
+        return state;
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    public void start() throws IOException {
+        state.setValue(StartStopState.STARTED);
+    }
+
+    /**
+     *
+     */
+    public void stop() {
+        state.setValue(StartStopState.STOPPED);
+    }
+
+    /**
+     *
+     *
+     * @param type
+     * @param hostToConnect
+     * @param portToConnect
+     * @param originatingHost
+     * @param originatingPort
+     *
+     * @return
+     *
+     * @throws ForwardingConfigurationException
+     */
+    public ForwardingSocketChannel createForwardingSocketChannel(String type,
+        String hostToConnect, int portToConnect, String originatingHost,
+        int originatingPort) throws ForwardingConfigurationException {
+        if (state.getValue() == StartStopState.STOPPED) {
+            throw new ForwardingConfigurationException(
+                "The forwarding has been stopped");
+        }
+
+        if (!type.equals(ForwardingChannel.LOCAL_FORWARDING_CHANNEL) &&
+                !type.equals(ForwardingChannel.REMOTE_FORWARDING_CHANNEL) &&
+                !type.equals(ForwardingChannel.X11_FORWARDING_CHANNEL)) {
+            throw new ForwardingConfigurationException(
+                "The channel type must either be " +
+                "ForwardingSocketChannel.LOCAL_FORWARDING_CHANNEL_TYPE or " +
+                "ForwardingSocketChannel.REMOTE_FORWARDING_CHANNEL_TYPE");
+        }
+
+        ForwardingSocketChannel channel;
+
+        if (type.equals(ForwardingChannel.LOCAL_FORWARDING_CHANNEL)) {
+            channel = new ForwardingSocketChannel(type, name, hostToConnect,
+                    portToConnect, originatingHost, originatingPort);
+        } else {
+            channel = new ForwardingSocketChannel(type, name,
+                    getAddressToBind(), getPortToBind(), originatingHost,
+                    originatingPort);
+        }
+
+        channel.addEventListener(monitor);
+
+        return channel;
+    }
+
+    /**
+     *
+     *
+     * @param type
+     * @param hostToConnect
+     * @param portToConnect
+     * @param originatingHost
+     * @param originatingPort
+     *
+     * @return
+     *
+     * @throws ForwardingConfigurationException
+     */
+    public ForwardingIOChannel createForwardingIOChannel(String type,
+        String hostToConnect, int portToConnect, String originatingHost,
+        int originatingPort) throws ForwardingConfigurationException {
+        if (state.getValue() == StartStopState.STOPPED) {
+            throw new ForwardingConfigurationException(
+                "The forwarding has been stopped");
+        }
+
+        if (!type.equals(ForwardingChannel.LOCAL_FORWARDING_CHANNEL) &&
+                !type.equals(ForwardingChannel.REMOTE_FORWARDING_CHANNEL) &&
+                !type.equals(ForwardingChannel.X11_FORWARDING_CHANNEL)) {
+            throw new ForwardingConfigurationException(
+                "The channel type must either be " +
+                "ForwardingSocketChannel.LOCAL_FORWARDING_CHANNEL_TYPE or " +
+                "ForwardingSocketChannel.REMOTE_FORWARDING_CHANNEL_TYPE");
+        }
+
+        ForwardingIOChannel channel;
+
+        if (type.equals(ForwardingChannel.LOCAL_FORWARDING_CHANNEL)) {
+            channel = new ForwardingIOChannel(type, name, getHostToConnect(),
+                    getPortToConnect(), originatingHost, originatingPort);
+        } else {
+            channel = new ForwardingIOChannel(type, name, getAddressToBind(),
+                    getPortToBind(), originatingHost, originatingPort);
+        }
+
+        channel.addEventListener(monitor);
+
+        return channel;
+    }
+
+    /**
+     *
+     *
+     * @param type
+     * @param hostToConnect
+     * @param portToConnect
+     * @param originatingHost
+     * @param originatingPort
+     *
+     * @return
+     *
+     * @throws ForwardingConfigurationException
+     */
+    public ForwardingBindingChannel createForwardingBindingChannel(
+        String type, String hostToConnect, int portToConnect,
+        String originatingHost, int originatingPort)
+        throws ForwardingConfigurationException {
+        if (state.getValue() == StartStopState.STOPPED) {
+            throw new ForwardingConfigurationException(
+                "The forwarding has been stopped");
+        }
+
+        if (!type.equals(ForwardingChannel.LOCAL_FORWARDING_CHANNEL) &&
+                !type.equals(ForwardingChannel.REMOTE_FORWARDING_CHANNEL) &&
+                !type.equals(ForwardingChannel.X11_FORWARDING_CHANNEL)) {
+            throw new ForwardingConfigurationException(
+                "The channel type must either be " +
+                "ForwardingSocketChannel.LOCAL_FORWARDING_CHANNEL_TYPE or " +
+                "ForwardingSocketChannel.REMOTE_FORWARDING_CHANNEL_TYPE");
+        }
+
+        ForwardingBindingChannel channel;
+
+        if (type.equals(ForwardingChannel.LOCAL_FORWARDING_CHANNEL)) {
+            channel = new ForwardingBindingChannel(type, name,
+                    getHostToConnect(), getPortToConnect(), originatingHost,
+                    originatingPort);
+        } else {
+            channel = new ForwardingBindingChannel(type, name,
+                    getAddressToBind(), getPortToBind(), originatingHost,
+                    originatingPort);
+        }
+
+        channel.addEventListener(monitor);
+
+        return channel;
+    }
+
+    public class ForwardingConfigurationMonitor implements ChannelEventListener {
+        public void onChannelOpen(Channel channel) {
+            if (log.isDebugEnabled()) {
+                ForwardingChannel fch = (ForwardingChannel) channel;
+                log.debug("Opening forwarding channel from " +
+                    fch.getOriginatingHost() + ":" +
+                    String.valueOf(fch.getOriginatingPort()));
+            }
+
+            // Add channel to the active forwardings
+            activeForwardings.add(channel);
+
+            ForwardingConfigurationListener[] l = (ForwardingConfigurationListener[]) listenerList.getListeners(ForwardingConfigurationListener.class);
+
+            for (int i = (l.length - 1); i >= 0; i--) {
+                l[i].opened(ForwardingConfiguration.this,
+                    (ForwardingSocketChannel) channel);
+            }
+        }
+
+        public void onChannelEOF(Channel channel) {
+            // Close the OutputStream to force the channel to close
+
+            /* try {
+               //channel.getOutputStream().close();
+               //channel.getInputStream().close();
+               channel.close();
+             }
+             catch (IOException ex) {
+             }*/
+        }
+
+        public void onChannelClose(Channel channel) {
+            if (log.isDebugEnabled()) {
+                ForwardingChannel fch = (ForwardingChannel) channel;
+                log.debug("Closing forwarding channel from " +
+                    fch.getOriginatingHost() + ":" +
+                    String.valueOf(fch.getOriginatingPort()));
+            }
+
+            // Remove channel from the active forwardings
+            activeForwardings.remove(channel);
+
+            ForwardingConfigurationListener[] l = (ForwardingConfigurationListener[]) listenerList.getListeners(ForwardingConfigurationListener.class);
+
+            for (int i = (l.length - 1); i >= 0; i--) {
+                l[i].closed(ForwardingConfiguration.this,
+                    (ForwardingSocketChannel) channel);
+            }
+        }
+
+        public void onDataReceived(Channel channel, byte[] data) {
+            ForwardingConfigurationListener[] l = (ForwardingConfigurationListener[]) listenerList.getListeners(ForwardingConfigurationListener.class);
+
+            for (int i = (l.length - 1); i >= 0; i--) {
+                l[i].dataReceived(ForwardingConfiguration.this,
+                    (ForwardingSocketChannel) channel, data.length);
+            }
+        }
+
+        public void onDataSent(Channel channel, byte[] data) {
+            ForwardingConfigurationListener[] l = (ForwardingConfigurationListener[]) listenerList.getListeners(ForwardingConfigurationListener.class);
+
+            for (int i = (l.length - 1); i >= 0; i--) {
+                l[i].dataSent(ForwardingConfiguration.this,
+                    (ForwardingSocketChannel) channel, data.length);
+            }
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/forwarding/ForwardingConfigurationException.java b/src/com/sshtools/j2ssh/forwarding/ForwardingConfigurationException.java
new file mode 100644
index 0000000000000000000000000000000000000000..03c13c03b51ec767863d5c104c9950a1c02d3697
--- /dev/null
+++ b/src/com/sshtools/j2ssh/forwarding/ForwardingConfigurationException.java
@@ -0,0 +1,46 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.forwarding;
+
+import java.io.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class ForwardingConfigurationException extends IOException {
+    /**
+     * Creates a new ForwardingConfigurationException object.
+     *
+     * @param msg
+     */
+    public ForwardingConfigurationException(String msg) {
+        super(msg);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/forwarding/ForwardingConfigurationListener.java b/src/com/sshtools/j2ssh/forwarding/ForwardingConfigurationListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..3c520d83e712e39ddf94f04e52530aa914bd7eb0
--- /dev/null
+++ b/src/com/sshtools/j2ssh/forwarding/ForwardingConfigurationListener.java
@@ -0,0 +1,71 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.forwarding;
+
+import java.util.EventListener;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public interface ForwardingConfigurationListener extends EventListener {
+    /**
+     *
+     *
+     * @param channel
+     */
+    public void opened(ForwardingConfiguration forward,
+        ForwardingChannel channel);
+
+    /**
+     *
+     *
+     * @param channel
+     */
+    public void closed(ForwardingConfiguration forward,
+        ForwardingChannel channel);
+
+    /**
+     *
+     *
+     * @param channel
+     * @param bytes
+     */
+    public void dataReceived(ForwardingConfiguration forward,
+        ForwardingChannel channel, int bytes);
+
+    /**
+     *
+     *
+     * @param channel
+     * @param bytes
+     */
+    public void dataSent(ForwardingConfiguration forward,
+        ForwardingChannel channel, int bytes);
+}
diff --git a/src/com/sshtools/j2ssh/forwarding/ForwardingIOChannel.java b/src/com/sshtools/j2ssh/forwarding/ForwardingIOChannel.java
new file mode 100644
index 0000000000000000000000000000000000000000..9a0d04286cabe8618812c5c57f0e49ad5abf1641
--- /dev/null
+++ b/src/com/sshtools/j2ssh/forwarding/ForwardingIOChannel.java
@@ -0,0 +1,189 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.forwarding;
+
+import com.sshtools.j2ssh.connection.IOChannel;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+//import java.net.InetSocketAddress;
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class ForwardingIOChannel extends IOChannel implements ForwardingChannel {
+    private static Log log = LogFactory.getLog(ForwardingIOChannel.class);
+    private ForwardingChannelImpl channel;
+
+    /**
+     * Creates a new ForwardingIOChannel object.
+     *
+     * @param forwardType
+     * @param hostToConnectOrBind
+     * @param portToConnectOrBind
+     * @param originatingHost
+     * @param originatingPort
+     *
+     * @throws ForwardingConfigurationException
+     */
+    public ForwardingIOChannel(String forwardType, String name, /*ForwardingConfiguration config,*/
+        String hostToConnectOrBind, int portToConnectOrBind,
+        String originatingHost, int originatingPort)
+        throws ForwardingConfigurationException {
+        if (!forwardType.equals(LOCAL_FORWARDING_CHANNEL) &&
+                !forwardType.equals(REMOTE_FORWARDING_CHANNEL) &&
+                !forwardType.equals(X11_FORWARDING_CHANNEL)) {
+            throw new ForwardingConfigurationException(
+                "The forwarding type is invalid");
+        }
+
+        channel = new ForwardingChannelImpl(forwardType, name,
+                hostToConnectOrBind, portToConnectOrBind, originatingHost,
+                originatingPort);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getChannelOpenData() {
+        return channel.getChannelOpenData();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getChannelConfirmationData() {
+        return channel.getChannelConfirmationData();
+    }
+
+    public String getName() {
+        return channel.getName();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getChannelType() {
+        return channel.getChannelType();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected int getMinimumWindowSpace() {
+        return 32768;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected int getMaximumWindowSpace() {
+        return 131072;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected int getMaximumPacketSize() {
+        return 32768;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getOriginatingHost() {
+        return channel.getOriginatingHost();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getOriginatingPort() {
+        return channel.getOriginatingPort();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getHostToConnectOrBind() {
+        return channel.getHostToConnectOrBind();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getPortToConnectOrBind() {
+        return channel.getPortToConnectOrBind();
+    }
+
+    /**
+     *
+     *
+     * @param request
+     * @param wantReply
+     * @param requestData
+     *
+     * @throws IOException
+     */
+    protected void onChannelRequest(String request, boolean wantReply,
+        byte[] requestData) throws IOException {
+        connection.sendChannelRequestFailure(this);
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    protected void onChannelOpen() throws IOException {
+    }
+}
diff --git a/src/com/sshtools/j2ssh/forwarding/ForwardingListener.java b/src/com/sshtools/j2ssh/forwarding/ForwardingListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..01189f154bfcd8c28903c72dcf8677180fcdb223
--- /dev/null
+++ b/src/com/sshtools/j2ssh/forwarding/ForwardingListener.java
@@ -0,0 +1,236 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.forwarding;
+
+import com.sshtools.j2ssh.SshThread;
+import com.sshtools.j2ssh.connection.ConnectionProtocol;
+import com.sshtools.j2ssh.util.StartStopState;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.30 $
+ */
+public abstract class ForwardingListener extends ForwardingConfiguration
+    implements Runnable {
+    private static Log log = LogFactory.getLog(ForwardingListener.class);
+    private ConnectionProtocol connection;
+    private ServerSocket server;
+    private Thread thread;
+    private boolean listening;
+
+    /**
+     * Creates a new ForwardingListener object.
+     *
+     * @param name
+     * @param connection
+     * @param addressToBind
+     * @param portToBind
+     * @param hostToConnect
+     * @param portToConnect
+     */
+    public ForwardingListener(String name, ConnectionProtocol connection,
+        String addressToBind, int portToBind, String hostToConnect,
+        int portToConnect) {
+        super(name, addressToBind, portToBind, hostToConnect, portToConnect);
+        log.info("Creating forwarding listener named '" + name + "'");
+        this.connection = connection;
+
+        if (log.isDebugEnabled()) {
+            log.debug("Address to bind: " + getAddressToBind());
+            log.debug("Port to bind: " + String.valueOf(getPortToBind()));
+            log.debug("Host to connect: " + hostToConnect);
+            log.debug("Port to connect: " + portToConnect);
+        }
+    }
+
+    /**
+     * Creates a new ForwardingListener object.
+     *
+     * @param connection
+     * @param addressToBind
+     * @param portToBind
+     */
+    public ForwardingListener(ConnectionProtocol connection,
+        String addressToBind, int portToBind) {
+        this(addressToBind + ":" + String.valueOf(portToBind), connection,
+            addressToBind, portToBind, "[Specified by connecting computer]", -1);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getLocalPort() {
+        return (server == null) ? (-1) : server.getLocalPort();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isListening() {
+        return listening;
+    }
+
+    /**
+     *
+     */
+    public void run() {
+        try {
+            log.info("Starting forwarding listener thread for '" + name + "'");
+
+            //
+            //            ServerSocket server = new ServerSocket(getPortToBind(), 50, InetAddress.getByName(getAddressToBind()));
+            //server = new ServerSocket(getPortToBind(), 50, InetAddress.getByName(getAddressToBind()));
+            Socket socket;
+
+            while (state.getValue() == StartStopState.STARTED) {
+                listening = true;
+                socket = server.accept();
+
+                if ((state.getValue() == StartStopState.STOPPED) ||
+                        (socket == null)) {
+                    break;
+                }
+
+                log.info("Connection accepted, creating forwarding channel");
+
+                try {
+                    ForwardingSocketChannel channel = createChannel(hostToConnect,
+                            portToConnect, socket);
+                    channel.bindSocket(socket);
+
+                    if (connection.openChannel(channel)) {
+                        log.info("Forwarding channel for '" + name +
+                            "' is open");
+                    } else {
+                        log.warn("Failed to open forwarding chanel " + name);
+                        socket.close();
+                    }
+                } catch (Exception ex) {
+                    log.warn("Failed to open forwarding chanel " + name, ex);
+
+                    try {
+                        socket.close();
+                    } catch (IOException ioe) {
+                    }
+                }
+            }
+        } catch (IOException ioe) {
+            /* only warn if the forwarding has not been stopped */
+            if (state.getValue() == StartStopState.STARTED) {
+                log.warn("Local forwarding listener to " + hostToConnect + ":" +
+                    String.valueOf(portToConnect) + " has failed", ioe);
+            }
+        } finally {
+            stop();
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isRunning() {
+        return (thread != null) && thread.isAlive();
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    public void start() throws IOException {
+        /* Set the state by calling the super method */
+        super.start();
+
+        /* Bind server socket */
+        try {
+            server = new ServerSocket(getPortToBind(), 50,
+                    InetAddress.getByName(getAddressToBind()));
+        } catch (IOException ioe) {
+            super.stop();
+            throw ioe;
+        }
+
+        /* Create a thread and start it */
+        thread = new SshThread(this, "Forwarding listener", true);
+
+        /* Create a thread and start it */
+        thread = new SshThread(this, "Forwarding listener", true);
+        thread.start();
+    }
+
+    /**
+     *
+     */
+    public void stop() {
+        /* Set the state by calling the super method */
+        super.stop();
+
+        try {
+            /* Close the server socket */
+            if (server != null) {
+                server.close();
+            }
+        } catch (IOException ioe) {
+            log.warn("Forwarding listener failed to stop", ioe);
+        }
+
+        thread = null;
+        listening = false;
+    }
+
+    /**
+     *
+     *
+     * @param hostToConnect
+     * @param portToConnect
+     * @param socket
+     *
+     * @return
+     *
+     * @throws ForwardingConfigurationException
+     */
+    protected abstract ForwardingSocketChannel createChannel(
+        String hostToConnect, int portToConnect, Socket socket)
+        throws ForwardingConfigurationException;
+}
diff --git a/src/com/sshtools/j2ssh/forwarding/ForwardingSocketChannel.java b/src/com/sshtools/j2ssh/forwarding/ForwardingSocketChannel.java
new file mode 100644
index 0000000000000000000000000000000000000000..e99c827165084237eab533f3da1a85713baa2f79
--- /dev/null
+++ b/src/com/sshtools/j2ssh/forwarding/ForwardingSocketChannel.java
@@ -0,0 +1,184 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.forwarding;
+
+import com.sshtools.j2ssh.connection.SocketChannel;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+//import java.net.InetSocketAddress;
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class ForwardingSocketChannel extends SocketChannel
+    implements ForwardingChannel {
+    private static Log log = LogFactory.getLog(ForwardingSocketChannel.class);
+    private ForwardingChannelImpl channel;
+
+    /**
+     * Creates a new ForwardingSocketChannel object.
+     *
+     * @param forwardType
+     * @param hostToConnectOrBind
+     * @param portToConnectOrBind
+     * @param originatingHost
+     * @param originatingPort
+     *
+     * @throws ForwardingConfigurationException
+     */
+    public ForwardingSocketChannel(String forwardType, String name,
+        
+    /*ForwardingConfiguration config,*/
+    String hostToConnectOrBind, int portToConnectOrBind,
+        String originatingHost, int originatingPort)
+        throws ForwardingConfigurationException {
+        if (!forwardType.equals(LOCAL_FORWARDING_CHANNEL) &&
+                !forwardType.equals(REMOTE_FORWARDING_CHANNEL) &&
+                !forwardType.equals(X11_FORWARDING_CHANNEL)) {
+            throw new ForwardingConfigurationException(
+                "The forwarding type is invalid");
+        }
+
+        channel = new ForwardingChannelImpl(forwardType, name,
+                hostToConnectOrBind, portToConnectOrBind, originatingHost,
+                originatingPort);
+    }
+
+    public String getName() {
+        return channel.getName();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getChannelOpenData() {
+        return channel.getChannelOpenData();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getChannelConfirmationData() {
+        return channel.getChannelConfirmationData();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getChannelType() {
+        return channel.getChannelType();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected int getMinimumWindowSpace() {
+        return 32768;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected int getMaximumWindowSpace() {
+        return 131072;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected int getMaximumPacketSize() {
+        return 32768;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getOriginatingHost() {
+        return channel.getOriginatingHost();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getOriginatingPort() {
+        return channel.getOriginatingPort();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getHostToConnectOrBind() {
+        return channel.getHostToConnectOrBind();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getPortToConnectOrBind() {
+        return channel.getPortToConnectOrBind();
+    }
+
+    /**
+     *
+     *
+     * @param request
+     * @param wantReply
+     * @param requestData
+     *
+     * @throws IOException
+     */
+    protected void onChannelRequest(String request, boolean wantReply,
+        byte[] requestData) throws IOException {
+        connection.sendChannelRequestFailure(this);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/forwarding/XDisplay.java b/src/com/sshtools/j2ssh/forwarding/XDisplay.java
new file mode 100644
index 0000000000000000000000000000000000000000..a76ee10edb322146b92811348287dbe1914a65f8
--- /dev/null
+++ b/src/com/sshtools/j2ssh/forwarding/XDisplay.java
@@ -0,0 +1,193 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.forwarding;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class XDisplay {
+    private String host;
+    private int display;
+    private int screen;
+    private int portOffset;
+
+    /**
+     * Creates a new XDisplay object.
+     *
+     * @param string
+     */
+    public XDisplay(String string) {
+        this(string, 6000);
+    }
+
+    /**
+     * Creates a new XDisplay object.
+     *
+     * @param string
+     * @param portOffset
+     */
+    public XDisplay(String string, int portOffset) {
+        setString(string);
+        setPortOffset(portOffset);
+    }
+
+    /**
+     *
+     *
+     * @param portOffset
+     */
+    public void setPortOffset(int portOffset) {
+        this.portOffset = portOffset;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getPortOffset() {
+        return portOffset;
+    }
+
+    /**
+     *
+     *
+     * @param string
+     */
+    public void setString(String string) {
+        int idx = string.indexOf(':');
+
+        if (idx == -1) {
+            display = 0;
+            host = string;
+        } else {
+            host = string.substring(0, idx);
+
+            String s = string.substring(idx + 1);
+            idx = s.indexOf(".");
+
+            if (idx == -1) {
+                screen = 0;
+
+                try {
+                    display = Integer.parseInt(s);
+                } catch (NumberFormatException nfe) {
+                    display = 0;
+                }
+            } else {
+                try {
+                    display = Integer.parseInt(s.substring(0, idx));
+                } catch (NumberFormatException nfe) {
+                    display = 0;
+                }
+
+                try {
+                    screen = Integer.parseInt(s.substring(idx + 1));
+                } catch (NumberFormatException nfe) {
+                    screen = 0;
+                }
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getPort() {
+        return (getDisplay() < getPortOffset())
+        ? (getDisplay() + getPortOffset()) : getDisplay();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getScreen() {
+        return screen;
+    }
+
+    /**
+     *
+     *
+     * @param host
+     */
+    public void setHost(String host) {
+        this.host = host;
+    }
+
+    /**
+     *
+     *
+     * @param display
+     */
+    public void setDisplay(int display) {
+        this.display = display;
+    }
+
+    /**
+     *
+     *
+     * @param screen
+     */
+    public void setScreen(int screen) {
+        this.screen = screen;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getHost() {
+        return host;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getDisplay() {
+        return display;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String toString() {
+        return getHost() + ":" + getDisplay() +
+        ((getScreen() == 0) ? "" : ("." + getScreen()));
+    }
+}
diff --git a/src/com/sshtools/j2ssh/io/ByteArrayReader.java b/src/com/sshtools/j2ssh/io/ByteArrayReader.java
new file mode 100644
index 0000000000000000000000000000000000000000..3875ad1dc5d8501529ae6ad09fbb4c9dba674a6a
--- /dev/null
+++ b/src/com/sshtools/j2ssh/io/ByteArrayReader.java
@@ -0,0 +1,168 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.io;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+import java.math.BigInteger;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public class ByteArrayReader extends ByteArrayInputStream {
+    /**
+     * Creates a new ByteArrayReader object.
+     *
+     * @param data
+     */
+    public ByteArrayReader(byte[] data) {
+        super(data);
+    }
+
+    /**
+     *
+     *
+     * @param data
+     * @param start
+     *
+     * @return
+     */
+    public static long readInt(byte[] data, int start) {
+        long ret = (((long) (data[start] & 0xFF) << 24) & 0xFFFFFFFF) |
+            ((data[start + 1] & 0xFF) << 16) | ((data[start + 2] & 0xFF) << 8) |
+            ((data[start + 3] & 0xFF) << 0);
+
+        return ret;
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public long readInt() throws IOException {
+        byte[] raw = new byte[4];
+        read(raw);
+
+        long ret = (((long) (raw[0] & 0xFF) << 24) & 0xFFFFFFFF) |
+            ((raw[1] & 0xFF) << 16) | ((raw[2] & 0xFF) << 8) | (raw[3] & 0xFF);
+
+        return ret;
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public UnsignedInteger32 readUINT32() throws IOException {
+        return new UnsignedInteger32(readInt());
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public UnsignedInteger64 readUINT64() throws IOException {
+        byte[] raw = new byte[8];
+        read(raw);
+
+        return new UnsignedInteger64(raw);
+    }
+
+    /**
+     *
+     *
+     * @param data
+     * @param start
+     *
+     * @return
+     */
+    public static String readString(byte[] data, int start) {
+        int len = (int) readInt(data, start);
+        byte[] chars = new byte[(int) len];
+        System.arraycopy(data, start + 4, chars, 0, len);
+
+        return new String(chars);
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public BigInteger readBigInteger() throws IOException {
+        int len = (int) readInt();
+        byte[] raw = new byte[len];
+        read(raw);
+
+        return new BigInteger(raw);
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public byte[] readBinaryString() throws IOException {
+        long len = readInt();
+        byte[] raw = new byte[(int) len];
+        read(raw);
+
+        return raw;
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public String readString() throws IOException {
+        long len = readInt();
+        byte[] raw = new byte[(int) len];
+        read(raw);
+
+        return new String(raw);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/io/ByteArrayWriter.java b/src/com/sshtools/j2ssh/io/ByteArrayWriter.java
new file mode 100644
index 0000000000000000000000000000000000000000..98dd71ece96a46ddad528b05f6f13e0557635b25
--- /dev/null
+++ b/src/com/sshtools/j2ssh/io/ByteArrayWriter.java
@@ -0,0 +1,206 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.io;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import java.math.BigInteger;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.18 $
+ */
+public class ByteArrayWriter extends ByteArrayOutputStream {
+    /**
+     * Creates a new ByteArrayWriter object.
+     */
+    public ByteArrayWriter() {
+    }
+
+    /**
+     *
+     *
+     * @param bi
+     *
+     * @throws IOException
+     */
+    public void writeBigInteger(BigInteger bi) throws IOException {
+        byte[] raw = bi.toByteArray();
+        writeInt(raw.length);
+        write(raw);
+    }
+
+    /**
+     *
+     *
+     * @param b
+     *
+     * @throws IOException
+     */
+    public void writeBoolean(boolean b) throws IOException {
+        write(b ? 1 : 0);
+    }
+
+    /**
+     *
+     *
+     * @param data
+     *
+     * @throws IOException
+     */
+    public void writeBinaryString(byte[] data) throws IOException {
+        writeInt(data.length);
+        write(data);
+    }
+
+    /**
+     *
+     *
+     * @param i
+     *
+     * @throws IOException
+     */
+    public void writeInt(long i) throws IOException {
+        byte[] raw = new byte[4];
+        raw[0] = (byte) (i >> 24);
+        raw[1] = (byte) (i >> 16);
+        raw[2] = (byte) (i >> 8);
+        raw[3] = (byte) (i);
+        write(raw);
+    }
+
+    /**
+     *
+     *
+     * @param i
+     *
+     * @throws IOException
+     */
+    public void writeInt(int i) throws IOException {
+        byte[] raw = new byte[4];
+        raw[0] = (byte) (i >> 24);
+        raw[1] = (byte) (i >> 16);
+        raw[2] = (byte) (i >> 8);
+        raw[3] = (byte) (i);
+        write(raw);
+    }
+
+    /**
+     *
+     *
+     * @param i
+     *
+     * @return
+     */
+    public static byte[] encodeInt(int i) {
+        byte[] raw = new byte[4];
+        raw[0] = (byte) (i >> 24);
+        raw[1] = (byte) (i >> 16);
+        raw[2] = (byte) (i >> 8);
+        raw[3] = (byte) (i);
+
+        return raw;
+    }
+
+    /**
+     *
+     *
+     * @param value
+     *
+     * @throws IOException
+     */
+    public void writeUINT32(UnsignedInteger32 value) throws IOException {
+        writeInt(value.longValue());
+    }
+
+    /**
+     *
+     *
+     * @param value
+     *
+     * @throws IOException
+     */
+    public void writeUINT64(UnsignedInteger64 value) throws IOException {
+        byte[] raw = new byte[8];
+        byte[] bi = value.bigIntValue().toByteArray();
+        System.arraycopy(bi, 0, raw, raw.length - bi.length, bi.length);
+
+        // Pad the raw data
+        write(raw);
+    }
+
+    /**
+     *
+     *
+     * @param array
+     * @param pos
+     * @param value
+     *
+     * @throws IOException
+     */
+    public static void writeIntToArray(byte[] array, int pos, int value)
+        throws IOException {
+        if ((array.length - pos) < 4) {
+            throw new IOException(
+                "Not enough data in array to write integer at position " +
+                String.valueOf(pos));
+        }
+
+        array[pos] = (byte) (value >> 24);
+        array[pos + 1] = (byte) (value >> 16);
+        array[pos + 2] = (byte) (value >> 8);
+        array[pos + 3] = (byte) (value);
+    }
+
+    /**
+     *
+     *
+     * @param str
+     *
+     * @throws IOException
+     */
+    public void writeString(String str) throws IOException {
+        if (str == null) {
+            writeInt(0);
+        } else {
+            /*
+            writeInt(str.length());
+            // don't use US-ASCII by default!
+            write(str.getBytes());
+            */
+            // patch as of version 0.2.9
+            // for UTF-8 length of string is not necessarily
+            // equal to number of bytes
+            byte[] strBytes = str.getBytes();
+            writeInt(strBytes.length);
+            write(strBytes);
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/io/DynamicBuffer.java b/src/com/sshtools/j2ssh/io/DynamicBuffer.java
new file mode 100644
index 0000000000000000000000000000000000000000..307016d5abaca1011b875b41624eb5216bb700ff
--- /dev/null
+++ b/src/com/sshtools/j2ssh/io/DynamicBuffer.java
@@ -0,0 +1,295 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.io;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+
+
+/**
+ * <p>
+ * This class provides an alternative method of storing data, used within the
+ * API where Piped Streams could have been used. We found that Piped streams
+ * would lock if a thread attempted to read to data when the OutputStream attached
+     * was not being read; since we have no control over when the user will actually
+ * read the data, this behaviour led us to develop this dynamic buffer which
+ * will automatically grow if the buffer is full.
+ * </p>
+ *  *
+ * @author Lee David Painter
+ * @version $Revision: 1.20 $
+ */
+public class DynamicBuffer {
+    private static Log log = LogFactory.getLog(DynamicBuffer.class);
+
+    /** Buffer size when the dynamic buffer is opened */
+    protected static final int DEFAULT_BUFFER_SIZE = 32768;
+
+    /** The buffer */
+    protected byte[] buf;
+
+    /** The current write position */
+    protected int writepos = 0;
+
+    /** The current read position */
+    protected int readpos = 0;
+
+    /** This buffers InputStream */
+    protected InputStream in;
+
+    /** This buffers OutputStream */
+    protected OutputStream out;
+    private boolean closed = false;
+    private int interrupt = 5000;
+
+    /**
+     * Creates a new DynamicBuffer object.
+     */
+    public DynamicBuffer() {
+        buf = new byte[DEFAULT_BUFFER_SIZE];
+        in = new DynamicBufferInputStream();
+        out = new DynamicBufferOutputStream();
+    }
+
+    /**
+     * Get the InputStream of this buffer. Use the stream to read data from
+     * this buffer.
+     *
+     * @return
+     */
+    public InputStream getInputStream() {
+        return in;
+    }
+
+    /**
+     * Get the OutputStream of the buffer. Use this stream to write data to
+     * the buffer.
+     *
+     * @return
+     */
+    public OutputStream getOutputStream() {
+        return out;
+    }
+
+    private synchronized void verifyBufferSize(int count) {
+        // If there is not enough data in the buffer, then first attempt to
+        // move the unread data back to the beginning
+        if (count > (buf.length - writepos)) {
+            System.arraycopy(buf, readpos, buf, 0, writepos - readpos);
+            writepos -= readpos;
+            readpos = 0;
+        }
+
+        // Now double check and increase the buffer size if necersary
+        if (count > (buf.length - writepos)) {
+            byte[] tmp = new byte[buf.length + DEFAULT_BUFFER_SIZE];
+            System.arraycopy(buf, 0, tmp, 0, writepos - readpos);
+            buf = tmp;
+        }
+    }
+
+    /**
+     * Return the number of bytes of data available to be read from the buffer
+     * @return
+     */
+    protected synchronized int available() {
+        return writepos - readpos;
+    }
+
+    private synchronized void block() throws InterruptedException {
+        if (log.isDebugEnabled()) {
+            log.debug("Buffer size: " + String.valueOf(buf.length));
+            log.debug("Unread data: " + String.valueOf(writepos - readpos));
+        }
+
+        // Block and wait for more data
+        if (!closed) {
+            while ((readpos >= writepos) && !closed) {
+                wait(interrupt);
+            }
+        }
+    }
+
+    /**
+     * Closes the buffer
+     */
+    public synchronized void close() {
+        if (!closed) {
+            closed = true;
+            notifyAll();
+        }
+    }
+
+    /**
+     * Write a byte array to the buffer
+     *
+     * @param b
+     *
+     * @throws IOException
+     */
+    protected synchronized void write(int b) throws IOException {
+        if (closed) {
+            throw new IOException("The buffer is closed");
+        }
+
+        verifyBufferSize(1);
+        buf[writepos] = (byte) b;
+        writepos++;
+        notifyAll();
+    }
+
+    /**
+     *
+     *
+     * @param data
+     * @param offset
+     * @param len
+     *
+     * @throws IOException
+     */
+    protected synchronized void write(byte[] data, int offset, int len)
+        throws IOException {
+        if (closed) {
+            throw new IOException("The buffer is closed");
+        }
+
+        verifyBufferSize(len);
+        System.arraycopy(data, offset, buf, writepos, len);
+        writepos += len;
+        notifyAll();
+    }
+
+    public void setBlockInterrupt(int interrupt) {
+        this.interrupt = interrupt;
+    }
+
+    /**
+     * Read a byte from the buffer
+     *
+     * @return
+     *
+     * @throws IOException
+     * @throws InterruptedIOException
+     */
+    protected synchronized int read() throws IOException {
+        try {
+            block();
+        } catch (InterruptedException ex) {
+            throw new InterruptedIOException(
+                "The blocking operation was interrupted");
+        }
+
+        if (closed && (available() <= 0)) {
+            return -1;
+        }
+
+        return (int) buf[readpos++];
+    }
+
+    /**
+     * Read a byte array from the buffer
+     *
+     * @param data
+     * @param offset
+     * @param len
+     *
+     * @return
+     *
+     * @throws IOException
+     * @throws InterruptedIOException
+     */
+    protected synchronized int read(byte[] data, int offset, int len)
+        throws IOException {
+        try {
+            block();
+        } catch (InterruptedException ex) {
+            throw new InterruptedIOException(
+                "The blocking operation was interrupted");
+        }
+
+        if (closed && (available() <= 0)) {
+            return -1;
+        }
+
+        int read = (len > (writepos - readpos)) ? (writepos - readpos) : len;
+        System.arraycopy(buf, readpos, data, offset, read);
+        readpos += read;
+
+        return read;
+    }
+
+    /**
+     * Flush data
+     *
+     * @throws IOException
+     */
+    protected synchronized void flush() throws IOException {
+        notifyAll();
+    }
+
+    class DynamicBufferInputStream extends InputStream {
+        public int read() throws IOException {
+            return DynamicBuffer.this.read();
+        }
+
+        public int read(byte[] data, int offset, int len)
+            throws IOException {
+            return DynamicBuffer.this.read(data, offset, len);
+        }
+
+        public int available() {
+            return DynamicBuffer.this.available();
+        }
+
+        public void close() {
+            DynamicBuffer.this.close();
+        }
+    }
+
+    class DynamicBufferOutputStream extends OutputStream {
+        public void write(int b) throws IOException {
+            DynamicBuffer.this.write(b);
+        }
+
+        public void write(byte[] data, int offset, int len)
+            throws IOException {
+            DynamicBuffer.this.write(data, offset, len);
+        }
+
+        public void flush() throws IOException {
+            DynamicBuffer.this.flush();
+        }
+
+        public void close() {
+            DynamicBuffer.this.close();
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/io/IOStreamConnector.java b/src/com/sshtools/j2ssh/io/IOStreamConnector.java
new file mode 100644
index 0000000000000000000000000000000000000000..e33800808875e0e59f067915169c6b5e379621e7
--- /dev/null
+++ b/src/com/sshtools/j2ssh/io/IOStreamConnector.java
@@ -0,0 +1,236 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.io;
+
+import com.sshtools.j2ssh.*;
+
+import org.apache.commons.logging.*;
+
+import java.io.*;
+
+import javax.swing.event.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.29 $
+ */
+public class IOStreamConnector {
+    private static Log log = LogFactory.getLog(IOStreamConnector.class);
+    private IOStreamConnectorState state = new IOStreamConnectorState();
+    private InputStream in = null;
+    private OutputStream out = null;
+    private Thread thread;
+    private long bytes;
+    private boolean closeInput = true;
+    private boolean closeOutput = true;
+
+    /**  */
+    protected EventListenerList listenerList = new EventListenerList();
+
+    /**
+     * Creates a new IOStreamConnector object.
+     */
+    public IOStreamConnector() {
+    }
+
+    /**
+     * Creates a new IOStreamConnector object.
+     *
+     * @param in
+     * @param out
+     */
+    public IOStreamConnector(InputStream in, OutputStream out) {
+        connect(in, out);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public IOStreamConnectorState getState() {
+        return state;
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    public void close() throws IOException {
+        log.info("Closing IOStreamConnector");
+        state.setValue(IOStreamConnectorState.CLOSED);
+
+        if (closeInput) {
+            in.close();
+        }
+
+        if (closeOutput) {
+            out.close();
+        }
+
+        thread = null;
+    }
+
+    /**
+     *
+     *
+     * @param closeInput
+     */
+    public void setCloseInput(boolean closeInput) {
+        this.closeInput = closeInput;
+    }
+
+    /**
+     *
+     *
+     * @param closeOutput
+     */
+    public void setCloseOutput(boolean closeOutput) {
+        this.closeOutput = closeOutput;
+    }
+
+    /**
+     *
+     *
+     * @param in
+     * @param out
+     */
+    public void connect(InputStream in, OutputStream out) {
+        this.in = in;
+        this.out = out;
+        log.info("Connecting InputStream to OutputStream");
+        state.setValue(IOStreamConnectorState.CONNECTED);
+        thread = new SshThread(new IOStreamConnectorThread(),
+                "IOStream connector", true);
+        thread.start();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getBytes() {
+        return bytes;
+    }
+
+    /**
+     *
+     *
+     * @param l
+     */
+    public void addIOStreamConnectorListener(IOStreamConnectorListener l) {
+        listenerList.add(IOStreamConnectorListener.class, l);
+    }
+
+    /**
+     *
+     *
+     * @param l
+     */
+    public void removeIOStreamConnectorListener(IOStreamConnectorListener l) {
+        listenerList.remove(IOStreamConnectorListener.class, l);
+    }
+
+    class IOStreamConnectorThread implements Runnable {
+        private Log log = LogFactory.getLog(IOStreamConnectorThread.class);
+
+        public void run() {
+            byte[] buffer = new byte[4096];
+            int read = 0;
+            int count;
+            int available;
+            log.info("Starting IOStreamConnectorThread thread");
+
+            while (state.getValue() == IOStreamConnectorState.CONNECTED) {
+                try {
+                    // Block
+                    read = in.read(buffer, 0, 1);
+
+                    if (read > 0) {
+                        count = read;
+                        available = in.available();
+
+                        // Verify the buffer length and adjust if necersary
+                        if ((available > 0) &&
+                                ((buffer.length - 1) < available)) {
+                            byte[] tmp = new byte[available + 1];
+                            System.arraycopy(buffer, 0, tmp, 0, 1);
+                            buffer = tmp;
+                        }
+
+                        // Read the remaining available bytes of the message
+                        if (available > 0) {
+                            read = in.read(buffer, 1, available);
+                            count += read;
+                        }
+
+                        // Write the message to the output stream
+                        out.write(buffer, 0, count);
+                        bytes += count;
+
+                        // Flush it
+                        out.flush();
+
+                        // Inform all of the listeners
+                        IOStreamConnectorListener[] l = (IOStreamConnectorListener[]) listenerList.getListeners(IOStreamConnectorListener.class);
+
+                        for (int i = (l.length - 1); i >= 0; i--) {
+                            l[i].data(buffer, count);
+                        }
+                    } else {
+                        log.debug("Blocking read returned with " +
+                            String.valueOf(read));
+
+                        if (read < 0) {
+                            state.setValue(IOStreamConnectorState.EOF);
+                        }
+                    }
+                } catch (IOException ioe) {
+                    // only warn if were supposed to be still connected, as we will ignore close exceptions
+                    if (state.getValue() == IOStreamConnectorState.CONNECTED) {
+                        log.debug(ioe.getMessage());
+                        state.setValue(IOStreamConnectorState.EOF);
+                    }
+                }
+            }
+
+            try {
+                // if were not already closed then close the connector
+                if (state.getValue() != IOStreamConnectorState.CLOSED) {
+                    close();
+                }
+            } catch (IOException ioe) {
+            }
+
+            log.info("IOStreamConnectorThread is exiting");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/io/IOStreamConnectorListener.java b/src/com/sshtools/j2ssh/io/IOStreamConnectorListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..cb707eca20503422dab2a1af5fddc1b45da466b0
--- /dev/null
+++ b/src/com/sshtools/j2ssh/io/IOStreamConnectorListener.java
@@ -0,0 +1,45 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.io;
+
+import java.util.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public interface IOStreamConnectorListener extends EventListener {
+    /**
+     *
+     *
+     * @param data
+     * @param count
+     */
+    public void data(byte[] data, int count);
+}
diff --git a/src/com/sshtools/j2ssh/io/IOStreamConnectorState.java b/src/com/sshtools/j2ssh/io/IOStreamConnectorState.java
new file mode 100644
index 0000000000000000000000000000000000000000..30052b4f6fc66aa554af773ab677429714de6ad1
--- /dev/null
+++ b/src/com/sshtools/j2ssh/io/IOStreamConnectorState.java
@@ -0,0 +1,68 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.io;
+
+import com.sshtools.j2ssh.util.State;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class IOStreamConnectorState extends State {
+    /**  */
+    public final static int BOF = 1;
+
+    /**  */
+    public final static int CONNECTED = 2;
+
+    /**  */
+    public final static int EOF = 3;
+
+    /**  */
+    public final static int CLOSED = 4;
+
+    /**
+     * Creates a new IOStreamConnectorState object.
+     */
+    public IOStreamConnectorState() {
+        super(BOF);
+    }
+
+    /**
+     *
+     *
+     * @param state
+     *
+     * @return
+     */
+    public boolean isValidState(int state) {
+        return ((state == BOF) || (state == CONNECTED) || (state == EOF) ||
+        (state == CLOSED));
+    }
+}
diff --git a/src/com/sshtools/j2ssh/io/IOUtil.java b/src/com/sshtools/j2ssh/io/IOUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..adeb1cbbb2da2d24d1271feb585c1ec7f08f989d
--- /dev/null
+++ b/src/com/sshtools/j2ssh/io/IOUtil.java
@@ -0,0 +1,181 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.io;
+
+import java.io.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.20 $
+ */
+public class IOUtil {
+    /**
+     *
+     *
+     * @param in
+     *
+     * @return
+     */
+    public static boolean closeStream(InputStream in) {
+        try {
+            if (in != null) {
+                in.close();
+            }
+
+            return true;
+        } catch (IOException ioe) {
+            return false;
+        }
+    }
+
+    /**
+     *
+     *
+     * @param out
+     *
+     * @return
+     */
+    public static boolean closeStream(OutputStream out) {
+        try {
+            if (out != null) {
+                out.close();
+            }
+
+            return true;
+        } catch (IOException ioe) {
+            return false;
+        }
+    }
+
+    public static boolean delTree(File file) {
+        if (file.isFile()) {
+            return file.delete();
+        } else {
+            File[] list = file.listFiles();
+
+            for (int i = 0; i < list.length; i++) {
+                if (!delTree(list[i])) {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    public static void recurseDeleteDirectory(File dir) {
+        File[] files = dir.listFiles(new FileFilter() {
+                    public boolean accept(File file) {
+                        return file.isDirectory();
+                    }
+                });
+
+        if (files == null) {
+            return; // Directory could not be read
+        }
+
+        for (int i = 0; i < files.length; i++) {
+            recurseDeleteDirectory(files[i]);
+            files[i].delete();
+        }
+
+        files = dir.listFiles(new FileFilter() {
+                    public boolean accept(File file) {
+                        return !file.isDirectory();
+                    }
+                });
+
+        for (int i = 0; i < files.length; i++) {
+            files[i].delete();
+        }
+
+        dir.delete();
+    }
+
+    public static void copyFile(File from, File to) throws IOException {
+        if (from.isDirectory()) {
+            if (!to.exists()) {
+                to.mkdir();
+            }
+
+            File[] children = from.listFiles();
+
+            for (int i = 0; i < children.length; i++) {
+                if (children[i].getName().equals(".") ||
+                        children[i].getName().equals("..")) {
+                    continue;
+                }
+
+                if (children[i].isDirectory()) {
+                    File f = new File(to, children[i].getName());
+                    copyFile(children[i], f);
+                } else {
+                    copyFile(children[i], to);
+                }
+            }
+        } else if (from.isFile() && (to.isDirectory() || to.isFile())) {
+            if (to.isDirectory()) {
+                to = new File(to, from.getName());
+            }
+
+            FileInputStream in = new FileInputStream(from);
+            FileOutputStream out = new FileOutputStream(to);
+            byte[] buf = new byte[32678];
+            int read;
+
+            while ((read = in.read(buf)) > -1) {
+                out.write(buf, 0, read);
+            }
+
+            closeStream(in);
+            closeStream(out);
+        }
+    }
+
+    public static void transfer(InputStream in, OutputStream out)
+        throws IOException {
+        try {
+            long bytesSoFar = 0;
+            byte[] buffer = new byte[65535];
+            int read;
+
+            while ((read = in.read(buffer)) > -1) {
+                if (read > 0) {
+                    out.write(buffer, 0, read);
+
+                    //out.flush();
+                    bytesSoFar += read;
+                }
+            }
+        } finally {
+            closeStream(in);
+            closeStream(out);
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/io/UnsignedInteger32.java b/src/com/sshtools/j2ssh/io/UnsignedInteger32.java
new file mode 100644
index 0000000000000000000000000000000000000000..02f635545b204f22230c868bc1e2b9223633b4af
--- /dev/null
+++ b/src/com/sshtools/j2ssh/io/UnsignedInteger32.java
@@ -0,0 +1,190 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.io;
+
+import java.io.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class UnsignedInteger32 extends Number implements Serializable {
+    final static long serialVersionUID = 200;
+
+    /**  */
+    public final static long MAX_VALUE = 0xffffffffL;
+
+    /**  */
+    public final static long MIN_VALUE = 0;
+    private Long value;
+
+    /**
+     * Creates a new UnsignedInteger32 object.
+     *
+     * @param a
+     *
+     * @throws NumberFormatException
+     */
+    public UnsignedInteger32(long a) {
+        if ((a < MIN_VALUE) || (a > MAX_VALUE)) {
+            throw new NumberFormatException();
+        }
+
+        value = new Long(a);
+    }
+
+    /**
+     * Creates a new UnsignedInteger32 object.
+     *
+     * @param a
+     *
+     * @throws NumberFormatException
+     */
+    public UnsignedInteger32(String a) throws NumberFormatException {
+        Long temp = new Long(a);
+        long longValue = temp.longValue();
+
+        if ((longValue < MIN_VALUE) || (longValue > MAX_VALUE)) {
+            throw new NumberFormatException();
+        }
+
+        value = new Long(longValue);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte byteValue() {
+        return value.byteValue();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public short shortValue() {
+        return value.shortValue();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int intValue() {
+        return value.intValue();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long longValue() {
+        return value.longValue();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public float floatValue() {
+        return value.floatValue();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public double doubleValue() {
+        return value.doubleValue();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String toString() {
+        return value.toString();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int hashCode() {
+        return value.hashCode();
+    }
+
+    /**
+     *
+     *
+     * @param o
+     *
+     * @return
+     */
+    public boolean equals(Object o) {
+        if (!(o instanceof UnsignedInteger32)) {
+            return false;
+        }
+
+        return (((UnsignedInteger32) o).value.equals(this.value));
+    }
+
+    /**
+     *
+     *
+     * @param x
+     * @param y
+     *
+     * @return
+     */
+    public static UnsignedInteger32 add(UnsignedInteger32 x, UnsignedInteger32 y) {
+        return new UnsignedInteger32(x.longValue() + y.longValue());
+    }
+
+    /**
+     *
+     *
+     * @param x
+     * @param y
+     *
+     * @return
+     */
+    public static UnsignedInteger32 add(UnsignedInteger32 x, int y) {
+        return new UnsignedInteger32(x.longValue() + y);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/io/UnsignedInteger64.java b/src/com/sshtools/j2ssh/io/UnsignedInteger64.java
new file mode 100644
index 0000000000000000000000000000000000000000..14942249706f784aa0a7ebdad17660da53ee7b00
--- /dev/null
+++ b/src/com/sshtools/j2ssh/io/UnsignedInteger64.java
@@ -0,0 +1,214 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.io;
+
+import java.io.*;
+
+import java.math.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class UnsignedInteger64 extends Number implements Serializable,
+    Comparable {
+    final static long serialVersionUID = 200;
+
+    /**  */
+    public final static BigInteger MAX_VALUE = new BigInteger(
+            "18446744073709551615");
+
+    /**  */
+    public final static BigInteger MIN_VALUE = new BigInteger("0");
+    private BigInteger bigInt;
+
+    /**
+     * Creates a new UnsignedInteger64 object.
+     *
+     * @param sval
+     *
+     * @throws NumberFormatException
+     */
+    public UnsignedInteger64(String sval) throws NumberFormatException {
+        bigInt = new BigInteger(sval);
+
+        if ((bigInt.compareTo(MIN_VALUE) < 0) ||
+                (bigInt.compareTo(MAX_VALUE) > 0)) {
+            throw new NumberFormatException();
+        }
+    }
+
+    /**
+     * Creates a new UnsignedInteger64 object.
+     *
+     * @param bval
+     *
+     * @throws NumberFormatException
+     */
+    public UnsignedInteger64(byte[] bval) throws NumberFormatException {
+        bigInt = new BigInteger(bval);
+
+        if ((bigInt.compareTo(MIN_VALUE) < 0) ||
+                (bigInt.compareTo(MAX_VALUE) > 0)) {
+            throw new NumberFormatException();
+        }
+    }
+
+    /**
+     * Creates a new UnsignedInteger64 object.
+     *
+     * @param input
+     *
+     * @throws NumberFormatException
+     */
+    public UnsignedInteger64(BigInteger input) {
+        bigInt = new BigInteger(input.toString());
+
+        if ((bigInt.compareTo(MIN_VALUE) < 0) ||
+                (bigInt.compareTo(MAX_VALUE) > 0)) {
+            throw new NumberFormatException();
+        }
+    }
+
+    /**
+     *
+     *
+     * @param o
+     *
+     * @return
+     */
+    public boolean equals(Object o) {
+        try {
+            UnsignedInteger64 u = (UnsignedInteger64) o;
+
+            return u.bigInt.equals(this.bigInt);
+        } catch (ClassCastException ce) {
+            // This was not an UnsignedInt64, so equals should fail.
+            return false;
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public BigInteger bigIntValue() {
+        return bigInt;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int intValue() {
+        return bigInt.intValue();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long longValue() {
+        return bigInt.longValue();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public double doubleValue() {
+        return bigInt.doubleValue();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public float floatValue() {
+        return bigInt.floatValue();
+    }
+
+    /**
+     *
+     *
+     * @param val
+     *
+     * @return
+     */
+    public int compareTo(Object val) {
+        return bigInt.compareTo(((UnsignedInteger64) val).bigInt);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String toString() {
+        return bigInt.toString();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int hashCode() {
+        return bigInt.hashCode();
+    }
+
+    /**
+     *
+     *
+     * @param x
+     * @param y
+     *
+     * @return
+     */
+    public static UnsignedInteger64 add(UnsignedInteger64 x, UnsignedInteger64 y) {
+        return new UnsignedInteger64(x.bigInt.add(y.bigInt));
+    }
+
+    /**
+     *
+     *
+     * @param x
+     * @param y
+     *
+     * @return
+     */
+    public static UnsignedInteger64 add(UnsignedInteger64 x, int y) {
+        return new UnsignedInteger64(x.bigInt.add(BigInteger.valueOf(y)));
+    }
+}
diff --git a/src/com/sshtools/j2ssh/net/ConnectedSocketTransportProvider.java b/src/com/sshtools/j2ssh/net/ConnectedSocketTransportProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..9e75e29ba6f7dcddb19c7d2be555ee3b97d4f1e1
--- /dev/null
+++ b/src/com/sshtools/j2ssh/net/ConnectedSocketTransportProvider.java
@@ -0,0 +1,88 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.net;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import java.net.Socket;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class ConnectedSocketTransportProvider implements TransportProvider {
+    Socket socket;
+
+    /**
+     * Creates a new ConnectedSocketTransportProvider object.
+     *
+     * @param socket
+     */
+    public ConnectedSocketTransportProvider(Socket socket) {
+        this.socket = socket;
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    public void close() throws IOException {
+        socket.close();
+    }
+
+    /*public boolean isConnected() {
+       return true; //socket.isConnected();
+     }*/
+    public InputStream getInputStream() throws IOException {
+        return socket.getInputStream();
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public OutputStream getOutputStream() throws IOException {
+        return socket.getOutputStream();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getProviderDetail() {
+        return socket.toString(); //getRemoteSocketAddress().toString();
+    }
+}
diff --git a/src/com/sshtools/j2ssh/net/HttpHeader.java b/src/com/sshtools/j2ssh/net/HttpHeader.java
new file mode 100644
index 0000000000000000000000000000000000000000..901ff9b2f363a9023c9a7ebbc1851fd39efdd9b2
--- /dev/null
+++ b/src/com/sshtools/j2ssh/net/HttpHeader.java
@@ -0,0 +1,223 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.net;
+
+import java.io.*;
+
+import java.util.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public abstract class HttpHeader {
+    /**  */
+    protected final static String white_SPACE = " \t\r";
+    HashMap fields;
+
+    /**  */
+    protected String begin;
+
+    /**
+     * Creates a new HttpHeader object.
+     */
+    protected HttpHeader() {
+        fields = new HashMap();
+    }
+
+    /**
+     *
+     *
+     * @param in
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    protected String readLine(InputStream in) throws IOException {
+        StringBuffer lineBuf = new StringBuffer();
+        int c;
+
+        while (true) {
+            c = in.read();
+
+            if (c == -1) {
+                throw new IOException(
+                    "Failed to read expected HTTP header line");
+            }
+
+            if (c == '\n') {
+                continue;
+            }
+
+            if (c != '\r') {
+                lineBuf.append((char) c);
+            } else {
+                break;
+            }
+        }
+
+        return new String(lineBuf);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getStartLine() {
+        return begin;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public Map getHeaderFields() {
+        return fields;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public Set getHeaderFieldNames() {
+        return fields.keySet();
+    }
+
+    /**
+     *
+     *
+     * @param headerName
+     *
+     * @return
+     */
+    public String getHeaderField(String headerName) {
+        return (String) fields.get(headerName.toLowerCase());
+    }
+
+    /**
+     *
+     *
+     * @param headerName
+     * @param value
+     */
+    public void setHeaderField(String headerName, String value) {
+        fields.put(headerName.toLowerCase(), value);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String toString() {
+        String str = begin + "\r\n";
+        Iterator it = getHeaderFieldNames().iterator();
+
+        while (it.hasNext()) {
+            String fieldName = (String) it.next();
+            str += (fieldName + ": " + getHeaderField(fieldName) + "\r\n");
+        }
+
+        str += "\r\n";
+
+        return str;
+    }
+
+    /**
+     *
+     *
+     * @param in
+     *
+     * @throws IOException
+     */
+    protected void processHeaderFields(InputStream in)
+        throws IOException {
+        fields = new HashMap();
+
+        StringBuffer lineBuf = new StringBuffer();
+        String lastHeaderName = null;
+        int c;
+
+        while (true) {
+            c = in.read();
+
+            if (c == -1) {
+                throw new IOException("The HTTP header is corrupt");
+            }
+
+            if (c == '\n') {
+                continue;
+            }
+
+            if (c != '\r') {
+                lineBuf.append((char) c);
+            } else {
+                if (lineBuf.length() != 0) {
+                    String line = lineBuf.toString();
+                    lastHeaderName = processNextLine(line, lastHeaderName);
+                    lineBuf.setLength(0);
+                } else {
+                    break;
+                }
+            }
+        }
+
+        c = in.read();
+    }
+
+    private String processNextLine(String line, String lastHeaderName)
+        throws IOException {
+        String name;
+        String value;
+        char c = line.charAt(0);
+
+        if ((c == ' ') || (c == '\t')) {
+            name = lastHeaderName;
+            value = getHeaderField(lastHeaderName) + " " + line.trim();
+        } else {
+            int n = line.indexOf(':');
+
+            if (n == -1) {
+                throw new IOException(
+                    "HTTP Header encoutered a corrupt field: '" + line + "'");
+            }
+
+            name = line.substring(0, n).toLowerCase();
+            value = line.substring(n + 1).trim();
+        }
+
+        setHeaderField(name, value);
+
+        return name;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/net/HttpProxySocketProvider.java b/src/com/sshtools/j2ssh/net/HttpProxySocketProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..5f1c512dbca4015ff295308b667ac03610c5621e
--- /dev/null
+++ b/src/com/sshtools/j2ssh/net/HttpProxySocketProvider.java
@@ -0,0 +1,205 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.net;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class HttpProxySocketProvider extends Socket implements TransportProvider {
+    private String proxyHost;
+    private int proxyPort;
+    private String remoteHost;
+    private int remotePort;
+    private HttpResponse responseHeader;
+    private String providerDetail;
+
+    private HttpProxySocketProvider(String host, int port, String proxyHost,
+        int proxyPort) throws IOException, UnknownHostException {
+        super(proxyHost, proxyPort);
+        this.proxyHost = proxyHost;
+        this.proxyPort = proxyPort;
+        this.remoteHost = remoteHost;
+        this.remotePort = remotePort;
+    }
+
+    /**
+     *
+     *
+     * @param host
+     * @param port
+     * @param proxyHost
+     * @param proxyPort
+     * @param username
+     * @param password
+     * @param userAgent
+     *
+     * @return
+     *
+     * @throws IOException
+     * @throws UnknownHostException
+     */
+    public static HttpProxySocketProvider connectViaProxy(String host,
+        int port, String proxyHost, int proxyPort, String username,
+        String password, String userAgent)
+        throws IOException, UnknownHostException {
+        return connectViaProxy(host, port, proxyHost, proxyPort, null,
+            username, password, userAgent);
+    }
+
+    /**
+     *
+     *
+     * @param host
+     * @param port
+     * @param proxyHost
+     * @param proxyPort
+     * @param protocol
+     * @param username
+     * @param password
+     * @param userAgent
+     *
+     * @return
+     *
+     * @throws IOException
+     * @throws UnknownHostException
+     * @throws SocketException
+     */
+    public static HttpProxySocketProvider connectViaProxy(String host,
+        int port, String proxyHost, int proxyPort, String protocol,
+        String username, String password, String userAgent)
+        throws IOException, UnknownHostException {
+        HttpProxySocketProvider socket = new HttpProxySocketProvider(host,
+                port, proxyHost, proxyPort);
+        int status;
+        String providerDetail;
+
+        try {
+            InputStream in = socket.getInputStream();
+            OutputStream out = socket.getOutputStream();
+            HttpRequest request = new HttpRequest();
+
+            if (protocol == null) {
+                protocol = "";
+            }
+
+            request.setHeaderBegin("CONNECT " + protocol + host + ":" + port +
+                " HTTP/1.0");
+            request.setHeaderField("User-Agent", userAgent);
+            request.setHeaderField("Pragma", "No-Cache");
+            request.setHeaderField("Proxy-Connection", "Keep-Alive");
+            out.write(request.toString().getBytes());
+            out.flush();
+            socket.responseHeader = new HttpResponse(in);
+            providerDetail = socket.responseHeader.getHeaderField("server");
+
+            if (socket.responseHeader.getStatus() == 407) {
+                String realm = socket.responseHeader.getAuthenticationRealm();
+                String method = socket.responseHeader.getAuthenticationMethod();
+
+                if (realm == null) {
+                    realm = "";
+                }
+
+                if (method.equalsIgnoreCase("basic")) {
+                    socket.close();
+                    socket = new HttpProxySocketProvider(host, port, proxyHost,
+                            proxyPort);
+                    in = socket.getInputStream();
+                    out = socket.getOutputStream();
+                    request.setBasicAuthentication(username, password);
+                    out.write(request.toString().getBytes());
+                    out.flush();
+                    socket.responseHeader = new HttpResponse(in);
+                } else if (method.equalsIgnoreCase("digest")) {
+                    throw new IOException(
+                        "Digest authentication is not supported");
+                } else {
+                    throw new IOException("'" + method + "' is not supported");
+                }
+            }
+
+            status = socket.responseHeader.getStatus();
+        } catch (SocketException e) {
+            throw new SocketException("Error communicating with proxy server " +
+                proxyHost + ":" + proxyPort + " (" + e.getMessage() + ")");
+        }
+
+        if ((status < 200) || (status > 299)) {
+            throw new IOException("Proxy tunnel setup failed: " +
+                socket.responseHeader.getStartLine());
+        }
+
+        socket.providerDetail = providerDetail;
+
+        return socket;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String toString() {
+        return "HTTPProxySocket [Proxy IP=" + getInetAddress() +
+        ",Proxy Port=" + getPort() + ",localport=" + getLocalPort() +
+        "Remote Host=" + remoteHost + "Remote Port=" +
+        String.valueOf(remotePort) + "]";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public HttpHeader getResponseHeader() {
+        return responseHeader;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getProviderDetail() {
+        return providerDetail;
+    }
+
+    /*public boolean isConnected() {
+       return true;
+     }*/
+}
diff --git a/src/com/sshtools/j2ssh/net/HttpRequest.java b/src/com/sshtools/j2ssh/net/HttpRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..4dc0d80ef899d81cea5c24519ff64d7767bf14e9
--- /dev/null
+++ b/src/com/sshtools/j2ssh/net/HttpRequest.java
@@ -0,0 +1,65 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.net;
+
+import com.sshtools.j2ssh.util.Base64;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class HttpRequest extends HttpHeader {
+    /**
+     * Creates a new HttpRequest object.
+     */
+    public HttpRequest() {
+        super();
+    }
+
+    /**
+     *
+     *
+     * @param begin
+     */
+    public void setHeaderBegin(String begin) {
+        this.begin = begin;
+    }
+
+    /**
+     *
+     *
+     * @param username
+     * @param password
+     */
+    public void setBasicAuthentication(String username, String password) {
+        String str = username + ":" + password;
+        setHeaderField("Proxy-Authorization",
+            "Basic " + Base64.encodeBytes(str.getBytes(), true));
+    }
+}
diff --git a/src/com/sshtools/j2ssh/net/HttpResponse.java b/src/com/sshtools/j2ssh/net/HttpResponse.java
new file mode 100644
index 0000000000000000000000000000000000000000..c3397ed7ea4f982fa3511b84ff5bd843f8353773
--- /dev/null
+++ b/src/com/sshtools/j2ssh/net/HttpResponse.java
@@ -0,0 +1,151 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.net;
+
+import java.io.*;
+
+import java.util.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class HttpResponse extends HttpHeader {
+    private String version;
+    private int status;
+    private String reason;
+
+    /**
+     * Creates a new HttpResponse object.
+     *
+     * @param input
+     *
+     * @throws IOException
+     */
+    public HttpResponse(InputStream input) throws IOException {
+        begin = readLine(input);
+
+        while (begin.trim().length() == 0) {
+            begin = readLine(input);
+        }
+
+        processResponse();
+        processHeaderFields(input);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getVersion() {
+        return version;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getStatus() {
+        return status;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getReason() {
+        return reason;
+    }
+
+    private void processResponse() throws IOException {
+        StringTokenizer tokens = new StringTokenizer(begin, white_SPACE, false);
+
+        try {
+            version = tokens.nextToken();
+            status = Integer.parseInt(tokens.nextToken());
+            reason = tokens.nextToken();
+        } catch (NoSuchElementException e) {
+            throw new IOException("Failed to read HTTP repsonse header");
+        } catch (NumberFormatException e) {
+            throw new IOException("Failed to read HTTP resposne header");
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getAuthenticationMethod() {
+        String auth = getHeaderField("Proxy-Authenticate");
+        String method = null;
+
+        if (auth != null) {
+            int n = auth.indexOf(' ');
+            method = auth.substring(0, n);
+        }
+
+        return method;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getAuthenticationRealm() {
+        String auth = getHeaderField("Proxy-Authenticate");
+        String realm = null;
+
+        if (auth != null) {
+            int l;
+            int r = auth.indexOf('=');
+
+            while (r >= 0) {
+                l = auth.lastIndexOf(' ', r);
+                realm = auth.substring(l + 1, r);
+
+                if (realm.equalsIgnoreCase("realm")) {
+                    l = r + 2;
+                    r = auth.indexOf('"', l);
+                    realm = auth.substring(l, r);
+
+                    break;
+                }
+
+                r = auth.indexOf('=', r + 1);
+            }
+        }
+
+        return realm;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/net/SocketTransportProvider.java b/src/com/sshtools/j2ssh/net/SocketTransportProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..034ded634bba3e3a3d70624418d2a7f057ffaac4
--- /dev/null
+++ b/src/com/sshtools/j2ssh/net/SocketTransportProvider.java
@@ -0,0 +1,65 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.net;
+
+import java.io.IOException;
+
+import java.net.Socket;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class SocketTransportProvider extends Socket implements TransportProvider {
+    /**
+     * Creates a new SocketTransportProvider object.
+     *
+     * @param host
+     * @param port
+     *
+     * @throws IOException
+     */
+    protected SocketTransportProvider(String host, int port)
+        throws IOException {
+        super(host, port);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getProviderDetail() {
+        return toString(); //getRemoteSocketAddress().toString();
+    }
+
+    /*public boolean isConnected() {
+       return true;
+     }*/
+}
diff --git a/src/com/sshtools/j2ssh/net/SocksProxySocket.java b/src/com/sshtools/j2ssh/net/SocksProxySocket.java
new file mode 100644
index 0000000000000000000000000000000000000000..79c01b6e872c832c168c67106dd9e84144d394b2
--- /dev/null
+++ b/src/com/sshtools/j2ssh/net/SocksProxySocket.java
@@ -0,0 +1,377 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.net;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class SocksProxySocket extends Socket implements TransportProvider {
+    /**  */
+    public static final int SOCKS4 = 0x04;
+
+    /**  */
+    public static final int SOCKS5 = 0x05;
+    private static final int CONNECT = 0x01;
+    private static final int NULL_TERMINATION = 0x00;
+    private final static String[] SOCKSV5_ERROR = {
+        "Success", "General SOCKS server failure",
+        "Connection not allowed by ruleset", "Network unreachable",
+        "Host unreachable", "Connection refused", "TTL expired",
+        "Command not supported", "Address type not supported"
+    };
+    private final static String[] SOCKSV4_ERROR = {
+        "Request rejected or failed",
+        "SOCKS server cannot connect to identd on the client",
+        "The client program and identd report different user-ids"
+    };
+    private String proxyHost;
+    private int proxyPort;
+    private String remoteHost;
+    private int remotePort;
+    private String providerDetail;
+
+    private SocksProxySocket(String remoteHost, int remotePort,
+        String proxyHost, int proxyPort)
+        throws IOException, UnknownHostException {
+        super(proxyHost, proxyPort);
+        this.proxyHost = proxyHost;
+        this.proxyPort = proxyPort;
+        this.remoteHost = remoteHost;
+        this.remotePort = remotePort;
+    }
+
+    /**
+     *
+     *
+     * @param remoteHost
+     * @param remotePort
+     * @param proxyHost
+     * @param proxyPort
+     * @param userId
+     *
+     * @return
+     *
+     * @throws IOException
+     * @throws UnknownHostException
+     * @throws SocketException
+     */
+    public static SocksProxySocket connectViaSocks4Proxy(String remoteHost,
+        int remotePort, String proxyHost, int proxyPort, String userId)
+        throws IOException, UnknownHostException {
+        SocksProxySocket proxySocket = new SocksProxySocket(remoteHost,
+                remotePort, proxyHost, proxyPort);
+
+        try {
+            InputStream proxyIn = proxySocket.getInputStream();
+            OutputStream proxyOut = proxySocket.getOutputStream();
+            InetAddress hostAddr = InetAddress.getByName(remoteHost);
+            proxyOut.write(SOCKS4);
+            proxyOut.write(CONNECT);
+            proxyOut.write((remotePort >>> 8) & 0xff);
+            proxyOut.write(remotePort & 0xff);
+            proxyOut.write(hostAddr.getAddress());
+            proxyOut.write(userId.getBytes());
+            proxyOut.write(NULL_TERMINATION);
+            proxyOut.flush();
+
+            int res = proxyIn.read();
+
+            if (res == -1) {
+                throw new IOException("SOCKS4 server " + proxyHost + ":" +
+                    proxyPort + " disconnected");
+            }
+
+            if (res != 0x00) {
+                throw new IOException("Invalid response from SOCKS4 server (" +
+                    res + ") " + proxyHost + ":" + proxyPort);
+            }
+
+            int code = proxyIn.read();
+
+            if (code != 90) {
+                if ((code > 90) && (code < 93)) {
+                    throw new IOException(
+                        "SOCKS4 server unable to connect, reason: " +
+                        SOCKSV4_ERROR[code - 91]);
+                } else {
+                    throw new IOException(
+                        "SOCKS4 server unable to connect, reason: " + code);
+                }
+            }
+
+            byte[] data = new byte[6];
+
+            if (proxyIn.read(data, 0, 6) != 6) {
+                throw new IOException(
+                    "SOCKS4 error reading destination address/port");
+            }
+
+            proxySocket.providerDetail = data[2] + "." + data[3] + "." +
+                data[4] + "." + data[5] + ":" + ((data[0] << 8) | data[1]);
+        } catch (SocketException e) {
+            throw new SocketException("Error communicating with SOCKS4 server " +
+                proxyHost + ":" + proxyPort + ", " + e.getMessage());
+        }
+
+        return proxySocket;
+    }
+
+    /**
+     *
+     *
+     * @param remoteHost
+     * @param remotePort
+     * @param proxyHost
+     * @param proxyPort
+     * @param localLookup
+     * @param username
+     * @param password
+     *
+     * @return
+     *
+     * @throws IOException
+     * @throws UnknownHostException
+     * @throws SocketException
+     */
+    public static SocksProxySocket connectViaSocks5Proxy(String remoteHost,
+        int remotePort, String proxyHost, int proxyPort, boolean localLookup,
+        String username, String password)
+        throws IOException, UnknownHostException {
+        SocksProxySocket proxySocket = new SocksProxySocket(remoteHost,
+                remotePort, proxyHost, proxyPort);
+
+        try {
+            InputStream proxyIn = proxySocket.getInputStream();
+            OutputStream proxyOut = proxySocket.getOutputStream();
+            byte[] request = {
+                (byte) SOCKS5, (byte) 0x02, (byte) 0x00, (byte) 0x02
+            };
+            byte[] reply = new byte[2];
+            proxyOut.write(request);
+            proxyOut.flush();
+
+            int res = proxyIn.read();
+
+            if (res == -1) {
+                throw new IOException("SOCKS5 server " + proxyHost + ":" +
+                    proxyPort + " disconnected");
+            }
+
+            if (res != 0x05) {
+                throw new IOException("Invalid response from SOCKS5 server (" +
+                    res + ") " + proxyHost + ":" + proxyPort);
+            }
+
+            int method = proxyIn.read();
+
+            switch (method) {
+            case 0x00:
+                break;
+
+            case 0x02:
+                performAuthentication(proxyIn, proxyOut, username, password,
+                    proxyHost, proxyPort);
+
+                break;
+
+            default:
+                throw new IOException(
+                    "SOCKS5 server does not support our authentication methods");
+            }
+
+            if (localLookup) {
+                InetAddress hostAddr;
+
+                try {
+                    hostAddr = InetAddress.getByName(remoteHost);
+                } catch (UnknownHostException e) {
+                    throw new IOException("Can't do local lookup on: " +
+                        remoteHost + ", try socks5 without local lookup");
+                }
+
+                request = new byte[] {
+                        (byte) SOCKS5, (byte) 0x01, (byte) 0x00, (byte) 0x01
+                    };
+                proxyOut.write(request);
+                proxyOut.write(hostAddr.getAddress());
+            } else {
+                request = new byte[] {
+                        (byte) SOCKS5, (byte) 0x01, (byte) 0x00, (byte) 0x03
+                    };
+                proxyOut.write(request);
+                proxyOut.write(remoteHost.length());
+                proxyOut.write(remoteHost.getBytes());
+            }
+
+            proxyOut.write((remotePort >>> 8) & 0xff);
+            proxyOut.write(remotePort & 0xff);
+            proxyOut.flush();
+            res = proxyIn.read();
+
+            if (res != 0x05) {
+                throw new IOException("Invalid response from SOCKS5 server (" +
+                    res + ") " + proxyHost + ":" + proxyPort);
+            }
+
+            int status = proxyIn.read();
+
+            if (status != 0x00) {
+                if ((status > 0) && (status < 9)) {
+                    throw new IOException(
+                        "SOCKS5 server unable to connect, reason: " +
+                        SOCKSV5_ERROR[status]);
+                } else {
+                    throw new IOException(
+                        "SOCKS5 server unable to connect, reason: " + status);
+                }
+            }
+
+            proxyIn.read();
+
+            int aType = proxyIn.read();
+            byte[] data = new byte[255];
+
+            switch (aType) {
+            case 0x01:
+
+                if (proxyIn.read(data, 0, 4) != 4) {
+                    throw new IOException("SOCKS5 error reading address");
+                }
+
+                proxySocket.providerDetail = data[0] + "." + data[1] + "." +
+                    data[2] + "." + data[3];
+
+                break;
+
+            case 0x03:
+
+                int n = proxyIn.read();
+
+                if (proxyIn.read(data, 0, n) != n) {
+                    throw new IOException("SOCKS5 error reading address");
+                }
+
+                proxySocket.providerDetail = new String(data);
+
+                break;
+
+            default:
+                throw new IOException("SOCKS5 gave unsupported address type: " +
+                    aType);
+            }
+
+            if (proxyIn.read(data, 0, 2) != 2) {
+                throw new IOException("SOCKS5 error reading port");
+            }
+
+            proxySocket.providerDetail += (":" + ((data[0] << 8) | data[1]));
+        } catch (SocketException e) {
+            throw new SocketException("Error communicating with SOCKS5 server " +
+                proxyHost + ":" + proxyPort + ", " + e.getMessage());
+        }
+
+        return proxySocket;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getProviderDetail() {
+        return providerDetail;
+    }
+
+    private static void performAuthentication(InputStream proxyIn,
+        OutputStream proxyOut, String username, String password,
+        String proxyHost, int proxyPort) throws IOException {
+        proxyOut.write(0x01);
+        proxyOut.write(username.length());
+        proxyOut.write(username.getBytes());
+        proxyOut.write(password.length());
+        proxyOut.write(password.getBytes());
+
+        int res = proxyIn.read();
+
+        if ((res != 0x01) && (res != 0x05)) {
+            throw new IOException("Invalid response from SOCKS5 server (" +
+                res + ") " + proxyHost + ":" + proxyPort);
+        }
+
+        if (proxyIn.read() != 0x00) {
+            throw new IOException("Invalid username/password for SOCKS5 server");
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String toString() {
+        return "SocksProxySocket[addr=" + getInetAddress() + ",port=" +
+        getPort() + ",localport=" + getLocalPort() + "]";
+    }
+
+    /**
+     *
+     *
+     * @param remoteHost
+     * @param remotePort
+     * @param proxyHost
+     * @param proxyPort
+     * @param username
+     * @param password
+     *
+     * @return
+     *
+     * @throws IOException
+     * @throws UnknownHostException
+     */
+    public static SocksProxySocket connectViaSocks5Proxy(String remoteHost,
+        int remotePort, String proxyHost, int proxyPort, String username,
+        String password) throws IOException, UnknownHostException {
+        return connectViaSocks5Proxy(remoteHost, remotePort, proxyHost,
+            proxyPort, false, username, password);
+    }
+
+    /*public boolean isConnected() {
+     return true;
+     }*/
+}
diff --git a/src/com/sshtools/j2ssh/net/TransportProvider.java b/src/com/sshtools/j2ssh/net/TransportProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..d47a95394fa5018659964f4c78874c0a28efeaaf
--- /dev/null
+++ b/src/com/sshtools/j2ssh/net/TransportProvider.java
@@ -0,0 +1,63 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.net;
+
+import java.io.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public interface TransportProvider {
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    public void close() throws IOException;
+
+    //public boolean isConnected();
+    public InputStream getInputStream() throws IOException;
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public OutputStream getOutputStream() throws IOException;
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getProviderDetail();
+}
diff --git a/src/com/sshtools/j2ssh/net/TransportProviderFactory.java b/src/com/sshtools/j2ssh/net/TransportProviderFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..c5d3b85c7500ed7605692f1e35acbb73e04cf46e
--- /dev/null
+++ b/src/com/sshtools/j2ssh/net/TransportProviderFactory.java
@@ -0,0 +1,86 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.net;
+
+import com.sshtools.j2ssh.configuration.SshConnectionProperties;
+
+import java.io.IOException;
+
+import java.net.UnknownHostException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class TransportProviderFactory {
+    /**
+     *
+     *
+     * @param properties
+     * @param socketTimeout
+     *
+     * @return
+     *
+     * @throws UnknownHostException
+     * @throws IOException
+     */
+    public static TransportProvider connectTransportProvider(
+        SshConnectionProperties properties /*, int connectTimeout*/,
+        int socketTimeout) throws UnknownHostException, IOException {
+        if (properties.getTransportProvider() == SshConnectionProperties.USE_HTTP_PROXY) {
+            return HttpProxySocketProvider.connectViaProxy(properties.getHost(),
+                properties.getPort(), properties.getProxyHost(),
+                properties.getProxyPort(), properties.getProxyUsername(),
+                properties.getProxyPassword(), "J2SSH");
+        } else if (properties.getTransportProvider() == SshConnectionProperties.USE_SOCKS4_PROXY) {
+            return SocksProxySocket.connectViaSocks4Proxy(properties.getHost(),
+                properties.getPort(), properties.getProxyHost(),
+                properties.getProxyPort(), properties.getProxyUsername());
+        } else if (properties.getTransportProvider() == SshConnectionProperties.USE_SOCKS5_PROXY) {
+            return SocksProxySocket.connectViaSocks5Proxy(properties.getHost(),
+                properties.getPort(), properties.getProxyHost(),
+                properties.getProxyPort(), properties.getProxyUsername(),
+                properties.getProxyPassword());
+        } else {
+            // No proxy just attempt a standard socket connection
+
+            /*SocketTransportProvider socket = new SocketTransportProvider();
+             socket.setSoTimeout(socketTimeout);
+             socket.connect(new InetSocketAddress(properties.getHost(),
+                                     properties.getPort()),
+               connectTimeout);*/
+            SocketTransportProvider socket = new SocketTransportProvider(properties.getHost(),
+                    properties.getPort());
+            socket.setTcpNoDelay(true);
+            socket.setSoTimeout(socketTimeout);
+
+            return socket;
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/openssh/DSAKeyInfo.java b/src/com/sshtools/j2ssh/openssh/DSAKeyInfo.java
new file mode 100644
index 0000000000000000000000000000000000000000..585cc4ae00566f04cf0635d44d1c14f15608e507
--- /dev/null
+++ b/src/com/sshtools/j2ssh/openssh/DSAKeyInfo.java
@@ -0,0 +1,202 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.openssh;
+
+import com.sshtools.j2ssh.util.SimpleASNReader;
+import com.sshtools.j2ssh.util.SimpleASNWriter;
+
+import java.io.IOException;
+
+import java.math.BigInteger;
+
+import java.security.spec.DSAPrivateKeySpec;
+import java.security.spec.DSAPublicKeySpec;
+import java.security.spec.KeySpec;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class DSAKeyInfo implements KeyInfo {
+    private BigInteger p;
+    private BigInteger q;
+    private BigInteger g;
+    private BigInteger x;
+    private BigInteger y;
+
+    /**
+     * Creates a new DSAKeyInfo object.
+     *
+     * @param p
+     * @param q
+     * @param g
+     * @param x
+     * @param y
+     */
+    public DSAKeyInfo(BigInteger p, BigInteger q, BigInteger g, BigInteger x,
+        BigInteger y) {
+        this.p = p;
+        this.q = q;
+        this.g = g;
+        this.x = x;
+        this.y = y;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public BigInteger getG() {
+        return g;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public BigInteger getP() {
+        return p;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public BigInteger getQ() {
+        return q;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public BigInteger getX() {
+        return x;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public BigInteger getY() {
+        return y;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public KeySpec getPrivateKeySpec() {
+        return new DSAPrivateKeySpec(x, p, q, g);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public KeySpec getPublicKeySpec() {
+        return new DSAPublicKeySpec(y, p, q, g);
+    }
+
+    /**
+     *
+     *
+     * @param asn
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public static DSAKeyInfo getDSAKeyInfo(SimpleASNReader asn)
+        throws IOException {
+        asn.assertByte(0x30); // SEQUENCE
+
+        int length = asn.getLength();
+        asn.assertByte(0x02); // INTEGER (version)
+
+        byte[] version = asn.getData();
+        asn.assertByte(0x02); // INTEGER (p)
+
+        byte[] paramP = asn.getData();
+        asn.assertByte(0x02); // INTEGER (q)
+
+        byte[] paramQ = asn.getData();
+        asn.assertByte(0x02); // INTEGER (g)
+
+        byte[] paramG = asn.getData();
+        asn.assertByte(0x02); // INTEGER (y)
+
+        byte[] paramY = asn.getData();
+        asn.assertByte(0x02); // INTEGER (x)
+
+        byte[] paramX = asn.getData();
+
+        return new DSAKeyInfo(new BigInteger(paramP), new BigInteger(paramQ),
+            new BigInteger(paramG), new BigInteger(paramX),
+            new BigInteger(paramY));
+    }
+
+    /**
+     *
+     *
+     * @param asn
+     * @param keyInfo
+     */
+    public static void writeDSAKeyInfo(SimpleASNWriter asn, DSAKeyInfo keyInfo) {
+        // Write to a substream temporarily.
+        // This code needs to know the length of the substream before it can write the data from
+        // the substream to the main stream.
+        SimpleASNWriter asn2 = new SimpleASNWriter();
+        asn2.writeByte(0x02); // INTEGER (version)
+
+        byte[] version = new byte[1];
+        asn2.writeData(version);
+        asn2.writeByte(0x02); // INTEGER (p)
+        asn2.writeData(keyInfo.getP().toByteArray());
+        asn2.writeByte(0x02); // INTEGER (q)
+        asn2.writeData(keyInfo.getQ().toByteArray());
+        asn2.writeByte(0x02); // INTEGER (g)
+        asn2.writeData(keyInfo.getG().toByteArray());
+        asn2.writeByte(0x02); // INTEGER (y)
+        asn2.writeData(keyInfo.getY().toByteArray());
+        asn2.writeByte(0x02); // INTEGER (x)
+        asn2.writeData(keyInfo.getX().toByteArray());
+
+        byte[] dsaKeyEncoded = asn2.toByteArray();
+        asn.writeByte(0x30); // SEQUENCE
+        asn.writeData(dsaKeyEncoded);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/openssh/KeyInfo.java b/src/com/sshtools/j2ssh/openssh/KeyInfo.java
new file mode 100644
index 0000000000000000000000000000000000000000..4df217d007f5a35a7ddc8d01da21ab06985f71d9
--- /dev/null
+++ b/src/com/sshtools/j2ssh/openssh/KeyInfo.java
@@ -0,0 +1,41 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.openssh;
+
+import java.security.spec.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public interface KeyInfo {
+    KeySpec getPrivateKeySpec();
+
+    KeySpec getPublicKeySpec();
+}
diff --git a/src/com/sshtools/j2ssh/openssh/OpenSSHPrivateKeyFormat.java b/src/com/sshtools/j2ssh/openssh/OpenSSHPrivateKeyFormat.java
new file mode 100644
index 0000000000000000000000000000000000000000..7ba99e4849c2b75158096d4fb361006217c42fc3
--- /dev/null
+++ b/src/com/sshtools/j2ssh/openssh/OpenSSHPrivateKeyFormat.java
@@ -0,0 +1,241 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.openssh;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.publickey.InvalidSshKeyException;
+import com.sshtools.j2ssh.transport.publickey.SshPrivateKeyFormat;
+import com.sshtools.j2ssh.util.SimpleASNReader;
+import com.sshtools.j2ssh.util.SimpleASNWriter;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+
+import java.math.BigInteger;
+
+import java.security.GeneralSecurityException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class OpenSSHPrivateKeyFormat implements SshPrivateKeyFormat {
+    /**
+     * Creates a new OpenSSHPrivateKeyFormat object.
+     */
+    public OpenSSHPrivateKeyFormat() {
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getFormatType() {
+        return "OpenSSH-PrivateKey";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String toString() {
+        return getFormatType();
+    }
+
+    /**
+     *
+     *
+     * @param formattedKey
+     * @param passphrase
+     *
+     * @return
+     *
+     * @throws InvalidSshKeyException
+     */
+    public byte[] decryptKeyblob(byte[] formattedKey, String passphrase)
+        throws InvalidSshKeyException {
+        //System.err.println("Decrypting key using passphrase " + passphrase);
+        try {
+            Reader r = new StringReader(new String(formattedKey, "US-ASCII"));
+            PEMReader pem = new PEMReader(r);
+            byte[] payload = pem.decryptPayload(passphrase);
+            SimpleASNReader asn = new SimpleASNReader(payload);
+
+            if (PEM.DSA_PRIVATE_KEY.equals(pem.getType())) {
+                DSAKeyInfo keyInfo = DSAKeyInfo.getDSAKeyInfo(asn);
+                ByteArrayWriter baw = new ByteArrayWriter();
+                baw.writeString("ssh-dss");
+                baw.writeBigInteger(keyInfo.getP());
+                baw.writeBigInteger(keyInfo.getQ());
+                baw.writeBigInteger(keyInfo.getG());
+                baw.writeBigInteger(keyInfo.getX());
+
+                return baw.toByteArray();
+            } else if (PEM.RSA_PRIVATE_KEY.equals(pem.getType())) {
+                RSAKeyInfo keyInfo = RSAKeyInfo.getRSAKeyInfo(asn);
+                ByteArrayWriter baw = new ByteArrayWriter();
+                baw.writeString("ssh-rsa");
+                baw.writeBigInteger(keyInfo.getPublicExponent());
+                baw.writeBigInteger(keyInfo.getModulus());
+                baw.writeBigInteger(keyInfo.getPrivateExponent());
+
+                return baw.toByteArray();
+            } else {
+                throw new InvalidSshKeyException("Unsupported type: " +
+                    pem.getType());
+            }
+        } catch (GeneralSecurityException e) {
+            //e.printStackTrace();
+            throw new InvalidSshKeyException(
+                "Can't read key due to cryptography problems: " + e);
+        } catch (IOException e) {
+            //e.printStackTrace();
+            throw new InvalidSshKeyException(
+                "Can't read key due to internal IO problems: " + e);
+        }
+    }
+
+    /**
+     *
+     *
+     * @param keyblob
+     * @param passphrase
+     *
+     * @return
+     *
+     * @throws InvalidSshKeyException
+     */
+    public byte[] encryptKeyblob(byte[] keyblob, String passphrase)
+        throws InvalidSshKeyException {
+        try {
+            ByteArrayReader bar = new ByteArrayReader(keyblob);
+            String algorithm = bar.readString(); // dsa or rsa
+            byte[] payload;
+            PEMWriter pem = new PEMWriter();
+
+            if ("ssh-dss".equals(algorithm)) {
+                BigInteger p = bar.readBigInteger();
+                BigInteger q = bar.readBigInteger();
+                BigInteger g = bar.readBigInteger();
+                BigInteger x = bar.readBigInteger();
+                DSAKeyInfo keyInfo = new DSAKeyInfo(p, q, g, x, BigInteger.ZERO);
+                SimpleASNWriter asn = new SimpleASNWriter();
+                DSAKeyInfo.writeDSAKeyInfo(asn, keyInfo);
+                payload = asn.toByteArray();
+                pem.setType(PEM.DSA_PRIVATE_KEY);
+            } else if ("ssh-rsa".equals(algorithm)) {
+                BigInteger e = bar.readBigInteger();
+                BigInteger n = bar.readBigInteger();
+                BigInteger p = bar.readBigInteger();
+                RSAKeyInfo keyInfo = new RSAKeyInfo(n, p, e, BigInteger.ZERO,
+                        BigInteger.ZERO, BigInteger.ZERO, BigInteger.ZERO,
+                        BigInteger.ZERO);
+                SimpleASNWriter asn = new SimpleASNWriter();
+                RSAKeyInfo.writeRSAKeyInfo(asn, keyInfo);
+                payload = asn.toByteArray();
+                pem.setType(PEM.RSA_PRIVATE_KEY);
+            } else {
+                throw new InvalidSshKeyException(
+                    "Unsupported J2SSH algorithm: " + algorithm);
+            }
+
+            pem.setPayload(payload);
+            pem.encryptPayload(payload, passphrase);
+
+            StringWriter w = new StringWriter();
+            pem.write(w);
+
+            return w.toString().getBytes("US-ASCII");
+        } catch (GeneralSecurityException e) {
+            //e.printStackTrace();
+            throw new InvalidSshKeyException(
+                "Can't read key due to cryptography problems: " + e);
+        } catch (IOException e) {
+            //e.printStackTrace();
+            throw new InvalidSshKeyException(
+                "Can't read key due to internal IO problems: " + e);
+        }
+    }
+
+    /**
+     *
+     *
+     * @param formattedKey
+     *
+     * @return
+     */
+    public boolean isFormatted(byte[] formattedKey) {
+        try {
+            Reader r = new StringReader(new String(formattedKey, "US-ASCII"));
+            PEMReader pem = new PEMReader(r);
+
+            return true;
+        } catch (IOException e) {
+            return false;
+        }
+    }
+
+    /**
+     *
+     *
+     * @param formattedKey
+     *
+     * @return
+     */
+    public boolean isPassphraseProtected(byte[] formattedKey) {
+        try {
+            Reader r = new StringReader(new String(formattedKey, "US-ASCII"));
+            PEMReader pem = new PEMReader(r);
+
+            return pem.getHeader().containsKey("DEK-Info");
+        } catch (IOException e) {
+            return true;
+        }
+    }
+
+    /**
+     *
+     *
+     * @param algorithm
+     *
+     * @return
+     */
+    public boolean supportsAlgorithm(String algorithm) {
+        if ("ssh-dss".equals(algorithm) || "ssh-rsa".equals(algorithm)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/openssh/PEM.java b/src/com/sshtools/j2ssh/openssh/PEM.java
new file mode 100644
index 0000000000000000000000000000000000000000..cdc6cd273c105760cbf7197c742322f97c0baa99
--- /dev/null
+++ b/src/com/sshtools/j2ssh/openssh/PEM.java
@@ -0,0 +1,121 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.openssh;
+
+import java.io.*;
+
+import java.security.*;
+import java.security.spec.*;
+
+import javax.crypto.*;
+import javax.crypto.spec.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class PEM {
+    /**  */
+    public final static String DSA_PRIVATE_KEY = "DSA PRIVATE KEY";
+
+    /**  */
+    public final static String RSA_PRIVATE_KEY = "RSA PRIVATE KEY";
+
+    /**  */
+    protected final static String PEM_BOUNDARY = "-----";
+
+    /**  */
+    protected final static String PEM_BEGIN = PEM_BOUNDARY + "BEGIN ";
+
+    /**  */
+    protected final static String PEM_END = PEM_BOUNDARY + "END ";
+
+    /**  */
+    protected final static int MAX_LINE_LENGTH = 75;
+
+    /**  */
+    protected final static char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
+    private final static int MD5_HASH_BYTES = 0x10;
+
+    /**
+     *
+     *
+     * @param passphrase
+     * @param iv
+     * @param keySize
+     *
+     * @return
+     *
+     * @throws NoSuchAlgorithmException
+     * @throws InvalidKeySpecException
+     * @throws Error
+     */
+    protected static SecretKey getKeyFromPassphrase(String passphrase,
+        byte[] iv, int keySize)
+        throws NoSuchAlgorithmException, InvalidKeySpecException {
+        byte[] passphraseBytes;
+
+        try {
+            passphraseBytes = passphrase.getBytes("US-ASCII");
+        } catch (UnsupportedEncodingException e) {
+            throw new Error(
+                "Mandatory US-ASCII character encoding is not supported by the VM");
+        }
+
+        /*
+           hash is MD5
+           h(0) <- hash(passphrase, iv);
+           h(n) <- hash(h(n-1), passphrase, iv);
+           key <- (h(0),...,h(n))[0,..,key.length];
+         */
+        MessageDigest hash = MessageDigest.getInstance("MD5");
+        byte[] key = new byte[keySize];
+        int hashesSize = keySize & 0xfffffff0;
+
+        if ((keySize & 0xf) != 0) {
+            hashesSize += MD5_HASH_BYTES;
+        }
+
+        byte[] hashes = new byte[hashesSize];
+        byte[] previous;
+
+        for (int index = 0; (index + MD5_HASH_BYTES) <= hashes.length;
+                hash.update(previous, 0, previous.length)) {
+            hash.update(passphraseBytes, 0, passphraseBytes.length);
+            hash.update(iv, 0, iv.length);
+            previous = hash.digest();
+            System.arraycopy(previous, 0, hashes, index, previous.length);
+            index += previous.length;
+        }
+
+        System.arraycopy(hashes, 0, key, 0, key.length);
+
+        return new SecretKeySpec(key, "DESede");
+    }
+}
diff --git a/src/com/sshtools/j2ssh/openssh/PEMReader.java b/src/com/sshtools/j2ssh/openssh/PEMReader.java
new file mode 100644
index 0000000000000000000000000000000000000000..d4f1789958926385aaaf36d274875d2f5d235ec3
--- /dev/null
+++ b/src/com/sshtools/j2ssh/openssh/PEMReader.java
@@ -0,0 +1,215 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.openssh;
+
+import com.sshtools.j2ssh.util.Base64;
+
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.io.Reader;
+
+import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class PEMReader extends PEM {
+    private LineNumberReader reader;
+    private String type;
+    private Map header;
+    private byte[] payload;
+
+    /**
+     * Creates a new PEMReader object.
+     *
+     * @param r
+     *
+     * @throws IOException
+     */
+    public PEMReader(Reader r) throws IOException {
+        reader = new LineNumberReader(r);
+        read();
+    }
+
+    private void read() throws IOException {
+        String line;
+
+        while ((line = reader.readLine()) != null) {
+            if (line.startsWith(PEM_BOUNDARY) && line.endsWith(PEM_BOUNDARY)) {
+                if (line.startsWith(PEM_BEGIN)) {
+                    type = line.substring(PEM_BEGIN.length(),
+                            line.length() - PEM_BOUNDARY.length());
+
+                    break;
+                } else {
+                    throw new IOException("Invalid PEM boundary at line " +
+                        reader.getLineNumber() + ": " + line);
+                }
+            }
+        }
+
+        header = new HashMap();
+
+        while ((line = reader.readLine()) != null) {
+            int colon = line.indexOf(':');
+
+            if (colon == -1) {
+                break;
+            }
+
+            String key = line.substring(0, colon).trim();
+
+            if (line.endsWith("\\")) {
+                String v = line.substring(colon + 1, line.length() - 1).trim();
+
+                // multi-line value
+                StringBuffer value = new StringBuffer(v);
+
+                while ((line = reader.readLine()) != null) {
+                    if (line.endsWith("\\")) {
+                        value.append(" ").append(line.substring(0,
+                                line.length() - 1).trim());
+                    } else {
+                        value.append(" ").append(line.trim());
+
+                        break;
+                    }
+                }
+            } else {
+                String value = line.substring(colon + 1).trim();
+                header.put(key, value);
+            }
+        }
+
+        // first line that is not part of the header
+        // could be an empty line, but if there is no header and the body begins straight after the -----
+        // then this line contains data
+        if (line == null) {
+            throw new IOException(
+                "The key format is invalid! OpenSSH formatted keys must begin with -----BEGIN RSA or -----BEGIN DSA");
+        }
+
+        StringBuffer body = new StringBuffer(line);
+
+        while ((line = reader.readLine()) != null) {
+            if (line.startsWith(PEM_BOUNDARY) && line.endsWith(PEM_BOUNDARY)) {
+                if (line.startsWith(PEM_END + type)) {
+                    break;
+                } else {
+                    throw new IOException("Invalid PEM end boundary at line " +
+                        reader.getLineNumber() + ": " + line);
+                }
+            }
+
+            body.append(line);
+        }
+
+        payload = Base64.decode(body.toString());
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public Map getHeader() {
+        return header;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getPayload() {
+        return payload;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getType() {
+        return type;
+    }
+
+    /**
+     *
+     *
+     * @param passphrase
+     *
+     * @return
+     *
+     * @throws GeneralSecurityException
+     * @throws NoSuchAlgorithmException
+     */
+    public byte[] decryptPayload(String passphrase)
+        throws GeneralSecurityException {
+        String dekInfo = (String) header.get("DEK-Info");
+
+        if (dekInfo != null) {
+            int comma = dekInfo.indexOf(',');
+            String keyAlgorithm = dekInfo.substring(0, comma);
+
+            if (!"DES-EDE3-CBC".equals(keyAlgorithm)) {
+                throw new NoSuchAlgorithmException(
+                    "Unsupported passphrase algorithm: " + keyAlgorithm);
+            }
+
+            String ivString = dekInfo.substring(comma + 1);
+            byte[] iv = new byte[ivString.length() / 2];
+
+            for (int i = 0; i < ivString.length(); i += 2) {
+                iv[i / 2] = (byte) Integer.parseInt(ivString.substring(i, i +
+                            2), 16);
+            }
+
+            Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding");
+            SecretKey key = getKeyFromPassphrase(passphrase, iv, 24);
+            cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
+
+            byte[] plain = new byte[payload.length];
+            cipher.update(payload, 0, payload.length, plain, 0);
+
+            return plain;
+        } else {
+            return payload;
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/openssh/PEMWriter.java b/src/com/sshtools/j2ssh/openssh/PEMWriter.java
new file mode 100644
index 0000000000000000000000000000000000000000..c25bfef100f651a360f4ef4598ac63e9d3630732
--- /dev/null
+++ b/src/com/sshtools/j2ssh/openssh/PEMWriter.java
@@ -0,0 +1,190 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.openssh;
+
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+import com.sshtools.j2ssh.util.Base64;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Writer;
+
+import java.security.GeneralSecurityException;
+import java.security.SecureRandom;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class PEMWriter extends PEM {
+    private String type;
+    private Map header = new HashMap();
+    private byte[] payload;
+
+    /**
+     * Creates a new PEMWriter object.
+     */
+    public PEMWriter() {
+    }
+
+    /**
+     *
+     *
+     * @param w
+     *
+     * @throws IOException
+     */
+    public void write(Writer w) throws IOException {
+        PrintWriter writer = new PrintWriter(w, true);
+        writer.println(PEM_BEGIN + type + PEM_BOUNDARY);
+
+        if (!header.isEmpty()) {
+            for (Iterator i = header.keySet().iterator(); i.hasNext();) {
+                String key = (String) i.next();
+                String value = (String) header.get(key);
+                writer.print(key + ": ");
+
+                if ((key.length() + value.length() + 2) > MAX_LINE_LENGTH) {
+                    int offset = Math.max(MAX_LINE_LENGTH - key.length() - 2, 0);
+                    writer.println(value.substring(0, offset) + "\\");
+
+                    for (; offset < value.length();
+                            offset += MAX_LINE_LENGTH) {
+                        if ((offset + MAX_LINE_LENGTH) >= value.length()) {
+                            writer.println(value.substring(offset));
+                        } else {
+                            writer.println(value.substring(offset,
+                                    offset + MAX_LINE_LENGTH) + "\\");
+                        }
+                    }
+                } else {
+                    writer.println(value);
+                }
+            }
+
+            writer.println();
+        }
+
+        writer.println(Base64.encodeBytes(payload, false));
+        writer.println(PEM_END + type + PEM_BOUNDARY);
+    }
+
+    /**
+     *
+     *
+     * @param payload
+     * @param passphrase
+     *
+     * @throws GeneralSecurityException
+     */
+    public void encryptPayload(byte[] payload, String passphrase)
+        throws GeneralSecurityException {
+        if ((passphrase == null) || (passphrase.length() == 0)) {
+            // Simple case: no passphrase means no encryption of the private key
+            setPayload(payload);
+
+            return;
+        }
+
+        SecureRandom rnd = ConfigurationLoader.getRND();
+        byte[] iv = new byte[8];
+        rnd.nextBytes(iv);
+
+        StringBuffer ivString = new StringBuffer(16);
+
+        for (int i = 0; i < iv.length; i++) {
+            ivString.append(HEX_CHARS[(iv[i] & 0xff) >> 4]);
+            ivString.append(HEX_CHARS[iv[i] & 0x0f]);
+        }
+
+        header.put("DEK-Info", "DES-EDE3-CBC," + ivString);
+        header.put("Proc-Type", "4,ENCRYPTED");
+
+        Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding");
+        SecretKey key = getKeyFromPassphrase(passphrase, iv, 24);
+        cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
+
+        byte[] encrypted = new byte[payload.length];
+        cipher.update(payload, 0, payload.length, encrypted, 0);
+        setPayload(encrypted);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public Map getHeader() {
+        return header;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getPayload() {
+        return payload;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getType() {
+        return type;
+    }
+
+    /**
+     *
+     *
+     * @param bs
+     */
+    public void setPayload(byte[] bs) {
+        payload = bs;
+    }
+
+    /**
+     *
+     *
+     * @param string
+     */
+    public void setType(String string) {
+        type = string;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/openssh/RSAKeyInfo.java b/src/com/sshtools/j2ssh/openssh/RSAKeyInfo.java
new file mode 100644
index 0000000000000000000000000000000000000000..5e2fff41c66896d65a02eed07d6f7a976526353e
--- /dev/null
+++ b/src/com/sshtools/j2ssh/openssh/RSAKeyInfo.java
@@ -0,0 +1,267 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.openssh;
+
+import com.sshtools.j2ssh.util.SimpleASNReader;
+import com.sshtools.j2ssh.util.SimpleASNWriter;
+
+import java.io.IOException;
+
+import java.math.BigInteger;
+
+import java.security.spec.KeySpec;
+import java.security.spec.RSAPrivateKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class RSAKeyInfo implements KeyInfo {
+    private BigInteger modulus;
+    private BigInteger publicExponent;
+    private BigInteger privateExponent;
+    private BigInteger primeP;
+    private BigInteger primeQ;
+    private BigInteger primeExponentP;
+    private BigInteger primeExponentQ;
+    private BigInteger crtCoefficient;
+
+    /**
+     * Creates a new RSAKeyInfo object.
+     *
+     * @param modulus
+     * @param publicExponent
+     * @param privateExponent
+     * @param primeP
+     * @param primeQ
+     * @param primeExponentP
+     * @param primeExponentQ
+     * @param crtCoefficient
+     */
+    public RSAKeyInfo(BigInteger modulus, BigInteger publicExponent,
+        BigInteger privateExponent, BigInteger primeP, BigInteger primeQ,
+        BigInteger primeExponentP, BigInteger primeExponentQ,
+        BigInteger crtCoefficient) {
+        this.modulus = modulus;
+        this.publicExponent = publicExponent;
+        this.privateExponent = privateExponent;
+        this.primeP = primeP;
+        this.primeQ = primeQ;
+        this.primeExponentP = primeExponentP;
+        this.primeExponentQ = primeExponentQ;
+        this.crtCoefficient = crtCoefficient;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public KeySpec getPrivateKeySpec() {
+        return new RSAPrivateKeySpec(modulus, privateExponent);
+
+        //        return new RSAPrivateCrtKeySpec(
+        //            modulus,
+        //            publicExponent,
+        //            privateExponent,
+        //            primeP,
+        //            primeQ,
+        //            primeExponentP,
+        //            primeExponentQ,
+        //            crtCoefficient);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public KeySpec getPublicKeySpec() {
+        return new RSAPublicKeySpec(modulus, publicExponent);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public BigInteger getCrtCoefficient() {
+        return crtCoefficient;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public BigInteger getModulus() {
+        return modulus;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public BigInteger getPrimeExponentP() {
+        return primeExponentP;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public BigInteger getPrimeExponentQ() {
+        return primeExponentQ;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public BigInteger getPrimeP() {
+        return primeP;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public BigInteger getPrimeQ() {
+        return primeQ;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public BigInteger getPrivateExponent() {
+        return privateExponent;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public BigInteger getPublicExponent() {
+        return publicExponent;
+    }
+
+    /**
+     *
+     *
+     * @param asn
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public static RSAKeyInfo getRSAKeyInfo(SimpleASNReader asn)
+        throws IOException {
+        asn.assertByte(0x30); // SEQUENCE
+
+        int length = asn.getLength();
+        asn.assertByte(0x02); // INTEGER (version)
+
+        byte[] version = asn.getData();
+        asn.assertByte(0x02); // INTEGER ()
+
+        byte[] modulus = asn.getData();
+        asn.assertByte(0x02); // INTEGER ()
+
+        byte[] publicExponent = asn.getData();
+        asn.assertByte(0x02); // INTEGER ()
+
+        byte[] privateExponent = asn.getData();
+        asn.assertByte(0x02); // INTEGER ()
+
+        byte[] primeP = asn.getData();
+        asn.assertByte(0x02); // INTEGER ()
+
+        byte[] primeQ = asn.getData();
+        asn.assertByte(0x02); // INTEGER ()
+
+        byte[] primeExponentP = asn.getData();
+        asn.assertByte(0x02); // INTEGER ()
+
+        byte[] primeExponentQ = asn.getData();
+        asn.assertByte(0x02); // INTEGER ()
+
+        byte[] crtCoefficient = asn.getData();
+
+        return new RSAKeyInfo(new BigInteger(modulus),
+            new BigInteger(publicExponent), new BigInteger(privateExponent),
+            new BigInteger(primeP), new BigInteger(primeQ),
+            new BigInteger(primeExponentP), new BigInteger(primeExponentQ),
+            new BigInteger(crtCoefficient));
+    }
+
+    /**
+     *
+     *
+     * @param asn
+     * @param keyInfo
+     */
+    public static void writeRSAKeyInfo(SimpleASNWriter asn, RSAKeyInfo keyInfo) {
+        // Write to a substream temporarily.
+        // This code needs to know the length of the substream before it can write the data from
+        // the substream to the main stream.
+        SimpleASNWriter asn2 = new SimpleASNWriter();
+        asn2.writeByte(0x02); // INTEGER (version)
+
+        byte[] version = new byte[1];
+        asn2.writeData(version);
+        asn2.writeByte(0x02); // INTEGER ()
+        asn2.writeData(keyInfo.getModulus().toByteArray());
+        asn2.writeByte(0x02); // INTEGER ()
+        asn2.writeData(keyInfo.getPublicExponent().toByteArray());
+        asn2.writeByte(0x02); // INTEGER ()
+        asn2.writeData(keyInfo.getPrivateExponent().toByteArray());
+        asn2.writeByte(0x02); // INTEGER ()
+        asn2.writeData(keyInfo.getPrimeP().toByteArray());
+        asn2.writeByte(0x02); // INTEGER ()
+        asn2.writeData(keyInfo.getPrimeQ().toByteArray());
+        asn2.writeByte(0x02); // INTEGER ()
+        asn2.writeData(keyInfo.getPrimeExponentP().toByteArray());
+        asn2.writeByte(0x02); // INTEGER ()
+        asn2.writeData(keyInfo.getPrimeExponentQ().toByteArray());
+        asn2.writeByte(0x02); // INTEGER ()
+        asn2.writeData(keyInfo.getCrtCoefficient().toByteArray());
+
+        byte[] rsaKeyEncoded = asn2.toByteArray();
+        asn.writeByte(0x30); // SEQUENCE
+        asn.writeData(rsaKeyEncoded);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/session/PseudoTerminal.java b/src/com/sshtools/j2ssh/session/PseudoTerminal.java
new file mode 100644
index 0000000000000000000000000000000000000000..0ba18308b89bc941754c5ee4f6f8930b53207b62
--- /dev/null
+++ b/src/com/sshtools/j2ssh/session/PseudoTerminal.java
@@ -0,0 +1,233 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.session;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.18 $
+ */
+public interface PseudoTerminal {
+    /**  */
+    public final int TTY_OP_END = 0; //Indicates end of options.
+
+    /**  */
+    public final int VINTR = 1; //Interrupt character; 255 if none.  Similarly for the
+
+    /**  */
+    public final int VQUIT = 2; //The quit character (sends SIGQUIT signal on POSIX
+    public final int VERASE = 3; //Erase the character to left of the cursor.
+
+    /**  */
+    public final int VKILL = 4; //Kill the current input line.
+
+    /**  */
+    public final int VEOF = 5; //End-of-file character (sends EOF from the terminal).
+
+    /**  */
+    public final int VEOL = 6; //End-of-line character in addition to carriage return
+    public final int VEOL2 = 7; //Additional end-of-line character.
+
+    /**  */
+    public final int VSTART = 8; //Continues paused output (normally control-Q).
+
+    /**  */
+    public final int VSTOP = 9; //Pauses output (normally control-S).
+
+    /**  */
+    public final int VSUSP = 10; //Suspends the current program.
+
+    /**  */
+    public final int VDSUSP = 11; //Another suspend character.
+
+    /**  */
+    public final int VREPRINT = 12; //Reprints the current input line.
+
+    /**  */
+    public final int VWERASE = 13; //Erases a word left of cursor.
+
+    /**  */
+    public final int VLNEXT = 14; //Enter the next character typed literally, even if it
+    public final int VFLUSH = 15; //Character to flush output.
+
+    /**  */
+    public final int VSWTCH = 16; //Switch to a different shell layer.
+
+    /**  */
+    public final int VSTATUS = 17; //Prints system status line (load, command, pid etc).
+
+    /**  */
+    public final int VDISCARD = 18; //Toggles the flushing of terminal output.
+
+    /**  */
+    public final int IGNPAR = 30; //The ignore parity flag.  The parameter SHOULD be 0 if
+    public final int PARMRK = 31; //Mark parity and framing errors.
+
+    /**  */
+    public final int INPCK = 32; //Enable checking of parity errors.
+
+    /**  */
+    public final int ISTRIP = 33; //Strip 8th bit off characters.
+
+    /**  */
+    public final int INLCR = 34; //Map NL into CR on input.
+
+    /**  */
+    public final int IGNCR = 35; //Ignore CR on input.
+
+    /**  */
+    public final int ICRNL = 36; //Map CR to NL on input.
+
+    /**  */
+    public final int IUCLC = 37; //Translate uppercase characters to lowercase.
+
+    /**  */
+    public final int IXON = 38; //Enable output flow control.
+
+    /**  */
+    public final int IXANY = 39; //Any char will restart after stop.
+
+    /**  */
+    public final int IXOFF = 40; //Enable input flow control.
+
+    /**  */
+    public final int IMAXBEL = 41; //Ring bell on input queue full.
+
+    /**  */
+    public final int ISIG = 50; //Enable signals INTR, QUIT, [D]SUSP.
+
+    /**  */
+    public final int ICANON = 51; //Canonicalize input lines.
+
+    /**  */
+    public final int XCASE = 52; //Enable input and output of uppercase characters by
+    public final int ECHO = 53; //Enable echoing.
+
+    /**  */
+    public final int ECHOE = 54; //Visually erase chars.
+
+    /**  */
+    public final int ECHOK = 55; //Kill character discards current line.
+
+    /**  */
+    public final int ECHONL = 56; //Echo NL even if ECHO is off.
+
+    /**  */
+    public final int NOFLSH = 57; //Don't flush after interrupt.
+
+    /**  */
+    public final int TOSTOP = 58; //Stop background jobs from output.
+
+    /**  */
+    public final int IEXTEN = 59; //Enable extensions.
+
+    /**  */
+    public final int ECHOCTL = 60; //Echo control characters as ^(Char).
+
+    /**  */
+    public final int ECHOKE = 61; //Visual erase for line kill.
+
+    /**  */
+    public final int PENDIN = 62; //Retype pending input.
+
+    /**  */
+    public final int OPOST = 70; //Enable output processing.
+
+    /**  */
+    public final int OLCUC = 71; //Convert lowercase to uppercase.
+
+    /**  */
+    public final int ONLCR = 72; //Map NL to CR-NL.
+
+    /**  */
+    public final int OCRNL = 73; //Translate carriage return to newline (output).
+
+    /**  */
+    public final int ONOCR = 74; //Translate newline to carriage return-newline
+    public final int ONLRET = 75; //Newline performs a carriage return (output).
+
+    /**  */
+    public final int CS7 = 90; //7 bit mode.
+
+    /**  */
+    public final int CS8 = 91; //8 bit mode.
+
+    /**  */
+    public final int PARENB = 92; //Parity enable.
+
+    /**  */
+    public final int PARODD = 93; //Odd parity, else even.
+
+    /**  */
+    public final int TTY_OP_ISPEED = 128; //Specifies the input baud rate in bits per second.
+
+    /**  */
+    public final int TTY_OP_OSPEED = 129; //Specifies the output baud rate in bits per second.
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getColumns();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getEncodedTerminalModes();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getHeight();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getRows();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getTerm();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getWidth();
+}
diff --git a/src/com/sshtools/j2ssh/session/SessionChannelClient.java b/src/com/sshtools/j2ssh/session/SessionChannelClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..22f79e9c3430d833a32b3e928a42a4a8abfffffc
--- /dev/null
+++ b/src/com/sshtools/j2ssh/session/SessionChannelClient.java
@@ -0,0 +1,555 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.session;
+
+import com.sshtools.j2ssh.SshException;
+import com.sshtools.j2ssh.agent.AgentSocketChannel;
+import com.sshtools.j2ssh.agent.SshAgentClient;
+import com.sshtools.j2ssh.connection.Channel;
+import com.sshtools.j2ssh.connection.ChannelFactory;
+import com.sshtools.j2ssh.connection.ChannelInputStream;
+import com.sshtools.j2ssh.connection.IOChannel;
+import com.sshtools.j2ssh.connection.InvalidChannelException;
+import com.sshtools.j2ssh.connection.SshMsgChannelExtendedData;
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.subsystem.SubsystemClient;
+import com.sshtools.j2ssh.transport.SshMessageStore;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import java.net.Socket;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.52 $
+ */
+public class SessionChannelClient extends IOChannel {
+    private static Log log = LogFactory.getLog(SessionChannelClient.class);
+    private Integer exitCode = null;
+    private String sessionType = "Uninitialized";
+    private SubsystemClient subsystem;
+    private boolean localFlowControl = false;
+    private SignalListener signalListener;
+    private SshMessageStore errorMessages = new SshMessageStore();
+    private ChannelInputStream stderr = new ChannelInputStream(
+        /*ChannelInputStream.createExtended(*/
+        errorMessages,
+            new Integer(SshMsgChannelExtendedData.SSH_EXTENDED_DATA_STDERR));
+
+    //  new Integer(SshMsgChannelExtendedData.SSH_EXTENDED_DATA_STDERR));
+
+    /**
+     * Creates a new SessionChannelClient object.
+     */
+    public SessionChannelClient() {
+        super();
+        setName("session");
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getChannelOpenData() {
+        return null;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getChannelConfirmationData() {
+        return null;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getChannelType() {
+        return "session";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected int getMinimumWindowSpace() {
+        return 1024;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected int getMaximumWindowSpace() {
+        return 32648;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected int getMaximumPacketSize() {
+        return 32648;
+    }
+
+    /**
+     *
+     *
+     * @param signalListener
+     */
+    public void setSignalListener(SignalListener signalListener) {
+        this.signalListener = signalListener;
+    }
+
+    /**
+     *
+     *
+     * @param name
+     * @param value
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public boolean setEnvironmentVariable(String name, String value)
+        throws IOException {
+        log.debug("Requesting environment variable to be set [" + name + "=" +
+            value + "]");
+
+        ByteArrayWriter baw = new ByteArrayWriter();
+        baw.writeString(name);
+        baw.writeString(value);
+
+        return connection.sendChannelRequest(this, "env", true,
+            baw.toByteArray());
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws IOException
+     * @throws SshException
+     * @throws InvalidChannelException
+     */
+    public boolean requestAgentForwarding() throws IOException {
+        log.info("Requesting agent forwarding for the session");
+
+        if (System.getProperty("sshtools.agent") == null) {
+            throw new SshException(
+                "Agent not found! 'sshtools.agent' system property should identify the agent location");
+        }
+
+        boolean success = connection.sendChannelRequest(this, "auth-agent-req",
+                true, null);
+
+        if (success) {
+            // Allow an Agent Channel to be opened
+            connection.addChannelFactory(AgentSocketChannel.AGENT_FORWARDING_CHANNEL,
+                new ChannelFactory() {
+                    public Channel createChannel(String channelType,
+                        byte[] requestData) throws InvalidChannelException {
+                        try {
+                            AgentSocketChannel channel = new AgentSocketChannel(false);
+                            Socket socket = SshAgentClient.connectAgentSocket(System.getProperty(
+                                        "sshtools.agent") /*, 5*/);
+                            channel.bindSocket(socket);
+
+                            return channel;
+                        } catch (Exception ex) {
+                            throw new InvalidChannelException(ex.getMessage());
+                        }
+                    }
+                });
+        }
+
+        return success;
+    }
+
+    /**
+     *
+     *
+     * @param display
+     * @param cookie
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public boolean requestX11Forwarding(int display, String cookie)
+        throws IOException {
+        log.debug("Requesting X11 forwarding for display " + display +
+            " using cookie " + cookie);
+
+        ByteArrayWriter baw = new ByteArrayWriter();
+        baw.writeBoolean(false);
+        baw.writeString("MIT-MAGIC-COOKIE-1");
+        baw.writeString(cookie);
+        baw.writeUINT32(new UnsignedInteger32(String.valueOf(display)));
+
+        return connection.sendChannelRequest(this, "x11-req", true,
+            baw.toByteArray());
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public Integer getExitCode() {
+        return exitCode;
+    }
+
+    /**
+     *
+     *
+     * @param term
+     *
+     * @throws IOException
+     */
+    public void changeTerminalDimensions(PseudoTerminal term)
+        throws IOException {
+        log.debug("Changing terminal dimensions");
+
+        ByteArrayWriter baw = new ByteArrayWriter();
+        baw.writeInt(term.getColumns());
+        baw.writeInt(term.getRows());
+        baw.writeInt(term.getWidth());
+        baw.writeInt(term.getHeight());
+        connection.sendChannelRequest(this, "window-change", false,
+            baw.toByteArray());
+    }
+
+    /**
+     *
+     *
+     * @param command
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public boolean executeCommand(String command) throws IOException {
+        log.info("Requesting command execution");
+        log.info("Command is " + command);
+
+        ByteArrayWriter baw = new ByteArrayWriter();
+        baw.writeString(command);
+
+        if( connection.sendChannelRequest(this, "exec", true, baw.toByteArray()) ) 
+        {
+          if (sessionType.equals("Uninitialized")) {
+            sessionType = command;
+          }
+          
+          return true;
+        } else {
+          return false;
+        }
+    }
+
+    /**
+     *
+     *
+     * @param term
+     * @param cols
+     * @param rows
+     * @param width
+     * @param height
+     * @param terminalModes
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public boolean requestPseudoTerminal(String term, int cols, int rows,
+        int width, int height, String terminalModes) throws IOException {
+        log.info("Requesting pseudo terminal");
+
+        if (log.isDebugEnabled()) {
+            log.debug("Terminal Type is " + term);
+            log.debug("Columns=" + String.valueOf(cols));
+            log.debug("Rows=" + String.valueOf(rows));
+            log.debug("Width=" + String.valueOf(width));
+            log.debug("Height=" + String.valueOf(height));
+        }
+
+        // This requests a pseudo terminal
+        ByteArrayWriter baw = new ByteArrayWriter();
+        baw.writeString(term);
+        baw.writeInt(cols);
+        baw.writeInt(rows);
+        baw.writeInt(width);
+        baw.writeInt(height);
+        baw.writeString(terminalModes);
+
+        return connection.sendChannelRequest(this, "pty-req", true,
+            baw.toByteArray());
+    }
+
+    /**
+     *
+     *
+     * @param term
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public boolean requestPseudoTerminal(PseudoTerminal term)
+        throws IOException {
+        return requestPseudoTerminal(term.getTerm(), term.getColumns(),
+            term.getRows(), term.getWidth(), term.getHeight(),
+            term.getEncodedTerminalModes());
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public boolean startShell() throws IOException {
+        log.debug("Requesting users shell");
+
+        // Send the request for a shell, we want a reply
+        if (connection.sendChannelRequest(this, "shell", true, null)) {
+            if (sessionType.equals("Uninitialized")) {
+                sessionType = "shell";
+            }
+
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     *
+     *
+     * @param subsystem
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public boolean startSubsystem(String subsystem) throws IOException {
+        log.info("Starting " + subsystem + " subsystem");
+
+        ByteArrayWriter baw = new ByteArrayWriter();
+        baw.writeString(subsystem);
+
+        if (connection.sendChannelRequest(this, "subsystem", true,
+                    baw.toByteArray())) {
+            if (sessionType.equals("Uninitialized")) {
+                sessionType = subsystem;
+            }
+
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     *
+     *
+     * @param subsystem
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public boolean startSubsystem(SubsystemClient subsystem)
+        throws IOException {
+        boolean result = startSubsystem(subsystem.getName());
+
+        if (result) {
+            this.subsystem = subsystem;
+            subsystem.setSessionChannel(this);
+            subsystem.start();
+        }
+
+        return result;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isLocalFlowControlEnabled() {
+        return localFlowControl;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getSessionType() {
+        return sessionType;
+    }
+
+    /**
+     *
+     *
+     * @param sessionType
+     */
+    public void setSessionType(String sessionType) {
+        this.sessionType = sessionType;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public SubsystemClient getSubsystem() {
+        return subsystem;
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    protected void onChannelClose() throws IOException {
+        super.onChannelClose();
+
+        try {
+            stderr.close();
+        } catch (IOException ex) {
+        }
+
+        Integer exitCode = getExitCode();
+
+        if (exitCode != null) {
+            log.debug("Exit code " + exitCode.toString());
+        }
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    protected void onChannelOpen() throws IOException {
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public InputStream getStderrInputStream() throws IOException {
+        /*if (stderr == null) {
+            throw new IOException("The session must be started first!");
+                 }*/
+        return stderr;
+    }
+
+    /**
+     *
+     *
+     * @param msg
+     *
+     * @throws IOException
+     */
+    protected void onChannelExtData(SshMsgChannelExtendedData msg)
+        throws IOException {
+        errorMessages.addMessage(msg);
+    }
+
+    /**
+     *
+     *
+     * @param requestType
+     * @param wantReply
+     * @param requestData
+     *
+     * @throws IOException
+     */
+    protected void onChannelRequest(String requestType, boolean wantReply,
+        byte[] requestData) throws IOException {
+        log.info("Channel Request received: " + requestType);
+
+        if (requestType.equals("exit-status")) {
+            exitCode = new Integer((int) ByteArrayReader.readInt(requestData, 0));
+            //log.debug("Exit code of " + exitCode.toString() + " received");
+            log.info("Exit code of " + exitCode.toString() + " received");
+        } else if (requestType.equals("exit-signal")) {
+            ByteArrayReader bar = new ByteArrayReader(requestData);
+            String signal = bar.readString();
+            boolean coredump = bar.read() != 0;
+            String message = bar.readString();
+            String language = bar.readString();
+            log.debug("Exit signal " + signal + " received");
+            log.debug("Signal message: " + message);
+            log.debug("Core dumped: " + String.valueOf(coredump));
+
+            if (signalListener != null) {
+                signalListener.onExitSignal(signal, coredump, message);
+            }
+        } else if (requestType.equals("xon-xoff")) {
+            if (requestData.length >= 1) {
+                localFlowControl = (requestData[0] != 0);
+            }
+        } else if (requestType.equals("signal")) {
+            String signal = ByteArrayReader.readString(requestData, 0);
+            log.debug("Signal " + signal + " received");
+
+            if (signalListener != null) {
+                signalListener.onSignal(signal);
+            }
+        } else {
+            if (wantReply) {
+                connection.sendChannelRequestFailure(this);
+            }
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/session/SessionOutputEcho.java b/src/com/sshtools/j2ssh/session/SessionOutputEcho.java
new file mode 100644
index 0000000000000000000000000000000000000000..dda3b3cd05198c297c44a044542f017a49dedc02
--- /dev/null
+++ b/src/com/sshtools/j2ssh/session/SessionOutputEcho.java
@@ -0,0 +1,56 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.session;
+
+
+/**
+ * <p>
+ * Title:
+ * </p>
+ *
+ * <p>
+ * Description:
+ * </p>
+ *
+ * <p>
+ * Copyright: Copyright (c) 2003
+ * </p>
+ *
+ * <p>
+ * Company:
+ * </p>
+ *
+ * @author Lee David Painter
+     * @version $Id: SessionOutputEcho.java,v 1.9 2003/09/11 15:35:13 martianx Exp $
+ */
+public interface SessionOutputEcho {
+    /**
+     *
+     *
+     * @param msg
+     */
+    public void echo(String msg);
+}
diff --git a/src/com/sshtools/j2ssh/session/SessionOutputReader.java b/src/com/sshtools/j2ssh/session/SessionOutputReader.java
new file mode 100644
index 0000000000000000000000000000000000000000..065f0672dfe069510a2dec117fdca800bc8d5681
--- /dev/null
+++ b/src/com/sshtools/j2ssh/session/SessionOutputReader.java
@@ -0,0 +1,254 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.session;
+
+import com.sshtools.j2ssh.connection.*;
+
+
+/**
+ * <p>
+ * This class provides a utility to read and parse the output a session,
+ * providing methods to wait for specific strings such as the prompt or
+ * command input requests.
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Revision: 1.13 $
+ *
+ * @since 0.2.1
+ */
+public class SessionOutputReader {
+    SessionChannelClient session;
+    int pos = 0;
+    int mark = 0;
+    String output = "";
+
+    /**
+     * <p>
+     * Contructs the session reader.
+     * </p>
+     *
+     * @param session the to read
+     */
+    public SessionOutputReader(SessionChannelClient session) {
+        this.session = session;
+        session.addEventListener(new SessionOutputListener());
+    }
+
+    /**
+     * Returns the output of the entire session.
+     *
+     * @return a string containing the entire output of the session so far.
+     */
+    public String getOutput() {
+        return output;
+    }
+
+    /**
+     * <p>
+     * Returns the current position of the session input pointer. This pointer
+     * is set to the position of the matched string everytime a match is found
+     * during a call by <code>waitForString</code>
+     * </p>
+     *
+     * @return the current input reader pointer
+     */
+    public int getPosition() {
+        return pos;
+    }
+
+    /**
+     * Mark the postion specified for filtering session output.
+     *
+     * @param mark output position to mark
+     */
+    public void markPosition(int mark) {
+        this.mark = mark;
+    }
+
+    /**
+     * Marks the current position.
+     */
+    public void markCurrentPosition() {
+        this.mark = pos;
+    }
+
+    /**
+     * <p>
+     * Returns a string containing the session output from the current marked
+     * position to the end of the output.
+     * </p>
+     *
+     * @return a string containing the session output from the marked position
+     *         to current position
+     */
+    public String getMarkedOutput() {
+        return output.substring(mark, pos);
+    }
+
+    /**
+     * <p>
+     * Wait for a given String in the output buffer.
+     * </p>
+     *
+     * @param str the string to wait for
+     * @param echo a callback interface to receive the session output whilst
+     *        the no match for the string is found
+     *
+     * @return true if the string was found, otherwise false
+     *
+     * @throws InterruptedException if the thread is interrupted
+     *
+     * @see waitForString(String, int, SessionOutputEcho)
+     */
+    public synchronized boolean waitForString(String str, SessionOutputEcho echo)
+        throws InterruptedException {
+        return waitForString(str, 0, echo);
+    }
+
+    /**
+     * <p>
+     * Wait for a given String in the output buffer. This method will block
+     * until the string is found.
+     * </p>
+     *
+     * @param str the string to wait for
+     *
+     * @return true if the string was found, otherwise false
+     *
+     * @throws InterruptedException if the thread is interrupted
+     *
+     * @see waitForString(String, int, SessionOutputEcho)
+     */
+    public synchronized boolean waitForString(String str)
+        throws InterruptedException {
+        return waitForString(str, 0, null);
+    }
+
+    /**
+     * <p>
+     * Wait for a given String in the output buffer.
+     * </p>
+     *
+     * @param str the string to wait for
+     * @param timeout the number of milliseconds to wait
+     *
+     * @return true if the string was found, otherwise false
+     *
+     * @throws InterruptedException if the thread is interrupted
+     *
+     * @see waitForString(String, int, SessionOutputEcho)
+     */
+    public synchronized boolean waitForString(String str, int timeout)
+        throws InterruptedException {
+        return waitForString(str, timeout, null);
+    }
+
+    /**
+     * <p>
+     * Wait for a given String in the output buffer. When this method is called
+     * the method will block unitil either the String arrives in the input
+     * buffer or the timeout specified has elasped.
+     * </p>
+     *
+     * @param str the string to wait for
+     * @param timeout the number of milliseconds to wait, 0=infinite
+     * @param echo a callback interface to receive the session output whilst
+     *        the no match for the string is found
+     *
+     * @return true if the string was found, otherwise false
+     *
+     * @throws InterruptedException if the thread is interrupted
+     */
+    public synchronized boolean waitForString(String str, int timeout,
+        SessionOutputEcho echo) throws InterruptedException {
+        long time = System.currentTimeMillis();
+
+        while ((output.indexOf(str, pos) == -1) &&
+                (((System.currentTimeMillis() - time) < timeout) ||
+                (timeout == 0))) {
+            int tmp = output.length();
+            wait((timeout > 0) ? (timeout -
+                (System.currentTimeMillis() - time)) : 0);
+
+            if ((output.length() > tmp) && (echo != null)) {
+                echo.echo(output.substring(tmp, output.length()));
+            }
+        }
+
+        if (output.indexOf(str, pos) > -1) {
+            pos = output.indexOf(str, pos) + str.length();
+
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     *
+     *
+     * @param echo
+     *
+     * @throws InterruptedException
+     */
+    public synchronized void echoLineByLineToClose(SessionOutputEcho echo)
+        throws InterruptedException {
+        while (session.isOpen()) {
+            waitForString("\n", 1000, echo);
+        }
+    }
+
+    private synchronized void breakWaiting() {
+        notifyAll();
+    }
+
+    /**
+     * The ChannelEventListener to receive event notifications
+     */
+    class SessionOutputListener implements ChannelEventListener {
+        public void onChannelOpen(Channel channel) {
+        }
+
+        public void onChannelClose(Channel channel) {
+            breakWaiting();
+        }
+
+        public void onChannelEOF(Channel channel) {
+            // Timeout
+            breakWaiting();
+        }
+
+        public void onDataSent(Channel channel, byte[] data) {
+            // Do nothing
+        }
+
+        public void onDataReceived(Channel channel, byte[] data) {
+            output += new String(data);
+            breakWaiting();
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/session/SignalListener.java b/src/com/sshtools/j2ssh/session/SignalListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..673acdcba151f9a9b9a0d3c2fb1bb7e8991509d6
--- /dev/null
+++ b/src/com/sshtools/j2ssh/session/SignalListener.java
@@ -0,0 +1,51 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.session;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public interface SignalListener {
+    /**
+     *
+     *
+     * @param signal
+     */
+    public void onSignal(String signal);
+
+    /**
+     *
+     *
+     * @param signal
+     * @param coredump
+     * @param message
+     */
+    public void onExitSignal(String signal, boolean coredump, String message);
+}
diff --git a/src/com/sshtools/j2ssh/sftp/FileAttributes.java b/src/com/sshtools/j2ssh/sftp/FileAttributes.java
new file mode 100644
index 0000000000000000000000000000000000000000..33153135b3600073e9f62036af65e68e202bf2ff
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/FileAttributes.java
@@ -0,0 +1,740 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.*;
+
+import java.io.*;
+
+import java.text.*;
+
+import java.util.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.22 $
+ */
+public class FileAttributes {
+    // Version 4 types
+
+    public static final int SSH_FILEXFER_TYPE_REGULAR      = 1;
+    public static final int SSH_FILEXFER_TYPE_DIRECTORY    = 2;
+    public static final int SSH_FILEXFER_TYPE_SYMLINK      = 3;
+    public static final int SSH_FILEXFER_TYPE_SPECIAL      = 4;
+    public static final int SSH_FILEXFER_TYPE_UNKNOWN      = 5;
+    public static final int SSH_FILEXFER_TYPE_SOCKET       = 6;
+    public static final int SSH_FILEXFER_TYPE_CHAR_DEVICE  = 7;
+    public static final int SSH_FILEXFER_TYPE_BLOCK_DEVICE = 8;
+    public static final int SSH_FILEXFER_TYPE_FIFO         = 9;
+    
+    private static final int SSH_FILEXFER_ATTR_SIZE        = 0x0000001;
+    private static final int SSH_FILEXFER_ATTR_UIDGID      = 0x00000002;
+    private static final int SSH_FILEXFER_ATTR_PERMISSIONS = 0x0000004;
+
+    // Also as ACMODTIME for version 3
+    private static final int SSH_FILEXFER_ATTR_ACCESSTIME = 0x0000008;
+    private static final int SSH_FILEXFER_ATTR_CREATETIME = 0x0000010;
+    private static final int SSH_FILEXFER_ATTR_MODIFYTIME = 0x0000020;
+    private static final int SSH_FILEXFER_ATTR_ACL = 0x0000040;
+    private static final int SSH_FILEXFER_ATTR_OWNERGROUP = 0x0000080;
+    private static final int SSH_FILEXFER_ATTR_EXTENDED = 0x8000000;
+
+    // Posix stats
+
+    public static final int S_IFMT = 0xF000;
+    public static final int S_IFSOCK = 0xC000;
+    public static final int S_IFLNK = 0xA000;
+    public static final int S_IFREG = 0x8000;
+    public static final int S_IFBLK = 0x6000;
+    public static final int S_IFDIR = 0x4000;
+    public static final int S_IFCHR = 0x2000;
+    public static final int S_IFIFO = 0x1000;
+    public final static int S_ISUID = 0x800;
+    public final static int S_ISGID = 0x400;
+    public final static int S_IRUSR = 0x100;
+    public final static int S_IWUSR = 0x80;
+    public final static int S_IXUSR = 0x40;
+    public final static int S_IRGRP = 0x20;
+    public final static int S_IWGRP = 0x10;
+    public final static int S_IXGRP = 0x08;
+    public final static int S_IROTH = 0x04;
+    public final static int S_IWOTH = 0x02;
+    public final static int S_IXOTH = 0x01;
+    int version = 3;
+    long flags = 0x0000000; // Version 3 & 4
+    int type; // Version 4 only
+    UnsignedInteger64 size = null; // Version 3 & 4
+    UnsignedInteger32 uid = null; // Version 3 only
+    UnsignedInteger32 gid = null; // Version 3 only
+    String owner = null; // Version 4 only
+    String group = null; // Version 4 only
+    UnsignedInteger32 permissions = null; // Version 3 & 4
+    UnsignedInteger32 atime = null; // Version 3 & 4
+    UnsignedInteger32 createtime = null; // Version 4 only
+    UnsignedInteger32 mtime = null; // Version 3 & 4
+    List acl = new Vector(); // Version 4 only
+    Map extended = new HashMap(); // Version 3 & 4
+    char[] types = { 'p', 'c', 'd', 'b', '-', 'l', 's', };
+
+    /**
+     * Creates a new FileAttributes object.
+     */
+    public FileAttributes() {
+    }
+
+    /*
+        public FileAttributes(int type, int version) {
+       if(type >= 1 && type <= 5) {
+         this.type = type;
+         this.version = version;
+       }
+       else
+         throw new IllegalArgumentException("The type must be a valid FileAttribute type");
+        }
+     */
+    public FileAttributes(ByteArrayReader bar) throws IOException {
+        flags = bar.readInt();
+
+        /*
+              type = bar.readInt();
+         */
+        if (isFlagSet(SSH_FILEXFER_ATTR_SIZE)) {
+            size = bar.readUINT64();
+        }
+
+        if (isFlagSet(SSH_FILEXFER_ATTR_UIDGID)) {
+            uid = bar.readUINT32();
+            gid = bar.readUINT32();
+        }
+
+        /*if(isFlagSet(SSH_FILEXFER_ATTR_OWNERGROUP)) {
+             owner = bar.readString();
+             group = bar.readString();
+              }*/
+        if (isFlagSet(SSH_FILEXFER_ATTR_PERMISSIONS)) {
+            permissions = bar.readUINT32();
+        }
+
+        if (isFlagSet(SSH_FILEXFER_ATTR_ACCESSTIME)) {
+            atime = bar.readUINT32();
+            mtime = bar.readUINT32();
+        }
+
+        /*
+              if(isFlagSet(SSH_FILEXFER_ATTR_CREATETIME))
+           createtime = bar.readUINT32();
+              if(isFlagSet(SSH_FILEXFER_ATTR_MODIFYTIME))
+           mtime = bar.readUINT32();
+         */
+        /*
+              if(isFlagSet(SSH_FILEXFER_ATTR_ACL)) {
+           int count = (int)bar.readInt();
+           for(int i=0;i<count;i++) {
+             acl.add(new ACL(bar));
+           }
+              }*/
+        if (isFlagSet(SSH_FILEXFER_ATTR_EXTENDED)) {
+            int count = (int) bar.readInt();
+            String type;
+            String data;
+
+            for (int i = 0; i < count; i++) {
+                type = bar.readString();
+                data = bar.readString();
+                extended.put(type, data);
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getUID() {
+        if (uid != null) {
+            return uid;
+        } else {
+            return new UnsignedInteger32(0);
+        }
+    }
+
+    /**
+     *
+     *
+     * @param uid
+     */
+    public void setUID(UnsignedInteger32 uid) {
+        flags |= SSH_FILEXFER_ATTR_UIDGID;
+        this.uid = uid;
+    }
+
+    /**
+     *
+     *
+     * @param gid
+     */
+    public void setGID(UnsignedInteger32 gid) {
+        flags |= SSH_FILEXFER_ATTR_UIDGID;
+        this.gid = gid;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getGID() {
+        if (gid != null) {
+            return gid;
+        } else {
+            return new UnsignedInteger32(0);
+        }
+    }
+
+    /**
+     *
+     *
+     * @param size
+     */
+    public void setSize(UnsignedInteger64 size) {
+        this.size = size;
+
+        // Set the flag
+        if (size != null) {
+            flags |= SSH_FILEXFER_ATTR_SIZE;
+        } else {
+            flags ^= SSH_FILEXFER_ATTR_SIZE;
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger64 getSize() {
+        if (size != null) {
+            return size;
+        } else {
+            return new UnsignedInteger64("0");
+        }
+    }
+
+    /*
+        public void setOwner(String owner) {
+       this.owner = owner;
+       // Set the flag
+       if(group!=null || owner!=null)
+         flags |= SSH_FILEXFER_ATTR_OWNERGROUP;
+       else
+         flags ^= SSH_FILEXFER_ATTR_OWNERGROUP;
+        }
+        public String getOwner() {
+       return owner;
+        }*/
+    /*
+        public void setGroup(String group) {
+       this.group = group;
+       // Set the flag
+       if(group!=null || owner!=null)
+         flags |= SSH_FILEXFER_ATTR_OWNERGROUP;
+       else
+         flags ^= SSH_FILEXFER_ATTR_OWNERGROUP;
+        }
+        public String getGroup() {
+       return group;
+        }*/
+    public void setPermissions(UnsignedInteger32 permissions) {
+        this.permissions = permissions;
+
+        // Set the flag
+        if (permissions != null) {
+            flags |= SSH_FILEXFER_ATTR_PERMISSIONS;
+        } else {
+            flags ^= SSH_FILEXFER_ATTR_PERMISSIONS;
+        }
+    }
+
+    /**
+     * Set permissions given a UNIX style mask
+     *
+     * @param mask mask
+     *
+     * @throws IllegalArgumentException if badly formatted string
+     */
+    public void setPermissionsFromMaskString(String mask) {
+        if (mask.length() != 4) {
+            throw new IllegalArgumentException("Mask length must be 4");
+        }
+
+        try {
+            setPermissions(new UnsignedInteger32(String.valueOf(
+                        Integer.parseInt(mask, 8))));
+        } catch (NumberFormatException nfe) {
+            throw new IllegalArgumentException(
+                "Mask must be 4 digit octal number.");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param newPermissions
+     */
+    public void setPermissions(String newPermissions) {
+        int cp = 0;
+
+        if (permissions != null) {
+            cp = cp |
+                (((permissions.intValue() & S_IFMT) == S_IFMT) ? S_IFMT : 0);
+            cp = cp |
+                (((permissions.intValue() & S_IFSOCK) == S_IFSOCK) ? S_IFSOCK : 0);
+            cp = cp |
+                (((permissions.intValue() & S_IFLNK) == S_IFLNK) ? S_IFLNK : 0);
+            cp = cp |
+                (((permissions.intValue() & S_IFREG) == S_IFREG) ? S_IFREG : 0);
+            cp = cp |
+                (((permissions.intValue() & S_IFBLK) == S_IFBLK) ? S_IFBLK : 0);
+            cp = cp |
+                (((permissions.intValue() & S_IFDIR) == S_IFDIR) ? S_IFDIR : 0);
+            cp = cp |
+                (((permissions.intValue() & S_IFCHR) == S_IFCHR) ? S_IFCHR : 0);
+            cp = cp |
+                (((permissions.intValue() & S_IFIFO) == S_IFIFO) ? S_IFIFO : 0);
+            cp = cp |
+                (((permissions.intValue() & S_ISUID) == S_ISUID) ? S_ISUID : 0);
+            cp = cp |
+                (((permissions.intValue() & S_ISGID) == S_ISGID) ? S_ISGID : 0);
+        }
+
+        int len = newPermissions.length();
+
+        if (len >= 1) {
+            cp = cp |
+                ((newPermissions.charAt(0) == 'r') ? FileAttributes.S_IRUSR : 0);
+        }
+
+        if (len >= 2) {
+            cp = cp |
+                ((newPermissions.charAt(1) == 'w') ? FileAttributes.S_IWUSR : 0);
+        }
+
+        if (len >= 3) {
+            cp = cp |
+                ((newPermissions.charAt(2) == 'x') ? FileAttributes.S_IXUSR : 0);
+        }
+
+        if (len >= 4) {
+            cp = cp |
+                ((newPermissions.charAt(3) == 'r') ? FileAttributes.S_IRGRP : 0);
+        }
+
+        if (len >= 5) {
+            cp = cp |
+                ((newPermissions.charAt(4) == 'w') ? FileAttributes.S_IWGRP : 0);
+        }
+
+        if (len >= 6) {
+            cp = cp |
+                ((newPermissions.charAt(5) == 'x') ? FileAttributes.S_IXGRP : 0);
+        }
+
+        if (len >= 7) {
+            cp = cp |
+                ((newPermissions.charAt(6) == 'r') ? FileAttributes.S_IROTH : 0);
+        }
+
+        if (len >= 8) {
+            cp = cp |
+                ((newPermissions.charAt(7) == 'w') ? FileAttributes.S_IWOTH : 0);
+        }
+
+        if (len >= 9) {
+            cp = cp |
+                ((newPermissions.charAt(8) == 'x') ? FileAttributes.S_IXOTH : 0);
+        }
+
+        setPermissions(new UnsignedInteger32(cp));
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getPermissions() {
+        return permissions;
+    }
+
+    /**
+     *
+     *
+     * @param atime
+     * @param mtime
+     */
+    public void setTimes(UnsignedInteger32 atime, UnsignedInteger32 mtime) {
+        this.atime = atime;
+        this.mtime = mtime;
+
+        // Set the flag
+        if (atime != null) {
+            flags |= SSH_FILEXFER_ATTR_ACCESSTIME;
+        } else {
+            flags ^= SSH_FILEXFER_ATTR_ACCESSTIME;
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getAccessedTime() {
+        return atime;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getModifiedTime() {
+        if (mtime != null) {
+            return mtime;
+        } else {
+            return new UnsignedInteger32(0);
+        }
+    }
+
+    /*
+        public void setCreatedTime(UnsignedInteger32 atime) {
+       this.createtime = createtime;
+       // Set the flag
+       if(createtime!=null)
+         flags |= SSH_FILEXFER_ATTR_CREATETIME;
+       else
+         flags ^= SSH_FILEXFER_ATTR_CREATETIME;
+        }
+        public UnsignedInteger32 getCreatedTime() {
+       return createtime;
+        }
+        public void setModifiedTime(UnsignedInteger32 atime) {
+       this.mtime = mtime;
+       // Set the flag
+       if(mtime!=null)
+         flags |= SSH_FILEXFER_ATTR_MODIFYTIME;
+       else
+         flags ^= SSH_FILEXFER_ATTR_MODIFYTIME;
+        }
+        public UnsignedInteger32 getModifiedTime() {
+       return mtime;
+        }*/
+    /*
+        public List getACL() {
+       return acl;
+        }*/
+    public Map getExtendedAttributes() {
+        return extended;
+    }
+
+    /**
+     *
+     *
+     * @param flag
+     *
+     * @return
+     */
+    public boolean isFlagSet(int flag) {
+        return ((flags & flag) == flag);
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public byte[] toByteArray() throws IOException {
+        ByteArrayWriter baw = new ByteArrayWriter();
+
+        if (extended.size() > 0) {
+            flags |= SSH_FILEXFER_ATTR_EXTENDED;
+
+            /*
+                  if(acl.size() > 0)
+               flags |= SSH_FILEXFER_ATTR_ACL;
+             */
+        }
+        
+        baw.writeInt(flags);
+
+        /*
+              baw.write(type);
+         */
+        
+        if (isFlagSet(SSH_FILEXFER_ATTR_SIZE)) {
+            baw.writeUINT64(size);
+        }
+
+        if (isFlagSet(SSH_FILEXFER_ATTR_UIDGID)) {
+            if (uid != null) {
+                baw.writeUINT32(uid);
+            } else {
+                baw.writeInt(0);
+            }
+
+            if (gid != null) {
+                baw.writeUINT32(gid);
+            } else {
+                baw.writeInt(0);
+            }
+        }
+
+        /*
+              if(isFlagSet(SSH_FILEXFER_ATTR_OWNERGROUP)) {
+            baw.writeString(owner);
+            baw.writeString(group);
+              }
+         */
+        if (isFlagSet(SSH_FILEXFER_ATTR_PERMISSIONS)) {
+            baw.writeUINT32(permissions);
+        }
+
+        if (isFlagSet(SSH_FILEXFER_ATTR_ACCESSTIME)) {
+            baw.writeUINT32(atime);
+            baw.writeUINT32(mtime);
+        }
+
+        /*
+              if(isFlagSet(SSH_FILEXFER_ATTR_CREATETIME))
+           baw.writeUINT32(createtime);
+              if(isFlagSet(SSH_FILEXFER_ATTR_MODIFYTIME))
+           baw.writeUINT32(mtime);
+              if(isFlagSet(SSH_FILEXFER_ATTR_ACL)) {
+           ByteArrayWriter acls = new ByteArrayWriter();
+           acls.writeInt(acl.size());
+           Iterator it = acl.iterator();
+           while(it.hasNext()) {
+             acls.write(((ACL)it.next()).toByteArray());
+           }
+           baw.writeBinaryString(acls.toByteArray());
+           acls = null;
+              }*/
+        if (isFlagSet(SSH_FILEXFER_ATTR_EXTENDED)) {
+            baw.writeInt(extended.size());
+
+            Iterator it = extended.entrySet().iterator();
+            Set set;
+
+            while (it.hasNext()) {
+                Map.Entry entry = (Map.Entry) it.next();
+                baw.writeString((String) entry.getKey());
+                baw.writeString((String) entry.getValue());
+            }
+        }
+
+        return baw.toByteArray();
+    }
+
+    private int octal(int v, int r) {
+        v >>>= r;
+
+        return (((v & 0x04) != 0) ? 4 : 0) + (((v & 0x02) != 0) ? 2 : 0) +
+        +(((v & 0x01) != 0) ? 1 : 0);
+    }
+
+    private String rwxString(int v, int r) {
+        v >>>= r;
+
+        String rwx = ((((v & 0x04) != 0) ? "r" : "-") +
+            (((v & 0x02) != 0) ? "w" : "-"));
+
+        if (((r == 6) && ((permissions.intValue() & S_ISUID) == S_ISUID)) ||
+                ((r == 3) && ((permissions.intValue() & S_ISGID) == S_ISGID))) {
+            rwx += (((v & 0x01) != 0) ? "s" : "S");
+        } else {
+            rwx += (((v & 0x01) != 0) ? "x" : "-");
+        }
+
+        return rwx;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getPermissionsString() {
+        if (permissions != null) {
+            StringBuffer str = new StringBuffer();
+            str.append(types[(permissions.intValue() & S_IFMT) >>> 13]);
+            str.append(rwxString(permissions.intValue(), 6));
+            str.append(rwxString(permissions.intValue(), 3));
+            str.append(rwxString(permissions.intValue(), 0));
+
+            return str.toString();
+        } else {
+            return "";
+        }
+    }
+
+    /**
+     * Return the UNIX style mode mask
+     *
+     * @return mask
+     */
+    public String getMaskString() {
+        StringBuffer buf = new StringBuffer();
+        buf.append('0');
+
+        int i = permissions.intValue();
+        buf.append(octal(i, 6));
+        buf.append(octal(i, 3));
+        buf.append(octal(i, 0));
+
+        return buf.toString();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getModTimeString() {
+        if (mtime == null) {
+            return "";
+        }
+
+        SimpleDateFormat df;
+        long mt = (mtime.longValue() * 1000L);
+        long now = System.currentTimeMillis();
+
+        if ((now - mt) > (6 * 30 * 24 * 60 * 60 * 1000L)) {
+            df = new SimpleDateFormat("MMM dd  yyyy");
+        } else {
+            df = new SimpleDateFormat("MMM dd hh:mm");
+        }
+
+        return df.format(new Date(mt));
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isDirectory() {
+      if (permissions == null) {
+        return false;
+      } 
+      else {
+        return ( ( permissions.intValue() &  S_IFMT ) == S_IFDIR );
+      }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isFile() {
+      if (permissions == null) {
+        return false;
+      } else {
+        return (permissions.intValue() & S_IFMT ) == S_IFREG;
+      }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isLink() {
+      if (permissions == null) {
+        return false;
+      } else {
+        return (permissions.intValue() & S_IFMT) == S_IFLNK;
+      }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isFifo() {
+      if (permissions == null) {
+        return false;
+      } else {
+        return (permissions.intValue() & S_IFMT) == S_IFIFO;
+      }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isBlock() {
+      if (permissions == null) {
+        return false;
+      } else {
+        return (permissions.intValue() & S_IFMT) == S_IFBLK;
+      }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isCharacter() {
+      if (permissions == null) {
+        return false;
+      } else {
+        return (permissions.intValue() & S_IFMT) == S_IFCHR;
+      }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isSocket() {
+      if (permissions == null) {
+        return false;
+      } else {
+        return (permissions.intValue() & S_IFMT) == S_IFSOCK;
+      }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/MessageRequestId.java b/src/com/sshtools/j2ssh/sftp/MessageRequestId.java
new file mode 100644
index 0000000000000000000000000000000000000000..952a3a9849db05be44e1c9c3ae9cba9960f31a67
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/MessageRequestId.java
@@ -0,0 +1,38 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.*;
+
+
+interface MessageRequestId {
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getId();
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SftpFile.java b/src/com/sshtools/j2ssh/sftp/SftpFile.java
new file mode 100644
index 0000000000000000000000000000000000000000..b6390defecee3353c63ac017c228a0c387074b40
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SftpFile.java
@@ -0,0 +1,326 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.23 $
+ */
+public class SftpFile implements Comparable {
+    private String filename;
+    private byte[] handle;
+    private FileAttributes attrs;
+    private SftpSubsystemClient sftp;
+    private String absolutePath;
+
+    /**
+     * Creates a new SftpFile object.
+     *
+     * @param absolutePath
+     * @param attrs
+     */
+    public SftpFile(String absolutePath, FileAttributes attrs) {
+        this.absolutePath = absolutePath;
+
+        int i = absolutePath.lastIndexOf("/");
+
+        if (i > -1) {
+            this.filename = absolutePath.substring(i + 1);
+        } else {
+            this.filename = absolutePath;
+        }
+
+        this.attrs = attrs;
+    }
+
+    /**
+     * Creates a new SftpFile object.
+     *
+     * @param absolutePath
+     */
+    public SftpFile(String absolutePath) {
+        this(absolutePath, new FileAttributes());
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    public void delete() throws IOException {
+        if (sftp == null) {
+            throw new IOException("Instance not connected to SFTP subsystem");
+        }
+
+        if (isDirectory()) {
+            sftp.removeDirectory(getAbsolutePath());
+        } else {
+            sftp.removeFile(getAbsolutePath());
+        }
+    }
+
+    /**
+     *
+     *
+     * @param newFilename
+     *
+     * @throws IOException
+     */
+    public void rename(String newFilename) throws IOException {
+        if (sftp == null) {
+            throw new IOException("Instance not connected to SFTP subsystem");
+        }
+
+        sftp.renameFile(getAbsolutePath() + filename, newFilename);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean canWrite() {
+        return (getAttributes().getPermissions().longValue() &
+        FileAttributes.S_IWUSR) == FileAttributes.S_IWUSR;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean canRead() {
+        return (getAttributes().getPermissions().longValue() &
+        FileAttributes.S_IRUSR) == FileAttributes.S_IRUSR;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isOpen() {
+        if (sftp == null) {
+            return false;
+        }
+
+        return sftp.isValidHandle(handle);
+    }
+
+    /**
+     *
+     *
+     * @param handle
+     */
+    protected void setHandle(byte[] handle) {
+        this.handle = handle;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected byte[] getHandle() {
+        return handle;
+    }
+
+    /**
+     *
+     *
+     * @param sftp
+     */
+    protected void setSFTPSubsystem(SftpSubsystemClient sftp) {
+        this.sftp = sftp;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected SftpSubsystemClient getSFTPSubsystem() {
+        return sftp;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getFilename() {
+        return filename;
+    }
+
+    private String pad(int num) {
+        String str = "";
+
+        if (num > 0) {
+            for (int i = 0; i < num; i++) {
+                str += " ";
+            }
+        }
+
+        return str;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getLongname() {
+        StringBuffer str = new StringBuffer();
+        str.append(pad(10 - getAttributes().getPermissionsString().length()) +
+            getAttributes().getPermissionsString());
+        str.append("   1 ");
+        str.append(getAttributes().getUID().toString() +
+            pad(8 - getAttributes().getUID().toString().length())); //uid
+        str.append(" ");
+        str.append(getAttributes().getGID().toString() +
+            pad(8 - getAttributes().getGID().toString().length())); //gid
+        str.append(" ");
+        str.append(pad(8 - getAttributes().getSize().toString().length()) +
+            getAttributes().getSize().toString());
+        str.append(" ");
+        str.append(pad(12 - getAttributes().getModTimeString().length()) +
+            getAttributes().getModTimeString());
+        str.append(" ");
+        str.append(filename);
+
+        return str.toString();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public FileAttributes getAttributes() {
+        try {
+            if (attrs == null) {
+                attrs = sftp.getAttributes(this);
+            }
+        } catch (IOException ioe) {
+            attrs = new FileAttributes();
+        }
+
+        return attrs;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getAbsolutePath() {
+        return absolutePath;
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    public void close() throws IOException {
+        sftp.closeFile(this);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isDirectory() {
+      return getAttributes().isDirectory();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isFile() {
+      return getAttributes().isFile();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isLink() {
+      return getAttributes().isLink();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isFifo() {
+      return getAttributes().isFifo();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isBlock() {
+      return getAttributes().isBlock();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isCharacter() {
+      return getAttributes().isCharacter();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isSocket() {
+      return getAttributes().isSocket();
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Comparable#compareTo(java.lang.Object)
+     */
+    public int compareTo(Object o) {
+      return getFilename().compareTo(((SftpFile) o).getFilename());
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SftpFileInputStream.java b/src/com/sshtools/j2ssh/sftp/SftpFileInputStream.java
new file mode 100644
index 0000000000000000000000000000000000000000..c2630a189ced877e7c0468ada01fbe8634158bd9
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SftpFileInputStream.java
@@ -0,0 +1,121 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.*;
+
+import java.io.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public class SftpFileInputStream extends InputStream {
+    SftpFile file;
+    UnsignedInteger64 position = new UnsignedInteger64("0");
+
+    /**
+     * Creates a new SftpFileInputStream object.
+     *
+     * @param file
+     *
+     * @throws IOException
+     */
+    public SftpFileInputStream(SftpFile file) throws IOException {
+        if (file.getHandle() == null) {
+            throw new IOException("The file does not have a valid handle!");
+        }
+
+        if (file.getSFTPSubsystem() == null) {
+            throw new IOException(
+                "The file is not attached to an SFTP subsystem!");
+        }
+
+        this.file = file;
+    }
+
+    /**
+     *
+     *
+     * @param buffer
+     * @param offset
+     * @param len
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public int read(byte[] buffer, int offset, int len)
+        throws IOException {
+        int read = file.getSFTPSubsystem().readFile(file.getHandle(), position,
+                buffer, offset, len);
+
+        if (read > 0) {
+            position = UnsignedInteger64.add(position, read);
+        }
+
+        return read;
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws java.io.IOException
+     */
+    public int read() throws java.io.IOException {
+        byte[] buffer = new byte[1];
+        int read = file.getSFTPSubsystem().readFile(file.getHandle(), position,
+                buffer, 0, 1);
+        position = UnsignedInteger64.add(position, read);
+
+        return buffer[0] & 0xFF;
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    public void close() throws IOException {
+        file.close();
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    protected void finalize() throws IOException {
+        if (file.getHandle() != null) {
+            close();
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SftpFileOutputStream.java b/src/com/sshtools/j2ssh/sftp/SftpFileOutputStream.java
new file mode 100644
index 0000000000000000000000000000000000000000..a9a271ddc73c5988c6442517f6271394e8b6e2ac
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SftpFileOutputStream.java
@@ -0,0 +1,124 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.*;
+
+import java.io.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.17 $
+ */
+public class SftpFileOutputStream extends OutputStream {
+    SftpFile file;
+    UnsignedInteger64 position = new UnsignedInteger64("0");
+
+    /**
+     * Creates a new SftpFileOutputStream object.
+     *
+     * @param file
+     *
+     * @throws IOException
+     */
+    public SftpFileOutputStream(SftpFile file) throws IOException {
+        if (file.getHandle() == null) {
+            throw new IOException("The file does not have a valid handle!");
+        }
+
+        if (file.getSFTPSubsystem() == null) {
+            throw new IOException(
+                "The file is not attached to an SFTP subsystem!");
+        }
+
+        this.file = file;
+    }
+
+    /**
+     *
+     *
+     * @param buffer
+     * @param offset
+     * @param len
+     *
+     * @throws IOException
+     */
+    public void write(byte[] buffer, int offset, int len)
+        throws IOException {
+        int pos = 0;
+        int count;
+        int available;
+        int max = (int) file.getSFTPSubsystem().maximumPacketSize();
+
+        while (pos < len) {
+            available = ((int) file.getSFTPSubsystem().availableWindowSpace() < max)
+                ? (int) file.getSFTPSubsystem().availableWindowSpace() : max;
+            count = (available < (len - pos)) ? available : (len - pos);
+            file.getSFTPSubsystem().writeFile(file.getHandle(), position,
+                buffer, offset + pos, count);
+            position = UnsignedInteger64.add(position, count);
+            pos += count;
+        }
+    }
+
+    /**
+     *
+     *
+     * @param b
+     *
+     * @throws IOException
+     */
+    public void write(int b) throws IOException {
+        byte[] buffer = new byte[1];
+        buffer[0] = (byte) b;
+        file.getSFTPSubsystem().writeFile(file.getHandle(), position, buffer,
+            0, 1);
+        position = UnsignedInteger64.add(position, 1);
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    public void close() throws IOException {
+        file.close();
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    protected void finalize() throws IOException {
+        if (file.getHandle() != null) {
+            close();
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SftpMessageStore.java b/src/com/sshtools/j2ssh/sftp/SftpMessageStore.java
new file mode 100644
index 0000000000000000000000000000000000000000..b9ee10aabf4b82088b4eed1559a7511c310b5f6b
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SftpMessageStore.java
@@ -0,0 +1,87 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+import com.sshtools.j2ssh.subsystem.SubsystemMessageStore;
+import com.sshtools.j2ssh.util.OpenClosedState;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.util.Iterator;
+
+
+class SftpMessageStore extends SubsystemMessageStore {
+    /**  */
+    public static Log log = LogFactory.getLog(SftpMessageStore.class);
+
+    /**
+     * Creates a new SftpMessageStore object.
+     */
+    public SftpMessageStore() {
+    }
+
+    /**
+     *
+     *
+     * @param requestId
+     *
+     * @return
+     *
+     * @throws InterruptedException
+     */
+    public synchronized SubsystemMessage getMessage(UnsignedInteger32 requestId)
+        throws InterruptedException {
+        Iterator it;
+        SubsystemMessage msg;
+
+        // If there ae no messages available then wait untill there are.
+        while (getState().getValue() == OpenClosedState.OPEN) {
+            if (messages.size() > 0) {
+                it = messages.iterator();
+
+                while (it.hasNext()) {
+                    msg = (SubsystemMessage) it.next();
+
+                    if (msg instanceof MessageRequestId) {
+                        if (((MessageRequestId) msg).getId().equals(requestId)) {
+                            messages.remove(msg);
+
+                            return msg;
+                        }
+                    }
+                }
+            }
+
+            log.debug("Waiting for new messages");
+            wait(5000);
+        }
+
+        return null;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SftpSubsystemClient.java b/src/com/sshtools/j2ssh/sftp/SftpSubsystemClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..4735198cd2fe7174f7b0dd4810a4487ee3d74d65
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SftpSubsystemClient.java
@@ -0,0 +1,931 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.connection.ChannelState;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.io.UnsignedInteger64;
+import com.sshtools.j2ssh.subsystem.SubsystemChannel;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+import com.sshtools.j2ssh.transport.MessageNotAvailableException;
+import com.sshtools.j2ssh.transport.MessageStoreEOFException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.38 $
+ */
+public class SftpSubsystemClient extends SubsystemChannel {
+    /**  */
+    public static final int OPEN_READ = SshFxpOpen.FXF_READ;
+
+    /**  */
+    public static final int OPEN_WRITE = SshFxpOpen.FXF_WRITE;
+
+    /**  */
+    public static final int OPEN_APPEND = SshFxpOpen.FXF_APPEND;
+
+    /**  */
+    public static final int OPEN_CREATE = SshFxpOpen.FXF_CREAT;
+
+    /**  */
+    public static final int OPEN_TRUNCATE = SshFxpOpen.FXF_TRUNC;
+
+    /**  */
+    public static final int OPEN_EXCLUSIVE = SshFxpOpen.FXF_EXCL;
+
+    /**  */
+    public static final int VERSION_1 = 1;
+
+    /**  */
+    public static final int VERSION_2 = 2;
+
+    /**  */
+    public static final int VERSION_3 = 3;
+
+    /**  */
+    public static final int VERSION_4 = 4;
+
+    /* Private variables */
+    private static Log log = LogFactory.getLog(SftpSubsystemClient.class);
+    private List handles = new Vector();
+    private UnsignedInteger32 nextRequestId = new UnsignedInteger32(1);
+    private int version = VERSION_3;
+    private SftpMessageStore messageStore;
+
+    /**
+     * Creates a new SftpSubsystemClient object.
+     */
+    public SftpSubsystemClient() {
+        // We will use our own message store implementation
+        super("sftp", new SftpMessageStore());
+        messageStore = (SftpMessageStore) super.messageStore;
+        registerMessages();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getName() {
+        return "sftp";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected long availableWindowSpace() {
+        return getRemoteWindow().getWindowSpace();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected long maximumPacketSize() {
+        return getRemotePacketSize();
+    }
+
+    /**
+     *
+     *
+     * @param handle
+     *
+     * @throws IOException
+     */
+    protected synchronized void closeHandle(byte[] handle)
+        throws IOException {
+        if (!isValidHandle(handle)) {
+            throw new IOException("The handle is invalid!");
+        }
+
+        // We will remove the handle first so that even if an excpetion occurs
+        // the file as far as were concerned is closed
+        handles.remove(handle);
+
+        UnsignedInteger32 requestId = nextRequestId();
+        SshFxpClose msg = new SshFxpClose(requestId, handle);
+        sendMessage(msg);
+        getOKRequestStatus(requestId);
+    }
+
+    /**
+     *
+     *
+     * @param file
+     *
+     * @throws IOException
+     */
+    public void closeFile(SftpFile file) throws IOException {
+        closeHandle(file.getHandle());
+    }
+
+    /**
+     *
+     *
+     * @param handle
+     *
+     * @return
+     */
+    protected boolean isValidHandle(byte[] handle) {
+        return handles.contains(handle);
+    }
+
+    /**
+     *
+     *
+     * @param file
+     * @param children
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public synchronized int listChildren(SftpFile file, List children)
+        throws IOException {
+        if (file.isDirectory()) {
+            if (!isValidHandle(file.getHandle())) {
+                file = openDirectory(file.getAbsolutePath());
+
+                if (!isValidHandle(file.getHandle())) {
+                    throw new IOException("Failed to open directory");
+                }
+            }
+        } else {
+            throw new IOException("Cannot list children for this file object");
+        }
+
+        UnsignedInteger32 requestId = nextRequestId();
+        SshFxpReadDir msg = new SshFxpReadDir(requestId, file.getHandle());
+        sendMessage(msg);
+
+        try {
+            SubsystemMessage reply = messageStore.getMessage(requestId);
+
+            if (reply instanceof SshFxpName) {
+                SshFxpName names = (SshFxpName) reply;
+                SftpFile[] files = names.getFiles();
+                SftpFile f;
+
+                for (int i = 0; i < files.length; i++) {
+                    f = new SftpFile(file.getAbsolutePath() + "/" +
+                            files[i].getFilename(), files[i].getAttributes());
+                    f.setSFTPSubsystem(this);
+                    children.add(f);
+                }
+
+                return files.length;
+            } else if (reply instanceof SshFxpStatus) {
+                SshFxpStatus status = (SshFxpStatus) reply;
+
+                if (status.getErrorCode().intValue() == SshFxpStatus.STATUS_FX_EOF) {
+                    return -1;
+                } else {
+                    throw new IOException(status.getErrorMessage());
+                }
+            } else {
+                throw new IOException("Unexpected server response " +
+                    reply.getMessageName());
+            }
+        } catch (InterruptedException ex) {
+            throw new IOException("The thread was interrupted");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param path
+     *
+     * @throws IOException
+     */
+    public synchronized void makeDirectory(String path)
+        throws IOException {
+        UnsignedInteger32 requestId = nextRequestId();
+        SshFxpMkdir msg = new SshFxpMkdir(requestId, path, new FileAttributes());
+        sendMessage(msg);
+        getOKRequestStatus(requestId);
+    }
+
+    /**
+     *
+     *
+     * @param path
+     *
+     * @throws IOException
+     */
+    public void recurseMakeDirectory(String path) throws IOException {
+        SftpFile file;
+
+        if (path.trim().length() > 0) {
+            try {
+                file = openDirectory(path);
+                file.close();
+            } catch (IOException ioe) {
+                StringTokenizer tokenizer = new StringTokenizer(path, "/", true);
+                String dir = "";
+
+                while (tokenizer.hasMoreElements()) {
+                    dir += tokenizer.nextElement();
+
+                    try {
+                        file = openDirectory(dir);
+                        file.close();
+                    } catch (IOException ioe2) {
+                        log.info("Creating " + dir);
+                        makeDirectory(dir);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+
+    /*protected boolean onStart() throws IOException {
+      return initialize();
+       }*/
+
+    /**
+     *
+     *
+     * @param path
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public synchronized SftpFile openDirectory(String path)
+        throws IOException {
+        String absolutePath = getAbsolutePath(path);
+        UnsignedInteger32 requestId = nextRequestId();
+        SubsystemMessage msg = new SshFxpOpenDir(requestId, absolutePath);
+        sendMessage(msg);
+
+        byte[] handle = getHandleResponse(requestId);
+        requestId = nextRequestId();
+        msg = new SshFxpStat(requestId, absolutePath);
+        sendMessage(msg);
+
+        try {
+            SubsystemMessage reply = messageStore.getMessage(requestId);
+
+            if (reply instanceof SshFxpAttrs) {
+                SftpFile file = new SftpFile(absolutePath,
+                        ((SshFxpAttrs) reply).getAttributes());
+                file.setHandle(handle);
+                file.setSFTPSubsystem(this);
+
+                return file;
+            } else if (reply instanceof SshFxpStatus) {
+                throw new IOException(((SshFxpStatus) reply).getErrorMessage());
+            } else {
+                throw new IOException("Unexpected server response " +
+                    reply.getMessageName());
+            }
+        } catch (InterruptedException ex) {
+            throw new IOException("The thread was interrupted");
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public String getDefaultDirectory() throws IOException {
+        return getAbsolutePath("");
+    }
+
+    /**
+     *
+     *
+     * @param path
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public synchronized String getAbsolutePath(String path)
+        throws IOException {
+        UnsignedInteger32 requestId = nextRequestId();
+        SubsystemMessage msg = new SshFxpRealPath(requestId, path);
+        sendMessage(msg);
+
+        try {
+            SubsystemMessage reply = messageStore.getMessage(requestId);
+
+            if (reply instanceof SshFxpName) {
+                SftpFile[] files = ((SshFxpName) reply).getFiles();
+
+                if (files.length != 1) {
+                    throw new IOException(
+                        "Server responded to SSH_FXP_REALPATH with too many files!");
+                }
+
+                return files[0].getAbsolutePath();
+            } else if (reply instanceof SshFxpStatus) {
+                throw new IOException(((SshFxpStatus) reply).getErrorMessage());
+            } else {
+                throw new IOException("Unexpected server response " +
+                    reply.getMessageName());
+            }
+        } catch (InterruptedException ex) {
+            throw new IOException("The thread was interrupted");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param file
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public String getAbsolutePath(SftpFile file) throws IOException {
+        return getAbsolutePath(file.getFilename());
+    }
+
+    /**
+     *
+     *
+     * @param filename
+     * @param flags
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public SftpFile openFile(String filename, int flags)
+        throws IOException {
+        return openFile(filename, flags, null);
+    }
+
+    /**
+     *
+     *
+     * @param absolutePath
+     * @param flags
+     * @param attrs
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public synchronized SftpFile openFile(String absolutePath, int flags,
+        FileAttributes attrs) throws IOException {
+        if (attrs == null) {
+            attrs = new FileAttributes();
+        }
+
+        UnsignedInteger32 requestId = nextRequestId();
+        SubsystemMessage msg = new SshFxpOpen(requestId, absolutePath,
+                new UnsignedInteger32(flags), attrs);
+        sendMessage(msg);
+
+        byte[] handle = getHandleResponse(requestId);
+        SftpFile file = new SftpFile(absolutePath, null);
+        file.setHandle(handle);
+        file.setSFTPSubsystem(this);
+
+        return file;
+    }
+
+    /**
+     *
+     *
+     * @param path
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public synchronized FileAttributes getAttributes(String path)
+        throws IOException {
+        SubsystemMessage msg;
+        UnsignedInteger32 requestId = nextRequestId();
+        msg = new SshFxpStat(requestId, path);
+        sendMessage(msg);
+
+        try {
+            SubsystemMessage reply = messageStore.getMessage(requestId);
+
+            if (reply instanceof SshFxpAttrs) {
+                return ((SshFxpAttrs) reply).getAttributes();
+            } else if (reply instanceof SshFxpStatus) {
+                throw new IOException(((SshFxpStatus) reply).getErrorMessage());
+            } else {
+                throw new IOException("Unexpected server response " +
+                    reply.getMessageName());
+            }
+        } catch (InterruptedException ex) {
+            throw new IOException("The thread was interrupted");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param file
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public synchronized FileAttributes getAttributes(SftpFile file)
+        throws IOException {
+        SubsystemMessage msg;
+        UnsignedInteger32 requestId = nextRequestId();
+
+        if (!isValidHandle(file.getHandle())) {
+            msg = new SshFxpStat(requestId, file.getAbsolutePath());
+        } else {
+            msg = new SshFxpFStat(requestId, file.getHandle());
+        }
+
+        sendMessage(msg);
+
+        try {
+            SubsystemMessage reply = messageStore.getMessage(requestId);
+
+            if (reply instanceof SshFxpAttrs) {
+                return ((SshFxpAttrs) reply).getAttributes();
+            } else if (reply instanceof SshFxpStatus) {
+                throw new IOException(((SshFxpStatus) reply).getErrorMessage());
+            } else {
+                throw new IOException("Unexpected server response " +
+                    reply.getMessageName());
+            }
+        } catch (InterruptedException ex) {
+            throw new IOException("The thread was interrupted");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param handle
+     * @param offset
+     * @param output
+     * @param off
+     * @param len
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    protected synchronized int readFile(byte[] handle,
+        UnsignedInteger64 offset, byte[] output, int off, int len)
+        throws IOException {
+        if (!handles.contains(handle)) {
+            throw new IOException("The file handle is invalid!");
+        }
+
+        if ((output.length - off) < len) {
+            throw new IOException(
+                "Output array size is smaller than read length!");
+        }
+
+        UnsignedInteger32 requestId = nextRequestId();
+        SshFxpRead msg = new SshFxpRead(requestId, handle, offset,
+                new UnsignedInteger32(len));
+        sendMessage(msg);
+
+        try {
+            SubsystemMessage reply = messageStore.getMessage(requestId);
+
+            if (reply instanceof SshFxpData) {
+                byte[] msgdata = ((SshFxpData) reply).getData();
+                System.arraycopy(msgdata, 0, output, off, msgdata.length);
+
+                return msgdata.length;
+            } else if (reply instanceof SshFxpStatus) {
+                SshFxpStatus status = (SshFxpStatus) reply;
+
+                if (status.getErrorCode().intValue() == SshFxpStatus.STATUS_FX_EOF) {
+                    return -1;
+                } else {
+                    throw new IOException(((SshFxpStatus) reply).getErrorMessage());
+                }
+            } else {
+                throw new IOException("Unexpected server response " +
+                    reply.getMessageName());
+            }
+        } catch (InterruptedException ex) {
+            throw new IOException("The thread was interrupted");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param path
+     *
+     * @throws IOException
+     */
+    public synchronized void removeDirectory(String path)
+        throws IOException {
+        UnsignedInteger32 requestId = nextRequestId();
+        SshFxpRmdir msg = new SshFxpRmdir(requestId, path);
+        sendMessage(msg);
+        getOKRequestStatus(requestId);
+    }
+
+    /**
+     *
+     *
+     * @param filename
+     *
+     * @throws IOException
+     */
+    public synchronized void removeFile(String filename)
+        throws IOException {
+        UnsignedInteger32 requestId = nextRequestId();
+        SshFxpRemove msg = new SshFxpRemove(requestId, filename);
+        sendMessage(msg);
+        getOKRequestStatus(requestId);
+    }
+
+    /**
+     *
+     *
+     * @param oldpath
+     * @param newpath
+     *
+     * @throws IOException
+     */
+    public synchronized void renameFile(String oldpath, String newpath)
+        throws IOException {
+        UnsignedInteger32 requestId = nextRequestId();
+        SshFxpRename msg = new SshFxpRename(requestId, oldpath, newpath);
+        sendMessage(msg);
+        getOKRequestStatus(requestId);
+    }
+
+    /**
+     *
+     *
+     * @param handle
+     * @param offset
+     * @param data
+     * @param off
+     * @param len
+     *
+     * @throws IOException
+     */
+    protected synchronized void writeFile(byte[] handle,
+        UnsignedInteger64 offset, byte[] data, int off, int len)
+        throws IOException {
+        if (!handles.contains(handle)) {
+            throw new IOException("The handle is not valid!");
+        }
+
+        if ((data.length - off) < len) {
+            throw new IOException("Incorrect data array size!");
+        }
+
+        UnsignedInteger32 requestId = nextRequestId();
+        SshFxpWrite msg = new SshFxpWrite(requestId, handle, offset, data, off,
+                len);
+        sendMessage(msg);
+        getOKRequestStatus(requestId);
+    }
+
+    /**
+     *
+     *
+     * @param targetpath
+     * @param linkpath
+     *
+     * @throws IOException
+     */
+    public synchronized void createSymbolicLink(String targetpath,
+        String linkpath) throws IOException {
+        UnsignedInteger32 requestId = nextRequestId();
+        SubsystemMessage msg = new SshFxpSymlink(requestId, targetpath, linkpath);
+        sendMessage(msg);
+        getOKRequestStatus(requestId);
+    }
+
+    /**
+     *
+     *
+     * @param linkpath
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public synchronized String getSymbolicLinkTarget(String linkpath)
+        throws IOException {
+        UnsignedInteger32 requestId = nextRequestId();
+        SubsystemMessage msg = new SshFxpReadlink(requestId, linkpath);
+        sendMessage(msg);
+
+        try {
+            SubsystemMessage reply = messageStore.getMessage(requestId);
+
+            if (reply instanceof SshFxpName) {
+                SftpFile[] files = ((SshFxpName) reply).getFiles();
+
+                if (files.length != 1) {
+                    throw new IOException(
+                        "Server responded to SSH_FXP_REALLINK with too many files!");
+                }
+
+                return files[0].getAbsolutePath();
+            } else if (reply instanceof SshFxpStatus) {
+                throw new IOException(((SshFxpStatus) reply).getErrorMessage());
+            } else {
+                throw new IOException("Unexpected server response " +
+                    reply.getMessageName());
+            }
+        } catch (InterruptedException ex) {
+            throw new IOException("The thread was interrupted");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param path
+     * @param attrs
+     *
+     * @throws IOException
+     */
+    public synchronized void setAttributes(String path, FileAttributes attrs)
+        throws IOException {
+        UnsignedInteger32 requestId = nextRequestId();
+        SubsystemMessage msg = new SshFxpSetStat(requestId, path, attrs);
+        sendMessage(msg);
+        getOKRequestStatus(requestId);
+    }
+
+    /**
+     *
+     *
+     * @param file
+     * @param attrs
+     *
+     * @throws IOException
+     */
+    public synchronized void setAttributes(SftpFile file, FileAttributes attrs)
+        throws IOException {
+        if (!isValidHandle(file.getHandle())) {
+            throw new IOException("The handle is not an open file handle!");
+        }
+
+        UnsignedInteger32 requestId = nextRequestId();
+        SubsystemMessage msg = new SshFxpFSetStat(requestId, file.getHandle(),
+                attrs);
+        sendMessage(msg);
+        getOKRequestStatus(requestId);
+    }
+
+    /**
+     *
+     *
+     * @param file
+     * @param permissions
+     *
+     * @throws IOException
+     */
+    public void changePermissions(SftpFile file, String permissions)
+        throws IOException {
+        FileAttributes attrs = new FileAttributes(); //file.getAttributes();
+        attrs.setPermissions(permissions);
+        setAttributes(file, attrs);
+    }
+
+    /**
+     *
+     *
+     * @param file
+     * @param permissions
+     *
+     * @throws IOException
+     */
+    public void changePermissions(SftpFile file, int permissions)
+        throws IOException {
+        FileAttributes attrs = new FileAttributes(); //file.getAttributes();
+        attrs.setPermissions(new UnsignedInteger32(permissions));
+        setAttributes(file, attrs);
+    }
+
+    /**
+     *
+     *
+     * @param filename
+     * @param permissions
+     *
+     * @throws IOException
+     */
+    public void changePermissions(String filename, int permissions)
+        throws IOException {
+        FileAttributes attrs = new FileAttributes();
+        attrs.setPermissions(new UnsignedInteger32(permissions));
+        setAttributes(filename, attrs);
+    }
+
+    /**
+     *
+     *
+     * @param filename
+     * @param permissions
+     *
+     * @throws IOException
+     */
+    public void changePermissions(String filename, String permissions)
+        throws IOException {
+        FileAttributes attrs = new FileAttributes();
+        attrs.setPermissions(permissions);
+        setAttributes(filename, attrs);
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public synchronized boolean initialize() throws IOException {
+        log.info("Initializing SFTP protocol version " +
+            String.valueOf(version));
+
+        if (!startSubsystem()) {
+            return false;
+        }
+
+        boolean result = false;
+        SshFxpInit msg = new SshFxpInit(new UnsignedInteger32(version), null);
+        sendMessage(msg);
+
+        // Lets give the sftp subsystem 30 seconds to reply
+        SubsystemMessage reply = null;
+
+        for (int i = 0; i < 30; i++) {
+            try {
+                reply = messageStore.nextMessage(1000);
+
+                break;
+            } catch (MessageNotAvailableException ex) {
+                // We timed out so just continue by looking at the session state
+            } catch (MessageStoreEOFException ex) {
+                return false;
+            }
+
+            if (getState().getValue() != ChannelState.CHANNEL_OPEN) {
+                return false;
+            }
+
+            // Try again
+        }
+
+        if (reply instanceof SshFxpVersion) {
+            result = true;
+            version = ((SshFxpVersion) reply).getVersion().intValue();
+            log.info("Server responded with version " +
+                String.valueOf(version));
+        }
+
+        return result;
+    }
+
+    private byte[] getHandleResponse(UnsignedInteger32 requestId)
+        throws IOException {
+        try {
+            SubsystemMessage reply = messageStore.getMessage(requestId);
+
+            if (reply instanceof SshFxpHandle) {
+                byte[] handle = ((SshFxpHandle) reply).getHandle();
+
+                // Add the handle to our managed list
+                handles.add(handle);
+
+                return handle;
+            } else if (reply instanceof SshFxpStatus) {
+                throw new IOException(((SshFxpStatus) reply).getErrorMessage());
+            } else {
+                throw new IOException("Unexpected server response " +
+                    reply.getMessageName());
+            }
+        } catch (InterruptedException ex) {
+            throw new IOException("The thread was interrupted");
+        }
+    }
+
+    private void getOKRequestStatus(UnsignedInteger32 requestId)
+        throws IOException {
+        try {
+            if (log.isDebugEnabled()) {
+                log.info("Waiting for response");
+            }
+
+            SubsystemMessage reply = messageStore.getMessage(requestId);
+            log.info("Received response");
+
+            if (reply instanceof SshFxpStatus) {
+              SshFxpStatus status = (SshFxpStatus) reply;
+              
+              if (status.getErrorCode().intValue() != SshFxpStatus.STATUS_FX_OK) {
+                throw new IOException(((SshFxpStatus) reply).getErrorMessage());
+              }
+            } else {
+              throw new IOException("Unexpected server response " +
+                  reply.getMessageName());
+            }
+        } catch (InterruptedException ex) {
+          throw new IOException("The thread was interrupted");
+        }
+    }
+
+    private UnsignedInteger32 nextRequestId() {
+        nextRequestId = UnsignedInteger32.add(nextRequestId, 1);
+
+        return nextRequestId;
+    }
+
+    private void registerMessages() {
+        messageStore.registerMessage(SshFxpVersion.SSH_FXP_VERSION,
+            SshFxpVersion.class);
+        messageStore.registerMessage(SshFxpAttrs.SSH_FXP_ATTRS,
+            SshFxpAttrs.class);
+        messageStore.registerMessage(SshFxpData.SSH_FXP_DATA, SshFxpData.class);
+        messageStore.registerMessage(SshFxpHandle.SSH_FXP_HANDLE,
+            SshFxpHandle.class);
+        messageStore.registerMessage(SshFxpStatus.SSH_FXP_STATUS,
+            SshFxpStatus.class);
+        messageStore.registerMessage(SshFxpName.SSH_FXP_NAME, SshFxpName.class);
+    }
+
+    protected int getMinimumWindowSpace() {
+        return 1024;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected int getMaximumWindowSpace() {
+        return 131070;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected int getMaximumPacketSize() {
+        return 65535;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SshFxpAttrs.java b/src/com/sshtools/j2ssh/sftp/SshFxpAttrs.java
new file mode 100644
index 0000000000000000000000000000000000000000..1d4c61f8ca52e8000ff2bbb81bd2dce28b228e44
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SshFxpAttrs.java
@@ -0,0 +1,123 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public class SshFxpAttrs extends SubsystemMessage implements MessageRequestId {
+    /**  */
+    public static final int SSH_FXP_ATTRS = 105;
+    private UnsignedInteger32 id;
+    private FileAttributes attrs;
+
+    /**
+     * Creates a new SshFxpAttrs object.
+     */
+    public SshFxpAttrs() {
+        super(SSH_FXP_ATTRS);
+    }
+
+    /**
+     * Creates a new SshFxpAttrs object.
+     *
+     * @param id
+     * @param attrs
+     */
+    public SshFxpAttrs(UnsignedInteger32 id, FileAttributes attrs) {
+        super(SSH_FXP_ATTRS);
+        this.id = id;
+        this.attrs = attrs;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getId() {
+        return id;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public FileAttributes getAttributes() {
+        return attrs;
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        id = bar.readUINT32();
+        attrs = new FileAttributes(bar);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_FXP_ATTRS";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        baw.writeUINT32(id);
+        baw.write(attrs.toByteArray());
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SshFxpClose.java b/src/com/sshtools/j2ssh/sftp/SshFxpClose.java
new file mode 100644
index 0000000000000000000000000000000000000000..18dd9c1796b2d526054e85d2bc40ea3dad817946
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SshFxpClose.java
@@ -0,0 +1,123 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.17 $
+ */
+public class SshFxpClose extends SubsystemMessage implements MessageRequestId {
+    /**  */
+    public static final int SSH_FXP_CLOSE = 4;
+    private UnsignedInteger32 id;
+    private byte[] handle;
+
+    /**
+     * Creates a new SshFxpClose object.
+     */
+    public SshFxpClose() {
+        super(SSH_FXP_CLOSE);
+    }
+
+    /**
+     * Creates a new SshFxpClose object.
+     *
+     * @param id
+     * @param handle
+     */
+    public SshFxpClose(UnsignedInteger32 id, byte[] handle) {
+        super(SSH_FXP_CLOSE);
+        this.id = id;
+        this.handle = handle;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getId() {
+        return id;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getHandle() {
+        return handle;
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        id = bar.readUINT32();
+        handle = bar.readBinaryString();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_FXP_CLOSE";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        baw.writeUINT32(id);
+        baw.writeBinaryString(handle);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SshFxpData.java b/src/com/sshtools/j2ssh/sftp/SshFxpData.java
new file mode 100644
index 0000000000000000000000000000000000000000..bf306678e08f7014ac8992c0061c17da68855dc8
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SshFxpData.java
@@ -0,0 +1,123 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public class SshFxpData extends SubsystemMessage implements MessageRequestId {
+    /**  */
+    public static final int SSH_FXP_DATA = 103;
+    private UnsignedInteger32 id;
+    private byte[] data;
+
+    /**
+     * Creates a new SshFxpData object.
+     *
+     * @param id
+     * @param data
+     */
+    public SshFxpData(UnsignedInteger32 id, byte[] data) {
+        super(SSH_FXP_DATA);
+        this.id = id;
+        this.data = data;
+    }
+
+    /**
+     * Creates a new SshFxpData object.
+     */
+    public SshFxpData() {
+        super(SSH_FXP_DATA);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getId() {
+        return id;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getData() {
+        return data;
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        id = bar.readUINT32();
+        data = bar.readBinaryString();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_FXP_DATA";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        baw.writeUINT32(id);
+        baw.writeBinaryString(data);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SshFxpFSetStat.java b/src/com/sshtools/j2ssh/sftp/SshFxpFSetStat.java
new file mode 100644
index 0000000000000000000000000000000000000000..87572545b8f13fc67313fbd3d091acb5963e5010
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SshFxpFSetStat.java
@@ -0,0 +1,138 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class SshFxpFSetStat extends SubsystemMessage implements MessageRequestId {
+    /**  */
+    public static final int SSH_FXP_FSETSTAT = 10;
+    private UnsignedInteger32 id;
+    private byte[] handle;
+    private FileAttributes attrs;
+
+    /**
+     * Creates a new SshFxpFSetStat object.
+     */
+    public SshFxpFSetStat() {
+        super(SSH_FXP_FSETSTAT);
+    }
+
+    /**
+     * Creates a new SshFxpFSetStat object.
+     *
+     * @param id
+     * @param handle
+     * @param attrs
+     */
+    public SshFxpFSetStat(UnsignedInteger32 id, byte[] handle,
+        FileAttributes attrs) {
+        super(SSH_FXP_FSETSTAT);
+        this.id = id;
+        this.handle = handle;
+        this.attrs = attrs;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getId() {
+        return id;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getHandle() {
+        return handle;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public FileAttributes getAttributes() {
+        return attrs;
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        id = bar.readUINT32();
+        handle = bar.readBinaryString();
+        attrs = new FileAttributes(bar);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_FXP_FSETSTAT";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        baw.writeUINT32(id);
+        baw.writeBinaryString(handle);
+        baw.write(attrs.toByteArray());
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SshFxpFStat.java b/src/com/sshtools/j2ssh/sftp/SshFxpFStat.java
new file mode 100644
index 0000000000000000000000000000000000000000..dc1d16351f81bd3b8d1f6a0b0fe820a1e362685f
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SshFxpFStat.java
@@ -0,0 +1,123 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public class SshFxpFStat extends SubsystemMessage implements MessageRequestId {
+    /**  */
+    public static final int SSH_FXP_FSTAT = 8;
+    private UnsignedInteger32 id;
+    private byte[] handle;
+
+    /**
+     * Creates a new SshFxpFStat object.
+     */
+    public SshFxpFStat() {
+        super(SSH_FXP_FSTAT);
+    }
+
+    /**
+     * Creates a new SshFxpFStat object.
+     *
+     * @param id
+     * @param handle
+     */
+    public SshFxpFStat(UnsignedInteger32 id, byte[] handle) {
+        super(SSH_FXP_FSTAT);
+        this.id = id;
+        this.handle = handle;
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        id = bar.readUINT32();
+        handle = bar.readBinaryString();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_FXP_FSTAT";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getId() {
+        return id;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getHandle() {
+        return handle;
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        baw.writeUINT32(id);
+        baw.writeBinaryString(handle);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SshFxpHandle.java b/src/com/sshtools/j2ssh/sftp/SshFxpHandle.java
new file mode 100644
index 0000000000000000000000000000000000000000..1215f3466668fb1c5d52ca064b8b6ad962707a0f
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SshFxpHandle.java
@@ -0,0 +1,123 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.17 $
+ */
+public class SshFxpHandle extends SubsystemMessage implements MessageRequestId {
+    /**  */
+    public static final int SSH_FXP_HANDLE = 102;
+    private UnsignedInteger32 id;
+    private byte[] handle;
+
+    /**
+     * Creates a new SshFxpHandle object.
+     *
+     * @param id
+     * @param handle
+     */
+    public SshFxpHandle(UnsignedInteger32 id, byte[] handle) {
+        super(SSH_FXP_HANDLE);
+        this.id = id;
+        this.handle = handle;
+    }
+
+    /**
+     * Creates a new SshFxpHandle object.
+     */
+    public SshFxpHandle() {
+        super(SSH_FXP_HANDLE);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getId() {
+        return id;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getHandle() {
+        return handle;
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        id = bar.readUINT32();
+        handle = bar.readBinaryString();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_FXP_HANDLE";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        baw.writeUINT32(id);
+        baw.writeBinaryString(handle);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SshFxpInit.java b/src/com/sshtools/j2ssh/sftp/SshFxpInit.java
new file mode 100644
index 0000000000000000000000000000000000000000..593b1cbe340908fde8c1ce724cd45f6d8f1e0c30
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SshFxpInit.java
@@ -0,0 +1,147 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+
+import java.io.IOException;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public class SshFxpInit extends SubsystemMessage {
+    /**  */
+    public static final int SSH_FXP_INIT = 1;
+    private UnsignedInteger32 version;
+    private Map extended;
+
+    /**
+     * Creates a new SshFxpInit object.
+     */
+    public SshFxpInit() {
+        super(SSH_FXP_INIT);
+    }
+
+    /**
+     * Creates a new SshFxpInit object.
+     *
+     * @param version
+     * @param extended
+     */
+    public SshFxpInit(UnsignedInteger32 version, Map extended) {
+        super(SSH_FXP_INIT);
+        this.version = version;
+        this.extended = extended;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getVersion() {
+        return version;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public Map getExtended() {
+        return extended;
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws IOException
+     * @throws InvalidMessageException
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws IOException, InvalidMessageException {
+        version = bar.readUINT32();
+        extended = new HashMap();
+
+        String key;
+        String value;
+
+        while (bar.available() > 0) {
+            key = bar.readString();
+            value = bar.readString();
+            extended.put(key, value);
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_FXP_INIT";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws IOException
+     * @throws InvalidMessageException
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws IOException, InvalidMessageException {
+        baw.writeUINT32(version);
+
+        if (extended != null) {
+            if (extended.size() > 0) {
+                Iterator it = extended.entrySet().iterator();
+                Map.Entry entry;
+
+                while (it.hasNext()) {
+                    entry = (Map.Entry) it.next();
+                    baw.writeString((String) entry.getKey());
+                    baw.writeString((String) entry.getValue());
+                }
+            }
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SshFxpLStat.java b/src/com/sshtools/j2ssh/sftp/SshFxpLStat.java
new file mode 100644
index 0000000000000000000000000000000000000000..70a8c6b6530a7fb43f7d68acb4c301c569efebfb
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SshFxpLStat.java
@@ -0,0 +1,123 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class SshFxpLStat extends SubsystemMessage implements MessageRequestId {
+    /**  */
+    public static final int SSH_FXP_LSTAT = 7;
+    private UnsignedInteger32 id;
+    private String path;
+
+    /**
+     * Creates a new SshFxpLStat object.
+     */
+    public SshFxpLStat() {
+        super(SSH_FXP_LSTAT);
+    }
+
+    /**
+     * Creates a new SshFxpLStat object.
+     *
+     * @param id
+     * @param path
+     */
+    public SshFxpLStat(UnsignedInteger32 id, String path) {
+        super(SSH_FXP_LSTAT);
+        this.id = id;
+        this.path = path;
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        id = bar.readUINT32();
+        path = bar.readString();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_FXP_LSTAT";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getId() {
+        return id;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getPath() {
+        return path;
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        baw.writeUINT32(id);
+        baw.writeString(path);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SshFxpMkdir.java b/src/com/sshtools/j2ssh/sftp/SshFxpMkdir.java
new file mode 100644
index 0000000000000000000000000000000000000000..bb3efda8778b0d23382cf9586b1a6652a73ab043
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SshFxpMkdir.java
@@ -0,0 +1,137 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public class SshFxpMkdir extends SubsystemMessage implements MessageRequestId {
+    /**  */
+    public static final int SSH_FXP_MKDIR = 14;
+    private UnsignedInteger32 id;
+    private String path;
+    private FileAttributes attrs;
+
+    /**
+     * Creates a new SshFxpMkdir object.
+     */
+    public SshFxpMkdir() {
+        super(SSH_FXP_MKDIR);
+    }
+
+    /**
+     * Creates a new SshFxpMkdir object.
+     *
+     * @param id
+     * @param path
+     * @param attrs
+     */
+    public SshFxpMkdir(UnsignedInteger32 id, String path, FileAttributes attrs) {
+        super(SSH_FXP_MKDIR);
+        this.id = id;
+        this.path = path;
+        this.attrs = attrs;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getId() {
+        return id;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getPath() {
+        return path;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public FileAttributes getAttributes() {
+        return attrs;
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        id = bar.readUINT32();
+        path = bar.readString();
+        attrs = new FileAttributes(bar);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_FXP_MKDIR";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        baw.writeUINT32(id);
+        baw.writeString(path);
+        baw.write(attrs.toByteArray());
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SshFxpName.java b/src/com/sshtools/j2ssh/sftp/SshFxpName.java
new file mode 100644
index 0000000000000000000000000000000000000000..92fd2bf25c85975db13efccb5233b7ddb58ec854
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SshFxpName.java
@@ -0,0 +1,142 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.20 $
+ */
+public class SshFxpName extends SubsystemMessage implements MessageRequestId {
+    /**  */
+    public static final int SSH_FXP_NAME = 104;
+    private UnsignedInteger32 id;
+    private SftpFile[] files;
+
+    /**
+     * Creates a new SshFxpName object.
+     *
+     * @param id
+     * @param files
+     */
+    public SshFxpName(UnsignedInteger32 id, SftpFile[] files) {
+        super(SSH_FXP_NAME);
+        this.id = id;
+        this.files = files;
+    }
+
+    /**
+     * Creates a new SshFxpName object.
+     */
+    public SshFxpName() {
+        super(SSH_FXP_NAME);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getId() {
+        return id;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public SftpFile[] getFiles() {
+        return files;
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        id = bar.readUINT32();
+
+        UnsignedInteger32 count = bar.readUINT32();
+        files = new SftpFile[count.intValue()];
+
+        String shortname;
+        String longname;
+
+        for (int i = 0; i < files.length; i++) {
+            shortname = bar.readString();
+            longname = bar.readString();
+            files[i] = new SftpFile(shortname, new FileAttributes(bar));
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_FXP_NAME";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        baw.writeUINT32(id);
+        baw.writeUINT32(new UnsignedInteger32(files.length));
+
+        SftpFile file;
+
+        for (int i = 0; i < files.length; i++) {
+            baw.writeString(files[i].getAbsolutePath());
+            baw.writeString(files[i].getLongname());
+            baw.write(files[i].getAttributes().toByteArray());
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SshFxpOpen.java b/src/com/sshtools/j2ssh/sftp/SshFxpOpen.java
new file mode 100644
index 0000000000000000000000000000000000000000..a9555c781f6ddbbde04ee7b6bc0613974381c3e8
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SshFxpOpen.java
@@ -0,0 +1,162 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public class SshFxpOpen extends SubsystemMessage implements MessageRequestId {
+    /**  */
+    public static final int SSH_FXP_OPEN = 3;
+
+    /**  */
+    public static final int FXF_READ = 0x00000001;
+
+    /**  */
+    public static final int FXF_WRITE = 0x00000002;
+
+    /**  */
+    public static final int FXF_APPEND = 0x00000004;
+
+    /**  */
+    public static final int FXF_CREAT = 0x00000008;
+
+    /**  */
+    public static final int FXF_TRUNC = 0x00000010;
+
+    /**  */
+    public static final int FXF_EXCL = 0x00000020;
+    private UnsignedInteger32 id;
+    private String filename;
+    private UnsignedInteger32 pflags;
+    private FileAttributes attrs;
+
+    //public static final int FXF_TEXT = 0x00000040;
+    public SshFxpOpen(UnsignedInteger32 id, String filename,
+        UnsignedInteger32 pflags, FileAttributes attrs) {
+        super(SSH_FXP_OPEN);
+        this.id = id;
+        this.filename = filename;
+        this.pflags = pflags;
+        this.attrs = attrs;
+    }
+
+    /**
+     * Creates a new SshFxpOpen object.
+     */
+    public SshFxpOpen() {
+        super(SSH_FXP_OPEN);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getId() {
+        return id;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getFilename() {
+        return filename;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getPflags() {
+        return pflags;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public FileAttributes getAttributes() {
+        return attrs;
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws IOException
+     * @throws InvalidMessageException
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws IOException, InvalidMessageException {
+        id = bar.readUINT32();
+        filename = bar.readString();
+        pflags = bar.readUINT32();
+        attrs = new FileAttributes(bar);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_FXP_OPEN";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws IOException
+     * @throws InvalidMessageException
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws IOException, InvalidMessageException {
+        baw.writeUINT32(id);
+        baw.writeString(filename);
+        baw.writeUINT32(pflags);
+        baw.writeBinaryString(attrs.toByteArray());
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SshFxpOpenDir.java b/src/com/sshtools/j2ssh/sftp/SshFxpOpenDir.java
new file mode 100644
index 0000000000000000000000000000000000000000..23ab70665b78ac9c23cf923e50c2229ce40f8483
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SshFxpOpenDir.java
@@ -0,0 +1,123 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class SshFxpOpenDir extends SubsystemMessage implements MessageRequestId {
+    /**  */
+    public static final int SSH_FXP_OPENDIR = 11;
+    private UnsignedInteger32 id;
+    private String path;
+
+    /**
+     * Creates a new SshFxpOpenDir object.
+     */
+    public SshFxpOpenDir() {
+        super(SSH_FXP_OPENDIR);
+    }
+
+    /**
+     * Creates a new SshFxpOpenDir object.
+     *
+     * @param id
+     * @param path
+     */
+    public SshFxpOpenDir(UnsignedInteger32 id, String path) {
+        super(SSH_FXP_OPENDIR);
+        this.id = id;
+        this.path = path;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getId() {
+        return id;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getPath() {
+        return path;
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        id = bar.readUINT32();
+        path = bar.readString();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_FXP_OPEDIR";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        baw.writeUINT32(id);
+        baw.writeString(path);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SshFxpRead.java b/src/com/sshtools/j2ssh/sftp/SshFxpRead.java
new file mode 100644
index 0000000000000000000000000000000000000000..c39e34dc4ae15b121cd20204b8bda6b713de3d32
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SshFxpRead.java
@@ -0,0 +1,153 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.io.UnsignedInteger64;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.17 $
+ */
+public class SshFxpRead extends SubsystemMessage implements MessageRequestId {
+    /**  */
+    public static final int SSH_FXP_READ = 5;
+    private UnsignedInteger32 id;
+    private byte[] handle;
+    private UnsignedInteger64 offset;
+    private UnsignedInteger32 length;
+
+    /**
+     * Creates a new SshFxpRead object.
+     *
+     * @param id
+     * @param handle
+     * @param offset
+     * @param length
+     */
+    public SshFxpRead(UnsignedInteger32 id, byte[] handle,
+        UnsignedInteger64 offset, UnsignedInteger32 length) {
+        super(SSH_FXP_READ);
+        this.id = id;
+        this.handle = handle;
+        this.offset = offset;
+        this.length = length;
+    }
+
+    /**
+     * Creates a new SshFxpRead object.
+     */
+    public SshFxpRead() {
+        super(SSH_FXP_READ);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getId() {
+        return id;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getHandle() {
+        return handle;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger64 getOffset() {
+        return offset;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getLength() {
+        return length;
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        id = bar.readUINT32();
+        handle = bar.readBinaryString();
+        offset = bar.readUINT64();
+        length = bar.readUINT32();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_FXP_READ";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        baw.writeUINT32(id);
+        baw.writeBinaryString(handle);
+        baw.writeUINT64(offset);
+        baw.writeUINT32(length);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SshFxpReadDir.java b/src/com/sshtools/j2ssh/sftp/SshFxpReadDir.java
new file mode 100644
index 0000000000000000000000000000000000000000..adac68e4d29b6466d36da4553e4c19bcd0a1573f
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SshFxpReadDir.java
@@ -0,0 +1,123 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public class SshFxpReadDir extends SubsystemMessage implements MessageRequestId {
+    /**  */
+    public static final int SSH_FXP_READDIR = 12;
+    private UnsignedInteger32 id;
+    private byte[] handle;
+
+    /**
+     * Creates a new SshFxpReadDir object.
+     */
+    public SshFxpReadDir() {
+        super(SSH_FXP_READDIR);
+    }
+
+    /**
+     * Creates a new SshFxpReadDir object.
+     *
+     * @param id
+     * @param handle
+     */
+    public SshFxpReadDir(UnsignedInteger32 id, byte[] handle) {
+        super(SSH_FXP_READDIR);
+        this.id = id;
+        this.handle = handle;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getId() {
+        return id;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getHandle() {
+        return handle;
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        id = bar.readUINT32();
+        handle = bar.readBinaryString();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_FXP_READDIR";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        baw.writeUINT32(id);
+        baw.writeBinaryString(handle);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SshFxpReadlink.java b/src/com/sshtools/j2ssh/sftp/SshFxpReadlink.java
new file mode 100644
index 0000000000000000000000000000000000000000..6563c3969a4d8983458c00fe487a47328cf1d391
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SshFxpReadlink.java
@@ -0,0 +1,123 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class SshFxpReadlink extends SubsystemMessage implements MessageRequestId {
+    /**  */
+    public static final int SSH_FXP_READLINK = 19;
+    private UnsignedInteger32 id;
+    private String path;
+
+    /**
+     * Creates a new SshFxpReadlink object.
+     *
+     * @param id
+     * @param path
+     */
+    public SshFxpReadlink(UnsignedInteger32 id, String path) {
+        super(SSH_FXP_READLINK);
+        this.id = id;
+        this.path = path;
+    }
+
+    /**
+     * Creates a new SshFxpReadlink object.
+     */
+    public SshFxpReadlink() {
+        super(SSH_FXP_READLINK);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getId() {
+        return id;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getPath() {
+        return path;
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        id = bar.readUINT32();
+        path = bar.readString();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_FXP_READLINK";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        baw.writeUINT32(id);
+        baw.writeString(path);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SshFxpRealPath.java b/src/com/sshtools/j2ssh/sftp/SshFxpRealPath.java
new file mode 100644
index 0000000000000000000000000000000000000000..fd5d0658c6ff8243520fa0e3d1052e4498c5843d
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SshFxpRealPath.java
@@ -0,0 +1,123 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class SshFxpRealPath extends SubsystemMessage implements MessageRequestId {
+    /**  */
+    public static final int SSH_FXP_REALPATH = 16;
+    private UnsignedInteger32 id;
+    private String path;
+
+    /**
+     * Creates a new SshFxpRealPath object.
+     */
+    public SshFxpRealPath() {
+        super(SSH_FXP_REALPATH);
+    }
+
+    /**
+     * Creates a new SshFxpRealPath object.
+     *
+     * @param id
+     * @param path
+     */
+    public SshFxpRealPath(UnsignedInteger32 id, String path) {
+        super(SSH_FXP_REALPATH);
+        this.id = id;
+        this.path = path;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getId() {
+        return id;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getPath() {
+        return path;
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        id = bar.readUINT32();
+        path = bar.readString();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_FXP_READPATH";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        baw.writeUINT32(id);
+        baw.writeString(path);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SshFxpRemove.java b/src/com/sshtools/j2ssh/sftp/SshFxpRemove.java
new file mode 100644
index 0000000000000000000000000000000000000000..e65e350e89cf923358682e5356992ddc7a84344b
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SshFxpRemove.java
@@ -0,0 +1,123 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public class SshFxpRemove extends SubsystemMessage implements MessageRequestId {
+    /**  */
+    public static final int SSH_FXP_REMOVE = 13;
+    private UnsignedInteger32 id;
+    private String filename;
+
+    /**
+     * Creates a new SshFxpRemove object.
+     */
+    public SshFxpRemove() {
+        super(SSH_FXP_REMOVE);
+    }
+
+    /**
+     * Creates a new SshFxpRemove object.
+     *
+     * @param id
+     * @param filename
+     */
+    public SshFxpRemove(UnsignedInteger32 id, String filename) {
+        super(SSH_FXP_REMOVE);
+        this.id = id;
+        this.filename = filename;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getId() {
+        return id;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getFilename() {
+        return filename;
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        id = bar.readUINT32();
+        filename = bar.readString();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_FXP_REMOVE";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        baw.writeUINT32(id);
+        baw.writeString(filename);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SshFxpRename.java b/src/com/sshtools/j2ssh/sftp/SshFxpRename.java
new file mode 100644
index 0000000000000000000000000000000000000000..cb3e6f773a38bd97490be92a2ff1e3904e2a1f5d
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SshFxpRename.java
@@ -0,0 +1,137 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public class SshFxpRename extends SubsystemMessage implements MessageRequestId {
+    /**  */
+    public static final int SSH_FXP_RENAME = 18;
+    private UnsignedInteger32 id;
+    String oldpath;
+    String newpath;
+
+    /**
+     * Creates a new SshFxpRename object.
+     */
+    public SshFxpRename() {
+        super(SSH_FXP_RENAME);
+    }
+
+    /**
+     * Creates a new SshFxpRename object.
+     *
+     * @param id
+     * @param oldpath
+     * @param newpath
+     */
+    public SshFxpRename(UnsignedInteger32 id, String oldpath, String newpath) {
+        super(SSH_FXP_RENAME);
+        this.id = id;
+        this.oldpath = oldpath;
+        this.newpath = newpath;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getId() {
+        return id;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getOldPath() {
+        return oldpath;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getNewPath() {
+        return newpath;
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        id = bar.readUINT32();
+        oldpath = bar.readString();
+        newpath = bar.readString();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_FXP_RENAME";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        baw.writeUINT32(id);
+        baw.writeString(oldpath);
+        baw.writeString(newpath);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SshFxpRmdir.java b/src/com/sshtools/j2ssh/sftp/SshFxpRmdir.java
new file mode 100644
index 0000000000000000000000000000000000000000..7542e2a65a43b8975d84d01f8ac833a65f60bff3
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SshFxpRmdir.java
@@ -0,0 +1,123 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public class SshFxpRmdir extends SubsystemMessage implements MessageRequestId {
+    /**  */
+    public static final int SSH_FXP_RMDIR = 15;
+    private UnsignedInteger32 id;
+    private String path;
+
+    /**
+     * Creates a new SshFxpRmdir object.
+     */
+    public SshFxpRmdir() {
+        super(SSH_FXP_RMDIR);
+    }
+
+    /**
+     * Creates a new SshFxpRmdir object.
+     *
+     * @param id
+     * @param path
+     */
+    public SshFxpRmdir(UnsignedInteger32 id, String path) {
+        super(SSH_FXP_RMDIR);
+        this.id = id;
+        this.path = path;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getId() {
+        return id;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getPath() {
+        return path;
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        id = bar.readUINT32();
+        path = bar.readString();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_FXP_RMDIR";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        baw.writeUINT32(id);
+        baw.writeString(path);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SshFxpSetStat.java b/src/com/sshtools/j2ssh/sftp/SshFxpSetStat.java
new file mode 100644
index 0000000000000000000000000000000000000000..df56969312836ebce072e895bbd5e433b14df7ce
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SshFxpSetStat.java
@@ -0,0 +1,137 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class SshFxpSetStat extends SubsystemMessage implements MessageRequestId {
+    /**  */
+    public static final int SSH_FXP_SETSTAT = 9;
+    private UnsignedInteger32 id;
+    private String path;
+    private FileAttributes attrs;
+
+    /**
+     * Creates a new SshFxpSetStat object.
+     */
+    public SshFxpSetStat() {
+        super(SSH_FXP_SETSTAT);
+    }
+
+    /**
+     * Creates a new SshFxpSetStat object.
+     *
+     * @param id
+     * @param path
+     * @param attrs
+     */
+    public SshFxpSetStat(UnsignedInteger32 id, String path, FileAttributes attrs) {
+        super(SSH_FXP_SETSTAT);
+        this.id = id;
+        this.path = path;
+        this.attrs = attrs;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getId() {
+        return id;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getPath() {
+        return path;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public FileAttributes getAttributes() {
+        return attrs;
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        id = bar.readUINT32();
+        path = bar.readString();
+        attrs = new FileAttributes(bar);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_FXP_SETSTAT";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        baw.writeUINT32(id);
+        baw.writeString(path);
+        baw.write(attrs.toByteArray());
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SshFxpStat.java b/src/com/sshtools/j2ssh/sftp/SshFxpStat.java
new file mode 100644
index 0000000000000000000000000000000000000000..7f9934ebbd8a3ba91ce016223ad8e8035a4299b5
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SshFxpStat.java
@@ -0,0 +1,123 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class SshFxpStat extends SubsystemMessage implements MessageRequestId {
+    /**  */
+    public static final int SSH_FXP_STAT = 17;
+    private UnsignedInteger32 id;
+    private String path;
+
+    /**
+     * Creates a new SshFxpStat object.
+     */
+    public SshFxpStat() {
+        super(SSH_FXP_STAT);
+    }
+
+    /**
+     * Creates a new SshFxpStat object.
+     *
+     * @param id
+     * @param path
+     */
+    public SshFxpStat(UnsignedInteger32 id, String path) {
+        super(SSH_FXP_STAT);
+        this.id = id;
+        this.path = path;
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        id = bar.readUINT32();
+        path = bar.readString();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_FXP_STAT";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getId() {
+        return id;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getPath() {
+        return path;
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        baw.writeUINT32(id);
+        baw.writeString(path);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SshFxpStatus.java b/src/com/sshtools/j2ssh/sftp/SshFxpStatus.java
new file mode 100644
index 0000000000000000000000000000000000000000..f3ba4e93ac2155e2918a3b345671693c984a329e
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SshFxpStatus.java
@@ -0,0 +1,184 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.18 $
+ */
+public class SshFxpStatus extends SubsystemMessage implements MessageRequestId {
+    /**  */
+    public static final int SSH_FXP_STATUS = 101;
+
+    /**  */
+    public static final int STATUS_FX_OK = 0;
+
+    /**  */
+    public static final int STATUS_FX_EOF = 1;
+
+    /**  */
+    public static final int STATUS_FX_NO_SUCH_FILE = 2;
+
+    /**  */
+    public static final int STATUS_FX_PERMISSION_DENIED = 3;
+
+    /**  */
+    public static final int STATUS_FX_FAILURE = 4;
+
+    /**  */
+    public static final int STATUS_FX_BAD_MESSAGE = 5;
+
+    /**  */
+    public static final int STATUS_FX_NO_CONNECTION = 6;
+
+    /**  */
+    public static final int STATUS_FX_CONNECTION_LOST = 7;
+
+    /**  */
+    public static final int STATUS_FX_OP_UNSUPPORTED = 8;
+
+    //public static final int STATUS_FX_INVALID_HANDLE = 9;
+    //public static final int STATUS_FX_NO_SUCH_PATH = 10;
+    //public static final int STATUS_FX_FILE_ALREADY_EXISTS = 11;
+    //public static final int STATUS_FX_WRITE_PROTECT = 12;
+    private UnsignedInteger32 id;
+    private UnsignedInteger32 errorCode;
+    private String errorMessage;
+    private String languageTag;
+
+    /**
+     * Creates a new SshFxpStatus object.
+     *
+     * @param id
+     * @param errorCode
+     * @param errorMessage
+     * @param languageTag
+     */
+    public SshFxpStatus(UnsignedInteger32 id, UnsignedInteger32 errorCode,
+        String errorMessage, String languageTag) {
+        super(SSH_FXP_STATUS);
+        this.id = id;
+        this.errorCode = errorCode;
+        this.errorMessage = errorMessage;
+        this.languageTag = languageTag;
+    }
+
+    /**
+     * Creates a new SshFxpStatus object.
+     */
+    public SshFxpStatus() {
+        super(SSH_FXP_STATUS);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getId() {
+        return id;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getErrorCode() {
+        return errorCode;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getErrorMessage() {
+        return errorMessage;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getLanguageTag() {
+        return languageTag;
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        id = bar.readUINT32();
+        errorCode = bar.readUINT32();
+        errorMessage = bar.readString();
+        languageTag = bar.readString();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_FXP_STATUS";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        baw.writeUINT32(id);
+        baw.writeUINT32(errorCode);
+        baw.writeString(errorMessage);
+        baw.writeString(languageTag);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SshFxpSymlink.java b/src/com/sshtools/j2ssh/sftp/SshFxpSymlink.java
new file mode 100644
index 0000000000000000000000000000000000000000..8c5d9497508b81e09bf6821996b151b8afe5b02f
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SshFxpSymlink.java
@@ -0,0 +1,138 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class SshFxpSymlink extends SubsystemMessage implements MessageRequestId {
+    /**  */
+    public static final int SSH_FXP_SYMLINK = 20;
+    private UnsignedInteger32 id;
+    private String linkpath;
+    private String targetpath;
+
+    /**
+     * Creates a new SshFxpSymlink object.
+     */
+    public SshFxpSymlink() {
+        super(SSH_FXP_SYMLINK);
+    }
+
+    /**
+     * Creates a new SshFxpSymlink object.
+     *
+     * @param id
+     * @param targetpath
+     * @param linkpath
+     */
+    public SshFxpSymlink(UnsignedInteger32 id, String targetpath,
+        String linkpath) {
+        super(SSH_FXP_SYMLINK);
+        this.id = id;
+        this.linkpath = linkpath;
+        this.targetpath = targetpath;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getId() {
+        return id;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getLinkPath() {
+        return linkpath;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getTargetPath() {
+        return targetpath;
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        id = bar.readUINT32();
+        linkpath = bar.readString();
+        targetpath = bar.readString();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_FXP_SYMLINK";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        baw.writeUINT32(id);
+        baw.writeString(linkpath);
+        baw.writeString(targetpath);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SshFxpVersion.java b/src/com/sshtools/j2ssh/sftp/SshFxpVersion.java
new file mode 100644
index 0000000000000000000000000000000000000000..6336fcf0c890d8fda946f003c752b6754d346178
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SshFxpVersion.java
@@ -0,0 +1,147 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+
+import java.io.IOException;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public class SshFxpVersion extends SubsystemMessage {
+    /**  */
+    public static final int SSH_FXP_VERSION = 2;
+    private UnsignedInteger32 version;
+    private Map extended = null;
+
+    /**
+     * Creates a new SshFxpVersion object.
+     */
+    public SshFxpVersion() {
+        super(SSH_FXP_VERSION);
+    }
+
+    /**
+     * Creates a new SshFxpVersion object.
+     *
+     * @param version
+     * @param extended
+     */
+    public SshFxpVersion(UnsignedInteger32 version, Map extended) {
+        super(SSH_FXP_VERSION);
+        this.version = version;
+        this.extended = extended;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getVersion() {
+        return version;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public Map getExtended() {
+        return extended;
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws IOException
+     * @throws InvalidMessageException
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws IOException, InvalidMessageException {
+        version = bar.readUINT32();
+        extended = new HashMap();
+
+        String key;
+        String value;
+
+        while (bar.available() > 0) {
+            key = bar.readString();
+            value = bar.readString();
+            extended.put(key, value);
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_FXP_INIT";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws IOException
+     * @throws InvalidMessageException
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws IOException, InvalidMessageException {
+        baw.writeUINT32(version);
+
+        if (extended != null) {
+            if (extended.size() > 0) {
+                Iterator it = extended.entrySet().iterator();
+                Map.Entry entry;
+
+                while (it.hasNext()) {
+                    entry = (Map.Entry) it.next();
+                    baw.writeString((String) entry.getKey());
+                    baw.writeString((String) entry.getValue());
+                }
+            }
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/sftp/SshFxpWrite.java b/src/com/sshtools/j2ssh/sftp/SshFxpWrite.java
new file mode 100644
index 0000000000000000000000000000000000000000..a4d3d7d2d95fd02830ae9262da0a5a3670fc5b5b
--- /dev/null
+++ b/src/com/sshtools/j2ssh/sftp/SshFxpWrite.java
@@ -0,0 +1,156 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.sftp;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.io.UnsignedInteger32;
+import com.sshtools.j2ssh.io.UnsignedInteger64;
+import com.sshtools.j2ssh.subsystem.SubsystemMessage;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.17 $
+ */
+public class SshFxpWrite extends SubsystemMessage implements MessageRequestId {
+    /**  */
+    public static final int SSH_FXP_WRITE = 6;
+    private UnsignedInteger32 id;
+    private byte[] handle;
+    private UnsignedInteger64 offset;
+    private byte[] data;
+
+    /**
+     * Creates a new SshFxpWrite object.
+     */
+    public SshFxpWrite() {
+        super(SSH_FXP_WRITE);
+    }
+
+    /**
+     * Creates a new SshFxpWrite object.
+     *
+     * @param id
+     * @param handle
+     * @param offset
+     * @param data
+     * @param off
+     * @param len
+     */
+    public SshFxpWrite(UnsignedInteger32 id, byte[] handle,
+        UnsignedInteger64 offset, byte[] data, int off, int len) {
+        super(SSH_FXP_WRITE);
+        this.id = id;
+        this.handle = handle;
+        this.offset = offset;
+        this.data = new byte[len];
+        System.arraycopy(data, off, this.data, 0, len);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger32 getId() {
+        return id;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getHandle() {
+        return handle;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public UnsignedInteger64 getOffset() {
+        return offset;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getData() {
+        return data;
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructMessage(ByteArrayReader bar)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        id = bar.readUINT32();
+        handle = bar.readBinaryString();
+        offset = bar.readUINT64();
+        data = bar.readBinaryString();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_FXP_WRITE";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws java.io.IOException
+     * @throws com.sshtools.j2ssh.transport.InvalidMessageException DOCUMENT
+     *         ME!
+     */
+    public void constructByteArray(ByteArrayWriter baw)
+        throws java.io.IOException, 
+            com.sshtools.j2ssh.transport.InvalidMessageException {
+        baw.writeUINT32(id);
+        baw.writeBinaryString(handle);
+        baw.writeUINT64(offset);
+        baw.writeBinaryString(data);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/subsystem/SubsystemChannel.java b/src/com/sshtools/j2ssh/subsystem/SubsystemChannel.java
new file mode 100644
index 0000000000000000000000000000000000000000..4cedeaf2a13a0db77dee9ec672c80c06545f7e88
--- /dev/null
+++ b/src/com/sshtools/j2ssh/subsystem/SubsystemChannel.java
@@ -0,0 +1,176 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.subsystem;
+
+import com.sshtools.j2ssh.connection.*;
+import com.sshtools.j2ssh.io.*;
+import com.sshtools.j2ssh.transport.*;
+
+import org.apache.commons.logging.*;
+
+import java.io.*;
+
+
+public abstract class SubsystemChannel extends Channel {
+    private static Log log = LogFactory.getLog(SubsystemChannel.class);
+    Integer exitCode = null;
+    String name;
+    protected SubsystemMessageStore messageStore;
+    DynamicBuffer buffer = new DynamicBuffer();
+    int nextMessageLength = -1;
+
+    public SubsystemChannel(String name) {
+        this.name = name;
+        this.messageStore = new SubsystemMessageStore();
+    }
+
+    public SubsystemChannel(String name, SubsystemMessageStore messageStore) {
+        this.name = name;
+        this.messageStore = messageStore;
+    }
+
+    public String getChannelType() {
+        return "session";
+    }
+
+    protected void sendMessage(SubsystemMessage msg)
+        throws InvalidMessageException, IOException {
+        if (log.isDebugEnabled()) {
+            log.debug("Sending " + msg.getMessageName() + " subsystem message");
+        }
+
+        byte[] msgdata = msg.toByteArray();
+
+        // Write the message length
+        sendChannelData(ByteArrayWriter.encodeInt(msgdata.length));
+
+        // Write the message data
+        sendChannelData(msgdata);
+    }
+
+    protected void onChannelRequest(String requestType, boolean wantReply,
+        byte[] requestData) throws java.io.IOException {
+        log.debug("Channel Request received: " + requestType);
+
+        if (requestType.equals("exit-status")) {
+            exitCode = new Integer((int) ByteArrayReader.readInt(requestData, 0));
+            log.debug("Exit code of " + exitCode.toString() + " received");
+        } else if (requestType.equals("exit-signal")) {
+            ByteArrayReader bar = new ByteArrayReader(requestData);
+            String signal = bar.readString();
+            boolean coredump = bar.read() != 0;
+            String message = bar.readString();
+            String language = bar.readString();
+            log.debug("Exit signal " + signal + " received");
+            log.debug("Signal message: " + message);
+            log.debug("Core dumped: " + String.valueOf(coredump));
+
+            /*if (signalListener != null) {
+              signalListener.onExitSignal(signal, coredump, message);
+                       }*/
+        } else if (requestType.equals("xon-xoff")) {
+            /*if (requestData.length >= 1) {
+              localFlowControl = (requestData[0] != 0);
+                       }*/
+        } else if (requestType.equals("signal")) {
+            String signal = ByteArrayReader.readString(requestData, 0);
+            log.debug("Signal " + signal + " received");
+
+            /*if (signalListener != null) {
+              signalListener.onSignal(signal);
+                       }*/
+        } else {
+            if (wantReply) {
+                connection.sendChannelRequestFailure(this);
+            }
+        }
+    }
+
+    protected void onChannelExtData(SshMsgChannelExtendedData msg)
+        throws java.io.IOException {
+    }
+
+    protected void onChannelData(SshMsgChannelData msg)
+        throws java.io.IOException {
+        // Write the data to a temporary buffer that may also contain data
+        // that has not been processed
+        buffer.getOutputStream().write(msg.getChannelData());
+
+        int read;
+        byte[] tmp = new byte[4];
+        byte[] msgdata;
+
+        // Now process any outstanding messages
+        while (buffer.getInputStream().available() > 4) {
+            if (nextMessageLength == -1) {
+                read = 0;
+
+                while ((read += buffer.getInputStream().read(tmp)) < 4) {
+                    ;
+                }
+
+                nextMessageLength = (int) ByteArrayReader.readInt(tmp, 0);
+            }
+
+            if (buffer.getInputStream().available() >= nextMessageLength) {
+                msgdata = new byte[nextMessageLength];
+                buffer.getInputStream().read(msgdata);
+                messageStore.addMessage(msgdata);
+                nextMessageLength = -1;
+            } else {
+                break;
+            }
+        }
+    }
+
+    protected void onChannelEOF() throws java.io.IOException {
+    }
+
+    protected void onChannelClose() throws java.io.IOException {
+		if (messageStore != null) messageStore.close();
+    }
+
+    public byte[] getChannelOpenData() {
+        return null;
+    }
+
+    protected void onChannelOpen() throws java.io.IOException {
+    }
+
+    public boolean startSubsystem() throws IOException {
+        log.info("Starting " + name + " subsystem");
+
+        ByteArrayWriter baw = new ByteArrayWriter();
+        baw.writeString(name);
+
+        return connection.sendChannelRequest(this, "subsystem", true,
+            baw.toByteArray());
+    }
+
+    public byte[] getChannelConfirmationData() {
+        return null;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/subsystem/SubsystemClient.java b/src/com/sshtools/j2ssh/subsystem/SubsystemClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..8417e72b08d3526b707a6b021b8490fc7738833a
--- /dev/null
+++ b/src/com/sshtools/j2ssh/subsystem/SubsystemClient.java
@@ -0,0 +1,239 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.subsystem;
+
+import com.sshtools.j2ssh.SshThread;
+import com.sshtools.j2ssh.connection.ChannelState;
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.session.SessionChannelClient;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.util.StartStopState;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.33 $
+ */
+public abstract class SubsystemClient implements Runnable {
+    private static Log log = LogFactory.getLog(SubsystemClient.class);
+    private InputStream in;
+    private OutputStream out;
+    private Thread thread;
+    private String name;
+    private StartStopState state = new StartStopState(StartStopState.STOPPED);
+
+    /**  */
+    protected SubsystemMessageStore messageStore;
+
+    /**  */
+    protected SessionChannelClient session;
+
+    /**
+     * Creates a new SubsystemClient object.
+     *
+     * @param name
+     */
+    public SubsystemClient(String name) {
+        this.name = name;
+        messageStore = new SubsystemMessageStore();
+    }
+
+    /**
+     * Creates a new SubsystemClient object.
+     *
+     * @param name
+     * @param messageStore
+     */
+    public SubsystemClient(String name, SubsystemMessageStore messageStore) {
+        this.name = name;
+        this.messageStore = messageStore;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isClosed() {
+        return state.getValue() == StartStopState.STOPPED;
+    }
+
+    /**
+     *
+     *
+     * @param session
+     */
+    public void setSessionChannel(SessionChannelClient session) {
+        this.session = session;
+        this.in = session.getInputStream();
+        this.out = session.getOutputStream();
+        session.setName(name);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public SessionChannelClient getSessionChannel() {
+        return this.session;
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public boolean start() throws IOException {
+        thread = new SshThread(this, name + " subsystem", true);
+
+        if (session == null) {
+            throw new IOException(
+                "No valid session is attached to the subsystem!");
+        }
+
+        if (session.getState().getValue() != ChannelState.CHANNEL_OPEN) {
+            throw new IOException("The session is not open!");
+        }
+
+        thread.start();
+
+        return onStart();
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    protected abstract boolean onStart() throws IOException;
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     *
+     *
+     * @param msg
+     *
+     * @throws InvalidMessageException
+     * @throws IOException
+     */
+    protected void sendMessage(SubsystemMessage msg)
+        throws InvalidMessageException, IOException {
+        if (log.isDebugEnabled()) {
+            log.debug("Sending " + msg.getMessageName() + " subsystem message");
+        }
+
+        byte[] msgdata = msg.toByteArray();
+
+        // Write the message length
+        out.write(ByteArrayWriter.encodeInt(msgdata.length));
+
+        // Write the message data
+        out.write(msgdata);
+    }
+
+    /**
+     *
+     */
+    public void run() {
+        int read;
+        int len;
+        int pos;
+        byte[] buffer = new byte[4];
+        byte[] msg;
+        state.setValue(StartStopState.STARTED);
+
+        try {
+            // read the first four bytes of data to determine the susbsytem
+            // message length
+            while ((state.getValue() == StartStopState.STARTED) &&
+                    (session.getState().getValue() == ChannelState.CHANNEL_OPEN)) {
+                read = in.read(buffer);
+
+                if (read > 0) {
+                    len = (int) ByteArrayReader.readInt(buffer, 0);
+                    msg = new byte[len];
+                    pos = 0;
+
+                    while (pos < len) {
+                        read = in.read(msg, pos, msg.length - pos);
+
+                        if (read > 0) {
+                            pos += read;
+                        } else if (read == -1) {
+                            break;
+                        }
+                    }
+
+                    messageStore.addMessage(msg);
+                    msg = null;
+                } else if (read == -1) {
+                    break;
+                }
+            }
+        } catch (IOException ioe) {
+            log.fatal("Subsystem message loop failed!", ioe);
+        } finally {
+            state.setValue(StartStopState.STOPPED);
+        }
+
+        thread = null;
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    public void stop() throws IOException {
+        state.setValue(StartStopState.STOPPED);
+        in.close();
+        out.close();
+        session.close();
+    }
+}
diff --git a/src/com/sshtools/j2ssh/subsystem/SubsystemInputStream.java b/src/com/sshtools/j2ssh/subsystem/SubsystemInputStream.java
new file mode 100644
index 0000000000000000000000000000000000000000..83a604202f1f58556b5337012bae7487af0bb1e3
--- /dev/null
+++ b/src/com/sshtools/j2ssh/subsystem/SubsystemInputStream.java
@@ -0,0 +1,103 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.subsystem;
+
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.19 $
+ */
+public class SubsystemInputStream extends InputStream {
+    byte[] msgdata;
+    int currentPos = 0;
+    private SubsystemMessageStore messageStore;
+
+    /**
+     * Creates a new SubsystemInputStream object.
+     *
+     * @param messageStore
+     */
+    public SubsystemInputStream(SubsystemMessageStore messageStore) {
+        this.messageStore = messageStore;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int available() {
+        if (msgdata == null) {
+            return 0;
+        }
+
+        return msgdata.length - currentPos;
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public int read() throws IOException {
+        if (msgdata == null) {
+            collectNextMessage();
+        }
+
+        if (currentPos >= msgdata.length) {
+            collectNextMessage();
+        }
+
+        return msgdata[currentPos++] & 0xFF;
+    }
+
+    private void collectNextMessage() throws IOException {
+        SubsystemMessage msg = messageStore.nextMessage();
+
+        try {
+            ByteArrayWriter baw = new ByteArrayWriter();
+            byte[] data = msg.toByteArray();
+            baw.writeInt(data.length);
+            baw.write(data);
+            msgdata = baw.toByteArray();
+        } catch (InvalidMessageException ime) {
+            throw new IOException(
+                "An invalid message was encountered in the inputstream");
+        }
+
+        currentPos = 0;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/subsystem/SubsystemMessage.java b/src/com/sshtools/j2ssh/subsystem/SubsystemMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..bbf58d4a350d6c5b98672e418b003ca4d4b54f78
--- /dev/null
+++ b/src/com/sshtools/j2ssh/subsystem/SubsystemMessage.java
@@ -0,0 +1,134 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.subsystem;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.19 $
+ */
+public abstract class SubsystemMessage {
+    private int type;
+
+    /**
+     * Creates a new SubsystemMessage object.
+     *
+     * @param type
+     */
+    public SubsystemMessage(int type) {
+        this.type = type;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public abstract String getMessageName();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getMessageType() {
+        return type;
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     * @throws IOException
+     */
+    public abstract void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException, IOException;
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     * @throws IOException
+     */
+    public abstract void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException, IOException;
+
+    /**
+     *
+     *
+     * @param data
+     *
+     * @throws InvalidMessageException
+     */
+    public void fromByteArray(byte[] data) throws InvalidMessageException {
+        try {
+            ByteArrayReader bar = new ByteArrayReader(data);
+
+            if (bar.available() > 0) {
+                type = bar.read();
+                constructMessage(bar);
+            } else {
+                throw new InvalidMessageException(
+                    "Not enough message data to complete the message");
+            }
+        } catch (IOException ioe) {
+            throw new InvalidMessageException(
+                "The message data cannot be read!");
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws InvalidMessageException
+     */
+    public byte[] toByteArray() throws InvalidMessageException {
+        try {
+            ByteArrayWriter baw = new ByteArrayWriter();
+            baw.write(type);
+            constructByteArray(baw);
+
+            return baw.toByteArray();
+        } catch (IOException ioe) {
+            throw new InvalidMessageException(
+                "The message data cannot be written!");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/subsystem/SubsystemMessageStore.java b/src/com/sshtools/j2ssh/subsystem/SubsystemMessageStore.java
new file mode 100644
index 0000000000000000000000000000000000000000..ac4ef30e184a4180912309ddbd0d314ecef9d5e6
--- /dev/null
+++ b/src/com/sshtools/j2ssh/subsystem/SubsystemMessageStore.java
@@ -0,0 +1,194 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.subsystem;
+
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.MessageNotAvailableException;
+import com.sshtools.j2ssh.transport.MessageStoreEOFException;
+import com.sshtools.j2ssh.util.OpenClosedState;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.28 $
+ */
+public class SubsystemMessageStore {
+    private static Log log = LogFactory.getLog(SubsystemMessageStore.class);
+
+    // List to hold messages as they are received
+
+    /**  */
+    protected List messages = new ArrayList();
+
+    // Map to hold message implementation classes
+
+    /**  */
+    protected Map registeredMessages = new HashMap();
+    private OpenClosedState state = new OpenClosedState(OpenClosedState.OPEN);
+
+    /**
+     * Creates a new SubsystemMessageStore object.
+     */
+    public SubsystemMessageStore() {
+    }
+
+    /**
+     *
+     *
+     * @param msg
+     */
+    public synchronized void addMessage(SubsystemMessage msg) {
+        if (log.isDebugEnabled()) {
+            log.debug("Received " + msg.getMessageName() +
+                " subsystem message");
+        }
+
+        // Add the message
+        messages.add(msg);
+
+        // Notify the threads
+        notifyAll();
+    }
+
+    /**
+     *
+     *
+     * @param msgdata
+     *
+     * @throws InvalidMessageException
+     */
+    public synchronized void addMessage(byte[] msgdata)
+        throws InvalidMessageException {
+        try {
+            Class impl = (Class) registeredMessages.get(new Integer(msgdata[0]));
+
+            if (impl == null) {
+                throw new InvalidMessageException("The message with id " +
+                    String.valueOf(msgdata[0]) + " is not implemented");
+            }
+
+            SubsystemMessage msg = (SubsystemMessage) impl.newInstance();
+            msg.fromByteArray(msgdata);
+            addMessage(msg);
+
+            return;
+        } catch (IllegalAccessException iae) {
+        } catch (InstantiationException ie) {
+        }
+
+        throw new InvalidMessageException("Could not instantiate message class");
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws MessageStoreEOFException
+     */
+    public synchronized SubsystemMessage nextMessage()
+        throws MessageStoreEOFException {
+        try {
+            return nextMessage(0);
+        } catch (MessageNotAvailableException mnae) {
+            return null;
+        }
+    }
+
+    /**
+     *
+     *
+     * @param timeout
+     *
+     * @return
+     *
+     * @throws MessageStoreEOFException
+     * @throws MessageNotAvailableException
+     */
+    public synchronized SubsystemMessage nextMessage(int timeout)
+        throws MessageStoreEOFException, MessageNotAvailableException {
+        // If there are no messages available then wait untill there are.
+        timeout = (timeout > 0) ? timeout : 0;
+
+        while (messages.size() <= 0) {
+            try {
+                wait(timeout);
+
+                if (timeout > 0) {
+                    break;
+                }
+            } catch (InterruptedException e) {
+            }
+        }
+
+        if (state.getValue() != OpenClosedState.OPEN) {
+            throw new MessageStoreEOFException();
+        }
+
+        if (messages.size() > 0) {
+            return (SubsystemMessage) messages.remove(0);
+        } else {
+            throw new MessageNotAvailableException();
+        }
+    }
+
+    /**
+     *
+     *
+     * @param messageId
+     * @param implementor
+     */
+    public void registerMessage(int messageId, Class implementor) {
+        registeredMessages.put(new Integer(messageId), implementor);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public OpenClosedState getState() {
+        return state;
+    }
+
+    /**
+     *
+     */
+    public synchronized void close() {
+        state.setValue(OpenClosedState.CLOSED);
+        notifyAll();
+    }
+}
diff --git a/src/com/sshtools/j2ssh/subsystem/SubsystemOutputStream.java b/src/com/sshtools/j2ssh/subsystem/SubsystemOutputStream.java
new file mode 100644
index 0000000000000000000000000000000000000000..8c5d73d8f24fe98d0ae8335273513850e46e0ef3
--- /dev/null
+++ b/src/com/sshtools/j2ssh/subsystem/SubsystemOutputStream.java
@@ -0,0 +1,114 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.subsystem;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.21 $
+ */
+public class SubsystemOutputStream extends OutputStream {
+    // Temporary storage buffer to build up a message
+    ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+    SubsystemMessageStore messageStore;
+    int messageStart = 0;
+
+    /**
+     * Creates a new SubsystemOutputStream object.
+     *
+     * @param messageStore
+     */
+    public SubsystemOutputStream(SubsystemMessageStore messageStore) {
+        super();
+        this.messageStore = messageStore;
+    }
+
+    /**
+     *
+     *
+     * @param b
+     * @param off
+     * @param len
+     *
+     * @throws IOException
+     */
+    public void write(byte[] b, int off, int len) throws IOException {
+        // Write the data
+        super.write(b, off, len);
+        processMessage();
+    }
+
+    /**
+     *
+     *
+     * @param b
+     *
+     * @throws IOException
+     */
+    public void write(int b) throws IOException {
+        buffer.write(b);
+    }
+
+    private void processMessage() throws IOException {
+        // Now try to process a message
+        if (buffer.size() > (messageStart + 4)) {
+            int messageLength = (int) ByteArrayReader.readInt(buffer.toByteArray(),
+                    messageStart);
+
+            if (messageLength <= (buffer.size() - 4)) {
+                byte[] msgdata = new byte[messageLength];
+
+                // Process a message
+                System.arraycopy(buffer.toByteArray(), messageStart + 4,
+                    msgdata, 0, messageLength);
+
+                try {
+                    messageStore.addMessage(msgdata);
+                } catch (InvalidMessageException ime) {
+                    throw new IOException(
+                        "An invalid message was encountered in the outputstream: " +
+                        ime.getMessage());
+                }
+
+                if (messageLength == (buffer.size() - 4)) {
+                    buffer.reset();
+                    messageStart = 0;
+                } else {
+                    messageStart = messageLength + 4;
+                }
+            }
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/AbstractKnownHostsKeyVerification.java b/src/com/sshtools/j2ssh/transport/AbstractKnownHostsKeyVerification.java
new file mode 100644
index 0000000000000000000000000000000000000000..67e726e5667060a33c7dcf7245b2be6e78ddc75b
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/AbstractKnownHostsKeyVerification.java
@@ -0,0 +1,473 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+import com.sshtools.j2ssh.transport.publickey.SshKeyPairFactory;
+import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
+import com.sshtools.j2ssh.util.Base64;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FilePermission;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import java.security.AccessControlException;
+import java.security.AccessController;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+
+/**
+ * <p>
+ * An abstract <code>HostKeyVerification</code> class providing validation
+ * against the known_hosts format.
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Revision: 1.18 $
+ *
+ * @since 0.2.0
+ */
+public abstract class AbstractKnownHostsKeyVerification
+    implements HostKeyVerification {
+    private static String defaultHostFile;
+    private static Log log = LogFactory.getLog(HostKeyVerification.class);
+
+    //private List deniedHosts = new ArrayList();
+    private Map allowedHosts = new HashMap();
+    private String knownhosts;
+    private boolean hostFileWriteable;
+
+    //private boolean expectEndElement = false;
+    //private String currentElement = null;
+
+    /**
+     * <p>
+     * Constructs a host key verification instance reading the specified
+     * known_hosts file.
+     * </p>
+     *
+     * @param knownhosts the path of the known_hosts file
+     *
+     * @throws InvalidHostFileException if the known_hosts file is invalid
+     *
+     * @since 0.2.0
+     */
+    public AbstractKnownHostsKeyVerification(String knownhosts)
+        throws InvalidHostFileException {
+        InputStream in = null;
+
+        try {
+            //  If no host file is supplied, or there is not enough permission to load
+            //  the file, then just create an empty list.
+            if (knownhosts != null) {
+                if (System.getSecurityManager() != null) {
+                    AccessController.checkPermission(new FilePermission(
+                            knownhosts, "read"));
+                }
+
+                //  Load the hosts file. Do not worry if fle doesnt exist, just disable
+                //  save of
+                File f = new File(knownhosts);
+
+                if (f.exists()) {
+                    in = new FileInputStream(f);
+
+                    BufferedReader reader = new BufferedReader(new InputStreamReader(
+                                in));
+                    String line;
+
+                    while ((line = reader.readLine()) != null) {
+                        StringTokenizer tokens = new StringTokenizer(line, " ");
+                        String host = (String) tokens.nextElement();
+                        String algorithm = (String) tokens.nextElement();
+                        String key = (String) tokens.nextElement();
+
+                        SshPublicKey pk = SshKeyPairFactory.decodePublicKey(Base64.decode(
+                            key));
+                          /*if (host.indexOf(",") > -1) {
+                           host = host.substring(0, host.indexOf(","));
+                           }*/
+                        putAllowedKey(host, pk);
+
+                        //allowedHosts.put(host + "#" + pk.getAlgorithmName(), pk);
+                    }
+
+                    reader.close();
+                    hostFileWriteable = f.canWrite();
+                } else {
+                    // Try to create the file and its parents if necersary
+                    f.getParentFile().mkdirs();
+
+                    if (f.createNewFile()) {
+                        FileOutputStream out = new FileOutputStream(f);
+                        out.write(toString().getBytes());
+                        out.close();
+                        hostFileWriteable = true;
+                    } else {
+                        hostFileWriteable = false;
+                    }
+                }
+
+                if (!hostFileWriteable) {
+                    log.warn("Host file is not writeable.");
+                }
+
+                this.knownhosts = knownhosts;
+            }
+        } catch (AccessControlException ace) {
+            hostFileWriteable = false;
+            log.warn(
+                "Not enough permission to load a hosts file, so just creating an empty list");
+        } catch (IOException ioe) {
+            hostFileWriteable = false;
+            log.info("Could not open or read " + knownhosts + ": " +
+                ioe.getMessage());
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException ioe) {
+                }
+            }
+        }
+    }
+
+    /**
+     * <p>
+     * Determines whether the host file is writable.
+     * </p>
+     *
+     * @return true if the host file is writable, otherwise false
+     *
+     * @since 0.2.0
+     */
+    public boolean isHostFileWriteable() {
+        return hostFileWriteable;
+    }
+
+    /**
+     * <p>
+     * Called by the <code>verifyHost</code> method when the host key supplied
+     * by the host does not match the current key recording in the known hosts
+     * file.
+     * </p>
+     *
+     * @param host the name of the host
+     * @param allowedHostKey the current key recorded in the known_hosts file.
+     * @param actualHostKey the actual key supplied by the user
+     *
+     * @throws TransportProtocolException if an error occurs
+     *
+     * @since 0.2.0
+     */
+    public abstract void onHostKeyMismatch(String host,
+        SshPublicKey allowedHostKey, SshPublicKey actualHostKey)
+        throws TransportProtocolException;
+
+    /**
+     * <p>
+     * Called by the <code>verifyHost</code> method when the host key supplied
+     * is not recorded in the known_hosts file.
+     * </p>
+     *
+     * <p></p>
+     *
+     * @param host the name of the host
+     * @param key the public key supplied by the host
+     *
+     * @throws TransportProtocolException if an error occurs
+     *
+     * @since 0.2.0
+     */
+    public abstract void onUnknownHost(String host, SshPublicKey key)
+        throws TransportProtocolException;
+
+    /**
+     * <p>
+     * Allows a host key, optionally recording the key to the known_hosts file.
+     * </p>
+     *
+     * @param host the name of the host
+     * @param pk the public key to allow
+     * @param always true if the key should be written to the known_hosts file
+     *
+     * @throws InvalidHostFileException if the host file cannot be written
+     *
+     * @since 0.2.0
+     */
+    public void allowHost(String host, SshPublicKey pk, boolean always)
+        throws InvalidHostFileException {
+        if (log.isDebugEnabled()) {
+            log.debug("Allowing " + host + " with fingerprint " +
+                pk.getFingerprint());
+        }
+
+        // Put the host into the allowed hosts list, overiding any previous
+        // entry
+        putAllowedKey(host, pk);
+
+        //allowedHosts.put(host, pk);
+        // If we always want to allow then save the host file with the
+        // new details
+        if (always) {
+            saveHostFile();
+        }
+    }
+
+    /**
+     * <p>
+     * Returns a Map of the allowed hosts.
+     * </p>
+     *
+     * <p>
+     * The keys of the returned Map are comma separated strings of
+     * "hostname,ipaddress". The value objects are Maps containing a string
+     * key of the public key alogorithm name and the public key as the value.
+     * </p>
+     *
+     * @return the allowed hosts
+     *
+     * @since 0.2.0
+     */
+    public Map allowedHosts() {
+        return allowedHosts;
+    }
+
+    /**
+     * <p>
+     * Removes an allowed host.
+     * </p>
+     *
+     * @param host the host to remove
+     *
+     * @since 0.2.0
+     */
+    public void removeAllowedHost(String host) {
+        Iterator it = allowedHosts.keySet().iterator();
+
+        while (it.hasNext()) {
+            StringTokenizer tokens = new StringTokenizer((String) it.next(), ",");
+
+            while (tokens.hasMoreElements()) {
+                String name = (String) tokens.nextElement();
+
+                if (name.equals(host)) {
+                    allowedHosts.remove(name);
+                }
+            }
+        }
+    }
+
+    /**
+     * <p>
+     * Verifies a host key against the list of known_hosts.
+     * </p>
+     *
+     * <p>
+     * If the host unknown or the key does not match the currently allowed host
+     * key the abstract <code>onUnknownHost</code> or
+     * <code>onHostKeyMismatch</code> methods are called so that the caller
+     * may identify and allow the host.
+     * </p>
+     *
+     * @param host the name of the host
+     * @param pk the host key supplied
+     *
+     * @return true if the host is accepted, otherwise false
+     *
+     * @throws TransportProtocolException if an error occurs
+     *
+     * @since 0.2.0
+     */
+    public boolean verifyHost(String host, SshPublicKey pk)
+        throws TransportProtocolException {
+        String fingerprint = pk.getFingerprint();
+        log.info("Verifying " + host + " host key");
+
+        if (log.isDebugEnabled()) {
+            log.debug("Fingerprint: " + fingerprint);
+        }
+
+        Iterator it = allowedHosts.keySet().iterator();
+
+        while (it.hasNext()) {
+            // Could be a comma delimited string of names/ip addresses
+            String names = (String) it.next();
+
+            if (names.equals(host)) {
+                return validateHost(names, pk);
+            }
+
+            StringTokenizer tokens = new StringTokenizer(names, ",");
+
+            while (tokens.hasMoreElements()) {
+                // Try the allowed hosts by looking at the allowed hosts map
+                String name = (String) tokens.nextElement();
+
+                if (name.equalsIgnoreCase(host)) {
+                    return validateHost(names, pk);
+                }
+            }
+        }
+
+        // The host is unknown os ask the user
+        onUnknownHost(host, pk);
+
+        // Recheck ans return the result
+        return checkKey(host, pk);
+    }
+
+    private boolean validateHost(String names, SshPublicKey pk)
+        throws TransportProtocolException {
+        // The host is allowed so check the fingerprint
+        SshPublicKey pub = getAllowedKey(names, pk.getAlgorithmName()); //shPublicKey) allowedHosts.get(host + "#" + pk.getAlgorithmName());
+
+        if ((pub != null) && pk.equals(pub)) {
+            return true;
+        } else {
+            // The host key does not match the recorded so call the abstract
+            // method so that the user can decide
+            if (pub == null) {
+                onUnknownHost(names, pk);
+            } else {
+                onHostKeyMismatch(names, pub, pk);
+            }
+
+            // Recheck the after the users input
+            return checkKey(names, pk);
+        }
+    }
+
+    private boolean checkKey(String host, SshPublicKey key) {
+        SshPublicKey pk = getAllowedKey(host, key.getAlgorithmName()); //shPublicKey) allowedHosts.get(host + "#" + key.getAlgorithmName());
+
+        if (pk != null) {
+            if (pk.equals(key)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private SshPublicKey getAllowedKey(String names, String algorithm) {
+        if (allowedHosts.containsKey(names)) {
+            Map map = (Map) allowedHosts.get(names);
+
+            return (SshPublicKey) map.get(algorithm);
+        }
+
+        return null;
+    }
+
+    private void putAllowedKey(String host, SshPublicKey key) {
+        if (!allowedHosts.containsKey(host)) {
+            allowedHosts.put(host, new HashMap());
+        }
+
+        Map map = (Map) allowedHosts.get(host);
+        map.put(key.getAlgorithmName(), key);
+    }
+
+    /**
+     * <p>
+     * Save's the host key file to be saved.
+     * </p>
+     *
+     * @throws InvalidHostFileException if the host file is invalid
+     *
+     * @since 0.2.0
+     */
+    public void saveHostFile() throws InvalidHostFileException {
+        if (!hostFileWriteable) {
+            throw new InvalidHostFileException("Host file is not writeable.");
+        }
+
+        log.info("Saving " + defaultHostFile);
+
+        try {
+            File f = new File(knownhosts);
+            FileOutputStream out = new FileOutputStream(f);
+            out.write(toString().getBytes());
+            out.close();
+        } catch (IOException e) {
+            throw new InvalidHostFileException("Could not write to " +
+                knownhosts);
+        }
+    }
+
+    /**
+     * <p>
+     * Outputs the allowed hosts in the known_hosts file format.
+     * </p>
+     *
+     * <p>
+     * The format consists of any number of lines each representing one key for
+     * a single host.
+     * </p>
+     * <code> titan,192.168.1.12 ssh-dss AAAAB3NzaC1kc3MAAACBAP1/U4Ed.....
+     * titan,192.168.1.12 ssh-rsa AAAAB3NzaC1kc3MAAACBAP1/U4Ed.....
+     * einstein,192.168.1.40 ssh-dss AAAAB3NzaC1kc3MAAACBAP1/U4Ed..... </code>
+     *
+     * @return
+     *
+     * @since 0.2.0
+     */
+    public String toString() {
+        String knownhosts = "";
+        Map.Entry entry;
+        Map.Entry entry2;
+        Iterator it = allowedHosts.entrySet().iterator();
+
+        while (it.hasNext()) {
+            entry = (Map.Entry) it.next();
+
+            Iterator it2 = ((Map) entry.getValue()).entrySet().iterator();
+
+            while (it2.hasNext()) {
+                entry2 = (Map.Entry) it2.next();
+
+                SshPublicKey pk = (SshPublicKey) entry2.getValue();
+                knownhosts += (entry.getKey().toString() + " " +
+                pk.getAlgorithmName() + " " +
+                Base64.encodeBytes(pk.getEncoded(), true) + "\n");
+            }
+        }
+
+        return knownhosts;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/AlgorithmInitializationException.java b/src/com/sshtools/j2ssh/transport/AlgorithmInitializationException.java
new file mode 100644
index 0000000000000000000000000000000000000000..02a08646b3976886dc8ea90acbe41fa25de1d007
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/AlgorithmInitializationException.java
@@ -0,0 +1,53 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+
+/**
+ * <p>
+ * Thrown by the transport protocol if an error occurs during any type of
+ * algorithm initialization.
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Revision: 1.18 $
+ *
+ * @since 0.2.0
+ */
+public class AlgorithmInitializationException extends TransportProtocolException {
+    /**
+     * <p>
+     * Constructs the exception.
+     * </p>
+     *
+     * @param msg the error message
+     *
+     * @since 0.2.0
+     */
+    public AlgorithmInitializationException(String msg) {
+        super(msg);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/AlgorithmNotAgreedException.java b/src/com/sshtools/j2ssh/transport/AlgorithmNotAgreedException.java
new file mode 100644
index 0000000000000000000000000000000000000000..37483eb5bc636df9c7167a400f18ce7943f795ff
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/AlgorithmNotAgreedException.java
@@ -0,0 +1,55 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+
+/**
+ * <p>
+ * Thrown by the transport protocol if an algortihm cannot be agreed between
+ * the client and server.
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Revision: 1.19 $
+ *
+ * @since 0.2.0
+ */
+public class AlgorithmNotAgreedException extends TransportProtocolException {
+    /**
+     * <p>
+     * Constructs the exception.
+     * </p>
+     *
+     * <p></p>
+     *
+     * @param msg the error message
+     *
+     * @since 0.2.0
+     */
+    public AlgorithmNotAgreedException(String msg) {
+        super(msg);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/AlgorithmNotSupportedException.java b/src/com/sshtools/j2ssh/transport/AlgorithmNotSupportedException.java
new file mode 100644
index 0000000000000000000000000000000000000000..a74ad7cda3515cdcc2961ae29e66bd627f7f3f3f
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/AlgorithmNotSupportedException.java
@@ -0,0 +1,53 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+
+/**
+ * <p>
+ * Thrown by the transport protocol if an algorithm is not supported by the
+ * underlying JCE.
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Revision: 1.19 $
+ *
+ * @since 0.2.0
+ */
+public class AlgorithmNotSupportedException extends TransportProtocolException {
+    /**
+     * <p>
+     * Constructs the exception.
+     * </p>
+     *
+     * @param msg the error message
+     *
+     * @since 0.2.0
+     */
+    public AlgorithmNotSupportedException(String msg) {
+        super(msg);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/AlgorithmOperationException.java b/src/com/sshtools/j2ssh/transport/AlgorithmOperationException.java
new file mode 100644
index 0000000000000000000000000000000000000000..0ff1f6170cde472b4de98c9a5671677e2d71727e
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/AlgorithmOperationException.java
@@ -0,0 +1,52 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+
+/**
+ * <p>
+ * Thrown by the transport protocol if an algorithm operation error occurs.
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Revision: 1.19 $
+ *
+ * @since 0.2.0
+ */
+public class AlgorithmOperationException extends TransportProtocolException {
+    /**
+     * <p>
+     * Contructs the exception.
+     * </p>
+     *
+     * @param msg the error message
+     *
+     * @since 0.2.0
+     */
+    public AlgorithmOperationException(String msg) {
+        super(msg);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/AsyncService.java b/src/com/sshtools/j2ssh/transport/AsyncService.java
new file mode 100644
index 0000000000000000000000000000000000000000..48d2028202b608435e8e2858893be1c8c7c590c6
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/AsyncService.java
@@ -0,0 +1,170 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+import com.sshtools.j2ssh.SshThread;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+
+
+/**
+ * <p>
+ * Extends the simple <code>Service</code> class to provide an asyncronous
+ * messaging service for the transport protocol.
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Revision: 1.28 $
+ *
+ * @since 0.2.0
+ */
+public abstract class AsyncService extends Service implements Runnable {
+    private static Log log = LogFactory.getLog(Service.class);
+
+    /**  */
+    protected SshThread thread;
+
+    /**
+     * <p>
+     * Constructs an asyncronous service.
+     * </p>
+     *
+     * @param serviceName the name of the service
+     *
+     * @since 0.2.0
+     */
+    public AsyncService(String serviceName) {
+        super(serviceName);
+    }
+
+    /**
+     * <p>
+     * Implements the abstract <code>Service</code> method and starts the
+     * service thread.
+     * </p>
+     *
+     * @throws IOException if an IO error occurs
+     *
+     * @since 0.2.0
+     */
+    protected void onStart() throws IOException {
+        if (Thread.currentThread() instanceof SshThread) {
+            thread = ((SshThread) Thread.currentThread()).cloneThread(this,
+                    getServiceName());
+        } else {
+            thread = new SshThread(this, getServiceName(), true);
+        }
+
+        log.info("Starting " + getServiceName() + " service thread");
+        thread.start();
+    }
+
+    /**
+     * <p>
+     * Implements the asyncronous services message loop.
+     * </p>
+     *
+     * @since 0.2.0
+     */
+    public final void run() {
+        int[] messageFilter = getAsyncMessageFilter();
+        state.setValue(ServiceState.SERVICE_STARTED);
+
+        SshMessage msg = null;
+
+        while ((state.getValue() == ServiceState.SERVICE_STARTED) &&
+                transport.isConnected()) {
+            try {
+                // Get the next message from the message store
+                msg = messageStore.getMessage(messageFilter);
+
+                if (state.getValue() == ServiceState.SERVICE_STOPPED) {
+                    break;
+                }
+
+                if (log.isDebugEnabled()) {
+                    log.debug("Routing " + msg.getMessageName());
+                }
+
+                onMessageReceived(msg);
+
+                if (log.isDebugEnabled()) {
+                    log.debug("Finished processing " + msg.getMessageName());
+                }
+            } catch (MessageStoreEOFException eof) {
+                stop();
+            } catch (Exception ex) {
+                if ((state.getValue() != ServiceState.SERVICE_STOPPED) &&
+                        transport.isConnected()) {
+                    log.fatal("Service message loop failed!", ex);
+                    stop();
+                }
+            }
+        }
+
+        onStop();
+        log.info(getServiceName() + " thread is exiting");
+        thread = null;
+    }
+
+    /**
+     * <p>
+     * The service thread calls this method when the thread is exiting.
+     * </p>
+     *
+     * @since 0.2.0
+     */
+    protected abstract void onStop();
+
+    /**
+     * <p>
+     * Implement this method by returning the message ids of the asyncrounous
+     * messages your implementation wants to receive.
+     * </p>
+     *
+     * @return an int array of message ids
+     *
+     * @since 0.2.0
+     */
+    protected abstract int[] getAsyncMessageFilter();
+
+    /**
+     * <p>
+     * Called by the service thread when an asyncronous message is received.
+     * </p>
+     *
+     * @param msg the message received
+     *
+     * @throws IOException if an IO error occurs
+     *
+     * @since 0.2.0
+     */
+    protected abstract void onMessageReceived(SshMessage msg)
+        throws IOException;
+}
diff --git a/src/com/sshtools/j2ssh/transport/ConsoleKnownHostsKeyVerification.java b/src/com/sshtools/j2ssh/transport/ConsoleKnownHostsKeyVerification.java
new file mode 100644
index 0000000000000000000000000000000000000000..85b6b2c7ed86e4d9e7d0d1b3ba758f141a535ea9
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/ConsoleKnownHostsKeyVerification.java
@@ -0,0 +1,161 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+
+/**
+ * <p>
+ * Implements the <code>AbstractKnownHostsKeyVerification</code> to provide
+ * host key verification through the console.
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Revision: 1.14 $
+ *
+ * @since 0.2.0
+ */
+public class ConsoleKnownHostsKeyVerification
+    extends AbstractKnownHostsKeyVerification {
+    /**
+     * <p>
+     * Constructs the verification instance with the default known_hosts file
+     * from $HOME/.ssh/known_hosts.
+     * </p>
+     *
+     * @throws InvalidHostFileException if the known_hosts file is invalid.
+     *
+     * @since 0.2.0
+     */
+    public ConsoleKnownHostsKeyVerification() throws InvalidHostFileException {
+        super(new File(System.getProperty("user.home"),
+                ".ssh" + File.separator + "known_hosts").getAbsolutePath());
+    }
+
+    /**
+     * <p>
+     * Constructs the verification instance with the specified known_hosts
+     * file.
+     * </p>
+     *
+     * @param knownhosts the path to the known_hosts file
+     *
+     * @throws InvalidHostFileException if the known_hosts file is invalid.
+     *
+     * @since 0.2.0
+     */
+    public ConsoleKnownHostsKeyVerification(String knownhosts)
+        throws InvalidHostFileException {
+        super(knownhosts);
+    }
+
+    /**
+     * <p>
+     * Prompts the user through the console to verify the host key.
+     * </p>
+     *
+     * @param host the name of the host
+     * @param pk the current public key of the host
+     * @param actual the actual public key supplied by the host
+     *
+     * @since 0.2.0
+     */
+    public void onHostKeyMismatch(String host, SshPublicKey pk,
+        SshPublicKey actual) {
+        try {
+            System.out.println("The host key supplied by " + host + " is: " +
+                actual.getFingerprint());
+            System.out.println("The current allowed key for " + host + " is: " +
+                pk.getFingerprint());
+            getResponse(host, pk);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * <p>
+     * Prompts the user through the console to verify the host key.
+     * </p>
+     *
+     * @param host the name of the host
+     * @param pk the public key supplied by the host
+     *
+     * @since 0.2.0
+     */
+    public void onUnknownHost(String host, SshPublicKey pk) {
+        try {
+            System.out.println("The host " + host +
+                " is currently unknown to the system");
+            System.out.println("The host key fingerprint is: " +
+                pk.getFingerprint());
+            getResponse(host, pk);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void getResponse(String host, SshPublicKey pk)
+        throws InvalidHostFileException, IOException {
+        String response = "";
+        BufferedReader reader = new BufferedReader(new InputStreamReader(
+                    System.in));
+
+        while (!(response.equalsIgnoreCase("YES") ||
+                response.equalsIgnoreCase("NO") ||
+                (response.equalsIgnoreCase("ALWAYS") && isHostFileWriteable()))) {
+            String options = (isHostFileWriteable() ? "Yes|No|Always" : "Yes|No");
+
+            if (!isHostFileWriteable()) {
+                System.out.println(
+                    "Always option disabled, host file is not writeable");
+            }
+
+            System.out.print("Do you want to allow this host key? [" + options +
+                "]: ");
+            response = reader.readLine();
+        }
+
+        if (response.equalsIgnoreCase("YES")) {
+            allowHost(host, pk, false);
+        }
+
+        if (response.equalsIgnoreCase("NO")) {
+            System.out.println("Cannot continue without a valid host key");
+            System.exit(1);
+        }
+
+        if (response.equalsIgnoreCase("ALWAYS") && isHostFileWriteable()) {
+            allowHost(host, pk, true);
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/HostKeyVerification.java b/src/com/sshtools/j2ssh/transport/HostKeyVerification.java
new file mode 100644
index 0000000000000000000000000000000000000000..c0be72f32c2a698623621a5a4409d1bee48b8ba4
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/HostKeyVerification.java
@@ -0,0 +1,60 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
+
+
+/**
+ * <p>
+ * An interface to allow the transport protocol to verify the public key
+ * supplied by the server during key-exchange
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Revision: 1.29 $
+ *
+ * @since 0.2.0
+ */
+public interface HostKeyVerification {
+    /**
+     * <p>
+     * Called by the transport protocol to verify the identity of the server
+     * through the supplied public key.
+     * </p>
+     *
+     * @param host the name of the host
+     * @param pk the public key supplied during key-exchange
+     *
+     * @return true if the host is acceptable, otherwise false
+     *
+     * @throws TransportProtocolException if an error occurs
+     *
+     * @since 0.2.0
+     */
+    public boolean verifyHost(String host, SshPublicKey pk)
+        throws TransportProtocolException;
+}
diff --git a/src/com/sshtools/j2ssh/transport/IgnoreHostKeyVerification.java b/src/com/sshtools/j2ssh/transport/IgnoreHostKeyVerification.java
new file mode 100644
index 0000000000000000000000000000000000000000..a2b6dbc39160dbbaf7cc5e3f8316f052fa0d8394
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/IgnoreHostKeyVerification.java
@@ -0,0 +1,62 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
+
+
+/**
+ * <p>
+ * A simple host key verification implementation that automatically approves
+ * the servers host key. It should be noted that using this implementation
+ * will render the protocol insecure against active attacks.
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Revision: 1.15 $
+ *
+ * @since 0.2.0
+ */
+public class IgnoreHostKeyVerification implements HostKeyVerification {
+    /**
+     * <p>
+     * Simply returns <code>true</code> to all requests.
+     * </p>
+     *
+     * @param host the name of the host
+     * @param pk the hosts public key
+     *
+     * @return <code>true</code>
+     *
+     * @throws TransportProtocolException if an error occurs
+     *
+     * @since 0.2.0
+     */
+    public boolean verifyHost(String host, SshPublicKey pk)
+        throws TransportProtocolException {
+        return true;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/InvalidHostFileException.java b/src/com/sshtools/j2ssh/transport/InvalidHostFileException.java
new file mode 100644
index 0000000000000000000000000000000000000000..f38ca1ce2842d65433a257332f13a05d220569f1
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/InvalidHostFileException.java
@@ -0,0 +1,52 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+
+/**
+ * <p>
+ * Thrown by abstract host key verifications when a host file is invalid
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Revision: 1.20 $
+ *
+ * @since 0.2.0
+ */
+public class InvalidHostFileException extends TransportProtocolException {
+    /**
+     * <p>
+     * Contructs the exception.
+     * </p>
+     *
+     * @param msg the error message
+     *
+     * @since 0.2.0
+     */
+    public InvalidHostFileException(String msg) {
+        super(msg);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/InvalidMessageException.java b/src/com/sshtools/j2ssh/transport/InvalidMessageException.java
new file mode 100644
index 0000000000000000000000000000000000000000..b524d67457c0dbb70c341b8aece5eed5bfc586df
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/InvalidMessageException.java
@@ -0,0 +1,53 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+
+/**
+ * <p>
+ * Thrown by <code>SshMessage</code> implementations when an invalid message is
+ * found.
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Revision: 1.19 $
+ *
+ * @since 0.2.0
+ */
+public class InvalidMessageException extends TransportProtocolException {
+    /**
+     * <p>
+     * Constructs the message.
+     * </p>
+     *
+     * @param msg the error description
+     *
+     * @since 0.2.0
+     */
+    public InvalidMessageException(String msg) {
+        super(msg);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/MessageAlreadyRegisteredException.java b/src/com/sshtools/j2ssh/transport/MessageAlreadyRegisteredException.java
new file mode 100644
index 0000000000000000000000000000000000000000..ac166d54e464a59c904b1f08259c1524d86a1942
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/MessageAlreadyRegisteredException.java
@@ -0,0 +1,54 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+import com.sshtools.j2ssh.*;
+
+
+/**
+ * <p>
+ * Thrown by message store when a message is already registered
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Revision: 1.19 $
+ *
+ * @since 0.2.0
+ */
+public class MessageAlreadyRegisteredException extends SshException {
+    /**
+     * <p>
+     * Constructs the exception.
+     * </p>
+     *
+     * @param messageId the id of the message already registered
+     *
+     * @since 0.2.0
+     */
+    public MessageAlreadyRegisteredException(Integer messageId) {
+        super("Message Id " + messageId.toString() + " is already registered");
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/MessageNotAvailableException.java b/src/com/sshtools/j2ssh/transport/MessageNotAvailableException.java
new file mode 100644
index 0000000000000000000000000000000000000000..9dccce4962c174c486af1ebd065ce6f734984d83
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/MessageNotAvailableException.java
@@ -0,0 +1,48 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+
+/**
+ * <p>
+ * Thrown by the message store when a message is not available.
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Revision: 1.14 $
+ *
+ * @since 0.2.0
+ */
+public class MessageNotAvailableException extends Exception {
+    /**
+     * <p>
+     * Constructs the excpetion.
+     * </p>
+     */
+    public MessageNotAvailableException() {
+        super("The message is not available");
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/MessageNotRegisteredException.java b/src/com/sshtools/j2ssh/transport/MessageNotRegisteredException.java
new file mode 100644
index 0000000000000000000000000000000000000000..a0b7704dc7a0e1af793f8bf3e04763c5f882e6d9
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/MessageNotRegisteredException.java
@@ -0,0 +1,71 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+import com.sshtools.j2ssh.SshException;
+
+
+/**
+ * <p>
+ * Thrown by the message store when a message is added which is not registered.
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Revision: 1.20 $
+ *
+ * @since 0.2.0
+ */
+public class MessageNotRegisteredException extends SshException {
+    /**
+     * <p>
+     * Constructs the exception.
+     * </p>
+     *
+     * @param messageId the id of the message not registered
+     *
+     * @since 0.2.0
+     */
+    public MessageNotRegisteredException(Integer messageId) {
+        super("Message Id " + messageId.toString() +
+            " is not currently registered");
+    }
+
+    /**
+     * <p>
+     * Consructs the exception.
+     * </p>
+     *
+     * @param messageId the id of the message not registered
+     * @param store the message store
+     *
+     * @since 0.2.0
+     */
+    public MessageNotRegisteredException(Integer messageId,
+        SshMessageStore store) {
+        super("Message Id " + messageId.toString() +
+            " is not registered to the message store specified");
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/MessageStoreEOFException.java b/src/com/sshtools/j2ssh/transport/MessageStoreEOFException.java
new file mode 100644
index 0000000000000000000000000000000000000000..cdf2a43b4492bcbfd518b6eb3a6a878feb27eda1
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/MessageStoreEOFException.java
@@ -0,0 +1,48 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+
+/**
+ * <p>
+ * Thrown by the message store when the store reaches EOF.
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Revision: 1.15 $
+ *
+ * @since 0.2.0
+ */
+public class MessageStoreEOFException extends TransportProtocolException {
+    /**
+     * <p>
+     * Constructs the exception.
+     * </p>
+     */
+    public MessageStoreEOFException() {
+        super("The message store has reached EOF");
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/Service.java b/src/com/sshtools/j2ssh/transport/Service.java
new file mode 100644
index 0000000000000000000000000000000000000000..ca059dd7167a7afadc15db1dde42eb1fdee07361
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/Service.java
@@ -0,0 +1,259 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+
+
+/**
+ * <p>
+ * This class implements the transport protocol service.
+ * </p>
+ *
+ * <p>
+ * After the transport protocol negotiates the protocol version and performs
+ * server authentication via key exchange, the client requests a service. The
+ * service is identified by a name and currently there are 2 services defined.<br>
+ * <br>
+ * ssh-userauth<br>
+ * ssh-connection<br>
+ * <br>
+ * These 2 services are implemented by the SSH authentication protocol and SSH
+ * connection protocol respectivley. Further services can be defined and a
+ * similar local naming policy is applied to the service names, as is applied
+ * to the algorithm names; a local service should use the
+ * "servicename(at)domain" syntax.
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Revision: 1.42 $
+ *
+ * @since 0.2.0
+ */
+public abstract class Service {
+    private static Log log = LogFactory.getLog(Service.class);
+
+    /**
+     * Service start mode passed into <code>init</code> method when the service
+     * is operating in client mode. i.e its requesting a service to be started
+     * on the remote server and requires a SSH_MSG_SERVICE_ACCEPT message.
+     */
+    public final static int REQUESTING_SERVICE = 1;
+
+    /**
+     * Serivce start mode passed into <code>init</code> method when the service
+     * is operating in server mode. i.e a client is requesting a service to be
+     * started on the local computer and requires the SSH_MSG_SERVICE_ACCEPT
+     * message to be sent.
+     */
+    public final static int ACCEPTING_SERVICE = 2;
+
+    /**
+     * The message store registered with the transport protocol to receive the
+     * service's message.
+     */
+    protected SshMessageStore messageStore = new SshMessageStore();
+
+    /** The underlying transport protocol */
+    protected TransportProtocol transport;
+
+    /** This instances start mode */
+    protected Integer startMode = null;
+
+    /** The current state of the service */
+    protected ServiceState state = new ServiceState();
+
+    /** The name of the service */
+    private String serviceName;
+
+    /**
+     * <p>
+     * Constructs the service.
+     * </p>
+     *
+     * @param serviceName the name of the service
+     *
+     * @since 0.2.0
+     */
+    public Service(String serviceName) {
+        this.serviceName = serviceName;
+    }
+
+    /**
+     * <p>
+     * Returns the service name.
+     * </p>
+     *
+     * @return the serivce name
+     *
+     * @since 0.2.0
+     */
+    public final String getServiceName() {
+        return serviceName;
+    }
+
+    /**
+     * <p>
+     * Starts the service.
+     * </p>
+     *
+     * @throws IOException if an IO error occurs
+     *
+     * @since 0.2.0
+     */
+    public final void start() throws IOException {
+        if (startMode == null) {
+            throw new IOException("Service must be initialized first!");
+        }
+
+        // If were accepted (i.e. client) we will call onServiceAccept()
+        if (startMode.intValue() == REQUESTING_SERVICE) {
+            log.info(serviceName + " has been accepted");
+            onServiceAccept();
+        } else {
+            // We've recevied a request instead
+            log.info(serviceName + " has been requested");
+            onServiceRequest();
+        }
+
+        onStart();
+        state.setValue(ServiceState.SERVICE_STARTED);
+    }
+
+    /**
+     * <p>
+     * Called when the service is started.
+     * </p>
+     *
+     * @throws IOException if an IO error occurs
+     *
+     * @since 0.2.0
+     */
+    protected abstract void onStart() throws IOException;
+
+    /**
+     * <p>
+     * Returns the state of the service.
+     * </p>
+     *
+     * @return the state of the service
+     *
+     * @see ServiceState
+     * @since 0.2.0
+     */
+    public ServiceState getState() {
+        return state;
+    }
+
+    /**
+     * <p>
+     * Initialize the service.
+     * </p>
+     *
+     * @param startMode the mode of the service
+     * @param transport the underlying transport protocol
+     *
+     * @throws IOException if an IO error occurs
+     *
+     * @since 0.2.0
+     */
+    public void init(int startMode, TransportProtocol transport)
+        throws IOException {
+        if ((startMode != REQUESTING_SERVICE) &&
+                (startMode != ACCEPTING_SERVICE)) {
+            throw new IOException("Invalid start mode!");
+        }
+
+        this.transport = transport;
+        this.startMode = new Integer(startMode);
+
+        //this.nativeSettings = nativeSettings;
+        onServiceInit(startMode);
+        transport.addMessageStore(messageStore);
+    }
+
+    /**
+     * <p>
+     * Stops the service.
+     * </p>
+     *
+     * @since 0.2.0
+     */
+    public final void stop() {
+        messageStore.close();
+        state.setValue(ServiceState.SERVICE_STOPPED);
+    }
+
+    /**
+     * <p>
+     * Called when the service is accepted by the remote server.
+     * </p>
+     *
+     * @throws IOException
+     *
+     * @since 0.2.0
+     */
+    protected abstract void onServiceAccept() throws IOException;
+
+    /**
+     * <p>
+     * Called when the service is intialized.
+     * </p>
+     *
+     * @param startMode the mode of the service
+     *
+     * @throws IOException if an IO error occurs
+     *
+     * @since 0.2.0
+     */
+    protected abstract void onServiceInit(int startMode)
+        throws IOException;
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    protected abstract void onServiceRequest() throws IOException;
+
+    /**
+     * <p>
+     * Sends the SSH_MSG_SERVICE_ACCEPT message to the client to indicate that
+     * the local computer is accepting the remote computers service request.
+     * </p>
+     *
+     * @throws IOException if an IO error occurs
+     *
+     * @since 0.2.0
+     */
+    protected void sendServiceAccept() throws IOException {
+        SshMsgServiceAccept msg = new SshMsgServiceAccept(serviceName);
+        transport.sendMessage(msg, this);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/ServiceState.java b/src/com/sshtools/j2ssh/transport/ServiceState.java
new file mode 100644
index 0000000000000000000000000000000000000000..cb412a8ecae2089a9050f0f2913f8946208abe87
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/ServiceState.java
@@ -0,0 +1,75 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+import com.sshtools.j2ssh.util.State;
+
+
+/**
+ * <p>
+ * This class represents the state of a transport protocol service.
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Revision: 1.24 $
+ *
+ * @since 0.2.0
+ */
+public class ServiceState extends State {
+    /** The service is unitialized */
+    public final static int SERVICE_UNINITIALIZED = 1;
+
+    /** The service has started and can send/recieve messages */
+    public final static int SERVICE_STARTED = 2;
+
+    /** The service has stopped and no messages can be sent or received */
+    public final static int SERVICE_STOPPED = 3;
+
+    /**
+     * <p>
+     * Constructs the state instance
+     * </p>
+     */
+    public ServiceState() {
+        super(SERVICE_UNINITIALIZED);
+    }
+
+    /**
+     * <p>
+     * Evaluates whether the state is valid.
+     * </p>
+     *
+     * @param state
+     *
+     * @return
+     *
+     * @since 0.2.0
+     */
+    public boolean isValidState(int state) {
+        return ((state == SERVICE_UNINITIALIZED) || (state == SERVICE_STARTED) ||
+        (state == SERVICE_STOPPED));
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/SshMessage.java b/src/com/sshtools/j2ssh/transport/SshMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..b7c5e6209838ea93fc647555f10630b450897cf5
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/SshMessage.java
@@ -0,0 +1,188 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+
+
+/**
+ * <p>
+ * This class implements the payload portion each message sent by the transport
+ * protocol. Each message consists of an integer message id followed by a
+ * variable byte array containing message data.
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Revision: 1.21 $
+ *
+ * @since 0.2.0
+ */
+public abstract class SshMessage {
+    // The message Id of the message
+    private int messageId;
+
+    /**
+     * <p>
+     * Contructs the message.
+     * </p>
+     *
+     * @param messageId the id of the message
+     *
+     * @since 0.2.0
+     */
+    public SshMessage(int messageId) {
+        // Save the message id
+        this.messageId = messageId;
+    }
+
+    /**
+     * <p>
+     * Returns the id of the message
+     * </p>
+     *
+     * @return an integer message id
+     *
+     * @since 0.2.0
+     */
+    public final int getMessageId() {
+        return messageId;
+    }
+
+    /**
+     * <p>
+     * Returns the name of the message implementation for debugging purposes.
+     * </p>
+     *
+     * @return the name of the message e.g. "SSH_MSG_DISCONNECT"
+     *
+     * @since 0.2.0
+     */
+    public abstract String getMessageName();
+
+    /**
+     * <p>
+     * Format the message into the payload array for sending by the transport
+     * protocol. This implementation creates a byte array, writes the  message
+     * id and calls the abstract <code>constructByteArray</code>.
+     * </p>
+     *
+     * @return the payload portion of a transport protocol message
+     *
+     * @throws InvalidMessageException if the message is invalid
+     *
+     * @since 0.2.0
+     */
+    public final byte[] toByteArray() throws InvalidMessageException {
+        // Create a writer object to construct the array
+        ByteArrayWriter baw = new ByteArrayWriter();
+
+        // Write the message id
+        baw.write(messageId);
+
+        // Call the abstract method so subclasses classes can add their data
+        constructByteArray(baw);
+
+        // Return the array
+        return baw.toByteArray();
+    }
+
+    /**
+     * <p>
+     * Initializes the message from a byte array.
+     * </p>
+     *
+     * @param data the byte array being read.
+     *
+     * @throws InvalidMessageException if the message is invalid
+     *
+     * @since 0.2.0
+     */
+    protected final void fromByteArray(ByteArrayReader data)
+        throws InvalidMessageException {
+        // Skip the first 5 bytes as this contains the packet length and payload
+        // length fields
+        data.skip(5);
+
+        int id = data.read();
+
+        if (id != messageId) {
+            throw new InvalidMessageException("The message id " +
+                String.valueOf(id) +
+                " is not the same as the message implementation id " +
+                String.valueOf(messageId));
+        }
+
+        // Call abstract method for subclasses to extract the message specific data
+        constructMessage(data);
+    }
+
+    /**
+     * <p>
+     * Helper method to extract the message id from the complete message data
+     * recieved by the transport protocol.
+     * </p>
+     *
+     * @param msgdata the transport protocol message
+     *
+     * @return the id of the message
+     *
+     * @since 0.2.0
+     */
+    public static Integer getMessageId(byte[] msgdata) {
+        return new Integer(msgdata[5]);
+    }
+
+    /**
+     * <p>
+     * Message implementations should implement this method, writing the data
+     * as exected in the transport protocol message format.
+     * </p>
+     *
+     * @param baw the byte array being written to
+     *
+     * @throws InvalidMessageException if the message is invalid
+     *
+     * @since 0.2.0
+     */
+    protected abstract void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException;
+
+    /**
+     * <p>
+     * Message implementation should implement this method, reading the data as
+     * expected in the transport protocol message format.
+     * </p>
+     *
+     * @param bar the byte array being read
+     *
+     * @throws InvalidMessageException if the message is invalid
+     *
+     * @since 0.2.0
+     */
+    protected abstract void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException;
+}
diff --git a/src/com/sshtools/j2ssh/transport/SshMessageListener.java b/src/com/sshtools/j2ssh/transport/SshMessageListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..47ffeb67bc1c7a8642e6979029ad92cb7e321fc4
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/SshMessageListener.java
@@ -0,0 +1,39 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+
+/**
+ * <p>Title: </p>
+ * <p>Description: </p>
+ * <p>Copyright: Copyright (c) 2003</p>
+ * <p>Company: </p>
+ * @author Lee David Painter
+     * @version $Id: SshMessageListener.java,v 1.6 2003/09/11 15:35:15 martianx Exp $
+ */
+public interface SshMessageListener {
+    public void messageReceived(SshMessage msg);
+}
diff --git a/src/com/sshtools/j2ssh/transport/SshMessageStore.java b/src/com/sshtools/j2ssh/transport/SshMessageStore.java
new file mode 100644
index 0000000000000000000000000000000000000000..0c5f346a6259f2dd1b4ea61511b6801b87058128
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/SshMessageStore.java
@@ -0,0 +1,622 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+
+/**
+ * <p>
+ * This class implements a message store that can be used to provide a blocking
+ * mechanism for transport protocol messages.
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Revision: 1.42 $
+ *
+ * @since 0.2.0
+ */
+public final class SshMessageStore {
+    private static Log log = LogFactory.getLog(SshMessageStore.class);
+
+    // List to hold messages as they are received
+    private List messages = new ArrayList();
+    private Map register = new HashMap();
+    private boolean isClosed = false;
+    private int[] singleIdFilter = new int[1];
+    private int interrupt = 5000;
+    private Vector listeners = new Vector();
+
+    /**
+     * <p>
+     * Contructs the message store.
+     * </p>
+     *
+     * @since 0.2.0
+     */
+    public SshMessageStore() {
+    }
+
+    /**
+     * <p>
+     * Evaluate whether the message store is closed.
+     * </p>
+     *
+     * @return
+     *
+     * @since 0.2.0
+     */
+    public boolean isClosed() {
+        return isClosed;
+    }
+
+    public void addMessageListener(SshMessageListener listener) {
+        synchronized (listeners) {
+            listeners.add(listener);
+        }
+    }
+
+    /**
+     * <p>
+     * Get a message from the store. This method will block until a message
+     * with an id matching the supplied filter arrives, or the message store
+     * closes. The message is removed from the store.
+     * </p>
+     *
+     * @param messageIdFilter an array of message ids that are acceptable
+     *
+     * @return the next available message
+     *
+     * @throws MessageStoreEOFException if the message store is closed
+     * @throws InterruptedException if the thread was interrupted
+     *
+     * @since 0.2.0
+     */
+    public synchronized SshMessage getMessage(int[] messageIdFilter)
+        throws MessageStoreEOFException, InterruptedException {
+        try {
+            return getMessage(messageIdFilter, 0);
+        } catch (MessageNotAvailableException e) {
+            // This should never happen but throw just in case
+            throw new MessageStoreEOFException();
+        }
+    }
+
+    /**
+     * <p>
+     * Get a message from the store. This method will block until a message
+     * with an id matching the supplied filter arrives, the specified timeout
+     * is reached or the message store closes. The message is removed from the
+     * store.
+     * </p>
+     *
+     * @param messageIdFilter an array of message ids that are acceptable.
+     * @param timeout the maximum number of milliseconds to block before
+     *        returning.
+     *
+     * @return the next available message
+     *
+     * @throws MessageStoreEOFException if the message store is closed
+     * @throws MessageNotAvailableException if the message is not available
+     *         after a timeout
+     * @throws InterruptedException if the thread is interrupted
+     *
+     * @since 0.2.0
+     */
+    public synchronized SshMessage getMessage(int[] messageIdFilter, int timeout)
+        throws MessageStoreEOFException, MessageNotAvailableException, 
+            InterruptedException {
+        if ((messages.size() <= 0) && isClosed) {
+            throw new MessageStoreEOFException();
+        }
+
+        if (messageIdFilter == null) {
+            return nextMessage();
+        }
+
+        SshMessage msg;
+        boolean firstPass = true;
+
+        if (timeout < 0) {
+            timeout = 0;
+        }
+
+        while ((messages.size() > 0) || !isClosed) {
+            // lookup the message
+            msg = lookupMessage(messageIdFilter, true);
+
+            if (msg != null) {
+                return msg;
+            } else {
+                // If this is the second time and there's no message, then throw
+                if (!firstPass && (timeout > 0)) {
+                    throw new MessageNotAvailableException();
+                }
+            }
+
+            // Now wait
+            if (!isClosed) {
+                wait((timeout == 0) ? interrupt : timeout);
+            }
+
+            firstPass = false;
+        }
+
+        throw new MessageStoreEOFException();
+    }
+
+    /**
+     * <p>
+     * Get a message from the store. This method will block until a message
+     * with an id matching the supplied id arrives, or the message store
+     * closes. The message is removed from the store.
+     * </p>
+     *
+     * @param messageId the id of the message requried
+     *
+     * @return the next available message with the id supplied
+     *
+     * @throws MessageStoreEOFException if the message store closed
+     * @throws InterruptedException if the thread is interrupted
+     *
+     * @since 0.2.0
+     */
+    public synchronized SshMessage getMessage(int messageId)
+        throws MessageStoreEOFException, InterruptedException {
+        try {
+            return getMessage(messageId, 0);
+        } catch (MessageNotAvailableException e) {
+            // This should never happen by throw jsut in case
+            throw new MessageStoreEOFException();
+        }
+    }
+
+    /**
+     * <p>
+     * Get a message from the store. This method will block until a message
+     * with an id matching the supplied id arrives,the specified timeout is
+     * reached or the message store closes. The message will be removed from
+     * the store.
+     * </p>
+     *
+     * @param messageId the id of the message requried
+     * @param timeout the maximum number of milliseconds to block before
+     *        returning.
+     *
+     * @return the next available message with the id supplied
+     *
+     * @throws MessageStoreEOFException if the message store closed
+     * @throws InterruptedException if the thread is interrupted
+     * @throws InterruptedException
+     *
+     * @since 0.2.0
+     */
+    public synchronized SshMessage getMessage(int messageId, int timeout)
+        throws MessageStoreEOFException, MessageNotAvailableException, 
+            InterruptedException {
+        singleIdFilter[0] = messageId;
+
+        return getMessage(singleIdFilter, timeout);
+    }
+
+    /**
+     * <p>
+     * Evaluate whether the store has any messages.
+     * </p>
+     *
+     * @return true if messages exist, otherwise false
+     *
+     * @since 0.2.0
+     */
+    public boolean hasMessages() {
+        return messages.size() > 0;
+    }
+
+    /**
+     * <p>
+     * Returns the number of messages contained within this message store.
+     * </p>
+     *
+     * @return the number of messages
+     *
+     * @since 0.2.0
+     */
+    public int size() {
+        return messages.size();
+    }
+
+    /**
+     * <p>
+     * Determines if the message id is a registered message of this store.
+     * </p>
+     *
+     * @param messageId the message id
+     *
+     * @return true if the message id is registered, otherwise false
+     *
+     * @since 0.2.0
+     */
+    public boolean isRegisteredMessage(Integer messageId) {
+        return register.containsKey(messageId);
+    }
+
+    /**
+     * <p>
+     * Adds a raw message to the store and processes the data into a registered
+     * message.
+     * </p>
+     *
+     * @param msgdata the raw message data to process
+     *
+     * @throws MessageNotRegisteredException if the message id of the raw data
+     *         is not a registered message
+     * @throws InvalidMessageException if the message is invalid
+     *
+     * @since 0.2.0
+     */
+    public void addMessage(byte[] msgdata)
+        throws MessageNotRegisteredException, InvalidMessageException {
+        Integer messageId = new Integer(msgdata[5]);
+
+        if (!isRegisteredMessage(messageId)) {
+            throw new MessageNotRegisteredException(messageId);
+        }
+
+        Class cls = (Class) register.get(SshMessage.getMessageId(msgdata));
+
+        try {
+            SshMessage msg = (SshMessage) cls.newInstance();
+            msg.fromByteArray(new ByteArrayReader(msgdata));
+            addMessage(msg);
+        } catch (IllegalAccessException iae) {
+            throw new InvalidMessageException(
+                "Illegal access for implementation class " + cls.getName());
+        } catch (InstantiationException ie) {
+            throw new InvalidMessageException("Instantiation failed for class " +
+                cls.getName());
+        }
+    }
+
+    /**
+     * <p>
+     * Add a formed message to the store.
+     * </p>
+     *
+     * @param msg the message to add to the store
+     *
+     * @throws MessageNotRegisteredException if the message type is not
+     *         registered with the store
+     *
+     * @since 0.2.0
+     */
+    public synchronized void addMessage(SshMessage msg)
+        throws MessageNotRegisteredException {
+        // Add the message
+        messages.add(messages.size(), msg);
+
+        synchronized (listeners) {
+            if (listeners.size() > 0) {
+                for (Iterator it = listeners.iterator(); it.hasNext();) {
+                    ((SshMessageListener) it.next()).messageReceived(msg);
+                }
+            }
+        }
+
+        // Notify the threads
+        notifyAll();
+    }
+
+    /**
+     * <p>
+     * Closes the store. This will cause any blocking operations on the message
+     * store to return.
+     * </p>
+     *
+     * @since 0.2.0
+     */
+    public synchronized void close() {
+        isClosed = true;
+
+        // We need to notify all anyway as if there are messages still available
+        // it should not affect the waiting threads as they are waiting for their
+        // own messages to be received because non were avaialable in the first place
+        //if (messages.size()<=0) {
+        notifyAll();
+
+        //}
+    }
+
+    /**
+     * <p>
+     * Get the next message in the store or wait until a new message arrives.
+     * The message is removed from the store.
+     * </p>
+     *
+     * @return the next available message.
+     *
+     * @throws MessageStoreEOFException if the message store is closed
+     * @throws InterruptedException if the thread is interrupted
+     *
+     * @since 0.2.0
+     */
+    public synchronized SshMessage nextMessage()
+        throws MessageStoreEOFException, InterruptedException {
+        if ((messages.size() <= 0) && isClosed) {
+            throw new MessageStoreEOFException();
+        }
+
+        // If there are no messages available then wait untill there are.
+        while ((messages.size() <= 0) && !isClosed) {
+            wait(interrupt);
+        }
+
+        if (messages.size() > 0) {
+            return (SshMessage) messages.remove(0);
+        } else {
+            throw new MessageStoreEOFException();
+        }
+    }
+
+    /**
+     *
+     */
+    public synchronized void breakWaiting() {
+        notifyAll();
+    }
+
+    /**
+     * <p>
+     * Get a message from the store without removing or blocking if the message
+     * does not exist.
+     * </p>
+     *
+     * @param messageIdFilter the id of the message requried
+     *
+     * @return the next available message with the id supplied
+     *
+     * @throws MessageStoreEOFException if the message store closed
+     * @throws MessageNotAvailableException if the message is not available
+     * @throws InterruptedException if the thread is interrupted
+     *
+     * @since 0.2.0
+     */
+    public synchronized SshMessage peekMessage(int[] messageIdFilter)
+        throws MessageStoreEOFException, MessageNotAvailableException, 
+            InterruptedException {
+        return peekMessage(messageIdFilter, 0);
+    }
+
+    /**
+     * <p>
+     * Get a message from the store without removing it; only blocking for the
+     * number of milliseconds specified in the timeout field. If timeout is
+     * zero, the method will not block.
+     * </p>
+     *
+     * @param messageIdFilter an array of acceptable message ids
+     * @param timeout the number of milliseconds to wait
+     *
+     * @return the next available message of the acceptable message ids
+     *
+     * @throws MessageStoreEOFException if the message store is closed
+     * @throws MessageNotAvailableException if the message is not available
+     * @throws InterruptedException if the thread is interrupted
+     *
+     * @since 0.2.0
+     */
+    public synchronized SshMessage peekMessage(int[] messageIdFilter,
+        int timeout)
+        throws MessageStoreEOFException, MessageNotAvailableException, 
+            InterruptedException {
+        SshMessage msg;
+
+        // Do a straight lookup
+        msg = lookupMessage(messageIdFilter, false);
+
+        if (msg != null) {
+            return msg;
+        }
+
+        // If were willing to wait the wait and look again
+        if (timeout > 0) {
+            if (log.isDebugEnabled()) {
+                log.debug("No message so waiting for " +
+                    String.valueOf(timeout) + " milliseconds");
+            }
+
+            wait(timeout);
+            msg = lookupMessage(messageIdFilter, false);
+
+            if (msg != null) {
+                return msg;
+            }
+        }
+
+        // Nothing even after a wait so throw the relevant exception
+        if (isClosed) {
+            throw new MessageStoreEOFException();
+        } else {
+            throw new MessageNotAvailableException();
+        }
+    }
+
+    private SshMessage lookupMessage(int[] messageIdFilter, boolean remove) {
+        SshMessage msg;
+
+        for (int x = 0; x < messages.size(); x++) {
+            msg = (SshMessage) messages.get(x);
+
+            // Determine whether its one of the filtered messages
+            for (int i = 0; i < messageIdFilter.length; i++) {
+                if (msg.getMessageId() == messageIdFilter[i]) {
+                    if (remove) {
+                        messages.remove(msg);
+                    }
+
+                    return msg;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * <p>
+     * Get a message from the store without removing it.
+     * </p>
+     *
+     * @param messageId the acceptable message id
+     *
+     * @return the next available message.
+     *
+     * @throws MessageStoreEOFException if the message store is closed.
+     * @throws MessageNotAvailableException if the message is not available.
+     * @throws InterruptedException if the thread is interrupted
+     *
+     * @since 0.2.0
+     */
+    public synchronized SshMessage peekMessage(int messageId)
+        throws MessageStoreEOFException, MessageNotAvailableException, 
+            InterruptedException {
+        return peekMessage(messageId, 0);
+    }
+
+    /**
+     * <p>
+     * Removes a message from the message store.
+     * </p>
+     *
+     * @param msg the message to remove
+     *
+     * @since 0.2.0
+     */
+    public synchronized void removeMessage(SshMessage msg) {
+        messages.remove(msg);
+    }
+
+    /**
+     * <p>
+     * Get a message from the store without removing it, only blocking for the
+     * number of milliseconds specified in the timeout field.
+     * </p>
+     *
+     * @param messageId the acceptable message id
+     * @param timeout the timeout setting in milliseconds
+     *
+     * @return the next available message
+     *
+     * @throws MessageStoreEOFException if the message store is closed
+     * @throws MessageNotAvailableException if the message is not available
+     * @throws InterruptedException if the thread is interrupted
+     *
+     * @since 0.2.0
+     */
+    public synchronized SshMessage peekMessage(int messageId, int timeout)
+        throws MessageStoreEOFException, MessageNotAvailableException, 
+            InterruptedException {
+        singleIdFilter[0] = messageId;
+
+        return peekMessage(singleIdFilter, timeout);
+    }
+
+    /**
+     * <p>
+     * Register a message implementation with the store.
+     * </p>
+     *
+     * @param messageId the id of the message
+     * @param implementor the class of the implementation
+     *
+     * @since 0.2.0
+     */
+    public void registerMessage(int messageId, Class implementor) {
+        Integer id = new Integer(messageId);
+        register.put(id, implementor);
+    }
+
+    /**
+     * <p>
+     * Returns an Object array (Integers) of the registered message ids.
+     * </p>
+     *
+     * @return the registered message id array
+     *
+     * @since 0.2.0
+     */
+    public Object[] getRegisteredMessageIds() {
+        return register.keySet().toArray();
+    }
+
+    /**
+     * <p>
+     * Create a formed message from raw message data.
+     * </p>
+     *
+     * @param msgdata the raw message data
+     *
+     * @return the formed message
+     *
+     * @throws MessageNotRegisteredException if the message is not a registered
+     *         message
+     * @throws InvalidMessageException if the message is invalid
+     *
+     * @since 0.2.0
+     */
+    public SshMessage createMessage(byte[] msgdata)
+        throws MessageNotRegisteredException, InvalidMessageException {
+        Integer messageId = SshMessage.getMessageId(msgdata);
+
+        if (!isRegisteredMessage(messageId)) {
+            throw new MessageNotRegisteredException(messageId);
+        }
+
+        Class cls = (Class) register.get(SshMessage.getMessageId(msgdata));
+
+        try {
+            SshMessage msg = (SshMessage) cls.newInstance();
+            msg.fromByteArray(new ByteArrayReader(msgdata));
+
+            return msg;
+        } catch (IllegalAccessException iae) {
+            throw new InvalidMessageException(
+                "Illegal access for implementation class " + cls.getName());
+        } catch (InstantiationException ie) {
+            throw new InvalidMessageException("Instantiation failed for class " +
+                cls.getName());
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/SshMsgDebug.java b/src/com/sshtools/j2ssh/transport/SshMsgDebug.java
new file mode 100644
index 0000000000000000000000000000000000000000..74e55b6b69320060d24d66f069caedb2e82b74e9
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/SshMsgDebug.java
@@ -0,0 +1,151 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.20 $
+ */
+public class SshMsgDebug extends SshMessage {
+    /**  */
+    protected final static int SSH_MSG_DEBUG = 4;
+
+    // Holds the language_tag value
+    private String langTag;
+
+    // Holds the message value
+    private String message;
+
+    // Holds the always_display value
+    private boolean alwaysDisplay;
+
+    /**
+     * Creates a new SshMsgDebug object.
+     *
+     * @param alwaysDisplay
+     * @param message
+     * @param langTag
+     */
+    public SshMsgDebug(boolean alwaysDisplay, String message, String langTag) {
+        super(SSH_MSG_DEBUG);
+
+        // Save the debug details
+        this.alwaysDisplay = alwaysDisplay;
+        this.message = message;
+        this.langTag = langTag;
+    }
+
+    /**
+     * Creates a new SshMsgDebug object.
+     */
+    public SshMsgDebug() {
+        super(SSH_MSG_DEBUG);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean getDisplayAlways() {
+        return alwaysDisplay;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getLanguageTag() {
+        return langTag;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessage() {
+        return message;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_DEBUG";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+        try {
+            // Write the data
+            baw.write(alwaysDisplay ? 1 : 0);
+            baw.writeString(message);
+            baw.writeString(langTag);
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Error writing message data: " +
+                ioe.getMessage());
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+        try {
+            // Extract the message information
+            alwaysDisplay = (bar.read() == 0) ? false : true;
+            message = bar.readString();
+            langTag = bar.readString();
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Error reading message data: " +
+                ioe.getMessage());
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/SshMsgDisconnect.java b/src/com/sshtools/j2ssh/transport/SshMsgDisconnect.java
new file mode 100644
index 0000000000000000000000000000000000000000..9989d994f5041b802271cfb23fe24777a13f09da
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/SshMsgDisconnect.java
@@ -0,0 +1,195 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.20 $
+ */
+public class SshMsgDisconnect extends SshMessage {
+    /**  */
+    protected final static int SSH_MSG_DISCONNECT = 1;
+
+    /**  */
+    public final static int HOST_NOT_ALLOWED = 1;
+
+    /**  */
+    public final static int PROTOCOL_ERROR = 2;
+
+    /**  */
+    public final static int KEY_EXCHANGE_FAILED = 3;
+
+    /**  */
+    public final static int RESERVED = 4;
+
+    /**  */
+    public final static int MAC_ERROR = 5;
+
+    /**  */
+    public final static int COMPRESSION_ERROR = 6;
+
+    /**  */
+    public final static int SERVICE_NOT_AVAILABLE = 7;
+
+    /**  */
+    public final static int PROTOCOL_VERSION_NOT_SUPPORTED = 8;
+
+    /**  */
+    public final static int HOST_KEY_NOT_VERIFIABLE = 9;
+
+    /**  */
+    public final static int CONNECTION_LOST = 10;
+
+    /**  */
+    public final static int BY_APPLICATION = 11;
+
+    /**  */
+    public final static int TOO_MANY_CONNECTIONS = 12;
+
+    /**  */
+    public final static int AUTH_CANCELLED_BY_USER = 13;
+
+    /**  */
+    public final static int NO_MORE_AUTH_METHODS_AVAILABLE = 14;
+
+    /**  */
+    public final static int ILLEGAL_USER_NAME = 15;
+
+    // The readble version of the disconneciton reason
+    private String desc;
+
+    // The language tag
+    private String langTag;
+
+    // Holds the reason for disconnection
+    private int reasonCode;
+
+    /**
+     * Creates a new SshMsgDisconnect object.
+     *
+     * @param reasonCode
+     * @param desc
+     * @param langTag
+     */
+    public SshMsgDisconnect(int reasonCode, String desc, String langTag) {
+        super(SSH_MSG_DISCONNECT);
+
+        // Store the message values
+        this.reasonCode = reasonCode;
+        this.desc = desc;
+        this.langTag = langTag;
+    }
+
+    /**
+     * Creates a new SshMsgDisconnect object.
+     */
+    public SshMsgDisconnect() {
+        super(SSH_MSG_DISCONNECT);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getDescription() {
+        return desc;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getLanguageTag() {
+        return langTag;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_DISCONNECT";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getReasonCode() {
+        return reasonCode;
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+        try {
+            baw.writeInt(reasonCode);
+            baw.writeString(desc);
+            baw.writeString(langTag);
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Error writing message data: " +
+                ioe.getMessage());
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+        try {
+            // Save the values
+            reasonCode = (int) bar.readInt();
+            desc = bar.readString();
+            langTag = bar.readString();
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Error reading message data: " +
+                ioe.getMessage());
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/SshMsgIgnore.java b/src/com/sshtools/j2ssh/transport/SshMsgIgnore.java
new file mode 100644
index 0000000000000000000000000000000000000000..441a0b3f983c5aa77ba7e84607ddcfb25eb5e302
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/SshMsgIgnore.java
@@ -0,0 +1,113 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.20 $
+ */
+public class SshMsgIgnore extends SshMessage {
+    /**  */
+    protected final static int SSH_MSG_IGNORE = 2;
+    private String data;
+
+    /**
+     * Creates a new SshMsgIgnore object.
+     *
+     * @param data
+     */
+    public SshMsgIgnore(String data) {
+        super(SSH_MSG_IGNORE);
+        this.data = data;
+    }
+
+    /**
+     * Creates a new SshMsgIgnore object.
+     */
+    public SshMsgIgnore() {
+        super(SSH_MSG_IGNORE);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getData() {
+        return data;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_IGNORE";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+        try {
+            baw.writeString(data);
+        } catch (IOException ioe) {
+            throw new InvalidMessageException(
+                "Error occurred writing message data: " + ioe.getMessage());
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+        try {
+            data = bar.readString();
+        } catch (IOException ioe) {
+            throw new InvalidMessageException(
+                "Error occurred reading message data: " + ioe.getMessage());
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/SshMsgKexInit.java b/src/com/sshtools/j2ssh/transport/SshMsgKexInit.java
new file mode 100644
index 0000000000000000000000000000000000000000..3f6dda922543c2133ec94621e3918c441bb2aa76
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/SshMsgKexInit.java
@@ -0,0 +1,352 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+import com.sshtools.j2ssh.configuration.SshConnectionProperties;
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.cipher.SshCipherFactory;
+import com.sshtools.j2ssh.transport.compression.SshCompressionFactory;
+import com.sshtools.j2ssh.transport.hmac.SshHmacFactory;
+import com.sshtools.j2ssh.transport.kex.SshKeyExchangeFactory;
+import com.sshtools.j2ssh.transport.publickey.SshKeyPairFactory;
+
+import java.io.IOException;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+import java.util.StringTokenizer;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.25 $
+ */
+public class SshMsgKexInit extends SshMessage {
+    /**  */
+    protected final static int SSH_MSG_KEX_INIT = 20;
+    private List supportedCompCS;
+    private List supportedCompSC;
+    private List supportedEncryptCS;
+    private List supportedEncryptSC;
+    private List supportedKex;
+    private List supportedLangCS;
+    private List supportedLangSC;
+    private List supportedMacCS;
+    private List supportedMacSC;
+    private List supportedPK;
+
+    // Message values
+    private byte[] cookie;
+    private boolean firstKexFollows;
+
+    /**
+     * Creates a new SshMsgKexInit object.
+     */
+    public SshMsgKexInit() {
+        super(SSH_MSG_KEX_INIT);
+    }
+
+    /**
+     * Creates a new SshMsgKexInit object.
+     *
+     * @param props
+     */
+    public SshMsgKexInit(SshConnectionProperties props) {
+        super(SSH_MSG_KEX_INIT);
+
+        // Create some random data
+        cookie = new byte[16];
+
+        // Seed the random number generator
+        Random r = ConfigurationLoader.getRND();
+
+        // Get the next random bytes into our cookie
+        r.nextBytes(cookie);
+
+        // Get the supported algorithms from the factory objects but adding the
+        // preffered algorithm to the top of the list
+        supportedKex = sortAlgorithmList(SshKeyExchangeFactory.getSupportedKeyExchanges(),
+                props.getPrefKex());
+        supportedPK = sortAlgorithmList(SshKeyPairFactory.getSupportedKeys(),
+                props.getPrefPublicKey());
+        supportedEncryptCS = sortAlgorithmList(SshCipherFactory.getSupportedCiphers(),
+                props.getPrefCSEncryption());
+        supportedEncryptSC = sortAlgorithmList(SshCipherFactory.getSupportedCiphers(),
+                props.getPrefSCEncryption());
+        supportedMacCS = sortAlgorithmList(SshHmacFactory.getSupportedMacs(),
+                props.getPrefCSMac());
+        supportedMacSC = sortAlgorithmList(SshHmacFactory.getSupportedMacs(),
+                props.getPrefSCMac());
+        supportedCompCS = sortAlgorithmList(SshCompressionFactory.getSupportedCompression(),
+                props.getPrefCSComp());
+        supportedCompSC = sortAlgorithmList(SshCompressionFactory.getSupportedCompression(),
+                props.getPrefSCComp());
+
+        // We currently don't support language preferences
+        supportedLangCS = new ArrayList();
+        supportedLangSC = new ArrayList();
+
+        // We don't guess (I don't see the point of this in the protocol!)
+        firstKexFollows = false;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_KEX_INIT";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public List getSupportedCSComp() {
+        return supportedCompCS;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public List getSupportedCSEncryption() {
+        return supportedEncryptCS;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public List getSupportedCSMac() {
+        return supportedMacCS;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public List getSupportedKex() {
+        return supportedKex;
+    }
+
+    /**
+     *
+     *
+     * @param pks
+     */
+    public void setSupportedPK(List pks) {
+        supportedPK.clear();
+        supportedPK.addAll(pks);
+        sortAlgorithmList(supportedPK, SshKeyPairFactory.getDefaultPublicKey());
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public List getSupportedPublicKeys() {
+        return supportedPK;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public List getSupportedSCComp() {
+        return supportedCompSC;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public List getSupportedSCEncryption() {
+        return supportedEncryptSC;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public List getSupportedSCMac() {
+        return supportedMacSC;
+    }
+
+    /**
+     *
+     *
+     * @param list
+     *
+     * @return
+     */
+    public String createDelimString(List list) {
+        // Set up the seperator (blank to start cause we dont want a comma
+        // at the beginning of the list)
+        String sep = "";
+        String ret = "";
+
+        // Iterate through the list
+        Iterator it = list.iterator();
+
+        while (it.hasNext()) {
+            // Add the seperator and then the item
+            ret += (sep + (String) it.next());
+            sep = ",";
+        }
+
+        return ret;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String toString() {
+        String ret = "SshMsgKexInit:\n";
+        ret += ("Supported Kex " + supportedKex.toString() + "\n");
+        ret += ("Supported Public Keys " + supportedPK.toString() + "\n");
+        ret += ("Supported Encryption Client->Server " +
+        supportedEncryptCS.toString() + "\n");
+        ret += ("Supported Encryption Server->Client " +
+        supportedEncryptSC.toString() + "\n");
+        ret += ("Supported Mac Client->Server " + supportedMacCS.toString() +
+        "\n");
+        ret += ("Supported Mac Server->Client " + supportedMacSC.toString() +
+        "\n");
+        ret += ("Supported Compression Client->Server " +
+        supportedCompCS.toString() + "\n");
+        ret += ("Supported Compression Server->Client " +
+        supportedCompSC.toString() + "\n");
+        ret += ("Supported Languages Client->Server " +
+        supportedLangCS.toString() + "\n");
+        ret += ("Supported Languages Server->Client " +
+        supportedLangSC.toString() + "\n");
+        ret += ("First Kex Packet Follows [" +
+        (firstKexFollows ? "TRUE]" : "FALSE]"));
+
+        return ret;
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+        try {
+            baw.write(cookie);
+            baw.writeString(createDelimString(supportedKex));
+            baw.writeString(createDelimString(supportedPK));
+            baw.writeString(createDelimString(supportedEncryptCS));
+            baw.writeString(createDelimString(supportedEncryptSC));
+            baw.writeString(createDelimString(supportedMacCS));
+            baw.writeString(createDelimString(supportedMacSC));
+            baw.writeString(createDelimString(supportedCompCS));
+            baw.writeString(createDelimString(supportedCompSC));
+            baw.writeString(createDelimString(supportedLangCS));
+            baw.writeString(createDelimString(supportedLangSC));
+            baw.write((firstKexFollows ? 1 : 0));
+            baw.writeInt(0);
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Error writing message data: " +
+                ioe.getMessage());
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+        try {
+            cookie = new byte[16];
+            bar.read(cookie);
+            supportedKex = loadListFromString(bar.readString());
+            supportedPK = loadListFromString(bar.readString());
+            supportedEncryptCS = loadListFromString(bar.readString());
+            supportedEncryptSC = loadListFromString(bar.readString());
+            supportedMacCS = loadListFromString(bar.readString());
+            supportedMacSC = loadListFromString(bar.readString());
+            supportedCompCS = loadListFromString(bar.readString());
+            supportedCompSC = loadListFromString(bar.readString());
+            supportedLangCS = loadListFromString(bar.readString());
+            supportedLangSC = loadListFromString(bar.readString());
+            firstKexFollows = (bar.read() == 0) ? false : true;
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Error reading message data: " +
+                ioe.getMessage());
+        }
+    }
+
+    private List loadListFromString(String str) {
+        // Create a tokeizer object
+        StringTokenizer tok = new StringTokenizer(str, ",");
+        List ret = new ArrayList();
+
+        // Iterate through the tokens adding the items to the list
+        while (tok.hasMoreElements()) {
+            ret.add(tok.nextElement());
+        }
+
+        return ret;
+    }
+
+    private List sortAlgorithmList(List list, String pref) {
+        if (list.contains(pref)) {
+            // Remove the prefered from the list wherever it may be
+            list.remove(pref);
+
+            // Add it to the beginning of the list
+            list.add(0, pref);
+        }
+
+        return list;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/SshMsgNewKeys.java b/src/com/sshtools/j2ssh/transport/SshMsgNewKeys.java
new file mode 100644
index 0000000000000000000000000000000000000000..b2095dc5be8ae96e268b035b09485324f15056d5
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/SshMsgNewKeys.java
@@ -0,0 +1,79 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.20 $
+ */
+public class SshMsgNewKeys extends SshMessage {
+    /**  */
+    protected final static int SSH_MSG_NEWKEYS = 21;
+
+    /**
+     * Creates a new SshMsgNewKeys object.
+     */
+    public SshMsgNewKeys() {
+        super(SSH_MSG_NEWKEYS);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_NEWKEYS";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/SshMsgServiceAccept.java b/src/com/sshtools/j2ssh/transport/SshMsgServiceAccept.java
new file mode 100644
index 0000000000000000000000000000000000000000..3c193d54d2da51ecf07fd268473fd389c7a9c55d
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/SshMsgServiceAccept.java
@@ -0,0 +1,115 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.21 $
+ */
+public class SshMsgServiceAccept extends SshMessage {
+    /**  */
+    protected final static int SSH_MSG_SERVICE_ACCEPT = 6;
+    private String serviceName;
+
+    /**
+     * Creates a new SshMsgServiceAccept object.
+     *
+     * @param serviceName
+     */
+    public SshMsgServiceAccept(String serviceName) {
+        super(SSH_MSG_SERVICE_ACCEPT);
+        this.serviceName = serviceName;
+    }
+
+    /**
+     * Creates a new SshMsgServiceAccept object.
+     */
+    public SshMsgServiceAccept() {
+        super(SSH_MSG_SERVICE_ACCEPT);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_SERVICE_ACCEPT";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getServiceName() {
+        return serviceName;
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+        try {
+            baw.writeString(serviceName);
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Error writing message data");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+        try {
+            if (bar.available() > 0) {
+                serviceName = bar.readString();
+            } else {
+                serviceName = "";
+            }
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Error reading message data");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/SshMsgServiceRequest.java b/src/com/sshtools/j2ssh/transport/SshMsgServiceRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..5d91ce5de4fcee11be6bff672fb10a4d2f219166
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/SshMsgServiceRequest.java
@@ -0,0 +1,111 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.21 $
+ */
+public class SshMsgServiceRequest extends SshMessage {
+    /**  */
+    public final static int SSH_MSG_SERVICE_REQUEST = 5;
+    private String serviceName;
+
+    /**
+     * Creates a new SshMsgServiceRequest object.
+     *
+     * @param serviceName
+     */
+    public SshMsgServiceRequest(String serviceName) {
+        super(SSH_MSG_SERVICE_REQUEST);
+        this.serviceName = serviceName;
+    }
+
+    /**
+     * Creates a new SshMsgServiceRequest object.
+     */
+    public SshMsgServiceRequest() {
+        super(SSH_MSG_SERVICE_REQUEST);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_SERVICE_REQUEST";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getServiceName() {
+        return serviceName;
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+        try {
+            baw.writeString(serviceName);
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Error writing message data");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+        try {
+            serviceName = bar.readString();
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Error reading message data");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/SshMsgUnimplemented.java b/src/com/sshtools/j2ssh/transport/SshMsgUnimplemented.java
new file mode 100644
index 0000000000000000000000000000000000000000..e4c2b98b4ce8875c7d76f0e636fa68999df7e9b6
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/SshMsgUnimplemented.java
@@ -0,0 +1,115 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.20 $
+ */
+public class SshMsgUnimplemented extends SshMessage {
+    /**  */
+    protected final static int SSH_MSG_UNIMPLEMENTED = 3;
+
+    // The sequence no of the message
+    private long sequenceNo;
+
+    /**
+     * Creates a new SshMsgUnimplemented object.
+     *
+     * @param sequenceNo
+     */
+    public SshMsgUnimplemented(long sequenceNo) {
+        super(SSH_MSG_UNIMPLEMENTED);
+        this.sequenceNo = sequenceNo;
+    }
+
+    /**
+     * Creates a new SshMsgUnimplemented object.
+     */
+    public SshMsgUnimplemented() {
+        super(SSH_MSG_UNIMPLEMENTED);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_UNIMPLEMENTED";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getSequenceNo() {
+        return sequenceNo;
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+        try {
+            baw.writeInt(sequenceNo);
+        } catch (IOException ioe) {
+            throw new InvalidMessageException(
+                "Error extracting SSH_MSG_UNIMPLMENTED, expected int value");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+        try {
+            sequenceNo = bar.readInt();
+        } catch (IOException ioe) {
+            throw new InvalidMessageException(
+                "Error contructing SSH_MSG_UNIMPLEMENTED, expected int value");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/TransportProtocol.java b/src/com/sshtools/j2ssh/transport/TransportProtocol.java
new file mode 100644
index 0000000000000000000000000000000000000000..4146bca05a173dd944913c25e42ef0fca017ece8
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/TransportProtocol.java
@@ -0,0 +1,101 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.25 $
+ */
+public interface TransportProtocol {
+    /**
+     *
+     *
+     * @param description
+     */
+    public void disconnect(String description);
+
+    /**
+     *
+     *
+     * @param store
+     *
+     * @throws MessageAlreadyRegisteredException
+     */
+    public void addMessageStore(SshMessageStore store)
+        throws MessageAlreadyRegisteredException;
+
+    /**
+     *
+     *
+     * @param ms
+     * @param sender
+     *
+     * @throws IOException
+     */
+    public void sendMessage(SshMessage ms, Object sender)
+        throws IOException;
+
+    /**
+     *
+     *
+     * @param filter
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public SshMessage readMessage(int[] filter) throws IOException;
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getSessionIdentifier();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getConnectionId();
+
+    public boolean isConnected();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public TransportProtocolState getState();
+
+    public String getUnderlyingProviderDetail();
+}
diff --git a/src/com/sshtools/j2ssh/transport/TransportProtocolAlgorithmSync.java b/src/com/sshtools/j2ssh/transport/TransportProtocolAlgorithmSync.java
new file mode 100644
index 0000000000000000000000000000000000000000..14590907bcddae24c6159f0d5140d0c8885e26a7
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/TransportProtocolAlgorithmSync.java
@@ -0,0 +1,130 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+import com.sshtools.j2ssh.transport.cipher.SshCipher;
+import com.sshtools.j2ssh.transport.compression.SshCompression;
+import com.sshtools.j2ssh.transport.hmac.SshHmac;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.18 $
+ */
+public class TransportProtocolAlgorithmSync {
+    private static Log log = LogFactory.getLog(TransportProtocolAlgorithmSync.class);
+    private SshCipher cipher = null;
+    private SshCompression compression = null;
+    private SshHmac hmac = null;
+    private boolean isLocked = false;
+
+    /**
+     * Creates a new TransportProtocolAlgorithmSync object.
+     */
+    public TransportProtocolAlgorithmSync() {
+    }
+
+    /**
+     *
+     *
+     * @param cipher
+     */
+    public synchronized void setCipher(SshCipher cipher) {
+        this.cipher = cipher;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public synchronized SshCipher getCipher() {
+        return cipher;
+    }
+
+    /**
+     *
+     *
+     * @param compression
+     */
+    public synchronized void setCompression(SshCompression compression) {
+        this.compression = compression;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public synchronized SshCompression getCompression() {
+        return compression;
+    }
+
+    /**
+     *
+     *
+     * @param hmac
+     */
+    public synchronized void setHmac(SshHmac hmac) {
+        this.hmac = hmac;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public synchronized SshHmac getHmac() {
+        return hmac;
+    }
+
+    /**
+     *
+     */
+    public synchronized void lock() {
+        while (isLocked) {
+            try {
+                wait(50);
+            } catch (InterruptedException e) {
+            }
+        }
+
+        isLocked = true;
+    }
+
+    /**
+     *
+     */
+    public synchronized void release() {
+        isLocked = false;
+        notifyAll();
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/TransportProtocolClient.java b/src/com/sshtools/j2ssh/transport/TransportProtocolClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..067598c924451d85560246c1e50b1a684939f29a
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/TransportProtocolClient.java
@@ -0,0 +1,457 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+import com.sshtools.j2ssh.SshException;
+import com.sshtools.j2ssh.transport.cipher.SshCipher;
+import com.sshtools.j2ssh.transport.cipher.SshCipherFactory;
+import com.sshtools.j2ssh.transport.hmac.SshHmac;
+import com.sshtools.j2ssh.transport.hmac.SshHmacFactory;
+import com.sshtools.j2ssh.transport.kex.KeyExchangeException;
+import com.sshtools.j2ssh.transport.kex.SshKeyExchange;
+import com.sshtools.j2ssh.transport.publickey.SshKeyPair;
+import com.sshtools.j2ssh.transport.publickey.SshKeyPairFactory;
+import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
+
+import java.io.IOException;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.49 $
+ */
+public class TransportProtocolClient extends TransportProtocolCommon {
+    /**  */
+    protected SshPublicKey pk;
+    private HostKeyVerification hosts;
+    private Map services = new HashMap();
+    private SshMessageStore ms = new SshMessageStore();
+
+    /**
+     * Creates a new TransportProtocolClient object.
+     *
+     * @param hosts
+     *
+     * @throws TransportProtocolException
+     */
+    public TransportProtocolClient(HostKeyVerification hosts)
+        throws TransportProtocolException {
+        super();
+        this.hosts = hosts;
+    }
+
+    /**
+     *
+     *
+     * @param msg
+     *
+     * @throws IOException
+     */
+    public void onMessageReceived(SshMessage msg) throws IOException {
+        throw new IOException("No messages are registered");
+    }
+
+    /**
+     *
+     *
+     * @throws MessageAlreadyRegisteredException
+     */
+    public void registerTransportMessages()
+        throws MessageAlreadyRegisteredException {
+        // Setup our private message store, we wont be registering any direct messages
+        ms.registerMessage(SshMsgServiceAccept.SSH_MSG_SERVICE_ACCEPT,
+            SshMsgServiceAccept.class);
+        this.addMessageStore(ms);
+    }
+
+    /**
+     *
+     *
+     * @param service
+     *
+     * @throws IOException
+     * @throws SshException
+     */
+    public void requestService(Service service) throws IOException {
+        // Make sure the service is supported
+        if (service.getState().getValue() != ServiceState.SERVICE_UNINITIALIZED) {
+            throw new IOException("The service instance must be uninitialized");
+        }
+
+        if ((state.getValue() != TransportProtocolState.CONNECTED) &&
+                (state.getValue() != TransportProtocolState.PERFORMING_KEYEXCHANGE)) {
+            throw new IOException("The transport protocol is not connected");
+        }
+
+        try {
+            state.waitForState(TransportProtocolState.CONNECTED);
+        } catch (InterruptedException ie) {
+            throw new IOException("The operation was interrupted");
+        }
+
+        service.init(Service.REQUESTING_SERVICE, this); // , null);
+
+        // Put the service on our list awaiting acceptance
+        services.put(service.getServiceName(), service);
+
+        // Create and send the message
+        SshMessage msg = new SshMsgServiceRequest(service.getServiceName());
+        sendMessage(msg, this);
+
+        try {
+            // Wait for the accept message, if the service is not accepted the
+            // transport protocol disconencts which should cause an excpetion
+            msg = ms.getMessage(SshMsgServiceAccept.SSH_MSG_SERVICE_ACCEPT);
+        } catch (InterruptedException ex) {
+            throw new SshException(
+                "The thread was interrupted whilst waiting for a transport protocol message");
+        }
+
+        return;
+    }
+
+    /**
+     *
+     */
+    protected void onDisconnect() {
+        Iterator it = services.entrySet().iterator();
+        Map.Entry entry;
+
+        while (it.hasNext()) {
+            entry = (Map.Entry) it.next();
+            ((Service) entry.getValue()).stop();
+        }
+
+        services.clear();
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws AlgorithmNotAgreedException
+     */
+    protected String getDecryptionAlgorithm()
+        throws AlgorithmNotAgreedException {
+        return determineAlgorithm(clientKexInit.getSupportedSCEncryption(),
+            serverKexInit.getSupportedSCEncryption());
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws AlgorithmNotAgreedException
+     */
+    protected String getEncryptionAlgorithm()
+        throws AlgorithmNotAgreedException {
+        return determineAlgorithm(clientKexInit.getSupportedCSEncryption(),
+            serverKexInit.getSupportedCSEncryption());
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws AlgorithmNotAgreedException
+     */
+    protected String getInputStreamCompAlgortihm()
+        throws AlgorithmNotAgreedException {
+        return determineAlgorithm(clientKexInit.getSupportedSCComp(),
+            serverKexInit.getSupportedSCComp());
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws AlgorithmNotAgreedException
+     */
+    protected String getInputStreamMacAlgorithm()
+        throws AlgorithmNotAgreedException {
+        return determineAlgorithm(clientKexInit.getSupportedSCMac(),
+            serverKexInit.getSupportedSCMac());
+    }
+
+    /**
+     *
+     */
+    protected void setLocalIdent() {
+        clientIdent = "SSH-" + PROTOCOL_VERSION + "-" +
+            SOFTWARE_VERSION_COMMENTS + " [CLIENT]";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getLocalId() {
+        return clientIdent;
+    }
+
+    /**
+     *
+     *
+     * @param msg
+     */
+    protected void setLocalKexInit(SshMsgKexInit msg) {
+        log.debug(msg.toString());
+        clientKexInit = msg;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected SshMsgKexInit getLocalKexInit() {
+        return clientKexInit;
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws AlgorithmNotAgreedException
+     */
+    protected String getOutputStreamCompAlgorithm()
+        throws AlgorithmNotAgreedException {
+        return determineAlgorithm(clientKexInit.getSupportedCSComp(),
+            serverKexInit.getSupportedCSComp());
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws AlgorithmNotAgreedException
+     */
+    protected String getOutputStreamMacAlgorithm()
+        throws AlgorithmNotAgreedException {
+        return determineAlgorithm(clientKexInit.getSupportedCSMac(),
+            serverKexInit.getSupportedCSMac());
+    }
+
+    /**
+     *
+     *
+     * @param ident
+     */
+    protected void setRemoteIdent(String ident) {
+        serverIdent = ident;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getRemoteId() {
+        return serverIdent;
+    }
+
+    /**
+     *
+     *
+     * @param msg
+     */
+    protected void setRemoteKexInit(SshMsgKexInit msg) {
+        serverKexInit = msg;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected SshMsgKexInit getRemoteKexInit() {
+        return serverKexInit;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public SshPublicKey getServerHostKey() {
+        return pk;
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     * @throws TransportProtocolException
+     */
+    protected void onStartTransportProtocol() throws IOException {
+        while ((state.getValue() != TransportProtocolState.CONNECTED) &&
+                (state.getValue() != TransportProtocolState.DISCONNECTED)) {
+            try {
+                state.waitForStateUpdate();
+            } catch (InterruptedException ex) {
+                throw new IOException("The operation was interrupted");
+            }
+        }
+
+        if (state.getValue() == TransportProtocolState.DISCONNECTED) {
+            if (state.hasError()) {
+                throw state.getLastError();
+            } else {
+                throw new TransportProtocolException(
+                    "The connection did not complete");
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @param kex
+     *
+     * @throws IOException
+     */
+    protected void performKeyExchange(SshKeyExchange kex)
+        throws IOException {
+        // Start the key exchange instance
+        kex.performClientExchange(clientIdent, serverIdent,
+            clientKexInit.toByteArray(), serverKexInit.toByteArray());
+
+        // Verify the hoskey
+        if (!verifyHostKey(kex.getHostKey(), kex.getSignature(),
+                    kex.getExchangeHash())) {
+            sendDisconnect(SshMsgDisconnect.HOST_KEY_NOT_VERIFIABLE,
+                "The host key supplied was not valid",
+                new KeyExchangeException(
+                    "The host key is invalid or was not accepted!"));
+        }
+    }
+
+    /**
+     *
+     *
+     * @param encryptCSKey
+     * @param encryptCSIV
+     * @param encryptSCKey
+     * @param encryptSCIV
+     * @param macCSKey
+     * @param macSCKey
+     *
+     * @throws AlgorithmNotAgreedException
+     * @throws AlgorithmOperationException
+     * @throws AlgorithmNotSupportedException
+     * @throws AlgorithmInitializationException
+     */
+    protected void setupNewKeys(byte[] encryptCSKey, byte[] encryptCSIV,
+        byte[] encryptSCKey, byte[] encryptSCIV, byte[] macCSKey,
+        byte[] macSCKey)
+        throws AlgorithmNotAgreedException, AlgorithmOperationException, 
+            AlgorithmNotSupportedException, AlgorithmInitializationException {
+        // Setup the encryption cipher
+        SshCipher sshCipher = SshCipherFactory.newInstance(getEncryptionAlgorithm());
+        sshCipher.init(SshCipher.ENCRYPT_MODE, encryptCSIV, encryptCSKey);
+        algorithmsOut.setCipher(sshCipher);
+
+        // Setup the decryption cipher
+        sshCipher = SshCipherFactory.newInstance(getDecryptionAlgorithm());
+        sshCipher.init(SshCipher.DECRYPT_MODE, encryptSCIV, encryptSCKey);
+        algorithmsIn.setCipher(sshCipher);
+
+        // Create and put our macs into operation
+        SshHmac hmac = SshHmacFactory.newInstance(getOutputStreamMacAlgorithm());
+        hmac.init(macCSKey);
+        algorithmsOut.setHmac(hmac);
+        hmac = SshHmacFactory.newInstance(getInputStreamMacAlgorithm());
+        hmac.init(macSCKey);
+        algorithmsIn.setHmac(hmac);
+    }
+
+    /**
+     *
+     *
+     * @param key
+     * @param sig
+     * @param sigdata
+     *
+     * @return
+     *
+     * @throws TransportProtocolException
+     */
+    protected boolean verifyHostKey(byte[] key, byte[] sig, byte[] sigdata)
+        throws TransportProtocolException {
+        // Determine the public key algorithm and obtain an instance
+        SshKeyPair pair = SshKeyPairFactory.newInstance(determineAlgorithm(
+                    clientKexInit.getSupportedPublicKeys(),
+                    serverKexInit.getSupportedPublicKeys()));
+
+        // Iniialize the public key instance
+        pk = pair.setPublicKey(key);
+
+        // We have a valid key so verify it against the allowed hosts
+        String host;
+
+        try {
+            InetAddress addr = InetAddress.getByName(properties.getHost());
+
+            if (!addr.getHostAddress().equals(properties.getHost())) {
+                host = addr.getHostName() + "," + addr.getHostAddress();
+            } else {
+                host = addr.getHostAddress();
+            }
+        } catch (UnknownHostException ex) {
+            log.info("The host " + properties.getHost() +
+                " could not be resolved");
+            host = properties.getHost();
+        }
+
+        if (!hosts.verifyHost(host, pk)) {
+            log.info("The host key was not accepted");
+
+            return false;
+        }
+
+        boolean result = pk.verifySignature(sig, sigdata);
+        log.info("The host key signature is " +
+            (result ? " valid" : "invalid"));
+
+        return result;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/TransportProtocolCommon.java b/src/com/sshtools/j2ssh/transport/TransportProtocolCommon.java
new file mode 100644
index 0000000000000000000000000000000000000000..a74503586cd8621e3da4f618527266b2e11e7f7f
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/TransportProtocolCommon.java
@@ -0,0 +1,1471 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+import com.sshtools.j2ssh.SshException;
+import com.sshtools.j2ssh.SshThread;
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+import com.sshtools.j2ssh.configuration.SshConnectionProperties;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.net.TransportProvider;
+import com.sshtools.j2ssh.transport.kex.KeyExchangeException;
+import com.sshtools.j2ssh.transport.kex.SshKeyExchange;
+import com.sshtools.j2ssh.transport.kex.SshKeyExchangeFactory;
+import com.sshtools.j2ssh.util.Hash;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+
+import java.math.BigInteger;
+
+import java.security.NoSuchAlgorithmException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.2 $
+ */
+public abstract class TransportProtocolCommon
+implements TransportProtocol, Runnable 
+{
+    // Flag to keep on running
+    //private boolean keepRunning = true;
+
+    /**  */
+    protected static Log log = LogFactory.getLog(TransportProtocolCommon.class);
+    private static int nextThreadNo = 1;
+
+    /**  */
+    public final static int EOL_CRLF = 1;
+
+    /**  */
+    public final static int EOL_LF = 2;
+
+    /**  */
+    public static final String PROTOCOL_VERSION = "2.0";
+
+    /**  */
+    public static String SOFTWARE_VERSION_COMMENTS = "http://www.sshtools.com " +
+        ConfigurationLoader.getVersionString("J2SSH", "j2ssh.properties");
+    private int threadNo = nextThreadNo++;
+
+    /**  */
+    protected BigInteger k = null;
+
+    /**  */
+    protected Boolean completeOnNewKeys = new Boolean(false);
+
+    /**  */
+    protected HostKeyVerification hosts;
+
+    /**  */
+    protected Map kexs = new HashMap();
+    private boolean sendIgnore = false;
+
+    //protected Map transportMessages = new HashMap();
+
+    /**  */
+    protected SshConnectionProperties properties;
+
+    /**  */
+    protected SshMessageStore messageStore = new SshMessageStore();
+
+    /**  */
+    protected SshMsgKexInit clientKexInit = null;
+
+    /**  */
+    protected SshMsgKexInit serverKexInit = null;
+
+    /**  */
+    protected String clientIdent = null;
+
+    /**  */
+    protected String serverIdent = null;
+
+    /**  */
+    protected TransportProtocolAlgorithmSync algorithmsIn;
+
+    /**  */
+    protected TransportProtocolAlgorithmSync algorithmsOut;
+
+    /**  */
+    protected TransportProtocolState state = new TransportProtocolState();
+    private byte[] exchangeHash = null;
+
+    /**  */
+    protected byte[] sessionIdentifier = null;
+
+    /**  */
+    protected byte[] hostKey = null;
+
+    /**  */
+    protected byte[] signature = null;
+    private Vector eventHandlers = new Vector();
+
+    // Storage of messages whilst in key exchange
+    private List messageStack = new ArrayList();
+
+    // Message notification registry
+    private Map messageNotifications = new HashMap();
+
+    // Key exchange lock for accessing the kex init messages
+    private Object kexLock = new Object();
+
+    // Object to synchronize key changing
+    private Object keyLock = new Object();
+
+    // The connected socket
+    //private Socket socket;
+    // The underlying transport provider
+    TransportProvider provider;
+
+    // The thread object
+    private SshThread thread;
+    private long kexTimeout = 3600000L;
+    // private long kexTransferLimitKB = 100L; // 100 K
+    private long kexTransferLimitKB = 1073741824L/1024L;
+    // private long kexTransferLimit = 1073741824L;
+    private long startTime = System.currentTimeMillis();
+    private long transferredKB   = 0;
+    private long lastTriggeredKB = 0;
+
+    // The input stream for recieving data
+
+    /**  */
+    protected TransportProtocolInputStream sshIn;
+
+    // The output stream for sending data
+
+    /**  */
+    protected TransportProtocolOutputStream sshOut;
+    private int remoteEOL = EOL_CRLF;
+
+    //private Map registeredMessages = new HashMap();
+    private Vector messageStores = new Vector();
+
+    /**
+     * Creates a new TransportProtocolCommon object.
+     */
+    public TransportProtocolCommon() {
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getConnectionId() {
+        return threadNo;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getRemoteEOL() {
+        return remoteEOL;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public TransportProtocolState getState() {
+        return state;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public SshConnectionProperties getProperties() {
+        return properties;
+    }
+
+    /**
+     *
+     */
+    protected abstract void onDisconnect();
+
+    /**
+     *
+     *
+     * @param description
+     */
+    public void disconnect(String description) {
+        if (log.isDebugEnabled()) {
+            log.debug("Disconnect: " + description);
+        }
+
+        try {
+            state.setValue(TransportProtocolState.DISCONNECTED);
+            state.setDisconnectReason(description);
+
+            // Send the disconnect message automatically
+            sendDisconnect(SshMsgDisconnect.BY_APPLICATION, description);
+        } catch (Exception e) {
+            log.warn("Failed to send disconnect", e);
+        }
+    }
+
+    /**
+     *
+     *
+     * @param sendIgnore
+     */
+    public void setSendIgnore(boolean sendIgnore) {
+        this.sendIgnore = sendIgnore;
+    }
+
+    /**
+     *
+     *
+     * @param seconds
+     *
+     * @throws TransportProtocolException
+     */
+    public void setKexTimeout(long seconds) throws TransportProtocolException {
+        if (seconds < 60) {
+            throw new TransportProtocolException(
+                "Keys can only be re-exchanged every minute or more");
+        }
+
+        kexTimeout = seconds * 1000;
+    }
+
+    /**
+     *
+     *
+     * @param kilobytes
+     *
+     * @throws TransportProtocolException
+     */
+    public void setKexTransferLimit(long kilobytes)
+        throws TransportProtocolException {
+        if (kilobytes < 10) {
+            throw new TransportProtocolException(
+                "Keys can only be re-exchanged after every 10k of data, or more");
+        }
+
+        //kexTransferLimit = kilobytes * 1024;
+        kexTransferLimitKB = kilobytes;
+    }
+
+    /*public InetSocketAddress getRemoteAddress() {
+       return (InetSocketAddress)socket.getRemoteSocketAddress();
+     }*/
+    public long getOutgoingByteCount() {
+        return sshOut.getNumBytesTransfered();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public long getIncomingByteCount() {
+        return sshIn.getNumBytesTransfered();
+    }
+
+    /**
+     *
+     *
+     * @param eventHandler
+     */
+    public void addEventHandler(TransportProtocolEventHandler eventHandler) {
+        if (eventHandler != null) {
+            eventHandlers.add(eventHandler);
+        }
+    }
+
+    /**
+     *
+     *
+     * @throws MessageAlreadyRegisteredException
+     */
+    public abstract void registerTransportMessages()
+        throws MessageAlreadyRegisteredException;
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getSessionIdentifier() {
+        return (byte[]) sessionIdentifier.clone();
+    }
+
+    /**
+     *
+     */
+    public void run() {
+      try {
+        state.setValue(TransportProtocolState.NEGOTIATING_PROTOCOL);
+        log.info("Registering transport protocol messages with inputstream");
+        algorithmsOut = new TransportProtocolAlgorithmSync();
+        algorithmsIn = new TransportProtocolAlgorithmSync();
+        
+        // Create the input/output streams
+        sshIn = new TransportProtocolInputStream(this,
+            provider.getInputStream(), algorithmsIn);
+        sshOut = new TransportProtocolOutputStream(provider.getOutputStream(),
+            this, algorithmsOut);
+        
+        // Register the transport layer messages that this class will handle
+        messageStore.registerMessage(SshMsgDisconnect.SSH_MSG_DISCONNECT,
+            SshMsgDisconnect.class);
+        messageStore.registerMessage(SshMsgIgnore.SSH_MSG_IGNORE,
+            SshMsgIgnore.class);
+        messageStore.registerMessage(SshMsgUnimplemented.SSH_MSG_UNIMPLEMENTED,
+            SshMsgUnimplemented.class);
+        messageStore.registerMessage(SshMsgDebug.SSH_MSG_DEBUG,
+            SshMsgDebug.class);
+        messageStore.registerMessage(SshMsgKexInit.SSH_MSG_KEX_INIT,
+            SshMsgKexInit.class);
+        messageStore.registerMessage(SshMsgNewKeys.SSH_MSG_NEWKEYS,
+            SshMsgNewKeys.class);
+        registerTransportMessages();
+        
+        List list = SshKeyExchangeFactory.getSupportedKeyExchanges();
+        Iterator it = list.iterator();
+        
+        while (it.hasNext()) {
+          String keyExchange = (String) it.next();
+          SshKeyExchange kex = SshKeyExchangeFactory.newInstance(keyExchange);
+          kex.init(this);
+          kexs.put(keyExchange, kex);
+        }
+        
+        // call abstract to initialise the local ident string
+        setLocalIdent();
+        
+        // negotiate the protocol version
+        negotiateVersion();
+        startBinaryPacketProtocol();
+      } 
+      catch (Throwable e) {
+        if (e instanceof IOException) {
+          state.setLastError((IOException) e);
+        }
+        
+        if (state.getValue() != TransportProtocolState.DISCONNECTED) {
+          log.error("The Transport Protocol thread failed", e);
+          
+          //log.info(e.getMessage());
+          stop();
+        }
+      } 
+      finally {
+        thread = null;
+      }
+      
+      log.debug("The Transport Protocol has been stopped");
+    }
+
+    /**
+     *
+     *
+     * @param msg
+     * @param sender
+     *
+     * @throws IOException
+     * @throws TransportProtocolException
+     */
+    public synchronized void sendMessage(SshMessage msg, Object sender)
+        throws IOException {
+        // Send a message, if were in key exchange then add it to
+        // the list unless of course it is a transport protocol or key
+        // exchange message
+        if (log.isDebugEnabled()) {
+            log.info("Sending " + msg.getMessageName());
+        }
+
+        int currentState = state.getValue();
+
+        if (sender instanceof SshKeyExchange ||
+                sender instanceof TransportProtocolCommon ||
+                (currentState == TransportProtocolState.CONNECTED)) {
+            sshOut.sendMessage(msg);
+
+            if (currentState == TransportProtocolState.CONNECTED) {
+                if (sendIgnore) {
+                    byte[] count = new byte[1];
+                    ConfigurationLoader.getRND().nextBytes(count);
+
+                    byte[] rand = new byte[(count[0] & 0xFF) + 1];
+                    ConfigurationLoader.getRND().nextBytes(rand);
+
+                    SshMsgIgnore ignore = new SshMsgIgnore(new String(rand));
+
+                    if (log.isDebugEnabled()) {
+                        log.debug("Sending " + ignore.getMessageName());
+                    }
+
+                    sshOut.sendMessage(ignore);
+                }
+            }
+        } else if (currentState == TransportProtocolState.PERFORMING_KEYEXCHANGE) {
+            log.debug("Adding to message queue whilst in key exchange");
+
+            synchronized (messageStack) {
+                // Add this message to the end of the list
+                messageStack.add(msg);
+            }
+        } else {
+            throw new TransportProtocolException(
+                "The transport protocol is disconnected");
+        }
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    protected abstract void onStartTransportProtocol()
+        throws IOException;
+
+    /**
+     *
+     *
+     * @param provider
+     * @param properties
+     *
+     * @throws IOException
+     */
+    public void startTransportProtocol(TransportProvider provider,
+        SshConnectionProperties properties) throws IOException {
+        // Save the connected socket for later use
+        this.provider = provider;
+        this.properties = properties;
+
+        // Start the transport layer message loop
+        log.info("Starting transport protocol");
+        thread = new SshThread(this, "Transport protocol", true);
+        thread.start();
+        onStartTransportProtocol();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getUnderlyingProviderDetail() {
+        return provider.getProviderDetail();
+    }
+
+    /**
+     *
+     *
+     * @param messageId
+     * @param store
+     *
+     * @throws MessageNotRegisteredException
+     */
+    public void unregisterMessage(Integer messageId, SshMessageStore store)
+        throws MessageNotRegisteredException {
+        if (log.isDebugEnabled()) {
+            log.debug("Unregistering message Id " + messageId.toString());
+        }
+
+        if (!messageNotifications.containsKey(messageId)) {
+            throw new MessageNotRegisteredException(messageId);
+        }
+
+        SshMessageStore actual = (SshMessageStore) messageNotifications.get(messageId);
+
+        if (!store.equals(actual)) {
+            throw new MessageNotRegisteredException(messageId, store);
+        }
+
+        messageNotifications.remove(messageId);
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws AlgorithmNotAgreedException
+     */
+    protected abstract String getDecryptionAlgorithm()
+        throws AlgorithmNotAgreedException;
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws AlgorithmNotAgreedException
+     */
+    protected abstract String getEncryptionAlgorithm()
+        throws AlgorithmNotAgreedException;
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws AlgorithmNotAgreedException
+     */
+    protected abstract String getInputStreamCompAlgortihm()
+        throws AlgorithmNotAgreedException;
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws AlgorithmNotAgreedException
+     */
+    protected abstract String getInputStreamMacAlgorithm()
+        throws AlgorithmNotAgreedException;
+
+    /**
+     *
+     */
+    protected abstract void setLocalIdent();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public abstract String getLocalId();
+
+    /**
+     *
+     *
+     * @param msg
+     */
+    protected abstract void setLocalKexInit(SshMsgKexInit msg);
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected abstract SshMsgKexInit getLocalKexInit();
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws AlgorithmNotAgreedException
+     */
+    protected abstract String getOutputStreamCompAlgorithm()
+        throws AlgorithmNotAgreedException;
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws AlgorithmNotAgreedException
+     */
+    protected abstract String getOutputStreamMacAlgorithm()
+        throws AlgorithmNotAgreedException;
+
+    /**
+     *
+     *
+     * @param ident
+     */
+    protected abstract void setRemoteIdent(String ident);
+
+    /**
+     *
+     *
+     * @return
+     */
+    public abstract String getRemoteId();
+
+    /**
+     *
+     *
+     * @param msg
+     */
+    protected abstract void setRemoteKexInit(SshMsgKexInit msg);
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected abstract SshMsgKexInit getRemoteKexInit();
+
+    /**
+     *
+     *
+     * @param kex
+     *
+     * @throws IOException
+     * @throws KeyExchangeException
+     */
+    protected abstract void performKeyExchange(SshKeyExchange kex)
+        throws IOException, KeyExchangeException;
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws AlgorithmNotAgreedException
+     */
+    protected String getKexAlgorithm() throws AlgorithmNotAgreedException {
+        return determineAlgorithm(clientKexInit.getSupportedKex(),
+            serverKexInit.getSupportedKex());
+    }
+
+    public boolean isConnected() {
+        return (state.getValue() == TransportProtocolState.CONNECTED) ||
+        (state.getValue() == TransportProtocolState.PERFORMING_KEYEXCHANGE);
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     * @throws KeyExchangeException
+     */
+    protected void beginKeyExchange() throws IOException, KeyExchangeException {
+        log.info("Starting key exchange");
+
+        //state.setValue(TransportProtocolState.PERFORMING_KEYEXCHANGE);
+        String kexAlgorithm = "";
+
+        // We now have both kex inits, this is where client/server
+        // implemtations take over so call abstract methods
+        try {
+            // Determine the key exchange algorithm
+            kexAlgorithm = getKexAlgorithm();
+
+            if (log.isDebugEnabled()) {
+                log.debug("Key exchange algorithm: " + kexAlgorithm);
+            }
+
+            // Get an instance of the key exchange algortihm
+            SshKeyExchange kex = (SshKeyExchange) kexs.get(kexAlgorithm);
+
+            // Do the key exchange
+            performKeyExchange(kex);
+
+            // Record the output
+            exchangeHash = kex.getExchangeHash();
+
+            if (sessionIdentifier == null) {
+                sessionIdentifier = new byte[exchangeHash.length];
+                System.arraycopy(exchangeHash, 0, sessionIdentifier, 0,
+                    sessionIdentifier.length);
+                thread.setSessionId(sessionIdentifier);
+            }
+
+            hostKey = kex.getHostKey();
+            signature = kex.getSignature();
+            k = kex.getSecret();
+
+            // Send new keys
+            sendNewKeys();
+            kex.reset();
+        } catch (AlgorithmNotAgreedException e) {
+            sendDisconnect(SshMsgDisconnect.KEY_EXCHANGE_FAILED,
+                "No suitable key exchange algorithm was agreed");
+            throw new KeyExchangeException(
+                "No suitable key exchange algorithm could be agreed.");
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    protected SshMsgKexInit createLocalKexInit() throws IOException {
+        return new SshMsgKexInit(properties);
+    }
+
+    /**
+     *
+     */
+    protected void onCorruptMac() {
+        log.fatal("Corrupt Mac on Input");
+
+        // Send a disconnect message
+        sendDisconnect(SshMsgDisconnect.MAC_ERROR, "Corrupt Mac on input",
+            new SshException("Corrupt Mac on Imput"));
+    }
+
+    /**
+     *
+     *
+     * @param msg
+     *
+     * @throws IOException
+     */
+    protected abstract void onMessageReceived(SshMessage msg)
+        throws IOException;
+
+    /**
+     *
+     *
+     * @param reason
+     * @param description
+     */
+    protected void sendDisconnect(int reason, String description) {
+        SshMsgDisconnect msg = new SshMsgDisconnect(reason, description, "");
+
+        try {
+            sendMessage(msg, this);
+            stop();
+        } catch (Exception e) {
+            log.warn("Failed to send disconnect", e);
+        }
+    }
+
+    /**
+     *
+     *
+     * @param reason
+     * @param description
+     * @param error
+     */
+    protected void sendDisconnect(int reason, String description,
+        IOException error) {
+        state.setLastError(error);
+        sendDisconnect(reason, description);
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    protected void sendKeyExchangeInit() throws IOException {
+        setLocalKexInit(createLocalKexInit());
+        sendMessage(getLocalKexInit(), this);
+        state.setValue(TransportProtocolState.PERFORMING_KEYEXCHANGE);
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    protected void sendNewKeys() throws IOException {
+        // Send new keys
+        SshMsgNewKeys msg = new SshMsgNewKeys();
+        sendMessage(msg, this);
+
+        // Lock the outgoing algorithms so nothing else is sent untill
+        // weve updated them with the new keys
+        algorithmsOut.lock();
+
+        // Do we need to hold the algorithmsOut lock during
+        // the input message handling below? If not, then the 
+        // lock could be taken just before completeKeyExchange 
+        // or even moved into the completeKeyExchange method.
+        // We would then not need the try-finally below (which
+        // is needed for exceptions from eg the readMessage call).
+        boolean hasReleasedLock = false;
+        try {
+            int[] filter = new int[1];
+            filter[0] = SshMsgNewKeys.SSH_MSG_NEWKEYS;
+            msg = (SshMsgNewKeys) readMessage(filter);
+            
+            if (log.isDebugEnabled()) {
+                log.debug("Received " + msg.getMessageName());
+            }
+            
+            // Release done in completeKeyExchange
+            hasReleasedLock = true;
+            completeKeyExchange();
+        }
+        finally {
+            if( ! hasReleasedLock ) {
+                algorithmsOut.release();
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @param encryptCSKey
+     * @param encryptCSIV
+     * @param encryptSCKey
+     * @param encryptSCIV
+     * @param macCSKey
+     * @param macSCKey
+     *
+     * @throws AlgorithmNotAgreedException
+     * @throws AlgorithmOperationException
+     * @throws AlgorithmNotSupportedException
+     * @throws AlgorithmInitializationException
+     */
+    protected abstract void setupNewKeys(byte[] encryptCSKey,
+        byte[] encryptCSIV, byte[] encryptSCKey, byte[] encryptSCIV,
+        byte[] macCSKey, byte[] macSCKey)
+        throws AlgorithmNotAgreedException, AlgorithmOperationException, 
+            AlgorithmNotSupportedException, AlgorithmInitializationException;
+
+    /**
+     *
+     *
+     * @throws IOException
+     * @throws TransportProtocolException
+     */
+    protected void completeKeyExchange() throws IOException {
+        log.info("Completing key exchange");
+
+        boolean hasReleasedLock = false;
+        try {
+            // Reset the state variables
+            //completeOnNewKeys = new Boolean(false);
+            log.debug("Making keys from key exchange output");
+
+            // Make the keys
+            byte[] encryptionKey = makeSshKey('C');
+            byte[] encryptionIV = makeSshKey('A');
+            byte[] decryptionKey = makeSshKey('D');
+            byte[] decryptionIV = makeSshKey('B');
+            byte[] sendMac = makeSshKey('E');
+            byte[] receiveMac = makeSshKey('F');
+            log.debug("Creating algorithm objects");
+            setupNewKeys(encryptionKey, encryptionIV, decryptionKey,
+                decryptionIV, sendMac, receiveMac);
+
+            // Reset the key exchange
+            clientKexInit = null;
+            serverKexInit = null;
+
+            //algorithmsIn.release();
+            algorithmsOut.release();
+            hasReleasedLock = true;
+
+            /*
+             *  Update our state, we can send all packets
+             *
+             */
+            state.setValue(TransportProtocolState.CONNECTED);
+
+            // Send any outstanding messages
+            synchronized (messageStack) {
+                Iterator it = messageStack.iterator();
+                log.debug("Sending queued messages");
+
+                while (it.hasNext()) {
+                    SshMessage msg = (SshMessage) it.next();
+                    sendMessage(msg, this);
+                }
+
+                messageStack.clear();
+            }
+        } catch (AlgorithmNotAgreedException anae) {
+            sendDisconnect(SshMsgDisconnect.KEY_EXCHANGE_FAILED,
+                "Algorithm not agreed");
+            throw new TransportProtocolException(
+                "The connection was disconnected because an algorithm could not be agreed");
+        } catch (AlgorithmNotSupportedException anse) {
+            sendDisconnect(SshMsgDisconnect.KEY_EXCHANGE_FAILED,
+                "Application error");
+            throw new TransportProtocolException(
+                "The connection was disconnected because an algorithm class could not be loaded");
+        } catch (AlgorithmOperationException aoe) {
+            sendDisconnect(SshMsgDisconnect.KEY_EXCHANGE_FAILED,
+                "Algorithm operation error");
+            throw new TransportProtocolException(
+                "The connection was disconnected because" +
+                " of an algorithm operation error");
+        } catch (AlgorithmInitializationException aie) {
+            sendDisconnect(SshMsgDisconnect.KEY_EXCHANGE_FAILED,
+                "Algorithm initialization error");
+            throw new TransportProtocolException(
+                "The connection was disconnected because" +
+                " of an algorithm initialization error");
+        }
+        finally {
+            if( ! hasReleasedLock ) {
+                algorithmsOut.release();
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected List getEventHandlers() {
+        return eventHandlers;
+    }
+
+    /**
+     *
+     *
+     * @param clientAlgorithms
+     * @param serverAlgorithms
+     *
+     * @return
+     *
+     * @throws AlgorithmNotAgreedException
+     */
+    protected String determineAlgorithm(List clientAlgorithms,
+        List serverAlgorithms) throws AlgorithmNotAgreedException {
+        if (log.isDebugEnabled()) {
+            log.debug("Determine Algorithm");
+            log.debug("Client Algorithms: " + clientAlgorithms.toString());
+            log.debug("Server Algorithms: " + serverAlgorithms.toString());
+        }
+
+        String algorithmClient;
+        String algorithmServer;
+        Iterator itClient = clientAlgorithms.iterator();
+
+        while (itClient.hasNext()) {
+            algorithmClient = (String) itClient.next();
+
+            Iterator itServer = serverAlgorithms.iterator();
+
+            while (itServer.hasNext()) {
+                algorithmServer = (String) itServer.next();
+
+                if (algorithmClient.equals(algorithmServer)) {
+                    log.debug("Returning " + algorithmClient);
+
+                    return algorithmClient;
+                }
+            }
+        }
+
+        throw new AlgorithmNotAgreedException("Could not agree algorithm");
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    protected void startBinaryPacketProtocol() throws IOException {
+        // Send our Kex Init
+        sendKeyExchangeInit();
+
+        SshMessage msg;
+
+        // Perform a transport protocol message loop
+        while (state.getValue() != TransportProtocolState.DISCONNECTED) {
+            // Process incoming messages returning any transport protocol
+            // messages to be handled here
+            msg = processMessages();
+
+            if (log.isDebugEnabled()) {
+                log.debug("Received " + msg.getMessageName());
+            }
+
+            switch (msg.getMessageId()) {
+            case SshMsgKexInit.SSH_MSG_KEX_INIT: {
+                onMsgKexInit((SshMsgKexInit) msg);
+
+                break;
+            }
+
+            case SshMsgDisconnect.SSH_MSG_DISCONNECT: {
+                onMsgDisconnect((SshMsgDisconnect) msg);
+
+                break;
+            }
+
+            case SshMsgIgnore.SSH_MSG_IGNORE: {
+                onMsgIgnore((SshMsgIgnore) msg);
+
+                break;
+            }
+
+            case SshMsgUnimplemented.SSH_MSG_UNIMPLEMENTED: {
+                onMsgUnimplemented((SshMsgUnimplemented) msg);
+
+                break;
+            }
+
+            case SshMsgDebug.SSH_MSG_DEBUG: {
+                onMsgDebug((SshMsgDebug) msg);
+
+                break;
+            }
+
+            default:
+                onMessageReceived(msg);
+            }
+        }
+    }
+
+    /**
+     *
+     */
+    protected final void stop() {
+        onDisconnect();
+
+        Iterator it = eventHandlers.iterator();
+        TransportProtocolEventHandler eventHandler;
+
+        while (it.hasNext()) {
+            eventHandler = (TransportProtocolEventHandler) it.next();
+            eventHandler.onDisconnect(this);
+        }
+
+        // Close the input/output streams
+        //sshIn.close();
+        if (messageStore != null) {
+            messageStore.close();
+        }
+
+        // 05/01/2003 moiz change begin:
+        // all close all the registerd messageStores
+        SshMessageStore ms;
+
+        for (it = messageStores.iterator(); (it != null) && it.hasNext();) {
+            ms = (SshMessageStore) it.next();
+
+            try {
+                ms.close();
+            } catch (Exception e) {
+            }
+        }
+
+        messageStores.clear();
+
+        // 05/01/2003 moizd change end:
+        messageStore = null;
+
+        try {
+            provider.close();
+        } catch (IOException ioe) {
+        }
+	state.setValue(TransportProtocolState.DISCONNECTED);
+    }
+
+    private byte[] makeSshKey(char chr) throws IOException {
+        try {
+            // Create the first 20 bytes of key data
+            ByteArrayWriter keydata = new ByteArrayWriter();
+            byte[] data = new byte[20];
+            Hash hash = new Hash("SHA");
+
+            // Put the dh k value
+            hash.putBigInteger(k);
+
+            // Put in the exchange hash
+            hash.putBytes(exchangeHash);
+
+            // Put in the character
+            hash.putByte((byte) chr);
+
+            // Put the exchange hash in again
+            hash.putBytes(sessionIdentifier);
+
+            // Create the fist 20 bytes
+            data = hash.doFinal();
+            keydata.write(data);
+
+            // Now do the next 20
+            hash.reset();
+
+            // Put the dh k value in again
+            hash.putBigInteger(k);
+
+            // And the exchange hash
+            hash.putBytes(exchangeHash);
+
+            // Finally the first 20 bytes of data we created
+            hash.putBytes(data);
+            data = hash.doFinal();
+
+            // Put it all together
+            keydata.write(data);
+
+            // Return it
+            return keydata.toByteArray();
+        } catch (NoSuchAlgorithmException nsae) {
+            sendDisconnect(SshMsgDisconnect.KEY_EXCHANGE_FAILED,
+                "Application error");
+            throw new TransportProtocolException("SHA algorithm not supported");
+        } catch (IOException ioe) {
+            sendDisconnect(SshMsgDisconnect.KEY_EXCHANGE_FAILED,
+                "Application error");
+            throw new TransportProtocolException("Error writing key data");
+        }
+    }
+
+    private void negotiateVersion() throws IOException {
+        byte[] buf;
+        int len;
+        String remoteVer = "";
+        log.info("Negotiating protocol version");
+        log.debug("Local identification: " + getLocalId());
+
+        // Get the local ident string by calling the abstract method, this
+        // way the implementations set the correct variables for computing the
+        // exchange hash
+        String data = getLocalId() + "\r\n";
+
+        // Send our version string
+        provider.getOutputStream().write(data.getBytes());
+
+        // Now wait for a reply and evaluate the ident string
+        //buf = new byte[255];
+        StringBuffer buffer = new StringBuffer();
+        char ch;
+        int MAX_BUFFER_LENGTH = 255;
+
+        // Look for a string starting with "SSH-"
+        while (!remoteVer.startsWith("SSH-") &&
+                (buffer.length() < MAX_BUFFER_LENGTH)) {
+            // Get the next string
+            while (((ch = (char) provider.getInputStream().read()) != '\n') &&
+                    (buffer.length() < MAX_BUFFER_LENGTH)) {
+                buffer.append(ch);
+            }
+
+            // Set trimming off any EOL characters
+            remoteVer = buffer.toString();
+
+            // Guess the remote sides EOL by looking at the end of the ident string
+            if (remoteVer.endsWith("\r")) {
+                remoteEOL = EOL_CRLF;
+            } else {
+                remoteEOL = EOL_LF;
+            }
+
+            log.debug("EOL is guessed at " +
+                ((remoteEOL == EOL_CRLF) ? "CR+LF" : "LF"));
+
+            // Remove any \r
+            remoteVer = remoteVer.trim();
+        }
+
+        // Get the index of the seperators
+        int l = remoteVer.indexOf("-");
+        int r = remoteVer.indexOf("-", l + 1);
+
+        // Call abstract method so the implementations can set the
+        // correct member variable
+        setRemoteIdent(remoteVer.trim());
+
+        if (log.isDebugEnabled()) {
+            log.debug("Remote identification: " + getRemoteId());
+        }
+
+        // Get the version
+        String remoteVersion = remoteVer.substring(l + 1, r);
+
+        // Evaluate the version, we only support 2.0
+        if (!(remoteVersion.equals("2.0") || (remoteVersion.equals("1.99")))) {
+            log.fatal(
+                "The remote computer does not support protocol version 2.0");
+            throw new TransportProtocolException(
+                "The protocol version of the remote computer is not supported!");
+        }
+
+        log.info("Protocol negotiation complete");
+    }
+
+    private void onMsgDebug(SshMsgDebug msg) {
+        log.debug(msg.getMessage());
+    }
+
+    private void onMsgDisconnect(SshMsgDisconnect msg)
+        throws IOException {
+        log.info("The remote computer disconnected: " + msg.getDescription());
+        state.setValue(TransportProtocolState.DISCONNECTED);
+        state.setDisconnectReason(msg.getDescription());
+        stop();
+    }
+
+    private void onMsgIgnore(SshMsgIgnore msg) {
+        if (log.isDebugEnabled()) {
+            log.debug("SSH_MSG_IGNORE with " +
+                String.valueOf(msg.getData().length()) + " bytes of data");
+        }
+    }
+
+    private void onMsgKexInit(SshMsgKexInit msg) throws IOException {
+        log.debug("Received remote key exchange init message");
+        log.debug(msg.toString());
+
+        synchronized (kexLock) {
+            setRemoteKexInit(msg);
+
+            // As either party can initiate a key exchange then we
+            // must check to see if we have sent our own
+            if (state.getValue() != TransportProtocolState.PERFORMING_KEYEXCHANGE) {
+                //if (getLocalKexInit() == null) {
+                sendKeyExchangeInit();
+            }
+
+            //}
+            beginKeyExchange();
+        }
+    }
+
+    private void onMsgNewKeys(SshMsgNewKeys msg) throws IOException {
+        // Determine whether we have completed our own
+        log.debug("Received New Keys");
+        //algorithmsIn.lock();
+
+        synchronized (completeOnNewKeys) {
+            if (completeOnNewKeys.booleanValue()) {
+                // We need to take this lock since
+                // it is released in completeKeyExchange.
+                algorithmsOut.lock();
+                completeKeyExchange();
+            } else {
+                completeOnNewKeys = new Boolean(true);
+            }
+        }
+    }
+
+    private void onMsgUnimplemented(SshMsgUnimplemented msg) {
+        if (log.isDebugEnabled()) {
+            log.debug("The message with sequence no " + msg.getSequenceNo() +
+                " was reported as unimplemented by the remote end.");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param filter
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public SshMessage readMessage(int[] filter) throws IOException {
+        byte[] msgdata = null;
+        SshMessage msg;
+
+        while (state.getValue() != TransportProtocolState.DISCONNECTED) {
+            boolean hasmsg = false;
+
+            while (!hasmsg) {
+                msgdata = sshIn.readMessage();
+                hasmsg = true;
+            }
+
+            Integer messageId = SshMessage.getMessageId(msgdata);
+
+            // First check the filter
+            for (int i = 0; i < filter.length; i++) {
+                if (filter[i] == messageId.intValue()) {
+                    if (messageStore.isRegisteredMessage(messageId)) {
+                        return messageStore.createMessage(msgdata);
+                    } else {
+                        SshMessageStore ms = getMessageStore(messageId);
+                        msg = ms.createMessage(msgdata);
+
+                        if (log.isDebugEnabled()) {
+                            log.debug("Processing " + msg.getMessageName());
+                        }
+
+                        return msg;
+                    }
+                }
+            }
+
+            if (messageStore.isRegisteredMessage(messageId)) {
+                msg = messageStore.createMessage(msgdata);
+
+                switch (messageId.intValue()) {
+                case SshMsgDisconnect.SSH_MSG_DISCONNECT: {
+                    onMsgDisconnect((SshMsgDisconnect) msg);
+
+                    break;
+                }
+
+                case SshMsgIgnore.SSH_MSG_IGNORE: {
+                    onMsgIgnore((SshMsgIgnore) msg);
+
+                    break;
+                }
+
+                case SshMsgUnimplemented.SSH_MSG_UNIMPLEMENTED: {
+                    onMsgUnimplemented((SshMsgUnimplemented) msg);
+
+                    break;
+                }
+
+                case SshMsgDebug.SSH_MSG_DEBUG: {
+                    onMsgDebug((SshMsgDebug) msg);
+
+                    break;
+                }
+
+                default: // Exception not allowed
+                    throw new IOException(
+                        "Unexpected transport protocol message");
+                }
+            } else {
+                throw new IOException("Unexpected message received");
+            }
+        }
+
+        throw new IOException("The transport protocol disconnected");
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    protected SshMessage processMessages() throws IOException {
+        byte[] msgdata = null;
+        SshMessage msg;
+        SshMessageStore ms;
+
+        while (state.getValue() != TransportProtocolState.DISCONNECTED) {
+            long currentTime = System.currentTimeMillis();
+
+            transferredKB =  sshIn.getNumBytesTransfered()/1024
+                          + sshOut.getNumBytesTransfered()/1024;
+           
+            long kbLimit = transferredKB - lastTriggeredKB; 
+            
+            if (((currentTime - startTime) > kexTimeout) ||
+                (kbLimit > kexTransferLimitKB) ) 
+            {
+                //    ((sshIn.getNumBytesTransfered() +
+                //    sshOut.getNumBytesTransfered()) > kexTransferLimit)) {
+              startTime     = currentTime;
+              lastTriggeredKB = transferredKB;
+              if (log.isDebugEnabled()) {
+                log.info("rekey");
+              }
+              sendKeyExchangeInit();
+            }
+            
+            boolean hasmsg = false;
+
+            while (!hasmsg) {
+                try {
+                    msgdata = sshIn.readMessage();
+                    hasmsg = true;
+                } catch (InterruptedIOException ex /*SocketTimeoutException ex*/) {
+                    log.info("Possible timeout on transport inputstream");
+
+                    Iterator it = eventHandlers.iterator();
+                    TransportProtocolEventHandler eventHandler;
+                    
+                    while (it.hasNext()) {
+                        eventHandler = (TransportProtocolEventHandler) it.next();
+                        eventHandler.onSocketTimeout(this /*,
+                        provider.isConnected()*/);
+                    }
+                }
+            }
+
+            Integer messageId = SshMessage.getMessageId(msgdata);
+
+            if (!messageStore.isRegisteredMessage(messageId)) {
+                try {
+                    ms = getMessageStore(messageId);
+                    msg = ms.createMessage(msgdata);
+
+                    if (log.isDebugEnabled()) {
+                        log.info("Received " + msg.getMessageName());
+                    }
+
+                    ms.addMessage(msg);
+                } catch (MessageNotRegisteredException mnre) {
+                    log.info("Unimplemented message received " +
+                        String.valueOf(messageId.intValue()));
+                    msg = new SshMsgUnimplemented(sshIn.getSequenceNo());
+                    sendMessage(msg, this);
+                }
+            } else {
+                return messageStore.createMessage(msgdata);
+            }
+        }
+
+        throw new IOException("The transport protocol has disconnected");
+    }
+
+    /**
+     *
+     *
+     * @param store
+     *
+     * @throws MessageAlreadyRegisteredException
+     */
+    public void addMessageStore(SshMessageStore store)
+        throws MessageAlreadyRegisteredException {
+        messageStores.add(store);
+    }
+
+    private SshMessageStore getMessageStore(Integer messageId)
+        throws MessageNotRegisteredException {
+        SshMessageStore ms;
+
+        for (Iterator it = messageStores.iterator();
+                (it != null) && it.hasNext();) {
+            ms = (SshMessageStore) it.next();
+
+            if (ms.isRegisteredMessage(messageId)) {
+                return ms;
+            }
+        }
+
+        throw new MessageNotRegisteredException(messageId);
+    }
+
+    /**
+     *
+     *
+     * @param ms
+     */
+    public void removeMessageStore(SshMessageStore ms) {
+        messageStores.remove(ms);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/TransportProtocolEventAdapter.java b/src/com/sshtools/j2ssh/transport/TransportProtocolEventAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..18fe1b3ea63d346f2abeaf5b2f9bca0f8be629f8
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/TransportProtocolEventAdapter.java
@@ -0,0 +1,80 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+
+/**
+ * <p>
+ * Title:
+ * </p>
+ *
+ * <p>
+ * Description:
+ * </p>
+ *
+ * <p>
+ * Copyright: Copyright (c) 2003
+ * </p>
+ *
+ * <p>
+ * Company:
+ * </p>
+ *
+ * @author Lee David Painter
+ * @version $Id: TransportProtocolEventAdapter.java,v 1.9 2003/09/11 15:35:15 martianx Exp $
+ */
+public class TransportProtocolEventAdapter
+    implements TransportProtocolEventHandler {
+    /**
+     * Creates a new TransportProtocolEventAdapter object.
+     */
+    public TransportProtocolEventAdapter() {
+    }
+
+    /**
+     *
+     *
+     * @param transport
+     */
+    public void onSocketTimeout(TransportProtocol transport) {
+    }
+
+    /**
+     *
+     *
+     * @param transport
+     */
+    public void onDisconnect(TransportProtocol transport) {
+    }
+
+    /**
+     *
+     *
+     * @param transport
+     */
+    public void onConnected(TransportProtocol transport) {
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/TransportProtocolEventHandler.java b/src/com/sshtools/j2ssh/transport/TransportProtocolEventHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..145eadc1d70b9516b16f1b1782f6f9fde3eacb1d
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/TransportProtocolEventHandler.java
@@ -0,0 +1,58 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.18 $
+ */
+public interface TransportProtocolEventHandler {
+    /**
+     *
+     *
+     * @param transport
+     */
+    public void onSocketTimeout(
+        TransportProtocol transport /*,
+    boolean stillConnected*/);
+
+    /**
+     *
+     *
+     * @param transport
+     */
+    public void onDisconnect(TransportProtocol transport);
+
+    /**
+     *
+     *
+     * @param transport
+     */
+    public void onConnected(TransportProtocol transport);
+}
diff --git a/src/com/sshtools/j2ssh/transport/TransportProtocolException.java b/src/com/sshtools/j2ssh/transport/TransportProtocolException.java
new file mode 100644
index 0000000000000000000000000000000000000000..5e886bdb62906bc37db7648ae2e5013ff4bc84d6
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/TransportProtocolException.java
@@ -0,0 +1,46 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+import com.sshtools.j2ssh.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.18 $
+ */
+public class TransportProtocolException extends SshException {
+    /**
+     * Creates a new TransportProtocolException object.
+     *
+     * @param msg
+     */
+    public TransportProtocolException(String msg) {
+        super(msg);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/TransportProtocolInputStream.java b/src/com/sshtools/j2ssh/transport/TransportProtocolInputStream.java
new file mode 100644
index 0000000000000000000000000000000000000000..94f532f7992d58d8d777a2f0e3e84671374ebcbd
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/TransportProtocolInputStream.java
@@ -0,0 +1,335 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.cipher.SshCipher;
+import com.sshtools.j2ssh.transport.compression.SshCompression;
+import com.sshtools.j2ssh.transport.hmac.SshHmac;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+
+import java.math.BigInteger;
+
+import java.net.SocketException;
+
+import java.util.Iterator;
+
+
+class TransportProtocolInputStream {
+    private static Log log = LogFactory.getLog(TransportProtocolInputStream.class);
+    private long bytesTransfered = 0;
+    private BufferedInputStream in;
+    private Object sequenceLock = new Object();
+    private TransportProtocolCommon transport;
+    private TransportProtocolAlgorithmSync algorithms;
+    private long sequenceNo = 0;
+    private long sequenceWrapLimit = BigInteger.valueOf(2).pow(32).longValue();
+    private SshCipher cipher;
+    private SshHmac hmac;
+    private SshCompression compression;
+    int msglen;
+    int padlen;
+    int read;
+    int remaining;
+    int cipherlen = 8;
+    int maclen = 0;
+
+    //byte[] buffer = new byte[128 * cipherlen];
+    ByteArrayWriter message = new ByteArrayWriter();
+    byte[] initial = new byte[cipherlen];
+    byte[] data = new byte[65535];
+    byte[] buffered = new byte[65535];
+    int startpos = 0;
+    int endpos = 0;
+
+    /**
+     * Creates a new TransportProtocolInputStream object.
+     *
+     * @param transport
+     * @param in
+     * @param algorithms
+     *
+     * @throws IOException
+     */
+    public TransportProtocolInputStream(TransportProtocolCommon transport,
+        
+    /*Socket socket,*/
+    InputStream in, TransportProtocolAlgorithmSync algorithms)
+        throws IOException {
+        this.transport = transport;
+
+        this.in = new BufferedInputStream(in); //socket.getInputStream());
+
+        this.algorithms = algorithms;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public synchronized long getSequenceNo() {
+        return sequenceNo;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected long getNumBytesTransfered() {
+        return bytesTransfered;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected int available() {
+        return endpos - startpos;
+    }
+
+    /**
+     *
+     *
+     * @param buf
+     * @param off
+     * @param len
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    protected int readBufferedData(byte[] buf, int off, int len)
+        throws IOException {
+        int read;
+
+        if ((endpos - startpos) < len) {
+            // Double check the buffer has enough room for the data
+            if ((buffered.length - endpos) < len) {
+                /*if (log.isDebugEnabled()) {
+                      log.debug("Trimming used data from buffer");
+                                 }*/
+
+                // no it does not odds are that the startpos is too high
+                System.arraycopy(buffered, startpos, buffered, 0,
+                    endpos - startpos);
+
+                endpos -= startpos;
+
+                startpos = 0;
+
+                if ((buffered.length - endpos) < len) {
+                    //log.debug("Resizing message buffer");
+                    // Last resort resize the buffer to the required length
+                    // this should stop any chance of error
+                    byte[] tmp = new byte[buffered.length + len];
+
+                    System.arraycopy(buffered, 0, tmp, 0, endpos);
+
+                    buffered = tmp;
+                }
+            }
+
+            // If there is not enough data then block and read until there is (if still connected)
+            while (((endpos - startpos) < len) &&
+                    (transport.getState().getValue() != TransportProtocolState.DISCONNECTED)) {
+                try {
+                    read = in.read(buffered, endpos, (buffered.length - endpos));
+                } catch (InterruptedIOException ex) {
+                    // We have an interrupted io; inform the event handler
+                    read = ex.bytesTransferred;
+
+                    Iterator it = transport.getEventHandlers().iterator();
+
+                    TransportProtocolEventHandler eventHandler;
+
+                    while (it.hasNext()) {
+                        eventHandler = (TransportProtocolEventHandler) it.next();
+
+                        eventHandler.onSocketTimeout(transport);
+                    }
+                }
+
+                if (read < 0) {
+                    throw new IOException("The socket is EOF");
+                }
+
+                endpos += read;
+            }
+        }
+
+        try {
+            System.arraycopy(buffered, startpos, buf, off, len);
+        } catch (Throwable t) {
+            System.out.println();
+        }
+
+        startpos += len;
+
+        /*if (log.isDebugEnabled()) {
+               log.debug("Buffer StartPos=" + String.valueOf(startpos)
+                + " EndPos=" + String.valueOf(endpos));
+         }*/
+
+        // Try to reset the buffer
+        if (startpos >= endpos) {
+            //if (log.isDebugEnabled()) {
+            // log.debug("Buffer has been reset");
+            // }*/
+            endpos = 0;
+
+            startpos = 0;
+        }
+
+        return len;
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws SocketException
+     * @throws IOException
+     */
+    public byte[] readMessage() throws SocketException, IOException {
+        // Reset the message for the next
+        message.reset();
+
+        // Read the first byte of this message (this is so we block
+        // but we will determine the cipher length before reading all
+        read = readBufferedData(initial, 0, cipherlen);
+
+        cipher = algorithms.getCipher();
+
+        hmac = algorithms.getHmac();
+
+        compression = algorithms.getCompression();
+
+        // If the cipher object has been set then make sure
+        // we have the correct blocksize
+        if (cipher != null) {
+            cipherlen = cipher.getBlockSize();
+        } else {
+            cipherlen = 8;
+        }
+
+        // Verify we have enough buffer size for the inital block
+        if (initial.length != cipherlen) {
+            // Create a temporary array for the new block size and copy
+            byte[] tmp = new byte[cipherlen];
+
+            System.arraycopy(initial, 0, tmp, 0, initial.length);
+
+            // Now change the initial buffer to our new array
+            initial = tmp;
+        }
+
+        // Now read the rest of the first block of data if necersary
+        int count = read;
+
+        if (count < initial.length) {
+            count += readBufferedData(initial, count, initial.length - count);
+        }
+
+        // Record the mac length
+        if (hmac != null) {
+            maclen = hmac.getMacLength();
+        } else {
+            maclen = 0;
+        }
+
+        // Decrypt the data if we have a valid cipher
+        if (cipher != null) {
+            initial = cipher.transform(initial);
+        }
+
+        // Save the initial data
+        message.write(initial);
+
+        // Preview the message length
+        msglen = (int) ByteArrayReader.readInt(initial, 0);
+
+        padlen = initial[4];
+
+        // Read, decrypt and save the remaining data
+        remaining = (msglen - (cipherlen - 4));
+
+        while (remaining > 0) {
+            read = readBufferedData(data, 0,
+                    (remaining < data.length)
+                    ? ((remaining / cipherlen) * cipherlen)
+                    : ((data.length / cipherlen) * cipherlen));
+            remaining -= read;
+
+            // Decrypt the data and/or write it to the message
+            message.write((cipher == null) ? data
+                                           : cipher.transform(data, 0, read),
+                0, read);
+        }
+
+        synchronized (sequenceLock) {
+            if (hmac != null) {
+                read = readBufferedData(data, 0, maclen);
+
+                message.write(data, 0, read);
+
+                // Verify the mac
+                if (!hmac.verify(sequenceNo, message.toByteArray())) {
+                    throw new IOException("Corrupt Mac on input");
+                }
+            }
+
+            // Increment the sequence no
+            if (sequenceNo < sequenceWrapLimit) {
+                sequenceNo++;
+            } else {
+                sequenceNo = 0;
+            }
+        }
+
+        bytesTransfered += message.size();
+
+        byte[] msg = message.toByteArray();
+
+        // Uncompress the message payload if necersary
+        if (compression != null) {
+            return compression.uncompress(msg, 5, (msglen + 4) - padlen - 5);
+        }
+
+        return msg;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/TransportProtocolOutputStream.java b/src/com/sshtools/j2ssh/transport/TransportProtocolOutputStream.java
new file mode 100644
index 0000000000000000000000000000000000000000..c4875d931a2bea88e1bffad7d1d03d1dfb0f52a7
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/TransportProtocolOutputStream.java
@@ -0,0 +1,197 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.cipher.SshCipher;
+import com.sshtools.j2ssh.transport.compression.SshCompression;
+import com.sshtools.j2ssh.transport.hmac.SshHmac;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import java.math.BigInteger;
+
+import java.util.Random;
+
+
+class TransportProtocolOutputStream {
+    //implements Runnable {
+    private static Log log = LogFactory.getLog(TransportProtocolOutputStream.class);
+    private OutputStream out;
+
+    //private Socket socket;
+    private TransportProtocolAlgorithmSync algorithms;
+    private TransportProtocolCommon transport;
+    private long sequenceNo = 0;
+    private long sequenceWrapLimit = BigInteger.valueOf(2).pow(32).longValue();
+    private Random rnd = ConfigurationLoader.getRND();
+    private long bytesTransfered = 0;
+
+    /**
+     * Creates a new TransportProtocolOutputStream object.
+     *
+     * @param out
+     * @param transport
+     * @param algorithms
+     *
+     * @throws TransportProtocolException
+     */
+    public TransportProtocolOutputStream( /*Socket socket,*/
+        OutputStream out, TransportProtocolCommon transport,
+        TransportProtocolAlgorithmSync algorithms)
+        throws TransportProtocolException {
+        // try {
+        //this.socket = socket;
+        this.out = out; //socket.getOutputStream();
+        this.transport = transport;
+        this.algorithms = algorithms;
+
+        /* } catch (IOException ioe) {
+               throw new TransportProtocolException(
+          "Failed to obtain socket output stream");
+           }*/
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    protected long getNumBytesTransfered() {
+        return bytesTransfered;
+    }
+
+    /**
+     *
+     *
+     * @param msg
+     *
+     * @throws TransportProtocolException
+     */
+    protected synchronized void sendMessage(SshMessage msg)
+        throws TransportProtocolException {
+        try {
+            // Get the algorithm objects
+            algorithms.lock();
+
+            SshCipher cipher = algorithms.getCipher();
+            SshHmac hmac = algorithms.getHmac();
+            SshCompression compression = algorithms.getCompression();
+
+            // Write the data into a byte array
+            ByteArrayWriter message = new ByteArrayWriter();
+
+            // Get the message payload data
+            byte[] msgdata = msg.toByteArray();
+
+            //int payload = msgdata.length;
+            int padding = 4;
+            int cipherlen = 8;
+
+            // Determine the cipher length
+            if (cipher != null) {
+                cipherlen = cipher.getBlockSize();
+            }
+
+            // Compress the payload if necersary
+            if (compression != null) {
+                msgdata = compression.compress(msgdata, 0, msgdata.length);
+            }
+
+            //Determine the padding length
+            padding += ((cipherlen -
+            ((msgdata.length + 5 + padding) % cipherlen)) % cipherlen);
+
+            // Write the packet length field
+            message.writeInt(msgdata.length + 1 + padding);
+
+            // Write the padding length
+            message.write(padding);
+
+            // Write the message payload
+            message.write(msgdata, 0, msgdata.length);
+
+            // Create some random data for the padding
+            byte[] pad = new byte[padding];
+            rnd.nextBytes(pad);
+
+            // Write the padding
+            message.write(pad);
+
+            // Get the unencrypted packet data
+            byte[] packet = message.toByteArray();
+            byte[] mac = null;
+
+            // Generate the MAC
+            if (hmac != null) {
+                mac = hmac.generate(sequenceNo, packet, 0, packet.length);
+            }
+
+            // Perfrom encrpytion
+            if (cipher != null) {
+                packet = cipher.transform(packet);
+            }
+
+            // Reset the message
+            message.reset();
+
+            // Write the packet data
+            message.write(packet);
+
+            // Combine the packet and MAC
+            if (mac != null) {
+                message.write(mac);
+            }
+
+            bytesTransfered += message.size();
+
+            // Send!
+            out.write(message.toByteArray());
+
+            out.flush();
+
+            // Increment the sequence no
+            if (sequenceNo < sequenceWrapLimit) {
+                sequenceNo++;
+            } else {
+                sequenceNo = 0;
+            }
+        } catch (IOException ioe) {
+            if (transport.getState().getValue() != TransportProtocolState.DISCONNECTED) {
+                throw new TransportProtocolException("IO Error on socket: " +
+                    ioe.getMessage());
+            }
+        }
+        finally {
+            algorithms.release();
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/TransportProtocolState.java b/src/com/sshtools/j2ssh/transport/TransportProtocolState.java
new file mode 100644
index 0000000000000000000000000000000000000000..975faba7fb8b3092934a79c9275390180c7380a5
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/TransportProtocolState.java
@@ -0,0 +1,125 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport;
+
+import com.sshtools.j2ssh.util.State;
+
+import java.io.IOException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.24 $
+ */
+public class TransportProtocolState extends State {
+    /**  */
+    public final static int UNINITIALIZED = 1;
+
+    /**  */
+    public final static int NEGOTIATING_PROTOCOL = 2;
+
+    /**  */
+    public final static int PERFORMING_KEYEXCHANGE = 3;
+
+    /**  */
+    public final static int CONNECTED = 4;
+
+    /**  */
+    public final static int DISCONNECTED = 5;
+
+    /**  */
+    public IOException lastError;
+
+    /**  */
+    public String reason = "";
+
+    /**
+     * Creates a new TransportProtocolState object.
+     */
+    public TransportProtocolState() {
+        super(UNINITIALIZED);
+    }
+
+    /**
+     *
+     *
+     * @param lastError
+     */
+    protected void setLastError(IOException lastError) {
+        this.lastError = lastError;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean hasError() {
+        return lastError != null;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public IOException getLastError() {
+        return lastError;
+    }
+
+    /**
+     *
+     *
+     * @param reason
+     */
+    protected void setDisconnectReason(String reason) {
+        this.reason = reason;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getDisconnectReason() {
+        return reason;
+    }
+
+    /**
+     *
+     *
+     * @param state
+     *
+     * @return
+     */
+    public boolean isValidState(int state) {
+        return ((state == UNINITIALIZED) || (state == NEGOTIATING_PROTOCOL) ||
+        (state == PERFORMING_KEYEXCHANGE) || (state == CONNECTED) ||
+        (state == DISCONNECTED));
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/cipher/BlowfishCbc.java b/src/com/sshtools/j2ssh/transport/cipher/BlowfishCbc.java
new file mode 100644
index 0000000000000000000000000000000000000000..44c3375e2b372082412f9621eeced5294ff6f3a0
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/cipher/BlowfishCbc.java
@@ -0,0 +1,128 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.cipher;
+
+import com.sshtools.j2ssh.transport.AlgorithmOperationException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Cipher;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.21 $
+ */
+public class BlowfishCbc extends SshCipher {
+    private static Log log = LogFactory.getLog(BlowfishCbc.class);
+
+    /**  */
+    protected static String algorithmName = "blowfish-cbc";
+    Cipher cipher;
+
+    /**
+     * Creates a new BlowfishCbc object.
+     */
+    public BlowfishCbc() {
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getBlockSize() {
+        return cipher.getBlockSize();
+    }
+
+    /**
+     *
+     *
+     * @param mode
+     * @param iv
+     * @param keydata
+     *
+     * @throws AlgorithmOperationException
+     */
+    public void init(int mode, byte[] iv, byte[] keydata)
+        throws AlgorithmOperationException {
+        try {
+            cipher = Cipher.getInstance("Blowfish/CBC/NoPadding");
+
+            // Create a 16 byte key
+            byte[] actualKey = new byte[16];
+            System.arraycopy(keydata, 0, actualKey, 0, actualKey.length);
+
+            SecretKeySpec keyspec = new SecretKeySpec(actualKey, "Blowfish");
+
+            // Create the cipher according to its algorithm
+            cipher.init(((mode == ENCRYPT_MODE) ? Cipher.ENCRYPT_MODE
+                                                : Cipher.DECRYPT_MODE),
+                keyspec, new IvParameterSpec(iv, 0, cipher.getBlockSize()));
+        } catch (NoSuchPaddingException nspe) {
+            log.error("Blowfish initialization failed", nspe);
+            throw new AlgorithmOperationException("No Padding not supported");
+        } catch (NoSuchAlgorithmException nsae) {
+            log.error("Blowfish initialization failed", nsae);
+            throw new AlgorithmOperationException("Algorithm not supported");
+        } catch (InvalidKeyException ike) {
+            log.error("Blowfish initialization failed", ike);
+            throw new AlgorithmOperationException("Invalid encryption key");
+
+            /*} catch (InvalidKeySpecException ispe) {
+                 throw new AlgorithmOperationException("Invalid encryption key specification");*/
+        } catch (InvalidAlgorithmParameterException ape) {
+            log.error("Blowfish initialization failed", ape);
+            throw new AlgorithmOperationException("Invalid algorithm parameter");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param data
+     * @param offset
+     * @param len
+     *
+     * @return
+     *
+     * @throws AlgorithmOperationException
+     */
+    public byte[] transform(byte[] data, int offset, int len)
+        throws AlgorithmOperationException {
+        return cipher.update(data, offset, len);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/cipher/SshCipher.java b/src/com/sshtools/j2ssh/transport/cipher/SshCipher.java
new file mode 100644
index 0000000000000000000000000000000000000000..0fea0aa61fc5b139cae08f320290ac09d8bdf43a
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/cipher/SshCipher.java
@@ -0,0 +1,89 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.cipher;
+
+import com.sshtools.j2ssh.transport.AlgorithmOperationException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.19 $
+ */
+public abstract class SshCipher {
+    /**  */
+    public static final int ENCRYPT_MODE = 0;
+
+    /**  */
+    public static final int DECRYPT_MODE = 1;
+
+    /**
+     *
+     *
+     * @return
+     */
+    public abstract int getBlockSize();
+
+    /**
+     *
+     *
+     * @param mode
+     * @param iv
+     * @param keydata
+     *
+     * @throws AlgorithmOperationException
+     */
+    public abstract void init(int mode, byte[] iv, byte[] keydata)
+        throws AlgorithmOperationException;
+
+    /**
+     *
+     *
+     * @param data
+     *
+     * @return
+     *
+     * @throws AlgorithmOperationException
+     */
+    public byte[] transform(byte[] data) throws AlgorithmOperationException {
+        return transform(data, 0, data.length);
+    }
+
+    /**
+     *
+     *
+     * @param data
+     * @param offset
+     * @param len
+     *
+     * @return
+     *
+     * @throws AlgorithmOperationException
+     */
+    public abstract byte[] transform(byte[] data, int offset, int len)
+        throws AlgorithmOperationException;
+}
diff --git a/src/com/sshtools/j2ssh/transport/cipher/SshCipherFactory.java b/src/com/sshtools/j2ssh/transport/cipher/SshCipherFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..01db3392eb844af3cef7aa3b7f416d8cf72ce285
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/cipher/SshCipherFactory.java
@@ -0,0 +1,165 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.cipher;
+
+import com.sshtools.j2ssh.configuration.ConfigurationException;
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+import com.sshtools.j2ssh.io.IOUtil;
+import com.sshtools.j2ssh.transport.AlgorithmNotSupportedException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.InputStream;
+
+import java.net.URL;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Vector;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.32 $
+ */
+public class SshCipherFactory {
+    private static HashMap ciphers;
+    private static String defaultCipher;
+    private static Log log = LogFactory.getLog(SshCipherFactory.class);
+    private static ArrayList supported;
+
+    static {
+        ciphers = new HashMap();
+
+        log.info("Loading supported cipher algorithms");
+
+        ciphers.put("3des-cbc", TripleDesCbc.class);
+        ciphers.put("blowfish-cbc", BlowfishCbc.class);
+        defaultCipher = "blowfish-cbc";
+
+        try {
+            Enumeration en = ConfigurationLoader.getExtensionClassLoader()
+                                                  .getResources("j2ssh.cipher");
+            URL url;
+            Properties properties = new Properties();
+            InputStream in;
+
+            while ((en != null) && en.hasMoreElements()) {
+                url = (URL) en.nextElement();
+                in = url.openStream();
+                properties.load(in);
+                IOUtil.closeStream(in);
+
+                int num = 1;
+                String name = "";
+                Class cls;
+
+                while (properties.getProperty("cipher.name." +
+                            String.valueOf(num)) != null) {
+                    try {
+                        name = properties.getProperty("cipher.name." +
+                                String.valueOf(num));
+                        cls = ConfigurationLoader.getExtensionClassLoader()
+                                                 .loadClass(properties.getProperty(
+                                    "cipher.class." + String.valueOf(num)));
+                        cls.newInstance();
+                        ciphers.put(name, cls);
+                        log.info("Installed " + name + " cipher");
+                    } catch (Throwable ex) {
+                        log.info("Could not install cipher class for " + name,
+                            ex);
+                    }
+
+                    num++;
+                }
+            }
+        } catch (Throwable t) {
+        }
+
+        // Build a list of the supported ciphers
+        supported = new ArrayList(ciphers.keySet());
+    }
+
+    /**
+     * Creates a new SshCipherFactory object.
+     */
+    protected SshCipherFactory() {
+    }
+
+    /**
+     *
+     */
+    public static void initialize() {
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public static String getDefaultCipher() {
+        return defaultCipher;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public static List getSupportedCiphers() {
+        // Return the list
+        return supported;
+    }
+
+    /**
+     *
+     *
+     * @param algorithmName
+     *
+     * @return
+     *
+     * @throws AlgorithmNotSupportedException
+     */
+    public static SshCipher newInstance(String algorithmName)
+        throws AlgorithmNotSupportedException {
+        log.info("Creating new " + algorithmName + " cipher instance");
+
+        try {
+            return (SshCipher) ((Class) ciphers.get(algorithmName)).newInstance();
+        } catch (Throwable t) {
+            throw new AlgorithmNotSupportedException(algorithmName +
+                " is not supported!");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/cipher/TripleDesCbc.java b/src/com/sshtools/j2ssh/transport/cipher/TripleDesCbc.java
new file mode 100644
index 0000000000000000000000000000000000000000..778510bc035864b32db8e2639d88a45755a88fe4
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/cipher/TripleDesCbc.java
@@ -0,0 +1,124 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.cipher;
+
+import com.sshtools.j2ssh.transport.AlgorithmOperationException;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+
+import javax.crypto.Cipher;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.DESedeKeySpec;
+import javax.crypto.spec.IvParameterSpec;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.19 $
+ */
+public class TripleDesCbc extends SshCipher {
+    /**  */
+    protected static String algorithmName = "3des-cbc";
+    Cipher cipher;
+
+    /**
+     * Creates a new TripleDesCbc object.
+     */
+    public TripleDesCbc() {
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getBlockSize() {
+        return cipher.getBlockSize();
+    }
+
+    /**
+     *
+     *
+     * @param mode
+     * @param iv
+     * @param keydata
+     *
+     * @throws AlgorithmOperationException
+     */
+    public void init(int mode, byte[] iv, byte[] keydata)
+        throws AlgorithmOperationException {
+        try {
+            KeySpec keyspec;
+            Key key;
+
+            // Create the cipher according to its algorithm
+            cipher = Cipher.getInstance("DESede/CBC/NoPadding");
+
+            byte[] actualKey = new byte[24];
+            System.arraycopy(keydata, 0, actualKey, 0, 24);
+            keyspec = new DESedeKeySpec(actualKey);
+            key = SecretKeyFactory.getInstance("DESede").generateSecret(keyspec);
+            cipher.init(((mode == ENCRYPT_MODE) ? Cipher.ENCRYPT_MODE
+                                                : Cipher.DECRYPT_MODE), key,
+                new IvParameterSpec(iv, 0, cipher.getBlockSize()));
+        } catch (NoSuchPaddingException nspe) {
+            throw new AlgorithmOperationException("Padding not supported");
+        } catch (NoSuchAlgorithmException nsae) {
+            throw new AlgorithmOperationException("Algorithm not supported");
+        } catch (InvalidKeyException ike) {
+            throw new AlgorithmOperationException("Invalid encryption key");
+        } catch (InvalidKeySpecException ispe) {
+            throw new AlgorithmOperationException(
+                "Invalid encryption key specification");
+        } catch (InvalidAlgorithmParameterException ape) {
+            throw new AlgorithmOperationException("Invalid algorithm parameter");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param data
+     * @param offset
+     * @param len
+     *
+     * @return
+     *
+     * @throws AlgorithmOperationException
+     */
+    public byte[] transform(byte[] data, int offset, int len)
+        throws AlgorithmOperationException {
+        return cipher.update(data, offset, len);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/compression/SshCompression.java b/src/com/sshtools/j2ssh/transport/compression/SshCompression.java
new file mode 100644
index 0000000000000000000000000000000000000000..1ba18de9f6d2a5ada2681fa4f86d9e85df6e1686
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/compression/SshCompression.java
@@ -0,0 +1,58 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.compression;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.18 $
+ */
+public interface SshCompression {
+    static public final int INFLATER = 0;
+    static public final int DEFLATER = 1;
+
+    public void init(int type, int level);
+
+    /**
+     *
+     *
+     * @param data
+     *
+     * @return
+     */
+    public byte[] compress(byte[] data, int start, int len);
+
+    /**
+     *
+     *
+     * @param data
+     *
+     * @return
+     */
+    public byte[] uncompress(byte[] data, int start, int len);
+}
diff --git a/src/com/sshtools/j2ssh/transport/compression/SshCompressionFactory.java b/src/com/sshtools/j2ssh/transport/compression/SshCompressionFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..386fc3e42ccfafababb6388f4d6dc6485d042cb9
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/compression/SshCompressionFactory.java
@@ -0,0 +1,164 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.compression;
+
+import com.sshtools.j2ssh.configuration.ConfigurationException;
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+import com.sshtools.j2ssh.io.IOUtil;
+import com.sshtools.j2ssh.transport.AlgorithmNotSupportedException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.InputStream;
+
+import java.net.URL;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Vector;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.30 $
+ */
+public class SshCompressionFactory {
+    /**  */
+    public final static String COMP_NONE = "none";
+    private static String defaultAlgorithm;
+    private static Map comps;
+    private static Log log = LogFactory.getLog(SshCompressionFactory.class);
+
+    static {
+        comps = new HashMap();
+
+        log.info("Loading compression methods");
+
+        comps.put(COMP_NONE, "");
+
+        defaultAlgorithm = COMP_NONE;
+
+        try {
+            Enumeration en = ConfigurationLoader.getExtensionClassLoader()
+                                                  .getResources("j2ssh.compression");
+            URL url;
+            Properties properties = new Properties();
+            InputStream in;
+
+            while ((en != null) && en.hasMoreElements()) {
+                url = (URL) en.nextElement();
+                in = url.openStream();
+                properties.load(in);
+                IOUtil.closeStream(in);
+
+                int num = 1;
+                String name = "";
+                Class cls;
+
+                while (properties.getProperty("compression.name." +
+                            String.valueOf(num)) != null) {
+                    try {
+                        name = properties.getProperty("compression.name." +
+                                String.valueOf(num));
+                        cls = ConfigurationLoader.getExtensionClassLoader()
+                                                 .loadClass(properties.getProperty(
+                                    "compression.class." + String.valueOf(num)));
+                        cls.newInstance();
+                        comps.put(name, cls);
+                        log.info("Installed " + name + " compression");
+                    } catch (Throwable ex) {
+                        log.info("Could not install cipher class for " + name,
+                            ex);
+                    }
+
+                    num++;
+                }
+            }
+        } catch (Throwable t) {
+        }
+    }
+
+    /**
+     * Creates a new SshCompressionFactory object.
+     */
+    protected SshCompressionFactory() {
+    }
+
+    /**
+     *
+     */
+    public static void initialize() {
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public static String getDefaultCompression() {
+        return defaultAlgorithm;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public static List getSupportedCompression() {
+        return new ArrayList(comps.keySet());
+    }
+
+    /**
+     *
+     *
+     * @param algorithmName
+     *
+     * @return
+     *
+     * @throws AlgorithmNotSupportedException
+     */
+    public static SshCompression newInstance(String algorithmName)
+        throws AlgorithmNotSupportedException {
+        try {
+            if (algorithmName.equals(COMP_NONE)) {
+                return null;
+            } else {
+                return (SshCompression) ((Class) comps.get(algorithmName)).newInstance();
+            }
+        } catch (Exception e) {
+            throw new AlgorithmNotSupportedException(algorithmName +
+                " is not supported!");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/hmac/HmacMd5.java b/src/com/sshtools/j2ssh/transport/hmac/HmacMd5.java
new file mode 100644
index 0000000000000000000000000000000000000000..d9a4211b63b3d563ef13e00a1b0270da98338c59
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/hmac/HmacMd5.java
@@ -0,0 +1,125 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.hmac;
+
+import com.sshtools.j2ssh.transport.AlgorithmInitializationException;
+
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class HmacMd5 implements SshHmac {
+    private Mac mac;
+
+    /**
+     * Creates a new HmacMd5 object.
+     */
+    public HmacMd5() {
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getMacLength() {
+        return mac.getMacLength();
+    }
+
+    /**
+     *
+     *
+     * @param sequenceNo
+     * @param data
+     * @param offset
+     * @param len
+     *
+     * @return
+     */
+    public byte[] generate(long sequenceNo, byte[] data, int offset, int len) {
+        // Write the sequence no
+        byte[] sequenceBytes = new byte[4];
+        sequenceBytes[0] = (byte) (sequenceNo >> 24);
+        sequenceBytes[1] = (byte) (sequenceNo >> 16);
+        sequenceBytes[2] = (byte) (sequenceNo >> 8);
+        sequenceBytes[3] = (byte) (sequenceNo >> 0);
+        mac.update(sequenceBytes);
+        mac.update(data, offset, len);
+
+        return mac.doFinal();
+    }
+
+    /**
+     *
+     *
+     * @param keydata
+     *
+     * @throws AlgorithmInitializationException
+     */
+    public void init(byte[] keydata) throws AlgorithmInitializationException {
+        try {
+            mac = Mac.getInstance("HmacMD5");
+
+            // Create a key of 16 bytes
+            byte[] key = new byte[16];
+            System.arraycopy(keydata, 0, key, 0, key.length);
+
+            SecretKeySpec keyspec = new SecretKeySpec(key, "HmacMD5");
+            mac.init(keyspec);
+        } catch (NoSuchAlgorithmException nsae) {
+            throw new AlgorithmInitializationException(
+                "No provider exists for the HmacSha1 algorithm");
+        } catch (InvalidKeyException ike) {
+            throw new AlgorithmInitializationException("Invalid key");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param sequenceNo
+     * @param data
+     *
+     * @return
+     */
+    public boolean verify(long sequenceNo, byte[] data) {
+        int len = getMacLength();
+        byte[] generated = generate(sequenceNo, data, 0, data.length - len);
+        String compare1 = new String(generated);
+        String compare2 = new String(data, data.length - len, len);
+
+        return compare1.equals(compare2);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/hmac/HmacMd596.java b/src/com/sshtools/j2ssh/transport/hmac/HmacMd596.java
new file mode 100644
index 0000000000000000000000000000000000000000..7405721e831a60dcf84b787308dae520cb240fb2
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/hmac/HmacMd596.java
@@ -0,0 +1,68 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.hmac;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class HmacMd596 extends HmacMd5 {
+    /**
+     * Creates a new HmacMd596 object.
+     */
+    public HmacMd596() {
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getMacLength() {
+        return 12;
+    }
+
+    /**
+     *
+     *
+     * @param sequenceNo
+     * @param data
+     * @param offset
+     * @param len
+     *
+     * @return
+     */
+    public byte[] generate(long sequenceNo, byte[] data, int offset, int len) {
+        byte[] generated = super.generate(sequenceNo, data, offset, len);
+        byte[] result = new byte[getMacLength()];
+        System.arraycopy(generated, 0, result, 0, getMacLength());
+
+        return result;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/hmac/HmacSha.java b/src/com/sshtools/j2ssh/transport/hmac/HmacSha.java
new file mode 100644
index 0000000000000000000000000000000000000000..302e228c059c4201fa312e3983a6b4a6e0c2b94d
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/hmac/HmacSha.java
@@ -0,0 +1,150 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.hmac;
+
+import com.sshtools.j2ssh.transport.AlgorithmInitializationException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.20 $
+ */
+public class HmacSha implements SshHmac {
+    private static Log log = LogFactory.getLog(HmacSha.class);
+    private Mac mac;
+
+    /**
+     * Creates a new HmacSha object.
+     */
+    public HmacSha() {
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getMacLength() {
+        return mac.getMacLength();
+    }
+
+    /**
+     *
+     *
+     * @param sequenceNo
+     * @param data
+     * @param offset
+     * @param len
+     *
+     * @return
+     */
+    public byte[] generate(long sequenceNo, byte[] data, int offset, int len) {
+        // Write the sequence no
+        byte[] sequenceBytes = new byte[4];
+        sequenceBytes[0] = (byte) (sequenceNo >> 24);
+        sequenceBytes[1] = (byte) (sequenceNo >> 16);
+        sequenceBytes[2] = (byte) (sequenceNo >> 8);
+        sequenceBytes[3] = (byte) (sequenceNo >> 0);
+        mac.update(sequenceBytes);
+        mac.update(data, offset, len);
+
+        return mac.doFinal();
+    }
+
+    /**
+     *
+     *
+     * @param keydata
+     *
+     * @throws AlgorithmInitializationException
+     */
+    public void init(byte[] keydata) throws AlgorithmInitializationException {
+        try {
+            mac = Mac.getInstance("HmacSha1");
+
+            byte[] key = new byte[20];
+            System.arraycopy(keydata, 0, key, 0, 20);
+
+            SecretKeySpec keyspec = new SecretKeySpec(key, "HmacSha1");
+            mac.init(keyspec);
+        } catch (NoSuchAlgorithmException nsae) {
+            throw new AlgorithmInitializationException(
+                "No provider exists for the HmacSha1 algorithm");
+        } catch (InvalidKeyException ike) {
+            throw new AlgorithmInitializationException("Invalid key");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param sequenceNo
+     * @param data
+     *
+     * @return
+     */
+    public boolean verify(long sequenceNo, byte[] data) {
+        int len = getMacLength();
+
+        //log.debug("MAC Data length: " + String.valueOf(data.length));
+        byte[] generated = generate(sequenceNo, data, 0, data.length - len);
+        String compare1 = new String(generated);
+        String compare2 = new String(data, data.length - len, len);
+
+        //log.debug("Generated: " + compare1);
+        //log.debug("Actual   : " + compare2);
+        boolean result = compare1.equals(compare2);
+
+        /*if (!result) {
+            /**
+          * Output some debug stuff
+          */
+        /*  String genhex = "";
+            String acthex = "";
+            boolean verify = true;
+            for(int i=0;i<generated.length;i++) {
+              genhex += (genhex.length()==0?"":",") + Integer.toHexString(generated[i] & 0xFF);
+              acthex += (acthex.length()==0?"":",") + Integer.toHexString(data[data.length-len+i] & 0xFF);
+              verify = (generated[i] == data[data.length-len+i]);
+            }
+            log.debug("Byte Verify: " + String.valueOf(verify));
+            log.debug("Generated: " + genhex);
+            log.debug("Actual: " + acthex);
+          }*/
+        return result;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/hmac/HmacSha96.java b/src/com/sshtools/j2ssh/transport/hmac/HmacSha96.java
new file mode 100644
index 0000000000000000000000000000000000000000..ec113be836d6228c89c0698da557840666adbe30
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/hmac/HmacSha96.java
@@ -0,0 +1,68 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.hmac;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class HmacSha96 extends HmacSha {
+    /**
+     * Creates a new HmacSha96 object.
+     */
+    public HmacSha96() {
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getMacLength() {
+        return 12;
+    }
+
+    /**
+     *
+     *
+     * @param sequenceNo
+     * @param data
+     * @param offset
+     * @param len
+     *
+     * @return
+     */
+    public byte[] generate(long sequenceNo, byte[] data, int offset, int len) {
+        byte[] generated = super.generate(sequenceNo, data, offset, len);
+        byte[] result = new byte[getMacLength()];
+        System.arraycopy(generated, 0, result, 0, getMacLength());
+
+        return result;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/hmac/SshHmac.java b/src/com/sshtools/j2ssh/transport/hmac/SshHmac.java
new file mode 100644
index 0000000000000000000000000000000000000000..73b42be32f589053fbd7c9049273a41ebbce4b2b
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/hmac/SshHmac.java
@@ -0,0 +1,75 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.hmac;
+
+import com.sshtools.j2ssh.transport.AlgorithmInitializationException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public interface SshHmac {
+    /**
+     *
+     *
+     * @return
+     */
+    public int getMacLength();
+
+    /**
+     *
+     *
+     * @param sequenceNo
+     * @param data
+     * @param offset
+     * @param len
+     *
+     * @return
+     */
+    public byte[] generate(long sequenceNo, byte[] data, int offset, int len);
+
+    /**
+     *
+     *
+     * @param keydata
+     *
+     * @throws AlgorithmInitializationException
+     */
+    public void init(byte[] keydata) throws AlgorithmInitializationException;
+
+    /**
+     *
+     *
+     * @param sequenceNo
+     * @param data
+     *
+     * @return
+     */
+    public boolean verify(long sequenceNo, byte[] data);
+}
diff --git a/src/com/sshtools/j2ssh/transport/hmac/SshHmacFactory.java b/src/com/sshtools/j2ssh/transport/hmac/SshHmacFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..304fcb73cf9add025997a9a2372631ecf3d61a1b
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/hmac/SshHmacFactory.java
@@ -0,0 +1,114 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.hmac;
+
+import com.sshtools.j2ssh.configuration.ConfigurationException;
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+import com.sshtools.j2ssh.transport.AlgorithmNotSupportedException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.30 $
+ */
+public class SshHmacFactory {
+    private static String defaultAlgorithm;
+    private static Map macs;
+    private static Log log = LogFactory.getLog(SshHmacFactory.class);
+
+    static {
+        macs = new HashMap();
+
+        log.info("Loading message authentication methods");
+
+        macs.put("hmac-sha1", HmacSha.class);
+        macs.put("hmac-sha1-96", HmacSha96.class);
+        macs.put("hmac-md5", HmacMd5.class);
+        macs.put("hmac-md5-96", HmacMd596.class);
+
+        defaultAlgorithm = "hmac-sha1";
+    }
+
+    /**
+     * Creates a new SshHmacFactory object.
+     */
+    protected SshHmacFactory() {
+    }
+
+    /**
+     *
+     */
+    public static void initialize() {
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public final static String getDefaultHmac() {
+        return defaultAlgorithm;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public static List getSupportedMacs() {
+        return new ArrayList(macs.keySet());
+    }
+
+    /**
+     *
+     *
+     * @param methodName
+     *
+     * @return
+     *
+     * @throws AlgorithmNotSupportedException
+     */
+    public static SshHmac newInstance(String methodName)
+        throws AlgorithmNotSupportedException {
+        try {
+            return (SshHmac) ((Class) macs.get(methodName)).newInstance();
+        } catch (Exception e) {
+            throw new AlgorithmNotSupportedException(methodName +
+                " is not supported!");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/kex/DhGroup1Sha1.java b/src/com/sshtools/j2ssh/transport/kex/DhGroup1Sha1.java
new file mode 100644
index 0000000000000000000000000000000000000000..913f61dc76b3c200bc85d1db0df67a63a0378d78
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/kex/DhGroup1Sha1.java
@@ -0,0 +1,313 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.kex;
+
+import com.sshtools.j2ssh.SshException;
+import com.sshtools.j2ssh.transport.AlgorithmNotSupportedException;
+import com.sshtools.j2ssh.transport.AlgorithmOperationException;
+import com.sshtools.j2ssh.transport.publickey.SshPrivateKey;
+import com.sshtools.j2ssh.util.Hash;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+
+import java.math.BigInteger;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.KeyAgreement;
+import javax.crypto.interfaces.DHPrivateKey;
+import javax.crypto.interfaces.DHPublicKey;
+import javax.crypto.spec.DHParameterSpec;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.25 $
+ */
+public class DhGroup1Sha1 extends SshKeyExchange {
+    private static Log log = LogFactory.getLog(DhGroup1Sha1.class);
+    private static BigInteger g = new BigInteger("2");
+    private static BigInteger p = new BigInteger(new byte[] {
+                (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
+                (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xC9,
+                (byte) 0x0F, (byte) 0xDA, (byte) 0xA2, (byte) 0x21, (byte) 0x68,
+                (byte) 0xC2, (byte) 0x34, (byte) 0xC4, (byte) 0xC6, (byte) 0x62,
+                (byte) 0x8B, (byte) 0x80, (byte) 0xDC, (byte) 0x1C, (byte) 0xD1,
+                (byte) 0x29, (byte) 0x02, (byte) 0x4E, (byte) 0x08, (byte) 0x8A,
+                (byte) 0x67, (byte) 0xCC, (byte) 0x74, (byte) 0x02, (byte) 0x0B,
+                (byte) 0xBE, (byte) 0xA6, (byte) 0x3B, (byte) 0x13, (byte) 0x9B,
+                (byte) 0x22, (byte) 0x51, (byte) 0x4A, (byte) 0x08, (byte) 0x79,
+                (byte) 0x8E, (byte) 0x34, (byte) 0x04, (byte) 0xDD, (byte) 0xEF,
+                (byte) 0x95, (byte) 0x19, (byte) 0xB3, (byte) 0xCD, (byte) 0x3A,
+                (byte) 0x43, (byte) 0x1B, (byte) 0x30, (byte) 0x2B, (byte) 0x0A,
+                (byte) 0x6D, (byte) 0xF2, (byte) 0x5F, (byte) 0x14, (byte) 0x37,
+                (byte) 0x4F, (byte) 0xE1, (byte) 0x35, (byte) 0x6D, (byte) 0x6D,
+                (byte) 0x51, (byte) 0xC2, (byte) 0x45, (byte) 0xE4, (byte) 0x85,
+                (byte) 0xB5, (byte) 0x76, (byte) 0x62, (byte) 0x5E, (byte) 0x7E,
+                (byte) 0xC6, (byte) 0xF4, (byte) 0x4C, (byte) 0x42, (byte) 0xE9,
+                (byte) 0xA6, (byte) 0x37, (byte) 0xED, (byte) 0x6B, (byte) 0x0B,
+                (byte) 0xFF, (byte) 0x5C, (byte) 0xB6, (byte) 0xF4, (byte) 0x06,
+                (byte) 0xB7, (byte) 0xED, (byte) 0xEE, (byte) 0x38, (byte) 0x6B,
+                (byte) 0xFB, (byte) 0x5A, (byte) 0x89, (byte) 0x9F, (byte) 0xA5,
+                (byte) 0xAE, (byte) 0x9F, (byte) 0x24, (byte) 0x11, (byte) 0x7C,
+                (byte) 0x4B, (byte) 0x1F, (byte) 0xE6, (byte) 0x49, (byte) 0x28,
+                (byte) 0x66, (byte) 0x51, (byte) 0xEC, (byte) 0xE6, (byte) 0x53,
+                (byte) 0x81, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
+                (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF
+            });
+    private BigInteger e = null;
+    private BigInteger f = null;
+
+    //private static BigInteger q = p.subtract(BigInteger.ONE).divide(g);
+    private BigInteger x = null;
+    private BigInteger y = null;
+    private String clientId;
+    private String serverId;
+    private byte[] clientKexInit;
+    private byte[] serverKexInit;
+    private KeyPairGenerator dhKeyPairGen;
+    private KeyAgreement dhKeyAgreement;
+
+    /**
+     * Creates a new DhGroup1Sha1 object.
+     */
+    public DhGroup1Sha1() {
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     * @throws AlgorithmNotSupportedException
+     */
+    protected void onInit() throws IOException {
+        messageStore.registerMessage(SshMsgKexDhInit.SSH_MSG_KEXDH_INIT,
+            SshMsgKexDhInit.class);
+        messageStore.registerMessage(SshMsgKexDhReply.SSH_MSG_KEXDH_REPLY,
+            SshMsgKexDhReply.class);
+
+        try {
+            dhKeyPairGen = KeyPairGenerator.getInstance("DH");
+            dhKeyAgreement = KeyAgreement.getInstance("DH");
+        } catch (NoSuchAlgorithmException ex) {
+            throw new AlgorithmNotSupportedException(ex.getMessage());
+        }
+    }
+
+    /**
+     *
+     *
+     * @param clientId
+     * @param serverId
+     * @param clientKexInit
+     * @param serverKexInit
+     *
+     * @throws IOException
+     * @throws AlgorithmOperationException
+     * @throws KeyExchangeException
+     */
+    public void performClientExchange(String clientId, String serverId,
+        byte[] clientKexInit, byte[] serverKexInit) throws IOException {
+        log.info("Starting client side key exchange.");
+        this.clientId = clientId;
+        this.serverId = serverId;
+        this.clientKexInit = clientKexInit;
+        this.serverKexInit = serverKexInit;
+
+        //int minBits = g.bitLength();
+        //int maxBits = q.bitLength();
+        //Random rnd = ConfigurationLoader.getRND();
+        // Generate a random bit count for the random x value
+
+        /*int genBits = (int) ( ( (maxBits - minBits + 1) * rnd.nextFloat())
+                     + minBits);
+              x = new BigInteger(genBits, rnd);
+              // Calculate e
+              e = g.modPow(x, p);*/
+        try {
+            DHParameterSpec dhSkipParamSpec = new DHParameterSpec(p, g);
+            dhKeyPairGen.initialize(dhSkipParamSpec);
+
+            KeyPair dhKeyPair = dhKeyPairGen.generateKeyPair();
+            dhKeyAgreement.init(dhKeyPair.getPrivate());
+            x = ((DHPrivateKey) dhKeyPair.getPrivate()).getX();
+            e = ((DHPublicKey) dhKeyPair.getPublic()).getY();
+        } catch (InvalidKeyException ex) {
+            throw new AlgorithmOperationException("Failed to generate DH value");
+        } catch (InvalidAlgorithmParameterException ex) {
+            throw new AlgorithmOperationException("Failed to generate DH value");
+        }
+
+        // Prepare the message
+        SshMsgKexDhInit msg = new SshMsgKexDhInit(e);
+
+        // Send it
+        try {
+          transport.sendMessage(msg, this);
+        } 
+        catch (SshException tpe) {
+          throw new KeyExchangeException(
+          "Failed to send key exchange initialization message");
+        }
+
+        int[] messageId = new int[1];
+        messageId[0] = SshMsgKexDhReply.SSH_MSG_KEXDH_REPLY;
+
+        SshMsgKexDhReply reply = (SshMsgKexDhReply) transport.readMessage(messageId);
+        hostKey = reply.getHostKey();
+        signature = reply.getSignature();
+        f = reply.getF();
+
+        // Calculate diffe hellman k value
+        secret = f.modPow(x, p);
+
+        // Calculate the exchange hash
+        calculateExchangeHash();
+    }
+
+    /**
+     *
+     *
+     * @param clientId
+     * @param serverId
+     * @param clientKexInit
+     * @param serverKexInit
+     * @param prvKey
+     *
+     * @throws IOException
+     * @throws KeyExchangeException
+     */
+    public void performServerExchange(String clientId, String serverId,
+        byte[] clientKexInit, byte[] serverKexInit, SshPrivateKey prvKey)
+        throws IOException {
+        try {
+            this.clientId = clientId;
+            this.serverId = serverId;
+            this.clientKexInit = clientKexInit;
+            this.serverKexInit = serverKexInit;
+
+            /*int minBits = g.bitLength();
+                    int maxBits = q.bitLength();
+                    Random rnd = ConfigurationLoader.getRND();
+                    // Generate a random bit count for the random x value
+                 int genBits = (int) ( ( (maxBits - minBits + 1) * rnd.nextFloat())
+                     + minBits);
+                    y = new BigInteger(genBits, rnd);*/
+            try {
+                DHParameterSpec dhSkipParamSpec = new DHParameterSpec(p, g);
+                dhKeyPairGen.initialize(dhSkipParamSpec);
+
+                KeyPair dhKeyPair = dhKeyPairGen.generateKeyPair();
+                dhKeyAgreement.init(dhKeyPair.getPrivate());
+                y = ((DHPrivateKey) dhKeyPair.getPrivate()).getX();
+                f = ((DHPublicKey) dhKeyPair.getPublic()).getY();
+            } catch (InvalidKeyException ex) {
+                throw new AlgorithmOperationException(
+                    "Failed to generate DH y value");
+            } catch (InvalidAlgorithmParameterException ex) {
+                throw new AlgorithmOperationException(
+                    "Failed to generate DH y value");
+            }
+
+            // Calculate f
+            //f = g.modPow(y, p);
+            // Wait for the e value and calculate the other parameters
+            int[] messageId = new int[1];
+            messageId[0] = SshMsgKexDhInit.SSH_MSG_KEXDH_INIT;
+
+            SshMsgKexDhInit msg = (SshMsgKexDhInit) transport.readMessage(messageId);
+            e = msg.getE();
+
+            // Calculate k
+            secret = e.modPow(y, p);
+            hostKey = prvKey.getPublicKey().getEncoded();
+            calculateExchangeHash();
+            signature = prvKey.generateSignature(exchangeHash);
+
+            SshMsgKexDhReply reply = new SshMsgKexDhReply(hostKey, f, signature);
+            transport.sendMessage(reply, this);
+        } catch (SshException e) {
+            throw new KeyExchangeException(e.getMessage());
+        }
+    }
+
+    /**
+     *
+     *
+     * @throws KeyExchangeException
+     */
+    protected void calculateExchangeHash() throws KeyExchangeException {
+        Hash hash;
+
+        try {
+            // Start a SHA hash
+            hash = new Hash("SHA");
+        } catch (NoSuchAlgorithmException nsae) {
+            throw new KeyExchangeException("SHA algorithm not supported");
+        }
+
+        int i;
+
+        // The local software version comments
+        hash.putString(clientId);
+
+        // The remote software version comments
+        hash.putString(serverId);
+
+        // The local kex init payload
+        hash.putInt(clientKexInit.length);
+        hash.putBytes(clientKexInit);
+
+        // The remote kex init payload
+        hash.putInt(serverKexInit.length);
+        hash.putBytes(serverKexInit);
+
+        // The host key
+        hash.putInt(hostKey.length);
+        hash.putBytes(hostKey);
+
+        // The diffie hellman e value
+        hash.putBigInteger(e);
+
+        // The diffie hellman f value
+        hash.putBigInteger(f);
+
+        // The diffie hellman k value
+        hash.putBigInteger(secret);
+
+        // Do the final output
+        exchangeHash = hash.doFinal();
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/kex/KeyExchangeException.java b/src/com/sshtools/j2ssh/transport/kex/KeyExchangeException.java
new file mode 100644
index 0000000000000000000000000000000000000000..fd127a20f5680b18932f8f425a46a2b9f88fbd17
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/kex/KeyExchangeException.java
@@ -0,0 +1,46 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.kex;
+
+import com.sshtools.j2ssh.transport.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public class KeyExchangeException extends TransportProtocolException {
+    /**
+     * Creates a new KeyExchangeException object.
+     *
+     * @param msg
+     */
+    public KeyExchangeException(String msg) {
+        super(msg);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/kex/KeyExchangeState.java b/src/com/sshtools/j2ssh/transport/kex/KeyExchangeState.java
new file mode 100644
index 0000000000000000000000000000000000000000..dec4e8dce068f81009bdbb7420ea184aee4cea14
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/kex/KeyExchangeState.java
@@ -0,0 +1,153 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.kex;
+
+import java.math.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public class KeyExchangeState {
+    /**  */
+    public final static int IN_PROGRESS = 0;
+
+    /**  */
+    public final static int COMPLETE = 1;
+
+    /**  */
+    public final static int FAILED = 2;
+    private BigInteger secret;
+    private String reason;
+    private byte[] exchangeHash;
+    private byte[] hostKey;
+    private byte[] signature;
+    private int state = IN_PROGRESS;
+
+    /**
+     * Creates a new KeyExchangeState object.
+     */
+    public KeyExchangeState() {
+    }
+
+    /**
+     *
+     *
+     * @param exchangeHash
+     * @param hostKey
+     * @param signature
+     * @param secret
+     */
+    public final synchronized void setComplete(byte[] exchangeHash,
+        byte[] hostKey, byte[] signature, BigInteger secret) {
+        this.exchangeHash = exchangeHash;
+        this.hostKey = hostKey;
+        this.signature = signature;
+        this.secret = secret;
+        state = COMPLETE;
+        notifyAll();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getExchangeHash() {
+        return exchangeHash;
+    }
+
+    /**
+     *
+     *
+     * @param reason
+     */
+    public final synchronized void setFailed(String reason) {
+        this.reason = reason;
+        state = FAILED;
+        notifyAll();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getHostKey() {
+        return hostKey;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public BigInteger getSecret() {
+        return secret;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getSignature() {
+        return signature;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public synchronized int getState() {
+        return state;
+    }
+
+    /**
+     *
+     */
+    public final synchronized void waitForCompletion() {
+        while (state == IN_PROGRESS) {
+            try {
+                wait();
+            } catch (InterruptedException e) {
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public synchronized String getFailureReason() {
+        return reason;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/kex/SshKeyExchange.java b/src/com/sshtools/j2ssh/transport/kex/SshKeyExchange.java
new file mode 100644
index 0000000000000000000000000000000000000000..101a893eef99d79b844d7bd1aaf319285812713e
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/kex/SshKeyExchange.java
@@ -0,0 +1,163 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.kex;
+
+import com.sshtools.j2ssh.transport.SshMessageStore;
+import com.sshtools.j2ssh.transport.TransportProtocol;
+import com.sshtools.j2ssh.transport.publickey.SshPrivateKey;
+
+import java.io.IOException;
+
+import java.math.BigInteger;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.22 $
+ */
+public abstract class SshKeyExchange { //implements Runnable {
+
+    /**  */
+    protected BigInteger secret;
+
+    /**  */
+    protected SshMessageStore messageStore = new SshMessageStore();
+
+    /**  */
+    protected byte[] exchangeHash;
+
+    /**  */
+    protected byte[] hostKey;
+
+    /**  */
+    protected byte[] signature;
+
+    /**  */
+    protected TransportProtocol transport;
+
+    /**
+     * Creates a new SshKeyExchange object.
+     */
+    public SshKeyExchange() {
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getExchangeHash() {
+        return exchangeHash;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getHostKey() {
+        return hostKey;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public BigInteger getSecret() {
+        return secret;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getSignature() {
+        return signature;
+    }
+
+    /**
+     *
+     *
+     * @param transport
+     *
+     * @throws IOException
+     */
+    public void init(TransportProtocol transport) throws IOException {
+        this.transport = transport;
+        onInit();
+        transport.addMessageStore(messageStore);
+    }
+
+    /**
+     *
+     *
+     * @throws IOException
+     */
+    protected abstract void onInit() throws IOException;
+
+    /**
+     *
+     *
+     * @param clientId
+     * @param serverId
+     * @param clientKexInit
+     * @param serverKexInit
+     *
+     * @throws IOException
+     */
+    public abstract void performClientExchange(String clientId,
+        String serverId, byte[] clientKexInit, byte[] serverKexInit)
+        throws IOException;
+
+    /**
+     *
+     *
+     * @param clientId
+     * @param serverId
+     * @param clientKexInit
+     * @param serverKexInit
+     * @param prvkey
+     *
+     * @throws IOException
+     */
+    public abstract void performServerExchange(String clientId,
+        String serverId, byte[] clientKexInit, byte[] serverKexInit,
+        SshPrivateKey prvkey) throws IOException;
+
+    /**
+     *
+     */
+    public void reset() {
+        exchangeHash = null;
+        hostKey = null;
+        signature = null;
+        secret = null;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/kex/SshKeyExchangeFactory.java b/src/com/sshtools/j2ssh/transport/kex/SshKeyExchangeFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..df35c12d8d805a4ba8f172b6e4e80644ca7d2ea5
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/kex/SshKeyExchangeFactory.java
@@ -0,0 +1,160 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.kex;
+
+import com.sshtools.j2ssh.configuration.ConfigurationException;
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+import com.sshtools.j2ssh.configuration.ExtensionAlgorithm;
+import com.sshtools.j2ssh.configuration.SshAPIConfiguration;
+import com.sshtools.j2ssh.transport.AlgorithmNotSupportedException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.28 $
+ */
+public class SshKeyExchangeFactory {
+    private static Map kexs;
+    private static String defaultAlgorithm;
+    private static Log log = LogFactory.getLog(SshKeyExchangeFactory.class);
+
+    static {
+        kexs = new HashMap();
+        log.info("Loading key exchange methods");
+        kexs.put("diffie-hellman-group1-sha1", DhGroup1Sha1.class);
+
+        try {
+            // Load external compression from configuration file
+            if (ConfigurationLoader.isConfigurationAvailable(
+                        SshAPIConfiguration.class)) {
+                SshAPIConfiguration config = (SshAPIConfiguration) ConfigurationLoader.getConfiguration(SshAPIConfiguration.class);
+
+                if (config != null) {
+                    List list = config.getKeyExchangeExtensions();
+
+                    if (list != null) {
+                        Iterator it = list.iterator();
+
+                        while (it.hasNext()) {
+                            ExtensionAlgorithm algorithm = (ExtensionAlgorithm) it.next();
+                            String name = algorithm.getAlgorithmName();
+
+                            if (kexs.containsKey(name)) {
+                                log.debug("Standard key exchange " + name +
+                                    " is being overidden by " +
+                                    algorithm.getImplementationClass());
+                            } else {
+                                log.debug(algorithm.getAlgorithmName() +
+                                    " key exchange is implemented by " +
+                                    algorithm.getImplementationClass());
+                            }
+
+                            try {
+                                kexs.put(algorithm.getAlgorithmName(),
+                                    ConfigurationLoader.getExtensionClass(
+                                        algorithm.getImplementationClass()));
+                            } catch (ClassNotFoundException cnfe) {
+                                log.error("Could not locate " +
+                                    algorithm.getImplementationClass());
+                            }
+                        }
+                    }
+
+                    defaultAlgorithm = config.getDefaultKeyExchange();
+                }
+            }
+        } catch (ConfigurationException ex) {
+        }
+
+        if ((defaultAlgorithm == null) || !kexs.containsKey(defaultAlgorithm)) {
+            log.debug(
+                "The default key exchange is not set! using first in list");
+
+            Iterator it = kexs.keySet().iterator();
+            defaultAlgorithm = (String) it.next();
+        }
+    }
+
+    /**
+     * Creates a new SshKeyExchangeFactory object.
+     */
+    protected SshKeyExchangeFactory() {
+    }
+
+    /**
+     *
+     */
+    public static void initialize() {
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public static String getDefaultKeyExchange() {
+        return defaultAlgorithm;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public static List getSupportedKeyExchanges() {
+        return new ArrayList(kexs.keySet());
+    }
+
+    /**
+     *
+     *
+     * @param methodName
+     *
+     * @return
+     *
+     * @throws AlgorithmNotSupportedException
+     */
+    public static SshKeyExchange newInstance(String methodName)
+        throws AlgorithmNotSupportedException {
+        try {
+            return (SshKeyExchange) ((Class) kexs.get(methodName)).newInstance();
+        } catch (Exception e) {
+            throw new AlgorithmNotSupportedException(methodName +
+                " is not supported!");
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/kex/SshMsgKexDhInit.java b/src/com/sshtools/j2ssh/transport/kex/SshMsgKexDhInit.java
new file mode 100644
index 0000000000000000000000000000000000000000..e68832ea138811708c9fe7cfb7473d85763b6a13
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/kex/SshMsgKexDhInit.java
@@ -0,0 +1,119 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.kex;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.SshMessage;
+
+import java.io.IOException;
+
+import java.math.BigInteger;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.17 $
+ */
+public class SshMsgKexDhInit extends SshMessage {
+    /**  */
+    protected final static int SSH_MSG_KEXDH_INIT = 30;
+
+    // Stores the e value
+    private BigInteger e;
+
+    /**
+     * Creates a new SshMsgKexDhInit object.
+     *
+     * @param e
+     */
+    public SshMsgKexDhInit(BigInteger e) {
+        super(SSH_MSG_KEXDH_INIT);
+        this.e = e;
+    }
+
+    /**
+     * Creates a new SshMsgKexDhInit object.
+     */
+    public SshMsgKexDhInit() {
+        super(SSH_MSG_KEXDH_INIT);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public BigInteger getE() {
+        return e;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_KEXDH_INIT";
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+        try {
+            baw.writeBigInteger(e);
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Error writing message data: " +
+                ioe.getMessage());
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+        try {
+            e = bar.readBigInteger();
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Error reading message data: " +
+                ioe.getMessage());
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/kex/SshMsgKexDhReply.java b/src/com/sshtools/j2ssh/transport/kex/SshMsgKexDhReply.java
new file mode 100644
index 0000000000000000000000000000000000000000..0b52ac369117aa80105211a75d3c692b30fe3a3d
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/kex/SshMsgKexDhReply.java
@@ -0,0 +1,151 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.kex;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.InvalidMessageException;
+import com.sshtools.j2ssh.transport.SshMessage;
+
+import java.io.IOException;
+
+import java.math.BigInteger;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.17 $
+ */
+public class SshMsgKexDhReply extends SshMessage {
+    /**  */
+    protected final static int SSH_MSG_KEXDH_REPLY = 31;
+
+    // The diffie hellman f value
+    private BigInteger f;
+
+    // The host key data
+    private byte[] hostKey;
+
+    // The signature
+    private byte[] signature;
+
+    /**
+     * Creates a new SshMsgKexDhReply object.
+     *
+     * @param hostKey
+     * @param f
+     * @param signature
+     */
+    public SshMsgKexDhReply(byte[] hostKey, BigInteger f, byte[] signature) {
+        super(SSH_MSG_KEXDH_REPLY);
+        this.hostKey = hostKey;
+        this.f = f;
+        this.signature = signature;
+    }
+
+    /**
+     * Creates a new SshMsgKexDhReply object.
+     */
+    public SshMsgKexDhReply() {
+        super(SSH_MSG_KEXDH_REPLY);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public BigInteger getF() {
+        return f;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getHostKey() {
+        return hostKey;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getMessageName() {
+        return "SSH_MSG_KEXDH_REPLY";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getSignature() {
+        return signature;
+    }
+
+    /**
+     *
+     *
+     * @param baw
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructByteArray(ByteArrayWriter baw)
+        throws InvalidMessageException {
+        try {
+            baw.writeBinaryString(hostKey);
+            baw.writeBigInteger(f);
+            baw.writeBinaryString(signature);
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Error writing message data: " +
+                ioe.getMessage());
+        }
+    }
+
+    /**
+     *
+     *
+     * @param bar
+     *
+     * @throws InvalidMessageException
+     */
+    protected void constructMessage(ByteArrayReader bar)
+        throws InvalidMessageException {
+        try {
+            hostKey = bar.readBinaryString();
+            f = bar.readBigInteger();
+            signature = bar.readBinaryString();
+        } catch (IOException ioe) {
+            throw new InvalidMessageException("Error reading message data: " +
+                ioe.getMessage());
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/publickey/Base64EncodedFileFormat.java b/src/com/sshtools/j2ssh/transport/publickey/Base64EncodedFileFormat.java
new file mode 100644
index 0000000000000000000000000000000000000000..02995cbca04ce3edcd807f86fe3ebbd3df87c789
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/publickey/Base64EncodedFileFormat.java
@@ -0,0 +1,253 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.publickey;
+
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.util.Base64;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.17 $
+ */
+public abstract class Base64EncodedFileFormat implements SshKeyFormatConversion {
+    /**  */
+    protected String begin;
+
+    /**  */
+    protected String end;
+    private Map headers = new HashMap();
+    private int MAX_LINE_LENGTH = 70;
+
+    /**
+     * Creates a new Base64EncodedFileFormat object.
+     *
+     * @param begin
+     * @param end
+     */
+    protected Base64EncodedFileFormat(String begin, String end) {
+        this.begin = begin;
+        this.end = end;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getFormatType() {
+        return "Base64Encoded";
+    }
+
+    /**
+     *
+     *
+     * @param formattedKey
+     *
+     * @return
+     */
+    public boolean isFormatted(byte[] formattedKey) {
+        String test = new String(formattedKey);
+
+        if ((test.indexOf(begin) >= 0) && (test.indexOf(end) > 0)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     *
+     *
+     * @param headerTag
+     * @param headerValue
+     */
+    public void setHeaderValue(String headerTag, String headerValue) {
+        headers.put(headerTag, headerValue);
+    }
+
+    /**
+     *
+     *
+     * @param headerTag
+     *
+     * @return
+     */
+    public String getHeaderValue(String headerTag) {
+        return (String) headers.get(headerTag);
+    }
+
+    /**
+     *
+     *
+     * @param formattedKey
+     *
+     * @return
+     *
+     * @throws InvalidSshKeyException
+     */
+    public byte[] getKeyBlob(byte[] formattedKey) throws InvalidSshKeyException {
+        BufferedReader reader = new BufferedReader(new InputStreamReader(
+                    new ByteArrayInputStream(formattedKey)));
+        String line;
+        String headerTag;
+        String headerValue;
+        String blob = "";
+        int index;
+
+        try {
+            // Read in the lines looking for the start
+            while (true) {
+                line = reader.readLine();
+
+                if (line == null) {
+                    throw new InvalidSshKeyException("Incorrect file format!");
+                }
+
+                if (line.endsWith(begin)) {
+                    break;
+                }
+            }
+
+            // Read the headers
+            while (true) {
+                line = reader.readLine();
+
+                if (line == null) {
+                    throw new InvalidSshKeyException("Incorrect file format!");
+                }
+
+                index = line.indexOf(": ");
+
+                if (index > 0) {
+                    while (line.endsWith("\\")) {
+                        line = line.substring(0, line.length() - 1);
+
+                        String tmp = reader.readLine();
+
+                        if (tmp == null) {
+                            throw new InvalidSshKeyException(
+                                "Incorrect file format!");
+                        }
+
+                        line += tmp;
+                    }
+
+                    // Record the header
+                    headerTag = line.substring(0, index);
+                    headerValue = line.substring(index + 2);
+                    headers.put(headerTag, headerValue);
+                } else {
+                    break;
+                }
+            }
+
+            // This is now the public key blob Base64 encoded
+            ByteArrayWriter baw = new ByteArrayWriter();
+
+            while (true) {
+                blob += line;
+                line = reader.readLine();
+
+                if (line == null) {
+                    throw new InvalidSshKeyException("Invalid file format!");
+                }
+
+                if (line.endsWith(end)) {
+                    break;
+                }
+            }
+
+            // Convert the blob to some useful data
+            return Base64.decode(blob);
+        } catch (IOException ioe) {
+            throw new InvalidSshKeyException();
+        }
+    }
+
+    /**
+     *
+     *
+     * @param keyblob
+     *
+     * @return
+     */
+    public byte[] formatKey(byte[] keyblob) {
+        try {
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            String headerTag;
+            String headerValue;
+            String line;
+            out.write(begin.getBytes());
+            out.write('\n');
+
+            int pos;
+            Set tags = headers.keySet();
+            Iterator it = tags.iterator();
+
+            while (it.hasNext()) {
+                headerTag = (String) it.next();
+                headerValue = (String) headers.get(headerTag);
+
+                String header = headerTag + ": " + headerValue;
+                pos = 0;
+
+                while (pos < header.length()) {
+                    line = header.substring(pos,
+                            (((pos + MAX_LINE_LENGTH) < header.length())
+                            ? (pos + MAX_LINE_LENGTH) : header.length())) +
+                        (((pos + MAX_LINE_LENGTH) < header.length()) ? "\\" : "");
+                    out.write(line.getBytes());
+                    out.write('\n');
+                    pos += MAX_LINE_LENGTH;
+                }
+            }
+
+            String encoded = Base64.encodeBytes(keyblob, false);
+            out.write(encoded.getBytes());
+            out.write('\n');
+            out.write(end.getBytes());
+            out.write('\n');
+
+            return out.toByteArray();
+        } catch (IOException ioe) {
+            return null;
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/publickey/InvalidSshKeyException.java b/src/com/sshtools/j2ssh/transport/publickey/InvalidSshKeyException.java
new file mode 100644
index 0000000000000000000000000000000000000000..72f5f2cdab129890e32b4b48dbfb6e9379f2b5de
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/publickey/InvalidSshKeyException.java
@@ -0,0 +1,53 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.publickey;
+
+import com.sshtools.j2ssh.transport.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class InvalidSshKeyException extends TransportProtocolException {
+    /**
+     * Creates a new InvalidSshKeyException object.
+     */
+    public InvalidSshKeyException() {
+        super("The SSH key supplied is invalid");
+    }
+
+    /**
+     * Creates a new InvalidSshKeyException object.
+     *
+     * @param msg
+     */
+    public InvalidSshKeyException(String msg) {
+        super(msg);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/publickey/InvalidSshKeySignatureException.java b/src/com/sshtools/j2ssh/transport/publickey/InvalidSshKeySignatureException.java
new file mode 100644
index 0000000000000000000000000000000000000000..67fbecf7c5f9bf5148a77b30ebf9cb6c24404d04
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/publickey/InvalidSshKeySignatureException.java
@@ -0,0 +1,53 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.publickey;
+
+import com.sshtools.j2ssh.transport.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class InvalidSshKeySignatureException extends TransportProtocolException {
+    /**
+     * Creates a new InvalidSshKeySignatureException object.
+     */
+    public InvalidSshKeySignatureException() {
+        super("The signature is invalid");
+    }
+
+    /**
+     * Creates a new InvalidSshKeySignatureException object.
+     *
+     * @param t
+     */
+    public InvalidSshKeySignatureException(Throwable t) {
+        super(t.getMessage());
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/publickey/OpenSSHPublicKeyFormat.java b/src/com/sshtools/j2ssh/transport/publickey/OpenSSHPublicKeyFormat.java
new file mode 100644
index 0000000000000000000000000000000000000000..6925e6841ab78608e52fbcb07c0ddc34504679b5
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/publickey/OpenSSHPublicKeyFormat.java
@@ -0,0 +1,169 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.publickey;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.util.Base64;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.19 $
+ */
+public class OpenSSHPublicKeyFormat implements SshPublicKeyFormat {
+    String comment = null;
+
+    /**
+     * Creates a new OpenSSHPublicKeyFormat object.
+     *
+     * @param comment
+     */
+    public OpenSSHPublicKeyFormat(String comment) {
+        setComment(comment);
+    }
+
+    /**
+     * Creates a new OpenSSHPublicKeyFormat object.
+     */
+    public OpenSSHPublicKeyFormat() {
+    }
+
+    /**
+     *
+     *
+     * @param comment
+     */
+    public void setComment(String comment) {
+        this.comment = comment;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getComment() {
+        return comment;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getFormatType() {
+        return "OpenSSH-PublicKey";
+    }
+
+    /**
+     *
+     *
+     * @param formattedKey
+     *
+     * @return
+     *
+     * @throws InvalidSshKeyException
+     */
+    public byte[] getKeyBlob(byte[] formattedKey) throws InvalidSshKeyException {
+        String temp = new String(formattedKey);
+
+        // Get the algorithm name end index
+        int i = temp.indexOf(" ");
+
+        if (i > 0) {
+            String algorithm = temp.substring(0, i);
+
+            if (supportsAlgorithm(algorithm)) {
+                // Get the keyblob end index
+                int i2 = temp.indexOf(" ", i + 1);
+
+                if (i2 > i) {
+                    String encoded = temp.substring(i + 1, i2);
+
+                    if (temp.length() > i2) {
+                        comment = temp.substring(i2 + 1).trim();
+                    }
+
+                    return Base64.decode(encoded);
+                }
+            }
+        }
+
+        throw new InvalidSshKeyException("Failed to read OpenSSH key format");
+    }
+
+    /**
+     *
+     *
+     * @param keyblob
+     *
+     * @return
+     */
+    public byte[] formatKey(byte[] keyblob) {
+        String algorithm = ByteArrayReader.readString(keyblob, 0);
+        String formatted = algorithm + " " + Base64.encodeBytes(keyblob, true);
+
+        if (comment != null) {
+            formatted += (" " + comment);
+        }
+
+        return formatted.getBytes();
+    }
+
+    /**
+     *
+     *
+     * @param formattedKey
+     *
+     * @return
+     */
+    public boolean isFormatted(byte[] formattedKey) {
+        String test = new String(formattedKey).trim();
+
+        if (test.startsWith("ssh-dss") || test.startsWith("ssh-rsa")) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     *
+     *
+     * @param algorithm
+     *
+     * @return
+     */
+    public boolean supportsAlgorithm(String algorithm) {
+        if (algorithm.equals("ssh-dss") || algorithm.equals("ssh-rsa")) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/publickey/SECSHPublicKeyFormat.java b/src/com/sshtools/j2ssh/transport/publickey/SECSHPublicKeyFormat.java
new file mode 100644
index 0000000000000000000000000000000000000000..8ead6c195a000bc6b02a1ebfcc05bf77e3b78b15
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/publickey/SECSHPublicKeyFormat.java
@@ -0,0 +1,98 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.publickey;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public class SECSHPublicKeyFormat extends Base64EncodedFileFormat
+    implements SshPublicKeyFormat {
+    private static String BEGIN = "---- BEGIN SSH2 PUBLIC KEY ----";
+    private static String END = "---- END SSH2 PUBLIC KEY ----";
+
+    /**
+     * Creates a new SECSHPublicKeyFormat object.
+     *
+     * @param subject
+     * @param comment
+     */
+    public SECSHPublicKeyFormat(String subject, String comment) {
+        super(BEGIN, END);
+        setHeaderValue("Subject", subject);
+        setComment(comment);
+    }
+
+    /**
+     * Creates a new SECSHPublicKeyFormat object.
+     */
+    public SECSHPublicKeyFormat() {
+        super(BEGIN, END);
+    }
+
+    /**
+     *
+     *
+     * @param comment
+     */
+    public void setComment(String comment) {
+        setHeaderValue("Comment",
+            (comment.trim().startsWith("\"") ? "" : "\"") + comment.trim() +
+            (comment.trim().endsWith("\"") ? "" : "\""));
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getComment() {
+        return getHeaderValue("Comment");
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getFormatType() {
+        return "SECSH-PublicKey-" + super.getFormatType();
+    }
+
+    /**
+     *
+     *
+     * @param algorithm
+     *
+     * @return
+     */
+    public boolean supportsAlgorithm(String algorithm) {
+        return SshKeyPairFactory.supportsKey(algorithm);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/publickey/SshKeyFormatConversion.java b/src/com/sshtools/j2ssh/transport/publickey/SshKeyFormatConversion.java
new file mode 100644
index 0000000000000000000000000000000000000000..127d450f82329bc05dbe3daa155472da9d55ba38
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/publickey/SshKeyFormatConversion.java
@@ -0,0 +1,71 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.publickey;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.16 $
+ */
+public interface SshKeyFormatConversion {
+    /**
+     *
+     *
+     * @return
+     */
+    public String getFormatType();
+
+    /**
+     *
+     *
+     * @param formattedKey
+     *
+     * @return
+     *
+     * @throws InvalidSshKeyException
+     */
+    public byte[] getKeyBlob(byte[] formattedKey) throws InvalidSshKeyException;
+
+    /**
+     *
+     *
+     * @param keyBlob
+     *
+     * @return
+     */
+    public byte[] formatKey(byte[] keyBlob);
+
+    /**
+     *
+     *
+     * @param formattedKey
+     *
+     * @return
+     */
+    public boolean isFormatted(byte[] formattedKey);
+}
diff --git a/src/com/sshtools/j2ssh/transport/publickey/SshKeyGenerator.java b/src/com/sshtools/j2ssh/transport/publickey/SshKeyGenerator.java
new file mode 100644
index 0000000000000000000000000000000000000000..417f40bd258371b5244b87f27334d954d0982a86
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/publickey/SshKeyGenerator.java
@@ -0,0 +1,377 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.publickey;
+
+import com.sshtools.j2ssh.SshThread;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+import java.lang.reflect.Method;
+
+
+/*import java.util.logging.FileHandler;
+ import java.util.logging.Handler;
+ import java.util.logging.Level;
+ import java.util.logging.Logger;
+ import java.util.logging.SimpleFormatter;*/
+public class SshKeyGenerator {
+    private static String filename = null;
+    private static String type = "dsa";
+    private static int bits = 1024;
+    private static boolean useGUI;
+    private static boolean guiAvailable;
+    private static boolean toOpenSSH = false;
+    private static boolean toSECSH = false;
+    private static boolean changePass = false;
+
+    // Test if the GUI is available
+    static {
+        try {
+            Class.forName("com.sshtools.j2ssh.keygen.Main");
+            guiAvailable = true;
+        } catch (ClassNotFoundException cnfe) {
+        }
+    }
+
+    /**
+     * Creates a new SshKeyGenerator object.
+     */
+    public SshKeyGenerator() {
+    }
+
+    /**
+     *
+     *
+     * @param type
+     * @param bits
+     * @param filename
+     * @param username
+     * @param passphrase
+     *
+     * @throws IOException
+     */
+    public void generateKeyPair(String type, int bits, String filename,
+        String username, String passphrase) throws IOException {
+        System.out.println("****Sshtools.com SSH Key Pair Generator****");
+
+        String keyType = type;
+
+        if (keyType.equalsIgnoreCase("DSA")) {
+            keyType = "ssh-dss";
+        }
+
+        if (keyType.equalsIgnoreCase("RSA")) {
+            keyType = "ssh-rsa";
+        }
+
+        final SshKeyPair pair = SshKeyPairFactory.newInstance(keyType);
+        System.out.println("Generating " + String.valueOf(bits) + " bit " +
+            keyType + " key pair");
+
+        Thread thread = new SshThread(new Runnable() {
+                    public void run() {
+                        pair.generate(SshKeyGenerator.this.bits);
+                    }
+                }, "Key generator", true);
+        thread.start();
+
+        while (thread.isAlive()) {
+            System.out.print(".");
+
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+            }
+        }
+
+        System.out.println();
+        System.out.println("Creating Public Key file " + filename + ".pub");
+
+        // Now save the files
+        SshPublicKeyFile pub = SshPublicKeyFile.create(pair.getPublicKey(),
+                new SECSHPublicKeyFormat(username,
+                    String.valueOf(bits) + "-bit " + type));
+        FileOutputStream out = new FileOutputStream(filename + ".pub");
+        out.write(pub.getBytes());
+        out.close();
+        System.out.println("Generating Private Key file " + filename);
+
+        if (passphrase == null) {
+            passphrase = promptForPassphrase(true);
+        }
+
+        SshPrivateKeyFile prv = SshPrivateKeyFile.create(pair.getPrivateKey(),
+                passphrase,
+                new SshtoolsPrivateKeyFormat(username,
+                    String.valueOf(bits) + "-bit " + type));
+        out = new FileOutputStream(filename);
+        out.write(prv.getBytes());
+        out.close();
+    }
+
+    /**
+     *
+     *
+     * @param args
+     */
+    public static void main(String[] args) {
+        try {
+            processCommandLine(args);
+
+            // Setup a logfile
+
+            /*Handler fh = new FileHandler("ssh-keygen.log");
+             fh.setFormatter(new SimpleFormatter());
+             Logger.getLogger("com.sshtools").setUseParentHandlers(false);
+             Logger.getLogger("com.sshtools").addHandler(fh);
+             Logger.getLogger("com.sshtools").setLevel(Level.ALL);*/
+            if (useGUI) {
+                Class c = Class.forName("com.sshtools.j2ssh.keygen.Main");
+                Method m = c.getMethod("main", new Class[] { args.getClass() });
+                m.invoke(null, new Object[] { new String[] {  } });
+            } else {
+                File f = new File(filename);
+
+                if (filename == null) {
+                    System.err.print("You must supply a valid file to convert!");
+                    System.exit(1);
+                }
+
+                if (toOpenSSH || toSECSH) {
+                    if (!f.exists()) {
+                        System.err.print("The file " + f.getAbsolutePath() +
+                            " does not exist!");
+                        System.exit(1);
+                    }
+
+                    try {
+                        if (toOpenSSH) {
+                            System.out.print(convertPublicKeyFile(f,
+                                    new OpenSSHPublicKeyFormat()));
+                        } else {
+                            System.out.print(convertPublicKeyFile(f,
+                                    new SECSHPublicKeyFormat()));
+                        }
+                    } catch (InvalidSshKeyException e) {
+                        System.err.println("The key format is invalid!");
+                    } catch (IOException ioe) {
+                        System.err.println(
+                            "An error occurs whilst reading the file " +
+                            f.getAbsolutePath());
+                    }
+
+                    System.exit(0);
+                }
+
+                if (changePass) {
+                    if (!f.exists()) {
+                        System.err.print("The file " + f.getAbsolutePath() +
+                            " does not exist!");
+                        System.exit(1);
+                    }
+
+                    changePassphrase(f);
+                } else {
+                    SshKeyGenerator generator = new SshKeyGenerator();
+                    String username = System.getProperty("user.name");
+                    generator.generateKeyPair(type, bits, filename, username,
+                        null);
+                }
+            }
+        } catch (Throwable t) {
+            t.printStackTrace();
+        }
+    }
+
+    /**
+     *
+     *
+     * @param args
+     */
+    public static void processCommandLine(String[] args) {
+        if (args.length > 0) {
+            for (int i = 0; i < args.length; i++) {
+                if (args[i].equalsIgnoreCase("-b")) {
+                    bits = Integer.parseInt(args[++i]);
+                } else if (args[i].equalsIgnoreCase("-t")) {
+                    type = args[++i];
+                } else if (args[i].equalsIgnoreCase("-p")) {
+                    changePass = true;
+                } else if (args[i].equalsIgnoreCase("-g") && guiAvailable) {
+                    useGUI = true;
+                } else if (args[i].equalsIgnoreCase("-i")) {
+                    toOpenSSH = true;
+                } else if (args[i].equalsIgnoreCase("-e")) {
+                    toSECSH = true;
+                } else if (!args[i].startsWith("-")) {
+                    if (filename != null) {
+                        printUsage();
+                        System.exit(1);
+                    }
+
+                    filename = args[i];
+                }
+            }
+        }
+
+        if (!useGUI && (filename == null)) {
+            printUsage();
+            System.exit(0);
+        }
+    }
+
+    private static void changePassphrase(File f) {
+        System.out.println("Opening Private Key file " + f.getAbsolutePath());
+
+        try {
+            System.out.println("Opening Private Key file " +
+                f.getAbsolutePath());
+
+            String oldPassphrase = promptForPassphrase(false);
+            String newPassphrase = promptForPassphrase(true);
+            changePassphrase(f, oldPassphrase, newPassphrase);
+        } catch (InvalidSshKeyException e) {
+            System.err.println("The key format is invalid!");
+        } catch (IOException ioe) {
+            System.err.println("An error occurs whilst reading the file " +
+                f.getAbsolutePath());
+        }
+    }
+
+    /**
+     *
+     *
+     * @param f
+     * @param oldPassphrase
+     * @param newPassphrase
+     *
+     * @throws IOException
+     * @throws InvalidSshKeyException
+     */
+    public static void changePassphrase(File f, String oldPassphrase,
+        String newPassphrase) throws IOException, InvalidSshKeyException {
+        // Open up the file with its current format
+        SshPrivateKeyFile file = SshPrivateKeyFile.parse(f);
+        System.out.println("Saving Private Key file with new passphrase");
+        file.changePassphrase(oldPassphrase, newPassphrase);
+
+        FileOutputStream out = null;
+
+        try {
+            out = new FileOutputStream(f);
+            out.write(file.getBytes());
+        } finally {
+            if (out != null) {
+                out.close();
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @param f
+     * @param convert
+     *
+     * @return
+     *
+     * @throws InvalidSshKeyException
+     * @throws IOException
+     */
+    public static String convertPublicKeyFile(File f, SshPublicKeyFormat convert)
+        throws InvalidSshKeyException, IOException {
+        // Open up the file with its current format
+        SshPublicKeyFile file = SshPublicKeyFile.parse(f);
+
+        // Set the new format
+        file.setFormat(convert);
+
+        // Output to stdout
+        return file.toString();
+    }
+
+    private static void printUsage() {
+        System.out.println("Usage: SshKeyGenerator [options] filename");
+        System.out.println("Options:");
+        System.out.println(
+            "-b bits        Number of bits in the key to create.");
+        System.out.println(
+            "-e             Convert OpenSSH to IETF SECSH key file.");
+        System.out.println(
+            "-i             Convert IETF SECSH to OpenSSH key file.");
+        System.out.println("-t type        The type of key to create.");
+        System.out.println(
+            "-p             Change the passphrase of the private key file.");
+
+        if (guiAvailable) {
+            System.out.println("-g \t\tUse GUI to create key");
+        }
+    }
+
+    private static String promptForPassphrase(boolean confirm)
+        throws IOException {
+        // Confirm the passphrase
+        BufferedReader reader = new BufferedReader(new InputStreamReader(
+                    System.in));
+        String pass1 = "";
+        String pass2 = "";
+
+        while (true) {
+            System.out.print("Enter passphrase: ");
+            pass1 = reader.readLine();
+
+            if (!confirm) {
+                break;
+            }
+
+            System.out.print("Confirm passphrase: ");
+            pass2 = reader.readLine();
+
+            if (pass1.equals(pass2)) {
+                if (pass1.trim().length() == 0) {
+                    System.out.print(
+                        "You supplied an empty passphrase, are you sure? [Yes|No]: ");
+                    pass2 = reader.readLine();
+
+                    if (pass2.equalsIgnoreCase("YES")) {
+                        break;
+                    }
+                } else {
+                    break;
+                }
+            } else {
+                System.out.println(
+                    "The passphrases supplied were not indentical! Try again");
+            }
+        }
+
+        return pass1;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/publickey/SshKeyPair.java b/src/com/sshtools/j2ssh/transport/publickey/SshKeyPair.java
new file mode 100644
index 0000000000000000000000000000000000000000..8ad48592b51bde2a16b892f2a0cc500b2f0b8bdc
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/publickey/SshKeyPair.java
@@ -0,0 +1,136 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.publickey;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.17 $
+ */
+public abstract class SshKeyPair {
+    private SshPrivateKey prv;
+    private SshPublicKey pub;
+
+    /**
+     * Creates a new SshKeyPair object.
+     */
+    public SshKeyPair() {
+    }
+
+    /**
+     *
+     *
+     * @param bits
+     */
+    public abstract void generate(int bits);
+
+    /**
+     *
+     *
+     * @param key
+     */
+    public void setPrivateKey(SshPrivateKey key) {
+        this.prv = key;
+        this.pub = key.getPublicKey();
+    }
+
+    /**
+     *
+     *
+     * @param encoded
+     *
+     * @return
+     *
+     * @throws InvalidSshKeyException
+     */
+    public SshPrivateKey setPrivateKey(byte[] encoded)
+        throws InvalidSshKeyException {
+        setPrivateKey(decodePrivateKey(encoded));
+
+        return this.prv;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public SshPrivateKey getPrivateKey() {
+        return prv;
+    }
+
+    /**
+     *
+     *
+     * @param encoded
+     *
+     * @return
+     *
+     * @throws InvalidSshKeyException
+     */
+    public SshPublicKey setPublicKey(byte[] encoded)
+        throws InvalidSshKeyException {
+        this.pub = decodePublicKey(encoded);
+        this.prv = null;
+
+        return this.pub;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public SshPublicKey getPublicKey() {
+        return pub;
+    }
+
+    /**
+     *
+     *
+     * @param encoded
+     *
+     * @return
+     *
+     * @throws InvalidSshKeyException
+     */
+    public abstract SshPrivateKey decodePrivateKey(byte[] encoded)
+        throws InvalidSshKeyException;
+
+    /**
+     *
+     *
+     * @param encoded
+     *
+     * @return
+     *
+     * @throws InvalidSshKeyException
+     */
+    public abstract SshPublicKey decodePublicKey(byte[] encoded)
+        throws InvalidSshKeyException;
+}
diff --git a/src/com/sshtools/j2ssh/transport/publickey/SshKeyPairFactory.java b/src/com/sshtools/j2ssh/transport/publickey/SshKeyPairFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..3c7b9bc95a4bbdd09e8b8f8de9229205504fc6f7
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/publickey/SshKeyPairFactory.java
@@ -0,0 +1,235 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.publickey;
+
+import com.sshtools.j2ssh.configuration.ConfigurationException;
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+import com.sshtools.j2ssh.configuration.ExtensionAlgorithm;
+import com.sshtools.j2ssh.configuration.SshAPIConfiguration;
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.transport.AlgorithmNotSupportedException;
+import com.sshtools.j2ssh.transport.publickey.dsa.SshDssKeyPair;
+import com.sshtools.j2ssh.transport.publickey.rsa.SshRsaKeyPair;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.26 $
+ */
+public class SshKeyPairFactory {
+    private static Map pks;
+    private static String defaultAlgorithm;
+    private static Log log = LogFactory.getLog(SshKeyPairFactory.class);
+
+    static {
+        pks = new HashMap();
+        log.info("Loading public key algorithms");
+        pks.put("ssh-dss", SshDssKeyPair.class);
+        pks.put("ssh-rsa", SshRsaKeyPair.class);
+
+        try {
+            // Load external pks from configuration file
+            if (ConfigurationLoader.isConfigurationAvailable(
+                        SshAPIConfiguration.class)) {
+                SshAPIConfiguration config = (SshAPIConfiguration) ConfigurationLoader.getConfiguration(SshAPIConfiguration.class);
+
+                if (config != null) {
+                    List list = config.getPublicKeyExtensions();
+
+                    if (list != null) {
+                        Iterator it = list.iterator();
+
+                        while (it.hasNext()) {
+                            ExtensionAlgorithm algorithm = (ExtensionAlgorithm) it.next();
+                            String name = algorithm.getAlgorithmName();
+
+                            if (pks.containsKey(name)) {
+                                log.debug("Standard public key " + name +
+                                    " is being overidden by " +
+                                    algorithm.getImplementationClass());
+                            } else {
+                                log.debug(algorithm.getAlgorithmName() +
+                                    " public key is implemented by " +
+                                    algorithm.getImplementationClass());
+                            }
+
+                            try {
+                                pks.put(algorithm.getAlgorithmName(),
+                                    ConfigurationLoader.getExtensionClass(
+                                        algorithm.getImplementationClass()));
+                            } catch (ClassNotFoundException cnfe) {
+                                log.error("Could not locate " +
+                                    algorithm.getImplementationClass());
+                            }
+                        }
+                    }
+
+                    defaultAlgorithm = config.getDefaultPublicKey();
+                }
+            }
+        } catch (ConfigurationException ex) {
+        }
+
+        if ((defaultAlgorithm == null) || !pks.containsKey(defaultAlgorithm)) {
+            log.debug("The default public key is not set! using first in list");
+
+            Iterator it = pks.keySet().iterator();
+            defaultAlgorithm = (String) it.next();
+        }
+    }
+
+    /**
+     * Creates a new SshKeyPairFactory object.
+     */
+    protected SshKeyPairFactory() {
+    }
+
+    /**
+     *
+     */
+    public static void initialize() {
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public static String getDefaultPublicKey() {
+        return defaultAlgorithm;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public static List getSupportedKeys() {
+        // Get the list of pks
+        return new ArrayList(pks.keySet());
+    }
+
+    /**
+     *
+     *
+     * @param methodName
+     *
+     * @return
+     *
+     * @throws AlgorithmNotSupportedException
+     */
+    public static SshKeyPair newInstance(String methodName)
+        throws AlgorithmNotSupportedException {
+        try {
+            return (SshKeyPair) ((Class) pks.get(methodName)).newInstance();
+        } catch (Exception e) {
+            throw new AlgorithmNotSupportedException(methodName +
+                " is not supported!");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param algorithm
+     *
+     * @return
+     */
+    public static boolean supportsKey(String algorithm) {
+        return pks.containsKey(algorithm);
+    }
+
+    /**
+     *
+     *
+     * @param encoded
+     *
+     * @return
+     *
+     * @throws InvalidSshKeyException
+     * @throws AlgorithmNotSupportedException
+     */
+    public static SshPrivateKey decodePrivateKey(byte[] encoded)
+        throws InvalidSshKeyException, AlgorithmNotSupportedException {
+        try {
+            ByteArrayReader bar = new ByteArrayReader(encoded);
+            String algorithm = bar.readString();
+
+            if (supportsKey(algorithm)) {
+                SshKeyPair pair = newInstance(algorithm);
+
+                return pair.decodePrivateKey(encoded);
+            } else {
+                throw new AlgorithmNotSupportedException(algorithm +
+                    " is not supported");
+            }
+        } catch (IOException ioe) {
+            throw new InvalidSshKeyException(ioe.getMessage());
+        }
+    }
+
+    /**
+     *
+     *
+     * @param encoded
+     *
+     * @return
+     *
+     * @throws InvalidSshKeyException
+     * @throws AlgorithmNotSupportedException
+     */
+    public static SshPublicKey decodePublicKey(byte[] encoded)
+        throws InvalidSshKeyException, AlgorithmNotSupportedException {
+        try {
+            ByteArrayReader bar = new ByteArrayReader(encoded);
+            String algorithm = bar.readString();
+
+            if (supportsKey(algorithm)) {
+                SshKeyPair pair = newInstance(algorithm);
+
+                return pair.decodePublicKey(encoded);
+            } else {
+                throw new AlgorithmNotSupportedException(algorithm +
+                    " is not supported");
+            }
+        } catch (IOException ioe) {
+            throw new InvalidSshKeyException(ioe.getMessage());
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/publickey/SshPrivateKey.java b/src/com/sshtools/j2ssh/transport/publickey/SshPrivateKey.java
new file mode 100644
index 0000000000000000000000000000000000000000..f8323c2eae9111a1cfe731342b93179ad91d4e1a
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/publickey/SshPrivateKey.java
@@ -0,0 +1,79 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.publickey;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.17 $
+ */
+public abstract class SshPrivateKey {
+    /**
+     * Creates a new SshPrivateKey object.
+     */
+    public SshPrivateKey() {
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public abstract String getAlgorithmName();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public abstract int getBitLength();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public abstract byte[] getEncoded();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public abstract SshPublicKey getPublicKey();
+
+    /**
+     *
+     *
+     * @param data
+     *
+     * @return
+     */
+    public abstract byte[] generateSignature(byte[] data)
+        throws InvalidSshKeySignatureException;
+}
diff --git a/src/com/sshtools/j2ssh/transport/publickey/SshPrivateKeyFile.java b/src/com/sshtools/j2ssh/transport/publickey/SshPrivateKeyFile.java
new file mode 100644
index 0000000000000000000000000000000000000000..1b59318e1960763acf50b55c0daf0eda1f60107d
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/publickey/SshPrivateKeyFile.java
@@ -0,0 +1,255 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.publickey;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.transport.AlgorithmNotSupportedException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import java.util.Iterator;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.21 $
+ */
+public class SshPrivateKeyFile {
+    private static Log log = LogFactory.getLog(SshPrivateKeyFile.class);
+    private SshPrivateKeyFormat format;
+    private byte[] keyblob;
+
+    /**
+     * Creates a new SshPrivateKeyFile object.
+     *
+     * @param keyblob
+     * @param format
+     */
+    protected SshPrivateKeyFile(byte[] keyblob, SshPrivateKeyFormat format) {
+        this.keyblob = keyblob;
+        this.format = format;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getBytes() {
+        return keyblob;
+    }
+
+    /**
+     *
+     *
+     * @param passphrase
+     *
+     * @return
+     *
+     * @throws InvalidSshKeyException
+     */
+    public byte[] getKeyBlob(String passphrase) throws InvalidSshKeyException {
+        return format.decryptKeyblob(keyblob, passphrase);
+    }
+
+    /**
+     *
+     *
+     * @param oldPassphrase
+     * @param newPassphrase
+     *
+     * @throws InvalidSshKeyException
+     */
+    public void changePassphrase(String oldPassphrase, String newPassphrase)
+        throws InvalidSshKeyException {
+        byte[] raw = format.decryptKeyblob(keyblob, oldPassphrase);
+        keyblob = format.encryptKeyblob(raw, newPassphrase);
+    }
+
+    /**
+     *
+     *
+     * @param formattedKey
+     *
+     * @return
+     *
+     * @throws InvalidSshKeyException
+     */
+    public static SshPrivateKeyFile parse(byte[] formattedKey)
+        throws InvalidSshKeyException {
+        if (formattedKey == null) {
+            throw new InvalidSshKeyException("Key data is null");
+        }
+
+        log.info("Parsing private key file");
+
+        // Try the default private key format
+        SshPrivateKeyFormat format;
+        format = SshPrivateKeyFormatFactory.newInstance(SshPrivateKeyFormatFactory.getDefaultFormatType());
+
+        boolean valid = format.isFormatted(formattedKey);
+
+        if (!valid) {
+            log.info(
+                "Private key is not in the default format, attempting parse with other supported formats");
+
+            Iterator it = SshPrivateKeyFormatFactory.getSupportedFormats()
+                                                    .iterator();
+            String ft;
+
+            while (it.hasNext() && !valid) {
+                ft = (String) it.next();
+                log.debug("Attempting " + ft);
+                format = SshPrivateKeyFormatFactory.newInstance(ft);
+                valid = format.isFormatted(formattedKey);
+            }
+        }
+
+        if (valid) {
+            return new SshPrivateKeyFile(formattedKey, format);
+        } else {
+            throw new InvalidSshKeyException(
+                "The key format is not a supported format");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param keyfile
+     *
+     * @return
+     *
+     * @throws InvalidSshKeyException
+     * @throws IOException
+     */
+    public static SshPrivateKeyFile parse(File keyfile)
+        throws InvalidSshKeyException, IOException {
+        FileInputStream in = new FileInputStream(keyfile);
+        byte[] data = null;
+
+        try {
+            data = new byte[in.available()];
+            in.read(data);
+        } finally {
+            try {
+                if (in != null) {
+                    in.close();
+                }
+            } catch (IOException ex) {
+            }
+        }
+
+        return parse(data);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean isPassphraseProtected() {
+        return format.isPassphraseProtected(keyblob);
+    }
+
+    /*public void changePassphrase(String oldPassphrase, String newPassphrase)
+     throws InvalidSshKeyException {
+     keyblob = format.changePassphrase(keyblob, oldPassphrase, newPassphrase);
+      }*/
+    public static SshPrivateKeyFile create(SshPrivateKey key,
+        String passphrase, SshPrivateKeyFormat format)
+        throws InvalidSshKeyException {
+        byte[] keyblob = format.encryptKeyblob(key.getEncoded(), passphrase);
+
+        return new SshPrivateKeyFile(keyblob, format);
+    }
+
+    /**
+     *
+     *
+     * @param newFormat
+     * @param passphrase
+     *
+     * @throws InvalidSshKeyException
+     */
+    public void setFormat(SshPrivateKeyFormat newFormat, String passphrase)
+        throws InvalidSshKeyException {
+        byte[] raw = this.format.decryptKeyblob(keyblob, passphrase);
+        format = newFormat;
+        keyblob = format.encryptKeyblob(raw, passphrase);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public SshPrivateKeyFormat getFormat() {
+        return format;
+    }
+
+    /**
+     *
+     *
+     * @param passphrase
+     *
+     * @return
+     *
+     * @throws InvalidSshKeyException
+     */
+    public SshPrivateKey toPrivateKey(String passphrase)
+        throws InvalidSshKeyException {
+        try {
+            byte[] raw = format.decryptKeyblob(keyblob, passphrase);
+            SshKeyPair pair = SshKeyPairFactory.newInstance(getAlgorithm(raw));
+
+            return pair.decodePrivateKey(raw);
+        } catch (AlgorithmNotSupportedException anse) {
+            throw new InvalidSshKeyException(
+                "The public key algorithm for this private key is not supported");
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String toString() {
+        return new String(keyblob);
+    }
+
+    private String getAlgorithm(byte[] raw) {
+        return ByteArrayReader.readString(raw, 0);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/publickey/SshPrivateKeyFormat.java b/src/com/sshtools/j2ssh/transport/publickey/SshPrivateKeyFormat.java
new file mode 100644
index 0000000000000000000000000000000000000000..8d84023f9ff18f9a2dbb6825aabdae378cc84e64
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/publickey/SshPrivateKeyFormat.java
@@ -0,0 +1,95 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.publickey;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.19 $
+ */
+public interface SshPrivateKeyFormat {
+    /**
+     *
+     *
+     * @param formattedKey
+     *
+     * @return
+     */
+    public boolean isPassphraseProtected(byte[] formattedKey);
+
+    /**
+     *
+     *
+     * @param formattedKey
+     *
+     * @return
+     */
+    public boolean isFormatted(byte[] formattedKey);
+
+    /**
+     *
+     *
+     * @param formattedKey
+     * @param passphrase
+     *
+     * @return
+     *
+     * @throws InvalidSshKeyException
+     */
+    public byte[] decryptKeyblob(byte[] formattedKey, String passphrase)
+        throws InvalidSshKeyException;
+
+    /**
+     *
+     *
+     * @param keyblob
+     * @param passphrase
+     *
+     * @return
+     *
+     * @throws InvalidSshKeyException
+     */
+    public byte[] encryptKeyblob(byte[] keyblob, String passphrase)
+        throws InvalidSshKeyException;
+
+    /**
+     *
+     *
+     * @param algorithm
+     *
+     * @return
+     */
+    public boolean supportsAlgorithm(String algorithm);
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getFormatType();
+}
diff --git a/src/com/sshtools/j2ssh/transport/publickey/SshPrivateKeyFormatFactory.java b/src/com/sshtools/j2ssh/transport/publickey/SshPrivateKeyFormatFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..87b7b78f31a47be6f9da16682374d2356aae2a3a
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/publickey/SshPrivateKeyFormatFactory.java
@@ -0,0 +1,171 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.publickey;
+
+import com.sshtools.j2ssh.configuration.ConfigurationException;
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+import com.sshtools.j2ssh.io.IOUtil;
+import com.sshtools.j2ssh.openssh.*;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.InputStream;
+
+import java.net.URL;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+import java.util.Vector;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.26 $
+ */
+public class SshPrivateKeyFormatFactory {
+    private static String defaultFormat;
+    private static HashMap formatTypes;
+    private static Log log = LogFactory.getLog(SshPrivateKeyFormatFactory.class);
+    private static Vector types;
+
+    static {
+        log.info("Loading private key formats");
+
+        List formats = new ArrayList();
+        types = new Vector();
+        formatTypes = new HashMap();
+        formats.add(SshtoolsPrivateKeyFormat.class.getName());
+        formats.add(OpenSSHPrivateKeyFormat.class.getName());
+
+        defaultFormat = "SSHTools-PrivateKey-Base64Encoded";
+
+        try {
+            Enumeration en = ConfigurationLoader.getExtensionClassLoader()
+                                                  .getResources("j2ssh.privatekey");
+            URL url;
+            Properties properties = new Properties();
+            InputStream in;
+
+            while ((en != null) && en.hasMoreElements()) {
+                url = (URL) en.nextElement();
+                in = url.openStream();
+                properties.load(in);
+                IOUtil.closeStream(in);
+
+                int num = 1;
+                String name = "";
+                Class cls;
+
+                while (properties.getProperty("privatekey.name." +
+                            String.valueOf(num)) != null) {
+                    name = properties.getProperty("privatekey.name." +
+                            String.valueOf(num));
+                    formats.add(properties.getProperty("privatekey.class." +
+                            String.valueOf(num)));
+
+                    num++;
+                }
+            }
+        } catch (Throwable t) {
+        }
+
+        SshPrivateKeyFormat f;
+
+        Iterator it = formats.iterator();
+        String classname;
+
+        while (it.hasNext()) {
+            classname = (String) it.next();
+
+            try {
+                Class cls = ConfigurationLoader.getExtensionClass(classname);
+                f = (SshPrivateKeyFormat) cls.newInstance();
+                log.debug("Installing " + f.getFormatType() +
+                    " private key format");
+                formatTypes.put(f.getFormatType(), cls);
+                types.add(f.getFormatType());
+            } catch (Throwable t) {
+                log.warn("Private key format implemented by " + classname +
+                    " will not be available", t);
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public static List getSupportedFormats() {
+        return types;
+    }
+
+    public static void initialize() {
+    }
+
+    /**
+     *
+     *
+     * @param type
+     *
+     * @return
+     *
+     * @throws InvalidSshKeyException
+     */
+    public static SshPrivateKeyFormat newInstance(String type)
+        throws InvalidSshKeyException {
+        try {
+            if (formatTypes.containsKey(type)) {
+                return (SshPrivateKeyFormat) ((Class) formatTypes.get(type)).newInstance();
+            } else {
+                throw new InvalidSshKeyException("The format type " + type +
+                    " is not supported");
+            }
+        } catch (IllegalAccessException iae) {
+            throw new InvalidSshKeyException(
+                "Illegal access to class implementation of " + type);
+        } catch (InstantiationException ie) {
+            throw new InvalidSshKeyException(
+                "Failed to create instance of format type " + type);
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public static String getDefaultFormatType() {
+        return defaultFormat;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/publickey/SshPublicKey.java b/src/com/sshtools/j2ssh/transport/publickey/SshPublicKey.java
new file mode 100644
index 0000000000000000000000000000000000000000..e79436c32d3c7147391dd44e89551864afa2388f
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/publickey/SshPublicKey.java
@@ -0,0 +1,124 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.publickey;
+
+import com.sshtools.j2ssh.util.Hash;
+
+import java.security.NoSuchAlgorithmException;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.24 $
+ */
+public abstract class SshPublicKey {
+    /**
+     *
+     *
+     * @return
+     */
+    public abstract String getAlgorithmName();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public abstract int getBitLength();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public abstract byte[] getEncoded();
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getFingerprint() {
+        try {
+            Hash md5 = new Hash("MD5");
+            md5.putBytes(getEncoded());
+
+            byte[] digest = md5.doFinal();
+            int bits = getBitLength();
+            bits = (((bits % 8) != 0) ? (bits += (bits % 8)) : bits);
+
+            String ret = String.valueOf(bits);
+
+            for (int i = 0; i < digest.length; i++) {
+                ret += (((i == 0) ? ":" : "") + " " +
+                Integer.toHexString(digest[i] & 0xFF));
+            }
+
+            return ret;
+        } catch (NoSuchAlgorithmException nsae) {
+            return null;
+        }
+    }
+
+    /**
+     *
+     *
+     * @param obj
+     *
+     * @return
+     */
+    public boolean equals(Object obj) {
+        if (obj instanceof SshPublicKey) {
+            return (getFingerprint().compareTo(((SshPublicKey) obj).getFingerprint()) == 0);
+        }
+
+        return false;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int hashCode() {
+        return getFingerprint().hashCode();
+    }
+
+    /**
+     *
+     *
+     * @param signature
+     * @param exchangeHash
+     *
+     * @return
+     *
+     * @throws InvalidSshKeySignatureException
+     */
+    public abstract boolean verifySignature(byte[] signature,
+        byte[] exchangeHash) throws InvalidSshKeySignatureException;
+}
diff --git a/src/com/sshtools/j2ssh/transport/publickey/SshPublicKeyFile.java b/src/com/sshtools/j2ssh/transport/publickey/SshPublicKeyFile.java
new file mode 100644
index 0000000000000000000000000000000000000000..b34a929a8f9709cfaa713f002d8b448d4af4efd6
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/publickey/SshPublicKeyFile.java
@@ -0,0 +1,241 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.publickey;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import java.util.Iterator;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.20 $
+ */
+public class SshPublicKeyFile {
+    private static Log log = LogFactory.getLog(SshPublicKeyFile.class);
+    private SshPublicKeyFormat format;
+    private byte[] keyblob;
+    private String comment;
+
+    /**
+     * Creates a new SshPublicKeyFile object.
+     *
+     * @param keyblob
+     * @param format
+     */
+    protected SshPublicKeyFile(byte[] keyblob, SshPublicKeyFormat format) {
+        this.keyblob = keyblob;
+        this.format = format;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getBytes() {
+        return format.formatKey(keyblob);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getComment() {
+        return comment;
+    }
+
+    /**
+     *
+     *
+     * @param comment
+     */
+    public void setComment(String comment) {
+        this.comment = comment;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getKeyBlob() {
+        return keyblob;
+    }
+
+    /**
+     *
+     *
+     * @param key
+     * @param format
+     *
+     * @return
+     */
+    public static SshPublicKeyFile create(SshPublicKey key,
+        SshPublicKeyFormat format) {
+        SshPublicKeyFile file = new SshPublicKeyFile(key.getEncoded(), format);
+        file.setComment(format.getComment());
+
+        return file;
+    }
+
+    /**
+     *
+     *
+     * @param keyfile
+     *
+     * @return
+     *
+     * @throws InvalidSshKeyException
+     * @throws IOException
+     */
+    public static SshPublicKeyFile parse(File keyfile)
+        throws InvalidSshKeyException, IOException {
+        FileInputStream in = new FileInputStream(keyfile);
+        byte[] data = new byte[in.available()];
+        in.read(data);
+        in.close();
+
+        return parse(data);
+    }
+
+    /**
+     *
+     *
+     * @param formattedKey
+     *
+     * @return
+     *
+     * @throws InvalidSshKeyException
+     */
+    public static SshPublicKeyFile parse(byte[] formattedKey)
+        throws InvalidSshKeyException {
+        log.info("Parsing public key file");
+
+        // Try the default private key format
+        SshPublicKeyFormat format;
+        format = SshPublicKeyFormatFactory.newInstance(SshPublicKeyFormatFactory.getDefaultFormatType());
+
+        boolean valid = format.isFormatted(formattedKey);
+
+        if (!valid) {
+            log.info(
+                "Public key is not in the default format, attempting parse with other supported formats");
+
+            Iterator it = SshPublicKeyFormatFactory.getSupportedFormats()
+                                                   .iterator();
+            String ft;
+
+            while (it.hasNext() && !valid) {
+                ft = (String) it.next();
+                log.debug("Attempting " + ft);
+                format = SshPublicKeyFormatFactory.newInstance(ft);
+                valid = format.isFormatted(formattedKey);
+            }
+        }
+
+        if (valid) {
+            SshPublicKeyFile file = new SshPublicKeyFile(format.getKeyBlob(
+                        formattedKey), format);
+            file.setComment(format.getComment());
+
+            return file;
+        } else {
+            throw new InvalidSshKeyException(
+                "The key format is not a supported format");
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getAlgorithm() {
+        return ByteArrayReader.readString(keyblob, 0);
+    }
+
+    /**
+     *
+     *
+     * @param newFormat
+     *
+     * @throws InvalidSshKeyException
+     */
+    public void setFormat(SshPublicKeyFormat newFormat)
+        throws InvalidSshKeyException {
+        if (newFormat.supportsAlgorithm(getAlgorithm())) {
+            newFormat.setComment(format.getComment());
+            this.format = newFormat;
+        } else {
+            throw new InvalidSshKeyException(
+                "The format does not support the public key algorithm");
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public SshPublicKeyFormat getFormat() {
+        return format;
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public SshPublicKey toPublicKey() throws IOException {
+        ByteArrayReader bar = new ByteArrayReader(keyblob);
+        String type = bar.readString();
+        SshKeyPair pair = SshKeyPairFactory.newInstance(type);
+
+        return pair.decodePublicKey(keyblob);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String toString() {
+        return new String(format.formatKey(keyblob));
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/publickey/SshPublicKeyFormat.java b/src/com/sshtools/j2ssh/transport/publickey/SshPublicKeyFormat.java
new file mode 100644
index 0000000000000000000000000000000000000000..e5d906853983de524b7be820ea45c2c38099d9cd
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/publickey/SshPublicKeyFormat.java
@@ -0,0 +1,94 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.publickey;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.17 $
+ */
+public interface SshPublicKeyFormat {
+    /**
+     *
+     *
+     * @param comment
+     */
+    public void setComment(String comment);
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getComment();
+
+    /**
+     *
+     *
+     * @param algorithm
+     *
+     * @return
+     */
+    public boolean supportsAlgorithm(String algorithm);
+
+    /**
+     *
+     *
+     * @param keyblob
+     *
+     * @return
+     */
+    public byte[] formatKey(byte[] keyblob);
+
+    /**
+     *
+     *
+     * @param formattedKey
+     *
+     * @return
+     *
+     * @throws InvalidSshKeyException
+     */
+    public byte[] getKeyBlob(byte[] formattedKey) throws InvalidSshKeyException;
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getFormatType();
+
+    /**
+     *
+     *
+     * @param formattedKey
+     *
+     * @return
+     */
+    public boolean isFormatted(byte[] formattedKey);
+}
diff --git a/src/com/sshtools/j2ssh/transport/publickey/SshPublicKeyFormatFactory.java b/src/com/sshtools/j2ssh/transport/publickey/SshPublicKeyFormatFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..4bf5cc5ce521cb30693e1416a9741c017019c4fa
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/publickey/SshPublicKeyFormatFactory.java
@@ -0,0 +1,137 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.publickey;
+
+import com.sshtools.j2ssh.configuration.ConfigurationException;
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+import com.sshtools.j2ssh.configuration.SshAPIConfiguration;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.22 $
+ */
+public class SshPublicKeyFormatFactory {
+    private static String defaultFormat;
+    private static HashMap formatTypes = new HashMap();
+    private static Log log = LogFactory.getLog(SshPublicKeyFormatFactory.class);
+    private static Vector types = new Vector();
+
+    static {
+        List formats = new ArrayList();
+        formats.add(SECSHPublicKeyFormat.class.getName());
+        formats.add(OpenSSHPublicKeyFormat.class.getName());
+        defaultFormat = "SECSH-PublicKey-Base64Encoded";
+
+        try {
+            if (ConfigurationLoader.isConfigurationAvailable(
+                        SshAPIConfiguration.class)) {
+                SshAPIConfiguration config = (SshAPIConfiguration) ConfigurationLoader.getConfiguration(SshAPIConfiguration.class);
+                defaultFormat = config.getDefaultPublicKeyFormat();
+                formats.addAll(config.getPublicKeyFormats());
+            }
+        } catch (ConfigurationException ex) {
+        }
+
+        log.debug("Default public key format will be " + defaultFormat);
+
+        SshPublicKeyFormat f;
+        Iterator it = formats.iterator();
+        String classname;
+
+        while (it.hasNext()) {
+            classname = (String) it.next();
+
+            try {
+                Class cls = ConfigurationLoader.getExtensionClass(classname);
+                f = (SshPublicKeyFormat) cls.newInstance();
+                log.debug("Installing " + f.getFormatType() +
+                    " public key format");
+                formatTypes.put(f.getFormatType(), cls);
+                types.add(f.getFormatType());
+            } catch (Exception iae) {
+                log.warn("Public key format implemented by " + classname +
+                    " will not be available", iae);
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public static List getSupportedFormats() {
+        return types;
+    }
+
+    /**
+     *
+     *
+     * @param type
+     *
+     * @return
+     *
+     * @throws InvalidSshKeyException
+     */
+    public static SshPublicKeyFormat newInstance(String type)
+        throws InvalidSshKeyException {
+        try {
+            if (formatTypes.containsKey(type)) {
+                return (SshPublicKeyFormat) ((Class) formatTypes.get(type)).newInstance();
+            } else {
+                throw new InvalidSshKeyException("The format type " + type +
+                    " is not supported");
+            }
+        } catch (IllegalAccessException iae) {
+            throw new InvalidSshKeyException(
+                "Illegal access to class implementation of " + type);
+        } catch (InstantiationException ie) {
+            throw new InvalidSshKeyException(
+                "Failed to create instance of format type " + type);
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public static String getDefaultFormatType() {
+        return defaultFormat;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/publickey/SshtoolsPrivateKeyFormat.java b/src/com/sshtools/j2ssh/transport/publickey/SshtoolsPrivateKeyFormat.java
new file mode 100644
index 0000000000000000000000000000000000000000..f530ac7745fb5b62819a20d11f82177e6c01aae4
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/publickey/SshtoolsPrivateKeyFormat.java
@@ -0,0 +1,252 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.publickey;
+
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.util.Hash;
+
+import java.io.IOException;
+
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.KeySpec;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.DESedeKeySpec;
+import javax.crypto.spec.IvParameterSpec;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.22 $
+ */
+public class SshtoolsPrivateKeyFormat extends Base64EncodedFileFormat
+    implements SshPrivateKeyFormat {
+    private static String BEGIN = "---- BEGIN SSHTOOLS ENCRYPTED PRIVATE KEY ----";
+    private static String END = "---- END SSHTOOLS ENCRYPTED PRIVATE KEY ----";
+    private int cookie = 0x52f37abe;
+
+    /**
+     * Creates a new SshtoolsPrivateKeyFormat object.
+     *
+     * @param subject
+     * @param comment
+     */
+    public SshtoolsPrivateKeyFormat(String subject, String comment) {
+        super(BEGIN, END);
+        setHeaderValue("Subject", subject);
+        setHeaderValue("Comment", comment);
+    }
+
+    /**
+     * Creates a new SshtoolsPrivateKeyFormat object.
+     */
+    public SshtoolsPrivateKeyFormat() {
+        super(BEGIN, END);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getFormatType() {
+        return "SSHTools-PrivateKey-" + super.getFormatType();
+    }
+
+    /**
+     *
+     *
+     * @param formattedKey
+     *
+     * @return
+     */
+    public boolean isPassphraseProtected(byte[] formattedKey) {
+        try {
+            ByteArrayReader bar = new ByteArrayReader(getKeyBlob(formattedKey));
+            String type = bar.readString();
+
+            if (type.equals("none")) {
+                return false;
+            }
+
+            if (type.equalsIgnoreCase("3des-cbc")) {
+                return true;
+            }
+        } catch (IOException ioe) {
+        }
+
+        return false;
+    }
+
+    /**
+     *
+     *
+     * @param formattedKey
+     * @param passphrase
+     *
+     * @return
+     *
+     * @throws InvalidSshKeyException
+     */
+    public byte[] decryptKeyblob(byte[] formattedKey, String passphrase)
+        throws InvalidSshKeyException {
+        try {
+            byte[] keyblob = getKeyBlob(formattedKey);
+            ByteArrayReader bar = new ByteArrayReader(keyblob);
+            String type = bar.readString();
+
+            if (type.equalsIgnoreCase("3des-cbc")) {
+                // Decrypt the key
+                byte[] keydata = makePassphraseKey(passphrase);
+                byte[] iv = new byte[8];
+
+                if (type.equals("3DES-CBC")) {
+                    bar.read(iv);
+                }
+
+                keyblob = bar.readBinaryString();
+
+                Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
+                KeySpec keyspec = new DESedeKeySpec(keydata);
+                Key key = SecretKeyFactory.getInstance("DESede").generateSecret(keyspec);
+                cipher.init(Cipher.DECRYPT_MODE, key,
+                    new IvParameterSpec(iv, 0, cipher.getBlockSize()));
+
+                ByteArrayReader data = new ByteArrayReader(cipher.doFinal(
+                            keyblob));
+
+                if (data.readInt() == cookie) {
+                    keyblob = data.readBinaryString();
+                } else {
+                    throw new InvalidSshKeyException(
+                        "The host key is invalid, check the passphrase supplied");
+                }
+            } else {
+                keyblob = bar.readBinaryString();
+            }
+
+            return keyblob;
+        } catch (Exception aoe) {
+            throw new InvalidSshKeyException("Failed to read host key");
+        }
+    }
+
+    /**
+     *
+     *
+     * @param keyblob
+     * @param passphrase
+     *
+     * @return
+     */
+    public byte[] encryptKeyblob(byte[] keyblob, String passphrase) {
+        try {
+            ByteArrayWriter baw = new ByteArrayWriter();
+            String type = "none";
+
+            if (passphrase != null) {
+                if (!passphrase.trim().equals("")) {
+                    // Encrypt the data
+                    type = "3DES-CBC";
+
+                    // Decrypt the key
+                    byte[] keydata = makePassphraseKey(passphrase);
+                    byte[] iv = new byte[8];
+                    ConfigurationLoader.getRND().nextBytes(iv);
+
+                    Cipher cipher = Cipher.getInstance(
+                            "DESede/CBC/PKCS5Padding");
+                    KeySpec keyspec = new DESedeKeySpec(keydata);
+                    Key key = SecretKeyFactory.getInstance("DESede")
+                                              .generateSecret(keyspec);
+                    cipher.init(Cipher.ENCRYPT_MODE, key,
+                        new IvParameterSpec(iv, 0, cipher.getBlockSize()));
+
+                    ByteArrayWriter data = new ByteArrayWriter();
+                    baw.writeString(type);
+                    baw.write(iv);
+                    data.writeInt(cookie);
+                    data.writeBinaryString(keyblob);
+
+                    // Encrypt and write
+                    baw.writeBinaryString(cipher.doFinal(data.toByteArray()));
+
+                    return formatKey(baw.toByteArray());
+                }
+            }
+
+            // Write the type of encryption
+            baw.writeString(type);
+
+            // Write the key blob
+            baw.writeBinaryString(keyblob);
+
+            // Now set the keyblob to our new encrpyted (or not) blob
+            return formatKey(baw.toByteArray());
+        } catch (Exception ioe) {
+            return null;
+        }
+    }
+
+    /**
+     *
+     *
+     * @param algorithm
+     *
+     * @return
+     */
+    public boolean supportsAlgorithm(String algorithm) {
+        return true;
+    }
+
+    private byte[] makePassphraseKey(String passphrase) {
+        try {
+            // Generate the key using the passphrase
+            Hash md5 = new Hash("MD5");
+            md5.putBytes(passphrase.getBytes());
+
+            byte[] key1 = md5.doFinal();
+            md5.reset();
+            md5.putBytes(passphrase.getBytes());
+            md5.putBytes(key1);
+
+            byte[] key2 = md5.doFinal();
+            byte[] key = new byte[32];
+            System.arraycopy(key1, 0, key, 0, 16);
+            System.arraycopy(key2, 0, key, 16, 16);
+
+            return key;
+        } catch (NoSuchAlgorithmException nsae) {
+            return null;
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/publickey/dsa/SshDssKeyPair.java b/src/com/sshtools/j2ssh/transport/publickey/dsa/SshDssKeyPair.java
new file mode 100644
index 0000000000000000000000000000000000000000..1362b71fdc9b8fb8d2f793dd981d83de4870e0c7
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/publickey/dsa/SshDssKeyPair.java
@@ -0,0 +1,98 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.publickey.dsa;
+
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+import com.sshtools.j2ssh.transport.publickey.InvalidSshKeyException;
+import com.sshtools.j2ssh.transport.publickey.SshKeyPair;
+import com.sshtools.j2ssh.transport.publickey.SshPrivateKey;
+import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.DSAPublicKey;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.17 $
+ */
+public class SshDssKeyPair extends SshKeyPair {
+    /**
+     *
+     *
+     * @param encoded
+     *
+     * @return
+     *
+     * @throws InvalidSshKeyException
+     */
+    public SshPrivateKey decodePrivateKey(byte[] encoded)
+        throws InvalidSshKeyException {
+        return new SshDssPrivateKey(encoded);
+    }
+
+    /**
+     *
+     *
+     * @param encoded
+     *
+     * @return
+     *
+     * @throws InvalidSshKeyException
+     */
+    public SshPublicKey decodePublicKey(byte[] encoded)
+        throws InvalidSshKeyException {
+        return new SshDssPublicKey(encoded);
+    }
+
+    /**
+     *
+     *
+     * @param bits
+     */
+    public void generate(int bits) {
+        try {
+            // Initialize the generator
+            KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA");
+            keyGen.initialize(bits, ConfigurationLoader.getRND());
+
+            KeyPair pair = keyGen.generateKeyPair();
+
+            // Get the keys
+            DSAPrivateKey prvKey = (DSAPrivateKey) pair.getPrivate();
+            DSAPublicKey pubKey = (DSAPublicKey) pair.getPublic();
+
+            // Set the private key (the public is automatically generated)
+            setPrivateKey(new SshDssPrivateKey(prvKey));
+        } catch (NoSuchAlgorithmException nsae) {
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/publickey/dsa/SshDssPrivateKey.java b/src/com/sshtools/j2ssh/transport/publickey/dsa/SshDssPrivateKey.java
new file mode 100644
index 0000000000000000000000000000000000000000..a81a130034b860010e2666ae4589ff281571953e
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/publickey/dsa/SshDssPrivateKey.java
@@ -0,0 +1,256 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.publickey.dsa;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.publickey.InvalidSshKeyException;
+import com.sshtools.j2ssh.transport.publickey.InvalidSshKeySignatureException;
+import com.sshtools.j2ssh.transport.publickey.SshPrivateKey;
+import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
+import com.sshtools.j2ssh.util.SimpleASNReader;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+
+import java.math.BigInteger;
+
+import java.security.KeyFactory;
+import java.security.Signature;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.DSAPublicKey;
+import java.security.spec.DSAPrivateKeySpec;
+import java.security.spec.DSAPublicKeySpec;
+
+
+class SshDssPrivateKey extends SshPrivateKey {
+    private static Log log = LogFactory.getLog(SshDssPrivateKey.class);
+    DSAPrivateKey prvkey;
+
+    /**
+     * Creates a new SshDssPrivateKey object.
+     *
+     * @param prvkey
+     */
+    public SshDssPrivateKey(DSAPrivateKey prvkey) {
+        this.prvkey = prvkey;
+    }
+
+    /**
+     * Creates a new SshDssPrivateKey object.
+     *
+     * @param key
+     *
+     * @throws InvalidSshKeyException
+     */
+    public SshDssPrivateKey(byte[] key) throws InvalidSshKeyException {
+        try {
+            DSAPrivateKeySpec dsaKey;
+
+            // Extract the key information
+            ByteArrayReader bar = new ByteArrayReader(key);
+            String header = bar.readString();
+
+            if (!header.equals(getAlgorithmName())) {
+                throw new InvalidSshKeyException();
+            }
+
+            BigInteger p = bar.readBigInteger();
+            BigInteger q = bar.readBigInteger();
+            BigInteger g = bar.readBigInteger();
+            BigInteger x = bar.readBigInteger();
+            dsaKey = new DSAPrivateKeySpec(x, p, q, g);
+
+            KeyFactory kf = KeyFactory.getInstance("DSA");
+            prvkey = (DSAPrivateKey) kf.generatePrivate(dsaKey);
+        } catch (Exception e) {
+            throw new InvalidSshKeyException();
+        }
+    }
+
+    /**
+     *
+     *
+     * @param obj
+     *
+     * @return
+     */
+    public boolean equals(Object obj) {
+        if (obj instanceof SshDssPrivateKey) {
+            return prvkey.equals(((SshDssPrivateKey) obj).prvkey);
+        }
+
+        return false;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int hashCode() {
+        return prvkey.hashCode();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getAlgorithmName() {
+        return "ssh-dss";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getBitLength() {
+        return prvkey.getX().bitLength();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getEncoded() {
+        try {
+            ByteArrayWriter baw = new ByteArrayWriter();
+            baw.writeString("ssh-dss");
+            baw.writeBigInteger(prvkey.getParams().getP());
+            baw.writeBigInteger(prvkey.getParams().getQ());
+            baw.writeBigInteger(prvkey.getParams().getG());
+            baw.writeBigInteger(prvkey.getX());
+
+            return baw.toByteArray();
+        } catch (IOException ioe) {
+            return null;
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public SshPublicKey getPublicKey() {
+        try {
+            DSAPublicKeySpec spec = new DSAPublicKeySpec(getY(),
+                    prvkey.getParams().getP(), prvkey.getParams().getQ(),
+                    prvkey.getParams().getG());
+            KeyFactory kf = KeyFactory.getInstance("DSA");
+
+            return new SshDssPublicKey((DSAPublicKey) kf.generatePublic(spec));
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+     *
+     *
+     * @param data
+     *
+     * @return
+     *
+     * @throws InvalidSshKeySignatureException
+     */
+    public byte[] generateSignature(byte[] data)
+        throws InvalidSshKeySignatureException {
+        try {
+            Signature sig = Signature.getInstance("SHA1withDSA");
+            sig.initSign(prvkey);
+
+            /*java.util.Random rnd = new java.util.Random();
+                         byte[] buffer = new byte[20];
+                         rnd.nextBytes(buffer);
+                         sig.update(buffer);
+                         byte[] test = sig.sign();*/
+            sig.update(data);
+
+            byte[] signature = sig.sign();
+            byte[] decoded = new byte[40];
+            SimpleASNReader asn = new SimpleASNReader(signature);
+            asn.getByte();
+            asn.getLength();
+            asn.getByte();
+
+            byte[] r = asn.getData();
+            asn.getByte();
+
+            byte[] s = asn.getData();
+
+            if (r.length >= 20) {
+                System.arraycopy(r, r.length - 20, decoded, 0, 20);
+            } else {
+                System.arraycopy(r, 0, decoded, 20 - r.length, r.length);
+            }
+
+            if (s.length >= 20) {
+                System.arraycopy(s, s.length - 20, decoded, 20, 20);
+            } else {
+                System.arraycopy(s, 0, decoded, 20 + (20 - s.length), s.length);
+            }
+
+            if (log.isDebugEnabled()) {
+                log.debug("s length is " + String.valueOf(s.length));
+                log.debug("r length is " + String.valueOf(r.length));
+
+                String str = "";
+
+                for (int i = 0; i < signature.length; i++) {
+                    str += (Integer.toHexString(signature[i] & 0xFF) + " ");
+                }
+
+                log.debug("Java signature is " + str);
+                str = "";
+
+                for (int i = 0; i < decoded.length; i++) {
+                    str += (Integer.toHexString(decoded[i] & 0xFF) + " ");
+                }
+
+                log.debug("SSH signature is " + str);
+            }
+
+            ByteArrayWriter baw = new ByteArrayWriter();
+            baw.writeString(getAlgorithmName());
+            baw.writeBinaryString(decoded);
+
+            return baw.toByteArray();
+        } catch (Exception e) {
+            throw new InvalidSshKeySignatureException(e);
+        }
+    }
+
+    private BigInteger getY() {
+        return prvkey.getParams().getG().modPow(prvkey.getX(),
+            prvkey.getParams().getP());
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/publickey/dsa/SshDssPublicKey.java b/src/com/sshtools/j2ssh/transport/publickey/dsa/SshDssPublicKey.java
new file mode 100644
index 0000000000000000000000000000000000000000..f8e86ccc7d639020c64a0b64264eb557a17f99d2
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/publickey/dsa/SshDssPublicKey.java
@@ -0,0 +1,262 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.publickey.dsa;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.publickey.InvalidSshKeyException;
+import com.sshtools.j2ssh.transport.publickey.InvalidSshKeySignatureException;
+import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
+import com.sshtools.j2ssh.util.SimpleASNWriter;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import java.math.BigInteger;
+
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.interfaces.DSAPublicKey;
+import java.security.spec.DSAPublicKeySpec;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.27 $
+ */
+public class SshDssPublicKey extends SshPublicKey {
+    private static Log log = LogFactory.getLog(SshDssPublicKey.class);
+    private DSAPublicKey pubkey;
+
+    /**
+     * Creates a new SshDssPublicKey object.
+     *
+     * @param key
+     */
+    public SshDssPublicKey(DSAPublicKey key) {
+        this.pubkey = key;
+    }
+
+    /**
+     * Creates a new SshDssPublicKey object.
+     *
+     * @param key
+     *
+     * @throws InvalidSshKeyException
+     */
+    public SshDssPublicKey(byte[] key) throws InvalidSshKeyException {
+        try {
+            DSAPublicKeySpec dsaKey;
+
+            // Extract the key information
+            ByteArrayReader bar = new ByteArrayReader(key);
+            String header = bar.readString();
+
+            if (!header.equals(getAlgorithmName())) {
+                throw new InvalidSshKeyException();
+            }
+
+            BigInteger p = bar.readBigInteger();
+            BigInteger q = bar.readBigInteger();
+            BigInteger g = bar.readBigInteger();
+            BigInteger y = bar.readBigInteger();
+            dsaKey = new DSAPublicKeySpec(y, p, q, g);
+
+            KeyFactory kf = KeyFactory.getInstance("DSA");
+            pubkey = (DSAPublicKey) kf.generatePublic(dsaKey);
+        } catch (Exception e) {
+            throw new InvalidSshKeyException();
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getAlgorithmName() {
+        return "ssh-dss";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getBitLength() {
+        return pubkey.getY().bitLength();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getEncoded() {
+        try {
+            ByteArrayWriter baw = new ByteArrayWriter();
+            baw.writeString(getAlgorithmName());
+            baw.writeBigInteger(pubkey.getParams().getP());
+            baw.writeBigInteger(pubkey.getParams().getQ());
+            baw.writeBigInteger(pubkey.getParams().getG());
+            baw.writeBigInteger(pubkey.getY());
+
+            return baw.toByteArray();
+        } catch (IOException ioe) {
+            return null;
+        }
+    }
+
+    /**
+     *
+     *
+     * @param signature
+     * @param data
+     *
+     * @return
+     *
+     * @throws InvalidSshKeySignatureException
+     */
+    public boolean verifySignature(byte[] signature, byte[] data)
+        throws InvalidSshKeySignatureException {
+        try {
+            // Check for differing version of the transport protocol
+            if (signature.length != 40) {
+                ByteArrayReader bar = new ByteArrayReader(signature);
+                byte[] sig = bar.readBinaryString();
+
+                //log.debug("Signature blob is " + new String(sig));
+                String header = new String(sig);
+                log.debug("Header is " + header);
+
+                if (!header.equals("ssh-dss")) {
+                    throw new InvalidSshKeySignatureException();
+                }
+
+                signature = bar.readBinaryString();
+
+                //log.debug("Read signature from blob: " + new String(signature));
+            }
+
+            // Using a SimpleASNWriter
+            ByteArrayOutputStream r = new ByteArrayOutputStream();
+            ByteArrayOutputStream s = new ByteArrayOutputStream();
+            SimpleASNWriter asn = new SimpleASNWriter();
+            asn.writeByte(0x02);
+
+            if (((signature[0] & 0x80) == 0x80) && (signature[0] != 0x00)) {
+                r.write(0);
+                r.write(signature, 0, 20);
+            } else {
+                r.write(signature, 0, 20);
+            }
+
+            asn.writeData(r.toByteArray());
+            asn.writeByte(0x02);
+
+            if (((signature[20] & 0x80) == 0x80) && (signature[20] != 0x00)) {
+                s.write(0);
+                s.write(signature, 20, 20);
+            } else {
+                s.write(signature, 20, 20);
+            }
+
+            asn.writeData(s.toByteArray());
+
+            SimpleASNWriter asnEncoded = new SimpleASNWriter();
+            asnEncoded.writeByte(0x30);
+            asnEncoded.writeData(asn.toByteArray());
+
+            byte[] encoded = asnEncoded.toByteArray();
+
+            if (log.isDebugEnabled()) {
+                log.debug("Verifying host key signature");
+                log.debug("Signature length is " +
+                    String.valueOf(signature.length));
+
+                String hex = "";
+
+                for (int i = 0; i < signature.length; i++) {
+                    hex += (Integer.toHexString(signature[i] & 0xFF) + " ");
+                }
+
+                log.debug("SSH: " + hex);
+                hex = "";
+
+                for (int i = 0; i < encoded.length; i++) {
+                    hex += (Integer.toHexString(encoded[i] & 0xFF) + " ");
+                }
+
+                log.debug("Encoded: " + hex);
+            }
+
+            // The previous way
+
+            /*byte[] encoded;
+                         // Determine the encoded length of the big integers
+                         int rlen = (((signature[0] & 0x80) == 0x80) ? 0x15 : 0x14);
+                         log.debug("rlen=" + String.valueOf(rlen));
+                         int slen = (((signature[20] & 0x80) == 0x80) ? 0x15 : 0x14);
+                         log.debug("slen=" + String.valueOf(slen));
+                 byte[] asn1r = { 0x30, (byte) (rlen + slen + 4), 0x02, (byte) rlen };
+                         byte[] asn1s = { 0x02, (byte) slen };
+                         // Create the encoded byte array
+                 encoded = new byte[asn1r.length + rlen + asn1s.length + slen];
+                         // Copy the data and encode it into the array
+                         System.arraycopy(asn1r, 0, encoded, 0, asn1r.length);
+                         // Copy the integer inserting a zero byte if signed
+                         int roffset = (((signature[0] & 0x80) == 0x80) ? 1 : 0);
+                 System.arraycopy(signature, 0, encoded, asn1r.length + roffset, 20);
+                 System.arraycopy(asn1s, 0, encoded, asn1r.length + roffset + 20,
+                asn1s.length);
+                         int soffset = (((signature[20] & 0x80) == 0x80) ? 1 : 0);
+                         System.arraycopy(signature, 20, encoded,
+                asn1r.length + roffset + 20 + asn1s.length + soffset, 20);
+             */
+            Signature sig = Signature.getInstance("SHA1withDSA");
+            sig.initVerify(pubkey);
+            sig.update(data);
+
+            return sig.verify(encoded);
+        } catch (NoSuchAlgorithmException nsae) {
+            throw new InvalidSshKeySignatureException();
+        } catch (InvalidKeyException ike) {
+            throw new InvalidSshKeySignatureException();
+        } catch (IOException ioe) {
+            throw new InvalidSshKeySignatureException();
+        } catch (SignatureException se) {
+            throw new InvalidSshKeySignatureException();
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/publickey/rsa/SshRsaKeyPair.java b/src/com/sshtools/j2ssh/transport/publickey/rsa/SshRsaKeyPair.java
new file mode 100644
index 0000000000000000000000000000000000000000..3df73ea81a7c3ad93de31094a58606d442b96d64
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/publickey/rsa/SshRsaKeyPair.java
@@ -0,0 +1,107 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.publickey.rsa;
+
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+import com.sshtools.j2ssh.transport.publickey.InvalidSshKeyException;
+import com.sshtools.j2ssh.transport.publickey.SshKeyPair;
+import com.sshtools.j2ssh.transport.publickey.SshPrivateKey;
+import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.19 $
+ */
+public class SshRsaKeyPair extends SshKeyPair {
+    private RSAPrivateKey prvKey;
+    private RSAPublicKey pubKey;
+
+    /**
+     * Creates a new SshRsaKeyPair object.
+     */
+    public SshRsaKeyPair() {
+    }
+
+    /**
+     *
+     *
+     * @param encoded
+     *
+     * @return
+     *
+     * @throws InvalidSshKeyException
+     */
+    public SshPrivateKey decodePrivateKey(byte[] encoded)
+        throws InvalidSshKeyException {
+        return new SshRsaPrivateKey(encoded);
+    }
+
+    /**
+     *
+     *
+     * @param encoded
+     *
+     * @return
+     *
+     * @throws InvalidSshKeyException
+     */
+    public SshPublicKey decodePublicKey(byte[] encoded)
+        throws InvalidSshKeyException {
+        return new SshRsaPublicKey(encoded);
+    }
+
+    /**
+     *
+     *
+     * @param bits
+     */
+    public void generate(int bits) {
+        try {
+            // Initialize the generator
+            KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+            keyGen.initialize(bits, ConfigurationLoader.getRND());
+
+            KeyPair pair = keyGen.generateKeyPair();
+
+            // Get the keys and set
+            setPrivateKey(new SshRsaPrivateKey(
+                    (RSAPrivateKey) pair.getPrivate(),
+                    (RSAPublicKey) pair.getPublic()));
+        } catch (NoSuchAlgorithmException nsae) {
+            prvKey = null;
+            pubKey = null;
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/publickey/rsa/SshRsaPrivateKey.java b/src/com/sshtools/j2ssh/transport/publickey/rsa/SshRsaPrivateKey.java
new file mode 100644
index 0000000000000000000000000000000000000000..3ac24a135eb7d04a248effaa2d3c85ae0976b220
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/publickey/rsa/SshRsaPrivateKey.java
@@ -0,0 +1,195 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.publickey.rsa;
+
+import com.sshtools.j2ssh.io.ByteArrayReader;
+import com.sshtools.j2ssh.io.ByteArrayWriter;
+import com.sshtools.j2ssh.transport.publickey.InvalidSshKeyException;
+import com.sshtools.j2ssh.transport.publickey.SshPrivateKey;
+import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
+
+import java.io.IOException;
+
+import java.math.BigInteger;
+
+import java.security.KeyFactory;
+import java.security.Signature;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.RSAPrivateKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.19 $
+ */
+public class SshRsaPrivateKey extends SshPrivateKey {
+    RSAPrivateKey prvKey;
+    RSAPublicKey pubKey;
+
+    /**
+     * Creates a new SshRsaPrivateKey object.
+     *
+     * @param prv
+     * @param pub
+     */
+    public SshRsaPrivateKey(RSAPrivateKey prv, RSAPublicKey pub) {
+        prvKey = prv;
+        pubKey = pub;
+    }
+
+    /**
+     * Creates a new SshRsaPrivateKey object.
+     *
+     * @param encoded
+     *
+     * @throws InvalidSshKeyException
+     */
+    public SshRsaPrivateKey(byte[] encoded) throws InvalidSshKeyException {
+        try {
+            // Extract the key information
+            ByteArrayReader bar = new ByteArrayReader(encoded);
+
+            // Read the public key
+            String header = bar.readString();
+
+            if (!header.equals(getAlgorithmName())) {
+                throw new InvalidSshKeyException();
+            }
+
+            BigInteger e = bar.readBigInteger();
+            BigInteger n = bar.readBigInteger();
+
+            // Read the private key
+            BigInteger p = bar.readBigInteger();
+            RSAPrivateKeySpec prvSpec = new RSAPrivateKeySpec(n, p);
+            RSAPublicKeySpec pubSpec = new RSAPublicKeySpec(n, e);
+            KeyFactory kf = KeyFactory.getInstance("RSA");
+            prvKey = (RSAPrivateKey) kf.generatePrivate(prvSpec);
+            pubKey = (RSAPublicKey) kf.generatePublic(pubSpec);
+        } catch (Exception e) {
+            throw new InvalidSshKeyException();
+        }
+    }
+
+    /**
+     *
+     *
+     * @param obj
+     *
+     * @return
+     */
+    public boolean equals(Object obj) {
+        if (obj instanceof SshRsaPrivateKey) {
+            return prvKey.equals(((SshRsaPrivateKey) obj).prvKey);
+        }
+
+        return false;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int hashCode() {
+        return prvKey.hashCode();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getAlgorithmName() {
+        return "ssh-rsa";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getBitLength() {
+        return prvKey.getModulus().bitLength();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getEncoded() {
+        try {
+            ByteArrayWriter baw = new ByteArrayWriter();
+
+            // The private key consists of the public key blob
+            baw.write(getPublicKey().getEncoded());
+
+            // And the private data
+            baw.writeBigInteger(prvKey.getPrivateExponent());
+
+            return baw.toByteArray();
+        } catch (IOException ioe) {
+            return null;
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public SshPublicKey getPublicKey() {
+        return new SshRsaPublicKey(pubKey);
+    }
+
+    /**
+     *
+     *
+     * @param data
+     *
+     * @return
+     */
+    public byte[] generateSignature(byte[] data) {
+        try {
+            Signature sig = Signature.getInstance("SHA1withRSA");
+            sig.initSign(prvKey);
+            sig.update(data);
+
+            ByteArrayWriter baw = new ByteArrayWriter();
+            baw.writeString(getAlgorithmName());
+            baw.writeBinaryString(sig.sign());
+
+            return baw.toByteArray();
+        } catch (Exception e) {
+            return null;
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/transport/publickey/rsa/SshRsaPublicKey.java b/src/com/sshtools/j2ssh/transport/publickey/rsa/SshRsaPublicKey.java
new file mode 100644
index 0000000000000000000000000000000000000000..5eb7a302e1599dc35889b07cb86979d6216fdf9d
--- /dev/null
+++ b/src/com/sshtools/j2ssh/transport/publickey/rsa/SshRsaPublicKey.java
@@ -0,0 +1,172 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.transport.publickey.rsa;
+
+import com.sshtools.j2ssh.io.*;
+import com.sshtools.j2ssh.transport.publickey.*;
+
+import java.io.*;
+
+import java.math.*;
+
+import java.security.*;
+import java.security.interfaces.*;
+import java.security.spec.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.19 $
+ */
+public class SshRsaPublicKey extends SshPublicKey {
+    RSAPublicKey pubKey;
+
+    /**
+     * Creates a new SshRsaPublicKey object.
+     *
+     * @param key
+     */
+    public SshRsaPublicKey(RSAPublicKey key) {
+        pubKey = key;
+    }
+
+    /**
+     * Creates a new SshRsaPublicKey object.
+     *
+     * @param encoded
+     *
+     * @throws InvalidSshKeyException
+     */
+    public SshRsaPublicKey(byte[] encoded) throws InvalidSshKeyException {
+        try {
+            //this.hostKey = hostKey;
+            RSAPublicKeySpec rsaKey;
+
+            // Extract the key information
+            ByteArrayReader bar = new ByteArrayReader(encoded);
+            String header = bar.readString();
+
+            if (!header.equals(getAlgorithmName())) {
+                throw new InvalidSshKeyException();
+            }
+
+            BigInteger e = bar.readBigInteger();
+            BigInteger n = bar.readBigInteger();
+            rsaKey = new RSAPublicKeySpec(n, e);
+
+            try {
+                KeyFactory kf = KeyFactory.getInstance("RSA");
+                pubKey = (RSAPublicKey) kf.generatePublic(rsaKey);
+            } catch (NoSuchAlgorithmException nsae) {
+                throw new InvalidSshKeyException();
+            } catch (InvalidKeySpecException ikpe) {
+                throw new InvalidSshKeyException();
+            }
+        } catch (IOException ioe) {
+            throw new InvalidSshKeyException();
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String getAlgorithmName() {
+        return "ssh-rsa";
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getBitLength() {
+        return pubKey.getModulus().bitLength();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getEncoded() {
+        try {
+            ByteArrayWriter baw = new ByteArrayWriter();
+            baw.writeString(getAlgorithmName());
+            baw.writeBigInteger(pubKey.getPublicExponent());
+            baw.writeBigInteger(pubKey.getModulus());
+
+            return baw.toByteArray();
+        } catch (IOException ioe) {
+            return null;
+        }
+    }
+
+    /**
+     *
+     *
+     * @param signature
+     * @param data
+     *
+     * @return
+     *
+     * @throws InvalidSshKeySignatureException
+     */
+    public boolean verifySignature(byte[] signature, byte[] data)
+        throws InvalidSshKeySignatureException {
+        try {
+            // Check for older versions of the transport protocol
+            if (signature.length != 128) {
+                ByteArrayReader bar = new ByteArrayReader(signature);
+                byte[] sig = bar.readBinaryString();
+                String header = new String(sig);
+
+                if (!header.equals(getAlgorithmName())) {
+                    throw new InvalidSshKeySignatureException();
+                }
+
+                signature = bar.readBinaryString();
+            }
+
+            Signature s = Signature.getInstance("SHA1withRSA");
+            s.initVerify(pubKey);
+            s.update(data);
+
+            return s.verify(signature);
+        } catch (NoSuchAlgorithmException nsae) {
+            throw new InvalidSshKeySignatureException();
+        } catch (IOException ioe) {
+            throw new InvalidSshKeySignatureException();
+        } catch (InvalidKeyException ike) {
+            throw new InvalidSshKeySignatureException();
+        } catch (SignatureException se) {
+            throw new InvalidSshKeySignatureException();
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/util/Base64.java b/src/com/sshtools/j2ssh/util/Base64.java
new file mode 100644
index 0000000000000000000000000000000000000000..51f4ece1a8bfdc5feecf0d34de8f2bf41812493d
--- /dev/null
+++ b/src/com/sshtools/j2ssh/util/Base64.java
@@ -0,0 +1,693 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.util;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class Base64 {
+    /**  */
+    public final static boolean ENCODE = true;
+
+    /**  */
+    public final static boolean DECODE = false;
+    private final static int MAX_LINE_LENGTH = 76;
+    private final static byte EQUALS_SIGN = (byte) '=';
+    private final static byte NEW_LINE = (byte) '\n';
+    private final static byte[] ALPHABET = {
+        (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F',
+        (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L',
+        (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R',
+        (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X',
+        (byte) 'Y', (byte) 'Z', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd',
+        (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
+        (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p',
+        (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', (byte) 'v',
+        (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z', (byte) '0', (byte) '1',
+        (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7',
+        (byte) '8', (byte) '9', (byte) '+', (byte) '/'
+    };
+    private final static byte[] DECODABET = {
+        -9, -9, -9, -9, -9, -9, -9, -9, -9, -5, -5, -9, -9, -5, -9, -9, -9, -9,
+        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -5, -9, -9, -9,
+        -9, -9, -9, -9, -9, -9, -9, 
+        // Decimal 33 - 42
+        62, -9, -9, -9, 
+        // Decimal 44 - 46
+        63, 
+        // Slash at decimal 47
+        52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -9, -9, -9, -1, -9, -9, -9, 
+        // Decimal 62 - 64
+        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 
+        // Letters 'A' through 'N'
+        14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -9, -9, -9, -9, -9, -9,
+        
+        // Decimal 91 - 96
+        26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 
+        // Letters 'a' through 'm'
+        39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -9, -9, -9, -9
+    };
+    private final static byte BAD_ENCODING = -9;
+
+    // Indicates error in encoding
+    private final static byte white_SPACE_ENC = -5;
+
+    // Indicates white space in encoding
+    private final static byte EQUALS_SIGN_ENC = -1;
+
+    // Indicates equals sign in encoding
+    private Base64() {
+    }
+
+    /**
+     *
+     *
+     * @param s
+     *
+     * @return
+     */
+    public static byte[] decode(String s) {
+        byte[] bytes = s.getBytes();
+
+        return decode(bytes, 0, bytes.length);
+    }
+
+    // end decode
+    public static byte[] decode(byte[] source, int off, int len) {
+        int len34 = (len * 3) / 4;
+        byte[] outBuff = new byte[len34];
+
+        // Upper limit on size of output
+        int outBuffPosn = 0;
+        byte[] b4 = new byte[4];
+        int b4Posn = 0;
+        int i = 0;
+        byte sbiCrop = 0;
+        byte sbiDecode = 0;
+
+        for (i = 0; i < len; i++) {
+            sbiCrop = (byte) (source[i] & 0x7f);
+
+            // Only the low seven bits
+            sbiDecode = DECODABET[sbiCrop];
+
+            if (sbiDecode >= white_SPACE_ENC) {
+                // White space, Equals sign or better
+                if (sbiDecode >= EQUALS_SIGN_ENC) {
+                    b4[b4Posn++] = sbiCrop;
+
+                    if (b4Posn > 3) {
+                        outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn);
+                        b4Posn = 0;
+
+                        // If that was the equals sign, break out of 'for' loop
+                        if (sbiCrop == EQUALS_SIGN) {
+                            break;
+                        }
+                    }
+
+                    // end if: quartet built
+                }
+
+                // end if: equals sign or better
+            }
+            // end if: white space, equals sign or better
+            else {
+                System.err.println("Bad Base64 input character at " + i + ": " +
+                    source[i] + "(decimal)");
+
+                return null;
+            }
+
+            // end else:
+        }
+
+        // each input character
+        byte[] out = new byte[outBuffPosn];
+        System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
+
+        return out;
+    }
+
+    // end decode
+    public static Object decodeToObject(String encodedObject) {
+        byte[] objBytes = decode(encodedObject);
+        java.io.ByteArrayInputStream bais = null;
+        java.io.ObjectInputStream ois = null;
+
+        try {
+            bais = new java.io.ByteArrayInputStream(objBytes);
+            ois = new java.io.ObjectInputStream(bais);
+
+            return ois.readObject();
+        }
+        // end try
+         catch (java.io.IOException e) {
+            e.printStackTrace();
+
+            return null;
+        }
+        // end catch
+         catch (java.lang.ClassNotFoundException e) {
+            e.printStackTrace();
+
+            return null;
+        }
+        // end catch
+         finally {
+            try {
+                bais.close();
+            } catch (Exception e) {
+            }
+
+            try {
+                ois.close();
+            } catch (Exception e) {
+            }
+        }
+
+        // end finally
+    }
+
+    // end decodeObject
+    public static String decodeToString(String s) {
+        return new String(decode(s));
+    }
+
+    // end decodeToString
+    public static String encodeBytes(byte[] source, boolean ignoreMaxLineLength) {
+        return encodeBytes(source, 0, source.length, ignoreMaxLineLength);
+    }
+
+    // end encodeBytes
+    public static String encodeBytes(byte[] source, int off, int len,
+        boolean ignoreMaxLineLength) {
+        int len43 = (len * 4) / 3;
+        byte[] outBuff = new byte[(len43) + (((len % 3) > 0) ? 4 : 0) +
+            (len43 / MAX_LINE_LENGTH)];
+
+        // New lines
+        int d = 0;
+        int e = 0;
+        int len2 = len - 2;
+        int lineLength = 0;
+
+        for (; d < len2; d += 3, e += 4) {
+            encode3to4(source, d + off, 3, outBuff, e);
+            lineLength += 4;
+
+            if (!ignoreMaxLineLength) {
+                if (lineLength == MAX_LINE_LENGTH) {
+                    outBuff[e + 4] = NEW_LINE;
+                    e++;
+                    lineLength = 0;
+                }
+
+                // end if: end of line
+            }
+        }
+
+        // en dfor: each piece of array
+        if (d < len) {
+            encode3to4(source, d + off, len - d, outBuff, e);
+            e += 4;
+        }
+
+        // end if: some padding needed
+        return new String(outBuff, 0, e);
+    }
+
+    // end encodeBytes
+    public static String encodeObject(java.io.Serializable serializableObject) {
+        java.io.ByteArrayOutputStream baos = null;
+        java.io.OutputStream b64os = null;
+        java.io.ObjectOutputStream oos = null;
+
+        try {
+            baos = new java.io.ByteArrayOutputStream();
+            b64os = new Base64.OutputStream(baos, Base64.ENCODE);
+            oos = new java.io.ObjectOutputStream(b64os);
+            oos.writeObject(serializableObject);
+        }
+        // end try
+         catch (java.io.IOException e) {
+            e.printStackTrace();
+
+            return null;
+        }
+        // end catch
+         finally {
+            try {
+                oos.close();
+            } catch (Exception e) {
+            }
+
+            try {
+                b64os.close();
+            } catch (Exception e) {
+            }
+
+            try {
+                baos.close();
+            } catch (Exception e) {
+            }
+        }
+
+        // end finally
+        return new String(baos.toByteArray());
+    }
+
+    // end encode
+    public static String encodeString(String s, boolean ignoreMaxLineLength) {
+        return encodeBytes(s.getBytes(), ignoreMaxLineLength);
+    }
+
+    // end encodeString
+    public static void main(String[] args) {
+        String s = "P2/56wAAAgoAAAAmZGwtbW9kcHtzaWdue2RzYS1uaXN0LXNoYTF9LGRoe3Bs" +
+            "YWlufX0AAAAIM2Rlcy1jYmMAAAHIifTc7/X/swWj4OHVWX9RsUxWh4citAMwGzv6X9mUG6a" +
+            "mh5/2f6IiQ3lOeHFd5J0EAOeGNuLqE/RWJ/fFaZAzD6YTr1GZ5hflMzvRu3jbgZoLRz2TaT" +
+            "qeRs1yWrQoqANE2nBx6uDNrRahduqalLg2P/ezRCLGpqbw3HFgXmiZvzhd/rdEgZur7ZPnm" +
+            "EK7t4Ldypk/7xcK192JTbBXLDSKOEAqfYQb9CzW8MgEXde0DpMRZ9Fgm0KWPfz4CCJ0F9dd" +
+            "zcWl1nuGibL3klLKQANgecTurFlrxkBaHgxgl9nIvf24wH3nscvmD/uFOzacT/LzFaD03HFj" +
+            "/QHCiTezxVyyuJ39d3e6BBegV26vEFoGbrZ2mMf08C2MBmLmZELYdBRJ4kLpT5EZkzR8L4rT" +
+            "GxNiWkb4dGT42gHH41p2ad053lctyFWp/uQJnvJEiEm3BMURVY7k1S7zgv2FHgHE0LssXvBHx" +
+            "n/wnft0ne2NOqEXfs/Y4I39Nd7eDIupSVy/ZFfMmNPIhzKyC5lFMkjIMxPXNk548ZoP9Tnga" +
+            "4NPhHNKtcMinVvO2HT6dnIKMNb/NuXooULHIMVISpslRzXiVlTcN9vL/jhJhn9S";
+        byte[] buf = Base64.decode(s);
+        System.out.println(new String(buf).toString());
+        s = "P2/56wAAAgIAAAAmZGwtbW9kcHtzaWdue2RzYS1uaXN0LXNoYTF9LGRoe3BsYWlufX0A" +
+            "AAAEbm9uZQAAAcQAAAHAAAAAAAAABACwV9/fUska/ZHWa4YzXIPFJ4RbcFqPVV0vmyWkFekw" +
+            "wKE1mA0GVDRq9X0HTjA2DZWcQ46suVP/8mLwpnjTKNiRdFvXWGkxEpavLp+bjPa/NXsEsjZL" +
+            "aeO5iPZ11Xw5lx7uor8q/Ewwo9IcYOXzuOWN1EPCpdRv5OOaO3PCMq6QSQAAA/9j/IrSUDtf" +
+            "BLBlzPHBrzboKjIMXv9O8CIRtSqnV7GV9wllgh3Cm+Eh+rd5CB698JGQD9tMdBn4s8bj/BDL" +
+            "4qsxbQbAsZOIin6fqDKNLDxFo357eXM06I5569PgC6cuBoJXOyQTg+sLrjT8/b3/1N4TjdZN" +
+            "JiKiSiuOzkn03tNSbgAAAKDJhI2ZNNvzOXhp+VFuoY//D9GvHwAAA/9NkAROm4wF7NCPsBXd" +
+            "/+QfNV3NM/FSpOonZZDg2AVnCCGdLOXCWEj60EVHWEf5FbOjJ1KynbbdZA6q5JtVDIYuU9wH" +
+            "BCsT5iCexGD5j2HYNcUXT4VG5a6qzqloR2JizlZOcjiEM2j0/hydFUei0VYmJNY5L//AprO6" +
+            "1UJL2OGFEQAAAJ0Ts+KlcAYmJjJWODOG3mYuiTgv7A==";
+        buf = Base64.decode(s);
+        System.out.println(new String(buf).toString());
+        buf = Base64.decode(
+                "P2/56wAAAi4AAAA3aWYtbW9kbntzaWdue3JzYS1wa2NzMS1zaGExfSxlbmNyeXB0e3JzYS1wa2NzMXYyLW9hZXB9fQAAAARub25lAAAB3wAAAdsAAAARAQABAAAD/iJ48YNIqLobasqkyRAD6Ejzhe0bK0Nd12iq0X9xG7M5xyVJns5SH3oAPwsa/V63omsQnm/ERG5lCnFGymTSCTpz0jGYLAh81S4XGbZEJRltP75LiM4J1OIfQkF7Zxd/mYFAYpu50fOLTrk+EwOCyQJK63uXzxQHCU1JKzt61m05AAAEALhvA6F1Ffhf/HLPKe3mp/CdTYQyioHzdL2ur6jyvh+b5wb8WuiaL+xu08vA7/Q763M/TXLX3jMWKOfV3HFn656hBCjnePwXp+uJNIQ4+oxg5H7nr8yo2Tc3Umt9fzgajoLDSd488iozmlSgKeRoVy7hKAGuveGtFqqruNAYArNfAAACALXcpb2stcqNdyTGUPIK/uUBkEeEJGgomqFPZbkMNHqZqEPLa7cJdHIl6wiol3ziQKvvUm/8ya4y7tR9Mzay/cIAAAIAwU2/rquz6oQ1GVJVRsO47Ibes2Hcl8tZRC9cBDy5vIPhzPhsD3pxxXnc1gEUybWqkuO6q1XilE/qN/eAKFSDuQAAAgD0QMX768Ucuv2Eu/ZVkebKBBV7jo4seZyd+hKloFotU4mReU7kNq+oYG19pL07n1TN4SodVoykXPSLBowCKCvX");
+        System.out.println(new String(buf).toString());
+        buf = Base64.decode(
+                "P2/56wAAAjsAAAA3aWYtbW9kbntzaWdue3JzYS1wa2NzMS1zaGExfSxlbmNyeXB0e3JzYS1wa2NzMXYyLW9hZXB9fQAAAAgzZGVzLWNiYwAAAegYJSJacx5dZo5rvtyJEp5qFyBXDOkcGH/H4/dJuny1cWnP5eXOaYt1hwc6ZEUIq4bUISGuXSzmRb+mpXZdkAPPt2RLhy66FnwnERnbItyWsNHrMxT5/oug/TW1l+rh0m/46edQhkla+qpgt3ZCJfBRzwihKAAeQJIt18e7XmvVT5g14Xu5fulXPfKT/cPu6Ox1pwRrOTv2ooM8alM2+K+5uCaP9C3qhEhFcyZOsKoigJt8oIZJD7TBrb2adVfzjyNWXZLw5Lq+liWmGTePvf9Mkx+MgFAyIOT4gV391+Rit8ZjSQaJ5jtsSaqw/MgqtTCWz6aXAaLnxP579a+tVubfVQrGLAa6ztGjI/0DmzEH+OvOLfXljeaEPKXhOxTf2O7Pwn8MDBStJHPXPLZZnsoUyTCajnzxw/ohqxOtgE9nqqO1QFVF6Cd74yZlhQSScRKkBcUlqcenxtruEOvvZXgAc8T5UtfvF8AooI22zltyKZDFJx3vJD6TEoFQSq4zu8H4Eipr42HPpUvIFuVAJFlZepI/RVirsU6sDjh8do0vj9ZGdhdBaD8kR7lrPHAJkmROHljJhEI97YWUJZNXS9i63gVvplsi9/x6uEWjn8eNu08IXID82X+LbvEdmTWOhuaSIqyNjyVe7g==");
+        System.out.println(new String(buf).toString());
+    }
+
+    /*
+     *  ********  D E C O D I N G   M E T H O D S  ********
+     */
+    private static byte[] decode4to3(byte[] fourBytes) {
+        byte[] outBuff1 = new byte[3];
+        int count = decode4to3(fourBytes, 0, outBuff1, 0);
+        byte[] outBuff2 = new byte[count];
+
+        for (int i = 0; i < count; i++) {
+            outBuff2[i] = outBuff1[i];
+        }
+
+        return outBuff2;
+    }
+
+    private static int decode4to3(byte[] source, int srcOffset,
+        byte[] destination, int destOffset) {
+        // Example: Dk==
+        if (source[srcOffset + 2] == EQUALS_SIGN) {
+            int outBuff = ((DECODABET[source[srcOffset]] << 24) >>> 6) |
+                ((DECODABET[source[srcOffset + 1]] << 24) >>> 12);
+            destination[destOffset] = (byte) (outBuff >>> 16);
+
+            return 1;
+        }
+        // Example: DkL=
+        else if (source[srcOffset + 3] == EQUALS_SIGN) {
+            int outBuff = ((DECODABET[source[srcOffset]] << 24) >>> 6) |
+                ((DECODABET[source[srcOffset + 1]] << 24) >>> 12) |
+                ((DECODABET[source[srcOffset + 2]] << 24) >>> 18);
+            destination[destOffset] = (byte) (outBuff >>> 16);
+            destination[destOffset + 1] = (byte) (outBuff >>> 8);
+
+            return 2;
+        }
+        // Example: DkLE
+        else {
+            int outBuff = ((DECODABET[source[srcOffset]] << 24) >>> 6) |
+                ((DECODABET[source[srcOffset + 1]] << 24) >>> 12) |
+                ((DECODABET[source[srcOffset + 2]] << 24) >>> 18) |
+                ((DECODABET[source[srcOffset + 3]] << 24) >>> 24);
+            destination[destOffset] = (byte) (outBuff >> 16);
+            destination[destOffset + 1] = (byte) (outBuff >> 8);
+            destination[destOffset + 2] = (byte) (outBuff);
+
+            return 3;
+        }
+    }
+
+    // end decodeToBytes
+
+    /*
+     *  ********  E N C O D I N G   M E T H O D S  ********
+     */
+    private static byte[] encode3to4(byte[] threeBytes) {
+        return encode3to4(threeBytes, 3);
+    }
+
+    // end encodeToBytes
+    private static byte[] encode3to4(byte[] threeBytes, int numSigBytes) {
+        byte[] dest = new byte[4];
+        encode3to4(threeBytes, 0, numSigBytes, dest, 0);
+
+        return dest;
+    }
+
+    private static byte[] encode3to4(byte[] source, int srcOffset,
+        int numSigBytes, byte[] destination, int destOffset) {
+        //           1         2         3
+        // 01234567890123456789012345678901 Bit position
+        // --------000000001111111122222222 Array position from threeBytes
+        // --------|    ||    ||    ||    | Six bit groups to index ALPHABET
+        //          >>18  >>12  >> 6  >> 0  Right shift necessary
+        //                0x3f  0x3f  0x3f  Additional AND
+        // Create buffer with zero-padding if there are only one or two
+        // significant bytes passed in the array.
+        // We have to shift left 24 in order to flush out the 1's that appear
+        // when Java treats a value as negative that is cast from a byte to an int.
+        int inBuff = ((numSigBytes > 0) ? ((source[srcOffset] << 24) >>> 8) : 0) |
+            ((numSigBytes > 1) ? ((source[srcOffset + 1] << 24) >>> 16) : 0) |
+            ((numSigBytes > 2) ? ((source[srcOffset + 2] << 24) >>> 24) : 0);
+
+        switch (numSigBytes) {
+        case 3:
+            destination[destOffset] = ALPHABET[(inBuff >>> 18)];
+            destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
+            destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f];
+            destination[destOffset + 3] = ALPHABET[(inBuff) & 0x3f];
+
+            return destination;
+
+        case 2:
+            destination[destOffset] = ALPHABET[(inBuff >>> 18)];
+            destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
+            destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f];
+            destination[destOffset + 3] = EQUALS_SIGN;
+
+            return destination;
+
+        case 1:
+            destination[destOffset] = ALPHABET[(inBuff >>> 18)];
+            destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
+            destination[destOffset + 2] = EQUALS_SIGN;
+            destination[destOffset + 3] = EQUALS_SIGN;
+
+            return destination;
+
+        default:
+            return destination;
+        }
+
+        // end switch
+    }
+
+    // end encode3to4
+
+    /*
+     *  ********  I N N E R   C L A S S   I N P U T S T R E A M  ********
+     */
+    public static class InputStream extends java.io.FilterInputStream {
+        private byte[] buffer;
+
+        // Small buffer holding converted data
+        private boolean encode;
+
+        // Encoding or decoding
+        private int bufferLength;
+
+        // Length of buffer (3 or 4)
+        private int numSigBytes;
+
+        // Number of meaningful bytes in the buffer
+        private int position;
+
+        // Current position in the buffer
+        public InputStream(java.io.InputStream in) {
+            this(in, Base64.DECODE);
+        }
+
+        // end constructor
+        public InputStream(java.io.InputStream in, boolean encode) {
+            super(in);
+            this.encode = encode;
+            this.bufferLength = encode ? 4 : 3;
+            this.buffer = new byte[bufferLength];
+            this.position = -1;
+        }
+
+        // end constructor
+        public int read() throws java.io.IOException {
+            // Do we need to get data?
+            if (position < 0) {
+                if (encode) {
+                    byte[] b3 = new byte[3];
+                    numSigBytes = 0;
+
+                    for (int i = 0; i < 3; i++) {
+                        try {
+                            int b = in.read();
+
+                            // If end of stream, b is -1.
+                            if (b >= 0) {
+                                b3[i] = (byte) b;
+                                numSigBytes++;
+                            }
+
+                            // end if: not end of stream
+                        }
+                        // end try: read
+                         catch (java.io.IOException e) {
+                            // Only a problem if we got no data at all.
+                            if (i == 0) {
+                                throw e;
+                            }
+                        }
+
+                        // end catch
+                    }
+
+                    // end for: each needed input byte
+                    if (numSigBytes > 0) {
+                        encode3to4(b3, 0, numSigBytes, buffer, 0);
+                        position = 0;
+                    }
+
+                    // end if: got data
+                }
+                // end if: encoding
+                // Else decoding
+                else {
+                    byte[] b4 = new byte[4];
+                    int i = 0;
+
+                    for (i = 0; i < 4; i++) {
+                        int b = 0;
+
+                        do {
+                            b = in.read();
+                        } while ((b >= 0) &&
+                                (DECODABET[b & 0x7f] < white_SPACE_ENC));
+
+                        if (b < 0) {
+                            break;
+
+                            // Reads a -1 if end of stream
+                        }
+
+                        b4[i] = (byte) b;
+                    }
+
+                    // end for: each needed input byte
+                    if (i == 4) {
+                        numSigBytes = decode4to3(b4, 0, buffer, 0);
+                        position = 0;
+                    }
+
+                    // end if: got four characters
+                }
+
+                // end else: decode
+            }
+
+            // end else: get data
+            // Got data?
+            if (position >= 0) {
+                // End of relevant data?
+                if (!encode && (position >= numSigBytes)) {
+                    return -1;
+                }
+
+                int b = buffer[position++];
+
+                if (position >= bufferLength) {
+                    position = -1;
+                }
+
+                return b;
+            }
+            // end if: position >= 0
+            // Else error
+            else {
+                return -1;
+            }
+        }
+
+        // end read
+        public int read(byte[] dest, int off, int len)
+            throws java.io.IOException {
+            int i;
+            int b;
+
+            for (i = 0; i < len; i++) {
+                b = read();
+
+                if (b < 0) {
+                    return -1;
+                }
+
+                dest[off + i] = (byte) b;
+            }
+
+            // end for: each byte read
+            return i;
+        }
+
+        // end read
+    }
+
+    // end inner class InputStream
+
+    /*
+     *  ********  I N N E R   C L A S S   O U T P U T S T R E A M  ********
+     */
+    public static class OutputStream extends java.io.FilterOutputStream {
+        private byte[] buffer;
+        private boolean encode;
+        private int bufferLength;
+        private int lineLength;
+        private int position;
+
+        public OutputStream(java.io.OutputStream out) {
+            this(out, Base64.ENCODE);
+        }
+
+        // end constructor
+        public OutputStream(java.io.OutputStream out, boolean encode) {
+            super(out);
+            this.encode = encode;
+            this.bufferLength = encode ? 3 : 4;
+            this.buffer = new byte[bufferLength];
+            this.position = 0;
+            this.lineLength = 0;
+        }
+
+        // end constructor
+        public void close() throws java.io.IOException {
+            this.flush();
+            super.close();
+            out.close();
+            buffer = null;
+            out = null;
+        }
+
+        // end close
+        public void flush() throws java.io.IOException {
+            if (position > 0) {
+                if (encode) {
+                    out.write(Base64.encode3to4(buffer, position));
+                }
+                // end if: encoding
+                else {
+                    throw new java.io.IOException(
+                        "Base64 input not properly padded.");
+                }
+
+                // end else: decoding
+            }
+
+            // end if: buffer partially full
+            super.flush();
+            out.flush();
+        }
+
+        // end flush
+        public void write(int theByte) throws java.io.IOException {
+            buffer[position++] = (byte) theByte;
+
+            if (position >= bufferLength) {
+                if (encode) {
+                    out.write(Base64.encode3to4(buffer, bufferLength));
+                    lineLength += 4;
+
+                    if (lineLength >= MAX_LINE_LENGTH) {
+                        out.write(NEW_LINE);
+                        lineLength = 0;
+                    }
+
+                    // end if: end o fline
+                }
+                // end if: encoding
+                else {
+                    out.write(Base64.decode4to3(buffer));
+                }
+
+                position = 0;
+            }
+
+            // end if: convert and flush
+        }
+
+        // end write
+        public void write(byte[] theBytes, int off, int len)
+            throws java.io.IOException {
+            for (int i = 0; i < len; i++) {
+                write(theBytes[off + i]);
+            }
+
+            // end for: each byte written
+        }
+
+        // end write
+    }
+
+    // end inner class OutputStream
+}
+
+
+// end class Base64
diff --git a/src/com/sshtools/j2ssh/util/DynamicClassLoader.java b/src/com/sshtools/j2ssh/util/DynamicClassLoader.java
new file mode 100644
index 0000000000000000000000000000000000000000..0a0580da04a0f51107d07f320399263e47138cc3
--- /dev/null
+++ b/src/com/sshtools/j2ssh/util/DynamicClassLoader.java
@@ -0,0 +1,533 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.util;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import java.net.URL;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class DynamicClassLoader extends ClassLoader {
+    private static Log log = LogFactory.getLog(DynamicClassLoader.class);
+    private static int generationCounter = 0;
+    private Hashtable cache;
+    private List classpath = new Vector();
+    private int generation;
+    private ClassLoader parent;
+
+    /**
+     * Creates a new DynamicClassLoader object.
+     *
+     * @param parent
+     * @param classpath
+     *
+     * @throws IllegalArgumentException
+     */
+    public DynamicClassLoader(ClassLoader parent, List classpath)
+        throws IllegalArgumentException {
+        this.parent = parent;
+
+        // Create the cache to hold the loaded classes
+        cache = new Hashtable();
+
+        Iterator it = classpath.iterator();
+
+        while (it.hasNext()) {
+            Object obj = it.next();
+            File f = null;
+
+            if (obj instanceof String) {
+                f = new File((String) obj);
+            } else if (obj instanceof File) {
+                f = (File) obj;
+            } else {
+                throw new IllegalArgumentException(
+                    "Entries in classpath must be either a String or File object");
+            }
+
+            if (!f.exists()) {
+                throw new IllegalArgumentException("Classpath " +
+                    f.getAbsolutePath() + " doesn't exist!");
+            } else if (!f.canRead()) {
+                throw new IllegalArgumentException(
+                    "Don't have read access for file " + f.getAbsolutePath());
+            }
+
+            // Check that it is a directory or jar file
+            if (!(f.isDirectory() || isJarArchive(f))) {
+                throw new IllegalArgumentException(f.getAbsolutePath() +
+                    " is not a directory or jar file" +
+                    " or if it's a jar file then it is corrupted.");
+            }
+
+            this.classpath.add(f);
+        }
+
+        // Increment and store generation counter
+        this.generation = generationCounter++;
+    }
+
+    /**
+     *
+     *
+     * @param name
+     *
+     * @return
+     */
+    public URL getResource(String name) {
+        URL u = getSystemResource(name);
+
+        if (u != null) {
+            return u;
+        }
+
+        // Load for it only in directories since no URL can point into
+        // a zip file.
+        Iterator it = classpath.iterator();
+
+        while (it.hasNext()) {
+            File file = (File) it.next();
+
+            if (file.isDirectory()) {
+                String fileName = name.replace('/', File.separatorChar);
+                File resFile = new File(file, fileName);
+
+                if (resFile.exists()) {
+                    // Build a file:// URL form the file name
+                    try {
+                        return new URL("file://" + resFile.getAbsolutePath());
+                    } catch (java.net.MalformedURLException badurl) {
+                        badurl.printStackTrace();
+
+                        return null;
+                    }
+                }
+            }
+        }
+
+        // Not found
+        return null;
+    }
+
+    /**
+     *
+     *
+     * @param name
+     *
+     * @return
+     */
+    public InputStream getResourceAsStream(String name) {
+        // Try to load it from the system class
+        InputStream s = getSystemResourceAsStream(name);
+
+        if (s == null) {
+            // Try to find it from every classpath
+            Iterator it = classpath.iterator();
+
+            while (it.hasNext()) {
+                File file = (File) it.next();
+
+                if (file.isDirectory()) {
+                    s = loadResourceFromDirectory(file, name);
+                } else {
+                    s = loadResourceFromZipfile(file, name);
+                }
+
+                if (s != null) {
+                    break;
+                }
+            }
+        }
+
+        return s;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public DynamicClassLoader reinstantiate() {
+        return new DynamicClassLoader(parent, classpath);
+    }
+
+    /**
+     *
+     *
+     * @param classname
+     *
+     * @return
+     */
+    public synchronized boolean shouldReload(String classname) {
+        ClassCacheEntry entry = (ClassCacheEntry) cache.get(classname);
+
+        if (entry == null) {
+            // class wasn't even loaded
+            return false;
+        } else if (entry.isSystemClass()) {
+            // System classes cannot be reloaded
+            return false;
+        } else {
+            boolean reload = (entry.origin.lastModified() != entry.lastModified);
+
+            return reload;
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public synchronized boolean shouldReload() {
+        // Check whether any class has changed
+        Enumeration e = cache.elements();
+
+        while (e.hasMoreElements()) {
+            ClassCacheEntry entry = (ClassCacheEntry) e.nextElement();
+
+            if (entry.isSystemClass()) {
+                continue;
+            }
+
+            long msOrigin = entry.origin.lastModified();
+
+            if (msOrigin == 0) {
+                // class no longer exists
+                return true;
+            }
+
+            if (msOrigin != entry.lastModified) {
+                // class is modified
+                return true;
+            }
+        }
+
+        // No changes, no need to reload
+        return false;
+    }
+
+    /**
+     *
+     *
+     * @param name
+     * @param resolve
+     *
+     * @return
+     *
+     * @throws ClassNotFoundException
+     */
+    protected synchronized Class loadClass(String name, boolean resolve)
+        throws ClassNotFoundException {
+        // The class object that will be returned.
+        Class c = null;
+
+        // Use the cached value, if this class is already loaded into
+        // this classloader.
+        ClassCacheEntry entry = (ClassCacheEntry) cache.get(name);
+
+        if (entry != null) {
+            // Class found in our cache
+            c = entry.loadedClass;
+
+            if (resolve) {
+                resolveClass(c);
+            }
+
+            return c;
+        }
+
+        if (!securityAllowsClass(name)) {
+            return loadSystemClass(name, resolve);
+        }
+
+        // Attempt to load the class from the system
+        try {
+            c = loadSystemClass(name, resolve);
+
+            if (c != null) {
+                if (resolve) {
+                    resolveClass(c);
+                }
+
+                return c;
+            }
+        } catch (Exception e) {
+            c = null;
+        }
+
+        // Try to load it from each classpath
+        Iterator it = classpath.iterator();
+
+        // Cache entry.
+        ClassCacheEntry classCache = new ClassCacheEntry();
+
+        while (it.hasNext()) {
+            byte[] classData;
+            File file = (File) it.next();
+
+            try {
+                if (file.isDirectory()) {
+                    classData = loadClassFromDirectory(file, name, classCache);
+                } else {
+                    classData = loadClassFromZipfile(file, name, classCache);
+                }
+            } catch (IOException ioe) {
+                // Error while reading in data, consider it as not found
+                classData = null;
+            }
+
+            if (classData != null) {
+                // Define the class
+                c = defineClass(name, classData, 0, classData.length);
+
+                // Cache the result;
+                classCache.loadedClass = c;
+
+                // Origin is set by the specific loader
+                classCache.lastModified = classCache.origin.lastModified();
+                cache.put(name, classCache);
+
+                // Resolve it if necessary
+                if (resolve) {
+                    resolveClass(c);
+                }
+
+                return c;
+            }
+        }
+
+        // If not found in any classpath
+        throw new ClassNotFoundException(name);
+    }
+
+    private boolean isJarArchive(File file) {
+        boolean isArchive = true;
+        ZipFile zipFile = null;
+
+        try {
+            zipFile = new ZipFile(file);
+        } catch (ZipException zipCurrupted) {
+            isArchive = false;
+        } catch (IOException anyIOError) {
+            isArchive = false;
+        } finally {
+            if (zipFile != null) {
+                try {
+                    zipFile.close();
+                } catch (IOException ignored) {
+                }
+            }
+        }
+
+        return isArchive;
+    }
+
+    private byte[] loadBytesFromStream(InputStream in, int length)
+        throws IOException {
+        byte[] buf = new byte[length];
+        int nRead;
+        int count = 0;
+
+        while ((length > 0) && ((nRead = in.read(buf, count, length)) != -1)) {
+            count += nRead;
+            length -= nRead;
+        }
+
+        return buf;
+    }
+
+    private byte[] loadClassFromDirectory(File dir, String name,
+        ClassCacheEntry cache) throws IOException {
+        // Translate class name to file name
+        String classFileName = name.replace('.', File.separatorChar) +
+            ".class";
+
+        // Check for garbage input at beginning of file name
+        // i.e. ../ or similar
+        if (!Character.isJavaIdentifierStart(classFileName.charAt(0))) {
+            // Find real beginning of class name
+            int start = 1;
+
+            while (!Character.isJavaIdentifierStart(classFileName.charAt(
+                            start++))) {
+                ;
+            }
+
+            classFileName = classFileName.substring(start);
+        }
+
+        File classFile = new File(dir, classFileName);
+
+        if (classFile.exists()) {
+            cache.origin = classFile;
+
+            InputStream in = new FileInputStream(classFile);
+
+            try {
+                return loadBytesFromStream(in, (int) classFile.length());
+            } finally {
+                in.close();
+            }
+        } else {
+            // Not found
+            return null;
+        }
+    }
+
+    private byte[] loadClassFromZipfile(File file, String name,
+        ClassCacheEntry cache) throws IOException {
+        // Translate class name to file name
+        String classFileName = name.replace('.', '/') + ".class";
+        ZipFile zipfile = new ZipFile(file);
+
+        try {
+            ZipEntry entry = zipfile.getEntry(classFileName);
+
+            if (entry != null) {
+                cache.origin = file;
+
+                return loadBytesFromStream(zipfile.getInputStream(entry),
+                    (int) entry.getSize());
+            } else {
+                // Not found
+                return null;
+            }
+        } finally {
+            zipfile.close();
+        }
+    }
+
+    private InputStream loadResourceFromDirectory(File dir, String name) {
+        // Name of resources are always separated by /
+        String fileName = name.replace('/', File.separatorChar);
+        File resFile = new File(dir, fileName);
+
+        if (resFile.exists()) {
+            try {
+                return new FileInputStream(resFile);
+            } catch (FileNotFoundException shouldnothappen) {
+                return null;
+            }
+        } else {
+            return null;
+        }
+    }
+
+    private InputStream loadResourceFromZipfile(File file, String name) {
+        try {
+            ZipFile zipfile = new ZipFile(file);
+            ZipEntry entry = zipfile.getEntry(name);
+
+            if (entry != null) {
+                return zipfile.getInputStream(entry);
+            } else {
+                return null;
+            }
+        } catch (IOException e) {
+            return null;
+        }
+    }
+
+    private Class loadSystemClass(String name, boolean resolve)
+        throws NoClassDefFoundError, ClassNotFoundException {
+        //        Class c = findSystemClass(name);
+        Class c = parent.loadClass(name);
+
+        if (resolve) {
+            resolveClass(c);
+        }
+
+        // Throws if not found.
+        // Add cache entry
+        ClassCacheEntry cacheEntry = new ClassCacheEntry();
+        cacheEntry.origin = null;
+        cacheEntry.loadedClass = c;
+        cacheEntry.lastModified = Long.MAX_VALUE;
+        cache.put(name, cacheEntry);
+
+        if (resolve) {
+            resolveClass(c);
+        }
+
+        return c;
+    }
+
+    private boolean securityAllowsClass(String className) {
+        try {
+            SecurityManager security = System.getSecurityManager();
+
+            if (security == null) {
+                // if there's no security manager then all classes
+                // are allowed to be loaded
+                return true;
+            }
+
+            int lastDot = className.lastIndexOf('.');
+
+            // Check if we are allowed to load the class' package
+            security.checkPackageDefinition((lastDot > -1)
+                ? className.substring(0, lastDot) : "");
+
+            // Throws if not allowed
+            return true;
+        } catch (SecurityException e) {
+            return false;
+        }
+    }
+
+    private static class ClassCacheEntry {
+        Class loadedClass;
+        File origin;
+        long lastModified;
+
+        public boolean isSystemClass() {
+            return origin == null;
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/util/ExtensionClassLoader.java b/src/com/sshtools/j2ssh/util/ExtensionClassLoader.java
new file mode 100644
index 0000000000000000000000000000000000000000..e29bfbb66a256cb2f61cdd2ddace79643dba5396
--- /dev/null
+++ b/src/com/sshtools/j2ssh/util/ExtensionClassLoader.java
@@ -0,0 +1,476 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.util;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.Vector;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+
+
+/**
+ * <p>Title: </p>
+ * <p>Description: </p>
+ * <p>Copyright: Copyright (c) 2003</p>
+ * <p>Company: </p>
+ * @author Lee David Painter
+ * @version $Id: ExtensionClassLoader.java,v 1.11 2003/09/11 15:35:16 martianx Exp $
+ */
+public class ExtensionClassLoader extends ClassLoader {
+    private static Log log = LogFactory.getLog(ExtensionClassLoader.class);
+    Vector classpath = new Vector();
+    private Hashtable cache = new Hashtable();
+    private HashMap packages = new HashMap();
+
+    public ExtensionClassLoader() {
+    }
+
+    public ExtensionClassLoader(ClassLoader parent) {
+        super(parent);
+    }
+
+    public void add(String file) {
+        add(new File(file));
+    }
+
+    public void add(File[] files) {
+        for (int i = 0; i < files.length; i++) {
+            add(files[i]);
+        }
+    }
+
+    public void add(File file) {
+        if (!file.exists()) {
+            throw new IllegalArgumentException("Classpath " +
+                file.getAbsolutePath() + " doesn't exist!");
+        } else if (!file.canRead()) {
+            throw new IllegalArgumentException(
+                "Don't have read access for file " + file.getAbsolutePath());
+        }
+
+        // Check that it is a directory or jar file
+        if (!(file.isDirectory() || isJarArchive(file))) {
+            throw new IllegalArgumentException(file.getAbsolutePath() +
+                " is not a directory or jar file" +
+                " or if it's a jar file then it is corrupted.");
+        }
+
+        log.info("Adding " + file.getAbsolutePath() +
+            " to the extension classpath");
+        this.classpath.add(file);
+    }
+
+    public boolean isJarArchive(File file) {
+        boolean isArchive = true;
+        ZipFile zipFile = null;
+
+        try {
+            zipFile = new ZipFile(file);
+        } catch (ZipException zipCurrupted) {
+            isArchive = false;
+        } catch (IOException anyIOError) {
+            isArchive = false;
+        } finally {
+            if (zipFile != null) {
+                try {
+                    zipFile.close();
+                } catch (IOException ignored) {
+                }
+            }
+        }
+
+        return isArchive;
+    }
+
+    public URL getResource(String name, File location) {
+        if (isJarArchive(location)) {
+            return findResourceInZipfile(location, name);
+        } else {
+            return findResourceInDirectory(location, name);
+        }
+    }
+
+    protected URL findResource(String name) {
+        // The class object that will be returned.
+        URL url = null;
+
+        // Try to load it from each classpath
+        Iterator it = classpath.iterator();
+
+        while (it.hasNext()) {
+            byte[] classData;
+            File file = (File) it.next();
+
+            if (file.isDirectory()) {
+                url = findResourceInDirectory(file, name);
+            } else {
+                url = findResourceInZipfile(file, name);
+            }
+
+            if (url != null) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Found resource " + url.toExternalForm());
+                }
+
+                return url;
+            }
+
+            if (log.isDebugEnabled()) {
+                log.debug("Could not find resource " + name);
+            }
+        }
+
+        return null;
+    }
+
+    protected Enumeration findResources(String name) {
+        HashSet resources = new HashSet();
+        URL url = null;
+
+        // Try to load it from each classpath
+        Iterator it = classpath.iterator();
+
+        while (it.hasNext()) {
+            byte[] classData;
+            File file = (File) it.next();
+
+            if (file.isDirectory()) {
+                url = findResourceInDirectory(file, name);
+            } else {
+                url = findResourceInZipfile(file, name);
+            }
+
+            if (url != null) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Found resource " + url.toExternalForm());
+                }
+
+                resources.add(url);
+            }
+        }
+
+        return new ResourceEnumeration(resources);
+    }
+
+    public byte[] loadClassData(String name) throws ClassNotFoundException {
+        // Try to load it from each classpath
+        Iterator it = classpath.iterator();
+
+        // Cache entry.
+        ClassCacheEntry classCache = new ClassCacheEntry();
+
+        while (it.hasNext()) {
+            byte[] classData;
+            File file = (File) it.next();
+
+            try {
+                if (file.isDirectory()) {
+                    classData = loadClassFromDirectory(file, name, null);
+                } else {
+                    classData = loadClassFromZipfile(file, name, null);
+                }
+            } catch (IOException ioe) {
+                // Error while reading in data, consider it as not found
+                classData = null;
+            }
+
+            if (classData != null) {
+                return classData;
+            }
+        }
+
+        // If not found in any classpath
+        throw new ClassNotFoundException(name);
+    }
+
+    public Class findClass(String name) throws ClassNotFoundException {
+        // The class object that will be returned.
+        Class c = null;
+
+        // Use the cached value, if this class is already loaded into
+        // this classloader.
+        ClassCacheEntry entry = (ClassCacheEntry) cache.get(name);
+
+        if (entry != null) {
+            if (log.isDebugEnabled()) {
+                log.debug("Loaded " + name + " from cache");
+            }
+
+            // Class found in our cache
+            c = entry.loadedClass;
+            resolveClass(c);
+
+            return c;
+        }
+
+        // Try to load it from each classpath
+        Iterator it = classpath.iterator();
+
+        // Cache entry.
+        ClassCacheEntry classCache = new ClassCacheEntry();
+
+        while (it.hasNext()) {
+            byte[] classData;
+            File file = (File) it.next();
+
+            try {
+                if (file.isDirectory()) {
+                    classData = loadClassFromDirectory(file, name, classCache);
+                } else {
+                    classData = loadClassFromZipfile(file, name, classCache);
+                }
+            } catch (IOException ioe) {
+                // Error while reading in data, consider it as not found
+                classData = null;
+            }
+
+            if (classData != null) {
+                // Does the package exist?
+                String packageName = "";
+
+                if (name.lastIndexOf(".") > 0) {
+                    packageName = name.substring(0, name.lastIndexOf("."));
+                }
+
+                if (!packageName.equals("") &&
+                        !packages.containsKey(packageName)) {
+                    packages.put(packageName,
+                        definePackage(packageName, "", "", "", "", "", "", null));
+
+                    // Define the class
+                }
+
+                c = defineClass(name, classData, 0, classData.length);
+
+                // Cache the result;
+                classCache.loadedClass = c;
+
+                // Origin is set by the specific loader
+                classCache.lastModified = classCache.origin.lastModified();
+                cache.put(name, classCache);
+                resolveClass(c);
+
+                if (log.isDebugEnabled()) {
+                    log.debug("Loaded " + name +
+                        " adding to cache and returning");
+                }
+
+                return c;
+            }
+        }
+
+        // If not found in any classpath
+        throw new ClassNotFoundException(name);
+    }
+
+    private byte[] loadBytesFromStream(InputStream in, int length)
+        throws IOException {
+        byte[] buf = new byte[length];
+        int nRead;
+        int count = 0;
+
+        while ((length > 0) && ((nRead = in.read(buf, count, length)) != -1)) {
+            count += nRead;
+            length -= nRead;
+        }
+
+        return buf;
+    }
+
+    private byte[] loadClassFromDirectory(File dir, String name,
+        ClassCacheEntry cache) throws IOException {
+        // Translate class name to file name
+        String classFileName = name.replace('.', File.separatorChar) +
+            ".class";
+
+        // Check for garbage input at beginning of file name
+        // i.e. ../ or similar
+        if (!Character.isJavaIdentifierStart(classFileName.charAt(0))) {
+            // Find real beginning of class name
+            int start = 1;
+
+            while (!Character.isJavaIdentifierStart(classFileName.charAt(
+                            start++))) {
+                ;
+            }
+
+            classFileName = classFileName.substring(start);
+        }
+
+        File classFile = new File(dir, classFileName);
+
+        if (classFile.exists()) {
+            if (cache != null) {
+                cache.origin = classFile;
+            }
+
+            InputStream in = new FileInputStream(classFile);
+
+            try {
+                return loadBytesFromStream(in, (int) classFile.length());
+            } finally {
+                in.close();
+            }
+        } else {
+            // Not found
+            return null;
+        }
+    }
+
+    private byte[] loadClassFromZipfile(File file, String name,
+        ClassCacheEntry cache) throws IOException {
+        // Translate class name to file name
+        String classFileName = name.replace('.', '/') + ".class";
+        ZipFile zipfile = new ZipFile(file);
+
+        try {
+            ZipEntry entry = zipfile.getEntry(classFileName);
+
+            if (entry != null) {
+                if (cache != null) {
+                    cache.origin = file;
+                }
+
+                return loadBytesFromStream(zipfile.getInputStream(entry),
+                    (int) entry.getSize());
+            } else {
+                // Not found
+                return null;
+            }
+        } finally {
+            zipfile.close();
+        }
+    }
+
+    private InputStream loadResourceFromDirectory(File dir, String name) {
+        // Name of resources are always separated by /
+        String fileName = name.replace('/', File.separatorChar);
+        File resFile = new File(dir, fileName);
+
+        if (resFile.exists()) {
+            try {
+                return new FileInputStream(resFile);
+            } catch (FileNotFoundException shouldnothappen) {
+                return null;
+            }
+        } else {
+            return null;
+        }
+    }
+
+    private URL findResourceInDirectory(File dir, String name) {
+        // Name of resources are always separated by /
+        String fileName = name.replace('/', File.separatorChar);
+        File resFile = new File(dir, fileName);
+
+        if (resFile.exists()) {
+            try {
+                return resFile.toURL();
+            } catch (MalformedURLException ex) {
+                return null;
+            }
+        } else {
+            return null;
+        }
+    }
+
+    private URL findResourceInZipfile(File file, String name) {
+        try {
+            ZipFile zipfile = new ZipFile(file);
+            ZipEntry entry = zipfile.getEntry(name);
+
+            if (entry != null) {
+                return new URL("jar:" + file.toURL() + "!" +
+                    (name.startsWith("/") ? "" : "/") + name);
+            } else {
+                return null;
+            }
+        } catch (IOException e) {
+            return null;
+        }
+    }
+
+    private InputStream loadResourceFromZipfile(File file, String name) {
+        try {
+            ZipFile zipfile = new ZipFile(file);
+            ZipEntry entry = zipfile.getEntry(name);
+
+            if (entry != null) {
+                return zipfile.getInputStream(entry);
+            } else {
+                return null;
+            }
+        } catch (IOException e) {
+            return null;
+        }
+    }
+
+    private class ResourceEnumeration implements Enumeration {
+        Set resources;
+        Iterator it;
+
+        ResourceEnumeration(Set resources) {
+            this.resources = resources;
+            it = resources.iterator();
+        }
+
+        public boolean hasMoreElements() {
+            return it.hasNext();
+        }
+
+        public Object nextElement() {
+            return it.next();
+        }
+    }
+
+    private static class ClassCacheEntry {
+        Class loadedClass;
+        File origin;
+        long lastModified;
+
+        public boolean isSystemClass() {
+            return origin == null;
+        }
+    }
+}
diff --git a/src/com/sshtools/j2ssh/util/Hash.java b/src/com/sshtools/j2ssh/util/Hash.java
new file mode 100644
index 0000000000000000000000000000000000000000..d69746916fd144a0b143eafaa95b8bfb74e060c3
--- /dev/null
+++ b/src/com/sshtools/j2ssh/util/Hash.java
@@ -0,0 +1,145 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.util;
+
+import com.sshtools.j2ssh.io.*;
+
+import java.io.*;
+
+import java.math.*;
+
+import java.security.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class Hash {
+    private MessageDigest hash;
+
+    /**
+     * Creates a new Hash object.
+     *
+     * @param algorithm
+     *
+     * @throws NoSuchAlgorithmException
+     */
+    public Hash(String algorithm) throws NoSuchAlgorithmException {
+        hash = MessageDigest.getInstance(algorithm);
+    }
+
+    /**
+     *
+     *
+     * @param bi
+     */
+    public void putBigInteger(BigInteger bi) {
+        byte[] data = bi.toByteArray();
+        putInt(data.length);
+        hash.update(data);
+    }
+
+    /**
+     *
+     *
+     * @param b
+     */
+    public void putByte(byte b) {
+        hash.update(b);
+    }
+
+    /**
+     *
+     *
+     * @param data
+     */
+    public void putBytes(byte[] data) {
+        hash.update(data);
+    }
+
+    /**
+     *
+     *
+     * @param i
+     */
+    public void putInt(int i) {
+        ByteArrayWriter baw = new ByteArrayWriter();
+
+        try {
+            baw.writeInt(i);
+        } catch (IOException ioe) {
+        }
+
+        hash.update(baw.toByteArray());
+    }
+
+    /**
+     *
+     *
+     * @param str
+     */
+    public void putString(String str) {
+        putInt(str.length());
+        hash.update(str.getBytes());
+    }
+
+    /**
+     *
+     */
+    public void reset() {
+        hash.reset();
+    }
+
+    /**
+     *
+     *
+     * @param data
+     * @param algorithm
+     *
+     * @return
+     *
+     * @throws NoSuchAlgorithmException
+     */
+    public static byte[] simple(byte[] data, String algorithm)
+        throws NoSuchAlgorithmException {
+        MessageDigest simp = MessageDigest.getInstance(algorithm);
+        simp.update(data);
+
+        return simp.digest();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] doFinal() {
+        return hash.digest();
+    }
+}
diff --git a/src/com/sshtools/j2ssh/util/InvalidStateException.java b/src/com/sshtools/j2ssh/util/InvalidStateException.java
new file mode 100644
index 0000000000000000000000000000000000000000..5ee00a8c3d3c87751724aa8fb1931be923f24b73
--- /dev/null
+++ b/src/com/sshtools/j2ssh/util/InvalidStateException.java
@@ -0,0 +1,44 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.util;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.14 $
+ */
+public class InvalidStateException extends RuntimeException {
+    /**
+     * Creates a new InvalidStateException object.
+     *
+     * @param msg
+     */
+    public InvalidStateException(String msg) {
+        super(msg);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/util/OpenClosedState.java b/src/com/sshtools/j2ssh/util/OpenClosedState.java
new file mode 100644
index 0000000000000000000000000000000000000000..c61e10d3db5af64f2b51f20a849545153d593e15
--- /dev/null
+++ b/src/com/sshtools/j2ssh/util/OpenClosedState.java
@@ -0,0 +1,61 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.util;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.13 $
+ */
+public class OpenClosedState extends State {
+    /**  */
+    public static final int OPEN = 1;
+
+    /**  */
+    public static final int CLOSED = 2;
+
+    /**
+     * Creates a new OpenClosedState object.
+     *
+     * @param initial
+     */
+    public OpenClosedState(int initial) {
+        super(initial);
+    }
+
+    /**
+     *
+     *
+     * @param state
+     *
+     * @return
+     */
+    public boolean isValidState(int state) {
+        return (state == OPEN) || (state == CLOSED);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/util/SimpleASNReader.java b/src/com/sshtools/j2ssh/util/SimpleASNReader.java
new file mode 100644
index 0000000000000000000000000000000000000000..4da0573a68d408ae739819cdc27481844e5f5a54
--- /dev/null
+++ b/src/com/sshtools/j2ssh/util/SimpleASNReader.java
@@ -0,0 +1,126 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.util;
+
+import java.io.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class SimpleASNReader {
+    private byte[] data;
+    private int offset;
+
+    /**
+     * Creates a new SimpleASNReader object.
+     *
+     * @param data
+     */
+    public SimpleASNReader(byte[] data) {
+        this.data = data;
+        this.offset = 0;
+    }
+
+    /**
+     *
+     *
+     * @param b
+     *
+     * @throws IOException
+     */
+    public void assertByte(int b) throws IOException {
+        int x = getByte();
+
+        if (x != b) {
+            throw new IOException("Assertion failed, next byte value is " +
+                Integer.toHexString(x) + " instead of asserted " +
+                Integer.toHexString(b));
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getByte() {
+        return data[offset++] & 0xff;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] getData() {
+        int length = getLength();
+
+        return getData(length);
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public int getLength() {
+        int b = data[offset++] & 0xff;
+
+        if ((b & 0x80) != 0) {
+            int length = 0;
+
+            for (int bytes = b & 0x7f; bytes > 0; bytes--) {
+                length <<= 8;
+                length |= (data[offset++] & 0xff);
+            }
+
+            return length;
+        }
+
+        return b;
+    }
+
+    private byte[] getData(int length) {
+        byte[] result = new byte[length];
+        System.arraycopy(data, offset, result, 0, length);
+        offset += length;
+
+        return result;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public boolean hasMoreData() {
+        return offset < data.length;
+    }
+}
diff --git a/src/com/sshtools/j2ssh/util/SimpleASNWriter.java b/src/com/sshtools/j2ssh/util/SimpleASNWriter.java
new file mode 100644
index 0000000000000000000000000000000000000000..3075a60309ce5c83b94e228df66e7de581b3287e
--- /dev/null
+++ b/src/com/sshtools/j2ssh/util/SimpleASNWriter.java
@@ -0,0 +1,105 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.util;
+
+import java.io.*;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.12 $
+ */
+public class SimpleASNWriter {
+    private ByteArrayOutputStream data;
+
+    /**
+     * Creates a new SimpleASNWriter object.
+     */
+    public SimpleASNWriter() {
+        this.data = new ByteArrayOutputStream();
+    }
+
+    /**
+     *
+     *
+     * @param b
+     */
+    public void writeByte(int b) {
+        data.write(b);
+    }
+
+    /**
+     *
+     *
+     * @param b
+     */
+    public void writeData(byte[] b) {
+        writeLength(b.length);
+        this.data.write(b, 0, b.length);
+    }
+
+    /**
+     *
+     *
+     * @param length
+     */
+    public void writeLength(int length) {
+        if (length < 0x80) {
+            data.write(length);
+        } else {
+            if (length < 0x100) {
+                data.write(0x81);
+                data.write(length);
+            } else if (length < 0x10000) {
+                data.write(0x82);
+                data.write(length >>> 8);
+                data.write(length);
+            } else if (length < 0x1000000) {
+                data.write(0x83);
+                data.write(length >>> 16);
+                data.write(length >>> 8);
+                data.write(length);
+            } else {
+                data.write(0x84);
+                data.write(length >>> 24);
+                data.write(length >>> 16);
+                data.write(length >>> 8);
+                data.write(length);
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public byte[] toByteArray() {
+        return data.toByteArray();
+    }
+}
diff --git a/src/com/sshtools/j2ssh/util/StartStopState.java b/src/com/sshtools/j2ssh/util/StartStopState.java
new file mode 100644
index 0000000000000000000000000000000000000000..6ef8bc550c8b96ced77ceae27252f1303dca149e
--- /dev/null
+++ b/src/com/sshtools/j2ssh/util/StartStopState.java
@@ -0,0 +1,64 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.util;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.15 $
+ */
+public class StartStopState extends State {
+    /**  */
+    public static final int STARTED = 1;
+
+    /**  */
+    public static final int STOPPED = 2;
+
+    /**  */
+    public static final int FAILED = 3;
+
+    /**
+     * Creates a new StartStopState object.
+     *
+     * @param initial
+     */
+    public StartStopState(int initial) {
+        super(initial);
+    }
+
+    /**
+     *
+     *
+     * @param state
+     *
+     * @return
+     */
+    public boolean isValidState(int state) {
+        return (state == STARTED) || (state == STOPPED) || (state == FAILED);
+    }
+}
diff --git a/src/com/sshtools/j2ssh/util/State.java b/src/com/sshtools/j2ssh/util/State.java
new file mode 100644
index 0000000000000000000000000000000000000000..b93e04e0dab6bf3907017e03526ae25642802d0d
--- /dev/null
+++ b/src/com/sshtools/j2ssh/util/State.java
@@ -0,0 +1,169 @@
+/*
+ *  SSHTools - Java SSH2 API
+ *
+ *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
+ *
+ *  Contributions made by:
+ *
+ *  Brett Smith
+ *  Richard Pernavas
+ *  Erwin Bolwidt
+ *
+ *  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
+ *  of the License, or (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package com.sshtools.j2ssh.util;
+
+import java.io.Serializable;
+
+
+/**
+ *
+ *
+ * @author $author$
+ * @version $Revision: 1.18 $
+ */
+public abstract class State implements Serializable {
+    /**  */
+    protected int state;
+
+    /**
+     * Creates a new State object.
+     *
+     * @param initialState
+     */
+    public State(int initialState) {
+        this.state = initialState;
+    }
+
+    /**
+     *
+     *
+     * @param state
+     *
+     * @return
+     */
+    public abstract boolean isValidState(int state);
+
+    /**
+     *
+     *
+     * @param state
+     *
+     * @throws InvalidStateException
+     */
+    public synchronized void setValue(int state) throws InvalidStateException {
+        if (!isValidState(state)) {
+            throw new InvalidStateException("The state is invalid");
+        }
+
+        this.state = state;
+        notifyAll();
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public synchronized int getValue() {
+        return state;
+    }
+
+    /**
+     *
+     */
+    public synchronized void breakWaiting() {
+        notifyAll();
+    }
+
+    /**
+     *
+     *
+     * @param state
+     *
+     * @return
+     *
+     * @throws InvalidStateException
+     * @throws InterruptedException
+     */
+    public synchronized boolean waitForState(int state)
+        throws InvalidStateException, InterruptedException {
+        return waitForState(state, 0);
+    }
+
+    /**
+     *
+     *
+     * @param state
+     * @param timeout
+     *
+     * @return
+     *
+     * @throws InvalidStateException
+     * @throws InterruptedException
+     */
+    public synchronized boolean waitForState(int state, long timeout)
+        throws InvalidStateException, InterruptedException {
+        if (!isValidState(state)) {
+            throw new InvalidStateException("The state is invalid");
+        }
+
+        if (timeout < 0) {
+            timeout = 0;
+        }
+
+        while (this.state != state) {
+            //   try {
+            wait(timeout);
+
+            if (timeout != 0) {
+                break;
+            }
+
+            //   } catch (InterruptedException e) {
+            //        break;
+            //    }
+        }
+
+        return this.state == state;
+    }
+
+    /**
+     *
+     *
+     * @return
+     *
+     * @throws InterruptedException
+     */
+    public synchronized int waitForStateUpdate() throws InterruptedException {
+        // try {
+        wait();
+
+        // } catch (InterruptedException ie) {
+        //  }
+        return state;
+    }
+
+    /*public synchronized boolean waitForStateUpdate(long timeout) {
+        int oldstate = state;
+        if(timeout<0)
+     timeout=0;
+        try {
+     wait(timeout);
+        } catch(InterruptedException ie) {
+        }
+        return !(oldstate==state);
+      }*/
+}