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