diff --git a/java-scpclient/src/main/java/cz/it4i/fiji/scpclient/AbstractBaseSshClient.java b/java-scpclient/src/main/java/cz/it4i/fiji/scpclient/AbstractBaseSshClient.java new file mode 100644 index 0000000000000000000000000000000000000000..793eb82e922be76e5f1b56f20bec76322da6fbc7 --- /dev/null +++ b/java-scpclient/src/main/java/cz/it4i/fiji/scpclient/AbstractBaseSshClient.java @@ -0,0 +1,173 @@ + +package cz.it4i.fiji.scpclient; + +import com.jcraft.jsch.Identity; +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; +import com.jcraft.jsch.UserInfo; + +import java.io.Closeable; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Field; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import cz.it4i.fiji.commons.DoActionEventualy; + +public class AbstractBaseSshClient implements Closeable { + + protected static final int MAX_NUMBER_OF_CONNECTION_ATTEMPTS = 5; + + protected static final long TIMEOUT_BETWEEN_CONNECTION_ATTEMPTS = 500; + + private final static Logger log = LoggerFactory.getLogger( + AbstractBaseSshClient.class); + + private String hostName; + private String username; + private final JSch jsch = new JSch(); + private Session session; + + private int port = 22; + + public AbstractBaseSshClient(String hostName, String username, + byte[] privateKeyFile) throws JSchException + { + init(hostName, username, new ByteIdentity(jsch, privateKeyFile)); + } + + public AbstractBaseSshClient(String hostName, String username, + Identity privateKeyFile) throws JSchException + { + init(hostName, username, privateKeyFile); + } + + public AbstractBaseSshClient(String hostName, String userName, String keyFile, + String pass) throws JSchException + { + Identity id = IdentityFile.newInstance(keyFile, null, jsch); + try { + if (pass != null) { + id.setPassphrase(pass.getBytes("UTF-8")); + } + } + catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + init(hostName, userName, id); + } + + public void setPort(int port) { + this.port = port; + } + + @Override + public void close() { + if (session != null && session.isConnected()) { + // log.info("disconnect"); + try (DoActionEventualy actionEventualy = new DoActionEventualy( + TIMEOUT_BETWEEN_CONNECTION_ATTEMPTS, this::interruptSessionThread)) + { + session.disconnect(); + } + } + session = null; + } + + protected Session getConnectedSession() throws JSchException { + if (session == null) { + session = jsch.getSession(username, hostName, port); + + UserInfo ui = new P_UserInfo(); + + session.setUserInfo(ui); + } + int connectRetry = 0; + long timoutBetweenRetry = TIMEOUT_BETWEEN_CONNECTION_ATTEMPTS; + while (!session.isConnected()) { + try { + session.connect(); + } + catch (JSchException e) { + if (e.getMessage().contains("Auth fail") || e.getMessage().contains( + "Packet corrupt")) + { + if (connectRetry < MAX_NUMBER_OF_CONNECTION_ATTEMPTS) { + connectRetry++; + try { + Thread.sleep(timoutBetweenRetry); + timoutBetweenRetry *= 2; + } + catch (InterruptedException exc) { + log.info("Interruption detected"); + throw new JSchException(exc.getMessage(), exc); + } + continue; + } + e = new AuthFailException(e.getMessage(), e); + } + throw e; + } + } + return session; + } + + private void interruptSessionThread() { + try { + Field f = session.getClass().getDeclaredField("connectThread"); + if (!f.isAccessible()) { + f.setAccessible(true); + Thread thread = (Thread) f.get(session); + thread.interrupt(); + } + } + catch (NoSuchFieldException | SecurityException | IllegalArgumentException + | IllegalAccessException exc) + { + log.error(exc.getMessage(), exc); + } + } + + private void init(String initHostName, String initUsername, + Identity privateKeyFile) throws JSchException + { + this.hostName = initHostName; + this.username = initUsername; + jsch.addIdentity(privateKeyFile, null); + } + + private class P_UserInfo implements UserInfo { + + @Override + public String getPassphrase() { + return null; + } + + @Override + public String getPassword() { + return null; + } + + @Override + public boolean promptPassword(String message) { + return false; + } + + @Override + public boolean promptPassphrase(String message) { + return false; + } + + @Override + public boolean promptYesNo(String message) { + return true; + } + + @Override + public void showMessage(String message) {} + + } + +} diff --git a/java-scpclient/src/main/java/cz/it4i/fiji/scpclient/ScpClient.java b/java-scpclient/src/main/java/cz/it4i/fiji/scpclient/ScpClient.java index b1736e5752892cdcc6b58cef38e897e9173fc91d..20cd2e9a1fc57357ce03b308278320832751426f 100644 --- a/java-scpclient/src/main/java/cz/it4i/fiji/scpclient/ScpClient.java +++ b/java-scpclient/src/main/java/cz/it4i/fiji/scpclient/ScpClient.java @@ -6,19 +6,13 @@ import com.jcraft.jsch.ChannelExec; import com.jcraft.jsch.ChannelSftp; import com.jcraft.jsch.ChannelSftp.LsEntry; import com.jcraft.jsch.Identity; -import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; -import com.jcraft.jsch.Session; import com.jcraft.jsch.SftpException; -import com.jcraft.jsch.UserInfo; -import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.lang.reflect.Field; import java.nio.channels.ClosedByInterruptException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; @@ -30,31 +24,20 @@ import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import cz.it4i.fiji.commons.DoActionEventualy; +public class ScpClient extends AbstractBaseSshClient { -public class ScpClient implements Closeable { + public static final Logger log = LoggerFactory.getLogger(ScpClient.class); private static final String NO_SUCH_FILE_OR_DIRECTORY_ERROR_TEXT = "No such file or directory"; - public static final Logger log = LoggerFactory.getLogger( - cz.it4i.fiji.scpclient.ScpClient.class); - private static String constructExceptionText(AckowledgementChecker ack) { return "Check acknowledgement failed with status: " + ack.getLastStatus() + " and message: " + ack.getLastMessage(); } - - private static final int MAX_NUMBER_OF_CONNECTION_ATTEMPTS = 5; - private static final long TIMEOUT_BETWEEN_CONNECTION_ATTEMPTS = 500; + private static final int BUFFER_SIZE = 4 * 1024 * 1024; // 4 MB - private static final int BUFFER_SIZE = 4 * 1024 * 1024 ; //4 MB - - private String hostName; - private String username; - private final JSch jsch = new JSch(); - private Session session; private final TransferFileProgress dummyProgress = new TransferFileProgress() { @@ -62,48 +45,25 @@ public class ScpClient implements Closeable { @Override public void dataTransfered(long bytesTransfered) { - } + } }; - private int port = 22; - public ScpClient(String hostName, String username, byte[] privateKeyFile) throws JSchException { - init(hostName, username, new ByteIdentity(jsch, privateKeyFile)); + super(hostName, username, privateKeyFile); } public ScpClient(String hostName, String username, Identity privateKeyFile) throws JSchException { - init(hostName, username, privateKeyFile); + super(hostName, username, privateKeyFile); } public ScpClient(String hostName, String userName, String keyFile, String pass) throws JSchException { - Identity id = IdentityFile.newInstance(keyFile, null, jsch); - try { - if (pass != null) { - id.setPassphrase(pass.getBytes("UTF-8")); - } - } - catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - init(hostName, userName, id); - } - - public void setPort(int port) { - this.port = port; - } - - private void init(String initHostName, String initUsername, - Identity privateKeyFile) throws JSchException - { - this.hostName = initHostName; - this.username = initUsername; - jsch.addIdentity(privateKeyFile, null); + super(hostName, userName, keyFile, pass); } public void download(String lfile, Path rFile) throws JSchException, @@ -112,8 +72,8 @@ public class ScpClient implements Closeable { download(lfile, rFile, dummyProgress); } - public void download(String lfile, Path rfile, - TransferFileProgress progress) throws JSchException, IOException + public void download(String lfile, Path rfile, TransferFileProgress progress) + throws JSchException, IOException { if (!Files.exists(rfile.getParent())) { Files.createDirectories(rfile.getParent()); @@ -129,8 +89,8 @@ public class ScpClient implements Closeable { AckowledgementChecker ack = new AckowledgementChecker(); // exec 'scp -f rfile' remotely - lfile=lfile.replace("'", "'\"'\"'"); - lfile="'"+lfile+"'"; + lfile = lfile.replace("'", "'\"'\"'"); + lfile = "'" + lfile + "'"; String command = "scp -f " + lfile; Channel channel = getConnectedSession().openChannel("exec"); @@ -184,7 +144,7 @@ public class ScpClient implements Closeable { // System.out.println("filesize="+filesize+", file="+file); // send '\0' - + buf[0] = 0; out.write(buf, 0, 1); out.flush(); @@ -236,8 +196,8 @@ public class ScpClient implements Closeable { throws JSchException, IOException { try (InputStream is = Files.newInputStream(file)) { - upload(is, rfile, file.toFile().length(), file.toFile() - .lastModified(), progress); + upload(is, rfile, file.toFile().length(), file.toFile().lastModified(), + progress); } } @@ -250,7 +210,8 @@ public class ScpClient implements Closeable { try { scp2Server(is, fileName, length, lastModified, progress); break; - } catch (NoSuchFileException e) { + } + catch (NoSuchFileException e) { if (noSuchFileExceptionThrown > MAX_NUMBER_OF_CONNECTION_ATTEMPTS) { throw new JSchException(e.getReason()); } @@ -258,22 +219,22 @@ public class ScpClient implements Closeable { try { Thread.sleep(TIMEOUT_BETWEEN_CONNECTION_ATTEMPTS); } - catch (InterruptedException exc) { - } + catch (InterruptedException exc) {} } mkdir(e.getFile()); noSuchFileExceptionThrown++; continue; } - } while(true); + } + while (true); } public long size(String lfile) throws JSchException, IOException { AckowledgementChecker ack = new AckowledgementChecker(); // exec 'scp -f rfile' remotely - lfile=lfile.replace("'", "'\"'\"'"); - lfile="'"+lfile+"'"; + lfile = lfile.replace("'", "'\"'\"'"); + lfile = "'" + lfile + "'"; String command = "scp -f " + lfile; Channel channel = getConnectedSession().openChannel("exec"); @@ -346,73 +307,10 @@ public class ScpClient implements Closeable { return null; } - @Override - public void close() { - if (session != null && session.isConnected()) { - // log.info("disconnect"); - try(DoActionEventualy actionEventualy = new DoActionEventualy(TIMEOUT_BETWEEN_CONNECTION_ATTEMPTS, this::interruptSessionThread)){ - session.disconnect(); - } - } - session = null; - } - - private void interruptSessionThread() { - try { - Field f= session.getClass().getDeclaredField("connectThread"); - if(!f.isAccessible()) { - f.setAccessible(true); - Thread thread = (Thread) f.get(session); - thread.interrupt(); - } - } - catch (NoSuchFieldException | SecurityException | IllegalArgumentException - | IllegalAccessException exc) - { - log.error(exc.getMessage(), exc); - } - } - private int getBufferSize() { return BUFFER_SIZE; } - private Session getConnectedSession() throws JSchException { - if (session == null) { - session = jsch.getSession(username, hostName, port); - - UserInfo ui = new P_UserInfo(); - - session.setUserInfo(ui); - } - int connectRetry = 0; - long timoutBetweenRetry = TIMEOUT_BETWEEN_CONNECTION_ATTEMPTS; - while (!session.isConnected()) { - try { - session.connect(); - } - catch(JSchException e) { - if(e.getMessage().contains("Auth fail") || e.getMessage().contains("Packet corrupt")) { - if(connectRetry < MAX_NUMBER_OF_CONNECTION_ATTEMPTS) { - connectRetry++; - try { - Thread.sleep(timoutBetweenRetry); - timoutBetweenRetry *= 2; - } - catch (InterruptedException exc) { - log.info("Interruption detected"); - throw new JSchException(exc.getMessage(), exc); - } - continue; - } - e = new AuthFailException(e.getMessage(), e); - } - throw e; - } - } - return session; - } - private void scp2Server(InputStream is, String fileName, long length, long lastModified, TransferFileProgress progress) throws JSchException, IOException, InterruptedIOException @@ -421,9 +319,10 @@ public class ScpClient implements Closeable { boolean ptimestamp = false; // exec 'scp -t rfile' remotely - fileName=fileName.replace("'", "'\"'\"'"); + fileName = fileName.replace("'", "'\"'\"'"); - String command = "scp " + (ptimestamp ? "-p" : "") + " -t '" + fileName + "'"; + String command = "scp " + (ptimestamp ? "-p" : "") + " -t '" + fileName + + "'"; Channel channel = getConnectedSession().openChannel("exec"); ((ChannelExec) channel).setCommand(command); // get I/O streams for remote scp @@ -434,7 +333,7 @@ public class ScpClient implements Closeable { if (!ack.checkAck(in)) { throw new JSchException(constructExceptionText(ack)); } - + if (ptimestamp) { command = "T " + (lastModified / 1000) + " 0"; // The access time should be sent here, @@ -446,7 +345,7 @@ public class ScpClient implements Closeable { throw new JSchException(constructExceptionText(ack)); } } - + // send "C0644 filesize filename", where filename should not include '/' long filesize = length; command = "C0644 " + filesize + " "; @@ -479,7 +378,7 @@ public class ScpClient implements Closeable { throw new JSchException(constructExceptionText(ack)); } out.close(); - + } catch (ClosedByInterruptException e) { Thread.interrupted(); @@ -491,12 +390,14 @@ public class ScpClient implements Closeable { } private int mkdir(String file) throws JSchException { - ChannelExec channel = (ChannelExec) getConnectedSession().openChannel("exec"); + ChannelExec channel = (ChannelExec) getConnectedSession().openChannel( + "exec"); channel.setCommand("mkdir -p '" + file + "'"); try { channel.connect(); return channel.getExitStatus(); - } finally { + } + finally { channel.disconnect(); } } @@ -509,38 +410,4 @@ public class ScpClient implements Closeable { return fileName.substring(0, index); } - private class P_UserInfo implements UserInfo { - - @Override - public String getPassphrase() { - return null; - } - - @Override - public String getPassword() { - return null; - } - - @Override - public boolean promptPassword(String message) { - return false; - } - - @Override - public boolean promptPassphrase(String message) { - return false; - } - - @Override - public boolean promptYesNo(String message) { - return true; - } - - @Override - public void showMessage(String message) {} - - } - - - } diff --git a/java-scpclient/src/main/java/cz/it4i/fiji/scpclient/SshCommandClient.java b/java-scpclient/src/main/java/cz/it4i/fiji/scpclient/SshCommandClient.java new file mode 100644 index 0000000000000000000000000000000000000000..6b3ab613800f32631ae07f84885a9d83e874baa2 --- /dev/null +++ b/java-scpclient/src/main/java/cz/it4i/fiji/scpclient/SshCommandClient.java @@ -0,0 +1,75 @@ + +package cz.it4i.fiji.scpclient; + +import com.jcraft.jsch.ChannelExec; +import com.jcraft.jsch.Identity; +import com.jcraft.jsch.JSchException; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SshCommandClient extends AbstractBaseSshClient { + + private final static Logger log = LoggerFactory.getLogger( + SshCommandClient.class); + + public SshCommandClient(String hostName, String username, + byte[] privateKeyFile) throws JSchException + { + super(hostName, username, privateKeyFile); + } + + public SshCommandClient(String hostName, String username, + Identity privateKeyFile) throws JSchException + { + super(hostName, username, privateKeyFile); + } + + public SshCommandClient(String hostName, String userName, String keyFile, + String pass) throws JSchException + { + super(hostName, userName, keyFile, pass); + } + + public String executeCommand(String command) { + StringBuilder sb = new StringBuilder(); + try { + ChannelExec channelExec = (ChannelExec) getConnectedSession().openChannel( + "exec"); + + InputStream in = channelExec.getInputStream(); + + channelExec.setCommand(command); + channelExec.connect(); + + BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + String line; + + while ((line = reader.readLine()) != null) { + sb.append(line).append('\n'); + } + + int exitStatus = channelExec.getExitStatus(); + channelExec.disconnect(); + + if (exitStatus < 0) { + log.debug("Done, but exit status not set!"); + } + else if (exitStatus > 0) { + log.debug("Done, but with error!"); + } + else { + log.debug("Done!"); + } + } + catch (Exception e) { + log.error("Error: ", e); + throw new RuntimeException(e); + } + return sb.toString(); + } +}