org.kepler.ssh.SshExec.java Source code

Java tutorial

Introduction

Here is the source code for org.kepler.ssh.SshExec.java

Source

/*
 * Copyright (c) 2004-2010 The Regents of the University of California.
 * All rights reserved.
 *
 * '$Author: barseghian $'
 * '$Date: 2012-06-15 13:43:25 -0700 (Fri, 15 Jun 2012) $' 
 * '$Revision: 29954 $'
 * 
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and its documentation for any purpose, provided that the above
 * copyright notice and the following two paragraphs appear in all copies
 * of this software.
 *
 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
 * ENHANCEMENTS, OR MODIFICATIONS.
 *
 */

package org.kepler.ssh;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;

/**
 * This class provides functionality to use an SSH session to execute remote
 * commands and to transfer files on a remote machine.
 * 
 * A session can be opened using one the following authentication protocols: -
 * public-key - password - keyboard-interactive
 * 
 * You can create several sessions to different sites and use any of them
 * referring to it with its user@host.
 * 
 * An Ssh object can be used then by anyone anytime to copy file or execute
 * commands without authenticating again. If the session becomes disconnected,
 * the connection will be reopened at the next use, and authentication will be
 * either automatic (public-key, or stored
 * 
 * <p>
 * 
 * @author Norbert Podhorszki
 * 
 *         Based on - JSch examples http://www.jcraft.com/jsch - OpenSSH 4.3
 *         source code http://www.openssh.com/ - org.sdm.spa.Ssh2Exec Kepler
 *         actor Authors of the Kepler actor: Ilkay Altintas, Xiaowen Xin
 */

public class SshExec extends RemoteExec {

    /* Private variables */
    protected SshSession session;
    protected Session jschSession;

    private static final Log log = LogFactory.getLog(SshExec.class.getName());
    private static final boolean isDebugging = log.isDebugEnabled();

    // timeout variables
    private int timeout = 0; // timeout in seconds
    private boolean timeoutRestartOnStdout = false; // restart timer if stdout
    // has data
    private boolean timeoutRestartOnStderr = false; // restart timer if stderr
    // has data

    // forced clean-up variables
    private boolean forcedCleanUp = false;
    private static String cleanUpInfoCmd = new String("echo $$; ");

    // variables added to extend SshExec
    private boolean pseudoTerminal = false;
    private String protocolPath = "";
    private String cmdLineOptions = "";

    public SshExec(String user, String host) {
        session = SshSessionFactory.getSession(user, host, 22);
    }

    public SshExec(String user, String host, int port) {
        session = SshSessionFactory.getSession(user, host, port);
    }

    /**
     * Create and SshExec object from a combined target string. Input: string of
     * format [user@]host[:port]
     */
    public SshExec(String target) {
        // get USER
        String user, host;
        int port = 22;

        int atPos = target.indexOf('@');
        if (atPos >= 0) {
            user = target.substring(0, target.indexOf('@'));
        } else {
            user = System.getProperty("user.name");
        }

        // get the HOST and PORT
        int colonPos = target.indexOf(':');
        if (colonPos >= 0 && colonPos > atPos) {
            host = target.substring(atPos + 1, colonPos);
            String portStr = target.substring(colonPos + 1);
            try {
                port = Integer.parseInt(portStr);
            } catch (java.lang.NumberFormatException ex) {
                log.error("The port should be a number or omitted in " + target);
            }
        } else
            host = target.substring(atPos + 1);
        session = SshSessionFactory.getSession(user, host, port);
    }

    /**
     * Open the connection to the remote machine now. It is not necessary to
     * call this method! If you do not want to prolong making the connection
     * until the first executeCmd / copyTo / copyFrom, then you need to use this
     * method.
     */
    public boolean openConnection() throws SshException {
        if (null == session)
            return false;

        jschSession = session.open();

        try {
            // send alive messages every 30 seconds to avoid
            // drops when running long-lasting commands without much stdout
            jschSession.setServerAliveInterval(30000);
        } catch (JSchException ex) {
            log.warn("ServerAliveInterval could not be set for session " + session.getUser() + "@"
                    + session.getHost() + ": " + ex);
        }

        return true;
    }

    /**
     * Close the connection This should be called only when there is no more use
     * of the connection to this user@host Important: the ssh session remains
     * opened as long as this method is not called, therefore, the the whole
     * java application will hang at the end until this method is called.
     */
    public void closeConnection() {
        if (null != session)
            session.close();
        // session = null; // disallow reconnection
        jschSession = null;
    }

    /**
     * Add identity file Public-key authentication can be used if you add
     * identity files
     */
    public void addIdentity(String identity) {
        try {
            if (null != session)
                session.addIdentity(identity);
        } catch (SshException e) {
            log.error("addIdentity error: " + e.toString());
            ; // don't do anything here at this moment
        }
    }

    public void addIdentity(File identity) {
        addIdentity(identity.getPath());
    }

    /**
     * Set a local port forwarding before the connection is made. Format of the
     * specification: lport:rhost:rport (int:string:int). lport on this host
     * will be forwarded through this session and the remote host to the
     * specified rhost:rport address.
     */
    public void setPortForwardingL(String spec) throws SshException {
        session.setPortForwardingL(spec);
    }

    /**
     * Add a local port forwarding to an open connection. Format of the
     * specification: lport:rhost:rport (int:string:int). lport on this host
     * will be forwarded through this session and the remote host to the
     * specified rhost:rport address.
     */
    public void addPortForwardL(String spec) {
        session.addPortForwardL(spec);
    }

    /**
     * Set a remote port forwarding before the connection is made. Format of the
     * specification: rport:lhost:lport (int:string:int). rport on remote host
     * will be forwarded through this session and our local host to the
     * specified lhost:lport address.
     */
    public void setPortForwardingR(String spec) throws SshException {
        session.setPortForwardingR(spec);
    }

    /**
     * Add a remote port forwarding to an open connection. Format of the
     * specification: rport:lhost:lport (int:string:int). rport on remote host
     * will be forwarded through this session and our local host to the
     * specified lhost:lport address.
     */
    public void addPortForwardR(String spec) {
        session.addPortForwardR(spec);
    }

    /**
     * Remove a local port forwarding.
     * @param port the local port that is forwarded.
     * @param closeIfLast If true, and there are no additional local ports
     * forwarded, close the connection.
     */
    public void removePortForwardL(int port, boolean closeIfLast) throws SshException {
        session.removePortForwardL(port, closeIfLast);
    }

    /**
     * Remove a remote port forwarding.
     * @param port the remote port that is forwarded.
     * @param closeIfLast If true, and there are no additional remote ports
     * forwarded, close the connection.
     */
    public void removePortForwardR(int port, boolean closeIfLast) throws SshException {
        session.removePortForwardR(port, closeIfLast);
    }

    /**
     * Set timeout for the operations. Timeout should be given in seconds. If
     * 'stdout' is set to true, the timer is restarted whenever there is data on
     * stdout. If 'stderr' is set to true, the timer is restarted whenever there
     * is data on stderr. executeCmd will throw an ExecException, an instance of
     * ExecTimeoutException if the timeout limit is reached. 'seconds' = 0 means
     * no timeout at all. Note: copyTo and copyFrom operations currently do not
     * support timeout. Note: currently, the timer cannot be restarted on stderr
     * events in executeCmd
     */
    public void setTimeout(int seconds, boolean stdout, boolean stderr) {
        timeout = seconds;
        timeoutRestartOnStdout = stdout;
        timeoutRestartOnStderr = stderr;
    }

    /**
     * Specify if killing of remote processes (i.e. clean-up) after error or
     * timeout is required. Unix specific solution is used for clean-up, so do
     * not use it when connecting to another servers. Default is false, of
     * course, but use it whenever you can (i.e. connect to unix machines).
     */
    public void setForcedCleanUp(boolean foo) {
        forcedCleanUp = foo;
    }

    /**
     * Execute a command on the remote machine. The streams <i>streamOut</i> and
     * <i>streamErr</i> should be provided to get the output and errors.
     * 
     * @return exit code of command if execution succeeded,
     * @throws ExecTimeoutException
     *             if the command failed because of timeout
     * @throws SshException
     *             if an error occurs for the ssh connection during the command
     *             execution
     */
    public int executeCmd(String command, OutputStream streamOut, OutputStream streamErr) throws ExecException {
        return executeCmd(command, streamOut, streamErr, null);
    } // end-of-method executeCmd

    /**
     * Execute a command on the remote machine and expect a password/passphrase
     * question from the command. The stream <i>streamOut</i> should be provided
     * to get the output and errors merged. <i>streamErr</i> is not used in this
     * method (it will be empty string finally).
     * 
     * @return exit code of command if execution succeeded,
     * @throws ExecTimeoutException
     *             if the command failed because of timeout
     * @throws SshException
     *             if an error occurs for the ssh connection during the command
     *             execution Note: in this method, the SSH Channel is forcing a
     *             pseudo-terminal allocation {see setPty(true)} to allow remote
     *             commands to read something from their stdin (i.e. from us
     *             here), thus, (1) remote environment is not set from
     *             .bashrc/.cshrc and (2) stdout and stderr come back merged in
     *             one stream.
     */
    public int executeCmd(String command, OutputStream streamOut, OutputStream streamErr, String thirdPartyTarget)
            throws ExecException {

        int exitCode = 0;
        String cmd = forcedCleanUp ? cleanUpInfoCmd + command : command;
        openConnection();

        // get the pwd to the third party (and perform authentication if not yet
        // done)
        String pwd = SshSession.getPwdToThirdParty(thirdPartyTarget);

        // create a piped stream to feed the input of the remote exec channel
        PipedInputStream pis = null;
        PipedOutputStream pos = null;
        if (pwd != null) {
            try {
                pis = new PipedInputStream();
                pos = new PipedOutputStream(pis);
            } catch (IOException ex) {
                log.error("Error when creating the piped stream for password feededing: " + ex);
            }
        }
        // At this point we have an opened session to the remote machine.
        // But session may be down, so this complex trial cycle here.

        boolean tryagain = true;
        while (tryagain) {
            tryagain = false;
            InputStream in = null;
            try {
                pwd = SshSession.getPwdToThirdParty(thirdPartyTarget);
                ChannelExec channel = null;
                _streamReaderThread readerThread;
                synchronized (session) {
                    channel = (ChannelExec) jschSession.openChannel("exec");
                    if (isDebugging)
                        log.debug("pseudoTerminal=" + pseudoTerminal);
                    channel.setPty(pseudoTerminal);
                    channel.setCommand(cmd);
                    // channel.setOutputStream(streamOut); // use rather
                    // getInputStream and read it
                    channel.setErrStream(streamErr); // Useless!! stderr goes
                    // into stdout
                    channel.setInputStream(pis); // remote command will read
                    // from our stream
                    in = channel.getInputStream();

                    // start output processing thread
                    // it checks for timeout too
                    readerThread = new _streamReaderThread(channel, in, streamOut, pwd, pos);
                    readerThread.start();

                    channel.connect(); // command starts here but we get back
                    // the control
                    // this thread runs further but reading of pis by the remote
                    // process may block it
                }
                if (isDebugging) {
                    log.debug("Started remote execution of command: " + command);
                }
                // wait for the reader thread to finish
                // It will timeout at the latest if the command does not finish
                // 3 ways to finish:
                // - command terminates
                // - timeout
                // - IOException when reading the command's output or writing
                // the caller's output
                readerThread.join();

                // on timeout finish here with a nice Exception
                if (readerThread.timeoutHappened()) {
                    log.error("Timeout: " + timeout + "s elapsed for command " + command);
                    // BUG?: disconnect does not kill the remote process!!!
                    // Clean-up should be done somehow
                    channel.disconnect();
                    if (forcedCleanUp) {
                        // time for clean-up ;-)
                        kill(readerThread.getProcessID(), true);
                    }
                    throw new ExecTimeoutException(command);
                }
                // if we cannot process output, still wait for the channel to be
                // closed
                // !!! This can lead to hang-up !!!
                while (!channel.isClosed()) {
                    try {
                        Thread.sleep(500);
                    } catch (Exception e) {
                    }
                }
                // command completed successfully
                if (isDebugging)
                    log.debug("Command execution terminated, now waiting for the channel to be closed.");

                if (isDebugging)
                    log.debug("Channel closed down, now get the exit status.");
                // this is the wrong test if the remote OS is OpenVMS,
                // but there doesn't seem to be a way to detect it.
                // Note: it must be called only when channel.isClosed() becomes
                // true.
                // Otherwise it may return -1, being not yet set to the exit
                // code
                exitCode = channel.getExitStatus();
                if (isDebugging)
                    log.debug("Exit status = " + exitCode + ". Now disconnect channel.");
                // Note: The jsch source suggests that when the exit code is
                // set, the
                // package disconnects the channel, so the extra call to
                // disconnect()
                // may be unnecessary. But I inherited this way and am tired of
                // debugging...
                channel.disconnect();

                if (exitCode != 0 && forcedCleanUp) {
                    // what is sure is sure ;-)
                    kill(readerThread.getProcessID(), true);
                }

            } catch (JSchException ex) {
                if (ex.toString().indexOf("session is down") > -1) {
                    log.error("Session to " + session.getUser() + "@" + session.getHost()
                            + " is down, try connect again");
                    closeConnection();
                    openConnection();
                    tryagain = true;
                } else {
                    throw new SshException("JSchException caught at command: " + command + "\n" + ex);
                }
            } catch (Exception e) {
                throw new SshException("Exception caught at command: " + command + "\n" + e);
            }
        } // end while

        return exitCode;

    } // end-of-method executeCmd

    /**
     * Copy _one_ local file to a remote directory Input: file of type File
     * (which can be a directory) Input must not have wildcards. targetPath is
     * either a directory or filename
     * 
     * @return number of files copied successfully SshException is thrown in
     *         case of error.
     */
    protected int _copyTo(File lfile, String targetPath, boolean recursive) throws SshException {

        if (!lfile.exists()) {
            throw new SshException("File does not exist: " + lfile);
        }

        String recursiveFlag = "";
        // check: recursive traversal of directories enabled?
        if (lfile.isDirectory()) {
            if (!recursive)
                throw new SshException("File " + lfile + " is a directory. Set recursive copy!");
            recursiveFlag = "-r ";
        }
        if (!openConnection())
            throw new SshException("Ssh connection could not be opened for copying.");

        // at this point we have a living, opened session to the remote machine
        int numberOfCopiedFiles = 0;

        if (isDebugging)
            log.debug(" %   Copy " + lfile + " to " + targetPath);
        String command = protocolPath + "scp " + cmdLineOptions + " " + recursiveFlag + "-p -t " + targetPath;
        OutputStream out;
        InputStream in;

        try {
            ChannelExec channel;

            synchronized (session) { // for thread safety here we need to sync
                channel = (ChannelExec) jschSession.openChannel("exec");
                channel.setCommand(command);
                // get I/O streams for remote scp
                out = channel.getOutputStream();
                in = channel.getInputStream();
                channel.connect();
            }

            if (checkAck(in) != 0)
                throw new SshException("Scp to remote site failed\n");

            // perform the protocol of actual copy of file/directory
            numberOfCopiedFiles = source(lfile, in, out);

            // close channel
            channel.disconnect();

        } catch (Exception e) {
            throw new SshException("Exception caught at command: " + command + "\n" + e);
        }

        return numberOfCopiedFiles;
    }

    /**
     * Copy one file to the remote location. This is the core of the copyTo
     * method, which sends a content of file to the remote ssh server using the
     * protocol of scp/rcp. If 'lfile' is a directory, it does this recursively.
     * 
     * @return The number of copied files, including the directories created.
     */
    private int source(File lfile, InputStream in, OutputStream out) throws Exception {

        // String accessRights="644"; // rw-r--r--
        String accessRights = "755"; // rwxr-xr-x
        byte[] buf = new byte[1024];

        String command;

        // recursive handling of directories
        if (lfile.isDirectory()) {
            File[] files = lfile.listFiles();
            if (files != null) {
                // send "C0755 0 filename", where filename should not include
                // '/'
                command = "D0" + accessRights + " 0 " + lfile.getName() + "\n";
                out.write(command.getBytes());
                out.flush();
                int numberOfCopiedFiles = 0;
                for (int i = 0; i < files.length; i++) {
                    if (isDebugging)
                        log.debug(" %     " + files[i]);
                    numberOfCopiedFiles += source(files[i], in, out);
                }
                // End of directory: send "E\n"
                buf[0] = 'E';
                buf[1] = '\n';
                out.write(buf, 0, 2);
                out.flush();
                return numberOfCopiedFiles + 1;
            } else {
                throw new SshException("Unable to read directory " + lfile.getPath()
                        + "\n Please check if the directory exists and has appropriate permission");
            }
        }

        // Now we deal with one single file, let's send it
        FileInputStream fis = new FileInputStream(lfile);

        // send "C0644 filesize filename", where filename should not include '/'
        command = "C0" + accessRights + " " + lfile.length() + " " + lfile.getName() + "\n";
        out.write(command.getBytes());
        out.flush();
        // System.out.print(" % sent command: " + command);

        if (checkAck(in) != 0) {
            try {
                if (fis != null)
                    fis.close();
            } catch (Exception ee) {
            }
            throw new SshException("Scp of file " + lfile + " to remote site failed\n");
        }

        // send the content of lfile
        int bytesSent = 0;
        int len = 0;
        while (true) {
            len = fis.read(buf, 0, buf.length);
            if (len <= 0)
                break;
            out.write(buf, 0, len);
            out.flush();
            bytesSent += len;
        }
        // System.out.println(" % sent file of bytes: " + bytesSent);

        // Finish copy of file: send '\0'
        buf[0] = 0;
        out.write(buf, 0, 1);
        out.flush();
        // System.out.println(" % sent zero\n");

        fis.close();
        fis = null;

        if (checkAck(in) != 0)
            throw new SshException("Scp to remote site failed\n");

        return 1;

    }

    protected static int checkAck(InputStream in) throws IOException, SshException {
        int b = in.read();
        // b may be 0 for success,
        // 1 for error,
        // 2 for fatal error,
        // -1

        // System.out.print("    --- checkAck = " + b + (b <= 0 ? "\n" : " "));
        if (b == 0)
            return b;
        if (b == -1)
            return b;

        if (b == 1 || b == 2 || b == 'E') {
            StringBuffer sb = new StringBuffer();
            int c;
            do {
                c = in.read();
                // System.out.print((char)c);
                sb.append((char) c);
            } while (c != '\n');
            // System.out.println(".");
            if (b == 1) { // error
                throw new SshException("Error at acknowledgement: " + sb.toString());
            }
            if (b == 2) { // fatal error
                throw new SshException("Fatal error at acknowledgement: " + sb.toString());
            }
        } else
            ; // System.out.println(".");
        return b;
    }

    /**
     * Anand: New method added to check if remote file is a directory
     * Run "cd" command on given file. It success, it is a directory, else it is not.
     * @param fileName : file which is to be checked to see if it is a directory
     * @return : true if it is a directory, else false
     * @throws Exception
     */
    public boolean isRemoteFileDirectory(String fileName) throws Exception {
        ChannelExec channel;
        OutputStream out;
        InputStream in;
        int exitCode = 5;
        // byte buf[] = new byte[1024];
        String command = "cd \"" + fileName + "\"";
        if (!openConnection())
            throw new SshException("Ssh connection could not be opened for copying.");

        synchronized (session) { // for thread safety here we need to sync
            channel = (ChannelExec) jschSession.openChannel("exec");
            channel.setCommand(command);
            // get I/O streams for remote scp
            out = channel.getOutputStream();
            in = channel.getInputStream();
            channel.connect();
        }
        while (true) {
            while (in.available() > 0) {
                int i = in.read();
                if (i < 0)
                    break;
            }
            if (channel.isClosed()) {
                exitCode = channel.getExitStatus();
                System.out.println("****************exit-status: " + channel.getExitStatus());
                break;
            }
            Thread.sleep(1000);
        }
        channel.disconnect();
        jschSession.disconnect();
        if (exitCode == 0)
            return true;
        else
            return false;
    }

    /**
     * Copy a remote file into a local file Input: 'rfile' of type String (can
     * be a directory or filename) 'localPath' is either a directory or filename
     * Only if 'recursive' is set, will directories copied recursively.
     * 
     * @return number of files copied successfully SshException is thrown in
     *         case of error.
     */
    public int copyFrom(String rfile, File localPath, boolean recursive) throws SshException {

        int numberOfCopiedFiles = 0;
        String recursiveFlag = recursive ? "-r " : "";
        String command = "";

        if (!openConnection())
            throw new SshException("Ssh connection could not be opened for copying.");
        // at this point we have a living, opened session to the remote machine

        command = protocolPath + "scp " + cmdLineOptions + " " + recursiveFlag + "-f " + rfile;

        if (isDebugging) {
            log.debug(" %   Copy " + rfile + " to " + localPath);
            log.debug("Command= " + command);
        }
        OutputStream out;
        InputStream in;

        try {
            ChannelExec channel;

            synchronized (session) { // for thread safety here we need to sync
                channel = (ChannelExec) jschSession.openChannel("exec");
                channel.setCommand(command);
                // get I/O streams for remote scp
                out = channel.getOutputStream();
                in = channel.getInputStream();
                channel.connect();
            }
            // Anand: Debug for SCP remote to local copy
            System.out.println("***********SCP : Remote to local : " + command);

            // perform the protocol of actual copy of file/directory
            numberOfCopiedFiles = sink(localPath, recursive, 0, in, out);

            // close channel
            channel.disconnect();

        } catch (Exception e) {
            throw new SshException("Exception caught at command: " + command + "\n" + e);
        }

        return numberOfCopiedFiles;
    }

    /**
     * Copy one file from the remote location. This is the core of the copyFrom
     * method, which receives a content of file from the remote ssh server using
     * the protocol of scp/rcp. If the received file is a directory, it does
     * this recursively. 'level' is used only for formatted log print.
     * 
     * @return The number of copied files, including the directories created.
     */
    private int sink(File localPath, boolean recursive, int level, InputStream in, OutputStream out)
            throws Exception {

        byte[] buf = new byte[1024];
        int numberOfCopiedFiles = 0;
        StringBuffer tab = new StringBuffer(level * 2);

        for (int i = 0; i <= level; i++)
            tab.append("  ");

        // initiate copy with acknowledgement (send a \0 byte)
        buf[0] = 0;
        out.write(buf, 0, 1);
        out.flush();

        boolean finished = false;
        while (!finished) {
            int c = checkAck(in);

            if (c == -1) {
                // no more files
                finished = true;
                break;
            }

            if (c == 'E') {
                // end of receiving a directory
                finished = true;
                break;
            }

            if (c != 'C' && c != 'D') // not a file or a directory to be copied
                // is the response
                throw new SshException("Scp from remote site failed\n");

            if (c == 'D' && !recursive) // directory is to be sent
                throw new SshException("Remote path is a directory, but not recursive copy is requested");

            // read: mode + length + filename
            // read '0644 ' or '0755 ' or something similar
            in.read(buf, 0, 5);

            long filesize = 0L;
            while (true) {
                if (in.read(buf, 0, 1) < 0) {
                    // error
                    break;
                }
                if (buf[0] == ' ')
                    break;
                filesize = filesize * 10L + (long) (buf[0] - '0');
            }

            String rFileName = null;
            for (int i = 0;; i++) {
                in.read(buf, i, 1);
                if (buf[i] == (byte) 0x0a) {
                    rFileName = new String(buf, 0, i);
                    break;
                }
            }

            // local file is either file localPath, or dir localPath + the
            // received name
            File lfile;
            if (localPath.isDirectory())
                lfile = new File(localPath, rFileName);
            else
                lfile = localPath;

            // if we receive a directory, we have to go recursive
            if (c == 'D') {
                if (!lfile.exists()) {
                    if (!lfile.mkdirs()) // create the directory
                        throw new SshException(lfile + ": Cannot create such directory");
                } else if (!lfile.isDirectory()) // already exists as a file
                    throw new SshException(lfile + ": Not a directory");

                // lfile is now an existing directory, fine
                if (isDebugging)
                    log.debug(" %   " + tab + rFileName + " -> " + lfile);
                numberOfCopiedFiles += 1 + sink(lfile, true, level + 1, in, out);
                // send acknowledgement (send a \0 byte)
                buf[0] = 0;
                out.write(buf, 0, 1);
                out.flush();
                continue;
            }

            if (isDebugging)
                log.debug(" %   " + tab + rFileName + " -> " + lfile);

            // now we deal with a single file. Let's read it.
            FileOutputStream fos = new FileOutputStream(lfile);

            // send acknowledgement (send a \0 byte)
            buf[0] = 0;
            out.write(buf, 0, 1);
            out.flush();

            int len;
            while (true) {
                if (buf.length < filesize)
                    len = buf.length;
                else
                    len = (int) filesize;
                len = in.read(buf, 0, len);
                if (len < 0) {
                    // error
                    break;
                }
                fos.write(buf, 0, len);
                filesize -= len;
                if (filesize == 0L)
                    break;
            }
            fos.close();
            fos = null;
            numberOfCopiedFiles++;

            if (checkAck(in) != 0)
                throw new SshException("Scp from remote site to file " + lfile + " failed\n");

            // send acknowledgement (send a \0 byte)
            buf[0] = 0;
            out.write(buf, 0, 1);
            out.flush();
        }

        return numberOfCopiedFiles;
    }

    /**
     * Separate class to process the output stream of the command, so that it
     * can be run in a separate thread. Note: we use InputStreamReader and not
     * BufferedReader because when the command asks for a password, it does not
     * send a newline with the request, so we would be blocked at readLine while
     * the command is waiting for response. Note: in case of timeout, the output
     * thread will forcefully disconnect the channel and if forcedCleanUp then
     * also kill the command process.
     */

    private class _streamReaderThread extends Thread {
        private InputStreamReader isr; // 'char' reader from the remote command
        private OutputStreamWriter osw; // 'char' writer to the caller's output
        // stream
        private boolean cleanUpInfoProcessed = !forcedCleanUp; // false: will
        // consume first
        // line for
        // process ID
        private String pwd; // the password to be fed to the command
        private PipedOutputStream pos; // the pipe-in to the stdin of the remote
        // command
        private ChannelExec channel;

        private StringBuffer processID; // the remote command's shell's process
        // id (to kill if needed)
        private boolean timeoutReached; // becomes true on timeout

        public _streamReaderThread(ChannelExec ch, InputStream in, OutputStream out, String pwd,
                PipedOutputStream pos) {
            try {
                isr = new InputStreamReader(in, "utf-8");
                osw = new OutputStreamWriter(out, "utf-8");
            } catch (UnsupportedEncodingException ex) {
                // get the default encoding
                isr = new InputStreamReader(in);
                osw = new OutputStreamWriter(out);
            }

            channel = ch;
            this.pwd = pwd;
            this.pos = pos;
        }

        public String getProcessID() {
            return processID.toString();
        }

        public boolean timeoutHappened() {
            return timeoutReached;
        }

        public void run() {
            char[] tmp = new char[1024];
            boolean checkForPwd = (pwd != null);
            processID = new StringBuffer();

            // variables for the timeout checking
            long start = System.currentTimeMillis();
            long current = 0;
            long maxtime = timeout * 1000L;

            // variables for the search for password request in the output
            // stream
            // int i; // search index variable
            // boolean foundpwd;

            while (true) { // read command's output until termination or timeout
                int len = 0;
                int j = 0;
                try {
                    while (isr.ready()) { // we do not want to block on read
                        // because we are counting for
                        // timeout
                        len = isr.read(tmp, 0, 1024);
                        if (len < 0) {
                            if (isDebugging)
                                log.debug("Read error on stdout stream: " + len);
                            break; // break the reading loop
                        }
                        j = 0;

                        // first line is remote process id in case of
                        // forcedCleanUp. Filter here
                        if (!cleanUpInfoProcessed) {
                            // if (isDebugging)
                            // log.debug("cleanup info string: " + new
                            // String(tmp, 0, len));
                            for (; j < len; j++) {
                                if (tmp[j] == '\n') {
                                    cleanUpInfoProcessed = true; // done
                                    j++;
                                    if (isDebugging)
                                        log.debug("Remote process id = " + processID);
                                    break; // break the reading loop
                                }
                                processID.append(tmp[j]);
                            }
                            // Note: j<=len here
                        }

                        // print the buffer to the output stream
                        osw.write(tmp, j, len - j);
                        osw.flush(); // send it really if someone is polling it
                        // above us
                        System.out.println(" %%% " + new String(tmp, j, len - j));
                        if (timeoutRestartOnStdout)
                            start = System.currentTimeMillis(); // restart
                        // timeout timer
                        String tempStr = new String(tmp, j, len - j);
                        // log.debug("%%%tempstr%%% "+tempStr);
                        if (tempStr.contains("RSA key fingerprint is") && tempStr.trim().endsWith("(yes/no)?")) {
                            boolean userInput = jschSession.getUserInfo().promptYesNo(tempStr);
                            log.debug("Prompt for host verification: " + tempStr);
                            if (userInput) {
                                pos.write("yes\n".getBytes());
                                pos.flush();
                                log.info("Added destination server to known_hosts of source");
                                continue;
                            } else {
                                pos.write("no\n".getBytes());
                                pos.flush();
                                pos.close();
                                log.error("Failed to accept RSA key fingerprint");
                                break;
                            }
                        }

                        if (checkForPwd && containsPasswordRequest(tmp, j, len)) {
                            // now feed the password to the process
                            try {
                                pos.write(pwd.getBytes());
                                // log.info("Sent password ");
                                pos.write("\n".getBytes());
                                // log.info("Sent newline ");
                                pos.flush();
                                // log.info("Flushed pos ");
                                pos.close();
                                log.info("Sent password to third party.");
                            } catch (IOException ex) {
                                log.error("Error when feeding the password to the piped stream: " + ex);
                            }
                            checkForPwd = false;
                        }

                    } // end while
                } catch (IOException ex) {
                    log.error("Error on the remote streams. Exiting reader thread: " + ex);
                    break; // exit the loop
                }
                if (channel.isClosed())
                    break; // exit the loop

                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                }

                // check timeout
                current = System.currentTimeMillis();
                if (timeout > 0 && maxtime < current - start) {
                    log.debug("Reader thread detected timeout: " + timeout + "s elapsed");
                    timeoutReached = true;
                    break; // exit the loop
                }
            } // while (true)

            try {
                osw.close();
            } catch (IOException ex) {
                log.error("Cannot flush and close the output stream: " + ex);
            }
        } // end method run()

        /**
         * Look for one of the strings password/passphrase/passcode in the
         * char[] array. Return true if found any. Case insensitive search.
         * Possible bug: we do not find the password text if it is broken into
         * two in two consecutive calls.
         */
        private boolean containsPasswordRequest(char[] buf, int startPos, int endPos) {
            // look for strings password/passphrase/passcode
            int i = startPos;
            while (i < endPos - 3) {
                if (Character.toLowerCase(buf[i]) == 'p' && Character.toLowerCase(buf[i + 1]) == 'a'
                        && Character.toLowerCase(buf[i + 2]) == 's' && Character.toLowerCase(buf[i + 3]) == 's') {

                    // found "pass", look further for word/code/phrase
                    if (i < endPos - 7 && Character.toLowerCase(buf[i + 4]) == 'w'
                            && Character.toLowerCase(buf[i + 5]) == 'o' && Character.toLowerCase(buf[i + 6]) == 'r'
                            && Character.toLowerCase(buf[i + 7]) == 'd') {
                        log.info("PWDSearch: found request for password.");
                        return true;
                    } else if (i < endPos - 7 && Character.toLowerCase(buf[i + 4]) == 'c'
                            && Character.toLowerCase(buf[i + 5]) == 'o' && Character.toLowerCase(buf[i + 6]) == 'd'
                            && Character.toLowerCase(buf[i + 7]) == 'e') {
                        log.info("PWDSearch: found request for passcode.");
                        return true;
                    } else if (i < endPos - 9 && Character.toLowerCase(buf[i + 4]) == 'p'
                            && Character.toLowerCase(buf[i + 5]) == 'h' && Character.toLowerCase(buf[i + 6]) == 'r'
                            && Character.toLowerCase(buf[i + 7]) == 'a' && Character.toLowerCase(buf[i + 8]) == 's'
                            && Character.toLowerCase(buf[i + 9]) == 'e') {
                        log.info("PWDSearch: found request for passphrase.");
                        return true;
                    }
                }
                i = i + 1;
            }
            return false;
        }
    } // end inner class _streamReaderThread

    public void setPseudoTerminal(boolean pseudoTerminal) {
        this.pseudoTerminal = pseudoTerminal;
    }

    public void setProtocolPath(String protocolPath) {
        this.protocolPath = protocolPath;
    }

    public void setcmdLineOptions(String cmdLineOptions) {
        this.cmdLineOptions = cmdLineOptions;
    }

    // @Override
    public boolean getForcedCleanUp() {
        return forcedCleanUp;
    }

    // @Override
    public int getTimeout() {
        return timeout;
    }

    public void setTimeout(int seconds) {
        timeout = seconds;

    }

    public String getwildcardFileListingBBCP(String srcConn, String wildcardPattern) throws ExecException {
        // TODO Auto-generated method stub
        String filePath = "";
        String filePattern = "";
        OutputStream streamOut = new ByteArrayOutputStream();
        OutputStream streamErr = new ByteArrayOutputStream();
        StringBuffer wildcardList = new StringBuffer();
        Vector<String> fileList = new Vector();
        int index = wildcardPattern.lastIndexOf("/");
        int exitCode = 1;

        //extract pattern and directory name from wildcard pattern
        if (index != -1) {
            filePattern = wildcardPattern.substring(index + 1);
            filePath = wildcardPattern.substring(0, index);
        }
        System.out.println("parent dir : " + filePath);
        System.out.println("File pattern : " + filePattern);

        try {
            //-Q is used to place file names into quotations
            exitCode = executeCmd("\\ls -Q " + filePath, streamOut, streamErr, srcConn);
        } catch (ExecException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            throw new ExecException("Could not list contents of remote directory " + filePath);
        }

        if (exitCode != 0) {
            throw new ExecException("Failed to execute remote command.");
        }

        //TODO Anand:extract filenames from ls entry
        // the character \n does not separate file names in a few scenarios.
        // To make the extraction more efficient - we are using a loop
        // If possible to get a separating character for ls field names use that instead of the loop 
        // IN THE LOOP: each filename has format "file_name"
        // We extract string in between " " to get the file name 
        String ls_output = streamOut.toString();
        int f_start = 0;
        int f_end = 0;
        while (f_end < ls_output.length()) {
            f_start = ls_output.indexOf("\"");
            ls_output = ls_output.substring(f_start + 1);
            f_end = ls_output.indexOf("\"");
            fileList.add(ls_output.substring(0, f_end));
            ls_output = ls_output.substring(f_end + 1);
        }

        //match filenames to pattern and create a file list
        String pattern = filePattern.replaceAll("\\.", "\\\\.").replaceAll("\\*", ".*").replaceAll("\\?", ".");
        Pattern p = Pattern.compile(pattern);

        for (String curFile : fileList) {
            System.out.println(":" + curFile + ";");
            curFile = curFile.trim();
            Matcher m = p.matcher(curFile);
            if (m.matches()) {
                wildcardList.append(srcConn);
                wildcardList.append(":\"");
                wildcardList.append(filePath);
                wildcardList.append("/");
                wildcardList.append(curFile);
                wildcardList.append("\"");
                wildcardList.append(" ");
            }
        }
        System.out.println("wild card list formed is : " + wildcardList);

        return wildcardList.toString();
    }
} // end-of-class Ssh