001/*
002 * Copyright (c) 2004-2010 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: barseghian $'
006 * '$Date: 2012-06-15 20:43:25 +0000 (Fri, 15 Jun 2012) $' 
007 * '$Revision: 29954 $'
008 * 
009 * Permission is hereby granted, without written agreement and without
010 * license or royalty fees, to use, copy, modify, and distribute this
011 * software and its documentation for any purpose, provided that the above
012 * copyright notice and the following two paragraphs appear in all copies
013 * of this software.
014 *
015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
019 * SUCH DAMAGE.
020 *
021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
026 * ENHANCEMENTS, OR MODIFICATIONS.
027 *
028 */
029
030package org.kepler.ssh;
031
032import java.io.ByteArrayOutputStream;
033import java.io.File;
034import java.io.FileInputStream;
035import java.io.FileOutputStream;
036import java.io.IOException;
037import java.io.InputStream;
038import java.io.InputStreamReader;
039import java.io.OutputStream;
040import java.io.OutputStreamWriter;
041import java.io.PipedInputStream;
042import java.io.PipedOutputStream;
043import java.io.UnsupportedEncodingException;
044import java.util.Vector;
045import java.util.regex.Matcher;
046import java.util.regex.Pattern;
047
048import org.apache.commons.logging.Log;
049import org.apache.commons.logging.LogFactory;
050
051import com.jcraft.jsch.ChannelExec;
052import com.jcraft.jsch.JSchException;
053import com.jcraft.jsch.Session;
054
055/**
056 * This class provides functionality to use an SSH session to execute remote
057 * commands and to transfer files on a remote machine.
058 * 
059 * A session can be opened using one the following authentication protocols: -
060 * public-key - password - keyboard-interactive
061 * 
062 * You can create several sessions to different sites and use any of them
063 * referring to it with its user@host.
064 * 
065 * An Ssh object can be used then by anyone anytime to copy file or execute
066 * commands without authenticating again. If the session becomes disconnected,
067 * the connection will be reopened at the next use, and authentication will be
068 * either automatic (public-key, or stored
069 * 
070 * <p>
071 * 
072 * @author Norbert Podhorszki
073 * 
074 *         Based on - JSch examples http://www.jcraft.com/jsch - OpenSSH 4.3
075 *         source code http://www.openssh.com/ - org.sdm.spa.Ssh2Exec Kepler
076 *         actor Authors of the Kepler actor: Ilkay Altintas, Xiaowen Xin
077 */
078
079public class SshExec extends RemoteExec {
080
081        /* Private variables */
082        protected SshSession session;
083        protected Session jschSession;
084
085        private static final Log log = LogFactory.getLog(SshExec.class.getName());
086        private static final boolean isDebugging = log.isDebugEnabled();
087
088        // timeout variables
089        private int timeout = 0; // timeout in seconds
090        private boolean timeoutRestartOnStdout = false; // restart timer if stdout
091        // has data
092        private boolean timeoutRestartOnStderr = false; // restart timer if stderr
093        // has data
094
095        // forced clean-up variables
096        private boolean forcedCleanUp = false;
097        private static String cleanUpInfoCmd = new String("echo $$; ");
098
099        // variables added to extend SshExec
100        private boolean pseudoTerminal = false;
101        private String protocolPath = "";
102        private String cmdLineOptions = "";
103
104        public SshExec(String user, String host) {
105                session = SshSessionFactory.getSession(user, host, 22);
106        }
107
108        public SshExec(String user, String host, int port) {
109                session = SshSessionFactory.getSession(user, host, port);
110        }
111
112        /**
113         * Create and SshExec object from a combined target string. Input: string of
114         * format [user@]host[:port]
115         */
116        public SshExec(String target) {
117                // get USER
118                String user, host;
119                int port = 22;
120
121                int atPos = target.indexOf('@');
122                if (atPos >= 0) {
123                        user = target.substring(0, target.indexOf('@'));
124                } else {
125                        user = System.getProperty("user.name");
126                }
127
128                // get the HOST and PORT
129                int colonPos = target.indexOf(':');
130                if (colonPos >= 0 && colonPos > atPos) {
131                        host = target.substring(atPos + 1, colonPos);
132                        String portStr = target.substring(colonPos + 1);
133                        try {
134                                port = Integer.parseInt(portStr);
135                        } catch (java.lang.NumberFormatException ex) {
136                                log
137                                                .error("The port should be a number or omitted in "
138                                                                + target);
139                        }
140                } else
141                        host = target.substring(atPos + 1);
142                session = SshSessionFactory.getSession(user, host, port);
143        }
144
145        /**
146         * Open the connection to the remote machine now. It is not necessary to
147         * call this method! If you do not want to prolong making the connection
148         * until the first executeCmd / copyTo / copyFrom, then you need to use this
149         * method.
150         */
151        public boolean openConnection() throws SshException {
152                if (null == session)
153                        return false;
154
155                jschSession = session.open();
156
157                try {
158                        // send alive messages every 30 seconds to avoid
159                        // drops when running long-lasting commands without much stdout
160                        jschSession.setServerAliveInterval(30000);
161                } catch (JSchException ex) {
162                        log.warn("ServerAliveInterval could not be set for session "
163                                        + session.getUser() + "@" + session.getHost() + ": " + ex);
164                }
165
166                return true;
167        }
168
169        /**
170         * Close the connection This should be called only when there is no more use
171         * of the connection to this user@host Important: the ssh session remains
172         * opened as long as this method is not called, therefore, the the whole
173         * java application will hang at the end until this method is called.
174         */
175        public void closeConnection() {
176                if (null != session)
177                        session.close();
178                // session = null; // disallow reconnection
179                jschSession = null;
180        }
181
182        /**
183         * Add identity file Public-key authentication can be used if you add
184         * identity files
185         */
186        public void addIdentity(String identity) {
187                try {
188                        if (null != session)
189                                session.addIdentity(identity);
190                } catch (SshException e) {
191                        log.error("addIdentity error: " + e.toString());
192                        ; // don't do anything here at this moment
193                }
194        }
195
196        public void addIdentity(File identity) {
197                addIdentity(identity.getPath());
198        }
199
200        /**
201         * Set a local port forwarding before the connection is made. Format of the
202         * specification: lport:rhost:rport (int:string:int). lport on this host
203         * will be forwarded through this session and the remote host to the
204         * specified rhost:rport address.
205         */
206        public void setPortForwardingL(String spec) throws SshException {
207                session.setPortForwardingL(spec);
208        }
209
210        /**
211         * Add a local port forwarding to an open connection. Format of the
212         * specification: lport:rhost:rport (int:string:int). lport on this host
213         * will be forwarded through this session and the remote host to the
214         * specified rhost:rport address.
215         */
216        public void addPortForwardL(String spec) {
217                session.addPortForwardL(spec);
218        }
219
220        /**
221         * Set a remote port forwarding before the connection is made. Format of the
222         * specification: rport:lhost:lport (int:string:int). rport on remote host
223         * will be forwarded through this session and our local host to the
224         * specified lhost:lport address.
225         */
226        public void setPortForwardingR(String spec) throws SshException {
227                session.setPortForwardingR(spec);
228        }
229        
230        /**
231         * Add a remote port forwarding to an open connection. Format of the
232         * specification: rport:lhost:lport (int:string:int). rport on remote host
233         * will be forwarded through this session and our local host to the
234         * specified lhost:lport address.
235         */
236        public void addPortForwardR(String spec) {
237                session.addPortForwardR(spec);
238        }
239
240        /**
241         * Remove a local port forwarding.
242         * @param port the local port that is forwarded.
243         * @param closeIfLast If true, and there are no additional local ports
244         * forwarded, close the connection.
245         */
246        public void removePortForwardL(int port, boolean closeIfLast) throws SshException {
247                session.removePortForwardL(port, closeIfLast);
248        }
249        
250        /**
251         * Remove a remote port forwarding.
252         * @param port the remote port that is forwarded.
253         * @param closeIfLast If true, and there are no additional remote ports
254         * forwarded, close the connection.
255         */
256        public void removePortForwardR(int port, boolean closeIfLast) throws SshException {
257                session.removePortForwardR(port, closeIfLast);
258        }
259        
260        /**
261         * Set timeout for the operations. Timeout should be given in seconds. If
262         * 'stdout' is set to true, the timer is restarted whenever there is data on
263         * stdout. If 'stderr' is set to true, the timer is restarted whenever there
264         * is data on stderr. executeCmd will throw an ExecException, an instance of
265         * ExecTimeoutException if the timeout limit is reached. 'seconds' = 0 means
266         * no timeout at all. Note: copyTo and copyFrom operations currently do not
267         * support timeout. Note: currently, the timer cannot be restarted on stderr
268         * events in executeCmd
269         */
270        public void setTimeout(int seconds, boolean stdout, boolean stderr) {
271                timeout = seconds;
272                timeoutRestartOnStdout = stdout;
273                timeoutRestartOnStderr = stderr;
274        }
275
276        /**
277         * Specify if killing of remote processes (i.e. clean-up) after error or
278         * timeout is required. Unix specific solution is used for clean-up, so do
279         * not use it when connecting to another servers. Default is false, of
280         * course, but use it whenever you can (i.e. connect to unix machines).
281         */
282        public void setForcedCleanUp(boolean foo) {
283                forcedCleanUp = foo;
284        }
285
286        /**
287         * Execute a command on the remote machine. The streams <i>streamOut</i> and
288         * <i>streamErr</i> should be provided to get the output and errors.
289         * 
290         * @return exit code of command if execution succeeded,
291         * @throws ExecTimeoutException
292         *             if the command failed because of timeout
293         * @throws SshException
294         *             if an error occurs for the ssh connection during the command
295         *             execution
296         */
297        public int executeCmd(String command, OutputStream streamOut,
298                        OutputStream streamErr) throws ExecException {
299                return executeCmd(command, streamOut, streamErr, null);
300        } // end-of-method executeCmd
301
302        /**
303         * Execute a command on the remote machine and expect a password/passphrase
304         * question from the command. The stream <i>streamOut</i> should be provided
305         * to get the output and errors merged. <i>streamErr</i> is not used in this
306         * method (it will be empty string finally).
307         * 
308         * @return exit code of command if execution succeeded,
309         * @throws ExecTimeoutException
310         *             if the command failed because of timeout
311         * @throws SshException
312         *             if an error occurs for the ssh connection during the command
313         *             execution Note: in this method, the SSH Channel is forcing a
314         *             pseudo-terminal allocation {see setPty(true)} to allow remote
315         *             commands to read something from their stdin (i.e. from us
316         *             here), thus, (1) remote environment is not set from
317         *             .bashrc/.cshrc and (2) stdout and stderr come back merged in
318         *             one stream.
319         */
320        public int executeCmd(String command, OutputStream streamOut,
321                        OutputStream streamErr, String thirdPartyTarget)
322                        throws ExecException {
323
324                int exitCode = 0;
325                String cmd = forcedCleanUp ? cleanUpInfoCmd + command : command;
326                openConnection();
327                
328                // get the pwd to the third party (and perform authentication if not yet
329                // done)
330                String pwd = SshSession.getPwdToThirdParty(thirdPartyTarget);
331
332                // create a piped stream to feed the input of the remote exec channel
333                PipedInputStream pis = null;
334                PipedOutputStream pos = null;
335                if (pwd != null) {
336                        try {
337                                pis = new PipedInputStream();
338                                pos = new PipedOutputStream(pis);
339                        } catch (IOException ex) {
340                                log
341                                                .error("Error when creating the piped stream for password feededing: "
342                                                                + ex);
343                        }
344                }
345                // At this point we have an opened session to the remote machine.
346                // But session may be down, so this complex trial cycle here.
347                
348                boolean tryagain = true;
349                while (tryagain) {
350                        tryagain = false;
351                        InputStream in = null;
352                        try {
353                                pwd = SshSession.getPwdToThirdParty(thirdPartyTarget);
354                                ChannelExec channel = null;
355                                _streamReaderThread readerThread;
356                                synchronized (session) {
357                                        channel = (ChannelExec) jschSession.openChannel("exec");
358                                        if (isDebugging)
359                                                log.debug("pseudoTerminal=" + pseudoTerminal);
360                                        channel.setPty(pseudoTerminal);
361                                        channel.setCommand(cmd);
362                                        // channel.setOutputStream(streamOut); // use rather
363                                        // getInputStream and read it
364                                        channel.setErrStream(streamErr); // Useless!! stderr goes
365                                        // into stdout
366                                        channel.setInputStream(pis); // remote command will read
367                                        // from our stream
368                                        in = channel.getInputStream();
369
370                                        // start output processing thread
371                                        // it checks for timeout too
372                                        readerThread = new _streamReaderThread(channel, in,
373                                                        streamOut, pwd, pos);
374                                        readerThread.start();
375
376                                        channel.connect(); // command starts here but we get back
377                                        // the control
378                                        // this thread runs further but reading of pis by the remote
379                                        // process may block it
380                                }
381                                if (isDebugging) {
382                                        log
383                                                        .debug("Started remote execution of command: "
384                                                                        + command);
385                                }
386                                // wait for the reader thread to finish
387                                // It will timeout at the latest if the command does not finish
388                                // 3 ways to finish:
389                                // - command terminates
390                                // - timeout
391                                // - IOException when reading the command's output or writing
392                                // the caller's output
393                                readerThread.join();
394
395                                // on timeout finish here with a nice Exception
396                                if (readerThread.timeoutHappened()) {
397                                        log.error("Timeout: " + timeout + "s elapsed for command "
398                                                        + command);
399                                        // BUG?: disconnect does not kill the remote process!!!
400                                        // Clean-up should be done somehow
401                                        channel.disconnect();
402                                        if (forcedCleanUp) {
403                                                // time for clean-up ;-)
404                                                kill(readerThread.getProcessID(), true);
405                                        }
406                                        throw new ExecTimeoutException(command);
407                                }
408                                // if we cannot process output, still wait for the channel to be
409                                // closed
410                                // !!! This can lead to hang-up !!!
411                                while (!channel.isClosed()) {
412                                        try {
413                                                Thread.sleep(500);
414                                        } catch (Exception e) {
415                                        }
416                                }
417                                // command completed successfully
418                                if (isDebugging)
419                                        log
420                                                        .debug("Command execution terminated, now waiting for the channel to be closed.");
421
422                                if (isDebugging)
423                                        log.debug("Channel closed down, now get the exit status.");
424                                // this is the wrong test if the remote OS is OpenVMS,
425                                // but there doesn't seem to be a way to detect it.
426                                // Note: it must be called only when channel.isClosed() becomes
427                                // true.
428                                // Otherwise it may return -1, being not yet set to the exit
429                                // code
430                                exitCode = channel.getExitStatus();
431                                if (isDebugging)
432                                        log.debug("Exit status = " + exitCode
433                                                        + ". Now disconnect channel.");
434                                // Note: The jsch source suggests that when the exit code is
435                                // set, the
436                                // package disconnects the channel, so the extra call to
437                                // disconnect()
438                                // may be unnecessary. But I inherited this way and am tired of
439                                // debugging...
440                                channel.disconnect();
441
442                                if (exitCode != 0 && forcedCleanUp) {
443                                        // what is sure is sure ;-)
444                                        kill(readerThread.getProcessID(), true);
445                                }
446
447                        } catch (JSchException ex) {
448                                if (ex.toString().indexOf("session is down") > -1) {
449                                        log
450                                                        .error("Session to " + session.getUser() + "@"
451                                                                        + session.getHost()
452                                                                        + " is down, try connect again");
453                                        closeConnection();
454                                        openConnection();
455                                        tryagain = true;
456                                } else {
457                                        throw new SshException("JSchException caught at command: "
458                                                        + command + "\n" + ex);
459                                }
460                        } catch (Exception e) {
461                                throw new SshException("Exception caught at command: "
462                                                + command + "\n" + e);
463                        }
464                } // end while
465
466                return exitCode;
467
468        } // end-of-method executeCmd
469
470        /**
471         * Copy _one_ local file to a remote directory Input: file of type File
472         * (which can be a directory) Input must not have wildcards. targetPath is
473         * either a directory or filename
474         * 
475         * @return number of files copied successfully SshException is thrown in
476         *         case of error.
477         */
478        protected int _copyTo(File lfile, String targetPath, boolean recursive)
479                        throws SshException {
480
481                if (!lfile.exists()) {
482                        throw new SshException("File does not exist: " + lfile);
483                }
484
485                String recursiveFlag = "";
486                // check: recursive traversal of directories enabled?
487                if (lfile.isDirectory()) {
488                        if (!recursive)
489                                throw new SshException("File " + lfile
490                                                + " is a directory. Set recursive copy!");
491                        recursiveFlag = "-r ";
492                }
493                if (!openConnection())
494                        throw new SshException(
495                                        "Ssh connection could not be opened for copying.");
496
497                // at this point we have a living, opened session to the remote machine
498                int numberOfCopiedFiles = 0;
499
500                if (isDebugging)
501                        log.debug(" %   Copy " + lfile + " to " + targetPath);
502                String command = protocolPath + "scp " + cmdLineOptions + " "
503                                + recursiveFlag + "-p -t " + targetPath;
504                OutputStream out;
505                InputStream in;
506
507                try {
508                        ChannelExec channel;
509
510                        synchronized (session) { // for thread safety here we need to sync
511                                channel = (ChannelExec) jschSession.openChannel("exec");
512                                channel.setCommand(command);
513                                // get I/O streams for remote scp
514                                out = channel.getOutputStream();
515                                in = channel.getInputStream();
516                                channel.connect();
517                        }
518
519                        if (checkAck(in) != 0)
520                                throw new SshException("Scp to remote site failed\n");
521
522                        // perform the protocol of actual copy of file/directory
523                        numberOfCopiedFiles = source(lfile, in, out);
524
525                        // close channel
526                        channel.disconnect();
527
528                } catch (Exception e) {
529                        throw new SshException("Exception caught at command: " + command
530                                        + "\n" + e);
531                }
532
533                return numberOfCopiedFiles;
534        }
535
536        /**
537         * Copy one file to the remote location. This is the core of the copyTo
538         * method, which sends a content of file to the remote ssh server using the
539         * protocol of scp/rcp. If 'lfile' is a directory, it does this recursively.
540         * 
541         * @return The number of copied files, including the directories created.
542         */
543        private int source(File lfile, InputStream in, OutputStream out)
544                        throws Exception {
545
546                // String accessRights="644"; // rw-r--r--
547                String accessRights = "755"; // rwxr-xr-x
548                byte[] buf = new byte[1024];
549
550                String command;
551
552                // recursive handling of directories
553                if (lfile.isDirectory()) {
554                        File[] files = lfile.listFiles();
555                        if (files != null) {
556                                // send "C0755 0 filename", where filename should not include
557                                // '/'
558                                command = "D0" + accessRights + " 0 " + lfile.getName() + "\n";
559                                out.write(command.getBytes());
560                                out.flush();
561                                int numberOfCopiedFiles = 0;
562                                for (int i = 0; i < files.length; i++) {
563                                        if (isDebugging)
564                                                log.debug(" %     " + files[i]);
565                                        numberOfCopiedFiles += source(files[i], in, out);
566                                }
567                                // End of directory: send "E\n"
568                                buf[0] = 'E';
569                                buf[1] = '\n';
570                                out.write(buf, 0, 2);
571                                out.flush();
572                                return numberOfCopiedFiles + 1;
573                        } else {
574                                throw new SshException(
575                                                "Unable to read directory "
576                                                                + lfile.getPath()
577                                                                + "\n Please check if the directory exists and has appropriate permission");
578                        }
579                }
580
581                // Now we deal with one single file, let's send it
582                FileInputStream fis = new FileInputStream(lfile);
583
584                // send "C0644 filesize filename", where filename should not include '/'
585                command = "C0" + accessRights + " " + lfile.length() + " "
586                                + lfile.getName() + "\n";
587                out.write(command.getBytes());
588                out.flush();
589                // System.out.print(" % sent command: " + command);
590
591                if (checkAck(in) != 0) {
592                        try {
593                                if (fis != null)
594                                        fis.close();
595                        } catch (Exception ee) {
596                        }
597                        throw new SshException("Scp of file " + lfile
598                                        + " to remote site failed\n");
599                }
600
601                // send the content of lfile
602                int bytesSent = 0;
603                int len = 0;
604                while (true) {
605                        len = fis.read(buf, 0, buf.length);
606                        if (len <= 0)
607                                break;
608                        out.write(buf, 0, len);
609                        out.flush();
610                        bytesSent += len;
611                }
612                // System.out.println(" % sent file of bytes: " + bytesSent);
613
614                // Finish copy of file: send '\0'
615                buf[0] = 0;
616                out.write(buf, 0, 1);
617                out.flush();
618                // System.out.println(" % sent zero\n");
619
620                fis.close();
621                fis = null;
622
623                if (checkAck(in) != 0)
624                        throw new SshException("Scp to remote site failed\n");
625
626                return 1;
627
628        }
629
630        protected static int checkAck(InputStream in) throws IOException,
631                        SshException {
632                int b = in.read();
633                // b may be 0 for success,
634                // 1 for error,
635                // 2 for fatal error,
636                // -1
637
638                // System.out.print("    --- checkAck = " + b + (b <= 0 ? "\n" : " "));
639                if (b == 0)
640                        return b;
641                if (b == -1)
642                        return b;
643
644                if (b == 1 || b == 2 || b == 'E') {
645                        StringBuffer sb = new StringBuffer();
646                        int c;
647                        do {
648                                c = in.read();
649                                // System.out.print((char)c);
650                                sb.append((char) c);
651                        } while (c != '\n');
652                        // System.out.println(".");
653                        if (b == 1) { // error
654                                throw new SshException("Error at acknowledgement: "
655                                                + sb.toString());
656                        }
657                        if (b == 2) { // fatal error
658                                throw new SshException("Fatal error at acknowledgement: "
659                                                + sb.toString());
660                        }
661                } else
662                        ; // System.out.println(".");
663                return b;
664        }
665/**
666 * Anand: New method added to check if remote file is a directory
667 * Run "cd" command on given file. It success, it is a directory, else it is not.
668 * @param fileName : file which is to be checked to see if it is a directory
669 * @return : true if it is a directory, else false
670 * @throws Exception
671 */
672        public boolean isRemoteFileDirectory(String fileName) throws Exception {
673                ChannelExec channel;
674                OutputStream out;
675                InputStream in;
676                int exitCode = 5;
677                // byte buf[] = new byte[1024];
678                String command = "cd \"" + fileName + "\"";
679                if (!openConnection())
680                        throw new SshException(
681                                        "Ssh connection could not be opened for copying.");
682
683                synchronized (session) { // for thread safety here we need to sync
684                        channel = (ChannelExec) jschSession.openChannel("exec");
685                        channel.setCommand(command);
686                        // get I/O streams for remote scp
687                        out = channel.getOutputStream();
688                        in = channel.getInputStream();
689                        channel.connect();
690                }
691                while (true) {
692                        while (in.available() > 0) {
693                                int i = in.read();
694                                if (i < 0)
695                                        break;
696                        }
697                        if (channel.isClosed()) {
698                                exitCode = channel.getExitStatus();
699                                System.out.println("****************exit-status: "
700                                                + channel.getExitStatus());
701                                break;
702                        }
703                        Thread.sleep(1000);
704                }
705                channel.disconnect();
706                jschSession.disconnect();
707                if (exitCode == 0)
708                        return true;
709                else
710                        return false;
711        }
712
713        /**
714         * Copy a remote file into a local file Input: 'rfile' of type String (can
715         * be a directory or filename) 'localPath' is either a directory or filename
716         * Only if 'recursive' is set, will directories copied recursively.
717         * 
718         * @return number of files copied successfully SshException is thrown in
719         *         case of error.
720         */
721        public int copyFrom(String rfile, File localPath, boolean recursive)
722                        throws SshException {
723
724                int numberOfCopiedFiles = 0;
725                String recursiveFlag = recursive ? "-r " : "";
726                String command = "";
727                
728                if (!openConnection())
729                        throw new SshException(
730                                        "Ssh connection could not be opened for copying.");
731                // at this point we have a living, opened session to the remote machine
732                
733                command = protocolPath + "scp " + cmdLineOptions + " "
734                        + recursiveFlag + "-f " + rfile;                        
735                
736                if (isDebugging) {
737                        log.debug(" %   Copy " + rfile + " to " + localPath);
738                        log.debug("Command= " + command);
739                }
740                OutputStream out;
741                InputStream in;
742
743                try {
744                        ChannelExec channel;
745
746                        synchronized (session) { // for thread safety here we need to sync
747                                channel = (ChannelExec) jschSession.openChannel("exec");
748                                channel.setCommand(command);
749                                // get I/O streams for remote scp
750                                out = channel.getOutputStream();
751                                in = channel.getInputStream();
752                                channel.connect();
753                        }
754                        // Anand: Debug for SCP remote to local copy
755                        System.out.println("***********SCP : Remote to local : " + command);
756
757                        // perform the protocol of actual copy of file/directory
758                        numberOfCopiedFiles = sink(localPath, recursive, 0, in, out);
759
760                        // close channel
761                        channel.disconnect();
762
763                } catch (Exception e) {
764                        throw new SshException("Exception caught at command: " + command
765                                        + "\n" + e);
766                }
767
768                return numberOfCopiedFiles;
769        }
770
771        /**
772         * Copy one file from the remote location. This is the core of the copyFrom
773         * method, which receives a content of file from the remote ssh server using
774         * the protocol of scp/rcp. If the received file is a directory, it does
775         * this recursively. 'level' is used only for formatted log print.
776         * 
777         * @return The number of copied files, including the directories created.
778         */
779        private int sink(File localPath, boolean recursive, int level,
780                        InputStream in, OutputStream out) throws Exception {
781
782                byte[] buf = new byte[1024];
783                int numberOfCopiedFiles = 0;
784                StringBuffer tab = new StringBuffer(level * 2);
785
786                for (int i = 0; i <= level; i++)
787                        tab.append("  ");
788
789                // initiate copy with acknowledgement (send a \0 byte)
790                buf[0] = 0;
791                out.write(buf, 0, 1);
792                out.flush();
793
794                boolean finished = false;
795                while (!finished) {
796                        int c = checkAck(in);
797
798                        if (c == -1) {
799                                // no more files
800                                finished = true;
801                                break;
802                        }
803
804                        if (c == 'E') {
805                                // end of receiving a directory
806                                finished = true;
807                                break;
808                        }
809
810                        if (c != 'C' && c != 'D') // not a file or a directory to be copied
811                                // is the response
812                                throw new SshException("Scp from remote site failed\n");
813
814                        if (c == 'D' && !recursive) // directory is to be sent
815                                throw new SshException(
816                                                "Remote path is a directory, but not recursive copy is requested");
817
818                        // read: mode + length + filename
819                        // read '0644 ' or '0755 ' or something similar
820                        in.read(buf, 0, 5);
821
822                        long filesize = 0L;
823                        while (true) {
824                                if (in.read(buf, 0, 1) < 0) {
825                                        // error
826                                        break;
827                                }
828                                if (buf[0] == ' ')
829                                        break;
830                                filesize = filesize * 10L + (long) (buf[0] - '0');
831                        }
832
833                        String rFileName = null;
834                        for (int i = 0;; i++) {
835                                in.read(buf, i, 1);
836                                if (buf[i] == (byte) 0x0a) {
837                                        rFileName = new String(buf, 0, i);
838                                        break;
839                                }
840                        }
841
842                        // local file is either file localPath, or dir localPath + the
843                        // received name
844                        File lfile;
845                        if (localPath.isDirectory())
846                                lfile = new File(localPath, rFileName);
847                        else
848                                lfile = localPath;
849
850                        // if we receive a directory, we have to go recursive
851                        if (c == 'D') {
852                                if (!lfile.exists()) {
853                                        if (!lfile.mkdirs()) // create the directory
854                                                throw new SshException(lfile
855                                                                + ": Cannot create such directory");
856                                } else if (!lfile.isDirectory()) // already exists as a file
857                                        throw new SshException(lfile + ": Not a directory");
858
859                                // lfile is now an existing directory, fine
860                                if (isDebugging)
861                                        log.debug(" %   " + tab + rFileName + " -> " + lfile);
862                                numberOfCopiedFiles += 1 + sink(lfile, true, level + 1, in, out);
863                                // send acknowledgement (send a \0 byte)
864                                buf[0] = 0;
865                                out.write(buf, 0, 1);
866                                out.flush();
867                                continue;
868                        }
869
870                        if (isDebugging)
871                                log.debug(" %   " + tab + rFileName + " -> " + lfile);
872
873                        // now we deal with a single file. Let's read it.
874                        FileOutputStream fos = new FileOutputStream(lfile);
875
876                        // send acknowledgement (send a \0 byte)
877                        buf[0] = 0;
878                        out.write(buf, 0, 1);
879                        out.flush();
880
881                        int len;
882                        while (true) {
883                                if (buf.length < filesize)
884                                        len = buf.length;
885                                else
886                                        len = (int) filesize;
887                                len = in.read(buf, 0, len);
888                                if (len < 0) {
889                                        // error
890                                        break;
891                                }
892                                fos.write(buf, 0, len);
893                                filesize -= len;
894                                if (filesize == 0L)
895                                        break;
896                        }
897                        fos.close();
898                        fos = null;
899                        numberOfCopiedFiles++;
900
901                        if (checkAck(in) != 0)
902                                throw new SshException("Scp from remote site to file " + lfile
903                                                + " failed\n");
904
905                        // send acknowledgement (send a \0 byte)
906                        buf[0] = 0;
907                        out.write(buf, 0, 1);
908                        out.flush();
909                }
910
911                return numberOfCopiedFiles;
912        }
913
914        /**
915         * Separate class to process the output stream of the command, so that it
916         * can be run in a separate thread. Note: we use InputStreamReader and not
917         * BufferedReader because when the command asks for a password, it does not
918         * send a newline with the request, so we would be blocked at readLine while
919         * the command is waiting for response. Note: in case of timeout, the output
920         * thread will forcefully disconnect the channel and if forcedCleanUp then
921         * also kill the command process.
922         */
923
924        private class _streamReaderThread extends Thread {
925                private InputStreamReader isr; // 'char' reader from the remote command
926                private OutputStreamWriter osw; // 'char' writer to the caller's output
927                // stream
928                private boolean cleanUpInfoProcessed = !forcedCleanUp; // false: will
929                // consume first
930                // line for
931                // process ID
932                private String pwd; // the password to be fed to the command
933                private PipedOutputStream pos; // the pipe-in to the stdin of the remote
934                // command
935                private ChannelExec channel;
936
937                private StringBuffer processID; // the remote command's shell's process
938                // id (to kill if needed)
939                private boolean timeoutReached; // becomes true on timeout
940
941                public _streamReaderThread(ChannelExec ch, InputStream in,
942                                OutputStream out, String pwd, PipedOutputStream pos) {
943                        try {
944                                isr = new InputStreamReader(in, "utf-8");
945                                osw = new OutputStreamWriter(out, "utf-8");
946                        } catch (UnsupportedEncodingException ex) {
947                                // get the default encoding
948                                isr = new InputStreamReader(in);
949                                osw = new OutputStreamWriter(out);
950                        }
951
952                        channel = ch;
953                        this.pwd = pwd;
954                        this.pos = pos;
955                }
956
957                public String getProcessID() {
958                        return processID.toString();
959                }
960
961                public boolean timeoutHappened() {
962                        return timeoutReached;
963                }
964
965                public void run() {
966                        char[] tmp = new char[1024];
967                        boolean checkForPwd = (pwd != null);
968                        processID = new StringBuffer();
969
970                        // variables for the timeout checking
971                        long start = System.currentTimeMillis();
972                        long current = 0;
973                        long maxtime = timeout * 1000L;
974
975                        // variables for the search for password request in the output
976                        // stream
977                        // int i; // search index variable
978                        // boolean foundpwd;
979
980                        while (true) { // read command's output until termination or timeout
981                                int len = 0;
982                                int j = 0;
983                                try {
984                                        while (isr.ready()) { // we do not want to block on read
985                                                // because we are counting for
986                                                // timeout
987                                                len = isr.read(tmp, 0, 1024);
988                                                if (len < 0) {
989                                                        if (isDebugging)
990                                                                log
991                                                                                .debug("Read error on stdout stream: "
992                                                                                                + len);
993                                                        break; // break the reading loop
994                                                }
995                                                j = 0;
996
997                                                // first line is remote process id in case of
998                                                // forcedCleanUp. Filter here
999                                                if (!cleanUpInfoProcessed) {
1000                                                        // if (isDebugging)
1001                                                        // log.debug("cleanup info string: " + new
1002                                                        // String(tmp, 0, len));
1003                                                        for (; j < len; j++) {
1004                                                                if (tmp[j] == '\n') {
1005                                                                        cleanUpInfoProcessed = true; // done
1006                                                                        j++;
1007                                                                        if (isDebugging)
1008                                                                                log.debug("Remote process id = "
1009                                                                                                + processID);
1010                                                                        break; // break the reading loop
1011                                                                }
1012                                                                processID.append(tmp[j]);
1013                                                        }
1014                                                        // Note: j<=len here
1015                                                }
1016
1017                                                // print the buffer to the output stream
1018                                                osw.write(tmp, j, len - j);
1019                                                osw.flush(); // send it really if someone is polling it
1020                                                // above us
1021                                                System.out.println(" %%% "
1022                                                                + new String(tmp, j, len - j));
1023                                                if (timeoutRestartOnStdout)
1024                                                        start = System.currentTimeMillis(); // restart
1025                                                // timeout timer
1026                                                String tempStr = new String(tmp, j, len - j);
1027                                                // log.debug("%%%tempstr%%% "+tempStr);
1028                                                if (tempStr.contains("RSA key fingerprint is")
1029                                                                && tempStr.trim().endsWith("(yes/no)?")) {
1030                                                        boolean userInput = jschSession.getUserInfo()
1031                                                                        .promptYesNo(tempStr);
1032                                                        log.debug("Prompt for host verification: "
1033                                                                        + tempStr);
1034                                                        if (userInput) {
1035                                                                pos.write("yes\n".getBytes());
1036                                                                pos.flush();
1037                                                                log
1038                                                                                .info("Added destination server to known_hosts of source");
1039                                                                continue;
1040                                                        } else {
1041                                                                pos.write("no\n".getBytes());
1042                                                                pos.flush();
1043                                                                pos.close();
1044                                                                log
1045                                                                                .error("Failed to accept RSA key fingerprint");
1046                                                                break;
1047                                                        }
1048                                                }
1049
1050                                                if (checkForPwd && containsPasswordRequest(tmp, j, len)) {
1051                                                        // now feed the password to the process
1052                                                        try {
1053                                                                pos.write(pwd.getBytes());
1054                                                                // log.info("Sent password ");
1055                                                                pos.write("\n".getBytes());
1056                                                                // log.info("Sent newline ");
1057                                                                pos.flush();
1058                                                                // log.info("Flushed pos ");
1059                                                                pos.close();
1060                                                                log.info("Sent password to third party.");
1061                                                        } catch (IOException ex) {
1062                                                                log
1063                                                                                .error("Error when feeding the password to the piped stream: "
1064                                                                                                + ex);
1065                                                        }
1066                                                        checkForPwd = false;
1067                                                }
1068
1069                                        } // end while
1070                                } catch (IOException ex) {
1071                                        log
1072                                                        .error("Error on the remote streams. Exiting reader thread: "
1073                                                                        + ex);
1074                                        break; // exit the loop
1075                                }
1076                                if (channel.isClosed())
1077                                        break; // exit the loop
1078
1079                                try {
1080                                        Thread.sleep(500);
1081                                } catch (Exception e) {
1082                                }
1083
1084                                // check timeout
1085                                current = System.currentTimeMillis();
1086                                if (timeout > 0 && maxtime < current - start) {
1087                                        log.debug("Reader thread detected timeout: " + timeout
1088                                                        + "s elapsed");
1089                                        timeoutReached = true;
1090                                        break; // exit the loop
1091                                }
1092                        } // while (true)
1093
1094                        try {
1095                                osw.close();
1096                        } catch (IOException ex) {
1097                                log.error("Cannot flush and close the output stream: " + ex);
1098                        }
1099                } // end method run()
1100
1101                /**
1102                 * Look for one of the strings password/passphrase/passcode in the
1103                 * char[] array. Return true if found any. Case insensitive search.
1104                 * Possible bug: we do not find the password text if it is broken into
1105                 * two in two consecutive calls.
1106                 */
1107                private boolean containsPasswordRequest(char[] buf, int startPos,
1108                                int endPos) {
1109                        // look for strings password/passphrase/passcode
1110                        int i = startPos;
1111                        while (i < endPos - 3) {
1112                                if (Character.toLowerCase(buf[i]) == 'p'
1113                                                && Character.toLowerCase(buf[i + 1]) == 'a'
1114                                                && Character.toLowerCase(buf[i + 2]) == 's'
1115                                                && Character.toLowerCase(buf[i + 3]) == 's') {
1116
1117                                        // found "pass", look further for word/code/phrase
1118                                        if (i < endPos - 7
1119                                                        && Character.toLowerCase(buf[i + 4]) == 'w'
1120                                                        && Character.toLowerCase(buf[i + 5]) == 'o'
1121                                                        && Character.toLowerCase(buf[i + 6]) == 'r'
1122                                                        && Character.toLowerCase(buf[i + 7]) == 'd') {
1123                                                log.info("PWDSearch: found request for password.");
1124                                                return true;
1125                                        } else if (i < endPos - 7
1126                                                        && Character.toLowerCase(buf[i + 4]) == 'c'
1127                                                        && Character.toLowerCase(buf[i + 5]) == 'o'
1128                                                        && Character.toLowerCase(buf[i + 6]) == 'd'
1129                                                        && Character.toLowerCase(buf[i + 7]) == 'e') {
1130                                                log.info("PWDSearch: found request for passcode.");
1131                                                return true;
1132                                        } else if (i < endPos - 9
1133                                                        && Character.toLowerCase(buf[i + 4]) == 'p'
1134                                                        && Character.toLowerCase(buf[i + 5]) == 'h'
1135                                                        && Character.toLowerCase(buf[i + 6]) == 'r'
1136                                                        && Character.toLowerCase(buf[i + 7]) == 'a'
1137                                                        && Character.toLowerCase(buf[i + 8]) == 's'
1138                                                        && Character.toLowerCase(buf[i + 9]) == 'e') {
1139                                                log.info("PWDSearch: found request for passphrase.");
1140                                                return true;
1141                                        }
1142                                }
1143                                i = i + 1;
1144                        }
1145                        return false;
1146                }
1147        } // end inner class _streamReaderThread
1148
1149        public void setPseudoTerminal(boolean pseudoTerminal) {
1150                this.pseudoTerminal = pseudoTerminal;
1151        }
1152
1153        public void setProtocolPath(String protocolPath) {
1154                this.protocolPath = protocolPath;
1155        }
1156
1157        public void setcmdLineOptions(String cmdLineOptions) {
1158                this.cmdLineOptions = cmdLineOptions;
1159        }
1160
1161        // @Override
1162        public boolean getForcedCleanUp() {
1163                return forcedCleanUp;
1164        }
1165
1166        // @Override
1167        public int getTimeout() {
1168                return timeout;
1169        }
1170
1171        public void setTimeout(int seconds) {
1172                timeout = seconds;
1173
1174        }
1175
1176        public String getwildcardFileListingBBCP(String srcConn, String wildcardPattern) throws ExecException{
1177                // TODO Auto-generated method stub
1178                String filePath = "";
1179                String filePattern = "";
1180                OutputStream streamOut = new ByteArrayOutputStream();
1181                OutputStream streamErr = new ByteArrayOutputStream();
1182                StringBuffer wildcardList = new StringBuffer();
1183                Vector<String> fileList = new Vector(); 
1184                int index = wildcardPattern.lastIndexOf("/");
1185                int exitCode = 1;
1186                
1187                //extract pattern and directory name from wildcard pattern
1188                if (index != -1) {
1189                        filePattern = wildcardPattern.substring(index + 1);
1190                        filePath = wildcardPattern.substring(0, index);
1191                }
1192System.out.println("parent dir : " + filePath);
1193System.out.println("File pattern : " + filePattern);
1194
1195                try {
1196                        //-Q is used to place file names into quotations
1197                        exitCode = executeCmd("\\ls -Q " + filePath, streamOut,streamErr, srcConn);
1198                } catch (ExecException e) {
1199                        // TODO Auto-generated catch block
1200                        e.printStackTrace();
1201                        throw new ExecException("Could not list contents of remote directory " + filePath);
1202                }
1203                
1204                if (exitCode != 0){
1205                        throw new ExecException("Failed to execute remote command.");
1206                }
1207                        
1208                //TODO Anand:extract filenames from ls entry
1209                // the character \n does not separate file names in a few scenarios.
1210                // To make the extraction more efficient - we are using a loop
1211                // If possible to get a separating character for ls field names use that instead of the loop 
1212                // IN THE LOOP: each filename has format "file_name"
1213                // We extract string in between " " to get the file name 
1214                String ls_output = streamOut.toString();
1215                int f_start = 0;
1216                int f_end = 0;
1217                while (f_end < ls_output.length()){
1218                        f_start = ls_output.indexOf("\"");
1219                        ls_output = ls_output.substring(f_start+1);
1220                        f_end = ls_output.indexOf("\"");
1221                        fileList.add(ls_output.substring(0, f_end));
1222                        ls_output = ls_output.substring(f_end+1);
1223                }
1224
1225                //match filenames to pattern and create a file list
1226                String pattern = filePattern.replaceAll("\\.", "\\\\.").replaceAll("\\*",
1227                ".*").replaceAll("\\?", ".");
1228                Pattern p = Pattern.compile(pattern);
1229                
1230                for (String curFile : fileList){
1231                        System.out.println(":"+curFile+";");
1232                        curFile = curFile.trim();
1233                        Matcher m = p.matcher(curFile);
1234                        if (m.matches()){
1235                                wildcardList.append(srcConn);
1236                                wildcardList.append(":\"");
1237                                wildcardList.append(filePath);
1238                                wildcardList.append("/");
1239                                wildcardList.append(curFile);
1240                                wildcardList.append("\"");
1241                                wildcardList.append(" ");
1242                        }
1243                }
1244System.out.println("wild card list formed is : " + wildcardList);               
1245        
1246                return wildcardList.toString();
1247        }
1248} // end-of-class Ssh