001/* 002 * Copyright (c) 2010 The Regents of the University of California. 003 * All rights reserved. 004 * 005 * '$Author: kulkarni $' 006 * '$Date: 2010-10-05 21:46:36 +0000 (Tue, 05 Oct 2010) $' 007 * '$Revision: 26020 $' 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.File; 033import java.util.Vector; 034import java.util.regex.Matcher; 035import java.util.regex.Pattern; 036 037import org.apache.commons.logging.Log; 038import org.apache.commons.logging.LogFactory; 039import org.kepler.util.FilenameFilter_RegularPattern; 040 041import com.jcraft.jsch.ChannelSftp; 042import com.jcraft.jsch.JSchException; 043import com.jcraft.jsch.SftpException; 044 045/** 046 * Class to handle file transfer based on sftp protocol. It uses the Jsch 047 * package to transfer files from local to remote host or remote host to local 048 * machine. It extends <code>SshExec</code> and uses its method to execute a 049 * sftp command in batch mode to transfer files between two remote machines 050 * <p> 051 * 052 * @see org.kepler.ssh.SshExec 053 * @author Chandrika Sivaramakrishnan 054 * 055 * Based on - JSch examples http://www.jcraft.com/jsch and SshExec code 056 * written by Norbert Podhorszki 057 * 058 */ 059public class SftpExec extends SshExec { 060 061 // private variables 062 private static final Log log = LogFactory.getLog(SftpExec.class.getName()); 063 private static final boolean isDebugging = log.isDebugEnabled(); 064 065 /** 066 * Creates session with give user and host name and default port 067 * 068 * @param user 069 * @param host 070 */ 071 public SftpExec(String user, String host) { 072 super(user, host); 073 } 074 075 /** 076 * Creates a session with given user name, host and port 077 * 078 * @param user 079 * @param host 080 * @param port 081 */ 082 public SftpExec(String user, String host, int port) { 083 super(user, host, port); 084 } 085 086 /** 087 * Copies file/directory from a remote machine to local machine using Jsch 088 * sftp channel. Since the sftp command is not directly used any command 089 * line options set in the super class will be ignored. 090 * 091 * @param rfile 092 * - remote file that should be copied to local machine 093 * @param localPath 094 * - path into to which the source file should be copied 095 * @param recursive 096 * - flag to represent recursive copy of a directory 097 */ 098 @Override 099 public int copyFrom(String rfile, File localPath, boolean recursive) 100 throws SshException { 101 int exitCode = 0; 102 103 if (!openConnection()) 104 throw new SshException( 105 "Ssh connection could not be opened for copying."); 106 107 // at this point we have a living, opened session to the remote machine 108 if (isDebugging) 109 log.debug(" % Copy " + rfile + " to " + localPath); 110 111 // Validate local path 112 /*if (recursive && (!localPath.isDirectory())) { 113 throw new SshException("Destination " + localPath 114 + " is missing or not a directory"); 115 }*/ 116 117 try { 118 ChannelSftp sftpChannel; 119 synchronized (session) { // for thread safety here we need to sync 120 sftpChannel = (ChannelSftp) jschSession.openChannel("sftp"); 121 sftpChannel.connect(); 122 } 123 124 if (isRegularOrLinkFile(rfile)) { 125 // If source is a regular file 126 sftpChannel.get(rfile, localPath.getAbsolutePath()); 127 } else if (rfile.contains("*") || rfile.contains("+")){ 128 //If the source file is a wildcard 129 try{ 130 if (rfile.contains("\\")) { 131 copyWildcardFrom(rfile, localPath, "\\", sftpChannel ); 132 } else { 133 copyWildcardFrom(rfile, localPath, "/", sftpChannel ); 134 } 135 }catch (Exception e){ 136 sftpChannel.disconnect(); 137 throw new SshException(e.getMessage()); 138 } 139 }else {//Source is directory 140 try { 141 sftpChannel.cd(rfile); // to check if remote file is 142 // directory 143 } catch (SftpException e) { 144 throw new SshException("Source " + rfile 145 + " is missing or not a directory"); 146 } 147 if (rfile.contains("\\")) { 148 copyDirectoryFrom(rfile, localPath, "\\", sftpChannel); 149 } else { 150 copyDirectoryFrom(rfile, localPath, "/", sftpChannel); 151 } 152 } 153 // close channel 154 sftpChannel.disconnect(); 155 } catch (Exception e) { 156 log.error("Exception doing put: " + e); 157 throw new SshException("Unable to copy " + rfile + " to " 158 + localPath + "\n" + e.getMessage()); 159 } 160 return exitCode; 161 } 162/** 163 * Anand: This function takes in remote file name, and local destination dir name. 164 * It scans the remote parent directory (of the wildcard), for all the matching file names. 165 * And the copies all the files from remote directory to local destination dir. 166 * 167 * @param rfile 168 * - remote wildcard pattern with full path 169 * @param localPath 170 * - Local directory path 171 * @param seperator 172 * - Path seperator to extract remote parent directory from rfile 173 * @param sftpChannel 174 * - SFTP connection channel 175 * @throws Exception 176 * - Exception code to indicate failure in operation 177 */ 178 private void copyWildcardFrom(String rfile, File localPath, String seperator, 179 ChannelSftp sftpChannel) throws Exception { 180 181 Vector<String> list = getWildcardFileListing(rfile, seperator); 182 for (String curFile : list) { 183 try{ 184 sftpChannel.get(curFile, localPath.getAbsolutePath()); 185 }catch (Exception e) 186 { 187 throw new SshException("Error in copying remote file " + curFile + 188 " to local directory " + localPath.getAbsolutePath()); 189 } 190 } 191 } 192 193 /** 194 * Anand: Receives the remote wildcard and returns list of files matching the pattern 195 * @param remoteFile : Wildcard pattern with path 196 * @param seperator : OS path seperator 197 * @return String array containing list of files matching pattern 198 * @throws Exception 199 */ 200 @SuppressWarnings("unchecked") 201 public Vector<String> getWildcardFileListing(String remoteFile, String seperator) throws Exception { 202 //string to store pattern 203 String filePattern = ""; 204 //string to store path 205 String filePath = ""; 206 //list of files which match pattern 207 Vector<String> fileList = new Vector(); 208 ChannelSftp sftpChannel = null; 209 210 if (!openConnection()) 211 throw new SshException( 212 "Ssh connection could not be opened for copying."); 213 214 try{ 215 synchronized (session) { // for thread safety here we need to sync 216 sftpChannel = (ChannelSftp) jschSession.openChannel("sftp"); 217 sftpChannel.connect(); 218 } 219 }catch (Exception e){ 220 e.printStackTrace(); 221 throw new SshException("Failed acquire SFTP Channel \n " + e.getMessage()); 222 } 223 224 while(!sftpChannel.isConnected()){} 225 //check if wildcard pattern is provided 226 if (remoteFile.indexOf("*") > 0 || remoteFile.indexOf("?") > 0) { 227 228 //list to store list of files from remote directory 229 Vector<ChannelSftp.LsEntry> list; 230 231 //extract pattern and directory name from rfile 232 int index = remoteFile.lastIndexOf(seperator); 233 if (index != -1) { 234 filePattern = remoteFile.substring(index + 1); 235 filePath = remoteFile.substring(0, index); 236 } 237 238 //Get the list of files from directory 239 try { 240 list = sftpChannel.ls(filePath.trim()); 241 } catch (SftpException e) { 242 e.printStackTrace(); 243 throw new SshException("Unable to list files in remote directory " 244 + filePath + " \n " + e.getMessage()); 245 } 246 if (list.size() < 1) { 247 throw new SshException("Unable to retrieve contents of directory " 248 + filePath 249 + "\nPlease check the permissions on this directory."); 250 } 251 252 //create the wildcard pattern 253 String pattern = filePattern.replaceAll("\\.", "\\\\.").replaceAll("\\*", 254 ".*").replaceAll("\\?", "."); 255 Pattern p = Pattern.compile(pattern); 256 for (ChannelSftp.LsEntry curFile : list) { 257 //match the input pattern(wildcard) to each file in the list 258 if (curFile.getFilename().equals(".") 259 || curFile.getFilename().equals("..") 260 || curFile.getAttrs().isDir()) { 261 continue; 262 } 263 Matcher m = p.matcher(curFile.getFilename()); 264 if (m.matches()){ 265 String newfile = filePath + seperator + curFile.getFilename(); 266 //add newfile to list of files to be copied 267 fileList.add(newfile.trim()); 268 } 269 } 270 }else 271 { 272 throw new SshException("Cannot find file " 273 + remoteFile 274 + ".\n"); 275 } 276 sftpChannel.disconnect(); 277 return fileList; 278 } 279 280 /** 281 * Copies files/directories from local machine to remote host 282 * 283 * @param lfile 284 * - local file that should be copied to remote machine 285 * @param targetPath 286 * - path on remote host into to which the file should be copied 287 * @param recursive 288 * - flag to represent recursive copy of a directory 289 * 290 */ 291 @Override 292 public int copyTo(File lfile, String targetPath, boolean recursive) 293 throws SshException { 294 295 ChannelSftp sftpChannel; 296 File[] files = null; 297 298 //Connection Setup - Start 299 if (!openConnection()) 300 throw new SshException( 301 "Ssh connection could not be opened for copying."); 302 // at this point we have a living, opened session to the remote machine 303 304 if (isDebugging) 305 log.debug(" % Copy " + lfile + " to " + targetPath); 306 307 try { 308 synchronized (session) { // for thread safety here we need to sync 309 sftpChannel = (ChannelSftp) jschSession.openChannel("sftp"); 310 sftpChannel.connect(); 311 } 312 } catch (JSchException e) { 313 e.printStackTrace(); 314 throw new SshException("Unable to create connection to copy " + lfile + " to " 315 + targetPath + "\n" + e.getMessage()); 316 } 317 //Connection Setup - End 318 319 if (lfile.isDirectory()) { // if file is a directory 320 // recursive is not set 321 if (!recursive) 322 throw new SshException("File " + lfile + " is a directory. Set recursive copy!"); 323 // Validation of remote dir 324 try { 325 sftpChannel.cd(targetPath); // to check if remote file is 326 // directory 327 } catch (SftpException e) { 328 throw new SshException("Destination " + targetPath 329 + " is missing or not a directory"); 330 } 331 log.debug("Calling dir copy"); 332 try{ 333 if (targetPath.contains("\\")) { 334 // Anand: for windows - file seperator is \\ 335 copyDirectoryTo(lfile, targetPath, "\\", sftpChannel); 336 } else { 337 // Anand: for linx file seperator is / 338 copyDirectoryTo(lfile, targetPath, "/", sftpChannel); 339 } 340 } catch (Exception e){ 341 throw new SshException("Error while copying directory" + "\n" + e.getMessage()); 342 } 343 } 344 else { //file is a single file or wildcard 345 // Anand: Get the complete file name 346 String name = lfile.getPath(); 347 // if file contains a wildcard 348 if (name.indexOf("*") >=0 || name.indexOf("?") >= 0) { 349System.out.println("**********found wildcard pattern for SFTP copyTo"); 350 name = lfile.getName(); 351 // create a pattern for wildcard, and form a filter 352 String pattern = name.replaceAll("\\.", "\\\\.").replaceAll("\\*", 353 ".*").replaceAll("\\?", "."); 354 FilenameFilter_RegularPattern filter = new FilenameFilter_RegularPattern( 355 pattern); 356 // search for file matching pattern in the directory 357 String dirname = lfile.getParent(); 358 if (dirname == null || dirname == "") 359 dirname = "."; 360 File dir = new File(dirname); 361 files = dir.listFiles(filter); 362 try { 363 // Copy files from wildcard using a loop 364 for (int i = 0; i < files.length; i++) 365 sftpChannel.put(files[i].getAbsolutePath(), targetPath); 366 } catch (SftpException e) { 367 e.printStackTrace(); 368 throw new SshException( 369 "SFTP Channel failed to copy files to remote machine."); 370 } 371 }else {// Only a single file is to be copied. 372 //Check if the file exists 373 if (!lfile.exists()) { 374 throw new SshException("Source file " + lfile 375 + " doesn't exists"); 376 } 377 files = new File[1]; 378 files[0] = lfile; 379 } 380 //copy the files - includes wildcard files or single file 381 log.debug("Calling file copy"); 382 try { 383 for (int i = 0; i < files.length; i++) 384 sftpChannel.put(files[i].getAbsolutePath(), targetPath); 385 } catch (Exception e) { 386 throw new SshException("Unable to copy " + lfile + " to " 387 + targetPath + "\n" + e.getMessage()); 388 } 389 }//else for file or directory ends 390 // close channel 391 sftpChannel.disconnect(); 392 return 0; 393 } 394 395 /** 396 * Returns the list of sftp commands (combination of "mkdir" and "put" 397 * commands) that are required to recursively copy a directory. 398 * 399 * @param srcFile 400 * - source file to be copied 401 * @param destFile 402 * - destination directory 403 * @param isConnectionOrigin 404 * @return command - String representing the list of commands seperated by 405 * \n character 406 * @throws SshException 407 */ 408 public String getRecursiveCopyCmd(String srcFile, String destFile, boolean isConnectionOrigin) 409 throws SshException { 410 StringBuffer cmd = new StringBuffer(100); 411 String destseperator = "/"; 412 String srcseperator = "/"; 413 try { 414 415 if (!openConnection()) 416 throw new SshException( 417 "Ssh connection could not be opened for copying."); 418 419 ChannelSftp sftpChannel; 420 synchronized (session) { // for thread safety here we need to sync 421 sftpChannel = (ChannelSftp) jschSession.openChannel("sftp"); 422 sftpChannel.connect(); 423 } 424 425 try { 426 log.debug("changing dir to " + srcFile); 427 sftpChannel.cd(srcFile.trim()); // to check if remote file is directory 428 } catch (SftpException e) { 429 throw new SshException("Source " + srcFile 430 + " is missing or not a directory"); 431 } 432 433 if (destFile.indexOf("\\") > -1) { 434 destseperator = "\\"; 435 } 436 if (srcFile.indexOf("\\") > -1) { 437 srcseperator = "\\"; 438 } 439 440 cmd.append(getDirCopyCmd(srcFile, destFile, srcseperator, 441 destseperator, sftpChannel, isConnectionOrigin)); 442 cmd.append("exit"); 443 444 // close channel 445 sftpChannel.disconnect(); 446 } catch (Exception e) { 447 throw new SshException(e.getMessage()); 448 } 449 if (isDebugging) { 450 log.debug("Returning cmd= " + cmd); 451 } 452 return cmd.toString(); 453 } 454 455 /** 456 * Checks if a given file is a regular file. 457 * 458 * @param srcFile 459 * - file that is to be checked for its type 460 * @return flag - true if the input is a regular file, false otherwise 461 * @throws SshException 462 * @throws JSchException 463 */ 464 @SuppressWarnings("unchecked") 465 public boolean isRegularOrLinkFile(String srcFile) throws SshException, 466 JSchException { 467 if (!openConnection()) 468 throw new SshException( 469 "Ssh connection could not be opened for copying."); 470 471 ChannelSftp sftpChannel; 472 Vector<ChannelSftp.LsEntry> list; 473 474 synchronized (session) { // for thread safety here we need to sync 475 sftpChannel = (ChannelSftp) jschSession.openChannel("sftp"); 476 sftpChannel.connect(); 477 } 478 479 try { 480 list = sftpChannel.ls(srcFile); 481 sftpChannel.disconnect(); 482 } catch (SftpException e) { 483 e.printStackTrace(); 484 log.error("Error checking file type- " + e + " :: for file ::" + srcFile); 485 throw new SshException( 486 "Unable to determine file type of source file " + srcFile 487 + "\n" + e); 488 } 489 490 // ls of regular file should have only one entry 491 if (list.size() != 1) { 492 return false; 493 } 494 for (ChannelSftp.LsEntry curFile : list) { 495 if (curFile.getLongname().startsWith("-") 496 || curFile.getLongname().startsWith("l")) { 497 return true; 498 } else { 499 return false; 500 } 501 } 502 return false; 503 } 504 505 // /////////////////////////private 506 // methods/////////////////////////////////// 507 508 /* 509 * Recursively copy directory to target 510 */ 511 private void copyDirectoryTo(File lfile, String targetPath, 512 String seperator, ChannelSftp sftpChannel) throws Exception { 513 514 if (isDebugging) { 515 log.debug("Copying directory-" + lfile); 516 } 517 File[] list = lfile.listFiles(); 518 if (list == null) { 519 throw new Exception( 520 "Unable to access local directory " 521 + lfile 522 + "\nPlease check if directory exists and has appropriate permissions for the user"); 523 } 524 try { 525 sftpChannel.cd(targetPath); 526 } catch (SftpException e) { 527 throw new Exception("Unable to access " + targetPath + "\n" 528 + e.getMessage()); 529 } 530 531 try { 532 sftpChannel.mkdir(lfile.getName()); 533 } catch (SftpException e) { 534 StringBuffer message = new StringBuffer(100); 535 message.append("Unable to create directory ").append( 536 lfile.getName()).append(" in ").append(targetPath).append( 537 "\n"); 538 String exmessage = e.getMessage().trim(); 539 if (exmessage.equals("") || exmessage.equals("Failure")) { 540 message 541 .append("Probable error: another file exists with the same name"); 542 } else { 543 message.append(exmessage); 544 } 545 throw new Exception(message.toString()); 546 } 547 548 targetPath = targetPath + seperator + lfile.getName(); 549 550 // Start copying files in the directory 551 for (File curFile : list) { 552 if (curFile.isDirectory()) { 553 copyDirectoryTo(curFile, targetPath, seperator, sftpChannel); 554 } else { 555 sftpChannel.put(curFile.getAbsolutePath(), targetPath); 556 } 557 } 558 559 } 560 561 // Recursively copy directory from remote host to local 562 private void copyDirectoryFrom(String rfile, File localPath, 563 String seperator, ChannelSftp sftpChannel) throws Exception { 564 565 if (isDebugging) { 566 log.debug("Copying remote directory-" + rfile); 567 } 568 Vector<ChannelSftp.LsEntry> list = null; 569 try { 570 list = sftpChannel.ls(rfile); 571 } catch (SftpException e) { 572 throw new SshException("Unable to list files in remote directory " 573 + rfile + " \n " + e.getMessage()); 574 } 575 if (list.size() < 1) { 576 throw new SshException("Unable to retrieve contents of directory " 577 + rfile 578 + "\nPlease check the permissions on this directory."); 579 } 580 581 // Create a sub directory in localPath with the same name as remote dir 582 String filename = rfile; 583 int index = rfile.lastIndexOf(seperator); 584 if (index != -1) { 585 filename = rfile.substring(index + 1); 586 } 587 File subdir = new File(localPath.getAbsolutePath() 588 + localPath.separator + filename); 589 subdir.mkdir(); 590 591 for (ChannelSftp.LsEntry curFile : list) { 592 593 if (curFile.getFilename().equals(".") 594 || curFile.getFilename().equals("..")) { 595 continue; 596 } 597 String newfile = rfile + seperator + curFile.getFilename(); 598 599 if (curFile.getAttrs().isDir()) { 600 copyDirectoryFrom(newfile, subdir, seperator, sftpChannel); 601 } else { 602 sftpChannel.get(newfile, subdir.getAbsolutePath()); 603 } 604 } 605 } 606 607 // Returns the the 'mkdir' command for a given source directory and a list 608 // of 609 // put command for all the files present in the directory 610 // This method is recursively called for every subdir in the source 611 // directory 612 private StringBuffer getDirCopyCmd(String srcFile, String destFile, 613 String srcseperator, String destseperator, ChannelSftp sftpChannel, boolean isConnectionOrigin) 614 throws SshException { 615 616 String curDestDir; 617 String curSrcDir; 618 Vector<ChannelSftp.LsEntry> list; 619 StringBuffer cmd = new StringBuffer(); 620 try { 621 list = sftpChannel.ls(srcFile); 622 } catch (SftpException e) { 623 throw new SshException("Unable to list files in source directory " 624 + srcFile + " \n " + e.getMessage()); 625 } 626 if (list.size() < 1) { 627 throw new SshException("Unable to retrieve contents of directory " 628 + srcFile 629 + "\nPlease check the permissions on this directory."); 630 } 631 632 curSrcDir = srcFile + srcseperator; 633 curDestDir = destFile + destseperator 634 + srcFile.substring(srcFile.lastIndexOf(srcseperator) + 1); 635 if (isConnectionOrigin) 636 cmd.append("mkdir "); 637 else 638 cmd.append("lmkdir "); 639 cmd.append("\\\""); 640 cmd.append(curDestDir); 641 cmd.append("\\\""); 642 cmd.append("\\n"); 643 644 for (ChannelSftp.LsEntry curFile : list) { 645 646 if (curFile.getFilename().equals(".") 647 || curFile.getFilename().equals("..")) { 648 continue; 649 } 650 651 if (curFile.getAttrs().isDir()) { 652 // recursive call with sub directory as the new source directory 653 cmd.append(getDirCopyCmd(curSrcDir + curFile.getFilename(), 654 curDestDir, srcseperator, destseperator, sftpChannel, isConnectionOrigin)); 655 656 } else { 657 if (isConnectionOrigin) 658 cmd.append("put "); 659 else 660 cmd.append("get "); 661 cmd.append("\\\""); 662 cmd.append(curSrcDir); 663 cmd.append(curFile.getFilename()); 664 cmd.append("\\\""); 665 cmd.append(" "); 666 cmd.append("\\\"");; 667 cmd.append(curDestDir); 668 cmd.append(destseperator); 669 cmd.append(curFile.getFilename()); 670 cmd.append("\\\""); 671 cmd.append("\\n"); 672 } 673 } 674 return cmd; 675 } 676 677}